@@ -27,6 +27,7 @@ def compute_closure(a, closure="IBOF"):
27
27
"ORW" ,
28
28
"ORW3" ,
29
29
"SIQ" ,
30
+ "SIHYB" ,
30
31
"SQC" ,
31
32
)
32
33
if closure == "IBOF" :
@@ -45,6 +46,8 @@ def compute_closure(a, closure="IBOF"):
45
46
return orthotropic_fitted_closures (a , "ORW3" )
46
47
if closure == "SIQ" :
47
48
return symmetric_implicit_closure (a )
49
+ if closure == "SIHYB" :
50
+ return implicit_hybrid_closure (a )
48
51
if closure == "SQC" :
49
52
return symmetric_quadratic_closure (a )
50
53
@@ -463,14 +466,18 @@ def symm(A):
463
466
This function computes the symmetric part of a fourth order Tensor A
464
467
and returns a symmetric fourth order tensor S.
465
468
"""
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
+ )
474
481
return S .sum (axis = 0 ) / 24
475
482
476
483
@@ -779,3 +786,129 @@ def symmetric_implicit_closure(a, eps_newton=1.0e-12, n_iter_newton=25):
779
786
+ np .einsum ("...il, ...kj -> ...ijkl" , b , b )
780
787
)
781
788
)
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
+ )
0 commit comments