Skip to main content

outram_foam_basic_lib/polynomial/
roots.rs

1// SPDX-License-Identifier: GPL-3.0-only
2// Copyright (C) 2026 OUTRAM PARK contributors
3// Derived from OpenFOAM (www.openfoam.com)
4// Copyright (C) 2004-2023 OpenFOAM Foundation
5// Copyright (C) 2016-2023 OpenCFD Ltd.
6//
7// This file is part of OUTRAM PARK.
8//
9// OUTRAM PARK is free software: you can redistribute it and/or modify it
10// under the terms of the GNU General Public License as published by the
11// Free Software Foundation, either version 3 of the License, or (at your
12// option) any later version.
13//
14// OUTRAM PARK is distributed in the hope that it will be useful, but
15// WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17// General Public License for more details.
18//
19// You should have received a copy of the GNU General Public License along
20// with OUTRAM PARK.  If not, see <https://www.gnu.org/licenses/>.
21
22/// Root classification tag, matching `Foam::roots::type`.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[repr(u64)]
25pub enum RootType {
26    Real = 0,
27    Complex = 1,
28    PosInf = 2,
29    NegInf = 3,
30    Nan = 4,
31}
32
33/// Tagged root container for N roots.
34/// Types are packed 3 bits per root into a u64, identical to C++ `Roots<N>`.
35/// For complex conjugate pairs, slot i holds the real part and slot i+1 holds
36/// the imaginary part; both slots are tagged `Complex`.
37#[derive(Debug, Clone, Copy)]
38pub struct Roots<const N: usize> {
39    pub(crate) values: [f64; N],
40    types: u64,
41}
42
43impl<const N: usize> Roots<N> {
44    /// Value stored at slot `i`.
45    ///
46    /// # Out of range reads `NaN`, and never panics
47    ///
48    /// `i >= N` is a caller bug either way. Returning `NaN` rather than
49    /// panicking is what lets this type be lifted verbatim into `petir`, which
50    /// targets bare metal -- there a panic is not a stack trace and a non-zero
51    /// exit, it is the end of the program. `NaN` still surfaces the bug, just
52    /// downstream of the read.
53    #[inline]
54    pub fn get(&self, i: usize) -> f64 {
55        match self.values.get(i) {
56            Some(&v) => v,
57            None => f64::NAN,
58        }
59    }
60
61    /// Root type at slot `i`.
62    #[inline]
63    pub fn root_type(&self, i: usize) -> RootType {
64        match (self.types >> (3 * i)) & 7 {
65            0 => RootType::Real,
66            1 => RootType::Complex,
67            2 => RootType::PosInf,
68            3 => RootType::NegInf,
69            _ => RootType::Nan,
70        }
71    }
72
73    /// Overwrite the type at slot `i`.
74    #[inline]
75    pub fn set_type(&mut self, i: usize, t: RootType) {
76        let shift = 3 * i;
77        self.types = (self.types & !(7u64 << shift)) | ((t as u64) << shift);
78    }
79}
80
81impl<const N: usize> std::ops::Index<usize> for Roots<N> {
82    type Output = f64;
83    #[inline]
84    fn index(&self, i: usize) -> &f64 {
85        // `Index` must hand back a reference, so an out-of-range slot borrows
86        // a shared NaN rather than panicking -- matching `get` above. Using
87        // the operator where a slot may be absent is still a caller bug; this
88        // only changes how it is reported.
89        static OUT_OF_RANGE: f64 = f64::NAN;
90        match self.values.get(i) {
91            Some(v) => v,
92            None => &OUT_OF_RANGE,
93        }
94    }
95}
96
97// ---------------------------------------------------------------------------
98// Constructors for each concrete arity.
99// The C++ template constructors (concatenation by prepend/append) are exposed
100// as named static methods on the concrete impl blocks.
101// ---------------------------------------------------------------------------
102
103impl Roots<1> {
104    /// Single root with the given type and value.
105    #[inline]
106    pub fn new(t: RootType, x: f64) -> Self {
107        Self {
108            values: [x],
109            types: t as u64,
110        }
111    }
112}
113
114impl Roots<2> {
115    /// Concatenate two single roots.  C++ `Roots<2>(Roots<1>, Roots<1>)`.
116    #[inline]
117    pub fn from_pair(a: Roots<1>, b: Roots<1>) -> Self {
118        // Destructured rather than subscripted. `Roots<1>::values` is
119        // `[f64; 1]`, so rustc checks `[a0]` exhaustively; the pattern also
120        // states the arity the concatenation depends on.
121        let ([a0], [b0]) = (a.values, b.values);
122        Self {
123            values: [a0, b0],
124            types: a.types | (b.types << 3),
125        }
126    }
127
128    /// `Roots<1>` followed by one additional root.
129    /// C++ `Roots<2>(Roots<1>, type, x)`.
130    #[inline]
131    pub fn with_tail(head: Roots<1>, t: RootType, x: f64) -> Self {
132        let [h0] = head.values;
133        Self {
134            values: [h0, x],
135            types: head.types | ((t as u64) << 3),
136        }
137    }
138
139    /// Duplicate a single root into both slots.
140    /// C++ `Roots<2>(r, r)`.
141    #[inline]
142    pub fn both(r: Roots<1>) -> Self {
143        Self::from_pair(r, r)
144    }
145}
146
147impl Roots<3> {
148    /// All three slots get the same type and value.
149    /// C++ `Roots<3>(type, x)`.
150    #[inline]
151    pub fn uniform(t: RootType, x: f64) -> Self {
152        let ti = t as u64;
153        Self {
154            values: [x, x, x],
155            types: ti | (ti << 3) | (ti << 6),
156        }
157    }
158
159    /// Concatenate `Roots<1>` then `Roots<2>`.
160    /// C++ `Roots<3>(Roots<1>, Roots<2>)`.
161    #[inline]
162    pub fn concat_1_2(a: Roots<1>, b: Roots<2>) -> Self {
163        let ([a0], [b0, b1]) = (a.values, b.values);
164        Self {
165            values: [a0, b0, b1],
166            types: a.types | (b.types << 3),
167        }
168    }
169
170    /// Concatenate `Roots<2>` then `Roots<1>`.
171    /// C++ `Roots<3>(Roots<2>, Roots<1>)`.
172    #[inline]
173    pub fn concat_2_1(a: Roots<2>, b: Roots<1>) -> Self {
174        let ([a0, a1], [b0]) = (a.values, b.values);
175        Self {
176            values: [a0, a1, b0],
177            types: a.types | (b.types << 6),
178        }
179    }
180
181    /// `Roots<2>` followed by one additional root.
182    /// C++ `Roots<3>(Roots<2>, type, x)`.
183    #[inline]
184    pub fn with_tail(head: Roots<2>, t: RootType, x: f64) -> Self {
185        Self::concat_2_1(head, Roots::<1>::new(t, x))
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn encoding_round_trip() {
195        let r = Roots::<1>::new(RootType::Real, 3.14);
196        assert_eq!(r.root_type(0), RootType::Real);
197        assert_eq!(r.get(0), 3.14);
198    }
199
200    #[test]
201    fn concat_2() {
202        let a = Roots::<1>::new(RootType::Real, 1.0);
203        let b = Roots::<1>::new(RootType::Nan, 0.0);
204        let r = Roots::<2>::from_pair(a, b);
205        assert_eq!(r.root_type(0), RootType::Real);
206        assert_eq!(r.root_type(1), RootType::Nan);
207        assert_eq!(r[0], 1.0);
208        assert_eq!(r[1], 0.0);
209    }
210
211    #[test]
212    fn concat_3() {
213        let a = Roots::<1>::new(RootType::Real, 1.0);
214        let b = Roots::<2>::from_pair(
215            Roots::<1>::new(RootType::Real, 2.0),
216            Roots::<1>::new(RootType::Nan, 0.0),
217        );
218        let r = Roots::<3>::concat_1_2(a, b);
219        assert_eq!(r.root_type(0), RootType::Real);
220        assert_eq!(r.root_type(1), RootType::Real);
221        assert_eq!(r.root_type(2), RootType::Nan);
222        assert_eq!(r[0], 1.0);
223        assert_eq!(r[1], 2.0);
224    }
225
226    #[test]
227    fn uniform_3() {
228        let r = Roots::<3>::uniform(RootType::Complex, -5.0);
229        for i in 0..3 {
230            assert_eq!(r.root_type(i), RootType::Complex);
231            assert_eq!(r[i], -5.0);
232        }
233    }
234}