Skip to main content

deep_causality_physics/quantities/fluids/
mod.rs

1/*
2 * SPDX-License-Identifier: MIT
3 * Copyright (c) 2023 - 2026. The DeepCausality Authors and Contributors. All Rights Reserved.
4 */
5
6use crate::PhysicsError;
7
8/// Pressure (Pascals).
9#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
10pub struct Pressure<R: deep_causality_algebra::RealField>(R);
11
12impl<R: deep_causality_algebra::RealField> Default for Pressure<R> {
13    fn default() -> Self {
14        Self(R::zero())
15    }
16}
17
18impl<R: deep_causality_algebra::RealField> Pressure<R> {
19    pub fn new(val: R) -> Result<Self, PhysicsError> {
20        if !val.is_finite() {
21            return Err(PhysicsError::PhysicalInvariantBroken(
22                "Pressure must be finite".into(),
23            ));
24        }
25        if val < R::zero() {
26            return Err(PhysicsError::PhysicalInvariantBroken(
27                "Negative Pressure".into(),
28            ));
29        }
30        Ok(Self(val))
31    }
32    pub fn new_unchecked(val: R) -> Self {
33        Self(val)
34    }
35    pub fn value(&self) -> R {
36        self.0
37    }
38}
39
40impl<R: deep_causality_algebra::RealField + Into<f64>> From<Pressure<R>> for f64 {
41    fn from(val: Pressure<R>) -> Self {
42        val.0.into()
43    }
44}
45
46/// Density (kg/m^3).
47#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
48pub struct Density<R: deep_causality_algebra::RealField>(R);
49
50impl<R: deep_causality_algebra::RealField> Default for Density<R> {
51    fn default() -> Self {
52        Self(R::zero())
53    }
54}
55
56impl<R: deep_causality_algebra::RealField> Density<R> {
57    pub fn new(val: R) -> Result<Self, PhysicsError> {
58        if !val.is_finite() {
59            return Err(PhysicsError::PhysicalInvariantBroken(
60                "Density must be finite".into(),
61            ));
62        }
63        if val < R::zero() {
64            return Err(PhysicsError::PhysicalInvariantBroken(
65                "Negative Density".into(),
66            ));
67        }
68        Ok(Self(val))
69    }
70    pub fn new_unchecked(val: R) -> Self {
71        Self(val)
72    }
73    pub fn value(&self) -> R {
74        self.0
75    }
76}
77
78impl<R: deep_causality_algebra::RealField + Into<f64>> From<Density<R>> for f64 {
79    fn from(val: Density<R>) -> Self {
80        val.0.into()
81    }
82}
83
84/// Dynamic Viscosity (Pa·s).
85#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
86pub struct Viscosity<R: deep_causality_algebra::RealField>(R);
87
88impl<R: deep_causality_algebra::RealField> Default for Viscosity<R> {
89    fn default() -> Self {
90        Self(R::zero())
91    }
92}
93
94impl<R: deep_causality_algebra::RealField> Viscosity<R> {
95    pub fn new(val: R) -> Result<Self, PhysicsError> {
96        if !val.is_finite() {
97            return Err(PhysicsError::PhysicalInvariantBroken(
98                "Viscosity must be finite".into(),
99            ));
100        }
101        if val < R::zero() {
102            return Err(PhysicsError::PhysicalInvariantBroken(
103                "Negative Viscosity".into(),
104            ));
105        }
106        Ok(Self(val))
107    }
108    pub fn new_unchecked(val: R) -> Self {
109        Self(val)
110    }
111    pub fn value(&self) -> R {
112        self.0
113    }
114}
115
116impl<R: deep_causality_algebra::RealField + Into<f64>> From<Viscosity<R>> for f64 {
117    fn from(val: Viscosity<R>) -> Self {
118        val.0.into()
119    }
120}
121
122/// Kinematic Viscosity (m^2/s). Equals dynamic viscosity divided by density.
123#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
124pub struct KinematicViscosity<R: deep_causality_algebra::RealField>(R);
125
126impl<R: deep_causality_algebra::RealField> Default for KinematicViscosity<R> {
127    fn default() -> Self {
128        Self(R::zero())
129    }
130}
131
132impl<R: deep_causality_algebra::RealField> KinematicViscosity<R> {
133    pub fn new(val: R) -> Result<Self, PhysicsError> {
134        if !val.is_finite() {
135            return Err(PhysicsError::PhysicalInvariantBroken(
136                "KinematicViscosity must be finite".into(),
137            ));
138        }
139        if val < R::zero() {
140            return Err(PhysicsError::PhysicalInvariantBroken(
141                "Negative KinematicViscosity".into(),
142            ));
143        }
144        Ok(Self(val))
145    }
146    pub fn new_unchecked(val: R) -> Self {
147        Self(val)
148    }
149    pub fn value(&self) -> R {
150        self.0
151    }
152}
153
154impl<R: deep_causality_algebra::RealField + Into<f64>> From<KinematicViscosity<R>> for f64 {
155    fn from(val: KinematicViscosity<R>) -> Self {
156        val.0.into()
157    }
158}
159
160/// Specific Enthalpy (J/kg). Reference-state dependent; may be negative.
161#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
162pub struct SpecificEnthalpy<R: deep_causality_algebra::RealField>(R);
163
164impl<R: deep_causality_algebra::RealField> Default for SpecificEnthalpy<R> {
165    fn default() -> Self {
166        Self(R::zero())
167    }
168}
169
170impl<R: deep_causality_algebra::RealField> SpecificEnthalpy<R> {
171    pub fn new(val: R) -> Result<Self, PhysicsError> {
172        if !val.is_finite() {
173            return Err(PhysicsError::PhysicalInvariantBroken(
174                "SpecificEnthalpy must be finite".into(),
175            ));
176        }
177        Ok(Self(val))
178    }
179    pub fn new_unchecked(val: R) -> Self {
180        Self(val)
181    }
182    pub fn value(&self) -> R {
183        self.0
184    }
185}
186
187impl<R: deep_causality_algebra::RealField + Into<f64>> From<SpecificEnthalpy<R>> for f64 {
188    fn from(val: SpecificEnthalpy<R>) -> Self {
189        val.0.into()
190    }
191}
192
193/// Wall Shear Stress magnitude (Pa). Stored as magnitude; sign convention is
194/// carried by the calling context, not by this type.
195#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
196pub struct WallShearStress<R: deep_causality_algebra::RealField>(R);
197
198impl<R: deep_causality_algebra::RealField> Default for WallShearStress<R> {
199    fn default() -> Self {
200        Self(R::zero())
201    }
202}
203
204impl<R: deep_causality_algebra::RealField> WallShearStress<R> {
205    pub fn new(val: R) -> Result<Self, PhysicsError> {
206        if !val.is_finite() {
207            return Err(PhysicsError::PhysicalInvariantBroken(
208                "WallShearStress must be finite".into(),
209            ));
210        }
211        if val < R::zero() {
212            return Err(PhysicsError::PhysicalInvariantBroken(
213                "Negative WallShearStress".into(),
214            ));
215        }
216        Ok(Self(val))
217    }
218    pub fn new_unchecked(val: R) -> Self {
219        Self(val)
220    }
221    pub fn value(&self) -> R {
222        self.0
223    }
224}
225
226impl<R: deep_causality_algebra::RealField + Into<f64>> From<WallShearStress<R>> for f64 {
227    fn from(val: WallShearStress<R>) -> Self {
228        val.0.into()
229    }
230}
231
232// =============================================================================
233// Typed vector newtypes — semantic-distinction wrappers around `[R; 3]`.
234//
235// Constructor invariant: every component must be finite. No magnitude or sign
236// constraint (any finite real triple is a valid velocity, vorticity, etc.).
237// =============================================================================
238
239/// Fluid velocity vector (m/s).
240#[derive(Debug, Clone, Copy, PartialEq)]
241pub struct Velocity3<R: deep_causality_algebra::RealField>([R; 3]);
242
243impl<R: deep_causality_algebra::RealField> Default for Velocity3<R> {
244    fn default() -> Self {
245        Self([R::zero(); 3])
246    }
247}
248
249impl<R: deep_causality_algebra::RealField> Velocity3<R> {
250    pub fn new(raw: [R; 3]) -> Result<Self, PhysicsError> {
251        if !raw[0].is_finite() || !raw[1].is_finite() || !raw[2].is_finite() {
252            return Err(PhysicsError::PhysicalInvariantBroken(
253                "Velocity3 components must be finite".into(),
254            ));
255        }
256        Ok(Self(raw))
257    }
258    pub fn new_unchecked(raw: [R; 3]) -> Self {
259        Self(raw)
260    }
261    pub fn value(&self) -> &[R; 3] {
262        &self.0
263    }
264    pub fn into_inner(self) -> [R; 3] {
265        self.0
266    }
267}
268
269impl<R: deep_causality_algebra::RealField> From<Velocity3<R>> for [R; 3] {
270    fn from(val: Velocity3<R>) -> Self {
271        val.0
272    }
273}
274
275/// Vorticity vector `ω = ∇ × u` (1/s). Pseudovector under spatial reflection.
276#[derive(Debug, Clone, Copy, PartialEq)]
277pub struct VorticityVector<R: deep_causality_algebra::RealField>([R; 3]);
278
279impl<R: deep_causality_algebra::RealField> Default for VorticityVector<R> {
280    fn default() -> Self {
281        Self([R::zero(); 3])
282    }
283}
284
285impl<R: deep_causality_algebra::RealField> VorticityVector<R> {
286    pub fn new(raw: [R; 3]) -> Result<Self, PhysicsError> {
287        if !raw[0].is_finite() || !raw[1].is_finite() || !raw[2].is_finite() {
288            return Err(PhysicsError::PhysicalInvariantBroken(
289                "VorticityVector components must be finite".into(),
290            ));
291        }
292        Ok(Self(raw))
293    }
294    pub fn new_unchecked(raw: [R; 3]) -> Self {
295        Self(raw)
296    }
297    pub fn value(&self) -> &[R; 3] {
298        &self.0
299    }
300    pub fn into_inner(self) -> [R; 3] {
301        self.0
302    }
303}
304
305impl<R: deep_causality_algebra::RealField> From<VorticityVector<R>> for [R; 3] {
306    fn from(val: VorticityVector<R>) -> Self {
307        val.0
308    }
309}
310
311/// Acceleration vector (m/s²). Return type of momentum-equation RHS evaluators.
312#[derive(Debug, Clone, Copy, PartialEq)]
313pub struct AccelerationVector<R: deep_causality_algebra::RealField>([R; 3]);
314
315impl<R: deep_causality_algebra::RealField> Default for AccelerationVector<R> {
316    fn default() -> Self {
317        Self([R::zero(); 3])
318    }
319}
320
321impl<R: deep_causality_algebra::RealField> AccelerationVector<R> {
322    pub fn new(raw: [R; 3]) -> Result<Self, PhysicsError> {
323        if !raw[0].is_finite() || !raw[1].is_finite() || !raw[2].is_finite() {
324            return Err(PhysicsError::PhysicalInvariantBroken(
325                "AccelerationVector components must be finite".into(),
326            ));
327        }
328        Ok(Self(raw))
329    }
330    pub fn new_unchecked(raw: [R; 3]) -> Self {
331        Self(raw)
332    }
333    pub fn value(&self) -> &[R; 3] {
334        &self.0
335    }
336    pub fn into_inner(self) -> [R; 3] {
337        self.0
338    }
339}
340
341impl<R: deep_causality_algebra::RealField> From<AccelerationVector<R>> for [R; 3] {
342    fn from(val: AccelerationVector<R>) -> Self {
343        val.0
344    }
345}
346
347/// Body force per unit volume (N/m³).
348#[derive(Debug, Clone, Copy, PartialEq)]
349pub struct BodyForceDensity<R: deep_causality_algebra::RealField>([R; 3]);
350
351impl<R: deep_causality_algebra::RealField> Default for BodyForceDensity<R> {
352    fn default() -> Self {
353        Self([R::zero(); 3])
354    }
355}
356
357impl<R: deep_causality_algebra::RealField> BodyForceDensity<R> {
358    pub fn new(raw: [R; 3]) -> Result<Self, PhysicsError> {
359        if !raw[0].is_finite() || !raw[1].is_finite() || !raw[2].is_finite() {
360            return Err(PhysicsError::PhysicalInvariantBroken(
361                "BodyForceDensity components must be finite".into(),
362            ));
363        }
364        Ok(Self(raw))
365    }
366    pub fn new_unchecked(raw: [R; 3]) -> Self {
367        Self(raw)
368    }
369    pub fn value(&self) -> &[R; 3] {
370        &self.0
371    }
372    pub fn into_inner(self) -> [R; 3] {
373        self.0
374    }
375}
376
377impl<R: deep_causality_algebra::RealField> From<BodyForceDensity<R>> for [R; 3] {
378    fn from(val: BodyForceDensity<R>) -> Self {
379        val.0
380    }
381}
382
383// =============================================================================
384// Typed rank-2 tensor newtypes — convention and algebraic-structure wrappers
385// around `[[R; 3]; 3]`.
386// =============================================================================
387
388#[inline]
389fn all_finite_3x3<R: deep_causality_algebra::RealField>(raw: &[[R; 3]; 3]) -> bool {
390    raw[0][0].is_finite()
391        && raw[0][1].is_finite()
392        && raw[0][2].is_finite()
393        && raw[1][0].is_finite()
394        && raw[1][1].is_finite()
395        && raw[1][2].is_finite()
396        && raw[2][0].is_finite()
397        && raw[2][1].is_finite()
398        && raw[2][2].is_finite()
399}
400
401/// Velocity gradient tensor `∇u`. Pinned to the Jacobian convention:
402/// `value[i][j] = ∂u_i / ∂x_j`. Construction-time check is finiteness only —
403/// any finite 3×3 matrix is a valid velocity gradient.
404#[derive(Debug, Clone, Copy, PartialEq)]
405pub struct VelocityGradient<R: deep_causality_algebra::RealField>([[R; 3]; 3]);
406
407impl<R: deep_causality_algebra::RealField> Default for VelocityGradient<R> {
408    fn default() -> Self {
409        Self([[R::zero(); 3]; 3])
410    }
411}
412
413impl<R: deep_causality_algebra::RealField> VelocityGradient<R> {
414    pub fn new(raw: [[R; 3]; 3]) -> Result<Self, PhysicsError> {
415        if !all_finite_3x3(&raw) {
416            return Err(PhysicsError::PhysicalInvariantBroken(
417                "VelocityGradient components must be finite".into(),
418            ));
419        }
420        Ok(Self(raw))
421    }
422    pub fn new_unchecked(raw: [[R; 3]; 3]) -> Self {
423        Self(raw)
424    }
425    pub fn value(&self) -> &[[R; 3]; 3] {
426        &self.0
427    }
428    pub fn into_inner(self) -> [[R; 3]; 3] {
429        self.0
430    }
431}
432
433impl<R: deep_causality_algebra::RealField> From<VelocityGradient<R>> for [[R; 3]; 3] {
434    fn from(val: VelocityGradient<R>) -> Self {
435        val.0
436    }
437}
438
439/// Strain-rate tensor `S = 0.5·(∇u + ∇uᵀ)`. Symmetric: `S_ij = S_ji`.
440/// `new` checks the symmetry invariant by exact equality, matching what natural
441/// construction `0.5·(G + Gᵀ)` produces in IEEE 754. Use `new_unchecked` to
442/// bypass the check in hot kernels where symmetry is guaranteed by the algebra.
443#[derive(Debug, Clone, Copy, PartialEq)]
444pub struct StrainRateTensor<R: deep_causality_algebra::RealField>([[R; 3]; 3]);
445
446impl<R: deep_causality_algebra::RealField> Default for StrainRateTensor<R> {
447    fn default() -> Self {
448        Self([[R::zero(); 3]; 3])
449    }
450}
451
452impl<R: deep_causality_algebra::RealField> StrainRateTensor<R> {
453    pub fn new(raw: [[R; 3]; 3]) -> Result<Self, PhysicsError> {
454        if !all_finite_3x3(&raw) {
455            return Err(PhysicsError::PhysicalInvariantBroken(
456                "StrainRateTensor components must be finite".into(),
457            ));
458        }
459        if raw[0][1] != raw[1][0] || raw[0][2] != raw[2][0] || raw[1][2] != raw[2][1] {
460            return Err(PhysicsError::PhysicalInvariantBroken(
461                "StrainRateTensor must be symmetric (S_ij == S_ji)".into(),
462            ));
463        }
464        Ok(Self(raw))
465    }
466    pub fn new_unchecked(raw: [[R; 3]; 3]) -> Self {
467        Self(raw)
468    }
469    pub fn value(&self) -> &[[R; 3]; 3] {
470        &self.0
471    }
472    pub fn into_inner(self) -> [[R; 3]; 3] {
473        self.0
474    }
475}
476
477impl<R: deep_causality_algebra::RealField> From<StrainRateTensor<R>> for [[R; 3]; 3] {
478    fn from(val: StrainRateTensor<R>) -> Self {
479        val.0
480    }
481}
482
483/// Rate-of-rotation (spin) tensor `Ω = 0.5·(∇u − ∇uᵀ)`. Antisymmetric:
484/// `Ω_ji = −Ω_ij`, with `Ω_ii = 0`. `new` checks the antisymmetry invariant
485/// by exact equality.
486#[derive(Debug, Clone, Copy, PartialEq)]
487pub struct RotationRateTensor<R: deep_causality_algebra::RealField>([[R; 3]; 3]);
488
489impl<R: deep_causality_algebra::RealField> Default for RotationRateTensor<R> {
490    fn default() -> Self {
491        Self([[R::zero(); 3]; 3])
492    }
493}
494
495impl<R: deep_causality_algebra::RealField> RotationRateTensor<R> {
496    pub fn new(raw: [[R; 3]; 3]) -> Result<Self, PhysicsError> {
497        if !all_finite_3x3(&raw) {
498            return Err(PhysicsError::PhysicalInvariantBroken(
499                "RotationRateTensor components must be finite".into(),
500            ));
501        }
502        let zero = R::zero();
503        if raw[0][0] != zero || raw[1][1] != zero || raw[2][2] != zero {
504            return Err(PhysicsError::PhysicalInvariantBroken(
505                "RotationRateTensor diagonal must be zero (antisymmetric)".into(),
506            ));
507        }
508        if raw[0][1] != -raw[1][0] || raw[0][2] != -raw[2][0] || raw[1][2] != -raw[2][1] {
509            return Err(PhysicsError::PhysicalInvariantBroken(
510                "RotationRateTensor must be antisymmetric (Ω_ji == -Ω_ij)".into(),
511            ));
512        }
513        Ok(Self(raw))
514    }
515    pub fn new_unchecked(raw: [[R; 3]; 3]) -> Self {
516        Self(raw)
517    }
518    pub fn value(&self) -> &[[R; 3]; 3] {
519        &self.0
520    }
521    pub fn into_inner(self) -> [[R; 3]; 3] {
522        self.0
523    }
524}
525
526impl<R: deep_causality_algebra::RealField> From<RotationRateTensor<R>> for [[R; 3]; 3] {
527    fn from(val: RotationRateTensor<R>) -> Self {
528        val.0
529    }
530}
531
532/// Cauchy stress tensor (Pa). Symmetric, positive-in-tension sign convention.
533#[derive(Debug, Clone, Copy, PartialEq)]
534pub struct CauchyStress<R: deep_causality_algebra::RealField>([[R; 3]; 3]);
535
536impl<R: deep_causality_algebra::RealField> Default for CauchyStress<R> {
537    fn default() -> Self {
538        Self([[R::zero(); 3]; 3])
539    }
540}
541
542impl<R: deep_causality_algebra::RealField> CauchyStress<R> {
543    pub fn new(raw: [[R; 3]; 3]) -> Result<Self, PhysicsError> {
544        if !all_finite_3x3(&raw) {
545            return Err(PhysicsError::PhysicalInvariantBroken(
546                "CauchyStress components must be finite".into(),
547            ));
548        }
549        if raw[0][1] != raw[1][0] || raw[0][2] != raw[2][0] || raw[1][2] != raw[2][1] {
550            return Err(PhysicsError::PhysicalInvariantBroken(
551                "CauchyStress must be symmetric (σ_ij == σ_ji)".into(),
552            ));
553        }
554        Ok(Self(raw))
555    }
556    pub fn new_unchecked(raw: [[R; 3]; 3]) -> Self {
557        Self(raw)
558    }
559    pub fn value(&self) -> &[[R; 3]; 3] {
560        &self.0
561    }
562    pub fn into_inner(self) -> [[R; 3]; 3] {
563        self.0
564    }
565}
566
567impl<R: deep_causality_algebra::RealField> From<CauchyStress<R>> for [[R; 3]; 3] {
568    fn from(val: CauchyStress<R>) -> Self {
569        val.0
570    }
571}
572
573/// Viscous (deviatoric) stress tensor `τ` (Pa). Symmetric. Distinct from the
574/// full Cauchy stress `σ = −p I + τ` — only the viscous part appears in the
575/// dissipation `Φ = τ:∇u ≥ 0` and entropy-production guarantees.
576#[derive(Debug, Clone, Copy, PartialEq)]
577pub struct ViscousStress<R: deep_causality_algebra::RealField>([[R; 3]; 3]);
578
579impl<R: deep_causality_algebra::RealField> Default for ViscousStress<R> {
580    fn default() -> Self {
581        Self([[R::zero(); 3]; 3])
582    }
583}
584
585impl<R: deep_causality_algebra::RealField> ViscousStress<R> {
586    pub fn new(raw: [[R; 3]; 3]) -> Result<Self, PhysicsError> {
587        if !all_finite_3x3(&raw) {
588            return Err(PhysicsError::PhysicalInvariantBroken(
589                "ViscousStress components must be finite".into(),
590            ));
591        }
592        if raw[0][1] != raw[1][0] || raw[0][2] != raw[2][0] || raw[1][2] != raw[2][1] {
593            return Err(PhysicsError::PhysicalInvariantBroken(
594                "ViscousStress must be symmetric (τ_ij == τ_ji)".into(),
595            ));
596        }
597        Ok(Self(raw))
598    }
599    pub fn new_unchecked(raw: [[R; 3]; 3]) -> Self {
600        Self(raw)
601    }
602    pub fn value(&self) -> &[[R; 3]; 3] {
603        &self.0
604    }
605    pub fn into_inner(self) -> [[R; 3]; 3] {
606        self.0
607    }
608}
609
610impl<R: deep_causality_algebra::RealField> From<ViscousStress<R>> for [[R; 3]; 3] {
611    fn from(val: ViscousStress<R>) -> Self {
612        val.0
613    }
614}
615
616/// Reynolds stress tensor `R_ij = ⟨u'_i u'_j⟩` (Pa, after multiplication by ρ
617/// in caller; here a kinematic Reynolds stress in m²/s² is also acceptable).
618/// Symmetric. Diagonal entries are non-negative (variances) — *not* enforced
619/// by the newtype to keep the constructor cheap; callers passing a tensor
620/// that violates the diagonal-positivity property are responsible for
621/// downstream interpretation.
622#[derive(Debug, Clone, Copy, PartialEq)]
623pub struct ReynoldsStress<R: deep_causality_algebra::RealField>([[R; 3]; 3]);
624
625impl<R: deep_causality_algebra::RealField> Default for ReynoldsStress<R> {
626    fn default() -> Self {
627        Self([[R::zero(); 3]; 3])
628    }
629}
630
631impl<R: deep_causality_algebra::RealField> ReynoldsStress<R> {
632    pub fn new(raw: [[R; 3]; 3]) -> Result<Self, PhysicsError> {
633        if !all_finite_3x3(&raw) {
634            return Err(PhysicsError::PhysicalInvariantBroken(
635                "ReynoldsStress components must be finite".into(),
636            ));
637        }
638        if raw[0][1] != raw[1][0] || raw[0][2] != raw[2][0] || raw[1][2] != raw[2][1] {
639            return Err(PhysicsError::PhysicalInvariantBroken(
640                "ReynoldsStress must be symmetric (R_ij == R_ji)".into(),
641            ));
642        }
643        Ok(Self(raw))
644    }
645    pub fn new_unchecked(raw: [[R; 3]; 3]) -> Self {
646        Self(raw)
647    }
648    pub fn value(&self) -> &[[R; 3]; 3] {
649        &self.0
650    }
651    pub fn into_inner(self) -> [[R; 3]; 3] {
652        self.0
653    }
654}
655
656impl<R: deep_causality_algebra::RealField> From<ReynoldsStress<R>> for [[R; 3]; 3] {
657    fn from(val: ReynoldsStress<R>) -> Self {
658        val.0
659    }
660}