use crate::error::{HullabalooError, HullabalooResult};
use calculo::num::{Epsilon, Num, Sign};
const MAX_CHAR_POLY_GROUND_SET: usize = 22;
fn two_rows_mut<T>(rows: &mut [T], i: usize, j: usize) -> (&mut T, &mut T) {
assert_ne!(i, j, "rows must be distinct");
if i < j {
let (left, right) = rows.split_at_mut(j);
(&mut left[i], &mut right[0])
} else {
let (left, right) = rows.split_at_mut(i);
(&mut right[0], &mut left[j])
}
}
fn gaussian_elimination_rank<N: Num>(m: &mut [Vec<N>], eps: &impl Epsilon<N>) -> usize {
let rows = m.len();
if rows == 0 {
return 0;
}
let cols = m[0].len();
if cols == 0 {
return 0;
}
let mut rank = 0usize;
let mut row = 0usize;
for col in 0..cols {
if row == rows {
break;
}
let mut pivot_row = None;
let mut best_abs = None;
for (i, m_row) in m.iter().enumerate().skip(row) {
let val = m_row[col].abs();
if eps.is_zero(&val) {
continue;
}
let better = match best_abs {
None => true,
Some(ref b) => val.partial_cmp(b).map(|o| o.is_gt()).unwrap_or(false),
};
if better {
best_abs = Some(val);
pivot_row = Some(i);
}
}
let Some(pivot_row) = pivot_row else {
continue;
};
if pivot_row != row {
m.swap(pivot_row, row);
}
let pivot = m[row][col].clone();
let inv_pivot = N::one().ref_div(&pivot);
for x in &mut m[row][col..] {
*x = x.ref_mul(&inv_pivot);
}
for i in 0..rows {
if i == row {
continue;
}
let (pivot_row, target_row) = two_rows_mut(m, row, i);
let factor = target_row[col].clone();
if eps.is_zero(&factor.abs()) {
continue;
}
let pivot_row = pivot_row.as_slice();
for (x, p) in target_row[col..].iter_mut().zip(&pivot_row[col..]) {
x.fused_sub_mul_assign(&factor, p);
}
}
rank += 1;
row += 1;
}
rank
}
fn full_row_rank<N: Num>(matrix: &[Vec<N>], eps: &impl Epsilon<N>) -> (Vec<Vec<N>>, usize) {
let mut m = matrix.to_vec();
let rank = gaussian_elimination_rank(&mut m, eps);
m.truncate(rank);
(m, rank)
}
fn rank_of_subset<N: Num>(matrix: &[Vec<N>], cols: &[usize], eps: &impl Epsilon<N>) -> usize {
if cols.is_empty() {
return 0;
}
if matrix.is_empty() {
return 0;
}
let mut sub = Vec::with_capacity(matrix.len());
for row in matrix {
let mut out = Vec::with_capacity(cols.len());
for &col_idx in cols {
out.push(row[col_idx].clone());
}
sub.push(out);
}
gaussian_elimination_rank(&mut sub, eps)
}
fn sign_i8<N: Num>(value: &N, eps: &impl Epsilon<N>) -> i8 {
match eps.sign(value) {
Sign::Negative => -1,
Sign::Zero => 0,
Sign::Positive => 1,
}
}
fn determinant_sign<N: Num>(mut m: Vec<Vec<N>>, eps: &impl Epsilon<N>) -> i8 {
let n = m.len();
if n == 0 {
return 1;
}
if m.iter().any(|row| row.len() != n) {
return 0;
}
let mut sign = 1i8;
for i in 0..n {
let mut pivot_row = None;
let mut best_abs = None;
for (r, m_row) in m.iter().enumerate().skip(i) {
let val = m_row[i].abs();
if eps.is_zero(&val) {
continue;
}
let better = match best_abs {
None => true,
Some(ref b) => val.partial_cmp(b).map(|o| o.is_gt()).unwrap_or(false),
};
if better {
best_abs = Some(val);
pivot_row = Some(r);
}
}
let Some(pivot_row) = pivot_row else {
return 0;
};
if pivot_row != i {
m.swap(pivot_row, i);
sign = -sign;
}
let pivot = m[i][i].clone();
let pivot_sign = sign_i8(&pivot, eps);
if pivot_sign == 0 {
return 0;
}
sign *= pivot_sign;
for r in (i + 1)..n {
let (pivot_row, target_row) = two_rows_mut(&mut m, i, r);
let factor = target_row[i].ref_div(&pivot);
let pivot_row = pivot_row.as_slice();
for (x, p) in target_row[(i + 1)..].iter_mut().zip(&pivot_row[(i + 1)..]) {
x.fused_sub_mul_assign(&factor, p);
}
}
}
sign
}
#[derive(Debug, Clone)]
pub struct LinearOrientedMatroid<N: Num> {
rank: usize,
n: usize,
matrix: Vec<Vec<N>>,
}
impl<N: Num> LinearOrientedMatroid<N> {
pub fn from_rows(matrix: Vec<Vec<N>>) -> HullabalooResult<Self> {
let Some(first) = matrix.first() else {
return Err(HullabalooError::InvalidInput {
message: "matroid matrix must be nonempty".to_string(),
});
};
let cols = first.len();
if cols == 0 || matrix.iter().any(|r| r.len() != cols) {
return Err(HullabalooError::InvalidInput {
message: "matroid matrix must have consistent, positive row length".to_string(),
});
}
let eps = N::default_eps();
let (reduced, rank) = full_row_rank(&matrix, &eps);
Ok(Self {
rank,
n: cols,
matrix: reduced,
})
}
pub fn from_columns(columns: Vec<Vec<N>>) -> HullabalooResult<Self> {
let Some(first) = columns.first() else {
return Err(HullabalooError::InvalidInput {
message: "matroid must have at least one element".to_string(),
});
};
let rows = first.len();
if rows == 0 || columns.iter().any(|c| c.len() != rows) {
return Err(HullabalooError::InvalidInput {
message: "matroid columns must have consistent, positive length".to_string(),
});
}
let n = columns.len();
let mut row_matrix: Vec<Vec<N>> = vec![vec![N::zero(); n]; rows];
for (j, col) in columns.iter().enumerate() {
for (i, v) in col.iter().enumerate() {
row_matrix[i][j] = v.clone();
}
}
Self::from_rows(row_matrix)
}
pub fn rank(&self) -> usize {
self.rank
}
pub fn num_elements(&self) -> usize {
self.n
}
pub fn matrix(&self) -> &[Vec<N>] {
&self.matrix
}
pub fn chirotope(&self, subset: &[usize]) -> i8 {
assert_eq!(
subset.len(),
self.rank,
"chirotope is defined only on r-element subsets"
);
let eps = N::default_eps();
let mut sub = Vec::with_capacity(self.rank);
for row in &self.matrix {
let mut out = Vec::with_capacity(self.rank);
for &col_idx in subset {
out.push(row[col_idx].clone());
}
sub.push(out);
}
determinant_sign(sub, &eps)
}
pub fn rank_of_subset(&self, subset: &[usize]) -> usize {
let eps = N::default_eps();
rank_of_subset(&self.matrix, subset, &eps)
}
pub fn characteristic_polynomial(&self) -> HullabalooResult<CharacteristicPolynomial> {
let n = self.n;
if n > MAX_CHAR_POLY_GROUND_SET {
return Err(HullabalooError::MatroidTooLarge { num_elements: n });
}
let eps = N::default_eps();
let all: Vec<usize> = (0..n).collect();
let r_total = rank_of_subset(&self.matrix, &all, &eps);
let mut coeffs = vec![0i64; r_total + 1];
let total_subsets = 1usize << n;
let mut indices = Vec::with_capacity(n);
for mask in 0..total_subsets {
let subset_size = mask.count_ones() as usize;
indices.clear();
for e in 0..n {
if (mask >> e) & 1 == 1 {
indices.push(e);
}
}
let r_s = rank_of_subset(&self.matrix, &indices, &eps);
if r_s > r_total {
return Err(HullabalooError::MatroidRankInconsistent {
subset_rank: r_s,
total_rank: r_total,
});
}
let exp = r_total - r_s;
let sign = if subset_size.is_multiple_of(2) { 1 } else { -1 };
coeffs[exp] += sign;
}
Ok(CharacteristicPolynomial {
coefficients: coeffs,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CharacteristicPolynomial {
pub coefficients: Vec<i64>,
}
impl CharacteristicPolynomial {
pub fn degree(&self) -> usize {
assert!(
!self.coefficients.is_empty(),
"characteristic polynomial must have at least one coefficient"
);
self.coefficients.len() - 1
}
}
impl std::fmt::Display for CharacteristicPolynomial {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut first = true;
for (k, &c) in self.coefficients.iter().enumerate().rev() {
if c == 0 {
continue;
}
let sign = if c < 0 { '-' } else { '+' };
let abs_c = c.abs();
if first {
if sign == '-' {
write!(f, "-")?;
}
} else {
write!(f, " {} ", sign)?;
}
match k {
0 => write!(f, "{abs_c}")?,
1 => {
if abs_c == 1 {
write!(f, "t")?;
} else {
write!(f, "{abs_c} t")?;
}
}
_ => {
if abs_c == 1 {
write!(f, "t^{k}")?;
} else {
write!(f, "{abs_c} t^{k}")?;
}
}
}
first = false;
}
if first {
write!(f, "0")?;
}
Ok(())
}
}