elasticipy 4.1.1__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,10 +1,25 @@
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
10
+ from warnings import warn
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
+
8
23
 
9
24
  def _parse_tensor_components(prefix, **kwargs):
10
25
  pattern = r'^{}(\d{{2}})$'.format(prefix)
@@ -18,15 +33,6 @@ def _parse_tensor_components(prefix, **kwargs):
18
33
  def _indices2str(ij):
19
34
  return f'{ij[0] + 1}{ij[1] + 1}'
20
35
 
21
- def _isotropic_matrix(C11, C12, C44):
22
- return np.array([[C11, C12, C12, 0, 0, 0],
23
- [C12, C11, C12, 0, 0, 0],
24
- [C12, C12, C11, 0, 0, 0],
25
- [0, 0, 0, C44, 0, 0],
26
- [0, 0, 0, 0, C44, 0],
27
- [0, 0, 0, 0, 0, C44]])
28
-
29
-
30
36
  def _check_definite_positive(mat):
31
37
  try:
32
38
  np.linalg.cholesky(mat)
@@ -46,30 +52,33 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
46
52
  """
47
53
  Class for manipulating fourth-order stiffness tensors.
48
54
  """
49
- tensor_name = 'Stiffness'
50
- C11_C12_factor = 0.5
51
- C46_C56_factor = 1.0
52
- component_prefix = 'C'
55
+ _tensor_name = 'Stiffness'
56
+ _C11_C12_factor = 0.5
57
+ _C46_C56_factor = 1.0
58
+ _component_prefix = 'C'
53
59
 
54
- 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):
55
61
  """
56
- Construct of stiffness tensor from a (6,6) matrix.
62
+ Construct a stiffness tensor or an array of stiffness tensors.
57
63
 
58
- 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)).
59
68
 
60
69
  Parameters
61
70
  ----------
62
- M : np.ndarray
63
- (6,6) matrix corresponding to the stiffness tensor, written using the Voigt notation, or array of shape
64
- (3,3,3,3).
65
- phase_name : str, default None
66
- Name to display
67
- symmetry : str, default Triclinic
68
- 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.
69
78
  check_symmetry : bool, optional
70
79
  Whether to check or not that the input matrix is symmetric.
71
80
  force_symmetry : bool, optional
72
- If true, the major symmetry of the tensor is forces
81
+ If true, the major symmetry of the tensor is forced
73
82
  mapping : str or MappingConvention
74
83
  mapping convention to use. Default is VoigtMapping.
75
84
 
@@ -79,11 +88,94 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
79
88
  results you will get when performing operations (Young's modulus, "product" with strain tensor etc.) will be
80
89
  consistent with these units. For instance, if the stiffness tensor is defined in GPa, the computed stress will
81
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.]]
82
175
  """
83
176
  super().__init__(M, mapping=mapping, **kwargs)
84
177
  if check_positive_definite:
85
178
  _check_definite_positive(self._matrix)
86
- self.symmetry = symmetry
87
179
  self.phase_name = phase_name
88
180
 
89
181
  def __mul__(self, other):
@@ -99,7 +191,6 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
99
191
  string = super().__repr__()
100
192
  if self.phase_name is not None:
101
193
  string += '\nPhase: {}'.format(self.phase_name)
102
- string += '\nSymmetry: {}'.format(self.symmetry)
103
194
  return string
104
195
 
105
196
  def inv(self):
@@ -110,9 +201,82 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
110
201
  -------
111
202
  ComplianceTensor
112
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 ]]]
113
275
  """
114
276
  C = np.linalg.inv(self._matrix)
115
- 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
116
280
 
117
281
  @classmethod
118
282
  def from_txt_file(cls, filename):
@@ -149,16 +313,11 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
149
313
  phase_name = lines[0].split(": ", 1)[1].strip()
150
314
  matrix_start_index += 1
151
315
 
152
- # Parse symmetry if available
153
- if len(lines) > matrix_start_index and lines[matrix_start_index].startswith("Symmetry:"):
154
- symmetry = lines[matrix_start_index].split(": ", 1)[1].strip()
155
- matrix_start_index += 1
156
-
157
316
  # Parse matrix
158
317
  matrix = np.loadtxt(lines[matrix_start_index:])
159
318
 
160
319
  # Return the reconstructed object
161
- return cls(matrix, phase_name=phase_name, symmetry=symmetry)
320
+ return cls(matrix, phase_name=phase_name)
162
321
 
163
322
  def save_to_txt(self, filename, matrix_only=False):
164
323
  """
@@ -169,7 +328,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
169
328
  filename : str
170
329
  Filename to save the tensor to.
171
330
  matrix_only : bool, False
172
- 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)
173
332
 
174
333
  See Also
175
334
  --------
@@ -180,14 +339,13 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
180
339
  if not matrix_only:
181
340
  if self.phase_name is not None:
182
341
  f.write(f"Phase Name: {self.phase_name}\n")
183
- f.write(f"Symmetry: {self.symmetry}\n")
184
- for row in self._matrix:
342
+ for row in self.matrix():
185
343
  f.write(" " + " ".join(f"{value:8.2f}" for value in row) + "\n")
186
344
 
187
345
  @classmethod
188
346
  def _matrixFromCrystalSymmetry(cls, symmetry='Triclinic', point_group=None, diad='y', prefix=None, **kwargs):
189
347
  if prefix is None:
190
- prefix = cls.component_prefix
348
+ prefix = cls._component_prefix
191
349
  values = _parse_tensor_components(prefix, **kwargs)
192
350
  C = np.zeros((6, 6))
193
351
  symmetry = symmetry.capitalize()
@@ -228,17 +386,17 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
228
386
  C11_C12 = symmetry_description.C11_C12
229
387
  if C11_C12:
230
388
  for index in C11_C12:
231
- C[index] = (C[0, 0] - C[0, 1]) * cls.C11_C12_factor
389
+ C[index] = (C[0, 0] - C[0, 1]) * cls._C11_C12_factor
232
390
 
233
391
  if symmetry == 'Trigonal':
234
- C[3, 5] = cls.C46_C56_factor * C[3, 5]
235
- C[4, 5] = cls.C46_C56_factor * C[4, 5]
392
+ C[3, 5] = cls._C46_C56_factor * C[3, 5]
393
+ C[4, 5] = cls._C46_C56_factor * C[4, 5]
236
394
 
237
395
  return C + np.tril(C.T, -1)
238
396
 
239
397
  @classmethod
240
398
  def fromCrystalSymmetry(cls, symmetry='Triclinic', point_group=None, diad='y', phase_name=None, prefix=None,
241
- **kwargs):
399
+ **kwargs):
242
400
  """
243
401
  Create a fourth-order tensor from limited number of components, taking advantage of crystallographic symmetries
244
402
 
@@ -271,11 +429,11 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
271
429
 
272
430
  Notes
273
431
  -----
274
- 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]_.
275
433
 
276
434
  References
277
435
  ----------
278
- .. [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.
279
437
 
280
438
  Examples
281
439
  --------
@@ -293,7 +451,6 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
293
451
  [-18. 1. -3. 0. 11. 0.]
294
452
  [ 0. 0. 0. 3. 0. 85.]]
295
453
  Phase: TiNi
296
- Symmetry: monoclinic
297
454
 
298
455
  >>> from Elasticipy.tensors.elasticity import ComplianceTensor\n
299
456
  >>> ComplianceTensor.fromCrystalSymmetry(symmetry='monoclinic', diad='y', phase_name='TiNi',
@@ -309,11 +466,15 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
309
466
  [ 14. -8. 0. 0. 116. 0.]
310
467
  [ 0. 0. 0. 0. 0. 12.]]
311
468
  Phase: TiNi
312
- Symmetry: monoclinic
313
469
  """
314
- matrix = cls._matrixFromCrystalSymmetry(point_group=point_group, diad=diad, symmetry=symmetry, prefix=prefix,
315
- **kwargs)
316
- return cls(matrix, phase_name=phase_name, symmetry=symmetry)
470
+ warn('This function will be removed in a future release. Use {}.{}() instead'.format(cls.__name__,symmetry), DeprecationWarning, stacklevel=2)
471
+ return cls._fromCrystalSymmetry(symmetry=symmetry, point_group=point_group, diad=diad, phase_name=phase_name,
472
+ prefix=prefix, **kwargs)
473
+
474
+ @classmethod
475
+ def _fromCrystalSymmetry(cls, symmetry, phase_name, **kwargs):
476
+ matrix = cls._matrixFromCrystalSymmetry(symmetry=symmetry, **kwargs)
477
+ return cls(matrix, phase_name=phase_name)
317
478
 
318
479
 
319
480
  @classmethod
@@ -337,7 +498,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
337
498
  cubic : create a tensor from cubic symmetry
338
499
  tetragonal : create a tensor from tetragonal symmetry
339
500
  """
340
- return cls.fromCrystalSymmetry(symmetry='hexagonal', C11=C11, C12=C12, C13=C13, C33=C33, C44=C44,
501
+ return cls._fromCrystalSymmetry(symmetry='hexagonal', C11=C11, C12=C12, C13=C13, C33=C33, C44=C44,
341
502
  phase_name=phase_name, prefix='C')
342
503
 
343
504
  @classmethod
@@ -362,8 +523,9 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
362
523
  tetragonal : create a tensor from tetragonal symmetry
363
524
  orthorhombic : create a tensor from orthorhombic symmetry
364
525
  """
365
- return cls.fromCrystalSymmetry(point_group='3', C11=C11, C12=C12, C13=C13, C14=C14, C15=C15,
366
- C33=C33, C44=C44, phase_name=phase_name, prefix='C')
526
+ return cls._fromCrystalSymmetry(symmetry='trigonal', point_group='3',
527
+ C11=C11, C12=C12, C13=C13, C14=C14, C15=C15,
528
+ C33=C33, C44=C44, phase_name=phase_name, prefix='C')
367
529
 
368
530
  @classmethod
369
531
  def tetragonal(cls, *, C11=0., C12=0., C13=0., C33=0., C44=0., C16=0., C66=0., phase_name=None):
@@ -388,8 +550,9 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
388
550
  trigonal : create a tensor from trigonal symmetry
389
551
  orthorhombic : create a tensor from orthorhombic symmetry
390
552
  """
391
- return cls.fromCrystalSymmetry(point_group='4', C11=C11, C12=C12, C13=C13, C16=C16,
392
- C33=C33, C44=C44, C66=C66, phase_name=phase_name, prefix='C')
553
+ return cls._fromCrystalSymmetry(symmetry='tetragonal', point_group='4',
554
+ C11=C11, C12=C12, C13=C13, C16=C16,
555
+ C33=C33, C44=C44, C66=C66, phase_name=phase_name, prefix='C')
393
556
 
394
557
  @classmethod
395
558
  def cubic(cls, *, C11=0., C12=0., C44=0., phase_name=None):
@@ -411,7 +574,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
411
574
  hexagonal : create a tensor from hexagonal symmetry
412
575
  orthorhombic : create a tensor from orthorhombic symmetry
413
576
  """
414
- return cls.fromCrystalSymmetry(symmetry='cubic', C11=C11, C12=C12, C44=C44, phase_name=phase_name, prefix='C')
577
+ return cls._fromCrystalSymmetry(symmetry='cubic', C11=C11, C12=C12, C44=C44, phase_name=phase_name, prefix='C')
415
578
 
416
579
  @classmethod
417
580
  def orthorhombic(cls, *, C11=0., C12=0., C13=0., C22=0., C23=0., C33=0., C44=0., C55=0., C66=0., phase_name=None):
@@ -434,7 +597,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
434
597
  monoclinic : create a tensor from monoclinic symmetry
435
598
  orthorhombic : create a tensor from orthorhombic symmetry
436
599
  """
437
- return cls.fromCrystalSymmetry(symmetry='orthorhombic',
600
+ return cls._fromCrystalSymmetry(symmetry='orthorhombic',
438
601
  C11=C11, C12=C12, C13=C13, C22=C22, C23=C23, C33=C33, C44=C44, C55=C55, C66=C66,
439
602
  phase_name=phase_name, prefix='C')
440
603
 
@@ -487,12 +650,12 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
487
650
  if diad_y and diad_z:
488
651
  raise KeyError('Ambiguous diad. Provide either C15, C25, C35 and C46; or C16, C26, C36 and C45')
489
652
  elif diad_y:
490
- return cls.fromCrystalSymmetry(symmetry='monoclinic', diad='y',
653
+ return cls._fromCrystalSymmetry(symmetry='monoclinic', diad='y',
491
654
  C11=C11, C12=C12, C13=C13, C22=C22, C23=C23, C33=C33, C44=C44, C55=C55,
492
655
  C66=C66,
493
656
  C15=C15, C25=C25, C35=C35, C46=C46, phase_name=phase_name, prefix='C')
494
657
  elif diad_z:
495
- return cls.fromCrystalSymmetry(symmetry='monoclinic', diad='z',
658
+ return cls._fromCrystalSymmetry(symmetry='monoclinic', diad='z',
496
659
  C11=C11, C12=C12, C13=C13, C22=C22, C23=C23, C33=C33, C44=C44, C55=C55,
497
660
  C66=C66,
498
661
  C16=C16, C26=C26, C36=C36, C45=C45, phase_name=phase_name, prefix='C')
@@ -533,12 +696,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
533
696
  [C16, C26, C36, C46, C56, C66]])
534
697
  return cls(matrix, phase_name=phase_name)
535
698
 
536
- def _single_tensor_only(self, fun_name=''):
537
- if self.ndim:
538
- err_msg = fun_name + ' is not suitable for tensor array. Consider subscripting (e.g. C[0].{}).'.format(fun_name)
539
- raise ValueError(err_msg)
540
-
541
- @property
699
+ @_elementwise_property
542
700
  def Young_modulus(self):
543
701
  """
544
702
  Directional Young's modulus
@@ -548,18 +706,17 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
548
706
  SphericalFunction
549
707
  Young's modulus
550
708
  """
551
- self._single_tensor_only('Young_modulus')
552
709
  if isinstance(self, ComplianceTensor):
553
710
  S = self
554
711
  else:
555
712
  S = self.inv()
556
713
  def compute_young_modulus(u):
557
- 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)
558
715
  return 1 / a
559
716
 
560
717
  return SphericalFunction(compute_young_modulus)
561
718
 
562
- @property
719
+ @_elementwise_property
563
720
  def shear_modulus(self):
564
721
  """
565
722
  Directional shear modulus
@@ -569,18 +726,17 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
569
726
  HyperSphericalFunction
570
727
  Shear modulus
571
728
  """
572
- self._single_tensor_only('shear_modulus')
573
729
  if isinstance(self, ComplianceTensor):
574
730
  S = self
575
731
  else:
576
732
  S = self.inv()
577
733
  def compute_shear_modulus(u, v):
578
- 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)
579
735
  return G
580
736
 
581
737
  return HyperSphericalFunction(compute_shear_modulus)
582
738
 
583
- @property
739
+ @_elementwise_property
584
740
  def Poisson_ratio(self):
585
741
  """
586
742
  Directional Poisson's ratio
@@ -601,11 +757,10 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
601
757
 
602
758
  where :math:`\\varepsilon_{jj}` denotes the (compressive) longitudinal strain along the j-th direction.
603
759
  """
604
- self._single_tensor_only('Poisson_ratio')
605
760
  if isinstance(self, ComplianceTensor):
606
- Sfull = self.full_tensor()
761
+ Sfull = self.full_tensor
607
762
  else:
608
- Sfull = self.inv().full_tensor()
763
+ Sfull = self.inv().full_tensor
609
764
  def compute_PoissonRatio(u, v):
610
765
  numer = np.einsum('ijkl,...i,...j,...k,...l->...',Sfull,v,v,u,u)
611
766
  denom = np.einsum('ijkl,...i,...j,...k,...l->...',Sfull,u,u,u,u)
@@ -613,7 +768,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
613
768
 
614
769
  return HyperSphericalFunction(compute_PoissonRatio)
615
770
 
616
- @property
771
+ @_elementwise_property
617
772
  def linear_compressibility(self):
618
773
  """
619
774
  Compute the directional linear compressibility.
@@ -627,13 +782,12 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
627
782
  --------
628
783
  bulk_modulus : bulk modulus of the material
629
784
  """
630
- self._single_tensor_only('linear_compressibility')
631
785
  if isinstance(self, ComplianceTensor):
632
786
  S = self
633
787
  else:
634
788
  S = self.inv()
635
789
  def compute_linear_compressibility(u):
636
- return np.einsum('ijkk,...i,...j->...',S.full_tensor(),u,u)
790
+ return np.einsum('ijkk,...i,...j->...',S.full_tensor,u,u)
637
791
 
638
792
  return SphericalFunction(compute_linear_compressibility)
639
793
 
@@ -653,12 +807,55 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
653
807
  """
654
808
  return self.inv().bulk_modulus
655
809
 
810
+ @_elementwise_property
811
+ def lame1(self):
812
+ """
813
+ Compute the first Lamé's parameter (only for isotropic materials).
814
+
815
+ If the stiffness/compliance tensor is not isotropic, NaN is returned.
816
+
817
+ Returns
818
+ -------
819
+ float
820
+ First Lamé's parameter
821
+
822
+ See Also
823
+ --------
824
+ lame2 : second Lamé's parameter
825
+ """
826
+ if self.is_isotropic():
827
+ C11 = (self.C11 + self.C22 + self.C33) / 3
828
+ return C11 - 2 * self.lame2
829
+ else:
830
+ return np.nan
831
+
832
+ @_elementwise_property
833
+ def lame2(self):
834
+ """
835
+ Compute the second Lamé's parameter (only for isotropic materials).
836
+
837
+ If the stiffness/compliance tensor is not isotropic, NaN is returned.
838
+
839
+ Returns
840
+ -------
841
+ float
842
+ Second Lamé's parameter
843
+
844
+ See Also
845
+ --------
846
+ lame1 : first Lamé's parameter
847
+ """
848
+ if self.is_isotropic():
849
+ return (self.C44 + self.C55 + self.C66) / 3
850
+ else:
851
+ return np.nan
852
+
656
853
  def Voigt_average(self, axis=None):
657
854
  """
658
- Compute the Voigt average of the stiffness tensor.
855
+ Compute the Voigt average (from the mean of stiffness tensors).
659
856
 
660
- If the tensor is a tensor array, all its values are considered. Otherwise (i.e. if single), the corresponding
661
- 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.
662
859
 
663
860
  Parameters
664
861
  ----------
@@ -675,24 +872,67 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
675
872
  Reuss_average : compute the Reuss average
676
873
  Hill_average : compute the Voigt-Reuss-Hill average
677
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]]
678
924
  """
679
925
  if self.ndim:
680
926
  return self.mean(axis=axis)
681
927
  else:
682
- c = self._matrix
683
- A = c[0, 0] + c[1, 1] + c[2, 2]
684
- B = c[0, 1] + c[0, 2] + c[1, 2]
685
- C = c[3, 3] + c[4, 4] + c[5, 5]
686
- C11 = 1 / 5 * A + 2 / 15 * B + 4 / 15 * C
687
- C12 = 1 / 15 * A + 4 / 15 * B - 2 / 15 * C
688
- C44 = (A - B) / 15 + C / 5
689
- mat = _isotropic_matrix(C11, C12, C44)
690
- return StiffnessTensor(mat, symmetry='isotropic', phase_name=self.phase_name)
928
+ return self.infinite_random_average()
691
929
 
692
930
  def Reuss_average(self, axis=None):
693
931
  """
694
- Compute the Reuss average of the stiffness tensor. If the tensor contains no orientation, we assume isotropic
695
- 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.
696
936
 
697
937
  Parameters
698
938
  ----------
@@ -709,13 +949,70 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
709
949
  Voigt_average : compute the Voigt average
710
950
  Hill_average : compute the Voigt-Reuss-Hill average
711
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]]
712
1007
  """
713
1008
  return self.inv().Reuss_average(axis=axis).inv()
714
1009
 
715
1010
  def Hill_average(self, axis=None):
716
1011
  """
717
- Compute the (Voigt-Reuss-)Hill average of the stiffness tensor. If the tensor contains no orientation, we assume
718
- 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.
719
1016
 
720
1017
  Parameters
721
1018
  ----------
@@ -732,6 +1029,61 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
732
1029
  Voigt_average : compute the Voigt average
733
1030
  Reuss_average : compute the Reuss average
734
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]]
735
1087
  """
736
1088
  Reuss = self.Reuss_average(axis=axis)
737
1089
  Voigt = self.Voigt_average(axis=axis)
@@ -768,21 +1120,28 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
768
1120
  raise NotImplementedError('Only Voigt, Reus, and Hill are implemented.')
769
1121
 
770
1122
  @classmethod
771
- def isotropic(cls, E=None, nu=None, lame1=None, lame2=None, phase_name=None):
1123
+ def isotropic(cls, E=None, nu=None, G=None, lame1=None, lame2=None, K=None, phase_name=None):
772
1124
  """
773
- Create an isotropic stiffness tensor from two elasticity coefficients, namely: E, nu, lame1, or lame2. Exactly
774
- two of these coefficients must be provided.
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).
775
1130
 
776
1131
  Parameters
777
1132
  ----------
778
- E : float, None
1133
+ E : float or list, None
779
1134
  Young modulus
780
- nu : float, None
1135
+ nu : float or list, None
781
1136
  Poisson ratio
782
- lame1 : float, None
1137
+ G : float or list, None
1138
+ Shear modulus
1139
+ lame1 : float or list, None
783
1140
  First Lamé coefficient
784
- lame2 : float, None
785
- Second Lamé coefficient
1141
+ lame2 : float or list, None
1142
+ Second Lamé coefficient (alias for G)
1143
+ K : float or list, None
1144
+ Bulk modulus
786
1145
  phase_name : str, None
787
1146
  Name to print
788
1147
 
@@ -808,34 +1167,33 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
808
1167
  >>> C=StiffnessTensor.isotropic(E=210e3, nu=0.28)
809
1168
  >>> C.shear_modulus
810
1169
  Hyperspherical function
811
- Min=82031.24999999997, Max=82031.25000000006
812
- """
813
- argument_vector = np.array([E, nu, lame1, lame2])
814
- if np.count_nonzero(argument_vector) != 2:
815
- raise ValueError("Exactly two values are required among E, nu, lame1 and lame2.")
816
- if E is not None:
817
- if nu is not None:
818
- lame1 = E * nu / ((1 + nu) * (1 - 2 * nu))
819
- lame2 = E / (1 + nu) / 2
820
- elif lame1 is not None:
821
- R = np.sqrt(E ** 2 + 9 * lame1 ** 2 + 2 * E * lame1)
822
- lame2 = (E - 3 * lame1 + R) / 4
823
- elif lame2 is not None:
824
- lame1 = lame2 * (E - 2 * lame2) / (3 * lame2 - E)
825
- else:
826
- raise ValueError('Either nu, lame1 or lame2 must be provided.')
827
- elif nu is not None:
828
- if lame1 is not None:
829
- lame2 = lame1 * (1 - 2 * nu) / (2 * nu)
830
- elif lame2 is not None:
831
- lame1 = 2 * lame2 * nu / (1 - 2 * nu)
832
- else:
833
- raise ValueError('Either lame1 or lame2 must be provided.')
834
- C11 = lame1 + 2 * lame2
835
- C12 = lame1
836
- C44 = lame2
837
- matrix = _isotropic_matrix(C11, C12, C44)
838
- return StiffnessTensor(np.array(matrix), symmetry='isotropic', phase_name=phase_name)
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
1195
+ """
1196
+ return ComplianceTensor.isotropic(E=E, nu=nu, G=G, lame1=lame1, lame2=lame2, K=K, phase_name=phase_name).inv()
839
1197
 
840
1198
  @classmethod
841
1199
  def orthotropic(cls, *, Ex, Ey, Ez, Gxy, Gxz, Gyz,
@@ -894,17 +1252,9 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
894
1252
  \\frac{\\nu_{xy}}{E_x} = \\frac{\\nu_{yx}}{E_y}
895
1253
 
896
1254
  """
897
- nu_yx = _switch_poisson_ratios(nu_xy, nu_yx, Ex, Ey,'xy')
898
- nu_zx = _switch_poisson_ratios(nu_xz, nu_zx, Ex, Ez,'xz')
899
- nu_zy = _switch_poisson_ratios(nu_yz, nu_zy, Ey, Ez,'yz')
900
- tri_sup = np.array([[1 / Ex, -nu_yx / Ey, -nu_zx / Ez, 0, 0, 0],
901
- [0, 1 / Ey, -nu_zy / Ez, 0, 0, 0],
902
- [0, 0, 1 / Ez, 0, 0, 0],
903
- [0, 0, 0, 1 / Gyz, 0, 0],
904
- [0, 0, 0, 0, 1 / Gxz, 0],
905
- [0, 0, 0, 0, 0, 1 / Gxy]])
906
- S = tri_sup + np.tril(tri_sup.T, -1)
907
- return StiffnessTensor(np.linalg.inv(S), symmetry='orthotropic', **kwargs)
1255
+ return ComplianceTensor.orthotropic(Ex=Ex, Ey=Ey, Ez=Ez, Gxy=Gxy, Gxz=Gxz, Gyz=Gyz,
1256
+ nu_yx=nu_yx, nu_zx=nu_zx, nu_zy=nu_zy,
1257
+ nu_xy=nu_xy, nu_xz=nu_xz, nu_yz=nu_yz).inv()
908
1258
 
909
1259
  @classmethod
910
1260
  def transverse_isotropic(cls, *, Ex, Ez, Gxz, nu_yx=None, nu_xy=None, nu_zx=None, nu_xz=None, **kwargs):
@@ -973,8 +1323,8 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
973
1323
 
974
1324
  Returns
975
1325
  -------
976
- Gamma : np.ndarray
977
- 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
978
1328
  direction u[i].
979
1329
 
980
1330
  See Also
@@ -983,14 +1333,24 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
983
1333
 
984
1334
  Notes
985
1335
  -----
986
- 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]_ :
987
1337
 
988
1338
  .. math:: M_{ij} = C_{iklj}.u_k.u_l
989
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.]]
990
1349
  """
991
1350
  u_vec = np.atleast_2d(u)
992
1351
  u_vec = (u_vec.T / np.linalg.norm(u_vec, axis=1)).T
993
- 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))
994
1354
 
995
1355
  def wave_velocity(self, rho):
996
1356
  """
@@ -1016,7 +1376,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1016
1376
 
1017
1377
  Notes
1018
1378
  -----
1019
- 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]_.
1020
1380
 
1021
1381
  One should double-check the units. The table below provides hints about the unit you get, depending on the units
1022
1382
  you use for stiffness and the mass density:
@@ -1037,22 +1397,51 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1037
1397
 
1038
1398
  References
1039
1399
  ----------
1040
- .. [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
1041
1401
  Communications (207), 2016, https://doi.org/10.1016/j.cpc.2016.06.014.
1042
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
1043
1437
  """
1044
- 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()).')
1045
1440
  def make_fun(index):
1046
1441
  def fun(n):
1047
1442
  Gamma = self.Christoffel_tensor(n)
1048
- eig, _ = np.linalg.eig(Gamma)
1049
- if index == 0:
1050
- eig_of_interest = np.max(eig, axis=-1)
1051
- elif index == 1:
1052
- eig_of_interest = np.median(eig, axis=-1)
1053
- else:
1054
- eig_of_interest = np.min(eig, axis=-1)
1055
- return np.sqrt(eig_of_interest / rho)
1443
+ eig = Gamma.eigvals()
1444
+ return np.sqrt(eig[...,index] / rho)
1056
1445
 
1057
1446
  return fun
1058
1447
 
@@ -1093,9 +1482,8 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1093
1482
  key = str(material.material_id)
1094
1483
  if material.elastic_tensor is not None:
1095
1484
  matrix = material.elastic_tensor.ieee_format
1096
- symmetry = material.symmetry.crystal_system.value
1097
1485
  phase_name = material.formula_pretty
1098
- C = StiffnessTensor(np.asarray(matrix), symmetry=str(symmetry), phase_name=phase_name)
1486
+ C = StiffnessTensor(np.asarray(matrix), phase_name=phase_name)
1099
1487
  else:
1100
1488
  C = None
1101
1489
  Cdict[key] = C
@@ -1130,7 +1518,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1130
1518
  if np.all([isinstance(a, ComplianceTensor) for a in Cs]):
1131
1519
  Cs = [C.inv() for C in Cs]
1132
1520
  if np.all([isinstance(a, StiffnessTensor) for a in Cs]):
1133
- C_stack = np.array([C._matrix for C in Cs])
1521
+ C_stack = np.array([C.matrix() for C in Cs])
1134
1522
  method = method.capitalize()
1135
1523
  if method == 'Voigt':
1136
1524
  C_avg = np.average(C_stack, weights=volume_fractions, axis=0)
@@ -1174,13 +1562,16 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1174
1562
  .. [3] S. I. Ranganathan and M. Ostoja-Starzewski, Universal Elastic Anisotropy Index,
1175
1563
  *Phys. Rev. Lett.*, 101(5), 055504, 2008. https://doi.org/10.1103/PhysRevLett.101.055504
1176
1564
  """
1177
- self._single_tensor_only('universal_anisotropy')
1178
- Cvoigt = self.Voigt_average()
1179
- Creuss = self.Reuss_average()
1180
- Gv = Cvoigt._matrix[3, 3]
1181
- Gr = Creuss._matrix[3, 3]
1182
- Kv = Cvoigt.bulk_modulus
1183
- 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
1184
1575
  return 5 * Gv / Gr + Kv / Kr - 6
1185
1576
 
1186
1577
  def Zener_ratio(self, tol=1e-4):
@@ -1245,7 +1636,6 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1245
1636
  0. ]
1246
1637
  [ 25.98076211 -25.98076211 0. 0. 0.
1247
1638
  65. ]]
1248
- Symmetry: cubic
1249
1639
 
1250
1640
  Still, we have
1251
1641
  >>> C_rot.Zener_ratio()
@@ -1274,7 +1664,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1274
1664
  from pymatgen.analysis.elasticity import elastic as matgenElast
1275
1665
  except ImportError:
1276
1666
  raise ModuleNotFoundError('pymatgen module is required for this function.')
1277
- return matgenElast.ElasticTensor(self.full_tensor())
1667
+ return matgenElast.ElasticTensor(self.full_tensor)
1278
1668
 
1279
1669
  def to_Kelvin(self):
1280
1670
  """
@@ -1292,7 +1682,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1292
1682
 
1293
1683
  Notes
1294
1684
  -----
1295
- This mapping convention is defined as follows [4]_:
1685
+ This mapping convention is defined as follows [Helbig]_:
1296
1686
 
1297
1687
  .. math::
1298
1688
 
@@ -1308,11 +1698,32 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1308
1698
 
1309
1699
  References
1310
1700
  ----------
1311
- .. [4] Helbig, K. (2013). What Kelvin might have written about Elasticity. Geophysical Prospecting, 61(1), 1-20.
1312
- doi: 10.1111/j.1365-2478.2011.01049.x
1313
- """
1314
- kelvin_mapping = KelvinMapping()
1315
- 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
1316
1727
 
1317
1728
  def eig(self):
1318
1729
  """
@@ -1335,9 +1746,9 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1335
1746
 
1336
1747
  Notes
1337
1748
  -----
1338
- The definition for eigenstiffnesses and the eigenstrains are introduced in [4]_.
1749
+ The definition for eigenstiffnesses and the eigenstrains are introduced in [Helbig]_.
1339
1750
  """
1340
- return np.linalg.eigh(self.to_Kelvin())
1751
+ return np.linalg.eigh(self._matrix)
1341
1752
 
1342
1753
  @property
1343
1754
  def eig_stiffnesses(self):
@@ -1354,8 +1765,22 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1354
1765
  eig : returns the eigenstiffnesses and the eigenstrains
1355
1766
  eig_strains : returns the eigenstrains only
1356
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]_.
1357
1782
  """
1358
- return np.linalg.eigvalsh(self.to_Kelvin())
1783
+ return np.linalg.eigvalsh(self._matrix)
1359
1784
 
1360
1785
  @property
1361
1786
  def eig_strains(self):
@@ -1409,10 +1834,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1409
1834
  --------
1410
1835
  to_Kelvin : return the components as a (6,6) matrix following the Kelvin convention
1411
1836
  """
1412
- kelvin_mapping = KelvinMapping()
1413
- t = cls(matrix / kelvin_mapping.matrix, **kwargs)
1414
- t._matrix *= t.mapping.matrix
1415
- return t
1837
+ return cls(matrix, mapping=kelvin_mapping, **kwargs)
1416
1838
 
1417
1839
  def eig_stiffnesses_multiplicity(self, tol=1e-4):
1418
1840
  """
@@ -1534,9 +1956,9 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1534
1956
  8.1901353 ]
1535
1957
  [-20.34233446 0.99714278 19.34519167 -6.52548033 8.1901353
1536
1958
  39.41409344]]
1537
- Symmetry: cubic
1538
1959
 
1539
- 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
+
1540
1962
  >>> C_rotated.is_cubic()
1541
1963
  True
1542
1964
 
@@ -1572,12 +1994,127 @@ class ComplianceTensor(StiffnessTensor):
1572
1994
  """
1573
1995
  Class for manipulating compliance tensors
1574
1996
  """
1575
- tensor_name = 'Compliance'
1576
- C11_C12_factor = 2.0
1577
- component_prefix = 'S'
1578
- C46_C56_factor = 2.0
1997
+ _tensor_name = 'Compliance'
1998
+ _C11_C12_factor = 2.0
1999
+ _component_prefix = 'S'
2000
+ _C46_C56_factor = 2.0
1579
2001
 
1580
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
+ """
1581
2118
  super().__init__(C, check_positive_definite=check_positive_definite, mapping=mapping, **kwargs)
1582
2119
  self.mapping_name = 'Voigt'
1583
2120
 
@@ -1600,21 +2137,15 @@ class ComplianceTensor(StiffnessTensor):
1600
2137
  Reciprocal tensor
1601
2138
  """
1602
2139
  S = np.linalg.inv(self._matrix)
1603
- 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
1604
2143
 
1605
2144
  def Reuss_average(self, axis=None):
1606
2145
  if self.ndim:
1607
2146
  return self.mean(axis=axis)
1608
2147
  else:
1609
- s = self._matrix
1610
- A = s[0, 0] + s[1, 1] + s[2, 2]
1611
- B = s[0, 1] + s[0, 2] + s[1, 2]
1612
- C = s[3, 3] + s[4, 4] + s[5, 5]
1613
- S11 = 1 / 5 * A + 2 / 15 * B + 1 / 15 * C
1614
- S12 = 1 / 15 * A + 4 / 15 * B - 1 / 30 * C
1615
- S44 = 4 / 15 * (A - B) + 1 / 5 * C
1616
- mat = _isotropic_matrix(S11, S12, S44)
1617
- return ComplianceTensor(mat, symmetry='isotropic', phase_name=self.phase_name)
2148
+ return self.infinite_random_average()
1618
2149
 
1619
2150
  def Voigt_average(self, axis=None):
1620
2151
  return self.inv().Voigt_average(axis=axis).inv()
@@ -1623,12 +2154,82 @@ class ComplianceTensor(StiffnessTensor):
1623
2154
  return self.inv().Hill_average(axis=axis).inv()
1624
2155
 
1625
2156
  @classmethod
1626
- def isotropic(cls, E=None, nu=None, lame1=None, lame2=None, phase_name=None):
1627
- return super().isotropic(E=E, nu=nu, lame1=lame1, lame2=lame2, phase_name=None).inv()
2157
+ def isotropic(cls, E=None, nu=None, G=None, lame1=None, lame2=None, K=None, phase_name=None):
2158
+ if lame2 is not None:
2159
+ if G is None:
2160
+ G = lame2
2161
+ else:
2162
+ raise ValueError('G and lame2 cannot be provided together.')
2163
+ n_specified = sum(v is not None for v in [E, nu, lame1, G, K])
2164
+ if n_specified != 2:
2165
+ raise ValueError("Exactly two values are required among E, nu, G, K, lame1 and lame2.")
2166
+ if K is not None:
2167
+ K = np.asarray(K)
2168
+ if E is not None:
2169
+ E = np.asarray(E, dtype=float)
2170
+ G = 3 * K * E / (9 * K - E)
2171
+ nu = (3 * K - E) / 6 / K
2172
+ elif lame1 is not None:
2173
+ lame1 = np.asarray(lame1, dtype=float)
2174
+ E = 9 * K * (K - lame1) / (3 * K -lame1)
2175
+ G= 3 * (K - lame1) / 2
2176
+ nu = lame1 / (3*K-lame1)
2177
+ elif G is not None:
2178
+ G = np.asarray(G, dtype=float)
2179
+ E = 9 * K * G / (3 * K + G)
2180
+ nu = (3 * K - 2 * G) / 2 / (3 * K + G)
2181
+ elif nu is not None:
2182
+ nu = np.asarray(nu, dtype=float)
2183
+ E = 3 * K * (1 - 2 * nu)
2184
+ G = E / 2 / (1 + nu)
2185
+ elif E is not None:
2186
+ E = np.asarray(E, dtype=float)
2187
+ if lame1 is not None:
2188
+ lame1 = np.asarray(lame1, dtype=float)
2189
+ R = np.sqrt(E**2 + 9*lame1**2 + 2*E*lame1)
2190
+ G = (E - 3 * lame1 + R) / 4
2191
+ nu = 2 * lame1 / (E + lame1 + R)
2192
+ elif G is not None:
2193
+ G = np.asarray(G, dtype=float)
2194
+ nu = E / 2 / G - 1
2195
+ elif nu is not None:
2196
+ nu = np.asarray(nu, dtype=float)
2197
+ G = E / 2 / (1 + nu)
2198
+ elif lame1 is not None:
2199
+ lame1 = np.asarray(lame1, dtype=float)
2200
+ if G is not None:
2201
+ G = np.asarray(G, dtype=float)
2202
+ E = G * (3 * lame1 + 2 * G) / (lame1 + G)
2203
+ nu = lame1 / 2 / (lame1 + G)
2204
+ elif nu is not None:
2205
+ nu = np.asarray(nu, dtype=float)
2206
+ E = lame1 * ( 1 + nu) * (1 - 2 * nu) / nu
2207
+ G = lame1 * (1 - 2 * nu) / 2 / nu
2208
+ elif (nu is not None) and (G is not None):
2209
+ nu = np.asarray(nu, dtype=float)
2210
+ G = np.asarray(G)
2211
+ E = 2 * G * (1 + nu)
2212
+ S11 = 1/E
2213
+ S12 = -nu/E
2214
+ S44 = 1 / G
2215
+ S_mat = _isotropic_matrix(S11, S12, S44)
2216
+ return ComplianceTensor(S_mat, phase_name=phase_name)
1628
2217
 
1629
2218
  @classmethod
1630
- def orthotropic(cls, *args, **kwargs):
1631
- return super().orthotropic(*args, **kwargs).inv()
2219
+ def orthotropic(cls, *, Ex, Ey, Ez, Gxy, Gxz, Gyz,
2220
+ nu_yx=None, nu_zx=None, nu_zy=None,
2221
+ nu_xy=None, nu_xz=None, nu_yz=None, **kwargs):
2222
+ nu_yx = _switch_poisson_ratios(nu_xy, nu_yx, Ex, Ey,'xy')
2223
+ nu_zx = _switch_poisson_ratios(nu_xz, nu_zx, Ex, Ez,'xz')
2224
+ nu_zy = _switch_poisson_ratios(nu_yz, nu_zy, Ey, Ez,'yz')
2225
+ tri_sup = np.array([[1 / Ex, -nu_yx / Ey, -nu_zx / Ez, 0, 0, 0],
2226
+ [0, 1 / Ey, -nu_zy / Ez, 0, 0, 0],
2227
+ [0, 0, 1 / Ez, 0, 0, 0],
2228
+ [0, 0, 0, 1 / Gyz, 0, 0],
2229
+ [0, 0, 0, 0, 1 / Gxz, 0],
2230
+ [0, 0, 0, 0, 0, 1 / Gxy]])
2231
+ S = tri_sup + np.tril(tri_sup.T, -1)
2232
+ return ComplianceTensor(S, **kwargs)
1632
2233
 
1633
2234
  @classmethod
1634
2235
  def transverse_isotropic(cls, *args, **kwargs):
@@ -1671,7 +2272,7 @@ class ComplianceTensor(StiffnessTensor):
1671
2272
  from pymatgen.analysis.elasticity import elastic as matgenElast
1672
2273
  except ImportError:
1673
2274
  raise ModuleNotFoundError('pymatgen module is required for this function.')
1674
- return matgenElast.ComplianceTensor(self.full_tensor())
2275
+ return matgenElast.ComplianceTensor(self.full_tensor)
1675
2276
 
1676
2277
  def eig(self):
1677
2278
  """
@@ -1694,7 +2295,7 @@ class ComplianceTensor(StiffnessTensor):
1694
2295
 
1695
2296
  Notes
1696
2297
  -----
1697
- The definition for eigencompliances and the eigenstresses are introduced in [4]_.
2298
+ The definition for eigencompliances and the eigenstresses are introduced in [Helbig]_.
1698
2299
  """
1699
2300
  return np.linalg.eigh(self.to_Kelvin())
1700
2301
 
@@ -1745,4 +2346,232 @@ class ComplianceTensor(StiffnessTensor):
1745
2346
  --------
1746
2347
  eig_compliances : compute the eigencompliances from the Kelvin's matrix of compliance
1747
2348
  """
1748
- return 1/self.eig_compliances
2349
+ return 1/self.eig_compliances
2350
+
2351
+ @property
2352
+ def lame1(self):
2353
+ return self.inv().lame1
2354
+
2355
+ @property
2356
+ def lame2(self):
2357
+ return self.inv().lame2
2358
+
2359
+
2360
+ @classmethod
2361
+ def hexagonal(cls, *, S11=0., S12=0., S13=0., S33=0., S44=0., phase_name=None):
2362
+ """
2363
+ Create a fourth-order tensor from hexagonal symmetry.
2364
+
2365
+ Parameters
2366
+ ----------
2367
+ S11, S12 , S13, S33, S44 : float
2368
+ Components of the tensor, using the Voigt notation
2369
+ phase_name : str, optional
2370
+ Phase name to display
2371
+ Returns
2372
+ -------
2373
+ FourthOrderTensor
2374
+
2375
+ See Also
2376
+ --------
2377
+ transverse_isotropic : creates a transverse-isotropic tensor from engineering parameters
2378
+ cubic : create a tensor from cubic symmetry
2379
+ tetragonal : create a tensor from tetragonal symmetry
2380
+ """
2381
+ return cls._fromCrystalSymmetry(symmetry='hexagonal', S11=S11, S12=S12, S13=S13, S33=S33, S44=S44,
2382
+ phase_name=phase_name, prefix='S')
2383
+
2384
+ @classmethod
2385
+ def trigonal(cls, *, S11=0., S12=0., S13=0., S14=0., S33=0., S44=0., S15=0., phase_name=None):
2386
+ """
2387
+ Create a fourth-order tensor from trigonal symmetry.
2388
+
2389
+ Parameters
2390
+ ----------
2391
+ S11, S12, S13, S14, S33, S44 : float
2392
+ Components of the tensor, using the Voigt notation
2393
+ S15 : float, optional
2394
+ S15 component of the tensor, only used for point groups 3 and -3.
2395
+ phase_name : str, optional
2396
+ Phase name to display
2397
+ Returns
2398
+ -------
2399
+ FourthOrderTensor
2400
+
2401
+ See Also
2402
+ --------
2403
+ tetragonal : create a tensor from tetragonal symmetry
2404
+ orthorhombic : create a tensor from orthorhombic symmetry
2405
+ """
2406
+ return cls._fromCrystalSymmetry(symmetry='trigonal', point_group='3',
2407
+ S11=S11, S12=S12, S13=S13, S14=S14, S15=S15,
2408
+ S33=S33, S44=S44, phase_name=phase_name, prefix='S')
2409
+
2410
+ @classmethod
2411
+ def tetragonal(cls, *, S11=0., S12=0., S13=0., S33=0., S44=0., S16=0., S66=0., phase_name=None):
2412
+ """
2413
+ Create a fourth-order tensor from tetragonal symmetry.
2414
+
2415
+ Parameters
2416
+ ----------
2417
+ S11, S12, S13, S33, S44, S66 : float
2418
+ Components of the tensor, using the Voigt notation
2419
+ S16 : float, optional
2420
+ S16 component in Voigt notation (for point groups 4, -4 and 4/m only)
2421
+ phase_name : str, optional
2422
+ Phase name to display
2423
+
2424
+ Returns
2425
+ -------
2426
+ FourthOrderTensor
2427
+
2428
+ See Also
2429
+ --------
2430
+ trigonal : create a tensor from trigonal symmetry
2431
+ orthorhombic : create a tensor from orthorhombic symmetry
2432
+ """
2433
+ return cls._fromCrystalSymmetry(symmetry='tetragonal', point_group='4',
2434
+ S11=S11, S12=S12, S13=S13, S16=S16,
2435
+ S33=S33, S44=S44, S66=S66, phase_name=phase_name, prefix='S')
2436
+
2437
+ @classmethod
2438
+ def cubic(cls, *, S11=0., S12=0., S44=0., phase_name=None):
2439
+ """
2440
+ Create a fourth-order tensor from cubic symmetry.
2441
+
2442
+ Parameters
2443
+ ----------
2444
+ S11 , S12, S44 : float
2445
+ phase_name : str, optional
2446
+ Phase name to display
2447
+
2448
+ Returns
2449
+ -------
2450
+ StiffnessTensor
2451
+
2452
+ See Also
2453
+ --------
2454
+ hexagonal : create a tensor from hexagonal symmetry
2455
+ orthorhombic : create a tensor from orthorhombic symmetry
2456
+ """
2457
+ return cls._fromCrystalSymmetry(symmetry='cubic', S11=S11, S12=S12, S44=S44, phase_name=phase_name, prefix='S')
2458
+
2459
+ @classmethod
2460
+ def orthorhombic(cls, *, S11=0., S12=0., S13=0., S22=0., S23=0., S33=0., S44=0., S55=0., S66=0., phase_name=None):
2461
+ """
2462
+ Create a fourth-order tensor from orthorhombic symmetry.
2463
+
2464
+ Parameters
2465
+ ----------
2466
+ S11, S12, S13, S22, S23, S33, S44, S55, S66 : float
2467
+ Components of the tensor, using the Voigt notation
2468
+ phase_name : str, optional
2469
+ Phase name to display
2470
+
2471
+ Returns
2472
+ -------
2473
+ FourthOrderTensor
2474
+
2475
+ See Also
2476
+ --------
2477
+ monoclinic : create a tensor from monoclinic symmetry
2478
+ orthorhombic : create a tensor from orthorhombic symmetry
2479
+ """
2480
+ return cls._fromCrystalSymmetry(symmetry='orthorhombic',
2481
+ S11=S11, S12=S12, S13=S13, S22=S22, S23=S23, S33=S33, S44=S44, S55=S55, S66=S66,
2482
+ phase_name=phase_name, prefix='S')
2483
+
2484
+ @classmethod
2485
+ def monoclinic(cls, *, S11=0., S12=0., S13=0., S22=0., S23=0., S33=0., S44=0., S55=0., S66=0.,
2486
+ S15=None, S25=None, S35=None, S46=None,
2487
+ S16=None, S26=None, S36=None, S45=None,
2488
+ phase_name=None):
2489
+ """
2490
+ Create a fourth-order tensor from monoclinic symmetry. It automatically detects whether the components are given
2491
+ according to the Y or Z diad, depending on the input arguments.
2492
+
2493
+ For Diad || y, S15, S25, S35 and S46 must be provided.
2494
+ For Diad || z, S16, S26, S36 and S45 must be provided.
2495
+
2496
+ Parameters
2497
+ ----------
2498
+ S11, S12 , S13, S22, S23, S33, S44, S55, S66 : float
2499
+ Components of the tensor, using the Voigt notation
2500
+ S15 : float, optional
2501
+ S15 component of the tensor (if Diad || y)
2502
+ S25 : float, optional
2503
+ S25 component of the tensor (if Diad || y)
2504
+ S35 : float, optional
2505
+ S35 component of the tensor (if Diad || y)
2506
+ S46 : float, optional
2507
+ S46 component of the tensor (if Diad || y)
2508
+ S16 : float, optional
2509
+ S16 component of the tensor (if Diad || z)
2510
+ S26 : float, optional
2511
+ S26 component of the tensor (if Diad || z)
2512
+ S36 : float, optional
2513
+ S36 component of the tensor (if Diad || z)
2514
+ S45 : float, optional
2515
+ S45 component of the tensor (if Diad || z)
2516
+ phase_name : str, optional
2517
+ Name to display
2518
+
2519
+ Returns
2520
+ -------
2521
+ FourthOrderTensor
2522
+
2523
+ See Also
2524
+ --------
2525
+ triclinic : create a tensor from triclinic symmetry
2526
+ orthorhombic : create a tensor from orthorhombic symmetry
2527
+ """
2528
+ diad_y = not (None in (S15, S25, S35, S46))
2529
+ diad_z = not (None in (S16, S26, S36, S45))
2530
+ if diad_y and diad_z:
2531
+ raise KeyError('Ambiguous diad. Provide either S15, S25, S35 and S46; or S16, S26, S36 and S45')
2532
+ elif diad_y:
2533
+ return cls._fromCrystalSymmetry(symmetry='monoclinic', diad='y',
2534
+ S11=S11, S12=S12, S13=S13, S22=S22, S23=S23, S33=S33, S44=S44, S55=S55,
2535
+ S66=S66,
2536
+ S15=S15, S25=S25, S35=S35, S46=S46, phase_name=phase_name, prefix='S')
2537
+ elif diad_z:
2538
+ return cls._fromCrystalSymmetry(symmetry='monoclinic', diad='z',
2539
+ S11=S11, S12=S12, S13=S13, S22=S22, S23=S23, S33=S33, S44=S44, S55=S55,
2540
+ S66=S66,
2541
+ S16=S16, S26=S26, S36=S36, S45=S45, phase_name=phase_name, prefix='S')
2542
+ else:
2543
+ raise KeyError('For monoclinic symmetry, one should provide either S15, S25, S35 and S46, '
2544
+ 'or S16, S26, S36 and S45.')
2545
+
2546
+ @classmethod
2547
+ def triclinic(cls, S11=0., S12=0., S13=0., S14=0., S15=0., S16=0.,
2548
+ S22=0., S23=0., C24=0., S25=0., S26=0.,
2549
+ S33=0., C34=0., S35=0., S36=0.,
2550
+ S44=0., S45=0., S46=0.,
2551
+ S55=0., C56=0.,
2552
+ S66=0., phase_name=None):
2553
+ """
2554
+
2555
+ Parameters
2556
+ ----------
2557
+ S11 , S12 , S13 , S14 , S15 , S16 , S22 , S23 , C24 , S25 , S26 , S33 , C34 , S35 , S36 , S44 , S45 , S46 , S55 , C56 , S66 : float
2558
+ Components of the tensor
2559
+ phase_name : str, optional
2560
+ Name to display
2561
+
2562
+ Returns
2563
+ -------
2564
+ FourthOrderTensor
2565
+
2566
+ See Also
2567
+ --------
2568
+ monoclinic : create a tensor from monoclinic symmetry
2569
+ orthorhombic : create a tensor from orthorhombic symmetry
2570
+ """
2571
+ matrix = np.array([[S11, S12, S13, S14, S15, S16],
2572
+ [S12, S22, S23, C24, S25, S26],
2573
+ [S13, S23, S33, C34, S35, S36],
2574
+ [S14, C24, C34, S44, S45, S46],
2575
+ [S15, S25, S35, S45, S55, C56],
2576
+ [S16, S26, S36, S46, C56, S66]])
2577
+ return cls(matrix, phase_name=phase_name)