Skip to content

Commit 6b54359

Browse files
committed
Many changes to make solver JIT compatible.
This needed various changes in the constructors of the different classes along with registering them as Pytree nodes in the jax registry so that I can pass around `self` as an argument. The JIT solver still needs work for branching and conditions but just for USF it works.
1 parent e394844 commit 6b54359

7 files changed

Lines changed: 869 additions & 89 deletions

File tree

diffmpm/material.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from jax.tree_util import register_pytree_node_class
2+
3+
4+
@register_pytree_node_class
15
class Material:
26
"""
37
Base material class.
@@ -16,3 +20,14 @@ def __init__(self, E, density):
1620
"""
1721
self.E = E
1822
self.density = density
23+
24+
def tree_flatten(self):
25+
return (tuple(), (self.E, self.density))
26+
27+
@classmethod
28+
def tree_unflatten(cls, aux_data, children):
29+
del children
30+
return cls(*aux_data)
31+
32+
def __repr__(self):
33+
return f"Material(E={self.E}, density={self.density})"

diffmpm/mesh.py

Lines changed: 208 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import jax.numpy as jnp
2-
from jax import vmap, lax
2+
from jax import vmap, lax, jit
3+
from tqdm import tqdm
34
from diffmpm.node import Nodes
45
from diffmpm.particle import Particles
56
from diffmpm.shapefn import ShapeFn
7+
from jax.tree_util import register_pytree_node_class
8+
from jax_tqdm import loop_tqdm
9+
from functools import partial
610

11+
from jax import debug
712

13+
14+
@register_pytree_node_class
815
class Mesh1D:
916
"""
1017
1D Mesh class with nodes, elements, and particles.
@@ -16,8 +23,14 @@ def __init__(
1623
material,
1724
domain_size,
1825
boundary_nodes,
26+
*,
1927
ppe=1,
2028
particle_distribution="uniform",
29+
elements=None,
30+
nodes=None,
31+
particles=None,
32+
shapefn=None,
33+
dim=1,
2134
):
2235
"""
2336
Construct a 1D Mesh.
@@ -36,20 +49,82 @@ def __init__(
3649
ppe : int
3750
Number of particles per element in Mesh.
3851
"""
39-
self.dim = 1
52+
self.dim = dim
4053
self.material = material
41-
self.shapefn = ShapeFn(self.dim)
54+
self.shapefn = (
55+
ShapeFn(self.dim)
56+
if (
57+
shapefn is None
58+
or type(shapefn) is object
59+
or isinstance(shapefn, Mesh1D)
60+
)
61+
else shapefn
62+
)
4263
self.domain_size = domain_size
4364
self.nelements = nelements
4465
self.element_length = domain_size / nelements
45-
self.elements = jnp.arange(nelements)
46-
self.nodes = Nodes(nelements + 1)
47-
self.nodes.position = jnp.arange(nelements + 1) * self.element_length
66+
self.elements = jnp.arange(nelements) if elements is None else elements
67+
nnodes = nelements + 1
68+
self.nodes = (
69+
Nodes(
70+
nnodes,
71+
jnp.arange(nelements + 1) * self.element_length,
72+
jnp.zeros(nnodes),
73+
jnp.zeros(nnodes),
74+
jnp.zeros(nnodes),
75+
jnp.zeros(nnodes),
76+
jnp.zeros(nnodes),
77+
jnp.zeros(nnodes),
78+
)
79+
if (
80+
nodes is None
81+
or type(nodes) is object
82+
or isinstance(nodes, Mesh1D)
83+
)
84+
else nodes
85+
)
4886
self.boundary_nodes = boundary_nodes
4987
self.ppe = ppe
50-
self.particles = self._init_particles(particle_distribution)
88+
self.particles = (
89+
self._init_particles(particle_distribution)
90+
if (
91+
particles is None
92+
or type(particles) is object
93+
or isinstance(particles, Mesh1D)
94+
)
95+
else particles
96+
)
5197
return
5298

99+
def tree_flatten(self):
100+
"Flatten Pytree for JAX JIT compatibility."
101+
children = (self.nodes, self.particles)
102+
aux_data = (
103+
(
104+
self.nelements,
105+
self.material,
106+
self.domain_size,
107+
self.boundary_nodes,
108+
),
109+
{
110+
"dim": self.dim,
111+
"shapefn": self.shapefn,
112+
"elements": self.elements,
113+
"ppe": self.ppe,
114+
},
115+
)
116+
return (children, aux_data)
117+
118+
@classmethod
119+
def tree_unflatten(cls, aux_data, children):
120+
"Unflatten Pytree for JAX JIT compatibility."
121+
return cls(
122+
*aux_data[0],
123+
nodes=children[0],
124+
particles=children[1],
125+
**aux_data[1],
126+
)
127+
53128
def _init_particles(self, distribution="uniform"):
54129
temp_px = jnp.linspace(0, self.element_length, self.ppe + 1)
55130
if distribution == "uniform":
@@ -62,16 +137,23 @@ def _init_particles(self, distribution="uniform"):
62137
particle_element_ids = jnp.repeat(
63138
jnp.arange(self.nelements), self.ppe
64139
)
140+
nparticles = (self.ppe * self.nelements,)
65141
particles = Particles(
66142
pmass,
67143
particles_x,
68144
particles_xi,
69-
self.material,
70145
self.material.density,
71-
self.ppe,
72-
self.nelements,
73146
particle_element_ids,
74-
self.domain_size,
147+
jnp.zeros(nparticles),
148+
jnp.zeros(nparticles),
149+
jnp.zeros(nparticles),
150+
jnp.zeros(nparticles),
151+
jnp.zeros(nparticles),
152+
jnp.zeros(nparticles),
153+
ppe=self.ppe,
154+
nelements=self.nelements,
155+
nparticles=nparticles,
156+
material=self.material,
75157
)
76158
return particles
77159
else:
@@ -101,6 +183,17 @@ def _get_element_node_vel(self, element_idx):
101183
"""
102184
return self.nodes.velocity[jnp.asarray([element_idx, element_idx + 1])]
103185

186+
def set_particle_velocity(self, vel):
187+
"""
188+
Set the velocities of all particles.
189+
190+
Arguments
191+
---------
192+
vel : array_like
193+
Velocity for each particle in the mesh.
194+
"""
195+
self.particles.velocity = vel
196+
104197
def _update_particle_element_ids(self):
105198
"""
106199
Find the element that the particles belong to.
@@ -109,6 +202,7 @@ def _update_particle_element_ids(self):
109202
element, it sets the element index to -1.
110203
"""
111204

205+
@jit
112206
def f(x):
113207
idl = (
114208
len(self.nodes.position)
@@ -332,6 +426,7 @@ def _transfer_node_force_vel_par(self, dt):
332426
)
333427
)
334428

429+
# @jit
335430
def _update_par_pos_node_mom(self, dt):
336431
"""
337432
Update particle position based on nodal momentum.
@@ -451,6 +546,7 @@ def step(pid, args):
451546
0, len(self.particles), step, args
452547
)
453548

549+
# @jit
454550
def _update_node_fint_par_mass(self):
455551
r"""
456552
Update the nodal internal force based on particle mass.
@@ -500,6 +596,7 @@ def step(pid, args):
500596
0, len(self.particles), step, args
501597
)
502598

599+
# @jit
503600
def _update_node_fext_par_fext(self):
504601
r"""
505602
Update the nodal external force based on particle f_ext.
@@ -529,21 +626,30 @@ def step(pid, args):
529626
0, len(self.particles), step, args
530627
)
531628

532-
def solve(self, **kwargs):
533-
"""Solve the mesh using explicit scheme (for now)."""
629+
def solve(self, nsteps=100, mpm_scheme="USF", **kwargs):
630+
"""
631+
Solve the mesh using explicit scheme (for now).
632+
"""
534633
# TODO: Add flow control and argument checking
535-
result = {"position": [], "velocity": []}
536-
b1 = jnp.pi * 0.5 / self.domain_size
537-
self.particles.velocity = 0.1 * jnp.sin(b1 * self.particles.x)
538-
for step in range(kwargs["nsteps"]):
634+
result = {
635+
"position": [],
636+
"velocity": [],
637+
}
638+
for _ in tqdm(range(nsteps)):
639+
# print("="*30)
539640
self._update_particle_natural_coords()
641+
# print(f"P.xi: {self.particles.xi}")
540642
self._update_particle_element_ids()
643+
# print(f"P.eid: {self.particles.element_ids}")
541644
self._update_node_momentum_par_vel()
645+
# print(f"P.v: {self.particles.velocity}")
542646
self._update_node_mass_par_mass()
647+
# print(f"P.m: {self.particles.mass}")
543648
self._update_nodes_bc_mom_vel()
544-
if kwargs["mpm_scheme"] == "USF":
649+
if mpm_scheme == "USF":
545650
self._update_nodes_mom_vel()
546651
self._update_particle_strain(kwargs["dt"])
652+
# print(f"P.strain: {self.particles.strain}")
547653
self._update_par_vol_density()
548654
self._update_particle_stress()
549655

@@ -553,16 +659,98 @@ def solve(self, **kwargs):
553659
self._update_node_momentum_force(kwargs["dt"])
554660
self._transfer_node_force_vel_par(kwargs["dt"])
555661
self._update_par_pos_node_mom(kwargs["dt"])
556-
if kwargs["mpm_scheme"] == "MUSL":
662+
if mpm_scheme == "MUSL":
557663
self._update_node_momentum_par_vel()
558664
self._update_nodes_bc_mom_vel()
559665

560-
if kwargs["mpm_scheme"] == "MUSL" or kwargs["mpm_scheme"] == "USL":
666+
if mpm_scheme in {"MUSL", "USL"}:
561667
self._update_nodes_mom_vel()
562668
self._update_particle_strain(kwargs["dt"])
563669
self._update_par_vol_density()
564670
self._update_particle_stress()
565671
self.nodes.reset_values()
566672
result["position"].append(self.particles.x)
567673
result["velocity"].append(self.particles.velocity)
674+
result = {k: jnp.asarray(v) for k, v in result.items()}
675+
return result
676+
677+
# @partial(jit, static_argnames=["nsteps", "mpm_scheme"])
678+
def solve_jit(self, nsteps=100, mpm_scheme=0, **kwargs):
679+
"""
680+
Solve the mesh using explicit scheme (for now).
681+
682+
mpm_scheme:
683+
{
684+
"USF": 0,
685+
"USL": 1,
686+
"MUSL": 2
687+
}
688+
"""
689+
# TODO: Add flow control and argument checking
690+
result = {
691+
"position": jnp.zeros((nsteps, self.particles.nparticles)),
692+
"velocity": jnp.zeros((nsteps, self.particles.nparticles)),
693+
}
694+
695+
@loop_tqdm(nsteps)
696+
def step(i, data):
697+
self, mpm_scheme, dt, result = data
698+
self._update_particle_natural_coords()
699+
self._update_particle_element_ids()
700+
self._update_node_momentum_par_vel()
701+
self._update_node_mass_par_mass()
702+
self._update_nodes_bc_mom_vel()
703+
704+
# debug.breakpoint()
705+
# if mpm_scheme == 0:
706+
# def f(data):
707+
# s, dt = data
708+
# s._update_nodes_mom_vel()
709+
# s._update_particle_strain(dt)
710+
# s._update_par_vol_density()
711+
# s._update_particle_stress()
712+
# return None
713+
714+
# lax.cond(mpm_scheme == 0, f, lambda data: None, (self, dt))
715+
self._update_nodes_mom_vel()
716+
self._update_particle_strain(dt)
717+
self._update_par_vol_density()
718+
self._update_particle_stress()
719+
720+
self._update_node_fint_par_mass()
721+
self._update_node_fext_par_fext()
722+
self._update_nodes_bc_force()
723+
self._update_node_momentum_force(dt)
724+
self._transfer_node_force_vel_par(dt)
725+
self._update_par_pos_node_mom(dt)
726+
727+
# def f(s):
728+
# s._update_node_momentum_par_vel()
729+
# s._update_nodes_bc_mom_vel()
730+
# return None
731+
732+
# lax.cond(mpm_scheme == 2, f, lambda s: None, self)
733+
734+
# def f(data):
735+
# s, dt = data
736+
# s._update_nodes_mom_vel()
737+
# s._update_particle_strain(dt)
738+
# s._update_par_vol_density()
739+
# s._update_particle_stress()
740+
# return None
741+
742+
# lax.cond(mpm_scheme in {1, 2}, f, lambda data: None, (self, dt))
743+
self.nodes.reset_values()
744+
result["position"] = (
745+
result["position"].at[i, :].set(self.particles.x)
746+
)
747+
result["velocity"] = (
748+
result["velocity"].at[i, :].set(self.particles.velocity)
749+
)
750+
return (self, mpm_scheme, dt, result)
751+
752+
_, _, _, result = lax.fori_loop(
753+
0, nsteps, step, (self, 0, kwargs["dt"], result)
754+
)
755+
result = {k: jnp.asarray(v) for k, v in result.items()}
568756
return result

0 commit comments

Comments
 (0)