Skip to content

Commit f8fd0af

Browse files
committed
2 parents 79f6b49 + d596250 commit f8fd0af

File tree

3 files changed

+144
-11
lines changed

3 files changed

+144
-11
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ Tobias Karl, Davide Gatti, Bettina Frohnapfel and Thomas Böhlke,\
8686
'Asymptotic fiber orientation states of the quadratically closed Folgar-Tucker equation and a subsequent closure improvement',\
8787
Journal of Rheology 65(5) : 999-1022, 2021\
8888
(https://doi.org/10.1122/8.0000245)
89-
* __SIC__:\
89+
* __SIC__, __SIHYB__:\
9090
Tobias Karl, Matti Schneider and Thomas Böhlke,\
9191
'On fully symmetric implicit closure approximations for fiber orientation tensors',\
9292
Journal of Non-Newtonian Fluid Mechanics 318 : 105049, 2023.\

fiberoripy/closures.py

+141-8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def compute_closure(a, closure="IBOF"):
2727
"ORW",
2828
"ORW3",
2929
"SIQ",
30+
"SIHYB",
3031
"SQC",
3132
)
3233
if closure == "IBOF":
@@ -45,6 +46,8 @@ def compute_closure(a, closure="IBOF"):
4546
return orthotropic_fitted_closures(a, "ORW3")
4647
if closure == "SIQ":
4748
return symmetric_implicit_closure(a)
49+
if closure == "SIHYB":
50+
return implicit_hybrid_closure(a)
4851
if closure == "SQC":
4952
return symmetric_quadratic_closure(a)
5053

@@ -463,14 +466,18 @@ def symm(A):
463466
This function computes the symmetric part of a fourth order Tensor A
464467
and returns a symmetric fourth order tensor S.
465468
"""
466-
if A.ndim == 4:
467-
S = np.stack(
468-
[np.transpose(A, axes=p) for p in permutations([0, 1, 2, 3])]
469-
)
470-
else:
471-
S = np.stack(
472-
[np.transpose(A, axes=(0, *p)) for p in permutations([1, 2, 3, 4])]
473-
)
469+
# if A.ndim == 4:
470+
# S = np.stack(
471+
# [np.transpose(A, axes=p) for p in permutations([0, 1, 2, 3])]
472+
# )sy
473+
S = np.stack(
474+
[
475+
np.transpose(A, axes=(*range(A.ndim - 4), *p))
476+
for p in permutations(
477+
[A.ndim - 4, A.ndim - 3, A.ndim - 2, A.ndim - 1]
478+
)
479+
]
480+
)
474481
return S.sum(axis=0) / 24
475482

476483

@@ -779,3 +786,129 @@ def symmetric_implicit_closure(a, eps_newton=1.0e-12, n_iter_newton=25):
779786
+ np.einsum("...il, ...kj -> ...ijkl", b, b)
780787
)
781788
)
789+
790+
791+
def implicit_hybrid_closure(a, eps_newton=1.0e-12, n_iter_newton=25):
792+
"""Generate implicit hybrid closure.
793+
Parameters
794+
----------
795+
a : ...x3x3 numpy array
796+
(Array of) second order fiber orientation tensor.
797+
eps_newton : float
798+
convergence criterion for newton algorithm
799+
optional: default 1.0e-12
800+
n_iter_newton : int
801+
number of maximum iterations in newton algorithm
802+
optional: default 25
803+
Returns
804+
-------
805+
...x3x3x3x3 numpy array
806+
Fourth order fiber orientation tensor.
807+
References
808+
----------
809+
.. [1] Karl, Tobias, Matti Schneider, and Thomas Böhlke,
810+
'On fully symmetric implicit closure approximations for fiber orientation tensors',
811+
Journal of Non-Newtonian Fluid Mechanics 318 : 105049,
812+
https://doi.org/10.1016/j.jnnfm.2023.105049
813+
Notes
814+
----------
815+
There seems to be a typo in the expression for f_prime in the original work.
816+
"""
817+
818+
assert_fot_properties(a)
819+
820+
evs, r = np.linalg.eigh(a)
821+
822+
d = evs.shape[-1]
823+
s = np.ones(a.shape[:-2])
824+
825+
k = 1.0 - 27.0 * np.prod(evs, axis=-1)
826+
827+
err, it = 1.0e12, 0
828+
829+
# ignore warnings when input is isotropic (results in k=0)
830+
with np.errstate(divide="ignore", invalid="ignore", over="ignore"):
831+
while err > eps_newton and it < n_iter_newton:
832+
833+
# l1 and l2 correspond to a, b in the original work
834+
l1 = np.nan_to_num(0.5 * (3.0 + (4.0 * s - 3.0) * k) / k)
835+
l2 = np.nan_to_num(
836+
(3.0 * (1.0 - k) * (4.0 * s - 1.0)) / (14.0 * k)
837+
)
838+
839+
l1_prime = 4.0 * np.ones(l1.shape)
840+
l2_prime = np.nan_to_num((6.0 * (1.0 - k)) / (7.0 * k))
841+
842+
f = np.nan_to_num(
843+
(
844+
4.0 * s
845+
+ 0.5 * l1 * d
846+
- np.sum(
847+
np.sqrt(
848+
1.5 * evs / k[..., None]
849+
+ 0.25 * l1[..., None] ** 2.0
850+
- l2[..., None]
851+
),
852+
axis=-1,
853+
)
854+
)
855+
)
856+
857+
f_prime = np.nan_to_num(
858+
(
859+
4.0
860+
+ 0.5 * (l1_prime * d)
861+
- 0.25
862+
* np.sum(
863+
(
864+
2.0 * l1[..., None] * l1_prime[..., None]
865+
- 4.0 * l2_prime[..., None]
866+
)
867+
/ np.sqrt(
868+
2.0 * evs / k[..., None]
869+
+ l1[..., None] ** 2.0
870+
- 4.0 * l2[..., None]
871+
),
872+
axis=-1,
873+
)
874+
)
875+
)
876+
877+
s -= f / f_prime
878+
879+
err = np.linalg.norm(f)
880+
it += 1
881+
882+
if err > eps_newton:
883+
raise ValueError("Newton algorithm did not converge.")
884+
885+
l1 = np.nan_to_num(0.5 * (3.0 + (4.0 * s - 3.0) * k) / k)
886+
l2 = np.nan_to_num((3.0 * (1.0 - k) * (4.0 * s - 1.0)) / (14.0 * k))
887+
888+
mus = -0.5 * l1[..., None] + np.sqrt(
889+
1.5 * evs / k[..., None]
890+
+ 0.25 * l1[..., None] ** 2
891+
- l2[..., None]
892+
)
893+
894+
b = np.einsum("...ij, ...j, ...kj -> ...ik", r, mus, r)
895+
896+
w1 = 1.0 - k
897+
sym_ii = (
898+
-3.0 / 35.0 * symm(np.einsum("ij, kl -> ijkl", np.eye(3), np.eye(3)))
899+
)
900+
sym_ib = 6.0 / 7.0 * symm(np.einsum("ij, ...kl -> ...ijkl", np.eye(3), b))
901+
sym_bb = symm(np.einsum("...ij, ...kl -> ...ijkl", b, b))
902+
903+
linear_term = np.einsum("..., ...ijkl -> ...ijkl", w1, sym_ii + sym_ib)
904+
quadratic_term = np.einsum("..., ...ijkl -> ...ijkl", k, sym_bb)
905+
906+
a4_iso = 0.2 * symm(np.einsum("ij, kl -> ijkl", *2 * (np.eye(3),)))
907+
a4 = linear_term + quadratic_term
908+
909+
# replace numerical garbage with true isotropic 4th-order FOT
910+
return np.where(
911+
np.isclose(k, 0.0)[..., None, None, None, None],
912+
a4_iso[..., :, :, :, :],
913+
a4,
914+
)

tests/test_closure.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def get_test_tensors():
2626
@pytest.mark.parametrize("a", get_test_tensors())
2727
@pytest.mark.parametrize(
2828
"type",
29-
["IBOF", "LINEAR", "HYBRID", "QUADRATIC", "ORF", "SIQ"],
29+
["IBOF", "LINEAR", "HYBRID", "QUADRATIC", "ORF", "SIQ", "SIHYB"],
3030
)
3131
def test_reduction(a, type):
3232
"""Test contraction property."""
@@ -40,7 +40,7 @@ def test_reduction(a, type):
4040
@pytest.mark.parametrize("a", get_test_tensors())
4141
@pytest.mark.parametrize(
4242
"type",
43-
["IBOF", "LINEAR", "HYBRID", "QUADRATIC", "ORF", "SIQ", "SQC"],
43+
["IBOF", "LINEAR", "HYBRID", "QUADRATIC", "ORF", "SIQ", "SIHYB", "SQC"],
4444
)
4545
def test_contraction(a, type):
4646
"""Test first contraction property."""

0 commit comments

Comments
 (0)