Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
89bc9a2
Add .bind() method to potentials and fix _base_recursor scoping
Jocho-Smith Jul 22, 2026
3e52f56
Add .bind() method to potentials and fix _base_recursor scoping
Jocho-Smith Jul 22, 2026
30459d9
Merge branch 'bind-method-clean' of github.com:jocho-smith/sbi into b…
Jocho-Smith Jul 22, 2026
8fdc10c
fix attempt with bound.neural_ode.params.update
Jocho-Smith Jul 22, 2026
968b58b
checkpoint after full bind replacement of set_x
Jocho-Smith Jul 22, 2026
84c4731
Merge branch 'gsoc-2026' into bind-method-clean
Jocho-Smith Jul 22, 2026
ee2adfa
fixup:slightly wrong last merge
Jocho-Smith Jul 22, 2026
0fe9359
3 things: remove set_x from BasePotential.__init__; replace overlooke…
Jocho-Smith Jul 22, 2026
e8444c5
fix attempt: VI training broken due to bind
Jocho-Smith Jul 22, 2026
81b0709
fixup: AttributeError: 'VectorFieldBasedPotential' object has no attr…
Jocho-Smith Jul 22, 2026
3292830
set_x-bind-swap: set_x now internally uses bind to implement a statef…
Jocho-Smith Jul 22, 2026
6e9020e
fixup: flows and flow
Jocho-Smith Jul 22, 2026
43e46b8
fix: some places still used set_x inside bind -> recursion loop...
Jocho-Smith Jul 22, 2026
5035bd1
Merge upstream/gsoc-2026 into test-set-x-deprecation
Jocho-Smith Jul 22, 2026
12944af
reverted user_input_checks_utils changes; moved imports up; fixed iid…
Jocho-Smith Jul 23, 2026
0a15ef2
moved imports up; changed warnings to include a note on future deletion
Jocho-Smith Jul 23, 2026
63845fe
Merge branch 'gsoc-2026' into test-set-x-deprecation
Jocho-Smith Jul 23, 2026
5247683
moved warnings import up
Jocho-Smith Jul 23, 2026
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
24 changes: 16 additions & 8 deletions sbi/inference/posteriors/ensemble_posterior.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,14 +479,22 @@ def allow_iid_x(self) -> bool:
)

def set_x(self, x_o: Optional[Tensor]):
"""Check the shape of the observed data and, if valid, set it."""
if x_o is not None:
x_o = process_x(x_o).to( # type: ignore
self.device
)
self._x_o = x_o
for comp_potential in self.potential_fns:
comp_potential.set_x(x_o)
Comment thread
dgedon marked this conversation as resolved.
"""Check the shape of the observed data and, if valid, set it.

DEPRECATED: Use bind() instead. This method delegates to bind() internally.
Comment thread
dgedon marked this conversation as resolved.
It will be removed in a future release.
"""
import warnings

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.

imports on file level

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.

Not yet addressed. If you decide against it, please let me know why or resolve the comment

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.

ah sorry, I overlooked that.


warnings.warn(
"set_x() is deprecated and will be removed in a future release. "
"Use bind() instead.",
FutureWarning,
stacklevel=2,
)
bound = self.bind(x_o)
self._x_o = bound._x_o
self.potential_fns = bound.potential_fns

def bind(
self,
Expand Down
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
24 changes: 18 additions & 6 deletions 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

@abstractmethod
def __call__(self, theta: Tensor, track_gradients: bool = True) -> Tensor:
Expand All @@ -53,11 +56,20 @@ def x_is_iid(self) -> bool:
)

def set_x(self, x_o: Optional[Tensor], x_is_iid: Optional[bool] = True):
"""Check the shape of the observed data and, if valid, set it."""
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
"""Check the shape of the observed data and, if valid, set it.

DEPRECATED: Use bind() instead. This method delegates to bind() internally.
"""
import warnings

warnings.warn(
"set_x() is deprecated, use bind() instead",
FutureWarning,
stacklevel=2,
)
bound = self.bind(x_o, x_is_iid=x_is_iid)
self._x_o = bound._x_o
self._x_is_iid = bound._x_is_iid

@property
def x_o(self) -> 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
23 changes: 19 additions & 4 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,28 @@ 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.

DEPRECATED: Use bind() instead. This method delegates to bind() internally.
It will be removed in a future release.
"""
Check the shape of the observed data and, if valid, set it.
"""
super().set_x(x_o, x_is_iid=x_is_iid)
import warnings

warnings.warn(
"set_x() is deprecated and will be removed in a future release. "
"Use bind() instead.",
FutureWarning,
stacklevel=2,
)
bound = self.bind(x_o, x_is_iid=x_is_iid)
self._x_o = bound._x_o
self._x_is_iid = bound._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
67 changes: 46 additions & 21 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 @@ -110,6 +113,8 @@ def set_x(

Rebuilds the continuous normalizing flow if the observed data is set.

DEPRECATED: Use bind() instead. This method delegates to bind() internally.
It will be removed in a future release.
Args:
x_o: The observed data.
x_is_iid: Whether the observed data is IID (if batch_dim>1).
Expand All @@ -119,15 +124,33 @@ def set_x(
`IIDScoreFunction`.
ode_kwargs: Additional keyword arguments for the neural ODE.
"""
super().set_x(x_o, x_is_iid)
self.iid_method = iid_method or self.iid_method
self.iid_params = iid_params
self.guidance_method = guidance_method
self.guidance_params = guidance_params
if not x_is_iid and (self._x_o is not None):
self.flow = self.rebuild_flow(**ode_kwargs)
elif self._x_o is not None:
self.flows = self.rebuild_flows_for_batch(**ode_kwargs)
import warnings

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.

import on file level


warnings.warn(
"set_x() is deprecated and will be removed in a future release. "
"Use bind() instead.",
FutureWarning,
stacklevel=2,
)
bound = self.bind(
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,
)
self._x_o = bound._x_o
self._x_is_iid = bound._x_is_iid
self.iid_method = bound.iid_method
self.iid_params = bound.iid_params
self.guidance_method = bound.guidance_method
self.guidance_params = bound.guidance_params
if not x_is_iid and (bound._x_o is not None):
self.flow = bound.flow
elif bound._x_o is not None:
self.flows = bound.flows

def bind(
self,
Expand All @@ -145,22 +168,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
20 changes: 14 additions & 6 deletions sbi/utils/conditional_density_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
MixtureDensityEstimator,
)
from sbi.utils.torchutils import ensure_theta_batched
from sbi.utils.user_input_checks import process_x


def compute_corrcoeff(probs: Tensor, limits: Tensor):
Expand Down Expand Up @@ -418,11 +417,20 @@ def x_is_iid(self) -> bool:
)

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

DEPRECATED: Use bind() instead. This method delegates to bind() internally.
"""
import warnings

warnings.warn(
"set_x() is deprecated, use bind() instead",
FutureWarning,
stacklevel=2,
)
bound = self.bind(x_o, x_is_iid=x_is_iid)
self._x_is_iid = bound._x_is_iid
self.potential_fn = bound.potential_fn

def bind(self, x_o: Tensor, x_is_iid: bool = True) -> "ConditionedPotential":
"""Create new potential with x bound, without mutable state."""
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.

same comments as on the last PR. Please merge first

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
Comment on lines +184 to +186

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.

Why was that moved from like 10 lines lower?

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.

the line super().init(...) below had strange side effects with the new bind definition. Moving it before that fixed it.

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
Loading