1use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign};
30
31use super::spherical_tensor::SphericalTensor;
32use super::vector::Vector3;
33
34#[derive(Debug, Clone, Copy, PartialEq, Default)]
37pub struct SymmTensor {
38 pub xx: f64,
39 pub xy: f64,
40 pub xz: f64,
41 pub yy: f64,
42 pub yz: f64,
43 pub zz: f64,
44}
45
46impl SymmTensor {
47 pub const ZERO: Self = Self {
49 xx: 0.0,
50 xy: 0.0,
51 xz: 0.0,
52 yy: 0.0,
53 yz: 0.0,
54 zz: 0.0,
55 };
56 pub const IDENTITY: Self = Self {
58 xx: 1.0,
59 xy: 0.0,
60 xz: 0.0,
61 yy: 1.0,
62 yz: 0.0,
63 zz: 1.0,
64 };
65
66 #[inline]
68 pub fn new(xx: f64, xy: f64, xz: f64, yy: f64, yz: f64, zz: f64) -> Self {
69 Self {
70 xx,
71 xy,
72 xz,
73 yy,
74 yz,
75 zz,
76 }
77 }
78
79 #[inline]
81 pub fn from_diag(xx: f64, yy: f64, zz: f64) -> Self {
82 Self {
83 xx,
84 xy: 0.0,
85 xz: 0.0,
86 yy,
87 yz: 0.0,
88 zz,
89 }
90 }
91
92 #[inline]
94 pub fn row_x(self) -> Vector3 {
95 Vector3::new(self.xx, self.xy, self.xz)
96 }
97 #[inline]
98 pub fn row_y(self) -> Vector3 {
99 Vector3::new(self.xy, self.yy, self.yz)
100 }
101 #[inline]
102 pub fn row_z(self) -> Vector3 {
103 Vector3::new(self.xz, self.yz, self.zz)
104 }
105
106 #[inline]
108 pub fn diag(self) -> Vector3 {
109 Vector3::new(self.xx, self.yy, self.zz)
110 }
111
112 #[inline]
114 pub fn tr(self) -> f64 {
115 self.xx + self.yy + self.zz
116 }
117
118 #[inline]
120 pub fn sph(self) -> SphericalTensor {
121 SphericalTensor::new(self.tr() / 3.0)
122 }
123
124 #[inline]
126 pub fn dev(self) -> Self {
127 self - self.sph()
128 }
129
130 #[inline]
132 pub fn dev2(self) -> Self {
133 self - 2.0 * self.sph()
134 }
135
136 #[inline]
138 pub fn det(self) -> f64 {
139 let Self {
140 xx,
141 xy,
142 xz,
143 yy,
144 yz,
145 zz,
146 } = self;
147 xx * yy * zz + xy * yz * xz + xz * xy * yz - xx * yz * yz - xy * xy * zz - xz * yy * xz
148 }
149
150 #[inline]
152 pub fn adjunct(self) -> Self {
153 let Self {
154 xx,
155 xy,
156 xz,
157 yy,
158 yz,
159 zz,
160 } = self;
161 Self {
162 xx: yy * zz - yz * yz,
163 xy: xz * yz - xy * zz,
164 xz: xy * yz - xz * yy,
165 yy: xx * zz - xz * xz,
166 yz: xy * xz - xx * yz,
167 zz: xx * yy - xy * xy,
168 }
169 }
170
171 #[inline]
173 pub fn inv(self) -> Self {
174 let d = self.det();
175 debug_assert!(d.abs() > 0.0, "SymmTensor is singular");
176 self.adjunct() / d
177 }
178
179 pub fn safe_inv(self) -> Self {
181 use super::scalar::SMALL;
182 let diag_sqr = self.xx * self.xx + self.yy * self.yy + self.zz * self.zz;
183 let threshold = SMALL * diag_sqr;
184 let mut work = self;
185 let small_xx = self.xx * self.xx < threshold;
186 let small_yy = self.yy * self.yy < threshold;
187 let small_zz = self.zz * self.zz < threshold;
188 if small_xx {
189 work.xx += 1.0;
190 }
191 if small_yy {
192 work.yy += 1.0;
193 }
194 if small_zz {
195 work.zz += 1.0;
196 }
197 let d = work.det();
198 if d.abs() < super::scalar::ROOT_VSMALL {
199 return Self::ZERO;
200 }
201 let mut result = work.adjunct() / d;
202 if small_xx {
203 result.xx -= 1.0;
204 }
205 if small_yy {
206 result.yy -= 1.0;
207 }
208 if small_zz {
209 result.zz -= 1.0;
210 }
211 result
212 }
213
214 #[inline]
216 pub fn mag_sqr(self) -> f64 {
217 let Self {
218 xx,
219 xy,
220 xz,
221 yy,
222 yz,
223 zz,
224 } = self;
225 xx * xx + 2.0 * xy * xy + 2.0 * xz * xz + yy * yy + 2.0 * yz * yz + zz * zz
226 }
227
228 #[inline]
229 pub fn mag(self) -> f64 {
230 self.mag_sqr().sqrt()
231 }
232
233 #[inline]
235 pub fn diag_sqr(self) -> f64 {
236 self.xx * self.xx + self.yy * self.yy + self.zz * self.zz
237 }
238
239 #[inline]
241 pub fn inner_sqr(self) -> Self {
242 let Self {
243 xx,
244 xy,
245 xz,
246 yy,
247 yz,
248 zz,
249 } = self;
250 Self {
251 xx: xx * xx + xy * xy + xz * xz,
252 xy: xx * xy + xy * yy + xz * yz,
253 xz: xx * xz + xy * yz + xz * zz,
254 yy: xy * xy + yy * yy + yz * yz,
255 yz: xy * xz + yy * yz + yz * zz,
256 zz: xz * xz + yz * yz + zz * zz,
257 }
258 }
259
260 #[inline]
263 pub fn double_inner(self, rhs: Self) -> f64 {
264 self.xx * rhs.xx
265 + 2.0 * self.xy * rhs.xy
266 + 2.0 * self.xz * rhs.xz
267 + self.yy * rhs.yy
268 + 2.0 * self.yz * rhs.yz
269 + self.zz * rhs.zz
270 }
271
272 #[inline]
274 pub fn mat_vec(self, v: Vector3) -> Vector3 {
275 Vector3::new(
276 self.xx * v.x + self.xy * v.y + self.xz * v.z,
277 self.xy * v.x + self.yy * v.y + self.yz * v.z,
278 self.xz * v.x + self.yz * v.y + self.zz * v.z,
279 )
280 }
281
282 #[inline]
284 pub fn hodge_dual(self) -> Vector3 {
285 Vector3::new(self.yz, -self.xz, self.xy)
286 }
287
288 #[inline]
291 pub fn from_outer(v: Vector3) -> Self {
292 Self {
293 xx: v.x * v.x,
294 xy: v.x * v.y,
295 xz: v.x * v.z,
296 yy: v.y * v.y,
297 yz: v.y * v.z,
298 zz: v.z * v.z,
299 }
300 }
301
302 #[inline]
304 pub fn invariant_i(self) -> f64 {
305 self.tr()
306 }
307
308 #[inline]
310 pub fn invariant_ii(self) -> f64 {
311 self.xx * self.yy + self.yy * self.zz + self.xx * self.zz
312 - self.xy * self.xy
313 - self.yz * self.yz
314 - self.xz * self.xz
315 }
316
317 #[inline]
319 pub fn invariant_iii(self) -> f64 {
320 self.det()
321 }
322
323 #[inline]
325 pub fn lerp(a: Self, b: Self, t: f64) -> Self {
326 let ot = 1.0 - t;
327 Self {
328 xx: ot * a.xx + t * b.xx,
329 xy: ot * a.xy + t * b.xy,
330 xz: ot * a.xz + t * b.xz,
331 yy: ot * a.yy + t * b.yy,
332 yz: ot * a.yz + t * b.yz,
333 zz: ot * a.zz + t * b.zz,
334 }
335 }
336
337 pub fn is_identity(self, tol: f64) -> bool {
339 (self.xx - 1.0).abs() < tol
340 && (self.yy - 1.0).abs() < tol
341 && (self.zz - 1.0).abs() < tol
342 && self.xy.abs() < tol
343 && self.xz.abs() < tol
344 && self.yz.abs() < tol
345 }
346}
347
348impl From<SphericalTensor> for SymmTensor {
351 #[inline]
352 fn from(st: SphericalTensor) -> Self {
353 Self {
354 xx: st.ii,
355 xy: 0.0,
356 xz: 0.0,
357 yy: st.ii,
358 yz: 0.0,
359 zz: st.ii,
360 }
361 }
362}
363
364impl Neg for SymmTensor {
367 type Output = Self;
368 #[inline]
369 fn neg(self) -> Self {
370 Self {
371 xx: -self.xx,
372 xy: -self.xy,
373 xz: -self.xz,
374 yy: -self.yy,
375 yz: -self.yz,
376 zz: -self.zz,
377 }
378 }
379}
380
381impl Add for SymmTensor {
382 type Output = Self;
383 #[inline]
384 fn add(self, r: Self) -> Self {
385 Self {
386 xx: self.xx + r.xx,
387 xy: self.xy + r.xy,
388 xz: self.xz + r.xz,
389 yy: self.yy + r.yy,
390 yz: self.yz + r.yz,
391 zz: self.zz + r.zz,
392 }
393 }
394}
395
396impl Sub for SymmTensor {
397 type Output = Self;
398 #[inline]
399 fn sub(self, r: Self) -> Self {
400 Self {
401 xx: self.xx - r.xx,
402 xy: self.xy - r.xy,
403 xz: self.xz - r.xz,
404 yy: self.yy - r.yy,
405 yz: self.yz - r.yz,
406 zz: self.zz - r.zz,
407 }
408 }
409}
410
411impl Mul<f64> for SymmTensor {
412 type Output = Self;
413 #[inline]
414 fn mul(self, s: f64) -> Self {
415 Self {
416 xx: self.xx * s,
417 xy: self.xy * s,
418 xz: self.xz * s,
419 yy: self.yy * s,
420 yz: self.yz * s,
421 zz: self.zz * s,
422 }
423 }
424}
425
426impl Mul<SymmTensor> for f64 {
427 type Output = SymmTensor;
428 #[inline]
429 fn mul(self, st: SymmTensor) -> SymmTensor {
430 st * self
431 }
432}
433
434impl Div<f64> for SymmTensor {
435 type Output = Self;
436 #[inline]
437 fn div(self, s: f64) -> Self {
438 Self {
439 xx: self.xx / s,
440 xy: self.xy / s,
441 xz: self.xz / s,
442 yy: self.yy / s,
443 yz: self.yz / s,
444 zz: self.zz / s,
445 }
446 }
447}
448
449impl AddAssign for SymmTensor {
450 #[inline]
451 fn add_assign(&mut self, r: Self) {
452 *self = *self + r;
453 }
454}
455
456impl SubAssign for SymmTensor {
457 #[inline]
458 fn sub_assign(&mut self, r: Self) {
459 *self = *self - r;
460 }
461}
462
463impl MulAssign<f64> for SymmTensor {
464 #[inline]
465 fn mul_assign(&mut self, s: f64) {
466 *self = *self * s;
467 }
468}
469
470impl Add<SymmTensor> for SphericalTensor {
473 type Output = SymmTensor;
474 #[inline]
475 fn add(self, st: SymmTensor) -> SymmTensor {
476 SymmTensor {
477 xx: self.ii + st.xx,
478 xy: st.xy,
479 xz: st.xz,
480 yy: self.ii + st.yy,
481 yz: st.yz,
482 zz: self.ii + st.zz,
483 }
484 }
485}
486
487impl Add<SphericalTensor> for SymmTensor {
488 type Output = Self;
489 #[inline]
490 fn add(self, spt: SphericalTensor) -> Self {
491 spt + self
492 }
493}
494
495impl Sub<SymmTensor> for SphericalTensor {
496 type Output = SymmTensor;
497 #[inline]
498 fn sub(self, st: SymmTensor) -> SymmTensor {
499 SymmTensor {
500 xx: self.ii - st.xx,
501 xy: -st.xy,
502 xz: -st.xz,
503 yy: self.ii - st.yy,
504 yz: -st.yz,
505 zz: self.ii - st.zz,
506 }
507 }
508}
509
510impl Sub<SphericalTensor> for SymmTensor {
511 type Output = Self;
512 #[inline]
513 fn sub(self, spt: SphericalTensor) -> Self {
514 Self {
515 xx: self.xx - spt.ii,
516 xy: self.xy,
517 xz: self.xz,
518 yy: self.yy - spt.ii,
519 yz: self.yz,
520 zz: self.zz - spt.ii,
521 }
522 }
523}
524
525impl Mul<SymmTensor> for SphericalTensor {
527 type Output = SymmTensor;
528 #[inline]
529 fn mul(self, st: SymmTensor) -> SymmTensor {
530 st * self.ii
531 }
532}
533
534impl Mul<SphericalTensor> for SymmTensor {
535 type Output = Self;
536 #[inline]
537 fn mul(self, spt: SphericalTensor) -> Self {
538 self * spt.ii
539 }
540}
541
542#[inline]
546pub fn tr(st: SymmTensor) -> f64 {
547 st.tr()
548}
549
550#[inline]
552pub fn det(st: SymmTensor) -> f64 {
553 st.det()
554}
555
556#[inline]
558pub fn inv(st: SymmTensor) -> SymmTensor {
559 st.inv()
560}
561
562#[inline]
564pub fn dev(st: SymmTensor) -> SymmTensor {
565 st.dev()
566}
567
568#[inline]
570pub fn dev2(st: SymmTensor) -> SymmTensor {
571 st.dev2()
572}
573
574#[inline]
576pub fn symm(st: SymmTensor) -> SymmTensor {
577 st
578}
579
580#[inline]
582pub fn two_symm(st: SymmTensor) -> SymmTensor {
583 st * 2.0
584}
585
586#[inline]
588pub fn dev_symm(st: SymmTensor) -> SymmTensor {
589 st.dev()
590}
591
592#[inline]
594pub fn dev_two_symm(st: SymmTensor) -> SymmTensor {
595 st.dev2()
596}
597
598#[inline]
600pub fn sqr(v: Vector3) -> SymmTensor {
601 SymmTensor::from_outer(v)
602}
603
604#[inline]
606pub fn mag_sqr(st: SymmTensor) -> f64 {
607 st.mag_sqr()
608}
609
610#[inline]
612pub fn lerp(a: SymmTensor, b: SymmTensor, t: f64) -> SymmTensor {
613 SymmTensor::lerp(a, b, t)
614}
615
616#[cfg(test)]
617mod tests {
618 use super::*;
619
620 #[test]
621 fn identity_properties() {
622 let i = SymmTensor::IDENTITY;
623 assert_eq!(i.tr(), 3.0);
624 assert_eq!(i.det(), 1.0);
625 let inv = i.inv();
626 assert!((inv.xx - 1.0).abs() < 1e-14);
627 assert!((inv.yy - 1.0).abs() < 1e-14);
628 assert!((inv.zz - 1.0).abs() < 1e-14);
629 }
630
631 #[test]
632 fn dev_removes_spherical() {
633 let st = SymmTensor::new(4.0, 0.0, 0.0, 2.0, 0.0, 3.0);
634 let d = st.dev();
635 assert!((d.tr()).abs() < 1e-14);
636 }
637
638 #[test]
639 fn from_outer_is_symmetric() {
640 let v = Vector3::new(1.0, 2.0, 3.0);
641 let s = SymmTensor::from_outer(v);
642 assert_eq!(s.xx, 1.0);
643 assert_eq!(s.xy, 2.0);
644 assert_eq!(s.xz, 3.0);
645 assert_eq!(s.yy, 4.0);
646 assert_eq!(s.yz, 6.0);
647 assert_eq!(s.zz, 9.0);
648 }
649
650 #[test]
651 fn mat_vec() {
652 let i = SymmTensor::IDENTITY;
653 let v = Vector3::new(1.0, 2.0, 3.0);
654 assert_eq!(i.mat_vec(v), v);
655 }
656
657 #[test]
658 fn dev2_regression() {
659 let t = SymmTensor::from_diag(6.0, 3.0, 3.0);
662 let d = t.dev2();
663 assert!((d.xx - (-2.0)).abs() < 1e-14, "xx={}", d.xx);
664 assert!((d.yy - (-5.0)).abs() < 1e-14, "yy={}", d.yy);
665 assert!((d.zz - (-5.0)).abs() < 1e-14, "zz={}", d.zz);
666 assert!(d.xy.abs() < 1e-14);
667 assert!(d.xz.abs() < 1e-14);
668 assert!(d.yz.abs() < 1e-14);
669 assert!((d.tr() - (-12.0)).abs() < 1e-14, "tr(dev2)={}", d.tr());
671 }
672
673 #[test]
674 fn inv_roundtrip_is_identity() {
675 use crate::primitives::Tensor;
677 let t = SymmTensor::new(4.0, 2.0, 1.0, 3.0, 0.5, 2.0);
678 let product = Tensor::from(t).mat_mul(Tensor::from(t.inv()));
679 assert!(product.is_identity(1e-12), "T·T⁻¹ is not identity");
680 }
681}