Skip to content

Commit da3fbe2

Browse files
committed
Add Newtonian material
1 parent 0f2e190 commit da3fbe2

File tree

3 files changed

+205
-1
lines changed

3 files changed

+205
-1
lines changed

diffmpm/materials/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from diffmpm.materials._base import _Material
22
from diffmpm.materials.simple import SimpleMaterial
33
from diffmpm.materials.linear_elastic import LinearElastic
4+
from diffmpm.materials.newtonian import Newtonian

diffmpm/materials/newtonian.py

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,114 @@
1-
#!/usr/bin/env python3
1+
import jax.numpy as jnp
2+
from jax import Array, lax
3+
from jax.typing import ArrayLike
4+
5+
from ._base import _Material
6+
7+
8+
class Newtonian(_Material):
9+
"""Newtonian fluid material model."""
10+
11+
_props = ("density", "bulk_modulus", "dynamic_viscosity")
12+
state_vars = ("pressure",)
13+
14+
def __init__(self, material_properties: dict):
15+
"""Create a Newtonian material.
16+
17+
Parameters
18+
----------
19+
material_properties: dict
20+
Dictionary with material properties. For newtonian
21+
materials, `density`, `bulk_modulus` and `dynamic_viscosity`
22+
are required keys.
23+
"""
24+
self.validate_props(material_properties)
25+
compressibility = 1
26+
27+
if material_properties.get("incompressible", False):
28+
compressibility = 0
29+
30+
self.properties = {
31+
**material_properties,
32+
"compressibility": compressibility,
33+
}
34+
35+
def __repr__(self):
36+
return f"Newtonian(props={self.properties})"
37+
38+
def initialize_state_variables(self, nparticles: int) -> dict:
39+
"""Return initial state variables dictionary.
40+
41+
Parameters
42+
----------
43+
nparticles : int
44+
Number of particles being simulated with this material.
45+
46+
Returns
47+
-------
48+
dict
49+
Dictionary of state variables initialized with values
50+
decided by material type.
51+
"""
52+
state_vars_dict = {var: jnp.zeros((nparticles, 1)) for var in self.state_vars}
53+
return state_vars_dict
54+
55+
def _thermodynamic_pressure(self, volumetric_strain: ArrayLike) -> Array:
56+
return -self.properties["bulk_modulus"] * volumetric_strain
57+
58+
def compute_stress(self, particles):
59+
"""Compute material stress."""
60+
ndim = particles.loc.shape[-1]
61+
if ndim not in {2, 3}:
62+
raise ValueError(f"Cannot compute stress for {ndim}-d Newotonian material.")
63+
volumetric_strain_rate = (
64+
particles.strain_rate[:, 0] + particles.strain_rate[:, 1]
65+
)
66+
particles.state_vars["pressure"] = (
67+
particles.state_vars["pressure"]
68+
.at[:]
69+
.add(
70+
self.properties["compressibility"]
71+
* self._thermodynamic_pressure(particles.dvolumetric_strain)
72+
)
73+
)
74+
75+
volumetric_stress_component = self.properties["compressibility"] * (
76+
-particles.state_vars["pressure"]
77+
- (2 * self.properties["dynamic_viscosity"] * volumetric_strain_rate / 3)
78+
)
79+
80+
stress = jnp.zeros_like(particles.stress)
81+
stress = stress.at[:, 0].set(
82+
volumetric_stress_component
83+
+ 2 * self.properties["dynamic_viscosity"] * particles.strain_rate[:, 0]
84+
)
85+
stress = stress.at[:, 1].set(
86+
volumetric_stress_component
87+
+ 2 * self.properties["dynamic_viscosity"] * particles.strain_rate[:, 1]
88+
)
89+
90+
extra_component_2 = lax.select(
91+
ndim == 3,
92+
2 * self.properties["dynamic_viscosity"] * particles.strain_rate[:, 2],
93+
jnp.zeros_like(particles.strain_rate[:, 2]),
94+
)
95+
stress = stress.at[:, 2].set(volumetric_stress_component + extra_component_2)
96+
97+
stress = stress.at[:, 3].set(
98+
self.properties["dynamic_viscosity"] * particles.strain_rate[:, 3]
99+
)
100+
101+
component_4 = lax.select(
102+
ndim == 3,
103+
self.properties["dynamic_viscosity"] * particles.strain_rate[:, 4],
104+
jnp.zeros_like(particles.strain_rate[:, 4]),
105+
)
106+
stress = stress.at[:, 4].set(component_4)
107+
component_5 = lax.select(
108+
ndim == 3,
109+
self.properties["dynamic_viscosity"] * particles.strain_rate[:, 5],
110+
jnp.zeros_like(particles.strain_rate[:, 5]),
111+
)
112+
stress = stress.at[:, 5].set(component_5)
113+
114+
return stress

tests/test_newtonian.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import jax.numpy as jnp
2+
import pytest
3+
from diffmpm.constraint import Constraint
4+
from diffmpm.element import Quadrilateral4Node
5+
from diffmpm.materials import Newtonian
6+
from diffmpm.node import Nodes
7+
from diffmpm.particle import Particles
8+
9+
particles_element_targets = [
10+
(
11+
Particles(
12+
jnp.array([[0.5, 0.5]]).reshape(1, 1, 2),
13+
Newtonian(
14+
{
15+
"density": 1000,
16+
"bulk_modulus": 8333333.333333333,
17+
"dynamic_viscosity": 8.9e-4,
18+
}
19+
),
20+
jnp.array([0]),
21+
),
22+
Quadrilateral4Node(
23+
(1, 1),
24+
1,
25+
(4.0, 4.0),
26+
[(0, Constraint(0, 0.02)), (0, Constraint(1, 0.03))],
27+
Nodes(4, jnp.array([-2, -2, 2, -2, -2, 2, 2, 2]).reshape((4, 1, 2))),
28+
),
29+
jnp.array(
30+
[
31+
-52083.3333338896,
32+
-52083.3333355583,
33+
-52083.3333305521,
34+
-0.0000041719,
35+
0,
36+
0,
37+
]
38+
).reshape(1, 6, 1),
39+
),
40+
(
41+
Particles(
42+
jnp.array([[0.5, 0.5]]).reshape(1, 1, 2),
43+
Newtonian(
44+
{
45+
"density": 1000,
46+
"bulk_modulus": 8333333.333333333,
47+
"dynamic_viscosity": 8.9e-4,
48+
"incompressible": True,
49+
}
50+
),
51+
jnp.array([0]),
52+
),
53+
Quadrilateral4Node(
54+
(1, 1),
55+
1,
56+
(4.0, 4.0),
57+
[(0, Constraint(0, 0.02)), (0, Constraint(1, 0.03))],
58+
Nodes(4, jnp.array([-2, -2, 2, -2, -2, 2, 2, 2]).reshape((4, 1, 2))),
59+
),
60+
jnp.array(
61+
[
62+
-0.0000033375,
63+
-0.00000500625,
64+
0,
65+
-0.0000041719,
66+
0,
67+
0,
68+
]
69+
).reshape(1, 6, 1),
70+
),
71+
]
72+
73+
74+
@pytest.mark.parametrize(
75+
"particles, element, target",
76+
particles_element_targets,
77+
)
78+
def test_compute_stress(particles, element, target):
79+
dt = 1
80+
particles.update_natural_coords(element)
81+
if element.constraints:
82+
element.apply_boundary_constraints()
83+
particles.compute_strain(element, dt)
84+
stress = particles.material.compute_stress(particles)
85+
assert jnp.allclose(stress, target)
86+
87+
88+
def test_init():
89+
with pytest.raises(KeyError):
90+
Newtonian({"dynamic_viscosity": 1, "density": 1})

0 commit comments

Comments
 (0)