use core::fmt;
use serde::de::{self, MapAccess, Visitor};
use serde::ser::SerializeStruct;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::mat::complex::{
Complex32, Complex64, ComplexI8, ComplexI16, ComplexI32, ComplexI64, ComplexU8, ComplexU16,
ComplexU32, ComplexU64,
};
use crate::mat::value::ComplexTag;
pub(crate) const MATRIX_SENTINEL: &str = "__hdf5_pure_mat_Matrix__";
macro_rules! matrix_complex_sentinels {
($($konst:ident => $elem:ty, $tag:ident, $suffix:literal),* $(,)?) => {
$(
#[doc = concat!(
"Sentinel for `Matrix<", stringify!($elem),
">`. Distinct from `MATRIX_SENTINEL` so an empty 0×0 / 0×N / \
N×0 matrix still writes as a complex (compound) dataset of \
this component class."
)]
pub(crate) const $konst: &str =
concat!("__hdf5_pure_mat_MatrixComplex", $suffix, "__");
)*
pub(crate) fn complex_tag_for_matrix_sentinel(name: &str) -> Option<ComplexTag> {
match name {
$($konst => Some(ComplexTag::$tag),)*
_ => None,
}
}
};
}
matrix_complex_sentinels! {
MATRIX_COMPLEX64_SENTINEL => Complex64, F64, "64",
MATRIX_COMPLEX32_SENTINEL => Complex32, F32, "32",
MATRIX_COMPLEX_I64_SENTINEL => ComplexI64, I64, "I64",
MATRIX_COMPLEX_I32_SENTINEL => ComplexI32, I32, "I32",
MATRIX_COMPLEX_I16_SENTINEL => ComplexI16, I16, "I16",
MATRIX_COMPLEX_I8_SENTINEL => ComplexI8, I8, "I8",
MATRIX_COMPLEX_U64_SENTINEL => ComplexU64, U64, "U64",
MATRIX_COMPLEX_U32_SENTINEL => ComplexU32, U32, "U32",
MATRIX_COMPLEX_U16_SENTINEL => ComplexU16, U16, "U16",
MATRIX_COMPLEX_U8_SENTINEL => ComplexU8, U8, "U8",
}
mod sealed {
pub trait Sealed {}
}
pub trait MatElement: sealed::Sealed {
const SENTINEL: &'static str;
}
macro_rules! impl_mat_element {
($($t:ty => $sentinel:ident),* $(,)?) => {
$(
impl sealed::Sealed for $t {}
impl MatElement for $t {
const SENTINEL: &'static str = $sentinel;
}
)*
};
}
impl_mat_element! {
f64 => MATRIX_SENTINEL,
f32 => MATRIX_SENTINEL,
i8 => MATRIX_SENTINEL,
i16 => MATRIX_SENTINEL,
i32 => MATRIX_SENTINEL,
i64 => MATRIX_SENTINEL,
u8 => MATRIX_SENTINEL,
u16 => MATRIX_SENTINEL,
u32 => MATRIX_SENTINEL,
u64 => MATRIX_SENTINEL,
bool => MATRIX_SENTINEL,
Complex64 => MATRIX_COMPLEX64_SENTINEL,
Complex32 => MATRIX_COMPLEX32_SENTINEL,
ComplexI64 => MATRIX_COMPLEX_I64_SENTINEL,
ComplexI32 => MATRIX_COMPLEX_I32_SENTINEL,
ComplexI16 => MATRIX_COMPLEX_I16_SENTINEL,
ComplexI8 => MATRIX_COMPLEX_I8_SENTINEL,
ComplexU64 => MATRIX_COMPLEX_U64_SENTINEL,
ComplexU32 => MATRIX_COMPLEX_U32_SENTINEL,
ComplexU16 => MATRIX_COMPLEX_U16_SENTINEL,
ComplexU8 => MATRIX_COMPLEX_U8_SENTINEL,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Matrix<T> {
rows: usize,
cols: usize,
data: Vec<T>,
}
impl<T> Matrix<T> {
pub fn from_row_major(rows: usize, cols: usize, data: Vec<T>) -> Self {
let total = rows
.checked_mul(cols)
.expect("Matrix::from_row_major: rows * cols overflows usize");
assert_eq!(
data.len(),
total,
"Matrix::from_row_major: data length {} does not match {rows}×{cols} = {total}",
data.len(),
);
Self { rows, cols, data }
}
pub fn rows(&self) -> usize {
self.rows
}
pub fn cols(&self) -> usize {
self.cols
}
pub fn data(&self) -> &[T] {
&self.data
}
pub fn into_data(self) -> Vec<T> {
self.data
}
}
impl<T: Clone + Default> Matrix<T> {
pub fn zeros(rows: usize, cols: usize) -> Self {
let total = rows
.checked_mul(cols)
.expect("Matrix::zeros: rows * cols overflows usize");
Self {
rows,
cols,
data: vec![T::default(); total],
}
}
}
impl<T: MatElement + Serialize> Serialize for Matrix<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut s = serializer.serialize_struct(T::SENTINEL, 3)?;
s.serialize_field("rows", &self.rows)?;
s.serialize_field("cols", &self.cols)?;
s.serialize_field("data", &self.data)?;
s.end()
}
}
impl<'de, T: MatElement + Deserialize<'de>> Deserialize<'de> for Matrix<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct MatrixVisitor<T>(core::marker::PhantomData<T>);
impl<'de, T: Deserialize<'de>> Visitor<'de> for MatrixVisitor<T> {
type Value = Matrix<T>;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a Matrix<T> struct with fields rows, cols, data")
}
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Matrix<T>, A::Error> {
let mut rows: Option<usize> = None;
let mut cols: Option<usize> = None;
let mut data: Option<Vec<T>> = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"rows" => rows = Some(map.next_value()?),
"cols" => cols = Some(map.next_value()?),
"data" => data = Some(map.next_value()?),
_ => {
let _: serde::de::IgnoredAny = map.next_value()?;
}
}
}
let rows = rows.ok_or_else(|| de::Error::missing_field("rows"))?;
let cols = cols.ok_or_else(|| de::Error::missing_field("cols"))?;
let data = data.ok_or_else(|| de::Error::missing_field("data"))?;
let total = rows.checked_mul(cols).ok_or_else(|| {
de::Error::custom(format!(
"Matrix dimensions {rows}×{cols} overflow the address space"
))
})?;
if data.len() != total {
return Err(de::Error::custom(format!(
"Matrix data length {} does not match {}×{} = {}",
data.len(),
rows,
cols,
total
)));
}
Ok(Matrix { rows, cols, data })
}
}
deserializer.deserialize_struct(
T::SENTINEL,
&["rows", "cols", "data"],
MatrixVisitor(core::marker::PhantomData),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn from_row_major_length_mismatch_panics() {
let _ = Matrix::from_row_major(2, 3, vec![1.0_f64]);
}
const WRAPS_TO_FOUR: usize = usize::MAX / 4 + 2;
#[test]
fn the_wrapping_shape_really_does_wrap() {
assert_eq!(WRAPS_TO_FOUR.wrapping_mul(4), 4);
assert!(WRAPS_TO_FOUR.checked_mul(4).is_none());
}
#[test]
#[should_panic(expected = "overflows usize")]
fn zeros_refuses_a_shape_whose_product_wraps() {
let _ = Matrix::<f64>::zeros(WRAPS_TO_FOUR, 4);
}
#[test]
fn deserializing_a_shape_whose_product_wraps_is_an_error() {
let json = format!(r#"{{"rows":{WRAPS_TO_FOUR},"cols":4,"data":[1.0,2.0,3.0,4.0]}}"#);
let err = serde_json::from_str::<Matrix<f64>>(&json).unwrap_err();
assert!(
err.to_string().contains("overflow the address space"),
"unexpected error: {err}"
);
}
#[test]
fn deserializing_an_honest_length_mismatch_is_still_an_error() {
let err =
serde_json::from_str::<Matrix<f64>>(r#"{"rows":2,"cols":3,"data":[1.0]}"#).unwrap_err();
assert!(
err.to_string().contains("does not match"),
"unexpected error: {err}"
);
}
}