Note
Go to the end to download the full example code.
Packing-fraction protocol: integrate with a scheduled box rescale#
run_packing_fraction_protocol()
is a thin wrapper around System.step() that interleaves
scale_to_packing_fraction() on a
user-supplied, per-frame schedule. Temperature control, bonded forces,
collider, etc. are whatever the System you pass in already has —
the protocol just delegates.
Here we:
Build a sphere packing at a modest
phi = 0.35withbuild_sphere_system().Drive
phiup to0.55along a linear ramp and then hold for a while, saving a frame at each step on a pseudolog schedule (so early, fast changes are well-resolved and late, slow drift is sparsely sampled).Read back per-frame
phiand total kinetic energy along the ramp.
Imports
import jax
jax.config.update("jax_enable_x64", True) # type: ignore[no-untyped-call]
import jax.numpy as jnp
import numpy as np
from jaxdem.utils.particle_creation import build_sphere_system
from jaxdem.utils.dynamics_routines import run_packing_fraction_protocol
from jaxdem.utils.packing_utils import compute_packing_fraction
from jaxdem.utils.rollout_schedules import make_save_steps_pseudolog
from jaxdem.utils.thermal import compute_translational_kinetic_energy
1) Build the starting system#
N = 128
phi_start = 0.35
phi_end = 0.55
dim = 3
state, system = build_sphere_system(
particle_radii=[0.1] * N,
phi=phi_start,
dim=dim,
dt=1e-3,
collider_type="naive",
seed=0,
)
print(f"initial phi = {float(compute_packing_fraction(state, system)):.4f}")
initial phi = 0.3500
2) Build the schedule#
make_save_steps_pseudolog gives us non-uniform save points that
densely sample the early transient and thin out later. np.diff
turns those absolute step indices into the per-frame strides the
protocol wants.
num_steps = 20_000
save_steps = make_save_steps_pseudolog(
num_steps=num_steps,
reset_save_decade=2_000,
min_save_decade=50,
decade=10,
# The protocol records each frame *after* its integration stride and
# box rescale, so a step-0 entry would just be a zero-length first
# stride; the initial state is the ``state`` we already hold.
include_step0=False,
)
strides = np.diff(
np.concatenate([[0], save_steps])
) # stride from step 0 to save_steps[0], then between frames
n_frames = int(strides.size)
# Target phi at each frame: linear ramp from phi_start -> phi_end over
# the first 60% of the protocol, then hold at phi_end.
t_frac = save_steps / float(num_steps)
ramp_end = 0.6
phi_at_frames = np.where(
t_frac < ramp_end,
phi_start + (phi_end - phi_start) * (t_frac / ramp_end),
phi_end,
).astype(float)
print(f"n_frames = {n_frames} (save_steps[:5] = {save_steps[:5]})")
n_frames = 130 (save_steps[:5] = [ 50 100 150 200 250])
3) Run the protocol#
Pure Verlet integration between rescale events — no thermostat, so compression pumps energy into the system and KE grows with phi.
state, system, (traj_state, traj_system) = run_packing_fraction_protocol(
state,
system,
strides=jnp.asarray(strides, dtype=int),
phi_at_frames=jnp.asarray(phi_at_frames, dtype=float),
)
4) Read back the trajectory#
phi_trace = np.asarray(jax.vmap(compute_packing_fraction)(traj_state, traj_system))
ke_trace = np.asarray(jax.vmap(compute_translational_kinetic_energy)(traj_state))
print("idx step phi KE")
for i in (0, 1, n_frames // 4, n_frames // 2, 3 * n_frames // 4, n_frames - 1):
print(f"{i:3d} {save_steps[i]:6d} {phi_trace[i]:.4f} {ke_trace[i]:.3e}")
idx step phi KE
0 50 0.3508 2.446e-17
1 100 0.3517 1.720e-09
32 4350 0.4225 3.007e-03
65 10050 0.5175 8.541e-03
97 14350 0.5500 1.163e-02
129 20000 0.5500 1.103e-02
To keep the kinetic temperature clamped while phi still ramps, build the system with a velocity-rescaling thermostat instead — the builder forwards integrator keyword arguments directly:
state, system = build_sphere_system(
particle_radii=[0.1] * N,
phi=phi_start,
dim=dim,
dt=1e-3,
collider_type="naive",
linear_integrator_type="verlet_rescaling",
linear_integrator_kw={"temperature": 1.0},
seed=0,
)
The same protocol call above then runs unchanged — the protocol itself is agnostic to the integrator choice.
Total running time of the script: (0 minutes 16.490 seconds)