Repository: sbi-dev/sbi

Issue:
ImportanceSamplingPosterior.sample is missing show_progress_bars argument. 
## Description:

Unlike other posterior classes in the SBI library, the `ImportanceSamplingPosterior` class `sample` method does not currently offer the option to enable or disable progress bars. This feature would be particularly useful for users who need to control output verbosity, especially when running simulations in environments where progress bars could clutter logs or are otherwise unnecessary.

https://github.com/sbi-dev/sbi/blob/3b1f34e3a5755f097652d8e54767881ad783d630/sbi/inference/posteriors/importance_posterior.py#L154-L162

The `sample` method internally decides to call either `_sir_sample`, which already includes a progress bar, or `_importance_sample`, which currently does not implement a progress bar. To maintain consistency across the library and to enhance user control over the interface, I suggest we introduce a parameter (e.g., `show_progress_bar`) in the `sample` method signature. This parameter should then control the display of progress bars in both `_sir_sample` and `_importance_sample` methods accordingly.

## Proposed Changes
- [ ] Implement progress bar in `_importance_sample`
- [x] Add a `show_progress_bar` boolean parameter to the `sample` method of `ImportanceSamplingPosterior`.
- [x] Ensure that this parameter controls the display of progress bars in both the `_sir_sample` and `_importance_sample` methods.

This enhancement would align `ImportanceSamplingPosterior` with the other posterior classes in terms of API consistency and usability.


Discussion:
Can I be assigned this issue? 🙏😄
Absolutely! Please feel free to create a PR. If you haven't already, check out the contribution guidelines 😊

Thanks a lot for tackling this! 🙏 

Resolving patch:
diff --git a/sbi/inference/posteriors/importance_posterior.py b/sbi/inference/posteriors/importance_posterior.py
--- a/sbi/inference/posteriors/importance_posterior.py
+++ b/sbi/inference/posteriors/importance_posterior.py
@@ -160,12 +160,18 @@ def sample(
         oversampling_factor: int = 32,
         max_sampling_batch_size: int = 10_000,
         sample_with: Optional[str] = None,
+        show_progress_bars: bool = False,
     ) -> Union[Tensor, Tuple[Tensor, Tensor]]:
         """Return samples from the approximate posterior distribution.
 
         Args:
             sample_shape: _description_
             x: _description_
+            oversampling_factor: Number of proposed samples from which only one is
+                selected based on its importance weight.
+            max_sampling_batch_size: The batch size of samples being drawn from the
+                proposal at every iteration.
+            show_progress_bars: Whether to show a progressbar during sampling.
         """
         if sample_with is not None:
             raise ValueError(
@@ -181,6 +187,7 @@ def sample(
                 sample_shape,
                 oversampling_factor=oversampling_factor,
                 max_sampling_batch_size=max_sampling_batch_size,
+                show_progress_bars=show_progress_bars,
             )
         elif self.method == "importance":
             return self._importance_sample(sample_shape)
@@ -190,6 +197,7 @@ def sample(
     def _importance_sample(
         self,
         sample_shape: Shape = torch.Size(),
+        show_progress_bars: bool = False,
     ) -> Tuple[Tensor, Tensor]:
         """Returns samples from the proposal and log of their importance weights.
 
@@ -197,6 +205,7 @@ def _importance_sample(
             sample_shape: Desired shape of samples that are drawn from posterior.
             sample_with: This argument only exists to keep backward-compatibility with
                 `sbi` v0.17.2 or older. If it is set, we instantly raise an error.
+            show_progress_bars: Whether to show sampling progress monitor.
 
         Returns:
             Samples and logarithm of corresponding importance weights.
@@ -206,6 +215,7 @@ def _importance_sample(
             self.potential_fn,
             proposal=self.proposal,
             num_samples=num_samples,
+            show_progress_bars=show_progress_bars,
         )
 
         samples = samples.reshape((*sample_shape, -1)).to(self._device)
diff --git a/sbi/samplers/importance/importance_sampling.py b/sbi/samplers/importance/importance_sampling.py
--- a/sbi/samplers/importance/importance_sampling.py
+++ b/sbi/samplers/importance/importance_sampling.py
@@ -9,6 +9,7 @@ def importance_sample(
     potential_fn,
     proposal,
     num_samples: int = 1,
+    show_progress_bars: bool = False,
 ) -> Tuple[Tensor, Tensor]:
     """Returns samples from proposal and log(importance weights).
 
@@ -20,7 +21,11 @@ def importance_sample(
     Returns:
         Samples and logarithm of importance weights.
     """
-    samples = proposal.sample((num_samples,))
+    # Use progress bars when available (e.g., for multi-round proposals)
+    try:
+        samples = proposal.sample((num_samples,), show_progress_bar=show_progress_bars)
+    except TypeError:
+        samples = proposal.sample((num_samples,))
 
     potential_logprobs = potential_fn(samples)
     proposal_logprobs = proposal.log_prob(samples)


Test patch:
diff --git a/tests/linearGaussian_snle_test.py b/tests/linearGaussian_snle_test.py
--- a/tests/linearGaussian_snle_test.py
+++ b/tests/linearGaussian_snle_test.py
@@ -460,50 +460,46 @@ def test_api_snl_sampling_methods(
     else:
         prior = BoxUniform(-1.0 * ones(num_dim), ones(num_dim))
 
-    # Why do we have this if-case? Only the `MCMCPosterior` uses the `init_strategy`.
-    # Thus, we would not like to run, e.g., VI with all init_strategies, but only once
-    # (namely with `init_strategy=proposal`).
-    if sample_with == "mcmc" or init_strategy == "proposal":
-        simulator = diagonal_linear_gaussian
+    simulator = diagonal_linear_gaussian
 
-        inference = SNLE(show_progress_bars=False)
+    inference = SNLE(show_progress_bars=False)
 
-        theta, x = simulate_for_sbi(
-            simulator, prior, num_simulations, simulation_batch_size=1000
+    theta, x = simulate_for_sbi(
+        simulator, prior, num_simulations, simulation_batch_size=1000
+    )
+    likelihood_estimator = inference.append_simulations(theta, x).train(
+        max_num_epochs=5
+    )
+    potential_fn, theta_transform = likelihood_estimator_based_potential(
+        prior=prior, likelihood_estimator=likelihood_estimator, x_o=x_o
+    )
+    if sample_with == "rejection":
+        posterior = RejectionPosterior(potential_fn=potential_fn, proposal=prior)
+    elif (
+        "slice" in sampling_method
+        or "nuts" in sampling_method
+        or "hmc" in sampling_method
+    ):
+        posterior = MCMCPosterior(
+            potential_fn,
+            proposal=prior,
+            theta_transform=theta_transform,
+            method=sampling_method,
+            init_strategy=init_strategy,
+            **mcmc_params_fast,
         )
-        likelihood_estimator = inference.append_simulations(theta, x).train(
-            max_num_epochs=5
+    elif sample_with == "importance":
+        posterior = ImportanceSamplingPosterior(
+            potential_fn,
+            proposal=prior,
+            theta_transform=theta_transform,
         )
-        potential_fn, theta_transform = likelihood_estimator_based_potential(
-            prior=prior, likelihood_estimator=likelihood_estimator, x_o=x_o
+    else:
+        posterior = VIPosterior(
+            potential_fn,
+            theta_transform=theta_transform,
+            vi_method=sampling_method,
         )
-        if sample_with == "rejection":
-            posterior = RejectionPosterior(potential_fn=potential_fn, proposal=prior)
-        elif (
-            "slice" in sampling_method
-            or "nuts" in sampling_method
-            or "hmc" in sampling_method
-        ):
-            posterior = MCMCPosterior(
-                potential_fn,
-                proposal=prior,
-                theta_transform=theta_transform,
-                method=sampling_method,
-                init_strategy=init_strategy,
-                **mcmc_params_fast,
-            )
-        elif sample_with == "importance":
-            posterior = ImportanceSamplingPosterior(
-                potential_fn,
-                proposal=prior,
-                theta_transform=theta_transform,
-            )
-        else:
-            posterior = VIPosterior(
-                potential_fn,
-                theta_transform=theta_transform,
-                vi_method=sampling_method,
-            )
-            posterior.train(max_num_iters=10)
+        posterior.train(max_num_iters=10)
 
-        posterior.sample(sample_shape=(num_samples,))
+    posterior.sample(sample_shape=(num_samples,), show_progress_bars=False)
diff --git a/tests/linearGaussian_snre_test.py b/tests/linearGaussian_snre_test.py
--- a/tests/linearGaussian_snre_test.py
+++ b/tests/linearGaussian_snre_test.py
@@ -419,4 +419,4 @@ def test_api_sre_sampling_methods(
         )
         posterior.train(max_num_iters=10)
 
-    posterior.sample(sample_shape=(num_samples,))
+    posterior.sample(sample_shape=(num_samples,), show_progress_bars=False)