1#![allow(clippy::missing_safety_doc)]
10
11use std::ffi::c_char;
12use std::panic::{AssertUnwindSafe, catch_unwind};
13use std::path::PathBuf;
14use std::sync::Arc;
15
16use powerio::{
17 BalancedNetwork, Destination, Diagnostic, EmitResult, EmittedOutput, PioScenarioSet,
18 PioTimeSeries, PioValue, Source,
19};
20use powerio_core::{ComponentId, HistoryEntry, HistoryId, HistoryKind, Producer};
21use powerio_matrix::{
22 AcOpfAssemblyOptions, AcOpfPreparation, AnalysisBranchSource, DcOperators,
23 DcOpfAssemblyOptions, DcOpfPreparation, PreparedObjective, SparseMatrix, Units,
24 build_ac_opf_preparation, build_dc_opf_preparation,
25};
26use powerio_prob::{
27 ActivePower, ActivePowerUnit, ApparentPower, ApparentPowerUnit, CalculationUpdate,
28 DcPfInstance, LoadAllocation, NetworkUpdate, OperatingPointUpdate, ReactivePower,
29 ReactivePowerUnit, UpdateChange, UpdatedField, apply_bus_load_active_power, apply_updates,
30};
31use powerio_tx::BranchSusceptanceFormula;
32
33use crate::diagnostics::codes;
34
35pub mod diagnostics;
36
37pub const PIO_ABI_VERSION: u32 = 7;
39
40#[repr(C)]
44#[derive(Clone, Copy)]
45pub struct PioStringView {
46 pub data: *const c_char,
47 pub len: usize,
48}
49
50impl PioStringView {
51 const EMPTY: Self = Self {
52 data: std::ptr::null(),
53 len: 0,
54 };
55
56 fn new(text: &str) -> Self {
57 Self {
58 data: text.as_ptr().cast(),
59 len: text.len(),
60 }
61 }
62}
63
64#[repr(C)]
66#[derive(Clone, Copy)]
67pub struct PioDiagnosticSpanView {
68 pub source: PioStringView,
69 pub byte_start: u64,
70 pub byte_end: u64,
71}
72
73#[repr(C)]
75#[derive(Clone, Copy)]
76pub struct PioModuleProducerView {
77 pub name: PioStringView,
78 pub version: PioStringView,
79}
80
81#[repr(C)]
83#[derive(Clone, Copy)]
84pub struct PioModuleSourceView {
85 pub id: PioStringView,
86 pub name: PioStringView,
87 pub byte_length: u64,
88 pub format: PioStringView,
89 pub has_format: bool,
90 pub digest_algorithm: PioStringView,
91 pub digest: PioStringView,
92 pub has_digest: bool,
93}
94
95#[repr(C)]
97#[derive(Clone, Copy)]
98pub struct PioSourceSpanView {
99 pub source: PioStringView,
100 pub byte_start: u64,
101 pub byte_end: u64,
102}
103
104#[repr(C)]
106#[derive(Clone, Copy)]
107pub struct PioModuleSourceMapEntryView {
108 pub target: PioStringView,
109 pub relation: PioStringView,
110 pub span_count: usize,
111}
112
113#[repr(C)]
115#[derive(Clone, Copy)]
116pub struct PioModuleHistoryEntryView {
117 pub id: PioStringView,
118 pub kind: PioStringView,
119 pub name: PioStringView,
120 pub input_type: PioStringView,
121 pub has_input_type: bool,
122 pub output_type: PioStringView,
123 pub has_output_type: bool,
124 pub parameter_count: usize,
125 pub assumption_count: usize,
126 pub loss_count: usize,
127}
128
129#[repr(C)]
131#[derive(Clone, Copy)]
132pub struct PioModuleHistoryParameterView {
133 pub name: PioStringView,
134 pub value_kind: PioStringView,
135}
136
137#[repr(C)]
139#[derive(Clone, Copy)]
140pub struct PioModuleExtensionView {
141 pub namespace: PioStringView,
142 pub value_kind: PioStringView,
143}
144
145#[repr(C)]
147#[derive(Clone, Copy)]
148pub struct PioJsonValueView {
149 pub kind: PioStringView,
150 pub boolean_value: bool,
151 pub number_kind: PioStringView,
152 pub signed_integer_value: i64,
153 pub unsigned_integer_value: u64,
154 pub floating_point_value: f64,
155 pub string_value: PioStringView,
156 pub element_count: usize,
157}
158
159#[repr(C)]
161#[derive(Clone, Copy)]
162pub struct PioJsonObjectEntryView {
163 pub key: PioStringView,
164 pub value_kind: PioStringView,
165}
166
167#[repr(C)]
169#[derive(Clone, Copy)]
170pub struct PioByteView {
171 pub data: *const u8,
172 pub len: usize,
173}
174
175impl PioByteView {
176 const EMPTY: Self = Self {
177 data: std::ptr::null(),
178 len: 0,
179 };
180
181 fn new(bytes: &[u8]) -> Self {
182 Self {
183 data: bytes.as_ptr(),
184 len: bytes.len(),
185 }
186 }
187}
188
189#[repr(C)]
191#[derive(Clone, Copy)]
192pub struct PioF64View {
193 pub data: *const f64,
194 pub len: usize,
195}
196
197impl PioF64View {
198 const EMPTY: Self = Self {
199 data: std::ptr::null(),
200 len: 0,
201 };
202
203 fn new(values: &[f64]) -> Self {
204 Self {
205 data: values.as_ptr(),
206 len: values.len(),
207 }
208 }
209}
210
211#[repr(C)]
213#[derive(Clone, Copy)]
214pub struct PioSizeView {
215 pub data: *const usize,
216 pub len: usize,
217}
218
219impl PioSizeView {
220 const EMPTY: Self = Self {
221 data: std::ptr::null(),
222 len: 0,
223 };
224
225 fn new(values: &[usize]) -> Self {
226 Self {
227 data: values.as_ptr(),
228 len: values.len(),
229 }
230 }
231}
232
233#[repr(C)]
235#[derive(Clone, Copy)]
236pub struct PioBalancedLocationView {
237 pub x: f64,
238 pub y: f64,
239 pub kind: PioStringView,
240 pub has_kind: bool,
241}
242
243#[repr(C)]
245#[derive(Clone, Copy)]
246pub struct PioBalancedGeoView {
247 pub has_geo: bool,
248 pub space: PioStringView,
249 pub crs: PioStringView,
250 pub has_crs: bool,
251 pub kind: PioStringView,
252 pub has_kind: bool,
253 pub has_canvas: bool,
254 pub canvas_width: f64,
255 pub has_canvas_width: bool,
256 pub canvas_height: f64,
257 pub has_canvas_height: bool,
258 pub canvas_units: PioStringView,
259 pub has_canvas_units: bool,
260}
261
262#[repr(C)]
264#[derive(Clone, Copy)]
265pub struct PioBalancedBusView {
266 pub component_id: PioStringView,
267 pub has_component_id: bool,
268 pub id: usize,
269 pub bus_type: PioStringView,
270 pub vm_pu: f64,
271 pub va_degrees: f64,
272 pub base_kv: f64,
273 pub vmax_pu: f64,
274 pub vmin_pu: f64,
275 pub has_emergency_voltage_limits: bool,
276 pub emergency_vmax_pu: f64,
277 pub emergency_vmin_pu: f64,
278 pub area: usize,
279 pub zone: usize,
280 pub name: PioStringView,
281 pub has_name: bool,
282 pub location: PioBalancedLocationView,
283 pub has_location: bool,
284}
285
286#[repr(C)]
288#[derive(Clone, Copy)]
289pub struct PioBalancedLoadVoltageModelView {
290 pub kind: PioStringView,
291 pub p_constant_power_mw: f64,
292 pub q_constant_power_mvar: f64,
293 pub p_constant_current_mw: f64,
294 pub q_constant_current_mvar: f64,
295 pub p_constant_impedance_mw: f64,
296 pub q_constant_impedance_mvar: f64,
297 pub exponential_p_mw: f64,
298 pub exponential_q_mvar: f64,
299 pub gamma_p: f64,
300 pub gamma_q: f64,
301 pub nominal_voltage_pu: f64,
302 pub has_nominal_voltage: bool,
303 pub load_type: i32,
304 pub has_load_type: bool,
305 pub scaling: f64,
306 pub has_scaling: bool,
307}
308
309#[repr(C)]
311#[derive(Clone, Copy)]
312pub struct PioBalancedLoadView {
313 pub component_id: PioStringView,
314 pub has_component_id: bool,
315 pub bus_id: usize,
316 pub p_mw: f64,
317 pub q_mvar: f64,
318 pub in_service: bool,
319 pub voltage_model: PioBalancedLoadVoltageModelView,
320}
321
322#[repr(C)]
324#[derive(Clone, Copy)]
325pub struct PioShuntBlockView {
326 pub steps: u32,
327 pub conductance_mw: f64,
328 pub susceptance_mvar: f64,
329}
330
331#[repr(C)]
333#[derive(Clone, Copy)]
334pub struct PioBalancedShuntView {
335 pub component_id: PioStringView,
336 pub has_component_id: bool,
337 pub bus_id: usize,
338 pub conductance_mw: f64,
339 pub susceptance_mvar: f64,
340 pub in_service: bool,
341 pub section_count: u32,
342 pub has_section_count: bool,
343 pub has_control: bool,
344 pub control_mode: PioStringView,
345 pub control_vmax_pu: f64,
346 pub control_vmin_pu: f64,
347 pub control_bus_id: usize,
348 pub has_control_bus: bool,
349 pub control_reactive_range_percent: f64,
350 pub control_block_count: usize,
351}
352
353#[repr(C)]
355#[derive(Clone, Copy)]
356pub struct PioBranchRatingView {
357 pub name: PioStringView,
358 pub rate_mva: f64,
359}
360
361#[repr(C)]
363#[derive(Clone, Copy)]
364pub struct PioBalancedBranchView {
365 pub component_id: PioStringView,
366 pub has_component_id: bool,
367 pub name: PioStringView,
368 pub has_name: bool,
369 pub from_bus_id: usize,
370 pub to_bus_id: usize,
371 pub resistance_pu: f64,
372 pub reactance_pu: f64,
373 pub total_charging_susceptance_pu: f64,
374 pub terminal_charging_is_explicit: bool,
375 pub from_conductance_pu: f64,
376 pub from_susceptance_pu: f64,
377 pub to_conductance_pu: f64,
378 pub to_susceptance_pu: f64,
379 pub rate_a_mva: f64,
380 pub rate_b_mva: f64,
381 pub rate_c_mva: f64,
382 pub additional_rating_count: usize,
383 pub has_current_ratings: bool,
384 pub current_rating_a: f64,
385 pub current_rating_b: f64,
386 pub current_rating_c: f64,
387 pub tap_ratio: f64,
388 pub effective_tap_ratio: f64,
389 pub phase_shift_degrees: f64,
390 pub in_service: bool,
391 pub angle_min_degrees: f64,
392 pub angle_max_degrees: f64,
393 pub control: PioTransformerControlView,
394 pub has_control: bool,
395 pub route_point_count: usize,
396 pub has_route: bool,
397}
398
399#[repr(C)]
401#[derive(Clone, Copy)]
402pub struct PioGeneratorCostView {
403 pub model: u8,
404 pub startup: f64,
405 pub shutdown: f64,
406 pub ncost: usize,
407 pub coefficients: PioF64View,
408}
409
410#[repr(C)]
412#[derive(Clone, Copy)]
413pub struct PioGeneratorCapabilityView {
414 pub name: PioStringView,
415 pub value: f64,
416 pub has_value: bool,
417}
418
419#[repr(C)]
421#[derive(Clone, Copy)]
422pub struct PioActivePowerControlView {
423 pub participate: bool,
424 pub droop_percent: f64,
425 pub has_droop_percent: bool,
426 pub participation_factor: f64,
427 pub has_participation_factor: bool,
428 pub minimum_target_active_power_mw: f64,
429 pub has_minimum_target_active_power: bool,
430 pub maximum_target_active_power_mw: f64,
431 pub has_maximum_target_active_power: bool,
432}
433
434#[repr(C)]
436#[derive(Clone, Copy)]
437pub struct PioBalancedGeneratorView {
438 pub component_id: PioStringView,
439 pub has_component_id: bool,
440 pub bus_id: usize,
441 pub energy_source: PioStringView,
442 pub active_power_mw: f64,
443 pub reactive_power_mvar: f64,
444 pub active_power_max_mw: f64,
445 pub active_power_min_mw: f64,
446 pub reactive_power_max_mvar: f64,
447 pub reactive_power_min_mvar: f64,
448 pub voltage_setpoint_pu: f64,
449 pub machine_base_mva: f64,
450 pub in_service: bool,
451 pub has_cost: bool,
452 pub cost: PioGeneratorCostView,
453 pub regulated_bus_id: usize,
454 pub has_regulated_bus: bool,
455 pub capability_count: usize,
456 pub active_power_control: PioActivePowerControlView,
457 pub has_active_power_control: bool,
458 pub voltage_regulation_on: bool,
459 pub regulating_terminal: PioTerminalReferenceView,
460 pub has_regulating_terminal: bool,
461}
462
463#[repr(C)]
465#[derive(Clone, Copy)]
466pub struct PioBalancedStorageView {
467 pub component_id: PioStringView,
468 pub has_component_id: bool,
469 pub bus_id: usize,
470 pub active_power_mw: f64,
471 pub reactive_power_mvar: f64,
472 pub energy_mwh: f64,
473 pub energy_rating_mwh: f64,
474 pub charge_rating_mw: f64,
475 pub discharge_rating_mw: f64,
476 pub charge_efficiency: f64,
477 pub discharge_efficiency: f64,
478 pub thermal_rating_mva: f64,
479 pub current_rating: f64,
480 pub has_current_rating: bool,
481 pub reactive_power_min_mvar: f64,
482 pub reactive_power_max_mvar: f64,
483 pub resistance_pu: f64,
484 pub reactance_pu: f64,
485 pub active_power_loss_mw: f64,
486 pub reactive_power_loss_mvar: f64,
487 pub in_service: bool,
488 pub active_power_control: PioActivePowerControlView,
489 pub has_active_power_control: bool,
490}
491
492#[repr(C)]
494#[derive(Clone, Copy)]
495pub struct PioTerminalReferenceView {
496 pub equipment: PioComponentIdView,
497 pub terminal: u8,
498}
499
500#[repr(C)]
502#[derive(Clone, Copy)]
503pub struct PioTransformerControlView {
504 pub mode: PioStringView,
505 pub enabled: bool,
506 pub controlled_bus_id: usize,
507 pub has_controlled_bus: bool,
508 pub controlled_bus_on_winding_side: bool,
509 pub regulating_terminal: PioTerminalReferenceView,
510 pub has_regulating_terminal: bool,
511 pub tap_min: f64,
512 pub tap_max: f64,
513 pub band_min: f64,
514 pub band_max: f64,
515 pub tap_position_count: u32,
516 pub mva_base: f64,
517 pub winding_connection_angle: f64,
518 pub has_winding_connection_angle: bool,
519}
520
521#[repr(C)]
523#[derive(Clone, Copy)]
524pub struct PioBalancedStaticVarCompensatorView {
525 pub component_id: PioStringView,
526 pub has_component_id: bool,
527 pub bus_id: usize,
528 pub minimum_susceptance_siemens: f64,
529 pub maximum_susceptance_siemens: f64,
530 pub voltage_setpoint_kv: f64,
531 pub reactive_power_setpoint_mvar: f64,
532 pub regulation_mode: PioStringView,
533 pub regulating: bool,
534 pub regulating_terminal: PioTerminalReferenceView,
535 pub has_regulating_terminal: bool,
536 pub active_power_mw: f64,
537 pub reactive_power_mvar: f64,
538 pub in_service: bool,
539}
540
541#[repr(C)]
543#[derive(Clone, Copy)]
544pub struct PioBalancedSwitchView {
545 pub component_id: PioStringView,
546 pub has_component_id: bool,
547 pub from_bus_id: usize,
548 pub to_bus_id: usize,
549 pub closed: bool,
550 pub thermal_rating_mva: f64,
551 pub has_thermal_rating: bool,
552 pub current_rating_a: f64,
553 pub has_current_rating: bool,
554 pub from_active_power_mw: f64,
555 pub has_from_active_power: bool,
556 pub from_reactive_power_mvar: f64,
557 pub has_from_reactive_power: bool,
558 pub to_active_power_mw: f64,
559 pub has_to_active_power: bool,
560 pub to_reactive_power_mvar: f64,
561 pub has_to_reactive_power: bool,
562}
563
564#[repr(C)]
566#[derive(Clone, Copy)]
567pub struct PioBalancedHvdcConverterView {
568 pub component: PioComponentIdView,
569 pub kind: PioStringView,
570 pub loss_factor_percent: f64,
571 pub voltage_regulator_on: bool,
572 pub has_voltage_regulator_on: bool,
573 pub voltage_setpoint_kv: f64,
574 pub has_voltage_setpoint: bool,
575 pub reactive_power_setpoint_mvar: f64,
576 pub has_reactive_power_setpoint: bool,
577 pub power_factor: f64,
578 pub has_power_factor: bool,
579 pub regulating_terminal: PioTerminalReferenceView,
580 pub has_regulating_terminal: bool,
581}
582
583#[repr(C)]
585#[derive(Clone, Copy)]
586pub struct PioBalancedHvdcView {
587 pub component_id: PioStringView,
588 pub has_component_id: bool,
589 pub from_bus_id: usize,
590 pub to_bus_id: usize,
591 pub in_service: bool,
592 pub from_active_power_mw: f64,
593 pub to_active_power_mw: f64,
594 pub from_reactive_power_mvar: f64,
595 pub to_reactive_power_mvar: f64,
596 pub from_voltage_pu: f64,
597 pub to_voltage_pu: f64,
598 pub minimum_active_power_mw: f64,
599 pub maximum_active_power_mw: f64,
600 pub minimum_from_reactive_power_mvar: f64,
601 pub maximum_from_reactive_power_mvar: f64,
602 pub minimum_to_reactive_power_mvar: f64,
603 pub maximum_to_reactive_power_mvar: f64,
604 pub constant_loss_mw: f64,
605 pub proportional_loss: f64,
606 pub resistance_ohm: f64,
607 pub has_resistance: bool,
608 pub nominal_voltage_kv: f64,
609 pub has_nominal_voltage: bool,
610 pub converters_mode: PioStringView,
611 pub has_converters_mode: bool,
612 pub converter1: PioBalancedHvdcConverterView,
613 pub has_converter1: bool,
614 pub converter2: PioBalancedHvdcConverterView,
615 pub has_converter2: bool,
616 pub cost: PioGeneratorCostView,
617 pub has_cost: bool,
618}
619
620#[repr(C)]
622#[derive(Clone, Copy)]
623pub struct PioThreeWindingTransformerWindingView {
624 pub bus_id: usize,
625 pub tap_ratio: f64,
626 pub phase_shift_degrees: f64,
627 pub nominal_voltage_kv: f64,
628 pub rating_a_mva: f64,
629 pub rating_b_mva: f64,
630 pub rating_c_mva: f64,
631 pub control: PioTransformerControlView,
632 pub has_control: bool,
633}
634
635#[repr(C)]
637#[derive(Clone, Copy)]
638pub struct PioThreeWindingTransformerImpedanceView {
639 pub resistance_pu: f64,
640 pub reactance_pu: f64,
641 pub base_mva: f64,
642}
643
644#[repr(C)]
646#[derive(Clone, Copy)]
647pub struct PioBalancedThreeWindingTransformerView {
648 pub component_id: PioStringView,
649 pub has_component_id: bool,
650 pub name: PioStringView,
651 pub has_name: bool,
652 pub winding_count: usize,
653 pub impedance_count: usize,
654 pub star_voltage_magnitude_pu: f64,
655 pub star_voltage_angle_degrees: f64,
656 pub magnetizing_conductance_pu: f64,
657 pub magnetizing_susceptance_pu: f64,
658 pub in_service: bool,
659}
660
661#[repr(C)]
663#[derive(Clone, Copy)]
664pub struct PioBalancedAreaView {
665 pub number: usize,
666 pub slack_bus_id: usize,
667 pub has_slack_bus: bool,
668 pub net_interchange_mw: f64,
669 pub tolerance_mw: f64,
670 pub name: PioStringView,
671 pub has_name: bool,
672 pub component_id: PioStringView,
673 pub has_component_id: bool,
674 pub area_type: PioStringView,
675 pub has_area_type: bool,
676}
677
678#[repr(C)]
680#[derive(Clone, Copy)]
681pub struct PioDetailedConnectivityCountsView {
682 pub omitted_fields: usize,
683 pub component_metadata: usize,
684 pub subnetworks: usize,
685 pub substations: usize,
686 pub voltage_levels: usize,
687 pub bus_breaker_buses: usize,
688 pub calculated_buses: usize,
689 pub connectivity_nodes: usize,
690 pub busbar_sections: usize,
691 pub junctions: usize,
692 pub terminals: usize,
693 pub switches: usize,
694 pub internal_connections: usize,
695 pub operational_limit_groups: usize,
696 pub tap_changers: usize,
697 pub equipment_reactive_limits: usize,
698 pub boundary_lines: usize,
699 pub tie_lines: usize,
700 pub dc_converter_units: usize,
701 pub dc_topological_nodes: usize,
702 pub dc_nodes: usize,
703 pub dc_grounds: usize,
704 pub dc_busbars: usize,
705 pub dc_lines: usize,
706 pub dc_series_devices: usize,
707 pub dc_switches: usize,
708 pub voltage_source_converters: usize,
709 pub line_commutated_converters: usize,
710}
711
712#[repr(C)]
714#[derive(Clone, Copy)]
715pub struct PioOmittedFieldView {
716 pub component: PioComponentIdView,
717 pub field: PioStringView,
718}
719
720#[repr(C)]
722#[derive(Clone, Copy)]
723pub struct PioEquipmentReactiveLimitsView {
724 pub equipment: PioComponentIdView,
725 pub limits: PioReactiveLimitsView,
726}
727
728#[repr(C)]
730#[derive(Clone, Copy)]
731pub struct PioCaseMetadataView {
732 pub case_date: PioStringView,
733 pub has_case_date: bool,
734 pub forecast_distance: i32,
735 pub has_forecast_distance: bool,
736 pub source_model_format: PioStringView,
737 pub has_source_model_format: bool,
738 pub minimum_validation_level: PioStringView,
739 pub has_minimum_validation_level: bool,
740}
741
742#[repr(C)]
744#[derive(Clone, Copy)]
745pub struct PioSubnetworkView {
746 pub component: PioComponentIdView,
747 pub parent: PioComponentIdView,
748 pub case_metadata: PioCaseMetadataView,
749 pub component_count: usize,
750}
751
752#[repr(C)]
754#[derive(Clone, Copy)]
755pub struct PioReactiveCapabilityCurvePointView {
756 pub active_power_mw: f64,
757 pub minimum_reactive_power_mvar: f64,
758 pub maximum_reactive_power_mvar: f64,
759 pub property_count: usize,
760}
761
762#[repr(C)]
764#[derive(Clone, Copy)]
765pub struct PioReactiveLimitsView {
766 pub kind: PioStringView,
767 pub minimum_reactive_power_mvar: f64,
768 pub maximum_reactive_power_mvar: f64,
769 pub has_minimum_and_maximum: bool,
770 pub curve_style: PioStringView,
771 pub has_curve_style: bool,
772 pub property_count: usize,
773 pub point_count: usize,
774}
775
776#[repr(C)]
778#[derive(Clone, Copy)]
779pub struct PioBoundaryLineGenerationView {
780 pub voltage_regulation_on: bool,
781 pub minimum_active_power_mw: f64,
782 pub has_minimum_active_power: bool,
783 pub maximum_active_power_mw: f64,
784 pub has_maximum_active_power: bool,
785 pub target_active_power_mw: f64,
786 pub has_target_active_power: bool,
787 pub target_reactive_power_mvar: f64,
788 pub has_target_reactive_power: bool,
789 pub target_voltage_kv: f64,
790 pub has_target_voltage: bool,
791 pub reactive_limits: PioReactiveLimitsView,
792 pub has_reactive_limits: bool,
793}
794
795#[repr(C)]
797#[derive(Clone, Copy)]
798pub struct PioBoundaryLineView {
799 pub component: PioComponentIdView,
800 pub voltage_level: PioComponentIdView,
801 pub active_power_setpoint_mw: f64,
802 pub reactive_power_setpoint_mvar: f64,
803 pub resistance_ohm: f64,
804 pub reactance_ohm: f64,
805 pub conductance_siemens: f64,
806 pub susceptance_siemens: f64,
807 pub pairing_key: PioStringView,
808 pub has_pairing_key: bool,
809 pub generation: PioBoundaryLineGenerationView,
810 pub has_generation: bool,
811 pub calculation_load: PioComponentIdView,
812 pub has_calculation_load: bool,
813 pub calculation_generator: PioComponentIdView,
814 pub has_calculation_generator: bool,
815}
816
817#[repr(C)]
819#[derive(Clone, Copy)]
820pub struct PioTieLineView {
821 pub component: PioComponentIdView,
822 pub boundary_line1: PioComponentIdView,
823 pub boundary_line2: PioComponentIdView,
824 pub calculation_branch: PioComponentIdView,
825 pub has_calculation_branch: bool,
826}
827
828#[repr(C)]
830#[derive(Clone, Copy)]
831pub struct PioComponentMetadataView {
832 pub component: PioComponentIdView,
833 pub name: PioStringView,
834 pub has_name: bool,
835 pub equipment_container: PioComponentIdView,
836 pub has_equipment_container: bool,
837 pub fictitious: bool,
838 pub alias_count: usize,
839 pub external_identifier_count: usize,
840 pub property_count: usize,
841}
842
843#[repr(C)]
845#[derive(Clone, Copy)]
846pub struct PioComponentAliasView {
847 pub value: PioStringView,
848 pub alias_type: PioStringView,
849 pub has_alias_type: bool,
850}
851
852#[repr(C)]
854#[derive(Clone, Copy)]
855pub struct PioExternalIdentifierView {
856 pub value: PioStringView,
857 pub authority: PioStringView,
858 pub has_authority: bool,
859}
860
861#[repr(C)]
863#[derive(Clone, Copy)]
864pub struct PioStringPropertyView {
865 pub name: PioStringView,
866 pub value: PioStringView,
867}
868
869#[repr(C)]
871#[derive(Clone, Copy)]
872pub struct PioSubstationView {
873 pub component: PioComponentIdView,
874 pub country: PioStringView,
875 pub has_country: bool,
876 pub operator_name: PioStringView,
877 pub has_operator_name: bool,
878 pub geographical_tag_count: usize,
879}
880
881#[repr(C)]
883#[derive(Clone, Copy)]
884pub struct PioVoltageLevelView {
885 pub component: PioComponentIdView,
886 pub substation: PioComponentIdView,
887 pub has_substation: bool,
888 pub nominal_voltage_kv: f64,
889 pub low_voltage_limit_kv: f64,
890 pub has_low_voltage_limit: bool,
891 pub high_voltage_limit_kv: f64,
892 pub has_high_voltage_limit: bool,
893 pub topology_kind: PioStringView,
894 pub bus_count: usize,
895}
896
897#[repr(C)]
899#[derive(Clone, Copy)]
900pub struct PioConnectivityNodeView {
901 pub component: PioComponentIdView,
902 pub voltage_level: PioComponentIdView,
903 pub node_number: i32,
904 pub has_node_number: bool,
905 pub calculated_bus_id: usize,
906 pub has_calculated_bus: bool,
907}
908
909#[repr(C)]
911#[derive(Clone, Copy)]
912pub struct PioBusBreakerBusView {
913 pub component: PioComponentIdView,
914 pub voltage_level: PioComponentIdView,
915 pub calculated_bus_id: usize,
916 pub has_calculated_bus: bool,
917 pub voltage_kv: f64,
918 pub has_voltage: bool,
919 pub angle_degrees: f64,
920 pub has_angle: bool,
921}
922
923#[repr(C)]
925#[derive(Clone, Copy)]
926pub struct PioCalculatedBusView {
927 pub voltage_level: PioComponentIdView,
928 pub calculated_bus_id: usize,
929 pub node_count: usize,
930 pub voltage_kv: f64,
931 pub has_voltage: bool,
932 pub angle_degrees: f64,
933 pub has_angle: bool,
934}
935
936#[repr(C)]
938#[derive(Clone, Copy)]
939pub struct PioBusbarSectionView {
940 pub component: PioComponentIdView,
941 pub voltage_level: PioComponentIdView,
942 pub node: PioComponentIdView,
943}
944
945#[repr(C)]
947#[derive(Clone, Copy)]
948pub struct PioJunctionView {
949 pub component: PioComponentIdView,
950}
951
952#[repr(C)]
954#[derive(Clone, Copy)]
955pub struct PioDetailedTerminalView {
956 pub component: PioComponentIdView,
957 pub has_component: bool,
958 pub equipment: PioComponentIdView,
959 pub terminal: u8,
960 pub voltage_level: PioComponentIdView,
961 pub bus: PioComponentIdView,
962 pub has_bus: bool,
963 pub connectable_bus: PioComponentIdView,
964 pub has_connectable_bus: bool,
965 pub node: PioComponentIdView,
966 pub has_node: bool,
967 pub connected: bool,
968 pub active_power_mw: f64,
969 pub has_active_power: bool,
970 pub reactive_power_mvar: f64,
971 pub has_reactive_power: bool,
972}
973
974#[repr(C)]
976#[derive(Clone, Copy)]
977pub struct PioTopologySwitchView {
978 pub component: PioComponentIdView,
979 pub voltage_level: PioComponentIdView,
980 pub kind: PioStringView,
981 pub endpoint1_kind: PioStringView,
982 pub endpoint1: PioComponentIdView,
983 pub endpoint2_kind: PioStringView,
984 pub endpoint2: PioComponentIdView,
985 pub open: bool,
986 pub retained: bool,
987}
988
989#[repr(C)]
991#[derive(Clone, Copy)]
992pub struct PioInternalConnectionView {
993 pub voltage_level: PioComponentIdView,
994 pub node1: PioComponentIdView,
995 pub node2: PioComponentIdView,
996}
997
998#[repr(C)]
1000#[derive(Clone, Copy)]
1001pub struct PioOperationalLimitGroupView {
1002 pub equipment: PioComponentIdView,
1003 pub terminal: u8,
1004 pub id: PioStringView,
1005 pub selected: bool,
1006 pub property_count: usize,
1007 pub has_current_limits: bool,
1008 pub current_permanent_limit_a: f64,
1009 pub current_permanent_limit_name: PioStringView,
1010 pub has_current_permanent_limit: bool,
1011 pub has_current_permanent_limit_name: bool,
1012 pub current_temporary_limit_count: usize,
1013 pub has_active_power_limits: bool,
1014 pub active_power_permanent_limit_mw: f64,
1015 pub active_power_permanent_limit_name: PioStringView,
1016 pub has_active_power_permanent_limit: bool,
1017 pub has_active_power_permanent_limit_name: bool,
1018 pub active_power_temporary_limit_count: usize,
1019 pub has_apparent_power_limits: bool,
1020 pub apparent_power_permanent_limit_mva: f64,
1021 pub apparent_power_permanent_limit_name: PioStringView,
1022 pub has_apparent_power_permanent_limit: bool,
1023 pub has_apparent_power_permanent_limit_name: bool,
1024 pub apparent_power_temporary_limit_count: usize,
1025}
1026
1027#[repr(C)]
1029#[derive(Clone, Copy)]
1030pub struct PioDroopCurveSegmentView {
1031 pub minimum_voltage_kv: f64,
1032 pub maximum_voltage_kv: f64,
1033 pub k: f64,
1034}
1035
1036#[repr(C)]
1038#[derive(Clone, Copy)]
1039pub struct PioTemporaryLimitView {
1040 pub name: PioStringView,
1041 pub value: f64,
1042 pub acceptable_duration_seconds: u64,
1043 pub fictitious: bool,
1044}
1045
1046#[repr(C)]
1048#[derive(Clone, Copy)]
1049pub struct PioTapChangerView {
1050 pub component: PioComponentIdView,
1051 pub has_component: bool,
1052 pub transformer: PioComponentIdView,
1053 pub winding: u8,
1054 pub kind: PioStringView,
1055 pub tap_position: i32,
1056 pub has_tap_position: bool,
1057 pub solved_tap_position: i32,
1058 pub has_solved_tap_position: bool,
1059 pub low_tap_position: i32,
1060 pub neutral_tap_position: i32,
1061 pub has_neutral_tap_position: bool,
1062 pub normal_tap_position: i32,
1063 pub has_normal_tap_position: bool,
1064 pub voltage_step_increment_percent: f64,
1065 pub has_voltage_step_increment_percent: bool,
1066 pub load_tap_changing_capabilities: bool,
1067 pub regulating: bool,
1068 pub regulation_mode: PioStringView,
1069 pub has_regulation_mode: bool,
1070 pub regulation_value: f64,
1071 pub has_regulation_value: bool,
1072 pub target_deadband: f64,
1073 pub has_target_deadband: bool,
1074 pub regulation_terminal: PioTerminalReferenceView,
1075 pub has_regulation_terminal: bool,
1076 pub step_count: usize,
1077}
1078
1079#[repr(C)]
1081#[derive(Clone, Copy)]
1082pub struct PioTapChangerStepView {
1083 pub position: i32,
1084 pub ratio_pu: f64,
1085 pub phase_shift_degrees: f64,
1086 pub resistance_deviation_percent: f64,
1087 pub reactance_deviation_percent: f64,
1088 pub conductance_deviation_percent: f64,
1089 pub susceptance_deviation_percent: f64,
1090}
1091
1092#[repr(C)]
1094#[derive(Clone, Copy)]
1095pub struct PioDcTerminalView {
1096 pub component: PioComponentIdView,
1097 pub has_component: bool,
1098 pub sequence_number: u32,
1099 pub has_sequence_number: bool,
1100 pub dc_node: PioComponentIdView,
1101 pub has_dc_node: bool,
1102 pub dc_topological_node: PioComponentIdView,
1103 pub has_dc_topological_node: bool,
1104 pub polarity: PioStringView,
1105 pub has_polarity: bool,
1106 pub connected: bool,
1107 pub has_connected: bool,
1108 pub active_power_mw: f64,
1109 pub has_active_power: bool,
1110 pub current_a: f64,
1111 pub has_current: bool,
1112}
1113
1114#[repr(C)]
1116#[derive(Clone, Copy)]
1117pub struct PioDcEquipmentView {
1118 pub component: PioComponentIdView,
1119 pub equipment_container: PioComponentIdView,
1120 pub has_equipment_container: bool,
1121 pub kind: PioStringView,
1122 pub terminal_count: usize,
1123 pub terminal1: PioDcTerminalView,
1124 pub terminal2: PioDcTerminalView,
1125 pub rated_dc_voltage_kv: f64,
1126 pub has_rated_dc_voltage: bool,
1127 pub resistance_ohm: f64,
1128 pub has_resistance: bool,
1129 pub inductance_h: f64,
1130 pub has_inductance: bool,
1131 pub capacitance_f: f64,
1132 pub has_capacitance: bool,
1133 pub length_km: f64,
1134 pub has_length: bool,
1135 pub switch_kind: PioStringView,
1136 pub has_switch_kind: bool,
1137 pub open: bool,
1138 pub has_open: bool,
1139}
1140
1141#[repr(C)]
1143#[derive(Clone, Copy)]
1144pub struct PioDcNodeView {
1145 pub component: PioComponentIdView,
1146 pub kind: PioStringView,
1147 pub nominal_voltage_kv: f64,
1148 pub has_nominal_voltage: bool,
1149 pub voltage_kv: f64,
1150 pub has_voltage: bool,
1151 pub dc_converter_unit: PioComponentIdView,
1152 pub has_dc_converter_unit: bool,
1153 pub dc_topological_node: PioComponentIdView,
1154 pub has_dc_topological_node: bool,
1155}
1156
1157#[repr(C)]
1159#[derive(Clone, Copy)]
1160pub struct PioDcConverterUnitView {
1161 pub component: PioComponentIdView,
1162 pub substation: PioComponentIdView,
1163 pub has_substation: bool,
1164 pub operation_mode: PioStringView,
1165}
1166
1167#[repr(C)]
1169#[derive(Clone, Copy)]
1170pub struct PioAcDcConverterView {
1171 pub component: PioComponentIdView,
1172 pub kind: PioStringView,
1173 pub dc_converter_unit: PioComponentIdView,
1174 pub has_dc_converter_unit: bool,
1175 pub dc_terminal1: PioDcTerminalView,
1176 pub dc_terminal2: PioDcTerminalView,
1177 pub base_apparent_power_mva: f64,
1178 pub has_base_apparent_power: bool,
1179 pub minimum_active_power_mw: f64,
1180 pub has_minimum_active_power: bool,
1181 pub maximum_active_power_mw: f64,
1182 pub has_maximum_active_power: bool,
1183 pub minimum_dc_voltage_kv: f64,
1184 pub has_minimum_dc_voltage: bool,
1185 pub maximum_dc_voltage_kv: f64,
1186 pub has_maximum_dc_voltage: bool,
1187 pub rated_dc_voltage_kv: f64,
1188 pub has_rated_dc_voltage: bool,
1189 pub valve_u0_kv: f64,
1190 pub has_valve_u0: bool,
1191 pub number_of_valves: u32,
1192 pub has_number_of_valves: bool,
1193 pub idle_loss_mw: f64,
1194 pub has_idle_loss: bool,
1195 pub switching_loss_mw_per_ampere: f64,
1196 pub has_switching_loss: bool,
1197 pub resistive_loss_ohm: f64,
1198 pub has_resistive_loss: bool,
1199 pub control_mode: PioStringView,
1200 pub has_control_mode: bool,
1201 pub active_power_at_pcc_mw: f64,
1202 pub has_active_power_at_pcc: bool,
1203 pub reactive_power_at_pcc_mvar: f64,
1204 pub has_reactive_power_at_pcc: bool,
1205 pub target_active_power_mw: f64,
1206 pub has_target_active_power: bool,
1207 pub target_dc_voltage_kv: f64,
1208 pub has_target_dc_voltage: bool,
1209 pub pcc_terminal: PioTerminalReferenceView,
1210 pub has_pcc_terminal: bool,
1211 pub droop_curve_segment_count: usize,
1212 pub has_droop_curve: bool,
1213 pub droop: f64,
1214 pub has_droop: bool,
1215 pub droop_compensation: f64,
1216 pub has_droop_compensation: bool,
1217 pub q_share: f64,
1218 pub has_q_share: bool,
1219 pub maximum_modulation_index: f64,
1220 pub has_maximum_modulation_index: bool,
1221 pub maximum_valve_current_a: f64,
1222 pub has_maximum_valve_current: bool,
1223 pub dc_current_a: f64,
1224 pub has_dc_current: bool,
1225 pub ac_voltage_kv: f64,
1226 pub has_ac_voltage: bool,
1227 pub dc_voltage_kv: f64,
1228 pub has_dc_voltage: bool,
1229 pub voltage_regulator_on: bool,
1230 pub has_voltage_regulator_on: bool,
1231 pub voltage_setpoint_kv: f64,
1232 pub has_voltage_setpoint: bool,
1233 pub reactive_power_setpoint_mvar: f64,
1234 pub has_reactive_power_setpoint: bool,
1235 pub reactive_limits: PioReactiveLimitsView,
1236 pub has_reactive_limits: bool,
1237 pub pole_loss_active_power_mw: f64,
1238 pub has_pole_loss_active_power: bool,
1239 pub reactive_model: PioStringView,
1240 pub has_reactive_model: bool,
1241 pub power_factor: f64,
1242 pub has_power_factor: bool,
1243 pub operating_mode: PioStringView,
1244 pub has_operating_mode: bool,
1245 pub rated_dc_current_a: f64,
1246 pub has_rated_dc_current: bool,
1247 pub minimum_alpha_degrees: f64,
1248 pub has_minimum_alpha: bool,
1249 pub maximum_alpha_degrees: f64,
1250 pub has_maximum_alpha: bool,
1251 pub minimum_gamma_degrees: f64,
1252 pub has_minimum_gamma: bool,
1253 pub maximum_gamma_degrees: f64,
1254 pub has_maximum_gamma: bool,
1255 pub target_alpha_degrees: f64,
1256 pub has_target_alpha: bool,
1257 pub target_gamma_degrees: f64,
1258 pub has_target_gamma: bool,
1259 pub target_dc_current_a: f64,
1260 pub has_target_dc_current: bool,
1261 pub alpha_degrees: f64,
1262 pub has_alpha: bool,
1263 pub gamma_degrees: f64,
1264 pub has_gamma: bool,
1265 pub delta_degrees: f64,
1266 pub has_delta: bool,
1267 pub uf_kv: f64,
1268 pub has_uf: bool,
1269 pub uv_kv: f64,
1270 pub has_uv: bool,
1271}
1272
1273#[repr(C)]
1275#[derive(Clone, Copy)]
1276pub struct PioMulticonductorLocationView {
1277 pub x: f64,
1278 pub y: f64,
1279 pub kind: PioStringView,
1280 pub has_kind: bool,
1281}
1282
1283#[repr(C)]
1285#[derive(Clone, Copy)]
1286pub struct PioMulticonductorGeoView {
1287 pub has_geo: bool,
1288 pub space: PioStringView,
1289 pub crs: PioStringView,
1290 pub has_crs: bool,
1291 pub kind: PioStringView,
1292 pub has_kind: bool,
1293 pub has_canvas: bool,
1294 pub canvas_width: f64,
1295 pub has_canvas_width: bool,
1296 pub canvas_height: f64,
1297 pub has_canvas_height: bool,
1298 pub canvas_units: PioStringView,
1299 pub has_canvas_units: bool,
1300}
1301
1302#[repr(C)]
1307#[derive(Clone, Copy)]
1308pub struct PioMulticonductorNetworkCountsView {
1309 pub buses: usize,
1310 pub line_codes: usize,
1311 pub lines: usize,
1312 pub switches: usize,
1313 pub transformers: usize,
1314 pub loads: usize,
1315 pub generators: usize,
1316 pub inverter_based_resources: usize,
1317 pub control_profiles: usize,
1318 pub shunts: usize,
1319 pub capacitors: usize,
1320 pub voltage_sources: usize,
1321 pub untyped_objects: usize,
1322 pub commands: usize,
1323 pub options: usize,
1324}
1325
1326#[repr(C)]
1328#[derive(Clone, Copy)]
1329pub struct PioMulticonductorBusView {
1330 pub id: PioStringView,
1331 pub terminal_count: usize,
1332 pub grounded_terminal_count: usize,
1333 pub voltage_min_v: f64,
1334 pub has_voltage_min: bool,
1335 pub voltage_max_v: f64,
1336 pub has_voltage_max: bool,
1337 pub phase_to_ground_voltage_min_v: PioF64View,
1339 pub has_phase_to_ground_voltage_min: bool,
1340 pub phase_to_ground_voltage_max_v: PioF64View,
1341 pub has_phase_to_ground_voltage_max: bool,
1342 pub phase_to_neutral_voltage_min_v: PioF64View,
1343 pub has_phase_to_neutral_voltage_min: bool,
1344 pub phase_to_neutral_voltage_max_v: PioF64View,
1345 pub has_phase_to_neutral_voltage_max: bool,
1346 pub phase_to_phase_voltage_min_v: PioF64View,
1347 pub has_phase_to_phase_voltage_min: bool,
1348 pub phase_to_phase_voltage_max_v: PioF64View,
1349 pub has_phase_to_phase_voltage_max: bool,
1350 pub positive_sequence_voltage_min_v: f64,
1351 pub has_positive_sequence_voltage_min: bool,
1352 pub positive_sequence_voltage_max_v: f64,
1353 pub has_positive_sequence_voltage_max: bool,
1354 pub negative_sequence_voltage_max_v: f64,
1355 pub has_negative_sequence_voltage_max: bool,
1356 pub zero_sequence_voltage_max_v: f64,
1357 pub has_zero_sequence_voltage_max: bool,
1358 pub neutral_to_ground_voltage_max_v: f64,
1359 pub has_neutral_to_ground_voltage_max: bool,
1360 pub location: PioMulticonductorLocationView,
1361 pub has_location: bool,
1362}
1363
1364#[repr(C)]
1366#[derive(Clone, Copy)]
1367pub struct PioMulticonductorLineCodeView {
1368 pub name: PioStringView,
1369 pub conductor_count: usize,
1370 pub resistance_matrix_row_count: usize,
1371 pub reactance_matrix_row_count: usize,
1372 pub conductance_from_matrix_row_count: usize,
1373 pub susceptance_from_matrix_row_count: usize,
1374 pub conductance_to_matrix_row_count: usize,
1375 pub susceptance_to_matrix_row_count: usize,
1376 pub current_limit_a: PioF64View,
1377 pub has_current_limit: bool,
1378 pub apparent_power_limit_va: PioF64View,
1379 pub has_apparent_power_limit: bool,
1380 pub source: PioStringView,
1381 pub has_source: bool,
1382}
1383
1384#[repr(C)]
1386#[derive(Clone, Copy)]
1387pub struct PioMulticonductorLineView {
1388 pub name: PioStringView,
1389 pub bus_from: PioStringView,
1390 pub bus_to: PioStringView,
1391 pub terminal_map_from_count: usize,
1392 pub terminal_map_to_count: usize,
1393 pub line_code: PioStringView,
1394 pub length_m: f64,
1395 pub route_point_count: usize,
1396 pub has_route: bool,
1397 pub current_limit_a: PioF64View,
1398 pub has_current_limit: bool,
1399 pub apparent_power_limit_va: PioF64View,
1400 pub has_apparent_power_limit: bool,
1401}
1402
1403#[repr(C)]
1405#[derive(Clone, Copy)]
1406pub struct PioMulticonductorSwitchView {
1407 pub name: PioStringView,
1408 pub bus_from: PioStringView,
1409 pub bus_to: PioStringView,
1410 pub terminal_map_from_count: usize,
1411 pub terminal_map_to_count: usize,
1412 pub open: bool,
1413 pub current_limit_a: PioF64View,
1414 pub has_current_limit: bool,
1415}
1416
1417#[repr(C)]
1419#[derive(Clone, Copy)]
1420pub struct PioMulticonductorTransformerView {
1421 pub name: PioStringView,
1422 pub winding_count: usize,
1423 pub short_circuit_reactance_percent: PioF64View,
1424 pub phase_count: usize,
1425}
1426
1427#[repr(C)]
1429#[derive(Clone, Copy)]
1430pub struct PioMulticonductorTransformerWindingView {
1431 pub bus: PioStringView,
1432 pub terminal_map_count: usize,
1433 pub connection: PioStringView,
1434 pub rated_voltage_v: f64,
1435 pub apparent_power_rating_va: f64,
1436 pub resistance_percent: f64,
1437 pub tap: f64,
1438 pub neutral_resistance_ohm: f64,
1439 pub has_neutral_resistance: bool,
1440 pub neutral_reactance_ohm: f64,
1441 pub has_neutral_reactance: bool,
1442}
1443
1444#[repr(C)]
1446#[derive(Clone, Copy)]
1447pub struct PioMulticonductorLoadView {
1448 pub name: PioStringView,
1449 pub bus: PioStringView,
1450 pub terminal_map_count: usize,
1451 pub configuration: PioStringView,
1452 pub active_power_nominal_w: PioF64View,
1453 pub reactive_power_nominal_var: PioF64View,
1454 pub voltage_model: PioStringView,
1455 pub nominal_voltage_v: PioF64View,
1456 pub active_power_constant_impedance: PioF64View,
1457 pub active_power_constant_current: PioF64View,
1458 pub active_power_constant_power: PioF64View,
1459 pub reactive_power_constant_impedance: PioF64View,
1460 pub reactive_power_constant_current: PioF64View,
1461 pub reactive_power_constant_power: PioF64View,
1462 pub active_power_exponent: PioF64View,
1463 pub reactive_power_exponent: PioF64View,
1464}
1465
1466#[repr(C)]
1468#[derive(Clone, Copy)]
1469pub struct PioMulticonductorGeneratorView {
1470 pub name: PioStringView,
1471 pub bus: PioStringView,
1472 pub terminal_map_count: usize,
1473 pub configuration: PioStringView,
1474 pub active_power_nominal_w: PioF64View,
1475 pub reactive_power_nominal_var: PioF64View,
1476 pub active_power_min_w: PioF64View,
1477 pub has_active_power_min: bool,
1478 pub active_power_max_w: PioF64View,
1479 pub has_active_power_max: bool,
1480 pub reactive_power_min_var: PioF64View,
1481 pub has_reactive_power_min: bool,
1482 pub reactive_power_max_var: PioF64View,
1483 pub has_reactive_power_max: bool,
1484 pub active_power_dispatch_cost_per_kwh: PioF64View,
1485 pub has_active_power_dispatch_cost: bool,
1486 pub apparent_power_limit_va: PioF64View,
1487 pub has_apparent_power_limit: bool,
1488 pub current_limit_a: PioF64View,
1489 pub has_current_limit: bool,
1490}
1491
1492#[repr(C)]
1494#[derive(Clone, Copy)]
1495pub struct PioInverterBasedResourceView {
1496 pub name: PioStringView,
1497 pub bus: PioStringView,
1498 pub terminal_map_count: usize,
1499 pub topology: PioStringView,
1500 pub prime_mover: PioStringView,
1501 pub apparent_power_limit_va: PioF64View,
1502 pub current_limit_a: PioF64View,
1503 pub has_current_limit: bool,
1504 pub active_power_available_w: f64,
1505 pub has_active_power_available: bool,
1506 pub active_power_min_w: PioF64View,
1507 pub has_active_power_min: bool,
1508 pub active_power_max_w: PioF64View,
1509 pub has_active_power_max: bool,
1510 pub reactive_power_min_var: PioF64View,
1511 pub has_reactive_power_min: bool,
1512 pub reactive_power_max_var: PioF64View,
1513 pub has_reactive_power_max: bool,
1514 pub control_profile: PioStringView,
1515 pub has_control_profile: bool,
1516 pub voltage_aggregation: PioStringView,
1517 pub has_voltage_aggregation: bool,
1518}
1519
1520#[repr(C)]
1522#[derive(Clone, Copy)]
1523pub struct PioControlProfileView {
1524 pub name: PioStringView,
1525 pub has_power_factor: bool,
1526 pub power_factor: f64,
1527 pub has_volt_var: bool,
1528 pub volt_var_voltage_reference: PioStringView,
1529 pub has_volt_var_voltage_reference: bool,
1530 pub volt_var_breakpoints: PioF64View,
1531 pub volt_var_reactive_power_limits: PioF64View,
1532 pub volt_var_reactive_power_unit: PioStringView,
1533 pub has_volt_var_reactive_power_unit: bool,
1534 pub volt_var_reactive_power_reference: PioStringView,
1535 pub has_volt_var_reactive_power_reference: bool,
1536 pub volt_var_active_power_min_for_reactive_power_w: f64,
1537 pub has_volt_var_active_power_min_for_reactive_power: bool,
1538 pub volt_var_active_power_min_for_max_reactive_power_w: f64,
1539 pub has_volt_var_active_power_min_for_max_reactive_power: bool,
1540 pub has_volt_watt: bool,
1541 pub volt_watt_voltage_reference: PioStringView,
1542 pub has_volt_watt_voltage_reference: bool,
1543 pub volt_watt_breakpoints: PioF64View,
1544 pub volt_watt_active_power_limits: PioF64View,
1545 pub volt_watt_active_power_unit: PioStringView,
1546 pub has_volt_watt_active_power_unit: bool,
1547 pub volt_watt_active_power_reference: PioStringView,
1548 pub has_volt_watt_active_power_reference: bool,
1549}
1550
1551#[repr(C)]
1553#[derive(Clone, Copy)]
1554pub struct PioMulticonductorShuntView {
1555 pub name: PioStringView,
1556 pub bus: PioStringView,
1557 pub terminal_map_count: usize,
1558 pub conductance_matrix_row_count: usize,
1559 pub susceptance_matrix_row_count: usize,
1560}
1561
1562#[repr(C)]
1564#[derive(Clone, Copy)]
1565pub struct PioMulticonductorCapacitorView {
1566 pub name: PioStringView,
1567 pub bus: PioStringView,
1568 pub terminal_map_count: usize,
1569 pub configuration: PioStringView,
1570 pub rated_reactive_power_var: f64,
1571 pub nominal_voltage_v: f64,
1572}
1573
1574#[repr(C)]
1576#[derive(Clone, Copy)]
1577pub struct PioVoltageSourceView {
1578 pub name: PioStringView,
1579 pub bus: PioStringView,
1580 pub terminal_map_count: usize,
1581 pub voltage_magnitude_v: PioF64View,
1582 pub voltage_angle_rad: PioF64View,
1583 pub energy_cost_rate_per_kwh: PioF64View,
1585 pub has_energy_cost_rate: bool,
1586}
1587
1588#[repr(C)]
1590#[derive(Clone, Copy)]
1591pub struct PioMulticonductorUntypedObjectView {
1592 pub class_name: PioStringView,
1593 pub name: PioStringView,
1594 pub property_count: usize,
1595}
1596
1597#[repr(C)]
1599#[derive(Clone, Copy)]
1600pub struct PioMulticonductorUntypedPropertyView {
1601 pub name: PioStringView,
1602 pub has_name: bool,
1603 pub value: PioStringView,
1604}
1605
1606#[repr(C)]
1608#[derive(Clone, Copy)]
1609pub struct PioMulticonductorCommandView {
1610 pub verb: PioStringView,
1611 pub args: PioStringView,
1612}
1613
1614#[repr(C)]
1616#[derive(Clone, Copy)]
1617pub struct PioDcBusSpecificationView {
1618 pub bus_id: usize,
1619 pub kind: PioStringView,
1620 pub net_active_power_mw: f64,
1621 pub voltage_angle_degrees: f64,
1622}
1623
1624#[repr(C)]
1626#[derive(Clone, Copy)]
1627pub struct PioAcBusSpecificationView {
1628 pub bus_id: usize,
1629 pub kind: PioStringView,
1630 pub net_active_power_mw: f64,
1631 pub net_reactive_power_mvar: f64,
1632 pub voltage_magnitude_pu: f64,
1633 pub voltage_angle_degrees: f64,
1634}
1635
1636#[repr(C)]
1638#[derive(Clone, Copy)]
1639pub struct PioDcOpfPreparationView {
1640 pub name: PioStringView,
1641 pub bus_count: usize,
1642 pub generator_count: usize,
1643 pub branch_count: usize,
1644 pub source_generator_count: usize,
1645 pub source_branch_count: usize,
1646 pub base_mva: f64,
1647 pub units: PioStringView,
1648 pub branch_susceptance_formula: PioStringView,
1649 pub objective: PioStringView,
1650 pub skip_zero_impedance: bool,
1651 pub synthesize_unrated_limits: bool,
1652 pub correct_angle_difference_bounds: bool,
1653 pub reference_bus_count: usize,
1654 pub skipped_zero_impedance_count: usize,
1655}
1656
1657#[repr(C)]
1659#[derive(Clone, Copy)]
1660pub struct PioDcOpfBusView {
1661 pub bus_id: usize,
1662 pub analysis_row: usize,
1663 pub source_row: usize,
1664 pub has_source_row: bool,
1665 pub active_power_demand: f64,
1666 pub shunt_conductance: f64,
1667 pub phase_shift_injection: f64,
1668}
1669
1670#[repr(C)]
1672#[derive(Clone, Copy)]
1673pub struct PioDcOpfGeneratorView {
1674 pub component_id: PioStringView,
1675 pub bus_index: usize,
1676 pub analysis_row: usize,
1677 pub source_row: usize,
1678 pub has_source_row: bool,
1679 pub quadratic_cost: f64,
1680 pub linear_cost: f64,
1681 pub constant_cost: f64,
1682 pub has_piecewise_linear_cost: bool,
1683 pub piecewise_linear_power: PioF64View,
1684 pub piecewise_linear_value: PioF64View,
1685 pub active_power_max: f64,
1686 pub active_power_min: f64,
1687 pub capability_active: bool,
1688}
1689
1690#[repr(C)]
1692#[derive(Clone, Copy)]
1693pub struct PioDcOpfBranchView {
1694 pub component_id: PioStringView,
1695 pub from_bus_index: usize,
1696 pub to_bus_index: usize,
1697 pub susceptance_magnitude: f64,
1698 pub phase_shift_radians: f64,
1699 pub active_power_max: f64,
1700 pub angle_difference_min_radians: f64,
1701 pub angle_difference_max_radians: f64,
1702 pub analysis_row: usize,
1703 pub source_kind: PioStringView,
1704 pub source_row: usize,
1705 pub winding: usize,
1706 pub has_winding: bool,
1707 pub thermal_limit_active: bool,
1708 pub angle_bound_active: bool,
1709}
1710
1711#[repr(C)]
1713#[derive(Clone, Copy)]
1714pub struct PioAcOpfPreparationView {
1715 pub name: PioStringView,
1716 pub bus_count: usize,
1717 pub generator_count: usize,
1718 pub storage_count: usize,
1719 pub branch_count: usize,
1720 pub source_generator_count: usize,
1721 pub source_branch_count: usize,
1722 pub base_mva: f64,
1723 pub units: PioStringView,
1724 pub objective: PioStringView,
1725 pub skip_zero_impedance: bool,
1726 pub synthesize_unrated_limits: bool,
1727 pub correct_angle_difference_bounds: bool,
1728 pub reference_bus_count: usize,
1729 pub skipped_zero_impedance_count: usize,
1730}
1731
1732#[repr(C)]
1734#[derive(Clone, Copy)]
1735pub struct PioAcOpfBusView {
1736 pub bus_id: usize,
1737 pub analysis_row: usize,
1738 pub source_row: usize,
1739 pub has_source_row: bool,
1740 pub active_power_demand: f64,
1741 pub reactive_power_demand: f64,
1742 pub shunt_conductance: f64,
1743 pub shunt_susceptance: f64,
1744 pub voltage_magnitude_min_pu: f64,
1745 pub voltage_magnitude_max_pu: f64,
1746 pub initial_voltage_magnitude_pu: f64,
1747 pub initial_voltage_angle_radians: f64,
1748 pub voltage_bound_active: bool,
1749}
1750
1751#[repr(C)]
1753#[derive(Clone, Copy)]
1754pub struct PioAcOpfGeneratorView {
1755 pub component_id: PioStringView,
1756 pub bus_index: usize,
1757 pub analysis_row: usize,
1758 pub source_row: usize,
1759 pub has_source_row: bool,
1760 pub quadratic_cost: f64,
1761 pub linear_cost: f64,
1762 pub constant_cost: f64,
1763 pub has_piecewise_linear_cost: bool,
1764 pub piecewise_linear_power: PioF64View,
1765 pub piecewise_linear_value: PioF64View,
1766 pub active_power_max: f64,
1767 pub active_power_min: f64,
1768 pub reactive_power_max: f64,
1769 pub reactive_power_min: f64,
1770 pub initial_active_power: f64,
1771 pub initial_reactive_power: f64,
1772 pub voltage_magnitude_setpoint_pu: f64,
1773 pub capability_active: bool,
1774}
1775
1776#[repr(C)]
1778#[derive(Clone, Copy)]
1779pub struct PioAcOpfStorageView {
1780 pub component_id: PioStringView,
1781 pub bus_index: usize,
1782 pub source_row: usize,
1783 pub initial_active_power: f64,
1784 pub initial_reactive_power: f64,
1785 pub energy: f64,
1786 pub energy_rating: f64,
1787 pub charge_rating: f64,
1788 pub discharge_rating: f64,
1789 pub charge_efficiency: f64,
1790 pub discharge_efficiency: f64,
1791 pub apparent_power_max: f64,
1792 pub reactive_power_min: f64,
1793 pub reactive_power_max: f64,
1794 pub resistance_pu: f64,
1795 pub reactance_pu: f64,
1796 pub active_power_loss: f64,
1797 pub reactive_power_loss: f64,
1798 pub in_service: bool,
1799}
1800
1801#[repr(C)]
1803#[derive(Clone, Copy)]
1804pub struct PioAcOpfBranchView {
1805 pub component_id: PioStringView,
1806 pub from_bus_index: usize,
1807 pub to_bus_index: usize,
1808 pub series_conductance: f64,
1809 pub series_susceptance: f64,
1810 pub from_conductance: f64,
1811 pub from_susceptance: f64,
1812 pub to_conductance: f64,
1813 pub to_susceptance: f64,
1814 pub tap_ratio: f64,
1815 pub phase_shift_radians: f64,
1816 pub apparent_power_max: f64,
1817 pub angle_difference_min_radians: f64,
1818 pub angle_difference_max_radians: f64,
1819 pub analysis_row: usize,
1820 pub source_kind: PioStringView,
1821 pub source_row: usize,
1822 pub winding: usize,
1823 pub has_winding: bool,
1824 pub thermal_limit_active: bool,
1825 pub angle_bound_active: bool,
1826}
1827
1828#[repr(C)]
1830#[derive(Clone, Copy)]
1831pub struct PioObjectiveTermView {
1832 pub kind: PioStringView,
1833}
1834
1835#[repr(C)]
1837#[derive(Clone, Copy)]
1838pub struct PioActiveConstraintView {
1839 pub family: PioStringView,
1840 pub selection: PioStringView,
1841 pub identity_count: usize,
1842}
1843
1844#[repr(C)]
1846#[derive(Clone, Copy)]
1847pub struct PioPrescribedTerminalPowerView {
1848 pub load: PioStringView,
1849 pub terminal_count: usize,
1850 pub voltage_model: PioStringView,
1851}
1852
1853#[repr(C)]
1855#[derive(Clone, Copy)]
1856pub struct PioTerminalPowerView {
1857 pub terminal: PioStringView,
1858 pub active_power_w: f64,
1859 pub reactive_power_var: f64,
1860 pub nominal_voltage_v: f64,
1861 pub has_nominal_voltage: bool,
1862 pub active_impedance_fraction: f64,
1863 pub active_current_fraction: f64,
1864 pub active_power_fraction: f64,
1865 pub reactive_impedance_fraction: f64,
1866 pub reactive_current_fraction: f64,
1867 pub reactive_power_fraction: f64,
1868 pub active_power_exponent: f64,
1869 pub reactive_power_exponent: f64,
1870}
1871
1872#[repr(C)]
1874#[derive(Clone, Copy)]
1875pub struct PioPrescribedSourceVoltageView {
1876 pub source: PioStringView,
1877 pub terminal_count: usize,
1878}
1879
1880#[repr(C)]
1882#[derive(Clone, Copy)]
1883pub struct PioTerminalVoltageView {
1884 pub terminal: PioStringView,
1885 pub magnitude_v: f64,
1886 pub angle_radians: f64,
1887}
1888
1889#[repr(C)]
1891#[derive(Clone, Copy)]
1892pub struct PioIsolatedTerminalView {
1893 pub bus: PioStringView,
1894 pub terminal: PioStringView,
1895}
1896
1897#[repr(C)]
1899#[derive(Clone, Copy)]
1900pub struct PioActiveControlView {
1901 pub kind: PioStringView,
1902 pub component_id: PioStringView,
1903}
1904
1905#[repr(C)]
1907#[derive(Clone, Copy)]
1908pub struct PioScucDimensionsView {
1909 pub period_count: usize,
1910 pub device_count: usize,
1911 pub producer_count: usize,
1912 pub consumer_count: usize,
1913 pub shunt_count: usize,
1914 pub branch_switching_cost_count: usize,
1915 pub transformer_control_count: usize,
1916 pub active_reserve_zone_count: usize,
1917 pub reactive_reserve_zone_count: usize,
1918 pub contingency_count: usize,
1919}
1920
1921#[repr(C)]
1923#[derive(Clone, Copy)]
1924pub struct PioScucViolationCostView {
1925 pub active_power_balance: f64,
1926 pub reactive_power_balance: f64,
1927 pub branch_thermal_limit: f64,
1928 pub energy_requirement: f64,
1929}
1930
1931#[repr(C)]
1933#[derive(Clone, Copy)]
1934pub struct PioComponentIdView {
1935 pub component_type: PioStringView,
1936 pub local_id: PioStringView,
1937}
1938
1939#[repr(C)]
1941#[derive(Clone, Copy)]
1942pub struct PioScucRampLimitsView {
1943 pub up_pu_per_hour: f64,
1944 pub down_pu_per_hour: f64,
1945 pub startup_pu_per_hour: f64,
1946 pub shutdown_pu_per_hour: f64,
1947}
1948
1949#[repr(C)]
1951#[derive(Clone, Copy)]
1952pub struct PioScucReserveLimitsView {
1953 pub regulation_up_pu: f64,
1954 pub regulation_down_pu: f64,
1955 pub synchronized_pu: f64,
1956 pub nonsynchronized_pu: f64,
1957 pub ramping_up_online_pu: f64,
1958 pub ramping_down_online_pu: f64,
1959 pub ramping_up_offline_pu: f64,
1960 pub ramping_down_offline_pu: f64,
1961}
1962
1963#[repr(C)]
1965#[derive(Clone, Copy)]
1966pub struct PioScucInitialCommitmentView {
1967 pub accumulated_up_time_hours: f64,
1968 pub accumulated_down_time_hours: f64,
1969}
1970
1971#[repr(C)]
1973#[derive(Clone, Copy)]
1974pub struct PioScucReactiveCapabilityView {
1975 pub kind: PioStringView,
1976 pub reactive_power_at_zero_active_power_pu: f64,
1977 pub reactive_power_at_zero_active_power_min_pu: f64,
1978 pub reactive_power_at_zero_active_power_max_pu: f64,
1979 pub slope: f64,
1980 pub slope_min: f64,
1981 pub slope_max: f64,
1982}
1983
1984#[repr(C)]
1986#[derive(Clone, Copy)]
1987pub struct PioScucDeviceView {
1988 pub id: PioComponentIdView,
1989 pub kind: PioStringView,
1990 pub initial_on_status: bool,
1991 pub on_cost: f64,
1992 pub startup_cost: f64,
1993 pub shutdown_cost: f64,
1994 pub minimum_up_time_hours: f64,
1995 pub minimum_down_time_hours: f64,
1996 pub ramp_limits: PioScucRampLimitsView,
1997 pub reserve_limits: PioScucReserveLimitsView,
1998 pub initial_commitment: PioScucInitialCommitmentView,
1999 pub reactive_capability: PioScucReactiveCapabilityView,
2000 pub period_count: usize,
2001 pub startup_cost_adjustment_count: usize,
2002 pub startup_limit_count: usize,
2003 pub energy_upper_bound_count: usize,
2004 pub energy_lower_bound_count: usize,
2005}
2006
2007#[repr(C)]
2009#[derive(Clone, Copy)]
2010pub struct PioScucStartupCostAdjustmentView {
2011 pub cost: f64,
2012 pub maximum_down_time_hours: f64,
2013}
2014
2015#[repr(C)]
2017#[derive(Clone, Copy)]
2018pub struct PioScucDevicePeriodView {
2019 pub on_status_min: bool,
2020 pub on_status_max: bool,
2021 pub active_power_min_pu: f64,
2022 pub active_power_max_pu: f64,
2023 pub reactive_power_min_pu: f64,
2024 pub reactive_power_max_pu: f64,
2025 pub energy_cost_block_count: usize,
2026 pub reserve_costs: PioScucReserveCostsView,
2027}
2028
2029#[repr(C)]
2031#[derive(Clone, Copy)]
2032pub struct PioScucStartupLimitView {
2033 pub start_time_hours: f64,
2034 pub end_time_hours: f64,
2035 pub maximum_startups: u64,
2036}
2037
2038#[repr(C)]
2040#[derive(Clone, Copy)]
2041pub struct PioScucEnergyRequirementView {
2042 pub start_time_hours: f64,
2043 pub end_time_hours: f64,
2044 pub energy_pu: f64,
2045}
2046
2047#[repr(C)]
2049#[derive(Clone, Copy)]
2050pub struct PioScucEnergyCostBlockView {
2051 pub marginal_cost: f64,
2052 pub block_size_pu: f64,
2053}
2054
2055#[repr(C)]
2057#[derive(Clone, Copy)]
2058pub struct PioScucReserveCostsView {
2059 pub regulation_up: f64,
2060 pub regulation_down: f64,
2061 pub synchronized: f64,
2062 pub nonsynchronized: f64,
2063 pub ramping_up_online: f64,
2064 pub ramping_down_online: f64,
2065 pub ramping_up_offline: f64,
2066 pub ramping_down_offline: f64,
2067 pub reactive_up: f64,
2068 pub reactive_down: f64,
2069}
2070
2071#[repr(C)]
2073#[derive(Clone, Copy)]
2074pub struct PioScucShuntView {
2075 pub id: PioComponentIdView,
2076 pub conductance_per_step_pu: f64,
2077 pub susceptance_per_step_pu: f64,
2078 pub step_min: i64,
2079 pub step_max: i64,
2080 pub initial_step: i64,
2081}
2082
2083#[repr(C)]
2085#[derive(Clone, Copy)]
2086pub struct PioScucBranchSwitchingCostView {
2087 pub id: PioComponentIdView,
2088 pub connection_cost: f64,
2089 pub disconnection_cost: f64,
2090}
2091
2092#[repr(C)]
2094#[derive(Clone, Copy)]
2095pub struct PioScucTransformerControlView {
2096 pub id: PioComponentIdView,
2097 pub tap_ratio_min: f64,
2098 pub tap_ratio_max: f64,
2099 pub phase_shift_min_radians: f64,
2100 pub phase_shift_max_radians: f64,
2101}
2102
2103#[repr(C)]
2105#[derive(Clone, Copy)]
2106pub struct PioScucActiveReserveZoneView {
2107 pub id: PioComponentIdView,
2108 pub regulation_up_requirement_fraction: f64,
2109 pub regulation_down_requirement_fraction: f64,
2110 pub synchronized_requirement_fraction: f64,
2111 pub nonsynchronized_requirement_fraction: f64,
2112 pub regulation_up_violation_cost: f64,
2113 pub regulation_down_violation_cost: f64,
2114 pub synchronized_violation_cost: f64,
2115 pub nonsynchronized_violation_cost: f64,
2116 pub ramping_up_violation_cost: f64,
2117 pub ramping_down_violation_cost: f64,
2118 pub period_count: usize,
2119 pub bus_count: usize,
2120}
2121
2122#[repr(C)]
2124#[derive(Clone, Copy)]
2125pub struct PioScucActiveReservePeriodView {
2126 pub ramping_up_requirement_pu: f64,
2127 pub ramping_down_requirement_pu: f64,
2128}
2129
2130#[repr(C)]
2132#[derive(Clone, Copy)]
2133pub struct PioScucReactiveReserveZoneView {
2134 pub id: PioComponentIdView,
2135 pub reactive_up_violation_cost: f64,
2136 pub reactive_down_violation_cost: f64,
2137 pub period_count: usize,
2138 pub bus_count: usize,
2139}
2140
2141#[repr(C)]
2143#[derive(Clone, Copy)]
2144pub struct PioScucReactiveReservePeriodView {
2145 pub reactive_up_requirement_pu: f64,
2146 pub reactive_down_requirement_pu: f64,
2147}
2148
2149#[repr(C)]
2151#[derive(Clone, Copy)]
2152pub struct PioScucContingencyView {
2153 pub id: PioComponentIdView,
2154 pub component_count: usize,
2155}
2156
2157#[repr(C)]
2159#[derive(Clone, Copy)]
2160pub struct PioScucContingencyComponentView {
2161 pub id: PioComponentIdView,
2162}
2163
2164const fn assert_send_sync<T: Send + Sync>() {}
2169
2170#[repr(transparent)]
2171struct HandleBox<T> {
2172 inner: Arc<T>,
2173}
2174
2175fn handle_new<T>(value: T) -> *mut HandleBox<T> {
2176 Box::into_raw(Box::new(HandleBox {
2177 inner: Arc::new(value),
2178 }))
2179}
2180
2181fn handle_from_arc<T>(inner: Arc<T>) -> *mut HandleBox<T> {
2182 Box::into_raw(Box::new(HandleBox { inner }))
2183}
2184
2185unsafe fn handle_get<'a, T>(raw: *const HandleBox<T>) -> Option<&'a T> {
2186 unsafe { raw.as_ref() }.map(|handle| handle.inner.as_ref())
2187}
2188
2189unsafe fn handle_arc<T>(raw: *const HandleBox<T>) -> Option<Arc<T>> {
2190 unsafe { raw.as_ref() }.map(|handle| Arc::clone(&handle.inner))
2191}
2192
2193unsafe fn handle_retain<T>(raw: *const HandleBox<T>) -> *mut HandleBox<T> {
2194 unsafe { handle_arc(raw) }.map_or(std::ptr::null_mut(), handle_from_arc)
2195}
2196
2197unsafe fn handle_release<T>(raw: *mut HandleBox<T>) {
2198 if !raw.is_null() {
2199 drop(unsafe { Box::from_raw(raw) });
2200 }
2201}
2202
2203macro_rules! opaque_handle {
2204 ($(#[$doc:meta])* $name:ident, $inner:ty) => {
2205 $(#[$doc])*
2206 #[repr(transparent)]
2207 pub struct $name(HandleBox<$inner>);
2208 const _: () = assert_send_sync::<$inner>();
2209
2210 #[allow(dead_code)]
2211 impl $name {
2212 fn new_raw(value: $inner) -> *mut Self {
2213 handle_new(value).cast()
2214 }
2215
2216 fn from_arc(inner: Arc<$inner>) -> *mut Self {
2217 handle_from_arc(inner).cast()
2218 }
2219
2220 unsafe fn get<'a>(raw: *const Self) -> Option<&'a $inner> {
2221 unsafe { handle_get(raw.cast()) }
2222 }
2223
2224 unsafe fn arc(raw: *const Self) -> Option<Arc<$inner>> {
2225 unsafe { handle_arc(raw.cast()) }
2226 }
2227
2228 unsafe fn retain_raw(raw: *const Self) -> *mut Self {
2229 unsafe { handle_retain(raw.cast::<HandleBox<$inner>>()) }.cast::<Self>()
2230 }
2231
2232 unsafe fn release_raw(raw: *mut Self) {
2233 unsafe { handle_release(raw.cast::<HandleBox<$inner>>()) }
2234 }
2235 }
2236 };
2237}
2238
2239struct ErrorInner {
2242 code: String,
2243 message: String,
2244 diagnostics: Arc<DiagnosticsInner>,
2245}
2246
2247struct DiagnosticsInner {
2248 owner: DiagnosticsOwner,
2249}
2250
2251enum DiagnosticsOwner {
2252 Owned(Vec<Diagnostic>),
2253 Module(Arc<ModuleInner>),
2254}
2255
2256impl DiagnosticsInner {
2257 fn records(&self) -> &[Diagnostic] {
2258 match &self.owner {
2259 DiagnosticsOwner::Owned(records) => records,
2260 DiagnosticsOwner::Module(module) => module.module.diagnostics(),
2261 }
2262 }
2263}
2264
2265opaque_handle!(
2266 PioError,
2268 ErrorInner
2269);
2270opaque_handle!(
2271 PioDiagnostics,
2273 DiagnosticsInner
2274);
2275
2276fn boundary_diagnostic(
2277 info: &'static powerio_core::DiagnosticInfo,
2278 message: impl Into<String>,
2279) -> Diagnostic {
2280 Diagnostic::of(info, message)
2281}
2282
2283fn error_from_diagnostics(message: String, mut records: Vec<Diagnostic>) -> *mut PioError {
2284 if records.is_empty() {
2285 records.push(boundary_diagnostic(
2286 &codes::BIND_CAPI_UNCODED_FAILURE,
2287 message.clone(),
2288 ));
2289 }
2290 let code = records[0].code().to_owned();
2291 PioError::new_raw(ErrorInner {
2292 code,
2293 message,
2294 diagnostics: Arc::new(DiagnosticsInner {
2295 owner: DiagnosticsOwner::Owned(records),
2296 }),
2297 })
2298}
2299
2300fn error_from_core(error: &powerio_core::Error) -> *mut PioError {
2301 error_from_diagnostics(error.to_string(), error.diagnostics().to_vec())
2302}
2303
2304fn error_from_tx(error: &powerio_tx::Error) -> *mut PioError {
2305 let diagnostic = Diagnostic::of(error.code(), error.to_string());
2306 error_from_diagnostics(error.to_string(), vec![diagnostic])
2307}
2308
2309fn error_from_matrix(error: &powerio_matrix::Error) -> *mut PioError {
2310 let diagnostic = Diagnostic::of(error.code(), error.to_string());
2311 error_from_diagnostics(error.to_string(), vec![diagnostic])
2312}
2313
2314fn boundary_error(
2315 info: &'static powerio_core::DiagnosticInfo,
2316 message: impl Into<String>,
2317) -> *mut PioError {
2318 let diagnostic = boundary_diagnostic(info, message);
2319 error_from_diagnostics(
2320 powerio_core::render_diagnostic(&diagnostic),
2321 vec![diagnostic],
2322 )
2323}
2324
2325unsafe fn store_error(slot: *mut *mut PioError, error: *mut PioError) {
2326 if slot.is_null() {
2327 unsafe { PioError::release_raw(error) };
2328 } else {
2329 unsafe { *slot = error };
2330 }
2331}
2332
2333unsafe fn entry<R>(
2334 error: *mut *mut PioError,
2335 fallback: R,
2336 operation: impl FnOnce() -> Result<R, *mut PioError>,
2337) -> R {
2338 if !error.is_null() {
2339 unsafe { *error = std::ptr::null_mut() };
2340 }
2341 match catch_unwind(AssertUnwindSafe(operation)) {
2342 Ok(Ok(value)) => value,
2343 Ok(Err(failure)) => {
2344 unsafe { store_error(error, failure) };
2345 fallback
2346 }
2347 Err(_) => {
2348 let failure = boundary_error(
2349 &codes::BIND_CAPI_PANIC,
2350 "the operation panicked and made no C-visible change",
2351 );
2352 unsafe { store_error(error, failure) };
2353 fallback
2354 }
2355 }
2356}
2357
2358unsafe fn input_bytes<'a>(
2359 data: *const u8,
2360 len: usize,
2361 argument: &str,
2362) -> Result<&'a [u8], *mut PioError> {
2363 if data.is_null() {
2364 if len == 0 {
2365 return Ok(&[]);
2366 }
2367 return Err(boundary_error(
2368 &codes::BIND_CAPI_NULL_ARGUMENT,
2369 format!("{argument} is NULL with a nonzero length"),
2370 ));
2371 }
2372 Ok(unsafe { std::slice::from_raw_parts(data, len) })
2373}
2374
2375unsafe fn input_str<'a>(
2376 data: *const c_char,
2377 len: usize,
2378 argument: &str,
2379) -> Result<&'a str, *mut PioError> {
2380 let bytes = unsafe { input_bytes(data.cast(), len, argument) }?;
2381 std::str::from_utf8(bytes).map_err(|_| {
2382 boundary_error(
2383 &codes::BIND_CAPI_INVALID_UTF8,
2384 format!("{argument} is not valid UTF-8"),
2385 )
2386 })
2387}
2388
2389unsafe fn required_str<'a>(
2390 data: *const c_char,
2391 len: usize,
2392 argument: &str,
2393) -> Result<&'a str, *mut PioError> {
2394 if data.is_null() || len == 0 {
2395 return Err(boundary_error(
2396 &codes::BIND_CAPI_NULL_ARGUMENT,
2397 format!("{argument} is required"),
2398 ));
2399 }
2400 unsafe { input_str(data, len, argument) }
2401}
2402
2403unsafe fn optional_str<'a>(
2404 data: *const c_char,
2405 len: usize,
2406 argument: &str,
2407) -> Result<Option<&'a str>, *mut PioError> {
2408 if data.is_null() {
2409 if len == 0 {
2410 return Ok(None);
2411 }
2412 return Err(boundary_error(
2413 &codes::BIND_CAPI_NULL_ARGUMENT,
2414 format!("{argument} is NULL with a nonzero length"),
2415 ));
2416 }
2417 unsafe { input_str(data, len, argument) }.map(Some)
2418}
2419
2420#[unsafe(no_mangle)]
2422pub extern "C" fn pio_abi_version() -> u32 {
2423 PIO_ABI_VERSION
2424}
2425
2426#[unsafe(no_mangle)]
2428pub extern "C" fn pio_version() -> PioStringView {
2429 PioStringView::new(powerio::VERSION)
2430}
2431
2432#[unsafe(no_mangle)]
2434pub unsafe extern "C" fn pio_error_code(error: *const PioError) -> PioStringView {
2435 unsafe { PioError::get(error) }.map_or(PioStringView::EMPTY, |error| {
2436 PioStringView::new(&error.code)
2437 })
2438}
2439
2440#[unsafe(no_mangle)]
2442pub unsafe extern "C" fn pio_error_message(error: *const PioError) -> PioStringView {
2443 unsafe { PioError::get(error) }.map_or(PioStringView::EMPTY, |error| {
2444 PioStringView::new(&error.message)
2445 })
2446}
2447
2448#[unsafe(no_mangle)]
2450pub unsafe extern "C" fn pio_error_diagnostics(error: *const PioError) -> *mut PioDiagnostics {
2451 unsafe { PioError::get(error) }.map_or(std::ptr::null_mut(), |error| {
2452 PioDiagnostics::from_arc(Arc::clone(&error.diagnostics))
2453 })
2454}
2455
2456#[unsafe(no_mangle)]
2457pub unsafe extern "C" fn pio_error_retain(error: *const PioError) -> *mut PioError {
2458 unsafe { PioError::retain_raw(error) }
2459}
2460
2461#[unsafe(no_mangle)]
2462pub unsafe extern "C" fn pio_error_release(error: *mut PioError) {
2463 unsafe { PioError::release_raw(error) };
2464}
2465
2466#[unsafe(no_mangle)]
2467pub unsafe extern "C" fn pio_diagnostics_len(diagnostics: *const PioDiagnostics) -> usize {
2468 unsafe { PioDiagnostics::get(diagnostics) }.map_or(0, |values| values.records().len())
2469}
2470
2471#[unsafe(no_mangle)]
2472pub unsafe extern "C" fn pio_diagnostic_code(
2473 diagnostics: *const PioDiagnostics,
2474 index: usize,
2475) -> PioStringView {
2476 unsafe { PioDiagnostics::get(diagnostics) }
2477 .and_then(|values| values.records().get(index))
2478 .map_or(PioStringView::EMPTY, |record| {
2479 PioStringView::new(record.code())
2480 })
2481}
2482
2483#[unsafe(no_mangle)]
2484pub unsafe extern "C" fn pio_diagnostic_severity(
2485 diagnostics: *const PioDiagnostics,
2486 index: usize,
2487) -> PioStringView {
2488 unsafe { PioDiagnostics::get(diagnostics) }
2489 .and_then(|values| values.records().get(index))
2490 .map_or(PioStringView::EMPTY, |record| {
2491 PioStringView::new(record.severity().as_str())
2492 })
2493}
2494
2495#[unsafe(no_mangle)]
2496pub unsafe extern "C" fn pio_diagnostic_message(
2497 diagnostics: *const PioDiagnostics,
2498 index: usize,
2499) -> PioStringView {
2500 unsafe { PioDiagnostics::get(diagnostics) }
2501 .and_then(|values| values.records().get(index))
2502 .map_or(PioStringView::EMPTY, |record| {
2503 PioStringView::new(record.message())
2504 })
2505}
2506
2507#[unsafe(no_mangle)]
2509pub unsafe extern "C" fn pio_diagnostic_has_id(
2510 diagnostics: *const PioDiagnostics,
2511 index: usize,
2512) -> bool {
2513 unsafe { PioDiagnostics::get(diagnostics) }
2514 .and_then(|values| values.records().get(index))
2515 .is_some_and(|record| record.id().is_some())
2516}
2517
2518#[unsafe(no_mangle)]
2520pub unsafe extern "C" fn pio_diagnostic_id(
2521 diagnostics: *const PioDiagnostics,
2522 index: usize,
2523) -> PioStringView {
2524 unsafe { PioDiagnostics::get(diagnostics) }
2525 .and_then(|values| values.records().get(index))
2526 .and_then(Diagnostic::id)
2527 .map_or(PioStringView::EMPTY, |id| PioStringView::new(id.as_str()))
2528}
2529
2530#[unsafe(no_mangle)]
2532pub unsafe extern "C" fn pio_diagnostic_has_target(
2533 diagnostics: *const PioDiagnostics,
2534 index: usize,
2535) -> bool {
2536 unsafe { PioDiagnostics::get(diagnostics) }
2537 .and_then(|values| values.records().get(index))
2538 .is_some_and(|record| record.target().is_some())
2539}
2540
2541#[unsafe(no_mangle)]
2543pub unsafe extern "C" fn pio_diagnostic_target(
2544 diagnostics: *const PioDiagnostics,
2545 index: usize,
2546) -> PioStringView {
2547 unsafe { PioDiagnostics::get(diagnostics) }
2548 .and_then(|values| values.records().get(index))
2549 .and_then(Diagnostic::target)
2550 .map_or(PioStringView::EMPTY, PioStringView::new)
2551}
2552
2553#[unsafe(no_mangle)]
2555pub unsafe extern "C" fn pio_diagnostic_has_suggested_action(
2556 diagnostics: *const PioDiagnostics,
2557 index: usize,
2558) -> bool {
2559 unsafe { PioDiagnostics::get(diagnostics) }
2560 .and_then(|values| values.records().get(index))
2561 .is_some_and(|record| record.suggested_action().is_some())
2562}
2563
2564#[unsafe(no_mangle)]
2566pub unsafe extern "C" fn pio_diagnostic_suggested_action(
2567 diagnostics: *const PioDiagnostics,
2568 index: usize,
2569) -> PioStringView {
2570 unsafe { PioDiagnostics::get(diagnostics) }
2571 .and_then(|values| values.records().get(index))
2572 .and_then(Diagnostic::suggested_action)
2573 .map_or(PioStringView::EMPTY, PioStringView::new)
2574}
2575
2576#[unsafe(no_mangle)]
2578pub unsafe extern "C" fn pio_diagnostic_n_spans(
2579 diagnostics: *const PioDiagnostics,
2580 index: usize,
2581) -> usize {
2582 unsafe { PioDiagnostics::get(diagnostics) }
2583 .and_then(|values| values.records().get(index))
2584 .map_or(0, |record| record.spans().len())
2585}
2586
2587#[unsafe(no_mangle)]
2589pub unsafe extern "C" fn pio_diagnostic_span(
2590 diagnostics: *const PioDiagnostics,
2591 index: usize,
2592 span_index: usize,
2593 output: *mut PioDiagnosticSpanView,
2594 error: *mut *mut PioError,
2595) -> bool {
2596 unsafe {
2597 entry(error, false, || {
2598 let values = PioDiagnostics::get(diagnostics).ok_or_else(|| {
2599 boundary_error(
2600 &codes::BIND_CAPI_NULL_HANDLE,
2601 "PioDiagnostics must not be NULL",
2602 )
2603 })?;
2604 let record = values.records().get(index).ok_or_else(|| {
2605 boundary_error(
2606 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
2607 format!("diagnostic index {index} is out of range"),
2608 )
2609 })?;
2610 let span = record.spans().get(span_index).ok_or_else(|| {
2611 boundary_error(
2612 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
2613 format!("diagnostic span index {span_index} is out of range"),
2614 )
2615 })?;
2616 *require_output(output, "output")? = PioDiagnosticSpanView {
2617 source: PioStringView::new(span.source().as_str()),
2618 byte_start: span.byte_start(),
2619 byte_end: span.byte_end(),
2620 };
2621 Ok(true)
2622 })
2623 }
2624}
2625
2626#[unsafe(no_mangle)]
2628pub unsafe extern "C" fn pio_diagnostic_n_related(
2629 diagnostics: *const PioDiagnostics,
2630 index: usize,
2631) -> usize {
2632 unsafe { PioDiagnostics::get(diagnostics) }
2633 .and_then(|values| values.records().get(index))
2634 .map_or(0, |record| record.related().len())
2635}
2636
2637#[unsafe(no_mangle)]
2639pub unsafe extern "C" fn pio_diagnostic_related(
2640 diagnostics: *const PioDiagnostics,
2641 index: usize,
2642 related_index: usize,
2643) -> PioStringView {
2644 unsafe { PioDiagnostics::get(diagnostics) }
2645 .and_then(|values| values.records().get(index))
2646 .and_then(|record| record.related().get(related_index))
2647 .map_or(PioStringView::EMPTY, |id| PioStringView::new(id.as_str()))
2648}
2649
2650#[unsafe(no_mangle)]
2652pub unsafe extern "C" fn pio_diagnostic_details_json(
2653 diagnostics: *const PioDiagnostics,
2654 index: usize,
2655 error: *mut *mut PioError,
2656) -> *mut PioString {
2657 unsafe {
2658 entry(error, std::ptr::null_mut(), || {
2659 let values = PioDiagnostics::get(diagnostics).ok_or_else(|| {
2660 boundary_error(
2661 &codes::BIND_CAPI_NULL_HANDLE,
2662 "PioDiagnostics must not be NULL",
2663 )
2664 })?;
2665 let record = values.records().get(index).ok_or_else(|| {
2666 boundary_error(
2667 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
2668 format!("diagnostic index {index} is out of range"),
2669 )
2670 })?;
2671 serde_json::to_string(record.details())
2672 .map(|text| PioString::new_raw(StringInner { text }))
2673 .map_err(|failure| {
2674 boundary_error(
2675 &codes::EMIT_CAPI_SERIALIZE_FAILED,
2676 format!("cannot serialize diagnostic details: {failure}"),
2677 )
2678 })
2679 })
2680 }
2681}
2682
2683#[unsafe(no_mangle)]
2684pub unsafe extern "C" fn pio_diagnostics_retain(
2685 diagnostics: *const PioDiagnostics,
2686) -> *mut PioDiagnostics {
2687 unsafe { PioDiagnostics::retain_raw(diagnostics) }
2688}
2689
2690#[unsafe(no_mangle)]
2691pub unsafe extern "C" fn pio_diagnostics_release(diagnostics: *mut PioDiagnostics) {
2692 unsafe { PioDiagnostics::release_raw(diagnostics) };
2693}
2694
2695opaque_handle!(
2698 PioSource,
2700 Source
2701);
2702
2703enum DestinationSpec {
2704 Path(PathBuf),
2705 Memory(String),
2706}
2707
2708impl DestinationSpec {
2709 fn build(&self) -> Result<Destination, powerio_core::Error> {
2710 match self {
2711 Self::Path(path) => Ok(Destination::path(path)),
2712 Self::Memory(root) => Destination::memory(root.clone()),
2713 }
2714 }
2715}
2716
2717opaque_handle!(
2718 PioDestination,
2720 DestinationSpec
2721);
2722
2723struct GeoLayerInner {
2724 layer: powerio::GeoLayer,
2725 diagnostics: Vec<Diagnostic>,
2726}
2727
2728opaque_handle!(
2729 PioGeoLayer,
2731 GeoLayerInner
2732);
2733opaque_handle!(
2734 PioGeoApplyReport,
2736 powerio::GeoApplyReport
2737);
2738
2739#[unsafe(no_mangle)]
2741pub unsafe extern "C" fn pio_source_open(
2742 path: *const c_char,
2743 path_len: usize,
2744 error: *mut *mut PioError,
2745) -> *mut PioSource {
2746 unsafe {
2747 entry(error, std::ptr::null_mut(), || {
2748 let path = required_str(path, path_len, "path")?;
2749 Source::open(path)
2750 .map(PioSource::new_raw)
2751 .map_err(|failure| error_from_core(&failure))
2752 })
2753 }
2754}
2755
2756#[unsafe(no_mangle)]
2758pub unsafe extern "C" fn pio_source_from_memory(
2759 name: *const c_char,
2760 name_len: usize,
2761 data: *const u8,
2762 data_len: usize,
2763 error: *mut *mut PioError,
2764) -> *mut PioSource {
2765 unsafe {
2766 entry(error, std::ptr::null_mut(), || {
2767 let name = required_str(name, name_len, "name")?;
2768 let bytes = input_bytes(data, data_len, "data")?;
2769 Source::from_memory(name, bytes.to_vec())
2770 .map(PioSource::new_raw)
2771 .map_err(|failure| error_from_core(&failure))
2772 })
2773 }
2774}
2775
2776#[unsafe(no_mangle)]
2777pub unsafe extern "C" fn pio_source_retain(source: *const PioSource) -> *mut PioSource {
2778 unsafe { PioSource::retain_raw(source) }
2779}
2780
2781#[unsafe(no_mangle)]
2782pub unsafe extern "C" fn pio_source_release(source: *mut PioSource) {
2783 unsafe { PioSource::release_raw(source) };
2784}
2785
2786#[unsafe(no_mangle)]
2788pub unsafe extern "C" fn pio_destination_path(
2789 path: *const c_char,
2790 path_len: usize,
2791 error: *mut *mut PioError,
2792) -> *mut PioDestination {
2793 unsafe {
2794 entry(error, std::ptr::null_mut(), || {
2795 let path = required_str(path, path_len, "path")?;
2796 Ok(PioDestination::new_raw(DestinationSpec::Path(
2797 PathBuf::from(path),
2798 )))
2799 })
2800 }
2801}
2802
2803#[unsafe(no_mangle)]
2805pub unsafe extern "C" fn pio_destination_memory(
2806 root: *const c_char,
2807 root_len: usize,
2808 error: *mut *mut PioError,
2809) -> *mut PioDestination {
2810 unsafe {
2811 entry(error, std::ptr::null_mut(), || {
2812 let root = required_str(root, root_len, "root")?;
2813 Destination::memory(root).map_err(|failure| error_from_core(&failure))?;
2814 Ok(PioDestination::new_raw(DestinationSpec::Memory(
2815 root.to_owned(),
2816 )))
2817 })
2818 }
2819}
2820
2821#[unsafe(no_mangle)]
2822pub unsafe extern "C" fn pio_destination_retain(
2823 destination: *const PioDestination,
2824) -> *mut PioDestination {
2825 unsafe { PioDestination::retain_raw(destination) }
2826}
2827
2828#[unsafe(no_mangle)]
2829pub unsafe extern "C" fn pio_destination_release(destination: *mut PioDestination) {
2830 unsafe { PioDestination::release_raw(destination) };
2831}
2832
2833#[unsafe(no_mangle)]
2835pub unsafe extern "C" fn pio_geo_layer_parse(
2836 source: *const PioSource,
2837 error: *mut *mut PioError,
2838) -> *mut PioGeoLayer {
2839 unsafe {
2840 entry(error, std::ptr::null_mut(), || {
2841 let source = PioSource::get(source).ok_or_else(|| {
2842 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioSource must not be NULL")
2843 })?;
2844 let buffer = source
2845 .primary_buffer()
2846 .map_err(|failure| error_from_core(&failure))?;
2847 let text = std::str::from_utf8(buffer.content_bytes()).map_err(|_| {
2848 boundary_error(
2849 &codes::BIND_CAPI_INVALID_UTF8,
2850 "a geographic sidecar must be valid UTF-8",
2851 )
2852 })?;
2853 powerio::GeoLayer::parse(text, Some(source.name()))
2854 .map(|parsed| {
2855 PioGeoLayer::new_raw(GeoLayerInner {
2856 layer: parsed.layer,
2857 diagnostics: parsed.diagnostics,
2858 })
2859 })
2860 .map_err(|failure| error_from_tx(&failure))
2861 })
2862 }
2863}
2864
2865#[unsafe(no_mangle)]
2867pub unsafe extern "C" fn pio_geo_layer_diagnostics(
2868 layer: *const PioGeoLayer,
2869) -> *mut PioDiagnostics {
2870 unsafe { PioGeoLayer::get(layer) }.map_or(std::ptr::null_mut(), |layer| {
2871 PioDiagnostics::new_raw(DiagnosticsInner {
2872 owner: DiagnosticsOwner::Owned(layer.diagnostics.clone()),
2873 })
2874 })
2875}
2876
2877#[unsafe(no_mangle)]
2878pub unsafe extern "C" fn pio_geo_layer_retain(layer: *const PioGeoLayer) -> *mut PioGeoLayer {
2879 unsafe { PioGeoLayer::retain_raw(layer) }
2880}
2881
2882#[unsafe(no_mangle)]
2883pub unsafe extern "C" fn pio_geo_layer_release(layer: *mut PioGeoLayer) {
2884 unsafe { PioGeoLayer::release_raw(layer) };
2885}
2886
2887#[derive(Clone)]
2890struct ModuleInner {
2891 module: powerio::PioModule<PioValue>,
2892}
2893
2894opaque_handle!(
2895 PioModule,
2897 ModuleInner
2898);
2899
2900#[derive(Clone, Copy)]
2901enum ModuleJsonRoot {
2902 Extension(usize),
2903 HistoryParameter {
2904 history_index: usize,
2905 parameter_index: usize,
2906 },
2907}
2908
2909#[derive(Clone, Copy)]
2910enum JsonValueStep {
2911 Array(usize),
2912 Object(usize),
2913}
2914
2915struct JsonValueInner {
2916 owner: Arc<ModuleInner>,
2917 root: ModuleJsonRoot,
2918 steps: Vec<JsonValueStep>,
2919}
2920
2921impl JsonValueInner {
2922 fn child(&self, step: JsonValueStep) -> Self {
2923 let mut steps = self.steps.clone();
2924 steps.push(step);
2925 Self {
2926 owner: Arc::clone(&self.owner),
2927 root: self.root,
2928 steps,
2929 }
2930 }
2931
2932 fn value(&self) -> Option<&serde_json::Value> {
2933 let mut value = match self.root {
2934 ModuleJsonRoot::Extension(index) => {
2935 self.owner.module.extensions().values().nth(index)?
2936 }
2937 ModuleJsonRoot::HistoryParameter {
2938 history_index,
2939 parameter_index,
2940 } => self
2941 .owner
2942 .module
2943 .history()
2944 .get(history_index)?
2945 .parameters()
2946 .values()
2947 .nth(parameter_index)?,
2948 };
2949 for step in &self.steps {
2950 value = match (step, value) {
2951 (JsonValueStep::Array(index), serde_json::Value::Array(values)) => {
2952 values.get(*index)?
2953 }
2954 (JsonValueStep::Object(index), serde_json::Value::Object(values)) => {
2955 values.values().nth(*index)?
2956 }
2957 _ => return None,
2958 };
2959 }
2960 Some(value)
2961 }
2962}
2963
2964opaque_handle!(
2965 PioJsonValue,
2967 JsonValueInner
2968);
2969
2970#[derive(Clone, Copy)]
2971enum ValueStep {
2972 TimeSeries(usize),
2973 Scenario(usize),
2974}
2975
2976struct ValueInner {
2977 owner: Arc<ModuleInner>,
2978 steps: Vec<ValueStep>,
2979}
2980
2981impl ValueInner {
2982 fn root(owner: Arc<ModuleInner>) -> Self {
2983 Self {
2984 owner,
2985 steps: Vec::new(),
2986 }
2987 }
2988
2989 fn child(&self, step: ValueStep) -> Self {
2990 let mut steps = self.steps.clone();
2991 steps.push(step);
2992 Self {
2993 owner: Arc::clone(&self.owner),
2994 steps,
2995 }
2996 }
2997
2998 fn value(&self) -> Option<&PioValue> {
2999 let mut value = self.owner.module.value();
3000 for step in &self.steps {
3001 value = match (step, value) {
3002 (ValueStep::TimeSeries(index), PioValue::TimeSeries(series)) => {
3003 series.get(*index)?
3004 }
3005 (ValueStep::Scenario(index), PioValue::ScenarioSet(scenarios)) => {
3006 scenarios.get_at(*index)?
3007 }
3008 _ => return None,
3009 };
3010 }
3011 Some(value)
3012 }
3013}
3014
3015opaque_handle!(
3016 PioValueHandle,
3018 ValueInner
3019);
3020
3021#[derive(Clone, Copy)]
3022enum BalancedNetworkProjection {
3023 Direct,
3024 OperatingPoint,
3025 DcPfInstance,
3026 AcPfInstance,
3027 DcOpfInstance,
3028 AcOpfInstance,
3029 AcScucInstance,
3030 DcPfSolution,
3031 AcPfSolution,
3032 DcOpfSolution,
3033 AcOpfSolution,
3034 SocwrOpfSolution,
3035 AcScucSolution,
3036}
3037
3038struct BalancedNetworkInner {
3039 value: ValueInner,
3040 projection: BalancedNetworkProjection,
3041}
3042
3043impl BalancedNetworkInner {
3044 fn network(&self) -> Option<&BalancedNetwork> {
3045 match (self.projection, self.value.value()?) {
3046 (BalancedNetworkProjection::Direct, PioValue::BalancedNetwork(network)) => {
3047 Some(network)
3048 }
3049 (
3050 BalancedNetworkProjection::OperatingPoint,
3051 PioValue::BalancedOperatingPoint(point),
3052 ) => Some(point.network()),
3053 (BalancedNetworkProjection::DcPfInstance, PioValue::DcPfInstance(instance)) => {
3054 Some(instance.network())
3055 }
3056 (BalancedNetworkProjection::AcPfInstance, PioValue::AcPfInstance(instance)) => {
3057 Some(instance.network())
3058 }
3059 (BalancedNetworkProjection::DcOpfInstance, PioValue::DcOpfInstance(instance)) => {
3060 Some(instance.network())
3061 }
3062 (BalancedNetworkProjection::AcOpfInstance, PioValue::AcOpfInstance(instance)) => {
3063 Some(instance.network())
3064 }
3065 (BalancedNetworkProjection::AcScucInstance, PioValue::AcScucInstance(instance)) => {
3066 Some(instance.network())
3067 }
3068 (BalancedNetworkProjection::DcPfSolution, PioValue::DcPfSolution(solution)) => {
3069 Some(solution.network())
3070 }
3071 (BalancedNetworkProjection::AcPfSolution, PioValue::AcPfSolution(solution)) => {
3072 Some(solution.network())
3073 }
3074 (BalancedNetworkProjection::DcOpfSolution, PioValue::DcOpfSolution(solution)) => {
3075 Some(solution.network())
3076 }
3077 (BalancedNetworkProjection::AcOpfSolution, PioValue::AcOpfSolution(solution)) => {
3078 Some(solution.network())
3079 }
3080 (BalancedNetworkProjection::SocwrOpfSolution, PioValue::SocwrOpfSolution(solution)) => {
3081 Some(solution.network())
3082 }
3083 (BalancedNetworkProjection::AcScucSolution, PioValue::AcScucSolution(solution)) => {
3084 Some(solution.instance().network())
3085 }
3086 _ => None,
3087 }
3088 }
3089}
3090
3091#[derive(Clone, Copy)]
3092enum MulticonductorNetworkProjection {
3093 Direct,
3094 OperatingPoint,
3095 McAcPfInstance,
3096 McAcOpfInstance,
3097 McAcPfSolution,
3098 McAcOpfSolution,
3099}
3100
3101struct MulticonductorNetworkInner {
3102 value: ValueInner,
3103 projection: MulticonductorNetworkProjection,
3104}
3105
3106impl MulticonductorNetworkInner {
3107 fn network(&self) -> Option<&powerio::MulticonductorNetwork> {
3108 match (self.projection, self.value.value()?) {
3109 (MulticonductorNetworkProjection::Direct, PioValue::MulticonductorNetwork(network)) => {
3110 Some(network)
3111 }
3112 (
3113 MulticonductorNetworkProjection::OperatingPoint,
3114 PioValue::MulticonductorOperatingPoint(point),
3115 ) => Some(point.network()),
3116 (
3117 MulticonductorNetworkProjection::McAcPfInstance,
3118 PioValue::McAcPfInstance(instance),
3119 ) => Some(instance.network()),
3120 (
3121 MulticonductorNetworkProjection::McAcOpfInstance,
3122 PioValue::McAcOpfInstance(instance),
3123 ) => Some(instance.network()),
3124 (
3125 MulticonductorNetworkProjection::McAcPfSolution,
3126 PioValue::McAcPfSolution(solution),
3127 ) => Some(solution.network()),
3128 (
3129 MulticonductorNetworkProjection::McAcOpfSolution,
3130 PioValue::McAcOpfSolution(solution),
3131 ) => Some(solution.network()),
3132 _ => None,
3133 }
3134 }
3135}
3136
3137#[derive(Clone, Copy)]
3138enum OperatingPointProjection {
3139 DirectBalanced,
3140 DirectMulticonductor,
3141 DcPfInitial,
3142 AcPfInitial,
3143 DcOpfInitial,
3144 AcOpfInitial,
3145 McAcPfInitial,
3146 McAcOpfInitial,
3147 DcPfSolutionInitial,
3148 AcPfSolutionInitial,
3149 DcOpfSolutionInitial,
3150 AcOpfSolutionInitial,
3151 SocwrOpfSolutionInitial,
3152 McAcPfSolutionInitial,
3153 McAcOpfSolutionInitial,
3154}
3155
3156struct OperatingPointInner {
3157 value: ValueInner,
3158 projection: OperatingPointProjection,
3159}
3160
3161impl OperatingPointInner {
3162 fn balanced(&self) -> Option<&powerio_prob::OperatingPoint<BalancedNetwork>> {
3163 match (self.projection, self.value.value()?) {
3164 (OperatingPointProjection::DirectBalanced, PioValue::BalancedOperatingPoint(point)) => {
3165 Some(point)
3166 }
3167 (OperatingPointProjection::DcPfInitial, PioValue::DcPfInstance(instance)) => {
3168 instance.initial_point()
3169 }
3170 (OperatingPointProjection::AcPfInitial, PioValue::AcPfInstance(instance)) => {
3171 instance.initial_point()
3172 }
3173 (OperatingPointProjection::DcOpfInitial, PioValue::DcOpfInstance(instance)) => {
3174 instance.initial_point()
3175 }
3176 (OperatingPointProjection::AcOpfInitial, PioValue::AcOpfInstance(instance)) => {
3177 instance.initial_point()
3178 }
3179 (OperatingPointProjection::DcPfSolutionInitial, PioValue::DcPfSolution(solution)) => {
3180 solution.instance().initial_point()
3181 }
3182 (OperatingPointProjection::AcPfSolutionInitial, PioValue::AcPfSolution(solution)) => {
3183 solution.instance().initial_point()
3184 }
3185 (OperatingPointProjection::DcOpfSolutionInitial, PioValue::DcOpfSolution(solution)) => {
3186 solution.instance().initial_point()
3187 }
3188 (OperatingPointProjection::AcOpfSolutionInitial, PioValue::AcOpfSolution(solution)) => {
3189 solution.instance().initial_point()
3190 }
3191 (
3192 OperatingPointProjection::SocwrOpfSolutionInitial,
3193 PioValue::SocwrOpfSolution(solution),
3194 ) => solution.instance().initial_point(),
3195 _ => None,
3196 }
3197 }
3198
3199 fn multiconductor(
3200 &self,
3201 ) -> Option<&powerio_prob::OperatingPoint<powerio::MulticonductorNetwork>> {
3202 match (self.projection, self.value.value()?) {
3203 (
3204 OperatingPointProjection::DirectMulticonductor,
3205 PioValue::MulticonductorOperatingPoint(point),
3206 ) => Some(point),
3207 (OperatingPointProjection::McAcPfInitial, PioValue::McAcPfInstance(instance)) => {
3208 instance.initial_point()
3209 }
3210 (OperatingPointProjection::McAcOpfInitial, PioValue::McAcOpfInstance(instance)) => {
3211 instance.initial_point()
3212 }
3213 (
3214 OperatingPointProjection::McAcPfSolutionInitial,
3215 PioValue::McAcPfSolution(solution),
3216 ) => solution.instance().initial_point(),
3217 (
3218 OperatingPointProjection::McAcOpfSolutionInitial,
3219 PioValue::McAcOpfSolution(solution),
3220 ) => solution.instance().initial_point(),
3221 _ => None,
3222 }
3223 }
3224
3225 fn type_name(&self) -> Option<&'static str> {
3226 if self.balanced().is_some() {
3227 Some("powerio.OperatingPoint<powerio.BalancedNetwork>")
3228 } else if self.multiconductor().is_some() {
3229 Some("powerio.OperatingPoint<powerio.MulticonductorNetwork>")
3230 } else {
3231 None
3232 }
3233 }
3234
3235 fn balanced_network_projection(&self) -> Option<BalancedNetworkProjection> {
3236 self.balanced()?;
3237 Some(match self.projection {
3238 OperatingPointProjection::DirectBalanced => BalancedNetworkProjection::OperatingPoint,
3239 OperatingPointProjection::DcPfInitial => BalancedNetworkProjection::DcPfInstance,
3240 OperatingPointProjection::AcPfInitial => BalancedNetworkProjection::AcPfInstance,
3241 OperatingPointProjection::DcOpfInitial => BalancedNetworkProjection::DcOpfInstance,
3242 OperatingPointProjection::AcOpfInitial => BalancedNetworkProjection::AcOpfInstance,
3243 OperatingPointProjection::DcPfSolutionInitial => {
3244 BalancedNetworkProjection::DcPfSolution
3245 }
3246 OperatingPointProjection::AcPfSolutionInitial => {
3247 BalancedNetworkProjection::AcPfSolution
3248 }
3249 OperatingPointProjection::DcOpfSolutionInitial => {
3250 BalancedNetworkProjection::DcOpfSolution
3251 }
3252 OperatingPointProjection::AcOpfSolutionInitial => {
3253 BalancedNetworkProjection::AcOpfSolution
3254 }
3255 OperatingPointProjection::SocwrOpfSolutionInitial => {
3256 BalancedNetworkProjection::SocwrOpfSolution
3257 }
3258 _ => return None,
3259 })
3260 }
3261
3262 fn multiconductor_network_projection(&self) -> Option<MulticonductorNetworkProjection> {
3263 self.multiconductor()?;
3264 Some(match self.projection {
3265 OperatingPointProjection::DirectMulticonductor => {
3266 MulticonductorNetworkProjection::OperatingPoint
3267 }
3268 OperatingPointProjection::McAcPfInitial => {
3269 MulticonductorNetworkProjection::McAcPfInstance
3270 }
3271 OperatingPointProjection::McAcOpfInitial => {
3272 MulticonductorNetworkProjection::McAcOpfInstance
3273 }
3274 OperatingPointProjection::McAcPfSolutionInitial => {
3275 MulticonductorNetworkProjection::McAcPfSolution
3276 }
3277 OperatingPointProjection::McAcOpfSolutionInitial => {
3278 MulticonductorNetworkProjection::McAcOpfSolution
3279 }
3280 _ => return None,
3281 })
3282 }
3283}
3284
3285#[derive(Clone, Copy)]
3286enum CalculationInstanceProjection {
3287 Direct,
3288 DcPfSolution,
3289 AcPfSolution,
3290 DcOpfSolution,
3291 AcOpfSolution,
3292 SocwrOpfSolution,
3293 McAcPfSolution,
3294 McAcOpfSolution,
3295 AcScucSolution,
3296}
3297
3298#[derive(Clone, Copy)]
3299enum CalculationInstanceRef<'a> {
3300 DcPf(&'a powerio_prob::DcPfInstance),
3301 AcPf(&'a powerio_prob::AcPfInstance),
3302 DcOpf(&'a powerio_prob::DcOpfInstance),
3303 AcOpf(&'a powerio_prob::AcOpfInstance),
3304 McAcPf(&'a powerio_prob::McAcPfInstance),
3305 McAcOpf(&'a powerio_prob::McAcOpfInstance),
3306 AcScuc(&'a powerio_prob::AcScucInstance),
3307}
3308
3309impl CalculationInstanceRef<'_> {
3310 fn type_name(self) -> &'static str {
3311 match self {
3312 Self::DcPf(_) => "powerio.DcPfInstance",
3313 Self::AcPf(_) => "powerio.AcPfInstance",
3314 Self::DcOpf(_) => "powerio.DcOpfInstance",
3315 Self::AcOpf(_) => "powerio.AcOpfInstance",
3316 Self::McAcPf(_) => "powerio.McAcPfInstance",
3317 Self::McAcOpf(_) => "powerio.McAcOpfInstance",
3318 Self::AcScuc(_) => "powerio.AcScucInstance",
3319 }
3320 }
3321}
3322
3323struct CalculationInstanceInner {
3324 value: ValueInner,
3325 projection: CalculationInstanceProjection,
3326}
3327
3328impl CalculationInstanceInner {
3329 fn instance(&self) -> Option<CalculationInstanceRef<'_>> {
3330 match (self.projection, self.value.value()?) {
3331 (CalculationInstanceProjection::Direct, PioValue::DcPfInstance(instance)) => {
3332 Some(CalculationInstanceRef::DcPf(instance))
3333 }
3334 (CalculationInstanceProjection::Direct, PioValue::AcPfInstance(instance)) => {
3335 Some(CalculationInstanceRef::AcPf(instance))
3336 }
3337 (CalculationInstanceProjection::Direct, PioValue::DcOpfInstance(instance)) => {
3338 Some(CalculationInstanceRef::DcOpf(instance))
3339 }
3340 (CalculationInstanceProjection::Direct, PioValue::AcOpfInstance(instance)) => {
3341 Some(CalculationInstanceRef::AcOpf(instance))
3342 }
3343 (CalculationInstanceProjection::Direct, PioValue::McAcPfInstance(instance)) => {
3344 Some(CalculationInstanceRef::McAcPf(instance))
3345 }
3346 (CalculationInstanceProjection::Direct, PioValue::McAcOpfInstance(instance)) => {
3347 Some(CalculationInstanceRef::McAcOpf(instance))
3348 }
3349 (CalculationInstanceProjection::Direct, PioValue::AcScucInstance(instance)) => {
3350 Some(CalculationInstanceRef::AcScuc(instance))
3351 }
3352 (CalculationInstanceProjection::DcPfSolution, PioValue::DcPfSolution(solution)) => {
3353 Some(CalculationInstanceRef::DcPf(solution.instance()))
3354 }
3355 (CalculationInstanceProjection::AcPfSolution, PioValue::AcPfSolution(solution)) => {
3356 Some(CalculationInstanceRef::AcPf(solution.instance()))
3357 }
3358 (CalculationInstanceProjection::DcOpfSolution, PioValue::DcOpfSolution(solution)) => {
3359 Some(CalculationInstanceRef::DcOpf(solution.instance()))
3360 }
3361 (CalculationInstanceProjection::AcOpfSolution, PioValue::AcOpfSolution(solution)) => {
3362 Some(CalculationInstanceRef::AcOpf(solution.instance()))
3363 }
3364 (
3365 CalculationInstanceProjection::SocwrOpfSolution,
3366 PioValue::SocwrOpfSolution(solution),
3367 ) => Some(CalculationInstanceRef::AcOpf(solution.instance())),
3368 (CalculationInstanceProjection::McAcPfSolution, PioValue::McAcPfSolution(solution)) => {
3369 Some(CalculationInstanceRef::McAcPf(solution.instance()))
3370 }
3371 (
3372 CalculationInstanceProjection::McAcOpfSolution,
3373 PioValue::McAcOpfSolution(solution),
3374 ) => Some(CalculationInstanceRef::McAcOpf(solution.instance())),
3375 (CalculationInstanceProjection::AcScucSolution, PioValue::AcScucSolution(solution)) => {
3376 Some(CalculationInstanceRef::AcScuc(solution.instance()))
3377 }
3378 _ => None,
3379 }
3380 }
3381
3382 fn type_name(&self) -> Option<&'static str> {
3383 self.instance().map(CalculationInstanceRef::type_name)
3384 }
3385
3386 fn dc_pf(&self) -> Option<&powerio_prob::DcPfInstance> {
3387 match self.instance()? {
3388 CalculationInstanceRef::DcPf(instance) => Some(instance),
3389 _ => None,
3390 }
3391 }
3392
3393 fn ac_pf(&self) -> Option<&powerio_prob::AcPfInstance> {
3394 match self.instance()? {
3395 CalculationInstanceRef::AcPf(instance) => Some(instance),
3396 _ => None,
3397 }
3398 }
3399
3400 fn dc_opf(&self) -> Option<&powerio_prob::DcOpfInstance> {
3401 match self.instance()? {
3402 CalculationInstanceRef::DcOpf(instance) => Some(instance),
3403 _ => None,
3404 }
3405 }
3406
3407 fn ac_opf(&self) -> Option<&powerio_prob::AcOpfInstance> {
3408 match self.instance()? {
3409 CalculationInstanceRef::AcOpf(instance) => Some(instance),
3410 _ => None,
3411 }
3412 }
3413
3414 fn mc_ac_pf(&self) -> Option<&powerio_prob::McAcPfInstance> {
3415 match self.instance()? {
3416 CalculationInstanceRef::McAcPf(instance) => Some(instance),
3417 _ => None,
3418 }
3419 }
3420
3421 fn ac_scuc(&self) -> Option<&powerio_prob::AcScucInstance> {
3422 match self.instance()? {
3423 CalculationInstanceRef::AcScuc(instance) => Some(instance),
3424 _ => None,
3425 }
3426 }
3427
3428 fn has_initial_point(&self) -> bool {
3429 match self.instance() {
3430 Some(CalculationInstanceRef::DcPf(instance)) => instance.initial_point().is_some(),
3431 Some(CalculationInstanceRef::AcPf(instance)) => instance.initial_point().is_some(),
3432 Some(CalculationInstanceRef::DcOpf(instance)) => instance.initial_point().is_some(),
3433 Some(CalculationInstanceRef::AcOpf(instance)) => instance.initial_point().is_some(),
3434 Some(CalculationInstanceRef::McAcPf(instance)) => instance.initial_point().is_some(),
3435 Some(CalculationInstanceRef::McAcOpf(instance)) => instance.initial_point().is_some(),
3436 _ => false,
3437 }
3438 }
3439}
3440
3441opaque_handle!(
3442 PioBalancedNetwork,
3444 BalancedNetworkInner
3445);
3446
3447struct DetailedConnectivityInner {
3448 owner: Arc<BalancedNetworkInner>,
3449}
3450
3451impl DetailedConnectivityInner {
3452 fn details(&self) -> Option<&powerio_tx::DetailedConnectivity> {
3453 self.owner.network()?.detailed_connectivity().as_deref()
3454 }
3455}
3456
3457opaque_handle!(
3458 PioDetailedConnectivity,
3460 DetailedConnectivityInner
3461);
3462opaque_handle!(
3463 PioMulticonductorNetwork,
3465 MulticonductorNetworkInner
3466);
3467opaque_handle!(
3468 PioTimeSeriesHandle,
3470 ValueInner
3471);
3472opaque_handle!(
3473 PioScenarioSetHandle,
3475 ValueInner
3476);
3477opaque_handle!(
3478 PioOperatingPoint,
3480 OperatingPointInner
3481);
3482opaque_handle!(
3483 PioCalculationInstance,
3485 CalculationInstanceInner
3486);
3487opaque_handle!(
3488 PioDcOpfPreparation,
3490 DcOpfPreparation
3491);
3492opaque_handle!(
3493 PioAcOpfPreparation,
3495 AcOpfPreparation
3496);
3497opaque_handle!(
3498 PioCalculationSolution,
3500 ValueInner
3501);
3502
3503fn module_handle(module: powerio::PioModule<PioValue>) -> *mut PioModule {
3504 PioModule::new_raw(ModuleInner { module })
3505}
3506
3507fn value_handle(value: ValueInner) -> *mut PioValueHandle {
3508 PioValueHandle::new_raw(value)
3509}
3510
3511unsafe fn require_value<'a>(value: *const PioValueHandle) -> Result<&'a ValueInner, *mut PioError> {
3512 unsafe { PioValueHandle::get(value) }.ok_or_else(|| {
3513 boundary_error(
3514 &codes::BIND_CAPI_NULL_HANDLE,
3515 "PioValueHandle must not be NULL",
3516 )
3517 })
3518}
3519
3520#[unsafe(no_mangle)]
3522pub unsafe extern "C" fn pio_parse(
3523 source: *const PioSource,
3524 format: *const c_char,
3525 format_len: usize,
3526 error: *mut *mut PioError,
3527) -> *mut PioModule {
3528 unsafe {
3529 entry(error, std::ptr::null_mut(), || {
3530 let source = PioSource::get(source).ok_or_else(|| {
3531 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioSource must not be NULL")
3532 })?;
3533 let format = optional_str(format, format_len, "format")?;
3534 let mut options = powerio::ParseOptions::default();
3535 if let Some(format) = format {
3536 options = options
3537 .format(format)
3538 .map_err(|failure| error_from_core(&failure))?;
3539 }
3540 powerio::parse_with_options(source.clone(), &options)
3541 .map(module_handle)
3542 .map_err(|failure| error_from_core(&failure))
3543 })
3544 }
3545}
3546
3547#[unsafe(no_mangle)]
3553pub unsafe extern "C" fn pio_module_deserialize(
3554 source: *const PioSource,
3555 error: *mut *mut PioError,
3556) -> *mut PioModule {
3557 unsafe {
3558 entry(error, std::ptr::null_mut(), || {
3559 let source = PioSource::get(source).ok_or_else(|| {
3560 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioSource must not be NULL")
3561 })?;
3562 powerio::deserialize(source.clone())
3563 .map(module_handle)
3564 .map_err(|failure| error_from_core(&failure))
3565 })
3566 }
3567}
3568
3569unsafe fn transform_module<T>(
3570 module: *const PioModule,
3571 error: *mut *mut PioError,
3572 transform: impl FnOnce(
3573 &powerio::PioModule<PioValue>,
3574 ) -> Result<powerio::PioModule<T>, powerio_core::Error>,
3575 wrap: impl FnOnce(T) -> PioValue,
3576) -> *mut PioModule {
3577 unsafe {
3578 entry(error, std::ptr::null_mut(), || {
3579 let module = PioModule::get(module).ok_or_else(|| {
3580 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
3581 })?;
3582 transform(&module.module)
3583 .map(|derived| module_handle(derived.map_value(wrap)))
3584 .map_err(|failure| error_from_core(&failure))
3585 })
3586 }
3587}
3588
3589#[unsafe(no_mangle)]
3591pub unsafe extern "C" fn pio_module_to_dc_pf_instance(
3592 module: *const PioModule,
3593 error: *mut *mut PioError,
3594) -> *mut PioModule {
3595 unsafe {
3596 transform_module(
3597 module,
3598 error,
3599 powerio::transform::to_dc_pf_instance,
3600 PioValue::DcPfInstance,
3601 )
3602 }
3603}
3604
3605#[unsafe(no_mangle)]
3607pub unsafe extern "C" fn pio_module_to_ac_pf_instance(
3608 module: *const PioModule,
3609 error: *mut *mut PioError,
3610) -> *mut PioModule {
3611 unsafe {
3612 transform_module(
3613 module,
3614 error,
3615 powerio::transform::to_ac_pf_instance,
3616 PioValue::AcPfInstance,
3617 )
3618 }
3619}
3620
3621#[unsafe(no_mangle)]
3623pub unsafe extern "C" fn pio_module_to_dc_opf_instance(
3624 module: *const PioModule,
3625 error: *mut *mut PioError,
3626) -> *mut PioModule {
3627 unsafe {
3628 transform_module(
3629 module,
3630 error,
3631 powerio::transform::to_dc_opf_instance,
3632 PioValue::DcOpfInstance,
3633 )
3634 }
3635}
3636
3637#[unsafe(no_mangle)]
3639pub unsafe extern "C" fn pio_module_to_ac_opf_instance(
3640 module: *const PioModule,
3641 error: *mut *mut PioError,
3642) -> *mut PioModule {
3643 unsafe {
3644 transform_module(
3645 module,
3646 error,
3647 powerio::transform::to_ac_opf_instance,
3648 PioValue::AcOpfInstance,
3649 )
3650 }
3651}
3652
3653#[unsafe(no_mangle)]
3656pub unsafe extern "C" fn pio_module_to_mc_ac_pf_instance(
3657 module: *const PioModule,
3658 error: *mut *mut PioError,
3659) -> *mut PioModule {
3660 unsafe {
3661 transform_module(
3662 module,
3663 error,
3664 powerio::transform::to_mc_ac_pf_instance,
3665 PioValue::McAcPfInstance,
3666 )
3667 }
3668}
3669
3670#[unsafe(no_mangle)]
3673pub unsafe extern "C" fn pio_module_to_mc_ac_opf_instance(
3674 module: *const PioModule,
3675 error: *mut *mut PioError,
3676) -> *mut PioModule {
3677 unsafe {
3678 transform_module(
3679 module,
3680 error,
3681 powerio::transform::to_mc_ac_opf_instance,
3682 PioValue::McAcOpfInstance,
3683 )
3684 }
3685}
3686
3687#[unsafe(no_mangle)]
3691pub unsafe extern "C" fn pio_module_apply_geo_layer(
3692 module: *const PioModule,
3693 layer: *const PioGeoLayer,
3694 out_report: *mut *mut PioGeoApplyReport,
3695 error: *mut *mut PioError,
3696) -> *mut PioModule {
3697 unsafe {
3698 if !out_report.is_null() {
3699 *out_report = std::ptr::null_mut();
3700 }
3701 entry(error, std::ptr::null_mut(), || {
3702 let module = PioModule::get(module).ok_or_else(|| {
3703 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
3704 })?;
3705 let layer = PioGeoLayer::get(layer).ok_or_else(|| {
3706 boundary_error(
3707 &codes::BIND_CAPI_NULL_HANDLE,
3708 "PioGeoLayer must not be NULL",
3709 )
3710 })?;
3711 let (mut derived, report) = powerio::apply_geo_layer(&module.module, &layer.layer)
3712 .map_err(|failure| error_from_core(&failure))?;
3713 for diagnostic in &layer.diagnostics {
3714 derived
3715 .add_diagnostic(diagnostic.clone())
3716 .map_err(|failure| error_from_core(&failure))?;
3717 }
3718 if !out_report.is_null() {
3719 *out_report = PioGeoApplyReport::new_raw(report);
3720 }
3721 Ok(module_handle(derived))
3722 })
3723 }
3724}
3725
3726#[unsafe(no_mangle)]
3727pub unsafe extern "C" fn pio_geo_apply_report_matched_buses(
3728 report: *const PioGeoApplyReport,
3729) -> usize {
3730 unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.matched_buses)
3731}
3732
3733#[unsafe(no_mangle)]
3734pub unsafe extern "C" fn pio_geo_apply_report_matched_branches(
3735 report: *const PioGeoApplyReport,
3736) -> usize {
3737 unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.matched_branches)
3738}
3739
3740#[unsafe(no_mangle)]
3741pub unsafe extern "C" fn pio_geo_apply_report_unmatched_features(
3742 report: *const PioGeoApplyReport,
3743) -> usize {
3744 unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.unmatched_features)
3745}
3746
3747#[unsafe(no_mangle)]
3748pub unsafe extern "C" fn pio_geo_apply_report_unlocated_buses(
3749 report: *const PioGeoApplyReport,
3750) -> usize {
3751 unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.unlocated_buses)
3752}
3753
3754#[unsafe(no_mangle)]
3755pub unsafe extern "C" fn pio_geo_apply_report_unlocated_branches(
3756 report: *const PioGeoApplyReport,
3757) -> usize {
3758 unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.unlocated_branches)
3759}
3760
3761#[unsafe(no_mangle)]
3762pub unsafe extern "C" fn pio_geo_apply_report_note_count(
3763 report: *const PioGeoApplyReport,
3764) -> usize {
3765 unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.notes.len())
3766}
3767
3768#[unsafe(no_mangle)]
3769pub unsafe extern "C" fn pio_geo_apply_report_note_at(
3770 report: *const PioGeoApplyReport,
3771 index: usize,
3772 error: *mut *mut PioError,
3773) -> PioStringView {
3774 unsafe {
3775 entry(error, PioStringView::EMPTY, || {
3776 let report = PioGeoApplyReport::get(report).ok_or_else(|| {
3777 boundary_error(
3778 &codes::BIND_CAPI_NULL_HANDLE,
3779 "PioGeoApplyReport must not be NULL",
3780 )
3781 })?;
3782 let note = report.notes.get(index).ok_or_else(|| {
3783 boundary_error(
3784 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3785 format!("geo apply note index {index} is out of range"),
3786 )
3787 })?;
3788 Ok(PioStringView::new(note))
3789 })
3790 }
3791}
3792
3793#[unsafe(no_mangle)]
3794pub unsafe extern "C" fn pio_geo_apply_report_retain(
3795 report: *const PioGeoApplyReport,
3796) -> *mut PioGeoApplyReport {
3797 unsafe { PioGeoApplyReport::retain_raw(report) }
3798}
3799
3800#[unsafe(no_mangle)]
3801pub unsafe extern "C" fn pio_geo_apply_report_release(report: *mut PioGeoApplyReport) {
3802 unsafe { PioGeoApplyReport::release_raw(report) };
3803}
3804
3805#[unsafe(no_mangle)]
3807pub unsafe extern "C" fn pio_module_value(module: *const PioModule) -> *mut PioValueHandle {
3808 unsafe { PioModule::arc(module) }.map_or(std::ptr::null_mut(), |owner| {
3809 value_handle(ValueInner::root(owner))
3810 })
3811}
3812
3813#[unsafe(no_mangle)]
3815pub unsafe extern "C" fn pio_module_diagnostics(module: *const PioModule) -> *mut PioDiagnostics {
3816 unsafe { PioModule::arc(module) }.map_or(std::ptr::null_mut(), |module| {
3817 PioDiagnostics::new_raw(DiagnosticsInner {
3818 owner: DiagnosticsOwner::Module(module),
3819 })
3820 })
3821}
3822
3823fn source_relation_name(relation: powerio_core::SourceRelation) -> &'static str {
3824 match relation {
3825 powerio_core::SourceRelation::Exact => "exact",
3826 powerio_core::SourceRelation::Defaulted => "defaulted",
3827 powerio_core::SourceRelation::Inferred => "inferred",
3828 powerio_core::SourceRelation::ConvertedUnits => "converted_units",
3829 powerio_core::SourceRelation::Aggregated => "aggregated",
3830 powerio_core::SourceRelation::Split => "split",
3831 powerio_core::SourceRelation::Synthetic => "synthetic",
3832 powerio_core::SourceRelation::Transformed => "transformed",
3833 powerio_core::SourceRelation::RetainedExtra => "retained_extra",
3834 _ => "unknown",
3835 }
3836}
3837
3838fn history_kind_name(kind: HistoryKind) -> &'static str {
3839 match kind {
3840 HistoryKind::Parse => "parse",
3841 HistoryKind::Transform => "transform",
3842 HistoryKind::Edit => "edit",
3843 HistoryKind::Repair => "repair",
3844 HistoryKind::Solve => "solve",
3845 _ => "unknown",
3846 }
3847}
3848
3849fn json_value_kind(value: &serde_json::Value) -> &'static str {
3850 match value {
3851 serde_json::Value::Null => "null",
3852 serde_json::Value::Bool(_) => "boolean",
3853 serde_json::Value::Number(_) => "number",
3854 serde_json::Value::String(_) => "string",
3855 serde_json::Value::Array(_) => "array",
3856 serde_json::Value::Object(_) => "object",
3857 }
3858}
3859
3860#[unsafe(no_mangle)]
3862pub unsafe extern "C" fn pio_module_producer(
3863 module: *const PioModule,
3864 output: *mut PioModuleProducerView,
3865 error: *mut *mut PioError,
3866) -> bool {
3867 unsafe {
3868 entry(error, false, || {
3869 let module = PioModule::get(module).ok_or_else(|| {
3870 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
3871 })?;
3872 let producer = module.module.producer();
3873 *require_output(output, "output")? = PioModuleProducerView {
3874 name: PioStringView::new(producer.name()),
3875 version: PioStringView::new(producer.version()),
3876 };
3877 Ok(true)
3878 })
3879 }
3880}
3881
3882#[unsafe(no_mangle)]
3883pub unsafe extern "C" fn pio_module_source_count(module: *const PioModule) -> usize {
3884 unsafe { PioModule::get(module) }.map_or(0, |module| module.module.sources().len())
3885}
3886
3887#[unsafe(no_mangle)]
3889pub unsafe extern "C" fn pio_module_source_at(
3890 module: *const PioModule,
3891 index: usize,
3892 output: *mut PioModuleSourceView,
3893 error: *mut *mut PioError,
3894) -> bool {
3895 unsafe {
3896 entry(error, false, || {
3897 let module = PioModule::get(module).ok_or_else(|| {
3898 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
3899 })?;
3900 let source = module.module.sources().get(index).ok_or_else(|| {
3901 boundary_error(
3902 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3903 format!("module source index {index} is out of range"),
3904 )
3905 })?;
3906 let (format, has_format) = source
3907 .format()
3908 .map_or((PioStringView::EMPTY, false), |format| {
3909 (PioStringView::new(format.as_str()), true)
3910 });
3911 let digest = source.digest();
3912 *require_output(output, "output")? = PioModuleSourceView {
3913 id: PioStringView::new(source.id().as_str()),
3914 name: PioStringView::new(source.name()),
3915 byte_length: source.byte_length(),
3916 format,
3917 has_format,
3918 digest_algorithm: digest.map_or(PioStringView::EMPTY, |digest| {
3919 PioStringView::new(digest.algorithm().as_str())
3920 }),
3921 digest: digest.map_or(PioStringView::EMPTY, |digest| {
3922 PioStringView::new(digest.value())
3923 }),
3924 has_digest: digest.is_some(),
3925 };
3926 Ok(true)
3927 })
3928 }
3929}
3930
3931#[unsafe(no_mangle)]
3932pub unsafe extern "C" fn pio_module_source_map_count(module: *const PioModule) -> usize {
3933 unsafe { PioModule::get(module) }.map_or(0, |module| module.module.source_map().len())
3934}
3935
3936#[unsafe(no_mangle)]
3938pub unsafe extern "C" fn pio_module_source_map_at(
3939 module: *const PioModule,
3940 index: usize,
3941 output: *mut PioModuleSourceMapEntryView,
3942 error: *mut *mut PioError,
3943) -> bool {
3944 unsafe {
3945 entry(error, false, || {
3946 let module = PioModule::get(module).ok_or_else(|| {
3947 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
3948 })?;
3949 let source_map = module.module.source_map().get(index).ok_or_else(|| {
3950 boundary_error(
3951 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3952 format!("module source map index {index} is out of range"),
3953 )
3954 })?;
3955 *require_output(output, "output")? = PioModuleSourceMapEntryView {
3956 target: PioStringView::new(source_map.target()),
3957 relation: PioStringView::new(source_relation_name(source_map.relation())),
3958 span_count: source_map.spans().len(),
3959 };
3960 Ok(true)
3961 })
3962 }
3963}
3964
3965#[unsafe(no_mangle)]
3967pub unsafe extern "C" fn pio_module_source_map_span_at(
3968 module: *const PioModule,
3969 entry_index: usize,
3970 span_index: usize,
3971 output: *mut PioSourceSpanView,
3972 error: *mut *mut PioError,
3973) -> bool {
3974 unsafe {
3975 entry(error, false, || {
3976 let module = PioModule::get(module).ok_or_else(|| {
3977 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
3978 })?;
3979 let source_map = module.module.source_map().get(entry_index).ok_or_else(|| {
3980 boundary_error(
3981 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3982 format!("module source map index {entry_index} is out of range"),
3983 )
3984 })?;
3985 let span = source_map.spans().get(span_index).ok_or_else(|| {
3986 boundary_error(
3987 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3988 format!(
3989 "source map span index {span_index} is out of range for entry {entry_index}"
3990 ),
3991 )
3992 })?;
3993 *require_output(output, "output")? = PioSourceSpanView {
3994 source: PioStringView::new(span.source().as_str()),
3995 byte_start: span.byte_start(),
3996 byte_end: span.byte_end(),
3997 };
3998 Ok(true)
3999 })
4000 }
4001}
4002
4003#[unsafe(no_mangle)]
4004pub unsafe extern "C" fn pio_module_history_count(module: *const PioModule) -> usize {
4005 unsafe { PioModule::get(module) }.map_or(0, |module| module.module.history().len())
4006}
4007
4008#[unsafe(no_mangle)]
4010pub unsafe extern "C" fn pio_module_history_at(
4011 module: *const PioModule,
4012 index: usize,
4013 output: *mut PioModuleHistoryEntryView,
4014 error: *mut *mut PioError,
4015) -> bool {
4016 unsafe {
4017 entry(error, false, || {
4018 let module = PioModule::get(module).ok_or_else(|| {
4019 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4020 })?;
4021 let history = module.module.history().get(index).ok_or_else(|| {
4022 boundary_error(
4023 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4024 format!("module history index {index} is out of range"),
4025 )
4026 })?;
4027 let (input_type, has_input_type) = optional_string_view(history.input_type());
4028 let (output_type, has_output_type) = optional_string_view(history.output_type());
4029 *require_output(output, "output")? = PioModuleHistoryEntryView {
4030 id: PioStringView::new(history.id().as_str()),
4031 kind: PioStringView::new(history_kind_name(history.kind())),
4032 name: PioStringView::new(history.name()),
4033 input_type,
4034 has_input_type,
4035 output_type,
4036 has_output_type,
4037 parameter_count: history.parameters().len(),
4038 assumption_count: history.assumptions().len(),
4039 loss_count: history.losses().len(),
4040 };
4041 Ok(true)
4042 })
4043 }
4044}
4045
4046#[unsafe(no_mangle)]
4048pub unsafe extern "C" fn pio_module_history_parameter_at(
4049 module: *const PioModule,
4050 history_index: usize,
4051 parameter_index: usize,
4052 output: *mut PioModuleHistoryParameterView,
4053 error: *mut *mut PioError,
4054) -> bool {
4055 unsafe {
4056 entry(error, false, || {
4057 let module = PioModule::get(module).ok_or_else(|| {
4058 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4059 })?;
4060 let history = module.module.history().get(history_index).ok_or_else(|| {
4061 boundary_error(
4062 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4063 format!("module history index {history_index} is out of range"),
4064 )
4065 })?;
4066 let (name, value) = history
4067 .parameters()
4068 .iter()
4069 .nth(parameter_index)
4070 .ok_or_else(|| {
4071 boundary_error(
4072 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4073 format!(
4074 "history parameter index {parameter_index} is out of range for entry {history_index}"
4075 ),
4076 )
4077 })?;
4078 *require_output(output, "output")? = PioModuleHistoryParameterView {
4079 name: PioStringView::new(name),
4080 value_kind: PioStringView::new(json_value_kind(value)),
4081 };
4082 Ok(true)
4083 })
4084 }
4085}
4086
4087#[unsafe(no_mangle)]
4089pub unsafe extern "C" fn pio_module_history_parameter_value_at(
4090 module: *const PioModule,
4091 history_index: usize,
4092 parameter_index: usize,
4093 error: *mut *mut PioError,
4094) -> *mut PioJsonValue {
4095 unsafe {
4096 entry(error, std::ptr::null_mut(), || {
4097 let owner = PioModule::arc(module).ok_or_else(|| {
4098 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4099 })?;
4100 let history = owner.module.history().get(history_index).ok_or_else(|| {
4101 boundary_error(
4102 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4103 format!("module history index {history_index} is out of range"),
4104 )
4105 })?;
4106 if history.parameters().values().nth(parameter_index).is_none() {
4107 return Err(boundary_error(
4108 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4109 format!(
4110 "history parameter index {parameter_index} is out of range for entry {history_index}"
4111 ),
4112 ));
4113 }
4114 Ok(PioJsonValue::new_raw(JsonValueInner {
4115 owner,
4116 root: ModuleJsonRoot::HistoryParameter {
4117 history_index,
4118 parameter_index,
4119 },
4120 steps: Vec::new(),
4121 }))
4122 })
4123 }
4124}
4125
4126#[unsafe(no_mangle)]
4128pub unsafe extern "C" fn pio_module_history_assumption_at(
4129 module: *const PioModule,
4130 history_index: usize,
4131 assumption_index: usize,
4132 error: *mut *mut PioError,
4133) -> PioStringView {
4134 unsafe {
4135 entry(error, PioStringView::EMPTY, || {
4136 let module = PioModule::get(module).ok_or_else(|| {
4137 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4138 })?;
4139 let history = module.module.history().get(history_index).ok_or_else(|| {
4140 boundary_error(
4141 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4142 format!("module history index {history_index} is out of range"),
4143 )
4144 })?;
4145 history
4146 .assumptions()
4147 .get(assumption_index)
4148 .map(|value| PioStringView::new(value))
4149 .ok_or_else(|| {
4150 boundary_error(
4151 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4152 format!(
4153 "history assumption index {assumption_index} is out of range for entry {history_index}"
4154 ),
4155 )
4156 })
4157 })
4158 }
4159}
4160
4161#[unsafe(no_mangle)]
4163pub unsafe extern "C" fn pio_module_history_loss_at(
4164 module: *const PioModule,
4165 history_index: usize,
4166 loss_index: usize,
4167 error: *mut *mut PioError,
4168) -> PioStringView {
4169 unsafe {
4170 entry(error, PioStringView::EMPTY, || {
4171 let module = PioModule::get(module).ok_or_else(|| {
4172 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4173 })?;
4174 let history = module.module.history().get(history_index).ok_or_else(|| {
4175 boundary_error(
4176 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4177 format!("module history index {history_index} is out of range"),
4178 )
4179 })?;
4180 history
4181 .losses()
4182 .get(loss_index)
4183 .map(|value| PioStringView::new(value))
4184 .ok_or_else(|| {
4185 boundary_error(
4186 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4187 format!(
4188 "history loss index {loss_index} is out of range for entry {history_index}"
4189 ),
4190 )
4191 })
4192 })
4193 }
4194}
4195
4196#[unsafe(no_mangle)]
4197pub unsafe extern "C" fn pio_module_extension_count(module: *const PioModule) -> usize {
4198 unsafe { PioModule::get(module) }.map_or(0, |module| module.module.extensions().len())
4199}
4200
4201#[unsafe(no_mangle)]
4203pub unsafe extern "C" fn pio_module_extension_at(
4204 module: *const PioModule,
4205 index: usize,
4206 output: *mut PioModuleExtensionView,
4207 error: *mut *mut PioError,
4208) -> bool {
4209 unsafe {
4210 entry(error, false, || {
4211 let module = PioModule::get(module).ok_or_else(|| {
4212 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4213 })?;
4214 let (namespace, value) =
4215 module
4216 .module
4217 .extensions()
4218 .iter()
4219 .nth(index)
4220 .ok_or_else(|| {
4221 boundary_error(
4222 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4223 format!("module extension index {index} is out of range"),
4224 )
4225 })?;
4226 *require_output(output, "output")? = PioModuleExtensionView {
4227 namespace: PioStringView::new(namespace),
4228 value_kind: PioStringView::new(json_value_kind(value)),
4229 };
4230 Ok(true)
4231 })
4232 }
4233}
4234
4235#[unsafe(no_mangle)]
4237pub unsafe extern "C" fn pio_module_extension_value_at(
4238 module: *const PioModule,
4239 index: usize,
4240 error: *mut *mut PioError,
4241) -> *mut PioJsonValue {
4242 unsafe {
4243 entry(error, std::ptr::null_mut(), || {
4244 let owner = PioModule::arc(module).ok_or_else(|| {
4245 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4246 })?;
4247 if owner.module.extensions().values().nth(index).is_none() {
4248 return Err(boundary_error(
4249 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4250 format!("module extension index {index} is out of range"),
4251 ));
4252 }
4253 Ok(PioJsonValue::new_raw(JsonValueInner {
4254 owner,
4255 root: ModuleJsonRoot::Extension(index),
4256 steps: Vec::new(),
4257 }))
4258 })
4259 }
4260}
4261
4262fn json_value_view(value: &serde_json::Value) -> PioJsonValueView {
4263 let mut view = PioJsonValueView {
4264 kind: PioStringView::new(json_value_kind(value)),
4265 boolean_value: false,
4266 number_kind: PioStringView::EMPTY,
4267 signed_integer_value: 0,
4268 unsigned_integer_value: 0,
4269 floating_point_value: 0.0,
4270 string_value: PioStringView::EMPTY,
4271 element_count: 0,
4272 };
4273 match value {
4274 serde_json::Value::Bool(value) => view.boolean_value = *value,
4275 serde_json::Value::Number(value) if value.is_i64() => {
4276 view.number_kind = PioStringView::new("signed_integer");
4277 view.signed_integer_value = value.as_i64().unwrap_or_default();
4278 }
4279 serde_json::Value::Number(value) if value.is_u64() => {
4280 view.number_kind = PioStringView::new("unsigned_integer");
4281 view.unsigned_integer_value = value.as_u64().unwrap_or_default();
4282 }
4283 serde_json::Value::Number(value) => {
4284 view.number_kind = PioStringView::new("floating_point");
4285 view.floating_point_value = value.as_f64().unwrap_or(f64::NAN);
4286 }
4287 serde_json::Value::String(value) => view.string_value = PioStringView::new(value),
4288 serde_json::Value::Array(values) => view.element_count = values.len(),
4289 serde_json::Value::Object(values) => view.element_count = values.len(),
4290 serde_json::Value::Null => {}
4291 }
4292 view
4293}
4294
4295#[unsafe(no_mangle)]
4297pub unsafe extern "C" fn pio_json_value_get(
4298 value: *const PioJsonValue,
4299 output: *mut PioJsonValueView,
4300 error: *mut *mut PioError,
4301) -> bool {
4302 unsafe {
4303 entry(error, false, || {
4304 let value = PioJsonValue::get(value).ok_or_else(|| {
4305 boundary_error(
4306 &codes::BIND_CAPI_NULL_HANDLE,
4307 "PioJsonValue must not be NULL",
4308 )
4309 })?;
4310 let value = value.value().ok_or_else(|| {
4311 boundary_error(
4312 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4313 "the structured value path is out of range",
4314 )
4315 })?;
4316 *require_output(output, "output")? = json_value_view(value);
4317 Ok(true)
4318 })
4319 }
4320}
4321
4322#[unsafe(no_mangle)]
4324pub unsafe extern "C" fn pio_json_value_array_at(
4325 value: *const PioJsonValue,
4326 index: usize,
4327 error: *mut *mut PioError,
4328) -> *mut PioJsonValue {
4329 unsafe {
4330 entry(error, std::ptr::null_mut(), || {
4331 let value = PioJsonValue::get(value).ok_or_else(|| {
4332 boundary_error(
4333 &codes::BIND_CAPI_NULL_HANDLE,
4334 "PioJsonValue must not be NULL",
4335 )
4336 })?;
4337 let values = value
4338 .value()
4339 .and_then(serde_json::Value::as_array)
4340 .ok_or_else(|| {
4341 boundary_error(
4342 &codes::REQUEST_CAPI_TYPE_MISMATCH,
4343 "the structured value is not an array",
4344 )
4345 })?;
4346 if values.get(index).is_none() {
4347 return Err(boundary_error(
4348 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4349 format!("structured array index {index} is out of range"),
4350 ));
4351 }
4352 Ok(PioJsonValue::new_raw(
4353 value.child(JsonValueStep::Array(index)),
4354 ))
4355 })
4356 }
4357}
4358
4359#[unsafe(no_mangle)]
4361pub unsafe extern "C" fn pio_json_value_object_entry_at(
4362 value: *const PioJsonValue,
4363 index: usize,
4364 output: *mut PioJsonObjectEntryView,
4365 error: *mut *mut PioError,
4366) -> bool {
4367 unsafe {
4368 entry(error, false, || {
4369 let value = PioJsonValue::get(value).ok_or_else(|| {
4370 boundary_error(
4371 &codes::BIND_CAPI_NULL_HANDLE,
4372 "PioJsonValue must not be NULL",
4373 )
4374 })?;
4375 let values = value
4376 .value()
4377 .and_then(serde_json::Value::as_object)
4378 .ok_or_else(|| {
4379 boundary_error(
4380 &codes::REQUEST_CAPI_TYPE_MISMATCH,
4381 "the structured value is not an object",
4382 )
4383 })?;
4384 let (key, value) = values.iter().nth(index).ok_or_else(|| {
4385 boundary_error(
4386 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4387 format!("structured object index {index} is out of range"),
4388 )
4389 })?;
4390 *require_output(output, "output")? = PioJsonObjectEntryView {
4391 key: PioStringView::new(key),
4392 value_kind: PioStringView::new(json_value_kind(value)),
4393 };
4394 Ok(true)
4395 })
4396 }
4397}
4398
4399#[unsafe(no_mangle)]
4401pub unsafe extern "C" fn pio_json_value_object_value_at(
4402 value: *const PioJsonValue,
4403 index: usize,
4404 error: *mut *mut PioError,
4405) -> *mut PioJsonValue {
4406 unsafe {
4407 entry(error, std::ptr::null_mut(), || {
4408 let value = PioJsonValue::get(value).ok_or_else(|| {
4409 boundary_error(
4410 &codes::BIND_CAPI_NULL_HANDLE,
4411 "PioJsonValue must not be NULL",
4412 )
4413 })?;
4414 let values = value
4415 .value()
4416 .and_then(serde_json::Value::as_object)
4417 .ok_or_else(|| {
4418 boundary_error(
4419 &codes::REQUEST_CAPI_TYPE_MISMATCH,
4420 "the structured value is not an object",
4421 )
4422 })?;
4423 if values.values().nth(index).is_none() {
4424 return Err(boundary_error(
4425 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4426 format!("structured object index {index} is out of range"),
4427 ));
4428 }
4429 Ok(PioJsonValue::new_raw(
4430 value.child(JsonValueStep::Object(index)),
4431 ))
4432 })
4433 }
4434}
4435
4436#[unsafe(no_mangle)]
4437pub unsafe extern "C" fn pio_json_value_retain(value: *const PioJsonValue) -> *mut PioJsonValue {
4438 unsafe { PioJsonValue::retain_raw(value) }
4439}
4440
4441#[unsafe(no_mangle)]
4442pub unsafe extern "C" fn pio_json_value_release(value: *mut PioJsonValue) {
4443 unsafe { PioJsonValue::release_raw(value) };
4444}
4445
4446#[unsafe(no_mangle)]
4447pub unsafe extern "C" fn pio_module_retain(module: *const PioModule) -> *mut PioModule {
4448 unsafe { PioModule::retain_raw(module) }
4449}
4450
4451#[unsafe(no_mangle)]
4452pub unsafe extern "C" fn pio_module_release(module: *mut PioModule) {
4453 unsafe { PioModule::release_raw(module) };
4454}
4455
4456#[unsafe(no_mangle)]
4458pub unsafe extern "C" fn pio_value_type_name(value: *const PioValueHandle) -> PioStringView {
4459 unsafe { PioValueHandle::get(value) }
4460 .and_then(ValueInner::value)
4461 .map_or(PioStringView::EMPTY, |value| {
4462 PioStringView::new(value.type_name())
4463 })
4464}
4465
4466#[unsafe(no_mangle)]
4468pub unsafe extern "C" fn pio_value_is_type(
4469 value: *const PioValueHandle,
4470 type_name: *const c_char,
4471 type_name_len: usize,
4472) -> bool {
4473 let Some(value) = unsafe { PioValueHandle::get(value) }.and_then(ValueInner::value) else {
4474 return false;
4475 };
4476 let Ok(type_name) = (unsafe { input_str(type_name, type_name_len, "type_name") }) else {
4477 return false;
4478 };
4479 value.type_name() == type_name
4480}
4481
4482#[unsafe(no_mangle)]
4484pub unsafe extern "C" fn pio_value_balanced_network(
4485 value: *const PioValueHandle,
4486 error: *mut *mut PioError,
4487) -> *mut PioBalancedNetwork {
4488 unsafe {
4489 entry(error, std::ptr::null_mut(), || {
4490 let value = require_value(value)?;
4491 if !matches!(value.value(), Some(PioValue::BalancedNetwork(_))) {
4492 return Err(boundary_error(
4493 &codes::REQUEST_CAPI_TYPE_MISMATCH,
4494 "the value is not powerio.BalancedNetwork",
4495 ));
4496 }
4497 Ok(PioBalancedNetwork::new_raw(BalancedNetworkInner {
4498 value: ValueInner {
4499 owner: Arc::clone(&value.owner),
4500 steps: value.steps.clone(),
4501 },
4502 projection: BalancedNetworkProjection::Direct,
4503 }))
4504 })
4505 }
4506}
4507
4508#[unsafe(no_mangle)]
4512pub unsafe extern "C" fn pio_value_geo_layer(
4513 value: *const PioValueHandle,
4514 error: *mut *mut PioError,
4515) -> *mut PioGeoLayer {
4516 unsafe {
4517 entry(error, std::ptr::null_mut(), || {
4518 let value = require_value(value)?;
4519 let Some(PioValue::GeoLayer(layer)) = value.value() else {
4520 return Err(boundary_error(
4521 &codes::REQUEST_CAPI_TYPE_MISMATCH,
4522 "the value is not powerio.GeoLayer",
4523 ));
4524 };
4525 Ok(PioGeoLayer::new_raw(GeoLayerInner {
4526 layer: layer.clone(),
4527 diagnostics: Vec::new(),
4528 }))
4529 })
4530 }
4531}
4532
4533#[unsafe(no_mangle)]
4535pub unsafe extern "C" fn pio_value_multiconductor_network(
4536 value: *const PioValueHandle,
4537 error: *mut *mut PioError,
4538) -> *mut PioMulticonductorNetwork {
4539 unsafe {
4540 entry(error, std::ptr::null_mut(), || {
4541 let value = require_value(value)?;
4542 if !matches!(value.value(), Some(PioValue::MulticonductorNetwork(_))) {
4543 return Err(boundary_error(
4544 &codes::REQUEST_CAPI_TYPE_MISMATCH,
4545 "the value is not powerio.MulticonductorNetwork",
4546 ));
4547 }
4548 Ok(PioMulticonductorNetwork::new_raw(
4549 MulticonductorNetworkInner {
4550 value: ValueInner {
4551 owner: Arc::clone(&value.owner),
4552 steps: value.steps.clone(),
4553 },
4554 projection: MulticonductorNetworkProjection::Direct,
4555 },
4556 ))
4557 })
4558 }
4559}
4560
4561#[unsafe(no_mangle)]
4563pub unsafe extern "C" fn pio_value_time_series(
4564 value: *const PioValueHandle,
4565 error: *mut *mut PioError,
4566) -> *mut PioTimeSeriesHandle {
4567 unsafe {
4568 entry(error, std::ptr::null_mut(), || {
4569 let value = require_value(value)?;
4570 if !matches!(value.value(), Some(PioValue::TimeSeries(_))) {
4571 return Err(boundary_error(
4572 &codes::REQUEST_CAPI_TYPE_MISMATCH,
4573 "the value is not powerio.TimeSeries<T>",
4574 ));
4575 }
4576 Ok(PioTimeSeriesHandle::new_raw(ValueInner {
4577 owner: Arc::clone(&value.owner),
4578 steps: value.steps.clone(),
4579 }))
4580 })
4581 }
4582}
4583
4584#[unsafe(no_mangle)]
4586pub unsafe extern "C" fn pio_value_scenario_set(
4587 value: *const PioValueHandle,
4588 error: *mut *mut PioError,
4589) -> *mut PioScenarioSetHandle {
4590 unsafe {
4591 entry(error, std::ptr::null_mut(), || {
4592 let value = require_value(value)?;
4593 if !matches!(value.value(), Some(PioValue::ScenarioSet(_))) {
4594 return Err(boundary_error(
4595 &codes::REQUEST_CAPI_TYPE_MISMATCH,
4596 "the value is not powerio.ScenarioSet<T>",
4597 ));
4598 }
4599 Ok(PioScenarioSetHandle::new_raw(ValueInner {
4600 owner: Arc::clone(&value.owner),
4601 steps: value.steps.clone(),
4602 }))
4603 })
4604 }
4605}
4606
4607#[derive(Clone, Copy)]
4608enum ExpectedValue {
4609 BalancedOperatingPoint,
4610 MulticonductorOperatingPoint,
4611 DcPfInstance,
4612 AcPfInstance,
4613 DcOpfInstance,
4614 AcOpfInstance,
4615 McAcPfInstance,
4616 McAcOpfInstance,
4617 AcScucInstance,
4618 DcPfSolution,
4619 AcPfSolution,
4620 DcOpfSolution,
4621 AcOpfSolution,
4622 SocwrOpfSolution,
4623 McAcPfSolution,
4624 McAcOpfSolution,
4625 AcScucSolution,
4626}
4627
4628impl ExpectedValue {
4629 fn matches(self, value: &PioValue) -> bool {
4630 matches!(
4631 (self, value),
4632 (
4633 Self::BalancedOperatingPoint,
4634 PioValue::BalancedOperatingPoint(_)
4635 ) | (
4636 Self::MulticonductorOperatingPoint,
4637 PioValue::MulticonductorOperatingPoint(_)
4638 ) | (Self::DcPfInstance, PioValue::DcPfInstance(_))
4639 | (Self::AcPfInstance, PioValue::AcPfInstance(_))
4640 | (Self::DcOpfInstance, PioValue::DcOpfInstance(_))
4641 | (Self::AcOpfInstance, PioValue::AcOpfInstance(_))
4642 | (Self::McAcPfInstance, PioValue::McAcPfInstance(_))
4643 | (Self::McAcOpfInstance, PioValue::McAcOpfInstance(_))
4644 | (Self::AcScucInstance, PioValue::AcScucInstance(_))
4645 | (Self::DcPfSolution, PioValue::DcPfSolution(_))
4646 | (Self::AcPfSolution, PioValue::AcPfSolution(_))
4647 | (Self::DcOpfSolution, PioValue::DcOpfSolution(_))
4648 | (Self::AcOpfSolution, PioValue::AcOpfSolution(_))
4649 | (Self::SocwrOpfSolution, PioValue::SocwrOpfSolution(_))
4650 | (Self::McAcPfSolution, PioValue::McAcPfSolution(_))
4651 | (Self::McAcOpfSolution, PioValue::McAcOpfSolution(_))
4652 | (Self::AcScucSolution, PioValue::AcScucSolution(_))
4653 )
4654 }
4655
4656 fn type_name(self) -> &'static str {
4657 match self {
4658 Self::BalancedOperatingPoint => "powerio.OperatingPoint<powerio.BalancedNetwork>",
4659 Self::MulticonductorOperatingPoint => {
4660 "powerio.OperatingPoint<powerio.MulticonductorNetwork>"
4661 }
4662 Self::DcPfInstance => "powerio.DcPfInstance",
4663 Self::AcPfInstance => "powerio.AcPfInstance",
4664 Self::DcOpfInstance => "powerio.DcOpfInstance",
4665 Self::AcOpfInstance => "powerio.AcOpfInstance",
4666 Self::McAcPfInstance => "powerio.McAcPfInstance",
4667 Self::McAcOpfInstance => "powerio.McAcOpfInstance",
4668 Self::AcScucInstance => "powerio.AcScucInstance",
4669 Self::DcPfSolution => "powerio.DcPfSolution",
4670 Self::AcPfSolution => "powerio.AcPfSolution",
4671 Self::DcOpfSolution => "powerio.DcOpfSolution",
4672 Self::AcOpfSolution => "powerio.AcOpfSolution",
4673 Self::SocwrOpfSolution => "powerio.SocwrOpfSolution",
4674 Self::McAcPfSolution => "powerio.McAcPfSolution",
4675 Self::McAcOpfSolution => "powerio.McAcOpfSolution",
4676 Self::AcScucSolution => "powerio.AcScucSolution",
4677 }
4678 }
4679}
4680
4681unsafe fn checked_typed_value(
4682 value: *const PioValueHandle,
4683 expected: ExpectedValue,
4684) -> Result<ValueInner, *mut PioError> {
4685 let value = unsafe { require_value(value) }?;
4686 if !value.value().is_some_and(|value| expected.matches(value)) {
4687 return Err(boundary_error(
4688 &codes::REQUEST_CAPI_TYPE_MISMATCH,
4689 format!("the value is not {}", expected.type_name()),
4690 ));
4691 }
4692 Ok(ValueInner {
4693 owner: Arc::clone(&value.owner),
4694 steps: value.steps.clone(),
4695 })
4696}
4697
4698unsafe fn operating_point_accessor(
4699 value: *const PioValueHandle,
4700 expected: ExpectedValue,
4701 error: *mut *mut PioError,
4702) -> *mut PioOperatingPoint {
4703 unsafe {
4704 entry(error, std::ptr::null_mut(), || {
4705 let value = checked_typed_value(value, expected)?;
4706 let projection = match expected {
4707 ExpectedValue::BalancedOperatingPoint => OperatingPointProjection::DirectBalanced,
4708 ExpectedValue::MulticonductorOperatingPoint => {
4709 OperatingPointProjection::DirectMulticonductor
4710 }
4711 _ => {
4712 return Err(boundary_error(
4713 &codes::REQUEST_CAPI_TYPE_MISMATCH,
4714 "the requested value is not an operating point",
4715 ));
4716 }
4717 };
4718 Ok(PioOperatingPoint::new_raw(OperatingPointInner {
4719 value,
4720 projection,
4721 }))
4722 })
4723 }
4724}
4725
4726unsafe fn instance_accessor(
4727 value: *const PioValueHandle,
4728 expected: ExpectedValue,
4729 error: *mut *mut PioError,
4730) -> *mut PioCalculationInstance {
4731 unsafe {
4732 entry(error, std::ptr::null_mut(), || {
4733 checked_typed_value(value, expected).map(|value| {
4734 PioCalculationInstance::new_raw(CalculationInstanceInner {
4735 value,
4736 projection: CalculationInstanceProjection::Direct,
4737 })
4738 })
4739 })
4740 }
4741}
4742
4743unsafe fn solution_accessor(
4744 value: *const PioValueHandle,
4745 expected: ExpectedValue,
4746 error: *mut *mut PioError,
4747) -> *mut PioCalculationSolution {
4748 unsafe {
4749 entry(error, std::ptr::null_mut(), || {
4750 checked_typed_value(value, expected).map(PioCalculationSolution::new_raw)
4751 })
4752 }
4753}
4754
4755#[unsafe(no_mangle)]
4756pub unsafe extern "C" fn pio_value_balanced_operating_point(
4757 value: *const PioValueHandle,
4758 error: *mut *mut PioError,
4759) -> *mut PioOperatingPoint {
4760 unsafe { operating_point_accessor(value, ExpectedValue::BalancedOperatingPoint, error) }
4761}
4762
4763#[unsafe(no_mangle)]
4764pub unsafe extern "C" fn pio_value_multiconductor_operating_point(
4765 value: *const PioValueHandle,
4766 error: *mut *mut PioError,
4767) -> *mut PioOperatingPoint {
4768 unsafe { operating_point_accessor(value, ExpectedValue::MulticonductorOperatingPoint, error) }
4769}
4770
4771#[unsafe(no_mangle)]
4772pub unsafe extern "C" fn pio_value_dc_pf_instance(
4773 value: *const PioValueHandle,
4774 error: *mut *mut PioError,
4775) -> *mut PioCalculationInstance {
4776 unsafe { instance_accessor(value, ExpectedValue::DcPfInstance, error) }
4777}
4778
4779#[unsafe(no_mangle)]
4780pub unsafe extern "C" fn pio_value_ac_pf_instance(
4781 value: *const PioValueHandle,
4782 error: *mut *mut PioError,
4783) -> *mut PioCalculationInstance {
4784 unsafe { instance_accessor(value, ExpectedValue::AcPfInstance, error) }
4785}
4786
4787#[unsafe(no_mangle)]
4788pub unsafe extern "C" fn pio_value_dc_opf_instance(
4789 value: *const PioValueHandle,
4790 error: *mut *mut PioError,
4791) -> *mut PioCalculationInstance {
4792 unsafe { instance_accessor(value, ExpectedValue::DcOpfInstance, error) }
4793}
4794
4795#[unsafe(no_mangle)]
4796pub unsafe extern "C" fn pio_value_ac_opf_instance(
4797 value: *const PioValueHandle,
4798 error: *mut *mut PioError,
4799) -> *mut PioCalculationInstance {
4800 unsafe { instance_accessor(value, ExpectedValue::AcOpfInstance, error) }
4801}
4802
4803#[unsafe(no_mangle)]
4804pub unsafe extern "C" fn pio_value_mc_ac_pf_instance(
4805 value: *const PioValueHandle,
4806 error: *mut *mut PioError,
4807) -> *mut PioCalculationInstance {
4808 unsafe { instance_accessor(value, ExpectedValue::McAcPfInstance, error) }
4809}
4810
4811#[unsafe(no_mangle)]
4812pub unsafe extern "C" fn pio_value_mc_ac_opf_instance(
4813 value: *const PioValueHandle,
4814 error: *mut *mut PioError,
4815) -> *mut PioCalculationInstance {
4816 unsafe { instance_accessor(value, ExpectedValue::McAcOpfInstance, error) }
4817}
4818
4819#[unsafe(no_mangle)]
4820pub unsafe extern "C" fn pio_value_ac_scuc_instance(
4821 value: *const PioValueHandle,
4822 error: *mut *mut PioError,
4823) -> *mut PioCalculationInstance {
4824 unsafe { instance_accessor(value, ExpectedValue::AcScucInstance, error) }
4825}
4826
4827#[unsafe(no_mangle)]
4828pub unsafe extern "C" fn pio_value_dc_pf_solution(
4829 value: *const PioValueHandle,
4830 error: *mut *mut PioError,
4831) -> *mut PioCalculationSolution {
4832 unsafe { solution_accessor(value, ExpectedValue::DcPfSolution, error) }
4833}
4834
4835#[unsafe(no_mangle)]
4836pub unsafe extern "C" fn pio_value_ac_pf_solution(
4837 value: *const PioValueHandle,
4838 error: *mut *mut PioError,
4839) -> *mut PioCalculationSolution {
4840 unsafe { solution_accessor(value, ExpectedValue::AcPfSolution, error) }
4841}
4842
4843#[unsafe(no_mangle)]
4844pub unsafe extern "C" fn pio_value_dc_opf_solution(
4845 value: *const PioValueHandle,
4846 error: *mut *mut PioError,
4847) -> *mut PioCalculationSolution {
4848 unsafe { solution_accessor(value, ExpectedValue::DcOpfSolution, error) }
4849}
4850
4851#[unsafe(no_mangle)]
4852pub unsafe extern "C" fn pio_value_ac_opf_solution(
4853 value: *const PioValueHandle,
4854 error: *mut *mut PioError,
4855) -> *mut PioCalculationSolution {
4856 unsafe { solution_accessor(value, ExpectedValue::AcOpfSolution, error) }
4857}
4858
4859#[unsafe(no_mangle)]
4860pub unsafe extern "C" fn pio_value_socwr_opf_solution(
4861 value: *const PioValueHandle,
4862 error: *mut *mut PioError,
4863) -> *mut PioCalculationSolution {
4864 unsafe { solution_accessor(value, ExpectedValue::SocwrOpfSolution, error) }
4865}
4866
4867#[unsafe(no_mangle)]
4868pub unsafe extern "C" fn pio_value_mc_ac_pf_solution(
4869 value: *const PioValueHandle,
4870 error: *mut *mut PioError,
4871) -> *mut PioCalculationSolution {
4872 unsafe { solution_accessor(value, ExpectedValue::McAcPfSolution, error) }
4873}
4874
4875#[unsafe(no_mangle)]
4876pub unsafe extern "C" fn pio_value_mc_ac_opf_solution(
4877 value: *const PioValueHandle,
4878 error: *mut *mut PioError,
4879) -> *mut PioCalculationSolution {
4880 unsafe { solution_accessor(value, ExpectedValue::McAcOpfSolution, error) }
4881}
4882
4883#[unsafe(no_mangle)]
4884pub unsafe extern "C" fn pio_value_ac_scuc_solution(
4885 value: *const PioValueHandle,
4886 error: *mut *mut PioError,
4887) -> *mut PioCalculationSolution {
4888 unsafe { solution_accessor(value, ExpectedValue::AcScucSolution, error) }
4889}
4890
4891#[unsafe(no_mangle)]
4892pub unsafe extern "C" fn pio_operating_point_type_name(
4893 point: *const PioOperatingPoint,
4894) -> PioStringView {
4895 unsafe { PioOperatingPoint::get(point) }
4896 .and_then(OperatingPointInner::type_name)
4897 .map_or(PioStringView::EMPTY, PioStringView::new)
4898}
4899
4900#[unsafe(no_mangle)]
4901pub unsafe extern "C" fn pio_calculation_instance_type_name(
4902 instance: *const PioCalculationInstance,
4903) -> PioStringView {
4904 unsafe { PioCalculationInstance::get(instance) }
4905 .and_then(CalculationInstanceInner::type_name)
4906 .map_or(PioStringView::EMPTY, PioStringView::new)
4907}
4908
4909#[unsafe(no_mangle)]
4910pub unsafe extern "C" fn pio_calculation_solution_type_name(
4911 solution: *const PioCalculationSolution,
4912) -> PioStringView {
4913 unsafe { PioCalculationSolution::get(solution) }
4914 .and_then(ValueInner::value)
4915 .map_or(PioStringView::EMPTY, |value| {
4916 PioStringView::new(value.type_name())
4917 })
4918}
4919
4920#[unsafe(no_mangle)]
4923pub unsafe extern "C" fn pio_calculation_solution_instance(
4924 solution: *const PioCalculationSolution,
4925 error: *mut *mut PioError,
4926) -> *mut PioCalculationInstance {
4927 unsafe {
4928 entry(error, std::ptr::null_mut(), || {
4929 let solution = PioCalculationSolution::get(solution).ok_or_else(|| {
4930 boundary_error(
4931 &codes::BIND_CAPI_NULL_HANDLE,
4932 "PioCalculationSolution must not be NULL",
4933 )
4934 })?;
4935 let projection = match solution.value() {
4936 Some(PioValue::DcPfSolution(_)) => CalculationInstanceProjection::DcPfSolution,
4937 Some(PioValue::AcPfSolution(_)) => CalculationInstanceProjection::AcPfSolution,
4938 Some(PioValue::DcOpfSolution(_)) => CalculationInstanceProjection::DcOpfSolution,
4939 Some(PioValue::AcOpfSolution(_)) => CalculationInstanceProjection::AcOpfSolution,
4940 Some(PioValue::SocwrOpfSolution(_)) => {
4941 CalculationInstanceProjection::SocwrOpfSolution
4942 }
4943 Some(PioValue::McAcPfSolution(_)) => CalculationInstanceProjection::McAcPfSolution,
4944 Some(PioValue::McAcOpfSolution(_)) => {
4945 CalculationInstanceProjection::McAcOpfSolution
4946 }
4947 Some(PioValue::AcScucSolution(_)) => CalculationInstanceProjection::AcScucSolution,
4948 _ => {
4949 return Err(boundary_error(
4950 &codes::REQUEST_CAPI_TYPE_MISMATCH,
4951 "the handle does not refer to a calculation solution",
4952 ));
4953 }
4954 };
4955 Ok(PioCalculationInstance::new_raw(CalculationInstanceInner {
4956 value: ValueInner {
4957 owner: Arc::clone(&solution.owner),
4958 steps: solution.steps.clone(),
4959 },
4960 projection,
4961 }))
4962 })
4963 }
4964}
4965
4966fn make_balanced_network_view(
4967 value: &ValueInner,
4968 projection: BalancedNetworkProjection,
4969) -> *mut PioBalancedNetwork {
4970 PioBalancedNetwork::new_raw(BalancedNetworkInner {
4971 value: ValueInner {
4972 owner: Arc::clone(&value.owner),
4973 steps: value.steps.clone(),
4974 },
4975 projection,
4976 })
4977}
4978
4979fn make_multiconductor_network_view(
4980 value: &ValueInner,
4981 projection: MulticonductorNetworkProjection,
4982) -> *mut PioMulticonductorNetwork {
4983 PioMulticonductorNetwork::new_raw(MulticonductorNetworkInner {
4984 value: ValueInner {
4985 owner: Arc::clone(&value.owner),
4986 steps: value.steps.clone(),
4987 },
4988 projection,
4989 })
4990}
4991
4992#[unsafe(no_mangle)]
4993pub unsafe extern "C" fn pio_operating_point_balanced_network(
4994 point: *const PioOperatingPoint,
4995 error: *mut *mut PioError,
4996) -> *mut PioBalancedNetwork {
4997 unsafe {
4998 entry(error, std::ptr::null_mut(), || {
4999 let point = PioOperatingPoint::get(point).ok_or_else(|| {
5000 boundary_error(
5001 &codes::BIND_CAPI_NULL_HANDLE,
5002 "PioOperatingPoint must not be NULL",
5003 )
5004 })?;
5005 let projection = point.balanced_network_projection().ok_or_else(|| {
5006 boundary_error(
5007 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5008 "the operating point does not use powerio.BalancedNetwork",
5009 )
5010 })?;
5011 Ok(make_balanced_network_view(&point.value, projection))
5012 })
5013 }
5014}
5015
5016#[unsafe(no_mangle)]
5017pub unsafe extern "C" fn pio_operating_point_multiconductor_network(
5018 point: *const PioOperatingPoint,
5019 error: *mut *mut PioError,
5020) -> *mut PioMulticonductorNetwork {
5021 unsafe {
5022 entry(error, std::ptr::null_mut(), || {
5023 let point = PioOperatingPoint::get(point).ok_or_else(|| {
5024 boundary_error(
5025 &codes::BIND_CAPI_NULL_HANDLE,
5026 "PioOperatingPoint must not be NULL",
5027 )
5028 })?;
5029 let projection = point.multiconductor_network_projection().ok_or_else(|| {
5030 boundary_error(
5031 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5032 "the operating point does not use powerio.MulticonductorNetwork",
5033 )
5034 })?;
5035 Ok(make_multiconductor_network_view(&point.value, projection))
5036 })
5037 }
5038}
5039
5040#[unsafe(no_mangle)]
5041pub unsafe extern "C" fn pio_calculation_instance_balanced_network(
5042 instance: *const PioCalculationInstance,
5043 error: *mut *mut PioError,
5044) -> *mut PioBalancedNetwork {
5045 unsafe {
5046 entry(error, std::ptr::null_mut(), || {
5047 let instance = PioCalculationInstance::get(instance).ok_or_else(|| {
5048 boundary_error(
5049 &codes::BIND_CAPI_NULL_HANDLE,
5050 "PioCalculationInstance must not be NULL",
5051 )
5052 })?;
5053 let projection = match instance.projection {
5054 CalculationInstanceProjection::Direct => match instance.instance() {
5055 Some(CalculationInstanceRef::DcPf(_)) => {
5056 BalancedNetworkProjection::DcPfInstance
5057 }
5058 Some(CalculationInstanceRef::AcPf(_)) => {
5059 BalancedNetworkProjection::AcPfInstance
5060 }
5061 Some(CalculationInstanceRef::DcOpf(_)) => {
5062 BalancedNetworkProjection::DcOpfInstance
5063 }
5064 Some(CalculationInstanceRef::AcOpf(_)) => {
5065 BalancedNetworkProjection::AcOpfInstance
5066 }
5067 Some(CalculationInstanceRef::AcScuc(_)) => {
5068 BalancedNetworkProjection::AcScucInstance
5069 }
5070 _ => {
5071 return Err(boundary_error(
5072 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5073 "the calculation instance does not use powerio.BalancedNetwork",
5074 ));
5075 }
5076 },
5077 CalculationInstanceProjection::DcPfSolution => {
5078 BalancedNetworkProjection::DcPfSolution
5079 }
5080 CalculationInstanceProjection::AcPfSolution => {
5081 BalancedNetworkProjection::AcPfSolution
5082 }
5083 CalculationInstanceProjection::DcOpfSolution => {
5084 BalancedNetworkProjection::DcOpfSolution
5085 }
5086 CalculationInstanceProjection::AcOpfSolution => {
5087 BalancedNetworkProjection::AcOpfSolution
5088 }
5089 CalculationInstanceProjection::SocwrOpfSolution => {
5090 BalancedNetworkProjection::SocwrOpfSolution
5091 }
5092 CalculationInstanceProjection::AcScucSolution => {
5093 BalancedNetworkProjection::AcScucSolution
5094 }
5095 _ => {
5096 return Err(boundary_error(
5097 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5098 "the calculation instance does not use powerio.BalancedNetwork",
5099 ));
5100 }
5101 };
5102 Ok(make_balanced_network_view(&instance.value, projection))
5103 })
5104 }
5105}
5106
5107#[unsafe(no_mangle)]
5108pub unsafe extern "C" fn pio_calculation_instance_multiconductor_network(
5109 instance: *const PioCalculationInstance,
5110 error: *mut *mut PioError,
5111) -> *mut PioMulticonductorNetwork {
5112 unsafe {
5113 entry(error, std::ptr::null_mut(), || {
5114 let instance = PioCalculationInstance::get(instance).ok_or_else(|| {
5115 boundary_error(
5116 &codes::BIND_CAPI_NULL_HANDLE,
5117 "PioCalculationInstance must not be NULL",
5118 )
5119 })?;
5120 let projection = match instance.projection {
5121 CalculationInstanceProjection::Direct => match instance.instance() {
5122 Some(CalculationInstanceRef::McAcPf(_)) => {
5123 MulticonductorNetworkProjection::McAcPfInstance
5124 }
5125 Some(CalculationInstanceRef::McAcOpf(_)) => {
5126 MulticonductorNetworkProjection::McAcOpfInstance
5127 }
5128 _ => {
5129 return Err(boundary_error(
5130 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5131 "the calculation instance does not use powerio.MulticonductorNetwork",
5132 ));
5133 }
5134 },
5135 CalculationInstanceProjection::McAcPfSolution => {
5136 MulticonductorNetworkProjection::McAcPfSolution
5137 }
5138 CalculationInstanceProjection::McAcOpfSolution => {
5139 MulticonductorNetworkProjection::McAcOpfSolution
5140 }
5141 _ => {
5142 return Err(boundary_error(
5143 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5144 "the calculation instance does not use powerio.MulticonductorNetwork",
5145 ));
5146 }
5147 };
5148 Ok(make_multiconductor_network_view(
5149 &instance.value,
5150 projection,
5151 ))
5152 })
5153 }
5154}
5155
5156fn branch_susceptance_formula_name(formula: BranchSusceptanceFormula) -> &'static str {
5157 match formula {
5158 BranchSusceptanceFormula::ReactanceOnly => "reactance_only",
5159 BranchSusceptanceFormula::TapAdjustedReactance => "tap_adjusted_reactance",
5160 BranchSusceptanceFormula::SeriesSusceptance => "series_susceptance",
5161 _ => "unknown",
5162 }
5163}
5164
5165unsafe fn require_calculation_instance<'a>(
5166 instance: *const PioCalculationInstance,
5167) -> Result<&'a CalculationInstanceInner, *mut PioError> {
5168 unsafe { PioCalculationInstance::get(instance) }.ok_or_else(|| {
5169 boundary_error(
5170 &codes::BIND_CAPI_NULL_HANDLE,
5171 "PioCalculationInstance must not be NULL",
5172 )
5173 })
5174}
5175
5176fn opf_preparation_units(name: &str) -> Result<Units, *mut PioError> {
5177 match name {
5178 "per_unit" => Ok(Units::PerUnit),
5179 "native" => Ok(Units::Native),
5180 _ => Err(boundary_error(
5181 &codes::BIND_CAPI_INVALID_OPTIONS,
5182 format!("unknown OPF preparation units '{name}'; expected 'per_unit' or 'native'"),
5183 )),
5184 }
5185}
5186
5187fn opf_preparation_units_name(units: Units) -> Option<&'static str> {
5188 match units {
5189 Units::PerUnit => Some("per_unit"),
5190 Units::Native => Some("native"),
5191 _ => None,
5192 }
5193}
5194
5195fn prepared_objective_name(objective: PreparedObjective) -> Option<&'static str> {
5196 match objective {
5197 PreparedObjective::Feasibility => Some("feasibility"),
5198 PreparedObjective::NetworkGeneratorCost => Some("network_generator_cost"),
5199 _ => None,
5200 }
5201}
5202
5203fn opf_analysis_branch_source(
5204 source: AnalysisBranchSource,
5205 preparation: &str,
5206) -> Result<(&'static str, usize, Option<usize>), *mut PioError> {
5207 match source {
5208 AnalysisBranchSource::Branch { row } => Ok(("branch", row, None)),
5209 AnalysisBranchSource::ThreeWindingTransformerWinding {
5210 transformer_row,
5211 winding,
5212 } => Ok((
5213 "three_winding_transformer_winding",
5214 transformer_row,
5215 Some(winding),
5216 )),
5217 _ => Err(boundary_error(
5218 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5219 format!(
5220 "the {preparation} OPF preparation has an analysis branch source unsupported by ABI 7"
5221 ),
5222 )),
5223 }
5224}
5225
5226#[unsafe(no_mangle)]
5228pub unsafe extern "C" fn pio_build_dc_opf_preparation(
5229 instance: *const PioCalculationInstance,
5230 units: *const c_char,
5231 units_len: usize,
5232 skip_zero_impedance: bool,
5233 synthesize_unrated_limits: bool,
5234 correct_angle_difference_bounds: bool,
5235 error: *mut *mut PioError,
5236) -> *mut PioDcOpfPreparation {
5237 unsafe {
5238 entry(error, std::ptr::null_mut(), || {
5239 let instance = require_calculation_instance(instance)?
5240 .dc_opf()
5241 .ok_or_else(|| {
5242 boundary_error(
5243 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5244 "DC OPF preparation requires powerio.DcOpfInstance",
5245 )
5246 })?;
5247 let units = opf_preparation_units(required_str(units, units_len, "units")?)?;
5248 let options = DcOpfAssemblyOptions::default()
5249 .with_units(units)
5250 .with_skip_zero_impedance(skip_zero_impedance)
5251 .with_synthesize_unrated_limits(synthesize_unrated_limits)
5252 .with_correct_angle_difference_bounds(correct_angle_difference_bounds);
5253 build_dc_opf_preparation(instance, &options)
5254 .map(PioDcOpfPreparation::new_raw)
5255 .map_err(|failure| error_from_matrix(&failure))
5256 })
5257 }
5258}
5259
5260#[unsafe(no_mangle)]
5262pub unsafe extern "C" fn pio_dc_opf_preparation_summary(
5263 preparation: *const PioDcOpfPreparation,
5264 output: *mut PioDcOpfPreparationView,
5265 error: *mut *mut PioError,
5266) -> bool {
5267 unsafe {
5268 entry(error, false, || {
5269 let preparation = PioDcOpfPreparation::get(preparation).ok_or_else(|| {
5270 boundary_error(
5271 &codes::BIND_CAPI_NULL_HANDLE,
5272 "PioDcOpfPreparation must not be NULL",
5273 )
5274 })?;
5275 let units = opf_preparation_units_name(preparation.units).ok_or_else(|| {
5276 boundary_error(
5277 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5278 "the DC OPF preparation uses units unsupported by ABI 7",
5279 )
5280 })?;
5281 let objective = prepared_objective_name(preparation.objective).ok_or_else(|| {
5282 boundary_error(
5283 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5284 "the DC OPF preparation uses an objective unsupported by ABI 7",
5285 )
5286 })?;
5287 *require_output(output, "output")? = PioDcOpfPreparationView {
5288 name: PioStringView::new(&preparation.name),
5289 bus_count: preparation.n_buses,
5290 generator_count: preparation.n_generators(),
5291 branch_count: preparation.n_branches(),
5292 source_generator_count: preparation.n_source_generators,
5293 source_branch_count: preparation.n_source_branches,
5294 base_mva: preparation.base_mva,
5295 units: PioStringView::new(units),
5296 branch_susceptance_formula: PioStringView::new(branch_susceptance_formula_name(
5297 preparation.formula,
5298 )),
5299 objective: PioStringView::new(objective),
5300 skip_zero_impedance: preparation.skip_zero_impedance,
5301 synthesize_unrated_limits: preparation.synthesize_unrated_limits,
5302 correct_angle_difference_bounds: preparation.correct_angle_difference_bounds,
5303 reference_bus_count: preparation.reference_buses.len(),
5304 skipped_zero_impedance_count: preparation.branches.skipped_zero_impedance.len(),
5305 };
5306 Ok(true)
5307 })
5308 }
5309}
5310
5311#[unsafe(no_mangle)]
5313pub unsafe extern "C" fn pio_dc_opf_preparation_reference_buses(
5314 preparation: *const PioDcOpfPreparation,
5315) -> PioSizeView {
5316 unsafe { PioDcOpfPreparation::get(preparation) }.map_or(PioSizeView::EMPTY, |value| {
5317 PioSizeView::new(value.reference_buses.as_ref())
5318 })
5319}
5320
5321#[unsafe(no_mangle)]
5323pub unsafe extern "C" fn pio_dc_opf_preparation_skipped_zero_impedance(
5324 preparation: *const PioDcOpfPreparation,
5325) -> PioSizeView {
5326 unsafe { PioDcOpfPreparation::get(preparation) }.map_or(PioSizeView::EMPTY, |value| {
5327 PioSizeView::new(&value.branches.skipped_zero_impedance)
5328 })
5329}
5330
5331#[unsafe(no_mangle)]
5333pub unsafe extern "C" fn pio_dc_opf_preparation_bus_at(
5334 preparation: *const PioDcOpfPreparation,
5335 index: usize,
5336 output: *mut PioDcOpfBusView,
5337 error: *mut *mut PioError,
5338) -> bool {
5339 unsafe {
5340 entry(error, false, || {
5341 let value = PioDcOpfPreparation::get(preparation).ok_or_else(|| {
5342 boundary_error(
5343 &codes::BIND_CAPI_NULL_HANDLE,
5344 "PioDcOpfPreparation must not be NULL",
5345 )
5346 })?;
5347 if index >= value.n_buses {
5348 return Err(boundary_error(
5349 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5350 format!("DC OPF preparation bus index {index} is out of range"),
5351 ));
5352 }
5353 let source_row = value.bus_source_rows[index];
5354 *require_output(output, "output")? = PioDcOpfBusView {
5355 bus_id: value.bus_ids[index].0,
5356 analysis_row: value.bus_analysis_rows[index],
5357 source_row: source_row.unwrap_or(0),
5358 has_source_row: source_row.is_some(),
5359 active_power_demand: value.p_d[index],
5360 shunt_conductance: value.g_s[index],
5361 phase_shift_injection: value.p_shift[index],
5362 };
5363 Ok(true)
5364 })
5365 }
5366}
5367
5368#[unsafe(no_mangle)]
5370pub unsafe extern "C" fn pio_dc_opf_preparation_generator_at(
5371 preparation: *const PioDcOpfPreparation,
5372 index: usize,
5373 output: *mut PioDcOpfGeneratorView,
5374 error: *mut *mut PioError,
5375) -> bool {
5376 unsafe {
5377 entry(error, false, || {
5378 let value = PioDcOpfPreparation::get(preparation).ok_or_else(|| {
5379 boundary_error(
5380 &codes::BIND_CAPI_NULL_HANDLE,
5381 "PioDcOpfPreparation must not be NULL",
5382 )
5383 })?;
5384 if index >= value.n_generators() {
5385 return Err(boundary_error(
5386 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5387 format!("DC OPF preparation generator index {index} is out of range"),
5388 ));
5389 }
5390 let source_row = value.generators.source_rows[index];
5391 let piecewise = value.generators.piecewise_linear[index].as_ref();
5392 *require_output(output, "output")? = PioDcOpfGeneratorView {
5393 component_id: PioStringView::new(&value.generators.identities[index]),
5394 bus_index: value.generators.bus_of_gen[index],
5395 analysis_row: value.generators.analysis_rows[index],
5396 source_row: source_row.unwrap_or(0),
5397 has_source_row: source_row.is_some(),
5398 quadratic_cost: value.generators.q[index],
5399 linear_cost: value.generators.c[index],
5400 constant_cost: value.generators.c0[index],
5401 has_piecewise_linear_cost: piecewise.is_some(),
5402 piecewise_linear_power: piecewise
5403 .map_or(PioF64View::EMPTY, |cost| PioF64View::new(&cost.power)),
5404 piecewise_linear_value: piecewise
5405 .map_or(PioF64View::EMPTY, |cost| PioF64View::new(&cost.value)),
5406 active_power_max: value.generators.pmax[index],
5407 active_power_min: value.generators.pmin[index],
5408 capability_active: value.generators.capability_active[index],
5409 };
5410 Ok(true)
5411 })
5412 }
5413}
5414
5415#[unsafe(no_mangle)]
5417pub unsafe extern "C" fn pio_dc_opf_preparation_branch_at(
5418 preparation: *const PioDcOpfPreparation,
5419 index: usize,
5420 output: *mut PioDcOpfBranchView,
5421 error: *mut *mut PioError,
5422) -> bool {
5423 unsafe {
5424 entry(error, false, || {
5425 let value = PioDcOpfPreparation::get(preparation).ok_or_else(|| {
5426 boundary_error(
5427 &codes::BIND_CAPI_NULL_HANDLE,
5428 "PioDcOpfPreparation must not be NULL",
5429 )
5430 })?;
5431 if index >= value.n_branches() {
5432 return Err(boundary_error(
5433 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5434 format!("DC OPF preparation branch index {index} is out of range"),
5435 ));
5436 }
5437 let (source_kind, source_row, winding) =
5438 opf_analysis_branch_source(value.branches.analysis_sources[index], "DC")?;
5439 *require_output(output, "output")? = PioDcOpfBranchView {
5440 component_id: PioStringView::new(&value.branches.identities[index]),
5441 from_bus_index: value.branches.from_bus[index],
5442 to_bus_index: value.branches.to_bus[index],
5443 susceptance_magnitude: value.branches.susceptance_magnitude[index],
5444 phase_shift_radians: value.branches.shift[index],
5445 active_power_max: value.branches.f_max[index],
5446 angle_difference_min_radians: value.branches.angle_min[index],
5447 angle_difference_max_radians: value.branches.angle_max[index],
5448 analysis_row: value.branches.analysis_rows[index],
5449 source_kind: PioStringView::new(source_kind),
5450 source_row,
5451 winding: winding.unwrap_or(0),
5452 has_winding: winding.is_some(),
5453 thermal_limit_active: value.branches.thermal_limit_active[index],
5454 angle_bound_active: value.branches.angle_bound_active[index],
5455 };
5456 Ok(true)
5457 })
5458 }
5459}
5460
5461#[unsafe(no_mangle)]
5463pub unsafe extern "C" fn pio_build_ac_opf_preparation(
5464 instance: *const PioCalculationInstance,
5465 units: *const c_char,
5466 units_len: usize,
5467 skip_zero_impedance: bool,
5468 synthesize_unrated_limits: bool,
5469 correct_angle_difference_bounds: bool,
5470 error: *mut *mut PioError,
5471) -> *mut PioAcOpfPreparation {
5472 unsafe {
5473 entry(error, std::ptr::null_mut(), || {
5474 let instance = require_calculation_instance(instance)?
5475 .ac_opf()
5476 .ok_or_else(|| {
5477 boundary_error(
5478 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5479 "AC OPF preparation requires powerio.AcOpfInstance",
5480 )
5481 })?;
5482 let units = opf_preparation_units(required_str(units, units_len, "units")?)?;
5483 let options = AcOpfAssemblyOptions::default()
5484 .with_units(units)
5485 .with_skip_zero_impedance(skip_zero_impedance)
5486 .with_synthesize_unrated_limits(synthesize_unrated_limits)
5487 .with_correct_angle_difference_bounds(correct_angle_difference_bounds);
5488 build_ac_opf_preparation(instance, &options)
5489 .map(PioAcOpfPreparation::new_raw)
5490 .map_err(|failure| error_from_matrix(&failure))
5491 })
5492 }
5493}
5494
5495#[unsafe(no_mangle)]
5497pub unsafe extern "C" fn pio_ac_opf_preparation_summary(
5498 preparation: *const PioAcOpfPreparation,
5499 output: *mut PioAcOpfPreparationView,
5500 error: *mut *mut PioError,
5501) -> bool {
5502 unsafe {
5503 entry(error, false, || {
5504 let preparation = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
5505 boundary_error(
5506 &codes::BIND_CAPI_NULL_HANDLE,
5507 "PioAcOpfPreparation must not be NULL",
5508 )
5509 })?;
5510 let units = opf_preparation_units_name(preparation.units).ok_or_else(|| {
5511 boundary_error(
5512 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5513 "the AC OPF preparation uses units unsupported by ABI 7",
5514 )
5515 })?;
5516 let objective = prepared_objective_name(preparation.objective).ok_or_else(|| {
5517 boundary_error(
5518 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5519 "the AC OPF preparation uses an objective unsupported by ABI 7",
5520 )
5521 })?;
5522 *require_output(output, "output")? = PioAcOpfPreparationView {
5523 name: PioStringView::new(&preparation.name),
5524 bus_count: preparation.n_buses,
5525 generator_count: preparation.n_generators(),
5526 storage_count: preparation.n_storage(),
5527 branch_count: preparation.n_branches(),
5528 source_generator_count: preparation.n_source_generators,
5529 source_branch_count: preparation.n_source_branches,
5530 base_mva: preparation.base_mva,
5531 units: PioStringView::new(units),
5532 objective: PioStringView::new(objective),
5533 skip_zero_impedance: preparation.skip_zero_impedance,
5534 synthesize_unrated_limits: preparation.synthesize_unrated_limits,
5535 correct_angle_difference_bounds: preparation.correct_angle_difference_bounds,
5536 reference_bus_count: preparation.reference_buses.len(),
5537 skipped_zero_impedance_count: preparation.branches.skipped_zero_impedance.len(),
5538 };
5539 Ok(true)
5540 })
5541 }
5542}
5543
5544#[unsafe(no_mangle)]
5546pub unsafe extern "C" fn pio_ac_opf_preparation_reference_buses(
5547 preparation: *const PioAcOpfPreparation,
5548) -> PioSizeView {
5549 unsafe { PioAcOpfPreparation::get(preparation) }.map_or(PioSizeView::EMPTY, |value| {
5550 PioSizeView::new(value.reference_buses.as_ref())
5551 })
5552}
5553
5554#[unsafe(no_mangle)]
5556pub unsafe extern "C" fn pio_ac_opf_preparation_skipped_zero_impedance(
5557 preparation: *const PioAcOpfPreparation,
5558) -> PioSizeView {
5559 unsafe { PioAcOpfPreparation::get(preparation) }.map_or(PioSizeView::EMPTY, |value| {
5560 PioSizeView::new(&value.branches.skipped_zero_impedance)
5561 })
5562}
5563
5564#[unsafe(no_mangle)]
5566pub unsafe extern "C" fn pio_ac_opf_preparation_bus_at(
5567 preparation: *const PioAcOpfPreparation,
5568 index: usize,
5569 output: *mut PioAcOpfBusView,
5570 error: *mut *mut PioError,
5571) -> bool {
5572 unsafe {
5573 entry(error, false, || {
5574 let value = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
5575 boundary_error(
5576 &codes::BIND_CAPI_NULL_HANDLE,
5577 "PioAcOpfPreparation must not be NULL",
5578 )
5579 })?;
5580 if index >= value.n_buses {
5581 return Err(boundary_error(
5582 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5583 format!("AC OPF preparation bus index {index} is out of range"),
5584 ));
5585 }
5586 let source_row = value.bus_source_rows[index];
5587 *require_output(output, "output")? = PioAcOpfBusView {
5588 bus_id: value.bus_ids[index].0,
5589 analysis_row: value.bus_analysis_rows[index],
5590 source_row: source_row.unwrap_or(0),
5591 has_source_row: source_row.is_some(),
5592 active_power_demand: value.buses.p_d[index],
5593 reactive_power_demand: value.buses.q_d[index],
5594 shunt_conductance: value.buses.g_s[index],
5595 shunt_susceptance: value.buses.b_s[index],
5596 voltage_magnitude_min_pu: value.buses.vm_min[index],
5597 voltage_magnitude_max_pu: value.buses.vm_max[index],
5598 initial_voltage_magnitude_pu: value.buses.initial_vm[index],
5599 initial_voltage_angle_radians: value.buses.initial_va[index],
5600 voltage_bound_active: value.buses.voltage_bound_active[index],
5601 };
5602 Ok(true)
5603 })
5604 }
5605}
5606
5607#[unsafe(no_mangle)]
5609pub unsafe extern "C" fn pio_ac_opf_preparation_generator_at(
5610 preparation: *const PioAcOpfPreparation,
5611 index: usize,
5612 output: *mut PioAcOpfGeneratorView,
5613 error: *mut *mut PioError,
5614) -> bool {
5615 unsafe {
5616 entry(error, false, || {
5617 let value = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
5618 boundary_error(
5619 &codes::BIND_CAPI_NULL_HANDLE,
5620 "PioAcOpfPreparation must not be NULL",
5621 )
5622 })?;
5623 if index >= value.n_generators() {
5624 return Err(boundary_error(
5625 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5626 format!("AC OPF preparation generator index {index} is out of range"),
5627 ));
5628 }
5629 let source_row = value.generators.source_rows[index];
5630 let piecewise = value.generators.piecewise_linear[index].as_ref();
5631 *require_output(output, "output")? = PioAcOpfGeneratorView {
5632 component_id: PioStringView::new(&value.generators.identities[index]),
5633 bus_index: value.generators.bus_of_gen[index],
5634 analysis_row: value.generators.analysis_rows[index],
5635 source_row: source_row.unwrap_or(0),
5636 has_source_row: source_row.is_some(),
5637 quadratic_cost: value.generators.q[index],
5638 linear_cost: value.generators.c[index],
5639 constant_cost: value.generators.c0[index],
5640 has_piecewise_linear_cost: piecewise.is_some(),
5641 piecewise_linear_power: piecewise
5642 .map_or(PioF64View::EMPTY, |cost| PioF64View::new(&cost.power)),
5643 piecewise_linear_value: piecewise
5644 .map_or(PioF64View::EMPTY, |cost| PioF64View::new(&cost.value)),
5645 active_power_max: value.generators.pmax[index],
5646 active_power_min: value.generators.pmin[index],
5647 reactive_power_max: value.generators.qmax[index],
5648 reactive_power_min: value.generators.qmin[index],
5649 initial_active_power: value.generators.pg[index],
5650 initial_reactive_power: value.generators.qg[index],
5651 voltage_magnitude_setpoint_pu: value.generators.vg[index],
5652 capability_active: value.generators.capability_active[index],
5653 };
5654 Ok(true)
5655 })
5656 }
5657}
5658
5659#[unsafe(no_mangle)]
5661pub unsafe extern "C" fn pio_ac_opf_preparation_storage_at(
5662 preparation: *const PioAcOpfPreparation,
5663 index: usize,
5664 output: *mut PioAcOpfStorageView,
5665 error: *mut *mut PioError,
5666) -> bool {
5667 unsafe {
5668 entry(error, false, || {
5669 let value = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
5670 boundary_error(
5671 &codes::BIND_CAPI_NULL_HANDLE,
5672 "PioAcOpfPreparation must not be NULL",
5673 )
5674 })?;
5675 if index >= value.n_storage() {
5676 return Err(boundary_error(
5677 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5678 format!("AC OPF preparation storage index {index} is out of range"),
5679 ));
5680 }
5681 let storage = &value.storage;
5682 *require_output(output, "output")? = PioAcOpfStorageView {
5683 component_id: PioStringView::new(&storage.identities[index]),
5684 bus_index: storage.bus_of_storage[index],
5685 source_row: storage.source_rows[index],
5686 initial_active_power: storage.p[index],
5687 initial_reactive_power: storage.q[index],
5688 energy: storage.energy[index],
5689 energy_rating: storage.energy_rating[index],
5690 charge_rating: storage.charge_rating[index],
5691 discharge_rating: storage.discharge_rating[index],
5692 charge_efficiency: storage.charge_efficiency[index],
5693 discharge_efficiency: storage.discharge_efficiency[index],
5694 apparent_power_max: storage.s_max[index],
5695 reactive_power_min: storage.qmin[index],
5696 reactive_power_max: storage.qmax[index],
5697 resistance_pu: storage.r[index],
5698 reactance_pu: storage.x[index],
5699 active_power_loss: storage.p_loss[index],
5700 reactive_power_loss: storage.q_loss[index],
5701 in_service: storage.in_service[index],
5702 };
5703 Ok(true)
5704 })
5705 }
5706}
5707
5708#[unsafe(no_mangle)]
5710pub unsafe extern "C" fn pio_ac_opf_preparation_branch_at(
5711 preparation: *const PioAcOpfPreparation,
5712 index: usize,
5713 output: *mut PioAcOpfBranchView,
5714 error: *mut *mut PioError,
5715) -> bool {
5716 unsafe {
5717 entry(error, false, || {
5718 let value = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
5719 boundary_error(
5720 &codes::BIND_CAPI_NULL_HANDLE,
5721 "PioAcOpfPreparation must not be NULL",
5722 )
5723 })?;
5724 if index >= value.n_branches() {
5725 return Err(boundary_error(
5726 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5727 format!("AC OPF preparation branch index {index} is out of range"),
5728 ));
5729 }
5730 let (source_kind, source_row, winding) =
5731 opf_analysis_branch_source(value.branches.analysis_sources[index], "AC")?;
5732 *require_output(output, "output")? = PioAcOpfBranchView {
5733 component_id: PioStringView::new(&value.branches.identities[index]),
5734 from_bus_index: value.branches.from_bus[index],
5735 to_bus_index: value.branches.to_bus[index],
5736 series_conductance: value.branches.g[index],
5737 series_susceptance: value.branches.b[index],
5738 from_conductance: value.branches.g_fr[index],
5739 from_susceptance: value.branches.b_fr[index],
5740 to_conductance: value.branches.g_to[index],
5741 to_susceptance: value.branches.b_to[index],
5742 tap_ratio: value.branches.tap[index],
5743 phase_shift_radians: value.branches.shift[index],
5744 apparent_power_max: value.branches.s_max[index],
5745 angle_difference_min_radians: value.branches.angle_min[index],
5746 angle_difference_max_radians: value.branches.angle_max[index],
5747 analysis_row: value.branches.analysis_rows[index],
5748 source_kind: PioStringView::new(source_kind),
5749 source_row,
5750 winding: winding.unwrap_or(0),
5751 has_winding: winding.is_some(),
5752 thermal_limit_active: value.branches.thermal_limit_active[index],
5753 angle_bound_active: value.branches.angle_bound_active[index],
5754 };
5755 Ok(true)
5756 })
5757 }
5758}
5759
5760#[unsafe(no_mangle)]
5761pub unsafe extern "C" fn pio_dc_pf_instance_bus_specification_count(
5762 instance: *const PioCalculationInstance,
5763) -> usize {
5764 unsafe { PioCalculationInstance::get(instance) }
5765 .and_then(CalculationInstanceInner::dc_pf)
5766 .map_or(0, |instance| instance.specifications().len())
5767}
5768
5769#[unsafe(no_mangle)]
5771pub unsafe extern "C" fn pio_dc_pf_instance_bus_specification_at(
5772 instance: *const PioCalculationInstance,
5773 index: usize,
5774 output: *mut PioDcBusSpecificationView,
5775 error: *mut *mut PioError,
5776) -> bool {
5777 unsafe {
5778 entry(error, false, || {
5779 let instance = require_calculation_instance(instance)?
5780 .dc_pf()
5781 .ok_or_else(|| {
5782 boundary_error(
5783 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5784 "the calculation instance is not powerio.DcPfInstance",
5785 )
5786 })?;
5787 let specification = instance.specifications().get(index).ok_or_else(|| {
5788 boundary_error(
5789 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5790 format!("DC bus specification index {index} is out of range"),
5791 )
5792 })?;
5793 let bus_id = instance
5794 .network()
5795 .buses()
5796 .get(index)
5797 .ok_or_else(|| {
5798 boundary_error(
5799 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5800 format!("bus specification index {index} has no bus"),
5801 )
5802 })?
5803 .id
5804 .0;
5805 let (kind, net_active_power_mw, voltage_angle_degrees) = match *specification {
5806 powerio_prob::DcBusSpecification::NetActivePower { p_mw } => {
5807 ("net_active_power", p_mw, 0.0)
5808 }
5809 powerio_prob::DcBusSpecification::Reference { va_degrees } => {
5810 ("reference", 0.0, va_degrees)
5811 }
5812 powerio_prob::DcBusSpecification::Isolated => ("isolated", 0.0, 0.0),
5813 _ => ("unknown", 0.0, 0.0),
5814 };
5815 *require_output(output, "output")? = PioDcBusSpecificationView {
5816 bus_id,
5817 kind: PioStringView::new(kind),
5818 net_active_power_mw,
5819 voltage_angle_degrees,
5820 };
5821 Ok(true)
5822 })
5823 }
5824}
5825
5826#[unsafe(no_mangle)]
5827pub unsafe extern "C" fn pio_dc_pf_instance_branch_susceptance_formula(
5828 instance: *const PioCalculationInstance,
5829) -> PioStringView {
5830 unsafe { PioCalculationInstance::get(instance) }
5831 .and_then(CalculationInstanceInner::dc_pf)
5832 .map_or(PioStringView::EMPTY, |instance| {
5833 PioStringView::new(branch_susceptance_formula_name(
5834 instance.branch_susceptance_formula(),
5835 ))
5836 })
5837}
5838
5839#[unsafe(no_mangle)]
5840pub unsafe extern "C" fn pio_ac_pf_instance_bus_specification_count(
5841 instance: *const PioCalculationInstance,
5842) -> usize {
5843 unsafe { PioCalculationInstance::get(instance) }
5844 .and_then(CalculationInstanceInner::ac_pf)
5845 .map_or(0, |instance| instance.specifications().len())
5846}
5847
5848#[unsafe(no_mangle)]
5850pub unsafe extern "C" fn pio_ac_pf_instance_bus_specification_at(
5851 instance: *const PioCalculationInstance,
5852 index: usize,
5853 output: *mut PioAcBusSpecificationView,
5854 error: *mut *mut PioError,
5855) -> bool {
5856 unsafe {
5857 entry(error, false, || {
5858 let instance = require_calculation_instance(instance)?
5859 .ac_pf()
5860 .ok_or_else(|| {
5861 boundary_error(
5862 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5863 "the calculation instance is not powerio.AcPfInstance",
5864 )
5865 })?;
5866 let specification = instance.specifications().get(index).ok_or_else(|| {
5867 boundary_error(
5868 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5869 format!("AC bus specification index {index} is out of range"),
5870 )
5871 })?;
5872 let bus_id = instance
5873 .network()
5874 .buses()
5875 .get(index)
5876 .ok_or_else(|| {
5877 boundary_error(
5878 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5879 format!("bus specification index {index} has no bus"),
5880 )
5881 })?
5882 .id
5883 .0;
5884 let (kind, p, q, vm, va) = match *specification {
5885 powerio_prob::AcBusSpecification::Pq { p, q } => ("pq", p, q, 0.0, 0.0),
5886 powerio_prob::AcBusSpecification::Pv { p, vm } => ("pv", p, 0.0, vm, 0.0),
5887 powerio_prob::AcBusSpecification::Reference { vm, va } => {
5888 ("reference", 0.0, 0.0, vm, va)
5889 }
5890 powerio_prob::AcBusSpecification::Isolated => ("isolated", 0.0, 0.0, 0.0, 0.0),
5891 _ => ("unknown", 0.0, 0.0, 0.0, 0.0),
5892 };
5893 *require_output(output, "output")? = PioAcBusSpecificationView {
5894 bus_id,
5895 kind: PioStringView::new(kind),
5896 net_active_power_mw: p,
5897 net_reactive_power_mvar: q,
5898 voltage_magnitude_pu: vm,
5899 voltage_angle_degrees: va,
5900 };
5901 Ok(true)
5902 })
5903 }
5904}
5905
5906#[unsafe(no_mangle)]
5907pub unsafe extern "C" fn pio_dc_opf_instance_branch_susceptance_formula(
5908 instance: *const PioCalculationInstance,
5909) -> PioStringView {
5910 unsafe { PioCalculationInstance::get(instance) }
5911 .and_then(CalculationInstanceInner::dc_opf)
5912 .map_or(PioStringView::EMPTY, |instance| {
5913 PioStringView::new(branch_susceptance_formula_name(
5914 instance.branch_susceptance_formula(),
5915 ))
5916 })
5917}
5918
5919fn objective(instance: &CalculationInstanceInner) -> Option<&powerio_prob::Objective> {
5920 match instance.instance()? {
5921 CalculationInstanceRef::DcOpf(instance) => Some(instance.objective()),
5922 CalculationInstanceRef::AcOpf(instance) => Some(instance.objective()),
5923 CalculationInstanceRef::McAcOpf(instance) => Some(instance.objective()),
5924 _ => None,
5925 }
5926}
5927
5928fn objective_term_name(term: &powerio_prob::ObjectiveTerm) -> &'static str {
5929 match term {
5930 powerio_prob::ObjectiveTerm::NetworkGeneratorCost => "network_generator_cost",
5931 powerio_prob::ObjectiveTerm::ActivePowerDispatchCost => "active_power_dispatch_cost",
5932 _ => "unknown",
5933 }
5934}
5935
5936#[unsafe(no_mangle)]
5937pub unsafe extern "C" fn pio_calculation_instance_objective_term_count(
5938 instance: *const PioCalculationInstance,
5939) -> usize {
5940 unsafe { PioCalculationInstance::get(instance) }
5941 .and_then(objective)
5942 .map_or(0, |objective| objective.terms().len())
5943}
5944
5945#[unsafe(no_mangle)]
5947pub unsafe extern "C" fn pio_calculation_instance_objective_term_at(
5948 instance: *const PioCalculationInstance,
5949 index: usize,
5950 output: *mut PioObjectiveTermView,
5951 error: *mut *mut PioError,
5952) -> bool {
5953 unsafe {
5954 entry(error, false, || {
5955 let value = require_calculation_instance(instance)?;
5956 let objective = objective(value).ok_or_else(|| {
5957 boundary_error(
5958 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5959 "the calculation instance has no optimization objective",
5960 )
5961 })?;
5962 let term = objective.terms().get(index).ok_or_else(|| {
5963 boundary_error(
5964 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5965 format!("objective term index {index} is out of range"),
5966 )
5967 })?;
5968 *require_output(output, "output")? = PioObjectiveTermView {
5969 kind: PioStringView::new(objective_term_name(term)),
5970 };
5971 Ok(true)
5972 })
5973 }
5974}
5975
5976fn active_constraint(
5977 instance: &CalculationInstanceInner,
5978 index: usize,
5979) -> Option<(&'static str, &powerio_prob::ConstraintSelection)> {
5980 match instance.instance()? {
5981 CalculationInstanceRef::DcOpf(instance) => match index {
5982 0 => Some((
5983 "generator_capability",
5984 &instance.constraints().generator_capability,
5985 )),
5986 1 => Some(("voltage_bounds", &instance.constraints().voltage_bounds)),
5987 2 => Some(("thermal_limits", &instance.constraints().thermal_limits)),
5988 3 => Some(("angle_bounds", &instance.constraints().angle_bounds)),
5989 _ => None,
5990 },
5991 CalculationInstanceRef::AcOpf(instance) => match index {
5992 0 => Some((
5993 "generator_capability",
5994 &instance.constraints().generator_capability,
5995 )),
5996 1 => Some(("voltage_bounds", &instance.constraints().voltage_bounds)),
5997 2 => Some(("thermal_limits", &instance.constraints().thermal_limits)),
5998 3 => Some(("angle_bounds", &instance.constraints().angle_bounds)),
5999 _ => None,
6000 },
6001 CalculationInstanceRef::McAcOpf(instance) => match index {
6002 0 => Some((
6003 "terminal_voltage_bounds",
6004 &instance.constraints().terminal_voltage_bounds,
6005 )),
6006 1 => Some(("conductor_limits", &instance.constraints().conductor_limits)),
6007 2 => Some((
6008 "generator_capability",
6009 &instance.constraints().generator_capability,
6010 )),
6011 _ => None,
6012 },
6013 _ => None,
6014 }
6015}
6016
6017fn constraint_selection_parts(
6018 selection: &powerio_prob::ConstraintSelection,
6019) -> (&'static str, &[String]) {
6020 match selection {
6021 powerio_prob::ConstraintSelection::All => ("all", &[]),
6022 powerio_prob::ConstraintSelection::None => ("none", &[]),
6023 powerio_prob::ConstraintSelection::Only(identities) => ("only", identities),
6024 _ => ("unknown", &[]),
6025 }
6026}
6027
6028#[unsafe(no_mangle)]
6029pub unsafe extern "C" fn pio_calculation_instance_active_constraint_count(
6030 instance: *const PioCalculationInstance,
6031) -> usize {
6032 match unsafe { PioCalculationInstance::get(instance) }
6033 .and_then(CalculationInstanceInner::instance)
6034 {
6035 Some(CalculationInstanceRef::DcOpf(_) | CalculationInstanceRef::AcOpf(_)) => 4,
6036 Some(CalculationInstanceRef::McAcOpf(_)) => 3,
6037 _ => 0,
6038 }
6039}
6040
6041#[unsafe(no_mangle)]
6043pub unsafe extern "C" fn pio_calculation_instance_active_constraint_at(
6044 instance: *const PioCalculationInstance,
6045 index: usize,
6046 output: *mut PioActiveConstraintView,
6047 error: *mut *mut PioError,
6048) -> bool {
6049 unsafe {
6050 entry(error, false, || {
6051 let instance = require_calculation_instance(instance)?;
6052 let (family, selection) = active_constraint(instance, index).ok_or_else(|| {
6053 boundary_error(
6054 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6055 format!("active constraint index {index} is out of range"),
6056 )
6057 })?;
6058 let (selection, identities) = constraint_selection_parts(selection);
6059 *require_output(output, "output")? = PioActiveConstraintView {
6060 family: PioStringView::new(family),
6061 selection: PioStringView::new(selection),
6062 identity_count: identities.len(),
6063 };
6064 Ok(true)
6065 })
6066 }
6067}
6068
6069#[unsafe(no_mangle)]
6071pub unsafe extern "C" fn pio_calculation_instance_active_constraint_identity_at(
6072 instance: *const PioCalculationInstance,
6073 constraint_index: usize,
6074 identity_index: usize,
6075 error: *mut *mut PioError,
6076) -> PioStringView {
6077 unsafe {
6078 entry(error, PioStringView::EMPTY, || {
6079 let instance = require_calculation_instance(instance)?;
6080 let (_, selection) =
6081 active_constraint(instance, constraint_index).ok_or_else(|| {
6082 boundary_error(
6083 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6084 format!("active constraint index {constraint_index} is out of range"),
6085 )
6086 })?;
6087 let (_, identities) = constraint_selection_parts(selection);
6088 let identity = identities.get(identity_index).ok_or_else(|| {
6089 boundary_error(
6090 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6091 format!("constraint identity index {identity_index} is out of range"),
6092 )
6093 })?;
6094 Ok(PioStringView::new(identity))
6095 })
6096 }
6097}
6098
6099#[unsafe(no_mangle)]
6100pub unsafe extern "C" fn pio_calculation_instance_has_initial_point(
6101 instance: *const PioCalculationInstance,
6102) -> bool {
6103 unsafe { PioCalculationInstance::get(instance) }
6104 .is_some_and(CalculationInstanceInner::has_initial_point)
6105}
6106
6107#[unsafe(no_mangle)]
6110pub unsafe extern "C" fn pio_calculation_instance_initial_point(
6111 instance: *const PioCalculationInstance,
6112 error: *mut *mut PioError,
6113) -> *mut PioOperatingPoint {
6114 unsafe {
6115 entry(error, std::ptr::null_mut(), || {
6116 let instance = PioCalculationInstance::get(instance).ok_or_else(|| {
6117 boundary_error(
6118 &codes::BIND_CAPI_NULL_HANDLE,
6119 "PioCalculationInstance must not be NULL",
6120 )
6121 })?;
6122 let has_initial_point = instance.has_initial_point();
6123 let projection = if has_initial_point {
6124 Some(match (instance.projection, instance.instance()) {
6125 (
6126 CalculationInstanceProjection::Direct,
6127 Some(CalculationInstanceRef::DcPf(_)),
6128 ) => OperatingPointProjection::DcPfInitial,
6129 (
6130 CalculationInstanceProjection::Direct,
6131 Some(CalculationInstanceRef::AcPf(_)),
6132 ) => OperatingPointProjection::AcPfInitial,
6133 (
6134 CalculationInstanceProjection::Direct,
6135 Some(CalculationInstanceRef::DcOpf(_)),
6136 ) => OperatingPointProjection::DcOpfInitial,
6137 (
6138 CalculationInstanceProjection::Direct,
6139 Some(CalculationInstanceRef::AcOpf(_)),
6140 ) => OperatingPointProjection::AcOpfInitial,
6141 (
6142 CalculationInstanceProjection::Direct,
6143 Some(CalculationInstanceRef::McAcPf(_)),
6144 ) => OperatingPointProjection::McAcPfInitial,
6145 (
6146 CalculationInstanceProjection::Direct,
6147 Some(CalculationInstanceRef::McAcOpf(_)),
6148 ) => OperatingPointProjection::McAcOpfInitial,
6149 (CalculationInstanceProjection::DcPfSolution, _) => {
6150 OperatingPointProjection::DcPfSolutionInitial
6151 }
6152 (CalculationInstanceProjection::AcPfSolution, _) => {
6153 OperatingPointProjection::AcPfSolutionInitial
6154 }
6155 (CalculationInstanceProjection::DcOpfSolution, _) => {
6156 OperatingPointProjection::DcOpfSolutionInitial
6157 }
6158 (CalculationInstanceProjection::AcOpfSolution, _) => {
6159 OperatingPointProjection::AcOpfSolutionInitial
6160 }
6161 (CalculationInstanceProjection::SocwrOpfSolution, _) => {
6162 OperatingPointProjection::SocwrOpfSolutionInitial
6163 }
6164 (CalculationInstanceProjection::McAcPfSolution, _) => {
6165 OperatingPointProjection::McAcPfSolutionInitial
6166 }
6167 (CalculationInstanceProjection::McAcOpfSolution, _) => {
6168 OperatingPointProjection::McAcOpfSolutionInitial
6169 }
6170 _ => {
6171 return Err(boundary_error(
6172 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6173 "the handle does not refer to a calculation instance with an operating point",
6174 ));
6175 }
6176 })
6177 } else {
6178 None
6179 };
6180 Ok(projection.map_or(std::ptr::null_mut(), |projection| {
6181 PioOperatingPoint::new_raw(OperatingPointInner {
6182 value: ValueInner {
6183 owner: Arc::clone(&instance.value.owner),
6184 steps: instance.value.steps.clone(),
6185 },
6186 projection,
6187 })
6188 }))
6189 })
6190 }
6191}
6192
6193fn dist_load_voltage_model_name(model: &powerio_dist::DistLoadVoltageModel) -> &'static str {
6194 match model {
6195 powerio_dist::DistLoadVoltageModel::ConstantPower { .. } => "constant_power",
6196 powerio_dist::DistLoadVoltageModel::ConstantCurrent { .. } => "constant_current",
6197 powerio_dist::DistLoadVoltageModel::ConstantImpedance { .. } => "constant_impedance",
6198 powerio_dist::DistLoadVoltageModel::Zip { .. } => "zip",
6199 powerio_dist::DistLoadVoltageModel::Exponential { .. } => "exponential",
6200 _ => "unknown",
6201 }
6202}
6203
6204#[unsafe(no_mangle)]
6205pub unsafe extern "C" fn pio_mc_ac_pf_instance_load_count(
6206 instance: *const PioCalculationInstance,
6207) -> usize {
6208 unsafe { PioCalculationInstance::get(instance) }
6209 .and_then(CalculationInstanceInner::mc_ac_pf)
6210 .map_or(0, |instance| instance.loads().len())
6211}
6212
6213#[unsafe(no_mangle)]
6214pub unsafe extern "C" fn pio_mc_ac_pf_instance_load_at(
6215 instance: *const PioCalculationInstance,
6216 index: usize,
6217 output: *mut PioPrescribedTerminalPowerView,
6218 error: *mut *mut PioError,
6219) -> bool {
6220 unsafe {
6221 entry(error, false, || {
6222 let instance = require_calculation_instance(instance)?
6223 .mc_ac_pf()
6224 .ok_or_else(|| {
6225 boundary_error(
6226 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6227 "the calculation instance is not powerio.McAcPfInstance",
6228 )
6229 })?;
6230 let load = instance.loads().get(index).ok_or_else(|| {
6231 boundary_error(
6232 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6233 format!("prescribed load index {index} is out of range"),
6234 )
6235 })?;
6236 *require_output(output, "output")? = PioPrescribedTerminalPowerView {
6237 load: PioStringView::new(&load.load),
6238 terminal_count: load.terminals.len(),
6239 voltage_model: PioStringView::new(dist_load_voltage_model_name(
6240 &load.voltage_model,
6241 )),
6242 };
6243 Ok(true)
6244 })
6245 }
6246}
6247
6248fn model_value(values: &[f64], index: usize) -> (f64, bool) {
6249 values
6250 .get(index)
6251 .copied()
6252 .map_or((0.0, false), |value| (value, true))
6253}
6254
6255#[unsafe(no_mangle)]
6256pub unsafe extern "C" fn pio_mc_ac_pf_instance_load_terminal_at(
6257 instance: *const PioCalculationInstance,
6258 load_index: usize,
6259 terminal_index: usize,
6260 output: *mut PioTerminalPowerView,
6261 error: *mut *mut PioError,
6262) -> bool {
6263 unsafe {
6264 entry(error, false, || {
6265 let instance = require_calculation_instance(instance)?
6266 .mc_ac_pf()
6267 .ok_or_else(|| {
6268 boundary_error(
6269 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6270 "the calculation instance is not powerio.McAcPfInstance",
6271 )
6272 })?;
6273 let load = instance.loads().get(load_index).ok_or_else(|| {
6274 boundary_error(
6275 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6276 format!("prescribed load index {load_index} is out of range"),
6277 )
6278 })?;
6279 let terminal = load.terminals.get(terminal_index).ok_or_else(|| {
6280 boundary_error(
6281 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6282 format!("load terminal index {terminal_index} is out of range"),
6283 )
6284 })?;
6285 let p = *load.p_w.get(terminal_index).ok_or_else(|| {
6286 boundary_error(
6287 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6288 "the prescribed load active power does not align with its terminals",
6289 )
6290 })?;
6291 let q = *load.q_var.get(terminal_index).ok_or_else(|| {
6292 boundary_error(
6293 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6294 "the prescribed load reactive power does not align with its terminals",
6295 )
6296 })?;
6297 let mut model_values = (0.0, false, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
6298 match &load.voltage_model {
6299 powerio_dist::DistLoadVoltageModel::ConstantPower { v_nom } => {
6300 let (v, has_v) = model_value(v_nom, terminal_index);
6301 model_values.0 = v;
6302 model_values.1 = has_v;
6303 }
6304 powerio_dist::DistLoadVoltageModel::ConstantCurrent { v_nom } => {
6305 let (v, has_v) = model_value(v_nom, terminal_index);
6306 model_values = (v, has_v, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0);
6307 }
6308 powerio_dist::DistLoadVoltageModel::ConstantImpedance { v_nom } => {
6309 let (v, has_v) = model_value(v_nom, terminal_index);
6310 model_values = (v, has_v, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0);
6311 }
6312 powerio_dist::DistLoadVoltageModel::Zip {
6313 v_nom,
6314 alpha_z,
6315 alpha_i,
6316 alpha_p,
6317 beta_z,
6318 beta_i,
6319 beta_p,
6320 } => {
6321 let (v, has_v) = model_value(v_nom, terminal_index);
6322 model_values = (
6323 v,
6324 has_v,
6325 alpha_z.get(terminal_index).copied().unwrap_or(0.0),
6326 alpha_i.get(terminal_index).copied().unwrap_or(0.0),
6327 alpha_p.get(terminal_index).copied().unwrap_or(0.0),
6328 beta_z.get(terminal_index).copied().unwrap_or(0.0),
6329 beta_i.get(terminal_index).copied().unwrap_or(0.0),
6330 beta_p.get(terminal_index).copied().unwrap_or(0.0),
6331 0.0,
6332 0.0,
6333 );
6334 }
6335 powerio_dist::DistLoadVoltageModel::Exponential {
6336 v_nom,
6337 gamma_p,
6338 gamma_q,
6339 } => {
6340 let (v, has_v) = model_value(v_nom, terminal_index);
6341 model_values = (
6342 v,
6343 has_v,
6344 0.0,
6345 0.0,
6346 0.0,
6347 0.0,
6348 0.0,
6349 0.0,
6350 gamma_p.get(terminal_index).copied().unwrap_or(0.0),
6351 gamma_q.get(terminal_index).copied().unwrap_or(0.0),
6352 );
6353 }
6354 _ => {}
6355 }
6356 *require_output(output, "output")? = PioTerminalPowerView {
6357 terminal: PioStringView::new(terminal),
6358 active_power_w: p,
6359 reactive_power_var: q,
6360 nominal_voltage_v: model_values.0,
6361 has_nominal_voltage: model_values.1,
6362 active_impedance_fraction: model_values.2,
6363 active_current_fraction: model_values.3,
6364 active_power_fraction: model_values.4,
6365 reactive_impedance_fraction: model_values.5,
6366 reactive_current_fraction: model_values.6,
6367 reactive_power_fraction: model_values.7,
6368 active_power_exponent: model_values.8,
6369 reactive_power_exponent: model_values.9,
6370 };
6371 Ok(true)
6372 })
6373 }
6374}
6375
6376#[unsafe(no_mangle)]
6377pub unsafe extern "C" fn pio_mc_ac_pf_instance_source_count(
6378 instance: *const PioCalculationInstance,
6379) -> usize {
6380 unsafe { PioCalculationInstance::get(instance) }
6381 .and_then(CalculationInstanceInner::mc_ac_pf)
6382 .map_or(0, |instance| instance.sources().len())
6383}
6384
6385#[unsafe(no_mangle)]
6386pub unsafe extern "C" fn pio_mc_ac_pf_instance_source_at(
6387 instance: *const PioCalculationInstance,
6388 index: usize,
6389 output: *mut PioPrescribedSourceVoltageView,
6390 error: *mut *mut PioError,
6391) -> bool {
6392 unsafe {
6393 entry(error, false, || {
6394 let instance = require_calculation_instance(instance)?
6395 .mc_ac_pf()
6396 .ok_or_else(|| {
6397 boundary_error(
6398 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6399 "the calculation instance is not powerio.McAcPfInstance",
6400 )
6401 })?;
6402 let source = instance.sources().get(index).ok_or_else(|| {
6403 boundary_error(
6404 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6405 format!("prescribed source index {index} is out of range"),
6406 )
6407 })?;
6408 *require_output(output, "output")? = PioPrescribedSourceVoltageView {
6409 source: PioStringView::new(&source.source),
6410 terminal_count: source.terminals.len(),
6411 };
6412 Ok(true)
6413 })
6414 }
6415}
6416
6417#[unsafe(no_mangle)]
6418pub unsafe extern "C" fn pio_mc_ac_pf_instance_source_terminal_at(
6419 instance: *const PioCalculationInstance,
6420 source_index: usize,
6421 terminal_index: usize,
6422 output: *mut PioTerminalVoltageView,
6423 error: *mut *mut PioError,
6424) -> bool {
6425 unsafe {
6426 entry(error, false, || {
6427 let instance = require_calculation_instance(instance)?
6428 .mc_ac_pf()
6429 .ok_or_else(|| {
6430 boundary_error(
6431 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6432 "the calculation instance is not powerio.McAcPfInstance",
6433 )
6434 })?;
6435 let source = instance.sources().get(source_index).ok_or_else(|| {
6436 boundary_error(
6437 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6438 format!("prescribed source index {source_index} is out of range"),
6439 )
6440 })?;
6441 let terminal = source.terminals.get(terminal_index).ok_or_else(|| {
6442 boundary_error(
6443 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6444 format!("source terminal index {terminal_index} is out of range"),
6445 )
6446 })?;
6447 let magnitude = source.v_magnitude.get(terminal_index).ok_or_else(|| {
6448 boundary_error(
6449 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6450 "the prescribed source magnitudes do not align with its terminals",
6451 )
6452 })?;
6453 let angle = source.v_angle.get(terminal_index).ok_or_else(|| {
6454 boundary_error(
6455 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6456 "the prescribed source angles do not align with its terminals",
6457 )
6458 })?;
6459 *require_output(output, "output")? = PioTerminalVoltageView {
6460 terminal: PioStringView::new(terminal),
6461 magnitude_v: *magnitude,
6462 angle_radians: *angle,
6463 };
6464 Ok(true)
6465 })
6466 }
6467}
6468
6469#[unsafe(no_mangle)]
6470pub unsafe extern "C" fn pio_mc_ac_pf_instance_isolated_terminal_count(
6471 instance: *const PioCalculationInstance,
6472) -> usize {
6473 unsafe { PioCalculationInstance::get(instance) }
6474 .and_then(CalculationInstanceInner::mc_ac_pf)
6475 .map_or(0, |instance| instance.isolated_terminals().len())
6476}
6477
6478#[unsafe(no_mangle)]
6479pub unsafe extern "C" fn pio_mc_ac_pf_instance_isolated_terminal_at(
6480 instance: *const PioCalculationInstance,
6481 index: usize,
6482 output: *mut PioIsolatedTerminalView,
6483 error: *mut *mut PioError,
6484) -> bool {
6485 unsafe {
6486 entry(error, false, || {
6487 let instance = require_calculation_instance(instance)?
6488 .mc_ac_pf()
6489 .ok_or_else(|| {
6490 boundary_error(
6491 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6492 "the calculation instance is not powerio.McAcPfInstance",
6493 )
6494 })?;
6495 let (bus, terminal) = instance.isolated_terminals().get(index).ok_or_else(|| {
6496 boundary_error(
6497 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6498 format!("isolated terminal index {index} is out of range"),
6499 )
6500 })?;
6501 *require_output(output, "output")? = PioIsolatedTerminalView {
6502 bus: PioStringView::new(bus),
6503 terminal: PioStringView::new(terminal),
6504 };
6505 Ok(true)
6506 })
6507 }
6508}
6509
6510#[unsafe(no_mangle)]
6511pub unsafe extern "C" fn pio_mc_ac_pf_instance_active_control_count(
6512 instance: *const PioCalculationInstance,
6513) -> usize {
6514 unsafe { PioCalculationInstance::get(instance) }
6515 .and_then(CalculationInstanceInner::mc_ac_pf)
6516 .map_or(0, |instance| instance.control_modes().len())
6517}
6518
6519#[unsafe(no_mangle)]
6520pub unsafe extern "C" fn pio_mc_ac_pf_instance_active_control_at(
6521 instance: *const PioCalculationInstance,
6522 index: usize,
6523 output: *mut PioActiveControlView,
6524 error: *mut *mut PioError,
6525) -> bool {
6526 unsafe {
6527 entry(error, false, || {
6528 let instance = require_calculation_instance(instance)?
6529 .mc_ac_pf()
6530 .ok_or_else(|| {
6531 boundary_error(
6532 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6533 "the calculation instance is not powerio.McAcPfInstance",
6534 )
6535 })?;
6536 let control = instance.control_modes().get(index).ok_or_else(|| {
6537 boundary_error(
6538 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6539 format!("active control index {index} is out of range"),
6540 )
6541 })?;
6542 let (kind, component_id) = match control {
6543 powerio_prob::ActiveControlMode::RegulatorTap { transformer } => {
6544 ("regulator_tap", transformer.as_str())
6545 }
6546 powerio_prob::ActiveControlMode::CapacitorSteps { capacitor } => {
6547 ("capacitor_steps", capacitor.as_str())
6548 }
6549 _ => ("unknown", ""),
6550 };
6551 *require_output(output, "output")? = PioActiveControlView {
6552 kind: PioStringView::new(kind),
6553 component_id: PioStringView::new(component_id),
6554 };
6555 Ok(true)
6556 })
6557 }
6558}
6559
6560fn scuc_inputs(instance: &CalculationInstanceInner) -> Option<&powerio_prob::ScucInputs> {
6561 instance.ac_scuc().map(powerio_prob::AcScucInstance::inputs)
6562}
6563
6564fn scuc_inputs_or_error(
6565 instance: &CalculationInstanceInner,
6566) -> Result<&powerio_prob::ScucInputs, *mut PioError> {
6567 scuc_inputs(instance).ok_or_else(|| {
6568 boundary_error(
6569 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6570 "the calculation instance is not powerio.AcScucInstance",
6571 )
6572 })
6573}
6574
6575fn component_id_view(id: &ComponentId) -> PioComponentIdView {
6576 PioComponentIdView {
6577 component_type: PioStringView::new(id.component_type()),
6578 local_id: PioStringView::new(id.local_id()),
6579 }
6580}
6581
6582fn empty_component_id_view() -> PioComponentIdView {
6583 PioComponentIdView {
6584 component_type: PioStringView::EMPTY,
6585 local_id: PioStringView::EMPTY,
6586 }
6587}
6588
6589fn optional_component_id_view(value: Option<&ComponentId>) -> (PioComponentIdView, bool) {
6590 value.map_or((empty_component_id_view(), false), |value| {
6591 (component_id_view(value), true)
6592 })
6593}
6594
6595fn terminal_reference_view(
6596 value: Option<&powerio_tx::TerminalReference>,
6597) -> (PioTerminalReferenceView, bool) {
6598 value.map_or(
6599 (
6600 PioTerminalReferenceView {
6601 equipment: empty_component_id_view(),
6602 terminal: 0,
6603 },
6604 false,
6605 ),
6606 |value| {
6607 (
6608 PioTerminalReferenceView {
6609 equipment: component_id_view(&value.equipment),
6610 terminal: value.terminal,
6611 },
6612 true,
6613 )
6614 },
6615 )
6616}
6617
6618fn transformer_control_mode_name(mode: powerio_tx::TransformerControlMode) -> &'static str {
6619 match mode {
6620 powerio_tx::TransformerControlMode::Fixed => "fixed",
6621 powerio_tx::TransformerControlMode::Voltage => "voltage",
6622 powerio_tx::TransformerControlMode::ReactiveFlow => "reactive_flow",
6623 powerio_tx::TransformerControlMode::ActiveFlow => "active_flow",
6624 powerio_tx::TransformerControlMode::DcLineQuantity => "dc_line_quantity",
6625 powerio_tx::TransformerControlMode::AsymmetricActiveFlow => "asymmetric_active_flow",
6626 _ => "unknown",
6627 }
6628}
6629
6630fn transformer_control_view(
6631 value: Option<&powerio_tx::TransformerControl>,
6632) -> (PioTransformerControlView, bool) {
6633 let empty_terminal = terminal_reference_view(None).0;
6634 value.map_or(
6635 (
6636 PioTransformerControlView {
6637 mode: PioStringView::EMPTY,
6638 enabled: false,
6639 controlled_bus_id: 0,
6640 has_controlled_bus: false,
6641 controlled_bus_on_winding_side: false,
6642 regulating_terminal: empty_terminal,
6643 has_regulating_terminal: false,
6644 tap_min: 0.0,
6645 tap_max: 0.0,
6646 band_min: 0.0,
6647 band_max: 0.0,
6648 tap_position_count: 0,
6649 mva_base: 0.0,
6650 winding_connection_angle: 0.0,
6651 has_winding_connection_angle: false,
6652 },
6653 false,
6654 ),
6655 |control| {
6656 let (regulating_terminal, has_regulating_terminal) =
6657 terminal_reference_view(control.regulating_terminal.as_ref());
6658 (
6659 PioTransformerControlView {
6660 mode: PioStringView::new(transformer_control_mode_name(control.mode)),
6661 enabled: control.enabled,
6662 controlled_bus_id: control.controlled_bus.map_or(0, |bus| bus.0),
6663 has_controlled_bus: control.controlled_bus.is_some(),
6664 controlled_bus_on_winding_side: control.controlled_bus_on_winding_side,
6665 regulating_terminal,
6666 has_regulating_terminal,
6667 tap_min: control.tap_min,
6668 tap_max: control.tap_max,
6669 band_min: control.band_min,
6670 band_max: control.band_max,
6671 tap_position_count: control.ntp,
6672 mva_base: control.mva_base,
6673 winding_connection_angle: control.winding_connection_angle.unwrap_or(0.0),
6674 has_winding_connection_angle: control.winding_connection_angle.is_some(),
6675 },
6676 true,
6677 )
6678 },
6679 )
6680}
6681
6682fn scuc_device_kind_name(kind: powerio_prob::ScucDeviceKind) -> &'static str {
6683 match kind {
6684 powerio_prob::ScucDeviceKind::Producer => "producer",
6685 powerio_prob::ScucDeviceKind::Consumer => "consumer",
6686 _ => "unknown",
6687 }
6688}
6689
6690fn scuc_reactive_capability_view(
6691 capability: &powerio_prob::ScucReactiveCapability,
6692) -> PioScucReactiveCapabilityView {
6693 match capability {
6694 powerio_prob::ScucReactiveCapability::None => PioScucReactiveCapabilityView {
6695 kind: PioStringView::new("none"),
6696 reactive_power_at_zero_active_power_pu: 0.0,
6697 reactive_power_at_zero_active_power_min_pu: 0.0,
6698 reactive_power_at_zero_active_power_max_pu: 0.0,
6699 slope: 0.0,
6700 slope_min: 0.0,
6701 slope_max: 0.0,
6702 },
6703 powerio_prob::ScucReactiveCapability::Linear {
6704 reactive_power_at_zero_active_power,
6705 slope,
6706 } => PioScucReactiveCapabilityView {
6707 kind: PioStringView::new("linear"),
6708 reactive_power_at_zero_active_power_pu: *reactive_power_at_zero_active_power,
6709 reactive_power_at_zero_active_power_min_pu: 0.0,
6710 reactive_power_at_zero_active_power_max_pu: 0.0,
6711 slope: *slope,
6712 slope_min: 0.0,
6713 slope_max: 0.0,
6714 },
6715 powerio_prob::ScucReactiveCapability::Bounded {
6716 reactive_power_at_zero_active_power_min,
6717 reactive_power_at_zero_active_power_max,
6718 slope_min,
6719 slope_max,
6720 } => PioScucReactiveCapabilityView {
6721 kind: PioStringView::new("bounded"),
6722 reactive_power_at_zero_active_power_pu: 0.0,
6723 reactive_power_at_zero_active_power_min_pu: *reactive_power_at_zero_active_power_min,
6724 reactive_power_at_zero_active_power_max_pu: *reactive_power_at_zero_active_power_max,
6725 slope: 0.0,
6726 slope_min: *slope_min,
6727 slope_max: *slope_max,
6728 },
6729 _ => PioScucReactiveCapabilityView {
6730 kind: PioStringView::new("unknown"),
6731 reactive_power_at_zero_active_power_pu: 0.0,
6732 reactive_power_at_zero_active_power_min_pu: 0.0,
6733 reactive_power_at_zero_active_power_max_pu: 0.0,
6734 slope: 0.0,
6735 slope_min: 0.0,
6736 slope_max: 0.0,
6737 },
6738 }
6739}
6740
6741fn scuc_reserve_costs_view(costs: &powerio_prob::ScucReserveCosts) -> PioScucReserveCostsView {
6742 PioScucReserveCostsView {
6743 regulation_up: costs.regulation_up,
6744 regulation_down: costs.regulation_down,
6745 synchronized: costs.synchronized,
6746 nonsynchronized: costs.nonsynchronized,
6747 ramping_up_online: costs.ramping_up_online,
6748 ramping_down_online: costs.ramping_down_online,
6749 ramping_up_offline: costs.ramping_up_offline,
6750 ramping_down_offline: costs.ramping_down_offline,
6751 reactive_up: costs.reactive_up,
6752 reactive_down: costs.reactive_down,
6753 }
6754}
6755
6756fn scuc_device_view(device: &powerio_prob::ScucDevice) -> PioScucDeviceView {
6757 PioScucDeviceView {
6758 id: component_id_view(&device.id),
6759 kind: PioStringView::new(scuc_device_kind_name(device.kind)),
6760 initial_on_status: device.initial_on_status,
6761 on_cost: device.on_cost,
6762 startup_cost: device.startup_cost,
6763 shutdown_cost: device.shutdown_cost,
6764 minimum_up_time_hours: device.minimum_up_time,
6765 minimum_down_time_hours: device.minimum_down_time,
6766 ramp_limits: PioScucRampLimitsView {
6767 up_pu_per_hour: device.ramp_limits.up,
6768 down_pu_per_hour: device.ramp_limits.down,
6769 startup_pu_per_hour: device.ramp_limits.startup,
6770 shutdown_pu_per_hour: device.ramp_limits.shutdown,
6771 },
6772 reserve_limits: PioScucReserveLimitsView {
6773 regulation_up_pu: device.reserve_limits.regulation_up,
6774 regulation_down_pu: device.reserve_limits.regulation_down,
6775 synchronized_pu: device.reserve_limits.synchronized,
6776 nonsynchronized_pu: device.reserve_limits.nonsynchronized,
6777 ramping_up_online_pu: device.reserve_limits.ramping_up_online,
6778 ramping_down_online_pu: device.reserve_limits.ramping_down_online,
6779 ramping_up_offline_pu: device.reserve_limits.ramping_up_offline,
6780 ramping_down_offline_pu: device.reserve_limits.ramping_down_offline,
6781 },
6782 initial_commitment: PioScucInitialCommitmentView {
6783 accumulated_up_time_hours: device.initial_commitment.accumulated_up_time,
6784 accumulated_down_time_hours: device.initial_commitment.accumulated_down_time,
6785 },
6786 reactive_capability: scuc_reactive_capability_view(&device.reactive_capability),
6787 period_count: device.periods.len(),
6788 startup_cost_adjustment_count: device.startup_cost_adjustments.len(),
6789 startup_limit_count: device.startup_limits.len(),
6790 energy_upper_bound_count: device.energy_upper_bounds.len(),
6791 energy_lower_bound_count: device.energy_lower_bounds.len(),
6792 }
6793}
6794
6795fn scuc_device_or_error(
6796 inputs: &powerio_prob::ScucInputs,
6797 device_index: usize,
6798) -> Result<&powerio_prob::ScucDevice, *mut PioError> {
6799 inputs.devices.get(device_index).ok_or_else(|| {
6800 boundary_error(
6801 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6802 format!("SCUC device index {device_index} is out of range"),
6803 )
6804 })
6805}
6806
6807#[unsafe(no_mangle)]
6809pub unsafe extern "C" fn pio_ac_scuc_instance_dimensions(
6810 instance: *const PioCalculationInstance,
6811 output: *mut PioScucDimensionsView,
6812 error: *mut *mut PioError,
6813) -> bool {
6814 unsafe {
6815 entry(error, false, || {
6816 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
6817 *require_output(output, "output")? = PioScucDimensionsView {
6818 period_count: inputs.interval_durations.len(),
6819 device_count: inputs.devices.len(),
6820 producer_count: inputs.producers().count(),
6821 consumer_count: inputs.consumers().count(),
6822 shunt_count: inputs.shunts.len(),
6823 branch_switching_cost_count: inputs.branch_switching_costs.len(),
6824 transformer_control_count: inputs.transformer_controls.len(),
6825 active_reserve_zone_count: inputs.active_reserve_zones.len(),
6826 reactive_reserve_zone_count: inputs.reactive_reserve_zones.len(),
6827 contingency_count: inputs.contingencies.len(),
6828 };
6829 Ok(true)
6830 })
6831 }
6832}
6833
6834#[unsafe(no_mangle)]
6836pub unsafe extern "C" fn pio_ac_scuc_instance_interval_durations(
6837 instance: *const PioCalculationInstance,
6838) -> PioF64View {
6839 unsafe { PioCalculationInstance::get(instance) }
6840 .and_then(scuc_inputs)
6841 .map_or(PioF64View::EMPTY, |inputs| {
6842 PioF64View::new(&inputs.interval_durations)
6843 })
6844}
6845
6846#[unsafe(no_mangle)]
6848pub unsafe extern "C" fn pio_ac_scuc_instance_violation_costs(
6849 instance: *const PioCalculationInstance,
6850 output: *mut PioScucViolationCostView,
6851 error: *mut *mut PioError,
6852) -> bool {
6853 unsafe {
6854 entry(error, false, || {
6855 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
6856 let costs = inputs.violation_costs;
6857 *require_output(output, "output")? = PioScucViolationCostView {
6858 active_power_balance: costs.active_power_balance,
6859 reactive_power_balance: costs.reactive_power_balance,
6860 branch_thermal_limit: costs.branch_thermal_limit,
6861 energy_requirement: costs.energy_requirement,
6862 };
6863 Ok(true)
6864 })
6865 }
6866}
6867
6868#[unsafe(no_mangle)]
6869pub unsafe extern "C" fn pio_ac_scuc_instance_device_count(
6870 instance: *const PioCalculationInstance,
6871) -> usize {
6872 unsafe { PioCalculationInstance::get(instance) }
6873 .and_then(scuc_inputs)
6874 .map_or(0, |inputs| inputs.devices.len())
6875}
6876
6877#[unsafe(no_mangle)]
6878pub unsafe extern "C" fn pio_ac_scuc_instance_device_at(
6879 instance: *const PioCalculationInstance,
6880 index: usize,
6881 output: *mut PioScucDeviceView,
6882 error: *mut *mut PioError,
6883) -> bool {
6884 unsafe {
6885 entry(error, false, || {
6886 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
6887 let device = scuc_device_or_error(inputs, index)?;
6888 *require_output(output, "output")? = scuc_device_view(device);
6889 Ok(true)
6890 })
6891 }
6892}
6893
6894#[unsafe(no_mangle)]
6896pub unsafe extern "C" fn pio_ac_scuc_instance_device_get(
6897 instance: *const PioCalculationInstance,
6898 uid: *const c_char,
6899 uid_len: usize,
6900 output: *mut PioScucDeviceView,
6901 error: *mut *mut PioError,
6902) -> bool {
6903 unsafe {
6904 entry(error, false, || {
6905 let uid = required_str(uid, uid_len, "device_uid")?;
6906 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
6907 let device = inputs.device(uid).ok_or_else(|| {
6908 boundary_error(
6909 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6910 format!("SCUC device UID '{uid}' does not exist"),
6911 )
6912 })?;
6913 *require_output(output, "output")? = scuc_device_view(device);
6914 Ok(true)
6915 })
6916 }
6917}
6918
6919#[unsafe(no_mangle)]
6920pub unsafe extern "C" fn pio_ac_scuc_instance_device_startup_cost_adjustment_count(
6921 instance: *const PioCalculationInstance,
6922 device_index: usize,
6923) -> usize {
6924 unsafe { PioCalculationInstance::get(instance) }
6925 .and_then(scuc_inputs)
6926 .and_then(|inputs| inputs.devices.get(device_index))
6927 .map_or(0, |device| device.startup_cost_adjustments.len())
6928}
6929
6930#[unsafe(no_mangle)]
6931pub unsafe extern "C" fn pio_ac_scuc_instance_device_startup_cost_adjustment_at(
6932 instance: *const PioCalculationInstance,
6933 device_index: usize,
6934 adjustment_index: usize,
6935 output: *mut PioScucStartupCostAdjustmentView,
6936 error: *mut *mut PioError,
6937) -> bool {
6938 unsafe {
6939 entry(error, false, || {
6940 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
6941 let device = scuc_device_or_error(inputs, device_index)?;
6942 let adjustment = device
6943 .startup_cost_adjustments
6944 .get(adjustment_index)
6945 .ok_or_else(|| {
6946 boundary_error(
6947 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6948 format!(
6949 "SCUC device {device_index} startup cost adjustment index {adjustment_index} is out of range"
6950 ),
6951 )
6952 })?;
6953 *require_output(output, "output")? = PioScucStartupCostAdjustmentView {
6954 cost: adjustment.cost,
6955 maximum_down_time_hours: adjustment.maximum_down_time,
6956 };
6957 Ok(true)
6958 })
6959 }
6960}
6961
6962#[unsafe(no_mangle)]
6963pub unsafe extern "C" fn pio_ac_scuc_instance_device_startup_limit_count(
6964 instance: *const PioCalculationInstance,
6965 device_index: usize,
6966) -> usize {
6967 unsafe { PioCalculationInstance::get(instance) }
6968 .and_then(scuc_inputs)
6969 .and_then(|inputs| inputs.devices.get(device_index))
6970 .map_or(0, |device| device.startup_limits.len())
6971}
6972
6973#[unsafe(no_mangle)]
6974pub unsafe extern "C" fn pio_ac_scuc_instance_device_startup_limit_at(
6975 instance: *const PioCalculationInstance,
6976 device_index: usize,
6977 limit_index: usize,
6978 output: *mut PioScucStartupLimitView,
6979 error: *mut *mut PioError,
6980) -> bool {
6981 unsafe {
6982 entry(error, false, || {
6983 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
6984 let device = scuc_device_or_error(inputs, device_index)?;
6985 let limit = device.startup_limits.get(limit_index).ok_or_else(|| {
6986 boundary_error(
6987 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6988 format!(
6989 "SCUC device {device_index} startup limit index {limit_index} is out of range"
6990 ),
6991 )
6992 })?;
6993 *require_output(output, "output")? = PioScucStartupLimitView {
6994 start_time_hours: limit.start_time,
6995 end_time_hours: limit.end_time,
6996 maximum_startups: limit.maximum_startups,
6997 };
6998 Ok(true)
6999 })
7000 }
7001}
7002
7003unsafe fn scuc_energy_requirement_count(
7004 instance: *const PioCalculationInstance,
7005 device_index: usize,
7006 upper: bool,
7007) -> usize {
7008 unsafe { PioCalculationInstance::get(instance) }
7009 .and_then(scuc_inputs)
7010 .and_then(|inputs| inputs.devices.get(device_index))
7011 .map_or(0, |device| {
7012 if upper {
7013 device.energy_upper_bounds.len()
7014 } else {
7015 device.energy_lower_bounds.len()
7016 }
7017 })
7018}
7019
7020unsafe fn scuc_energy_requirement_at(
7021 instance: *const PioCalculationInstance,
7022 device_index: usize,
7023 requirement_index: usize,
7024 upper: bool,
7025 output: *mut PioScucEnergyRequirementView,
7026 error: *mut *mut PioError,
7027) -> bool {
7028 unsafe {
7029 entry(error, false, || {
7030 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7031 let device = scuc_device_or_error(inputs, device_index)?;
7032 let requirements = if upper {
7033 &device.energy_upper_bounds
7034 } else {
7035 &device.energy_lower_bounds
7036 };
7037 let requirement = requirements.get(requirement_index).ok_or_else(|| {
7038 boundary_error(
7039 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7040 format!(
7041 "SCUC device {device_index} {} energy requirement index {requirement_index} is out of range",
7042 if upper { "upper" } else { "lower" }
7043 ),
7044 )
7045 })?;
7046 *require_output(output, "output")? = PioScucEnergyRequirementView {
7047 start_time_hours: requirement.start_time,
7048 end_time_hours: requirement.end_time,
7049 energy_pu: requirement.energy,
7050 };
7051 Ok(true)
7052 })
7053 }
7054}
7055
7056#[unsafe(no_mangle)]
7057pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_upper_bound_count(
7058 instance: *const PioCalculationInstance,
7059 device_index: usize,
7060) -> usize {
7061 unsafe { scuc_energy_requirement_count(instance, device_index, true) }
7062}
7063
7064#[unsafe(no_mangle)]
7065pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_upper_bound_at(
7066 instance: *const PioCalculationInstance,
7067 device_index: usize,
7068 requirement_index: usize,
7069 output: *mut PioScucEnergyRequirementView,
7070 error: *mut *mut PioError,
7071) -> bool {
7072 unsafe {
7073 scuc_energy_requirement_at(
7074 instance,
7075 device_index,
7076 requirement_index,
7077 true,
7078 output,
7079 error,
7080 )
7081 }
7082}
7083
7084#[unsafe(no_mangle)]
7085pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_lower_bound_count(
7086 instance: *const PioCalculationInstance,
7087 device_index: usize,
7088) -> usize {
7089 unsafe { scuc_energy_requirement_count(instance, device_index, false) }
7090}
7091
7092#[unsafe(no_mangle)]
7093pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_lower_bound_at(
7094 instance: *const PioCalculationInstance,
7095 device_index: usize,
7096 requirement_index: usize,
7097 output: *mut PioScucEnergyRequirementView,
7098 error: *mut *mut PioError,
7099) -> bool {
7100 unsafe {
7101 scuc_energy_requirement_at(
7102 instance,
7103 device_index,
7104 requirement_index,
7105 false,
7106 output,
7107 error,
7108 )
7109 }
7110}
7111
7112#[unsafe(no_mangle)]
7113pub unsafe extern "C" fn pio_ac_scuc_instance_device_period_at(
7114 instance: *const PioCalculationInstance,
7115 device_index: usize,
7116 period_index: usize,
7117 output: *mut PioScucDevicePeriodView,
7118 error: *mut *mut PioError,
7119) -> bool {
7120 unsafe {
7121 entry(error, false, || {
7122 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7123 let device = scuc_device_or_error(inputs, device_index)?;
7124 let period = device.periods.get(period_index).ok_or_else(|| {
7125 boundary_error(
7126 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7127 format!(
7128 "SCUC device {device_index} period index {period_index} is out of range"
7129 ),
7130 )
7131 })?;
7132 *require_output(output, "output")? = PioScucDevicePeriodView {
7133 on_status_min: period.on_status_min,
7134 on_status_max: period.on_status_max,
7135 active_power_min_pu: period.active_power_min,
7136 active_power_max_pu: period.active_power_max,
7137 reactive_power_min_pu: period.reactive_power_min,
7138 reactive_power_max_pu: period.reactive_power_max,
7139 energy_cost_block_count: period.energy_cost_blocks.len(),
7140 reserve_costs: scuc_reserve_costs_view(&period.reserve_costs),
7141 };
7142 Ok(true)
7143 })
7144 }
7145}
7146
7147#[unsafe(no_mangle)]
7148pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_cost_block_count(
7149 instance: *const PioCalculationInstance,
7150 device_index: usize,
7151 period_index: usize,
7152) -> usize {
7153 unsafe { PioCalculationInstance::get(instance) }
7154 .and_then(scuc_inputs)
7155 .and_then(|inputs| inputs.devices.get(device_index))
7156 .and_then(|device| device.periods.get(period_index))
7157 .map_or(0, |period| period.energy_cost_blocks.len())
7158}
7159
7160#[unsafe(no_mangle)]
7161pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_cost_block_at(
7162 instance: *const PioCalculationInstance,
7163 device_index: usize,
7164 period_index: usize,
7165 block_index: usize,
7166 output: *mut PioScucEnergyCostBlockView,
7167 error: *mut *mut PioError,
7168) -> bool {
7169 unsafe {
7170 entry(error, false, || {
7171 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7172 let device = scuc_device_or_error(inputs, device_index)?;
7173 let period = device.periods.get(period_index).ok_or_else(|| {
7174 boundary_error(
7175 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7176 format!(
7177 "SCUC device {device_index} period index {period_index} is out of range"
7178 ),
7179 )
7180 })?;
7181 let block = period.energy_cost_blocks.get(block_index).ok_or_else(|| {
7182 boundary_error(
7183 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7184 format!(
7185 "SCUC device {device_index} period {period_index} energy cost block index {block_index} is out of range"
7186 ),
7187 )
7188 })?;
7189 *require_output(output, "output")? = PioScucEnergyCostBlockView {
7190 marginal_cost: block.marginal_cost,
7191 block_size_pu: block.block_size,
7192 };
7193 Ok(true)
7194 })
7195 }
7196}
7197
7198#[unsafe(no_mangle)]
7199pub unsafe extern "C" fn pio_ac_scuc_instance_shunt_count(
7200 instance: *const PioCalculationInstance,
7201) -> usize {
7202 unsafe { PioCalculationInstance::get(instance) }
7203 .and_then(scuc_inputs)
7204 .map_or(0, |inputs| inputs.shunts.len())
7205}
7206
7207fn scuc_shunt_view(shunt: &powerio_prob::ScucShunt) -> PioScucShuntView {
7208 PioScucShuntView {
7209 id: component_id_view(&shunt.id),
7210 conductance_per_step_pu: shunt.conductance_per_step,
7211 susceptance_per_step_pu: shunt.susceptance_per_step,
7212 step_min: shunt.step_min,
7213 step_max: shunt.step_max,
7214 initial_step: shunt.initial_step,
7215 }
7216}
7217
7218#[unsafe(no_mangle)]
7219pub unsafe extern "C" fn pio_ac_scuc_instance_shunt_at(
7220 instance: *const PioCalculationInstance,
7221 index: usize,
7222 output: *mut PioScucShuntView,
7223 error: *mut *mut PioError,
7224) -> bool {
7225 unsafe {
7226 entry(error, false, || {
7227 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7228 let shunt = inputs.shunts.get(index).ok_or_else(|| {
7229 boundary_error(
7230 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7231 format!("SCUC shunt index {index} is out of range"),
7232 )
7233 })?;
7234 *require_output(output, "output")? = scuc_shunt_view(shunt);
7235 Ok(true)
7236 })
7237 }
7238}
7239
7240#[unsafe(no_mangle)]
7242pub unsafe extern "C" fn pio_ac_scuc_instance_shunt_get(
7243 instance: *const PioCalculationInstance,
7244 uid: *const c_char,
7245 uid_len: usize,
7246 output: *mut PioScucShuntView,
7247 error: *mut *mut PioError,
7248) -> bool {
7249 unsafe {
7250 entry(error, false, || {
7251 let uid = required_str(uid, uid_len, "shunt_uid")?;
7252 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7253 let shunt = inputs.shunt(uid).ok_or_else(|| {
7254 boundary_error(
7255 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7256 format!("SCUC shunt UID '{uid}' does not exist"),
7257 )
7258 })?;
7259 *require_output(output, "output")? = scuc_shunt_view(shunt);
7260 Ok(true)
7261 })
7262 }
7263}
7264
7265#[unsafe(no_mangle)]
7266pub unsafe extern "C" fn pio_ac_scuc_instance_branch_switching_cost_count(
7267 instance: *const PioCalculationInstance,
7268) -> usize {
7269 unsafe { PioCalculationInstance::get(instance) }
7270 .and_then(scuc_inputs)
7271 .map_or(0, |inputs| inputs.branch_switching_costs.len())
7272}
7273
7274#[unsafe(no_mangle)]
7275pub unsafe extern "C" fn pio_ac_scuc_instance_branch_switching_cost_at(
7276 instance: *const PioCalculationInstance,
7277 index: usize,
7278 output: *mut PioScucBranchSwitchingCostView,
7279 error: *mut *mut PioError,
7280) -> bool {
7281 unsafe {
7282 entry(error, false, || {
7283 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7284 let cost = inputs.branch_switching_costs.get(index).ok_or_else(|| {
7285 boundary_error(
7286 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7287 format!("SCUC branch switching cost index {index} is out of range"),
7288 )
7289 })?;
7290 *require_output(output, "output")? = PioScucBranchSwitchingCostView {
7291 id: component_id_view(&cost.id),
7292 connection_cost: cost.connection_cost,
7293 disconnection_cost: cost.disconnection_cost,
7294 };
7295 Ok(true)
7296 })
7297 }
7298}
7299
7300#[unsafe(no_mangle)]
7301pub unsafe extern "C" fn pio_ac_scuc_instance_transformer_control_count(
7302 instance: *const PioCalculationInstance,
7303) -> usize {
7304 unsafe { PioCalculationInstance::get(instance) }
7305 .and_then(scuc_inputs)
7306 .map_or(0, |inputs| inputs.transformer_controls.len())
7307}
7308
7309#[unsafe(no_mangle)]
7310pub unsafe extern "C" fn pio_ac_scuc_instance_transformer_control_at(
7311 instance: *const PioCalculationInstance,
7312 index: usize,
7313 output: *mut PioScucTransformerControlView,
7314 error: *mut *mut PioError,
7315) -> bool {
7316 unsafe {
7317 entry(error, false, || {
7318 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7319 let control = inputs.transformer_controls.get(index).ok_or_else(|| {
7320 boundary_error(
7321 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7322 format!("SCUC transformer control index {index} is out of range"),
7323 )
7324 })?;
7325 *require_output(output, "output")? = PioScucTransformerControlView {
7326 id: component_id_view(&control.id),
7327 tap_ratio_min: control.tap_ratio_min,
7328 tap_ratio_max: control.tap_ratio_max,
7329 phase_shift_min_radians: control.phase_shift_min,
7330 phase_shift_max_radians: control.phase_shift_max,
7331 };
7332 Ok(true)
7333 })
7334 }
7335}
7336
7337#[unsafe(no_mangle)]
7338pub unsafe extern "C" fn pio_ac_scuc_instance_active_reserve_zone_count(
7339 instance: *const PioCalculationInstance,
7340) -> usize {
7341 unsafe { PioCalculationInstance::get(instance) }
7342 .and_then(scuc_inputs)
7343 .map_or(0, |inputs| inputs.active_reserve_zones.len())
7344}
7345
7346#[unsafe(no_mangle)]
7347pub unsafe extern "C" fn pio_ac_scuc_instance_active_reserve_zone_at(
7348 instance: *const PioCalculationInstance,
7349 index: usize,
7350 output: *mut PioScucActiveReserveZoneView,
7351 error: *mut *mut PioError,
7352) -> bool {
7353 unsafe {
7354 entry(error, false, || {
7355 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7356 let zone = inputs.active_reserve_zones.get(index).ok_or_else(|| {
7357 boundary_error(
7358 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7359 format!("SCUC active reserve zone index {index} is out of range"),
7360 )
7361 })?;
7362 *require_output(output, "output")? = PioScucActiveReserveZoneView {
7363 id: component_id_view(&zone.id),
7364 regulation_up_requirement_fraction: zone.regulation_up_requirement_fraction,
7365 regulation_down_requirement_fraction: zone.regulation_down_requirement_fraction,
7366 synchronized_requirement_fraction: zone.synchronized_requirement_fraction,
7367 nonsynchronized_requirement_fraction: zone.nonsynchronized_requirement_fraction,
7368 regulation_up_violation_cost: zone.regulation_up_violation_cost,
7369 regulation_down_violation_cost: zone.regulation_down_violation_cost,
7370 synchronized_violation_cost: zone.synchronized_violation_cost,
7371 nonsynchronized_violation_cost: zone.nonsynchronized_violation_cost,
7372 ramping_up_violation_cost: zone.ramping_up_violation_cost,
7373 ramping_down_violation_cost: zone.ramping_down_violation_cost,
7374 period_count: zone.ramping_up_requirement.len(),
7375 bus_count: zone.buses.len(),
7376 };
7377 Ok(true)
7378 })
7379 }
7380}
7381
7382#[unsafe(no_mangle)]
7383pub unsafe extern "C" fn pio_ac_scuc_instance_active_reserve_zone_period_at(
7384 instance: *const PioCalculationInstance,
7385 zone_index: usize,
7386 period_index: usize,
7387 output: *mut PioScucActiveReservePeriodView,
7388 error: *mut *mut PioError,
7389) -> bool {
7390 unsafe {
7391 entry(error, false, || {
7392 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7393 let zone = inputs.active_reserve_zones.get(zone_index).ok_or_else(|| {
7394 boundary_error(
7395 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7396 format!("SCUC active reserve zone index {zone_index} is out of range"),
7397 )
7398 })?;
7399 let up = zone
7400 .ramping_up_requirement
7401 .get(period_index)
7402 .copied()
7403 .ok_or_else(|| {
7404 boundary_error(
7405 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7406 format!("SCUC reserve period index {period_index} is out of range"),
7407 )
7408 })?;
7409 let down = zone
7410 .ramping_down_requirement
7411 .get(period_index)
7412 .copied()
7413 .ok_or_else(|| {
7414 boundary_error(
7415 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7416 format!("SCUC reserve period index {period_index} is out of range"),
7417 )
7418 })?;
7419 *require_output(output, "output")? = PioScucActiveReservePeriodView {
7420 ramping_up_requirement_pu: up,
7421 ramping_down_requirement_pu: down,
7422 };
7423 Ok(true)
7424 })
7425 }
7426}
7427
7428#[unsafe(no_mangle)]
7429pub unsafe extern "C" fn pio_ac_scuc_instance_active_reserve_zone_bus_at(
7430 instance: *const PioCalculationInstance,
7431 zone_index: usize,
7432 bus_index: usize,
7433 output: *mut PioComponentIdView,
7434 error: *mut *mut PioError,
7435) -> bool {
7436 unsafe {
7437 entry(error, false, || {
7438 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7439 let zone = inputs.active_reserve_zones.get(zone_index).ok_or_else(|| {
7440 boundary_error(
7441 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7442 format!("SCUC active reserve zone index {zone_index} is out of range"),
7443 )
7444 })?;
7445 let bus = zone.buses.get(bus_index).ok_or_else(|| {
7446 boundary_error(
7447 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7448 format!("SCUC reserve zone bus index {bus_index} is out of range"),
7449 )
7450 })?;
7451 *require_output(output, "output")? = component_id_view(bus);
7452 Ok(true)
7453 })
7454 }
7455}
7456
7457#[unsafe(no_mangle)]
7458pub unsafe extern "C" fn pio_ac_scuc_instance_reactive_reserve_zone_count(
7459 instance: *const PioCalculationInstance,
7460) -> usize {
7461 unsafe { PioCalculationInstance::get(instance) }
7462 .and_then(scuc_inputs)
7463 .map_or(0, |inputs| inputs.reactive_reserve_zones.len())
7464}
7465
7466#[unsafe(no_mangle)]
7467pub unsafe extern "C" fn pio_ac_scuc_instance_reactive_reserve_zone_at(
7468 instance: *const PioCalculationInstance,
7469 index: usize,
7470 output: *mut PioScucReactiveReserveZoneView,
7471 error: *mut *mut PioError,
7472) -> bool {
7473 unsafe {
7474 entry(error, false, || {
7475 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7476 let zone = inputs.reactive_reserve_zones.get(index).ok_or_else(|| {
7477 boundary_error(
7478 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7479 format!("SCUC reactive reserve zone index {index} is out of range"),
7480 )
7481 })?;
7482 *require_output(output, "output")? = PioScucReactiveReserveZoneView {
7483 id: component_id_view(&zone.id),
7484 reactive_up_violation_cost: zone.reactive_up_violation_cost,
7485 reactive_down_violation_cost: zone.reactive_down_violation_cost,
7486 period_count: zone.reactive_up_requirement.len(),
7487 bus_count: zone.buses.len(),
7488 };
7489 Ok(true)
7490 })
7491 }
7492}
7493
7494#[unsafe(no_mangle)]
7495pub unsafe extern "C" fn pio_ac_scuc_instance_reactive_reserve_zone_period_at(
7496 instance: *const PioCalculationInstance,
7497 zone_index: usize,
7498 period_index: usize,
7499 output: *mut PioScucReactiveReservePeriodView,
7500 error: *mut *mut PioError,
7501) -> bool {
7502 unsafe {
7503 entry(error, false, || {
7504 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7505 let zone = inputs
7506 .reactive_reserve_zones
7507 .get(zone_index)
7508 .ok_or_else(|| {
7509 boundary_error(
7510 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7511 format!("SCUC reactive reserve zone index {zone_index} is out of range"),
7512 )
7513 })?;
7514 let up = zone
7515 .reactive_up_requirement
7516 .get(period_index)
7517 .copied()
7518 .ok_or_else(|| {
7519 boundary_error(
7520 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7521 format!("SCUC reserve period index {period_index} is out of range"),
7522 )
7523 })?;
7524 let down = zone
7525 .reactive_down_requirement
7526 .get(period_index)
7527 .copied()
7528 .ok_or_else(|| {
7529 boundary_error(
7530 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7531 format!("SCUC reserve period index {period_index} is out of range"),
7532 )
7533 })?;
7534 *require_output(output, "output")? = PioScucReactiveReservePeriodView {
7535 reactive_up_requirement_pu: up,
7536 reactive_down_requirement_pu: down,
7537 };
7538 Ok(true)
7539 })
7540 }
7541}
7542
7543#[unsafe(no_mangle)]
7544pub unsafe extern "C" fn pio_ac_scuc_instance_reactive_reserve_zone_bus_at(
7545 instance: *const PioCalculationInstance,
7546 zone_index: usize,
7547 bus_index: usize,
7548 output: *mut PioComponentIdView,
7549 error: *mut *mut PioError,
7550) -> bool {
7551 unsafe {
7552 entry(error, false, || {
7553 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7554 let zone = inputs
7555 .reactive_reserve_zones
7556 .get(zone_index)
7557 .ok_or_else(|| {
7558 boundary_error(
7559 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7560 format!("SCUC reactive reserve zone index {zone_index} is out of range"),
7561 )
7562 })?;
7563 let bus = zone.buses.get(bus_index).ok_or_else(|| {
7564 boundary_error(
7565 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7566 format!("SCUC reserve zone bus index {bus_index} is out of range"),
7567 )
7568 })?;
7569 *require_output(output, "output")? = component_id_view(bus);
7570 Ok(true)
7571 })
7572 }
7573}
7574
7575#[unsafe(no_mangle)]
7577pub unsafe extern "C" fn pio_ac_scuc_instance_contingency_count(
7578 instance: *const PioCalculationInstance,
7579) -> usize {
7580 unsafe { PioCalculationInstance::get(instance) }
7581 .and_then(scuc_inputs)
7582 .map_or(0, |inputs| inputs.contingencies.len())
7583}
7584
7585fn scuc_contingency_view(contingency: &powerio_prob::ScucContingency) -> PioScucContingencyView {
7586 PioScucContingencyView {
7587 id: component_id_view(&contingency.id),
7588 component_count: contingency.components.len(),
7589 }
7590}
7591
7592#[unsafe(no_mangle)]
7594pub unsafe extern "C" fn pio_ac_scuc_instance_contingency_at(
7595 instance: *const PioCalculationInstance,
7596 contingency_index: usize,
7597 output: *mut PioScucContingencyView,
7598 error: *mut *mut PioError,
7599) -> bool {
7600 unsafe {
7601 entry(error, false, || {
7602 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7603 let contingency = inputs.contingencies.get(contingency_index).ok_or_else(|| {
7604 boundary_error(
7605 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7606 format!("SCUC contingency index {contingency_index} is out of range"),
7607 )
7608 })?;
7609 *require_output(output, "output")? = scuc_contingency_view(contingency);
7610 Ok(true)
7611 })
7612 }
7613}
7614
7615#[unsafe(no_mangle)]
7617pub unsafe extern "C" fn pio_ac_scuc_instance_contingency_get(
7618 instance: *const PioCalculationInstance,
7619 uid: *const c_char,
7620 uid_len: usize,
7621 output: *mut PioScucContingencyView,
7622 error: *mut *mut PioError,
7623) -> bool {
7624 unsafe {
7625 entry(error, false, || {
7626 let uid = required_str(uid, uid_len, "contingency_uid")?;
7627 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7628 let contingency = inputs.contingency(uid).ok_or_else(|| {
7629 boundary_error(
7630 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7631 format!("SCUC contingency UID '{uid}' does not exist"),
7632 )
7633 })?;
7634 *require_output(output, "output")? = scuc_contingency_view(contingency);
7635 Ok(true)
7636 })
7637 }
7638}
7639
7640#[unsafe(no_mangle)]
7642pub unsafe extern "C" fn pio_ac_scuc_instance_contingency_component_at(
7643 instance: *const PioCalculationInstance,
7644 contingency_index: usize,
7645 component_index: usize,
7646 output: *mut PioScucContingencyComponentView,
7647 error: *mut *mut PioError,
7648) -> bool {
7649 unsafe {
7650 entry(error, false, || {
7651 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
7652 let contingency = inputs.contingencies.get(contingency_index).ok_or_else(|| {
7653 boundary_error(
7654 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7655 format!("SCUC contingency index {contingency_index} is out of range"),
7656 )
7657 })?;
7658 let component = contingency.components.get(component_index).ok_or_else(|| {
7659 boundary_error(
7660 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7661 format!(
7662 "SCUC contingency {contingency_index} component index {component_index} is out of range"
7663 ),
7664 )
7665 })?;
7666 *require_output(output, "output")? = PioScucContingencyComponentView {
7667 id: component_id_view(component),
7668 };
7669 Ok(true)
7670 })
7671 }
7672}
7673
7674#[unsafe(no_mangle)]
7675pub unsafe extern "C" fn pio_calculation_solution_balanced_network(
7676 solution: *const PioCalculationSolution,
7677 error: *mut *mut PioError,
7678) -> *mut PioBalancedNetwork {
7679 unsafe {
7680 entry(error, std::ptr::null_mut(), || {
7681 let solution = PioCalculationSolution::get(solution).ok_or_else(|| {
7682 boundary_error(
7683 &codes::BIND_CAPI_NULL_HANDLE,
7684 "PioCalculationSolution must not be NULL",
7685 )
7686 })?;
7687 let projection = match solution.value() {
7688 Some(PioValue::DcPfSolution(_)) => BalancedNetworkProjection::DcPfSolution,
7689 Some(PioValue::AcPfSolution(_)) => BalancedNetworkProjection::AcPfSolution,
7690 Some(PioValue::DcOpfSolution(_)) => BalancedNetworkProjection::DcOpfSolution,
7691 Some(PioValue::AcOpfSolution(_)) => BalancedNetworkProjection::AcOpfSolution,
7692 Some(PioValue::SocwrOpfSolution(_)) => BalancedNetworkProjection::SocwrOpfSolution,
7693 Some(PioValue::AcScucSolution(_)) => BalancedNetworkProjection::AcScucSolution,
7694 _ => {
7695 return Err(boundary_error(
7696 &codes::REQUEST_CAPI_TYPE_MISMATCH,
7697 "the calculation solution does not use powerio.BalancedNetwork",
7698 ));
7699 }
7700 };
7701 Ok(make_balanced_network_view(solution, projection))
7702 })
7703 }
7704}
7705
7706#[unsafe(no_mangle)]
7707pub unsafe extern "C" fn pio_calculation_solution_multiconductor_network(
7708 solution: *const PioCalculationSolution,
7709 error: *mut *mut PioError,
7710) -> *mut PioMulticonductorNetwork {
7711 unsafe {
7712 entry(error, std::ptr::null_mut(), || {
7713 let solution = PioCalculationSolution::get(solution).ok_or_else(|| {
7714 boundary_error(
7715 &codes::BIND_CAPI_NULL_HANDLE,
7716 "PioCalculationSolution must not be NULL",
7717 )
7718 })?;
7719 let projection = match solution.value() {
7720 Some(PioValue::McAcPfSolution(_)) => {
7721 MulticonductorNetworkProjection::McAcPfSolution
7722 }
7723 Some(PioValue::McAcOpfSolution(_)) => {
7724 MulticonductorNetworkProjection::McAcOpfSolution
7725 }
7726 _ => {
7727 return Err(boundary_error(
7728 &codes::REQUEST_CAPI_TYPE_MISMATCH,
7729 "the calculation solution does not use powerio.MulticonductorNetwork",
7730 ));
7731 }
7732 };
7733 Ok(make_multiconductor_network_view(solution, projection))
7734 })
7735 }
7736}
7737
7738#[unsafe(no_mangle)]
7743pub unsafe extern "C" fn pio_operating_point_get_value(
7744 point: *const PioOperatingPoint,
7745 quantity: *const c_char,
7746 quantity_len: usize,
7747 identity: *const c_char,
7748 identity_len: usize,
7749 out_value: *mut f64,
7750 error: *mut *mut PioError,
7751) -> bool {
7752 unsafe {
7753 entry(error, false, || {
7754 if out_value.is_null() {
7755 return Err(boundary_error(
7756 &codes::BIND_CAPI_NULL_ARGUMENT,
7757 "out_value must not be NULL",
7758 ));
7759 }
7760 let quantity = required_str(quantity, quantity_len, "quantity")?;
7761 let identity = required_str(identity, identity_len, "identity")?;
7762 let point = PioOperatingPoint::get(point).ok_or_else(|| {
7763 boundary_error(
7764 &codes::BIND_CAPI_NULL_HANDLE,
7765 "PioOperatingPoint must not be NULL",
7766 )
7767 })?;
7768 let value = if let Some(point) = point.balanced() {
7769 use powerio_prob::{
7770 BalancedOperatingPointFlag as F, BalancedOperatingPointQuantity as Q,
7771 };
7772 match quantity {
7773 "bus_voltage_magnitude" => point.values(Q::BusVoltageMagnitude),
7774 "bus_voltage_angle" => point.values(Q::BusVoltageAngle),
7775 "bus_active_injection" => point.values(Q::BusActiveInjection),
7776 "bus_reactive_injection" => point.values(Q::BusReactiveInjection),
7777 "generator_active_power" => point.values(Q::GeneratorActivePower),
7778 "generator_reactive_power" => point.values(Q::GeneratorReactivePower),
7779 "generator_voltage_setpoint" => point.values(Q::GeneratorVoltageSetpoint),
7780 "load_active_power" => point.values(Q::LoadActivePower),
7781 "load_reactive_power" => point.values(Q::LoadReactivePower),
7782 "branch_tap_ratio" => point.values(Q::BranchTapRatio),
7783 "branch_phase_shift" => point.values(Q::BranchPhaseShift),
7784 "generator_in_service" => {
7785 let found = point
7786 .flags(F::GeneratorInService)
7787 .and_then(|mut values| values.find(|(key, _)| *key == identity))
7788 .map(|(_, value)| if value { 1.0 } else { 0.0 });
7789 if let Some(found) = found {
7790 *out_value = found;
7791 return Ok(true);
7792 }
7793 return Ok(false);
7794 }
7795 "branch_in_service" => {
7796 let found = point
7797 .flags(F::BranchInService)
7798 .and_then(|mut values| values.find(|(key, _)| *key == identity))
7799 .map(|(_, value)| if value { 1.0 } else { 0.0 });
7800 if let Some(found) = found {
7801 *out_value = found;
7802 return Ok(true);
7803 }
7804 return Ok(false);
7805 }
7806 "switch_closed" => {
7807 let found = point
7808 .flags(F::SwitchClosed)
7809 .and_then(|mut values| values.find(|(key, _)| *key == identity))
7810 .map(|(_, value)| if value { 1.0 } else { 0.0 });
7811 if let Some(found) = found {
7812 *out_value = found;
7813 return Ok(true);
7814 }
7815 return Ok(false);
7816 }
7817 _ => {
7818 return Err(boundary_error(
7819 &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
7820 format!("unknown balanced operating point quantity '{quantity}'"),
7821 ));
7822 }
7823 }
7824 .and_then(|mut values| values.find(|(key, _)| *key == identity))
7825 .map(|(_, value)| value)
7826 } else if let Some(point) = point.multiconductor() {
7827 use powerio_prob::{
7828 MulticonductorOperatingPointFlag as F,
7829 MulticonductorOperatingPointQuantity as Q,
7830 };
7831 match quantity {
7832 "terminal_voltage_magnitude" => point.values(Q::TerminalVoltageMagnitude),
7833 "terminal_voltage_angle" => point.values(Q::TerminalVoltageAngle),
7834 "load_active_power" => point.values(Q::LoadActivePower),
7835 "load_reactive_power" => point.values(Q::LoadReactivePower),
7836 "generator_active_power" => point.values(Q::GeneratorActivePower),
7837 "generator_reactive_power" => point.values(Q::GeneratorReactivePower),
7838 "transformer_tap" => point.values(Q::TransformerTap),
7839 "capacitor_steps" => point.values(Q::CapacitorSteps),
7840 "switch_closed" => {
7841 let found = point
7842 .flags(F::SwitchClosed)
7843 .and_then(|mut values| values.find(|(key, _)| *key == identity))
7844 .map(|(_, value)| if value { 1.0 } else { 0.0 });
7845 if let Some(found) = found {
7846 *out_value = found;
7847 return Ok(true);
7848 }
7849 return Ok(false);
7850 }
7851 _ => {
7852 return Err(boundary_error(
7853 &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
7854 format!("unknown multiconductor operating point quantity '{quantity}'"),
7855 ));
7856 }
7857 }
7858 .and_then(|mut values| values.find(|(key, _)| *key == identity))
7859 .map(|(_, value)| value)
7860 } else {
7861 return Err(boundary_error(
7862 &codes::REQUEST_CAPI_TYPE_MISMATCH,
7863 "the handle does not refer to an operating point",
7864 ));
7865 };
7866 if let Some(value) = value {
7867 *out_value = value;
7868 Ok(true)
7869 } else {
7870 Ok(false)
7871 }
7872 })
7873 }
7874}
7875
7876#[unsafe(no_mangle)]
7877pub unsafe extern "C" fn pio_operating_point_retain(
7878 point: *const PioOperatingPoint,
7879) -> *mut PioOperatingPoint {
7880 unsafe { PioOperatingPoint::retain_raw(point) }
7881}
7882
7883#[unsafe(no_mangle)]
7884pub unsafe extern "C" fn pio_operating_point_release(point: *mut PioOperatingPoint) {
7885 unsafe { PioOperatingPoint::release_raw(point) };
7886}
7887
7888#[unsafe(no_mangle)]
7889pub unsafe extern "C" fn pio_calculation_instance_retain(
7890 instance: *const PioCalculationInstance,
7891) -> *mut PioCalculationInstance {
7892 unsafe { PioCalculationInstance::retain_raw(instance) }
7893}
7894
7895#[unsafe(no_mangle)]
7896pub unsafe extern "C" fn pio_calculation_instance_release(instance: *mut PioCalculationInstance) {
7897 unsafe { PioCalculationInstance::release_raw(instance) };
7898}
7899
7900#[unsafe(no_mangle)]
7901pub unsafe extern "C" fn pio_dc_opf_preparation_retain(
7902 preparation: *const PioDcOpfPreparation,
7903) -> *mut PioDcOpfPreparation {
7904 unsafe { PioDcOpfPreparation::retain_raw(preparation) }
7905}
7906
7907#[unsafe(no_mangle)]
7908pub unsafe extern "C" fn pio_dc_opf_preparation_release(preparation: *mut PioDcOpfPreparation) {
7909 unsafe { PioDcOpfPreparation::release_raw(preparation) };
7910}
7911
7912#[unsafe(no_mangle)]
7913pub unsafe extern "C" fn pio_ac_opf_preparation_retain(
7914 preparation: *const PioAcOpfPreparation,
7915) -> *mut PioAcOpfPreparation {
7916 unsafe { PioAcOpfPreparation::retain_raw(preparation) }
7917}
7918
7919#[unsafe(no_mangle)]
7920pub unsafe extern "C" fn pio_ac_opf_preparation_release(preparation: *mut PioAcOpfPreparation) {
7921 unsafe { PioAcOpfPreparation::release_raw(preparation) };
7922}
7923
7924#[unsafe(no_mangle)]
7925pub unsafe extern "C" fn pio_calculation_solution_retain(
7926 solution: *const PioCalculationSolution,
7927) -> *mut PioCalculationSolution {
7928 unsafe { PioCalculationSolution::retain_raw(solution) }
7929}
7930
7931#[unsafe(no_mangle)]
7932pub unsafe extern "C" fn pio_calculation_solution_release(solution: *mut PioCalculationSolution) {
7933 unsafe { PioCalculationSolution::release_raw(solution) };
7934}
7935
7936#[unsafe(no_mangle)]
7937pub unsafe extern "C" fn pio_value_retain(value: *const PioValueHandle) -> *mut PioValueHandle {
7938 unsafe { PioValueHandle::retain_raw(value) }
7939}
7940
7941#[unsafe(no_mangle)]
7942pub unsafe extern "C" fn pio_value_release(value: *mut PioValueHandle) {
7943 unsafe { PioValueHandle::release_raw(value) };
7944}
7945
7946fn time_series(value: &ValueInner) -> Option<&PioTimeSeries> {
7949 match value.value()? {
7950 PioValue::TimeSeries(series) => Some(series),
7951 _ => None,
7952 }
7953}
7954
7955fn scenario_set(value: &ValueInner) -> Option<&PioScenarioSet> {
7956 match value.value()? {
7957 PioValue::ScenarioSet(scenarios) => Some(scenarios),
7958 _ => None,
7959 }
7960}
7961
7962#[unsafe(no_mangle)]
7963pub unsafe extern "C" fn pio_time_series_len(series: *const PioTimeSeriesHandle) -> usize {
7964 unsafe { PioTimeSeriesHandle::get(series) }
7965 .and_then(time_series)
7966 .map_or(0, PioTimeSeries::len)
7967}
7968
7969#[unsafe(no_mangle)]
7970pub unsafe extern "C" fn pio_time_series_element_type(
7971 series: *const PioTimeSeriesHandle,
7972) -> PioStringView {
7973 unsafe { PioTimeSeriesHandle::get(series) }
7974 .and_then(time_series)
7975 .map_or(PioStringView::EMPTY, |series| {
7976 PioStringView::new(series.element_type())
7977 })
7978}
7979
7980#[unsafe(no_mangle)]
7982pub unsafe extern "C" fn pio_time_series_get(
7983 series: *const PioTimeSeriesHandle,
7984 index: usize,
7985 error: *mut *mut PioError,
7986) -> *mut PioValueHandle {
7987 unsafe {
7988 entry(error, std::ptr::null_mut(), || {
7989 let series = PioTimeSeriesHandle::get(series).ok_or_else(|| {
7990 boundary_error(
7991 &codes::BIND_CAPI_NULL_HANDLE,
7992 "PioTimeSeriesHandle must not be NULL",
7993 )
7994 })?;
7995 let values = time_series(series).ok_or_else(|| {
7996 boundary_error(
7997 &codes::REQUEST_CAPI_TYPE_MISMATCH,
7998 "the handle does not refer to a time series",
7999 )
8000 })?;
8001 if values.get(index).is_none() {
8002 return Err(boundary_error(
8003 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8004 format!("time series index {index} is out of range"),
8005 ));
8006 }
8007 Ok(value_handle(series.child(ValueStep::TimeSeries(index))))
8008 })
8009 }
8010}
8011
8012#[unsafe(no_mangle)]
8013pub unsafe extern "C" fn pio_time_series_retain(
8014 series: *const PioTimeSeriesHandle,
8015) -> *mut PioTimeSeriesHandle {
8016 unsafe { PioTimeSeriesHandle::retain_raw(series) }
8017}
8018
8019#[unsafe(no_mangle)]
8020pub unsafe extern "C" fn pio_time_series_release(series: *mut PioTimeSeriesHandle) {
8021 unsafe { PioTimeSeriesHandle::release_raw(series) };
8022}
8023
8024#[unsafe(no_mangle)]
8025pub unsafe extern "C" fn pio_scenario_set_len(set: *const PioScenarioSetHandle) -> usize {
8026 unsafe { PioScenarioSetHandle::get(set) }
8027 .and_then(scenario_set)
8028 .map_or(0, PioScenarioSet::len)
8029}
8030
8031#[unsafe(no_mangle)]
8032pub unsafe extern "C" fn pio_scenario_set_element_type(
8033 set: *const PioScenarioSetHandle,
8034) -> PioStringView {
8035 unsafe { PioScenarioSetHandle::get(set) }
8036 .and_then(scenario_set)
8037 .map_or(PioStringView::EMPTY, |set| {
8038 PioStringView::new(set.element_type())
8039 })
8040}
8041
8042#[unsafe(no_mangle)]
8043pub unsafe extern "C" fn pio_scenario_set_id_at(
8044 set: *const PioScenarioSetHandle,
8045 index: usize,
8046) -> PioStringView {
8047 unsafe { PioScenarioSetHandle::get(set) }
8048 .and_then(scenario_set)
8049 .and_then(|set| set.iter().nth(index))
8050 .map_or(PioStringView::EMPTY, |scenario| {
8051 PioStringView::new(scenario.id().as_str())
8052 })
8053}
8054
8055#[unsafe(no_mangle)]
8057pub unsafe extern "C" fn pio_scenario_set_get_at(
8058 set: *const PioScenarioSetHandle,
8059 index: usize,
8060 error: *mut *mut PioError,
8061) -> *mut PioValueHandle {
8062 unsafe {
8063 entry(error, std::ptr::null_mut(), || {
8064 let set = PioScenarioSetHandle::get(set).ok_or_else(|| {
8065 boundary_error(
8066 &codes::BIND_CAPI_NULL_HANDLE,
8067 "PioScenarioSetHandle must not be NULL",
8068 )
8069 })?;
8070 let values = scenario_set(set).ok_or_else(|| {
8071 boundary_error(
8072 &codes::REQUEST_CAPI_TYPE_MISMATCH,
8073 "the handle does not refer to a scenario set",
8074 )
8075 })?;
8076 if values.get_at(index).is_none() {
8077 return Err(boundary_error(
8078 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8079 format!("scenario position {index} is out of range"),
8080 ));
8081 }
8082 Ok(value_handle(set.child(ValueStep::Scenario(index))))
8083 })
8084 }
8085}
8086
8087#[unsafe(no_mangle)]
8089pub unsafe extern "C" fn pio_scenario_set_get(
8090 set: *const PioScenarioSetHandle,
8091 id: *const c_char,
8092 id_len: usize,
8093 error: *mut *mut PioError,
8094) -> *mut PioValueHandle {
8095 unsafe {
8096 entry(error, std::ptr::null_mut(), || {
8097 let id = required_str(id, id_len, "scenario_id")?;
8098 let set = PioScenarioSetHandle::get(set).ok_or_else(|| {
8099 boundary_error(
8100 &codes::BIND_CAPI_NULL_HANDLE,
8101 "PioScenarioSetHandle must not be NULL",
8102 )
8103 })?;
8104 let values = scenario_set(set).ok_or_else(|| {
8105 boundary_error(
8106 &codes::REQUEST_CAPI_TYPE_MISMATCH,
8107 "the handle does not refer to a scenario set",
8108 )
8109 })?;
8110 let Some(position) = values
8111 .iter()
8112 .position(|scenario| scenario.id().as_str() == id)
8113 else {
8114 return Err(boundary_error(
8115 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8116 format!("scenario ID '{id}' does not exist"),
8117 ));
8118 };
8119 Ok(value_handle(set.child(ValueStep::Scenario(position))))
8120 })
8121 }
8122}
8123
8124#[unsafe(no_mangle)]
8125pub unsafe extern "C" fn pio_scenario_set_retain(
8126 set: *const PioScenarioSetHandle,
8127) -> *mut PioScenarioSetHandle {
8128 unsafe { PioScenarioSetHandle::retain_raw(set) }
8129}
8130
8131#[unsafe(no_mangle)]
8132pub unsafe extern "C" fn pio_scenario_set_release(set: *mut PioScenarioSetHandle) {
8133 unsafe { PioScenarioSetHandle::release_raw(set) };
8134}
8135
8136#[unsafe(no_mangle)]
8139pub unsafe extern "C" fn pio_balanced_network_name(
8140 network: *const PioBalancedNetwork,
8141) -> PioStringView {
8142 unsafe { PioBalancedNetwork::get(network) }
8143 .and_then(BalancedNetworkInner::network)
8144 .map_or(PioStringView::EMPTY, |network| {
8145 PioStringView::new(network.name())
8146 })
8147}
8148
8149#[unsafe(no_mangle)]
8150pub unsafe extern "C" fn pio_balanced_network_base_mva(network: *const PioBalancedNetwork) -> f64 {
8151 unsafe { PioBalancedNetwork::get(network) }
8152 .and_then(BalancedNetworkInner::network)
8153 .map_or(f64::NAN, BalancedNetwork::base_mva)
8154}
8155
8156#[unsafe(no_mangle)]
8157pub unsafe extern "C" fn pio_balanced_network_base_frequency_hz(
8158 network: *const PioBalancedNetwork,
8159) -> f64 {
8160 unsafe { PioBalancedNetwork::get(network) }
8161 .and_then(BalancedNetworkInner::network)
8162 .map_or(f64::NAN, BalancedNetwork::base_frequency)
8163}
8164
8165#[unsafe(no_mangle)]
8167pub unsafe extern "C" fn pio_balanced_network_geo(
8168 network: *const PioBalancedNetwork,
8169 output: *mut PioBalancedGeoView,
8170 error: *mut *mut PioError,
8171) -> bool {
8172 unsafe {
8173 entry(error, false, || {
8174 let network = require_balanced_network(network)?;
8175 *require_output(output, "output")? = balanced_geo_view(network.geo().as_ref());
8176 Ok(true)
8177 })
8178 }
8179}
8180
8181#[unsafe(no_mangle)]
8182pub unsafe extern "C" fn pio_balanced_network_has_detailed_connectivity(
8183 network: *const PioBalancedNetwork,
8184) -> bool {
8185 unsafe { PioBalancedNetwork::get(network) }
8186 .and_then(BalancedNetworkInner::network)
8187 .is_some_and(|network| network.detailed_connectivity().is_some())
8188}
8189
8190#[unsafe(no_mangle)]
8192pub unsafe extern "C" fn pio_balanced_network_detailed_connectivity(
8193 network: *const PioBalancedNetwork,
8194) -> *mut PioDetailedConnectivity {
8195 let Some(owner) = (unsafe { PioBalancedNetwork::arc(network) }) else {
8196 return std::ptr::null_mut();
8197 };
8198 if owner
8199 .network()
8200 .is_none_or(|network| network.detailed_connectivity().is_none())
8201 {
8202 return std::ptr::null_mut();
8203 }
8204 PioDetailedConnectivity::new_raw(DetailedConnectivityInner { owner })
8205}
8206
8207#[unsafe(no_mangle)]
8209pub unsafe extern "C" fn pio_detailed_connectivity_counts(
8210 details: *const PioDetailedConnectivity,
8211 output: *mut PioDetailedConnectivityCountsView,
8212 error: *mut *mut PioError,
8213) -> bool {
8214 unsafe {
8215 entry(error, false, || {
8216 let details = require_detailed_connectivity(details)?;
8217 *require_output(output, "output")? = PioDetailedConnectivityCountsView {
8218 omitted_fields: details.omitted_fields.len(),
8219 component_metadata: details.component_metadata.len(),
8220 subnetworks: details.subnetworks.len(),
8221 substations: details.substations.len(),
8222 voltage_levels: details.voltage_levels.len(),
8223 bus_breaker_buses: details.bus_breaker_buses.len(),
8224 calculated_buses: details.calculated_buses.len(),
8225 connectivity_nodes: details.connectivity_nodes.len(),
8226 busbar_sections: details.busbar_sections.len(),
8227 junctions: details.junctions.len(),
8228 terminals: details.terminals.len(),
8229 switches: details.switches.len(),
8230 internal_connections: details.internal_connections.len(),
8231 operational_limit_groups: details.operational_limit_groups.len(),
8232 tap_changers: details.tap_changers.len(),
8233 equipment_reactive_limits: details.equipment_reactive_limits.len(),
8234 boundary_lines: details.boundary_lines.len(),
8235 tie_lines: details.tie_lines.len(),
8236 dc_converter_units: details.dc_converter_units.len(),
8237 dc_topological_nodes: details.dc_topological_nodes.len(),
8238 dc_nodes: details.dc_nodes.len(),
8239 dc_grounds: details.dc_grounds.len(),
8240 dc_busbars: details.dc_busbars.len(),
8241 dc_lines: details.dc_lines.len(),
8242 dc_series_devices: details.dc_series_devices.len(),
8243 dc_switches: details.dc_switches.len(),
8244 voltage_source_converters: details.voltage_source_converters.len(),
8245 line_commutated_converters: details.line_commutated_converters.len(),
8246 };
8247 Ok(true)
8248 })
8249 }
8250}
8251
8252fn omitted_field_name(value: powerio_tx::OmittedFieldName) -> &'static str {
8253 match value {
8254 powerio_tx::OmittedFieldName::ActivePower => "active_power",
8255 powerio_tx::OmittedFieldName::ReactivePower => "reactive_power",
8256 powerio_tx::OmittedFieldName::VoltageSetpoint => "voltage_setpoint",
8257 powerio_tx::OmittedFieldName::RatedApparentPower => "rated_apparent_power",
8258 powerio_tx::OmittedFieldName::ShuntConductancePerSection => "shunt_conductance_per_section",
8259 _ => "unknown",
8260 }
8261}
8262
8263fn topology_kind_name(kind: powerio_tx::TopologyKind) -> &'static str {
8264 match kind {
8265 powerio_tx::TopologyKind::BusBreaker => "bus_breaker",
8266 powerio_tx::TopologyKind::NodeBreaker => "node_breaker",
8267 _ => "unknown",
8268 }
8269}
8270
8271fn topology_switch_kind_name(kind: powerio_tx::SwitchKind) -> &'static str {
8272 match kind {
8273 powerio_tx::SwitchKind::Breaker => "breaker",
8274 powerio_tx::SwitchKind::Disconnector => "disconnector",
8275 powerio_tx::SwitchKind::LoadBreakSwitch => "load_break_switch",
8276 _ => "unknown",
8277 }
8278}
8279
8280fn curve_style_name(style: powerio_tx::CurveStyle) -> &'static str {
8281 match style {
8282 powerio_tx::CurveStyle::ConstantYValue => "constant_y_value",
8283 powerio_tx::CurveStyle::StraightLineYValues => "straight_line_y_values",
8284 _ => "unknown",
8285 }
8286}
8287
8288fn empty_reactive_limits_view() -> PioReactiveLimitsView {
8289 PioReactiveLimitsView {
8290 kind: PioStringView::EMPTY,
8291 minimum_reactive_power_mvar: 0.0,
8292 maximum_reactive_power_mvar: 0.0,
8293 has_minimum_and_maximum: false,
8294 curve_style: PioStringView::EMPTY,
8295 has_curve_style: false,
8296 property_count: 0,
8297 point_count: 0,
8298 }
8299}
8300
8301fn reactive_limits_view(
8302 value: Option<&powerio_tx::ReactiveLimits>,
8303) -> (PioReactiveLimitsView, bool) {
8304 match value {
8305 Some(powerio_tx::ReactiveLimits::MinMax(limits)) => (
8306 PioReactiveLimitsView {
8307 kind: PioStringView::new("min_max"),
8308 minimum_reactive_power_mvar: limits.minimum_reactive_power_mvar,
8309 maximum_reactive_power_mvar: limits.maximum_reactive_power_mvar,
8310 has_minimum_and_maximum: true,
8311 curve_style: PioStringView::EMPTY,
8312 has_curve_style: false,
8313 property_count: limits.properties.len(),
8314 point_count: 0,
8315 },
8316 true,
8317 ),
8318 Some(powerio_tx::ReactiveLimits::CapabilityCurve(curve)) => (
8319 PioReactiveLimitsView {
8320 kind: PioStringView::new("capability_curve"),
8321 minimum_reactive_power_mvar: 0.0,
8322 maximum_reactive_power_mvar: 0.0,
8323 has_minimum_and_maximum: false,
8324 curve_style: PioStringView::new(curve_style_name(curve.curve_style)),
8325 has_curve_style: true,
8326 property_count: curve.properties.len(),
8327 point_count: curve.points.len(),
8328 },
8329 true,
8330 ),
8331 Some(_) => (empty_reactive_limits_view(), true),
8332 None => (empty_reactive_limits_view(), false),
8333 }
8334}
8335
8336fn reactive_limit_properties(
8337 limits: &powerio_tx::ReactiveLimits,
8338) -> &std::collections::BTreeMap<String, String> {
8339 match limits {
8340 powerio_tx::ReactiveLimits::MinMax(limits) => &limits.properties,
8341 powerio_tx::ReactiveLimits::CapabilityCurve(curve) => &curve.properties,
8342 _ => unreachable!("all reactive limit forms are handled"),
8343 }
8344}
8345
8346fn reactive_capability_curve(
8347 limits: &powerio_tx::ReactiveLimits,
8348) -> Option<&powerio_tx::ReactiveCapabilityCurve> {
8349 match limits {
8350 powerio_tx::ReactiveLimits::CapabilityCurve(curve) => Some(curve),
8351 _ => None,
8352 }
8353}
8354
8355fn string_property_view(
8356 properties: &std::collections::BTreeMap<String, String>,
8357 index: usize,
8358) -> Result<PioStringPropertyView, *mut PioError> {
8359 let (name, value) = properties.iter().nth(index).ok_or_else(|| {
8360 boundary_error(
8361 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8362 format!("property index {index} is out of range"),
8363 )
8364 })?;
8365 Ok(PioStringPropertyView {
8366 name: PioStringView::new(name),
8367 value: PioStringView::new(value),
8368 })
8369}
8370
8371fn reactive_capability_point_view(
8372 curve: &powerio_tx::ReactiveCapabilityCurve,
8373 index: usize,
8374) -> Result<PioReactiveCapabilityCurvePointView, *mut PioError> {
8375 let point = curve.points.get(index).ok_or_else(|| {
8376 boundary_error(
8377 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8378 format!("reactive capability point index {index} is out of range"),
8379 )
8380 })?;
8381 Ok(PioReactiveCapabilityCurvePointView {
8382 active_power_mw: point.active_power_mw,
8383 minimum_reactive_power_mvar: point.minimum_reactive_power_mvar,
8384 maximum_reactive_power_mvar: point.maximum_reactive_power_mvar,
8385 property_count: point.properties.len(),
8386 })
8387}
8388
8389fn empty_boundary_line_generation_view() -> PioBoundaryLineGenerationView {
8390 PioBoundaryLineGenerationView {
8391 voltage_regulation_on: false,
8392 minimum_active_power_mw: 0.0,
8393 has_minimum_active_power: false,
8394 maximum_active_power_mw: 0.0,
8395 has_maximum_active_power: false,
8396 target_active_power_mw: 0.0,
8397 has_target_active_power: false,
8398 target_reactive_power_mvar: 0.0,
8399 has_target_reactive_power: false,
8400 target_voltage_kv: 0.0,
8401 has_target_voltage: false,
8402 reactive_limits: empty_reactive_limits_view(),
8403 has_reactive_limits: false,
8404 }
8405}
8406
8407fn boundary_line_generation_view(
8408 value: Option<&powerio_tx::BoundaryLineGeneration>,
8409) -> (PioBoundaryLineGenerationView, bool) {
8410 let Some(value) = value else {
8411 return (empty_boundary_line_generation_view(), false);
8412 };
8413 let (reactive_limits, has_reactive_limits) =
8414 reactive_limits_view(value.reactive_limits.as_ref());
8415 (
8416 PioBoundaryLineGenerationView {
8417 voltage_regulation_on: value.voltage_regulation_on,
8418 minimum_active_power_mw: value.minimum_active_power_mw.unwrap_or(0.0),
8419 has_minimum_active_power: value.minimum_active_power_mw.is_some(),
8420 maximum_active_power_mw: value.maximum_active_power_mw.unwrap_or(0.0),
8421 has_maximum_active_power: value.maximum_active_power_mw.is_some(),
8422 target_active_power_mw: value.target_active_power_mw.unwrap_or(0.0),
8423 has_target_active_power: value.target_active_power_mw.is_some(),
8424 target_reactive_power_mvar: value.target_reactive_power_mvar.unwrap_or(0.0),
8425 has_target_reactive_power: value.target_reactive_power_mvar.is_some(),
8426 target_voltage_kv: value.target_voltage_kv.unwrap_or(0.0),
8427 has_target_voltage: value.target_voltage_kv.is_some(),
8428 reactive_limits,
8429 has_reactive_limits,
8430 },
8431 true,
8432 )
8433}
8434
8435fn topology_endpoint_view(
8436 endpoint: &powerio_tx::TopologyEndpoint,
8437) -> (PioStringView, PioComponentIdView) {
8438 match endpoint {
8439 powerio_tx::TopologyEndpoint::Bus(component) => {
8440 (PioStringView::new("bus"), component_id_view(component))
8441 }
8442 powerio_tx::TopologyEndpoint::Node(component) => {
8443 (PioStringView::new("node"), component_id_view(component))
8444 }
8445 _ => (PioStringView::new("unknown"), empty_component_id_view()),
8446 }
8447}
8448
8449fn tap_changer_kind_name(kind: powerio_tx::TapChangerKind) -> &'static str {
8450 match kind {
8451 powerio_tx::TapChangerKind::Ratio => "ratio",
8452 powerio_tx::TapChangerKind::Phase => "phase",
8453 _ => "unknown",
8454 }
8455}
8456
8457fn tap_changer_regulation_mode_name(mode: powerio_tx::TapChangerRegulationMode) -> &'static str {
8458 match mode {
8459 powerio_tx::TapChangerRegulationMode::Voltage => "voltage",
8460 powerio_tx::TapChangerRegulationMode::ReactivePower => "reactive_power",
8461 powerio_tx::TapChangerRegulationMode::ActivePower => "active_power",
8462 powerio_tx::TapChangerRegulationMode::Current => "current",
8463 _ => "unknown",
8464 }
8465}
8466
8467fn dc_polarity_name(value: powerio_tx::DcPolarity) -> &'static str {
8468 match value {
8469 powerio_tx::DcPolarity::Positive => "positive",
8470 powerio_tx::DcPolarity::Middle => "middle",
8471 powerio_tx::DcPolarity::Negative => "negative",
8472 _ => "unknown",
8473 }
8474}
8475
8476fn dc_switch_kind_name(value: powerio_tx::DcSwitchKind) -> &'static str {
8477 match value {
8478 powerio_tx::DcSwitchKind::Switch => "switch",
8479 powerio_tx::DcSwitchKind::Breaker => "breaker",
8480 powerio_tx::DcSwitchKind::Disconnector => "disconnector",
8481 _ => "unknown",
8482 }
8483}
8484
8485fn dc_converter_operation_mode_name(value: powerio_tx::DcConverterOperatingMode) -> &'static str {
8486 match value {
8487 powerio_tx::DcConverterOperatingMode::Bipolar => "bipolar",
8488 powerio_tx::DcConverterOperatingMode::MonopolarGroundReturn => "monopolar_ground_return",
8489 powerio_tx::DcConverterOperatingMode::MonopolarMetallicReturn => {
8490 "monopolar_metallic_return"
8491 }
8492 _ => "unknown",
8493 }
8494}
8495
8496fn ac_dc_converter_control_mode_name(value: powerio_tx::AcDcConverterControlMode) -> &'static str {
8497 match value {
8498 powerio_tx::AcDcConverterControlMode::ActivePowerAtPcc => "active_power_at_pcc",
8499 powerio_tx::AcDcConverterControlMode::DcVoltage => "dc_voltage",
8500 powerio_tx::AcDcConverterControlMode::DcCurrent => "dc_current",
8501 powerio_tx::AcDcConverterControlMode::ActivePowerAtPccAndDcVoltageDroopCurve => {
8502 "active_power_at_pcc_and_dc_voltage_droop_curve"
8503 }
8504 powerio_tx::AcDcConverterControlMode::ActivePowerAtPccAndDcVoltageDroop => {
8505 "active_power_at_pcc_and_dc_voltage_droop"
8506 }
8507 powerio_tx::AcDcConverterControlMode::ActivePowerAtPccAndDcVoltageDroopWithCompensation => {
8508 "active_power_at_pcc_and_dc_voltage_droop_with_compensation"
8509 }
8510 powerio_tx::AcDcConverterControlMode::ActivePowerAtPccAndDcVoltageDroopPilot => {
8511 "active_power_at_pcc_and_dc_voltage_droop_pilot"
8512 }
8513 _ => "unknown",
8514 }
8515}
8516
8517fn line_commutated_converter_reactive_model_name(
8518 value: powerio_tx::LineCommutatedConverterReactiveModel,
8519) -> &'static str {
8520 match value {
8521 powerio_tx::LineCommutatedConverterReactiveModel::FixedPowerFactor => "fixed_power_factor",
8522 powerio_tx::LineCommutatedConverterReactiveModel::CalculatedPowerFactor => {
8523 "calculated_power_factor"
8524 }
8525 _ => "unknown",
8526 }
8527}
8528
8529fn line_commutated_converter_operating_mode_name(
8530 value: powerio_tx::LineCommutatedConverterOperatingMode,
8531) -> &'static str {
8532 match value {
8533 powerio_tx::LineCommutatedConverterOperatingMode::Rectifier => "rectifier",
8534 powerio_tx::LineCommutatedConverterOperatingMode::Inverter => "inverter",
8535 _ => "unknown",
8536 }
8537}
8538
8539fn empty_dc_terminal_view() -> PioDcTerminalView {
8540 PioDcTerminalView {
8541 component: empty_component_id_view(),
8542 has_component: false,
8543 sequence_number: 0,
8544 has_sequence_number: false,
8545 dc_node: empty_component_id_view(),
8546 has_dc_node: false,
8547 dc_topological_node: empty_component_id_view(),
8548 has_dc_topological_node: false,
8549 polarity: PioStringView::EMPTY,
8550 has_polarity: false,
8551 connected: false,
8552 has_connected: false,
8553 active_power_mw: 0.0,
8554 has_active_power: false,
8555 current_a: 0.0,
8556 has_current: false,
8557 }
8558}
8559
8560fn dc_terminal_view(value: &powerio_tx::DcTerminal) -> PioDcTerminalView {
8561 let (component, has_component) = optional_component_id_view(value.component.as_ref());
8562 let (dc_node, has_dc_node) = optional_component_id_view(value.dc_node.as_ref());
8563 let (dc_topological_node, has_dc_topological_node) =
8564 optional_component_id_view(value.dc_topological_node.as_ref());
8565 let (polarity, has_polarity) = value
8566 .polarity
8567 .map_or((PioStringView::EMPTY, false), |polarity| {
8568 (PioStringView::new(dc_polarity_name(polarity)), true)
8569 });
8570 PioDcTerminalView {
8571 component,
8572 has_component,
8573 sequence_number: value.sequence_number.unwrap_or(0),
8574 has_sequence_number: value.sequence_number.is_some(),
8575 dc_node,
8576 has_dc_node,
8577 dc_topological_node,
8578 has_dc_topological_node,
8579 polarity,
8580 has_polarity,
8581 connected: value.connected.unwrap_or(false),
8582 has_connected: value.connected.is_some(),
8583 active_power_mw: value.active_power_mw.unwrap_or(0.0),
8584 has_active_power: value.active_power_mw.is_some(),
8585 current_a: value.current_a.unwrap_or(0.0),
8586 has_current: value.current_a.is_some(),
8587 }
8588}
8589
8590#[allow(clippy::too_many_arguments)]
8591fn dc_equipment_view(
8592 component: &ComponentId,
8593 equipment_container: Option<&ComponentId>,
8594 kind: &'static str,
8595 terminal1: &powerio_tx::DcTerminal,
8596 terminal2: Option<&powerio_tx::DcTerminal>,
8597 rated_dc_voltage_kv: Option<f64>,
8598 resistance_ohm: Option<f64>,
8599 inductance_h: Option<f64>,
8600 capacitance_f: Option<f64>,
8601 length_km: Option<f64>,
8602 switch_kind: Option<powerio_tx::DcSwitchKind>,
8603 open: Option<bool>,
8604) -> PioDcEquipmentView {
8605 let (equipment_container, has_equipment_container) =
8606 optional_component_id_view(equipment_container);
8607 let (switch_kind, has_switch_kind) = switch_kind
8608 .map_or((PioStringView::EMPTY, false), |kind| {
8609 (PioStringView::new(dc_switch_kind_name(kind)), true)
8610 });
8611 PioDcEquipmentView {
8612 component: component_id_view(component),
8613 equipment_container,
8614 has_equipment_container,
8615 kind: PioStringView::new(kind),
8616 terminal_count: usize::from(terminal2.is_some()) + 1,
8617 terminal1: dc_terminal_view(terminal1),
8618 terminal2: terminal2.map_or_else(empty_dc_terminal_view, dc_terminal_view),
8619 rated_dc_voltage_kv: rated_dc_voltage_kv.unwrap_or(0.0),
8620 has_rated_dc_voltage: rated_dc_voltage_kv.is_some(),
8621 resistance_ohm: resistance_ohm.unwrap_or(0.0),
8622 has_resistance: resistance_ohm.is_some(),
8623 inductance_h: inductance_h.unwrap_or(0.0),
8624 has_inductance: inductance_h.is_some(),
8625 capacitance_f: capacitance_f.unwrap_or(0.0),
8626 has_capacitance: capacitance_f.is_some(),
8627 length_km: length_km.unwrap_or(0.0),
8628 has_length: length_km.is_some(),
8629 switch_kind,
8630 has_switch_kind,
8631 open: open.unwrap_or(false),
8632 has_open: open.is_some(),
8633 }
8634}
8635
8636#[unsafe(no_mangle)]
8638pub unsafe extern "C" fn pio_detailed_connectivity_omitted_field_at(
8639 details: *const PioDetailedConnectivity,
8640 index: usize,
8641 output: *mut PioOmittedFieldView,
8642 error: *mut *mut PioError,
8643) -> bool {
8644 unsafe {
8645 entry(error, false, || {
8646 let details = require_detailed_connectivity(details)?;
8647 let field = details.omitted_fields.get(index).ok_or_else(|| {
8648 boundary_error(
8649 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8650 format!("omitted field index {index} is out of range"),
8651 )
8652 })?;
8653 *require_output(output, "output")? = PioOmittedFieldView {
8654 component: component_id_view(&field.component),
8655 field: PioStringView::new(omitted_field_name(field.field)),
8656 };
8657 Ok(true)
8658 })
8659 }
8660}
8661
8662#[unsafe(no_mangle)]
8664pub unsafe extern "C" fn pio_detailed_connectivity_component_metadata_at(
8665 details: *const PioDetailedConnectivity,
8666 index: usize,
8667 output: *mut PioComponentMetadataView,
8668 error: *mut *mut PioError,
8669) -> bool {
8670 unsafe {
8671 entry(error, false, || {
8672 let details = require_detailed_connectivity(details)?;
8673 let metadata = details.component_metadata.get(index).ok_or_else(|| {
8674 boundary_error(
8675 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8676 format!("component metadata index {index} is out of range"),
8677 )
8678 })?;
8679 let (name, has_name) = optional_string_view(metadata.name.as_deref());
8680 let (equipment_container, has_equipment_container) =
8681 optional_component_id_view(metadata.equipment_container.as_ref());
8682 *require_output(output, "output")? = PioComponentMetadataView {
8683 component: component_id_view(&metadata.component),
8684 name,
8685 has_name,
8686 equipment_container,
8687 has_equipment_container,
8688 fictitious: metadata.fictitious,
8689 alias_count: metadata.aliases.len(),
8690 external_identifier_count: metadata.external_identifiers.len(),
8691 property_count: metadata.properties.len(),
8692 };
8693 Ok(true)
8694 })
8695 }
8696}
8697
8698#[unsafe(no_mangle)]
8700pub unsafe extern "C" fn pio_detailed_connectivity_component_alias_at(
8701 details: *const PioDetailedConnectivity,
8702 metadata_index: usize,
8703 alias_index: usize,
8704 output: *mut PioComponentAliasView,
8705 error: *mut *mut PioError,
8706) -> bool {
8707 unsafe {
8708 entry(error, false, || {
8709 let details = require_detailed_connectivity(details)?;
8710 let metadata = details
8711 .component_metadata
8712 .get(metadata_index)
8713 .ok_or_else(|| {
8714 boundary_error(
8715 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8716 format!("component metadata index {metadata_index} is out of range"),
8717 )
8718 })?;
8719 let alias = metadata.aliases.get(alias_index).ok_or_else(|| {
8720 boundary_error(
8721 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8722 format!("component alias index {alias_index} is out of range"),
8723 )
8724 })?;
8725 let (alias_type, has_alias_type) = optional_string_view(alias.alias_type.as_deref());
8726 *require_output(output, "output")? = PioComponentAliasView {
8727 value: PioStringView::new(&alias.value),
8728 alias_type,
8729 has_alias_type,
8730 };
8731 Ok(true)
8732 })
8733 }
8734}
8735
8736#[unsafe(no_mangle)]
8738pub unsafe extern "C" fn pio_detailed_connectivity_external_identifier_at(
8739 details: *const PioDetailedConnectivity,
8740 metadata_index: usize,
8741 identifier_index: usize,
8742 output: *mut PioExternalIdentifierView,
8743 error: *mut *mut PioError,
8744) -> bool {
8745 unsafe {
8746 entry(error, false, || {
8747 let details = require_detailed_connectivity(details)?;
8748 let metadata = details
8749 .component_metadata
8750 .get(metadata_index)
8751 .ok_or_else(|| {
8752 boundary_error(
8753 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8754 format!("component metadata index {metadata_index} is out of range"),
8755 )
8756 })?;
8757 let identifier = metadata
8758 .external_identifiers
8759 .get(identifier_index)
8760 .ok_or_else(|| {
8761 boundary_error(
8762 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8763 format!("external identifier index {identifier_index} is out of range"),
8764 )
8765 })?;
8766 let (authority, has_authority) = optional_string_view(identifier.authority.as_deref());
8767 *require_output(output, "output")? = PioExternalIdentifierView {
8768 value: PioStringView::new(&identifier.value),
8769 authority,
8770 has_authority,
8771 };
8772 Ok(true)
8773 })
8774 }
8775}
8776
8777#[unsafe(no_mangle)]
8779pub unsafe extern "C" fn pio_detailed_connectivity_component_property_at(
8780 details: *const PioDetailedConnectivity,
8781 metadata_index: usize,
8782 property_index: usize,
8783 output: *mut PioStringPropertyView,
8784 error: *mut *mut PioError,
8785) -> bool {
8786 unsafe {
8787 entry(error, false, || {
8788 let details = require_detailed_connectivity(details)?;
8789 let metadata = details
8790 .component_metadata
8791 .get(metadata_index)
8792 .ok_or_else(|| {
8793 boundary_error(
8794 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8795 format!("component metadata index {metadata_index} is out of range"),
8796 )
8797 })?;
8798 let (name, value) =
8799 metadata
8800 .properties
8801 .iter()
8802 .nth(property_index)
8803 .ok_or_else(|| {
8804 boundary_error(
8805 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8806 format!("component property index {property_index} is out of range"),
8807 )
8808 })?;
8809 *require_output(output, "output")? = PioStringPropertyView {
8810 name: PioStringView::new(name),
8811 value: PioStringView::new(value),
8812 };
8813 Ok(true)
8814 })
8815 }
8816}
8817
8818fn case_metadata_view(metadata: &powerio_tx::CaseMetadata) -> PioCaseMetadataView {
8819 let (case_date, has_case_date) = optional_string_view(metadata.case_date.as_deref());
8820 let (source_model_format, has_source_model_format) =
8821 optional_string_view(metadata.source_model_format.as_deref());
8822 let (minimum_validation_level, has_minimum_validation_level) =
8823 optional_string_view(metadata.minimum_validation_level.as_deref());
8824 PioCaseMetadataView {
8825 case_date,
8826 has_case_date,
8827 forecast_distance: metadata.forecast_distance.unwrap_or(0),
8828 has_forecast_distance: metadata.forecast_distance.is_some(),
8829 source_model_format,
8830 has_source_model_format,
8831 minimum_validation_level,
8832 has_minimum_validation_level,
8833 }
8834}
8835
8836#[unsafe(no_mangle)]
8838pub unsafe extern "C" fn pio_detailed_connectivity_subnetwork_at(
8839 details: *const PioDetailedConnectivity,
8840 index: usize,
8841 output: *mut PioSubnetworkView,
8842 error: *mut *mut PioError,
8843) -> bool {
8844 unsafe {
8845 entry(error, false, || {
8846 let details = require_detailed_connectivity(details)?;
8847 let subnetwork = details.subnetworks.get(index).ok_or_else(|| {
8848 boundary_error(
8849 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8850 format!("subnetwork index {index} is out of range"),
8851 )
8852 })?;
8853 *require_output(output, "output")? = PioSubnetworkView {
8854 component: component_id_view(&subnetwork.component),
8855 parent: component_id_view(&subnetwork.parent),
8856 case_metadata: case_metadata_view(&subnetwork.case_metadata),
8857 component_count: subnetwork.components.len(),
8858 };
8859 Ok(true)
8860 })
8861 }
8862}
8863
8864#[unsafe(no_mangle)]
8866pub unsafe extern "C" fn pio_detailed_connectivity_subnetwork_component_at(
8867 details: *const PioDetailedConnectivity,
8868 subnetwork_index: usize,
8869 component_index: usize,
8870 output: *mut PioComponentIdView,
8871 error: *mut *mut PioError,
8872) -> bool {
8873 unsafe {
8874 entry(error, false, || {
8875 let details = require_detailed_connectivity(details)?;
8876 let subnetwork = details.subnetworks.get(subnetwork_index).ok_or_else(|| {
8877 boundary_error(
8878 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8879 format!("subnetwork index {subnetwork_index} is out of range"),
8880 )
8881 })?;
8882 let component = subnetwork.components.get(component_index).ok_or_else(|| {
8883 boundary_error(
8884 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8885 format!("subnetwork component index {component_index} is out of range"),
8886 )
8887 })?;
8888 *require_output(output, "output")? = component_id_view(component);
8889 Ok(true)
8890 })
8891 }
8892}
8893
8894#[unsafe(no_mangle)]
8896pub unsafe extern "C" fn pio_detailed_connectivity_substation_at(
8897 details: *const PioDetailedConnectivity,
8898 index: usize,
8899 output: *mut PioSubstationView,
8900 error: *mut *mut PioError,
8901) -> bool {
8902 unsafe {
8903 entry(error, false, || {
8904 let details = require_detailed_connectivity(details)?;
8905 let substation = details.substations.get(index).ok_or_else(|| {
8906 boundary_error(
8907 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8908 format!("substation index {index} is out of range"),
8909 )
8910 })?;
8911 let (country, has_country) = optional_string_view(substation.country.as_deref());
8912 let (operator_name, has_operator_name) =
8913 optional_string_view(substation.operator.as_deref());
8914 *require_output(output, "output")? = PioSubstationView {
8915 component: component_id_view(&substation.component),
8916 country,
8917 has_country,
8918 operator_name,
8919 has_operator_name,
8920 geographical_tag_count: substation.geographical_tags.len(),
8921 };
8922 Ok(true)
8923 })
8924 }
8925}
8926
8927#[unsafe(no_mangle)]
8929pub unsafe extern "C" fn pio_detailed_connectivity_substation_geographical_tag_at(
8930 details: *const PioDetailedConnectivity,
8931 substation_index: usize,
8932 tag_index: usize,
8933 output: *mut PioStringView,
8934 error: *mut *mut PioError,
8935) -> bool {
8936 unsafe {
8937 entry(error, false, || {
8938 let details = require_detailed_connectivity(details)?;
8939 let substation = details.substations.get(substation_index).ok_or_else(|| {
8940 boundary_error(
8941 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8942 format!("substation index {substation_index} is out of range"),
8943 )
8944 })?;
8945 let tag = substation.geographical_tags.get(tag_index).ok_or_else(|| {
8946 boundary_error(
8947 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8948 format!("geographical tag index {tag_index} is out of range"),
8949 )
8950 })?;
8951 *require_output(output, "output")? = PioStringView::new(tag);
8952 Ok(true)
8953 })
8954 }
8955}
8956
8957#[unsafe(no_mangle)]
8959pub unsafe extern "C" fn pio_detailed_connectivity_voltage_level_at(
8960 details: *const PioDetailedConnectivity,
8961 index: usize,
8962 output: *mut PioVoltageLevelView,
8963 error: *mut *mut PioError,
8964) -> bool {
8965 unsafe {
8966 entry(error, false, || {
8967 let details = require_detailed_connectivity(details)?;
8968 let level = details.voltage_levels.get(index).ok_or_else(|| {
8969 boundary_error(
8970 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8971 format!("voltage level index {index} is out of range"),
8972 )
8973 })?;
8974 let (substation, has_substation) =
8975 optional_component_id_view(level.substation.as_ref());
8976 *require_output(output, "output")? = PioVoltageLevelView {
8977 component: component_id_view(&level.component),
8978 substation,
8979 has_substation,
8980 nominal_voltage_kv: level.nominal_kv,
8981 low_voltage_limit_kv: level.low_voltage_limit_kv.unwrap_or(0.0),
8982 has_low_voltage_limit: level.low_voltage_limit_kv.is_some(),
8983 high_voltage_limit_kv: level.high_voltage_limit_kv.unwrap_or(0.0),
8984 has_high_voltage_limit: level.high_voltage_limit_kv.is_some(),
8985 topology_kind: PioStringView::new(topology_kind_name(level.topology_kind)),
8986 bus_count: level.buses.len(),
8987 };
8988 Ok(true)
8989 })
8990 }
8991}
8992
8993#[unsafe(no_mangle)]
8995pub unsafe extern "C" fn pio_detailed_connectivity_voltage_level_bus_at(
8996 details: *const PioDetailedConnectivity,
8997 voltage_level_index: usize,
8998 bus_index: usize,
8999 output: *mut usize,
9000 error: *mut *mut PioError,
9001) -> bool {
9002 unsafe {
9003 entry(error, false, || {
9004 let details = require_detailed_connectivity(details)?;
9005 let level = details
9006 .voltage_levels
9007 .get(voltage_level_index)
9008 .ok_or_else(|| {
9009 boundary_error(
9010 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9011 format!("voltage level index {voltage_level_index} is out of range"),
9012 )
9013 })?;
9014 let bus = level.buses.get(bus_index).ok_or_else(|| {
9015 boundary_error(
9016 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9017 format!("voltage level bus index {bus_index} is out of range"),
9018 )
9019 })?;
9020 *require_output(output, "output")? = bus.0;
9021 Ok(true)
9022 })
9023 }
9024}
9025
9026#[unsafe(no_mangle)]
9028pub unsafe extern "C" fn pio_detailed_connectivity_bus_breaker_bus_at(
9029 details: *const PioDetailedConnectivity,
9030 index: usize,
9031 output: *mut PioBusBreakerBusView,
9032 error: *mut *mut PioError,
9033) -> bool {
9034 unsafe {
9035 entry(error, false, || {
9036 let details = require_detailed_connectivity(details)?;
9037 let bus = details.bus_breaker_buses.get(index).ok_or_else(|| {
9038 boundary_error(
9039 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9040 format!("bus breaker bus index {index} is out of range"),
9041 )
9042 })?;
9043 *require_output(output, "output")? = PioBusBreakerBusView {
9044 component: component_id_view(&bus.component),
9045 voltage_level: component_id_view(&bus.voltage_level),
9046 calculated_bus_id: bus.calculated_bus.map_or(0, |value| value.0),
9047 has_calculated_bus: bus.calculated_bus.is_some(),
9048 voltage_kv: bus.voltage_kv.unwrap_or(0.0),
9049 has_voltage: bus.voltage_kv.is_some(),
9050 angle_degrees: bus.angle_degrees.unwrap_or(0.0),
9051 has_angle: bus.angle_degrees.is_some(),
9052 };
9053 Ok(true)
9054 })
9055 }
9056}
9057
9058#[unsafe(no_mangle)]
9060pub unsafe extern "C" fn pio_detailed_connectivity_calculated_bus_at(
9061 details: *const PioDetailedConnectivity,
9062 index: usize,
9063 output: *mut PioCalculatedBusView,
9064 error: *mut *mut PioError,
9065) -> bool {
9066 unsafe {
9067 entry(error, false, || {
9068 let details = require_detailed_connectivity(details)?;
9069 let bus = details.calculated_buses.get(index).ok_or_else(|| {
9070 boundary_error(
9071 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9072 format!("calculated bus index {index} is out of range"),
9073 )
9074 })?;
9075 *require_output(output, "output")? = PioCalculatedBusView {
9076 voltage_level: component_id_view(&bus.voltage_level),
9077 calculated_bus_id: bus.calculated_bus.0,
9078 node_count: bus.nodes.len(),
9079 voltage_kv: bus.voltage_kv.unwrap_or(0.0),
9080 has_voltage: bus.voltage_kv.is_some(),
9081 angle_degrees: bus.angle_degrees.unwrap_or(0.0),
9082 has_angle: bus.angle_degrees.is_some(),
9083 };
9084 Ok(true)
9085 })
9086 }
9087}
9088
9089#[unsafe(no_mangle)]
9091pub unsafe extern "C" fn pio_detailed_connectivity_calculated_bus_node_at(
9092 details: *const PioDetailedConnectivity,
9093 calculated_bus_index: usize,
9094 node_index: usize,
9095 output: *mut PioComponentIdView,
9096 error: *mut *mut PioError,
9097) -> bool {
9098 unsafe {
9099 entry(error, false, || {
9100 let details = require_detailed_connectivity(details)?;
9101 let bus = details
9102 .calculated_buses
9103 .get(calculated_bus_index)
9104 .ok_or_else(|| {
9105 boundary_error(
9106 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9107 format!("calculated bus index {calculated_bus_index} is out of range"),
9108 )
9109 })?;
9110 let node = bus.nodes.get(node_index).ok_or_else(|| {
9111 boundary_error(
9112 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9113 format!("calculated bus node index {node_index} is out of range"),
9114 )
9115 })?;
9116 *require_output(output, "output")? = component_id_view(node);
9117 Ok(true)
9118 })
9119 }
9120}
9121
9122#[unsafe(no_mangle)]
9124pub unsafe extern "C" fn pio_detailed_connectivity_node_at(
9125 details: *const PioDetailedConnectivity,
9126 index: usize,
9127 output: *mut PioConnectivityNodeView,
9128 error: *mut *mut PioError,
9129) -> bool {
9130 unsafe {
9131 entry(error, false, || {
9132 let details = require_detailed_connectivity(details)?;
9133 let node = details.connectivity_nodes.get(index).ok_or_else(|| {
9134 boundary_error(
9135 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9136 format!("connectivity node index {index} is out of range"),
9137 )
9138 })?;
9139 *require_output(output, "output")? = PioConnectivityNodeView {
9140 component: component_id_view(&node.component),
9141 voltage_level: component_id_view(&node.voltage_level),
9142 node_number: node.node_number.unwrap_or(0),
9143 has_node_number: node.node_number.is_some(),
9144 calculated_bus_id: node.calculated_bus.map_or(0, |bus| bus.0),
9145 has_calculated_bus: node.calculated_bus.is_some(),
9146 };
9147 Ok(true)
9148 })
9149 }
9150}
9151
9152#[unsafe(no_mangle)]
9154pub unsafe extern "C" fn pio_detailed_connectivity_busbar_section_at(
9155 details: *const PioDetailedConnectivity,
9156 index: usize,
9157 output: *mut PioBusbarSectionView,
9158 error: *mut *mut PioError,
9159) -> bool {
9160 unsafe {
9161 entry(error, false, || {
9162 let details = require_detailed_connectivity(details)?;
9163 let section = details.busbar_sections.get(index).ok_or_else(|| {
9164 boundary_error(
9165 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9166 format!("busbar section index {index} is out of range"),
9167 )
9168 })?;
9169 *require_output(output, "output")? = PioBusbarSectionView {
9170 component: component_id_view(§ion.component),
9171 voltage_level: component_id_view(§ion.voltage_level),
9172 node: component_id_view(§ion.node),
9173 };
9174 Ok(true)
9175 })
9176 }
9177}
9178
9179#[unsafe(no_mangle)]
9181pub unsafe extern "C" fn pio_detailed_connectivity_junction_at(
9182 details: *const PioDetailedConnectivity,
9183 index: usize,
9184 output: *mut PioJunctionView,
9185 error: *mut *mut PioError,
9186) -> bool {
9187 unsafe {
9188 entry(error, false, || {
9189 let details = require_detailed_connectivity(details)?;
9190 let junction = details.junctions.get(index).ok_or_else(|| {
9191 boundary_error(
9192 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9193 format!("junction index {index} is out of range"),
9194 )
9195 })?;
9196 *require_output(output, "output")? = PioJunctionView {
9197 component: component_id_view(&junction.component),
9198 };
9199 Ok(true)
9200 })
9201 }
9202}
9203
9204#[unsafe(no_mangle)]
9206pub unsafe extern "C" fn pio_detailed_connectivity_terminal_at(
9207 details: *const PioDetailedConnectivity,
9208 index: usize,
9209 output: *mut PioDetailedTerminalView,
9210 error: *mut *mut PioError,
9211) -> bool {
9212 unsafe {
9213 entry(error, false, || {
9214 let details = require_detailed_connectivity(details)?;
9215 let terminal = details.terminals.get(index).ok_or_else(|| {
9216 boundary_error(
9217 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9218 format!("terminal index {index} is out of range"),
9219 )
9220 })?;
9221 let (bus, has_bus) = optional_component_id_view(terminal.bus.as_ref());
9222 let (connectable_bus, has_connectable_bus) =
9223 optional_component_id_view(terminal.connectable_bus.as_ref());
9224 let (node, has_node) = optional_component_id_view(terminal.node.as_ref());
9225 let (component, has_component) =
9226 optional_component_id_view(terminal.component.as_ref());
9227 *require_output(output, "output")? = PioDetailedTerminalView {
9228 component,
9229 has_component,
9230 equipment: component_id_view(&terminal.equipment),
9231 terminal: terminal.terminal,
9232 voltage_level: component_id_view(&terminal.voltage_level),
9233 bus,
9234 has_bus,
9235 connectable_bus,
9236 has_connectable_bus,
9237 node,
9238 has_node,
9239 connected: terminal.connected,
9240 active_power_mw: terminal.active_power_mw.unwrap_or(0.0),
9241 has_active_power: terminal.active_power_mw.is_some(),
9242 reactive_power_mvar: terminal.reactive_power_mvar.unwrap_or(0.0),
9243 has_reactive_power: terminal.reactive_power_mvar.is_some(),
9244 };
9245 Ok(true)
9246 })
9247 }
9248}
9249
9250#[unsafe(no_mangle)]
9252pub unsafe extern "C" fn pio_detailed_connectivity_switch_at(
9253 details: *const PioDetailedConnectivity,
9254 index: usize,
9255 output: *mut PioTopologySwitchView,
9256 error: *mut *mut PioError,
9257) -> bool {
9258 unsafe {
9259 entry(error, false, || {
9260 let details = require_detailed_connectivity(details)?;
9261 let switch = details.switches.get(index).ok_or_else(|| {
9262 boundary_error(
9263 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9264 format!("topology switch index {index} is out of range"),
9265 )
9266 })?;
9267 let (endpoint1_kind, endpoint1) = topology_endpoint_view(&switch.endpoint1);
9268 let (endpoint2_kind, endpoint2) = topology_endpoint_view(&switch.endpoint2);
9269 *require_output(output, "output")? = PioTopologySwitchView {
9270 component: component_id_view(&switch.component),
9271 voltage_level: component_id_view(&switch.voltage_level),
9272 kind: PioStringView::new(topology_switch_kind_name(switch.kind)),
9273 endpoint1_kind,
9274 endpoint1,
9275 endpoint2_kind,
9276 endpoint2,
9277 open: switch.open,
9278 retained: switch.retained,
9279 };
9280 Ok(true)
9281 })
9282 }
9283}
9284
9285#[unsafe(no_mangle)]
9287pub unsafe extern "C" fn pio_detailed_connectivity_internal_connection_at(
9288 details: *const PioDetailedConnectivity,
9289 index: usize,
9290 output: *mut PioInternalConnectionView,
9291 error: *mut *mut PioError,
9292) -> bool {
9293 unsafe {
9294 entry(error, false, || {
9295 let details = require_detailed_connectivity(details)?;
9296 let connection = details.internal_connections.get(index).ok_or_else(|| {
9297 boundary_error(
9298 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9299 format!("internal connection index {index} is out of range"),
9300 )
9301 })?;
9302 *require_output(output, "output")? = PioInternalConnectionView {
9303 voltage_level: component_id_view(&connection.voltage_level),
9304 node1: component_id_view(&connection.node1),
9305 node2: component_id_view(&connection.node2),
9306 };
9307 Ok(true)
9308 })
9309 }
9310}
9311
9312#[unsafe(no_mangle)]
9314pub unsafe extern "C" fn pio_detailed_connectivity_operational_limit_group_at(
9315 details: *const PioDetailedConnectivity,
9316 index: usize,
9317 output: *mut PioOperationalLimitGroupView,
9318 error: *mut *mut PioError,
9319) -> bool {
9320 unsafe {
9321 entry(error, false, || {
9322 let details = require_detailed_connectivity(details)?;
9323 let group = details.operational_limit_groups.get(index).ok_or_else(|| {
9324 boundary_error(
9325 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9326 format!("operational limit group index {index} is out of range"),
9327 )
9328 })?;
9329 let current = group.current_limits.as_ref();
9330 let active = group.active_power_limits.as_ref();
9331 let apparent = group.apparent_power_limits.as_ref();
9332 let (current_name, has_current_name) = optional_string_view(
9333 current.and_then(|limits| limits.permanent_limit_name.as_deref()),
9334 );
9335 let (active_name, has_active_name) = optional_string_view(
9336 active.and_then(|limits| limits.permanent_limit_name.as_deref()),
9337 );
9338 let (apparent_name, has_apparent_name) = optional_string_view(
9339 apparent.and_then(|limits| limits.permanent_limit_name.as_deref()),
9340 );
9341 *require_output(output, "output")? = PioOperationalLimitGroupView {
9342 equipment: component_id_view(&group.equipment),
9343 terminal: group.terminal,
9344 id: PioStringView::new(&group.id),
9345 selected: group.selected,
9346 property_count: group.properties.len(),
9347 has_current_limits: current.is_some(),
9348 current_permanent_limit_a: current
9349 .and_then(|limits| limits.permanent_limit)
9350 .unwrap_or(0.0),
9351 current_permanent_limit_name: current_name,
9352 has_current_permanent_limit: current
9353 .is_some_and(|limits| limits.permanent_limit.is_some()),
9354 has_current_permanent_limit_name: has_current_name,
9355 current_temporary_limit_count: current
9356 .map_or(0, |limits| limits.temporary_limits.len()),
9357 has_active_power_limits: active.is_some(),
9358 active_power_permanent_limit_mw: active
9359 .and_then(|limits| limits.permanent_limit)
9360 .unwrap_or(0.0),
9361 active_power_permanent_limit_name: active_name,
9362 has_active_power_permanent_limit: active
9363 .is_some_and(|limits| limits.permanent_limit.is_some()),
9364 has_active_power_permanent_limit_name: has_active_name,
9365 active_power_temporary_limit_count: active
9366 .map_or(0, |limits| limits.temporary_limits.len()),
9367 has_apparent_power_limits: apparent.is_some(),
9368 apparent_power_permanent_limit_mva: apparent
9369 .and_then(|limits| limits.permanent_limit)
9370 .unwrap_or(0.0),
9371 apparent_power_permanent_limit_name: apparent_name,
9372 has_apparent_power_permanent_limit: apparent
9373 .is_some_and(|limits| limits.permanent_limit.is_some()),
9374 has_apparent_power_permanent_limit_name: has_apparent_name,
9375 apparent_power_temporary_limit_count: apparent
9376 .map_or(0, |limits| limits.temporary_limits.len()),
9377 };
9378 Ok(true)
9379 })
9380 }
9381}
9382
9383#[unsafe(no_mangle)]
9385pub unsafe extern "C" fn pio_detailed_connectivity_operational_limit_group_property_at(
9386 details: *const PioDetailedConnectivity,
9387 group_index: usize,
9388 property_index: usize,
9389 output: *mut PioStringPropertyView,
9390 error: *mut *mut PioError,
9391) -> bool {
9392 unsafe {
9393 entry(error, false, || {
9394 let details = require_detailed_connectivity(details)?;
9395 let group = details
9396 .operational_limit_groups
9397 .get(group_index)
9398 .ok_or_else(|| {
9399 boundary_error(
9400 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9401 format!("operational limit group index {group_index} is out of range"),
9402 )
9403 })?;
9404 *require_output(output, "output")? =
9405 string_property_view(&group.properties, property_index)?;
9406 Ok(true)
9407 })
9408 }
9409}
9410
9411#[unsafe(no_mangle)]
9413pub unsafe extern "C" fn pio_detailed_connectivity_temporary_limit_at(
9414 details: *const PioDetailedConnectivity,
9415 group_index: usize,
9416 quantity: *const c_char,
9417 quantity_len: usize,
9418 limit_index: usize,
9419 output: *mut PioTemporaryLimitView,
9420 error: *mut *mut PioError,
9421) -> bool {
9422 unsafe {
9423 entry(error, false, || {
9424 let details = require_detailed_connectivity(details)?;
9425 let group = details
9426 .operational_limit_groups
9427 .get(group_index)
9428 .ok_or_else(|| {
9429 boundary_error(
9430 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9431 format!("operational limit group index {group_index} is out of range"),
9432 )
9433 })?;
9434 let quantity = required_str(quantity, quantity_len, "quantity")?;
9435 let limits = match quantity {
9436 "current" => group.current_limits.as_ref(),
9437 "active_power" => group.active_power_limits.as_ref(),
9438 "apparent_power" => group.apparent_power_limits.as_ref(),
9439 _ => {
9440 return Err(boundary_error(
9441 &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
9442 "quantity must be current, active_power, or apparent_power",
9443 ));
9444 }
9445 }
9446 .ok_or_else(|| {
9447 boundary_error(
9448 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9449 format!("operational limit group {group_index} has no {quantity} limits"),
9450 )
9451 })?;
9452 let limit = limits.temporary_limits.get(limit_index).ok_or_else(|| {
9453 boundary_error(
9454 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9455 format!("temporary limit index {limit_index} is out of range"),
9456 )
9457 })?;
9458 *require_output(output, "output")? = PioTemporaryLimitView {
9459 name: PioStringView::new(&limit.name),
9460 value: limit.value,
9461 acceptable_duration_seconds: limit.acceptable_duration_seconds,
9462 fictitious: limit.fictitious,
9463 };
9464 Ok(true)
9465 })
9466 }
9467}
9468
9469#[unsafe(no_mangle)]
9471pub unsafe extern "C" fn pio_detailed_connectivity_boundary_line_at(
9472 details: *const PioDetailedConnectivity,
9473 index: usize,
9474 output: *mut PioBoundaryLineView,
9475 error: *mut *mut PioError,
9476) -> bool {
9477 unsafe {
9478 entry(error, false, || {
9479 let details = require_detailed_connectivity(details)?;
9480 let line = details.boundary_lines.get(index).ok_or_else(|| {
9481 boundary_error(
9482 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9483 format!("boundary line index {index} is out of range"),
9484 )
9485 })?;
9486 let (pairing_key, has_pairing_key) = optional_string_view(line.pairing_key.as_deref());
9487 let (generation, has_generation) =
9488 boundary_line_generation_view(line.generation.as_ref());
9489 let (calculation_load, has_calculation_load) =
9490 optional_component_id_view(line.calculation_load.as_ref());
9491 let (calculation_generator, has_calculation_generator) =
9492 optional_component_id_view(line.calculation_generator.as_ref());
9493 *require_output(output, "output")? = PioBoundaryLineView {
9494 component: component_id_view(&line.component),
9495 voltage_level: component_id_view(&line.voltage_level),
9496 active_power_setpoint_mw: line.active_power_setpoint_mw,
9497 reactive_power_setpoint_mvar: line.reactive_power_setpoint_mvar,
9498 resistance_ohm: line.resistance_ohm,
9499 reactance_ohm: line.reactance_ohm,
9500 conductance_siemens: line.conductance_siemens,
9501 susceptance_siemens: line.susceptance_siemens,
9502 pairing_key,
9503 has_pairing_key,
9504 generation,
9505 has_generation,
9506 calculation_load,
9507 has_calculation_load,
9508 calculation_generator,
9509 has_calculation_generator,
9510 };
9511 Ok(true)
9512 })
9513 }
9514}
9515
9516#[unsafe(no_mangle)]
9518pub unsafe extern "C" fn pio_detailed_connectivity_boundary_line_reactive_limit_property_at(
9519 details: *const PioDetailedConnectivity,
9520 boundary_line_index: usize,
9521 property_index: usize,
9522 output: *mut PioStringPropertyView,
9523 error: *mut *mut PioError,
9524) -> bool {
9525 unsafe {
9526 entry(error, false, || {
9527 let details = require_detailed_connectivity(details)?;
9528 let line = details
9529 .boundary_lines
9530 .get(boundary_line_index)
9531 .ok_or_else(|| {
9532 boundary_error(
9533 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9534 format!("boundary line index {boundary_line_index} is out of range"),
9535 )
9536 })?;
9537 let limits = line
9538 .generation
9539 .as_ref()
9540 .and_then(|generation| generation.reactive_limits.as_ref())
9541 .ok_or_else(|| {
9542 boundary_error(
9543 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9544 format!(
9545 "boundary line {boundary_line_index} has no generation reactive limits"
9546 ),
9547 )
9548 })?;
9549 let properties = match limits {
9550 powerio_tx::ReactiveLimits::MinMax(limits) => &limits.properties,
9551 powerio_tx::ReactiveLimits::CapabilityCurve(curve) => &curve.properties,
9552 _ => {
9553 return Err(boundary_error(
9554 &codes::REQUEST_CAPI_TYPE_MISMATCH,
9555 "unsupported reactive limit record",
9556 ));
9557 }
9558 };
9559 let (name, value) = properties.iter().nth(property_index).ok_or_else(|| {
9560 boundary_error(
9561 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9562 format!("reactive limit property index {property_index} is out of range"),
9563 )
9564 })?;
9565 *require_output(output, "output")? = PioStringPropertyView {
9566 name: PioStringView::new(name),
9567 value: PioStringView::new(value),
9568 };
9569 Ok(true)
9570 })
9571 }
9572}
9573
9574#[unsafe(no_mangle)]
9576pub unsafe extern "C" fn pio_detailed_connectivity_boundary_line_reactive_capability_point_at(
9577 details: *const PioDetailedConnectivity,
9578 boundary_line_index: usize,
9579 point_index: usize,
9580 output: *mut PioReactiveCapabilityCurvePointView,
9581 error: *mut *mut PioError,
9582) -> bool {
9583 unsafe {
9584 entry(error, false, || {
9585 let details = require_detailed_connectivity(details)?;
9586 let line = details
9587 .boundary_lines
9588 .get(boundary_line_index)
9589 .ok_or_else(|| {
9590 boundary_error(
9591 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9592 format!("boundary line index {boundary_line_index} is out of range"),
9593 )
9594 })?;
9595 let curve = line
9596 .generation
9597 .as_ref()
9598 .and_then(|generation| generation.reactive_limits.as_ref())
9599 .and_then(|limits| match limits {
9600 powerio_tx::ReactiveLimits::CapabilityCurve(curve) => Some(curve),
9601 _ => None,
9602 })
9603 .ok_or_else(|| {
9604 boundary_error(
9605 &codes::REQUEST_CAPI_TYPE_MISMATCH,
9606 format!(
9607 "boundary line {boundary_line_index} has no generation reactive capability curve"
9608 ),
9609 )
9610 })?;
9611 let point = curve.points.get(point_index).ok_or_else(|| {
9612 boundary_error(
9613 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9614 format!("reactive capability point index {point_index} is out of range"),
9615 )
9616 })?;
9617 *require_output(output, "output")? = PioReactiveCapabilityCurvePointView {
9618 active_power_mw: point.active_power_mw,
9619 minimum_reactive_power_mvar: point.minimum_reactive_power_mvar,
9620 maximum_reactive_power_mvar: point.maximum_reactive_power_mvar,
9621 property_count: point.properties.len(),
9622 };
9623 Ok(true)
9624 })
9625 }
9626}
9627
9628#[unsafe(no_mangle)]
9630pub unsafe extern "C" fn pio_detailed_connectivity_boundary_line_reactive_capability_point_property_at(
9631 details: *const PioDetailedConnectivity,
9632 boundary_line_index: usize,
9633 point_index: usize,
9634 property_index: usize,
9635 output: *mut PioStringPropertyView,
9636 error: *mut *mut PioError,
9637) -> bool {
9638 unsafe {
9639 entry(error, false, || {
9640 let details = require_detailed_connectivity(details)?;
9641 let line = details
9642 .boundary_lines
9643 .get(boundary_line_index)
9644 .ok_or_else(|| {
9645 boundary_error(
9646 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9647 format!("boundary line index {boundary_line_index} is out of range"),
9648 )
9649 })?;
9650 let curve = line
9651 .generation
9652 .as_ref()
9653 .and_then(|generation| generation.reactive_limits.as_ref())
9654 .and_then(|limits| match limits {
9655 powerio_tx::ReactiveLimits::CapabilityCurve(curve) => Some(curve),
9656 _ => None,
9657 })
9658 .ok_or_else(|| {
9659 boundary_error(
9660 &codes::REQUEST_CAPI_TYPE_MISMATCH,
9661 format!(
9662 "boundary line {boundary_line_index} has no generation reactive capability curve"
9663 ),
9664 )
9665 })?;
9666 let point = curve.points.get(point_index).ok_or_else(|| {
9667 boundary_error(
9668 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9669 format!("reactive capability point index {point_index} is out of range"),
9670 )
9671 })?;
9672 let (name, value) = point.properties.iter().nth(property_index).ok_or_else(|| {
9673 boundary_error(
9674 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9675 format!(
9676 "reactive capability point property index {property_index} is out of range"
9677 ),
9678 )
9679 })?;
9680 *require_output(output, "output")? = PioStringPropertyView {
9681 name: PioStringView::new(name),
9682 value: PioStringView::new(value),
9683 };
9684 Ok(true)
9685 })
9686 }
9687}
9688
9689#[unsafe(no_mangle)]
9691pub unsafe extern "C" fn pio_detailed_connectivity_tie_line_at(
9692 details: *const PioDetailedConnectivity,
9693 index: usize,
9694 output: *mut PioTieLineView,
9695 error: *mut *mut PioError,
9696) -> bool {
9697 unsafe {
9698 entry(error, false, || {
9699 let details = require_detailed_connectivity(details)?;
9700 let tie = details.tie_lines.get(index).ok_or_else(|| {
9701 boundary_error(
9702 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9703 format!("tie line index {index} is out of range"),
9704 )
9705 })?;
9706 let (calculation_branch, has_calculation_branch) =
9707 optional_component_id_view(tie.calculation_branch.as_ref());
9708 *require_output(output, "output")? = PioTieLineView {
9709 component: component_id_view(&tie.component),
9710 boundary_line1: component_id_view(&tie.boundary_line1),
9711 boundary_line2: component_id_view(&tie.boundary_line2),
9712 calculation_branch,
9713 has_calculation_branch,
9714 };
9715 Ok(true)
9716 })
9717 }
9718}
9719
9720#[unsafe(no_mangle)]
9722pub unsafe extern "C" fn pio_detailed_connectivity_tap_changer_at(
9723 details: *const PioDetailedConnectivity,
9724 index: usize,
9725 output: *mut PioTapChangerView,
9726 error: *mut *mut PioError,
9727) -> bool {
9728 unsafe {
9729 entry(error, false, || {
9730 let details = require_detailed_connectivity(details)?;
9731 let changer = details.tap_changers.get(index).ok_or_else(|| {
9732 boundary_error(
9733 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9734 format!("tap changer index {index} is out of range"),
9735 )
9736 })?;
9737 let (regulation_mode, has_regulation_mode) =
9738 changer
9739 .regulation_mode
9740 .map_or((PioStringView::EMPTY, false), |mode| {
9741 (
9742 PioStringView::new(tap_changer_regulation_mode_name(mode)),
9743 true,
9744 )
9745 });
9746 let (regulation_terminal, has_regulation_terminal) =
9747 terminal_reference_view(changer.regulation_terminal.as_ref());
9748 let (component, has_component) = optional_component_id_view(changer.component.as_ref());
9749 *require_output(output, "output")? = PioTapChangerView {
9750 component,
9751 has_component,
9752 transformer: component_id_view(&changer.transformer),
9753 winding: changer.winding,
9754 kind: PioStringView::new(tap_changer_kind_name(changer.kind)),
9755 tap_position: changer.tap_position.unwrap_or(0),
9756 has_tap_position: changer.tap_position.is_some(),
9757 solved_tap_position: changer.solved_tap_position.unwrap_or(0),
9758 has_solved_tap_position: changer.solved_tap_position.is_some(),
9759 low_tap_position: changer.low_tap_position,
9760 neutral_tap_position: changer.neutral_tap_position.unwrap_or(0),
9761 has_neutral_tap_position: changer.neutral_tap_position.is_some(),
9762 normal_tap_position: changer.normal_tap_position.unwrap_or(0),
9763 has_normal_tap_position: changer.normal_tap_position.is_some(),
9764 voltage_step_increment_percent: changer
9765 .voltage_step_increment_percent
9766 .unwrap_or(0.0),
9767 has_voltage_step_increment_percent: changer
9768 .voltage_step_increment_percent
9769 .is_some(),
9770 load_tap_changing_capabilities: changer.load_tap_changing_capabilities,
9771 regulating: changer.regulating,
9772 regulation_mode,
9773 has_regulation_mode,
9774 regulation_value: changer.regulation_value.unwrap_or(0.0),
9775 has_regulation_value: changer.regulation_value.is_some(),
9776 target_deadband: changer.target_deadband.unwrap_or(0.0),
9777 has_target_deadband: changer.target_deadband.is_some(),
9778 regulation_terminal,
9779 has_regulation_terminal,
9780 step_count: changer.steps.len(),
9781 };
9782 Ok(true)
9783 })
9784 }
9785}
9786
9787#[unsafe(no_mangle)]
9789pub unsafe extern "C" fn pio_detailed_connectivity_tap_changer_step_at(
9790 details: *const PioDetailedConnectivity,
9791 tap_changer_index: usize,
9792 step_index: usize,
9793 output: *mut PioTapChangerStepView,
9794 error: *mut *mut PioError,
9795) -> bool {
9796 unsafe {
9797 entry(error, false, || {
9798 let details = require_detailed_connectivity(details)?;
9799 let changer = details.tap_changers.get(tap_changer_index).ok_or_else(|| {
9800 boundary_error(
9801 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9802 format!("tap changer index {tap_changer_index} is out of range"),
9803 )
9804 })?;
9805 let step = changer.steps.get(step_index).ok_or_else(|| {
9806 boundary_error(
9807 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9808 format!("tap changer step index {step_index} is out of range"),
9809 )
9810 })?;
9811 *require_output(output, "output")? = PioTapChangerStepView {
9812 position: step.position,
9813 ratio_pu: step.rho,
9814 phase_shift_degrees: step.alpha_degrees,
9815 resistance_deviation_percent: step.resistance_deviation_percent,
9816 reactance_deviation_percent: step.reactance_deviation_percent,
9817 conductance_deviation_percent: step.conductance_deviation_percent,
9818 susceptance_deviation_percent: step.susceptance_deviation_percent,
9819 };
9820 Ok(true)
9821 })
9822 }
9823}
9824
9825#[unsafe(no_mangle)]
9827pub unsafe extern "C" fn pio_detailed_connectivity_equipment_reactive_limits_at(
9828 details: *const PioDetailedConnectivity,
9829 index: usize,
9830 output: *mut PioEquipmentReactiveLimitsView,
9831 error: *mut *mut PioError,
9832) -> bool {
9833 unsafe {
9834 entry(error, false, || {
9835 let details = require_detailed_connectivity(details)?;
9836 let record = details
9837 .equipment_reactive_limits
9838 .get(index)
9839 .ok_or_else(|| {
9840 boundary_error(
9841 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9842 format!("equipment reactive limits index {index} is out of range"),
9843 )
9844 })?;
9845 *require_output(output, "output")? = PioEquipmentReactiveLimitsView {
9846 equipment: component_id_view(&record.equipment),
9847 limits: reactive_limits_view(Some(&record.limits)).0,
9848 };
9849 Ok(true)
9850 })
9851 }
9852}
9853
9854#[unsafe(no_mangle)]
9856pub unsafe extern "C" fn pio_detailed_connectivity_equipment_reactive_limit_property_at(
9857 details: *const PioDetailedConnectivity,
9858 equipment_index: usize,
9859 property_index: usize,
9860 output: *mut PioStringPropertyView,
9861 error: *mut *mut PioError,
9862) -> bool {
9863 unsafe {
9864 entry(error, false, || {
9865 let details = require_detailed_connectivity(details)?;
9866 let record = details
9867 .equipment_reactive_limits
9868 .get(equipment_index)
9869 .ok_or_else(|| {
9870 boundary_error(
9871 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9872 format!(
9873 "equipment reactive limits index {equipment_index} is out of range"
9874 ),
9875 )
9876 })?;
9877 *require_output(output, "output")? =
9878 string_property_view(reactive_limit_properties(&record.limits), property_index)?;
9879 Ok(true)
9880 })
9881 }
9882}
9883
9884#[unsafe(no_mangle)]
9886pub unsafe extern "C" fn pio_detailed_connectivity_equipment_reactive_capability_point_at(
9887 details: *const PioDetailedConnectivity,
9888 equipment_index: usize,
9889 point_index: usize,
9890 output: *mut PioReactiveCapabilityCurvePointView,
9891 error: *mut *mut PioError,
9892) -> bool {
9893 unsafe {
9894 entry(error, false, || {
9895 let details = require_detailed_connectivity(details)?;
9896 let record = details
9897 .equipment_reactive_limits
9898 .get(equipment_index)
9899 .ok_or_else(|| {
9900 boundary_error(
9901 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9902 format!(
9903 "equipment reactive limits index {equipment_index} is out of range"
9904 ),
9905 )
9906 })?;
9907 let curve = reactive_capability_curve(&record.limits).ok_or_else(|| {
9908 boundary_error(
9909 &codes::REQUEST_CAPI_TYPE_MISMATCH,
9910 format!(
9911 "equipment reactive limits {equipment_index} is not a capability curve"
9912 ),
9913 )
9914 })?;
9915 *require_output(output, "output")? =
9916 reactive_capability_point_view(curve, point_index)?;
9917 Ok(true)
9918 })
9919 }
9920}
9921
9922#[unsafe(no_mangle)]
9924pub unsafe extern "C" fn pio_detailed_connectivity_equipment_reactive_capability_point_property_at(
9925 details: *const PioDetailedConnectivity,
9926 equipment_index: usize,
9927 point_index: usize,
9928 property_index: usize,
9929 output: *mut PioStringPropertyView,
9930 error: *mut *mut PioError,
9931) -> bool {
9932 unsafe {
9933 entry(error, false, || {
9934 let details = require_detailed_connectivity(details)?;
9935 let record = details
9936 .equipment_reactive_limits
9937 .get(equipment_index)
9938 .ok_or_else(|| {
9939 boundary_error(
9940 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9941 format!(
9942 "equipment reactive limits index {equipment_index} is out of range"
9943 ),
9944 )
9945 })?;
9946 let curve = reactive_capability_curve(&record.limits).ok_or_else(|| {
9947 boundary_error(
9948 &codes::REQUEST_CAPI_TYPE_MISMATCH,
9949 format!(
9950 "equipment reactive limits {equipment_index} is not a capability curve"
9951 ),
9952 )
9953 })?;
9954 let point = curve.points.get(point_index).ok_or_else(|| {
9955 boundary_error(
9956 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9957 format!("reactive capability point index {point_index} is out of range"),
9958 )
9959 })?;
9960 *require_output(output, "output")? =
9961 string_property_view(&point.properties, property_index)?;
9962 Ok(true)
9963 })
9964 }
9965}
9966
9967#[unsafe(no_mangle)]
9969pub unsafe extern "C" fn pio_detailed_connectivity_dc_converter_unit_at(
9970 details: *const PioDetailedConnectivity,
9971 index: usize,
9972 output: *mut PioDcConverterUnitView,
9973 error: *mut *mut PioError,
9974) -> bool {
9975 unsafe {
9976 entry(error, false, || {
9977 let details = require_detailed_connectivity(details)?;
9978 let unit = details.dc_converter_units.get(index).ok_or_else(|| {
9979 boundary_error(
9980 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9981 format!("DC converter unit index {index} is out of range"),
9982 )
9983 })?;
9984 let (substation, has_substation) = optional_component_id_view(unit.substation.as_ref());
9985 *require_output(output, "output")? = PioDcConverterUnitView {
9986 component: component_id_view(&unit.component),
9987 substation,
9988 has_substation,
9989 operation_mode: PioStringView::new(dc_converter_operation_mode_name(
9990 unit.operation_mode,
9991 )),
9992 };
9993 Ok(true)
9994 })
9995 }
9996}
9997
9998#[unsafe(no_mangle)]
10000pub unsafe extern "C" fn pio_detailed_connectivity_dc_topological_node_at(
10001 details: *const PioDetailedConnectivity,
10002 index: usize,
10003 output: *mut PioDcNodeView,
10004 error: *mut *mut PioError,
10005) -> bool {
10006 unsafe {
10007 entry(error, false, || {
10008 let details = require_detailed_connectivity(details)?;
10009 let node = details.dc_topological_nodes.get(index).ok_or_else(|| {
10010 boundary_error(
10011 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10012 format!("DC topological node index {index} is out of range"),
10013 )
10014 })?;
10015 let (dc_converter_unit, has_dc_converter_unit) =
10016 optional_component_id_view(node.dc_converter_unit.as_ref());
10017 *require_output(output, "output")? = PioDcNodeView {
10018 component: component_id_view(&node.component),
10019 kind: PioStringView::new("topological_node"),
10020 nominal_voltage_kv: 0.0,
10021 has_nominal_voltage: false,
10022 voltage_kv: 0.0,
10023 has_voltage: false,
10024 dc_converter_unit,
10025 has_dc_converter_unit,
10026 dc_topological_node: empty_component_id_view(),
10027 has_dc_topological_node: false,
10028 };
10029 Ok(true)
10030 })
10031 }
10032}
10033
10034#[unsafe(no_mangle)]
10036pub unsafe extern "C" fn pio_detailed_connectivity_dc_node_at(
10037 details: *const PioDetailedConnectivity,
10038 index: usize,
10039 output: *mut PioDcNodeView,
10040 error: *mut *mut PioError,
10041) -> bool {
10042 unsafe {
10043 entry(error, false, || {
10044 let details = require_detailed_connectivity(details)?;
10045 let node = details.dc_nodes.get(index).ok_or_else(|| {
10046 boundary_error(
10047 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10048 format!("DC node index {index} is out of range"),
10049 )
10050 })?;
10051 let (dc_converter_unit, has_dc_converter_unit) =
10052 optional_component_id_view(node.dc_converter_unit.as_ref());
10053 let (dc_topological_node, has_dc_topological_node) =
10054 optional_component_id_view(node.dc_topological_node.as_ref());
10055 *require_output(output, "output")? = PioDcNodeView {
10056 component: component_id_view(&node.component),
10057 kind: PioStringView::new("node"),
10058 nominal_voltage_kv: node.nominal_voltage_kv.unwrap_or(0.0),
10059 has_nominal_voltage: node.nominal_voltage_kv.is_some(),
10060 voltage_kv: node.voltage_kv.unwrap_or(0.0),
10061 has_voltage: node.voltage_kv.is_some(),
10062 dc_converter_unit,
10063 has_dc_converter_unit,
10064 dc_topological_node,
10065 has_dc_topological_node,
10066 };
10067 Ok(true)
10068 })
10069 }
10070}
10071
10072#[unsafe(no_mangle)]
10074pub unsafe extern "C" fn pio_detailed_connectivity_dc_ground_at(
10075 details: *const PioDetailedConnectivity,
10076 index: usize,
10077 output: *mut PioDcEquipmentView,
10078 error: *mut *mut PioError,
10079) -> bool {
10080 unsafe {
10081 entry(error, false, || {
10082 let details = require_detailed_connectivity(details)?;
10083 let record = details.dc_grounds.get(index).ok_or_else(|| {
10084 boundary_error(
10085 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10086 format!("DC ground index {index} is out of range"),
10087 )
10088 })?;
10089 *require_output(output, "output")? = dc_equipment_view(
10090 &record.component,
10091 record.equipment_container.as_ref(),
10092 "ground",
10093 &record.dc_terminal,
10094 None,
10095 record.rated_dc_voltage_kv,
10096 record.resistance_ohm,
10097 record.inductance_h,
10098 None,
10099 None,
10100 None,
10101 None,
10102 );
10103 Ok(true)
10104 })
10105 }
10106}
10107
10108#[unsafe(no_mangle)]
10110pub unsafe extern "C" fn pio_detailed_connectivity_dc_busbar_at(
10111 details: *const PioDetailedConnectivity,
10112 index: usize,
10113 output: *mut PioDcEquipmentView,
10114 error: *mut *mut PioError,
10115) -> bool {
10116 unsafe {
10117 entry(error, false, || {
10118 let details = require_detailed_connectivity(details)?;
10119 let record = details.dc_busbars.get(index).ok_or_else(|| {
10120 boundary_error(
10121 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10122 format!("DC busbar index {index} is out of range"),
10123 )
10124 })?;
10125 *require_output(output, "output")? = dc_equipment_view(
10126 &record.component,
10127 record.equipment_container.as_ref(),
10128 "busbar",
10129 &record.dc_terminal,
10130 None,
10131 record.rated_dc_voltage_kv,
10132 None,
10133 None,
10134 None,
10135 None,
10136 None,
10137 None,
10138 );
10139 Ok(true)
10140 })
10141 }
10142}
10143
10144#[unsafe(no_mangle)]
10146pub unsafe extern "C" fn pio_detailed_connectivity_dc_line_at(
10147 details: *const PioDetailedConnectivity,
10148 index: usize,
10149 output: *mut PioDcEquipmentView,
10150 error: *mut *mut PioError,
10151) -> bool {
10152 unsafe {
10153 entry(error, false, || {
10154 let details = require_detailed_connectivity(details)?;
10155 let record = details.dc_lines.get(index).ok_or_else(|| {
10156 boundary_error(
10157 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10158 format!("DC line index {index} is out of range"),
10159 )
10160 })?;
10161 *require_output(output, "output")? = dc_equipment_view(
10162 &record.component,
10163 record.equipment_container.as_ref(),
10164 "line",
10165 &record.dc_terminal1,
10166 Some(&record.dc_terminal2),
10167 record.rated_dc_voltage_kv,
10168 record.resistance_ohm,
10169 record.inductance_h,
10170 record.capacitance_f,
10171 record.length_km,
10172 None,
10173 None,
10174 );
10175 Ok(true)
10176 })
10177 }
10178}
10179
10180#[unsafe(no_mangle)]
10182pub unsafe extern "C" fn pio_detailed_connectivity_dc_series_device_at(
10183 details: *const PioDetailedConnectivity,
10184 index: usize,
10185 output: *mut PioDcEquipmentView,
10186 error: *mut *mut PioError,
10187) -> bool {
10188 unsafe {
10189 entry(error, false, || {
10190 let details = require_detailed_connectivity(details)?;
10191 let record = details.dc_series_devices.get(index).ok_or_else(|| {
10192 boundary_error(
10193 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10194 format!("DC series device index {index} is out of range"),
10195 )
10196 })?;
10197 *require_output(output, "output")? = dc_equipment_view(
10198 &record.component,
10199 record.equipment_container.as_ref(),
10200 "series_device",
10201 &record.dc_terminal1,
10202 Some(&record.dc_terminal2),
10203 record.rated_dc_voltage_kv,
10204 record.resistance_ohm,
10205 record.inductance_h,
10206 None,
10207 None,
10208 None,
10209 None,
10210 );
10211 Ok(true)
10212 })
10213 }
10214}
10215
10216#[unsafe(no_mangle)]
10218pub unsafe extern "C" fn pio_detailed_connectivity_dc_switch_at(
10219 details: *const PioDetailedConnectivity,
10220 index: usize,
10221 output: *mut PioDcEquipmentView,
10222 error: *mut *mut PioError,
10223) -> bool {
10224 unsafe {
10225 entry(error, false, || {
10226 let details = require_detailed_connectivity(details)?;
10227 let record = details.dc_switches.get(index).ok_or_else(|| {
10228 boundary_error(
10229 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10230 format!("DC switch index {index} is out of range"),
10231 )
10232 })?;
10233 *require_output(output, "output")? = dc_equipment_view(
10234 &record.component,
10235 record.equipment_container.as_ref(),
10236 "switch",
10237 &record.dc_terminal1,
10238 Some(&record.dc_terminal2),
10239 record.rated_dc_voltage_kv,
10240 record.resistance_ohm,
10241 None,
10242 None,
10243 None,
10244 Some(record.kind),
10245 record.open,
10246 );
10247 Ok(true)
10248 })
10249 }
10250}
10251
10252#[unsafe(no_mangle)]
10254pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_at(
10255 details: *const PioDetailedConnectivity,
10256 index: usize,
10257 output: *mut PioAcDcConverterView,
10258 error: *mut *mut PioError,
10259) -> bool {
10260 unsafe {
10261 entry(error, false, || {
10262 let details = require_detailed_connectivity(details)?;
10263 let converter = details
10264 .voltage_source_converters
10265 .get(index)
10266 .ok_or_else(|| {
10267 boundary_error(
10268 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10269 format!("voltage source converter index {index} is out of range"),
10270 )
10271 })?;
10272 let (dc_converter_unit, has_dc_converter_unit) =
10273 optional_component_id_view(converter.dc_converter_unit.as_ref());
10274 let (control_mode, has_control_mode) =
10275 converter
10276 .control_mode
10277 .map_or((PioStringView::EMPTY, false), |mode| {
10278 (
10279 PioStringView::new(ac_dc_converter_control_mode_name(mode)),
10280 true,
10281 )
10282 });
10283 let (pcc_terminal, has_pcc_terminal) =
10284 terminal_reference_view(converter.pcc_terminal.as_ref());
10285 let (reactive_limits, has_reactive_limits) =
10286 reactive_limits_view(converter.reactive_limits.as_ref());
10287 *require_output(output, "output")? = PioAcDcConverterView {
10288 component: component_id_view(&converter.component),
10289 kind: PioStringView::new("voltage_source"),
10290 dc_converter_unit,
10291 has_dc_converter_unit,
10292 dc_terminal1: dc_terminal_view(&converter.dc_terminal1),
10293 dc_terminal2: dc_terminal_view(&converter.dc_terminal2),
10294 base_apparent_power_mva: converter.base_apparent_power_mva.unwrap_or(0.0),
10295 has_base_apparent_power: converter.base_apparent_power_mva.is_some(),
10296 minimum_active_power_mw: converter.minimum_active_power_mw.unwrap_or(0.0),
10297 has_minimum_active_power: converter.minimum_active_power_mw.is_some(),
10298 maximum_active_power_mw: converter.maximum_active_power_mw.unwrap_or(0.0),
10299 has_maximum_active_power: converter.maximum_active_power_mw.is_some(),
10300 minimum_dc_voltage_kv: converter.minimum_dc_voltage_kv.unwrap_or(0.0),
10301 has_minimum_dc_voltage: converter.minimum_dc_voltage_kv.is_some(),
10302 maximum_dc_voltage_kv: converter.maximum_dc_voltage_kv.unwrap_or(0.0),
10303 has_maximum_dc_voltage: converter.maximum_dc_voltage_kv.is_some(),
10304 rated_dc_voltage_kv: converter.rated_dc_voltage_kv.unwrap_or(0.0),
10305 has_rated_dc_voltage: converter.rated_dc_voltage_kv.is_some(),
10306 valve_u0_kv: converter.valve_u0_kv.unwrap_or(0.0),
10307 has_valve_u0: converter.valve_u0_kv.is_some(),
10308 number_of_valves: converter.number_of_valves.unwrap_or(0),
10309 has_number_of_valves: converter.number_of_valves.is_some(),
10310 idle_loss_mw: converter.idle_loss_mw.unwrap_or(0.0),
10311 has_idle_loss: converter.idle_loss_mw.is_some(),
10312 switching_loss_mw_per_ampere: converter.switching_loss_mw_per_ampere.unwrap_or(0.0),
10313 has_switching_loss: converter.switching_loss_mw_per_ampere.is_some(),
10314 resistive_loss_ohm: converter.resistive_loss_ohm.unwrap_or(0.0),
10315 has_resistive_loss: converter.resistive_loss_ohm.is_some(),
10316 control_mode,
10317 has_control_mode,
10318 active_power_at_pcc_mw: converter.active_power_at_pcc_mw.unwrap_or(0.0),
10319 has_active_power_at_pcc: converter.active_power_at_pcc_mw.is_some(),
10320 reactive_power_at_pcc_mvar: converter.reactive_power_at_pcc_mvar.unwrap_or(0.0),
10321 has_reactive_power_at_pcc: converter.reactive_power_at_pcc_mvar.is_some(),
10322 target_active_power_mw: converter.target_active_power_mw.unwrap_or(0.0),
10323 has_target_active_power: converter.target_active_power_mw.is_some(),
10324 target_dc_voltage_kv: converter.target_dc_voltage_kv.unwrap_or(0.0),
10325 has_target_dc_voltage: converter.target_dc_voltage_kv.is_some(),
10326 pcc_terminal,
10327 has_pcc_terminal,
10328 droop_curve_segment_count: converter
10329 .droop_curve
10330 .as_ref()
10331 .map_or(0, |curve| curve.segments.len()),
10332 has_droop_curve: converter.droop_curve.is_some(),
10333 droop: converter.droop.unwrap_or(0.0),
10334 has_droop: converter.droop.is_some(),
10335 droop_compensation: converter.droop_compensation.unwrap_or(0.0),
10336 has_droop_compensation: converter.droop_compensation.is_some(),
10337 q_share: converter.q_share.unwrap_or(0.0),
10338 has_q_share: converter.q_share.is_some(),
10339 maximum_modulation_index: converter.maximum_modulation_index.unwrap_or(0.0),
10340 has_maximum_modulation_index: converter.maximum_modulation_index.is_some(),
10341 maximum_valve_current_a: converter.maximum_valve_current_a.unwrap_or(0.0),
10342 has_maximum_valve_current: converter.maximum_valve_current_a.is_some(),
10343 dc_current_a: converter.dc_current_a.unwrap_or(0.0),
10344 has_dc_current: converter.dc_current_a.is_some(),
10345 ac_voltage_kv: converter.ac_voltage_kv.unwrap_or(0.0),
10346 has_ac_voltage: converter.ac_voltage_kv.is_some(),
10347 dc_voltage_kv: converter.dc_voltage_kv.unwrap_or(0.0),
10348 has_dc_voltage: converter.dc_voltage_kv.is_some(),
10349 voltage_regulator_on: converter.voltage_regulator_on.unwrap_or(false),
10350 has_voltage_regulator_on: converter.voltage_regulator_on.is_some(),
10351 voltage_setpoint_kv: converter.voltage_setpoint_kv.unwrap_or(0.0),
10352 has_voltage_setpoint: converter.voltage_setpoint_kv.is_some(),
10353 reactive_power_setpoint_mvar: converter.reactive_power_setpoint_mvar.unwrap_or(0.0),
10354 has_reactive_power_setpoint: converter.reactive_power_setpoint_mvar.is_some(),
10355 reactive_limits,
10356 has_reactive_limits,
10357 pole_loss_active_power_mw: converter.pole_loss_active_power_mw.unwrap_or(0.0),
10358 has_pole_loss_active_power: converter.pole_loss_active_power_mw.is_some(),
10359 reactive_model: PioStringView::EMPTY,
10360 has_reactive_model: false,
10361 power_factor: 0.0,
10362 has_power_factor: false,
10363 operating_mode: PioStringView::EMPTY,
10364 has_operating_mode: false,
10365 rated_dc_current_a: 0.0,
10366 has_rated_dc_current: false,
10367 minimum_alpha_degrees: 0.0,
10368 has_minimum_alpha: false,
10369 maximum_alpha_degrees: 0.0,
10370 has_maximum_alpha: false,
10371 minimum_gamma_degrees: 0.0,
10372 has_minimum_gamma: false,
10373 maximum_gamma_degrees: 0.0,
10374 has_maximum_gamma: false,
10375 target_alpha_degrees: 0.0,
10376 has_target_alpha: false,
10377 target_gamma_degrees: 0.0,
10378 has_target_gamma: false,
10379 target_dc_current_a: 0.0,
10380 has_target_dc_current: false,
10381 alpha_degrees: 0.0,
10382 has_alpha: false,
10383 gamma_degrees: 0.0,
10384 has_gamma: false,
10385 delta_degrees: converter.delta_degrees.unwrap_or(0.0),
10386 has_delta: converter.delta_degrees.is_some(),
10387 uf_kv: converter.uf_kv.unwrap_or(0.0),
10388 has_uf: converter.uf_kv.is_some(),
10389 uv_kv: converter.uv_kv.unwrap_or(0.0),
10390 has_uv: converter.uv_kv.is_some(),
10391 };
10392 Ok(true)
10393 })
10394 }
10395}
10396
10397#[unsafe(no_mangle)]
10399pub unsafe extern "C" fn pio_detailed_connectivity_line_commutated_converter_at(
10400 details: *const PioDetailedConnectivity,
10401 index: usize,
10402 output: *mut PioAcDcConverterView,
10403 error: *mut *mut PioError,
10404) -> bool {
10405 unsafe {
10406 entry(error, false, || {
10407 let details = require_detailed_connectivity(details)?;
10408 let converter = details
10409 .line_commutated_converters
10410 .get(index)
10411 .ok_or_else(|| {
10412 boundary_error(
10413 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10414 format!("line commutated converter index {index} is out of range"),
10415 )
10416 })?;
10417 let (dc_converter_unit, has_dc_converter_unit) =
10418 optional_component_id_view(converter.dc_converter_unit.as_ref());
10419 let (control_mode, has_control_mode) =
10420 converter
10421 .control_mode
10422 .map_or((PioStringView::EMPTY, false), |mode| {
10423 (
10424 PioStringView::new(ac_dc_converter_control_mode_name(mode)),
10425 true,
10426 )
10427 });
10428 let (reactive_model, has_reactive_model) =
10429 converter
10430 .reactive_model
10431 .map_or((PioStringView::EMPTY, false), |model| {
10432 (
10433 PioStringView::new(line_commutated_converter_reactive_model_name(
10434 model,
10435 )),
10436 true,
10437 )
10438 });
10439 let (operating_mode, has_operating_mode) =
10440 converter
10441 .operating_mode
10442 .map_or((PioStringView::EMPTY, false), |mode| {
10443 (
10444 PioStringView::new(line_commutated_converter_operating_mode_name(mode)),
10445 true,
10446 )
10447 });
10448 let (pcc_terminal, has_pcc_terminal) =
10449 terminal_reference_view(converter.pcc_terminal.as_ref());
10450 *require_output(output, "output")? = PioAcDcConverterView {
10451 component: component_id_view(&converter.component),
10452 kind: PioStringView::new("line_commutated"),
10453 dc_converter_unit,
10454 has_dc_converter_unit,
10455 dc_terminal1: dc_terminal_view(&converter.dc_terminal1),
10456 dc_terminal2: dc_terminal_view(&converter.dc_terminal2),
10457 base_apparent_power_mva: converter.base_apparent_power_mva.unwrap_or(0.0),
10458 has_base_apparent_power: converter.base_apparent_power_mva.is_some(),
10459 minimum_active_power_mw: converter.minimum_active_power_mw.unwrap_or(0.0),
10460 has_minimum_active_power: converter.minimum_active_power_mw.is_some(),
10461 maximum_active_power_mw: converter.maximum_active_power_mw.unwrap_or(0.0),
10462 has_maximum_active_power: converter.maximum_active_power_mw.is_some(),
10463 minimum_dc_voltage_kv: converter.minimum_dc_voltage_kv.unwrap_or(0.0),
10464 has_minimum_dc_voltage: converter.minimum_dc_voltage_kv.is_some(),
10465 maximum_dc_voltage_kv: converter.maximum_dc_voltage_kv.unwrap_or(0.0),
10466 has_maximum_dc_voltage: converter.maximum_dc_voltage_kv.is_some(),
10467 rated_dc_voltage_kv: converter.rated_dc_voltage_kv.unwrap_or(0.0),
10468 has_rated_dc_voltage: converter.rated_dc_voltage_kv.is_some(),
10469 valve_u0_kv: converter.valve_u0_kv.unwrap_or(0.0),
10470 has_valve_u0: converter.valve_u0_kv.is_some(),
10471 number_of_valves: converter.number_of_valves.unwrap_or(0),
10472 has_number_of_valves: converter.number_of_valves.is_some(),
10473 idle_loss_mw: converter.idle_loss_mw.unwrap_or(0.0),
10474 has_idle_loss: converter.idle_loss_mw.is_some(),
10475 switching_loss_mw_per_ampere: converter.switching_loss_mw_per_ampere.unwrap_or(0.0),
10476 has_switching_loss: converter.switching_loss_mw_per_ampere.is_some(),
10477 resistive_loss_ohm: converter.resistive_loss_ohm.unwrap_or(0.0),
10478 has_resistive_loss: converter.resistive_loss_ohm.is_some(),
10479 control_mode,
10480 has_control_mode,
10481 active_power_at_pcc_mw: converter.active_power_at_pcc_mw.unwrap_or(0.0),
10482 has_active_power_at_pcc: converter.active_power_at_pcc_mw.is_some(),
10483 reactive_power_at_pcc_mvar: converter.reactive_power_at_pcc_mvar.unwrap_or(0.0),
10484 has_reactive_power_at_pcc: converter.reactive_power_at_pcc_mvar.is_some(),
10485 target_active_power_mw: converter.target_active_power_mw.unwrap_or(0.0),
10486 has_target_active_power: converter.target_active_power_mw.is_some(),
10487 target_dc_voltage_kv: converter.target_dc_voltage_kv.unwrap_or(0.0),
10488 has_target_dc_voltage: converter.target_dc_voltage_kv.is_some(),
10489 pcc_terminal,
10490 has_pcc_terminal,
10491 droop_curve_segment_count: converter
10492 .droop_curve
10493 .as_ref()
10494 .map_or(0, |curve| curve.segments.len()),
10495 has_droop_curve: converter.droop_curve.is_some(),
10496 droop: 0.0,
10497 has_droop: false,
10498 droop_compensation: 0.0,
10499 has_droop_compensation: false,
10500 q_share: 0.0,
10501 has_q_share: false,
10502 maximum_modulation_index: 0.0,
10503 has_maximum_modulation_index: false,
10504 maximum_valve_current_a: 0.0,
10505 has_maximum_valve_current: false,
10506 dc_current_a: converter.dc_current_a.unwrap_or(0.0),
10507 has_dc_current: converter.dc_current_a.is_some(),
10508 ac_voltage_kv: converter.ac_voltage_kv.unwrap_or(0.0),
10509 has_ac_voltage: converter.ac_voltage_kv.is_some(),
10510 dc_voltage_kv: converter.dc_voltage_kv.unwrap_or(0.0),
10511 has_dc_voltage: converter.dc_voltage_kv.is_some(),
10512 voltage_regulator_on: false,
10513 has_voltage_regulator_on: false,
10514 voltage_setpoint_kv: 0.0,
10515 has_voltage_setpoint: false,
10516 reactive_power_setpoint_mvar: 0.0,
10517 has_reactive_power_setpoint: false,
10518 reactive_limits: empty_reactive_limits_view(),
10519 has_reactive_limits: false,
10520 pole_loss_active_power_mw: converter.pole_loss_active_power_mw.unwrap_or(0.0),
10521 has_pole_loss_active_power: converter.pole_loss_active_power_mw.is_some(),
10522 reactive_model,
10523 has_reactive_model,
10524 power_factor: converter.power_factor.unwrap_or(0.0),
10525 has_power_factor: converter.power_factor.is_some(),
10526 operating_mode,
10527 has_operating_mode,
10528 rated_dc_current_a: converter.rated_dc_current_a.unwrap_or(0.0),
10529 has_rated_dc_current: converter.rated_dc_current_a.is_some(),
10530 minimum_alpha_degrees: converter.minimum_alpha_degrees.unwrap_or(0.0),
10531 has_minimum_alpha: converter.minimum_alpha_degrees.is_some(),
10532 maximum_alpha_degrees: converter.maximum_alpha_degrees.unwrap_or(0.0),
10533 has_maximum_alpha: converter.maximum_alpha_degrees.is_some(),
10534 minimum_gamma_degrees: converter.minimum_gamma_degrees.unwrap_or(0.0),
10535 has_minimum_gamma: converter.minimum_gamma_degrees.is_some(),
10536 maximum_gamma_degrees: converter.maximum_gamma_degrees.unwrap_or(0.0),
10537 has_maximum_gamma: converter.maximum_gamma_degrees.is_some(),
10538 target_alpha_degrees: converter.target_alpha_degrees.unwrap_or(0.0),
10539 has_target_alpha: converter.target_alpha_degrees.is_some(),
10540 target_gamma_degrees: converter.target_gamma_degrees.unwrap_or(0.0),
10541 has_target_gamma: converter.target_gamma_degrees.is_some(),
10542 target_dc_current_a: converter.target_dc_current_a.unwrap_or(0.0),
10543 has_target_dc_current: converter.target_dc_current_a.is_some(),
10544 alpha_degrees: converter.alpha_degrees.unwrap_or(0.0),
10545 has_alpha: converter.alpha_degrees.is_some(),
10546 gamma_degrees: converter.gamma_degrees.unwrap_or(0.0),
10547 has_gamma: converter.gamma_degrees.is_some(),
10548 delta_degrees: 0.0,
10549 has_delta: false,
10550 uf_kv: 0.0,
10551 has_uf: false,
10552 uv_kv: 0.0,
10553 has_uv: false,
10554 };
10555 Ok(true)
10556 })
10557 }
10558}
10559
10560fn droop_curve_segment_view(segment: &powerio_tx::DroopCurveSegment) -> PioDroopCurveSegmentView {
10561 PioDroopCurveSegmentView {
10562 minimum_voltage_kv: segment.minimum_voltage_kv,
10563 maximum_voltage_kv: segment.maximum_voltage_kv,
10564 k: segment.k,
10565 }
10566}
10567
10568#[unsafe(no_mangle)]
10570pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_droop_curve_segment_at(
10571 details: *const PioDetailedConnectivity,
10572 converter_index: usize,
10573 segment_index: usize,
10574 output: *mut PioDroopCurveSegmentView,
10575 error: *mut *mut PioError,
10576) -> bool {
10577 unsafe {
10578 entry(error, false, || {
10579 let details = require_detailed_connectivity(details)?;
10580 let converter = details
10581 .voltage_source_converters
10582 .get(converter_index)
10583 .ok_or_else(|| {
10584 boundary_error(
10585 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10586 format!("voltage source converter index {converter_index} is out of range"),
10587 )
10588 })?;
10589 let curve = converter.droop_curve.as_ref().ok_or_else(|| {
10590 boundary_error(
10591 &codes::REQUEST_CAPI_TYPE_MISMATCH,
10592 format!("voltage source converter {converter_index} has no droop curve"),
10593 )
10594 })?;
10595 let segment = curve.segments.get(segment_index).ok_or_else(|| {
10596 boundary_error(
10597 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10598 format!("droop curve segment index {segment_index} is out of range"),
10599 )
10600 })?;
10601 *require_output(output, "output")? = droop_curve_segment_view(segment);
10602 Ok(true)
10603 })
10604 }
10605}
10606
10607#[unsafe(no_mangle)]
10609pub unsafe extern "C" fn pio_detailed_connectivity_line_commutated_converter_droop_curve_segment_at(
10610 details: *const PioDetailedConnectivity,
10611 converter_index: usize,
10612 segment_index: usize,
10613 output: *mut PioDroopCurveSegmentView,
10614 error: *mut *mut PioError,
10615) -> bool {
10616 unsafe {
10617 entry(error, false, || {
10618 let details = require_detailed_connectivity(details)?;
10619 let converter = details
10620 .line_commutated_converters
10621 .get(converter_index)
10622 .ok_or_else(|| {
10623 boundary_error(
10624 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10625 format!(
10626 "line commutated converter index {converter_index} is out of range"
10627 ),
10628 )
10629 })?;
10630 let curve = converter.droop_curve.as_ref().ok_or_else(|| {
10631 boundary_error(
10632 &codes::REQUEST_CAPI_TYPE_MISMATCH,
10633 format!("line commutated converter {converter_index} has no droop curve"),
10634 )
10635 })?;
10636 let segment = curve.segments.get(segment_index).ok_or_else(|| {
10637 boundary_error(
10638 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10639 format!("droop curve segment index {segment_index} is out of range"),
10640 )
10641 })?;
10642 *require_output(output, "output")? = droop_curve_segment_view(segment);
10643 Ok(true)
10644 })
10645 }
10646}
10647
10648#[unsafe(no_mangle)]
10650pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_reactive_limit_property_at(
10651 details: *const PioDetailedConnectivity,
10652 converter_index: usize,
10653 property_index: usize,
10654 output: *mut PioStringPropertyView,
10655 error: *mut *mut PioError,
10656) -> bool {
10657 unsafe {
10658 entry(error, false, || {
10659 let details = require_detailed_connectivity(details)?;
10660 let converter = details
10661 .voltage_source_converters
10662 .get(converter_index)
10663 .ok_or_else(|| {
10664 boundary_error(
10665 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10666 format!("voltage source converter index {converter_index} is out of range"),
10667 )
10668 })?;
10669 let limits = converter.reactive_limits.as_ref().ok_or_else(|| {
10670 boundary_error(
10671 &codes::REQUEST_CAPI_TYPE_MISMATCH,
10672 format!("voltage source converter {converter_index} has no reactive limits"),
10673 )
10674 })?;
10675 *require_output(output, "output")? =
10676 string_property_view(reactive_limit_properties(limits), property_index)?;
10677 Ok(true)
10678 })
10679 }
10680}
10681
10682#[unsafe(no_mangle)]
10684pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_reactive_capability_point_at(
10685 details: *const PioDetailedConnectivity,
10686 converter_index: usize,
10687 point_index: usize,
10688 output: *mut PioReactiveCapabilityCurvePointView,
10689 error: *mut *mut PioError,
10690) -> bool {
10691 unsafe {
10692 entry(error, false, || {
10693 let details = require_detailed_connectivity(details)?;
10694 let converter = details
10695 .voltage_source_converters
10696 .get(converter_index)
10697 .ok_or_else(|| {
10698 boundary_error(
10699 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10700 format!("voltage source converter index {converter_index} is out of range"),
10701 )
10702 })?;
10703 let curve = converter
10704 .reactive_limits
10705 .as_ref()
10706 .and_then(reactive_capability_curve)
10707 .ok_or_else(|| {
10708 boundary_error(
10709 &codes::REQUEST_CAPI_TYPE_MISMATCH,
10710 format!(
10711 "voltage source converter {converter_index} has no reactive capability curve"
10712 ),
10713 )
10714 })?;
10715 *require_output(output, "output")? =
10716 reactive_capability_point_view(curve, point_index)?;
10717 Ok(true)
10718 })
10719 }
10720}
10721
10722#[unsafe(no_mangle)]
10724pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_reactive_capability_point_property_at(
10725 details: *const PioDetailedConnectivity,
10726 converter_index: usize,
10727 point_index: usize,
10728 property_index: usize,
10729 output: *mut PioStringPropertyView,
10730 error: *mut *mut PioError,
10731) -> bool {
10732 unsafe {
10733 entry(error, false, || {
10734 let details = require_detailed_connectivity(details)?;
10735 let converter = details
10736 .voltage_source_converters
10737 .get(converter_index)
10738 .ok_or_else(|| {
10739 boundary_error(
10740 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10741 format!("voltage source converter index {converter_index} is out of range"),
10742 )
10743 })?;
10744 let curve = converter
10745 .reactive_limits
10746 .as_ref()
10747 .and_then(reactive_capability_curve)
10748 .ok_or_else(|| {
10749 boundary_error(
10750 &codes::REQUEST_CAPI_TYPE_MISMATCH,
10751 format!(
10752 "voltage source converter {converter_index} has no reactive capability curve"
10753 ),
10754 )
10755 })?;
10756 let point = curve.points.get(point_index).ok_or_else(|| {
10757 boundary_error(
10758 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10759 format!("reactive capability point index {point_index} is out of range"),
10760 )
10761 })?;
10762 *require_output(output, "output")? =
10763 string_property_view(&point.properties, property_index)?;
10764 Ok(true)
10765 })
10766 }
10767}
10768
10769#[unsafe(no_mangle)]
10770pub unsafe extern "C" fn pio_detailed_connectivity_retain(
10771 details: *const PioDetailedConnectivity,
10772) -> *mut PioDetailedConnectivity {
10773 unsafe { PioDetailedConnectivity::retain_raw(details) }
10774}
10775
10776#[unsafe(no_mangle)]
10777pub unsafe extern "C" fn pio_detailed_connectivity_release(details: *mut PioDetailedConnectivity) {
10778 unsafe { PioDetailedConnectivity::release_raw(details) };
10779}
10780
10781#[unsafe(no_mangle)]
10782pub unsafe extern "C" fn pio_balanced_network_bus_count(
10783 network: *const PioBalancedNetwork,
10784) -> usize {
10785 unsafe { PioBalancedNetwork::get(network) }
10786 .and_then(BalancedNetworkInner::network)
10787 .map_or(0, |network| network.buses().len())
10788}
10789
10790#[unsafe(no_mangle)]
10791pub unsafe extern "C" fn pio_balanced_network_branch_count(
10792 network: *const PioBalancedNetwork,
10793) -> usize {
10794 unsafe { PioBalancedNetwork::get(network) }
10795 .and_then(BalancedNetworkInner::network)
10796 .map_or(0, |network| network.branches().len())
10797}
10798
10799#[unsafe(no_mangle)]
10800pub unsafe extern "C" fn pio_balanced_network_load_count(
10801 network: *const PioBalancedNetwork,
10802) -> usize {
10803 unsafe { PioBalancedNetwork::get(network) }
10804 .and_then(BalancedNetworkInner::network)
10805 .map_or(0, |network| network.loads().len())
10806}
10807
10808#[unsafe(no_mangle)]
10809pub unsafe extern "C" fn pio_balanced_network_shunt_count(
10810 network: *const PioBalancedNetwork,
10811) -> usize {
10812 unsafe { PioBalancedNetwork::get(network) }
10813 .and_then(BalancedNetworkInner::network)
10814 .map_or(0, |network| network.shunts().len())
10815}
10816
10817#[unsafe(no_mangle)]
10818pub unsafe extern "C" fn pio_balanced_network_static_var_compensator_count(
10819 network: *const PioBalancedNetwork,
10820) -> usize {
10821 unsafe { PioBalancedNetwork::get(network) }
10822 .and_then(BalancedNetworkInner::network)
10823 .map_or(0, |network| network.static_var_compensators().len())
10824}
10825
10826#[unsafe(no_mangle)]
10827pub unsafe extern "C" fn pio_balanced_network_generator_count(
10828 network: *const PioBalancedNetwork,
10829) -> usize {
10830 unsafe { PioBalancedNetwork::get(network) }
10831 .and_then(BalancedNetworkInner::network)
10832 .map_or(0, |network| network.generators().len())
10833}
10834
10835#[unsafe(no_mangle)]
10836pub unsafe extern "C" fn pio_balanced_network_storage_count(
10837 network: *const PioBalancedNetwork,
10838) -> usize {
10839 unsafe { PioBalancedNetwork::get(network) }
10840 .and_then(BalancedNetworkInner::network)
10841 .map_or(0, |network| network.storage().len())
10842}
10843
10844#[unsafe(no_mangle)]
10845pub unsafe extern "C" fn pio_balanced_network_switch_count(
10846 network: *const PioBalancedNetwork,
10847) -> usize {
10848 unsafe { PioBalancedNetwork::get(network) }
10849 .and_then(BalancedNetworkInner::network)
10850 .map_or(0, |network| network.switches().len())
10851}
10852
10853#[unsafe(no_mangle)]
10854pub unsafe extern "C" fn pio_balanced_network_hvdc_count(
10855 network: *const PioBalancedNetwork,
10856) -> usize {
10857 unsafe { PioBalancedNetwork::get(network) }
10858 .and_then(BalancedNetworkInner::network)
10859 .map_or(0, |network| network.hvdc().len())
10860}
10861
10862#[unsafe(no_mangle)]
10863pub unsafe extern "C" fn pio_balanced_network_three_winding_transformer_count(
10864 network: *const PioBalancedNetwork,
10865) -> usize {
10866 unsafe { PioBalancedNetwork::get(network) }
10867 .and_then(BalancedNetworkInner::network)
10868 .map_or(0, |network| network.transformers_3w().len())
10869}
10870
10871#[unsafe(no_mangle)]
10872pub unsafe extern "C" fn pio_balanced_network_area_count(
10873 network: *const PioBalancedNetwork,
10874) -> usize {
10875 unsafe { PioBalancedNetwork::get(network) }
10876 .and_then(BalancedNetworkInner::network)
10877 .map_or(0, |network| network.areas().len())
10878}
10879
10880unsafe fn require_balanced_network<'a>(
10881 network: *const PioBalancedNetwork,
10882) -> Result<&'a BalancedNetwork, *mut PioError> {
10883 unsafe { PioBalancedNetwork::get(network) }
10884 .and_then(BalancedNetworkInner::network)
10885 .ok_or_else(|| {
10886 boundary_error(
10887 &codes::BIND_CAPI_NULL_HANDLE,
10888 "PioBalancedNetwork must not be NULL",
10889 )
10890 })
10891}
10892
10893unsafe fn require_output<'a, T>(
10894 output: *mut T,
10895 argument: &str,
10896) -> Result<&'a mut T, *mut PioError> {
10897 unsafe { output.as_mut() }.ok_or_else(|| {
10898 boundary_error(
10899 &codes::BIND_CAPI_NULL_ARGUMENT,
10900 format!("{argument} must not be NULL"),
10901 )
10902 })
10903}
10904
10905unsafe fn require_detailed_connectivity<'a>(
10906 details: *const PioDetailedConnectivity,
10907) -> Result<&'a powerio_tx::DetailedConnectivity, *mut PioError> {
10908 unsafe { PioDetailedConnectivity::get(details) }
10909 .and_then(DetailedConnectivityInner::details)
10910 .ok_or_else(|| {
10911 boundary_error(
10912 &codes::BIND_CAPI_NULL_HANDLE,
10913 "PioDetailedConnectivity must not be NULL",
10914 )
10915 })
10916}
10917
10918fn optional_string_view(value: Option<&str>) -> (PioStringView, bool) {
10919 value.map_or((PioStringView::EMPTY, false), |value| {
10920 (PioStringView::new(value), true)
10921 })
10922}
10923
10924fn balanced_coords_kind_name(kind: powerio_tx::CoordsKind) -> &'static str {
10925 match kind {
10926 powerio_tx::CoordsKind::Source => "source",
10927 powerio_tx::CoordsKind::Synthetic => "synthetic",
10928 powerio_tx::CoordsKind::Manual => "manual",
10929 powerio_tx::CoordsKind::Derived => "derived",
10930 _ => "unknown",
10931 }
10932}
10933
10934fn balanced_location_view(location: &powerio_tx::Location) -> PioBalancedLocationView {
10935 let (kind, has_kind) = location.kind.map_or((PioStringView::EMPTY, false), |kind| {
10936 (PioStringView::new(balanced_coords_kind_name(kind)), true)
10937 });
10938 PioBalancedLocationView {
10939 x: location.x,
10940 y: location.y,
10941 kind,
10942 has_kind,
10943 }
10944}
10945
10946fn empty_balanced_location_view() -> PioBalancedLocationView {
10947 PioBalancedLocationView {
10948 x: 0.0,
10949 y: 0.0,
10950 kind: PioStringView::EMPTY,
10951 has_kind: false,
10952 }
10953}
10954
10955fn balanced_geo_view(geo: Option<&powerio_tx::GeoMeta>) -> PioBalancedGeoView {
10956 let Some(geo) = geo else {
10957 return PioBalancedGeoView {
10958 has_geo: false,
10959 space: PioStringView::EMPTY,
10960 crs: PioStringView::EMPTY,
10961 has_crs: false,
10962 kind: PioStringView::EMPTY,
10963 has_kind: false,
10964 has_canvas: false,
10965 canvas_width: 0.0,
10966 has_canvas_width: false,
10967 canvas_height: 0.0,
10968 has_canvas_height: false,
10969 canvas_units: PioStringView::EMPTY,
10970 has_canvas_units: false,
10971 };
10972 };
10973 let (space, crs, has_crs, canvas) = match &geo.space {
10974 powerio_tx::CoordinateSpace::Geographic { crs } => (
10975 "geographic",
10976 crs.as_deref()
10977 .map_or(PioStringView::EMPTY, PioStringView::new),
10978 crs.is_some(),
10979 None,
10980 ),
10981 powerio_tx::CoordinateSpace::Projected { crs } => (
10982 "projected",
10983 crs.as_deref()
10984 .map_or(PioStringView::EMPTY, PioStringView::new),
10985 crs.is_some(),
10986 None,
10987 ),
10988 powerio_tx::CoordinateSpace::Diagram { canvas } => {
10989 ("diagram", PioStringView::EMPTY, false, canvas.as_ref())
10990 }
10991 powerio_tx::CoordinateSpace::Unknown => ("unknown", PioStringView::EMPTY, false, None),
10992 _ => ("unknown", PioStringView::EMPTY, false, None),
10993 };
10994 let (kind, has_kind) = geo.kind.map_or((PioStringView::EMPTY, false), |kind| {
10995 (PioStringView::new(balanced_coords_kind_name(kind)), true)
10996 });
10997 PioBalancedGeoView {
10998 has_geo: true,
10999 space: PioStringView::new(space),
11000 crs,
11001 has_crs,
11002 kind,
11003 has_kind,
11004 has_canvas: canvas.is_some(),
11005 canvas_width: canvas.and_then(|canvas| canvas.width).unwrap_or(0.0),
11006 has_canvas_width: canvas.is_some_and(|canvas| canvas.width.is_some()),
11007 canvas_height: canvas.and_then(|canvas| canvas.height).unwrap_or(0.0),
11008 has_canvas_height: canvas.is_some_and(|canvas| canvas.height.is_some()),
11009 canvas_units: canvas
11010 .and_then(|canvas| canvas.units.as_deref())
11011 .map_or(PioStringView::EMPTY, PioStringView::new),
11012 has_canvas_units: canvas.is_some_and(|canvas| canvas.units.is_some()),
11013 }
11014}
11015
11016fn load_voltage_model_view(load: &powerio_tx::Load) -> PioBalancedLoadVoltageModelView {
11017 let mut view = PioBalancedLoadVoltageModelView {
11018 kind: PioStringView::new("constant_power"),
11019 p_constant_power_mw: load.p,
11020 q_constant_power_mvar: load.q,
11021 p_constant_current_mw: 0.0,
11022 q_constant_current_mvar: 0.0,
11023 p_constant_impedance_mw: 0.0,
11024 q_constant_impedance_mvar: 0.0,
11025 exponential_p_mw: 0.0,
11026 exponential_q_mvar: 0.0,
11027 gamma_p: 0.0,
11028 gamma_q: 0.0,
11029 nominal_voltage_pu: 0.0,
11030 has_nominal_voltage: false,
11031 load_type: 0,
11032 has_load_type: false,
11033 scaling: 0.0,
11034 has_scaling: false,
11035 };
11036 match &load.voltage_model {
11037 None | Some(powerio_tx::LoadVoltageModel::ConstantPower) => {}
11038 Some(powerio_tx::LoadVoltageModel::Zip {
11039 p_constant_power,
11040 q_constant_power,
11041 p_constant_current,
11042 q_constant_current,
11043 p_constant_impedance,
11044 q_constant_impedance,
11045 v_nom,
11046 load_type,
11047 scaling,
11048 }) => {
11049 view.kind = PioStringView::new("zip");
11050 view.p_constant_power_mw = *p_constant_power;
11051 view.q_constant_power_mvar = *q_constant_power;
11052 view.p_constant_current_mw = *p_constant_current;
11053 view.q_constant_current_mvar = *q_constant_current;
11054 view.p_constant_impedance_mw = *p_constant_impedance;
11055 view.q_constant_impedance_mvar = *q_constant_impedance;
11056 if let Some(value) = v_nom {
11057 view.nominal_voltage_pu = *value;
11058 view.has_nominal_voltage = true;
11059 }
11060 if let Some(value) = load_type {
11061 view.load_type = *value;
11062 view.has_load_type = true;
11063 }
11064 if let Some(value) = scaling {
11065 view.scaling = *value;
11066 view.has_scaling = true;
11067 }
11068 }
11069 Some(powerio_tx::LoadVoltageModel::Exponential {
11070 p,
11071 q,
11072 v_nom,
11073 gamma_p,
11074 gamma_q,
11075 }) => {
11076 view.kind = PioStringView::new("exponential");
11077 view.p_constant_power_mw = 0.0;
11078 view.q_constant_power_mvar = 0.0;
11079 view.exponential_p_mw = *p;
11080 view.exponential_q_mvar = *q;
11081 view.gamma_p = *gamma_p;
11082 view.gamma_q = *gamma_q;
11083 if let Some(value) = v_nom {
11084 view.nominal_voltage_pu = *value;
11085 view.has_nominal_voltage = true;
11086 }
11087 }
11088 Some(_) => view.kind = PioStringView::new("unknown"),
11089 }
11090 view
11091}
11092
11093fn switched_shunt_mode_name(mode: powerio_tx::SwitchedShuntMode) -> &'static str {
11094 match mode {
11095 powerio_tx::SwitchedShuntMode::Locked => "locked",
11096 powerio_tx::SwitchedShuntMode::Continuous => "continuous",
11097 powerio_tx::SwitchedShuntMode::Discrete => "discrete",
11098 _ => "unknown",
11099 }
11100}
11101
11102const GENERATOR_CAPABILITY_NAMES: [&str; 11] = [
11103 "pc1", "pc2", "qc1min", "qc1max", "qc2min", "qc2max", "ramp_agc", "ramp_10", "ramp_30",
11104 "ramp_q", "apf",
11105];
11106
11107fn static_var_compensator_regulation_mode_name(
11108 mode: powerio_tx::StaticVarCompensatorRegulationMode,
11109) -> &'static str {
11110 match mode {
11111 powerio_tx::StaticVarCompensatorRegulationMode::Voltage => "voltage",
11112 powerio_tx::StaticVarCompensatorRegulationMode::ReactivePower => "reactive_power",
11113 _ => "unknown",
11114 }
11115}
11116
11117fn hvdc_converter_kind_name(kind: powerio_tx::HvdcConverterKind) -> &'static str {
11118 match kind {
11119 powerio_tx::HvdcConverterKind::Vsc => "vsc",
11120 powerio_tx::HvdcConverterKind::Lcc => "lcc",
11121 _ => "unknown",
11122 }
11123}
11124
11125fn hvdc_converters_mode_name(mode: powerio_tx::HvdcConvertersMode) -> &'static str {
11126 match mode {
11127 powerio_tx::HvdcConvertersMode::Side1RectifierSide2Inverter => {
11128 "side1_rectifier_side2_inverter"
11129 }
11130 powerio_tx::HvdcConvertersMode::Side1InverterSide2Rectifier => {
11131 "side1_inverter_side2_rectifier"
11132 }
11133 _ => "unknown",
11134 }
11135}
11136
11137fn hvdc_converter_view(
11138 converter: Option<&powerio_tx::HvdcConverter>,
11139) -> (PioBalancedHvdcConverterView, bool) {
11140 let empty_terminal = terminal_reference_view(None).0;
11141 converter.map_or(
11142 (
11143 PioBalancedHvdcConverterView {
11144 component: empty_component_id_view(),
11145 kind: PioStringView::EMPTY,
11146 loss_factor_percent: 0.0,
11147 voltage_regulator_on: false,
11148 has_voltage_regulator_on: false,
11149 voltage_setpoint_kv: 0.0,
11150 has_voltage_setpoint: false,
11151 reactive_power_setpoint_mvar: 0.0,
11152 has_reactive_power_setpoint: false,
11153 power_factor: 0.0,
11154 has_power_factor: false,
11155 regulating_terminal: empty_terminal,
11156 has_regulating_terminal: false,
11157 },
11158 false,
11159 ),
11160 |converter| {
11161 let (regulating_terminal, has_regulating_terminal) =
11162 terminal_reference_view(converter.regulating_terminal.as_ref());
11163 (
11164 PioBalancedHvdcConverterView {
11165 component: component_id_view(&converter.component),
11166 kind: PioStringView::new(hvdc_converter_kind_name(converter.kind)),
11167 loss_factor_percent: converter.loss_factor_percent,
11168 voltage_regulator_on: converter.voltage_regulator_on.unwrap_or(false),
11169 has_voltage_regulator_on: converter.voltage_regulator_on.is_some(),
11170 voltage_setpoint_kv: converter.voltage_setpoint_kv.unwrap_or(0.0),
11171 has_voltage_setpoint: converter.voltage_setpoint_kv.is_some(),
11172 reactive_power_setpoint_mvar: converter
11173 .reactive_power_setpoint_mvar
11174 .unwrap_or(0.0),
11175 has_reactive_power_setpoint: converter.reactive_power_setpoint_mvar.is_some(),
11176 power_factor: converter.power_factor.unwrap_or(0.0),
11177 has_power_factor: converter.power_factor.is_some(),
11178 regulating_terminal,
11179 has_regulating_terminal,
11180 },
11181 true,
11182 )
11183 },
11184 )
11185}
11186
11187#[unsafe(no_mangle)]
11189pub unsafe extern "C" fn pio_balanced_network_bus_at(
11190 network: *const PioBalancedNetwork,
11191 index: usize,
11192 output: *mut PioBalancedBusView,
11193 error: *mut *mut PioError,
11194) -> bool {
11195 unsafe {
11196 entry(error, false, || {
11197 let network = require_balanced_network(network)?;
11198 let bus = network.buses().get(index).ok_or_else(|| {
11199 boundary_error(
11200 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11201 format!("bus index {index} is out of range"),
11202 )
11203 })?;
11204 let output = require_output(output, "output")?;
11205 let (component_id, has_component_id) = optional_string_view(bus.uid.as_deref());
11206 let (name, has_name) = optional_string_view(bus.name.as_deref());
11207 let (location, has_location) = bus
11208 .location
11209 .as_ref()
11210 .map_or((empty_balanced_location_view(), false), |location| {
11211 (balanced_location_view(location), true)
11212 });
11213 *output = PioBalancedBusView {
11214 component_id,
11215 has_component_id,
11216 id: bus.id.0,
11217 bus_type: PioStringView::new(bus.kind.as_str()),
11218 vm_pu: bus.vm,
11219 va_degrees: bus.va,
11220 base_kv: bus.base_kv,
11221 vmax_pu: bus.vmax,
11222 vmin_pu: bus.vmin,
11223 has_emergency_voltage_limits: bus.evhi.is_some() || bus.evlo.is_some(),
11224 emergency_vmax_pu: bus.evhi.unwrap_or(bus.vmax),
11225 emergency_vmin_pu: bus.evlo.unwrap_or(bus.vmin),
11226 area: bus.area,
11227 zone: bus.zone,
11228 name,
11229 has_name,
11230 location,
11231 has_location,
11232 };
11233 Ok(true)
11234 })
11235 }
11236}
11237
11238#[unsafe(no_mangle)]
11240pub unsafe extern "C" fn pio_balanced_network_load_at(
11241 network: *const PioBalancedNetwork,
11242 index: usize,
11243 output: *mut PioBalancedLoadView,
11244 error: *mut *mut PioError,
11245) -> bool {
11246 unsafe {
11247 entry(error, false, || {
11248 let network = require_balanced_network(network)?;
11249 let load = network.loads().get(index).ok_or_else(|| {
11250 boundary_error(
11251 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11252 format!("load index {index} is out of range"),
11253 )
11254 })?;
11255 let output = require_output(output, "output")?;
11256 let (component_id, has_component_id) = optional_string_view(load.uid.as_deref());
11257 *output = PioBalancedLoadView {
11258 component_id,
11259 has_component_id,
11260 bus_id: load.bus.0,
11261 p_mw: load.p,
11262 q_mvar: load.q,
11263 in_service: load.in_service,
11264 voltage_model: load_voltage_model_view(load),
11265 };
11266 Ok(true)
11267 })
11268 }
11269}
11270
11271#[unsafe(no_mangle)]
11273pub unsafe extern "C" fn pio_balanced_network_shunt_at(
11274 network: *const PioBalancedNetwork,
11275 index: usize,
11276 output: *mut PioBalancedShuntView,
11277 error: *mut *mut PioError,
11278) -> bool {
11279 unsafe {
11280 entry(error, false, || {
11281 let network = require_balanced_network(network)?;
11282 let shunt = network.shunts().get(index).ok_or_else(|| {
11283 boundary_error(
11284 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11285 format!("shunt index {index} is out of range"),
11286 )
11287 })?;
11288 let output = require_output(output, "output")?;
11289 let (component_id, has_component_id) = optional_string_view(shunt.uid.as_deref());
11290 let control = shunt.control.as_ref();
11291 *output = PioBalancedShuntView {
11292 component_id,
11293 has_component_id,
11294 bus_id: shunt.bus.0,
11295 conductance_mw: shunt.g,
11296 susceptance_mvar: shunt.b,
11297 in_service: shunt.in_service,
11298 section_count: shunt.section_count.unwrap_or(0),
11299 has_section_count: shunt.section_count.is_some(),
11300 has_control: control.is_some(),
11301 control_mode: control.map_or(PioStringView::EMPTY, |control| {
11302 PioStringView::new(switched_shunt_mode_name(control.mode))
11303 }),
11304 control_vmax_pu: control.map_or(0.0, |control| control.vhigh),
11305 control_vmin_pu: control.map_or(0.0, |control| control.vlow),
11306 control_bus_id: control
11307 .and_then(|control| control.control_bus)
11308 .map_or(0, |bus| bus.0),
11309 has_control_bus: control.is_some_and(|control| control.control_bus.is_some()),
11310 control_reactive_range_percent: control.map_or(0.0, |control| control.rmpct),
11311 control_block_count: control.map_or(0, |control| control.blocks.len()),
11312 };
11313 Ok(true)
11314 })
11315 }
11316}
11317
11318#[unsafe(no_mangle)]
11320pub unsafe extern "C" fn pio_balanced_network_shunt_block_at(
11321 network: *const PioBalancedNetwork,
11322 shunt_index: usize,
11323 block_index: usize,
11324 output: *mut PioShuntBlockView,
11325 error: *mut *mut PioError,
11326) -> bool {
11327 unsafe {
11328 entry(error, false, || {
11329 let network = require_balanced_network(network)?;
11330 let shunt = network.shunts().get(shunt_index).ok_or_else(|| {
11331 boundary_error(
11332 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11333 format!("shunt index {shunt_index} is out of range"),
11334 )
11335 })?;
11336 let block = shunt
11337 .control
11338 .as_ref()
11339 .and_then(|control| control.blocks.get(block_index))
11340 .ok_or_else(|| {
11341 boundary_error(
11342 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11343 format!(
11344 "shunt block index {block_index} is out of range for shunt {shunt_index}"
11345 ),
11346 )
11347 })?;
11348 *require_output(output, "output")? = PioShuntBlockView {
11349 steps: block.steps,
11350 conductance_mw: block.g,
11351 susceptance_mvar: block.b,
11352 };
11353 Ok(true)
11354 })
11355 }
11356}
11357
11358#[unsafe(no_mangle)]
11360pub unsafe extern "C" fn pio_balanced_network_static_var_compensator_at(
11361 network: *const PioBalancedNetwork,
11362 index: usize,
11363 output: *mut PioBalancedStaticVarCompensatorView,
11364 error: *mut *mut PioError,
11365) -> bool {
11366 unsafe {
11367 entry(error, false, || {
11368 let network = require_balanced_network(network)?;
11369 let svc = network
11370 .static_var_compensators()
11371 .get(index)
11372 .ok_or_else(|| {
11373 boundary_error(
11374 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11375 format!("static VAR compensator index {index} is out of range"),
11376 )
11377 })?;
11378 let (component_id, has_component_id) = optional_string_view(svc.uid.as_deref());
11379 let (regulating_terminal, has_regulating_terminal) =
11380 terminal_reference_view(svc.regulating_terminal.as_ref());
11381 *require_output(output, "output")? = PioBalancedStaticVarCompensatorView {
11382 component_id,
11383 has_component_id,
11384 bus_id: svc.bus.0,
11385 minimum_susceptance_siemens: svc.b_min_siemens,
11386 maximum_susceptance_siemens: svc.b_max_siemens,
11387 voltage_setpoint_kv: svc.voltage_setpoint_kv,
11388 reactive_power_setpoint_mvar: svc.reactive_power_setpoint_mvar,
11389 regulation_mode: PioStringView::new(static_var_compensator_regulation_mode_name(
11390 svc.regulation_mode,
11391 )),
11392 regulating: svc.regulating,
11393 regulating_terminal,
11394 has_regulating_terminal,
11395 active_power_mw: svc.p,
11396 reactive_power_mvar: svc.q,
11397 in_service: svc.in_service,
11398 };
11399 Ok(true)
11400 })
11401 }
11402}
11403
11404#[unsafe(no_mangle)]
11406pub unsafe extern "C" fn pio_balanced_network_branch_at(
11407 network: *const PioBalancedNetwork,
11408 index: usize,
11409 output: *mut PioBalancedBranchView,
11410 error: *mut *mut PioError,
11411) -> bool {
11412 unsafe {
11413 entry(error, false, || {
11414 let network = require_balanced_network(network)?;
11415 let branch = network.branches().get(index).ok_or_else(|| {
11416 boundary_error(
11417 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11418 format!("branch index {index} is out of range"),
11419 )
11420 })?;
11421 let output = require_output(output, "output")?;
11422 let (component_id, has_component_id) = optional_string_view(branch.uid.as_deref());
11423 let (name, has_name) = optional_string_view(branch.name.as_deref());
11424 let charging = branch.calc_terminal_charging();
11425 let current = branch.current_ratings;
11426 let (control, has_control) = transformer_control_view(branch.control.as_ref());
11427 *output = PioBalancedBranchView {
11428 component_id,
11429 has_component_id,
11430 name,
11431 has_name,
11432 from_bus_id: branch.from.0,
11433 to_bus_id: branch.to.0,
11434 resistance_pu: branch.r,
11435 reactance_pu: branch.x,
11436 total_charging_susceptance_pu: branch.b,
11437 terminal_charging_is_explicit: branch.charging.is_some(),
11438 from_conductance_pu: charging.g_fr,
11439 from_susceptance_pu: charging.b_fr,
11440 to_conductance_pu: charging.g_to,
11441 to_susceptance_pu: charging.b_to,
11442 rate_a_mva: branch.rate_a,
11443 rate_b_mva: branch.rate_b,
11444 rate_c_mva: branch.rate_c,
11445 additional_rating_count: branch.rating_sets.len(),
11446 has_current_ratings: current.is_some(),
11447 current_rating_a: current.map_or(0.0, |rating| rating.c_rating_a),
11448 current_rating_b: current.map_or(0.0, |rating| rating.c_rating_b),
11449 current_rating_c: current.map_or(0.0, |rating| rating.c_rating_c),
11450 tap_ratio: branch.tap,
11451 effective_tap_ratio: branch.calc_effective_tap(),
11452 phase_shift_degrees: branch.shift,
11453 in_service: branch.in_service,
11454 angle_min_degrees: branch.angmin,
11455 angle_max_degrees: branch.angmax,
11456 control,
11457 has_control,
11458 route_point_count: branch.route.as_ref().map_or(0, Vec::len),
11459 has_route: branch.route.is_some(),
11460 };
11461 Ok(true)
11462 })
11463 }
11464}
11465
11466#[unsafe(no_mangle)]
11468pub unsafe extern "C" fn pio_balanced_network_branch_route_point_at(
11469 network: *const PioBalancedNetwork,
11470 branch_index: usize,
11471 point_index: usize,
11472 output: *mut PioBalancedLocationView,
11473 error: *mut *mut PioError,
11474) -> bool {
11475 unsafe {
11476 entry(error, false, || {
11477 let network = require_balanced_network(network)?;
11478 let branch = network.branches().get(branch_index).ok_or_else(|| {
11479 boundary_error(
11480 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11481 format!("branch index {branch_index} is out of range"),
11482 )
11483 })?;
11484 let route = branch.route.as_ref().ok_or_else(|| {
11485 boundary_error(
11486 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11487 format!("balanced branch {branch_index} has no route"),
11488 )
11489 })?;
11490 let point = route.get(point_index).ok_or_else(|| {
11491 boundary_error(
11492 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11493 format!("branch route point index {point_index} is out of range"),
11494 )
11495 })?;
11496 *require_output(output, "output")? = balanced_location_view(point);
11497 Ok(true)
11498 })
11499 }
11500}
11501
11502#[unsafe(no_mangle)]
11504pub unsafe extern "C" fn pio_balanced_network_branch_rating_at(
11505 network: *const PioBalancedNetwork,
11506 branch_index: usize,
11507 rating_index: usize,
11508 output: *mut PioBranchRatingView,
11509 error: *mut *mut PioError,
11510) -> bool {
11511 unsafe {
11512 entry(error, false, || {
11513 let network = require_balanced_network(network)?;
11514 let branch = network.branches().get(branch_index).ok_or_else(|| {
11515 boundary_error(
11516 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11517 format!("branch index {branch_index} is out of range"),
11518 )
11519 })?;
11520 let rating = branch.rating_sets.get(rating_index).ok_or_else(|| {
11521 boundary_error(
11522 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11523 format!(
11524 "rating index {rating_index} is out of range for branch {branch_index}"
11525 ),
11526 )
11527 })?;
11528 *require_output(output, "output")? = PioBranchRatingView {
11529 name: PioStringView::new(&rating.name),
11530 rate_mva: rating.rate_mva,
11531 };
11532 Ok(true)
11533 })
11534 }
11535}
11536
11537fn active_power_control_view(
11538 value: Option<&powerio_tx::ActivePowerControl>,
11539) -> (PioActivePowerControlView, bool) {
11540 let Some(value) = value else {
11541 return (
11542 PioActivePowerControlView {
11543 participate: false,
11544 droop_percent: 0.0,
11545 has_droop_percent: false,
11546 participation_factor: 0.0,
11547 has_participation_factor: false,
11548 minimum_target_active_power_mw: 0.0,
11549 has_minimum_target_active_power: false,
11550 maximum_target_active_power_mw: 0.0,
11551 has_maximum_target_active_power: false,
11552 },
11553 false,
11554 );
11555 };
11556 (
11557 PioActivePowerControlView {
11558 participate: value.participate,
11559 droop_percent: value.droop_percent.unwrap_or(0.0),
11560 has_droop_percent: value.droop_percent.is_some(),
11561 participation_factor: value.participation_factor.unwrap_or(0.0),
11562 has_participation_factor: value.participation_factor.is_some(),
11563 minimum_target_active_power_mw: value.minimum_target_active_power_mw.unwrap_or(0.0),
11564 has_minimum_target_active_power: value.minimum_target_active_power_mw.is_some(),
11565 maximum_target_active_power_mw: value.maximum_target_active_power_mw.unwrap_or(0.0),
11566 has_maximum_target_active_power: value.maximum_target_active_power_mw.is_some(),
11567 },
11568 true,
11569 )
11570}
11571
11572fn generator_energy_source_name(value: powerio_tx::GeneratorEnergySource) -> &'static str {
11573 match value {
11574 powerio_tx::GeneratorEnergySource::Hydro => "hydro",
11575 powerio_tx::GeneratorEnergySource::Nuclear => "nuclear",
11576 powerio_tx::GeneratorEnergySource::Wind => "wind",
11577 powerio_tx::GeneratorEnergySource::Thermal => "thermal",
11578 powerio_tx::GeneratorEnergySource::Solar => "solar",
11579 powerio_tx::GeneratorEnergySource::Other => "other",
11580 _ => "unknown",
11581 }
11582}
11583
11584#[unsafe(no_mangle)]
11586pub unsafe extern "C" fn pio_balanced_network_generator_at(
11587 network: *const PioBalancedNetwork,
11588 index: usize,
11589 output: *mut PioBalancedGeneratorView,
11590 error: *mut *mut PioError,
11591) -> bool {
11592 unsafe {
11593 entry(error, false, || {
11594 let network = require_balanced_network(network)?;
11595 let generator = network.generators().get(index).ok_or_else(|| {
11596 boundary_error(
11597 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11598 format!("generator index {index} is out of range"),
11599 )
11600 })?;
11601 let output = require_output(output, "output")?;
11602 let (component_id, has_component_id) = optional_string_view(generator.uid.as_deref());
11603 let cost = generator.cost.as_ref();
11604 let (active_power_control, has_active_power_control) =
11605 active_power_control_view(generator.active_power_control.as_ref());
11606 let (regulating_terminal, has_regulating_terminal) =
11607 terminal_reference_view(generator.regulating_terminal.as_ref());
11608 *output = PioBalancedGeneratorView {
11609 component_id,
11610 has_component_id,
11611 bus_id: generator.bus.0,
11612 energy_source: PioStringView::new(generator_energy_source_name(
11613 generator.energy_source,
11614 )),
11615 active_power_mw: generator.pg,
11616 reactive_power_mvar: generator.qg,
11617 active_power_max_mw: generator.pmax,
11618 active_power_min_mw: generator.pmin,
11619 reactive_power_max_mvar: generator.qmax,
11620 reactive_power_min_mvar: generator.qmin,
11621 voltage_setpoint_pu: generator.vg,
11622 machine_base_mva: generator.mbase,
11623 in_service: generator.in_service,
11624 has_cost: cost.is_some(),
11625 cost: cost.map_or(
11626 PioGeneratorCostView {
11627 model: 0,
11628 startup: 0.0,
11629 shutdown: 0.0,
11630 ncost: 0,
11631 coefficients: PioF64View::EMPTY,
11632 },
11633 |cost| PioGeneratorCostView {
11634 model: cost.model,
11635 startup: cost.startup,
11636 shutdown: cost.shutdown,
11637 ncost: cost.ncost,
11638 coefficients: PioF64View::new(&cost.coeffs),
11639 },
11640 ),
11641 regulated_bus_id: generator.regulated_bus.map_or(0, |bus| bus.0),
11642 has_regulated_bus: generator.regulated_bus.is_some(),
11643 capability_count: GENERATOR_CAPABILITY_NAMES.len(),
11644 active_power_control,
11645 has_active_power_control,
11646 voltage_regulation_on: generator.voltage_regulation_on,
11647 regulating_terminal,
11648 has_regulating_terminal,
11649 };
11650 Ok(true)
11651 })
11652 }
11653}
11654
11655#[unsafe(no_mangle)]
11657pub unsafe extern "C" fn pio_balanced_network_generator_capability_at(
11658 network: *const PioBalancedNetwork,
11659 generator_index: usize,
11660 capability_index: usize,
11661 output: *mut PioGeneratorCapabilityView,
11662 error: *mut *mut PioError,
11663) -> bool {
11664 unsafe {
11665 entry(error, false, || {
11666 let network = require_balanced_network(network)?;
11667 let generator = network.generators().get(generator_index).ok_or_else(|| {
11668 boundary_error(
11669 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11670 format!("generator index {generator_index} is out of range"),
11671 )
11672 })?;
11673 let name = GENERATOR_CAPABILITY_NAMES
11674 .get(capability_index)
11675 .ok_or_else(|| {
11676 boundary_error(
11677 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11678 format!("generator capability index {capability_index} is out of range"),
11679 )
11680 })?;
11681 let value = generator.caps[capability_index];
11682 *require_output(output, "output")? = PioGeneratorCapabilityView {
11683 name: PioStringView::new(name),
11684 value: value.unwrap_or(0.0),
11685 has_value: value.is_some(),
11686 };
11687 Ok(true)
11688 })
11689 }
11690}
11691
11692#[unsafe(no_mangle)]
11694pub unsafe extern "C" fn pio_balanced_network_storage_at(
11695 network: *const PioBalancedNetwork,
11696 index: usize,
11697 output: *mut PioBalancedStorageView,
11698 error: *mut *mut PioError,
11699) -> bool {
11700 unsafe {
11701 entry(error, false, || {
11702 let network = require_balanced_network(network)?;
11703 let storage = network.storage().get(index).ok_or_else(|| {
11704 boundary_error(
11705 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11706 format!("storage index {index} is out of range"),
11707 )
11708 })?;
11709 let output = require_output(output, "output")?;
11710 let (component_id, has_component_id) = optional_string_view(storage.uid.as_deref());
11711 let (active_power_control, has_active_power_control) =
11712 active_power_control_view(storage.active_power_control.as_ref());
11713 *output = PioBalancedStorageView {
11714 component_id,
11715 has_component_id,
11716 bus_id: storage.bus.0,
11717 active_power_mw: storage.ps,
11718 reactive_power_mvar: storage.qs,
11719 energy_mwh: storage.energy,
11720 energy_rating_mwh: storage.energy_rating,
11721 charge_rating_mw: storage.charge_rating,
11722 discharge_rating_mw: storage.discharge_rating,
11723 charge_efficiency: storage.charge_efficiency,
11724 discharge_efficiency: storage.discharge_efficiency,
11725 thermal_rating_mva: storage.thermal_rating,
11726 current_rating: storage.current_rating.unwrap_or(0.0),
11727 has_current_rating: storage.current_rating.is_some(),
11728 reactive_power_min_mvar: storage.qmin,
11729 reactive_power_max_mvar: storage.qmax,
11730 resistance_pu: storage.r,
11731 reactance_pu: storage.x,
11732 active_power_loss_mw: storage.p_loss,
11733 reactive_power_loss_mvar: storage.q_loss,
11734 in_service: storage.in_service,
11735 active_power_control,
11736 has_active_power_control,
11737 };
11738 Ok(true)
11739 })
11740 }
11741}
11742
11743#[unsafe(no_mangle)]
11745pub unsafe extern "C" fn pio_balanced_network_switch_at(
11746 network: *const PioBalancedNetwork,
11747 index: usize,
11748 output: *mut PioBalancedSwitchView,
11749 error: *mut *mut PioError,
11750) -> bool {
11751 unsafe {
11752 entry(error, false, || {
11753 let network = require_balanced_network(network)?;
11754 let switch = network.switches().get(index).ok_or_else(|| {
11755 boundary_error(
11756 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11757 format!("switch index {index} is out of range"),
11758 )
11759 })?;
11760 let (component_id, has_component_id) = optional_string_view(switch.uid.as_deref());
11761 *require_output(output, "output")? = PioBalancedSwitchView {
11762 component_id,
11763 has_component_id,
11764 from_bus_id: switch.from.0,
11765 to_bus_id: switch.to.0,
11766 closed: switch.closed,
11767 thermal_rating_mva: switch.thermal_rating.unwrap_or(0.0),
11768 has_thermal_rating: switch.thermal_rating.is_some(),
11769 current_rating_a: switch.current_rating.unwrap_or(0.0),
11770 has_current_rating: switch.current_rating.is_some(),
11771 from_active_power_mw: switch.pf.unwrap_or(0.0),
11772 has_from_active_power: switch.pf.is_some(),
11773 from_reactive_power_mvar: switch.qf.unwrap_or(0.0),
11774 has_from_reactive_power: switch.qf.is_some(),
11775 to_active_power_mw: switch.pt.unwrap_or(0.0),
11776 has_to_active_power: switch.pt.is_some(),
11777 to_reactive_power_mvar: switch.qt.unwrap_or(0.0),
11778 has_to_reactive_power: switch.qt.is_some(),
11779 };
11780 Ok(true)
11781 })
11782 }
11783}
11784
11785#[unsafe(no_mangle)]
11787pub unsafe extern "C" fn pio_balanced_network_hvdc_at(
11788 network: *const PioBalancedNetwork,
11789 index: usize,
11790 output: *mut PioBalancedHvdcView,
11791 error: *mut *mut PioError,
11792) -> bool {
11793 unsafe {
11794 entry(error, false, || {
11795 let network = require_balanced_network(network)?;
11796 let hvdc = network.hvdc().get(index).ok_or_else(|| {
11797 boundary_error(
11798 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11799 format!("HVDC index {index} is out of range"),
11800 )
11801 })?;
11802 let (component_id, has_component_id) = optional_string_view(hvdc.uid.as_deref());
11803 let (converter1, has_converter1) = hvdc_converter_view(hvdc.converter1.as_ref());
11804 let (converter2, has_converter2) = hvdc_converter_view(hvdc.converter2.as_ref());
11805 let (converters_mode, has_converters_mode) = hvdc
11806 .converters_mode
11807 .map_or((PioStringView::EMPTY, false), |mode| {
11808 (PioStringView::new(hvdc_converters_mode_name(mode)), true)
11809 });
11810 let cost = hvdc.cost.as_ref();
11811 *require_output(output, "output")? = PioBalancedHvdcView {
11812 component_id,
11813 has_component_id,
11814 from_bus_id: hvdc.from.0,
11815 to_bus_id: hvdc.to.0,
11816 in_service: hvdc.in_service,
11817 from_active_power_mw: hvdc.pf,
11818 to_active_power_mw: hvdc.pt,
11819 from_reactive_power_mvar: hvdc.qf,
11820 to_reactive_power_mvar: hvdc.qt,
11821 from_voltage_pu: hvdc.vf,
11822 to_voltage_pu: hvdc.vt,
11823 minimum_active_power_mw: hvdc.pmin,
11824 maximum_active_power_mw: hvdc.pmax,
11825 minimum_from_reactive_power_mvar: hvdc.qminf,
11826 maximum_from_reactive_power_mvar: hvdc.qmaxf,
11827 minimum_to_reactive_power_mvar: hvdc.qmint,
11828 maximum_to_reactive_power_mvar: hvdc.qmaxt,
11829 constant_loss_mw: hvdc.loss0,
11830 proportional_loss: hvdc.loss1,
11831 resistance_ohm: hvdc.resistance_ohm.unwrap_or(0.0),
11832 has_resistance: hvdc.resistance_ohm.is_some(),
11833 nominal_voltage_kv: hvdc.nominal_voltage_kv.unwrap_or(0.0),
11834 has_nominal_voltage: hvdc.nominal_voltage_kv.is_some(),
11835 converters_mode,
11836 has_converters_mode,
11837 converter1,
11838 has_converter1,
11839 converter2,
11840 has_converter2,
11841 cost: cost.map_or(
11842 PioGeneratorCostView {
11843 model: 0,
11844 startup: 0.0,
11845 shutdown: 0.0,
11846 ncost: 0,
11847 coefficients: PioF64View::EMPTY,
11848 },
11849 |cost| PioGeneratorCostView {
11850 model: cost.model,
11851 startup: cost.startup,
11852 shutdown: cost.shutdown,
11853 ncost: cost.ncost,
11854 coefficients: PioF64View::new(&cost.coeffs),
11855 },
11856 ),
11857 has_cost: cost.is_some(),
11858 };
11859 Ok(true)
11860 })
11861 }
11862}
11863
11864#[unsafe(no_mangle)]
11866pub unsafe extern "C" fn pio_balanced_network_three_winding_transformer_at(
11867 network: *const PioBalancedNetwork,
11868 index: usize,
11869 output: *mut PioBalancedThreeWindingTransformerView,
11870 error: *mut *mut PioError,
11871) -> bool {
11872 unsafe {
11873 entry(error, false, || {
11874 let network = require_balanced_network(network)?;
11875 let transformer = network.transformers_3w().get(index).ok_or_else(|| {
11876 boundary_error(
11877 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11878 format!("three winding transformer index {index} is out of range"),
11879 )
11880 })?;
11881 let (component_id, has_component_id) = optional_string_view(transformer.uid.as_deref());
11882 let (name, has_name) = optional_string_view(transformer.name.as_deref());
11883 *require_output(output, "output")? = PioBalancedThreeWindingTransformerView {
11884 component_id,
11885 has_component_id,
11886 name,
11887 has_name,
11888 winding_count: transformer.windings.len(),
11889 impedance_count: transformer.z.len(),
11890 star_voltage_magnitude_pu: transformer.star_vm,
11891 star_voltage_angle_degrees: transformer.star_va,
11892 magnetizing_conductance_pu: transformer.mag_g,
11893 magnetizing_susceptance_pu: transformer.mag_b,
11894 in_service: transformer.in_service,
11895 };
11896 Ok(true)
11897 })
11898 }
11899}
11900
11901#[unsafe(no_mangle)]
11903pub unsafe extern "C" fn pio_balanced_network_three_winding_transformer_winding_at(
11904 network: *const PioBalancedNetwork,
11905 transformer_index: usize,
11906 winding_index: usize,
11907 output: *mut PioThreeWindingTransformerWindingView,
11908 error: *mut *mut PioError,
11909) -> bool {
11910 unsafe {
11911 entry(error, false, || {
11912 let network = require_balanced_network(network)?;
11913 let transformer = network
11914 .transformers_3w()
11915 .get(transformer_index)
11916 .ok_or_else(|| {
11917 boundary_error(
11918 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11919 format!(
11920 "three winding transformer index {transformer_index} is out of range"
11921 ),
11922 )
11923 })?;
11924 let winding = transformer.windings.get(winding_index).ok_or_else(|| {
11925 boundary_error(
11926 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11927 format!("winding index {winding_index} is out of range"),
11928 )
11929 })?;
11930 let (control, has_control) = transformer_control_view(winding.control.as_ref());
11931 *require_output(output, "output")? = PioThreeWindingTransformerWindingView {
11932 bus_id: winding.bus.0,
11933 tap_ratio: winding.tap,
11934 phase_shift_degrees: winding.shift,
11935 nominal_voltage_kv: winding.nominal_kv,
11936 rating_a_mva: winding.rate_a,
11937 rating_b_mva: winding.rate_b,
11938 rating_c_mva: winding.rate_c,
11939 control,
11940 has_control,
11941 };
11942 Ok(true)
11943 })
11944 }
11945}
11946
11947#[unsafe(no_mangle)]
11949pub unsafe extern "C" fn pio_balanced_network_three_winding_transformer_impedance_at(
11950 network: *const PioBalancedNetwork,
11951 transformer_index: usize,
11952 impedance_index: usize,
11953 output: *mut PioThreeWindingTransformerImpedanceView,
11954 error: *mut *mut PioError,
11955) -> bool {
11956 unsafe {
11957 entry(error, false, || {
11958 let network = require_balanced_network(network)?;
11959 let transformer = network
11960 .transformers_3w()
11961 .get(transformer_index)
11962 .ok_or_else(|| {
11963 boundary_error(
11964 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11965 format!(
11966 "three winding transformer index {transformer_index} is out of range"
11967 ),
11968 )
11969 })?;
11970 let impedance = transformer.z.get(impedance_index).ok_or_else(|| {
11971 boundary_error(
11972 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11973 format!("impedance index {impedance_index} is out of range"),
11974 )
11975 })?;
11976 *require_output(output, "output")? = PioThreeWindingTransformerImpedanceView {
11977 resistance_pu: impedance.r,
11978 reactance_pu: impedance.x,
11979 base_mva: impedance.base_mva,
11980 };
11981 Ok(true)
11982 })
11983 }
11984}
11985
11986#[unsafe(no_mangle)]
11988pub unsafe extern "C" fn pio_balanced_network_area_at(
11989 network: *const PioBalancedNetwork,
11990 index: usize,
11991 output: *mut PioBalancedAreaView,
11992 error: *mut *mut PioError,
11993) -> bool {
11994 unsafe {
11995 entry(error, false, || {
11996 let network = require_balanced_network(network)?;
11997 let area = network.areas().get(index).ok_or_else(|| {
11998 boundary_error(
11999 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12000 format!("area index {index} is out of range"),
12001 )
12002 })?;
12003 let (name, has_name) = optional_string_view(area.name.as_deref());
12004 let (component_id, has_component_id) = optional_string_view(area.uid.as_deref());
12005 let (area_type, has_area_type) = optional_string_view(area.area_type.as_deref());
12006 *require_output(output, "output")? = PioBalancedAreaView {
12007 number: area.number,
12008 slack_bus_id: area.slack_bus.map_or(0, |bus| bus.0),
12009 has_slack_bus: area.slack_bus.is_some(),
12010 net_interchange_mw: area.net_interchange,
12011 tolerance_mw: area.tolerance,
12012 name,
12013 has_name,
12014 component_id,
12015 has_component_id,
12016 area_type,
12017 has_area_type,
12018 };
12019 Ok(true)
12020 })
12021 }
12022}
12023
12024#[unsafe(no_mangle)]
12025pub unsafe extern "C" fn pio_balanced_network_retain(
12026 network: *const PioBalancedNetwork,
12027) -> *mut PioBalancedNetwork {
12028 unsafe { PioBalancedNetwork::retain_raw(network) }
12029}
12030
12031#[unsafe(no_mangle)]
12032pub unsafe extern "C" fn pio_balanced_network_release(network: *mut PioBalancedNetwork) {
12033 unsafe { PioBalancedNetwork::release_raw(network) };
12034}
12035
12036unsafe fn require_multiconductor_network<'a>(
12037 network: *const PioMulticonductorNetwork,
12038) -> Result<&'a powerio_dist::MulticonductorNetwork, *mut PioError> {
12039 unsafe { PioMulticonductorNetwork::get(network) }
12040 .and_then(MulticonductorNetworkInner::network)
12041 .ok_or_else(|| {
12042 boundary_error(
12043 &codes::BIND_CAPI_NULL_HANDLE,
12044 "PioMulticonductorNetwork must not be NULL",
12045 )
12046 })
12047}
12048
12049fn optional_f64_view(value: Option<&[f64]>) -> (PioF64View, bool) {
12050 value.map_or((PioF64View::EMPTY, false), |value| {
12051 (PioF64View::new(value), true)
12052 })
12053}
12054
12055fn dist_coords_kind_name(kind: powerio_dist::DistCoordsKind) -> &'static str {
12056 match kind {
12057 powerio_dist::DistCoordsKind::Source => "source",
12058 powerio_dist::DistCoordsKind::Synthetic => "synthetic",
12059 powerio_dist::DistCoordsKind::Manual => "manual",
12060 powerio_dist::DistCoordsKind::Derived => "derived",
12061 _ => "unknown",
12062 }
12063}
12064
12065fn multiconductor_location_view(
12066 location: &powerio_dist::DistLocation,
12067) -> PioMulticonductorLocationView {
12068 let (kind, has_kind) = location.kind.map_or((PioStringView::EMPTY, false), |kind| {
12069 (PioStringView::new(dist_coords_kind_name(kind)), true)
12070 });
12071 PioMulticonductorLocationView {
12072 x: location.x,
12073 y: location.y,
12074 kind,
12075 has_kind,
12076 }
12077}
12078
12079fn empty_multiconductor_location_view() -> PioMulticonductorLocationView {
12080 PioMulticonductorLocationView {
12081 x: 0.0,
12082 y: 0.0,
12083 kind: PioStringView::EMPTY,
12084 has_kind: false,
12085 }
12086}
12087
12088fn multiconductor_geo_view(geo: Option<&powerio_dist::DistGeoMeta>) -> PioMulticonductorGeoView {
12089 let Some(geo) = geo else {
12090 return PioMulticonductorGeoView {
12091 has_geo: false,
12092 space: PioStringView::EMPTY,
12093 crs: PioStringView::EMPTY,
12094 has_crs: false,
12095 kind: PioStringView::EMPTY,
12096 has_kind: false,
12097 has_canvas: false,
12098 canvas_width: 0.0,
12099 has_canvas_width: false,
12100 canvas_height: 0.0,
12101 has_canvas_height: false,
12102 canvas_units: PioStringView::EMPTY,
12103 has_canvas_units: false,
12104 };
12105 };
12106 let (space, crs, has_crs, canvas) = match &geo.space {
12107 powerio_dist::CoordinateSpace::Geographic { crs } => (
12108 "geographic",
12109 crs.as_deref()
12110 .map_or(PioStringView::EMPTY, PioStringView::new),
12111 crs.is_some(),
12112 None,
12113 ),
12114 powerio_dist::CoordinateSpace::Projected { crs } => (
12115 "projected",
12116 crs.as_deref()
12117 .map_or(PioStringView::EMPTY, PioStringView::new),
12118 crs.is_some(),
12119 None,
12120 ),
12121 powerio_dist::CoordinateSpace::Diagram { canvas } => {
12122 ("diagram", PioStringView::EMPTY, false, canvas.as_ref())
12123 }
12124 powerio_dist::CoordinateSpace::Unknown => ("unknown", PioStringView::EMPTY, false, None),
12125 _ => ("unknown", PioStringView::EMPTY, false, None),
12126 };
12127 let (kind, has_kind) = geo.kind.map_or((PioStringView::EMPTY, false), |kind| {
12128 (PioStringView::new(dist_coords_kind_name(kind)), true)
12129 });
12130 PioMulticonductorGeoView {
12131 has_geo: true,
12132 space: PioStringView::new(space),
12133 crs,
12134 has_crs,
12135 kind,
12136 has_kind,
12137 has_canvas: canvas.is_some(),
12138 canvas_width: canvas.and_then(|canvas| canvas.width).unwrap_or(0.0),
12139 has_canvas_width: canvas.is_some_and(|canvas| canvas.width.is_some()),
12140 canvas_height: canvas.and_then(|canvas| canvas.height).unwrap_or(0.0),
12141 has_canvas_height: canvas.is_some_and(|canvas| canvas.height.is_some()),
12142 canvas_units: canvas
12143 .and_then(|canvas| canvas.units.as_deref())
12144 .map_or(PioStringView::EMPTY, PioStringView::new),
12145 has_canvas_units: canvas.is_some_and(|canvas| canvas.units.is_some()),
12146 }
12147}
12148
12149fn multiconductor_configuration_name(configuration: powerio_dist::Configuration) -> &'static str {
12150 match configuration {
12151 powerio_dist::Configuration::Wye => "wye",
12152 powerio_dist::Configuration::Delta => "delta",
12153 powerio_dist::Configuration::SinglePhase => "single_phase",
12154 _ => "unknown",
12155 }
12156}
12157
12158fn multiconductor_winding_connection_name(
12159 connection: powerio_dist::DistWindingConn,
12160) -> &'static str {
12161 match connection {
12162 powerio_dist::DistWindingConn::Wye => "wye",
12163 powerio_dist::DistWindingConn::Delta => "delta",
12164 _ => "unknown",
12165 }
12166}
12167
12168fn inverter_topology_name(topology: powerio_dist::IbrTopology) -> &'static str {
12169 match topology {
12170 powerio_dist::IbrTopology::SinglePhase => "SINGLE_PHASE",
12171 powerio_dist::IbrTopology::ThreeLeg => "THREE_LEG",
12172 powerio_dist::IbrTopology::FourLeg => "FOUR_LEG",
12173 _ => "UNKNOWN",
12174 }
12175}
12176
12177fn inverter_prime_mover_name(prime_mover: powerio_dist::IbrPrimeMover) -> &'static str {
12178 match prime_mover {
12179 powerio_dist::IbrPrimeMover::Pv => "PV",
12180 powerio_dist::IbrPrimeMover::Battery => "BATTERY",
12181 powerio_dist::IbrPrimeMover::Generic => "GENERIC",
12182 powerio_dist::IbrPrimeMover::Statcom => "STATCOM",
12183 powerio_dist::IbrPrimeMover::Dstatcom => "DSTATCOM",
12184 _ => "UNKNOWN",
12185 }
12186}
12187
12188fn inverter_voltage_aggregation_name(
12189 aggregation: powerio_dist::IbrVoltageAggregation,
12190) -> &'static str {
12191 match aggregation {
12192 powerio_dist::IbrVoltageAggregation::PerPhase => "PER_PHASE",
12193 powerio_dist::IbrVoltageAggregation::Average => "AVERAGE",
12194 _ => "UNKNOWN",
12195 }
12196}
12197
12198fn control_voltage_reference_name(
12199 reference: powerio_dist::ControlVoltageReference,
12200) -> &'static str {
12201 match reference {
12202 powerio_dist::ControlVoltageReference::PnPerPhase => "PN_PER_PHASE",
12203 powerio_dist::ControlVoltageReference::PpPerPhase => "PP_PER_PHASE",
12204 powerio_dist::ControlVoltageReference::PpAveraged => "PP_AVERAGED",
12205 powerio_dist::ControlVoltageReference::PgAveraged => "PG_AVERAGED",
12206 powerio_dist::ControlVoltageReference::PnAveraged => "PN_AVERAGED",
12207 powerio_dist::ControlVoltageReference::PgPerPhase => "PG_PER_PHASE",
12208 _ => "UNKNOWN",
12209 }
12210}
12211
12212fn reactive_power_unit_name(unit: powerio_dist::ReactivePowerUnit) -> &'static str {
12213 match unit {
12214 powerio_dist::ReactivePowerUnit::VaFraction => "VA_FRACTION",
12215 powerio_dist::ReactivePowerUnit::Var => "VAR",
12216 _ => "UNKNOWN",
12217 }
12218}
12219
12220fn active_power_unit_name(unit: powerio_dist::ActivePowerUnit) -> &'static str {
12221 match unit {
12222 powerio_dist::ActivePowerUnit::VaFraction => "VA_FRACTION",
12223 powerio_dist::ActivePowerUnit::W => "W",
12224 _ => "UNKNOWN",
12225 }
12226}
12227
12228fn reactive_power_reference_name(reference: powerio_dist::ReactivePowerReference) -> &'static str {
12229 match reference {
12230 powerio_dist::ReactivePowerReference::VarMax => "VAR_MAX",
12231 powerio_dist::ReactivePowerReference::VarAvailable => "VAR_AVAILABLE",
12232 _ => "UNKNOWN",
12233 }
12234}
12235
12236fn active_power_reference_name(reference: powerio_dist::ActivePowerReference) -> &'static str {
12237 match reference {
12238 powerio_dist::ActivePowerReference::PAvailable => "P_AVAILABLE",
12239 powerio_dist::ActivePowerReference::PMax => "P_MAX",
12240 powerio_dist::ActivePowerReference::SMax => "S_MAX",
12241 _ => "UNKNOWN",
12242 }
12243}
12244
12245fn string_slice_at(
12246 values: &[String],
12247 index: usize,
12248 description: &str,
12249) -> Result<PioStringView, *mut PioError> {
12250 values
12251 .get(index)
12252 .map(|value| PioStringView::new(value))
12253 .ok_or_else(|| {
12254 boundary_error(
12255 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12256 format!("{description} index {index} is out of range"),
12257 )
12258 })
12259}
12260
12261fn conductor_matrix_row(
12262 matrix: &powerio_dist::ConductorMatrix,
12263 row_index: usize,
12264 description: &str,
12265) -> Result<PioF64View, *mut PioError> {
12266 matrix
12267 .get(row_index)
12268 .map(|row| PioF64View::new(row))
12269 .ok_or_else(|| {
12270 boundary_error(
12271 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12272 format!("{description} row index {row_index} is out of range"),
12273 )
12274 })
12275}
12276
12277fn multiconductor_bus_view(bus: &powerio_dist::DistBus) -> PioMulticonductorBusView {
12278 let (vpn_min, has_vpn_min) = optional_f64_view(bus.vpn_min.as_deref());
12279 let (vpn_max, has_vpn_max) = optional_f64_view(bus.vpn_max.as_deref());
12280 let (vpp_min, has_vpp_min) = optional_f64_view(bus.vpp_min.as_deref());
12281 let (vpp_max, has_vpp_max) = optional_f64_view(bus.vpp_max.as_deref());
12282 let (location, has_location) = bus
12283 .location
12284 .as_ref()
12285 .map_or((empty_multiconductor_location_view(), false), |location| {
12286 (multiconductor_location_view(location), true)
12287 });
12288 PioMulticonductorBusView {
12289 id: PioStringView::new(&bus.id),
12290 terminal_count: bus.terminals.len(),
12291 grounded_terminal_count: bus.grounded.len(),
12292 voltage_min_v: bus.v_min.unwrap_or(0.0),
12293 has_voltage_min: bus.v_min.is_some(),
12294 voltage_max_v: bus.v_max.unwrap_or(0.0),
12295 has_voltage_max: bus.v_max.is_some(),
12296 phase_to_ground_voltage_min_v: optional_f64_view(bus.v_min_phase.as_deref()).0,
12297 has_phase_to_ground_voltage_min: bus.v_min_phase.is_some(),
12298 phase_to_ground_voltage_max_v: optional_f64_view(bus.v_max_phase.as_deref()).0,
12299 has_phase_to_ground_voltage_max: bus.v_max_phase.is_some(),
12300 phase_to_neutral_voltage_min_v: vpn_min,
12301 has_phase_to_neutral_voltage_min: has_vpn_min,
12302 phase_to_neutral_voltage_max_v: vpn_max,
12303 has_phase_to_neutral_voltage_max: has_vpn_max,
12304 phase_to_phase_voltage_min_v: vpp_min,
12305 has_phase_to_phase_voltage_min: has_vpp_min,
12306 phase_to_phase_voltage_max_v: vpp_max,
12307 has_phase_to_phase_voltage_max: has_vpp_max,
12308 positive_sequence_voltage_min_v: bus.vpos_min.unwrap_or(0.0),
12309 has_positive_sequence_voltage_min: bus.vpos_min.is_some(),
12310 positive_sequence_voltage_max_v: bus.vpos_max.unwrap_or(0.0),
12311 has_positive_sequence_voltage_max: bus.vpos_max.is_some(),
12312 negative_sequence_voltage_max_v: bus.vneg_max.unwrap_or(0.0),
12313 has_negative_sequence_voltage_max: bus.vneg_max.is_some(),
12314 zero_sequence_voltage_max_v: bus.vzero_max.unwrap_or(0.0),
12315 has_zero_sequence_voltage_max: bus.vzero_max.is_some(),
12316 neutral_to_ground_voltage_max_v: bus.vn_max.unwrap_or(0.0),
12317 has_neutral_to_ground_voltage_max: bus.vn_max.is_some(),
12318 location,
12319 has_location,
12320 }
12321}
12322
12323fn multiconductor_line_code_view(
12324 line_code: &powerio_dist::DistLineCode,
12325) -> PioMulticonductorLineCodeView {
12326 let (current_limit, has_current_limit) = optional_f64_view(line_code.i_max.as_deref());
12327 let (apparent_power_limit, has_apparent_power_limit) =
12328 optional_f64_view(line_code.s_max.as_deref());
12329 let (source, has_source) = optional_string_view(line_code.source.as_deref());
12330 PioMulticonductorLineCodeView {
12331 name: PioStringView::new(&line_code.name),
12332 conductor_count: line_code.n_conductors,
12333 resistance_matrix_row_count: line_code.r_series.len(),
12334 reactance_matrix_row_count: line_code.x_series.len(),
12335 conductance_from_matrix_row_count: line_code.g_from.len(),
12336 susceptance_from_matrix_row_count: line_code.b_from.len(),
12337 conductance_to_matrix_row_count: line_code.g_to.len(),
12338 susceptance_to_matrix_row_count: line_code.b_to.len(),
12339 current_limit_a: current_limit,
12340 has_current_limit,
12341 apparent_power_limit_va: apparent_power_limit,
12342 has_apparent_power_limit,
12343 source,
12344 has_source,
12345 }
12346}
12347
12348fn multiconductor_line_view(line: &powerio_dist::DistLine) -> PioMulticonductorLineView {
12349 let (current_limit, has_current_limit) = optional_f64_view(line.i_max.as_deref());
12350 let (apparent_power_limit, has_apparent_power_limit) = optional_f64_view(line.s_max.as_deref());
12351 PioMulticonductorLineView {
12352 name: PioStringView::new(&line.name),
12353 bus_from: PioStringView::new(&line.bus_from),
12354 bus_to: PioStringView::new(&line.bus_to),
12355 terminal_map_from_count: line.terminal_map_from.len(),
12356 terminal_map_to_count: line.terminal_map_to.len(),
12357 line_code: PioStringView::new(&line.linecode),
12358 length_m: line.length,
12359 route_point_count: line.route.as_ref().map_or(0, Vec::len),
12360 has_route: line.route.is_some(),
12361 current_limit_a: current_limit,
12362 has_current_limit,
12363 apparent_power_limit_va: apparent_power_limit,
12364 has_apparent_power_limit,
12365 }
12366}
12367
12368fn multiconductor_switch_view(switch: &powerio_dist::DistSwitch) -> PioMulticonductorSwitchView {
12369 let (current_limit, has_current_limit) = optional_f64_view(switch.i_max.as_deref());
12370 PioMulticonductorSwitchView {
12371 name: PioStringView::new(&switch.name),
12372 bus_from: PioStringView::new(&switch.bus_from),
12373 bus_to: PioStringView::new(&switch.bus_to),
12374 terminal_map_from_count: switch.terminal_map_from.len(),
12375 terminal_map_to_count: switch.terminal_map_to.len(),
12376 open: switch.open,
12377 current_limit_a: current_limit,
12378 has_current_limit,
12379 }
12380}
12381
12382fn multiconductor_load_view(load: &powerio_dist::DistLoad) -> PioMulticonductorLoadView {
12383 let empty = PioF64View::EMPTY;
12384 let (
12385 voltage_model,
12386 nominal_voltage,
12387 alpha_z,
12388 alpha_i,
12389 alpha_p,
12390 beta_z,
12391 beta_i,
12392 beta_p,
12393 gamma_p,
12394 gamma_q,
12395 ) = match &load.voltage_model {
12396 powerio_dist::DistLoadVoltageModel::ConstantPower { v_nom } => (
12397 "constant_power",
12398 PioF64View::new(v_nom),
12399 empty,
12400 empty,
12401 empty,
12402 empty,
12403 empty,
12404 empty,
12405 empty,
12406 empty,
12407 ),
12408 powerio_dist::DistLoadVoltageModel::ConstantCurrent { v_nom } => (
12409 "constant_current",
12410 PioF64View::new(v_nom),
12411 empty,
12412 empty,
12413 empty,
12414 empty,
12415 empty,
12416 empty,
12417 empty,
12418 empty,
12419 ),
12420 powerio_dist::DistLoadVoltageModel::ConstantImpedance { v_nom } => (
12421 "constant_impedance",
12422 PioF64View::new(v_nom),
12423 empty,
12424 empty,
12425 empty,
12426 empty,
12427 empty,
12428 empty,
12429 empty,
12430 empty,
12431 ),
12432 powerio_dist::DistLoadVoltageModel::Zip {
12433 v_nom,
12434 alpha_z,
12435 alpha_i,
12436 alpha_p,
12437 beta_z,
12438 beta_i,
12439 beta_p,
12440 } => (
12441 "zip",
12442 PioF64View::new(v_nom),
12443 PioF64View::new(alpha_z),
12444 PioF64View::new(alpha_i),
12445 PioF64View::new(alpha_p),
12446 PioF64View::new(beta_z),
12447 PioF64View::new(beta_i),
12448 PioF64View::new(beta_p),
12449 empty,
12450 empty,
12451 ),
12452 powerio_dist::DistLoadVoltageModel::Exponential {
12453 v_nom,
12454 gamma_p,
12455 gamma_q,
12456 } => (
12457 "exponential",
12458 PioF64View::new(v_nom),
12459 empty,
12460 empty,
12461 empty,
12462 empty,
12463 empty,
12464 empty,
12465 PioF64View::new(gamma_p),
12466 PioF64View::new(gamma_q),
12467 ),
12468 _ => (
12469 "unknown", empty, empty, empty, empty, empty, empty, empty, empty, empty,
12470 ),
12471 };
12472 PioMulticonductorLoadView {
12473 name: PioStringView::new(&load.name),
12474 bus: PioStringView::new(&load.bus),
12475 terminal_map_count: load.terminal_map.len(),
12476 configuration: PioStringView::new(multiconductor_configuration_name(load.configuration)),
12477 active_power_nominal_w: PioF64View::new(&load.p_nom),
12478 reactive_power_nominal_var: PioF64View::new(&load.q_nom),
12479 voltage_model: PioStringView::new(voltage_model),
12480 nominal_voltage_v: nominal_voltage,
12481 active_power_constant_impedance: alpha_z,
12482 active_power_constant_current: alpha_i,
12483 active_power_constant_power: alpha_p,
12484 reactive_power_constant_impedance: beta_z,
12485 reactive_power_constant_current: beta_i,
12486 reactive_power_constant_power: beta_p,
12487 active_power_exponent: gamma_p,
12488 reactive_power_exponent: gamma_q,
12489 }
12490}
12491
12492fn multiconductor_generator_view(
12493 generator: &powerio_dist::DistGenerator,
12494) -> PioMulticonductorGeneratorView {
12495 let (p_min, has_p_min) = optional_f64_view(generator.p_min.as_deref());
12496 let (p_max, has_p_max) = optional_f64_view(generator.p_max.as_deref());
12497 let (q_min, has_q_min) = optional_f64_view(generator.q_min.as_deref());
12498 let (q_max, has_q_max) = optional_f64_view(generator.q_max.as_deref());
12499 let (cost, has_cost) = optional_f64_view(generator.cost.as_deref());
12500 let (s_max, has_s_max) = optional_f64_view(generator.s_max.as_deref());
12501 let (i_max, has_i_max) = optional_f64_view(generator.i_max.as_deref());
12502 PioMulticonductorGeneratorView {
12503 name: PioStringView::new(&generator.name),
12504 bus: PioStringView::new(&generator.bus),
12505 terminal_map_count: generator.terminal_map.len(),
12506 configuration: PioStringView::new(multiconductor_configuration_name(
12507 generator.configuration,
12508 )),
12509 active_power_nominal_w: PioF64View::new(&generator.p_nom),
12510 reactive_power_nominal_var: PioF64View::new(&generator.q_nom),
12511 active_power_min_w: p_min,
12512 has_active_power_min: has_p_min,
12513 active_power_max_w: p_max,
12514 has_active_power_max: has_p_max,
12515 reactive_power_min_var: q_min,
12516 has_reactive_power_min: has_q_min,
12517 reactive_power_max_var: q_max,
12518 has_reactive_power_max: has_q_max,
12519 active_power_dispatch_cost_per_kwh: cost,
12520 has_active_power_dispatch_cost: has_cost,
12521 apparent_power_limit_va: s_max,
12522 has_apparent_power_limit: has_s_max,
12523 current_limit_a: i_max,
12524 has_current_limit: has_i_max,
12525 }
12526}
12527
12528fn inverter_based_resource_view(ibr: &powerio_dist::DistIbr) -> PioInverterBasedResourceView {
12529 let (i_max, has_i_max) = optional_f64_view(ibr.i_max.as_deref());
12530 let (p_min, has_p_min) = optional_f64_view(ibr.p_min.as_deref());
12531 let (p_max, has_p_max) = optional_f64_view(ibr.p_max.as_deref());
12532 let (q_min, has_q_min) = optional_f64_view(ibr.q_min.as_deref());
12533 let (q_max, has_q_max) = optional_f64_view(ibr.q_max.as_deref());
12534 let (control_profile, has_control_profile) =
12535 optional_string_view(ibr.control_profile.as_deref());
12536 let (voltage_aggregation, has_voltage_aggregation) =
12537 ibr.voltage_aggregation
12538 .map_or((PioStringView::EMPTY, false), |aggregation| {
12539 (
12540 PioStringView::new(inverter_voltage_aggregation_name(aggregation)),
12541 true,
12542 )
12543 });
12544 PioInverterBasedResourceView {
12545 name: PioStringView::new(&ibr.name),
12546 bus: PioStringView::new(&ibr.bus),
12547 terminal_map_count: ibr.terminal_map.len(),
12548 topology: PioStringView::new(inverter_topology_name(ibr.topology)),
12549 prime_mover: PioStringView::new(inverter_prime_mover_name(ibr.prime_mover)),
12550 apparent_power_limit_va: PioF64View::new(&ibr.s_max),
12551 current_limit_a: i_max,
12552 has_current_limit: has_i_max,
12553 active_power_available_w: ibr.p_avail.unwrap_or(0.0),
12554 has_active_power_available: ibr.p_avail.is_some(),
12555 active_power_min_w: p_min,
12556 has_active_power_min: has_p_min,
12557 active_power_max_w: p_max,
12558 has_active_power_max: has_p_max,
12559 reactive_power_min_var: q_min,
12560 has_reactive_power_min: has_q_min,
12561 reactive_power_max_var: q_max,
12562 has_reactive_power_max: has_q_max,
12563 control_profile,
12564 has_control_profile,
12565 voltage_aggregation,
12566 has_voltage_aggregation,
12567 }
12568}
12569
12570fn control_profile_view(profile: &powerio_dist::DistControlProfile) -> PioControlProfileView {
12571 let power_factor = profile.power_factor.as_ref();
12572 let volt_var = profile.volt_var.as_ref();
12573 let volt_watt = profile.volt_watt.as_ref();
12574 let (vv_voltage_reference, has_vv_voltage_reference) = volt_var
12575 .and_then(|control| control.voltage_reference)
12576 .map_or((PioStringView::EMPTY, false), |reference| {
12577 (
12578 PioStringView::new(control_voltage_reference_name(reference)),
12579 true,
12580 )
12581 });
12582 let (vv_q_unit, has_vv_q_unit) = volt_var
12583 .and_then(|control| control.q_unit)
12584 .map_or((PioStringView::EMPTY, false), |unit| {
12585 (PioStringView::new(reactive_power_unit_name(unit)), true)
12586 });
12587 let (vv_q_ref, has_vv_q_ref) = volt_var.and_then(|control| control.q_ref).map_or(
12588 (PioStringView::EMPTY, false),
12589 |reference| {
12590 (
12591 PioStringView::new(reactive_power_reference_name(reference)),
12592 true,
12593 )
12594 },
12595 );
12596 let (vw_voltage_reference, has_vw_voltage_reference) = volt_watt
12597 .and_then(|control| control.voltage_reference)
12598 .map_or((PioStringView::EMPTY, false), |reference| {
12599 (
12600 PioStringView::new(control_voltage_reference_name(reference)),
12601 true,
12602 )
12603 });
12604 let (vw_p_unit, has_vw_p_unit) = volt_watt
12605 .and_then(|control| control.p_unit)
12606 .map_or((PioStringView::EMPTY, false), |unit| {
12607 (PioStringView::new(active_power_unit_name(unit)), true)
12608 });
12609 let (vw_p_ref, has_vw_p_ref) = volt_watt.and_then(|control| control.p_ref).map_or(
12610 (PioStringView::EMPTY, false),
12611 |reference| {
12612 (
12613 PioStringView::new(active_power_reference_name(reference)),
12614 true,
12615 )
12616 },
12617 );
12618 PioControlProfileView {
12619 name: PioStringView::new(&profile.name),
12620 has_power_factor: power_factor.is_some(),
12621 power_factor: power_factor.map_or(0.0, |control| control.pf),
12622 has_volt_var: volt_var.is_some(),
12623 volt_var_voltage_reference: vv_voltage_reference,
12624 has_volt_var_voltage_reference: has_vv_voltage_reference,
12625 volt_var_breakpoints: volt_var.map_or(PioF64View::EMPTY, |control| {
12626 PioF64View::new(&control.breakpoints)
12627 }),
12628 volt_var_reactive_power_limits: volt_var.map_or(PioF64View::EMPTY, |control| {
12629 PioF64View::new(&control.q_limits)
12630 }),
12631 volt_var_reactive_power_unit: vv_q_unit,
12632 has_volt_var_reactive_power_unit: has_vv_q_unit,
12633 volt_var_reactive_power_reference: vv_q_ref,
12634 has_volt_var_reactive_power_reference: has_vv_q_ref,
12635 volt_var_active_power_min_for_reactive_power_w: volt_var
12636 .and_then(|control| control.p_min_for_q)
12637 .unwrap_or(0.0),
12638 has_volt_var_active_power_min_for_reactive_power: volt_var
12639 .is_some_and(|control| control.p_min_for_q.is_some()),
12640 volt_var_active_power_min_for_max_reactive_power_w: volt_var
12641 .and_then(|control| control.p_min_for_q_max)
12642 .unwrap_or(0.0),
12643 has_volt_var_active_power_min_for_max_reactive_power: volt_var
12644 .is_some_and(|control| control.p_min_for_q_max.is_some()),
12645 has_volt_watt: volt_watt.is_some(),
12646 volt_watt_voltage_reference: vw_voltage_reference,
12647 has_volt_watt_voltage_reference: has_vw_voltage_reference,
12648 volt_watt_breakpoints: volt_watt.map_or(PioF64View::EMPTY, |control| {
12649 PioF64View::new(&control.breakpoints)
12650 }),
12651 volt_watt_active_power_limits: volt_watt.map_or(PioF64View::EMPTY, |control| {
12652 PioF64View::new(&control.p_limits)
12653 }),
12654 volt_watt_active_power_unit: vw_p_unit,
12655 has_volt_watt_active_power_unit: has_vw_p_unit,
12656 volt_watt_active_power_reference: vw_p_ref,
12657 has_volt_watt_active_power_reference: has_vw_p_ref,
12658 }
12659}
12660
12661#[unsafe(no_mangle)]
12662pub unsafe extern "C" fn pio_multiconductor_network_name(
12663 network: *const PioMulticonductorNetwork,
12664) -> PioStringView {
12665 unsafe { PioMulticonductorNetwork::get(network) }
12666 .and_then(MulticonductorNetworkInner::network)
12667 .and_then(|network| network.name().as_deref())
12668 .map_or(PioStringView::EMPTY, PioStringView::new)
12669}
12670
12671#[unsafe(no_mangle)]
12672pub unsafe extern "C" fn pio_multiconductor_network_has_name(
12673 network: *const PioMulticonductorNetwork,
12674) -> bool {
12675 unsafe { PioMulticonductorNetwork::get(network) }
12676 .and_then(MulticonductorNetworkInner::network)
12677 .is_some_and(|network| network.name().is_some())
12678}
12679
12680#[unsafe(no_mangle)]
12681pub unsafe extern "C" fn pio_multiconductor_network_source_format(
12682 network: *const PioMulticonductorNetwork,
12683) -> PioStringView {
12684 unsafe { PioMulticonductorNetwork::get(network) }
12685 .and_then(MulticonductorNetworkInner::network)
12686 .and_then(|network| *network.source_format())
12687 .map_or(PioStringView::EMPTY, |format| {
12688 PioStringView::new(format.name())
12689 })
12690}
12691
12692#[unsafe(no_mangle)]
12693pub unsafe extern "C" fn pio_multiconductor_network_has_source_format(
12694 network: *const PioMulticonductorNetwork,
12695) -> bool {
12696 unsafe { PioMulticonductorNetwork::get(network) }
12697 .and_then(MulticonductorNetworkInner::network)
12698 .is_some_and(|network| network.source_format().is_some())
12699}
12700
12701#[unsafe(no_mangle)]
12703pub unsafe extern "C" fn pio_multiconductor_network_geo(
12704 network: *const PioMulticonductorNetwork,
12705 output: *mut PioMulticonductorGeoView,
12706 error: *mut *mut PioError,
12707) -> bool {
12708 unsafe {
12709 entry(error, false, || {
12710 let network = require_multiconductor_network(network)?;
12711 *require_output(output, "output")? = multiconductor_geo_view(network.geo().as_ref());
12712 Ok(true)
12713 })
12714 }
12715}
12716
12717#[unsafe(no_mangle)]
12720pub unsafe extern "C" fn pio_multiconductor_network_counts(
12721 network: *const PioMulticonductorNetwork,
12722 output: *mut PioMulticonductorNetworkCountsView,
12723 error: *mut *mut PioError,
12724) -> bool {
12725 unsafe {
12726 entry(error, false, || {
12727 let network = require_multiconductor_network(network)?;
12728 *require_output(output, "output")? = PioMulticonductorNetworkCountsView {
12729 buses: network.buses().len(),
12730 line_codes: network.line_codes().len(),
12731 lines: network.lines().len(),
12732 switches: network.switches().len(),
12733 transformers: network.transformers().len(),
12734 loads: network.loads().len(),
12735 generators: network.generators().len(),
12736 inverter_based_resources: network.ibrs().len(),
12737 control_profiles: network.control_profiles().len(),
12738 shunts: network.shunts().len(),
12739 capacitors: network.capacitors().len(),
12740 voltage_sources: network.sources().len(),
12741 untyped_objects: network.untyped_objects().len(),
12742 commands: network.commands().len(),
12743 options: network.options().len(),
12744 };
12745 Ok(true)
12746 })
12747 }
12748}
12749
12750#[unsafe(no_mangle)]
12751pub unsafe extern "C" fn pio_multiconductor_network_base_frequency_hz(
12752 network: *const PioMulticonductorNetwork,
12753) -> f64 {
12754 unsafe { PioMulticonductorNetwork::get(network) }
12755 .and_then(MulticonductorNetworkInner::network)
12756 .map_or(f64::NAN, |network| network.base_frequency())
12757}
12758
12759#[unsafe(no_mangle)]
12760pub unsafe extern "C" fn pio_multiconductor_network_bus_count(
12761 network: *const PioMulticonductorNetwork,
12762) -> usize {
12763 unsafe { PioMulticonductorNetwork::get(network) }
12764 .and_then(MulticonductorNetworkInner::network)
12765 .map_or(0, |network| network.buses().len())
12766}
12767
12768#[unsafe(no_mangle)]
12769pub unsafe extern "C" fn pio_multiconductor_network_line_count(
12770 network: *const PioMulticonductorNetwork,
12771) -> usize {
12772 unsafe { PioMulticonductorNetwork::get(network) }
12773 .and_then(MulticonductorNetworkInner::network)
12774 .map_or(0, |network| network.lines().len())
12775}
12776
12777#[unsafe(no_mangle)]
12778pub unsafe extern "C" fn pio_multiconductor_network_load_count(
12779 network: *const PioMulticonductorNetwork,
12780) -> usize {
12781 unsafe { PioMulticonductorNetwork::get(network) }
12782 .and_then(MulticonductorNetworkInner::network)
12783 .map_or(0, |network| network.loads().len())
12784}
12785
12786#[unsafe(no_mangle)]
12787pub unsafe extern "C" fn pio_multiconductor_network_generator_count(
12788 network: *const PioMulticonductorNetwork,
12789) -> usize {
12790 unsafe { PioMulticonductorNetwork::get(network) }
12791 .and_then(MulticonductorNetworkInner::network)
12792 .map_or(0, |network| network.generators().len())
12793}
12794
12795#[unsafe(no_mangle)]
12798pub unsafe extern "C" fn pio_multiconductor_network_bus_at(
12799 network: *const PioMulticonductorNetwork,
12800 index: usize,
12801 output: *mut PioMulticonductorBusView,
12802 error: *mut *mut PioError,
12803) -> bool {
12804 unsafe {
12805 entry(error, false, || {
12806 let network = require_multiconductor_network(network)?;
12807 let bus = network.buses().get(index).ok_or_else(|| {
12808 boundary_error(
12809 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12810 format!("multiconductor bus index {index} is out of range"),
12811 )
12812 })?;
12813 *require_output(output, "output")? = multiconductor_bus_view(bus);
12814 Ok(true)
12815 })
12816 }
12817}
12818
12819#[unsafe(no_mangle)]
12820pub unsafe extern "C" fn pio_multiconductor_network_bus_terminal_at(
12821 network: *const PioMulticonductorNetwork,
12822 bus_index: usize,
12823 terminal_index: usize,
12824 output: *mut PioStringView,
12825 error: *mut *mut PioError,
12826) -> bool {
12827 unsafe {
12828 entry(error, false, || {
12829 let network = require_multiconductor_network(network)?;
12830 let bus = network.buses().get(bus_index).ok_or_else(|| {
12831 boundary_error(
12832 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12833 format!("multiconductor bus index {bus_index} is out of range"),
12834 )
12835 })?;
12836 *require_output(output, "output")? =
12837 string_slice_at(&bus.terminals, terminal_index, "bus terminal")?;
12838 Ok(true)
12839 })
12840 }
12841}
12842
12843#[unsafe(no_mangle)]
12844pub unsafe extern "C" fn pio_multiconductor_network_bus_grounded_terminal_at(
12845 network: *const PioMulticonductorNetwork,
12846 bus_index: usize,
12847 terminal_index: usize,
12848 output: *mut PioStringView,
12849 error: *mut *mut PioError,
12850) -> bool {
12851 unsafe {
12852 entry(error, false, || {
12853 let network = require_multiconductor_network(network)?;
12854 let bus = network.buses().get(bus_index).ok_or_else(|| {
12855 boundary_error(
12856 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12857 format!("multiconductor bus index {bus_index} is out of range"),
12858 )
12859 })?;
12860 *require_output(output, "output")? =
12861 string_slice_at(&bus.grounded, terminal_index, "grounded bus terminal")?;
12862 Ok(true)
12863 })
12864 }
12865}
12866
12867#[unsafe(no_mangle)]
12868pub unsafe extern "C" fn pio_multiconductor_network_line_code_at(
12869 network: *const PioMulticonductorNetwork,
12870 index: usize,
12871 output: *mut PioMulticonductorLineCodeView,
12872 error: *mut *mut PioError,
12873) -> bool {
12874 unsafe {
12875 entry(error, false, || {
12876 let network = require_multiconductor_network(network)?;
12877 let line_code = network.line_codes().get(index).ok_or_else(|| {
12878 boundary_error(
12879 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12880 format!("multiconductor line code index {index} is out of range"),
12881 )
12882 })?;
12883 *require_output(output, "output")? = multiconductor_line_code_view(line_code);
12884 Ok(true)
12885 })
12886 }
12887}
12888
12889fn multiconductor_line_code_matrix<'a>(
12890 network: &'a powerio_dist::MulticonductorNetwork,
12891 line_code_index: usize,
12892 matrix: &str,
12893) -> Result<&'a powerio_dist::ConductorMatrix, *mut PioError> {
12894 let line_code = network.line_codes().get(line_code_index).ok_or_else(|| {
12895 boundary_error(
12896 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12897 format!("multiconductor line code index {line_code_index} is out of range"),
12898 )
12899 })?;
12900 Ok(match matrix {
12901 "resistance" => &line_code.r_series,
12902 "reactance" => &line_code.x_series,
12903 "conductance_from" => &line_code.g_from,
12904 "susceptance_from" => &line_code.b_from,
12905 "conductance_to" => &line_code.g_to,
12906 "susceptance_to" => &line_code.b_to,
12907 _ => unreachable!("all line code matrices are handled"),
12908 })
12909}
12910
12911unsafe fn write_multiconductor_line_code_matrix_row(
12912 network: *const PioMulticonductorNetwork,
12913 line_code_index: usize,
12914 row_index: usize,
12915 matrix: &str,
12916 output: *mut PioF64View,
12917 error: *mut *mut PioError,
12918) -> bool {
12919 unsafe {
12920 entry(error, false, || {
12921 let network = require_multiconductor_network(network)?;
12922 let values = multiconductor_line_code_matrix(network, line_code_index, matrix)?;
12923 *require_output(output, "output")? =
12924 conductor_matrix_row(values, row_index, &format!("line code {matrix} matrix"))?;
12925 Ok(true)
12926 })
12927 }
12928}
12929
12930#[unsafe(no_mangle)]
12931pub unsafe extern "C" fn pio_multiconductor_network_line_code_resistance_matrix_row_at(
12932 network: *const PioMulticonductorNetwork,
12933 line_code_index: usize,
12934 row_index: usize,
12935 output: *mut PioF64View,
12936 error: *mut *mut PioError,
12937) -> bool {
12938 unsafe {
12939 write_multiconductor_line_code_matrix_row(
12940 network,
12941 line_code_index,
12942 row_index,
12943 "resistance",
12944 output,
12945 error,
12946 )
12947 }
12948}
12949
12950#[unsafe(no_mangle)]
12951pub unsafe extern "C" fn pio_multiconductor_network_line_code_reactance_matrix_row_at(
12952 network: *const PioMulticonductorNetwork,
12953 line_code_index: usize,
12954 row_index: usize,
12955 output: *mut PioF64View,
12956 error: *mut *mut PioError,
12957) -> bool {
12958 unsafe {
12959 write_multiconductor_line_code_matrix_row(
12960 network,
12961 line_code_index,
12962 row_index,
12963 "reactance",
12964 output,
12965 error,
12966 )
12967 }
12968}
12969
12970#[unsafe(no_mangle)]
12971pub unsafe extern "C" fn pio_multiconductor_network_line_code_conductance_from_matrix_row_at(
12972 network: *const PioMulticonductorNetwork,
12973 line_code_index: usize,
12974 row_index: usize,
12975 output: *mut PioF64View,
12976 error: *mut *mut PioError,
12977) -> bool {
12978 unsafe {
12979 write_multiconductor_line_code_matrix_row(
12980 network,
12981 line_code_index,
12982 row_index,
12983 "conductance_from",
12984 output,
12985 error,
12986 )
12987 }
12988}
12989
12990#[unsafe(no_mangle)]
12991pub unsafe extern "C" fn pio_multiconductor_network_line_code_susceptance_from_matrix_row_at(
12992 network: *const PioMulticonductorNetwork,
12993 line_code_index: usize,
12994 row_index: usize,
12995 output: *mut PioF64View,
12996 error: *mut *mut PioError,
12997) -> bool {
12998 unsafe {
12999 write_multiconductor_line_code_matrix_row(
13000 network,
13001 line_code_index,
13002 row_index,
13003 "susceptance_from",
13004 output,
13005 error,
13006 )
13007 }
13008}
13009
13010#[unsafe(no_mangle)]
13011pub unsafe extern "C" fn pio_multiconductor_network_line_code_conductance_to_matrix_row_at(
13012 network: *const PioMulticonductorNetwork,
13013 line_code_index: usize,
13014 row_index: usize,
13015 output: *mut PioF64View,
13016 error: *mut *mut PioError,
13017) -> bool {
13018 unsafe {
13019 write_multiconductor_line_code_matrix_row(
13020 network,
13021 line_code_index,
13022 row_index,
13023 "conductance_to",
13024 output,
13025 error,
13026 )
13027 }
13028}
13029
13030#[unsafe(no_mangle)]
13031pub unsafe extern "C" fn pio_multiconductor_network_line_code_susceptance_to_matrix_row_at(
13032 network: *const PioMulticonductorNetwork,
13033 line_code_index: usize,
13034 row_index: usize,
13035 output: *mut PioF64View,
13036 error: *mut *mut PioError,
13037) -> bool {
13038 unsafe {
13039 write_multiconductor_line_code_matrix_row(
13040 network,
13041 line_code_index,
13042 row_index,
13043 "susceptance_to",
13044 output,
13045 error,
13046 )
13047 }
13048}
13049
13050#[unsafe(no_mangle)]
13051pub unsafe extern "C" fn pio_multiconductor_network_line_at(
13052 network: *const PioMulticonductorNetwork,
13053 index: usize,
13054 output: *mut PioMulticonductorLineView,
13055 error: *mut *mut PioError,
13056) -> bool {
13057 unsafe {
13058 entry(error, false, || {
13059 let network = require_multiconductor_network(network)?;
13060 let line = network.lines().get(index).ok_or_else(|| {
13061 boundary_error(
13062 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13063 format!("multiconductor line index {index} is out of range"),
13064 )
13065 })?;
13066 *require_output(output, "output")? = multiconductor_line_view(line);
13067 Ok(true)
13068 })
13069 }
13070}
13071
13072unsafe fn write_multiconductor_line_terminal(
13073 network: *const PioMulticonductorNetwork,
13074 line_index: usize,
13075 terminal_index: usize,
13076 from: bool,
13077 output: *mut PioStringView,
13078 error: *mut *mut PioError,
13079) -> bool {
13080 unsafe {
13081 entry(error, false, || {
13082 let network = require_multiconductor_network(network)?;
13083 let line = network.lines().get(line_index).ok_or_else(|| {
13084 boundary_error(
13085 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13086 format!("multiconductor line index {line_index} is out of range"),
13087 )
13088 })?;
13089 let (terminals, description) = if from {
13090 (&line.terminal_map_from, "line from terminal")
13091 } else {
13092 (&line.terminal_map_to, "line to terminal")
13093 };
13094 *require_output(output, "output")? =
13095 string_slice_at(terminals, terminal_index, description)?;
13096 Ok(true)
13097 })
13098 }
13099}
13100
13101#[unsafe(no_mangle)]
13102pub unsafe extern "C" fn pio_multiconductor_network_line_terminal_from_at(
13103 network: *const PioMulticonductorNetwork,
13104 line_index: usize,
13105 terminal_index: usize,
13106 output: *mut PioStringView,
13107 error: *mut *mut PioError,
13108) -> bool {
13109 unsafe {
13110 write_multiconductor_line_terminal(network, line_index, terminal_index, true, output, error)
13111 }
13112}
13113
13114#[unsafe(no_mangle)]
13115pub unsafe extern "C" fn pio_multiconductor_network_line_terminal_to_at(
13116 network: *const PioMulticonductorNetwork,
13117 line_index: usize,
13118 terminal_index: usize,
13119 output: *mut PioStringView,
13120 error: *mut *mut PioError,
13121) -> bool {
13122 unsafe {
13123 write_multiconductor_line_terminal(
13124 network,
13125 line_index,
13126 terminal_index,
13127 false,
13128 output,
13129 error,
13130 )
13131 }
13132}
13133
13134#[unsafe(no_mangle)]
13135pub unsafe extern "C" fn pio_multiconductor_network_line_route_point_at(
13136 network: *const PioMulticonductorNetwork,
13137 line_index: usize,
13138 point_index: usize,
13139 output: *mut PioMulticonductorLocationView,
13140 error: *mut *mut PioError,
13141) -> bool {
13142 unsafe {
13143 entry(error, false, || {
13144 let network = require_multiconductor_network(network)?;
13145 let line = network.lines().get(line_index).ok_or_else(|| {
13146 boundary_error(
13147 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13148 format!("multiconductor line index {line_index} is out of range"),
13149 )
13150 })?;
13151 let route = line.route.as_ref().ok_or_else(|| {
13152 boundary_error(
13153 &codes::REQUEST_CAPI_TYPE_MISMATCH,
13154 format!("multiconductor line {line_index} has no route"),
13155 )
13156 })?;
13157 let point = route.get(point_index).ok_or_else(|| {
13158 boundary_error(
13159 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13160 format!("line route point index {point_index} is out of range"),
13161 )
13162 })?;
13163 *require_output(output, "output")? = multiconductor_location_view(point);
13164 Ok(true)
13165 })
13166 }
13167}
13168
13169#[unsafe(no_mangle)]
13170pub unsafe extern "C" fn pio_multiconductor_network_switch_at(
13171 network: *const PioMulticonductorNetwork,
13172 index: usize,
13173 output: *mut PioMulticonductorSwitchView,
13174 error: *mut *mut PioError,
13175) -> bool {
13176 unsafe {
13177 entry(error, false, || {
13178 let network = require_multiconductor_network(network)?;
13179 let switch = network.switches().get(index).ok_or_else(|| {
13180 boundary_error(
13181 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13182 format!("multiconductor switch index {index} is out of range"),
13183 )
13184 })?;
13185 *require_output(output, "output")? = multiconductor_switch_view(switch);
13186 Ok(true)
13187 })
13188 }
13189}
13190
13191unsafe fn write_multiconductor_switch_terminal(
13192 network: *const PioMulticonductorNetwork,
13193 switch_index: usize,
13194 terminal_index: usize,
13195 from: bool,
13196 output: *mut PioStringView,
13197 error: *mut *mut PioError,
13198) -> bool {
13199 unsafe {
13200 entry(error, false, || {
13201 let network = require_multiconductor_network(network)?;
13202 let switch = network.switches().get(switch_index).ok_or_else(|| {
13203 boundary_error(
13204 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13205 format!("multiconductor switch index {switch_index} is out of range"),
13206 )
13207 })?;
13208 let (terminals, description) = if from {
13209 (&switch.terminal_map_from, "switch from terminal")
13210 } else {
13211 (&switch.terminal_map_to, "switch to terminal")
13212 };
13213 *require_output(output, "output")? =
13214 string_slice_at(terminals, terminal_index, description)?;
13215 Ok(true)
13216 })
13217 }
13218}
13219
13220#[unsafe(no_mangle)]
13221pub unsafe extern "C" fn pio_multiconductor_network_switch_terminal_from_at(
13222 network: *const PioMulticonductorNetwork,
13223 switch_index: usize,
13224 terminal_index: usize,
13225 output: *mut PioStringView,
13226 error: *mut *mut PioError,
13227) -> bool {
13228 unsafe {
13229 write_multiconductor_switch_terminal(
13230 network,
13231 switch_index,
13232 terminal_index,
13233 true,
13234 output,
13235 error,
13236 )
13237 }
13238}
13239
13240#[unsafe(no_mangle)]
13241pub unsafe extern "C" fn pio_multiconductor_network_switch_terminal_to_at(
13242 network: *const PioMulticonductorNetwork,
13243 switch_index: usize,
13244 terminal_index: usize,
13245 output: *mut PioStringView,
13246 error: *mut *mut PioError,
13247) -> bool {
13248 unsafe {
13249 write_multiconductor_switch_terminal(
13250 network,
13251 switch_index,
13252 terminal_index,
13253 false,
13254 output,
13255 error,
13256 )
13257 }
13258}
13259
13260#[unsafe(no_mangle)]
13261pub unsafe extern "C" fn pio_multiconductor_network_transformer_at(
13262 network: *const PioMulticonductorNetwork,
13263 index: usize,
13264 output: *mut PioMulticonductorTransformerView,
13265 error: *mut *mut PioError,
13266) -> bool {
13267 unsafe {
13268 entry(error, false, || {
13269 let network = require_multiconductor_network(network)?;
13270 let transformer = network.transformers().get(index).ok_or_else(|| {
13271 boundary_error(
13272 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13273 format!("multiconductor transformer index {index} is out of range"),
13274 )
13275 })?;
13276 *require_output(output, "output")? = PioMulticonductorTransformerView {
13277 name: PioStringView::new(&transformer.name),
13278 winding_count: transformer.windings.len(),
13279 short_circuit_reactance_percent: PioF64View::new(&transformer.xsc_pct),
13280 phase_count: transformer.phases,
13281 };
13282 Ok(true)
13283 })
13284 }
13285}
13286
13287#[unsafe(no_mangle)]
13288pub unsafe extern "C" fn pio_multiconductor_network_transformer_winding_at(
13289 network: *const PioMulticonductorNetwork,
13290 transformer_index: usize,
13291 winding_index: usize,
13292 output: *mut PioMulticonductorTransformerWindingView,
13293 error: *mut *mut PioError,
13294) -> bool {
13295 unsafe {
13296 entry(error, false, || {
13297 let network = require_multiconductor_network(network)?;
13298 let transformer = network
13299 .transformers()
13300 .get(transformer_index)
13301 .ok_or_else(|| {
13302 boundary_error(
13303 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13304 format!(
13305 "multiconductor transformer index {transformer_index} is out of range"
13306 ),
13307 )
13308 })?;
13309 let winding = transformer.windings.get(winding_index).ok_or_else(|| {
13310 boundary_error(
13311 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13312 format!("transformer winding index {winding_index} is out of range"),
13313 )
13314 })?;
13315 *require_output(output, "output")? = PioMulticonductorTransformerWindingView {
13316 bus: PioStringView::new(&winding.bus),
13317 terminal_map_count: winding.terminal_map.len(),
13318 connection: PioStringView::new(multiconductor_winding_connection_name(
13319 winding.conn,
13320 )),
13321 rated_voltage_v: winding.v_ref,
13322 apparent_power_rating_va: winding.s_rating,
13323 resistance_percent: winding.r_pct,
13324 tap: winding.tap,
13325 neutral_resistance_ohm: winding.r_neutral.unwrap_or(0.0),
13326 has_neutral_resistance: winding.r_neutral.is_some(),
13327 neutral_reactance_ohm: winding.x_neutral.unwrap_or(0.0),
13328 has_neutral_reactance: winding.x_neutral.is_some(),
13329 };
13330 Ok(true)
13331 })
13332 }
13333}
13334
13335#[unsafe(no_mangle)]
13336pub unsafe extern "C" fn pio_multiconductor_network_transformer_winding_terminal_at(
13337 network: *const PioMulticonductorNetwork,
13338 transformer_index: usize,
13339 winding_index: usize,
13340 terminal_index: usize,
13341 output: *mut PioStringView,
13342 error: *mut *mut PioError,
13343) -> bool {
13344 unsafe {
13345 entry(error, false, || {
13346 let network = require_multiconductor_network(network)?;
13347 let transformer = network
13348 .transformers()
13349 .get(transformer_index)
13350 .ok_or_else(|| {
13351 boundary_error(
13352 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13353 format!(
13354 "multiconductor transformer index {transformer_index} is out of range"
13355 ),
13356 )
13357 })?;
13358 let winding = transformer.windings.get(winding_index).ok_or_else(|| {
13359 boundary_error(
13360 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13361 format!("transformer winding index {winding_index} is out of range"),
13362 )
13363 })?;
13364 *require_output(output, "output")? = string_slice_at(
13365 &winding.terminal_map,
13366 terminal_index,
13367 "transformer winding terminal",
13368 )?;
13369 Ok(true)
13370 })
13371 }
13372}
13373
13374#[derive(Clone, Copy)]
13375enum MulticonductorTerminalTable {
13376 Load,
13377 Generator,
13378 InverterBasedResource,
13379 Shunt,
13380 Capacitor,
13381 VoltageSource,
13382}
13383
13384unsafe fn write_multiconductor_terminal(
13385 network: *const PioMulticonductorNetwork,
13386 table: MulticonductorTerminalTable,
13387 element_index: usize,
13388 terminal_index: usize,
13389 output: *mut PioStringView,
13390 error: *mut *mut PioError,
13391) -> bool {
13392 unsafe {
13393 entry(error, false, || {
13394 let network = require_multiconductor_network(network)?;
13395 let (terminals, description): (&[String], &str) = match table {
13396 MulticonductorTerminalTable::Load => (
13397 &network
13398 .loads()
13399 .get(element_index)
13400 .ok_or_else(|| {
13401 boundary_error(
13402 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13403 format!(
13404 "multiconductor load index {element_index} is out of range"
13405 ),
13406 )
13407 })?
13408 .terminal_map,
13409 "load terminal",
13410 ),
13411 MulticonductorTerminalTable::Generator => (
13412 &network
13413 .generators()
13414 .get(element_index)
13415 .ok_or_else(|| {
13416 boundary_error(
13417 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13418 format!(
13419 "multiconductor generator index {element_index} is out of range"
13420 ),
13421 )
13422 })?
13423 .terminal_map,
13424 "generator terminal",
13425 ),
13426 MulticonductorTerminalTable::InverterBasedResource => (
13427 &network
13428 .ibrs()
13429 .get(element_index)
13430 .ok_or_else(|| {
13431 boundary_error(
13432 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13433 format!(
13434 "inverter based resource index {element_index} is out of range"
13435 ),
13436 )
13437 })?
13438 .terminal_map,
13439 "inverter based resource terminal",
13440 ),
13441 MulticonductorTerminalTable::Shunt => (
13442 &network
13443 .shunts()
13444 .get(element_index)
13445 .ok_or_else(|| {
13446 boundary_error(
13447 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13448 format!(
13449 "multiconductor shunt index {element_index} is out of range"
13450 ),
13451 )
13452 })?
13453 .terminal_map,
13454 "shunt terminal",
13455 ),
13456 MulticonductorTerminalTable::Capacitor => (
13457 &network
13458 .capacitors()
13459 .get(element_index)
13460 .ok_or_else(|| {
13461 boundary_error(
13462 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13463 format!(
13464 "multiconductor capacitor index {element_index} is out of range"
13465 ),
13466 )
13467 })?
13468 .terminal_map,
13469 "capacitor terminal",
13470 ),
13471 MulticonductorTerminalTable::VoltageSource => (
13472 &network
13473 .sources()
13474 .get(element_index)
13475 .ok_or_else(|| {
13476 boundary_error(
13477 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13478 format!("voltage source index {element_index} is out of range"),
13479 )
13480 })?
13481 .terminal_map,
13482 "voltage source terminal",
13483 ),
13484 };
13485 *require_output(output, "output")? =
13486 string_slice_at(terminals, terminal_index, description)?;
13487 Ok(true)
13488 })
13489 }
13490}
13491
13492#[unsafe(no_mangle)]
13493pub unsafe extern "C" fn pio_multiconductor_network_load_at(
13494 network: *const PioMulticonductorNetwork,
13495 index: usize,
13496 output: *mut PioMulticonductorLoadView,
13497 error: *mut *mut PioError,
13498) -> bool {
13499 unsafe {
13500 entry(error, false, || {
13501 let network = require_multiconductor_network(network)?;
13502 let load = network.loads().get(index).ok_or_else(|| {
13503 boundary_error(
13504 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13505 format!("multiconductor load index {index} is out of range"),
13506 )
13507 })?;
13508 *require_output(output, "output")? = multiconductor_load_view(load);
13509 Ok(true)
13510 })
13511 }
13512}
13513
13514#[unsafe(no_mangle)]
13515pub unsafe extern "C" fn pio_multiconductor_network_load_terminal_at(
13516 network: *const PioMulticonductorNetwork,
13517 load_index: usize,
13518 terminal_index: usize,
13519 output: *mut PioStringView,
13520 error: *mut *mut PioError,
13521) -> bool {
13522 unsafe {
13523 write_multiconductor_terminal(
13524 network,
13525 MulticonductorTerminalTable::Load,
13526 load_index,
13527 terminal_index,
13528 output,
13529 error,
13530 )
13531 }
13532}
13533
13534#[unsafe(no_mangle)]
13535pub unsafe extern "C" fn pio_multiconductor_network_generator_at(
13536 network: *const PioMulticonductorNetwork,
13537 index: usize,
13538 output: *mut PioMulticonductorGeneratorView,
13539 error: *mut *mut PioError,
13540) -> bool {
13541 unsafe {
13542 entry(error, false, || {
13543 let network = require_multiconductor_network(network)?;
13544 let generator = network.generators().get(index).ok_or_else(|| {
13545 boundary_error(
13546 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13547 format!("multiconductor generator index {index} is out of range"),
13548 )
13549 })?;
13550 *require_output(output, "output")? = multiconductor_generator_view(generator);
13551 Ok(true)
13552 })
13553 }
13554}
13555
13556#[unsafe(no_mangle)]
13557pub unsafe extern "C" fn pio_multiconductor_network_generator_terminal_at(
13558 network: *const PioMulticonductorNetwork,
13559 generator_index: usize,
13560 terminal_index: usize,
13561 output: *mut PioStringView,
13562 error: *mut *mut PioError,
13563) -> bool {
13564 unsafe {
13565 write_multiconductor_terminal(
13566 network,
13567 MulticonductorTerminalTable::Generator,
13568 generator_index,
13569 terminal_index,
13570 output,
13571 error,
13572 )
13573 }
13574}
13575
13576#[unsafe(no_mangle)]
13577pub unsafe extern "C" fn pio_multiconductor_network_inverter_based_resource_at(
13578 network: *const PioMulticonductorNetwork,
13579 index: usize,
13580 output: *mut PioInverterBasedResourceView,
13581 error: *mut *mut PioError,
13582) -> bool {
13583 unsafe {
13584 entry(error, false, || {
13585 let network = require_multiconductor_network(network)?;
13586 let resource = network.ibrs().get(index).ok_or_else(|| {
13587 boundary_error(
13588 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13589 format!("inverter based resource index {index} is out of range"),
13590 )
13591 })?;
13592 *require_output(output, "output")? = inverter_based_resource_view(resource);
13593 Ok(true)
13594 })
13595 }
13596}
13597
13598#[unsafe(no_mangle)]
13599pub unsafe extern "C" fn pio_multiconductor_network_inverter_based_resource_terminal_at(
13600 network: *const PioMulticonductorNetwork,
13601 resource_index: usize,
13602 terminal_index: usize,
13603 output: *mut PioStringView,
13604 error: *mut *mut PioError,
13605) -> bool {
13606 unsafe {
13607 write_multiconductor_terminal(
13608 network,
13609 MulticonductorTerminalTable::InverterBasedResource,
13610 resource_index,
13611 terminal_index,
13612 output,
13613 error,
13614 )
13615 }
13616}
13617
13618#[unsafe(no_mangle)]
13619pub unsafe extern "C" fn pio_multiconductor_network_control_profile_at(
13620 network: *const PioMulticonductorNetwork,
13621 index: usize,
13622 output: *mut PioControlProfileView,
13623 error: *mut *mut PioError,
13624) -> bool {
13625 unsafe {
13626 entry(error, false, || {
13627 let network = require_multiconductor_network(network)?;
13628 let profile = network.control_profiles().get(index).ok_or_else(|| {
13629 boundary_error(
13630 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13631 format!("control profile index {index} is out of range"),
13632 )
13633 })?;
13634 *require_output(output, "output")? = control_profile_view(profile);
13635 Ok(true)
13636 })
13637 }
13638}
13639
13640#[unsafe(no_mangle)]
13641pub unsafe extern "C" fn pio_multiconductor_network_shunt_at(
13642 network: *const PioMulticonductorNetwork,
13643 index: usize,
13644 output: *mut PioMulticonductorShuntView,
13645 error: *mut *mut PioError,
13646) -> bool {
13647 unsafe {
13648 entry(error, false, || {
13649 let network = require_multiconductor_network(network)?;
13650 let shunt = network.shunts().get(index).ok_or_else(|| {
13651 boundary_error(
13652 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13653 format!("multiconductor shunt index {index} is out of range"),
13654 )
13655 })?;
13656 *require_output(output, "output")? = PioMulticonductorShuntView {
13657 name: PioStringView::new(&shunt.name),
13658 bus: PioStringView::new(&shunt.bus),
13659 terminal_map_count: shunt.terminal_map.len(),
13660 conductance_matrix_row_count: shunt.g.len(),
13661 susceptance_matrix_row_count: shunt.b.len(),
13662 };
13663 Ok(true)
13664 })
13665 }
13666}
13667
13668#[unsafe(no_mangle)]
13669pub unsafe extern "C" fn pio_multiconductor_network_shunt_terminal_at(
13670 network: *const PioMulticonductorNetwork,
13671 shunt_index: usize,
13672 terminal_index: usize,
13673 output: *mut PioStringView,
13674 error: *mut *mut PioError,
13675) -> bool {
13676 unsafe {
13677 write_multiconductor_terminal(
13678 network,
13679 MulticonductorTerminalTable::Shunt,
13680 shunt_index,
13681 terminal_index,
13682 output,
13683 error,
13684 )
13685 }
13686}
13687
13688unsafe fn write_multiconductor_shunt_matrix_row(
13689 network: *const PioMulticonductorNetwork,
13690 shunt_index: usize,
13691 row_index: usize,
13692 conductance: bool,
13693 output: *mut PioF64View,
13694 error: *mut *mut PioError,
13695) -> bool {
13696 unsafe {
13697 entry(error, false, || {
13698 let network = require_multiconductor_network(network)?;
13699 let shunt = network.shunts().get(shunt_index).ok_or_else(|| {
13700 boundary_error(
13701 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13702 format!("multiconductor shunt index {shunt_index} is out of range"),
13703 )
13704 })?;
13705 let (matrix, description) = if conductance {
13706 (&shunt.g, "shunt conductance matrix")
13707 } else {
13708 (&shunt.b, "shunt susceptance matrix")
13709 };
13710 *require_output(output, "output")? =
13711 conductor_matrix_row(matrix, row_index, description)?;
13712 Ok(true)
13713 })
13714 }
13715}
13716
13717#[unsafe(no_mangle)]
13718pub unsafe extern "C" fn pio_multiconductor_network_shunt_conductance_matrix_row_at(
13719 network: *const PioMulticonductorNetwork,
13720 shunt_index: usize,
13721 row_index: usize,
13722 output: *mut PioF64View,
13723 error: *mut *mut PioError,
13724) -> bool {
13725 unsafe {
13726 write_multiconductor_shunt_matrix_row(network, shunt_index, row_index, true, output, error)
13727 }
13728}
13729
13730#[unsafe(no_mangle)]
13731pub unsafe extern "C" fn pio_multiconductor_network_shunt_susceptance_matrix_row_at(
13732 network: *const PioMulticonductorNetwork,
13733 shunt_index: usize,
13734 row_index: usize,
13735 output: *mut PioF64View,
13736 error: *mut *mut PioError,
13737) -> bool {
13738 unsafe {
13739 write_multiconductor_shunt_matrix_row(network, shunt_index, row_index, false, output, error)
13740 }
13741}
13742
13743#[unsafe(no_mangle)]
13744pub unsafe extern "C" fn pio_multiconductor_network_capacitor_at(
13745 network: *const PioMulticonductorNetwork,
13746 index: usize,
13747 output: *mut PioMulticonductorCapacitorView,
13748 error: *mut *mut PioError,
13749) -> bool {
13750 unsafe {
13751 entry(error, false, || {
13752 let network = require_multiconductor_network(network)?;
13753 let capacitor = network.capacitors().get(index).ok_or_else(|| {
13754 boundary_error(
13755 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13756 format!("multiconductor capacitor index {index} is out of range"),
13757 )
13758 })?;
13759 *require_output(output, "output")? = PioMulticonductorCapacitorView {
13760 name: PioStringView::new(&capacitor.name),
13761 bus: PioStringView::new(&capacitor.bus),
13762 terminal_map_count: capacitor.terminal_map.len(),
13763 configuration: PioStringView::new(multiconductor_configuration_name(
13764 capacitor.configuration,
13765 )),
13766 rated_reactive_power_var: capacitor.q_rated,
13767 nominal_voltage_v: capacitor.v_nom,
13768 };
13769 Ok(true)
13770 })
13771 }
13772}
13773
13774#[unsafe(no_mangle)]
13775pub unsafe extern "C" fn pio_multiconductor_network_capacitor_terminal_at(
13776 network: *const PioMulticonductorNetwork,
13777 capacitor_index: usize,
13778 terminal_index: usize,
13779 output: *mut PioStringView,
13780 error: *mut *mut PioError,
13781) -> bool {
13782 unsafe {
13783 write_multiconductor_terminal(
13784 network,
13785 MulticonductorTerminalTable::Capacitor,
13786 capacitor_index,
13787 terminal_index,
13788 output,
13789 error,
13790 )
13791 }
13792}
13793
13794#[unsafe(no_mangle)]
13795pub unsafe extern "C" fn pio_multiconductor_network_voltage_source_at(
13796 network: *const PioMulticonductorNetwork,
13797 index: usize,
13798 output: *mut PioVoltageSourceView,
13799 error: *mut *mut PioError,
13800) -> bool {
13801 unsafe {
13802 entry(error, false, || {
13803 let network = require_multiconductor_network(network)?;
13804 let source = network.sources().get(index).ok_or_else(|| {
13805 boundary_error(
13806 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13807 format!("voltage source index {index} is out of range"),
13808 )
13809 })?;
13810 *require_output(output, "output")? = PioVoltageSourceView {
13811 name: PioStringView::new(&source.name),
13812 bus: PioStringView::new(&source.bus),
13813 terminal_map_count: source.terminal_map.len(),
13814 voltage_magnitude_v: PioF64View::new(&source.v_magnitude),
13815 voltage_angle_rad: PioF64View::new(&source.v_angle),
13816 energy_cost_rate_per_kwh: PioF64View::new(
13817 source.energy_cost_rate.as_deref().unwrap_or(&[]),
13818 ),
13819 has_energy_cost_rate: source.energy_cost_rate.is_some(),
13820 };
13821 Ok(true)
13822 })
13823 }
13824}
13825
13826#[unsafe(no_mangle)]
13827pub unsafe extern "C" fn pio_multiconductor_network_voltage_source_terminal_at(
13828 network: *const PioMulticonductorNetwork,
13829 source_index: usize,
13830 terminal_index: usize,
13831 output: *mut PioStringView,
13832 error: *mut *mut PioError,
13833) -> bool {
13834 unsafe {
13835 write_multiconductor_terminal(
13836 network,
13837 MulticonductorTerminalTable::VoltageSource,
13838 source_index,
13839 terminal_index,
13840 output,
13841 error,
13842 )
13843 }
13844}
13845
13846#[unsafe(no_mangle)]
13847pub unsafe extern "C" fn pio_multiconductor_network_untyped_object_at(
13848 network: *const PioMulticonductorNetwork,
13849 index: usize,
13850 output: *mut PioMulticonductorUntypedObjectView,
13851 error: *mut *mut PioError,
13852) -> bool {
13853 unsafe {
13854 entry(error, false, || {
13855 let network = require_multiconductor_network(network)?;
13856 let object = network.untyped_objects().get(index).ok_or_else(|| {
13857 boundary_error(
13858 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13859 format!("untyped object index {index} is out of range"),
13860 )
13861 })?;
13862 *require_output(output, "output")? = PioMulticonductorUntypedObjectView {
13863 class_name: PioStringView::new(&object.class),
13864 name: PioStringView::new(&object.name),
13865 property_count: object.props.len(),
13866 };
13867 Ok(true)
13868 })
13869 }
13870}
13871
13872#[unsafe(no_mangle)]
13873pub unsafe extern "C" fn pio_multiconductor_network_untyped_object_property_at(
13874 network: *const PioMulticonductorNetwork,
13875 object_index: usize,
13876 property_index: usize,
13877 output: *mut PioMulticonductorUntypedPropertyView,
13878 error: *mut *mut PioError,
13879) -> bool {
13880 unsafe {
13881 entry(error, false, || {
13882 let network = require_multiconductor_network(network)?;
13883 let object = network.untyped_objects().get(object_index).ok_or_else(|| {
13884 boundary_error(
13885 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13886 format!("untyped object index {object_index} is out of range"),
13887 )
13888 })?;
13889 let (name, value) = object.props.get(property_index).ok_or_else(|| {
13890 boundary_error(
13891 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13892 format!("untyped object property index {property_index} is out of range"),
13893 )
13894 })?;
13895 let (name, has_name) = optional_string_view(name.as_deref());
13896 *require_output(output, "output")? = PioMulticonductorUntypedPropertyView {
13897 name,
13898 has_name,
13899 value: PioStringView::new(value),
13900 };
13901 Ok(true)
13902 })
13903 }
13904}
13905
13906#[unsafe(no_mangle)]
13907pub unsafe extern "C" fn pio_multiconductor_network_command_at(
13908 network: *const PioMulticonductorNetwork,
13909 index: usize,
13910 output: *mut PioMulticonductorCommandView,
13911 error: *mut *mut PioError,
13912) -> bool {
13913 unsafe {
13914 entry(error, false, || {
13915 let network = require_multiconductor_network(network)?;
13916 let (verb, args) = network.commands().get(index).ok_or_else(|| {
13917 boundary_error(
13918 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13919 format!("source command index {index} is out of range"),
13920 )
13921 })?;
13922 *require_output(output, "output")? = PioMulticonductorCommandView {
13923 verb: PioStringView::new(verb),
13924 args: PioStringView::new(args),
13925 };
13926 Ok(true)
13927 })
13928 }
13929}
13930
13931#[unsafe(no_mangle)]
13932pub unsafe extern "C" fn pio_multiconductor_network_option_at(
13933 network: *const PioMulticonductorNetwork,
13934 index: usize,
13935 output: *mut PioStringPropertyView,
13936 error: *mut *mut PioError,
13937) -> bool {
13938 unsafe {
13939 entry(error, false, || {
13940 let network = require_multiconductor_network(network)?;
13941 let (name, value) = network.options().get(index).ok_or_else(|| {
13942 boundary_error(
13943 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13944 format!("source option index {index} is out of range"),
13945 )
13946 })?;
13947 *require_output(output, "output")? = PioStringPropertyView {
13948 name: PioStringView::new(name),
13949 value: PioStringView::new(value),
13950 };
13951 Ok(true)
13952 })
13953 }
13954}
13955
13956#[unsafe(no_mangle)]
13957pub unsafe extern "C" fn pio_multiconductor_network_retain(
13958 network: *const PioMulticonductorNetwork,
13959) -> *mut PioMulticonductorNetwork {
13960 unsafe { PioMulticonductorNetwork::retain_raw(network) }
13961}
13962
13963#[unsafe(no_mangle)]
13964pub unsafe extern "C" fn pio_multiconductor_network_release(
13965 network: *mut PioMulticonductorNetwork,
13966) {
13967 unsafe { PioMulticonductorNetwork::release_raw(network) };
13968}
13969
13970opaque_handle!(
13973 PioComponentId,
13975 ComponentId
13976);
13977opaque_handle!(
13978 PioActivePower,
13980 ActivePower
13981);
13982opaque_handle!(
13983 PioReactivePower,
13985 ReactivePower
13986);
13987opaque_handle!(
13988 PioApparentPower,
13990 ApparentPower
13991);
13992opaque_handle!(
13993 PioOperatingPointUpdate,
13995 OperatingPointUpdate
13996);
13997opaque_handle!(
13998 PioNetworkUpdate,
14000 NetworkUpdate
14001);
14002opaque_handle!(
14003 PioCalculationUpdate,
14005 CalculationUpdate
14006);
14007
14008struct UpdateReportInner {
14009 changes: Vec<UpdateChange>,
14010 connectivity_changed: bool,
14011}
14012
14013struct UpdateChangeInner {
14014 owner: Arc<UpdateReportInner>,
14015 index: usize,
14016}
14017
14018impl UpdateChangeInner {
14019 fn change(&self) -> Option<&UpdateChange> {
14020 self.owner.changes.get(self.index)
14021 }
14022}
14023
14024opaque_handle!(
14025 PioUpdateReport,
14027 UpdateReportInner
14028);
14029opaque_handle!(
14030 PioUpdateChange,
14032 UpdateChangeInner
14033);
14034
14035unsafe fn require_handle<'a, T>(
14036 handle: *const HandleBox<T>,
14037 name: &str,
14038) -> Result<&'a T, *mut PioError> {
14039 unsafe { handle_get(handle) }.ok_or_else(|| {
14040 boundary_error(
14041 &codes::BIND_CAPI_NULL_HANDLE,
14042 format!("{name} must not be NULL"),
14043 )
14044 })
14045}
14046
14047unsafe fn optional_owned_string(
14048 value: *const c_char,
14049 value_len: usize,
14050 name: &str,
14051) -> Result<Option<String>, *mut PioError> {
14052 unsafe { optional_str(value, value_len, name) }.map(|value| value.map(str::to_owned))
14053}
14054
14055#[unsafe(no_mangle)]
14057pub unsafe extern "C" fn pio_component_id_new(
14058 component_type: *const c_char,
14059 component_type_len: usize,
14060 local_id: *const c_char,
14061 local_id_len: usize,
14062 error: *mut *mut PioError,
14063) -> *mut PioComponentId {
14064 unsafe {
14065 entry(error, std::ptr::null_mut(), || {
14066 let component_type =
14067 required_str(component_type, component_type_len, "component_type")?;
14068 let local_id = required_str(local_id, local_id_len, "local_id")?;
14069 ComponentId::new(component_type, local_id)
14070 .map(PioComponentId::new_raw)
14071 .map_err(|failure| error_from_core(&failure))
14072 })
14073 }
14074}
14075
14076#[unsafe(no_mangle)]
14077pub unsafe extern "C" fn pio_component_id_type(component: *const PioComponentId) -> PioStringView {
14078 unsafe { PioComponentId::get(component) }.map_or(PioStringView::EMPTY, |component| {
14079 PioStringView::new(component.component_type())
14080 })
14081}
14082
14083#[unsafe(no_mangle)]
14084pub unsafe extern "C" fn pio_component_id_local_id(
14085 component: *const PioComponentId,
14086) -> PioStringView {
14087 unsafe { PioComponentId::get(component) }.map_or(PioStringView::EMPTY, |component| {
14088 PioStringView::new(component.local_id())
14089 })
14090}
14091
14092#[unsafe(no_mangle)]
14093pub unsafe extern "C" fn pio_component_id_retain(
14094 component: *const PioComponentId,
14095) -> *mut PioComponentId {
14096 unsafe { PioComponentId::retain_raw(component) }
14097}
14098
14099#[unsafe(no_mangle)]
14100pub unsafe extern "C" fn pio_component_id_release(component: *mut PioComponentId) {
14101 unsafe { PioComponentId::release_raw(component) };
14102}
14103
14104#[unsafe(no_mangle)]
14105pub extern "C" fn pio_active_power_from_watts(value: f64) -> *mut PioActivePower {
14106 PioActivePower::new_raw(ActivePower::from_watts(value))
14107}
14108
14109#[unsafe(no_mangle)]
14110pub extern "C" fn pio_active_power_from_megawatts(value: f64) -> *mut PioActivePower {
14111 PioActivePower::new_raw(ActivePower::from_megawatts(value))
14112}
14113
14114#[unsafe(no_mangle)]
14115pub unsafe extern "C" fn pio_active_power_value(power: *const PioActivePower) -> f64 {
14116 unsafe { PioActivePower::get(power) }.map_or(f64::NAN, |power| power.value())
14117}
14118
14119#[unsafe(no_mangle)]
14120pub unsafe extern "C" fn pio_active_power_unit(power: *const PioActivePower) -> PioStringView {
14121 unsafe { PioActivePower::get(power) }.map_or(PioStringView::EMPTY, |power| {
14122 PioStringView::new(match power.unit() {
14123 ActivePowerUnit::Watts => "watts",
14124 ActivePowerUnit::Megawatts => "megawatts",
14125 _ => "unknown",
14126 })
14127 })
14128}
14129
14130#[unsafe(no_mangle)]
14131pub unsafe extern "C" fn pio_active_power_retain(
14132 power: *const PioActivePower,
14133) -> *mut PioActivePower {
14134 unsafe { PioActivePower::retain_raw(power) }
14135}
14136
14137#[unsafe(no_mangle)]
14138pub unsafe extern "C" fn pio_active_power_release(power: *mut PioActivePower) {
14139 unsafe { PioActivePower::release_raw(power) };
14140}
14141
14142#[unsafe(no_mangle)]
14143pub extern "C" fn pio_reactive_power_from_vars(value: f64) -> *mut PioReactivePower {
14144 PioReactivePower::new_raw(ReactivePower::from_vars(value))
14145}
14146
14147#[unsafe(no_mangle)]
14148pub extern "C" fn pio_reactive_power_from_megavars(value: f64) -> *mut PioReactivePower {
14149 PioReactivePower::new_raw(ReactivePower::from_megavars(value))
14150}
14151
14152#[unsafe(no_mangle)]
14153pub unsafe extern "C" fn pio_reactive_power_value(power: *const PioReactivePower) -> f64 {
14154 unsafe { PioReactivePower::get(power) }.map_or(f64::NAN, |power| power.value())
14155}
14156
14157#[unsafe(no_mangle)]
14158pub unsafe extern "C" fn pio_reactive_power_unit(power: *const PioReactivePower) -> PioStringView {
14159 unsafe { PioReactivePower::get(power) }.map_or(PioStringView::EMPTY, |power| {
14160 PioStringView::new(match power.unit() {
14161 ReactivePowerUnit::Vars => "vars",
14162 ReactivePowerUnit::Megavars => "megavars",
14163 _ => "unknown",
14164 })
14165 })
14166}
14167
14168#[unsafe(no_mangle)]
14169pub unsafe extern "C" fn pio_reactive_power_retain(
14170 power: *const PioReactivePower,
14171) -> *mut PioReactivePower {
14172 unsafe { PioReactivePower::retain_raw(power) }
14173}
14174
14175#[unsafe(no_mangle)]
14176pub unsafe extern "C" fn pio_reactive_power_release(power: *mut PioReactivePower) {
14177 unsafe { PioReactivePower::release_raw(power) };
14178}
14179
14180#[unsafe(no_mangle)]
14181pub extern "C" fn pio_apparent_power_from_volt_amperes(value: f64) -> *mut PioApparentPower {
14182 PioApparentPower::new_raw(ApparentPower::from_volt_amperes(value))
14183}
14184
14185#[unsafe(no_mangle)]
14186pub extern "C" fn pio_apparent_power_from_megavolt_amperes(value: f64) -> *mut PioApparentPower {
14187 PioApparentPower::new_raw(ApparentPower::from_megavolt_amperes(value))
14188}
14189
14190#[unsafe(no_mangle)]
14191pub unsafe extern "C" fn pio_apparent_power_value(power: *const PioApparentPower) -> f64 {
14192 unsafe { PioApparentPower::get(power) }.map_or(f64::NAN, |power| power.value())
14193}
14194
14195#[unsafe(no_mangle)]
14196pub unsafe extern "C" fn pio_apparent_power_unit(power: *const PioApparentPower) -> PioStringView {
14197 unsafe { PioApparentPower::get(power) }.map_or(PioStringView::EMPTY, |power| {
14198 PioStringView::new(match power.unit() {
14199 ApparentPowerUnit::VoltAmperes => "volt_amperes",
14200 ApparentPowerUnit::MegavoltAmperes => "megavolt_amperes",
14201 _ => "unknown",
14202 })
14203 })
14204}
14205
14206#[unsafe(no_mangle)]
14207pub unsafe extern "C" fn pio_apparent_power_retain(
14208 power: *const PioApparentPower,
14209) -> *mut PioApparentPower {
14210 unsafe { PioApparentPower::retain_raw(power) }
14211}
14212
14213#[unsafe(no_mangle)]
14214pub unsafe extern "C" fn pio_apparent_power_release(power: *mut PioApparentPower) {
14215 unsafe { PioApparentPower::release_raw(power) };
14216}
14217
14218unsafe fn component_clone(component: *const PioComponentId) -> Result<ComponentId, *mut PioError> {
14219 unsafe { require_handle(component.cast(), "PioComponentId") }.cloned()
14220}
14221
14222unsafe fn active_power_copy(power: *const PioActivePower) -> Result<ActivePower, *mut PioError> {
14223 unsafe { require_handle(power.cast(), "PioActivePower") }.copied()
14224}
14225
14226unsafe fn reactive_power_copy(
14227 power: *const PioReactivePower,
14228) -> Result<ReactivePower, *mut PioError> {
14229 unsafe { require_handle(power.cast(), "PioReactivePower") }.copied()
14230}
14231
14232unsafe fn apparent_power_copy(
14233 power: *const PioApparentPower,
14234) -> Result<ApparentPower, *mut PioError> {
14235 unsafe { require_handle(power.cast(), "PioApparentPower") }.copied()
14236}
14237
14238#[unsafe(no_mangle)]
14239pub unsafe extern "C" fn pio_operating_point_update_set_load_active_power(
14240 load: *const PioComponentId,
14241 terminal: *const c_char,
14242 terminal_len: usize,
14243 power: *const PioActivePower,
14244 error: *mut *mut PioError,
14245) -> *mut PioOperatingPointUpdate {
14246 unsafe {
14247 entry(error, std::ptr::null_mut(), || {
14248 Ok(PioOperatingPointUpdate::new_raw(
14249 OperatingPointUpdate::LoadActivePower {
14250 load: component_clone(load)?,
14251 terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
14252 p: active_power_copy(power)?,
14253 },
14254 ))
14255 })
14256 }
14257}
14258
14259#[unsafe(no_mangle)]
14260pub unsafe extern "C" fn pio_operating_point_update_set_load_reactive_power(
14261 load: *const PioComponentId,
14262 terminal: *const c_char,
14263 terminal_len: usize,
14264 power: *const PioReactivePower,
14265 error: *mut *mut PioError,
14266) -> *mut PioOperatingPointUpdate {
14267 unsafe {
14268 entry(error, std::ptr::null_mut(), || {
14269 Ok(PioOperatingPointUpdate::new_raw(
14270 OperatingPointUpdate::LoadReactivePower {
14271 load: component_clone(load)?,
14272 terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
14273 q: reactive_power_copy(power)?,
14274 },
14275 ))
14276 })
14277 }
14278}
14279
14280#[unsafe(no_mangle)]
14281pub unsafe extern "C" fn pio_operating_point_update_set_generator_active_power(
14282 generator: *const PioComponentId,
14283 terminal: *const c_char,
14284 terminal_len: usize,
14285 power: *const PioActivePower,
14286 error: *mut *mut PioError,
14287) -> *mut PioOperatingPointUpdate {
14288 unsafe {
14289 entry(error, std::ptr::null_mut(), || {
14290 Ok(PioOperatingPointUpdate::new_raw(
14291 OperatingPointUpdate::GeneratorActivePower {
14292 generator: component_clone(generator)?,
14293 terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
14294 p: active_power_copy(power)?,
14295 },
14296 ))
14297 })
14298 }
14299}
14300
14301#[unsafe(no_mangle)]
14302pub unsafe extern "C" fn pio_operating_point_update_set_generator_reactive_power(
14303 generator: *const PioComponentId,
14304 terminal: *const c_char,
14305 terminal_len: usize,
14306 power: *const PioReactivePower,
14307 error: *mut *mut PioError,
14308) -> *mut PioOperatingPointUpdate {
14309 unsafe {
14310 entry(error, std::ptr::null_mut(), || {
14311 Ok(PioOperatingPointUpdate::new_raw(
14312 OperatingPointUpdate::GeneratorReactivePower {
14313 generator: component_clone(generator)?,
14314 terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
14315 q: reactive_power_copy(power)?,
14316 },
14317 ))
14318 })
14319 }
14320}
14321
14322#[unsafe(no_mangle)]
14323pub unsafe extern "C" fn pio_operating_point_update_set_generator_voltage_magnitude(
14324 generator: *const PioComponentId,
14325 voltage_magnitude_per_unit: f64,
14326 error: *mut *mut PioError,
14327) -> *mut PioOperatingPointUpdate {
14328 unsafe {
14329 entry(error, std::ptr::null_mut(), || {
14330 Ok(PioOperatingPointUpdate::new_raw(
14331 OperatingPointUpdate::GeneratorVoltageMagnitude {
14332 generator: component_clone(generator)?,
14333 vm_pu: voltage_magnitude_per_unit,
14334 },
14335 ))
14336 })
14337 }
14338}
14339
14340#[unsafe(no_mangle)]
14341pub unsafe extern "C" fn pio_operating_point_update_set_generator_in_service(
14342 generator: *const PioComponentId,
14343 in_service: bool,
14344 error: *mut *mut PioError,
14345) -> *mut PioOperatingPointUpdate {
14346 unsafe {
14347 entry(error, std::ptr::null_mut(), || {
14348 Ok(PioOperatingPointUpdate::new_raw(
14349 OperatingPointUpdate::GeneratorInService {
14350 generator: component_clone(generator)?,
14351 in_service,
14352 },
14353 ))
14354 })
14355 }
14356}
14357
14358#[unsafe(no_mangle)]
14359pub unsafe extern "C" fn pio_operating_point_update_set_branch_in_service(
14360 branch: *const PioComponentId,
14361 in_service: bool,
14362 error: *mut *mut PioError,
14363) -> *mut PioOperatingPointUpdate {
14364 unsafe {
14365 entry(error, std::ptr::null_mut(), || {
14366 Ok(PioOperatingPointUpdate::new_raw(
14367 OperatingPointUpdate::BranchInService {
14368 branch: component_clone(branch)?,
14369 in_service,
14370 },
14371 ))
14372 })
14373 }
14374}
14375
14376#[unsafe(no_mangle)]
14377pub unsafe extern "C" fn pio_operating_point_update_set_transformer_tap_ratio(
14378 transformer: *const PioComponentId,
14379 tap_ratio: f64,
14380 error: *mut *mut PioError,
14381) -> *mut PioOperatingPointUpdate {
14382 unsafe {
14383 entry(error, std::ptr::null_mut(), || {
14384 Ok(PioOperatingPointUpdate::new_raw(
14385 OperatingPointUpdate::TransformerTapRatio {
14386 transformer: component_clone(transformer)?,
14387 tap_ratio,
14388 },
14389 ))
14390 })
14391 }
14392}
14393
14394#[unsafe(no_mangle)]
14395pub unsafe extern "C" fn pio_operating_point_update_set_transformer_phase_shift_degrees(
14396 transformer: *const PioComponentId,
14397 phase_shift_degrees: f64,
14398 error: *mut *mut PioError,
14399) -> *mut PioOperatingPointUpdate {
14400 unsafe {
14401 entry(error, std::ptr::null_mut(), || {
14402 Ok(PioOperatingPointUpdate::new_raw(
14403 OperatingPointUpdate::TransformerPhaseShift {
14404 transformer: component_clone(transformer)?,
14405 shift_degrees: phase_shift_degrees,
14406 },
14407 ))
14408 })
14409 }
14410}
14411
14412#[unsafe(no_mangle)]
14413pub unsafe extern "C" fn pio_operating_point_update_set_switch_closed(
14414 switch_id: *const PioComponentId,
14415 closed: bool,
14416 error: *mut *mut PioError,
14417) -> *mut PioOperatingPointUpdate {
14418 unsafe {
14419 entry(error, std::ptr::null_mut(), || {
14420 Ok(PioOperatingPointUpdate::new_raw(
14421 OperatingPointUpdate::SwitchClosed {
14422 switch: component_clone(switch_id)?,
14423 closed,
14424 },
14425 ))
14426 })
14427 }
14428}
14429
14430#[unsafe(no_mangle)]
14431pub unsafe extern "C" fn pio_network_update_set_branch_thermal_rating(
14432 branch: *const PioComponentId,
14433 terminal: *const c_char,
14434 terminal_len: usize,
14435 rating: *const PioApparentPower,
14436 error: *mut *mut PioError,
14437) -> *mut PioNetworkUpdate {
14438 unsafe {
14439 entry(error, std::ptr::null_mut(), || {
14440 Ok(PioNetworkUpdate::new_raw(
14441 NetworkUpdate::BranchThermalRating {
14442 branch: component_clone(branch)?,
14443 terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
14444 rating: apparent_power_copy(rating)?,
14445 },
14446 ))
14447 })
14448 }
14449}
14450
14451#[unsafe(no_mangle)]
14452pub unsafe extern "C" fn pio_calculation_update_from_operating_point(
14453 update: *const PioOperatingPointUpdate,
14454 error: *mut *mut PioError,
14455) -> *mut PioCalculationUpdate {
14456 unsafe {
14457 entry(error, std::ptr::null_mut(), || {
14458 let update = PioOperatingPointUpdate::get(update)
14459 .ok_or_else(|| {
14460 boundary_error(
14461 &codes::BIND_CAPI_NULL_HANDLE,
14462 "PioOperatingPointUpdate must not be NULL",
14463 )
14464 })?
14465 .clone();
14466 Ok(PioCalculationUpdate::new_raw(
14467 CalculationUpdate::OperatingPoint(update),
14468 ))
14469 })
14470 }
14471}
14472
14473#[unsafe(no_mangle)]
14474pub unsafe extern "C" fn pio_calculation_update_from_network(
14475 update: *const PioNetworkUpdate,
14476 error: *mut *mut PioError,
14477) -> *mut PioCalculationUpdate {
14478 unsafe {
14479 entry(error, std::ptr::null_mut(), || {
14480 let update = PioNetworkUpdate::get(update)
14481 .ok_or_else(|| {
14482 boundary_error(
14483 &codes::BIND_CAPI_NULL_HANDLE,
14484 "PioNetworkUpdate must not be NULL",
14485 )
14486 })?
14487 .clone();
14488 Ok(PioCalculationUpdate::new_raw(CalculationUpdate::Network(
14489 update,
14490 )))
14491 })
14492 }
14493}
14494
14495#[unsafe(no_mangle)]
14496pub unsafe extern "C" fn pio_operating_point_update_retain(
14497 update: *const PioOperatingPointUpdate,
14498) -> *mut PioOperatingPointUpdate {
14499 unsafe { PioOperatingPointUpdate::retain_raw(update) }
14500}
14501
14502#[unsafe(no_mangle)]
14503pub unsafe extern "C" fn pio_operating_point_update_release(update: *mut PioOperatingPointUpdate) {
14504 unsafe { PioOperatingPointUpdate::release_raw(update) };
14505}
14506
14507#[unsafe(no_mangle)]
14508pub unsafe extern "C" fn pio_network_update_retain(
14509 update: *const PioNetworkUpdate,
14510) -> *mut PioNetworkUpdate {
14511 unsafe { PioNetworkUpdate::retain_raw(update) }
14512}
14513
14514#[unsafe(no_mangle)]
14515pub unsafe extern "C" fn pio_network_update_release(update: *mut PioNetworkUpdate) {
14516 unsafe { PioNetworkUpdate::release_raw(update) };
14517}
14518
14519#[unsafe(no_mangle)]
14520pub unsafe extern "C" fn pio_calculation_update_retain(
14521 update: *const PioCalculationUpdate,
14522) -> *mut PioCalculationUpdate {
14523 unsafe { PioCalculationUpdate::retain_raw(update) }
14524}
14525
14526#[unsafe(no_mangle)]
14527pub unsafe extern "C" fn pio_calculation_update_release(update: *mut PioCalculationUpdate) {
14528 unsafe { PioCalculationUpdate::release_raw(update) };
14529}
14530
14531fn append_update_report(output: &mut UpdateReportInner, report: powerio_prob::UpdateReport) {
14532 output.connectivity_changed |= report.connectivity_changed();
14533 output.changes.extend_from_slice(report.changes());
14534}
14535
14536fn apply_bus_load_to_typed_module<T>(
14537 module: &mut powerio::PioModule<PioValue>,
14538 bus: powerio_tx::BusId,
14539 total: ActivePower,
14540 allocation: LoadAllocation,
14541 narrow: impl FnOnce(PioValue) -> Result<T, PioValue>,
14542 wrap: impl FnOnce(T) -> PioValue,
14543) -> Result<UpdateReportInner, powerio_core::Error>
14544where
14545 T: powerio_prob::BalancedCalculationInstance,
14546{
14547 let mut typed = module.clone().__try_map_value(narrow).map_err(|value| {
14548 powerio_core::Error::new(
14549 &codes::REQUEST_CAPI_TYPE_MISMATCH,
14550 format!(
14551 "{} does not accept aggregate bus active demand",
14552 value.value().type_name()
14553 ),
14554 )
14555 })?;
14556 let report = apply_bus_load_active_power(&mut typed, bus, total, allocation)?;
14557 *module = typed.map_value(wrap);
14558 let mut output = UpdateReportInner {
14559 changes: Vec::new(),
14560 connectivity_changed: false,
14561 };
14562 append_update_report(&mut output, report);
14563 Ok(output)
14564}
14565
14566#[allow(clippy::result_large_err)]
14569fn apply_dynamic_bus_load_active_power(
14570 module: &mut powerio::PioModule<PioValue>,
14571 bus: powerio_tx::BusId,
14572 total: ActivePower,
14573 allocation: LoadAllocation,
14574) -> Result<UpdateReportInner, powerio_core::Error> {
14575 match &module.value() {
14576 PioValue::DcPfInstance(_) => apply_bus_load_to_typed_module(
14577 module,
14578 bus,
14579 total,
14580 allocation,
14581 |value| match value {
14582 PioValue::DcPfInstance(instance) => Ok(instance),
14583 other => Err(other),
14584 },
14585 PioValue::DcPfInstance,
14586 ),
14587 PioValue::AcPfInstance(_) => apply_bus_load_to_typed_module(
14588 module,
14589 bus,
14590 total,
14591 allocation,
14592 |value| match value {
14593 PioValue::AcPfInstance(instance) => Ok(instance),
14594 other => Err(other),
14595 },
14596 PioValue::AcPfInstance,
14597 ),
14598 PioValue::DcOpfInstance(_) => apply_bus_load_to_typed_module(
14599 module,
14600 bus,
14601 total,
14602 allocation,
14603 |value| match value {
14604 PioValue::DcOpfInstance(instance) => Ok(instance),
14605 other => Err(other),
14606 },
14607 PioValue::DcOpfInstance,
14608 ),
14609 PioValue::AcOpfInstance(_) => apply_bus_load_to_typed_module(
14610 module,
14611 bus,
14612 total,
14613 allocation,
14614 |value| match value {
14615 PioValue::AcOpfInstance(instance) => Ok(instance),
14616 other => Err(other),
14617 },
14618 PioValue::AcOpfInstance,
14619 ),
14620 value => Err(powerio_core::Error::new(
14621 &codes::REQUEST_CAPI_TYPE_MISMATCH,
14622 format!(
14623 "{} does not accept aggregate bus active demand",
14624 value.type_name()
14625 ),
14626 )),
14627 }
14628}
14629
14630fn parse_load_allocation(value: &str) -> Result<LoadAllocation, *mut PioError> {
14631 match value {
14632 "equal" => Ok(LoadAllocation::Equal),
14633 "proportional_to_current_active_power" => {
14634 Ok(LoadAllocation::ProportionalToCurrentActivePower)
14635 }
14636 other => Err(boundary_error(
14637 &codes::REQUEST_CAPI_ALLOCATION_UNKNOWN,
14638 format!("unknown load allocation rule '{other}'"),
14639 )),
14640 }
14641}
14642
14643fn separated_updates(
14644 updates: &[CalculationUpdate],
14645) -> (Vec<OperatingPointUpdate>, Vec<NetworkUpdate>) {
14646 let mut operating = Vec::new();
14647 let mut network = Vec::new();
14648 for update in updates {
14649 match update {
14650 CalculationUpdate::OperatingPoint(update) => operating.push(update.clone()),
14651 CalculationUpdate::Network(update) => network.push(update.clone()),
14652 _ => unreachable!("unsupported calculation update from this PowerIO build"),
14653 }
14654 }
14655 (operating, network)
14656}
14657
14658fn reject_network_updates(
14659 updates: &[CalculationUpdate],
14660) -> Result<Vec<OperatingPointUpdate>, powerio_core::Error> {
14661 updates
14662 .iter()
14663 .map(|update| match update {
14664 CalculationUpdate::OperatingPoint(update) => Ok(update.clone()),
14665 CalculationUpdate::Network(_) => Err(powerio_core::Error::new(
14666 &codes::REQUEST_CAPI_TYPE_MISMATCH,
14667 "a network update cannot be applied to an operating point",
14668 )),
14669 _ => unreachable!("unsupported calculation update from this PowerIO build"),
14670 })
14671 .collect()
14672}
14673
14674fn apply_dynamic_updates(
14675 value: &mut PioValue,
14676 updates: &[CalculationUpdate],
14677) -> Result<UpdateReportInner, powerio_core::Error> {
14678 let mut output = UpdateReportInner {
14679 changes: Vec::new(),
14680 connectivity_changed: false,
14681 };
14682 match value {
14683 PioValue::BalancedNetwork(network) => {
14684 let (operating, physical) = separated_updates(updates);
14685 append_update_report(&mut output, apply_updates(network, &operating)?);
14686 append_update_report(&mut output, apply_updates(network, &physical)?);
14687 }
14688 PioValue::MulticonductorNetwork(network) => {
14689 let (operating, physical) = separated_updates(updates);
14690 append_update_report(&mut output, apply_updates(network, &operating)?);
14691 append_update_report(&mut output, apply_updates(network, &physical)?);
14692 }
14693 PioValue::BalancedOperatingPoint(point) => {
14694 let operating = reject_network_updates(updates)?;
14695 append_update_report(&mut output, apply_updates(point, &operating)?);
14696 }
14697 PioValue::MulticonductorOperatingPoint(point) => {
14698 let operating = reject_network_updates(updates)?;
14699 append_update_report(&mut output, apply_updates(point, &operating)?);
14700 }
14701 PioValue::DcPfInstance(instance) => {
14702 append_update_report(&mut output, apply_updates(instance, updates)?);
14703 }
14704 PioValue::AcPfInstance(instance) => {
14705 append_update_report(&mut output, apply_updates(instance, updates)?);
14706 }
14707 PioValue::DcOpfInstance(instance) => {
14708 append_update_report(&mut output, apply_updates(instance, updates)?);
14709 }
14710 PioValue::AcOpfInstance(instance) => {
14711 append_update_report(&mut output, apply_updates(instance, updates)?);
14712 }
14713 PioValue::McAcPfInstance(instance) => {
14714 append_update_report(&mut output, apply_updates(instance, updates)?);
14715 }
14716 PioValue::McAcOpfInstance(instance) => {
14717 append_update_report(&mut output, apply_updates(instance, updates)?);
14718 }
14719 _ => {
14720 return Err(powerio_core::Error::new(
14721 &codes::REQUEST_CAPI_TYPE_MISMATCH,
14722 format!("{} does not accept calculation updates", value.type_name()),
14723 ));
14724 }
14725 }
14726 Ok(output)
14727}
14728
14729fn update_history_id(
14730 module: &powerio::PioModule<PioValue>,
14731) -> Result<HistoryId, powerio_core::Error> {
14732 let mut suffix = 1usize;
14733 loop {
14734 let value = if suffix == 1 {
14735 "apply-updates".to_owned()
14736 } else {
14737 format!("apply-updates-{suffix}")
14738 };
14739 if module
14740 .history()
14741 .iter()
14742 .all(|entry| entry.id().as_str() != value)
14743 {
14744 return HistoryId::new(value);
14745 }
14746 suffix += 1;
14747 }
14748}
14749
14750fn apply_module_updates(
14751 module: &mut powerio::PioModule<PioValue>,
14752 updates: &[CalculationUpdate],
14753) -> Result<UpdateReportInner, powerio_core::Error> {
14754 let history_id = update_history_id(module)?;
14755 let mut edit = module.stage_edit();
14756 let report = apply_dynamic_updates(edit.value_mut(), updates)?;
14757 if report.changes.is_empty() {
14758 return Ok(report);
14759 }
14760
14761 let mut parameters = std::collections::BTreeMap::new();
14762 parameters.insert(
14763 "updates".to_owned(),
14764 serde_json::to_value(updates).map_err(|failure| {
14765 powerio_core::Error::new(
14766 &codes::EMIT_CAPI_SERIALIZE_FAILED,
14767 format!("cannot record applied updates: {failure}"),
14768 )
14769 })?,
14770 );
14771 parameters.insert(
14772 "changes".to_owned(),
14773 serde_json::to_value(&report.changes).map_err(|failure| {
14774 powerio_core::Error::new(
14775 &codes::EMIT_CAPI_SERIALIZE_FAILED,
14776 format!("cannot record update changes: {failure}"),
14777 )
14778 })?,
14779 );
14780 parameters.insert(
14781 "connectivity_changed".to_owned(),
14782 serde_json::Value::Bool(report.connectivity_changed),
14783 );
14784 let history = HistoryEntry::new(history_id, HistoryKind::Edit, "apply_updates")?
14785 .with_parameters(parameters)?;
14786 edit.commit(Producer::new("powerio-capi", powerio::VERSION)?, history)?;
14787 Ok(report)
14788}
14789
14790unsafe fn module_make_mut<'a>(
14791 module: *mut PioModule,
14792) -> Result<&'a mut ModuleInner, *mut PioError> {
14793 let handle = unsafe { module.cast::<HandleBox<ModuleInner>>().as_mut() }.ok_or_else(|| {
14794 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
14795 })?;
14796 Ok(Arc::make_mut(&mut handle.inner))
14797}
14798
14799#[unsafe(no_mangle)]
14808pub unsafe extern "C" fn pio_apply_updates(
14809 module: *mut PioModule,
14810 updates: *const *const PioCalculationUpdate,
14811 updates_len: usize,
14812 error: *mut *mut PioError,
14813) -> *mut PioUpdateReport {
14814 unsafe {
14815 entry(error, std::ptr::null_mut(), || {
14816 if updates.is_null() && updates_len != 0 {
14817 return Err(boundary_error(
14818 &codes::BIND_CAPI_NULL_ARGUMENT,
14819 "updates is NULL with a nonzero length",
14820 ));
14821 }
14822 let handles = if updates_len == 0 {
14823 &[]
14824 } else {
14825 std::slice::from_raw_parts(updates, updates_len)
14826 };
14827 let mut values = Vec::with_capacity(handles.len());
14828 for (index, handle) in handles.iter().copied().enumerate() {
14829 let update = PioCalculationUpdate::get(handle).ok_or_else(|| {
14830 boundary_error(
14831 &codes::BIND_CAPI_NULL_HANDLE,
14832 format!("updates[{index}] must not be NULL"),
14833 )
14834 })?;
14835 values.push(update.clone());
14836 }
14837 let module = module_make_mut(module)?;
14838 apply_module_updates(&mut module.module, &values)
14839 .map(PioUpdateReport::new_raw)
14840 .map_err(|failure| error_from_core(&failure))
14841 })
14842 }
14843}
14844
14845#[unsafe(no_mangle)]
14850pub unsafe extern "C" fn pio_apply_bus_load_active_power(
14851 module: *mut PioModule,
14852 bus_id: usize,
14853 power: *const PioActivePower,
14854 allocation: *const c_char,
14855 allocation_len: usize,
14856 error: *mut *mut PioError,
14857) -> *mut PioUpdateReport {
14858 unsafe {
14859 entry(error, std::ptr::null_mut(), || {
14860 let total = active_power_copy(power)?;
14861 let allocation = required_str(allocation, allocation_len, "allocation")?;
14862 let allocation = parse_load_allocation(allocation)?;
14863 let module = module_make_mut(module)?;
14864 apply_dynamic_bus_load_active_power(
14865 &mut module.module,
14866 powerio_tx::BusId::new(bus_id),
14867 total,
14868 allocation,
14869 )
14870 .map(PioUpdateReport::new_raw)
14871 .map_err(|failure| error_from_core(&failure))
14872 })
14873 }
14874}
14875
14876#[unsafe(no_mangle)]
14877pub unsafe extern "C" fn pio_update_report_len(report: *const PioUpdateReport) -> usize {
14878 unsafe { PioUpdateReport::get(report) }.map_or(0, |report| report.changes.len())
14879}
14880
14881#[unsafe(no_mangle)]
14882pub unsafe extern "C" fn pio_update_report_connectivity_changed(
14883 report: *const PioUpdateReport,
14884) -> bool {
14885 unsafe { PioUpdateReport::get(report) }.is_some_and(|report| report.connectivity_changed)
14886}
14887
14888#[unsafe(no_mangle)]
14889pub unsafe extern "C" fn pio_update_report_change(
14890 report: *const PioUpdateReport,
14891 index: usize,
14892 error: *mut *mut PioError,
14893) -> *mut PioUpdateChange {
14894 unsafe {
14895 entry(error, std::ptr::null_mut(), || {
14896 let owner = PioUpdateReport::arc(report).ok_or_else(|| {
14897 boundary_error(
14898 &codes::BIND_CAPI_NULL_HANDLE,
14899 "PioUpdateReport must not be NULL",
14900 )
14901 })?;
14902 if index >= owner.changes.len() {
14903 return Err(boundary_error(
14904 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14905 format!("update change index {index} is out of range"),
14906 ));
14907 }
14908 Ok(PioUpdateChange::new_raw(UpdateChangeInner { owner, index }))
14909 })
14910 }
14911}
14912
14913#[unsafe(no_mangle)]
14914pub unsafe extern "C" fn pio_update_change_component_id(
14915 change: *const PioUpdateChange,
14916) -> *mut PioComponentId {
14917 unsafe { PioUpdateChange::get(change) }
14918 .and_then(UpdateChangeInner::change)
14919 .map_or(std::ptr::null_mut(), |change| {
14920 PioComponentId::new_raw(change.component_id().clone())
14921 })
14922}
14923
14924fn updated_field_name(field: UpdatedField) -> &'static str {
14925 match field {
14926 UpdatedField::LoadActivePower => "load_active_power",
14927 UpdatedField::LoadReactivePower => "load_reactive_power",
14928 UpdatedField::GeneratorActivePower => "generator_active_power",
14929 UpdatedField::GeneratorReactivePower => "generator_reactive_power",
14930 UpdatedField::GeneratorVoltageMagnitude => "generator_voltage_magnitude",
14931 UpdatedField::GeneratorInService => "generator_in_service",
14932 UpdatedField::BranchThermalRating => "branch_thermal_rating",
14933 UpdatedField::BranchInService => "branch_in_service",
14934 UpdatedField::TransformerTapRatio => "transformer_tap_ratio",
14935 UpdatedField::TransformerPhaseShift => "transformer_phase_shift",
14936 UpdatedField::SwitchClosed => "switch_closed",
14937 _ => "unknown",
14938 }
14939}
14940
14941#[unsafe(no_mangle)]
14942pub unsafe extern "C" fn pio_update_change_field(change: *const PioUpdateChange) -> PioStringView {
14943 unsafe { PioUpdateChange::get(change) }
14944 .and_then(UpdateChangeInner::change)
14945 .map_or(PioStringView::EMPTY, |change| {
14946 PioStringView::new(updated_field_name(change.field()))
14947 })
14948}
14949
14950#[unsafe(no_mangle)]
14951pub unsafe extern "C" fn pio_update_change_terminal(
14952 change: *const PioUpdateChange,
14953) -> PioStringView {
14954 unsafe { PioUpdateChange::get(change) }
14955 .and_then(UpdateChangeInner::change)
14956 .and_then(UpdateChange::terminal)
14957 .map_or(PioStringView::EMPTY, PioStringView::new)
14958}
14959
14960#[unsafe(no_mangle)]
14961pub unsafe extern "C" fn pio_update_report_retain(
14962 report: *const PioUpdateReport,
14963) -> *mut PioUpdateReport {
14964 unsafe { PioUpdateReport::retain_raw(report) }
14965}
14966
14967#[unsafe(no_mangle)]
14968pub unsafe extern "C" fn pio_update_report_release(report: *mut PioUpdateReport) {
14969 unsafe { PioUpdateReport::release_raw(report) };
14970}
14971
14972#[unsafe(no_mangle)]
14973pub unsafe extern "C" fn pio_update_change_retain(
14974 change: *const PioUpdateChange,
14975) -> *mut PioUpdateChange {
14976 unsafe { PioUpdateChange::retain_raw(change) }
14977}
14978
14979#[unsafe(no_mangle)]
14980pub unsafe extern "C" fn pio_update_change_release(change: *mut PioUpdateChange) {
14981 unsafe { PioUpdateChange::release_raw(change) };
14982}
14983
14984struct ArtifactRecord {
14987 name: String,
14988 bytes: Option<Vec<u8>>,
14989}
14990
14991struct EmitResultInner {
14992 layout: &'static str,
14993 fidelity: &'static str,
14994 artifacts: Vec<ArtifactRecord>,
14995 diagnostics: Arc<DiagnosticsInner>,
14996}
14997
14998struct ArtifactInner {
14999 owner: Arc<EmitResultInner>,
15000 index: usize,
15001}
15002
15003impl ArtifactInner {
15004 fn artifact(&self) -> Option<&ArtifactRecord> {
15005 self.owner.artifacts.get(self.index)
15006 }
15007}
15008
15009opaque_handle!(
15010 PioEmitResult,
15012 EmitResultInner
15013);
15014opaque_handle!(
15015 PioArtifact,
15017 ArtifactInner
15018);
15019
15020fn emit_result_handle(result: EmitResult) -> *mut PioEmitResult {
15021 let layout = match result.layout() {
15022 powerio::OutputLayout::File => "file",
15023 powerio::OutputLayout::Directory => "directory",
15024 };
15025 let fidelity = match result.fidelity() {
15026 powerio::Fidelity::ExactSameFormat => "exact_same_format",
15027 powerio::Fidelity::Canonical => "canonical",
15028 };
15029 let diagnostics = Arc::new(DiagnosticsInner {
15030 owner: DiagnosticsOwner::Owned(result.diagnostics().to_vec()),
15031 });
15032 let artifacts = match result.into_output() {
15033 EmittedOutput::Memory { artifacts } => artifacts
15034 .into_iter()
15035 .map(|artifact| {
15036 let name = artifact.name().as_str().to_owned();
15037 ArtifactRecord {
15038 name,
15039 bytes: Some(artifact.into_bytes()),
15040 }
15041 })
15042 .collect(),
15043 EmittedOutput::Path { artifacts, .. } => artifacts
15044 .into_iter()
15045 .map(|path| ArtifactRecord {
15046 name: path.to_string_lossy().into_owned(),
15047 bytes: None,
15048 })
15049 .collect(),
15050 _ => unreachable!("unsupported emitted output from this PowerIO build"),
15051 };
15052 PioEmitResult::new_raw(EmitResultInner {
15053 layout,
15054 fidelity,
15055 artifacts,
15056 diagnostics,
15057 })
15058}
15059
15060unsafe fn run_output_operation(
15061 module: *const PioModule,
15062 destination: *const PioDestination,
15063 error: *mut *mut PioError,
15064 operation: impl FnOnce(
15065 &powerio::PioModule<PioValue>,
15066 Destination,
15067 ) -> Result<EmitResult, *mut PioError>,
15068) -> *mut PioEmitResult {
15069 unsafe {
15070 entry(error, std::ptr::null_mut(), || {
15071 let module = PioModule::get(module).ok_or_else(|| {
15072 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
15073 })?;
15074 let destination = PioDestination::get(destination).ok_or_else(|| {
15075 boundary_error(
15076 &codes::BIND_CAPI_NULL_HANDLE,
15077 "PioDestination must not be NULL",
15078 )
15079 })?;
15080 let destination = destination
15081 .build()
15082 .map_err(|failure| error_from_core(&failure))?;
15083 operation(&module.module, destination).map(emit_result_handle)
15084 })
15085 }
15086}
15087
15088#[unsafe(no_mangle)]
15090pub unsafe extern "C" fn pio_emit(
15091 module: *const PioModule,
15092 format: *const c_char,
15093 format_len: usize,
15094 destination: *const PioDestination,
15095 error: *mut *mut PioError,
15096) -> *mut PioEmitResult {
15097 unsafe {
15098 run_output_operation(module, destination, error, |module, destination| {
15099 let format = required_str(format, format_len, "format")?;
15100 powerio::emit(module, format, destination).map_err(|failure| error_from_core(&failure))
15101 })
15102 }
15103}
15104
15105#[unsafe(no_mangle)]
15107pub unsafe extern "C" fn pio_module_serialize(
15108 module: *const PioModule,
15109 destination: *const PioDestination,
15110 error: *mut *mut PioError,
15111) -> *mut PioEmitResult {
15112 unsafe {
15113 run_output_operation(module, destination, error, |module, destination| {
15114 powerio::serialize(module, destination).map_err(|failure| error_from_core(&failure))
15115 })
15116 }
15117}
15118
15119#[unsafe(no_mangle)]
15120pub unsafe extern "C" fn pio_emit_result_layout(result: *const PioEmitResult) -> PioStringView {
15121 unsafe { PioEmitResult::get(result) }.map_or(PioStringView::EMPTY, |result| {
15122 PioStringView::new(result.layout)
15123 })
15124}
15125
15126#[unsafe(no_mangle)]
15127pub unsafe extern "C" fn pio_emit_result_fidelity(result: *const PioEmitResult) -> PioStringView {
15128 unsafe { PioEmitResult::get(result) }.map_or(PioStringView::EMPTY, |result| {
15129 PioStringView::new(result.fidelity)
15130 })
15131}
15132
15133#[unsafe(no_mangle)]
15134pub unsafe extern "C" fn pio_emit_result_artifact_count(result: *const PioEmitResult) -> usize {
15135 unsafe { PioEmitResult::get(result) }.map_or(0, |result| result.artifacts.len())
15136}
15137
15138#[unsafe(no_mangle)]
15139pub unsafe extern "C" fn pio_emit_result_artifact(
15140 result: *const PioEmitResult,
15141 index: usize,
15142 error: *mut *mut PioError,
15143) -> *mut PioArtifact {
15144 unsafe {
15145 entry(error, std::ptr::null_mut(), || {
15146 let owner = PioEmitResult::arc(result).ok_or_else(|| {
15147 boundary_error(
15148 &codes::BIND_CAPI_NULL_HANDLE,
15149 "PioEmitResult must not be NULL",
15150 )
15151 })?;
15152 if index >= owner.artifacts.len() {
15153 return Err(boundary_error(
15154 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15155 format!("artifact index {index} is out of range"),
15156 ));
15157 }
15158 Ok(PioArtifact::new_raw(ArtifactInner { owner, index }))
15159 })
15160 }
15161}
15162
15163#[unsafe(no_mangle)]
15164pub unsafe extern "C" fn pio_emit_result_diagnostics(
15165 result: *const PioEmitResult,
15166) -> *mut PioDiagnostics {
15167 unsafe { PioEmitResult::get(result) }.map_or(std::ptr::null_mut(), |result| {
15168 PioDiagnostics::from_arc(Arc::clone(&result.diagnostics))
15169 })
15170}
15171
15172#[unsafe(no_mangle)]
15173pub unsafe extern "C" fn pio_emit_result_retain(
15174 result: *const PioEmitResult,
15175) -> *mut PioEmitResult {
15176 unsafe { PioEmitResult::retain_raw(result) }
15177}
15178
15179#[unsafe(no_mangle)]
15180pub unsafe extern "C" fn pio_emit_result_release(result: *mut PioEmitResult) {
15181 unsafe { PioEmitResult::release_raw(result) };
15182}
15183
15184#[unsafe(no_mangle)]
15185pub unsafe extern "C" fn pio_artifact_name(artifact: *const PioArtifact) -> PioStringView {
15186 unsafe { PioArtifact::get(artifact) }
15187 .and_then(ArtifactInner::artifact)
15188 .map_or(PioStringView::EMPTY, |artifact| {
15189 PioStringView::new(&artifact.name)
15190 })
15191}
15192
15193#[unsafe(no_mangle)]
15196pub unsafe extern "C" fn pio_artifact_bytes(artifact: *const PioArtifact) -> PioByteView {
15197 unsafe { PioArtifact::get(artifact) }
15198 .and_then(ArtifactInner::artifact)
15199 .and_then(|artifact| artifact.bytes.as_deref())
15200 .map_or(PioByteView::EMPTY, PioByteView::new)
15201}
15202
15203#[unsafe(no_mangle)]
15204pub unsafe extern "C" fn pio_artifact_retain(artifact: *const PioArtifact) -> *mut PioArtifact {
15205 unsafe { PioArtifact::retain_raw(artifact) }
15206}
15207
15208#[unsafe(no_mangle)]
15209pub unsafe extern "C" fn pio_artifact_release(artifact: *mut PioArtifact) {
15210 unsafe { PioArtifact::release_raw(artifact) };
15211}
15212
15213struct SparseMatrixInner {
15216 rows: usize,
15217 columns: usize,
15218 row_offsets: Vec<usize>,
15219 column_indices: Vec<usize>,
15220 values: Vec<f64>,
15221}
15222
15223impl From<SparseMatrix> for SparseMatrixInner {
15224 fn from(matrix: SparseMatrix) -> Self {
15225 let matrix = matrix.to_csr();
15226 Self {
15227 rows: matrix.rows(),
15228 columns: matrix.cols(),
15229 row_offsets: matrix.indptr().raw_storage().to_vec(),
15230 column_indices: matrix.indices().to_vec(),
15231 values: matrix.data().to_vec(),
15232 }
15233 }
15234}
15235
15236struct VectorInner {
15237 values: Vec<f64>,
15238}
15239
15240opaque_handle!(
15241 PioSparseMatrix,
15243 SparseMatrixInner
15244);
15245opaque_handle!(
15246 PioVector,
15248 VectorInner
15249);
15250
15251fn termination_name(termination: &powerio_prob::Termination) -> &'static str {
15252 use powerio_prob::Termination;
15253 match termination {
15254 Termination::Converged => "converged",
15255 Termination::IterationLimit => "iteration_limit",
15256 Termination::Infeasible => "infeasible",
15257 Termination::Unbounded => "unbounded",
15258 Termination::Failed => "failed",
15259 Termination::NotReported => "not_reported",
15260 _ => "not_reported",
15261 }
15262}
15263
15264#[unsafe(no_mangle)]
15265pub unsafe extern "C" fn pio_calculation_solution_termination(
15266 solution: *const PioCalculationSolution,
15267) -> PioStringView {
15268 let Some(solution) =
15269 (unsafe { PioCalculationSolution::get(solution) }).and_then(ValueInner::value)
15270 else {
15271 return PioStringView::EMPTY;
15272 };
15273 let termination = match solution {
15274 PioValue::DcPfSolution(solution) => solution.termination(),
15275 PioValue::AcPfSolution(solution) => solution.termination(),
15276 PioValue::DcOpfSolution(solution) => solution.termination(),
15277 PioValue::AcOpfSolution(solution) => solution.termination(),
15278 PioValue::SocwrOpfSolution(solution) => solution.termination(),
15279 PioValue::McAcPfSolution(solution) => solution.termination(),
15280 PioValue::McAcOpfSolution(solution) => solution.termination(),
15281 PioValue::AcScucSolution(solution) => solution.termination(),
15282 _ => return PioStringView::EMPTY,
15283 };
15284 PioStringView::new(termination_name(termination))
15285}
15286
15287#[unsafe(no_mangle)]
15290pub unsafe extern "C" fn pio_calculation_solution_get_objective(
15291 solution: *const PioCalculationSolution,
15292 out_objective: *mut f64,
15293) -> bool {
15294 if out_objective.is_null() {
15295 return false;
15296 }
15297 unsafe {
15300 entry(std::ptr::null_mut(), false, || {
15301 let Some(solution) = PioCalculationSolution::get(solution).and_then(ValueInner::value)
15302 else {
15303 return Ok(false);
15304 };
15305 let objective = match solution {
15306 PioValue::DcOpfSolution(solution) => Some(solution.objective()),
15307 PioValue::AcOpfSolution(solution) => Some(solution.objective()),
15308 PioValue::McAcOpfSolution(solution) => Some(solution.objective()),
15309 PioValue::AcScucSolution(solution) => solution.objective(),
15310 _ => None,
15311 };
15312 Ok(match objective {
15313 Some(objective) => {
15314 *out_objective = objective;
15315 true
15316 }
15317 None => false,
15318 })
15319 })
15320 }
15321}
15322
15323#[unsafe(no_mangle)]
15324pub unsafe extern "C" fn pio_socwr_opf_solution_get_objective_lower_bound(
15325 solution: *const PioCalculationSolution,
15326 out_lower_bound: *mut f64,
15327) -> bool {
15328 if out_lower_bound.is_null() {
15329 return false;
15330 }
15331 unsafe {
15333 entry(std::ptr::null_mut(), false, || {
15334 let Some(PioValue::SocwrOpfSolution(solution)) =
15335 PioCalculationSolution::get(solution).and_then(ValueInner::value)
15336 else {
15337 return Ok(false);
15338 };
15339 *out_lower_bound = solution.objective_lower_bound();
15340 Ok(true)
15341 })
15342 }
15343}
15344
15345fn balanced_branch_identities(network: &BalancedNetwork) -> impl Iterator<Item = &str> {
15346 network
15347 .branches()
15348 .iter()
15349 .map(|branch| branch.uid.as_deref().expect("facade assigns component IDs"))
15350}
15351
15352fn balanced_generator_identities(network: &BalancedNetwork) -> impl Iterator<Item = &str> {
15353 network.generators().iter().map(|generator| {
15354 generator
15355 .uid
15356 .as_deref()
15357 .expect("facade assigns component IDs")
15358 })
15359}
15360
15361fn collect_solution_values(solution: &PioValue, quantity: &str) -> Result<Vec<f64>, *mut PioError> {
15362 let unknown = || {
15363 boundary_error(
15364 &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
15365 format!(
15366 "{} does not define solution quantity '{quantity}'",
15367 solution.type_name()
15368 ),
15369 )
15370 };
15371 let values = match solution {
15372 PioValue::DcPfSolution(solution) => match quantity {
15373 "bus_voltage_angle" => solution.bus_voltage_angles().to_vec(),
15374 "bus_active_injection" => solution.bus_active_injections().to_vec(),
15375 "branch_from_active_flow" => solution.branch_from_active_flows().to_vec(),
15376 "branch_to_active_flow" => solution.branch_to_active_flows().to_vec(),
15377 "generator_active_power" => solution
15378 .generator_dispatch()
15379 .map(|dispatch| dispatch.p_mw.clone())
15380 .ok_or_else(unknown)?,
15381 "generator_reactive_power" => solution
15382 .generator_dispatch()
15383 .map(|dispatch| dispatch.q_mvar.clone())
15384 .ok_or_else(unknown)?,
15385 _ => return Err(unknown()),
15386 },
15387 PioValue::AcPfSolution(solution) => match quantity {
15388 "bus_voltage_magnitude" => solution
15389 .network()
15390 .buses()
15391 .iter()
15392 .map(|bus| {
15393 solution
15394 .bus_voltage_magnitude(bus.id)
15395 .expect("complete solution")
15396 })
15397 .collect(),
15398 "bus_voltage_angle" => solution
15399 .network()
15400 .buses()
15401 .iter()
15402 .map(|bus| {
15403 solution
15404 .bus_voltage_angle(bus.id)
15405 .expect("complete solution")
15406 })
15407 .collect(),
15408 "bus_active_injection" => solution
15409 .network()
15410 .buses()
15411 .iter()
15412 .map(|bus| {
15413 solution
15414 .bus_active_injection(bus.id)
15415 .expect("complete solution")
15416 })
15417 .collect(),
15418 "bus_reactive_injection" => solution
15419 .network()
15420 .buses()
15421 .iter()
15422 .map(|bus| {
15423 solution
15424 .bus_reactive_injection(bus.id)
15425 .expect("complete solution")
15426 })
15427 .collect(),
15428 "branch_from_active_flow" => balanced_branch_identities(solution.network())
15429 .map(|id| {
15430 solution
15431 .branch_from_active_flow(id)
15432 .expect("complete solution")
15433 })
15434 .collect(),
15435 "branch_from_reactive_flow" => balanced_branch_identities(solution.network())
15436 .map(|id| {
15437 solution
15438 .branch_from_reactive_flow(id)
15439 .expect("complete solution")
15440 })
15441 .collect(),
15442 "branch_to_active_flow" => balanced_branch_identities(solution.network())
15443 .map(|id| {
15444 solution
15445 .branch_to_active_flow(id)
15446 .expect("complete solution")
15447 })
15448 .collect(),
15449 "branch_to_reactive_flow" => balanced_branch_identities(solution.network())
15450 .map(|id| {
15451 solution
15452 .branch_to_reactive_flow(id)
15453 .expect("complete solution")
15454 })
15455 .collect(),
15456 "generator_active_power" => solution
15457 .generator_dispatch()
15458 .map(|dispatch| dispatch.p_mw.clone())
15459 .ok_or_else(unknown)?,
15460 "generator_reactive_power" => solution
15461 .generator_dispatch()
15462 .map(|dispatch| dispatch.q_mvar.clone())
15463 .ok_or_else(unknown)?,
15464 _ => return Err(unknown()),
15465 },
15466 PioValue::DcOpfSolution(solution) => match quantity {
15467 "bus_voltage_angle" => solution
15468 .network()
15469 .buses()
15470 .iter()
15471 .map(|bus| {
15472 solution
15473 .bus_voltage_angle(bus.id)
15474 .expect("complete solution")
15475 })
15476 .collect(),
15477 "bus_active_injection" => solution
15478 .network()
15479 .buses()
15480 .iter()
15481 .map(|bus| {
15482 solution
15483 .bus_active_injection(bus.id)
15484 .expect("complete solution")
15485 })
15486 .collect(),
15487 "branch_from_active_flow" => balanced_branch_identities(solution.network())
15488 .map(|id| {
15489 solution
15490 .branch_from_active_flow(id)
15491 .expect("complete solution")
15492 })
15493 .collect(),
15494 "branch_to_active_flow" => balanced_branch_identities(solution.network())
15495 .map(|id| {
15496 solution
15497 .branch_to_active_flow(id)
15498 .expect("complete solution")
15499 })
15500 .collect(),
15501 "generator_active_power" => balanced_generator_identities(solution.network())
15502 .map(|id| {
15503 solution
15504 .generator_active_power(id)
15505 .expect("complete solution")
15506 })
15507 .collect(),
15508 "bus_active_power_marginal" => solution
15509 .bus_active_power_marginals()
15510 .map(<[f64]>::to_vec)
15511 .ok_or_else(unknown)?,
15512 "branch_from_limit_multiplier" => solution
15513 .branch_from_limit_multipliers()
15514 .map(<[f64]>::to_vec)
15515 .ok_or_else(unknown)?,
15516 "branch_to_limit_multiplier" => solution
15517 .branch_to_limit_multipliers()
15518 .map(<[f64]>::to_vec)
15519 .ok_or_else(unknown)?,
15520 _ => return Err(unknown()),
15521 },
15522 PioValue::AcOpfSolution(solution) => match quantity {
15523 "bus_voltage_magnitude" => solution.bus_voltage_magnitudes().to_vec(),
15524 "bus_voltage_angle" => solution.bus_voltage_angles().to_vec(),
15525 "bus_active_injection" => solution.bus_active_injections().to_vec(),
15526 "bus_reactive_injection" => solution.bus_reactive_injections().to_vec(),
15527 "branch_from_active_flow" => solution.branch_from_active_flows().to_vec(),
15528 "branch_from_reactive_flow" => solution.branch_from_reactive_flows().to_vec(),
15529 "branch_to_active_flow" => solution.branch_to_active_flows().to_vec(),
15530 "branch_to_reactive_flow" => solution.branch_to_reactive_flows().to_vec(),
15531 "generator_active_power" => solution.generator_active_powers().to_vec(),
15532 "generator_reactive_power" => solution.generator_reactive_powers().to_vec(),
15533 "bus_active_power_marginal" => solution
15534 .bus_active_power_marginals()
15535 .map(<[f64]>::to_vec)
15536 .ok_or_else(unknown)?,
15537 "bus_reactive_power_marginal" => solution
15538 .bus_reactive_power_marginals()
15539 .map(<[f64]>::to_vec)
15540 .ok_or_else(unknown)?,
15541 "branch_from_limit_multiplier" => solution
15542 .branch_from_limit_multipliers()
15543 .map(<[f64]>::to_vec)
15544 .ok_or_else(unknown)?,
15545 "branch_to_limit_multiplier" => solution
15546 .branch_to_limit_multipliers()
15547 .map(<[f64]>::to_vec)
15548 .ok_or_else(unknown)?,
15549 _ => return Err(unknown()),
15550 },
15551 PioValue::SocwrOpfSolution(solution) => {
15552 let values = solution.values();
15553 match quantity {
15554 "bus_voltage_magnitude_squared" => values.bus_voltage_magnitude_squared.clone(),
15555 "branch_voltage_product_real" => values.branch_voltage_product_real.clone(),
15556 "branch_voltage_product_imaginary" => {
15557 values.branch_voltage_product_imaginary.clone()
15558 }
15559 "generator_active_power" => values.generator_active_power.clone(),
15560 "generator_reactive_power" => values.generator_reactive_power.clone(),
15561 "branch_from_active_power" => values.branch_from_active_power.clone(),
15562 "branch_from_reactive_power" => values.branch_from_reactive_power.clone(),
15563 "branch_to_active_power" => values.branch_to_active_power.clone(),
15564 "branch_to_reactive_power" => values.branch_to_reactive_power.clone(),
15565 _ => return Err(unknown()),
15566 }
15567 }
15568 PioValue::McAcPfSolution(solution) => match quantity {
15569 "terminal_voltage_magnitude" => solution
15570 .network()
15571 .buses()
15572 .iter()
15573 .flat_map(|bus| {
15574 bus.terminals.iter().map(move |terminal| {
15575 solution
15576 .terminal_voltage_magnitude(&bus.id, terminal)
15577 .expect("complete solution")
15578 })
15579 })
15580 .collect(),
15581 "terminal_voltage_angle" => solution
15582 .network()
15583 .buses()
15584 .iter()
15585 .flat_map(|bus| {
15586 bus.terminals.iter().map(move |terminal| {
15587 solution
15588 .terminal_voltage_angle(&bus.id, terminal)
15589 .expect("complete solution")
15590 })
15591 })
15592 .collect(),
15593 "source_active_injection" => solution.source_active_injections().to_vec(),
15594 _ => return Err(unknown()),
15595 },
15596 PioValue::McAcOpfSolution(solution) => match quantity {
15597 "terminal_voltage_magnitude" => solution
15598 .network()
15599 .buses()
15600 .iter()
15601 .flat_map(|bus| {
15602 bus.terminals.iter().map(move |terminal| {
15603 solution
15604 .terminal_voltage_magnitude(&bus.id, terminal)
15605 .expect("complete solution")
15606 })
15607 })
15608 .collect(),
15609 "terminal_voltage_angle" => solution
15610 .network()
15611 .buses()
15612 .iter()
15613 .flat_map(|bus| {
15614 bus.terminals.iter().map(move |terminal| {
15615 solution
15616 .terminal_voltage_angle(&bus.id, terminal)
15617 .expect("complete solution")
15618 })
15619 })
15620 .collect(),
15621 "source_active_injection" => solution.source_active_injections().to_vec(),
15622 "generator_active_power" => solution.generator_active_powers().to_vec(),
15623 _ => return Err(unknown()),
15624 },
15625 _ => {
15626 return Err(boundary_error(
15627 &codes::REQUEST_CAPI_TYPE_MISMATCH,
15628 "the handle does not refer to a supported calculation solution",
15629 ));
15630 }
15631 };
15632 Ok(values)
15633}
15634
15635#[unsafe(no_mangle)]
15637pub unsafe extern "C" fn pio_calculation_solution_get_values(
15638 solution: *const PioCalculationSolution,
15639 quantity: *const c_char,
15640 quantity_len: usize,
15641 error: *mut *mut PioError,
15642) -> *mut PioVector {
15643 unsafe {
15644 entry(error, std::ptr::null_mut(), || {
15645 let quantity = required_str(quantity, quantity_len, "quantity")?;
15646 let solution = PioCalculationSolution::get(solution)
15647 .and_then(ValueInner::value)
15648 .ok_or_else(|| {
15649 boundary_error(
15650 &codes::BIND_CAPI_NULL_HANDLE,
15651 "PioCalculationSolution must not be NULL",
15652 )
15653 })?;
15654 collect_solution_values(solution, quantity)
15655 .map(|values| PioVector::new_raw(VectorInner { values }))
15656 })
15657 }
15658}
15659
15660#[unsafe(no_mangle)]
15661pub unsafe extern "C" fn pio_ac_scuc_solution_time_count(
15662 solution: *const PioCalculationSolution,
15663) -> usize {
15664 let Some(PioValue::AcScucSolution(solution)) =
15665 (unsafe { PioCalculationSolution::get(solution) }).and_then(ValueInner::value)
15666 else {
15667 return 0;
15668 };
15669 solution.instance().inputs().interval_durations.len()
15670}
15671
15672#[unsafe(no_mangle)]
15674pub unsafe extern "C" fn pio_ac_scuc_solution_get_values_at(
15675 solution: *const PioCalculationSolution,
15676 quantity: *const c_char,
15677 quantity_len: usize,
15678 time_index: usize,
15679 error: *mut *mut PioError,
15680) -> *mut PioVector {
15681 unsafe {
15682 entry(error, std::ptr::null_mut(), || {
15683 let quantity = required_str(quantity, quantity_len, "quantity")?;
15684 let Some(PioValue::AcScucSolution(solution)) =
15685 PioCalculationSolution::get(solution).and_then(ValueInner::value)
15686 else {
15687 return Err(boundary_error(
15688 &codes::REQUEST_CAPI_TYPE_MISMATCH,
15689 "the handle does not refer to powerio.AcScucSolution",
15690 ));
15691 };
15692 let network = solution.network_outputs();
15693 let device = solution.device_outputs();
15694 macro_rules! row {
15695 ($rows:expr, $convert:expr) => {{
15696 $rows
15697 .get(time_index)
15698 .ok_or_else(|| {
15699 boundary_error(
15700 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15701 format!("AC SCUC time index {time_index} is out of range"),
15702 )
15703 })?
15704 .iter()
15705 .copied()
15706 .map($convert)
15707 .collect::<Vec<f64>>()
15708 }};
15709 }
15710 let values = match quantity {
15711 "bus_voltage_magnitude" => row!(&network.bus_vm, |value| value),
15712 "bus_voltage_angle" => row!(&network.bus_va, |value| value),
15713 "shunt_step" => row!(&network.shunt_step, |value| value as f64),
15714 "ac_line_on_status" => row!(&network.ac_line_on_status, f64::from),
15715 "transformer_tap_ratio" => row!(&network.transformer_tm, |value| value),
15716 "transformer_phase_shift" => row!(&network.transformer_ta, |value| value),
15717 "transformer_on_status" => row!(&network.transformer_on_status, f64::from),
15718 "dc_line_from_active_power" => row!(&network.dc_line_pdc_fr, |value| value),
15719 "dc_line_from_reactive_power" => row!(&network.dc_line_qdc_fr, |value| value),
15720 "dc_line_to_reactive_power" => row!(&network.dc_line_qdc_to, |value| value),
15721 "device_on_status" => row!(&device.on_status, f64::from),
15722 "device_startup_status" => row!(&device.startup_status, f64::from),
15723 "device_shutdown_status" => row!(&device.shutdown_status, f64::from),
15724 "device_active_power" => row!(&device.p_on, |value| value),
15725 "device_reactive_power" => row!(&device.q, |value| value),
15726 "regulation_reserve_up" => row!(&device.p_reg_res_up, |value| value),
15727 "regulation_reserve_down" => row!(&device.p_reg_res_down, |value| value),
15728 "synchronized_reserve" => row!(&device.p_syn_res, |value| value),
15729 "nonsynchronized_reserve" => row!(&device.p_nsyn_res, |value| value),
15730 "ramping_reserve_up_online" => row!(&device.p_ramp_res_up_online, |value| value),
15731 "ramping_reserve_up_offline" => {
15732 row!(&device.p_ramp_res_up_offline, |value| value)
15733 }
15734 "ramping_reserve_down_online" => {
15735 row!(&device.p_ramp_res_down_online, |value| value)
15736 }
15737 "ramping_reserve_down_offline" => {
15738 row!(&device.p_ramp_res_down_offline, |value| value)
15739 }
15740 "reactive_reserve_up" => row!(&device.q_res_up, |value| value),
15741 "reactive_reserve_down" => row!(&device.q_res_down, |value| value),
15742 _ => {
15743 return Err(boundary_error(
15744 &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
15745 format!("unknown AC SCUC solution quantity '{quantity}'"),
15746 ));
15747 }
15748 };
15749 Ok(PioVector::new_raw(VectorInner { values }))
15750 })
15751 }
15752}
15753
15754fn formula(name: Option<&str>) -> Result<BranchSusceptanceFormula, *mut PioError> {
15755 match name.unwrap_or("series_susceptance") {
15756 "series_susceptance" => Ok(BranchSusceptanceFormula::SeriesSusceptance),
15757 "tap_adjusted_reactance" => Ok(BranchSusceptanceFormula::TapAdjustedReactance),
15758 "reactance_only" => Ok(BranchSusceptanceFormula::ReactanceOnly),
15759 other => Err(boundary_error(
15760 &codes::REQUEST_CAPI_UNKNOWN_FORMULA,
15761 format!("unknown branch susceptance formula '{other}'"),
15762 )),
15763 }
15764}
15765
15766unsafe fn dc_operators(
15767 network: *const PioBalancedNetwork,
15768 formula_name: *const c_char,
15769 formula_name_len: usize,
15770) -> Result<DcOperators, *mut PioError> {
15771 let network = unsafe { PioBalancedNetwork::get(network) }
15772 .and_then(BalancedNetworkInner::network)
15773 .ok_or_else(|| {
15774 boundary_error(
15775 &codes::BIND_CAPI_NULL_HANDLE,
15776 "PioBalancedNetwork must not be NULL",
15777 )
15778 })?;
15779 let formula_name = unsafe { optional_str(formula_name, formula_name_len, "formula") }?;
15780 let formula = formula(formula_name)?;
15781 let instance = DcPfInstance::from_network(network.clone())
15782 .map_err(|failure| error_from_core(&failure))?
15783 .with_branch_susceptance_formula(formula);
15784 DcOperators::build(&instance).map_err(|failure| error_from_core(&failure))
15785}
15786
15787unsafe fn dc_matrix(
15788 network: *const PioBalancedNetwork,
15789 formula: *const c_char,
15790 formula_len: usize,
15791 error: *mut *mut PioError,
15792 calculation: impl FnOnce(&DcOperators) -> SparseMatrix,
15793) -> *mut PioSparseMatrix {
15794 unsafe {
15795 entry(error, std::ptr::null_mut(), || {
15796 let operators = dc_operators(network, formula, formula_len)?;
15797 Ok(PioSparseMatrix::new_raw(SparseMatrixInner::from(
15798 calculation(&operators),
15799 )))
15800 })
15801 }
15802}
15803
15804unsafe fn dc_vector(
15805 network: *const PioBalancedNetwork,
15806 formula: *const c_char,
15807 formula_len: usize,
15808 error: *mut *mut PioError,
15809 calculation: impl FnOnce(&DcOperators) -> Vec<f64>,
15810) -> *mut PioVector {
15811 unsafe {
15812 entry(error, std::ptr::null_mut(), || {
15813 let operators = dc_operators(network, formula, formula_len)?;
15814 Ok(PioVector::new_raw(VectorInner {
15815 values: calculation(&operators),
15816 }))
15817 })
15818 }
15819}
15820
15821#[unsafe(no_mangle)]
15822pub unsafe extern "C" fn pio_calc_incidence_matrix(
15823 network: *const PioBalancedNetwork,
15824 formula: *const c_char,
15825 formula_len: usize,
15826 error: *mut *mut PioError,
15827) -> *mut PioSparseMatrix {
15828 unsafe {
15829 dc_matrix(network, formula, formula_len, error, |operators| {
15830 operators.calc_incidence_matrix()
15831 })
15832 }
15833}
15834
15835#[unsafe(no_mangle)]
15836pub unsafe extern "C" fn pio_calc_bus_susceptance_matrix(
15837 network: *const PioBalancedNetwork,
15838 formula: *const c_char,
15839 formula_len: usize,
15840 error: *mut *mut PioError,
15841) -> *mut PioSparseMatrix {
15842 unsafe {
15843 dc_matrix(network, formula, formula_len, error, |operators| {
15844 operators.calc_bus_susceptance_matrix()
15845 })
15846 }
15847}
15848
15849#[unsafe(no_mangle)]
15850pub unsafe extern "C" fn pio_calc_branch_flow_matrix(
15851 network: *const PioBalancedNetwork,
15852 formula: *const c_char,
15853 formula_len: usize,
15854 error: *mut *mut PioError,
15855) -> *mut PioSparseMatrix {
15856 unsafe {
15857 dc_matrix(network, formula, formula_len, error, |operators| {
15858 operators.calc_branch_flow_matrix()
15859 })
15860 }
15861}
15862
15863#[unsafe(no_mangle)]
15864pub unsafe extern "C" fn pio_calc_branch_susceptances(
15865 network: *const PioBalancedNetwork,
15866 formula: *const c_char,
15867 formula_len: usize,
15868 error: *mut *mut PioError,
15869) -> *mut PioVector {
15870 unsafe {
15871 dc_vector(network, formula, formula_len, error, |operators| {
15872 operators.calc_branch_susceptances().to_vec()
15873 })
15874 }
15875}
15876
15877#[unsafe(no_mangle)]
15878pub unsafe extern "C" fn pio_calc_branch_phase_shift_injection(
15879 network: *const PioBalancedNetwork,
15880 formula: *const c_char,
15881 formula_len: usize,
15882 error: *mut *mut PioError,
15883) -> *mut PioVector {
15884 unsafe {
15885 dc_vector(network, formula, formula_len, error, |operators| {
15886 operators.calc_branch_phase_shift_injection()
15887 })
15888 }
15889}
15890
15891#[unsafe(no_mangle)]
15892pub unsafe extern "C" fn pio_calc_bus_phase_shift_injection(
15893 network: *const PioBalancedNetwork,
15894 formula: *const c_char,
15895 formula_len: usize,
15896 error: *mut *mut PioError,
15897) -> *mut PioVector {
15898 unsafe {
15899 dc_vector(network, formula, formula_len, error, |operators| {
15900 operators.calc_bus_phase_shift_injection()
15901 })
15902 }
15903}
15904
15905unsafe fn dc_vector_from_angles(
15906 network: *const PioBalancedNetwork,
15907 formula_name: *const c_char,
15908 formula_name_len: usize,
15909 voltage_angles: *const f64,
15910 voltage_angles_len: usize,
15911 error: *mut *mut PioError,
15912 calculation: impl FnOnce(&DcOperators, &[f64]) -> Result<Vec<f64>, powerio_core::Error>,
15913) -> *mut PioVector {
15914 unsafe {
15915 entry(error, std::ptr::null_mut(), || {
15916 if voltage_angles.is_null() && voltage_angles_len != 0 {
15917 return Err(boundary_error(
15918 &codes::BIND_CAPI_NULL_ARGUMENT,
15919 "voltage_angles is NULL with a nonzero length",
15920 ));
15921 }
15922 let angles = if voltage_angles_len == 0 {
15923 &[]
15924 } else {
15925 std::slice::from_raw_parts(voltage_angles, voltage_angles_len)
15926 };
15927 let operators = dc_operators(network, formula_name, formula_name_len)?;
15928 calculation(&operators, angles)
15929 .map(|values| PioVector::new_raw(VectorInner { values }))
15930 .map_err(|failure| error_from_core(&failure))
15931 })
15932 }
15933}
15934
15935#[unsafe(no_mangle)]
15936pub unsafe extern "C" fn pio_calc_branch_flow_dc(
15937 network: *const PioBalancedNetwork,
15938 formula: *const c_char,
15939 formula_len: usize,
15940 voltage_angles: *const f64,
15941 voltage_angles_len: usize,
15942 error: *mut *mut PioError,
15943) -> *mut PioVector {
15944 unsafe {
15945 dc_vector_from_angles(
15946 network,
15947 formula,
15948 formula_len,
15949 voltage_angles,
15950 voltage_angles_len,
15951 error,
15952 DcOperators::calc_branch_flow_dc,
15953 )
15954 }
15955}
15956
15957#[unsafe(no_mangle)]
15958pub unsafe extern "C" fn pio_calc_bus_injection_dc(
15959 network: *const PioBalancedNetwork,
15960 formula: *const c_char,
15961 formula_len: usize,
15962 voltage_angles: *const f64,
15963 voltage_angles_len: usize,
15964 error: *mut *mut PioError,
15965) -> *mut PioVector {
15966 unsafe {
15967 dc_vector_from_angles(
15968 network,
15969 formula,
15970 formula_len,
15971 voltage_angles,
15972 voltage_angles_len,
15973 error,
15974 DcOperators::calc_bus_injection_dc,
15975 )
15976 }
15977}
15978
15979#[unsafe(no_mangle)]
15980pub unsafe extern "C" fn pio_sparse_matrix_rows(matrix: *const PioSparseMatrix) -> usize {
15981 unsafe { PioSparseMatrix::get(matrix) }.map_or(0, |matrix| matrix.rows)
15982}
15983
15984#[unsafe(no_mangle)]
15985pub unsafe extern "C" fn pio_sparse_matrix_columns(matrix: *const PioSparseMatrix) -> usize {
15986 unsafe { PioSparseMatrix::get(matrix) }.map_or(0, |matrix| matrix.columns)
15987}
15988
15989#[unsafe(no_mangle)]
15990pub unsafe extern "C" fn pio_sparse_matrix_row_offsets(
15991 matrix: *const PioSparseMatrix,
15992) -> PioSizeView {
15993 unsafe { PioSparseMatrix::get(matrix) }.map_or(PioSizeView::EMPTY, |matrix| {
15994 PioSizeView::new(&matrix.row_offsets)
15995 })
15996}
15997
15998#[unsafe(no_mangle)]
15999pub unsafe extern "C" fn pio_sparse_matrix_column_indices(
16000 matrix: *const PioSparseMatrix,
16001) -> PioSizeView {
16002 unsafe { PioSparseMatrix::get(matrix) }.map_or(PioSizeView::EMPTY, |matrix| {
16003 PioSizeView::new(&matrix.column_indices)
16004 })
16005}
16006
16007#[unsafe(no_mangle)]
16008pub unsafe extern "C" fn pio_sparse_matrix_values(matrix: *const PioSparseMatrix) -> PioF64View {
16009 unsafe { PioSparseMatrix::get(matrix) }
16010 .map_or(PioF64View::EMPTY, |matrix| PioF64View::new(&matrix.values))
16011}
16012
16013#[unsafe(no_mangle)]
16014pub unsafe extern "C" fn pio_sparse_matrix_retain(
16015 matrix: *const PioSparseMatrix,
16016) -> *mut PioSparseMatrix {
16017 unsafe { PioSparseMatrix::retain_raw(matrix) }
16018}
16019
16020#[unsafe(no_mangle)]
16021pub unsafe extern "C" fn pio_sparse_matrix_release(matrix: *mut PioSparseMatrix) {
16022 unsafe { PioSparseMatrix::release_raw(matrix) };
16023}
16024
16025#[unsafe(no_mangle)]
16026pub unsafe extern "C" fn pio_vector_values(vector: *const PioVector) -> PioF64View {
16027 unsafe { PioVector::get(vector) }
16028 .map_or(PioF64View::EMPTY, |vector| PioF64View::new(&vector.values))
16029}
16030
16031#[unsafe(no_mangle)]
16032pub unsafe extern "C" fn pio_vector_retain(vector: *const PioVector) -> *mut PioVector {
16033 unsafe { PioVector::retain_raw(vector) }
16034}
16035
16036#[unsafe(no_mangle)]
16037pub unsafe extern "C" fn pio_vector_release(vector: *mut PioVector) {
16038 unsafe { PioVector::release_raw(vector) };
16039}
16040
16041struct StringInner {
16044 text: String,
16045}
16046
16047opaque_handle!(
16048 PioString,
16050 StringInner
16051);
16052
16053#[unsafe(no_mangle)]
16055pub unsafe extern "C" fn pio_schema_report(error: *mut *mut PioError) -> *mut PioString {
16056 unsafe {
16057 entry(error, std::ptr::null_mut(), || {
16058 serde_json::to_string(&serde_json::json!({
16059 "powerio_version": powerio::VERSION,
16060 "abi": PIO_ABI_VERSION,
16061 "powerio_ir": {
16062 "schema": powerio::IR_SCHEMA_NAME,
16063 "version": powerio::IR_VERSION
16064 },
16065 "bmopf_schema": powerio_dist::BMOPF_SCHEMA_VERSION,
16066 "features": {
16070 "matrix": true,
16071 "gridfm": cfg!(feature = "gridfm"),
16072 "dist": true,
16073 "prob": true
16074 },
16075 "foreign_schemas": {
16076 "bmopf": powerio_dist::BMOPF_SCHEMA_VERSION
16077 },
16078 "error_categories": powerio_core::ErrorCategory::TOKENS,
16079 "diagnostic_namespaces": powerio_core::DiagnosticStage::NAMESPACES,
16080 "json_classes": powerio::JSON_CLASSES
16081 }))
16082 .map(|text| PioString::new_raw(StringInner { text }))
16083 .map_err(|failure| {
16084 boundary_error(
16085 &codes::EMIT_CAPI_SERIALIZE_FAILED,
16086 format!("cannot serialize the schema report: {failure}"),
16087 )
16088 })
16089 })
16090 }
16091}
16092
16093#[unsafe(no_mangle)]
16094pub unsafe extern "C" fn pio_string_view(string: *const PioString) -> PioStringView {
16095 unsafe { PioString::get(string) }.map_or(PioStringView::EMPTY, |string| {
16096 PioStringView::new(&string.text)
16097 })
16098}
16099
16100#[unsafe(no_mangle)]
16101pub unsafe extern "C" fn pio_string_retain(string: *const PioString) -> *mut PioString {
16102 unsafe { PioString::retain_raw(string) }
16103}
16104
16105#[unsafe(no_mangle)]
16106pub unsafe extern "C" fn pio_string_release(string: *mut PioString) {
16107 unsafe { PioString::release_raw(string) };
16108}
16109
16110#[cfg(test)]
16111mod tests {
16112 use super::*;
16113
16114 unsafe fn view_text(view: PioStringView) -> String {
16115 if view.data.is_null() {
16116 return String::new();
16117 }
16118 String::from_utf8_lossy(unsafe {
16119 std::slice::from_raw_parts(view.data.cast::<u8>(), view.len)
16120 })
16121 .into_owned()
16122 }
16123
16124 unsafe fn parse_case9() -> *mut PioModule {
16125 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/data/case9.m");
16126 let path = path.to_string_lossy();
16127 let mut error = std::ptr::null_mut();
16128 let source = unsafe { pio_source_open(path.as_ptr().cast(), path.len(), &mut error) };
16129 assert!(!source.is_null(), "{}", unsafe { error_text(error) });
16130 let module = unsafe { pio_parse(source, std::ptr::null(), 0, &mut error) };
16131 unsafe { pio_source_release(source) };
16132 assert!(!module.is_null(), "{}", unsafe { error_text(error) });
16133 module
16134 }
16135
16136 fn parse_xiidm_text(text: &str) -> *mut PioModule {
16137 unsafe {
16138 let name = b"case.xiidm";
16139 let format = b"xiidm";
16140 let mut error = std::ptr::null_mut();
16141 let source = pio_source_from_memory(
16142 name.as_ptr().cast(),
16143 name.len(),
16144 text.as_ptr(),
16145 text.len(),
16146 &mut error,
16147 );
16148 assert!(!source.is_null(), "{}", error_text(error));
16149 let module = pio_parse(source, format.as_ptr().cast(), format.len(), &mut error);
16150 pio_source_release(source);
16151 assert!(!module.is_null(), "{}", error_text(error));
16152 module
16153 }
16154 }
16155
16156 unsafe fn parse_bmopf() -> *mut PioModule {
16157 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
16158 .join("../tests/data/dist/bmopf/example_ieee13.json");
16159 let path = path.to_string_lossy();
16160 let mut error = std::ptr::null_mut();
16161 let source = unsafe { pio_source_open(path.as_ptr().cast(), path.len(), &mut error) };
16162 assert!(!source.is_null(), "{}", unsafe { error_text(error) });
16163 let module = unsafe { pio_parse(source, std::ptr::null(), 0, &mut error) };
16164 unsafe { pio_source_release(source) };
16165 assert!(!module.is_null(), "{}", unsafe { error_text(error) });
16166 module
16167 }
16168
16169 unsafe fn error_text(error: *const PioError) -> String {
16170 let view = unsafe { pio_error_message(error) };
16171 if view.data.is_null() {
16172 return "missing PioError".to_owned();
16173 }
16174 String::from_utf8_lossy(unsafe {
16175 std::slice::from_raw_parts(view.data.cast::<u8>(), view.len)
16176 })
16177 .into_owned()
16178 }
16179
16180 unsafe fn case9_network() -> BalancedNetwork {
16181 let module = unsafe { parse_case9() };
16182 let network = match &unsafe { PioModule::get(module) }.unwrap().module.value() {
16183 PioValue::BalancedNetwork(network) => network.clone(),
16184 value => panic!("expected balanced network, got {}", value.type_name()),
16185 };
16186 unsafe { pio_module_release(module) };
16187 network
16188 }
16189
16190 fn module_with_complete_records() -> *mut PioModule {
16191 use std::collections::BTreeMap;
16192
16193 let mut module = powerio::PioModule::new(PioValue::BalancedNetwork(BalancedNetwork::new(
16194 "metadata", 100.0,
16195 )))
16196 .with_producer(Producer::new("record-test", "1.2.3").unwrap());
16197 let source_id = powerio_core::SourceId::new("source-1").unwrap();
16198 let source =
16199 powerio_core::SourceDescriptor::new(source_id.clone(), "record-test.xiidm", 128)
16200 .unwrap()
16201 .with_format(powerio_core::FormatId::new("xiidm").unwrap())
16202 .with_digest(
16203 powerio_core::Digest::sha256(
16204 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
16205 )
16206 .unwrap(),
16207 );
16208 module.add_source_descriptor(source).unwrap();
16209 module
16210 .add_source_map_entry(
16211 powerio_core::SourceMapEntry::new(
16212 "/value/buses/0",
16213 powerio_core::SourceRelation::Exact,
16214 vec![powerio_core::SourceSpan::new(source_id, 4, 12).unwrap()],
16215 )
16216 .unwrap(),
16217 )
16218 .unwrap();
16219 let parameters = BTreeMap::from([(
16220 "settings".to_owned(),
16221 serde_json::json!({
16222 "items": [null, true, "named", 18446744073709551615u64, 1.25]
16223 }),
16224 )]);
16225 let history = HistoryEntry::new(
16226 HistoryId::new("history-1").unwrap(),
16227 HistoryKind::Transform,
16228 "normalize",
16229 )
16230 .unwrap()
16231 .with_input_type("powerio.BalancedNetwork")
16232 .unwrap()
16233 .with_output_type("powerio.BalancedNetwork")
16234 .unwrap()
16235 .with_parameters(parameters)
16236 .unwrap()
16237 .with_assumption("base power is known")
16238 .unwrap()
16239 .with_loss("source token spelling")
16240 .unwrap();
16241 module.add_history_entry(history).unwrap();
16242 module
16243 .insert_extension("org.example.record", serde_json::json!({"enabled": true}))
16244 .unwrap();
16245 module_handle(module)
16246 }
16247
16248 fn multiconductor_network() -> powerio_dist::MulticonductorNetwork {
16249 let mut network = powerio_dist::MulticonductorNetwork::named("capi-solution-instance");
16250 network.buses_mut().push(powerio_dist::DistBus::new(
16251 "source",
16252 vec!["1".into(), "2".into(), "3".into()],
16253 ));
16254 network.sources_mut().push(powerio_dist::VoltageSource::new(
16255 "source",
16256 "source",
16257 vec!["1".into(), "2".into(), "3".into()],
16258 vec![240.0, 240.0, 240.0],
16259 vec![0.0, -2.094, 2.094],
16260 ));
16261 network
16262 }
16263
16264 fn complete_multiconductor_network() -> powerio_dist::MulticonductorNetwork {
16265 let mut network = powerio_dist::MulticonductorNetwork::named("");
16266 *network.source_format_mut() = Some(powerio_dist::DistSourceFormat::Dss);
16267 *network.geo_mut() = Some(powerio_dist::DistGeoMeta {
16268 space: powerio_dist::CoordinateSpace::Diagram {
16269 canvas: Some(powerio_dist::DistCanvas {
16270 width: Some(640.0),
16271 height: None,
16272 units: Some(String::new()),
16273 }),
16274 },
16275 kind: Some(powerio_dist::DistCoordsKind::Manual),
16276 });
16277
16278 let mut bus = powerio_dist::DistBus::new("bus", vec!["1".into(), "2".into()]);
16279 bus.grounded.push("2".into());
16280 bus.v_min = Some(0.0);
16281 bus.vpn_min = Some(Vec::new());
16282 bus.vpp_max = Some(vec![240.0, 240.0]);
16283 bus.location = Some(powerio_dist::DistLocation {
16284 x: 10.0,
16285 y: 20.0,
16286 kind: None,
16287 });
16288 network.buses_mut().push(bus);
16289
16290 let mut line_code = powerio_dist::DistLineCode::new(
16291 "code",
16292 vec![vec![1.0, 2.0], vec![3.0]],
16293 vec![vec![4.0]],
16294 );
16295 line_code.g_from = vec![vec![5.0]];
16296 line_code.b_from = vec![vec![6.0]];
16297 line_code.g_to = vec![vec![7.0]];
16298 line_code.b_to = vec![vec![8.0]];
16299 line_code.i_max = Some(Vec::new());
16300 line_code.s_max = Some(vec![900.0]);
16301 line_code.source = Some(String::new());
16302 network.line_codes_mut().push(line_code);
16303
16304 let mut line = powerio_dist::DistLine::new(
16305 "line",
16306 "bus",
16307 "bus",
16308 vec!["1".into()],
16309 vec!["2".into()],
16310 "code",
16311 12.0,
16312 );
16313 line.route = Some(Vec::new());
16314 line.i_max = Some(vec![10.0]);
16315 network.lines_mut().push(line);
16316
16317 let mut switch = powerio_dist::DistSwitch::new(
16318 "switch",
16319 "bus",
16320 "bus",
16321 vec!["1".into()],
16322 vec!["2".into()],
16323 true,
16324 );
16325 switch.i_max = Some(Vec::new());
16326 network.switches_mut().push(switch);
16327
16328 let mut winding = powerio_dist::DistWinding::new(
16329 "bus",
16330 vec!["1".into(), "2".into()],
16331 powerio_dist::DistWindingConn::Wye,
16332 240.0,
16333 1_000.0,
16334 );
16335 winding.r_pct = 0.5;
16336 winding.r_neutral = Some(0.0);
16337 network
16338 .transformers_mut()
16339 .push(powerio_dist::DistTransformer::new(
16340 "transformer",
16341 vec![winding],
16342 vec![2.5],
16343 1,
16344 ));
16345
16346 let mut load = powerio_dist::DistLoad::new(
16347 "load",
16348 "bus",
16349 vec!["1".into()],
16350 powerio_dist::Configuration::Wye,
16351 vec![100.0],
16352 vec![20.0],
16353 );
16354 load.voltage_model = powerio_dist::DistLoadVoltageModel::Zip {
16355 v_nom: vec![240.0],
16356 alpha_z: vec![0.1],
16357 alpha_i: vec![0.2],
16358 alpha_p: vec![0.7],
16359 beta_z: vec![0.2],
16360 beta_i: vec![0.3],
16361 beta_p: vec![0.5],
16362 };
16363 network.loads_mut().push(load);
16364
16365 let mut generator = powerio_dist::DistGenerator::new(
16366 "generator",
16367 "bus",
16368 vec!["1".into()],
16369 powerio_dist::Configuration::SinglePhase,
16370 vec![50.0],
16371 vec![5.0],
16372 );
16373 generator.p_min = Some(Vec::new());
16374 generator.cost = Some(vec![0.25]);
16375 network.generators_mut().push(generator);
16376
16377 let mut resource = powerio_dist::DistIbr::new(
16378 "ibr",
16379 "bus",
16380 vec!["1".into()],
16381 powerio_dist::IbrTopology::SinglePhase,
16382 powerio_dist::IbrPrimeMover::Pv,
16383 vec![100.0],
16384 );
16385 resource.i_max = Some(Vec::new());
16386 resource.p_avail = Some(75.0);
16387 resource.control_profile = Some(String::new());
16388 resource.voltage_aggregation = Some(powerio_dist::IbrVoltageAggregation::PerPhase);
16389 network.ibrs_mut().push(resource);
16390
16391 let profile: powerio_dist::DistControlProfile = serde_json::from_value(serde_json::json!({
16392 "name": "profile",
16393 "power_factor": { "pf": 0.97 },
16394 "volt_var": {
16395 "voltage_reference": "PN_PER_PHASE",
16396 "breakpoints": [0.95, 1.05],
16397 "q_limits": [-0.4, 0.4],
16398 "q_unit": "VA_FRACTION",
16399 "q_ref": "VAR_MAX",
16400 "p_min_for_q": 10.0,
16401 "p_min_for_q_max": null
16402 },
16403 "volt_watt": {
16404 "voltage_reference": null,
16405 "breakpoints": [1.0],
16406 "p_limits": [0.8],
16407 "p_unit": "W",
16408 "p_ref": "P_AVAILABLE"
16409 },
16410 "extras": {}
16411 }))
16412 .unwrap();
16413 network.control_profiles_mut().push(profile);
16414
16415 network.shunts_mut().push(powerio_dist::DistShunt::new(
16416 "shunt",
16417 "bus",
16418 vec!["2".into()],
16419 vec![vec![0.01, 0.02], vec![0.03]],
16420 vec![vec![0.04]],
16421 ));
16422 network
16423 .capacitors_mut()
16424 .push(powerio_dist::DistCapacitor::new(
16425 "capacitor",
16426 "bus",
16427 vec!["1".into()],
16428 powerio_dist::Configuration::SinglePhase,
16429 100.0,
16430 240.0,
16431 ));
16432 network.sources_mut().push(powerio_dist::VoltageSource::new(
16433 "source",
16434 "bus",
16435 vec!["1".into()],
16436 vec![240.0],
16437 vec![0.0],
16438 ));
16439 network.sources_mut()[0].energy_cost_rate = Some(vec![0.125]);
16440 network
16441 .untyped_objects_mut()
16442 .push(powerio_dist::UntypedObject::new(
16443 "curve",
16444 "raw",
16445 vec![
16446 (None, "first".into()),
16447 (Some(String::new()), "second".into()),
16448 ],
16449 ));
16450 network
16451 .commands_mut()
16452 .push(("solve".into(), "mode=daily".into()));
16453 network
16454 .options_mut()
16455 .push(("frequency".into(), "60".into()));
16456 network
16457 }
16458
16459 fn goc3_instance(source: Source) -> powerio_prob::AcScucInstance {
16460 let module = powerio::parse_with_options(
16461 source,
16462 &powerio::ParseOptions::default()
16463 .format("goc3-json")
16464 .unwrap(),
16465 )
16466 .unwrap();
16467 let PioValue::AcScucInstance(instance) = module.into_value() else {
16468 panic!("GO Challenge 3 problem did not produce powerio.AcScucInstance");
16469 };
16470 instance
16471 }
16472
16473 #[test]
16474 fn reports_abi_seven() {
16475 assert_eq!(pio_abi_version(), 7);
16476 assert_eq!(PIO_ABI_VERSION, 7);
16477 unsafe {
16478 let mut error = std::ptr::null_mut();
16479 let report = pio_schema_report(&mut error);
16480 assert!(!report.is_null(), "{}", error_text(error));
16481 let parsed: serde_json::Value =
16482 serde_json::from_str(&view_text(pio_string_view(report))).unwrap();
16483 assert_eq!(parsed["abi"], 7);
16484 assert_eq!(parsed["powerio_ir"]["schema"], powerio::IR_SCHEMA_NAME);
16485 assert_eq!(parsed["powerio_ir"]["version"], powerio::IR_VERSION);
16486 assert_eq!(
16487 parsed["bmopf_schema"],
16488 serde_json::json!(powerio_dist::BMOPF_SCHEMA_VERSION)
16489 );
16490 assert_eq!(parsed["foreign_schemas"]["bmopf"], parsed["bmopf_schema"]);
16491 assert_eq!(
16492 parsed["error_categories"],
16493 serde_json::json!(powerio_core::ErrorCategory::TOKENS)
16494 );
16495 assert_eq!(
16496 parsed["diagnostic_namespaces"],
16497 serde_json::json!(powerio_core::DiagnosticStage::NAMESPACES)
16498 );
16499 assert_eq!(
16500 parsed["json_classes"],
16501 serde_json::json!(powerio::JSON_CLASSES)
16502 );
16503 for feature in ["matrix", "gridfm", "dist", "prob"] {
16504 assert!(parsed["features"][feature].is_boolean());
16505 }
16506 pio_string_release(report);
16507 }
16508 }
16509
16510 #[test]
16511 fn structured_diagnostic_fields_keep_their_owner_alive() {
16512 unsafe {
16513 let source = powerio_core::SourceId::new("input").unwrap();
16514 let span = powerio_core::SourceSpan::new(source, 2, 7).unwrap();
16515 let mut details = serde_json::Map::new();
16516 details.insert("count".to_owned(), serde_json::json!(3));
16517 details.insert("nested".to_owned(), serde_json::json!({ "ok": true }));
16518 let diagnostic = Diagnostic::new(
16519 powerio_core::DiagnosticCode::new("PARTNER.TEST.STRUCTURED").unwrap(),
16520 powerio_core::DiagnosticSeverity::Warning,
16521 "structured finding",
16522 )
16523 .with_id(powerio_core::DiagnosticId::new("d1").unwrap())
16524 .with_target("/value/data")
16525 .unwrap()
16526 .with_span(span)
16527 .unwrap()
16528 .with_related(powerio_core::DiagnosticId::new("d0").unwrap())
16529 .unwrap()
16530 .with_details(details)
16531 .unwrap()
16532 .with_suggested_action("fix it");
16533 let diagnostics = PioDiagnostics::new_raw(DiagnosticsInner {
16534 owner: DiagnosticsOwner::Owned(vec![diagnostic]),
16535 });
16536 let retained = pio_diagnostics_retain(diagnostics);
16537 pio_diagnostics_release(diagnostics);
16538
16539 assert_eq!(pio_diagnostics_len(retained), 1);
16540 assert_eq!(
16541 view_text(pio_diagnostic_code(retained, 0)),
16542 "PARTNER.TEST.STRUCTURED"
16543 );
16544 assert_eq!(view_text(pio_diagnostic_severity(retained, 0)), "warning");
16545 assert_eq!(
16546 view_text(pio_diagnostic_message(retained, 0)),
16547 "structured finding"
16548 );
16549 assert!(pio_diagnostic_has_id(retained, 0));
16550 assert_eq!(view_text(pio_diagnostic_id(retained, 0)), "d1");
16551 assert!(pio_diagnostic_has_target(retained, 0));
16552 assert_eq!(view_text(pio_diagnostic_target(retained, 0)), "/value/data");
16553 assert!(pio_diagnostic_has_suggested_action(retained, 0));
16554 assert_eq!(
16555 view_text(pio_diagnostic_suggested_action(retained, 0)),
16556 "fix it"
16557 );
16558
16559 assert_eq!(pio_diagnostic_n_spans(retained, 0), 1);
16560 let mut output = PioDiagnosticSpanView {
16561 source: PioStringView::EMPTY,
16562 byte_start: 0,
16563 byte_end: 0,
16564 };
16565 let mut error = std::ptr::null_mut();
16566 assert!(pio_diagnostic_span(retained, 0, 0, &mut output, &mut error));
16567 assert!(error.is_null());
16568 assert_eq!(view_text(output.source), "input");
16569 assert_eq!(output.byte_start, 2);
16570 assert_eq!(output.byte_end, 7);
16571
16572 assert_eq!(pio_diagnostic_n_related(retained, 0), 1);
16573 assert_eq!(view_text(pio_diagnostic_related(retained, 0, 0)), "d0");
16574
16575 let details_text = pio_diagnostic_details_json(retained, 0, &mut error);
16576 assert!(!details_text.is_null(), "{}", error_text(error));
16577 let details: serde_json::Value =
16578 serde_json::from_str(&view_text(pio_string_view(details_text))).unwrap();
16579 assert_eq!(details["count"], 3);
16580 assert_eq!(details["nested"]["ok"], true);
16581 pio_string_release(details_text);
16582
16583 pio_diagnostics_release(retained);
16584 }
16585 }
16586
16587 #[test]
16588 fn module_records_are_typed_and_structured_values_keep_the_owner_alive() {
16589 unsafe {
16590 let module = module_with_complete_records();
16591 let mut error = std::ptr::null_mut();
16592
16593 let mut producer = std::mem::MaybeUninit::<PioModuleProducerView>::uninit();
16594 assert!(pio_module_producer(
16595 module,
16596 producer.as_mut_ptr(),
16597 &mut error
16598 ));
16599 let producer = producer.assume_init();
16600 assert_eq!(view_text(producer.name), "record-test");
16601 assert_eq!(view_text(producer.version), "1.2.3");
16602
16603 assert_eq!(pio_module_source_count(module), 1);
16604 let mut source = std::mem::MaybeUninit::<PioModuleSourceView>::uninit();
16605 assert!(pio_module_source_at(
16606 module,
16607 0,
16608 source.as_mut_ptr(),
16609 &mut error
16610 ));
16611 let source = source.assume_init();
16612 assert_eq!(view_text(source.id), "source-1");
16613 assert_eq!(view_text(source.name), "record-test.xiidm");
16614 assert_eq!(source.byte_length, 128);
16615 assert!(source.has_format);
16616 assert_eq!(view_text(source.format), "xiidm");
16617 assert!(source.has_digest);
16618 assert_eq!(view_text(source.digest_algorithm), "sha256");
16619 assert_eq!(source.digest.len, 64);
16620
16621 assert_eq!(pio_module_source_map_count(module), 1);
16622 let mut mapping = std::mem::MaybeUninit::<PioModuleSourceMapEntryView>::uninit();
16623 assert!(pio_module_source_map_at(
16624 module,
16625 0,
16626 mapping.as_mut_ptr(),
16627 &mut error
16628 ));
16629 let mapping = mapping.assume_init();
16630 assert_eq!(view_text(mapping.target), "/value/buses/0");
16631 assert_eq!(view_text(mapping.relation), "exact");
16632 assert_eq!(mapping.span_count, 1);
16633 let mut span = std::mem::MaybeUninit::<PioSourceSpanView>::uninit();
16634 assert!(pio_module_source_map_span_at(
16635 module,
16636 0,
16637 0,
16638 span.as_mut_ptr(),
16639 &mut error
16640 ));
16641 let span = span.assume_init();
16642 assert_eq!(view_text(span.source), "source-1");
16643 assert_eq!((span.byte_start, span.byte_end), (4, 12));
16644
16645 assert_eq!(pio_module_history_count(module), 1);
16646 let mut history = std::mem::MaybeUninit::<PioModuleHistoryEntryView>::uninit();
16647 assert!(pio_module_history_at(
16648 module,
16649 0,
16650 history.as_mut_ptr(),
16651 &mut error
16652 ));
16653 let history = history.assume_init();
16654 assert_eq!(view_text(history.id), "history-1");
16655 assert_eq!(view_text(history.kind), "transform");
16656 assert_eq!(view_text(history.name), "normalize");
16657 assert!(history.has_input_type);
16658 assert_eq!(view_text(history.input_type), "powerio.BalancedNetwork");
16659 assert!(history.has_output_type);
16660 assert_eq!(view_text(history.output_type), "powerio.BalancedNetwork");
16661 assert_eq!(history.parameter_count, 1);
16662 assert_eq!(history.assumption_count, 1);
16663 assert_eq!(history.loss_count, 1);
16664 assert_eq!(
16665 view_text(pio_module_history_assumption_at(module, 0, 0, &mut error)),
16666 "base power is known"
16667 );
16668 assert_eq!(
16669 view_text(pio_module_history_loss_at(module, 0, 0, &mut error)),
16670 "source token spelling"
16671 );
16672 let mut parameter = std::mem::MaybeUninit::<PioModuleHistoryParameterView>::uninit();
16673 assert!(pio_module_history_parameter_at(
16674 module,
16675 0,
16676 0,
16677 parameter.as_mut_ptr(),
16678 &mut error
16679 ));
16680 let parameter = parameter.assume_init();
16681 assert_eq!(view_text(parameter.name), "settings");
16682 assert_eq!(view_text(parameter.value_kind), "object");
16683
16684 assert_eq!(pio_module_extension_count(module), 1);
16685 let mut extension = std::mem::MaybeUninit::<PioModuleExtensionView>::uninit();
16686 assert!(pio_module_extension_at(
16687 module,
16688 0,
16689 extension.as_mut_ptr(),
16690 &mut error
16691 ));
16692 let extension = extension.assume_init();
16693 assert_eq!(view_text(extension.namespace), "org.example.record");
16694 assert_eq!(view_text(extension.value_kind), "object");
16695
16696 let parameter_value = pio_module_history_parameter_value_at(module, 0, 0, &mut error);
16697 assert!(!parameter_value.is_null(), "{}", error_text(error));
16698 let items = pio_json_value_object_value_at(parameter_value, 0, &mut error);
16699 assert!(!items.is_null(), "{}", error_text(error));
16700 let unsigned = pio_json_value_array_at(items, 3, &mut error);
16701 assert!(!unsigned.is_null(), "{}", error_text(error));
16702 let retained_unsigned = pio_json_value_retain(unsigned);
16703 let extension_value = pio_module_extension_value_at(module, 0, &mut error);
16704 assert!(!extension_value.is_null(), "{}", error_text(error));
16705
16706 let mut ignored = std::mem::MaybeUninit::<PioModuleSourceView>::uninit();
16707 assert!(!pio_module_source_at(
16708 module,
16709 1,
16710 ignored.as_mut_ptr(),
16711 &mut error
16712 ));
16713 assert!(!error.is_null());
16714 pio_error_release(error);
16715 error = std::ptr::null_mut();
16716
16717 pio_module_release(module);
16718 pio_json_value_release(parameter_value);
16719 pio_json_value_release(items);
16720 pio_json_value_release(unsigned);
16721
16722 let mut number = std::mem::MaybeUninit::<PioJsonValueView>::uninit();
16723 assert!(pio_json_value_get(
16724 retained_unsigned,
16725 number.as_mut_ptr(),
16726 &mut error
16727 ));
16728 let number = number.assume_init();
16729 assert_eq!(view_text(number.kind), "number");
16730 assert_eq!(view_text(number.number_kind), "unsigned_integer");
16731 assert_eq!(number.unsigned_integer_value, u64::MAX);
16732
16733 let enabled = pio_json_value_object_value_at(extension_value, 0, &mut error);
16734 assert!(!enabled.is_null(), "{}", error_text(error));
16735 let mut boolean = std::mem::MaybeUninit::<PioJsonValueView>::uninit();
16736 assert!(pio_json_value_get(
16737 enabled,
16738 boolean.as_mut_ptr(),
16739 &mut error
16740 ));
16741 let boolean = boolean.assume_init();
16742 assert_eq!(view_text(boolean.kind), "boolean");
16743 assert!(boolean.boolean_value);
16744
16745 let mut null_output = std::mem::MaybeUninit::<PioModuleProducerView>::uninit();
16746 assert!(!pio_module_producer(
16747 std::ptr::null(),
16748 null_output.as_mut_ptr(),
16749 &mut error
16750 ));
16751 assert!(!error.is_null());
16752 pio_error_release(error);
16753 pio_json_value_release(enabled);
16754 pio_json_value_release(extension_value);
16755 pio_json_value_release(retained_unsigned);
16756 }
16757 }
16758
16759 #[test]
16760 fn structural_access_keeps_the_module_owner_alive() {
16761 unsafe {
16762 let module = parse_case9();
16763 let diagnostics = pio_module_diagnostics(module);
16764 let value = pio_module_value(module);
16765 assert!(pio_value_is_type(
16766 value,
16767 c"powerio.BalancedNetwork".as_ptr(),
16768 "powerio.BalancedNetwork".len(),
16769 ));
16770 let mut error = std::ptr::null_mut();
16771 let network = pio_value_balanced_network(value, &mut error);
16772 assert!(!network.is_null(), "{}", error_text(error));
16773
16774 pio_module_release(module);
16775 pio_value_release(value);
16776 assert_eq!(pio_balanced_network_bus_count(network), 9);
16777 assert_eq!(pio_balanced_network_branch_count(network), 9);
16778 assert_eq!(pio_diagnostics_len(diagnostics), 0);
16779
16780 pio_balanced_network_release(network);
16781 pio_diagnostics_release(diagnostics);
16782 }
16783 }
16784
16785 #[test]
16786 fn abi_seven_bus_views_preserve_phase_bounds_after_owner_release() {
16787 unsafe {
16788 let mut network = complete_multiconductor_network();
16789 network.buses_mut()[0].v_min = None;
16790 network.buses_mut()[0].v_min_phase = Some(vec![210.0, 215.0]);
16791 let module = module_handle(powerio::PioModule::new(PioValue::from(network)));
16792 let value = pio_module_value(module);
16793 let mut error = std::ptr::null_mut();
16794 let network = pio_value_multiconductor_network(value, &mut error);
16795 assert!(!network.is_null());
16796 let mut bus = std::mem::MaybeUninit::<PioMulticonductorBusView>::uninit();
16797 pio_module_release(module);
16798 pio_value_release(value);
16799 assert!(pio_multiconductor_network_bus_at(
16800 network,
16801 0,
16802 bus.as_mut_ptr(),
16803 &mut error
16804 ));
16805 let bus = bus.assume_init();
16806 assert!(bus.has_phase_to_ground_voltage_min);
16807 assert!(!bus.has_voltage_min);
16808 assert_eq!(
16809 std::slice::from_raw_parts(
16810 bus.phase_to_ground_voltage_min_v.data,
16811 bus.phase_to_ground_voltage_min_v.len
16812 ),
16813 &[210.0, 215.0]
16814 );
16815 pio_multiconductor_network_release(network);
16816 }
16817 }
16818
16819 #[test]
16820 #[allow(clippy::too_many_lines)]
16821 fn multiconductor_typed_views_preserve_ownership_and_optional_values() {
16822 unsafe {
16823 let module = module_handle(powerio::PioModule::new(PioValue::from(
16824 complete_multiconductor_network(),
16825 )));
16826 let value = pio_module_value(module);
16827 let mut error = std::ptr::null_mut();
16828 let network = pio_value_multiconductor_network(value, &mut error);
16829 assert!(!network.is_null(), "{}", error_text(error));
16830 let retained = pio_multiconductor_network_retain(network);
16831 pio_module_release(module);
16832 pio_value_release(value);
16833 pio_multiconductor_network_release(network);
16834
16835 assert!(pio_multiconductor_network_has_name(retained));
16836 assert_eq!(pio_multiconductor_network_name(retained).len, 0);
16837 assert!(pio_multiconductor_network_has_source_format(retained));
16838 assert_eq!(
16839 view_text(pio_multiconductor_network_source_format(retained)),
16840 "dss"
16841 );
16842
16843 let mut geo = std::mem::MaybeUninit::<PioMulticonductorGeoView>::uninit();
16844 assert!(pio_multiconductor_network_geo(
16845 retained,
16846 geo.as_mut_ptr(),
16847 &mut error,
16848 ));
16849 let geo = geo.assume_init();
16850 assert!(geo.has_geo);
16851 assert_eq!(view_text(geo.space), "diagram");
16852 assert!(geo.has_canvas);
16853 assert!(geo.has_canvas_width);
16854 assert!(!geo.has_canvas_height);
16855 assert!(geo.has_canvas_units);
16856 assert_eq!(geo.canvas_units.len, 0);
16857
16858 let mut counts = std::mem::MaybeUninit::<PioMulticonductorNetworkCountsView>::uninit();
16859 assert!(pio_multiconductor_network_counts(
16860 retained,
16861 counts.as_mut_ptr(),
16862 &mut error,
16863 ));
16864 let counts = counts.assume_init();
16865 for count in [
16866 counts.buses,
16867 counts.line_codes,
16868 counts.lines,
16869 counts.switches,
16870 counts.transformers,
16871 counts.loads,
16872 counts.generators,
16873 counts.inverter_based_resources,
16874 counts.control_profiles,
16875 counts.shunts,
16876 counts.capacitors,
16877 counts.voltage_sources,
16878 counts.untyped_objects,
16879 counts.commands,
16880 counts.options,
16881 ] {
16882 assert_eq!(count, 1);
16883 }
16884
16885 let mut bus = std::mem::MaybeUninit::<PioMulticonductorBusView>::uninit();
16886 assert!(pio_multiconductor_network_bus_at(
16887 retained,
16888 0,
16889 bus.as_mut_ptr(),
16890 &mut error,
16891 ));
16892 let bus = bus.assume_init();
16893 assert_eq!(view_text(bus.id), "bus");
16894 assert!(bus.has_phase_to_neutral_voltage_min);
16895 assert_eq!(bus.phase_to_neutral_voltage_min_v.len, 0);
16896 assert!(bus.has_location);
16897 assert!(!bus.location.has_kind);
16898 let mut text = std::mem::MaybeUninit::<PioStringView>::uninit();
16899 assert!(pio_multiconductor_network_bus_terminal_at(
16900 retained,
16901 0,
16902 1,
16903 text.as_mut_ptr(),
16904 &mut error,
16905 ));
16906 assert_eq!(view_text(text.assume_init()), "2");
16907
16908 let mut line_code = std::mem::MaybeUninit::<PioMulticonductorLineCodeView>::uninit();
16909 assert!(pio_multiconductor_network_line_code_at(
16910 retained,
16911 0,
16912 line_code.as_mut_ptr(),
16913 &mut error,
16914 ));
16915 let line_code = line_code.assume_init();
16916 assert_eq!(line_code.resistance_matrix_row_count, 2);
16917 assert!(line_code.has_current_limit);
16918 assert_eq!(line_code.current_limit_a.len, 0);
16919 assert!(line_code.has_source);
16920 assert_eq!(line_code.source.len, 0);
16921 let mut matrix_row = std::mem::MaybeUninit::<PioF64View>::uninit();
16922 assert!(
16923 pio_multiconductor_network_line_code_resistance_matrix_row_at(
16924 retained,
16925 0,
16926 1,
16927 matrix_row.as_mut_ptr(),
16928 &mut error,
16929 )
16930 );
16931 let matrix_row = matrix_row.assume_init();
16932 assert_eq!(matrix_row.len, 1);
16933 assert_eq!(*matrix_row.data, 3.0);
16934
16935 let mut line = std::mem::MaybeUninit::<PioMulticonductorLineView>::uninit();
16936 assert!(pio_multiconductor_network_line_at(
16937 retained,
16938 0,
16939 line.as_mut_ptr(),
16940 &mut error,
16941 ));
16942 let line = line.assume_init();
16943 assert!(line.has_route);
16944 assert_eq!(line.route_point_count, 0);
16945
16946 let mut switch = std::mem::MaybeUninit::<PioMulticonductorSwitchView>::uninit();
16947 assert!(pio_multiconductor_network_switch_at(
16948 retained,
16949 0,
16950 switch.as_mut_ptr(),
16951 &mut error,
16952 ));
16953 let switch = switch.assume_init();
16954 assert!(switch.open);
16955 assert!(switch.has_current_limit);
16956 assert_eq!(switch.current_limit_a.len, 0);
16957
16958 let mut transformer =
16959 std::mem::MaybeUninit::<PioMulticonductorTransformerView>::uninit();
16960 assert!(pio_multiconductor_network_transformer_at(
16961 retained,
16962 0,
16963 transformer.as_mut_ptr(),
16964 &mut error,
16965 ));
16966 assert_eq!(transformer.assume_init().winding_count, 1);
16967 let mut winding =
16968 std::mem::MaybeUninit::<PioMulticonductorTransformerWindingView>::uninit();
16969 assert!(pio_multiconductor_network_transformer_winding_at(
16970 retained,
16971 0,
16972 0,
16973 winding.as_mut_ptr(),
16974 &mut error,
16975 ));
16976 let winding = winding.assume_init();
16977 assert!(winding.has_neutral_resistance);
16978 assert!(!winding.has_neutral_reactance);
16979
16980 let mut load = std::mem::MaybeUninit::<PioMulticonductorLoadView>::uninit();
16981 assert!(pio_multiconductor_network_load_at(
16982 retained,
16983 0,
16984 load.as_mut_ptr(),
16985 &mut error,
16986 ));
16987 let load = load.assume_init();
16988 assert_eq!(view_text(load.voltage_model), "zip");
16989 assert_eq!(*load.active_power_constant_power.data, 0.7);
16990
16991 let mut generator = std::mem::MaybeUninit::<PioMulticonductorGeneratorView>::uninit();
16992 assert!(pio_multiconductor_network_generator_at(
16993 retained,
16994 0,
16995 generator.as_mut_ptr(),
16996 &mut error,
16997 ));
16998 let generator = generator.assume_init();
16999 assert!(generator.has_active_power_min);
17000 assert_eq!(generator.active_power_min_w.len, 0);
17001 assert!(generator.has_active_power_dispatch_cost);
17002
17003 let mut resource = std::mem::MaybeUninit::<PioInverterBasedResourceView>::uninit();
17004 assert!(pio_multiconductor_network_inverter_based_resource_at(
17005 retained,
17006 0,
17007 resource.as_mut_ptr(),
17008 &mut error,
17009 ));
17010 let resource = resource.assume_init();
17011 assert_eq!(view_text(resource.topology), "SINGLE_PHASE");
17012 assert!(resource.has_control_profile);
17013 assert_eq!(resource.control_profile.len, 0);
17014
17015 let mut profile = std::mem::MaybeUninit::<PioControlProfileView>::uninit();
17016 assert!(pio_multiconductor_network_control_profile_at(
17017 retained,
17018 0,
17019 profile.as_mut_ptr(),
17020 &mut error,
17021 ));
17022 let profile = profile.assume_init();
17023 assert!(profile.has_power_factor);
17024 assert!(profile.has_volt_var);
17025 assert!(profile.has_volt_watt);
17026 assert_eq!(
17027 view_text(profile.volt_var_voltage_reference),
17028 "PN_PER_PHASE"
17029 );
17030
17031 let mut shunt = std::mem::MaybeUninit::<PioMulticonductorShuntView>::uninit();
17032 assert!(pio_multiconductor_network_shunt_at(
17033 retained,
17034 0,
17035 shunt.as_mut_ptr(),
17036 &mut error,
17037 ));
17038 assert_eq!(shunt.assume_init().conductance_matrix_row_count, 2);
17039 let mut shunt_matrix_row = std::mem::MaybeUninit::<PioF64View>::uninit();
17040 assert!(pio_multiconductor_network_shunt_conductance_matrix_row_at(
17041 retained,
17042 0,
17043 1,
17044 shunt_matrix_row.as_mut_ptr(),
17045 &mut error,
17046 ));
17047 assert_eq!(shunt_matrix_row.assume_init().len, 1);
17048
17049 let mut capacitor = std::mem::MaybeUninit::<PioMulticonductorCapacitorView>::uninit();
17050 assert!(pio_multiconductor_network_capacitor_at(
17051 retained,
17052 0,
17053 capacitor.as_mut_ptr(),
17054 &mut error,
17055 ));
17056 assert_eq!(capacitor.assume_init().rated_reactive_power_var, 100.0);
17057
17058 let mut source = std::mem::MaybeUninit::<PioVoltageSourceView>::uninit();
17059 assert!(pio_multiconductor_network_voltage_source_at(
17060 retained,
17061 0,
17062 source.as_mut_ptr(),
17063 &mut error,
17064 ));
17065 let source = source.assume_init();
17066 assert_eq!(*source.voltage_magnitude_v.data, 240.0);
17067 assert!(source.has_energy_cost_rate);
17068 assert_eq!(source.energy_cost_rate_per_kwh.len, 1);
17069 assert_eq!(*source.energy_cost_rate_per_kwh.data, 0.125);
17070
17071 let mut object = std::mem::MaybeUninit::<PioMulticonductorUntypedObjectView>::uninit();
17072 assert!(pio_multiconductor_network_untyped_object_at(
17073 retained,
17074 0,
17075 object.as_mut_ptr(),
17076 &mut error,
17077 ));
17078 assert_eq!(object.assume_init().property_count, 2);
17079 let mut property =
17080 std::mem::MaybeUninit::<PioMulticonductorUntypedPropertyView>::uninit();
17081 assert!(pio_multiconductor_network_untyped_object_property_at(
17082 retained,
17083 0,
17084 0,
17085 property.as_mut_ptr(),
17086 &mut error,
17087 ));
17088 assert!(!property.assume_init().has_name);
17089 assert!(pio_multiconductor_network_untyped_object_property_at(
17090 retained,
17091 0,
17092 1,
17093 property.as_mut_ptr(),
17094 &mut error,
17095 ));
17096 let property = property.assume_init();
17097 assert!(property.has_name);
17098 assert_eq!(property.name.len, 0);
17099
17100 let mut command = std::mem::MaybeUninit::<PioMulticonductorCommandView>::uninit();
17101 assert!(pio_multiconductor_network_command_at(
17102 retained,
17103 0,
17104 command.as_mut_ptr(),
17105 &mut error,
17106 ));
17107 assert_eq!(view_text(command.assume_init().verb), "solve");
17108 let mut option = std::mem::MaybeUninit::<PioStringPropertyView>::uninit();
17109 assert!(pio_multiconductor_network_option_at(
17110 retained,
17111 0,
17112 option.as_mut_ptr(),
17113 &mut error,
17114 ));
17115 assert_eq!(view_text(option.assume_init().name), "frequency");
17116
17117 assert!(
17118 std::mem::offset_of!(PioMulticonductorLineView, has_route)
17119 > std::mem::offset_of!(PioMulticonductorLineView, route_point_count)
17120 );
17121 assert!(
17122 std::mem::offset_of!(PioMulticonductorBusView, has_location)
17123 > std::mem::offset_of!(PioMulticonductorBusView, location)
17124 );
17125
17126 let mut null_error = std::ptr::null_mut();
17127 let mut null_output = std::mem::MaybeUninit::<PioMulticonductorBusView>::uninit();
17128 assert!(!pio_multiconductor_network_bus_at(
17129 std::ptr::null(),
17130 0,
17131 null_output.as_mut_ptr(),
17132 &mut null_error,
17133 ));
17134 assert!(!null_error.is_null());
17135 assert_eq!(
17136 view_text(pio_error_code(null_error)),
17137 codes::BIND_CAPI_NULL_HANDLE.code
17138 );
17139 pio_error_release(null_error);
17140 pio_multiconductor_network_release(retained);
17141 }
17142 }
17143
17144 #[test]
17145 fn balanced_geography_is_typed_and_preserves_optional_routes() {
17146 unsafe {
17147 let mut network = case9_network();
17148 *network.geo_mut() = Some(powerio_tx::GeoMeta {
17149 space: powerio_tx::CoordinateSpace::Diagram {
17150 canvas: Some(powerio_tx::Canvas {
17151 width: Some(800.0),
17152 height: None,
17153 units: Some(String::new()),
17154 }),
17155 },
17156 kind: Some(powerio_tx::CoordsKind::Manual),
17157 });
17158 network.buses_mut()[0].location = Some(powerio_tx::Location {
17159 x: 12.0,
17160 y: 34.0,
17161 kind: None,
17162 });
17163 network.branches_mut()[0].route = Some(Vec::new());
17164 network.branches_mut()[1].route = Some(vec![powerio_tx::Location {
17165 x: 56.0,
17166 y: 78.0,
17167 kind: Some(powerio_tx::CoordsKind::Derived),
17168 }]);
17169
17170 let module = module_handle(powerio::PioModule::new(PioValue::BalancedNetwork(network)));
17171 let value = pio_module_value(module);
17172 let mut error = std::ptr::null_mut();
17173 let balanced = pio_value_balanced_network(value, &mut error);
17174 assert!(!balanced.is_null(), "{}", error_text(error));
17175 let retained = pio_balanced_network_retain(balanced);
17176 pio_balanced_network_release(balanced);
17177 pio_value_release(value);
17178 pio_module_release(module);
17179
17180 let mut geo = std::mem::MaybeUninit::<PioBalancedGeoView>::uninit();
17181 assert!(pio_balanced_network_geo(
17182 retained,
17183 geo.as_mut_ptr(),
17184 &mut error
17185 ));
17186 let geo = geo.assume_init();
17187 assert!(geo.has_geo);
17188 assert_eq!(view_text(geo.space), "diagram");
17189 assert!(geo.has_kind);
17190 assert_eq!(view_text(geo.kind), "manual");
17191 assert!(geo.has_canvas);
17192 assert!(geo.has_canvas_width);
17193 assert_eq!(geo.canvas_width, 800.0);
17194 assert!(!geo.has_canvas_height);
17195 assert!(geo.has_canvas_units);
17196 assert_eq!(geo.canvas_units.len, 0);
17197
17198 let mut bus = std::mem::MaybeUninit::<PioBalancedBusView>::uninit();
17199 assert!(pio_balanced_network_bus_at(
17200 retained,
17201 0,
17202 bus.as_mut_ptr(),
17203 &mut error
17204 ));
17205 let bus = bus.assume_init();
17206 assert!(bus.has_location);
17207 assert_eq!((bus.location.x, bus.location.y), (12.0, 34.0));
17208 assert!(!bus.location.has_kind);
17209
17210 let mut branch = std::mem::MaybeUninit::<PioBalancedBranchView>::uninit();
17211 assert!(pio_balanced_network_branch_at(
17212 retained,
17213 0,
17214 branch.as_mut_ptr(),
17215 &mut error
17216 ));
17217 let branch = branch.assume_init();
17218 assert!(branch.has_route);
17219 assert_eq!(branch.route_point_count, 0);
17220
17221 let mut routed_branch = std::mem::MaybeUninit::<PioBalancedBranchView>::uninit();
17222 assert!(pio_balanced_network_branch_at(
17223 retained,
17224 1,
17225 routed_branch.as_mut_ptr(),
17226 &mut error
17227 ));
17228 let routed_branch = routed_branch.assume_init();
17229 assert!(routed_branch.has_route);
17230 assert_eq!(routed_branch.route_point_count, 1);
17231 let mut point = std::mem::MaybeUninit::<PioBalancedLocationView>::uninit();
17232 assert!(pio_balanced_network_branch_route_point_at(
17233 retained,
17234 1,
17235 0,
17236 point.as_mut_ptr(),
17237 &mut error
17238 ));
17239 let point = point.assume_init();
17240 assert_eq!((point.x, point.y), (56.0, 78.0));
17241 assert!(point.has_kind);
17242 assert_eq!(view_text(point.kind), "derived");
17243
17244 assert!(
17245 std::mem::offset_of!(PioBalancedBusView, has_location)
17246 > std::mem::offset_of!(PioBalancedBusView, location)
17247 );
17248 assert!(
17249 std::mem::offset_of!(PioBalancedBranchView, has_route)
17250 > std::mem::offset_of!(PioBalancedBranchView, route_point_count)
17251 );
17252
17253 let mut null_geo = std::mem::MaybeUninit::<PioBalancedGeoView>::uninit();
17254 assert!(!pio_balanced_network_geo(
17255 std::ptr::null(),
17256 null_geo.as_mut_ptr(),
17257 &mut error
17258 ));
17259 assert!(!error.is_null());
17260 pio_error_release(error);
17261 pio_balanced_network_release(retained);
17262 }
17263 }
17264
17265 #[test]
17266 #[allow(clippy::too_many_lines)]
17267 fn every_solution_exposes_its_owner_rooted_instance() {
17268 unsafe {
17269 let network = case9_network();
17270 let buses = network.buses().len();
17271 let branches = network.branches().len();
17272 let generators = network.generators().len();
17273 let three_winding = network.transformers_3w().len();
17274
17275 let dc_pf_instance =
17276 Arc::new(powerio_prob::DcPfInstance::from_network(network.clone()).unwrap());
17277 let dc_pf = powerio_prob::DcPfSolution::new(
17278 dc_pf_instance,
17279 powerio_prob::Termination::Converged,
17280 vec![0.0; buses],
17281 vec![0.0; buses],
17282 vec![0.0; branches],
17283 vec![0.0; branches],
17284 vec![Default::default(); three_winding],
17285 )
17286 .unwrap();
17287
17288 let ac_pf_instance =
17289 Arc::new(powerio_prob::AcPfInstance::from_network(network.clone()).unwrap());
17290 let ac_pf = powerio_prob::AcPfSolution::new(
17291 ac_pf_instance,
17292 powerio_prob::Termination::Converged,
17293 vec![1.0; buses],
17294 vec![0.0; buses],
17295 vec![0.0; buses],
17296 vec![0.0; buses],
17297 vec![0.0; branches],
17298 vec![0.0; branches],
17299 vec![0.0; branches],
17300 vec![0.0; branches],
17301 vec![Default::default(); three_winding],
17302 )
17303 .unwrap();
17304
17305 let dc_opf_instance =
17306 Arc::new(powerio_prob::DcOpfInstance::from_network(network.clone()).unwrap());
17307 let dc_opf = powerio_prob::DcOpfSolution::new(
17308 dc_opf_instance,
17309 powerio_prob::Termination::Converged,
17310 vec![0.0; buses],
17311 vec![0.0; buses],
17312 vec![0.0; branches],
17313 vec![0.0; branches],
17314 vec![0.0; generators],
17315 0.0,
17316 vec![Default::default(); three_winding],
17317 )
17318 .unwrap();
17319
17320 let ac_opf_instance =
17321 Arc::new(powerio_prob::AcOpfInstance::from_network(network.clone()).unwrap());
17322 let ac_opf = powerio_prob::AcOpfSolution::new(
17323 Arc::clone(&ac_opf_instance),
17324 powerio_prob::Termination::Converged,
17325 vec![1.0; buses],
17326 vec![0.0; buses],
17327 vec![0.0; buses],
17328 vec![0.0; buses],
17329 vec![0.0; branches],
17330 vec![0.0; branches],
17331 vec![0.0; branches],
17332 vec![0.0; branches],
17333 vec![0.0; generators],
17334 vec![0.0; generators],
17335 0.0,
17336 vec![Default::default(); three_winding],
17337 )
17338 .unwrap();
17339
17340 let mut socwr_values = powerio_prob::solution::SocwrOpfValues::default();
17341 socwr_values.bus_voltage_magnitude_squared = vec![1.0; buses];
17342 socwr_values.branch_voltage_product_real = vec![0.0; branches];
17343 socwr_values.branch_voltage_product_imaginary = vec![0.0; branches];
17344 socwr_values.generator_active_power = vec![0.0; generators];
17345 socwr_values.generator_reactive_power = vec![0.0; generators];
17346 socwr_values.branch_from_active_power = vec![0.0; branches];
17347 socwr_values.branch_from_reactive_power = vec![0.0; branches];
17348 socwr_values.branch_to_active_power = vec![0.0; branches];
17349 socwr_values.branch_to_reactive_power = vec![0.0; branches];
17350 socwr_values.three_winding_transformer_terminal_powers =
17351 vec![Default::default(); three_winding];
17352 let socwr = powerio_prob::solution::SocwrOpfSolution::new(
17353 ac_opf_instance,
17354 powerio_prob::Termination::Converged,
17355 socwr_values,
17356 0.0,
17357 )
17358 .unwrap();
17359
17360 let multiconductor = multiconductor_network();
17361 let terminals: usize = multiconductor
17362 .buses()
17363 .iter()
17364 .map(|bus| bus.terminals.len())
17365 .sum();
17366 let source_terminals: usize = multiconductor
17367 .sources()
17368 .iter()
17369 .map(|source| source.terminal_map.len())
17370 .sum();
17371 let generator_terminals: usize = multiconductor
17372 .generators()
17373 .iter()
17374 .map(|generator| generator.terminal_map.len())
17375 .sum();
17376 let mc_pf_instance = Arc::new(
17377 powerio_prob::McAcPfInstance::from_network(multiconductor.clone()).unwrap(),
17378 );
17379 let mc_pf = powerio_prob::McAcPfSolution::new(
17380 mc_pf_instance,
17381 powerio_prob::Termination::Converged,
17382 vec![1.0; terminals],
17383 vec![0.0; terminals],
17384 vec![0.0; source_terminals],
17385 )
17386 .unwrap();
17387 let mc_opf_instance =
17388 Arc::new(powerio_prob::McAcOpfInstance::from_network(multiconductor).unwrap());
17389 let mc_opf = powerio_prob::McAcOpfSolution::new(
17390 mc_opf_instance,
17391 powerio_prob::Termination::Converged,
17392 vec![1.0; terminals],
17393 vec![0.0; terminals],
17394 vec![0.0; source_terminals],
17395 vec![0.0; generator_terminals],
17396 0.0,
17397 )
17398 .unwrap();
17399
17400 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
17401 .join("../tests/data/goc3/goc3_small.json");
17402 let scuc_instance = goc3_instance(Source::open(path).unwrap());
17403 let scuc = powerio_prob::AcScucSolution::new(
17404 Arc::new(scuc_instance),
17405 powerio_prob::Termination::Converged,
17406 powerio_prob::ScucNetworkOutputs::default(),
17407 powerio_prob::ScucDeviceOutputs::default(),
17408 None,
17409 )
17410 .unwrap();
17411
17412 type SolutionAccessor = unsafe extern "C" fn(
17413 *const PioValueHandle,
17414 *mut *mut PioError,
17415 )
17416 -> *mut PioCalculationSolution;
17417 let solutions: [(PioValue, &str, bool, SolutionAccessor); 8] = [
17418 (
17419 PioValue::DcPfSolution(dc_pf),
17420 "powerio.DcPfInstance",
17421 false,
17422 pio_value_dc_pf_solution,
17423 ),
17424 (
17425 PioValue::AcPfSolution(ac_pf),
17426 "powerio.AcPfInstance",
17427 false,
17428 pio_value_ac_pf_solution,
17429 ),
17430 (
17431 PioValue::DcOpfSolution(dc_opf),
17432 "powerio.DcOpfInstance",
17433 false,
17434 pio_value_dc_opf_solution,
17435 ),
17436 (
17437 PioValue::AcOpfSolution(ac_opf),
17438 "powerio.AcOpfInstance",
17439 false,
17440 pio_value_ac_opf_solution,
17441 ),
17442 (
17443 PioValue::SocwrOpfSolution(socwr),
17444 "powerio.AcOpfInstance",
17445 false,
17446 pio_value_socwr_opf_solution,
17447 ),
17448 (
17449 PioValue::McAcPfSolution(mc_pf),
17450 "powerio.McAcPfInstance",
17451 true,
17452 pio_value_mc_ac_pf_solution,
17453 ),
17454 (
17455 PioValue::McAcOpfSolution(mc_opf),
17456 "powerio.McAcOpfInstance",
17457 true,
17458 pio_value_mc_ac_opf_solution,
17459 ),
17460 (
17461 PioValue::AcScucSolution(scuc),
17462 "powerio.AcScucInstance",
17463 false,
17464 pio_value_ac_scuc_solution,
17465 ),
17466 ];
17467
17468 for (value, expected_instance_type, is_multiconductor, accessor) in solutions {
17469 let module = module_handle(powerio::PioModule::new(value));
17470 let value = pio_module_value(module);
17471 let mut error = std::ptr::null_mut();
17472 let solution = accessor(value, &mut error);
17473 assert!(!solution.is_null(), "{}", error_text(error));
17474 let instance = pio_calculation_solution_instance(solution, &mut error);
17475 assert!(!instance.is_null(), "{}", error_text(error));
17476
17477 pio_calculation_solution_release(solution);
17478 pio_value_release(value);
17479 pio_module_release(module);
17480
17481 assert_eq!(
17482 view_text(pio_calculation_instance_type_name(instance)),
17483 expected_instance_type
17484 );
17485 if is_multiconductor {
17486 let network =
17487 pio_calculation_instance_multiconductor_network(instance, &mut error);
17488 assert!(!network.is_null(), "{}", error_text(error));
17489 assert!(pio_multiconductor_network_bus_count(network) > 0);
17490 pio_multiconductor_network_release(network);
17491 } else {
17492 let network = pio_calculation_instance_balanced_network(instance, &mut error);
17493 assert!(!network.is_null(), "{}", error_text(error));
17494 assert!(pio_balanced_network_bus_count(network) > 0);
17495 pio_balanced_network_release(network);
17496 }
17497 pio_calculation_instance_release(instance);
17498 }
17499 }
17500 }
17501
17502 #[test]
17503 fn balanced_rows_are_checked_borrowed_views() {
17504 unsafe {
17505 let module = parse_case9();
17506 let value = pio_module_value(module);
17507 let mut error = std::ptr::null_mut();
17508 let network = pio_value_balanced_network(value, &mut error);
17509 assert!(!network.is_null(), "{}", error_text(error));
17510
17511 pio_module_release(module);
17512 pio_value_release(value);
17513
17514 assert_eq!(pio_balanced_network_base_mva(network), 100.0);
17515 assert_eq!(pio_balanced_network_base_frequency_hz(network), 60.0);
17516 assert_eq!(pio_balanced_network_bus_count(network), 9);
17517 assert_eq!(pio_balanced_network_load_count(network), 3);
17518 assert_eq!(pio_balanced_network_shunt_count(network), 0);
17519 assert_eq!(pio_balanced_network_branch_count(network), 9);
17520 assert_eq!(pio_balanced_network_generator_count(network), 3);
17521 assert_eq!(pio_balanced_network_storage_count(network), 0);
17522
17523 let mut bus = std::mem::MaybeUninit::<PioBalancedBusView>::uninit();
17524 assert!(pio_balanced_network_bus_at(
17525 network,
17526 0,
17527 bus.as_mut_ptr(),
17528 &mut error,
17529 ));
17530 let bus = bus.assume_init();
17531 assert_eq!(bus.id, 1);
17532 assert_eq!(view_text(bus.bus_type), "REF");
17533 assert_eq!(bus.base_kv, 345.0);
17534 assert!(bus.has_component_id);
17535 assert_eq!(view_text(bus.component_id), "1");
17536
17537 let mut load = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
17538 assert!(pio_balanced_network_load_at(
17539 network,
17540 0,
17541 load.as_mut_ptr(),
17542 &mut error,
17543 ));
17544 let load = load.assume_init();
17545 assert_eq!(load.bus_id, 5);
17546 assert_eq!(load.p_mw, 90.0);
17547 assert_eq!(load.q_mvar, 30.0);
17548 assert_eq!(view_text(load.voltage_model.kind), "constant_power");
17549
17550 let mut branch = std::mem::MaybeUninit::<PioBalancedBranchView>::uninit();
17551 assert!(pio_balanced_network_branch_at(
17552 network,
17553 0,
17554 branch.as_mut_ptr(),
17555 &mut error,
17556 ));
17557 let branch = branch.assume_init();
17558 assert_eq!(branch.from_bus_id, 1);
17559 assert_eq!(branch.to_bus_id, 4);
17560 assert_eq!(branch.reactance_pu, 0.0576);
17561 assert_eq!(branch.rate_a_mva, 250.0);
17562 assert_eq!(branch.tap_ratio, 0.0);
17563 assert_eq!(branch.effective_tap_ratio, 1.0);
17564 assert!(!branch.terminal_charging_is_explicit);
17565 assert_eq!(branch.additional_rating_count, 0);
17566
17567 let mut generator = std::mem::MaybeUninit::<PioBalancedGeneratorView>::uninit();
17568 assert!(pio_balanced_network_generator_at(
17569 network,
17570 0,
17571 generator.as_mut_ptr(),
17572 &mut error,
17573 ));
17574 let generator = generator.assume_init();
17575 assert_eq!(generator.bus_id, 1);
17576 assert_eq!(view_text(generator.energy_source), "other");
17577 assert_eq!(generator.active_power_mw, 72.3);
17578 assert!(generator.has_cost);
17579 assert!(generator.voltage_regulation_on);
17580 assert!(!generator.has_regulating_terminal);
17581 assert!(!generator.has_active_power_control);
17582 assert!(!generator.active_power_control.has_droop_percent);
17583 assert_eq!(generator.cost.model, 2);
17584 assert_eq!(generator.cost.ncost, 3);
17585 assert_eq!(generator.cost.coefficients.len, 3);
17586 let coefficients = std::slice::from_raw_parts(
17587 generator.cost.coefficients.data,
17588 generator.cost.coefficients.len,
17589 );
17590 assert_eq!(coefficients, &[0.11, 5.0, 150.0]);
17591
17592 let mut capability = std::mem::MaybeUninit::<PioGeneratorCapabilityView>::uninit();
17593 assert!(pio_balanced_network_generator_capability_at(
17594 network,
17595 0,
17596 0,
17597 capability.as_mut_ptr(),
17598 &mut error,
17599 ));
17600 let capability = capability.assume_init();
17601 assert_eq!(view_text(capability.name), "pc1");
17602 assert!(capability.has_value);
17603 assert_eq!(capability.value, 0.0);
17604
17605 let mut missing = std::mem::MaybeUninit::<PioBalancedBusView>::uninit();
17606 assert!(!pio_balanced_network_bus_at(
17607 network,
17608 9,
17609 missing.as_mut_ptr(),
17610 &mut error,
17611 ));
17612 assert_eq!(
17613 view_text(pio_error_code(error)),
17614 "BIND.CAPI.INDEX_OUT_OF_RANGE"
17615 );
17616 pio_error_release(error);
17617
17618 pio_balanced_network_release(network);
17619 }
17620 }
17621
17622 #[test]
17623 fn balanced_optional_row_fields_are_explicit() {
17624 unsafe {
17625 let mut network = BalancedNetwork::new("optional-fields", 100.0);
17626 network.buses_mut().push(powerio_tx::Bus::new(
17627 powerio_tx::BusId(1),
17628 powerio_tx::BusType::Ref,
17629 115.0,
17630 ));
17631 network.buses_mut().push(powerio_tx::Bus::new(
17632 powerio_tx::BusId(2),
17633 powerio_tx::BusType::Pq,
17634 115.0,
17635 ));
17636
17637 let mut load = powerio_tx::Load::new(powerio_tx::BusId(2), 6.0, 3.0);
17638 load.voltage_model = Some(powerio_tx::LoadVoltageModel::Zip {
17639 p_constant_power: 3.0,
17640 q_constant_power: 1.5,
17641 p_constant_current: 2.0,
17642 q_constant_current: 1.0,
17643 p_constant_impedance: 1.0,
17644 q_constant_impedance: 0.5,
17645 v_nom: Some(1.0),
17646 load_type: Some(7),
17647 scaling: Some(0.9),
17648 });
17649 network.loads_mut().push(load);
17650
17651 let mut shunt = powerio_tx::Shunt::new(powerio_tx::BusId(2), 0.0, 2.0);
17652 let mut control = powerio_tx::SwitchedShuntControl::new(
17653 powerio_tx::SwitchedShuntMode::Discrete,
17654 1.05,
17655 0.95,
17656 vec![powerio_tx::ShuntBlock::with_admittance(4, 0.25, 0.5)],
17657 );
17658 control.control_bus = Some(powerio_tx::BusId(1));
17659 shunt.control = Some(control);
17660 network.shunts_mut().push(shunt);
17661
17662 let mut branch =
17663 powerio_tx::Branch::new(powerio_tx::BusId(1), powerio_tx::BusId(2), 0.01, 0.1);
17664 branch.name = Some("controlled transformer".to_owned());
17665 branch.charging = Some(powerio_tx::BranchCharging::new(0.01, 0.02, 0.03, 0.04));
17666 branch
17667 .rating_sets
17668 .push(powerio_tx::BranchRatingSet::new("LTE", 175.0));
17669 branch.current_ratings = Some(powerio_tx::BranchCurrentRatings::new(1.0, 2.0, 3.0));
17670 branch.tap = 1.05;
17671 branch.shift = 4.0;
17672 branch.angmin = -30.0;
17673 branch.angmax = 30.0;
17674 let mut transformer_control = powerio_tx::TransformerControl::new(
17675 powerio_tx::TransformerControlMode::DcLineQuantity,
17676 );
17677 transformer_control.enabled = false;
17678 transformer_control.controlled_bus = Some(powerio_tx::BusId(2));
17679 transformer_control.controlled_bus_on_winding_side = true;
17680 transformer_control.regulating_terminal = Some(
17681 serde_json::from_value(serde_json::json!({
17682 "equipment": {
17683 "component_type": "transformer",
17684 "local_id": "controlled-transformer"
17685 },
17686 "terminal": 2
17687 }))
17688 .unwrap(),
17689 );
17690 transformer_control.ntp = 17;
17691 transformer_control.winding_connection_angle = Some(12.5);
17692 branch.control = Some(transformer_control);
17693 network.branches_mut().push(branch);
17694
17695 let mut generator = powerio_tx::Generator::new(powerio_tx::BusId(1));
17696 generator.energy_source = powerio_tx::GeneratorEnergySource::Nuclear;
17697 generator.cost = Some(powerio_tx::GenCost::new(2, 10.0, 20.0, vec![1.0, 2.0]));
17698 generator.caps[8] = Some(25.0);
17699 generator.voltage_regulation_on = false;
17700 generator.regulating_terminal = Some(
17701 serde_json::from_value(serde_json::json!({
17702 "equipment": {
17703 "component_type": "load",
17704 "local_id": "regulated-load"
17705 },
17706 "terminal": 1
17707 }))
17708 .unwrap(),
17709 );
17710 generator.regulated_bus = Some(powerio_tx::BusId(2));
17711 let mut generator_control = powerio_tx::ActivePowerControl::new(true);
17712 generator_control.droop_percent = Some(4.0);
17713 generator_control.participation_factor = Some(0.6);
17714 generator_control.minimum_target_active_power_mw = Some(10.0);
17715 generator_control.maximum_target_active_power_mw = Some(100.0);
17716 generator.active_power_control = Some(generator_control);
17717 network.generators_mut().push(generator);
17718
17719 let mut storage = powerio_tx::Storage::new(powerio_tx::BusId(2));
17720 storage.energy = 2.0;
17721 storage.energy_rating = 10.0;
17722 storage.current_rating = Some(100.0);
17723 let mut storage_control = powerio_tx::ActivePowerControl::new(false);
17724 storage_control.participation_factor = Some(0.4);
17725 storage.active_power_control = Some(storage_control);
17726 network.storage_mut().push(storage);
17727
17728 let module = module_handle(powerio::PioModule::new(PioValue::from(network)));
17729 let value = pio_module_value(module);
17730 let mut error = std::ptr::null_mut();
17731 let network = pio_value_balanced_network(value, &mut error);
17732
17733 let mut load = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
17734 assert!(pio_balanced_network_load_at(
17735 network,
17736 0,
17737 load.as_mut_ptr(),
17738 &mut error
17739 ));
17740 let load = load.assume_init();
17741 assert_eq!(view_text(load.voltage_model.kind), "zip");
17742 assert_eq!(load.voltage_model.p_constant_current_mw, 2.0);
17743 assert!(load.voltage_model.has_nominal_voltage);
17744 assert!(load.voltage_model.has_load_type);
17745 assert!(load.voltage_model.has_scaling);
17746
17747 let mut shunt = std::mem::MaybeUninit::<PioBalancedShuntView>::uninit();
17748 assert!(pio_balanced_network_shunt_at(
17749 network,
17750 0,
17751 shunt.as_mut_ptr(),
17752 &mut error
17753 ));
17754 let shunt = shunt.assume_init();
17755 assert!(!shunt.has_section_count);
17756 assert_eq!(shunt.section_count, 0);
17757 assert!(shunt.has_control);
17758 assert_eq!(view_text(shunt.control_mode), "discrete");
17759 assert_eq!(shunt.control_block_count, 1);
17760 assert!(shunt.has_control_bus);
17761 let mut block = std::mem::MaybeUninit::<PioShuntBlockView>::uninit();
17762 assert!(pio_balanced_network_shunt_block_at(
17763 network,
17764 0,
17765 0,
17766 block.as_mut_ptr(),
17767 &mut error,
17768 ));
17769 let block = block.assume_init();
17770 assert_eq!(block.steps, 4);
17771 assert_eq!(block.conductance_mw, 0.25);
17772 assert_eq!(block.susceptance_mvar, 0.5);
17773
17774 let mut branch = std::mem::MaybeUninit::<PioBalancedBranchView>::uninit();
17775 assert!(pio_balanced_network_branch_at(
17776 network,
17777 0,
17778 branch.as_mut_ptr(),
17779 &mut error,
17780 ));
17781 let branch = branch.assume_init();
17782 assert!(branch.has_name);
17783 assert_eq!(view_text(branch.name), "controlled transformer");
17784 assert!(branch.has_control);
17785 assert_eq!(view_text(branch.control.mode), "dc_line_quantity");
17786 assert!(!branch.control.enabled);
17787 assert!(branch.control.has_controlled_bus);
17788 assert_eq!(branch.control.controlled_bus_id, 2);
17789 assert!(branch.control.controlled_bus_on_winding_side);
17790 assert!(branch.control.has_regulating_terminal);
17791 assert_eq!(branch.control.regulating_terminal.terminal, 2);
17792 assert_eq!(branch.control.tap_position_count, 17);
17793 assert!(branch.control.has_winding_connection_angle);
17794 assert_eq!(branch.control.winding_connection_angle, 12.5);
17795 assert!(branch.terminal_charging_is_explicit);
17796 assert_eq!(branch.from_conductance_pu, 0.01);
17797 assert_eq!(branch.to_susceptance_pu, 0.04);
17798 assert!(branch.has_current_ratings);
17799 assert_eq!(branch.current_rating_c, 3.0);
17800 assert_eq!(branch.additional_rating_count, 1);
17801 let mut rating = std::mem::MaybeUninit::<PioBranchRatingView>::uninit();
17802 assert!(pio_balanced_network_branch_rating_at(
17803 network,
17804 0,
17805 0,
17806 rating.as_mut_ptr(),
17807 &mut error,
17808 ));
17809 let rating = rating.assume_init();
17810 assert_eq!(view_text(rating.name), "LTE");
17811 assert_eq!(rating.rate_mva, 175.0);
17812
17813 let mut generator = std::mem::MaybeUninit::<PioBalancedGeneratorView>::uninit();
17814 assert!(pio_balanced_network_generator_at(
17815 network,
17816 0,
17817 generator.as_mut_ptr(),
17818 &mut error,
17819 ));
17820 let generator = generator.assume_init();
17821 assert!(generator.has_regulated_bus);
17822 assert_eq!(generator.regulated_bus_id, 2);
17823 assert!(!generator.voltage_regulation_on);
17824 assert!(generator.has_regulating_terminal);
17825 assert_eq!(view_text(generator.energy_source), "nuclear");
17826 assert_eq!(
17827 view_text(generator.regulating_terminal.equipment.component_type),
17828 "load"
17829 );
17830 assert_eq!(
17831 view_text(generator.regulating_terminal.equipment.local_id),
17832 "regulated-load"
17833 );
17834 assert_eq!(generator.regulating_terminal.terminal, 1);
17835 assert!(generator.has_active_power_control);
17836 assert!(generator.active_power_control.participate);
17837 assert!(generator.active_power_control.has_droop_percent);
17838 assert_eq!(generator.active_power_control.droop_percent, 4.0);
17839 assert!(generator.active_power_control.has_participation_factor);
17840 assert_eq!(generator.active_power_control.participation_factor, 0.6);
17841 assert!(
17842 generator
17843 .active_power_control
17844 .has_minimum_target_active_power
17845 );
17846 assert_eq!(
17847 generator
17848 .active_power_control
17849 .minimum_target_active_power_mw,
17850 10.0
17851 );
17852 assert!(
17853 generator
17854 .active_power_control
17855 .has_maximum_target_active_power
17856 );
17857 assert_eq!(
17858 generator
17859 .active_power_control
17860 .maximum_target_active_power_mw,
17861 100.0
17862 );
17863 let mut capability = std::mem::MaybeUninit::<PioGeneratorCapabilityView>::uninit();
17864 assert!(pio_balanced_network_generator_capability_at(
17865 network,
17866 0,
17867 8,
17868 capability.as_mut_ptr(),
17869 &mut error,
17870 ));
17871 let capability = capability.assume_init();
17872 assert_eq!(view_text(capability.name), "ramp_30");
17873 assert!(capability.has_value);
17874 assert_eq!(capability.value, 25.0);
17875
17876 let mut storage = std::mem::MaybeUninit::<PioBalancedStorageView>::uninit();
17877 assert!(pio_balanced_network_storage_at(
17878 network,
17879 0,
17880 storage.as_mut_ptr(),
17881 &mut error,
17882 ));
17883 let storage = storage.assume_init();
17884 assert_eq!(storage.energy_mwh, 2.0);
17885 assert_eq!(storage.energy_rating_mwh, 10.0);
17886 assert!(storage.has_current_rating);
17887 assert_eq!(storage.current_rating, 100.0);
17888 assert!(storage.has_active_power_control);
17889 assert!(!storage.active_power_control.participate);
17890 assert!(!storage.active_power_control.has_droop_percent);
17891 assert!(storage.active_power_control.has_participation_factor);
17892 assert_eq!(storage.active_power_control.participation_factor, 0.4);
17893
17894 pio_balanced_network_release(network);
17895 pio_value_release(value);
17896 pio_module_release(module);
17897 }
17898 }
17899
17900 #[test]
17901 fn powsybl_balanced_tables_are_borrowed_without_serialization() {
17902 unsafe {
17903 let mut network = BalancedNetwork::new("powsybl-tables", 100.0);
17904 for (id, kind, kv) in [
17905 (1, powerio_tx::BusType::Ref, 400.0),
17906 (2, powerio_tx::BusType::Pq, 225.0),
17907 (3, powerio_tx::BusType::Pq, 63.0),
17908 ] {
17909 network
17910 .buses_mut()
17911 .push(powerio_tx::Bus::new(powerio_tx::BusId(id), kind, kv));
17912 }
17913
17914 let mut shunt = powerio_tx::Shunt::new(powerio_tx::BusId(2), 0.0, 12.0);
17915 shunt.uid = Some("SH".to_owned());
17916 shunt.section_count = Some(2);
17917 network.shunts_mut().push(shunt);
17918
17919 let mut svc = powerio_tx::StaticVarCompensator::new(powerio_tx::BusId(2), -0.02, 0.03);
17920 svc.uid = Some("SVC".to_owned());
17921 svc.regulating = true;
17922 svc.regulation_mode = powerio_tx::StaticVarCompensatorRegulationMode::ReactivePower;
17923 svc.reactive_power_setpoint_mvar = 12.0;
17924 svc.regulating_terminal = Some(
17925 serde_json::from_value(serde_json::json!({
17926 "equipment": {
17927 "component_type": "static_var_compensator",
17928 "local_id": "SVC"
17929 },
17930 "terminal": 1
17931 }))
17932 .unwrap(),
17933 );
17934 network.static_var_compensators_mut().push(svc);
17935
17936 let mut switch =
17937 powerio_tx::Switch::new(powerio_tx::BusId(1), powerio_tx::BusId(2), true);
17938 switch.uid = Some("SW".to_owned());
17939 switch.thermal_rating = Some(500.0);
17940 switch.pf = Some(75.0);
17941 network.switches_mut().push(switch);
17942
17943 let mut hvdc = powerio_tx::Hvdc::new(powerio_tx::BusId(1), powerio_tx::BusId(2));
17944 hvdc.uid = Some("HVDC".to_owned());
17945 hvdc.resistance_ohm = Some(1.5);
17946 hvdc.nominal_voltage_kv = Some(320.0);
17947 hvdc.converters_mode =
17948 Some(powerio_tx::HvdcConvertersMode::Side1RectifierSide2Inverter);
17949 hvdc.converter1 = Some(
17950 serde_json::from_value(serde_json::json!({
17951 "component": {
17952 "component_type": "hvdc_converter",
17953 "local_id": "C1"
17954 },
17955 "kind": "vsc",
17956 "loss_factor_percent": 1.0,
17957 "voltage_regulator_on": true,
17958 "voltage_setpoint_kv": 400.0
17959 }))
17960 .unwrap(),
17961 );
17962 network.hvdc_mut().push(hvdc);
17963
17964 let mut windings = [
17965 powerio_tx::Winding::new(powerio_tx::BusId(1)),
17966 powerio_tx::Winding::new(powerio_tx::BusId(2)),
17967 powerio_tx::Winding::new(powerio_tx::BusId(3)),
17968 ];
17969 let mut winding_control =
17970 powerio_tx::TransformerControl::new(powerio_tx::TransformerControlMode::ActiveFlow);
17971 winding_control.controlled_bus = Some(powerio_tx::BusId(3));
17972 winding_control.regulating_terminal = Some(
17973 serde_json::from_value(serde_json::json!({
17974 "equipment": {
17975 "component_type": "transformer",
17976 "local_id": "T3"
17977 },
17978 "terminal": 3
17979 }))
17980 .unwrap(),
17981 );
17982 winding_control.ntp = 21;
17983 windings[2].control = Some(winding_control);
17984 let mut transformer = powerio_tx::Transformer3W::new(
17985 windings,
17986 [
17987 powerio_tx::Impedance::new(0.01, 0.1, 100.0),
17988 powerio_tx::Impedance::new(0.02, 0.2, 100.0),
17989 powerio_tx::Impedance::new(0.03, 0.3, 100.0),
17990 ],
17991 );
17992 transformer.uid = Some("T3".to_owned());
17993 transformer.name = Some("three winding".to_owned());
17994 network.transformers_3w_mut().push(transformer);
17995
17996 let mut area = powerio_tx::Area::new(7);
17997 area.uid = Some("A7".to_owned());
17998 area.name = Some("control area".to_owned());
17999 area.area_type = Some("control_area".to_owned());
18000 area.slack_bus = Some(powerio_tx::BusId(1));
18001 area.net_interchange = 12.5;
18002 network.areas_mut().push(area);
18003
18004 let module = module_handle(powerio::PioModule::new(PioValue::from(network)));
18005 let value = pio_module_value(module);
18006 let mut error = std::ptr::null_mut();
18007 let network = pio_value_balanced_network(value, &mut error);
18008 pio_value_release(value);
18009 pio_module_release(module);
18010
18011 assert_eq!(
18012 pio_balanced_network_static_var_compensator_count(network),
18013 1
18014 );
18015 assert_eq!(pio_balanced_network_shunt_count(network), 1);
18016 assert_eq!(pio_balanced_network_switch_count(network), 1);
18017 assert_eq!(pio_balanced_network_hvdc_count(network), 1);
18018 assert_eq!(
18019 pio_balanced_network_three_winding_transformer_count(network),
18020 1
18021 );
18022 assert_eq!(pio_balanced_network_area_count(network), 1);
18023
18024 let mut svc = std::mem::MaybeUninit::<PioBalancedStaticVarCompensatorView>::uninit();
18025 assert!(pio_balanced_network_static_var_compensator_at(
18026 network,
18027 0,
18028 svc.as_mut_ptr(),
18029 &mut error,
18030 ));
18031 let svc = svc.assume_init();
18032 assert_eq!(view_text(svc.component_id), "SVC");
18033 assert_eq!(view_text(svc.regulation_mode), "reactive_power");
18034 assert!(svc.has_regulating_terminal);
18035
18036 let mut shunt = std::mem::MaybeUninit::<PioBalancedShuntView>::uninit();
18037 assert!(pio_balanced_network_shunt_at(
18038 network,
18039 0,
18040 shunt.as_mut_ptr(),
18041 &mut error,
18042 ));
18043 let shunt = shunt.assume_init();
18044 assert!(shunt.has_section_count);
18045 assert_eq!(shunt.section_count, 2);
18046
18047 let mut switch = std::mem::MaybeUninit::<PioBalancedSwitchView>::uninit();
18048 assert!(pio_balanced_network_switch_at(
18049 network,
18050 0,
18051 switch.as_mut_ptr(),
18052 &mut error,
18053 ));
18054 let switch = switch.assume_init();
18055 assert!(switch.closed);
18056 assert_eq!(switch.thermal_rating_mva, 500.0);
18057 assert!(switch.has_from_active_power);
18058
18059 let mut hvdc = std::mem::MaybeUninit::<PioBalancedHvdcView>::uninit();
18060 assert!(pio_balanced_network_hvdc_at(
18061 network,
18062 0,
18063 hvdc.as_mut_ptr(),
18064 &mut error,
18065 ));
18066 let hvdc = hvdc.assume_init();
18067 assert!(hvdc.has_converter1);
18068 assert_eq!(view_text(hvdc.converter1.kind), "vsc");
18069 assert_eq!(hvdc.nominal_voltage_kv, 320.0);
18070
18071 let mut transformer =
18072 std::mem::MaybeUninit::<PioBalancedThreeWindingTransformerView>::uninit();
18073 assert!(pio_balanced_network_three_winding_transformer_at(
18074 network,
18075 0,
18076 transformer.as_mut_ptr(),
18077 &mut error,
18078 ));
18079 assert_eq!(transformer.assume_init().winding_count, 3);
18080 let mut winding =
18081 std::mem::MaybeUninit::<PioThreeWindingTransformerWindingView>::uninit();
18082 assert!(pio_balanced_network_three_winding_transformer_winding_at(
18083 network,
18084 0,
18085 2,
18086 winding.as_mut_ptr(),
18087 &mut error,
18088 ));
18089 let winding = winding.assume_init();
18090 assert_eq!(winding.bus_id, 3);
18091 assert!(winding.has_control);
18092 assert_eq!(view_text(winding.control.mode), "active_flow");
18093 assert!(winding.control.enabled);
18094 assert_eq!(winding.control.controlled_bus_id, 3);
18095 assert!(winding.control.has_regulating_terminal);
18096 assert_eq!(winding.control.regulating_terminal.terminal, 3);
18097 assert_eq!(winding.control.tap_position_count, 21);
18098
18099 let mut area = std::mem::MaybeUninit::<PioBalancedAreaView>::uninit();
18100 assert!(pio_balanced_network_area_at(
18101 network,
18102 0,
18103 area.as_mut_ptr(),
18104 &mut error,
18105 ));
18106 let area = area.assume_init();
18107 assert_eq!(view_text(area.component_id), "A7");
18108 assert_eq!(area.slack_bus_id, 1);
18109
18110 pio_balanced_network_release(network);
18111 }
18112 }
18113
18114 #[test]
18115 fn detailed_connectivity_tables_are_owner_rooted_views() {
18116 const HIERARCHY: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
18117<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">
18118 <iidm:substation id="S" country="US" tso="MISO" geographicalTags="east">
18119 <iidm:voltageLevel id="VL1" nominalV="132" topologyKind="BUS_BREAKER"><iidm:busBreakerTopology><iidm:bus id="B1"/></iidm:busBreakerTopology></iidm:voltageLevel>
18120 <iidm:voltageLevel id="VL2" nominalV="33" topologyKind="BUS_BREAKER"><iidm:busBreakerTopology><iidm:bus id="B2"/></iidm:busBreakerTopology></iidm:voltageLevel>
18121 <iidm:voltageLevel id="VL3" nominalV="11" topologyKind="BUS_BREAKER"><iidm:busBreakerTopology><iidm:bus id="B3"/></iidm:busBreakerTopology></iidm:voltageLevel>
18122 <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">
18123 <iidm:ratioTapChanger2 tapPosition="0" lowTapPosition="0" loadTapChangingCapabilities="false"><iidm:step rho="1.05"/></iidm:ratioTapChanger2>
18124 <iidm:operationalLimitsGroup1 id="normal"><iidm:apparentPowerLimits permanentLimit="90"><iidm:temporaryLimit name="emergency" acceptableDuration="600" value="100"/></iidm:apparentPowerLimits></iidm:operationalLimitsGroup1>
18125 </iidm:threeWindingsTransformer>
18126 </iidm:substation>
18127</iidm:network>"#;
18128
18129 unsafe {
18130 let module = parse_xiidm_text(HIERARCHY);
18131 let mut identified_network = match &PioModule::get(module).unwrap().module.value() {
18132 PioValue::BalancedNetwork(network) => network.clone(),
18133 value => panic!("expected balanced network, got {}", value.type_name()),
18134 };
18135 let identified_details = Arc::make_mut(
18136 identified_network
18137 .detailed_connectivity_mut()
18138 .as_mut()
18139 .unwrap(),
18140 );
18141 identified_details.terminals[0].component =
18142 Some(ComponentId::new("terminal", "terminal-T3-1").unwrap());
18143 identified_details.tap_changers[0].component =
18144 Some(ComponentId::new("tap_changer", "tap-T3-2").unwrap());
18145 let value = pio_module_value(module);
18146 let mut error = std::ptr::null_mut();
18147 let network = pio_value_balanced_network(value, &mut error);
18148 assert!(pio_balanced_network_has_detailed_connectivity(network));
18149 let details = pio_balanced_network_detailed_connectivity(network);
18150 assert!(!details.is_null());
18151 pio_value_release(value);
18152 pio_module_release(module);
18153 pio_balanced_network_release(network);
18154
18155 let mut counts = std::mem::MaybeUninit::<PioDetailedConnectivityCountsView>::uninit();
18156 assert!(pio_detailed_connectivity_counts(
18157 details,
18158 counts.as_mut_ptr(),
18159 &mut error,
18160 ));
18161 let counts = counts.assume_init();
18162 assert_eq!(counts.substations, 1);
18163 assert_eq!(counts.voltage_levels, 3);
18164 assert_eq!(counts.terminals, 3);
18165 assert_eq!(counts.operational_limit_groups, 1);
18166 assert_eq!(counts.tap_changers, 1);
18167
18168 let mut substation = std::mem::MaybeUninit::<PioSubstationView>::uninit();
18169 assert!(pio_detailed_connectivity_substation_at(
18170 details,
18171 0,
18172 substation.as_mut_ptr(),
18173 &mut error,
18174 ));
18175 let substation = substation.assume_init();
18176 assert_eq!(view_text(substation.component.local_id), "S");
18177 assert_eq!(substation.geographical_tag_count, 1);
18178
18179 let mut level = std::mem::MaybeUninit::<PioVoltageLevelView>::uninit();
18180 assert!(pio_detailed_connectivity_voltage_level_at(
18181 details,
18182 0,
18183 level.as_mut_ptr(),
18184 &mut error,
18185 ));
18186 let level = level.assume_init();
18187 assert_eq!(view_text(level.topology_kind), "bus_breaker");
18188 assert_eq!(level.nominal_voltage_kv, 132.0);
18189
18190 let mut terminal = std::mem::MaybeUninit::<PioDetailedTerminalView>::uninit();
18191 assert!(pio_detailed_connectivity_terminal_at(
18192 details,
18193 0,
18194 terminal.as_mut_ptr(),
18195 &mut error,
18196 ));
18197 let terminal = terminal.assume_init();
18198 assert!(terminal.connected);
18199 assert!(!terminal.has_component);
18200
18201 let mut limit = std::mem::MaybeUninit::<PioOperationalLimitGroupView>::uninit();
18202 assert!(pio_detailed_connectivity_operational_limit_group_at(
18203 details,
18204 0,
18205 limit.as_mut_ptr(),
18206 &mut error,
18207 ));
18208 let limit = limit.assume_init();
18209 assert!(limit.has_apparent_power_limits);
18210 assert_eq!(limit.apparent_power_permanent_limit_mva, 90.0);
18211 assert_eq!(limit.apparent_power_temporary_limit_count, 1);
18212
18213 let mut changer = std::mem::MaybeUninit::<PioTapChangerView>::uninit();
18214 assert!(pio_detailed_connectivity_tap_changer_at(
18215 details,
18216 0,
18217 changer.as_mut_ptr(),
18218 &mut error,
18219 ));
18220 let changer = changer.assume_init();
18221 assert!(!changer.has_component);
18222 assert_eq!(view_text(changer.kind), "ratio");
18223 assert!(changer.has_tap_position);
18224 assert_eq!(changer.tap_position, 0);
18225 assert_eq!(changer.step_count, 1);
18226
18227 pio_detailed_connectivity_release(details);
18228
18229 let module = module_handle(powerio::PioModule::new(PioValue::BalancedNetwork(
18230 identified_network,
18231 )));
18232 let value = pio_module_value(module);
18233 let network = pio_value_balanced_network(value, &mut error);
18234 let details = pio_balanced_network_detailed_connectivity(network);
18235 pio_value_release(value);
18236 pio_module_release(module);
18237 pio_balanced_network_release(network);
18238
18239 let mut terminal = std::mem::MaybeUninit::<PioDetailedTerminalView>::uninit();
18240 assert!(pio_detailed_connectivity_terminal_at(
18241 details,
18242 0,
18243 terminal.as_mut_ptr(),
18244 &mut error,
18245 ));
18246 let terminal = terminal.assume_init();
18247 assert!(terminal.has_component);
18248 assert_eq!(view_text(terminal.component.component_type), "terminal");
18249 assert_eq!(view_text(terminal.component.local_id), "terminal-T3-1");
18250
18251 let mut changer = std::mem::MaybeUninit::<PioTapChangerView>::uninit();
18252 assert!(pio_detailed_connectivity_tap_changer_at(
18253 details,
18254 0,
18255 changer.as_mut_ptr(),
18256 &mut error,
18257 ));
18258 let changer = changer.assume_init();
18259 assert!(changer.has_component);
18260 assert_eq!(view_text(changer.component.component_type), "tap_changer");
18261 assert_eq!(view_text(changer.component.local_id), "tap-T3-2");
18262 pio_detailed_connectivity_release(details);
18263
18264 let without_assigned_tap = HIERARCHY.replace(r#" tapPosition="0""#, "");
18265 let module = parse_xiidm_text(&without_assigned_tap);
18266 let value = pio_module_value(module);
18267 let network = pio_value_balanced_network(value, &mut error);
18268 let details = pio_balanced_network_detailed_connectivity(network);
18269 let mut changer = std::mem::MaybeUninit::<PioTapChangerView>::uninit();
18270 assert!(pio_detailed_connectivity_tap_changer_at(
18271 details,
18272 0,
18273 changer.as_mut_ptr(),
18274 &mut error,
18275 ));
18276 let changer = changer.assume_init();
18277 assert!(!changer.has_tap_position);
18278 assert_eq!(changer.tap_position, 0);
18279 pio_detailed_connectivity_release(details);
18280 pio_balanced_network_release(network);
18281 pio_value_release(value);
18282 pio_module_release(module);
18283 }
18284 }
18285
18286 #[test]
18287 fn subnetworks_boundary_lines_and_tie_lines_are_owner_rooted_views() {
18288 const XIIDM: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
18289<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">
18290 <iidm:network id="A" caseDate="2026-01-01T01:00:00Z" forecastDistance="1" sourceFormat="part-a" minimumValidationLevel="STEADY_STATE_HYPOTHESIS">
18291 <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>
18292 </iidm:network>
18293 <iidm:network id="B" caseDate="2026-01-01T02:00:00Z" forecastDistance="2" sourceFormat="part-b" minimumValidationLevel="STEADY_STATE_HYPOTHESIS">
18294 <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>
18295 </iidm:network>
18296 <iidm:tieLine id="TL" boundaryLineId1="DLA" boundaryLineId2="DLB"/>
18297</iidm:network>"#;
18298
18299 unsafe {
18300 let module = parse_xiidm_text(XIIDM);
18301 let value = pio_module_value(module);
18302 let mut error = std::ptr::null_mut();
18303 let network = pio_value_balanced_network(value, &mut error);
18304 let details = pio_balanced_network_detailed_connectivity(network);
18305 pio_value_release(value);
18306 pio_module_release(module);
18307 pio_balanced_network_release(network);
18308
18309 let mut counts = std::mem::MaybeUninit::<PioDetailedConnectivityCountsView>::uninit();
18310 assert!(pio_detailed_connectivity_counts(
18311 details,
18312 counts.as_mut_ptr(),
18313 &mut error,
18314 ));
18315 let counts = counts.assume_init();
18316 assert_eq!(counts.subnetworks, 2);
18317 assert_eq!(counts.boundary_lines, 2);
18318 assert_eq!(counts.tie_lines, 1);
18319
18320 let mut subnetwork = std::mem::MaybeUninit::<PioSubnetworkView>::uninit();
18321 assert!(pio_detailed_connectivity_subnetwork_at(
18322 details,
18323 0,
18324 subnetwork.as_mut_ptr(),
18325 &mut error,
18326 ));
18327 let subnetwork = subnetwork.assume_init();
18328 assert_eq!(view_text(subnetwork.component.local_id), "A");
18329 assert_eq!(view_text(subnetwork.parent.local_id), "Merged");
18330 assert!(subnetwork.case_metadata.has_forecast_distance);
18331 assert_eq!(subnetwork.case_metadata.forecast_distance, 1);
18332 assert_eq!(
18333 view_text(subnetwork.case_metadata.source_model_format),
18334 "part-a"
18335 );
18336 assert!(subnetwork.component_count >= 4);
18337
18338 let mut member = std::mem::MaybeUninit::<PioComponentIdView>::uninit();
18339 assert!(pio_detailed_connectivity_subnetwork_component_at(
18340 details,
18341 0,
18342 0,
18343 member.as_mut_ptr(),
18344 &mut error,
18345 ));
18346 assert!(!view_text(member.assume_init().local_id).is_empty());
18347
18348 let mut boundary = std::mem::MaybeUninit::<PioBoundaryLineView>::uninit();
18349 assert!(pio_detailed_connectivity_boundary_line_at(
18350 details,
18351 0,
18352 boundary.as_mut_ptr(),
18353 &mut error,
18354 ));
18355 let boundary = boundary.assume_init();
18356 assert_eq!(view_text(boundary.component.local_id), "DLA");
18357 assert_eq!(boundary.active_power_setpoint_mw, 5.0);
18358 assert!(boundary.has_generation);
18359 assert!(boundary.generation.voltage_regulation_on);
18360 assert_eq!(boundary.generation.target_active_power_mw, 10.0);
18361 assert!(boundary.generation.has_reactive_limits);
18362 assert_eq!(
18363 view_text(boundary.generation.reactive_limits.kind),
18364 "capability_curve"
18365 );
18366 assert_eq!(boundary.generation.reactive_limits.point_count, 2);
18367
18368 let mut property = std::mem::MaybeUninit::<PioStringPropertyView>::uninit();
18369 assert!(
18370 pio_detailed_connectivity_boundary_line_reactive_limit_property_at(
18371 details,
18372 0,
18373 0,
18374 property.as_mut_ptr(),
18375 &mut error,
18376 )
18377 );
18378 let property = property.assume_init();
18379 assert_eq!(view_text(property.name), "owner");
18380 assert_eq!(view_text(property.value), "RTE");
18381
18382 let mut point = std::mem::MaybeUninit::<PioReactiveCapabilityCurvePointView>::uninit();
18383 assert!(
18384 pio_detailed_connectivity_boundary_line_reactive_capability_point_at(
18385 details,
18386 0,
18387 1,
18388 point.as_mut_ptr(),
18389 &mut error,
18390 )
18391 );
18392 let point = point.assume_init();
18393 assert_eq!(point.active_power_mw, 10.0);
18394 assert_eq!(point.maximum_reactive_power_mvar, 20.0);
18395
18396 let mut tie = std::mem::MaybeUninit::<PioTieLineView>::uninit();
18397 assert!(pio_detailed_connectivity_tie_line_at(
18398 details,
18399 0,
18400 tie.as_mut_ptr(),
18401 &mut error,
18402 ));
18403 let tie = tie.assume_init();
18404 assert_eq!(view_text(tie.component.local_id), "TL");
18405 assert_eq!(view_text(tie.boundary_line1.local_id), "DLA");
18406 assert_eq!(view_text(tie.boundary_line2.local_id), "DLB");
18407 assert!(tie.has_calculation_branch);
18408
18409 pio_detailed_connectivity_release(details);
18410 }
18411 }
18412
18413 #[test]
18414 fn connectivity_node_numbers_are_optional_borrowed_fields() {
18415 const XIIDM: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
18416<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">
18417 <iidm:substation id="S">
18418 <iidm:voltageLevel id="VL" nominalV="110" topologyKind="NODE_BREAKER">
18419 <iidm:nodeBreakerTopology>
18420 <iidm:bus v="110" angle="0" nodes="0,1,2"/>
18421 <iidm:busbarSection id="BBS" node="2"/>
18422 <iidm:switch id="BR" kind="BREAKER" open="false" node1="1" node2="2"/>
18423 <iidm:internalConnection node1="0" node2="1"/>
18424 </iidm:nodeBreakerTopology>
18425 <iidm:generator id="G" energySource="OTHER" minP="0" maxP="10" voltageRegulatorOn="true" targetP="5" node="0"><iidm:minMaxReactiveLimits minQ="-2" maxQ="2"/></iidm:generator>
18426 </iidm:voltageLevel>
18427 </iidm:substation>
18428</iidm:network>"#;
18429
18430 unsafe {
18431 let module = parse_xiidm_text(XIIDM);
18432 let value = pio_module_value(module);
18433 let mut error = std::ptr::null_mut();
18434 let network = pio_value_balanced_network(value, &mut error);
18435 let details = pio_balanced_network_detailed_connectivity(network);
18436
18437 let mut numbers = Vec::new();
18438 for index in 0..3 {
18439 let mut node = std::mem::MaybeUninit::<PioConnectivityNodeView>::uninit();
18440 assert!(pio_detailed_connectivity_node_at(
18441 details,
18442 index,
18443 node.as_mut_ptr(),
18444 &mut error,
18445 ));
18446 let node = node.assume_init();
18447 assert!(node.has_node_number);
18448 assert!(node.has_calculated_bus);
18449 numbers.push(node.node_number);
18450 }
18451 numbers.sort_unstable();
18452 assert_eq!(numbers, [0, 1, 2]);
18453
18454 pio_detailed_connectivity_release(details);
18455 pio_balanced_network_release(network);
18456 pio_value_release(value);
18457 pio_module_release(module);
18458 }
18459 }
18460
18461 #[test]
18462 fn omitted_fields_and_equipment_reactive_limits_are_typed_views() {
18463 const XIIDM: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
18464<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">
18465 <iidm:substation id="S"><iidm:voltageLevel id="VL" nominalV="225" topologyKind="NODE_BREAKER">
18466 <iidm:nodeBreakerTopology><iidm:busbarSection id="BBS" node="0"/></iidm:nodeBreakerTopology>
18467 <iidm:generator id="G" energySource="SOLAR" minP="0" maxP="100" voltageRegulatorOn="true" node="0">
18468 <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>
18469 </iidm:generator>
18470 </iidm:voltageLevel></iidm:substation>
18471</iidm:network>"#;
18472
18473 unsafe {
18474 let module = parse_xiidm_text(XIIDM);
18475 let value = pio_module_value(module);
18476 let mut error = std::ptr::null_mut();
18477 let network = pio_value_balanced_network(value, &mut error);
18478 let details = pio_balanced_network_detailed_connectivity(network);
18479
18480 let mut counts = std::mem::MaybeUninit::<PioDetailedConnectivityCountsView>::uninit();
18481 assert!(pio_detailed_connectivity_counts(
18482 details,
18483 counts.as_mut_ptr(),
18484 &mut error,
18485 ));
18486 let counts = counts.assume_init();
18487 assert_eq!(counts.omitted_fields, 4);
18488 assert_eq!(counts.equipment_reactive_limits, 1);
18489
18490 let mut omitted = std::mem::MaybeUninit::<PioOmittedFieldView>::uninit();
18491 assert!(pio_detailed_connectivity_omitted_field_at(
18492 details,
18493 2,
18494 omitted.as_mut_ptr(),
18495 &mut error,
18496 ));
18497 let omitted = omitted.assume_init();
18498 assert_eq!(view_text(omitted.component.local_id), "G");
18499 assert_eq!(view_text(omitted.field), "voltage_setpoint");
18500
18501 let mut limits = std::mem::MaybeUninit::<PioEquipmentReactiveLimitsView>::uninit();
18502 assert!(pio_detailed_connectivity_equipment_reactive_limits_at(
18503 details,
18504 0,
18505 limits.as_mut_ptr(),
18506 &mut error,
18507 ));
18508 let limits = limits.assume_init();
18509 assert_eq!(view_text(limits.equipment.local_id), "G");
18510 assert_eq!(view_text(limits.limits.kind), "capability_curve");
18511 assert_eq!(limits.limits.property_count, 1);
18512 assert_eq!(limits.limits.point_count, 2);
18513
18514 let mut point = std::mem::MaybeUninit::<PioReactiveCapabilityCurvePointView>::uninit();
18515 assert!(
18516 pio_detailed_connectivity_equipment_reactive_capability_point_at(
18517 details,
18518 0,
18519 0,
18520 point.as_mut_ptr(),
18521 &mut error,
18522 )
18523 );
18524 let point = point.assume_init();
18525 assert_eq!(point.minimum_reactive_power_mvar, -20.0);
18526 assert_eq!(point.property_count, 1);
18527
18528 let mut property = std::mem::MaybeUninit::<PioStringPropertyView>::uninit();
18529 assert!(
18530 pio_detailed_connectivity_equipment_reactive_capability_point_property_at(
18531 details,
18532 0,
18533 0,
18534 0,
18535 property.as_mut_ptr(),
18536 &mut error,
18537 )
18538 );
18539 let property = property.assume_init();
18540 assert_eq!(view_text(property.name), "point");
18541 assert_eq!(view_text(property.value), "first");
18542
18543 let mut generator = std::mem::MaybeUninit::<PioBalancedGeneratorView>::uninit();
18544 assert!(pio_balanced_network_generator_at(
18545 network,
18546 0,
18547 generator.as_mut_ptr(),
18548 &mut error,
18549 ));
18550 assert_eq!(view_text(generator.assume_init().energy_source), "solar");
18551
18552 pio_detailed_connectivity_release(details);
18553 pio_balanced_network_release(network);
18554 pio_value_release(value);
18555 pio_module_release(module);
18556 }
18557 }
18558
18559 #[test]
18560 fn detailed_dc_and_converter_records_are_typed_views() {
18561 const DC: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
18562<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">
18563 <iidm:dcNode id="N1" nominalV="500" v="498"/><iidm:dcNode id="N2" nominalV="500"/>
18564 <iidm:dcSwitch id="S" dcNode1="N1" dcNode2="N2" kind="DISCONNECTOR" open="true" r="0.9"/>
18565 <iidm:dcGround id="G" dcNode="N1" r="0.1" connected="false"/>
18566 <iidm:dcLine id="L" dcNode1="N1" dcNode2="N2" r="4" connected1="true" connected2="true" dcP1="100" dcI1="200" dcP2="-98" dcI2="-195"/>
18567 <iidm:substation id="SUB"><iidm:voltageLevel id="VL" nominalV="400" topologyKind="BUS_BREAKER">
18568 <iidm:busBreakerTopology><iidm:bus id="B1"/><iidm:bus id="B2"/></iidm:busBreakerTopology>
18569 <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>
18570 </iidm:voltageLevel></iidm:substation>
18571</iidm:network>"#;
18572
18573 unsafe {
18574 let module = parse_xiidm_text(DC);
18575 let value = pio_module_value(module);
18576 let mut error = std::ptr::null_mut();
18577 let network = pio_value_balanced_network(value, &mut error);
18578 let details = pio_balanced_network_detailed_connectivity(network);
18579
18580 let mut counts = std::mem::MaybeUninit::<PioDetailedConnectivityCountsView>::uninit();
18581 assert!(pio_detailed_connectivity_counts(
18582 details,
18583 counts.as_mut_ptr(),
18584 &mut error,
18585 ));
18586 let counts = counts.assume_init();
18587 assert_eq!(counts.dc_nodes, 2);
18588 assert_eq!(counts.dc_grounds, 1);
18589 assert_eq!(counts.dc_lines, 1);
18590 assert_eq!(counts.dc_switches, 1);
18591 assert_eq!(counts.voltage_source_converters, 1);
18592
18593 let mut line = std::mem::MaybeUninit::<PioDcEquipmentView>::uninit();
18594 assert!(pio_detailed_connectivity_dc_line_at(
18595 details,
18596 0,
18597 line.as_mut_ptr(),
18598 &mut error,
18599 ));
18600 let line = line.assume_init();
18601 assert_eq!(view_text(line.kind), "line");
18602 assert_eq!(line.terminal_count, 2);
18603 assert_eq!(line.resistance_ohm, 4.0);
18604 assert_eq!(line.terminal1.current_a, 200.0);
18605
18606 let mut converter = std::mem::MaybeUninit::<PioAcDcConverterView>::uninit();
18607 assert!(pio_detailed_connectivity_voltage_source_converter_at(
18608 details,
18609 0,
18610 converter.as_mut_ptr(),
18611 &mut error,
18612 ));
18613 let converter = converter.assume_init();
18614 assert_eq!(view_text(converter.kind), "voltage_source");
18615 assert_eq!(
18616 view_text(converter.control_mode),
18617 "active_power_at_pcc_and_dc_voltage_droop_curve"
18618 );
18619 assert!(converter.voltage_regulator_on);
18620 assert_eq!(converter.idle_loss_mw, 2.0);
18621 assert!(converter.has_switching_loss);
18622 assert_eq!(converter.switching_loss_mw_per_ampere, 0.2);
18623 assert!(converter.has_resistive_loss);
18624 assert!(converter.has_pcc_terminal);
18625 assert_eq!(converter.pcc_terminal.terminal, 1);
18626 assert!(converter.has_reactive_limits);
18627 assert_eq!(
18628 view_text(converter.reactive_limits.kind),
18629 "capability_curve"
18630 );
18631 assert_eq!(converter.droop_curve_segment_count, 1);
18632 assert!(converter.has_droop_curve);
18633
18634 let mut segment = std::mem::MaybeUninit::<PioDroopCurveSegmentView>::uninit();
18635 assert!(
18636 pio_detailed_connectivity_voltage_source_converter_droop_curve_segment_at(
18637 details,
18638 0,
18639 0,
18640 segment.as_mut_ptr(),
18641 &mut error,
18642 )
18643 );
18644 let segment = segment.assume_init();
18645 assert_eq!(segment.minimum_voltage_kv, -100.0);
18646 assert_eq!(segment.maximum_voltage_kv, 100.0);
18647 assert_eq!(segment.k, -5.0);
18648
18649 let mut point = std::mem::MaybeUninit::<PioReactiveCapabilityCurvePointView>::uninit();
18650 assert!(
18651 pio_detailed_connectivity_voltage_source_converter_reactive_capability_point_at(
18652 details,
18653 0,
18654 0,
18655 point.as_mut_ptr(),
18656 &mut error,
18657 )
18658 );
18659 let point = point.assume_init();
18660 assert_eq!(point.active_power_mw, -200.0);
18661 assert_eq!(point.property_count, 1);
18662
18663 let base_network = match &PioModule::get(module).unwrap().module.value() {
18664 PioValue::BalancedNetwork(network) => network.clone(),
18665 value => panic!("expected balanced network, got {}", value.type_name()),
18666 };
18667
18668 pio_detailed_connectivity_release(details);
18669 pio_balanced_network_release(network);
18670 pio_value_release(value);
18671 pio_module_release(module);
18672
18673 let mut empty_curve_network = base_network.clone();
18674 std::sync::Arc::make_mut(
18675 empty_curve_network
18676 .detailed_connectivity_mut()
18677 .as_mut()
18678 .unwrap(),
18679 )
18680 .voltage_source_converters[0]
18681 .droop_curve = Some(serde_json::from_str(r#"{"segments":[]}"#).unwrap());
18682 let mut absent_curve_network = base_network;
18683 std::sync::Arc::make_mut(
18684 absent_curve_network
18685 .detailed_connectivity_mut()
18686 .as_mut()
18687 .unwrap(),
18688 )
18689 .voltage_source_converters[0]
18690 .droop_curve = None;
18691
18692 for (network, has_droop_curve) in
18693 [(empty_curve_network, true), (absent_curve_network, false)]
18694 {
18695 let module = module_handle(powerio::PioModule::new(PioValue::from(network)));
18696 let value = pio_module_value(module);
18697 let network = pio_value_balanced_network(value, &mut error);
18698 let details = pio_balanced_network_detailed_connectivity(network);
18699 let mut converter = std::mem::MaybeUninit::<PioAcDcConverterView>::uninit();
18700 assert!(pio_detailed_connectivity_voltage_source_converter_at(
18701 details,
18702 0,
18703 converter.as_mut_ptr(),
18704 &mut error,
18705 ));
18706 let converter = converter.assume_init();
18707 assert_eq!(converter.droop_curve_segment_count, 0);
18708 assert_eq!(converter.has_droop_curve, has_droop_curve);
18709 pio_detailed_connectivity_release(details);
18710 pio_balanced_network_release(network);
18711 pio_value_release(value);
18712 pio_module_release(module);
18713 }
18714 }
18715 }
18716
18717 #[test]
18718 fn balanced_calculation_instances_expose_typed_inputs() {
18719 unsafe {
18720 let network = case9_network();
18721 let dc_pf = powerio_prob::DcPfInstance::from_network(network.clone()).unwrap();
18722 let dc_pf_module =
18723 module_handle(powerio::PioModule::new(PioValue::DcPfInstance(dc_pf)));
18724 let dc_pf_value = pio_module_value(dc_pf_module);
18725 let mut error = std::ptr::null_mut();
18726 let dc_pf = pio_value_dc_pf_instance(dc_pf_value, &mut error);
18727 assert!(!dc_pf.is_null(), "{}", error_text(error));
18728 assert_eq!(pio_dc_pf_instance_bus_specification_count(dc_pf), 9);
18729 assert_eq!(
18730 view_text(pio_dc_pf_instance_branch_susceptance_formula(dc_pf)),
18731 "series_susceptance"
18732 );
18733 assert!(!pio_calculation_instance_has_initial_point(dc_pf));
18734 assert!(pio_calculation_instance_initial_point(dc_pf, &mut error).is_null());
18735 assert!(error.is_null());
18736
18737 let mut reference = std::mem::MaybeUninit::<PioDcBusSpecificationView>::uninit();
18738 assert!(pio_dc_pf_instance_bus_specification_at(
18739 dc_pf,
18740 0,
18741 reference.as_mut_ptr(),
18742 &mut error,
18743 ));
18744 let reference = reference.assume_init();
18745 assert_eq!(reference.bus_id, 1);
18746 assert_eq!(view_text(reference.kind), "reference");
18747 assert_eq!(reference.voltage_angle_degrees, 0.0);
18748
18749 let mut load_bus = std::mem::MaybeUninit::<PioDcBusSpecificationView>::uninit();
18750 assert!(pio_dc_pf_instance_bus_specification_at(
18751 dc_pf,
18752 4,
18753 load_bus.as_mut_ptr(),
18754 &mut error,
18755 ));
18756 let load_bus = load_bus.assume_init();
18757 assert_eq!(load_bus.bus_id, 5);
18758 assert_eq!(view_text(load_bus.kind), "net_active_power");
18759 assert_eq!(load_bus.net_active_power_mw, -90.0);
18760
18761 pio_calculation_instance_release(dc_pf);
18762 pio_value_release(dc_pf_value);
18763 pio_module_release(dc_pf_module);
18764
18765 let initial = powerio_prob::BalancedOperatingPointBuilder::for_point(network.clone())
18766 .bus_voltage_magnitudes(vec![1.01; network.buses().len()])
18767 .build_point()
18768 .unwrap();
18769 let dc_pf = powerio_prob::DcPfInstance::from_network(network.clone())
18770 .unwrap()
18771 .with_initial_point(initial);
18772 let dc_pf_module =
18773 module_handle(powerio::PioModule::new(PioValue::DcPfInstance(dc_pf)));
18774 let dc_pf_value = pio_module_value(dc_pf_module);
18775 let dc_pf = pio_value_dc_pf_instance(dc_pf_value, &mut error);
18776 let initial = pio_calculation_instance_initial_point(dc_pf, &mut error);
18777 assert!(!initial.is_null(), "{}", error_text(error));
18778 assert_eq!(
18779 view_text(pio_operating_point_type_name(initial)),
18780 "powerio.OperatingPoint<powerio.BalancedNetwork>"
18781 );
18782 pio_calculation_instance_release(dc_pf);
18783 pio_value_release(dc_pf_value);
18784 pio_module_release(dc_pf_module);
18785 let mut initial_vm = 0.0;
18786 assert!(pio_operating_point_get_value(
18787 initial,
18788 c"bus_voltage_magnitude".as_ptr(),
18789 "bus_voltage_magnitude".len(),
18790 c"1".as_ptr(),
18791 1,
18792 &mut initial_vm,
18793 &mut error,
18794 ));
18795 assert_eq!(initial_vm, 1.01);
18796 let initial_network = pio_operating_point_balanced_network(initial, &mut error);
18797 assert_eq!(pio_balanced_network_bus_count(initial_network), 9);
18798 pio_balanced_network_release(initial_network);
18799 pio_operating_point_release(initial);
18800
18801 let ac_pf = powerio_prob::AcPfInstance::from_network(network.clone()).unwrap();
18802 let ac_pf_module =
18803 module_handle(powerio::PioModule::new(PioValue::AcPfInstance(ac_pf)));
18804 let ac_pf_value = pio_module_value(ac_pf_module);
18805 let ac_pf = pio_value_ac_pf_instance(ac_pf_value, &mut error);
18806 let mut pv = std::mem::MaybeUninit::<PioAcBusSpecificationView>::uninit();
18807 assert!(pio_ac_pf_instance_bus_specification_at(
18808 ac_pf,
18809 1,
18810 pv.as_mut_ptr(),
18811 &mut error,
18812 ));
18813 let pv = pv.assume_init();
18814 assert_eq!(pv.bus_id, 2);
18815 assert_eq!(view_text(pv.kind), "pv");
18816 assert_eq!(pv.net_active_power_mw, 163.0);
18817 assert_eq!(pv.voltage_magnitude_pu, 1.025);
18818 pio_calculation_instance_release(ac_pf);
18819 pio_value_release(ac_pf_value);
18820 pio_module_release(ac_pf_module);
18821
18822 let dc_opf = powerio_prob::DcOpfInstance::from_network(network).unwrap();
18823 let dc_opf_module =
18824 module_handle(powerio::PioModule::new(PioValue::DcOpfInstance(dc_opf)));
18825 let dc_opf_value = pio_module_value(dc_opf_module);
18826 let dc_opf = pio_value_dc_opf_instance(dc_opf_value, &mut error);
18827 assert_eq!(pio_calculation_instance_objective_term_count(dc_opf), 1);
18828 let mut term = std::mem::MaybeUninit::<PioObjectiveTermView>::uninit();
18829 assert!(pio_calculation_instance_objective_term_at(
18830 dc_opf,
18831 0,
18832 term.as_mut_ptr(),
18833 &mut error,
18834 ));
18835 assert_eq!(view_text(term.assume_init().kind), "network_generator_cost");
18836 assert_eq!(pio_calculation_instance_active_constraint_count(dc_opf), 4);
18837 let mut constraint = std::mem::MaybeUninit::<PioActiveConstraintView>::uninit();
18838 assert!(pio_calculation_instance_active_constraint_at(
18839 dc_opf,
18840 2,
18841 constraint.as_mut_ptr(),
18842 &mut error,
18843 ));
18844 let constraint = constraint.assume_init();
18845 assert_eq!(view_text(constraint.family), "thermal_limits");
18846 assert_eq!(view_text(constraint.selection), "all");
18847 assert_eq!(constraint.identity_count, 0);
18848
18849 pio_calculation_instance_release(dc_opf);
18850 pio_value_release(dc_opf_value);
18851 pio_module_release(dc_opf_module);
18852 }
18853 }
18854
18855 #[test]
18856 fn dc_opf_preparation_rows_keep_their_owner_alive() {
18857 unsafe {
18858 let source_module = parse_case9();
18859 let mut error = std::ptr::null_mut();
18860 let instance_module = pio_module_to_dc_opf_instance(source_module, &mut error);
18861 assert!(!instance_module.is_null(), "{}", error_text(error));
18862 let value = pio_module_value(instance_module);
18863 let instance = pio_value_dc_opf_instance(value, &mut error);
18864 assert!(!instance.is_null(), "{}", error_text(error));
18865 let units = "per_unit";
18866 let preparation = pio_build_dc_opf_preparation(
18867 instance,
18868 units.as_ptr().cast(),
18869 units.len(),
18870 false,
18871 false,
18872 true,
18873 &mut error,
18874 );
18875 assert!(!preparation.is_null(), "{}", error_text(error));
18876
18877 pio_calculation_instance_release(instance);
18878 pio_value_release(value);
18879 pio_module_release(instance_module);
18880 pio_module_release(source_module);
18881
18882 let mut summary = std::mem::MaybeUninit::<PioDcOpfPreparationView>::uninit();
18883 assert!(pio_dc_opf_preparation_summary(
18884 preparation,
18885 summary.as_mut_ptr(),
18886 &mut error,
18887 ));
18888 let summary = summary.assume_init();
18889 assert_eq!(summary.bus_count, 9);
18890 assert_eq!(summary.generator_count, 3);
18891 assert_eq!(summary.branch_count, 9);
18892 assert_eq!(summary.base_mva, 100.0);
18893 assert_eq!(view_text(summary.units), "per_unit");
18894 assert!(summary.correct_angle_difference_bounds);
18895 assert_eq!(
18896 view_text(summary.branch_susceptance_formula),
18897 "series_susceptance"
18898 );
18899 assert_eq!(view_text(summary.objective), "network_generator_cost");
18900
18901 let reference = pio_dc_opf_preparation_reference_buses(preparation);
18902 assert_eq!(
18903 std::slice::from_raw_parts(reference.data, reference.len),
18904 &[0]
18905 );
18906
18907 let mut bus = std::mem::MaybeUninit::<PioDcOpfBusView>::uninit();
18908 assert!(pio_dc_opf_preparation_bus_at(
18909 preparation,
18910 4,
18911 bus.as_mut_ptr(),
18912 &mut error,
18913 ));
18914 let bus = bus.assume_init();
18915 assert_eq!(bus.bus_id, 5);
18916 assert_eq!(bus.active_power_demand, 0.9);
18917 assert_eq!(bus.shunt_conductance, 0.0);
18918 assert_eq!(bus.phase_shift_injection, 0.0);
18919
18920 let mut generator = std::mem::MaybeUninit::<PioDcOpfGeneratorView>::uninit();
18921 assert!(pio_dc_opf_preparation_generator_at(
18922 preparation,
18923 0,
18924 generator.as_mut_ptr(),
18925 &mut error,
18926 ));
18927 let generator = generator.assume_init();
18928 assert_eq!(generator.bus_index, 0);
18929 assert_eq!(generator.quadratic_cost, 2200.0);
18930 assert!(!generator.has_piecewise_linear_cost);
18931
18932 let mut branch = std::mem::MaybeUninit::<PioDcOpfBranchView>::uninit();
18933 assert!(pio_dc_opf_preparation_branch_at(
18934 preparation,
18935 0,
18936 branch.as_mut_ptr(),
18937 &mut error,
18938 ));
18939 let branch = branch.assume_init();
18940 assert_eq!(branch.from_bus_index, 0);
18941 assert_eq!(branch.to_bus_index, 3);
18942 assert!(branch.susceptance_magnitude > 0.0);
18943 assert_eq!(view_text(branch.source_kind), "branch");
18944 assert_eq!(branch.source_row, 0);
18945 assert!(!branch.has_winding);
18946
18947 let mut missing = std::mem::MaybeUninit::<PioDcOpfBusView>::uninit();
18948 assert!(!pio_dc_opf_preparation_bus_at(
18949 preparation,
18950 summary.bus_count,
18951 missing.as_mut_ptr(),
18952 &mut error,
18953 ));
18954 assert_eq!(
18955 view_text(pio_error_code(error)),
18956 "BIND.CAPI.INDEX_OUT_OF_RANGE"
18957 );
18958 pio_error_release(error);
18959 pio_dc_opf_preparation_release(preparation);
18960 }
18961 }
18962
18963 #[test]
18964 fn ac_opf_preparation_rows_keep_their_owner_alive() {
18965 unsafe {
18966 let source_module = parse_case9();
18967 let mut error = std::ptr::null_mut();
18968 let instance_module = pio_module_to_ac_opf_instance(source_module, &mut error);
18969 assert!(!instance_module.is_null(), "{}", error_text(error));
18970 let value = pio_module_value(instance_module);
18971 let instance = pio_value_ac_opf_instance(value, &mut error);
18972 assert!(!instance.is_null(), "{}", error_text(error));
18973 let units = "per_unit";
18974 let preparation = pio_build_ac_opf_preparation(
18975 instance,
18976 units.as_ptr().cast(),
18977 units.len(),
18978 false,
18979 false,
18980 true,
18981 &mut error,
18982 );
18983 assert!(!preparation.is_null(), "{}", error_text(error));
18984
18985 pio_calculation_instance_release(instance);
18986 pio_value_release(value);
18987 pio_module_release(instance_module);
18988 pio_module_release(source_module);
18989
18990 let mut summary = std::mem::MaybeUninit::<PioAcOpfPreparationView>::uninit();
18991 assert!(pio_ac_opf_preparation_summary(
18992 preparation,
18993 summary.as_mut_ptr(),
18994 &mut error,
18995 ));
18996 let summary = summary.assume_init();
18997 assert_eq!(summary.bus_count, 9);
18998 assert_eq!(summary.generator_count, 3);
18999 assert_eq!(summary.branch_count, 9);
19000 assert_eq!(summary.base_mva, 100.0);
19001 assert_eq!(view_text(summary.units), "per_unit");
19002 assert!(summary.correct_angle_difference_bounds);
19003 assert_eq!(view_text(summary.objective), "network_generator_cost");
19004
19005 let reference = pio_ac_opf_preparation_reference_buses(preparation);
19006 assert_eq!(
19007 std::slice::from_raw_parts(reference.data, reference.len),
19008 &[0]
19009 );
19010
19011 let mut bus = std::mem::MaybeUninit::<PioAcOpfBusView>::uninit();
19012 assert!(pio_ac_opf_preparation_bus_at(
19013 preparation,
19014 4,
19015 bus.as_mut_ptr(),
19016 &mut error,
19017 ));
19018 let bus = bus.assume_init();
19019 assert_eq!(bus.bus_id, 5);
19020 assert_eq!(bus.active_power_demand, 0.9);
19021 assert_eq!(bus.reactive_power_demand, 0.3);
19022 assert_eq!(bus.initial_voltage_angle_radians, 0.0);
19023
19024 let mut generator = std::mem::MaybeUninit::<PioAcOpfGeneratorView>::uninit();
19025 assert!(pio_ac_opf_preparation_generator_at(
19026 preparation,
19027 0,
19028 generator.as_mut_ptr(),
19029 &mut error,
19030 ));
19031 let generator = generator.assume_init();
19032 assert_eq!(generator.bus_index, 0);
19033 assert_eq!(generator.initial_active_power, 0.723);
19034 assert_eq!(generator.quadratic_cost, 2200.0);
19035 assert!(!generator.has_piecewise_linear_cost);
19036
19037 let mut branch = std::mem::MaybeUninit::<PioAcOpfBranchView>::uninit();
19038 assert!(pio_ac_opf_preparation_branch_at(
19039 preparation,
19040 0,
19041 branch.as_mut_ptr(),
19042 &mut error,
19043 ));
19044 let branch = branch.assume_init();
19045 assert_eq!(branch.from_bus_index, 0);
19046 assert_eq!(branch.to_bus_index, 3);
19047 assert_eq!(view_text(branch.source_kind), "branch");
19048 assert_eq!(branch.source_row, 0);
19049 assert!(!branch.has_winding);
19050
19051 let mut missing = std::mem::MaybeUninit::<PioAcOpfBusView>::uninit();
19052 assert!(!pio_ac_opf_preparation_bus_at(
19053 preparation,
19054 summary.bus_count,
19055 missing.as_mut_ptr(),
19056 &mut error,
19057 ));
19058 assert_eq!(
19059 view_text(pio_error_code(error)),
19060 "BIND.CAPI.INDEX_OUT_OF_RANGE"
19061 );
19062 pio_error_release(error);
19063 pio_ac_opf_preparation_release(preparation);
19064 }
19065 }
19066
19067 #[test]
19068 fn ac_opf_storage_view_preserves_fields_and_units() {
19069 unsafe {
19070 let mut network = case9_network();
19071 let mut storage = powerio_tx::Storage::new(powerio_tx::BusId(5));
19072 storage.uid = Some("battery".into());
19073 storage.ps = 12.0;
19074 storage.qs = -3.0;
19075 storage.energy = 80.0;
19076 storage.energy_rating = 120.0;
19077 storage.charge_rating = 25.0;
19078 storage.discharge_rating = 30.0;
19079 storage.charge_efficiency = 0.91;
19080 storage.discharge_efficiency = 0.88;
19081 storage.thermal_rating = 35.0;
19082 storage.qmin = -14.0;
19083 storage.qmax = 16.0;
19084 storage.r = 0.001;
19085 storage.x = 0.002;
19086 storage.p_loss = 0.4;
19087 storage.q_loss = 0.5;
19088 network.storage_mut().push(storage);
19089 let instance = powerio_prob::AcOpfInstance::from_network(network).unwrap();
19090 let close = |actual: f64, expected: f64| assert!((actual - expected).abs() < 1e-12);
19091
19092 for (units, power_scale) in [(Units::PerUnit, 0.01), (Units::Native, 1.0)] {
19093 let prepared = build_ac_opf_preparation(
19094 &instance,
19095 &AcOpfAssemblyOptions::default().with_units(units),
19096 )
19097 .unwrap();
19098 let preparation = PioAcOpfPreparation::new_raw(prepared);
19099 let mut error = std::ptr::null_mut();
19100 let mut summary = std::mem::MaybeUninit::<PioAcOpfPreparationView>::uninit();
19101 assert!(pio_ac_opf_preparation_summary(
19102 preparation,
19103 summary.as_mut_ptr(),
19104 &mut error,
19105 ));
19106 assert_eq!(summary.assume_init().storage_count, 1);
19107
19108 let mut row = std::mem::MaybeUninit::<PioAcOpfStorageView>::uninit();
19109 assert!(pio_ac_opf_preparation_storage_at(
19110 preparation,
19111 0,
19112 row.as_mut_ptr(),
19113 &mut error,
19114 ));
19115 let row = row.assume_init();
19116 assert_eq!(view_text(row.component_id), "battery");
19117 assert_eq!(row.bus_index, 4);
19118 assert_eq!(row.source_row, 0);
19119 close(row.initial_active_power, 12.0 * power_scale);
19120 close(row.initial_reactive_power, -3.0 * power_scale);
19121 close(row.energy, 80.0 * power_scale);
19122 close(row.energy_rating, 120.0 * power_scale);
19123 close(row.charge_rating, 25.0 * power_scale);
19124 close(row.discharge_rating, 30.0 * power_scale);
19125 assert_eq!(row.charge_efficiency, 0.91);
19126 assert_eq!(row.discharge_efficiency, 0.88);
19127 close(row.apparent_power_max, 35.0 * power_scale);
19128 close(row.reactive_power_min, -14.0 * power_scale);
19129 close(row.reactive_power_max, 16.0 * power_scale);
19130 assert_eq!(row.resistance_pu, 0.001);
19131 assert_eq!(row.reactance_pu, 0.002);
19132 close(row.active_power_loss, 0.4 * power_scale);
19133 close(row.reactive_power_loss, 0.5 * power_scale);
19134 assert!(row.in_service);
19135
19136 pio_ac_opf_preparation_release(preparation);
19137 }
19138 }
19139 }
19140
19141 #[test]
19142 fn module_transforms_build_typed_calculation_modules() {
19143 unsafe {
19144 type Transform =
19145 unsafe extern "C" fn(*const PioModule, *mut *mut PioError) -> *mut PioModule;
19146 let source = parse_case9();
19147 let operations: [(Transform, &str, &str); 4] = [
19148 (
19149 pio_module_to_dc_pf_instance,
19150 "powerio.DcPfInstance",
19151 "to_dc_pf_instance",
19152 ),
19153 (
19154 pio_module_to_ac_pf_instance,
19155 "powerio.AcPfInstance",
19156 "to_ac_pf_instance",
19157 ),
19158 (
19159 pio_module_to_dc_opf_instance,
19160 "powerio.DcOpfInstance",
19161 "to_dc_opf_instance",
19162 ),
19163 (
19164 pio_module_to_ac_opf_instance,
19165 "powerio.AcOpfInstance",
19166 "to_ac_opf_instance",
19167 ),
19168 ];
19169 for (operation, expected_type, expected_history) in operations {
19170 let mut error = std::ptr::null_mut();
19171 let derived = operation(source, &mut error);
19172 assert!(!derived.is_null(), "{}", error_text(error));
19173 let value = pio_module_value(derived);
19174 assert!(pio_value_is_type(
19175 value,
19176 expected_type.as_ptr().cast(),
19177 expected_type.len(),
19178 ));
19179 let derived_module = &PioModule::get(derived).unwrap().module;
19180 assert_eq!(
19181 derived_module.history().last().unwrap().name(),
19182 expected_history
19183 );
19184 assert!(derived_module.source().is_none());
19185 pio_value_release(value);
19186 pio_module_release(derived);
19187 }
19188
19189 let mut error = std::ptr::null_mut();
19190 let instance = pio_module_to_dc_pf_instance(source, &mut error);
19191 let invalid = pio_module_to_ac_pf_instance(instance, &mut error);
19192 assert!(invalid.is_null());
19193 assert_eq!(
19194 view_text(pio_error_code(error)),
19195 "REQUEST.MODULE.WRONG_MODEL_KIND"
19196 );
19197 pio_error_release(error);
19198 pio_module_release(instance);
19199 pio_module_release(source);
19200 }
19201 }
19202
19203 #[test]
19204 fn multiconductor_module_transforms_build_typed_calculation_modules() {
19205 unsafe {
19206 let source = parse_bmopf();
19207 let value = pio_module_value(source);
19208 let network_type = "powerio.MulticonductorNetwork";
19209 assert!(pio_value_is_type(
19210 value,
19211 network_type.as_ptr().cast(),
19212 network_type.len(),
19213 ));
19214 pio_value_release(value);
19215
19216 type Transform =
19217 unsafe extern "C" fn(*const PioModule, *mut *mut PioError) -> *mut PioModule;
19218 let operations: [(Transform, &str, &str); 2] = [
19219 (
19220 pio_module_to_mc_ac_pf_instance,
19221 "powerio.McAcPfInstance",
19222 "to_mc_ac_pf_instance",
19223 ),
19224 (
19225 pio_module_to_mc_ac_opf_instance,
19226 "powerio.McAcOpfInstance",
19227 "to_mc_ac_opf_instance",
19228 ),
19229 ];
19230 for (operation, expected_type, expected_history) in operations {
19231 let mut error = std::ptr::null_mut();
19232 let derived = operation(source, &mut error);
19233 assert!(!derived.is_null(), "{}", error_text(error));
19234 let value = pio_module_value(derived);
19235 assert!(pio_value_is_type(
19236 value,
19237 expected_type.as_ptr().cast(),
19238 expected_type.len(),
19239 ));
19240 let derived_module = &PioModule::get(derived).unwrap().module;
19241 assert_eq!(
19242 derived_module.history().last().unwrap().name(),
19243 expected_history
19244 );
19245 assert!(derived_module.source().is_none());
19246 pio_value_release(value);
19247 pio_module_release(derived);
19248 }
19249 pio_module_release(source);
19250 }
19251 }
19252
19253 #[test]
19254 fn geo_layer_derives_a_new_network_module() {
19255 unsafe {
19256 let source_module = parse_case9();
19257 let geojson = br#"{
19258 "type": "FeatureCollection",
19259 "features": [{
19260 "type": "Feature",
19261 "geometry": {
19262 "type": "Point",
19263 "coordinates": [-83.743, 42.281]
19264 },
19265 "properties": {"bus": "1"}
19266 }]
19267 }"#;
19268 let mut error = std::ptr::null_mut();
19269 let source = pio_source_from_memory(
19270 c"case9.geojson".as_ptr(),
19271 "case9.geojson".len(),
19272 geojson.as_ptr(),
19273 geojson.len(),
19274 &mut error,
19275 );
19276 assert!(!source.is_null(), "{}", error_text(error));
19277 let layer = pio_geo_layer_parse(source, &mut error);
19278 pio_source_release(source);
19279 assert!(!layer.is_null(), "{}", error_text(error));
19280
19281 let diagnostics = pio_geo_layer_diagnostics(layer);
19282 assert_eq!(pio_diagnostics_len(diagnostics), 0);
19283 pio_diagnostics_release(diagnostics);
19284
19285 let mut report = std::ptr::null_mut();
19286 let derived = pio_module_apply_geo_layer(source_module, layer, &mut report, &mut error);
19287 assert!(!derived.is_null(), "{}", error_text(error));
19288 assert!(!report.is_null());
19289 assert_eq!(pio_geo_apply_report_matched_buses(report), 1);
19290 assert_eq!(pio_geo_apply_report_matched_branches(report), 0);
19291 assert_eq!(pio_geo_apply_report_unmatched_features(report), 0);
19292 assert_eq!(pio_geo_apply_report_unlocated_buses(report), 8);
19293 assert_eq!(pio_geo_apply_report_unlocated_branches(report), 9);
19294
19295 let note_count = pio_geo_apply_report_note_count(report);
19296 let note = pio_geo_apply_report_note_at(report, note_count, &mut error);
19297 assert_eq!(note.len, 0);
19298 assert_eq!(
19299 view_text(pio_error_code(error)),
19300 "BIND.CAPI.INDEX_OUT_OF_RANGE"
19301 );
19302 pio_error_release(error);
19303 error = std::ptr::null_mut();
19304
19305 let derived_module = &PioModule::get(derived).unwrap().module;
19306 assert!(derived_module.source().is_none());
19307 assert_eq!(
19308 derived_module.history().last().unwrap().name(),
19309 "apply_geo_layer"
19310 );
19311 let PioValue::BalancedNetwork(derived_network) = &derived_module.value() else {
19312 panic!("geo application changed the value type")
19313 };
19314 let derived_location = derived_network
19315 .buses()
19316 .iter()
19317 .find(|bus| bus.id == powerio_tx::BusId(1))
19318 .and_then(|bus| bus.location)
19319 .unwrap();
19320 assert_eq!((derived_location.x, derived_location.y), (-83.743, 42.281));
19321
19322 let original_module = &PioModule::get(source_module).unwrap().module;
19323 let PioValue::BalancedNetwork(original_network) = &original_module.value() else {
19324 unreachable!()
19325 };
19326 assert!(
19327 original_network
19328 .buses()
19329 .iter()
19330 .find(|bus| bus.id == powerio_tx::BusId(1))
19331 .unwrap()
19332 .location
19333 .is_none()
19334 );
19335
19336 let calculation = pio_module_to_dc_pf_instance(source_module, &mut error);
19337 assert!(!calculation.is_null(), "{}", error_text(error));
19338 let rejected =
19339 pio_module_apply_geo_layer(calculation, layer, std::ptr::null_mut(), &mut error);
19340 assert!(rejected.is_null());
19341 assert_eq!(
19342 view_text(pio_error_code(error)),
19343 "REQUEST.MODULE.WRONG_MODEL_KIND"
19344 );
19345 pio_error_release(error);
19346
19347 pio_module_release(calculation);
19348 pio_geo_apply_report_release(report);
19349 pio_geo_layer_release(layer);
19350 pio_module_release(derived);
19351 pio_module_release(source_module);
19352 }
19353 }
19354
19355 #[test]
19356 fn aggregate_bus_active_demand_uses_explicit_proportional_allocation() {
19357 unsafe {
19358 let mut network = case9_network();
19359 let mut second = network
19360 .loads()
19361 .iter()
19362 .find(|load| load.bus == powerio_tx::BusId::new(5))
19363 .unwrap()
19364 .clone();
19365 second.p = 30.0;
19366 second.q = 10.0;
19367 second.uid = Some("extra-load-at-bus-5".to_owned());
19368 network.loads_mut().push(second);
19369 let source = module_handle(powerio::PioModule::new(PioValue::BalancedNetwork(network)));
19370 let mut error = std::ptr::null_mut();
19371 let module = pio_module_to_dc_pf_instance(source, &mut error);
19372 assert!(!module.is_null(), "{}", error_text(error));
19373 pio_module_release(source);
19374
19375 let value_before = pio_module_value(module);
19376 let instance_before = pio_value_dc_pf_instance(value_before, &mut error);
19377 let network_before =
19378 pio_calculation_instance_balanced_network(instance_before, &mut error);
19379
19380 let power = pio_active_power_from_watts(240_000_000.0);
19381 let bad_rule = "first_load";
19382 let bad_report = pio_apply_bus_load_active_power(
19383 module,
19384 5,
19385 power,
19386 bad_rule.as_ptr().cast(),
19387 bad_rule.len(),
19388 &mut error,
19389 );
19390 assert!(bad_report.is_null());
19391 assert_eq!(
19392 view_text(pio_error_code(error)),
19393 "REQUEST.CAPI.ALLOCATION_UNKNOWN"
19394 );
19395 pio_error_release(error);
19396 error = std::ptr::null_mut();
19397
19398 let rule = "proportional_to_current_active_power";
19399 let report = pio_apply_bus_load_active_power(
19400 module,
19401 5,
19402 power,
19403 rule.as_ptr().cast(),
19404 rule.len(),
19405 &mut error,
19406 );
19407 assert!(!report.is_null(), "{}", error_text(error));
19408 assert_eq!(pio_update_report_len(report), 2);
19409 assert!(!pio_update_report_connectivity_changed(report));
19410
19411 let value_after = pio_module_value(module);
19412 let instance_after = pio_value_dc_pf_instance(value_after, &mut error);
19413 let network_after =
19414 pio_calculation_instance_balanced_network(instance_after, &mut error);
19415 let mut first_before = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
19416 let mut second_before = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
19417 let mut first_after = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
19418 let mut second_after = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
19419 assert!(pio_balanced_network_load_at(
19420 network_before,
19421 0,
19422 first_before.as_mut_ptr(),
19423 &mut error,
19424 ));
19425 assert!(pio_balanced_network_load_at(
19426 network_before,
19427 3,
19428 second_before.as_mut_ptr(),
19429 &mut error,
19430 ));
19431 assert!(pio_balanced_network_load_at(
19432 network_after,
19433 0,
19434 first_after.as_mut_ptr(),
19435 &mut error,
19436 ));
19437 assert!(pio_balanced_network_load_at(
19438 network_after,
19439 3,
19440 second_after.as_mut_ptr(),
19441 &mut error,
19442 ));
19443 assert_eq!(first_before.assume_init().p_mw, 90.0);
19444 assert_eq!(second_before.assume_init().p_mw, 30.0);
19445 assert_eq!(first_after.assume_init().p_mw, 180.0);
19446 assert_eq!(second_after.assume_init().p_mw, 60.0);
19447 assert_eq!(
19448 PioModule::get(module)
19449 .unwrap()
19450 .module
19451 .history()
19452 .last()
19453 .unwrap()
19454 .name(),
19455 "apply_updates"
19456 );
19457
19458 pio_balanced_network_release(network_before);
19459 pio_calculation_instance_release(instance_before);
19460 pio_value_release(value_before);
19461 pio_balanced_network_release(network_after);
19462 pio_calculation_instance_release(instance_after);
19463 pio_value_release(value_after);
19464 pio_update_report_release(report);
19465 pio_active_power_release(power);
19466 pio_module_release(module);
19467 }
19468 }
19469
19470 #[test]
19471 fn scuc_instance_exposes_typed_scheduling_inputs() {
19472 unsafe {
19473 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
19474 .join("../tests/data/goc3/goc3_small.json");
19475 let source = std::fs::read_to_string(path).unwrap().replacen(
19476 "\"startup_states\": []",
19477 "\"startup_states\": [[2.0, 3.0]]",
19478 1,
19479 );
19480 let parsed =
19481 goc3_instance(Source::from_memory("goc3_small.json", source.into_bytes()).unwrap());
19482 let module = module_handle(powerio::PioModule::new(PioValue::AcScucInstance(parsed)));
19483 let value = pio_module_value(module);
19484 let mut error = std::ptr::null_mut();
19485 let instance = pio_value_ac_scuc_instance(value, &mut error);
19486 assert!(!instance.is_null(), "{}", error_text(error));
19487 pio_value_release(value);
19488 pio_module_release(module);
19489
19490 let mut dimensions = std::mem::MaybeUninit::<PioScucDimensionsView>::uninit();
19491 assert!(pio_ac_scuc_instance_dimensions(
19492 instance,
19493 dimensions.as_mut_ptr(),
19494 &mut error,
19495 ));
19496 let dimensions = dimensions.assume_init();
19497 assert_eq!(dimensions.period_count, 2);
19498 assert_eq!(dimensions.device_count, 2);
19499 assert_eq!(dimensions.producer_count, 1);
19500 assert_eq!(dimensions.consumer_count, 1);
19501 assert_eq!(dimensions.shunt_count, 1);
19502 assert_eq!(dimensions.branch_switching_cost_count, 3);
19503 assert_eq!(dimensions.transformer_control_count, 1);
19504 assert_eq!(dimensions.active_reserve_zone_count, 1);
19505 assert_eq!(dimensions.reactive_reserve_zone_count, 1);
19506 assert_eq!(dimensions.contingency_count, 3);
19507
19508 let durations = pio_ac_scuc_instance_interval_durations(instance);
19509 assert_eq!(
19510 std::slice::from_raw_parts(durations.data, durations.len),
19511 &[1.0, 1.0]
19512 );
19513
19514 let mut violation = std::mem::MaybeUninit::<PioScucViolationCostView>::uninit();
19515 assert!(pio_ac_scuc_instance_violation_costs(
19516 instance,
19517 violation.as_mut_ptr(),
19518 &mut error,
19519 ));
19520 let violation = violation.assume_init();
19521 assert_eq!(violation.active_power_balance, 1.0);
19522 assert_eq!(violation.reactive_power_balance, 1.0);
19523 assert_eq!(violation.branch_thermal_limit, 1.0);
19524 assert_eq!(violation.energy_requirement, 1.0);
19525
19526 let mut producer = std::mem::MaybeUninit::<PioScucDeviceView>::uninit();
19527 assert!(pio_ac_scuc_instance_device_at(
19528 instance,
19529 0,
19530 producer.as_mut_ptr(),
19531 &mut error,
19532 ));
19533 let producer = producer.assume_init();
19534 assert_eq!(view_text(producer.id.component_type), "generator");
19535 assert_eq!(view_text(producer.id.local_id), "sd_00");
19536 assert_eq!(view_text(producer.kind), "producer");
19537 assert!(producer.initial_on_status);
19538 assert_eq!(producer.minimum_up_time_hours, 1.0);
19539 assert_eq!(producer.minimum_down_time_hours, 1.0);
19540 assert_eq!(producer.initial_commitment.accumulated_up_time_hours, 4.0);
19541 assert_eq!(producer.initial_commitment.accumulated_down_time_hours, 0.0);
19542 assert_eq!(producer.startup_cost_adjustment_count, 1);
19543 assert_eq!(producer.startup_limit_count, 1);
19544 assert_eq!(producer.energy_upper_bound_count, 1);
19545 assert_eq!(producer.energy_lower_bound_count, 1);
19546 assert_eq!(producer.period_count, 2);
19547
19548 let uid = "sd_00";
19549 let mut found_device = std::mem::MaybeUninit::<PioScucDeviceView>::uninit();
19550 assert!(pio_ac_scuc_instance_device_get(
19551 instance,
19552 uid.as_ptr().cast(),
19553 uid.len(),
19554 found_device.as_mut_ptr(),
19555 &mut error,
19556 ));
19557 assert_eq!(view_text(found_device.assume_init().id.local_id), "sd_00");
19558
19559 let mut adjustment =
19560 std::mem::MaybeUninit::<PioScucStartupCostAdjustmentView>::uninit();
19561 assert!(pio_ac_scuc_instance_device_startup_cost_adjustment_at(
19562 instance,
19563 0,
19564 0,
19565 adjustment.as_mut_ptr(),
19566 &mut error,
19567 ));
19568 assert_eq!(adjustment.assume_init().maximum_down_time_hours, 3.0);
19569
19570 let mut limit = std::mem::MaybeUninit::<PioScucStartupLimitView>::uninit();
19571 assert!(pio_ac_scuc_instance_device_startup_limit_at(
19572 instance,
19573 0,
19574 0,
19575 limit.as_mut_ptr(),
19576 &mut error,
19577 ));
19578 assert_eq!(limit.assume_init().maximum_startups, 1);
19579
19580 let mut energy = std::mem::MaybeUninit::<PioScucEnergyRequirementView>::uninit();
19581 assert!(pio_ac_scuc_instance_device_energy_upper_bound_at(
19582 instance,
19583 0,
19584 0,
19585 energy.as_mut_ptr(),
19586 &mut error,
19587 ));
19588 assert_eq!(energy.assume_init().energy_pu, 9.0);
19589 assert!(pio_ac_scuc_instance_device_energy_lower_bound_at(
19590 instance,
19591 0,
19592 0,
19593 energy.as_mut_ptr(),
19594 &mut error,
19595 ));
19596 assert_eq!(energy.assume_init().energy_pu, 1.0);
19597
19598 let mut period = std::mem::MaybeUninit::<PioScucDevicePeriodView>::uninit();
19599 assert!(pio_ac_scuc_instance_device_period_at(
19600 instance,
19601 0,
19602 0,
19603 period.as_mut_ptr(),
19604 &mut error,
19605 ));
19606 let period = period.assume_init();
19607 assert!(period.on_status_min);
19608 assert!(period.on_status_max);
19609 assert_eq!(period.active_power_min_pu, 2.0);
19610 assert_eq!(period.active_power_max_pu, 5.0);
19611 assert_eq!(period.energy_cost_block_count, 1);
19612
19613 let mut cost_block = std::mem::MaybeUninit::<PioScucEnergyCostBlockView>::uninit();
19614 assert!(pio_ac_scuc_instance_device_energy_cost_block_at(
19615 instance,
19616 0,
19617 0,
19618 0,
19619 cost_block.as_mut_ptr(),
19620 &mut error,
19621 ));
19622 let cost_block = cost_block.assume_init();
19623 assert_eq!(cost_block.marginal_cost, 10.0);
19624 assert_eq!(cost_block.block_size_pu, 5.0);
19625
19626 let mut shunt = std::mem::MaybeUninit::<PioScucShuntView>::uninit();
19627 assert!(pio_ac_scuc_instance_shunt_at(
19628 instance,
19629 0,
19630 shunt.as_mut_ptr(),
19631 &mut error,
19632 ));
19633 let shunt = shunt.assume_init();
19634 assert_eq!(view_text(shunt.id.local_id), "sh_00");
19635 assert_eq!(shunt.conductance_per_step_pu, 0.0);
19636 assert_eq!(shunt.susceptance_per_step_pu, 3.0);
19637 assert_eq!(
19638 (shunt.step_min, shunt.initial_step, shunt.step_max),
19639 (0, 1, 4)
19640 );
19641 let shunt_uid = "sh_00";
19642 let mut found_shunt = std::mem::MaybeUninit::<PioScucShuntView>::uninit();
19643 assert!(pio_ac_scuc_instance_shunt_get(
19644 instance,
19645 shunt_uid.as_ptr().cast(),
19646 shunt_uid.len(),
19647 found_shunt.as_mut_ptr(),
19648 &mut error,
19649 ));
19650 assert_eq!(view_text(found_shunt.assume_init().id.local_id), "sh_00");
19651
19652 let mut switching = std::mem::MaybeUninit::<PioScucBranchSwitchingCostView>::uninit();
19653 assert!(pio_ac_scuc_instance_branch_switching_cost_at(
19654 instance,
19655 0,
19656 switching.as_mut_ptr(),
19657 &mut error,
19658 ));
19659 let switching = switching.assume_init();
19660 assert_eq!(view_text(switching.id.component_type), "branch");
19661 assert_eq!(view_text(switching.id.local_id), "acl_00");
19662
19663 let mut control = std::mem::MaybeUninit::<PioScucTransformerControlView>::uninit();
19664 assert!(pio_ac_scuc_instance_transformer_control_at(
19665 instance,
19666 0,
19667 control.as_mut_ptr(),
19668 &mut error,
19669 ));
19670 let control = control.assume_init();
19671 assert_eq!(view_text(control.id.component_type), "transformer");
19672 assert_eq!(view_text(control.id.local_id), "xf_00");
19673 assert_eq!((control.tap_ratio_min, control.tap_ratio_max), (1.0, 1.0));
19674
19675 let mut active_zone = std::mem::MaybeUninit::<PioScucActiveReserveZoneView>::uninit();
19676 assert!(pio_ac_scuc_instance_active_reserve_zone_at(
19677 instance,
19678 0,
19679 active_zone.as_mut_ptr(),
19680 &mut error,
19681 ));
19682 assert_eq!(active_zone.assume_init().bus_count, 2);
19683 let mut active_period =
19684 std::mem::MaybeUninit::<PioScucActiveReservePeriodView>::uninit();
19685 assert!(pio_ac_scuc_instance_active_reserve_zone_period_at(
19686 instance,
19687 0,
19688 0,
19689 active_period.as_mut_ptr(),
19690 &mut error,
19691 ));
19692 assert_eq!(active_period.assume_init().ramping_up_requirement_pu, 0.0);
19693 let mut reserve_bus = std::mem::MaybeUninit::<PioComponentIdView>::uninit();
19694 assert!(pio_ac_scuc_instance_active_reserve_zone_bus_at(
19695 instance,
19696 0,
19697 0,
19698 reserve_bus.as_mut_ptr(),
19699 &mut error,
19700 ));
19701 assert_eq!(view_text(reserve_bus.assume_init().local_id), "bus_00");
19702
19703 let mut reactive_zone =
19704 std::mem::MaybeUninit::<PioScucReactiveReserveZoneView>::uninit();
19705 assert!(pio_ac_scuc_instance_reactive_reserve_zone_at(
19706 instance,
19707 0,
19708 reactive_zone.as_mut_ptr(),
19709 &mut error,
19710 ));
19711 assert_eq!(reactive_zone.assume_init().bus_count, 2);
19712 let mut reactive_period =
19713 std::mem::MaybeUninit::<PioScucReactiveReservePeriodView>::uninit();
19714 assert!(pio_ac_scuc_instance_reactive_reserve_zone_period_at(
19715 instance,
19716 0,
19717 0,
19718 reactive_period.as_mut_ptr(),
19719 &mut error,
19720 ));
19721 assert_eq!(
19722 reactive_period.assume_init().reactive_up_requirement_pu,
19723 0.0
19724 );
19725 assert!(pio_ac_scuc_instance_reactive_reserve_zone_bus_at(
19726 instance,
19727 0,
19728 1,
19729 reserve_bus.as_mut_ptr(),
19730 &mut error,
19731 ));
19732 assert_eq!(view_text(reserve_bus.assume_init().local_id), "bus_01");
19733
19734 let mut contingency = std::mem::MaybeUninit::<PioScucContingencyView>::uninit();
19735 assert!(pio_ac_scuc_instance_contingency_at(
19736 instance,
19737 0,
19738 contingency.as_mut_ptr(),
19739 &mut error,
19740 ));
19741 let first_contingency = contingency.assume_init();
19742 assert_eq!(
19743 view_text(first_contingency.id.component_type),
19744 "contingency"
19745 );
19746 assert_eq!(view_text(first_contingency.id.local_id), "ctg_00");
19747 let contingency_uid = "ctg_02";
19748 assert!(pio_ac_scuc_instance_contingency_get(
19749 instance,
19750 contingency_uid.as_ptr().cast(),
19751 contingency_uid.len(),
19752 contingency.as_mut_ptr(),
19753 &mut error,
19754 ));
19755 assert_eq!(view_text(contingency.assume_init().id.local_id), "ctg_02");
19756 let mut component = std::mem::MaybeUninit::<PioScucContingencyComponentView>::uninit();
19757 assert!(pio_ac_scuc_instance_contingency_component_at(
19758 instance,
19759 0,
19760 0,
19761 component.as_mut_ptr(),
19762 &mut error,
19763 ));
19764 let component = component.assume_init();
19765 assert_eq!(view_text(component.id.component_type), "branch");
19766 assert_eq!(view_text(component.id.local_id), "acl_00");
19767
19768 pio_calculation_instance_release(instance);
19769 }
19770 }
19771
19772 #[test]
19773 fn one_parse_reads_a_goc3_problem_and_solution_directory() {
19774 unsafe {
19775 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/data/goc3");
19776 let path = path.to_string_lossy();
19777 let mut error = std::ptr::null_mut();
19778 let source = pio_source_open(path.as_ptr().cast(), path.len(), &mut error);
19779 assert!(!source.is_null(), "{}", error_text(error));
19780 let module = pio_parse(source, std::ptr::null(), 0, &mut error);
19781 pio_source_release(source);
19782 assert!(!module.is_null(), "{}", error_text(error));
19783
19784 let inner = PioModule::get(module).unwrap();
19785 assert_eq!(inner.module.value().type_name(), "powerio.AcScucSolution");
19786 assert_eq!(inner.module.sources().len(), 2);
19787
19788 let value = pio_module_value(module);
19789 let solution = pio_value_ac_scuc_solution(value, &mut error);
19790 pio_value_release(value);
19791 assert!(!solution.is_null(), "{}", error_text(error));
19792 assert_eq!(pio_ac_scuc_solution_time_count(solution), 2);
19793 let quantity = "bus_voltage_magnitude";
19794 let row = pio_ac_scuc_solution_get_values_at(
19795 solution,
19796 quantity.as_ptr().cast(),
19797 quantity.len(),
19798 0,
19799 &mut error,
19800 );
19801 assert!(!row.is_null(), "{}", error_text(error));
19802 let values = pio_vector_values(row);
19803 assert_eq!(
19804 std::slice::from_raw_parts(values.data, values.len),
19805 &[1.0, 0.99]
19806 );
19807 pio_vector_release(row);
19808
19809 for quantity in [
19810 "bus_voltage_magnitude",
19811 "bus_voltage_angle",
19812 "shunt_step",
19813 "ac_line_on_status",
19814 "transformer_tap_ratio",
19815 "transformer_phase_shift",
19816 "transformer_on_status",
19817 "dc_line_from_active_power",
19818 "dc_line_from_reactive_power",
19819 "dc_line_to_reactive_power",
19820 "device_on_status",
19821 "device_startup_status",
19822 "device_shutdown_status",
19823 "device_active_power",
19824 "device_reactive_power",
19825 "regulation_reserve_up",
19826 "regulation_reserve_down",
19827 "synchronized_reserve",
19828 "nonsynchronized_reserve",
19829 "ramping_reserve_up_online",
19830 "ramping_reserve_up_offline",
19831 "ramping_reserve_down_online",
19832 "ramping_reserve_down_offline",
19833 "reactive_reserve_up",
19834 "reactive_reserve_down",
19835 ] {
19836 let vector = pio_ac_scuc_solution_get_values_at(
19837 solution,
19838 quantity.as_ptr().cast(),
19839 quantity.len(),
19840 0,
19841 &mut error,
19842 );
19843 assert!(!vector.is_null(), "{quantity}: {}", error_text(error));
19844 pio_vector_release(vector);
19845 }
19846
19847 pio_calculation_solution_release(solution);
19848 pio_module_release(module);
19849 }
19850 }
19851
19852 #[test]
19853 fn one_parse_and_one_emit_cover_powsybl_formats() {
19854 unsafe {
19855 let module = parse_case9();
19856 let directory = tempfile::tempdir().unwrap();
19857
19858 for (format, output_name) in [
19859 ("xiidm", "case9.xiidm"),
19860 ("cgmes", "case9-cgmes"),
19861 ("psse-rawx", "case9.rawx"),
19862 ("ucte", "case9.uct"),
19863 ] {
19864 let output = directory.path().join(output_name);
19865 let output_text = output.to_string_lossy();
19866 let mut error = std::ptr::null_mut();
19867 let destination = pio_destination_path(
19868 output_text.as_ptr().cast(),
19869 output_text.len(),
19870 &mut error,
19871 );
19872 assert!(!destination.is_null(), "{}", error_text(error));
19873 let result = pio_emit(
19874 module,
19875 format.as_ptr().cast(),
19876 format.len(),
19877 destination,
19878 &mut error,
19879 );
19880 pio_destination_release(destination);
19881 assert!(!result.is_null(), "{format}: {}", error_text(error));
19882 pio_emit_result_release(result);
19883
19884 let source =
19885 pio_source_open(output_text.as_ptr().cast(), output_text.len(), &mut error);
19886 assert!(!source.is_null(), "{format}: {}", error_text(error));
19887 let reparsed = pio_parse(source, std::ptr::null(), 0, &mut error);
19888 pio_source_release(source);
19889 assert!(!reparsed.is_null(), "{format}: {}", error_text(error));
19890 assert_eq!(
19891 PioModule::get(reparsed).unwrap().module.value().type_name(),
19892 "powerio.BalancedNetwork"
19893 );
19894 pio_module_release(reparsed);
19895 }
19896
19897 pio_module_release(module);
19898 }
19899 }
19900
19901 #[test]
19902 fn scuc_solution_exposes_every_device_status_and_offline_reserve() {
19903 unsafe {
19904 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
19905 .join("../tests/data/goc3/goc3_small.json");
19906 let parsed = goc3_instance(Source::open(path).unwrap());
19907 let mut outputs = powerio_prob::ScucDeviceOutputs::default();
19908 outputs.startup_status = vec![vec![false, true], vec![true, false]];
19909 outputs.shutdown_status = vec![vec![true, false], vec![false, true]];
19910 outputs.p_ramp_res_up_offline = vec![vec![1.25, 1.75], vec![1.5, 2.0]];
19911 outputs.p_ramp_res_down_offline = vec![vec![2.25, 2.75], vec![2.5, 3.0]];
19912 let solution = powerio_prob::AcScucSolution::new(
19913 Arc::new(parsed),
19914 powerio_prob::Termination::Converged,
19915 powerio_prob::ScucNetworkOutputs::default(),
19916 outputs,
19917 None,
19918 )
19919 .unwrap();
19920 let module = module_handle(powerio::PioModule::new(PioValue::AcScucSolution(solution)));
19921 let value = pio_module_value(module);
19922 let mut error = std::ptr::null_mut();
19923 let solution = pio_value_ac_scuc_solution(value, &mut error);
19924 assert!(!solution.is_null(), "{}", error_text(error));
19925 pio_value_release(value);
19926 pio_module_release(module);
19927
19928 for (quantity, expected) in [
19929 ("device_startup_status", [0.0, 1.0]),
19930 ("device_shutdown_status", [1.0, 0.0]),
19931 ("ramping_reserve_up_offline", [1.25, 1.75]),
19932 ("ramping_reserve_down_offline", [2.25, 2.75]),
19933 ] {
19934 let vector = pio_ac_scuc_solution_get_values_at(
19935 solution,
19936 quantity.as_ptr().cast(),
19937 quantity.len(),
19938 0,
19939 &mut error,
19940 );
19941 assert!(!vector.is_null(), "{}", error_text(error));
19942 let view = pio_vector_values(vector);
19943 assert_eq!(view.len, 2);
19944 assert_eq!(std::slice::from_raw_parts(view.data, view.len), &expected);
19945 pio_vector_release(vector);
19946 }
19947
19948 pio_calculation_solution_release(solution);
19949 }
19950 }
19951
19952 #[test]
19953 fn update_detaches_from_retained_value_views() {
19954 unsafe {
19955 let module = parse_case9();
19956 let value = pio_module_value(module);
19957 let mut error = std::ptr::null_mut();
19958 let old_network = pio_value_balanced_network(value, &mut error);
19959 pio_value_release(value);
19960 assert!(!old_network.is_null(), "{}", error_text(error));
19961
19962 let old = PioBalancedNetwork::get(old_network)
19963 .and_then(BalancedNetworkInner::network)
19964 .unwrap();
19965 let load = &old.loads()[0];
19966 let old_active_power = load.p;
19967 let local_id = load.uid.as_deref().unwrap().to_owned();
19968
19969 let component = pio_component_id_new(
19970 c"load".as_ptr(),
19971 4,
19972 local_id.as_ptr().cast(),
19973 local_id.len(),
19974 &mut error,
19975 );
19976 let replacement = pio_active_power_from_megawatts(old_active_power + 1.0);
19977 let operating = pio_operating_point_update_set_load_active_power(
19978 component,
19979 std::ptr::null(),
19980 0,
19981 replacement,
19982 &mut error,
19983 );
19984 let update = pio_calculation_update_from_operating_point(operating, &mut error);
19985 let updates = [update.cast_const()];
19986 let report = pio_apply_updates(module, updates.as_ptr(), updates.len(), &mut error);
19987 assert!(!report.is_null(), "{}", error_text(error));
19988 assert_eq!(pio_update_report_len(report), 1);
19989 assert!(!pio_update_report_connectivity_changed(report));
19990
19991 let new_value = pio_module_value(module);
19992 let new_network = pio_value_balanced_network(new_value, &mut error);
19993 let new = PioBalancedNetwork::get(new_network)
19994 .and_then(BalancedNetworkInner::network)
19995 .unwrap();
19996 assert_eq!(new.loads()[0].p, old_active_power + 1.0);
19997 assert_eq!(
19998 PioBalancedNetwork::get(old_network)
19999 .and_then(BalancedNetworkInner::network)
20000 .unwrap()
20001 .loads()[0]
20002 .p,
20003 old_active_power,
20004 );
20005
20006 pio_balanced_network_release(new_network);
20007 pio_value_release(new_value);
20008 pio_update_report_release(report);
20009 pio_calculation_update_release(update);
20010 pio_operating_point_update_release(operating);
20011 pio_active_power_release(replacement);
20012 pio_component_id_release(component);
20013 pio_balanced_network_release(old_network);
20014 pio_module_release(module);
20015 }
20016 }
20017
20018 #[test]
20019 fn dc_calculations_return_named_generic_handles() {
20020 unsafe {
20021 let module = parse_case9();
20022 let value = pio_module_value(module);
20023 let mut error = std::ptr::null_mut();
20024 let network = pio_value_balanced_network(value, &mut error);
20025 let incidence = pio_calc_incidence_matrix(network, std::ptr::null(), 0, &mut error);
20026 assert!(!incidence.is_null(), "{}", error_text(error));
20027 assert_eq!(pio_sparse_matrix_rows(incidence), 9);
20028 assert_eq!(pio_sparse_matrix_columns(incidence), 9);
20029 assert_eq!(pio_sparse_matrix_values(incidence).len, 18);
20030
20031 let branch = pio_calc_branch_susceptances(
20032 network,
20033 c"reactance_only".as_ptr(),
20034 "reactance_only".len(),
20035 &mut error,
20036 );
20037 assert!(!branch.is_null(), "{}", error_text(error));
20038 assert_eq!(pio_vector_values(branch).len, 9);
20039
20040 pio_vector_release(branch);
20041 pio_sparse_matrix_release(incidence);
20042 pio_balanced_network_release(network);
20043 pio_value_release(value);
20044 pio_module_release(module);
20045 }
20046 }
20047
20048 #[test]
20049 fn powerio_ir_uses_serialize_and_deserialize() {
20050 unsafe {
20051 let module = parse_case9();
20052 let mut error = std::ptr::null_mut();
20053 let destination = pio_destination_memory(
20054 c"case.pio.json".as_ptr(),
20055 "case.pio.json".len(),
20056 &mut error,
20057 );
20058 let result = pio_module_serialize(module, destination, &mut error);
20059 assert!(!result.is_null(), "{}", error_text(error));
20060 let artifact = pio_emit_result_artifact(result, 0, &mut error);
20061 let bytes = pio_artifact_bytes(artifact);
20062 let source = pio_source_from_memory(
20063 c"case.pio.json".as_ptr(),
20064 "case.pio.json".len(),
20065 bytes.data,
20066 bytes.len,
20067 &mut error,
20068 );
20069 let decoded = pio_module_deserialize(source, &mut error);
20070 assert!(!decoded.is_null(), "{}", error_text(error));
20071
20072 pio_module_release(decoded);
20073 pio_source_release(source);
20074 pio_artifact_release(artifact);
20075 pio_emit_result_release(result);
20076 pio_destination_release(destination);
20077 pio_module_release(module);
20078 }
20079 }
20080}