conspire 0.7.1

The Rust interface to conspire.
Documentation
#[cfg(test)]
mod test;

use crate::{
    geometry::mesh::{Connectivity, Mesh},
    math::Tensor,
};
use std::{
    fs::File,
    io::{BufWriter, Error, ErrorKind, Result, Write},
    path::Path,
};

pub trait WriteVtu<P>
where
    P: AsRef<Path>,
{
    fn write_vtu(&self, output: P) -> Result<()>;
}

fn cell_type(connectivity: &Connectivity) -> Result<u8> {
    Ok(match connectivity {
        Connectivity::Triangular(_) => 5,
        Connectivity::Quadrilateral(_) => 9,
        Connectivity::Tetrahedral(_) => 10,
        Connectivity::Hexahedral(_) => 12,
        Connectivity::Wedge(_) => 13,
        Connectivity::Pyramidal(_) => 14,
        Connectivity::Polygonal(_) | Connectivity::Polyhedral(_) => {
            return Err(Error::new(
                ErrorKind::Unsupported,
                "VTU writer does not support polygonal/polyhedral blocks",
            ));
        }
    })
}

impl<const D: usize, P> WriteVtu<P> for Mesh<D>
where
    P: AsRef<Path>,
{
    fn write_vtu(&self, output: P) -> Result<()> {
        if D != 2 && D != 3 {
            return Err(Error::new(
                ErrorKind::Unsupported,
                "VTU supports only 2D or 3D meshes",
            ));
        }
        let coordinates = self.coordinates();
        let mut points = Vec::with_capacity(coordinates.len() * 3 * 8);
        for node in 0..coordinates.len() {
            for i in 0..3 {
                let value = if i < D { coordinates[node][i] } else { 0.0 };
                points.extend_from_slice(&value.to_le_bytes());
            }
        }
        let mut connectivity = Vec::new();
        let mut offsets = Vec::new();
        let mut types = Vec::new();
        let mut offset: i64 = 0;
        for block in self.iter() {
            let cell = cell_type(block)?;
            for element in block.iter() {
                for &node in element {
                    connectivity.extend_from_slice(&(node as i64).to_le_bytes());
                }
                offset += element.len() as i64;
                offsets.extend_from_slice(&offset.to_le_bytes());
                types.push(cell);
            }
        }
        let mut file = BufWriter::new(File::create(output)?);
        writeln!(file, "<?xml version=\"1.0\"?>")?;
        writeln!(
            file,
            "<VTKFile type=\"UnstructuredGrid\" version=\"1.0\" byte_order=\"LittleEndian\" header_type=\"UInt64\">"
        )?;
        writeln!(file, "  <UnstructuredGrid>")?;
        writeln!(
            file,
            "    <Piece NumberOfPoints=\"{}\" NumberOfCells=\"{}\">",
            coordinates.len(),
            types.len()
        )?;
        writeln!(file, "      <Points>")?;
        writeln!(
            file,
            "        <DataArray type=\"Float64\" NumberOfComponents=\"3\" format=\"binary\">{}</DataArray>",
            data_array(&points)
        )?;
        writeln!(file, "      </Points>")?;
        writeln!(file, "      <Cells>")?;
        writeln!(
            file,
            "        <DataArray type=\"Int64\" Name=\"connectivity\" format=\"binary\">{}</DataArray>",
            data_array(&connectivity)
        )?;
        writeln!(
            file,
            "        <DataArray type=\"Int64\" Name=\"offsets\" format=\"binary\">{}</DataArray>",
            data_array(&offsets)
        )?;
        writeln!(
            file,
            "        <DataArray type=\"UInt8\" Name=\"types\" format=\"binary\">{}</DataArray>",
            data_array(&types)
        )?;
        writeln!(file, "      </Cells>")?;
        writeln!(file, "    </Piece>")?;
        writeln!(file, "  </UnstructuredGrid>")?;
        writeln!(file, "</VTKFile>")?;
        Ok(())
    }
}

fn data_array(data: &[u8]) -> String {
    let mut buffer = Vec::with_capacity(8 + data.len());
    buffer.extend_from_slice(&(data.len() as u64).to_le_bytes());
    buffer.extend_from_slice(data);
    base64(&buffer)
}

fn base64(bytes: &[u8]) -> String {
    const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
    for chunk in bytes.chunks(3) {
        let triple = ((chunk[0] as u32) << 16)
            | ((*chunk.get(1).unwrap_or(&0) as u32) << 8)
            | (*chunk.get(2).unwrap_or(&0) as u32);
        out.push(ALPHABET[(triple >> 18 & 63) as usize] as char);
        out.push(ALPHABET[(triple >> 12 & 63) as usize] as char);
        out.push(if chunk.len() > 1 {
            ALPHABET[(triple >> 6 & 63) as usize] as char
        } else {
            '='
        });
        out.push(if chunk.len() > 2 {
            ALPHABET[(triple & 63) as usize] as char
        } else {
            '='
        });
    }
    out
}