1use glam::DVec3;
4use pantometry_core::conserved::quantity;
5use pantometry_core::{Domain, Exchange, Kind, Ledger, Reading, Violation};
6use pantometry_units::{Energy, Length, LengthVec, Time, Velocity};
7
8use crate::Fluid;
9
10const ITERATION_BUDGET: usize = 4;
12
13pub const CELL_REYNOLDS_LIMIT: f64 = 2.0;
19
20#[derive(Clone, Copy, Debug, PartialEq)]
22pub enum Walls {
23 None,
25 Sliding {
29 low: f64,
31 high: f64,
33 },
34}
35
36#[derive(Clone, Debug)]
46pub struct Channel {
47 name: String,
48 counts: (usize, usize, usize),
49 dx: f64,
50 fluid: Fluid,
51 walls: Walls,
52 force: DVec3,
54 driven: f64,
66 saved_driven: f64,
68 u: Vec<f64>,
70 v: Vec<f64>,
72 w: Vec<f64>,
74 p: Vec<f64>,
76 tolerance: f64,
77 residual: f64,
78 converged: bool,
79 saved: Option<Box<Saved>>,
80}
81
82#[derive(Clone, Debug)]
83struct Saved {
84 u: Vec<f64>,
85 v: Vec<f64>,
86 w: Vec<f64>,
87 p: Vec<f64>,
88}
89
90impl pantometry_core::ScalarField for Channel {
91 fn unit(&self) -> &'static str {
93 "m/s"
94 }
95
96 fn at(&self, p: LengthVec, _t: Time) -> f64 {
103 let (nx, ny, nz) = self.counts;
104 let q = p.to_si() / self.dx;
105 if q.is_nan() {
106 return f64::NAN;
107 }
108 let pick = |v: f64, n: usize| (v.floor().max(0.0) as usize).min(n.saturating_sub(1));
109 self.velocity_at(pick(q.x, nx), pick(q.y, ny), pick(q.z, nz))
110 .length()
111 }
112}
113
114impl Channel {
115 pub fn new(
117 name: impl Into<String>,
118 counts: (usize, usize, usize),
119 cell: Length,
120 fluid: Fluid,
121 walls: Walls,
122 ) -> Channel {
123 let counts = (counts.0.max(1), counts.1.max(1), counts.2.max(1));
124 let (nx, ny, nz) = counts;
125 Channel {
126 name: name.into(),
127 counts,
128 dx: cell.to_si(),
129 fluid,
130 walls,
131 force: DVec3::ZERO,
132 driven: 0.0,
133 saved_driven: 0.0,
134 u: vec![0.0; nx * ny * nz],
135 v: vec![0.0; nx * (ny + 1) * nz],
136 w: vec![0.0; nx * ny * nz],
137 p: vec![0.0; nx * ny * nz],
138 tolerance: 1e-12,
139 residual: f64::INFINITY,
140 converged: true,
141 saved: None,
142 }
143 }
144
145 pub fn counts(&self) -> (usize, usize, usize) {
147 self.counts
148 }
149
150 pub fn cell(&self) -> Length {
152 Length::from_si(self.dx)
153 }
154
155 pub fn size(&self) -> LengthVec {
157 LengthVec::from_si(
158 DVec3::new(
159 self.counts.0 as f64,
160 self.counts.1 as f64,
161 self.counts.2 as f64,
162 ) * self.dx,
163 )
164 }
165
166 pub fn fluid(&self) -> Fluid {
168 self.fluid
169 }
170
171 pub fn gap(&self) -> Length {
173 Length::from_si(self.counts.1 as f64 * self.dx)
174 }
175
176 pub fn drive(&mut self, force: DVec3) -> &mut Channel {
181 self.force = force;
182 self
183 }
184
185 pub fn set_velocity(&mut self, field: impl Fn(DVec3) -> DVec3) -> &mut Channel {
192 let (nx, ny, nz) = self.counts;
193 let h = self.dx;
194 for k in 0..nz {
195 for j in 0..ny {
196 for i in 0..nx {
197 let at = DVec3::new(i as f64, j as f64 + 0.5, k as f64 + 0.5) * h;
198 let at_i = self.iu(i, j, k);
199 self.u[at_i] = field(at).x;
200 let at = DVec3::new(i as f64 + 0.5, j as f64 + 0.5, k as f64) * h;
201 let at_i = self.iw(i, j, k);
202 self.w[at_i] = field(at).z;
203 }
204 }
205 }
206 for k in 0..nz {
207 for j in 0..=ny {
208 for i in 0..nx {
209 let at = DVec3::new(i as f64 + 0.5, j as f64, k as f64 + 0.5) * h;
210 let at_i = self.iv(i, j, k);
211 self.v[at_i] = field(at).y;
212 }
213 }
214 }
215 self.apply_walls();
216 self
217 }
218
219 pub fn velocity_at(&self, i: usize, j: usize, k: usize) -> DVec3 {
221 let (nx, ny, nz) = self.counts;
222 let (i, j, k) = (i.min(nx - 1), j.min(ny - 1), k.min(nz - 1));
223 DVec3::new(
224 0.5 * (self.u[self.iu(i, j, k)] + self.u[self.iu((i + 1) % nx, j, k)]),
225 0.5 * (self.v[self.iv(i, j, k)] + self.v[self.iv(i, j + 1, k)]),
226 0.5 * (self.w[self.iw(i, j, k)] + self.w[self.iw(i, j, (k + 1) % nz)]),
227 )
228 }
229
230 pub fn mean_speed(&self) -> Velocity {
232 Velocity::from_si(self.u.iter().sum::<f64>() / self.u.len() as f64)
233 }
234
235 pub fn layer_speed(&self, j: usize) -> Velocity {
237 let (nx, ny, nz) = self.counts;
238 let j = j.min(ny - 1);
239 let mut sum = 0.0;
240 for k in 0..nz {
241 for i in 0..nx {
242 sum += self.u[self.iu(i, j, k)];
243 }
244 }
245 Velocity::from_si(sum / (nx * nz) as f64)
246 }
247
248 pub fn kinetic_energy(&self) -> Energy {
250 let cell = self.dx.powi(3);
251 let rho = self.fluid.density.to_si();
252 let (_, ny, _) = self.counts;
255 let sum_u: f64 = self.u.iter().map(|a| a * a).sum();
256 let sum_w: f64 = self.w.iter().map(|a| a * a).sum();
257 let mut sum_v = 0.0;
258 for (idx, val) in self.v.iter().enumerate() {
259 let j = (idx / self.counts.0) % (ny + 1);
260 let weight = if j == 0 || j == ny { 0.5 } else { 1.0 };
261 sum_v += weight * val * val;
262 }
263 Energy::from_si(0.5 * rho * (sum_u + sum_v + sum_w) * cell)
264 }
265
266 pub fn momentum_x(&self) -> f64 {
273 self.fluid.density.to_si() * self.dx.powi(3) * self.u.iter().sum::<f64>()
274 }
275
276 pub fn divergence(&self) -> f64 {
283 let (nx, ny, nz) = self.counts;
284 let mut worst: f64 = 0.0;
285 for k in 0..nz {
286 for j in 0..ny {
287 for i in 0..nx {
288 worst = worst.max(self.divergence_at(i, j, k).abs());
289 }
290 }
291 }
292 worst * self.dx
293 }
294
295 fn divergence_at(&self, i: usize, j: usize, k: usize) -> f64 {
296 let (nx, _, nz) = self.counts;
297 ((self.u[self.iu((i + 1) % nx, j, k)] - self.u[self.iu(i, j, k)])
298 + (self.v[self.iv(i, j + 1, k)] - self.v[self.iv(i, j, k)])
299 + (self.w[self.iw(i, j, (k + 1) % nz)] - self.w[self.iw(i, j, k)]))
300 / self.dx
301 }
302
303 pub fn cell_reynolds(&self) -> f64 {
307 self.peak_speed() * self.dx / self.fluid.kinematic_viscosity.to_si()
308 }
309
310 pub fn peak_speed(&self) -> f64 {
312 self.u
313 .iter()
314 .chain(&self.v)
315 .chain(&self.w)
316 .fold(0.0f64, |a, b| a.max(b.abs()))
317 }
318
319 pub fn viscous_limit(&self) -> Time {
321 Time::from_si(self.dx * self.dx / (6.0 * self.fluid.kinematic_viscosity.to_si()))
322 }
323
324 pub fn courant_limit(&self) -> Time {
326 let speed = self.peak_speed();
327 Time::from_si(if speed > 0.0 {
328 self.dx / speed
329 } else {
330 f64::INFINITY
331 })
332 }
333
334 pub fn converged(&self) -> bool {
336 self.converged
337 }
338
339 pub fn residual(&self) -> f64 {
341 self.residual
342 }
343
344 fn iu(&self, i: usize, j: usize, k: usize) -> usize {
347 let (nx, ny, _) = self.counts;
348 i + nx * (j + ny * k)
349 }
350 fn iv(&self, i: usize, j: usize, k: usize) -> usize {
351 let (nx, ny, _) = self.counts;
352 i + nx * (j + (ny + 1) * k)
353 }
354 fn iw(&self, i: usize, j: usize, k: usize) -> usize {
355 let (nx, ny, _) = self.counts;
356 i + nx * (j + ny * k)
357 }
358
359 fn u_at(&self, i: usize, j: isize, k: usize) -> f64 {
366 let (_, ny, _) = self.counts;
367 match self.walls {
368 Walls::None => {
369 let jj = ((j % ny as isize) + ny as isize) as usize % ny;
370 self.u[self.iu(i, jj, k)]
371 }
372 Walls::Sliding { low, high } => {
373 if j < 0 {
374 2.0 * low - self.u[self.iu(i, 0, k)]
375 } else if j >= ny as isize {
376 2.0 * high - self.u[self.iu(i, ny - 1, k)]
377 } else {
378 self.u[self.iu(i, j as usize, k)]
379 }
380 }
381 }
382 }
383
384 fn w_at(&self, i: usize, j: isize, k: usize) -> f64 {
385 let (_, ny, _) = self.counts;
386 match self.walls {
387 Walls::None => {
388 let jj = ((j % ny as isize) + ny as isize) as usize % ny;
389 self.w[self.iw(i, jj, k)]
390 }
391 Walls::Sliding { .. } => {
392 if j < 0 {
393 -self.w[self.iw(i, 0, k)]
394 } else if j >= ny as isize {
395 -self.w[self.iw(i, ny - 1, k)]
396 } else {
397 self.w[self.iw(i, j as usize, k)]
398 }
399 }
400 }
401 }
402
403 fn apply_walls(&mut self) {
405 if let Walls::Sliding { .. } = self.walls {
406 let (nx, ny, nz) = self.counts;
407 for k in 0..nz {
408 for i in 0..nx {
409 let (a, b) = (self.iv(i, 0, k), self.iv(i, ny, k));
410 self.v[a] = 0.0;
411 self.v[b] = 0.0;
412 }
413 }
414 } else {
415 let (nx, ny, nz) = self.counts;
423 for k in 0..nz {
424 for i in 0..nx {
425 let (a, b) = (self.iv(i, 0, k), self.iv(i, ny, k));
426 self.v[b] = self.v[a];
427 }
428 }
429 }
430 }
431 fn v_at(&self, i: usize, j: isize, k: usize) -> f64 {
433 let (_, ny, _) = self.counts;
434 match self.walls {
435 Walls::None => {
436 let jj = (((j % ny as isize) + ny as isize) % ny as isize) as usize;
437 self.v[self.iv(i, jj, k)]
438 }
439 Walls::Sliding { .. } => {
442 if j < 0 {
443 -self.v[self.iv(i, 1, k)]
444 } else if j > ny as isize {
445 -self.v[self.iv(i, ny - 1, k)]
446 } else {
447 self.v[self.iv(i, j as usize, k)]
448 }
449 }
450 }
451 }
452
453 fn v_interior(&self) -> (usize, usize) {
455 let (_, ny, _) = self.counts;
456 match self.walls {
457 Walls::None => (0, ny),
458 Walls::Sliding { .. } => (1, ny),
459 }
460 }
461
462 fn advance(&mut self, dt: f64) {
477 let (nx, ny, nz) = self.counts;
478 let h = self.dx;
479 let nu = self.fluid.kinematic_viscosity.to_si();
480 let (mut du, mut dv, mut dw) = (
481 vec![0.0; self.u.len()],
482 vec![0.0; self.v.len()],
483 vec![0.0; self.w.len()],
484 );
485 let left = |i: usize| (i + nx - 1) % nx;
486 let right = |i: usize| (i + 1) % nx;
487 let back = |k: usize| (k + nz - 1) % nz;
488 let front = |k: usize| (k + 1) % nz;
489
490 for k in 0..nz {
491 for j in 0..ny {
492 for i in 0..nx {
493 let jj = j as isize;
494
495 let uc = self.u_at(i, jj, k);
497 let ue = 0.5 * (uc + self.u_at(right(i), jj, k));
498 let uw = 0.5 * (self.u_at(left(i), jj, k) + uc);
499 let duudx = (ue * ue - uw * uw) / h;
500
501 let v_up = 0.5 * (self.v_at(i, jj + 1, k) + self.v_at(left(i), jj + 1, k));
502 let v_dn = 0.5 * (self.v_at(i, jj, k) + self.v_at(left(i), jj, k));
503 let u_up = 0.5 * (uc + self.u_at(i, jj + 1, k));
504 let u_dn = 0.5 * (self.u_at(i, jj - 1, k) + uc);
505 let duvdy = (u_up * v_up - u_dn * v_dn) / h;
506
507 let w_f = 0.5
508 * (self.w[self.iw(i, j, front(k))] + self.w[self.iw(left(i), j, front(k))]);
509 let w_b = 0.5 * (self.w[self.iw(i, j, k)] + self.w[self.iw(left(i), j, k)]);
510 let u_f = 0.5 * (uc + self.u_at(i, jj, front(k)));
511 let u_b = 0.5 * (self.u_at(i, jj, back(k)) + uc);
512 let duwdz = (u_f * w_f - u_b * w_b) / h;
513
514 let lap = (self.u_at(right(i), jj, k)
515 + self.u_at(left(i), jj, k)
516 + self.u_at(i, jj + 1, k)
517 + self.u_at(i, jj - 1, k)
518 + self.u_at(i, jj, front(k))
519 + self.u_at(i, jj, back(k))
520 - 6.0 * uc)
521 / (h * h);
522 let at = self.iu(i, j, k);
523 du[at] = dt * (-(duudx + duvdy + duwdz) + nu * lap + self.force.x);
524
525 let wc = self.w[self.iw(i, j, k)];
527 let wf = 0.5 * (wc + self.w[self.iw(i, j, front(k))]);
528 let wb = 0.5 * (self.w[self.iw(i, j, back(k))] + wc);
529 let dwwdz = (wf * wf - wb * wb) / h;
530
531 let u_e = 0.5 * (self.u_at(right(i), jj, k) + self.u_at(right(i), jj, back(k)));
532 let u_w2 = 0.5 * (self.u_at(i, jj, k) + self.u_at(i, jj, back(k)));
533 let w_e = 0.5 * (wc + self.w[self.iw(right(i), j, k)]);
534 let w_w = 0.5 * (self.w[self.iw(left(i), j, k)] + wc);
535 let dwudx = (w_e * u_e - w_w * u_w2) / h;
536
537 let v_up2 = 0.5 * (self.v_at(i, jj + 1, k) + self.v_at(i, jj + 1, back(k)));
538 let v_dn2 = 0.5 * (self.v_at(i, jj, k) + self.v_at(i, jj, back(k)));
539 let w_up = 0.5 * (wc + self.w_at(i, jj + 1, k));
540 let w_dn = 0.5 * (self.w_at(i, jj - 1, k) + wc);
541 let dwvdy = (w_up * v_up2 - w_dn * v_dn2) / h;
542
543 let lap = (self.w[self.iw(right(i), j, k)]
544 + self.w[self.iw(left(i), j, k)]
545 + self.w_at(i, jj + 1, k)
546 + self.w_at(i, jj - 1, k)
547 + self.w[self.iw(i, j, front(k))]
548 + self.w[self.iw(i, j, back(k))]
549 - 6.0 * wc)
550 / (h * h);
551 let at = self.iw(i, j, k);
552 dw[at] = dt * (-(dwwdz + dwudx + dwvdy) + nu * lap + self.force.z);
553 }
554 }
555 }
556
557 let (lo, hi) = self.v_interior();
558 for k in 0..nz {
559 for j in lo..hi {
560 for i in 0..nx {
561 let jj = j as isize;
562 let vc = self.v_at(i, jj, k);
563 let vu = 0.5 * (vc + self.v_at(i, jj + 1, k));
564 let vd = 0.5 * (self.v_at(i, jj - 1, k) + vc);
565 let dvvdy = (vu * vu - vd * vd) / h;
566
567 let u_e = 0.5 * (self.u_at(right(i), jj, k) + self.u_at(right(i), jj - 1, k));
568 let u_w = 0.5 * (self.u_at(i, jj, k) + self.u_at(i, jj - 1, k));
569 let v_e = 0.5 * (vc + self.v_at(right(i), jj, k));
570 let v_w = 0.5 * (self.v_at(left(i), jj, k) + vc);
571 let dvudx = (v_e * u_e - v_w * u_w) / h;
572
573 let w_f = 0.5 * (self.w_at(i, jj, front(k)) + self.w_at(i, jj - 1, front(k)));
574 let w_b = 0.5 * (self.w_at(i, jj, k) + self.w_at(i, jj - 1, k));
575 let v_f = 0.5 * (vc + self.v_at(i, jj, front(k)));
576 let v_b = 0.5 * (self.v_at(i, jj, back(k)) + vc);
577 let dvwdz = (v_f * w_f - v_b * w_b) / h;
578
579 let lap = (self.v_at(right(i), jj, k)
580 + self.v_at(left(i), jj, k)
581 + self.v_at(i, jj + 1, k)
582 + self.v_at(i, jj - 1, k)
583 + self.v_at(i, jj, front(k))
584 + self.v_at(i, jj, back(k))
585 - 6.0 * vc)
586 / (h * h);
587 let at = self.iv(i, j, k);
588 dv[at] = dt * (-(dvvdy + dvudx + dvwdz) + nu * lap + self.force.y);
589 }
590 }
591 }
592
593 for (a, b) in self.u.iter_mut().zip(&du) {
594 *a += b;
595 }
596 for (a, b) in self.v.iter_mut().zip(&dv) {
597 *a += b;
598 }
599 for (a, b) in self.w.iter_mut().zip(&dw) {
600 *a += b;
601 }
602 self.apply_walls();
603 }
604
605 fn project(&mut self, dt: f64) -> bool {
613 let (nx, ny, nz) = self.counts;
614 let cells = nx * ny * nz;
615 let mut b = vec![0.0; cells];
616 for k in 0..nz {
617 for j in 0..ny {
618 for i in 0..nx {
619 b[i + nx * (j + ny * k)] = self.divergence_at(i, j, k) / dt;
620 }
621 }
622 }
623 let mean: f64 = b.iter().sum::<f64>() / cells as f64;
624 for v in b.iter_mut() {
625 *v -= mean;
626 }
627
628 let mut x = std::mem::take(&mut self.p);
629 if x.len() != cells {
630 x = vec![0.0; cells];
631 }
632 let ax = self.laplacian(&x);
633 let mut r: Vec<f64> = b.iter().zip(&ax).map(|(bi, axi)| bi - axi).collect();
634 let mut p = r.clone();
635 let mut rr: f64 = r.iter().map(|v| v * v).sum();
636 let scale = b
637 .iter()
638 .map(|v| v * v)
639 .sum::<f64>()
640 .sqrt()
641 .max(f64::MIN_POSITIVE);
642 let budget = ITERATION_BUDGET * cells + 64;
643 let mut iterations = 0;
644 while rr.sqrt() / scale > self.tolerance && iterations < budget {
645 let ap = self.laplacian(&p);
646 let pap: f64 = p.iter().zip(&ap).map(|(a, b)| a * b).sum();
647 if pap.abs() <= 0.0 {
648 break;
649 }
650 let alpha = rr / pap;
651 for (xi, pi) in x.iter_mut().zip(&p) {
652 *xi += alpha * pi;
653 }
654 for (ri, api) in r.iter_mut().zip(&ap) {
655 *ri -= alpha * api;
656 }
657 let rr_next: f64 = r.iter().map(|v| v * v).sum();
658 let beta = rr_next / rr;
659 for (pi, ri) in p.iter_mut().zip(&r) {
660 *pi = ri + beta * *pi;
661 }
662 rr = rr_next;
663 iterations += 1;
664 }
665 let phi_mean: f64 = x.iter().sum::<f64>() / cells as f64;
666 for v in x.iter_mut() {
667 *v -= phi_mean;
668 }
669 self.p = x;
670 self.residual = rr.sqrt() / scale;
671 self.converged = self.residual <= self.tolerance;
672
673 let h = self.dx;
674 let phi = self.p.clone();
675 let idx = |i: usize, j: usize, k: usize| i + nx * (j + ny * k);
676 for k in 0..nz {
677 for j in 0..ny {
678 for i in 0..nx {
679 let at = self.iu(i, j, k);
680 self.u[at] -= dt * (phi[idx(i, j, k)] - phi[idx((i + nx - 1) % nx, j, k)]) / h;
681 let at = self.iw(i, j, k);
682 self.w[at] -= dt * (phi[idx(i, j, k)] - phi[idx(i, j, (k + nz - 1) % nz)]) / h;
683 }
684 }
685 }
686 let (lo, hi) = self.v_interior();
687 for k in 0..nz {
688 for j in lo..hi {
689 for i in 0..nx {
690 let up = idx(i, j % ny, k);
691 let dn = idx(i, (j + ny - 1) % ny, k);
692 let at = self.iv(i, j, k);
693 self.v[at] -= dt * (phi[up] - phi[dn]) / h;
694 }
695 }
696 }
697 self.apply_walls();
698 self.converged
699 }
700
701 fn laplacian(&self, x: &[f64]) -> Vec<f64> {
703 let (nx, ny, nz) = self.counts;
704 let h2 = self.dx * self.dx;
705 let idx = |i: usize, j: usize, k: usize| i + nx * (j + ny * k);
706 let mut y = vec![0.0; x.len()];
707 let walled = matches!(self.walls, Walls::Sliding { .. });
708 for k in 0..nz {
709 for j in 0..ny {
710 for i in 0..nx {
711 let c = idx(i, j, k);
712 let mut acc =
713 x[idx((i + 1) % nx, j, k)] + x[idx((i + nx - 1) % nx, j, k)] - 2.0 * x[c];
714 acc +=
715 x[idx(i, j, (k + 1) % nz)] + x[idx(i, j, (k + nz - 1) % nz)] - 2.0 * x[c];
716 if walled {
719 if j + 1 < ny {
720 acc += x[idx(i, j + 1, k)] - x[c];
721 }
722 if j > 0 {
723 acc += x[idx(i, j - 1, k)] - x[c];
724 }
725 } else {
726 acc += x[idx(i, (j + 1) % ny, k)] + x[idx(i, (j + ny - 1) % ny, k)]
727 - 2.0 * x[c];
728 }
729 y[c] = acc / h2;
730 }
731 }
732 }
733 y
734 }
735}
736
737impl Domain for Channel {
738 fn name(&self) -> &str {
739 &self.name
740 }
741
742 fn kind(&self) -> Kind {
743 Kind::Evolving
744 }
745
746 fn max_stable_dt(&self, _now: Time) -> Time {
747 Time::from_si(
748 self.viscous_limit()
749 .to_si()
750 .min(self.courant_limit().to_si()),
751 )
752 }
753
754 fn step(&mut self, _t: Time, dt: Time, _bus: &mut Exchange) -> Result<(), Violation> {
755 let h = dt.to_si();
756 let limit = self.max_stable_dt(Time::from_si(0.0)).to_si();
757 if h > limit {
758 return Err(Violation {
759 quantity: "flow step".into(),
760 site: self.name.clone(),
761 before: limit,
762 after: h,
763 scale: limit,
764 tolerance: 0.0,
765 });
766 }
767 let re = self.cell_reynolds();
768 if re > CELL_REYNOLDS_LIMIT {
769 return Err(Violation {
770 quantity: "cell Reynolds number".into(),
771 site: self.name.clone(),
772 before: CELL_REYNOLDS_LIMIT,
773 after: re,
774 scale: CELL_REYNOLDS_LIMIT,
775 tolerance: 0.0,
776 });
777 }
778
779 let before = self.kinetic_energy().to_si();
784 self.advance(h);
785 if !self.project(h) {
786 return Err(Violation::at(
787 self.name.clone(),
788 "pressure residual",
789 self.residual,
790 ));
791 }
792 if self.force != DVec3::ZERO {
793 self.driven += self.kinetic_energy().to_si() - before;
794 }
795 Ok(())
796 }
797
798 fn ledger(&self) -> Ledger {
807 Ledger::new()
808 .with(quantity::ENERGY, self.kinetic_energy().to_si())
809 .with(quantity::ENERGY, -self.driven)
810 }
811
812 fn readings(&self) -> Vec<Reading> {
813 let mut out = vec![
814 Reading::new(&self.name, "mean speed", self.mean_speed().to_si(), "m/s"),
815 Reading::new(&self.name, "peak speed", self.peak_speed(), "m/s"),
816 Reading::new(
817 &self.name,
818 "kinetic energy",
819 self.kinetic_energy().to_si(),
820 "J",
821 ),
822 Reading::new(&self.name, "divergence", self.divergence(), "m/s"),
823 Reading::new(&self.name, "cell Reynolds", self.cell_reynolds(), ""),
824 ];
825 if self.force != DVec3::ZERO {
830 out.push(Reading::new(&self.name, "work driven in", self.driven, "J"));
831 }
832 out
833 }
834
835 fn as_field(&self) -> Option<&dyn pantometry_core::ScalarField> {
847 Some(self)
848 }
849
850 fn as_any(&self) -> Option<&dyn std::any::Any> {
851 Some(self)
852 }
853
854 fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
860 Some(self)
861 }
862
863 fn checkpoint(&mut self) {
864 self.saved = Some(Box::new(Saved {
865 u: self.u.clone(),
866 v: self.v.clone(),
867 w: self.w.clone(),
868 p: self.p.clone(),
869 }));
870 self.saved_driven = self.driven;
871 }
872
873 fn restore(&mut self) {
874 if let Some(s) = self.saved.take() {
875 self.u = s.u;
876 self.v = s.v;
877 self.w = s.w;
878 self.p = s.p;
879 self.driven = self.saved_driven;
880 }
881 }
882}