1mod mesh_construction;
5#[doc(hidden)]
8pub use mesh_construction::{tiny_mesh_2d, tiny_mesh_3d};
9
10mod views;
12use views::IndexIter;
13pub use views::{DualCellIter, DualCellView, SimplexIter, SimplexView};
14
15mod subset;
16pub use subset::{Subset, SubsetImpl};
17
18use fixedbitset as fb;
21use nalgebra as na;
22use nalgebra_sparse as nas;
23
24use itertools::izip;
25use std::{cell::OnceCell, collections::HashMap, rc::Rc};
26
27use crate::{
28 cochain::{Cochain, CochainImpl},
29 operator::Scaling,
30 quadrature::Quadrature,
31};
32
33#[derive(Clone, Debug)]
36pub struct SimplicialMesh<const DIM: usize> {
37 pub vertices: Rc<[na::SVector<f64, DIM>]>,
40 pub(crate) simplices: Vec<SimplexCollection<DIM>>,
42 bounds: BoundingBox<DIM>,
43}
44
45#[derive(Clone, Copy, Debug)]
47pub struct BoundingBox<const DIM: usize> {
48 pub min: na::SVector<f64, DIM>,
50 pub max: na::SVector<f64, DIM>,
52}
53
54#[derive(Clone, Debug)]
55pub(crate) struct SimplexCollection<const MESH_DIM: usize> {
56 simplex_size: usize,
58 pub indices: Vec<usize>,
60 index_map: OnceCell<HashMap<Vec<usize>, usize>>,
63 boundary_map: nas::CsrMatrix<i8>,
70 coboundary_map: nas::CsrMatrix<i8>,
75 mesh_boundary: fb::FixedBitSet,
77 custom_subsets: HashMap<String, fb::FixedBitSet>,
79 circumcenters: Rc<[na::SVector<f64, MESH_DIM>]>,
82 barycenters: Rc<[na::SVector<f64, MESH_DIM>]>,
83 barycentric_differentials: OnceCell<Vec<na::SVector<f64, MESH_DIM>>>,
87 volumes: Vec<f64>,
89 dual_volumes: Vec<f64>,
91 orientations: Vec<i8>,
97}
98
99impl<const DIM: usize> Default for SimplexCollection<DIM> {
100 fn default() -> Self {
101 Self {
104 simplex_size: 0,
105 indices: Vec::new(),
106 index_map: OnceCell::new(),
107 boundary_map: nas::CsrMatrix::zeros(0, 0),
108 coboundary_map: nas::CsrMatrix::zeros(0, 0),
109 mesh_boundary: fb::FixedBitSet::default(),
110 custom_subsets: HashMap::new(),
111 circumcenters: Rc::from([]),
112 barycenters: Rc::from([]),
113 barycentric_differentials: OnceCell::new(),
114 volumes: Vec::new(),
115 dual_volumes: Vec::new(),
116 orientations: Vec::new(),
117 }
118 }
119}
120
121impl<const DIM: usize> SimplexCollection<DIM> {
122 #[inline]
124 fn len(&self) -> usize {
125 self.indices.len() / self.simplex_size
126 }
127
128 fn simplex_indices(&self, simplex_idx: usize) -> &[usize] {
130 let start_idx = simplex_idx * self.simplex_size;
131 &self.indices[start_idx..start_idx + self.simplex_size]
132 }
133}
134
135impl<const MESH_DIM: usize> SimplicialMesh<MESH_DIM> {
136 #[inline]
141 pub fn new(vertices: Vec<na::SVector<f64, MESH_DIM>>, indices: Vec<usize>) -> Self {
142 mesh_construction::build_mesh(vertices, indices)
143 }
144
145 #[inline]
147 pub fn simplex_count<const DIM: usize>(&self) -> usize
148 where
149 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
150 {
151 self.simplex_count_dyn(DIM)
152 }
153
154 #[inline]
157 fn simplex_count_dyn(&self, dim: usize) -> usize {
158 self.simplices[dim].len()
159 }
160
161 #[inline]
163 pub fn get_simplex_by_index<const DIM: usize>(
164 &self,
165 idx: usize,
166 ) -> SimplexView<'_, na::Const<DIM>, MESH_DIM>
167 where
168 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
169 {
170 self.get_simplex_by_index_impl::<na::Const<DIM>>(idx)
171 }
172
173 fn get_simplex_by_index_impl<Dim: na::DimName>(
174 &self,
175 idx: usize,
176 ) -> SimplexView<'_, Dim, MESH_DIM> {
177 let start_idx = idx * self.simplices[Dim::USIZE].simplex_size;
178 let idx_range = start_idx..start_idx + self.simplices[Dim::USIZE].simplex_size;
179 SimplexView {
180 mesh: self,
181 index: idx,
182 indices: &self.simplices[Dim::USIZE].indices[idx_range],
183 _marker: std::marker::PhantomData,
184 }
185 }
186
187 #[inline]
189 pub fn vertices(&self) -> &[na::SVector<f64, MESH_DIM>] {
190 &self.vertices
191 }
192
193 pub fn simplices<const DIM: usize>(&self) -> SimplexIter<'_, DIM, MESH_DIM>
195 where
196 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
197 {
198 SimplexIter {
199 mesh: self,
200 idx_iter: IndexIter::All(0..self.simplices[DIM].len()),
201 }
202 }
203
204 pub fn simplices_in<'me, 'sub: 'me, const DIM: usize>(
206 &'me self,
207 subset: &'sub Subset<DIM, Primal>,
208 ) -> SimplexIter<'me, DIM, MESH_DIM>
209 where
210 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
211 {
212 SimplexIter {
213 mesh: self,
214 idx_iter: IndexIter::Subset(subset.indices.ones()),
215 }
216 }
217
218 pub fn dual_cells<const DIM: usize>(&self) -> DualCellIter<'_, DIM, MESH_DIM>
220 where
221 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
222 {
223 DualCellIter {
224 mesh: self,
225 idx_iter: IndexIter::All(0..self.simplices[MESH_DIM - DIM].len()),
226 }
227 }
228
229 pub fn dual_cells_in<'me, 'sub: 'me, const DIM: usize>(
231 &'me self,
232 subset: &'sub Subset<DIM, Dual>,
233 ) -> DualCellIter<'me, DIM, MESH_DIM>
234 where
235 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
236 {
237 DualCellIter {
238 mesh: self,
239 idx_iter: IndexIter::Subset(subset.indices.ones()),
240 }
241 }
242
243 pub fn empty_subset<const DIM: usize, Primality>(&self) -> Subset<DIM, Primality>
248 where
249 Primality: MeshPrimality,
250 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
251 {
252 Subset::<DIM, Primality>::new(fb::FixedBitSet::new())
253 }
254
255 pub fn full_subset<const DIM: usize, Primality>(&self) -> Subset<DIM, Primality>
260 where
261 Primality: MeshPrimality,
262 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
263 {
264 let simplex_count = if Primality::IS_PRIMAL {
265 self.simplex_count_dyn(DIM)
266 } else {
267 self.simplex_count_dyn(MESH_DIM - DIM)
268 };
269 let mut indices = fb::FixedBitSet::with_capacity(simplex_count);
270 indices.set_range(.., true);
271 Subset::<DIM, Primality>::new(indices)
272 }
273
274 pub fn subset_boundary(
279 &self,
280 subset: &Subset<MESH_DIM, Primal>,
281 ) -> SubsetImpl<na::DimNameDiff<na::Const<MESH_DIM>, na::U1>, Primal>
282 where
283 na::Const<MESH_DIM>: na::DimNameSub<na::U1>
286 + na::DimNameSub<na::Const<MESH_DIM>>
287 + na::DimNameSub<na::DimNameSum<na::DimNameDiff<na::Const<MESH_DIM>, na::U1>, na::U1>>
288 + na::DimNameSub<na::DimNameDiff<na::Const<MESH_DIM>, na::U1>>,
289 na::DimNameDiff<na::Const<MESH_DIM>, na::U1>: na::DimNameAdd<na::U1>,
290 {
291 let simplices = self
292 .simplices_in::<MESH_DIM>(subset)
293 .flat_map(|s| s.boundary().map(|(_, b)| b))
294 .filter(|b| {
295 b.coboundary()
296 .filter(|(_, cob)| subset.indices.contains(cob.index()))
297 .count()
298 == 1
299 });
300 SubsetImpl::<na::DimNameDiff<na::Const<MESH_DIM>, na::U1>, Primal>::from_simplex_iter(
301 simplices,
302 )
303 }
304
305 pub fn subset_complement<const DIM: usize, Primality>(
312 &self,
313 subset: &Subset<DIM, Primality>,
314 ) -> Subset<DIM, Primality>
315 where
316 Primality: MeshPrimality,
317 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
318 {
319 let mut indices = self.full_subset::<DIM, Primality>().indices;
320 indices.set_range(.., true);
321 indices.difference_with(&subset.indices);
322 Subset::<DIM, Primality>::new(indices)
323 }
324
325 #[inline]
329 pub fn indices<const DIM: usize>(&self) -> std::slice::ChunksExact<'_, usize>
330 where
331 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
332 {
333 self.simplices[DIM].indices.chunks_exact(DIM + 1)
334 }
335
336 #[inline]
338 pub fn barycenters<const DIM: usize>(&self) -> &[na::SVector<f64, MESH_DIM>]
339 where
340 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
341 {
342 &self.simplices[DIM].barycenters
343 }
344
345 #[inline]
347 pub fn bounds(&self) -> BoundingBox<MESH_DIM> {
348 self.bounds
349 }
350
351 pub fn new_zero_cochain<const DIM: usize, Primality>(&self) -> crate::Cochain<DIM, Primality>
356 where
357 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
358 Primality: MeshPrimality,
359 {
360 let primal_dim = if Primality::IS_PRIMAL {
361 DIM
362 } else {
363 MESH_DIM - DIM
364 };
365 crate::Cochain::zeros(self.simplex_count_dyn(primal_dim))
366 }
367
368 pub fn integrate_cochain<const DIM: usize, Primality>(
380 &self,
381 quadrature: impl Quadrature<DIM, MESH_DIM>,
382 ) -> crate::Cochain<DIM, Primality>
383 where
384 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
385 Primality: MeshPrimality,
386 {
387 let cell_count = if Primality::IS_PRIMAL {
388 self.simplex_count_dyn(DIM)
389 } else {
390 self.simplex_count_dyn(MESH_DIM - DIM)
391 };
392 self.integrate_cochain_impl(IndexIter::All(0..cell_count), quadrature)
393 }
394
395 pub fn integrate_cochain_partial<const DIM: usize, Primality>(
401 &self,
402 subset: &Subset<DIM, Primality>,
403 quadrature: impl Quadrature<DIM, MESH_DIM>,
404 ) -> crate::Cochain<DIM, Primality>
405 where
406 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
407 Primality: MeshPrimality,
408 {
409 self.integrate_cochain_impl(IndexIter::Subset(subset.indices.ones()), quadrature)
410 }
411
412 pub fn integrate_overwrite<const DIM: usize, Primality>(
418 &self,
419 cochain: &mut Cochain<DIM, Primality>,
420 subset: &Subset<DIM, Primality>,
421 quadrature: impl Quadrature<DIM, MESH_DIM>,
422 ) where
423 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>> + Copy,
424 Primality: MeshPrimality + Copy,
425 {
426 cochain.overwrite(subset, &self.integrate_cochain_partial(subset, quadrature));
427 }
428
429 fn integrate_cochain_impl<const DIM: usize, Primality>(
430 &self,
431 idx_iter: IndexIter,
432 quadrature: impl Quadrature<DIM, MESH_DIM>,
433 ) -> crate::Cochain<DIM, Primality>
434 where
435 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
436 Primality: MeshPrimality,
437 {
438 let mut c = self.new_zero_cochain::<DIM, Primality>();
439
440 match (DIM, Primality::IS_PRIMAL) {
441 (0, true) => {
443 for idx in idx_iter {
444 c.values[idx] = quadrature.compute(&[self.vertices[idx]]);
445 }
446 }
447 (0, false) => {
448 for idx in idx_iter {
450 c.values[idx] =
451 quadrature.compute(&[self.simplices[MESH_DIM].circumcenters[idx]]);
452 }
453 }
454 (_, true) => {
457 let mut vertices = Vec::new();
461
462 for idx in idx_iter {
463 let simplex = self.get_simplex_by_index::<DIM>(idx);
464 vertices.extend(simplex.vertices());
465 c.values[idx] = quadrature.compute(&vertices);
466 vertices.clear();
467 }
468 }
469 (_, false) => {
470 let mut vertices = Vec::new();
471 let primal_dim = MESH_DIM - DIM;
472 for idx in idx_iter {
476 let cob_row = self.simplices[primal_dim].coboundary_map.row(idx);
477 for &cob_idx in cob_row.col_indices() {
478 let cob_circumcenter =
479 self.simplices[primal_dim + 1].circumcenters[cob_idx];
480 vertices.push(cob_circumcenter);
481 }
482 if DIM == 1 && vertices.len() <= 1 {
486 vertices.push(self.simplices[primal_dim].circumcenters[idx])
487 }
488
489 c.values[idx] = quadrature.compute(&vertices);
490 vertices.clear();
491 }
492 }
493 }
494
495 c
496 }
497
498 pub fn d<const DIM: usize, Primality>(&self) -> crate::ExteriorDerivative<DIM, Primality>
502 where
503 na::Const<DIM>: na::DimNameAdd<na::U1>,
504 na::Const<MESH_DIM>:
505 na::DimNameSub<na::DimNameSum<na::Const<DIM>, na::U1>> + na::DimNameSub<na::Const<DIM>>,
506 Primality: MeshPrimality,
507 {
508 let orientation_mat = if Primality::IS_PRIMAL {
509 &self.simplices[DIM + 1].boundary_map
510 } else {
511 &self.simplices[MESH_DIM - DIM - 1].coboundary_map
512 };
513 let float_mat = nas::CsrMatrix::try_from_pattern_and_values(
518 orientation_mat.pattern().clone(),
519 orientation_mat
520 .values()
521 .iter()
522 .map(|o| *o as isize as f64)
523 .collect(),
524 )
525 .unwrap();
526 crate::ExteriorDerivative::new(float_mat)
527 }
528
529 pub fn star<const DIM: usize, Primality>(&self) -> crate::HodgeStar<DIM, MESH_DIM, Primality>
533 where
534 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
535 Primality: MeshPrimality,
536 {
537 let primal_dim = if Primality::IS_PRIMAL {
538 DIM
539 } else {
540 MESH_DIM - DIM
541 };
542 let simplex_count = self.simplex_count_dyn(primal_dim);
543
544 let compute_diag_val = if Primality::IS_PRIMAL {
545 |(primal_vol, dual_vol): (&f64, &f64)| *dual_vol / *primal_vol
546 } else {
547 |(primal_vol, dual_vol): (&f64, &f64)| *primal_vol / *dual_vol
548 };
549
550 let diag = na::DVector::from_iterator(
551 simplex_count,
552 izip!(
553 &self.simplices[primal_dim].volumes,
554 &self.simplices[primal_dim].dual_volumes
555 )
556 .map(compute_diag_val),
557 );
558
559 let diag = if !Primality::IS_PRIMAL && primal_dim * (MESH_DIM - primal_dim) % 2 != 0 {
563 -diag
564 } else {
565 diag
566 };
567
568 crate::HodgeStar::new(diag)
569 }
570
571 pub fn scaling<const DIM: usize>(
577 &self,
578 elem_scaling: impl Fn(SimplexView<na::Const<DIM>, MESH_DIM>) -> f64,
579 ) -> Scaling<DIM, Primal>
580 where
581 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
582 {
583 let diag = na::DVector::from_iterator(
584 self.simplex_count::<DIM>(),
585 self.simplices::<DIM>().map(elem_scaling),
586 );
587 Scaling::new(diag)
588 }
589
590 pub fn scaling_dual<const DIM: usize>(
596 &self,
597 elem_scaling: impl Fn(DualCellView<na::Const<DIM>, MESH_DIM>) -> f64,
598 ) -> Scaling<DIM, Dual>
599 where
600 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
601 {
602 let diag = na::DVector::from_iterator(
603 self.simplex_count_dyn(MESH_DIM - DIM),
604 self.dual_cells::<DIM>().map(elem_scaling),
605 );
606 Scaling::new(diag)
607 }
608
609 pub fn wedge<const D1: usize, const D2: usize>(
620 &self,
621 c1: &Cochain<D1, Primal>,
622 c2: &Cochain<D2, Primal>,
623 ) -> CochainImpl<na::DimNameSum<na::Const<D1>, na::Const<D2>>, Primal>
624 where
625 na::Const<D1>: na::DimNameAdd<na::Const<D2>>,
626 na::Const<MESH_DIM>: na::DimNameSub<na::DimNameSum<na::Const<D1>, na::Const<D2>>>,
627 na::Const<MESH_DIM>: na::DimNameSub<na::Const<D1>>,
628 na::Const<MESH_DIM>: na::DimNameSub<na::Const<D2>>,
629 {
630 let ret_dim: usize = D1 + D2;
631 let ret_simplex_count = self.simplex_count_dyn(ret_dim);
632 let mut ret = CochainImpl::zeros(ret_simplex_count);
633
634 let permutations = crate::permutation::Permutations::new(ret_dim + 1);
635 let scaling = 1. / (1..=(D1 + D2 + 1)).product::<usize>() as f64;
637
638 let mut left_indices: Vec<usize> = Vec::with_capacity(D1 + 1);
641 let mut right_indices: Vec<usize> = Vec::with_capacity(D2 + 1);
642
643 for simplex in (0..ret_simplex_count).map(|i| self.get_simplex_by_index_impl(i)) {
646 let simplex_orientation = if ret_dim < MESH_DIM {
651 1.
652 } else {
653 self.simplices[MESH_DIM].orientations[simplex.index()] as f64
654 };
655 let mut sum = 0.;
656 for perm in permutations.iter() {
657 left_indices.extend(perm.indices[..=D1].iter().map(|i| simplex.indices[*i]));
663 right_indices.extend(perm.indices[D1..].iter().map(|i| simplex.indices[*i]));
664 let left_parity = crate::permutation::get_parity(&left_indices) as f64;
665 let right_parity = crate::permutation::get_parity(&right_indices) as f64;
666
667 left_indices.sort();
674 right_indices.sort();
675 let left_simplex = self
676 .find_simplex_index_impl::<na::Const<D1>>(&left_indices)
677 .unwrap();
678 let right_simplex = self
679 .find_simplex_index_impl::<na::Const<D2>>(&right_indices)
680 .unwrap();
681
682 sum += perm.sign as f64
683 * simplex_orientation
684 * (left_parity
685 * c1.values[left_simplex]
686 * right_parity
687 * c2.values[right_simplex]);
688
689 left_indices.clear();
690 right_indices.clear();
691 }
692 ret[simplex] = scaling * sum;
693 }
694
695 ret
696 }
697
698 #[inline]
703 pub fn boundary<const DIM: usize>(&self) -> Subset<DIM, Primal>
704 where
705 na::Const<DIM>: na::DimNameAdd<na::U1>,
706 na::Const<MESH_DIM>: na::DimNameSub<na::DimNameSum<na::Const<DIM>, na::U1>>,
707 {
708 Subset::new(self.simplices[DIM].mesh_boundary.clone())
709 }
710
711 #[inline]
718 pub fn get_subset<const DIM: usize>(&self, name: &str) -> Option<Subset<DIM, Primal>>
719 where
720 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
721 {
722 let indices = self.simplices[DIM].custom_subsets.get(name)?.clone();
723 Some(Subset::<DIM, Primal>::new(indices))
724 }
725
726 #[inline]
729 pub(crate) fn store_subset<const DIM: usize>(
730 &mut self,
731 name: &str,
732 subset: Subset<DIM, Primal>,
733 ) {
734 self.simplices[DIM]
735 .custom_subsets
736 .insert(name.to_string(), subset.indices);
737 }
738
739 #[inline]
748 pub fn find_simplex_index<const DIM: usize>(&self, indices: &[usize]) -> Option<usize>
749 where
750 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
751 {
752 self.find_simplex_index_impl(indices)
753 }
754
755 fn find_simplex_index_impl<Dim>(&self, indices: &[usize]) -> Option<usize>
756 where
757 Dim: na::DimName,
758 na::Const<MESH_DIM>: na::DimNameSub<Dim>,
759 {
760 if Dim::USIZE == 0 {
762 return Some(indices[0]);
763 }
764
765 let simplices = &self.simplices[Dim::USIZE];
766 let index_map = simplices.index_map.get_or_init(|| {
767 simplices
768 .indices
769 .chunks_exact(simplices.simplex_size)
770 .enumerate()
771 .map(|(i, vert_is)| (Vec::from(vert_is), i))
772 .collect()
773 });
774 index_map.get(indices).copied()
775 }
776
777 pub fn barycentric_differentials<const DIM: usize>(
783 &self,
784 ) -> std::slice::ChunksExact<na::SVector<f64, MESH_DIM>>
785 where
786 na::Const<MESH_DIM>: na::DimNameSub<na::Const<DIM>>,
787 na::Const<DIM>: na::DimNameSub<na::U1>,
788 {
789 let simplices = &self.simplices[DIM];
790 let bary_diffs = simplices.barycentric_differentials.get_or_init(|| {
791 mesh_construction::compute_barycentric_differentials::<DIM, MESH_DIM>(self)
792 });
793 bary_diffs.chunks_exact(simplices.simplex_size)
794 }
795
796 pub fn barycentric_interpolate_1(
801 &self,
802 c: &crate::Cochain<1, Primal>,
803 ) -> Vec<na::SVector<f64, MESH_DIM>>
804 where
805 na::Const<MESH_DIM>:
806 na::DimNameSub<na::U1> + na::DimNameSub<na::U2> + na::DimNameSub<na::Const<MESH_DIM>>,
807 {
808 let mut whitney_vals = Vec::new();
809
810 let top_simplices = &self.simplices[MESH_DIM];
811 for (indices, bary_diffs) in izip!(
812 top_simplices
813 .indices
814 .chunks_exact(top_simplices.simplex_size),
815 self.barycentric_differentials::<MESH_DIM>(),
816 ) {
817 let mut whitney_val = na::SVector::zeros();
818 for (start, end) in
821 (0..indices.len() - 1).flat_map(|s| (s + 1..indices.len()).map(move |e| (s, e)))
822 {
823 let edge_indices = [indices[start], indices[end]];
824 let cochain_idx = self.find_simplex_index::<1>(&edge_indices).unwrap();
825 whitney_val += c.values[cochain_idx] * (bary_diffs[start] - bary_diffs[end]);
828 }
829 whitney_val /= MESH_DIM as f64;
830 whitney_vals.push(whitney_val);
831 }
832
833 whitney_vals
834 }
835}
836
837#[derive(Clone, Copy, Debug)]
844pub struct Primal;
845
846#[derive(Clone, Copy, Debug)]
849pub struct Dual;
850
851pub trait MeshPrimality {
858 #[doc(hidden)]
864 const IS_PRIMAL: bool;
865 #[doc(hidden)]
867 type Opposite: MeshPrimality;
868}
869
870impl MeshPrimality for Primal {
871 const IS_PRIMAL: bool = true;
872 type Opposite = Dual;
873}
874
875impl MeshPrimality for Dual {
876 const IS_PRIMAL: bool = false;
877 type Opposite = Primal;
878}
879
880#[cfg(test)]
881mod tests {
882 use super::*;
883
884 #[test]
886 fn wedge() {
887 let mesh_2d = tiny_mesh_2d();
888
889 let mut c0 = mesh_2d.new_zero_cochain::<0, Primal>();
892 let mut c1 = mesh_2d.new_zero_cochain::<1, Primal>();
893 let mut c1_2 = mesh_2d.new_zero_cochain::<1, Primal>();
896 let mut c2 = mesh_2d.new_zero_cochain::<2, Primal>();
897
898 for vals in [
899 c0.values.iter_mut(),
900 c1.values.iter_mut(),
901 c2.values.iter_mut(),
902 ] {
903 for (i, v) in vals.enumerate() {
904 *v = i as f64;
905 }
906 }
907 for (i, v) in c1_2.values.iter_mut().rev().enumerate() {
908 *v = i as f64;
909 }
910
911 let wedge_00 = mesh_2d.wedge(&c0, &c0);
912 let expected_00: Cochain<0, Primal> =
913 Cochain::from_values(vec![0., 1., 4., 9., 16., 25., 36.].into());
914 assert_eq!(
915 wedge_00, expected_00,
916 "0-cochains should be pointwise multiplied by wedge"
917 );
918
919 let wedge_01 = mesh_2d.wedge(&c0, &c1);
920 let wedge_10 = mesh_2d.wedge(&c1, &c0);
921 assert_eq!(wedge_01, wedge_10, "wedge with 0-cochain should commute");
922 let expected_01: Cochain<1, Primal> = Cochain::from_values(
923 vec![0., 1., 3., 6., 10., 12.5, 21., 24.5, 32., 40.5, 50., 60.5].into(),
924 );
925 assert_eq!(wedge_01, expected_01, "wrong result from 0-1 wedge");
926
927 let wedge_11_self = mesh_2d.wedge(&c1, &c1);
928 let expected_11: Cochain<2, Primal> = mesh_2d.new_zero_cochain();
929 assert_eq!(
930 wedge_11_self, expected_11,
931 "wedge of 1-cochain with itself should be zero"
932 );
933
934 let wedge_11_flipped = mesh_2d.wedge(&c1_2, &c1);
935 let wedge_11 = mesh_2d.wedge(&c1, &c1_2);
936 assert_eq!(
937 wedge_11, -wedge_11_flipped,
938 "wedge of 1-cochains should anticommute"
939 );
940 let expected_11: Cochain<2, Primal> =
941 Cochain::from_values(vec![-14. - 2. / 3., 11., -14. - 2. / 3., 11., -11., 11.].into());
942 assert_eq!(wedge_11, expected_11, "wrong result from 1-1 wedge");
943
944 }
948}