Skip to content

Commit d29575b

Browse files
manzuoni-asteraxraymemory
authored andcommitted
refactor(fk_steering): extract per-particle guided step into a helper (#299)
## Summary Fixes #77. `FKSteering._run_step` was a ~125-line method that mixed two concerns: the unguided batched fast path and the guided per-particle loop (with its 4D restacking and `SamplerStepOutput` reassembly). Extracted the guided branch into `_run_guided_step_per_particle`, so `_run_step` now reads as a small dispatch: ```python coords_4d = coords.reshape(num_particles, self.ensemble_size, -1, 3) if step_scaler is None: ... # batched fast path, early return return self._run_guided_step_per_particle(coords_4d, model, sampler, features, context, step_scaler, num_particles) ``` **Pure extraction — no behavior change.** The guided body (per-particle `sampler.step`, loss/log-correction reduction, `torch.stack` restacking) is moved verbatim. ### Why the two paths stay separate The issue notes the guided/unguided paths look duplicated, but they can't be fully merged: the unguided path runs a **single batched forward pass** over all `particles * ensemble` structures (one `sampler.step`), while the guided path must step **each particle independently** so its guidance gradient is computed only from its own state (correct FK semantics). Funneling the batched output through the per-particle stacker would misinterpret the whole batch as one particle. So the split is intentional; this change makes that boundary explicit and readable rather than collapsing it. Documented the rationale in the new method's docstring. Closes #77. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Improved the stepping flow for guided sampling by separating the per-particle path into a dedicated internal routine. * Preserved the existing fast path when no step scaling is used, including returning denoised coordinates when available. * Kept guided particle-by-particle behavior unchanged, with results still aggregated and reshaped into the expected output format. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: xraymemory <me.anzuoni@gmail.com> Co-authored-by: M E A <xraymemory@users.noreply.github.com>
1 parent 990372a commit d29575b

1 file changed

Lines changed: 46 additions & 0 deletions

File tree

src/sampleworks/core/scalers/fk_steering.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,52 @@ def _run_step(
286286

287287
return step_output, denoised_4d
288288

289+
return self._run_guided_step_per_particle(
290+
coords_4d, model, sampler, features, context, step_scaler, num_particles
291+
)
292+
293+
def _run_guided_step_per_particle(
294+
self,
295+
coords_4d: torch.Tensor,
296+
model: FlowModelWrapper,
297+
sampler: TrajectorySampler,
298+
features: GenerativeModelInput,
299+
context: StepParams,
300+
step_scaler: StepScalerProtocol,
301+
num_particles: int,
302+
) -> tuple[SamplerStepOutput, torch.Tensor | None]:
303+
r"""Run the denoising step one particle at a time, then restack to a batch.
304+
305+
Each particle is stepped independently so its guidance gradient is computed
306+
only from its own state (correct FK semantics). This is the guided
307+
counterpart to the batched fast path in :meth:`_run_step`; the two are kept
308+
separate because the fast path runs a single forward pass over all particles,
309+
which cannot provide per-particle gradients.
310+
311+
Parameters
312+
----------
313+
coords_4d : torch.Tensor
314+
Current coordinates, shape ``(particles, ensemble, atoms, 3)``.
315+
model : FlowModelWrapper
316+
Model wrapper for denoising.
317+
sampler : TrajectorySampler
318+
Sampler that handles the step mechanics.
319+
features : GenerativeModelInput
320+
Model features/inputs.
321+
context : StepParams
322+
Step context with time info and optionally reward.
323+
step_scaler : StepScalerProtocol
324+
Active step scaler providing per-particle guidance.
325+
num_particles : int
326+
Number of particles to iterate over.
327+
328+
Returns
329+
-------
330+
tuple[SamplerStepOutput, torch.Tensor | None]
331+
``(step_output, denoised_4d)`` matching :meth:`_run_step`, with
332+
``step_output.state`` shape ``(particles * ensemble, atoms, 3)`` and
333+
``denoised_4d`` shape ``(particles, ensemble, atoms, 3)``.
334+
"""
289335
states_per_particle: list[torch.Tensor] = []
290336
denoised_per_particle: list[torch.Tensor] = []
291337
losses_per_particle: list[torch.Tensor] = []

0 commit comments

Comments
 (0)