Skip to content

Commit 0225856

Browse files
committed
Add broken nodal forces
1 parent ab9f39d commit 0225856

10 files changed

Lines changed: 311 additions & 51 deletions

File tree

diffmpm/element.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,9 @@ def compute_body_force(self, particles, gravity: float):
196196

197197
def _step(pid, args):
198198
f_ext, pmass, mapped_pos, el_nodes, gravity = args
199-
f_ext = f_ext.at[el_nodes[pid]].add(mapped_pos[pid] @ (pmass * gravity))
199+
f_ext = f_ext.at[el_nodes[pid]].add(
200+
mapped_pos[pid] @ (pmass[pid] * gravity)
201+
)
200202
return f_ext, pmass, mapped_pos, el_nodes, gravity
201203

202204
mapped_positions = self.shapefn(particles.reference_loc)
@@ -210,17 +212,30 @@ def _step(pid, args):
210212
)
211213
self.nodes.f_ext, _, _, _, _ = lax.fori_loop(0, len(particles), _step, args)
212214

213-
def apply_concentrated_nodal_force(self, particles, curr_time):
214-
def _step(fid, args):
215-
f_ext, cnf, curr_time = args
216-
factor = cnf[fid].value(curr_time)
217-
f_ext = f_ext.at[cnf[fid].node_ids].add(factor * cnf[fid].force)
218-
return f_ext, cnf, curr_time
215+
def apply_concentrated_nodal_forces(self, particles, curr_time):
216+
# def _step(fid, args):
217+
# f_ext, cnf, curr_time = args
218+
# breakpoint()
219+
# factor = cnf[fid].function.value(curr_time)
220+
# f_ext = f_ext.at[cnf[fid].node_ids].add(factor * cnf[fid].force)
221+
# return f_ext, cnf, curr_time
222+
223+
# args = (self.nodes.f_ext, self.concentrated_nodal_forces, curr_time)
224+
# self.nodes.f_ext, _, _ = lax.fori_loop(
225+
# 0, len(self.concentrated_nodal_forces), _step, args
226+
# )
227+
# breakpoint()
228+
import jax.debug as db
219229

220-
args = (self.nodes.f_ext, self.concentrated_nodal_forces, curr_time)
221-
self.nodes.f_ext, _, _ = lax.fori_loop(
222-
0, len(self.concentrated_nodal_forces), _step, args
223-
)
230+
for cnf in self.concentrated_nodal_forces:
231+
factor = cnf.function.value(curr_time)
232+
self.nodes.f_ext = self.nodes.f_ext.at[cnf.node_ids, 0, cnf.dir].add(
233+
factor * cnf.force
234+
)
235+
db.print(
236+
f"Factor: {factor}, curr_time: {curr_time}, "
237+
f"f_ext[3]: {self.nodes.f_ext[3].squeeze()}"
238+
)
224239

225240
def compute_internal_force(self, particles):
226241
r"""
@@ -276,7 +291,11 @@ def _step(pid, args):
276291

277292
def update_nodal_acceleration_velocity(self, particles, dt: float, *args):
278293
"""Update the nodal momentum based on total force on nodes."""
294+
import jax.debug as db
295+
279296
total_force = self.nodes.get_total_force()
297+
db.print(f"Before: nodes.velocity[3]: {self.nodes.velocity[3].squeeze()}")
298+
breakpoint()
280299
self.nodes.acceleration = self.nodes.acceleration.at[:].set(
281300
jnp.nan_to_num(jnp.divide(total_force, self.nodes.mass))
282301
)
@@ -287,6 +306,7 @@ def update_nodal_acceleration_velocity(self, particles, dt: float, *args):
287306
self.nodes.momentum = self.nodes.momentum.at[:].set(
288307
self.nodes.mass * self.nodes.velocity
289308
)
309+
db.print(f"After: nodes.velocity[3]: {self.nodes.velocity[3].squeeze()}")
290310

291311
def apply_boundary_constraints(self, *args):
292312
"""Apply boundary conditions for nodal velocity."""
@@ -720,7 +740,7 @@ def _step(pid, args):
720740
) = args
721741
# TODO: correct matrix multiplication for n-d
722742
# update = -(pvol[pid]) * pstress[pid] @ mapped_grads[pid]
723-
force = jnp.zeros((mapped_grads.shape[0], 1, 2))
743+
force = jnp.zeros((mapped_grads.shape[1], 1, 2))
724744
force = force.at[:, 0, 0].set(
725745
mapped_grads[pid][:, 0] * pstress[pid][0]
726746
+ mapped_grads[pid][:, 1] * pstress[pid][3]

diffmpm/forces.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
from collections import namedtuple
2+
from jax.tree_util import register_pytree_node
23

34
NodalForce = namedtuple("NodalForce", ("node_ids", "function", "dir", "force"))
45
ParticleTraction = namedtuple(
56
"ParticleTraction", ("pset", "function", "dir", "traction")
67
)
8+
register_pytree_node(
9+
NodalForce,
10+
lambda xs: (tuple(xs), None), # tell JAX how to unpack to an iterable
11+
lambda _, xs: NodalForce(*xs), # tell JAX how to pack back into a NodalForce
12+
)
13+
register_pytree_node(
14+
ParticleTraction,
15+
lambda xs: (tuple(xs), None), # tell JAX how to unpack to an iterable
16+
lambda _, xs: ParticleTraction(*xs), # tell JAX how to pack back
17+
)

diffmpm/functions.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import abc
22
import jax.numpy as jnp
3+
from jax.tree_util import register_pytree_node_class
34

45

56
class Function(abc.ABC):
@@ -11,14 +12,24 @@ def value(self):
1112
...
1213

1314

15+
@register_pytree_node_class
1416
class Unit(Function):
1517
def __init__(self, id):
1618
super().__init__(id)
1719

1820
def value(self, x):
1921
return 1.0
2022

23+
def tree_flatten(self):
24+
return ((), (self.id))
2125

26+
@classmethod
27+
def tree_unflatten(cls, aux_data, children):
28+
del children
29+
return cls(*aux_data)
30+
31+
32+
@register_pytree_node_class
2233
class Linear(Function):
2334
def __init__(self, id, xvalues, fxvalues):
2435
self.xvalues = xvalues
@@ -27,3 +38,11 @@ def __init__(self, id, xvalues, fxvalues):
2738

2839
def value(self, x):
2940
return jnp.interp(x, self.xvalues, self.fxvalues)
41+
42+
def tree_flatten(self):
43+
return ((), (self.id, self.xvalues, self.fxvalues))
44+
45+
@classmethod
46+
def tree_unflatten(cls, aux_data, children):
47+
del children
48+
return cls(*aux_data)

diffmpm/io.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def _parse_external_loading(self, config):
8686
fn = Unit(-1)
8787
cnf = NodalForce(
8888
node_ids=jnp.array(cnfconfig["node_ids"]),
89-
math_function=fn,
89+
function=fn,
9090
dir=cnfconfig["dir"],
9191
force=cnfconfig["force"],
9292
)
@@ -95,12 +95,12 @@ def _parse_external_loading(self, config):
9595
pst_list = []
9696
for pstconfig in config["external_loading"]["particle_surface_traction"]:
9797
pst = ParticleTraction(
98-
pset=jnp.array(cnfconfig["pset"]),
99-
math_function=self.parsed_config["math_functions"][
98+
pset=jnp.array(pstconfig["pset"]),
99+
function=self.parsed_config["math_functions"][
100100
pstconfig["math_function_id"]
101101
],
102-
dir=cnfconfig["dir"],
103-
traction=cnfconfig["traction"],
102+
dir=pstconfig["dir"],
103+
traction=pstconfig["traction"],
104104
)
105105
pst_list.append(pst)
106106
external_loading["concentrated_nodal_forces"] = cnf_list

diffmpm/particle.py

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import jax.numpy as jnp
44
from jax import jit, vmap, lax
5+
import jax.debug as db
56
from jax.tree_util import register_pytree_node_class
67

78
from diffmpm.element import _Element
@@ -45,8 +46,7 @@ def __init__(
4546
self.element_ids = element_ids
4647
if len(loc.shape) != 3:
4748
raise ValueError(
48-
f"`loc` should be of size (nparticles, 1, ndim); "
49-
f"found {loc.shape}"
49+
f"`loc` should be of size (nparticles, 1, ndim); " f"found {loc.shape}"
5050
)
5151
self.loc = loc
5252

@@ -140,16 +140,13 @@ def set_mass_volume(self, m: float | jnp.ndarray):
140140
self.mass = m
141141
else:
142142
raise ValueError(
143-
f"Incompatible shapes. Expected {self.mass.shape}, "
144-
f"found {m.shape}."
143+
f"Incompatible shapes. Expected {self.mass.shape}, " f"found {m.shape}."
145144
)
146145
self.volume = jnp.divide(self.mass, self.material.properties["density"])
147146

148147
def compute_volume(self, elements: _Element):
149148
elements.compute_volume()
150-
particles_per_element = jnp.bincount(
151-
self.element_ids, length=len(elements.ids)
152-
)
149+
particles_per_element = jnp.bincount(self.element_ids, length=len(elements.ids))
153150
vol = (
154151
elements.volume.squeeze((1, 2))[self.element_ids]
155152
/ particles_per_element[self.element_ids]
@@ -238,29 +235,26 @@ def compute_strain(self, elements: _Element, dt: float):
238235
dt : float
239236
Timestep.
240237
"""
241-
mapped_coords = vmap(elements.id_to_node_loc)(self.element_ids).squeeze(
242-
2
243-
)
238+
mapped_coords = vmap(elements.id_to_node_loc)(self.element_ids).squeeze(2)
244239
dn_dx_ = vmap(elements.shapefn_grad)(
245240
self.reference_loc[:, jnp.newaxis, ...], mapped_coords
246241
)
247242
self.strain_rate = self._compute_strain_rate(dn_dx_, elements)
248243
self.dstrain = self.dstrain.at[:].set(self.strain_rate * dt)
244+
245+
# db.print(f"compute_strain() - dstrain: {self.dstrain.squeeze()[3, :2]}")
249246
self.strain = self.strain.at[:].add(self.dstrain)
247+
# db.print(f"compute_strain() - strain: {self.strain.squeeze()[3, :2]}")
250248
centroids = jnp.zeros_like(self.loc)
251249
dn_dx_centroid_ = vmap(elements.shapefn_grad)(
252250
centroids[:, jnp.newaxis, ...], mapped_coords
253251
)
254-
strain_rate_centroid = self._compute_strain_rate(
255-
dn_dx_centroid_, elements
256-
)
252+
strain_rate_centroid = self._compute_strain_rate(dn_dx_centroid_, elements)
257253
ndim = self.loc.shape[-1]
258-
self.dvolumetric_strain = dt * strain_rate_centroid[:, :ndim].sum(
259-
axis=1
254+
self.dvolumetric_strain = dt * strain_rate_centroid[:, :ndim].sum(axis=1)
255+
self.volumetric_strain_centroid = self.volumetric_strain_centroid.at[:].add(
256+
self.dvolumetric_strain
260257
)
261-
self.volumetric_strain_centroid = self.volumetric_strain_centroid.at[
262-
:
263-
].add(self.dvolumetric_strain)
264258

265259
def _compute_strain_rate(self, dn_dx: jnp.ndarray, elements: _Element):
266260
"""
@@ -279,6 +273,7 @@ def _compute_strain_rate(self, dn_dx: jnp.ndarray, elements: _Element):
279273
self.element_ids
280274
) # (nparticles, 2, 1)
281275

276+
# db.print(f"mapped_val[3]: {mapped_vel[3, :, 0]}")
282277
# TODO: This will need to change to be more general for ndim.
283278
# breakpoint()
284279
# L = jnp.einsum("ijk, ikj -> ijk", dn_dx, mapped_vel.squeeze(-1)).sum(
@@ -295,9 +290,7 @@ def _step(pid, args):
295290
matmul = dndx[pid].T @ nvel[pid]
296291
strain_rate = strain_rate.at[pid, 0].add(matmul[0, 0])
297292
strain_rate = strain_rate.at[pid, 1].add(matmul[1, 1])
298-
strain_rate = strain_rate.at[pid, 3].add(
299-
matmul[0, 1] + matmul[1, 0]
300-
)
293+
strain_rate = strain_rate.at[pid, 3].add(matmul[0, 1] + matmul[1, 0])
301294
return dndx, nvel, strain_rate
302295

303296
args = (dn_dx, temp, strain_rate)
@@ -314,18 +307,12 @@ def compute_stress(self, *args):
314307
particles. The stress calculated by the material is then
315308
added to the particles current stress values.
316309
"""
317-
self.stress = self.stress.at[:].add(
318-
self.material.compute_stress(self.dstrain)
319-
)
310+
self.stress = self.stress.at[:].add(self.material.compute_stress(self.dstrain))
320311

321312
def update_volume(self, *args):
322313
"""Update volume based on central strain rate."""
323-
self.volume = self.volume.at[:, 0, :].multiply(
324-
1 + self.dvolumetric_strain
325-
)
326-
self.density = self.density.at[:, 0, :].divide(
327-
1 + self.dvolumetric_strain
328-
)
314+
self.volume = self.volume.at[:, 0, :].multiply(1 + self.dvolumetric_strain)
315+
self.density = self.density.at[:, 0, :].divide(1 + self.dvolumetric_strain)
329316

330317

331318
if __name__ == "__main__":

diffmpm/solver.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,17 @@ def tree_unflatten(cls, aux_data, children):
5656
def solve(self, nsteps: int, gravity: float | jnp.ndarray):
5757
result = defaultdict(list)
5858
for step in tqdm(range(nsteps)):
59+
# breakpoint()
5960
self.mpm_scheme.compute_nodal_kinematics()
6061
self.mpm_scheme.precompute_stress_strain()
6162
self.mpm_scheme.compute_forces(gravity, step)
6263
self.mpm_scheme.compute_particle_kinematics()
6364
self.mpm_scheme.postcompute_stress_strain()
6465
for pset in self.mesh.particles:
65-
# result["position"].append(pset.loc)
66-
# result["velocity"].append(pset.velocity)
67-
result["stress"].append(pset.stress)
66+
result["position"].append(pset.loc)
67+
result["velocity"].append(pset.velocity)
68+
result["stress"].append(pset.stress[:, :2, 0])
69+
result["strain"].append(pset.strain[:, :2, 0])
6870

6971
result = {k: jnp.asarray(v) for k, v in result.items()}
7072
return result

examples/mpm-nodal-forces.toml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# The `meta` group contains top level attributes that govern the
2+
# behaviour of the MPM Solver.
3+
#
4+
# Attributes:
5+
# title: The title of the experiment. This is just for the user's
6+
# reference.
7+
# type: The type of simulation to be used. Allowed values are
8+
# {"MPMExplicit"}
9+
# scheme: The MPM Scheme used for simulation. Allowed values are
10+
# {"usl", "usf"}
11+
# dt: Timestep used in the simulation.
12+
# nsteps: Number of steps to run the simulation for.
13+
[meta]
14+
title = "uniaxial-nodal-traction"
15+
type = "MPMExplicit"
16+
dimension = 2
17+
scheme = "usf"
18+
dt = 0.001
19+
nsteps = 20
20+
velocity_update = true
21+
22+
[output]
23+
type = "hdf5"
24+
file = "results/example_2d_out.hdf5"
25+
step_frequency = 5
26+
27+
[mesh]
28+
# type = "file"
29+
# file = "mesh-1d.txt"
30+
# boundary_nodes = "boundary-1d.txt"
31+
# particle_element_ids = "particles-elements.txt"
32+
type = "generator"
33+
nelements = [3, 1]
34+
element_length = [0.1, 0.1]
35+
particle_element_ids = [0]
36+
element = "Quadrilateral4Node"
37+
38+
[[mesh.constraints]]
39+
node_ids = [0, 4]
40+
dir = 0
41+
velocity = 0.0
42+
43+
[[materials]]
44+
id = 0
45+
density = 1000
46+
poisson_ratio = 0
47+
youngs_modulus = 1000000
48+
type = "LinearElastic"
49+
50+
[[particles]]
51+
file = "examples/particles-2d-nodal-force.json"
52+
material_id = 0
53+
init_velocity = 0.0
54+
55+
[external_loading]
56+
gravity = [0, 0]
57+
58+
[[external_loading.concentrated_nodal_forces]]
59+
node_ids = [3, 7]
60+
math_function_id = 0
61+
dir = 0
62+
force = 0.01
63+
64+
[[external_loading.particle_surface_traction]]
65+
pset = [1]
66+
dir = 1
67+
math_function_id = 0
68+
traction = 10.5
69+
70+
[[math_functions]]
71+
type = "Linear"
72+
xvalues = [0.0, 0.5, 1.0]
73+
fxvalues = [0.0, 1.0, 1.0]

0 commit comments

Comments
 (0)