use crate::error::{Error, Result};
#[derive(Debug, Clone)]
pub struct RectangularBinaryMatrix {
rows: usize,
cols: usize,
columns: Option<Vec<u64>>,
}
impl RectangularBinaryMatrix {
pub fn new(rows: usize, cols: usize, columns: Vec<u64>) -> Result<Self> {
if rows > 64 {
return Err(Error::InvalidHeader(format!(
"matrix rows ({rows}) exceeds maximum of 64"
)));
}
if columns.len() != cols {
return Err(Error::InvalidHeader(format!(
"expected {cols} columns, got {}",
columns.len()
)));
}
Ok(Self {
rows,
cols,
columns: Some(columns),
})
}
pub fn identity(size: usize) -> Self {
Self {
rows: size.min(64),
cols: size,
columns: None,
}
}
pub fn from_json(value: &serde_json::Value) -> Result<Self> {
if value.is_null() {
return Ok(Self::identity(64));
}
if let Some(obj) = value.as_object() {
let rows = obj
.get("r")
.and_then(|v| v.as_u64())
.ok_or_else(|| Error::MissingField("matrix.r".to_string()))?
as usize;
let cols = obj
.get("c")
.and_then(|v| v.as_u64())
.ok_or_else(|| Error::MissingField("matrix.c".to_string()))?
as usize;
if let Some(identity) = obj.get("identity") {
if identity.as_bool().unwrap_or(false) {
return Ok(Self::identity(rows.max(cols)));
}
}
let columns = if let Some(col_arr) = obj.get("columns").and_then(|v| v.as_array()) {
col_arr
.iter()
.map(|v| {
v.as_u64()
.or_else(|| v.as_i64().map(|i| i as u64))
.ok_or_else(|| {
Error::InvalidHeader("invalid matrix column value".to_string())
})
})
.collect::<Result<Vec<u64>>>()?
} else {
let data = obj.get("data").and_then(|v| v.as_array()).ok_or_else(|| {
Error::MissingField("matrix.columns or matrix.data".to_string())
})?;
data.iter()
.map(|v| {
v.as_u64()
.or_else(|| v.as_i64().map(|i| i as u64))
.ok_or_else(|| {
Error::InvalidHeader("invalid matrix data value".to_string())
})
})
.collect::<Result<Vec<u64>>>()?
};
Self::new(rows, cols, columns)
} else {
Err(Error::InvalidHeader(
"matrix must be a JSON object".to_string(),
))
}
}
pub fn rows(&self) -> usize {
self.rows
}
pub fn cols(&self) -> usize {
self.cols
}
pub fn is_identity(&self) -> bool {
self.columns.is_none()
}
pub fn times(&self, vector: &[u64]) -> u64 {
match &self.columns {
None => {
if vector.is_empty() { 0 } else { vector[0] }
}
Some(columns) => {
let mut result = 0u64;
let mut col_idx = 0;
for &word in vector {
let mut w = word;
let remaining = self.cols - col_idx;
let bits_in_word = remaining.min(64);
for _ in 0..bits_in_word {
if w & 1 != 0 {
result ^= columns[col_idx];
}
w >>= 1;
col_idx += 1;
}
}
result
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_identity_matrix() {
let m = RectangularBinaryMatrix::identity(64);
assert!(m.is_identity());
assert_eq!(m.times(&[0x12345678]), 0x12345678);
assert_eq!(m.times(&[0xDEADBEEF]), 0xDEADBEEF);
}
#[test]
fn test_identity_empty() {
let m = RectangularBinaryMatrix::identity(64);
assert_eq!(m.times(&[]), 0);
}
#[test]
fn test_zero_matrix() {
let cols = vec![0u64; 8];
let m = RectangularBinaryMatrix::new(4, 8, cols).unwrap();
assert_eq!(m.times(&[0xFF]), 0);
}
#[test]
fn test_simple_matrix() {
let cols = vec![0b01u64, 0b10u64];
let m = RectangularBinaryMatrix::new(2, 2, cols).unwrap();
assert_eq!(m.times(&[0b11]), 0b11);
assert_eq!(m.times(&[0b01]), 0b01);
assert_eq!(m.times(&[0b10]), 0b10);
}
#[test]
fn test_invalid_dimensions() {
assert!(RectangularBinaryMatrix::new(65, 4, vec![0; 4]).is_err());
assert!(RectangularBinaryMatrix::new(4, 4, vec![0; 3]).is_err());
}
#[test]
fn test_from_json_identity() {
let json = serde_json::json!({
"r": 32,
"c": 64,
"identity": true
});
let m = RectangularBinaryMatrix::from_json(&json).unwrap();
assert!(m.is_identity());
}
#[test]
fn test_from_json_with_columns() {
let json = serde_json::json!({
"r": 2,
"c": 2,
"columns": [1, 2]
});
let m = RectangularBinaryMatrix::from_json(&json).unwrap();
assert!(!m.is_identity());
assert_eq!(m.rows(), 2);
assert_eq!(m.cols(), 2);
}
#[test]
fn test_from_json_null() {
let json = serde_json::Value::Null;
let m = RectangularBinaryMatrix::from_json(&json).unwrap();
assert!(m.is_identity());
}
}