1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
//! A C ABI, so a C++ project can embed gridoxide.
//!
//! The **mirror image** of the rest of this crate's FFI. `sparse_klu`,
//! `sparse_pardiso`, `opf::highs` and `opf::ipopt` all consume a C header to
//! reach a library; this module produces one so a library can reach us.
//! `include/gridoxide.h` is generated from what is here.
//!
//! `src/python.rs` is the other binding, and its curation decisions
//! are followed rather than re-litigated — in particular **an opaque handle
//! exists only where a factorization survives between calls**, everything else
//! being a plain function. That is why there is one handle type here and not
//! several.
//!
//! # Three things a C boundary needs that PyO3 handled for us
//!
//! **1. Panics must not cross it.** A `panic!` unwinding out of an
//! `extern "C"` function aborts the process — it would take the host C++
//! program down with it, from inside a library it merely linked. That is not
//! hypothetical: [`DistributedSlack`](crate::outerloop::DistributedSlack) asserts on a
//! wrong-length participation vector, `YBus::finish` expects its triplets to
//! be well formed, and the PGM conversion unwraps in places. Every exported
//! function therefore runs its body inside [`catch_unwind`], turning an abort
//! into [`GRIDOXIDE_ERR_PANIC`](Status::Panic).
//!
//! **2. A third answer, beside success and failure.** The Rust API uses
//! `Option::None` to mean *"no such answer exists"* — a radial branch has no
//! outage distribution factors, an island with no source has no reference bus.
//! Neither is an error. Collapsing them into one would force a caller to parse
//! message strings to tell "your input was wrong" from "the question does not
//! apply here", so [`Status::NoAnswer`] is its own code.
//!
//! **3. Thread affinity, stated rather than enforced.** A handle owns a
//! [`PersistentSolver`](crate::solver::PersistentSolver), and under the `klu`
//! or `pardiso` features that holds raw pointers into SuiteSparse or MKL state
//! that is not safe for concurrent use. PyO3 encodes this as
//! `#[pyclass(unsendable)]`; C has no equivalent, so the header says it and
//! this module repeats it: **one handle, one thread**. Separate handles on
//! separate threads are fine.
//!
//! # Memory
//!
//! **Nothing allocated here is ever freed by the caller, and vice versa.**
//! Results are written into caller-owned buffers whose length the caller
//! learns from a `_count` function first. That removes the whole category of
//! allocator-mismatch bugs that `malloc`-in-Rust/`free`-in-C++ invites, and it
//! costs nothing: every result this API returns is a flat array indexed by bus
//! or by branch, which is exactly the shape a caller wants anyway.
//!
//! The one exception is the handle itself, which is opaque and freed by
//! [`gridoxide_powerflow_free`](powerflow::gridoxide_powerflow_free).
use RefCell;
use ;
use ;
/// The ABI this build speaks.
///
/// Bumped on any change that alters the meaning or layout of anything crossing
/// the boundary. A prebuilt-artifact consumer — one who did not compile the
/// header and the library together — can call this and refuse to continue if
/// it disagrees with `GRIDOXIDE_ABI_VERSION` in the header it compiled
/// against, which turns a silent struct-layout mismatch into an early, legible
/// failure.
pub const ABI_VERSION: u32 = 1;
/// How a call ended.
///
/// The split between [`InvalidArgument`](Self::InvalidArgument) and the
/// numerical codes mirrors the `PyValueError`/`PyRuntimeError` distinction
/// `src/python.rs` already draws: the first says *you* passed
/// something wrong, the rest say the computation or its environment did not
/// work out.
thread_local!
pub
pub
/// The last error message on this thread, or `NULL` if the last call succeeded.
///
/// # Safety
///
/// The returned pointer is owned by gridoxide and stays valid **only until the
/// next gridoxide call on this thread**. Copy it before calling anything else.
/// Never free it.
pub extern "C"
/// The ABI version this library was built with — see [`ABI_VERSION`].
pub extern "C"
/// The crate version, as a NUL-terminated string. Static; never freed.
pub extern "C"
/// Runs `body`, converting a panic into [`Status::Panic`] instead of letting
/// it unwind out of an `extern "C"` function and abort the process.
///
/// `AssertUnwindSafe` is sound here for a specific reason rather than by
/// convenience: a panic leaves the handle's cached solver and bus state
/// possibly half-updated, but every entry point re-clones its pristine
/// `buses_template` before solving, so the next call starts from a known state
/// regardless. Nothing observes the torn intermediate.
pub
/// Reports a caller-fault with a message, in one expression.
pub
/// Copies `values` into a caller-owned buffer, refusing a length mismatch.
///
/// The length is checked rather than trusted. A caller who allocated for the
/// bus count and passed it to a per-branch accessor would otherwise get a
/// buffer overrun — silent, memory-corrupting, and attributed to the wrong
/// library.
///
/// # Safety
///
/// `out` must point to `len` writable `f64`s.
pub unsafe
/// How one connected component of the network turned out.
///
/// The last two are **not failures**. A real network routinely contains a
/// de-energized pocket, and a solve containing one still returns
/// [`Status::Ok`] — so this is the only place such a pocket becomes visible.
/// Maps an [`IslandStatus`](crate::solver::IslandStatus) onto the ABI's own
/// enum.
///
/// Written out rather than derived from the Rust enum's discriminant, so that
/// reordering that enum — a change with no other consequence — cannot silently
/// renumber a value a compiled C++ binary is comparing against.
pub