Skip to content

Commit ab9f39d

Browse files
committed
Add concentrated forces, math funcs & code cleanup
1 parent 5b1fb5c commit ab9f39d

12 files changed

Lines changed: 328 additions & 240 deletions

File tree

diffmpm/element.py

Lines changed: 91 additions & 190 deletions
Large diffs are not rendered by default.

diffmpm/forces.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from collections import namedtuple
2+
3+
NodalForce = namedtuple("NodalForce", ("node_ids", "function", "dir", "force"))
4+
ParticleTraction = namedtuple(
5+
"ParticleTraction", ("pset", "function", "dir", "traction")
6+
)

diffmpm/functions.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import abc
2+
import jax.numpy as jnp
3+
4+
5+
class Function(abc.ABC):
6+
def __init__(self, id):
7+
self.id = id
8+
9+
@abc.abstractmethod
10+
def value(self):
11+
...
12+
13+
14+
class Unit(Function):
15+
def __init__(self, id):
16+
super().__init__(id)
17+
18+
def value(self, x):
19+
return 1.0
20+
21+
22+
class Linear(Function):
23+
def __init__(self, id, xvalues, fxvalues):
24+
self.xvalues = xvalues
25+
self.fxvalues = fxvalues
26+
super().__init__(id)
27+
28+
def value(self, x):
29+
return jnp.interp(x, self.xvalues, self.fxvalues)

diffmpm/io.py

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import json
22
import tomllib as tl
3+
from collections import namedtuple
34

45
import jax.numpy as jnp
5-
import numpy as np
66

77
from diffmpm import element as mpel
88
from diffmpm import material as mpmat
99
from diffmpm import mesh as mpmesh
1010
from diffmpm.constraint import Constraint
11-
from diffmpm.node import Nodes
11+
from diffmpm.forces import NodalForce, ParticleTraction
12+
from diffmpm.functions import Unit, Linear
1213
from diffmpm.particle import Particles
1314

1415

1516
class Config:
1617
def __init__(self, filepath):
1718
self._filepath = filepath
18-
self.config = {}
19+
self.parsed_config = {}
1920
self.parse()
2021

2122
def parse(self):
@@ -26,14 +27,16 @@ def parse(self):
2627
self._parse_output(self._fileconfig)
2728
self._parse_materials(self._fileconfig)
2829
self._parse_particles(self._fileconfig)
30+
self._parse_math_functions(self._fileconfig)
31+
self._parse_external_loading(self._fileconfig)
2932
mesh = self._parse_mesh(self._fileconfig)
3033
return mesh
3134

3235
def _parse_meta(self, config):
33-
self.config["meta"] = config["meta"]
36+
self.parsed_config["meta"] = config["meta"]
3437

3538
def _parse_output(self, config):
36-
self.config["output"] = config["output"]
39+
self.parsed_config["output"] = config["output"]
3740

3841
def _parse_materials(self, config):
3942
materials = []
@@ -42,21 +45,67 @@ def _parse_materials(self, config):
4245
mat_cls = getattr(mpmat, mat_type)
4346
mat = mat_cls(mat_config)
4447
materials.append(mat)
45-
self.config["materials"] = materials
48+
self.parsed_config["materials"] = materials
4649

4750
def _parse_particles(self, config):
4851
particle_sets = []
4952
for pset_config in config["particles"]:
50-
pmat = self.config["materials"][pset_config["material_id"]]
53+
pmat = self.parsed_config["materials"][pset_config["material_id"]]
5154
with open(pset_config["file"], "r") as f:
5255
ploc = jnp.asarray(json.load(f))
5356
peids = jnp.zeros(ploc.shape[0], dtype=jnp.int32)
5457
pset = Particles(ploc, pmat, peids)
55-
pset.velocity = pset.velocity.at[:].set(
56-
pset_config["init_velocity"]
57-
)
58+
pset.velocity = pset.velocity.at[:].set(pset_config["init_velocity"])
5859
particle_sets.append(pset)
59-
self.config["particles"] = particle_sets
60+
self.parsed_config["particles"] = particle_sets
61+
62+
def _parse_math_functions(self, config):
63+
flist = []
64+
for i, fnconfig in enumerate(config["math_functions"]):
65+
if fnconfig["type"] == "Linear":
66+
fn = Linear(
67+
i,
68+
jnp.array(fnconfig["xvalues"]),
69+
jnp.array(fnconfig["fxvalues"]),
70+
)
71+
flist.append(fn)
72+
else:
73+
raise NotImplementedError(
74+
"Function type other than `Linear` not yet supported"
75+
)
76+
self.parsed_config["math_functions"] = flist
77+
78+
def _parse_external_loading(self, config):
79+
external_loading = {}
80+
external_loading["gravity"] = jnp.array(config["external_loading"]["gravity"])
81+
cnf_list = []
82+
for cnfconfig in config["external_loading"]["concentrated_nodal_forces"]:
83+
if "math_function_id" in cnfconfig:
84+
fn = self.parsed_config["math_functions"][cnfconfig["math_function_id"]]
85+
else:
86+
fn = Unit(-1)
87+
cnf = NodalForce(
88+
node_ids=jnp.array(cnfconfig["node_ids"]),
89+
math_function=fn,
90+
dir=cnfconfig["dir"],
91+
force=cnfconfig["force"],
92+
)
93+
cnf_list.append(cnf)
94+
95+
pst_list = []
96+
for pstconfig in config["external_loading"]["particle_surface_traction"]:
97+
pst = ParticleTraction(
98+
pset=jnp.array(cnfconfig["pset"]),
99+
math_function=self.parsed_config["math_functions"][
100+
pstconfig["math_function_id"]
101+
],
102+
dir=cnfconfig["dir"],
103+
traction=cnfconfig["traction"],
104+
)
105+
pst_list.append(pst)
106+
external_loading["concentrated_nodal_forces"] = cnf_list
107+
external_loading["particle_surface_traction"] = pst_list
108+
self.parsed_config["external_loading"] = external_loading
60109

61110
def _parse_mesh(self, config):
62111
element_cls = getattr(mpel, config["mesh"]["element"])
@@ -65,16 +114,19 @@ def _parse_mesh(self, config):
65114
(jnp.asarray(c["node_ids"]), Constraint(c["dir"], c["velocity"]))
66115
for c in config["mesh"]["constraints"]
67116
]
68-
if config["mesh"]["type"] == "file":
69-
nodes_loc = jnp.asarray(np.loadtxt(config["mesh"]["file"]))
70-
# nodes = Nodes(len(nodes_loc), nodes_loc)
71-
# elements = element_cls(nelements, el_len, boundary_nodes)
72-
elif config["mesh"]["type"] == "generator":
117+
if config["mesh"]["type"] == "generator":
73118
elements = element_cls(
74119
config["mesh"]["nelements"],
75120
config["mesh"]["element_length"],
76121
constraints,
122+
concentrated_nodal_forces=self.parsed_config["external_loading"][
123+
"concentrated_nodal_forces"
124+
],
125+
)
126+
else:
127+
raise NotImplementedError(
128+
"Mesh type other than `generator` not yet supported."
77129
)
78-
self.config["elements"] = elements
79-
mesh = mesh_cls(self.config)
130+
self.parsed_config["elements"] = elements
131+
mesh = mesh_cls(self.parsed_config)
80132
return mesh

diffmpm/particle.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def update_natural_coords(self, elements: _Element):
184184
self.reference_loc = xi_coords
185185

186186
def update_position_velocity(
187-
self, elements: _Element, dt: float, velocity_update: bool = False
187+
self, elements: _Element, dt: float, velocity_update: bool
188188
):
189189
"""
190190
Transfer nodal velocity to particles and update particle position.
@@ -281,14 +281,17 @@ def _compute_strain_rate(self, dn_dx: jnp.ndarray, elements: _Element):
281281

282282
# TODO: This will need to change to be more general for ndim.
283283
# breakpoint()
284-
# L = jnp.einsum("ijk, ikj -> ijk", dn_dx, mapped_vel).sum(axis=2)
284+
# L = jnp.einsum("ijk, ikj -> ijk", dn_dx, mapped_vel.squeeze(-1)).sum(
285+
# axis=2
286+
# )
285287
# strain_rate = strain_rate.at[:, 0, :].add(L)
286288

287289
# For 2d
288-
temp = mapped_vel.squeeze()
290+
temp = mapped_vel.squeeze(2)
289291

290292
def _step(pid, args):
291293
dndx, nvel, strain_rate = args
294+
# breakpoint()
292295
matmul = dndx[pid].T @ nvel[pid]
293296
strain_rate = strain_rate.at[pid, 0].add(matmul[0, 0])
294297
strain_rate = strain_rate.at[pid, 1].add(matmul[1, 1])
@@ -298,7 +301,9 @@ def _step(pid, args):
298301
return dndx, nvel, strain_rate
299302

300303
args = (dn_dx, temp, strain_rate)
304+
# _step(0, args)
301305
_, _, strain_rate = lax.fori_loop(0, self.loc.shape[0], _step, args)
306+
# breakpoint()
302307
return strain_rate
303308

304309
def compute_stress(self, *args):

diffmpm/scheme.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55

66
class _MPMScheme(abc.ABC):
7-
def __init__(self, mesh, dt):
7+
def __init__(self, mesh, dt, velocity_update):
88
self.mesh = mesh
9+
self.velocity_update = velocity_update
910
self.dt = dt
1011

1112
def compute_nodal_kinematics(self):
@@ -20,9 +21,12 @@ def compute_stress_strain(self):
2021
self.mesh.apply_on_particles("update_volume")
2122
self.mesh.apply_on_particles("compute_stress")
2223

23-
def compute_forces(self, gravity):
24+
def compute_forces(self, gravity, step):
2425
self.mesh.apply_on_elements("compute_external_force")
2526
self.mesh.apply_on_elements("compute_body_force", args=(gravity,))
27+
self.mesh.apply_on_elements(
28+
"apply_concentrated_nodal_forces", args=(step * self.dt,)
29+
)
2630
self.mesh.apply_on_elements("compute_internal_force")
2731
# self.mesh.apply_on_elements("apply_force_boundary_constraints")
2832

@@ -31,7 +35,8 @@ def compute_particle_kinematics(self):
3135
"update_nodal_acceleration_velocity", args=(self.dt,)
3236
)
3337
self.mesh.apply_on_particles(
34-
"update_position_velocity", args=(self.dt,)
38+
"update_position_velocity",
39+
args=(self.dt, self.velocity_update),
3540
)
3641
# TODO: Apply particle velocity constraints.
3742
self.mesh.apply_on_elements("compute_nodal_momentum")
@@ -49,8 +54,8 @@ def postcompute_stress_strain():
4954
class USF(_MPMScheme):
5055
"""USF Scheme solver."""
5156

52-
def __init__(self, mesh, dt):
53-
super().__init__(mesh, dt)
57+
def __init__(self, mesh, dt, velocity_update):
58+
super().__init__(mesh, dt, velocity_update)
5459

5560
def precompute_stress_strain(self):
5661
self.compute_stress_strain()
@@ -62,8 +67,8 @@ def postcompute_stress_strain(self):
6267
class USL(_MPMScheme):
6368
"""USL Scheme solver."""
6469

65-
def __init__(self, mesh, dt):
66-
super().__init__(mesh, dt)
70+
def __init__(self, mesh, dt, velocity_update):
71+
super().__init__(mesh, dt, velocity_update)
6772

6873
def precompute_stress_strain(self):
6974
pass

diffmpm/solver.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from jax import lax
33
from jax.tree_util import register_pytree_node_class
44
from tqdm import tqdm
5+
from collections import defaultdict
56

67
from diffmpm.io import Config
78
from diffmpm.scheme import USF, USL, _schemes
@@ -12,30 +13,32 @@ class MPM:
1213
def __init__(self, filepath):
1314
self._config = Config(filepath)
1415
mesh = self._config.parse()
15-
if self._config.config["meta"]["type"] == "MPMExplicit":
16-
self.solver = MPMExplicit(mesh, self._config.config["meta"]["dt"])
16+
if self._config.parsed_config["meta"]["type"] == "MPMExplicit":
17+
self.solver = MPMExplicit(
18+
mesh,
19+
self._config.parsed_config["meta"]["dt"],
20+
velocity_update=self._config.parsed_config["meta"]["velocity_update"],
21+
)
1722
else:
1823
raise ValueError("Wrong type of solver specified.")
1924

2025
def solve(self):
2126
res = self.solver.solve(
22-
self._config.config["meta"]["nsteps"],
23-
self._config.config["meta"]["gravity"],
27+
self._config.parsed_config["meta"]["nsteps"],
28+
self._config.parsed_config["external_loading"]["gravity"],
2429
)
2530
return res
2631

2732

2833
@register_pytree_node_class
2934
class MPMExplicit:
30-
def __init__(self, mesh, dt, scheme="usf"):
35+
def __init__(self, mesh, dt, scheme="usf", velocity_update=False):
3136
if scheme == "usf":
32-
self.mpm_scheme = USF(mesh, dt)
37+
self.mpm_scheme = USF(mesh, dt, velocity_update)
3338
elif scheme == "usl":
34-
self.mpm_scheme = USL(mesh, dt)
39+
self.mpm_scheme = USL(mesh, dt, velocity_update)
3540
else:
36-
raise ValueError(
37-
f"Please select scheme from {_schemes}. Found {scheme}"
38-
)
41+
raise ValueError(f"Please select scheme from {_schemes}. Found {scheme}")
3942
self.mesh = mesh
4043
self.dt = dt
4144
self.scheme = scheme
@@ -51,16 +54,17 @@ def tree_unflatten(cls, aux_data, children):
5154
return cls(*children, aux_data[0], scheme=aux_data[1])
5255

5356
def solve(self, nsteps: int, gravity: float | jnp.ndarray):
54-
result = {"position": [], "velocity": []}
57+
result = defaultdict(list)
5558
for step in tqdm(range(nsteps)):
5659
self.mpm_scheme.compute_nodal_kinematics()
5760
self.mpm_scheme.precompute_stress_strain()
58-
self.mpm_scheme.compute_forces(gravity)
61+
self.mpm_scheme.compute_forces(gravity, step)
5962
self.mpm_scheme.compute_particle_kinematics()
6063
self.mpm_scheme.postcompute_stress_strain()
6164
for pset in self.mesh.particles:
62-
result["position"].append(pset.loc)
63-
result["velocity"].append(pset.velocity)
65+
# result["position"].append(pset.loc)
66+
# result["velocity"].append(pset.velocity)
67+
result["stress"].append(pset.stress)
6468

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

examples/input_1d.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ scheme = "usl"
1818
dt = 0.01
1919
nsteps = 1000
2020
gravity = 0
21+
velocity_update = True
2122

2223
[output]
2324
type = "hdf5"

0 commit comments

Comments
 (0)