apsg 1.3.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.
@@ -0,0 +1,550 @@
1
+ import math
2
+ import numpy as np
3
+ from scipy import linalg as spla
4
+
5
+ from apsg.helpers._math import sind, cosd, atan2d
6
+ from apsg.math._vector import Vector2
7
+ from apsg.math._matrix import Matrix2
8
+ from apsg.decorator._decorator import ensure_arguments
9
+
10
+
11
+ class DeformationGradient2(Matrix2):
12
+ """
13
+ The class to represent 2D deformation gradient tensor.
14
+
15
+ Args:
16
+ a (2x2 array_like): Input data, that can be converted to
17
+ 2x2 2D array. This includes lists, tuples and ndarrays.
18
+
19
+ Returns:
20
+ ``DeformationGradient2`` object
21
+
22
+ Example:
23
+ >>> F = defgrad2(np.diag([2, 0.5]))
24
+ """
25
+
26
+ @classmethod
27
+ def from_comp(cls, xx=1, xy=0, yx=0, yy=1):
28
+ """Return ``DeformationGradient2`` defined by individual components.
29
+ Default is zero tensor.
30
+
31
+ Keyword Args:
32
+ xx (float): tensor component F_xx
33
+ xy (float): tensor component F_xy
34
+ yx (float): tensor component F_yx
35
+ yy (float): tensor component F_yy
36
+
37
+ Example:
38
+ >>> F = matrix2.from_comp(xy=2)
39
+ >>> F
40
+ [[0. 2.]
41
+ [0. 0.]]
42
+
43
+ """
44
+
45
+ return cls([[xx, xy], [yx, yy]])
46
+
47
+ @classmethod
48
+ def from_ratio(cls, R=1):
49
+ """Return isochoric ``DeformationGradient2`` tensor with axial stretches defined by strain ratio.
50
+ Default is identity tensor.
51
+
52
+ Keyword Args:
53
+ R (float): strain ratio
54
+
55
+ Example:
56
+ >>> F = defgrad2.from_ratio(R=4)
57
+ >> F
58
+ DeformationGradient2
59
+ [[2. 0. ]
60
+ [0. 0.5]]
61
+
62
+ """
63
+
64
+ return cls.from_comp(xx=R ** (1 / 2), yy=R ** (-1 / 2))
65
+
66
+ @classmethod
67
+ def from_angle(cls, theta):
68
+ """Return ``DeformationGradient2`` representing rotation by angle theta.
69
+
70
+ Args:
71
+ theta: Angle of rotation in degrees
72
+
73
+ Example:
74
+ >>> F = defgrad2.from_angle(45)
75
+ >>> F
76
+ DeformationGradient2
77
+ [[ 0.707 -0.707]
78
+ [ 0.707 0.707]]
79
+
80
+ """
81
+
82
+ c, s = cosd(theta), sind(theta)
83
+ return cls([[c, -s], [s, c]])
84
+
85
+ @classmethod
86
+ @ensure_arguments(Vector2, Vector2)
87
+ def from_two_vectors(cls, v1, v2):
88
+ """Return ``DeformationGradient2`` representing rotation around axis perpendicular
89
+ to both vectors and rotate v1 to v2.
90
+
91
+ Args:
92
+ v1: ``Vector2`` like object
93
+ v2: ``Vector2`` like object
94
+
95
+ Example:
96
+ >>> F = defgrad2.from_two_vectors(vec2(1, 1), vec2(0, 1))
97
+ >>> F
98
+ DeformationGradient2
99
+ [[ 0.707 -0.707]
100
+ [ 0.707 0.707]]
101
+
102
+ """
103
+ return cls.from_angle(v1.angle(v2))
104
+
105
+ @property
106
+ def R(self):
107
+ """Return rotation part of ``DeformationGradient2`` from polar decomposition."""
108
+ R, _ = spla.polar(self)
109
+ return type(self)(R)
110
+
111
+ @property
112
+ def U(self):
113
+ """Return stretching part of ``DeformationGradient2`` from right polar decomposition."""
114
+ _, U = spla.polar(self, "right")
115
+ return type(self)(U)
116
+
117
+ @property
118
+ def V(self):
119
+ """Return stretching part of ``DeformationGradient2`` from left polar decomposition."""
120
+ _, V = spla.polar(self, "left")
121
+ return type(self)(V)
122
+
123
+ def angle(self):
124
+ """Return rotation part of ``DeformationGradient2`` as angle."""
125
+ R, _ = spla.polar(self)
126
+ return atan2d(R[1, 0], R[0, 0])
127
+
128
+ def velgrad(self, time=1):
129
+ """Return ``VelocityGradient2`` for given time"""
130
+ return VelocityGradient2(spla.logm(np.asarray(self)) / time)
131
+
132
+
133
+ class VelocityGradient2(Matrix2):
134
+ """
135
+ The class to represent 2D velocity gradient tensor.
136
+
137
+ Args:
138
+ a (2x2 array_like): Input data, that can be converted to
139
+ 2x2 2D array. This includes lists, tuples and ndarrays.
140
+
141
+ Returns:
142
+ ``VelocityGradient2`` object
143
+
144
+ Example:
145
+ >>> L = velgrad2(np.diag([0.1, -0.1]))
146
+
147
+ """
148
+
149
+ @classmethod
150
+ def from_comp(cls, xx=0, xy=0, yx=0, yy=0):
151
+ """Return ``VelocityGradient2`` defined by individual components.
152
+ Default is zero tensor.
153
+
154
+ Keyword Args:
155
+ xx (float): tensor component L_xx
156
+ xy (float): tensor component L_xy
157
+ yx (float): tensor component L_yx
158
+ yy (float): tensor component L_yy
159
+
160
+ Example:
161
+ >>> L = velgrad2.from_comp(xy=2)
162
+ >>> L
163
+ [[0. 2.]
164
+ [0. 0.]]
165
+
166
+ """
167
+
168
+ return cls([[xx, xy], [yx, yy]])
169
+
170
+ def defgrad(self, time=1, steps=1):
171
+ """
172
+ Return ``DeformationGradient2`` tensor accumulated after given time.
173
+
174
+ Keyword Args:
175
+ time (float): time of deformation. Default 1
176
+ steps (int): when bigger than 1, will return a list
177
+ of ``DeformationGradient2`` tensors for each timestep.
178
+ """
179
+ if steps > 1: # FIX once container for matrix will be implemented
180
+ return [
181
+ DeformationGradient2(spla.expm(np.asarray(self) * t))
182
+ for t in np.linspace(0, time, steps)
183
+ ]
184
+ else:
185
+ return DeformationGradient2(spla.expm(np.asarray(self) * time))
186
+
187
+ def rate(self):
188
+ """
189
+ Return rate of deformation tensor
190
+ """
191
+
192
+ return type(self)((self + self.T) / 2)
193
+
194
+ def spin(self):
195
+ """
196
+ Return spin tensor
197
+ """
198
+
199
+ return type(self)((self - self.T) / 2)
200
+
201
+
202
+ class Tensor2(Matrix2):
203
+ pass
204
+
205
+
206
+ class Stress2(Tensor2):
207
+ """
208
+ The class to represent 2D stress tensor.
209
+
210
+ Args:
211
+ a (2x2 array_like): Input data, that can be converted to
212
+ 2x2 2D array. This includes lists, tuples and ndarrays.
213
+
214
+ Returns:
215
+ ``Stress2`` object
216
+
217
+ Example:
218
+ >>> S = Stress2([[-8, 0, 0],[0, -5, 0],[0, 0, -1]])
219
+
220
+ """
221
+
222
+ @classmethod
223
+ def from_comp(cls, xx=0, xy=0, yy=0):
224
+ """
225
+ Return ``Stress2`` tensor. Default is zero tensor.
226
+
227
+ Note that stress tensor must be symmetrical.
228
+
229
+ Keyword Args:
230
+ xx, xy, yy (float): tensor components
231
+
232
+ Example:
233
+ >>> S = stress2.from_comp(xx=-5, yy=-2, xy=1)
234
+ >>> S
235
+ Stress2
236
+ [[-5. 1.]
237
+ [ 1. -2.]]
238
+ """
239
+
240
+ return cls([[xx, xy], [xy, yy]])
241
+
242
+ @property
243
+ def mean_stress(self):
244
+ """
245
+ Mean stress
246
+ """
247
+
248
+ return self.I1 / 2
249
+
250
+ @property
251
+ def hydrostatic(self):
252
+ """
253
+ Mean hydrostatic stress tensor component
254
+ """
255
+
256
+ return type(self)(np.diag(self.mean_stress * np.ones(2)))
257
+
258
+ @property
259
+ def deviatoric(self):
260
+ """
261
+ A stress deviator tensor component
262
+ """
263
+
264
+ return type(self)(self - self.hydrostatic)
265
+
266
+ @property
267
+ def sigma1(self):
268
+ """
269
+ A maximum principal stress (max compressive)
270
+ """
271
+
272
+ return self.E2
273
+
274
+ @property
275
+ def sigma2(self):
276
+ """
277
+ A minimum principal stress
278
+ """
279
+
280
+ return self.E1
281
+
282
+ @property
283
+ def sigma1dir(self):
284
+ """
285
+ Return unit length vector in direction of maximum
286
+ principal stress (max compressive)
287
+ """
288
+
289
+ return self.V2
290
+
291
+ @property
292
+ def sigma2dir(self):
293
+ """
294
+ Return unit length vector in direction of minimum
295
+ principal stress
296
+ """
297
+
298
+ return self.V1
299
+
300
+ @property
301
+ def sigma1vec(self):
302
+ """
303
+ Return maximum principal stress vector (max compressive)
304
+ """
305
+
306
+ return self.E2 * self.V2
307
+
308
+ @property
309
+ def sigma2vec(self):
310
+ """
311
+ Return minimum principal stress vector
312
+ """
313
+
314
+ return self.E1 * self.V1
315
+
316
+ @property
317
+ def I1(self):
318
+ """
319
+ First invariant
320
+ """
321
+
322
+ return float(np.trace(self))
323
+
324
+ @property
325
+ def I2(self):
326
+ """
327
+ Second invariant
328
+ """
329
+
330
+ return float((self.I1**2 - np.trace(self**2)) / 2)
331
+
332
+ @property
333
+ def I3(self):
334
+ """
335
+ Third invariant
336
+ """
337
+
338
+ return self.det
339
+
340
+ @property
341
+ def diagonalized(self):
342
+ """
343
+ Returns diagonalized Stress tensor and orthogonal matrix R, which transforms actual
344
+ coordinate system to the principal one.
345
+ """
346
+ return (
347
+ type(self)(np.diag(self.eigenvalues())),
348
+ DeformationGradient2(self.eigenvectors()),
349
+ )
350
+
351
+ def cauchy(self, n):
352
+ """
353
+ Return stress vector associated with plane given by normal vector.
354
+
355
+ Args:
356
+ n: normal given as ``Vector2`` object
357
+
358
+ Example:
359
+ >>> S = Stress.from_comp(xx=-5, yy=-2, xy=1)
360
+ >>> S.cauchy(vec2(1,1))
361
+ V(-2.520, 0.812, 8.660)
362
+
363
+ """
364
+
365
+ return Vector2(np.dot(self, n.normalized()))
366
+
367
+ def stress_comp(self, n):
368
+ """
369
+ Return normal and shear stress ``Vector2`` components on plane given
370
+ by normal vector.
371
+ """
372
+
373
+ t = self.cauchy(n)
374
+ sn = t.proj(n)
375
+
376
+ return sn, t - sn
377
+
378
+ def normal_stress(self, n):
379
+ """
380
+ Return normal stress magnitude on plane given by normal vector.
381
+ """
382
+
383
+ return float(np.dot(n, self.cauchy(n)))
384
+
385
+ def shear_stress(self, n):
386
+ """
387
+ Return shear stress magnitude on plane given by normal vector.
388
+ """
389
+
390
+ sn, tau = self.stress_comp(n)
391
+ return abs(tau)
392
+
393
+ def signed_shear_stress(self, n):
394
+ """
395
+ Return signed shear stress magnitude on plane given by normal vector.
396
+ """
397
+ R = DeformationGradient2.from_angle(n.direction)
398
+ return self.transform(R)[1, 0]
399
+
400
+
401
+ class Ellipse(Tensor2):
402
+ """
403
+ The class to represent 2D ellipse
404
+
405
+ See following methods and properties for additional operations.
406
+
407
+ Args:
408
+ matrix (2x2 array_like): Input data, that can be converted to
409
+ 2x2 2D matrix. This includes lists, tuples and ndarrays.
410
+
411
+ Returns:
412
+ ``Ellipse`` object
413
+
414
+ Example:
415
+ >>> E = ellipse([[8, 0], [0, 2]])
416
+ >>> E
417
+ Ellipse
418
+ [[8. 0.]
419
+ [0. 2.]]
420
+ (ar:2, ori:0)
421
+
422
+ """
423
+
424
+ def __repr__(self) -> str:
425
+ return (
426
+ f"{Matrix2.__repr__(self)}\n(ar:{self.ar:.3g}, ori:{self.orientation:.3g})"
427
+ )
428
+
429
+ @classmethod
430
+ def from_defgrad(cls, F, form="left", **kwargs) -> "Ellipse":
431
+ """
432
+ Return deformation tensor from ``Defgrad2``.
433
+
434
+ Kwargs:
435
+ form: 'left' or 'B' for left Cauchy–Green deformation tensor or
436
+ Finger deformation tensor
437
+ 'right' or 'C' for right Cauchy–Green deformation tensor or
438
+ Green's deformation tensor.
439
+ Default is 'left'.
440
+ """
441
+ if form in ("left", "B"):
442
+ return cls(np.dot(F, np.transpose(F)), **kwargs)
443
+ elif form in ("right", "C"):
444
+ return cls(np.dot(np.transpose(F), F), **kwargs)
445
+ else:
446
+ raise TypeError("Wrong form argument")
447
+
448
+ @classmethod
449
+ def from_stretch(cls, x=1, y=1, **kwargs) -> "Ellipse":
450
+ """
451
+ Return diagonal tensor defined by magnitudes of principal stretches.
452
+ """
453
+ return cls([[x * x, 0], [0, y * y]], **kwargs)
454
+
455
+ @property
456
+ def S1(self) -> float:
457
+ """
458
+ Return the maximum principal stretch.
459
+ """
460
+ return math.sqrt(self.E1)
461
+
462
+ @property
463
+ def S2(self) -> float:
464
+ """
465
+ Return the minimum principal stretch.
466
+ """
467
+ return math.sqrt(self.E2)
468
+
469
+ @property
470
+ def e1(self) -> float:
471
+ """
472
+ Return the maximum natural principal strain.
473
+ """
474
+ return math.log(self.S1)
475
+
476
+ @property
477
+ def e2(self) -> float:
478
+ """
479
+ Return the minimum natural principal strain.
480
+ """
481
+ return math.log(self.S2)
482
+
483
+ @property
484
+ def ar(self) -> float:
485
+ """
486
+ Return the ellipse axial ratio.
487
+ """
488
+ return self.S1 / self.S2
489
+
490
+ @property
491
+ def orientation(self):
492
+ """
493
+ Return the orientation of the maximum eigenvector.
494
+ """
495
+ return self.V1.direction % 180
496
+
497
+ @property
498
+ def e12(self) -> float:
499
+ """
500
+ Return the difference between natural principal strains.
501
+ """
502
+ return self.e1 - self.e2
503
+
504
+
505
+ class OrientationTensor2(Ellipse):
506
+ """
507
+ Represents an 2D orientation tensor, which characterize data distribution
508
+ using eigenvalue method. See (Watson 1966, Scheidegger 1965).
509
+
510
+ See following methods and properties for additional operations.
511
+
512
+ Args:
513
+ matrix (2x2 array_like): Input data, that can be converted to
514
+ 2x2 2D matrix. This includes lists, tuples and ndarrays.
515
+ Array could be also ``Group`` (for backward compatibility)
516
+
517
+ Returns:
518
+ ``OrientationTensor2`` object
519
+
520
+ Example:
521
+ >>> v = vec2set.random(n=1000)
522
+ >>> ot = v.ortensor()
523
+ >>> ot
524
+ OrientationTensor2
525
+ [[ 0.502 -0.011]
526
+ [-0.011 0.498]]
527
+ (ar:1.02, ori:140)
528
+
529
+ """
530
+
531
+ @classmethod
532
+ def from_features(cls, g) -> "OrientationTensor2":
533
+ """
534
+ Return ``Ortensor`` of data in Vector2Set features
535
+
536
+ Args:
537
+ g (Vector2Set): Set of features
538
+
539
+ Example:
540
+ >>> v = vec2set.random_vonmises(position=120)
541
+ >>> ot = v.ortensor()
542
+ >>> ot
543
+ OrientationTensor2
544
+ [[ 0.377 -0.282]
545
+ [-0.282 0.623]]
546
+ (ar:2.05, ori:123)
547
+
548
+ """
549
+
550
+ return cls(np.dot(np.array(g).T, np.array(g)) / len(g))