extern crate nalgebra as na;
extern crate nalgebra_sparse as nas;
use formoniq::whitney_complex::WhitneyComplex;
use simplicial::{
gen::cartesian::CartesianGrid,
linalg::{Matrix, Vector},
Dim,
};
use std::sync::LazyLock;
fn kronecker_sum<T>(mats: &[Matrix<T>]) -> Matrix<T>
where
T: na::Scalar + num_traits::Zero + num_traits::One + na::ClosedMulAssign + na::ClosedAddAssign,
{
assert!(!mats.is_empty());
assert!(mats.iter().all(|m| m.nrows() == m.ncols()));
let eyes: Vec<_> = mats
.iter()
.map(|m| Matrix::identity(m.nrows(), m.nrows()))
.collect();
let kron_size = mats.iter().map(na::Matrix::nrows).product::<usize>();
let mut kron_sum = Matrix::zeros(kron_size, kron_size);
for (dim, mat) in mats.iter().enumerate() {
let eyes_before = eyes[..dim]
.iter()
.fold(Matrix::identity(1, 1), |prod, eye| prod.kronecker(eye));
let eyes_after = eyes[dim + 1..]
.iter()
.fold(Matrix::identity(1, 1), |prod, eye| prod.kronecker(eye));
let kron_prod = eyes_before.kronecker(mat).kronecker(&eyes_after);
kron_sum += kron_prod;
}
kron_sum
}
fn matrix_from_const_diagonals<T>(
values: &[T],
offsets: &[isize],
nrows: usize,
ncols: usize,
) -> Matrix<T>
where
T: num_traits::Zero + na::Scalar + Copy,
{
let mut matrix = Matrix::zeros(nrows, ncols);
for (idiag, &offset) in offsets.iter().enumerate() {
let [start_row, start_col] = if offset >= 0 {
[0, offset as usize]
} else {
[(-offset) as usize, 0]
};
let mut r = start_row;
let mut c = start_col;
while r < nrows && c < ncols {
matrix[(r, c)] = values[idiag];
r += 1;
c += 1;
}
}
matrix
}
#[rustfmt::skip]
static LAPLACE_MATRICES_INTERIOR: LazyLock<[Matrix<i32>; 4]> = LazyLock::new(|| [
Matrix::from_row_slice(1, 1, &[
2
]),
Matrix::from_row_slice(2, 2, &[
2,-1,
-1, 2
]),
Matrix::from_row_slice(4, 4, &[
4,-1,-1, 0,
-1, 4, 0,-1,
-1, 0, 4,-1,
0,-1,-1, 4,
]),
Matrix::from_row_slice(8, 8, &[
6,-1,-1, 0,-1, 0, 0, 0,
-1, 6, 0,-1, 0,-1, 0, 0,
-1, 0, 6,-1, 0, 0,-1, 0,
0,-1,-1, 6, 0, 0, 0,-1,
-1, 0, 0, 0, 6,-1,-1, 0,
0,-1, 0, 0,-1, 6, 0,-1,
0, 0,-1, 0,-1, 0, 6,-1,
0, 0, 0,-1, 0,-1,-1, 6,
]),
]);
#[test]
fn fdm_vs_handchecked_interior() {
for (dim, handchecked) in LAPLACE_MATRICES_INTERIOR.iter().enumerate().skip(1) {
let fdm = ndimensionalize_operator(|_| laplace_matrix_1d_interior(2), &vec![1; dim]);
let diff = &fdm - handchecked;
assert!(diff.iter().all(|&e| e == 0));
}
}
fn ndimensionalize_operator<F>(f: F, vertex_counts: &[usize]) -> Matrix<i32>
where
F: Fn(usize) -> Matrix<i32>,
{
let lapls: Vec<_> = vertex_counts
.iter()
.map(|&nvertices| f(nvertices))
.collect();
kronecker_sum(&lapls)
}
fn laplace_matrix_1d_interior(nvertices: usize) -> Matrix<i32> {
let stencil = [-1, 2, -1];
matrix_from_const_diagonals(&stencil[..], &[-1, 0, 1], nvertices, nvertices)
}
#[test]
fn feec_vs_fdm_interior() {
for nboxes_per_dim in 1..=3 {
for dim in 1..=4 {
let nvertices_per_dim = nboxes_per_dim + 1;
let feec = feec_galmat_interior(dim, nboxes_per_dim);
let fdm = ndimensionalize_operator(laplace_matrix_1d_interior, &vec![nvertices_per_dim; dim]);
assert_eq!(feec, fdm, "dim={dim} nboxes_per_dim={nboxes_per_dim}");
}
}
}
fn feec_galmat_interior(dim: Dim, mut nboxes_per_dim: usize) -> Matrix<i32> {
nboxes_per_dim += 2;
let full_galmat = feec_galmat_full(dim, nboxes_per_dim);
let boundary_vertices =
CartesianGrid::new_unit_scaled(dim, nboxes_per_dim, nboxes_per_dim as f64).boundary_vertices();
let galmat = full_galmat
.remove_columns_at(&boundary_vertices)
.remove_rows_at(&boundary_vertices);
cast_int(galmat)
}
fn feec_galmat_full(dim: Dim, nboxes_axis: usize) -> Matrix {
let grid = CartesianGrid::new_unit_scaled(dim, nboxes_axis, nboxes_axis as f64);
let (topology, coords) = grid.triangulate();
let metric = coords.to_edge_lengths_sq(&topology);
let whitney = WhitneyComplex::new(&topology, &metric);
let mut galmat = Matrix::from(&whitney.codif_dif(0));
let mass = Matrix::from(&whitney.mass(0));
let mut galvec = mass * Vector::from_element(topology.vertices().len(), 1.0);
normalize_galerkin_lse(&mut galmat, &mut galvec);
galmat
}
fn normalize_galerkin_lse(galmat: &mut Matrix, galvec: &mut Vector) {
for (mut galmat_row, galvec_entry) in galmat.row_iter_mut().zip(galvec.iter_mut()) {
galmat_row /= *galvec_entry;
*galvec_entry = 1.0;
}
}
fn cast_int(mat: Matrix) -> Matrix<i32> {
const TOL: f64 = 10e-12;
assert!(
mat.iter().all(|e| (e - e.round()).abs() <= TOL),
"Failed to round matrix:\n{mat:.2}"
);
mat.map(f64::round).try_cast().unwrap()
}