Skip to content

Commit 47af137

Browse files
committed
Update Kinematics class and add new tests
1 parent 9b346cf commit 47af137

File tree

4 files changed

+134
-72
lines changed

4 files changed

+134
-72
lines changed

hyper_surrogate/kinematics.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,41 +68,42 @@ def left_cauchy_green(f: np.ndarray) -> Any:
6868
# and 'jk' are the indices for the second matrix (transposed to 'kj' for multiplication).
6969
return np.einsum("nij,nkj->nik", f, f)
7070

71-
def strain_tensor(self, f: np.ndarray) -> Any:
71+
@staticmethod
72+
def strain_tensor(f: np.ndarray) -> Any:
7273
"""
7374
Compute the strain tensor.
7475
7576
Args:
76-
f (np.ndarray): The deformation gradient.
77+
f (np.ndarray): The deformation gradients. batched with shape (N, 3, 3).
7778
7879
Returns:
79-
np.ndarray: The strain tensor.
80+
np.ndarray: The strain tensors. batched with shape (N, 3, 3).
8081
"""
81-
return 0.5 * (f.T @ f - np.eye(3))
82+
return 0.5 * (np.einsum("nji,njk->nik", f, f) - np.eye(3))
8283

8384
def stretch_tensor(self, f: np.ndarray) -> Any:
8485
"""
8586
Compute the stretch tensor.
8687
8788
Args:
88-
f (np.ndarray): The deformation gradient.
89+
f (np.ndarray): The deformation gradients. batched with shape (N, 3, 3).
8990
9091
Returns:
91-
np.ndarray: The stretch tensor.
92+
np.ndarray: The stretch tensor. batched with shape (N, 3, 3).
9293
"""
93-
return np.sqrt(self.right_cauchy_green(f))
94+
return np.sqrt(np.einsum("nji,njk->nik", f, f))
9495

95-
def rotation_tensor(self, f: np.ndarray) -> np.ndarray:
96+
def rotation_tensor(self, f: np.ndarray) -> Any:
9697
"""
97-
Compute the rotation tensor.
98+
Compute the rotation tensors.
9899
99100
Args:
100-
f (np.ndarray): The deformation gradient.
101+
f (np.ndarray): The deformation gradients. batched with shape (N, 3, 3).
101102
102103
Returns:
103-
np.ndarray: The rotation tensor.
104+
np.ndarray: The rotation tensors. batched with shape (N, 3, 3).
104105
"""
105-
return f @ np.linalg.inv(self.stretch_tensor(f))
106+
return np.einsum("nij,njk->nik", f, np.linalg.inv(f))
106107

107108
def principal_stretches(self, f: np.ndarray) -> np.ndarray:
108109
"""

hyper_surrogate/symbolic.py

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def _c_tensor(self) -> sym.Matrix:
2525
return sym.Matrix(3, 3, lambda i, j: sym.Symbol(f"C_{i+1}{j+1}"))
2626

2727
# multuply c_tensor by itself
28-
def _c_tensor_squared(self) -> sym.Matrix:
28+
def _c_tensor_squared(self) -> Any:
2929
"""
3030
Compute the square of the c_tensor.
3131
@@ -68,7 +68,7 @@ def invariant3(self) -> Any:
6868
"""
6969
return self.c_tensor.det()
7070

71-
def pk2_tensor(self, sef: sym.Expr) -> sym.MutableDenseNDimArray:
71+
def pk2_tensor(self, sef: sym.Expr) -> sym.Matrix:
7272
"""
7373
Compute the pk2 tensor.
7474
@@ -78,9 +78,9 @@ def pk2_tensor(self, sef: sym.Expr) -> sym.MutableDenseNDimArray:
7878
Returns:
7979
sym.Matrix: The pk2 tensor.
8080
"""
81-
return sym.MutableDenseNDimArray([[sym.diff(sef, self.c_tensor[i, j]) for j in range(3)] for i in range(3)])
81+
return sym.Matrix([[sym.diff(sef, self.c_tensor[i, j]) for j in range(3)] for i in range(3)])
8282

83-
def cmat_tensor(self, pk2: sym.Matrix) -> sym.MutableDenseNDimArray:
83+
def cmat_tensor(self, pk2: sym.Matrix) -> sym.ImmutableDenseNDimArray:
8484
"""
8585
Compute the cmat tensor.
8686
@@ -90,27 +90,51 @@ def cmat_tensor(self, pk2: sym.Matrix) -> sym.MutableDenseNDimArray:
9090
Returns:
9191
sym.MutableDenseNDimArray: The cmat tensor.
9292
"""
93-
return sym.MutableDenseNDimArray(
93+
return sym.ImmutableDenseNDimArray(
9494
[
9595
[[[sym.diff(pk2[i, j], self.c_tensor[k, ll]) for ll in range(3)] for k in range(3)] for j in range(3)]
9696
for i in range(3)
9797
]
9898
)
9999

100-
def substitute(self, symbolic_tensor: sym.Expr, numerical_c_tensor: np.ndarray) -> Any:
100+
def substitute(self, symbolic_tensor: sym.Expr, numerical_c_tensor: np.ndarray, *args: dict) -> Any:
101101
"""
102102
Automatically substitute numerical values from a given 3x3 numerical matrix into c_tensor.
103103
104104
Args:
105105
symbolic_tensor (sym.Matrix): A symbolic tensor to substitute numerical values into.
106106
numerical_matrix (np.ndarray): A 3x3 numerical matrix to substitute into c_tensor.
107+
args (dict): Additional substitution dictionaries.
107108
108109
Returns:
109-
sym.Matrix: The c_tensor with numerical values substituted.
110+
sym.Matrix: The symbolic_tensor with numerical values substituted.
110111
111112
Raises:
112113
ValueError: If numerical_tensor is not a 3x3 matrix.
113114
"""
114-
return symbolic_tensor.subs(
115-
{self.c_tensor[i, j]: np.array(numerical_c_tensor)[i, j] for i in range(3) for j in range(3)}
116-
)
115+
if not isinstance(numerical_c_tensor, np.ndarray) or numerical_c_tensor.shape != (3, 3):
116+
raise ValueError("c_tensor.shape")
117+
118+
# Start with substitutions for c_tensor elements
119+
substitutions = {self.c_tensor[i, j]: numerical_c_tensor[i, j] for i in range(3) for j in range(3)}
120+
# Merge additional substitution dictionaries from *args
121+
substitutions.update(*args)
122+
return symbolic_tensor.subs(substitutions)
123+
124+
def substitute_iterator(self, symbolic_tensor: sym.Expr, numerical_c_tensors: np.ndarray, *args: dict) -> Any:
125+
"""
126+
Automatically substitute numerical values from a given 3x3 numerical matrix into c_tensor.
127+
128+
Args:
129+
symbolic_tensor (sym.Matrix): A symbolic tensor to substitute numerical values into.
130+
numerical_matrix (np.ndarray): A 3x3 numerical matrix to substitute into c_tensor.
131+
args (dict): Additional substitution dictionaries.
132+
133+
Returns:
134+
sym.Matrix: The symbolic_tensor with numerical values substituted.
135+
136+
Raises:
137+
ValueError: If numerical_tensor is not a 3x3 matrix.
138+
"""
139+
for numerical_c_tensor in numerical_c_tensors:
140+
yield self.substitute(symbolic_tensor, numerical_c_tensor, *args)

tests/test_kinematics.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22
import pytest
33

44
from hyper_surrogate.deformation_gradient import DeformationGradientGenerator as FGen
5-
from hyper_surrogate.kinematics import Kinematics as K
5+
from hyper_surrogate.kinematics import Kinematics
66

7-
SIZE = 2
7+
SIZE = 1
8+
9+
10+
@pytest.fixture
11+
def K():
12+
return Kinematics()
813

914

1015
@pytest.fixture
@@ -18,6 +23,27 @@ def right_cauchys(def_gradients):
1823
return np.array([np.matmul(f, f.T) for f in def_gradients])
1924

2025

21-
def test_right_cauchys(def_gradients):
26+
@pytest.fixture
27+
def left_cauchys(def_gradients):
28+
return np.array([np.matmul(f.T, f) for f in def_gradients])
29+
30+
31+
def test_right_cauchys(def_gradients, K):
2232
right_cauchys = K.right_cauchy_green(def_gradients)
2333
assert np.allclose(right_cauchys, np.array([np.matmul(f.T, f) for f in def_gradients]))
34+
35+
36+
def test_left_cauchys(def_gradients, K):
37+
left_cauchys = K.left_cauchy_green(def_gradients)
38+
assert np.allclose(left_cauchys, np.array([np.matmul(f, f.T) for f in def_gradients]))
39+
40+
41+
def test_strain_tensor(def_gradients, K):
42+
strain_tensors = K.strain_tensor(def_gradients)
43+
assert np.allclose(strain_tensors, 0.5 * (np.array([f.T @ f for f in def_gradients]) - np.eye(3)))
44+
45+
46+
# def test_stretch_tensor(def_gradients, K):
47+
# stretch_tensors = K.stretch_tensor(def_gradients)
48+
# logging.info(stretch_tensors)
49+
# assert np.allclose(stretch_tensors, np.sqrt(np.array([f.T @ f for f in def_gradients])))

tests/test_symbolic_rules.py

Lines changed: 58 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import logging
2-
31
import numpy as np
42
import pytest
53
import sympy as sym
@@ -11,41 +9,59 @@
119
SIZE = 2
1210

1311

12+
# numeric fixtures
1413
@pytest.fixture
15-
def def_gradients():
14+
def def_gradients() -> np.ndarray:
1615
return FGen(seed=42, size=SIZE).generate()
1716

1817

1918
@pytest.fixture
20-
def handler():
19+
def right_cauchys(def_gradients) -> np.ndarray:
20+
return K.right_cauchy_green(def_gradients)
21+
22+
23+
# symbolic fixtures
24+
@pytest.fixture
25+
def handler() -> SymbolicHandler:
2126
return SymbolicHandler()
2227

2328

29+
# mooney_rivlin
2430
@pytest.fixture
25-
def sef():
26-
symbolic_handler = SymbolicHandler()
27-
return (symbolic_handler.invariant1 - 3) * sym.Symbol("C10") + (symbolic_handler.invariant2 - 3) * sym.Symbol("C01")
31+
def sef(handler) -> sym.Expr:
32+
return (handler.invariant1 - 3) * sym.Symbol("C10") + (handler.invariant2 - 3) * sym.Symbol("C01")
2833

2934

30-
# kinematics testing
3135
@pytest.fixture
32-
def right_cauchys(def_gradients):
33-
return np.array([np.matmul(f, f.T) for f in def_gradients])
36+
def sef_args() -> dict:
37+
return {"C10": 1, "C01": 1}
38+
39+
40+
@pytest.fixture
41+
def pk2(handler, sef) -> sym.Matrix:
42+
return handler.pk2_tensor(sef)
43+
44+
45+
@pytest.fixture
46+
def cmat(handler, pk2) -> sym.ImmutableDenseNDimArray:
47+
return handler.cmat_tensor(pk2)
48+
3449

50+
# testing
3551

36-
def test_symbolic_pk2_cmat(handler, sef):
52+
53+
def test_symbolic_pk2_cmat(pk2, cmat):
3754
# derivative of sef in order to c_tensor
38-
pk2 = handler.pk2_tensor(sef)
39-
cmat = handler.cmat_tensor(pk2)
55+
4056
# assert instance
41-
assert isinstance(pk2, sym.MutableDenseNDimArray)
42-
assert isinstance(cmat, sym.MutableDenseNDimArray)
57+
assert isinstance(pk2, sym.Matrix)
58+
assert isinstance(cmat, sym.ImmutableDenseNDimArray)
4359
# assert shape
4460
assert pk2.shape == (3, 3)
4561
assert cmat.shape == (3, 3, 3, 3)
4662

4763

48-
def test_symbolic_subs(handler):
64+
def test_symbolic_subs_in_c(handler):
4965
# substitute the c_tensor with a 3x3 matrix of ones
5066
c_tensor = handler.c_tensor
5167
# dummy c values
@@ -56,34 +72,29 @@ def test_symbolic_subs(handler):
5672
assert all(c_tensor_subs[i, j] == 1 for i in range(3) for j in range(3))
5773

5874

59-
def test_symbolic_subs_in_batch(handler, def_gradients):
60-
#
61-
c_tensor = handler.c_tensor
62-
# get first c from def_gradients
63-
c = K.right_cauchy_green(def_gradients)
64-
# subs c_tensor with c values
65-
c_all = [handler.substitute(c_tensor, cc) for cc in c]
66-
logging.info(c_all)
67-
68-
69-
# voigt notation
70-
# def test_voigt_notation(handler):
71-
# c_tensor = handler.c_tensor
72-
# c_voigt = sym.Matrix([c_tensor[0, 0], c_tensor[1, 1], c_tensor[2, 2], c_tensor[0, 1], c_tensor[0, 2], c_tensor[1, 2]])
73-
74-
# invariant3 = symbolic_handler.invariant3_symbolic()
75-
76-
77-
# def test_derivative(def_gradients,c_tensor_symbolic):
78-
# logging.info(def_gradients)
79-
80-
# C_10_sym=sym.Symbol("C_10_sym")
81-
# #get symbols from c_tensor_symbolic
82-
# C11,C12,C13,C21,C22,C23,C31,C32,C33=c_tensor_symbolic.args
83-
# I3=C11*C22*C33 - C11*C23*C32 - C12*C21*C33 + C12*C23*C31 + C13*C21*C32 - C13*C22*C31
84-
# trace=C11+C22+C33
85-
# I1=trace*(I3)**(-1/3)
86-
87-
# #Symbolic Strain Energy Function
88-
# SEF=(I1-3)*C_10_sym
89-
# logging.info(SEF)
75+
def test_symbolic_subs_in_pk2(handler, pk2, right_cauchys, sef_args):
76+
# right_cauchys # (N, 3, 3)
77+
# for each c_tensor in c, substitute the pk2 tensor with c values and material parameters values.
78+
assert all(
79+
isinstance(subs, sym.Matrix)
80+
and subs.shape == (3, 3)
81+
and all(isinstance(subs[i, j], sym.Expr) for i in range(3) for j in range(3))
82+
for subs in handler.substitute_iterator(pk2, right_cauchys, sef_args)
83+
)
84+
85+
86+
def test_symbolic_subs_in_cmat(handler, cmat, right_cauchys, sef_args):
87+
# right_cauchys # (N, 3, 3)
88+
# for each c_tensor in c, substitute the cmat tensor with c values and material parameters values.
89+
assert all(
90+
isinstance(subs, sym.ImmutableDenseNDimArray)
91+
and subs.shape == (3, 3, 3, 3)
92+
and all(
93+
isinstance(subs[i, j, k, ll], sym.Expr)
94+
for i in range(3)
95+
for j in range(3)
96+
for k in range(3)
97+
for ll in range(3)
98+
)
99+
for subs in handler.substitute_iterator(cmat, right_cauchys, sef_args)
100+
)

0 commit comments

Comments
 (0)