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}