Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions sbi/inference/posteriors/vi_posterior.py
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,10 @@ def train(
break
# Training finished:
self._trained_on = x

# Bind potential_fn to the trained x so it can be used for sampling/evaluation
self.potential_fn = self.potential_fn.bind(x)

if self._mode == "amortized":
warnings.warn(
"Switching from amortized to single-x mode. "
Expand Down
5 changes: 4 additions & 1 deletion sbi/inference/potentials/base_potential.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ def __init__(
"""
self.device = device
self.prior = prior
self.set_x(x_o)
if x_o is not None:
x_o = process_x(x_o).to(self.device)
self._x_o = x_o
self._x_is_iid = True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compared with set_x() we hard to _x_is_iid to True here. Since it's not a method argument, maybe True is a good default. We have to nesure though that it is set correctly in each inherited class. Because right now by calling super() in e.g. the likelihood potential, we will ahve x_is_iid=True by default

@Jocho-Smith Jocho-Smith Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right now by calling super() in e.g. the likelihood potential, we will ahve x_is_iid=True by default

This is exactly what is happening right now on the main branch. likelihood calls super without _x_is_iid : https://github.com/sbi-dev/sbi/blob/main/sbi/inference/potentials/likelihood_based_potential.py#L80

BasePotential.init always calls set_x without any iid information: https://github.com/sbi-dev/sbi/blob/main/sbi/inference/potentials/base_potential.py#L33

x_is_iid is set with default value True: https://github.com/sbi-dev/sbi/blob/main/sbi/inference/potentials/base_potential.py#L60


@abstractmethod
def __call__(self, theta: Tensor, track_gradients: bool = True) -> Tensor:
Expand Down
5 changes: 4 additions & 1 deletion sbi/inference/potentials/likelihood_based_potential.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
)
from sbi.sbi_types import TorchTransform
from sbi.utils.sbiutils import mcmc_transform
from sbi.utils.user_input_checks import process_x


def likelihood_estimator_based_potential(
Expand Down Expand Up @@ -102,7 +103,9 @@ def bind(self, x_o: Tensor, x_is_iid: bool = True) -> "LikelihoodBasedPotential"
x_o=None,
device=self.device,
)
bound.set_x(x_o, x_is_iid=x_is_iid)
x_o = process_x(x_o).to(self.device)
bound._x_o = x_o
bound._x_is_iid = x_is_iid
return bound

def __call__(self, theta: Tensor, track_gradients: bool = True) -> Tensor:
Expand Down
10 changes: 8 additions & 2 deletions sbi/inference/potentials/posterior_based_potential.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
within_support,
)
from sbi.utils.torchutils import ensure_theta_batched, infer_module_device
from sbi.utils.user_input_checks import process_x


def posterior_estimator_based_potential(
Expand Down Expand Up @@ -108,14 +109,19 @@ def bind(self, x_o: Tensor, x_is_iid: bool = False) -> "PosteriorBasedPotential"
x_o=None,
device=self.device,
)
bound.set_x(x_o, x_is_iid=x_is_iid)
x_o = process_x(x_o).to(self.device)
bound._x_o = x_o
bound._x_is_iid = x_is_iid
return bound

def set_x(self, x_o: Optional[Tensor], x_is_iid: Optional[bool] = False):
"""
Check the shape of the observed data and, if valid, set it.
"""
super().set_x(x_o, x_is_iid=x_is_iid)
if x_o is not None:
x_o = process_x(x_o).to(self.device)
self._x_o = x_o
self._x_is_iid = x_is_iid

def __call__(self, theta: Tensor, track_gradients: bool = True) -> Tensor:
r"""Returns the potential for posterior-based methods.
Expand Down
5 changes: 4 additions & 1 deletion sbi/inference/potentials/ratio_based_potential.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from sbi.sbi_types import TorchTransform
from sbi.utils.sbiutils import match_theta_and_x_batch_shapes, mcmc_transform
from sbi.utils.torchutils import atleast_2d
from sbi.utils.user_input_checks import process_x


def ratio_estimator_based_potential(
Expand Down Expand Up @@ -90,7 +91,9 @@ def bind(self, x_o: Tensor, x_is_iid: bool = True) -> "RatioBasedPotential":
x_o=None,
device=self.device,
)
bound.set_x(x_o, x_is_iid=x_is_iid)
x_o = process_x(x_o).to(self.device)
bound._x_o = x_o
bound._x_is_iid = x_is_iid
return bound

def __call__(self, theta: Tensor, track_gradients: bool = True) -> Tensor:
Expand Down
34 changes: 21 additions & 13 deletions sbi/inference/potentials/vector_field_potential.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from sbi.sbi_types import TorchTransform
from sbi.utils.sbiutils import mcmc_transform, within_support
from sbi.utils.torchutils import ensure_theta_batched
from sbi.utils.user_input_checks import process_x


class VectorFieldBasedPotential(BasePotential):
Expand Down Expand Up @@ -61,6 +62,8 @@ def __init__(
self.vector_field_estimator.eval()
self.iid_method = iid_method
self.iid_params = iid_params
self.guidance_method: Optional[str] = None
self.guidance_params: Optional[Dict[str, Any]] = None
self.neural_ode_backend = neural_ode_backend

neural_ode_kwargs = neural_ode_kwargs or {}
Expand Down Expand Up @@ -119,7 +122,10 @@ def set_x(
`IIDScoreFunction`.
ode_kwargs: Additional keyword arguments for the neural ODE.
"""
super().set_x(x_o, x_is_iid)
if x_o is not None:
x_o = process_x(x_o).to(self.device)
self._x_o = x_o
self._x_is_iid = x_is_iid
self.iid_method = iid_method or self.iid_method
self.iid_params = iid_params
self.guidance_method = guidance_method
Expand All @@ -145,22 +151,24 @@ def bind(
prior=self.prior,
x_o=None,
device=self.device,
iid_method=self.iid_method,
iid_params=self.iid_params,
iid_method=iid_method if iid_method is not None else self.iid_method,
iid_params=iid_params if iid_params is not None else self.iid_params,
neural_ode_backend=self.neural_ode_backend,
)
bound.neural_ode.params.update(self.neural_ode.params)
# Transfer learned neural ODE parameters (e.g., from training) to the bound
# potential. This preserves the trained flow model when conditioning on new x.
bound.set_x(
x_o,
x_is_iid=x_is_iid,
iid_method=iid_method,
iid_params=iid_params,
guidance_method=guidance_method,
guidance_params=guidance_params,
**ode_kwargs,
x_o = process_x(x_o).to(self.device)
bound._x_o = x_o
bound._x_is_iid = x_is_iid
bound.guidance_method = (
guidance_method if guidance_method is not None else self.guidance_method
)
bound.guidance_params = (
guidance_params if guidance_params is not None else self.guidance_params
)
if not x_is_iid and (bound._x_o is not None):
bound.flow = bound.rebuild_flow(**ode_kwargs)
elif bound._x_o is not None:
bound.flows = bound.rebuild_flows_for_batch(**ode_kwargs)
return bound

def __call__(
Expand Down
4 changes: 2 additions & 2 deletions sbi/samplers/vi/vi_divergence_optimizers.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ def generate_elbo_particles(
samples = self.q.rsample(torch.Size((num_samples,)))
log_q = self.q.log_prob(samples)

self.potential_fn.set_x(x_o)
self.potential_fn = self.potential_fn.bind(x_o)
log_potential = self.potential_fn(samples)
elbo = log_potential - log_q
return elbo
Expand Down Expand Up @@ -638,7 +638,7 @@ def _loss_q_proposal(self, x_o: Tensor) -> Tuple[Tensor, Tensor]:
if hasattr(self.q, "clear_cache"):
self.q.clear_cache()
logq = self.q.log_prob(samples)
self.potential_fn.set_x(x_o)
self.potential_fn = self.potential_fn.bind(x_o)
logp = self.potential_fn(samples)
with torch.no_grad():
logweights = logp - logq
Expand Down
17 changes: 9 additions & 8 deletions sbi/utils/torchutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,13 +677,14 @@ def _base_recursor(
_active: Object identities on the active recursion path. Used internally
to avoid infinite recursion in cyclic object graphs.
"""
if _active is None:
_active = set()
_active_holder: Optional[set[int]] = _active
if _active_holder is None:
_active_holder = set()

obj_id = id(obj)
if obj_id in _active:
if obj_id in _active_holder:
return
_active.add(obj_id)
_active_holder.add(obj_id)

if isinstance(obj, Module) and check(obj):
action(obj)
Expand All @@ -698,7 +699,7 @@ def _base_recursor(
key=k,
check=check,
action=action,
_active=_active,
_active=_active_holder,
)
elif isinstance(obj, type):
# Skip class/type objects to avoid modifying immutable C extension types
Expand All @@ -715,20 +716,20 @@ def _base_recursor(
key=k,
check=check,
action=action,
_active=_active,
_active=_active_holder,
)
elif isinstance(obj, (List, Tuple, Generator)):
new_obj = []
for o in obj:
if check(o):
new_obj.append(action(o))
else:
_base_recursor(o, check=check, action=action, _active=_active)
_base_recursor(o, check=check, action=action, _active=_active_holder)
new_obj.append(o)
if parent is not None and key is not None:
setattr(parent, key, type(obj)(new_obj)) # type: ignore

_active.remove(obj_id)
_active_holder.remove(obj_id)


def move_all_tensor_to_device(obj: object, device: Union[str, torch.device]) -> None:
Expand Down
7 changes: 3 additions & 4 deletions sbi/utils/user_input_checks_utils.py

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we seem to have the same modifications that we dicussed in #1943 before.

  • there is code duplication for __deepcopy__
  • I don't see where deepcopy is used and why it is necessary to introduce
  • we silently change the validate_args hard coded to False which seems to be a bug.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ouh sorry. I messed up the merge. Will fix it.

Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ def __init__(
event_shape=torch.Size(),
validate_args=None,
):
self.prior = prior
self.device = None
self.return_type = return_type
super().__init__(
batch_shape=batch_shape,
event_shape=event_shape,
Expand All @@ -189,10 +192,6 @@ def __init__(
),
)

self.prior = prior
self.device = None
self.return_type = return_type

def log_prob(self, value) -> Tensor:
return torch.as_tensor(
self.prior.log_prob(value),
Expand Down
Loading