@@ -181,7 +181,7 @@ class RedisEvalParallelSampler(RedisSamplerBase):
181181 lead to a worse performance, especially if evaluation is costly
182182 compared to simulation, because evaluation happens sequentially on the
183183 main thread.
184- Only effective if `look_ahead= True`.
184+ Only effective if `look_ahead is True`.
185185 max_n_eval_look_ahead_factor:
186186 In delayed evaluation, only this factor times the previous number of
187187 samples are generated, afterwards the workers wait.
@@ -194,6 +194,21 @@ class RedisEvalParallelSampler(RedisSamplerBase):
194194 If not, then the sampler only waits for all simulations that were
195195 started prior to the last started particle of the first `n`
196196 acceptances.
197+ Waiting for all should not be needed, this is for studying purposes.
198+ adapt_look_ahead_proposal:
199+ In look-ahead mode, adapt the preliminary proposal based on previous
200+ acceptances.
201+ In theory, as long as proposal >> prior, everything is fine.
202+ However, in practice, given a finite sample size, in some cases the
203+ preliminary proposal may be biased towards earlier-accepted particles,
204+ which can induce a similar bias in the next accepted population.
205+ Thus, if any parameter dependent simulation time heterogeneity is to be
206+ expected, i.e. if different plausible parameter space regions come
207+ with different simulation times, then this flag should be set to False.
208+ If no such heterogeneity is to be expected, this flag can be set to
209+ True, which can result in improved performance due to a more tailored
210+ proposal distribution.
211+ Only effective if `look_ahead is True`.
197212 log_file:
198213 A file for a dedicated sampler history. Updated in each iteration.
199214 This log file is complementary to the logging realized via the
@@ -210,6 +225,7 @@ def __init__(
210225 look_ahead_delay_evaluation : bool = True ,
211226 max_n_eval_look_ahead_factor : float = 10.0 ,
212227 wait_for_all_samples : bool = False ,
228+ adapt_look_ahead_proposal : bool = False ,
213229 log_file : str = None ,
214230 ):
215231 super ().__init__ (
@@ -220,6 +236,7 @@ def __init__(
220236 self .look_ahead_delay_evaluation : bool = look_ahead_delay_evaluation
221237 self .max_n_eval_look_ahead_factor : float = max_n_eval_look_ahead_factor
222238 self .wait_for_all_samples : bool = wait_for_all_samples
239+ self .adapt_look_ahead_proposal : bool = adapt_look_ahead_proposal
223240
224241 def sample_until_n_accepted (
225242 self ,
@@ -546,6 +563,7 @@ def maybe_start_next_generation(
546563 t = t + 1 ,
547564 population = population ,
548565 delay_evaluation = self .look_ahead_delay_evaluation ,
566+ adapt_proposal = self .adapt_look_ahead_proposal ,
549567 ana_vars = ana_vars ,
550568 )
551569
@@ -591,6 +609,12 @@ def create_sample(self, id_results: List[Tuple], n: int) -> Sample:
591609 for j in range (n ):
592610 sample += results [j ]
593611
612+ # check number of acceptances
613+ if (n_accepted := sample .n_accepted ) != n :
614+ raise AssertionError (
615+ f"Expected { n } accepted particles but got { n_accepted } "
616+ )
617+
594618 return sample
595619
596620 def check_analysis_variables (
@@ -604,7 +628,7 @@ def check_analysis_variables(
604628 # nothing to be done
605629 return
606630
607- def check_bad (var ):
631+ def _check_bad (var ):
608632 """Check whether a component is incompatible."""
609633 # do not check for `requires_calibration()`, because in the first
610634 # iteration we do not look ahead
@@ -615,15 +639,16 @@ def check_bad(var):
615639 "sampler's `look_ahead_delay_evaluation` flag."
616640 )
617641
618- check_bad (acceptor )
619- check_bad (distance_function )
620- check_bad (eps )
642+ _check_bad (acceptor )
643+ _check_bad (distance_function )
644+ _check_bad (eps )
621645
622646
623647def create_preliminary_simulate_one (
624648 t ,
625649 population ,
626650 delay_evaluation : bool ,
651+ adapt_proposal : bool ,
627652 ana_vars : AnalysisVars ,
628653) -> Callable :
629654 """Create a preliminary simulate_one function for generation `t`.
@@ -636,24 +661,36 @@ def create_preliminary_simulate_one(
636661
637662 Parameters
638663 ----------
639- t: The time index for which to create the function (i.e. call with t+1).
640- population: The preliminary population.
641- delay_evaluation: Whether to delay evaluation.
642- ana_vars: The analysis variables.
664+ t:
665+ The time index for which to create the function (i.e. call with t+1).
666+ population:
667+ The preliminary population.
668+ delay_evaluation:
669+ Whether to delay evaluation.
670+ adapt_proposal:
671+ Whether to fit the proposal distribution to the new population.
672+ ana_vars:
673+ The analysis variables.
643674
644675 Returns
645676 -------
646677 simulate_one: The preliminary sampling function.
647678 """
648679 model_probabilities = population .get_model_probabilities ()
649680
650- # create deep copy of the transition function
651- transitions = copy .deepcopy (ana_vars .transitions )
652-
653- # fit transition
654- for m in population .get_alive_models ():
655- parameters , w = population .get_distribution (m )
656- transitions [m ].fit (parameters , w )
681+ # set proposal distribution
682+ transitions = ana_vars .transitions
683+ if adapt_proposal :
684+ # create deep copy of the transition function
685+ transitions = copy .deepcopy (transitions )
686+ # fit transitions
687+ for m in population .get_alive_models ():
688+ parameters , w = population .get_distribution (m )
689+ transitions [m ].fit (parameters , w )
690+ elif t == 1 :
691+ # at t=0, the prior is used for sampling
692+ # (and the transition not fitted yet)
693+ transitions = ana_vars .parameter_priors
657694
658695 return create_simulate_function (
659696 t = t ,
@@ -753,6 +790,13 @@ def post_check_acceptance(
753790def self_normalize_within_subpopulations (sample : Sample , n : int ) -> Sample :
754791 """Applies subpopulation-wise self-normalization of samples, in-place.
755792
793+ The weights are adjusted per proposal id, such that all particles
794+ belonging to one proposal id have a total weight proportional to the
795+ effective sample size of the sub-population.
796+ This defines the relative importances of all particles in the accepted
797+ population in a reasonabler manner.
798+ Conceptually, also hter normalizations are possible.
799+
756800 Parameters
757801 ----------
758802 sample: The population to be returned by the sampler.
0 commit comments