jaxdem.system#
The simulation configuration and the tools that drive the simulation.
Classes
|
The full simulation configuration. |
- final class jaxdem.system.System(linear_integrator: LinearIntegrator, rotation_integrator: RotationIntegrator, collider: Collider, domain: Domain, force_manager: ForceManager, bonded_force_model: BondedForceModel | None, force_model: ForceModel, mat_table: MaterialTable, dt: jax.Array, time: jax.Array, dim: jax.Array, step_count: jax.Array, key: jax.Array, interact_same_bond_id: jax.Array, search_overflow: jax.Array = <factory>, user_pre_step_actions: Callable[[State, System], tuple[State, System]] = <PjitFunction of <function _save_state_system>>, user_post_step_actions: Callable[[State, System], tuple[State, System]] = <PjitFunction of <function _save_state_system>>, minimizer: Any = None, target_fn: Callable[[State, System], jax.Array] | None = None)#
Bases:
objectThe full simulation configuration.
Notes
The System object supports JIT compilation for efficient execution.
Dynamic numeric leaves are JAX arrays. Callbacks, optimizers, targets, and component configuration can carry explicit static metadata.
Example
Creating a basic 2D simulation system:
>>> import jaxdem as jdem >>> import jax.numpy as jnp >>> >>> # Create a System instance >>> sim_system = jdem.System.create( ... state_shape=state.shape, ... dt=0.001, ... linear_integrator_type="euler", ... rotation_integrator_type="spiral", ... collider_type="naive", ... domain_type="free", ... force_model_type="spring", ... # You can pass keyword arguments to component constructors via '_kw' dicts ... domain_kw=dict(box_size=jnp.array([5.0, 5.0]), anchor=jnp.array([0.0, 0.0])) ... ) >>> >>> print(f"System integrator: {sim_system.linear_integrator.__class__.__name__}") >>> print(f"System force model: {sim_system.force_model.__class__.__name__}") >>> print(f"Domain box size: {sim_system.domain.box_size}")
- linear_integrator: LinearIntegrator#
Instance of
jaxdem.LinearIntegratorthat advances the simulation linear state in time.
- rotation_integrator: RotationIntegrator#
Instance of
jaxdem.RotationIntegratorthat advances the simulation angular state in time.
- collider: Collider#
Instance of
jaxdem.Colliderthat performs contact detection and computes inter-particle forces and potential energies.
- domain: Domain#
Instance of
jaxdem.Domainthat defines the simulation boundaries, displacement rules, and boundary conditions.
- force_manager: ForceManager#
Instance of
jaxdem.ForceManagerthat handles per particle forces like external forces and resets forces.
- bonded_force_model: BondedForceModel | None#
Optional instance of
jaxdem.BondedForceModelthat defines bonded interactions by passing a force and energy function to the ForceManager.
- force_model: ForceModel#
Instance of
jaxdem.ForceModelthat defines the physical laws for inter-particle interactions.
- mat_table: MaterialTable#
Instance of
jaxdem.MaterialTableholding material properties and pairwise interaction parameters.
- dt: jax.Array#
The global simulation time step \(\Delta t\).
- time: jax.Array#
Elapsed simulation time.
- dim: jax.Array#
Spatial dimension of the system.
- step_count: jax.Array#
Number of integration steps that have been performed.
- key: jax.Array#
PRNG key for stochastic operations. Always update it with split so each use gets new random numbers.
- interact_same_bond_id: jax.Array#
Boolean scalar controlling interactions between particles connected by
bond_idadjacency.If
False(default), colliders mask out directly bonded pairs. IfTrue, these pairs interact.
- search_overflow: jax.Array#
a force evaluation used an incomplete spatial search.
Check with
check_overflowat host chunk boundaries. Retry from the last valid state after resizing; clearing this flag cannot repair a trajectory.- Type:
Sticky flag
- user_pre_step_actions: Callable[[State, System], tuple[State, System]]#
Function called before every step to perform user-defined actions.
The callback sees the tentative end time using the entering dt. The recorded end time uses the callback’s dt; any callback change to time is retained as an offset from the tentative end time.
- user_post_step_actions: Callable[[State, System], tuple[State, System]]#
Function called after every step to perform user-defined actions.
- minimizer: Any#
An optax GradientTransformation wrapped in CustomGradientTransformation, used for target_fn minimization.
- target_fn: Callable[[State, System], jax.Array] | None#
Optional custom target evaluation function for minimization.
- static create(state_shape: tuple[int, ...] | None = None, *, state: State | None = None, dt: ArrayLike = 0.005, time: float = 0.0, linear_integrator_type: str | None = 'verlet', rotation_integrator_type: str | None = 'verletspiral', collider_type: str | None = 'naive', domain_type: str = 'free', bonded_force_model_type: str | None = None, bonded_force_model_kw: dict[str, Any] | None = None, bonded_force_manager_kw: dict[str, Any] | None = None, bonded_force_model: BondedForceModel | None = None, force_model_type: str = 'spring', force_manager_kw: dict[str, Any] | None = None, mat_table: MaterialTable | None = None, linear_integrator: LinearIntegrator | None = None, rotation_integrator: RotationIntegrator | None = None, collider: Collider | None = None, domain: Domain | None = None, force_model: ForceModel | None = None, force_manager: ForceManager | None = None, linear_integrator_kw: dict[str, Any] | None = None, rotation_integrator_kw: dict[str, Any] | None = None, collider_kw: dict[str, Any] | None = None, domain_kw: dict[str, Any] | None = None, force_model_kw: dict[str, Any] | None = None, seed: int = 0, key: jax.Array | None = None, interact_same_bond_id: bool = False, user_pre_step_actions: Callable[[State, System], tuple[State, System]] | None = None, user_post_step_actions: Callable[[State, System], tuple[State, System]] | None = None, minimizer: Any = None, minimizer_kw: dict[str, Any] | None = None, target_fn: Callable[[State, System], jax.Array] | None = None) System[source]#
Factory method to create a
Systeminstance with specified components.Every component slot accepts either a pre-built instance (
linear_integrator,rotation_integrator,collider,domain,force_model,force_manager,bonded_force_model,mat_table) or a registered type string plus keyword dict (<component>_type/<component>_kw). When you provide an instance, the method uses it as-is and ignores the corresponding*_type/*_kwarguments.- Parameters:
state_shape (Tuple, optional) – Shape of the state tensors handled by the simulation. The penultimate dimension corresponds to the number of particles
Nand the last dimension corresponds to the spatial dimensiondim. You can omit it when you providestate.state (State, optional) – The initial simulation state. When provided, the method infers
state_shapefrom it and forwards the state to colliders whoseCreatemethod requires one (e.g."CellList","NeighborList"), socollider_kw={"state": state}is not needed.dt (ArrayLike, optional) – The scalar global simulation time step, including a traced JAX scalar.
linear_integrator_type (str or None, optional) – The registered type string for the
jaxdem.integrators.LinearIntegratorused to evolve translational degrees of freedom.None(or the empty string) disables linear integration (no-op integrator).rotation_integrator_type (str or None, optional) – The registered type string for the
jaxdem.integrators.RotationIntegratorused to evolve angular degrees of freedom.None(or the empty string) disables rotational integration (no-op integrator).collider_type (str or None, optional) – The registered type string for the
jaxdem.Colliderto use.None(or the empty string) disables non-bonded particle interactions by selecting the no-op collider. Bonded forces, external forces, and domain boundaries remain active.domain_type (str, optional) – The registered type string for the
jaxdem.Domainto use.bonded_force_model_type (str or None, optional) – The registered type string for the
jaxdem.BondedForceModelto use.bonded_force_model_kw (Dict[str, Any] or None, optional) – Keyword arguments forwarded to
BondedForceModel.create.bonded_force_manager_kw (Dict[str, Any] or None, optional) – Deprecated alias of
bonded_force_model_kw(the dict has always been forwarded to the bonded force model, not the manager).force_model_type (str, optional) – The registered type string for the
jaxdem.ForceModelto use.force_manager_kw (Dict[str, Any] or None, optional) – Keyword arguments to pass to the constructor of ForceManager.
mat_table (MaterialTable or None, optional) – An optional pre-configured
jaxdem.MaterialTable. If None, the method creates a default jaxdem.MaterialTable with one generic elastic material and the “harmonic” jaxdem.MaterialMatchmaker.linear_integrator (LinearIntegrator, optional) – Pre-built linear integrator instance. Overrides
linear_integrator_type/linear_integrator_kw.rotation_integrator (RotationIntegrator, optional) – Pre-built rotation integrator instance. Overrides
rotation_integrator_type/rotation_integrator_kw.collider (Collider, optional) – Pre-built collider instance. Overrides
collider_type/collider_kw.domain (Domain, optional) – Pre-built domain instance. Overrides
domain_type/domain_kw.force_model (ForceModel, optional) – Pre-built force model instance. Overrides
force_model_type/force_model_kw.force_manager (ForceManager, optional) – Pre-built force manager instance. Overrides
force_manager_kw. Cannot be combined with a bonded force model (the bonded force functions must already be part of the provided manager).linear_integrator_kw (Dict[str, Any] or None, optional) – Keyword arguments forwarded to the constructor of the selected LinearIntegrator type.
rotation_integrator_kw (Dict[str, Any] or None, optional) – Keyword arguments forwarded to the constructor of the selected RotationIntegrator type.
collider_kw (Dict[str, Any] or None, optional) – Keyword arguments to pass to the constructor of the selected Collider type.
domain_kw (Dict[str, Any] or None, optional) – Keyword arguments to pass to the constructor of the selected Domain type.
force_model_kw (Dict[str, Any] or None, optional) – Keyword arguments to pass to the constructor of the selected ForceModel type.
seed (int, optional) – Integer seed used for random number generation. Defaults to 0. Used only when
keyis not provided.key (jax.Array, optional) – Key for JAX random number generation. When you provide
key, the method ignoresseed.interact_same_bond_id (bool, optional) – Whether particles with the same bond_id interact. Defaults to False.
user_pre_step_actions (Callable, optional) – A function called before every time step to perform user-defined actions.
user_post_step_actions (Callable, optional) – A function called after every time step to perform user-defined actions.
minimizer (Callable, optional) – Optimizer factory used by
System.minimize. Called asminimizer(**minimizer_kw)and must return an optax-styleGradientTransformation. Defaults to FIRE (jaxdem.minimizers.fire).minimizer_kw (Dict[str, Any] or None, optional) – Keyword arguments passed to
minimizer. If the minimizer’s signature accepts adtparameter and none is given here, the method passes the systemdtautomatically.target_fn (Callable, optional) – Custom objective
(state, system) -> scalarminimized bySystem.minimize. WhenNone,System.minimizeuses the total potential energy.
- Returns:
A fully configured System instance ready for simulation.
- Return type:
- Raises:
KeyError – If a specified *_type is not registered in its respective factory, or if the mat_table is missing properties required by the force_model.
TypeError – If constructor keyword arguments are invalid for any component.
ValueError – If the domain_kw ‘box_size’ or ‘anchor’ shapes do not match the dim.
Example
Creating a 3D system with reflective boundaries and a custom dt:
>>> import jaxdem as jdem >>> import jax.numpy as jnp >>> >>> system_reflect = jdem.System.create( ... state_shape=(N, 3), ... dt=0.0005, ... domain_type="reflect", ... domain_kw=dict(box_size=jnp.array([20.0, 20.0, 20.0]), anchor=jnp.array([-10.0, -10.0, -10.0])), ... force_model_type="spring", ... ) >>> print(f"System dt: {system_reflect.dt}") >>> print(f"Domain type: {system_reflect.domain.__class__.__name__}")
Creating a system with a pre-defined MaterialTable:
>>> custom_mat_kw = dict(young=2.0e5, poisson=0.25) >>> custom_material = jdem.Material.create("custom_mat", **custom_mat_kw) >>> custom_mat_table = jdem.MaterialTable.from_materials( ... [custom_material], matcher=jdem.MaterialMatchmaker.create("linear") ... ) >>> >>> system_custom_mat = jdem.System.create( ... state_shape=(N, 2), ... mat_table=custom_mat_table, ... force_model_type="spring" ... )
- static initialize(state: State, system: System) tuple[State, System][source]#
Compute initial forces and initialize both configured integrators.
Call explicitly after constructing the state and system, before the first integration step. This method supports one snapshot or matching stacked states and systems, like
step.Bounds and collider caches are updated, pair and managed forces are evaluated, then the linear and rotational
initializehooks run. An integrator can use its hook to stagger velocities, for example by a backward half-kick. The default integrator hook leaves state unchanged.Initialization does not advance time, step count, contact/plastic history, or timestep callbacks. Managed force buffers follow their ordinary apply-and-clear semantics. No initialized flag is stored: callers must initialize new trajectories themselves and must not repeat integrator setup when continuing an initialized checkpoint.
Example
>>> state, system = System.initialize(state, system) >>> state, system = System.step(state, system, n=100)
- static evaluate_forces(state: State, system: System) tuple[State, System][source]#
Inspect instantaneous forces without advancing physical state.
Return updated forces and search caches while preserving contact and plastic history, queued loads, time, and integrator state. Queued loads contribute to the returned forces but remain available for the next physical step. This is an observation, not integrator initialization. Custom force callbacks must themselves be pure evaluations.
- check_overflow() None[source]#
Raise at a host boundary if this trajectory used truncated searches.
This synchronizes the status flag, so call once per completed chunk, outside compiled loops. A successful later query does not clear it.
- validate(state: State, *, strict_clumps: bool = True) None[source]#
Run opt-in host validation for a state against this system.
- static trajectory_rollout(state: State, system: System, *, n: int | None = None, stride: int = 1, strides: jax.Array | None = None, save_fn: Callable[[State, System], Any] = <PjitFunction of <function _save_state_system>>, unroll: int = 2) tuple[State, System, Any][source]#
Roll the system forward while collecting saved outputs at each frame.
The rollout always stores one output per frame via save_fn(state, system). The output of save_fn must be a pytree. Frame spacing can be either: - constant (stride), or - variable (strides jax.Array).
The rollout saves each frame after its integration steps, so it does not store the initial (step-0) state. To record it, save it before the rollout, or pass a leading
0entry in strides.- Parameters:
state (State) – Initial state.
system (System) – Initial system configuration.
n (int, optional) – Number of saved frames. Required when strides is None. The method ignores n when you provide strides.
stride (int, optional) – Constant number of integration steps between consecutive saves. Used only when strides is None. Defaults to 1.
strides (jax.Array, optional) – Integer 1D array of per-frame integration strides. When provided, this overrides stride, and the method infers n from len(strides).
save_fn (Callable[[State, System], Any], optional) – Function called after each saved frame. The rollout stacks its return pytree along axis 0 across frames. Defaults to returning (state, system).
unroll (int, optional) – Unroll factor passed to the outer jax.lax.scan. Defaults to 2.
- Returns:
(final_state, final_system, trajectory_like) where trajectory_like is the stacked output of save_fn.
- Return type:
- Raises:
ValueError – If n is missing while strides is None, or if strides is not 1D.
Example
>>> import jaxdem as jdem >>> import jax.numpy as jnp >>> >>> state = jdem.utils.grid_state(n_per_axis=(1, 1), spacing=1.0, radius=0.1) >>> system = jdem.System.create(state_shape=state.shape, dt=0.01) >>> >>> # Constant stride: n is required >>> final_state, final_system, traj = jdem.System.trajectory_rollout( ... state, system, n=10, stride=5 ... ) >>> >>> # Variable strides: n inferred from len(strides) >>> deltas = jnp.array([1, 2, 4, 8]) >>> final_state, final_system, traj = jdem.System.trajectory_rollout( ... state, system, strides=deltas ... )
- static step(state: State, system: System, *, n: int = 1) tuple[State, System][source]#
Advance the simulation by n integration steps.
- Parameters:
state (State) – Current state.
system (System) – Current system configuration.
n (int, optional) – Static number of integration steps. Defaults to 1. Use
step_dynamicfor a traced scalar count.
- Returns:
(final_state, final_system) after n steps.
- Return type:
Example
>>> # Advance by 10 steps >>> state_after_10_steps, system_after_10_steps = jdem.System.step(state, system, n=10)
Notes
Call
initializeexplicitly before stepping a new trajectory. This method performs no automatic force or integrator initialization. Checkpoint continuation uses its saved forces and integrator state without repeating initialization.This method does not check collider overflow, to avoid a host synchronization per step.
- static step_checked(state: State, system: System, *, n: int = 1) StepResult[source]#
Advance at most
nsteps, stopping and rolling back on failure.Unlike
step, this opt-in device loop checks finite particle state and spatial-search overflow after every step.result.stepscounts accepted steps;result.statuspreserves failure even though the returned state/system precede the failed step. An invalid input is returned unchanged with zero accepted steps. Checkresult.check()on the host before continuing or writing successful output.Initialization and physical setup validation remain explicit. A batch returns one status/count per snapshot. This dynamic stopping loop does not support reverse-mode differentiation. No automatic retry changes the timestep, neighbor capacity, or physical history.
- static step_dynamic(state: State, system: System, *, n: int | jax.Array) tuple[State, System][source]#
Advance by a scalar integer count that may be traced at run time.
This path uses
lax.fori_loopwith dynamic bounds. It is intended for compiled control flow such as variable trajectory strides. Reverse mode differentiation through dynamic loop bounds is not supported by JAX; usestepwith a Python integer for differentiable rollouts.
- static stack(systems: Sequence[System]) System[source]#
Concatenate a sequence of
Systemsnapshots into a trajectory or batch along axis 0.Use this method to collect simulation snapshots over time into a single System object where the leading dimension represents time, or to prepare a batched system.
- Parameters:
systems (Sequence[System]) – A sequence (e.g., list, tuple) of
Systeminstances to be stacked.- Returns:
A new
Systeminstance where each attribute is a JAX array with an additional leading dimension representing the stacked trajectory. For example, if input pos was (N, dim), output pos will be (T, N, dim).- Return type:
- static unstack(system: System) list[System][source]#
Split a stacked/batched
Systemalong the leading axis into a Python list.This method is the inverse of
System.stack:If stacked = System.stack([sys0, sys1, …]), then System.unstack(stacked) returns [sys0, sys1, …].
Notes
The method splits along axis 0 (the leading axis).
This method cannot split a single snapshot System.
- static minimize(state: State, system: System, *, max_steps: int = 10000, force_tol: float = 1e-12, torque_tol: float | None = None, advance_history: bool = False) MinimizationResult[source]#
Relax until both free-body residual norms meet tolerance.
Returns an iterable four-value result with
reasonandinfodiagnostics. Energy is reported per constituent sphere for the physical objective. Pair history is frozen unlessadvance_history=Truerequests displacement-driven updates after accepted optimizer steps. Residuals are mechanical forces and torques for force-based relaxation, or objective gradients for energies and targets differentiated by AD. Seejaxdem.minimizers.minimizefor convergence and optimizer behavior.