Skip to main content

oximo_io/nl/
options.rs

1//! Caller-facing options for the `.nl` writer.
2//!
3//! Mirrors the configuration surface of the official AMPL `nl-writer2`:
4//! ASCII vs binary output, precision control, comment toggle, NaN/Inf
5//! handling, and pluggable sources for the segments oximo's `Model` cannot
6//! supply directly (imported functions `F`, suffixes `S`, defined variables
7//! `V`, dual seeds `d`, complementarity entries in `r`).
8
9/// Encoding of the body that follows the (always-ASCII) 10-line header.
10#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
11pub enum NlFormat {
12    /// ASCII `g` header, every token on its own line.
13    #[default]
14    Ascii,
15    /// Binary `b` header, opcodes as single bytes, ints as 4-byte LE, doubles
16    /// as 8-byte LE, integer-valued numeric constants emitted as `s` (short)
17    /// or `l` (long) when they fit.
18    Binary,
19}
20
21/// Options for the writer.
22///
23/// `Default::default()` matches the writer's original behaviour: ASCII output
24/// with trailing `\t# ...` comments, shortest-round-trip numeric formatting,
25/// and an error on NaN/Inf constants.
26#[derive(Clone, Debug)]
27pub struct WriteOptions {
28    pub format: NlFormat,
29    /// `None` -> shortest round-trip (default Rust `{}` for `f64`).
30    /// `Some(n)` -> `n` significant digits in scientific notation (`{:.*e}`,
31    /// e.g. `3.142e0`).
32    pub precision: Option<u32>,
33    /// Emit trailing `\t# ...` comments in the header and segment introducers
34    /// (ASCII format only). Ignored in binary mode.
35    pub comments: bool,
36    /// When `true`, NaN/Inf numeric constants are emitted as `Infinity` /
37    /// `-Infinity` / `NaN` strings (matching AMPL `NL_LIB_GFMT`). When `false`
38    /// (default) the writer fails with `IoError::InvalidNumber`.
39    pub nonfinite_strings: bool,
40    /// When `true`, a `write_nl_files` call also emits the sibling
41    /// `<stub>.row` (constraint names) and `<stub>.col` (variable names) files
42    /// next to the `.nl`, and populates the header's `max_name_len` fields.
43    pub aux_files: bool,
44    /// Imported function declarations (F segments). Empty by default.
45    pub functions: Vec<ImportedFunction>,
46    /// Suffix entries (S segments). Empty by default.
47    pub suffixes: Vec<SuffixData>,
48    /// Defined variables (V segments). Empty by default.
49    pub defined_vars: Vec<DefinedVar>,
50    /// Dual seeds (d segment), sparse `(constraint_nl_index, value)` pairs.
51    pub dual_init: Vec<(u32, f64)>,
52    /// Complementarity entries for the `r` segment. Maps a constraint index
53    /// to its complementarity info. Empty by default.
54    pub complementarity: Vec<(usize, Complementarity)>,
55}
56
57impl Default for WriteOptions {
58    fn default() -> Self {
59        Self {
60            format: NlFormat::Ascii,
61            precision: None,
62            comments: true,
63            nonfinite_strings: false,
64            aux_files: false,
65            functions: Vec::new(),
66            suffixes: Vec::new(),
67            defined_vars: Vec::new(),
68            dual_init: Vec::new(),
69            complementarity: Vec::new(),
70        }
71    }
72}
73
74impl WriteOptions {
75    /// ASCII with comments. Identity with `Default::default()`.
76    pub fn ascii() -> Self {
77        Self::default()
78    }
79
80    /// ASCII, no trailing comments. Slimmer files for production use.
81    pub fn ascii_lean() -> Self {
82        Self { comments: false, ..Self::default() }
83    }
84
85    /// Binary `b` header. Comments are unused in binary mode.
86    pub fn binary() -> Self {
87        Self { format: NlFormat::Binary, comments: false, ..Self::default() }
88    }
89}
90
91/// One entry in the `F` segment: an externally-defined function the model
92/// references via opcode `f<i> <n_args>` inside expression graphs.
93#[derive(Clone, Debug)]
94pub struct ImportedFunction {
95    pub name: String,
96    /// `0` -> no string arguments.
97    /// `1` -> string arguments allowed.
98    pub allow_string_args: u8,
99    /// `>= 0` -> exact arity.
100    /// `< 0` -> at least `-(n + 1)` arguments.
101    pub n_args: i32,
102}
103
104/// One `S` segment: suffix values attached to one of four entity kinds.
105#[derive(Clone, Debug)]
106pub struct SuffixData {
107    pub name: String,
108    pub kind: SuffixKind,
109    pub flavour: SuffixFlavour,
110    /// Sparse `(offset, value)` pairs. `offset` is the NL index of the
111    /// entity (0-based). Only nonzero values need to be listed.
112    pub values: Vec<(u32, f64)>,
113}
114
115/// Which kind of entity a suffix attaches to. Encoded as the low two bits of
116/// the suffix kind word (D. M. Gay, Table 14).
117#[derive(Copy, Clone, Debug, PartialEq, Eq)]
118pub enum SuffixKind {
119    Variable = 0,
120    Constraint = 1,
121    Objective = 2,
122    Problem = 3,
123}
124
125/// Whether suffix values are integer-valued (`Int`) or real-valued (`Real`).
126/// Sets the `4` bit of the suffix kind word.
127#[derive(Copy, Clone, Debug, PartialEq, Eq)]
128pub enum SuffixFlavour {
129    Int,
130    Real,
131}
132
133/// One `V` segment: a defined variable holding a named common subexpression.
134#[derive(Clone, Debug)]
135pub struct DefinedVar {
136    /// The NL variable index this defined var occupies. Must be `>= n_var`
137    /// (real decision variables come first).
138    pub nl_index: u32,
139    /// Linear part `sum(coef_i * v_i)` (referencing other NL indices).
140    pub linear: Vec<(u32, f64)>,
141    /// Which constraint or objective this defined var is private to:
142    /// `0` -> shared (appears in V block at the top, before C/L/O),
143    /// `m+1` -> only in constraint `m`, `n_con + n_lcon + m` -> only in
144    /// objective `m`.
145    pub appearance: u32,
146    /// Polish-prefix expression text for the nonlinear part (e.g. `"o5\nv0\nn2\n"`).
147    /// `""` means a purely-linear defined var (the expression part collapses
148    /// to a single `n0` constant when emitted).
149    pub nonlinear_polish: String,
150}
151
152/// Complementarity declaration for a constraint (D. M. Gay, Table 17, line type 5:
153/// `5 k i` where `k` says which bounds on `v_{i-1}` are finite).
154#[derive(Copy, Clone, Debug)]
155pub struct Complementarity {
156    /// Bits: `1` = finite lower bound, `2` = finite upper bound, `3` = both.
157    pub k: u8,
158    /// 1-based variable index `i` (the constraint complements `v_{i-1}`).
159    /// The writer's `WriteOptions::complementarity` field carries the
160    /// constraint index in original (unpermuted) order and the `Complementarity`
161    /// here uses NL variable indices already.
162    pub i: u32,
163}