conspire 0.7.1

The Rust interface to conspire.
Documentation
use super::WriteHtg;
use crate::geometry::{
    grid::Voxels,
    ntree::{
        Balancing, Octree, Pairing, Quadtree, Rescaling,
        node::{Kind, Node},
    },
};
use std::fs::read_to_string;

fn octree() -> Octree<u16, usize> {
    let mut octree = Octree::<u16, usize> {
        balanced: Balancing::None,
        nodes: vec![Node {
            corner: [0, 0, 0],
            length: 8,
            facets: [None; 6],
            kind: Kind::Leaf,
            value: None,
        }],
        paired: Pairing::None,
        rescale: Rescaling {
            center: [4.0, 4.0, 4.0],
            cell: 1.0,
            half: 4.0,
        },
    };
    octree.subdivide(0).unwrap();
    octree
}

fn quadtree() -> Quadtree<u16, usize> {
    let mut quadtree = Quadtree::<u16, usize> {
        balanced: Balancing::None,
        nodes: vec![Node {
            corner: [0, 0],
            length: 8,
            facets: [None; 4],
            kind: Kind::Leaf,
            value: None,
        }],
        paired: Pairing::None,
        rescale: Rescaling {
            center: [4.0, 4.0],
            cell: 1.0,
            half: 4.0,
        },
    };
    quadtree.subdivide(0).unwrap();
    quadtree
}

fn unbase64(text: &str) -> Vec<u8> {
    let value = |c: u8| match c {
        b'A'..=b'Z' => (c - b'A') as u32,
        b'a'..=b'z' => (c - b'a' + 26) as u32,
        b'0'..=b'9' => (c - b'0' + 52) as u32,
        b'+' => 62,
        b'/' => 63,
        _ => 0,
    };
    let chars: Vec<u8> = text.bytes().filter(|b| *b != b'=').collect();
    let mut out = Vec::new();
    for chunk in chars.chunks(4) {
        let mut block = 0u32;
        for &c in chunk {
            block = (block << 6) | value(c);
        }
        block <<= 6 * (4 - chunk.len());
        for shift in (0..chunk.len() - 1).map(|i| 16 - 8 * i) {
            out.push((block >> shift) as u8);
        }
    }
    out
}

fn bytes(contents: &str, name: &str) -> Vec<u8> {
    let at = contents.find(&format!("Name=\"{name}\"")).unwrap();
    let key = "format=\"binary\">";
    let start = contents[at..].find(key).unwrap() + at + key.len();
    let end = contents[start..].find('<').unwrap() + start;
    unbase64(&contents[start..end]).split_off(8)
}

fn ints(contents: &str, name: &str) -> Vec<i64> {
    bytes(contents, name)
        .chunks(8)
        .map(|b| i64::from_le_bytes(b.try_into().unwrap()))
        .collect()
}

fn floats(contents: &str, name: &str) -> Vec<f64> {
    bytes(contents, name)
        .chunks(8)
        .map(|b| f64::from_le_bytes(b.try_into().unwrap()))
        .collect()
}

fn descriptor_bits(contents: &str, nbits: usize) -> Vec<u8> {
    let packed = bytes(contents, "Descriptor");
    (0..nbits)
        .map(|i| packed[i / 8] >> (7 - i % 8) & 1)
        .collect()
}

#[test]
fn octree_once() {
    let path = "target/octree.htg";
    octree().write_htg(path).unwrap();
    let contents = read_to_string(path).unwrap();
    assert!(contents.contains("Dimensions=\"2 2 2\""));
    assert!(contents.contains("NumberOfLevels=\"2\" NumberOfVertices=\"9\""));
    assert_eq!(floats(&contents, "XCoordinates"), [0.0, 8.0]);
    assert_eq!(ints(&contents, "NbVerticesByLevel"), [1, 8]);
    assert_eq!(descriptor_bits(&contents, 1), [1]);
    assert_eq!(ints(&contents, "Depth"), [0, 1, 1, 1, 1, 1, 1, 1, 1]);
}

#[test]
fn octree_asymmetric() {
    let mut octree = octree();
    let children = *octree.nodes[0].orthants().unwrap();
    for &child in &[children[1], children[2], children[4]] {
        octree.subdivide(child).unwrap();
    }
    let path = "target/octree_asymmetric.htg";
    octree.write_htg(path).unwrap();
    let contents = read_to_string(path).unwrap();
    assert!(contents.contains("NumberOfLevels=\"3\" NumberOfVertices=\"33\""));
    assert_eq!(ints(&contents, "NbVerticesByLevel"), [1, 8, 24]);
    assert_eq!(descriptor_bits(&contents, 9), [1, 0, 1, 1, 0, 1, 0, 0, 0]);
}

#[test]
fn geometric_tree_has_no_value_array() {
    let path = "target/octree_no_value.htg";
    octree().write_htg(path).unwrap();
    assert!(!read_to_string(path).unwrap().contains("Name=\"Value\""));
}

#[test]
fn valued_octree_writes_value_array() {
    let data: Vec<u8> = (1..=8).collect();
    let octree = Octree::<u16, usize, u8>::from(Voxels::new(data, [2, 2, 2]));
    let path = "target/octree_value.htg";
    octree.write_htg(path).unwrap();
    let contents = read_to_string(path).unwrap();
    let values = floats(&contents, "Value");
    assert!(values[0].is_nan());
    assert_eq!(values[1..], [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]);
}

#[test]
fn quadtree_once() {
    let path = "target/quadtree.htg";
    quadtree().write_htg(path).unwrap();
    let contents = read_to_string(path).unwrap();
    assert!(contents.contains("Dimensions=\"2 2 1\""));
    assert!(contents.contains("NumberOfLevels=\"2\" NumberOfVertices=\"5\""));
    assert_eq!(floats(&contents, "XCoordinates"), [0.0, 8.0]);
    assert_eq!(floats(&contents, "ZCoordinates"), [0.0]);
    assert_eq!(ints(&contents, "NbVerticesByLevel"), [1, 4]);
    assert_eq!(descriptor_bits(&contents, 1), [1]);
    assert_eq!(ints(&contents, "Depth"), [0, 1, 1, 1, 1]);
}