#[derive(Debug, Clone)]
pub struct NlHeader {
pub n_vars: usize,
pub n_constrs: usize,
pub n_objs: usize,
pub n_ranges: usize,
pub n_eqns: usize,
pub n_nl_constrs: usize,
pub n_nl_objs: usize,
pub nlvb: usize,
pub nlvc: usize,
pub nlvo: usize,
pub common_expr_counts: [usize; 5],
pub nnz_jac: usize,
pub nnz_grad: usize,
}
impl NlHeader {
pub fn n_common_exprs(&self) -> usize {
self.common_expr_counts.iter().sum()
}
}
pub fn parse_header<'a>(lines: &mut impl Iterator<Item = &'a str>) -> Result<NlHeader, String> {
let magic = lines.next().ok_or("Empty NL file")?;
if !magic.starts_with('g') {
return Err(format!("Expected text NL file (starting with 'g'), got: {}", magic));
}
let mut dim_lines: Vec<Vec<usize>> = Vec::new();
for i in 0..9 {
let line = lines
.next()
.ok_or(format!("NL header truncated at line {}", i + 1))?;
let nums: Vec<usize> = parse_nums(line);
dim_lines.push(nums);
}
let get = |line: usize, field: usize| -> usize {
dim_lines
.get(line)
.and_then(|l| l.get(field))
.copied()
.unwrap_or(0)
};
Ok(NlHeader {
n_vars: get(0, 0),
n_constrs: get(0, 1),
n_objs: get(0, 2),
n_ranges: get(0, 3),
n_eqns: get(0, 4),
n_nl_constrs: get(1, 0),
n_nl_objs: get(1, 1),
nlvb: get(3, 0),
nlvc: get(3, 1),
nlvo: get(3, 2),
common_expr_counts: [get(8, 0), get(8, 1), get(8, 2), get(8, 3), get(8, 4)],
nnz_jac: get(6, 0),
nnz_grad: get(6, 1),
})
}
fn parse_nums(line: &str) -> Vec<usize> {
let line = line.split('#').next().unwrap_or("");
line.split_whitespace()
.filter_map(|s| s.parse::<usize>().ok())
.collect()
}