#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SparsityPattern {
pub dim: usize,
pub rows: Vec<u32>,
pub cols: Vec<u32>,
}
impl SparsityPattern {
#[must_use]
pub fn nnz(&self) -> usize {
self.rows.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
#[must_use]
pub fn contains(&self, i: usize, j: usize) -> bool {
let (r, c) = if i >= j { (i, j) } else { (j, i) };
self.rows
.iter()
.zip(self.cols.iter())
.any(|(&row, &col)| row as usize == r && col as usize == c)
}
}
#[must_use]
pub fn greedy_coloring(pattern: &SparsityPattern) -> (Vec<u32>, u32) {
let n = pattern.dim;
if n == 0 {
return (Vec::new(), 0);
}
let mut adj: Vec<Vec<u32>> = vec![Vec::new(); n];
for (&r, &c) in pattern.rows.iter().zip(pattern.cols.iter()) {
let r = r as usize;
let c = c as usize;
if r != c {
adj[r].push(c as u32);
adj[c].push(r as u32);
}
}
let mut adj2: Vec<Vec<u32>> = vec![Vec::new(); n];
for v in 0..n {
for &u in &adj[v] {
adj2[v].push(u);
}
for &u in &adj[v] {
for &w in &adj[u as usize] {
if w as usize != v {
adj2[v].push(w);
}
}
}
adj2[v].sort_unstable();
adj2[v].dedup();
}
greedy_distance1_coloring(&adj2, n)
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CsrPattern {
pub dim: usize,
pub row_ptr: Vec<u32>,
pub col_ind: Vec<u32>,
}
impl CsrPattern {
#[must_use]
pub fn nnz(&self) -> usize {
self.col_ind.len()
}
pub fn reorder_values<F: Copy>(&self, coo: &SparsityPattern, coo_vals: &[F]) -> Vec<F> {
assert_eq!(coo_vals.len(), coo.nnz());
assert_eq!(self.nnz(), coo.nnz());
let mut result = Vec::with_capacity(self.nnz());
for row in 0..self.dim {
let start = self.row_ptr[row] as usize;
let end = self.row_ptr[row + 1] as usize;
for csr_idx in start..end {
let col = self.col_ind[csr_idx];
let coo_idx = coo
.rows
.iter()
.zip(coo.cols.iter())
.position(|(&r, &c)| r == row as u32 && c == col)
.expect("CSR entry not found in COO pattern");
result.push(coo_vals[coo_idx]);
}
}
result
}
}
impl SparsityPattern {
#[must_use]
pub fn to_csr_lower(&self) -> CsrPattern {
let n = self.dim;
let mut row_ptr = vec![0u32; n + 1];
for &r in &self.rows {
row_ptr[r as usize + 1] += 1;
}
for i in 1..=n {
row_ptr[i] += row_ptr[i - 1];
}
let nnz = self.nnz();
let mut col_ind = vec![0u32; nnz];
let mut pos = vec![0u32; n]; for k in 0..nnz {
let r = self.rows[k] as usize;
let offset = row_ptr[r] + pos[r];
col_ind[offset as usize] = self.cols[k];
pos[r] += 1;
}
CsrPattern {
dim: n,
row_ptr,
col_ind,
}
}
#[must_use]
pub fn to_csr(&self) -> CsrPattern {
let n = self.dim;
let mut row_ptr = vec![0u32; n + 1];
for (&r, &c) in self.rows.iter().zip(self.cols.iter()) {
row_ptr[r as usize + 1] += 1;
if r != c {
row_ptr[c as usize + 1] += 1;
}
}
for i in 1..=n {
row_ptr[i] += row_ptr[i - 1];
}
let nnz = row_ptr[n] as usize;
let mut col_ind = vec![0u32; nnz];
let mut pos = vec![0u32; n];
for (&r, &c) in self.rows.iter().zip(self.cols.iter()) {
let ri = r as usize;
let offset = row_ptr[ri] + pos[ri];
col_ind[offset as usize] = c;
pos[ri] += 1;
if r != c {
let ci = c as usize;
let offset = row_ptr[ci] + pos[ci];
col_ind[offset as usize] = r;
pos[ci] += 1;
}
}
for i in 0..n {
let start = row_ptr[i] as usize;
let end = row_ptr[i + 1] as usize;
col_ind[start..end].sort_unstable();
}
CsrPattern {
dim: n,
row_ptr,
col_ind,
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct JacobianSparsityPattern {
pub num_outputs: usize,
pub num_inputs: usize,
pub rows: Vec<u32>,
pub cols: Vec<u32>,
}
impl JacobianSparsityPattern {
#[must_use]
pub fn nnz(&self) -> usize {
self.rows.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
#[must_use]
pub fn contains(&self, output_idx: usize, input_idx: usize) -> bool {
self.rows
.iter()
.zip(self.cols.iter())
.any(|(&r, &c)| r as usize == output_idx && c as usize == input_idx)
}
}
#[must_use]
pub fn column_coloring(pattern: &JacobianSparsityPattern) -> (Vec<u32>, u32) {
intersection_graph_coloring(
&pattern.rows,
&pattern.cols,
pattern.num_outputs,
pattern.num_inputs,
)
}
#[must_use]
pub fn row_coloring(pattern: &JacobianSparsityPattern) -> (Vec<u32>, u32) {
intersection_graph_coloring(
&pattern.cols,
&pattern.rows,
pattern.num_inputs,
pattern.num_outputs,
)
}
fn intersection_graph_coloring(
group_keys: &[u32],
color_keys: &[u32],
group_dim: usize,
color_dim: usize,
) -> (Vec<u32>, u32) {
if color_dim == 0 {
return (Vec::new(), 0);
}
let mut groups: Vec<Vec<u32>> = vec![Vec::new(); group_dim];
for (&g, &c) in group_keys.iter().zip(color_keys.iter()) {
groups[g as usize].push(c);
}
let mut adj: Vec<Vec<u32>> = vec![Vec::new(); color_dim];
for members in &groups {
for i in 0..members.len() {
for j in (i + 1)..members.len() {
let a = members[i] as usize;
let b = members[j] as usize;
adj[a].push(b as u32);
adj[b].push(a as u32);
}
}
}
for list in &mut adj {
list.sort_unstable();
list.dedup();
}
greedy_distance1_coloring(&adj, color_dim)
}
fn greedy_distance1_coloring(adj: &[Vec<u32>], n: usize) -> (Vec<u32>, u32) {
let mut order: Vec<usize> = (0..n).collect();
order.sort_by(|&a, &b| adj[b].len().cmp(&adj[a].len()));
let mut colors = vec![u32::MAX; n];
let mut num_colors = 0u32;
for &v in &order {
let mut used_bits: u64 = 0;
let mut needs_fallback = false;
for &neighbor in &adj[v] {
let c = colors[neighbor as usize];
if c != u32::MAX {
if c < 64 {
used_bits |= 1u64 << c;
} else {
needs_fallback = true;
}
}
}
let color = if !needs_fallback {
(!used_bits).trailing_zeros()
} else {
let mut used_vec: Vec<u32> = adj[v]
.iter()
.filter_map(|&neighbor| {
let c = colors[neighbor as usize];
if c != u32::MAX {
Some(c)
} else {
None
}
})
.collect();
used_vec.sort_unstable();
used_vec.dedup();
let mut c = 0u32;
for &u in &used_vec {
if u != c {
break;
}
c += 1;
}
c
};
colors[v] = color;
if color + 1 > num_colors {
num_colors = color + 1;
}
}
(colors, num_colors)
}