Skip to content

Add explicit init() to BasePotential and NeuralPosterior#1829

Closed
patelshivani2283-lab wants to merge 7 commits intosbi-dev:mainfrom
patelshivani2283-lab:feature/init-vector-field
Closed

Add explicit init() to BasePotential and NeuralPosterior#1829
patelshivani2283-lab wants to merge 7 commits intosbi-dev:mainfrom
patelshivani2283-lab:feature/init-vector-field

Conversation

@patelshivani2283-lab
Copy link
Copy Markdown
Contributor

@patelshivani2283-lab patelshivani2283-lab commented Mar 31, 2026

Summary

Added an _initialized flag and an init() method to BasePotential and NeuralPosterior.

What changed

You can now explicitly initialize the posterior or potential by calling init().
At the same time, things will still work as before if init() is not called.

Why

In some cases, using the posterior or potential without proper initialization could lead to subtle issues.
This change makes initialization clearer and more explicit, while keeping everything backward compatible.

Example

posterior = NeuralPosterior(potential_fn)

# optional explicit initialization
posterior.init().sample()

# still works without calling init()
posterior.sample()

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 31, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.28%. Comparing base (937efc2) to head (ce5929d).
⚠️ Report is 48 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1829      +/-   ##
==========================================
- Coverage   88.54%   86.28%   -2.27%     
==========================================
  Files         137      143       +6     
  Lines       11515    17808    +6293     
==========================================
+ Hits        10196    15365    +5169     
- Misses       1319     2443    +1124     
Flag Coverage Δ
fast 82.76% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sbi/inference/posteriors/base_posterior.py 86.36% <100.00%> (+1.17%) ⬆️
sbi/inference/potentials/base_potential.py 85.86% <100.00%> (+5.10%) ⬆️

... and 76 files with indirect coverage changes

@patelshivani2283-lab
Copy link
Copy Markdown
Contributor Author

Hi! This PR is ready for review.
I added an init() step while keeping things working as before.
Let me know what you think

Copy link
Copy Markdown

@JiwaniZakir JiwaniZakir left a comment

Choose a reason for hiding this comment

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

The hasattr(self.potential_fn, "init") guard in base_posterior.py's init() is now vacuously true since BasePotential always defines init() — this check no longer provides meaningful protection and will silently do the wrong thing if a non-BasePotential potential function is ever passed that lacks init(). The lazy auto-initialization in both potential() (base_posterior.py line 104–105) and __call__ (base_potential.py line 144–145) also undermines the stated goal of explicit initialization: callers who skip init() will never notice, making the explicit method mostly ceremonial rather than enforcing a required lifecycle step. If the intent is truly to require initialization before use, consider raising an error instead of silently initializing, or dropping the explicit init() pattern entirely in favor of always-lazy init. Additionally, if init() is designed to be overridden in subclasses (e.g., to load model weights or move tensors to a device), the implicit call inside __call__ could silently swallow expensive repeated work if _initialized is somehow reset; a more defensive pattern would be to only expose the lazy path and remove the public init(), or vice versa — pick one and be consistent across both files.

@patelshivani2283-lab
Copy link
Copy Markdown
Contributor Author

Thanks for the feedback!

I initially enforced explicit initialization by raising an error, but this caused failures in existing tests. I've now reverted to lazy initialization to preserve backward compatibility, while still supporting explicit init().

I also removed the hasattr(...) check as suggested.

Happy to adjust further if a stricter approach is preferred.

@patelshivani2283-lab
Copy link
Copy Markdown
Contributor Author

Thanks for the detailed feedback!

I initially tried enforcing explicit initialization by raising an error,
but this caused multiple test failures, which suggests that the current
codebase relies on lazy initialization.

To keep things backward compatible, I restored lazy initialization while
still supporting an explicit init() method for clarity.

I also removed the hasattr(...) check as suggested.

Please let me know if you'd prefer moving fully to explicit initialization
in a separate change, as that would likely require updating existing usage
and tests.

@janfb
Copy link
Copy Markdown
Contributor

janfb commented May 8, 2026

Hi @patelshivani2283-lab, thanks for the iteration on this!

I think @JiwaniZakir's point is right: with the lazy auto-init in potential() and __call__, calling init() is never actually required, and BasePotential.init() doesn't do anything beyond setting the flag. So the new method is kind of ceremonial :)

But more generally, the underlying design question belongs to #1783 and our GSoC 2026 P3 project, which will redesign the potential function API end-to-end (replacing the current set_x() / _x_o pattern). And we will find a general solution to #1783 when working on the GSoC student, i.e., adding a partial solution here would be counterproductive I believe.

Thus, I'll close this PR, but would be happy to see your work on #1828!

Thanks for your efforts 🙏
Jan

@janfb janfb closed this May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants