1pub mod boundary;
2pub mod error;
3pub mod report;
4pub mod sampling;
5
6pub use boundary::*;
7pub use error::*;
8pub use sampling::*;
9
10use core::fmt;
11
12use nalgebra::{Const, OMatrix, SVector};
13
14use crate::utils::vector_to_string;
15
16#[derive(Debug, Clone, PartialEq)]
19pub struct Span<const N: usize> {
20 u: SVector<f64, N>,
21 v: SVector<f64, N>,
22}
23
24#[derive(Debug, Clone, PartialEq)]
28pub struct Domain<const N: usize> {
29 low: SVector<f64, N>,
30 high: SVector<f64, N>,
31}
32
33impl<const N: usize> Span<N> {
34 pub fn new(u: SVector<f64, N>, v: SVector<f64, N>) -> Self {
38 let u = u.normalize();
39 let v = v.normalize();
40 let v = (v - u * u.dot(&v)).normalize();
41 Span { u, v }
42 }
43
44 pub fn u(&self) -> SVector<f64, N> {
45 self.u
46 }
47 pub fn v(&self) -> SVector<f64, N> {
48 self.v
49 }
50
51 pub fn get_rotater(&self) -> impl Fn(f64) -> OMatrix<f64, Const<N>, Const<N>> {
54 let identity = OMatrix::<f64, Const<N>, Const<N>>::identity();
55
56 let a = self.u * self.v.transpose() - self.v * self.u.transpose();
57 let b = self.v * self.v.transpose() + self.u * self.u.transpose();
58
59 move |angle: f64| identity + a * angle.sin() + b * (angle.cos() - 1.0)
60 }
61}
62
63impl<const N: usize> Domain<N> {
64 pub fn new(p1: SVector<f64, N>, p2: SVector<f64, N>) -> Self {
66 let low = p1.zip_map(&p2, |a, b| a.min(b));
67 let high = p1.zip_map(&p2, |a, b| a.max(b));
68
69 Domain { low, high }
70 }
71
72 pub unsafe fn new_from_bounds(low: SVector<f64, N>, high: SVector<f64, N>) -> Self {
78 Domain { low, high }
79 }
80
81 pub fn normalized() -> Self {
83 let low = SVector::<f64, N>::zeros();
84 let high = SVector::<f64, N>::repeat(1.0);
85 Domain { low, high }
86 }
87
88 pub fn low(&self) -> &SVector<f64, N> {
90 &self.low
91 }
92
93 pub fn high(&self) -> &SVector<f64, N> {
95 &self.high
96 }
97
98 pub fn contains(&self, p: &SVector<f64, N>) -> bool {
100 let below_low = SVector::<bool, N>::from_fn(|i, _| p[i] < self.low[i]);
101 if below_low.iter().any(|&x| x) {
102 return false;
103 }
104
105 let above_high = SVector::<bool, N>::from_fn(|i, _| p[i] > self.high[i]);
106 if above_high.iter().any(|&x| x) {
107 return false;
108 }
109
110 true
111 }
112
113 pub fn dimensions(&self) -> SVector<f64, N> {
115 self.high - self.low
116 }
117
118 pub fn project_point_domains(
127 p: &SVector<f64, N>,
128 from: &Domain<N>,
129 to: &Domain<N>,
130 ) -> SVector<f64, N> {
131 ((p - from.low).component_div(&from.dimensions())).component_mul(&to.dimensions()) + to.low
132 }
133
134 pub fn distance_to_edge(&self, p: &SVector<f64, N>, v: &SVector<f64, N>) -> Result<f64> {
143 let t_lower = (self.low - p).component_div(v);
144 let t_upper = (self.high - p).component_div(v);
145
146 let l = t_lower
147 .iter()
148 .filter(|&&xi| xi >= 0.0)
149 .min_by(|a, b| a.partial_cmp(b).unwrap())
150 .cloned();
151
152 let u = t_upper
153 .iter()
154 .filter(|&&xi| xi >= 0.0)
155 .min_by(|a, b| a.partial_cmp(b).unwrap())
156 .cloned();
157
158 let t = match (l, u) {
159 (None, Some(t)) => t,
160 (Some(t), None) => t,
161 (Some(tl), Some(tu)) => tl.min(tu),
162 (None, None) => return Err(SamplingError::OutOfBounds),
164 };
165
166 Ok(t)
167 }
168}
169
170impl<const N: usize> fmt::Display for Span<N> {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 write!(
173 f,
174 "Span({:?}, {:?})",
175 vector_to_string(&self.u),
176 vector_to_string(&self.v)
177 )
178 }
179}
180
181#[cfg(test)]
182mod span_tests {
183 use super::*;
184
185 const ATOL: f64 = 1e-10;
186
187 fn approx(a: f64, b: f64, atol: f64) -> bool {
188 (a - b).abs() < atol
189 }
190
191 #[test]
192 fn is_orthogonal() {
193 let u = nalgebra::vector![0.5, 0.5, 0.1, 0.4, 1.0];
194 let v = nalgebra::vector![1.1, -0.2, -0.5, 0.1, 0.8];
195
196 let span = Span::new(u, v);
197
198 assert!(approx(span.u.angle(&span.v).to_degrees(), 90.0, ATOL));
199 }
200
201 #[test]
202 fn is_normal() {
203 let u = nalgebra::vector![0.5, 0.5, 0.1, 0.4, 1.0];
204 let v = nalgebra::vector![1.1, -0.2, -0.5, 0.1, 0.8];
205
206 let span = Span::new(u, v);
207
208 assert!(approx(span.u.norm(), 1.0, ATOL));
209 assert!(approx(span.v.norm(), 1.0, ATOL));
210 }
211
212 #[test]
213 fn rotater_90() {
214 let u = nalgebra::vector![0.5, 0.5, 0.1, 0.4, 1.0];
215 let v = nalgebra::vector![1.1, -0.2, -0.5, 0.1, 0.8];
216
217 let span = Span::new(u, v);
218
219 let x0 = v;
220 let angle = 90.0f64.to_radians();
221
222 let x1 = span.get_rotater()(angle) * x0;
223
224 assert!(approx(x0.angle(&x1), angle, ATOL));
225 }
226
227 #[test]
228 fn rotater_25() {
229 let u = nalgebra::vector![0.5, 0.5, 0.1, 0.4, 1.0];
230 let v = nalgebra::vector![1.1, -0.2, -0.5, 0.1, 0.8];
231
232 let span = Span::new(u, v);
233
234 let x0 = v;
235 let angle = 25.0f64.to_radians();
236
237 let x1 = span.get_rotater()(angle) * x0;
238
239 assert!(approx(x0.angle(&x1), angle, ATOL));
240 }
241}
242
243#[cfg(test)]
244mod domain_tests {
245 use nalgebra::vector;
246
247 use super::*;
248
249 const ATOL: f64 = 1e-10;
250
251 fn is_near<const N: usize>(a: &SVector<f64, N>, b: &SVector<f64, N>, atol: f64) -> bool {
252 (b - a).norm() <= atol
253 }
254
255 #[test]
256 fn point_translation_low_to_low() {
257 let src = Domain::<3>::normalized();
258 let dst = Domain::<3>::new(vector![1.0, 2.5, 3.5], vector![4.0, 5.0, 6.0]);
259
260 let p0 = src.low();
261 let p1 = Domain::project_point_domains(p0, &src, &dst);
262
263 assert!(is_near(&p1, dst.low(), ATOL))
264 }
265
266 #[test]
267 fn point_translation_high_to_high() {
268 let src = Domain::<3>::normalized();
269 let dst = Domain::<3>::new(vector![1.0, 2.5, 3.5], vector![4.0, 5.0, 6.0]);
270
271 let p0 = src.high();
272 let p1 = Domain::project_point_domains(p0, &src, &dst);
273
274 assert!(is_near(&p1, dst.high(), ATOL))
275 }
276
277 #[test]
278 fn point_translation_mid_to_mid() {
279 let src = Domain::<3>::normalized();
280 let dst = Domain::<3>::new(vector![1.0, 2.5, 3.5], vector![4.0, 5.0, 6.0]);
281
282 let src_mid = src.low() + src.dimensions() / 2.0;
283 let dst_mid = dst.low() + dst.dimensions() / 2.0;
284
285 let p0 = src_mid;
286 let p1 = Domain::project_point_domains(&p0, &src, &dst);
287
288 assert!(is_near(&p1, &dst_mid, ATOL))
289 }
290
291 #[test]
292 fn low_is_below_high() {
293 let d = Domain::<3>::new(vector![4.0, 2.5, 6.0], vector![1.0, 5.0, 3.5]);
294
295 assert!(d.low().iter().zip(d.high.iter()).all(|(l, h)| l < h));
296 }
297
298 #[test]
299 fn contains_false_when_below_low() {
300 let d = Domain::<3>::new(vector![4.0, 2.5, 6.0], vector![1.0, 5.0, 3.5]);
301 let p = d.low() - vector![0.01, 0.01, 0.01];
302
303 assert!(!d.contains(&p))
304 }
305
306 #[test]
307 fn contains_true_when_on_low() {
308 let d = Domain::<3>::new(vector![4.0, 2.5, 6.0], vector![1.0, 5.0, 3.5]);
309 let p = d.low();
310
311 assert!(d.contains(p))
312 }
313
314 #[test]
315 fn contains_false_when_above_high() {
316 let d = Domain::<3>::new(vector![4.0, 2.5, 6.0], vector![1.0, 5.0, 3.5]);
317 let p = d.high() + vector![0.01, 0.01, 0.01];
318
319 assert!(!d.contains(&p))
320 }
321
322 #[test]
323 fn contains_true_when_on_high() {
324 let d = Domain::<3>::new(vector![4.0, 2.5, 6.0], vector![1.0, 5.0, 3.5]);
325 let p = d.high();
326
327 assert!(d.contains(p))
328 }
329}