Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions src/models/algebraic/bmf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ inventory::submit! {
/// assert!(problem.is_exact(&witness).unwrap());
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "BMFData")]
pub struct BMF {
/// The target matrix A (m x n).
matrix: Vec<Vec<bool>>,
Expand All @@ -65,22 +66,40 @@ pub struct BMF {
k: usize,
}

#[derive(Deserialize)]
struct BMFData {
matrix: Vec<Vec<bool>>,
k: usize,
}

impl TryFrom<BMFData> for BMF {
type Error = crate::registry::ConstructionError;

fn try_from(data: BMFData) -> Result<Self, Self::Error> {
Self::try_new(data.matrix, data.k)
}
}

impl BMF {
/// Create a new BMF problem.
///
/// # Arguments
/// * `matrix` - The target m x n boolean matrix
/// * `k` - The factorization rank
pub fn new(matrix: Vec<Vec<bool>>, k: usize) -> Self {
let m = matrix.len();
let n = if m > 0 { matrix[0].len() } else { 0 };
Self::try_new(matrix, k).unwrap_or_else(|error| panic!("{error}"))
}

// Validate matrix dimensions
for row in &matrix {
assert_eq!(row.len(), n, "All rows must have the same length");
fn try_new(
matrix: Vec<Vec<bool>>,
k: usize,
) -> Result<Self, crate::registry::ConstructionError> {
let m = matrix.len();
let n = matrix.first().map_or(0, Vec::len);
if matrix.iter().any(|row| row.len() != n) {
return Err("all matrix rows must have the same length".into());
}

Self { matrix, m, n, k }
Ok(Self { matrix, m, n, k })
}

/// Get the number of rows.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ inventory::submit! {
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "ConsecutiveOnesMatrixAugmentationCreateSpec")]
pub struct ConsecutiveOnesMatrixAugmentation {
matrix: Vec<Vec<bool>>,
bound: i64,
Expand Down
41 changes: 29 additions & 12 deletions src/models/algebraic/consecutive_ones_submatrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,31 +59,48 @@ inventory::submit! {
/// assert!(solution.is_some());
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "ConsecutiveOnesSubmatrixData")]
pub struct ConsecutiveOnesSubmatrix {
matrix: Vec<Vec<bool>>,
bound: i64,
}

#[derive(Deserialize)]
struct ConsecutiveOnesSubmatrixData {
matrix: Vec<Vec<bool>>,
bound: i64,
}

impl TryFrom<ConsecutiveOnesSubmatrixData> for ConsecutiveOnesSubmatrix {
type Error = crate::registry::ConstructionError;

fn try_from(data: ConsecutiveOnesSubmatrixData) -> Result<Self, Self::Error> {
Self::try_new(data.matrix, data.bound)
}
}

impl ConsecutiveOnesSubmatrix {
/// Create a new ConsecutiveOnesSubmatrix instance.
///
/// # Panics
///
/// Panics if `bound > n`, or if rows have inconsistent lengths.
pub fn new(matrix: Vec<Vec<bool>>, bound: i64) -> Self {
let n = if matrix.is_empty() {
0
} else {
matrix[0].len()
};
for row in &matrix {
assert_eq!(row.len(), n, "All rows must have the same length");
Self::try_new(matrix, bound).unwrap_or_else(|error| panic!("{error}"))
}

fn try_new(
matrix: Vec<Vec<bool>>,
bound: i64,
) -> Result<Self, crate::registry::ConstructionError> {
let n = matrix.first().map_or(0, Vec::len);
if matrix.iter().any(|row| row.len() != n) {
return Err("all matrix rows must have the same length".into());
}
if !(bound < 0 || usize::try_from(bound).is_ok_and(|bound| bound <= n)) {
return Err(format!("bound ({bound}) must be <= number of columns ({n})").into());
}
assert!(
bound < 0 || usize::try_from(bound).is_ok_and(|bound| bound <= n),
"bound ({bound}) must be <= number of columns ({n})"
);
Self { matrix, bound }
Ok(Self { matrix, bound })
}

/// Returns the binary matrix.
Expand Down
98 changes: 34 additions & 64 deletions src/models/algebraic/feasible_basis_extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ inventory::submit! {
/// assert!(solution.is_some());
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "FeasibleBasisExtensionCreateSpec")]
pub struct FeasibleBasisExtension {
matrix: Vec<Vec<i64>>,
rhs: Vec<i64>,
Expand All @@ -78,39 +79,9 @@ struct FeasibleBasisExtensionCreateSpec {

impl TryFrom<FeasibleBasisExtensionCreateSpec> for FeasibleBasisExtension {
type Error = crate::registry::ConstructionError;

fn try_from(spec: FeasibleBasisExtensionCreateSpec) -> Result<Self, Self::Error> {
let m = spec.matrix.len();
let first = spec
.matrix
.first()
.ok_or("matrix must have at least one row")?;
let n = first.len();
if spec.matrix.iter().any(|row| row.len() != n) {
return Err("all matrix rows must have the same length".into());
}
if m >= n {
return Err("number of rows must be less than number of columns".into());
}
if spec.rhs.len() != m {
return Err("rhs length must equal number of rows".into());
}
if spec.required_columns.len() >= m {
return Err("required_columns length must be less than number of rows".into());
}
let mut seen = std::collections::HashSet::new();
for &column in &spec.required_columns {
if column >= n {
return Err(format!("required column {column} is out of bounds").into());
}
if !seen.insert(column) {
return Err(format!("duplicate required column {column}").into());
}
}
Ok(Self {
matrix: spec.matrix,
rhs: spec.rhs,
required_columns: spec.required_columns,
})
Self::try_new(spec.matrix, spec.rhs, spec.required_columns)
}
}

Expand All @@ -127,46 +98,45 @@ impl FeasibleBasisExtension {
/// - Any required column index is out of bounds
/// - Required columns contain duplicates
pub fn new(matrix: Vec<Vec<i64>>, rhs: Vec<i64>, required_columns: Vec<usize>) -> Self {
Self::try_new(matrix, rhs, required_columns).unwrap_or_else(|error| panic!("{error}"))
}

fn try_new(
matrix: Vec<Vec<i64>>,
rhs: Vec<i64>,
required_columns: Vec<usize>,
) -> Result<Self, crate::registry::ConstructionError> {
let m = matrix.len();
assert!(m > 0, "Matrix must have at least one row");
let n = matrix[0].len();
for row in &matrix {
assert_eq!(row.len(), n, "All rows must have the same length");
let first = matrix.first().ok_or("matrix must have at least one row")?;
let n = first.len();
if matrix.iter().any(|row| row.len() != n) {
return Err("all matrix rows must have the same length".into());
}
assert!(
m < n,
"Number of rows ({m}) must be less than number of columns ({n})"
);
assert_eq!(
rhs.len(),
m,
"rhs length ({}) must equal number of rows ({m})",
rhs.len()
);
assert!(
required_columns.len() < m,
"|S| ({}) must be less than m ({m})",
required_columns.len()
);
for &col in &required_columns {
assert!(col < n, "Required column index {col} out of bounds (n={n})");
if m >= n {
return Err("number of rows must be less than number of columns".into());
}
if rhs.len() != m {
return Err("rhs length must equal number of rows".into());
}
// Check for duplicates
let mut sorted = required_columns.clone();
sorted.sort_unstable();
for i in 1..sorted.len() {
assert_ne!(
sorted[i - 1],
sorted[i],
"Duplicate required column index {}",
sorted[i]
if required_columns.len() >= m {
return Err(
format!("|S| ({}) must be less than m ({m})", required_columns.len()).into(),
);
}
Self {
let mut seen = std::collections::HashSet::new();
for &column in &required_columns {
if column >= n {
return Err(format!("required column {column} is out of bounds").into());
}
if !seen.insert(column) {
return Err(format!("Duplicate required column index {column}").into());
}
}
Ok(Self {
matrix,
rhs,
required_columns,
}
})
}

/// Returns the matrix A.
Expand Down
28 changes: 23 additions & 5 deletions src/models/algebraic/minimum_matrix_domination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,35 +54,53 @@ inventory::submit! {
/// assert_eq!(witness, Some(vec![true, true, true]));
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "MinimumMatrixDominationData")]
pub struct MinimumMatrixDomination {
/// The binary matrix.
matrix: Vec<Vec<bool>>,
/// Positions of 1-entries in row-major order: (row, col).
ones: Vec<(usize, usize)>,
}

#[derive(Deserialize)]
struct MinimumMatrixDominationData {
matrix: Vec<Vec<bool>>,
}

impl TryFrom<MinimumMatrixDominationData> for MinimumMatrixDomination {
type Error = crate::registry::ConstructionError;

fn try_from(data: MinimumMatrixDominationData) -> Result<Self, Self::Error> {
Self::try_new(data.matrix)
}
}

impl MinimumMatrixDomination {
/// Create a new MinimumMatrixDomination instance.
///
/// # Panics
///
/// Panics if the matrix rows have inconsistent lengths.
pub fn new(matrix: Vec<Vec<bool>>) -> Self {
Self::try_new(matrix).unwrap_or_else(|error| panic!("{error}"))
}

fn try_new(matrix: Vec<Vec<bool>>) -> Result<Self, crate::registry::ConstructionError> {
let num_cols = matrix.first().map_or(0, Vec::len);
for row in &matrix {
assert_eq!(row.len(), num_cols, "All rows must have the same length");
if matrix.iter().any(|row| row.len() != num_cols) {
return Err("all matrix rows must have the same length".into());
}
let ones: Vec<(usize, usize)> = matrix
let ones = matrix
.iter()
.enumerate()
.flat_map(|(i, row)| {
row.iter()
.enumerate()
.filter(|(_, &v)| v)
.filter(|(_, &value)| value)
.map(move |(j, _)| (i, j))
})
.collect();
Self { matrix, ones }
Ok(Self { matrix, ones })
}

/// Returns a reference to the binary matrix.
Expand Down
48 changes: 20 additions & 28 deletions src/models/algebraic/minimum_weight_decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ inventory::submit! {
/// assert!(witness.is_some());
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "MinimumWeightDecodingCreateSpec")]
pub struct MinimumWeightDecoding {
/// The n×m binary parity-check matrix H.
matrix: Vec<Vec<bool>>,
Expand All @@ -71,24 +72,9 @@ struct MinimumWeightDecodingCreateSpec {

impl TryFrom<MinimumWeightDecodingCreateSpec> for MinimumWeightDecoding {
type Error = crate::registry::ConstructionError;

fn try_from(spec: MinimumWeightDecodingCreateSpec) -> Result<Self, Self::Error> {
let first = spec
.matrix
.first()
.ok_or("matrix must have at least one row")?;
if first.is_empty() {
return Err("matrix must have at least one column".into());
}
if spec.matrix.iter().any(|row| row.len() != first.len()) {
return Err("all matrix rows must have the same length".into());
}
if spec.target.len() != spec.matrix.len() {
return Err("rhs length must equal number of rows".into());
}
Ok(Self {
matrix: spec.matrix,
target: spec.target,
})
Self::try_new(spec.matrix, spec.target)
}
}

Expand All @@ -100,18 +86,24 @@ impl MinimumWeightDecoding {
/// Panics if the matrix is empty, rows have inconsistent lengths,
/// target length does not match the number of rows, or there are no columns.
pub fn new(matrix: Vec<Vec<bool>>, target: Vec<bool>) -> Self {
assert!(!matrix.is_empty(), "Matrix must have at least one row");
let num_cols = matrix[0].len();
assert!(num_cols > 0, "Matrix must have at least one column");
for row in &matrix {
assert_eq!(row.len(), num_cols, "All rows must have the same length");
Self::try_new(matrix, target).unwrap_or_else(|error| panic!("{error}"))
}

fn try_new(
matrix: Vec<Vec<bool>>,
target: Vec<bool>,
) -> Result<Self, crate::registry::ConstructionError> {
let first = matrix.first().ok_or("matrix must have at least one row")?;
if first.is_empty() {
return Err("matrix must have at least one column".into());
}
if matrix.iter().any(|row| row.len() != first.len()) {
return Err("all matrix rows must have the same length".into());
}
if target.len() != matrix.len() {
return Err("Target length must equal number of rows".into());
}
assert_eq!(
target.len(),
matrix.len(),
"Target length must equal number of rows"
);
Self { matrix, target }
Ok(Self { matrix, target })
}

/// Returns a reference to the parity-check matrix H.
Expand Down
Loading
Loading