elasticipy 4.2.0__py3-none-any.whl → 5.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,26 @@
1
- from Elasticipy.tensors.fourth_order import SymmetricFourthOrderTensor
1
+ from Elasticipy.tensors.fourth_order import SymmetricFourthOrderTensor, kelvin_mapping, _isotropic_matrix
2
2
  from Elasticipy.spherical_function import SphericalFunction, HyperSphericalFunction
3
3
  from Elasticipy.crystal_symmetries import SYMMETRIES
4
+ from Elasticipy.tensors.second_order import SymmetricSecondOrderTensor
4
5
  from Elasticipy.tensors.stress_strain import StrainTensor, StressTensor
5
- from Elasticipy.tensors.mapping import VoigtMapping, KelvinMapping
6
+ from Elasticipy.tensors.mapping import VoigtMapping
7
+ from functools import wraps
6
8
  import numpy as np
7
9
  import re
8
10
  from warnings import warn
9
11
 
12
+ def _elementwise_property(func):
13
+ @property
14
+ @wraps(func)
15
+ def wrapper(self, *args, **kwargs):
16
+ if getattr(self, "ndim", 0) != 0:
17
+ flat = self.flatten()
18
+ result = [getattr(e, func.__name__) for e in flat]
19
+ return np.array(result).reshape(self.shape)
20
+ return func(self, *args, **kwargs)
21
+ return wrapper
22
+
23
+
10
24
  def _parse_tensor_components(prefix, **kwargs):
11
25
  pattern = r'^{}(\d{{2}})$'.format(prefix)
12
26
  value = dict()
@@ -19,15 +33,6 @@ def _parse_tensor_components(prefix, **kwargs):
19
33
  def _indices2str(ij):
20
34
  return f'{ij[0] + 1}{ij[1] + 1}'
21
35
 
22
- def _isotropic_matrix(C11, C12, C44):
23
- return np.array([[C11, C12, C12, 0, 0, 0],
24
- [C12, C11, C12, 0, 0, 0],
25
- [C12, C12, C11, 0, 0, 0],
26
- [0, 0, 0, C44, 0, 0],
27
- [0, 0, 0, 0, C44, 0],
28
- [0, 0, 0, 0, 0, C44]])
29
-
30
-
31
36
  def _check_definite_positive(mat):
32
37
  try:
33
38
  np.linalg.cholesky(mat)
@@ -47,30 +52,33 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
47
52
  """
48
53
  Class for manipulating fourth-order stiffness tensors.
49
54
  """
50
- tensor_name = 'Stiffness'
55
+ _tensor_name = 'Stiffness'
51
56
  _C11_C12_factor = 0.5
52
57
  _C46_C56_factor = 1.0
53
58
  _component_prefix = 'C'
54
59
 
55
- def __init__(self, M, symmetry='Triclinic', check_positive_definite=True, phase_name= None, mapping=VoigtMapping(), **kwargs):
60
+ def __init__(self, M, check_positive_definite=True, phase_name= None, mapping=VoigtMapping(), **kwargs):
56
61
  """
57
- Construct of stiffness tensor from a (6,6) matrix.
62
+ Construct a stiffness tensor or an array of stiffness tensors.
58
63
 
59
- The input matrix must be symmetric, otherwise an error is thrown (except if check_symmetry==False, see below)
64
+ The stiffness tensor can be constructed from a (6,6) matrix or slices of (6,6) matrices. These matrices must be
65
+ symmetric. An error is thrown if this matrix in not definite positive (except if
66
+ ``check_positive_definite==False``, see below). The input argument can also be the full tensor (array of shape
67
+ (...,3,3,3,3)).
60
68
 
61
69
  Parameters
62
70
  ----------
63
- M : np.ndarray
64
- (6,6) matrix corresponding to the stiffness tensor, written using the Voigt notation, or array of shape
65
- (3,3,3,3).
66
- phase_name : str, default None
67
- Name to display
68
- symmetry : str, default Triclinic
69
- Name of the crystal's symmetry
71
+ M : list or np.ndarray
72
+ (...,6,6) matrix corresponding to the stiffness tensor, written using the Voigt notation, or array of shape
73
+ (...,3,3,3,3).
74
+ phase_name : str, optional
75
+ Phase name to display
76
+ check_positive_definite : bool, optional
77
+ Whether to check if the input matrix is positive definite or not. True by default.
70
78
  check_symmetry : bool, optional
71
79
  Whether to check or not that the input matrix is symmetric.
72
80
  force_symmetry : bool, optional
73
- If true, the major symmetry of the tensor is forces
81
+ If true, the major symmetry of the tensor is forced
74
82
  mapping : str or MappingConvention
75
83
  mapping convention to use. Default is VoigtMapping.
76
84
 
@@ -80,11 +88,94 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
80
88
  results you will get when performing operations (Young's modulus, "product" with strain tensor etc.) will be
81
89
  consistent with these units. For instance, if the stiffness tensor is defined in GPa, the computed stress will
82
90
  be given in GPa as well.
91
+
92
+ Examples
93
+ --------
94
+ Create a stiffness tensor for cubic symmetry:
95
+
96
+ >>> matrix = [[200, 40, 40, 0, 0, 0 ],
97
+ ... [40, 200, 40, 0, 0, 0 ],
98
+ ... [40, 40, 200, 0, 0, 0 ],
99
+ ... [0, 0, 0, 20, 0, 0 ],
100
+ ... [0, 0, 0, 0, 20, 0 ],
101
+ ... [0, 0, 0, 0, 0, 20]]
102
+ >>> from Elasticipy.tensors.elasticity import StiffnessTensor
103
+ >>> C = StiffnessTensor(matrix)
104
+ >>> print(C)
105
+ Stiffness tensor (in Voigt mapping):
106
+ [[200. 40. 40. 0. 0. 0.]
107
+ [ 40. 200. 40. 0. 0. 0.]
108
+ [ 40. 40. 200. 0. 0. 0.]
109
+ [ 0. 0. 0. 20. 0. 0.]
110
+ [ 0. 0. 0. 0. 20. 0.]
111
+ [ 0. 0. 0. 0. 0. 20.]]
112
+
113
+ Create a stiffness tensor from full (3,3,3,3) array:
114
+
115
+ >>> C_full = C.full_tensor # (3,3,3,3) numpy array
116
+ >>> print((type(C_full), C_full.shape))
117
+ (<class 'numpy.ndarray'>, (3, 3, 3, 3))
118
+
119
+ >>> StiffnessTensor(C_full)
120
+ Stiffness tensor (in Voigt mapping):
121
+ [[200. 40. 40. 0. 0. 0.]
122
+ [ 40. 200. 40. 0. 0. 0.]
123
+ [ 40. 40. 200. 0. 0. 0.]
124
+ [ 0. 0. 0. 20. 0. 0.]
125
+ [ 0. 0. 0. 0. 20. 0.]
126
+ [ 0. 0. 0. 0. 0. 20.]]
127
+
128
+ Create an array of stiffness tensors:
129
+
130
+ First, we create two slices of (6,6) matrices, corresponding to two stiffness values:
131
+
132
+ >>> slices = [[[200, 40, 40, 0, 0, 0 ],
133
+ ... [40, 200, 40, 0, 0, 0 ],
134
+ ... [40, 40, 200, 0, 0, 0 ],
135
+ ... [0, 0, 0, 20, 0, 0 ],
136
+ ... [0, 0, 0, 0, 20, 0 ],
137
+ ... [0, 0, 0, 0, 0, 20]],
138
+ ... [[250, 80, 80, 0, 0, 0 ],
139
+ ... [80, 250, 80, 0, 0, 0 ],
140
+ ... [80, 80, 250, 0, 0, 0 ],
141
+ ... [0, 0, 0, 40, 0, 0 ],
142
+ ... [0, 0, 0, 0, 40, 0 ],
143
+ ... [0, 0, 0, 0, 0, 40]]]
144
+
145
+ Then, one can create an array of stiffness tensors:
146
+
147
+ >>> C_array=StiffnessTensor(slices)
148
+ >>> print(C_array)
149
+ Stiffness tensor (in Voigt mapping):
150
+ [[[200. 40. 40. 0. 0. 0.]
151
+ [ 40. 200. 40. 0. 0. 0.]
152
+ [ 40. 40. 200. 0. 0. 0.]
153
+ [ 0. 0. 0. 20. 0. 0.]
154
+ [ 0. 0. 0. 0. 20. 0.]
155
+ [ 0. 0. 0. 0. 0. 20.]]
156
+ <BLANKLINE>
157
+ [[250. 80. 80. 0. 0. 0.]
158
+ [ 80. 250. 80. 0. 0. 0.]
159
+ [ 80. 80. 250. 0. 0. 0.]
160
+ [ 0. 0. 0. 40. 0. 0.]
161
+ [ 0. 0. 0. 0. 40. 0.]
162
+ [ 0. 0. 0. 0. 0. 40.]]]
163
+
164
+
165
+ This array can be subindexed. E.g.:
166
+
167
+ >>> C_array[0]
168
+ Stiffness tensor (in Voigt mapping):
169
+ [[200. 40. 40. 0. 0. 0.]
170
+ [ 40. 200. 40. 0. 0. 0.]
171
+ [ 40. 40. 200. 0. 0. 0.]
172
+ [ 0. 0. 0. 20. 0. 0.]
173
+ [ 0. 0. 0. 0. 20. 0.]
174
+ [ 0. 0. 0. 0. 0. 20.]]
83
175
  """
84
176
  super().__init__(M, mapping=mapping, **kwargs)
85
177
  if check_positive_definite:
86
178
  _check_definite_positive(self._matrix)
87
- self.symmetry = symmetry
88
179
  self.phase_name = phase_name
89
180
 
90
181
  def __mul__(self, other):
@@ -100,7 +191,6 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
100
191
  string = super().__repr__()
101
192
  if self.phase_name is not None:
102
193
  string += '\nPhase: {}'.format(self.phase_name)
103
- string += '\nSymmetry: {}'.format(self.symmetry)
104
194
  return string
105
195
 
106
196
  def inv(self):
@@ -111,9 +201,82 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
111
201
  -------
112
202
  ComplianceTensor
113
203
  Reciprocal tensor
204
+
205
+ Examples
206
+ --------
207
+ Create a stiffness tensor for cubic symmetry:
208
+
209
+ >>> C = StiffnessTensor.cubic(C11=186, C12=134, C44=77)
210
+ >>> print(C)
211
+ Stiffness tensor (in Voigt mapping):
212
+ [[186. 134. 134. 0. 0. 0.]
213
+ [134. 186. 134. 0. 0. 0.]
214
+ [134. 134. 186. 0. 0. 0.]
215
+ [ 0. 0. 0. 77. 0. 0.]
216
+ [ 0. 0. 0. 0. 77. 0.]
217
+ [ 0. 0. 0. 0. 0. 77.]]
218
+
219
+ The reciprocal (compliance) tensor is given by:
220
+
221
+ >>> S = C.inv()
222
+ >>> print(S)
223
+ Compliance tensor (in Voigt mapping):
224
+ [[ 0.01355473 -0.00567604 -0.00567604 0. 0. 0. ]
225
+ [-0.00567604 0.01355473 -0.00567604 0. 0. 0. ]
226
+ [-0.00567604 -0.00567604 0.01355473 0. 0. 0. ]
227
+ [ 0. 0. 0. 0.01298701 0. 0. ]
228
+ [ 0. 0. 0. 0. 0.01298701 0. ]
229
+ [ 0. 0. 0. 0. 0. 0.01298701]]
230
+
231
+ This also works for tensor arrays. In this case, the returned value will be a compliance tensor array of the
232
+ same shape as the tensor array. For instance:
233
+
234
+ >>> slices = [[[200, 40, 40, 0, 0, 0 ],
235
+ ... [40, 200, 40, 0, 0, 0 ],
236
+ ... [40, 40, 200, 0, 0, 0 ],
237
+ ... [0, 0, 0, 20, 0, 0 ],
238
+ ... [0, 0, 0, 0, 20, 0 ],
239
+ ... [0, 0, 0, 0, 0, 20]],
240
+ ... [[250, 80, 80, 0, 0, 0 ],
241
+ ... [80, 250, 80, 0, 0, 0 ],
242
+ ... [80, 80, 250, 0, 0, 0 ],
243
+ ... [0, 0, 0, 40, 0, 0 ],
244
+ ... [0, 0, 0, 0, 40, 0 ],
245
+ ... [0, 0, 0, 0, 0, 40]]]
246
+ >>> C = StiffnessTensor(slices)
247
+ >>> S = C.inv()
248
+ >>> print(S)
249
+ Compliance tensor (in Voigt mapping):
250
+ [[[ 0.00535714 -0.00089286 -0.00089286 0. 0.
251
+ 0. ]
252
+ [-0.00089286 0.00535714 -0.00089286 0. 0.
253
+ 0. ]
254
+ [-0.00089286 -0.00089286 0.00535714 0. 0.
255
+ 0. ]
256
+ [ 0. 0. 0. 0.05 0.
257
+ 0. ]
258
+ [ 0. 0. 0. 0. 0.05
259
+ 0. ]
260
+ [ 0. 0. 0. 0. 0.
261
+ 0.05 ]]
262
+ <BLANKLINE>
263
+ [[ 0.00473458 -0.00114778 -0.00114778 0. 0.
264
+ 0. ]
265
+ [-0.00114778 0.00473458 -0.00114778 0. 0.
266
+ 0. ]
267
+ [-0.00114778 -0.00114778 0.00473458 0. 0.
268
+ 0. ]
269
+ [ 0. 0. 0. 0.025 0.
270
+ 0. ]
271
+ [ 0. 0. 0. 0. 0.025
272
+ 0. ]
273
+ [ 0. 0. 0. 0. 0.
274
+ 0.025 ]]]
114
275
  """
115
276
  C = np.linalg.inv(self._matrix)
116
- return ComplianceTensor(C, symmetry=self.symmetry, phase_name=self.phase_name)
277
+ t2 = ComplianceTensor(C, mapping=kelvin_mapping, phase_name=self.phase_name)
278
+ t2.mapping = self.mapping.mapping_inverse
279
+ return t2
117
280
 
118
281
  @classmethod
119
282
  def from_txt_file(cls, filename):
@@ -150,16 +313,11 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
150
313
  phase_name = lines[0].split(": ", 1)[1].strip()
151
314
  matrix_start_index += 1
152
315
 
153
- # Parse symmetry if available
154
- if len(lines) > matrix_start_index and lines[matrix_start_index].startswith("Symmetry:"):
155
- symmetry = lines[matrix_start_index].split(": ", 1)[1].strip()
156
- matrix_start_index += 1
157
-
158
316
  # Parse matrix
159
317
  matrix = np.loadtxt(lines[matrix_start_index:])
160
318
 
161
319
  # Return the reconstructed object
162
- return cls(matrix, phase_name=phase_name, symmetry=symmetry)
320
+ return cls(matrix, phase_name=phase_name)
163
321
 
164
322
  def save_to_txt(self, filename, matrix_only=False):
165
323
  """
@@ -170,7 +328,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
170
328
  filename : str
171
329
  Filename to save the tensor to.
172
330
  matrix_only : bool, False
173
- If true, only the components of tje stiffness tensor is saved (no data about phase nor symmetry)
331
+ If true, only the components of the stiffness tensor is saved (no data about phase nor symmetry)
174
332
 
175
333
  See Also
176
334
  --------
@@ -181,8 +339,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
181
339
  if not matrix_only:
182
340
  if self.phase_name is not None:
183
341
  f.write(f"Phase Name: {self.phase_name}\n")
184
- f.write(f"Symmetry: {self.symmetry}\n")
185
- for row in self._matrix:
342
+ for row in self.matrix():
186
343
  f.write(" " + " ".join(f"{value:8.2f}" for value in row) + "\n")
187
344
 
188
345
  @classmethod
@@ -272,11 +429,11 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
272
429
 
273
430
  Notes
274
431
  -----
275
- The relationships between the tensor's components depend on the crystallogrpahic symmetry [1]_.
432
+ The relationships between the tensor's components depend on the crystallogrpahic symmetry [Nye]_.
276
433
 
277
434
  References
278
435
  ----------
279
- .. [1] Nye, J. F. Physical Properties of Crystals. London: Oxford University Press, 1959.
436
+ .. [Nye] Nye, J. F. Physical Properties of Crystals. London: Oxford University Press, 1959.
280
437
 
281
438
  Examples
282
439
  --------
@@ -294,7 +451,6 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
294
451
  [-18. 1. -3. 0. 11. 0.]
295
452
  [ 0. 0. 0. 3. 0. 85.]]
296
453
  Phase: TiNi
297
- Symmetry: monoclinic
298
454
 
299
455
  >>> from Elasticipy.tensors.elasticity import ComplianceTensor\n
300
456
  >>> ComplianceTensor.fromCrystalSymmetry(symmetry='monoclinic', diad='y', phase_name='TiNi',
@@ -310,7 +466,6 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
310
466
  [ 14. -8. 0. 0. 116. 0.]
311
467
  [ 0. 0. 0. 0. 0. 12.]]
312
468
  Phase: TiNi
313
- Symmetry: monoclinic
314
469
  """
315
470
  warn('This function will be removed in a future release. Use {}.{}() instead'.format(cls.__name__,symmetry), DeprecationWarning, stacklevel=2)
316
471
  return cls._fromCrystalSymmetry(symmetry=symmetry, point_group=point_group, diad=diad, phase_name=phase_name,
@@ -319,7 +474,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
319
474
  @classmethod
320
475
  def _fromCrystalSymmetry(cls, symmetry, phase_name, **kwargs):
321
476
  matrix = cls._matrixFromCrystalSymmetry(symmetry=symmetry, **kwargs)
322
- return cls(matrix, phase_name=phase_name, symmetry=symmetry)
477
+ return cls(matrix, phase_name=phase_name)
323
478
 
324
479
 
325
480
  @classmethod
@@ -541,12 +696,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
541
696
  [C16, C26, C36, C46, C56, C66]])
542
697
  return cls(matrix, phase_name=phase_name)
543
698
 
544
- def _single_tensor_only(self, fun_name=''):
545
- if self.ndim:
546
- err_msg = fun_name + ' is not suitable for tensor array. Consider subscripting (e.g. C[0].{}).'.format(fun_name)
547
- raise ValueError(err_msg)
548
-
549
- @property
699
+ @_elementwise_property
550
700
  def Young_modulus(self):
551
701
  """
552
702
  Directional Young's modulus
@@ -556,18 +706,17 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
556
706
  SphericalFunction
557
707
  Young's modulus
558
708
  """
559
- self._single_tensor_only('Young_modulus')
560
709
  if isinstance(self, ComplianceTensor):
561
710
  S = self
562
711
  else:
563
712
  S = self.inv()
564
713
  def compute_young_modulus(u):
565
- a = np.einsum('ijkl,...i,...j,...k,...l->...', S.full_tensor(), u, u, u, u)
714
+ a = np.einsum('ijkl,...i,...j,...k,...l->...', S.full_tensor, u, u, u, u)
566
715
  return 1 / a
567
716
 
568
717
  return SphericalFunction(compute_young_modulus)
569
718
 
570
- @property
719
+ @_elementwise_property
571
720
  def shear_modulus(self):
572
721
  """
573
722
  Directional shear modulus
@@ -577,18 +726,17 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
577
726
  HyperSphericalFunction
578
727
  Shear modulus
579
728
  """
580
- self._single_tensor_only('shear_modulus')
581
729
  if isinstance(self, ComplianceTensor):
582
730
  S = self
583
731
  else:
584
732
  S = self.inv()
585
733
  def compute_shear_modulus(u, v):
586
- G = 0.25/np.einsum('ijkl,...i,...j,...k,...l->...',S.full_tensor(),u,v,u,v)
734
+ G = 0.25/np.einsum('ijkl,...i,...j,...k,...l->...',S.full_tensor,u,v,u,v)
587
735
  return G
588
736
 
589
737
  return HyperSphericalFunction(compute_shear_modulus)
590
738
 
591
- @property
739
+ @_elementwise_property
592
740
  def Poisson_ratio(self):
593
741
  """
594
742
  Directional Poisson's ratio
@@ -609,11 +757,10 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
609
757
 
610
758
  where :math:`\\varepsilon_{jj}` denotes the (compressive) longitudinal strain along the j-th direction.
611
759
  """
612
- self._single_tensor_only('Poisson_ratio')
613
760
  if isinstance(self, ComplianceTensor):
614
- Sfull = self.full_tensor()
761
+ Sfull = self.full_tensor
615
762
  else:
616
- Sfull = self.inv().full_tensor()
763
+ Sfull = self.inv().full_tensor
617
764
  def compute_PoissonRatio(u, v):
618
765
  numer = np.einsum('ijkl,...i,...j,...k,...l->...',Sfull,v,v,u,u)
619
766
  denom = np.einsum('ijkl,...i,...j,...k,...l->...',Sfull,u,u,u,u)
@@ -621,7 +768,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
621
768
 
622
769
  return HyperSphericalFunction(compute_PoissonRatio)
623
770
 
624
- @property
771
+ @_elementwise_property
625
772
  def linear_compressibility(self):
626
773
  """
627
774
  Compute the directional linear compressibility.
@@ -635,13 +782,12 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
635
782
  --------
636
783
  bulk_modulus : bulk modulus of the material
637
784
  """
638
- self._single_tensor_only('linear_compressibility')
639
785
  if isinstance(self, ComplianceTensor):
640
786
  S = self
641
787
  else:
642
788
  S = self.inv()
643
789
  def compute_linear_compressibility(u):
644
- return np.einsum('ijkk,...i,...j->...',S.full_tensor(),u,u)
790
+ return np.einsum('ijkk,...i,...j->...',S.full_tensor,u,u)
645
791
 
646
792
  return SphericalFunction(compute_linear_compressibility)
647
793
 
@@ -661,9 +807,9 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
661
807
  """
662
808
  return self.inv().bulk_modulus
663
809
 
664
- @property
810
+ @_elementwise_property
665
811
  def lame1(self):
666
- """"
812
+ """
667
813
  Compute the first Lamé's parameter (only for isotropic materials).
668
814
 
669
815
  If the stiffness/compliance tensor is not isotropic, NaN is returned.
@@ -677,16 +823,15 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
677
823
  --------
678
824
  lame2 : second Lamé's parameter
679
825
  """
680
- self._single_tensor_only('lame1')
681
826
  if self.is_isotropic():
682
827
  C11 = (self.C11 + self.C22 + self.C33) / 3
683
828
  return C11 - 2 * self.lame2
684
829
  else:
685
830
  return np.nan
686
831
 
687
- @property
832
+ @_elementwise_property
688
833
  def lame2(self):
689
- """"
834
+ """
690
835
  Compute the second Lamé's parameter (only for isotropic materials).
691
836
 
692
837
  If the stiffness/compliance tensor is not isotropic, NaN is returned.
@@ -700,7 +845,6 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
700
845
  --------
701
846
  lame1 : first Lamé's parameter
702
847
  """
703
- self._single_tensor_only('lame2')
704
848
  if self.is_isotropic():
705
849
  return (self.C44 + self.C55 + self.C66) / 3
706
850
  else:
@@ -708,10 +852,10 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
708
852
 
709
853
  def Voigt_average(self, axis=None):
710
854
  """
711
- Compute the Voigt average of the stiffness tensor.
855
+ Compute the Voigt average (from the mean of stiffness tensors).
712
856
 
713
- If the tensor is a tensor array, all its values are considered. Otherwise (i.e. if single), the corresponding
714
- isotropic tensor is returned.
857
+ If the object is a single tensor, the returned tensor corresponds to the average over an infinite set of random
858
+ rotations, resulting in an isotropic behavior. Otherwise, the mean is computed over the given axis.
715
859
 
716
860
  Parameters
717
861
  ----------
@@ -728,24 +872,67 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
728
872
  Reuss_average : compute the Reuss average
729
873
  Hill_average : compute the Voigt-Reuss-Hill average
730
874
  average : generic function for calling either the Voigt, Reuss or Hill average
875
+
876
+ Examples
877
+ --------
878
+ Let us consider the stiffness of pure copper:
879
+
880
+ >>> from Elasticipy.tensors.elasticity import StiffnessTensor
881
+ >>> C = StiffnessTensor.cubic(C11=186, C12=134, C44=77)
882
+
883
+ If we assume that an aggregate is composed of an infinite set grains whose orientations are uniformly
884
+ distributed, the Voigt average is:
885
+
886
+ >>> C.Voigt_average()
887
+ Stiffness tensor (in Voigt mapping):
888
+ [[226.8 113.6 113.6 0. 0. 0. ]
889
+ [113.6 226.8 113.6 0. 0. 0. ]
890
+ [113.6 113.6 226.8 0. 0. 0. ]
891
+ [ 0. 0. 0. 56.6 0. 0. ]
892
+ [ 0. 0. 0. 0. 56.6 0. ]
893
+ [ 0. 0. 0. 0. 0. 56.6]]
894
+
895
+ Now, we consider an aggregate of grains whose orientations follow a pure fibre texture along the X axis. First,
896
+ generate a (large) random set of rotation corresponding to this texture:
897
+
898
+ >>> from scipy.spatial.transform import Rotation
899
+ >>> import numpy as np
900
+ >>> g = Rotation.from_euler('X', np.linspace(0, 90, 1000), degrees=True) # 1000 rotations around X
901
+
902
+ Now apply rotations to compute the array of stiffness tensors:
903
+
904
+ >>> C_rotated = C * g
905
+ >>> C_rotated
906
+ Stiffness tensor array of shape (1000,)
907
+
908
+ Finally, one can check that the Voigt average, computed from the rotated stiffness tensors is:
909
+
910
+ >>> C_rotated.Voigt_average()
911
+ Stiffness tensor (in Voigt mapping):
912
+ [[ 1.86000000e+02 1.34000000e+02 1.34000000e+02 -4.39648318e-17
913
+ 0.00000000e+00 0.00000000e+00]
914
+ [ 1.34000000e+02 2.11474500e+02 1.08525500e+02 0.00000000e+00
915
+ 0.00000000e+00 0.00000000e+00]
916
+ [ 1.34000000e+02 1.08525500e+02 2.11474500e+02 0.00000000e+00
917
+ 0.00000000e+00 0.00000000e+00]
918
+ [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 5.15255000e+01
919
+ 0.00000000e+00 0.00000000e+00]
920
+ [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
921
+ 7.70000000e+01 -7.54674101e-17]
922
+ [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
923
+ 7.54674101e-17 7.70000000e+01]]
731
924
  """
732
925
  if self.ndim:
733
926
  return self.mean(axis=axis)
734
927
  else:
735
- c = self._matrix
736
- A = c[0, 0] + c[1, 1] + c[2, 2]
737
- B = c[0, 1] + c[0, 2] + c[1, 2]
738
- C = c[3, 3] + c[4, 4] + c[5, 5]
739
- C11 = 1 / 5 * A + 2 / 15 * B + 4 / 15 * C
740
- C12 = 1 / 15 * A + 4 / 15 * B - 2 / 15 * C
741
- C44 = (A - B) / 15 + C / 5
742
- mat = _isotropic_matrix(C11, C12, C44)
743
- return StiffnessTensor(mat, symmetry='isotropic', phase_name=self.phase_name)
928
+ return self.infinite_random_average()
744
929
 
745
930
  def Reuss_average(self, axis=None):
746
931
  """
747
- Compute the Reuss average of the stiffness tensor. If the tensor contains no orientation, we assume isotropic
748
- behaviour. Otherwise, the mean is computed over all orientations.
932
+ Compute the Reuss average (from the mean of compliance tensors).
933
+
934
+ If the object is a single tensor, the returned tensor corresponds to the average over an infinite set of random
935
+ rotations, resulting in an isotropic behavior. Otherwise, the mean is computed over the given axis.
749
936
 
750
937
  Parameters
751
938
  ----------
@@ -762,13 +949,70 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
762
949
  Voigt_average : compute the Voigt average
763
950
  Hill_average : compute the Voigt-Reuss-Hill average
764
951
  average : generic function for calling either the Voigt, Reuss or Hill average
952
+
953
+ Examples
954
+ --------
955
+ Let us consider the stiffness of pure copper:
956
+
957
+ >>> from Elasticipy.tensors.elasticity import StiffnessTensor
958
+ >>> C = StiffnessTensor.cubic(C11=186, C12=134, C44=77)
959
+
960
+ If we assume that an aggregate is composed of an infinite set grains whose orientations are uniformly
961
+ distributed, the Voigt average is:
962
+
963
+ >>> C.Reuss_average()
964
+ Stiffness tensor (in Voigt mapping):
965
+ [[208.86206897 122.56896552 122.56896552 0. 0.
966
+ 0. ]
967
+ [122.56896552 208.86206897 122.56896552 0. 0.
968
+ 0. ]
969
+ [122.56896552 122.56896552 208.86206897 0. 0.
970
+ 0. ]
971
+ [ 0. 0. 0. 43.14655172 0.
972
+ 0. ]
973
+ [ 0. 0. 0. 0. 43.14655172
974
+ 0. ]
975
+ [ 0. 0. 0. 0. 0.
976
+ 43.14655172]]
977
+
978
+ Now, we consider an aggregate of grains whose orientations follow a pure fibre texture along the X axis. First,
979
+ generate a (large) random set of rotation corresponding to this texture:
980
+
981
+ >>> from scipy.spatial.transform import Rotation
982
+ >>> import numpy as np
983
+ >>> g = Rotation.from_euler('X', np.linspace(0, 90, 1000), degrees=True) # 1000 rotations around X
984
+
985
+ Now apply rotations to compute the array of stiffness tensors:
986
+
987
+ >>> C_rotated = C * g
988
+ >>> C_rotated
989
+ Stiffness tensor array of shape (1000,)
990
+
991
+ Finally, one can check that the Voigt average, computed from the rotated stiffness tensors is:
992
+
993
+ >>> C_rotated.Reuss_average()
994
+ Stiffness tensor (in Voigt mapping):
995
+ [[ 1.86000000e+02 1.34000000e+02 1.34000000e+02 -1.68008952e-15
996
+ 0.00000000e+00 0.00000000e+00]
997
+ [ 1.34000000e+02 1.98854548e+02 1.21145452e+02 -1.09777845e-15
998
+ 0.00000000e+00 0.00000000e+00]
999
+ [ 1.34000000e+02 1.21145452e+02 1.98854548e+02 -2.89552069e-15
1000
+ 0.00000000e+00 0.00000000e+00]
1001
+ [-1.61936693e-16 1.69241946e-16 -7.96730255e-16 3.88930441e+01
1002
+ 0.00000000e+00 0.00000000e+00]
1003
+ [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
1004
+ 7.70000000e+01 -7.54674101e-17]
1005
+ [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
1006
+ 7.54674101e-17 7.70000000e+01]]
765
1007
  """
766
1008
  return self.inv().Reuss_average(axis=axis).inv()
767
1009
 
768
1010
  def Hill_average(self, axis=None):
769
1011
  """
770
- Compute the (Voigt-Reuss-)Hill average of the stiffness tensor. If the tensor contains no orientation, we assume
771
- isotropic behaviour. Otherwise, the mean is computed over all orientations.
1012
+ Compute the Voigt-Reuss-Hill average (mean of Voigt and Reuss averages for stiffness tensors).
1013
+
1014
+ If the object is a single tensor, the returned tensor corresponds to the average over an infinite set of random
1015
+ rotations, resulting in an isotropic behavior. Otherwise, the mean is computed over the given axis.
772
1016
 
773
1017
  Parameters
774
1018
  ----------
@@ -785,6 +1029,61 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
785
1029
  Voigt_average : compute the Voigt average
786
1030
  Reuss_average : compute the Reuss average
787
1031
  average : generic function for calling either the Voigt, Reuss or Hill average
1032
+
1033
+ Examples
1034
+ --------
1035
+ Let us consider the stiffness of pure copper:
1036
+
1037
+ >>> from Elasticipy.tensors.elasticity import StiffnessTensor
1038
+ >>> C = StiffnessTensor.cubic(C11=186, C12=134, C44=77)
1039
+
1040
+ If we assume that an aggregate is composed of an infinite set grains whose orientations are uniformly
1041
+ distributed, the Voigt average is:
1042
+
1043
+ >>> C.Hill_average()
1044
+ Stiffness tensor (in Voigt mapping):
1045
+ [[217.83103448 118.08448276 118.08448276 0. 0.
1046
+ 0. ]
1047
+ [118.08448276 217.83103448 118.08448276 0. 0.
1048
+ 0. ]
1049
+ [118.08448276 118.08448276 217.83103448 0. 0.
1050
+ 0. ]
1051
+ [ 0. 0. 0. 49.87327586 0.
1052
+ 0. ]
1053
+ [ 0. 0. 0. 0. 49.87327586
1054
+ 0. ]
1055
+ [ 0. 0. 0. 0. 0.
1056
+ 49.87327586]]
1057
+
1058
+ Now, we consider an aggregate of grains whose orientations follow a pure fibre texture along the X axis. First,
1059
+ generate a (large) random set of rotation corresponding to this texture:
1060
+
1061
+ >>> from scipy.spatial.transform import Rotation
1062
+ >>> import numpy as np
1063
+ >>> g = Rotation.from_euler('X', np.linspace(0, 90, 1000), degrees=True) # 1000 rotations around X
1064
+
1065
+ Now apply rotations to compute the array of stiffness tensors:
1066
+
1067
+ >>> C_rotated = C * g
1068
+ >>> C_rotated
1069
+ Stiffness tensor array of shape (1000,)
1070
+
1071
+ Finally, one can check that the Voigt average, computed from the rotated stiffness tensors is:
1072
+
1073
+ >>> C_rotated.Hill_average()
1074
+ Stiffness tensor (in Voigt mapping):
1075
+ [[ 1.86000000e+02 1.34000000e+02 1.34000000e+02 -8.62027175e-16
1076
+ 0.00000000e+00 0.00000000e+00]
1077
+ [ 1.34000000e+02 2.05164524e+02 1.14835476e+02 -5.48889226e-16
1078
+ 0.00000000e+00 0.00000000e+00]
1079
+ [ 1.34000000e+02 1.14835476e+02 2.05164524e+02 -1.44776034e-15
1080
+ 0.00000000e+00 0.00000000e+00]
1081
+ [-8.09683464e-17 8.46209730e-17 -3.98365128e-16 4.52092721e+01
1082
+ 0.00000000e+00 0.00000000e+00]
1083
+ [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
1084
+ 7.70000000e+01 -7.54674101e-17]
1085
+ [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
1086
+ 7.54674101e-17 7.70000000e+01]]
788
1087
  """
789
1088
  Reuss = self.Reuss_average(axis=axis)
790
1089
  Voigt = self.Voigt_average(axis=axis)
@@ -823,22 +1122,25 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
823
1122
  @classmethod
824
1123
  def isotropic(cls, E=None, nu=None, G=None, lame1=None, lame2=None, K=None, phase_name=None):
825
1124
  """
826
- Create an isotropic stiffness tensor from two elasticity coefficients, namely: E, nu, G, lame1, or lame2.
827
- Exactly two of these coefficients must be provided. Note that lame2 is just an alias for G.
1125
+ Create an isotropic stiffness tensor.
1126
+
1127
+ The stiffness tensor must be constructed from exactly two elasticity coefficients, namely: E, nu, G, lame1, or
1128
+ lame2. Note that lame2 is just an alias for G. Each of these coefficient can be a list; in this case, the
1129
+ returned object is a tensor array (see examples).
828
1130
 
829
1131
  Parameters
830
1132
  ----------
831
- E : float, None
1133
+ E : float or list, None
832
1134
  Young modulus
833
- nu : float, None
1135
+ nu : float or list, None
834
1136
  Poisson ratio
835
- G : float, None
1137
+ G : float or list, None
836
1138
  Shear modulus
837
- lame1 : float, None
1139
+ lame1 : float or list, None
838
1140
  First Lamé coefficient
839
- lame2 : float, None
1141
+ lame2 : float or list, None
840
1142
  Second Lamé coefficient (alias for G)
841
- K : float, None
1143
+ K : float or list, None
842
1144
  Bulk modulus
843
1145
  phase_name : str, None
844
1146
  Name to print
@@ -866,6 +1168,30 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
866
1168
  >>> C.shear_modulus
867
1169
  Hyperspherical function
868
1170
  Min=82031.24999999991, Max=82031.25000000006
1171
+
1172
+ Similarly, the same tensor can be defined from another pair of parameters, e.g. Young and shear moduli:
1173
+
1174
+ >>> C=StiffnessTensor.isotropic(E=210e3, G=82031)
1175
+ >>> C.Young_modulus
1176
+ Spherical function
1177
+ Min=210000.0000000001, Max=210000.00000000032
1178
+
1179
+ One can build a tensor array by providing a list of values for the input arguments, instead of floats. For
1180
+ instance:
1181
+
1182
+ >>> C = StiffnessTensor.isotropic(E=(210, 70), nu=(0.28, 0.35)) # Elastic moduli for steel and aluminium
1183
+ >>> C.shape
1184
+ (2,)
1185
+
1186
+ We can easily check that the shear moduli for steel and aluminium are:
1187
+
1188
+ >>> C[0].shear_modulus
1189
+ Hyperspherical function
1190
+ Min=82.0312499999999, Max=82.03125000000006
1191
+
1192
+ >>> C[1].shear_modulus
1193
+ Hyperspherical function
1194
+ Min=25.925925925925895, Max=25.925925925925952
869
1195
  """
870
1196
  return ComplianceTensor.isotropic(E=E, nu=nu, G=G, lame1=lame1, lame2=lame2, K=K, phase_name=phase_name).inv()
871
1197
 
@@ -997,8 +1323,8 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
997
1323
 
998
1324
  Returns
999
1325
  -------
1000
- Gamma : np.ndarray
1001
- Array of Christoffel tensor(s). if u is a list of directions, Gamma[i] is the Christoffel tensor for
1326
+ SymmetricSecondOrderTensor
1327
+ Christoffel tensor(s). if u is a list of directions, Gamma[i] is the Christoffel tensor for
1002
1328
  direction u[i].
1003
1329
 
1004
1330
  See Also
@@ -1007,14 +1333,24 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1007
1333
 
1008
1334
  Notes
1009
1335
  -----
1010
- For a given stiffness tensor **C** and a given unit vector **u**, the Christoffel tensor is defined as [2]_ :
1336
+ For a given stiffness tensor **C** and a given unit vector **u**, the Christoffel tensor is defined as [Jaeken]_ :
1011
1337
 
1012
1338
  .. math:: M_{ij} = C_{iklj}.u_k.u_l
1013
1339
 
1340
+ Examples
1341
+ --------
1342
+ >>> from Elasticipy.tensors.elasticity import StiffnessTensor
1343
+ >>> C = StiffnessTensor.cubic(C11=186, C12=134, C44=77) # Cubic Cu
1344
+ >>> C.Christoffel_tensor([0, 0, 1])
1345
+ Symmetric second-order tensor
1346
+ [[ 77. 0. 0.]
1347
+ [ 0. 77. 0.]
1348
+ [ 0. 0. 186.]]
1014
1349
  """
1015
1350
  u_vec = np.atleast_2d(u)
1016
1351
  u_vec = (u_vec.T / np.linalg.norm(u_vec, axis=1)).T
1017
- return np.einsum('inmj,pn,pm->pij', self.full_tensor(), u_vec, u_vec)
1352
+ G = np.einsum('inmj,pn,pm->pij', self.full_tensor, u_vec, u_vec)
1353
+ return SymmetricSecondOrderTensor(np.squeeze(G))
1018
1354
 
1019
1355
  def wave_velocity(self, rho):
1020
1356
  """
@@ -1040,7 +1376,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1040
1376
 
1041
1377
  Notes
1042
1378
  -----
1043
- The estimation of the wave velocities is made by finding the eigenvalues of the Christoffel tensor [2]_.
1379
+ The estimation of the wave velocities is made by finding the eigenvalues of the Christoffel tensor [Jaeken]_.
1044
1380
 
1045
1381
  One should double-check the units. The table below provides hints about the unit you get, depending on the units
1046
1382
  you use for stiffness and the mass density:
@@ -1061,17 +1397,51 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1061
1397
 
1062
1398
  References
1063
1399
  ----------
1064
- .. [2] J. W. Jaeken, S. Cottenier, Solving the Christoffel equation: Phase and group velocities, Computer Physics
1400
+ .. [Jaeken] J. W. Jaeken, S. Cottenier, Solving the Christoffel equation: Phase and group velocities, Computer Physics
1065
1401
  Communications (207), 2016, https://doi.org/10.1016/j.cpc.2016.06.014.
1066
1402
 
1403
+ Examples
1404
+ --------
1405
+ We will investigate the wave velocities in cubic Cu. First, define the stiffness tensor:
1406
+
1407
+ >>> C = StiffnessTensor.cubic(C11=186e3, C12=134e3, C44=77e3) # Stiffness in MPa
1408
+ >>> rho = 8960 # mass density in kg/m³
1409
+ >>> c_p, c_s1, c_s2 = C.wave_velocity(rho)
1410
+
1411
+ The range of velocities of the (fast) primary wave range can be printed with:
1412
+
1413
+ >>> print(c_p)
1414
+ Spherical function
1415
+ Min=4.556196722204669, Max=5.324304112812698
1416
+
1417
+ As the stiffness is given in MPa and the mass desity is given in kg/m³, the velocities are returned in km/s
1418
+ (see Notes). If one wants to know the direction for min/max values:
1419
+
1420
+ >>> val_min, u_min = c_p.min()
1421
+ >>> print(u_min)
1422
+ [[0. 0. 1.]]
1423
+
1424
+ >>> val_max, u_max = c_p.max()
1425
+ >>> print(u_max)
1426
+ [[0.57735022 0.57735033 0.57735026]]
1427
+
1428
+ The secondary wave velocities are given by ``cs_1`` and ``cs_2``:
1429
+
1430
+ >>> c_s1
1431
+ Spherical function
1432
+ Min=2.1906864565414192, Max=2.9315098498896446
1433
+
1434
+ >>> c_s2
1435
+ Spherical function
1436
+ Min=1.7034628596749235, Max=2.9315098498896437
1067
1437
  """
1068
- self._single_tensor_only('wave_velocity')
1438
+ if self.ndim:
1439
+ raise ValueError('This function is not suitable for tensor array. Consider subscripting (e.g. C[0].wave_velocity()).')
1069
1440
  def make_fun(index):
1070
1441
  def fun(n):
1071
1442
  Gamma = self.Christoffel_tensor(n)
1072
- eig, _ = np.linalg.eigh(Gamma)
1073
- eig_of_interest = eig[...,2-index] # Switch ordering (descending order)
1074
- return np.sqrt(eig_of_interest / rho)
1443
+ eig = Gamma.eigvals()
1444
+ return np.sqrt(eig[...,index] / rho)
1075
1445
 
1076
1446
  return fun
1077
1447
 
@@ -1112,9 +1482,8 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1112
1482
  key = str(material.material_id)
1113
1483
  if material.elastic_tensor is not None:
1114
1484
  matrix = material.elastic_tensor.ieee_format
1115
- symmetry = material.symmetry.crystal_system.value
1116
1485
  phase_name = material.formula_pretty
1117
- C = StiffnessTensor(np.asarray(matrix), symmetry=str(symmetry), phase_name=phase_name)
1486
+ C = StiffnessTensor(np.asarray(matrix), phase_name=phase_name)
1118
1487
  else:
1119
1488
  C = None
1120
1489
  Cdict[key] = C
@@ -1149,7 +1518,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1149
1518
  if np.all([isinstance(a, ComplianceTensor) for a in Cs]):
1150
1519
  Cs = [C.inv() for C in Cs]
1151
1520
  if np.all([isinstance(a, StiffnessTensor) for a in Cs]):
1152
- C_stack = np.array([C._matrix for C in Cs])
1521
+ C_stack = np.array([C.matrix() for C in Cs])
1153
1522
  method = method.capitalize()
1154
1523
  if method == 'Voigt':
1155
1524
  C_avg = np.average(C_stack, weights=volume_fractions, axis=0)
@@ -1193,13 +1562,16 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1193
1562
  .. [3] S. I. Ranganathan and M. Ostoja-Starzewski, Universal Elastic Anisotropy Index,
1194
1563
  *Phys. Rev. Lett.*, 101(5), 055504, 2008. https://doi.org/10.1103/PhysRevLett.101.055504
1195
1564
  """
1196
- self._single_tensor_only('universal_anisotropy')
1197
- Cvoigt = self.Voigt_average()
1198
- Creuss = self.Reuss_average()
1199
- Gv = Cvoigt._matrix[3, 3]
1200
- Gr = Creuss._matrix[3, 3]
1201
- Kv = Cvoigt.bulk_modulus
1202
- Kr = Creuss.bulk_modulus
1565
+ if isinstance(self, StiffnessTensor):
1566
+ C_voigt = self.infinite_random_average()
1567
+ S_reuss = self.inv().infinite_random_average()
1568
+ else:
1569
+ S_reuss = self.infinite_random_average()
1570
+ C_voigt = self.inv().infinite_random_average()
1571
+ Kv = C_voigt.bulk_modulus
1572
+ Kr = S_reuss.bulk_modulus
1573
+ Gv = C_voigt._matrix[..., 3, 3] / 2
1574
+ Gr = 1 / S_reuss._matrix[..., 3, 3] / 2
1203
1575
  return 5 * Gv / Gr + Kv / Kr - 6
1204
1576
 
1205
1577
  def Zener_ratio(self, tol=1e-4):
@@ -1264,7 +1636,6 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1264
1636
  0. ]
1265
1637
  [ 25.98076211 -25.98076211 0. 0. 0.
1266
1638
  65. ]]
1267
- Symmetry: cubic
1268
1639
 
1269
1640
  Still, we have
1270
1641
  >>> C_rot.Zener_ratio()
@@ -1293,7 +1664,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1293
1664
  from pymatgen.analysis.elasticity import elastic as matgenElast
1294
1665
  except ImportError:
1295
1666
  raise ModuleNotFoundError('pymatgen module is required for this function.')
1296
- return matgenElast.ElasticTensor(self.full_tensor())
1667
+ return matgenElast.ElasticTensor(self.full_tensor)
1297
1668
 
1298
1669
  def to_Kelvin(self):
1299
1670
  """
@@ -1311,7 +1682,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1311
1682
 
1312
1683
  Notes
1313
1684
  -----
1314
- This mapping convention is defined as follows [4]_:
1685
+ This mapping convention is defined as follows [Helbig]_:
1315
1686
 
1316
1687
  .. math::
1317
1688
 
@@ -1327,11 +1698,32 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1327
1698
 
1328
1699
  References
1329
1700
  ----------
1330
- .. [4] Helbig, K. (2013). What Kelvin might have written about Elasticity. Geophysical Prospecting, 61(1), 1-20.
1331
- doi: 10.1111/j.1365-2478.2011.01049.x
1332
- """
1333
- kelvin_mapping = KelvinMapping()
1334
- return self._matrix /self.mapping.matrix * kelvin_mapping.matrix
1701
+ .. [Helbig] Helbig, K. (2013). What Kelvin might have written about Elasticity. Geophysical Prospecting, 61(1), 1-20.
1702
+ doi: `10.1111/j.1365-2478.2011.01049.x`_
1703
+
1704
+ .. _10.1111/j.1365-2478.2011.01049.x: https://doi.org/10.1111/j.1365-2478.2011.01049.x
1705
+
1706
+ Examples
1707
+ --------
1708
+ >>> from Elasticipy.tensors.elasticity import StiffnessTensor
1709
+ >>> C = StiffnessTensor.cubic(C11=200, C12=40, C44=20)
1710
+ >>> print(C)
1711
+ Stiffness tensor (in Voigt mapping):
1712
+ [[200. 40. 40. 0. 0. 0.]
1713
+ [ 40. 200. 40. 0. 0. 0.]
1714
+ [ 40. 40. 200. 0. 0. 0.]
1715
+ [ 0. 0. 0. 20. 0. 0.]
1716
+ [ 0. 0. 0. 0. 20. 0.]
1717
+ [ 0. 0. 0. 0. 0. 20.]]
1718
+ >>> C.to_Kelvin()
1719
+ array([[200., 40., 40., 0., 0., 0.],
1720
+ [ 40., 200., 40., 0., 0., 0.],
1721
+ [ 40., 40., 200., 0., 0., 0.],
1722
+ [ 0., 0., 0., 40., 0., 0.],
1723
+ [ 0., 0., 0., 0., 40., 0.],
1724
+ [ 0., 0., 0., 0., 0., 40.]])
1725
+ """
1726
+ return self._matrix
1335
1727
 
1336
1728
  def eig(self):
1337
1729
  """
@@ -1354,9 +1746,9 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1354
1746
 
1355
1747
  Notes
1356
1748
  -----
1357
- The definition for eigenstiffnesses and the eigenstrains are introduced in [4]_.
1749
+ The definition for eigenstiffnesses and the eigenstrains are introduced in [Helbig]_.
1358
1750
  """
1359
- return np.linalg.eigh(self.to_Kelvin())
1751
+ return np.linalg.eigh(self._matrix)
1360
1752
 
1361
1753
  @property
1362
1754
  def eig_stiffnesses(self):
@@ -1373,8 +1765,22 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1373
1765
  eig : returns the eigenstiffnesses and the eigenstrains
1374
1766
  eig_strains : returns the eigenstrains only
1375
1767
  eig_stiffnesses_multiplicity : returns the unique values of eigenstiffnesses with multiplicity
1768
+
1769
+ Notes
1770
+ -----
1771
+ The eigenstiffnesses are defined in [Helbig]_.
1772
+
1773
+ Examples
1774
+ --------
1775
+ >>> from Elasticipy.tensors.elasticity import StiffnessTensor
1776
+ >>> C = StiffnessTensor.cubic(C11=200, C12=40, C44=20)
1777
+ >>> C.eig_stiffnesses
1778
+ array([ 40., 40., 40., 160., 160., 280.])
1779
+
1780
+ These values actually correspond to 2*C44 (with multiplicity = 3), C11-C12 (with multiplicity = 2) and C11+2C12
1781
+ (no multiplicity); see [Helbig]_.
1376
1782
  """
1377
- return np.linalg.eigvalsh(self.to_Kelvin())
1783
+ return np.linalg.eigvalsh(self._matrix)
1378
1784
 
1379
1785
  @property
1380
1786
  def eig_strains(self):
@@ -1428,10 +1834,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1428
1834
  --------
1429
1835
  to_Kelvin : return the components as a (6,6) matrix following the Kelvin convention
1430
1836
  """
1431
- kelvin_mapping = KelvinMapping()
1432
- t = cls(matrix / kelvin_mapping.matrix, **kwargs)
1433
- t._matrix *= t.mapping.matrix
1434
- return t
1837
+ return cls(matrix, mapping=kelvin_mapping, **kwargs)
1435
1838
 
1436
1839
  def eig_stiffnesses_multiplicity(self, tol=1e-4):
1437
1840
  """
@@ -1553,9 +1956,9 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1553
1956
  8.1901353 ]
1554
1957
  [-20.34233446 0.99714278 19.34519167 -6.52548033 8.1901353
1555
1958
  39.41409344]]
1556
- Symmetry: cubic
1557
1959
 
1558
- Once rotated, it is not clear if the stiffness tensors has cubic symmetry. Yet:
1960
+ Once rotated, it is not clear if the stiffness tensors has cubic symmetry on sight. Yet:
1961
+
1559
1962
  >>> C_rotated.is_cubic()
1560
1963
  True
1561
1964
 
@@ -1591,12 +1994,127 @@ class ComplianceTensor(StiffnessTensor):
1591
1994
  """
1592
1995
  Class for manipulating compliance tensors
1593
1996
  """
1594
- tensor_name = 'Compliance'
1997
+ _tensor_name = 'Compliance'
1595
1998
  _C11_C12_factor = 2.0
1596
1999
  _component_prefix = 'S'
1597
2000
  _C46_C56_factor = 2.0
1598
2001
 
1599
2002
  def __init__(self, C, check_positive_definite=True, mapping=VoigtMapping(tensor='Compliance'), **kwargs):
2003
+ """
2004
+ Construct a compliance tensor or an array of commpliance tensors.
2005
+
2006
+ The compliance tensor can be constructed from a (6,6) matrix or slices of (6,6) matrices. These matrices must be
2007
+ symmetric. An error is thrown if this matrix in not definite positive (except if
2008
+ ``check_positive_definite==False``, see below). The input argument can also be the full tensor (array of shape
2009
+ (...,3,3,3,3)).
2010
+
2011
+ Parameters
2012
+ ----------
2013
+ M : list or np.ndarray
2014
+ (...,6,6) matrix corresponding to the compliance tensor, written using the Voigt notation, or array of shape
2015
+ (...,3,3,3,3).
2016
+ phase_name : str, optional
2017
+ Phase name to display
2018
+ check_positive_definite : bool, optional
2019
+ Whether to check if the input matrix is positive definite or not. True by default.
2020
+ check_symmetry : bool, optional
2021
+ Whether to check or not that the input matrix is symmetric.
2022
+ force_symmetry : bool, optional
2023
+ If true, the major symmetry of the tensor is forced
2024
+ mapping : str or MappingConvention
2025
+ mapping convention to use. Default is VoigtMapping.
2026
+
2027
+ Notes
2028
+ -----
2029
+ The units used when building the comliance tensor are up to the user (/GPa, /MPa, /psi etc.). Therefore, the
2030
+ results you will get when performing operations (Young's modulus, "product" with strain tensor etc.) will be
2031
+ consistent with these units. For instance, if the compliance tensor is defined in /GPa, the computed stress will
2032
+ be given in GPa.
2033
+
2034
+ Examples
2035
+ --------
2036
+ Create a compliance tensor for cubic symmetry:
2037
+
2038
+ >>> matrix = [[200, 40, 40, 0, 0, 0 ],
2039
+ ... [40, 200, 40, 0, 0, 0 ],
2040
+ ... [40, 40, 200, 0, 0, 0 ],
2041
+ ... [0, 0, 0, 20, 0, 0 ],
2042
+ ... [0, 0, 0, 0, 20, 0 ],
2043
+ ... [0, 0, 0, 0, 0, 20]]
2044
+ >>> from Elasticipy.tensors.elasticity import ComplianceTensor
2045
+ >>> S = ComplianceTensor(matrix)
2046
+ >>> print(S)
2047
+ Compliance tensor (in Voigt mapping):
2048
+ [[200. 40. 40. 0. 0. 0.]
2049
+ [ 40. 200. 40. 0. 0. 0.]
2050
+ [ 40. 40. 200. 0. 0. 0.]
2051
+ [ 0. 0. 0. 20. 0. 0.]
2052
+ [ 0. 0. 0. 0. 20. 0.]
2053
+ [ 0. 0. 0. 0. 0. 20.]]
2054
+
2055
+ Create a stiffness tensor from full (3,3,3,3) array:
2056
+
2057
+ >>> S_full = S.full_tensor # (3,3,3,3) numpy array
2058
+ >>> print((type(S_full), S_full.shape))
2059
+ (<class 'numpy.ndarray'>, (3, 3, 3, 3))
2060
+
2061
+ >>> ComplianceTensor(S_full)
2062
+ Compliance tensor (in Voigt mapping):
2063
+ [[200. 40. 40. 0. 0. 0.]
2064
+ [ 40. 200. 40. 0. 0. 0.]
2065
+ [ 40. 40. 200. 0. 0. 0.]
2066
+ [ 0. 0. 0. 20. 0. 0.]
2067
+ [ 0. 0. 0. 0. 20. 0.]
2068
+ [ 0. 0. 0. 0. 0. 20.]]
2069
+
2070
+ Create an array of compliance tensors:
2071
+
2072
+ First, we create two slices of (6,6) matrices, corresponding to two compliance values:
2073
+
2074
+ >>> slices = [[[200, 40, 40, 0, 0, 0 ],
2075
+ ... [40, 200, 40, 0, 0, 0 ],
2076
+ ... [40, 40, 200, 0, 0, 0 ],
2077
+ ... [0, 0, 0, 20, 0, 0 ],
2078
+ ... [0, 0, 0, 0, 20, 0 ],
2079
+ ... [0, 0, 0, 0, 0, 20]],
2080
+ ... [[250, 80, 80, 0, 0, 0 ],
2081
+ ... [80, 250, 80, 0, 0, 0 ],
2082
+ ... [80, 80, 250, 0, 0, 0 ],
2083
+ ... [0, 0, 0, 40, 0, 0 ],
2084
+ ... [0, 0, 0, 0, 40, 0 ],
2085
+ ... [0, 0, 0, 0, 0, 40]]]
2086
+
2087
+ Then, one can create an array of compliance tensors:
2088
+
2089
+ >>> S_array=ComplianceTensor(slices)
2090
+ >>> print(S_array)
2091
+ Compliance tensor (in Voigt mapping):
2092
+ [[[200. 40. 40. 0. 0. 0.]
2093
+ [ 40. 200. 40. 0. 0. 0.]
2094
+ [ 40. 40. 200. 0. 0. 0.]
2095
+ [ 0. 0. 0. 20. 0. 0.]
2096
+ [ 0. 0. 0. 0. 20. 0.]
2097
+ [ 0. 0. 0. 0. 0. 20.]]
2098
+ <BLANKLINE>
2099
+ [[250. 80. 80. 0. 0. 0.]
2100
+ [ 80. 250. 80. 0. 0. 0.]
2101
+ [ 80. 80. 250. 0. 0. 0.]
2102
+ [ 0. 0. 0. 40. 0. 0.]
2103
+ [ 0. 0. 0. 0. 40. 0.]
2104
+ [ 0. 0. 0. 0. 0. 40.]]]
2105
+
2106
+
2107
+ This array can be subindexed. E.g.:
2108
+
2109
+ >>> S_array[0]
2110
+ Compliance tensor (in Voigt mapping):
2111
+ [[200. 40. 40. 0. 0. 0.]
2112
+ [ 40. 200. 40. 0. 0. 0.]
2113
+ [ 40. 40. 200. 0. 0. 0.]
2114
+ [ 0. 0. 0. 20. 0. 0.]
2115
+ [ 0. 0. 0. 0. 20. 0.]
2116
+ [ 0. 0. 0. 0. 0. 20.]]
2117
+ """
1600
2118
  super().__init__(C, check_positive_definite=check_positive_definite, mapping=mapping, **kwargs)
1601
2119
  self.mapping_name = 'Voigt'
1602
2120
 
@@ -1619,21 +2137,15 @@ class ComplianceTensor(StiffnessTensor):
1619
2137
  Reciprocal tensor
1620
2138
  """
1621
2139
  S = np.linalg.inv(self._matrix)
1622
- return StiffnessTensor(S, symmetry=self.symmetry, phase_name=self.phase_name)
2140
+ t = StiffnessTensor(S, mapping=kelvin_mapping, phase_name=self.phase_name)
2141
+ t.mapping = self.mapping.mapping_inverse
2142
+ return t
1623
2143
 
1624
2144
  def Reuss_average(self, axis=None):
1625
2145
  if self.ndim:
1626
2146
  return self.mean(axis=axis)
1627
2147
  else:
1628
- s = self._matrix
1629
- A = s[0, 0] + s[1, 1] + s[2, 2]
1630
- B = s[0, 1] + s[0, 2] + s[1, 2]
1631
- C = s[3, 3] + s[4, 4] + s[5, 5]
1632
- S11 = 1 / 5 * A + 2 / 15 * B + 1 / 15 * C
1633
- S12 = 1 / 15 * A + 4 / 15 * B - 1 / 30 * C
1634
- S44 = 4 / 15 * (A - B) + 1 / 5 * C
1635
- mat = _isotropic_matrix(S11, S12, S44)
1636
- return ComplianceTensor(mat, symmetry='isotropic', phase_name=self.phase_name)
2148
+ return self.infinite_random_average()
1637
2149
 
1638
2150
  def Voigt_average(self, axis=None):
1639
2151
  return self.inv().Voigt_average(axis=axis).inv()
@@ -1652,42 +2164,56 @@ class ComplianceTensor(StiffnessTensor):
1652
2164
  if n_specified != 2:
1653
2165
  raise ValueError("Exactly two values are required among E, nu, G, K, lame1 and lame2.")
1654
2166
  if K is not None:
2167
+ K = np.asarray(K)
1655
2168
  if E is not None:
2169
+ E = np.asarray(E, dtype=float)
1656
2170
  G = 3 * K * E / (9 * K - E)
1657
2171
  nu = (3 * K - E) / 6 / K
1658
2172
  elif lame1 is not None:
2173
+ lame1 = np.asarray(lame1, dtype=float)
1659
2174
  E = 9 * K * (K - lame1) / (3 * K -lame1)
1660
2175
  G= 3 * (K - lame1) / 2
1661
2176
  nu = lame1 / (3*K-lame1)
1662
2177
  elif G is not None:
2178
+ G = np.asarray(G, dtype=float)
1663
2179
  E = 9 * K * G / (3 * K + G)
1664
2180
  nu = (3 * K - 2 * G) / 2 / (3 * K + G)
1665
2181
  elif nu is not None:
2182
+ nu = np.asarray(nu, dtype=float)
1666
2183
  E = 3 * K * (1 - 2 * nu)
1667
2184
  G = E / 2 / (1 + nu)
1668
2185
  elif E is not None:
2186
+ E = np.asarray(E, dtype=float)
1669
2187
  if lame1 is not None:
1670
- R = np.sqrt(E**2 + 9*lame1**2+2*E*lame1)
2188
+ lame1 = np.asarray(lame1, dtype=float)
2189
+ R = np.sqrt(E**2 + 9*lame1**2 + 2*E*lame1)
1671
2190
  G = (E - 3 * lame1 + R) / 4
1672
2191
  nu = 2 * lame1 / (E + lame1 + R)
1673
2192
  elif G is not None:
2193
+ G = np.asarray(G, dtype=float)
1674
2194
  nu = E / 2 / G - 1
1675
2195
  elif nu is not None:
2196
+ nu = np.asarray(nu, dtype=float)
1676
2197
  G = E / 2 / (1 + nu)
1677
2198
  elif lame1 is not None:
2199
+ lame1 = np.asarray(lame1, dtype=float)
1678
2200
  if G is not None:
2201
+ G = np.asarray(G, dtype=float)
1679
2202
  E = G * (3 * lame1 + 2 * G) / (lame1 + G)
1680
2203
  nu = lame1 / 2 / (lame1 + G)
1681
2204
  elif nu is not None:
2205
+ nu = np.asarray(nu, dtype=float)
1682
2206
  E = lame1 * ( 1 + nu) * (1 - 2 * nu) / nu
1683
2207
  G = lame1 * (1 - 2 * nu) / 2 / nu
1684
2208
  elif (nu is not None) and (G is not None):
2209
+ nu = np.asarray(nu, dtype=float)
2210
+ G = np.asarray(G)
1685
2211
  E = 2 * G * (1 + nu)
1686
2212
  S11 = 1/E
1687
2213
  S12 = -nu/E
1688
2214
  S44 = 1 / G
1689
2215
  S_mat = _isotropic_matrix(S11, S12, S44)
1690
- return ComplianceTensor(S_mat, symmetry='isotropic', phase_name=phase_name)
2216
+ return ComplianceTensor(S_mat, phase_name=phase_name)
1691
2217
 
1692
2218
  @classmethod
1693
2219
  def orthotropic(cls, *, Ex, Ey, Ez, Gxy, Gxz, Gyz,
@@ -1703,7 +2229,7 @@ class ComplianceTensor(StiffnessTensor):
1703
2229
  [0, 0, 0, 0, 1 / Gxz, 0],
1704
2230
  [0, 0, 0, 0, 0, 1 / Gxy]])
1705
2231
  S = tri_sup + np.tril(tri_sup.T, -1)
1706
- return ComplianceTensor(S, symmetry='orthotropic', **kwargs)
2232
+ return ComplianceTensor(S, **kwargs)
1707
2233
 
1708
2234
  @classmethod
1709
2235
  def transverse_isotropic(cls, *args, **kwargs):
@@ -1746,7 +2272,7 @@ class ComplianceTensor(StiffnessTensor):
1746
2272
  from pymatgen.analysis.elasticity import elastic as matgenElast
1747
2273
  except ImportError:
1748
2274
  raise ModuleNotFoundError('pymatgen module is required for this function.')
1749
- return matgenElast.ComplianceTensor(self.full_tensor())
2275
+ return matgenElast.ComplianceTensor(self.full_tensor)
1750
2276
 
1751
2277
  def eig(self):
1752
2278
  """
@@ -1769,7 +2295,7 @@ class ComplianceTensor(StiffnessTensor):
1769
2295
 
1770
2296
  Notes
1771
2297
  -----
1772
- The definition for eigencompliances and the eigenstresses are introduced in [4]_.
2298
+ The definition for eigencompliances and the eigenstresses are introduced in [Helbig]_.
1773
2299
  """
1774
2300
  return np.linalg.eigh(self.to_Kelvin())
1775
2301