Skip to main content

pounce_cinterface/
errors.rs

1//! One error namespace for the whole C API.
2//!
3//! Every negative code a POUNCE extension entry point returns is a
4//! `POUNCE_ERR_*` value from this module, and each value has exactly one
5//! meaning wherever it appears: a wrapper can keep a single code-to-message
6//! table, and a log line that says `-3` means "index out of range" whichever
7//! function wrote it. [`PounceErrorString`] is that table, so every frontend
8//! reports the same text.
9//!
10//! The values are grouped in blocks:
11//!
12//! | block        | meaning                                                   |
13//! |--------------|-----------------------------------------------------------|
14//! | `-1 … -19`   | generic: any function may return these                    |
15//! | `-20 … -39`  | complementarity pairs (`*PounceComplementarityPair*`, `AddPounceComplementsConstraint`) |
16//! | `-40 … -59`  | bound updates (`SetPounceBounds`, `GetPounceBounds`)      |
17//! | `-60 … -79`  | the QP handle (`PounceQp*`)                               |
18//!
19//! A new code is appended inside its block, and a number is never reused or
20//! renumbered once released. A new function family gets a new block. A code
21//! from one family's block may be returned by another family when the
22//! meaning is the same (`POUNCE_ERR_ROW_NOT_FREE` is returned by both
23//! `AddPounceComplementsConstraint` and `SetPounceBounds`).
24//!
25//! Every function checks in the same order, so a call with several faults
26//! reports the same code whichever function it is:
27//!
28//! 1. the handle ([`POUNCE_ERR_NULL`]);
29//! 2. `flags` and enum arguments ([`POUNCE_ERR_FLAGS`], [`POUNCE_ERR_ARGUMENT`]);
30//! 3. counts ([`POUNCE_ERR_COUNT`]), then the arrays they size (`NULL`);
31//! 4. per entry, over the whole batch in batch order: range, then duplicate
32//!    or already paired, then NaN;
33//! 5. per entry, over the whole batch in batch order: what the entry would
34//!    mean (shape, row not free, variable bounds, `Complements` class, pair
35//!    changed);
36//! 6. state beyond the batch (the staged working set; `PounceQpSolve`'s
37//!    checks).
38//!
39//! An argument fault anywhere in a batch is therefore reported before a
40//! semantic fault anywhere in it. `tests/check_order.rs` holds the batch
41//! functions to this.
42//!
43//! Non-negative returns stay per function: `POUNCE_OK` (0) for success, and
44//! the QP statuses (`POUNCE_QP_STATUS_*`) and `AddPounceComplementsConstraint`
45//! outcomes (`POUNCE_COMPLEMENTS_*`) are results, not errors.
46//!
47//! The header (`include/pounce.h`) repeats every value; `tests/header_abi.rs`
48//! holds the two to the same names and values, and checks that
49//! [`PounceErrorString`] has a message for each.
50
51use std::ffi::{c_char, c_int};
52
53/// Success.
54pub const POUNCE_OK: c_int = 0;
55
56// --- generic, -1 … -19 -----------------------------------------------------
57
58/// A NULL handle, or a NULL array the call needed.
59pub const POUNCE_ERR_NULL: c_int = -1;
60/// A count out of range: negative, larger than the dimension it indexes, or
61/// inconsistent with a NULL index array.
62pub const POUNCE_ERR_COUNT: c_int = -2;
63/// An index outside `[0, n)` / `[0, m)`, read in the problem's index style.
64pub const POUNCE_ERR_OUT_OF_RANGE: c_int = -3;
65/// The same index given twice where each may appear once.
66pub const POUNCE_ERR_DUPLICATE: c_int = -4;
67/// A NaN value where a number is required.
68pub const POUNCE_ERR_NAN: c_int = -5;
69/// `flags` carries a bit this library does not define. An unknown bit is
70/// refused, so an older library never silently ignores one.
71pub const POUNCE_ERR_FLAGS: c_int = -6;
72/// An argument outside its domain: an unknown enum value (a view, a cone
73/// kind), a bad cone dimension or exponent, or entries whose sum overflows.
74pub const POUNCE_ERR_ARGUMENT: c_int = -7;
75/// A Rust panic caught at the boundary, or a handle whose stored state no
76/// longer validates. Not expected; it exists so neither is reported as a
77/// diagnosis of the caller's input. Nothing was changed.
78pub const POUNCE_ERR_INTERNAL: c_int = -8;
79
80// --- complementarity pairs, -20 … -39 --------------------------------------
81
82/// The variable is already in a pair (declared earlier, or earlier in the
83/// same batch or an earlier accepted call).
84pub const POUNCE_ERR_VAR_ALREADY_PAIRED: c_int = -20;
85/// The row is already in a pair (declared earlier, or earlier in the same
86/// batch or an earlier accepted call).
87pub const POUNCE_ERR_ROW_ALREADY_PAIRED: c_int = -21;
88/// The pair's bounds are none of the lowered shapes: one-sided, one-sided
89/// plus a plain bound, or mixed.
90pub const POUNCE_ERR_SHAPE: c_int = -22;
91/// A row that must stay a free function was given (or has) a finite bound:
92/// MOI's `F` in `AddPounceComplementsConstraint`, which `SetPounceBounds`
93/// then refuses to bound.
94pub const POUNCE_ERR_ROW_NOT_FREE: c_int = -23;
95/// The variable's bounds are NaN or crossed (`l > u`): no row of the MOI
96/// complements table applies.
97pub const POUNCE_ERR_VAR_BOUNDS: c_int = -24;
98
99// --- bound updates, -40 … -59 ----------------------------------------------
100
101/// A declared pair would leave its native shapes, or change which variable
102/// bound and which row side it pairs.
103pub const POUNCE_ERR_PAIR_CHANGED: c_int = -40;
104/// A variable used by `AddPounceComplementsConstraint` would move to another
105/// row of the MOI table (lower-only, upper-only, boxed, fixed, free).
106pub const POUNCE_ERR_COMPLEMENTS_CLASS: c_int = -41;
107/// The working set staged by `IpoptSetWarmStartWorkingSet` would claim
108/// something the new bounds contradict.
109pub const POUNCE_ERR_WORKING_SET: c_int = -42;
110
111// --- QP handle, -60 … -79 --------------------------------------------------
112
113/// The QP is incomplete: no objective, or declared rows never set.
114pub const POUNCE_ERR_INCOMPLETE: c_int = -60;
115/// The cones do not partition the inequality rows, or PSD is mixed with the
116/// exponential or power cone.
117pub const POUNCE_ERR_CONES: c_int = -61;
118/// An option the selected engine would silently drop.
119pub const POUNCE_ERR_OPTIONS: c_int = -62;
120/// `method=ipm` and `P` not certified positive semidefinite.
121pub const POUNCE_ERR_INDEFINITE: c_int = -63;
122
123/// Every code with its message: the single table [`PounceErrorString`]
124/// reads, and the list `tests/header_abi.rs` checks the header against.
125pub const POUNCE_ERROR_CODES: &[(&str, c_int, &std::ffi::CStr)] = &[
126    ("POUNCE_OK", POUNCE_OK, c"success"),
127    ("POUNCE_ERR_NULL", POUNCE_ERR_NULL, c"NULL handle or array"),
128    ("POUNCE_ERR_COUNT", POUNCE_ERR_COUNT, c"count out of range"),
129    (
130        "POUNCE_ERR_OUT_OF_RANGE",
131        POUNCE_ERR_OUT_OF_RANGE,
132        c"index out of range",
133    ),
134    (
135        "POUNCE_ERR_DUPLICATE",
136        POUNCE_ERR_DUPLICATE,
137        c"index given more than once",
138    ),
139    ("POUNCE_ERR_NAN", POUNCE_ERR_NAN, c"NaN value"),
140    (
141        "POUNCE_ERR_FLAGS",
142        POUNCE_ERR_FLAGS,
143        c"unknown bit in flags",
144    ),
145    (
146        "POUNCE_ERR_ARGUMENT",
147        POUNCE_ERR_ARGUMENT,
148        c"argument outside its domain",
149    ),
150    (
151        "POUNCE_ERR_INTERNAL",
152        POUNCE_ERR_INTERNAL,
153        c"internal error; nothing was changed",
154    ),
155    (
156        "POUNCE_ERR_VAR_ALREADY_PAIRED",
157        POUNCE_ERR_VAR_ALREADY_PAIRED,
158        c"variable already in a complementarity pair",
159    ),
160    (
161        "POUNCE_ERR_ROW_ALREADY_PAIRED",
162        POUNCE_ERR_ROW_ALREADY_PAIRED,
163        c"row already in a complementarity pair",
164    ),
165    (
166        "POUNCE_ERR_SHAPE",
167        POUNCE_ERR_SHAPE,
168        c"complementarity pair has no supported shape",
169    ),
170    (
171        "POUNCE_ERR_ROW_NOT_FREE",
172        POUNCE_ERR_ROW_NOT_FREE,
173        c"row must stay free (no finite bound)",
174    ),
175    (
176        "POUNCE_ERR_VAR_BOUNDS",
177        POUNCE_ERR_VAR_BOUNDS,
178        c"variable bounds NaN or crossed",
179    ),
180    (
181        "POUNCE_ERR_PAIR_CHANGED",
182        POUNCE_ERR_PAIR_CHANGED,
183        c"bound update would change a declared pair",
184    ),
185    (
186        "POUNCE_ERR_COMPLEMENTS_CLASS",
187        POUNCE_ERR_COMPLEMENTS_CLASS,
188        c"bound update would change a Complements variable's class",
189    ),
190    (
191        "POUNCE_ERR_WORKING_SET",
192        POUNCE_ERR_WORKING_SET,
193        c"bound update contradicts the staged working set",
194    ),
195    (
196        "POUNCE_ERR_INCOMPLETE",
197        POUNCE_ERR_INCOMPLETE,
198        c"QP incomplete: no objective, or rows never set",
199    ),
200    (
201        "POUNCE_ERR_CONES",
202        POUNCE_ERR_CONES,
203        c"cones do not partition the inequality rows",
204    ),
205    (
206        "POUNCE_ERR_OPTIONS",
207        POUNCE_ERR_OPTIONS,
208        c"option the selected engine would drop",
209    ),
210    (
211        "POUNCE_ERR_INDEFINITE",
212        POUNCE_ERR_INDEFINITE,
213        c"P not certified positive semidefinite",
214    ),
215];
216
217/// The message for a `POUNCE_OK` / `POUNCE_ERR_*` code, as a static
218/// NUL-terminated string the caller must not free; `"unknown error code"`
219/// for any other value. Never NULL.
220///
221/// Only the shared codes are covered: a non-negative function-specific
222/// result (a QP status, an `AddPounceComplementsConstraint` outcome) other
223/// than `POUNCE_OK` reads as unknown.
224///
225/// # Safety
226///
227/// None required: any `code` is accepted. It is `unsafe` only to match the
228/// rest of the exported surface.
229#[unsafe(no_mangle)]
230pub unsafe extern "C" fn PounceErrorString(code: c_int) -> *const c_char {
231    POUNCE_ERROR_CODES
232        .iter()
233        .find(|(_, c, _)| *c == code)
234        .map_or(c"unknown error code", |(_, _, msg)| *msg)
235        .as_ptr()
236}