Dynamics Hooks — Stages & Usage#
This page covers hook behaviour specific to dynamics simulations. For the general hook protocol, context, and registry see Hooks — Core Framework.
See also
User guide: Hooks — conceptual overview, writing custom hooks, and composing hook pipelines.
Core framework: Hooks — Core Framework — the
Hookprotocol,HookContext, andHookRegistryMixin.
DynamicsStage#
DynamicsStage enumerates the nine
hook-firing points within a single dynamics step:
![digraph dynamics_stages {
rankdir=TB
compound=true
fontname="Helvetica"
node [fontname="Helvetica" fontsize=11 shape=box style="rounded,filled" fillcolor="#dce6f1"]
edge [fontname="Helvetica" fontsize=10 style=bold]
BEFORE_STEP [label="BEFORE_STEP" fillcolor="#f9e2ae"]
subgraph cluster_step {
label="step body"
style=rounded
color="#4a90d9"
fontcolor="#4a90d9"
fontname="Helvetica"
fontsize=12
BEFORE_PRE_UPDATE [label="BEFORE_PRE_UPDATE"]
pre_update [label="pre_update()" fillcolor="#eeeeee"]
AFTER_PRE_UPDATE [label="AFTER_PRE_UPDATE"]
BEFORE_COMPUTE [label="BEFORE_COMPUTE"]
compute [label="compute()" fillcolor="#eeeeee"]
AFTER_COMPUTE [label="AFTER_COMPUTE"]
BEFORE_POST_UPDATE [label="BEFORE_POST_UPDATE"]
post_update [label="post_update()" fillcolor="#eeeeee"]
AFTER_POST_UPDATE [label="AFTER_POST_UPDATE"]
BEFORE_PRE_UPDATE -> pre_update -> AFTER_PRE_UPDATE
AFTER_PRE_UPDATE -> BEFORE_COMPUTE
BEFORE_COMPUTE -> compute -> AFTER_COMPUTE
AFTER_COMPUTE -> BEFORE_POST_UPDATE
BEFORE_POST_UPDATE -> post_update -> AFTER_POST_UPDATE
}
AFTER_STEP [label="AFTER_STEP" fillcolor="#f9e2ae"]
ON_CONVERGE [label="ON_CONVERGE\n(if converged)" fillcolor="#f9e2ae"]
BEFORE_STEP -> BEFORE_PRE_UPDATE [lhead=cluster_step]
AFTER_POST_UPDATE -> AFTER_STEP [ltail=cluster_step]
AFTER_STEP -> ON_CONVERGE [style=dashed]
}](../../_images/graphviz-b31219482a2a9627a840c96d6f124258cee41f0d.png)
DynamicsStage hook firing points within a single step.#
Stage |
Value |
When it fires |
|---|---|---|
|
0 |
Very start of each step, before any operations. |
|
1 |
Before the first integrator half-step (positions). |
|
2 |
After positions are updated, before the forward pass. |
|
3 |
Before the model forward pass. |
|
4 |
After forces/energy are written to the batch. |
|
5 |
Before the second integrator half-step (velocities). |
|
6 |
After velocities are updated. |
|
7 |
Very end of the step, after all operations. |
|
8 |
Only when the convergence hook detects converged samples. |
Built-in dynamics hooks#
The nvalchemi.dynamics.hooks package ships production-ready hooks
organized into four categories. General-purpose hooks
(NeighborListHook,
BiasedPotentialHook,
WrapPeriodicHook) are documented in
Hooks — Core Framework.
Observer hooks (read-only, fire at AFTER_STEP)#
These hooks do not modify the batch — they record, log, or monitor simulation state.
Hook |
Purpose |
|---|---|
Log scalar observables (energy, fmax, temperature) to
|
|
Write the full batch state to a
|
|
Write only newly converged samples to a
|
|
Track cumulative energy drift in NVE runs; warn or halt on excessive drift. |
|
Instrument steps with NVTX ranges and wall-clock timing for
Nsight Systems profiling. Fires at multiple stages via
|
Post-compute hooks (modify batch, fire at AFTER_COMPUTE)#
These hooks modify the batch after the model forward pass and before the velocity update.
Hook |
Purpose |
|---|---|
Detect NaN/Inf in forces and energy; raise with diagnostic info (affected graph indices, step count). |
|
Clamp per-atom force magnitudes to a safe maximum, preserving force direction. Prevents numerical explosions. |
Constraint hooks (modify batch, fire at BEFORE_PRE_UPDATE)#
These hooks enforce geometric constraints across integration steps.
Hook |
Purpose |
|---|---|
Freeze atoms by category (e.g. substrate, boundary). Snapshots
positions at |
Usage examples#
Logging to CSV every 100 steps#
from nvalchemi.dynamics.hooks import LoggingHook
hook = LoggingHook(frequency=100, backend="csv", log_path="md_log.csv")
dynamics = DemoDynamics(model=model, n_steps=10_000, dt=0.5, hooks=[hook])
dynamics.run(batch)
Recording trajectories to a data sink#
from nvalchemi.dynamics.hooks import SnapshotHook
from nvalchemi.dynamics import HostMemory
sink = HostMemory(capacity=10_000)
hook = SnapshotHook(sink=sink, frequency=10)
dynamics = DemoDynamics(model=model, n_steps=1_000, dt=0.5, hooks=[hook])
dynamics.run(batch) # 100 snapshots
trajectory = sink.read()
Safety: NaN detection + force clamping#
from nvalchemi.dynamics.hooks import MaxForceClampHook, NaNDetectorHook
dynamics = DemoDynamics(
model=model,
dt=0.5,
hooks=[
# Clamp first, then check — both fire at AFTER_COMPUTE
# in registration order.
MaxForceClampHook(max_force=50.0, log_clamps=True),
NaNDetectorHook(extra_keys=["stress"]),
],
)
Enhanced sampling with a bias potential#
from nvalchemi.hooks import BiasedPotentialHook
from nvalchemi.dynamics.base import DynamicsStage
def harmonic_restraint(batch):
"""Restrain center of mass to the origin."""
k = 10.0 # eV/Ų
com = batch.positions.mean(dim=0, keepdim=True)
bias_energy = 0.5 * k * (com ** 2).sum().unsqueeze(0).unsqueeze(0)
bias_forces = -k * com.expand_as(batch.positions) / batch.num_nodes
return bias_energy, bias_forces
hook = BiasedPotentialHook(bias_fn=harmonic_restraint, stage=DynamicsStage.AFTER_COMPUTE)
dynamics = DemoDynamics(model=model, dt=0.5, hooks=[hook])
Profiling with Nsight Systems#
from nvalchemi.dynamics.hooks import ProfilerHook
hook = ProfilerHook(enable_nvtx=True, enable_timer=True, frequency=10)
dynamics = DemoDynamics(model=model, n_steps=1_000, dt=0.5, hooks=[hook])
# Run under: nsys profile python my_script.py
dynamics.run(batch)
NVE energy drift monitoring#
from nvalchemi.dynamics.hooks import EnergyDriftMonitorHook
hook = EnergyDriftMonitorHook(
threshold=1e-5,
metric="per_atom_per_step",
action="raise", # or "warn" for production
frequency=100,
)
dynamics = DemoDynamics(model=model, dt=0.5, hooks=[hook])
Hooks inside FusedStage#
When hooks are registered on sub-stage dynamics inside a
FusedStage, their firing semantics differ
slightly from standalone execution:
Fired on each sub-stage:
BEFORE_STEP,AFTER_COMPUTE,BEFORE_PRE_UPDATE,AFTER_POST_UPDATE,AFTER_STEP,ON_CONVERGE
Not fired on sub-stages (because the forward pass is shared):
BEFORE_COMPUTE,AFTER_PRE_UPDATE,BEFORE_POST_UPDATE
This means safety hooks (NaNDetectorHook, MaxForceClampHook)
and observer hooks (LoggingHook, SnapshotHook) work as expected
inside fused stages, since they fire at AFTER_COMPUTE or
AFTER_STEP.
Hook ordering inside a fused step:
![digraph fused_hook_order {
rankdir=TB
compound=true
fontname="Helvetica"
node [fontname="Helvetica" fontsize=11 shape=box style="rounded,filled" fillcolor="#dce6f1"]
edge [fontname="Helvetica" fontsize=10 style=bold]
subgraph cluster_before {
label="for each sub-stage"
style=dashed
color="#4a90d9"
fontcolor="#4a90d9"
fontname="Helvetica"
fontsize=10
BEFORE_STEP [label="BEFORE_STEP hooks"]
}
compute [label="single compute()" fillcolor="#f9e2ae"]
subgraph cluster_after_compute {
label="for each sub-stage"
style=dashed
color="#4a90d9"
fontcolor="#4a90d9"
fontname="Helvetica"
fontsize=10
AFTER_COMPUTE [label="AFTER_COMPUTE hooks"]
}
subgraph cluster_update {
label="for each sub-stage"
style=dashed
color="#4a90d9"
fontcolor="#4a90d9"
fontname="Helvetica"
fontsize=10
BEFORE_PRE [label="BEFORE_PRE_UPDATE hooks"]
masked [label="masked_update()\n(if samples match status)" fillcolor="#eeeeee"]
AFTER_POST [label="AFTER_POST_UPDATE hooks"]
BEFORE_PRE -> masked -> AFTER_POST
}
subgraph cluster_after_step {
label="for each sub-stage"
style=dashed
color="#4a90d9"
fontcolor="#4a90d9"
fontname="Helvetica"
fontsize=10
AFTER_STEP [label="AFTER_STEP hooks"]
}
subgraph cluster_converge {
label="for each sub-stage"
style=dashed
color="#4a90d9"
fontcolor="#4a90d9"
fontname="Helvetica"
fontsize=10
conv_check [label="convergence check" fillcolor="#eeeeee"]
ON_CONVERGE [label="ON_CONVERGE hooks" fillcolor="#f9e2ae"]
conv_check -> ON_CONVERGE [style=dashed label="if converged"]
}
BEFORE_STEP -> compute
compute -> AFTER_COMPUTE
AFTER_COMPUTE -> BEFORE_PRE
AFTER_POST -> AFTER_STEP
AFTER_STEP -> conv_check
}](../../_images/graphviz-f044f3a85e74ad67597f3cedd8c7b499f37aa02e.png)
Hook ordering inside a single FusedStage.step().#