#[cfg(feature = "ndarray")]
use ndarray::prelude::*;
use rustc_hash::FxHashMap as HashMap;
use super::DMatrix;
pub fn build_name2index_map(_names: &[Box<str>]) -> HashMap<Box<str>, usize> {
_names
.iter()
.enumerate()
.map(|(r, name)| (name.clone(), r))
.collect()
}
pub fn take_subset_indices_names(
new_indices: &[usize],
ntot: usize,
old_names: Vec<Box<str>>,
) -> (HashMap<u64, u64>, Vec<Box<str>>) {
let mut old2new: HashMap<u64, u64> = Default::default();
let mut new2old = vec![];
debug_assert!(ntot == old_names.len());
let mut k = 0_u64;
for idx in new_indices.iter() {
if *idx < ntot {
old2new.insert(*idx as u64, k);
new2old.push(*idx);
k += 1;
}
}
let new_names = new2old
.iter()
.map(|&i| old_names[i].clone())
.collect::<Vec<Box<str>>>();
(old2new, new_names)
}
pub fn take_subset_indices_names_if_needed(
new_indices: Option<&Vec<usize>>,
ntot: Option<usize>,
old_names: Vec<Box<str>>,
) -> (HashMap<u64, u64>, Vec<Box<str>>) {
let ntot = ntot.unwrap_or(old_names.len());
if let Some(new_indices) = new_indices {
take_subset_indices_names(new_indices, ntot, old_names)
} else {
let names = old_names;
let identity = (0..(ntot as u64))
.zip(0..(ntot as u64))
.collect::<HashMap<u64, u64>>();
(identity, names)
}
}
#[cfg(feature = "ndarray")]
pub fn ndarray_to_triplets(array: &Array2<f32>) -> Vec<(u64, u64, f32)> {
let eps = 1e-6;
array
.indexed_iter()
.filter(|(_, &elem)| elem.abs() > eps)
.map(|((row, col), &value)| (row as u64, col as u64, value))
.collect::<Vec<(u64, u64, f32)>>()
}
pub fn dmatrix_to_triplets(matrix: &DMatrix<f32>) -> Vec<(u64, u64, f32)> {
let (nrow, _) = matrix.shape();
let eps = 1e-6;
matrix
.iter() .enumerate()
.filter(|(_, &elem)| elem.abs() > eps)
.map(|(idx, &value)| {
let row = idx % nrow;
let col = idx / nrow;
(row as u64, col as u64, value)
})
.collect::<Vec<(u64, u64, f32)>>()
}
pub fn remove_backend_path(path: &str) -> anyhow::Result<()> {
let p = std::path::Path::new(path);
if p.exists() {
if p.is_file() {
std::fs::remove_file(p)?;
} else {
std::fs::remove_dir_all(p)?;
}
}
Ok(())
}
pub fn preload_within_budget(nnz: usize, what: &str) -> bool {
use std::sync::atomic::{AtomicUsize, Ordering};
const BYTES_PER_NNZ: usize = 12;
static RESERVED: AtomicUsize = AtomicUsize::new(0);
let cost = nnz.saturating_mul(BYTES_PER_NNZ);
if let Some(budget) = std::env::var("LEGUME_PRELOAD_BUDGET_BYTES")
.ok()
.and_then(|v| v.parse::<usize>().ok())
{
return fits_or_warn(cost, budget, nnz, what);
}
let budget = auto_preload_budget_bytes();
let reserved = RESERVED.fetch_add(cost, Ordering::Relaxed);
if fits_or_warn(reserved.saturating_add(cost), budget, nnz, what) {
true
} else {
RESERVED.fetch_sub(cost, Ordering::Relaxed);
false
}
}
fn fits_or_warn(cost: usize, budget: usize, nnz: usize, what: &str) -> bool {
if cost > budget {
log::warn!(
"skipping {what} preload of {nnz} nnz: {cost} bytes would exceed the \
{budget}-byte budget (LEGUME_PRELOAD_BUDGET_BYTES to raise); reads stay on the \
streaming path"
);
false
} else {
true
}
}
fn auto_preload_budget_bytes() -> usize {
static BUDGET: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
*BUDGET.get_or_init(|| {
const FALLBACK_BYTES: usize = 8 << 30;
let mut sys = sysinfo::System::new();
sys.refresh_memory();
match usize::try_from(sys.available_memory()) {
Ok(avail) if avail > 0 => avail / 2,
_ => FALLBACK_BYTES,
}
})
}
#[allow(dead_code)] pub fn auto_preload_enabled() -> bool {
!matches!(
std::env::var("LEGUME_AUTO_PRELOAD").as_deref(),
Ok("0") | Ok("false") | Ok("off")
)
}
pub const SLAB_BUDGET_BYTES: usize = 256 << 20;
pub const TRIPLET_BYTES: usize = 24;