Skip to main content

powerio_capi/
lib.rs

1//! PowerIO C ABI, version 7.
2//!
3//! The ABI exposes the same four representation operations as Rust:
4//! [`pio_parse`], [`pio_emit`], [`pio_module_serialize`], and
5//! [`pio_module_deserialize`]. Files and memory enter through [`PioSource`];
6//! files, directories, and memory output use [`PioDestination`]. Values are
7//! identified by canonical structural type names rather than an ordinal enum.
8
9//! # Safety
10//!
11//! Non-null handles must be live handles of the declared type from this library.
12//! Retain creates an independently releasable handle. Release consumes one owned
13//! handle and must not overlap any use of that same raw handle. Concurrent
14//! immutable access is permitted. Module mutation requires exclusive access,
15//! including exclusion of retain calls, until the call returns.
16//!
17//! Every non-null input pointer must be aligned and readable for its stated
18//! length; output pointers must be aligned and writable for their declared type.
19//! Spans must fit within one allocation and at most `isize::MAX` bytes. Input
20//! memory must remain unchanged while read. Output memory must not alias inputs
21//! or other outputs. Null pointers are accepted only where the operation handles
22//! their absence; a reported pointer error does not validate arbitrary addresses.
23//!
24//! Borrowed views remain valid while their owner remains alive and unchanged.
25//! Module mutation invalidates plain views; independently owned child handles
26//! retain their snapshots. Returned owned handles must use their matching release
27//! function. These requirements apply to direct Rust callers as well as C callers.
28
29use std::ffi::c_char;
30use std::panic::{AssertUnwindSafe, catch_unwind};
31use std::path::PathBuf;
32use std::sync::Arc;
33
34use powerio::{
35    BalancedNetwork, Destination, Diagnostic, EmitResult, EmittedOutput, PioScenarioSet,
36    PioTimeSeries, PioValue, Source,
37};
38use powerio_core::{ComponentId, HistoryEntry, HistoryId, HistoryKind, Producer};
39use powerio_matrix::{
40    AcOpfAssemblyOptions, AcOpfPreparation, AnalysisBranchSource, DcOperatorOptions, DcOperators,
41    DcOpfAssemblyOptions, DcOpfPreparation, PreparedObjective, SparseMatrix, Units,
42    build_ac_opf_preparation, build_dc_opf_preparation,
43};
44use powerio_prob::{
45    ActivePower, ActivePowerUnit, ApparentPower, ApparentPowerUnit, CalculationUpdate,
46    DcPfInstance, LoadAllocation, NetworkUpdate, OperatingPointUpdate, ReactivePower,
47    ReactivePowerUnit, UpdateChange, UpdatedField, apply_bus_load_active_power, apply_updates,
48};
49use powerio_tx::BranchSusceptanceFormula;
50
51use crate::diagnostics::codes;
52
53pub mod diagnostics;
54
55/// C ABI version.
56pub const PIO_ABI_VERSION: u32 = 7;
57
58// ---- views -----------------------------------------------------------------
59
60/// Borrowed UTF-8 bytes. The bytes need not end in NUL.
61#[repr(C)]
62#[derive(Clone, Copy)]
63pub struct PioStringView {
64    pub data: *const c_char,
65    pub len: usize,
66}
67
68impl PioStringView {
69    const EMPTY: Self = Self {
70        data: std::ptr::null(),
71        len: 0,
72    };
73
74    fn new(text: &str) -> Self {
75        Self {
76            data: text.as_ptr().cast(),
77            len: text.len(),
78        }
79    }
80}
81
82/// One borrowed source byte range from a diagnostic.
83#[repr(C)]
84#[derive(Clone, Copy)]
85pub struct PioDiagnosticSpanView {
86    pub source: PioStringView,
87    pub byte_start: u64,
88    pub byte_end: u64,
89}
90
91/// Program identity recorded with one module.
92#[repr(C)]
93#[derive(Clone, Copy)]
94pub struct PioModuleProducerView {
95    pub name: PioStringView,
96    pub version: PioStringView,
97}
98
99/// One durable source descriptor recorded with a module.
100#[repr(C)]
101#[derive(Clone, Copy)]
102pub struct PioModuleSourceView {
103    pub id: PioStringView,
104    pub name: PioStringView,
105    pub byte_length: u64,
106    pub format: PioStringView,
107    pub has_format: bool,
108    pub digest_algorithm: PioStringView,
109    pub digest: PioStringView,
110    pub has_digest: bool,
111}
112
113/// One borrowed source byte range from a source map entry.
114#[repr(C)]
115#[derive(Clone, Copy)]
116pub struct PioSourceSpanView {
117    pub source: PioStringView,
118    pub byte_start: u64,
119    pub byte_end: u64,
120}
121
122/// One typed value target and its relation to source bytes.
123#[repr(C)]
124#[derive(Clone, Copy)]
125pub struct PioModuleSourceMapEntryView {
126    pub target: PioStringView,
127    pub relation: PioStringView,
128    pub span_count: usize,
129}
130
131/// One operation recorded in module history.
132#[repr(C)]
133#[derive(Clone, Copy)]
134pub struct PioModuleHistoryEntryView {
135    pub id: PioStringView,
136    pub kind: PioStringView,
137    pub name: PioStringView,
138    pub input_type: PioStringView,
139    pub has_input_type: bool,
140    pub output_type: PioStringView,
141    pub has_output_type: bool,
142    pub parameter_count: usize,
143    pub assumption_count: usize,
144    pub loss_count: usize,
145}
146
147/// One named structured parameter in a module history entry.
148#[repr(C)]
149#[derive(Clone, Copy)]
150pub struct PioModuleHistoryParameterView {
151    pub name: PioStringView,
152    pub value_kind: PioStringView,
153}
154
155/// One namespaced structured module extension.
156#[repr(C)]
157#[derive(Clone, Copy)]
158pub struct PioModuleExtensionView {
159    pub namespace: PioStringView,
160    pub value_kind: PioStringView,
161}
162
163/// One structured JSON value stored in module history or extensions.
164#[repr(C)]
165#[derive(Clone, Copy)]
166pub struct PioJsonValueView {
167    pub kind: PioStringView,
168    pub boolean_value: bool,
169    pub number_kind: PioStringView,
170    pub signed_integer_value: i64,
171    pub unsigned_integer_value: u64,
172    pub floating_point_value: f64,
173    pub string_value: PioStringView,
174    pub element_count: usize,
175}
176
177/// One key and value type in a structured JSON object.
178#[repr(C)]
179#[derive(Clone, Copy)]
180pub struct PioJsonObjectEntryView {
181    pub key: PioStringView,
182    pub value_kind: PioStringView,
183}
184
185/// Borrowed binary bytes.
186#[repr(C)]
187#[derive(Clone, Copy)]
188pub struct PioByteView {
189    pub data: *const u8,
190    pub len: usize,
191}
192
193impl PioByteView {
194    const EMPTY: Self = Self {
195        data: std::ptr::null(),
196        len: 0,
197    };
198
199    fn new(bytes: &[u8]) -> Self {
200        Self {
201            data: bytes.as_ptr(),
202            len: bytes.len(),
203        }
204    }
205}
206
207/// Borrowed `double` values.
208#[repr(C)]
209#[derive(Clone, Copy)]
210pub struct PioF64View {
211    pub data: *const f64,
212    pub len: usize,
213}
214
215impl PioF64View {
216    const EMPTY: Self = Self {
217        data: std::ptr::null(),
218        len: 0,
219    };
220
221    fn new(values: &[f64]) -> Self {
222        Self {
223            data: values.as_ptr(),
224            len: values.len(),
225        }
226    }
227}
228
229/// Borrowed `size_t` values.
230#[repr(C)]
231#[derive(Clone, Copy)]
232pub struct PioSizeView {
233    pub data: *const usize,
234    pub len: usize,
235}
236
237impl PioSizeView {
238    const EMPTY: Self = Self {
239        data: std::ptr::null(),
240        len: 0,
241    };
242
243    fn new(values: &[usize]) -> Self {
244        Self {
245            data: values.as_ptr(),
246            len: values.len(),
247        }
248    }
249}
250
251/// One point in a balanced network coordinate space.
252#[repr(C)]
253#[derive(Clone, Copy)]
254pub struct PioBalancedLocationView {
255    pub x: f64,
256    pub y: f64,
257    pub kind: PioStringView,
258    pub has_kind: bool,
259}
260
261/// Coordinate metadata for a balanced network.
262#[repr(C)]
263#[derive(Clone, Copy)]
264pub struct PioBalancedGeoView {
265    pub has_geo: bool,
266    pub space: PioStringView,
267    pub crs: PioStringView,
268    pub has_crs: bool,
269    pub kind: PioStringView,
270    pub has_kind: bool,
271    pub has_canvas: bool,
272    pub canvas_width: f64,
273    pub has_canvas_width: bool,
274    pub canvas_height: f64,
275    pub has_canvas_height: bool,
276    pub canvas_units: PioStringView,
277    pub has_canvas_units: bool,
278}
279
280/// One balanced bus. String and coefficient spans borrow from the network.
281#[repr(C)]
282#[derive(Clone, Copy)]
283pub struct PioBalancedBusView {
284    pub component_id: PioStringView,
285    pub has_component_id: bool,
286    pub id: usize,
287    pub bus_type: PioStringView,
288    pub vm_pu: f64,
289    pub va_degrees: f64,
290    pub base_kv: f64,
291    pub vmax_pu: f64,
292    pub vmin_pu: f64,
293    pub has_emergency_voltage_limits: bool,
294    pub emergency_vmax_pu: f64,
295    pub emergency_vmin_pu: f64,
296    pub area: usize,
297    pub zone: usize,
298    pub name: PioStringView,
299    pub has_name: bool,
300    pub location: PioBalancedLocationView,
301    pub has_location: bool,
302}
303
304/// Voltage dependence attached to one balanced load.
305#[repr(C)]
306#[derive(Clone, Copy)]
307pub struct PioBalancedLoadVoltageModelView {
308    pub kind: PioStringView,
309    pub p_constant_power_mw: f64,
310    pub q_constant_power_mvar: f64,
311    pub p_constant_current_mw: f64,
312    pub q_constant_current_mvar: f64,
313    pub p_constant_impedance_mw: f64,
314    pub q_constant_impedance_mvar: f64,
315    pub exponential_p_mw: f64,
316    pub exponential_q_mvar: f64,
317    pub gamma_p: f64,
318    pub gamma_q: f64,
319    pub nominal_voltage_pu: f64,
320    pub has_nominal_voltage: bool,
321    pub load_type: i32,
322    pub has_load_type: bool,
323    pub scaling: f64,
324    pub has_scaling: bool,
325}
326
327/// One balanced load.
328#[repr(C)]
329#[derive(Clone, Copy)]
330pub struct PioBalancedLoadView {
331    pub component_id: PioStringView,
332    pub has_component_id: bool,
333    pub bus_id: usize,
334    pub p_mw: f64,
335    pub q_mvar: f64,
336    pub in_service: bool,
337    pub voltage_model: PioBalancedLoadVoltageModelView,
338}
339
340/// One switched shunt block.
341#[repr(C)]
342#[derive(Clone, Copy)]
343pub struct PioShuntBlockView {
344    pub steps: u32,
345    pub conductance_mw: f64,
346    pub susceptance_mvar: f64,
347}
348
349/// One balanced shunt.
350#[repr(C)]
351#[derive(Clone, Copy)]
352pub struct PioBalancedShuntView {
353    pub component_id: PioStringView,
354    pub has_component_id: bool,
355    pub bus_id: usize,
356    pub conductance_mw: f64,
357    pub susceptance_mvar: f64,
358    pub in_service: bool,
359    pub section_count: u32,
360    pub has_section_count: bool,
361    pub has_control: bool,
362    pub control_mode: PioStringView,
363    pub control_vmax_pu: f64,
364    pub control_vmin_pu: f64,
365    pub control_bus_id: usize,
366    pub has_control_bus: bool,
367    pub control_reactive_range_percent: f64,
368    pub control_block_count: usize,
369}
370
371/// One named branch MVA rating beyond rating A, B, and C.
372#[repr(C)]
373#[derive(Clone, Copy)]
374pub struct PioBranchRatingView {
375    pub name: PioStringView,
376    pub rate_mva: f64,
377}
378
379/// One balanced branch or two winding transformer.
380#[repr(C)]
381#[derive(Clone, Copy)]
382pub struct PioBalancedBranchView {
383    pub component_id: PioStringView,
384    pub has_component_id: bool,
385    pub name: PioStringView,
386    pub has_name: bool,
387    pub from_bus_id: usize,
388    pub to_bus_id: usize,
389    pub resistance_pu: f64,
390    pub reactance_pu: f64,
391    pub total_charging_susceptance_pu: f64,
392    pub terminal_charging_is_explicit: bool,
393    pub from_conductance_pu: f64,
394    pub from_susceptance_pu: f64,
395    pub to_conductance_pu: f64,
396    pub to_susceptance_pu: f64,
397    pub rate_a_mva: f64,
398    pub rate_b_mva: f64,
399    pub rate_c_mva: f64,
400    pub additional_rating_count: usize,
401    pub has_current_ratings: bool,
402    pub current_rating_a: f64,
403    pub current_rating_b: f64,
404    pub current_rating_c: f64,
405    pub tap_ratio: f64,
406    pub effective_tap_ratio: f64,
407    pub phase_shift_degrees: f64,
408    pub in_service: bool,
409    pub angle_min_degrees: f64,
410    pub angle_max_degrees: f64,
411    pub control: PioTransformerControlView,
412    pub has_control: bool,
413    pub route_point_count: usize,
414    pub has_route: bool,
415}
416
417/// One generator cost curve.
418#[repr(C)]
419#[derive(Clone, Copy)]
420pub struct PioGeneratorCostView {
421    pub model: u8,
422    pub startup: f64,
423    pub shutdown: f64,
424    pub ncost: usize,
425    pub coefficients: PioF64View,
426}
427
428/// One optional generator capability or ramp field.
429#[repr(C)]
430#[derive(Clone, Copy)]
431pub struct PioGeneratorCapabilityView {
432    pub name: PioStringView,
433    pub value: f64,
434    pub has_value: bool,
435}
436
437/// Governor and distributed slack settings for a generator or storage element.
438#[repr(C)]
439#[derive(Clone, Copy)]
440pub struct PioActivePowerControlView {
441    pub participate: bool,
442    pub droop_percent: f64,
443    pub has_droop_percent: bool,
444    pub participation_factor: f64,
445    pub has_participation_factor: bool,
446    pub minimum_target_active_power_mw: f64,
447    pub has_minimum_target_active_power: bool,
448    pub maximum_target_active_power_mw: f64,
449    pub has_maximum_target_active_power: bool,
450}
451
452/// One balanced generator.
453#[repr(C)]
454#[derive(Clone, Copy)]
455pub struct PioBalancedGeneratorView {
456    pub component_id: PioStringView,
457    pub has_component_id: bool,
458    pub bus_id: usize,
459    pub energy_source: PioStringView,
460    pub active_power_mw: f64,
461    pub reactive_power_mvar: f64,
462    pub active_power_max_mw: f64,
463    pub active_power_min_mw: f64,
464    pub reactive_power_max_mvar: f64,
465    pub reactive_power_min_mvar: f64,
466    pub voltage_setpoint_pu: f64,
467    pub machine_base_mva: f64,
468    pub in_service: bool,
469    pub has_cost: bool,
470    pub cost: PioGeneratorCostView,
471    pub regulated_bus_id: usize,
472    pub has_regulated_bus: bool,
473    pub capability_count: usize,
474    pub active_power_control: PioActivePowerControlView,
475    pub has_active_power_control: bool,
476    pub voltage_regulation_on: bool,
477    pub regulating_terminal: PioTerminalReferenceView,
478    pub has_regulating_terminal: bool,
479}
480
481/// One balanced storage element.
482#[repr(C)]
483#[derive(Clone, Copy)]
484pub struct PioBalancedStorageView {
485    pub component_id: PioStringView,
486    pub has_component_id: bool,
487    pub bus_id: usize,
488    pub active_power_mw: f64,
489    pub reactive_power_mvar: f64,
490    pub energy_mwh: f64,
491    pub energy_rating_mwh: f64,
492    pub charge_rating_mw: f64,
493    pub discharge_rating_mw: f64,
494    pub charge_efficiency: f64,
495    pub discharge_efficiency: f64,
496    pub thermal_rating_mva: f64,
497    pub current_rating: f64,
498    pub has_current_rating: bool,
499    pub reactive_power_min_mvar: f64,
500    pub reactive_power_max_mvar: f64,
501    pub resistance_pu: f64,
502    pub reactance_pu: f64,
503    pub active_power_loss_mw: f64,
504    pub reactive_power_loss_mvar: f64,
505    pub in_service: bool,
506    pub active_power_control: PioActivePowerControlView,
507    pub has_active_power_control: bool,
508}
509
510/// Borrowed reference to one numbered equipment terminal.
511#[repr(C)]
512#[derive(Clone, Copy)]
513pub struct PioTerminalReferenceView {
514    pub equipment: PioComponentIdView,
515    pub terminal: u8,
516}
517
518/// Automatic transformer tap or phase control.
519#[repr(C)]
520#[derive(Clone, Copy)]
521pub struct PioTransformerControlView {
522    pub mode: PioStringView,
523    pub enabled: bool,
524    pub controlled_bus_id: usize,
525    pub has_controlled_bus: bool,
526    pub controlled_bus_on_winding_side: bool,
527    pub regulating_terminal: PioTerminalReferenceView,
528    pub has_regulating_terminal: bool,
529    pub tap_min: f64,
530    pub tap_max: f64,
531    pub band_min: f64,
532    pub band_max: f64,
533    pub tap_position_count: u32,
534    pub mva_base: f64,
535    pub winding_connection_angle: f64,
536    pub has_winding_connection_angle: bool,
537}
538
539/// One balanced static VAR compensator.
540#[repr(C)]
541#[derive(Clone, Copy)]
542pub struct PioBalancedStaticVarCompensatorView {
543    pub component_id: PioStringView,
544    pub has_component_id: bool,
545    pub bus_id: usize,
546    pub minimum_susceptance_siemens: f64,
547    pub maximum_susceptance_siemens: f64,
548    pub voltage_setpoint_kv: f64,
549    pub reactive_power_setpoint_mvar: f64,
550    pub regulation_mode: PioStringView,
551    pub regulating: bool,
552    pub regulating_terminal: PioTerminalReferenceView,
553    pub has_regulating_terminal: bool,
554    pub active_power_mw: f64,
555    pub reactive_power_mvar: f64,
556    pub in_service: bool,
557}
558
559/// One balanced transmission switch.
560#[repr(C)]
561#[derive(Clone, Copy)]
562pub struct PioBalancedSwitchView {
563    pub component_id: PioStringView,
564    pub has_component_id: bool,
565    pub from_bus_id: usize,
566    pub to_bus_id: usize,
567    pub closed: bool,
568    pub thermal_rating_mva: f64,
569    pub has_thermal_rating: bool,
570    pub current_rating_a: f64,
571    pub has_current_rating: bool,
572    pub from_active_power_mw: f64,
573    pub has_from_active_power: bool,
574    pub from_reactive_power_mvar: f64,
575    pub has_from_reactive_power: bool,
576    pub to_active_power_mw: f64,
577    pub has_to_active_power: bool,
578    pub to_reactive_power_mvar: f64,
579    pub has_to_reactive_power: bool,
580}
581
582/// One AC terminal converter station of a balanced HVDC line.
583#[repr(C)]
584#[derive(Clone, Copy)]
585pub struct PioBalancedHvdcConverterView {
586    pub component: PioComponentIdView,
587    pub kind: PioStringView,
588    pub loss_factor_percent: f64,
589    pub voltage_regulator_on: bool,
590    pub has_voltage_regulator_on: bool,
591    pub voltage_setpoint_kv: f64,
592    pub has_voltage_setpoint: bool,
593    pub reactive_power_setpoint_mvar: f64,
594    pub has_reactive_power_setpoint: bool,
595    pub power_factor: f64,
596    pub has_power_factor: bool,
597    pub regulating_terminal: PioTerminalReferenceView,
598    pub has_regulating_terminal: bool,
599}
600
601/// One balanced two terminal HVDC line.
602#[repr(C)]
603#[derive(Clone, Copy)]
604pub struct PioBalancedHvdcView {
605    pub component_id: PioStringView,
606    pub has_component_id: bool,
607    pub from_bus_id: usize,
608    pub to_bus_id: usize,
609    pub in_service: bool,
610    pub from_active_power_mw: f64,
611    pub to_active_power_mw: f64,
612    pub from_reactive_power_mvar: f64,
613    pub to_reactive_power_mvar: f64,
614    pub from_voltage_pu: f64,
615    pub to_voltage_pu: f64,
616    pub minimum_active_power_mw: f64,
617    pub maximum_active_power_mw: f64,
618    pub minimum_from_reactive_power_mvar: f64,
619    pub maximum_from_reactive_power_mvar: f64,
620    pub minimum_to_reactive_power_mvar: f64,
621    pub maximum_to_reactive_power_mvar: f64,
622    pub constant_loss_mw: f64,
623    pub proportional_loss: f64,
624    pub resistance_ohm: f64,
625    pub has_resistance: bool,
626    pub nominal_voltage_kv: f64,
627    pub has_nominal_voltage: bool,
628    pub converters_mode: PioStringView,
629    pub has_converters_mode: bool,
630    pub converter1: PioBalancedHvdcConverterView,
631    pub has_converter1: bool,
632    pub converter2: PioBalancedHvdcConverterView,
633    pub has_converter2: bool,
634    pub cost: PioGeneratorCostView,
635    pub has_cost: bool,
636}
637
638/// One winding of a balanced three winding transformer.
639#[repr(C)]
640#[derive(Clone, Copy)]
641pub struct PioThreeWindingTransformerWindingView {
642    pub bus_id: usize,
643    pub tap_ratio: f64,
644    pub phase_shift_degrees: f64,
645    pub nominal_voltage_kv: f64,
646    pub rating_a_mva: f64,
647    pub rating_b_mva: f64,
648    pub rating_c_mva: f64,
649    pub control: PioTransformerControlView,
650    pub has_control: bool,
651}
652
653/// One pairwise impedance of a balanced three winding transformer.
654#[repr(C)]
655#[derive(Clone, Copy)]
656pub struct PioThreeWindingTransformerImpedanceView {
657    pub resistance_pu: f64,
658    pub reactance_pu: f64,
659    pub base_mva: f64,
660}
661
662/// One balanced three winding transformer.
663#[repr(C)]
664#[derive(Clone, Copy)]
665pub struct PioBalancedThreeWindingTransformerView {
666    pub component_id: PioStringView,
667    pub has_component_id: bool,
668    pub name: PioStringView,
669    pub has_name: bool,
670    pub winding_count: usize,
671    pub impedance_count: usize,
672    pub star_voltage_magnitude_pu: f64,
673    pub star_voltage_angle_degrees: f64,
674    pub magnetizing_conductance_pu: f64,
675    pub magnetizing_susceptance_pu: f64,
676    pub in_service: bool,
677}
678
679/// One balanced control area.
680#[repr(C)]
681#[derive(Clone, Copy)]
682pub struct PioBalancedAreaView {
683    pub number: usize,
684    pub slack_bus_id: usize,
685    pub has_slack_bus: bool,
686    pub net_interchange_mw: f64,
687    pub tolerance_mw: f64,
688    pub name: PioStringView,
689    pub has_name: bool,
690    pub component_id: PioStringView,
691    pub has_component_id: bool,
692    pub area_type: PioStringView,
693    pub has_area_type: bool,
694}
695
696/// Exact table lengths in source neutral detailed connectivity.
697#[repr(C)]
698#[derive(Clone, Copy)]
699pub struct PioDetailedConnectivityCountsView {
700    pub omitted_fields: usize,
701    pub component_metadata: usize,
702    pub subnetworks: usize,
703    pub substations: usize,
704    pub voltage_levels: usize,
705    pub bus_breaker_buses: usize,
706    pub calculated_buses: usize,
707    pub connectivity_nodes: usize,
708    pub busbar_sections: usize,
709    pub junctions: usize,
710    pub terminals: usize,
711    pub switches: usize,
712    pub internal_connections: usize,
713    pub operational_limit_groups: usize,
714    pub tap_changers: usize,
715    pub equipment_reactive_limits: usize,
716    pub boundary_lines: usize,
717    pub tie_lines: usize,
718    pub dc_converter_units: usize,
719    pub dc_topological_nodes: usize,
720    pub dc_nodes: usize,
721    pub dc_grounds: usize,
722    pub dc_busbars: usize,
723    pub dc_lines: usize,
724    pub dc_series_devices: usize,
725    pub dc_switches: usize,
726    pub voltage_source_converters: usize,
727    pub line_commutated_converters: usize,
728}
729
730/// One source field that was absent rather than explicitly assigned a value.
731#[repr(C)]
732#[derive(Clone, Copy)]
733pub struct PioOmittedFieldView {
734    pub component: PioComponentIdView,
735    pub field: PioStringView,
736}
737
738/// Reactive limits retained for one equipment record.
739#[repr(C)]
740#[derive(Clone, Copy)]
741pub struct PioEquipmentReactiveLimitsView {
742    pub equipment: PioComponentIdView,
743    pub limits: PioReactiveLimitsView,
744}
745
746/// Source neutral case metadata attached to one subnetwork.
747#[repr(C)]
748#[derive(Clone, Copy)]
749pub struct PioCaseMetadataView {
750    pub case_date: PioStringView,
751    pub has_case_date: bool,
752    pub forecast_distance: i32,
753    pub has_forecast_distance: bool,
754    pub source_model_format: PioStringView,
755    pub has_source_model_format: bool,
756    pub minimum_validation_level: PioStringView,
757    pub has_minimum_validation_level: bool,
758}
759
760/// One PowSybl subnetwork contained directly by the balanced network.
761#[repr(C)]
762#[derive(Clone, Copy)]
763pub struct PioSubnetworkView {
764    pub component: PioComponentIdView,
765    pub parent: PioComponentIdView,
766    pub case_metadata: PioCaseMetadataView,
767    pub component_count: usize,
768}
769
770/// One point of a reactive capability curve.
771#[repr(C)]
772#[derive(Clone, Copy)]
773pub struct PioReactiveCapabilityCurvePointView {
774    pub active_power_mw: f64,
775    pub minimum_reactive_power_mvar: f64,
776    pub maximum_reactive_power_mvar: f64,
777    pub property_count: usize,
778}
779
780/// Min/max or active power dependent reactive limits.
781#[repr(C)]
782#[derive(Clone, Copy)]
783pub struct PioReactiveLimitsView {
784    pub kind: PioStringView,
785    pub minimum_reactive_power_mvar: f64,
786    pub maximum_reactive_power_mvar: f64,
787    pub has_minimum_and_maximum: bool,
788    pub curve_style: PioStringView,
789    pub has_curve_style: bool,
790    pub property_count: usize,
791    pub point_count: usize,
792}
793
794/// Optional generation attached to one PowSybl boundary line.
795#[repr(C)]
796#[derive(Clone, Copy)]
797pub struct PioBoundaryLineGenerationView {
798    pub voltage_regulation_on: bool,
799    pub minimum_active_power_mw: f64,
800    pub has_minimum_active_power: bool,
801    pub maximum_active_power_mw: f64,
802    pub has_maximum_active_power: bool,
803    pub target_active_power_mw: f64,
804    pub has_target_active_power: bool,
805    pub target_reactive_power_mvar: f64,
806    pub has_target_reactive_power: bool,
807    pub target_voltage_kv: f64,
808    pub has_target_voltage: bool,
809    pub reactive_limits: PioReactiveLimitsView,
810    pub has_reactive_limits: bool,
811}
812
813/// One PowSybl boundary line retained beside the balanced calculation view.
814#[repr(C)]
815#[derive(Clone, Copy)]
816pub struct PioBoundaryLineView {
817    pub component: PioComponentIdView,
818    pub voltage_level: PioComponentIdView,
819    pub active_power_setpoint_mw: f64,
820    pub reactive_power_setpoint_mvar: f64,
821    pub resistance_ohm: f64,
822    pub reactance_ohm: f64,
823    pub conductance_siemens: f64,
824    pub susceptance_siemens: f64,
825    pub pairing_key: PioStringView,
826    pub has_pairing_key: bool,
827    pub generation: PioBoundaryLineGenerationView,
828    pub has_generation: bool,
829    pub calculation_load: PioComponentIdView,
830    pub has_calculation_load: bool,
831    pub calculation_generator: PioComponentIdView,
832    pub has_calculation_generator: bool,
833}
834
835/// One PowSybl tie line and the two boundary lines that define it.
836#[repr(C)]
837#[derive(Clone, Copy)]
838pub struct PioTieLineView {
839    pub component: PioComponentIdView,
840    pub boundary_line1: PioComponentIdView,
841    pub boundary_line2: PioComponentIdView,
842    pub calculation_branch: PioComponentIdView,
843    pub has_calculation_branch: bool,
844}
845
846/// Source neutral metadata attached to one stable component identity.
847#[repr(C)]
848#[derive(Clone, Copy)]
849pub struct PioComponentMetadataView {
850    pub component: PioComponentIdView,
851    pub name: PioStringView,
852    pub has_name: bool,
853    pub equipment_container: PioComponentIdView,
854    pub has_equipment_container: bool,
855    pub fictitious: bool,
856    pub alias_count: usize,
857    pub external_identifier_count: usize,
858    pub property_count: usize,
859}
860
861/// One source neutral component alias.
862#[repr(C)]
863#[derive(Clone, Copy)]
864pub struct PioComponentAliasView {
865    pub value: PioStringView,
866    pub alias_type: PioStringView,
867    pub has_alias_type: bool,
868}
869
870/// One source neutral external component identifier.
871#[repr(C)]
872#[derive(Clone, Copy)]
873pub struct PioExternalIdentifierView {
874    pub value: PioStringView,
875    pub authority: PioStringView,
876    pub has_authority: bool,
877}
878
879/// One source neutral string property.
880#[repr(C)]
881#[derive(Clone, Copy)]
882pub struct PioStringPropertyView {
883    pub name: PioStringView,
884    pub value: PioStringView,
885}
886
887/// One source neutral substation.
888#[repr(C)]
889#[derive(Clone, Copy)]
890pub struct PioSubstationView {
891    pub component: PioComponentIdView,
892    pub country: PioStringView,
893    pub has_country: bool,
894    pub operator_name: PioStringView,
895    pub has_operator_name: bool,
896    pub geographical_tag_count: usize,
897}
898
899/// One source neutral voltage level.
900#[repr(C)]
901#[derive(Clone, Copy)]
902pub struct PioVoltageLevelView {
903    pub component: PioComponentIdView,
904    pub substation: PioComponentIdView,
905    pub has_substation: bool,
906    pub nominal_voltage_kv: f64,
907    pub low_voltage_limit_kv: f64,
908    pub has_low_voltage_limit: bool,
909    pub high_voltage_limit_kv: f64,
910    pub has_high_voltage_limit: bool,
911    pub topology_kind: PioStringView,
912    pub bus_count: usize,
913}
914
915/// One source neutral connectivity node.
916#[repr(C)]
917#[derive(Clone, Copy)]
918pub struct PioConnectivityNodeView {
919    pub component: PioComponentIdView,
920    pub voltage_level: PioComponentIdView,
921    pub node_number: i32,
922    pub has_node_number: bool,
923    pub calculated_bus_id: usize,
924    pub has_calculated_bus: bool,
925}
926
927/// One configured bus in bus breaker topology.
928#[repr(C)]
929#[derive(Clone, Copy)]
930pub struct PioBusBreakerBusView {
931    pub component: PioComponentIdView,
932    pub voltage_level: PioComponentIdView,
933    pub calculated_bus_id: usize,
934    pub has_calculated_bus: bool,
935    pub voltage_kv: f64,
936    pub has_voltage: bool,
937    pub angle_degrees: f64,
938    pub has_angle: bool,
939}
940
941/// One calculated bus explicitly recorded in node breaker topology.
942#[repr(C)]
943#[derive(Clone, Copy)]
944pub struct PioCalculatedBusView {
945    pub voltage_level: PioComponentIdView,
946    pub calculated_bus_id: usize,
947    pub node_count: usize,
948    pub voltage_kv: f64,
949    pub has_voltage: bool,
950    pub angle_degrees: f64,
951    pub has_angle: bool,
952}
953
954/// One source neutral busbar section.
955#[repr(C)]
956#[derive(Clone, Copy)]
957pub struct PioBusbarSectionView {
958    pub component: PioComponentIdView,
959    pub voltage_level: PioComponentIdView,
960    pub node: PioComponentIdView,
961}
962
963/// One source neutral CIM junction.
964#[repr(C)]
965#[derive(Clone, Copy)]
966pub struct PioJunctionView {
967    pub component: PioComponentIdView,
968}
969
970/// One source neutral AC terminal.
971#[repr(C)]
972#[derive(Clone, Copy)]
973pub struct PioDetailedTerminalView {
974    pub component: PioComponentIdView,
975    pub has_component: bool,
976    pub equipment: PioComponentIdView,
977    pub terminal: u8,
978    pub voltage_level: PioComponentIdView,
979    pub bus: PioComponentIdView,
980    pub has_bus: bool,
981    pub connectable_bus: PioComponentIdView,
982    pub has_connectable_bus: bool,
983    pub node: PioComponentIdView,
984    pub has_node: bool,
985    pub connected: bool,
986    pub active_power_mw: f64,
987    pub has_active_power: bool,
988    pub reactive_power_mvar: f64,
989    pub has_reactive_power: bool,
990}
991
992/// One source neutral bus breaker or node breaker switch.
993#[repr(C)]
994#[derive(Clone, Copy)]
995pub struct PioTopologySwitchView {
996    pub component: PioComponentIdView,
997    pub voltage_level: PioComponentIdView,
998    pub kind: PioStringView,
999    pub endpoint1_kind: PioStringView,
1000    pub endpoint1: PioComponentIdView,
1001    pub endpoint2_kind: PioStringView,
1002    pub endpoint2: PioComponentIdView,
1003    pub open: bool,
1004    pub retained: bool,
1005}
1006
1007/// One permanent connection between two node breaker connectivity nodes.
1008#[repr(C)]
1009#[derive(Clone, Copy)]
1010pub struct PioInternalConnectionView {
1011    pub voltage_level: PioComponentIdView,
1012    pub node1: PioComponentIdView,
1013    pub node2: PioComponentIdView,
1014}
1015
1016/// One named source neutral loading limit set at an equipment terminal.
1017#[repr(C)]
1018#[derive(Clone, Copy)]
1019pub struct PioOperationalLimitGroupView {
1020    pub equipment: PioComponentIdView,
1021    pub terminal: u8,
1022    pub id: PioStringView,
1023    pub selected: bool,
1024    pub property_count: usize,
1025    pub has_current_limits: bool,
1026    pub current_permanent_limit_a: f64,
1027    pub current_permanent_limit_name: PioStringView,
1028    pub has_current_permanent_limit: bool,
1029    pub has_current_permanent_limit_name: bool,
1030    pub current_temporary_limit_count: usize,
1031    pub has_active_power_limits: bool,
1032    pub active_power_permanent_limit_mw: f64,
1033    pub active_power_permanent_limit_name: PioStringView,
1034    pub has_active_power_permanent_limit: bool,
1035    pub has_active_power_permanent_limit_name: bool,
1036    pub active_power_temporary_limit_count: usize,
1037    pub has_apparent_power_limits: bool,
1038    pub apparent_power_permanent_limit_mva: f64,
1039    pub apparent_power_permanent_limit_name: PioStringView,
1040    pub has_apparent_power_permanent_limit: bool,
1041    pub has_apparent_power_permanent_limit_name: bool,
1042    pub apparent_power_temporary_limit_count: usize,
1043}
1044
1045/// One segment of an AC/DC converter DC voltage droop curve.
1046#[repr(C)]
1047#[derive(Clone, Copy)]
1048pub struct PioDroopCurveSegmentView {
1049    pub minimum_voltage_kv: f64,
1050    pub maximum_voltage_kv: f64,
1051    pub k: f64,
1052}
1053
1054/// One temporary source neutral loading limit.
1055#[repr(C)]
1056#[derive(Clone, Copy)]
1057pub struct PioTemporaryLimitView {
1058    pub name: PioStringView,
1059    pub value: f64,
1060    pub acceptable_duration_seconds: u64,
1061    pub fictitious: bool,
1062}
1063
1064/// One source neutral transformer tap changer.
1065#[repr(C)]
1066#[derive(Clone, Copy)]
1067pub struct PioTapChangerView {
1068    pub component: PioComponentIdView,
1069    pub has_component: bool,
1070    pub transformer: PioComponentIdView,
1071    pub winding: u8,
1072    pub kind: PioStringView,
1073    pub tap_position: i32,
1074    pub has_tap_position: bool,
1075    pub solved_tap_position: i32,
1076    pub has_solved_tap_position: bool,
1077    pub low_tap_position: i32,
1078    pub neutral_tap_position: i32,
1079    pub has_neutral_tap_position: bool,
1080    pub normal_tap_position: i32,
1081    pub has_normal_tap_position: bool,
1082    pub voltage_step_increment_percent: f64,
1083    pub has_voltage_step_increment_percent: bool,
1084    pub load_tap_changing_capabilities: bool,
1085    pub regulating: bool,
1086    pub regulation_mode: PioStringView,
1087    pub has_regulation_mode: bool,
1088    pub regulation_value: f64,
1089    pub has_regulation_value: bool,
1090    pub target_deadband: f64,
1091    pub has_target_deadband: bool,
1092    pub regulation_terminal: PioTerminalReferenceView,
1093    pub has_regulation_terminal: bool,
1094    pub step_count: usize,
1095}
1096
1097/// One source neutral transformer tap changer step.
1098#[repr(C)]
1099#[derive(Clone, Copy)]
1100pub struct PioTapChangerStepView {
1101    pub position: i32,
1102    pub ratio_pu: f64,
1103    pub phase_shift_degrees: f64,
1104    pub resistance_deviation_percent: f64,
1105    pub reactance_deviation_percent: f64,
1106    pub conductance_deviation_percent: f64,
1107    pub susceptance_deviation_percent: f64,
1108}
1109
1110/// One terminal of source neutral DC conducting equipment.
1111#[repr(C)]
1112#[derive(Clone, Copy)]
1113pub struct PioDcTerminalView {
1114    pub component: PioComponentIdView,
1115    pub has_component: bool,
1116    pub sequence_number: u32,
1117    pub has_sequence_number: bool,
1118    pub dc_node: PioComponentIdView,
1119    pub has_dc_node: bool,
1120    pub dc_topological_node: PioComponentIdView,
1121    pub has_dc_topological_node: bool,
1122    pub polarity: PioStringView,
1123    pub has_polarity: bool,
1124    pub connected: bool,
1125    pub has_connected: bool,
1126    pub active_power_mw: f64,
1127    pub has_active_power: bool,
1128    pub current_a: f64,
1129    pub has_current: bool,
1130}
1131
1132/// One source neutral DC conducting equipment record.
1133#[repr(C)]
1134#[derive(Clone, Copy)]
1135pub struct PioDcEquipmentView {
1136    pub component: PioComponentIdView,
1137    pub equipment_container: PioComponentIdView,
1138    pub has_equipment_container: bool,
1139    pub kind: PioStringView,
1140    pub terminal_count: usize,
1141    pub terminal1: PioDcTerminalView,
1142    pub terminal2: PioDcTerminalView,
1143    pub rated_dc_voltage_kv: f64,
1144    pub has_rated_dc_voltage: bool,
1145    pub resistance_ohm: f64,
1146    pub has_resistance: bool,
1147    pub inductance_h: f64,
1148    pub has_inductance: bool,
1149    pub capacitance_f: f64,
1150    pub has_capacitance: bool,
1151    pub length_km: f64,
1152    pub has_length: bool,
1153    pub switch_kind: PioStringView,
1154    pub has_switch_kind: bool,
1155    pub open: bool,
1156    pub has_open: bool,
1157}
1158
1159/// One physical or energized node in source neutral DC connectivity.
1160#[repr(C)]
1161#[derive(Clone, Copy)]
1162pub struct PioDcNodeView {
1163    pub component: PioComponentIdView,
1164    pub kind: PioStringView,
1165    pub nominal_voltage_kv: f64,
1166    pub has_nominal_voltage: bool,
1167    pub voltage_kv: f64,
1168    pub has_voltage: bool,
1169    pub dc_converter_unit: PioComponentIdView,
1170    pub has_dc_converter_unit: bool,
1171    pub dc_topological_node: PioComponentIdView,
1172    pub has_dc_topological_node: bool,
1173}
1174
1175/// One source neutral DC converter unit.
1176#[repr(C)]
1177#[derive(Clone, Copy)]
1178pub struct PioDcConverterUnitView {
1179    pub component: PioComponentIdView,
1180    pub substation: PioComponentIdView,
1181    pub has_substation: bool,
1182    pub operation_mode: PioStringView,
1183}
1184
1185/// One source neutral AC/DC converter.
1186#[repr(C)]
1187#[derive(Clone, Copy)]
1188pub struct PioAcDcConverterView {
1189    pub component: PioComponentIdView,
1190    pub kind: PioStringView,
1191    pub dc_converter_unit: PioComponentIdView,
1192    pub has_dc_converter_unit: bool,
1193    pub dc_terminal1: PioDcTerminalView,
1194    pub dc_terminal2: PioDcTerminalView,
1195    pub base_apparent_power_mva: f64,
1196    pub has_base_apparent_power: bool,
1197    pub minimum_active_power_mw: f64,
1198    pub has_minimum_active_power: bool,
1199    pub maximum_active_power_mw: f64,
1200    pub has_maximum_active_power: bool,
1201    pub minimum_dc_voltage_kv: f64,
1202    pub has_minimum_dc_voltage: bool,
1203    pub maximum_dc_voltage_kv: f64,
1204    pub has_maximum_dc_voltage: bool,
1205    pub rated_dc_voltage_kv: f64,
1206    pub has_rated_dc_voltage: bool,
1207    pub valve_u0_kv: f64,
1208    pub has_valve_u0: bool,
1209    pub number_of_valves: u32,
1210    pub has_number_of_valves: bool,
1211    pub idle_loss_mw: f64,
1212    pub has_idle_loss: bool,
1213    pub switching_loss_mw_per_ampere: f64,
1214    pub has_switching_loss: bool,
1215    pub resistive_loss_ohm: f64,
1216    pub has_resistive_loss: bool,
1217    pub control_mode: PioStringView,
1218    pub has_control_mode: bool,
1219    pub active_power_at_pcc_mw: f64,
1220    pub has_active_power_at_pcc: bool,
1221    pub reactive_power_at_pcc_mvar: f64,
1222    pub has_reactive_power_at_pcc: bool,
1223    pub target_active_power_mw: f64,
1224    pub has_target_active_power: bool,
1225    pub target_dc_voltage_kv: f64,
1226    pub has_target_dc_voltage: bool,
1227    pub pcc_terminal: PioTerminalReferenceView,
1228    pub has_pcc_terminal: bool,
1229    pub droop_curve_segment_count: usize,
1230    pub has_droop_curve: bool,
1231    pub droop: f64,
1232    pub has_droop: bool,
1233    pub droop_compensation: f64,
1234    pub has_droop_compensation: bool,
1235    pub q_share: f64,
1236    pub has_q_share: bool,
1237    pub maximum_modulation_index: f64,
1238    pub has_maximum_modulation_index: bool,
1239    pub maximum_valve_current_a: f64,
1240    pub has_maximum_valve_current: bool,
1241    pub dc_current_a: f64,
1242    pub has_dc_current: bool,
1243    pub ac_voltage_kv: f64,
1244    pub has_ac_voltage: bool,
1245    pub dc_voltage_kv: f64,
1246    pub has_dc_voltage: bool,
1247    pub voltage_regulator_on: bool,
1248    pub has_voltage_regulator_on: bool,
1249    pub voltage_setpoint_kv: f64,
1250    pub has_voltage_setpoint: bool,
1251    pub reactive_power_setpoint_mvar: f64,
1252    pub has_reactive_power_setpoint: bool,
1253    pub reactive_limits: PioReactiveLimitsView,
1254    pub has_reactive_limits: bool,
1255    pub pole_loss_active_power_mw: f64,
1256    pub has_pole_loss_active_power: bool,
1257    pub reactive_model: PioStringView,
1258    pub has_reactive_model: bool,
1259    pub power_factor: f64,
1260    pub has_power_factor: bool,
1261    pub operating_mode: PioStringView,
1262    pub has_operating_mode: bool,
1263    pub rated_dc_current_a: f64,
1264    pub has_rated_dc_current: bool,
1265    pub minimum_alpha_degrees: f64,
1266    pub has_minimum_alpha: bool,
1267    pub maximum_alpha_degrees: f64,
1268    pub has_maximum_alpha: bool,
1269    pub minimum_gamma_degrees: f64,
1270    pub has_minimum_gamma: bool,
1271    pub maximum_gamma_degrees: f64,
1272    pub has_maximum_gamma: bool,
1273    pub target_alpha_degrees: f64,
1274    pub has_target_alpha: bool,
1275    pub target_gamma_degrees: f64,
1276    pub has_target_gamma: bool,
1277    pub target_dc_current_a: f64,
1278    pub has_target_dc_current: bool,
1279    pub alpha_degrees: f64,
1280    pub has_alpha: bool,
1281    pub gamma_degrees: f64,
1282    pub has_gamma: bool,
1283    pub delta_degrees: f64,
1284    pub has_delta: bool,
1285    pub uf_kv: f64,
1286    pub has_uf: bool,
1287    pub uv_kv: f64,
1288    pub has_uv: bool,
1289}
1290
1291/// One point in a multiconductor network coordinate space.
1292#[repr(C)]
1293#[derive(Clone, Copy)]
1294pub struct PioMulticonductorLocationView {
1295    pub x: f64,
1296    pub y: f64,
1297    pub kind: PioStringView,
1298    pub has_kind: bool,
1299}
1300
1301/// Coordinate metadata for a multiconductor network.
1302#[repr(C)]
1303#[derive(Clone, Copy)]
1304pub struct PioMulticonductorGeoView {
1305    pub has_geo: bool,
1306    pub space: PioStringView,
1307    pub crs: PioStringView,
1308    pub has_crs: bool,
1309    pub kind: PioStringView,
1310    pub has_kind: bool,
1311    pub has_canvas: bool,
1312    pub canvas_width: f64,
1313    pub has_canvas_width: bool,
1314    pub canvas_height: f64,
1315    pub has_canvas_height: bool,
1316    pub canvas_units: PioStringView,
1317    pub has_canvas_units: bool,
1318}
1319
1320/// Exact table lengths in a multiconductor network.
1321///
1322/// Source extension `extras` maps are not exposed through ABI 7. They are
1323/// retained by PowerIO for same format emission but are not PowerIO domain data.
1324#[repr(C)]
1325#[derive(Clone, Copy)]
1326pub struct PioMulticonductorNetworkCountsView {
1327    pub buses: usize,
1328    pub line_codes: usize,
1329    pub lines: usize,
1330    pub switches: usize,
1331    pub transformers: usize,
1332    pub loads: usize,
1333    pub generators: usize,
1334    pub inverter_based_resources: usize,
1335    pub control_profiles: usize,
1336    pub shunts: usize,
1337    pub capacitors: usize,
1338    pub voltage_sources: usize,
1339    pub untyped_objects: usize,
1340    pub commands: usize,
1341    pub options: usize,
1342}
1343
1344/// One multiconductor bus.
1345#[repr(C)]
1346#[derive(Clone, Copy)]
1347pub struct PioMulticonductorBusView {
1348    pub id: PioStringView,
1349    pub terminal_count: usize,
1350    pub grounded_terminal_count: usize,
1351    pub voltage_min_v: f64,
1352    pub has_voltage_min: bool,
1353    pub voltage_max_v: f64,
1354    pub has_voltage_max: bool,
1355    /// Nonuniform phase bounds, in phase-terminal order, used when the scalar is absent.
1356    pub phase_to_ground_voltage_min_v: PioF64View,
1357    pub has_phase_to_ground_voltage_min: bool,
1358    pub phase_to_ground_voltage_max_v: PioF64View,
1359    pub has_phase_to_ground_voltage_max: bool,
1360    pub phase_to_neutral_voltage_min_v: PioF64View,
1361    pub has_phase_to_neutral_voltage_min: bool,
1362    pub phase_to_neutral_voltage_max_v: PioF64View,
1363    pub has_phase_to_neutral_voltage_max: bool,
1364    pub phase_to_phase_voltage_min_v: PioF64View,
1365    pub has_phase_to_phase_voltage_min: bool,
1366    pub phase_to_phase_voltage_max_v: PioF64View,
1367    pub has_phase_to_phase_voltage_max: bool,
1368    pub positive_sequence_voltage_min_v: f64,
1369    pub has_positive_sequence_voltage_min: bool,
1370    pub positive_sequence_voltage_max_v: f64,
1371    pub has_positive_sequence_voltage_max: bool,
1372    pub negative_sequence_voltage_max_v: f64,
1373    pub has_negative_sequence_voltage_max: bool,
1374    pub zero_sequence_voltage_max_v: f64,
1375    pub has_zero_sequence_voltage_max: bool,
1376    pub neutral_to_ground_voltage_max_v: f64,
1377    pub has_neutral_to_ground_voltage_max: bool,
1378    pub location: PioMulticonductorLocationView,
1379    pub has_location: bool,
1380}
1381
1382/// One multiconductor line code.
1383#[repr(C)]
1384#[derive(Clone, Copy)]
1385pub struct PioMulticonductorLineCodeView {
1386    pub name: PioStringView,
1387    pub conductor_count: usize,
1388    pub resistance_matrix_row_count: usize,
1389    pub reactance_matrix_row_count: usize,
1390    pub conductance_from_matrix_row_count: usize,
1391    pub susceptance_from_matrix_row_count: usize,
1392    pub conductance_to_matrix_row_count: usize,
1393    pub susceptance_to_matrix_row_count: usize,
1394    pub current_limit_a: PioF64View,
1395    pub has_current_limit: bool,
1396    pub apparent_power_limit_va: PioF64View,
1397    pub has_apparent_power_limit: bool,
1398    pub source: PioStringView,
1399    pub has_source: bool,
1400}
1401
1402/// One multiconductor line.
1403#[repr(C)]
1404#[derive(Clone, Copy)]
1405pub struct PioMulticonductorLineView {
1406    pub name: PioStringView,
1407    pub bus_from: PioStringView,
1408    pub bus_to: PioStringView,
1409    pub terminal_map_from_count: usize,
1410    pub terminal_map_to_count: usize,
1411    pub line_code: PioStringView,
1412    pub length_m: f64,
1413    pub route_point_count: usize,
1414    pub has_route: bool,
1415    pub current_limit_a: PioF64View,
1416    pub has_current_limit: bool,
1417    pub apparent_power_limit_va: PioF64View,
1418    pub has_apparent_power_limit: bool,
1419}
1420
1421/// One multiconductor switch.
1422#[repr(C)]
1423#[derive(Clone, Copy)]
1424pub struct PioMulticonductorSwitchView {
1425    pub name: PioStringView,
1426    pub bus_from: PioStringView,
1427    pub bus_to: PioStringView,
1428    pub terminal_map_from_count: usize,
1429    pub terminal_map_to_count: usize,
1430    pub open: bool,
1431    pub current_limit_a: PioF64View,
1432    pub has_current_limit: bool,
1433}
1434
1435/// One multiconductor transformer.
1436#[repr(C)]
1437#[derive(Clone, Copy)]
1438pub struct PioMulticonductorTransformerView {
1439    pub name: PioStringView,
1440    pub winding_count: usize,
1441    pub short_circuit_reactance_percent: PioF64View,
1442    pub phase_count: usize,
1443}
1444
1445/// One winding of a multiconductor transformer.
1446#[repr(C)]
1447#[derive(Clone, Copy)]
1448pub struct PioMulticonductorTransformerWindingView {
1449    pub bus: PioStringView,
1450    pub terminal_map_count: usize,
1451    pub connection: PioStringView,
1452    pub rated_voltage_v: f64,
1453    pub apparent_power_rating_va: f64,
1454    pub resistance_percent: f64,
1455    pub tap: f64,
1456    pub neutral_resistance_ohm: f64,
1457    pub has_neutral_resistance: bool,
1458    pub neutral_reactance_ohm: f64,
1459    pub has_neutral_reactance: bool,
1460}
1461
1462/// One multiconductor load.
1463#[repr(C)]
1464#[derive(Clone, Copy)]
1465pub struct PioMulticonductorLoadView {
1466    pub name: PioStringView,
1467    pub bus: PioStringView,
1468    pub terminal_map_count: usize,
1469    pub configuration: PioStringView,
1470    pub active_power_nominal_w: PioF64View,
1471    pub reactive_power_nominal_var: PioF64View,
1472    pub voltage_model: PioStringView,
1473    pub nominal_voltage_v: PioF64View,
1474    pub active_power_constant_impedance: PioF64View,
1475    pub active_power_constant_current: PioF64View,
1476    pub active_power_constant_power: PioF64View,
1477    pub reactive_power_constant_impedance: PioF64View,
1478    pub reactive_power_constant_current: PioF64View,
1479    pub reactive_power_constant_power: PioF64View,
1480    pub active_power_exponent: PioF64View,
1481    pub reactive_power_exponent: PioF64View,
1482}
1483
1484/// One multiconductor generator.
1485#[repr(C)]
1486#[derive(Clone, Copy)]
1487pub struct PioMulticonductorGeneratorView {
1488    pub name: PioStringView,
1489    pub bus: PioStringView,
1490    pub terminal_map_count: usize,
1491    pub configuration: PioStringView,
1492    pub active_power_nominal_w: PioF64View,
1493    pub reactive_power_nominal_var: PioF64View,
1494    pub active_power_min_w: PioF64View,
1495    pub has_active_power_min: bool,
1496    pub active_power_max_w: PioF64View,
1497    pub has_active_power_max: bool,
1498    pub reactive_power_min_var: PioF64View,
1499    pub has_reactive_power_min: bool,
1500    pub reactive_power_max_var: PioF64View,
1501    pub has_reactive_power_max: bool,
1502    pub active_power_dispatch_cost_per_kwh: PioF64View,
1503    pub has_active_power_dispatch_cost: bool,
1504    pub apparent_power_limit_va: PioF64View,
1505    pub has_apparent_power_limit: bool,
1506    pub current_limit_a: PioF64View,
1507    pub has_current_limit: bool,
1508}
1509
1510/// One inverter based resource.
1511#[repr(C)]
1512#[derive(Clone, Copy)]
1513pub struct PioInverterBasedResourceView {
1514    pub name: PioStringView,
1515    pub bus: PioStringView,
1516    pub terminal_map_count: usize,
1517    pub topology: PioStringView,
1518    pub prime_mover: PioStringView,
1519    pub apparent_power_limit_va: PioF64View,
1520    pub current_limit_a: PioF64View,
1521    pub has_current_limit: bool,
1522    pub active_power_available_w: f64,
1523    pub has_active_power_available: bool,
1524    pub active_power_min_w: PioF64View,
1525    pub has_active_power_min: bool,
1526    pub active_power_max_w: PioF64View,
1527    pub has_active_power_max: bool,
1528    pub reactive_power_min_var: PioF64View,
1529    pub has_reactive_power_min: bool,
1530    pub reactive_power_max_var: PioF64View,
1531    pub has_reactive_power_max: bool,
1532    pub control_profile: PioStringView,
1533    pub has_control_profile: bool,
1534    pub voltage_aggregation: PioStringView,
1535    pub has_voltage_aggregation: bool,
1536}
1537
1538/// One inverter control profile.
1539#[repr(C)]
1540#[derive(Clone, Copy)]
1541pub struct PioControlProfileView {
1542    pub name: PioStringView,
1543    pub has_power_factor: bool,
1544    pub power_factor: f64,
1545    pub has_volt_var: bool,
1546    pub volt_var_voltage_reference: PioStringView,
1547    pub has_volt_var_voltage_reference: bool,
1548    pub volt_var_breakpoints: PioF64View,
1549    pub volt_var_reactive_power_limits: PioF64View,
1550    pub volt_var_reactive_power_unit: PioStringView,
1551    pub has_volt_var_reactive_power_unit: bool,
1552    pub volt_var_reactive_power_reference: PioStringView,
1553    pub has_volt_var_reactive_power_reference: bool,
1554    pub volt_var_active_power_min_for_reactive_power_w: f64,
1555    pub has_volt_var_active_power_min_for_reactive_power: bool,
1556    pub volt_var_active_power_min_for_max_reactive_power_w: f64,
1557    pub has_volt_var_active_power_min_for_max_reactive_power: bool,
1558    pub has_volt_watt: bool,
1559    pub volt_watt_voltage_reference: PioStringView,
1560    pub has_volt_watt_voltage_reference: bool,
1561    pub volt_watt_breakpoints: PioF64View,
1562    pub volt_watt_active_power_limits: PioF64View,
1563    pub volt_watt_active_power_unit: PioStringView,
1564    pub has_volt_watt_active_power_unit: bool,
1565    pub volt_watt_active_power_reference: PioStringView,
1566    pub has_volt_watt_active_power_reference: bool,
1567}
1568
1569/// One multiconductor shunt.
1570#[repr(C)]
1571#[derive(Clone, Copy)]
1572pub struct PioMulticonductorShuntView {
1573    pub name: PioStringView,
1574    pub bus: PioStringView,
1575    pub terminal_map_count: usize,
1576    pub conductance_matrix_row_count: usize,
1577    pub susceptance_matrix_row_count: usize,
1578}
1579
1580/// One multiconductor capacitor.
1581#[repr(C)]
1582#[derive(Clone, Copy)]
1583pub struct PioMulticonductorCapacitorView {
1584    pub name: PioStringView,
1585    pub bus: PioStringView,
1586    pub terminal_map_count: usize,
1587    pub configuration: PioStringView,
1588    pub rated_reactive_power_var: f64,
1589    pub nominal_voltage_v: f64,
1590}
1591
1592/// One multiconductor voltage source.
1593#[repr(C)]
1594#[derive(Clone, Copy)]
1595pub struct PioVoltageSourceView {
1596    pub name: PioStringView,
1597    pub bus: PioStringView,
1598    pub terminal_map_count: usize,
1599    pub voltage_magnitude_v: PioF64View,
1600    pub voltage_angle_rad: PioF64View,
1601    /// Dollars/kWh in phase order, excluding neutral terminals.
1602    pub energy_cost_rate_per_kwh: PioF64View,
1603    pub has_energy_cost_rate: bool,
1604}
1605
1606/// One source object retained without a typed PowerIO representation.
1607#[repr(C)]
1608#[derive(Clone, Copy)]
1609pub struct PioMulticonductorUntypedObjectView {
1610    pub class_name: PioStringView,
1611    pub name: PioStringView,
1612    pub property_count: usize,
1613}
1614
1615/// One property of an untyped source object.
1616#[repr(C)]
1617#[derive(Clone, Copy)]
1618pub struct PioMulticonductorUntypedPropertyView {
1619    pub name: PioStringView,
1620    pub has_name: bool,
1621    pub value: PioStringView,
1622}
1623
1624/// One retained source command.
1625#[repr(C)]
1626#[derive(Clone, Copy)]
1627pub struct PioMulticonductorCommandView {
1628    pub verb: PioStringView,
1629    pub args: PioStringView,
1630}
1631
1632/// One bus boundary specification in a DC power flow instance.
1633#[repr(C)]
1634#[derive(Clone, Copy)]
1635pub struct PioDcBusSpecificationView {
1636    pub bus_id: usize,
1637    pub kind: PioStringView,
1638    pub net_active_power_mw: f64,
1639    pub voltage_angle_degrees: f64,
1640}
1641
1642/// One bus boundary specification in an AC power flow instance.
1643#[repr(C)]
1644#[derive(Clone, Copy)]
1645pub struct PioAcBusSpecificationView {
1646    pub bus_id: usize,
1647    pub kind: PioStringView,
1648    pub net_active_power_mw: f64,
1649    pub net_reactive_power_mvar: f64,
1650    pub voltage_magnitude_pu: f64,
1651    pub voltage_angle_degrees: f64,
1652}
1653
1654/// Shape and conventions of one prepared DC OPF calculation.
1655#[repr(C)]
1656#[derive(Clone, Copy)]
1657pub struct PioDcOpfPreparationView {
1658    pub name: PioStringView,
1659    pub bus_count: usize,
1660    pub generator_count: usize,
1661    pub branch_count: usize,
1662    pub source_generator_count: usize,
1663    pub source_branch_count: usize,
1664    pub base_mva: f64,
1665    pub units: PioStringView,
1666    pub branch_susceptance_formula: PioStringView,
1667    pub objective: PioStringView,
1668    pub skip_zero_impedance: bool,
1669    pub synthesize_unrated_limits: bool,
1670    pub correct_angle_difference_bounds: bool,
1671    pub reference_bus_count: usize,
1672    pub skipped_zero_impedance_count: usize,
1673}
1674
1675/// One dense bus row in a DC OPF preparation.
1676#[repr(C)]
1677#[derive(Clone, Copy)]
1678pub struct PioDcOpfBusView {
1679    pub bus_id: usize,
1680    pub analysis_row: usize,
1681    pub source_row: usize,
1682    pub has_source_row: bool,
1683    pub active_power_demand: f64,
1684    pub shunt_conductance: f64,
1685    pub phase_shift_injection: f64,
1686}
1687
1688/// One generator row in a DC OPF preparation.
1689#[repr(C)]
1690#[derive(Clone, Copy)]
1691pub struct PioDcOpfGeneratorView {
1692    pub component_id: PioStringView,
1693    pub bus_index: usize,
1694    pub analysis_row: usize,
1695    pub source_row: usize,
1696    pub has_source_row: bool,
1697    pub quadratic_cost: f64,
1698    pub linear_cost: f64,
1699    pub constant_cost: f64,
1700    pub has_piecewise_linear_cost: bool,
1701    pub piecewise_linear_power: PioF64View,
1702    pub piecewise_linear_value: PioF64View,
1703    pub active_power_max: f64,
1704    pub active_power_min: f64,
1705    pub capability_active: bool,
1706}
1707
1708/// One active branch row in a DC OPF preparation.
1709#[repr(C)]
1710#[derive(Clone, Copy)]
1711pub struct PioDcOpfBranchView {
1712    pub component_id: PioStringView,
1713    pub from_bus_index: usize,
1714    pub to_bus_index: usize,
1715    pub susceptance_magnitude: f64,
1716    pub phase_shift_radians: f64,
1717    pub active_power_max: f64,
1718    pub angle_difference_min_radians: f64,
1719    pub angle_difference_max_radians: f64,
1720    pub analysis_row: usize,
1721    pub source_kind: PioStringView,
1722    pub source_row: usize,
1723    pub winding: usize,
1724    pub has_winding: bool,
1725    pub thermal_limit_active: bool,
1726    pub angle_bound_active: bool,
1727}
1728
1729/// Shape and conventions of one prepared AC OPF calculation.
1730#[repr(C)]
1731#[derive(Clone, Copy)]
1732pub struct PioAcOpfPreparationView {
1733    pub name: PioStringView,
1734    pub bus_count: usize,
1735    pub generator_count: usize,
1736    pub storage_count: usize,
1737    pub branch_count: usize,
1738    pub source_generator_count: usize,
1739    pub source_branch_count: usize,
1740    pub base_mva: f64,
1741    pub units: PioStringView,
1742    pub objective: PioStringView,
1743    pub skip_zero_impedance: bool,
1744    pub synthesize_unrated_limits: bool,
1745    pub correct_angle_difference_bounds: bool,
1746    pub reference_bus_count: usize,
1747    pub skipped_zero_impedance_count: usize,
1748}
1749
1750/// One dense bus row in an AC OPF preparation.
1751#[repr(C)]
1752#[derive(Clone, Copy)]
1753pub struct PioAcOpfBusView {
1754    pub bus_id: usize,
1755    pub analysis_row: usize,
1756    pub source_row: usize,
1757    pub has_source_row: bool,
1758    pub active_power_demand: f64,
1759    pub reactive_power_demand: f64,
1760    pub shunt_conductance: f64,
1761    pub shunt_susceptance: f64,
1762    pub voltage_magnitude_min_pu: f64,
1763    pub voltage_magnitude_max_pu: f64,
1764    pub initial_voltage_magnitude_pu: f64,
1765    pub initial_voltage_angle_radians: f64,
1766    pub voltage_bound_active: bool,
1767}
1768
1769/// One generator row in an AC OPF preparation.
1770#[repr(C)]
1771#[derive(Clone, Copy)]
1772pub struct PioAcOpfGeneratorView {
1773    pub component_id: PioStringView,
1774    pub bus_index: usize,
1775    pub analysis_row: usize,
1776    pub source_row: usize,
1777    pub has_source_row: bool,
1778    pub quadratic_cost: f64,
1779    pub linear_cost: f64,
1780    pub constant_cost: f64,
1781    pub has_piecewise_linear_cost: bool,
1782    pub piecewise_linear_power: PioF64View,
1783    pub piecewise_linear_value: PioF64View,
1784    pub active_power_max: f64,
1785    pub active_power_min: f64,
1786    pub reactive_power_max: f64,
1787    pub reactive_power_min: f64,
1788    pub initial_active_power: f64,
1789    pub initial_reactive_power: f64,
1790    pub voltage_magnitude_setpoint_pu: f64,
1791    pub capability_active: bool,
1792}
1793
1794/// One storage row in an AC OPF preparation.
1795#[repr(C)]
1796#[derive(Clone, Copy)]
1797pub struct PioAcOpfStorageView {
1798    pub component_id: PioStringView,
1799    pub bus_index: usize,
1800    pub source_row: usize,
1801    pub initial_active_power: f64,
1802    pub initial_reactive_power: f64,
1803    pub energy: f64,
1804    pub energy_rating: f64,
1805    pub charge_rating: f64,
1806    pub discharge_rating: f64,
1807    pub charge_efficiency: f64,
1808    pub discharge_efficiency: f64,
1809    pub apparent_power_max: f64,
1810    pub reactive_power_min: f64,
1811    pub reactive_power_max: f64,
1812    pub resistance_pu: f64,
1813    pub reactance_pu: f64,
1814    pub active_power_loss: f64,
1815    pub reactive_power_loss: f64,
1816    pub in_service: bool,
1817}
1818
1819/// One active branch row in an AC OPF preparation.
1820#[repr(C)]
1821#[derive(Clone, Copy)]
1822pub struct PioAcOpfBranchView {
1823    pub component_id: PioStringView,
1824    pub from_bus_index: usize,
1825    pub to_bus_index: usize,
1826    pub series_conductance: f64,
1827    pub series_susceptance: f64,
1828    pub from_conductance: f64,
1829    pub from_susceptance: f64,
1830    pub to_conductance: f64,
1831    pub to_susceptance: f64,
1832    pub tap_ratio: f64,
1833    pub phase_shift_radians: f64,
1834    pub apparent_power_max: f64,
1835    pub angle_difference_min_radians: f64,
1836    pub angle_difference_max_radians: f64,
1837    pub analysis_row: usize,
1838    pub source_kind: PioStringView,
1839    pub source_row: usize,
1840    pub winding: usize,
1841    pub has_winding: bool,
1842    pub thermal_limit_active: bool,
1843    pub angle_bound_active: bool,
1844}
1845
1846/// One typed objective term.
1847#[repr(C)]
1848#[derive(Clone, Copy)]
1849pub struct PioObjectiveTermView {
1850    pub kind: PioStringView,
1851}
1852
1853/// One active constraint family and its element selection.
1854#[repr(C)]
1855#[derive(Clone, Copy)]
1856pub struct PioActiveConstraintView {
1857    pub family: PioStringView,
1858    pub selection: PioStringView,
1859    pub identity_count: usize,
1860}
1861
1862/// One prescribed multiconductor load terminal power.
1863#[repr(C)]
1864#[derive(Clone, Copy)]
1865pub struct PioPrescribedTerminalPowerView {
1866    pub load: PioStringView,
1867    pub terminal_count: usize,
1868    pub voltage_model: PioStringView,
1869}
1870
1871/// One terminal of a prescribed multiconductor load.
1872#[repr(C)]
1873#[derive(Clone, Copy)]
1874pub struct PioTerminalPowerView {
1875    pub terminal: PioStringView,
1876    pub active_power_w: f64,
1877    pub reactive_power_var: f64,
1878    pub nominal_voltage_v: f64,
1879    pub has_nominal_voltage: bool,
1880    pub active_impedance_fraction: f64,
1881    pub active_current_fraction: f64,
1882    pub active_power_fraction: f64,
1883    pub reactive_impedance_fraction: f64,
1884    pub reactive_current_fraction: f64,
1885    pub reactive_power_fraction: f64,
1886    pub active_power_exponent: f64,
1887    pub reactive_power_exponent: f64,
1888}
1889
1890/// One prescribed multiconductor source terminal voltage.
1891#[repr(C)]
1892#[derive(Clone, Copy)]
1893pub struct PioPrescribedSourceVoltageView {
1894    pub source: PioStringView,
1895    pub terminal_count: usize,
1896}
1897
1898/// One terminal of a prescribed multiconductor source.
1899#[repr(C)]
1900#[derive(Clone, Copy)]
1901pub struct PioTerminalVoltageView {
1902    pub terminal: PioStringView,
1903    pub magnitude_v: f64,
1904    pub angle_radians: f64,
1905}
1906
1907/// One isolated multiconductor terminal.
1908#[repr(C)]
1909#[derive(Clone, Copy)]
1910pub struct PioIsolatedTerminalView {
1911    pub bus: PioStringView,
1912    pub terminal: PioStringView,
1913}
1914
1915/// One active multiconductor equipment control.
1916#[repr(C)]
1917#[derive(Clone, Copy)]
1918pub struct PioActiveControlView {
1919    pub kind: PioStringView,
1920    pub component_id: PioStringView,
1921}
1922
1923/// Set sizes and time horizon of one AC SCUC instance.
1924#[repr(C)]
1925#[derive(Clone, Copy)]
1926pub struct PioScucDimensionsView {
1927    pub period_count: usize,
1928    pub device_count: usize,
1929    pub producer_count: usize,
1930    pub consumer_count: usize,
1931    pub shunt_count: usize,
1932    pub branch_switching_cost_count: usize,
1933    pub transformer_control_count: usize,
1934    pub active_reserve_zone_count: usize,
1935    pub reactive_reserve_zone_count: usize,
1936    pub contingency_count: usize,
1937}
1938
1939/// Required SCUC violation costs.
1940#[repr(C)]
1941#[derive(Clone, Copy)]
1942pub struct PioScucViolationCostView {
1943    pub active_power_balance: f64,
1944    pub reactive_power_balance: f64,
1945    pub branch_thermal_limit: f64,
1946    pub energy_requirement: f64,
1947}
1948
1949/// Borrowed stable component identity.
1950#[repr(C)]
1951#[derive(Clone, Copy)]
1952pub struct PioComponentIdView {
1953    pub component_type: PioStringView,
1954    pub local_id: PioStringView,
1955}
1956
1957/// One network element a contingency case bound to. `id.component_type` names
1958/// the table `row` indexes, and every element states it. `id.local_id` is the
1959/// element's own identity, and its `len` is 0 when the network states none for
1960/// the row; read the `len` rather than the `data` pointer, which is
1961/// unspecified at that length. `in_service` is the element's own flag as the
1962/// network states it before the case is applied.
1963#[repr(C)]
1964#[derive(Clone, Copy)]
1965pub struct PioContingencyComponentView {
1966    pub id: PioComponentIdView,
1967    pub row: usize,
1968    pub in_service: bool,
1969}
1970
1971/// Active power ramp limits for one SCUC device, in per unit per hour.
1972#[repr(C)]
1973#[derive(Clone, Copy)]
1974pub struct PioScucRampLimitsView {
1975    pub up_pu_per_hour: f64,
1976    pub down_pu_per_hour: f64,
1977    pub startup_pu_per_hour: f64,
1978    pub shutdown_pu_per_hour: f64,
1979}
1980
1981/// Reserve quantity limits for one SCUC device.
1982#[repr(C)]
1983#[derive(Clone, Copy)]
1984pub struct PioScucReserveLimitsView {
1985    pub regulation_up_pu: f64,
1986    pub regulation_down_pu: f64,
1987    pub synchronized_pu: f64,
1988    pub nonsynchronized_pu: f64,
1989    pub ramping_up_online_pu: f64,
1990    pub ramping_down_online_pu: f64,
1991    pub ramping_up_offline_pu: f64,
1992    pub ramping_down_offline_pu: f64,
1993}
1994
1995/// Initial commitment durations for one SCUC device.
1996#[repr(C)]
1997#[derive(Clone, Copy)]
1998pub struct PioScucInitialCommitmentView {
1999    pub accumulated_up_time_hours: f64,
2000    pub accumulated_down_time_hours: f64,
2001}
2002
2003/// Additional active and reactive power capability relation for one device.
2004#[repr(C)]
2005#[derive(Clone, Copy)]
2006pub struct PioScucReactiveCapabilityView {
2007    pub kind: PioStringView,
2008    pub reactive_power_at_zero_active_power_pu: f64,
2009    pub reactive_power_at_zero_active_power_min_pu: f64,
2010    pub reactive_power_at_zero_active_power_max_pu: f64,
2011    pub slope: f64,
2012    pub slope_min: f64,
2013    pub slope_max: f64,
2014}
2015
2016/// One SCUC producer or consumer.
2017#[repr(C)]
2018#[derive(Clone, Copy)]
2019pub struct PioScucDeviceView {
2020    pub id: PioComponentIdView,
2021    pub kind: PioStringView,
2022    pub initial_on_status: bool,
2023    pub on_cost: f64,
2024    pub startup_cost: f64,
2025    pub shutdown_cost: f64,
2026    pub minimum_up_time_hours: f64,
2027    pub minimum_down_time_hours: f64,
2028    pub ramp_limits: PioScucRampLimitsView,
2029    pub reserve_limits: PioScucReserveLimitsView,
2030    pub initial_commitment: PioScucInitialCommitmentView,
2031    pub reactive_capability: PioScucReactiveCapabilityView,
2032    pub period_count: usize,
2033    pub startup_cost_adjustment_count: usize,
2034    pub startup_limit_count: usize,
2035    pub energy_upper_bound_count: usize,
2036    pub energy_lower_bound_count: usize,
2037}
2038
2039/// One downtime dependent startup cost adjustment.
2040#[repr(C)]
2041#[derive(Clone, Copy)]
2042pub struct PioScucStartupCostAdjustmentView {
2043    pub cost: f64,
2044    pub maximum_down_time_hours: f64,
2045}
2046
2047/// One SCUC device period.
2048#[repr(C)]
2049#[derive(Clone, Copy)]
2050pub struct PioScucDevicePeriodView {
2051    pub on_status_min: bool,
2052    pub on_status_max: bool,
2053    pub active_power_min_pu: f64,
2054    pub active_power_max_pu: f64,
2055    pub reactive_power_min_pu: f64,
2056    pub reactive_power_max_pu: f64,
2057    pub energy_cost_block_count: usize,
2058    pub reserve_costs: PioScucReserveCostsView,
2059}
2060
2061/// One limit on the number of device startups during a time window.
2062#[repr(C)]
2063#[derive(Clone, Copy)]
2064pub struct PioScucStartupLimitView {
2065    pub start_time_hours: f64,
2066    pub end_time_hours: f64,
2067    pub maximum_startups: u64,
2068}
2069
2070/// One energy requirement over a time window, in per unit as defined by GOC3.
2071#[repr(C)]
2072#[derive(Clone, Copy)]
2073pub struct PioScucEnergyRequirementView {
2074    pub start_time_hours: f64,
2075    pub end_time_hours: f64,
2076    pub energy_pu: f64,
2077}
2078
2079/// One piecewise linear active energy cost block.
2080#[repr(C)]
2081#[derive(Clone, Copy)]
2082pub struct PioScucEnergyCostBlockView {
2083    pub marginal_cost: f64,
2084    pub block_size_pu: f64,
2085}
2086
2087/// Reserve costs for one device and one interval, in $/(p.u. h).
2088#[repr(C)]
2089#[derive(Clone, Copy)]
2090pub struct PioScucReserveCostsView {
2091    pub regulation_up: f64,
2092    pub regulation_down: f64,
2093    pub synchronized: f64,
2094    pub nonsynchronized: f64,
2095    pub ramping_up_online: f64,
2096    pub ramping_down_online: f64,
2097    pub ramping_up_offline: f64,
2098    pub ramping_down_offline: f64,
2099    pub reactive_up: f64,
2100    pub reactive_down: f64,
2101}
2102
2103/// Discrete step limits for one SCUC shunt.
2104#[repr(C)]
2105#[derive(Clone, Copy)]
2106pub struct PioScucShuntView {
2107    pub id: PioComponentIdView,
2108    pub conductance_per_step_pu: f64,
2109    pub susceptance_per_step_pu: f64,
2110    pub step_min: i64,
2111    pub step_max: i64,
2112    pub initial_step: i64,
2113}
2114
2115/// Connection and disconnection costs for one switchable AC branch.
2116#[repr(C)]
2117#[derive(Clone, Copy)]
2118pub struct PioScucBranchSwitchingCostView {
2119    pub id: PioComponentIdView,
2120    pub connection_cost: f64,
2121    pub disconnection_cost: f64,
2122}
2123
2124/// Tap ratio and phase shift bounds for one transformer.
2125#[repr(C)]
2126#[derive(Clone, Copy)]
2127pub struct PioScucTransformerControlView {
2128    pub id: PioComponentIdView,
2129    pub tap_ratio_min: f64,
2130    pub tap_ratio_max: f64,
2131    pub phase_shift_min_radians: f64,
2132    pub phase_shift_max_radians: f64,
2133}
2134
2135/// One active power reserve zone.
2136#[repr(C)]
2137#[derive(Clone, Copy)]
2138pub struct PioScucActiveReserveZoneView {
2139    pub id: PioComponentIdView,
2140    pub regulation_up_requirement_fraction: f64,
2141    pub regulation_down_requirement_fraction: f64,
2142    pub synchronized_requirement_fraction: f64,
2143    pub nonsynchronized_requirement_fraction: f64,
2144    pub regulation_up_violation_cost: f64,
2145    pub regulation_down_violation_cost: f64,
2146    pub synchronized_violation_cost: f64,
2147    pub nonsynchronized_violation_cost: f64,
2148    pub ramping_up_violation_cost: f64,
2149    pub ramping_down_violation_cost: f64,
2150    pub period_count: usize,
2151    pub bus_count: usize,
2152}
2153
2154/// One period of an active power reserve zone.
2155#[repr(C)]
2156#[derive(Clone, Copy)]
2157pub struct PioScucActiveReservePeriodView {
2158    pub ramping_up_requirement_pu: f64,
2159    pub ramping_down_requirement_pu: f64,
2160}
2161
2162/// One reactive power reserve zone.
2163#[repr(C)]
2164#[derive(Clone, Copy)]
2165pub struct PioScucReactiveReserveZoneView {
2166    pub id: PioComponentIdView,
2167    pub reactive_up_violation_cost: f64,
2168    pub reactive_down_violation_cost: f64,
2169    pub period_count: usize,
2170    pub bus_count: usize,
2171}
2172
2173/// One period of a reactive power reserve zone.
2174#[repr(C)]
2175#[derive(Clone, Copy)]
2176pub struct PioScucReactiveReservePeriodView {
2177    pub reactive_up_requirement_pu: f64,
2178    pub reactive_down_requirement_pu: f64,
2179}
2180
2181/// One named SCUC contingency.
2182#[repr(C)]
2183#[derive(Clone, Copy)]
2184pub struct PioScucContingencyView {
2185    pub id: PioComponentIdView,
2186    pub component_count: usize,
2187}
2188
2189/// One component removed by a SCUC contingency.
2190#[repr(C)]
2191#[derive(Clone, Copy)]
2192pub struct PioScucContingencyComponentView {
2193    pub id: PioComponentIdView,
2194}
2195
2196// ---- shared handle machinery ----------------------------------------------
2197
2198/// Every handle payload must be shareable across threads: the header lets
2199/// callers move and retain handles from any thread.
2200const fn assert_send_sync<T: Send + Sync>() {}
2201
2202#[repr(transparent)]
2203struct HandleBox<T> {
2204    inner: Arc<T>,
2205}
2206
2207fn handle_new<T>(value: T) -> *mut HandleBox<T> {
2208    Box::into_raw(Box::new(HandleBox {
2209        inner: Arc::new(value),
2210    }))
2211}
2212
2213fn handle_from_arc<T>(inner: Arc<T>) -> *mut HandleBox<T> {
2214    Box::into_raw(Box::new(HandleBox { inner }))
2215}
2216
2217unsafe fn handle_get<'a, T>(raw: *const HandleBox<T>) -> Option<&'a T> {
2218    unsafe { raw.as_ref() }.map(|handle| handle.inner.as_ref())
2219}
2220
2221unsafe fn handle_arc<T>(raw: *const HandleBox<T>) -> Option<Arc<T>> {
2222    unsafe { raw.as_ref() }.map(|handle| Arc::clone(&handle.inner))
2223}
2224
2225unsafe fn handle_retain<T>(raw: *const HandleBox<T>) -> *mut HandleBox<T> {
2226    unsafe { handle_arc(raw) }.map_or(std::ptr::null_mut(), handle_from_arc)
2227}
2228
2229unsafe fn handle_release<T>(raw: *mut HandleBox<T>) {
2230    if !raw.is_null() {
2231        drop(unsafe { Box::from_raw(raw) });
2232    }
2233}
2234
2235macro_rules! opaque_handle {
2236    ($(#[$doc:meta])* $name:ident, $inner:ty) => {
2237        $(#[$doc])*
2238        #[repr(transparent)]
2239        pub struct $name(HandleBox<$inner>);
2240        const _: () = assert_send_sync::<$inner>();
2241
2242        #[allow(dead_code)]
2243        impl $name {
2244            fn new_raw(value: $inner) -> *mut Self {
2245                handle_new(value).cast()
2246            }
2247
2248            fn from_arc(inner: Arc<$inner>) -> *mut Self {
2249                handle_from_arc(inner).cast()
2250            }
2251
2252            unsafe fn get<'a>(raw: *const Self) -> Option<&'a $inner> {
2253                unsafe { handle_get(raw.cast()) }
2254            }
2255
2256            unsafe fn arc(raw: *const Self) -> Option<Arc<$inner>> {
2257                unsafe { handle_arc(raw.cast()) }
2258            }
2259
2260            unsafe fn retain_raw(raw: *const Self) -> *mut Self {
2261                unsafe { handle_retain(raw.cast::<HandleBox<$inner>>()) }.cast::<Self>()
2262            }
2263
2264            unsafe fn release_raw(raw: *mut Self) {
2265                unsafe { handle_release(raw.cast::<HandleBox<$inner>>()) }
2266            }
2267        }
2268    };
2269}
2270
2271// ---- errors and diagnostics ------------------------------------------------
2272
2273struct ErrorInner {
2274    code: String,
2275    message: String,
2276    diagnostics: Arc<DiagnosticsInner>,
2277}
2278
2279struct DiagnosticsInner {
2280    owner: DiagnosticsOwner,
2281}
2282
2283enum DiagnosticsOwner {
2284    Owned(Vec<Diagnostic>),
2285    Module(Arc<ModuleInner>),
2286}
2287
2288impl DiagnosticsInner {
2289    fn records(&self) -> &[Diagnostic] {
2290        match &self.owner {
2291            DiagnosticsOwner::Owned(records) => records,
2292            DiagnosticsOwner::Module(module) => module.module.diagnostics(),
2293        }
2294    }
2295}
2296
2297opaque_handle!(
2298    /// Structured operation failure.
2299    PioError,
2300    ErrorInner
2301);
2302opaque_handle!(
2303    /// Immutable diagnostic list.
2304    PioDiagnostics,
2305    DiagnosticsInner
2306);
2307
2308fn boundary_diagnostic(
2309    info: &'static powerio_core::DiagnosticInfo,
2310    message: impl Into<String>,
2311) -> Diagnostic {
2312    Diagnostic::of(info, message)
2313}
2314
2315fn error_from_diagnostics(message: String, mut records: Vec<Diagnostic>) -> *mut PioError {
2316    if records.is_empty() {
2317        records.push(boundary_diagnostic(
2318            &codes::BIND_CAPI_UNCODED_FAILURE,
2319            message.clone(),
2320        ));
2321    }
2322    let code = records[0].code().to_owned();
2323    PioError::new_raw(ErrorInner {
2324        code,
2325        message,
2326        diagnostics: Arc::new(DiagnosticsInner {
2327            owner: DiagnosticsOwner::Owned(records),
2328        }),
2329    })
2330}
2331
2332fn error_from_core(error: &powerio_core::Error) -> *mut PioError {
2333    error_from_diagnostics(error.to_string(), error.diagnostics().to_vec())
2334}
2335
2336fn error_from_tx(error: &powerio_tx::Error) -> *mut PioError {
2337    let diagnostic = Diagnostic::of(error.code(), error.to_string());
2338    error_from_diagnostics(error.to_string(), vec![diagnostic])
2339}
2340
2341fn error_from_matrix(error: &powerio_matrix::Error) -> *mut PioError {
2342    let diagnostic = Diagnostic::of(error.code(), error.to_string());
2343    error_from_diagnostics(error.to_string(), vec![diagnostic])
2344}
2345
2346fn boundary_error(
2347    info: &'static powerio_core::DiagnosticInfo,
2348    message: impl Into<String>,
2349) -> *mut PioError {
2350    let diagnostic = boundary_diagnostic(info, message);
2351    error_from_diagnostics(
2352        powerio_core::render_diagnostic(&diagnostic),
2353        vec![diagnostic],
2354    )
2355}
2356
2357unsafe fn store_error(slot: *mut *mut PioError, error: *mut PioError) {
2358    if slot.is_null() {
2359        unsafe { PioError::release_raw(error) };
2360    } else {
2361        unsafe { *slot = error };
2362    }
2363}
2364
2365unsafe fn entry<R>(
2366    error: *mut *mut PioError,
2367    fallback: R,
2368    operation: impl FnOnce() -> Result<R, *mut PioError>,
2369) -> R {
2370    if !error.is_null() {
2371        unsafe { *error = std::ptr::null_mut() };
2372    }
2373    match catch_unwind(AssertUnwindSafe(operation)) {
2374        Ok(Ok(value)) => value,
2375        Ok(Err(failure)) => {
2376            unsafe { store_error(error, failure) };
2377            fallback
2378        }
2379        Err(_) => {
2380            let failure = boundary_error(
2381                &codes::BIND_CAPI_PANIC,
2382                "the operation panicked and made no C-visible change",
2383            );
2384            unsafe { store_error(error, failure) };
2385            fallback
2386        }
2387    }
2388}
2389
2390unsafe fn input_bytes<'a>(
2391    data: *const u8,
2392    len: usize,
2393    argument: &str,
2394) -> Result<&'a [u8], *mut PioError> {
2395    if data.is_null() {
2396        if len == 0 {
2397            return Ok(&[]);
2398        }
2399        return Err(boundary_error(
2400            &codes::BIND_CAPI_NULL_ARGUMENT,
2401            format!("{argument} is NULL with a nonzero length"),
2402        ));
2403    }
2404    Ok(unsafe { std::slice::from_raw_parts(data, len) })
2405}
2406
2407unsafe fn input_str<'a>(
2408    data: *const c_char,
2409    len: usize,
2410    argument: &str,
2411) -> Result<&'a str, *mut PioError> {
2412    let bytes = unsafe { input_bytes(data.cast(), len, argument) }?;
2413    std::str::from_utf8(bytes).map_err(|_| {
2414        boundary_error(
2415            &codes::BIND_CAPI_INVALID_UTF8,
2416            format!("{argument} is not valid UTF-8"),
2417        )
2418    })
2419}
2420
2421unsafe fn required_str<'a>(
2422    data: *const c_char,
2423    len: usize,
2424    argument: &str,
2425) -> Result<&'a str, *mut PioError> {
2426    if data.is_null() || len == 0 {
2427        return Err(boundary_error(
2428            &codes::BIND_CAPI_NULL_ARGUMENT,
2429            format!("{argument} is required"),
2430        ));
2431    }
2432    unsafe { input_str(data, len, argument) }
2433}
2434
2435unsafe fn optional_str<'a>(
2436    data: *const c_char,
2437    len: usize,
2438    argument: &str,
2439) -> Result<Option<&'a str>, *mut PioError> {
2440    if data.is_null() {
2441        if len == 0 {
2442            return Ok(None);
2443        }
2444        return Err(boundary_error(
2445            &codes::BIND_CAPI_NULL_ARGUMENT,
2446            format!("{argument} is NULL with a nonzero length"),
2447        ));
2448    }
2449    unsafe { input_str(data, len, argument) }.map(Some)
2450}
2451
2452/// Return the ABI number compiled into this library.
2453#[unsafe(no_mangle)]
2454pub extern "C" fn pio_abi_version() -> u32 {
2455    PIO_ABI_VERSION
2456}
2457
2458/// Return the PowerIO crate version.
2459#[unsafe(no_mangle)]
2460pub extern "C" fn pio_version() -> PioStringView {
2461    PioStringView::new(powerio::VERSION)
2462}
2463
2464/// The failure's stable diagnostic code.
2465///
2466/// # Safety
2467/// Pointers and handles must satisfy the crate-level safety requirements.
2468#[unsafe(no_mangle)]
2469pub unsafe extern "C" fn pio_error_code(error: *const PioError) -> PioStringView {
2470    unsafe { PioError::get(error) }.map_or(PioStringView::EMPTY, |error| {
2471        PioStringView::new(&error.code)
2472    })
2473}
2474
2475/// The rendered failure message.
2476///
2477/// # Safety
2478/// Pointers and handles must satisfy the crate-level safety requirements.
2479#[unsafe(no_mangle)]
2480pub unsafe extern "C" fn pio_error_message(error: *const PioError) -> PioStringView {
2481    unsafe { PioError::get(error) }.map_or(PioStringView::EMPTY, |error| {
2482        PioStringView::new(&error.message)
2483    })
2484}
2485
2486/// The structured diagnostics that caused the failure.
2487///
2488/// # Safety
2489/// Pointers and handles must satisfy the crate-level safety requirements.
2490#[unsafe(no_mangle)]
2491pub unsafe extern "C" fn pio_error_diagnostics(error: *const PioError) -> *mut PioDiagnostics {
2492    unsafe { PioError::get(error) }.map_or(std::ptr::null_mut(), |error| {
2493        PioDiagnostics::from_arc(Arc::clone(&error.diagnostics))
2494    })
2495}
2496
2497///
2498/// # Safety
2499/// Pointers and handles must satisfy the crate-level safety requirements.
2500/// The input handle must remain live and immutable throughout this call.
2501#[unsafe(no_mangle)]
2502pub unsafe extern "C" fn pio_error_retain(error: *const PioError) -> *mut PioError {
2503    unsafe { PioError::retain_raw(error) }
2504}
2505
2506///
2507/// # Safety
2508/// Pointers and handles must satisfy the crate-level safety requirements.
2509/// A non-null handle must be owned and unused by concurrent calls.
2510#[unsafe(no_mangle)]
2511pub unsafe extern "C" fn pio_error_release(error: *mut PioError) {
2512    unsafe { PioError::release_raw(error) };
2513}
2514
2515///
2516/// # Safety
2517/// Pointers and handles must satisfy the crate-level safety requirements.
2518#[unsafe(no_mangle)]
2519pub unsafe extern "C" fn pio_diagnostics_len(diagnostics: *const PioDiagnostics) -> usize {
2520    unsafe { PioDiagnostics::get(diagnostics) }.map_or(0, |values| values.records().len())
2521}
2522
2523///
2524/// # Safety
2525/// Pointers and handles must satisfy the crate-level safety requirements.
2526#[unsafe(no_mangle)]
2527pub unsafe extern "C" fn pio_diagnostic_code(
2528    diagnostics: *const PioDiagnostics,
2529    index: usize,
2530) -> PioStringView {
2531    unsafe { PioDiagnostics::get(diagnostics) }
2532        .and_then(|values| values.records().get(index))
2533        .map_or(PioStringView::EMPTY, |record| {
2534            PioStringView::new(record.code())
2535        })
2536}
2537
2538///
2539/// # Safety
2540/// Pointers and handles must satisfy the crate-level safety requirements.
2541#[unsafe(no_mangle)]
2542pub unsafe extern "C" fn pio_diagnostic_severity(
2543    diagnostics: *const PioDiagnostics,
2544    index: usize,
2545) -> PioStringView {
2546    unsafe { PioDiagnostics::get(diagnostics) }
2547        .and_then(|values| values.records().get(index))
2548        .map_or(PioStringView::EMPTY, |record| {
2549            PioStringView::new(record.severity().as_str())
2550        })
2551}
2552
2553///
2554/// # Safety
2555/// Pointers and handles must satisfy the crate-level safety requirements.
2556#[unsafe(no_mangle)]
2557pub unsafe extern "C" fn pio_diagnostic_message(
2558    diagnostics: *const PioDiagnostics,
2559    index: usize,
2560) -> PioStringView {
2561    unsafe { PioDiagnostics::get(diagnostics) }
2562        .and_then(|values| values.records().get(index))
2563        .map_or(PioStringView::EMPTY, |record| {
2564            PioStringView::new(record.message())
2565        })
2566}
2567
2568/// Whether this diagnostic has a durable identity.
2569///
2570/// # Safety
2571/// Pointers and handles must satisfy the crate-level safety requirements.
2572#[unsafe(no_mangle)]
2573pub unsafe extern "C" fn pio_diagnostic_has_id(
2574    diagnostics: *const PioDiagnostics,
2575    index: usize,
2576) -> bool {
2577    unsafe { PioDiagnostics::get(diagnostics) }
2578        .and_then(|values| values.records().get(index))
2579        .is_some_and(|record| record.id().is_some())
2580}
2581
2582/// Borrow this diagnostic's durable identity, or an empty view when absent.
2583///
2584/// # Safety
2585/// Pointers and handles must satisfy the crate-level safety requirements.
2586#[unsafe(no_mangle)]
2587pub unsafe extern "C" fn pio_diagnostic_id(
2588    diagnostics: *const PioDiagnostics,
2589    index: usize,
2590) -> PioStringView {
2591    unsafe { PioDiagnostics::get(diagnostics) }
2592        .and_then(|values| values.records().get(index))
2593        .and_then(Diagnostic::id)
2594        .map_or(PioStringView::EMPTY, |id| PioStringView::new(id.as_str()))
2595}
2596
2597/// Whether this diagnostic names a value element.
2598///
2599/// # Safety
2600/// Pointers and handles must satisfy the crate-level safety requirements.
2601#[unsafe(no_mangle)]
2602pub unsafe extern "C" fn pio_diagnostic_has_target(
2603    diagnostics: *const PioDiagnostics,
2604    index: usize,
2605) -> bool {
2606    unsafe { PioDiagnostics::get(diagnostics) }
2607        .and_then(|values| values.records().get(index))
2608        .is_some_and(|record| record.target().is_some())
2609}
2610
2611/// Borrow this diagnostic's value element locator, or an empty view when absent.
2612///
2613/// # Safety
2614/// Pointers and handles must satisfy the crate-level safety requirements.
2615#[unsafe(no_mangle)]
2616pub unsafe extern "C" fn pio_diagnostic_target(
2617    diagnostics: *const PioDiagnostics,
2618    index: usize,
2619) -> PioStringView {
2620    unsafe { PioDiagnostics::get(diagnostics) }
2621        .and_then(|values| values.records().get(index))
2622        .and_then(Diagnostic::target)
2623        .map_or(PioStringView::EMPTY, PioStringView::new)
2624}
2625
2626/// Whether this diagnostic carries a suggested action.
2627///
2628/// # Safety
2629/// Pointers and handles must satisfy the crate-level safety requirements.
2630#[unsafe(no_mangle)]
2631pub unsafe extern "C" fn pio_diagnostic_has_suggested_action(
2632    diagnostics: *const PioDiagnostics,
2633    index: usize,
2634) -> bool {
2635    unsafe { PioDiagnostics::get(diagnostics) }
2636        .and_then(|values| values.records().get(index))
2637        .is_some_and(|record| record.suggested_action().is_some())
2638}
2639
2640/// Borrow this diagnostic's suggested action, or an empty view when absent.
2641///
2642/// # Safety
2643/// Pointers and handles must satisfy the crate-level safety requirements.
2644#[unsafe(no_mangle)]
2645pub unsafe extern "C" fn pio_diagnostic_suggested_action(
2646    diagnostics: *const PioDiagnostics,
2647    index: usize,
2648) -> PioStringView {
2649    unsafe { PioDiagnostics::get(diagnostics) }
2650        .and_then(|values| values.records().get(index))
2651        .and_then(Diagnostic::suggested_action)
2652        .map_or(PioStringView::EMPTY, PioStringView::new)
2653}
2654
2655/// Number of source byte ranges attached to this diagnostic.
2656///
2657/// # Safety
2658/// Pointers and handles must satisfy the crate-level safety requirements.
2659#[unsafe(no_mangle)]
2660pub unsafe extern "C" fn pio_diagnostic_n_spans(
2661    diagnostics: *const PioDiagnostics,
2662    index: usize,
2663) -> usize {
2664    unsafe { PioDiagnostics::get(diagnostics) }
2665        .and_then(|values| values.records().get(index))
2666        .map_or(0, |record| record.spans().len())
2667}
2668
2669/// Read one source byte range. The source string borrows from `diagnostics`.
2670///
2671/// # Safety
2672/// Pointers and handles must satisfy the crate-level safety requirements.
2673#[unsafe(no_mangle)]
2674pub unsafe extern "C" fn pio_diagnostic_span(
2675    diagnostics: *const PioDiagnostics,
2676    index: usize,
2677    span_index: usize,
2678    output: *mut PioDiagnosticSpanView,
2679    error: *mut *mut PioError,
2680) -> bool {
2681    unsafe {
2682        entry(error, false, || {
2683            let values = PioDiagnostics::get(diagnostics).ok_or_else(|| {
2684                boundary_error(
2685                    &codes::BIND_CAPI_NULL_HANDLE,
2686                    "PioDiagnostics must not be NULL",
2687                )
2688            })?;
2689            let record = values.records().get(index).ok_or_else(|| {
2690                boundary_error(
2691                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
2692                    format!("diagnostic index {index} is out of range"),
2693                )
2694            })?;
2695            let span = record.spans().get(span_index).ok_or_else(|| {
2696                boundary_error(
2697                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
2698                    format!("diagnostic span index {span_index} is out of range"),
2699                )
2700            })?;
2701            *require_output(output, "output")? = PioDiagnosticSpanView {
2702                source: PioStringView::new(span.source().as_str()),
2703                byte_start: span.byte_start(),
2704                byte_end: span.byte_end(),
2705            };
2706            Ok(true)
2707        })
2708    }
2709}
2710
2711/// Number of other diagnostic identities referenced by this diagnostic.
2712///
2713/// # Safety
2714/// Pointers and handles must satisfy the crate-level safety requirements.
2715#[unsafe(no_mangle)]
2716pub unsafe extern "C" fn pio_diagnostic_n_related(
2717    diagnostics: *const PioDiagnostics,
2718    index: usize,
2719) -> usize {
2720    unsafe { PioDiagnostics::get(diagnostics) }
2721        .and_then(|values| values.records().get(index))
2722        .map_or(0, |record| record.related().len())
2723}
2724
2725/// Borrow one related diagnostic identity, or an empty view when out of range.
2726///
2727/// # Safety
2728/// Pointers and handles must satisfy the crate-level safety requirements.
2729#[unsafe(no_mangle)]
2730pub unsafe extern "C" fn pio_diagnostic_related(
2731    diagnostics: *const PioDiagnostics,
2732    index: usize,
2733    related_index: usize,
2734) -> PioStringView {
2735    unsafe { PioDiagnostics::get(diagnostics) }
2736        .and_then(|values| values.records().get(index))
2737        .and_then(|record| record.related().get(related_index))
2738        .map_or(PioStringView::EMPTY, |id| PioStringView::new(id.as_str()))
2739}
2740
2741/// Serialize this diagnostic's structured details as an owned JSON object.
2742///
2743/// # Safety
2744/// Pointers and handles must satisfy the crate-level safety requirements.
2745#[unsafe(no_mangle)]
2746pub unsafe extern "C" fn pio_diagnostic_details_json(
2747    diagnostics: *const PioDiagnostics,
2748    index: usize,
2749    error: *mut *mut PioError,
2750) -> *mut PioString {
2751    unsafe {
2752        entry(error, std::ptr::null_mut(), || {
2753            let values = PioDiagnostics::get(diagnostics).ok_or_else(|| {
2754                boundary_error(
2755                    &codes::BIND_CAPI_NULL_HANDLE,
2756                    "PioDiagnostics must not be NULL",
2757                )
2758            })?;
2759            let record = values.records().get(index).ok_or_else(|| {
2760                boundary_error(
2761                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
2762                    format!("diagnostic index {index} is out of range"),
2763                )
2764            })?;
2765            serde_json::to_string(record.details())
2766                .map(|text| PioString::new_raw(StringInner { text }))
2767                .map_err(|failure| {
2768                    boundary_error(
2769                        &codes::EMIT_CAPI_SERIALIZE_FAILED,
2770                        format!("cannot serialize diagnostic details: {failure}"),
2771                    )
2772                })
2773        })
2774    }
2775}
2776
2777///
2778/// # Safety
2779/// Pointers and handles must satisfy the crate-level safety requirements.
2780/// The input handle must remain live and immutable throughout this call.
2781#[unsafe(no_mangle)]
2782pub unsafe extern "C" fn pio_diagnostics_retain(
2783    diagnostics: *const PioDiagnostics,
2784) -> *mut PioDiagnostics {
2785    unsafe { PioDiagnostics::retain_raw(diagnostics) }
2786}
2787
2788///
2789/// # Safety
2790/// Pointers and handles must satisfy the crate-level safety requirements.
2791/// A non-null handle must be owned and unused by concurrent calls.
2792#[unsafe(no_mangle)]
2793pub unsafe extern "C" fn pio_diagnostics_release(diagnostics: *mut PioDiagnostics) {
2794    unsafe { PioDiagnostics::release_raw(diagnostics) };
2795}
2796
2797// ---- source and destination ------------------------------------------------
2798
2799opaque_handle!(
2800    /// Acquired file, directory, or named memory bytes.
2801    PioSource,
2802    Source
2803);
2804
2805enum DestinationSpec {
2806    Path(PathBuf),
2807    Memory(String),
2808}
2809
2810impl DestinationSpec {
2811    fn build(&self) -> Result<Destination, powerio_core::Error> {
2812        match self {
2813            Self::Path(path) => Ok(Destination::path(path)),
2814            Self::Memory(root) => Destination::memory(root.clone()),
2815        }
2816    }
2817}
2818
2819opaque_handle!(
2820    /// File, directory, or memory output destination.
2821    PioDestination,
2822    DestinationSpec
2823);
2824
2825struct GeoLayerInner {
2826    layer: powerio::GeoLayer,
2827    diagnostics: Vec<Diagnostic>,
2828}
2829
2830opaque_handle!(
2831    /// Parsed geographic sidecar.
2832    PioGeoLayer,
2833    GeoLayerInner
2834);
2835opaque_handle!(
2836    /// Counts and notes from applying one geographic layer.
2837    PioGeoApplyReport,
2838    powerio::GeoApplyReport
2839);
2840
2841/// Where one PSS/E contingency analysis handle takes its set from: a set the
2842/// handle owns, or a module value the handle projects on every access. A view
2843/// holds the module owner, so it stays readable after the module handle that
2844/// produced it is released.
2845enum ContingencySource<T> {
2846    Owned(T),
2847    Value(ValueInner),
2848}
2849
2850struct ContingencySetInner {
2851    source: ContingencySource<powerio::ContingencySet>,
2852    /// The notes of the reader that produced the set. A view and an expanded
2853    /// set carry none.
2854    diagnostics: Vec<Diagnostic>,
2855}
2856
2857impl ContingencySetInner {
2858    fn set(&self) -> Option<&powerio::ContingencySet> {
2859        match &self.source {
2860            ContingencySource::Owned(set) => Some(set),
2861            ContingencySource::Value(value) => match value.value()? {
2862                PioValue::ContingencySet(set) => Some(set),
2863                _ => None,
2864            },
2865        }
2866    }
2867}
2868
2869struct SubsystemSetInner {
2870    source: ContingencySource<powerio::SubsystemSet>,
2871}
2872
2873impl SubsystemSetInner {
2874    fn set(&self) -> Option<&powerio::SubsystemSet> {
2875        match &self.source {
2876            ContingencySource::Owned(set) => Some(set),
2877            ContingencySource::Value(value) => match value.value()? {
2878                PioValue::SubsystemSet(set) => Some(set),
2879                _ => None,
2880            },
2881        }
2882    }
2883}
2884
2885struct MonitoredSetInner {
2886    source: ContingencySource<powerio::MonitoredSet>,
2887}
2888
2889impl MonitoredSetInner {
2890    fn set(&self) -> Option<&powerio::MonitoredSet> {
2891        match &self.source {
2892            ContingencySource::Owned(set) => Some(set),
2893            ContingencySource::Value(value) => match value.value()? {
2894                PioValue::MonitoredSet(set) => Some(set),
2895                _ => None,
2896            },
2897        }
2898    }
2899}
2900
2901/// One resolution together with the `.con` statement of every action that
2902/// bound to nothing. The statements are written once, when the handle is
2903/// built, so an accessor lends one rather than returning owned text.
2904struct ContingencyResolutionInner {
2905    resolution: powerio::ContingencyResolution,
2906    unresolved_actions: Vec<Vec<String>>,
2907}
2908
2909impl ContingencyResolutionInner {
2910    fn new(resolution: powerio::ContingencyResolution) -> Self {
2911        let unresolved_actions = resolution
2912            .cases
2913            .iter()
2914            .map(|case| {
2915                case.unresolved
2916                    .iter()
2917                    .map(|unresolved| unresolved.action.to_con_statement())
2918                    .collect()
2919            })
2920            .collect();
2921        Self {
2922            resolution,
2923            unresolved_actions,
2924        }
2925    }
2926}
2927
2928opaque_handle!(
2929    /// PSS/E contingency description file: the cases, the automatic
2930    /// specifications, and the `SKIP` rules. A handle taken from a module
2931    /// value holds that module owner alive.
2932    PioContingencySet,
2933    ContingencySetInner
2934);
2935opaque_handle!(
2936    /// PSS/E subsystem description file: the named bus groups. A handle taken
2937    /// from a module value holds that module owner alive.
2938    PioSubsystemSet,
2939    SubsystemSetInner
2940);
2941opaque_handle!(
2942    /// PSS/E monitored element file: the monitor statements. A handle taken
2943    /// from a module value holds that module owner alive.
2944    PioMonitoredSet,
2945    MonitoredSetInner
2946);
2947opaque_handle!(
2948    /// What one contingency set bound to on one network.
2949    PioContingencyResolution,
2950    ContingencyResolutionInner
2951);
2952
2953/// Acquire a file or directory path.
2954///
2955/// # Safety
2956/// Pointers and handles must satisfy the crate-level safety requirements.
2957#[unsafe(no_mangle)]
2958pub unsafe extern "C" fn pio_source_open(
2959    path: *const c_char,
2960    path_len: usize,
2961    error: *mut *mut PioError,
2962) -> *mut PioSource {
2963    unsafe {
2964        entry(error, std::ptr::null_mut(), || {
2965            let path = required_str(path, path_len, "path")?;
2966            Source::open(path)
2967                .map(PioSource::new_raw)
2968                .map_err(|failure| error_from_core(&failure))
2969        })
2970    }
2971}
2972
2973/// Retain named bytes as an in-memory source. Binary content is supported.
2974///
2975/// # Safety
2976/// Pointers and handles must satisfy the crate-level safety requirements.
2977#[unsafe(no_mangle)]
2978pub unsafe extern "C" fn pio_source_from_memory(
2979    name: *const c_char,
2980    name_len: usize,
2981    data: *const u8,
2982    data_len: usize,
2983    error: *mut *mut PioError,
2984) -> *mut PioSource {
2985    unsafe {
2986        entry(error, std::ptr::null_mut(), || {
2987            let name = required_str(name, name_len, "name")?;
2988            let bytes = input_bytes(data, data_len, "data")?;
2989            Source::from_memory(name, bytes.to_vec())
2990                .map(PioSource::new_raw)
2991                .map_err(|failure| error_from_core(&failure))
2992        })
2993    }
2994}
2995
2996///
2997/// # Safety
2998/// Pointers and handles must satisfy the crate-level safety requirements.
2999/// The input handle must remain live and immutable throughout this call.
3000#[unsafe(no_mangle)]
3001pub unsafe extern "C" fn pio_source_retain(source: *const PioSource) -> *mut PioSource {
3002    unsafe { PioSource::retain_raw(source) }
3003}
3004
3005///
3006/// # Safety
3007/// Pointers and handles must satisfy the crate-level safety requirements.
3008/// A non-null handle must be owned and unused by concurrent calls.
3009#[unsafe(no_mangle)]
3010pub unsafe extern "C" fn pio_source_release(source: *mut PioSource) {
3011    unsafe { PioSource::release_raw(source) };
3012}
3013
3014/// Select a filesystem output path.
3015///
3016/// # Safety
3017/// Pointers and handles must satisfy the crate-level safety requirements.
3018#[unsafe(no_mangle)]
3019pub unsafe extern "C" fn pio_destination_path(
3020    path: *const c_char,
3021    path_len: usize,
3022    error: *mut *mut PioError,
3023) -> *mut PioDestination {
3024    unsafe {
3025        entry(error, std::ptr::null_mut(), || {
3026            let path = required_str(path, path_len, "path")?;
3027            Ok(PioDestination::new_raw(DestinationSpec::Path(
3028                PathBuf::from(path),
3029            )))
3030        })
3031    }
3032}
3033
3034/// Select memory output and prefix returned artifact names with `root`.
3035///
3036/// # Safety
3037/// Pointers and handles must satisfy the crate-level safety requirements.
3038#[unsafe(no_mangle)]
3039pub unsafe extern "C" fn pio_destination_memory(
3040    root: *const c_char,
3041    root_len: usize,
3042    error: *mut *mut PioError,
3043) -> *mut PioDestination {
3044    unsafe {
3045        entry(error, std::ptr::null_mut(), || {
3046            let root = required_str(root, root_len, "root")?;
3047            Destination::memory(root).map_err(|failure| error_from_core(&failure))?;
3048            Ok(PioDestination::new_raw(DestinationSpec::Memory(
3049                root.to_owned(),
3050            )))
3051        })
3052    }
3053}
3054
3055///
3056/// # Safety
3057/// Pointers and handles must satisfy the crate-level safety requirements.
3058/// The input handle must remain live and immutable throughout this call.
3059#[unsafe(no_mangle)]
3060pub unsafe extern "C" fn pio_destination_retain(
3061    destination: *const PioDestination,
3062) -> *mut PioDestination {
3063    unsafe { PioDestination::retain_raw(destination) }
3064}
3065
3066///
3067/// # Safety
3068/// Pointers and handles must satisfy the crate-level safety requirements.
3069/// A non-null handle must be owned and unused by concurrent calls.
3070#[unsafe(no_mangle)]
3071pub unsafe extern "C" fn pio_destination_release(destination: *mut PioDestination) {
3072    unsafe { PioDestination::release_raw(destination) };
3073}
3074
3075/// Parse one geographic sidecar from an acquired source.
3076///
3077/// # Safety
3078/// Pointers and handles must satisfy the crate-level safety requirements.
3079#[unsafe(no_mangle)]
3080pub unsafe extern "C" fn pio_geo_layer_parse(
3081    source: *const PioSource,
3082    error: *mut *mut PioError,
3083) -> *mut PioGeoLayer {
3084    unsafe {
3085        entry(error, std::ptr::null_mut(), || {
3086            let source = PioSource::get(source).ok_or_else(|| {
3087                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioSource must not be NULL")
3088            })?;
3089            let buffer = source
3090                .primary_buffer()
3091                .map_err(|failure| error_from_core(&failure))?;
3092            let text = std::str::from_utf8(buffer.content_bytes()).map_err(|_| {
3093                boundary_error(
3094                    &codes::BIND_CAPI_INVALID_UTF8,
3095                    "a geographic sidecar must be valid UTF-8",
3096                )
3097            })?;
3098            powerio::GeoLayer::parse(text, Some(source.name()))
3099                .map(|parsed| {
3100                    PioGeoLayer::new_raw(GeoLayerInner {
3101                        layer: parsed.layer,
3102                        diagnostics: parsed.diagnostics,
3103                    })
3104                })
3105                .map_err(|failure| error_from_tx(&failure))
3106        })
3107    }
3108}
3109
3110/// Return diagnostics produced while parsing a geographic sidecar.
3111///
3112/// # Safety
3113/// Pointers and handles must satisfy the crate-level safety requirements.
3114#[unsafe(no_mangle)]
3115pub unsafe extern "C" fn pio_geo_layer_diagnostics(
3116    layer: *const PioGeoLayer,
3117) -> *mut PioDiagnostics {
3118    unsafe { PioGeoLayer::get(layer) }.map_or(std::ptr::null_mut(), |layer| {
3119        PioDiagnostics::new_raw(DiagnosticsInner {
3120            owner: DiagnosticsOwner::Owned(layer.diagnostics.clone()),
3121        })
3122    })
3123}
3124
3125/// Write the layer as the canonical `.geo.json` document: a GeoJSON
3126/// FeatureCollection whose `powerio_geo` member states the coordinate space
3127/// and the writer's version. The text is owned by the returned handle and read
3128/// with `pio_string_view`; release it with `pio_string_release`.
3129///
3130/// # Safety
3131/// Pointers and handles must satisfy the crate-level safety requirements.
3132#[unsafe(no_mangle)]
3133pub unsafe extern "C" fn pio_geo_layer_to_geojson(
3134    layer: *const PioGeoLayer,
3135    error: *mut *mut PioError,
3136) -> *mut PioString {
3137    unsafe {
3138        entry(error, std::ptr::null_mut(), || {
3139            let layer = PioGeoLayer::get(layer).ok_or_else(|| {
3140                boundary_error(
3141                    &codes::BIND_CAPI_NULL_HANDLE,
3142                    "PioGeoLayer must not be NULL",
3143                )
3144            })?;
3145            Ok(PioString::new_raw(StringInner {
3146                text: layer.layer.to_geojson(),
3147            }))
3148        })
3149    }
3150}
3151
3152///
3153/// # Safety
3154/// Pointers and handles must satisfy the crate-level safety requirements.
3155/// The input handle must remain live and immutable throughout this call.
3156#[unsafe(no_mangle)]
3157pub unsafe extern "C" fn pio_geo_layer_retain(layer: *const PioGeoLayer) -> *mut PioGeoLayer {
3158    unsafe { PioGeoLayer::retain_raw(layer) }
3159}
3160
3161///
3162/// # Safety
3163/// Pointers and handles must satisfy the crate-level safety requirements.
3164/// A non-null handle must be owned and unused by concurrent calls.
3165#[unsafe(no_mangle)]
3166pub unsafe extern "C" fn pio_geo_layer_release(layer: *mut PioGeoLayer) {
3167    unsafe { PioGeoLayer::release_raw(layer) };
3168}
3169
3170/// The UTF-8 text of an acquired source's primary buffer, for the readers that
3171/// take text rather than a routed format.
3172unsafe fn contingency_source_text(
3173    source: *const PioSource,
3174    what: &str,
3175) -> Result<String, *mut PioError> {
3176    let source = unsafe { PioSource::get(source) }.ok_or_else(|| {
3177        boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioSource must not be NULL")
3178    })?;
3179    let buffer = source
3180        .primary_buffer()
3181        .map_err(|failure| error_from_core(&failure))?;
3182    std::str::from_utf8(buffer.content_bytes())
3183        .map(str::to_owned)
3184        .map_err(|_| {
3185            boundary_error(
3186                &codes::BIND_CAPI_INVALID_UTF8,
3187                format!("{what} must be valid UTF-8"),
3188            )
3189        })
3190}
3191
3192unsafe fn require_contingency_set<'a>(
3193    set: *const PioContingencySet,
3194) -> Result<&'a powerio::ContingencySet, *mut PioError> {
3195    unsafe { PioContingencySet::get(set) }
3196        .and_then(ContingencySetInner::set)
3197        .ok_or_else(|| {
3198            boundary_error(
3199                &codes::BIND_CAPI_NULL_HANDLE,
3200                "PioContingencySet must not be NULL",
3201            )
3202        })
3203}
3204
3205unsafe fn require_subsystem_set<'a>(
3206    set: *const PioSubsystemSet,
3207) -> Result<&'a powerio::SubsystemSet, *mut PioError> {
3208    unsafe { PioSubsystemSet::get(set) }
3209        .and_then(SubsystemSetInner::set)
3210        .ok_or_else(|| {
3211            boundary_error(
3212                &codes::BIND_CAPI_NULL_HANDLE,
3213                "PioSubsystemSet must not be NULL",
3214            )
3215        })
3216}
3217
3218unsafe fn require_monitored_set<'a>(
3219    set: *const PioMonitoredSet,
3220) -> Result<&'a powerio::MonitoredSet, *mut PioError> {
3221    unsafe { PioMonitoredSet::get(set) }
3222        .and_then(MonitoredSetInner::set)
3223        .ok_or_else(|| {
3224            boundary_error(
3225                &codes::BIND_CAPI_NULL_HANDLE,
3226                "PioMonitoredSet must not be NULL",
3227            )
3228        })
3229}
3230
3231unsafe fn require_resolution<'a>(
3232    resolution: *const PioContingencyResolution,
3233) -> Result<&'a ContingencyResolutionInner, *mut PioError> {
3234    unsafe { PioContingencyResolution::get(resolution) }.ok_or_else(|| {
3235        boundary_error(
3236            &codes::BIND_CAPI_NULL_HANDLE,
3237            "PioContingencyResolution must not be NULL",
3238        )
3239    })
3240}
3241
3242unsafe fn require_resolved_case<'a>(
3243    resolution: *const PioContingencyResolution,
3244    index: usize,
3245) -> Result<&'a powerio::ResolvedCase, *mut PioError> {
3246    unsafe { require_resolution(resolution) }?
3247        .resolution
3248        .cases
3249        .get(index)
3250        .ok_or_else(|| {
3251            boundary_error(
3252                &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3253                format!("contingency case index {index} is out of range"),
3254            )
3255        })
3256}
3257
3258/// Read text from an acquired source as a PSS/E contingency description file.
3259///
3260/// # Safety
3261/// Pointers and handles must satisfy the crate-level safety requirements.
3262#[unsafe(no_mangle)]
3263pub unsafe extern "C" fn pio_contingency_set_parse(
3264    source: *const PioSource,
3265    error: *mut *mut PioError,
3266) -> *mut PioContingencySet {
3267    unsafe {
3268        entry(error, std::ptr::null_mut(), || {
3269            let text = contingency_source_text(source, "a contingency description file")?;
3270            powerio::ContingencySet::parse(&text)
3271                .map(|parsed| {
3272                    PioContingencySet::new_raw(ContingencySetInner {
3273                        source: ContingencySource::Owned(parsed.set),
3274                        diagnostics: parsed.diagnostics,
3275                    })
3276                })
3277                .map_err(|failure| error_from_tx(&failure))
3278        })
3279    }
3280}
3281
3282/// Return the notes the contingency reader produced, which are empty for a set
3283/// taken from a module value and for an expanded set.
3284///
3285/// # Safety
3286/// Pointers and handles must satisfy the crate-level safety requirements.
3287#[unsafe(no_mangle)]
3288pub unsafe extern "C" fn pio_contingency_set_diagnostics(
3289    set: *const PioContingencySet,
3290) -> *mut PioDiagnostics {
3291    unsafe { PioContingencySet::get(set) }.map_or(std::ptr::null_mut(), |set| {
3292        PioDiagnostics::new_raw(DiagnosticsInner {
3293            owner: DiagnosticsOwner::Owned(set.diagnostics.clone()),
3294        })
3295    })
3296}
3297
3298///
3299/// # Safety
3300/// Pointers and handles must satisfy the crate-level safety requirements.
3301#[unsafe(no_mangle)]
3302pub unsafe extern "C" fn pio_contingency_set_case_count(set: *const PioContingencySet) -> usize {
3303    unsafe { PioContingencySet::get(set) }
3304        .and_then(ContingencySetInner::set)
3305        .map_or(0, |set| set.cases.len())
3306}
3307
3308/// Read one case name by zero based position, in the set's own order.
3309///
3310/// # Safety
3311/// Pointers and handles must satisfy the crate-level safety requirements.
3312#[unsafe(no_mangle)]
3313pub unsafe extern "C" fn pio_contingency_set_case_name(
3314    set: *const PioContingencySet,
3315    index: usize,
3316    error: *mut *mut PioError,
3317) -> PioStringView {
3318    unsafe {
3319        entry(error, PioStringView::EMPTY, || {
3320            let set = require_contingency_set(set)?;
3321            let case = set.cases.get(index).ok_or_else(|| {
3322                boundary_error(
3323                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3324                    format!("contingency case index {index} is out of range"),
3325                )
3326            })?;
3327            Ok(PioStringView::new(&case.name))
3328        })
3329    }
3330}
3331
3332/// Write the set back as `.con` text, so an expanded set reaches a file. The
3333/// text is owned by the returned handle and read with `pio_string_view`;
3334/// release it with `pio_string_release`.
3335///
3336/// # Safety
3337/// Pointers and handles must satisfy the crate-level safety requirements.
3338#[unsafe(no_mangle)]
3339pub unsafe extern "C" fn pio_contingency_set_to_con(
3340    set: *const PioContingencySet,
3341    error: *mut *mut PioError,
3342) -> *mut PioString {
3343    unsafe {
3344        entry(error, std::ptr::null_mut(), || {
3345            let set = require_contingency_set(set)?;
3346            Ok(PioString::new_raw(StringInner { text: set.to_con() }))
3347        })
3348    }
3349}
3350
3351///
3352/// # Safety
3353/// Pointers and handles must satisfy the crate-level safety requirements.
3354/// The input handle must remain live and immutable throughout this call.
3355#[unsafe(no_mangle)]
3356pub unsafe extern "C" fn pio_contingency_set_retain(
3357    set: *const PioContingencySet,
3358) -> *mut PioContingencySet {
3359    unsafe { PioContingencySet::retain_raw(set) }
3360}
3361
3362///
3363/// # Safety
3364/// Pointers and handles must satisfy the crate-level safety requirements.
3365/// A non-null handle must be owned and unused by concurrent calls.
3366#[unsafe(no_mangle)]
3367pub unsafe extern "C" fn pio_contingency_set_release(set: *mut PioContingencySet) {
3368    unsafe { PioContingencySet::release_raw(set) };
3369}
3370
3371///
3372/// # Safety
3373/// Pointers and handles must satisfy the crate-level safety requirements.
3374#[unsafe(no_mangle)]
3375pub unsafe extern "C" fn pio_subsystem_set_count(set: *const PioSubsystemSet) -> usize {
3376    unsafe { PioSubsystemSet::get(set) }
3377        .and_then(SubsystemSetInner::set)
3378        .map_or(0, |set| set.subsystems.len())
3379}
3380
3381/// Read one subsystem name by zero based position, in the file's own order.
3382///
3383/// # Safety
3384/// Pointers and handles must satisfy the crate-level safety requirements.
3385#[unsafe(no_mangle)]
3386pub unsafe extern "C" fn pio_subsystem_set_name(
3387    set: *const PioSubsystemSet,
3388    index: usize,
3389    error: *mut *mut PioError,
3390) -> PioStringView {
3391    unsafe {
3392        entry(error, PioStringView::EMPTY, || {
3393            let set = require_subsystem_set(set)?;
3394            let subsystem = set.subsystems.get(index).ok_or_else(|| {
3395                boundary_error(
3396                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3397                    format!("subsystem index {index} is out of range"),
3398                )
3399            })?;
3400            Ok(PioStringView::new(&subsystem.name))
3401        })
3402    }
3403}
3404
3405/// Write the set back as `.sub` text. The text is owned by the returned
3406/// handle and read with `pio_string_view`; release it with
3407/// `pio_string_release`.
3408///
3409/// # Safety
3410/// Pointers and handles must satisfy the crate-level safety requirements.
3411#[unsafe(no_mangle)]
3412pub unsafe extern "C" fn pio_subsystem_set_to_sub(
3413    set: *const PioSubsystemSet,
3414    error: *mut *mut PioError,
3415) -> *mut PioString {
3416    unsafe {
3417        entry(error, std::ptr::null_mut(), || {
3418            let set = require_subsystem_set(set)?;
3419            Ok(PioString::new_raw(StringInner { text: set.to_sub() }))
3420        })
3421    }
3422}
3423
3424///
3425/// # Safety
3426/// Pointers and handles must satisfy the crate-level safety requirements.
3427/// The input handle must remain live and immutable throughout this call.
3428#[unsafe(no_mangle)]
3429pub unsafe extern "C" fn pio_subsystem_set_retain(
3430    set: *const PioSubsystemSet,
3431) -> *mut PioSubsystemSet {
3432    unsafe { PioSubsystemSet::retain_raw(set) }
3433}
3434
3435///
3436/// # Safety
3437/// Pointers and handles must satisfy the crate-level safety requirements.
3438/// A non-null handle must be owned and unused by concurrent calls.
3439#[unsafe(no_mangle)]
3440pub unsafe extern "C" fn pio_subsystem_set_release(set: *mut PioSubsystemSet) {
3441    unsafe { PioSubsystemSet::release_raw(set) };
3442}
3443
3444///
3445/// # Safety
3446/// Pointers and handles must satisfy the crate-level safety requirements.
3447#[unsafe(no_mangle)]
3448pub unsafe extern "C" fn pio_monitored_set_statement_count(set: *const PioMonitoredSet) -> usize {
3449    unsafe { PioMonitoredSet::get(set) }
3450        .and_then(MonitoredSetInner::set)
3451        .map_or(0, |set| set.statements.len())
3452}
3453
3454/// Write the set back as `.mon` text. The text is owned by the returned
3455/// handle and read with `pio_string_view`; release it with
3456/// `pio_string_release`.
3457///
3458/// # Safety
3459/// Pointers and handles must satisfy the crate-level safety requirements.
3460#[unsafe(no_mangle)]
3461pub unsafe extern "C" fn pio_monitored_set_to_mon(
3462    set: *const PioMonitoredSet,
3463    error: *mut *mut PioError,
3464) -> *mut PioString {
3465    unsafe {
3466        entry(error, std::ptr::null_mut(), || {
3467            let set = require_monitored_set(set)?;
3468            Ok(PioString::new_raw(StringInner { text: set.to_mon() }))
3469        })
3470    }
3471}
3472
3473///
3474/// # Safety
3475/// Pointers and handles must satisfy the crate-level safety requirements.
3476/// The input handle must remain live and immutable throughout this call.
3477#[unsafe(no_mangle)]
3478pub unsafe extern "C" fn pio_monitored_set_retain(
3479    set: *const PioMonitoredSet,
3480) -> *mut PioMonitoredSet {
3481    unsafe { PioMonitoredSet::retain_raw(set) }
3482}
3483
3484///
3485/// # Safety
3486/// Pointers and handles must satisfy the crate-level safety requirements.
3487/// A non-null handle must be owned and unused by concurrent calls.
3488#[unsafe(no_mangle)]
3489pub unsafe extern "C" fn pio_monitored_set_release(set: *mut PioMonitoredSet) {
3490    unsafe { PioMonitoredSet::release_raw(set) };
3491}
3492
3493/// Bind every case of the set to the elements of one balanced network.
3494///
3495/// Binding reports rather than refuses: an action naming no element of the
3496/// network is kept with its reason and its case is counted unresolved, while
3497/// the actions of that case that did bind stay listed.
3498///
3499/// # Safety
3500/// Pointers and handles must satisfy the crate-level safety requirements.
3501#[unsafe(no_mangle)]
3502pub unsafe extern "C" fn pio_contingency_set_resolve(
3503    set: *const PioContingencySet,
3504    network: *const PioBalancedNetwork,
3505    error: *mut *mut PioError,
3506) -> *mut PioContingencyResolution {
3507    unsafe {
3508        entry(error, std::ptr::null_mut(), || {
3509            let set = require_contingency_set(set)?;
3510            let network = require_balanced_network(network)?;
3511            Ok(PioContingencyResolution::new_raw(
3512                ContingencyResolutionInner::new(set.resolve(network)),
3513            ))
3514        })
3515    }
3516}
3517
3518/// Expand every automatic specification of the set against one network and one
3519/// subsystem set, producing a set whose cases are all explicit.
3520///
3521/// When `out_notes` is not NULL it receives an independently owned diagnostics
3522/// handle holding one note per specification that expanded into nothing. A
3523/// specification naming a subsystem the subsystem set does not state stays in
3524/// the returned set.
3525///
3526/// # Safety
3527/// Pointers and handles must satisfy the crate-level safety requirements.
3528#[unsafe(no_mangle)]
3529pub unsafe extern "C" fn pio_contingency_set_expand(
3530    set: *const PioContingencySet,
3531    network: *const PioBalancedNetwork,
3532    subsystems: *const PioSubsystemSet,
3533    out_notes: *mut *mut PioDiagnostics,
3534    error: *mut *mut PioError,
3535) -> *mut PioContingencySet {
3536    unsafe {
3537        if !out_notes.is_null() {
3538            *out_notes = std::ptr::null_mut();
3539        }
3540        entry(error, std::ptr::null_mut(), || {
3541            let set = require_contingency_set(set)?;
3542            let network = require_balanced_network(network)?;
3543            let subsystems = require_subsystem_set(subsystems)?;
3544            let expanded = set.expand(network, subsystems);
3545            if !out_notes.is_null() {
3546                *out_notes = PioDiagnostics::new_raw(DiagnosticsInner {
3547                    owner: DiagnosticsOwner::Owned(expanded.diagnostics),
3548                });
3549            }
3550            Ok(PioContingencySet::new_raw(ContingencySetInner {
3551                source: ContingencySource::Owned(expanded.set),
3552                diagnostics: Vec::new(),
3553            }))
3554        })
3555    }
3556}
3557
3558/// The buses of one balanced network that a named subsystem selects, in
3559/// ascending bus number order.
3560///
3561/// The name is matched without case and without surrounding whitespace, the
3562/// way a `.con` or `.mon` statement names a subsystem. A name the subsystem
3563/// set does not state reports `BIND.CAPI.INDEX_OUT_OF_RANGE`.
3564///
3565/// The returned vector owns its values: read them with `pio_vector_values` and
3566/// release the handle with `pio_vector_release`. Each value is a bus number,
3567/// an integer a double represents exactly.
3568///
3569/// # Safety
3570/// Pointers and handles must satisfy the crate-level safety requirements.
3571#[unsafe(no_mangle)]
3572pub unsafe extern "C" fn pio_balanced_network_select_subsystem_buses(
3573    network: *const PioBalancedNetwork,
3574    subsystems: *const PioSubsystemSet,
3575    name: *const c_char,
3576    name_len: usize,
3577    error: *mut *mut PioError,
3578) -> *mut PioVector {
3579    unsafe {
3580        entry(error, std::ptr::null_mut(), || {
3581            let network = require_balanced_network(network)?;
3582            let subsystems = require_subsystem_set(subsystems)?;
3583            let name = required_str(name, name_len, "name")?;
3584            let subsystem = subsystems.get(name).ok_or_else(|| {
3585                boundary_error(
3586                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3587                    format!("subsystem '{name}' is not stated by the subsystem set"),
3588                )
3589            })?;
3590            let values = subsystem
3591                .select_buses(network)
3592                .iter()
3593                .map(|bus| bus.0 as f64)
3594                .collect();
3595            Ok(PioVector::new_raw(VectorInner { values }))
3596        })
3597    }
3598}
3599
3600///
3601/// # Safety
3602/// Pointers and handles must satisfy the crate-level safety requirements.
3603#[unsafe(no_mangle)]
3604pub unsafe extern "C" fn pio_contingency_resolution_case_count(
3605    resolution: *const PioContingencyResolution,
3606) -> usize {
3607    unsafe { PioContingencyResolution::get(resolution) }
3608        .map_or(0, |inner| inner.resolution.cases.len())
3609}
3610
3611/// The number of cases whose every action bound.
3612///
3613/// # Safety
3614/// Pointers and handles must satisfy the crate-level safety requirements.
3615#[unsafe(no_mangle)]
3616pub unsafe extern "C" fn pio_contingency_resolution_resolved_count(
3617    resolution: *const PioContingencyResolution,
3618) -> usize {
3619    unsafe { PioContingencyResolution::get(resolution) }
3620        .map_or(0, |inner| inner.resolution.resolved)
3621}
3622
3623/// The number of cases holding at least one action that did not bind.
3624///
3625/// # Safety
3626/// Pointers and handles must satisfy the crate-level safety requirements.
3627#[unsafe(no_mangle)]
3628pub unsafe extern "C" fn pio_contingency_resolution_unresolved_count(
3629    resolution: *const PioContingencyResolution,
3630) -> usize {
3631    unsafe { PioContingencyResolution::get(resolution) }
3632        .map_or(0, |inner| inner.resolution.unresolved)
3633}
3634
3635/// The number of actions the reader kept as text, counted over every case.
3636///
3637/// # Safety
3638/// Pointers and handles must satisfy the crate-level safety requirements.
3639#[unsafe(no_mangle)]
3640pub unsafe extern "C" fn pio_contingency_resolution_unrecognized_statement_count(
3641    resolution: *const PioContingencyResolution,
3642) -> usize {
3643    unsafe { PioContingencyResolution::get(resolution) }
3644        .map_or(0, |inner| inner.resolution.unrecognized_statements)
3645}
3646
3647/// Read one case name by zero based position, in the set's own order.
3648///
3649/// # Safety
3650/// Pointers and handles must satisfy the crate-level safety requirements.
3651#[unsafe(no_mangle)]
3652pub unsafe extern "C" fn pio_contingency_resolution_case_name(
3653    resolution: *const PioContingencyResolution,
3654    index: usize,
3655    error: *mut *mut PioError,
3656) -> PioStringView {
3657    unsafe {
3658        entry(error, PioStringView::EMPTY, || {
3659            let case = require_resolved_case(resolution, index)?;
3660            Ok(PioStringView::new(&case.name))
3661        })
3662    }
3663}
3664
3665/// Whether every action of one case bound. A NULL handle or an index out of
3666/// range reads as false; `pio_contingency_resolution_case_unresolved_count`
3667/// tells the two apart.
3668///
3669/// # Safety
3670/// Pointers and handles must satisfy the crate-level safety requirements.
3671#[unsafe(no_mangle)]
3672pub unsafe extern "C" fn pio_contingency_resolution_case_is_resolved(
3673    resolution: *const PioContingencyResolution,
3674    index: usize,
3675) -> bool {
3676    unsafe { PioContingencyResolution::get(resolution) }
3677        .and_then(|inner| inner.resolution.cases.get(index))
3678        .is_some_and(powerio::ResolvedCase::is_resolved)
3679}
3680
3681/// The number of elements one case's actions bound to.
3682///
3683/// # Safety
3684/// Pointers and handles must satisfy the crate-level safety requirements.
3685#[unsafe(no_mangle)]
3686pub unsafe extern "C" fn pio_contingency_resolution_case_component_count(
3687    resolution: *const PioContingencyResolution,
3688    index: usize,
3689) -> usize {
3690    unsafe { PioContingencyResolution::get(resolution) }
3691        .and_then(|inner| inner.resolution.cases.get(index))
3692        .map_or(0, |case| case.components.len())
3693}
3694
3695/// Read one element one case bound to, by zero based case and element
3696/// position, in action order.
3697///
3698/// The element's `id.component_type` names the table `row` indexes whether or
3699/// not the network states an identity for the row, and `id.local_id` has `len`
3700/// 0 when it states none.
3701///
3702/// # Safety
3703/// Pointers and handles must satisfy the crate-level safety requirements.
3704#[unsafe(no_mangle)]
3705pub unsafe extern "C" fn pio_contingency_resolution_case_component(
3706    resolution: *const PioContingencyResolution,
3707    case_index: usize,
3708    component_index: usize,
3709    output: *mut PioContingencyComponentView,
3710    error: *mut *mut PioError,
3711) -> bool {
3712    unsafe {
3713        entry(error, false, || {
3714            let case = require_resolved_case(resolution, case_index)?;
3715            let component = case.components.get(component_index).ok_or_else(|| {
3716                boundary_error(
3717                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3718                    format!(
3719                        "contingency case {case_index} component index {component_index} is out of range"
3720                    ),
3721                )
3722            })?;
3723            *require_output(output, "output")? = PioContingencyComponentView {
3724                id: PioComponentIdView {
3725                    component_type: PioStringView::new(component.component_type),
3726                    local_id: component
3727                        .id
3728                        .as_ref()
3729                        .map_or(PioStringView::EMPTY, |id| PioStringView::new(id.local_id())),
3730                },
3731                row: component.row,
3732                in_service: component.in_service,
3733            };
3734            Ok(true)
3735        })
3736    }
3737}
3738
3739/// The number of one case's actions that bound to nothing.
3740///
3741/// # Safety
3742/// Pointers and handles must satisfy the crate-level safety requirements.
3743#[unsafe(no_mangle)]
3744pub unsafe extern "C" fn pio_contingency_resolution_case_unresolved_count(
3745    resolution: *const PioContingencyResolution,
3746    index: usize,
3747) -> usize {
3748    unsafe { PioContingencyResolution::get(resolution) }
3749        .and_then(|inner| inner.resolution.cases.get(index))
3750        .map_or(0, |case| case.unresolved.len())
3751}
3752
3753/// Read why one action of one case bound to nothing, as a fixed snake case
3754/// name: `no_such_bus`, `no_such_branch`, `ambiguous_branch`,
3755/// `ambiguous_transformer_3w`, `no_such_machine`, `no_such_shunt`,
3756/// `no_such_load`, `no_such_transformer_3w`, or `unrecognized`. Python reports
3757/// the same names.
3758///
3759/// # Safety
3760/// Pointers and handles must satisfy the crate-level safety requirements.
3761#[unsafe(no_mangle)]
3762pub unsafe extern "C" fn pio_contingency_resolution_case_unresolved_reason(
3763    resolution: *const PioContingencyResolution,
3764    case_index: usize,
3765    unresolved_index: usize,
3766    error: *mut *mut PioError,
3767) -> PioStringView {
3768    unsafe {
3769        entry(error, PioStringView::EMPTY, || {
3770            let case = require_resolved_case(resolution, case_index)?;
3771            let action = case.unresolved.get(unresolved_index).ok_or_else(|| {
3772                boundary_error(
3773                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3774                    format!(
3775                        "contingency case {case_index} unresolved index {unresolved_index} is out of range"
3776                    ),
3777                )
3778            })?;
3779            Ok(PioStringView::new(action.reason.name()))
3780        })
3781    }
3782}
3783
3784/// Read the statement of one action of one case that bound to nothing, by zero
3785/// based case and action position. The text is the `.con` line
3786/// `pio_contingency_set_to_con` writes for that action, without its line
3787/// ending, and the view borrows the resolution handle.
3788///
3789/// # Safety
3790/// Pointers and handles must satisfy the crate-level safety requirements.
3791#[unsafe(no_mangle)]
3792pub unsafe extern "C" fn pio_contingency_resolution_case_unresolved_action(
3793    resolution: *const PioContingencyResolution,
3794    case_index: usize,
3795    unresolved_index: usize,
3796    error: *mut *mut PioError,
3797) -> PioStringView {
3798    unsafe {
3799        entry(error, PioStringView::EMPTY, || {
3800            let inner = require_resolution(resolution)?;
3801            let case = inner.unresolved_actions.get(case_index).ok_or_else(|| {
3802                boundary_error(
3803                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3804                    format!("contingency case index {case_index} is out of range"),
3805                )
3806            })?;
3807            let statement = case.get(unresolved_index).ok_or_else(|| {
3808                boundary_error(
3809                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3810                    format!(
3811                        "contingency case {case_index} unresolved index {unresolved_index} is out of range"
3812                    ),
3813                )
3814            })?;
3815            Ok(PioStringView::new(statement))
3816        })
3817    }
3818}
3819
3820///
3821/// # Safety
3822/// Pointers and handles must satisfy the crate-level safety requirements.
3823/// The input handle must remain live and immutable throughout this call.
3824#[unsafe(no_mangle)]
3825pub unsafe extern "C" fn pio_contingency_resolution_retain(
3826    resolution: *const PioContingencyResolution,
3827) -> *mut PioContingencyResolution {
3828    unsafe { PioContingencyResolution::retain_raw(resolution) }
3829}
3830
3831///
3832/// # Safety
3833/// Pointers and handles must satisfy the crate-level safety requirements.
3834/// A non-null handle must be owned and unused by concurrent calls.
3835#[unsafe(no_mangle)]
3836pub unsafe extern "C" fn pio_contingency_resolution_release(
3837    resolution: *mut PioContingencyResolution,
3838) {
3839    unsafe { PioContingencyResolution::release_raw(resolution) };
3840}
3841
3842// ---- modules and values ----------------------------------------------------
3843
3844#[derive(Clone)]
3845struct ModuleInner {
3846    module: powerio::PioModule<PioValue>,
3847}
3848
3849opaque_handle!(
3850    /// PowerIO value with diagnostics, source mappings, and history.
3851    PioModule,
3852    ModuleInner
3853);
3854
3855#[derive(Clone, Copy)]
3856enum ModuleJsonRoot {
3857    Extension(usize),
3858    HistoryParameter {
3859        history_index: usize,
3860        parameter_index: usize,
3861    },
3862}
3863
3864#[derive(Clone, Copy)]
3865enum JsonValueStep {
3866    Array(usize),
3867    Object(usize),
3868}
3869
3870struct JsonValueInner {
3871    owner: Arc<ModuleInner>,
3872    root: ModuleJsonRoot,
3873    steps: Vec<JsonValueStep>,
3874}
3875
3876impl JsonValueInner {
3877    fn child(&self, step: JsonValueStep) -> Self {
3878        let mut steps = self.steps.clone();
3879        steps.push(step);
3880        Self {
3881            owner: Arc::clone(&self.owner),
3882            root: self.root,
3883            steps,
3884        }
3885    }
3886
3887    fn value(&self) -> Option<&serde_json::Value> {
3888        let mut value = match self.root {
3889            ModuleJsonRoot::Extension(index) => {
3890                self.owner.module.extensions().values().nth(index)?
3891            }
3892            ModuleJsonRoot::HistoryParameter {
3893                history_index,
3894                parameter_index,
3895            } => self
3896                .owner
3897                .module
3898                .history()
3899                .get(history_index)?
3900                .parameters()
3901                .values()
3902                .nth(parameter_index)?,
3903        };
3904        for step in &self.steps {
3905            value = match (step, value) {
3906                (JsonValueStep::Array(index), serde_json::Value::Array(values)) => {
3907                    values.get(*index)?
3908                }
3909                (JsonValueStep::Object(index), serde_json::Value::Object(values)) => {
3910                    values.values().nth(*index)?
3911                }
3912                _ => return None,
3913            };
3914        }
3915        Some(value)
3916    }
3917}
3918
3919opaque_handle!(
3920    /// Owner-rooted structured value from module history or extensions.
3921    PioJsonValue,
3922    JsonValueInner
3923);
3924
3925#[derive(Clone, Copy)]
3926enum ValueStep {
3927    TimeSeries(usize),
3928    Scenario(usize),
3929}
3930
3931struct ValueInner {
3932    owner: Arc<ModuleInner>,
3933    steps: Vec<ValueStep>,
3934}
3935
3936impl ValueInner {
3937    fn root(owner: Arc<ModuleInner>) -> Self {
3938        Self {
3939            owner,
3940            steps: Vec::new(),
3941        }
3942    }
3943
3944    fn child(&self, step: ValueStep) -> Self {
3945        let mut steps = self.steps.clone();
3946        steps.push(step);
3947        Self {
3948            owner: Arc::clone(&self.owner),
3949            steps,
3950        }
3951    }
3952
3953    fn value(&self) -> Option<&PioValue> {
3954        let mut value = self.owner.module.value();
3955        for step in &self.steps {
3956            value = match (step, value) {
3957                (ValueStep::TimeSeries(index), PioValue::TimeSeries(series)) => {
3958                    series.get(*index)?
3959                }
3960                (ValueStep::Scenario(index), PioValue::ScenarioSet(scenarios)) => {
3961                    scenarios.get_at(*index)?
3962                }
3963                _ => return None,
3964            };
3965        }
3966        Some(value)
3967    }
3968}
3969
3970opaque_handle!(
3971    /// Owner-rooted view of one module or collection value.
3972    PioValueHandle,
3973    ValueInner
3974);
3975
3976#[derive(Clone, Copy)]
3977enum BalancedNetworkProjection {
3978    Direct,
3979    OperatingPoint,
3980    DcPfInstance,
3981    AcPfInstance,
3982    DcOpfInstance,
3983    AcOpfInstance,
3984    AcScucInstance,
3985    DcPfSolution,
3986    AcPfSolution,
3987    DcOpfSolution,
3988    AcOpfSolution,
3989    SocwrOpfSolution,
3990    AcScucSolution,
3991}
3992
3993struct BalancedNetworkInner {
3994    value: ValueInner,
3995    projection: BalancedNetworkProjection,
3996}
3997
3998impl BalancedNetworkInner {
3999    fn network(&self) -> Option<&BalancedNetwork> {
4000        match (self.projection, self.value.value()?) {
4001            (BalancedNetworkProjection::Direct, PioValue::BalancedNetwork(network)) => {
4002                Some(network)
4003            }
4004            (
4005                BalancedNetworkProjection::OperatingPoint,
4006                PioValue::BalancedOperatingPoint(point),
4007            ) => Some(point.network()),
4008            (BalancedNetworkProjection::DcPfInstance, PioValue::DcPfInstance(instance)) => {
4009                Some(instance.network())
4010            }
4011            (BalancedNetworkProjection::AcPfInstance, PioValue::AcPfInstance(instance)) => {
4012                Some(instance.network())
4013            }
4014            (BalancedNetworkProjection::DcOpfInstance, PioValue::DcOpfInstance(instance)) => {
4015                Some(instance.network())
4016            }
4017            (BalancedNetworkProjection::AcOpfInstance, PioValue::AcOpfInstance(instance)) => {
4018                Some(instance.network())
4019            }
4020            (BalancedNetworkProjection::AcScucInstance, PioValue::AcScucInstance(instance)) => {
4021                Some(instance.network())
4022            }
4023            (BalancedNetworkProjection::DcPfSolution, PioValue::DcPfSolution(solution)) => {
4024                Some(solution.network())
4025            }
4026            (BalancedNetworkProjection::AcPfSolution, PioValue::AcPfSolution(solution)) => {
4027                Some(solution.network())
4028            }
4029            (BalancedNetworkProjection::DcOpfSolution, PioValue::DcOpfSolution(solution)) => {
4030                Some(solution.network())
4031            }
4032            (BalancedNetworkProjection::AcOpfSolution, PioValue::AcOpfSolution(solution)) => {
4033                Some(solution.network())
4034            }
4035            (BalancedNetworkProjection::SocwrOpfSolution, PioValue::SocwrOpfSolution(solution)) => {
4036                Some(solution.network())
4037            }
4038            (BalancedNetworkProjection::AcScucSolution, PioValue::AcScucSolution(solution)) => {
4039                Some(solution.instance().network())
4040            }
4041            _ => None,
4042        }
4043    }
4044}
4045
4046#[derive(Clone, Copy)]
4047enum MulticonductorNetworkProjection {
4048    Direct,
4049    OperatingPoint,
4050    McAcPfInstance,
4051    McAcOpfInstance,
4052    LinDist3FlowOpfInstance,
4053    McAcPfSolution,
4054    McAcOpfSolution,
4055    LinDist3FlowOpfSolution,
4056}
4057
4058struct MulticonductorNetworkInner {
4059    value: ValueInner,
4060    projection: MulticonductorNetworkProjection,
4061}
4062
4063impl MulticonductorNetworkInner {
4064    fn network(&self) -> Option<&powerio::MulticonductorNetwork> {
4065        match (self.projection, self.value.value()?) {
4066            (MulticonductorNetworkProjection::Direct, PioValue::MulticonductorNetwork(network)) => {
4067                Some(network)
4068            }
4069            (
4070                MulticonductorNetworkProjection::OperatingPoint,
4071                PioValue::MulticonductorOperatingPoint(point),
4072            ) => Some(point.network()),
4073            (
4074                MulticonductorNetworkProjection::McAcPfInstance,
4075                PioValue::McAcPfInstance(instance),
4076            ) => Some(instance.network()),
4077            (
4078                MulticonductorNetworkProjection::McAcOpfInstance,
4079                PioValue::McAcOpfInstance(instance),
4080            ) => Some(instance.network()),
4081            (
4082                MulticonductorNetworkProjection::LinDist3FlowOpfInstance,
4083                PioValue::LinDist3FlowOpfInstance(instance),
4084            ) => Some(instance.network()),
4085            (
4086                MulticonductorNetworkProjection::McAcPfSolution,
4087                PioValue::McAcPfSolution(solution),
4088            ) => Some(solution.network()),
4089            (
4090                MulticonductorNetworkProjection::McAcOpfSolution,
4091                PioValue::McAcOpfSolution(solution),
4092            ) => Some(solution.network()),
4093            (
4094                MulticonductorNetworkProjection::LinDist3FlowOpfSolution,
4095                PioValue::LinDist3FlowOpfSolution(solution),
4096            ) => Some(solution.network()),
4097            _ => None,
4098        }
4099    }
4100}
4101
4102#[derive(Clone, Copy)]
4103enum OperatingPointProjection {
4104    DirectBalanced,
4105    DirectMulticonductor,
4106    DcPfInitial,
4107    AcPfInitial,
4108    DcOpfInitial,
4109    AcOpfInitial,
4110    McAcPfInitial,
4111    McAcOpfInitial,
4112    LinDist3FlowOpfInitial,
4113    DcPfSolutionInitial,
4114    AcPfSolutionInitial,
4115    DcOpfSolutionInitial,
4116    AcOpfSolutionInitial,
4117    SocwrOpfSolutionInitial,
4118    McAcPfSolutionInitial,
4119    McAcOpfSolutionInitial,
4120    LinDist3FlowOpfSolutionInitial,
4121}
4122
4123struct OperatingPointInner {
4124    value: ValueInner,
4125    projection: OperatingPointProjection,
4126}
4127
4128impl OperatingPointInner {
4129    fn balanced(&self) -> Option<&powerio_prob::OperatingPoint<BalancedNetwork>> {
4130        match (self.projection, self.value.value()?) {
4131            (OperatingPointProjection::DirectBalanced, PioValue::BalancedOperatingPoint(point)) => {
4132                Some(point)
4133            }
4134            (OperatingPointProjection::DcPfInitial, PioValue::DcPfInstance(instance)) => {
4135                instance.initial_point()
4136            }
4137            (OperatingPointProjection::AcPfInitial, PioValue::AcPfInstance(instance)) => {
4138                instance.initial_point()
4139            }
4140            (OperatingPointProjection::DcOpfInitial, PioValue::DcOpfInstance(instance)) => {
4141                instance.initial_point()
4142            }
4143            (OperatingPointProjection::AcOpfInitial, PioValue::AcOpfInstance(instance)) => {
4144                instance.initial_point()
4145            }
4146            (OperatingPointProjection::DcPfSolutionInitial, PioValue::DcPfSolution(solution)) => {
4147                solution.instance().initial_point()
4148            }
4149            (OperatingPointProjection::AcPfSolutionInitial, PioValue::AcPfSolution(solution)) => {
4150                solution.instance().initial_point()
4151            }
4152            (OperatingPointProjection::DcOpfSolutionInitial, PioValue::DcOpfSolution(solution)) => {
4153                solution.instance().initial_point()
4154            }
4155            (OperatingPointProjection::AcOpfSolutionInitial, PioValue::AcOpfSolution(solution)) => {
4156                solution.instance().initial_point()
4157            }
4158            (
4159                OperatingPointProjection::SocwrOpfSolutionInitial,
4160                PioValue::SocwrOpfSolution(solution),
4161            ) => solution.instance().initial_point(),
4162            _ => None,
4163        }
4164    }
4165
4166    fn multiconductor(
4167        &self,
4168    ) -> Option<&powerio_prob::OperatingPoint<powerio::MulticonductorNetwork>> {
4169        match (self.projection, self.value.value()?) {
4170            (
4171                OperatingPointProjection::DirectMulticonductor,
4172                PioValue::MulticonductorOperatingPoint(point),
4173            ) => Some(point),
4174            (OperatingPointProjection::McAcPfInitial, PioValue::McAcPfInstance(instance)) => {
4175                instance.initial_point()
4176            }
4177            (OperatingPointProjection::McAcOpfInitial, PioValue::McAcOpfInstance(instance)) => {
4178                instance.initial_point()
4179            }
4180            (
4181                OperatingPointProjection::LinDist3FlowOpfInitial,
4182                PioValue::LinDist3FlowOpfInstance(instance),
4183            ) => instance.base_instance().initial_point(),
4184            (
4185                OperatingPointProjection::McAcPfSolutionInitial,
4186                PioValue::McAcPfSolution(solution),
4187            ) => solution.instance().initial_point(),
4188            (
4189                OperatingPointProjection::McAcOpfSolutionInitial,
4190                PioValue::McAcOpfSolution(solution),
4191            ) => solution.instance().initial_point(),
4192            (
4193                OperatingPointProjection::LinDist3FlowOpfSolutionInitial,
4194                PioValue::LinDist3FlowOpfSolution(solution),
4195            ) => solution.instance().base_instance().initial_point(),
4196            _ => None,
4197        }
4198    }
4199
4200    fn type_name(&self) -> Option<&'static str> {
4201        if self.balanced().is_some() {
4202            Some("powerio.OperatingPoint<powerio.BalancedNetwork>")
4203        } else if self.multiconductor().is_some() {
4204            Some("powerio.OperatingPoint<powerio.MulticonductorNetwork>")
4205        } else {
4206            None
4207        }
4208    }
4209
4210    fn balanced_network_projection(&self) -> Option<BalancedNetworkProjection> {
4211        self.balanced()?;
4212        Some(match self.projection {
4213            OperatingPointProjection::DirectBalanced => BalancedNetworkProjection::OperatingPoint,
4214            OperatingPointProjection::DcPfInitial => BalancedNetworkProjection::DcPfInstance,
4215            OperatingPointProjection::AcPfInitial => BalancedNetworkProjection::AcPfInstance,
4216            OperatingPointProjection::DcOpfInitial => BalancedNetworkProjection::DcOpfInstance,
4217            OperatingPointProjection::AcOpfInitial => BalancedNetworkProjection::AcOpfInstance,
4218            OperatingPointProjection::DcPfSolutionInitial => {
4219                BalancedNetworkProjection::DcPfSolution
4220            }
4221            OperatingPointProjection::AcPfSolutionInitial => {
4222                BalancedNetworkProjection::AcPfSolution
4223            }
4224            OperatingPointProjection::DcOpfSolutionInitial => {
4225                BalancedNetworkProjection::DcOpfSolution
4226            }
4227            OperatingPointProjection::AcOpfSolutionInitial => {
4228                BalancedNetworkProjection::AcOpfSolution
4229            }
4230            OperatingPointProjection::SocwrOpfSolutionInitial => {
4231                BalancedNetworkProjection::SocwrOpfSolution
4232            }
4233            _ => return None,
4234        })
4235    }
4236
4237    fn multiconductor_network_projection(&self) -> Option<MulticonductorNetworkProjection> {
4238        self.multiconductor()?;
4239        Some(match self.projection {
4240            OperatingPointProjection::DirectMulticonductor => {
4241                MulticonductorNetworkProjection::OperatingPoint
4242            }
4243            OperatingPointProjection::McAcPfInitial => {
4244                MulticonductorNetworkProjection::McAcPfInstance
4245            }
4246            OperatingPointProjection::McAcOpfInitial => {
4247                MulticonductorNetworkProjection::McAcOpfInstance
4248            }
4249            OperatingPointProjection::LinDist3FlowOpfInitial => {
4250                MulticonductorNetworkProjection::LinDist3FlowOpfInstance
4251            }
4252            OperatingPointProjection::McAcPfSolutionInitial => {
4253                MulticonductorNetworkProjection::McAcPfSolution
4254            }
4255            OperatingPointProjection::McAcOpfSolutionInitial => {
4256                MulticonductorNetworkProjection::McAcOpfSolution
4257            }
4258            OperatingPointProjection::LinDist3FlowOpfSolutionInitial => {
4259                MulticonductorNetworkProjection::LinDist3FlowOpfSolution
4260            }
4261            _ => return None,
4262        })
4263    }
4264}
4265
4266#[derive(Clone, Copy)]
4267enum CalculationInstanceProjection {
4268    Direct,
4269    DcPfSolution,
4270    AcPfSolution,
4271    DcOpfSolution,
4272    AcOpfSolution,
4273    SocwrOpfSolution,
4274    McAcPfSolution,
4275    McAcOpfSolution,
4276    LinDist3FlowOpfSolution,
4277    AcScucSolution,
4278}
4279
4280#[derive(Clone, Copy)]
4281enum CalculationInstanceRef<'a> {
4282    DcPf(&'a powerio_prob::DcPfInstance),
4283    AcPf(&'a powerio_prob::AcPfInstance),
4284    DcOpf(&'a powerio_prob::DcOpfInstance),
4285    AcOpf(&'a powerio_prob::AcOpfInstance),
4286    McAcPf(&'a powerio_prob::McAcPfInstance),
4287    McAcOpf(&'a powerio_prob::McAcOpfInstance),
4288    LinDist3FlowOpf(&'a powerio_prob::LinDist3FlowOpfInstance),
4289    AcScuc(&'a powerio_prob::AcScucInstance),
4290}
4291
4292impl CalculationInstanceRef<'_> {
4293    fn type_name(self) -> &'static str {
4294        match self {
4295            Self::DcPf(_) => "powerio.DcPfInstance",
4296            Self::AcPf(_) => "powerio.AcPfInstance",
4297            Self::DcOpf(_) => "powerio.DcOpfInstance",
4298            Self::AcOpf(_) => "powerio.AcOpfInstance",
4299            Self::McAcPf(_) => "powerio.McAcPfInstance",
4300            Self::McAcOpf(_) => "powerio.McAcOpfInstance",
4301            Self::LinDist3FlowOpf(_) => "powerio.LinDist3FlowOpfInstance",
4302            Self::AcScuc(_) => "powerio.AcScucInstance",
4303        }
4304    }
4305}
4306
4307struct CalculationInstanceInner {
4308    value: ValueInner,
4309    projection: CalculationInstanceProjection,
4310}
4311
4312impl CalculationInstanceInner {
4313    fn instance(&self) -> Option<CalculationInstanceRef<'_>> {
4314        match (self.projection, self.value.value()?) {
4315            (CalculationInstanceProjection::Direct, PioValue::DcPfInstance(instance)) => {
4316                Some(CalculationInstanceRef::DcPf(instance))
4317            }
4318            (CalculationInstanceProjection::Direct, PioValue::AcPfInstance(instance)) => {
4319                Some(CalculationInstanceRef::AcPf(instance))
4320            }
4321            (CalculationInstanceProjection::Direct, PioValue::DcOpfInstance(instance)) => {
4322                Some(CalculationInstanceRef::DcOpf(instance))
4323            }
4324            (CalculationInstanceProjection::Direct, PioValue::AcOpfInstance(instance)) => {
4325                Some(CalculationInstanceRef::AcOpf(instance))
4326            }
4327            (CalculationInstanceProjection::Direct, PioValue::McAcPfInstance(instance)) => {
4328                Some(CalculationInstanceRef::McAcPf(instance))
4329            }
4330            (CalculationInstanceProjection::Direct, PioValue::McAcOpfInstance(instance)) => {
4331                Some(CalculationInstanceRef::McAcOpf(instance))
4332            }
4333            (
4334                CalculationInstanceProjection::Direct,
4335                PioValue::LinDist3FlowOpfInstance(instance),
4336            ) => Some(CalculationInstanceRef::LinDist3FlowOpf(instance)),
4337            (CalculationInstanceProjection::Direct, PioValue::AcScucInstance(instance)) => {
4338                Some(CalculationInstanceRef::AcScuc(instance))
4339            }
4340            (CalculationInstanceProjection::DcPfSolution, PioValue::DcPfSolution(solution)) => {
4341                Some(CalculationInstanceRef::DcPf(solution.instance()))
4342            }
4343            (CalculationInstanceProjection::AcPfSolution, PioValue::AcPfSolution(solution)) => {
4344                Some(CalculationInstanceRef::AcPf(solution.instance()))
4345            }
4346            (CalculationInstanceProjection::DcOpfSolution, PioValue::DcOpfSolution(solution)) => {
4347                Some(CalculationInstanceRef::DcOpf(solution.instance()))
4348            }
4349            (CalculationInstanceProjection::AcOpfSolution, PioValue::AcOpfSolution(solution)) => {
4350                Some(CalculationInstanceRef::AcOpf(solution.instance()))
4351            }
4352            (
4353                CalculationInstanceProjection::SocwrOpfSolution,
4354                PioValue::SocwrOpfSolution(solution),
4355            ) => Some(CalculationInstanceRef::AcOpf(solution.instance())),
4356            (CalculationInstanceProjection::McAcPfSolution, PioValue::McAcPfSolution(solution)) => {
4357                Some(CalculationInstanceRef::McAcPf(solution.instance()))
4358            }
4359            (
4360                CalculationInstanceProjection::McAcOpfSolution,
4361                PioValue::McAcOpfSolution(solution),
4362            ) => Some(CalculationInstanceRef::McAcOpf(solution.instance())),
4363            (
4364                CalculationInstanceProjection::LinDist3FlowOpfSolution,
4365                PioValue::LinDist3FlowOpfSolution(solution),
4366            ) => Some(CalculationInstanceRef::LinDist3FlowOpf(solution.instance())),
4367            (CalculationInstanceProjection::AcScucSolution, PioValue::AcScucSolution(solution)) => {
4368                Some(CalculationInstanceRef::AcScuc(solution.instance()))
4369            }
4370            _ => None,
4371        }
4372    }
4373
4374    fn type_name(&self) -> Option<&'static str> {
4375        self.instance().map(CalculationInstanceRef::type_name)
4376    }
4377
4378    fn dc_pf(&self) -> Option<&powerio_prob::DcPfInstance> {
4379        match self.instance()? {
4380            CalculationInstanceRef::DcPf(instance) => Some(instance),
4381            _ => None,
4382        }
4383    }
4384
4385    fn ac_pf(&self) -> Option<&powerio_prob::AcPfInstance> {
4386        match self.instance()? {
4387            CalculationInstanceRef::AcPf(instance) => Some(instance),
4388            _ => None,
4389        }
4390    }
4391
4392    fn dc_opf(&self) -> Option<&powerio_prob::DcOpfInstance> {
4393        match self.instance()? {
4394            CalculationInstanceRef::DcOpf(instance) => Some(instance),
4395            _ => None,
4396        }
4397    }
4398
4399    fn ac_opf(&self) -> Option<&powerio_prob::AcOpfInstance> {
4400        match self.instance()? {
4401            CalculationInstanceRef::AcOpf(instance) => Some(instance),
4402            _ => None,
4403        }
4404    }
4405
4406    fn mc_ac_pf(&self) -> Option<&powerio_prob::McAcPfInstance> {
4407        match self.instance()? {
4408            CalculationInstanceRef::McAcPf(instance) => Some(instance),
4409            _ => None,
4410        }
4411    }
4412
4413    fn ac_scuc(&self) -> Option<&powerio_prob::AcScucInstance> {
4414        match self.instance()? {
4415            CalculationInstanceRef::AcScuc(instance) => Some(instance),
4416            _ => None,
4417        }
4418    }
4419
4420    fn has_initial_point(&self) -> bool {
4421        match self.instance() {
4422            Some(CalculationInstanceRef::DcPf(instance)) => instance.initial_point().is_some(),
4423            Some(CalculationInstanceRef::AcPf(instance)) => instance.initial_point().is_some(),
4424            Some(CalculationInstanceRef::DcOpf(instance)) => instance.initial_point().is_some(),
4425            Some(CalculationInstanceRef::AcOpf(instance)) => instance.initial_point().is_some(),
4426            Some(CalculationInstanceRef::McAcPf(instance)) => instance.initial_point().is_some(),
4427            Some(CalculationInstanceRef::McAcOpf(instance)) => instance.initial_point().is_some(),
4428            Some(CalculationInstanceRef::LinDist3FlowOpf(instance)) => {
4429                instance.base_instance().initial_point().is_some()
4430            }
4431            _ => false,
4432        }
4433    }
4434}
4435
4436opaque_handle!(
4437    /// Owner-rooted balanced network view.
4438    PioBalancedNetwork,
4439    BalancedNetworkInner
4440);
4441
4442struct DetailedConnectivityInner {
4443    owner: Arc<BalancedNetworkInner>,
4444}
4445
4446impl DetailedConnectivityInner {
4447    fn details(&self) -> Option<&powerio_tx::DetailedConnectivity> {
4448        self.owner.network()?.detailed_connectivity().as_deref()
4449    }
4450}
4451
4452opaque_handle!(
4453    /// Owner-rooted source neutral hierarchy and detailed connectivity view.
4454    PioDetailedConnectivity,
4455    DetailedConnectivityInner
4456);
4457opaque_handle!(
4458    /// Owner-rooted multiconductor network view.
4459    PioMulticonductorNetwork,
4460    MulticonductorNetworkInner
4461);
4462opaque_handle!(
4463    /// Owner-rooted time series view.
4464    PioTimeSeriesHandle,
4465    ValueInner
4466);
4467opaque_handle!(
4468    /// Owner-rooted scenario set view.
4469    PioScenarioSetHandle,
4470    ValueInner
4471);
4472opaque_handle!(
4473    /// Owner-rooted operating point view.
4474    PioOperatingPoint,
4475    OperatingPointInner
4476);
4477opaque_handle!(
4478    /// Owner-rooted calculation instance view.
4479    PioCalculationInstance,
4480    CalculationInstanceInner
4481);
4482opaque_handle!(
4483    /// Owned DC OPF preparation whose borrowed row views remain valid until release.
4484    PioDcOpfPreparation,
4485    DcOpfPreparation
4486);
4487opaque_handle!(
4488    /// Owned AC OPF preparation whose borrowed row views remain valid until release.
4489    PioAcOpfPreparation,
4490    AcOpfPreparation
4491);
4492opaque_handle!(
4493    /// Owner-rooted calculation solution view.
4494    PioCalculationSolution,
4495    ValueInner
4496);
4497
4498fn module_handle(module: powerio::PioModule<PioValue>) -> *mut PioModule {
4499    PioModule::new_raw(ModuleInner { module })
4500}
4501
4502fn value_handle(value: ValueInner) -> *mut PioValueHandle {
4503    PioValueHandle::new_raw(value)
4504}
4505
4506unsafe fn require_value<'a>(value: *const PioValueHandle) -> Result<&'a ValueInner, *mut PioError> {
4507    unsafe { PioValueHandle::get(value) }.ok_or_else(|| {
4508        boundary_error(
4509            &codes::BIND_CAPI_NULL_HANDLE,
4510            "PioValueHandle must not be NULL",
4511        )
4512    })
4513}
4514
4515/// Parse one acquired grid exchange source.
4516///
4517/// # Safety
4518/// Pointers and handles must satisfy the crate-level safety requirements.
4519#[unsafe(no_mangle)]
4520pub unsafe extern "C" fn pio_parse(
4521    source: *const PioSource,
4522    format: *const c_char,
4523    format_len: usize,
4524    error: *mut *mut PioError,
4525) -> *mut PioModule {
4526    unsafe {
4527        entry(error, std::ptr::null_mut(), || {
4528            let source = PioSource::get(source).ok_or_else(|| {
4529                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioSource must not be NULL")
4530            })?;
4531            let format = optional_str(format, format_len, "format")?;
4532            let mut options = powerio::ParseOptions::default();
4533            if let Some(format) = format {
4534                options = options
4535                    .format(format)
4536                    .map_err(|failure| error_from_core(&failure))?;
4537            }
4538            powerio::parse_with_options(source.clone(), &options)
4539                .map(module_handle)
4540                .map_err(|failure| error_from_core(&failure))
4541        })
4542    }
4543}
4544
4545/// Deserialize one PowerIO IR source.
4546///
4547/// A document carries the independent PowerIO IR version reported by
4548/// `pio_schema_report`. This library refuses any unsupported identity or
4549/// version through `error`, naming what it found.
4550///
4551/// # Safety
4552/// Pointers and handles must satisfy the crate-level safety requirements.
4553#[unsafe(no_mangle)]
4554pub unsafe extern "C" fn pio_module_deserialize(
4555    source: *const PioSource,
4556    error: *mut *mut PioError,
4557) -> *mut PioModule {
4558    unsafe {
4559        entry(error, std::ptr::null_mut(), || {
4560            let source = PioSource::get(source).ok_or_else(|| {
4561                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioSource must not be NULL")
4562            })?;
4563            powerio::deserialize(source.clone())
4564                .map(module_handle)
4565                .map_err(|failure| error_from_core(&failure))
4566        })
4567    }
4568}
4569
4570unsafe fn transform_module<T>(
4571    module: *const PioModule,
4572    error: *mut *mut PioError,
4573    transform: impl FnOnce(
4574        &powerio::PioModule<PioValue>,
4575    ) -> Result<powerio::PioModule<T>, powerio_core::Error>,
4576    wrap: impl FnOnce(T) -> PioValue,
4577) -> *mut PioModule {
4578    unsafe {
4579        entry(error, std::ptr::null_mut(), || {
4580            let module = PioModule::get(module).ok_or_else(|| {
4581                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4582            })?;
4583            transform(&module.module)
4584                .map(|derived| module_handle(derived.map_value(wrap)))
4585                .map_err(|failure| error_from_core(&failure))
4586        })
4587    }
4588}
4589
4590/// Construct a DC power flow calculation module from a balanced network module.
4591///
4592/// # Safety
4593/// Pointers and handles must satisfy the crate-level safety requirements.
4594#[unsafe(no_mangle)]
4595pub unsafe extern "C" fn pio_module_to_dc_pf_instance(
4596    module: *const PioModule,
4597    error: *mut *mut PioError,
4598) -> *mut PioModule {
4599    unsafe {
4600        transform_module(
4601            module,
4602            error,
4603            powerio::transform::to_dc_pf_instance,
4604            PioValue::DcPfInstance,
4605        )
4606    }
4607}
4608
4609/// Construct an AC power flow calculation module from a balanced network module.
4610///
4611/// # Safety
4612/// Pointers and handles must satisfy the crate-level safety requirements.
4613#[unsafe(no_mangle)]
4614pub unsafe extern "C" fn pio_module_to_ac_pf_instance(
4615    module: *const PioModule,
4616    error: *mut *mut PioError,
4617) -> *mut PioModule {
4618    unsafe {
4619        transform_module(
4620            module,
4621            error,
4622            powerio::transform::to_ac_pf_instance,
4623            PioValue::AcPfInstance,
4624        )
4625    }
4626}
4627
4628/// Construct a DC optimal power flow calculation module from a balanced network module.
4629///
4630/// # Safety
4631/// Pointers and handles must satisfy the crate-level safety requirements.
4632#[unsafe(no_mangle)]
4633pub unsafe extern "C" fn pio_module_to_dc_opf_instance(
4634    module: *const PioModule,
4635    error: *mut *mut PioError,
4636) -> *mut PioModule {
4637    unsafe {
4638        transform_module(
4639            module,
4640            error,
4641            powerio::transform::to_dc_opf_instance,
4642            PioValue::DcOpfInstance,
4643        )
4644    }
4645}
4646
4647/// Construct an AC optimal power flow calculation module from a balanced network module.
4648///
4649/// # Safety
4650/// Pointers and handles must satisfy the crate-level safety requirements.
4651#[unsafe(no_mangle)]
4652pub unsafe extern "C" fn pio_module_to_ac_opf_instance(
4653    module: *const PioModule,
4654    error: *mut *mut PioError,
4655) -> *mut PioModule {
4656    unsafe {
4657        transform_module(
4658            module,
4659            error,
4660            powerio::transform::to_ac_opf_instance,
4661            PioValue::AcOpfInstance,
4662        )
4663    }
4664}
4665
4666/// Construct a multiconductor AC power flow calculation module from a
4667/// multiconductor network module.
4668///
4669/// # Safety
4670/// Pointers and handles must satisfy the crate-level safety requirements.
4671#[unsafe(no_mangle)]
4672pub unsafe extern "C" fn pio_module_to_mc_ac_pf_instance(
4673    module: *const PioModule,
4674    error: *mut *mut PioError,
4675) -> *mut PioModule {
4676    unsafe {
4677        transform_module(
4678            module,
4679            error,
4680            powerio::transform::to_mc_ac_pf_instance,
4681            PioValue::McAcPfInstance,
4682        )
4683    }
4684}
4685
4686/// Construct a multiconductor AC optimal power flow calculation module from a
4687/// multiconductor network module.
4688///
4689/// # Safety
4690/// Pointers and handles must satisfy the crate-level safety requirements.
4691#[unsafe(no_mangle)]
4692pub unsafe extern "C" fn pio_module_to_mc_ac_opf_instance(
4693    module: *const PioModule,
4694    error: *mut *mut PioError,
4695) -> *mut PioModule {
4696    unsafe {
4697        transform_module(
4698            module,
4699            error,
4700            powerio::transform::to_mc_ac_opf_instance,
4701            PioValue::McAcOpfInstance,
4702        )
4703    }
4704}
4705
4706///
4707/// # Safety
4708/// Pointers and handles must satisfy the crate-level safety requirements.
4709#[unsafe(no_mangle)]
4710pub unsafe extern "C" fn pio_module_to_lindist3flow_opf_instance(
4711    module: *const PioModule,
4712    error: *mut *mut PioError,
4713) -> *mut PioModule {
4714    unsafe {
4715        transform_module(
4716            module,
4717            error,
4718            powerio::transform::to_lindist3flow_opf_instance,
4719            PioValue::LinDist3FlowOpfInstance,
4720        )
4721    }
4722}
4723
4724/// Apply one geographic layer to a balanced or multiconductor network module.
4725/// The input module is unchanged. When `out_report` is not NULL, it receives
4726/// an independently owned report handle.
4727///
4728/// # Safety
4729/// Pointers and handles must satisfy the crate-level safety requirements.
4730#[unsafe(no_mangle)]
4731pub unsafe extern "C" fn pio_module_apply_geo_layer(
4732    module: *const PioModule,
4733    layer: *const PioGeoLayer,
4734    out_report: *mut *mut PioGeoApplyReport,
4735    error: *mut *mut PioError,
4736) -> *mut PioModule {
4737    unsafe {
4738        if !out_report.is_null() {
4739            *out_report = std::ptr::null_mut();
4740        }
4741        entry(error, std::ptr::null_mut(), || {
4742            let module = PioModule::get(module).ok_or_else(|| {
4743                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4744            })?;
4745            let layer = PioGeoLayer::get(layer).ok_or_else(|| {
4746                boundary_error(
4747                    &codes::BIND_CAPI_NULL_HANDLE,
4748                    "PioGeoLayer must not be NULL",
4749                )
4750            })?;
4751            let (mut derived, report) = powerio::apply_geo_layer(&module.module, &layer.layer)
4752                .map_err(|failure| error_from_core(&failure))?;
4753            for diagnostic in &layer.diagnostics {
4754                derived
4755                    .add_diagnostic(diagnostic.clone())
4756                    .map_err(|failure| error_from_core(&failure))?;
4757            }
4758            if !out_report.is_null() {
4759                *out_report = PioGeoApplyReport::new_raw(report);
4760            }
4761            Ok(module_handle(derived))
4762        })
4763    }
4764}
4765
4766///
4767/// # Safety
4768/// Pointers and handles must satisfy the crate-level safety requirements.
4769#[unsafe(no_mangle)]
4770pub unsafe extern "C" fn pio_geo_apply_report_matched_buses(
4771    report: *const PioGeoApplyReport,
4772) -> usize {
4773    unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.matched_buses)
4774}
4775
4776///
4777/// # Safety
4778/// Pointers and handles must satisfy the crate-level safety requirements.
4779#[unsafe(no_mangle)]
4780pub unsafe extern "C" fn pio_geo_apply_report_matched_branches(
4781    report: *const PioGeoApplyReport,
4782) -> usize {
4783    unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.matched_branches)
4784}
4785
4786///
4787/// # Safety
4788/// Pointers and handles must satisfy the crate-level safety requirements.
4789#[unsafe(no_mangle)]
4790pub unsafe extern "C" fn pio_geo_apply_report_unmatched_features(
4791    report: *const PioGeoApplyReport,
4792) -> usize {
4793    unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.unmatched_features)
4794}
4795
4796///
4797/// # Safety
4798/// Pointers and handles must satisfy the crate-level safety requirements.
4799#[unsafe(no_mangle)]
4800pub unsafe extern "C" fn pio_geo_apply_report_unlocated_buses(
4801    report: *const PioGeoApplyReport,
4802) -> usize {
4803    unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.unlocated_buses)
4804}
4805
4806///
4807/// # Safety
4808/// Pointers and handles must satisfy the crate-level safety requirements.
4809#[unsafe(no_mangle)]
4810pub unsafe extern "C" fn pio_geo_apply_report_unlocated_branches(
4811    report: *const PioGeoApplyReport,
4812) -> usize {
4813    unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.unlocated_branches)
4814}
4815
4816///
4817/// # Safety
4818/// Pointers and handles must satisfy the crate-level safety requirements.
4819#[unsafe(no_mangle)]
4820pub unsafe extern "C" fn pio_geo_apply_report_note_count(
4821    report: *const PioGeoApplyReport,
4822) -> usize {
4823    unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.notes.len())
4824}
4825
4826///
4827/// # Safety
4828/// Pointers and handles must satisfy the crate-level safety requirements.
4829#[unsafe(no_mangle)]
4830pub unsafe extern "C" fn pio_geo_apply_report_note_at(
4831    report: *const PioGeoApplyReport,
4832    index: usize,
4833    error: *mut *mut PioError,
4834) -> PioStringView {
4835    unsafe {
4836        entry(error, PioStringView::EMPTY, || {
4837            let report = PioGeoApplyReport::get(report).ok_or_else(|| {
4838                boundary_error(
4839                    &codes::BIND_CAPI_NULL_HANDLE,
4840                    "PioGeoApplyReport must not be NULL",
4841                )
4842            })?;
4843            let note = report.notes.get(index).ok_or_else(|| {
4844                boundary_error(
4845                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4846                    format!("geo apply note index {index} is out of range"),
4847                )
4848            })?;
4849            Ok(PioStringView::new(note))
4850        })
4851    }
4852}
4853
4854///
4855/// # Safety
4856/// Pointers and handles must satisfy the crate-level safety requirements.
4857/// The input handle must remain live and immutable throughout this call.
4858#[unsafe(no_mangle)]
4859pub unsafe extern "C" fn pio_geo_apply_report_retain(
4860    report: *const PioGeoApplyReport,
4861) -> *mut PioGeoApplyReport {
4862    unsafe { PioGeoApplyReport::retain_raw(report) }
4863}
4864
4865///
4866/// # Safety
4867/// Pointers and handles must satisfy the crate-level safety requirements.
4868/// A non-null handle must be owned and unused by concurrent calls.
4869#[unsafe(no_mangle)]
4870pub unsafe extern "C" fn pio_geo_apply_report_release(report: *mut PioGeoApplyReport) {
4871    unsafe { PioGeoApplyReport::release_raw(report) };
4872}
4873
4874/// Return an owner-rooted view of the module's value.
4875///
4876/// # Safety
4877/// Pointers and handles must satisfy the crate-level safety requirements.
4878#[unsafe(no_mangle)]
4879pub unsafe extern "C" fn pio_module_value(module: *const PioModule) -> *mut PioValueHandle {
4880    unsafe { PioModule::arc(module) }.map_or(std::ptr::null_mut(), |owner| {
4881        value_handle(ValueInner::root(owner))
4882    })
4883}
4884
4885/// Return the module's stored diagnostics.
4886///
4887/// # Safety
4888/// Pointers and handles must satisfy the crate-level safety requirements.
4889#[unsafe(no_mangle)]
4890pub unsafe extern "C" fn pio_module_diagnostics(module: *const PioModule) -> *mut PioDiagnostics {
4891    unsafe { PioModule::arc(module) }.map_or(std::ptr::null_mut(), |module| {
4892        PioDiagnostics::new_raw(DiagnosticsInner {
4893            owner: DiagnosticsOwner::Module(module),
4894        })
4895    })
4896}
4897
4898fn source_relation_name(relation: powerio_core::SourceRelation) -> &'static str {
4899    match relation {
4900        powerio_core::SourceRelation::Exact => "exact",
4901        powerio_core::SourceRelation::Defaulted => "defaulted",
4902        powerio_core::SourceRelation::Inferred => "inferred",
4903        powerio_core::SourceRelation::ConvertedUnits => "converted_units",
4904        powerio_core::SourceRelation::Aggregated => "aggregated",
4905        powerio_core::SourceRelation::Split => "split",
4906        powerio_core::SourceRelation::Synthetic => "synthetic",
4907        powerio_core::SourceRelation::Transformed => "transformed",
4908        powerio_core::SourceRelation::RetainedExtra => "retained_extra",
4909        _ => "unknown",
4910    }
4911}
4912
4913fn history_kind_name(kind: HistoryKind) -> &'static str {
4914    match kind {
4915        HistoryKind::Parse => "parse",
4916        HistoryKind::Transform => "transform",
4917        HistoryKind::Edit => "edit",
4918        HistoryKind::Repair => "repair",
4919        HistoryKind::Solve => "solve",
4920        _ => "unknown",
4921    }
4922}
4923
4924fn json_value_kind(value: &serde_json::Value) -> &'static str {
4925    match value {
4926        serde_json::Value::Null => "null",
4927        serde_json::Value::Bool(_) => "boolean",
4928        serde_json::Value::Number(_) => "number",
4929        serde_json::Value::String(_) => "string",
4930        serde_json::Value::Array(_) => "array",
4931        serde_json::Value::Object(_) => "object",
4932    }
4933}
4934
4935/// Read the program identity recorded with a module.
4936///
4937/// # Safety
4938/// Pointers and handles must satisfy the crate-level safety requirements.
4939#[unsafe(no_mangle)]
4940pub unsafe extern "C" fn pio_module_producer(
4941    module: *const PioModule,
4942    output: *mut PioModuleProducerView,
4943    error: *mut *mut PioError,
4944) -> bool {
4945    unsafe {
4946        entry(error, false, || {
4947            let module = PioModule::get(module).ok_or_else(|| {
4948                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4949            })?;
4950            let producer = module.module.producer();
4951            *require_output(output, "output")? = PioModuleProducerView {
4952                name: PioStringView::new(producer.name()),
4953                version: PioStringView::new(producer.version()),
4954            };
4955            Ok(true)
4956        })
4957    }
4958}
4959
4960///
4961/// # Safety
4962/// Pointers and handles must satisfy the crate-level safety requirements.
4963#[unsafe(no_mangle)]
4964pub unsafe extern "C" fn pio_module_source_count(module: *const PioModule) -> usize {
4965    unsafe { PioModule::get(module) }.map_or(0, |module| module.module.sources().len())
4966}
4967
4968/// Read one durable source descriptor by zero based position.
4969///
4970/// # Safety
4971/// Pointers and handles must satisfy the crate-level safety requirements.
4972#[unsafe(no_mangle)]
4973pub unsafe extern "C" fn pio_module_source_at(
4974    module: *const PioModule,
4975    index: usize,
4976    output: *mut PioModuleSourceView,
4977    error: *mut *mut PioError,
4978) -> bool {
4979    unsafe {
4980        entry(error, false, || {
4981            let module = PioModule::get(module).ok_or_else(|| {
4982                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4983            })?;
4984            let source = module.module.sources().get(index).ok_or_else(|| {
4985                boundary_error(
4986                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4987                    format!("module source index {index} is out of range"),
4988                )
4989            })?;
4990            let (format, has_format) = source
4991                .format()
4992                .map_or((PioStringView::EMPTY, false), |format| {
4993                    (PioStringView::new(format.as_str()), true)
4994                });
4995            let digest = source.digest();
4996            *require_output(output, "output")? = PioModuleSourceView {
4997                id: PioStringView::new(source.id().as_str()),
4998                name: PioStringView::new(source.name()),
4999                byte_length: source.byte_length(),
5000                format,
5001                has_format,
5002                digest_algorithm: digest.map_or(PioStringView::EMPTY, |digest| {
5003                    PioStringView::new(digest.algorithm().as_str())
5004                }),
5005                digest: digest.map_or(PioStringView::EMPTY, |digest| {
5006                    PioStringView::new(digest.value())
5007                }),
5008                has_digest: digest.is_some(),
5009            };
5010            Ok(true)
5011        })
5012    }
5013}
5014
5015///
5016/// # Safety
5017/// Pointers and handles must satisfy the crate-level safety requirements.
5018#[unsafe(no_mangle)]
5019pub unsafe extern "C" fn pio_module_source_map_count(module: *const PioModule) -> usize {
5020    unsafe { PioModule::get(module) }.map_or(0, |module| module.module.source_map().len())
5021}
5022
5023/// Read one source map entry by zero based position.
5024///
5025/// # Safety
5026/// Pointers and handles must satisfy the crate-level safety requirements.
5027#[unsafe(no_mangle)]
5028pub unsafe extern "C" fn pio_module_source_map_at(
5029    module: *const PioModule,
5030    index: usize,
5031    output: *mut PioModuleSourceMapEntryView,
5032    error: *mut *mut PioError,
5033) -> bool {
5034    unsafe {
5035        entry(error, false, || {
5036            let module = PioModule::get(module).ok_or_else(|| {
5037                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5038            })?;
5039            let source_map = module.module.source_map().get(index).ok_or_else(|| {
5040                boundary_error(
5041                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5042                    format!("module source map index {index} is out of range"),
5043                )
5044            })?;
5045            *require_output(output, "output")? = PioModuleSourceMapEntryView {
5046                target: PioStringView::new(source_map.target()),
5047                relation: PioStringView::new(source_relation_name(source_map.relation())),
5048                span_count: source_map.spans().len(),
5049            };
5050            Ok(true)
5051        })
5052    }
5053}
5054
5055/// Read one byte range from a source map entry.
5056///
5057/// # Safety
5058/// Pointers and handles must satisfy the crate-level safety requirements.
5059#[unsafe(no_mangle)]
5060pub unsafe extern "C" fn pio_module_source_map_span_at(
5061    module: *const PioModule,
5062    entry_index: usize,
5063    span_index: usize,
5064    output: *mut PioSourceSpanView,
5065    error: *mut *mut PioError,
5066) -> bool {
5067    unsafe {
5068        entry(error, false, || {
5069            let module = PioModule::get(module).ok_or_else(|| {
5070                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5071            })?;
5072            let source_map = module.module.source_map().get(entry_index).ok_or_else(|| {
5073                boundary_error(
5074                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5075                    format!("module source map index {entry_index} is out of range"),
5076                )
5077            })?;
5078            let span = source_map.spans().get(span_index).ok_or_else(|| {
5079                boundary_error(
5080                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5081                    format!(
5082                        "source map span index {span_index} is out of range for entry {entry_index}"
5083                    ),
5084                )
5085            })?;
5086            *require_output(output, "output")? = PioSourceSpanView {
5087                source: PioStringView::new(span.source().as_str()),
5088                byte_start: span.byte_start(),
5089                byte_end: span.byte_end(),
5090            };
5091            Ok(true)
5092        })
5093    }
5094}
5095
5096///
5097/// # Safety
5098/// Pointers and handles must satisfy the crate-level safety requirements.
5099#[unsafe(no_mangle)]
5100pub unsafe extern "C" fn pio_module_history_count(module: *const PioModule) -> usize {
5101    unsafe { PioModule::get(module) }.map_or(0, |module| module.module.history().len())
5102}
5103
5104/// Read one operation from module history by zero based position.
5105///
5106/// # Safety
5107/// Pointers and handles must satisfy the crate-level safety requirements.
5108#[unsafe(no_mangle)]
5109pub unsafe extern "C" fn pio_module_history_at(
5110    module: *const PioModule,
5111    index: usize,
5112    output: *mut PioModuleHistoryEntryView,
5113    error: *mut *mut PioError,
5114) -> bool {
5115    unsafe {
5116        entry(error, false, || {
5117            let module = PioModule::get(module).ok_or_else(|| {
5118                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5119            })?;
5120            let history = module.module.history().get(index).ok_or_else(|| {
5121                boundary_error(
5122                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5123                    format!("module history index {index} is out of range"),
5124                )
5125            })?;
5126            let (input_type, has_input_type) = optional_string_view(history.input_type());
5127            let (output_type, has_output_type) = optional_string_view(history.output_type());
5128            *require_output(output, "output")? = PioModuleHistoryEntryView {
5129                id: PioStringView::new(history.id().as_str()),
5130                kind: PioStringView::new(history_kind_name(history.kind())),
5131                name: PioStringView::new(history.name()),
5132                input_type,
5133                has_input_type,
5134                output_type,
5135                has_output_type,
5136                parameter_count: history.parameters().len(),
5137                assumption_count: history.assumptions().len(),
5138                loss_count: history.losses().len(),
5139            };
5140            Ok(true)
5141        })
5142    }
5143}
5144
5145/// Read one named structured history parameter by zero based position.
5146///
5147/// # Safety
5148/// Pointers and handles must satisfy the crate-level safety requirements.
5149#[unsafe(no_mangle)]
5150pub unsafe extern "C" fn pio_module_history_parameter_at(
5151    module: *const PioModule,
5152    history_index: usize,
5153    parameter_index: usize,
5154    output: *mut PioModuleHistoryParameterView,
5155    error: *mut *mut PioError,
5156) -> bool {
5157    unsafe {
5158        entry(error, false, || {
5159            let module = PioModule::get(module).ok_or_else(|| {
5160                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5161            })?;
5162            let history = module.module.history().get(history_index).ok_or_else(|| {
5163                boundary_error(
5164                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5165                    format!("module history index {history_index} is out of range"),
5166                )
5167            })?;
5168            let (name, value) = history
5169                .parameters()
5170                .iter()
5171                .nth(parameter_index)
5172                .ok_or_else(|| {
5173                    boundary_error(
5174                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5175                        format!(
5176                            "history parameter index {parameter_index} is out of range for entry {history_index}"
5177                        ),
5178                    )
5179                })?;
5180            *require_output(output, "output")? = PioModuleHistoryParameterView {
5181                name: PioStringView::new(name),
5182                value_kind: PioStringView::new(json_value_kind(value)),
5183            };
5184            Ok(true)
5185        })
5186    }
5187}
5188
5189/// Return an owner-rooted structured history parameter value.
5190///
5191/// # Safety
5192/// Pointers and handles must satisfy the crate-level safety requirements.
5193#[unsafe(no_mangle)]
5194pub unsafe extern "C" fn pio_module_history_parameter_value_at(
5195    module: *const PioModule,
5196    history_index: usize,
5197    parameter_index: usize,
5198    error: *mut *mut PioError,
5199) -> *mut PioJsonValue {
5200    unsafe {
5201        entry(error, std::ptr::null_mut(), || {
5202            let owner = PioModule::arc(module).ok_or_else(|| {
5203                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5204            })?;
5205            let history = owner.module.history().get(history_index).ok_or_else(|| {
5206                boundary_error(
5207                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5208                    format!("module history index {history_index} is out of range"),
5209                )
5210            })?;
5211            if history.parameters().values().nth(parameter_index).is_none() {
5212                return Err(boundary_error(
5213                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5214                    format!(
5215                        "history parameter index {parameter_index} is out of range for entry {history_index}"
5216                    ),
5217                ));
5218            }
5219            Ok(PioJsonValue::new_raw(JsonValueInner {
5220                owner,
5221                root: ModuleJsonRoot::HistoryParameter {
5222                    history_index,
5223                    parameter_index,
5224                },
5225                steps: Vec::new(),
5226            }))
5227        })
5228    }
5229}
5230
5231/// Read one assumption attached to a history entry.
5232///
5233/// # Safety
5234/// Pointers and handles must satisfy the crate-level safety requirements.
5235#[unsafe(no_mangle)]
5236pub unsafe extern "C" fn pio_module_history_assumption_at(
5237    module: *const PioModule,
5238    history_index: usize,
5239    assumption_index: usize,
5240    error: *mut *mut PioError,
5241) -> PioStringView {
5242    unsafe {
5243        entry(error, PioStringView::EMPTY, || {
5244            let module = PioModule::get(module).ok_or_else(|| {
5245                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5246            })?;
5247            let history = module.module.history().get(history_index).ok_or_else(|| {
5248                boundary_error(
5249                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5250                    format!("module history index {history_index} is out of range"),
5251                )
5252            })?;
5253            history
5254                .assumptions()
5255                .get(assumption_index)
5256                .map(|value| PioStringView::new(value))
5257                .ok_or_else(|| {
5258                    boundary_error(
5259                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5260                        format!(
5261                            "history assumption index {assumption_index} is out of range for entry {history_index}"
5262                        ),
5263                    )
5264                })
5265        })
5266    }
5267}
5268
5269/// Read one declared loss attached to a history entry.
5270///
5271/// # Safety
5272/// Pointers and handles must satisfy the crate-level safety requirements.
5273#[unsafe(no_mangle)]
5274pub unsafe extern "C" fn pio_module_history_loss_at(
5275    module: *const PioModule,
5276    history_index: usize,
5277    loss_index: usize,
5278    error: *mut *mut PioError,
5279) -> PioStringView {
5280    unsafe {
5281        entry(error, PioStringView::EMPTY, || {
5282            let module = PioModule::get(module).ok_or_else(|| {
5283                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5284            })?;
5285            let history = module.module.history().get(history_index).ok_or_else(|| {
5286                boundary_error(
5287                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5288                    format!("module history index {history_index} is out of range"),
5289                )
5290            })?;
5291            history
5292                .losses()
5293                .get(loss_index)
5294                .map(|value| PioStringView::new(value))
5295                .ok_or_else(|| {
5296                    boundary_error(
5297                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5298                        format!(
5299                            "history loss index {loss_index} is out of range for entry {history_index}"
5300                        ),
5301                    )
5302                })
5303        })
5304    }
5305}
5306
5307///
5308/// # Safety
5309/// Pointers and handles must satisfy the crate-level safety requirements.
5310#[unsafe(no_mangle)]
5311pub unsafe extern "C" fn pio_module_extension_count(module: *const PioModule) -> usize {
5312    unsafe { PioModule::get(module) }.map_or(0, |module| module.module.extensions().len())
5313}
5314
5315/// Read one namespaced structured module extension by zero based position.
5316///
5317/// # Safety
5318/// Pointers and handles must satisfy the crate-level safety requirements.
5319#[unsafe(no_mangle)]
5320pub unsafe extern "C" fn pio_module_extension_at(
5321    module: *const PioModule,
5322    index: usize,
5323    output: *mut PioModuleExtensionView,
5324    error: *mut *mut PioError,
5325) -> bool {
5326    unsafe {
5327        entry(error, false, || {
5328            let module = PioModule::get(module).ok_or_else(|| {
5329                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5330            })?;
5331            let (namespace, value) =
5332                module
5333                    .module
5334                    .extensions()
5335                    .iter()
5336                    .nth(index)
5337                    .ok_or_else(|| {
5338                        boundary_error(
5339                            &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5340                            format!("module extension index {index} is out of range"),
5341                        )
5342                    })?;
5343            *require_output(output, "output")? = PioModuleExtensionView {
5344                namespace: PioStringView::new(namespace),
5345                value_kind: PioStringView::new(json_value_kind(value)),
5346            };
5347            Ok(true)
5348        })
5349    }
5350}
5351
5352/// Return an owner-rooted structured module extension value.
5353///
5354/// # Safety
5355/// Pointers and handles must satisfy the crate-level safety requirements.
5356#[unsafe(no_mangle)]
5357pub unsafe extern "C" fn pio_module_extension_value_at(
5358    module: *const PioModule,
5359    index: usize,
5360    error: *mut *mut PioError,
5361) -> *mut PioJsonValue {
5362    unsafe {
5363        entry(error, std::ptr::null_mut(), || {
5364            let owner = PioModule::arc(module).ok_or_else(|| {
5365                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5366            })?;
5367            if owner.module.extensions().values().nth(index).is_none() {
5368                return Err(boundary_error(
5369                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5370                    format!("module extension index {index} is out of range"),
5371                ));
5372            }
5373            Ok(PioJsonValue::new_raw(JsonValueInner {
5374                owner,
5375                root: ModuleJsonRoot::Extension(index),
5376                steps: Vec::new(),
5377            }))
5378        })
5379    }
5380}
5381
5382fn json_value_view(value: &serde_json::Value) -> PioJsonValueView {
5383    let mut view = PioJsonValueView {
5384        kind: PioStringView::new(json_value_kind(value)),
5385        boolean_value: false,
5386        number_kind: PioStringView::EMPTY,
5387        signed_integer_value: 0,
5388        unsigned_integer_value: 0,
5389        floating_point_value: 0.0,
5390        string_value: PioStringView::EMPTY,
5391        element_count: 0,
5392    };
5393    match value {
5394        serde_json::Value::Bool(value) => view.boolean_value = *value,
5395        serde_json::Value::Number(value) if value.is_i64() => {
5396            view.number_kind = PioStringView::new("signed_integer");
5397            view.signed_integer_value = value.as_i64().unwrap_or_default();
5398        }
5399        serde_json::Value::Number(value) if value.is_u64() => {
5400            view.number_kind = PioStringView::new("unsigned_integer");
5401            view.unsigned_integer_value = value.as_u64().unwrap_or_default();
5402        }
5403        serde_json::Value::Number(value) => {
5404            view.number_kind = PioStringView::new("floating_point");
5405            view.floating_point_value = value.as_f64().unwrap_or(f64::NAN);
5406        }
5407        serde_json::Value::String(value) => view.string_value = PioStringView::new(value),
5408        serde_json::Value::Array(values) => view.element_count = values.len(),
5409        serde_json::Value::Object(values) => view.element_count = values.len(),
5410        serde_json::Value::Null => {}
5411    }
5412    view
5413}
5414
5415/// Read the type and scalar or collection data for a structured value.
5416///
5417/// # Safety
5418/// Pointers and handles must satisfy the crate-level safety requirements.
5419#[unsafe(no_mangle)]
5420pub unsafe extern "C" fn pio_json_value_get(
5421    value: *const PioJsonValue,
5422    output: *mut PioJsonValueView,
5423    error: *mut *mut PioError,
5424) -> bool {
5425    unsafe {
5426        entry(error, false, || {
5427            let value = PioJsonValue::get(value).ok_or_else(|| {
5428                boundary_error(
5429                    &codes::BIND_CAPI_NULL_HANDLE,
5430                    "PioJsonValue must not be NULL",
5431                )
5432            })?;
5433            let value = value.value().ok_or_else(|| {
5434                boundary_error(
5435                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5436                    "the structured value path is out of range",
5437                )
5438            })?;
5439            *require_output(output, "output")? = json_value_view(value);
5440            Ok(true)
5441        })
5442    }
5443}
5444
5445/// Return one owner-rooted element from a structured JSON array.
5446///
5447/// # Safety
5448/// Pointers and handles must satisfy the crate-level safety requirements.
5449#[unsafe(no_mangle)]
5450pub unsafe extern "C" fn pio_json_value_array_at(
5451    value: *const PioJsonValue,
5452    index: usize,
5453    error: *mut *mut PioError,
5454) -> *mut PioJsonValue {
5455    unsafe {
5456        entry(error, std::ptr::null_mut(), || {
5457            let value = PioJsonValue::get(value).ok_or_else(|| {
5458                boundary_error(
5459                    &codes::BIND_CAPI_NULL_HANDLE,
5460                    "PioJsonValue must not be NULL",
5461                )
5462            })?;
5463            let values = value
5464                .value()
5465                .and_then(serde_json::Value::as_array)
5466                .ok_or_else(|| {
5467                    boundary_error(
5468                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
5469                        "the structured value is not an array",
5470                    )
5471                })?;
5472            if values.get(index).is_none() {
5473                return Err(boundary_error(
5474                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5475                    format!("structured array index {index} is out of range"),
5476                ));
5477            }
5478            Ok(PioJsonValue::new_raw(
5479                value.child(JsonValueStep::Array(index)),
5480            ))
5481        })
5482    }
5483}
5484
5485/// Read one key and value type from a structured JSON object.
5486///
5487/// # Safety
5488/// Pointers and handles must satisfy the crate-level safety requirements.
5489#[unsafe(no_mangle)]
5490pub unsafe extern "C" fn pio_json_value_object_entry_at(
5491    value: *const PioJsonValue,
5492    index: usize,
5493    output: *mut PioJsonObjectEntryView,
5494    error: *mut *mut PioError,
5495) -> bool {
5496    unsafe {
5497        entry(error, false, || {
5498            let value = PioJsonValue::get(value).ok_or_else(|| {
5499                boundary_error(
5500                    &codes::BIND_CAPI_NULL_HANDLE,
5501                    "PioJsonValue must not be NULL",
5502                )
5503            })?;
5504            let values = value
5505                .value()
5506                .and_then(serde_json::Value::as_object)
5507                .ok_or_else(|| {
5508                    boundary_error(
5509                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
5510                        "the structured value is not an object",
5511                    )
5512                })?;
5513            let (key, value) = values.iter().nth(index).ok_or_else(|| {
5514                boundary_error(
5515                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5516                    format!("structured object index {index} is out of range"),
5517                )
5518            })?;
5519            *require_output(output, "output")? = PioJsonObjectEntryView {
5520                key: PioStringView::new(key),
5521                value_kind: PioStringView::new(json_value_kind(value)),
5522            };
5523            Ok(true)
5524        })
5525    }
5526}
5527
5528/// Return one owner-rooted value from a structured JSON object by position.
5529///
5530/// # Safety
5531/// Pointers and handles must satisfy the crate-level safety requirements.
5532#[unsafe(no_mangle)]
5533pub unsafe extern "C" fn pio_json_value_object_value_at(
5534    value: *const PioJsonValue,
5535    index: usize,
5536    error: *mut *mut PioError,
5537) -> *mut PioJsonValue {
5538    unsafe {
5539        entry(error, std::ptr::null_mut(), || {
5540            let value = PioJsonValue::get(value).ok_or_else(|| {
5541                boundary_error(
5542                    &codes::BIND_CAPI_NULL_HANDLE,
5543                    "PioJsonValue must not be NULL",
5544                )
5545            })?;
5546            let values = value
5547                .value()
5548                .and_then(serde_json::Value::as_object)
5549                .ok_or_else(|| {
5550                    boundary_error(
5551                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
5552                        "the structured value is not an object",
5553                    )
5554                })?;
5555            if values.values().nth(index).is_none() {
5556                return Err(boundary_error(
5557                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5558                    format!("structured object index {index} is out of range"),
5559                ));
5560            }
5561            Ok(PioJsonValue::new_raw(
5562                value.child(JsonValueStep::Object(index)),
5563            ))
5564        })
5565    }
5566}
5567
5568///
5569/// # Safety
5570/// Pointers and handles must satisfy the crate-level safety requirements.
5571/// The input handle must remain live and immutable throughout this call.
5572#[unsafe(no_mangle)]
5573pub unsafe extern "C" fn pio_json_value_retain(value: *const PioJsonValue) -> *mut PioJsonValue {
5574    unsafe { PioJsonValue::retain_raw(value) }
5575}
5576
5577///
5578/// # Safety
5579/// Pointers and handles must satisfy the crate-level safety requirements.
5580/// A non-null handle must be owned and unused by concurrent calls.
5581#[unsafe(no_mangle)]
5582pub unsafe extern "C" fn pio_json_value_release(value: *mut PioJsonValue) {
5583    unsafe { PioJsonValue::release_raw(value) };
5584}
5585
5586///
5587/// # Safety
5588/// Pointers and handles must satisfy the crate-level safety requirements.
5589/// The input handle must remain live and immutable throughout this call.
5590#[unsafe(no_mangle)]
5591pub unsafe extern "C" fn pio_module_retain(module: *const PioModule) -> *mut PioModule {
5592    unsafe { PioModule::retain_raw(module) }
5593}
5594
5595///
5596/// # Safety
5597/// Pointers and handles must satisfy the crate-level safety requirements.
5598/// A non-null handle must be owned and unused by concurrent calls.
5599#[unsafe(no_mangle)]
5600pub unsafe extern "C" fn pio_module_release(module: *mut PioModule) {
5601    unsafe { PioModule::release_raw(module) };
5602}
5603
5604/// Canonical structural type name, such as `powerio.BalancedNetwork`.
5605///
5606/// # Safety
5607/// Pointers and handles must satisfy the crate-level safety requirements.
5608#[unsafe(no_mangle)]
5609pub unsafe extern "C" fn pio_value_type_name(value: *const PioValueHandle) -> PioStringView {
5610    unsafe { PioValueHandle::get(value) }
5611        .and_then(ValueInner::value)
5612        .map_or(PioStringView::EMPTY, |value| {
5613            PioStringView::new(value.type_name())
5614        })
5615}
5616
5617/// Exact structural type predicate.
5618///
5619/// # Safety
5620/// Pointers and handles must satisfy the crate-level safety requirements.
5621#[unsafe(no_mangle)]
5622pub unsafe extern "C" fn pio_value_is_type(
5623    value: *const PioValueHandle,
5624    type_name: *const c_char,
5625    type_name_len: usize,
5626) -> bool {
5627    let Some(value) = unsafe { PioValueHandle::get(value) }.and_then(ValueInner::value) else {
5628        return false;
5629    };
5630    let Ok(type_name) = (unsafe { input_str(type_name, type_name_len, "type_name") }) else {
5631        return false;
5632    };
5633    value.type_name() == type_name
5634}
5635
5636/// Borrow the value as a balanced network without serialization or copying.
5637///
5638/// # Safety
5639/// Pointers and handles must satisfy the crate-level safety requirements.
5640#[unsafe(no_mangle)]
5641pub unsafe extern "C" fn pio_value_balanced_network(
5642    value: *const PioValueHandle,
5643    error: *mut *mut PioError,
5644) -> *mut PioBalancedNetwork {
5645    unsafe {
5646        entry(error, std::ptr::null_mut(), || {
5647            let value = require_value(value)?;
5648            if !matches!(value.value(), Some(PioValue::BalancedNetwork(_))) {
5649                return Err(boundary_error(
5650                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
5651                    "the value is not powerio.BalancedNetwork",
5652                ));
5653            }
5654            Ok(PioBalancedNetwork::new_raw(BalancedNetworkInner {
5655                value: ValueInner {
5656                    owner: Arc::clone(&value.owner),
5657                    steps: value.steps.clone(),
5658                },
5659                projection: BalancedNetworkProjection::Direct,
5660            }))
5661        })
5662    }
5663}
5664
5665/// Take the value as a geographic layer handle. The layer is copied out of
5666/// the value, so the handle outlives the module the way
5667/// `pio_geo_layer_parse` produces one.
5668///
5669/// # Safety
5670/// Pointers and handles must satisfy the crate-level safety requirements.
5671#[unsafe(no_mangle)]
5672pub unsafe extern "C" fn pio_value_geo_layer(
5673    value: *const PioValueHandle,
5674    error: *mut *mut PioError,
5675) -> *mut PioGeoLayer {
5676    unsafe {
5677        entry(error, std::ptr::null_mut(), || {
5678            let value = require_value(value)?;
5679            let Some(PioValue::GeoLayer(layer)) = value.value() else {
5680                return Err(boundary_error(
5681                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
5682                    "the value is not powerio.GeoLayer",
5683                ));
5684            };
5685            Ok(PioGeoLayer::new_raw(GeoLayerInner {
5686                layer: layer.clone(),
5687                diagnostics: Vec::new(),
5688            }))
5689        })
5690    }
5691}
5692
5693/// Borrow the value as a PSS/E contingency description file without copying.
5694/// The handle holds the module owner alive, so it stays readable after the
5695/// module handle is released. A set taken this way carries no reader notes.
5696///
5697/// # Safety
5698/// Pointers and handles must satisfy the crate-level safety requirements.
5699#[unsafe(no_mangle)]
5700pub unsafe extern "C" fn pio_value_contingency_set(
5701    value: *const PioValueHandle,
5702    error: *mut *mut PioError,
5703) -> *mut PioContingencySet {
5704    unsafe {
5705        entry(error, std::ptr::null_mut(), || {
5706            let value = require_value(value)?;
5707            if !matches!(value.value(), Some(PioValue::ContingencySet(_))) {
5708                return Err(boundary_error(
5709                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
5710                    "the value is not powerio.ContingencySet",
5711                ));
5712            }
5713            Ok(PioContingencySet::new_raw(ContingencySetInner {
5714                source: ContingencySource::Value(ValueInner {
5715                    owner: Arc::clone(&value.owner),
5716                    steps: value.steps.clone(),
5717                }),
5718                diagnostics: Vec::new(),
5719            }))
5720        })
5721    }
5722}
5723
5724/// Borrow the value as a PSS/E subsystem description file without copying. The
5725/// handle holds the module owner alive, so it stays readable after the module
5726/// handle is released.
5727///
5728/// # Safety
5729/// Pointers and handles must satisfy the crate-level safety requirements.
5730#[unsafe(no_mangle)]
5731pub unsafe extern "C" fn pio_value_subsystem_set(
5732    value: *const PioValueHandle,
5733    error: *mut *mut PioError,
5734) -> *mut PioSubsystemSet {
5735    unsafe {
5736        entry(error, std::ptr::null_mut(), || {
5737            let value = require_value(value)?;
5738            if !matches!(value.value(), Some(PioValue::SubsystemSet(_))) {
5739                return Err(boundary_error(
5740                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
5741                    "the value is not powerio.SubsystemSet",
5742                ));
5743            }
5744            Ok(PioSubsystemSet::new_raw(SubsystemSetInner {
5745                source: ContingencySource::Value(ValueInner {
5746                    owner: Arc::clone(&value.owner),
5747                    steps: value.steps.clone(),
5748                }),
5749            }))
5750        })
5751    }
5752}
5753
5754/// Borrow the value as a PSS/E monitored element file without copying. The
5755/// handle holds the module owner alive, so it stays readable after the module
5756/// handle is released.
5757///
5758/// # Safety
5759/// Pointers and handles must satisfy the crate-level safety requirements.
5760#[unsafe(no_mangle)]
5761pub unsafe extern "C" fn pio_value_monitored_set(
5762    value: *const PioValueHandle,
5763    error: *mut *mut PioError,
5764) -> *mut PioMonitoredSet {
5765    unsafe {
5766        entry(error, std::ptr::null_mut(), || {
5767            let value = require_value(value)?;
5768            if !matches!(value.value(), Some(PioValue::MonitoredSet(_))) {
5769                return Err(boundary_error(
5770                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
5771                    "the value is not powerio.MonitoredSet",
5772                ));
5773            }
5774            Ok(PioMonitoredSet::new_raw(MonitoredSetInner {
5775                source: ContingencySource::Value(ValueInner {
5776                    owner: Arc::clone(&value.owner),
5777                    steps: value.steps.clone(),
5778                }),
5779            }))
5780        })
5781    }
5782}
5783
5784/// Borrow the value as a multiconductor network without serialization or copying.
5785///
5786/// # Safety
5787/// Pointers and handles must satisfy the crate-level safety requirements.
5788#[unsafe(no_mangle)]
5789pub unsafe extern "C" fn pio_value_multiconductor_network(
5790    value: *const PioValueHandle,
5791    error: *mut *mut PioError,
5792) -> *mut PioMulticonductorNetwork {
5793    unsafe {
5794        entry(error, std::ptr::null_mut(), || {
5795            let value = require_value(value)?;
5796            if !matches!(value.value(), Some(PioValue::MulticonductorNetwork(_))) {
5797                return Err(boundary_error(
5798                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
5799                    "the value is not powerio.MulticonductorNetwork",
5800                ));
5801            }
5802            Ok(PioMulticonductorNetwork::new_raw(
5803                MulticonductorNetworkInner {
5804                    value: ValueInner {
5805                        owner: Arc::clone(&value.owner),
5806                        steps: value.steps.clone(),
5807                    },
5808                    projection: MulticonductorNetworkProjection::Direct,
5809                },
5810            ))
5811        })
5812    }
5813}
5814
5815/// Borrow the value as a time series.
5816///
5817/// # Safety
5818/// Pointers and handles must satisfy the crate-level safety requirements.
5819#[unsafe(no_mangle)]
5820pub unsafe extern "C" fn pio_value_time_series(
5821    value: *const PioValueHandle,
5822    error: *mut *mut PioError,
5823) -> *mut PioTimeSeriesHandle {
5824    unsafe {
5825        entry(error, std::ptr::null_mut(), || {
5826            let value = require_value(value)?;
5827            if !matches!(value.value(), Some(PioValue::TimeSeries(_))) {
5828                return Err(boundary_error(
5829                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
5830                    "the value is not powerio.TimeSeries<T>",
5831                ));
5832            }
5833            Ok(PioTimeSeriesHandle::new_raw(ValueInner {
5834                owner: Arc::clone(&value.owner),
5835                steps: value.steps.clone(),
5836            }))
5837        })
5838    }
5839}
5840
5841/// Borrow the value as a scenario set.
5842///
5843/// # Safety
5844/// Pointers and handles must satisfy the crate-level safety requirements.
5845#[unsafe(no_mangle)]
5846pub unsafe extern "C" fn pio_value_scenario_set(
5847    value: *const PioValueHandle,
5848    error: *mut *mut PioError,
5849) -> *mut PioScenarioSetHandle {
5850    unsafe {
5851        entry(error, std::ptr::null_mut(), || {
5852            let value = require_value(value)?;
5853            if !matches!(value.value(), Some(PioValue::ScenarioSet(_))) {
5854                return Err(boundary_error(
5855                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
5856                    "the value is not powerio.ScenarioSet<T>",
5857                ));
5858            }
5859            Ok(PioScenarioSetHandle::new_raw(ValueInner {
5860                owner: Arc::clone(&value.owner),
5861                steps: value.steps.clone(),
5862            }))
5863        })
5864    }
5865}
5866
5867#[derive(Clone, Copy)]
5868enum ExpectedValue {
5869    BalancedOperatingPoint,
5870    MulticonductorOperatingPoint,
5871    DcPfInstance,
5872    AcPfInstance,
5873    DcOpfInstance,
5874    AcOpfInstance,
5875    McAcPfInstance,
5876    McAcOpfInstance,
5877    LinDist3FlowOpfInstance,
5878    AcScucInstance,
5879    DcPfSolution,
5880    AcPfSolution,
5881    DcOpfSolution,
5882    AcOpfSolution,
5883    SocwrOpfSolution,
5884    McAcPfSolution,
5885    McAcOpfSolution,
5886    LinDist3FlowOpfSolution,
5887    AcScucSolution,
5888}
5889
5890impl ExpectedValue {
5891    fn matches(self, value: &PioValue) -> bool {
5892        matches!(
5893            (self, value),
5894            (
5895                Self::BalancedOperatingPoint,
5896                PioValue::BalancedOperatingPoint(_)
5897            ) | (
5898                Self::MulticonductorOperatingPoint,
5899                PioValue::MulticonductorOperatingPoint(_)
5900            ) | (Self::DcPfInstance, PioValue::DcPfInstance(_))
5901                | (Self::AcPfInstance, PioValue::AcPfInstance(_))
5902                | (Self::DcOpfInstance, PioValue::DcOpfInstance(_))
5903                | (Self::AcOpfInstance, PioValue::AcOpfInstance(_))
5904                | (Self::McAcPfInstance, PioValue::McAcPfInstance(_))
5905                | (Self::McAcOpfInstance, PioValue::McAcOpfInstance(_))
5906                | (
5907                    Self::LinDist3FlowOpfInstance,
5908                    PioValue::LinDist3FlowOpfInstance(_)
5909                )
5910                | (Self::AcScucInstance, PioValue::AcScucInstance(_))
5911                | (Self::DcPfSolution, PioValue::DcPfSolution(_))
5912                | (Self::AcPfSolution, PioValue::AcPfSolution(_))
5913                | (Self::DcOpfSolution, PioValue::DcOpfSolution(_))
5914                | (Self::AcOpfSolution, PioValue::AcOpfSolution(_))
5915                | (Self::SocwrOpfSolution, PioValue::SocwrOpfSolution(_))
5916                | (Self::McAcPfSolution, PioValue::McAcPfSolution(_))
5917                | (Self::McAcOpfSolution, PioValue::McAcOpfSolution(_))
5918                | (
5919                    Self::LinDist3FlowOpfSolution,
5920                    PioValue::LinDist3FlowOpfSolution(_)
5921                )
5922                | (Self::AcScucSolution, PioValue::AcScucSolution(_))
5923        )
5924    }
5925
5926    fn type_name(self) -> &'static str {
5927        match self {
5928            Self::BalancedOperatingPoint => "powerio.OperatingPoint<powerio.BalancedNetwork>",
5929            Self::MulticonductorOperatingPoint => {
5930                "powerio.OperatingPoint<powerio.MulticonductorNetwork>"
5931            }
5932            Self::DcPfInstance => "powerio.DcPfInstance",
5933            Self::AcPfInstance => "powerio.AcPfInstance",
5934            Self::DcOpfInstance => "powerio.DcOpfInstance",
5935            Self::AcOpfInstance => "powerio.AcOpfInstance",
5936            Self::McAcPfInstance => "powerio.McAcPfInstance",
5937            Self::McAcOpfInstance => "powerio.McAcOpfInstance",
5938            Self::LinDist3FlowOpfInstance => "powerio.LinDist3FlowOpfInstance",
5939            Self::AcScucInstance => "powerio.AcScucInstance",
5940            Self::DcPfSolution => "powerio.DcPfSolution",
5941            Self::AcPfSolution => "powerio.AcPfSolution",
5942            Self::DcOpfSolution => "powerio.DcOpfSolution",
5943            Self::AcOpfSolution => "powerio.AcOpfSolution",
5944            Self::SocwrOpfSolution => "powerio.SocwrOpfSolution",
5945            Self::McAcPfSolution => "powerio.McAcPfSolution",
5946            Self::McAcOpfSolution => "powerio.McAcOpfSolution",
5947            Self::LinDist3FlowOpfSolution => "powerio.LinDist3FlowOpfSolution",
5948            Self::AcScucSolution => "powerio.AcScucSolution",
5949        }
5950    }
5951}
5952
5953unsafe fn checked_typed_value(
5954    value: *const PioValueHandle,
5955    expected: ExpectedValue,
5956) -> Result<ValueInner, *mut PioError> {
5957    let value = unsafe { require_value(value) }?;
5958    if !value.value().is_some_and(|value| expected.matches(value)) {
5959        return Err(boundary_error(
5960            &codes::REQUEST_CAPI_TYPE_MISMATCH,
5961            format!("the value is not {}", expected.type_name()),
5962        ));
5963    }
5964    Ok(ValueInner {
5965        owner: Arc::clone(&value.owner),
5966        steps: value.steps.clone(),
5967    })
5968}
5969
5970unsafe fn operating_point_accessor(
5971    value: *const PioValueHandle,
5972    expected: ExpectedValue,
5973    error: *mut *mut PioError,
5974) -> *mut PioOperatingPoint {
5975    unsafe {
5976        entry(error, std::ptr::null_mut(), || {
5977            let value = checked_typed_value(value, expected)?;
5978            let projection = match expected {
5979                ExpectedValue::BalancedOperatingPoint => OperatingPointProjection::DirectBalanced,
5980                ExpectedValue::MulticonductorOperatingPoint => {
5981                    OperatingPointProjection::DirectMulticonductor
5982                }
5983                _ => {
5984                    return Err(boundary_error(
5985                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
5986                        "the requested value is not an operating point",
5987                    ));
5988                }
5989            };
5990            Ok(PioOperatingPoint::new_raw(OperatingPointInner {
5991                value,
5992                projection,
5993            }))
5994        })
5995    }
5996}
5997
5998unsafe fn instance_accessor(
5999    value: *const PioValueHandle,
6000    expected: ExpectedValue,
6001    error: *mut *mut PioError,
6002) -> *mut PioCalculationInstance {
6003    unsafe {
6004        entry(error, std::ptr::null_mut(), || {
6005            checked_typed_value(value, expected).map(|value| {
6006                PioCalculationInstance::new_raw(CalculationInstanceInner {
6007                    value,
6008                    projection: CalculationInstanceProjection::Direct,
6009                })
6010            })
6011        })
6012    }
6013}
6014
6015unsafe fn solution_accessor(
6016    value: *const PioValueHandle,
6017    expected: ExpectedValue,
6018    error: *mut *mut PioError,
6019) -> *mut PioCalculationSolution {
6020    unsafe {
6021        entry(error, std::ptr::null_mut(), || {
6022            checked_typed_value(value, expected).map(PioCalculationSolution::new_raw)
6023        })
6024    }
6025}
6026
6027///
6028/// # Safety
6029/// Pointers and handles must satisfy the crate-level safety requirements.
6030#[unsafe(no_mangle)]
6031pub unsafe extern "C" fn pio_value_balanced_operating_point(
6032    value: *const PioValueHandle,
6033    error: *mut *mut PioError,
6034) -> *mut PioOperatingPoint {
6035    unsafe { operating_point_accessor(value, ExpectedValue::BalancedOperatingPoint, error) }
6036}
6037
6038///
6039/// # Safety
6040/// Pointers and handles must satisfy the crate-level safety requirements.
6041#[unsafe(no_mangle)]
6042pub unsafe extern "C" fn pio_value_multiconductor_operating_point(
6043    value: *const PioValueHandle,
6044    error: *mut *mut PioError,
6045) -> *mut PioOperatingPoint {
6046    unsafe { operating_point_accessor(value, ExpectedValue::MulticonductorOperatingPoint, error) }
6047}
6048
6049///
6050/// # Safety
6051/// Pointers and handles must satisfy the crate-level safety requirements.
6052#[unsafe(no_mangle)]
6053pub unsafe extern "C" fn pio_value_dc_pf_instance(
6054    value: *const PioValueHandle,
6055    error: *mut *mut PioError,
6056) -> *mut PioCalculationInstance {
6057    unsafe { instance_accessor(value, ExpectedValue::DcPfInstance, error) }
6058}
6059
6060///
6061/// # Safety
6062/// Pointers and handles must satisfy the crate-level safety requirements.
6063#[unsafe(no_mangle)]
6064pub unsafe extern "C" fn pio_value_ac_pf_instance(
6065    value: *const PioValueHandle,
6066    error: *mut *mut PioError,
6067) -> *mut PioCalculationInstance {
6068    unsafe { instance_accessor(value, ExpectedValue::AcPfInstance, error) }
6069}
6070
6071///
6072/// # Safety
6073/// Pointers and handles must satisfy the crate-level safety requirements.
6074#[unsafe(no_mangle)]
6075pub unsafe extern "C" fn pio_value_dc_opf_instance(
6076    value: *const PioValueHandle,
6077    error: *mut *mut PioError,
6078) -> *mut PioCalculationInstance {
6079    unsafe { instance_accessor(value, ExpectedValue::DcOpfInstance, error) }
6080}
6081
6082///
6083/// # Safety
6084/// Pointers and handles must satisfy the crate-level safety requirements.
6085#[unsafe(no_mangle)]
6086pub unsafe extern "C" fn pio_value_ac_opf_instance(
6087    value: *const PioValueHandle,
6088    error: *mut *mut PioError,
6089) -> *mut PioCalculationInstance {
6090    unsafe { instance_accessor(value, ExpectedValue::AcOpfInstance, error) }
6091}
6092
6093///
6094/// # Safety
6095/// Pointers and handles must satisfy the crate-level safety requirements.
6096#[unsafe(no_mangle)]
6097pub unsafe extern "C" fn pio_value_mc_ac_pf_instance(
6098    value: *const PioValueHandle,
6099    error: *mut *mut PioError,
6100) -> *mut PioCalculationInstance {
6101    unsafe { instance_accessor(value, ExpectedValue::McAcPfInstance, error) }
6102}
6103
6104///
6105/// # Safety
6106/// Pointers and handles must satisfy the crate-level safety requirements.
6107#[unsafe(no_mangle)]
6108pub unsafe extern "C" fn pio_value_mc_ac_opf_instance(
6109    value: *const PioValueHandle,
6110    error: *mut *mut PioError,
6111) -> *mut PioCalculationInstance {
6112    unsafe { instance_accessor(value, ExpectedValue::McAcOpfInstance, error) }
6113}
6114
6115///
6116/// # Safety
6117/// Pointers and handles must satisfy the crate-level safety requirements.
6118#[unsafe(no_mangle)]
6119pub unsafe extern "C" fn pio_value_lindist3flow_opf_instance(
6120    value: *const PioValueHandle,
6121    error: *mut *mut PioError,
6122) -> *mut PioCalculationInstance {
6123    unsafe { instance_accessor(value, ExpectedValue::LinDist3FlowOpfInstance, error) }
6124}
6125
6126///
6127/// # Safety
6128/// Pointers and handles must satisfy the crate-level safety requirements.
6129#[unsafe(no_mangle)]
6130pub unsafe extern "C" fn pio_value_ac_scuc_instance(
6131    value: *const PioValueHandle,
6132    error: *mut *mut PioError,
6133) -> *mut PioCalculationInstance {
6134    unsafe { instance_accessor(value, ExpectedValue::AcScucInstance, error) }
6135}
6136
6137///
6138/// # Safety
6139/// Pointers and handles must satisfy the crate-level safety requirements.
6140#[unsafe(no_mangle)]
6141pub unsafe extern "C" fn pio_value_dc_pf_solution(
6142    value: *const PioValueHandle,
6143    error: *mut *mut PioError,
6144) -> *mut PioCalculationSolution {
6145    unsafe { solution_accessor(value, ExpectedValue::DcPfSolution, error) }
6146}
6147
6148///
6149/// # Safety
6150/// Pointers and handles must satisfy the crate-level safety requirements.
6151#[unsafe(no_mangle)]
6152pub unsafe extern "C" fn pio_value_ac_pf_solution(
6153    value: *const PioValueHandle,
6154    error: *mut *mut PioError,
6155) -> *mut PioCalculationSolution {
6156    unsafe { solution_accessor(value, ExpectedValue::AcPfSolution, error) }
6157}
6158
6159///
6160/// # Safety
6161/// Pointers and handles must satisfy the crate-level safety requirements.
6162#[unsafe(no_mangle)]
6163pub unsafe extern "C" fn pio_value_dc_opf_solution(
6164    value: *const PioValueHandle,
6165    error: *mut *mut PioError,
6166) -> *mut PioCalculationSolution {
6167    unsafe { solution_accessor(value, ExpectedValue::DcOpfSolution, error) }
6168}
6169
6170///
6171/// # Safety
6172/// Pointers and handles must satisfy the crate-level safety requirements.
6173#[unsafe(no_mangle)]
6174pub unsafe extern "C" fn pio_value_ac_opf_solution(
6175    value: *const PioValueHandle,
6176    error: *mut *mut PioError,
6177) -> *mut PioCalculationSolution {
6178    unsafe { solution_accessor(value, ExpectedValue::AcOpfSolution, error) }
6179}
6180
6181///
6182/// # Safety
6183/// Pointers and handles must satisfy the crate-level safety requirements.
6184#[unsafe(no_mangle)]
6185pub unsafe extern "C" fn pio_value_socwr_opf_solution(
6186    value: *const PioValueHandle,
6187    error: *mut *mut PioError,
6188) -> *mut PioCalculationSolution {
6189    unsafe { solution_accessor(value, ExpectedValue::SocwrOpfSolution, error) }
6190}
6191
6192///
6193/// # Safety
6194/// Pointers and handles must satisfy the crate-level safety requirements.
6195#[unsafe(no_mangle)]
6196pub unsafe extern "C" fn pio_value_mc_ac_pf_solution(
6197    value: *const PioValueHandle,
6198    error: *mut *mut PioError,
6199) -> *mut PioCalculationSolution {
6200    unsafe { solution_accessor(value, ExpectedValue::McAcPfSolution, error) }
6201}
6202
6203///
6204/// # Safety
6205/// Pointers and handles must satisfy the crate-level safety requirements.
6206#[unsafe(no_mangle)]
6207pub unsafe extern "C" fn pio_value_mc_ac_opf_solution(
6208    value: *const PioValueHandle,
6209    error: *mut *mut PioError,
6210) -> *mut PioCalculationSolution {
6211    unsafe { solution_accessor(value, ExpectedValue::McAcOpfSolution, error) }
6212}
6213
6214///
6215/// # Safety
6216/// Pointers and handles must satisfy the crate-level safety requirements.
6217#[unsafe(no_mangle)]
6218pub unsafe extern "C" fn pio_value_lindist3flow_opf_solution(
6219    value: *const PioValueHandle,
6220    error: *mut *mut PioError,
6221) -> *mut PioCalculationSolution {
6222    unsafe { solution_accessor(value, ExpectedValue::LinDist3FlowOpfSolution, error) }
6223}
6224
6225///
6226/// # Safety
6227/// Pointers and handles must satisfy the crate-level safety requirements.
6228#[unsafe(no_mangle)]
6229pub unsafe extern "C" fn pio_value_ac_scuc_solution(
6230    value: *const PioValueHandle,
6231    error: *mut *mut PioError,
6232) -> *mut PioCalculationSolution {
6233    unsafe { solution_accessor(value, ExpectedValue::AcScucSolution, error) }
6234}
6235
6236///
6237/// # Safety
6238/// Pointers and handles must satisfy the crate-level safety requirements.
6239#[unsafe(no_mangle)]
6240pub unsafe extern "C" fn pio_operating_point_type_name(
6241    point: *const PioOperatingPoint,
6242) -> PioStringView {
6243    unsafe { PioOperatingPoint::get(point) }
6244        .and_then(OperatingPointInner::type_name)
6245        .map_or(PioStringView::EMPTY, PioStringView::new)
6246}
6247
6248///
6249/// # Safety
6250/// Pointers and handles must satisfy the crate-level safety requirements.
6251#[unsafe(no_mangle)]
6252pub unsafe extern "C" fn pio_calculation_instance_type_name(
6253    instance: *const PioCalculationInstance,
6254) -> PioStringView {
6255    unsafe { PioCalculationInstance::get(instance) }
6256        .and_then(CalculationInstanceInner::type_name)
6257        .map_or(PioStringView::EMPTY, PioStringView::new)
6258}
6259
6260///
6261/// # Safety
6262/// Pointers and handles must satisfy the crate-level safety requirements.
6263#[unsafe(no_mangle)]
6264pub unsafe extern "C" fn pio_calculation_solution_type_name(
6265    solution: *const PioCalculationSolution,
6266) -> PioStringView {
6267    unsafe { PioCalculationSolution::get(solution) }
6268        .and_then(ValueInner::value)
6269        .map_or(PioStringView::EMPTY, |value| {
6270            PioStringView::new(value.type_name())
6271        })
6272}
6273
6274/// Return an owner-rooted view of the exact calculation instance retained by
6275/// a calculation solution.
6276///
6277/// # Safety
6278/// Pointers and handles must satisfy the crate-level safety requirements.
6279#[unsafe(no_mangle)]
6280pub unsafe extern "C" fn pio_calculation_solution_instance(
6281    solution: *const PioCalculationSolution,
6282    error: *mut *mut PioError,
6283) -> *mut PioCalculationInstance {
6284    unsafe {
6285        entry(error, std::ptr::null_mut(), || {
6286            let solution = PioCalculationSolution::get(solution).ok_or_else(|| {
6287                boundary_error(
6288                    &codes::BIND_CAPI_NULL_HANDLE,
6289                    "PioCalculationSolution must not be NULL",
6290                )
6291            })?;
6292            let projection = match solution.value() {
6293                Some(PioValue::DcPfSolution(_)) => CalculationInstanceProjection::DcPfSolution,
6294                Some(PioValue::AcPfSolution(_)) => CalculationInstanceProjection::AcPfSolution,
6295                Some(PioValue::DcOpfSolution(_)) => CalculationInstanceProjection::DcOpfSolution,
6296                Some(PioValue::AcOpfSolution(_)) => CalculationInstanceProjection::AcOpfSolution,
6297                Some(PioValue::SocwrOpfSolution(_)) => {
6298                    CalculationInstanceProjection::SocwrOpfSolution
6299                }
6300                Some(PioValue::McAcPfSolution(_)) => CalculationInstanceProjection::McAcPfSolution,
6301                Some(PioValue::McAcOpfSolution(_)) => {
6302                    CalculationInstanceProjection::McAcOpfSolution
6303                }
6304                Some(PioValue::LinDist3FlowOpfSolution(_)) => {
6305                    CalculationInstanceProjection::LinDist3FlowOpfSolution
6306                }
6307                Some(PioValue::AcScucSolution(_)) => CalculationInstanceProjection::AcScucSolution,
6308                _ => {
6309                    return Err(boundary_error(
6310                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
6311                        "the handle does not refer to a calculation solution",
6312                    ));
6313                }
6314            };
6315            Ok(PioCalculationInstance::new_raw(CalculationInstanceInner {
6316                value: ValueInner {
6317                    owner: Arc::clone(&solution.owner),
6318                    steps: solution.steps.clone(),
6319                },
6320                projection,
6321            }))
6322        })
6323    }
6324}
6325
6326fn make_balanced_network_view(
6327    value: &ValueInner,
6328    projection: BalancedNetworkProjection,
6329) -> *mut PioBalancedNetwork {
6330    PioBalancedNetwork::new_raw(BalancedNetworkInner {
6331        value: ValueInner {
6332            owner: Arc::clone(&value.owner),
6333            steps: value.steps.clone(),
6334        },
6335        projection,
6336    })
6337}
6338
6339fn make_multiconductor_network_view(
6340    value: &ValueInner,
6341    projection: MulticonductorNetworkProjection,
6342) -> *mut PioMulticonductorNetwork {
6343    PioMulticonductorNetwork::new_raw(MulticonductorNetworkInner {
6344        value: ValueInner {
6345            owner: Arc::clone(&value.owner),
6346            steps: value.steps.clone(),
6347        },
6348        projection,
6349    })
6350}
6351
6352///
6353/// # Safety
6354/// Pointers and handles must satisfy the crate-level safety requirements.
6355#[unsafe(no_mangle)]
6356pub unsafe extern "C" fn pio_operating_point_balanced_network(
6357    point: *const PioOperatingPoint,
6358    error: *mut *mut PioError,
6359) -> *mut PioBalancedNetwork {
6360    unsafe {
6361        entry(error, std::ptr::null_mut(), || {
6362            let point = PioOperatingPoint::get(point).ok_or_else(|| {
6363                boundary_error(
6364                    &codes::BIND_CAPI_NULL_HANDLE,
6365                    "PioOperatingPoint must not be NULL",
6366                )
6367            })?;
6368            let projection = point.balanced_network_projection().ok_or_else(|| {
6369                boundary_error(
6370                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
6371                    "the operating point does not use powerio.BalancedNetwork",
6372                )
6373            })?;
6374            Ok(make_balanced_network_view(&point.value, projection))
6375        })
6376    }
6377}
6378
6379///
6380/// # Safety
6381/// Pointers and handles must satisfy the crate-level safety requirements.
6382#[unsafe(no_mangle)]
6383pub unsafe extern "C" fn pio_operating_point_multiconductor_network(
6384    point: *const PioOperatingPoint,
6385    error: *mut *mut PioError,
6386) -> *mut PioMulticonductorNetwork {
6387    unsafe {
6388        entry(error, std::ptr::null_mut(), || {
6389            let point = PioOperatingPoint::get(point).ok_or_else(|| {
6390                boundary_error(
6391                    &codes::BIND_CAPI_NULL_HANDLE,
6392                    "PioOperatingPoint must not be NULL",
6393                )
6394            })?;
6395            let projection = point.multiconductor_network_projection().ok_or_else(|| {
6396                boundary_error(
6397                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
6398                    "the operating point does not use powerio.MulticonductorNetwork",
6399                )
6400            })?;
6401            Ok(make_multiconductor_network_view(&point.value, projection))
6402        })
6403    }
6404}
6405
6406///
6407/// # Safety
6408/// Pointers and handles must satisfy the crate-level safety requirements.
6409#[unsafe(no_mangle)]
6410pub unsafe extern "C" fn pio_calculation_instance_balanced_network(
6411    instance: *const PioCalculationInstance,
6412    error: *mut *mut PioError,
6413) -> *mut PioBalancedNetwork {
6414    unsafe {
6415        entry(error, std::ptr::null_mut(), || {
6416            let instance = PioCalculationInstance::get(instance).ok_or_else(|| {
6417                boundary_error(
6418                    &codes::BIND_CAPI_NULL_HANDLE,
6419                    "PioCalculationInstance must not be NULL",
6420                )
6421            })?;
6422            let projection = match instance.projection {
6423                CalculationInstanceProjection::Direct => match instance.instance() {
6424                    Some(CalculationInstanceRef::DcPf(_)) => {
6425                        BalancedNetworkProjection::DcPfInstance
6426                    }
6427                    Some(CalculationInstanceRef::AcPf(_)) => {
6428                        BalancedNetworkProjection::AcPfInstance
6429                    }
6430                    Some(CalculationInstanceRef::DcOpf(_)) => {
6431                        BalancedNetworkProjection::DcOpfInstance
6432                    }
6433                    Some(CalculationInstanceRef::AcOpf(_)) => {
6434                        BalancedNetworkProjection::AcOpfInstance
6435                    }
6436                    Some(CalculationInstanceRef::AcScuc(_)) => {
6437                        BalancedNetworkProjection::AcScucInstance
6438                    }
6439                    _ => {
6440                        return Err(boundary_error(
6441                            &codes::REQUEST_CAPI_TYPE_MISMATCH,
6442                            "the calculation instance does not use powerio.BalancedNetwork",
6443                        ));
6444                    }
6445                },
6446                CalculationInstanceProjection::DcPfSolution => {
6447                    BalancedNetworkProjection::DcPfSolution
6448                }
6449                CalculationInstanceProjection::AcPfSolution => {
6450                    BalancedNetworkProjection::AcPfSolution
6451                }
6452                CalculationInstanceProjection::DcOpfSolution => {
6453                    BalancedNetworkProjection::DcOpfSolution
6454                }
6455                CalculationInstanceProjection::AcOpfSolution => {
6456                    BalancedNetworkProjection::AcOpfSolution
6457                }
6458                CalculationInstanceProjection::SocwrOpfSolution => {
6459                    BalancedNetworkProjection::SocwrOpfSolution
6460                }
6461                CalculationInstanceProjection::AcScucSolution => {
6462                    BalancedNetworkProjection::AcScucSolution
6463                }
6464                _ => {
6465                    return Err(boundary_error(
6466                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
6467                        "the calculation instance does not use powerio.BalancedNetwork",
6468                    ));
6469                }
6470            };
6471            Ok(make_balanced_network_view(&instance.value, projection))
6472        })
6473    }
6474}
6475
6476///
6477/// # Safety
6478/// Pointers and handles must satisfy the crate-level safety requirements.
6479#[unsafe(no_mangle)]
6480pub unsafe extern "C" fn pio_calculation_instance_multiconductor_network(
6481    instance: *const PioCalculationInstance,
6482    error: *mut *mut PioError,
6483) -> *mut PioMulticonductorNetwork {
6484    unsafe {
6485        entry(error, std::ptr::null_mut(), || {
6486            let instance = PioCalculationInstance::get(instance).ok_or_else(|| {
6487                boundary_error(
6488                    &codes::BIND_CAPI_NULL_HANDLE,
6489                    "PioCalculationInstance must not be NULL",
6490                )
6491            })?;
6492            let projection = match instance.projection {
6493                CalculationInstanceProjection::Direct => match instance.instance() {
6494                    Some(CalculationInstanceRef::McAcPf(_)) => {
6495                        MulticonductorNetworkProjection::McAcPfInstance
6496                    }
6497                    Some(CalculationInstanceRef::McAcOpf(_)) => {
6498                        MulticonductorNetworkProjection::McAcOpfInstance
6499                    }
6500                    Some(CalculationInstanceRef::LinDist3FlowOpf(_)) => {
6501                        MulticonductorNetworkProjection::LinDist3FlowOpfInstance
6502                    }
6503                    _ => {
6504                        return Err(boundary_error(
6505                            &codes::REQUEST_CAPI_TYPE_MISMATCH,
6506                            "the calculation instance does not use powerio.MulticonductorNetwork",
6507                        ));
6508                    }
6509                },
6510                CalculationInstanceProjection::McAcPfSolution => {
6511                    MulticonductorNetworkProjection::McAcPfSolution
6512                }
6513                CalculationInstanceProjection::McAcOpfSolution => {
6514                    MulticonductorNetworkProjection::McAcOpfSolution
6515                }
6516                CalculationInstanceProjection::LinDist3FlowOpfSolution => {
6517                    MulticonductorNetworkProjection::LinDist3FlowOpfSolution
6518                }
6519                _ => {
6520                    return Err(boundary_error(
6521                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
6522                        "the calculation instance does not use powerio.MulticonductorNetwork",
6523                    ));
6524                }
6525            };
6526            Ok(make_multiconductor_network_view(
6527                &instance.value,
6528                projection,
6529            ))
6530        })
6531    }
6532}
6533
6534fn branch_susceptance_formula_name(formula: BranchSusceptanceFormula) -> &'static str {
6535    match formula {
6536        BranchSusceptanceFormula::ReactanceOnly => "reactance_only",
6537        BranchSusceptanceFormula::TapAdjustedReactance => "tap_adjusted_reactance",
6538        BranchSusceptanceFormula::SeriesSusceptance => "series_susceptance",
6539        _ => "unknown",
6540    }
6541}
6542
6543unsafe fn require_calculation_instance<'a>(
6544    instance: *const PioCalculationInstance,
6545) -> Result<&'a CalculationInstanceInner, *mut PioError> {
6546    unsafe { PioCalculationInstance::get(instance) }.ok_or_else(|| {
6547        boundary_error(
6548            &codes::BIND_CAPI_NULL_HANDLE,
6549            "PioCalculationInstance must not be NULL",
6550        )
6551    })
6552}
6553
6554fn opf_preparation_units(name: &str) -> Result<Units, *mut PioError> {
6555    match name {
6556        "per_unit" => Ok(Units::PerUnit),
6557        "native" => Ok(Units::Native),
6558        _ => Err(boundary_error(
6559            &codes::BIND_CAPI_INVALID_OPTIONS,
6560            format!("unknown OPF preparation units '{name}'; expected 'per_unit' or 'native'"),
6561        )),
6562    }
6563}
6564
6565fn opf_preparation_units_name(units: Units) -> Option<&'static str> {
6566    match units {
6567        Units::PerUnit => Some("per_unit"),
6568        Units::Native => Some("native"),
6569        _ => None,
6570    }
6571}
6572
6573fn prepared_objective_name(objective: PreparedObjective) -> Option<&'static str> {
6574    match objective {
6575        PreparedObjective::Feasibility => Some("feasibility"),
6576        PreparedObjective::NetworkGeneratorCost => Some("network_generator_cost"),
6577        _ => None,
6578    }
6579}
6580
6581fn opf_analysis_branch_source(
6582    source: AnalysisBranchSource,
6583    preparation: &str,
6584) -> Result<(&'static str, usize, Option<usize>), *mut PioError> {
6585    match source {
6586        AnalysisBranchSource::Branch { row } => Ok(("branch", row, None)),
6587        AnalysisBranchSource::ThreeWindingTransformerWinding {
6588            transformer_row,
6589            winding,
6590        } => Ok((
6591            "three_winding_transformer_winding",
6592            transformer_row,
6593            Some(winding),
6594        )),
6595        _ => Err(boundary_error(
6596            &codes::REQUEST_CAPI_TYPE_MISMATCH,
6597            format!(
6598                "the {preparation} OPF preparation has an analysis branch source unsupported by ABI 7"
6599            ),
6600        )),
6601    }
6602}
6603
6604/// Build the matrix free DC OPF inputs from one typed instance.
6605///
6606/// # Safety
6607/// Pointers and handles must satisfy the crate-level safety requirements.
6608#[unsafe(no_mangle)]
6609pub unsafe extern "C" fn pio_build_dc_opf_preparation(
6610    instance: *const PioCalculationInstance,
6611    units: *const c_char,
6612    units_len: usize,
6613    skip_zero_impedance: bool,
6614    synthesize_unrated_limits: bool,
6615    correct_angle_difference_bounds: bool,
6616    error: *mut *mut PioError,
6617) -> *mut PioDcOpfPreparation {
6618    unsafe {
6619        entry(error, std::ptr::null_mut(), || {
6620            let instance = require_calculation_instance(instance)?
6621                .dc_opf()
6622                .ok_or_else(|| {
6623                    boundary_error(
6624                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
6625                        "DC OPF preparation requires powerio.DcOpfInstance",
6626                    )
6627                })?;
6628            let units = opf_preparation_units(required_str(units, units_len, "units")?)?;
6629            let options = DcOpfAssemblyOptions::default()
6630                .with_units(units)
6631                .with_skip_zero_impedance(skip_zero_impedance)
6632                .with_synthesize_unrated_limits(synthesize_unrated_limits)
6633                .with_correct_angle_difference_bounds(correct_angle_difference_bounds);
6634            build_dc_opf_preparation(instance, &options)
6635                .map(PioDcOpfPreparation::new_raw)
6636                .map_err(|failure| error_from_matrix(&failure))
6637        })
6638    }
6639}
6640
6641/// Read the dimensions and conventions of a DC OPF preparation.
6642///
6643/// # Safety
6644/// Pointers and handles must satisfy the crate-level safety requirements.
6645#[unsafe(no_mangle)]
6646pub unsafe extern "C" fn pio_dc_opf_preparation_summary(
6647    preparation: *const PioDcOpfPreparation,
6648    output: *mut PioDcOpfPreparationView,
6649    error: *mut *mut PioError,
6650) -> bool {
6651    unsafe {
6652        entry(error, false, || {
6653            let preparation = PioDcOpfPreparation::get(preparation).ok_or_else(|| {
6654                boundary_error(
6655                    &codes::BIND_CAPI_NULL_HANDLE,
6656                    "PioDcOpfPreparation must not be NULL",
6657                )
6658            })?;
6659            let units = opf_preparation_units_name(preparation.units).ok_or_else(|| {
6660                boundary_error(
6661                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
6662                    "the DC OPF preparation uses units unsupported by ABI 7",
6663                )
6664            })?;
6665            let objective = prepared_objective_name(preparation.objective).ok_or_else(|| {
6666                boundary_error(
6667                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
6668                    "the DC OPF preparation uses an objective unsupported by ABI 7",
6669                )
6670            })?;
6671            *require_output(output, "output")? = PioDcOpfPreparationView {
6672                name: PioStringView::new(&preparation.name),
6673                bus_count: preparation.n_buses,
6674                generator_count: preparation.n_generators(),
6675                branch_count: preparation.n_branches(),
6676                source_generator_count: preparation.n_source_generators,
6677                source_branch_count: preparation.n_source_branches,
6678                base_mva: preparation.base_mva,
6679                units: PioStringView::new(units),
6680                branch_susceptance_formula: PioStringView::new(branch_susceptance_formula_name(
6681                    preparation.formula,
6682                )),
6683                objective: PioStringView::new(objective),
6684                skip_zero_impedance: preparation.skip_zero_impedance,
6685                synthesize_unrated_limits: preparation.synthesize_unrated_limits,
6686                correct_angle_difference_bounds: preparation.correct_angle_difference_bounds,
6687                reference_bus_count: preparation.reference_buses.len(),
6688                skipped_zero_impedance_count: preparation.branches.skipped_zero_impedance.len(),
6689            };
6690            Ok(true)
6691        })
6692    }
6693}
6694
6695/// Borrow the dense reference bus indices of a DC OPF preparation.
6696///
6697/// # Safety
6698/// Pointers and handles must satisfy the crate-level safety requirements.
6699#[unsafe(no_mangle)]
6700pub unsafe extern "C" fn pio_dc_opf_preparation_reference_buses(
6701    preparation: *const PioDcOpfPreparation,
6702) -> PioSizeView {
6703    unsafe { PioDcOpfPreparation::get(preparation) }.map_or(PioSizeView::EMPTY, |value| {
6704        PioSizeView::new(value.reference_buses.as_ref())
6705    })
6706}
6707
6708/// Borrow the analysis rows skipped for zero impedance.
6709///
6710/// # Safety
6711/// Pointers and handles must satisfy the crate-level safety requirements.
6712#[unsafe(no_mangle)]
6713pub unsafe extern "C" fn pio_dc_opf_preparation_skipped_zero_impedance(
6714    preparation: *const PioDcOpfPreparation,
6715) -> PioSizeView {
6716    unsafe { PioDcOpfPreparation::get(preparation) }.map_or(PioSizeView::EMPTY, |value| {
6717        PioSizeView::new(&value.branches.skipped_zero_impedance)
6718    })
6719}
6720
6721/// Read one dense bus row of a DC OPF preparation.
6722///
6723/// # Safety
6724/// Pointers and handles must satisfy the crate-level safety requirements.
6725#[unsafe(no_mangle)]
6726pub unsafe extern "C" fn pio_dc_opf_preparation_bus_at(
6727    preparation: *const PioDcOpfPreparation,
6728    index: usize,
6729    output: *mut PioDcOpfBusView,
6730    error: *mut *mut PioError,
6731) -> bool {
6732    unsafe {
6733        entry(error, false, || {
6734            let value = PioDcOpfPreparation::get(preparation).ok_or_else(|| {
6735                boundary_error(
6736                    &codes::BIND_CAPI_NULL_HANDLE,
6737                    "PioDcOpfPreparation must not be NULL",
6738                )
6739            })?;
6740            if index >= value.n_buses {
6741                return Err(boundary_error(
6742                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6743                    format!("DC OPF preparation bus index {index} is out of range"),
6744                ));
6745            }
6746            let source_row = value.bus_source_rows[index];
6747            *require_output(output, "output")? = PioDcOpfBusView {
6748                bus_id: value.bus_ids[index].0,
6749                analysis_row: value.bus_analysis_rows[index],
6750                source_row: source_row.unwrap_or(0),
6751                has_source_row: source_row.is_some(),
6752                active_power_demand: value.p_d[index],
6753                shunt_conductance: value.g_s[index],
6754                phase_shift_injection: value.p_shift[index],
6755            };
6756            Ok(true)
6757        })
6758    }
6759}
6760
6761/// Read one generator row of a DC OPF preparation.
6762///
6763/// # Safety
6764/// Pointers and handles must satisfy the crate-level safety requirements.
6765#[unsafe(no_mangle)]
6766pub unsafe extern "C" fn pio_dc_opf_preparation_generator_at(
6767    preparation: *const PioDcOpfPreparation,
6768    index: usize,
6769    output: *mut PioDcOpfGeneratorView,
6770    error: *mut *mut PioError,
6771) -> bool {
6772    unsafe {
6773        entry(error, false, || {
6774            let value = PioDcOpfPreparation::get(preparation).ok_or_else(|| {
6775                boundary_error(
6776                    &codes::BIND_CAPI_NULL_HANDLE,
6777                    "PioDcOpfPreparation must not be NULL",
6778                )
6779            })?;
6780            if index >= value.n_generators() {
6781                return Err(boundary_error(
6782                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6783                    format!("DC OPF preparation generator index {index} is out of range"),
6784                ));
6785            }
6786            let source_row = value.generators.source_rows[index];
6787            let piecewise = value.generators.piecewise_linear[index].as_ref();
6788            *require_output(output, "output")? = PioDcOpfGeneratorView {
6789                component_id: PioStringView::new(&value.generators.identities[index]),
6790                bus_index: value.generators.bus_of_gen[index],
6791                analysis_row: value.generators.analysis_rows[index],
6792                source_row: source_row.unwrap_or(0),
6793                has_source_row: source_row.is_some(),
6794                quadratic_cost: value.generators.q[index],
6795                linear_cost: value.generators.c[index],
6796                constant_cost: value.generators.c0[index],
6797                has_piecewise_linear_cost: piecewise.is_some(),
6798                piecewise_linear_power: piecewise
6799                    .map_or(PioF64View::EMPTY, |cost| PioF64View::new(&cost.power)),
6800                piecewise_linear_value: piecewise
6801                    .map_or(PioF64View::EMPTY, |cost| PioF64View::new(&cost.value)),
6802                active_power_max: value.generators.pmax[index],
6803                active_power_min: value.generators.pmin[index],
6804                capability_active: value.generators.capability_active[index],
6805            };
6806            Ok(true)
6807        })
6808    }
6809}
6810
6811/// Read one active branch row of a DC OPF preparation.
6812///
6813/// # Safety
6814/// Pointers and handles must satisfy the crate-level safety requirements.
6815#[unsafe(no_mangle)]
6816pub unsafe extern "C" fn pio_dc_opf_preparation_branch_at(
6817    preparation: *const PioDcOpfPreparation,
6818    index: usize,
6819    output: *mut PioDcOpfBranchView,
6820    error: *mut *mut PioError,
6821) -> bool {
6822    unsafe {
6823        entry(error, false, || {
6824            let value = PioDcOpfPreparation::get(preparation).ok_or_else(|| {
6825                boundary_error(
6826                    &codes::BIND_CAPI_NULL_HANDLE,
6827                    "PioDcOpfPreparation must not be NULL",
6828                )
6829            })?;
6830            if index >= value.n_branches() {
6831                return Err(boundary_error(
6832                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6833                    format!("DC OPF preparation branch index {index} is out of range"),
6834                ));
6835            }
6836            let (source_kind, source_row, winding) =
6837                opf_analysis_branch_source(value.branches.analysis_sources[index], "DC")?;
6838            *require_output(output, "output")? = PioDcOpfBranchView {
6839                component_id: PioStringView::new(&value.branches.identities[index]),
6840                from_bus_index: value.branches.from_bus[index],
6841                to_bus_index: value.branches.to_bus[index],
6842                susceptance_magnitude: value.branches.susceptance_magnitude[index],
6843                phase_shift_radians: value.branches.shift[index],
6844                active_power_max: value.branches.f_max[index],
6845                angle_difference_min_radians: value.branches.angle_min[index],
6846                angle_difference_max_radians: value.branches.angle_max[index],
6847                analysis_row: value.branches.analysis_rows[index],
6848                source_kind: PioStringView::new(source_kind),
6849                source_row,
6850                winding: winding.unwrap_or(0),
6851                has_winding: winding.is_some(),
6852                thermal_limit_active: value.branches.thermal_limit_active[index],
6853                angle_bound_active: value.branches.angle_bound_active[index],
6854            };
6855            Ok(true)
6856        })
6857    }
6858}
6859
6860/// Build the matrix free AC OPF inputs from one typed instance.
6861///
6862/// # Safety
6863/// Pointers and handles must satisfy the crate-level safety requirements.
6864#[unsafe(no_mangle)]
6865pub unsafe extern "C" fn pio_build_ac_opf_preparation(
6866    instance: *const PioCalculationInstance,
6867    units: *const c_char,
6868    units_len: usize,
6869    skip_zero_impedance: bool,
6870    synthesize_unrated_limits: bool,
6871    correct_angle_difference_bounds: bool,
6872    error: *mut *mut PioError,
6873) -> *mut PioAcOpfPreparation {
6874    unsafe {
6875        entry(error, std::ptr::null_mut(), || {
6876            let instance = require_calculation_instance(instance)?
6877                .ac_opf()
6878                .ok_or_else(|| {
6879                    boundary_error(
6880                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
6881                        "AC OPF preparation requires powerio.AcOpfInstance",
6882                    )
6883                })?;
6884            let units = opf_preparation_units(required_str(units, units_len, "units")?)?;
6885            let options = AcOpfAssemblyOptions::default()
6886                .with_units(units)
6887                .with_skip_zero_impedance(skip_zero_impedance)
6888                .with_synthesize_unrated_limits(synthesize_unrated_limits)
6889                .with_correct_angle_difference_bounds(correct_angle_difference_bounds);
6890            build_ac_opf_preparation(instance, &options)
6891                .map(PioAcOpfPreparation::new_raw)
6892                .map_err(|failure| error_from_matrix(&failure))
6893        })
6894    }
6895}
6896
6897/// Read the dimensions and conventions of an AC OPF preparation.
6898///
6899/// # Safety
6900/// Pointers and handles must satisfy the crate-level safety requirements.
6901#[unsafe(no_mangle)]
6902pub unsafe extern "C" fn pio_ac_opf_preparation_summary(
6903    preparation: *const PioAcOpfPreparation,
6904    output: *mut PioAcOpfPreparationView,
6905    error: *mut *mut PioError,
6906) -> bool {
6907    unsafe {
6908        entry(error, false, || {
6909            let preparation = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
6910                boundary_error(
6911                    &codes::BIND_CAPI_NULL_HANDLE,
6912                    "PioAcOpfPreparation must not be NULL",
6913                )
6914            })?;
6915            let units = opf_preparation_units_name(preparation.units).ok_or_else(|| {
6916                boundary_error(
6917                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
6918                    "the AC OPF preparation uses units unsupported by ABI 7",
6919                )
6920            })?;
6921            let objective = prepared_objective_name(preparation.objective).ok_or_else(|| {
6922                boundary_error(
6923                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
6924                    "the AC OPF preparation uses an objective unsupported by ABI 7",
6925                )
6926            })?;
6927            *require_output(output, "output")? = PioAcOpfPreparationView {
6928                name: PioStringView::new(&preparation.name),
6929                bus_count: preparation.n_buses,
6930                generator_count: preparation.n_generators(),
6931                storage_count: preparation.n_storage(),
6932                branch_count: preparation.n_branches(),
6933                source_generator_count: preparation.n_source_generators,
6934                source_branch_count: preparation.n_source_branches,
6935                base_mva: preparation.base_mva,
6936                units: PioStringView::new(units),
6937                objective: PioStringView::new(objective),
6938                skip_zero_impedance: preparation.skip_zero_impedance,
6939                synthesize_unrated_limits: preparation.synthesize_unrated_limits,
6940                correct_angle_difference_bounds: preparation.correct_angle_difference_bounds,
6941                reference_bus_count: preparation.reference_buses.len(),
6942                skipped_zero_impedance_count: preparation.branches.skipped_zero_impedance.len(),
6943            };
6944            Ok(true)
6945        })
6946    }
6947}
6948
6949/// Borrow the dense reference bus indices of an AC OPF preparation.
6950///
6951/// # Safety
6952/// Pointers and handles must satisfy the crate-level safety requirements.
6953#[unsafe(no_mangle)]
6954pub unsafe extern "C" fn pio_ac_opf_preparation_reference_buses(
6955    preparation: *const PioAcOpfPreparation,
6956) -> PioSizeView {
6957    unsafe { PioAcOpfPreparation::get(preparation) }.map_or(PioSizeView::EMPTY, |value| {
6958        PioSizeView::new(value.reference_buses.as_ref())
6959    })
6960}
6961
6962/// Borrow the analysis rows skipped for zero impedance.
6963///
6964/// # Safety
6965/// Pointers and handles must satisfy the crate-level safety requirements.
6966#[unsafe(no_mangle)]
6967pub unsafe extern "C" fn pio_ac_opf_preparation_skipped_zero_impedance(
6968    preparation: *const PioAcOpfPreparation,
6969) -> PioSizeView {
6970    unsafe { PioAcOpfPreparation::get(preparation) }.map_or(PioSizeView::EMPTY, |value| {
6971        PioSizeView::new(&value.branches.skipped_zero_impedance)
6972    })
6973}
6974
6975/// Read one dense bus row of an AC OPF preparation.
6976///
6977/// # Safety
6978/// Pointers and handles must satisfy the crate-level safety requirements.
6979#[unsafe(no_mangle)]
6980pub unsafe extern "C" fn pio_ac_opf_preparation_bus_at(
6981    preparation: *const PioAcOpfPreparation,
6982    index: usize,
6983    output: *mut PioAcOpfBusView,
6984    error: *mut *mut PioError,
6985) -> bool {
6986    unsafe {
6987        entry(error, false, || {
6988            let value = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
6989                boundary_error(
6990                    &codes::BIND_CAPI_NULL_HANDLE,
6991                    "PioAcOpfPreparation must not be NULL",
6992                )
6993            })?;
6994            if index >= value.n_buses {
6995                return Err(boundary_error(
6996                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6997                    format!("AC OPF preparation bus index {index} is out of range"),
6998                ));
6999            }
7000            let source_row = value.bus_source_rows[index];
7001            *require_output(output, "output")? = PioAcOpfBusView {
7002                bus_id: value.bus_ids[index].0,
7003                analysis_row: value.bus_analysis_rows[index],
7004                source_row: source_row.unwrap_or(0),
7005                has_source_row: source_row.is_some(),
7006                active_power_demand: value.buses.p_d[index],
7007                reactive_power_demand: value.buses.q_d[index],
7008                shunt_conductance: value.buses.g_s[index],
7009                shunt_susceptance: value.buses.b_s[index],
7010                voltage_magnitude_min_pu: value.buses.vm_min[index],
7011                voltage_magnitude_max_pu: value.buses.vm_max[index],
7012                initial_voltage_magnitude_pu: value.buses.initial_vm[index],
7013                initial_voltage_angle_radians: value.buses.initial_va[index],
7014                voltage_bound_active: value.buses.voltage_bound_active[index],
7015            };
7016            Ok(true)
7017        })
7018    }
7019}
7020
7021/// Read one generator row of an AC OPF preparation.
7022///
7023/// # Safety
7024/// Pointers and handles must satisfy the crate-level safety requirements.
7025#[unsafe(no_mangle)]
7026pub unsafe extern "C" fn pio_ac_opf_preparation_generator_at(
7027    preparation: *const PioAcOpfPreparation,
7028    index: usize,
7029    output: *mut PioAcOpfGeneratorView,
7030    error: *mut *mut PioError,
7031) -> bool {
7032    unsafe {
7033        entry(error, false, || {
7034            let value = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
7035                boundary_error(
7036                    &codes::BIND_CAPI_NULL_HANDLE,
7037                    "PioAcOpfPreparation must not be NULL",
7038                )
7039            })?;
7040            if index >= value.n_generators() {
7041                return Err(boundary_error(
7042                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7043                    format!("AC OPF preparation generator index {index} is out of range"),
7044                ));
7045            }
7046            let source_row = value.generators.source_rows[index];
7047            let piecewise = value.generators.piecewise_linear[index].as_ref();
7048            *require_output(output, "output")? = PioAcOpfGeneratorView {
7049                component_id: PioStringView::new(&value.generators.identities[index]),
7050                bus_index: value.generators.bus_of_gen[index],
7051                analysis_row: value.generators.analysis_rows[index],
7052                source_row: source_row.unwrap_or(0),
7053                has_source_row: source_row.is_some(),
7054                quadratic_cost: value.generators.q[index],
7055                linear_cost: value.generators.c[index],
7056                constant_cost: value.generators.c0[index],
7057                has_piecewise_linear_cost: piecewise.is_some(),
7058                piecewise_linear_power: piecewise
7059                    .map_or(PioF64View::EMPTY, |cost| PioF64View::new(&cost.power)),
7060                piecewise_linear_value: piecewise
7061                    .map_or(PioF64View::EMPTY, |cost| PioF64View::new(&cost.value)),
7062                active_power_max: value.generators.pmax[index],
7063                active_power_min: value.generators.pmin[index],
7064                reactive_power_max: value.generators.qmax[index],
7065                reactive_power_min: value.generators.qmin[index],
7066                initial_active_power: value.generators.pg[index],
7067                initial_reactive_power: value.generators.qg[index],
7068                voltage_magnitude_setpoint_pu: value.generators.vg[index],
7069                capability_active: value.generators.capability_active[index],
7070            };
7071            Ok(true)
7072        })
7073    }
7074}
7075
7076/// Read one storage row of an AC OPF preparation.
7077///
7078/// # Safety
7079/// Pointers and handles must satisfy the crate-level safety requirements.
7080#[unsafe(no_mangle)]
7081pub unsafe extern "C" fn pio_ac_opf_preparation_storage_at(
7082    preparation: *const PioAcOpfPreparation,
7083    index: usize,
7084    output: *mut PioAcOpfStorageView,
7085    error: *mut *mut PioError,
7086) -> bool {
7087    unsafe {
7088        entry(error, false, || {
7089            let value = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
7090                boundary_error(
7091                    &codes::BIND_CAPI_NULL_HANDLE,
7092                    "PioAcOpfPreparation must not be NULL",
7093                )
7094            })?;
7095            if index >= value.n_storage() {
7096                return Err(boundary_error(
7097                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7098                    format!("AC OPF preparation storage index {index} is out of range"),
7099                ));
7100            }
7101            let storage = &value.storage;
7102            *require_output(output, "output")? = PioAcOpfStorageView {
7103                component_id: PioStringView::new(&storage.identities[index]),
7104                bus_index: storage.bus_of_storage[index],
7105                source_row: storage.source_rows[index],
7106                initial_active_power: storage.p[index],
7107                initial_reactive_power: storage.q[index],
7108                energy: storage.energy[index],
7109                energy_rating: storage.energy_rating[index],
7110                charge_rating: storage.charge_rating[index],
7111                discharge_rating: storage.discharge_rating[index],
7112                charge_efficiency: storage.charge_efficiency[index],
7113                discharge_efficiency: storage.discharge_efficiency[index],
7114                apparent_power_max: storage.s_max[index],
7115                reactive_power_min: storage.qmin[index],
7116                reactive_power_max: storage.qmax[index],
7117                resistance_pu: storage.r[index],
7118                reactance_pu: storage.x[index],
7119                active_power_loss: storage.p_loss[index],
7120                reactive_power_loss: storage.q_loss[index],
7121                in_service: storage.in_service[index],
7122            };
7123            Ok(true)
7124        })
7125    }
7126}
7127
7128/// Read one active branch row of an AC OPF preparation.
7129///
7130/// # Safety
7131/// Pointers and handles must satisfy the crate-level safety requirements.
7132#[unsafe(no_mangle)]
7133pub unsafe extern "C" fn pio_ac_opf_preparation_branch_at(
7134    preparation: *const PioAcOpfPreparation,
7135    index: usize,
7136    output: *mut PioAcOpfBranchView,
7137    error: *mut *mut PioError,
7138) -> bool {
7139    unsafe {
7140        entry(error, false, || {
7141            let value = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
7142                boundary_error(
7143                    &codes::BIND_CAPI_NULL_HANDLE,
7144                    "PioAcOpfPreparation must not be NULL",
7145                )
7146            })?;
7147            if index >= value.n_branches() {
7148                return Err(boundary_error(
7149                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7150                    format!("AC OPF preparation branch index {index} is out of range"),
7151                ));
7152            }
7153            let (source_kind, source_row, winding) =
7154                opf_analysis_branch_source(value.branches.analysis_sources[index], "AC")?;
7155            *require_output(output, "output")? = PioAcOpfBranchView {
7156                component_id: PioStringView::new(&value.branches.identities[index]),
7157                from_bus_index: value.branches.from_bus[index],
7158                to_bus_index: value.branches.to_bus[index],
7159                series_conductance: value.branches.g[index],
7160                series_susceptance: value.branches.b[index],
7161                from_conductance: value.branches.g_fr[index],
7162                from_susceptance: value.branches.b_fr[index],
7163                to_conductance: value.branches.g_to[index],
7164                to_susceptance: value.branches.b_to[index],
7165                tap_ratio: value.branches.tap[index],
7166                phase_shift_radians: value.branches.shift[index],
7167                apparent_power_max: value.branches.s_max[index],
7168                angle_difference_min_radians: value.branches.angle_min[index],
7169                angle_difference_max_radians: value.branches.angle_max[index],
7170                analysis_row: value.branches.analysis_rows[index],
7171                source_kind: PioStringView::new(source_kind),
7172                source_row,
7173                winding: winding.unwrap_or(0),
7174                has_winding: winding.is_some(),
7175                thermal_limit_active: value.branches.thermal_limit_active[index],
7176                angle_bound_active: value.branches.angle_bound_active[index],
7177            };
7178            Ok(true)
7179        })
7180    }
7181}
7182
7183///
7184/// # Safety
7185/// Pointers and handles must satisfy the crate-level safety requirements.
7186#[unsafe(no_mangle)]
7187pub unsafe extern "C" fn pio_dc_pf_instance_bus_specification_count(
7188    instance: *const PioCalculationInstance,
7189) -> usize {
7190    unsafe { PioCalculationInstance::get(instance) }
7191        .and_then(CalculationInstanceInner::dc_pf)
7192        .map_or(0, |instance| instance.specifications().len())
7193}
7194
7195/// Read one DC power flow bus specification by zero based bus table position.
7196///
7197/// # Safety
7198/// Pointers and handles must satisfy the crate-level safety requirements.
7199#[unsafe(no_mangle)]
7200pub unsafe extern "C" fn pio_dc_pf_instance_bus_specification_at(
7201    instance: *const PioCalculationInstance,
7202    index: usize,
7203    output: *mut PioDcBusSpecificationView,
7204    error: *mut *mut PioError,
7205) -> bool {
7206    unsafe {
7207        entry(error, false, || {
7208            let instance = require_calculation_instance(instance)?
7209                .dc_pf()
7210                .ok_or_else(|| {
7211                    boundary_error(
7212                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
7213                        "the calculation instance is not powerio.DcPfInstance",
7214                    )
7215                })?;
7216            let specification = instance.specifications().get(index).ok_or_else(|| {
7217                boundary_error(
7218                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7219                    format!("DC bus specification index {index} is out of range"),
7220                )
7221            })?;
7222            let bus_id = instance
7223                .network()
7224                .buses()
7225                .get(index)
7226                .ok_or_else(|| {
7227                    boundary_error(
7228                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7229                        format!("bus specification index {index} has no bus"),
7230                    )
7231                })?
7232                .id
7233                .0;
7234            let (kind, net_active_power_mw, voltage_angle_degrees) = match *specification {
7235                powerio_prob::DcBusSpecification::NetActivePower { p_mw } => {
7236                    ("net_active_power", p_mw, 0.0)
7237                }
7238                powerio_prob::DcBusSpecification::Reference { va_degrees } => {
7239                    ("reference", 0.0, va_degrees)
7240                }
7241                powerio_prob::DcBusSpecification::Isolated => ("isolated", 0.0, 0.0),
7242                _ => ("unknown", 0.0, 0.0),
7243            };
7244            *require_output(output, "output")? = PioDcBusSpecificationView {
7245                bus_id,
7246                kind: PioStringView::new(kind),
7247                net_active_power_mw,
7248                voltage_angle_degrees,
7249            };
7250            Ok(true)
7251        })
7252    }
7253}
7254
7255///
7256/// # Safety
7257/// Pointers and handles must satisfy the crate-level safety requirements.
7258#[unsafe(no_mangle)]
7259pub unsafe extern "C" fn pio_dc_pf_instance_branch_susceptance_formula(
7260    instance: *const PioCalculationInstance,
7261) -> PioStringView {
7262    unsafe { PioCalculationInstance::get(instance) }
7263        .and_then(CalculationInstanceInner::dc_pf)
7264        .map_or(PioStringView::EMPTY, |instance| {
7265            PioStringView::new(branch_susceptance_formula_name(
7266                instance.branch_susceptance_formula(),
7267            ))
7268        })
7269}
7270
7271///
7272/// # Safety
7273/// Pointers and handles must satisfy the crate-level safety requirements.
7274#[unsafe(no_mangle)]
7275pub unsafe extern "C" fn pio_ac_pf_instance_bus_specification_count(
7276    instance: *const PioCalculationInstance,
7277) -> usize {
7278    unsafe { PioCalculationInstance::get(instance) }
7279        .and_then(CalculationInstanceInner::ac_pf)
7280        .map_or(0, |instance| instance.specifications().len())
7281}
7282
7283/// Read one AC power flow bus specification by zero based bus table position.
7284///
7285/// # Safety
7286/// Pointers and handles must satisfy the crate-level safety requirements.
7287#[unsafe(no_mangle)]
7288pub unsafe extern "C" fn pio_ac_pf_instance_bus_specification_at(
7289    instance: *const PioCalculationInstance,
7290    index: usize,
7291    output: *mut PioAcBusSpecificationView,
7292    error: *mut *mut PioError,
7293) -> bool {
7294    unsafe {
7295        entry(error, false, || {
7296            let instance = require_calculation_instance(instance)?
7297                .ac_pf()
7298                .ok_or_else(|| {
7299                    boundary_error(
7300                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
7301                        "the calculation instance is not powerio.AcPfInstance",
7302                    )
7303                })?;
7304            let specification = instance.specifications().get(index).ok_or_else(|| {
7305                boundary_error(
7306                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7307                    format!("AC bus specification index {index} is out of range"),
7308                )
7309            })?;
7310            let bus_id = instance
7311                .network()
7312                .buses()
7313                .get(index)
7314                .ok_or_else(|| {
7315                    boundary_error(
7316                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7317                        format!("bus specification index {index} has no bus"),
7318                    )
7319                })?
7320                .id
7321                .0;
7322            let (kind, p, q, vm, va) = match *specification {
7323                powerio_prob::AcBusSpecification::Pq { p, q } => ("pq", p, q, 0.0, 0.0),
7324                powerio_prob::AcBusSpecification::Pv { p, vm } => ("pv", p, 0.0, vm, 0.0),
7325                powerio_prob::AcBusSpecification::Reference { vm, va } => {
7326                    ("reference", 0.0, 0.0, vm, va)
7327                }
7328                powerio_prob::AcBusSpecification::Isolated => ("isolated", 0.0, 0.0, 0.0, 0.0),
7329                _ => ("unknown", 0.0, 0.0, 0.0, 0.0),
7330            };
7331            *require_output(output, "output")? = PioAcBusSpecificationView {
7332                bus_id,
7333                kind: PioStringView::new(kind),
7334                net_active_power_mw: p,
7335                net_reactive_power_mvar: q,
7336                voltage_magnitude_pu: vm,
7337                voltage_angle_degrees: va,
7338            };
7339            Ok(true)
7340        })
7341    }
7342}
7343
7344///
7345/// # Safety
7346/// Pointers and handles must satisfy the crate-level safety requirements.
7347#[unsafe(no_mangle)]
7348pub unsafe extern "C" fn pio_dc_opf_instance_branch_susceptance_formula(
7349    instance: *const PioCalculationInstance,
7350) -> PioStringView {
7351    unsafe { PioCalculationInstance::get(instance) }
7352        .and_then(CalculationInstanceInner::dc_opf)
7353        .map_or(PioStringView::EMPTY, |instance| {
7354            PioStringView::new(branch_susceptance_formula_name(
7355                instance.branch_susceptance_formula(),
7356            ))
7357        })
7358}
7359
7360/// One voltage node and its fixed phasor, in volts and radians.
7361#[repr(C)]
7362#[derive(Clone, Copy)]
7363pub struct PioLinDist3FlowNodeView {
7364    pub bus: PioStringView,
7365    pub terminal: PioStringView,
7366    pub reference_magnitude: f64,
7367    pub reference_angle: f64,
7368    pub is_root: bool,
7369}
7370
7371/// One line conductor in the solution's physical row order.
7372/// Positions are zero based. Positive power flows from parent to child.
7373#[repr(C)]
7374#[derive(Clone, Copy)]
7375pub struct PioLinDist3FlowConductorView {
7376    pub line: PioStringView,
7377    pub source_line_row: usize,
7378    pub conductor_position: usize,
7379    pub parent_bus: PioStringView,
7380    pub parent_terminal: PioStringView,
7381    pub child_bus: PioStringView,
7382    pub child_terminal: PioStringView,
7383    pub reversed: bool,
7384}
7385
7386impl CalculationInstanceInner {
7387    fn lindist3flow(&self) -> Option<&powerio_prob::LinDist3FlowOpfInstance> {
7388        match self.instance()? {
7389            CalculationInstanceRef::LinDist3FlowOpf(instance) => Some(instance),
7390            _ => None,
7391        }
7392    }
7393}
7394
7395///
7396/// # Safety
7397/// Pointers and handles must satisfy the crate-level safety requirements.
7398#[unsafe(no_mangle)]
7399pub unsafe extern "C" fn pio_lindist3flow_opf_instance_node_count(
7400    instance: *const PioCalculationInstance,
7401) -> usize {
7402    unsafe { PioCalculationInstance::get(instance) }
7403        .and_then(CalculationInstanceInner::lindist3flow)
7404        .map_or(0, |instance| instance.topology().nodes.len())
7405}
7406
7407///
7408/// # Safety
7409/// Pointers and handles must satisfy the crate-level safety requirements.
7410#[unsafe(no_mangle)]
7411pub unsafe extern "C" fn pio_lindist3flow_opf_instance_conductor_count(
7412    instance: *const PioCalculationInstance,
7413) -> usize {
7414    unsafe { PioCalculationInstance::get(instance) }
7415        .and_then(CalculationInstanceInner::lindist3flow)
7416        .map_or(0, |instance| instance.topology().conductors.len())
7417}
7418
7419/// Read a node by zero based position. Strings borrow the instance handle.
7420///
7421/// # Safety
7422/// Pointers and handles must satisfy the crate-level safety requirements.
7423#[unsafe(no_mangle)]
7424pub unsafe extern "C" fn pio_lindist3flow_opf_instance_node_at(
7425    instance: *const PioCalculationInstance,
7426    index: usize,
7427    output: *mut PioLinDist3FlowNodeView,
7428    error: *mut *mut PioError,
7429) -> bool {
7430    unsafe {
7431        entry(error, false, || {
7432            let instance = require_calculation_instance(instance)?
7433                .lindist3flow()
7434                .ok_or_else(|| {
7435                    boundary_error(
7436                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
7437                        "expected a LinDist3Flow OPF instance",
7438                    )
7439                })?;
7440            let voltage = instance.reference().voltages.get(index).ok_or_else(|| {
7441                boundary_error(
7442                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7443                    format!("node index {index} is out of range"),
7444                )
7445            })?;
7446            *require_output(output, "output")? = PioLinDist3FlowNodeView {
7447                bus: PioStringView::new(&voltage.node.bus),
7448                terminal: PioStringView::new(&voltage.node.terminal),
7449                reference_magnitude: voltage.magnitude,
7450                reference_angle: voltage.angle,
7451                is_root: instance.topology().roots.contains(&voltage.node),
7452            };
7453            Ok(true)
7454        })
7455    }
7456}
7457
7458/// Read a conductor by zero based position. Strings borrow the instance handle.
7459///
7460/// # Safety
7461/// Pointers and handles must satisfy the crate-level safety requirements.
7462#[unsafe(no_mangle)]
7463pub unsafe extern "C" fn pio_lindist3flow_opf_instance_conductor_at(
7464    instance: *const PioCalculationInstance,
7465    index: usize,
7466    output: *mut PioLinDist3FlowConductorView,
7467    error: *mut *mut PioError,
7468) -> bool {
7469    unsafe {
7470        entry(error, false, || {
7471            let instance = require_calculation_instance(instance)?
7472                .lindist3flow()
7473                .ok_or_else(|| {
7474                    boundary_error(
7475                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
7476                        "expected a LinDist3Flow OPF instance",
7477                    )
7478                })?;
7479            let conductor = instance.topology().conductors.get(index).ok_or_else(|| {
7480                boundary_error(
7481                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7482                    format!("conductor index {index} is out of range"),
7483                )
7484            })?;
7485            *require_output(output, "output")? = PioLinDist3FlowConductorView {
7486                line: PioStringView::new(&conductor.line),
7487                source_line_row: conductor.source_line_row,
7488                conductor_position: conductor.conductor_position,
7489                parent_bus: PioStringView::new(&conductor.parent.bus),
7490                parent_terminal: PioStringView::new(&conductor.parent.terminal),
7491                child_bus: PioStringView::new(&conductor.child.bus),
7492                child_terminal: PioStringView::new(&conductor.child.terminal),
7493                reversed: conductor.reversed,
7494            };
7495            Ok(true)
7496        })
7497    }
7498}
7499
7500fn objective(instance: &CalculationInstanceInner) -> Option<&powerio_prob::Objective> {
7501    match instance.instance()? {
7502        CalculationInstanceRef::DcOpf(instance) => Some(instance.objective()),
7503        CalculationInstanceRef::AcOpf(instance) => Some(instance.objective()),
7504        CalculationInstanceRef::McAcOpf(instance) => Some(instance.objective()),
7505        CalculationInstanceRef::LinDist3FlowOpf(instance) => {
7506            Some(instance.base_instance().objective())
7507        }
7508        _ => None,
7509    }
7510}
7511
7512fn objective_term_name(term: &powerio_prob::ObjectiveTerm) -> &'static str {
7513    match term {
7514        powerio_prob::ObjectiveTerm::NetworkGeneratorCost => "network_generator_cost",
7515        powerio_prob::ObjectiveTerm::ActivePowerDispatchCost => "active_power_dispatch_cost",
7516        _ => "unknown",
7517    }
7518}
7519
7520///
7521/// # Safety
7522/// Pointers and handles must satisfy the crate-level safety requirements.
7523#[unsafe(no_mangle)]
7524pub unsafe extern "C" fn pio_calculation_instance_objective_term_count(
7525    instance: *const PioCalculationInstance,
7526) -> usize {
7527    unsafe { PioCalculationInstance::get(instance) }
7528        .and_then(objective)
7529        .map_or(0, |objective| objective.terms().len())
7530}
7531
7532/// Read one typed objective term by zero based position.
7533///
7534/// # Safety
7535/// Pointers and handles must satisfy the crate-level safety requirements.
7536#[unsafe(no_mangle)]
7537pub unsafe extern "C" fn pio_calculation_instance_objective_term_at(
7538    instance: *const PioCalculationInstance,
7539    index: usize,
7540    output: *mut PioObjectiveTermView,
7541    error: *mut *mut PioError,
7542) -> bool {
7543    unsafe {
7544        entry(error, false, || {
7545            let value = require_calculation_instance(instance)?;
7546            let objective = objective(value).ok_or_else(|| {
7547                boundary_error(
7548                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
7549                    "the calculation instance has no optimization objective",
7550                )
7551            })?;
7552            let term = objective.terms().get(index).ok_or_else(|| {
7553                boundary_error(
7554                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7555                    format!("objective term index {index} is out of range"),
7556                )
7557            })?;
7558            *require_output(output, "output")? = PioObjectiveTermView {
7559                kind: PioStringView::new(objective_term_name(term)),
7560            };
7561            Ok(true)
7562        })
7563    }
7564}
7565
7566fn active_constraint(
7567    instance: &CalculationInstanceInner,
7568    index: usize,
7569) -> Option<(&'static str, &powerio_prob::ConstraintSelection)> {
7570    match instance.instance()? {
7571        CalculationInstanceRef::DcOpf(instance) => match index {
7572            0 => Some((
7573                "generator_capability",
7574                &instance.constraints().generator_capability,
7575            )),
7576            1 => Some(("voltage_bounds", &instance.constraints().voltage_bounds)),
7577            2 => Some(("thermal_limits", &instance.constraints().thermal_limits)),
7578            3 => Some(("angle_bounds", &instance.constraints().angle_bounds)),
7579            _ => None,
7580        },
7581        CalculationInstanceRef::AcOpf(instance) => match index {
7582            0 => Some((
7583                "generator_capability",
7584                &instance.constraints().generator_capability,
7585            )),
7586            1 => Some(("voltage_bounds", &instance.constraints().voltage_bounds)),
7587            2 => Some(("thermal_limits", &instance.constraints().thermal_limits)),
7588            3 => Some(("angle_bounds", &instance.constraints().angle_bounds)),
7589            _ => None,
7590        },
7591        CalculationInstanceRef::McAcOpf(instance) => match index {
7592            0 => Some((
7593                "terminal_voltage_bounds",
7594                &instance.constraints().terminal_voltage_bounds,
7595            )),
7596            1 => Some(("conductor_limits", &instance.constraints().conductor_limits)),
7597            2 => Some((
7598                "generator_capability",
7599                &instance.constraints().generator_capability,
7600            )),
7601            _ => None,
7602        },
7603        CalculationInstanceRef::LinDist3FlowOpf(instance) => match index {
7604            0 => Some((
7605                "terminal_voltage_bounds",
7606                &instance
7607                    .base_instance()
7608                    .constraints()
7609                    .terminal_voltage_bounds,
7610            )),
7611            1 => Some((
7612                "conductor_limits",
7613                &instance.base_instance().constraints().conductor_limits,
7614            )),
7615            2 => Some((
7616                "generator_capability",
7617                &instance.base_instance().constraints().generator_capability,
7618            )),
7619            _ => None,
7620        },
7621        _ => None,
7622    }
7623}
7624
7625fn constraint_selection_parts(
7626    selection: &powerio_prob::ConstraintSelection,
7627) -> (&'static str, &[String]) {
7628    match selection {
7629        powerio_prob::ConstraintSelection::All => ("all", &[]),
7630        powerio_prob::ConstraintSelection::None => ("none", &[]),
7631        powerio_prob::ConstraintSelection::Only(identities) => ("only", identities),
7632        _ => ("unknown", &[]),
7633    }
7634}
7635
7636///
7637/// # Safety
7638/// Pointers and handles must satisfy the crate-level safety requirements.
7639#[unsafe(no_mangle)]
7640pub unsafe extern "C" fn pio_calculation_instance_active_constraint_count(
7641    instance: *const PioCalculationInstance,
7642) -> usize {
7643    match unsafe { PioCalculationInstance::get(instance) }
7644        .and_then(CalculationInstanceInner::instance)
7645    {
7646        Some(CalculationInstanceRef::DcOpf(_) | CalculationInstanceRef::AcOpf(_)) => 4,
7647        Some(CalculationInstanceRef::McAcOpf(_)) => 3,
7648        Some(CalculationInstanceRef::LinDist3FlowOpf(_)) => 3,
7649        _ => 0,
7650    }
7651}
7652
7653/// Read one active constraint family by zero based position.
7654///
7655/// # Safety
7656/// Pointers and handles must satisfy the crate-level safety requirements.
7657#[unsafe(no_mangle)]
7658pub unsafe extern "C" fn pio_calculation_instance_active_constraint_at(
7659    instance: *const PioCalculationInstance,
7660    index: usize,
7661    output: *mut PioActiveConstraintView,
7662    error: *mut *mut PioError,
7663) -> bool {
7664    unsafe {
7665        entry(error, false, || {
7666            let instance = require_calculation_instance(instance)?;
7667            let (family, selection) = active_constraint(instance, index).ok_or_else(|| {
7668                boundary_error(
7669                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7670                    format!("active constraint index {index} is out of range"),
7671                )
7672            })?;
7673            let (selection, identities) = constraint_selection_parts(selection);
7674            *require_output(output, "output")? = PioActiveConstraintView {
7675                family: PioStringView::new(family),
7676                selection: PioStringView::new(selection),
7677                identity_count: identities.len(),
7678            };
7679            Ok(true)
7680        })
7681    }
7682}
7683
7684/// Read one selected component identity from an `only` constraint selection.
7685///
7686/// # Safety
7687/// Pointers and handles must satisfy the crate-level safety requirements.
7688#[unsafe(no_mangle)]
7689pub unsafe extern "C" fn pio_calculation_instance_active_constraint_identity_at(
7690    instance: *const PioCalculationInstance,
7691    constraint_index: usize,
7692    identity_index: usize,
7693    error: *mut *mut PioError,
7694) -> PioStringView {
7695    unsafe {
7696        entry(error, PioStringView::EMPTY, || {
7697            let instance = require_calculation_instance(instance)?;
7698            let (_, selection) =
7699                active_constraint(instance, constraint_index).ok_or_else(|| {
7700                    boundary_error(
7701                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7702                        format!("active constraint index {constraint_index} is out of range"),
7703                    )
7704                })?;
7705            let (_, identities) = constraint_selection_parts(selection);
7706            let identity = identities.get(identity_index).ok_or_else(|| {
7707                boundary_error(
7708                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7709                    format!("constraint identity index {identity_index} is out of range"),
7710                )
7711            })?;
7712            Ok(PioStringView::new(identity))
7713        })
7714    }
7715}
7716
7717///
7718/// # Safety
7719/// Pointers and handles must satisfy the crate-level safety requirements.
7720#[unsafe(no_mangle)]
7721pub unsafe extern "C" fn pio_calculation_instance_has_initial_point(
7722    instance: *const PioCalculationInstance,
7723) -> bool {
7724    unsafe { PioCalculationInstance::get(instance) }
7725        .is_some_and(CalculationInstanceInner::has_initial_point)
7726}
7727
7728/// Return the optional owner-rooted initial operating point. A calculation
7729/// instance with no initial point returns NULL without setting an error.
7730///
7731/// # Safety
7732/// Pointers and handles must satisfy the crate-level safety requirements.
7733#[unsafe(no_mangle)]
7734pub unsafe extern "C" fn pio_calculation_instance_initial_point(
7735    instance: *const PioCalculationInstance,
7736    error: *mut *mut PioError,
7737) -> *mut PioOperatingPoint {
7738    unsafe {
7739        entry(error, std::ptr::null_mut(), || {
7740            let instance = PioCalculationInstance::get(instance).ok_or_else(|| {
7741                boundary_error(
7742                    &codes::BIND_CAPI_NULL_HANDLE,
7743                    "PioCalculationInstance must not be NULL",
7744                )
7745            })?;
7746            let has_initial_point = instance.has_initial_point();
7747            let projection = if has_initial_point {
7748                Some(match (instance.projection, instance.instance()) {
7749                    (
7750                        CalculationInstanceProjection::Direct,
7751                        Some(CalculationInstanceRef::DcPf(_)),
7752                    ) => OperatingPointProjection::DcPfInitial,
7753                    (
7754                        CalculationInstanceProjection::Direct,
7755                        Some(CalculationInstanceRef::AcPf(_)),
7756                    ) => OperatingPointProjection::AcPfInitial,
7757                    (
7758                        CalculationInstanceProjection::Direct,
7759                        Some(CalculationInstanceRef::DcOpf(_)),
7760                    ) => OperatingPointProjection::DcOpfInitial,
7761                    (
7762                        CalculationInstanceProjection::Direct,
7763                        Some(CalculationInstanceRef::AcOpf(_)),
7764                    ) => OperatingPointProjection::AcOpfInitial,
7765                    (
7766                        CalculationInstanceProjection::Direct,
7767                        Some(CalculationInstanceRef::McAcPf(_)),
7768                    ) => OperatingPointProjection::McAcPfInitial,
7769                    (
7770                        CalculationInstanceProjection::Direct,
7771                        Some(CalculationInstanceRef::McAcOpf(_)),
7772                    ) => OperatingPointProjection::McAcOpfInitial,
7773                    (
7774                        CalculationInstanceProjection::Direct,
7775                        Some(CalculationInstanceRef::LinDist3FlowOpf(_)),
7776                    ) => OperatingPointProjection::LinDist3FlowOpfInitial,
7777                    (CalculationInstanceProjection::DcPfSolution, _) => {
7778                        OperatingPointProjection::DcPfSolutionInitial
7779                    }
7780                    (CalculationInstanceProjection::AcPfSolution, _) => {
7781                        OperatingPointProjection::AcPfSolutionInitial
7782                    }
7783                    (CalculationInstanceProjection::DcOpfSolution, _) => {
7784                        OperatingPointProjection::DcOpfSolutionInitial
7785                    }
7786                    (CalculationInstanceProjection::AcOpfSolution, _) => {
7787                        OperatingPointProjection::AcOpfSolutionInitial
7788                    }
7789                    (CalculationInstanceProjection::SocwrOpfSolution, _) => {
7790                        OperatingPointProjection::SocwrOpfSolutionInitial
7791                    }
7792                    (CalculationInstanceProjection::McAcPfSolution, _) => {
7793                        OperatingPointProjection::McAcPfSolutionInitial
7794                    }
7795                    (CalculationInstanceProjection::McAcOpfSolution, _) => {
7796                        OperatingPointProjection::McAcOpfSolutionInitial
7797                    }
7798                    (CalculationInstanceProjection::LinDist3FlowOpfSolution, _) => {
7799                        OperatingPointProjection::LinDist3FlowOpfSolutionInitial
7800                    }
7801                    _ => {
7802                        return Err(boundary_error(
7803                            &codes::REQUEST_CAPI_TYPE_MISMATCH,
7804                            "the handle does not refer to a calculation instance with an operating point",
7805                        ));
7806                    }
7807                })
7808            } else {
7809                None
7810            };
7811            Ok(projection.map_or(std::ptr::null_mut(), |projection| {
7812                PioOperatingPoint::new_raw(OperatingPointInner {
7813                    value: ValueInner {
7814                        owner: Arc::clone(&instance.value.owner),
7815                        steps: instance.value.steps.clone(),
7816                    },
7817                    projection,
7818                })
7819            }))
7820        })
7821    }
7822}
7823
7824fn dist_load_voltage_model_name(model: &powerio_dist::DistLoadVoltageModel) -> &'static str {
7825    match model {
7826        powerio_dist::DistLoadVoltageModel::ConstantPower { .. } => "constant_power",
7827        powerio_dist::DistLoadVoltageModel::ConstantCurrent { .. } => "constant_current",
7828        powerio_dist::DistLoadVoltageModel::ConstantImpedance { .. } => "constant_impedance",
7829        powerio_dist::DistLoadVoltageModel::Zip { .. } => "zip",
7830        powerio_dist::DistLoadVoltageModel::Exponential { .. } => "exponential",
7831        _ => "unknown",
7832    }
7833}
7834
7835///
7836/// # Safety
7837/// Pointers and handles must satisfy the crate-level safety requirements.
7838#[unsafe(no_mangle)]
7839pub unsafe extern "C" fn pio_mc_ac_pf_instance_load_count(
7840    instance: *const PioCalculationInstance,
7841) -> usize {
7842    unsafe { PioCalculationInstance::get(instance) }
7843        .and_then(CalculationInstanceInner::mc_ac_pf)
7844        .map_or(0, |instance| instance.loads().len())
7845}
7846
7847///
7848/// # Safety
7849/// Pointers and handles must satisfy the crate-level safety requirements.
7850#[unsafe(no_mangle)]
7851pub unsafe extern "C" fn pio_mc_ac_pf_instance_load_at(
7852    instance: *const PioCalculationInstance,
7853    index: usize,
7854    output: *mut PioPrescribedTerminalPowerView,
7855    error: *mut *mut PioError,
7856) -> bool {
7857    unsafe {
7858        entry(error, false, || {
7859            let instance = require_calculation_instance(instance)?
7860                .mc_ac_pf()
7861                .ok_or_else(|| {
7862                    boundary_error(
7863                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
7864                        "the calculation instance is not powerio.McAcPfInstance",
7865                    )
7866                })?;
7867            let load = instance.loads().get(index).ok_or_else(|| {
7868                boundary_error(
7869                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7870                    format!("prescribed load index {index} is out of range"),
7871                )
7872            })?;
7873            *require_output(output, "output")? = PioPrescribedTerminalPowerView {
7874                load: PioStringView::new(&load.load),
7875                terminal_count: load.terminals.len(),
7876                voltage_model: PioStringView::new(dist_load_voltage_model_name(
7877                    &load.voltage_model,
7878                )),
7879            };
7880            Ok(true)
7881        })
7882    }
7883}
7884
7885fn model_value(values: &[f64], index: usize) -> (f64, bool) {
7886    values
7887        .get(index)
7888        .copied()
7889        .map_or((0.0, false), |value| (value, true))
7890}
7891
7892///
7893/// # Safety
7894/// Pointers and handles must satisfy the crate-level safety requirements.
7895#[unsafe(no_mangle)]
7896pub unsafe extern "C" fn pio_mc_ac_pf_instance_load_terminal_at(
7897    instance: *const PioCalculationInstance,
7898    load_index: usize,
7899    terminal_index: usize,
7900    output: *mut PioTerminalPowerView,
7901    error: *mut *mut PioError,
7902) -> bool {
7903    unsafe {
7904        entry(error, false, || {
7905            let instance = require_calculation_instance(instance)?
7906                .mc_ac_pf()
7907                .ok_or_else(|| {
7908                    boundary_error(
7909                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
7910                        "the calculation instance is not powerio.McAcPfInstance",
7911                    )
7912                })?;
7913            let load = instance.loads().get(load_index).ok_or_else(|| {
7914                boundary_error(
7915                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7916                    format!("prescribed load index {load_index} is out of range"),
7917                )
7918            })?;
7919            let terminal = load.terminals.get(terminal_index).ok_or_else(|| {
7920                boundary_error(
7921                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7922                    format!("load terminal index {terminal_index} is out of range"),
7923                )
7924            })?;
7925            let p = *load.p_w.get(terminal_index).ok_or_else(|| {
7926                boundary_error(
7927                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
7928                    "the prescribed load active power does not align with its terminals",
7929                )
7930            })?;
7931            let q = *load.q_var.get(terminal_index).ok_or_else(|| {
7932                boundary_error(
7933                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
7934                    "the prescribed load reactive power does not align with its terminals",
7935                )
7936            })?;
7937            let mut model_values = (0.0, false, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
7938            match &load.voltage_model {
7939                powerio_dist::DistLoadVoltageModel::ConstantPower { v_nom } => {
7940                    let (v, has_v) = model_value(v_nom, terminal_index);
7941                    model_values.0 = v;
7942                    model_values.1 = has_v;
7943                }
7944                powerio_dist::DistLoadVoltageModel::ConstantCurrent { v_nom } => {
7945                    let (v, has_v) = model_value(v_nom, terminal_index);
7946                    model_values = (v, has_v, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0);
7947                }
7948                powerio_dist::DistLoadVoltageModel::ConstantImpedance { v_nom } => {
7949                    let (v, has_v) = model_value(v_nom, terminal_index);
7950                    model_values = (v, has_v, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0);
7951                }
7952                powerio_dist::DistLoadVoltageModel::Zip {
7953                    v_nom,
7954                    alpha_z,
7955                    alpha_i,
7956                    alpha_p,
7957                    beta_z,
7958                    beta_i,
7959                    beta_p,
7960                } => {
7961                    let (v, has_v) = model_value(v_nom, terminal_index);
7962                    model_values = (
7963                        v,
7964                        has_v,
7965                        alpha_z.get(terminal_index).copied().unwrap_or(0.0),
7966                        alpha_i.get(terminal_index).copied().unwrap_or(0.0),
7967                        alpha_p.get(terminal_index).copied().unwrap_or(0.0),
7968                        beta_z.get(terminal_index).copied().unwrap_or(0.0),
7969                        beta_i.get(terminal_index).copied().unwrap_or(0.0),
7970                        beta_p.get(terminal_index).copied().unwrap_or(0.0),
7971                        0.0,
7972                        0.0,
7973                    );
7974                }
7975                powerio_dist::DistLoadVoltageModel::Exponential {
7976                    v_nom,
7977                    gamma_p,
7978                    gamma_q,
7979                } => {
7980                    let (v, has_v) = model_value(v_nom, terminal_index);
7981                    model_values = (
7982                        v,
7983                        has_v,
7984                        0.0,
7985                        0.0,
7986                        0.0,
7987                        0.0,
7988                        0.0,
7989                        0.0,
7990                        gamma_p.get(terminal_index).copied().unwrap_or(0.0),
7991                        gamma_q.get(terminal_index).copied().unwrap_or(0.0),
7992                    );
7993                }
7994                _ => {}
7995            }
7996            *require_output(output, "output")? = PioTerminalPowerView {
7997                terminal: PioStringView::new(terminal),
7998                active_power_w: p,
7999                reactive_power_var: q,
8000                nominal_voltage_v: model_values.0,
8001                has_nominal_voltage: model_values.1,
8002                active_impedance_fraction: model_values.2,
8003                active_current_fraction: model_values.3,
8004                active_power_fraction: model_values.4,
8005                reactive_impedance_fraction: model_values.5,
8006                reactive_current_fraction: model_values.6,
8007                reactive_power_fraction: model_values.7,
8008                active_power_exponent: model_values.8,
8009                reactive_power_exponent: model_values.9,
8010            };
8011            Ok(true)
8012        })
8013    }
8014}
8015
8016///
8017/// # Safety
8018/// Pointers and handles must satisfy the crate-level safety requirements.
8019#[unsafe(no_mangle)]
8020pub unsafe extern "C" fn pio_mc_ac_pf_instance_source_count(
8021    instance: *const PioCalculationInstance,
8022) -> usize {
8023    unsafe { PioCalculationInstance::get(instance) }
8024        .and_then(CalculationInstanceInner::mc_ac_pf)
8025        .map_or(0, |instance| instance.sources().len())
8026}
8027
8028///
8029/// # Safety
8030/// Pointers and handles must satisfy the crate-level safety requirements.
8031#[unsafe(no_mangle)]
8032pub unsafe extern "C" fn pio_mc_ac_pf_instance_source_at(
8033    instance: *const PioCalculationInstance,
8034    index: usize,
8035    output: *mut PioPrescribedSourceVoltageView,
8036    error: *mut *mut PioError,
8037) -> bool {
8038    unsafe {
8039        entry(error, false, || {
8040            let instance = require_calculation_instance(instance)?
8041                .mc_ac_pf()
8042                .ok_or_else(|| {
8043                    boundary_error(
8044                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
8045                        "the calculation instance is not powerio.McAcPfInstance",
8046                    )
8047                })?;
8048            let source = instance.sources().get(index).ok_or_else(|| {
8049                boundary_error(
8050                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8051                    format!("prescribed source index {index} is out of range"),
8052                )
8053            })?;
8054            *require_output(output, "output")? = PioPrescribedSourceVoltageView {
8055                source: PioStringView::new(&source.source),
8056                terminal_count: source.terminals.len(),
8057            };
8058            Ok(true)
8059        })
8060    }
8061}
8062
8063///
8064/// # Safety
8065/// Pointers and handles must satisfy the crate-level safety requirements.
8066#[unsafe(no_mangle)]
8067pub unsafe extern "C" fn pio_mc_ac_pf_instance_source_terminal_at(
8068    instance: *const PioCalculationInstance,
8069    source_index: usize,
8070    terminal_index: usize,
8071    output: *mut PioTerminalVoltageView,
8072    error: *mut *mut PioError,
8073) -> bool {
8074    unsafe {
8075        entry(error, false, || {
8076            let instance = require_calculation_instance(instance)?
8077                .mc_ac_pf()
8078                .ok_or_else(|| {
8079                    boundary_error(
8080                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
8081                        "the calculation instance is not powerio.McAcPfInstance",
8082                    )
8083                })?;
8084            let source = instance.sources().get(source_index).ok_or_else(|| {
8085                boundary_error(
8086                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8087                    format!("prescribed source index {source_index} is out of range"),
8088                )
8089            })?;
8090            let terminal = source.terminals.get(terminal_index).ok_or_else(|| {
8091                boundary_error(
8092                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8093                    format!("source terminal index {terminal_index} is out of range"),
8094                )
8095            })?;
8096            let magnitude = source.v_magnitude.get(terminal_index).ok_or_else(|| {
8097                boundary_error(
8098                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
8099                    "the prescribed source magnitudes do not align with its terminals",
8100                )
8101            })?;
8102            let angle = source.v_angle.get(terminal_index).ok_or_else(|| {
8103                boundary_error(
8104                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
8105                    "the prescribed source angles do not align with its terminals",
8106                )
8107            })?;
8108            *require_output(output, "output")? = PioTerminalVoltageView {
8109                terminal: PioStringView::new(terminal),
8110                magnitude_v: *magnitude,
8111                angle_radians: *angle,
8112            };
8113            Ok(true)
8114        })
8115    }
8116}
8117
8118///
8119/// # Safety
8120/// Pointers and handles must satisfy the crate-level safety requirements.
8121#[unsafe(no_mangle)]
8122pub unsafe extern "C" fn pio_mc_ac_pf_instance_isolated_terminal_count(
8123    instance: *const PioCalculationInstance,
8124) -> usize {
8125    unsafe { PioCalculationInstance::get(instance) }
8126        .and_then(CalculationInstanceInner::mc_ac_pf)
8127        .map_or(0, |instance| instance.isolated_terminals().len())
8128}
8129
8130///
8131/// # Safety
8132/// Pointers and handles must satisfy the crate-level safety requirements.
8133#[unsafe(no_mangle)]
8134pub unsafe extern "C" fn pio_mc_ac_pf_instance_isolated_terminal_at(
8135    instance: *const PioCalculationInstance,
8136    index: usize,
8137    output: *mut PioIsolatedTerminalView,
8138    error: *mut *mut PioError,
8139) -> bool {
8140    unsafe {
8141        entry(error, false, || {
8142            let instance = require_calculation_instance(instance)?
8143                .mc_ac_pf()
8144                .ok_or_else(|| {
8145                    boundary_error(
8146                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
8147                        "the calculation instance is not powerio.McAcPfInstance",
8148                    )
8149                })?;
8150            let (bus, terminal) = instance.isolated_terminals().get(index).ok_or_else(|| {
8151                boundary_error(
8152                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8153                    format!("isolated terminal index {index} is out of range"),
8154                )
8155            })?;
8156            *require_output(output, "output")? = PioIsolatedTerminalView {
8157                bus: PioStringView::new(bus),
8158                terminal: PioStringView::new(terminal),
8159            };
8160            Ok(true)
8161        })
8162    }
8163}
8164
8165///
8166/// # Safety
8167/// Pointers and handles must satisfy the crate-level safety requirements.
8168#[unsafe(no_mangle)]
8169pub unsafe extern "C" fn pio_mc_ac_pf_instance_active_control_count(
8170    instance: *const PioCalculationInstance,
8171) -> usize {
8172    unsafe { PioCalculationInstance::get(instance) }
8173        .and_then(CalculationInstanceInner::mc_ac_pf)
8174        .map_or(0, |instance| instance.control_modes().len())
8175}
8176
8177///
8178/// # Safety
8179/// Pointers and handles must satisfy the crate-level safety requirements.
8180#[unsafe(no_mangle)]
8181pub unsafe extern "C" fn pio_mc_ac_pf_instance_active_control_at(
8182    instance: *const PioCalculationInstance,
8183    index: usize,
8184    output: *mut PioActiveControlView,
8185    error: *mut *mut PioError,
8186) -> bool {
8187    unsafe {
8188        entry(error, false, || {
8189            let instance = require_calculation_instance(instance)?
8190                .mc_ac_pf()
8191                .ok_or_else(|| {
8192                    boundary_error(
8193                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
8194                        "the calculation instance is not powerio.McAcPfInstance",
8195                    )
8196                })?;
8197            let control = instance.control_modes().get(index).ok_or_else(|| {
8198                boundary_error(
8199                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8200                    format!("active control index {index} is out of range"),
8201                )
8202            })?;
8203            let (kind, component_id) = match control {
8204                powerio_prob::ActiveControlMode::RegulatorTap { transformer } => {
8205                    ("regulator_tap", transformer.as_str())
8206                }
8207                powerio_prob::ActiveControlMode::CapacitorSteps { capacitor } => {
8208                    ("capacitor_steps", capacitor.as_str())
8209                }
8210                _ => ("unknown", ""),
8211            };
8212            *require_output(output, "output")? = PioActiveControlView {
8213                kind: PioStringView::new(kind),
8214                component_id: PioStringView::new(component_id),
8215            };
8216            Ok(true)
8217        })
8218    }
8219}
8220
8221fn scuc_inputs(instance: &CalculationInstanceInner) -> Option<&powerio_prob::ScucInputs> {
8222    instance.ac_scuc().map(powerio_prob::AcScucInstance::inputs)
8223}
8224
8225fn scuc_inputs_or_error(
8226    instance: &CalculationInstanceInner,
8227) -> Result<&powerio_prob::ScucInputs, *mut PioError> {
8228    scuc_inputs(instance).ok_or_else(|| {
8229        boundary_error(
8230            &codes::REQUEST_CAPI_TYPE_MISMATCH,
8231            "the calculation instance is not powerio.AcScucInstance",
8232        )
8233    })
8234}
8235
8236fn component_id_view(id: &ComponentId) -> PioComponentIdView {
8237    PioComponentIdView {
8238        component_type: PioStringView::new(id.component_type()),
8239        local_id: PioStringView::new(id.local_id()),
8240    }
8241}
8242
8243fn empty_component_id_view() -> PioComponentIdView {
8244    PioComponentIdView {
8245        component_type: PioStringView::EMPTY,
8246        local_id: PioStringView::EMPTY,
8247    }
8248}
8249
8250fn optional_component_id_view(value: Option<&ComponentId>) -> (PioComponentIdView, bool) {
8251    value.map_or((empty_component_id_view(), false), |value| {
8252        (component_id_view(value), true)
8253    })
8254}
8255
8256fn terminal_reference_view(
8257    value: Option<&powerio_tx::TerminalReference>,
8258) -> (PioTerminalReferenceView, bool) {
8259    value.map_or(
8260        (
8261            PioTerminalReferenceView {
8262                equipment: empty_component_id_view(),
8263                terminal: 0,
8264            },
8265            false,
8266        ),
8267        |value| {
8268            (
8269                PioTerminalReferenceView {
8270                    equipment: component_id_view(&value.equipment),
8271                    terminal: value.terminal,
8272                },
8273                true,
8274            )
8275        },
8276    )
8277}
8278
8279fn transformer_control_mode_name(mode: powerio_tx::TransformerControlMode) -> &'static str {
8280    match mode {
8281        powerio_tx::TransformerControlMode::Fixed => "fixed",
8282        powerio_tx::TransformerControlMode::Voltage => "voltage",
8283        powerio_tx::TransformerControlMode::ReactiveFlow => "reactive_flow",
8284        powerio_tx::TransformerControlMode::ActiveFlow => "active_flow",
8285        powerio_tx::TransformerControlMode::DcLineQuantity => "dc_line_quantity",
8286        powerio_tx::TransformerControlMode::AsymmetricActiveFlow => "asymmetric_active_flow",
8287        _ => "unknown",
8288    }
8289}
8290
8291fn transformer_control_view(
8292    value: Option<&powerio_tx::TransformerControl>,
8293) -> (PioTransformerControlView, bool) {
8294    let empty_terminal = terminal_reference_view(None).0;
8295    value.map_or(
8296        (
8297            PioTransformerControlView {
8298                mode: PioStringView::EMPTY,
8299                enabled: false,
8300                controlled_bus_id: 0,
8301                has_controlled_bus: false,
8302                controlled_bus_on_winding_side: false,
8303                regulating_terminal: empty_terminal,
8304                has_regulating_terminal: false,
8305                tap_min: 0.0,
8306                tap_max: 0.0,
8307                band_min: 0.0,
8308                band_max: 0.0,
8309                tap_position_count: 0,
8310                mva_base: 0.0,
8311                winding_connection_angle: 0.0,
8312                has_winding_connection_angle: false,
8313            },
8314            false,
8315        ),
8316        |control| {
8317            let (regulating_terminal, has_regulating_terminal) =
8318                terminal_reference_view(control.regulating_terminal.as_ref());
8319            (
8320                PioTransformerControlView {
8321                    mode: PioStringView::new(transformer_control_mode_name(control.mode)),
8322                    enabled: control.enabled,
8323                    controlled_bus_id: control.controlled_bus.map_or(0, |bus| bus.0),
8324                    has_controlled_bus: control.controlled_bus.is_some(),
8325                    controlled_bus_on_winding_side: control.controlled_bus_on_winding_side,
8326                    regulating_terminal,
8327                    has_regulating_terminal,
8328                    tap_min: control.tap_min,
8329                    tap_max: control.tap_max,
8330                    band_min: control.band_min,
8331                    band_max: control.band_max,
8332                    tap_position_count: control.ntp,
8333                    mva_base: control.mva_base,
8334                    winding_connection_angle: control.winding_connection_angle.unwrap_or(0.0),
8335                    has_winding_connection_angle: control.winding_connection_angle.is_some(),
8336                },
8337                true,
8338            )
8339        },
8340    )
8341}
8342
8343fn scuc_device_kind_name(kind: powerio_prob::ScucDeviceKind) -> &'static str {
8344    match kind {
8345        powerio_prob::ScucDeviceKind::Producer => "producer",
8346        powerio_prob::ScucDeviceKind::Consumer => "consumer",
8347        _ => "unknown",
8348    }
8349}
8350
8351fn scuc_reactive_capability_view(
8352    capability: &powerio_prob::ScucReactiveCapability,
8353) -> PioScucReactiveCapabilityView {
8354    match capability {
8355        powerio_prob::ScucReactiveCapability::None => PioScucReactiveCapabilityView {
8356            kind: PioStringView::new("none"),
8357            reactive_power_at_zero_active_power_pu: 0.0,
8358            reactive_power_at_zero_active_power_min_pu: 0.0,
8359            reactive_power_at_zero_active_power_max_pu: 0.0,
8360            slope: 0.0,
8361            slope_min: 0.0,
8362            slope_max: 0.0,
8363        },
8364        powerio_prob::ScucReactiveCapability::Linear {
8365            reactive_power_at_zero_active_power,
8366            slope,
8367        } => PioScucReactiveCapabilityView {
8368            kind: PioStringView::new("linear"),
8369            reactive_power_at_zero_active_power_pu: *reactive_power_at_zero_active_power,
8370            reactive_power_at_zero_active_power_min_pu: 0.0,
8371            reactive_power_at_zero_active_power_max_pu: 0.0,
8372            slope: *slope,
8373            slope_min: 0.0,
8374            slope_max: 0.0,
8375        },
8376        powerio_prob::ScucReactiveCapability::Bounded {
8377            reactive_power_at_zero_active_power_min,
8378            reactive_power_at_zero_active_power_max,
8379            slope_min,
8380            slope_max,
8381        } => PioScucReactiveCapabilityView {
8382            kind: PioStringView::new("bounded"),
8383            reactive_power_at_zero_active_power_pu: 0.0,
8384            reactive_power_at_zero_active_power_min_pu: *reactive_power_at_zero_active_power_min,
8385            reactive_power_at_zero_active_power_max_pu: *reactive_power_at_zero_active_power_max,
8386            slope: 0.0,
8387            slope_min: *slope_min,
8388            slope_max: *slope_max,
8389        },
8390        _ => PioScucReactiveCapabilityView {
8391            kind: PioStringView::new("unknown"),
8392            reactive_power_at_zero_active_power_pu: 0.0,
8393            reactive_power_at_zero_active_power_min_pu: 0.0,
8394            reactive_power_at_zero_active_power_max_pu: 0.0,
8395            slope: 0.0,
8396            slope_min: 0.0,
8397            slope_max: 0.0,
8398        },
8399    }
8400}
8401
8402fn scuc_reserve_costs_view(costs: &powerio_prob::ScucReserveCosts) -> PioScucReserveCostsView {
8403    PioScucReserveCostsView {
8404        regulation_up: costs.regulation_up,
8405        regulation_down: costs.regulation_down,
8406        synchronized: costs.synchronized,
8407        nonsynchronized: costs.nonsynchronized,
8408        ramping_up_online: costs.ramping_up_online,
8409        ramping_down_online: costs.ramping_down_online,
8410        ramping_up_offline: costs.ramping_up_offline,
8411        ramping_down_offline: costs.ramping_down_offline,
8412        reactive_up: costs.reactive_up,
8413        reactive_down: costs.reactive_down,
8414    }
8415}
8416
8417fn scuc_device_view(device: &powerio_prob::ScucDevice) -> PioScucDeviceView {
8418    PioScucDeviceView {
8419        id: component_id_view(&device.id),
8420        kind: PioStringView::new(scuc_device_kind_name(device.kind)),
8421        initial_on_status: device.initial_on_status,
8422        on_cost: device.on_cost,
8423        startup_cost: device.startup_cost,
8424        shutdown_cost: device.shutdown_cost,
8425        minimum_up_time_hours: device.minimum_up_time,
8426        minimum_down_time_hours: device.minimum_down_time,
8427        ramp_limits: PioScucRampLimitsView {
8428            up_pu_per_hour: device.ramp_limits.up,
8429            down_pu_per_hour: device.ramp_limits.down,
8430            startup_pu_per_hour: device.ramp_limits.startup,
8431            shutdown_pu_per_hour: device.ramp_limits.shutdown,
8432        },
8433        reserve_limits: PioScucReserveLimitsView {
8434            regulation_up_pu: device.reserve_limits.regulation_up,
8435            regulation_down_pu: device.reserve_limits.regulation_down,
8436            synchronized_pu: device.reserve_limits.synchronized,
8437            nonsynchronized_pu: device.reserve_limits.nonsynchronized,
8438            ramping_up_online_pu: device.reserve_limits.ramping_up_online,
8439            ramping_down_online_pu: device.reserve_limits.ramping_down_online,
8440            ramping_up_offline_pu: device.reserve_limits.ramping_up_offline,
8441            ramping_down_offline_pu: device.reserve_limits.ramping_down_offline,
8442        },
8443        initial_commitment: PioScucInitialCommitmentView {
8444            accumulated_up_time_hours: device.initial_commitment.accumulated_up_time,
8445            accumulated_down_time_hours: device.initial_commitment.accumulated_down_time,
8446        },
8447        reactive_capability: scuc_reactive_capability_view(&device.reactive_capability),
8448        period_count: device.periods.len(),
8449        startup_cost_adjustment_count: device.startup_cost_adjustments.len(),
8450        startup_limit_count: device.startup_limits.len(),
8451        energy_upper_bound_count: device.energy_upper_bounds.len(),
8452        energy_lower_bound_count: device.energy_lower_bounds.len(),
8453    }
8454}
8455
8456fn scuc_device_or_error(
8457    inputs: &powerio_prob::ScucInputs,
8458    device_index: usize,
8459) -> Result<&powerio_prob::ScucDevice, *mut PioError> {
8460    inputs.devices.get(device_index).ok_or_else(|| {
8461        boundary_error(
8462            &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8463            format!("SCUC device index {device_index} is out of range"),
8464        )
8465    })
8466}
8467
8468/// Read semantic collection sizes for one AC SCUC instance.
8469///
8470/// # Safety
8471/// Pointers and handles must satisfy the crate-level safety requirements.
8472#[unsafe(no_mangle)]
8473pub unsafe extern "C" fn pio_ac_scuc_instance_dimensions(
8474    instance: *const PioCalculationInstance,
8475    output: *mut PioScucDimensionsView,
8476    error: *mut *mut PioError,
8477) -> bool {
8478    unsafe {
8479        entry(error, false, || {
8480            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8481            *require_output(output, "output")? = PioScucDimensionsView {
8482                period_count: inputs.interval_durations.len(),
8483                device_count: inputs.devices.len(),
8484                producer_count: inputs.producers().count(),
8485                consumer_count: inputs.consumers().count(),
8486                shunt_count: inputs.shunts.len(),
8487                branch_switching_cost_count: inputs.branch_switching_costs.len(),
8488                transformer_control_count: inputs.transformer_controls.len(),
8489                active_reserve_zone_count: inputs.active_reserve_zones.len(),
8490                reactive_reserve_zone_count: inputs.reactive_reserve_zones.len(),
8491                contingency_count: inputs.contingencies.len(),
8492            };
8493            Ok(true)
8494        })
8495    }
8496}
8497
8498/// Borrow interval durations in hours, in chronological order.
8499///
8500/// # Safety
8501/// Pointers and handles must satisfy the crate-level safety requirements.
8502#[unsafe(no_mangle)]
8503pub unsafe extern "C" fn pio_ac_scuc_instance_interval_durations(
8504    instance: *const PioCalculationInstance,
8505) -> PioF64View {
8506    unsafe { PioCalculationInstance::get(instance) }
8507        .and_then(scuc_inputs)
8508        .map_or(PioF64View::EMPTY, |inputs| {
8509            PioF64View::new(&inputs.interval_durations)
8510        })
8511}
8512
8513/// Read the four required violation costs.
8514///
8515/// # Safety
8516/// Pointers and handles must satisfy the crate-level safety requirements.
8517#[unsafe(no_mangle)]
8518pub unsafe extern "C" fn pio_ac_scuc_instance_violation_costs(
8519    instance: *const PioCalculationInstance,
8520    output: *mut PioScucViolationCostView,
8521    error: *mut *mut PioError,
8522) -> bool {
8523    unsafe {
8524        entry(error, false, || {
8525            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8526            let costs = inputs.violation_costs;
8527            *require_output(output, "output")? = PioScucViolationCostView {
8528                active_power_balance: costs.active_power_balance,
8529                reactive_power_balance: costs.reactive_power_balance,
8530                branch_thermal_limit: costs.branch_thermal_limit,
8531                energy_requirement: costs.energy_requirement,
8532            };
8533            Ok(true)
8534        })
8535    }
8536}
8537
8538///
8539/// # Safety
8540/// Pointers and handles must satisfy the crate-level safety requirements.
8541#[unsafe(no_mangle)]
8542pub unsafe extern "C" fn pio_ac_scuc_instance_device_count(
8543    instance: *const PioCalculationInstance,
8544) -> usize {
8545    unsafe { PioCalculationInstance::get(instance) }
8546        .and_then(scuc_inputs)
8547        .map_or(0, |inputs| inputs.devices.len())
8548}
8549
8550///
8551/// # Safety
8552/// Pointers and handles must satisfy the crate-level safety requirements.
8553#[unsafe(no_mangle)]
8554pub unsafe extern "C" fn pio_ac_scuc_instance_device_at(
8555    instance: *const PioCalculationInstance,
8556    index: usize,
8557    output: *mut PioScucDeviceView,
8558    error: *mut *mut PioError,
8559) -> bool {
8560    unsafe {
8561        entry(error, false, || {
8562            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8563            let device = scuc_device_or_error(inputs, index)?;
8564            *require_output(output, "output")? = scuc_device_view(device);
8565            Ok(true)
8566        })
8567    }
8568}
8569
8570/// Read one device by its exact source UID.
8571///
8572/// # Safety
8573/// Pointers and handles must satisfy the crate-level safety requirements.
8574#[unsafe(no_mangle)]
8575pub unsafe extern "C" fn pio_ac_scuc_instance_device_get(
8576    instance: *const PioCalculationInstance,
8577    uid: *const c_char,
8578    uid_len: usize,
8579    output: *mut PioScucDeviceView,
8580    error: *mut *mut PioError,
8581) -> bool {
8582    unsafe {
8583        entry(error, false, || {
8584            let uid = required_str(uid, uid_len, "device_uid")?;
8585            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8586            let device = inputs.device(uid).ok_or_else(|| {
8587                boundary_error(
8588                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8589                    format!("SCUC device UID '{uid}' does not exist"),
8590                )
8591            })?;
8592            *require_output(output, "output")? = scuc_device_view(device);
8593            Ok(true)
8594        })
8595    }
8596}
8597
8598///
8599/// # Safety
8600/// Pointers and handles must satisfy the crate-level safety requirements.
8601#[unsafe(no_mangle)]
8602pub unsafe extern "C" fn pio_ac_scuc_instance_device_startup_cost_adjustment_count(
8603    instance: *const PioCalculationInstance,
8604    device_index: usize,
8605) -> usize {
8606    unsafe { PioCalculationInstance::get(instance) }
8607        .and_then(scuc_inputs)
8608        .and_then(|inputs| inputs.devices.get(device_index))
8609        .map_or(0, |device| device.startup_cost_adjustments.len())
8610}
8611
8612///
8613/// # Safety
8614/// Pointers and handles must satisfy the crate-level safety requirements.
8615#[unsafe(no_mangle)]
8616pub unsafe extern "C" fn pio_ac_scuc_instance_device_startup_cost_adjustment_at(
8617    instance: *const PioCalculationInstance,
8618    device_index: usize,
8619    adjustment_index: usize,
8620    output: *mut PioScucStartupCostAdjustmentView,
8621    error: *mut *mut PioError,
8622) -> bool {
8623    unsafe {
8624        entry(error, false, || {
8625            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8626            let device = scuc_device_or_error(inputs, device_index)?;
8627            let adjustment = device
8628                .startup_cost_adjustments
8629                .get(adjustment_index)
8630                .ok_or_else(|| {
8631                    boundary_error(
8632                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8633                        format!(
8634                            "SCUC device {device_index} startup cost adjustment index {adjustment_index} is out of range"
8635                        ),
8636                    )
8637                })?;
8638            *require_output(output, "output")? = PioScucStartupCostAdjustmentView {
8639                cost: adjustment.cost,
8640                maximum_down_time_hours: adjustment.maximum_down_time,
8641            };
8642            Ok(true)
8643        })
8644    }
8645}
8646
8647///
8648/// # Safety
8649/// Pointers and handles must satisfy the crate-level safety requirements.
8650#[unsafe(no_mangle)]
8651pub unsafe extern "C" fn pio_ac_scuc_instance_device_startup_limit_count(
8652    instance: *const PioCalculationInstance,
8653    device_index: usize,
8654) -> usize {
8655    unsafe { PioCalculationInstance::get(instance) }
8656        .and_then(scuc_inputs)
8657        .and_then(|inputs| inputs.devices.get(device_index))
8658        .map_or(0, |device| device.startup_limits.len())
8659}
8660
8661///
8662/// # Safety
8663/// Pointers and handles must satisfy the crate-level safety requirements.
8664#[unsafe(no_mangle)]
8665pub unsafe extern "C" fn pio_ac_scuc_instance_device_startup_limit_at(
8666    instance: *const PioCalculationInstance,
8667    device_index: usize,
8668    limit_index: usize,
8669    output: *mut PioScucStartupLimitView,
8670    error: *mut *mut PioError,
8671) -> bool {
8672    unsafe {
8673        entry(error, false, || {
8674            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8675            let device = scuc_device_or_error(inputs, device_index)?;
8676            let limit = device.startup_limits.get(limit_index).ok_or_else(|| {
8677                boundary_error(
8678                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8679                    format!(
8680                        "SCUC device {device_index} startup limit index {limit_index} is out of range"
8681                    ),
8682                )
8683            })?;
8684            *require_output(output, "output")? = PioScucStartupLimitView {
8685                start_time_hours: limit.start_time,
8686                end_time_hours: limit.end_time,
8687                maximum_startups: limit.maximum_startups,
8688            };
8689            Ok(true)
8690        })
8691    }
8692}
8693
8694unsafe fn scuc_energy_requirement_count(
8695    instance: *const PioCalculationInstance,
8696    device_index: usize,
8697    upper: bool,
8698) -> usize {
8699    unsafe { PioCalculationInstance::get(instance) }
8700        .and_then(scuc_inputs)
8701        .and_then(|inputs| inputs.devices.get(device_index))
8702        .map_or(0, |device| {
8703            if upper {
8704                device.energy_upper_bounds.len()
8705            } else {
8706                device.energy_lower_bounds.len()
8707            }
8708        })
8709}
8710
8711unsafe fn scuc_energy_requirement_at(
8712    instance: *const PioCalculationInstance,
8713    device_index: usize,
8714    requirement_index: usize,
8715    upper: bool,
8716    output: *mut PioScucEnergyRequirementView,
8717    error: *mut *mut PioError,
8718) -> bool {
8719    unsafe {
8720        entry(error, false, || {
8721            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8722            let device = scuc_device_or_error(inputs, device_index)?;
8723            let requirements = if upper {
8724                &device.energy_upper_bounds
8725            } else {
8726                &device.energy_lower_bounds
8727            };
8728            let requirement = requirements.get(requirement_index).ok_or_else(|| {
8729                boundary_error(
8730                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8731                    format!(
8732                        "SCUC device {device_index} {} energy requirement index {requirement_index} is out of range",
8733                        if upper { "upper" } else { "lower" }
8734                    ),
8735                )
8736            })?;
8737            *require_output(output, "output")? = PioScucEnergyRequirementView {
8738                start_time_hours: requirement.start_time,
8739                end_time_hours: requirement.end_time,
8740                energy_pu: requirement.energy,
8741            };
8742            Ok(true)
8743        })
8744    }
8745}
8746
8747///
8748/// # Safety
8749/// Pointers and handles must satisfy the crate-level safety requirements.
8750#[unsafe(no_mangle)]
8751pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_upper_bound_count(
8752    instance: *const PioCalculationInstance,
8753    device_index: usize,
8754) -> usize {
8755    unsafe { scuc_energy_requirement_count(instance, device_index, true) }
8756}
8757
8758///
8759/// # Safety
8760/// Pointers and handles must satisfy the crate-level safety requirements.
8761#[unsafe(no_mangle)]
8762pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_upper_bound_at(
8763    instance: *const PioCalculationInstance,
8764    device_index: usize,
8765    requirement_index: usize,
8766    output: *mut PioScucEnergyRequirementView,
8767    error: *mut *mut PioError,
8768) -> bool {
8769    unsafe {
8770        scuc_energy_requirement_at(
8771            instance,
8772            device_index,
8773            requirement_index,
8774            true,
8775            output,
8776            error,
8777        )
8778    }
8779}
8780
8781///
8782/// # Safety
8783/// Pointers and handles must satisfy the crate-level safety requirements.
8784#[unsafe(no_mangle)]
8785pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_lower_bound_count(
8786    instance: *const PioCalculationInstance,
8787    device_index: usize,
8788) -> usize {
8789    unsafe { scuc_energy_requirement_count(instance, device_index, false) }
8790}
8791
8792///
8793/// # Safety
8794/// Pointers and handles must satisfy the crate-level safety requirements.
8795#[unsafe(no_mangle)]
8796pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_lower_bound_at(
8797    instance: *const PioCalculationInstance,
8798    device_index: usize,
8799    requirement_index: usize,
8800    output: *mut PioScucEnergyRequirementView,
8801    error: *mut *mut PioError,
8802) -> bool {
8803    unsafe {
8804        scuc_energy_requirement_at(
8805            instance,
8806            device_index,
8807            requirement_index,
8808            false,
8809            output,
8810            error,
8811        )
8812    }
8813}
8814
8815///
8816/// # Safety
8817/// Pointers and handles must satisfy the crate-level safety requirements.
8818#[unsafe(no_mangle)]
8819pub unsafe extern "C" fn pio_ac_scuc_instance_device_period_at(
8820    instance: *const PioCalculationInstance,
8821    device_index: usize,
8822    period_index: usize,
8823    output: *mut PioScucDevicePeriodView,
8824    error: *mut *mut PioError,
8825) -> bool {
8826    unsafe {
8827        entry(error, false, || {
8828            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8829            let device = scuc_device_or_error(inputs, device_index)?;
8830            let period = device.periods.get(period_index).ok_or_else(|| {
8831                boundary_error(
8832                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8833                    format!(
8834                        "SCUC device {device_index} period index {period_index} is out of range"
8835                    ),
8836                )
8837            })?;
8838            *require_output(output, "output")? = PioScucDevicePeriodView {
8839                on_status_min: period.on_status_min,
8840                on_status_max: period.on_status_max,
8841                active_power_min_pu: period.active_power_min,
8842                active_power_max_pu: period.active_power_max,
8843                reactive_power_min_pu: period.reactive_power_min,
8844                reactive_power_max_pu: period.reactive_power_max,
8845                energy_cost_block_count: period.energy_cost_blocks.len(),
8846                reserve_costs: scuc_reserve_costs_view(&period.reserve_costs),
8847            };
8848            Ok(true)
8849        })
8850    }
8851}
8852
8853///
8854/// # Safety
8855/// Pointers and handles must satisfy the crate-level safety requirements.
8856#[unsafe(no_mangle)]
8857pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_cost_block_count(
8858    instance: *const PioCalculationInstance,
8859    device_index: usize,
8860    period_index: usize,
8861) -> usize {
8862    unsafe { PioCalculationInstance::get(instance) }
8863        .and_then(scuc_inputs)
8864        .and_then(|inputs| inputs.devices.get(device_index))
8865        .and_then(|device| device.periods.get(period_index))
8866        .map_or(0, |period| period.energy_cost_blocks.len())
8867}
8868
8869///
8870/// # Safety
8871/// Pointers and handles must satisfy the crate-level safety requirements.
8872#[unsafe(no_mangle)]
8873pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_cost_block_at(
8874    instance: *const PioCalculationInstance,
8875    device_index: usize,
8876    period_index: usize,
8877    block_index: usize,
8878    output: *mut PioScucEnergyCostBlockView,
8879    error: *mut *mut PioError,
8880) -> bool {
8881    unsafe {
8882        entry(error, false, || {
8883            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8884            let device = scuc_device_or_error(inputs, device_index)?;
8885            let period = device.periods.get(period_index).ok_or_else(|| {
8886                boundary_error(
8887                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8888                    format!(
8889                        "SCUC device {device_index} period index {period_index} is out of range"
8890                    ),
8891                )
8892            })?;
8893            let block = period.energy_cost_blocks.get(block_index).ok_or_else(|| {
8894                boundary_error(
8895                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8896                    format!(
8897                        "SCUC device {device_index} period {period_index} energy cost block index {block_index} is out of range"
8898                    ),
8899                )
8900            })?;
8901            *require_output(output, "output")? = PioScucEnergyCostBlockView {
8902                marginal_cost: block.marginal_cost,
8903                block_size_pu: block.block_size,
8904            };
8905            Ok(true)
8906        })
8907    }
8908}
8909
8910///
8911/// # Safety
8912/// Pointers and handles must satisfy the crate-level safety requirements.
8913#[unsafe(no_mangle)]
8914pub unsafe extern "C" fn pio_ac_scuc_instance_shunt_count(
8915    instance: *const PioCalculationInstance,
8916) -> usize {
8917    unsafe { PioCalculationInstance::get(instance) }
8918        .and_then(scuc_inputs)
8919        .map_or(0, |inputs| inputs.shunts.len())
8920}
8921
8922fn scuc_shunt_view(shunt: &powerio_prob::ScucShunt) -> PioScucShuntView {
8923    PioScucShuntView {
8924        id: component_id_view(&shunt.id),
8925        conductance_per_step_pu: shunt.conductance_per_step,
8926        susceptance_per_step_pu: shunt.susceptance_per_step,
8927        step_min: shunt.step_min,
8928        step_max: shunt.step_max,
8929        initial_step: shunt.initial_step,
8930    }
8931}
8932
8933///
8934/// # Safety
8935/// Pointers and handles must satisfy the crate-level safety requirements.
8936#[unsafe(no_mangle)]
8937pub unsafe extern "C" fn pio_ac_scuc_instance_shunt_at(
8938    instance: *const PioCalculationInstance,
8939    index: usize,
8940    output: *mut PioScucShuntView,
8941    error: *mut *mut PioError,
8942) -> bool {
8943    unsafe {
8944        entry(error, false, || {
8945            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8946            let shunt = inputs.shunts.get(index).ok_or_else(|| {
8947                boundary_error(
8948                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8949                    format!("SCUC shunt index {index} is out of range"),
8950                )
8951            })?;
8952            *require_output(output, "output")? = scuc_shunt_view(shunt);
8953            Ok(true)
8954        })
8955    }
8956}
8957
8958/// Read one shunt by its exact source UID.
8959///
8960/// # Safety
8961/// Pointers and handles must satisfy the crate-level safety requirements.
8962#[unsafe(no_mangle)]
8963pub unsafe extern "C" fn pio_ac_scuc_instance_shunt_get(
8964    instance: *const PioCalculationInstance,
8965    uid: *const c_char,
8966    uid_len: usize,
8967    output: *mut PioScucShuntView,
8968    error: *mut *mut PioError,
8969) -> bool {
8970    unsafe {
8971        entry(error, false, || {
8972            let uid = required_str(uid, uid_len, "shunt_uid")?;
8973            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8974            let shunt = inputs.shunt(uid).ok_or_else(|| {
8975                boundary_error(
8976                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8977                    format!("SCUC shunt UID '{uid}' does not exist"),
8978                )
8979            })?;
8980            *require_output(output, "output")? = scuc_shunt_view(shunt);
8981            Ok(true)
8982        })
8983    }
8984}
8985
8986///
8987/// # Safety
8988/// Pointers and handles must satisfy the crate-level safety requirements.
8989#[unsafe(no_mangle)]
8990pub unsafe extern "C" fn pio_ac_scuc_instance_branch_switching_cost_count(
8991    instance: *const PioCalculationInstance,
8992) -> usize {
8993    unsafe { PioCalculationInstance::get(instance) }
8994        .and_then(scuc_inputs)
8995        .map_or(0, |inputs| inputs.branch_switching_costs.len())
8996}
8997
8998///
8999/// # Safety
9000/// Pointers and handles must satisfy the crate-level safety requirements.
9001#[unsafe(no_mangle)]
9002pub unsafe extern "C" fn pio_ac_scuc_instance_branch_switching_cost_at(
9003    instance: *const PioCalculationInstance,
9004    index: usize,
9005    output: *mut PioScucBranchSwitchingCostView,
9006    error: *mut *mut PioError,
9007) -> bool {
9008    unsafe {
9009        entry(error, false, || {
9010            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9011            let cost = inputs.branch_switching_costs.get(index).ok_or_else(|| {
9012                boundary_error(
9013                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9014                    format!("SCUC branch switching cost index {index} is out of range"),
9015                )
9016            })?;
9017            *require_output(output, "output")? = PioScucBranchSwitchingCostView {
9018                id: component_id_view(&cost.id),
9019                connection_cost: cost.connection_cost,
9020                disconnection_cost: cost.disconnection_cost,
9021            };
9022            Ok(true)
9023        })
9024    }
9025}
9026
9027///
9028/// # Safety
9029/// Pointers and handles must satisfy the crate-level safety requirements.
9030#[unsafe(no_mangle)]
9031pub unsafe extern "C" fn pio_ac_scuc_instance_transformer_control_count(
9032    instance: *const PioCalculationInstance,
9033) -> usize {
9034    unsafe { PioCalculationInstance::get(instance) }
9035        .and_then(scuc_inputs)
9036        .map_or(0, |inputs| inputs.transformer_controls.len())
9037}
9038
9039///
9040/// # Safety
9041/// Pointers and handles must satisfy the crate-level safety requirements.
9042#[unsafe(no_mangle)]
9043pub unsafe extern "C" fn pio_ac_scuc_instance_transformer_control_at(
9044    instance: *const PioCalculationInstance,
9045    index: usize,
9046    output: *mut PioScucTransformerControlView,
9047    error: *mut *mut PioError,
9048) -> bool {
9049    unsafe {
9050        entry(error, false, || {
9051            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9052            let control = inputs.transformer_controls.get(index).ok_or_else(|| {
9053                boundary_error(
9054                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9055                    format!("SCUC transformer control index {index} is out of range"),
9056                )
9057            })?;
9058            *require_output(output, "output")? = PioScucTransformerControlView {
9059                id: component_id_view(&control.id),
9060                tap_ratio_min: control.tap_ratio_min,
9061                tap_ratio_max: control.tap_ratio_max,
9062                phase_shift_min_radians: control.phase_shift_min,
9063                phase_shift_max_radians: control.phase_shift_max,
9064            };
9065            Ok(true)
9066        })
9067    }
9068}
9069
9070///
9071/// # Safety
9072/// Pointers and handles must satisfy the crate-level safety requirements.
9073#[unsafe(no_mangle)]
9074pub unsafe extern "C" fn pio_ac_scuc_instance_active_reserve_zone_count(
9075    instance: *const PioCalculationInstance,
9076) -> usize {
9077    unsafe { PioCalculationInstance::get(instance) }
9078        .and_then(scuc_inputs)
9079        .map_or(0, |inputs| inputs.active_reserve_zones.len())
9080}
9081
9082///
9083/// # Safety
9084/// Pointers and handles must satisfy the crate-level safety requirements.
9085#[unsafe(no_mangle)]
9086pub unsafe extern "C" fn pio_ac_scuc_instance_active_reserve_zone_at(
9087    instance: *const PioCalculationInstance,
9088    index: usize,
9089    output: *mut PioScucActiveReserveZoneView,
9090    error: *mut *mut PioError,
9091) -> bool {
9092    unsafe {
9093        entry(error, false, || {
9094            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9095            let zone = inputs.active_reserve_zones.get(index).ok_or_else(|| {
9096                boundary_error(
9097                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9098                    format!("SCUC active reserve zone index {index} is out of range"),
9099                )
9100            })?;
9101            *require_output(output, "output")? = PioScucActiveReserveZoneView {
9102                id: component_id_view(&zone.id),
9103                regulation_up_requirement_fraction: zone.regulation_up_requirement_fraction,
9104                regulation_down_requirement_fraction: zone.regulation_down_requirement_fraction,
9105                synchronized_requirement_fraction: zone.synchronized_requirement_fraction,
9106                nonsynchronized_requirement_fraction: zone.nonsynchronized_requirement_fraction,
9107                regulation_up_violation_cost: zone.regulation_up_violation_cost,
9108                regulation_down_violation_cost: zone.regulation_down_violation_cost,
9109                synchronized_violation_cost: zone.synchronized_violation_cost,
9110                nonsynchronized_violation_cost: zone.nonsynchronized_violation_cost,
9111                ramping_up_violation_cost: zone.ramping_up_violation_cost,
9112                ramping_down_violation_cost: zone.ramping_down_violation_cost,
9113                period_count: zone.ramping_up_requirement.len(),
9114                bus_count: zone.buses.len(),
9115            };
9116            Ok(true)
9117        })
9118    }
9119}
9120
9121///
9122/// # Safety
9123/// Pointers and handles must satisfy the crate-level safety requirements.
9124#[unsafe(no_mangle)]
9125pub unsafe extern "C" fn pio_ac_scuc_instance_active_reserve_zone_period_at(
9126    instance: *const PioCalculationInstance,
9127    zone_index: usize,
9128    period_index: usize,
9129    output: *mut PioScucActiveReservePeriodView,
9130    error: *mut *mut PioError,
9131) -> bool {
9132    unsafe {
9133        entry(error, false, || {
9134            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9135            let zone = inputs.active_reserve_zones.get(zone_index).ok_or_else(|| {
9136                boundary_error(
9137                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9138                    format!("SCUC active reserve zone index {zone_index} is out of range"),
9139                )
9140            })?;
9141            let up = zone
9142                .ramping_up_requirement
9143                .get(period_index)
9144                .copied()
9145                .ok_or_else(|| {
9146                    boundary_error(
9147                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9148                        format!("SCUC reserve period index {period_index} is out of range"),
9149                    )
9150                })?;
9151            let down = zone
9152                .ramping_down_requirement
9153                .get(period_index)
9154                .copied()
9155                .ok_or_else(|| {
9156                    boundary_error(
9157                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9158                        format!("SCUC reserve period index {period_index} is out of range"),
9159                    )
9160                })?;
9161            *require_output(output, "output")? = PioScucActiveReservePeriodView {
9162                ramping_up_requirement_pu: up,
9163                ramping_down_requirement_pu: down,
9164            };
9165            Ok(true)
9166        })
9167    }
9168}
9169
9170///
9171/// # Safety
9172/// Pointers and handles must satisfy the crate-level safety requirements.
9173#[unsafe(no_mangle)]
9174pub unsafe extern "C" fn pio_ac_scuc_instance_active_reserve_zone_bus_at(
9175    instance: *const PioCalculationInstance,
9176    zone_index: usize,
9177    bus_index: usize,
9178    output: *mut PioComponentIdView,
9179    error: *mut *mut PioError,
9180) -> bool {
9181    unsafe {
9182        entry(error, false, || {
9183            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9184            let zone = inputs.active_reserve_zones.get(zone_index).ok_or_else(|| {
9185                boundary_error(
9186                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9187                    format!("SCUC active reserve zone index {zone_index} is out of range"),
9188                )
9189            })?;
9190            let bus = zone.buses.get(bus_index).ok_or_else(|| {
9191                boundary_error(
9192                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9193                    format!("SCUC reserve zone bus index {bus_index} is out of range"),
9194                )
9195            })?;
9196            *require_output(output, "output")? = component_id_view(bus);
9197            Ok(true)
9198        })
9199    }
9200}
9201
9202///
9203/// # Safety
9204/// Pointers and handles must satisfy the crate-level safety requirements.
9205#[unsafe(no_mangle)]
9206pub unsafe extern "C" fn pio_ac_scuc_instance_reactive_reserve_zone_count(
9207    instance: *const PioCalculationInstance,
9208) -> usize {
9209    unsafe { PioCalculationInstance::get(instance) }
9210        .and_then(scuc_inputs)
9211        .map_or(0, |inputs| inputs.reactive_reserve_zones.len())
9212}
9213
9214///
9215/// # Safety
9216/// Pointers and handles must satisfy the crate-level safety requirements.
9217#[unsafe(no_mangle)]
9218pub unsafe extern "C" fn pio_ac_scuc_instance_reactive_reserve_zone_at(
9219    instance: *const PioCalculationInstance,
9220    index: usize,
9221    output: *mut PioScucReactiveReserveZoneView,
9222    error: *mut *mut PioError,
9223) -> bool {
9224    unsafe {
9225        entry(error, false, || {
9226            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9227            let zone = inputs.reactive_reserve_zones.get(index).ok_or_else(|| {
9228                boundary_error(
9229                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9230                    format!("SCUC reactive reserve zone index {index} is out of range"),
9231                )
9232            })?;
9233            *require_output(output, "output")? = PioScucReactiveReserveZoneView {
9234                id: component_id_view(&zone.id),
9235                reactive_up_violation_cost: zone.reactive_up_violation_cost,
9236                reactive_down_violation_cost: zone.reactive_down_violation_cost,
9237                period_count: zone.reactive_up_requirement.len(),
9238                bus_count: zone.buses.len(),
9239            };
9240            Ok(true)
9241        })
9242    }
9243}
9244
9245///
9246/// # Safety
9247/// Pointers and handles must satisfy the crate-level safety requirements.
9248#[unsafe(no_mangle)]
9249pub unsafe extern "C" fn pio_ac_scuc_instance_reactive_reserve_zone_period_at(
9250    instance: *const PioCalculationInstance,
9251    zone_index: usize,
9252    period_index: usize,
9253    output: *mut PioScucReactiveReservePeriodView,
9254    error: *mut *mut PioError,
9255) -> bool {
9256    unsafe {
9257        entry(error, false, || {
9258            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9259            let zone = inputs
9260                .reactive_reserve_zones
9261                .get(zone_index)
9262                .ok_or_else(|| {
9263                    boundary_error(
9264                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9265                        format!("SCUC reactive reserve zone index {zone_index} is out of range"),
9266                    )
9267                })?;
9268            let up = zone
9269                .reactive_up_requirement
9270                .get(period_index)
9271                .copied()
9272                .ok_or_else(|| {
9273                    boundary_error(
9274                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9275                        format!("SCUC reserve period index {period_index} is out of range"),
9276                    )
9277                })?;
9278            let down = zone
9279                .reactive_down_requirement
9280                .get(period_index)
9281                .copied()
9282                .ok_or_else(|| {
9283                    boundary_error(
9284                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9285                        format!("SCUC reserve period index {period_index} is out of range"),
9286                    )
9287                })?;
9288            *require_output(output, "output")? = PioScucReactiveReservePeriodView {
9289                reactive_up_requirement_pu: up,
9290                reactive_down_requirement_pu: down,
9291            };
9292            Ok(true)
9293        })
9294    }
9295}
9296
9297///
9298/// # Safety
9299/// Pointers and handles must satisfy the crate-level safety requirements.
9300#[unsafe(no_mangle)]
9301pub unsafe extern "C" fn pio_ac_scuc_instance_reactive_reserve_zone_bus_at(
9302    instance: *const PioCalculationInstance,
9303    zone_index: usize,
9304    bus_index: usize,
9305    output: *mut PioComponentIdView,
9306    error: *mut *mut PioError,
9307) -> bool {
9308    unsafe {
9309        entry(error, false, || {
9310            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9311            let zone = inputs
9312                .reactive_reserve_zones
9313                .get(zone_index)
9314                .ok_or_else(|| {
9315                    boundary_error(
9316                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9317                        format!("SCUC reactive reserve zone index {zone_index} is out of range"),
9318                    )
9319                })?;
9320            let bus = zone.buses.get(bus_index).ok_or_else(|| {
9321                boundary_error(
9322                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9323                    format!("SCUC reserve zone bus index {bus_index} is out of range"),
9324                )
9325            })?;
9326            *require_output(output, "output")? = component_id_view(bus);
9327            Ok(true)
9328        })
9329    }
9330}
9331
9332/// Return the number of named contingencies.
9333///
9334/// # Safety
9335/// Pointers and handles must satisfy the crate-level safety requirements.
9336#[unsafe(no_mangle)]
9337pub unsafe extern "C" fn pio_ac_scuc_instance_contingency_count(
9338    instance: *const PioCalculationInstance,
9339) -> usize {
9340    unsafe { PioCalculationInstance::get(instance) }
9341        .and_then(scuc_inputs)
9342        .map_or(0, |inputs| inputs.contingencies.len())
9343}
9344
9345fn scuc_contingency_view(contingency: &powerio_prob::ScucContingency) -> PioScucContingencyView {
9346    PioScucContingencyView {
9347        id: component_id_view(&contingency.id),
9348        component_count: contingency.components.len(),
9349    }
9350}
9351
9352/// Read one named contingency in source order.
9353///
9354/// # Safety
9355/// Pointers and handles must satisfy the crate-level safety requirements.
9356#[unsafe(no_mangle)]
9357pub unsafe extern "C" fn pio_ac_scuc_instance_contingency_at(
9358    instance: *const PioCalculationInstance,
9359    contingency_index: usize,
9360    output: *mut PioScucContingencyView,
9361    error: *mut *mut PioError,
9362) -> bool {
9363    unsafe {
9364        entry(error, false, || {
9365            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9366            let contingency = inputs.contingencies.get(contingency_index).ok_or_else(|| {
9367                boundary_error(
9368                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9369                    format!("SCUC contingency index {contingency_index} is out of range"),
9370                )
9371            })?;
9372            *require_output(output, "output")? = scuc_contingency_view(contingency);
9373            Ok(true)
9374        })
9375    }
9376}
9377
9378/// Read one named contingency by its exact source UID.
9379///
9380/// # Safety
9381/// Pointers and handles must satisfy the crate-level safety requirements.
9382#[unsafe(no_mangle)]
9383pub unsafe extern "C" fn pio_ac_scuc_instance_contingency_get(
9384    instance: *const PioCalculationInstance,
9385    uid: *const c_char,
9386    uid_len: usize,
9387    output: *mut PioScucContingencyView,
9388    error: *mut *mut PioError,
9389) -> bool {
9390    unsafe {
9391        entry(error, false, || {
9392            let uid = required_str(uid, uid_len, "contingency_uid")?;
9393            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9394            let contingency = inputs.contingency(uid).ok_or_else(|| {
9395                boundary_error(
9396                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9397                    format!("SCUC contingency UID '{uid}' does not exist"),
9398                )
9399            })?;
9400            *require_output(output, "output")? = scuc_contingency_view(contingency);
9401            Ok(true)
9402        })
9403    }
9404}
9405
9406/// Read one stable component identity from a named contingency.
9407///
9408/// # Safety
9409/// Pointers and handles must satisfy the crate-level safety requirements.
9410#[unsafe(no_mangle)]
9411pub unsafe extern "C" fn pio_ac_scuc_instance_contingency_component_at(
9412    instance: *const PioCalculationInstance,
9413    contingency_index: usize,
9414    component_index: usize,
9415    output: *mut PioScucContingencyComponentView,
9416    error: *mut *mut PioError,
9417) -> bool {
9418    unsafe {
9419        entry(error, false, || {
9420            let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9421            let contingency = inputs.contingencies.get(contingency_index).ok_or_else(|| {
9422                boundary_error(
9423                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9424                    format!("SCUC contingency index {contingency_index} is out of range"),
9425                )
9426            })?;
9427            let component = contingency.components.get(component_index).ok_or_else(|| {
9428                boundary_error(
9429                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9430                    format!(
9431                        "SCUC contingency {contingency_index} component index {component_index} is out of range"
9432                    ),
9433                )
9434            })?;
9435            *require_output(output, "output")? = PioScucContingencyComponentView {
9436                id: component_id_view(component),
9437            };
9438            Ok(true)
9439        })
9440    }
9441}
9442
9443///
9444/// # Safety
9445/// Pointers and handles must satisfy the crate-level safety requirements.
9446#[unsafe(no_mangle)]
9447pub unsafe extern "C" fn pio_calculation_solution_balanced_network(
9448    solution: *const PioCalculationSolution,
9449    error: *mut *mut PioError,
9450) -> *mut PioBalancedNetwork {
9451    unsafe {
9452        entry(error, std::ptr::null_mut(), || {
9453            let solution = PioCalculationSolution::get(solution).ok_or_else(|| {
9454                boundary_error(
9455                    &codes::BIND_CAPI_NULL_HANDLE,
9456                    "PioCalculationSolution must not be NULL",
9457                )
9458            })?;
9459            let projection = match solution.value() {
9460                Some(PioValue::DcPfSolution(_)) => BalancedNetworkProjection::DcPfSolution,
9461                Some(PioValue::AcPfSolution(_)) => BalancedNetworkProjection::AcPfSolution,
9462                Some(PioValue::DcOpfSolution(_)) => BalancedNetworkProjection::DcOpfSolution,
9463                Some(PioValue::AcOpfSolution(_)) => BalancedNetworkProjection::AcOpfSolution,
9464                Some(PioValue::SocwrOpfSolution(_)) => BalancedNetworkProjection::SocwrOpfSolution,
9465                Some(PioValue::AcScucSolution(_)) => BalancedNetworkProjection::AcScucSolution,
9466                _ => {
9467                    return Err(boundary_error(
9468                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
9469                        "the calculation solution does not use powerio.BalancedNetwork",
9470                    ));
9471                }
9472            };
9473            Ok(make_balanced_network_view(solution, projection))
9474        })
9475    }
9476}
9477
9478///
9479/// # Safety
9480/// Pointers and handles must satisfy the crate-level safety requirements.
9481#[unsafe(no_mangle)]
9482pub unsafe extern "C" fn pio_calculation_solution_multiconductor_network(
9483    solution: *const PioCalculationSolution,
9484    error: *mut *mut PioError,
9485) -> *mut PioMulticonductorNetwork {
9486    unsafe {
9487        entry(error, std::ptr::null_mut(), || {
9488            let solution = PioCalculationSolution::get(solution).ok_or_else(|| {
9489                boundary_error(
9490                    &codes::BIND_CAPI_NULL_HANDLE,
9491                    "PioCalculationSolution must not be NULL",
9492                )
9493            })?;
9494            let projection = match solution.value() {
9495                Some(PioValue::McAcPfSolution(_)) => {
9496                    MulticonductorNetworkProjection::McAcPfSolution
9497                }
9498                Some(PioValue::McAcOpfSolution(_)) => {
9499                    MulticonductorNetworkProjection::McAcOpfSolution
9500                }
9501                Some(PioValue::LinDist3FlowOpfSolution(_)) => {
9502                    MulticonductorNetworkProjection::LinDist3FlowOpfSolution
9503                }
9504                _ => {
9505                    return Err(boundary_error(
9506                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
9507                        "the calculation solution does not use powerio.MulticonductorNetwork",
9508                    ));
9509                }
9510            };
9511            Ok(make_multiconductor_network_view(solution, projection))
9512        })
9513    }
9514}
9515
9516/// Read one operating point quantity by its PowerIO quantity name and stable
9517/// component identity. Multiconductor terminal identities use
9518/// component/terminal. Returns false when the point does not contain the
9519/// quantity or identity.
9520///
9521/// # Safety
9522/// Pointers and handles must satisfy the crate-level safety requirements.
9523#[unsafe(no_mangle)]
9524pub unsafe extern "C" fn pio_operating_point_get_value(
9525    point: *const PioOperatingPoint,
9526    quantity: *const c_char,
9527    quantity_len: usize,
9528    identity: *const c_char,
9529    identity_len: usize,
9530    out_value: *mut f64,
9531    error: *mut *mut PioError,
9532) -> bool {
9533    unsafe {
9534        entry(error, false, || {
9535            if out_value.is_null() {
9536                return Err(boundary_error(
9537                    &codes::BIND_CAPI_NULL_ARGUMENT,
9538                    "out_value must not be NULL",
9539                ));
9540            }
9541            let quantity = required_str(quantity, quantity_len, "quantity")?;
9542            let identity = required_str(identity, identity_len, "identity")?;
9543            let point = PioOperatingPoint::get(point).ok_or_else(|| {
9544                boundary_error(
9545                    &codes::BIND_CAPI_NULL_HANDLE,
9546                    "PioOperatingPoint must not be NULL",
9547                )
9548            })?;
9549            let value = if let Some(point) = point.balanced() {
9550                use powerio_prob::{
9551                    BalancedOperatingPointFlag as F, BalancedOperatingPointQuantity as Q,
9552                };
9553                match quantity {
9554                    "bus_voltage_magnitude" => point.values(Q::BusVoltageMagnitude),
9555                    "bus_voltage_angle" => point.values(Q::BusVoltageAngle),
9556                    "bus_active_injection" => point.values(Q::BusActiveInjection),
9557                    "bus_reactive_injection" => point.values(Q::BusReactiveInjection),
9558                    "generator_active_power" => point.values(Q::GeneratorActivePower),
9559                    "generator_reactive_power" => point.values(Q::GeneratorReactivePower),
9560                    "generator_voltage_setpoint" => point.values(Q::GeneratorVoltageSetpoint),
9561                    "load_active_power" => point.values(Q::LoadActivePower),
9562                    "load_reactive_power" => point.values(Q::LoadReactivePower),
9563                    "branch_tap_ratio" => point.values(Q::BranchTapRatio),
9564                    "branch_phase_shift" => point.values(Q::BranchPhaseShift),
9565                    "generator_in_service" => {
9566                        let found = point
9567                            .flags(F::GeneratorInService)
9568                            .and_then(|mut values| values.find(|(key, _)| *key == identity))
9569                            .map(|(_, value)| if value { 1.0 } else { 0.0 });
9570                        if let Some(found) = found {
9571                            *out_value = found;
9572                            return Ok(true);
9573                        }
9574                        return Ok(false);
9575                    }
9576                    "branch_in_service" => {
9577                        let found = point
9578                            .flags(F::BranchInService)
9579                            .and_then(|mut values| values.find(|(key, _)| *key == identity))
9580                            .map(|(_, value)| if value { 1.0 } else { 0.0 });
9581                        if let Some(found) = found {
9582                            *out_value = found;
9583                            return Ok(true);
9584                        }
9585                        return Ok(false);
9586                    }
9587                    "switch_closed" => {
9588                        let found = point
9589                            .flags(F::SwitchClosed)
9590                            .and_then(|mut values| values.find(|(key, _)| *key == identity))
9591                            .map(|(_, value)| if value { 1.0 } else { 0.0 });
9592                        if let Some(found) = found {
9593                            *out_value = found;
9594                            return Ok(true);
9595                        }
9596                        return Ok(false);
9597                    }
9598                    _ => {
9599                        return Err(boundary_error(
9600                            &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
9601                            format!("unknown balanced operating point quantity '{quantity}'"),
9602                        ));
9603                    }
9604                }
9605                .and_then(|mut values| values.find(|(key, _)| *key == identity))
9606                .map(|(_, value)| value)
9607            } else if let Some(point) = point.multiconductor() {
9608                use powerio_prob::{
9609                    MulticonductorOperatingPointFlag as F,
9610                    MulticonductorOperatingPointQuantity as Q,
9611                };
9612                match quantity {
9613                    "terminal_voltage_magnitude" => point.values(Q::TerminalVoltageMagnitude),
9614                    "terminal_voltage_angle" => point.values(Q::TerminalVoltageAngle),
9615                    "load_active_power" => point.values(Q::LoadActivePower),
9616                    "load_reactive_power" => point.values(Q::LoadReactivePower),
9617                    "generator_active_power" => point.values(Q::GeneratorActivePower),
9618                    "generator_reactive_power" => point.values(Q::GeneratorReactivePower),
9619                    "transformer_tap" => point.values(Q::TransformerTap),
9620                    "capacitor_steps" => point.values(Q::CapacitorSteps),
9621                    "switch_closed" => {
9622                        let found = point
9623                            .flags(F::SwitchClosed)
9624                            .and_then(|mut values| values.find(|(key, _)| *key == identity))
9625                            .map(|(_, value)| if value { 1.0 } else { 0.0 });
9626                        if let Some(found) = found {
9627                            *out_value = found;
9628                            return Ok(true);
9629                        }
9630                        return Ok(false);
9631                    }
9632                    _ => {
9633                        return Err(boundary_error(
9634                            &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
9635                            format!("unknown multiconductor operating point quantity '{quantity}'"),
9636                        ));
9637                    }
9638                }
9639                .and_then(|mut values| values.find(|(key, _)| *key == identity))
9640                .map(|(_, value)| value)
9641            } else {
9642                return Err(boundary_error(
9643                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
9644                    "the handle does not refer to an operating point",
9645                ));
9646            };
9647            if let Some(value) = value {
9648                *out_value = value;
9649                Ok(true)
9650            } else {
9651                Ok(false)
9652            }
9653        })
9654    }
9655}
9656
9657///
9658/// # Safety
9659/// Pointers and handles must satisfy the crate-level safety requirements.
9660/// The input handle must remain live and immutable throughout this call.
9661#[unsafe(no_mangle)]
9662pub unsafe extern "C" fn pio_operating_point_retain(
9663    point: *const PioOperatingPoint,
9664) -> *mut PioOperatingPoint {
9665    unsafe { PioOperatingPoint::retain_raw(point) }
9666}
9667
9668///
9669/// # Safety
9670/// Pointers and handles must satisfy the crate-level safety requirements.
9671/// A non-null handle must be owned and unused by concurrent calls.
9672#[unsafe(no_mangle)]
9673pub unsafe extern "C" fn pio_operating_point_release(point: *mut PioOperatingPoint) {
9674    unsafe { PioOperatingPoint::release_raw(point) };
9675}
9676
9677///
9678/// # Safety
9679/// Pointers and handles must satisfy the crate-level safety requirements.
9680/// The input handle must remain live and immutable throughout this call.
9681#[unsafe(no_mangle)]
9682pub unsafe extern "C" fn pio_calculation_instance_retain(
9683    instance: *const PioCalculationInstance,
9684) -> *mut PioCalculationInstance {
9685    unsafe { PioCalculationInstance::retain_raw(instance) }
9686}
9687
9688///
9689/// # Safety
9690/// Pointers and handles must satisfy the crate-level safety requirements.
9691/// A non-null handle must be owned and unused by concurrent calls.
9692#[unsafe(no_mangle)]
9693pub unsafe extern "C" fn pio_calculation_instance_release(instance: *mut PioCalculationInstance) {
9694    unsafe { PioCalculationInstance::release_raw(instance) };
9695}
9696
9697///
9698/// # Safety
9699/// Pointers and handles must satisfy the crate-level safety requirements.
9700/// The input handle must remain live and immutable throughout this call.
9701#[unsafe(no_mangle)]
9702pub unsafe extern "C" fn pio_dc_opf_preparation_retain(
9703    preparation: *const PioDcOpfPreparation,
9704) -> *mut PioDcOpfPreparation {
9705    unsafe { PioDcOpfPreparation::retain_raw(preparation) }
9706}
9707
9708///
9709/// # Safety
9710/// Pointers and handles must satisfy the crate-level safety requirements.
9711/// A non-null handle must be owned and unused by concurrent calls.
9712#[unsafe(no_mangle)]
9713pub unsafe extern "C" fn pio_dc_opf_preparation_release(preparation: *mut PioDcOpfPreparation) {
9714    unsafe { PioDcOpfPreparation::release_raw(preparation) };
9715}
9716
9717///
9718/// # Safety
9719/// Pointers and handles must satisfy the crate-level safety requirements.
9720/// The input handle must remain live and immutable throughout this call.
9721#[unsafe(no_mangle)]
9722pub unsafe extern "C" fn pio_ac_opf_preparation_retain(
9723    preparation: *const PioAcOpfPreparation,
9724) -> *mut PioAcOpfPreparation {
9725    unsafe { PioAcOpfPreparation::retain_raw(preparation) }
9726}
9727
9728///
9729/// # Safety
9730/// Pointers and handles must satisfy the crate-level safety requirements.
9731/// A non-null handle must be owned and unused by concurrent calls.
9732#[unsafe(no_mangle)]
9733pub unsafe extern "C" fn pio_ac_opf_preparation_release(preparation: *mut PioAcOpfPreparation) {
9734    unsafe { PioAcOpfPreparation::release_raw(preparation) };
9735}
9736
9737///
9738/// # Safety
9739/// Pointers and handles must satisfy the crate-level safety requirements.
9740/// The input handle must remain live and immutable throughout this call.
9741#[unsafe(no_mangle)]
9742pub unsafe extern "C" fn pio_calculation_solution_retain(
9743    solution: *const PioCalculationSolution,
9744) -> *mut PioCalculationSolution {
9745    unsafe { PioCalculationSolution::retain_raw(solution) }
9746}
9747
9748///
9749/// # Safety
9750/// Pointers and handles must satisfy the crate-level safety requirements.
9751/// A non-null handle must be owned and unused by concurrent calls.
9752#[unsafe(no_mangle)]
9753pub unsafe extern "C" fn pio_calculation_solution_release(solution: *mut PioCalculationSolution) {
9754    unsafe { PioCalculationSolution::release_raw(solution) };
9755}
9756
9757///
9758/// # Safety
9759/// Pointers and handles must satisfy the crate-level safety requirements.
9760/// The input handle must remain live and immutable throughout this call.
9761#[unsafe(no_mangle)]
9762pub unsafe extern "C" fn pio_value_retain(value: *const PioValueHandle) -> *mut PioValueHandle {
9763    unsafe { PioValueHandle::retain_raw(value) }
9764}
9765
9766///
9767/// # Safety
9768/// Pointers and handles must satisfy the crate-level safety requirements.
9769/// A non-null handle must be owned and unused by concurrent calls.
9770#[unsafe(no_mangle)]
9771pub unsafe extern "C" fn pio_value_release(value: *mut PioValueHandle) {
9772    unsafe { PioValueHandle::release_raw(value) };
9773}
9774
9775// ---- collections -----------------------------------------------------------
9776
9777fn time_series(value: &ValueInner) -> Option<&PioTimeSeries> {
9778    match value.value()? {
9779        PioValue::TimeSeries(series) => Some(series),
9780        _ => None,
9781    }
9782}
9783
9784fn scenario_set(value: &ValueInner) -> Option<&PioScenarioSet> {
9785    match value.value()? {
9786        PioValue::ScenarioSet(scenarios) => Some(scenarios),
9787        _ => None,
9788    }
9789}
9790
9791///
9792/// # Safety
9793/// Pointers and handles must satisfy the crate-level safety requirements.
9794#[unsafe(no_mangle)]
9795pub unsafe extern "C" fn pio_time_series_len(series: *const PioTimeSeriesHandle) -> usize {
9796    unsafe { PioTimeSeriesHandle::get(series) }
9797        .and_then(time_series)
9798        .map_or(0, PioTimeSeries::len)
9799}
9800
9801///
9802/// # Safety
9803/// Pointers and handles must satisfy the crate-level safety requirements.
9804#[unsafe(no_mangle)]
9805pub unsafe extern "C" fn pio_time_series_element_type(
9806    series: *const PioTimeSeriesHandle,
9807) -> PioStringView {
9808    unsafe { PioTimeSeriesHandle::get(series) }
9809        .and_then(time_series)
9810        .map_or(PioStringView::EMPTY, |series| {
9811            PioStringView::new(series.element_type())
9812        })
9813}
9814
9815/// Return an owner-rooted entry by zero-based position.
9816///
9817/// # Safety
9818/// Pointers and handles must satisfy the crate-level safety requirements.
9819#[unsafe(no_mangle)]
9820pub unsafe extern "C" fn pio_time_series_get(
9821    series: *const PioTimeSeriesHandle,
9822    index: usize,
9823    error: *mut *mut PioError,
9824) -> *mut PioValueHandle {
9825    unsafe {
9826        entry(error, std::ptr::null_mut(), || {
9827            let series = PioTimeSeriesHandle::get(series).ok_or_else(|| {
9828                boundary_error(
9829                    &codes::BIND_CAPI_NULL_HANDLE,
9830                    "PioTimeSeriesHandle must not be NULL",
9831                )
9832            })?;
9833            let values = time_series(series).ok_or_else(|| {
9834                boundary_error(
9835                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
9836                    "the handle does not refer to a time series",
9837                )
9838            })?;
9839            if values.get(index).is_none() {
9840                return Err(boundary_error(
9841                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9842                    format!("time series index {index} is out of range"),
9843                ));
9844            }
9845            Ok(value_handle(series.child(ValueStep::TimeSeries(index))))
9846        })
9847    }
9848}
9849
9850///
9851/// # Safety
9852/// Pointers and handles must satisfy the crate-level safety requirements.
9853/// The input handle must remain live and immutable throughout this call.
9854#[unsafe(no_mangle)]
9855pub unsafe extern "C" fn pio_time_series_retain(
9856    series: *const PioTimeSeriesHandle,
9857) -> *mut PioTimeSeriesHandle {
9858    unsafe { PioTimeSeriesHandle::retain_raw(series) }
9859}
9860
9861///
9862/// # Safety
9863/// Pointers and handles must satisfy the crate-level safety requirements.
9864/// A non-null handle must be owned and unused by concurrent calls.
9865#[unsafe(no_mangle)]
9866pub unsafe extern "C" fn pio_time_series_release(series: *mut PioTimeSeriesHandle) {
9867    unsafe { PioTimeSeriesHandle::release_raw(series) };
9868}
9869
9870///
9871/// # Safety
9872/// Pointers and handles must satisfy the crate-level safety requirements.
9873#[unsafe(no_mangle)]
9874pub unsafe extern "C" fn pio_scenario_set_len(set: *const PioScenarioSetHandle) -> usize {
9875    unsafe { PioScenarioSetHandle::get(set) }
9876        .and_then(scenario_set)
9877        .map_or(0, PioScenarioSet::len)
9878}
9879
9880///
9881/// # Safety
9882/// Pointers and handles must satisfy the crate-level safety requirements.
9883#[unsafe(no_mangle)]
9884pub unsafe extern "C" fn pio_scenario_set_element_type(
9885    set: *const PioScenarioSetHandle,
9886) -> PioStringView {
9887    unsafe { PioScenarioSetHandle::get(set) }
9888        .and_then(scenario_set)
9889        .map_or(PioStringView::EMPTY, |set| {
9890            PioStringView::new(set.element_type())
9891        })
9892}
9893
9894///
9895/// # Safety
9896/// Pointers and handles must satisfy the crate-level safety requirements.
9897#[unsafe(no_mangle)]
9898pub unsafe extern "C" fn pio_scenario_set_id_at(
9899    set: *const PioScenarioSetHandle,
9900    index: usize,
9901) -> PioStringView {
9902    unsafe { PioScenarioSetHandle::get(set) }
9903        .and_then(scenario_set)
9904        .and_then(|set| set.iter().nth(index))
9905        .map_or(PioStringView::EMPTY, |scenario| {
9906            PioStringView::new(scenario.id().as_str())
9907        })
9908}
9909
9910/// Return an owner-rooted scenario value by zero-based position.
9911///
9912/// # Safety
9913/// Pointers and handles must satisfy the crate-level safety requirements.
9914#[unsafe(no_mangle)]
9915pub unsafe extern "C" fn pio_scenario_set_get_at(
9916    set: *const PioScenarioSetHandle,
9917    index: usize,
9918    error: *mut *mut PioError,
9919) -> *mut PioValueHandle {
9920    unsafe {
9921        entry(error, std::ptr::null_mut(), || {
9922            let set = PioScenarioSetHandle::get(set).ok_or_else(|| {
9923                boundary_error(
9924                    &codes::BIND_CAPI_NULL_HANDLE,
9925                    "PioScenarioSetHandle must not be NULL",
9926                )
9927            })?;
9928            let values = scenario_set(set).ok_or_else(|| {
9929                boundary_error(
9930                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
9931                    "the handle does not refer to a scenario set",
9932                )
9933            })?;
9934            if values.get_at(index).is_none() {
9935                return Err(boundary_error(
9936                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9937                    format!("scenario position {index} is out of range"),
9938                ));
9939            }
9940            Ok(value_handle(set.child(ValueStep::Scenario(index))))
9941        })
9942    }
9943}
9944
9945/// Return an owner-rooted scenario value by exact scenario ID.
9946///
9947/// # Safety
9948/// Pointers and handles must satisfy the crate-level safety requirements.
9949#[unsafe(no_mangle)]
9950pub unsafe extern "C" fn pio_scenario_set_get(
9951    set: *const PioScenarioSetHandle,
9952    id: *const c_char,
9953    id_len: usize,
9954    error: *mut *mut PioError,
9955) -> *mut PioValueHandle {
9956    unsafe {
9957        entry(error, std::ptr::null_mut(), || {
9958            let id = required_str(id, id_len, "scenario_id")?;
9959            let set = PioScenarioSetHandle::get(set).ok_or_else(|| {
9960                boundary_error(
9961                    &codes::BIND_CAPI_NULL_HANDLE,
9962                    "PioScenarioSetHandle must not be NULL",
9963                )
9964            })?;
9965            let values = scenario_set(set).ok_or_else(|| {
9966                boundary_error(
9967                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
9968                    "the handle does not refer to a scenario set",
9969                )
9970            })?;
9971            let Some(position) = values
9972                .iter()
9973                .position(|scenario| scenario.id().as_str() == id)
9974            else {
9975                return Err(boundary_error(
9976                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9977                    format!("scenario ID '{id}' does not exist"),
9978                ));
9979            };
9980            Ok(value_handle(set.child(ValueStep::Scenario(position))))
9981        })
9982    }
9983}
9984
9985///
9986/// # Safety
9987/// Pointers and handles must satisfy the crate-level safety requirements.
9988/// The input handle must remain live and immutable throughout this call.
9989#[unsafe(no_mangle)]
9990pub unsafe extern "C" fn pio_scenario_set_retain(
9991    set: *const PioScenarioSetHandle,
9992) -> *mut PioScenarioSetHandle {
9993    unsafe { PioScenarioSetHandle::retain_raw(set) }
9994}
9995
9996///
9997/// # Safety
9998/// Pointers and handles must satisfy the crate-level safety requirements.
9999/// A non-null handle must be owned and unused by concurrent calls.
10000#[unsafe(no_mangle)]
10001pub unsafe extern "C" fn pio_scenario_set_release(set: *mut PioScenarioSetHandle) {
10002    unsafe { PioScenarioSetHandle::release_raw(set) };
10003}
10004
10005// ---- network access --------------------------------------------------------
10006
10007///
10008/// # Safety
10009/// Pointers and handles must satisfy the crate-level safety requirements.
10010#[unsafe(no_mangle)]
10011pub unsafe extern "C" fn pio_balanced_network_name(
10012    network: *const PioBalancedNetwork,
10013) -> PioStringView {
10014    unsafe { PioBalancedNetwork::get(network) }
10015        .and_then(BalancedNetworkInner::network)
10016        .map_or(PioStringView::EMPTY, |network| {
10017            PioStringView::new(network.name())
10018        })
10019}
10020
10021///
10022/// # Safety
10023/// Pointers and handles must satisfy the crate-level safety requirements.
10024#[unsafe(no_mangle)]
10025pub unsafe extern "C" fn pio_balanced_network_base_mva(network: *const PioBalancedNetwork) -> f64 {
10026    unsafe { PioBalancedNetwork::get(network) }
10027        .and_then(BalancedNetworkInner::network)
10028        .map_or(f64::NAN, BalancedNetwork::base_mva)
10029}
10030
10031///
10032/// # Safety
10033/// Pointers and handles must satisfy the crate-level safety requirements.
10034#[unsafe(no_mangle)]
10035pub unsafe extern "C" fn pio_balanced_network_base_frequency_hz(
10036    network: *const PioBalancedNetwork,
10037) -> f64 {
10038    unsafe { PioBalancedNetwork::get(network) }
10039        .and_then(BalancedNetworkInner::network)
10040        .map_or(f64::NAN, BalancedNetwork::base_frequency)
10041}
10042
10043/// Read the optional coordinate space metadata for a balanced network.
10044///
10045/// # Safety
10046/// Pointers and handles must satisfy the crate-level safety requirements.
10047#[unsafe(no_mangle)]
10048pub unsafe extern "C" fn pio_balanced_network_geo(
10049    network: *const PioBalancedNetwork,
10050    output: *mut PioBalancedGeoView,
10051    error: *mut *mut PioError,
10052) -> bool {
10053    unsafe {
10054        entry(error, false, || {
10055            let network = require_balanced_network(network)?;
10056            *require_output(output, "output")? = balanced_geo_view(network.geo().as_ref());
10057            Ok(true)
10058        })
10059    }
10060}
10061
10062///
10063/// # Safety
10064/// Pointers and handles must satisfy the crate-level safety requirements.
10065#[unsafe(no_mangle)]
10066pub unsafe extern "C" fn pio_balanced_network_has_detailed_connectivity(
10067    network: *const PioBalancedNetwork,
10068) -> bool {
10069    unsafe { PioBalancedNetwork::get(network) }
10070        .and_then(BalancedNetworkInner::network)
10071        .is_some_and(|network| network.detailed_connectivity().is_some())
10072}
10073
10074/// Return the optional owner-rooted detailed connectivity view.
10075///
10076/// # Safety
10077/// Pointers and handles must satisfy the crate-level safety requirements.
10078#[unsafe(no_mangle)]
10079pub unsafe extern "C" fn pio_balanced_network_detailed_connectivity(
10080    network: *const PioBalancedNetwork,
10081) -> *mut PioDetailedConnectivity {
10082    let Some(owner) = (unsafe { PioBalancedNetwork::arc(network) }) else {
10083        return std::ptr::null_mut();
10084    };
10085    if owner
10086        .network()
10087        .is_none_or(|network| network.detailed_connectivity().is_none())
10088    {
10089        return std::ptr::null_mut();
10090    }
10091    PioDetailedConnectivity::new_raw(DetailedConnectivityInner { owner })
10092}
10093
10094/// Read every detailed connectivity table length.
10095///
10096/// # Safety
10097/// Pointers and handles must satisfy the crate-level safety requirements.
10098#[unsafe(no_mangle)]
10099pub unsafe extern "C" fn pio_detailed_connectivity_counts(
10100    details: *const PioDetailedConnectivity,
10101    output: *mut PioDetailedConnectivityCountsView,
10102    error: *mut *mut PioError,
10103) -> bool {
10104    unsafe {
10105        entry(error, false, || {
10106            let details = require_detailed_connectivity(details)?;
10107            *require_output(output, "output")? = PioDetailedConnectivityCountsView {
10108                omitted_fields: details.omitted_fields.len(),
10109                component_metadata: details.component_metadata.len(),
10110                subnetworks: details.subnetworks.len(),
10111                substations: details.substations.len(),
10112                voltage_levels: details.voltage_levels.len(),
10113                bus_breaker_buses: details.bus_breaker_buses.len(),
10114                calculated_buses: details.calculated_buses.len(),
10115                connectivity_nodes: details.connectivity_nodes.len(),
10116                busbar_sections: details.busbar_sections.len(),
10117                junctions: details.junctions.len(),
10118                terminals: details.terminals.len(),
10119                switches: details.switches.len(),
10120                internal_connections: details.internal_connections.len(),
10121                operational_limit_groups: details.operational_limit_groups.len(),
10122                tap_changers: details.tap_changers.len(),
10123                equipment_reactive_limits: details.equipment_reactive_limits.len(),
10124                boundary_lines: details.boundary_lines.len(),
10125                tie_lines: details.tie_lines.len(),
10126                dc_converter_units: details.dc_converter_units.len(),
10127                dc_topological_nodes: details.dc_topological_nodes.len(),
10128                dc_nodes: details.dc_nodes.len(),
10129                dc_grounds: details.dc_grounds.len(),
10130                dc_busbars: details.dc_busbars.len(),
10131                dc_lines: details.dc_lines.len(),
10132                dc_series_devices: details.dc_series_devices.len(),
10133                dc_switches: details.dc_switches.len(),
10134                voltage_source_converters: details.voltage_source_converters.len(),
10135                line_commutated_converters: details.line_commutated_converters.len(),
10136            };
10137            Ok(true)
10138        })
10139    }
10140}
10141
10142fn omitted_field_name(value: powerio_tx::OmittedFieldName) -> &'static str {
10143    match value {
10144        powerio_tx::OmittedFieldName::ActivePower => "active_power",
10145        powerio_tx::OmittedFieldName::ReactivePower => "reactive_power",
10146        powerio_tx::OmittedFieldName::VoltageSetpoint => "voltage_setpoint",
10147        powerio_tx::OmittedFieldName::RatedApparentPower => "rated_apparent_power",
10148        powerio_tx::OmittedFieldName::ShuntConductancePerSection => "shunt_conductance_per_section",
10149        _ => "unknown",
10150    }
10151}
10152
10153fn topology_kind_name(kind: powerio_tx::TopologyKind) -> &'static str {
10154    match kind {
10155        powerio_tx::TopologyKind::BusBreaker => "bus_breaker",
10156        powerio_tx::TopologyKind::NodeBreaker => "node_breaker",
10157        _ => "unknown",
10158    }
10159}
10160
10161fn topology_switch_kind_name(kind: powerio_tx::SwitchKind) -> &'static str {
10162    match kind {
10163        powerio_tx::SwitchKind::Breaker => "breaker",
10164        powerio_tx::SwitchKind::Disconnector => "disconnector",
10165        powerio_tx::SwitchKind::LoadBreakSwitch => "load_break_switch",
10166        _ => "unknown",
10167    }
10168}
10169
10170fn curve_style_name(style: powerio_tx::CurveStyle) -> &'static str {
10171    match style {
10172        powerio_tx::CurveStyle::ConstantYValue => "constant_y_value",
10173        powerio_tx::CurveStyle::StraightLineYValues => "straight_line_y_values",
10174        _ => "unknown",
10175    }
10176}
10177
10178fn empty_reactive_limits_view() -> PioReactiveLimitsView {
10179    PioReactiveLimitsView {
10180        kind: PioStringView::EMPTY,
10181        minimum_reactive_power_mvar: 0.0,
10182        maximum_reactive_power_mvar: 0.0,
10183        has_minimum_and_maximum: false,
10184        curve_style: PioStringView::EMPTY,
10185        has_curve_style: false,
10186        property_count: 0,
10187        point_count: 0,
10188    }
10189}
10190
10191fn reactive_limits_view(
10192    value: Option<&powerio_tx::ReactiveLimits>,
10193) -> (PioReactiveLimitsView, bool) {
10194    match value {
10195        Some(powerio_tx::ReactiveLimits::MinMax(limits)) => (
10196            PioReactiveLimitsView {
10197                kind: PioStringView::new("min_max"),
10198                minimum_reactive_power_mvar: limits.minimum_reactive_power_mvar,
10199                maximum_reactive_power_mvar: limits.maximum_reactive_power_mvar,
10200                has_minimum_and_maximum: true,
10201                curve_style: PioStringView::EMPTY,
10202                has_curve_style: false,
10203                property_count: limits.properties.len(),
10204                point_count: 0,
10205            },
10206            true,
10207        ),
10208        Some(powerio_tx::ReactiveLimits::CapabilityCurve(curve)) => (
10209            PioReactiveLimitsView {
10210                kind: PioStringView::new("capability_curve"),
10211                minimum_reactive_power_mvar: 0.0,
10212                maximum_reactive_power_mvar: 0.0,
10213                has_minimum_and_maximum: false,
10214                curve_style: PioStringView::new(curve_style_name(curve.curve_style)),
10215                has_curve_style: true,
10216                property_count: curve.properties.len(),
10217                point_count: curve.points.len(),
10218            },
10219            true,
10220        ),
10221        Some(_) => (empty_reactive_limits_view(), true),
10222        None => (empty_reactive_limits_view(), false),
10223    }
10224}
10225
10226fn reactive_limit_properties(
10227    limits: &powerio_tx::ReactiveLimits,
10228) -> Result<&std::collections::BTreeMap<String, String>, *mut PioError> {
10229    Ok(match limits {
10230        powerio_tx::ReactiveLimits::MinMax(limits) => &limits.properties,
10231        powerio_tx::ReactiveLimits::CapabilityCurve(curve) => &curve.properties,
10232        _ => {
10233            return Err(boundary_error(
10234                &codes::REQUEST_CAPI_TYPE_MISMATCH,
10235                "unsupported reactive limit form",
10236            ));
10237        }
10238    })
10239}
10240
10241fn reactive_capability_curve(
10242    limits: &powerio_tx::ReactiveLimits,
10243) -> Option<&powerio_tx::ReactiveCapabilityCurve> {
10244    match limits {
10245        powerio_tx::ReactiveLimits::CapabilityCurve(curve) => Some(curve),
10246        _ => None,
10247    }
10248}
10249
10250fn string_property_view(
10251    properties: &std::collections::BTreeMap<String, String>,
10252    index: usize,
10253) -> Result<PioStringPropertyView, *mut PioError> {
10254    let (name, value) = properties.iter().nth(index).ok_or_else(|| {
10255        boundary_error(
10256            &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10257            format!("property index {index} is out of range"),
10258        )
10259    })?;
10260    Ok(PioStringPropertyView {
10261        name: PioStringView::new(name),
10262        value: PioStringView::new(value),
10263    })
10264}
10265
10266fn reactive_capability_point_view(
10267    curve: &powerio_tx::ReactiveCapabilityCurve,
10268    index: usize,
10269) -> Result<PioReactiveCapabilityCurvePointView, *mut PioError> {
10270    let point = curve.points.get(index).ok_or_else(|| {
10271        boundary_error(
10272            &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10273            format!("reactive capability point index {index} is out of range"),
10274        )
10275    })?;
10276    Ok(PioReactiveCapabilityCurvePointView {
10277        active_power_mw: point.active_power_mw,
10278        minimum_reactive_power_mvar: point.minimum_reactive_power_mvar,
10279        maximum_reactive_power_mvar: point.maximum_reactive_power_mvar,
10280        property_count: point.properties.len(),
10281    })
10282}
10283
10284fn empty_boundary_line_generation_view() -> PioBoundaryLineGenerationView {
10285    PioBoundaryLineGenerationView {
10286        voltage_regulation_on: false,
10287        minimum_active_power_mw: 0.0,
10288        has_minimum_active_power: false,
10289        maximum_active_power_mw: 0.0,
10290        has_maximum_active_power: false,
10291        target_active_power_mw: 0.0,
10292        has_target_active_power: false,
10293        target_reactive_power_mvar: 0.0,
10294        has_target_reactive_power: false,
10295        target_voltage_kv: 0.0,
10296        has_target_voltage: false,
10297        reactive_limits: empty_reactive_limits_view(),
10298        has_reactive_limits: false,
10299    }
10300}
10301
10302fn boundary_line_generation_view(
10303    value: Option<&powerio_tx::BoundaryLineGeneration>,
10304) -> (PioBoundaryLineGenerationView, bool) {
10305    let Some(value) = value else {
10306        return (empty_boundary_line_generation_view(), false);
10307    };
10308    let (reactive_limits, has_reactive_limits) =
10309        reactive_limits_view(value.reactive_limits.as_ref());
10310    (
10311        PioBoundaryLineGenerationView {
10312            voltage_regulation_on: value.voltage_regulation_on,
10313            minimum_active_power_mw: value.minimum_active_power_mw.unwrap_or(0.0),
10314            has_minimum_active_power: value.minimum_active_power_mw.is_some(),
10315            maximum_active_power_mw: value.maximum_active_power_mw.unwrap_or(0.0),
10316            has_maximum_active_power: value.maximum_active_power_mw.is_some(),
10317            target_active_power_mw: value.target_active_power_mw.unwrap_or(0.0),
10318            has_target_active_power: value.target_active_power_mw.is_some(),
10319            target_reactive_power_mvar: value.target_reactive_power_mvar.unwrap_or(0.0),
10320            has_target_reactive_power: value.target_reactive_power_mvar.is_some(),
10321            target_voltage_kv: value.target_voltage_kv.unwrap_or(0.0),
10322            has_target_voltage: value.target_voltage_kv.is_some(),
10323            reactive_limits,
10324            has_reactive_limits,
10325        },
10326        true,
10327    )
10328}
10329
10330fn topology_endpoint_view(
10331    endpoint: &powerio_tx::TopologyEndpoint,
10332) -> (PioStringView, PioComponentIdView) {
10333    match endpoint {
10334        powerio_tx::TopologyEndpoint::Bus(component) => {
10335            (PioStringView::new("bus"), component_id_view(component))
10336        }
10337        powerio_tx::TopologyEndpoint::Node(component) => {
10338            (PioStringView::new("node"), component_id_view(component))
10339        }
10340        _ => (PioStringView::new("unknown"), empty_component_id_view()),
10341    }
10342}
10343
10344fn tap_changer_kind_name(kind: powerio_tx::TapChangerKind) -> &'static str {
10345    match kind {
10346        powerio_tx::TapChangerKind::Ratio => "ratio",
10347        powerio_tx::TapChangerKind::Phase => "phase",
10348        _ => "unknown",
10349    }
10350}
10351
10352fn tap_changer_regulation_mode_name(mode: powerio_tx::TapChangerRegulationMode) -> &'static str {
10353    match mode {
10354        powerio_tx::TapChangerRegulationMode::Voltage => "voltage",
10355        powerio_tx::TapChangerRegulationMode::ReactivePower => "reactive_power",
10356        powerio_tx::TapChangerRegulationMode::ActivePower => "active_power",
10357        powerio_tx::TapChangerRegulationMode::Current => "current",
10358        _ => "unknown",
10359    }
10360}
10361
10362fn dc_polarity_name(value: powerio_tx::DcPolarity) -> &'static str {
10363    match value {
10364        powerio_tx::DcPolarity::Positive => "positive",
10365        powerio_tx::DcPolarity::Middle => "middle",
10366        powerio_tx::DcPolarity::Negative => "negative",
10367        _ => "unknown",
10368    }
10369}
10370
10371fn dc_switch_kind_name(value: powerio_tx::DcSwitchKind) -> &'static str {
10372    match value {
10373        powerio_tx::DcSwitchKind::Switch => "switch",
10374        powerio_tx::DcSwitchKind::Breaker => "breaker",
10375        powerio_tx::DcSwitchKind::Disconnector => "disconnector",
10376        _ => "unknown",
10377    }
10378}
10379
10380fn dc_converter_operation_mode_name(value: powerio_tx::DcConverterOperatingMode) -> &'static str {
10381    match value {
10382        powerio_tx::DcConverterOperatingMode::Bipolar => "bipolar",
10383        powerio_tx::DcConverterOperatingMode::MonopolarGroundReturn => "monopolar_ground_return",
10384        powerio_tx::DcConverterOperatingMode::MonopolarMetallicReturn => {
10385            "monopolar_metallic_return"
10386        }
10387        _ => "unknown",
10388    }
10389}
10390
10391fn ac_dc_converter_control_mode_name(value: powerio_tx::AcDcConverterControlMode) -> &'static str {
10392    match value {
10393        powerio_tx::AcDcConverterControlMode::ActivePowerAtPcc => "active_power_at_pcc",
10394        powerio_tx::AcDcConverterControlMode::DcVoltage => "dc_voltage",
10395        powerio_tx::AcDcConverterControlMode::DcCurrent => "dc_current",
10396        powerio_tx::AcDcConverterControlMode::ActivePowerAtPccAndDcVoltageDroopCurve => {
10397            "active_power_at_pcc_and_dc_voltage_droop_curve"
10398        }
10399        powerio_tx::AcDcConverterControlMode::ActivePowerAtPccAndDcVoltageDroop => {
10400            "active_power_at_pcc_and_dc_voltage_droop"
10401        }
10402        powerio_tx::AcDcConverterControlMode::ActivePowerAtPccAndDcVoltageDroopWithCompensation => {
10403            "active_power_at_pcc_and_dc_voltage_droop_with_compensation"
10404        }
10405        powerio_tx::AcDcConverterControlMode::ActivePowerAtPccAndDcVoltageDroopPilot => {
10406            "active_power_at_pcc_and_dc_voltage_droop_pilot"
10407        }
10408        _ => "unknown",
10409    }
10410}
10411
10412fn line_commutated_converter_reactive_model_name(
10413    value: powerio_tx::LineCommutatedConverterReactiveModel,
10414) -> &'static str {
10415    match value {
10416        powerio_tx::LineCommutatedConverterReactiveModel::FixedPowerFactor => "fixed_power_factor",
10417        powerio_tx::LineCommutatedConverterReactiveModel::CalculatedPowerFactor => {
10418            "calculated_power_factor"
10419        }
10420        _ => "unknown",
10421    }
10422}
10423
10424fn line_commutated_converter_operating_mode_name(
10425    value: powerio_tx::LineCommutatedConverterOperatingMode,
10426) -> &'static str {
10427    match value {
10428        powerio_tx::LineCommutatedConverterOperatingMode::Rectifier => "rectifier",
10429        powerio_tx::LineCommutatedConverterOperatingMode::Inverter => "inverter",
10430        _ => "unknown",
10431    }
10432}
10433
10434fn empty_dc_terminal_view() -> PioDcTerminalView {
10435    PioDcTerminalView {
10436        component: empty_component_id_view(),
10437        has_component: false,
10438        sequence_number: 0,
10439        has_sequence_number: false,
10440        dc_node: empty_component_id_view(),
10441        has_dc_node: false,
10442        dc_topological_node: empty_component_id_view(),
10443        has_dc_topological_node: false,
10444        polarity: PioStringView::EMPTY,
10445        has_polarity: false,
10446        connected: false,
10447        has_connected: false,
10448        active_power_mw: 0.0,
10449        has_active_power: false,
10450        current_a: 0.0,
10451        has_current: false,
10452    }
10453}
10454
10455fn dc_terminal_view(value: &powerio_tx::DcTerminal) -> PioDcTerminalView {
10456    let (component, has_component) = optional_component_id_view(value.component.as_ref());
10457    let (dc_node, has_dc_node) = optional_component_id_view(value.dc_node.as_ref());
10458    let (dc_topological_node, has_dc_topological_node) =
10459        optional_component_id_view(value.dc_topological_node.as_ref());
10460    let (polarity, has_polarity) = value
10461        .polarity
10462        .map_or((PioStringView::EMPTY, false), |polarity| {
10463            (PioStringView::new(dc_polarity_name(polarity)), true)
10464        });
10465    PioDcTerminalView {
10466        component,
10467        has_component,
10468        sequence_number: value.sequence_number.unwrap_or(0),
10469        has_sequence_number: value.sequence_number.is_some(),
10470        dc_node,
10471        has_dc_node,
10472        dc_topological_node,
10473        has_dc_topological_node,
10474        polarity,
10475        has_polarity,
10476        connected: value.connected.unwrap_or(false),
10477        has_connected: value.connected.is_some(),
10478        active_power_mw: value.active_power_mw.unwrap_or(0.0),
10479        has_active_power: value.active_power_mw.is_some(),
10480        current_a: value.current_a.unwrap_or(0.0),
10481        has_current: value.current_a.is_some(),
10482    }
10483}
10484
10485#[allow(clippy::too_many_arguments)]
10486fn dc_equipment_view(
10487    component: &ComponentId,
10488    equipment_container: Option<&ComponentId>,
10489    kind: &'static str,
10490    terminal1: &powerio_tx::DcTerminal,
10491    terminal2: Option<&powerio_tx::DcTerminal>,
10492    rated_dc_voltage_kv: Option<f64>,
10493    resistance_ohm: Option<f64>,
10494    inductance_h: Option<f64>,
10495    capacitance_f: Option<f64>,
10496    length_km: Option<f64>,
10497    switch_kind: Option<powerio_tx::DcSwitchKind>,
10498    open: Option<bool>,
10499) -> PioDcEquipmentView {
10500    let (equipment_container, has_equipment_container) =
10501        optional_component_id_view(equipment_container);
10502    let (switch_kind, has_switch_kind) = switch_kind
10503        .map_or((PioStringView::EMPTY, false), |kind| {
10504            (PioStringView::new(dc_switch_kind_name(kind)), true)
10505        });
10506    PioDcEquipmentView {
10507        component: component_id_view(component),
10508        equipment_container,
10509        has_equipment_container,
10510        kind: PioStringView::new(kind),
10511        terminal_count: usize::from(terminal2.is_some()) + 1,
10512        terminal1: dc_terminal_view(terminal1),
10513        terminal2: terminal2.map_or_else(empty_dc_terminal_view, dc_terminal_view),
10514        rated_dc_voltage_kv: rated_dc_voltage_kv.unwrap_or(0.0),
10515        has_rated_dc_voltage: rated_dc_voltage_kv.is_some(),
10516        resistance_ohm: resistance_ohm.unwrap_or(0.0),
10517        has_resistance: resistance_ohm.is_some(),
10518        inductance_h: inductance_h.unwrap_or(0.0),
10519        has_inductance: inductance_h.is_some(),
10520        capacitance_f: capacitance_f.unwrap_or(0.0),
10521        has_capacitance: capacitance_f.is_some(),
10522        length_km: length_km.unwrap_or(0.0),
10523        has_length: length_km.is_some(),
10524        switch_kind,
10525        has_switch_kind,
10526        open: open.unwrap_or(false),
10527        has_open: open.is_some(),
10528    }
10529}
10530
10531/// Read one field that was absent from the source representation.
10532///
10533/// # Safety
10534/// Pointers and handles must satisfy the crate-level safety requirements.
10535#[unsafe(no_mangle)]
10536pub unsafe extern "C" fn pio_detailed_connectivity_omitted_field_at(
10537    details: *const PioDetailedConnectivity,
10538    index: usize,
10539    output: *mut PioOmittedFieldView,
10540    error: *mut *mut PioError,
10541) -> bool {
10542    unsafe {
10543        entry(error, false, || {
10544            let details = require_detailed_connectivity(details)?;
10545            let field = details.omitted_fields.get(index).ok_or_else(|| {
10546                boundary_error(
10547                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10548                    format!("omitted field index {index} is out of range"),
10549                )
10550            })?;
10551            *require_output(output, "output")? = PioOmittedFieldView {
10552                component: component_id_view(&field.component),
10553                field: PioStringView::new(omitted_field_name(field.field)),
10554            };
10555            Ok(true)
10556        })
10557    }
10558}
10559
10560/// Read one component metadata record by zero based table position.
10561///
10562/// # Safety
10563/// Pointers and handles must satisfy the crate-level safety requirements.
10564#[unsafe(no_mangle)]
10565pub unsafe extern "C" fn pio_detailed_connectivity_component_metadata_at(
10566    details: *const PioDetailedConnectivity,
10567    index: usize,
10568    output: *mut PioComponentMetadataView,
10569    error: *mut *mut PioError,
10570) -> bool {
10571    unsafe {
10572        entry(error, false, || {
10573            let details = require_detailed_connectivity(details)?;
10574            let metadata = details.component_metadata.get(index).ok_or_else(|| {
10575                boundary_error(
10576                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10577                    format!("component metadata index {index} is out of range"),
10578                )
10579            })?;
10580            let (name, has_name) = optional_string_view(metadata.name.as_deref());
10581            let (equipment_container, has_equipment_container) =
10582                optional_component_id_view(metadata.equipment_container.as_ref());
10583            *require_output(output, "output")? = PioComponentMetadataView {
10584                component: component_id_view(&metadata.component),
10585                name,
10586                has_name,
10587                equipment_container,
10588                has_equipment_container,
10589                fictitious: metadata.fictitious,
10590                alias_count: metadata.aliases.len(),
10591                external_identifier_count: metadata.external_identifiers.len(),
10592                property_count: metadata.properties.len(),
10593            };
10594            Ok(true)
10595        })
10596    }
10597}
10598
10599/// Read one alias from a component metadata record.
10600///
10601/// # Safety
10602/// Pointers and handles must satisfy the crate-level safety requirements.
10603#[unsafe(no_mangle)]
10604pub unsafe extern "C" fn pio_detailed_connectivity_component_alias_at(
10605    details: *const PioDetailedConnectivity,
10606    metadata_index: usize,
10607    alias_index: usize,
10608    output: *mut PioComponentAliasView,
10609    error: *mut *mut PioError,
10610) -> bool {
10611    unsafe {
10612        entry(error, false, || {
10613            let details = require_detailed_connectivity(details)?;
10614            let metadata = details
10615                .component_metadata
10616                .get(metadata_index)
10617                .ok_or_else(|| {
10618                    boundary_error(
10619                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10620                        format!("component metadata index {metadata_index} is out of range"),
10621                    )
10622                })?;
10623            let alias = metadata.aliases.get(alias_index).ok_or_else(|| {
10624                boundary_error(
10625                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10626                    format!("component alias index {alias_index} is out of range"),
10627                )
10628            })?;
10629            let (alias_type, has_alias_type) = optional_string_view(alias.alias_type.as_deref());
10630            *require_output(output, "output")? = PioComponentAliasView {
10631                value: PioStringView::new(&alias.value),
10632                alias_type,
10633                has_alias_type,
10634            };
10635            Ok(true)
10636        })
10637    }
10638}
10639
10640/// Read one external identifier from a component metadata record.
10641///
10642/// # Safety
10643/// Pointers and handles must satisfy the crate-level safety requirements.
10644#[unsafe(no_mangle)]
10645pub unsafe extern "C" fn pio_detailed_connectivity_external_identifier_at(
10646    details: *const PioDetailedConnectivity,
10647    metadata_index: usize,
10648    identifier_index: usize,
10649    output: *mut PioExternalIdentifierView,
10650    error: *mut *mut PioError,
10651) -> bool {
10652    unsafe {
10653        entry(error, false, || {
10654            let details = require_detailed_connectivity(details)?;
10655            let metadata = details
10656                .component_metadata
10657                .get(metadata_index)
10658                .ok_or_else(|| {
10659                    boundary_error(
10660                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10661                        format!("component metadata index {metadata_index} is out of range"),
10662                    )
10663                })?;
10664            let identifier = metadata
10665                .external_identifiers
10666                .get(identifier_index)
10667                .ok_or_else(|| {
10668                    boundary_error(
10669                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10670                        format!("external identifier index {identifier_index} is out of range"),
10671                    )
10672                })?;
10673            let (authority, has_authority) = optional_string_view(identifier.authority.as_deref());
10674            *require_output(output, "output")? = PioExternalIdentifierView {
10675                value: PioStringView::new(&identifier.value),
10676                authority,
10677                has_authority,
10678            };
10679            Ok(true)
10680        })
10681    }
10682}
10683
10684/// Read one string property from a component metadata record.
10685///
10686/// # Safety
10687/// Pointers and handles must satisfy the crate-level safety requirements.
10688#[unsafe(no_mangle)]
10689pub unsafe extern "C" fn pio_detailed_connectivity_component_property_at(
10690    details: *const PioDetailedConnectivity,
10691    metadata_index: usize,
10692    property_index: usize,
10693    output: *mut PioStringPropertyView,
10694    error: *mut *mut PioError,
10695) -> bool {
10696    unsafe {
10697        entry(error, false, || {
10698            let details = require_detailed_connectivity(details)?;
10699            let metadata = details
10700                .component_metadata
10701                .get(metadata_index)
10702                .ok_or_else(|| {
10703                    boundary_error(
10704                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10705                        format!("component metadata index {metadata_index} is out of range"),
10706                    )
10707                })?;
10708            let (name, value) =
10709                metadata
10710                    .properties
10711                    .iter()
10712                    .nth(property_index)
10713                    .ok_or_else(|| {
10714                        boundary_error(
10715                            &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10716                            format!("component property index {property_index} is out of range"),
10717                        )
10718                    })?;
10719            *require_output(output, "output")? = PioStringPropertyView {
10720                name: PioStringView::new(name),
10721                value: PioStringView::new(value),
10722            };
10723            Ok(true)
10724        })
10725    }
10726}
10727
10728fn case_metadata_view(metadata: &powerio_tx::CaseMetadata) -> PioCaseMetadataView {
10729    let (case_date, has_case_date) = optional_string_view(metadata.case_date.as_deref());
10730    let (source_model_format, has_source_model_format) =
10731        optional_string_view(metadata.source_model_format.as_deref());
10732    let (minimum_validation_level, has_minimum_validation_level) =
10733        optional_string_view(metadata.minimum_validation_level.as_deref());
10734    PioCaseMetadataView {
10735        case_date,
10736        has_case_date,
10737        forecast_distance: metadata.forecast_distance.unwrap_or(0),
10738        has_forecast_distance: metadata.forecast_distance.is_some(),
10739        source_model_format,
10740        has_source_model_format,
10741        minimum_validation_level,
10742        has_minimum_validation_level,
10743    }
10744}
10745
10746/// Read one PowSybl subnetwork by zero based table position.
10747///
10748/// # Safety
10749/// Pointers and handles must satisfy the crate-level safety requirements.
10750#[unsafe(no_mangle)]
10751pub unsafe extern "C" fn pio_detailed_connectivity_subnetwork_at(
10752    details: *const PioDetailedConnectivity,
10753    index: usize,
10754    output: *mut PioSubnetworkView,
10755    error: *mut *mut PioError,
10756) -> bool {
10757    unsafe {
10758        entry(error, false, || {
10759            let details = require_detailed_connectivity(details)?;
10760            let subnetwork = details.subnetworks.get(index).ok_or_else(|| {
10761                boundary_error(
10762                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10763                    format!("subnetwork index {index} is out of range"),
10764                )
10765            })?;
10766            *require_output(output, "output")? = PioSubnetworkView {
10767                component: component_id_view(&subnetwork.component),
10768                parent: component_id_view(&subnetwork.parent),
10769                case_metadata: case_metadata_view(&subnetwork.case_metadata),
10770                component_count: subnetwork.components.len(),
10771            };
10772            Ok(true)
10773        })
10774    }
10775}
10776
10777/// Read one component identity contained by a PowSybl subnetwork.
10778///
10779/// # Safety
10780/// Pointers and handles must satisfy the crate-level safety requirements.
10781#[unsafe(no_mangle)]
10782pub unsafe extern "C" fn pio_detailed_connectivity_subnetwork_component_at(
10783    details: *const PioDetailedConnectivity,
10784    subnetwork_index: usize,
10785    component_index: usize,
10786    output: *mut PioComponentIdView,
10787    error: *mut *mut PioError,
10788) -> bool {
10789    unsafe {
10790        entry(error, false, || {
10791            let details = require_detailed_connectivity(details)?;
10792            let subnetwork = details.subnetworks.get(subnetwork_index).ok_or_else(|| {
10793                boundary_error(
10794                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10795                    format!("subnetwork index {subnetwork_index} is out of range"),
10796                )
10797            })?;
10798            let component = subnetwork.components.get(component_index).ok_or_else(|| {
10799                boundary_error(
10800                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10801                    format!("subnetwork component index {component_index} is out of range"),
10802                )
10803            })?;
10804            *require_output(output, "output")? = component_id_view(component);
10805            Ok(true)
10806        })
10807    }
10808}
10809
10810/// Read one substation by zero based table position.
10811///
10812/// # Safety
10813/// Pointers and handles must satisfy the crate-level safety requirements.
10814#[unsafe(no_mangle)]
10815pub unsafe extern "C" fn pio_detailed_connectivity_substation_at(
10816    details: *const PioDetailedConnectivity,
10817    index: usize,
10818    output: *mut PioSubstationView,
10819    error: *mut *mut PioError,
10820) -> bool {
10821    unsafe {
10822        entry(error, false, || {
10823            let details = require_detailed_connectivity(details)?;
10824            let substation = details.substations.get(index).ok_or_else(|| {
10825                boundary_error(
10826                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10827                    format!("substation index {index} is out of range"),
10828                )
10829            })?;
10830            let (country, has_country) = optional_string_view(substation.country.as_deref());
10831            let (operator_name, has_operator_name) =
10832                optional_string_view(substation.operator.as_deref());
10833            *require_output(output, "output")? = PioSubstationView {
10834                component: component_id_view(&substation.component),
10835                country,
10836                has_country,
10837                operator_name,
10838                has_operator_name,
10839                geographical_tag_count: substation.geographical_tags.len(),
10840            };
10841            Ok(true)
10842        })
10843    }
10844}
10845
10846/// Read one geographical tag of a substation.
10847///
10848/// # Safety
10849/// Pointers and handles must satisfy the crate-level safety requirements.
10850#[unsafe(no_mangle)]
10851pub unsafe extern "C" fn pio_detailed_connectivity_substation_geographical_tag_at(
10852    details: *const PioDetailedConnectivity,
10853    substation_index: usize,
10854    tag_index: usize,
10855    output: *mut PioStringView,
10856    error: *mut *mut PioError,
10857) -> bool {
10858    unsafe {
10859        entry(error, false, || {
10860            let details = require_detailed_connectivity(details)?;
10861            let substation = details.substations.get(substation_index).ok_or_else(|| {
10862                boundary_error(
10863                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10864                    format!("substation index {substation_index} is out of range"),
10865                )
10866            })?;
10867            let tag = substation.geographical_tags.get(tag_index).ok_or_else(|| {
10868                boundary_error(
10869                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10870                    format!("geographical tag index {tag_index} is out of range"),
10871                )
10872            })?;
10873            *require_output(output, "output")? = PioStringView::new(tag);
10874            Ok(true)
10875        })
10876    }
10877}
10878
10879/// Read one voltage level by zero based table position.
10880///
10881/// # Safety
10882/// Pointers and handles must satisfy the crate-level safety requirements.
10883#[unsafe(no_mangle)]
10884pub unsafe extern "C" fn pio_detailed_connectivity_voltage_level_at(
10885    details: *const PioDetailedConnectivity,
10886    index: usize,
10887    output: *mut PioVoltageLevelView,
10888    error: *mut *mut PioError,
10889) -> bool {
10890    unsafe {
10891        entry(error, false, || {
10892            let details = require_detailed_connectivity(details)?;
10893            let level = details.voltage_levels.get(index).ok_or_else(|| {
10894                boundary_error(
10895                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10896                    format!("voltage level index {index} is out of range"),
10897                )
10898            })?;
10899            let (substation, has_substation) =
10900                optional_component_id_view(level.substation.as_ref());
10901            *require_output(output, "output")? = PioVoltageLevelView {
10902                component: component_id_view(&level.component),
10903                substation,
10904                has_substation,
10905                nominal_voltage_kv: level.nominal_kv,
10906                low_voltage_limit_kv: level.low_voltage_limit_kv.unwrap_or(0.0),
10907                has_low_voltage_limit: level.low_voltage_limit_kv.is_some(),
10908                high_voltage_limit_kv: level.high_voltage_limit_kv.unwrap_or(0.0),
10909                has_high_voltage_limit: level.high_voltage_limit_kv.is_some(),
10910                topology_kind: PioStringView::new(topology_kind_name(level.topology_kind)),
10911                bus_count: level.buses.len(),
10912            };
10913            Ok(true)
10914        })
10915    }
10916}
10917
10918/// Read one balanced bus ID assigned to a voltage level.
10919///
10920/// # Safety
10921/// Pointers and handles must satisfy the crate-level safety requirements.
10922#[unsafe(no_mangle)]
10923pub unsafe extern "C" fn pio_detailed_connectivity_voltage_level_bus_at(
10924    details: *const PioDetailedConnectivity,
10925    voltage_level_index: usize,
10926    bus_index: usize,
10927    output: *mut usize,
10928    error: *mut *mut PioError,
10929) -> bool {
10930    unsafe {
10931        entry(error, false, || {
10932            let details = require_detailed_connectivity(details)?;
10933            let level = details
10934                .voltage_levels
10935                .get(voltage_level_index)
10936                .ok_or_else(|| {
10937                    boundary_error(
10938                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10939                        format!("voltage level index {voltage_level_index} is out of range"),
10940                    )
10941                })?;
10942            let bus = level.buses.get(bus_index).ok_or_else(|| {
10943                boundary_error(
10944                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10945                    format!("voltage level bus index {bus_index} is out of range"),
10946                )
10947            })?;
10948            *require_output(output, "output")? = bus.0;
10949            Ok(true)
10950        })
10951    }
10952}
10953
10954/// Read one configured bus breaker bus by zero based table position.
10955///
10956/// # Safety
10957/// Pointers and handles must satisfy the crate-level safety requirements.
10958#[unsafe(no_mangle)]
10959pub unsafe extern "C" fn pio_detailed_connectivity_bus_breaker_bus_at(
10960    details: *const PioDetailedConnectivity,
10961    index: usize,
10962    output: *mut PioBusBreakerBusView,
10963    error: *mut *mut PioError,
10964) -> bool {
10965    unsafe {
10966        entry(error, false, || {
10967            let details = require_detailed_connectivity(details)?;
10968            let bus = details.bus_breaker_buses.get(index).ok_or_else(|| {
10969                boundary_error(
10970                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10971                    format!("bus breaker bus index {index} is out of range"),
10972                )
10973            })?;
10974            *require_output(output, "output")? = PioBusBreakerBusView {
10975                component: component_id_view(&bus.component),
10976                voltage_level: component_id_view(&bus.voltage_level),
10977                calculated_bus_id: bus.calculated_bus.map_or(0, |value| value.0),
10978                has_calculated_bus: bus.calculated_bus.is_some(),
10979                voltage_kv: bus.voltage_kv.unwrap_or(0.0),
10980                has_voltage: bus.voltage_kv.is_some(),
10981                angle_degrees: bus.angle_degrees.unwrap_or(0.0),
10982                has_angle: bus.angle_degrees.is_some(),
10983            };
10984            Ok(true)
10985        })
10986    }
10987}
10988
10989/// Read one calculated bus by zero based table position.
10990///
10991/// # Safety
10992/// Pointers and handles must satisfy the crate-level safety requirements.
10993#[unsafe(no_mangle)]
10994pub unsafe extern "C" fn pio_detailed_connectivity_calculated_bus_at(
10995    details: *const PioDetailedConnectivity,
10996    index: usize,
10997    output: *mut PioCalculatedBusView,
10998    error: *mut *mut PioError,
10999) -> bool {
11000    unsafe {
11001        entry(error, false, || {
11002            let details = require_detailed_connectivity(details)?;
11003            let bus = details.calculated_buses.get(index).ok_or_else(|| {
11004                boundary_error(
11005                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11006                    format!("calculated bus index {index} is out of range"),
11007                )
11008            })?;
11009            *require_output(output, "output")? = PioCalculatedBusView {
11010                voltage_level: component_id_view(&bus.voltage_level),
11011                calculated_bus_id: bus.calculated_bus.0,
11012                node_count: bus.nodes.len(),
11013                voltage_kv: bus.voltage_kv.unwrap_or(0.0),
11014                has_voltage: bus.voltage_kv.is_some(),
11015                angle_degrees: bus.angle_degrees.unwrap_or(0.0),
11016                has_angle: bus.angle_degrees.is_some(),
11017            };
11018            Ok(true)
11019        })
11020    }
11021}
11022
11023/// Read one node identity from a calculated bus.
11024///
11025/// # Safety
11026/// Pointers and handles must satisfy the crate-level safety requirements.
11027#[unsafe(no_mangle)]
11028pub unsafe extern "C" fn pio_detailed_connectivity_calculated_bus_node_at(
11029    details: *const PioDetailedConnectivity,
11030    calculated_bus_index: usize,
11031    node_index: usize,
11032    output: *mut PioComponentIdView,
11033    error: *mut *mut PioError,
11034) -> bool {
11035    unsafe {
11036        entry(error, false, || {
11037            let details = require_detailed_connectivity(details)?;
11038            let bus = details
11039                .calculated_buses
11040                .get(calculated_bus_index)
11041                .ok_or_else(|| {
11042                    boundary_error(
11043                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11044                        format!("calculated bus index {calculated_bus_index} is out of range"),
11045                    )
11046                })?;
11047            let node = bus.nodes.get(node_index).ok_or_else(|| {
11048                boundary_error(
11049                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11050                    format!("calculated bus node index {node_index} is out of range"),
11051                )
11052            })?;
11053            *require_output(output, "output")? = component_id_view(node);
11054            Ok(true)
11055        })
11056    }
11057}
11058
11059/// Read one connectivity node by zero based table position.
11060///
11061/// # Safety
11062/// Pointers and handles must satisfy the crate-level safety requirements.
11063#[unsafe(no_mangle)]
11064pub unsafe extern "C" fn pio_detailed_connectivity_node_at(
11065    details: *const PioDetailedConnectivity,
11066    index: usize,
11067    output: *mut PioConnectivityNodeView,
11068    error: *mut *mut PioError,
11069) -> bool {
11070    unsafe {
11071        entry(error, false, || {
11072            let details = require_detailed_connectivity(details)?;
11073            let node = details.connectivity_nodes.get(index).ok_or_else(|| {
11074                boundary_error(
11075                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11076                    format!("connectivity node index {index} is out of range"),
11077                )
11078            })?;
11079            *require_output(output, "output")? = PioConnectivityNodeView {
11080                component: component_id_view(&node.component),
11081                voltage_level: component_id_view(&node.voltage_level),
11082                node_number: node.node_number.unwrap_or(0),
11083                has_node_number: node.node_number.is_some(),
11084                calculated_bus_id: node.calculated_bus.map_or(0, |bus| bus.0),
11085                has_calculated_bus: node.calculated_bus.is_some(),
11086            };
11087            Ok(true)
11088        })
11089    }
11090}
11091
11092/// Read one busbar section by zero based table position.
11093///
11094/// # Safety
11095/// Pointers and handles must satisfy the crate-level safety requirements.
11096#[unsafe(no_mangle)]
11097pub unsafe extern "C" fn pio_detailed_connectivity_busbar_section_at(
11098    details: *const PioDetailedConnectivity,
11099    index: usize,
11100    output: *mut PioBusbarSectionView,
11101    error: *mut *mut PioError,
11102) -> bool {
11103    unsafe {
11104        entry(error, false, || {
11105            let details = require_detailed_connectivity(details)?;
11106            let section = details.busbar_sections.get(index).ok_or_else(|| {
11107                boundary_error(
11108                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11109                    format!("busbar section index {index} is out of range"),
11110                )
11111            })?;
11112            *require_output(output, "output")? = PioBusbarSectionView {
11113                component: component_id_view(&section.component),
11114                voltage_level: component_id_view(&section.voltage_level),
11115                node: component_id_view(&section.node),
11116            };
11117            Ok(true)
11118        })
11119    }
11120}
11121
11122/// Read one CIM junction by zero based table position.
11123///
11124/// # Safety
11125/// Pointers and handles must satisfy the crate-level safety requirements.
11126#[unsafe(no_mangle)]
11127pub unsafe extern "C" fn pio_detailed_connectivity_junction_at(
11128    details: *const PioDetailedConnectivity,
11129    index: usize,
11130    output: *mut PioJunctionView,
11131    error: *mut *mut PioError,
11132) -> bool {
11133    unsafe {
11134        entry(error, false, || {
11135            let details = require_detailed_connectivity(details)?;
11136            let junction = details.junctions.get(index).ok_or_else(|| {
11137                boundary_error(
11138                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11139                    format!("junction index {index} is out of range"),
11140                )
11141            })?;
11142            *require_output(output, "output")? = PioJunctionView {
11143                component: component_id_view(&junction.component),
11144            };
11145            Ok(true)
11146        })
11147    }
11148}
11149
11150/// Read one AC terminal by zero based table position.
11151///
11152/// # Safety
11153/// Pointers and handles must satisfy the crate-level safety requirements.
11154#[unsafe(no_mangle)]
11155pub unsafe extern "C" fn pio_detailed_connectivity_terminal_at(
11156    details: *const PioDetailedConnectivity,
11157    index: usize,
11158    output: *mut PioDetailedTerminalView,
11159    error: *mut *mut PioError,
11160) -> bool {
11161    unsafe {
11162        entry(error, false, || {
11163            let details = require_detailed_connectivity(details)?;
11164            let terminal = details.terminals.get(index).ok_or_else(|| {
11165                boundary_error(
11166                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11167                    format!("terminal index {index} is out of range"),
11168                )
11169            })?;
11170            let (bus, has_bus) = optional_component_id_view(terminal.bus.as_ref());
11171            let (connectable_bus, has_connectable_bus) =
11172                optional_component_id_view(terminal.connectable_bus.as_ref());
11173            let (node, has_node) = optional_component_id_view(terminal.node.as_ref());
11174            let (component, has_component) =
11175                optional_component_id_view(terminal.component.as_ref());
11176            *require_output(output, "output")? = PioDetailedTerminalView {
11177                component,
11178                has_component,
11179                equipment: component_id_view(&terminal.equipment),
11180                terminal: terminal.terminal,
11181                voltage_level: component_id_view(&terminal.voltage_level),
11182                bus,
11183                has_bus,
11184                connectable_bus,
11185                has_connectable_bus,
11186                node,
11187                has_node,
11188                connected: terminal.connected,
11189                active_power_mw: terminal.active_power_mw.unwrap_or(0.0),
11190                has_active_power: terminal.active_power_mw.is_some(),
11191                reactive_power_mvar: terminal.reactive_power_mvar.unwrap_or(0.0),
11192                has_reactive_power: terminal.reactive_power_mvar.is_some(),
11193            };
11194            Ok(true)
11195        })
11196    }
11197}
11198
11199/// Read one detailed topology switch by zero based table position.
11200///
11201/// # Safety
11202/// Pointers and handles must satisfy the crate-level safety requirements.
11203#[unsafe(no_mangle)]
11204pub unsafe extern "C" fn pio_detailed_connectivity_switch_at(
11205    details: *const PioDetailedConnectivity,
11206    index: usize,
11207    output: *mut PioTopologySwitchView,
11208    error: *mut *mut PioError,
11209) -> bool {
11210    unsafe {
11211        entry(error, false, || {
11212            let details = require_detailed_connectivity(details)?;
11213            let switch = details.switches.get(index).ok_or_else(|| {
11214                boundary_error(
11215                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11216                    format!("topology switch index {index} is out of range"),
11217                )
11218            })?;
11219            let (endpoint1_kind, endpoint1) = topology_endpoint_view(&switch.endpoint1);
11220            let (endpoint2_kind, endpoint2) = topology_endpoint_view(&switch.endpoint2);
11221            *require_output(output, "output")? = PioTopologySwitchView {
11222                component: component_id_view(&switch.component),
11223                voltage_level: component_id_view(&switch.voltage_level),
11224                kind: PioStringView::new(topology_switch_kind_name(switch.kind)),
11225                endpoint1_kind,
11226                endpoint1,
11227                endpoint2_kind,
11228                endpoint2,
11229                open: switch.open,
11230                retained: switch.retained,
11231            };
11232            Ok(true)
11233        })
11234    }
11235}
11236
11237/// Read one node breaker internal connection by zero based table position.
11238///
11239/// # Safety
11240/// Pointers and handles must satisfy the crate-level safety requirements.
11241#[unsafe(no_mangle)]
11242pub unsafe extern "C" fn pio_detailed_connectivity_internal_connection_at(
11243    details: *const PioDetailedConnectivity,
11244    index: usize,
11245    output: *mut PioInternalConnectionView,
11246    error: *mut *mut PioError,
11247) -> bool {
11248    unsafe {
11249        entry(error, false, || {
11250            let details = require_detailed_connectivity(details)?;
11251            let connection = details.internal_connections.get(index).ok_or_else(|| {
11252                boundary_error(
11253                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11254                    format!("internal connection index {index} is out of range"),
11255                )
11256            })?;
11257            *require_output(output, "output")? = PioInternalConnectionView {
11258                voltage_level: component_id_view(&connection.voltage_level),
11259                node1: component_id_view(&connection.node1),
11260                node2: component_id_view(&connection.node2),
11261            };
11262            Ok(true)
11263        })
11264    }
11265}
11266
11267/// Read one operational limit group by zero based table position.
11268///
11269/// # Safety
11270/// Pointers and handles must satisfy the crate-level safety requirements.
11271#[unsafe(no_mangle)]
11272pub unsafe extern "C" fn pio_detailed_connectivity_operational_limit_group_at(
11273    details: *const PioDetailedConnectivity,
11274    index: usize,
11275    output: *mut PioOperationalLimitGroupView,
11276    error: *mut *mut PioError,
11277) -> bool {
11278    unsafe {
11279        entry(error, false, || {
11280            let details = require_detailed_connectivity(details)?;
11281            let group = details.operational_limit_groups.get(index).ok_or_else(|| {
11282                boundary_error(
11283                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11284                    format!("operational limit group index {index} is out of range"),
11285                )
11286            })?;
11287            let current = group.current_limits.as_ref();
11288            let active = group.active_power_limits.as_ref();
11289            let apparent = group.apparent_power_limits.as_ref();
11290            let (current_name, has_current_name) = optional_string_view(
11291                current.and_then(|limits| limits.permanent_limit_name.as_deref()),
11292            );
11293            let (active_name, has_active_name) = optional_string_view(
11294                active.and_then(|limits| limits.permanent_limit_name.as_deref()),
11295            );
11296            let (apparent_name, has_apparent_name) = optional_string_view(
11297                apparent.and_then(|limits| limits.permanent_limit_name.as_deref()),
11298            );
11299            *require_output(output, "output")? = PioOperationalLimitGroupView {
11300                equipment: component_id_view(&group.equipment),
11301                terminal: group.terminal,
11302                id: PioStringView::new(&group.id),
11303                selected: group.selected,
11304                property_count: group.properties.len(),
11305                has_current_limits: current.is_some(),
11306                current_permanent_limit_a: current
11307                    .and_then(|limits| limits.permanent_limit)
11308                    .unwrap_or(0.0),
11309                current_permanent_limit_name: current_name,
11310                has_current_permanent_limit: current
11311                    .is_some_and(|limits| limits.permanent_limit.is_some()),
11312                has_current_permanent_limit_name: has_current_name,
11313                current_temporary_limit_count: current
11314                    .map_or(0, |limits| limits.temporary_limits.len()),
11315                has_active_power_limits: active.is_some(),
11316                active_power_permanent_limit_mw: active
11317                    .and_then(|limits| limits.permanent_limit)
11318                    .unwrap_or(0.0),
11319                active_power_permanent_limit_name: active_name,
11320                has_active_power_permanent_limit: active
11321                    .is_some_and(|limits| limits.permanent_limit.is_some()),
11322                has_active_power_permanent_limit_name: has_active_name,
11323                active_power_temporary_limit_count: active
11324                    .map_or(0, |limits| limits.temporary_limits.len()),
11325                has_apparent_power_limits: apparent.is_some(),
11326                apparent_power_permanent_limit_mva: apparent
11327                    .and_then(|limits| limits.permanent_limit)
11328                    .unwrap_or(0.0),
11329                apparent_power_permanent_limit_name: apparent_name,
11330                has_apparent_power_permanent_limit: apparent
11331                    .is_some_and(|limits| limits.permanent_limit.is_some()),
11332                has_apparent_power_permanent_limit_name: has_apparent_name,
11333                apparent_power_temporary_limit_count: apparent
11334                    .map_or(0, |limits| limits.temporary_limits.len()),
11335            };
11336            Ok(true)
11337        })
11338    }
11339}
11340
11341/// Read one string property from an operational limit group.
11342///
11343/// # Safety
11344/// Pointers and handles must satisfy the crate-level safety requirements.
11345#[unsafe(no_mangle)]
11346pub unsafe extern "C" fn pio_detailed_connectivity_operational_limit_group_property_at(
11347    details: *const PioDetailedConnectivity,
11348    group_index: usize,
11349    property_index: usize,
11350    output: *mut PioStringPropertyView,
11351    error: *mut *mut PioError,
11352) -> bool {
11353    unsafe {
11354        entry(error, false, || {
11355            let details = require_detailed_connectivity(details)?;
11356            let group = details
11357                .operational_limit_groups
11358                .get(group_index)
11359                .ok_or_else(|| {
11360                    boundary_error(
11361                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11362                        format!("operational limit group index {group_index} is out of range"),
11363                    )
11364                })?;
11365            *require_output(output, "output")? =
11366                string_property_view(&group.properties, property_index)?;
11367            Ok(true)
11368        })
11369    }
11370}
11371
11372/// Read one temporary current, active power, or apparent power limit.
11373///
11374/// # Safety
11375/// Pointers and handles must satisfy the crate-level safety requirements.
11376#[unsafe(no_mangle)]
11377pub unsafe extern "C" fn pio_detailed_connectivity_temporary_limit_at(
11378    details: *const PioDetailedConnectivity,
11379    group_index: usize,
11380    quantity: *const c_char,
11381    quantity_len: usize,
11382    limit_index: usize,
11383    output: *mut PioTemporaryLimitView,
11384    error: *mut *mut PioError,
11385) -> bool {
11386    unsafe {
11387        entry(error, false, || {
11388            let details = require_detailed_connectivity(details)?;
11389            let group = details
11390                .operational_limit_groups
11391                .get(group_index)
11392                .ok_or_else(|| {
11393                    boundary_error(
11394                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11395                        format!("operational limit group index {group_index} is out of range"),
11396                    )
11397                })?;
11398            let quantity = required_str(quantity, quantity_len, "quantity")?;
11399            let limits = match quantity {
11400                "current" => group.current_limits.as_ref(),
11401                "active_power" => group.active_power_limits.as_ref(),
11402                "apparent_power" => group.apparent_power_limits.as_ref(),
11403                _ => {
11404                    return Err(boundary_error(
11405                        &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
11406                        "quantity must be current, active_power, or apparent_power",
11407                    ));
11408                }
11409            }
11410            .ok_or_else(|| {
11411                boundary_error(
11412                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11413                    format!("operational limit group {group_index} has no {quantity} limits"),
11414                )
11415            })?;
11416            let limit = limits.temporary_limits.get(limit_index).ok_or_else(|| {
11417                boundary_error(
11418                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11419                    format!("temporary limit index {limit_index} is out of range"),
11420                )
11421            })?;
11422            *require_output(output, "output")? = PioTemporaryLimitView {
11423                name: PioStringView::new(&limit.name),
11424                value: limit.value,
11425                acceptable_duration_seconds: limit.acceptable_duration_seconds,
11426                fictitious: limit.fictitious,
11427            };
11428            Ok(true)
11429        })
11430    }
11431}
11432
11433/// Read one PowSybl boundary line by zero based table position.
11434///
11435/// # Safety
11436/// Pointers and handles must satisfy the crate-level safety requirements.
11437#[unsafe(no_mangle)]
11438pub unsafe extern "C" fn pio_detailed_connectivity_boundary_line_at(
11439    details: *const PioDetailedConnectivity,
11440    index: usize,
11441    output: *mut PioBoundaryLineView,
11442    error: *mut *mut PioError,
11443) -> bool {
11444    unsafe {
11445        entry(error, false, || {
11446            let details = require_detailed_connectivity(details)?;
11447            let line = details.boundary_lines.get(index).ok_or_else(|| {
11448                boundary_error(
11449                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11450                    format!("boundary line index {index} is out of range"),
11451                )
11452            })?;
11453            let (pairing_key, has_pairing_key) = optional_string_view(line.pairing_key.as_deref());
11454            let (generation, has_generation) =
11455                boundary_line_generation_view(line.generation.as_ref());
11456            let (calculation_load, has_calculation_load) =
11457                optional_component_id_view(line.calculation_load.as_ref());
11458            let (calculation_generator, has_calculation_generator) =
11459                optional_component_id_view(line.calculation_generator.as_ref());
11460            *require_output(output, "output")? = PioBoundaryLineView {
11461                component: component_id_view(&line.component),
11462                voltage_level: component_id_view(&line.voltage_level),
11463                active_power_setpoint_mw: line.active_power_setpoint_mw,
11464                reactive_power_setpoint_mvar: line.reactive_power_setpoint_mvar,
11465                resistance_ohm: line.resistance_ohm,
11466                reactance_ohm: line.reactance_ohm,
11467                conductance_siemens: line.conductance_siemens,
11468                susceptance_siemens: line.susceptance_siemens,
11469                pairing_key,
11470                has_pairing_key,
11471                generation,
11472                has_generation,
11473                calculation_load,
11474                has_calculation_load,
11475                calculation_generator,
11476                has_calculation_generator,
11477            };
11478            Ok(true)
11479        })
11480    }
11481}
11482
11483/// Read one property on a boundary line generation reactive limit record.
11484///
11485/// # Safety
11486/// Pointers and handles must satisfy the crate-level safety requirements.
11487#[unsafe(no_mangle)]
11488pub unsafe extern "C" fn pio_detailed_connectivity_boundary_line_reactive_limit_property_at(
11489    details: *const PioDetailedConnectivity,
11490    boundary_line_index: usize,
11491    property_index: usize,
11492    output: *mut PioStringPropertyView,
11493    error: *mut *mut PioError,
11494) -> bool {
11495    unsafe {
11496        entry(error, false, || {
11497            let details = require_detailed_connectivity(details)?;
11498            let line = details
11499                .boundary_lines
11500                .get(boundary_line_index)
11501                .ok_or_else(|| {
11502                    boundary_error(
11503                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11504                        format!("boundary line index {boundary_line_index} is out of range"),
11505                    )
11506                })?;
11507            let limits = line
11508                .generation
11509                .as_ref()
11510                .and_then(|generation| generation.reactive_limits.as_ref())
11511                .ok_or_else(|| {
11512                    boundary_error(
11513                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11514                        format!(
11515                            "boundary line {boundary_line_index} has no generation reactive limits"
11516                        ),
11517                    )
11518                })?;
11519            let properties = match limits {
11520                powerio_tx::ReactiveLimits::MinMax(limits) => &limits.properties,
11521                powerio_tx::ReactiveLimits::CapabilityCurve(curve) => &curve.properties,
11522                _ => {
11523                    return Err(boundary_error(
11524                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
11525                        "unsupported reactive limit record",
11526                    ));
11527                }
11528            };
11529            let (name, value) = properties.iter().nth(property_index).ok_or_else(|| {
11530                boundary_error(
11531                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11532                    format!("reactive limit property index {property_index} is out of range"),
11533                )
11534            })?;
11535            *require_output(output, "output")? = PioStringPropertyView {
11536                name: PioStringView::new(name),
11537                value: PioStringView::new(value),
11538            };
11539            Ok(true)
11540        })
11541    }
11542}
11543
11544/// Read one point from a boundary line generation reactive capability curve.
11545///
11546/// # Safety
11547/// Pointers and handles must satisfy the crate-level safety requirements.
11548#[unsafe(no_mangle)]
11549pub unsafe extern "C" fn pio_detailed_connectivity_boundary_line_reactive_capability_point_at(
11550    details: *const PioDetailedConnectivity,
11551    boundary_line_index: usize,
11552    point_index: usize,
11553    output: *mut PioReactiveCapabilityCurvePointView,
11554    error: *mut *mut PioError,
11555) -> bool {
11556    unsafe {
11557        entry(error, false, || {
11558            let details = require_detailed_connectivity(details)?;
11559            let line = details
11560                .boundary_lines
11561                .get(boundary_line_index)
11562                .ok_or_else(|| {
11563                    boundary_error(
11564                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11565                        format!("boundary line index {boundary_line_index} is out of range"),
11566                    )
11567                })?;
11568            let curve = line
11569                .generation
11570                .as_ref()
11571                .and_then(|generation| generation.reactive_limits.as_ref())
11572                .and_then(|limits| match limits {
11573                    powerio_tx::ReactiveLimits::CapabilityCurve(curve) => Some(curve),
11574                    _ => None,
11575                })
11576                .ok_or_else(|| {
11577                    boundary_error(
11578                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
11579                        format!(
11580                            "boundary line {boundary_line_index} has no generation reactive capability curve"
11581                        ),
11582                    )
11583                })?;
11584            let point = curve.points.get(point_index).ok_or_else(|| {
11585                boundary_error(
11586                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11587                    format!("reactive capability point index {point_index} is out of range"),
11588                )
11589            })?;
11590            *require_output(output, "output")? = PioReactiveCapabilityCurvePointView {
11591                active_power_mw: point.active_power_mw,
11592                minimum_reactive_power_mvar: point.minimum_reactive_power_mvar,
11593                maximum_reactive_power_mvar: point.maximum_reactive_power_mvar,
11594                property_count: point.properties.len(),
11595            };
11596            Ok(true)
11597        })
11598    }
11599}
11600
11601/// Read one property from one boundary line reactive capability curve point.
11602///
11603/// # Safety
11604/// Pointers and handles must satisfy the crate-level safety requirements.
11605#[unsafe(no_mangle)]
11606pub unsafe extern "C" fn pio_detailed_connectivity_boundary_line_reactive_capability_point_property_at(
11607    details: *const PioDetailedConnectivity,
11608    boundary_line_index: usize,
11609    point_index: usize,
11610    property_index: usize,
11611    output: *mut PioStringPropertyView,
11612    error: *mut *mut PioError,
11613) -> bool {
11614    unsafe {
11615        entry(error, false, || {
11616            let details = require_detailed_connectivity(details)?;
11617            let line = details
11618                .boundary_lines
11619                .get(boundary_line_index)
11620                .ok_or_else(|| {
11621                    boundary_error(
11622                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11623                        format!("boundary line index {boundary_line_index} is out of range"),
11624                    )
11625                })?;
11626            let curve = line
11627                .generation
11628                .as_ref()
11629                .and_then(|generation| generation.reactive_limits.as_ref())
11630                .and_then(|limits| match limits {
11631                    powerio_tx::ReactiveLimits::CapabilityCurve(curve) => Some(curve),
11632                    _ => None,
11633                })
11634                .ok_or_else(|| {
11635                    boundary_error(
11636                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
11637                        format!(
11638                            "boundary line {boundary_line_index} has no generation reactive capability curve"
11639                        ),
11640                    )
11641                })?;
11642            let point = curve.points.get(point_index).ok_or_else(|| {
11643                boundary_error(
11644                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11645                    format!("reactive capability point index {point_index} is out of range"),
11646                )
11647            })?;
11648            let (name, value) = point.properties.iter().nth(property_index).ok_or_else(|| {
11649                boundary_error(
11650                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11651                    format!(
11652                        "reactive capability point property index {property_index} is out of range"
11653                    ),
11654                )
11655            })?;
11656            *require_output(output, "output")? = PioStringPropertyView {
11657                name: PioStringView::new(name),
11658                value: PioStringView::new(value),
11659            };
11660            Ok(true)
11661        })
11662    }
11663}
11664
11665/// Read one PowSybl tie line by zero based table position.
11666///
11667/// # Safety
11668/// Pointers and handles must satisfy the crate-level safety requirements.
11669#[unsafe(no_mangle)]
11670pub unsafe extern "C" fn pio_detailed_connectivity_tie_line_at(
11671    details: *const PioDetailedConnectivity,
11672    index: usize,
11673    output: *mut PioTieLineView,
11674    error: *mut *mut PioError,
11675) -> bool {
11676    unsafe {
11677        entry(error, false, || {
11678            let details = require_detailed_connectivity(details)?;
11679            let tie = details.tie_lines.get(index).ok_or_else(|| {
11680                boundary_error(
11681                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11682                    format!("tie line index {index} is out of range"),
11683                )
11684            })?;
11685            let (calculation_branch, has_calculation_branch) =
11686                optional_component_id_view(tie.calculation_branch.as_ref());
11687            *require_output(output, "output")? = PioTieLineView {
11688                component: component_id_view(&tie.component),
11689                boundary_line1: component_id_view(&tie.boundary_line1),
11690                boundary_line2: component_id_view(&tie.boundary_line2),
11691                calculation_branch,
11692                has_calculation_branch,
11693            };
11694            Ok(true)
11695        })
11696    }
11697}
11698
11699/// Read one transformer tap changer by zero based table position.
11700///
11701/// # Safety
11702/// Pointers and handles must satisfy the crate-level safety requirements.
11703#[unsafe(no_mangle)]
11704pub unsafe extern "C" fn pio_detailed_connectivity_tap_changer_at(
11705    details: *const PioDetailedConnectivity,
11706    index: usize,
11707    output: *mut PioTapChangerView,
11708    error: *mut *mut PioError,
11709) -> bool {
11710    unsafe {
11711        entry(error, false, || {
11712            let details = require_detailed_connectivity(details)?;
11713            let changer = details.tap_changers.get(index).ok_or_else(|| {
11714                boundary_error(
11715                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11716                    format!("tap changer index {index} is out of range"),
11717                )
11718            })?;
11719            let (regulation_mode, has_regulation_mode) =
11720                changer
11721                    .regulation_mode
11722                    .map_or((PioStringView::EMPTY, false), |mode| {
11723                        (
11724                            PioStringView::new(tap_changer_regulation_mode_name(mode)),
11725                            true,
11726                        )
11727                    });
11728            let (regulation_terminal, has_regulation_terminal) =
11729                terminal_reference_view(changer.regulation_terminal.as_ref());
11730            let (component, has_component) = optional_component_id_view(changer.component.as_ref());
11731            *require_output(output, "output")? = PioTapChangerView {
11732                component,
11733                has_component,
11734                transformer: component_id_view(&changer.transformer),
11735                winding: changer.winding,
11736                kind: PioStringView::new(tap_changer_kind_name(changer.kind)),
11737                tap_position: changer.tap_position.unwrap_or(0),
11738                has_tap_position: changer.tap_position.is_some(),
11739                solved_tap_position: changer.solved_tap_position.unwrap_or(0),
11740                has_solved_tap_position: changer.solved_tap_position.is_some(),
11741                low_tap_position: changer.low_tap_position,
11742                neutral_tap_position: changer.neutral_tap_position.unwrap_or(0),
11743                has_neutral_tap_position: changer.neutral_tap_position.is_some(),
11744                normal_tap_position: changer.normal_tap_position.unwrap_or(0),
11745                has_normal_tap_position: changer.normal_tap_position.is_some(),
11746                voltage_step_increment_percent: changer
11747                    .voltage_step_increment_percent
11748                    .unwrap_or(0.0),
11749                has_voltage_step_increment_percent: changer
11750                    .voltage_step_increment_percent
11751                    .is_some(),
11752                load_tap_changing_capabilities: changer.load_tap_changing_capabilities,
11753                regulating: changer.regulating,
11754                regulation_mode,
11755                has_regulation_mode,
11756                regulation_value: changer.regulation_value.unwrap_or(0.0),
11757                has_regulation_value: changer.regulation_value.is_some(),
11758                target_deadband: changer.target_deadband.unwrap_or(0.0),
11759                has_target_deadband: changer.target_deadband.is_some(),
11760                regulation_terminal,
11761                has_regulation_terminal,
11762                step_count: changer.steps.len(),
11763            };
11764            Ok(true)
11765        })
11766    }
11767}
11768
11769/// Read one transformer tap changer step by zero based position.
11770///
11771/// # Safety
11772/// Pointers and handles must satisfy the crate-level safety requirements.
11773#[unsafe(no_mangle)]
11774pub unsafe extern "C" fn pio_detailed_connectivity_tap_changer_step_at(
11775    details: *const PioDetailedConnectivity,
11776    tap_changer_index: usize,
11777    step_index: usize,
11778    output: *mut PioTapChangerStepView,
11779    error: *mut *mut PioError,
11780) -> bool {
11781    unsafe {
11782        entry(error, false, || {
11783            let details = require_detailed_connectivity(details)?;
11784            let changer = details.tap_changers.get(tap_changer_index).ok_or_else(|| {
11785                boundary_error(
11786                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11787                    format!("tap changer index {tap_changer_index} is out of range"),
11788                )
11789            })?;
11790            let step = changer.steps.get(step_index).ok_or_else(|| {
11791                boundary_error(
11792                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11793                    format!("tap changer step index {step_index} is out of range"),
11794                )
11795            })?;
11796            *require_output(output, "output")? = PioTapChangerStepView {
11797                position: step.position,
11798                ratio_pu: step.rho,
11799                phase_shift_degrees: step.alpha_degrees,
11800                resistance_deviation_percent: step.resistance_deviation_percent,
11801                reactance_deviation_percent: step.reactance_deviation_percent,
11802                conductance_deviation_percent: step.conductance_deviation_percent,
11803                susceptance_deviation_percent: step.susceptance_deviation_percent,
11804            };
11805            Ok(true)
11806        })
11807    }
11808}
11809
11810/// Read reactive limits retained for one equipment record.
11811///
11812/// # Safety
11813/// Pointers and handles must satisfy the crate-level safety requirements.
11814#[unsafe(no_mangle)]
11815pub unsafe extern "C" fn pio_detailed_connectivity_equipment_reactive_limits_at(
11816    details: *const PioDetailedConnectivity,
11817    index: usize,
11818    output: *mut PioEquipmentReactiveLimitsView,
11819    error: *mut *mut PioError,
11820) -> bool {
11821    unsafe {
11822        entry(error, false, || {
11823            let details = require_detailed_connectivity(details)?;
11824            let record = details
11825                .equipment_reactive_limits
11826                .get(index)
11827                .ok_or_else(|| {
11828                    boundary_error(
11829                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11830                        format!("equipment reactive limits index {index} is out of range"),
11831                    )
11832                })?;
11833            *require_output(output, "output")? = PioEquipmentReactiveLimitsView {
11834                equipment: component_id_view(&record.equipment),
11835                limits: reactive_limits_view(Some(&record.limits)).0,
11836            };
11837            Ok(true)
11838        })
11839    }
11840}
11841
11842/// Read one property from an equipment reactive limit record.
11843///
11844/// # Safety
11845/// Pointers and handles must satisfy the crate-level safety requirements.
11846#[unsafe(no_mangle)]
11847pub unsafe extern "C" fn pio_detailed_connectivity_equipment_reactive_limit_property_at(
11848    details: *const PioDetailedConnectivity,
11849    equipment_index: usize,
11850    property_index: usize,
11851    output: *mut PioStringPropertyView,
11852    error: *mut *mut PioError,
11853) -> bool {
11854    unsafe {
11855        entry(error, false, || {
11856            let details = require_detailed_connectivity(details)?;
11857            let record = details
11858                .equipment_reactive_limits
11859                .get(equipment_index)
11860                .ok_or_else(|| {
11861                    boundary_error(
11862                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11863                        format!(
11864                            "equipment reactive limits index {equipment_index} is out of range"
11865                        ),
11866                    )
11867                })?;
11868            *require_output(output, "output")? =
11869                string_property_view(reactive_limit_properties(&record.limits)?, property_index)?;
11870            Ok(true)
11871        })
11872    }
11873}
11874
11875/// Read one point from an equipment reactive capability curve.
11876///
11877/// # Safety
11878/// Pointers and handles must satisfy the crate-level safety requirements.
11879#[unsafe(no_mangle)]
11880pub unsafe extern "C" fn pio_detailed_connectivity_equipment_reactive_capability_point_at(
11881    details: *const PioDetailedConnectivity,
11882    equipment_index: usize,
11883    point_index: usize,
11884    output: *mut PioReactiveCapabilityCurvePointView,
11885    error: *mut *mut PioError,
11886) -> bool {
11887    unsafe {
11888        entry(error, false, || {
11889            let details = require_detailed_connectivity(details)?;
11890            let record = details
11891                .equipment_reactive_limits
11892                .get(equipment_index)
11893                .ok_or_else(|| {
11894                    boundary_error(
11895                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11896                        format!(
11897                            "equipment reactive limits index {equipment_index} is out of range"
11898                        ),
11899                    )
11900                })?;
11901            let curve = reactive_capability_curve(&record.limits).ok_or_else(|| {
11902                boundary_error(
11903                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
11904                    format!(
11905                        "equipment reactive limits {equipment_index} is not a capability curve"
11906                    ),
11907                )
11908            })?;
11909            *require_output(output, "output")? =
11910                reactive_capability_point_view(curve, point_index)?;
11911            Ok(true)
11912        })
11913    }
11914}
11915
11916/// Read one property from an equipment reactive capability curve point.
11917///
11918/// # Safety
11919/// Pointers and handles must satisfy the crate-level safety requirements.
11920#[unsafe(no_mangle)]
11921pub unsafe extern "C" fn pio_detailed_connectivity_equipment_reactive_capability_point_property_at(
11922    details: *const PioDetailedConnectivity,
11923    equipment_index: usize,
11924    point_index: usize,
11925    property_index: usize,
11926    output: *mut PioStringPropertyView,
11927    error: *mut *mut PioError,
11928) -> bool {
11929    unsafe {
11930        entry(error, false, || {
11931            let details = require_detailed_connectivity(details)?;
11932            let record = details
11933                .equipment_reactive_limits
11934                .get(equipment_index)
11935                .ok_or_else(|| {
11936                    boundary_error(
11937                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11938                        format!(
11939                            "equipment reactive limits index {equipment_index} is out of range"
11940                        ),
11941                    )
11942                })?;
11943            let curve = reactive_capability_curve(&record.limits).ok_or_else(|| {
11944                boundary_error(
11945                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
11946                    format!(
11947                        "equipment reactive limits {equipment_index} is not a capability curve"
11948                    ),
11949                )
11950            })?;
11951            let point = curve.points.get(point_index).ok_or_else(|| {
11952                boundary_error(
11953                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11954                    format!("reactive capability point index {point_index} is out of range"),
11955                )
11956            })?;
11957            *require_output(output, "output")? =
11958                string_property_view(&point.properties, property_index)?;
11959            Ok(true)
11960        })
11961    }
11962}
11963
11964/// Read one DC converter unit by zero based table position.
11965///
11966/// # Safety
11967/// Pointers and handles must satisfy the crate-level safety requirements.
11968#[unsafe(no_mangle)]
11969pub unsafe extern "C" fn pio_detailed_connectivity_dc_converter_unit_at(
11970    details: *const PioDetailedConnectivity,
11971    index: usize,
11972    output: *mut PioDcConverterUnitView,
11973    error: *mut *mut PioError,
11974) -> bool {
11975    unsafe {
11976        entry(error, false, || {
11977            let details = require_detailed_connectivity(details)?;
11978            let unit = details.dc_converter_units.get(index).ok_or_else(|| {
11979                boundary_error(
11980                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11981                    format!("DC converter unit index {index} is out of range"),
11982                )
11983            })?;
11984            let (substation, has_substation) = optional_component_id_view(unit.substation.as_ref());
11985            *require_output(output, "output")? = PioDcConverterUnitView {
11986                component: component_id_view(&unit.component),
11987                substation,
11988                has_substation,
11989                operation_mode: PioStringView::new(dc_converter_operation_mode_name(
11990                    unit.operation_mode,
11991                )),
11992            };
11993            Ok(true)
11994        })
11995    }
11996}
11997
11998/// Read one DC topological node by zero based table position.
11999///
12000/// # Safety
12001/// Pointers and handles must satisfy the crate-level safety requirements.
12002#[unsafe(no_mangle)]
12003pub unsafe extern "C" fn pio_detailed_connectivity_dc_topological_node_at(
12004    details: *const PioDetailedConnectivity,
12005    index: usize,
12006    output: *mut PioDcNodeView,
12007    error: *mut *mut PioError,
12008) -> bool {
12009    unsafe {
12010        entry(error, false, || {
12011            let details = require_detailed_connectivity(details)?;
12012            let node = details.dc_topological_nodes.get(index).ok_or_else(|| {
12013                boundary_error(
12014                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12015                    format!("DC topological node index {index} is out of range"),
12016                )
12017            })?;
12018            let (dc_converter_unit, has_dc_converter_unit) =
12019                optional_component_id_view(node.dc_converter_unit.as_ref());
12020            *require_output(output, "output")? = PioDcNodeView {
12021                component: component_id_view(&node.component),
12022                kind: PioStringView::new("topological_node"),
12023                nominal_voltage_kv: 0.0,
12024                has_nominal_voltage: false,
12025                voltage_kv: 0.0,
12026                has_voltage: false,
12027                dc_converter_unit,
12028                has_dc_converter_unit,
12029                dc_topological_node: empty_component_id_view(),
12030                has_dc_topological_node: false,
12031            };
12032            Ok(true)
12033        })
12034    }
12035}
12036
12037/// Read one physical DC node by zero based table position.
12038///
12039/// # Safety
12040/// Pointers and handles must satisfy the crate-level safety requirements.
12041#[unsafe(no_mangle)]
12042pub unsafe extern "C" fn pio_detailed_connectivity_dc_node_at(
12043    details: *const PioDetailedConnectivity,
12044    index: usize,
12045    output: *mut PioDcNodeView,
12046    error: *mut *mut PioError,
12047) -> bool {
12048    unsafe {
12049        entry(error, false, || {
12050            let details = require_detailed_connectivity(details)?;
12051            let node = details.dc_nodes.get(index).ok_or_else(|| {
12052                boundary_error(
12053                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12054                    format!("DC node index {index} is out of range"),
12055                )
12056            })?;
12057            let (dc_converter_unit, has_dc_converter_unit) =
12058                optional_component_id_view(node.dc_converter_unit.as_ref());
12059            let (dc_topological_node, has_dc_topological_node) =
12060                optional_component_id_view(node.dc_topological_node.as_ref());
12061            *require_output(output, "output")? = PioDcNodeView {
12062                component: component_id_view(&node.component),
12063                kind: PioStringView::new("node"),
12064                nominal_voltage_kv: node.nominal_voltage_kv.unwrap_or(0.0),
12065                has_nominal_voltage: node.nominal_voltage_kv.is_some(),
12066                voltage_kv: node.voltage_kv.unwrap_or(0.0),
12067                has_voltage: node.voltage_kv.is_some(),
12068                dc_converter_unit,
12069                has_dc_converter_unit,
12070                dc_topological_node,
12071                has_dc_topological_node,
12072            };
12073            Ok(true)
12074        })
12075    }
12076}
12077
12078/// Read one DC ground by zero based table position.
12079///
12080/// # Safety
12081/// Pointers and handles must satisfy the crate-level safety requirements.
12082#[unsafe(no_mangle)]
12083pub unsafe extern "C" fn pio_detailed_connectivity_dc_ground_at(
12084    details: *const PioDetailedConnectivity,
12085    index: usize,
12086    output: *mut PioDcEquipmentView,
12087    error: *mut *mut PioError,
12088) -> bool {
12089    unsafe {
12090        entry(error, false, || {
12091            let details = require_detailed_connectivity(details)?;
12092            let record = details.dc_grounds.get(index).ok_or_else(|| {
12093                boundary_error(
12094                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12095                    format!("DC ground index {index} is out of range"),
12096                )
12097            })?;
12098            *require_output(output, "output")? = dc_equipment_view(
12099                &record.component,
12100                record.equipment_container.as_ref(),
12101                "ground",
12102                &record.dc_terminal,
12103                None,
12104                record.rated_dc_voltage_kv,
12105                record.resistance_ohm,
12106                record.inductance_h,
12107                None,
12108                None,
12109                None,
12110                None,
12111            );
12112            Ok(true)
12113        })
12114    }
12115}
12116
12117/// Read one DC busbar by zero based table position.
12118///
12119/// # Safety
12120/// Pointers and handles must satisfy the crate-level safety requirements.
12121#[unsafe(no_mangle)]
12122pub unsafe extern "C" fn pio_detailed_connectivity_dc_busbar_at(
12123    details: *const PioDetailedConnectivity,
12124    index: usize,
12125    output: *mut PioDcEquipmentView,
12126    error: *mut *mut PioError,
12127) -> bool {
12128    unsafe {
12129        entry(error, false, || {
12130            let details = require_detailed_connectivity(details)?;
12131            let record = details.dc_busbars.get(index).ok_or_else(|| {
12132                boundary_error(
12133                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12134                    format!("DC busbar index {index} is out of range"),
12135                )
12136            })?;
12137            *require_output(output, "output")? = dc_equipment_view(
12138                &record.component,
12139                record.equipment_container.as_ref(),
12140                "busbar",
12141                &record.dc_terminal,
12142                None,
12143                record.rated_dc_voltage_kv,
12144                None,
12145                None,
12146                None,
12147                None,
12148                None,
12149                None,
12150            );
12151            Ok(true)
12152        })
12153    }
12154}
12155
12156/// Read one DC line by zero based table position.
12157///
12158/// # Safety
12159/// Pointers and handles must satisfy the crate-level safety requirements.
12160#[unsafe(no_mangle)]
12161pub unsafe extern "C" fn pio_detailed_connectivity_dc_line_at(
12162    details: *const PioDetailedConnectivity,
12163    index: usize,
12164    output: *mut PioDcEquipmentView,
12165    error: *mut *mut PioError,
12166) -> bool {
12167    unsafe {
12168        entry(error, false, || {
12169            let details = require_detailed_connectivity(details)?;
12170            let record = details.dc_lines.get(index).ok_or_else(|| {
12171                boundary_error(
12172                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12173                    format!("DC line index {index} is out of range"),
12174                )
12175            })?;
12176            *require_output(output, "output")? = dc_equipment_view(
12177                &record.component,
12178                record.equipment_container.as_ref(),
12179                "line",
12180                &record.dc_terminal1,
12181                Some(&record.dc_terminal2),
12182                record.rated_dc_voltage_kv,
12183                record.resistance_ohm,
12184                record.inductance_h,
12185                record.capacitance_f,
12186                record.length_km,
12187                None,
12188                None,
12189            );
12190            Ok(true)
12191        })
12192    }
12193}
12194
12195/// Read one DC series device by zero based table position.
12196///
12197/// # Safety
12198/// Pointers and handles must satisfy the crate-level safety requirements.
12199#[unsafe(no_mangle)]
12200pub unsafe extern "C" fn pio_detailed_connectivity_dc_series_device_at(
12201    details: *const PioDetailedConnectivity,
12202    index: usize,
12203    output: *mut PioDcEquipmentView,
12204    error: *mut *mut PioError,
12205) -> bool {
12206    unsafe {
12207        entry(error, false, || {
12208            let details = require_detailed_connectivity(details)?;
12209            let record = details.dc_series_devices.get(index).ok_or_else(|| {
12210                boundary_error(
12211                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12212                    format!("DC series device index {index} is out of range"),
12213                )
12214            })?;
12215            *require_output(output, "output")? = dc_equipment_view(
12216                &record.component,
12217                record.equipment_container.as_ref(),
12218                "series_device",
12219                &record.dc_terminal1,
12220                Some(&record.dc_terminal2),
12221                record.rated_dc_voltage_kv,
12222                record.resistance_ohm,
12223                record.inductance_h,
12224                None,
12225                None,
12226                None,
12227                None,
12228            );
12229            Ok(true)
12230        })
12231    }
12232}
12233
12234/// Read one DC switch by zero based table position.
12235///
12236/// # Safety
12237/// Pointers and handles must satisfy the crate-level safety requirements.
12238#[unsafe(no_mangle)]
12239pub unsafe extern "C" fn pio_detailed_connectivity_dc_switch_at(
12240    details: *const PioDetailedConnectivity,
12241    index: usize,
12242    output: *mut PioDcEquipmentView,
12243    error: *mut *mut PioError,
12244) -> bool {
12245    unsafe {
12246        entry(error, false, || {
12247            let details = require_detailed_connectivity(details)?;
12248            let record = details.dc_switches.get(index).ok_or_else(|| {
12249                boundary_error(
12250                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12251                    format!("DC switch index {index} is out of range"),
12252                )
12253            })?;
12254            *require_output(output, "output")? = dc_equipment_view(
12255                &record.component,
12256                record.equipment_container.as_ref(),
12257                "switch",
12258                &record.dc_terminal1,
12259                Some(&record.dc_terminal2),
12260                record.rated_dc_voltage_kv,
12261                record.resistance_ohm,
12262                None,
12263                None,
12264                None,
12265                Some(record.kind),
12266                record.open,
12267            );
12268            Ok(true)
12269        })
12270    }
12271}
12272
12273/// Read one voltage source converter by zero based table position.
12274///
12275/// # Safety
12276/// Pointers and handles must satisfy the crate-level safety requirements.
12277#[unsafe(no_mangle)]
12278pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_at(
12279    details: *const PioDetailedConnectivity,
12280    index: usize,
12281    output: *mut PioAcDcConverterView,
12282    error: *mut *mut PioError,
12283) -> bool {
12284    unsafe {
12285        entry(error, false, || {
12286            let details = require_detailed_connectivity(details)?;
12287            let converter = details
12288                .voltage_source_converters
12289                .get(index)
12290                .ok_or_else(|| {
12291                    boundary_error(
12292                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12293                        format!("voltage source converter index {index} is out of range"),
12294                    )
12295                })?;
12296            let (dc_converter_unit, has_dc_converter_unit) =
12297                optional_component_id_view(converter.dc_converter_unit.as_ref());
12298            let (control_mode, has_control_mode) =
12299                converter
12300                    .control_mode
12301                    .map_or((PioStringView::EMPTY, false), |mode| {
12302                        (
12303                            PioStringView::new(ac_dc_converter_control_mode_name(mode)),
12304                            true,
12305                        )
12306                    });
12307            let (pcc_terminal, has_pcc_terminal) =
12308                terminal_reference_view(converter.pcc_terminal.as_ref());
12309            let (reactive_limits, has_reactive_limits) =
12310                reactive_limits_view(converter.reactive_limits.as_ref());
12311            *require_output(output, "output")? = PioAcDcConverterView {
12312                component: component_id_view(&converter.component),
12313                kind: PioStringView::new("voltage_source"),
12314                dc_converter_unit,
12315                has_dc_converter_unit,
12316                dc_terminal1: dc_terminal_view(&converter.dc_terminal1),
12317                dc_terminal2: dc_terminal_view(&converter.dc_terminal2),
12318                base_apparent_power_mva: converter.base_apparent_power_mva.unwrap_or(0.0),
12319                has_base_apparent_power: converter.base_apparent_power_mva.is_some(),
12320                minimum_active_power_mw: converter.minimum_active_power_mw.unwrap_or(0.0),
12321                has_minimum_active_power: converter.minimum_active_power_mw.is_some(),
12322                maximum_active_power_mw: converter.maximum_active_power_mw.unwrap_or(0.0),
12323                has_maximum_active_power: converter.maximum_active_power_mw.is_some(),
12324                minimum_dc_voltage_kv: converter.minimum_dc_voltage_kv.unwrap_or(0.0),
12325                has_minimum_dc_voltage: converter.minimum_dc_voltage_kv.is_some(),
12326                maximum_dc_voltage_kv: converter.maximum_dc_voltage_kv.unwrap_or(0.0),
12327                has_maximum_dc_voltage: converter.maximum_dc_voltage_kv.is_some(),
12328                rated_dc_voltage_kv: converter.rated_dc_voltage_kv.unwrap_or(0.0),
12329                has_rated_dc_voltage: converter.rated_dc_voltage_kv.is_some(),
12330                valve_u0_kv: converter.valve_u0_kv.unwrap_or(0.0),
12331                has_valve_u0: converter.valve_u0_kv.is_some(),
12332                number_of_valves: converter.number_of_valves.unwrap_or(0),
12333                has_number_of_valves: converter.number_of_valves.is_some(),
12334                idle_loss_mw: converter.idle_loss_mw.unwrap_or(0.0),
12335                has_idle_loss: converter.idle_loss_mw.is_some(),
12336                switching_loss_mw_per_ampere: converter.switching_loss_mw_per_ampere.unwrap_or(0.0),
12337                has_switching_loss: converter.switching_loss_mw_per_ampere.is_some(),
12338                resistive_loss_ohm: converter.resistive_loss_ohm.unwrap_or(0.0),
12339                has_resistive_loss: converter.resistive_loss_ohm.is_some(),
12340                control_mode,
12341                has_control_mode,
12342                active_power_at_pcc_mw: converter.active_power_at_pcc_mw.unwrap_or(0.0),
12343                has_active_power_at_pcc: converter.active_power_at_pcc_mw.is_some(),
12344                reactive_power_at_pcc_mvar: converter.reactive_power_at_pcc_mvar.unwrap_or(0.0),
12345                has_reactive_power_at_pcc: converter.reactive_power_at_pcc_mvar.is_some(),
12346                target_active_power_mw: converter.target_active_power_mw.unwrap_or(0.0),
12347                has_target_active_power: converter.target_active_power_mw.is_some(),
12348                target_dc_voltage_kv: converter.target_dc_voltage_kv.unwrap_or(0.0),
12349                has_target_dc_voltage: converter.target_dc_voltage_kv.is_some(),
12350                pcc_terminal,
12351                has_pcc_terminal,
12352                droop_curve_segment_count: converter
12353                    .droop_curve
12354                    .as_ref()
12355                    .map_or(0, |curve| curve.segments.len()),
12356                has_droop_curve: converter.droop_curve.is_some(),
12357                droop: converter.droop.unwrap_or(0.0),
12358                has_droop: converter.droop.is_some(),
12359                droop_compensation: converter.droop_compensation.unwrap_or(0.0),
12360                has_droop_compensation: converter.droop_compensation.is_some(),
12361                q_share: converter.q_share.unwrap_or(0.0),
12362                has_q_share: converter.q_share.is_some(),
12363                maximum_modulation_index: converter.maximum_modulation_index.unwrap_or(0.0),
12364                has_maximum_modulation_index: converter.maximum_modulation_index.is_some(),
12365                maximum_valve_current_a: converter.maximum_valve_current_a.unwrap_or(0.0),
12366                has_maximum_valve_current: converter.maximum_valve_current_a.is_some(),
12367                dc_current_a: converter.dc_current_a.unwrap_or(0.0),
12368                has_dc_current: converter.dc_current_a.is_some(),
12369                ac_voltage_kv: converter.ac_voltage_kv.unwrap_or(0.0),
12370                has_ac_voltage: converter.ac_voltage_kv.is_some(),
12371                dc_voltage_kv: converter.dc_voltage_kv.unwrap_or(0.0),
12372                has_dc_voltage: converter.dc_voltage_kv.is_some(),
12373                voltage_regulator_on: converter.voltage_regulator_on.unwrap_or(false),
12374                has_voltage_regulator_on: converter.voltage_regulator_on.is_some(),
12375                voltage_setpoint_kv: converter.voltage_setpoint_kv.unwrap_or(0.0),
12376                has_voltage_setpoint: converter.voltage_setpoint_kv.is_some(),
12377                reactive_power_setpoint_mvar: converter.reactive_power_setpoint_mvar.unwrap_or(0.0),
12378                has_reactive_power_setpoint: converter.reactive_power_setpoint_mvar.is_some(),
12379                reactive_limits,
12380                has_reactive_limits,
12381                pole_loss_active_power_mw: converter.pole_loss_active_power_mw.unwrap_or(0.0),
12382                has_pole_loss_active_power: converter.pole_loss_active_power_mw.is_some(),
12383                reactive_model: PioStringView::EMPTY,
12384                has_reactive_model: false,
12385                power_factor: 0.0,
12386                has_power_factor: false,
12387                operating_mode: PioStringView::EMPTY,
12388                has_operating_mode: false,
12389                rated_dc_current_a: 0.0,
12390                has_rated_dc_current: false,
12391                minimum_alpha_degrees: 0.0,
12392                has_minimum_alpha: false,
12393                maximum_alpha_degrees: 0.0,
12394                has_maximum_alpha: false,
12395                minimum_gamma_degrees: 0.0,
12396                has_minimum_gamma: false,
12397                maximum_gamma_degrees: 0.0,
12398                has_maximum_gamma: false,
12399                target_alpha_degrees: 0.0,
12400                has_target_alpha: false,
12401                target_gamma_degrees: 0.0,
12402                has_target_gamma: false,
12403                target_dc_current_a: 0.0,
12404                has_target_dc_current: false,
12405                alpha_degrees: 0.0,
12406                has_alpha: false,
12407                gamma_degrees: 0.0,
12408                has_gamma: false,
12409                delta_degrees: converter.delta_degrees.unwrap_or(0.0),
12410                has_delta: converter.delta_degrees.is_some(),
12411                uf_kv: converter.uf_kv.unwrap_or(0.0),
12412                has_uf: converter.uf_kv.is_some(),
12413                uv_kv: converter.uv_kv.unwrap_or(0.0),
12414                has_uv: converter.uv_kv.is_some(),
12415            };
12416            Ok(true)
12417        })
12418    }
12419}
12420
12421/// Read one line commutated converter by zero based table position.
12422///
12423/// # Safety
12424/// Pointers and handles must satisfy the crate-level safety requirements.
12425#[unsafe(no_mangle)]
12426pub unsafe extern "C" fn pio_detailed_connectivity_line_commutated_converter_at(
12427    details: *const PioDetailedConnectivity,
12428    index: usize,
12429    output: *mut PioAcDcConverterView,
12430    error: *mut *mut PioError,
12431) -> bool {
12432    unsafe {
12433        entry(error, false, || {
12434            let details = require_detailed_connectivity(details)?;
12435            let converter = details
12436                .line_commutated_converters
12437                .get(index)
12438                .ok_or_else(|| {
12439                    boundary_error(
12440                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12441                        format!("line commutated converter index {index} is out of range"),
12442                    )
12443                })?;
12444            let (dc_converter_unit, has_dc_converter_unit) =
12445                optional_component_id_view(converter.dc_converter_unit.as_ref());
12446            let (control_mode, has_control_mode) =
12447                converter
12448                    .control_mode
12449                    .map_or((PioStringView::EMPTY, false), |mode| {
12450                        (
12451                            PioStringView::new(ac_dc_converter_control_mode_name(mode)),
12452                            true,
12453                        )
12454                    });
12455            let (reactive_model, has_reactive_model) =
12456                converter
12457                    .reactive_model
12458                    .map_or((PioStringView::EMPTY, false), |model| {
12459                        (
12460                            PioStringView::new(line_commutated_converter_reactive_model_name(
12461                                model,
12462                            )),
12463                            true,
12464                        )
12465                    });
12466            let (operating_mode, has_operating_mode) =
12467                converter
12468                    .operating_mode
12469                    .map_or((PioStringView::EMPTY, false), |mode| {
12470                        (
12471                            PioStringView::new(line_commutated_converter_operating_mode_name(mode)),
12472                            true,
12473                        )
12474                    });
12475            let (pcc_terminal, has_pcc_terminal) =
12476                terminal_reference_view(converter.pcc_terminal.as_ref());
12477            *require_output(output, "output")? = PioAcDcConverterView {
12478                component: component_id_view(&converter.component),
12479                kind: PioStringView::new("line_commutated"),
12480                dc_converter_unit,
12481                has_dc_converter_unit,
12482                dc_terminal1: dc_terminal_view(&converter.dc_terminal1),
12483                dc_terminal2: dc_terminal_view(&converter.dc_terminal2),
12484                base_apparent_power_mva: converter.base_apparent_power_mva.unwrap_or(0.0),
12485                has_base_apparent_power: converter.base_apparent_power_mva.is_some(),
12486                minimum_active_power_mw: converter.minimum_active_power_mw.unwrap_or(0.0),
12487                has_minimum_active_power: converter.minimum_active_power_mw.is_some(),
12488                maximum_active_power_mw: converter.maximum_active_power_mw.unwrap_or(0.0),
12489                has_maximum_active_power: converter.maximum_active_power_mw.is_some(),
12490                minimum_dc_voltage_kv: converter.minimum_dc_voltage_kv.unwrap_or(0.0),
12491                has_minimum_dc_voltage: converter.minimum_dc_voltage_kv.is_some(),
12492                maximum_dc_voltage_kv: converter.maximum_dc_voltage_kv.unwrap_or(0.0),
12493                has_maximum_dc_voltage: converter.maximum_dc_voltage_kv.is_some(),
12494                rated_dc_voltage_kv: converter.rated_dc_voltage_kv.unwrap_or(0.0),
12495                has_rated_dc_voltage: converter.rated_dc_voltage_kv.is_some(),
12496                valve_u0_kv: converter.valve_u0_kv.unwrap_or(0.0),
12497                has_valve_u0: converter.valve_u0_kv.is_some(),
12498                number_of_valves: converter.number_of_valves.unwrap_or(0),
12499                has_number_of_valves: converter.number_of_valves.is_some(),
12500                idle_loss_mw: converter.idle_loss_mw.unwrap_or(0.0),
12501                has_idle_loss: converter.idle_loss_mw.is_some(),
12502                switching_loss_mw_per_ampere: converter.switching_loss_mw_per_ampere.unwrap_or(0.0),
12503                has_switching_loss: converter.switching_loss_mw_per_ampere.is_some(),
12504                resistive_loss_ohm: converter.resistive_loss_ohm.unwrap_or(0.0),
12505                has_resistive_loss: converter.resistive_loss_ohm.is_some(),
12506                control_mode,
12507                has_control_mode,
12508                active_power_at_pcc_mw: converter.active_power_at_pcc_mw.unwrap_or(0.0),
12509                has_active_power_at_pcc: converter.active_power_at_pcc_mw.is_some(),
12510                reactive_power_at_pcc_mvar: converter.reactive_power_at_pcc_mvar.unwrap_or(0.0),
12511                has_reactive_power_at_pcc: converter.reactive_power_at_pcc_mvar.is_some(),
12512                target_active_power_mw: converter.target_active_power_mw.unwrap_or(0.0),
12513                has_target_active_power: converter.target_active_power_mw.is_some(),
12514                target_dc_voltage_kv: converter.target_dc_voltage_kv.unwrap_or(0.0),
12515                has_target_dc_voltage: converter.target_dc_voltage_kv.is_some(),
12516                pcc_terminal,
12517                has_pcc_terminal,
12518                droop_curve_segment_count: converter
12519                    .droop_curve
12520                    .as_ref()
12521                    .map_or(0, |curve| curve.segments.len()),
12522                has_droop_curve: converter.droop_curve.is_some(),
12523                droop: 0.0,
12524                has_droop: false,
12525                droop_compensation: 0.0,
12526                has_droop_compensation: false,
12527                q_share: 0.0,
12528                has_q_share: false,
12529                maximum_modulation_index: 0.0,
12530                has_maximum_modulation_index: false,
12531                maximum_valve_current_a: 0.0,
12532                has_maximum_valve_current: false,
12533                dc_current_a: converter.dc_current_a.unwrap_or(0.0),
12534                has_dc_current: converter.dc_current_a.is_some(),
12535                ac_voltage_kv: converter.ac_voltage_kv.unwrap_or(0.0),
12536                has_ac_voltage: converter.ac_voltage_kv.is_some(),
12537                dc_voltage_kv: converter.dc_voltage_kv.unwrap_or(0.0),
12538                has_dc_voltage: converter.dc_voltage_kv.is_some(),
12539                voltage_regulator_on: false,
12540                has_voltage_regulator_on: false,
12541                voltage_setpoint_kv: 0.0,
12542                has_voltage_setpoint: false,
12543                reactive_power_setpoint_mvar: 0.0,
12544                has_reactive_power_setpoint: false,
12545                reactive_limits: empty_reactive_limits_view(),
12546                has_reactive_limits: false,
12547                pole_loss_active_power_mw: converter.pole_loss_active_power_mw.unwrap_or(0.0),
12548                has_pole_loss_active_power: converter.pole_loss_active_power_mw.is_some(),
12549                reactive_model,
12550                has_reactive_model,
12551                power_factor: converter.power_factor.unwrap_or(0.0),
12552                has_power_factor: converter.power_factor.is_some(),
12553                operating_mode,
12554                has_operating_mode,
12555                rated_dc_current_a: converter.rated_dc_current_a.unwrap_or(0.0),
12556                has_rated_dc_current: converter.rated_dc_current_a.is_some(),
12557                minimum_alpha_degrees: converter.minimum_alpha_degrees.unwrap_or(0.0),
12558                has_minimum_alpha: converter.minimum_alpha_degrees.is_some(),
12559                maximum_alpha_degrees: converter.maximum_alpha_degrees.unwrap_or(0.0),
12560                has_maximum_alpha: converter.maximum_alpha_degrees.is_some(),
12561                minimum_gamma_degrees: converter.minimum_gamma_degrees.unwrap_or(0.0),
12562                has_minimum_gamma: converter.minimum_gamma_degrees.is_some(),
12563                maximum_gamma_degrees: converter.maximum_gamma_degrees.unwrap_or(0.0),
12564                has_maximum_gamma: converter.maximum_gamma_degrees.is_some(),
12565                target_alpha_degrees: converter.target_alpha_degrees.unwrap_or(0.0),
12566                has_target_alpha: converter.target_alpha_degrees.is_some(),
12567                target_gamma_degrees: converter.target_gamma_degrees.unwrap_or(0.0),
12568                has_target_gamma: converter.target_gamma_degrees.is_some(),
12569                target_dc_current_a: converter.target_dc_current_a.unwrap_or(0.0),
12570                has_target_dc_current: converter.target_dc_current_a.is_some(),
12571                alpha_degrees: converter.alpha_degrees.unwrap_or(0.0),
12572                has_alpha: converter.alpha_degrees.is_some(),
12573                gamma_degrees: converter.gamma_degrees.unwrap_or(0.0),
12574                has_gamma: converter.gamma_degrees.is_some(),
12575                delta_degrees: 0.0,
12576                has_delta: false,
12577                uf_kv: 0.0,
12578                has_uf: false,
12579                uv_kv: 0.0,
12580                has_uv: false,
12581            };
12582            Ok(true)
12583        })
12584    }
12585}
12586
12587fn droop_curve_segment_view(segment: &powerio_tx::DroopCurveSegment) -> PioDroopCurveSegmentView {
12588    PioDroopCurveSegmentView {
12589        minimum_voltage_kv: segment.minimum_voltage_kv,
12590        maximum_voltage_kv: segment.maximum_voltage_kv,
12591        k: segment.k,
12592    }
12593}
12594
12595/// Read one DC voltage droop curve segment from a voltage source converter.
12596///
12597/// # Safety
12598/// Pointers and handles must satisfy the crate-level safety requirements.
12599#[unsafe(no_mangle)]
12600pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_droop_curve_segment_at(
12601    details: *const PioDetailedConnectivity,
12602    converter_index: usize,
12603    segment_index: usize,
12604    output: *mut PioDroopCurveSegmentView,
12605    error: *mut *mut PioError,
12606) -> bool {
12607    unsafe {
12608        entry(error, false, || {
12609            let details = require_detailed_connectivity(details)?;
12610            let converter = details
12611                .voltage_source_converters
12612                .get(converter_index)
12613                .ok_or_else(|| {
12614                    boundary_error(
12615                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12616                        format!("voltage source converter index {converter_index} is out of range"),
12617                    )
12618                })?;
12619            let curve = converter.droop_curve.as_ref().ok_or_else(|| {
12620                boundary_error(
12621                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
12622                    format!("voltage source converter {converter_index} has no droop curve"),
12623                )
12624            })?;
12625            let segment = curve.segments.get(segment_index).ok_or_else(|| {
12626                boundary_error(
12627                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12628                    format!("droop curve segment index {segment_index} is out of range"),
12629                )
12630            })?;
12631            *require_output(output, "output")? = droop_curve_segment_view(segment);
12632            Ok(true)
12633        })
12634    }
12635}
12636
12637/// Read one DC voltage droop curve segment from a line commutated converter.
12638///
12639/// # Safety
12640/// Pointers and handles must satisfy the crate-level safety requirements.
12641#[unsafe(no_mangle)]
12642pub unsafe extern "C" fn pio_detailed_connectivity_line_commutated_converter_droop_curve_segment_at(
12643    details: *const PioDetailedConnectivity,
12644    converter_index: usize,
12645    segment_index: usize,
12646    output: *mut PioDroopCurveSegmentView,
12647    error: *mut *mut PioError,
12648) -> bool {
12649    unsafe {
12650        entry(error, false, || {
12651            let details = require_detailed_connectivity(details)?;
12652            let converter = details
12653                .line_commutated_converters
12654                .get(converter_index)
12655                .ok_or_else(|| {
12656                    boundary_error(
12657                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12658                        format!(
12659                            "line commutated converter index {converter_index} is out of range"
12660                        ),
12661                    )
12662                })?;
12663            let curve = converter.droop_curve.as_ref().ok_or_else(|| {
12664                boundary_error(
12665                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
12666                    format!("line commutated converter {converter_index} has no droop curve"),
12667                )
12668            })?;
12669            let segment = curve.segments.get(segment_index).ok_or_else(|| {
12670                boundary_error(
12671                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12672                    format!("droop curve segment index {segment_index} is out of range"),
12673                )
12674            })?;
12675            *require_output(output, "output")? = droop_curve_segment_view(segment);
12676            Ok(true)
12677        })
12678    }
12679}
12680
12681/// Read one property from a voltage source converter reactive limit record.
12682///
12683/// # Safety
12684/// Pointers and handles must satisfy the crate-level safety requirements.
12685#[unsafe(no_mangle)]
12686pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_reactive_limit_property_at(
12687    details: *const PioDetailedConnectivity,
12688    converter_index: usize,
12689    property_index: usize,
12690    output: *mut PioStringPropertyView,
12691    error: *mut *mut PioError,
12692) -> bool {
12693    unsafe {
12694        entry(error, false, || {
12695            let details = require_detailed_connectivity(details)?;
12696            let converter = details
12697                .voltage_source_converters
12698                .get(converter_index)
12699                .ok_or_else(|| {
12700                    boundary_error(
12701                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12702                        format!("voltage source converter index {converter_index} is out of range"),
12703                    )
12704                })?;
12705            let limits = converter.reactive_limits.as_ref().ok_or_else(|| {
12706                boundary_error(
12707                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
12708                    format!("voltage source converter {converter_index} has no reactive limits"),
12709                )
12710            })?;
12711            *require_output(output, "output")? =
12712                string_property_view(reactive_limit_properties(limits)?, property_index)?;
12713            Ok(true)
12714        })
12715    }
12716}
12717
12718/// Read one point from a voltage source converter reactive capability curve.
12719///
12720/// # Safety
12721/// Pointers and handles must satisfy the crate-level safety requirements.
12722#[unsafe(no_mangle)]
12723pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_reactive_capability_point_at(
12724    details: *const PioDetailedConnectivity,
12725    converter_index: usize,
12726    point_index: usize,
12727    output: *mut PioReactiveCapabilityCurvePointView,
12728    error: *mut *mut PioError,
12729) -> bool {
12730    unsafe {
12731        entry(error, false, || {
12732            let details = require_detailed_connectivity(details)?;
12733            let converter = details
12734                .voltage_source_converters
12735                .get(converter_index)
12736                .ok_or_else(|| {
12737                    boundary_error(
12738                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12739                        format!("voltage source converter index {converter_index} is out of range"),
12740                    )
12741                })?;
12742            let curve = converter
12743                .reactive_limits
12744                .as_ref()
12745                .and_then(reactive_capability_curve)
12746                .ok_or_else(|| {
12747                    boundary_error(
12748                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
12749                        format!(
12750                            "voltage source converter {converter_index} has no reactive capability curve"
12751                        ),
12752                    )
12753                })?;
12754            *require_output(output, "output")? =
12755                reactive_capability_point_view(curve, point_index)?;
12756            Ok(true)
12757        })
12758    }
12759}
12760
12761/// Read one property from a voltage source converter reactive capability point.
12762///
12763/// # Safety
12764/// Pointers and handles must satisfy the crate-level safety requirements.
12765#[unsafe(no_mangle)]
12766pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_reactive_capability_point_property_at(
12767    details: *const PioDetailedConnectivity,
12768    converter_index: usize,
12769    point_index: usize,
12770    property_index: usize,
12771    output: *mut PioStringPropertyView,
12772    error: *mut *mut PioError,
12773) -> bool {
12774    unsafe {
12775        entry(error, false, || {
12776            let details = require_detailed_connectivity(details)?;
12777            let converter = details
12778                .voltage_source_converters
12779                .get(converter_index)
12780                .ok_or_else(|| {
12781                    boundary_error(
12782                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12783                        format!("voltage source converter index {converter_index} is out of range"),
12784                    )
12785                })?;
12786            let curve = converter
12787                .reactive_limits
12788                .as_ref()
12789                .and_then(reactive_capability_curve)
12790                .ok_or_else(|| {
12791                    boundary_error(
12792                        &codes::REQUEST_CAPI_TYPE_MISMATCH,
12793                        format!(
12794                            "voltage source converter {converter_index} has no reactive capability curve"
12795                        ),
12796                    )
12797                })?;
12798            let point = curve.points.get(point_index).ok_or_else(|| {
12799                boundary_error(
12800                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12801                    format!("reactive capability point index {point_index} is out of range"),
12802                )
12803            })?;
12804            *require_output(output, "output")? =
12805                string_property_view(&point.properties, property_index)?;
12806            Ok(true)
12807        })
12808    }
12809}
12810
12811///
12812/// # Safety
12813/// Pointers and handles must satisfy the crate-level safety requirements.
12814/// The input handle must remain live and immutable throughout this call.
12815#[unsafe(no_mangle)]
12816pub unsafe extern "C" fn pio_detailed_connectivity_retain(
12817    details: *const PioDetailedConnectivity,
12818) -> *mut PioDetailedConnectivity {
12819    unsafe { PioDetailedConnectivity::retain_raw(details) }
12820}
12821
12822///
12823/// # Safety
12824/// Pointers and handles must satisfy the crate-level safety requirements.
12825/// A non-null handle must be owned and unused by concurrent calls.
12826#[unsafe(no_mangle)]
12827pub unsafe extern "C" fn pio_detailed_connectivity_release(details: *mut PioDetailedConnectivity) {
12828    unsafe { PioDetailedConnectivity::release_raw(details) };
12829}
12830
12831///
12832/// # Safety
12833/// Pointers and handles must satisfy the crate-level safety requirements.
12834#[unsafe(no_mangle)]
12835pub unsafe extern "C" fn pio_balanced_network_bus_count(
12836    network: *const PioBalancedNetwork,
12837) -> usize {
12838    unsafe { PioBalancedNetwork::get(network) }
12839        .and_then(BalancedNetworkInner::network)
12840        .map_or(0, |network| network.buses().len())
12841}
12842
12843///
12844/// # Safety
12845/// Pointers and handles must satisfy the crate-level safety requirements.
12846#[unsafe(no_mangle)]
12847pub unsafe extern "C" fn pio_balanced_network_branch_count(
12848    network: *const PioBalancedNetwork,
12849) -> usize {
12850    unsafe { PioBalancedNetwork::get(network) }
12851        .and_then(BalancedNetworkInner::network)
12852        .map_or(0, |network| network.branches().len())
12853}
12854
12855///
12856/// # Safety
12857/// Pointers and handles must satisfy the crate-level safety requirements.
12858#[unsafe(no_mangle)]
12859pub unsafe extern "C" fn pio_balanced_network_load_count(
12860    network: *const PioBalancedNetwork,
12861) -> usize {
12862    unsafe { PioBalancedNetwork::get(network) }
12863        .and_then(BalancedNetworkInner::network)
12864        .map_or(0, |network| network.loads().len())
12865}
12866
12867///
12868/// # Safety
12869/// Pointers and handles must satisfy the crate-level safety requirements.
12870#[unsafe(no_mangle)]
12871pub unsafe extern "C" fn pio_balanced_network_shunt_count(
12872    network: *const PioBalancedNetwork,
12873) -> usize {
12874    unsafe { PioBalancedNetwork::get(network) }
12875        .and_then(BalancedNetworkInner::network)
12876        .map_or(0, |network| network.shunts().len())
12877}
12878
12879///
12880/// # Safety
12881/// Pointers and handles must satisfy the crate-level safety requirements.
12882#[unsafe(no_mangle)]
12883pub unsafe extern "C" fn pio_balanced_network_static_var_compensator_count(
12884    network: *const PioBalancedNetwork,
12885) -> usize {
12886    unsafe { PioBalancedNetwork::get(network) }
12887        .and_then(BalancedNetworkInner::network)
12888        .map_or(0, |network| network.static_var_compensators().len())
12889}
12890
12891///
12892/// # Safety
12893/// Pointers and handles must satisfy the crate-level safety requirements.
12894#[unsafe(no_mangle)]
12895pub unsafe extern "C" fn pio_balanced_network_generator_count(
12896    network: *const PioBalancedNetwork,
12897) -> usize {
12898    unsafe { PioBalancedNetwork::get(network) }
12899        .and_then(BalancedNetworkInner::network)
12900        .map_or(0, |network| network.generators().len())
12901}
12902
12903///
12904/// # Safety
12905/// Pointers and handles must satisfy the crate-level safety requirements.
12906#[unsafe(no_mangle)]
12907pub unsafe extern "C" fn pio_balanced_network_storage_count(
12908    network: *const PioBalancedNetwork,
12909) -> usize {
12910    unsafe { PioBalancedNetwork::get(network) }
12911        .and_then(BalancedNetworkInner::network)
12912        .map_or(0, |network| network.storage().len())
12913}
12914
12915///
12916/// # Safety
12917/// Pointers and handles must satisfy the crate-level safety requirements.
12918#[unsafe(no_mangle)]
12919pub unsafe extern "C" fn pio_balanced_network_switch_count(
12920    network: *const PioBalancedNetwork,
12921) -> usize {
12922    unsafe { PioBalancedNetwork::get(network) }
12923        .and_then(BalancedNetworkInner::network)
12924        .map_or(0, |network| network.switches().len())
12925}
12926
12927///
12928/// # Safety
12929/// Pointers and handles must satisfy the crate-level safety requirements.
12930#[unsafe(no_mangle)]
12931pub unsafe extern "C" fn pio_balanced_network_hvdc_count(
12932    network: *const PioBalancedNetwork,
12933) -> usize {
12934    unsafe { PioBalancedNetwork::get(network) }
12935        .and_then(BalancedNetworkInner::network)
12936        .map_or(0, |network| network.hvdc().len())
12937}
12938
12939///
12940/// # Safety
12941/// Pointers and handles must satisfy the crate-level safety requirements.
12942#[unsafe(no_mangle)]
12943pub unsafe extern "C" fn pio_balanced_network_three_winding_transformer_count(
12944    network: *const PioBalancedNetwork,
12945) -> usize {
12946    unsafe { PioBalancedNetwork::get(network) }
12947        .and_then(BalancedNetworkInner::network)
12948        .map_or(0, |network| network.transformers_3w().len())
12949}
12950
12951///
12952/// # Safety
12953/// Pointers and handles must satisfy the crate-level safety requirements.
12954#[unsafe(no_mangle)]
12955pub unsafe extern "C" fn pio_balanced_network_area_count(
12956    network: *const PioBalancedNetwork,
12957) -> usize {
12958    unsafe { PioBalancedNetwork::get(network) }
12959        .and_then(BalancedNetworkInner::network)
12960        .map_or(0, |network| network.areas().len())
12961}
12962
12963unsafe fn require_balanced_network<'a>(
12964    network: *const PioBalancedNetwork,
12965) -> Result<&'a BalancedNetwork, *mut PioError> {
12966    unsafe { PioBalancedNetwork::get(network) }
12967        .and_then(BalancedNetworkInner::network)
12968        .ok_or_else(|| {
12969            boundary_error(
12970                &codes::BIND_CAPI_NULL_HANDLE,
12971                "PioBalancedNetwork must not be NULL",
12972            )
12973        })
12974}
12975
12976unsafe fn require_output<'a, T>(
12977    output: *mut T,
12978    argument: &str,
12979) -> Result<&'a mut T, *mut PioError> {
12980    unsafe { output.as_mut() }.ok_or_else(|| {
12981        boundary_error(
12982            &codes::BIND_CAPI_NULL_ARGUMENT,
12983            format!("{argument} must not be NULL"),
12984        )
12985    })
12986}
12987
12988unsafe fn require_detailed_connectivity<'a>(
12989    details: *const PioDetailedConnectivity,
12990) -> Result<&'a powerio_tx::DetailedConnectivity, *mut PioError> {
12991    unsafe { PioDetailedConnectivity::get(details) }
12992        .and_then(DetailedConnectivityInner::details)
12993        .ok_or_else(|| {
12994            boundary_error(
12995                &codes::BIND_CAPI_NULL_HANDLE,
12996                "PioDetailedConnectivity must not be NULL",
12997            )
12998        })
12999}
13000
13001fn optional_string_view(value: Option<&str>) -> (PioStringView, bool) {
13002    value.map_or((PioStringView::EMPTY, false), |value| {
13003        (PioStringView::new(value), true)
13004    })
13005}
13006
13007fn balanced_coords_kind_name(kind: powerio_tx::CoordsKind) -> &'static str {
13008    match kind {
13009        powerio_tx::CoordsKind::Source => "source",
13010        powerio_tx::CoordsKind::Synthetic => "synthetic",
13011        powerio_tx::CoordsKind::Manual => "manual",
13012        powerio_tx::CoordsKind::Derived => "derived",
13013        _ => "unknown",
13014    }
13015}
13016
13017fn balanced_location_view(location: &powerio_tx::Location) -> PioBalancedLocationView {
13018    let (kind, has_kind) = location.kind.map_or((PioStringView::EMPTY, false), |kind| {
13019        (PioStringView::new(balanced_coords_kind_name(kind)), true)
13020    });
13021    PioBalancedLocationView {
13022        x: location.x,
13023        y: location.y,
13024        kind,
13025        has_kind,
13026    }
13027}
13028
13029fn empty_balanced_location_view() -> PioBalancedLocationView {
13030    PioBalancedLocationView {
13031        x: 0.0,
13032        y: 0.0,
13033        kind: PioStringView::EMPTY,
13034        has_kind: false,
13035    }
13036}
13037
13038fn balanced_geo_view(geo: Option<&powerio_tx::GeoMeta>) -> PioBalancedGeoView {
13039    let Some(geo) = geo else {
13040        return PioBalancedGeoView {
13041            has_geo: false,
13042            space: PioStringView::EMPTY,
13043            crs: PioStringView::EMPTY,
13044            has_crs: false,
13045            kind: PioStringView::EMPTY,
13046            has_kind: false,
13047            has_canvas: false,
13048            canvas_width: 0.0,
13049            has_canvas_width: false,
13050            canvas_height: 0.0,
13051            has_canvas_height: false,
13052            canvas_units: PioStringView::EMPTY,
13053            has_canvas_units: false,
13054        };
13055    };
13056    let (space, crs, has_crs, canvas) = match &geo.space {
13057        powerio_tx::CoordinateSpace::Geographic { crs } => (
13058            "geographic",
13059            crs.as_deref()
13060                .map_or(PioStringView::EMPTY, PioStringView::new),
13061            crs.is_some(),
13062            None,
13063        ),
13064        powerio_tx::CoordinateSpace::Projected { crs } => (
13065            "projected",
13066            crs.as_deref()
13067                .map_or(PioStringView::EMPTY, PioStringView::new),
13068            crs.is_some(),
13069            None,
13070        ),
13071        powerio_tx::CoordinateSpace::Diagram { canvas } => {
13072            ("diagram", PioStringView::EMPTY, false, canvas.as_ref())
13073        }
13074        powerio_tx::CoordinateSpace::Unknown => ("unknown", PioStringView::EMPTY, false, None),
13075        _ => ("unknown", PioStringView::EMPTY, false, None),
13076    };
13077    let (kind, has_kind) = geo.kind.map_or((PioStringView::EMPTY, false), |kind| {
13078        (PioStringView::new(balanced_coords_kind_name(kind)), true)
13079    });
13080    PioBalancedGeoView {
13081        has_geo: true,
13082        space: PioStringView::new(space),
13083        crs,
13084        has_crs,
13085        kind,
13086        has_kind,
13087        has_canvas: canvas.is_some(),
13088        canvas_width: canvas.and_then(|canvas| canvas.width).unwrap_or(0.0),
13089        has_canvas_width: canvas.is_some_and(|canvas| canvas.width.is_some()),
13090        canvas_height: canvas.and_then(|canvas| canvas.height).unwrap_or(0.0),
13091        has_canvas_height: canvas.is_some_and(|canvas| canvas.height.is_some()),
13092        canvas_units: canvas
13093            .and_then(|canvas| canvas.units.as_deref())
13094            .map_or(PioStringView::EMPTY, PioStringView::new),
13095        has_canvas_units: canvas.is_some_and(|canvas| canvas.units.is_some()),
13096    }
13097}
13098
13099fn load_voltage_model_view(load: &powerio_tx::Load) -> PioBalancedLoadVoltageModelView {
13100    let mut view = PioBalancedLoadVoltageModelView {
13101        kind: PioStringView::new("constant_power"),
13102        p_constant_power_mw: load.p,
13103        q_constant_power_mvar: load.q,
13104        p_constant_current_mw: 0.0,
13105        q_constant_current_mvar: 0.0,
13106        p_constant_impedance_mw: 0.0,
13107        q_constant_impedance_mvar: 0.0,
13108        exponential_p_mw: 0.0,
13109        exponential_q_mvar: 0.0,
13110        gamma_p: 0.0,
13111        gamma_q: 0.0,
13112        nominal_voltage_pu: 0.0,
13113        has_nominal_voltage: false,
13114        load_type: 0,
13115        has_load_type: false,
13116        scaling: 0.0,
13117        has_scaling: false,
13118    };
13119    match &load.voltage_model {
13120        None | Some(powerio_tx::LoadVoltageModel::ConstantPower) => {}
13121        Some(powerio_tx::LoadVoltageModel::Zip {
13122            p_constant_power,
13123            q_constant_power,
13124            p_constant_current,
13125            q_constant_current,
13126            p_constant_impedance,
13127            q_constant_impedance,
13128            v_nom,
13129            load_type,
13130            scaling,
13131        }) => {
13132            view.kind = PioStringView::new("zip");
13133            view.p_constant_power_mw = *p_constant_power;
13134            view.q_constant_power_mvar = *q_constant_power;
13135            view.p_constant_current_mw = *p_constant_current;
13136            view.q_constant_current_mvar = *q_constant_current;
13137            view.p_constant_impedance_mw = *p_constant_impedance;
13138            view.q_constant_impedance_mvar = *q_constant_impedance;
13139            if let Some(value) = v_nom {
13140                view.nominal_voltage_pu = *value;
13141                view.has_nominal_voltage = true;
13142            }
13143            if let Some(value) = load_type {
13144                view.load_type = *value;
13145                view.has_load_type = true;
13146            }
13147            if let Some(value) = scaling {
13148                view.scaling = *value;
13149                view.has_scaling = true;
13150            }
13151        }
13152        Some(powerio_tx::LoadVoltageModel::Exponential {
13153            p,
13154            q,
13155            v_nom,
13156            gamma_p,
13157            gamma_q,
13158        }) => {
13159            view.kind = PioStringView::new("exponential");
13160            view.p_constant_power_mw = 0.0;
13161            view.q_constant_power_mvar = 0.0;
13162            view.exponential_p_mw = *p;
13163            view.exponential_q_mvar = *q;
13164            view.gamma_p = *gamma_p;
13165            view.gamma_q = *gamma_q;
13166            if let Some(value) = v_nom {
13167                view.nominal_voltage_pu = *value;
13168                view.has_nominal_voltage = true;
13169            }
13170        }
13171        Some(_) => view.kind = PioStringView::new("unknown"),
13172    }
13173    view
13174}
13175
13176fn switched_shunt_mode_name(mode: powerio_tx::SwitchedShuntMode) -> &'static str {
13177    match mode {
13178        powerio_tx::SwitchedShuntMode::Locked => "locked",
13179        powerio_tx::SwitchedShuntMode::Continuous => "continuous",
13180        powerio_tx::SwitchedShuntMode::Discrete => "discrete",
13181        _ => "unknown",
13182    }
13183}
13184
13185use powerio_tx::network::GEN_EXTRA_KEYS as GENERATOR_CAPABILITY_NAMES;
13186
13187fn static_var_compensator_regulation_mode_name(
13188    mode: powerio_tx::StaticVarCompensatorRegulationMode,
13189) -> &'static str {
13190    match mode {
13191        powerio_tx::StaticVarCompensatorRegulationMode::Voltage => "voltage",
13192        powerio_tx::StaticVarCompensatorRegulationMode::ReactivePower => "reactive_power",
13193        _ => "unknown",
13194    }
13195}
13196
13197fn hvdc_converter_kind_name(kind: powerio_tx::HvdcConverterKind) -> &'static str {
13198    match kind {
13199        powerio_tx::HvdcConverterKind::Vsc => "vsc",
13200        powerio_tx::HvdcConverterKind::Lcc => "lcc",
13201        _ => "unknown",
13202    }
13203}
13204
13205fn hvdc_converters_mode_name(mode: powerio_tx::HvdcConvertersMode) -> &'static str {
13206    match mode {
13207        powerio_tx::HvdcConvertersMode::Side1RectifierSide2Inverter => {
13208            "side1_rectifier_side2_inverter"
13209        }
13210        powerio_tx::HvdcConvertersMode::Side1InverterSide2Rectifier => {
13211            "side1_inverter_side2_rectifier"
13212        }
13213        _ => "unknown",
13214    }
13215}
13216
13217fn hvdc_converter_view(
13218    converter: Option<&powerio_tx::HvdcConverter>,
13219) -> (PioBalancedHvdcConverterView, bool) {
13220    let empty_terminal = terminal_reference_view(None).0;
13221    converter.map_or(
13222        (
13223            PioBalancedHvdcConverterView {
13224                component: empty_component_id_view(),
13225                kind: PioStringView::EMPTY,
13226                loss_factor_percent: 0.0,
13227                voltage_regulator_on: false,
13228                has_voltage_regulator_on: false,
13229                voltage_setpoint_kv: 0.0,
13230                has_voltage_setpoint: false,
13231                reactive_power_setpoint_mvar: 0.0,
13232                has_reactive_power_setpoint: false,
13233                power_factor: 0.0,
13234                has_power_factor: false,
13235                regulating_terminal: empty_terminal,
13236                has_regulating_terminal: false,
13237            },
13238            false,
13239        ),
13240        |converter| {
13241            let (regulating_terminal, has_regulating_terminal) =
13242                terminal_reference_view(converter.regulating_terminal.as_ref());
13243            (
13244                PioBalancedHvdcConverterView {
13245                    component: component_id_view(&converter.component),
13246                    kind: PioStringView::new(hvdc_converter_kind_name(converter.kind)),
13247                    loss_factor_percent: converter.loss_factor_percent,
13248                    voltage_regulator_on: converter.voltage_regulator_on.unwrap_or(false),
13249                    has_voltage_regulator_on: converter.voltage_regulator_on.is_some(),
13250                    voltage_setpoint_kv: converter.voltage_setpoint_kv.unwrap_or(0.0),
13251                    has_voltage_setpoint: converter.voltage_setpoint_kv.is_some(),
13252                    reactive_power_setpoint_mvar: converter
13253                        .reactive_power_setpoint_mvar
13254                        .unwrap_or(0.0),
13255                    has_reactive_power_setpoint: converter.reactive_power_setpoint_mvar.is_some(),
13256                    power_factor: converter.power_factor.unwrap_or(0.0),
13257                    has_power_factor: converter.power_factor.is_some(),
13258                    regulating_terminal,
13259                    has_regulating_terminal,
13260                },
13261                true,
13262            )
13263        },
13264    )
13265}
13266
13267/// Read one bus by zero based table position.
13268///
13269/// # Safety
13270/// Pointers and handles must satisfy the crate-level safety requirements.
13271#[unsafe(no_mangle)]
13272pub unsafe extern "C" fn pio_balanced_network_bus_at(
13273    network: *const PioBalancedNetwork,
13274    index: usize,
13275    output: *mut PioBalancedBusView,
13276    error: *mut *mut PioError,
13277) -> bool {
13278    unsafe {
13279        entry(error, false, || {
13280            let network = require_balanced_network(network)?;
13281            let bus = network.buses().get(index).ok_or_else(|| {
13282                boundary_error(
13283                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13284                    format!("bus index {index} is out of range"),
13285                )
13286            })?;
13287            let output = require_output(output, "output")?;
13288            let (component_id, has_component_id) = optional_string_view(bus.uid.as_deref());
13289            let (name, has_name) = optional_string_view(bus.name.as_deref());
13290            let (location, has_location) = bus
13291                .location
13292                .as_ref()
13293                .map_or((empty_balanced_location_view(), false), |location| {
13294                    (balanced_location_view(location), true)
13295                });
13296            *output = PioBalancedBusView {
13297                component_id,
13298                has_component_id,
13299                id: bus.id.0,
13300                bus_type: PioStringView::new(bus.kind.as_str()),
13301                vm_pu: bus.vm,
13302                va_degrees: bus.va,
13303                base_kv: bus.base_kv,
13304                vmax_pu: bus.vmax,
13305                vmin_pu: bus.vmin,
13306                has_emergency_voltage_limits: bus.evhi.is_some() || bus.evlo.is_some(),
13307                emergency_vmax_pu: bus.evhi.unwrap_or(bus.vmax),
13308                emergency_vmin_pu: bus.evlo.unwrap_or(bus.vmin),
13309                area: bus.area,
13310                zone: bus.zone,
13311                name,
13312                has_name,
13313                location,
13314                has_location,
13315            };
13316            Ok(true)
13317        })
13318    }
13319}
13320
13321/// Read one load by zero based table position.
13322///
13323/// # Safety
13324/// Pointers and handles must satisfy the crate-level safety requirements.
13325#[unsafe(no_mangle)]
13326pub unsafe extern "C" fn pio_balanced_network_load_at(
13327    network: *const PioBalancedNetwork,
13328    index: usize,
13329    output: *mut PioBalancedLoadView,
13330    error: *mut *mut PioError,
13331) -> bool {
13332    unsafe {
13333        entry(error, false, || {
13334            let network = require_balanced_network(network)?;
13335            let load = network.loads().get(index).ok_or_else(|| {
13336                boundary_error(
13337                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13338                    format!("load index {index} is out of range"),
13339                )
13340            })?;
13341            let output = require_output(output, "output")?;
13342            let (component_id, has_component_id) = optional_string_view(load.uid.as_deref());
13343            *output = PioBalancedLoadView {
13344                component_id,
13345                has_component_id,
13346                bus_id: load.bus.0,
13347                p_mw: load.p,
13348                q_mvar: load.q,
13349                in_service: load.in_service,
13350                voltage_model: load_voltage_model_view(load),
13351            };
13352            Ok(true)
13353        })
13354    }
13355}
13356
13357/// Read one shunt by zero based table position.
13358///
13359/// # Safety
13360/// Pointers and handles must satisfy the crate-level safety requirements.
13361#[unsafe(no_mangle)]
13362pub unsafe extern "C" fn pio_balanced_network_shunt_at(
13363    network: *const PioBalancedNetwork,
13364    index: usize,
13365    output: *mut PioBalancedShuntView,
13366    error: *mut *mut PioError,
13367) -> bool {
13368    unsafe {
13369        entry(error, false, || {
13370            let network = require_balanced_network(network)?;
13371            let shunt = network.shunts().get(index).ok_or_else(|| {
13372                boundary_error(
13373                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13374                    format!("shunt index {index} is out of range"),
13375                )
13376            })?;
13377            let output = require_output(output, "output")?;
13378            let (component_id, has_component_id) = optional_string_view(shunt.uid.as_deref());
13379            let control = shunt.control.as_ref();
13380            *output = PioBalancedShuntView {
13381                component_id,
13382                has_component_id,
13383                bus_id: shunt.bus.0,
13384                conductance_mw: shunt.g,
13385                susceptance_mvar: shunt.b,
13386                in_service: shunt.in_service,
13387                section_count: shunt.section_count.unwrap_or(0),
13388                has_section_count: shunt.section_count.is_some(),
13389                has_control: control.is_some(),
13390                control_mode: control.map_or(PioStringView::EMPTY, |control| {
13391                    PioStringView::new(switched_shunt_mode_name(control.mode))
13392                }),
13393                control_vmax_pu: control.map_or(0.0, |control| control.vhigh),
13394                control_vmin_pu: control.map_or(0.0, |control| control.vlow),
13395                control_bus_id: control
13396                    .and_then(|control| control.control_bus)
13397                    .map_or(0, |bus| bus.0),
13398                has_control_bus: control.is_some_and(|control| control.control_bus.is_some()),
13399                control_reactive_range_percent: control.map_or(0.0, |control| control.rmpct),
13400                control_block_count: control.map_or(0, |control| control.blocks.len()),
13401            };
13402            Ok(true)
13403        })
13404    }
13405}
13406
13407/// Read one switched shunt block by zero based position.
13408///
13409/// # Safety
13410/// Pointers and handles must satisfy the crate-level safety requirements.
13411#[unsafe(no_mangle)]
13412pub unsafe extern "C" fn pio_balanced_network_shunt_block_at(
13413    network: *const PioBalancedNetwork,
13414    shunt_index: usize,
13415    block_index: usize,
13416    output: *mut PioShuntBlockView,
13417    error: *mut *mut PioError,
13418) -> bool {
13419    unsafe {
13420        entry(error, false, || {
13421            let network = require_balanced_network(network)?;
13422            let shunt = network.shunts().get(shunt_index).ok_or_else(|| {
13423                boundary_error(
13424                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13425                    format!("shunt index {shunt_index} is out of range"),
13426                )
13427            })?;
13428            let block = shunt
13429                .control
13430                .as_ref()
13431                .and_then(|control| control.blocks.get(block_index))
13432                .ok_or_else(|| {
13433                    boundary_error(
13434                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13435                        format!(
13436                            "shunt block index {block_index} is out of range for shunt {shunt_index}"
13437                        ),
13438                    )
13439                })?;
13440            *require_output(output, "output")? = PioShuntBlockView {
13441                steps: block.steps,
13442                conductance_mw: block.g,
13443                susceptance_mvar: block.b,
13444            };
13445            Ok(true)
13446        })
13447    }
13448}
13449
13450/// Read one static VAR compensator by zero based table position.
13451///
13452/// # Safety
13453/// Pointers and handles must satisfy the crate-level safety requirements.
13454#[unsafe(no_mangle)]
13455pub unsafe extern "C" fn pio_balanced_network_static_var_compensator_at(
13456    network: *const PioBalancedNetwork,
13457    index: usize,
13458    output: *mut PioBalancedStaticVarCompensatorView,
13459    error: *mut *mut PioError,
13460) -> bool {
13461    unsafe {
13462        entry(error, false, || {
13463            let network = require_balanced_network(network)?;
13464            let svc = network
13465                .static_var_compensators()
13466                .get(index)
13467                .ok_or_else(|| {
13468                    boundary_error(
13469                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13470                        format!("static VAR compensator index {index} is out of range"),
13471                    )
13472                })?;
13473            let (component_id, has_component_id) = optional_string_view(svc.uid.as_deref());
13474            let (regulating_terminal, has_regulating_terminal) =
13475                terminal_reference_view(svc.regulating_terminal.as_ref());
13476            *require_output(output, "output")? = PioBalancedStaticVarCompensatorView {
13477                component_id,
13478                has_component_id,
13479                bus_id: svc.bus.0,
13480                minimum_susceptance_siemens: svc.b_min_siemens,
13481                maximum_susceptance_siemens: svc.b_max_siemens,
13482                voltage_setpoint_kv: svc.voltage_setpoint_kv,
13483                reactive_power_setpoint_mvar: svc.reactive_power_setpoint_mvar,
13484                regulation_mode: PioStringView::new(static_var_compensator_regulation_mode_name(
13485                    svc.regulation_mode,
13486                )),
13487                regulating: svc.regulating,
13488                regulating_terminal,
13489                has_regulating_terminal,
13490                active_power_mw: svc.p,
13491                reactive_power_mvar: svc.q,
13492                in_service: svc.in_service,
13493            };
13494            Ok(true)
13495        })
13496    }
13497}
13498
13499/// Read one branch by zero based table position.
13500///
13501/// # Safety
13502/// Pointers and handles must satisfy the crate-level safety requirements.
13503#[unsafe(no_mangle)]
13504pub unsafe extern "C" fn pio_balanced_network_branch_at(
13505    network: *const PioBalancedNetwork,
13506    index: usize,
13507    output: *mut PioBalancedBranchView,
13508    error: *mut *mut PioError,
13509) -> bool {
13510    unsafe {
13511        entry(error, false, || {
13512            let network = require_balanced_network(network)?;
13513            let branch = network.branches().get(index).ok_or_else(|| {
13514                boundary_error(
13515                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13516                    format!("branch index {index} is out of range"),
13517                )
13518            })?;
13519            let output = require_output(output, "output")?;
13520            let (component_id, has_component_id) = optional_string_view(branch.uid.as_deref());
13521            let (name, has_name) = optional_string_view(branch.name.as_deref());
13522            let charging = branch.calc_terminal_charging();
13523            let current = branch.current_ratings;
13524            let (control, has_control) = transformer_control_view(branch.control.as_ref());
13525            *output = PioBalancedBranchView {
13526                component_id,
13527                has_component_id,
13528                name,
13529                has_name,
13530                from_bus_id: branch.from.0,
13531                to_bus_id: branch.to.0,
13532                resistance_pu: branch.r,
13533                reactance_pu: branch.x,
13534                total_charging_susceptance_pu: branch.b,
13535                terminal_charging_is_explicit: branch.charging.is_some(),
13536                from_conductance_pu: charging.g_fr,
13537                from_susceptance_pu: charging.b_fr,
13538                to_conductance_pu: charging.g_to,
13539                to_susceptance_pu: charging.b_to,
13540                rate_a_mva: branch.rate_a,
13541                rate_b_mva: branch.rate_b,
13542                rate_c_mva: branch.rate_c,
13543                additional_rating_count: branch.rating_sets.len(),
13544                has_current_ratings: current.is_some(),
13545                current_rating_a: current.map_or(0.0, |rating| rating.c_rating_a),
13546                current_rating_b: current.map_or(0.0, |rating| rating.c_rating_b),
13547                current_rating_c: current.map_or(0.0, |rating| rating.c_rating_c),
13548                tap_ratio: branch.tap,
13549                effective_tap_ratio: branch.calc_effective_tap(),
13550                phase_shift_degrees: branch.shift,
13551                in_service: branch.in_service,
13552                angle_min_degrees: branch.angmin,
13553                angle_max_degrees: branch.angmax,
13554                control,
13555                has_control,
13556                route_point_count: branch.route.as_ref().map_or(0, Vec::len),
13557                has_route: branch.route.is_some(),
13558            };
13559            Ok(true)
13560        })
13561    }
13562}
13563
13564/// Read one point from an explicitly stored balanced branch route.
13565///
13566/// # Safety
13567/// Pointers and handles must satisfy the crate-level safety requirements.
13568#[unsafe(no_mangle)]
13569pub unsafe extern "C" fn pio_balanced_network_branch_route_point_at(
13570    network: *const PioBalancedNetwork,
13571    branch_index: usize,
13572    point_index: usize,
13573    output: *mut PioBalancedLocationView,
13574    error: *mut *mut PioError,
13575) -> bool {
13576    unsafe {
13577        entry(error, false, || {
13578            let network = require_balanced_network(network)?;
13579            let branch = network.branches().get(branch_index).ok_or_else(|| {
13580                boundary_error(
13581                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13582                    format!("branch index {branch_index} is out of range"),
13583                )
13584            })?;
13585            let route = branch.route.as_ref().ok_or_else(|| {
13586                boundary_error(
13587                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13588                    format!("balanced branch {branch_index} has no route"),
13589                )
13590            })?;
13591            let point = route.get(point_index).ok_or_else(|| {
13592                boundary_error(
13593                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13594                    format!("branch route point index {point_index} is out of range"),
13595                )
13596            })?;
13597            *require_output(output, "output")? = balanced_location_view(point);
13598            Ok(true)
13599        })
13600    }
13601}
13602
13603/// Read one additional named branch MVA rating.
13604///
13605/// # Safety
13606/// Pointers and handles must satisfy the crate-level safety requirements.
13607#[unsafe(no_mangle)]
13608pub unsafe extern "C" fn pio_balanced_network_branch_rating_at(
13609    network: *const PioBalancedNetwork,
13610    branch_index: usize,
13611    rating_index: usize,
13612    output: *mut PioBranchRatingView,
13613    error: *mut *mut PioError,
13614) -> bool {
13615    unsafe {
13616        entry(error, false, || {
13617            let network = require_balanced_network(network)?;
13618            let branch = network.branches().get(branch_index).ok_or_else(|| {
13619                boundary_error(
13620                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13621                    format!("branch index {branch_index} is out of range"),
13622                )
13623            })?;
13624            let rating = branch.rating_sets.get(rating_index).ok_or_else(|| {
13625                boundary_error(
13626                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13627                    format!(
13628                        "rating index {rating_index} is out of range for branch {branch_index}"
13629                    ),
13630                )
13631            })?;
13632            *require_output(output, "output")? = PioBranchRatingView {
13633                name: PioStringView::new(&rating.name),
13634                rate_mva: rating.rate_mva,
13635            };
13636            Ok(true)
13637        })
13638    }
13639}
13640
13641fn active_power_control_view(
13642    value: Option<&powerio_tx::ActivePowerControl>,
13643) -> (PioActivePowerControlView, bool) {
13644    let Some(value) = value else {
13645        return (
13646            PioActivePowerControlView {
13647                participate: false,
13648                droop_percent: 0.0,
13649                has_droop_percent: false,
13650                participation_factor: 0.0,
13651                has_participation_factor: false,
13652                minimum_target_active_power_mw: 0.0,
13653                has_minimum_target_active_power: false,
13654                maximum_target_active_power_mw: 0.0,
13655                has_maximum_target_active_power: false,
13656            },
13657            false,
13658        );
13659    };
13660    (
13661        PioActivePowerControlView {
13662            participate: value.participate,
13663            droop_percent: value.droop_percent.unwrap_or(0.0),
13664            has_droop_percent: value.droop_percent.is_some(),
13665            participation_factor: value.participation_factor.unwrap_or(0.0),
13666            has_participation_factor: value.participation_factor.is_some(),
13667            minimum_target_active_power_mw: value.minimum_target_active_power_mw.unwrap_or(0.0),
13668            has_minimum_target_active_power: value.minimum_target_active_power_mw.is_some(),
13669            maximum_target_active_power_mw: value.maximum_target_active_power_mw.unwrap_or(0.0),
13670            has_maximum_target_active_power: value.maximum_target_active_power_mw.is_some(),
13671        },
13672        true,
13673    )
13674}
13675
13676fn generator_energy_source_name(value: powerio_tx::GeneratorEnergySource) -> &'static str {
13677    match value {
13678        powerio_tx::GeneratorEnergySource::Hydro => "hydro",
13679        powerio_tx::GeneratorEnergySource::Nuclear => "nuclear",
13680        powerio_tx::GeneratorEnergySource::Wind => "wind",
13681        powerio_tx::GeneratorEnergySource::Thermal => "thermal",
13682        powerio_tx::GeneratorEnergySource::Solar => "solar",
13683        powerio_tx::GeneratorEnergySource::Other => "other",
13684        _ => "unknown",
13685    }
13686}
13687
13688/// Read one generator by zero based table position.
13689///
13690/// # Safety
13691/// Pointers and handles must satisfy the crate-level safety requirements.
13692#[unsafe(no_mangle)]
13693pub unsafe extern "C" fn pio_balanced_network_generator_at(
13694    network: *const PioBalancedNetwork,
13695    index: usize,
13696    output: *mut PioBalancedGeneratorView,
13697    error: *mut *mut PioError,
13698) -> bool {
13699    unsafe {
13700        entry(error, false, || {
13701            let network = require_balanced_network(network)?;
13702            let generator = network.generators().get(index).ok_or_else(|| {
13703                boundary_error(
13704                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13705                    format!("generator index {index} is out of range"),
13706                )
13707            })?;
13708            let output = require_output(output, "output")?;
13709            let (component_id, has_component_id) = optional_string_view(generator.uid.as_deref());
13710            let cost = generator.cost.as_ref();
13711            let (active_power_control, has_active_power_control) =
13712                active_power_control_view(generator.active_power_control.as_ref());
13713            let (regulating_terminal, has_regulating_terminal) =
13714                terminal_reference_view(generator.regulating_terminal.as_ref());
13715            *output = PioBalancedGeneratorView {
13716                component_id,
13717                has_component_id,
13718                bus_id: generator.bus.0,
13719                energy_source: PioStringView::new(generator_energy_source_name(
13720                    generator.energy_source,
13721                )),
13722                active_power_mw: generator.pg,
13723                reactive_power_mvar: generator.qg,
13724                active_power_max_mw: generator.pmax,
13725                active_power_min_mw: generator.pmin,
13726                reactive_power_max_mvar: generator.qmax,
13727                reactive_power_min_mvar: generator.qmin,
13728                voltage_setpoint_pu: generator.vg,
13729                machine_base_mva: generator.mbase,
13730                in_service: generator.in_service,
13731                has_cost: cost.is_some(),
13732                cost: cost.map_or(
13733                    PioGeneratorCostView {
13734                        model: 0,
13735                        startup: 0.0,
13736                        shutdown: 0.0,
13737                        ncost: 0,
13738                        coefficients: PioF64View::EMPTY,
13739                    },
13740                    |cost| PioGeneratorCostView {
13741                        model: cost.model,
13742                        startup: cost.startup,
13743                        shutdown: cost.shutdown,
13744                        ncost: cost.ncost,
13745                        coefficients: PioF64View::new(&cost.coeffs),
13746                    },
13747                ),
13748                regulated_bus_id: generator.regulated_bus.map_or(0, |bus| bus.0),
13749                has_regulated_bus: generator.regulated_bus.is_some(),
13750                capability_count: GENERATOR_CAPABILITY_NAMES.len(),
13751                active_power_control,
13752                has_active_power_control,
13753                voltage_regulation_on: generator.voltage_regulation_on,
13754                regulating_terminal,
13755                has_regulating_terminal,
13756            };
13757            Ok(true)
13758        })
13759    }
13760}
13761
13762/// Read one named generator capability or ramp field.
13763///
13764/// # Safety
13765/// Pointers and handles must satisfy the crate-level safety requirements.
13766#[unsafe(no_mangle)]
13767pub unsafe extern "C" fn pio_balanced_network_generator_capability_at(
13768    network: *const PioBalancedNetwork,
13769    generator_index: usize,
13770    capability_index: usize,
13771    output: *mut PioGeneratorCapabilityView,
13772    error: *mut *mut PioError,
13773) -> bool {
13774    unsafe {
13775        entry(error, false, || {
13776            let network = require_balanced_network(network)?;
13777            let generator = network.generators().get(generator_index).ok_or_else(|| {
13778                boundary_error(
13779                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13780                    format!("generator index {generator_index} is out of range"),
13781                )
13782            })?;
13783            let name = GENERATOR_CAPABILITY_NAMES
13784                .get(capability_index)
13785                .ok_or_else(|| {
13786                    boundary_error(
13787                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13788                        format!("generator capability index {capability_index} is out of range"),
13789                    )
13790                })?;
13791            let value = *generator.caps.get(capability_index).ok_or_else(|| {
13792                boundary_error(
13793                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13794                    "generator capability index is out of range",
13795                )
13796            })?;
13797            *require_output(output, "output")? = PioGeneratorCapabilityView {
13798                name: PioStringView::new(name),
13799                value: value.unwrap_or(0.0),
13800                has_value: value.is_some(),
13801            };
13802            Ok(true)
13803        })
13804    }
13805}
13806
13807/// Read one storage element by zero based table position.
13808///
13809/// # Safety
13810/// Pointers and handles must satisfy the crate-level safety requirements.
13811#[unsafe(no_mangle)]
13812pub unsafe extern "C" fn pio_balanced_network_storage_at(
13813    network: *const PioBalancedNetwork,
13814    index: usize,
13815    output: *mut PioBalancedStorageView,
13816    error: *mut *mut PioError,
13817) -> bool {
13818    unsafe {
13819        entry(error, false, || {
13820            let network = require_balanced_network(network)?;
13821            let storage = network.storage().get(index).ok_or_else(|| {
13822                boundary_error(
13823                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13824                    format!("storage index {index} is out of range"),
13825                )
13826            })?;
13827            let output = require_output(output, "output")?;
13828            let (component_id, has_component_id) = optional_string_view(storage.uid.as_deref());
13829            let (active_power_control, has_active_power_control) =
13830                active_power_control_view(storage.active_power_control.as_ref());
13831            *output = PioBalancedStorageView {
13832                component_id,
13833                has_component_id,
13834                bus_id: storage.bus.0,
13835                active_power_mw: storage.ps,
13836                reactive_power_mvar: storage.qs,
13837                energy_mwh: storage.energy,
13838                energy_rating_mwh: storage.energy_rating,
13839                charge_rating_mw: storage.charge_rating,
13840                discharge_rating_mw: storage.discharge_rating,
13841                charge_efficiency: storage.charge_efficiency,
13842                discharge_efficiency: storage.discharge_efficiency,
13843                thermal_rating_mva: storage.thermal_rating,
13844                current_rating: storage.current_rating.unwrap_or(0.0),
13845                has_current_rating: storage.current_rating.is_some(),
13846                reactive_power_min_mvar: storage.qmin,
13847                reactive_power_max_mvar: storage.qmax,
13848                resistance_pu: storage.r,
13849                reactance_pu: storage.x,
13850                active_power_loss_mw: storage.p_loss,
13851                reactive_power_loss_mvar: storage.q_loss,
13852                in_service: storage.in_service,
13853                active_power_control,
13854                has_active_power_control,
13855            };
13856            Ok(true)
13857        })
13858    }
13859}
13860
13861/// Read one transmission switch by zero based table position.
13862///
13863/// # Safety
13864/// Pointers and handles must satisfy the crate-level safety requirements.
13865#[unsafe(no_mangle)]
13866pub unsafe extern "C" fn pio_balanced_network_switch_at(
13867    network: *const PioBalancedNetwork,
13868    index: usize,
13869    output: *mut PioBalancedSwitchView,
13870    error: *mut *mut PioError,
13871) -> bool {
13872    unsafe {
13873        entry(error, false, || {
13874            let network = require_balanced_network(network)?;
13875            let switch = network.switches().get(index).ok_or_else(|| {
13876                boundary_error(
13877                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13878                    format!("switch index {index} is out of range"),
13879                )
13880            })?;
13881            let (component_id, has_component_id) = optional_string_view(switch.uid.as_deref());
13882            *require_output(output, "output")? = PioBalancedSwitchView {
13883                component_id,
13884                has_component_id,
13885                from_bus_id: switch.from.0,
13886                to_bus_id: switch.to.0,
13887                closed: switch.closed,
13888                thermal_rating_mva: switch.thermal_rating.unwrap_or(0.0),
13889                has_thermal_rating: switch.thermal_rating.is_some(),
13890                current_rating_a: switch.current_rating.unwrap_or(0.0),
13891                has_current_rating: switch.current_rating.is_some(),
13892                from_active_power_mw: switch.pf.unwrap_or(0.0),
13893                has_from_active_power: switch.pf.is_some(),
13894                from_reactive_power_mvar: switch.qf.unwrap_or(0.0),
13895                has_from_reactive_power: switch.qf.is_some(),
13896                to_active_power_mw: switch.pt.unwrap_or(0.0),
13897                has_to_active_power: switch.pt.is_some(),
13898                to_reactive_power_mvar: switch.qt.unwrap_or(0.0),
13899                has_to_reactive_power: switch.qt.is_some(),
13900            };
13901            Ok(true)
13902        })
13903    }
13904}
13905
13906/// Read one HVDC line by zero based table position.
13907///
13908/// # Safety
13909/// Pointers and handles must satisfy the crate-level safety requirements.
13910#[unsafe(no_mangle)]
13911pub unsafe extern "C" fn pio_balanced_network_hvdc_at(
13912    network: *const PioBalancedNetwork,
13913    index: usize,
13914    output: *mut PioBalancedHvdcView,
13915    error: *mut *mut PioError,
13916) -> bool {
13917    unsafe {
13918        entry(error, false, || {
13919            let network = require_balanced_network(network)?;
13920            let hvdc = network.hvdc().get(index).ok_or_else(|| {
13921                boundary_error(
13922                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13923                    format!("HVDC index {index} is out of range"),
13924                )
13925            })?;
13926            let (component_id, has_component_id) = optional_string_view(hvdc.uid.as_deref());
13927            let (converter1, has_converter1) = hvdc_converter_view(hvdc.converter1.as_ref());
13928            let (converter2, has_converter2) = hvdc_converter_view(hvdc.converter2.as_ref());
13929            let (converters_mode, has_converters_mode) = hvdc
13930                .converters_mode
13931                .map_or((PioStringView::EMPTY, false), |mode| {
13932                    (PioStringView::new(hvdc_converters_mode_name(mode)), true)
13933                });
13934            let cost = hvdc.cost.as_ref();
13935            *require_output(output, "output")? = PioBalancedHvdcView {
13936                component_id,
13937                has_component_id,
13938                from_bus_id: hvdc.from.0,
13939                to_bus_id: hvdc.to.0,
13940                in_service: hvdc.in_service,
13941                from_active_power_mw: hvdc.pf,
13942                to_active_power_mw: hvdc.pt,
13943                from_reactive_power_mvar: hvdc.qf,
13944                to_reactive_power_mvar: hvdc.qt,
13945                from_voltage_pu: hvdc.vf,
13946                to_voltage_pu: hvdc.vt,
13947                minimum_active_power_mw: hvdc.pmin,
13948                maximum_active_power_mw: hvdc.pmax,
13949                minimum_from_reactive_power_mvar: hvdc.qminf,
13950                maximum_from_reactive_power_mvar: hvdc.qmaxf,
13951                minimum_to_reactive_power_mvar: hvdc.qmint,
13952                maximum_to_reactive_power_mvar: hvdc.qmaxt,
13953                constant_loss_mw: hvdc.loss0,
13954                proportional_loss: hvdc.loss1,
13955                resistance_ohm: hvdc.resistance_ohm.unwrap_or(0.0),
13956                has_resistance: hvdc.resistance_ohm.is_some(),
13957                nominal_voltage_kv: hvdc.nominal_voltage_kv.unwrap_or(0.0),
13958                has_nominal_voltage: hvdc.nominal_voltage_kv.is_some(),
13959                converters_mode,
13960                has_converters_mode,
13961                converter1,
13962                has_converter1,
13963                converter2,
13964                has_converter2,
13965                cost: cost.map_or(
13966                    PioGeneratorCostView {
13967                        model: 0,
13968                        startup: 0.0,
13969                        shutdown: 0.0,
13970                        ncost: 0,
13971                        coefficients: PioF64View::EMPTY,
13972                    },
13973                    |cost| PioGeneratorCostView {
13974                        model: cost.model,
13975                        startup: cost.startup,
13976                        shutdown: cost.shutdown,
13977                        ncost: cost.ncost,
13978                        coefficients: PioF64View::new(&cost.coeffs),
13979                    },
13980                ),
13981                has_cost: cost.is_some(),
13982            };
13983            Ok(true)
13984        })
13985    }
13986}
13987
13988/// Read one three winding transformer by zero based table position.
13989///
13990/// # Safety
13991/// Pointers and handles must satisfy the crate-level safety requirements.
13992#[unsafe(no_mangle)]
13993pub unsafe extern "C" fn pio_balanced_network_three_winding_transformer_at(
13994    network: *const PioBalancedNetwork,
13995    index: usize,
13996    output: *mut PioBalancedThreeWindingTransformerView,
13997    error: *mut *mut PioError,
13998) -> bool {
13999    unsafe {
14000        entry(error, false, || {
14001            let network = require_balanced_network(network)?;
14002            let transformer = network.transformers_3w().get(index).ok_or_else(|| {
14003                boundary_error(
14004                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14005                    format!("three winding transformer index {index} is out of range"),
14006                )
14007            })?;
14008            let (component_id, has_component_id) = optional_string_view(transformer.uid.as_deref());
14009            let (name, has_name) = optional_string_view(transformer.name.as_deref());
14010            *require_output(output, "output")? = PioBalancedThreeWindingTransformerView {
14011                component_id,
14012                has_component_id,
14013                name,
14014                has_name,
14015                winding_count: transformer.windings.len(),
14016                impedance_count: transformer.z.len(),
14017                star_voltage_magnitude_pu: transformer.star_vm,
14018                star_voltage_angle_degrees: transformer.star_va,
14019                magnetizing_conductance_pu: transformer.mag_g,
14020                magnetizing_susceptance_pu: transformer.mag_b,
14021                in_service: transformer.in_service,
14022            };
14023            Ok(true)
14024        })
14025    }
14026}
14027
14028/// Read one winding of a three winding transformer.
14029///
14030/// # Safety
14031/// Pointers and handles must satisfy the crate-level safety requirements.
14032#[unsafe(no_mangle)]
14033pub unsafe extern "C" fn pio_balanced_network_three_winding_transformer_winding_at(
14034    network: *const PioBalancedNetwork,
14035    transformer_index: usize,
14036    winding_index: usize,
14037    output: *mut PioThreeWindingTransformerWindingView,
14038    error: *mut *mut PioError,
14039) -> bool {
14040    unsafe {
14041        entry(error, false, || {
14042            let network = require_balanced_network(network)?;
14043            let transformer = network
14044                .transformers_3w()
14045                .get(transformer_index)
14046                .ok_or_else(|| {
14047                    boundary_error(
14048                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14049                        format!(
14050                            "three winding transformer index {transformer_index} is out of range"
14051                        ),
14052                    )
14053                })?;
14054            let winding = transformer.windings.get(winding_index).ok_or_else(|| {
14055                boundary_error(
14056                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14057                    format!("winding index {winding_index} is out of range"),
14058                )
14059            })?;
14060            let (control, has_control) = transformer_control_view(winding.control.as_ref());
14061            *require_output(output, "output")? = PioThreeWindingTransformerWindingView {
14062                bus_id: winding.bus.0,
14063                tap_ratio: winding.tap,
14064                phase_shift_degrees: winding.shift,
14065                nominal_voltage_kv: winding.nominal_kv,
14066                rating_a_mva: winding.rate_a,
14067                rating_b_mva: winding.rate_b,
14068                rating_c_mva: winding.rate_c,
14069                control,
14070                has_control,
14071            };
14072            Ok(true)
14073        })
14074    }
14075}
14076
14077/// Read one pairwise impedance of a three winding transformer.
14078///
14079/// # Safety
14080/// Pointers and handles must satisfy the crate-level safety requirements.
14081#[unsafe(no_mangle)]
14082pub unsafe extern "C" fn pio_balanced_network_three_winding_transformer_impedance_at(
14083    network: *const PioBalancedNetwork,
14084    transformer_index: usize,
14085    impedance_index: usize,
14086    output: *mut PioThreeWindingTransformerImpedanceView,
14087    error: *mut *mut PioError,
14088) -> bool {
14089    unsafe {
14090        entry(error, false, || {
14091            let network = require_balanced_network(network)?;
14092            let transformer = network
14093                .transformers_3w()
14094                .get(transformer_index)
14095                .ok_or_else(|| {
14096                    boundary_error(
14097                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14098                        format!(
14099                            "three winding transformer index {transformer_index} is out of range"
14100                        ),
14101                    )
14102                })?;
14103            let impedance = transformer.z.get(impedance_index).ok_or_else(|| {
14104                boundary_error(
14105                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14106                    format!("impedance index {impedance_index} is out of range"),
14107                )
14108            })?;
14109            *require_output(output, "output")? = PioThreeWindingTransformerImpedanceView {
14110                resistance_pu: impedance.r,
14111                reactance_pu: impedance.x,
14112                base_mva: impedance.base_mva,
14113            };
14114            Ok(true)
14115        })
14116    }
14117}
14118
14119/// Read one control area by zero based table position.
14120///
14121/// # Safety
14122/// Pointers and handles must satisfy the crate-level safety requirements.
14123#[unsafe(no_mangle)]
14124pub unsafe extern "C" fn pio_balanced_network_area_at(
14125    network: *const PioBalancedNetwork,
14126    index: usize,
14127    output: *mut PioBalancedAreaView,
14128    error: *mut *mut PioError,
14129) -> bool {
14130    unsafe {
14131        entry(error, false, || {
14132            let network = require_balanced_network(network)?;
14133            let area = network.areas().get(index).ok_or_else(|| {
14134                boundary_error(
14135                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14136                    format!("area index {index} is out of range"),
14137                )
14138            })?;
14139            let (name, has_name) = optional_string_view(area.name.as_deref());
14140            let (component_id, has_component_id) = optional_string_view(area.uid.as_deref());
14141            let (area_type, has_area_type) = optional_string_view(area.area_type.as_deref());
14142            *require_output(output, "output")? = PioBalancedAreaView {
14143                number: area.number,
14144                slack_bus_id: area.slack_bus.map_or(0, |bus| bus.0),
14145                has_slack_bus: area.slack_bus.is_some(),
14146                net_interchange_mw: area.net_interchange,
14147                tolerance_mw: area.tolerance,
14148                name,
14149                has_name,
14150                component_id,
14151                has_component_id,
14152                area_type,
14153                has_area_type,
14154            };
14155            Ok(true)
14156        })
14157    }
14158}
14159
14160///
14161/// # Safety
14162/// Pointers and handles must satisfy the crate-level safety requirements.
14163/// The input handle must remain live and immutable throughout this call.
14164#[unsafe(no_mangle)]
14165pub unsafe extern "C" fn pio_balanced_network_retain(
14166    network: *const PioBalancedNetwork,
14167) -> *mut PioBalancedNetwork {
14168    unsafe { PioBalancedNetwork::retain_raw(network) }
14169}
14170
14171///
14172/// # Safety
14173/// Pointers and handles must satisfy the crate-level safety requirements.
14174/// A non-null handle must be owned and unused by concurrent calls.
14175#[unsafe(no_mangle)]
14176pub unsafe extern "C" fn pio_balanced_network_release(network: *mut PioBalancedNetwork) {
14177    unsafe { PioBalancedNetwork::release_raw(network) };
14178}
14179
14180unsafe fn require_multiconductor_network<'a>(
14181    network: *const PioMulticonductorNetwork,
14182) -> Result<&'a powerio_dist::MulticonductorNetwork, *mut PioError> {
14183    unsafe { PioMulticonductorNetwork::get(network) }
14184        .and_then(MulticonductorNetworkInner::network)
14185        .ok_or_else(|| {
14186            boundary_error(
14187                &codes::BIND_CAPI_NULL_HANDLE,
14188                "PioMulticonductorNetwork must not be NULL",
14189            )
14190        })
14191}
14192
14193fn optional_f64_view(value: Option<&[f64]>) -> (PioF64View, bool) {
14194    value.map_or((PioF64View::EMPTY, false), |value| {
14195        (PioF64View::new(value), true)
14196    })
14197}
14198
14199fn dist_coords_kind_name(kind: powerio_dist::DistCoordsKind) -> &'static str {
14200    match kind {
14201        powerio_dist::DistCoordsKind::Source => "source",
14202        powerio_dist::DistCoordsKind::Synthetic => "synthetic",
14203        powerio_dist::DistCoordsKind::Manual => "manual",
14204        powerio_dist::DistCoordsKind::Derived => "derived",
14205        _ => "unknown",
14206    }
14207}
14208
14209fn multiconductor_location_view(
14210    location: &powerio_dist::DistLocation,
14211) -> PioMulticonductorLocationView {
14212    let (kind, has_kind) = location.kind.map_or((PioStringView::EMPTY, false), |kind| {
14213        (PioStringView::new(dist_coords_kind_name(kind)), true)
14214    });
14215    PioMulticonductorLocationView {
14216        x: location.x,
14217        y: location.y,
14218        kind,
14219        has_kind,
14220    }
14221}
14222
14223fn empty_multiconductor_location_view() -> PioMulticonductorLocationView {
14224    PioMulticonductorLocationView {
14225        x: 0.0,
14226        y: 0.0,
14227        kind: PioStringView::EMPTY,
14228        has_kind: false,
14229    }
14230}
14231
14232fn multiconductor_geo_view(geo: Option<&powerio_dist::DistGeoMeta>) -> PioMulticonductorGeoView {
14233    let Some(geo) = geo else {
14234        return PioMulticonductorGeoView {
14235            has_geo: false,
14236            space: PioStringView::EMPTY,
14237            crs: PioStringView::EMPTY,
14238            has_crs: false,
14239            kind: PioStringView::EMPTY,
14240            has_kind: false,
14241            has_canvas: false,
14242            canvas_width: 0.0,
14243            has_canvas_width: false,
14244            canvas_height: 0.0,
14245            has_canvas_height: false,
14246            canvas_units: PioStringView::EMPTY,
14247            has_canvas_units: false,
14248        };
14249    };
14250    let (space, crs, has_crs, canvas) = match &geo.space {
14251        powerio_dist::CoordinateSpace::Geographic { crs } => (
14252            "geographic",
14253            crs.as_deref()
14254                .map_or(PioStringView::EMPTY, PioStringView::new),
14255            crs.is_some(),
14256            None,
14257        ),
14258        powerio_dist::CoordinateSpace::Projected { crs } => (
14259            "projected",
14260            crs.as_deref()
14261                .map_or(PioStringView::EMPTY, PioStringView::new),
14262            crs.is_some(),
14263            None,
14264        ),
14265        powerio_dist::CoordinateSpace::Diagram { canvas } => {
14266            ("diagram", PioStringView::EMPTY, false, canvas.as_ref())
14267        }
14268        powerio_dist::CoordinateSpace::Unknown => ("unknown", PioStringView::EMPTY, false, None),
14269        _ => ("unknown", PioStringView::EMPTY, false, None),
14270    };
14271    let (kind, has_kind) = geo.kind.map_or((PioStringView::EMPTY, false), |kind| {
14272        (PioStringView::new(dist_coords_kind_name(kind)), true)
14273    });
14274    PioMulticonductorGeoView {
14275        has_geo: true,
14276        space: PioStringView::new(space),
14277        crs,
14278        has_crs,
14279        kind,
14280        has_kind,
14281        has_canvas: canvas.is_some(),
14282        canvas_width: canvas.and_then(|canvas| canvas.width).unwrap_or(0.0),
14283        has_canvas_width: canvas.is_some_and(|canvas| canvas.width.is_some()),
14284        canvas_height: canvas.and_then(|canvas| canvas.height).unwrap_or(0.0),
14285        has_canvas_height: canvas.is_some_and(|canvas| canvas.height.is_some()),
14286        canvas_units: canvas
14287            .and_then(|canvas| canvas.units.as_deref())
14288            .map_or(PioStringView::EMPTY, PioStringView::new),
14289        has_canvas_units: canvas.is_some_and(|canvas| canvas.units.is_some()),
14290    }
14291}
14292
14293fn multiconductor_configuration_name(configuration: powerio_dist::Configuration) -> &'static str {
14294    match configuration {
14295        powerio_dist::Configuration::Wye => "wye",
14296        powerio_dist::Configuration::Delta => "delta",
14297        powerio_dist::Configuration::SinglePhase => "single_phase",
14298        _ => "unknown",
14299    }
14300}
14301
14302fn multiconductor_winding_connection_name(
14303    connection: powerio_dist::DistWindingConn,
14304) -> &'static str {
14305    match connection {
14306        powerio_dist::DistWindingConn::Wye => "wye",
14307        powerio_dist::DistWindingConn::Delta => "delta",
14308        _ => "unknown",
14309    }
14310}
14311
14312fn inverter_topology_name(topology: powerio_dist::IbrTopology) -> &'static str {
14313    match topology {
14314        powerio_dist::IbrTopology::SinglePhase => "SINGLE_PHASE",
14315        powerio_dist::IbrTopology::ThreeLeg => "THREE_LEG",
14316        powerio_dist::IbrTopology::FourLeg => "FOUR_LEG",
14317        _ => "UNKNOWN",
14318    }
14319}
14320
14321fn inverter_prime_mover_name(prime_mover: powerio_dist::IbrPrimeMover) -> &'static str {
14322    match prime_mover {
14323        powerio_dist::IbrPrimeMover::Pv => "PV",
14324        powerio_dist::IbrPrimeMover::Battery => "BATTERY",
14325        powerio_dist::IbrPrimeMover::Generic => "GENERIC",
14326        powerio_dist::IbrPrimeMover::Statcom => "STATCOM",
14327        powerio_dist::IbrPrimeMover::Dstatcom => "DSTATCOM",
14328        _ => "UNKNOWN",
14329    }
14330}
14331
14332fn inverter_voltage_aggregation_name(
14333    aggregation: powerio_dist::IbrVoltageAggregation,
14334) -> &'static str {
14335    match aggregation {
14336        powerio_dist::IbrVoltageAggregation::PerPhase => "PER_PHASE",
14337        powerio_dist::IbrVoltageAggregation::Average => "AVERAGE",
14338        _ => "UNKNOWN",
14339    }
14340}
14341
14342fn control_voltage_reference_name(
14343    reference: powerio_dist::ControlVoltageReference,
14344) -> &'static str {
14345    match reference {
14346        powerio_dist::ControlVoltageReference::PnPerPhase => "PN_PER_PHASE",
14347        powerio_dist::ControlVoltageReference::PpPerPhase => "PP_PER_PHASE",
14348        powerio_dist::ControlVoltageReference::PpAveraged => "PP_AVERAGED",
14349        powerio_dist::ControlVoltageReference::PgAveraged => "PG_AVERAGED",
14350        powerio_dist::ControlVoltageReference::PnAveraged => "PN_AVERAGED",
14351        powerio_dist::ControlVoltageReference::PgPerPhase => "PG_PER_PHASE",
14352        _ => "UNKNOWN",
14353    }
14354}
14355
14356fn reactive_power_unit_name(unit: powerio_dist::ReactivePowerUnit) -> &'static str {
14357    match unit {
14358        powerio_dist::ReactivePowerUnit::VaFraction => "VA_FRACTION",
14359        powerio_dist::ReactivePowerUnit::Var => "VAR",
14360        _ => "UNKNOWN",
14361    }
14362}
14363
14364fn active_power_unit_name(unit: powerio_dist::ActivePowerUnit) -> &'static str {
14365    match unit {
14366        powerio_dist::ActivePowerUnit::VaFraction => "VA_FRACTION",
14367        powerio_dist::ActivePowerUnit::W => "W",
14368        _ => "UNKNOWN",
14369    }
14370}
14371
14372fn reactive_power_reference_name(reference: powerio_dist::ReactivePowerReference) -> &'static str {
14373    match reference {
14374        powerio_dist::ReactivePowerReference::VarMax => "VAR_MAX",
14375        powerio_dist::ReactivePowerReference::VarAvailable => "VAR_AVAILABLE",
14376        _ => "UNKNOWN",
14377    }
14378}
14379
14380fn active_power_reference_name(reference: powerio_dist::ActivePowerReference) -> &'static str {
14381    match reference {
14382        powerio_dist::ActivePowerReference::PAvailable => "P_AVAILABLE",
14383        powerio_dist::ActivePowerReference::PMax => "P_MAX",
14384        powerio_dist::ActivePowerReference::SMax => "S_MAX",
14385        _ => "UNKNOWN",
14386    }
14387}
14388
14389fn string_slice_at(
14390    values: &[String],
14391    index: usize,
14392    description: &str,
14393) -> Result<PioStringView, *mut PioError> {
14394    values
14395        .get(index)
14396        .map(|value| PioStringView::new(value))
14397        .ok_or_else(|| {
14398            boundary_error(
14399                &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14400                format!("{description} index {index} is out of range"),
14401            )
14402        })
14403}
14404
14405fn conductor_matrix_row(
14406    matrix: &powerio_dist::ConductorMatrix,
14407    row_index: usize,
14408    description: &str,
14409) -> Result<PioF64View, *mut PioError> {
14410    matrix
14411        .get(row_index)
14412        .map(|row| PioF64View::new(row))
14413        .ok_or_else(|| {
14414            boundary_error(
14415                &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14416                format!("{description} row index {row_index} is out of range"),
14417            )
14418        })
14419}
14420
14421fn multiconductor_bus_view(bus: &powerio_dist::DistBus) -> PioMulticonductorBusView {
14422    let (vpn_min, has_vpn_min) = optional_f64_view(bus.vpn_min.as_deref());
14423    let (vpn_max, has_vpn_max) = optional_f64_view(bus.vpn_max.as_deref());
14424    let (vpp_min, has_vpp_min) = optional_f64_view(bus.vpp_min.as_deref());
14425    let (vpp_max, has_vpp_max) = optional_f64_view(bus.vpp_max.as_deref());
14426    let (location, has_location) = bus
14427        .location
14428        .as_ref()
14429        .map_or((empty_multiconductor_location_view(), false), |location| {
14430            (multiconductor_location_view(location), true)
14431        });
14432    PioMulticonductorBusView {
14433        id: PioStringView::new(&bus.id),
14434        terminal_count: bus.terminals.len(),
14435        grounded_terminal_count: bus.grounded.len(),
14436        voltage_min_v: bus.v_min.unwrap_or(0.0),
14437        has_voltage_min: bus.v_min.is_some(),
14438        voltage_max_v: bus.v_max.unwrap_or(0.0),
14439        has_voltage_max: bus.v_max.is_some(),
14440        phase_to_ground_voltage_min_v: optional_f64_view(bus.v_min_phase.as_deref()).0,
14441        has_phase_to_ground_voltage_min: bus.v_min_phase.is_some(),
14442        phase_to_ground_voltage_max_v: optional_f64_view(bus.v_max_phase.as_deref()).0,
14443        has_phase_to_ground_voltage_max: bus.v_max_phase.is_some(),
14444        phase_to_neutral_voltage_min_v: vpn_min,
14445        has_phase_to_neutral_voltage_min: has_vpn_min,
14446        phase_to_neutral_voltage_max_v: vpn_max,
14447        has_phase_to_neutral_voltage_max: has_vpn_max,
14448        phase_to_phase_voltage_min_v: vpp_min,
14449        has_phase_to_phase_voltage_min: has_vpp_min,
14450        phase_to_phase_voltage_max_v: vpp_max,
14451        has_phase_to_phase_voltage_max: has_vpp_max,
14452        positive_sequence_voltage_min_v: bus.vpos_min.unwrap_or(0.0),
14453        has_positive_sequence_voltage_min: bus.vpos_min.is_some(),
14454        positive_sequence_voltage_max_v: bus.vpos_max.unwrap_or(0.0),
14455        has_positive_sequence_voltage_max: bus.vpos_max.is_some(),
14456        negative_sequence_voltage_max_v: bus.vneg_max.unwrap_or(0.0),
14457        has_negative_sequence_voltage_max: bus.vneg_max.is_some(),
14458        zero_sequence_voltage_max_v: bus.vzero_max.unwrap_or(0.0),
14459        has_zero_sequence_voltage_max: bus.vzero_max.is_some(),
14460        neutral_to_ground_voltage_max_v: bus.vn_max.unwrap_or(0.0),
14461        has_neutral_to_ground_voltage_max: bus.vn_max.is_some(),
14462        location,
14463        has_location,
14464    }
14465}
14466
14467fn multiconductor_line_code_view(
14468    line_code: &powerio_dist::DistLineCode,
14469) -> PioMulticonductorLineCodeView {
14470    let (current_limit, has_current_limit) = optional_f64_view(line_code.i_max.as_deref());
14471    let (apparent_power_limit, has_apparent_power_limit) =
14472        optional_f64_view(line_code.s_max.as_deref());
14473    let (source, has_source) = optional_string_view(line_code.source.as_deref());
14474    PioMulticonductorLineCodeView {
14475        name: PioStringView::new(&line_code.name),
14476        conductor_count: line_code.n_conductors,
14477        resistance_matrix_row_count: line_code.r_series.len(),
14478        reactance_matrix_row_count: line_code.x_series.len(),
14479        conductance_from_matrix_row_count: line_code.g_from.len(),
14480        susceptance_from_matrix_row_count: line_code.b_from.len(),
14481        conductance_to_matrix_row_count: line_code.g_to.len(),
14482        susceptance_to_matrix_row_count: line_code.b_to.len(),
14483        current_limit_a: current_limit,
14484        has_current_limit,
14485        apparent_power_limit_va: apparent_power_limit,
14486        has_apparent_power_limit,
14487        source,
14488        has_source,
14489    }
14490}
14491
14492fn multiconductor_line_view(line: &powerio_dist::DistLine) -> PioMulticonductorLineView {
14493    let (current_limit, has_current_limit) = optional_f64_view(line.i_max.as_deref());
14494    let (apparent_power_limit, has_apparent_power_limit) = optional_f64_view(line.s_max.as_deref());
14495    PioMulticonductorLineView {
14496        name: PioStringView::new(&line.name),
14497        bus_from: PioStringView::new(&line.bus_from),
14498        bus_to: PioStringView::new(&line.bus_to),
14499        terminal_map_from_count: line.terminal_map_from.len(),
14500        terminal_map_to_count: line.terminal_map_to.len(),
14501        line_code: PioStringView::new(&line.linecode),
14502        length_m: line.length,
14503        route_point_count: line.route.as_ref().map_or(0, Vec::len),
14504        has_route: line.route.is_some(),
14505        current_limit_a: current_limit,
14506        has_current_limit,
14507        apparent_power_limit_va: apparent_power_limit,
14508        has_apparent_power_limit,
14509    }
14510}
14511
14512fn multiconductor_switch_view(switch: &powerio_dist::DistSwitch) -> PioMulticonductorSwitchView {
14513    let (current_limit, has_current_limit) = optional_f64_view(switch.i_max.as_deref());
14514    PioMulticonductorSwitchView {
14515        name: PioStringView::new(&switch.name),
14516        bus_from: PioStringView::new(&switch.bus_from),
14517        bus_to: PioStringView::new(&switch.bus_to),
14518        terminal_map_from_count: switch.terminal_map_from.len(),
14519        terminal_map_to_count: switch.terminal_map_to.len(),
14520        open: switch.open,
14521        current_limit_a: current_limit,
14522        has_current_limit,
14523    }
14524}
14525
14526fn multiconductor_load_view(load: &powerio_dist::DistLoad) -> PioMulticonductorLoadView {
14527    let empty = PioF64View::EMPTY;
14528    let (
14529        voltage_model,
14530        nominal_voltage,
14531        alpha_z,
14532        alpha_i,
14533        alpha_p,
14534        beta_z,
14535        beta_i,
14536        beta_p,
14537        gamma_p,
14538        gamma_q,
14539    ) = match &load.voltage_model {
14540        powerio_dist::DistLoadVoltageModel::ConstantPower { v_nom } => (
14541            "constant_power",
14542            PioF64View::new(v_nom),
14543            empty,
14544            empty,
14545            empty,
14546            empty,
14547            empty,
14548            empty,
14549            empty,
14550            empty,
14551        ),
14552        powerio_dist::DistLoadVoltageModel::ConstantCurrent { v_nom } => (
14553            "constant_current",
14554            PioF64View::new(v_nom),
14555            empty,
14556            empty,
14557            empty,
14558            empty,
14559            empty,
14560            empty,
14561            empty,
14562            empty,
14563        ),
14564        powerio_dist::DistLoadVoltageModel::ConstantImpedance { v_nom } => (
14565            "constant_impedance",
14566            PioF64View::new(v_nom),
14567            empty,
14568            empty,
14569            empty,
14570            empty,
14571            empty,
14572            empty,
14573            empty,
14574            empty,
14575        ),
14576        powerio_dist::DistLoadVoltageModel::Zip {
14577            v_nom,
14578            alpha_z,
14579            alpha_i,
14580            alpha_p,
14581            beta_z,
14582            beta_i,
14583            beta_p,
14584        } => (
14585            "zip",
14586            PioF64View::new(v_nom),
14587            PioF64View::new(alpha_z),
14588            PioF64View::new(alpha_i),
14589            PioF64View::new(alpha_p),
14590            PioF64View::new(beta_z),
14591            PioF64View::new(beta_i),
14592            PioF64View::new(beta_p),
14593            empty,
14594            empty,
14595        ),
14596        powerio_dist::DistLoadVoltageModel::Exponential {
14597            v_nom,
14598            gamma_p,
14599            gamma_q,
14600        } => (
14601            "exponential",
14602            PioF64View::new(v_nom),
14603            empty,
14604            empty,
14605            empty,
14606            empty,
14607            empty,
14608            empty,
14609            PioF64View::new(gamma_p),
14610            PioF64View::new(gamma_q),
14611        ),
14612        _ => (
14613            "unknown", empty, empty, empty, empty, empty, empty, empty, empty, empty,
14614        ),
14615    };
14616    PioMulticonductorLoadView {
14617        name: PioStringView::new(&load.name),
14618        bus: PioStringView::new(&load.bus),
14619        terminal_map_count: load.terminal_map.len(),
14620        configuration: PioStringView::new(multiconductor_configuration_name(load.configuration)),
14621        active_power_nominal_w: PioF64View::new(&load.p_nom),
14622        reactive_power_nominal_var: PioF64View::new(&load.q_nom),
14623        voltage_model: PioStringView::new(voltage_model),
14624        nominal_voltage_v: nominal_voltage,
14625        active_power_constant_impedance: alpha_z,
14626        active_power_constant_current: alpha_i,
14627        active_power_constant_power: alpha_p,
14628        reactive_power_constant_impedance: beta_z,
14629        reactive_power_constant_current: beta_i,
14630        reactive_power_constant_power: beta_p,
14631        active_power_exponent: gamma_p,
14632        reactive_power_exponent: gamma_q,
14633    }
14634}
14635
14636fn multiconductor_generator_view(
14637    generator: &powerio_dist::DistGenerator,
14638) -> PioMulticonductorGeneratorView {
14639    let (p_min, has_p_min) = optional_f64_view(generator.p_min.as_deref());
14640    let (p_max, has_p_max) = optional_f64_view(generator.p_max.as_deref());
14641    let (q_min, has_q_min) = optional_f64_view(generator.q_min.as_deref());
14642    let (q_max, has_q_max) = optional_f64_view(generator.q_max.as_deref());
14643    let (cost, has_cost) = optional_f64_view(generator.cost.as_deref());
14644    let (s_max, has_s_max) = optional_f64_view(generator.s_max.as_deref());
14645    let (i_max, has_i_max) = optional_f64_view(generator.i_max.as_deref());
14646    PioMulticonductorGeneratorView {
14647        name: PioStringView::new(&generator.name),
14648        bus: PioStringView::new(&generator.bus),
14649        terminal_map_count: generator.terminal_map.len(),
14650        configuration: PioStringView::new(multiconductor_configuration_name(
14651            generator.configuration,
14652        )),
14653        active_power_nominal_w: PioF64View::new(&generator.p_nom),
14654        reactive_power_nominal_var: PioF64View::new(&generator.q_nom),
14655        active_power_min_w: p_min,
14656        has_active_power_min: has_p_min,
14657        active_power_max_w: p_max,
14658        has_active_power_max: has_p_max,
14659        reactive_power_min_var: q_min,
14660        has_reactive_power_min: has_q_min,
14661        reactive_power_max_var: q_max,
14662        has_reactive_power_max: has_q_max,
14663        active_power_dispatch_cost_per_kwh: cost,
14664        has_active_power_dispatch_cost: has_cost,
14665        apparent_power_limit_va: s_max,
14666        has_apparent_power_limit: has_s_max,
14667        current_limit_a: i_max,
14668        has_current_limit: has_i_max,
14669    }
14670}
14671
14672fn inverter_based_resource_view(ibr: &powerio_dist::DistIbr) -> PioInverterBasedResourceView {
14673    let (i_max, has_i_max) = optional_f64_view(ibr.i_max.as_deref());
14674    let (p_min, has_p_min) = optional_f64_view(ibr.p_min.as_deref());
14675    let (p_max, has_p_max) = optional_f64_view(ibr.p_max.as_deref());
14676    let (q_min, has_q_min) = optional_f64_view(ibr.q_min.as_deref());
14677    let (q_max, has_q_max) = optional_f64_view(ibr.q_max.as_deref());
14678    let (control_profile, has_control_profile) =
14679        optional_string_view(ibr.control_profile.as_deref());
14680    let (voltage_aggregation, has_voltage_aggregation) =
14681        ibr.voltage_aggregation
14682            .map_or((PioStringView::EMPTY, false), |aggregation| {
14683                (
14684                    PioStringView::new(inverter_voltage_aggregation_name(aggregation)),
14685                    true,
14686                )
14687            });
14688    PioInverterBasedResourceView {
14689        name: PioStringView::new(&ibr.name),
14690        bus: PioStringView::new(&ibr.bus),
14691        terminal_map_count: ibr.terminal_map.len(),
14692        topology: PioStringView::new(inverter_topology_name(ibr.topology)),
14693        prime_mover: PioStringView::new(inverter_prime_mover_name(ibr.prime_mover)),
14694        apparent_power_limit_va: PioF64View::new(&ibr.s_max),
14695        current_limit_a: i_max,
14696        has_current_limit: has_i_max,
14697        active_power_available_w: ibr.p_avail.unwrap_or(0.0),
14698        has_active_power_available: ibr.p_avail.is_some(),
14699        active_power_min_w: p_min,
14700        has_active_power_min: has_p_min,
14701        active_power_max_w: p_max,
14702        has_active_power_max: has_p_max,
14703        reactive_power_min_var: q_min,
14704        has_reactive_power_min: has_q_min,
14705        reactive_power_max_var: q_max,
14706        has_reactive_power_max: has_q_max,
14707        control_profile,
14708        has_control_profile,
14709        voltage_aggregation,
14710        has_voltage_aggregation,
14711    }
14712}
14713
14714fn control_profile_view(profile: &powerio_dist::DistControlProfile) -> PioControlProfileView {
14715    let power_factor = profile.power_factor.as_ref();
14716    let volt_var = profile.volt_var.as_ref();
14717    let volt_watt = profile.volt_watt.as_ref();
14718    let (vv_voltage_reference, has_vv_voltage_reference) = volt_var
14719        .and_then(|control| control.voltage_reference)
14720        .map_or((PioStringView::EMPTY, false), |reference| {
14721            (
14722                PioStringView::new(control_voltage_reference_name(reference)),
14723                true,
14724            )
14725        });
14726    let (vv_q_unit, has_vv_q_unit) = volt_var
14727        .and_then(|control| control.q_unit)
14728        .map_or((PioStringView::EMPTY, false), |unit| {
14729            (PioStringView::new(reactive_power_unit_name(unit)), true)
14730        });
14731    let (vv_q_ref, has_vv_q_ref) = volt_var.and_then(|control| control.q_ref).map_or(
14732        (PioStringView::EMPTY, false),
14733        |reference| {
14734            (
14735                PioStringView::new(reactive_power_reference_name(reference)),
14736                true,
14737            )
14738        },
14739    );
14740    let (vw_voltage_reference, has_vw_voltage_reference) = volt_watt
14741        .and_then(|control| control.voltage_reference)
14742        .map_or((PioStringView::EMPTY, false), |reference| {
14743            (
14744                PioStringView::new(control_voltage_reference_name(reference)),
14745                true,
14746            )
14747        });
14748    let (vw_p_unit, has_vw_p_unit) = volt_watt
14749        .and_then(|control| control.p_unit)
14750        .map_or((PioStringView::EMPTY, false), |unit| {
14751            (PioStringView::new(active_power_unit_name(unit)), true)
14752        });
14753    let (vw_p_ref, has_vw_p_ref) = volt_watt.and_then(|control| control.p_ref).map_or(
14754        (PioStringView::EMPTY, false),
14755        |reference| {
14756            (
14757                PioStringView::new(active_power_reference_name(reference)),
14758                true,
14759            )
14760        },
14761    );
14762    PioControlProfileView {
14763        name: PioStringView::new(&profile.name),
14764        has_power_factor: power_factor.is_some(),
14765        power_factor: power_factor.map_or(0.0, |control| control.pf),
14766        has_volt_var: volt_var.is_some(),
14767        volt_var_voltage_reference: vv_voltage_reference,
14768        has_volt_var_voltage_reference: has_vv_voltage_reference,
14769        volt_var_breakpoints: volt_var.map_or(PioF64View::EMPTY, |control| {
14770            PioF64View::new(&control.breakpoints)
14771        }),
14772        volt_var_reactive_power_limits: volt_var.map_or(PioF64View::EMPTY, |control| {
14773            PioF64View::new(&control.q_limits)
14774        }),
14775        volt_var_reactive_power_unit: vv_q_unit,
14776        has_volt_var_reactive_power_unit: has_vv_q_unit,
14777        volt_var_reactive_power_reference: vv_q_ref,
14778        has_volt_var_reactive_power_reference: has_vv_q_ref,
14779        volt_var_active_power_min_for_reactive_power_w: volt_var
14780            .and_then(|control| control.p_min_for_q)
14781            .unwrap_or(0.0),
14782        has_volt_var_active_power_min_for_reactive_power: volt_var
14783            .is_some_and(|control| control.p_min_for_q.is_some()),
14784        volt_var_active_power_min_for_max_reactive_power_w: volt_var
14785            .and_then(|control| control.p_min_for_q_max)
14786            .unwrap_or(0.0),
14787        has_volt_var_active_power_min_for_max_reactive_power: volt_var
14788            .is_some_and(|control| control.p_min_for_q_max.is_some()),
14789        has_volt_watt: volt_watt.is_some(),
14790        volt_watt_voltage_reference: vw_voltage_reference,
14791        has_volt_watt_voltage_reference: has_vw_voltage_reference,
14792        volt_watt_breakpoints: volt_watt.map_or(PioF64View::EMPTY, |control| {
14793            PioF64View::new(&control.breakpoints)
14794        }),
14795        volt_watt_active_power_limits: volt_watt.map_or(PioF64View::EMPTY, |control| {
14796            PioF64View::new(&control.p_limits)
14797        }),
14798        volt_watt_active_power_unit: vw_p_unit,
14799        has_volt_watt_active_power_unit: has_vw_p_unit,
14800        volt_watt_active_power_reference: vw_p_ref,
14801        has_volt_watt_active_power_reference: has_vw_p_ref,
14802    }
14803}
14804
14805///
14806/// # Safety
14807/// Pointers and handles must satisfy the crate-level safety requirements.
14808#[unsafe(no_mangle)]
14809pub unsafe extern "C" fn pio_multiconductor_network_name(
14810    network: *const PioMulticonductorNetwork,
14811) -> PioStringView {
14812    unsafe { PioMulticonductorNetwork::get(network) }
14813        .and_then(MulticonductorNetworkInner::network)
14814        .and_then(|network| network.name().as_deref())
14815        .map_or(PioStringView::EMPTY, PioStringView::new)
14816}
14817
14818///
14819/// # Safety
14820/// Pointers and handles must satisfy the crate-level safety requirements.
14821#[unsafe(no_mangle)]
14822pub unsafe extern "C" fn pio_multiconductor_network_has_name(
14823    network: *const PioMulticonductorNetwork,
14824) -> bool {
14825    unsafe { PioMulticonductorNetwork::get(network) }
14826        .and_then(MulticonductorNetworkInner::network)
14827        .is_some_and(|network| network.name().is_some())
14828}
14829
14830///
14831/// # Safety
14832/// Pointers and handles must satisfy the crate-level safety requirements.
14833#[unsafe(no_mangle)]
14834pub unsafe extern "C" fn pio_multiconductor_network_source_format(
14835    network: *const PioMulticonductorNetwork,
14836) -> PioStringView {
14837    unsafe { PioMulticonductorNetwork::get(network) }
14838        .and_then(MulticonductorNetworkInner::network)
14839        .and_then(|network| *network.source_format())
14840        .map_or(PioStringView::EMPTY, |format| {
14841            PioStringView::new(format.name())
14842        })
14843}
14844
14845///
14846/// # Safety
14847/// Pointers and handles must satisfy the crate-level safety requirements.
14848#[unsafe(no_mangle)]
14849pub unsafe extern "C" fn pio_multiconductor_network_has_source_format(
14850    network: *const PioMulticonductorNetwork,
14851) -> bool {
14852    unsafe { PioMulticonductorNetwork::get(network) }
14853        .and_then(MulticonductorNetworkInner::network)
14854        .is_some_and(|network| network.source_format().is_some())
14855}
14856
14857/// Read the network coordinate metadata, including absence through `has_geo`.
14858///
14859/// # Safety
14860/// Pointers and handles must satisfy the crate-level safety requirements.
14861#[unsafe(no_mangle)]
14862pub unsafe extern "C" fn pio_multiconductor_network_geo(
14863    network: *const PioMulticonductorNetwork,
14864    output: *mut PioMulticonductorGeoView,
14865    error: *mut *mut PioError,
14866) -> bool {
14867    unsafe {
14868        entry(error, false, || {
14869            let network = require_multiconductor_network(network)?;
14870            *require_output(output, "output")? = multiconductor_geo_view(network.geo().as_ref());
14871            Ok(true)
14872        })
14873    }
14874}
14875
14876/// Read exact table lengths. Defaulted source fields and arbitrary extension
14877/// maps are retained internally and are not separate domain tables.
14878///
14879/// # Safety
14880/// Pointers and handles must satisfy the crate-level safety requirements.
14881#[unsafe(no_mangle)]
14882pub unsafe extern "C" fn pio_multiconductor_network_counts(
14883    network: *const PioMulticonductorNetwork,
14884    output: *mut PioMulticonductorNetworkCountsView,
14885    error: *mut *mut PioError,
14886) -> bool {
14887    unsafe {
14888        entry(error, false, || {
14889            let network = require_multiconductor_network(network)?;
14890            *require_output(output, "output")? = PioMulticonductorNetworkCountsView {
14891                buses: network.buses().len(),
14892                line_codes: network.line_codes().len(),
14893                lines: network.lines().len(),
14894                switches: network.switches().len(),
14895                transformers: network.transformers().len(),
14896                loads: network.loads().len(),
14897                generators: network.generators().len(),
14898                inverter_based_resources: network.ibrs().len(),
14899                control_profiles: network.control_profiles().len(),
14900                shunts: network.shunts().len(),
14901                capacitors: network.capacitors().len(),
14902                voltage_sources: network.sources().len(),
14903                untyped_objects: network.untyped_objects().len(),
14904                commands: network.commands().len(),
14905                options: network.options().len(),
14906            };
14907            Ok(true)
14908        })
14909    }
14910}
14911
14912///
14913/// # Safety
14914/// Pointers and handles must satisfy the crate-level safety requirements.
14915#[unsafe(no_mangle)]
14916pub unsafe extern "C" fn pio_multiconductor_network_base_frequency_hz(
14917    network: *const PioMulticonductorNetwork,
14918) -> f64 {
14919    unsafe { PioMulticonductorNetwork::get(network) }
14920        .and_then(MulticonductorNetworkInner::network)
14921        .map_or(f64::NAN, |network| network.base_frequency())
14922}
14923
14924///
14925/// # Safety
14926/// Pointers and handles must satisfy the crate-level safety requirements.
14927#[unsafe(no_mangle)]
14928pub unsafe extern "C" fn pio_multiconductor_network_bus_count(
14929    network: *const PioMulticonductorNetwork,
14930) -> usize {
14931    unsafe { PioMulticonductorNetwork::get(network) }
14932        .and_then(MulticonductorNetworkInner::network)
14933        .map_or(0, |network| network.buses().len())
14934}
14935
14936///
14937/// # Safety
14938/// Pointers and handles must satisfy the crate-level safety requirements.
14939#[unsafe(no_mangle)]
14940pub unsafe extern "C" fn pio_multiconductor_network_line_count(
14941    network: *const PioMulticonductorNetwork,
14942) -> usize {
14943    unsafe { PioMulticonductorNetwork::get(network) }
14944        .and_then(MulticonductorNetworkInner::network)
14945        .map_or(0, |network| network.lines().len())
14946}
14947
14948///
14949/// # Safety
14950/// Pointers and handles must satisfy the crate-level safety requirements.
14951#[unsafe(no_mangle)]
14952pub unsafe extern "C" fn pio_multiconductor_network_load_count(
14953    network: *const PioMulticonductorNetwork,
14954) -> usize {
14955    unsafe { PioMulticonductorNetwork::get(network) }
14956        .and_then(MulticonductorNetworkInner::network)
14957        .map_or(0, |network| network.loads().len())
14958}
14959
14960///
14961/// # Safety
14962/// Pointers and handles must satisfy the crate-level safety requirements.
14963#[unsafe(no_mangle)]
14964pub unsafe extern "C" fn pio_multiconductor_network_generator_count(
14965    network: *const PioMulticonductorNetwork,
14966) -> usize {
14967    unsafe { PioMulticonductorNetwork::get(network) }
14968        .and_then(MulticonductorNetworkInner::network)
14969        .map_or(0, |network| network.generators().len())
14970}
14971
14972/// Read one multiconductor bus by zero based table position. Borrowed strings
14973/// and numeric spans remain valid while the network handle is alive.
14974///
14975/// # Safety
14976/// Pointers and handles must satisfy the crate-level safety requirements.
14977#[unsafe(no_mangle)]
14978pub unsafe extern "C" fn pio_multiconductor_network_bus_at(
14979    network: *const PioMulticonductorNetwork,
14980    index: usize,
14981    output: *mut PioMulticonductorBusView,
14982    error: *mut *mut PioError,
14983) -> bool {
14984    unsafe {
14985        entry(error, false, || {
14986            let network = require_multiconductor_network(network)?;
14987            let bus = network.buses().get(index).ok_or_else(|| {
14988                boundary_error(
14989                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14990                    format!("multiconductor bus index {index} is out of range"),
14991                )
14992            })?;
14993            *require_output(output, "output")? = multiconductor_bus_view(bus);
14994            Ok(true)
14995        })
14996    }
14997}
14998
14999///
15000/// # Safety
15001/// Pointers and handles must satisfy the crate-level safety requirements.
15002#[unsafe(no_mangle)]
15003pub unsafe extern "C" fn pio_multiconductor_network_bus_terminal_at(
15004    network: *const PioMulticonductorNetwork,
15005    bus_index: usize,
15006    terminal_index: usize,
15007    output: *mut PioStringView,
15008    error: *mut *mut PioError,
15009) -> bool {
15010    unsafe {
15011        entry(error, false, || {
15012            let network = require_multiconductor_network(network)?;
15013            let bus = network.buses().get(bus_index).ok_or_else(|| {
15014                boundary_error(
15015                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15016                    format!("multiconductor bus index {bus_index} is out of range"),
15017                )
15018            })?;
15019            *require_output(output, "output")? =
15020                string_slice_at(&bus.terminals, terminal_index, "bus terminal")?;
15021            Ok(true)
15022        })
15023    }
15024}
15025
15026///
15027/// # Safety
15028/// Pointers and handles must satisfy the crate-level safety requirements.
15029#[unsafe(no_mangle)]
15030pub unsafe extern "C" fn pio_multiconductor_network_bus_grounded_terminal_at(
15031    network: *const PioMulticonductorNetwork,
15032    bus_index: usize,
15033    terminal_index: usize,
15034    output: *mut PioStringView,
15035    error: *mut *mut PioError,
15036) -> bool {
15037    unsafe {
15038        entry(error, false, || {
15039            let network = require_multiconductor_network(network)?;
15040            let bus = network.buses().get(bus_index).ok_or_else(|| {
15041                boundary_error(
15042                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15043                    format!("multiconductor bus index {bus_index} is out of range"),
15044                )
15045            })?;
15046            *require_output(output, "output")? =
15047                string_slice_at(&bus.grounded, terminal_index, "grounded bus terminal")?;
15048            Ok(true)
15049        })
15050    }
15051}
15052
15053///
15054/// # Safety
15055/// Pointers and handles must satisfy the crate-level safety requirements.
15056#[unsafe(no_mangle)]
15057pub unsafe extern "C" fn pio_multiconductor_network_line_code_at(
15058    network: *const PioMulticonductorNetwork,
15059    index: usize,
15060    output: *mut PioMulticonductorLineCodeView,
15061    error: *mut *mut PioError,
15062) -> bool {
15063    unsafe {
15064        entry(error, false, || {
15065            let network = require_multiconductor_network(network)?;
15066            let line_code = network.line_codes().get(index).ok_or_else(|| {
15067                boundary_error(
15068                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15069                    format!("multiconductor line code index {index} is out of range"),
15070                )
15071            })?;
15072            *require_output(output, "output")? = multiconductor_line_code_view(line_code);
15073            Ok(true)
15074        })
15075    }
15076}
15077
15078fn multiconductor_line_code_matrix<'a>(
15079    network: &'a powerio_dist::MulticonductorNetwork,
15080    line_code_index: usize,
15081    matrix: &str,
15082) -> Result<&'a powerio_dist::ConductorMatrix, *mut PioError> {
15083    let line_code = network.line_codes().get(line_code_index).ok_or_else(|| {
15084        boundary_error(
15085            &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15086            format!("multiconductor line code index {line_code_index} is out of range"),
15087        )
15088    })?;
15089    Ok(match matrix {
15090        "resistance" => &line_code.r_series,
15091        "reactance" => &line_code.x_series,
15092        "conductance_from" => &line_code.g_from,
15093        "susceptance_from" => &line_code.b_from,
15094        "conductance_to" => &line_code.g_to,
15095        "susceptance_to" => &line_code.b_to,
15096        _ => {
15097            return Err(boundary_error(
15098                &codes::REQUEST_CAPI_TYPE_MISMATCH,
15099                "unsupported line code matrix",
15100            ));
15101        }
15102    })
15103}
15104
15105unsafe fn write_multiconductor_line_code_matrix_row(
15106    network: *const PioMulticonductorNetwork,
15107    line_code_index: usize,
15108    row_index: usize,
15109    matrix: &str,
15110    output: *mut PioF64View,
15111    error: *mut *mut PioError,
15112) -> bool {
15113    unsafe {
15114        entry(error, false, || {
15115            let network = require_multiconductor_network(network)?;
15116            let values = multiconductor_line_code_matrix(network, line_code_index, matrix)?;
15117            *require_output(output, "output")? =
15118                conductor_matrix_row(values, row_index, &format!("line code {matrix} matrix"))?;
15119            Ok(true)
15120        })
15121    }
15122}
15123
15124///
15125/// # Safety
15126/// Pointers and handles must satisfy the crate-level safety requirements.
15127#[unsafe(no_mangle)]
15128pub unsafe extern "C" fn pio_multiconductor_network_line_code_resistance_matrix_row_at(
15129    network: *const PioMulticonductorNetwork,
15130    line_code_index: usize,
15131    row_index: usize,
15132    output: *mut PioF64View,
15133    error: *mut *mut PioError,
15134) -> bool {
15135    unsafe {
15136        write_multiconductor_line_code_matrix_row(
15137            network,
15138            line_code_index,
15139            row_index,
15140            "resistance",
15141            output,
15142            error,
15143        )
15144    }
15145}
15146
15147///
15148/// # Safety
15149/// Pointers and handles must satisfy the crate-level safety requirements.
15150#[unsafe(no_mangle)]
15151pub unsafe extern "C" fn pio_multiconductor_network_line_code_reactance_matrix_row_at(
15152    network: *const PioMulticonductorNetwork,
15153    line_code_index: usize,
15154    row_index: usize,
15155    output: *mut PioF64View,
15156    error: *mut *mut PioError,
15157) -> bool {
15158    unsafe {
15159        write_multiconductor_line_code_matrix_row(
15160            network,
15161            line_code_index,
15162            row_index,
15163            "reactance",
15164            output,
15165            error,
15166        )
15167    }
15168}
15169
15170///
15171/// # Safety
15172/// Pointers and handles must satisfy the crate-level safety requirements.
15173#[unsafe(no_mangle)]
15174pub unsafe extern "C" fn pio_multiconductor_network_line_code_conductance_from_matrix_row_at(
15175    network: *const PioMulticonductorNetwork,
15176    line_code_index: usize,
15177    row_index: usize,
15178    output: *mut PioF64View,
15179    error: *mut *mut PioError,
15180) -> bool {
15181    unsafe {
15182        write_multiconductor_line_code_matrix_row(
15183            network,
15184            line_code_index,
15185            row_index,
15186            "conductance_from",
15187            output,
15188            error,
15189        )
15190    }
15191}
15192
15193///
15194/// # Safety
15195/// Pointers and handles must satisfy the crate-level safety requirements.
15196#[unsafe(no_mangle)]
15197pub unsafe extern "C" fn pio_multiconductor_network_line_code_susceptance_from_matrix_row_at(
15198    network: *const PioMulticonductorNetwork,
15199    line_code_index: usize,
15200    row_index: usize,
15201    output: *mut PioF64View,
15202    error: *mut *mut PioError,
15203) -> bool {
15204    unsafe {
15205        write_multiconductor_line_code_matrix_row(
15206            network,
15207            line_code_index,
15208            row_index,
15209            "susceptance_from",
15210            output,
15211            error,
15212        )
15213    }
15214}
15215
15216///
15217/// # Safety
15218/// Pointers and handles must satisfy the crate-level safety requirements.
15219#[unsafe(no_mangle)]
15220pub unsafe extern "C" fn pio_multiconductor_network_line_code_conductance_to_matrix_row_at(
15221    network: *const PioMulticonductorNetwork,
15222    line_code_index: usize,
15223    row_index: usize,
15224    output: *mut PioF64View,
15225    error: *mut *mut PioError,
15226) -> bool {
15227    unsafe {
15228        write_multiconductor_line_code_matrix_row(
15229            network,
15230            line_code_index,
15231            row_index,
15232            "conductance_to",
15233            output,
15234            error,
15235        )
15236    }
15237}
15238
15239///
15240/// # Safety
15241/// Pointers and handles must satisfy the crate-level safety requirements.
15242#[unsafe(no_mangle)]
15243pub unsafe extern "C" fn pio_multiconductor_network_line_code_susceptance_to_matrix_row_at(
15244    network: *const PioMulticonductorNetwork,
15245    line_code_index: usize,
15246    row_index: usize,
15247    output: *mut PioF64View,
15248    error: *mut *mut PioError,
15249) -> bool {
15250    unsafe {
15251        write_multiconductor_line_code_matrix_row(
15252            network,
15253            line_code_index,
15254            row_index,
15255            "susceptance_to",
15256            output,
15257            error,
15258        )
15259    }
15260}
15261
15262///
15263/// # Safety
15264/// Pointers and handles must satisfy the crate-level safety requirements.
15265#[unsafe(no_mangle)]
15266pub unsafe extern "C" fn pio_multiconductor_network_line_at(
15267    network: *const PioMulticonductorNetwork,
15268    index: usize,
15269    output: *mut PioMulticonductorLineView,
15270    error: *mut *mut PioError,
15271) -> bool {
15272    unsafe {
15273        entry(error, false, || {
15274            let network = require_multiconductor_network(network)?;
15275            let line = network.lines().get(index).ok_or_else(|| {
15276                boundary_error(
15277                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15278                    format!("multiconductor line index {index} is out of range"),
15279                )
15280            })?;
15281            *require_output(output, "output")? = multiconductor_line_view(line);
15282            Ok(true)
15283        })
15284    }
15285}
15286
15287unsafe fn write_multiconductor_line_terminal(
15288    network: *const PioMulticonductorNetwork,
15289    line_index: usize,
15290    terminal_index: usize,
15291    from: bool,
15292    output: *mut PioStringView,
15293    error: *mut *mut PioError,
15294) -> bool {
15295    unsafe {
15296        entry(error, false, || {
15297            let network = require_multiconductor_network(network)?;
15298            let line = network.lines().get(line_index).ok_or_else(|| {
15299                boundary_error(
15300                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15301                    format!("multiconductor line index {line_index} is out of range"),
15302                )
15303            })?;
15304            let (terminals, description) = if from {
15305                (&line.terminal_map_from, "line from terminal")
15306            } else {
15307                (&line.terminal_map_to, "line to terminal")
15308            };
15309            *require_output(output, "output")? =
15310                string_slice_at(terminals, terminal_index, description)?;
15311            Ok(true)
15312        })
15313    }
15314}
15315
15316///
15317/// # Safety
15318/// Pointers and handles must satisfy the crate-level safety requirements.
15319#[unsafe(no_mangle)]
15320pub unsafe extern "C" fn pio_multiconductor_network_line_terminal_from_at(
15321    network: *const PioMulticonductorNetwork,
15322    line_index: usize,
15323    terminal_index: usize,
15324    output: *mut PioStringView,
15325    error: *mut *mut PioError,
15326) -> bool {
15327    unsafe {
15328        write_multiconductor_line_terminal(network, line_index, terminal_index, true, output, error)
15329    }
15330}
15331
15332///
15333/// # Safety
15334/// Pointers and handles must satisfy the crate-level safety requirements.
15335#[unsafe(no_mangle)]
15336pub unsafe extern "C" fn pio_multiconductor_network_line_terminal_to_at(
15337    network: *const PioMulticonductorNetwork,
15338    line_index: usize,
15339    terminal_index: usize,
15340    output: *mut PioStringView,
15341    error: *mut *mut PioError,
15342) -> bool {
15343    unsafe {
15344        write_multiconductor_line_terminal(
15345            network,
15346            line_index,
15347            terminal_index,
15348            false,
15349            output,
15350            error,
15351        )
15352    }
15353}
15354
15355///
15356/// # Safety
15357/// Pointers and handles must satisfy the crate-level safety requirements.
15358#[unsafe(no_mangle)]
15359pub unsafe extern "C" fn pio_multiconductor_network_line_route_point_at(
15360    network: *const PioMulticonductorNetwork,
15361    line_index: usize,
15362    point_index: usize,
15363    output: *mut PioMulticonductorLocationView,
15364    error: *mut *mut PioError,
15365) -> bool {
15366    unsafe {
15367        entry(error, false, || {
15368            let network = require_multiconductor_network(network)?;
15369            let line = network.lines().get(line_index).ok_or_else(|| {
15370                boundary_error(
15371                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15372                    format!("multiconductor line index {line_index} is out of range"),
15373                )
15374            })?;
15375            let route = line.route.as_ref().ok_or_else(|| {
15376                boundary_error(
15377                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
15378                    format!("multiconductor line {line_index} has no route"),
15379                )
15380            })?;
15381            let point = route.get(point_index).ok_or_else(|| {
15382                boundary_error(
15383                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15384                    format!("line route point index {point_index} is out of range"),
15385                )
15386            })?;
15387            *require_output(output, "output")? = multiconductor_location_view(point);
15388            Ok(true)
15389        })
15390    }
15391}
15392
15393///
15394/// # Safety
15395/// Pointers and handles must satisfy the crate-level safety requirements.
15396#[unsafe(no_mangle)]
15397pub unsafe extern "C" fn pio_multiconductor_network_switch_at(
15398    network: *const PioMulticonductorNetwork,
15399    index: usize,
15400    output: *mut PioMulticonductorSwitchView,
15401    error: *mut *mut PioError,
15402) -> bool {
15403    unsafe {
15404        entry(error, false, || {
15405            let network = require_multiconductor_network(network)?;
15406            let switch = network.switches().get(index).ok_or_else(|| {
15407                boundary_error(
15408                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15409                    format!("multiconductor switch index {index} is out of range"),
15410                )
15411            })?;
15412            *require_output(output, "output")? = multiconductor_switch_view(switch);
15413            Ok(true)
15414        })
15415    }
15416}
15417
15418unsafe fn write_multiconductor_switch_terminal(
15419    network: *const PioMulticonductorNetwork,
15420    switch_index: usize,
15421    terminal_index: usize,
15422    from: bool,
15423    output: *mut PioStringView,
15424    error: *mut *mut PioError,
15425) -> bool {
15426    unsafe {
15427        entry(error, false, || {
15428            let network = require_multiconductor_network(network)?;
15429            let switch = network.switches().get(switch_index).ok_or_else(|| {
15430                boundary_error(
15431                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15432                    format!("multiconductor switch index {switch_index} is out of range"),
15433                )
15434            })?;
15435            let (terminals, description) = if from {
15436                (&switch.terminal_map_from, "switch from terminal")
15437            } else {
15438                (&switch.terminal_map_to, "switch to terminal")
15439            };
15440            *require_output(output, "output")? =
15441                string_slice_at(terminals, terminal_index, description)?;
15442            Ok(true)
15443        })
15444    }
15445}
15446
15447///
15448/// # Safety
15449/// Pointers and handles must satisfy the crate-level safety requirements.
15450#[unsafe(no_mangle)]
15451pub unsafe extern "C" fn pio_multiconductor_network_switch_terminal_from_at(
15452    network: *const PioMulticonductorNetwork,
15453    switch_index: usize,
15454    terminal_index: usize,
15455    output: *mut PioStringView,
15456    error: *mut *mut PioError,
15457) -> bool {
15458    unsafe {
15459        write_multiconductor_switch_terminal(
15460            network,
15461            switch_index,
15462            terminal_index,
15463            true,
15464            output,
15465            error,
15466        )
15467    }
15468}
15469
15470///
15471/// # Safety
15472/// Pointers and handles must satisfy the crate-level safety requirements.
15473#[unsafe(no_mangle)]
15474pub unsafe extern "C" fn pio_multiconductor_network_switch_terminal_to_at(
15475    network: *const PioMulticonductorNetwork,
15476    switch_index: usize,
15477    terminal_index: usize,
15478    output: *mut PioStringView,
15479    error: *mut *mut PioError,
15480) -> bool {
15481    unsafe {
15482        write_multiconductor_switch_terminal(
15483            network,
15484            switch_index,
15485            terminal_index,
15486            false,
15487            output,
15488            error,
15489        )
15490    }
15491}
15492
15493///
15494/// # Safety
15495/// Pointers and handles must satisfy the crate-level safety requirements.
15496#[unsafe(no_mangle)]
15497pub unsafe extern "C" fn pio_multiconductor_network_transformer_at(
15498    network: *const PioMulticonductorNetwork,
15499    index: usize,
15500    output: *mut PioMulticonductorTransformerView,
15501    error: *mut *mut PioError,
15502) -> bool {
15503    unsafe {
15504        entry(error, false, || {
15505            let network = require_multiconductor_network(network)?;
15506            let transformer = network.transformers().get(index).ok_or_else(|| {
15507                boundary_error(
15508                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15509                    format!("multiconductor transformer index {index} is out of range"),
15510                )
15511            })?;
15512            *require_output(output, "output")? = PioMulticonductorTransformerView {
15513                name: PioStringView::new(&transformer.name),
15514                winding_count: transformer.windings.len(),
15515                short_circuit_reactance_percent: PioF64View::new(&transformer.xsc_pct),
15516                phase_count: transformer.phases,
15517            };
15518            Ok(true)
15519        })
15520    }
15521}
15522
15523///
15524/// # Safety
15525/// Pointers and handles must satisfy the crate-level safety requirements.
15526#[unsafe(no_mangle)]
15527pub unsafe extern "C" fn pio_multiconductor_network_transformer_winding_at(
15528    network: *const PioMulticonductorNetwork,
15529    transformer_index: usize,
15530    winding_index: usize,
15531    output: *mut PioMulticonductorTransformerWindingView,
15532    error: *mut *mut PioError,
15533) -> bool {
15534    unsafe {
15535        entry(error, false, || {
15536            let network = require_multiconductor_network(network)?;
15537            let transformer = network
15538                .transformers()
15539                .get(transformer_index)
15540                .ok_or_else(|| {
15541                    boundary_error(
15542                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15543                        format!(
15544                            "multiconductor transformer index {transformer_index} is out of range"
15545                        ),
15546                    )
15547                })?;
15548            let winding = transformer.windings.get(winding_index).ok_or_else(|| {
15549                boundary_error(
15550                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15551                    format!("transformer winding index {winding_index} is out of range"),
15552                )
15553            })?;
15554            *require_output(output, "output")? = PioMulticonductorTransformerWindingView {
15555                bus: PioStringView::new(&winding.bus),
15556                terminal_map_count: winding.terminal_map.len(),
15557                connection: PioStringView::new(multiconductor_winding_connection_name(
15558                    winding.conn,
15559                )),
15560                rated_voltage_v: winding.v_ref,
15561                apparent_power_rating_va: winding.s_rating,
15562                resistance_percent: winding.r_pct,
15563                tap: winding.tap,
15564                neutral_resistance_ohm: winding.r_neutral.unwrap_or(0.0),
15565                has_neutral_resistance: winding.r_neutral.is_some(),
15566                neutral_reactance_ohm: winding.x_neutral.unwrap_or(0.0),
15567                has_neutral_reactance: winding.x_neutral.is_some(),
15568            };
15569            Ok(true)
15570        })
15571    }
15572}
15573
15574///
15575/// # Safety
15576/// Pointers and handles must satisfy the crate-level safety requirements.
15577#[unsafe(no_mangle)]
15578pub unsafe extern "C" fn pio_multiconductor_network_transformer_winding_terminal_at(
15579    network: *const PioMulticonductorNetwork,
15580    transformer_index: usize,
15581    winding_index: usize,
15582    terminal_index: usize,
15583    output: *mut PioStringView,
15584    error: *mut *mut PioError,
15585) -> bool {
15586    unsafe {
15587        entry(error, false, || {
15588            let network = require_multiconductor_network(network)?;
15589            let transformer = network
15590                .transformers()
15591                .get(transformer_index)
15592                .ok_or_else(|| {
15593                    boundary_error(
15594                        &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15595                        format!(
15596                            "multiconductor transformer index {transformer_index} is out of range"
15597                        ),
15598                    )
15599                })?;
15600            let winding = transformer.windings.get(winding_index).ok_or_else(|| {
15601                boundary_error(
15602                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15603                    format!("transformer winding index {winding_index} is out of range"),
15604                )
15605            })?;
15606            *require_output(output, "output")? = string_slice_at(
15607                &winding.terminal_map,
15608                terminal_index,
15609                "transformer winding terminal",
15610            )?;
15611            Ok(true)
15612        })
15613    }
15614}
15615
15616#[derive(Clone, Copy)]
15617enum MulticonductorTerminalTable {
15618    Load,
15619    Generator,
15620    InverterBasedResource,
15621    Shunt,
15622    Capacitor,
15623    VoltageSource,
15624}
15625
15626unsafe fn write_multiconductor_terminal(
15627    network: *const PioMulticonductorNetwork,
15628    table: MulticonductorTerminalTable,
15629    element_index: usize,
15630    terminal_index: usize,
15631    output: *mut PioStringView,
15632    error: *mut *mut PioError,
15633) -> bool {
15634    unsafe {
15635        entry(error, false, || {
15636            let network = require_multiconductor_network(network)?;
15637            let (terminals, description): (&[String], &str) = match table {
15638                MulticonductorTerminalTable::Load => (
15639                    &network
15640                        .loads()
15641                        .get(element_index)
15642                        .ok_or_else(|| {
15643                            boundary_error(
15644                                &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15645                                format!(
15646                                    "multiconductor load index {element_index} is out of range"
15647                                ),
15648                            )
15649                        })?
15650                        .terminal_map,
15651                    "load terminal",
15652                ),
15653                MulticonductorTerminalTable::Generator => (
15654                    &network
15655                        .generators()
15656                        .get(element_index)
15657                        .ok_or_else(|| {
15658                            boundary_error(
15659                                &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15660                                format!(
15661                                    "multiconductor generator index {element_index} is out of range"
15662                                ),
15663                            )
15664                        })?
15665                        .terminal_map,
15666                    "generator terminal",
15667                ),
15668                MulticonductorTerminalTable::InverterBasedResource => (
15669                    &network
15670                        .ibrs()
15671                        .get(element_index)
15672                        .ok_or_else(|| {
15673                            boundary_error(
15674                                &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15675                                format!(
15676                                    "inverter based resource index {element_index} is out of range"
15677                                ),
15678                            )
15679                        })?
15680                        .terminal_map,
15681                    "inverter based resource terminal",
15682                ),
15683                MulticonductorTerminalTable::Shunt => (
15684                    &network
15685                        .shunts()
15686                        .get(element_index)
15687                        .ok_or_else(|| {
15688                            boundary_error(
15689                                &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15690                                format!(
15691                                    "multiconductor shunt index {element_index} is out of range"
15692                                ),
15693                            )
15694                        })?
15695                        .terminal_map,
15696                    "shunt terminal",
15697                ),
15698                MulticonductorTerminalTable::Capacitor => (
15699                    &network
15700                        .capacitors()
15701                        .get(element_index)
15702                        .ok_or_else(|| {
15703                            boundary_error(
15704                                &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15705                                format!(
15706                                    "multiconductor capacitor index {element_index} is out of range"
15707                                ),
15708                            )
15709                        })?
15710                        .terminal_map,
15711                    "capacitor terminal",
15712                ),
15713                MulticonductorTerminalTable::VoltageSource => (
15714                    &network
15715                        .sources()
15716                        .get(element_index)
15717                        .ok_or_else(|| {
15718                            boundary_error(
15719                                &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15720                                format!("voltage source index {element_index} is out of range"),
15721                            )
15722                        })?
15723                        .terminal_map,
15724                    "voltage source terminal",
15725                ),
15726            };
15727            *require_output(output, "output")? =
15728                string_slice_at(terminals, terminal_index, description)?;
15729            Ok(true)
15730        })
15731    }
15732}
15733
15734///
15735/// # Safety
15736/// Pointers and handles must satisfy the crate-level safety requirements.
15737#[unsafe(no_mangle)]
15738pub unsafe extern "C" fn pio_multiconductor_network_load_at(
15739    network: *const PioMulticonductorNetwork,
15740    index: usize,
15741    output: *mut PioMulticonductorLoadView,
15742    error: *mut *mut PioError,
15743) -> bool {
15744    unsafe {
15745        entry(error, false, || {
15746            let network = require_multiconductor_network(network)?;
15747            let load = network.loads().get(index).ok_or_else(|| {
15748                boundary_error(
15749                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15750                    format!("multiconductor load index {index} is out of range"),
15751                )
15752            })?;
15753            *require_output(output, "output")? = multiconductor_load_view(load);
15754            Ok(true)
15755        })
15756    }
15757}
15758
15759///
15760/// # Safety
15761/// Pointers and handles must satisfy the crate-level safety requirements.
15762#[unsafe(no_mangle)]
15763pub unsafe extern "C" fn pio_multiconductor_network_load_terminal_at(
15764    network: *const PioMulticonductorNetwork,
15765    load_index: usize,
15766    terminal_index: usize,
15767    output: *mut PioStringView,
15768    error: *mut *mut PioError,
15769) -> bool {
15770    unsafe {
15771        write_multiconductor_terminal(
15772            network,
15773            MulticonductorTerminalTable::Load,
15774            load_index,
15775            terminal_index,
15776            output,
15777            error,
15778        )
15779    }
15780}
15781
15782///
15783/// # Safety
15784/// Pointers and handles must satisfy the crate-level safety requirements.
15785#[unsafe(no_mangle)]
15786pub unsafe extern "C" fn pio_multiconductor_network_generator_at(
15787    network: *const PioMulticonductorNetwork,
15788    index: usize,
15789    output: *mut PioMulticonductorGeneratorView,
15790    error: *mut *mut PioError,
15791) -> bool {
15792    unsafe {
15793        entry(error, false, || {
15794            let network = require_multiconductor_network(network)?;
15795            let generator = network.generators().get(index).ok_or_else(|| {
15796                boundary_error(
15797                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15798                    format!("multiconductor generator index {index} is out of range"),
15799                )
15800            })?;
15801            *require_output(output, "output")? = multiconductor_generator_view(generator);
15802            Ok(true)
15803        })
15804    }
15805}
15806
15807///
15808/// # Safety
15809/// Pointers and handles must satisfy the crate-level safety requirements.
15810#[unsafe(no_mangle)]
15811pub unsafe extern "C" fn pio_multiconductor_network_generator_terminal_at(
15812    network: *const PioMulticonductorNetwork,
15813    generator_index: usize,
15814    terminal_index: usize,
15815    output: *mut PioStringView,
15816    error: *mut *mut PioError,
15817) -> bool {
15818    unsafe {
15819        write_multiconductor_terminal(
15820            network,
15821            MulticonductorTerminalTable::Generator,
15822            generator_index,
15823            terminal_index,
15824            output,
15825            error,
15826        )
15827    }
15828}
15829
15830///
15831/// # Safety
15832/// Pointers and handles must satisfy the crate-level safety requirements.
15833#[unsafe(no_mangle)]
15834pub unsafe extern "C" fn pio_multiconductor_network_inverter_based_resource_at(
15835    network: *const PioMulticonductorNetwork,
15836    index: usize,
15837    output: *mut PioInverterBasedResourceView,
15838    error: *mut *mut PioError,
15839) -> bool {
15840    unsafe {
15841        entry(error, false, || {
15842            let network = require_multiconductor_network(network)?;
15843            let resource = network.ibrs().get(index).ok_or_else(|| {
15844                boundary_error(
15845                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15846                    format!("inverter based resource index {index} is out of range"),
15847                )
15848            })?;
15849            *require_output(output, "output")? = inverter_based_resource_view(resource);
15850            Ok(true)
15851        })
15852    }
15853}
15854
15855///
15856/// # Safety
15857/// Pointers and handles must satisfy the crate-level safety requirements.
15858#[unsafe(no_mangle)]
15859pub unsafe extern "C" fn pio_multiconductor_network_inverter_based_resource_terminal_at(
15860    network: *const PioMulticonductorNetwork,
15861    resource_index: usize,
15862    terminal_index: usize,
15863    output: *mut PioStringView,
15864    error: *mut *mut PioError,
15865) -> bool {
15866    unsafe {
15867        write_multiconductor_terminal(
15868            network,
15869            MulticonductorTerminalTable::InverterBasedResource,
15870            resource_index,
15871            terminal_index,
15872            output,
15873            error,
15874        )
15875    }
15876}
15877
15878///
15879/// # Safety
15880/// Pointers and handles must satisfy the crate-level safety requirements.
15881#[unsafe(no_mangle)]
15882pub unsafe extern "C" fn pio_multiconductor_network_control_profile_at(
15883    network: *const PioMulticonductorNetwork,
15884    index: usize,
15885    output: *mut PioControlProfileView,
15886    error: *mut *mut PioError,
15887) -> bool {
15888    unsafe {
15889        entry(error, false, || {
15890            let network = require_multiconductor_network(network)?;
15891            let profile = network.control_profiles().get(index).ok_or_else(|| {
15892                boundary_error(
15893                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15894                    format!("control profile index {index} is out of range"),
15895                )
15896            })?;
15897            *require_output(output, "output")? = control_profile_view(profile);
15898            Ok(true)
15899        })
15900    }
15901}
15902
15903///
15904/// # Safety
15905/// Pointers and handles must satisfy the crate-level safety requirements.
15906#[unsafe(no_mangle)]
15907pub unsafe extern "C" fn pio_multiconductor_network_shunt_at(
15908    network: *const PioMulticonductorNetwork,
15909    index: usize,
15910    output: *mut PioMulticonductorShuntView,
15911    error: *mut *mut PioError,
15912) -> bool {
15913    unsafe {
15914        entry(error, false, || {
15915            let network = require_multiconductor_network(network)?;
15916            let shunt = network.shunts().get(index).ok_or_else(|| {
15917                boundary_error(
15918                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15919                    format!("multiconductor shunt index {index} is out of range"),
15920                )
15921            })?;
15922            *require_output(output, "output")? = PioMulticonductorShuntView {
15923                name: PioStringView::new(&shunt.name),
15924                bus: PioStringView::new(&shunt.bus),
15925                terminal_map_count: shunt.terminal_map.len(),
15926                conductance_matrix_row_count: shunt.g.len(),
15927                susceptance_matrix_row_count: shunt.b.len(),
15928            };
15929            Ok(true)
15930        })
15931    }
15932}
15933
15934///
15935/// # Safety
15936/// Pointers and handles must satisfy the crate-level safety requirements.
15937#[unsafe(no_mangle)]
15938pub unsafe extern "C" fn pio_multiconductor_network_shunt_terminal_at(
15939    network: *const PioMulticonductorNetwork,
15940    shunt_index: usize,
15941    terminal_index: usize,
15942    output: *mut PioStringView,
15943    error: *mut *mut PioError,
15944) -> bool {
15945    unsafe {
15946        write_multiconductor_terminal(
15947            network,
15948            MulticonductorTerminalTable::Shunt,
15949            shunt_index,
15950            terminal_index,
15951            output,
15952            error,
15953        )
15954    }
15955}
15956
15957unsafe fn write_multiconductor_shunt_matrix_row(
15958    network: *const PioMulticonductorNetwork,
15959    shunt_index: usize,
15960    row_index: usize,
15961    conductance: bool,
15962    output: *mut PioF64View,
15963    error: *mut *mut PioError,
15964) -> bool {
15965    unsafe {
15966        entry(error, false, || {
15967            let network = require_multiconductor_network(network)?;
15968            let shunt = network.shunts().get(shunt_index).ok_or_else(|| {
15969                boundary_error(
15970                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15971                    format!("multiconductor shunt index {shunt_index} is out of range"),
15972                )
15973            })?;
15974            let (matrix, description) = if conductance {
15975                (&shunt.g, "shunt conductance matrix")
15976            } else {
15977                (&shunt.b, "shunt susceptance matrix")
15978            };
15979            *require_output(output, "output")? =
15980                conductor_matrix_row(matrix, row_index, description)?;
15981            Ok(true)
15982        })
15983    }
15984}
15985
15986///
15987/// # Safety
15988/// Pointers and handles must satisfy the crate-level safety requirements.
15989#[unsafe(no_mangle)]
15990pub unsafe extern "C" fn pio_multiconductor_network_shunt_conductance_matrix_row_at(
15991    network: *const PioMulticonductorNetwork,
15992    shunt_index: usize,
15993    row_index: usize,
15994    output: *mut PioF64View,
15995    error: *mut *mut PioError,
15996) -> bool {
15997    unsafe {
15998        write_multiconductor_shunt_matrix_row(network, shunt_index, row_index, true, output, error)
15999    }
16000}
16001
16002///
16003/// # Safety
16004/// Pointers and handles must satisfy the crate-level safety requirements.
16005#[unsafe(no_mangle)]
16006pub unsafe extern "C" fn pio_multiconductor_network_shunt_susceptance_matrix_row_at(
16007    network: *const PioMulticonductorNetwork,
16008    shunt_index: usize,
16009    row_index: usize,
16010    output: *mut PioF64View,
16011    error: *mut *mut PioError,
16012) -> bool {
16013    unsafe {
16014        write_multiconductor_shunt_matrix_row(network, shunt_index, row_index, false, output, error)
16015    }
16016}
16017
16018///
16019/// # Safety
16020/// Pointers and handles must satisfy the crate-level safety requirements.
16021#[unsafe(no_mangle)]
16022pub unsafe extern "C" fn pio_multiconductor_network_capacitor_at(
16023    network: *const PioMulticonductorNetwork,
16024    index: usize,
16025    output: *mut PioMulticonductorCapacitorView,
16026    error: *mut *mut PioError,
16027) -> bool {
16028    unsafe {
16029        entry(error, false, || {
16030            let network = require_multiconductor_network(network)?;
16031            let capacitor = network.capacitors().get(index).ok_or_else(|| {
16032                boundary_error(
16033                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
16034                    format!("multiconductor capacitor index {index} is out of range"),
16035                )
16036            })?;
16037            *require_output(output, "output")? = PioMulticonductorCapacitorView {
16038                name: PioStringView::new(&capacitor.name),
16039                bus: PioStringView::new(&capacitor.bus),
16040                terminal_map_count: capacitor.terminal_map.len(),
16041                configuration: PioStringView::new(multiconductor_configuration_name(
16042                    capacitor.configuration,
16043                )),
16044                rated_reactive_power_var: capacitor.q_rated,
16045                nominal_voltage_v: capacitor.v_nom,
16046            };
16047            Ok(true)
16048        })
16049    }
16050}
16051
16052///
16053/// # Safety
16054/// Pointers and handles must satisfy the crate-level safety requirements.
16055#[unsafe(no_mangle)]
16056pub unsafe extern "C" fn pio_multiconductor_network_capacitor_terminal_at(
16057    network: *const PioMulticonductorNetwork,
16058    capacitor_index: usize,
16059    terminal_index: usize,
16060    output: *mut PioStringView,
16061    error: *mut *mut PioError,
16062) -> bool {
16063    unsafe {
16064        write_multiconductor_terminal(
16065            network,
16066            MulticonductorTerminalTable::Capacitor,
16067            capacitor_index,
16068            terminal_index,
16069            output,
16070            error,
16071        )
16072    }
16073}
16074
16075///
16076/// # Safety
16077/// Pointers and handles must satisfy the crate-level safety requirements.
16078#[unsafe(no_mangle)]
16079pub unsafe extern "C" fn pio_multiconductor_network_voltage_source_at(
16080    network: *const PioMulticonductorNetwork,
16081    index: usize,
16082    output: *mut PioVoltageSourceView,
16083    error: *mut *mut PioError,
16084) -> bool {
16085    unsafe {
16086        entry(error, false, || {
16087            let network = require_multiconductor_network(network)?;
16088            let source = network.sources().get(index).ok_or_else(|| {
16089                boundary_error(
16090                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
16091                    format!("voltage source index {index} is out of range"),
16092                )
16093            })?;
16094            *require_output(output, "output")? = PioVoltageSourceView {
16095                name: PioStringView::new(&source.name),
16096                bus: PioStringView::new(&source.bus),
16097                terminal_map_count: source.terminal_map.len(),
16098                voltage_magnitude_v: PioF64View::new(&source.v_magnitude),
16099                voltage_angle_rad: PioF64View::new(&source.v_angle),
16100                energy_cost_rate_per_kwh: PioF64View::new(
16101                    source.energy_cost_rate.as_deref().unwrap_or(&[]),
16102                ),
16103                has_energy_cost_rate: source.energy_cost_rate.is_some(),
16104            };
16105            Ok(true)
16106        })
16107    }
16108}
16109
16110///
16111/// # Safety
16112/// Pointers and handles must satisfy the crate-level safety requirements.
16113#[unsafe(no_mangle)]
16114pub unsafe extern "C" fn pio_multiconductor_network_voltage_source_terminal_at(
16115    network: *const PioMulticonductorNetwork,
16116    source_index: usize,
16117    terminal_index: usize,
16118    output: *mut PioStringView,
16119    error: *mut *mut PioError,
16120) -> bool {
16121    unsafe {
16122        write_multiconductor_terminal(
16123            network,
16124            MulticonductorTerminalTable::VoltageSource,
16125            source_index,
16126            terminal_index,
16127            output,
16128            error,
16129        )
16130    }
16131}
16132
16133///
16134/// # Safety
16135/// Pointers and handles must satisfy the crate-level safety requirements.
16136#[unsafe(no_mangle)]
16137pub unsafe extern "C" fn pio_multiconductor_network_untyped_object_at(
16138    network: *const PioMulticonductorNetwork,
16139    index: usize,
16140    output: *mut PioMulticonductorUntypedObjectView,
16141    error: *mut *mut PioError,
16142) -> bool {
16143    unsafe {
16144        entry(error, false, || {
16145            let network = require_multiconductor_network(network)?;
16146            let object = network.untyped_objects().get(index).ok_or_else(|| {
16147                boundary_error(
16148                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
16149                    format!("untyped object index {index} is out of range"),
16150                )
16151            })?;
16152            *require_output(output, "output")? = PioMulticonductorUntypedObjectView {
16153                class_name: PioStringView::new(&object.class),
16154                name: PioStringView::new(&object.name),
16155                property_count: object.props.len(),
16156            };
16157            Ok(true)
16158        })
16159    }
16160}
16161
16162///
16163/// # Safety
16164/// Pointers and handles must satisfy the crate-level safety requirements.
16165#[unsafe(no_mangle)]
16166pub unsafe extern "C" fn pio_multiconductor_network_untyped_object_property_at(
16167    network: *const PioMulticonductorNetwork,
16168    object_index: usize,
16169    property_index: usize,
16170    output: *mut PioMulticonductorUntypedPropertyView,
16171    error: *mut *mut PioError,
16172) -> bool {
16173    unsafe {
16174        entry(error, false, || {
16175            let network = require_multiconductor_network(network)?;
16176            let object = network.untyped_objects().get(object_index).ok_or_else(|| {
16177                boundary_error(
16178                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
16179                    format!("untyped object index {object_index} is out of range"),
16180                )
16181            })?;
16182            let (name, value) = object.props.get(property_index).ok_or_else(|| {
16183                boundary_error(
16184                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
16185                    format!("untyped object property index {property_index} is out of range"),
16186                )
16187            })?;
16188            let (name, has_name) = optional_string_view(name.as_deref());
16189            *require_output(output, "output")? = PioMulticonductorUntypedPropertyView {
16190                name,
16191                has_name,
16192                value: PioStringView::new(value),
16193            };
16194            Ok(true)
16195        })
16196    }
16197}
16198
16199///
16200/// # Safety
16201/// Pointers and handles must satisfy the crate-level safety requirements.
16202#[unsafe(no_mangle)]
16203pub unsafe extern "C" fn pio_multiconductor_network_command_at(
16204    network: *const PioMulticonductorNetwork,
16205    index: usize,
16206    output: *mut PioMulticonductorCommandView,
16207    error: *mut *mut PioError,
16208) -> bool {
16209    unsafe {
16210        entry(error, false, || {
16211            let network = require_multiconductor_network(network)?;
16212            let (verb, args) = network.commands().get(index).ok_or_else(|| {
16213                boundary_error(
16214                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
16215                    format!("source command index {index} is out of range"),
16216                )
16217            })?;
16218            *require_output(output, "output")? = PioMulticonductorCommandView {
16219                verb: PioStringView::new(verb),
16220                args: PioStringView::new(args),
16221            };
16222            Ok(true)
16223        })
16224    }
16225}
16226
16227///
16228/// # Safety
16229/// Pointers and handles must satisfy the crate-level safety requirements.
16230#[unsafe(no_mangle)]
16231pub unsafe extern "C" fn pio_multiconductor_network_option_at(
16232    network: *const PioMulticonductorNetwork,
16233    index: usize,
16234    output: *mut PioStringPropertyView,
16235    error: *mut *mut PioError,
16236) -> bool {
16237    unsafe {
16238        entry(error, false, || {
16239            let network = require_multiconductor_network(network)?;
16240            let (name, value) = network.options().get(index).ok_or_else(|| {
16241                boundary_error(
16242                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
16243                    format!("source option index {index} is out of range"),
16244                )
16245            })?;
16246            *require_output(output, "output")? = PioStringPropertyView {
16247                name: PioStringView::new(name),
16248                value: PioStringView::new(value),
16249            };
16250            Ok(true)
16251        })
16252    }
16253}
16254
16255///
16256/// # Safety
16257/// Pointers and handles must satisfy the crate-level safety requirements.
16258/// The input handle must remain live and immutable throughout this call.
16259#[unsafe(no_mangle)]
16260pub unsafe extern "C" fn pio_multiconductor_network_retain(
16261    network: *const PioMulticonductorNetwork,
16262) -> *mut PioMulticonductorNetwork {
16263    unsafe { PioMulticonductorNetwork::retain_raw(network) }
16264}
16265
16266///
16267/// # Safety
16268/// Pointers and handles must satisfy the crate-level safety requirements.
16269/// A non-null handle must be owned and unused by concurrent calls.
16270#[unsafe(no_mangle)]
16271pub unsafe extern "C" fn pio_multiconductor_network_release(
16272    network: *mut PioMulticonductorNetwork,
16273) {
16274    unsafe { PioMulticonductorNetwork::release_raw(network) };
16275}
16276
16277// ---- typed updates ---------------------------------------------------------
16278
16279opaque_handle!(
16280    /// Stable, type-qualified component identity.
16281    PioComponentId,
16282    ComponentId
16283);
16284opaque_handle!(
16285    /// Active power replacement with an explicit unit.
16286    PioActivePower,
16287    ActivePower
16288);
16289opaque_handle!(
16290    /// Reactive power replacement with an explicit unit.
16291    PioReactivePower,
16292    ReactivePower
16293);
16294opaque_handle!(
16295    /// Apparent power replacement with an explicit unit.
16296    PioApparentPower,
16297    ApparentPower
16298);
16299opaque_handle!(
16300    /// Typed update to an operating point.
16301    PioOperatingPointUpdate,
16302    OperatingPointUpdate
16303);
16304opaque_handle!(
16305    /// Typed update to physical network data.
16306    PioNetworkUpdate,
16307    NetworkUpdate
16308);
16309opaque_handle!(
16310    /// Typed update to a calculation instance.
16311    PioCalculationUpdate,
16312    CalculationUpdate
16313);
16314
16315struct UpdateReportInner {
16316    changes: Vec<UpdateChange>,
16317    connectivity_changed: bool,
16318}
16319
16320struct UpdateChangeInner {
16321    owner: Arc<UpdateReportInner>,
16322    index: usize,
16323}
16324
16325impl UpdateChangeInner {
16326    fn change(&self) -> Option<&UpdateChange> {
16327        self.owner.changes.get(self.index)
16328    }
16329}
16330
16331opaque_handle!(
16332    /// Exact changes made by one atomic update batch.
16333    PioUpdateReport,
16334    UpdateReportInner
16335);
16336opaque_handle!(
16337    /// Owner-rooted view of one changed component field.
16338    PioUpdateChange,
16339    UpdateChangeInner
16340);
16341
16342unsafe fn require_handle<'a, T>(
16343    handle: *const HandleBox<T>,
16344    name: &str,
16345) -> Result<&'a T, *mut PioError> {
16346    unsafe { handle_get(handle) }.ok_or_else(|| {
16347        boundary_error(
16348            &codes::BIND_CAPI_NULL_HANDLE,
16349            format!("{name} must not be NULL"),
16350        )
16351    })
16352}
16353
16354unsafe fn optional_owned_string(
16355    value: *const c_char,
16356    value_len: usize,
16357    name: &str,
16358) -> Result<Option<String>, *mut PioError> {
16359    unsafe { optional_str(value, value_len, name) }.map(|value| value.map(str::to_owned))
16360}
16361
16362/// Construct a stable component identity.
16363///
16364/// # Safety
16365/// Pointers and handles must satisfy the crate-level safety requirements.
16366#[unsafe(no_mangle)]
16367pub unsafe extern "C" fn pio_component_id_new(
16368    component_type: *const c_char,
16369    component_type_len: usize,
16370    local_id: *const c_char,
16371    local_id_len: usize,
16372    error: *mut *mut PioError,
16373) -> *mut PioComponentId {
16374    unsafe {
16375        entry(error, std::ptr::null_mut(), || {
16376            let component_type =
16377                required_str(component_type, component_type_len, "component_type")?;
16378            let local_id = required_str(local_id, local_id_len, "local_id")?;
16379            ComponentId::new(component_type, local_id)
16380                .map(PioComponentId::new_raw)
16381                .map_err(|failure| error_from_core(&failure))
16382        })
16383    }
16384}
16385
16386///
16387/// # Safety
16388/// Pointers and handles must satisfy the crate-level safety requirements.
16389#[unsafe(no_mangle)]
16390pub unsafe extern "C" fn pio_component_id_type(component: *const PioComponentId) -> PioStringView {
16391    unsafe { PioComponentId::get(component) }.map_or(PioStringView::EMPTY, |component| {
16392        PioStringView::new(component.component_type())
16393    })
16394}
16395
16396///
16397/// # Safety
16398/// Pointers and handles must satisfy the crate-level safety requirements.
16399#[unsafe(no_mangle)]
16400pub unsafe extern "C" fn pio_component_id_local_id(
16401    component: *const PioComponentId,
16402) -> PioStringView {
16403    unsafe { PioComponentId::get(component) }.map_or(PioStringView::EMPTY, |component| {
16404        PioStringView::new(component.local_id())
16405    })
16406}
16407
16408///
16409/// # Safety
16410/// Pointers and handles must satisfy the crate-level safety requirements.
16411/// The input handle must remain live and immutable throughout this call.
16412#[unsafe(no_mangle)]
16413pub unsafe extern "C" fn pio_component_id_retain(
16414    component: *const PioComponentId,
16415) -> *mut PioComponentId {
16416    unsafe { PioComponentId::retain_raw(component) }
16417}
16418
16419///
16420/// # Safety
16421/// Pointers and handles must satisfy the crate-level safety requirements.
16422/// A non-null handle must be owned and unused by concurrent calls.
16423#[unsafe(no_mangle)]
16424pub unsafe extern "C" fn pio_component_id_release(component: *mut PioComponentId) {
16425    unsafe { PioComponentId::release_raw(component) };
16426}
16427
16428#[unsafe(no_mangle)]
16429pub extern "C" fn pio_active_power_from_watts(value: f64) -> *mut PioActivePower {
16430    PioActivePower::new_raw(ActivePower::from_watts(value))
16431}
16432
16433#[unsafe(no_mangle)]
16434pub extern "C" fn pio_active_power_from_megawatts(value: f64) -> *mut PioActivePower {
16435    PioActivePower::new_raw(ActivePower::from_megawatts(value))
16436}
16437
16438///
16439/// # Safety
16440/// Pointers and handles must satisfy the crate-level safety requirements.
16441#[unsafe(no_mangle)]
16442pub unsafe extern "C" fn pio_active_power_value(power: *const PioActivePower) -> f64 {
16443    unsafe { PioActivePower::get(power) }.map_or(f64::NAN, |power| power.value())
16444}
16445
16446///
16447/// # Safety
16448/// Pointers and handles must satisfy the crate-level safety requirements.
16449#[unsafe(no_mangle)]
16450pub unsafe extern "C" fn pio_active_power_unit(power: *const PioActivePower) -> PioStringView {
16451    unsafe { PioActivePower::get(power) }.map_or(PioStringView::EMPTY, |power| {
16452        PioStringView::new(match power.unit() {
16453            ActivePowerUnit::Watts => "watts",
16454            ActivePowerUnit::Megawatts => "megawatts",
16455            _ => "unknown",
16456        })
16457    })
16458}
16459
16460///
16461/// # Safety
16462/// Pointers and handles must satisfy the crate-level safety requirements.
16463/// The input handle must remain live and immutable throughout this call.
16464#[unsafe(no_mangle)]
16465pub unsafe extern "C" fn pio_active_power_retain(
16466    power: *const PioActivePower,
16467) -> *mut PioActivePower {
16468    unsafe { PioActivePower::retain_raw(power) }
16469}
16470
16471///
16472/// # Safety
16473/// Pointers and handles must satisfy the crate-level safety requirements.
16474/// A non-null handle must be owned and unused by concurrent calls.
16475#[unsafe(no_mangle)]
16476pub unsafe extern "C" fn pio_active_power_release(power: *mut PioActivePower) {
16477    unsafe { PioActivePower::release_raw(power) };
16478}
16479
16480#[unsafe(no_mangle)]
16481pub extern "C" fn pio_reactive_power_from_vars(value: f64) -> *mut PioReactivePower {
16482    PioReactivePower::new_raw(ReactivePower::from_vars(value))
16483}
16484
16485#[unsafe(no_mangle)]
16486pub extern "C" fn pio_reactive_power_from_megavars(value: f64) -> *mut PioReactivePower {
16487    PioReactivePower::new_raw(ReactivePower::from_megavars(value))
16488}
16489
16490///
16491/// # Safety
16492/// Pointers and handles must satisfy the crate-level safety requirements.
16493#[unsafe(no_mangle)]
16494pub unsafe extern "C" fn pio_reactive_power_value(power: *const PioReactivePower) -> f64 {
16495    unsafe { PioReactivePower::get(power) }.map_or(f64::NAN, |power| power.value())
16496}
16497
16498///
16499/// # Safety
16500/// Pointers and handles must satisfy the crate-level safety requirements.
16501#[unsafe(no_mangle)]
16502pub unsafe extern "C" fn pio_reactive_power_unit(power: *const PioReactivePower) -> PioStringView {
16503    unsafe { PioReactivePower::get(power) }.map_or(PioStringView::EMPTY, |power| {
16504        PioStringView::new(match power.unit() {
16505            ReactivePowerUnit::Vars => "vars",
16506            ReactivePowerUnit::Megavars => "megavars",
16507            _ => "unknown",
16508        })
16509    })
16510}
16511
16512///
16513/// # Safety
16514/// Pointers and handles must satisfy the crate-level safety requirements.
16515/// The input handle must remain live and immutable throughout this call.
16516#[unsafe(no_mangle)]
16517pub unsafe extern "C" fn pio_reactive_power_retain(
16518    power: *const PioReactivePower,
16519) -> *mut PioReactivePower {
16520    unsafe { PioReactivePower::retain_raw(power) }
16521}
16522
16523///
16524/// # Safety
16525/// Pointers and handles must satisfy the crate-level safety requirements.
16526/// A non-null handle must be owned and unused by concurrent calls.
16527#[unsafe(no_mangle)]
16528pub unsafe extern "C" fn pio_reactive_power_release(power: *mut PioReactivePower) {
16529    unsafe { PioReactivePower::release_raw(power) };
16530}
16531
16532#[unsafe(no_mangle)]
16533pub extern "C" fn pio_apparent_power_from_volt_amperes(value: f64) -> *mut PioApparentPower {
16534    PioApparentPower::new_raw(ApparentPower::from_volt_amperes(value))
16535}
16536
16537#[unsafe(no_mangle)]
16538pub extern "C" fn pio_apparent_power_from_megavolt_amperes(value: f64) -> *mut PioApparentPower {
16539    PioApparentPower::new_raw(ApparentPower::from_megavolt_amperes(value))
16540}
16541
16542///
16543/// # Safety
16544/// Pointers and handles must satisfy the crate-level safety requirements.
16545#[unsafe(no_mangle)]
16546pub unsafe extern "C" fn pio_apparent_power_value(power: *const PioApparentPower) -> f64 {
16547    unsafe { PioApparentPower::get(power) }.map_or(f64::NAN, |power| power.value())
16548}
16549
16550///
16551/// # Safety
16552/// Pointers and handles must satisfy the crate-level safety requirements.
16553#[unsafe(no_mangle)]
16554pub unsafe extern "C" fn pio_apparent_power_unit(power: *const PioApparentPower) -> PioStringView {
16555    unsafe { PioApparentPower::get(power) }.map_or(PioStringView::EMPTY, |power| {
16556        PioStringView::new(match power.unit() {
16557            ApparentPowerUnit::VoltAmperes => "volt_amperes",
16558            ApparentPowerUnit::MegavoltAmperes => "megavolt_amperes",
16559            _ => "unknown",
16560        })
16561    })
16562}
16563
16564///
16565/// # Safety
16566/// Pointers and handles must satisfy the crate-level safety requirements.
16567/// The input handle must remain live and immutable throughout this call.
16568#[unsafe(no_mangle)]
16569pub unsafe extern "C" fn pio_apparent_power_retain(
16570    power: *const PioApparentPower,
16571) -> *mut PioApparentPower {
16572    unsafe { PioApparentPower::retain_raw(power) }
16573}
16574
16575///
16576/// # Safety
16577/// Pointers and handles must satisfy the crate-level safety requirements.
16578/// A non-null handle must be owned and unused by concurrent calls.
16579#[unsafe(no_mangle)]
16580pub unsafe extern "C" fn pio_apparent_power_release(power: *mut PioApparentPower) {
16581    unsafe { PioApparentPower::release_raw(power) };
16582}
16583
16584unsafe fn component_clone(component: *const PioComponentId) -> Result<ComponentId, *mut PioError> {
16585    unsafe { require_handle(component.cast(), "PioComponentId") }.cloned()
16586}
16587
16588unsafe fn active_power_copy(power: *const PioActivePower) -> Result<ActivePower, *mut PioError> {
16589    unsafe { require_handle(power.cast(), "PioActivePower") }.copied()
16590}
16591
16592unsafe fn reactive_power_copy(
16593    power: *const PioReactivePower,
16594) -> Result<ReactivePower, *mut PioError> {
16595    unsafe { require_handle(power.cast(), "PioReactivePower") }.copied()
16596}
16597
16598unsafe fn apparent_power_copy(
16599    power: *const PioApparentPower,
16600) -> Result<ApparentPower, *mut PioError> {
16601    unsafe { require_handle(power.cast(), "PioApparentPower") }.copied()
16602}
16603
16604///
16605/// # Safety
16606/// Pointers and handles must satisfy the crate-level safety requirements.
16607#[unsafe(no_mangle)]
16608pub unsafe extern "C" fn pio_operating_point_update_set_load_active_power(
16609    load: *const PioComponentId,
16610    terminal: *const c_char,
16611    terminal_len: usize,
16612    power: *const PioActivePower,
16613    error: *mut *mut PioError,
16614) -> *mut PioOperatingPointUpdate {
16615    unsafe {
16616        entry(error, std::ptr::null_mut(), || {
16617            Ok(PioOperatingPointUpdate::new_raw(
16618                OperatingPointUpdate::LoadActivePower {
16619                    load: component_clone(load)?,
16620                    terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
16621                    p: active_power_copy(power)?,
16622                },
16623            ))
16624        })
16625    }
16626}
16627
16628///
16629/// # Safety
16630/// Pointers and handles must satisfy the crate-level safety requirements.
16631#[unsafe(no_mangle)]
16632pub unsafe extern "C" fn pio_operating_point_update_set_load_reactive_power(
16633    load: *const PioComponentId,
16634    terminal: *const c_char,
16635    terminal_len: usize,
16636    power: *const PioReactivePower,
16637    error: *mut *mut PioError,
16638) -> *mut PioOperatingPointUpdate {
16639    unsafe {
16640        entry(error, std::ptr::null_mut(), || {
16641            Ok(PioOperatingPointUpdate::new_raw(
16642                OperatingPointUpdate::LoadReactivePower {
16643                    load: component_clone(load)?,
16644                    terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
16645                    q: reactive_power_copy(power)?,
16646                },
16647            ))
16648        })
16649    }
16650}
16651
16652///
16653/// # Safety
16654/// Pointers and handles must satisfy the crate-level safety requirements.
16655#[unsafe(no_mangle)]
16656pub unsafe extern "C" fn pio_operating_point_update_set_generator_active_power(
16657    generator: *const PioComponentId,
16658    terminal: *const c_char,
16659    terminal_len: usize,
16660    power: *const PioActivePower,
16661    error: *mut *mut PioError,
16662) -> *mut PioOperatingPointUpdate {
16663    unsafe {
16664        entry(error, std::ptr::null_mut(), || {
16665            Ok(PioOperatingPointUpdate::new_raw(
16666                OperatingPointUpdate::GeneratorActivePower {
16667                    generator: component_clone(generator)?,
16668                    terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
16669                    p: active_power_copy(power)?,
16670                },
16671            ))
16672        })
16673    }
16674}
16675
16676///
16677/// # Safety
16678/// Pointers and handles must satisfy the crate-level safety requirements.
16679#[unsafe(no_mangle)]
16680pub unsafe extern "C" fn pio_operating_point_update_set_generator_reactive_power(
16681    generator: *const PioComponentId,
16682    terminal: *const c_char,
16683    terminal_len: usize,
16684    power: *const PioReactivePower,
16685    error: *mut *mut PioError,
16686) -> *mut PioOperatingPointUpdate {
16687    unsafe {
16688        entry(error, std::ptr::null_mut(), || {
16689            Ok(PioOperatingPointUpdate::new_raw(
16690                OperatingPointUpdate::GeneratorReactivePower {
16691                    generator: component_clone(generator)?,
16692                    terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
16693                    q: reactive_power_copy(power)?,
16694                },
16695            ))
16696        })
16697    }
16698}
16699
16700///
16701/// # Safety
16702/// Pointers and handles must satisfy the crate-level safety requirements.
16703#[unsafe(no_mangle)]
16704pub unsafe extern "C" fn pio_operating_point_update_set_generator_voltage_magnitude(
16705    generator: *const PioComponentId,
16706    voltage_magnitude_per_unit: f64,
16707    error: *mut *mut PioError,
16708) -> *mut PioOperatingPointUpdate {
16709    unsafe {
16710        entry(error, std::ptr::null_mut(), || {
16711            Ok(PioOperatingPointUpdate::new_raw(
16712                OperatingPointUpdate::GeneratorVoltageMagnitude {
16713                    generator: component_clone(generator)?,
16714                    vm_pu: voltage_magnitude_per_unit,
16715                },
16716            ))
16717        })
16718    }
16719}
16720
16721///
16722/// # Safety
16723/// Pointers and handles must satisfy the crate-level safety requirements.
16724#[unsafe(no_mangle)]
16725pub unsafe extern "C" fn pio_operating_point_update_set_generator_in_service(
16726    generator: *const PioComponentId,
16727    in_service: bool,
16728    error: *mut *mut PioError,
16729) -> *mut PioOperatingPointUpdate {
16730    unsafe {
16731        entry(error, std::ptr::null_mut(), || {
16732            Ok(PioOperatingPointUpdate::new_raw(
16733                OperatingPointUpdate::GeneratorInService {
16734                    generator: component_clone(generator)?,
16735                    in_service,
16736                },
16737            ))
16738        })
16739    }
16740}
16741
16742///
16743/// # Safety
16744/// Pointers and handles must satisfy the crate-level safety requirements.
16745#[unsafe(no_mangle)]
16746pub unsafe extern "C" fn pio_operating_point_update_set_branch_in_service(
16747    branch: *const PioComponentId,
16748    in_service: bool,
16749    error: *mut *mut PioError,
16750) -> *mut PioOperatingPointUpdate {
16751    unsafe {
16752        entry(error, std::ptr::null_mut(), || {
16753            Ok(PioOperatingPointUpdate::new_raw(
16754                OperatingPointUpdate::BranchInService {
16755                    branch: component_clone(branch)?,
16756                    in_service,
16757                },
16758            ))
16759        })
16760    }
16761}
16762
16763///
16764/// # Safety
16765/// Pointers and handles must satisfy the crate-level safety requirements.
16766#[unsafe(no_mangle)]
16767pub unsafe extern "C" fn pio_operating_point_update_set_transformer_tap_ratio(
16768    transformer: *const PioComponentId,
16769    tap_ratio: f64,
16770    error: *mut *mut PioError,
16771) -> *mut PioOperatingPointUpdate {
16772    unsafe {
16773        entry(error, std::ptr::null_mut(), || {
16774            Ok(PioOperatingPointUpdate::new_raw(
16775                OperatingPointUpdate::TransformerTapRatio {
16776                    transformer: component_clone(transformer)?,
16777                    tap_ratio,
16778                },
16779            ))
16780        })
16781    }
16782}
16783
16784///
16785/// # Safety
16786/// Pointers and handles must satisfy the crate-level safety requirements.
16787#[unsafe(no_mangle)]
16788pub unsafe extern "C" fn pio_operating_point_update_set_transformer_phase_shift_degrees(
16789    transformer: *const PioComponentId,
16790    phase_shift_degrees: f64,
16791    error: *mut *mut PioError,
16792) -> *mut PioOperatingPointUpdate {
16793    unsafe {
16794        entry(error, std::ptr::null_mut(), || {
16795            Ok(PioOperatingPointUpdate::new_raw(
16796                OperatingPointUpdate::TransformerPhaseShift {
16797                    transformer: component_clone(transformer)?,
16798                    shift_degrees: phase_shift_degrees,
16799                },
16800            ))
16801        })
16802    }
16803}
16804
16805///
16806/// # Safety
16807/// Pointers and handles must satisfy the crate-level safety requirements.
16808#[unsafe(no_mangle)]
16809pub unsafe extern "C" fn pio_operating_point_update_set_switch_closed(
16810    switch_id: *const PioComponentId,
16811    closed: bool,
16812    error: *mut *mut PioError,
16813) -> *mut PioOperatingPointUpdate {
16814    unsafe {
16815        entry(error, std::ptr::null_mut(), || {
16816            Ok(PioOperatingPointUpdate::new_raw(
16817                OperatingPointUpdate::SwitchClosed {
16818                    switch: component_clone(switch_id)?,
16819                    closed,
16820                },
16821            ))
16822        })
16823    }
16824}
16825
16826///
16827/// # Safety
16828/// Pointers and handles must satisfy the crate-level safety requirements.
16829#[unsafe(no_mangle)]
16830pub unsafe extern "C" fn pio_network_update_set_branch_thermal_rating(
16831    branch: *const PioComponentId,
16832    terminal: *const c_char,
16833    terminal_len: usize,
16834    rating: *const PioApparentPower,
16835    error: *mut *mut PioError,
16836) -> *mut PioNetworkUpdate {
16837    unsafe {
16838        entry(error, std::ptr::null_mut(), || {
16839            Ok(PioNetworkUpdate::new_raw(
16840                NetworkUpdate::BranchThermalRating {
16841                    branch: component_clone(branch)?,
16842                    terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
16843                    rating: apparent_power_copy(rating)?,
16844                },
16845            ))
16846        })
16847    }
16848}
16849
16850///
16851/// # Safety
16852/// Pointers and handles must satisfy the crate-level safety requirements.
16853#[unsafe(no_mangle)]
16854pub unsafe extern "C" fn pio_calculation_update_from_operating_point(
16855    update: *const PioOperatingPointUpdate,
16856    error: *mut *mut PioError,
16857) -> *mut PioCalculationUpdate {
16858    unsafe {
16859        entry(error, std::ptr::null_mut(), || {
16860            let update = PioOperatingPointUpdate::get(update)
16861                .ok_or_else(|| {
16862                    boundary_error(
16863                        &codes::BIND_CAPI_NULL_HANDLE,
16864                        "PioOperatingPointUpdate must not be NULL",
16865                    )
16866                })?
16867                .clone();
16868            Ok(PioCalculationUpdate::new_raw(
16869                CalculationUpdate::OperatingPoint(update),
16870            ))
16871        })
16872    }
16873}
16874
16875///
16876/// # Safety
16877/// Pointers and handles must satisfy the crate-level safety requirements.
16878#[unsafe(no_mangle)]
16879pub unsafe extern "C" fn pio_calculation_update_from_network(
16880    update: *const PioNetworkUpdate,
16881    error: *mut *mut PioError,
16882) -> *mut PioCalculationUpdate {
16883    unsafe {
16884        entry(error, std::ptr::null_mut(), || {
16885            let update = PioNetworkUpdate::get(update)
16886                .ok_or_else(|| {
16887                    boundary_error(
16888                        &codes::BIND_CAPI_NULL_HANDLE,
16889                        "PioNetworkUpdate must not be NULL",
16890                    )
16891                })?
16892                .clone();
16893            Ok(PioCalculationUpdate::new_raw(CalculationUpdate::Network(
16894                update,
16895            )))
16896        })
16897    }
16898}
16899
16900///
16901/// # Safety
16902/// Pointers and handles must satisfy the crate-level safety requirements.
16903/// The input handle must remain live and immutable throughout this call.
16904#[unsafe(no_mangle)]
16905pub unsafe extern "C" fn pio_operating_point_update_retain(
16906    update: *const PioOperatingPointUpdate,
16907) -> *mut PioOperatingPointUpdate {
16908    unsafe { PioOperatingPointUpdate::retain_raw(update) }
16909}
16910
16911///
16912/// # Safety
16913/// Pointers and handles must satisfy the crate-level safety requirements.
16914/// A non-null handle must be owned and unused by concurrent calls.
16915#[unsafe(no_mangle)]
16916pub unsafe extern "C" fn pio_operating_point_update_release(update: *mut PioOperatingPointUpdate) {
16917    unsafe { PioOperatingPointUpdate::release_raw(update) };
16918}
16919
16920///
16921/// # Safety
16922/// Pointers and handles must satisfy the crate-level safety requirements.
16923/// The input handle must remain live and immutable throughout this call.
16924#[unsafe(no_mangle)]
16925pub unsafe extern "C" fn pio_network_update_retain(
16926    update: *const PioNetworkUpdate,
16927) -> *mut PioNetworkUpdate {
16928    unsafe { PioNetworkUpdate::retain_raw(update) }
16929}
16930
16931///
16932/// # Safety
16933/// Pointers and handles must satisfy the crate-level safety requirements.
16934/// A non-null handle must be owned and unused by concurrent calls.
16935#[unsafe(no_mangle)]
16936pub unsafe extern "C" fn pio_network_update_release(update: *mut PioNetworkUpdate) {
16937    unsafe { PioNetworkUpdate::release_raw(update) };
16938}
16939
16940///
16941/// # Safety
16942/// Pointers and handles must satisfy the crate-level safety requirements.
16943/// The input handle must remain live and immutable throughout this call.
16944#[unsafe(no_mangle)]
16945pub unsafe extern "C" fn pio_calculation_update_retain(
16946    update: *const PioCalculationUpdate,
16947) -> *mut PioCalculationUpdate {
16948    unsafe { PioCalculationUpdate::retain_raw(update) }
16949}
16950
16951///
16952/// # Safety
16953/// Pointers and handles must satisfy the crate-level safety requirements.
16954/// A non-null handle must be owned and unused by concurrent calls.
16955#[unsafe(no_mangle)]
16956pub unsafe extern "C" fn pio_calculation_update_release(update: *mut PioCalculationUpdate) {
16957    unsafe { PioCalculationUpdate::release_raw(update) };
16958}
16959
16960fn append_update_report(output: &mut UpdateReportInner, report: powerio_prob::UpdateReport) {
16961    output.connectivity_changed |= report.connectivity_changed();
16962    output.changes.extend_from_slice(report.changes());
16963}
16964
16965fn apply_bus_load_to_typed_module<T>(
16966    module: &mut powerio::PioModule<PioValue>,
16967    bus: powerio_tx::BusId,
16968    total: ActivePower,
16969    allocation: LoadAllocation,
16970    narrow: impl FnOnce(PioValue) -> Result<T, PioValue>,
16971    wrap: impl FnOnce(T) -> PioValue,
16972) -> Result<UpdateReportInner, powerio_core::Error>
16973where
16974    T: powerio_prob::BalancedCalculationInstance,
16975{
16976    let mut typed = module.clone().__try_map_value(narrow).map_err(|value| {
16977        powerio_core::Error::new(
16978            &codes::REQUEST_CAPI_TYPE_MISMATCH,
16979            format!(
16980                "{} does not accept aggregate bus active demand",
16981                value.value().type_name()
16982            ),
16983        )
16984    })?;
16985    let report = apply_bus_load_active_power(&mut typed, bus, total, allocation)?;
16986    *module = typed.map_value(wrap);
16987    let mut output = UpdateReportInner {
16988        changes: Vec::new(),
16989        connectivity_changed: false,
16990    };
16991    append_update_report(&mut output, report);
16992    Ok(output)
16993}
16994
16995// Recoverable narrowing returns the original dynamic value on a type mismatch,
16996// preserving the module records without serializing or cloning the value again.
16997#[allow(clippy::result_large_err)]
16998fn apply_dynamic_bus_load_active_power(
16999    module: &mut powerio::PioModule<PioValue>,
17000    bus: powerio_tx::BusId,
17001    total: ActivePower,
17002    allocation: LoadAllocation,
17003) -> Result<UpdateReportInner, powerio_core::Error> {
17004    match &module.value() {
17005        PioValue::DcPfInstance(_) => apply_bus_load_to_typed_module(
17006            module,
17007            bus,
17008            total,
17009            allocation,
17010            |value| match value {
17011                PioValue::DcPfInstance(instance) => Ok(instance),
17012                other => Err(other),
17013            },
17014            PioValue::DcPfInstance,
17015        ),
17016        PioValue::AcPfInstance(_) => apply_bus_load_to_typed_module(
17017            module,
17018            bus,
17019            total,
17020            allocation,
17021            |value| match value {
17022                PioValue::AcPfInstance(instance) => Ok(instance),
17023                other => Err(other),
17024            },
17025            PioValue::AcPfInstance,
17026        ),
17027        PioValue::DcOpfInstance(_) => apply_bus_load_to_typed_module(
17028            module,
17029            bus,
17030            total,
17031            allocation,
17032            |value| match value {
17033                PioValue::DcOpfInstance(instance) => Ok(instance),
17034                other => Err(other),
17035            },
17036            PioValue::DcOpfInstance,
17037        ),
17038        PioValue::AcOpfInstance(_) => apply_bus_load_to_typed_module(
17039            module,
17040            bus,
17041            total,
17042            allocation,
17043            |value| match value {
17044                PioValue::AcOpfInstance(instance) => Ok(instance),
17045                other => Err(other),
17046            },
17047            PioValue::AcOpfInstance,
17048        ),
17049        value => Err(powerio_core::Error::new(
17050            &codes::REQUEST_CAPI_TYPE_MISMATCH,
17051            format!(
17052                "{} does not accept aggregate bus active demand",
17053                value.type_name()
17054            ),
17055        )),
17056    }
17057}
17058
17059fn parse_load_allocation(value: &str) -> Result<LoadAllocation, *mut PioError> {
17060    match value {
17061        "equal" => Ok(LoadAllocation::Equal),
17062        "proportional_to_current_active_power" => {
17063            Ok(LoadAllocation::ProportionalToCurrentActivePower)
17064        }
17065        other => Err(boundary_error(
17066            &codes::REQUEST_CAPI_ALLOCATION_UNKNOWN,
17067            format!("unknown load allocation rule '{other}'"),
17068        )),
17069    }
17070}
17071
17072fn separated_updates(
17073    updates: &[CalculationUpdate],
17074) -> Result<(Vec<OperatingPointUpdate>, Vec<NetworkUpdate>), powerio_core::Error> {
17075    let mut operating = Vec::new();
17076    let mut network = Vec::new();
17077    for update in updates {
17078        match update {
17079            CalculationUpdate::OperatingPoint(update) => operating.push(update.clone()),
17080            CalculationUpdate::Network(update) => network.push(update.clone()),
17081            _ => {
17082                return Err(powerio_core::Error::new(
17083                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
17084                    "unsupported calculation update",
17085                ));
17086            }
17087        }
17088    }
17089    Ok((operating, network))
17090}
17091
17092fn reject_network_updates(
17093    updates: &[CalculationUpdate],
17094) -> Result<Vec<OperatingPointUpdate>, powerio_core::Error> {
17095    updates
17096        .iter()
17097        .map(|update| match update {
17098            CalculationUpdate::OperatingPoint(update) => Ok(update.clone()),
17099            CalculationUpdate::Network(_) => Err(powerio_core::Error::new(
17100                &codes::REQUEST_CAPI_TYPE_MISMATCH,
17101                "a network update cannot be applied to an operating point",
17102            )),
17103            _ => Err(powerio_core::Error::new(
17104                &codes::REQUEST_CAPI_TYPE_MISMATCH,
17105                "unsupported calculation update",
17106            )),
17107        })
17108        .collect()
17109}
17110
17111fn apply_dynamic_updates(
17112    value: &mut PioValue,
17113    updates: &[CalculationUpdate],
17114) -> Result<UpdateReportInner, powerio_core::Error> {
17115    let mut output = UpdateReportInner {
17116        changes: Vec::new(),
17117        connectivity_changed: false,
17118    };
17119    match value {
17120        PioValue::BalancedNetwork(network) => {
17121            let (operating, physical) = separated_updates(updates)?;
17122            append_update_report(&mut output, apply_updates(network, &operating)?);
17123            append_update_report(&mut output, apply_updates(network, &physical)?);
17124        }
17125        PioValue::MulticonductorNetwork(network) => {
17126            let (operating, physical) = separated_updates(updates)?;
17127            append_update_report(&mut output, apply_updates(network, &operating)?);
17128            append_update_report(&mut output, apply_updates(network, &physical)?);
17129        }
17130        PioValue::BalancedOperatingPoint(point) => {
17131            let operating = reject_network_updates(updates)?;
17132            append_update_report(&mut output, apply_updates(point, &operating)?);
17133        }
17134        PioValue::MulticonductorOperatingPoint(point) => {
17135            let operating = reject_network_updates(updates)?;
17136            append_update_report(&mut output, apply_updates(point, &operating)?);
17137        }
17138        PioValue::DcPfInstance(instance) => {
17139            append_update_report(&mut output, apply_updates(instance, updates)?);
17140        }
17141        PioValue::AcPfInstance(instance) => {
17142            append_update_report(&mut output, apply_updates(instance, updates)?);
17143        }
17144        PioValue::DcOpfInstance(instance) => {
17145            append_update_report(&mut output, apply_updates(instance, updates)?);
17146        }
17147        PioValue::AcOpfInstance(instance) => {
17148            append_update_report(&mut output, apply_updates(instance, updates)?);
17149        }
17150        PioValue::McAcPfInstance(instance) => {
17151            append_update_report(&mut output, apply_updates(instance, updates)?);
17152        }
17153        PioValue::McAcOpfInstance(instance) => {
17154            append_update_report(&mut output, apply_updates(instance, updates)?);
17155        }
17156        _ => {
17157            return Err(powerio_core::Error::new(
17158                &codes::REQUEST_CAPI_TYPE_MISMATCH,
17159                format!("{} does not accept calculation updates", value.type_name()),
17160            ));
17161        }
17162    }
17163    Ok(output)
17164}
17165
17166fn update_history_id(
17167    module: &powerio::PioModule<PioValue>,
17168) -> Result<HistoryId, powerio_core::Error> {
17169    let mut suffix = 1usize;
17170    loop {
17171        let value = if suffix == 1 {
17172            "apply-updates".to_owned()
17173        } else {
17174            format!("apply-updates-{suffix}")
17175        };
17176        if module
17177            .history()
17178            .iter()
17179            .all(|entry| entry.id().as_str() != value)
17180        {
17181            return HistoryId::new(value);
17182        }
17183        suffix += 1;
17184    }
17185}
17186
17187fn apply_module_updates(
17188    module: &mut powerio::PioModule<PioValue>,
17189    updates: &[CalculationUpdate],
17190) -> Result<UpdateReportInner, powerio_core::Error> {
17191    let history_id = update_history_id(module)?;
17192    let mut edit = module.stage_edit();
17193    let report = apply_dynamic_updates(edit.value_mut(), updates)?;
17194    if report.changes.is_empty() {
17195        return Ok(report);
17196    }
17197
17198    let mut parameters = std::collections::BTreeMap::new();
17199    parameters.insert(
17200        "updates".to_owned(),
17201        serde_json::to_value(updates).map_err(|failure| {
17202            powerio_core::Error::new(
17203                &codes::EMIT_CAPI_SERIALIZE_FAILED,
17204                format!("cannot record applied updates: {failure}"),
17205            )
17206        })?,
17207    );
17208    parameters.insert(
17209        "changes".to_owned(),
17210        serde_json::to_value(&report.changes).map_err(|failure| {
17211            powerio_core::Error::new(
17212                &codes::EMIT_CAPI_SERIALIZE_FAILED,
17213                format!("cannot record update changes: {failure}"),
17214            )
17215        })?,
17216    );
17217    parameters.insert(
17218        "connectivity_changed".to_owned(),
17219        serde_json::Value::Bool(report.connectivity_changed),
17220    );
17221    let history = HistoryEntry::new(history_id, HistoryKind::Edit, "apply_updates")?
17222        .with_parameters(parameters)?;
17223    edit.commit(Producer::new("powerio-capi", powerio::VERSION)?, history)?;
17224    Ok(report)
17225}
17226
17227unsafe fn module_make_mut<'a>(
17228    module: *mut PioModule,
17229) -> Result<&'a mut ModuleInner, *mut PioError> {
17230    let handle = unsafe { module.cast::<HandleBox<ModuleInner>>().as_mut() }.ok_or_else(|| {
17231        boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
17232    })?;
17233    Ok(Arc::make_mut(&mut handle.inner))
17234}
17235
17236/// Apply a complete typed update batch atomically.
17237///
17238/// Owner rooted handles obtained before the call (values, networks, collection
17239/// entries, artifacts) keep the pre-update module alive. Plain view structs read
17240/// from this module handle (`PioStringView`, `PioModuleSourceView`, history and
17241/// source map views) are invalidated by a successful call and must be read
17242/// again. The caller must hold exclusive access to `module` for the duration of
17243/// the call: no concurrent call of any kind on this handle, including retain.
17244///
17245/// # Safety
17246/// Pointers and handles must satisfy the crate-level safety requirements.
17247/// The module handle requires exclusive access, including exclusion of retain.
17248#[unsafe(no_mangle)]
17249pub unsafe extern "C" fn pio_apply_updates(
17250    module: *mut PioModule,
17251    updates: *const *const PioCalculationUpdate,
17252    updates_len: usize,
17253    error: *mut *mut PioError,
17254) -> *mut PioUpdateReport {
17255    unsafe {
17256        entry(error, std::ptr::null_mut(), || {
17257            if updates.is_null() && updates_len != 0 {
17258                return Err(boundary_error(
17259                    &codes::BIND_CAPI_NULL_ARGUMENT,
17260                    "updates is NULL with a nonzero length",
17261                ));
17262            }
17263            let handles = if updates_len == 0 {
17264                &[]
17265            } else {
17266                std::slice::from_raw_parts(updates, updates_len)
17267            };
17268            let mut values = Vec::with_capacity(handles.len());
17269            for (index, handle) in handles.iter().copied().enumerate() {
17270                let update = PioCalculationUpdate::get(handle).ok_or_else(|| {
17271                    boundary_error(
17272                        &codes::BIND_CAPI_NULL_HANDLE,
17273                        format!("updates[{index}] must not be NULL"),
17274                    )
17275                })?;
17276                values.push(update.clone());
17277            }
17278            let module = module_make_mut(module)?;
17279            apply_module_updates(&mut module.module, &values)
17280                .map(PioUpdateReport::new_raw)
17281                .map_err(|failure| error_from_core(&failure))
17282        })
17283    }
17284}
17285
17286/// Replace aggregate active demand at one bus using the named allocation rule.
17287///
17288/// The same view invalidation and exclusivity rules as `pio_apply_updates`
17289/// apply.
17290///
17291/// # Safety
17292/// Pointers and handles must satisfy the crate-level safety requirements.
17293/// The module handle requires exclusive access, including exclusion of retain.
17294#[unsafe(no_mangle)]
17295pub unsafe extern "C" fn pio_apply_bus_load_active_power(
17296    module: *mut PioModule,
17297    bus_id: usize,
17298    power: *const PioActivePower,
17299    allocation: *const c_char,
17300    allocation_len: usize,
17301    error: *mut *mut PioError,
17302) -> *mut PioUpdateReport {
17303    unsafe {
17304        entry(error, std::ptr::null_mut(), || {
17305            let total = active_power_copy(power)?;
17306            let allocation = required_str(allocation, allocation_len, "allocation")?;
17307            let allocation = parse_load_allocation(allocation)?;
17308            let module = module_make_mut(module)?;
17309            apply_dynamic_bus_load_active_power(
17310                &mut module.module,
17311                powerio_tx::BusId::new(bus_id),
17312                total,
17313                allocation,
17314            )
17315            .map(PioUpdateReport::new_raw)
17316            .map_err(|failure| error_from_core(&failure))
17317        })
17318    }
17319}
17320
17321///
17322/// # Safety
17323/// Pointers and handles must satisfy the crate-level safety requirements.
17324#[unsafe(no_mangle)]
17325pub unsafe extern "C" fn pio_update_report_len(report: *const PioUpdateReport) -> usize {
17326    unsafe { PioUpdateReport::get(report) }.map_or(0, |report| report.changes.len())
17327}
17328
17329///
17330/// # Safety
17331/// Pointers and handles must satisfy the crate-level safety requirements.
17332#[unsafe(no_mangle)]
17333pub unsafe extern "C" fn pio_update_report_connectivity_changed(
17334    report: *const PioUpdateReport,
17335) -> bool {
17336    unsafe { PioUpdateReport::get(report) }.is_some_and(|report| report.connectivity_changed)
17337}
17338
17339///
17340/// # Safety
17341/// Pointers and handles must satisfy the crate-level safety requirements.
17342#[unsafe(no_mangle)]
17343pub unsafe extern "C" fn pio_update_report_change(
17344    report: *const PioUpdateReport,
17345    index: usize,
17346    error: *mut *mut PioError,
17347) -> *mut PioUpdateChange {
17348    unsafe {
17349        entry(error, std::ptr::null_mut(), || {
17350            let owner = PioUpdateReport::arc(report).ok_or_else(|| {
17351                boundary_error(
17352                    &codes::BIND_CAPI_NULL_HANDLE,
17353                    "PioUpdateReport must not be NULL",
17354                )
17355            })?;
17356            if index >= owner.changes.len() {
17357                return Err(boundary_error(
17358                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
17359                    format!("update change index {index} is out of range"),
17360                ));
17361            }
17362            Ok(PioUpdateChange::new_raw(UpdateChangeInner { owner, index }))
17363        })
17364    }
17365}
17366
17367///
17368/// # Safety
17369/// Pointers and handles must satisfy the crate-level safety requirements.
17370#[unsafe(no_mangle)]
17371pub unsafe extern "C" fn pio_update_change_component_id(
17372    change: *const PioUpdateChange,
17373) -> *mut PioComponentId {
17374    unsafe { PioUpdateChange::get(change) }
17375        .and_then(UpdateChangeInner::change)
17376        .map_or(std::ptr::null_mut(), |change| {
17377            PioComponentId::new_raw(change.component_id().clone())
17378        })
17379}
17380
17381fn updated_field_name(field: UpdatedField) -> &'static str {
17382    match field {
17383        UpdatedField::LoadActivePower => "load_active_power",
17384        UpdatedField::LoadReactivePower => "load_reactive_power",
17385        UpdatedField::GeneratorActivePower => "generator_active_power",
17386        UpdatedField::GeneratorReactivePower => "generator_reactive_power",
17387        UpdatedField::GeneratorVoltageMagnitude => "generator_voltage_magnitude",
17388        UpdatedField::GeneratorInService => "generator_in_service",
17389        UpdatedField::BranchThermalRating => "branch_thermal_rating",
17390        UpdatedField::BranchInService => "branch_in_service",
17391        UpdatedField::TransformerTapRatio => "transformer_tap_ratio",
17392        UpdatedField::TransformerPhaseShift => "transformer_phase_shift",
17393        UpdatedField::SwitchClosed => "switch_closed",
17394        _ => "unknown",
17395    }
17396}
17397
17398///
17399/// # Safety
17400/// Pointers and handles must satisfy the crate-level safety requirements.
17401#[unsafe(no_mangle)]
17402pub unsafe extern "C" fn pio_update_change_field(change: *const PioUpdateChange) -> PioStringView {
17403    unsafe { PioUpdateChange::get(change) }
17404        .and_then(UpdateChangeInner::change)
17405        .map_or(PioStringView::EMPTY, |change| {
17406            PioStringView::new(updated_field_name(change.field()))
17407        })
17408}
17409
17410///
17411/// # Safety
17412/// Pointers and handles must satisfy the crate-level safety requirements.
17413#[unsafe(no_mangle)]
17414pub unsafe extern "C" fn pio_update_change_terminal(
17415    change: *const PioUpdateChange,
17416) -> PioStringView {
17417    unsafe { PioUpdateChange::get(change) }
17418        .and_then(UpdateChangeInner::change)
17419        .and_then(UpdateChange::terminal)
17420        .map_or(PioStringView::EMPTY, PioStringView::new)
17421}
17422
17423///
17424/// # Safety
17425/// Pointers and handles must satisfy the crate-level safety requirements.
17426/// The input handle must remain live and immutable throughout this call.
17427#[unsafe(no_mangle)]
17428pub unsafe extern "C" fn pio_update_report_retain(
17429    report: *const PioUpdateReport,
17430) -> *mut PioUpdateReport {
17431    unsafe { PioUpdateReport::retain_raw(report) }
17432}
17433
17434///
17435/// # Safety
17436/// Pointers and handles must satisfy the crate-level safety requirements.
17437/// A non-null handle must be owned and unused by concurrent calls.
17438#[unsafe(no_mangle)]
17439pub unsafe extern "C" fn pio_update_report_release(report: *mut PioUpdateReport) {
17440    unsafe { PioUpdateReport::release_raw(report) };
17441}
17442
17443///
17444/// # Safety
17445/// Pointers and handles must satisfy the crate-level safety requirements.
17446/// The input handle must remain live and immutable throughout this call.
17447#[unsafe(no_mangle)]
17448pub unsafe extern "C" fn pio_update_change_retain(
17449    change: *const PioUpdateChange,
17450) -> *mut PioUpdateChange {
17451    unsafe { PioUpdateChange::retain_raw(change) }
17452}
17453
17454///
17455/// # Safety
17456/// Pointers and handles must satisfy the crate-level safety requirements.
17457/// A non-null handle must be owned and unused by concurrent calls.
17458#[unsafe(no_mangle)]
17459pub unsafe extern "C" fn pio_update_change_release(change: *mut PioUpdateChange) {
17460    unsafe { PioUpdateChange::release_raw(change) };
17461}
17462
17463// ---- emission --------------------------------------------------------------
17464
17465struct ArtifactRecord {
17466    name: String,
17467    bytes: Option<Vec<u8>>,
17468}
17469
17470struct EmitResultInner {
17471    layout: &'static str,
17472    fidelity: &'static str,
17473    artifacts: Vec<ArtifactRecord>,
17474    diagnostics: Arc<DiagnosticsInner>,
17475}
17476
17477struct ArtifactInner {
17478    owner: Arc<EmitResultInner>,
17479    index: usize,
17480}
17481
17482impl ArtifactInner {
17483    fn artifact(&self) -> Option<&ArtifactRecord> {
17484        self.owner.artifacts.get(self.index)
17485    }
17486}
17487
17488opaque_handle!(
17489    /// Completed artifact inventory and emission diagnostics.
17490    PioEmitResult,
17491    EmitResultInner
17492);
17493opaque_handle!(
17494    /// Owner-rooted emitted artifact.
17495    PioArtifact,
17496    ArtifactInner
17497);
17498
17499fn emit_result_handle(result: EmitResult) -> Result<*mut PioEmitResult, *mut PioError> {
17500    let layout = match result.layout() {
17501        powerio::OutputLayout::File => "file",
17502        powerio::OutputLayout::Directory => "directory",
17503    };
17504    let fidelity = match result.fidelity() {
17505        powerio::Fidelity::ExactSameFormat => "exact_same_format",
17506        powerio::Fidelity::Canonical => "canonical",
17507    };
17508    let diagnostics = Arc::new(DiagnosticsInner {
17509        owner: DiagnosticsOwner::Owned(result.diagnostics().to_vec()),
17510    });
17511    let artifacts = match result.into_output() {
17512        EmittedOutput::Memory { artifacts } => artifacts
17513            .into_iter()
17514            .map(|artifact| {
17515                let name = artifact.name().as_str().to_owned();
17516                ArtifactRecord {
17517                    name,
17518                    bytes: Some(artifact.into_bytes()),
17519                }
17520            })
17521            .collect(),
17522        EmittedOutput::Path { artifacts, .. } => artifacts
17523            .into_iter()
17524            .map(|path| ArtifactRecord {
17525                name: path.to_string_lossy().into_owned(),
17526                bytes: None,
17527            })
17528            .collect(),
17529        _ => {
17530            return Err(boundary_error(
17531                &codes::REQUEST_CAPI_TYPE_MISMATCH,
17532                "unsupported emitted output",
17533            ));
17534        }
17535    };
17536    Ok(PioEmitResult::new_raw(EmitResultInner {
17537        layout,
17538        fidelity,
17539        artifacts,
17540        diagnostics,
17541    }))
17542}
17543
17544unsafe fn run_output_operation(
17545    module: *const PioModule,
17546    destination: *const PioDestination,
17547    error: *mut *mut PioError,
17548    operation: impl FnOnce(
17549        &powerio::PioModule<PioValue>,
17550        Destination,
17551    ) -> Result<EmitResult, *mut PioError>,
17552) -> *mut PioEmitResult {
17553    unsafe {
17554        entry(error, std::ptr::null_mut(), || {
17555            let module = PioModule::get(module).ok_or_else(|| {
17556                boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
17557            })?;
17558            let destination = PioDestination::get(destination).ok_or_else(|| {
17559                boundary_error(
17560                    &codes::BIND_CAPI_NULL_HANDLE,
17561                    "PioDestination must not be NULL",
17562                )
17563            })?;
17564            let destination = destination
17565                .build()
17566                .map_err(|failure| error_from_core(&failure))?;
17567            operation(&module.module, destination).and_then(emit_result_handle)
17568        })
17569    }
17570}
17571
17572/// Emit one module as a grid exchange format.
17573///
17574/// # Safety
17575/// Pointers and handles must satisfy the crate-level safety requirements.
17576#[unsafe(no_mangle)]
17577pub unsafe extern "C" fn pio_emit(
17578    module: *const PioModule,
17579    format: *const c_char,
17580    format_len: usize,
17581    destination: *const PioDestination,
17582    error: *mut *mut PioError,
17583) -> *mut PioEmitResult {
17584    unsafe {
17585        run_output_operation(module, destination, error, |module, destination| {
17586            let format = required_str(format, format_len, "format")?;
17587            powerio::emit(module, format, destination).map_err(|failure| error_from_core(&failure))
17588        })
17589    }
17590}
17591
17592/// Serialize one module as PowerIO IR.
17593///
17594/// # Safety
17595/// Pointers and handles must satisfy the crate-level safety requirements.
17596#[unsafe(no_mangle)]
17597pub unsafe extern "C" fn pio_module_serialize(
17598    module: *const PioModule,
17599    destination: *const PioDestination,
17600    error: *mut *mut PioError,
17601) -> *mut PioEmitResult {
17602    unsafe {
17603        run_output_operation(module, destination, error, |module, destination| {
17604            powerio::serialize(module, destination).map_err(|failure| error_from_core(&failure))
17605        })
17606    }
17607}
17608
17609///
17610/// # Safety
17611/// Pointers and handles must satisfy the crate-level safety requirements.
17612#[unsafe(no_mangle)]
17613pub unsafe extern "C" fn pio_emit_result_layout(result: *const PioEmitResult) -> PioStringView {
17614    unsafe { PioEmitResult::get(result) }.map_or(PioStringView::EMPTY, |result| {
17615        PioStringView::new(result.layout)
17616    })
17617}
17618
17619///
17620/// # Safety
17621/// Pointers and handles must satisfy the crate-level safety requirements.
17622#[unsafe(no_mangle)]
17623pub unsafe extern "C" fn pio_emit_result_fidelity(result: *const PioEmitResult) -> PioStringView {
17624    unsafe { PioEmitResult::get(result) }.map_or(PioStringView::EMPTY, |result| {
17625        PioStringView::new(result.fidelity)
17626    })
17627}
17628
17629///
17630/// # Safety
17631/// Pointers and handles must satisfy the crate-level safety requirements.
17632#[unsafe(no_mangle)]
17633pub unsafe extern "C" fn pio_emit_result_artifact_count(result: *const PioEmitResult) -> usize {
17634    unsafe { PioEmitResult::get(result) }.map_or(0, |result| result.artifacts.len())
17635}
17636
17637///
17638/// # Safety
17639/// Pointers and handles must satisfy the crate-level safety requirements.
17640#[unsafe(no_mangle)]
17641pub unsafe extern "C" fn pio_emit_result_artifact(
17642    result: *const PioEmitResult,
17643    index: usize,
17644    error: *mut *mut PioError,
17645) -> *mut PioArtifact {
17646    unsafe {
17647        entry(error, std::ptr::null_mut(), || {
17648            let owner = PioEmitResult::arc(result).ok_or_else(|| {
17649                boundary_error(
17650                    &codes::BIND_CAPI_NULL_HANDLE,
17651                    "PioEmitResult must not be NULL",
17652                )
17653            })?;
17654            if index >= owner.artifacts.len() {
17655                return Err(boundary_error(
17656                    &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
17657                    format!("artifact index {index} is out of range"),
17658                ));
17659            }
17660            Ok(PioArtifact::new_raw(ArtifactInner { owner, index }))
17661        })
17662    }
17663}
17664
17665///
17666/// # Safety
17667/// Pointers and handles must satisfy the crate-level safety requirements.
17668#[unsafe(no_mangle)]
17669pub unsafe extern "C" fn pio_emit_result_diagnostics(
17670    result: *const PioEmitResult,
17671) -> *mut PioDiagnostics {
17672    unsafe { PioEmitResult::get(result) }.map_or(std::ptr::null_mut(), |result| {
17673        PioDiagnostics::from_arc(Arc::clone(&result.diagnostics))
17674    })
17675}
17676
17677///
17678/// # Safety
17679/// Pointers and handles must satisfy the crate-level safety requirements.
17680/// The input handle must remain live and immutable throughout this call.
17681#[unsafe(no_mangle)]
17682pub unsafe extern "C" fn pio_emit_result_retain(
17683    result: *const PioEmitResult,
17684) -> *mut PioEmitResult {
17685    unsafe { PioEmitResult::retain_raw(result) }
17686}
17687
17688///
17689/// # Safety
17690/// Pointers and handles must satisfy the crate-level safety requirements.
17691/// A non-null handle must be owned and unused by concurrent calls.
17692#[unsafe(no_mangle)]
17693pub unsafe extern "C" fn pio_emit_result_release(result: *mut PioEmitResult) {
17694    unsafe { PioEmitResult::release_raw(result) };
17695}
17696
17697///
17698/// # Safety
17699/// Pointers and handles must satisfy the crate-level safety requirements.
17700#[unsafe(no_mangle)]
17701pub unsafe extern "C" fn pio_artifact_name(artifact: *const PioArtifact) -> PioStringView {
17702    unsafe { PioArtifact::get(artifact) }
17703        .and_then(ArtifactInner::artifact)
17704        .map_or(PioStringView::EMPTY, |artifact| {
17705            PioStringView::new(&artifact.name)
17706        })
17707}
17708
17709/// Return emitted memory bytes. A path destination has no memory bytes and
17710/// returns an empty view.
17711///
17712/// # Safety
17713/// Pointers and handles must satisfy the crate-level safety requirements.
17714#[unsafe(no_mangle)]
17715pub unsafe extern "C" fn pio_artifact_bytes(artifact: *const PioArtifact) -> PioByteView {
17716    unsafe { PioArtifact::get(artifact) }
17717        .and_then(ArtifactInner::artifact)
17718        .and_then(|artifact| artifact.bytes.as_deref())
17719        .map_or(PioByteView::EMPTY, PioByteView::new)
17720}
17721
17722///
17723/// # Safety
17724/// Pointers and handles must satisfy the crate-level safety requirements.
17725/// The input handle must remain live and immutable throughout this call.
17726#[unsafe(no_mangle)]
17727pub unsafe extern "C" fn pio_artifact_retain(artifact: *const PioArtifact) -> *mut PioArtifact {
17728    unsafe { PioArtifact::retain_raw(artifact) }
17729}
17730
17731///
17732/// # Safety
17733/// Pointers and handles must satisfy the crate-level safety requirements.
17734/// A non-null handle must be owned and unused by concurrent calls.
17735#[unsafe(no_mangle)]
17736pub unsafe extern "C" fn pio_artifact_release(artifact: *mut PioArtifact) {
17737    unsafe { PioArtifact::release_raw(artifact) };
17738}
17739
17740// ---- sparse matrices and vectors ------------------------------------------
17741
17742struct SparseMatrixInner {
17743    rows: usize,
17744    columns: usize,
17745    row_offsets: Vec<usize>,
17746    column_indices: Vec<usize>,
17747    values: Vec<f64>,
17748}
17749
17750impl From<SparseMatrix> for SparseMatrixInner {
17751    fn from(matrix: SparseMatrix) -> Self {
17752        let matrix = matrix.to_csr();
17753        Self {
17754            rows: matrix.rows(),
17755            columns: matrix.cols(),
17756            row_offsets: matrix.indptr().raw_storage().to_vec(),
17757            column_indices: matrix.indices().to_vec(),
17758            values: matrix.data().to_vec(),
17759        }
17760    }
17761}
17762
17763struct VectorInner {
17764    values: Vec<f64>,
17765}
17766
17767opaque_handle!(
17768    /// Immutable CSR sparse matrix.
17769    PioSparseMatrix,
17770    SparseMatrixInner
17771);
17772opaque_handle!(
17773    /// Immutable dense vector.
17774    PioVector,
17775    VectorInner
17776);
17777
17778fn termination_name(termination: &powerio_prob::Termination) -> &'static str {
17779    use powerio_prob::Termination;
17780    match termination {
17781        Termination::Converged => "converged",
17782        Termination::IterationLimit => "iteration_limit",
17783        Termination::Infeasible => "infeasible",
17784        Termination::Unbounded => "unbounded",
17785        Termination::Failed => "failed",
17786        Termination::NotReported => "not_reported",
17787        _ => "not_reported",
17788    }
17789}
17790
17791///
17792/// # Safety
17793/// Pointers and handles must satisfy the crate-level safety requirements.
17794#[unsafe(no_mangle)]
17795pub unsafe extern "C" fn pio_calculation_solution_termination(
17796    solution: *const PioCalculationSolution,
17797) -> PioStringView {
17798    let Some(solution) =
17799        (unsafe { PioCalculationSolution::get(solution) }).and_then(ValueInner::value)
17800    else {
17801        return PioStringView::EMPTY;
17802    };
17803    let termination = match solution {
17804        PioValue::DcPfSolution(solution) => solution.termination(),
17805        PioValue::AcPfSolution(solution) => solution.termination(),
17806        PioValue::DcOpfSolution(solution) => solution.termination(),
17807        PioValue::AcOpfSolution(solution) => solution.termination(),
17808        PioValue::SocwrOpfSolution(solution) => solution.termination(),
17809        PioValue::McAcPfSolution(solution) => solution.termination(),
17810        PioValue::McAcOpfSolution(solution) => solution.termination(),
17811        PioValue::LinDist3FlowOpfSolution(solution) => solution.termination(),
17812        PioValue::AcScucSolution(solution) => solution.termination(),
17813        _ => return PioStringView::EMPTY,
17814    };
17815    PioStringView::new(termination_name(termination))
17816}
17817
17818/// Return an OPF or SCUC objective. SOCWR reports a lower bound through
17819/// pio_socwr_opf_solution_get_objective_lower_bound instead.
17820///
17821/// # Safety
17822/// Pointers and handles must satisfy the crate-level safety requirements.
17823#[unsafe(no_mangle)]
17824pub unsafe extern "C" fn pio_calculation_solution_get_objective(
17825    solution: *const PioCalculationSolution,
17826    out_objective: *mut f64,
17827) -> bool {
17828    if out_objective.is_null() {
17829        return false;
17830    }
17831    // No error slot: a missing objective is an ordinary false. `entry` still
17832    // turns a panic into false instead of aborting the caller.
17833    unsafe {
17834        entry(std::ptr::null_mut(), false, || {
17835            let Some(solution) = PioCalculationSolution::get(solution).and_then(ValueInner::value)
17836            else {
17837                return Ok(false);
17838            };
17839            let objective = match solution {
17840                PioValue::DcOpfSolution(solution) => Some(solution.objective()),
17841                PioValue::AcOpfSolution(solution) => Some(solution.objective()),
17842                PioValue::McAcOpfSolution(solution) => Some(solution.objective()),
17843                PioValue::LinDist3FlowOpfSolution(solution) => Some(solution.objective()),
17844                PioValue::AcScucSolution(solution) => solution.objective(),
17845                _ => None,
17846            };
17847            Ok(match objective {
17848                Some(objective) => {
17849                    *out_objective = objective;
17850                    true
17851                }
17852                None => false,
17853            })
17854        })
17855    }
17856}
17857
17858///
17859/// # Safety
17860/// Pointers and handles must satisfy the crate-level safety requirements.
17861#[unsafe(no_mangle)]
17862pub unsafe extern "C" fn pio_socwr_opf_solution_get_objective_lower_bound(
17863    solution: *const PioCalculationSolution,
17864    out_lower_bound: *mut f64,
17865) -> bool {
17866    if out_lower_bound.is_null() {
17867        return false;
17868    }
17869    // No error slot, as for pio_calculation_solution_get_objective.
17870    unsafe {
17871        entry(std::ptr::null_mut(), false, || {
17872            let Some(PioValue::SocwrOpfSolution(solution)) =
17873                PioCalculationSolution::get(solution).and_then(ValueInner::value)
17874            else {
17875                return Ok(false);
17876            };
17877            *out_lower_bound = solution.objective_lower_bound();
17878            Ok(true)
17879        })
17880    }
17881}
17882
17883fn balanced_branch_identities(network: &BalancedNetwork) -> impl Iterator<Item = &str> {
17884    network
17885        .branches()
17886        .iter()
17887        .map(|branch| branch.uid.as_deref().expect("facade assigns component IDs"))
17888}
17889
17890fn balanced_generator_identities(network: &BalancedNetwork) -> impl Iterator<Item = &str> {
17891    network.generators().iter().map(|generator| {
17892        generator
17893            .uid
17894            .as_deref()
17895            .expect("facade assigns component IDs")
17896    })
17897}
17898
17899fn collect_solution_values(solution: &PioValue, quantity: &str) -> Result<Vec<f64>, *mut PioError> {
17900    let unknown = || {
17901        boundary_error(
17902            &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
17903            format!(
17904                "{} does not define solution quantity '{quantity}'",
17905                solution.type_name()
17906            ),
17907        )
17908    };
17909    let values = match solution {
17910        PioValue::DcPfSolution(solution) => match quantity {
17911            "bus_voltage_angle" => solution.bus_voltage_angles().to_vec(),
17912            "bus_active_injection" => solution.bus_active_injections().to_vec(),
17913            "branch_from_active_flow" => solution.branch_from_active_flows().to_vec(),
17914            "branch_to_active_flow" => solution.branch_to_active_flows().to_vec(),
17915            "generator_active_power" => solution
17916                .generator_dispatch()
17917                .map(|dispatch| dispatch.p_mw.clone())
17918                .ok_or_else(unknown)?,
17919            "generator_reactive_power" => solution
17920                .generator_dispatch()
17921                .map(|dispatch| dispatch.q_mvar.clone())
17922                .ok_or_else(unknown)?,
17923            _ => return Err(unknown()),
17924        },
17925        PioValue::AcPfSolution(solution) => match quantity {
17926            "bus_voltage_magnitude" => solution
17927                .network()
17928                .buses()
17929                .iter()
17930                .map(|bus| {
17931                    solution
17932                        .bus_voltage_magnitude(bus.id)
17933                        .expect("complete solution")
17934                })
17935                .collect(),
17936            "bus_voltage_angle" => solution
17937                .network()
17938                .buses()
17939                .iter()
17940                .map(|bus| {
17941                    solution
17942                        .bus_voltage_angle(bus.id)
17943                        .expect("complete solution")
17944                })
17945                .collect(),
17946            "bus_active_injection" => solution
17947                .network()
17948                .buses()
17949                .iter()
17950                .map(|bus| {
17951                    solution
17952                        .bus_active_injection(bus.id)
17953                        .expect("complete solution")
17954                })
17955                .collect(),
17956            "bus_reactive_injection" => solution
17957                .network()
17958                .buses()
17959                .iter()
17960                .map(|bus| {
17961                    solution
17962                        .bus_reactive_injection(bus.id)
17963                        .expect("complete solution")
17964                })
17965                .collect(),
17966            "branch_from_active_flow" => balanced_branch_identities(solution.network())
17967                .map(|id| {
17968                    solution
17969                        .branch_from_active_flow(id)
17970                        .expect("complete solution")
17971                })
17972                .collect(),
17973            "branch_from_reactive_flow" => balanced_branch_identities(solution.network())
17974                .map(|id| {
17975                    solution
17976                        .branch_from_reactive_flow(id)
17977                        .expect("complete solution")
17978                })
17979                .collect(),
17980            "branch_to_active_flow" => balanced_branch_identities(solution.network())
17981                .map(|id| {
17982                    solution
17983                        .branch_to_active_flow(id)
17984                        .expect("complete solution")
17985                })
17986                .collect(),
17987            "branch_to_reactive_flow" => balanced_branch_identities(solution.network())
17988                .map(|id| {
17989                    solution
17990                        .branch_to_reactive_flow(id)
17991                        .expect("complete solution")
17992                })
17993                .collect(),
17994            "generator_active_power" => solution
17995                .generator_dispatch()
17996                .map(|dispatch| dispatch.p_mw.clone())
17997                .ok_or_else(unknown)?,
17998            "generator_reactive_power" => solution
17999                .generator_dispatch()
18000                .map(|dispatch| dispatch.q_mvar.clone())
18001                .ok_or_else(unknown)?,
18002            _ => return Err(unknown()),
18003        },
18004        PioValue::DcOpfSolution(solution) => match quantity {
18005            "bus_voltage_angle" => solution
18006                .network()
18007                .buses()
18008                .iter()
18009                .map(|bus| {
18010                    solution
18011                        .bus_voltage_angle(bus.id)
18012                        .expect("complete solution")
18013                })
18014                .collect(),
18015            "bus_active_injection" => solution
18016                .network()
18017                .buses()
18018                .iter()
18019                .map(|bus| {
18020                    solution
18021                        .bus_active_injection(bus.id)
18022                        .expect("complete solution")
18023                })
18024                .collect(),
18025            "branch_from_active_flow" => balanced_branch_identities(solution.network())
18026                .map(|id| {
18027                    solution
18028                        .branch_from_active_flow(id)
18029                        .expect("complete solution")
18030                })
18031                .collect(),
18032            "branch_to_active_flow" => balanced_branch_identities(solution.network())
18033                .map(|id| {
18034                    solution
18035                        .branch_to_active_flow(id)
18036                        .expect("complete solution")
18037                })
18038                .collect(),
18039            "generator_active_power" => balanced_generator_identities(solution.network())
18040                .map(|id| {
18041                    solution
18042                        .generator_active_power(id)
18043                        .expect("complete solution")
18044                })
18045                .collect(),
18046            "bus_active_power_marginal" => solution
18047                .bus_active_power_marginals()
18048                .map(<[f64]>::to_vec)
18049                .ok_or_else(unknown)?,
18050            "branch_from_limit_multiplier" => solution
18051                .branch_from_limit_multipliers()
18052                .map(<[f64]>::to_vec)
18053                .ok_or_else(unknown)?,
18054            "branch_to_limit_multiplier" => solution
18055                .branch_to_limit_multipliers()
18056                .map(<[f64]>::to_vec)
18057                .ok_or_else(unknown)?,
18058            _ => return Err(unknown()),
18059        },
18060        PioValue::AcOpfSolution(solution) => match quantity {
18061            "bus_voltage_magnitude" => solution.bus_voltage_magnitudes().to_vec(),
18062            "bus_voltage_angle" => solution.bus_voltage_angles().to_vec(),
18063            "bus_active_injection" => solution.bus_active_injections().to_vec(),
18064            "bus_reactive_injection" => solution.bus_reactive_injections().to_vec(),
18065            "branch_from_active_flow" => solution.branch_from_active_flows().to_vec(),
18066            "branch_from_reactive_flow" => solution.branch_from_reactive_flows().to_vec(),
18067            "branch_to_active_flow" => solution.branch_to_active_flows().to_vec(),
18068            "branch_to_reactive_flow" => solution.branch_to_reactive_flows().to_vec(),
18069            "generator_active_power" => solution.generator_active_powers().to_vec(),
18070            "generator_reactive_power" => solution.generator_reactive_powers().to_vec(),
18071            "bus_active_power_marginal" => solution
18072                .bus_active_power_marginals()
18073                .map(<[f64]>::to_vec)
18074                .ok_or_else(unknown)?,
18075            "bus_reactive_power_marginal" => solution
18076                .bus_reactive_power_marginals()
18077                .map(<[f64]>::to_vec)
18078                .ok_or_else(unknown)?,
18079            "branch_from_limit_multiplier" => solution
18080                .branch_from_limit_multipliers()
18081                .map(<[f64]>::to_vec)
18082                .ok_or_else(unknown)?,
18083            "branch_to_limit_multiplier" => solution
18084                .branch_to_limit_multipliers()
18085                .map(<[f64]>::to_vec)
18086                .ok_or_else(unknown)?,
18087            _ => return Err(unknown()),
18088        },
18089        PioValue::SocwrOpfSolution(solution) => {
18090            let values = solution.values();
18091            match quantity {
18092                "bus_voltage_magnitude_squared" => values.bus_voltage_magnitude_squared.clone(),
18093                "branch_voltage_product_real" => values.branch_voltage_product_real.clone(),
18094                "branch_voltage_product_imaginary" => {
18095                    values.branch_voltage_product_imaginary.clone()
18096                }
18097                "generator_active_power" => values.generator_active_power.clone(),
18098                "generator_reactive_power" => values.generator_reactive_power.clone(),
18099                "branch_from_active_power" => values.branch_from_active_power.clone(),
18100                "branch_from_reactive_power" => values.branch_from_reactive_power.clone(),
18101                "branch_to_active_power" => values.branch_to_active_power.clone(),
18102                "branch_to_reactive_power" => values.branch_to_reactive_power.clone(),
18103                _ => return Err(unknown()),
18104            }
18105        }
18106        PioValue::McAcPfSolution(solution) => match quantity {
18107            "terminal_voltage_magnitude" => solution
18108                .network()
18109                .buses()
18110                .iter()
18111                .flat_map(|bus| {
18112                    bus.terminals.iter().map(move |terminal| {
18113                        solution
18114                            .terminal_voltage_magnitude(&bus.id, terminal)
18115                            .expect("complete solution")
18116                    })
18117                })
18118                .collect(),
18119            "terminal_voltage_angle" => solution
18120                .network()
18121                .buses()
18122                .iter()
18123                .flat_map(|bus| {
18124                    bus.terminals.iter().map(move |terminal| {
18125                        solution
18126                            .terminal_voltage_angle(&bus.id, terminal)
18127                            .expect("complete solution")
18128                    })
18129                })
18130                .collect(),
18131            "source_active_injection" => solution.source_active_injections().to_vec(),
18132            _ => return Err(unknown()),
18133        },
18134        PioValue::LinDist3FlowOpfSolution(solution) => match quantity {
18135            "terminal_voltage_magnitude_squared" => {
18136                solution.values().terminal_voltage_magnitude_squared.clone()
18137            }
18138            "line_active_power" => solution.values().line_active_power.clone(),
18139            "line_reactive_power" => solution.values().line_reactive_power.clone(),
18140            "generator_active_power" => solution.values().generator_active_power.clone(),
18141            "generator_reactive_power" => solution.values().generator_reactive_power.clone(),
18142            "source_active_power" => solution.values().source_active_power.clone(),
18143            "source_reactive_power" => solution.values().source_reactive_power.clone(),
18144            _ => return Err(unknown()),
18145        },
18146        PioValue::McAcOpfSolution(solution) => match quantity {
18147            "terminal_voltage_magnitude" => solution
18148                .network()
18149                .buses()
18150                .iter()
18151                .flat_map(|bus| {
18152                    bus.terminals.iter().map(move |terminal| {
18153                        solution
18154                            .terminal_voltage_magnitude(&bus.id, terminal)
18155                            .expect("complete solution")
18156                    })
18157                })
18158                .collect(),
18159            "terminal_voltage_angle" => solution
18160                .network()
18161                .buses()
18162                .iter()
18163                .flat_map(|bus| {
18164                    bus.terminals.iter().map(move |terminal| {
18165                        solution
18166                            .terminal_voltage_angle(&bus.id, terminal)
18167                            .expect("complete solution")
18168                    })
18169                })
18170                .collect(),
18171            "source_active_injection" => solution.source_active_injections().to_vec(),
18172            "generator_active_power" => solution.generator_active_powers().to_vec(),
18173            _ => return Err(unknown()),
18174        },
18175        _ => {
18176            return Err(boundary_error(
18177                &codes::REQUEST_CAPI_TYPE_MISMATCH,
18178                "the handle does not refer to a supported calculation solution",
18179            ));
18180        }
18181    };
18182    Ok(values)
18183}
18184
18185/// Copy one named solution quantity into an independently owned vector.
18186///
18187/// # Safety
18188/// Pointers and handles must satisfy the crate-level safety requirements.
18189#[unsafe(no_mangle)]
18190pub unsafe extern "C" fn pio_calculation_solution_get_values(
18191    solution: *const PioCalculationSolution,
18192    quantity: *const c_char,
18193    quantity_len: usize,
18194    error: *mut *mut PioError,
18195) -> *mut PioVector {
18196    unsafe {
18197        entry(error, std::ptr::null_mut(), || {
18198            let quantity = required_str(quantity, quantity_len, "quantity")?;
18199            let solution = PioCalculationSolution::get(solution)
18200                .and_then(ValueInner::value)
18201                .ok_or_else(|| {
18202                    boundary_error(
18203                        &codes::BIND_CAPI_NULL_HANDLE,
18204                        "PioCalculationSolution must not be NULL",
18205                    )
18206                })?;
18207            collect_solution_values(solution, quantity)
18208                .map(|values| PioVector::new_raw(VectorInner { values }))
18209        })
18210    }
18211}
18212
18213///
18214/// # Safety
18215/// Pointers and handles must satisfy the crate-level safety requirements.
18216#[unsafe(no_mangle)]
18217pub unsafe extern "C" fn pio_ac_scuc_solution_time_count(
18218    solution: *const PioCalculationSolution,
18219) -> usize {
18220    let Some(PioValue::AcScucSolution(solution)) =
18221        (unsafe { PioCalculationSolution::get(solution) }).and_then(ValueInner::value)
18222    else {
18223        return 0;
18224    };
18225    solution.instance().inputs().interval_durations.len()
18226}
18227
18228/// Copy one AC SCUC output row for one time position into an owned vector.
18229///
18230/// # Safety
18231/// Pointers and handles must satisfy the crate-level safety requirements.
18232#[unsafe(no_mangle)]
18233pub unsafe extern "C" fn pio_ac_scuc_solution_get_values_at(
18234    solution: *const PioCalculationSolution,
18235    quantity: *const c_char,
18236    quantity_len: usize,
18237    time_index: usize,
18238    error: *mut *mut PioError,
18239) -> *mut PioVector {
18240    unsafe {
18241        entry(error, std::ptr::null_mut(), || {
18242            let quantity = required_str(quantity, quantity_len, "quantity")?;
18243            let Some(PioValue::AcScucSolution(solution)) =
18244                PioCalculationSolution::get(solution).and_then(ValueInner::value)
18245            else {
18246                return Err(boundary_error(
18247                    &codes::REQUEST_CAPI_TYPE_MISMATCH,
18248                    "the handle does not refer to powerio.AcScucSolution",
18249                ));
18250            };
18251            let network = solution.network_outputs();
18252            let device = solution.device_outputs();
18253            macro_rules! row {
18254                ($rows:expr, $convert:expr) => {{
18255                    $rows
18256                        .get(time_index)
18257                        .ok_or_else(|| {
18258                            boundary_error(
18259                                &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
18260                                format!("AC SCUC time index {time_index} is out of range"),
18261                            )
18262                        })?
18263                        .iter()
18264                        .copied()
18265                        .map($convert)
18266                        .collect::<Vec<f64>>()
18267                }};
18268            }
18269            let values = match quantity {
18270                "bus_voltage_magnitude" => row!(&network.bus_vm, |value| value),
18271                "bus_voltage_angle" => row!(&network.bus_va, |value| value),
18272                "shunt_step" => row!(&network.shunt_step, |value| value as f64),
18273                "ac_line_on_status" => row!(&network.ac_line_on_status, f64::from),
18274                "transformer_tap_ratio" => row!(&network.transformer_tm, |value| value),
18275                "transformer_phase_shift" => row!(&network.transformer_ta, |value| value),
18276                "transformer_on_status" => row!(&network.transformer_on_status, f64::from),
18277                "dc_line_from_active_power" => row!(&network.dc_line_pdc_fr, |value| value),
18278                "dc_line_from_reactive_power" => row!(&network.dc_line_qdc_fr, |value| value),
18279                "dc_line_to_reactive_power" => row!(&network.dc_line_qdc_to, |value| value),
18280                "device_on_status" => row!(&device.on_status, f64::from),
18281                "device_startup_status" => row!(&device.startup_status, f64::from),
18282                "device_shutdown_status" => row!(&device.shutdown_status, f64::from),
18283                "device_active_power" => row!(&device.p_on, |value| value),
18284                "device_reactive_power" => row!(&device.q, |value| value),
18285                "regulation_reserve_up" => row!(&device.p_reg_res_up, |value| value),
18286                "regulation_reserve_down" => row!(&device.p_reg_res_down, |value| value),
18287                "synchronized_reserve" => row!(&device.p_syn_res, |value| value),
18288                "nonsynchronized_reserve" => row!(&device.p_nsyn_res, |value| value),
18289                "ramping_reserve_up_online" => row!(&device.p_ramp_res_up_online, |value| value),
18290                "ramping_reserve_up_offline" => {
18291                    row!(&device.p_ramp_res_up_offline, |value| value)
18292                }
18293                "ramping_reserve_down_online" => {
18294                    row!(&device.p_ramp_res_down_online, |value| value)
18295                }
18296                "ramping_reserve_down_offline" => {
18297                    row!(&device.p_ramp_res_down_offline, |value| value)
18298                }
18299                "reactive_reserve_up" => row!(&device.q_res_up, |value| value),
18300                "reactive_reserve_down" => row!(&device.q_res_down, |value| value),
18301                _ => {
18302                    return Err(boundary_error(
18303                        &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
18304                        format!("unknown AC SCUC solution quantity '{quantity}'"),
18305                    ));
18306                }
18307            };
18308            Ok(PioVector::new_raw(VectorInner { values }))
18309        })
18310    }
18311}
18312
18313fn formula(name: Option<&str>) -> Result<BranchSusceptanceFormula, *mut PioError> {
18314    match name.unwrap_or("series_susceptance") {
18315        "series_susceptance" => Ok(BranchSusceptanceFormula::SeriesSusceptance),
18316        "tap_adjusted_reactance" => Ok(BranchSusceptanceFormula::TapAdjustedReactance),
18317        "reactance_only" => Ok(BranchSusceptanceFormula::ReactanceOnly),
18318        other => Err(boundary_error(
18319            &codes::REQUEST_CAPI_UNKNOWN_FORMULA,
18320            format!("unknown branch susceptance formula '{other}'"),
18321        )),
18322    }
18323}
18324
18325unsafe fn dc_operators(
18326    network: *const PioBalancedNetwork,
18327    formula_name: *const c_char,
18328    formula_name_len: usize,
18329) -> Result<DcOperators, *mut PioError> {
18330    let network = unsafe { PioBalancedNetwork::get(network) }
18331        .and_then(BalancedNetworkInner::network)
18332        .ok_or_else(|| {
18333            boundary_error(
18334                &codes::BIND_CAPI_NULL_HANDLE,
18335                "PioBalancedNetwork must not be NULL",
18336            )
18337        })?;
18338    let formula_name = unsafe { optional_str(formula_name, formula_name_len, "formula") }?;
18339    let formula = formula(formula_name)?;
18340    let instance = DcPfInstance::from_network(network.clone())
18341        .map_err(|failure| error_from_core(&failure))?
18342        .with_branch_susceptance_formula(formula);
18343    DcOperators::build(&instance).map_err(|failure| error_from_core(&failure))
18344}
18345
18346// ---- DC operators handle -----------------------------------------------------
18347
18348struct DcOperatorsInner {
18349    operators: DcOperators,
18350    bus_ids: Vec<usize>,
18351}
18352
18353opaque_handle!(
18354    /// DC operators built once from a balanced network: the incidence,
18355    /// susceptance, flow, and injection calculations share one bus axis and
18356    /// one branch axis, and the handle names both axes.
18357    PioDcOperators,
18358    DcOperatorsInner
18359);
18360
18361/// Build the DC operators of a balanced network once. `formula` selects the
18362/// branch susceptance as in `pio_calc_incidence_matrix`; NULL means
18363/// `series_susceptance`. With `skip_zero_impedance` false a zero impedance
18364/// branch fails the build with `BUILD.OPERATOR.ZERO_IMPEDANCE`; with it true
18365/// the branch is dropped from the branch axis and reported by
18366/// `pio_dc_operators_skipped_branch_rows`. The bus axis is every bus in table
18367/// order; the branch axis is every in service, non self loop branch in table
18368/// order, followed by three winding transformer windings, less any skipped
18369/// branch. Release with `pio_dc_operators_release`.
18370///
18371/// # Safety
18372/// Pointers and handles must satisfy the crate-level safety requirements.
18373#[unsafe(no_mangle)]
18374pub unsafe extern "C" fn pio_calc_dc_operators(
18375    network: *const PioBalancedNetwork,
18376    formula: *const c_char,
18377    formula_len: usize,
18378    skip_zero_impedance: bool,
18379    error: *mut *mut PioError,
18380) -> *mut PioDcOperators {
18381    unsafe {
18382        entry(error, std::ptr::null_mut(), || {
18383            let network = PioBalancedNetwork::get(network)
18384                .and_then(BalancedNetworkInner::network)
18385                .ok_or_else(|| {
18386                    boundary_error(
18387                        &codes::BIND_CAPI_NULL_HANDLE,
18388                        "PioBalancedNetwork must not be NULL",
18389                    )
18390                })?;
18391            let formula_name = optional_str(formula, formula_len, "formula")?;
18392            let formula = self::formula(formula_name)?;
18393            let instance = DcPfInstance::from_network(network.clone())
18394                .map_err(|failure| error_from_core(&failure))?
18395                .with_branch_susceptance_formula(formula);
18396            let options = DcOperatorOptions::new().with_skip_zero_impedance(skip_zero_impedance);
18397            let operators = DcOperators::build_with(&instance, &options)
18398                .map_err(|failure| error_from_core(&failure))?;
18399            let bus_ids = operators.bus_ids().iter().map(|bus| bus.0).collect();
18400            Ok(PioDcOperators::new_raw(DcOperatorsInner {
18401                operators,
18402                bus_ids,
18403            }))
18404        })
18405    }
18406}
18407
18408/// The length of the bus axis.
18409///
18410/// # Safety
18411/// Pointers and handles must satisfy the crate-level safety requirements.
18412#[unsafe(no_mangle)]
18413pub unsafe extern "C" fn pio_dc_operators_n_buses(operators: *const PioDcOperators) -> usize {
18414    unsafe { PioDcOperators::get(operators) }.map_or(0, |inner| inner.bus_ids.len())
18415}
18416
18417/// The length of the branch axis.
18418///
18419/// # Safety
18420/// Pointers and handles must satisfy the crate-level safety requirements.
18421#[unsafe(no_mangle)]
18422pub unsafe extern "C" fn pio_dc_operators_n_branches(operators: *const PioDcOperators) -> usize {
18423    unsafe { PioDcOperators::get(operators) }
18424        .map_or(0, |inner| inner.operators.branch_identities().len())
18425}
18426
18427/// Bus axis row to source bus id. Borrowed from the handle.
18428///
18429/// # Safety
18430/// Pointers and handles must satisfy the crate-level safety requirements.
18431#[unsafe(no_mangle)]
18432pub unsafe extern "C" fn pio_dc_operators_bus_ids(operators: *const PioDcOperators) -> PioSizeView {
18433    unsafe { PioDcOperators::get(operators) }
18434        .map_or(PioSizeView::EMPTY, |inner| PioSizeView::new(&inner.bus_ids))
18435}
18436
18437/// Branch axis row to the analysis branch row it represents: the position in
18438/// the network's branch table, with three winding transformer windings after
18439/// the branches. Borrowed from the handle.
18440///
18441/// # Safety
18442/// Pointers and handles must satisfy the crate-level safety requirements.
18443#[unsafe(no_mangle)]
18444pub unsafe extern "C" fn pio_dc_operators_branch_rows(
18445    operators: *const PioDcOperators,
18446) -> PioSizeView {
18447    unsafe { PioDcOperators::get(operators) }.map_or(PioSizeView::EMPTY, |inner| {
18448        PioSizeView::new(inner.operators.branch_rows())
18449    })
18450}
18451
18452/// Analysis branch rows dropped under `skip_zero_impedance`, in table order.
18453/// Borrowed from the handle.
18454///
18455/// # Safety
18456/// Pointers and handles must satisfy the crate-level safety requirements.
18457#[unsafe(no_mangle)]
18458pub unsafe extern "C" fn pio_dc_operators_skipped_branch_rows(
18459    operators: *const PioDcOperators,
18460) -> PioSizeView {
18461    unsafe { PioDcOperators::get(operators) }.map_or(PioSizeView::EMPTY, |inner| {
18462        PioSizeView::new(inner.operators.skipped_branch_rows())
18463    })
18464}
18465
18466/// The stable identity of branch axis row `index`: the source uid when one
18467/// exists, else `branches:<row>`. An index past the branch axis returns an
18468/// empty view. Borrowed from the handle.
18469///
18470/// # Safety
18471/// Pointers and handles must satisfy the crate-level safety requirements.
18472#[unsafe(no_mangle)]
18473pub unsafe extern "C" fn pio_dc_operators_branch_identity(
18474    operators: *const PioDcOperators,
18475    index: usize,
18476) -> PioStringView {
18477    unsafe { PioDcOperators::get(operators) }
18478        .and_then(|inner| inner.operators.branch_identities().get(index))
18479        .map_or(PioStringView::EMPTY, |identity| {
18480            PioStringView::new(identity)
18481        })
18482}
18483
18484unsafe fn dc_operators_matrix(
18485    operators: *const PioDcOperators,
18486    error: *mut *mut PioError,
18487    calculation: impl FnOnce(&DcOperators) -> SparseMatrix,
18488) -> *mut PioSparseMatrix {
18489    unsafe {
18490        entry(error, std::ptr::null_mut(), || {
18491            let inner = PioDcOperators::get(operators).ok_or_else(|| {
18492                boundary_error(
18493                    &codes::BIND_CAPI_NULL_HANDLE,
18494                    "PioDcOperators must not be NULL",
18495                )
18496            })?;
18497            Ok(PioSparseMatrix::new_raw(SparseMatrixInner::from(
18498                calculation(&inner.operators),
18499            )))
18500        })
18501    }
18502}
18503
18504unsafe fn dc_operators_vector(
18505    operators: *const PioDcOperators,
18506    error: *mut *mut PioError,
18507    calculation: impl FnOnce(&DcOperators) -> Vec<f64>,
18508) -> *mut PioVector {
18509    unsafe {
18510        entry(error, std::ptr::null_mut(), || {
18511            let inner = PioDcOperators::get(operators).ok_or_else(|| {
18512                boundary_error(
18513                    &codes::BIND_CAPI_NULL_HANDLE,
18514                    "PioDcOperators must not be NULL",
18515                )
18516            })?;
18517            Ok(PioVector::new_raw(VectorInner {
18518                values: calculation(&inner.operators),
18519            }))
18520        })
18521    }
18522}
18523
18524unsafe fn dc_operators_vector_from_angles(
18525    operators: *const PioDcOperators,
18526    voltage_angles: *const f64,
18527    voltage_angles_len: usize,
18528    error: *mut *mut PioError,
18529    calculation: impl FnOnce(&DcOperators, &[f64]) -> Result<Vec<f64>, powerio_core::Error>,
18530) -> *mut PioVector {
18531    unsafe {
18532        entry(error, std::ptr::null_mut(), || {
18533            if voltage_angles.is_null() && voltage_angles_len != 0 {
18534                return Err(boundary_error(
18535                    &codes::BIND_CAPI_NULL_ARGUMENT,
18536                    "voltage_angles is NULL with a nonzero length",
18537                ));
18538            }
18539            let angles = if voltage_angles_len == 0 {
18540                &[]
18541            } else {
18542                std::slice::from_raw_parts(voltage_angles, voltage_angles_len)
18543            };
18544            let inner = PioDcOperators::get(operators).ok_or_else(|| {
18545                boundary_error(
18546                    &codes::BIND_CAPI_NULL_HANDLE,
18547                    "PioDcOperators must not be NULL",
18548                )
18549            })?;
18550            calculation(&inner.operators, angles)
18551                .map(|values| PioVector::new_raw(VectorInner { values }))
18552                .map_err(|failure| error_from_core(&failure))
18553        })
18554    }
18555}
18556
18557/// The incidence matrix `A`, branches by buses, over the handle's axes.
18558///
18559/// # Safety
18560/// Pointers and handles must satisfy the crate-level safety requirements.
18561#[unsafe(no_mangle)]
18562pub unsafe extern "C" fn pio_dc_operators_incidence_matrix(
18563    operators: *const PioDcOperators,
18564    error: *mut *mut PioError,
18565) -> *mut PioSparseMatrix {
18566    unsafe { dc_operators_matrix(operators, error, DcOperators::calc_incidence_matrix) }
18567}
18568
18569/// The bus susceptance matrix `B = A' diag(b) A`, buses by buses.
18570///
18571/// # Safety
18572/// Pointers and handles must satisfy the crate-level safety requirements.
18573#[unsafe(no_mangle)]
18574pub unsafe extern "C" fn pio_dc_operators_bus_susceptance_matrix(
18575    operators: *const PioDcOperators,
18576    error: *mut *mut PioError,
18577) -> *mut PioSparseMatrix {
18578    unsafe { dc_operators_matrix(operators, error, DcOperators::calc_bus_susceptance_matrix) }
18579}
18580
18581/// The branch flow matrix `Bf = diag(b) A`, branches by buses.
18582///
18583/// # Safety
18584/// Pointers and handles must satisfy the crate-level safety requirements.
18585#[unsafe(no_mangle)]
18586pub unsafe extern "C" fn pio_dc_operators_branch_flow_matrix(
18587    operators: *const PioDcOperators,
18588    error: *mut *mut PioError,
18589) -> *mut PioSparseMatrix {
18590    unsafe { dc_operators_matrix(operators, error, DcOperators::calc_branch_flow_matrix) }
18591}
18592
18593/// The per branch susceptances `b` over the branch axis.
18594///
18595/// # Safety
18596/// Pointers and handles must satisfy the crate-level safety requirements.
18597#[unsafe(no_mangle)]
18598pub unsafe extern "C" fn pio_dc_operators_branch_susceptances(
18599    operators: *const PioDcOperators,
18600    error: *mut *mut PioError,
18601) -> *mut PioVector {
18602    unsafe {
18603        dc_operators_vector(operators, error, |operators| {
18604            operators.calc_branch_susceptances().to_vec()
18605        })
18606    }
18607}
18608
18609/// The per branch phase shift injection `b .* shift`.
18610///
18611/// # Safety
18612/// Pointers and handles must satisfy the crate-level safety requirements.
18613#[unsafe(no_mangle)]
18614pub unsafe extern "C" fn pio_dc_operators_branch_phase_shift_injection(
18615    operators: *const PioDcOperators,
18616    error: *mut *mut PioError,
18617) -> *mut PioVector {
18618    unsafe {
18619        dc_operators_vector(
18620            operators,
18621            error,
18622            DcOperators::calc_branch_phase_shift_injection,
18623        )
18624    }
18625}
18626
18627/// The per bus phase shift injection `A' (b .* shift)`.
18628///
18629/// # Safety
18630/// Pointers and handles must satisfy the crate-level safety requirements.
18631#[unsafe(no_mangle)]
18632pub unsafe extern "C" fn pio_dc_operators_bus_phase_shift_injection(
18633    operators: *const PioDcOperators,
18634    error: *mut *mut PioError,
18635) -> *mut PioVector {
18636    unsafe {
18637        dc_operators_vector(
18638            operators,
18639            error,
18640            DcOperators::calc_bus_phase_shift_injection,
18641        )
18642    }
18643}
18644
18645/// DC branch flows for bus voltage angles in radians over the bus axis.
18646///
18647/// # Safety
18648/// Pointers and handles must satisfy the crate-level safety requirements.
18649#[unsafe(no_mangle)]
18650pub unsafe extern "C" fn pio_dc_operators_branch_flow_dc(
18651    operators: *const PioDcOperators,
18652    voltage_angles: *const f64,
18653    voltage_angles_len: usize,
18654    error: *mut *mut PioError,
18655) -> *mut PioVector {
18656    unsafe {
18657        dc_operators_vector_from_angles(
18658            operators,
18659            voltage_angles,
18660            voltage_angles_len,
18661            error,
18662            DcOperators::calc_branch_flow_dc,
18663        )
18664    }
18665}
18666
18667/// DC bus injections for bus voltage angles in radians over the bus axis.
18668///
18669/// # Safety
18670/// Pointers and handles must satisfy the crate-level safety requirements.
18671#[unsafe(no_mangle)]
18672pub unsafe extern "C" fn pio_dc_operators_bus_injection_dc(
18673    operators: *const PioDcOperators,
18674    voltage_angles: *const f64,
18675    voltage_angles_len: usize,
18676    error: *mut *mut PioError,
18677) -> *mut PioVector {
18678    unsafe {
18679        dc_operators_vector_from_angles(
18680            operators,
18681            voltage_angles,
18682            voltage_angles_len,
18683            error,
18684            DcOperators::calc_bus_injection_dc,
18685        )
18686    }
18687}
18688
18689///
18690/// # Safety
18691/// Pointers and handles must satisfy the crate-level safety requirements.
18692/// The input handle must remain live and immutable throughout this call.
18693#[unsafe(no_mangle)]
18694pub unsafe extern "C" fn pio_dc_operators_retain(
18695    operators: *const PioDcOperators,
18696) -> *mut PioDcOperators {
18697    unsafe { PioDcOperators::retain_raw(operators) }
18698}
18699
18700///
18701/// # Safety
18702/// Pointers and handles must satisfy the crate-level safety requirements.
18703/// A non-null handle must be owned and unused by concurrent calls.
18704#[unsafe(no_mangle)]
18705pub unsafe extern "C" fn pio_dc_operators_release(operators: *mut PioDcOperators) {
18706    unsafe { PioDcOperators::release_raw(operators) };
18707}
18708
18709unsafe fn dc_matrix(
18710    network: *const PioBalancedNetwork,
18711    formula: *const c_char,
18712    formula_len: usize,
18713    error: *mut *mut PioError,
18714    calculation: impl FnOnce(&DcOperators) -> SparseMatrix,
18715) -> *mut PioSparseMatrix {
18716    unsafe {
18717        entry(error, std::ptr::null_mut(), || {
18718            let operators = dc_operators(network, formula, formula_len)?;
18719            Ok(PioSparseMatrix::new_raw(SparseMatrixInner::from(
18720                calculation(&operators),
18721            )))
18722        })
18723    }
18724}
18725
18726unsafe fn dc_vector(
18727    network: *const PioBalancedNetwork,
18728    formula: *const c_char,
18729    formula_len: usize,
18730    error: *mut *mut PioError,
18731    calculation: impl FnOnce(&DcOperators) -> Vec<f64>,
18732) -> *mut PioVector {
18733    unsafe {
18734        entry(error, std::ptr::null_mut(), || {
18735            let operators = dc_operators(network, formula, formula_len)?;
18736            Ok(PioVector::new_raw(VectorInner {
18737                values: calculation(&operators),
18738            }))
18739        })
18740    }
18741}
18742
18743///
18744/// # Safety
18745/// Pointers and handles must satisfy the crate-level safety requirements.
18746#[unsafe(no_mangle)]
18747pub unsafe extern "C" fn pio_calc_incidence_matrix(
18748    network: *const PioBalancedNetwork,
18749    formula: *const c_char,
18750    formula_len: usize,
18751    error: *mut *mut PioError,
18752) -> *mut PioSparseMatrix {
18753    unsafe {
18754        dc_matrix(network, formula, formula_len, error, |operators| {
18755            operators.calc_incidence_matrix()
18756        })
18757    }
18758}
18759
18760///
18761/// # Safety
18762/// Pointers and handles must satisfy the crate-level safety requirements.
18763#[unsafe(no_mangle)]
18764pub unsafe extern "C" fn pio_calc_bus_susceptance_matrix(
18765    network: *const PioBalancedNetwork,
18766    formula: *const c_char,
18767    formula_len: usize,
18768    error: *mut *mut PioError,
18769) -> *mut PioSparseMatrix {
18770    unsafe {
18771        dc_matrix(network, formula, formula_len, error, |operators| {
18772            operators.calc_bus_susceptance_matrix()
18773        })
18774    }
18775}
18776
18777///
18778/// # Safety
18779/// Pointers and handles must satisfy the crate-level safety requirements.
18780#[unsafe(no_mangle)]
18781pub unsafe extern "C" fn pio_calc_branch_flow_matrix(
18782    network: *const PioBalancedNetwork,
18783    formula: *const c_char,
18784    formula_len: usize,
18785    error: *mut *mut PioError,
18786) -> *mut PioSparseMatrix {
18787    unsafe {
18788        dc_matrix(network, formula, formula_len, error, |operators| {
18789            operators.calc_branch_flow_matrix()
18790        })
18791    }
18792}
18793
18794///
18795/// # Safety
18796/// Pointers and handles must satisfy the crate-level safety requirements.
18797#[unsafe(no_mangle)]
18798pub unsafe extern "C" fn pio_calc_branch_susceptances(
18799    network: *const PioBalancedNetwork,
18800    formula: *const c_char,
18801    formula_len: usize,
18802    error: *mut *mut PioError,
18803) -> *mut PioVector {
18804    unsafe {
18805        dc_vector(network, formula, formula_len, error, |operators| {
18806            operators.calc_branch_susceptances().to_vec()
18807        })
18808    }
18809}
18810
18811///
18812/// # Safety
18813/// Pointers and handles must satisfy the crate-level safety requirements.
18814#[unsafe(no_mangle)]
18815pub unsafe extern "C" fn pio_calc_branch_phase_shift_injection(
18816    network: *const PioBalancedNetwork,
18817    formula: *const c_char,
18818    formula_len: usize,
18819    error: *mut *mut PioError,
18820) -> *mut PioVector {
18821    unsafe {
18822        dc_vector(network, formula, formula_len, error, |operators| {
18823            operators.calc_branch_phase_shift_injection()
18824        })
18825    }
18826}
18827
18828///
18829/// # Safety
18830/// Pointers and handles must satisfy the crate-level safety requirements.
18831#[unsafe(no_mangle)]
18832pub unsafe extern "C" fn pio_calc_bus_phase_shift_injection(
18833    network: *const PioBalancedNetwork,
18834    formula: *const c_char,
18835    formula_len: usize,
18836    error: *mut *mut PioError,
18837) -> *mut PioVector {
18838    unsafe {
18839        dc_vector(network, formula, formula_len, error, |operators| {
18840            operators.calc_bus_phase_shift_injection()
18841        })
18842    }
18843}
18844
18845unsafe fn dc_vector_from_angles(
18846    network: *const PioBalancedNetwork,
18847    formula_name: *const c_char,
18848    formula_name_len: usize,
18849    voltage_angles: *const f64,
18850    voltage_angles_len: usize,
18851    error: *mut *mut PioError,
18852    calculation: impl FnOnce(&DcOperators, &[f64]) -> Result<Vec<f64>, powerio_core::Error>,
18853) -> *mut PioVector {
18854    unsafe {
18855        entry(error, std::ptr::null_mut(), || {
18856            if voltage_angles.is_null() && voltage_angles_len != 0 {
18857                return Err(boundary_error(
18858                    &codes::BIND_CAPI_NULL_ARGUMENT,
18859                    "voltage_angles is NULL with a nonzero length",
18860                ));
18861            }
18862            let angles = if voltage_angles_len == 0 {
18863                &[]
18864            } else {
18865                std::slice::from_raw_parts(voltage_angles, voltage_angles_len)
18866            };
18867            let operators = dc_operators(network, formula_name, formula_name_len)?;
18868            calculation(&operators, angles)
18869                .map(|values| PioVector::new_raw(VectorInner { values }))
18870                .map_err(|failure| error_from_core(&failure))
18871        })
18872    }
18873}
18874
18875///
18876/// # Safety
18877/// Pointers and handles must satisfy the crate-level safety requirements.
18878#[unsafe(no_mangle)]
18879pub unsafe extern "C" fn pio_calc_branch_flow_dc(
18880    network: *const PioBalancedNetwork,
18881    formula: *const c_char,
18882    formula_len: usize,
18883    voltage_angles: *const f64,
18884    voltage_angles_len: usize,
18885    error: *mut *mut PioError,
18886) -> *mut PioVector {
18887    unsafe {
18888        dc_vector_from_angles(
18889            network,
18890            formula,
18891            formula_len,
18892            voltage_angles,
18893            voltage_angles_len,
18894            error,
18895            DcOperators::calc_branch_flow_dc,
18896        )
18897    }
18898}
18899
18900///
18901/// # Safety
18902/// Pointers and handles must satisfy the crate-level safety requirements.
18903#[unsafe(no_mangle)]
18904pub unsafe extern "C" fn pio_calc_bus_injection_dc(
18905    network: *const PioBalancedNetwork,
18906    formula: *const c_char,
18907    formula_len: usize,
18908    voltage_angles: *const f64,
18909    voltage_angles_len: usize,
18910    error: *mut *mut PioError,
18911) -> *mut PioVector {
18912    unsafe {
18913        dc_vector_from_angles(
18914            network,
18915            formula,
18916            formula_len,
18917            voltage_angles,
18918            voltage_angles_len,
18919            error,
18920            DcOperators::calc_bus_injection_dc,
18921        )
18922    }
18923}
18924
18925///
18926/// # Safety
18927/// Pointers and handles must satisfy the crate-level safety requirements.
18928#[unsafe(no_mangle)]
18929pub unsafe extern "C" fn pio_sparse_matrix_rows(matrix: *const PioSparseMatrix) -> usize {
18930    unsafe { PioSparseMatrix::get(matrix) }.map_or(0, |matrix| matrix.rows)
18931}
18932
18933///
18934/// # Safety
18935/// Pointers and handles must satisfy the crate-level safety requirements.
18936#[unsafe(no_mangle)]
18937pub unsafe extern "C" fn pio_sparse_matrix_columns(matrix: *const PioSparseMatrix) -> usize {
18938    unsafe { PioSparseMatrix::get(matrix) }.map_or(0, |matrix| matrix.columns)
18939}
18940
18941///
18942/// # Safety
18943/// Pointers and handles must satisfy the crate-level safety requirements.
18944#[unsafe(no_mangle)]
18945pub unsafe extern "C" fn pio_sparse_matrix_row_offsets(
18946    matrix: *const PioSparseMatrix,
18947) -> PioSizeView {
18948    unsafe { PioSparseMatrix::get(matrix) }.map_or(PioSizeView::EMPTY, |matrix| {
18949        PioSizeView::new(&matrix.row_offsets)
18950    })
18951}
18952
18953///
18954/// # Safety
18955/// Pointers and handles must satisfy the crate-level safety requirements.
18956#[unsafe(no_mangle)]
18957pub unsafe extern "C" fn pio_sparse_matrix_column_indices(
18958    matrix: *const PioSparseMatrix,
18959) -> PioSizeView {
18960    unsafe { PioSparseMatrix::get(matrix) }.map_or(PioSizeView::EMPTY, |matrix| {
18961        PioSizeView::new(&matrix.column_indices)
18962    })
18963}
18964
18965///
18966/// # Safety
18967/// Pointers and handles must satisfy the crate-level safety requirements.
18968#[unsafe(no_mangle)]
18969pub unsafe extern "C" fn pio_sparse_matrix_values(matrix: *const PioSparseMatrix) -> PioF64View {
18970    unsafe { PioSparseMatrix::get(matrix) }
18971        .map_or(PioF64View::EMPTY, |matrix| PioF64View::new(&matrix.values))
18972}
18973
18974///
18975/// # Safety
18976/// Pointers and handles must satisfy the crate-level safety requirements.
18977/// The input handle must remain live and immutable throughout this call.
18978#[unsafe(no_mangle)]
18979pub unsafe extern "C" fn pio_sparse_matrix_retain(
18980    matrix: *const PioSparseMatrix,
18981) -> *mut PioSparseMatrix {
18982    unsafe { PioSparseMatrix::retain_raw(matrix) }
18983}
18984
18985///
18986/// # Safety
18987/// Pointers and handles must satisfy the crate-level safety requirements.
18988/// A non-null handle must be owned and unused by concurrent calls.
18989#[unsafe(no_mangle)]
18990pub unsafe extern "C" fn pio_sparse_matrix_release(matrix: *mut PioSparseMatrix) {
18991    unsafe { PioSparseMatrix::release_raw(matrix) };
18992}
18993
18994///
18995/// # Safety
18996/// Pointers and handles must satisfy the crate-level safety requirements.
18997#[unsafe(no_mangle)]
18998pub unsafe extern "C" fn pio_vector_values(vector: *const PioVector) -> PioF64View {
18999    unsafe { PioVector::get(vector) }
19000        .map_or(PioF64View::EMPTY, |vector| PioF64View::new(&vector.values))
19001}
19002
19003///
19004/// # Safety
19005/// Pointers and handles must satisfy the crate-level safety requirements.
19006/// The input handle must remain live and immutable throughout this call.
19007#[unsafe(no_mangle)]
19008pub unsafe extern "C" fn pio_vector_retain(vector: *const PioVector) -> *mut PioVector {
19009    unsafe { PioVector::retain_raw(vector) }
19010}
19011
19012///
19013/// # Safety
19014/// Pointers and handles must satisfy the crate-level safety requirements.
19015/// A non-null handle must be owned and unused by concurrent calls.
19016#[unsafe(no_mangle)]
19017pub unsafe extern "C" fn pio_vector_release(vector: *mut PioVector) {
19018    unsafe { PioVector::release_raw(vector) };
19019}
19020
19021// ---- schema report ---------------------------------------------------------
19022
19023struct StringInner {
19024    text: String,
19025}
19026
19027opaque_handle!(
19028    /// Owned UTF-8 text returned by report functions.
19029    PioString,
19030    StringInner
19031);
19032
19033/// Return version information for this ABI and the PowerIO IR serializer/deserializer.
19034///
19035/// # Safety
19036/// Pointers and handles must satisfy the crate-level safety requirements.
19037#[unsafe(no_mangle)]
19038pub unsafe extern "C" fn pio_schema_report(error: *mut *mut PioError) -> *mut PioString {
19039    unsafe {
19040        entry(error, std::ptr::null_mut(), || {
19041            serde_json::to_string(&serde_json::json!({
19042                "powerio_version": powerio::VERSION,
19043                "abi": PIO_ABI_VERSION,
19044                "powerio_ir": {
19045                    "schema": powerio::IR_SCHEMA_NAME,
19046                    "version": powerio::IR_VERSION
19047                },
19048                "bmopf_schema": powerio_dist::BMOPF_SCHEMA_VERSION,
19049                // ABI 7 exports one fixed symbol set. Matrices, the
19050                // multiconductor model, and calculation types are always
19051                // present; only GridFM Parquet support is a build option.
19052                "features": {
19053                    "matrix": true,
19054                    "gridfm": cfg!(feature = "gridfm"),
19055                    "dist": true,
19056                    "prob": true
19057                },
19058                "foreign_schemas": {
19059                    "bmopf": powerio_dist::BMOPF_SCHEMA_VERSION
19060                },
19061                "error_categories": powerio_core::ErrorCategory::TOKENS,
19062                "diagnostic_namespaces": powerio_core::DiagnosticStage::NAMESPACES,
19063                "json_classes": powerio::JSON_CLASSES
19064            }))
19065            .map(|text| PioString::new_raw(StringInner { text }))
19066            .map_err(|failure| {
19067                boundary_error(
19068                    &codes::EMIT_CAPI_SERIALIZE_FAILED,
19069                    format!("cannot serialize the schema report: {failure}"),
19070                )
19071            })
19072        })
19073    }
19074}
19075
19076///
19077/// # Safety
19078/// Pointers and handles must satisfy the crate-level safety requirements.
19079#[unsafe(no_mangle)]
19080pub unsafe extern "C" fn pio_string_view(string: *const PioString) -> PioStringView {
19081    unsafe { PioString::get(string) }.map_or(PioStringView::EMPTY, |string| {
19082        PioStringView::new(&string.text)
19083    })
19084}
19085
19086///
19087/// # Safety
19088/// Pointers and handles must satisfy the crate-level safety requirements.
19089/// The input handle must remain live and immutable throughout this call.
19090#[unsafe(no_mangle)]
19091pub unsafe extern "C" fn pio_string_retain(string: *const PioString) -> *mut PioString {
19092    unsafe { PioString::retain_raw(string) }
19093}
19094
19095///
19096/// # Safety
19097/// Pointers and handles must satisfy the crate-level safety requirements.
19098/// A non-null handle must be owned and unused by concurrent calls.
19099#[unsafe(no_mangle)]
19100pub unsafe extern "C" fn pio_string_release(string: *mut PioString) {
19101    unsafe { PioString::release_raw(string) };
19102}
19103
19104#[cfg(test)]
19105mod tests {
19106    use super::*;
19107
19108    unsafe fn view_text(view: PioStringView) -> String {
19109        if view.data.is_null() {
19110            return String::new();
19111        }
19112        String::from_utf8_lossy(unsafe {
19113            std::slice::from_raw_parts(view.data.cast::<u8>(), view.len)
19114        })
19115        .into_owned()
19116    }
19117
19118    unsafe fn parse_case9() -> *mut PioModule {
19119        let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/data/case9.m");
19120        let path = path.to_string_lossy();
19121        let mut error = std::ptr::null_mut();
19122        let source = unsafe { pio_source_open(path.as_ptr().cast(), path.len(), &mut error) };
19123        assert!(!source.is_null(), "{}", unsafe { error_text(error) });
19124        let module = unsafe { pio_parse(source, std::ptr::null(), 0, &mut error) };
19125        unsafe { pio_source_release(source) };
19126        assert!(!module.is_null(), "{}", unsafe { error_text(error) });
19127        module
19128    }
19129
19130    fn parse_xiidm_text(text: &str) -> *mut PioModule {
19131        unsafe {
19132            let name = b"case.xiidm";
19133            let format = b"xiidm";
19134            let mut error = std::ptr::null_mut();
19135            let source = pio_source_from_memory(
19136                name.as_ptr().cast(),
19137                name.len(),
19138                text.as_ptr(),
19139                text.len(),
19140                &mut error,
19141            );
19142            assert!(!source.is_null(), "{}", error_text(error));
19143            let module = pio_parse(source, format.as_ptr().cast(), format.len(), &mut error);
19144            pio_source_release(source);
19145            assert!(!module.is_null(), "{}", error_text(error));
19146            module
19147        }
19148    }
19149
19150    unsafe fn parse_bmopf() -> *mut PioModule {
19151        let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
19152            .join("../tests/data/dist/bmopf/example_ieee13.json");
19153        let path = path.to_string_lossy();
19154        let mut error = std::ptr::null_mut();
19155        let source = unsafe { pio_source_open(path.as_ptr().cast(), path.len(), &mut error) };
19156        assert!(!source.is_null(), "{}", unsafe { error_text(error) });
19157        let module = unsafe { pio_parse(source, std::ptr::null(), 0, &mut error) };
19158        unsafe { pio_source_release(source) };
19159        assert!(!module.is_null(), "{}", unsafe { error_text(error) });
19160        module
19161    }
19162
19163    unsafe fn error_text(error: *const PioError) -> String {
19164        let view = unsafe { pio_error_message(error) };
19165        if view.data.is_null() {
19166            return "missing PioError".to_owned();
19167        }
19168        String::from_utf8_lossy(unsafe {
19169            std::slice::from_raw_parts(view.data.cast::<u8>(), view.len)
19170        })
19171        .into_owned()
19172    }
19173
19174    unsafe fn case9_network() -> BalancedNetwork {
19175        let module = unsafe { parse_case9() };
19176        let network = match &unsafe { PioModule::get(module) }.unwrap().module.value() {
19177            PioValue::BalancedNetwork(network) => network.clone(),
19178            value => panic!("expected balanced network, got {}", value.type_name()),
19179        };
19180        unsafe { pio_module_release(module) };
19181        network
19182    }
19183
19184    fn module_with_complete_records() -> *mut PioModule {
19185        use std::collections::BTreeMap;
19186
19187        let mut module = powerio::PioModule::new(PioValue::BalancedNetwork(BalancedNetwork::new(
19188            "metadata", 100.0,
19189        )))
19190        .with_producer(Producer::new("record-test", "1.2.3").unwrap());
19191        let source_id = powerio_core::SourceId::new("source-1").unwrap();
19192        let source =
19193            powerio_core::SourceDescriptor::new(source_id.clone(), "record-test.xiidm", 128)
19194                .unwrap()
19195                .with_format(powerio_core::FormatId::new("xiidm").unwrap())
19196                .with_digest(
19197                    powerio_core::Digest::sha256(
19198                        "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
19199                    )
19200                    .unwrap(),
19201                );
19202        module.add_source_descriptor(source).unwrap();
19203        module
19204            .add_source_map_entry(
19205                powerio_core::SourceMapEntry::new(
19206                    "/value/buses/0",
19207                    powerio_core::SourceRelation::Exact,
19208                    vec![powerio_core::SourceSpan::new(source_id, 4, 12).unwrap()],
19209                )
19210                .unwrap(),
19211            )
19212            .unwrap();
19213        let parameters = BTreeMap::from([(
19214            "settings".to_owned(),
19215            serde_json::json!({
19216                "items": [null, true, "named", 18446744073709551615u64, 1.25]
19217            }),
19218        )]);
19219        let history = HistoryEntry::new(
19220            HistoryId::new("history-1").unwrap(),
19221            HistoryKind::Transform,
19222            "normalize",
19223        )
19224        .unwrap()
19225        .with_input_type("powerio.BalancedNetwork")
19226        .unwrap()
19227        .with_output_type("powerio.BalancedNetwork")
19228        .unwrap()
19229        .with_parameters(parameters)
19230        .unwrap()
19231        .with_assumption("base power is known")
19232        .unwrap()
19233        .with_loss("source token spelling")
19234        .unwrap();
19235        module.add_history_entry(history).unwrap();
19236        module
19237            .insert_extension("org.example.record", serde_json::json!({"enabled": true}))
19238            .unwrap();
19239        module_handle(module)
19240    }
19241
19242    fn multiconductor_network() -> powerio_dist::MulticonductorNetwork {
19243        let mut network = powerio_dist::MulticonductorNetwork::named("capi-solution-instance");
19244        network.buses_mut().push(powerio_dist::DistBus::new(
19245            "source",
19246            vec!["1".into(), "2".into(), "3".into()],
19247        ));
19248        network.sources_mut().push(powerio_dist::VoltageSource::new(
19249            "source",
19250            "source",
19251            vec!["1".into(), "2".into(), "3".into()],
19252            vec![240.0, 240.0, 240.0],
19253            vec![0.0, -2.094, 2.094],
19254        ));
19255        network
19256    }
19257
19258    fn complete_multiconductor_network() -> powerio_dist::MulticonductorNetwork {
19259        let mut network = powerio_dist::MulticonductorNetwork::named("");
19260        *network.source_format_mut() = Some(powerio_dist::DistSourceFormat::Dss);
19261        *network.geo_mut() = Some(powerio_dist::DistGeoMeta {
19262            space: powerio_dist::CoordinateSpace::Diagram {
19263                canvas: Some(powerio_dist::DistCanvas {
19264                    width: Some(640.0),
19265                    height: None,
19266                    units: Some(String::new()),
19267                }),
19268            },
19269            kind: Some(powerio_dist::DistCoordsKind::Manual),
19270        });
19271
19272        let mut bus = powerio_dist::DistBus::new("bus", vec!["1".into(), "2".into()]);
19273        bus.grounded.push("2".into());
19274        bus.v_min = Some(0.0);
19275        bus.vpn_min = Some(Vec::new());
19276        bus.vpp_max = Some(vec![240.0, 240.0]);
19277        bus.location = Some(powerio_dist::DistLocation {
19278            x: 10.0,
19279            y: 20.0,
19280            kind: None,
19281        });
19282        network.buses_mut().push(bus);
19283
19284        let mut line_code = powerio_dist::DistLineCode::new(
19285            "code",
19286            vec![vec![1.0, 2.0], vec![3.0]],
19287            vec![vec![4.0]],
19288        );
19289        line_code.g_from = vec![vec![5.0]];
19290        line_code.b_from = vec![vec![6.0]];
19291        line_code.g_to = vec![vec![7.0]];
19292        line_code.b_to = vec![vec![8.0]];
19293        line_code.i_max = Some(Vec::new());
19294        line_code.s_max = Some(vec![900.0]);
19295        line_code.source = Some(String::new());
19296        network.line_codes_mut().push(line_code);
19297
19298        let mut line = powerio_dist::DistLine::new(
19299            "line",
19300            "bus",
19301            "bus",
19302            vec!["1".into()],
19303            vec!["2".into()],
19304            "code",
19305            12.0,
19306        );
19307        line.route = Some(Vec::new());
19308        line.i_max = Some(vec![10.0]);
19309        network.lines_mut().push(line);
19310
19311        let mut switch = powerio_dist::DistSwitch::new(
19312            "switch",
19313            "bus",
19314            "bus",
19315            vec!["1".into()],
19316            vec!["2".into()],
19317            true,
19318        );
19319        switch.i_max = Some(Vec::new());
19320        network.switches_mut().push(switch);
19321
19322        let mut winding = powerio_dist::DistWinding::new(
19323            "bus",
19324            vec!["1".into(), "2".into()],
19325            powerio_dist::DistWindingConn::Wye,
19326            240.0,
19327            1_000.0,
19328        );
19329        winding.r_pct = 0.5;
19330        winding.r_neutral = Some(0.0);
19331        network
19332            .transformers_mut()
19333            .push(powerio_dist::DistTransformer::new(
19334                "transformer",
19335                vec![winding],
19336                vec![2.5],
19337                1,
19338            ));
19339
19340        let mut load = powerio_dist::DistLoad::new(
19341            "load",
19342            "bus",
19343            vec!["1".into()],
19344            powerio_dist::Configuration::Wye,
19345            vec![100.0],
19346            vec![20.0],
19347        );
19348        load.voltage_model = powerio_dist::DistLoadVoltageModel::Zip {
19349            v_nom: vec![240.0],
19350            alpha_z: vec![0.1],
19351            alpha_i: vec![0.2],
19352            alpha_p: vec![0.7],
19353            beta_z: vec![0.2],
19354            beta_i: vec![0.3],
19355            beta_p: vec![0.5],
19356        };
19357        network.loads_mut().push(load);
19358
19359        let mut generator = powerio_dist::DistGenerator::new(
19360            "generator",
19361            "bus",
19362            vec!["1".into()],
19363            powerio_dist::Configuration::SinglePhase,
19364            vec![50.0],
19365            vec![5.0],
19366        );
19367        generator.p_min = Some(Vec::new());
19368        generator.cost = Some(vec![0.25]);
19369        network.generators_mut().push(generator);
19370
19371        let mut resource = powerio_dist::DistIbr::new(
19372            "ibr",
19373            "bus",
19374            vec!["1".into()],
19375            powerio_dist::IbrTopology::SinglePhase,
19376            powerio_dist::IbrPrimeMover::Pv,
19377            vec![100.0],
19378        );
19379        resource.i_max = Some(Vec::new());
19380        resource.p_avail = Some(75.0);
19381        resource.control_profile = Some(String::new());
19382        resource.voltage_aggregation = Some(powerio_dist::IbrVoltageAggregation::PerPhase);
19383        network.ibrs_mut().push(resource);
19384
19385        let profile: powerio_dist::DistControlProfile = serde_json::from_value(serde_json::json!({
19386            "name": "profile",
19387            "power_factor": { "pf": 0.97 },
19388            "volt_var": {
19389                "voltage_reference": "PN_PER_PHASE",
19390                "breakpoints": [0.95, 1.05],
19391                "q_limits": [-0.4, 0.4],
19392                "q_unit": "VA_FRACTION",
19393                "q_ref": "VAR_MAX",
19394                "p_min_for_q": 10.0,
19395                "p_min_for_q_max": null
19396            },
19397            "volt_watt": {
19398                "voltage_reference": null,
19399                "breakpoints": [1.0],
19400                "p_limits": [0.8],
19401                "p_unit": "W",
19402                "p_ref": "P_AVAILABLE"
19403            },
19404            "extras": {}
19405        }))
19406        .unwrap();
19407        network.control_profiles_mut().push(profile);
19408
19409        network.shunts_mut().push(powerio_dist::DistShunt::new(
19410            "shunt",
19411            "bus",
19412            vec!["2".into()],
19413            vec![vec![0.01, 0.02], vec![0.03]],
19414            vec![vec![0.04]],
19415        ));
19416        network
19417            .capacitors_mut()
19418            .push(powerio_dist::DistCapacitor::new(
19419                "capacitor",
19420                "bus",
19421                vec!["1".into()],
19422                powerio_dist::Configuration::SinglePhase,
19423                100.0,
19424                240.0,
19425            ));
19426        network.sources_mut().push(powerio_dist::VoltageSource::new(
19427            "source",
19428            "bus",
19429            vec!["1".into()],
19430            vec![240.0],
19431            vec![0.0],
19432        ));
19433        network.sources_mut()[0].energy_cost_rate = Some(vec![0.125]);
19434        network
19435            .untyped_objects_mut()
19436            .push(powerio_dist::UntypedObject::new(
19437                "curve",
19438                "raw",
19439                vec![
19440                    (None, "first".into()),
19441                    (Some(String::new()), "second".into()),
19442                ],
19443            ));
19444        network
19445            .commands_mut()
19446            .push(("solve".into(), "mode=daily".into()));
19447        network
19448            .options_mut()
19449            .push(("frequency".into(), "60".into()));
19450        network
19451    }
19452
19453    fn goc3_instance(source: Source) -> powerio_prob::AcScucInstance {
19454        let module = powerio::parse_with_options(
19455            source,
19456            &powerio::ParseOptions::default()
19457                .format("goc3-json")
19458                .unwrap(),
19459        )
19460        .unwrap();
19461        let PioValue::AcScucInstance(instance) = module.into_value() else {
19462            panic!("GO Challenge 3 problem did not produce powerio.AcScucInstance");
19463        };
19464        instance
19465    }
19466
19467    #[test]
19468    fn reports_abi_seven() {
19469        assert_eq!(pio_abi_version(), 7);
19470        assert_eq!(PIO_ABI_VERSION, 7);
19471        unsafe {
19472            let mut error = std::ptr::null_mut();
19473            let report = pio_schema_report(&mut error);
19474            assert!(!report.is_null(), "{}", error_text(error));
19475            let parsed: serde_json::Value =
19476                serde_json::from_str(&view_text(pio_string_view(report))).unwrap();
19477            assert_eq!(parsed["abi"], 7);
19478            assert_eq!(parsed["powerio_ir"]["schema"], powerio::IR_SCHEMA_NAME);
19479            assert_eq!(parsed["powerio_ir"]["version"], powerio::IR_VERSION);
19480            assert_eq!(
19481                parsed["bmopf_schema"],
19482                serde_json::json!(powerio_dist::BMOPF_SCHEMA_VERSION)
19483            );
19484            assert_eq!(parsed["foreign_schemas"]["bmopf"], parsed["bmopf_schema"]);
19485            assert_eq!(
19486                parsed["error_categories"],
19487                serde_json::json!(powerio_core::ErrorCategory::TOKENS)
19488            );
19489            assert_eq!(
19490                parsed["diagnostic_namespaces"],
19491                serde_json::json!(powerio_core::DiagnosticStage::NAMESPACES)
19492            );
19493            assert_eq!(
19494                parsed["json_classes"],
19495                serde_json::json!(powerio::JSON_CLASSES)
19496            );
19497            for feature in ["matrix", "gridfm", "dist", "prob"] {
19498                assert!(parsed["features"][feature].is_boolean());
19499            }
19500            pio_string_release(report);
19501        }
19502    }
19503
19504    #[test]
19505    fn structured_diagnostic_fields_keep_their_owner_alive() {
19506        unsafe {
19507            let source = powerio_core::SourceId::new("input").unwrap();
19508            let span = powerio_core::SourceSpan::new(source, 2, 7).unwrap();
19509            let mut details = serde_json::Map::new();
19510            details.insert("count".to_owned(), serde_json::json!(3));
19511            details.insert("nested".to_owned(), serde_json::json!({ "ok": true }));
19512            let diagnostic = Diagnostic::new(
19513                powerio_core::DiagnosticCode::new("PARTNER.TEST.STRUCTURED").unwrap(),
19514                powerio_core::DiagnosticSeverity::Warning,
19515                "structured finding",
19516            )
19517            .with_id(powerio_core::DiagnosticId::new("d1").unwrap())
19518            .with_target("/value/data")
19519            .unwrap()
19520            .with_span(span)
19521            .unwrap()
19522            .with_related(powerio_core::DiagnosticId::new("d0").unwrap())
19523            .unwrap()
19524            .with_details(details)
19525            .unwrap()
19526            .with_suggested_action("fix it");
19527            let diagnostics = PioDiagnostics::new_raw(DiagnosticsInner {
19528                owner: DiagnosticsOwner::Owned(vec![diagnostic]),
19529            });
19530            let retained = pio_diagnostics_retain(diagnostics);
19531            pio_diagnostics_release(diagnostics);
19532
19533            assert_eq!(pio_diagnostics_len(retained), 1);
19534            assert_eq!(
19535                view_text(pio_diagnostic_code(retained, 0)),
19536                "PARTNER.TEST.STRUCTURED"
19537            );
19538            assert_eq!(view_text(pio_diagnostic_severity(retained, 0)), "warning");
19539            assert_eq!(
19540                view_text(pio_diagnostic_message(retained, 0)),
19541                "structured finding"
19542            );
19543            assert!(pio_diagnostic_has_id(retained, 0));
19544            assert_eq!(view_text(pio_diagnostic_id(retained, 0)), "d1");
19545            assert!(pio_diagnostic_has_target(retained, 0));
19546            assert_eq!(view_text(pio_diagnostic_target(retained, 0)), "/value/data");
19547            assert!(pio_diagnostic_has_suggested_action(retained, 0));
19548            assert_eq!(
19549                view_text(pio_diagnostic_suggested_action(retained, 0)),
19550                "fix it"
19551            );
19552
19553            assert_eq!(pio_diagnostic_n_spans(retained, 0), 1);
19554            let mut output = PioDiagnosticSpanView {
19555                source: PioStringView::EMPTY,
19556                byte_start: 0,
19557                byte_end: 0,
19558            };
19559            let mut error = std::ptr::null_mut();
19560            assert!(pio_diagnostic_span(retained, 0, 0, &mut output, &mut error));
19561            assert!(error.is_null());
19562            assert_eq!(view_text(output.source), "input");
19563            assert_eq!(output.byte_start, 2);
19564            assert_eq!(output.byte_end, 7);
19565
19566            assert_eq!(pio_diagnostic_n_related(retained, 0), 1);
19567            assert_eq!(view_text(pio_diagnostic_related(retained, 0, 0)), "d0");
19568
19569            let details_text = pio_diagnostic_details_json(retained, 0, &mut error);
19570            assert!(!details_text.is_null(), "{}", error_text(error));
19571            let details: serde_json::Value =
19572                serde_json::from_str(&view_text(pio_string_view(details_text))).unwrap();
19573            assert_eq!(details["count"], 3);
19574            assert_eq!(details["nested"]["ok"], true);
19575            pio_string_release(details_text);
19576
19577            pio_diagnostics_release(retained);
19578        }
19579    }
19580
19581    #[test]
19582    fn module_records_are_typed_and_structured_values_keep_the_owner_alive() {
19583        unsafe {
19584            let module = module_with_complete_records();
19585            let mut error = std::ptr::null_mut();
19586
19587            let mut producer = std::mem::MaybeUninit::<PioModuleProducerView>::uninit();
19588            assert!(pio_module_producer(
19589                module,
19590                producer.as_mut_ptr(),
19591                &mut error
19592            ));
19593            let producer = producer.assume_init();
19594            assert_eq!(view_text(producer.name), "record-test");
19595            assert_eq!(view_text(producer.version), "1.2.3");
19596
19597            assert_eq!(pio_module_source_count(module), 1);
19598            let mut source = std::mem::MaybeUninit::<PioModuleSourceView>::uninit();
19599            assert!(pio_module_source_at(
19600                module,
19601                0,
19602                source.as_mut_ptr(),
19603                &mut error
19604            ));
19605            let source = source.assume_init();
19606            assert_eq!(view_text(source.id), "source-1");
19607            assert_eq!(view_text(source.name), "record-test.xiidm");
19608            assert_eq!(source.byte_length, 128);
19609            assert!(source.has_format);
19610            assert_eq!(view_text(source.format), "xiidm");
19611            assert!(source.has_digest);
19612            assert_eq!(view_text(source.digest_algorithm), "sha256");
19613            assert_eq!(source.digest.len, 64);
19614
19615            assert_eq!(pio_module_source_map_count(module), 1);
19616            let mut mapping = std::mem::MaybeUninit::<PioModuleSourceMapEntryView>::uninit();
19617            assert!(pio_module_source_map_at(
19618                module,
19619                0,
19620                mapping.as_mut_ptr(),
19621                &mut error
19622            ));
19623            let mapping = mapping.assume_init();
19624            assert_eq!(view_text(mapping.target), "/value/buses/0");
19625            assert_eq!(view_text(mapping.relation), "exact");
19626            assert_eq!(mapping.span_count, 1);
19627            let mut span = std::mem::MaybeUninit::<PioSourceSpanView>::uninit();
19628            assert!(pio_module_source_map_span_at(
19629                module,
19630                0,
19631                0,
19632                span.as_mut_ptr(),
19633                &mut error
19634            ));
19635            let span = span.assume_init();
19636            assert_eq!(view_text(span.source), "source-1");
19637            assert_eq!((span.byte_start, span.byte_end), (4, 12));
19638
19639            assert_eq!(pio_module_history_count(module), 1);
19640            let mut history = std::mem::MaybeUninit::<PioModuleHistoryEntryView>::uninit();
19641            assert!(pio_module_history_at(
19642                module,
19643                0,
19644                history.as_mut_ptr(),
19645                &mut error
19646            ));
19647            let history = history.assume_init();
19648            assert_eq!(view_text(history.id), "history-1");
19649            assert_eq!(view_text(history.kind), "transform");
19650            assert_eq!(view_text(history.name), "normalize");
19651            assert!(history.has_input_type);
19652            assert_eq!(view_text(history.input_type), "powerio.BalancedNetwork");
19653            assert!(history.has_output_type);
19654            assert_eq!(view_text(history.output_type), "powerio.BalancedNetwork");
19655            assert_eq!(history.parameter_count, 1);
19656            assert_eq!(history.assumption_count, 1);
19657            assert_eq!(history.loss_count, 1);
19658            assert_eq!(
19659                view_text(pio_module_history_assumption_at(module, 0, 0, &mut error)),
19660                "base power is known"
19661            );
19662            assert_eq!(
19663                view_text(pio_module_history_loss_at(module, 0, 0, &mut error)),
19664                "source token spelling"
19665            );
19666            let mut parameter = std::mem::MaybeUninit::<PioModuleHistoryParameterView>::uninit();
19667            assert!(pio_module_history_parameter_at(
19668                module,
19669                0,
19670                0,
19671                parameter.as_mut_ptr(),
19672                &mut error
19673            ));
19674            let parameter = parameter.assume_init();
19675            assert_eq!(view_text(parameter.name), "settings");
19676            assert_eq!(view_text(parameter.value_kind), "object");
19677
19678            assert_eq!(pio_module_extension_count(module), 1);
19679            let mut extension = std::mem::MaybeUninit::<PioModuleExtensionView>::uninit();
19680            assert!(pio_module_extension_at(
19681                module,
19682                0,
19683                extension.as_mut_ptr(),
19684                &mut error
19685            ));
19686            let extension = extension.assume_init();
19687            assert_eq!(view_text(extension.namespace), "org.example.record");
19688            assert_eq!(view_text(extension.value_kind), "object");
19689
19690            let parameter_value = pio_module_history_parameter_value_at(module, 0, 0, &mut error);
19691            assert!(!parameter_value.is_null(), "{}", error_text(error));
19692            let items = pio_json_value_object_value_at(parameter_value, 0, &mut error);
19693            assert!(!items.is_null(), "{}", error_text(error));
19694            let unsigned = pio_json_value_array_at(items, 3, &mut error);
19695            assert!(!unsigned.is_null(), "{}", error_text(error));
19696            let retained_unsigned = pio_json_value_retain(unsigned);
19697            let extension_value = pio_module_extension_value_at(module, 0, &mut error);
19698            assert!(!extension_value.is_null(), "{}", error_text(error));
19699
19700            let mut ignored = std::mem::MaybeUninit::<PioModuleSourceView>::uninit();
19701            assert!(!pio_module_source_at(
19702                module,
19703                1,
19704                ignored.as_mut_ptr(),
19705                &mut error
19706            ));
19707            assert!(!error.is_null());
19708            pio_error_release(error);
19709            error = std::ptr::null_mut();
19710
19711            pio_module_release(module);
19712            pio_json_value_release(parameter_value);
19713            pio_json_value_release(items);
19714            pio_json_value_release(unsigned);
19715
19716            let mut number = std::mem::MaybeUninit::<PioJsonValueView>::uninit();
19717            assert!(pio_json_value_get(
19718                retained_unsigned,
19719                number.as_mut_ptr(),
19720                &mut error
19721            ));
19722            let number = number.assume_init();
19723            assert_eq!(view_text(number.kind), "number");
19724            assert_eq!(view_text(number.number_kind), "unsigned_integer");
19725            assert_eq!(number.unsigned_integer_value, u64::MAX);
19726
19727            let enabled = pio_json_value_object_value_at(extension_value, 0, &mut error);
19728            assert!(!enabled.is_null(), "{}", error_text(error));
19729            let mut boolean = std::mem::MaybeUninit::<PioJsonValueView>::uninit();
19730            assert!(pio_json_value_get(
19731                enabled,
19732                boolean.as_mut_ptr(),
19733                &mut error
19734            ));
19735            let boolean = boolean.assume_init();
19736            assert_eq!(view_text(boolean.kind), "boolean");
19737            assert!(boolean.boolean_value);
19738
19739            let mut null_output = std::mem::MaybeUninit::<PioModuleProducerView>::uninit();
19740            assert!(!pio_module_producer(
19741                std::ptr::null(),
19742                null_output.as_mut_ptr(),
19743                &mut error
19744            ));
19745            assert!(!error.is_null());
19746            pio_error_release(error);
19747            pio_json_value_release(enabled);
19748            pio_json_value_release(extension_value);
19749            pio_json_value_release(retained_unsigned);
19750        }
19751    }
19752
19753    #[test]
19754    fn structural_access_keeps_the_module_owner_alive() {
19755        unsafe {
19756            let module = parse_case9();
19757            let diagnostics = pio_module_diagnostics(module);
19758            let value = pio_module_value(module);
19759            assert!(pio_value_is_type(
19760                value,
19761                c"powerio.BalancedNetwork".as_ptr(),
19762                "powerio.BalancedNetwork".len(),
19763            ));
19764            let mut error = std::ptr::null_mut();
19765            let network = pio_value_balanced_network(value, &mut error);
19766            assert!(!network.is_null(), "{}", error_text(error));
19767
19768            pio_module_release(module);
19769            pio_value_release(value);
19770            assert_eq!(pio_balanced_network_bus_count(network), 9);
19771            assert_eq!(pio_balanced_network_branch_count(network), 9);
19772            assert_eq!(pio_diagnostics_len(diagnostics), 0);
19773
19774            pio_balanced_network_release(network);
19775            pio_diagnostics_release(diagnostics);
19776        }
19777    }
19778
19779    #[test]
19780    fn abi_seven_bus_views_preserve_phase_bounds_after_owner_release() {
19781        unsafe {
19782            let mut network = complete_multiconductor_network();
19783            network.buses_mut()[0].v_min = None;
19784            network.buses_mut()[0].v_min_phase = Some(vec![210.0, 215.0]);
19785            let module = module_handle(powerio::PioModule::new(PioValue::from(network)));
19786            let value = pio_module_value(module);
19787            let mut error = std::ptr::null_mut();
19788            let network = pio_value_multiconductor_network(value, &mut error);
19789            assert!(!network.is_null());
19790            let mut bus = std::mem::MaybeUninit::<PioMulticonductorBusView>::uninit();
19791            pio_module_release(module);
19792            pio_value_release(value);
19793            assert!(pio_multiconductor_network_bus_at(
19794                network,
19795                0,
19796                bus.as_mut_ptr(),
19797                &mut error
19798            ));
19799            let bus = bus.assume_init();
19800            assert!(bus.has_phase_to_ground_voltage_min);
19801            assert!(!bus.has_voltage_min);
19802            assert_eq!(
19803                std::slice::from_raw_parts(
19804                    bus.phase_to_ground_voltage_min_v.data,
19805                    bus.phase_to_ground_voltage_min_v.len
19806                ),
19807                &[210.0, 215.0]
19808            );
19809            pio_multiconductor_network_release(network);
19810        }
19811    }
19812
19813    #[test]
19814    #[allow(clippy::too_many_lines)]
19815    fn multiconductor_typed_views_preserve_ownership_and_optional_values() {
19816        unsafe {
19817            let module = module_handle(powerio::PioModule::new(PioValue::from(
19818                complete_multiconductor_network(),
19819            )));
19820            let value = pio_module_value(module);
19821            let mut error = std::ptr::null_mut();
19822            let network = pio_value_multiconductor_network(value, &mut error);
19823            assert!(!network.is_null(), "{}", error_text(error));
19824            let retained = pio_multiconductor_network_retain(network);
19825            pio_module_release(module);
19826            pio_value_release(value);
19827            pio_multiconductor_network_release(network);
19828
19829            assert!(pio_multiconductor_network_has_name(retained));
19830            assert_eq!(pio_multiconductor_network_name(retained).len, 0);
19831            assert!(pio_multiconductor_network_has_source_format(retained));
19832            assert_eq!(
19833                view_text(pio_multiconductor_network_source_format(retained)),
19834                "dss"
19835            );
19836
19837            let mut geo = std::mem::MaybeUninit::<PioMulticonductorGeoView>::uninit();
19838            assert!(pio_multiconductor_network_geo(
19839                retained,
19840                geo.as_mut_ptr(),
19841                &mut error,
19842            ));
19843            let geo = geo.assume_init();
19844            assert!(geo.has_geo);
19845            assert_eq!(view_text(geo.space), "diagram");
19846            assert!(geo.has_canvas);
19847            assert!(geo.has_canvas_width);
19848            assert!(!geo.has_canvas_height);
19849            assert!(geo.has_canvas_units);
19850            assert_eq!(geo.canvas_units.len, 0);
19851
19852            let mut counts = std::mem::MaybeUninit::<PioMulticonductorNetworkCountsView>::uninit();
19853            assert!(pio_multiconductor_network_counts(
19854                retained,
19855                counts.as_mut_ptr(),
19856                &mut error,
19857            ));
19858            let counts = counts.assume_init();
19859            for count in [
19860                counts.buses,
19861                counts.line_codes,
19862                counts.lines,
19863                counts.switches,
19864                counts.transformers,
19865                counts.loads,
19866                counts.generators,
19867                counts.inverter_based_resources,
19868                counts.control_profiles,
19869                counts.shunts,
19870                counts.capacitors,
19871                counts.voltage_sources,
19872                counts.untyped_objects,
19873                counts.commands,
19874                counts.options,
19875            ] {
19876                assert_eq!(count, 1);
19877            }
19878
19879            let mut bus = std::mem::MaybeUninit::<PioMulticonductorBusView>::uninit();
19880            assert!(pio_multiconductor_network_bus_at(
19881                retained,
19882                0,
19883                bus.as_mut_ptr(),
19884                &mut error,
19885            ));
19886            let bus = bus.assume_init();
19887            assert_eq!(view_text(bus.id), "bus");
19888            assert!(bus.has_phase_to_neutral_voltage_min);
19889            assert_eq!(bus.phase_to_neutral_voltage_min_v.len, 0);
19890            assert!(bus.has_location);
19891            assert!(!bus.location.has_kind);
19892            let mut text = std::mem::MaybeUninit::<PioStringView>::uninit();
19893            assert!(pio_multiconductor_network_bus_terminal_at(
19894                retained,
19895                0,
19896                1,
19897                text.as_mut_ptr(),
19898                &mut error,
19899            ));
19900            assert_eq!(view_text(text.assume_init()), "2");
19901
19902            let mut line_code = std::mem::MaybeUninit::<PioMulticonductorLineCodeView>::uninit();
19903            assert!(pio_multiconductor_network_line_code_at(
19904                retained,
19905                0,
19906                line_code.as_mut_ptr(),
19907                &mut error,
19908            ));
19909            let line_code = line_code.assume_init();
19910            assert_eq!(line_code.resistance_matrix_row_count, 2);
19911            assert!(line_code.has_current_limit);
19912            assert_eq!(line_code.current_limit_a.len, 0);
19913            assert!(line_code.has_source);
19914            assert_eq!(line_code.source.len, 0);
19915            let mut matrix_row = std::mem::MaybeUninit::<PioF64View>::uninit();
19916            assert!(
19917                pio_multiconductor_network_line_code_resistance_matrix_row_at(
19918                    retained,
19919                    0,
19920                    1,
19921                    matrix_row.as_mut_ptr(),
19922                    &mut error,
19923                )
19924            );
19925            let matrix_row = matrix_row.assume_init();
19926            assert_eq!(matrix_row.len, 1);
19927            assert_eq!(*matrix_row.data, 3.0);
19928
19929            let mut line = std::mem::MaybeUninit::<PioMulticonductorLineView>::uninit();
19930            assert!(pio_multiconductor_network_line_at(
19931                retained,
19932                0,
19933                line.as_mut_ptr(),
19934                &mut error,
19935            ));
19936            let line = line.assume_init();
19937            assert!(line.has_route);
19938            assert_eq!(line.route_point_count, 0);
19939
19940            let mut switch = std::mem::MaybeUninit::<PioMulticonductorSwitchView>::uninit();
19941            assert!(pio_multiconductor_network_switch_at(
19942                retained,
19943                0,
19944                switch.as_mut_ptr(),
19945                &mut error,
19946            ));
19947            let switch = switch.assume_init();
19948            assert!(switch.open);
19949            assert!(switch.has_current_limit);
19950            assert_eq!(switch.current_limit_a.len, 0);
19951
19952            let mut transformer =
19953                std::mem::MaybeUninit::<PioMulticonductorTransformerView>::uninit();
19954            assert!(pio_multiconductor_network_transformer_at(
19955                retained,
19956                0,
19957                transformer.as_mut_ptr(),
19958                &mut error,
19959            ));
19960            assert_eq!(transformer.assume_init().winding_count, 1);
19961            let mut winding =
19962                std::mem::MaybeUninit::<PioMulticonductorTransformerWindingView>::uninit();
19963            assert!(pio_multiconductor_network_transformer_winding_at(
19964                retained,
19965                0,
19966                0,
19967                winding.as_mut_ptr(),
19968                &mut error,
19969            ));
19970            let winding = winding.assume_init();
19971            assert!(winding.has_neutral_resistance);
19972            assert!(!winding.has_neutral_reactance);
19973
19974            let mut load = std::mem::MaybeUninit::<PioMulticonductorLoadView>::uninit();
19975            assert!(pio_multiconductor_network_load_at(
19976                retained,
19977                0,
19978                load.as_mut_ptr(),
19979                &mut error,
19980            ));
19981            let load = load.assume_init();
19982            assert_eq!(view_text(load.voltage_model), "zip");
19983            assert_eq!(*load.active_power_constant_power.data, 0.7);
19984
19985            let mut generator = std::mem::MaybeUninit::<PioMulticonductorGeneratorView>::uninit();
19986            assert!(pio_multiconductor_network_generator_at(
19987                retained,
19988                0,
19989                generator.as_mut_ptr(),
19990                &mut error,
19991            ));
19992            let generator = generator.assume_init();
19993            assert!(generator.has_active_power_min);
19994            assert_eq!(generator.active_power_min_w.len, 0);
19995            assert!(generator.has_active_power_dispatch_cost);
19996
19997            let mut resource = std::mem::MaybeUninit::<PioInverterBasedResourceView>::uninit();
19998            assert!(pio_multiconductor_network_inverter_based_resource_at(
19999                retained,
20000                0,
20001                resource.as_mut_ptr(),
20002                &mut error,
20003            ));
20004            let resource = resource.assume_init();
20005            assert_eq!(view_text(resource.topology), "SINGLE_PHASE");
20006            assert!(resource.has_control_profile);
20007            assert_eq!(resource.control_profile.len, 0);
20008
20009            let mut profile = std::mem::MaybeUninit::<PioControlProfileView>::uninit();
20010            assert!(pio_multiconductor_network_control_profile_at(
20011                retained,
20012                0,
20013                profile.as_mut_ptr(),
20014                &mut error,
20015            ));
20016            let profile = profile.assume_init();
20017            assert!(profile.has_power_factor);
20018            assert!(profile.has_volt_var);
20019            assert!(profile.has_volt_watt);
20020            assert_eq!(
20021                view_text(profile.volt_var_voltage_reference),
20022                "PN_PER_PHASE"
20023            );
20024
20025            let mut shunt = std::mem::MaybeUninit::<PioMulticonductorShuntView>::uninit();
20026            assert!(pio_multiconductor_network_shunt_at(
20027                retained,
20028                0,
20029                shunt.as_mut_ptr(),
20030                &mut error,
20031            ));
20032            assert_eq!(shunt.assume_init().conductance_matrix_row_count, 2);
20033            let mut shunt_matrix_row = std::mem::MaybeUninit::<PioF64View>::uninit();
20034            assert!(pio_multiconductor_network_shunt_conductance_matrix_row_at(
20035                retained,
20036                0,
20037                1,
20038                shunt_matrix_row.as_mut_ptr(),
20039                &mut error,
20040            ));
20041            assert_eq!(shunt_matrix_row.assume_init().len, 1);
20042
20043            let mut capacitor = std::mem::MaybeUninit::<PioMulticonductorCapacitorView>::uninit();
20044            assert!(pio_multiconductor_network_capacitor_at(
20045                retained,
20046                0,
20047                capacitor.as_mut_ptr(),
20048                &mut error,
20049            ));
20050            assert_eq!(capacitor.assume_init().rated_reactive_power_var, 100.0);
20051
20052            let mut source = std::mem::MaybeUninit::<PioVoltageSourceView>::uninit();
20053            assert!(pio_multiconductor_network_voltage_source_at(
20054                retained,
20055                0,
20056                source.as_mut_ptr(),
20057                &mut error,
20058            ));
20059            let source = source.assume_init();
20060            assert_eq!(*source.voltage_magnitude_v.data, 240.0);
20061            assert!(source.has_energy_cost_rate);
20062            assert_eq!(source.energy_cost_rate_per_kwh.len, 1);
20063            assert_eq!(*source.energy_cost_rate_per_kwh.data, 0.125);
20064
20065            let mut object = std::mem::MaybeUninit::<PioMulticonductorUntypedObjectView>::uninit();
20066            assert!(pio_multiconductor_network_untyped_object_at(
20067                retained,
20068                0,
20069                object.as_mut_ptr(),
20070                &mut error,
20071            ));
20072            assert_eq!(object.assume_init().property_count, 2);
20073            let mut property =
20074                std::mem::MaybeUninit::<PioMulticonductorUntypedPropertyView>::uninit();
20075            assert!(pio_multiconductor_network_untyped_object_property_at(
20076                retained,
20077                0,
20078                0,
20079                property.as_mut_ptr(),
20080                &mut error,
20081            ));
20082            assert!(!property.assume_init().has_name);
20083            assert!(pio_multiconductor_network_untyped_object_property_at(
20084                retained,
20085                0,
20086                1,
20087                property.as_mut_ptr(),
20088                &mut error,
20089            ));
20090            let property = property.assume_init();
20091            assert!(property.has_name);
20092            assert_eq!(property.name.len, 0);
20093
20094            let mut command = std::mem::MaybeUninit::<PioMulticonductorCommandView>::uninit();
20095            assert!(pio_multiconductor_network_command_at(
20096                retained,
20097                0,
20098                command.as_mut_ptr(),
20099                &mut error,
20100            ));
20101            assert_eq!(view_text(command.assume_init().verb), "solve");
20102            let mut option = std::mem::MaybeUninit::<PioStringPropertyView>::uninit();
20103            assert!(pio_multiconductor_network_option_at(
20104                retained,
20105                0,
20106                option.as_mut_ptr(),
20107                &mut error,
20108            ));
20109            assert_eq!(view_text(option.assume_init().name), "frequency");
20110
20111            assert!(
20112                std::mem::offset_of!(PioMulticonductorLineView, has_route)
20113                    > std::mem::offset_of!(PioMulticonductorLineView, route_point_count)
20114            );
20115            assert!(
20116                std::mem::offset_of!(PioMulticonductorBusView, has_location)
20117                    > std::mem::offset_of!(PioMulticonductorBusView, location)
20118            );
20119
20120            let mut null_error = std::ptr::null_mut();
20121            let mut null_output = std::mem::MaybeUninit::<PioMulticonductorBusView>::uninit();
20122            assert!(!pio_multiconductor_network_bus_at(
20123                std::ptr::null(),
20124                0,
20125                null_output.as_mut_ptr(),
20126                &mut null_error,
20127            ));
20128            assert!(!null_error.is_null());
20129            assert_eq!(
20130                view_text(pio_error_code(null_error)),
20131                codes::BIND_CAPI_NULL_HANDLE.code
20132            );
20133            pio_error_release(null_error);
20134            pio_multiconductor_network_release(retained);
20135        }
20136    }
20137
20138    #[test]
20139    fn balanced_geography_is_typed_and_preserves_optional_routes() {
20140        unsafe {
20141            let mut network = case9_network();
20142            *network.geo_mut() = Some(powerio_tx::GeoMeta {
20143                space: powerio_tx::CoordinateSpace::Diagram {
20144                    canvas: Some(powerio_tx::Canvas {
20145                        width: Some(800.0),
20146                        height: None,
20147                        units: Some(String::new()),
20148                    }),
20149                },
20150                kind: Some(powerio_tx::CoordsKind::Manual),
20151            });
20152            network.buses_mut()[0].location = Some(powerio_tx::Location {
20153                x: 12.0,
20154                y: 34.0,
20155                kind: None,
20156            });
20157            network.branches_mut()[0].route = Some(Vec::new());
20158            network.branches_mut()[1].route = Some(vec![powerio_tx::Location {
20159                x: 56.0,
20160                y: 78.0,
20161                kind: Some(powerio_tx::CoordsKind::Derived),
20162            }]);
20163
20164            let module = module_handle(powerio::PioModule::new(PioValue::BalancedNetwork(network)));
20165            let value = pio_module_value(module);
20166            let mut error = std::ptr::null_mut();
20167            let balanced = pio_value_balanced_network(value, &mut error);
20168            assert!(!balanced.is_null(), "{}", error_text(error));
20169            let retained = pio_balanced_network_retain(balanced);
20170            pio_balanced_network_release(balanced);
20171            pio_value_release(value);
20172            pio_module_release(module);
20173
20174            let mut geo = std::mem::MaybeUninit::<PioBalancedGeoView>::uninit();
20175            assert!(pio_balanced_network_geo(
20176                retained,
20177                geo.as_mut_ptr(),
20178                &mut error
20179            ));
20180            let geo = geo.assume_init();
20181            assert!(geo.has_geo);
20182            assert_eq!(view_text(geo.space), "diagram");
20183            assert!(geo.has_kind);
20184            assert_eq!(view_text(geo.kind), "manual");
20185            assert!(geo.has_canvas);
20186            assert!(geo.has_canvas_width);
20187            assert_eq!(geo.canvas_width, 800.0);
20188            assert!(!geo.has_canvas_height);
20189            assert!(geo.has_canvas_units);
20190            assert_eq!(geo.canvas_units.len, 0);
20191
20192            let mut bus = std::mem::MaybeUninit::<PioBalancedBusView>::uninit();
20193            assert!(pio_balanced_network_bus_at(
20194                retained,
20195                0,
20196                bus.as_mut_ptr(),
20197                &mut error
20198            ));
20199            let bus = bus.assume_init();
20200            assert!(bus.has_location);
20201            assert_eq!((bus.location.x, bus.location.y), (12.0, 34.0));
20202            assert!(!bus.location.has_kind);
20203
20204            let mut branch = std::mem::MaybeUninit::<PioBalancedBranchView>::uninit();
20205            assert!(pio_balanced_network_branch_at(
20206                retained,
20207                0,
20208                branch.as_mut_ptr(),
20209                &mut error
20210            ));
20211            let branch = branch.assume_init();
20212            assert!(branch.has_route);
20213            assert_eq!(branch.route_point_count, 0);
20214
20215            let mut routed_branch = std::mem::MaybeUninit::<PioBalancedBranchView>::uninit();
20216            assert!(pio_balanced_network_branch_at(
20217                retained,
20218                1,
20219                routed_branch.as_mut_ptr(),
20220                &mut error
20221            ));
20222            let routed_branch = routed_branch.assume_init();
20223            assert!(routed_branch.has_route);
20224            assert_eq!(routed_branch.route_point_count, 1);
20225            let mut point = std::mem::MaybeUninit::<PioBalancedLocationView>::uninit();
20226            assert!(pio_balanced_network_branch_route_point_at(
20227                retained,
20228                1,
20229                0,
20230                point.as_mut_ptr(),
20231                &mut error
20232            ));
20233            let point = point.assume_init();
20234            assert_eq!((point.x, point.y), (56.0, 78.0));
20235            assert!(point.has_kind);
20236            assert_eq!(view_text(point.kind), "derived");
20237
20238            assert!(
20239                std::mem::offset_of!(PioBalancedBusView, has_location)
20240                    > std::mem::offset_of!(PioBalancedBusView, location)
20241            );
20242            assert!(
20243                std::mem::offset_of!(PioBalancedBranchView, has_route)
20244                    > std::mem::offset_of!(PioBalancedBranchView, route_point_count)
20245            );
20246
20247            let mut null_geo = std::mem::MaybeUninit::<PioBalancedGeoView>::uninit();
20248            assert!(!pio_balanced_network_geo(
20249                std::ptr::null(),
20250                null_geo.as_mut_ptr(),
20251                &mut error
20252            ));
20253            assert!(!error.is_null());
20254            pio_error_release(error);
20255            pio_balanced_network_release(retained);
20256        }
20257    }
20258
20259    #[test]
20260    fn lindist3flow_views_and_quantities_keep_their_owner_alive() {
20261        unsafe {
20262            let text = include_bytes!("../../tests/data/dist/micro/lindist3flow-solution.pio.json");
20263            let module = module_handle(
20264                powerio::deserialize(
20265                    powerio_core::Source::from_memory("solution.pio.json", text.to_vec()).unwrap(),
20266                )
20267                .unwrap(),
20268            );
20269            let value = pio_module_value(module);
20270            let mut error = std::ptr::null_mut();
20271            assert!(pio_value_mc_ac_opf_solution(value, &mut error).is_null());
20272            assert!(!error.is_null());
20273            pio_error_release(error);
20274            error = std::ptr::null_mut();
20275            let solution = pio_value_lindist3flow_opf_solution(value, &mut error);
20276            assert!(!solution.is_null(), "{}", error_text(error));
20277            let instance = pio_calculation_solution_instance(solution, &mut error);
20278            assert!(!instance.is_null(), "{}", error_text(error));
20279            assert_eq!(
20280                view_text(pio_calculation_instance_type_name(instance)),
20281                "powerio.LinDist3FlowOpfInstance"
20282            );
20283            let network = pio_calculation_instance_multiconductor_network(instance, &mut error);
20284            assert!(!network.is_null(), "{}", error_text(error));
20285            pio_module_release(module);
20286            pio_value_release(value);
20287            assert_eq!(
20288                view_text(pio_calculation_solution_termination(solution)),
20289                "converged"
20290            );
20291            let mut objective = 0.0;
20292            assert!(pio_calculation_solution_get_objective(
20293                solution,
20294                &mut objective
20295            ));
20296            assert_eq!(objective, 1.0);
20297            let quantity = "line_active_power";
20298            let values = pio_calculation_solution_get_values(
20299                solution,
20300                quantity.as_ptr().cast(),
20301                quantity.len(),
20302                &mut error,
20303            );
20304            assert!(!values.is_null(), "{}", error_text(error));
20305            pio_calculation_solution_release(solution);
20306            let values_view = pio_vector_values(values);
20307            assert_eq!(values_view.len, 1);
20308            assert_eq!(values_view.data.as_ref(), Some(&1000.0));
20309            assert_eq!(pio_lindist3flow_opf_instance_node_count(instance), 2);
20310            assert_eq!(pio_lindist3flow_opf_instance_conductor_count(instance), 1);
20311            let mut node = std::mem::MaybeUninit::<PioLinDist3FlowNodeView>::uninit();
20312            assert!(pio_lindist3flow_opf_instance_node_at(
20313                instance,
20314                0,
20315                node.as_mut_ptr(),
20316                &mut error
20317            ));
20318            assert_eq!(node.assume_init().reference_magnitude, 230.0);
20319            let mut conductor = std::mem::MaybeUninit::<PioLinDist3FlowConductorView>::uninit();
20320            assert!(pio_lindist3flow_opf_instance_conductor_at(
20321                instance,
20322                0,
20323                conductor.as_mut_ptr(),
20324                &mut error
20325            ));
20326            let conductor = conductor.assume_init();
20327            assert!(conductor.reversed);
20328            assert_eq!(view_text(conductor.parent_bus), "source");
20329            assert_eq!(view_text(conductor.child_bus), "load");
20330            assert!(!pio_lindist3flow_opf_instance_node_at(
20331                instance,
20332                2,
20333                node.as_mut_ptr(),
20334                &mut error
20335            ));
20336            assert!(!error.is_null());
20337            pio_error_release(error);
20338            pio_vector_release(values);
20339            pio_calculation_instance_release(instance);
20340            assert_eq!(pio_multiconductor_network_bus_count(network), 2);
20341            pio_multiconductor_network_release(network);
20342        }
20343    }
20344
20345    fn check_stored_solution_rejected(document: &serde_json::Value) {
20346        let bytes = serde_json::to_vec(document).unwrap();
20347        unsafe {
20348            let mut error = std::ptr::null_mut();
20349            let source = pio_source_from_memory(
20350                c"input.pio.json".as_ptr(),
20351                14,
20352                bytes.as_ptr(),
20353                bytes.len(),
20354                &mut error,
20355            );
20356            assert!(!source.is_null());
20357            let decoded = pio_module_deserialize(source, &mut error);
20358            assert!(decoded.is_null());
20359            assert!(!error.is_null());
20360            assert_ne!(view_text(pio_error_code(error)), "BIND.CAPI.PANIC");
20361            pio_error_release(error);
20362            pio_source_release(source);
20363        }
20364    }
20365
20366    fn check_stored_solution_columns(module: &powerio::PioModule<PioValue>) {
20367        let result = powerio::serialize(module, Destination::memory("solution").unwrap()).unwrap();
20368        let EmittedOutput::Memory { artifacts } = result.into_output() else {
20369            panic!("memory output required")
20370        };
20371        let document: serde_json::Value = serde_json::from_slice(artifacts[0].bytes()).unwrap();
20372        for key in [
20373            "bus_voltage_magnitude",
20374            "bus_voltage_angle",
20375            "bus_active_injection",
20376            "branch_from_active_flow",
20377            "terminal_voltage_magnitude",
20378            "terminal_voltage_angle",
20379        ] {
20380            let Some(values) = document["value"]["data"][key].as_array() else {
20381                continue;
20382            };
20383            if values.is_empty() {
20384                continue;
20385            }
20386            assert_eq!(
20387                collect_solution_values(module.value(), key).unwrap().len(),
20388                values.len()
20389            );
20390            let mut malformed = document.clone();
20391            malformed["value"]["data"][key]
20392                .as_array_mut()
20393                .unwrap()
20394                .pop();
20395            check_stored_solution_rejected(&malformed);
20396        }
20397        let mut duplicate = document;
20398        let buses = duplicate["value"]["data"]["instance"]["network"]["buses"]
20399            .as_array_mut()
20400            .unwrap();
20401        if buses.len() > 1 {
20402            buses[1]["id"] = buses[0]["id"].clone();
20403        } else {
20404            let terminals = buses[0]["terminals"].as_array_mut().unwrap();
20405            assert!(terminals.len() >= 2);
20406            terminals[1] = terminals[0].clone();
20407        }
20408        check_stored_solution_rejected(&duplicate);
20409    }
20410
20411    #[test]
20412    #[allow(clippy::too_many_lines)]
20413    fn every_solution_exposes_its_owner_rooted_instance() {
20414        unsafe {
20415            let network = case9_network();
20416            let buses = network.buses().len();
20417            let branches = network.branches().len();
20418            let generators = network.generators().len();
20419            let three_winding = network.transformers_3w().len();
20420
20421            let dc_pf_instance =
20422                Arc::new(powerio_prob::DcPfInstance::from_network(network.clone()).unwrap());
20423            let dc_pf = powerio_prob::DcPfSolution::new(
20424                dc_pf_instance,
20425                powerio_prob::Termination::Converged,
20426                vec![0.0; buses],
20427                vec![0.0; buses],
20428                vec![0.0; branches],
20429                vec![0.0; branches],
20430                vec![Default::default(); three_winding],
20431            )
20432            .unwrap();
20433
20434            let ac_pf_instance =
20435                Arc::new(powerio_prob::AcPfInstance::from_network(network.clone()).unwrap());
20436            let ac_pf = powerio_prob::AcPfSolution::new(
20437                ac_pf_instance,
20438                powerio_prob::Termination::Converged,
20439                vec![1.0; buses],
20440                vec![0.0; buses],
20441                vec![0.0; buses],
20442                vec![0.0; buses],
20443                vec![0.0; branches],
20444                vec![0.0; branches],
20445                vec![0.0; branches],
20446                vec![0.0; branches],
20447                vec![Default::default(); three_winding],
20448            )
20449            .unwrap();
20450
20451            let dc_opf_instance =
20452                Arc::new(powerio_prob::DcOpfInstance::from_network(network.clone()).unwrap());
20453            let dc_opf = powerio_prob::DcOpfSolution::new(
20454                dc_opf_instance,
20455                powerio_prob::Termination::Converged,
20456                vec![0.0; buses],
20457                vec![0.0; buses],
20458                vec![0.0; branches],
20459                vec![0.0; branches],
20460                vec![0.0; generators],
20461                0.0,
20462                vec![Default::default(); three_winding],
20463            )
20464            .unwrap();
20465
20466            let ac_opf_instance =
20467                Arc::new(powerio_prob::AcOpfInstance::from_network(network.clone()).unwrap());
20468            let ac_opf = powerio_prob::AcOpfSolution::new(
20469                Arc::clone(&ac_opf_instance),
20470                powerio_prob::Termination::Converged,
20471                vec![1.0; buses],
20472                vec![0.0; buses],
20473                vec![0.0; buses],
20474                vec![0.0; buses],
20475                vec![0.0; branches],
20476                vec![0.0; branches],
20477                vec![0.0; branches],
20478                vec![0.0; branches],
20479                vec![0.0; generators],
20480                vec![0.0; generators],
20481                0.0,
20482                vec![Default::default(); three_winding],
20483            )
20484            .unwrap();
20485
20486            let mut socwr_values = powerio_prob::solution::SocwrOpfValues::default();
20487            socwr_values.bus_voltage_magnitude_squared = vec![1.0; buses];
20488            socwr_values.branch_voltage_product_real = vec![0.0; branches];
20489            socwr_values.branch_voltage_product_imaginary = vec![0.0; branches];
20490            socwr_values.generator_active_power = vec![0.0; generators];
20491            socwr_values.generator_reactive_power = vec![0.0; generators];
20492            socwr_values.branch_from_active_power = vec![0.0; branches];
20493            socwr_values.branch_from_reactive_power = vec![0.0; branches];
20494            socwr_values.branch_to_active_power = vec![0.0; branches];
20495            socwr_values.branch_to_reactive_power = vec![0.0; branches];
20496            socwr_values.three_winding_transformer_terminal_powers =
20497                vec![Default::default(); three_winding];
20498            let socwr = powerio_prob::solution::SocwrOpfSolution::new(
20499                ac_opf_instance,
20500                powerio_prob::Termination::Converged,
20501                socwr_values,
20502                0.0,
20503            )
20504            .unwrap();
20505
20506            let multiconductor = multiconductor_network();
20507            let terminals: usize = multiconductor
20508                .buses()
20509                .iter()
20510                .map(|bus| bus.terminals.len())
20511                .sum();
20512            let source_terminals: usize = multiconductor
20513                .sources()
20514                .iter()
20515                .map(|source| source.terminal_map.len())
20516                .sum();
20517            let generator_terminals: usize = multiconductor
20518                .generators()
20519                .iter()
20520                .map(|generator| generator.terminal_map.len())
20521                .sum();
20522            let mc_pf_instance = Arc::new(
20523                powerio_prob::McAcPfInstance::from_network(multiconductor.clone()).unwrap(),
20524            );
20525            let mc_pf = powerio_prob::McAcPfSolution::new(
20526                mc_pf_instance,
20527                powerio_prob::Termination::Converged,
20528                vec![1.0; terminals],
20529                vec![0.0; terminals],
20530                vec![0.0; source_terminals],
20531            )
20532            .unwrap();
20533            let mc_opf_instance =
20534                Arc::new(powerio_prob::McAcOpfInstance::from_network(multiconductor).unwrap());
20535            let mc_opf = powerio_prob::McAcOpfSolution::new(
20536                mc_opf_instance,
20537                powerio_prob::Termination::Converged,
20538                vec![1.0; terminals],
20539                vec![0.0; terminals],
20540                vec![0.0; source_terminals],
20541                vec![0.0; generator_terminals],
20542                0.0,
20543            )
20544            .unwrap();
20545
20546            let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
20547                .join("../tests/data/goc3/goc3_small.json");
20548            let scuc_instance = goc3_instance(Source::open(path).unwrap());
20549            let scuc = powerio_prob::AcScucSolution::new(
20550                Arc::new(scuc_instance),
20551                powerio_prob::Termination::Converged,
20552                powerio_prob::ScucNetworkOutputs::default(),
20553                powerio_prob::ScucDeviceOutputs::default(),
20554                None,
20555            )
20556            .unwrap();
20557
20558            type SolutionAccessor = unsafe extern "C" fn(
20559                *const PioValueHandle,
20560                *mut *mut PioError,
20561            )
20562                -> *mut PioCalculationSolution;
20563            let solutions: [(PioValue, &str, bool, SolutionAccessor); 8] = [
20564                (
20565                    PioValue::DcPfSolution(dc_pf),
20566                    "powerio.DcPfInstance",
20567                    false,
20568                    pio_value_dc_pf_solution,
20569                ),
20570                (
20571                    PioValue::AcPfSolution(ac_pf),
20572                    "powerio.AcPfInstance",
20573                    false,
20574                    pio_value_ac_pf_solution,
20575                ),
20576                (
20577                    PioValue::DcOpfSolution(dc_opf),
20578                    "powerio.DcOpfInstance",
20579                    false,
20580                    pio_value_dc_opf_solution,
20581                ),
20582                (
20583                    PioValue::AcOpfSolution(ac_opf),
20584                    "powerio.AcOpfInstance",
20585                    false,
20586                    pio_value_ac_opf_solution,
20587                ),
20588                (
20589                    PioValue::SocwrOpfSolution(socwr),
20590                    "powerio.AcOpfInstance",
20591                    false,
20592                    pio_value_socwr_opf_solution,
20593                ),
20594                (
20595                    PioValue::McAcPfSolution(mc_pf),
20596                    "powerio.McAcPfInstance",
20597                    true,
20598                    pio_value_mc_ac_pf_solution,
20599                ),
20600                (
20601                    PioValue::McAcOpfSolution(mc_opf),
20602                    "powerio.McAcOpfInstance",
20603                    true,
20604                    pio_value_mc_ac_opf_solution,
20605                ),
20606                (
20607                    PioValue::AcScucSolution(scuc),
20608                    "powerio.AcScucInstance",
20609                    false,
20610                    pio_value_ac_scuc_solution,
20611                ),
20612            ];
20613
20614            for (value, expected_instance_type, is_multiconductor, accessor) in solutions {
20615                let stored = powerio::PioModule::new(value);
20616                if matches!(
20617                    stored.value(),
20618                    PioValue::AcPfSolution(_)
20619                        | PioValue::DcOpfSolution(_)
20620                        | PioValue::McAcPfSolution(_)
20621                        | PioValue::McAcOpfSolution(_)
20622                ) {
20623                    check_stored_solution_columns(&stored);
20624                }
20625                let module = module_handle(stored);
20626                let value = pio_module_value(module);
20627                let mut error = std::ptr::null_mut();
20628                let solution = accessor(value, &mut error);
20629                assert!(!solution.is_null(), "{}", error_text(error));
20630                let instance = pio_calculation_solution_instance(solution, &mut error);
20631                assert!(!instance.is_null(), "{}", error_text(error));
20632
20633                pio_calculation_solution_release(solution);
20634                pio_value_release(value);
20635                pio_module_release(module);
20636
20637                assert_eq!(
20638                    view_text(pio_calculation_instance_type_name(instance)),
20639                    expected_instance_type
20640                );
20641                if is_multiconductor {
20642                    let network =
20643                        pio_calculation_instance_multiconductor_network(instance, &mut error);
20644                    assert!(!network.is_null(), "{}", error_text(error));
20645                    assert!(pio_multiconductor_network_bus_count(network) > 0);
20646                    pio_multiconductor_network_release(network);
20647                } else {
20648                    let network = pio_calculation_instance_balanced_network(instance, &mut error);
20649                    assert!(!network.is_null(), "{}", error_text(error));
20650                    assert!(pio_balanced_network_bus_count(network) > 0);
20651                    pio_balanced_network_release(network);
20652                }
20653                pio_calculation_instance_release(instance);
20654            }
20655        }
20656    }
20657
20658    #[test]
20659    fn balanced_rows_are_checked_borrowed_views() {
20660        unsafe {
20661            let module = parse_case9();
20662            let value = pio_module_value(module);
20663            let mut error = std::ptr::null_mut();
20664            let network = pio_value_balanced_network(value, &mut error);
20665            assert!(!network.is_null(), "{}", error_text(error));
20666
20667            pio_module_release(module);
20668            pio_value_release(value);
20669
20670            assert_eq!(pio_balanced_network_base_mva(network), 100.0);
20671            assert_eq!(pio_balanced_network_base_frequency_hz(network), 60.0);
20672            assert_eq!(pio_balanced_network_bus_count(network), 9);
20673            assert_eq!(pio_balanced_network_load_count(network), 3);
20674            assert_eq!(pio_balanced_network_shunt_count(network), 0);
20675            assert_eq!(pio_balanced_network_branch_count(network), 9);
20676            assert_eq!(pio_balanced_network_generator_count(network), 3);
20677            assert_eq!(pio_balanced_network_storage_count(network), 0);
20678
20679            let mut bus = std::mem::MaybeUninit::<PioBalancedBusView>::uninit();
20680            assert!(pio_balanced_network_bus_at(
20681                network,
20682                0,
20683                bus.as_mut_ptr(),
20684                &mut error,
20685            ));
20686            let bus = bus.assume_init();
20687            assert_eq!(bus.id, 1);
20688            assert_eq!(view_text(bus.bus_type), "REF");
20689            assert_eq!(bus.base_kv, 345.0);
20690            assert!(bus.has_component_id);
20691            assert_eq!(view_text(bus.component_id), "1");
20692
20693            let mut load = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
20694            assert!(pio_balanced_network_load_at(
20695                network,
20696                0,
20697                load.as_mut_ptr(),
20698                &mut error,
20699            ));
20700            let load = load.assume_init();
20701            assert_eq!(load.bus_id, 5);
20702            assert_eq!(load.p_mw, 90.0);
20703            assert_eq!(load.q_mvar, 30.0);
20704            assert_eq!(view_text(load.voltage_model.kind), "constant_power");
20705
20706            let mut branch = std::mem::MaybeUninit::<PioBalancedBranchView>::uninit();
20707            assert!(pio_balanced_network_branch_at(
20708                network,
20709                0,
20710                branch.as_mut_ptr(),
20711                &mut error,
20712            ));
20713            let branch = branch.assume_init();
20714            assert_eq!(branch.from_bus_id, 1);
20715            assert_eq!(branch.to_bus_id, 4);
20716            assert_eq!(branch.reactance_pu, 0.0576);
20717            assert_eq!(branch.rate_a_mva, 250.0);
20718            assert_eq!(branch.tap_ratio, 0.0);
20719            assert_eq!(branch.effective_tap_ratio, 1.0);
20720            assert!(!branch.terminal_charging_is_explicit);
20721            assert_eq!(branch.additional_rating_count, 0);
20722
20723            let mut generator = std::mem::MaybeUninit::<PioBalancedGeneratorView>::uninit();
20724            assert!(pio_balanced_network_generator_at(
20725                network,
20726                0,
20727                generator.as_mut_ptr(),
20728                &mut error,
20729            ));
20730            let generator = generator.assume_init();
20731            assert_eq!(generator.bus_id, 1);
20732            assert_eq!(view_text(generator.energy_source), "other");
20733            assert_eq!(generator.active_power_mw, 72.3);
20734            assert!(generator.has_cost);
20735            assert!(generator.voltage_regulation_on);
20736            assert!(!generator.has_regulating_terminal);
20737            assert!(!generator.has_active_power_control);
20738            assert!(!generator.active_power_control.has_droop_percent);
20739            assert_eq!(generator.cost.model, 2);
20740            assert_eq!(generator.cost.ncost, 3);
20741            assert_eq!(generator.cost.coefficients.len, 3);
20742            let coefficients = std::slice::from_raw_parts(
20743                generator.cost.coefficients.data,
20744                generator.cost.coefficients.len,
20745            );
20746            assert_eq!(coefficients, &[0.11, 5.0, 150.0]);
20747
20748            let mut capability = std::mem::MaybeUninit::<PioGeneratorCapabilityView>::uninit();
20749            assert!(pio_balanced_network_generator_capability_at(
20750                network,
20751                0,
20752                0,
20753                capability.as_mut_ptr(),
20754                &mut error,
20755            ));
20756            let capability = capability.assume_init();
20757            assert_eq!(view_text(capability.name), "pc1");
20758            assert!(capability.has_value);
20759            assert_eq!(capability.value, 0.0);
20760
20761            let mut missing = std::mem::MaybeUninit::<PioBalancedBusView>::uninit();
20762            assert!(!pio_balanced_network_bus_at(
20763                network,
20764                9,
20765                missing.as_mut_ptr(),
20766                &mut error,
20767            ));
20768            assert_eq!(
20769                view_text(pio_error_code(error)),
20770                "BIND.CAPI.INDEX_OUT_OF_RANGE"
20771            );
20772            pio_error_release(error);
20773
20774            pio_balanced_network_release(network);
20775        }
20776    }
20777
20778    #[test]
20779    fn balanced_optional_row_fields_are_explicit() {
20780        unsafe {
20781            let mut network = BalancedNetwork::new("optional-fields", 100.0);
20782            network.buses_mut().push(powerio_tx::Bus::new(
20783                powerio_tx::BusId(1),
20784                powerio_tx::BusType::Ref,
20785                115.0,
20786            ));
20787            network.buses_mut().push(powerio_tx::Bus::new(
20788                powerio_tx::BusId(2),
20789                powerio_tx::BusType::Pq,
20790                115.0,
20791            ));
20792
20793            let mut load = powerio_tx::Load::new(powerio_tx::BusId(2), 6.0, 3.0);
20794            load.voltage_model = Some(powerio_tx::LoadVoltageModel::Zip {
20795                p_constant_power: 3.0,
20796                q_constant_power: 1.5,
20797                p_constant_current: 2.0,
20798                q_constant_current: 1.0,
20799                p_constant_impedance: 1.0,
20800                q_constant_impedance: 0.5,
20801                v_nom: Some(1.0),
20802                load_type: Some(7),
20803                scaling: Some(0.9),
20804            });
20805            network.loads_mut().push(load);
20806
20807            let mut shunt = powerio_tx::Shunt::new(powerio_tx::BusId(2), 0.0, 2.0);
20808            let mut control = powerio_tx::SwitchedShuntControl::new(
20809                powerio_tx::SwitchedShuntMode::Discrete,
20810                1.05,
20811                0.95,
20812                vec![powerio_tx::ShuntBlock::with_admittance(4, 0.25, 0.5)],
20813            );
20814            control.control_bus = Some(powerio_tx::BusId(1));
20815            shunt.control = Some(control);
20816            network.shunts_mut().push(shunt);
20817
20818            let mut branch =
20819                powerio_tx::Branch::new(powerio_tx::BusId(1), powerio_tx::BusId(2), 0.01, 0.1);
20820            branch.name = Some("controlled transformer".to_owned());
20821            branch.charging = Some(powerio_tx::BranchCharging::new(0.01, 0.02, 0.03, 0.04));
20822            branch
20823                .rating_sets
20824                .push(powerio_tx::BranchRatingSet::new("LTE", 175.0));
20825            branch.current_ratings = Some(powerio_tx::BranchCurrentRatings::new(1.0, 2.0, 3.0));
20826            branch.tap = 1.05;
20827            branch.shift = 4.0;
20828            branch.angmin = -30.0;
20829            branch.angmax = 30.0;
20830            let mut transformer_control = powerio_tx::TransformerControl::new(
20831                powerio_tx::TransformerControlMode::DcLineQuantity,
20832            );
20833            transformer_control.enabled = false;
20834            transformer_control.controlled_bus = Some(powerio_tx::BusId(2));
20835            transformer_control.controlled_bus_on_winding_side = true;
20836            transformer_control.regulating_terminal = Some(
20837                serde_json::from_value(serde_json::json!({
20838                    "equipment": {
20839                        "component_type": "transformer",
20840                        "local_id": "controlled-transformer"
20841                    },
20842                    "terminal": 2
20843                }))
20844                .unwrap(),
20845            );
20846            transformer_control.ntp = 17;
20847            transformer_control.winding_connection_angle = Some(12.5);
20848            branch.control = Some(transformer_control);
20849            network.branches_mut().push(branch);
20850
20851            let mut generator = powerio_tx::Generator::new(powerio_tx::BusId(1));
20852            generator.energy_source = powerio_tx::GeneratorEnergySource::Nuclear;
20853            generator.cost = Some(powerio_tx::GenCost::new(2, 10.0, 20.0, vec![1.0, 2.0]));
20854            generator.caps[8] = Some(25.0);
20855            generator.voltage_regulation_on = false;
20856            generator.regulating_terminal = Some(
20857                serde_json::from_value(serde_json::json!({
20858                    "equipment": {
20859                        "component_type": "load",
20860                        "local_id": "regulated-load"
20861                    },
20862                    "terminal": 1
20863                }))
20864                .unwrap(),
20865            );
20866            generator.regulated_bus = Some(powerio_tx::BusId(2));
20867            let mut generator_control = powerio_tx::ActivePowerControl::new(true);
20868            generator_control.droop_percent = Some(4.0);
20869            generator_control.participation_factor = Some(0.6);
20870            generator_control.minimum_target_active_power_mw = Some(10.0);
20871            generator_control.maximum_target_active_power_mw = Some(100.0);
20872            generator.active_power_control = Some(generator_control);
20873            network.generators_mut().push(generator);
20874
20875            let mut storage = powerio_tx::Storage::new(powerio_tx::BusId(2));
20876            storage.energy = 2.0;
20877            storage.energy_rating = 10.0;
20878            storage.current_rating = Some(100.0);
20879            let mut storage_control = powerio_tx::ActivePowerControl::new(false);
20880            storage_control.participation_factor = Some(0.4);
20881            storage.active_power_control = Some(storage_control);
20882            network.storage_mut().push(storage);
20883
20884            let module = module_handle(powerio::PioModule::new(PioValue::from(network)));
20885            let value = pio_module_value(module);
20886            let mut error = std::ptr::null_mut();
20887            let network = pio_value_balanced_network(value, &mut error);
20888
20889            let mut load = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
20890            assert!(pio_balanced_network_load_at(
20891                network,
20892                0,
20893                load.as_mut_ptr(),
20894                &mut error
20895            ));
20896            let load = load.assume_init();
20897            assert_eq!(view_text(load.voltage_model.kind), "zip");
20898            assert_eq!(load.voltage_model.p_constant_current_mw, 2.0);
20899            assert!(load.voltage_model.has_nominal_voltage);
20900            assert!(load.voltage_model.has_load_type);
20901            assert!(load.voltage_model.has_scaling);
20902
20903            let mut shunt = std::mem::MaybeUninit::<PioBalancedShuntView>::uninit();
20904            assert!(pio_balanced_network_shunt_at(
20905                network,
20906                0,
20907                shunt.as_mut_ptr(),
20908                &mut error
20909            ));
20910            let shunt = shunt.assume_init();
20911            assert!(!shunt.has_section_count);
20912            assert_eq!(shunt.section_count, 0);
20913            assert!(shunt.has_control);
20914            assert_eq!(view_text(shunt.control_mode), "discrete");
20915            assert_eq!(shunt.control_block_count, 1);
20916            assert!(shunt.has_control_bus);
20917            let mut block = std::mem::MaybeUninit::<PioShuntBlockView>::uninit();
20918            assert!(pio_balanced_network_shunt_block_at(
20919                network,
20920                0,
20921                0,
20922                block.as_mut_ptr(),
20923                &mut error,
20924            ));
20925            let block = block.assume_init();
20926            assert_eq!(block.steps, 4);
20927            assert_eq!(block.conductance_mw, 0.25);
20928            assert_eq!(block.susceptance_mvar, 0.5);
20929
20930            let mut branch = std::mem::MaybeUninit::<PioBalancedBranchView>::uninit();
20931            assert!(pio_balanced_network_branch_at(
20932                network,
20933                0,
20934                branch.as_mut_ptr(),
20935                &mut error,
20936            ));
20937            let branch = branch.assume_init();
20938            assert!(branch.has_name);
20939            assert_eq!(view_text(branch.name), "controlled transformer");
20940            assert!(branch.has_control);
20941            assert_eq!(view_text(branch.control.mode), "dc_line_quantity");
20942            assert!(!branch.control.enabled);
20943            assert!(branch.control.has_controlled_bus);
20944            assert_eq!(branch.control.controlled_bus_id, 2);
20945            assert!(branch.control.controlled_bus_on_winding_side);
20946            assert!(branch.control.has_regulating_terminal);
20947            assert_eq!(branch.control.regulating_terminal.terminal, 2);
20948            assert_eq!(branch.control.tap_position_count, 17);
20949            assert!(branch.control.has_winding_connection_angle);
20950            assert_eq!(branch.control.winding_connection_angle, 12.5);
20951            assert!(branch.terminal_charging_is_explicit);
20952            assert_eq!(branch.from_conductance_pu, 0.01);
20953            assert_eq!(branch.to_susceptance_pu, 0.04);
20954            assert!(branch.has_current_ratings);
20955            assert_eq!(branch.current_rating_c, 3.0);
20956            assert_eq!(branch.additional_rating_count, 1);
20957            let mut rating = std::mem::MaybeUninit::<PioBranchRatingView>::uninit();
20958            assert!(pio_balanced_network_branch_rating_at(
20959                network,
20960                0,
20961                0,
20962                rating.as_mut_ptr(),
20963                &mut error,
20964            ));
20965            let rating = rating.assume_init();
20966            assert_eq!(view_text(rating.name), "LTE");
20967            assert_eq!(rating.rate_mva, 175.0);
20968
20969            let mut generator = std::mem::MaybeUninit::<PioBalancedGeneratorView>::uninit();
20970            assert!(pio_balanced_network_generator_at(
20971                network,
20972                0,
20973                generator.as_mut_ptr(),
20974                &mut error,
20975            ));
20976            let generator = generator.assume_init();
20977            assert!(generator.has_regulated_bus);
20978            assert_eq!(generator.regulated_bus_id, 2);
20979            assert!(!generator.voltage_regulation_on);
20980            assert!(generator.has_regulating_terminal);
20981            assert_eq!(view_text(generator.energy_source), "nuclear");
20982            assert_eq!(
20983                view_text(generator.regulating_terminal.equipment.component_type),
20984                "load"
20985            );
20986            assert_eq!(
20987                view_text(generator.regulating_terminal.equipment.local_id),
20988                "regulated-load"
20989            );
20990            assert_eq!(generator.regulating_terminal.terminal, 1);
20991            assert!(generator.has_active_power_control);
20992            assert!(generator.active_power_control.participate);
20993            assert!(generator.active_power_control.has_droop_percent);
20994            assert_eq!(generator.active_power_control.droop_percent, 4.0);
20995            assert!(generator.active_power_control.has_participation_factor);
20996            assert_eq!(generator.active_power_control.participation_factor, 0.6);
20997            assert!(
20998                generator
20999                    .active_power_control
21000                    .has_minimum_target_active_power
21001            );
21002            assert_eq!(
21003                generator
21004                    .active_power_control
21005                    .minimum_target_active_power_mw,
21006                10.0
21007            );
21008            assert!(
21009                generator
21010                    .active_power_control
21011                    .has_maximum_target_active_power
21012            );
21013            assert_eq!(
21014                generator
21015                    .active_power_control
21016                    .maximum_target_active_power_mw,
21017                100.0
21018            );
21019            let mut capability = std::mem::MaybeUninit::<PioGeneratorCapabilityView>::uninit();
21020            assert!(pio_balanced_network_generator_capability_at(
21021                network,
21022                0,
21023                8,
21024                capability.as_mut_ptr(),
21025                &mut error,
21026            ));
21027            let capability = capability.assume_init();
21028            assert_eq!(view_text(capability.name), "ramp_30");
21029            assert!(capability.has_value);
21030            assert_eq!(capability.value, 25.0);
21031
21032            let mut storage = std::mem::MaybeUninit::<PioBalancedStorageView>::uninit();
21033            assert!(pio_balanced_network_storage_at(
21034                network,
21035                0,
21036                storage.as_mut_ptr(),
21037                &mut error,
21038            ));
21039            let storage = storage.assume_init();
21040            assert_eq!(storage.energy_mwh, 2.0);
21041            assert_eq!(storage.energy_rating_mwh, 10.0);
21042            assert!(storage.has_current_rating);
21043            assert_eq!(storage.current_rating, 100.0);
21044            assert!(storage.has_active_power_control);
21045            assert!(!storage.active_power_control.participate);
21046            assert!(!storage.active_power_control.has_droop_percent);
21047            assert!(storage.active_power_control.has_participation_factor);
21048            assert_eq!(storage.active_power_control.participation_factor, 0.4);
21049
21050            pio_balanced_network_release(network);
21051            pio_value_release(value);
21052            pio_module_release(module);
21053        }
21054    }
21055
21056    #[test]
21057    fn powsybl_balanced_tables_are_borrowed_without_serialization() {
21058        unsafe {
21059            let mut network = BalancedNetwork::new("powsybl-tables", 100.0);
21060            for (id, kind, kv) in [
21061                (1, powerio_tx::BusType::Ref, 400.0),
21062                (2, powerio_tx::BusType::Pq, 225.0),
21063                (3, powerio_tx::BusType::Pq, 63.0),
21064            ] {
21065                network
21066                    .buses_mut()
21067                    .push(powerio_tx::Bus::new(powerio_tx::BusId(id), kind, kv));
21068            }
21069
21070            let mut shunt = powerio_tx::Shunt::new(powerio_tx::BusId(2), 0.0, 12.0);
21071            shunt.uid = Some("SH".to_owned());
21072            shunt.section_count = Some(2);
21073            network.shunts_mut().push(shunt);
21074
21075            let mut svc = powerio_tx::StaticVarCompensator::new(powerio_tx::BusId(2), -0.02, 0.03);
21076            svc.uid = Some("SVC".to_owned());
21077            svc.regulating = true;
21078            svc.regulation_mode = powerio_tx::StaticVarCompensatorRegulationMode::ReactivePower;
21079            svc.reactive_power_setpoint_mvar = 12.0;
21080            svc.regulating_terminal = Some(
21081                serde_json::from_value(serde_json::json!({
21082                    "equipment": {
21083                        "component_type": "static_var_compensator",
21084                        "local_id": "SVC"
21085                    },
21086                    "terminal": 1
21087                }))
21088                .unwrap(),
21089            );
21090            network.static_var_compensators_mut().push(svc);
21091
21092            let mut switch =
21093                powerio_tx::Switch::new(powerio_tx::BusId(1), powerio_tx::BusId(2), true);
21094            switch.uid = Some("SW".to_owned());
21095            switch.thermal_rating = Some(500.0);
21096            switch.pf = Some(75.0);
21097            network.switches_mut().push(switch);
21098
21099            let mut hvdc = powerio_tx::Hvdc::new(powerio_tx::BusId(1), powerio_tx::BusId(2));
21100            hvdc.uid = Some("HVDC".to_owned());
21101            hvdc.resistance_ohm = Some(1.5);
21102            hvdc.nominal_voltage_kv = Some(320.0);
21103            hvdc.converters_mode =
21104                Some(powerio_tx::HvdcConvertersMode::Side1RectifierSide2Inverter);
21105            hvdc.converter1 = Some(
21106                serde_json::from_value(serde_json::json!({
21107                    "component": {
21108                        "component_type": "hvdc_converter",
21109                        "local_id": "C1"
21110                    },
21111                    "kind": "vsc",
21112                    "loss_factor_percent": 1.0,
21113                    "voltage_regulator_on": true,
21114                    "voltage_setpoint_kv": 400.0
21115                }))
21116                .unwrap(),
21117            );
21118            network.hvdc_mut().push(hvdc);
21119
21120            let mut windings = [
21121                powerio_tx::Winding::new(powerio_tx::BusId(1)),
21122                powerio_tx::Winding::new(powerio_tx::BusId(2)),
21123                powerio_tx::Winding::new(powerio_tx::BusId(3)),
21124            ];
21125            let mut winding_control =
21126                powerio_tx::TransformerControl::new(powerio_tx::TransformerControlMode::ActiveFlow);
21127            winding_control.controlled_bus = Some(powerio_tx::BusId(3));
21128            winding_control.regulating_terminal = Some(
21129                serde_json::from_value(serde_json::json!({
21130                    "equipment": {
21131                        "component_type": "transformer",
21132                        "local_id": "T3"
21133                    },
21134                    "terminal": 3
21135                }))
21136                .unwrap(),
21137            );
21138            winding_control.ntp = 21;
21139            windings[2].control = Some(winding_control);
21140            let mut transformer = powerio_tx::Transformer3W::new(
21141                windings,
21142                [
21143                    powerio_tx::Impedance::new(0.01, 0.1, 100.0),
21144                    powerio_tx::Impedance::new(0.02, 0.2, 100.0),
21145                    powerio_tx::Impedance::new(0.03, 0.3, 100.0),
21146                ],
21147            );
21148            transformer.uid = Some("T3".to_owned());
21149            transformer.name = Some("three winding".to_owned());
21150            network.transformers_3w_mut().push(transformer);
21151
21152            let mut area = powerio_tx::Area::new(7);
21153            area.uid = Some("A7".to_owned());
21154            area.name = Some("control area".to_owned());
21155            area.area_type = Some("control_area".to_owned());
21156            area.slack_bus = Some(powerio_tx::BusId(1));
21157            area.net_interchange = 12.5;
21158            network.areas_mut().push(area);
21159
21160            let module = module_handle(powerio::PioModule::new(PioValue::from(network)));
21161            let value = pio_module_value(module);
21162            let mut error = std::ptr::null_mut();
21163            let network = pio_value_balanced_network(value, &mut error);
21164            pio_value_release(value);
21165            pio_module_release(module);
21166
21167            assert_eq!(
21168                pio_balanced_network_static_var_compensator_count(network),
21169                1
21170            );
21171            assert_eq!(pio_balanced_network_shunt_count(network), 1);
21172            assert_eq!(pio_balanced_network_switch_count(network), 1);
21173            assert_eq!(pio_balanced_network_hvdc_count(network), 1);
21174            assert_eq!(
21175                pio_balanced_network_three_winding_transformer_count(network),
21176                1
21177            );
21178            assert_eq!(pio_balanced_network_area_count(network), 1);
21179
21180            let mut svc = std::mem::MaybeUninit::<PioBalancedStaticVarCompensatorView>::uninit();
21181            assert!(pio_balanced_network_static_var_compensator_at(
21182                network,
21183                0,
21184                svc.as_mut_ptr(),
21185                &mut error,
21186            ));
21187            let svc = svc.assume_init();
21188            assert_eq!(view_text(svc.component_id), "SVC");
21189            assert_eq!(view_text(svc.regulation_mode), "reactive_power");
21190            assert!(svc.has_regulating_terminal);
21191
21192            let mut shunt = std::mem::MaybeUninit::<PioBalancedShuntView>::uninit();
21193            assert!(pio_balanced_network_shunt_at(
21194                network,
21195                0,
21196                shunt.as_mut_ptr(),
21197                &mut error,
21198            ));
21199            let shunt = shunt.assume_init();
21200            assert!(shunt.has_section_count);
21201            assert_eq!(shunt.section_count, 2);
21202
21203            let mut switch = std::mem::MaybeUninit::<PioBalancedSwitchView>::uninit();
21204            assert!(pio_balanced_network_switch_at(
21205                network,
21206                0,
21207                switch.as_mut_ptr(),
21208                &mut error,
21209            ));
21210            let switch = switch.assume_init();
21211            assert!(switch.closed);
21212            assert_eq!(switch.thermal_rating_mva, 500.0);
21213            assert!(switch.has_from_active_power);
21214
21215            let mut hvdc = std::mem::MaybeUninit::<PioBalancedHvdcView>::uninit();
21216            assert!(pio_balanced_network_hvdc_at(
21217                network,
21218                0,
21219                hvdc.as_mut_ptr(),
21220                &mut error,
21221            ));
21222            let hvdc = hvdc.assume_init();
21223            assert!(hvdc.has_converter1);
21224            assert_eq!(view_text(hvdc.converter1.kind), "vsc");
21225            assert_eq!(hvdc.nominal_voltage_kv, 320.0);
21226
21227            let mut transformer =
21228                std::mem::MaybeUninit::<PioBalancedThreeWindingTransformerView>::uninit();
21229            assert!(pio_balanced_network_three_winding_transformer_at(
21230                network,
21231                0,
21232                transformer.as_mut_ptr(),
21233                &mut error,
21234            ));
21235            assert_eq!(transformer.assume_init().winding_count, 3);
21236            let mut winding =
21237                std::mem::MaybeUninit::<PioThreeWindingTransformerWindingView>::uninit();
21238            assert!(pio_balanced_network_three_winding_transformer_winding_at(
21239                network,
21240                0,
21241                2,
21242                winding.as_mut_ptr(),
21243                &mut error,
21244            ));
21245            let winding = winding.assume_init();
21246            assert_eq!(winding.bus_id, 3);
21247            assert!(winding.has_control);
21248            assert_eq!(view_text(winding.control.mode), "active_flow");
21249            assert!(winding.control.enabled);
21250            assert_eq!(winding.control.controlled_bus_id, 3);
21251            assert!(winding.control.has_regulating_terminal);
21252            assert_eq!(winding.control.regulating_terminal.terminal, 3);
21253            assert_eq!(winding.control.tap_position_count, 21);
21254
21255            let mut area = std::mem::MaybeUninit::<PioBalancedAreaView>::uninit();
21256            assert!(pio_balanced_network_area_at(
21257                network,
21258                0,
21259                area.as_mut_ptr(),
21260                &mut error,
21261            ));
21262            let area = area.assume_init();
21263            assert_eq!(view_text(area.component_id), "A7");
21264            assert_eq!(area.slack_bus_id, 1);
21265
21266            pio_balanced_network_release(network);
21267        }
21268    }
21269
21270    #[test]
21271    fn detailed_connectivity_tables_are_owner_rooted_views() {
21272        const HIERARCHY: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
21273<iidm:network xmlns:iidm="http://www.powsybl.org/schema/iidm/1_17" id="details" caseDate="2026-01-01T00:00:00Z" forecastDistance="0" sourceFormat="test" minimumValidationLevel="STEADY_STATE_HYPOTHESIS">
21274  <iidm:substation id="S" country="US" tso="MISO" geographicalTags="east">
21275    <iidm:voltageLevel id="VL1" nominalV="132" topologyKind="BUS_BREAKER"><iidm:busBreakerTopology><iidm:bus id="B1"/></iidm:busBreakerTopology></iidm:voltageLevel>
21276    <iidm:voltageLevel id="VL2" nominalV="33" topologyKind="BUS_BREAKER"><iidm:busBreakerTopology><iidm:bus id="B2"/></iidm:busBreakerTopology></iidm:voltageLevel>
21277    <iidm:voltageLevel id="VL3" nominalV="11" topologyKind="BUS_BREAKER"><iidm:busBreakerTopology><iidm:bus id="B3"/></iidm:busBreakerTopology></iidm:voltageLevel>
21278    <iidm:threeWindingsTransformer id="T3" ratedU0="132" ratedU1="132" ratedU2="33" ratedU3="11" r1="17.424" x1="34.848" r2="1.7424" x2="3.4848" r3="0.8712" x3="1.7424" bus1="B1" connectableBus1="B1" voltageLevelId1="VL1" bus2="B2" connectableBus2="B2" voltageLevelId2="VL2" bus3="B3" connectableBus3="B3" voltageLevelId3="VL3" selectedOperationalLimitsGroupIds1="normal">
21279      <iidm:ratioTapChanger2 tapPosition="0" lowTapPosition="0" loadTapChangingCapabilities="false"><iidm:step rho="1.05"/></iidm:ratioTapChanger2>
21280      <iidm:operationalLimitsGroup1 id="normal"><iidm:apparentPowerLimits permanentLimit="90"><iidm:temporaryLimit name="emergency" acceptableDuration="600" value="100"/></iidm:apparentPowerLimits></iidm:operationalLimitsGroup1>
21281    </iidm:threeWindingsTransformer>
21282  </iidm:substation>
21283</iidm:network>"#;
21284
21285        unsafe {
21286            let module = parse_xiidm_text(HIERARCHY);
21287            let mut identified_network = match &PioModule::get(module).unwrap().module.value() {
21288                PioValue::BalancedNetwork(network) => network.clone(),
21289                value => panic!("expected balanced network, got {}", value.type_name()),
21290            };
21291            let identified_details = Arc::make_mut(
21292                identified_network
21293                    .detailed_connectivity_mut()
21294                    .as_mut()
21295                    .unwrap(),
21296            );
21297            identified_details.terminals[0].component =
21298                Some(ComponentId::new("terminal", "terminal-T3-1").unwrap());
21299            identified_details.tap_changers[0].component =
21300                Some(ComponentId::new("tap_changer", "tap-T3-2").unwrap());
21301            let value = pio_module_value(module);
21302            let mut error = std::ptr::null_mut();
21303            let network = pio_value_balanced_network(value, &mut error);
21304            assert!(pio_balanced_network_has_detailed_connectivity(network));
21305            let details = pio_balanced_network_detailed_connectivity(network);
21306            assert!(!details.is_null());
21307            pio_value_release(value);
21308            pio_module_release(module);
21309            pio_balanced_network_release(network);
21310
21311            let mut counts = std::mem::MaybeUninit::<PioDetailedConnectivityCountsView>::uninit();
21312            assert!(pio_detailed_connectivity_counts(
21313                details,
21314                counts.as_mut_ptr(),
21315                &mut error,
21316            ));
21317            let counts = counts.assume_init();
21318            assert_eq!(counts.substations, 1);
21319            assert_eq!(counts.voltage_levels, 3);
21320            assert_eq!(counts.terminals, 3);
21321            assert_eq!(counts.operational_limit_groups, 1);
21322            assert_eq!(counts.tap_changers, 1);
21323
21324            let mut substation = std::mem::MaybeUninit::<PioSubstationView>::uninit();
21325            assert!(pio_detailed_connectivity_substation_at(
21326                details,
21327                0,
21328                substation.as_mut_ptr(),
21329                &mut error,
21330            ));
21331            let substation = substation.assume_init();
21332            assert_eq!(view_text(substation.component.local_id), "S");
21333            assert_eq!(substation.geographical_tag_count, 1);
21334
21335            let mut level = std::mem::MaybeUninit::<PioVoltageLevelView>::uninit();
21336            assert!(pio_detailed_connectivity_voltage_level_at(
21337                details,
21338                0,
21339                level.as_mut_ptr(),
21340                &mut error,
21341            ));
21342            let level = level.assume_init();
21343            assert_eq!(view_text(level.topology_kind), "bus_breaker");
21344            assert_eq!(level.nominal_voltage_kv, 132.0);
21345
21346            let mut terminal = std::mem::MaybeUninit::<PioDetailedTerminalView>::uninit();
21347            assert!(pio_detailed_connectivity_terminal_at(
21348                details,
21349                0,
21350                terminal.as_mut_ptr(),
21351                &mut error,
21352            ));
21353            let terminal = terminal.assume_init();
21354            assert!(terminal.connected);
21355            assert!(!terminal.has_component);
21356
21357            let mut limit = std::mem::MaybeUninit::<PioOperationalLimitGroupView>::uninit();
21358            assert!(pio_detailed_connectivity_operational_limit_group_at(
21359                details,
21360                0,
21361                limit.as_mut_ptr(),
21362                &mut error,
21363            ));
21364            let limit = limit.assume_init();
21365            assert!(limit.has_apparent_power_limits);
21366            assert_eq!(limit.apparent_power_permanent_limit_mva, 90.0);
21367            assert_eq!(limit.apparent_power_temporary_limit_count, 1);
21368
21369            let mut changer = std::mem::MaybeUninit::<PioTapChangerView>::uninit();
21370            assert!(pio_detailed_connectivity_tap_changer_at(
21371                details,
21372                0,
21373                changer.as_mut_ptr(),
21374                &mut error,
21375            ));
21376            let changer = changer.assume_init();
21377            assert!(!changer.has_component);
21378            assert_eq!(view_text(changer.kind), "ratio");
21379            assert!(changer.has_tap_position);
21380            assert_eq!(changer.tap_position, 0);
21381            assert_eq!(changer.step_count, 1);
21382
21383            pio_detailed_connectivity_release(details);
21384
21385            let module = module_handle(powerio::PioModule::new(PioValue::BalancedNetwork(
21386                identified_network,
21387            )));
21388            let value = pio_module_value(module);
21389            let network = pio_value_balanced_network(value, &mut error);
21390            let details = pio_balanced_network_detailed_connectivity(network);
21391            pio_value_release(value);
21392            pio_module_release(module);
21393            pio_balanced_network_release(network);
21394
21395            let mut terminal = std::mem::MaybeUninit::<PioDetailedTerminalView>::uninit();
21396            assert!(pio_detailed_connectivity_terminal_at(
21397                details,
21398                0,
21399                terminal.as_mut_ptr(),
21400                &mut error,
21401            ));
21402            let terminal = terminal.assume_init();
21403            assert!(terminal.has_component);
21404            assert_eq!(view_text(terminal.component.component_type), "terminal");
21405            assert_eq!(view_text(terminal.component.local_id), "terminal-T3-1");
21406
21407            let mut changer = std::mem::MaybeUninit::<PioTapChangerView>::uninit();
21408            assert!(pio_detailed_connectivity_tap_changer_at(
21409                details,
21410                0,
21411                changer.as_mut_ptr(),
21412                &mut error,
21413            ));
21414            let changer = changer.assume_init();
21415            assert!(changer.has_component);
21416            assert_eq!(view_text(changer.component.component_type), "tap_changer");
21417            assert_eq!(view_text(changer.component.local_id), "tap-T3-2");
21418            pio_detailed_connectivity_release(details);
21419
21420            let without_assigned_tap = HIERARCHY.replace(r#" tapPosition="0""#, "");
21421            let module = parse_xiidm_text(&without_assigned_tap);
21422            let value = pio_module_value(module);
21423            let network = pio_value_balanced_network(value, &mut error);
21424            let details = pio_balanced_network_detailed_connectivity(network);
21425            let mut changer = std::mem::MaybeUninit::<PioTapChangerView>::uninit();
21426            assert!(pio_detailed_connectivity_tap_changer_at(
21427                details,
21428                0,
21429                changer.as_mut_ptr(),
21430                &mut error,
21431            ));
21432            let changer = changer.assume_init();
21433            assert!(!changer.has_tap_position);
21434            assert_eq!(changer.tap_position, 0);
21435            pio_detailed_connectivity_release(details);
21436            pio_balanced_network_release(network);
21437            pio_value_release(value);
21438            pio_module_release(module);
21439        }
21440    }
21441
21442    #[test]
21443    fn subnetworks_boundary_lines_and_tie_lines_are_owner_rooted_views() {
21444        const XIIDM: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
21445<iidm:network xmlns:iidm="http://www.powsybl.org/schema/iidm/1_17" id="Merged" caseDate="2026-01-01T00:00:00Z" forecastDistance="0" sourceFormat="root" minimumValidationLevel="STEADY_STATE_HYPOTHESIS">
21446  <iidm:network id="A" caseDate="2026-01-01T01:00:00Z" forecastDistance="1" sourceFormat="part-a" minimumValidationLevel="STEADY_STATE_HYPOTHESIS">
21447    <iidm:substation id="SA"><iidm:voltageLevel id="VA" nominalV="100" topologyKind="BUS_BREAKER"><iidm:busBreakerTopology><iidm:bus id="BA"/></iidm:busBreakerTopology><iidm:boundaryLine id="DLA" p0="5" q0="6" r="1" x="2" generationVoltageRegulationOn="true" generationMinP="0" generationMaxP="20" generationTargetP="10" generationTargetV="100" bus="BA" connectableBus="BA"><iidm:reactiveCapabilityCurve><iidm:property name="owner" value="RTE"/><iidm:point p="0" minQ="-10" maxQ="10"/><iidm:point p="10" minQ="0" maxQ="20"/></iidm:reactiveCapabilityCurve></iidm:boundaryLine></iidm:voltageLevel></iidm:substation>
21448  </iidm:network>
21449  <iidm:network id="B" caseDate="2026-01-01T02:00:00Z" forecastDistance="2" sourceFormat="part-b" minimumValidationLevel="STEADY_STATE_HYPOTHESIS">
21450    <iidm:substation id="SB"><iidm:voltageLevel id="VB" nominalV="100" topologyKind="BUS_BREAKER"><iidm:busBreakerTopology><iidm:bus id="BB"/></iidm:busBreakerTopology><iidm:boundaryLine id="DLB" p0="-5" q0="-6" r="3" x="4" bus="BB" connectableBus="BB"/></iidm:voltageLevel></iidm:substation>
21451  </iidm:network>
21452  <iidm:tieLine id="TL" boundaryLineId1="DLA" boundaryLineId2="DLB"/>
21453</iidm:network>"#;
21454
21455        unsafe {
21456            let module = parse_xiidm_text(XIIDM);
21457            let value = pio_module_value(module);
21458            let mut error = std::ptr::null_mut();
21459            let network = pio_value_balanced_network(value, &mut error);
21460            let details = pio_balanced_network_detailed_connectivity(network);
21461            pio_value_release(value);
21462            pio_module_release(module);
21463            pio_balanced_network_release(network);
21464
21465            let mut counts = std::mem::MaybeUninit::<PioDetailedConnectivityCountsView>::uninit();
21466            assert!(pio_detailed_connectivity_counts(
21467                details,
21468                counts.as_mut_ptr(),
21469                &mut error,
21470            ));
21471            let counts = counts.assume_init();
21472            assert_eq!(counts.subnetworks, 2);
21473            assert_eq!(counts.boundary_lines, 2);
21474            assert_eq!(counts.tie_lines, 1);
21475
21476            let mut subnetwork = std::mem::MaybeUninit::<PioSubnetworkView>::uninit();
21477            assert!(pio_detailed_connectivity_subnetwork_at(
21478                details,
21479                0,
21480                subnetwork.as_mut_ptr(),
21481                &mut error,
21482            ));
21483            let subnetwork = subnetwork.assume_init();
21484            assert_eq!(view_text(subnetwork.component.local_id), "A");
21485            assert_eq!(view_text(subnetwork.parent.local_id), "Merged");
21486            assert!(subnetwork.case_metadata.has_forecast_distance);
21487            assert_eq!(subnetwork.case_metadata.forecast_distance, 1);
21488            assert_eq!(
21489                view_text(subnetwork.case_metadata.source_model_format),
21490                "part-a"
21491            );
21492            assert!(subnetwork.component_count >= 4);
21493
21494            let mut member = std::mem::MaybeUninit::<PioComponentIdView>::uninit();
21495            assert!(pio_detailed_connectivity_subnetwork_component_at(
21496                details,
21497                0,
21498                0,
21499                member.as_mut_ptr(),
21500                &mut error,
21501            ));
21502            assert_ne!(view_text(member.assume_init().local_id), "");
21503
21504            let mut boundary = std::mem::MaybeUninit::<PioBoundaryLineView>::uninit();
21505            assert!(pio_detailed_connectivity_boundary_line_at(
21506                details,
21507                0,
21508                boundary.as_mut_ptr(),
21509                &mut error,
21510            ));
21511            let boundary = boundary.assume_init();
21512            assert_eq!(view_text(boundary.component.local_id), "DLA");
21513            assert_eq!(boundary.active_power_setpoint_mw, 5.0);
21514            assert!(boundary.has_generation);
21515            assert!(boundary.generation.voltage_regulation_on);
21516            assert_eq!(boundary.generation.target_active_power_mw, 10.0);
21517            assert!(boundary.generation.has_reactive_limits);
21518            assert_eq!(
21519                view_text(boundary.generation.reactive_limits.kind),
21520                "capability_curve"
21521            );
21522            assert_eq!(boundary.generation.reactive_limits.point_count, 2);
21523
21524            let mut property = std::mem::MaybeUninit::<PioStringPropertyView>::uninit();
21525            assert!(
21526                pio_detailed_connectivity_boundary_line_reactive_limit_property_at(
21527                    details,
21528                    0,
21529                    0,
21530                    property.as_mut_ptr(),
21531                    &mut error,
21532                )
21533            );
21534            let property = property.assume_init();
21535            assert_eq!(view_text(property.name), "owner");
21536            assert_eq!(view_text(property.value), "RTE");
21537
21538            let mut point = std::mem::MaybeUninit::<PioReactiveCapabilityCurvePointView>::uninit();
21539            assert!(
21540                pio_detailed_connectivity_boundary_line_reactive_capability_point_at(
21541                    details,
21542                    0,
21543                    1,
21544                    point.as_mut_ptr(),
21545                    &mut error,
21546                )
21547            );
21548            let point = point.assume_init();
21549            assert_eq!(point.active_power_mw, 10.0);
21550            assert_eq!(point.maximum_reactive_power_mvar, 20.0);
21551
21552            let mut tie = std::mem::MaybeUninit::<PioTieLineView>::uninit();
21553            assert!(pio_detailed_connectivity_tie_line_at(
21554                details,
21555                0,
21556                tie.as_mut_ptr(),
21557                &mut error,
21558            ));
21559            let tie = tie.assume_init();
21560            assert_eq!(view_text(tie.component.local_id), "TL");
21561            assert_eq!(view_text(tie.boundary_line1.local_id), "DLA");
21562            assert_eq!(view_text(tie.boundary_line2.local_id), "DLB");
21563            assert!(tie.has_calculation_branch);
21564
21565            pio_detailed_connectivity_release(details);
21566        }
21567    }
21568
21569    #[test]
21570    fn connectivity_node_numbers_are_optional_borrowed_fields() {
21571        const XIIDM: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
21572<iidm:network xmlns:iidm="http://www.powsybl.org/schema/iidm/1_17" id="nodes" caseDate="2025-01-01T00:00:00Z" forecastDistance="0" sourceFormat="test" minimumValidationLevel="STEADY_STATE_HYPOTHESIS">
21573  <iidm:substation id="S">
21574    <iidm:voltageLevel id="VL" nominalV="110" topologyKind="NODE_BREAKER">
21575      <iidm:nodeBreakerTopology>
21576        <iidm:bus v="110" angle="0" nodes="0,1,2"/>
21577        <iidm:busbarSection id="BBS" node="2"/>
21578        <iidm:switch id="BR" kind="BREAKER" open="false" node1="1" node2="2"/>
21579        <iidm:internalConnection node1="0" node2="1"/>
21580      </iidm:nodeBreakerTopology>
21581      <iidm:generator id="G" energySource="OTHER" minP="0" maxP="10" voltageRegulatorOn="true" targetP="5" node="0"><iidm:minMaxReactiveLimits minQ="-2" maxQ="2"/></iidm:generator>
21582    </iidm:voltageLevel>
21583  </iidm:substation>
21584</iidm:network>"#;
21585
21586        unsafe {
21587            let module = parse_xiidm_text(XIIDM);
21588            let value = pio_module_value(module);
21589            let mut error = std::ptr::null_mut();
21590            let network = pio_value_balanced_network(value, &mut error);
21591            let details = pio_balanced_network_detailed_connectivity(network);
21592
21593            let mut numbers = Vec::new();
21594            for index in 0..3 {
21595                let mut node = std::mem::MaybeUninit::<PioConnectivityNodeView>::uninit();
21596                assert!(pio_detailed_connectivity_node_at(
21597                    details,
21598                    index,
21599                    node.as_mut_ptr(),
21600                    &mut error,
21601                ));
21602                let node = node.assume_init();
21603                assert!(node.has_node_number);
21604                assert!(node.has_calculated_bus);
21605                numbers.push(node.node_number);
21606            }
21607            numbers.sort_unstable();
21608            assert_eq!(numbers, [0, 1, 2]);
21609
21610            pio_detailed_connectivity_release(details);
21611            pio_balanced_network_release(network);
21612            pio_value_release(value);
21613            pio_module_release(module);
21614        }
21615    }
21616
21617    #[test]
21618    fn omitted_fields_and_equipment_reactive_limits_are_typed_views() {
21619        const XIIDM: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
21620<iidm:network xmlns:iidm="http://www.powsybl.org/schema/iidm/equipment/1_12" id="equipment" caseDate="2021-01-03T00:00:00Z" forecastDistance="0" sourceFormat="test" minimumValidationLevel="EQUIPMENT">
21621  <iidm:substation id="S"><iidm:voltageLevel id="VL" nominalV="225" topologyKind="NODE_BREAKER">
21622    <iidm:nodeBreakerTopology><iidm:busbarSection id="BBS" node="0"/></iidm:nodeBreakerTopology>
21623    <iidm:generator id="G" energySource="SOLAR" minP="0" maxP="100" voltageRegulatorOn="true" node="0">
21624      <iidm:reactiveCapabilityCurve><iidm:property name="curve" value="retained"/><iidm:point p="0" minQ="-20" maxQ="20"><iidm:property name="point" value="first"/></iidm:point><iidm:point p="100" minQ="-10" maxQ="10"/></iidm:reactiveCapabilityCurve>
21625    </iidm:generator>
21626  </iidm:voltageLevel></iidm:substation>
21627</iidm:network>"#;
21628
21629        unsafe {
21630            let module = parse_xiidm_text(XIIDM);
21631            let value = pio_module_value(module);
21632            let mut error = std::ptr::null_mut();
21633            let network = pio_value_balanced_network(value, &mut error);
21634            let details = pio_balanced_network_detailed_connectivity(network);
21635
21636            let mut counts = std::mem::MaybeUninit::<PioDetailedConnectivityCountsView>::uninit();
21637            assert!(pio_detailed_connectivity_counts(
21638                details,
21639                counts.as_mut_ptr(),
21640                &mut error,
21641            ));
21642            let counts = counts.assume_init();
21643            assert_eq!(counts.omitted_fields, 4);
21644            assert_eq!(counts.equipment_reactive_limits, 1);
21645
21646            let mut omitted = std::mem::MaybeUninit::<PioOmittedFieldView>::uninit();
21647            assert!(pio_detailed_connectivity_omitted_field_at(
21648                details,
21649                2,
21650                omitted.as_mut_ptr(),
21651                &mut error,
21652            ));
21653            let omitted = omitted.assume_init();
21654            assert_eq!(view_text(omitted.component.local_id), "G");
21655            assert_eq!(view_text(omitted.field), "voltage_setpoint");
21656
21657            let mut limits = std::mem::MaybeUninit::<PioEquipmentReactiveLimitsView>::uninit();
21658            assert!(pio_detailed_connectivity_equipment_reactive_limits_at(
21659                details,
21660                0,
21661                limits.as_mut_ptr(),
21662                &mut error,
21663            ));
21664            let limits = limits.assume_init();
21665            assert_eq!(view_text(limits.equipment.local_id), "G");
21666            assert_eq!(view_text(limits.limits.kind), "capability_curve");
21667            assert_eq!(limits.limits.property_count, 1);
21668            assert_eq!(limits.limits.point_count, 2);
21669
21670            let mut point = std::mem::MaybeUninit::<PioReactiveCapabilityCurvePointView>::uninit();
21671            assert!(
21672                pio_detailed_connectivity_equipment_reactive_capability_point_at(
21673                    details,
21674                    0,
21675                    0,
21676                    point.as_mut_ptr(),
21677                    &mut error,
21678                )
21679            );
21680            let point = point.assume_init();
21681            assert_eq!(point.minimum_reactive_power_mvar, -20.0);
21682            assert_eq!(point.property_count, 1);
21683
21684            let mut property = std::mem::MaybeUninit::<PioStringPropertyView>::uninit();
21685            assert!(
21686                pio_detailed_connectivity_equipment_reactive_capability_point_property_at(
21687                    details,
21688                    0,
21689                    0,
21690                    0,
21691                    property.as_mut_ptr(),
21692                    &mut error,
21693                )
21694            );
21695            let property = property.assume_init();
21696            assert_eq!(view_text(property.name), "point");
21697            assert_eq!(view_text(property.value), "first");
21698
21699            let mut generator = std::mem::MaybeUninit::<PioBalancedGeneratorView>::uninit();
21700            assert!(pio_balanced_network_generator_at(
21701                network,
21702                0,
21703                generator.as_mut_ptr(),
21704                &mut error,
21705            ));
21706            assert_eq!(view_text(generator.assume_init().energy_source), "solar");
21707
21708            pio_detailed_connectivity_release(details);
21709            pio_balanced_network_release(network);
21710            pio_value_release(value);
21711            pio_module_release(module);
21712        }
21713    }
21714
21715    #[test]
21716    fn detailed_dc_and_converter_records_are_typed_views() {
21717        const DC: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
21718<iidm:network xmlns:iidm="http://www.powsybl.org/schema/iidm/1_17" id="dc" caseDate="2026-01-01T00:00:00Z" forecastDistance="0" sourceFormat="test" minimumValidationLevel="STEADY_STATE_HYPOTHESIS">
21719  <iidm:dcNode id="N1" nominalV="500" v="498"/><iidm:dcNode id="N2" nominalV="500"/>
21720  <iidm:dcSwitch id="S" dcNode1="N1" dcNode2="N2" kind="DISCONNECTOR" open="true" r="0.9"/>
21721  <iidm:dcGround id="G" dcNode="N1" r="0.1" connected="false"/>
21722  <iidm:dcLine id="L" dcNode1="N1" dcNode2="N2" r="4" connected1="true" connected2="true" dcP1="100" dcI1="200" dcP2="-98" dcI2="-195"/>
21723  <iidm:substation id="SUB"><iidm:voltageLevel id="VL" nominalV="400" topologyKind="BUS_BREAKER">
21724    <iidm:busBreakerTopology><iidm:bus id="B1"/><iidm:bus id="B2"/></iidm:busBreakerTopology>
21725    <iidm:voltageSourceConverter id="VSC" dcNode1="N1" dcConnected1="true" dcNode2="N2" dcConnected2="false" idleLoss="2" switchingLoss="0.2" resistiveLoss="0.000002" controlMode="P_PCC_DROOP" targetP="301" targetVdc="502" bus1="B1" connectableBus1="B1" bus2="B2" connectableBus2="B2" voltageRegulatorOn="true" voltageSetpoint="397"><iidm:pccTerminal id="VSC" number="ONE"/><iidm:droopCurve><iidm:segment minV="-100" maxV="100" k="-5"/></iidm:droopCurve><iidm:reactiveCapabilityCurve><iidm:property name="curve" value="retained"/><iidm:point p="-200" minQ="-190" maxQ="192"><iidm:property name="point" value="one"/></iidm:point><iidm:point p="200" minQ="-189" maxQ="191"/></iidm:reactiveCapabilityCurve></iidm:voltageSourceConverter>
21726  </iidm:voltageLevel></iidm:substation>
21727</iidm:network>"#;
21728
21729        unsafe {
21730            let module = parse_xiidm_text(DC);
21731            let value = pio_module_value(module);
21732            let mut error = std::ptr::null_mut();
21733            let network = pio_value_balanced_network(value, &mut error);
21734            let details = pio_balanced_network_detailed_connectivity(network);
21735
21736            let mut counts = std::mem::MaybeUninit::<PioDetailedConnectivityCountsView>::uninit();
21737            assert!(pio_detailed_connectivity_counts(
21738                details,
21739                counts.as_mut_ptr(),
21740                &mut error,
21741            ));
21742            let counts = counts.assume_init();
21743            assert_eq!(counts.dc_nodes, 2);
21744            assert_eq!(counts.dc_grounds, 1);
21745            assert_eq!(counts.dc_lines, 1);
21746            assert_eq!(counts.dc_switches, 1);
21747            assert_eq!(counts.voltage_source_converters, 1);
21748
21749            let mut line = std::mem::MaybeUninit::<PioDcEquipmentView>::uninit();
21750            assert!(pio_detailed_connectivity_dc_line_at(
21751                details,
21752                0,
21753                line.as_mut_ptr(),
21754                &mut error,
21755            ));
21756            let line = line.assume_init();
21757            assert_eq!(view_text(line.kind), "line");
21758            assert_eq!(line.terminal_count, 2);
21759            assert_eq!(line.resistance_ohm, 4.0);
21760            assert_eq!(line.terminal1.current_a, 200.0);
21761
21762            let mut converter = std::mem::MaybeUninit::<PioAcDcConverterView>::uninit();
21763            assert!(pio_detailed_connectivity_voltage_source_converter_at(
21764                details,
21765                0,
21766                converter.as_mut_ptr(),
21767                &mut error,
21768            ));
21769            let converter = converter.assume_init();
21770            assert_eq!(view_text(converter.kind), "voltage_source");
21771            assert_eq!(
21772                view_text(converter.control_mode),
21773                "active_power_at_pcc_and_dc_voltage_droop_curve"
21774            );
21775            assert!(converter.voltage_regulator_on);
21776            assert_eq!(converter.idle_loss_mw, 2.0);
21777            assert!(converter.has_switching_loss);
21778            assert_eq!(converter.switching_loss_mw_per_ampere, 0.2);
21779            assert!(converter.has_resistive_loss);
21780            assert!(converter.has_pcc_terminal);
21781            assert_eq!(converter.pcc_terminal.terminal, 1);
21782            assert!(converter.has_reactive_limits);
21783            assert_eq!(
21784                view_text(converter.reactive_limits.kind),
21785                "capability_curve"
21786            );
21787            assert_eq!(converter.droop_curve_segment_count, 1);
21788            assert!(converter.has_droop_curve);
21789
21790            let mut segment = std::mem::MaybeUninit::<PioDroopCurveSegmentView>::uninit();
21791            assert!(
21792                pio_detailed_connectivity_voltage_source_converter_droop_curve_segment_at(
21793                    details,
21794                    0,
21795                    0,
21796                    segment.as_mut_ptr(),
21797                    &mut error,
21798                )
21799            );
21800            let segment = segment.assume_init();
21801            assert_eq!(segment.minimum_voltage_kv, -100.0);
21802            assert_eq!(segment.maximum_voltage_kv, 100.0);
21803            assert_eq!(segment.k, -5.0);
21804
21805            let mut point = std::mem::MaybeUninit::<PioReactiveCapabilityCurvePointView>::uninit();
21806            assert!(
21807                pio_detailed_connectivity_voltage_source_converter_reactive_capability_point_at(
21808                    details,
21809                    0,
21810                    0,
21811                    point.as_mut_ptr(),
21812                    &mut error,
21813                )
21814            );
21815            let point = point.assume_init();
21816            assert_eq!(point.active_power_mw, -200.0);
21817            assert_eq!(point.property_count, 1);
21818
21819            let base_network = match &PioModule::get(module).unwrap().module.value() {
21820                PioValue::BalancedNetwork(network) => network.clone(),
21821                value => panic!("expected balanced network, got {}", value.type_name()),
21822            };
21823
21824            pio_detailed_connectivity_release(details);
21825            pio_balanced_network_release(network);
21826            pio_value_release(value);
21827            pio_module_release(module);
21828
21829            let mut empty_curve_network = base_network.clone();
21830            std::sync::Arc::make_mut(
21831                empty_curve_network
21832                    .detailed_connectivity_mut()
21833                    .as_mut()
21834                    .unwrap(),
21835            )
21836            .voltage_source_converters[0]
21837                .droop_curve = Some(serde_json::from_str(r#"{"segments":[]}"#).unwrap());
21838            let mut absent_curve_network = base_network;
21839            std::sync::Arc::make_mut(
21840                absent_curve_network
21841                    .detailed_connectivity_mut()
21842                    .as_mut()
21843                    .unwrap(),
21844            )
21845            .voltage_source_converters[0]
21846                .droop_curve = None;
21847
21848            for (network, has_droop_curve) in
21849                [(empty_curve_network, true), (absent_curve_network, false)]
21850            {
21851                let module = module_handle(powerio::PioModule::new(PioValue::from(network)));
21852                let value = pio_module_value(module);
21853                let network = pio_value_balanced_network(value, &mut error);
21854                let details = pio_balanced_network_detailed_connectivity(network);
21855                let mut converter = std::mem::MaybeUninit::<PioAcDcConverterView>::uninit();
21856                assert!(pio_detailed_connectivity_voltage_source_converter_at(
21857                    details,
21858                    0,
21859                    converter.as_mut_ptr(),
21860                    &mut error,
21861                ));
21862                let converter = converter.assume_init();
21863                assert_eq!(converter.droop_curve_segment_count, 0);
21864                assert_eq!(converter.has_droop_curve, has_droop_curve);
21865                pio_detailed_connectivity_release(details);
21866                pio_balanced_network_release(network);
21867                pio_value_release(value);
21868                pio_module_release(module);
21869            }
21870        }
21871    }
21872
21873    #[test]
21874    fn balanced_calculation_instances_expose_typed_inputs() {
21875        unsafe {
21876            let network = case9_network();
21877            let dc_pf = powerio_prob::DcPfInstance::from_network(network.clone()).unwrap();
21878            let dc_pf_module =
21879                module_handle(powerio::PioModule::new(PioValue::DcPfInstance(dc_pf)));
21880            let dc_pf_value = pio_module_value(dc_pf_module);
21881            let mut error = std::ptr::null_mut();
21882            let dc_pf = pio_value_dc_pf_instance(dc_pf_value, &mut error);
21883            assert!(!dc_pf.is_null(), "{}", error_text(error));
21884            assert_eq!(pio_dc_pf_instance_bus_specification_count(dc_pf), 9);
21885            assert_eq!(
21886                view_text(pio_dc_pf_instance_branch_susceptance_formula(dc_pf)),
21887                "series_susceptance"
21888            );
21889            assert!(!pio_calculation_instance_has_initial_point(dc_pf));
21890            assert!(pio_calculation_instance_initial_point(dc_pf, &mut error).is_null());
21891            assert!(error.is_null());
21892
21893            let mut reference = std::mem::MaybeUninit::<PioDcBusSpecificationView>::uninit();
21894            assert!(pio_dc_pf_instance_bus_specification_at(
21895                dc_pf,
21896                0,
21897                reference.as_mut_ptr(),
21898                &mut error,
21899            ));
21900            let reference = reference.assume_init();
21901            assert_eq!(reference.bus_id, 1);
21902            assert_eq!(view_text(reference.kind), "reference");
21903            assert_eq!(reference.voltage_angle_degrees, 0.0);
21904
21905            let mut load_bus = std::mem::MaybeUninit::<PioDcBusSpecificationView>::uninit();
21906            assert!(pio_dc_pf_instance_bus_specification_at(
21907                dc_pf,
21908                4,
21909                load_bus.as_mut_ptr(),
21910                &mut error,
21911            ));
21912            let load_bus = load_bus.assume_init();
21913            assert_eq!(load_bus.bus_id, 5);
21914            assert_eq!(view_text(load_bus.kind), "net_active_power");
21915            assert_eq!(load_bus.net_active_power_mw, -90.0);
21916
21917            pio_calculation_instance_release(dc_pf);
21918            pio_value_release(dc_pf_value);
21919            pio_module_release(dc_pf_module);
21920
21921            let initial = powerio_prob::BalancedOperatingPointBuilder::for_point(network.clone())
21922                .bus_voltage_magnitudes(vec![1.01; network.buses().len()])
21923                .build_point()
21924                .unwrap();
21925            let dc_pf = powerio_prob::DcPfInstance::from_network(network.clone())
21926                .unwrap()
21927                .with_initial_point(initial);
21928            let dc_pf_module =
21929                module_handle(powerio::PioModule::new(PioValue::DcPfInstance(dc_pf)));
21930            let dc_pf_value = pio_module_value(dc_pf_module);
21931            let dc_pf = pio_value_dc_pf_instance(dc_pf_value, &mut error);
21932            let initial = pio_calculation_instance_initial_point(dc_pf, &mut error);
21933            assert!(!initial.is_null(), "{}", error_text(error));
21934            assert_eq!(
21935                view_text(pio_operating_point_type_name(initial)),
21936                "powerio.OperatingPoint<powerio.BalancedNetwork>"
21937            );
21938            pio_calculation_instance_release(dc_pf);
21939            pio_value_release(dc_pf_value);
21940            pio_module_release(dc_pf_module);
21941            let mut initial_vm = 0.0;
21942            assert!(pio_operating_point_get_value(
21943                initial,
21944                c"bus_voltage_magnitude".as_ptr(),
21945                "bus_voltage_magnitude".len(),
21946                c"1".as_ptr(),
21947                1,
21948                &mut initial_vm,
21949                &mut error,
21950            ));
21951            assert_eq!(initial_vm, 1.01);
21952            let initial_network = pio_operating_point_balanced_network(initial, &mut error);
21953            assert_eq!(pio_balanced_network_bus_count(initial_network), 9);
21954            pio_balanced_network_release(initial_network);
21955            pio_operating_point_release(initial);
21956
21957            let ac_pf = powerio_prob::AcPfInstance::from_network(network.clone()).unwrap();
21958            let ac_pf_module =
21959                module_handle(powerio::PioModule::new(PioValue::AcPfInstance(ac_pf)));
21960            let ac_pf_value = pio_module_value(ac_pf_module);
21961            let ac_pf = pio_value_ac_pf_instance(ac_pf_value, &mut error);
21962            let mut pv = std::mem::MaybeUninit::<PioAcBusSpecificationView>::uninit();
21963            assert!(pio_ac_pf_instance_bus_specification_at(
21964                ac_pf,
21965                1,
21966                pv.as_mut_ptr(),
21967                &mut error,
21968            ));
21969            let pv = pv.assume_init();
21970            assert_eq!(pv.bus_id, 2);
21971            assert_eq!(view_text(pv.kind), "pv");
21972            assert_eq!(pv.net_active_power_mw, 163.0);
21973            assert_eq!(pv.voltage_magnitude_pu, 1.025);
21974            pio_calculation_instance_release(ac_pf);
21975            pio_value_release(ac_pf_value);
21976            pio_module_release(ac_pf_module);
21977
21978            let dc_opf = powerio_prob::DcOpfInstance::from_network(network).unwrap();
21979            let dc_opf_module =
21980                module_handle(powerio::PioModule::new(PioValue::DcOpfInstance(dc_opf)));
21981            let dc_opf_value = pio_module_value(dc_opf_module);
21982            let dc_opf = pio_value_dc_opf_instance(dc_opf_value, &mut error);
21983            assert_eq!(pio_calculation_instance_objective_term_count(dc_opf), 1);
21984            let mut term = std::mem::MaybeUninit::<PioObjectiveTermView>::uninit();
21985            assert!(pio_calculation_instance_objective_term_at(
21986                dc_opf,
21987                0,
21988                term.as_mut_ptr(),
21989                &mut error,
21990            ));
21991            assert_eq!(view_text(term.assume_init().kind), "network_generator_cost");
21992            assert_eq!(pio_calculation_instance_active_constraint_count(dc_opf), 4);
21993            let mut constraint = std::mem::MaybeUninit::<PioActiveConstraintView>::uninit();
21994            assert!(pio_calculation_instance_active_constraint_at(
21995                dc_opf,
21996                2,
21997                constraint.as_mut_ptr(),
21998                &mut error,
21999            ));
22000            let constraint = constraint.assume_init();
22001            assert_eq!(view_text(constraint.family), "thermal_limits");
22002            assert_eq!(view_text(constraint.selection), "all");
22003            assert_eq!(constraint.identity_count, 0);
22004
22005            pio_calculation_instance_release(dc_opf);
22006            pio_value_release(dc_opf_value);
22007            pio_module_release(dc_opf_module);
22008        }
22009    }
22010
22011    #[test]
22012    fn dc_opf_preparation_rows_keep_their_owner_alive() {
22013        unsafe {
22014            let source_module = parse_case9();
22015            let mut error = std::ptr::null_mut();
22016            let instance_module = pio_module_to_dc_opf_instance(source_module, &mut error);
22017            assert!(!instance_module.is_null(), "{}", error_text(error));
22018            let value = pio_module_value(instance_module);
22019            let instance = pio_value_dc_opf_instance(value, &mut error);
22020            assert!(!instance.is_null(), "{}", error_text(error));
22021            let units = "per_unit";
22022            let preparation = pio_build_dc_opf_preparation(
22023                instance,
22024                units.as_ptr().cast(),
22025                units.len(),
22026                false,
22027                false,
22028                true,
22029                &mut error,
22030            );
22031            assert!(!preparation.is_null(), "{}", error_text(error));
22032
22033            pio_calculation_instance_release(instance);
22034            pio_value_release(value);
22035            pio_module_release(instance_module);
22036            pio_module_release(source_module);
22037
22038            let mut summary = std::mem::MaybeUninit::<PioDcOpfPreparationView>::uninit();
22039            assert!(pio_dc_opf_preparation_summary(
22040                preparation,
22041                summary.as_mut_ptr(),
22042                &mut error,
22043            ));
22044            let summary = summary.assume_init();
22045            assert_eq!(summary.bus_count, 9);
22046            assert_eq!(summary.generator_count, 3);
22047            assert_eq!(summary.branch_count, 9);
22048            assert_eq!(summary.base_mva, 100.0);
22049            assert_eq!(view_text(summary.units), "per_unit");
22050            assert!(summary.correct_angle_difference_bounds);
22051            assert_eq!(
22052                view_text(summary.branch_susceptance_formula),
22053                "series_susceptance"
22054            );
22055            assert_eq!(view_text(summary.objective), "network_generator_cost");
22056
22057            let reference = pio_dc_opf_preparation_reference_buses(preparation);
22058            assert_eq!(
22059                std::slice::from_raw_parts(reference.data, reference.len),
22060                &[0]
22061            );
22062
22063            let mut bus = std::mem::MaybeUninit::<PioDcOpfBusView>::uninit();
22064            assert!(pio_dc_opf_preparation_bus_at(
22065                preparation,
22066                4,
22067                bus.as_mut_ptr(),
22068                &mut error,
22069            ));
22070            let bus = bus.assume_init();
22071            assert_eq!(bus.bus_id, 5);
22072            assert_eq!(bus.active_power_demand, 0.9);
22073            assert_eq!(bus.shunt_conductance, 0.0);
22074            assert_eq!(bus.phase_shift_injection, 0.0);
22075
22076            let mut generator = std::mem::MaybeUninit::<PioDcOpfGeneratorView>::uninit();
22077            assert!(pio_dc_opf_preparation_generator_at(
22078                preparation,
22079                0,
22080                generator.as_mut_ptr(),
22081                &mut error,
22082            ));
22083            let generator = generator.assume_init();
22084            assert_eq!(generator.bus_index, 0);
22085            assert_eq!(generator.quadratic_cost, 2200.0);
22086            assert!(!generator.has_piecewise_linear_cost);
22087
22088            let mut branch = std::mem::MaybeUninit::<PioDcOpfBranchView>::uninit();
22089            assert!(pio_dc_opf_preparation_branch_at(
22090                preparation,
22091                0,
22092                branch.as_mut_ptr(),
22093                &mut error,
22094            ));
22095            let branch = branch.assume_init();
22096            assert_eq!(branch.from_bus_index, 0);
22097            assert_eq!(branch.to_bus_index, 3);
22098            assert!(branch.susceptance_magnitude > 0.0);
22099            assert_eq!(view_text(branch.source_kind), "branch");
22100            assert_eq!(branch.source_row, 0);
22101            assert!(!branch.has_winding);
22102
22103            let mut missing = std::mem::MaybeUninit::<PioDcOpfBusView>::uninit();
22104            assert!(!pio_dc_opf_preparation_bus_at(
22105                preparation,
22106                summary.bus_count,
22107                missing.as_mut_ptr(),
22108                &mut error,
22109            ));
22110            assert_eq!(
22111                view_text(pio_error_code(error)),
22112                "BIND.CAPI.INDEX_OUT_OF_RANGE"
22113            );
22114            pio_error_release(error);
22115            pio_dc_opf_preparation_release(preparation);
22116        }
22117    }
22118
22119    #[test]
22120    fn ac_opf_preparation_rows_keep_their_owner_alive() {
22121        unsafe {
22122            let source_module = parse_case9();
22123            let mut error = std::ptr::null_mut();
22124            let instance_module = pio_module_to_ac_opf_instance(source_module, &mut error);
22125            assert!(!instance_module.is_null(), "{}", error_text(error));
22126            let value = pio_module_value(instance_module);
22127            let instance = pio_value_ac_opf_instance(value, &mut error);
22128            assert!(!instance.is_null(), "{}", error_text(error));
22129            let units = "per_unit";
22130            let preparation = pio_build_ac_opf_preparation(
22131                instance,
22132                units.as_ptr().cast(),
22133                units.len(),
22134                false,
22135                false,
22136                true,
22137                &mut error,
22138            );
22139            assert!(!preparation.is_null(), "{}", error_text(error));
22140
22141            pio_calculation_instance_release(instance);
22142            pio_value_release(value);
22143            pio_module_release(instance_module);
22144            pio_module_release(source_module);
22145
22146            let mut summary = std::mem::MaybeUninit::<PioAcOpfPreparationView>::uninit();
22147            assert!(pio_ac_opf_preparation_summary(
22148                preparation,
22149                summary.as_mut_ptr(),
22150                &mut error,
22151            ));
22152            let summary = summary.assume_init();
22153            assert_eq!(summary.bus_count, 9);
22154            assert_eq!(summary.generator_count, 3);
22155            assert_eq!(summary.branch_count, 9);
22156            assert_eq!(summary.base_mva, 100.0);
22157            assert_eq!(view_text(summary.units), "per_unit");
22158            assert!(summary.correct_angle_difference_bounds);
22159            assert_eq!(view_text(summary.objective), "network_generator_cost");
22160
22161            let reference = pio_ac_opf_preparation_reference_buses(preparation);
22162            assert_eq!(
22163                std::slice::from_raw_parts(reference.data, reference.len),
22164                &[0]
22165            );
22166
22167            let mut bus = std::mem::MaybeUninit::<PioAcOpfBusView>::uninit();
22168            assert!(pio_ac_opf_preparation_bus_at(
22169                preparation,
22170                4,
22171                bus.as_mut_ptr(),
22172                &mut error,
22173            ));
22174            let bus = bus.assume_init();
22175            assert_eq!(bus.bus_id, 5);
22176            assert_eq!(bus.active_power_demand, 0.9);
22177            assert_eq!(bus.reactive_power_demand, 0.3);
22178            assert_eq!(bus.initial_voltage_angle_radians, 0.0);
22179
22180            let mut generator = std::mem::MaybeUninit::<PioAcOpfGeneratorView>::uninit();
22181            assert!(pio_ac_opf_preparation_generator_at(
22182                preparation,
22183                0,
22184                generator.as_mut_ptr(),
22185                &mut error,
22186            ));
22187            let generator = generator.assume_init();
22188            assert_eq!(generator.bus_index, 0);
22189            assert_eq!(generator.initial_active_power, 0.723);
22190            assert_eq!(generator.quadratic_cost, 2200.0);
22191            assert!(!generator.has_piecewise_linear_cost);
22192
22193            let mut branch = std::mem::MaybeUninit::<PioAcOpfBranchView>::uninit();
22194            assert!(pio_ac_opf_preparation_branch_at(
22195                preparation,
22196                0,
22197                branch.as_mut_ptr(),
22198                &mut error,
22199            ));
22200            let branch = branch.assume_init();
22201            assert_eq!(branch.from_bus_index, 0);
22202            assert_eq!(branch.to_bus_index, 3);
22203            assert_eq!(view_text(branch.source_kind), "branch");
22204            assert_eq!(branch.source_row, 0);
22205            assert!(!branch.has_winding);
22206
22207            let mut missing = std::mem::MaybeUninit::<PioAcOpfBusView>::uninit();
22208            assert!(!pio_ac_opf_preparation_bus_at(
22209                preparation,
22210                summary.bus_count,
22211                missing.as_mut_ptr(),
22212                &mut error,
22213            ));
22214            assert_eq!(
22215                view_text(pio_error_code(error)),
22216                "BIND.CAPI.INDEX_OUT_OF_RANGE"
22217            );
22218            pio_error_release(error);
22219            pio_ac_opf_preparation_release(preparation);
22220        }
22221    }
22222
22223    #[test]
22224    fn ac_opf_storage_view_preserves_fields_and_units() {
22225        unsafe {
22226            let mut network = case9_network();
22227            let mut storage = powerio_tx::Storage::new(powerio_tx::BusId(5));
22228            storage.uid = Some("battery".into());
22229            storage.ps = 12.0;
22230            storage.qs = -3.0;
22231            storage.energy = 80.0;
22232            storage.energy_rating = 120.0;
22233            storage.charge_rating = 25.0;
22234            storage.discharge_rating = 30.0;
22235            storage.charge_efficiency = 0.91;
22236            storage.discharge_efficiency = 0.88;
22237            storage.thermal_rating = 35.0;
22238            storage.qmin = -14.0;
22239            storage.qmax = 16.0;
22240            storage.r = 0.001;
22241            storage.x = 0.002;
22242            storage.p_loss = 0.4;
22243            storage.q_loss = 0.5;
22244            network.storage_mut().push(storage);
22245            let instance = powerio_prob::AcOpfInstance::from_network(network).unwrap();
22246            let close = |actual: f64, expected: f64| assert!((actual - expected).abs() < 1e-12);
22247
22248            for (units, power_scale) in [(Units::PerUnit, 0.01), (Units::Native, 1.0)] {
22249                let prepared = build_ac_opf_preparation(
22250                    &instance,
22251                    &AcOpfAssemblyOptions::default().with_units(units),
22252                )
22253                .unwrap();
22254                let preparation = PioAcOpfPreparation::new_raw(prepared);
22255                let mut error = std::ptr::null_mut();
22256                let mut summary = std::mem::MaybeUninit::<PioAcOpfPreparationView>::uninit();
22257                assert!(pio_ac_opf_preparation_summary(
22258                    preparation,
22259                    summary.as_mut_ptr(),
22260                    &mut error,
22261                ));
22262                assert_eq!(summary.assume_init().storage_count, 1);
22263
22264                let mut row = std::mem::MaybeUninit::<PioAcOpfStorageView>::uninit();
22265                assert!(pio_ac_opf_preparation_storage_at(
22266                    preparation,
22267                    0,
22268                    row.as_mut_ptr(),
22269                    &mut error,
22270                ));
22271                let row = row.assume_init();
22272                assert_eq!(view_text(row.component_id), "battery");
22273                assert_eq!(row.bus_index, 4);
22274                assert_eq!(row.source_row, 0);
22275                close(row.initial_active_power, 12.0 * power_scale);
22276                close(row.initial_reactive_power, -3.0 * power_scale);
22277                close(row.energy, 80.0 * power_scale);
22278                close(row.energy_rating, 120.0 * power_scale);
22279                close(row.charge_rating, 25.0 * power_scale);
22280                close(row.discharge_rating, 30.0 * power_scale);
22281                assert_eq!(row.charge_efficiency, 0.91);
22282                assert_eq!(row.discharge_efficiency, 0.88);
22283                close(row.apparent_power_max, 35.0 * power_scale);
22284                close(row.reactive_power_min, -14.0 * power_scale);
22285                close(row.reactive_power_max, 16.0 * power_scale);
22286                assert_eq!(row.resistance_pu, 0.001);
22287                assert_eq!(row.reactance_pu, 0.002);
22288                close(row.active_power_loss, 0.4 * power_scale);
22289                close(row.reactive_power_loss, 0.5 * power_scale);
22290                assert!(row.in_service);
22291
22292                pio_ac_opf_preparation_release(preparation);
22293            }
22294        }
22295    }
22296
22297    #[test]
22298    fn module_transforms_build_typed_calculation_modules() {
22299        unsafe {
22300            type Transform =
22301                unsafe extern "C" fn(*const PioModule, *mut *mut PioError) -> *mut PioModule;
22302            let source = parse_case9();
22303            let operations: [(Transform, &str, &str); 4] = [
22304                (
22305                    pio_module_to_dc_pf_instance,
22306                    "powerio.DcPfInstance",
22307                    "to_dc_pf_instance",
22308                ),
22309                (
22310                    pio_module_to_ac_pf_instance,
22311                    "powerio.AcPfInstance",
22312                    "to_ac_pf_instance",
22313                ),
22314                (
22315                    pio_module_to_dc_opf_instance,
22316                    "powerio.DcOpfInstance",
22317                    "to_dc_opf_instance",
22318                ),
22319                (
22320                    pio_module_to_ac_opf_instance,
22321                    "powerio.AcOpfInstance",
22322                    "to_ac_opf_instance",
22323                ),
22324            ];
22325            for (operation, expected_type, expected_history) in operations {
22326                let mut error = std::ptr::null_mut();
22327                let derived = operation(source, &mut error);
22328                assert!(!derived.is_null(), "{}", error_text(error));
22329                let value = pio_module_value(derived);
22330                assert!(pio_value_is_type(
22331                    value,
22332                    expected_type.as_ptr().cast(),
22333                    expected_type.len(),
22334                ));
22335                let derived_module = &PioModule::get(derived).unwrap().module;
22336                assert_eq!(
22337                    derived_module.history().last().unwrap().name(),
22338                    expected_history
22339                );
22340                assert!(derived_module.source().is_none());
22341                pio_value_release(value);
22342                pio_module_release(derived);
22343            }
22344
22345            let mut error = std::ptr::null_mut();
22346            let instance = pio_module_to_dc_pf_instance(source, &mut error);
22347            let invalid = pio_module_to_ac_pf_instance(instance, &mut error);
22348            assert!(invalid.is_null());
22349            assert_eq!(
22350                view_text(pio_error_code(error)),
22351                "REQUEST.MODULE.WRONG_MODEL_KIND"
22352            );
22353            pio_error_release(error);
22354            pio_module_release(instance);
22355            pio_module_release(source);
22356        }
22357    }
22358
22359    #[test]
22360    fn multiconductor_module_transforms_build_typed_calculation_modules() {
22361        unsafe {
22362            let source = parse_bmopf();
22363            let value = pio_module_value(source);
22364            let network_type = "powerio.MulticonductorNetwork";
22365            assert!(pio_value_is_type(
22366                value,
22367                network_type.as_ptr().cast(),
22368                network_type.len(),
22369            ));
22370            pio_value_release(value);
22371
22372            type Transform =
22373                unsafe extern "C" fn(*const PioModule, *mut *mut PioError) -> *mut PioModule;
22374            let operations: [(Transform, &str, &str); 2] = [
22375                (
22376                    pio_module_to_mc_ac_pf_instance,
22377                    "powerio.McAcPfInstance",
22378                    "to_mc_ac_pf_instance",
22379                ),
22380                (
22381                    pio_module_to_mc_ac_opf_instance,
22382                    "powerio.McAcOpfInstance",
22383                    "to_mc_ac_opf_instance",
22384                ),
22385            ];
22386            for (operation, expected_type, expected_history) in operations {
22387                let mut error = std::ptr::null_mut();
22388                let derived = operation(source, &mut error);
22389                assert!(!derived.is_null(), "{}", error_text(error));
22390                let value = pio_module_value(derived);
22391                assert!(pio_value_is_type(
22392                    value,
22393                    expected_type.as_ptr().cast(),
22394                    expected_type.len(),
22395                ));
22396                let derived_module = &PioModule::get(derived).unwrap().module;
22397                assert_eq!(
22398                    derived_module.history().last().unwrap().name(),
22399                    expected_history
22400                );
22401                assert!(derived_module.source().is_none());
22402                pio_value_release(value);
22403                pio_module_release(derived);
22404            }
22405            pio_module_release(source);
22406        }
22407    }
22408
22409    #[test]
22410    fn geo_layer_derives_a_new_network_module() {
22411        unsafe {
22412            let source_module = parse_case9();
22413            let geojson = br#"{
22414                "type": "FeatureCollection",
22415                "features": [{
22416                    "type": "Feature",
22417                    "geometry": {
22418                        "type": "Point",
22419                        "coordinates": [-83.743, 42.281]
22420                    },
22421                    "properties": {"bus": "1"}
22422                }]
22423            }"#;
22424            let mut error = std::ptr::null_mut();
22425            let source = pio_source_from_memory(
22426                c"case9.geojson".as_ptr(),
22427                "case9.geojson".len(),
22428                geojson.as_ptr(),
22429                geojson.len(),
22430                &mut error,
22431            );
22432            assert!(!source.is_null(), "{}", error_text(error));
22433            let layer = pio_geo_layer_parse(source, &mut error);
22434            pio_source_release(source);
22435            assert!(!layer.is_null(), "{}", error_text(error));
22436
22437            let diagnostics = pio_geo_layer_diagnostics(layer);
22438            assert_eq!(pio_diagnostics_len(diagnostics), 0);
22439            pio_diagnostics_release(diagnostics);
22440
22441            let mut report = std::ptr::null_mut();
22442            let derived = pio_module_apply_geo_layer(source_module, layer, &mut report, &mut error);
22443            assert!(!derived.is_null(), "{}", error_text(error));
22444            assert!(!report.is_null());
22445            assert_eq!(pio_geo_apply_report_matched_buses(report), 1);
22446            assert_eq!(pio_geo_apply_report_matched_branches(report), 0);
22447            assert_eq!(pio_geo_apply_report_unmatched_features(report), 0);
22448            assert_eq!(pio_geo_apply_report_unlocated_buses(report), 8);
22449            assert_eq!(pio_geo_apply_report_unlocated_branches(report), 9);
22450
22451            let note_count = pio_geo_apply_report_note_count(report);
22452            let note = pio_geo_apply_report_note_at(report, note_count, &mut error);
22453            assert_eq!(note.len, 0);
22454            assert_eq!(
22455                view_text(pio_error_code(error)),
22456                "BIND.CAPI.INDEX_OUT_OF_RANGE"
22457            );
22458            pio_error_release(error);
22459            error = std::ptr::null_mut();
22460
22461            let derived_module = &PioModule::get(derived).unwrap().module;
22462            assert!(derived_module.source().is_none());
22463            assert_eq!(
22464                derived_module.history().last().unwrap().name(),
22465                "apply_geo_layer"
22466            );
22467            let PioValue::BalancedNetwork(derived_network) = &derived_module.value() else {
22468                panic!("geo application changed the value type")
22469            };
22470            let derived_location = derived_network
22471                .buses()
22472                .iter()
22473                .find(|bus| bus.id == powerio_tx::BusId(1))
22474                .and_then(|bus| bus.location)
22475                .unwrap();
22476            assert_eq!((derived_location.x, derived_location.y), (-83.743, 42.281));
22477
22478            let original_module = &PioModule::get(source_module).unwrap().module;
22479            let PioValue::BalancedNetwork(original_network) = &original_module.value() else {
22480                unreachable!()
22481            };
22482            assert!(
22483                original_network
22484                    .buses()
22485                    .iter()
22486                    .find(|bus| bus.id == powerio_tx::BusId(1))
22487                    .unwrap()
22488                    .location
22489                    .is_none()
22490            );
22491
22492            let calculation = pio_module_to_dc_pf_instance(source_module, &mut error);
22493            assert!(!calculation.is_null(), "{}", error_text(error));
22494            let rejected =
22495                pio_module_apply_geo_layer(calculation, layer, std::ptr::null_mut(), &mut error);
22496            assert!(rejected.is_null());
22497            assert_eq!(
22498                view_text(pio_error_code(error)),
22499                "REQUEST.MODULE.WRONG_MODEL_KIND"
22500            );
22501            pio_error_release(error);
22502
22503            pio_module_release(calculation);
22504            pio_geo_apply_report_release(report);
22505            pio_geo_layer_release(layer);
22506            pio_module_release(derived);
22507            pio_module_release(source_module);
22508        }
22509    }
22510
22511    #[test]
22512    fn a_geo_layer_writes_the_canonical_document_it_parses_back() {
22513        unsafe {
22514            let document = br#"{
22515                "type": "FeatureCollection",
22516                "features": [{
22517                    "type": "Feature",
22518                    "geometry": {
22519                        "type": "Point",
22520                        "coordinates": [-83.743, 42.281]
22521                    },
22522                    "properties": {"bus": "1"}
22523                }]
22524            }"#;
22525            let mut error = std::ptr::null_mut();
22526            let source = pio_source_from_memory(
22527                c"case9.geojson".as_ptr(),
22528                "case9.geojson".len(),
22529                document.as_ptr(),
22530                document.len(),
22531                &mut error,
22532            );
22533            assert!(!source.is_null(), "{}", error_text(error));
22534            let layer = pio_geo_layer_parse(source, &mut error);
22535            pio_source_release(source);
22536            assert!(!layer.is_null(), "{}", error_text(error));
22537
22538            let written = pio_geo_layer_to_geojson(layer, &mut error);
22539            assert!(!written.is_null(), "{}", error_text(error));
22540            let written_text = view_text(pio_string_view(written));
22541            pio_string_release(written);
22542            assert!(written_text.contains("FeatureCollection"), "{written_text}");
22543            assert!(written_text.contains("powerio_geo"), "{written_text}");
22544
22545            // The canonical document is what the reader takes, and writing it
22546            // again reproduces it byte for byte.
22547            let echo_source = pio_source_from_memory(
22548                c"case9.geo.json".as_ptr(),
22549                "case9.geo.json".len(),
22550                written_text.as_ptr(),
22551                written_text.len(),
22552                &mut error,
22553            );
22554            assert!(!echo_source.is_null(), "{}", error_text(error));
22555            let echo = pio_geo_layer_parse(echo_source, &mut error);
22556            pio_source_release(echo_source);
22557            assert!(!echo.is_null(), "{}", error_text(error));
22558            let again = pio_geo_layer_to_geojson(echo, &mut error);
22559            assert!(!again.is_null(), "{}", error_text(error));
22560            assert_eq!(view_text(pio_string_view(again)), written_text);
22561            pio_string_release(again);
22562            pio_geo_layer_release(echo);
22563
22564            let missing = pio_geo_layer_to_geojson(std::ptr::null(), &mut error);
22565            assert!(missing.is_null());
22566            assert_eq!(view_text(pio_error_code(error)), "BIND.CAPI.NULL_HANDLE");
22567            pio_error_release(error);
22568
22569            pio_geo_layer_release(layer);
22570        }
22571    }
22572
22573    unsafe fn contingency_fixture(name: &str) -> std::path::PathBuf {
22574        std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
22575            .join("../tests/data/psse/contingency")
22576            .join(name)
22577    }
22578
22579    unsafe fn parse_contingency_fixture(name: &str) -> *mut PioModule {
22580        let path = unsafe { contingency_fixture(name) };
22581        let path = path.to_string_lossy();
22582        let mut error = std::ptr::null_mut();
22583        let source = unsafe { pio_source_open(path.as_ptr().cast(), path.len(), &mut error) };
22584        assert!(!source.is_null(), "{}", unsafe { error_text(error) });
22585        let module = unsafe { pio_parse(source, std::ptr::null(), 0, &mut error) };
22586        unsafe { pio_source_release(source) };
22587        assert!(!module.is_null(), "{name}: {}", unsafe {
22588            error_text(error)
22589        });
22590        module
22591    }
22592
22593    /// Read text back as a module under one contingency analysis format. The
22594    /// module is released by the caller.
22595    unsafe fn reread_contingency_text(name: &str, format: &str, text: &str) -> *mut PioModule {
22596        let mut error = std::ptr::null_mut();
22597        let source = unsafe {
22598            pio_source_from_memory(
22599                name.as_ptr().cast(),
22600                name.len(),
22601                text.as_ptr(),
22602                text.len(),
22603                &mut error,
22604            )
22605        };
22606        assert!(!source.is_null(), "{}", unsafe { error_text(error) });
22607        let module = unsafe { pio_parse(source, format.as_ptr().cast(), format.len(), &mut error) };
22608        unsafe { pio_source_release(source) };
22609        assert!(!module.is_null(), "{name}: {}", unsafe {
22610            error_text(error)
22611        });
22612        module
22613    }
22614
22615    /// The balanced network of one PSS/E RAW fixture, plus the module that
22616    /// owns it. Both handles are released by the caller.
22617    unsafe fn contingency_network(name: &str) -> (*mut PioModule, *mut PioBalancedNetwork) {
22618        let module = unsafe { parse_contingency_fixture(name) };
22619        let mut error = std::ptr::null_mut();
22620        let value = unsafe { pio_module_value(module) };
22621        let network = unsafe { pio_value_balanced_network(value, &mut error) };
22622        assert!(!network.is_null(), "{}", unsafe { error_text(error) });
22623        unsafe { pio_value_release(value) };
22624        (module, network)
22625    }
22626
22627    #[test]
22628    fn a_contingency_set_binds_every_case_to_the_network_it_names() {
22629        unsafe {
22630            let path = contingency_fixture("resolve_cases.con");
22631            let path_text = path.to_string_lossy();
22632            let mut error = std::ptr::null_mut();
22633            let source = pio_source_open(path_text.as_ptr().cast(), path_text.len(), &mut error);
22634            assert!(!source.is_null(), "{}", error_text(error));
22635            let set = pio_contingency_set_parse(source, &mut error);
22636            pio_source_release(source);
22637            assert!(!set.is_null(), "{}", error_text(error));
22638
22639            assert_eq!(pio_contingency_set_case_count(set), 20);
22640            assert_eq!(
22641                view_text(pio_contingency_set_case_name(set, 0, &mut error)),
22642                "BR_1_2_C1"
22643            );
22644            let past_end = pio_contingency_set_case_name(set, 20, &mut error);
22645            assert_eq!(past_end.len, 0);
22646            assert_eq!(
22647                view_text(pio_error_code(error)),
22648                "BIND.CAPI.INDEX_OUT_OF_RANGE"
22649            );
22650            pio_error_release(error);
22651            error = std::ptr::null_mut();
22652
22653            // One statement of the fixture is kept as text, so the reader
22654            // records it.
22655            let notes = pio_contingency_set_diagnostics(set);
22656            assert!(pio_diagnostics_len(notes) > 0);
22657            pio_diagnostics_release(notes);
22658
22659            let (module, network) = contingency_network("resolve_v33.raw");
22660            let resolution = pio_contingency_set_resolve(set, network, &mut error);
22661            assert!(!resolution.is_null(), "{}", error_text(error));
22662
22663            assert_eq!(pio_contingency_resolution_case_count(resolution), 20);
22664            assert_eq!(pio_contingency_resolution_resolved_count(resolution), 17);
22665            assert_eq!(pio_contingency_resolution_unresolved_count(resolution), 3);
22666            assert_eq!(
22667                pio_contingency_resolution_unrecognized_statement_count(resolution),
22668                1
22669            );
22670
22671            // The first case opens one branch, so it binds to exactly one row.
22672            assert!(pio_contingency_resolution_case_is_resolved(resolution, 0));
22673            assert_eq!(
22674                view_text(pio_contingency_resolution_case_name(
22675                    resolution, 0, &mut error
22676                )),
22677                "BR_1_2_C1"
22678            );
22679            assert_eq!(
22680                pio_contingency_resolution_case_component_count(resolution, 0),
22681                1
22682            );
22683            let mut component = std::mem::MaybeUninit::<PioContingencyComponentView>::uninit();
22684            assert!(pio_contingency_resolution_case_component(
22685                resolution,
22686                0,
22687                0,
22688                component.as_mut_ptr(),
22689                &mut error,
22690            ));
22691            let component = component.assume_init();
22692            assert_eq!(view_text(component.id.component_type), "branch");
22693            assert!(component.in_service);
22694            assert_eq!(
22695                pio_contingency_resolution_case_unresolved_count(resolution, 0),
22696                0
22697            );
22698
22699            // Every case that did not bind states one reason and the
22700            // statement of each action that named no element.
22701            let unresolved: Vec<(String, String, String)> = (0
22702                ..pio_contingency_resolution_case_count(resolution))
22703                .filter(|index| !pio_contingency_resolution_case_is_resolved(resolution, *index))
22704                .map(|index| {
22705                    assert_eq!(
22706                        pio_contingency_resolution_case_unresolved_count(resolution, index),
22707                        1
22708                    );
22709                    (
22710                        view_text(pio_contingency_resolution_case_name(
22711                            resolution, index, &mut error,
22712                        )),
22713                        view_text(pio_contingency_resolution_case_unresolved_reason(
22714                            resolution, index, 0, &mut error,
22715                        )),
22716                        view_text(pio_contingency_resolution_case_unresolved_action(
22717                            resolution, index, 0, &mut error,
22718                        )),
22719                    )
22720                })
22721                .collect();
22722            assert_eq!(
22723                unresolved,
22724                vec![
22725                    (
22726                        "BR_MISSING".to_owned(),
22727                        "no_such_branch".to_owned(),
22728                        "OPEN LINE FROM BUS      3 TO BUS      4 CIRCUIT 1".to_owned(),
22729                    ),
22730                    (
22731                        "MACHINE_MISSING".to_owned(),
22732                        "no_such_machine".to_owned(),
22733                        "REMOVE MACHINE 9 FROM BUS      1".to_owned(),
22734                    ),
22735                    (
22736                        "UNRECOGNIZED".to_owned(),
22737                        "unrecognized".to_owned(),
22738                        "PARALLEL BRANCH FROM BUS      1 TO BUS      2".to_owned(),
22739                    ),
22740                ]
22741            );
22742            let past_end =
22743                pio_contingency_resolution_case_unresolved_action(resolution, 0, 0, &mut error);
22744            assert_eq!(past_end.len, 0);
22745            assert_eq!(
22746                view_text(pio_error_code(error)),
22747                "BIND.CAPI.INDEX_OUT_OF_RANGE"
22748            );
22749            pio_error_release(error);
22750            error = std::ptr::null_mut();
22751
22752            // The writer states every case, and the text it produces reads
22753            // back as the same set.
22754            let written = pio_contingency_set_to_con(set, &mut error);
22755            assert!(!written.is_null(), "{}", error_text(error));
22756            let written_text = view_text(pio_string_view(written));
22757            pio_string_release(written);
22758            assert!(
22759                written_text.contains(
22760                    "CONTINGENCY 'BR_MISSING'\nOPEN LINE FROM BUS      3 TO BUS      4 CIRCUIT 1\nEND\n"
22761                ),
22762                "{written_text}"
22763            );
22764            let echo = pio_source_from_memory(
22765                c"written.con".as_ptr(),
22766                "written.con".len(),
22767                written_text.as_ptr(),
22768                written_text.len(),
22769                &mut error,
22770            );
22771            assert!(!echo.is_null(), "{}", error_text(error));
22772            let again = pio_contingency_set_parse(echo, &mut error);
22773            pio_source_release(echo);
22774            assert!(!again.is_null(), "{}", error_text(error));
22775            assert_eq!(pio_contingency_set_case_count(again), 20);
22776            assert_eq!(
22777                view_text(pio_contingency_set_case_name(again, 19, &mut error)),
22778                "UNRECOGNIZED"
22779            );
22780            pio_contingency_set_release(again);
22781
22782            // Retaining and releasing leaves every handle usable.
22783            let retained = pio_contingency_resolution_retain(resolution);
22784            pio_contingency_resolution_release(retained);
22785            assert_eq!(pio_contingency_resolution_case_count(resolution), 20);
22786            let retained_set = pio_contingency_set_retain(set);
22787            pio_contingency_set_release(retained_set);
22788            assert_eq!(pio_contingency_set_case_count(set), 20);
22789
22790            pio_contingency_resolution_release(resolution);
22791            pio_balanced_network_release(network);
22792            pio_module_release(module);
22793            pio_contingency_set_release(set);
22794        }
22795    }
22796
22797    /// A row the network states no identity for still names the table it
22798    /// indexes. The IR document of the fixture network is rewritten with an
22799    /// empty load `uid`, a stated field the reader keeps as it stands and one
22800    /// no component identity accepts, so both loads bind with an empty local
22801    /// id while `component_type` and `row` still name them.
22802    #[test]
22803    fn a_bound_component_states_its_table_and_an_optional_identity() {
22804        unsafe {
22805            let mut error = std::ptr::null_mut();
22806            let module = parse_contingency_fixture("resolve_v33.raw");
22807            let destination = pio_destination_memory(
22808                c"network.pio.json".as_ptr(),
22809                "network.pio.json".len(),
22810                &mut error,
22811            );
22812            assert!(!destination.is_null(), "{}", error_text(error));
22813            let result = pio_module_serialize(module, destination, &mut error);
22814            assert!(!result.is_null(), "{}", error_text(error));
22815            let artifact = pio_emit_result_artifact(result, 0, &mut error);
22816            assert!(!artifact.is_null(), "{}", error_text(error));
22817            let bytes = pio_artifact_bytes(artifact);
22818            let mut document: serde_json::Value =
22819                serde_json::from_slice(std::slice::from_raw_parts(bytes.data, bytes.len)).unwrap();
22820            let loads = document["value"]["data"]["loads"].as_array_mut().unwrap();
22821            assert_eq!(loads.len(), 2);
22822            for load in loads {
22823                load["uid"] = serde_json::Value::String(String::new());
22824            }
22825            let rewritten = serde_json::to_vec(&document).unwrap();
22826            pio_artifact_release(artifact);
22827            pio_emit_result_release(result);
22828            pio_destination_release(destination);
22829            pio_module_release(module);
22830
22831            let source = pio_source_from_memory(
22832                c"network.pio.json".as_ptr(),
22833                "network.pio.json".len(),
22834                rewritten.as_ptr(),
22835                rewritten.len(),
22836                &mut error,
22837            );
22838            assert!(!source.is_null(), "{}", error_text(error));
22839            let decoded = pio_module_deserialize(source, &mut error);
22840            pio_source_release(source);
22841            assert!(!decoded.is_null(), "{}", error_text(error));
22842            let value = pio_module_value(decoded);
22843            let network = pio_value_balanced_network(value, &mut error);
22844            assert!(!network.is_null(), "{}", error_text(error));
22845            pio_value_release(value);
22846
22847            let path = contingency_fixture("resolve_cases.con");
22848            let path_text = path.to_string_lossy();
22849            let cases = pio_source_open(path_text.as_ptr().cast(), path_text.len(), &mut error);
22850            assert!(!cases.is_null(), "{}", error_text(error));
22851            let set = pio_contingency_set_parse(cases, &mut error);
22852            pio_source_release(cases);
22853            assert!(!set.is_null(), "{}", error_text(error));
22854            let resolution = pio_contingency_set_resolve(set, network, &mut error);
22855            assert!(!resolution.is_null(), "{}", error_text(error));
22856
22857            let case_of = |name: &str| {
22858                let mut case_error = std::ptr::null_mut();
22859                (0..pio_contingency_resolution_case_count(resolution))
22860                    .find(|index| {
22861                        view_text(pio_contingency_resolution_case_name(
22862                            resolution,
22863                            *index,
22864                            &mut case_error,
22865                        )) == name
22866                    })
22867                    .unwrap_or_else(|| panic!("the fixture states a case named {name}"))
22868            };
22869            let read = |case_index: usize, component_index: usize| {
22870                let mut component = std::mem::MaybeUninit::<PioContingencyComponentView>::uninit();
22871                let mut read_error = std::ptr::null_mut();
22872                assert!(
22873                    pio_contingency_resolution_case_component(
22874                        resolution,
22875                        case_index,
22876                        component_index,
22877                        component.as_mut_ptr(),
22878                        &mut read_error,
22879                    ),
22880                    "{}",
22881                    error_text(read_error)
22882                );
22883                component.assume_init()
22884            };
22885
22886            // Both loads bind and name their table, and neither states an
22887            // identity.
22888            let loads_at_bus = case_of("LOADS_AT_BUS");
22889            assert_eq!(
22890                pio_contingency_resolution_case_component_count(resolution, loads_at_bus),
22891                2
22892            );
22893            for row in 0..2 {
22894                let component = read(loads_at_bus, row);
22895                assert_eq!(view_text(component.id.component_type), "load");
22896                assert_eq!(component.id.local_id.len, 0);
22897                assert_eq!(component.row, row);
22898                assert!(component.in_service);
22899            }
22900
22901            // A row the network does state a `uid` for states it as the local
22902            // id, under the same component type.
22903            let branch = read(case_of("BR_1_2_C1"), 0);
22904            assert_eq!(view_text(branch.id.component_type), "branch");
22905            assert_eq!(view_text(branch.id.local_id), "1-2");
22906
22907            pio_contingency_resolution_release(resolution);
22908            pio_contingency_set_release(set);
22909            pio_balanced_network_release(network);
22910            pio_module_release(decoded);
22911        }
22912    }
22913
22914    #[test]
22915    fn an_automatic_specification_expands_against_a_subsystem_set() {
22916        unsafe {
22917            let mut error = std::ptr::null_mut();
22918            let cases_module = parse_contingency_fixture("expand.con");
22919            let cases_value = pio_module_value(cases_module);
22920            assert!(pio_value_is_type(
22921                cases_value,
22922                c"powerio.ContingencySet".as_ptr().cast(),
22923                "powerio.ContingencySet".len(),
22924            ));
22925            let set = pio_value_contingency_set(cases_value, &mut error);
22926            assert!(!set.is_null(), "{}", error_text(error));
22927            // A set taken from a module value carries no reader notes.
22928            let notes = pio_contingency_set_diagnostics(set);
22929            assert_eq!(pio_diagnostics_len(notes), 0);
22930            pio_diagnostics_release(notes);
22931
22932            // The typed accessor for another value type refuses.
22933            let wrong = pio_value_subsystem_set(cases_value, &mut error);
22934            assert!(wrong.is_null());
22935            assert_eq!(
22936                view_text(pio_error_code(error)),
22937                "REQUEST.CAPI.TYPE_MISMATCH"
22938            );
22939            pio_error_release(error);
22940            error = std::ptr::null_mut();
22941            pio_value_release(cases_value);
22942
22943            // The handle holds the module owner, so releasing the module
22944            // handle leaves the set readable and writable.
22945            pio_module_release(cases_module);
22946            assert_eq!(pio_contingency_set_case_count(set), 1);
22947            let written = pio_contingency_set_to_con(set, &mut error);
22948            assert!(!written.is_null(), "{}", error_text(error));
22949            let written_text = view_text(pio_string_view(written));
22950            assert!(
22951                written_text.contains("CONTINGENCY 'EXPLICIT'"),
22952                "{written_text}"
22953            );
22954            assert!(
22955                written_text.contains("SINGLE BRANCH IN SUBSYSTEM 'NOSUCH'"),
22956                "{written_text}"
22957            );
22958            pio_string_release(written);
22959
22960            let sub_module = parse_contingency_fixture("selectors.sub");
22961            let sub_value = pio_module_value(sub_module);
22962            let subsystems = pio_value_subsystem_set(sub_value, &mut error);
22963            assert!(!subsystems.is_null(), "{}", error_text(error));
22964            pio_value_release(sub_value);
22965            assert_eq!(pio_subsystem_set_count(subsystems), 10);
22966            assert_eq!(
22967                view_text(pio_subsystem_set_name(subsystems, 0, &mut error)),
22968                "A1"
22969            );
22970            let written = pio_subsystem_set_to_sub(subsystems, &mut error);
22971            assert!(!written.is_null(), "{}", error_text(error));
22972            let written_text = view_text(pio_string_view(written));
22973            pio_string_release(written);
22974            let echo = reread_contingency_text("written.sub", "psse-sub", &written_text);
22975            let echo_value = pio_module_value(echo);
22976            let again = pio_value_subsystem_set(echo_value, &mut error);
22977            assert!(!again.is_null(), "{}", error_text(error));
22978            pio_value_release(echo_value);
22979            assert_eq!(pio_subsystem_set_count(again), 10);
22980            assert_eq!(
22981                view_text(pio_subsystem_set_name(again, 7, &mut error)),
22982                "BAREJOIN"
22983            );
22984            pio_subsystem_set_release(again);
22985            pio_module_release(echo);
22986
22987            let (network_module, network) = contingency_network("select_v33.raw");
22988            let mut expansion_notes = std::ptr::null_mut();
22989            let expanded = pio_contingency_set_expand(
22990                set,
22991                network,
22992                subsystems,
22993                &mut expansion_notes,
22994                &mut error,
22995            );
22996            assert!(!expanded.is_null(), "{}", error_text(error));
22997            assert_eq!(pio_contingency_set_case_count(expanded), 8);
22998            // An expanded set reaches a file through its own writer.
22999            let written = pio_contingency_set_to_con(expanded, &mut error);
23000            assert!(!written.is_null(), "{}", error_text(error));
23001            assert!(
23002                view_text(pio_string_view(written)).contains("CONTINGENCY 'T_101_102_103_1'"),
23003                "the expanded text states every generated case"
23004            );
23005            pio_string_release(written);
23006            assert_eq!(
23007                view_text(pio_contingency_set_case_name(expanded, 0, &mut error)),
23008                "EXPLICIT"
23009            );
23010            assert_eq!(
23011                view_text(pio_contingency_set_case_name(expanded, 3, &mut error)),
23012                "T_101_102_103_1"
23013            );
23014            // The one specification naming a subsystem the `.sub` file does
23015            // not state earns a note.
23016            assert_eq!(pio_diagnostics_len(expansion_notes), 1);
23017            pio_diagnostics_release(expansion_notes);
23018
23019            let monitored_module = parse_contingency_fixture("generated.mon");
23020            let monitored_value = pio_module_value(monitored_module);
23021            let monitored = pio_value_monitored_set(monitored_value, &mut error);
23022            assert!(!monitored.is_null(), "{}", error_text(error));
23023            pio_value_release(monitored_value);
23024            assert_eq!(pio_monitored_set_statement_count(monitored), 12);
23025            let written = pio_monitored_set_to_mon(monitored, &mut error);
23026            assert!(!written.is_null(), "{}", error_text(error));
23027            let written_text = view_text(pio_string_view(written));
23028            pio_string_release(written);
23029            let echo = reread_contingency_text("written.mon", "psse-mon", &written_text);
23030            let echo_value = pio_module_value(echo);
23031            let again = pio_value_monitored_set(echo_value, &mut error);
23032            assert!(!again.is_null(), "{}", error_text(error));
23033            pio_value_release(echo_value);
23034            assert_eq!(pio_monitored_set_statement_count(again), 12);
23035            pio_monitored_set_release(again);
23036            pio_module_release(echo);
23037
23038            pio_monitored_set_release(monitored);
23039            pio_module_release(monitored_module);
23040            pio_contingency_set_release(expanded);
23041            pio_balanced_network_release(network);
23042            pio_module_release(network_module);
23043            pio_subsystem_set_release(subsystems);
23044            pio_module_release(sub_module);
23045            pio_contingency_set_release(set);
23046        }
23047    }
23048
23049    #[test]
23050    fn a_named_subsystem_selects_the_buses_of_one_network() {
23051        unsafe {
23052            let mut error = std::ptr::null_mut();
23053            let (network_module, network) = contingency_network("select_v33.raw");
23054            let sub_module = parse_contingency_fixture("selectors.sub");
23055            let sub_value = pio_module_value(sub_module);
23056            let subsystems = pio_value_subsystem_set(sub_value, &mut error);
23057            assert!(!subsystems.is_null(), "{}", error_text(error));
23058            pio_value_release(sub_value);
23059            // The handle holds the module owner, so a set projected from a
23060            // module value selects after the module handle is released.
23061            pio_module_release(sub_module);
23062
23063            for (name, expected) in [
23064                ("A1", &[101.0, 102.0, 103.0][..]),
23065                ("AREARANGE", &[101.0, 102.0, 103.0, 201.0, 202.0][..]),
23066                ("KV", &[101.0, 102.0, 103.0, 201.0][..]),
23067                // The name is matched without case and without surrounding
23068                // whitespace, as a statement names a subsystem.
23069                (" a1 ", &[101.0, 102.0, 103.0][..]),
23070            ] {
23071                let selected = pio_balanced_network_select_subsystem_buses(
23072                    network,
23073                    subsystems,
23074                    name.as_ptr().cast(),
23075                    name.len(),
23076                    &mut error,
23077                );
23078                assert!(!selected.is_null(), "{name}: {}", error_text(error));
23079                let values = pio_vector_values(selected);
23080                assert_eq!(
23081                    std::slice::from_raw_parts(values.data, values.len),
23082                    expected,
23083                    "{name}"
23084                );
23085                pio_vector_release(selected);
23086            }
23087
23088            let unknown = pio_balanced_network_select_subsystem_buses(
23089                network,
23090                subsystems,
23091                c"NOSUCH".as_ptr(),
23092                "NOSUCH".len(),
23093                &mut error,
23094            );
23095            assert!(unknown.is_null());
23096            assert_eq!(
23097                view_text(pio_error_code(error)),
23098                "BIND.CAPI.INDEX_OUT_OF_RANGE"
23099            );
23100            pio_error_release(error);
23101            error = std::ptr::null_mut();
23102
23103            let no_network = pio_balanced_network_select_subsystem_buses(
23104                std::ptr::null(),
23105                subsystems,
23106                c"A1".as_ptr(),
23107                "A1".len(),
23108                &mut error,
23109            );
23110            assert!(no_network.is_null());
23111            assert_eq!(view_text(pio_error_code(error)), "BIND.CAPI.NULL_HANDLE");
23112            pio_error_release(error);
23113            error = std::ptr::null_mut();
23114
23115            let no_subsystems = pio_balanced_network_select_subsystem_buses(
23116                network,
23117                std::ptr::null(),
23118                c"A1".as_ptr(),
23119                "A1".len(),
23120                &mut error,
23121            );
23122            assert!(no_subsystems.is_null());
23123            assert_eq!(view_text(pio_error_code(error)), "BIND.CAPI.NULL_HANDLE");
23124            pio_error_release(error);
23125            error = std::ptr::null_mut();
23126
23127            let no_name = pio_balanced_network_select_subsystem_buses(
23128                network,
23129                subsystems,
23130                std::ptr::null(),
23131                0,
23132                &mut error,
23133            );
23134            assert!(no_name.is_null());
23135            assert_eq!(view_text(pio_error_code(error)), "BIND.CAPI.NULL_ARGUMENT");
23136            pio_error_release(error);
23137
23138            pio_subsystem_set_release(subsystems);
23139            pio_balanced_network_release(network);
23140            pio_module_release(network_module);
23141        }
23142    }
23143
23144    #[test]
23145    fn each_contingency_file_emits_the_bytes_it_was_read_from() {
23146        unsafe {
23147            for (name, format) in [
23148                ("resolve_cases.con", "psse-con"),
23149                ("selectors.sub", "psse-sub"),
23150                ("generated.mon", "psse-mon"),
23151            ] {
23152                let module = parse_contingency_fixture(name);
23153                let mut error = std::ptr::null_mut();
23154                let destination = pio_destination_memory(c"out".as_ptr(), 3, &mut error);
23155                assert!(!destination.is_null(), "{}", error_text(error));
23156                let result = pio_emit(
23157                    module,
23158                    format.as_ptr().cast(),
23159                    format.len(),
23160                    destination,
23161                    &mut error,
23162                );
23163                pio_destination_release(destination);
23164                assert!(!result.is_null(), "{name}: {}", error_text(error));
23165                assert_eq!(pio_emit_result_artifact_count(result), 1);
23166                let artifact = pio_emit_result_artifact(result, 0, &mut error);
23167                assert!(!artifact.is_null(), "{name}: {}", error_text(error));
23168                let bytes = pio_artifact_bytes(artifact);
23169                let emitted = std::slice::from_raw_parts(bytes.data, bytes.len).to_vec();
23170                assert_eq!(
23171                    emitted,
23172                    std::fs::read(contingency_fixture(name)).unwrap(),
23173                    "{name}"
23174                );
23175                pio_artifact_release(artifact);
23176                pio_emit_result_release(result);
23177                pio_module_release(module);
23178            }
23179        }
23180    }
23181
23182    #[test]
23183    fn aggregate_bus_active_demand_uses_explicit_proportional_allocation() {
23184        unsafe {
23185            let mut network = case9_network();
23186            let mut second = network
23187                .loads()
23188                .iter()
23189                .find(|load| load.bus == powerio_tx::BusId::new(5))
23190                .unwrap()
23191                .clone();
23192            second.p = 30.0;
23193            second.q = 10.0;
23194            second.uid = Some("extra-load-at-bus-5".to_owned());
23195            network.loads_mut().push(second);
23196            let source = module_handle(powerio::PioModule::new(PioValue::BalancedNetwork(network)));
23197            let mut error = std::ptr::null_mut();
23198            let module = pio_module_to_dc_pf_instance(source, &mut error);
23199            assert!(!module.is_null(), "{}", error_text(error));
23200            pio_module_release(source);
23201
23202            let value_before = pio_module_value(module);
23203            let instance_before = pio_value_dc_pf_instance(value_before, &mut error);
23204            let network_before =
23205                pio_calculation_instance_balanced_network(instance_before, &mut error);
23206
23207            let power = pio_active_power_from_watts(240_000_000.0);
23208            let bad_rule = "first_load";
23209            let bad_report = pio_apply_bus_load_active_power(
23210                module,
23211                5,
23212                power,
23213                bad_rule.as_ptr().cast(),
23214                bad_rule.len(),
23215                &mut error,
23216            );
23217            assert!(bad_report.is_null());
23218            assert_eq!(
23219                view_text(pio_error_code(error)),
23220                "REQUEST.CAPI.ALLOCATION_UNKNOWN"
23221            );
23222            pio_error_release(error);
23223            error = std::ptr::null_mut();
23224
23225            let rule = "proportional_to_current_active_power";
23226            let report = pio_apply_bus_load_active_power(
23227                module,
23228                5,
23229                power,
23230                rule.as_ptr().cast(),
23231                rule.len(),
23232                &mut error,
23233            );
23234            assert!(!report.is_null(), "{}", error_text(error));
23235            assert_eq!(pio_update_report_len(report), 2);
23236            assert!(!pio_update_report_connectivity_changed(report));
23237
23238            let value_after = pio_module_value(module);
23239            let instance_after = pio_value_dc_pf_instance(value_after, &mut error);
23240            let network_after =
23241                pio_calculation_instance_balanced_network(instance_after, &mut error);
23242            let mut first_before = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
23243            let mut second_before = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
23244            let mut first_after = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
23245            let mut second_after = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
23246            assert!(pio_balanced_network_load_at(
23247                network_before,
23248                0,
23249                first_before.as_mut_ptr(),
23250                &mut error,
23251            ));
23252            assert!(pio_balanced_network_load_at(
23253                network_before,
23254                3,
23255                second_before.as_mut_ptr(),
23256                &mut error,
23257            ));
23258            assert!(pio_balanced_network_load_at(
23259                network_after,
23260                0,
23261                first_after.as_mut_ptr(),
23262                &mut error,
23263            ));
23264            assert!(pio_balanced_network_load_at(
23265                network_after,
23266                3,
23267                second_after.as_mut_ptr(),
23268                &mut error,
23269            ));
23270            assert_eq!(first_before.assume_init().p_mw, 90.0);
23271            assert_eq!(second_before.assume_init().p_mw, 30.0);
23272            assert_eq!(first_after.assume_init().p_mw, 180.0);
23273            assert_eq!(second_after.assume_init().p_mw, 60.0);
23274            assert_eq!(
23275                PioModule::get(module)
23276                    .unwrap()
23277                    .module
23278                    .history()
23279                    .last()
23280                    .unwrap()
23281                    .name(),
23282                "apply_updates"
23283            );
23284
23285            pio_balanced_network_release(network_before);
23286            pio_calculation_instance_release(instance_before);
23287            pio_value_release(value_before);
23288            pio_balanced_network_release(network_after);
23289            pio_calculation_instance_release(instance_after);
23290            pio_value_release(value_after);
23291            pio_update_report_release(report);
23292            pio_active_power_release(power);
23293            pio_module_release(module);
23294        }
23295    }
23296
23297    #[test]
23298    fn scuc_instance_exposes_typed_scheduling_inputs() {
23299        unsafe {
23300            let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
23301                .join("../tests/data/goc3/goc3_small.json");
23302            let source = std::fs::read_to_string(path).unwrap().replacen(
23303                "\"startup_states\": []",
23304                "\"startup_states\": [[2.0, 3.0]]",
23305                1,
23306            );
23307            let parsed =
23308                goc3_instance(Source::from_memory("goc3_small.json", source.into_bytes()).unwrap());
23309            let module = module_handle(powerio::PioModule::new(PioValue::AcScucInstance(parsed)));
23310            let value = pio_module_value(module);
23311            let mut error = std::ptr::null_mut();
23312            let instance = pio_value_ac_scuc_instance(value, &mut error);
23313            assert!(!instance.is_null(), "{}", error_text(error));
23314            pio_value_release(value);
23315            pio_module_release(module);
23316
23317            let mut dimensions = std::mem::MaybeUninit::<PioScucDimensionsView>::uninit();
23318            assert!(pio_ac_scuc_instance_dimensions(
23319                instance,
23320                dimensions.as_mut_ptr(),
23321                &mut error,
23322            ));
23323            let dimensions = dimensions.assume_init();
23324            assert_eq!(dimensions.period_count, 2);
23325            assert_eq!(dimensions.device_count, 2);
23326            assert_eq!(dimensions.producer_count, 1);
23327            assert_eq!(dimensions.consumer_count, 1);
23328            assert_eq!(dimensions.shunt_count, 1);
23329            assert_eq!(dimensions.branch_switching_cost_count, 3);
23330            assert_eq!(dimensions.transformer_control_count, 1);
23331            assert_eq!(dimensions.active_reserve_zone_count, 1);
23332            assert_eq!(dimensions.reactive_reserve_zone_count, 1);
23333            assert_eq!(dimensions.contingency_count, 3);
23334
23335            let durations = pio_ac_scuc_instance_interval_durations(instance);
23336            assert_eq!(
23337                std::slice::from_raw_parts(durations.data, durations.len),
23338                &[1.0, 1.0]
23339            );
23340
23341            let mut violation = std::mem::MaybeUninit::<PioScucViolationCostView>::uninit();
23342            assert!(pio_ac_scuc_instance_violation_costs(
23343                instance,
23344                violation.as_mut_ptr(),
23345                &mut error,
23346            ));
23347            let violation = violation.assume_init();
23348            assert_eq!(violation.active_power_balance, 1.0);
23349            assert_eq!(violation.reactive_power_balance, 1.0);
23350            assert_eq!(violation.branch_thermal_limit, 1.0);
23351            assert_eq!(violation.energy_requirement, 1.0);
23352
23353            let mut producer = std::mem::MaybeUninit::<PioScucDeviceView>::uninit();
23354            assert!(pio_ac_scuc_instance_device_at(
23355                instance,
23356                0,
23357                producer.as_mut_ptr(),
23358                &mut error,
23359            ));
23360            let producer = producer.assume_init();
23361            assert_eq!(view_text(producer.id.component_type), "generator");
23362            assert_eq!(view_text(producer.id.local_id), "sd_00");
23363            assert_eq!(view_text(producer.kind), "producer");
23364            assert!(producer.initial_on_status);
23365            assert_eq!(producer.minimum_up_time_hours, 1.0);
23366            assert_eq!(producer.minimum_down_time_hours, 1.0);
23367            assert_eq!(producer.initial_commitment.accumulated_up_time_hours, 4.0);
23368            assert_eq!(producer.initial_commitment.accumulated_down_time_hours, 0.0);
23369            assert_eq!(producer.startup_cost_adjustment_count, 1);
23370            assert_eq!(producer.startup_limit_count, 1);
23371            assert_eq!(producer.energy_upper_bound_count, 1);
23372            assert_eq!(producer.energy_lower_bound_count, 1);
23373            assert_eq!(producer.period_count, 2);
23374
23375            let uid = "sd_00";
23376            let mut found_device = std::mem::MaybeUninit::<PioScucDeviceView>::uninit();
23377            assert!(pio_ac_scuc_instance_device_get(
23378                instance,
23379                uid.as_ptr().cast(),
23380                uid.len(),
23381                found_device.as_mut_ptr(),
23382                &mut error,
23383            ));
23384            assert_eq!(view_text(found_device.assume_init().id.local_id), "sd_00");
23385
23386            let mut adjustment =
23387                std::mem::MaybeUninit::<PioScucStartupCostAdjustmentView>::uninit();
23388            assert!(pio_ac_scuc_instance_device_startup_cost_adjustment_at(
23389                instance,
23390                0,
23391                0,
23392                adjustment.as_mut_ptr(),
23393                &mut error,
23394            ));
23395            assert_eq!(adjustment.assume_init().maximum_down_time_hours, 3.0);
23396
23397            let mut limit = std::mem::MaybeUninit::<PioScucStartupLimitView>::uninit();
23398            assert!(pio_ac_scuc_instance_device_startup_limit_at(
23399                instance,
23400                0,
23401                0,
23402                limit.as_mut_ptr(),
23403                &mut error,
23404            ));
23405            assert_eq!(limit.assume_init().maximum_startups, 1);
23406
23407            let mut energy = std::mem::MaybeUninit::<PioScucEnergyRequirementView>::uninit();
23408            assert!(pio_ac_scuc_instance_device_energy_upper_bound_at(
23409                instance,
23410                0,
23411                0,
23412                energy.as_mut_ptr(),
23413                &mut error,
23414            ));
23415            assert_eq!(energy.assume_init().energy_pu, 9.0);
23416            assert!(pio_ac_scuc_instance_device_energy_lower_bound_at(
23417                instance,
23418                0,
23419                0,
23420                energy.as_mut_ptr(),
23421                &mut error,
23422            ));
23423            assert_eq!(energy.assume_init().energy_pu, 1.0);
23424
23425            let mut period = std::mem::MaybeUninit::<PioScucDevicePeriodView>::uninit();
23426            assert!(pio_ac_scuc_instance_device_period_at(
23427                instance,
23428                0,
23429                0,
23430                period.as_mut_ptr(),
23431                &mut error,
23432            ));
23433            let period = period.assume_init();
23434            assert!(period.on_status_min);
23435            assert!(period.on_status_max);
23436            assert_eq!(period.active_power_min_pu, 2.0);
23437            assert_eq!(period.active_power_max_pu, 5.0);
23438            assert_eq!(period.energy_cost_block_count, 1);
23439
23440            let mut cost_block = std::mem::MaybeUninit::<PioScucEnergyCostBlockView>::uninit();
23441            assert!(pio_ac_scuc_instance_device_energy_cost_block_at(
23442                instance,
23443                0,
23444                0,
23445                0,
23446                cost_block.as_mut_ptr(),
23447                &mut error,
23448            ));
23449            let cost_block = cost_block.assume_init();
23450            assert_eq!(cost_block.marginal_cost, 10.0);
23451            assert_eq!(cost_block.block_size_pu, 5.0);
23452
23453            let mut shunt = std::mem::MaybeUninit::<PioScucShuntView>::uninit();
23454            assert!(pio_ac_scuc_instance_shunt_at(
23455                instance,
23456                0,
23457                shunt.as_mut_ptr(),
23458                &mut error,
23459            ));
23460            let shunt = shunt.assume_init();
23461            assert_eq!(view_text(shunt.id.local_id), "sh_00");
23462            assert_eq!(shunt.conductance_per_step_pu, 0.0);
23463            assert_eq!(shunt.susceptance_per_step_pu, 3.0);
23464            assert_eq!(
23465                (shunt.step_min, shunt.initial_step, shunt.step_max),
23466                (0, 1, 4)
23467            );
23468            let shunt_uid = "sh_00";
23469            let mut found_shunt = std::mem::MaybeUninit::<PioScucShuntView>::uninit();
23470            assert!(pio_ac_scuc_instance_shunt_get(
23471                instance,
23472                shunt_uid.as_ptr().cast(),
23473                shunt_uid.len(),
23474                found_shunt.as_mut_ptr(),
23475                &mut error,
23476            ));
23477            assert_eq!(view_text(found_shunt.assume_init().id.local_id), "sh_00");
23478
23479            let mut switching = std::mem::MaybeUninit::<PioScucBranchSwitchingCostView>::uninit();
23480            assert!(pio_ac_scuc_instance_branch_switching_cost_at(
23481                instance,
23482                0,
23483                switching.as_mut_ptr(),
23484                &mut error,
23485            ));
23486            let switching = switching.assume_init();
23487            assert_eq!(view_text(switching.id.component_type), "branch");
23488            assert_eq!(view_text(switching.id.local_id), "acl_00");
23489
23490            let mut control = std::mem::MaybeUninit::<PioScucTransformerControlView>::uninit();
23491            assert!(pio_ac_scuc_instance_transformer_control_at(
23492                instance,
23493                0,
23494                control.as_mut_ptr(),
23495                &mut error,
23496            ));
23497            let control = control.assume_init();
23498            assert_eq!(view_text(control.id.component_type), "transformer");
23499            assert_eq!(view_text(control.id.local_id), "xf_00");
23500            assert_eq!((control.tap_ratio_min, control.tap_ratio_max), (1.0, 1.0));
23501
23502            let mut active_zone = std::mem::MaybeUninit::<PioScucActiveReserveZoneView>::uninit();
23503            assert!(pio_ac_scuc_instance_active_reserve_zone_at(
23504                instance,
23505                0,
23506                active_zone.as_mut_ptr(),
23507                &mut error,
23508            ));
23509            assert_eq!(active_zone.assume_init().bus_count, 2);
23510            let mut active_period =
23511                std::mem::MaybeUninit::<PioScucActiveReservePeriodView>::uninit();
23512            assert!(pio_ac_scuc_instance_active_reserve_zone_period_at(
23513                instance,
23514                0,
23515                0,
23516                active_period.as_mut_ptr(),
23517                &mut error,
23518            ));
23519            assert_eq!(active_period.assume_init().ramping_up_requirement_pu, 0.0);
23520            let mut reserve_bus = std::mem::MaybeUninit::<PioComponentIdView>::uninit();
23521            assert!(pio_ac_scuc_instance_active_reserve_zone_bus_at(
23522                instance,
23523                0,
23524                0,
23525                reserve_bus.as_mut_ptr(),
23526                &mut error,
23527            ));
23528            assert_eq!(view_text(reserve_bus.assume_init().local_id), "bus_00");
23529
23530            let mut reactive_zone =
23531                std::mem::MaybeUninit::<PioScucReactiveReserveZoneView>::uninit();
23532            assert!(pio_ac_scuc_instance_reactive_reserve_zone_at(
23533                instance,
23534                0,
23535                reactive_zone.as_mut_ptr(),
23536                &mut error,
23537            ));
23538            assert_eq!(reactive_zone.assume_init().bus_count, 2);
23539            let mut reactive_period =
23540                std::mem::MaybeUninit::<PioScucReactiveReservePeriodView>::uninit();
23541            assert!(pio_ac_scuc_instance_reactive_reserve_zone_period_at(
23542                instance,
23543                0,
23544                0,
23545                reactive_period.as_mut_ptr(),
23546                &mut error,
23547            ));
23548            assert_eq!(
23549                reactive_period.assume_init().reactive_up_requirement_pu,
23550                0.0
23551            );
23552            assert!(pio_ac_scuc_instance_reactive_reserve_zone_bus_at(
23553                instance,
23554                0,
23555                1,
23556                reserve_bus.as_mut_ptr(),
23557                &mut error,
23558            ));
23559            assert_eq!(view_text(reserve_bus.assume_init().local_id), "bus_01");
23560
23561            let mut contingency = std::mem::MaybeUninit::<PioScucContingencyView>::uninit();
23562            assert!(pio_ac_scuc_instance_contingency_at(
23563                instance,
23564                0,
23565                contingency.as_mut_ptr(),
23566                &mut error,
23567            ));
23568            let first_contingency = contingency.assume_init();
23569            assert_eq!(
23570                view_text(first_contingency.id.component_type),
23571                "contingency"
23572            );
23573            assert_eq!(view_text(first_contingency.id.local_id), "ctg_00");
23574            let contingency_uid = "ctg_02";
23575            assert!(pio_ac_scuc_instance_contingency_get(
23576                instance,
23577                contingency_uid.as_ptr().cast(),
23578                contingency_uid.len(),
23579                contingency.as_mut_ptr(),
23580                &mut error,
23581            ));
23582            assert_eq!(view_text(contingency.assume_init().id.local_id), "ctg_02");
23583            let mut component = std::mem::MaybeUninit::<PioScucContingencyComponentView>::uninit();
23584            assert!(pio_ac_scuc_instance_contingency_component_at(
23585                instance,
23586                0,
23587                0,
23588                component.as_mut_ptr(),
23589                &mut error,
23590            ));
23591            let component = component.assume_init();
23592            assert_eq!(view_text(component.id.component_type), "branch");
23593            assert_eq!(view_text(component.id.local_id), "acl_00");
23594
23595            pio_calculation_instance_release(instance);
23596        }
23597    }
23598
23599    #[test]
23600    fn one_parse_reads_a_goc3_problem_and_solution_directory() {
23601        unsafe {
23602            let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/data/goc3");
23603            let path = path.to_string_lossy();
23604            let mut error = std::ptr::null_mut();
23605            let source = pio_source_open(path.as_ptr().cast(), path.len(), &mut error);
23606            assert!(!source.is_null(), "{}", error_text(error));
23607            let module = pio_parse(source, std::ptr::null(), 0, &mut error);
23608            pio_source_release(source);
23609            assert!(!module.is_null(), "{}", error_text(error));
23610
23611            let inner = PioModule::get(module).unwrap();
23612            assert_eq!(inner.module.value().type_name(), "powerio.AcScucSolution");
23613            assert_eq!(inner.module.sources().len(), 2);
23614
23615            let value = pio_module_value(module);
23616            let solution = pio_value_ac_scuc_solution(value, &mut error);
23617            pio_value_release(value);
23618            assert!(!solution.is_null(), "{}", error_text(error));
23619            assert_eq!(pio_ac_scuc_solution_time_count(solution), 2);
23620            let quantity = "bus_voltage_magnitude";
23621            let row = pio_ac_scuc_solution_get_values_at(
23622                solution,
23623                quantity.as_ptr().cast(),
23624                quantity.len(),
23625                0,
23626                &mut error,
23627            );
23628            assert!(!row.is_null(), "{}", error_text(error));
23629            let values = pio_vector_values(row);
23630            assert_eq!(
23631                std::slice::from_raw_parts(values.data, values.len),
23632                &[1.0, 0.99]
23633            );
23634            pio_vector_release(row);
23635
23636            for quantity in [
23637                "bus_voltage_magnitude",
23638                "bus_voltage_angle",
23639                "shunt_step",
23640                "ac_line_on_status",
23641                "transformer_tap_ratio",
23642                "transformer_phase_shift",
23643                "transformer_on_status",
23644                "dc_line_from_active_power",
23645                "dc_line_from_reactive_power",
23646                "dc_line_to_reactive_power",
23647                "device_on_status",
23648                "device_startup_status",
23649                "device_shutdown_status",
23650                "device_active_power",
23651                "device_reactive_power",
23652                "regulation_reserve_up",
23653                "regulation_reserve_down",
23654                "synchronized_reserve",
23655                "nonsynchronized_reserve",
23656                "ramping_reserve_up_online",
23657                "ramping_reserve_up_offline",
23658                "ramping_reserve_down_online",
23659                "ramping_reserve_down_offline",
23660                "reactive_reserve_up",
23661                "reactive_reserve_down",
23662            ] {
23663                let vector = pio_ac_scuc_solution_get_values_at(
23664                    solution,
23665                    quantity.as_ptr().cast(),
23666                    quantity.len(),
23667                    0,
23668                    &mut error,
23669                );
23670                assert!(!vector.is_null(), "{quantity}: {}", error_text(error));
23671                pio_vector_release(vector);
23672            }
23673
23674            pio_calculation_solution_release(solution);
23675            pio_module_release(module);
23676        }
23677    }
23678
23679    #[test]
23680    fn one_parse_and_one_emit_cover_powsybl_formats() {
23681        unsafe {
23682            let module = parse_case9();
23683            let directory = tempfile::tempdir().unwrap();
23684
23685            for (format, output_name) in [
23686                ("xiidm", "case9.xiidm"),
23687                ("cgmes", "case9-cgmes"),
23688                ("psse-rawx", "case9.rawx"),
23689                ("ucte", "case9.uct"),
23690            ] {
23691                let output = directory.path().join(output_name);
23692                let output_text = output.to_string_lossy();
23693                let mut error = std::ptr::null_mut();
23694                let destination = pio_destination_path(
23695                    output_text.as_ptr().cast(),
23696                    output_text.len(),
23697                    &mut error,
23698                );
23699                assert!(!destination.is_null(), "{}", error_text(error));
23700                let result = pio_emit(
23701                    module,
23702                    format.as_ptr().cast(),
23703                    format.len(),
23704                    destination,
23705                    &mut error,
23706                );
23707                pio_destination_release(destination);
23708                assert!(!result.is_null(), "{format}: {}", error_text(error));
23709                pio_emit_result_release(result);
23710
23711                let source =
23712                    pio_source_open(output_text.as_ptr().cast(), output_text.len(), &mut error);
23713                assert!(!source.is_null(), "{format}: {}", error_text(error));
23714                let reparsed = pio_parse(source, std::ptr::null(), 0, &mut error);
23715                pio_source_release(source);
23716                assert!(!reparsed.is_null(), "{format}: {}", error_text(error));
23717                assert_eq!(
23718                    PioModule::get(reparsed).unwrap().module.value().type_name(),
23719                    "powerio.BalancedNetwork"
23720                );
23721                pio_module_release(reparsed);
23722            }
23723
23724            pio_module_release(module);
23725        }
23726    }
23727
23728    #[test]
23729    fn scuc_solution_exposes_every_device_status_and_offline_reserve() {
23730        unsafe {
23731            let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
23732                .join("../tests/data/goc3/goc3_small.json");
23733            let parsed = goc3_instance(Source::open(path).unwrap());
23734            let mut outputs = powerio_prob::ScucDeviceOutputs::default();
23735            outputs.startup_status = vec![vec![false, true], vec![true, false]];
23736            outputs.shutdown_status = vec![vec![true, false], vec![false, true]];
23737            outputs.p_ramp_res_up_offline = vec![vec![1.25, 1.75], vec![1.5, 2.0]];
23738            outputs.p_ramp_res_down_offline = vec![vec![2.25, 2.75], vec![2.5, 3.0]];
23739            let solution = powerio_prob::AcScucSolution::new(
23740                Arc::new(parsed),
23741                powerio_prob::Termination::Converged,
23742                powerio_prob::ScucNetworkOutputs::default(),
23743                outputs,
23744                None,
23745            )
23746            .unwrap();
23747            let module = module_handle(powerio::PioModule::new(PioValue::AcScucSolution(solution)));
23748            let value = pio_module_value(module);
23749            let mut error = std::ptr::null_mut();
23750            let solution = pio_value_ac_scuc_solution(value, &mut error);
23751            assert!(!solution.is_null(), "{}", error_text(error));
23752            pio_value_release(value);
23753            pio_module_release(module);
23754
23755            for (quantity, expected) in [
23756                ("device_startup_status", [0.0, 1.0]),
23757                ("device_shutdown_status", [1.0, 0.0]),
23758                ("ramping_reserve_up_offline", [1.25, 1.75]),
23759                ("ramping_reserve_down_offline", [2.25, 2.75]),
23760            ] {
23761                let vector = pio_ac_scuc_solution_get_values_at(
23762                    solution,
23763                    quantity.as_ptr().cast(),
23764                    quantity.len(),
23765                    0,
23766                    &mut error,
23767                );
23768                assert!(!vector.is_null(), "{}", error_text(error));
23769                let view = pio_vector_values(vector);
23770                assert_eq!(view.len, 2);
23771                assert_eq!(std::slice::from_raw_parts(view.data, view.len), &expected);
23772                pio_vector_release(vector);
23773            }
23774
23775            pio_calculation_solution_release(solution);
23776        }
23777    }
23778
23779    #[test]
23780    fn update_detaches_from_retained_value_views() {
23781        unsafe {
23782            let module = parse_case9();
23783            let value = pio_module_value(module);
23784            let mut error = std::ptr::null_mut();
23785            let old_network = pio_value_balanced_network(value, &mut error);
23786            pio_value_release(value);
23787            assert!(!old_network.is_null(), "{}", error_text(error));
23788
23789            let old = PioBalancedNetwork::get(old_network)
23790                .and_then(BalancedNetworkInner::network)
23791                .unwrap();
23792            let load = &old.loads()[0];
23793            let old_active_power = load.p;
23794            let local_id = load.uid.as_deref().unwrap().to_owned();
23795
23796            let component = pio_component_id_new(
23797                c"load".as_ptr(),
23798                4,
23799                local_id.as_ptr().cast(),
23800                local_id.len(),
23801                &mut error,
23802            );
23803            let replacement = pio_active_power_from_megawatts(old_active_power + 1.0);
23804            let operating = pio_operating_point_update_set_load_active_power(
23805                component,
23806                std::ptr::null(),
23807                0,
23808                replacement,
23809                &mut error,
23810            );
23811            let update = pio_calculation_update_from_operating_point(operating, &mut error);
23812            let updates = [update.cast_const()];
23813            let report = pio_apply_updates(module, updates.as_ptr(), updates.len(), &mut error);
23814            assert!(!report.is_null(), "{}", error_text(error));
23815            assert_eq!(pio_update_report_len(report), 1);
23816            assert!(!pio_update_report_connectivity_changed(report));
23817
23818            let new_value = pio_module_value(module);
23819            let new_network = pio_value_balanced_network(new_value, &mut error);
23820            let new = PioBalancedNetwork::get(new_network)
23821                .and_then(BalancedNetworkInner::network)
23822                .unwrap();
23823            assert_eq!(new.loads()[0].p, old_active_power + 1.0);
23824            assert_eq!(
23825                PioBalancedNetwork::get(old_network)
23826                    .and_then(BalancedNetworkInner::network)
23827                    .unwrap()
23828                    .loads()[0]
23829                    .p,
23830                old_active_power,
23831            );
23832
23833            pio_balanced_network_release(new_network);
23834            pio_value_release(new_value);
23835            pio_update_report_release(report);
23836            pio_calculation_update_release(update);
23837            pio_operating_point_update_release(operating);
23838            pio_active_power_release(replacement);
23839            pio_component_id_release(component);
23840            pio_balanced_network_release(old_network);
23841            pio_module_release(module);
23842        }
23843    }
23844
23845    #[test]
23846    fn dc_calculations_return_named_generic_handles() {
23847        unsafe {
23848            let module = parse_case9();
23849            let value = pio_module_value(module);
23850            let mut error = std::ptr::null_mut();
23851            let network = pio_value_balanced_network(value, &mut error);
23852            let incidence = pio_calc_incidence_matrix(network, std::ptr::null(), 0, &mut error);
23853            assert!(!incidence.is_null(), "{}", error_text(error));
23854            assert_eq!(pio_sparse_matrix_rows(incidence), 9);
23855            assert_eq!(pio_sparse_matrix_columns(incidence), 9);
23856            assert_eq!(pio_sparse_matrix_values(incidence).len, 18);
23857
23858            let branch = pio_calc_branch_susceptances(
23859                network,
23860                c"reactance_only".as_ptr(),
23861                "reactance_only".len(),
23862                &mut error,
23863            );
23864            assert!(!branch.is_null(), "{}", error_text(error));
23865            assert_eq!(pio_vector_values(branch).len, 9);
23866
23867            pio_vector_release(branch);
23868            pio_sparse_matrix_release(incidence);
23869            pio_balanced_network_release(network);
23870            pio_value_release(value);
23871            pio_module_release(module);
23872        }
23873    }
23874
23875    /// Three buses and two branches; branch row 0 states r = x = 0.
23876    const ZERO_IMPEDANCE_CASE: &str = "\
23877function mpc = case3_zero_impedance
23878mpc.version = '2';
23879mpc.baseMVA = 100;
23880mpc.bus = [
23881  1 3  0 0 0 0 1 1 0 230 1 1.1 0.9;
23882  2 1 50 0 0 0 1 1 0 230 1 1.1 0.9;
23883  3 1 50 0 0 0 1 1 0 230 1 1.1 0.9;
23884];
23885mpc.gen = [
23886  1 100 0 100 -100 1 100 1 200 0;
23887];
23888mpc.branch = [
23889  1 2 0 0    0.02 100 100 100 0 0 1 -60 60;
23890  2 3 0 0.1  0    100 100 100 0 0 1 -60 60;
23891];
23892mpc.gencost = [
23893  2 0 0 3 0.01 10 0;
23894];
23895";
23896
23897    #[test]
23898    fn dc_operators_handle_names_its_axes_and_skips_a_zero_impedance_branch() {
23899        unsafe {
23900            let name = b"zero.m";
23901            let format = b"matpower";
23902            let mut error = std::ptr::null_mut();
23903            let source = pio_source_from_memory(
23904                name.as_ptr().cast(),
23905                name.len(),
23906                ZERO_IMPEDANCE_CASE.as_ptr(),
23907                ZERO_IMPEDANCE_CASE.len(),
23908                &mut error,
23909            );
23910            assert!(!source.is_null(), "{}", error_text(error));
23911            let module = pio_parse(source, format.as_ptr().cast(), format.len(), &mut error);
23912            pio_source_release(source);
23913            assert!(!module.is_null(), "{}", error_text(error));
23914            let value = pio_module_value(module);
23915            let network = pio_value_balanced_network(value, &mut error);
23916            assert!(!network.is_null(), "{}", error_text(error));
23917
23918            // The zero impedance branch fails the build unless it is skipped.
23919            let refused = pio_calc_dc_operators(network, std::ptr::null(), 0, false, &mut error);
23920            assert!(refused.is_null());
23921            assert_eq!(
23922                view_text(pio_error_code(error)),
23923                "BUILD.OPERATOR.ZERO_IMPEDANCE"
23924            );
23925            pio_error_release(error);
23926            error = std::ptr::null_mut();
23927
23928            let operators = pio_calc_dc_operators(network, std::ptr::null(), 0, true, &mut error);
23929            assert!(!operators.is_null(), "{}", error_text(error));
23930            assert_eq!(pio_dc_operators_n_buses(operators), 3);
23931            assert_eq!(pio_dc_operators_n_branches(operators), 1);
23932
23933            let bus_ids = pio_dc_operators_bus_ids(operators);
23934            assert_eq!(
23935                std::slice::from_raw_parts(bus_ids.data, bus_ids.len),
23936                &[1, 2, 3]
23937            );
23938            // Original table indices: branch row 0 left the axis.
23939            let branch_rows = pio_dc_operators_branch_rows(operators);
23940            assert_eq!(
23941                std::slice::from_raw_parts(branch_rows.data, branch_rows.len),
23942                &[1]
23943            );
23944            let skipped = pio_dc_operators_skipped_branch_rows(operators);
23945            assert_eq!(std::slice::from_raw_parts(skipped.data, skipped.len), &[0]);
23946            assert_ne!(
23947                view_text(pio_dc_operators_branch_identity(operators, 0)),
23948                ""
23949            );
23950            let past_end = pio_dc_operators_branch_identity(operators, 1);
23951            assert!(past_end.data.is_null());
23952            assert_eq!(past_end.len, 0);
23953
23954            let incidence = pio_dc_operators_incidence_matrix(operators, &mut error);
23955            assert!(!incidence.is_null(), "{}", error_text(error));
23956            assert_eq!(pio_sparse_matrix_rows(incidence), 1);
23957            assert_eq!(pio_sparse_matrix_columns(incidence), 3);
23958
23959            let bus_susceptance = pio_dc_operators_bus_susceptance_matrix(operators, &mut error);
23960            assert!(!bus_susceptance.is_null(), "{}", error_text(error));
23961            assert_eq!(pio_sparse_matrix_rows(bus_susceptance), 3);
23962            assert_eq!(pio_sparse_matrix_columns(bus_susceptance), 3);
23963
23964            let branch_flow = pio_dc_operators_branch_flow_matrix(operators, &mut error);
23965            assert!(!branch_flow.is_null(), "{}", error_text(error));
23966            assert_eq!(pio_sparse_matrix_rows(branch_flow), 1);
23967            assert_eq!(pio_sparse_matrix_columns(branch_flow), 3);
23968
23969            let susceptances = pio_dc_operators_branch_susceptances(operators, &mut error);
23970            assert!(!susceptances.is_null(), "{}", error_text(error));
23971            assert_eq!(pio_vector_values(susceptances).len, 1);
23972
23973            let branch_shift = pio_dc_operators_branch_phase_shift_injection(operators, &mut error);
23974            assert!(!branch_shift.is_null(), "{}", error_text(error));
23975            assert_eq!(pio_vector_values(branch_shift).len, 1);
23976
23977            let bus_shift = pio_dc_operators_bus_phase_shift_injection(operators, &mut error);
23978            assert!(!bus_shift.is_null(), "{}", error_text(error));
23979            assert_eq!(pio_vector_values(bus_shift).len, 3);
23980
23981            let angles = [0.0_f64, 0.0, 0.1];
23982            let flows = pio_dc_operators_branch_flow_dc(
23983                operators,
23984                angles.as_ptr(),
23985                angles.len(),
23986                &mut error,
23987            );
23988            assert!(!flows.is_null(), "{}", error_text(error));
23989            assert_eq!(pio_vector_values(flows).len, 1);
23990
23991            let injections = pio_dc_operators_bus_injection_dc(
23992                operators,
23993                angles.as_ptr(),
23994                angles.len(),
23995                &mut error,
23996            );
23997            assert!(!injections.is_null(), "{}", error_text(error));
23998            assert_eq!(pio_vector_values(injections).len, 3);
23999
24000            // A retained handle names the same axes and outlives one release.
24001            let retained = pio_dc_operators_retain(operators);
24002            assert!(!retained.is_null());
24003            pio_dc_operators_release(retained);
24004            assert_eq!(pio_dc_operators_n_branches(operators), 1);
24005
24006            // A NULL handle reads as an empty axis rather than faulting.
24007            assert_eq!(pio_dc_operators_n_buses(std::ptr::null()), 0);
24008            assert_eq!(pio_dc_operators_n_branches(std::ptr::null()), 0);
24009            let empty = pio_dc_operators_bus_ids(std::ptr::null());
24010            assert!(empty.data.is_null());
24011            assert_eq!(empty.len, 0);
24012
24013            pio_vector_release(injections);
24014            pio_vector_release(flows);
24015            pio_vector_release(bus_shift);
24016            pio_vector_release(branch_shift);
24017            pio_vector_release(susceptances);
24018            pio_sparse_matrix_release(branch_flow);
24019            pio_sparse_matrix_release(bus_susceptance);
24020            pio_sparse_matrix_release(incidence);
24021            pio_dc_operators_release(operators);
24022            pio_balanced_network_release(network);
24023            pio_value_release(value);
24024            pio_module_release(module);
24025        }
24026    }
24027
24028    #[test]
24029    fn powerio_ir_uses_serialize_and_deserialize() {
24030        unsafe {
24031            let module = parse_case9();
24032            let mut error = std::ptr::null_mut();
24033            let destination = pio_destination_memory(
24034                c"case.pio.json".as_ptr(),
24035                "case.pio.json".len(),
24036                &mut error,
24037            );
24038            let result = pio_module_serialize(module, destination, &mut error);
24039            assert!(!result.is_null(), "{}", error_text(error));
24040            let artifact = pio_emit_result_artifact(result, 0, &mut error);
24041            let bytes = pio_artifact_bytes(artifact);
24042            let source = pio_source_from_memory(
24043                c"case.pio.json".as_ptr(),
24044                "case.pio.json".len(),
24045                bytes.data,
24046                bytes.len,
24047                &mut error,
24048            );
24049            let decoded = pio_module_deserialize(source, &mut error);
24050            assert!(!decoded.is_null(), "{}", error_text(error));
24051
24052            pio_module_release(decoded);
24053            pio_source_release(source);
24054            pio_artifact_release(artifact);
24055            pio_emit_result_release(result);
24056            pio_destination_release(destination);
24057            pio_module_release(module);
24058        }
24059    }
24060}