1use numra_core::Scalar;
13
14#[derive(Clone, Debug)]
16pub struct Domain1D<S: Scalar> {
17 pub left: Bound<S>,
19 pub right: Bound<S>,
21 pub n_points: usize,
23}
24
25impl<S: Scalar> Domain1D<S> {
26 pub fn fixed(x_min: S, x_max: S, n_points: usize) -> Self {
28 Self {
29 left: Bound::Fixed(x_min),
30 right: Bound::Fixed(x_max),
31 n_points,
32 }
33 }
34
35 pub fn with_moving_right(x_min: S, s_initial: S, n_points: usize) -> Self {
37 Self {
38 left: Bound::Fixed(x_min),
39 right: Bound::Moving(MovingBound::new(s_initial)),
40 n_points,
41 }
42 }
43
44 pub fn bounds(&self) -> (S, S) {
46 (self.left.position(), self.right.position())
47 }
48
49 pub fn length(&self) -> S {
51 let (lo, hi) = self.bounds();
52 hi - lo
53 }
54
55 pub fn grid_points(&self) -> Vec<S> {
57 let (lo, hi) = self.bounds();
58 let n = self.n_points;
59 (0..n)
60 .map(|i| lo + (hi - lo) * S::from_usize(i) / S::from_usize(n - 1))
61 .collect()
62 }
63
64 pub fn dx(&self) -> S {
66 self.length() / S::from_usize(self.n_points - 1)
67 }
68}
69
70#[derive(Clone, Debug)]
72pub enum Bound<S: Scalar> {
73 Fixed(S),
75 Moving(MovingBound<S>),
77}
78
79impl<S: Scalar> Bound<S> {
80 pub fn position(&self) -> S {
82 match self {
83 Bound::Fixed(x) => *x,
84 Bound::Moving(mb) => mb.position,
85 }
86 }
87
88 pub fn is_fixed(&self) -> bool {
90 matches!(self, Bound::Fixed(_))
91 }
92
93 pub fn is_moving(&self) -> bool {
95 matches!(self, Bound::Moving(_))
96 }
97
98 pub fn set_position(&mut self, new_pos: S) {
100 if let Bound::Moving(mb) = self {
101 mb.position = new_pos;
102 }
103 }
104}
105
106#[derive(Clone, Debug)]
108pub struct MovingBound<S: Scalar> {
109 pub position: S,
111 pub stefan: Option<StefanCondition<S>>,
113}
114
115impl<S: Scalar> MovingBound<S> {
116 pub fn new(position: S) -> Self {
118 Self {
119 position,
120 stefan: None,
121 }
122 }
123
124 pub fn with_stefan(position: S, stefan: StefanCondition<S>) -> Self {
126 Self {
127 position,
128 stefan: Some(stefan),
129 }
130 }
131}
132
133#[derive(Clone, Debug)]
139pub struct StefanCondition<S: Scalar> {
140 pub coefficient: S,
143}
144
145impl<S: Scalar> StefanCondition<S> {
146 pub fn new(coefficient: S) -> Self {
148 Self { coefficient }
149 }
150
151 pub fn velocity(&self, flux: S) -> S {
156 -self.coefficient * flux
157 }
158}
159
160#[derive(Clone)]
165pub struct CoordinateTransform<S: Scalar> {
166 pub s: S,
168}
169
170impl<S: Scalar> CoordinateTransform<S> {
171 pub fn new(s: S) -> Self {
173 Self { s }
174 }
175
176 pub fn update(&mut self, s: S) {
178 self.s = s;
179 }
180
181 pub fn physical_to_computational(&self, x: S) -> S {
184 x / self.s
185 }
186
187 pub fn computational_to_physical(&self, xi: S) -> S {
190 xi * self.s
191 }
192
193 pub fn jacobian(&self) -> S {
195 self.s
196 }
197
198 pub fn transform_d1(&self, du_dxi: S) -> S {
200 du_dxi / self.s
201 }
202
203 pub fn transform_d2(&self, d2u_dxi2: S) -> S {
205 d2u_dxi2 / (self.s * self.s)
206 }
207
208 pub fn convection_coefficient(&self, dsdt: S, xi: S) -> S {
215 dsdt * xi / self.s
216 }
217}
218
219#[derive(Clone)]
223pub struct FieldState1D<S: Scalar> {
224 pub values: Vec<S>,
226 pub domain: Domain1D<S>,
228}
229
230impl<S: Scalar> FieldState1D<S> {
231 pub fn new(values: Vec<S>, domain: Domain1D<S>) -> Self {
233 assert_eq!(values.len(), domain.n_points);
234 Self { values, domain }
235 }
236
237 pub fn get(&self, i: usize) -> S {
239 self.values[i]
240 }
241
242 pub fn set(&mut self, i: usize, value: S) {
244 self.values[i] = value;
245 }
246
247 pub fn flux_left(&self) -> S {
250 let dx = self.domain.dx();
251 let v = &self.values;
252 let n = v.len();
253 if n < 3 {
254 return (v[1] - v[0]) / dx;
255 }
256 (S::from_f64(-3.0) * v[0] + S::from_f64(4.0) * v[1] - v[2]) / (S::from_f64(2.0) * dx)
257 }
258
259 pub fn flux_right(&self) -> S {
262 let dx = self.domain.dx();
263 let v = &self.values;
264 let n = v.len();
265 if n < 3 {
266 return (v[n - 1] - v[n - 2]) / dx;
267 }
268 (S::from_f64(3.0) * v[n - 1] - S::from_f64(4.0) * v[n - 2] + v[n - 3])
269 / (S::from_f64(2.0) * dx)
270 }
271
272 pub fn evaluate(&self, x: S) -> S {
274 let points = self.domain.grid_points();
275 let (lo, hi) = self.domain.bounds();
276
277 if x <= lo {
278 return self.values[0];
279 }
280 if x >= hi {
281 return *self.values.last().unwrap();
282 }
283
284 let dx = self.domain.dx();
286 let i = ((x - lo) / dx).to_f64().floor() as usize;
287 let i = i.min(self.values.len() - 2);
288
289 let x0 = points[i];
290 let x1 = points[i + 1];
291 let t = (x - x0) / (x1 - x0);
292
293 self.values[i] * (S::ONE - t) + self.values[i + 1] * t
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_fixed_domain() {
303 let domain = Domain1D::fixed(0.0_f64, 1.0, 11);
304 assert!((domain.length() - 1.0).abs() < 1e-10);
305 assert!((domain.dx() - 0.1).abs() < 1e-10);
306
307 let points = domain.grid_points();
308 assert_eq!(points.len(), 11);
309 assert!((points[0]).abs() < 1e-10);
310 assert!((points[10] - 1.0).abs() < 1e-10);
311 }
312
313 #[test]
314 fn test_moving_domain() {
315 let mut domain = Domain1D::with_moving_right(0.0_f64, 0.5, 11);
316 assert!((domain.length() - 0.5).abs() < 1e-10);
317
318 domain.right.set_position(0.8);
320 assert!((domain.length() - 0.8).abs() < 1e-10);
321 }
322
323 #[test]
324 fn test_stefan_condition() {
325 let stefan = StefanCondition::new(0.1_f64);
326
327 let velocity = stefan.velocity(10.0);
329 assert!((velocity - (-1.0)).abs() < 1e-10);
330
331 let velocity = stefan.velocity(-5.0);
333 assert!((velocity - 0.5).abs() < 1e-10);
334 }
335
336 #[test]
337 fn test_coordinate_transform() {
338 let transform = CoordinateTransform::new(2.0_f64);
339
340 let xi = transform.physical_to_computational(1.0);
342 assert!((xi - 0.5).abs() < 1e-10);
343
344 let x = transform.computational_to_physical(0.5);
346 assert!((x - 1.0).abs() < 1e-10);
347
348 assert!((transform.jacobian() - 2.0).abs() < 1e-10);
350
351 let du_dx = transform.transform_d1(4.0);
354 assert!((du_dx - 2.0).abs() < 1e-10);
355
356 let d2u_dx2 = transform.transform_d2(8.0);
358 assert!((d2u_dx2 - 2.0).abs() < 1e-10);
359 }
360
361 #[test]
362 fn test_field_state_flux() {
363 let domain = Domain1D::fixed(0.0_f64, 1.0, 11);
365 let values: Vec<f64> = (0..11).map(|i| i as f64 * 0.1).collect();
366 let field = FieldState1D::new(values, domain);
367
368 let flux_left = field.flux_left();
370 let flux_right = field.flux_right();
371
372 assert!((flux_left - 1.0).abs() < 0.01);
373 assert!((flux_right - 1.0).abs() < 0.01);
374 }
375
376 #[test]
377 fn test_field_evaluate() {
378 let domain = Domain1D::fixed(0.0_f64, 1.0, 11);
379 let values: Vec<f64> = (0..11).map(|i| i as f64 * 0.1).collect();
380 let field = FieldState1D::new(values, domain);
381
382 assert!((field.evaluate(0.0) - 0.0).abs() < 1e-10);
384 assert!((field.evaluate(0.5) - 0.5).abs() < 1e-10);
385 assert!((field.evaluate(1.0) - 1.0).abs() < 1e-10);
386
387 assert!((field.evaluate(0.25) - 0.25).abs() < 0.01);
389 assert!((field.evaluate(0.75) - 0.75).abs() < 0.01);
390 }
391}