jflows 0.5.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1069 @@
1
+ """Pure adaptive-staging and fixed-schedule Boltzmann computations.
2
+
3
+ The controller calls only the public direct trainers. It contains no file,
4
+ manifest, resume, or artifact logic; ``jflows.boltzmann.write`` and
5
+ ``jflows.boltzmann.load`` handle complete-stage persistence separately.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import time
11
+
12
+ import equinox as eqx
13
+ import jax
14
+ import jax.numpy as jnp
15
+ import numpy as np
16
+ from jax import Array
17
+
18
+ from ..flow import Flow
19
+ from ..potential import Potential, linear_combination
20
+ from ..utils.anneal import sequential_monte_carlo
21
+ from ..utils.metrics import (
22
+ compute_ESS_log,
23
+ importance_weights_log,
24
+ linear_weights_from_log,
25
+ resample,
26
+ )
27
+ from ..utils.rejuvenation import langevin
28
+ from ..train import (
29
+ Monitor,
30
+ _training_identity,
31
+ train_forward_KL_G,
32
+ train_forward_KLX_G,
33
+ train_forward_KLXX_G,
34
+ train_reverse_KL_F,
35
+ )
36
+
37
+ __all__ = [
38
+ "boltzmann_identity",
39
+ "boltzmann_forward_KL_G",
40
+ "boltzmann_forward_KL_G_fixed",
41
+ "boltzmann_forward_KLX_G",
42
+ "boltzmann_forward_KLX_G_fixed",
43
+ "boltzmann_forward_KLXX_G",
44
+ "boltzmann_forward_KLXX_G_fixed",
45
+ "boltzmann_reverse_KL_F",
46
+ "boltzmann_reverse_KL_F_fixed",
47
+ ]
48
+
49
+ _OBJECTIVES = {
50
+ "reverse_kl": ("F", 101),
51
+ "forward_kl": ("G", 102),
52
+ "forward_klx": ("G", 103),
53
+ "forward_klxx": ("G", 104),
54
+ }
55
+ _POLICY = {
56
+ "t_safe": 0.2,
57
+ "shrink_factor": 0.7,
58
+ "enlarge_factor": 1.5,
59
+ "tau_smc": 0.0,
60
+ "tau_ess": 0.6,
61
+ "t_tol": 0.01,
62
+ "max_stages": 30,
63
+ "max_retry": 6,
64
+ }
65
+ _SELECTION_PROPOSAL_LIMIT = 60
66
+
67
+
68
+ def _adaptive_policy(values):
69
+ policy = dict(_POLICY)
70
+ if values:
71
+ policy.update(values)
72
+ return policy
73
+ _importance_kernel = eqx.filter_jit(importance_weights_log)
74
+ _identity_kernel = eqx.filter_jit(lambda y, source, target: source(y) - target(y))
75
+
76
+
77
+ def _chunked_log_weights(samples, source, target, flow, direction, chunks):
78
+ """Evaluate trained-flow log weights in sequential device chunks."""
79
+ result = jnp.concatenate([
80
+ _importance_kernel(part, source, target, flow, direction)
81
+ for part in jnp.array_split(samples, chunks, axis=0)
82
+ ])
83
+ return jax.block_until_ready(result)
84
+
85
+
86
+ def _identity_log_weights(samples, source, target, chunks):
87
+ """Evaluate exact-identity log weights in sequential device chunks."""
88
+ result = jnp.concatenate([
89
+ _identity_kernel(part, source, target)
90
+ for part in jnp.array_split(samples, chunks, axis=0)
91
+ ])
92
+ return jax.block_until_ready(result)
93
+
94
+
95
+ def _advance_stage_samples(
96
+ key_resample,
97
+ key_mc,
98
+ samples,
99
+ log_weights,
100
+ flow,
101
+ direction,
102
+ target,
103
+ mc_dt,
104
+ mc_steps,
105
+ mc_adjust,
106
+ chunks,
107
+ ):
108
+ """Push, resample, and rejuvenate one accepted stage population."""
109
+ push = flow.__call__ if direction == "F" else flow.inv
110
+ proposal = jnp.concatenate([
111
+ push(part) for part in jnp.array_split(samples, chunks, axis=0)
112
+ ])
113
+ advanced = resample(
114
+ key_resample,
115
+ proposal,
116
+ linear_weights_from_log(log_weights),
117
+ N=samples.shape[0],
118
+ )
119
+ return langevin(
120
+ key_mc,
121
+ advanced,
122
+ target,
123
+ dt=mc_dt,
124
+ steps=mc_steps,
125
+ adjust=mc_adjust,
126
+ chunks=chunks,
127
+ )
128
+
129
+
130
+ def _operation_key(base_key, namespace, stage, attempt=0, index=0):
131
+ """Derive one deterministic key for a stage operation."""
132
+ key = jax.random.fold_in(base_key, namespace)
133
+ key = jax.random.fold_in(key, stage)
134
+ key = jax.random.fold_in(key, attempt)
135
+ return jax.random.fold_in(key, index)
136
+
137
+
138
+ def _select_adaptive_endpoint(
139
+ *,
140
+ base_key,
141
+ stage,
142
+ samples,
143
+ source,
144
+ target,
145
+ t_start,
146
+ t_end,
147
+ policy,
148
+ ladder,
149
+ mc_dt,
150
+ mc_steps,
151
+ mc_adjust,
152
+ chunks,
153
+ emit,
154
+ ):
155
+ """Apply the optional SMC gate to one proposed stage endpoint."""
156
+ if policy["tau_smc"] == 0.0:
157
+ return t_end, [{
158
+ "index": 0,
159
+ "t_start": t_start,
160
+ "t_end": t_end,
161
+ "status": "skipped",
162
+ "reason": "tau_smc_zero",
163
+ "validation_sample_count": int(samples.shape[0]),
164
+ }], True
165
+ history = []
166
+ source_bridge = linear_combination(
167
+ [target, source], [t_start, 1.0 - t_start]
168
+ )
169
+ for index in range(_SELECTION_PROPOSAL_LIMIT):
170
+ target_bridge = linear_combination(
171
+ [target, source], [t_end, 1.0 - t_end]
172
+ )
173
+ key = _operation_key(base_key, 201, stage, index=index)
174
+ _, per_level = sequential_monte_carlo(
175
+ key,
176
+ samples,
177
+ source_bridge,
178
+ target_bridge,
179
+ ladder=ladder,
180
+ mc_dt=mc_dt,
181
+ mc_steps=mc_steps,
182
+ adjust=mc_adjust,
183
+ chunks=chunks,
184
+ )
185
+ per_level = jax.block_until_ready(per_level)
186
+ minimum = float(jnp.min(per_level))
187
+ accepted = minimum >= policy["tau_smc"]
188
+ history.append({
189
+ "index": index + 1,
190
+ "t_start": t_start,
191
+ "t_end": t_end,
192
+ "smc_ess": np.asarray(per_level).tolist(),
193
+ "minimum_smc_ess": minimum,
194
+ "decision": "accepted" if accepted else "shrink",
195
+ "validation_sample_count": int(samples.shape[0]),
196
+ })
197
+ emit(
198
+ f"[stage {stage:03d} | t: {t_start:.6f} -> {t_end:.6f}] "
199
+ f"selection min SMC ESS={minimum:.4f} "
200
+ f"({'accept' if accepted else 'shrink'})"
201
+ )
202
+ if accepted:
203
+ return t_end, history, True
204
+ next_t = t_start + policy["shrink_factor"] * (t_end - t_start)
205
+ if not t_start < next_t < t_end:
206
+ return t_end, history, False
207
+ t_end = next_t
208
+ return t_end, history, False
209
+
210
+
211
+ def _stage_monitor(monitor, stage, attempt):
212
+ """Add stage context to an existing monitor without changing its sink."""
213
+ if monitor is None:
214
+ return None
215
+ return Monitor(
216
+ monitor.every,
217
+ f"{monitor.prefix}[stage {stage:03d} attempt {attempt:02d}] ",
218
+ monitor.printer,
219
+ )
220
+
221
+
222
+ def _train_attempt(
223
+ objective,
224
+ samples,
225
+ source,
226
+ target,
227
+ flow,
228
+ *,
229
+ pool_size,
230
+ batch_size,
231
+ train_steps,
232
+ lr,
233
+ ladder,
234
+ mc_dt,
235
+ mc_steps,
236
+ mc_adjust,
237
+ chunks,
238
+ monitor,
239
+ seed,
240
+ checkpoint,
241
+ u_clip,
242
+ g_clip,
243
+ coeff_lambda,
244
+ coeff_alpha,
245
+ coeff_beta,
246
+ melt,
247
+ opt_dt,
248
+ opt_steps,
249
+ t_start,
250
+ t_end,
251
+ ):
252
+ """Dispatch one objective to its public direct trainer."""
253
+ common = dict(
254
+ initialize_from_identity=False,
255
+ t_start=t_start,
256
+ t_end=t_end,
257
+ )
258
+ if objective == "reverse_kl":
259
+ return train_reverse_KL_F(
260
+ samples, source, target, flow, batch_size, train_steps, lr,
261
+ mc_dt, mc_steps, mc_adjust, monitor, seed, checkpoint, **common,
262
+ )
263
+ if objective == "forward_kl":
264
+ return train_forward_KL_G(
265
+ samples, source, target, flow, batch_size, train_steps, lr,
266
+ ladder, mc_dt, mc_steps, mc_adjust, monitor, seed, checkpoint,
267
+ u_clip, g_clip, **common,
268
+ )
269
+ if objective == "forward_klx":
270
+ return train_forward_KLX_G(
271
+ samples, source, target, flow, batch_size, train_steps, lr,
272
+ ladder, mc_dt, mc_steps, coeff_lambda, mc_adjust, monitor, seed,
273
+ checkpoint, u_clip, g_clip, **common,
274
+ )
275
+ if objective == "forward_klxx":
276
+ return train_forward_KLXX_G(
277
+ samples, source, target, flow, pool_size, batch_size, train_steps,
278
+ lr, ladder, melt, opt_dt, opt_steps, mc_dt, mc_steps,
279
+ coeff_lambda, coeff_alpha, coeff_beta, mc_adjust, monitor, seed,
280
+ checkpoint, u_clip, g_clip, chunks=chunks, **common,
281
+ )
282
+ def _stage_record(
283
+ *,
284
+ t_start,
285
+ t_end,
286
+ selected,
287
+ selected_flow,
288
+ continuation_flow,
289
+ sample_count,
290
+ t_history,
291
+ batch_ess_history,
292
+ trained_ess_history,
293
+ identity_ess_history,
294
+ status_history,
295
+ selection_history,
296
+ elapsed_seconds,
297
+ ):
298
+ """Build one accepted in-memory stage record."""
299
+ return {
300
+ "t": float(t_end),
301
+ "t_start": float(t_start),
302
+ "valid_selected_ess": float(
303
+ max(trained_ess_history[-1], identity_ess_history[-1])
304
+ ),
305
+ "valid_trained_ess": float(trained_ess_history[-1]),
306
+ "valid_identity_ess": float(identity_ess_history[-1]),
307
+ "valid_sample_count": int(sample_count),
308
+ "selected": selected,
309
+ "flow": selected_flow,
310
+ "continuation_flow": continuation_flow,
311
+ "t_hist": jnp.asarray(t_history),
312
+ "batch_ess_hist": jnp.stack(batch_ess_history),
313
+ "valid_trained_ess_hist": jnp.asarray(trained_ess_history),
314
+ "valid_identity_ess_hist": jnp.asarray(identity_ess_history),
315
+ "attempt_status_hist": tuple(status_history),
316
+ "selection_history": tuple(selection_history),
317
+ "elapsed_seconds": float(elapsed_seconds),
318
+ "selected_flow_path": None,
319
+ "continuation_flow_path": None,
320
+ "validation_samples_path": None,
321
+ }
322
+
323
+
324
+ def _identity_stage_record(
325
+ *,
326
+ t_start,
327
+ t_end,
328
+ sample_count,
329
+ t_history,
330
+ identity_ess_history,
331
+ status_history,
332
+ selection_history,
333
+ elapsed_seconds,
334
+ ):
335
+ """Build one accepted identity-only stage record."""
336
+ return {
337
+ "t": float(t_end),
338
+ "t_start": float(t_start),
339
+ "valid_selected_ess": float(identity_ess_history[-1]),
340
+ "valid_identity_ess": float(identity_ess_history[-1]),
341
+ "valid_sample_count": int(sample_count),
342
+ "selected": "identity",
343
+ "t_hist": jnp.asarray(t_history),
344
+ "valid_identity_ess_hist": jnp.asarray(identity_ess_history),
345
+ "attempt_status_hist": tuple(status_history),
346
+ "selection_history": tuple(selection_history),
347
+ "elapsed_seconds": float(elapsed_seconds),
348
+ "validation_samples_path": None,
349
+ }
350
+
351
+
352
+ def iterate_identity(
353
+ samples,
354
+ source,
355
+ target,
356
+ *,
357
+ ladder,
358
+ mc_dt,
359
+ mc_steps,
360
+ mc_adjust,
361
+ monitor,
362
+ chunks,
363
+ seed,
364
+ bg_param=None,
365
+ accepted_t=(0.0,),
366
+ start_stage=1,
367
+ ):
368
+ """Yield adaptive-staging identity-only Boltzmann stages."""
369
+ samples = jnp.asarray(samples)
370
+ policy = _adaptive_policy(bg_param)
371
+ accepted = [float(value) for value in accepted_t]
372
+ base_key = jax.random.key(seed)
373
+ emit = monitor.printer if monitor is not None else print
374
+ stage = start_stage
375
+
376
+ while accepted[-1] < 1.0:
377
+ if stage > policy["max_stages"]:
378
+ return
379
+ t_start = accepted[-1]
380
+ if len(accepted) == 1:
381
+ t_end = policy["t_safe"]
382
+ else:
383
+ t_end = min(
384
+ accepted[-1]
385
+ + policy["enlarge_factor"]
386
+ * (accepted[-1] - accepted[-2]),
387
+ 1.0,
388
+ )
389
+ if 1.0 - t_end < policy["t_tol"]:
390
+ t_end = 1.0
391
+ t_end, selection_history, selected_endpoint = _select_adaptive_endpoint(
392
+ base_key=base_key,
393
+ stage=stage,
394
+ samples=samples,
395
+ source=source,
396
+ target=target,
397
+ t_start=t_start,
398
+ t_end=t_end,
399
+ policy=policy,
400
+ ladder=ladder,
401
+ mc_dt=mc_dt,
402
+ mc_steps=mc_steps,
403
+ mc_adjust=mc_adjust,
404
+ chunks=chunks,
405
+ emit=emit,
406
+ )
407
+ if not selected_endpoint:
408
+ return
409
+
410
+ stage_started = time.perf_counter()
411
+ t_history = []
412
+ identity_history = []
413
+ status_history = []
414
+ accepted_stage = False
415
+ for attempt in range(1, policy["max_retry"] + 1):
416
+ source_bridge = linear_combination(
417
+ [target, source], [t_start, 1.0 - t_start]
418
+ )
419
+ target_bridge = linear_combination(
420
+ [target, source], [t_end, 1.0 - t_end]
421
+ )
422
+ log_weights = _identity_log_weights(
423
+ samples, source_bridge, target_bridge, chunks
424
+ )
425
+ identity_ess = float(compute_ESS_log(log_weights))
426
+ accepted_attempt = identity_ess >= policy["tau_ess"]
427
+ status = "accepted" if accepted_attempt else "rejected"
428
+ t_history.append(t_end)
429
+ identity_history.append(identity_ess)
430
+ status_history.append(status)
431
+ emit(
432
+ f"[stage {stage:03d} attempt {attempt:02d} | "
433
+ f"t: {t_start:.6f} -> {t_end:.6f}] identity "
434
+ f"ESS={identity_ess:.4f} {status.upper()}"
435
+ )
436
+ if accepted_attempt:
437
+ accepted_stage = True
438
+ break
439
+ next_t = t_start + policy["shrink_factor"] * (t_end - t_start)
440
+ if not t_start < next_t < t_end:
441
+ break
442
+ t_end = next_t
443
+
444
+ if not accepted_stage:
445
+ return
446
+ advance_key = _operation_key(base_key, 301, stage)
447
+ key_resample, key_mc = jax.random.split(advance_key)
448
+ samples = resample(
449
+ key_resample,
450
+ samples,
451
+ linear_weights_from_log(log_weights),
452
+ N=samples.shape[0],
453
+ )
454
+ samples = langevin(
455
+ key_mc,
456
+ samples,
457
+ target_bridge,
458
+ dt=mc_dt,
459
+ steps=mc_steps,
460
+ adjust=mc_adjust,
461
+ chunks=chunks,
462
+ )
463
+ samples = jax.block_until_ready(samples)
464
+ if not bool(jnp.all(jnp.isfinite(samples))):
465
+ raise FloatingPointError(
466
+ "post-stage samples contain nonfinite coordinates"
467
+ )
468
+ record = _identity_stage_record(
469
+ t_start=t_start,
470
+ t_end=t_end,
471
+ sample_count=samples.shape[0],
472
+ t_history=t_history,
473
+ identity_ess_history=identity_history,
474
+ status_history=status_history,
475
+ selection_history=selection_history,
476
+ elapsed_seconds=time.perf_counter() - stage_started,
477
+ )
478
+ emit(
479
+ f"[stage {stage:03d} | t: {t_start:.6f} -> {t_end:.6f}] "
480
+ f"ACCEPTED in {record['elapsed_seconds']:.3f}s"
481
+ )
482
+ yield samples, record, None
483
+ accepted.append(t_end)
484
+ stage += 1
485
+
486
+
487
+ def iterate_boltzmann(
488
+ samples,
489
+ source,
490
+ target,
491
+ flow,
492
+ *,
493
+ objective,
494
+ pool_size,
495
+ batch_size,
496
+ train_steps,
497
+ lr,
498
+ ladder,
499
+ mc_dt,
500
+ mc_steps,
501
+ initialize_from_identity,
502
+ mc_adjust,
503
+ monitor,
504
+ chunks,
505
+ checkpoint,
506
+ seed,
507
+ bg_param=None,
508
+ t_list=None,
509
+ u_clip=float("inf"),
510
+ g_clip=float("inf"),
511
+ coeff_lambda=1.0,
512
+ coeff_alpha=0.5,
513
+ coeff_beta=0.5,
514
+ melt=0.0,
515
+ opt_dt=1.0,
516
+ opt_steps=1,
517
+ accepted_t=(0.0,),
518
+ start_stage=1,
519
+ ):
520
+ """Yield each newly completed Boltzmann stage and particle population."""
521
+ samples = jnp.asarray(samples)
522
+
523
+ adaptive = t_list is None
524
+ policy = (
525
+ _adaptive_policy(bg_param)
526
+ if adaptive else None
527
+ )
528
+ schedule = (
529
+ None if adaptive else list(t_list)
530
+ )
531
+ accepted = [float(value) for value in accepted_t]
532
+ direction, namespace = _OBJECTIVES[objective]
533
+ entry_flow = flow
534
+ base_key = jax.random.key(seed)
535
+ emit = monitor.printer if monitor is not None else print
536
+ stage = start_stage
537
+
538
+ while accepted[-1] < 1.0:
539
+ t_start = accepted[-1]
540
+ if adaptive:
541
+ if stage > policy["max_stages"]:
542
+ return
543
+ if len(accepted) == 1:
544
+ t_end = policy["t_safe"]
545
+ else:
546
+ t_end = min(
547
+ accepted[-1]
548
+ + policy["enlarge_factor"]
549
+ * (accepted[-1] - accepted[-2]),
550
+ 1.0,
551
+ )
552
+ if 1.0 - t_end < policy["t_tol"]:
553
+ t_end = 1.0
554
+ t_end, selection_history, selected_endpoint = (
555
+ _select_adaptive_endpoint(
556
+ base_key=base_key,
557
+ stage=stage,
558
+ samples=samples,
559
+ source=source,
560
+ target=target,
561
+ t_start=t_start,
562
+ t_end=t_end,
563
+ policy=policy,
564
+ ladder=ladder,
565
+ mc_dt=mc_dt,
566
+ mc_steps=mc_steps,
567
+ mc_adjust=mc_adjust,
568
+ chunks=chunks,
569
+ emit=emit,
570
+ )
571
+ )
572
+ if not selected_endpoint:
573
+ return
574
+ max_attempts = policy["max_retry"]
575
+ else:
576
+ remaining = [endpoint for endpoint in schedule if endpoint > t_start]
577
+ if not remaining:
578
+ return
579
+ t_end = remaining[0]
580
+ selection_history = ({
581
+ "index": 0,
582
+ "t_start": t_start,
583
+ "t_end": t_end,
584
+ "status": "skipped",
585
+ "reason": "fixed_schedule",
586
+ "validation_sample_count": int(samples.shape[0]),
587
+ },)
588
+ max_attempts = 1
589
+
590
+ stage_started = time.perf_counter()
591
+ identity_flow = entry_flow.zeros()
592
+ training_identity = _training_identity(entry_flow)
593
+ t_history = []
594
+ batch_histories = []
595
+ trained_history = []
596
+ identity_history = []
597
+ status_history = []
598
+ accepted_stage = False
599
+ for attempt in range(1, max_attempts + 1):
600
+ optimizer_initial = (
601
+ training_identity if initialize_from_identity else entry_flow
602
+ )
603
+ source_bridge = linear_combination(
604
+ [target, source], [t_start, 1.0 - t_start]
605
+ )
606
+ target_bridge = linear_combination(
607
+ [target, source], [t_end, 1.0 - t_end]
608
+ )
609
+ emit(
610
+ f"[stage {stage:03d} attempt {attempt:02d} | "
611
+ f"t: {t_start:.6f} -> {t_end:.6f}] training"
612
+ )
613
+ training_key = _operation_key(
614
+ base_key, namespace, stage, attempt
615
+ )
616
+ trainer_seed = jax.random.key_data(training_key)[0]
617
+ candidate, batch_ess = _train_attempt(
618
+ objective,
619
+ samples,
620
+ source_bridge,
621
+ target_bridge,
622
+ optimizer_initial,
623
+ pool_size=pool_size,
624
+ batch_size=batch_size,
625
+ train_steps=train_steps,
626
+ lr=lr,
627
+ ladder=ladder,
628
+ mc_dt=mc_dt,
629
+ mc_steps=mc_steps,
630
+ mc_adjust=mc_adjust,
631
+ chunks=chunks,
632
+ monitor=_stage_monitor(monitor, stage, attempt),
633
+ seed=trainer_seed,
634
+ checkpoint=checkpoint,
635
+ u_clip=u_clip,
636
+ g_clip=g_clip,
637
+ coeff_lambda=coeff_lambda,
638
+ coeff_alpha=coeff_alpha,
639
+ coeff_beta=coeff_beta,
640
+ melt=melt,
641
+ opt_dt=opt_dt,
642
+ opt_steps=opt_steps,
643
+ t_start=t_start,
644
+ t_end=t_end,
645
+ )
646
+ candidate, batch_ess = jax.block_until_ready(
647
+ (candidate, batch_ess)
648
+ )
649
+ trained_log_weights = _chunked_log_weights(
650
+ samples,
651
+ source_bridge,
652
+ target_bridge,
653
+ candidate,
654
+ direction,
655
+ chunks,
656
+ )
657
+ identity_log_weights = _identity_log_weights(
658
+ samples, source_bridge, target_bridge, chunks
659
+ )
660
+ trained_ess = float(compute_ESS_log(trained_log_weights))
661
+ identity_ess = float(compute_ESS_log(identity_log_weights))
662
+ if trained_ess > identity_ess:
663
+ selected_flow = candidate
664
+ continuation_flow = candidate
665
+ selected = "trained"
666
+ selected_ess = trained_ess
667
+ selected_log_weights = trained_log_weights
668
+ else:
669
+ selected_flow = identity_flow
670
+ continuation_flow = training_identity
671
+ selected = "identity"
672
+ selected_ess = identity_ess
673
+ selected_log_weights = identity_log_weights
674
+ accepted_attempt = not adaptive or selected_ess >= policy["tau_ess"]
675
+ status = "accepted" if accepted_attempt else "rejected"
676
+ t_history.append(t_end)
677
+ batch_histories.append(batch_ess)
678
+ trained_history.append(trained_ess)
679
+ identity_history.append(identity_ess)
680
+ status_history.append(status)
681
+ emit(
682
+ f"[stage {stage:03d} attempt {attempt:02d} | "
683
+ f"t: {t_start:.6f} -> {t_end:.6f}] validation "
684
+ f"ESS={selected_ess:.4f} "
685
+ f"(trained={trained_ess:.4f}, identity={identity_ess:.4f}) "
686
+ f"{status.upper()}"
687
+ )
688
+ if accepted_attempt:
689
+ accepted_stage = True
690
+ break
691
+ next_t = t_start + policy["shrink_factor"] * (t_end - t_start)
692
+ if not t_start < next_t < t_end:
693
+ break
694
+ t_end = next_t
695
+
696
+ if not accepted_stage:
697
+ return
698
+ advance_key = _operation_key(base_key, 301, stage)
699
+ key_resample, key_mc = jax.random.split(advance_key)
700
+ samples = _advance_stage_samples(
701
+ key_resample,
702
+ key_mc,
703
+ samples,
704
+ selected_log_weights,
705
+ selected_flow,
706
+ direction,
707
+ target_bridge,
708
+ mc_dt,
709
+ mc_steps,
710
+ mc_adjust,
711
+ chunks,
712
+ )
713
+ samples = jax.block_until_ready(samples)
714
+ if not bool(jnp.all(jnp.isfinite(samples))):
715
+ raise FloatingPointError(
716
+ "post-stage samples contain nonfinite coordinates"
717
+ )
718
+ record = _stage_record(
719
+ t_start=t_start,
720
+ t_end=t_end,
721
+ selected=selected,
722
+ selected_flow=selected_flow,
723
+ continuation_flow=continuation_flow,
724
+ sample_count=samples.shape[0],
725
+ t_history=t_history,
726
+ batch_ess_history=batch_histories,
727
+ trained_ess_history=trained_history,
728
+ identity_ess_history=identity_history,
729
+ status_history=status_history,
730
+ selection_history=selection_history,
731
+ elapsed_seconds=time.perf_counter() - stage_started,
732
+ )
733
+ emit(
734
+ f"[stage {stage:03d} | t: {t_start:.6f} -> {t_end:.6f}] "
735
+ f"ACCEPTED in {record['elapsed_seconds']:.3f}s"
736
+ )
737
+ yield samples, record, continuation_flow
738
+ accepted.append(t_end)
739
+ entry_flow = continuation_flow
740
+ stage += 1
741
+
742
+
743
+ def run_boltzmann(samples, source, target, flow, **controls):
744
+ """Collect the pure stage iterator into the public return contract."""
745
+ stages = []
746
+ current = jnp.asarray(samples)
747
+ for current, record, _ in iterate_boltzmann(
748
+ current, source, target, flow, **controls
749
+ ):
750
+ stages.append(record)
751
+ return current, stages
752
+
753
+
754
+ def boltzmann_identity(
755
+ x_valid,
756
+ source,
757
+ target,
758
+ ladder,
759
+ mc_dt,
760
+ mc_steps,
761
+ *,
762
+ mc_adjust=True,
763
+ monitor=None,
764
+ bg_param=None,
765
+ chunks=1,
766
+ seed=0,
767
+ ):
768
+ """Run adaptive-staging identity-only Boltzmann stages without flow training."""
769
+ stages = []
770
+ current = jnp.asarray(x_valid)
771
+ for current, record, _ in iterate_identity(
772
+ current,
773
+ source,
774
+ target,
775
+ ladder=ladder,
776
+ mc_dt=mc_dt,
777
+ mc_steps=mc_steps,
778
+ mc_adjust=mc_adjust,
779
+ monitor=monitor,
780
+ bg_param=bg_param,
781
+ chunks=chunks,
782
+ seed=seed,
783
+ ):
784
+ stages.append(record)
785
+ return current, stages
786
+
787
+
788
+ def boltzmann_reverse_KL_F(
789
+ x_valid,
790
+ source,
791
+ target,
792
+ flow,
793
+ batch_size,
794
+ train_steps,
795
+ lr,
796
+ ladder,
797
+ mc_dt,
798
+ mc_steps,
799
+ *,
800
+ initialize_from_identity=True,
801
+ mc_adjust=True,
802
+ monitor=None,
803
+ bg_param=None,
804
+ chunks=1,
805
+ checkpoint=False,
806
+ seed=0,
807
+ ):
808
+ """Run adaptive-staging reverse-KL Boltzmann stages."""
809
+ return run_boltzmann(
810
+ x_valid, source, target, flow, objective="reverse_kl", pool_size=0,
811
+ batch_size=batch_size, train_steps=train_steps, lr=lr, ladder=ladder,
812
+ mc_dt=mc_dt, mc_steps=mc_steps,
813
+ initialize_from_identity=initialize_from_identity,
814
+ mc_adjust=mc_adjust, monitor=monitor, bg_param=bg_param,
815
+ chunks=chunks, checkpoint=checkpoint, seed=seed,
816
+ )
817
+
818
+
819
+ def boltzmann_forward_KL_G(
820
+ x_valid,
821
+ source,
822
+ target,
823
+ flow,
824
+ batch_size,
825
+ train_steps,
826
+ lr,
827
+ ladder,
828
+ mc_dt,
829
+ mc_steps,
830
+ *,
831
+ initialize_from_identity=True,
832
+ mc_adjust=True,
833
+ monitor=None,
834
+ bg_param=None,
835
+ chunks=1,
836
+ checkpoint=False,
837
+ u_clip=float("inf"),
838
+ g_clip=float("inf"),
839
+ seed=0,
840
+ ):
841
+ """Run adaptive-staging forward-KL Boltzmann stages."""
842
+ return run_boltzmann(
843
+ x_valid, source, target, flow, objective="forward_kl", pool_size=0,
844
+ batch_size=batch_size, train_steps=train_steps, lr=lr, ladder=ladder,
845
+ mc_dt=mc_dt, mc_steps=mc_steps,
846
+ initialize_from_identity=initialize_from_identity,
847
+ mc_adjust=mc_adjust, monitor=monitor, bg_param=bg_param,
848
+ chunks=chunks, checkpoint=checkpoint, u_clip=u_clip, g_clip=g_clip,
849
+ seed=seed,
850
+ )
851
+
852
+
853
+ def boltzmann_forward_KLX_G(
854
+ x_valid,
855
+ source,
856
+ target,
857
+ flow,
858
+ batch_size,
859
+ train_steps,
860
+ lr,
861
+ ladder,
862
+ mc_dt,
863
+ mc_steps,
864
+ *,
865
+ initialize_from_identity=True,
866
+ coeff_lambda=1.0,
867
+ mc_adjust=True,
868
+ monitor=None,
869
+ bg_param=None,
870
+ chunks=1,
871
+ checkpoint=False,
872
+ u_clip=float("inf"),
873
+ g_clip=float("inf"),
874
+ seed=0,
875
+ ):
876
+ """Run adaptive-staging forward-KLX Boltzmann stages."""
877
+ return run_boltzmann(
878
+ x_valid, source, target, flow, objective="forward_klx", pool_size=0,
879
+ batch_size=batch_size, train_steps=train_steps, lr=lr, ladder=ladder,
880
+ mc_dt=mc_dt, mc_steps=mc_steps,
881
+ initialize_from_identity=initialize_from_identity,
882
+ coeff_lambda=coeff_lambda, mc_adjust=mc_adjust, monitor=monitor,
883
+ bg_param=bg_param, chunks=chunks, checkpoint=checkpoint,
884
+ u_clip=u_clip, g_clip=g_clip, seed=seed,
885
+ )
886
+
887
+
888
+ def boltzmann_forward_KLXX_G(
889
+ x_valid,
890
+ source,
891
+ target,
892
+ flow,
893
+ pool_size,
894
+ batch_size,
895
+ train_steps,
896
+ lr,
897
+ ladder,
898
+ melt,
899
+ opt_dt,
900
+ opt_steps,
901
+ mc_dt,
902
+ mc_steps,
903
+ *,
904
+ initialize_from_identity=True,
905
+ coeff_lambda=1.0,
906
+ coeff_alpha=0.5,
907
+ coeff_beta=0.5,
908
+ mc_adjust=True,
909
+ monitor=None,
910
+ bg_param=None,
911
+ chunks=1,
912
+ checkpoint=False,
913
+ u_clip=float("inf"),
914
+ g_clip=float("inf"),
915
+ seed=0,
916
+ ):
917
+ """Run adaptive-staging KLXX stages with full or separately sampled QT pools."""
918
+ return run_boltzmann(
919
+ x_valid, source, target, flow, objective="forward_klxx",
920
+ pool_size=pool_size, batch_size=batch_size, train_steps=train_steps,
921
+ lr=lr, ladder=ladder, melt=melt, opt_dt=opt_dt,
922
+ opt_steps=opt_steps, mc_dt=mc_dt, mc_steps=mc_steps,
923
+ initialize_from_identity=initialize_from_identity,
924
+ coeff_lambda=coeff_lambda, coeff_alpha=coeff_alpha,
925
+ coeff_beta=coeff_beta, mc_adjust=mc_adjust, monitor=monitor,
926
+ bg_param=bg_param, chunks=chunks, checkpoint=checkpoint,
927
+ u_clip=u_clip, g_clip=g_clip, seed=seed,
928
+ )
929
+
930
+
931
+ def boltzmann_reverse_KL_F_fixed(
932
+ x_valid,
933
+ source,
934
+ target,
935
+ flow,
936
+ batch_size,
937
+ train_steps,
938
+ lr,
939
+ mc_dt,
940
+ mc_steps,
941
+ t_list,
942
+ *,
943
+ initialize_from_identity=True,
944
+ mc_adjust=True,
945
+ monitor=None,
946
+ chunks=1,
947
+ checkpoint=False,
948
+ seed=0,
949
+ ):
950
+ """Run fixed-schedule reverse-KL Boltzmann stages."""
951
+ return run_boltzmann(
952
+ x_valid, source, target, flow, objective="reverse_kl", pool_size=0,
953
+ batch_size=batch_size, train_steps=train_steps, lr=lr, ladder=1,
954
+ mc_dt=mc_dt, mc_steps=mc_steps,
955
+ initialize_from_identity=initialize_from_identity,
956
+ mc_adjust=mc_adjust, monitor=monitor, t_list=t_list, chunks=chunks,
957
+ checkpoint=checkpoint, seed=seed,
958
+ )
959
+
960
+
961
+ def boltzmann_forward_KL_G_fixed(
962
+ x_valid,
963
+ source,
964
+ target,
965
+ flow,
966
+ batch_size,
967
+ train_steps,
968
+ lr,
969
+ ladder,
970
+ mc_dt,
971
+ mc_steps,
972
+ t_list,
973
+ *,
974
+ initialize_from_identity=True,
975
+ mc_adjust=True,
976
+ monitor=None,
977
+ chunks=1,
978
+ checkpoint=False,
979
+ u_clip=float("inf"),
980
+ g_clip=float("inf"),
981
+ seed=0,
982
+ ):
983
+ """Run fixed-schedule forward-KL Boltzmann stages."""
984
+ return run_boltzmann(
985
+ x_valid, source, target, flow, objective="forward_kl", pool_size=0,
986
+ batch_size=batch_size, train_steps=train_steps, lr=lr, ladder=ladder,
987
+ mc_dt=mc_dt, mc_steps=mc_steps,
988
+ initialize_from_identity=initialize_from_identity,
989
+ mc_adjust=mc_adjust, monitor=monitor, t_list=t_list, chunks=chunks,
990
+ checkpoint=checkpoint, u_clip=u_clip, g_clip=g_clip, seed=seed,
991
+ )
992
+
993
+
994
+ def boltzmann_forward_KLX_G_fixed(
995
+ x_valid,
996
+ source,
997
+ target,
998
+ flow,
999
+ batch_size,
1000
+ train_steps,
1001
+ lr,
1002
+ ladder,
1003
+ mc_dt,
1004
+ mc_steps,
1005
+ t_list,
1006
+ *,
1007
+ initialize_from_identity=True,
1008
+ coeff_lambda=1.0,
1009
+ mc_adjust=True,
1010
+ monitor=None,
1011
+ chunks=1,
1012
+ checkpoint=False,
1013
+ u_clip=float("inf"),
1014
+ g_clip=float("inf"),
1015
+ seed=0,
1016
+ ):
1017
+ """Run fixed-schedule forward-KLX Boltzmann stages."""
1018
+ return run_boltzmann(
1019
+ x_valid, source, target, flow, objective="forward_klx", pool_size=0,
1020
+ batch_size=batch_size, train_steps=train_steps, lr=lr, ladder=ladder,
1021
+ mc_dt=mc_dt, mc_steps=mc_steps,
1022
+ initialize_from_identity=initialize_from_identity,
1023
+ coeff_lambda=coeff_lambda, mc_adjust=mc_adjust, monitor=monitor,
1024
+ t_list=t_list, chunks=chunks, checkpoint=checkpoint,
1025
+ u_clip=u_clip, g_clip=g_clip, seed=seed,
1026
+ )
1027
+
1028
+
1029
+ def boltzmann_forward_KLXX_G_fixed(
1030
+ x_valid,
1031
+ source,
1032
+ target,
1033
+ flow,
1034
+ pool_size,
1035
+ batch_size,
1036
+ train_steps,
1037
+ lr,
1038
+ ladder,
1039
+ melt,
1040
+ opt_dt,
1041
+ opt_steps,
1042
+ mc_dt,
1043
+ mc_steps,
1044
+ t_list,
1045
+ *,
1046
+ initialize_from_identity=True,
1047
+ coeff_lambda=1.0,
1048
+ coeff_alpha=0.5,
1049
+ coeff_beta=0.5,
1050
+ mc_adjust=True,
1051
+ monitor=None,
1052
+ chunks=1,
1053
+ checkpoint=False,
1054
+ u_clip=float("inf"),
1055
+ g_clip=float("inf"),
1056
+ seed=0,
1057
+ ):
1058
+ """Run fixed-schedule KLXX stages with configurable QT pool sizing."""
1059
+ return run_boltzmann(
1060
+ x_valid, source, target, flow, objective="forward_klxx",
1061
+ pool_size=pool_size, batch_size=batch_size, train_steps=train_steps,
1062
+ lr=lr, ladder=ladder, melt=melt, opt_dt=opt_dt,
1063
+ opt_steps=opt_steps, mc_dt=mc_dt, mc_steps=mc_steps,
1064
+ initialize_from_identity=initialize_from_identity,
1065
+ coeff_lambda=coeff_lambda, coeff_alpha=coeff_alpha,
1066
+ coeff_beta=coeff_beta, mc_adjust=mc_adjust, monitor=monitor,
1067
+ t_list=t_list, chunks=chunks, checkpoint=checkpoint,
1068
+ u_clip=u_clip, g_clip=g_clip, seed=seed,
1069
+ )