1use crate::geometry::{
7 CartesianIter, Face, FaceArray, HyperBox, IndexSpace, Region, Side, regions,
8};
9use crate::kernel::{
10 Border, BoundaryClass, BoundaryConds, BoundaryKind, Convolution, Interpolant, Kernel, Value,
11 boundary::is_boundary_compatible,
12};
13use std::array::{self, from_fn};
14
15#[derive(Copy, Clone, PartialEq, Eq, Debug)]
16pub enum Support {
17 Interior,
18 Negative(usize),
19 Positive(usize),
20}
21
22pub fn vertex_from_node<const N: usize>(node: [isize; N]) -> [usize; N] {
24 array::from_fn(|axis| node[axis] as usize)
25}
26
27pub fn node_from_vertex<const N: usize>(vertex: [usize; N]) -> [isize; N] {
29 array::from_fn(|axis| vertex[axis] as isize)
30}
31
32#[derive(Debug, Clone)]
34pub struct NodeSpace<const N: usize> {
35 pub size: [usize; N],
37 pub ghost: usize,
39 pub bounds: HyperBox<N>,
41 pub boundary: FaceArray<N, BoundaryClass>,
42}
43
44impl<const N: usize> NodeSpace<N> {
45 pub fn ghost(&self) -> usize {
46 self.ghost
47 }
48
49 pub fn cell_size(&self) -> [usize; N] {
50 self.size
51 }
52
53 pub fn vertex_size(&self) -> [usize; N] {
54 array::from_fn(|axis| self.size[axis] + 1)
55 }
56
57 pub fn node_size(&self) -> [usize; N] {
58 array::from_fn(|axis| self.size[axis] + 1 + 2 * self.ghost)
59 }
60
61 pub fn num_nodes(&self) -> usize {
63 self.node_size().iter().product()
64 }
65
66 pub fn spacing(&self) -> [f64; N] {
68 from_fn(|axis| self.bounds.size[axis] / self.size[axis] as f64)
69 }
70
71 pub fn spacing_axis(&self, axis: usize) -> f64 {
72 self.bounds.size[axis] / self.size[axis] as f64
73 }
74
75 pub fn is_interior(&self, node: [isize; N]) -> bool {
77 (0..N)
78 .map(|axis| node[axis] >= 0 && node[axis] <= self.size[axis] as isize)
79 .all(|b| b)
80 }
81
82 pub fn position(&self, node: [isize; N]) -> [f64; N] {
84 let spacing: [_; N] = from_fn(|axis| self.bounds.size[axis] / self.size[axis] as f64);
85
86 let mut result = [0.0; N];
87
88 for i in 0..N {
89 result[i] = self.bounds.origin[i] + spacing[i] * node[i] as f64;
90 }
91
92 result
93 }
94
95 pub fn index_from_node(&self, node: [isize; N]) -> usize {
97 for axis in 0..N {
98 debug_assert!(node[axis] >= -(self.ghost as isize));
99 debug_assert!(node[axis] <= (self.size[axis] + self.ghost) as isize);
100 }
101
102 let cartesian = array::from_fn(|axis| {
103 let mut vertex = node[axis];
104 vertex += self.ghost as isize;
105 vertex as usize
106 });
107
108 IndexSpace::new(self.node_size()).linear_from_cartesian(cartesian)
109 }
110
111 pub fn index_from_vertex(&self, vertex: [usize; N]) -> usize {
113 self.index_from_node(node_from_vertex(vertex))
114 }
115
116 pub fn inner_window(&self) -> NodeWindow<N> {
118 NodeWindow {
119 origin: [0isize; N],
120 size: self.vertex_size(),
121 }
122 }
123
124 pub fn full_window(&self) -> NodeWindow<N> {
126 let mut origin = [0; N];
127
128 for axis in 0..N {
129 origin[axis] = -(self.ghost as isize);
130 }
131
132 NodeWindow {
133 origin,
134 size: self.node_size(),
135 }
136 }
137
138 pub fn region_window(&self, extent: usize, region: Region<N>) -> NodeWindow<N> {
140 debug_assert!(extent <= self.ghost);
141
142 let origin = array::from_fn(|axis| match region.side(axis) {
143 Side::Left => -(extent as isize),
144 Side::Right => self.size[axis] as isize + 1,
145 Side::Middle => 0,
146 });
147
148 let size = array::from_fn(|axis: usize| match region.side(axis) {
149 Side::Left | Side::Right => extent,
150 Side::Middle => self.size[axis] + 1,
151 });
152
153 NodeWindow { origin, size }
154 }
155
156 pub fn face_window(&self, Face { axis, side }: Face<N>) -> NodeWindow<N> {
158 let intercept = if side { self.size[axis] } else { 0 } as isize;
159
160 let mut origin = [0; N];
161 let mut size = array::from_fn(|axis| self.size[axis] + 1);
162
163 origin[axis] = intercept;
164 size[axis] = 1;
165
166 NodeWindow { origin, size }
167 }
168
169 pub fn face_window_disjoint(&self, face: Face<N>) -> NodeWindow<N> {
170 let intercept = if face.side { self.size[face.axis] } else { 0 } as isize;
171
172 let mut origin = [0; N];
173 let mut size = self.vertex_size();
174
175 origin[face.axis] = intercept;
176 size[face.axis] = 1;
177
178 if face.side {
179 for axis in (0..N).filter(|&axis| axis != face.axis) {
180 origin[axis] += 1;
181 size[axis] -= 1;
182 }
183
184 for axis in 0..face.axis {
185 size[axis] -= 1;
186 }
187 } else {
188 for axis in 0..face.axis {
189 origin[axis] += 1;
190 size[axis] -= 1;
191 }
192 }
193
194 NodeWindow { origin, size }
195 }
196
197 pub fn contains_window(&self, window: NodeWindow<N>) -> bool {
198 (0..N).into_iter().all(|axis| {
199 window.origin[axis] >= -(self.ghost as isize)
200 && window.size[axis] < self.size[axis] + 1 + 2 * self.ghost
201 })
202 }
203
204 fn support_axis(&self, vertex: usize, border_width: usize, axis: usize) -> Support {
205 debug_assert!(self.ghost >= border_width);
206 debug_assert!(self.size[axis] + 1 >= border_width);
207
208 let has_negative = self.boundary[Face::negative(axis)].has_ghost();
209 let has_positive = self.boundary[Face::positive(axis)].has_ghost();
210
211 if !has_negative && vertex < border_width {
212 Support::Negative(vertex as usize)
213 } else if !has_positive && vertex >= (self.size[axis] + 1 - border_width) {
214 Support::Positive(self.size[axis] - vertex as usize)
215 } else {
216 Support::Interior
217 }
218 }
219
220 fn support_cell_axis(&self, cell: usize, border_width: usize, axis: usize) -> Support {
221 debug_assert!(self.ghost >= border_width);
222 debug_assert!(cell < self.size[axis]);
223
224 let has_negative = self.boundary[Face::negative(axis)].has_ghost();
225 let has_positive = self.boundary[Face::positive(axis)].has_ghost();
226
227 if !has_negative && cell < border_width {
228 let right = cell;
229 Support::Negative(right)
230 } else if !has_positive && cell >= self.size[axis] - border_width {
231 let left = self.size[axis] - 1 - cell;
232 Support::Positive(left)
233 } else {
234 Support::Interior
235 }
236 }
237
238 pub fn fill_boundary(&self, extent: usize, cond: impl BoundaryConds<N>, dest: &mut [f64]) {
241 debug_assert!(is_boundary_compatible(&self.boundary, &cond));
242
243 for face in Face::<N>::iterate() {
245 if cond.kind(face) == BoundaryKind::AntiSymmetric {
247 for node in self.face_window_disjoint(face) {
249 let index = self.index_from_node(node);
251 dest[index] = 0.0;
252 }
253 }
254
255 if cond.kind(face) == BoundaryKind::StrongDirichlet {
256 for node in self.face_window_disjoint(face) {
258 let position = self.position(node);
259 let index = self.index_from_node(node);
260 dest[index] = cond.dirichlet(position).target;
261 }
262 }
263 }
264
265 for region in regions::<N>() {
267 if region == Region::CENTRAL {
268 continue;
269 }
270
271 let window = self.region_window(extent, region);
273
274 let mut parity = true;
276
277 for face in region.adjacent_faces() {
278 parity ^= cond.kind(face) == BoundaryKind::AntiSymmetric;
280 }
281
282 let sign = if parity { 1.0 } else { -1.0 };
283
284 for node in window {
285 let mut source = node;
286
287 for face in region.adjacent_faces() {
288 if !matches!(
289 cond.kind(face),
290 BoundaryKind::AntiSymmetric | BoundaryKind::Symmetric
291 ) {
292 continue;
293 }
294
295 if face.side {
296 let dist = node[face.axis] - self.size[face.axis] as isize;
297 source[face.axis] = self.size[face.axis] as isize - dist;
298 } else {
299 source[face.axis] = -node[face.axis];
300 }
301 }
302
303 dest[self.index_from_node(node)] = sign * dest[self.index_from_node(source)];
304 }
305 }
306 }
307
308 pub fn apply(&self, corner: [isize; N], stencils: [&[f64]; N], field: &[f64]) -> f64 {
310 for axis in 0..N {
311 debug_assert!(corner[axis] >= -(self.ghost as isize));
312 debug_assert!(
313 corner[axis] <= (self.size[axis] + 1 + self.ghost - stencils[axis].len()) as isize
314 );
315 }
316
317 let ssize: [_; N] = array::from_fn(|axis| stencils[axis].len());
318
319 let mut result = 0.0;
320
321 for offset in IndexSpace::new(ssize).iter() {
322 let mut weight = 1.0;
323
324 for axis in 0..N {
325 weight *= stencils[axis][offset[axis]];
326 }
327
328 let index =
329 self.index_from_node(array::from_fn(|axis| corner[axis] + offset[axis] as isize));
330 result += field[index] * weight;
331 }
332
333 result
334 }
335
336 pub fn apply_axis(
338 &self,
339 corner: [isize; N],
340 stencil: &[f64],
341 field: &[f64],
342 axis: usize,
343 ) -> f64 {
344 let mut result = 0.0;
345
346 let mut node = corner;
347
348 for (i, weight) in stencil.iter().enumerate() {
349 node[axis] = corner[axis] + i as isize;
350
351 let index = self.index_from_node(node);
352 result += field[index] * weight;
353 }
354
355 result
356 }
357
358 pub fn evaluate_interior(
359 &self,
360 convolution: impl Convolution<N>,
361 node: [isize; N],
362 field: &[f64],
363 ) -> f64 {
364 let spacing = self.spacing();
365 let stencils = array::from_fn(|axis| convolution.interior(axis));
366 let corner = array::from_fn(|axis| node[axis] - convolution.border_width(axis) as isize);
367 self.apply(corner, stencils, field) * convolution.scale(spacing)
368 }
369
370 pub fn evaluate_axis_interior(
371 &self,
372 kernel: impl Kernel,
373 node: [isize; N],
374 field: &[f64],
375 axis: usize,
376 ) -> f64 {
377 let spacing = self.spacing_axis(axis);
378 let stencil = kernel.interior();
379 let mut corner = node;
380 corner[axis] -= kernel.border_width() as isize;
381 self.apply_axis(corner, stencil, field, axis) * kernel.scale(spacing)
382 }
383
384 fn stencils<'a, C: Convolution<N>>(
385 &self,
386 support: [Support; N],
387 convolution: &'a C,
388 ) -> [&'a [f64]; N] {
389 array::from_fn(move |axis| {
390 let border = match support[axis] {
391 Support::Negative(border) => Border::Negative(border),
392 Support::Positive(border) => Border::Positive(border),
393 Support::Interior => {
394 return convolution.interior(axis);
395 }
396 };
397
398 let face = Face {
399 axis,
400 side: border.side(),
401 };
402
403 if self.boundary[face].has_ghost() {
404 convolution.interior(axis)
405 } else {
406 convolution.free(border, axis)
407 }
408 })
409 }
410
411 fn stencil_axis<'a, K: Kernel>(
412 &self,
413 support: Support,
414 kernel: &'a K,
415 axis: usize,
416 ) -> &'a [f64] {
417 let border = match support {
418 Support::Negative(border) => Border::Negative(border),
419 Support::Positive(border) => Border::Positive(border),
420 Support::Interior => {
421 return kernel.interior();
422 }
423 };
424
425 let face = Face {
426 axis,
427 side: border.side(),
428 };
429
430 if self.boundary[face].has_ghost() {
431 kernel.interior()
432 } else {
433 kernel.free(border)
434 }
435 }
436
437 pub fn evaluate(
439 &self,
440 convolution: impl Convolution<N>,
441 node: [isize; N],
442 field: &[f64],
443 ) -> f64 {
444 let spacing = self.spacing();
445 let support = array::from_fn(|axis| {
446 if convolution.border_width(axis) > 0 {
447 debug_assert!(node[axis] >= 0);
448 debug_assert!(node[axis] <= self.size[axis] as isize);
449
450 self.support_axis(node[axis] as usize, convolution.border_width(axis), axis)
451 } else {
452 Support::Interior
453 }
454 });
455 let stencils = self.stencils(support, &convolution);
456 let corner = array::from_fn(|axis| match support[axis] {
457 Support::Interior => node[axis] as isize - convolution.border_width(axis) as isize,
458 Support::Negative(_) => 0,
459 Support::Positive(_) => (self.size[axis] + 1) as isize - stencils[axis].len() as isize,
460 });
461 self.apply(corner, stencils, field) * convolution.scale(spacing)
462 }
463
464 pub fn evaluate_axis(
466 &self,
467 kernel: impl Kernel,
468 node: [isize; N],
469 field: &[f64],
470 axis: usize,
471 ) -> f64 {
472 #[cfg(debug_assertions)]
473 for axis in 0..N {
474 assert!(node[axis] <= (self.size[axis] + self.ghost) as isize);
475 assert!(node[axis] >= -(self.ghost as isize));
476 }
477
478 debug_assert!(node[axis] >= 0);
479 debug_assert!(node[axis] <= self.size[axis] as isize);
480
481 let spacing = self.spacing_axis(axis);
482 let support = self.support_axis(node[axis] as usize, kernel.border_width(), axis);
483 let stencil = self.stencil_axis(support, &kernel, axis);
484
485 let mut corner = node;
486 corner[axis] = match support {
487 Support::Interior => node[axis] - kernel.border_width() as isize,
488 Support::Negative(_) => 0,
489 Support::Positive(_) => (self.size[axis] + 1) as isize - stencil.len() as isize,
490 };
491
492 self.apply_axis(corner, stencil, field, axis) * kernel.scale(spacing)
493 }
494
495 pub fn prolong(&self, kernel: impl Interpolant, supernode: [isize; N], field: &[f64]) -> f64 {
497 for axis in 0..N {
498 debug_assert!(supernode[axis] <= 2 * self.size[axis] as isize);
499 debug_assert!(supernode[axis] >= 0);
500 }
501
502 let node: [_; N] = array::from_fn(|axis| supernode[axis] / 2);
503 let flags: [_; N] = array::from_fn(|axis| supernode[axis] % 2 == 1);
504
505 let mut scale = 1.0;
506
507 for _ in (0..N).filter(|&axis| flags[axis]) {
508 scale *= kernel.scale();
509 }
510
511 let support: [_; N] = array::from_fn(|axis| {
512 if flags[axis] {
513 self.support_cell_axis(node[axis] as usize, kernel.border_width(), axis)
514 } else {
515 Support::Interior
516 }
517 });
518
519 let stencils = array::from_fn(|axis| {
520 if !flags[axis] {
521 return Value.interior();
522 }
523
524 let border = match support[axis] {
525 Support::Interior => return kernel.interior(),
526 Support::Negative(i) => Border::Negative(i),
527 Support::Positive(i) => Border::Positive(i),
528 };
529
530 let face = Face {
531 axis,
532 side: border.side(),
533 };
534
535 if self.boundary[face].has_ghost() {
536 kernel.interior()
537 } else {
538 kernel.free(border)
539 }
540 });
541
542 let corner = array::from_fn(|axis| {
543 if !flags[axis] {
544 return node[axis] as isize;
545 }
546
547 match support[axis] {
548 Support::Interior => node[axis] as isize - kernel.border_width() as isize,
549 Support::Negative(_) => 0,
550 Support::Positive(_) => {
551 self.size[axis] as isize + 1 - stencils[axis].len() as isize
552 }
553 }
554 });
555 self.apply(corner, stencils, field) * scale
556 }
557}
558
559#[derive(Debug, Clone, Copy, PartialEq, Eq)]
565pub struct NodeWindow<const N: usize> {
566 pub origin: [isize; N],
567 pub size: [usize; N],
568}
569
570impl<const N: usize> NodeWindow<N> {
571 pub fn num_nodes(&self) -> usize {
572 self.size.iter().product()
573 }
574
575 pub fn iter(&self) -> NodeCartesianIter<N> {
577 NodeCartesianIter::new(self.origin, self.size)
578 }
579
580 pub fn plane(&self, axis: usize, intercept: isize) -> NodePlaneIter<N> {
582 debug_assert!(
583 intercept >= self.origin[axis]
584 && intercept < self.size[axis] as isize + self.origin[axis]
585 );
586
587 let mut size = self.size;
588 size[axis] = 1;
589
590 NodePlaneIter {
591 axis,
592 intercept,
593 inner: NodeCartesianIter::new(self.origin, size),
594 }
595 }
596}
597
598impl<const N: usize> IntoIterator for NodeWindow<N> {
599 type IntoIter = NodeCartesianIter<N>;
600 type Item = [isize; N];
601
602 fn into_iter(self) -> Self::IntoIter {
603 self.iter()
604 }
605}
606
607pub struct NodeCartesianIter<const N: usize> {
609 origin: [isize; N],
610 inner: CartesianIter<N>,
611}
612
613impl<const N: usize> NodeCartesianIter<N> {
614 pub fn new(origin: [isize; N], size: [usize; N]) -> Self {
615 Self {
616 origin,
617 inner: IndexSpace::new(size).iter(),
618 }
619 }
620}
621
622impl<const N: usize> Iterator for NodeCartesianIter<N> {
623 type Item = [isize; N];
624
625 fn next(&mut self) -> Option<Self::Item> {
626 let index = self.inner.next()?;
627
628 let mut result = self.origin;
629
630 for axis in 0..N {
631 result[axis] += index[axis] as isize;
632 }
633
634 Some(result)
635 }
636}
637
638pub struct NodePlaneIter<const N: usize> {
640 axis: usize,
641 intercept: isize,
642 inner: NodeCartesianIter<N>,
643}
644
645impl<const N: usize> Iterator for NodePlaneIter<N> {
646 type Item = [isize; N];
647
648 fn next(&mut self) -> Option<Self::Item> {
649 let mut index = self.inner.next()?;
650 index[self.axis] = self.intercept;
651 Some(index)
652 }
653}
654
655#[cfg(test)]
656mod tests {
657 use crate::{
658 geometry::HyperBox,
659 kernel::{Derivative, Interpolation},
660 };
661
662 use super::*;
663
664 fn boundary<const N: usize>() -> FaceArray<N, BoundaryClass> {
665 FaceArray::from_fn(|face| {
666 if face.side {
667 BoundaryClass::OneSided
668 } else {
669 BoundaryClass::Ghost
670 }
671 })
672 }
673
674 #[test]
675 fn support_vertex() {
676 let space = NodeSpace {
677 size: [8],
678 ghost: 3,
679 bounds: HyperBox::UNIT,
680 boundary: boundary(),
681 };
682
683 const BORDER: usize = 3;
684
685 let supports = [
686 Support::Interior,
687 Support::Interior,
688 Support::Interior,
689 Support::Interior,
690 Support::Interior,
691 Support::Interior,
692 Support::Positive(2),
693 Support::Positive(1),
694 Support::Positive(0),
695 ];
696
697 for i in 0..9 {
698 assert_eq!(space.support_axis(i, BORDER, 0), supports[i]);
699 }
700 }
701
702 #[test]
703 fn support_cell() {
704 let space = NodeSpace {
705 size: [8],
706 ghost: 3,
707 bounds: HyperBox::UNIT,
708 boundary: boundary(),
709 };
710
711 const BORDER: usize = 3;
712
713 let supports = [
714 Support::Interior,
715 Support::Interior,
716 Support::Interior,
717 Support::Interior,
718 Support::Interior,
719 Support::Positive(2),
720 Support::Positive(1),
721 Support::Positive(0),
722 ];
723
724 for i in 0..8 {
725 assert_eq!(space.support_cell_axis(i, BORDER, 0), supports[i]);
726 }
727 }
728
729 fn eval_convergence(size: [usize; 2]) -> f64 {
730 let space = NodeSpace {
731 size,
732 ghost: 2,
733 bounds: HyperBox::UNIT,
734 boundary: boundary(),
735 };
736
737 let mut field = vec![0.0; space.num_nodes()];
738
739 for node in space.full_window().iter() {
740 let index = space.index_from_node(node);
741 let [x, y] = space.position(node);
742 field[index] = x.sin() * y.sin();
743 }
744
745 let mut result: f64 = 0.0;
746
747 for node in space.inner_window().iter() {
748 let [x, y] = space.position(node);
749 let numerical = space.evaluate((Derivative::<4>, Derivative::<4>), node, &field);
750 let analytical = x.cos() * y.cos();
751 let error: f64 = (numerical - analytical).abs();
753 result = result.max(error);
754 }
755
756 result
757 }
758
759 #[test]
760 fn evaluate() {
761 let error16 = eval_convergence([16, 16]);
762 let error32 = eval_convergence([32, 32]);
763 let error64 = eval_convergence([64, 64]);
764
765 assert!(error16 / error32 >= 16.0);
766 assert!(error32 / error64 >= 16.0);
767 }
768
769 fn prolong_convergence(size: [usize; 2]) -> f64 {
770 let cspace = NodeSpace {
771 size,
772 ghost: 2,
773 boundary: boundary(),
774 bounds: HyperBox::UNIT,
775 };
776 let rspace = NodeSpace {
777 size: size.map(|v| v * 2),
778 ghost: 2,
779 boundary: boundary(),
780 bounds: HyperBox::UNIT,
781 };
782
783 let mut field = vec![0.0; cspace.num_nodes()];
784
785 for node in cspace.full_window().iter() {
786 let index = cspace.index_from_node(node);
787 let [x, y] = cspace.position(node);
788 field[index] = x.sin() * y.sin();
789 }
790
791 let mut result: f64 = 0.0;
792
793 for node in rspace.inner_window().iter() {
794 let [x, y] = rspace.position(node);
795 let numerical = cspace.prolong(Interpolation::<4>, node, &field);
796 let analytical = x.sin() * y.sin();
797 let error: f64 = (numerical - analytical).abs();
798 result = result.max(error);
799 }
800
801 result
802 }
803
804 #[test]
805 fn prolong() {
806 let error16 = prolong_convergence([16, 16]);
807 let error32 = prolong_convergence([32, 32]);
808 let error64 = prolong_convergence([64, 64]);
809
810 assert!(error16 / error32 >= 32.0);
811 assert!(error32 / error64 >= 32.0);
812 }
813
814 #[test]
815 fn windows() {
816 let space = NodeSpace {
817 size: [10; 2],
818 ghost: 2,
819 boundary: boundary(),
820 bounds: HyperBox::UNIT,
821 };
822
823 assert_eq!(
825 space.region_window(2, Region::new([Side::Left, Side::Right])),
826 NodeWindow {
827 origin: [-2, 11],
828 size: [2, 2],
829 }
830 );
831 assert_eq!(
832 space.region_window(2, Region::new([Side::Middle, Side::Right])),
833 NodeWindow {
834 origin: [0, 11],
835 size: [11, 2]
836 }
837 );
838
839 assert_eq!(
841 space.face_window(Face::positive(1)),
842 NodeWindow {
843 origin: [0, 10],
844 size: [11, 1]
845 }
846 );
847
848 assert_eq!(
850 space.face_window_disjoint(Face::negative(0)),
851 NodeWindow {
852 origin: [0, 0],
853 size: [1, 11],
854 }
855 );
856 assert_eq!(
857 space.face_window_disjoint(Face::negative(1)),
858 NodeWindow {
859 origin: [1, 0],
860 size: [10, 1],
861 }
862 );
863 assert_eq!(
864 space.face_window_disjoint(Face::positive(0)),
865 NodeWindow {
866 origin: [10, 1],
867 size: [1, 10],
868 }
869 );
870 assert_eq!(
871 space.face_window_disjoint(Face::positive(1)),
872 NodeWindow {
873 origin: [1, 10],
874 size: [9, 1],
875 }
876 );
877 }
878}