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}