Note
Go to the end to download the full example code.
Geometric-asperity deformable particles: step-by-step pipeline#
This example builds a packing of deformable particles (DPs) whose nodes
are the surface asperities of a geometric-asperity body. Each DP is a
soft body. A DeformableParticleModel
(or a plastic variant) holds it together and applies the standard DP
energies: area/volume (ec), element-measure (em), edges (el),
bending (eb), and surface tension (gamma).
Pipeline:
create_ga_state()withparticle_type='dp'builds a State where each node carriesmass / nvandunion_volume / nv, and shares abond_idwith every other node in its body.distribute_bodies()places each body’s bounding sphere at the target initial packing fraction and applies a uniform-random per-body rotation. The rotation rotates the DP nodes around their centroid.create_dp_container()builds the bonded-force container on the placed state. A per-body convex-hull test (ga_surface_mask()) detects surface and interior nodes. A body with a solid core gets an interior node connected with “fan”-style struts. A hollow body has every node on the surface, with no extra edges.quasistatic_compress_to_packing_fraction()compresses to the target true-body packing fraction while the DP bonds keep each body self-consistent.Build a Verlet system (with the container as
bonded_force_model) and run a few time steps.
Imports
import jax
import numpy as np
jax.config.update("jax_enable_x64", True) # type: ignore[no-untyped-call]
import jaxdem as jdem
from jaxdem.utils.particle_creation import (
create_ga_state,
create_dp_container,
distribute_bodies,
)
from jaxdem.utils.packing_utils import (
compute_packing_fraction,
quasistatic_compress_to_packing_fraction,
)
Parameters#
6 deformable particles in 3D, 12 surface asperities each. core_type
is ‘hollow’ here — try ‘solid’ to get a central node connected to every
surface node via interior_edges='fan'.
N = 6
nv = 12
dim = 3
particle_radius = 0.5
asperity_radius = 0.1
initial_phi_bb = 0.2
target_phi = 0.14 # true-body phi — DP hulls are less dense than bounding spheres
seed = 0
1) Build the DP state#
DP nodes get equal shares of mass and union volume. bond_id groups
them by body. create_ga_state applies no rigid-body frame. Each
node’s pos_c is its world-frame position.
state = create_ga_state(
N=N,
nv=nv,
dim=dim,
particle_radius=particle_radius,
asperity_radius=asperity_radius,
particle_type="dp",
core_type="hollow",
n_samples=50_000,
seed=seed,
mesh_kwargs={"steps": 1_000},
)
print(f"DP state: N={state.N} nodes, {N} bodies")
DP state: N=72 nodes, 6 bodies
Place + orient bodies
state, box_size = distribute_bodies(
state,
phi=initial_phi_bb,
domain_type="periodic",
seed=seed,
randomize_orientation=True,
)
print(f"placed at bounding-sphere phi={initial_phi_bb}: box = {np.asarray(box_size)}")
placed at bounding-sphere phi=0.2: box = [2.65053448 2.65053448 2.65053448]
3) Build the DP container#
Build the container from the placed state so that its
initial_edge_lengths / initial_bendings / etc. reflect the
actual rest configuration. A per-body convex-hull test detects
is_surface. For hollow DPs every node is on the hull.
interior_edges='fan' connects any interior (core) node to every
surface node in its body. It is a no-op here because there are no
interior nodes, but it matters for core_type='solid'.
container = create_dp_container(
state,
em=1.0,
ec=1.0,
eb=0.1, # bending is stiff for small triangles; keep this modest
el=0.3,
gamma=None, # no surface tension in this example
interior_edges="fan",
)
print(f"container: {type(container).__name__}")
print(
f" elements: {container.elements.shape if container.elements is not None else None}"
)
print(f" edges: {container.edges.shape if container.edges is not None else None}")
print(
f" adjacency:{container.element_adjacency.shape if container.element_adjacency is not None else None}"
)
container: DeformableParticleModel
elements: (120, 3)
edges: (180, 2)
adjacency:(180, 2)
4) Compress with FIRE, DP bonds active#
Compression calls scale_to_packing_fraction, which translates every
body’s centroid uniformly (internal geometry preserved), and then FIRE
relaxes. Because the System holds the DP container, bonded forces keep
each body coherent. Contact forces from other bodies can still deform
it locally.
mats = [jdem.Material.create("elastic", young=1.0, poisson=0.5, density=1.0)]
mat_table = jdem.MaterialTable.from_materials(
mats, matcher=jdem.MaterialMatchmaker.create("harmonic")
)
fire_system = jdem.System.create(
state_shape=state.shape,
dt=1e-2,
minimizer=jdem.minimizers.fire,
minimizer_kw={"dt": 1e-2},
domain_type="periodic",
force_model_type="spring",
collider_type="naive",
mat_table=mat_table,
domain_kw={"box_size": box_size},
bonded_force_model=container,
)
phi0 = float(compute_packing_fraction(state, fire_system))
print(f"before compression: true-body phi = {phi0:.4f}")
state, fire_system, final_phi, final_pe = quasistatic_compress_to_packing_fraction(
state,
fire_system,
target_phi=target_phi,
step=5e-3,
max_n_min_steps_per_outer=50_000,
)
print(f"after compression: phi = {float(final_phi):.4f} PE = {float(final_pe):.3e}")
before compression: true-body phi = 0.0162
after compression: phi = 0.1400 PE = 9.176e-17
5) Short Verlet rollout (dynamics)#
dataclasses.replace swaps the FIRE setup for plain Verlet
and keeps everything else. This includes the post-compression box size,
the material table, the collider, and the DP bonded-force
container, so no manual rebuild is needed. Passing "" disables
the rotation integrator: DP nodes carry no rigid-body orientation.
import dataclasses
import jax.numpy as jnp
from jaxdem.integrators import LinearIntegrator, RotationIntegrator
sim_system = dataclasses.replace(
fire_system,
linear_integrator=LinearIntegrator.create("verlet"),
rotation_integrator=RotationIntegrator.create(
""
), # disable rotation: DPs have no rigid-body orientation
dt=jnp.asarray(1e-3, dtype=float),
)
for _ in range(100):
state, sim_system = sim_system.step(state, sim_system)
print("100 Verlet steps completed")
100 Verlet steps completed
Total running time of the script: (0 minutes 15.019 seconds)