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