1use std::ffi::c_char;
30use std::panic::{AssertUnwindSafe, catch_unwind};
31use std::path::PathBuf;
32use std::sync::Arc;
33
34use powerio::{
35 BalancedNetwork, Destination, Diagnostic, EmitResult, EmittedOutput, PioScenarioSet,
36 PioTimeSeries, PioValue, Source,
37};
38use powerio_core::{ComponentId, HistoryEntry, HistoryId, HistoryKind, Producer};
39use powerio_matrix::{
40 AcOpfAssemblyOptions, AcOpfPreparation, AnalysisBranchSource, DcOperatorOptions, DcOperators,
41 DcOpfAssemblyOptions, DcOpfPreparation, PreparedObjective, SparseMatrix, Units,
42 build_ac_opf_preparation, build_dc_opf_preparation,
43};
44use powerio_prob::{
45 ActivePower, ActivePowerUnit, ApparentPower, ApparentPowerUnit, CalculationUpdate,
46 DcPfInstance, LoadAllocation, NetworkUpdate, OperatingPointUpdate, ReactivePower,
47 ReactivePowerUnit, UpdateChange, UpdatedField, apply_bus_load_active_power, apply_updates,
48};
49use powerio_tx::BranchSusceptanceFormula;
50
51use crate::diagnostics::codes;
52
53pub mod diagnostics;
54
55pub const PIO_ABI_VERSION: u32 = 7;
57
58#[repr(C)]
62#[derive(Clone, Copy)]
63pub struct PioStringView {
64 pub data: *const c_char,
65 pub len: usize,
66}
67
68impl PioStringView {
69 const EMPTY: Self = Self {
70 data: std::ptr::null(),
71 len: 0,
72 };
73
74 fn new(text: &str) -> Self {
75 Self {
76 data: text.as_ptr().cast(),
77 len: text.len(),
78 }
79 }
80}
81
82#[repr(C)]
84#[derive(Clone, Copy)]
85pub struct PioDiagnosticSpanView {
86 pub source: PioStringView,
87 pub byte_start: u64,
88 pub byte_end: u64,
89}
90
91#[repr(C)]
93#[derive(Clone, Copy)]
94pub struct PioModuleProducerView {
95 pub name: PioStringView,
96 pub version: PioStringView,
97}
98
99#[repr(C)]
101#[derive(Clone, Copy)]
102pub struct PioModuleSourceView {
103 pub id: PioStringView,
104 pub name: PioStringView,
105 pub byte_length: u64,
106 pub format: PioStringView,
107 pub has_format: bool,
108 pub digest_algorithm: PioStringView,
109 pub digest: PioStringView,
110 pub has_digest: bool,
111}
112
113#[repr(C)]
115#[derive(Clone, Copy)]
116pub struct PioSourceSpanView {
117 pub source: PioStringView,
118 pub byte_start: u64,
119 pub byte_end: u64,
120}
121
122#[repr(C)]
124#[derive(Clone, Copy)]
125pub struct PioModuleSourceMapEntryView {
126 pub target: PioStringView,
127 pub relation: PioStringView,
128 pub span_count: usize,
129}
130
131#[repr(C)]
133#[derive(Clone, Copy)]
134pub struct PioModuleHistoryEntryView {
135 pub id: PioStringView,
136 pub kind: PioStringView,
137 pub name: PioStringView,
138 pub input_type: PioStringView,
139 pub has_input_type: bool,
140 pub output_type: PioStringView,
141 pub has_output_type: bool,
142 pub parameter_count: usize,
143 pub assumption_count: usize,
144 pub loss_count: usize,
145}
146
147#[repr(C)]
149#[derive(Clone, Copy)]
150pub struct PioModuleHistoryParameterView {
151 pub name: PioStringView,
152 pub value_kind: PioStringView,
153}
154
155#[repr(C)]
157#[derive(Clone, Copy)]
158pub struct PioModuleExtensionView {
159 pub namespace: PioStringView,
160 pub value_kind: PioStringView,
161}
162
163#[repr(C)]
165#[derive(Clone, Copy)]
166pub struct PioJsonValueView {
167 pub kind: PioStringView,
168 pub boolean_value: bool,
169 pub number_kind: PioStringView,
170 pub signed_integer_value: i64,
171 pub unsigned_integer_value: u64,
172 pub floating_point_value: f64,
173 pub string_value: PioStringView,
174 pub element_count: usize,
175}
176
177#[repr(C)]
179#[derive(Clone, Copy)]
180pub struct PioJsonObjectEntryView {
181 pub key: PioStringView,
182 pub value_kind: PioStringView,
183}
184
185#[repr(C)]
187#[derive(Clone, Copy)]
188pub struct PioByteView {
189 pub data: *const u8,
190 pub len: usize,
191}
192
193impl PioByteView {
194 const EMPTY: Self = Self {
195 data: std::ptr::null(),
196 len: 0,
197 };
198
199 fn new(bytes: &[u8]) -> Self {
200 Self {
201 data: bytes.as_ptr(),
202 len: bytes.len(),
203 }
204 }
205}
206
207#[repr(C)]
209#[derive(Clone, Copy)]
210pub struct PioF64View {
211 pub data: *const f64,
212 pub len: usize,
213}
214
215impl PioF64View {
216 const EMPTY: Self = Self {
217 data: std::ptr::null(),
218 len: 0,
219 };
220
221 fn new(values: &[f64]) -> Self {
222 Self {
223 data: values.as_ptr(),
224 len: values.len(),
225 }
226 }
227}
228
229#[repr(C)]
231#[derive(Clone, Copy)]
232pub struct PioSizeView {
233 pub data: *const usize,
234 pub len: usize,
235}
236
237impl PioSizeView {
238 const EMPTY: Self = Self {
239 data: std::ptr::null(),
240 len: 0,
241 };
242
243 fn new(values: &[usize]) -> Self {
244 Self {
245 data: values.as_ptr(),
246 len: values.len(),
247 }
248 }
249}
250
251#[repr(C)]
253#[derive(Clone, Copy)]
254pub struct PioBalancedLocationView {
255 pub x: f64,
256 pub y: f64,
257 pub kind: PioStringView,
258 pub has_kind: bool,
259}
260
261#[repr(C)]
263#[derive(Clone, Copy)]
264pub struct PioBalancedGeoView {
265 pub has_geo: bool,
266 pub space: PioStringView,
267 pub crs: PioStringView,
268 pub has_crs: bool,
269 pub kind: PioStringView,
270 pub has_kind: bool,
271 pub has_canvas: bool,
272 pub canvas_width: f64,
273 pub has_canvas_width: bool,
274 pub canvas_height: f64,
275 pub has_canvas_height: bool,
276 pub canvas_units: PioStringView,
277 pub has_canvas_units: bool,
278}
279
280#[repr(C)]
282#[derive(Clone, Copy)]
283pub struct PioBalancedBusView {
284 pub component_id: PioStringView,
285 pub has_component_id: bool,
286 pub id: usize,
287 pub bus_type: PioStringView,
288 pub vm_pu: f64,
289 pub va_degrees: f64,
290 pub base_kv: f64,
291 pub vmax_pu: f64,
292 pub vmin_pu: f64,
293 pub has_emergency_voltage_limits: bool,
294 pub emergency_vmax_pu: f64,
295 pub emergency_vmin_pu: f64,
296 pub area: usize,
297 pub zone: usize,
298 pub name: PioStringView,
299 pub has_name: bool,
300 pub location: PioBalancedLocationView,
301 pub has_location: bool,
302}
303
304#[repr(C)]
306#[derive(Clone, Copy)]
307pub struct PioBalancedLoadVoltageModelView {
308 pub kind: PioStringView,
309 pub p_constant_power_mw: f64,
310 pub q_constant_power_mvar: f64,
311 pub p_constant_current_mw: f64,
312 pub q_constant_current_mvar: f64,
313 pub p_constant_impedance_mw: f64,
314 pub q_constant_impedance_mvar: f64,
315 pub exponential_p_mw: f64,
316 pub exponential_q_mvar: f64,
317 pub gamma_p: f64,
318 pub gamma_q: f64,
319 pub nominal_voltage_pu: f64,
320 pub has_nominal_voltage: bool,
321 pub load_type: i32,
322 pub has_load_type: bool,
323 pub scaling: f64,
324 pub has_scaling: bool,
325}
326
327#[repr(C)]
329#[derive(Clone, Copy)]
330pub struct PioBalancedLoadView {
331 pub component_id: PioStringView,
332 pub has_component_id: bool,
333 pub bus_id: usize,
334 pub p_mw: f64,
335 pub q_mvar: f64,
336 pub in_service: bool,
337 pub voltage_model: PioBalancedLoadVoltageModelView,
338}
339
340#[repr(C)]
342#[derive(Clone, Copy)]
343pub struct PioShuntBlockView {
344 pub steps: u32,
345 pub conductance_mw: f64,
346 pub susceptance_mvar: f64,
347}
348
349#[repr(C)]
351#[derive(Clone, Copy)]
352pub struct PioBalancedShuntView {
353 pub component_id: PioStringView,
354 pub has_component_id: bool,
355 pub bus_id: usize,
356 pub conductance_mw: f64,
357 pub susceptance_mvar: f64,
358 pub in_service: bool,
359 pub section_count: u32,
360 pub has_section_count: bool,
361 pub has_control: bool,
362 pub control_mode: PioStringView,
363 pub control_vmax_pu: f64,
364 pub control_vmin_pu: f64,
365 pub control_bus_id: usize,
366 pub has_control_bus: bool,
367 pub control_reactive_range_percent: f64,
368 pub control_block_count: usize,
369}
370
371#[repr(C)]
373#[derive(Clone, Copy)]
374pub struct PioBranchRatingView {
375 pub name: PioStringView,
376 pub rate_mva: f64,
377}
378
379#[repr(C)]
381#[derive(Clone, Copy)]
382pub struct PioBalancedBranchView {
383 pub component_id: PioStringView,
384 pub has_component_id: bool,
385 pub name: PioStringView,
386 pub has_name: bool,
387 pub from_bus_id: usize,
388 pub to_bus_id: usize,
389 pub resistance_pu: f64,
390 pub reactance_pu: f64,
391 pub total_charging_susceptance_pu: f64,
392 pub terminal_charging_is_explicit: bool,
393 pub from_conductance_pu: f64,
394 pub from_susceptance_pu: f64,
395 pub to_conductance_pu: f64,
396 pub to_susceptance_pu: f64,
397 pub rate_a_mva: f64,
398 pub rate_b_mva: f64,
399 pub rate_c_mva: f64,
400 pub additional_rating_count: usize,
401 pub has_current_ratings: bool,
402 pub current_rating_a: f64,
403 pub current_rating_b: f64,
404 pub current_rating_c: f64,
405 pub tap_ratio: f64,
406 pub effective_tap_ratio: f64,
407 pub phase_shift_degrees: f64,
408 pub in_service: bool,
409 pub angle_min_degrees: f64,
410 pub angle_max_degrees: f64,
411 pub control: PioTransformerControlView,
412 pub has_control: bool,
413 pub route_point_count: usize,
414 pub has_route: bool,
415}
416
417#[repr(C)]
419#[derive(Clone, Copy)]
420pub struct PioGeneratorCostView {
421 pub model: u8,
422 pub startup: f64,
423 pub shutdown: f64,
424 pub ncost: usize,
425 pub coefficients: PioF64View,
426}
427
428#[repr(C)]
430#[derive(Clone, Copy)]
431pub struct PioGeneratorCapabilityView {
432 pub name: PioStringView,
433 pub value: f64,
434 pub has_value: bool,
435}
436
437#[repr(C)]
439#[derive(Clone, Copy)]
440pub struct PioActivePowerControlView {
441 pub participate: bool,
442 pub droop_percent: f64,
443 pub has_droop_percent: bool,
444 pub participation_factor: f64,
445 pub has_participation_factor: bool,
446 pub minimum_target_active_power_mw: f64,
447 pub has_minimum_target_active_power: bool,
448 pub maximum_target_active_power_mw: f64,
449 pub has_maximum_target_active_power: bool,
450}
451
452#[repr(C)]
454#[derive(Clone, Copy)]
455pub struct PioBalancedGeneratorView {
456 pub component_id: PioStringView,
457 pub has_component_id: bool,
458 pub bus_id: usize,
459 pub energy_source: PioStringView,
460 pub active_power_mw: f64,
461 pub reactive_power_mvar: f64,
462 pub active_power_max_mw: f64,
463 pub active_power_min_mw: f64,
464 pub reactive_power_max_mvar: f64,
465 pub reactive_power_min_mvar: f64,
466 pub voltage_setpoint_pu: f64,
467 pub machine_base_mva: f64,
468 pub in_service: bool,
469 pub has_cost: bool,
470 pub cost: PioGeneratorCostView,
471 pub regulated_bus_id: usize,
472 pub has_regulated_bus: bool,
473 pub capability_count: usize,
474 pub active_power_control: PioActivePowerControlView,
475 pub has_active_power_control: bool,
476 pub voltage_regulation_on: bool,
477 pub regulating_terminal: PioTerminalReferenceView,
478 pub has_regulating_terminal: bool,
479}
480
481#[repr(C)]
483#[derive(Clone, Copy)]
484pub struct PioBalancedStorageView {
485 pub component_id: PioStringView,
486 pub has_component_id: bool,
487 pub bus_id: usize,
488 pub active_power_mw: f64,
489 pub reactive_power_mvar: f64,
490 pub energy_mwh: f64,
491 pub energy_rating_mwh: f64,
492 pub charge_rating_mw: f64,
493 pub discharge_rating_mw: f64,
494 pub charge_efficiency: f64,
495 pub discharge_efficiency: f64,
496 pub thermal_rating_mva: f64,
497 pub current_rating: f64,
498 pub has_current_rating: bool,
499 pub reactive_power_min_mvar: f64,
500 pub reactive_power_max_mvar: f64,
501 pub resistance_pu: f64,
502 pub reactance_pu: f64,
503 pub active_power_loss_mw: f64,
504 pub reactive_power_loss_mvar: f64,
505 pub in_service: bool,
506 pub active_power_control: PioActivePowerControlView,
507 pub has_active_power_control: bool,
508}
509
510#[repr(C)]
512#[derive(Clone, Copy)]
513pub struct PioTerminalReferenceView {
514 pub equipment: PioComponentIdView,
515 pub terminal: u8,
516}
517
518#[repr(C)]
520#[derive(Clone, Copy)]
521pub struct PioTransformerControlView {
522 pub mode: PioStringView,
523 pub enabled: bool,
524 pub controlled_bus_id: usize,
525 pub has_controlled_bus: bool,
526 pub controlled_bus_on_winding_side: bool,
527 pub regulating_terminal: PioTerminalReferenceView,
528 pub has_regulating_terminal: bool,
529 pub tap_min: f64,
530 pub tap_max: f64,
531 pub band_min: f64,
532 pub band_max: f64,
533 pub tap_position_count: u32,
534 pub mva_base: f64,
535 pub winding_connection_angle: f64,
536 pub has_winding_connection_angle: bool,
537}
538
539#[repr(C)]
541#[derive(Clone, Copy)]
542pub struct PioBalancedStaticVarCompensatorView {
543 pub component_id: PioStringView,
544 pub has_component_id: bool,
545 pub bus_id: usize,
546 pub minimum_susceptance_siemens: f64,
547 pub maximum_susceptance_siemens: f64,
548 pub voltage_setpoint_kv: f64,
549 pub reactive_power_setpoint_mvar: f64,
550 pub regulation_mode: PioStringView,
551 pub regulating: bool,
552 pub regulating_terminal: PioTerminalReferenceView,
553 pub has_regulating_terminal: bool,
554 pub active_power_mw: f64,
555 pub reactive_power_mvar: f64,
556 pub in_service: bool,
557}
558
559#[repr(C)]
561#[derive(Clone, Copy)]
562pub struct PioBalancedSwitchView {
563 pub component_id: PioStringView,
564 pub has_component_id: bool,
565 pub from_bus_id: usize,
566 pub to_bus_id: usize,
567 pub closed: bool,
568 pub thermal_rating_mva: f64,
569 pub has_thermal_rating: bool,
570 pub current_rating_a: f64,
571 pub has_current_rating: bool,
572 pub from_active_power_mw: f64,
573 pub has_from_active_power: bool,
574 pub from_reactive_power_mvar: f64,
575 pub has_from_reactive_power: bool,
576 pub to_active_power_mw: f64,
577 pub has_to_active_power: bool,
578 pub to_reactive_power_mvar: f64,
579 pub has_to_reactive_power: bool,
580}
581
582#[repr(C)]
584#[derive(Clone, Copy)]
585pub struct PioBalancedHvdcConverterView {
586 pub component: PioComponentIdView,
587 pub kind: PioStringView,
588 pub loss_factor_percent: f64,
589 pub voltage_regulator_on: bool,
590 pub has_voltage_regulator_on: bool,
591 pub voltage_setpoint_kv: f64,
592 pub has_voltage_setpoint: bool,
593 pub reactive_power_setpoint_mvar: f64,
594 pub has_reactive_power_setpoint: bool,
595 pub power_factor: f64,
596 pub has_power_factor: bool,
597 pub regulating_terminal: PioTerminalReferenceView,
598 pub has_regulating_terminal: bool,
599}
600
601#[repr(C)]
603#[derive(Clone, Copy)]
604pub struct PioBalancedHvdcView {
605 pub component_id: PioStringView,
606 pub has_component_id: bool,
607 pub from_bus_id: usize,
608 pub to_bus_id: usize,
609 pub in_service: bool,
610 pub from_active_power_mw: f64,
611 pub to_active_power_mw: f64,
612 pub from_reactive_power_mvar: f64,
613 pub to_reactive_power_mvar: f64,
614 pub from_voltage_pu: f64,
615 pub to_voltage_pu: f64,
616 pub minimum_active_power_mw: f64,
617 pub maximum_active_power_mw: f64,
618 pub minimum_from_reactive_power_mvar: f64,
619 pub maximum_from_reactive_power_mvar: f64,
620 pub minimum_to_reactive_power_mvar: f64,
621 pub maximum_to_reactive_power_mvar: f64,
622 pub constant_loss_mw: f64,
623 pub proportional_loss: f64,
624 pub resistance_ohm: f64,
625 pub has_resistance: bool,
626 pub nominal_voltage_kv: f64,
627 pub has_nominal_voltage: bool,
628 pub converters_mode: PioStringView,
629 pub has_converters_mode: bool,
630 pub converter1: PioBalancedHvdcConverterView,
631 pub has_converter1: bool,
632 pub converter2: PioBalancedHvdcConverterView,
633 pub has_converter2: bool,
634 pub cost: PioGeneratorCostView,
635 pub has_cost: bool,
636}
637
638#[repr(C)]
640#[derive(Clone, Copy)]
641pub struct PioThreeWindingTransformerWindingView {
642 pub bus_id: usize,
643 pub tap_ratio: f64,
644 pub phase_shift_degrees: f64,
645 pub nominal_voltage_kv: f64,
646 pub rating_a_mva: f64,
647 pub rating_b_mva: f64,
648 pub rating_c_mva: f64,
649 pub control: PioTransformerControlView,
650 pub has_control: bool,
651}
652
653#[repr(C)]
655#[derive(Clone, Copy)]
656pub struct PioThreeWindingTransformerImpedanceView {
657 pub resistance_pu: f64,
658 pub reactance_pu: f64,
659 pub base_mva: f64,
660}
661
662#[repr(C)]
664#[derive(Clone, Copy)]
665pub struct PioBalancedThreeWindingTransformerView {
666 pub component_id: PioStringView,
667 pub has_component_id: bool,
668 pub name: PioStringView,
669 pub has_name: bool,
670 pub winding_count: usize,
671 pub impedance_count: usize,
672 pub star_voltage_magnitude_pu: f64,
673 pub star_voltage_angle_degrees: f64,
674 pub magnetizing_conductance_pu: f64,
675 pub magnetizing_susceptance_pu: f64,
676 pub in_service: bool,
677}
678
679#[repr(C)]
681#[derive(Clone, Copy)]
682pub struct PioBalancedAreaView {
683 pub number: usize,
684 pub slack_bus_id: usize,
685 pub has_slack_bus: bool,
686 pub net_interchange_mw: f64,
687 pub tolerance_mw: f64,
688 pub name: PioStringView,
689 pub has_name: bool,
690 pub component_id: PioStringView,
691 pub has_component_id: bool,
692 pub area_type: PioStringView,
693 pub has_area_type: bool,
694}
695
696#[repr(C)]
698#[derive(Clone, Copy)]
699pub struct PioDetailedConnectivityCountsView {
700 pub omitted_fields: usize,
701 pub component_metadata: usize,
702 pub subnetworks: usize,
703 pub substations: usize,
704 pub voltage_levels: usize,
705 pub bus_breaker_buses: usize,
706 pub calculated_buses: usize,
707 pub connectivity_nodes: usize,
708 pub busbar_sections: usize,
709 pub junctions: usize,
710 pub terminals: usize,
711 pub switches: usize,
712 pub internal_connections: usize,
713 pub operational_limit_groups: usize,
714 pub tap_changers: usize,
715 pub equipment_reactive_limits: usize,
716 pub boundary_lines: usize,
717 pub tie_lines: usize,
718 pub dc_converter_units: usize,
719 pub dc_topological_nodes: usize,
720 pub dc_nodes: usize,
721 pub dc_grounds: usize,
722 pub dc_busbars: usize,
723 pub dc_lines: usize,
724 pub dc_series_devices: usize,
725 pub dc_switches: usize,
726 pub voltage_source_converters: usize,
727 pub line_commutated_converters: usize,
728}
729
730#[repr(C)]
732#[derive(Clone, Copy)]
733pub struct PioOmittedFieldView {
734 pub component: PioComponentIdView,
735 pub field: PioStringView,
736}
737
738#[repr(C)]
740#[derive(Clone, Copy)]
741pub struct PioEquipmentReactiveLimitsView {
742 pub equipment: PioComponentIdView,
743 pub limits: PioReactiveLimitsView,
744}
745
746#[repr(C)]
748#[derive(Clone, Copy)]
749pub struct PioCaseMetadataView {
750 pub case_date: PioStringView,
751 pub has_case_date: bool,
752 pub forecast_distance: i32,
753 pub has_forecast_distance: bool,
754 pub source_model_format: PioStringView,
755 pub has_source_model_format: bool,
756 pub minimum_validation_level: PioStringView,
757 pub has_minimum_validation_level: bool,
758}
759
760#[repr(C)]
762#[derive(Clone, Copy)]
763pub struct PioSubnetworkView {
764 pub component: PioComponentIdView,
765 pub parent: PioComponentIdView,
766 pub case_metadata: PioCaseMetadataView,
767 pub component_count: usize,
768}
769
770#[repr(C)]
772#[derive(Clone, Copy)]
773pub struct PioReactiveCapabilityCurvePointView {
774 pub active_power_mw: f64,
775 pub minimum_reactive_power_mvar: f64,
776 pub maximum_reactive_power_mvar: f64,
777 pub property_count: usize,
778}
779
780#[repr(C)]
782#[derive(Clone, Copy)]
783pub struct PioReactiveLimitsView {
784 pub kind: PioStringView,
785 pub minimum_reactive_power_mvar: f64,
786 pub maximum_reactive_power_mvar: f64,
787 pub has_minimum_and_maximum: bool,
788 pub curve_style: PioStringView,
789 pub has_curve_style: bool,
790 pub property_count: usize,
791 pub point_count: usize,
792}
793
794#[repr(C)]
796#[derive(Clone, Copy)]
797pub struct PioBoundaryLineGenerationView {
798 pub voltage_regulation_on: bool,
799 pub minimum_active_power_mw: f64,
800 pub has_minimum_active_power: bool,
801 pub maximum_active_power_mw: f64,
802 pub has_maximum_active_power: bool,
803 pub target_active_power_mw: f64,
804 pub has_target_active_power: bool,
805 pub target_reactive_power_mvar: f64,
806 pub has_target_reactive_power: bool,
807 pub target_voltage_kv: f64,
808 pub has_target_voltage: bool,
809 pub reactive_limits: PioReactiveLimitsView,
810 pub has_reactive_limits: bool,
811}
812
813#[repr(C)]
815#[derive(Clone, Copy)]
816pub struct PioBoundaryLineView {
817 pub component: PioComponentIdView,
818 pub voltage_level: PioComponentIdView,
819 pub active_power_setpoint_mw: f64,
820 pub reactive_power_setpoint_mvar: f64,
821 pub resistance_ohm: f64,
822 pub reactance_ohm: f64,
823 pub conductance_siemens: f64,
824 pub susceptance_siemens: f64,
825 pub pairing_key: PioStringView,
826 pub has_pairing_key: bool,
827 pub generation: PioBoundaryLineGenerationView,
828 pub has_generation: bool,
829 pub calculation_load: PioComponentIdView,
830 pub has_calculation_load: bool,
831 pub calculation_generator: PioComponentIdView,
832 pub has_calculation_generator: bool,
833}
834
835#[repr(C)]
837#[derive(Clone, Copy)]
838pub struct PioTieLineView {
839 pub component: PioComponentIdView,
840 pub boundary_line1: PioComponentIdView,
841 pub boundary_line2: PioComponentIdView,
842 pub calculation_branch: PioComponentIdView,
843 pub has_calculation_branch: bool,
844}
845
846#[repr(C)]
848#[derive(Clone, Copy)]
849pub struct PioComponentMetadataView {
850 pub component: PioComponentIdView,
851 pub name: PioStringView,
852 pub has_name: bool,
853 pub equipment_container: PioComponentIdView,
854 pub has_equipment_container: bool,
855 pub fictitious: bool,
856 pub alias_count: usize,
857 pub external_identifier_count: usize,
858 pub property_count: usize,
859}
860
861#[repr(C)]
863#[derive(Clone, Copy)]
864pub struct PioComponentAliasView {
865 pub value: PioStringView,
866 pub alias_type: PioStringView,
867 pub has_alias_type: bool,
868}
869
870#[repr(C)]
872#[derive(Clone, Copy)]
873pub struct PioExternalIdentifierView {
874 pub value: PioStringView,
875 pub authority: PioStringView,
876 pub has_authority: bool,
877}
878
879#[repr(C)]
881#[derive(Clone, Copy)]
882pub struct PioStringPropertyView {
883 pub name: PioStringView,
884 pub value: PioStringView,
885}
886
887#[repr(C)]
889#[derive(Clone, Copy)]
890pub struct PioSubstationView {
891 pub component: PioComponentIdView,
892 pub country: PioStringView,
893 pub has_country: bool,
894 pub operator_name: PioStringView,
895 pub has_operator_name: bool,
896 pub geographical_tag_count: usize,
897}
898
899#[repr(C)]
901#[derive(Clone, Copy)]
902pub struct PioVoltageLevelView {
903 pub component: PioComponentIdView,
904 pub substation: PioComponentIdView,
905 pub has_substation: bool,
906 pub nominal_voltage_kv: f64,
907 pub low_voltage_limit_kv: f64,
908 pub has_low_voltage_limit: bool,
909 pub high_voltage_limit_kv: f64,
910 pub has_high_voltage_limit: bool,
911 pub topology_kind: PioStringView,
912 pub bus_count: usize,
913}
914
915#[repr(C)]
917#[derive(Clone, Copy)]
918pub struct PioConnectivityNodeView {
919 pub component: PioComponentIdView,
920 pub voltage_level: PioComponentIdView,
921 pub node_number: i32,
922 pub has_node_number: bool,
923 pub calculated_bus_id: usize,
924 pub has_calculated_bus: bool,
925}
926
927#[repr(C)]
929#[derive(Clone, Copy)]
930pub struct PioBusBreakerBusView {
931 pub component: PioComponentIdView,
932 pub voltage_level: PioComponentIdView,
933 pub calculated_bus_id: usize,
934 pub has_calculated_bus: bool,
935 pub voltage_kv: f64,
936 pub has_voltage: bool,
937 pub angle_degrees: f64,
938 pub has_angle: bool,
939}
940
941#[repr(C)]
943#[derive(Clone, Copy)]
944pub struct PioCalculatedBusView {
945 pub voltage_level: PioComponentIdView,
946 pub calculated_bus_id: usize,
947 pub node_count: usize,
948 pub voltage_kv: f64,
949 pub has_voltage: bool,
950 pub angle_degrees: f64,
951 pub has_angle: bool,
952}
953
954#[repr(C)]
956#[derive(Clone, Copy)]
957pub struct PioBusbarSectionView {
958 pub component: PioComponentIdView,
959 pub voltage_level: PioComponentIdView,
960 pub node: PioComponentIdView,
961}
962
963#[repr(C)]
965#[derive(Clone, Copy)]
966pub struct PioJunctionView {
967 pub component: PioComponentIdView,
968}
969
970#[repr(C)]
972#[derive(Clone, Copy)]
973pub struct PioDetailedTerminalView {
974 pub component: PioComponentIdView,
975 pub has_component: bool,
976 pub equipment: PioComponentIdView,
977 pub terminal: u8,
978 pub voltage_level: PioComponentIdView,
979 pub bus: PioComponentIdView,
980 pub has_bus: bool,
981 pub connectable_bus: PioComponentIdView,
982 pub has_connectable_bus: bool,
983 pub node: PioComponentIdView,
984 pub has_node: bool,
985 pub connected: bool,
986 pub active_power_mw: f64,
987 pub has_active_power: bool,
988 pub reactive_power_mvar: f64,
989 pub has_reactive_power: bool,
990}
991
992#[repr(C)]
994#[derive(Clone, Copy)]
995pub struct PioTopologySwitchView {
996 pub component: PioComponentIdView,
997 pub voltage_level: PioComponentIdView,
998 pub kind: PioStringView,
999 pub endpoint1_kind: PioStringView,
1000 pub endpoint1: PioComponentIdView,
1001 pub endpoint2_kind: PioStringView,
1002 pub endpoint2: PioComponentIdView,
1003 pub open: bool,
1004 pub retained: bool,
1005}
1006
1007#[repr(C)]
1009#[derive(Clone, Copy)]
1010pub struct PioInternalConnectionView {
1011 pub voltage_level: PioComponentIdView,
1012 pub node1: PioComponentIdView,
1013 pub node2: PioComponentIdView,
1014}
1015
1016#[repr(C)]
1018#[derive(Clone, Copy)]
1019pub struct PioOperationalLimitGroupView {
1020 pub equipment: PioComponentIdView,
1021 pub terminal: u8,
1022 pub id: PioStringView,
1023 pub selected: bool,
1024 pub property_count: usize,
1025 pub has_current_limits: bool,
1026 pub current_permanent_limit_a: f64,
1027 pub current_permanent_limit_name: PioStringView,
1028 pub has_current_permanent_limit: bool,
1029 pub has_current_permanent_limit_name: bool,
1030 pub current_temporary_limit_count: usize,
1031 pub has_active_power_limits: bool,
1032 pub active_power_permanent_limit_mw: f64,
1033 pub active_power_permanent_limit_name: PioStringView,
1034 pub has_active_power_permanent_limit: bool,
1035 pub has_active_power_permanent_limit_name: bool,
1036 pub active_power_temporary_limit_count: usize,
1037 pub has_apparent_power_limits: bool,
1038 pub apparent_power_permanent_limit_mva: f64,
1039 pub apparent_power_permanent_limit_name: PioStringView,
1040 pub has_apparent_power_permanent_limit: bool,
1041 pub has_apparent_power_permanent_limit_name: bool,
1042 pub apparent_power_temporary_limit_count: usize,
1043}
1044
1045#[repr(C)]
1047#[derive(Clone, Copy)]
1048pub struct PioDroopCurveSegmentView {
1049 pub minimum_voltage_kv: f64,
1050 pub maximum_voltage_kv: f64,
1051 pub k: f64,
1052}
1053
1054#[repr(C)]
1056#[derive(Clone, Copy)]
1057pub struct PioTemporaryLimitView {
1058 pub name: PioStringView,
1059 pub value: f64,
1060 pub acceptable_duration_seconds: u64,
1061 pub fictitious: bool,
1062}
1063
1064#[repr(C)]
1066#[derive(Clone, Copy)]
1067pub struct PioTapChangerView {
1068 pub component: PioComponentIdView,
1069 pub has_component: bool,
1070 pub transformer: PioComponentIdView,
1071 pub winding: u8,
1072 pub kind: PioStringView,
1073 pub tap_position: i32,
1074 pub has_tap_position: bool,
1075 pub solved_tap_position: i32,
1076 pub has_solved_tap_position: bool,
1077 pub low_tap_position: i32,
1078 pub neutral_tap_position: i32,
1079 pub has_neutral_tap_position: bool,
1080 pub normal_tap_position: i32,
1081 pub has_normal_tap_position: bool,
1082 pub voltage_step_increment_percent: f64,
1083 pub has_voltage_step_increment_percent: bool,
1084 pub load_tap_changing_capabilities: bool,
1085 pub regulating: bool,
1086 pub regulation_mode: PioStringView,
1087 pub has_regulation_mode: bool,
1088 pub regulation_value: f64,
1089 pub has_regulation_value: bool,
1090 pub target_deadband: f64,
1091 pub has_target_deadband: bool,
1092 pub regulation_terminal: PioTerminalReferenceView,
1093 pub has_regulation_terminal: bool,
1094 pub step_count: usize,
1095}
1096
1097#[repr(C)]
1099#[derive(Clone, Copy)]
1100pub struct PioTapChangerStepView {
1101 pub position: i32,
1102 pub ratio_pu: f64,
1103 pub phase_shift_degrees: f64,
1104 pub resistance_deviation_percent: f64,
1105 pub reactance_deviation_percent: f64,
1106 pub conductance_deviation_percent: f64,
1107 pub susceptance_deviation_percent: f64,
1108}
1109
1110#[repr(C)]
1112#[derive(Clone, Copy)]
1113pub struct PioDcTerminalView {
1114 pub component: PioComponentIdView,
1115 pub has_component: bool,
1116 pub sequence_number: u32,
1117 pub has_sequence_number: bool,
1118 pub dc_node: PioComponentIdView,
1119 pub has_dc_node: bool,
1120 pub dc_topological_node: PioComponentIdView,
1121 pub has_dc_topological_node: bool,
1122 pub polarity: PioStringView,
1123 pub has_polarity: bool,
1124 pub connected: bool,
1125 pub has_connected: bool,
1126 pub active_power_mw: f64,
1127 pub has_active_power: bool,
1128 pub current_a: f64,
1129 pub has_current: bool,
1130}
1131
1132#[repr(C)]
1134#[derive(Clone, Copy)]
1135pub struct PioDcEquipmentView {
1136 pub component: PioComponentIdView,
1137 pub equipment_container: PioComponentIdView,
1138 pub has_equipment_container: bool,
1139 pub kind: PioStringView,
1140 pub terminal_count: usize,
1141 pub terminal1: PioDcTerminalView,
1142 pub terminal2: PioDcTerminalView,
1143 pub rated_dc_voltage_kv: f64,
1144 pub has_rated_dc_voltage: bool,
1145 pub resistance_ohm: f64,
1146 pub has_resistance: bool,
1147 pub inductance_h: f64,
1148 pub has_inductance: bool,
1149 pub capacitance_f: f64,
1150 pub has_capacitance: bool,
1151 pub length_km: f64,
1152 pub has_length: bool,
1153 pub switch_kind: PioStringView,
1154 pub has_switch_kind: bool,
1155 pub open: bool,
1156 pub has_open: bool,
1157}
1158
1159#[repr(C)]
1161#[derive(Clone, Copy)]
1162pub struct PioDcNodeView {
1163 pub component: PioComponentIdView,
1164 pub kind: PioStringView,
1165 pub nominal_voltage_kv: f64,
1166 pub has_nominal_voltage: bool,
1167 pub voltage_kv: f64,
1168 pub has_voltage: bool,
1169 pub dc_converter_unit: PioComponentIdView,
1170 pub has_dc_converter_unit: bool,
1171 pub dc_topological_node: PioComponentIdView,
1172 pub has_dc_topological_node: bool,
1173}
1174
1175#[repr(C)]
1177#[derive(Clone, Copy)]
1178pub struct PioDcConverterUnitView {
1179 pub component: PioComponentIdView,
1180 pub substation: PioComponentIdView,
1181 pub has_substation: bool,
1182 pub operation_mode: PioStringView,
1183}
1184
1185#[repr(C)]
1187#[derive(Clone, Copy)]
1188pub struct PioAcDcConverterView {
1189 pub component: PioComponentIdView,
1190 pub kind: PioStringView,
1191 pub dc_converter_unit: PioComponentIdView,
1192 pub has_dc_converter_unit: bool,
1193 pub dc_terminal1: PioDcTerminalView,
1194 pub dc_terminal2: PioDcTerminalView,
1195 pub base_apparent_power_mva: f64,
1196 pub has_base_apparent_power: bool,
1197 pub minimum_active_power_mw: f64,
1198 pub has_minimum_active_power: bool,
1199 pub maximum_active_power_mw: f64,
1200 pub has_maximum_active_power: bool,
1201 pub minimum_dc_voltage_kv: f64,
1202 pub has_minimum_dc_voltage: bool,
1203 pub maximum_dc_voltage_kv: f64,
1204 pub has_maximum_dc_voltage: bool,
1205 pub rated_dc_voltage_kv: f64,
1206 pub has_rated_dc_voltage: bool,
1207 pub valve_u0_kv: f64,
1208 pub has_valve_u0: bool,
1209 pub number_of_valves: u32,
1210 pub has_number_of_valves: bool,
1211 pub idle_loss_mw: f64,
1212 pub has_idle_loss: bool,
1213 pub switching_loss_mw_per_ampere: f64,
1214 pub has_switching_loss: bool,
1215 pub resistive_loss_ohm: f64,
1216 pub has_resistive_loss: bool,
1217 pub control_mode: PioStringView,
1218 pub has_control_mode: bool,
1219 pub active_power_at_pcc_mw: f64,
1220 pub has_active_power_at_pcc: bool,
1221 pub reactive_power_at_pcc_mvar: f64,
1222 pub has_reactive_power_at_pcc: bool,
1223 pub target_active_power_mw: f64,
1224 pub has_target_active_power: bool,
1225 pub target_dc_voltage_kv: f64,
1226 pub has_target_dc_voltage: bool,
1227 pub pcc_terminal: PioTerminalReferenceView,
1228 pub has_pcc_terminal: bool,
1229 pub droop_curve_segment_count: usize,
1230 pub has_droop_curve: bool,
1231 pub droop: f64,
1232 pub has_droop: bool,
1233 pub droop_compensation: f64,
1234 pub has_droop_compensation: bool,
1235 pub q_share: f64,
1236 pub has_q_share: bool,
1237 pub maximum_modulation_index: f64,
1238 pub has_maximum_modulation_index: bool,
1239 pub maximum_valve_current_a: f64,
1240 pub has_maximum_valve_current: bool,
1241 pub dc_current_a: f64,
1242 pub has_dc_current: bool,
1243 pub ac_voltage_kv: f64,
1244 pub has_ac_voltage: bool,
1245 pub dc_voltage_kv: f64,
1246 pub has_dc_voltage: bool,
1247 pub voltage_regulator_on: bool,
1248 pub has_voltage_regulator_on: bool,
1249 pub voltage_setpoint_kv: f64,
1250 pub has_voltage_setpoint: bool,
1251 pub reactive_power_setpoint_mvar: f64,
1252 pub has_reactive_power_setpoint: bool,
1253 pub reactive_limits: PioReactiveLimitsView,
1254 pub has_reactive_limits: bool,
1255 pub pole_loss_active_power_mw: f64,
1256 pub has_pole_loss_active_power: bool,
1257 pub reactive_model: PioStringView,
1258 pub has_reactive_model: bool,
1259 pub power_factor: f64,
1260 pub has_power_factor: bool,
1261 pub operating_mode: PioStringView,
1262 pub has_operating_mode: bool,
1263 pub rated_dc_current_a: f64,
1264 pub has_rated_dc_current: bool,
1265 pub minimum_alpha_degrees: f64,
1266 pub has_minimum_alpha: bool,
1267 pub maximum_alpha_degrees: f64,
1268 pub has_maximum_alpha: bool,
1269 pub minimum_gamma_degrees: f64,
1270 pub has_minimum_gamma: bool,
1271 pub maximum_gamma_degrees: f64,
1272 pub has_maximum_gamma: bool,
1273 pub target_alpha_degrees: f64,
1274 pub has_target_alpha: bool,
1275 pub target_gamma_degrees: f64,
1276 pub has_target_gamma: bool,
1277 pub target_dc_current_a: f64,
1278 pub has_target_dc_current: bool,
1279 pub alpha_degrees: f64,
1280 pub has_alpha: bool,
1281 pub gamma_degrees: f64,
1282 pub has_gamma: bool,
1283 pub delta_degrees: f64,
1284 pub has_delta: bool,
1285 pub uf_kv: f64,
1286 pub has_uf: bool,
1287 pub uv_kv: f64,
1288 pub has_uv: bool,
1289}
1290
1291#[repr(C)]
1293#[derive(Clone, Copy)]
1294pub struct PioMulticonductorLocationView {
1295 pub x: f64,
1296 pub y: f64,
1297 pub kind: PioStringView,
1298 pub has_kind: bool,
1299}
1300
1301#[repr(C)]
1303#[derive(Clone, Copy)]
1304pub struct PioMulticonductorGeoView {
1305 pub has_geo: bool,
1306 pub space: PioStringView,
1307 pub crs: PioStringView,
1308 pub has_crs: bool,
1309 pub kind: PioStringView,
1310 pub has_kind: bool,
1311 pub has_canvas: bool,
1312 pub canvas_width: f64,
1313 pub has_canvas_width: bool,
1314 pub canvas_height: f64,
1315 pub has_canvas_height: bool,
1316 pub canvas_units: PioStringView,
1317 pub has_canvas_units: bool,
1318}
1319
1320#[repr(C)]
1325#[derive(Clone, Copy)]
1326pub struct PioMulticonductorNetworkCountsView {
1327 pub buses: usize,
1328 pub line_codes: usize,
1329 pub lines: usize,
1330 pub switches: usize,
1331 pub transformers: usize,
1332 pub loads: usize,
1333 pub generators: usize,
1334 pub inverter_based_resources: usize,
1335 pub control_profiles: usize,
1336 pub shunts: usize,
1337 pub capacitors: usize,
1338 pub voltage_sources: usize,
1339 pub untyped_objects: usize,
1340 pub commands: usize,
1341 pub options: usize,
1342}
1343
1344#[repr(C)]
1346#[derive(Clone, Copy)]
1347pub struct PioMulticonductorBusView {
1348 pub id: PioStringView,
1349 pub terminal_count: usize,
1350 pub grounded_terminal_count: usize,
1351 pub voltage_min_v: f64,
1352 pub has_voltage_min: bool,
1353 pub voltage_max_v: f64,
1354 pub has_voltage_max: bool,
1355 pub phase_to_ground_voltage_min_v: PioF64View,
1357 pub has_phase_to_ground_voltage_min: bool,
1358 pub phase_to_ground_voltage_max_v: PioF64View,
1359 pub has_phase_to_ground_voltage_max: bool,
1360 pub phase_to_neutral_voltage_min_v: PioF64View,
1361 pub has_phase_to_neutral_voltage_min: bool,
1362 pub phase_to_neutral_voltage_max_v: PioF64View,
1363 pub has_phase_to_neutral_voltage_max: bool,
1364 pub phase_to_phase_voltage_min_v: PioF64View,
1365 pub has_phase_to_phase_voltage_min: bool,
1366 pub phase_to_phase_voltage_max_v: PioF64View,
1367 pub has_phase_to_phase_voltage_max: bool,
1368 pub positive_sequence_voltage_min_v: f64,
1369 pub has_positive_sequence_voltage_min: bool,
1370 pub positive_sequence_voltage_max_v: f64,
1371 pub has_positive_sequence_voltage_max: bool,
1372 pub negative_sequence_voltage_max_v: f64,
1373 pub has_negative_sequence_voltage_max: bool,
1374 pub zero_sequence_voltage_max_v: f64,
1375 pub has_zero_sequence_voltage_max: bool,
1376 pub neutral_to_ground_voltage_max_v: f64,
1377 pub has_neutral_to_ground_voltage_max: bool,
1378 pub location: PioMulticonductorLocationView,
1379 pub has_location: bool,
1380}
1381
1382#[repr(C)]
1384#[derive(Clone, Copy)]
1385pub struct PioMulticonductorLineCodeView {
1386 pub name: PioStringView,
1387 pub conductor_count: usize,
1388 pub resistance_matrix_row_count: usize,
1389 pub reactance_matrix_row_count: usize,
1390 pub conductance_from_matrix_row_count: usize,
1391 pub susceptance_from_matrix_row_count: usize,
1392 pub conductance_to_matrix_row_count: usize,
1393 pub susceptance_to_matrix_row_count: usize,
1394 pub current_limit_a: PioF64View,
1395 pub has_current_limit: bool,
1396 pub apparent_power_limit_va: PioF64View,
1397 pub has_apparent_power_limit: bool,
1398 pub source: PioStringView,
1399 pub has_source: bool,
1400}
1401
1402#[repr(C)]
1404#[derive(Clone, Copy)]
1405pub struct PioMulticonductorLineView {
1406 pub name: PioStringView,
1407 pub bus_from: PioStringView,
1408 pub bus_to: PioStringView,
1409 pub terminal_map_from_count: usize,
1410 pub terminal_map_to_count: usize,
1411 pub line_code: PioStringView,
1412 pub length_m: f64,
1413 pub route_point_count: usize,
1414 pub has_route: bool,
1415 pub current_limit_a: PioF64View,
1416 pub has_current_limit: bool,
1417 pub apparent_power_limit_va: PioF64View,
1418 pub has_apparent_power_limit: bool,
1419}
1420
1421#[repr(C)]
1423#[derive(Clone, Copy)]
1424pub struct PioMulticonductorSwitchView {
1425 pub name: PioStringView,
1426 pub bus_from: PioStringView,
1427 pub bus_to: PioStringView,
1428 pub terminal_map_from_count: usize,
1429 pub terminal_map_to_count: usize,
1430 pub open: bool,
1431 pub current_limit_a: PioF64View,
1432 pub has_current_limit: bool,
1433}
1434
1435#[repr(C)]
1437#[derive(Clone, Copy)]
1438pub struct PioMulticonductorTransformerView {
1439 pub name: PioStringView,
1440 pub winding_count: usize,
1441 pub short_circuit_reactance_percent: PioF64View,
1442 pub phase_count: usize,
1443}
1444
1445#[repr(C)]
1447#[derive(Clone, Copy)]
1448pub struct PioMulticonductorTransformerWindingView {
1449 pub bus: PioStringView,
1450 pub terminal_map_count: usize,
1451 pub connection: PioStringView,
1452 pub rated_voltage_v: f64,
1453 pub apparent_power_rating_va: f64,
1454 pub resistance_percent: f64,
1455 pub tap: f64,
1456 pub neutral_resistance_ohm: f64,
1457 pub has_neutral_resistance: bool,
1458 pub neutral_reactance_ohm: f64,
1459 pub has_neutral_reactance: bool,
1460}
1461
1462#[repr(C)]
1464#[derive(Clone, Copy)]
1465pub struct PioMulticonductorLoadView {
1466 pub name: PioStringView,
1467 pub bus: PioStringView,
1468 pub terminal_map_count: usize,
1469 pub configuration: PioStringView,
1470 pub active_power_nominal_w: PioF64View,
1471 pub reactive_power_nominal_var: PioF64View,
1472 pub voltage_model: PioStringView,
1473 pub nominal_voltage_v: PioF64View,
1474 pub active_power_constant_impedance: PioF64View,
1475 pub active_power_constant_current: PioF64View,
1476 pub active_power_constant_power: PioF64View,
1477 pub reactive_power_constant_impedance: PioF64View,
1478 pub reactive_power_constant_current: PioF64View,
1479 pub reactive_power_constant_power: PioF64View,
1480 pub active_power_exponent: PioF64View,
1481 pub reactive_power_exponent: PioF64View,
1482}
1483
1484#[repr(C)]
1486#[derive(Clone, Copy)]
1487pub struct PioMulticonductorGeneratorView {
1488 pub name: PioStringView,
1489 pub bus: PioStringView,
1490 pub terminal_map_count: usize,
1491 pub configuration: PioStringView,
1492 pub active_power_nominal_w: PioF64View,
1493 pub reactive_power_nominal_var: PioF64View,
1494 pub active_power_min_w: PioF64View,
1495 pub has_active_power_min: bool,
1496 pub active_power_max_w: PioF64View,
1497 pub has_active_power_max: bool,
1498 pub reactive_power_min_var: PioF64View,
1499 pub has_reactive_power_min: bool,
1500 pub reactive_power_max_var: PioF64View,
1501 pub has_reactive_power_max: bool,
1502 pub active_power_dispatch_cost_per_kwh: PioF64View,
1503 pub has_active_power_dispatch_cost: bool,
1504 pub apparent_power_limit_va: PioF64View,
1505 pub has_apparent_power_limit: bool,
1506 pub current_limit_a: PioF64View,
1507 pub has_current_limit: bool,
1508}
1509
1510#[repr(C)]
1512#[derive(Clone, Copy)]
1513pub struct PioInverterBasedResourceView {
1514 pub name: PioStringView,
1515 pub bus: PioStringView,
1516 pub terminal_map_count: usize,
1517 pub topology: PioStringView,
1518 pub prime_mover: PioStringView,
1519 pub apparent_power_limit_va: PioF64View,
1520 pub current_limit_a: PioF64View,
1521 pub has_current_limit: bool,
1522 pub active_power_available_w: f64,
1523 pub has_active_power_available: bool,
1524 pub active_power_min_w: PioF64View,
1525 pub has_active_power_min: bool,
1526 pub active_power_max_w: PioF64View,
1527 pub has_active_power_max: bool,
1528 pub reactive_power_min_var: PioF64View,
1529 pub has_reactive_power_min: bool,
1530 pub reactive_power_max_var: PioF64View,
1531 pub has_reactive_power_max: bool,
1532 pub control_profile: PioStringView,
1533 pub has_control_profile: bool,
1534 pub voltage_aggregation: PioStringView,
1535 pub has_voltage_aggregation: bool,
1536}
1537
1538#[repr(C)]
1540#[derive(Clone, Copy)]
1541pub struct PioControlProfileView {
1542 pub name: PioStringView,
1543 pub has_power_factor: bool,
1544 pub power_factor: f64,
1545 pub has_volt_var: bool,
1546 pub volt_var_voltage_reference: PioStringView,
1547 pub has_volt_var_voltage_reference: bool,
1548 pub volt_var_breakpoints: PioF64View,
1549 pub volt_var_reactive_power_limits: PioF64View,
1550 pub volt_var_reactive_power_unit: PioStringView,
1551 pub has_volt_var_reactive_power_unit: bool,
1552 pub volt_var_reactive_power_reference: PioStringView,
1553 pub has_volt_var_reactive_power_reference: bool,
1554 pub volt_var_active_power_min_for_reactive_power_w: f64,
1555 pub has_volt_var_active_power_min_for_reactive_power: bool,
1556 pub volt_var_active_power_min_for_max_reactive_power_w: f64,
1557 pub has_volt_var_active_power_min_for_max_reactive_power: bool,
1558 pub has_volt_watt: bool,
1559 pub volt_watt_voltage_reference: PioStringView,
1560 pub has_volt_watt_voltage_reference: bool,
1561 pub volt_watt_breakpoints: PioF64View,
1562 pub volt_watt_active_power_limits: PioF64View,
1563 pub volt_watt_active_power_unit: PioStringView,
1564 pub has_volt_watt_active_power_unit: bool,
1565 pub volt_watt_active_power_reference: PioStringView,
1566 pub has_volt_watt_active_power_reference: bool,
1567}
1568
1569#[repr(C)]
1571#[derive(Clone, Copy)]
1572pub struct PioMulticonductorShuntView {
1573 pub name: PioStringView,
1574 pub bus: PioStringView,
1575 pub terminal_map_count: usize,
1576 pub conductance_matrix_row_count: usize,
1577 pub susceptance_matrix_row_count: usize,
1578}
1579
1580#[repr(C)]
1582#[derive(Clone, Copy)]
1583pub struct PioMulticonductorCapacitorView {
1584 pub name: PioStringView,
1585 pub bus: PioStringView,
1586 pub terminal_map_count: usize,
1587 pub configuration: PioStringView,
1588 pub rated_reactive_power_var: f64,
1589 pub nominal_voltage_v: f64,
1590}
1591
1592#[repr(C)]
1594#[derive(Clone, Copy)]
1595pub struct PioVoltageSourceView {
1596 pub name: PioStringView,
1597 pub bus: PioStringView,
1598 pub terminal_map_count: usize,
1599 pub voltage_magnitude_v: PioF64View,
1600 pub voltage_angle_rad: PioF64View,
1601 pub energy_cost_rate_per_kwh: PioF64View,
1603 pub has_energy_cost_rate: bool,
1604}
1605
1606#[repr(C)]
1608#[derive(Clone, Copy)]
1609pub struct PioMulticonductorUntypedObjectView {
1610 pub class_name: PioStringView,
1611 pub name: PioStringView,
1612 pub property_count: usize,
1613}
1614
1615#[repr(C)]
1617#[derive(Clone, Copy)]
1618pub struct PioMulticonductorUntypedPropertyView {
1619 pub name: PioStringView,
1620 pub has_name: bool,
1621 pub value: PioStringView,
1622}
1623
1624#[repr(C)]
1626#[derive(Clone, Copy)]
1627pub struct PioMulticonductorCommandView {
1628 pub verb: PioStringView,
1629 pub args: PioStringView,
1630}
1631
1632#[repr(C)]
1634#[derive(Clone, Copy)]
1635pub struct PioDcBusSpecificationView {
1636 pub bus_id: usize,
1637 pub kind: PioStringView,
1638 pub net_active_power_mw: f64,
1639 pub voltage_angle_degrees: f64,
1640}
1641
1642#[repr(C)]
1644#[derive(Clone, Copy)]
1645pub struct PioAcBusSpecificationView {
1646 pub bus_id: usize,
1647 pub kind: PioStringView,
1648 pub net_active_power_mw: f64,
1649 pub net_reactive_power_mvar: f64,
1650 pub voltage_magnitude_pu: f64,
1651 pub voltage_angle_degrees: f64,
1652}
1653
1654#[repr(C)]
1656#[derive(Clone, Copy)]
1657pub struct PioDcOpfPreparationView {
1658 pub name: PioStringView,
1659 pub bus_count: usize,
1660 pub generator_count: usize,
1661 pub branch_count: usize,
1662 pub source_generator_count: usize,
1663 pub source_branch_count: usize,
1664 pub base_mva: f64,
1665 pub units: PioStringView,
1666 pub branch_susceptance_formula: PioStringView,
1667 pub objective: PioStringView,
1668 pub skip_zero_impedance: bool,
1669 pub synthesize_unrated_limits: bool,
1670 pub correct_angle_difference_bounds: bool,
1671 pub reference_bus_count: usize,
1672 pub skipped_zero_impedance_count: usize,
1673}
1674
1675#[repr(C)]
1677#[derive(Clone, Copy)]
1678pub struct PioDcOpfBusView {
1679 pub bus_id: usize,
1680 pub analysis_row: usize,
1681 pub source_row: usize,
1682 pub has_source_row: bool,
1683 pub active_power_demand: f64,
1684 pub shunt_conductance: f64,
1685 pub phase_shift_injection: f64,
1686}
1687
1688#[repr(C)]
1690#[derive(Clone, Copy)]
1691pub struct PioDcOpfGeneratorView {
1692 pub component_id: PioStringView,
1693 pub bus_index: usize,
1694 pub analysis_row: usize,
1695 pub source_row: usize,
1696 pub has_source_row: bool,
1697 pub quadratic_cost: f64,
1698 pub linear_cost: f64,
1699 pub constant_cost: f64,
1700 pub has_piecewise_linear_cost: bool,
1701 pub piecewise_linear_power: PioF64View,
1702 pub piecewise_linear_value: PioF64View,
1703 pub active_power_max: f64,
1704 pub active_power_min: f64,
1705 pub capability_active: bool,
1706}
1707
1708#[repr(C)]
1710#[derive(Clone, Copy)]
1711pub struct PioDcOpfBranchView {
1712 pub component_id: PioStringView,
1713 pub from_bus_index: usize,
1714 pub to_bus_index: usize,
1715 pub susceptance_magnitude: f64,
1716 pub phase_shift_radians: f64,
1717 pub active_power_max: f64,
1718 pub angle_difference_min_radians: f64,
1719 pub angle_difference_max_radians: f64,
1720 pub analysis_row: usize,
1721 pub source_kind: PioStringView,
1722 pub source_row: usize,
1723 pub winding: usize,
1724 pub has_winding: bool,
1725 pub thermal_limit_active: bool,
1726 pub angle_bound_active: bool,
1727}
1728
1729#[repr(C)]
1731#[derive(Clone, Copy)]
1732pub struct PioAcOpfPreparationView {
1733 pub name: PioStringView,
1734 pub bus_count: usize,
1735 pub generator_count: usize,
1736 pub storage_count: usize,
1737 pub branch_count: usize,
1738 pub source_generator_count: usize,
1739 pub source_branch_count: usize,
1740 pub base_mva: f64,
1741 pub units: PioStringView,
1742 pub objective: PioStringView,
1743 pub skip_zero_impedance: bool,
1744 pub synthesize_unrated_limits: bool,
1745 pub correct_angle_difference_bounds: bool,
1746 pub reference_bus_count: usize,
1747 pub skipped_zero_impedance_count: usize,
1748}
1749
1750#[repr(C)]
1752#[derive(Clone, Copy)]
1753pub struct PioAcOpfBusView {
1754 pub bus_id: usize,
1755 pub analysis_row: usize,
1756 pub source_row: usize,
1757 pub has_source_row: bool,
1758 pub active_power_demand: f64,
1759 pub reactive_power_demand: f64,
1760 pub shunt_conductance: f64,
1761 pub shunt_susceptance: f64,
1762 pub voltage_magnitude_min_pu: f64,
1763 pub voltage_magnitude_max_pu: f64,
1764 pub initial_voltage_magnitude_pu: f64,
1765 pub initial_voltage_angle_radians: f64,
1766 pub voltage_bound_active: bool,
1767}
1768
1769#[repr(C)]
1771#[derive(Clone, Copy)]
1772pub struct PioAcOpfGeneratorView {
1773 pub component_id: PioStringView,
1774 pub bus_index: usize,
1775 pub analysis_row: usize,
1776 pub source_row: usize,
1777 pub has_source_row: bool,
1778 pub quadratic_cost: f64,
1779 pub linear_cost: f64,
1780 pub constant_cost: f64,
1781 pub has_piecewise_linear_cost: bool,
1782 pub piecewise_linear_power: PioF64View,
1783 pub piecewise_linear_value: PioF64View,
1784 pub active_power_max: f64,
1785 pub active_power_min: f64,
1786 pub reactive_power_max: f64,
1787 pub reactive_power_min: f64,
1788 pub initial_active_power: f64,
1789 pub initial_reactive_power: f64,
1790 pub voltage_magnitude_setpoint_pu: f64,
1791 pub capability_active: bool,
1792}
1793
1794#[repr(C)]
1796#[derive(Clone, Copy)]
1797pub struct PioAcOpfStorageView {
1798 pub component_id: PioStringView,
1799 pub bus_index: usize,
1800 pub source_row: usize,
1801 pub initial_active_power: f64,
1802 pub initial_reactive_power: f64,
1803 pub energy: f64,
1804 pub energy_rating: f64,
1805 pub charge_rating: f64,
1806 pub discharge_rating: f64,
1807 pub charge_efficiency: f64,
1808 pub discharge_efficiency: f64,
1809 pub apparent_power_max: f64,
1810 pub reactive_power_min: f64,
1811 pub reactive_power_max: f64,
1812 pub resistance_pu: f64,
1813 pub reactance_pu: f64,
1814 pub active_power_loss: f64,
1815 pub reactive_power_loss: f64,
1816 pub in_service: bool,
1817}
1818
1819#[repr(C)]
1821#[derive(Clone, Copy)]
1822pub struct PioAcOpfBranchView {
1823 pub component_id: PioStringView,
1824 pub from_bus_index: usize,
1825 pub to_bus_index: usize,
1826 pub series_conductance: f64,
1827 pub series_susceptance: f64,
1828 pub from_conductance: f64,
1829 pub from_susceptance: f64,
1830 pub to_conductance: f64,
1831 pub to_susceptance: f64,
1832 pub tap_ratio: f64,
1833 pub phase_shift_radians: f64,
1834 pub apparent_power_max: f64,
1835 pub angle_difference_min_radians: f64,
1836 pub angle_difference_max_radians: f64,
1837 pub analysis_row: usize,
1838 pub source_kind: PioStringView,
1839 pub source_row: usize,
1840 pub winding: usize,
1841 pub has_winding: bool,
1842 pub thermal_limit_active: bool,
1843 pub angle_bound_active: bool,
1844}
1845
1846#[repr(C)]
1848#[derive(Clone, Copy)]
1849pub struct PioObjectiveTermView {
1850 pub kind: PioStringView,
1851}
1852
1853#[repr(C)]
1855#[derive(Clone, Copy)]
1856pub struct PioActiveConstraintView {
1857 pub family: PioStringView,
1858 pub selection: PioStringView,
1859 pub identity_count: usize,
1860}
1861
1862#[repr(C)]
1864#[derive(Clone, Copy)]
1865pub struct PioPrescribedTerminalPowerView {
1866 pub load: PioStringView,
1867 pub terminal_count: usize,
1868 pub voltage_model: PioStringView,
1869}
1870
1871#[repr(C)]
1873#[derive(Clone, Copy)]
1874pub struct PioTerminalPowerView {
1875 pub terminal: PioStringView,
1876 pub active_power_w: f64,
1877 pub reactive_power_var: f64,
1878 pub nominal_voltage_v: f64,
1879 pub has_nominal_voltage: bool,
1880 pub active_impedance_fraction: f64,
1881 pub active_current_fraction: f64,
1882 pub active_power_fraction: f64,
1883 pub reactive_impedance_fraction: f64,
1884 pub reactive_current_fraction: f64,
1885 pub reactive_power_fraction: f64,
1886 pub active_power_exponent: f64,
1887 pub reactive_power_exponent: f64,
1888}
1889
1890#[repr(C)]
1892#[derive(Clone, Copy)]
1893pub struct PioPrescribedSourceVoltageView {
1894 pub source: PioStringView,
1895 pub terminal_count: usize,
1896}
1897
1898#[repr(C)]
1900#[derive(Clone, Copy)]
1901pub struct PioTerminalVoltageView {
1902 pub terminal: PioStringView,
1903 pub magnitude_v: f64,
1904 pub angle_radians: f64,
1905}
1906
1907#[repr(C)]
1909#[derive(Clone, Copy)]
1910pub struct PioIsolatedTerminalView {
1911 pub bus: PioStringView,
1912 pub terminal: PioStringView,
1913}
1914
1915#[repr(C)]
1917#[derive(Clone, Copy)]
1918pub struct PioActiveControlView {
1919 pub kind: PioStringView,
1920 pub component_id: PioStringView,
1921}
1922
1923#[repr(C)]
1925#[derive(Clone, Copy)]
1926pub struct PioScucDimensionsView {
1927 pub period_count: usize,
1928 pub device_count: usize,
1929 pub producer_count: usize,
1930 pub consumer_count: usize,
1931 pub shunt_count: usize,
1932 pub branch_switching_cost_count: usize,
1933 pub transformer_control_count: usize,
1934 pub active_reserve_zone_count: usize,
1935 pub reactive_reserve_zone_count: usize,
1936 pub contingency_count: usize,
1937}
1938
1939#[repr(C)]
1941#[derive(Clone, Copy)]
1942pub struct PioScucViolationCostView {
1943 pub active_power_balance: f64,
1944 pub reactive_power_balance: f64,
1945 pub branch_thermal_limit: f64,
1946 pub energy_requirement: f64,
1947}
1948
1949#[repr(C)]
1951#[derive(Clone, Copy)]
1952pub struct PioComponentIdView {
1953 pub component_type: PioStringView,
1954 pub local_id: PioStringView,
1955}
1956
1957#[repr(C)]
1964#[derive(Clone, Copy)]
1965pub struct PioContingencyComponentView {
1966 pub id: PioComponentIdView,
1967 pub row: usize,
1968 pub in_service: bool,
1969}
1970
1971#[repr(C)]
1973#[derive(Clone, Copy)]
1974pub struct PioScucRampLimitsView {
1975 pub up_pu_per_hour: f64,
1976 pub down_pu_per_hour: f64,
1977 pub startup_pu_per_hour: f64,
1978 pub shutdown_pu_per_hour: f64,
1979}
1980
1981#[repr(C)]
1983#[derive(Clone, Copy)]
1984pub struct PioScucReserveLimitsView {
1985 pub regulation_up_pu: f64,
1986 pub regulation_down_pu: f64,
1987 pub synchronized_pu: f64,
1988 pub nonsynchronized_pu: f64,
1989 pub ramping_up_online_pu: f64,
1990 pub ramping_down_online_pu: f64,
1991 pub ramping_up_offline_pu: f64,
1992 pub ramping_down_offline_pu: f64,
1993}
1994
1995#[repr(C)]
1997#[derive(Clone, Copy)]
1998pub struct PioScucInitialCommitmentView {
1999 pub accumulated_up_time_hours: f64,
2000 pub accumulated_down_time_hours: f64,
2001}
2002
2003#[repr(C)]
2005#[derive(Clone, Copy)]
2006pub struct PioScucReactiveCapabilityView {
2007 pub kind: PioStringView,
2008 pub reactive_power_at_zero_active_power_pu: f64,
2009 pub reactive_power_at_zero_active_power_min_pu: f64,
2010 pub reactive_power_at_zero_active_power_max_pu: f64,
2011 pub slope: f64,
2012 pub slope_min: f64,
2013 pub slope_max: f64,
2014}
2015
2016#[repr(C)]
2018#[derive(Clone, Copy)]
2019pub struct PioScucDeviceView {
2020 pub id: PioComponentIdView,
2021 pub kind: PioStringView,
2022 pub initial_on_status: bool,
2023 pub on_cost: f64,
2024 pub startup_cost: f64,
2025 pub shutdown_cost: f64,
2026 pub minimum_up_time_hours: f64,
2027 pub minimum_down_time_hours: f64,
2028 pub ramp_limits: PioScucRampLimitsView,
2029 pub reserve_limits: PioScucReserveLimitsView,
2030 pub initial_commitment: PioScucInitialCommitmentView,
2031 pub reactive_capability: PioScucReactiveCapabilityView,
2032 pub period_count: usize,
2033 pub startup_cost_adjustment_count: usize,
2034 pub startup_limit_count: usize,
2035 pub energy_upper_bound_count: usize,
2036 pub energy_lower_bound_count: usize,
2037}
2038
2039#[repr(C)]
2041#[derive(Clone, Copy)]
2042pub struct PioScucStartupCostAdjustmentView {
2043 pub cost: f64,
2044 pub maximum_down_time_hours: f64,
2045}
2046
2047#[repr(C)]
2049#[derive(Clone, Copy)]
2050pub struct PioScucDevicePeriodView {
2051 pub on_status_min: bool,
2052 pub on_status_max: bool,
2053 pub active_power_min_pu: f64,
2054 pub active_power_max_pu: f64,
2055 pub reactive_power_min_pu: f64,
2056 pub reactive_power_max_pu: f64,
2057 pub energy_cost_block_count: usize,
2058 pub reserve_costs: PioScucReserveCostsView,
2059}
2060
2061#[repr(C)]
2063#[derive(Clone, Copy)]
2064pub struct PioScucStartupLimitView {
2065 pub start_time_hours: f64,
2066 pub end_time_hours: f64,
2067 pub maximum_startups: u64,
2068}
2069
2070#[repr(C)]
2072#[derive(Clone, Copy)]
2073pub struct PioScucEnergyRequirementView {
2074 pub start_time_hours: f64,
2075 pub end_time_hours: f64,
2076 pub energy_pu: f64,
2077}
2078
2079#[repr(C)]
2081#[derive(Clone, Copy)]
2082pub struct PioScucEnergyCostBlockView {
2083 pub marginal_cost: f64,
2084 pub block_size_pu: f64,
2085}
2086
2087#[repr(C)]
2089#[derive(Clone, Copy)]
2090pub struct PioScucReserveCostsView {
2091 pub regulation_up: f64,
2092 pub regulation_down: f64,
2093 pub synchronized: f64,
2094 pub nonsynchronized: f64,
2095 pub ramping_up_online: f64,
2096 pub ramping_down_online: f64,
2097 pub ramping_up_offline: f64,
2098 pub ramping_down_offline: f64,
2099 pub reactive_up: f64,
2100 pub reactive_down: f64,
2101}
2102
2103#[repr(C)]
2105#[derive(Clone, Copy)]
2106pub struct PioScucShuntView {
2107 pub id: PioComponentIdView,
2108 pub conductance_per_step_pu: f64,
2109 pub susceptance_per_step_pu: f64,
2110 pub step_min: i64,
2111 pub step_max: i64,
2112 pub initial_step: i64,
2113}
2114
2115#[repr(C)]
2117#[derive(Clone, Copy)]
2118pub struct PioScucBranchSwitchingCostView {
2119 pub id: PioComponentIdView,
2120 pub connection_cost: f64,
2121 pub disconnection_cost: f64,
2122}
2123
2124#[repr(C)]
2126#[derive(Clone, Copy)]
2127pub struct PioScucTransformerControlView {
2128 pub id: PioComponentIdView,
2129 pub tap_ratio_min: f64,
2130 pub tap_ratio_max: f64,
2131 pub phase_shift_min_radians: f64,
2132 pub phase_shift_max_radians: f64,
2133}
2134
2135#[repr(C)]
2137#[derive(Clone, Copy)]
2138pub struct PioScucActiveReserveZoneView {
2139 pub id: PioComponentIdView,
2140 pub regulation_up_requirement_fraction: f64,
2141 pub regulation_down_requirement_fraction: f64,
2142 pub synchronized_requirement_fraction: f64,
2143 pub nonsynchronized_requirement_fraction: f64,
2144 pub regulation_up_violation_cost: f64,
2145 pub regulation_down_violation_cost: f64,
2146 pub synchronized_violation_cost: f64,
2147 pub nonsynchronized_violation_cost: f64,
2148 pub ramping_up_violation_cost: f64,
2149 pub ramping_down_violation_cost: f64,
2150 pub period_count: usize,
2151 pub bus_count: usize,
2152}
2153
2154#[repr(C)]
2156#[derive(Clone, Copy)]
2157pub struct PioScucActiveReservePeriodView {
2158 pub ramping_up_requirement_pu: f64,
2159 pub ramping_down_requirement_pu: f64,
2160}
2161
2162#[repr(C)]
2164#[derive(Clone, Copy)]
2165pub struct PioScucReactiveReserveZoneView {
2166 pub id: PioComponentIdView,
2167 pub reactive_up_violation_cost: f64,
2168 pub reactive_down_violation_cost: f64,
2169 pub period_count: usize,
2170 pub bus_count: usize,
2171}
2172
2173#[repr(C)]
2175#[derive(Clone, Copy)]
2176pub struct PioScucReactiveReservePeriodView {
2177 pub reactive_up_requirement_pu: f64,
2178 pub reactive_down_requirement_pu: f64,
2179}
2180
2181#[repr(C)]
2183#[derive(Clone, Copy)]
2184pub struct PioScucContingencyView {
2185 pub id: PioComponentIdView,
2186 pub component_count: usize,
2187}
2188
2189#[repr(C)]
2191#[derive(Clone, Copy)]
2192pub struct PioScucContingencyComponentView {
2193 pub id: PioComponentIdView,
2194}
2195
2196const fn assert_send_sync<T: Send + Sync>() {}
2201
2202#[repr(transparent)]
2203struct HandleBox<T> {
2204 inner: Arc<T>,
2205}
2206
2207fn handle_new<T>(value: T) -> *mut HandleBox<T> {
2208 Box::into_raw(Box::new(HandleBox {
2209 inner: Arc::new(value),
2210 }))
2211}
2212
2213fn handle_from_arc<T>(inner: Arc<T>) -> *mut HandleBox<T> {
2214 Box::into_raw(Box::new(HandleBox { inner }))
2215}
2216
2217unsafe fn handle_get<'a, T>(raw: *const HandleBox<T>) -> Option<&'a T> {
2218 unsafe { raw.as_ref() }.map(|handle| handle.inner.as_ref())
2219}
2220
2221unsafe fn handle_arc<T>(raw: *const HandleBox<T>) -> Option<Arc<T>> {
2222 unsafe { raw.as_ref() }.map(|handle| Arc::clone(&handle.inner))
2223}
2224
2225unsafe fn handle_retain<T>(raw: *const HandleBox<T>) -> *mut HandleBox<T> {
2226 unsafe { handle_arc(raw) }.map_or(std::ptr::null_mut(), handle_from_arc)
2227}
2228
2229unsafe fn handle_release<T>(raw: *mut HandleBox<T>) {
2230 if !raw.is_null() {
2231 drop(unsafe { Box::from_raw(raw) });
2232 }
2233}
2234
2235macro_rules! opaque_handle {
2236 ($(#[$doc:meta])* $name:ident, $inner:ty) => {
2237 $(#[$doc])*
2238 #[repr(transparent)]
2239 pub struct $name(HandleBox<$inner>);
2240 const _: () = assert_send_sync::<$inner>();
2241
2242 #[allow(dead_code)]
2243 impl $name {
2244 fn new_raw(value: $inner) -> *mut Self {
2245 handle_new(value).cast()
2246 }
2247
2248 fn from_arc(inner: Arc<$inner>) -> *mut Self {
2249 handle_from_arc(inner).cast()
2250 }
2251
2252 unsafe fn get<'a>(raw: *const Self) -> Option<&'a $inner> {
2253 unsafe { handle_get(raw.cast()) }
2254 }
2255
2256 unsafe fn arc(raw: *const Self) -> Option<Arc<$inner>> {
2257 unsafe { handle_arc(raw.cast()) }
2258 }
2259
2260 unsafe fn retain_raw(raw: *const Self) -> *mut Self {
2261 unsafe { handle_retain(raw.cast::<HandleBox<$inner>>()) }.cast::<Self>()
2262 }
2263
2264 unsafe fn release_raw(raw: *mut Self) {
2265 unsafe { handle_release(raw.cast::<HandleBox<$inner>>()) }
2266 }
2267 }
2268 };
2269}
2270
2271struct ErrorInner {
2274 code: String,
2275 message: String,
2276 diagnostics: Arc<DiagnosticsInner>,
2277}
2278
2279struct DiagnosticsInner {
2280 owner: DiagnosticsOwner,
2281}
2282
2283enum DiagnosticsOwner {
2284 Owned(Vec<Diagnostic>),
2285 Module(Arc<ModuleInner>),
2286}
2287
2288impl DiagnosticsInner {
2289 fn records(&self) -> &[Diagnostic] {
2290 match &self.owner {
2291 DiagnosticsOwner::Owned(records) => records,
2292 DiagnosticsOwner::Module(module) => module.module.diagnostics(),
2293 }
2294 }
2295}
2296
2297opaque_handle!(
2298 PioError,
2300 ErrorInner
2301);
2302opaque_handle!(
2303 PioDiagnostics,
2305 DiagnosticsInner
2306);
2307
2308fn boundary_diagnostic(
2309 info: &'static powerio_core::DiagnosticInfo,
2310 message: impl Into<String>,
2311) -> Diagnostic {
2312 Diagnostic::of(info, message)
2313}
2314
2315fn error_from_diagnostics(message: String, mut records: Vec<Diagnostic>) -> *mut PioError {
2316 if records.is_empty() {
2317 records.push(boundary_diagnostic(
2318 &codes::BIND_CAPI_UNCODED_FAILURE,
2319 message.clone(),
2320 ));
2321 }
2322 let code = records[0].code().to_owned();
2323 PioError::new_raw(ErrorInner {
2324 code,
2325 message,
2326 diagnostics: Arc::new(DiagnosticsInner {
2327 owner: DiagnosticsOwner::Owned(records),
2328 }),
2329 })
2330}
2331
2332fn error_from_core(error: &powerio_core::Error) -> *mut PioError {
2333 error_from_diagnostics(error.to_string(), error.diagnostics().to_vec())
2334}
2335
2336fn error_from_tx(error: &powerio_tx::Error) -> *mut PioError {
2337 let diagnostic = Diagnostic::of(error.code(), error.to_string());
2338 error_from_diagnostics(error.to_string(), vec![diagnostic])
2339}
2340
2341fn error_from_matrix(error: &powerio_matrix::Error) -> *mut PioError {
2342 let diagnostic = Diagnostic::of(error.code(), error.to_string());
2343 error_from_diagnostics(error.to_string(), vec![diagnostic])
2344}
2345
2346fn boundary_error(
2347 info: &'static powerio_core::DiagnosticInfo,
2348 message: impl Into<String>,
2349) -> *mut PioError {
2350 let diagnostic = boundary_diagnostic(info, message);
2351 error_from_diagnostics(
2352 powerio_core::render_diagnostic(&diagnostic),
2353 vec![diagnostic],
2354 )
2355}
2356
2357unsafe fn store_error(slot: *mut *mut PioError, error: *mut PioError) {
2358 if slot.is_null() {
2359 unsafe { PioError::release_raw(error) };
2360 } else {
2361 unsafe { *slot = error };
2362 }
2363}
2364
2365unsafe fn entry<R>(
2366 error: *mut *mut PioError,
2367 fallback: R,
2368 operation: impl FnOnce() -> Result<R, *mut PioError>,
2369) -> R {
2370 if !error.is_null() {
2371 unsafe { *error = std::ptr::null_mut() };
2372 }
2373 match catch_unwind(AssertUnwindSafe(operation)) {
2374 Ok(Ok(value)) => value,
2375 Ok(Err(failure)) => {
2376 unsafe { store_error(error, failure) };
2377 fallback
2378 }
2379 Err(_) => {
2380 let failure = boundary_error(
2381 &codes::BIND_CAPI_PANIC,
2382 "the operation panicked and made no C-visible change",
2383 );
2384 unsafe { store_error(error, failure) };
2385 fallback
2386 }
2387 }
2388}
2389
2390unsafe fn input_bytes<'a>(
2391 data: *const u8,
2392 len: usize,
2393 argument: &str,
2394) -> Result<&'a [u8], *mut PioError> {
2395 if data.is_null() {
2396 if len == 0 {
2397 return Ok(&[]);
2398 }
2399 return Err(boundary_error(
2400 &codes::BIND_CAPI_NULL_ARGUMENT,
2401 format!("{argument} is NULL with a nonzero length"),
2402 ));
2403 }
2404 Ok(unsafe { std::slice::from_raw_parts(data, len) })
2405}
2406
2407unsafe fn input_str<'a>(
2408 data: *const c_char,
2409 len: usize,
2410 argument: &str,
2411) -> Result<&'a str, *mut PioError> {
2412 let bytes = unsafe { input_bytes(data.cast(), len, argument) }?;
2413 std::str::from_utf8(bytes).map_err(|_| {
2414 boundary_error(
2415 &codes::BIND_CAPI_INVALID_UTF8,
2416 format!("{argument} is not valid UTF-8"),
2417 )
2418 })
2419}
2420
2421unsafe fn required_str<'a>(
2422 data: *const c_char,
2423 len: usize,
2424 argument: &str,
2425) -> Result<&'a str, *mut PioError> {
2426 if data.is_null() || len == 0 {
2427 return Err(boundary_error(
2428 &codes::BIND_CAPI_NULL_ARGUMENT,
2429 format!("{argument} is required"),
2430 ));
2431 }
2432 unsafe { input_str(data, len, argument) }
2433}
2434
2435unsafe fn optional_str<'a>(
2436 data: *const c_char,
2437 len: usize,
2438 argument: &str,
2439) -> Result<Option<&'a str>, *mut PioError> {
2440 if data.is_null() {
2441 if len == 0 {
2442 return Ok(None);
2443 }
2444 return Err(boundary_error(
2445 &codes::BIND_CAPI_NULL_ARGUMENT,
2446 format!("{argument} is NULL with a nonzero length"),
2447 ));
2448 }
2449 unsafe { input_str(data, len, argument) }.map(Some)
2450}
2451
2452#[unsafe(no_mangle)]
2454pub extern "C" fn pio_abi_version() -> u32 {
2455 PIO_ABI_VERSION
2456}
2457
2458#[unsafe(no_mangle)]
2460pub extern "C" fn pio_version() -> PioStringView {
2461 PioStringView::new(powerio::VERSION)
2462}
2463
2464#[unsafe(no_mangle)]
2469pub unsafe extern "C" fn pio_error_code(error: *const PioError) -> PioStringView {
2470 unsafe { PioError::get(error) }.map_or(PioStringView::EMPTY, |error| {
2471 PioStringView::new(&error.code)
2472 })
2473}
2474
2475#[unsafe(no_mangle)]
2480pub unsafe extern "C" fn pio_error_message(error: *const PioError) -> PioStringView {
2481 unsafe { PioError::get(error) }.map_or(PioStringView::EMPTY, |error| {
2482 PioStringView::new(&error.message)
2483 })
2484}
2485
2486#[unsafe(no_mangle)]
2491pub unsafe extern "C" fn pio_error_diagnostics(error: *const PioError) -> *mut PioDiagnostics {
2492 unsafe { PioError::get(error) }.map_or(std::ptr::null_mut(), |error| {
2493 PioDiagnostics::from_arc(Arc::clone(&error.diagnostics))
2494 })
2495}
2496
2497#[unsafe(no_mangle)]
2502pub unsafe extern "C" fn pio_error_retain(error: *const PioError) -> *mut PioError {
2503 unsafe { PioError::retain_raw(error) }
2504}
2505
2506#[unsafe(no_mangle)]
2511pub unsafe extern "C" fn pio_error_release(error: *mut PioError) {
2512 unsafe { PioError::release_raw(error) };
2513}
2514
2515#[unsafe(no_mangle)]
2519pub unsafe extern "C" fn pio_diagnostics_len(diagnostics: *const PioDiagnostics) -> usize {
2520 unsafe { PioDiagnostics::get(diagnostics) }.map_or(0, |values| values.records().len())
2521}
2522
2523#[unsafe(no_mangle)]
2527pub unsafe extern "C" fn pio_diagnostic_code(
2528 diagnostics: *const PioDiagnostics,
2529 index: usize,
2530) -> PioStringView {
2531 unsafe { PioDiagnostics::get(diagnostics) }
2532 .and_then(|values| values.records().get(index))
2533 .map_or(PioStringView::EMPTY, |record| {
2534 PioStringView::new(record.code())
2535 })
2536}
2537
2538#[unsafe(no_mangle)]
2542pub unsafe extern "C" fn pio_diagnostic_severity(
2543 diagnostics: *const PioDiagnostics,
2544 index: usize,
2545) -> PioStringView {
2546 unsafe { PioDiagnostics::get(diagnostics) }
2547 .and_then(|values| values.records().get(index))
2548 .map_or(PioStringView::EMPTY, |record| {
2549 PioStringView::new(record.severity().as_str())
2550 })
2551}
2552
2553#[unsafe(no_mangle)]
2557pub unsafe extern "C" fn pio_diagnostic_message(
2558 diagnostics: *const PioDiagnostics,
2559 index: usize,
2560) -> PioStringView {
2561 unsafe { PioDiagnostics::get(diagnostics) }
2562 .and_then(|values| values.records().get(index))
2563 .map_or(PioStringView::EMPTY, |record| {
2564 PioStringView::new(record.message())
2565 })
2566}
2567
2568#[unsafe(no_mangle)]
2573pub unsafe extern "C" fn pio_diagnostic_has_id(
2574 diagnostics: *const PioDiagnostics,
2575 index: usize,
2576) -> bool {
2577 unsafe { PioDiagnostics::get(diagnostics) }
2578 .and_then(|values| values.records().get(index))
2579 .is_some_and(|record| record.id().is_some())
2580}
2581
2582#[unsafe(no_mangle)]
2587pub unsafe extern "C" fn pio_diagnostic_id(
2588 diagnostics: *const PioDiagnostics,
2589 index: usize,
2590) -> PioStringView {
2591 unsafe { PioDiagnostics::get(diagnostics) }
2592 .and_then(|values| values.records().get(index))
2593 .and_then(Diagnostic::id)
2594 .map_or(PioStringView::EMPTY, |id| PioStringView::new(id.as_str()))
2595}
2596
2597#[unsafe(no_mangle)]
2602pub unsafe extern "C" fn pio_diagnostic_has_target(
2603 diagnostics: *const PioDiagnostics,
2604 index: usize,
2605) -> bool {
2606 unsafe { PioDiagnostics::get(diagnostics) }
2607 .and_then(|values| values.records().get(index))
2608 .is_some_and(|record| record.target().is_some())
2609}
2610
2611#[unsafe(no_mangle)]
2616pub unsafe extern "C" fn pio_diagnostic_target(
2617 diagnostics: *const PioDiagnostics,
2618 index: usize,
2619) -> PioStringView {
2620 unsafe { PioDiagnostics::get(diagnostics) }
2621 .and_then(|values| values.records().get(index))
2622 .and_then(Diagnostic::target)
2623 .map_or(PioStringView::EMPTY, PioStringView::new)
2624}
2625
2626#[unsafe(no_mangle)]
2631pub unsafe extern "C" fn pio_diagnostic_has_suggested_action(
2632 diagnostics: *const PioDiagnostics,
2633 index: usize,
2634) -> bool {
2635 unsafe { PioDiagnostics::get(diagnostics) }
2636 .and_then(|values| values.records().get(index))
2637 .is_some_and(|record| record.suggested_action().is_some())
2638}
2639
2640#[unsafe(no_mangle)]
2645pub unsafe extern "C" fn pio_diagnostic_suggested_action(
2646 diagnostics: *const PioDiagnostics,
2647 index: usize,
2648) -> PioStringView {
2649 unsafe { PioDiagnostics::get(diagnostics) }
2650 .and_then(|values| values.records().get(index))
2651 .and_then(Diagnostic::suggested_action)
2652 .map_or(PioStringView::EMPTY, PioStringView::new)
2653}
2654
2655#[unsafe(no_mangle)]
2660pub unsafe extern "C" fn pio_diagnostic_n_spans(
2661 diagnostics: *const PioDiagnostics,
2662 index: usize,
2663) -> usize {
2664 unsafe { PioDiagnostics::get(diagnostics) }
2665 .and_then(|values| values.records().get(index))
2666 .map_or(0, |record| record.spans().len())
2667}
2668
2669#[unsafe(no_mangle)]
2674pub unsafe extern "C" fn pio_diagnostic_span(
2675 diagnostics: *const PioDiagnostics,
2676 index: usize,
2677 span_index: usize,
2678 output: *mut PioDiagnosticSpanView,
2679 error: *mut *mut PioError,
2680) -> bool {
2681 unsafe {
2682 entry(error, false, || {
2683 let values = PioDiagnostics::get(diagnostics).ok_or_else(|| {
2684 boundary_error(
2685 &codes::BIND_CAPI_NULL_HANDLE,
2686 "PioDiagnostics must not be NULL",
2687 )
2688 })?;
2689 let record = values.records().get(index).ok_or_else(|| {
2690 boundary_error(
2691 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
2692 format!("diagnostic index {index} is out of range"),
2693 )
2694 })?;
2695 let span = record.spans().get(span_index).ok_or_else(|| {
2696 boundary_error(
2697 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
2698 format!("diagnostic span index {span_index} is out of range"),
2699 )
2700 })?;
2701 *require_output(output, "output")? = PioDiagnosticSpanView {
2702 source: PioStringView::new(span.source().as_str()),
2703 byte_start: span.byte_start(),
2704 byte_end: span.byte_end(),
2705 };
2706 Ok(true)
2707 })
2708 }
2709}
2710
2711#[unsafe(no_mangle)]
2716pub unsafe extern "C" fn pio_diagnostic_n_related(
2717 diagnostics: *const PioDiagnostics,
2718 index: usize,
2719) -> usize {
2720 unsafe { PioDiagnostics::get(diagnostics) }
2721 .and_then(|values| values.records().get(index))
2722 .map_or(0, |record| record.related().len())
2723}
2724
2725#[unsafe(no_mangle)]
2730pub unsafe extern "C" fn pio_diagnostic_related(
2731 diagnostics: *const PioDiagnostics,
2732 index: usize,
2733 related_index: usize,
2734) -> PioStringView {
2735 unsafe { PioDiagnostics::get(diagnostics) }
2736 .and_then(|values| values.records().get(index))
2737 .and_then(|record| record.related().get(related_index))
2738 .map_or(PioStringView::EMPTY, |id| PioStringView::new(id.as_str()))
2739}
2740
2741#[unsafe(no_mangle)]
2746pub unsafe extern "C" fn pio_diagnostic_details_json(
2747 diagnostics: *const PioDiagnostics,
2748 index: usize,
2749 error: *mut *mut PioError,
2750) -> *mut PioString {
2751 unsafe {
2752 entry(error, std::ptr::null_mut(), || {
2753 let values = PioDiagnostics::get(diagnostics).ok_or_else(|| {
2754 boundary_error(
2755 &codes::BIND_CAPI_NULL_HANDLE,
2756 "PioDiagnostics must not be NULL",
2757 )
2758 })?;
2759 let record = values.records().get(index).ok_or_else(|| {
2760 boundary_error(
2761 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
2762 format!("diagnostic index {index} is out of range"),
2763 )
2764 })?;
2765 serde_json::to_string(record.details())
2766 .map(|text| PioString::new_raw(StringInner { text }))
2767 .map_err(|failure| {
2768 boundary_error(
2769 &codes::EMIT_CAPI_SERIALIZE_FAILED,
2770 format!("cannot serialize diagnostic details: {failure}"),
2771 )
2772 })
2773 })
2774 }
2775}
2776
2777#[unsafe(no_mangle)]
2782pub unsafe extern "C" fn pio_diagnostics_retain(
2783 diagnostics: *const PioDiagnostics,
2784) -> *mut PioDiagnostics {
2785 unsafe { PioDiagnostics::retain_raw(diagnostics) }
2786}
2787
2788#[unsafe(no_mangle)]
2793pub unsafe extern "C" fn pio_diagnostics_release(diagnostics: *mut PioDiagnostics) {
2794 unsafe { PioDiagnostics::release_raw(diagnostics) };
2795}
2796
2797opaque_handle!(
2800 PioSource,
2802 Source
2803);
2804
2805enum DestinationSpec {
2806 Path(PathBuf),
2807 Memory(String),
2808}
2809
2810impl DestinationSpec {
2811 fn build(&self) -> Result<Destination, powerio_core::Error> {
2812 match self {
2813 Self::Path(path) => Ok(Destination::path(path)),
2814 Self::Memory(root) => Destination::memory(root.clone()),
2815 }
2816 }
2817}
2818
2819opaque_handle!(
2820 PioDestination,
2822 DestinationSpec
2823);
2824
2825struct GeoLayerInner {
2826 layer: powerio::GeoLayer,
2827 diagnostics: Vec<Diagnostic>,
2828}
2829
2830opaque_handle!(
2831 PioGeoLayer,
2833 GeoLayerInner
2834);
2835opaque_handle!(
2836 PioGeoApplyReport,
2838 powerio::GeoApplyReport
2839);
2840
2841enum ContingencySource<T> {
2846 Owned(T),
2847 Value(ValueInner),
2848}
2849
2850struct ContingencySetInner {
2851 source: ContingencySource<powerio::ContingencySet>,
2852 diagnostics: Vec<Diagnostic>,
2855}
2856
2857impl ContingencySetInner {
2858 fn set(&self) -> Option<&powerio::ContingencySet> {
2859 match &self.source {
2860 ContingencySource::Owned(set) => Some(set),
2861 ContingencySource::Value(value) => match value.value()? {
2862 PioValue::ContingencySet(set) => Some(set),
2863 _ => None,
2864 },
2865 }
2866 }
2867}
2868
2869struct SubsystemSetInner {
2870 source: ContingencySource<powerio::SubsystemSet>,
2871}
2872
2873impl SubsystemSetInner {
2874 fn set(&self) -> Option<&powerio::SubsystemSet> {
2875 match &self.source {
2876 ContingencySource::Owned(set) => Some(set),
2877 ContingencySource::Value(value) => match value.value()? {
2878 PioValue::SubsystemSet(set) => Some(set),
2879 _ => None,
2880 },
2881 }
2882 }
2883}
2884
2885struct MonitoredSetInner {
2886 source: ContingencySource<powerio::MonitoredSet>,
2887}
2888
2889impl MonitoredSetInner {
2890 fn set(&self) -> Option<&powerio::MonitoredSet> {
2891 match &self.source {
2892 ContingencySource::Owned(set) => Some(set),
2893 ContingencySource::Value(value) => match value.value()? {
2894 PioValue::MonitoredSet(set) => Some(set),
2895 _ => None,
2896 },
2897 }
2898 }
2899}
2900
2901struct ContingencyResolutionInner {
2905 resolution: powerio::ContingencyResolution,
2906 unresolved_actions: Vec<Vec<String>>,
2907}
2908
2909impl ContingencyResolutionInner {
2910 fn new(resolution: powerio::ContingencyResolution) -> Self {
2911 let unresolved_actions = resolution
2912 .cases
2913 .iter()
2914 .map(|case| {
2915 case.unresolved
2916 .iter()
2917 .map(|unresolved| unresolved.action.to_con_statement())
2918 .collect()
2919 })
2920 .collect();
2921 Self {
2922 resolution,
2923 unresolved_actions,
2924 }
2925 }
2926}
2927
2928opaque_handle!(
2929 PioContingencySet,
2933 ContingencySetInner
2934);
2935opaque_handle!(
2936 PioSubsystemSet,
2939 SubsystemSetInner
2940);
2941opaque_handle!(
2942 PioMonitoredSet,
2945 MonitoredSetInner
2946);
2947opaque_handle!(
2948 PioContingencyResolution,
2950 ContingencyResolutionInner
2951);
2952
2953#[unsafe(no_mangle)]
2958pub unsafe extern "C" fn pio_source_open(
2959 path: *const c_char,
2960 path_len: usize,
2961 error: *mut *mut PioError,
2962) -> *mut PioSource {
2963 unsafe {
2964 entry(error, std::ptr::null_mut(), || {
2965 let path = required_str(path, path_len, "path")?;
2966 Source::open(path)
2967 .map(PioSource::new_raw)
2968 .map_err(|failure| error_from_core(&failure))
2969 })
2970 }
2971}
2972
2973#[unsafe(no_mangle)]
2978pub unsafe extern "C" fn pio_source_from_memory(
2979 name: *const c_char,
2980 name_len: usize,
2981 data: *const u8,
2982 data_len: usize,
2983 error: *mut *mut PioError,
2984) -> *mut PioSource {
2985 unsafe {
2986 entry(error, std::ptr::null_mut(), || {
2987 let name = required_str(name, name_len, "name")?;
2988 let bytes = input_bytes(data, data_len, "data")?;
2989 Source::from_memory(name, bytes.to_vec())
2990 .map(PioSource::new_raw)
2991 .map_err(|failure| error_from_core(&failure))
2992 })
2993 }
2994}
2995
2996#[unsafe(no_mangle)]
3001pub unsafe extern "C" fn pio_source_retain(source: *const PioSource) -> *mut PioSource {
3002 unsafe { PioSource::retain_raw(source) }
3003}
3004
3005#[unsafe(no_mangle)]
3010pub unsafe extern "C" fn pio_source_release(source: *mut PioSource) {
3011 unsafe { PioSource::release_raw(source) };
3012}
3013
3014#[unsafe(no_mangle)]
3019pub unsafe extern "C" fn pio_destination_path(
3020 path: *const c_char,
3021 path_len: usize,
3022 error: *mut *mut PioError,
3023) -> *mut PioDestination {
3024 unsafe {
3025 entry(error, std::ptr::null_mut(), || {
3026 let path = required_str(path, path_len, "path")?;
3027 Ok(PioDestination::new_raw(DestinationSpec::Path(
3028 PathBuf::from(path),
3029 )))
3030 })
3031 }
3032}
3033
3034#[unsafe(no_mangle)]
3039pub unsafe extern "C" fn pio_destination_memory(
3040 root: *const c_char,
3041 root_len: usize,
3042 error: *mut *mut PioError,
3043) -> *mut PioDestination {
3044 unsafe {
3045 entry(error, std::ptr::null_mut(), || {
3046 let root = required_str(root, root_len, "root")?;
3047 Destination::memory(root).map_err(|failure| error_from_core(&failure))?;
3048 Ok(PioDestination::new_raw(DestinationSpec::Memory(
3049 root.to_owned(),
3050 )))
3051 })
3052 }
3053}
3054
3055#[unsafe(no_mangle)]
3060pub unsafe extern "C" fn pio_destination_retain(
3061 destination: *const PioDestination,
3062) -> *mut PioDestination {
3063 unsafe { PioDestination::retain_raw(destination) }
3064}
3065
3066#[unsafe(no_mangle)]
3071pub unsafe extern "C" fn pio_destination_release(destination: *mut PioDestination) {
3072 unsafe { PioDestination::release_raw(destination) };
3073}
3074
3075#[unsafe(no_mangle)]
3080pub unsafe extern "C" fn pio_geo_layer_parse(
3081 source: *const PioSource,
3082 error: *mut *mut PioError,
3083) -> *mut PioGeoLayer {
3084 unsafe {
3085 entry(error, std::ptr::null_mut(), || {
3086 let source = PioSource::get(source).ok_or_else(|| {
3087 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioSource must not be NULL")
3088 })?;
3089 let buffer = source
3090 .primary_buffer()
3091 .map_err(|failure| error_from_core(&failure))?;
3092 let text = std::str::from_utf8(buffer.content_bytes()).map_err(|_| {
3093 boundary_error(
3094 &codes::BIND_CAPI_INVALID_UTF8,
3095 "a geographic sidecar must be valid UTF-8",
3096 )
3097 })?;
3098 powerio::GeoLayer::parse(text, Some(source.name()))
3099 .map(|parsed| {
3100 PioGeoLayer::new_raw(GeoLayerInner {
3101 layer: parsed.layer,
3102 diagnostics: parsed.diagnostics,
3103 })
3104 })
3105 .map_err(|failure| error_from_tx(&failure))
3106 })
3107 }
3108}
3109
3110#[unsafe(no_mangle)]
3115pub unsafe extern "C" fn pio_geo_layer_diagnostics(
3116 layer: *const PioGeoLayer,
3117) -> *mut PioDiagnostics {
3118 unsafe { PioGeoLayer::get(layer) }.map_or(std::ptr::null_mut(), |layer| {
3119 PioDiagnostics::new_raw(DiagnosticsInner {
3120 owner: DiagnosticsOwner::Owned(layer.diagnostics.clone()),
3121 })
3122 })
3123}
3124
3125#[unsafe(no_mangle)]
3133pub unsafe extern "C" fn pio_geo_layer_to_geojson(
3134 layer: *const PioGeoLayer,
3135 error: *mut *mut PioError,
3136) -> *mut PioString {
3137 unsafe {
3138 entry(error, std::ptr::null_mut(), || {
3139 let layer = PioGeoLayer::get(layer).ok_or_else(|| {
3140 boundary_error(
3141 &codes::BIND_CAPI_NULL_HANDLE,
3142 "PioGeoLayer must not be NULL",
3143 )
3144 })?;
3145 Ok(PioString::new_raw(StringInner {
3146 text: layer.layer.to_geojson(),
3147 }))
3148 })
3149 }
3150}
3151
3152#[unsafe(no_mangle)]
3157pub unsafe extern "C" fn pio_geo_layer_retain(layer: *const PioGeoLayer) -> *mut PioGeoLayer {
3158 unsafe { PioGeoLayer::retain_raw(layer) }
3159}
3160
3161#[unsafe(no_mangle)]
3166pub unsafe extern "C" fn pio_geo_layer_release(layer: *mut PioGeoLayer) {
3167 unsafe { PioGeoLayer::release_raw(layer) };
3168}
3169
3170unsafe fn contingency_source_text(
3173 source: *const PioSource,
3174 what: &str,
3175) -> Result<String, *mut PioError> {
3176 let source = unsafe { PioSource::get(source) }.ok_or_else(|| {
3177 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioSource must not be NULL")
3178 })?;
3179 let buffer = source
3180 .primary_buffer()
3181 .map_err(|failure| error_from_core(&failure))?;
3182 std::str::from_utf8(buffer.content_bytes())
3183 .map(str::to_owned)
3184 .map_err(|_| {
3185 boundary_error(
3186 &codes::BIND_CAPI_INVALID_UTF8,
3187 format!("{what} must be valid UTF-8"),
3188 )
3189 })
3190}
3191
3192unsafe fn require_contingency_set<'a>(
3193 set: *const PioContingencySet,
3194) -> Result<&'a powerio::ContingencySet, *mut PioError> {
3195 unsafe { PioContingencySet::get(set) }
3196 .and_then(ContingencySetInner::set)
3197 .ok_or_else(|| {
3198 boundary_error(
3199 &codes::BIND_CAPI_NULL_HANDLE,
3200 "PioContingencySet must not be NULL",
3201 )
3202 })
3203}
3204
3205unsafe fn require_subsystem_set<'a>(
3206 set: *const PioSubsystemSet,
3207) -> Result<&'a powerio::SubsystemSet, *mut PioError> {
3208 unsafe { PioSubsystemSet::get(set) }
3209 .and_then(SubsystemSetInner::set)
3210 .ok_or_else(|| {
3211 boundary_error(
3212 &codes::BIND_CAPI_NULL_HANDLE,
3213 "PioSubsystemSet must not be NULL",
3214 )
3215 })
3216}
3217
3218unsafe fn require_monitored_set<'a>(
3219 set: *const PioMonitoredSet,
3220) -> Result<&'a powerio::MonitoredSet, *mut PioError> {
3221 unsafe { PioMonitoredSet::get(set) }
3222 .and_then(MonitoredSetInner::set)
3223 .ok_or_else(|| {
3224 boundary_error(
3225 &codes::BIND_CAPI_NULL_HANDLE,
3226 "PioMonitoredSet must not be NULL",
3227 )
3228 })
3229}
3230
3231unsafe fn require_resolution<'a>(
3232 resolution: *const PioContingencyResolution,
3233) -> Result<&'a ContingencyResolutionInner, *mut PioError> {
3234 unsafe { PioContingencyResolution::get(resolution) }.ok_or_else(|| {
3235 boundary_error(
3236 &codes::BIND_CAPI_NULL_HANDLE,
3237 "PioContingencyResolution must not be NULL",
3238 )
3239 })
3240}
3241
3242unsafe fn require_resolved_case<'a>(
3243 resolution: *const PioContingencyResolution,
3244 index: usize,
3245) -> Result<&'a powerio::ResolvedCase, *mut PioError> {
3246 unsafe { require_resolution(resolution) }?
3247 .resolution
3248 .cases
3249 .get(index)
3250 .ok_or_else(|| {
3251 boundary_error(
3252 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3253 format!("contingency case index {index} is out of range"),
3254 )
3255 })
3256}
3257
3258#[unsafe(no_mangle)]
3263pub unsafe extern "C" fn pio_contingency_set_parse(
3264 source: *const PioSource,
3265 error: *mut *mut PioError,
3266) -> *mut PioContingencySet {
3267 unsafe {
3268 entry(error, std::ptr::null_mut(), || {
3269 let text = contingency_source_text(source, "a contingency description file")?;
3270 powerio::ContingencySet::parse(&text)
3271 .map(|parsed| {
3272 PioContingencySet::new_raw(ContingencySetInner {
3273 source: ContingencySource::Owned(parsed.set),
3274 diagnostics: parsed.diagnostics,
3275 })
3276 })
3277 .map_err(|failure| error_from_tx(&failure))
3278 })
3279 }
3280}
3281
3282#[unsafe(no_mangle)]
3288pub unsafe extern "C" fn pio_contingency_set_diagnostics(
3289 set: *const PioContingencySet,
3290) -> *mut PioDiagnostics {
3291 unsafe { PioContingencySet::get(set) }.map_or(std::ptr::null_mut(), |set| {
3292 PioDiagnostics::new_raw(DiagnosticsInner {
3293 owner: DiagnosticsOwner::Owned(set.diagnostics.clone()),
3294 })
3295 })
3296}
3297
3298#[unsafe(no_mangle)]
3302pub unsafe extern "C" fn pio_contingency_set_case_count(set: *const PioContingencySet) -> usize {
3303 unsafe { PioContingencySet::get(set) }
3304 .and_then(ContingencySetInner::set)
3305 .map_or(0, |set| set.cases.len())
3306}
3307
3308#[unsafe(no_mangle)]
3313pub unsafe extern "C" fn pio_contingency_set_case_name(
3314 set: *const PioContingencySet,
3315 index: usize,
3316 error: *mut *mut PioError,
3317) -> PioStringView {
3318 unsafe {
3319 entry(error, PioStringView::EMPTY, || {
3320 let set = require_contingency_set(set)?;
3321 let case = set.cases.get(index).ok_or_else(|| {
3322 boundary_error(
3323 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3324 format!("contingency case index {index} is out of range"),
3325 )
3326 })?;
3327 Ok(PioStringView::new(&case.name))
3328 })
3329 }
3330}
3331
3332#[unsafe(no_mangle)]
3339pub unsafe extern "C" fn pio_contingency_set_to_con(
3340 set: *const PioContingencySet,
3341 error: *mut *mut PioError,
3342) -> *mut PioString {
3343 unsafe {
3344 entry(error, std::ptr::null_mut(), || {
3345 let set = require_contingency_set(set)?;
3346 Ok(PioString::new_raw(StringInner { text: set.to_con() }))
3347 })
3348 }
3349}
3350
3351#[unsafe(no_mangle)]
3356pub unsafe extern "C" fn pio_contingency_set_retain(
3357 set: *const PioContingencySet,
3358) -> *mut PioContingencySet {
3359 unsafe { PioContingencySet::retain_raw(set) }
3360}
3361
3362#[unsafe(no_mangle)]
3367pub unsafe extern "C" fn pio_contingency_set_release(set: *mut PioContingencySet) {
3368 unsafe { PioContingencySet::release_raw(set) };
3369}
3370
3371#[unsafe(no_mangle)]
3375pub unsafe extern "C" fn pio_subsystem_set_count(set: *const PioSubsystemSet) -> usize {
3376 unsafe { PioSubsystemSet::get(set) }
3377 .and_then(SubsystemSetInner::set)
3378 .map_or(0, |set| set.subsystems.len())
3379}
3380
3381#[unsafe(no_mangle)]
3386pub unsafe extern "C" fn pio_subsystem_set_name(
3387 set: *const PioSubsystemSet,
3388 index: usize,
3389 error: *mut *mut PioError,
3390) -> PioStringView {
3391 unsafe {
3392 entry(error, PioStringView::EMPTY, || {
3393 let set = require_subsystem_set(set)?;
3394 let subsystem = set.subsystems.get(index).ok_or_else(|| {
3395 boundary_error(
3396 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3397 format!("subsystem index {index} is out of range"),
3398 )
3399 })?;
3400 Ok(PioStringView::new(&subsystem.name))
3401 })
3402 }
3403}
3404
3405#[unsafe(no_mangle)]
3412pub unsafe extern "C" fn pio_subsystem_set_to_sub(
3413 set: *const PioSubsystemSet,
3414 error: *mut *mut PioError,
3415) -> *mut PioString {
3416 unsafe {
3417 entry(error, std::ptr::null_mut(), || {
3418 let set = require_subsystem_set(set)?;
3419 Ok(PioString::new_raw(StringInner { text: set.to_sub() }))
3420 })
3421 }
3422}
3423
3424#[unsafe(no_mangle)]
3429pub unsafe extern "C" fn pio_subsystem_set_retain(
3430 set: *const PioSubsystemSet,
3431) -> *mut PioSubsystemSet {
3432 unsafe { PioSubsystemSet::retain_raw(set) }
3433}
3434
3435#[unsafe(no_mangle)]
3440pub unsafe extern "C" fn pio_subsystem_set_release(set: *mut PioSubsystemSet) {
3441 unsafe { PioSubsystemSet::release_raw(set) };
3442}
3443
3444#[unsafe(no_mangle)]
3448pub unsafe extern "C" fn pio_monitored_set_statement_count(set: *const PioMonitoredSet) -> usize {
3449 unsafe { PioMonitoredSet::get(set) }
3450 .and_then(MonitoredSetInner::set)
3451 .map_or(0, |set| set.statements.len())
3452}
3453
3454#[unsafe(no_mangle)]
3461pub unsafe extern "C" fn pio_monitored_set_to_mon(
3462 set: *const PioMonitoredSet,
3463 error: *mut *mut PioError,
3464) -> *mut PioString {
3465 unsafe {
3466 entry(error, std::ptr::null_mut(), || {
3467 let set = require_monitored_set(set)?;
3468 Ok(PioString::new_raw(StringInner { text: set.to_mon() }))
3469 })
3470 }
3471}
3472
3473#[unsafe(no_mangle)]
3478pub unsafe extern "C" fn pio_monitored_set_retain(
3479 set: *const PioMonitoredSet,
3480) -> *mut PioMonitoredSet {
3481 unsafe { PioMonitoredSet::retain_raw(set) }
3482}
3483
3484#[unsafe(no_mangle)]
3489pub unsafe extern "C" fn pio_monitored_set_release(set: *mut PioMonitoredSet) {
3490 unsafe { PioMonitoredSet::release_raw(set) };
3491}
3492
3493#[unsafe(no_mangle)]
3502pub unsafe extern "C" fn pio_contingency_set_resolve(
3503 set: *const PioContingencySet,
3504 network: *const PioBalancedNetwork,
3505 error: *mut *mut PioError,
3506) -> *mut PioContingencyResolution {
3507 unsafe {
3508 entry(error, std::ptr::null_mut(), || {
3509 let set = require_contingency_set(set)?;
3510 let network = require_balanced_network(network)?;
3511 Ok(PioContingencyResolution::new_raw(
3512 ContingencyResolutionInner::new(set.resolve(network)),
3513 ))
3514 })
3515 }
3516}
3517
3518#[unsafe(no_mangle)]
3529pub unsafe extern "C" fn pio_contingency_set_expand(
3530 set: *const PioContingencySet,
3531 network: *const PioBalancedNetwork,
3532 subsystems: *const PioSubsystemSet,
3533 out_notes: *mut *mut PioDiagnostics,
3534 error: *mut *mut PioError,
3535) -> *mut PioContingencySet {
3536 unsafe {
3537 if !out_notes.is_null() {
3538 *out_notes = std::ptr::null_mut();
3539 }
3540 entry(error, std::ptr::null_mut(), || {
3541 let set = require_contingency_set(set)?;
3542 let network = require_balanced_network(network)?;
3543 let subsystems = require_subsystem_set(subsystems)?;
3544 let expanded = set.expand(network, subsystems);
3545 if !out_notes.is_null() {
3546 *out_notes = PioDiagnostics::new_raw(DiagnosticsInner {
3547 owner: DiagnosticsOwner::Owned(expanded.diagnostics),
3548 });
3549 }
3550 Ok(PioContingencySet::new_raw(ContingencySetInner {
3551 source: ContingencySource::Owned(expanded.set),
3552 diagnostics: Vec::new(),
3553 }))
3554 })
3555 }
3556}
3557
3558#[unsafe(no_mangle)]
3572pub unsafe extern "C" fn pio_balanced_network_select_subsystem_buses(
3573 network: *const PioBalancedNetwork,
3574 subsystems: *const PioSubsystemSet,
3575 name: *const c_char,
3576 name_len: usize,
3577 error: *mut *mut PioError,
3578) -> *mut PioVector {
3579 unsafe {
3580 entry(error, std::ptr::null_mut(), || {
3581 let network = require_balanced_network(network)?;
3582 let subsystems = require_subsystem_set(subsystems)?;
3583 let name = required_str(name, name_len, "name")?;
3584 let subsystem = subsystems.get(name).ok_or_else(|| {
3585 boundary_error(
3586 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3587 format!("subsystem '{name}' is not stated by the subsystem set"),
3588 )
3589 })?;
3590 let values = subsystem
3591 .select_buses(network)
3592 .iter()
3593 .map(|bus| bus.0 as f64)
3594 .collect();
3595 Ok(PioVector::new_raw(VectorInner { values }))
3596 })
3597 }
3598}
3599
3600#[unsafe(no_mangle)]
3604pub unsafe extern "C" fn pio_contingency_resolution_case_count(
3605 resolution: *const PioContingencyResolution,
3606) -> usize {
3607 unsafe { PioContingencyResolution::get(resolution) }
3608 .map_or(0, |inner| inner.resolution.cases.len())
3609}
3610
3611#[unsafe(no_mangle)]
3616pub unsafe extern "C" fn pio_contingency_resolution_resolved_count(
3617 resolution: *const PioContingencyResolution,
3618) -> usize {
3619 unsafe { PioContingencyResolution::get(resolution) }
3620 .map_or(0, |inner| inner.resolution.resolved)
3621}
3622
3623#[unsafe(no_mangle)]
3628pub unsafe extern "C" fn pio_contingency_resolution_unresolved_count(
3629 resolution: *const PioContingencyResolution,
3630) -> usize {
3631 unsafe { PioContingencyResolution::get(resolution) }
3632 .map_or(0, |inner| inner.resolution.unresolved)
3633}
3634
3635#[unsafe(no_mangle)]
3640pub unsafe extern "C" fn pio_contingency_resolution_unrecognized_statement_count(
3641 resolution: *const PioContingencyResolution,
3642) -> usize {
3643 unsafe { PioContingencyResolution::get(resolution) }
3644 .map_or(0, |inner| inner.resolution.unrecognized_statements)
3645}
3646
3647#[unsafe(no_mangle)]
3652pub unsafe extern "C" fn pio_contingency_resolution_case_name(
3653 resolution: *const PioContingencyResolution,
3654 index: usize,
3655 error: *mut *mut PioError,
3656) -> PioStringView {
3657 unsafe {
3658 entry(error, PioStringView::EMPTY, || {
3659 let case = require_resolved_case(resolution, index)?;
3660 Ok(PioStringView::new(&case.name))
3661 })
3662 }
3663}
3664
3665#[unsafe(no_mangle)]
3672pub unsafe extern "C" fn pio_contingency_resolution_case_is_resolved(
3673 resolution: *const PioContingencyResolution,
3674 index: usize,
3675) -> bool {
3676 unsafe { PioContingencyResolution::get(resolution) }
3677 .and_then(|inner| inner.resolution.cases.get(index))
3678 .is_some_and(powerio::ResolvedCase::is_resolved)
3679}
3680
3681#[unsafe(no_mangle)]
3686pub unsafe extern "C" fn pio_contingency_resolution_case_component_count(
3687 resolution: *const PioContingencyResolution,
3688 index: usize,
3689) -> usize {
3690 unsafe { PioContingencyResolution::get(resolution) }
3691 .and_then(|inner| inner.resolution.cases.get(index))
3692 .map_or(0, |case| case.components.len())
3693}
3694
3695#[unsafe(no_mangle)]
3705pub unsafe extern "C" fn pio_contingency_resolution_case_component(
3706 resolution: *const PioContingencyResolution,
3707 case_index: usize,
3708 component_index: usize,
3709 output: *mut PioContingencyComponentView,
3710 error: *mut *mut PioError,
3711) -> bool {
3712 unsafe {
3713 entry(error, false, || {
3714 let case = require_resolved_case(resolution, case_index)?;
3715 let component = case.components.get(component_index).ok_or_else(|| {
3716 boundary_error(
3717 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3718 format!(
3719 "contingency case {case_index} component index {component_index} is out of range"
3720 ),
3721 )
3722 })?;
3723 *require_output(output, "output")? = PioContingencyComponentView {
3724 id: PioComponentIdView {
3725 component_type: PioStringView::new(component.component_type),
3726 local_id: component
3727 .id
3728 .as_ref()
3729 .map_or(PioStringView::EMPTY, |id| PioStringView::new(id.local_id())),
3730 },
3731 row: component.row,
3732 in_service: component.in_service,
3733 };
3734 Ok(true)
3735 })
3736 }
3737}
3738
3739#[unsafe(no_mangle)]
3744pub unsafe extern "C" fn pio_contingency_resolution_case_unresolved_count(
3745 resolution: *const PioContingencyResolution,
3746 index: usize,
3747) -> usize {
3748 unsafe { PioContingencyResolution::get(resolution) }
3749 .and_then(|inner| inner.resolution.cases.get(index))
3750 .map_or(0, |case| case.unresolved.len())
3751}
3752
3753#[unsafe(no_mangle)]
3762pub unsafe extern "C" fn pio_contingency_resolution_case_unresolved_reason(
3763 resolution: *const PioContingencyResolution,
3764 case_index: usize,
3765 unresolved_index: usize,
3766 error: *mut *mut PioError,
3767) -> PioStringView {
3768 unsafe {
3769 entry(error, PioStringView::EMPTY, || {
3770 let case = require_resolved_case(resolution, case_index)?;
3771 let action = case.unresolved.get(unresolved_index).ok_or_else(|| {
3772 boundary_error(
3773 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3774 format!(
3775 "contingency case {case_index} unresolved index {unresolved_index} is out of range"
3776 ),
3777 )
3778 })?;
3779 Ok(PioStringView::new(action.reason.name()))
3780 })
3781 }
3782}
3783
3784#[unsafe(no_mangle)]
3792pub unsafe extern "C" fn pio_contingency_resolution_case_unresolved_action(
3793 resolution: *const PioContingencyResolution,
3794 case_index: usize,
3795 unresolved_index: usize,
3796 error: *mut *mut PioError,
3797) -> PioStringView {
3798 unsafe {
3799 entry(error, PioStringView::EMPTY, || {
3800 let inner = require_resolution(resolution)?;
3801 let case = inner.unresolved_actions.get(case_index).ok_or_else(|| {
3802 boundary_error(
3803 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3804 format!("contingency case index {case_index} is out of range"),
3805 )
3806 })?;
3807 let statement = case.get(unresolved_index).ok_or_else(|| {
3808 boundary_error(
3809 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
3810 format!(
3811 "contingency case {case_index} unresolved index {unresolved_index} is out of range"
3812 ),
3813 )
3814 })?;
3815 Ok(PioStringView::new(statement))
3816 })
3817 }
3818}
3819
3820#[unsafe(no_mangle)]
3825pub unsafe extern "C" fn pio_contingency_resolution_retain(
3826 resolution: *const PioContingencyResolution,
3827) -> *mut PioContingencyResolution {
3828 unsafe { PioContingencyResolution::retain_raw(resolution) }
3829}
3830
3831#[unsafe(no_mangle)]
3836pub unsafe extern "C" fn pio_contingency_resolution_release(
3837 resolution: *mut PioContingencyResolution,
3838) {
3839 unsafe { PioContingencyResolution::release_raw(resolution) };
3840}
3841
3842#[derive(Clone)]
3845struct ModuleInner {
3846 module: powerio::PioModule<PioValue>,
3847}
3848
3849opaque_handle!(
3850 PioModule,
3852 ModuleInner
3853);
3854
3855#[derive(Clone, Copy)]
3856enum ModuleJsonRoot {
3857 Extension(usize),
3858 HistoryParameter {
3859 history_index: usize,
3860 parameter_index: usize,
3861 },
3862}
3863
3864#[derive(Clone, Copy)]
3865enum JsonValueStep {
3866 Array(usize),
3867 Object(usize),
3868}
3869
3870struct JsonValueInner {
3871 owner: Arc<ModuleInner>,
3872 root: ModuleJsonRoot,
3873 steps: Vec<JsonValueStep>,
3874}
3875
3876impl JsonValueInner {
3877 fn child(&self, step: JsonValueStep) -> Self {
3878 let mut steps = self.steps.clone();
3879 steps.push(step);
3880 Self {
3881 owner: Arc::clone(&self.owner),
3882 root: self.root,
3883 steps,
3884 }
3885 }
3886
3887 fn value(&self) -> Option<&serde_json::Value> {
3888 let mut value = match self.root {
3889 ModuleJsonRoot::Extension(index) => {
3890 self.owner.module.extensions().values().nth(index)?
3891 }
3892 ModuleJsonRoot::HistoryParameter {
3893 history_index,
3894 parameter_index,
3895 } => self
3896 .owner
3897 .module
3898 .history()
3899 .get(history_index)?
3900 .parameters()
3901 .values()
3902 .nth(parameter_index)?,
3903 };
3904 for step in &self.steps {
3905 value = match (step, value) {
3906 (JsonValueStep::Array(index), serde_json::Value::Array(values)) => {
3907 values.get(*index)?
3908 }
3909 (JsonValueStep::Object(index), serde_json::Value::Object(values)) => {
3910 values.values().nth(*index)?
3911 }
3912 _ => return None,
3913 };
3914 }
3915 Some(value)
3916 }
3917}
3918
3919opaque_handle!(
3920 PioJsonValue,
3922 JsonValueInner
3923);
3924
3925#[derive(Clone, Copy)]
3926enum ValueStep {
3927 TimeSeries(usize),
3928 Scenario(usize),
3929}
3930
3931struct ValueInner {
3932 owner: Arc<ModuleInner>,
3933 steps: Vec<ValueStep>,
3934}
3935
3936impl ValueInner {
3937 fn root(owner: Arc<ModuleInner>) -> Self {
3938 Self {
3939 owner,
3940 steps: Vec::new(),
3941 }
3942 }
3943
3944 fn child(&self, step: ValueStep) -> Self {
3945 let mut steps = self.steps.clone();
3946 steps.push(step);
3947 Self {
3948 owner: Arc::clone(&self.owner),
3949 steps,
3950 }
3951 }
3952
3953 fn value(&self) -> Option<&PioValue> {
3954 let mut value = self.owner.module.value();
3955 for step in &self.steps {
3956 value = match (step, value) {
3957 (ValueStep::TimeSeries(index), PioValue::TimeSeries(series)) => {
3958 series.get(*index)?
3959 }
3960 (ValueStep::Scenario(index), PioValue::ScenarioSet(scenarios)) => {
3961 scenarios.get_at(*index)?
3962 }
3963 _ => return None,
3964 };
3965 }
3966 Some(value)
3967 }
3968}
3969
3970opaque_handle!(
3971 PioValueHandle,
3973 ValueInner
3974);
3975
3976#[derive(Clone, Copy)]
3977enum BalancedNetworkProjection {
3978 Direct,
3979 OperatingPoint,
3980 DcPfInstance,
3981 AcPfInstance,
3982 DcOpfInstance,
3983 AcOpfInstance,
3984 AcScucInstance,
3985 DcPfSolution,
3986 AcPfSolution,
3987 DcOpfSolution,
3988 AcOpfSolution,
3989 SocwrOpfSolution,
3990 AcScucSolution,
3991}
3992
3993struct BalancedNetworkInner {
3994 value: ValueInner,
3995 projection: BalancedNetworkProjection,
3996}
3997
3998impl BalancedNetworkInner {
3999 fn network(&self) -> Option<&BalancedNetwork> {
4000 match (self.projection, self.value.value()?) {
4001 (BalancedNetworkProjection::Direct, PioValue::BalancedNetwork(network)) => {
4002 Some(network)
4003 }
4004 (
4005 BalancedNetworkProjection::OperatingPoint,
4006 PioValue::BalancedOperatingPoint(point),
4007 ) => Some(point.network()),
4008 (BalancedNetworkProjection::DcPfInstance, PioValue::DcPfInstance(instance)) => {
4009 Some(instance.network())
4010 }
4011 (BalancedNetworkProjection::AcPfInstance, PioValue::AcPfInstance(instance)) => {
4012 Some(instance.network())
4013 }
4014 (BalancedNetworkProjection::DcOpfInstance, PioValue::DcOpfInstance(instance)) => {
4015 Some(instance.network())
4016 }
4017 (BalancedNetworkProjection::AcOpfInstance, PioValue::AcOpfInstance(instance)) => {
4018 Some(instance.network())
4019 }
4020 (BalancedNetworkProjection::AcScucInstance, PioValue::AcScucInstance(instance)) => {
4021 Some(instance.network())
4022 }
4023 (BalancedNetworkProjection::DcPfSolution, PioValue::DcPfSolution(solution)) => {
4024 Some(solution.network())
4025 }
4026 (BalancedNetworkProjection::AcPfSolution, PioValue::AcPfSolution(solution)) => {
4027 Some(solution.network())
4028 }
4029 (BalancedNetworkProjection::DcOpfSolution, PioValue::DcOpfSolution(solution)) => {
4030 Some(solution.network())
4031 }
4032 (BalancedNetworkProjection::AcOpfSolution, PioValue::AcOpfSolution(solution)) => {
4033 Some(solution.network())
4034 }
4035 (BalancedNetworkProjection::SocwrOpfSolution, PioValue::SocwrOpfSolution(solution)) => {
4036 Some(solution.network())
4037 }
4038 (BalancedNetworkProjection::AcScucSolution, PioValue::AcScucSolution(solution)) => {
4039 Some(solution.instance().network())
4040 }
4041 _ => None,
4042 }
4043 }
4044}
4045
4046#[derive(Clone, Copy)]
4047enum MulticonductorNetworkProjection {
4048 Direct,
4049 OperatingPoint,
4050 McAcPfInstance,
4051 McAcOpfInstance,
4052 LinDist3FlowOpfInstance,
4053 McAcPfSolution,
4054 McAcOpfSolution,
4055 LinDist3FlowOpfSolution,
4056}
4057
4058struct MulticonductorNetworkInner {
4059 value: ValueInner,
4060 projection: MulticonductorNetworkProjection,
4061}
4062
4063impl MulticonductorNetworkInner {
4064 fn network(&self) -> Option<&powerio::MulticonductorNetwork> {
4065 match (self.projection, self.value.value()?) {
4066 (MulticonductorNetworkProjection::Direct, PioValue::MulticonductorNetwork(network)) => {
4067 Some(network)
4068 }
4069 (
4070 MulticonductorNetworkProjection::OperatingPoint,
4071 PioValue::MulticonductorOperatingPoint(point),
4072 ) => Some(point.network()),
4073 (
4074 MulticonductorNetworkProjection::McAcPfInstance,
4075 PioValue::McAcPfInstance(instance),
4076 ) => Some(instance.network()),
4077 (
4078 MulticonductorNetworkProjection::McAcOpfInstance,
4079 PioValue::McAcOpfInstance(instance),
4080 ) => Some(instance.network()),
4081 (
4082 MulticonductorNetworkProjection::LinDist3FlowOpfInstance,
4083 PioValue::LinDist3FlowOpfInstance(instance),
4084 ) => Some(instance.network()),
4085 (
4086 MulticonductorNetworkProjection::McAcPfSolution,
4087 PioValue::McAcPfSolution(solution),
4088 ) => Some(solution.network()),
4089 (
4090 MulticonductorNetworkProjection::McAcOpfSolution,
4091 PioValue::McAcOpfSolution(solution),
4092 ) => Some(solution.network()),
4093 (
4094 MulticonductorNetworkProjection::LinDist3FlowOpfSolution,
4095 PioValue::LinDist3FlowOpfSolution(solution),
4096 ) => Some(solution.network()),
4097 _ => None,
4098 }
4099 }
4100}
4101
4102#[derive(Clone, Copy)]
4103enum OperatingPointProjection {
4104 DirectBalanced,
4105 DirectMulticonductor,
4106 DcPfInitial,
4107 AcPfInitial,
4108 DcOpfInitial,
4109 AcOpfInitial,
4110 McAcPfInitial,
4111 McAcOpfInitial,
4112 LinDist3FlowOpfInitial,
4113 DcPfSolutionInitial,
4114 AcPfSolutionInitial,
4115 DcOpfSolutionInitial,
4116 AcOpfSolutionInitial,
4117 SocwrOpfSolutionInitial,
4118 McAcPfSolutionInitial,
4119 McAcOpfSolutionInitial,
4120 LinDist3FlowOpfSolutionInitial,
4121}
4122
4123struct OperatingPointInner {
4124 value: ValueInner,
4125 projection: OperatingPointProjection,
4126}
4127
4128impl OperatingPointInner {
4129 fn balanced(&self) -> Option<&powerio_prob::OperatingPoint<BalancedNetwork>> {
4130 match (self.projection, self.value.value()?) {
4131 (OperatingPointProjection::DirectBalanced, PioValue::BalancedOperatingPoint(point)) => {
4132 Some(point)
4133 }
4134 (OperatingPointProjection::DcPfInitial, PioValue::DcPfInstance(instance)) => {
4135 instance.initial_point()
4136 }
4137 (OperatingPointProjection::AcPfInitial, PioValue::AcPfInstance(instance)) => {
4138 instance.initial_point()
4139 }
4140 (OperatingPointProjection::DcOpfInitial, PioValue::DcOpfInstance(instance)) => {
4141 instance.initial_point()
4142 }
4143 (OperatingPointProjection::AcOpfInitial, PioValue::AcOpfInstance(instance)) => {
4144 instance.initial_point()
4145 }
4146 (OperatingPointProjection::DcPfSolutionInitial, PioValue::DcPfSolution(solution)) => {
4147 solution.instance().initial_point()
4148 }
4149 (OperatingPointProjection::AcPfSolutionInitial, PioValue::AcPfSolution(solution)) => {
4150 solution.instance().initial_point()
4151 }
4152 (OperatingPointProjection::DcOpfSolutionInitial, PioValue::DcOpfSolution(solution)) => {
4153 solution.instance().initial_point()
4154 }
4155 (OperatingPointProjection::AcOpfSolutionInitial, PioValue::AcOpfSolution(solution)) => {
4156 solution.instance().initial_point()
4157 }
4158 (
4159 OperatingPointProjection::SocwrOpfSolutionInitial,
4160 PioValue::SocwrOpfSolution(solution),
4161 ) => solution.instance().initial_point(),
4162 _ => None,
4163 }
4164 }
4165
4166 fn multiconductor(
4167 &self,
4168 ) -> Option<&powerio_prob::OperatingPoint<powerio::MulticonductorNetwork>> {
4169 match (self.projection, self.value.value()?) {
4170 (
4171 OperatingPointProjection::DirectMulticonductor,
4172 PioValue::MulticonductorOperatingPoint(point),
4173 ) => Some(point),
4174 (OperatingPointProjection::McAcPfInitial, PioValue::McAcPfInstance(instance)) => {
4175 instance.initial_point()
4176 }
4177 (OperatingPointProjection::McAcOpfInitial, PioValue::McAcOpfInstance(instance)) => {
4178 instance.initial_point()
4179 }
4180 (
4181 OperatingPointProjection::LinDist3FlowOpfInitial,
4182 PioValue::LinDist3FlowOpfInstance(instance),
4183 ) => instance.base_instance().initial_point(),
4184 (
4185 OperatingPointProjection::McAcPfSolutionInitial,
4186 PioValue::McAcPfSolution(solution),
4187 ) => solution.instance().initial_point(),
4188 (
4189 OperatingPointProjection::McAcOpfSolutionInitial,
4190 PioValue::McAcOpfSolution(solution),
4191 ) => solution.instance().initial_point(),
4192 (
4193 OperatingPointProjection::LinDist3FlowOpfSolutionInitial,
4194 PioValue::LinDist3FlowOpfSolution(solution),
4195 ) => solution.instance().base_instance().initial_point(),
4196 _ => None,
4197 }
4198 }
4199
4200 fn type_name(&self) -> Option<&'static str> {
4201 if self.balanced().is_some() {
4202 Some("powerio.OperatingPoint<powerio.BalancedNetwork>")
4203 } else if self.multiconductor().is_some() {
4204 Some("powerio.OperatingPoint<powerio.MulticonductorNetwork>")
4205 } else {
4206 None
4207 }
4208 }
4209
4210 fn balanced_network_projection(&self) -> Option<BalancedNetworkProjection> {
4211 self.balanced()?;
4212 Some(match self.projection {
4213 OperatingPointProjection::DirectBalanced => BalancedNetworkProjection::OperatingPoint,
4214 OperatingPointProjection::DcPfInitial => BalancedNetworkProjection::DcPfInstance,
4215 OperatingPointProjection::AcPfInitial => BalancedNetworkProjection::AcPfInstance,
4216 OperatingPointProjection::DcOpfInitial => BalancedNetworkProjection::DcOpfInstance,
4217 OperatingPointProjection::AcOpfInitial => BalancedNetworkProjection::AcOpfInstance,
4218 OperatingPointProjection::DcPfSolutionInitial => {
4219 BalancedNetworkProjection::DcPfSolution
4220 }
4221 OperatingPointProjection::AcPfSolutionInitial => {
4222 BalancedNetworkProjection::AcPfSolution
4223 }
4224 OperatingPointProjection::DcOpfSolutionInitial => {
4225 BalancedNetworkProjection::DcOpfSolution
4226 }
4227 OperatingPointProjection::AcOpfSolutionInitial => {
4228 BalancedNetworkProjection::AcOpfSolution
4229 }
4230 OperatingPointProjection::SocwrOpfSolutionInitial => {
4231 BalancedNetworkProjection::SocwrOpfSolution
4232 }
4233 _ => return None,
4234 })
4235 }
4236
4237 fn multiconductor_network_projection(&self) -> Option<MulticonductorNetworkProjection> {
4238 self.multiconductor()?;
4239 Some(match self.projection {
4240 OperatingPointProjection::DirectMulticonductor => {
4241 MulticonductorNetworkProjection::OperatingPoint
4242 }
4243 OperatingPointProjection::McAcPfInitial => {
4244 MulticonductorNetworkProjection::McAcPfInstance
4245 }
4246 OperatingPointProjection::McAcOpfInitial => {
4247 MulticonductorNetworkProjection::McAcOpfInstance
4248 }
4249 OperatingPointProjection::LinDist3FlowOpfInitial => {
4250 MulticonductorNetworkProjection::LinDist3FlowOpfInstance
4251 }
4252 OperatingPointProjection::McAcPfSolutionInitial => {
4253 MulticonductorNetworkProjection::McAcPfSolution
4254 }
4255 OperatingPointProjection::McAcOpfSolutionInitial => {
4256 MulticonductorNetworkProjection::McAcOpfSolution
4257 }
4258 OperatingPointProjection::LinDist3FlowOpfSolutionInitial => {
4259 MulticonductorNetworkProjection::LinDist3FlowOpfSolution
4260 }
4261 _ => return None,
4262 })
4263 }
4264}
4265
4266#[derive(Clone, Copy)]
4267enum CalculationInstanceProjection {
4268 Direct,
4269 DcPfSolution,
4270 AcPfSolution,
4271 DcOpfSolution,
4272 AcOpfSolution,
4273 SocwrOpfSolution,
4274 McAcPfSolution,
4275 McAcOpfSolution,
4276 LinDist3FlowOpfSolution,
4277 AcScucSolution,
4278}
4279
4280#[derive(Clone, Copy)]
4281enum CalculationInstanceRef<'a> {
4282 DcPf(&'a powerio_prob::DcPfInstance),
4283 AcPf(&'a powerio_prob::AcPfInstance),
4284 DcOpf(&'a powerio_prob::DcOpfInstance),
4285 AcOpf(&'a powerio_prob::AcOpfInstance),
4286 McAcPf(&'a powerio_prob::McAcPfInstance),
4287 McAcOpf(&'a powerio_prob::McAcOpfInstance),
4288 LinDist3FlowOpf(&'a powerio_prob::LinDist3FlowOpfInstance),
4289 AcScuc(&'a powerio_prob::AcScucInstance),
4290}
4291
4292impl CalculationInstanceRef<'_> {
4293 fn type_name(self) -> &'static str {
4294 match self {
4295 Self::DcPf(_) => "powerio.DcPfInstance",
4296 Self::AcPf(_) => "powerio.AcPfInstance",
4297 Self::DcOpf(_) => "powerio.DcOpfInstance",
4298 Self::AcOpf(_) => "powerio.AcOpfInstance",
4299 Self::McAcPf(_) => "powerio.McAcPfInstance",
4300 Self::McAcOpf(_) => "powerio.McAcOpfInstance",
4301 Self::LinDist3FlowOpf(_) => "powerio.LinDist3FlowOpfInstance",
4302 Self::AcScuc(_) => "powerio.AcScucInstance",
4303 }
4304 }
4305}
4306
4307struct CalculationInstanceInner {
4308 value: ValueInner,
4309 projection: CalculationInstanceProjection,
4310}
4311
4312impl CalculationInstanceInner {
4313 fn instance(&self) -> Option<CalculationInstanceRef<'_>> {
4314 match (self.projection, self.value.value()?) {
4315 (CalculationInstanceProjection::Direct, PioValue::DcPfInstance(instance)) => {
4316 Some(CalculationInstanceRef::DcPf(instance))
4317 }
4318 (CalculationInstanceProjection::Direct, PioValue::AcPfInstance(instance)) => {
4319 Some(CalculationInstanceRef::AcPf(instance))
4320 }
4321 (CalculationInstanceProjection::Direct, PioValue::DcOpfInstance(instance)) => {
4322 Some(CalculationInstanceRef::DcOpf(instance))
4323 }
4324 (CalculationInstanceProjection::Direct, PioValue::AcOpfInstance(instance)) => {
4325 Some(CalculationInstanceRef::AcOpf(instance))
4326 }
4327 (CalculationInstanceProjection::Direct, PioValue::McAcPfInstance(instance)) => {
4328 Some(CalculationInstanceRef::McAcPf(instance))
4329 }
4330 (CalculationInstanceProjection::Direct, PioValue::McAcOpfInstance(instance)) => {
4331 Some(CalculationInstanceRef::McAcOpf(instance))
4332 }
4333 (
4334 CalculationInstanceProjection::Direct,
4335 PioValue::LinDist3FlowOpfInstance(instance),
4336 ) => Some(CalculationInstanceRef::LinDist3FlowOpf(instance)),
4337 (CalculationInstanceProjection::Direct, PioValue::AcScucInstance(instance)) => {
4338 Some(CalculationInstanceRef::AcScuc(instance))
4339 }
4340 (CalculationInstanceProjection::DcPfSolution, PioValue::DcPfSolution(solution)) => {
4341 Some(CalculationInstanceRef::DcPf(solution.instance()))
4342 }
4343 (CalculationInstanceProjection::AcPfSolution, PioValue::AcPfSolution(solution)) => {
4344 Some(CalculationInstanceRef::AcPf(solution.instance()))
4345 }
4346 (CalculationInstanceProjection::DcOpfSolution, PioValue::DcOpfSolution(solution)) => {
4347 Some(CalculationInstanceRef::DcOpf(solution.instance()))
4348 }
4349 (CalculationInstanceProjection::AcOpfSolution, PioValue::AcOpfSolution(solution)) => {
4350 Some(CalculationInstanceRef::AcOpf(solution.instance()))
4351 }
4352 (
4353 CalculationInstanceProjection::SocwrOpfSolution,
4354 PioValue::SocwrOpfSolution(solution),
4355 ) => Some(CalculationInstanceRef::AcOpf(solution.instance())),
4356 (CalculationInstanceProjection::McAcPfSolution, PioValue::McAcPfSolution(solution)) => {
4357 Some(CalculationInstanceRef::McAcPf(solution.instance()))
4358 }
4359 (
4360 CalculationInstanceProjection::McAcOpfSolution,
4361 PioValue::McAcOpfSolution(solution),
4362 ) => Some(CalculationInstanceRef::McAcOpf(solution.instance())),
4363 (
4364 CalculationInstanceProjection::LinDist3FlowOpfSolution,
4365 PioValue::LinDist3FlowOpfSolution(solution),
4366 ) => Some(CalculationInstanceRef::LinDist3FlowOpf(solution.instance())),
4367 (CalculationInstanceProjection::AcScucSolution, PioValue::AcScucSolution(solution)) => {
4368 Some(CalculationInstanceRef::AcScuc(solution.instance()))
4369 }
4370 _ => None,
4371 }
4372 }
4373
4374 fn type_name(&self) -> Option<&'static str> {
4375 self.instance().map(CalculationInstanceRef::type_name)
4376 }
4377
4378 fn dc_pf(&self) -> Option<&powerio_prob::DcPfInstance> {
4379 match self.instance()? {
4380 CalculationInstanceRef::DcPf(instance) => Some(instance),
4381 _ => None,
4382 }
4383 }
4384
4385 fn ac_pf(&self) -> Option<&powerio_prob::AcPfInstance> {
4386 match self.instance()? {
4387 CalculationInstanceRef::AcPf(instance) => Some(instance),
4388 _ => None,
4389 }
4390 }
4391
4392 fn dc_opf(&self) -> Option<&powerio_prob::DcOpfInstance> {
4393 match self.instance()? {
4394 CalculationInstanceRef::DcOpf(instance) => Some(instance),
4395 _ => None,
4396 }
4397 }
4398
4399 fn ac_opf(&self) -> Option<&powerio_prob::AcOpfInstance> {
4400 match self.instance()? {
4401 CalculationInstanceRef::AcOpf(instance) => Some(instance),
4402 _ => None,
4403 }
4404 }
4405
4406 fn mc_ac_pf(&self) -> Option<&powerio_prob::McAcPfInstance> {
4407 match self.instance()? {
4408 CalculationInstanceRef::McAcPf(instance) => Some(instance),
4409 _ => None,
4410 }
4411 }
4412
4413 fn ac_scuc(&self) -> Option<&powerio_prob::AcScucInstance> {
4414 match self.instance()? {
4415 CalculationInstanceRef::AcScuc(instance) => Some(instance),
4416 _ => None,
4417 }
4418 }
4419
4420 fn has_initial_point(&self) -> bool {
4421 match self.instance() {
4422 Some(CalculationInstanceRef::DcPf(instance)) => instance.initial_point().is_some(),
4423 Some(CalculationInstanceRef::AcPf(instance)) => instance.initial_point().is_some(),
4424 Some(CalculationInstanceRef::DcOpf(instance)) => instance.initial_point().is_some(),
4425 Some(CalculationInstanceRef::AcOpf(instance)) => instance.initial_point().is_some(),
4426 Some(CalculationInstanceRef::McAcPf(instance)) => instance.initial_point().is_some(),
4427 Some(CalculationInstanceRef::McAcOpf(instance)) => instance.initial_point().is_some(),
4428 Some(CalculationInstanceRef::LinDist3FlowOpf(instance)) => {
4429 instance.base_instance().initial_point().is_some()
4430 }
4431 _ => false,
4432 }
4433 }
4434}
4435
4436opaque_handle!(
4437 PioBalancedNetwork,
4439 BalancedNetworkInner
4440);
4441
4442struct DetailedConnectivityInner {
4443 owner: Arc<BalancedNetworkInner>,
4444}
4445
4446impl DetailedConnectivityInner {
4447 fn details(&self) -> Option<&powerio_tx::DetailedConnectivity> {
4448 self.owner.network()?.detailed_connectivity().as_deref()
4449 }
4450}
4451
4452opaque_handle!(
4453 PioDetailedConnectivity,
4455 DetailedConnectivityInner
4456);
4457opaque_handle!(
4458 PioMulticonductorNetwork,
4460 MulticonductorNetworkInner
4461);
4462opaque_handle!(
4463 PioTimeSeriesHandle,
4465 ValueInner
4466);
4467opaque_handle!(
4468 PioScenarioSetHandle,
4470 ValueInner
4471);
4472opaque_handle!(
4473 PioOperatingPoint,
4475 OperatingPointInner
4476);
4477opaque_handle!(
4478 PioCalculationInstance,
4480 CalculationInstanceInner
4481);
4482opaque_handle!(
4483 PioDcOpfPreparation,
4485 DcOpfPreparation
4486);
4487opaque_handle!(
4488 PioAcOpfPreparation,
4490 AcOpfPreparation
4491);
4492opaque_handle!(
4493 PioCalculationSolution,
4495 ValueInner
4496);
4497
4498fn module_handle(module: powerio::PioModule<PioValue>) -> *mut PioModule {
4499 PioModule::new_raw(ModuleInner { module })
4500}
4501
4502fn value_handle(value: ValueInner) -> *mut PioValueHandle {
4503 PioValueHandle::new_raw(value)
4504}
4505
4506unsafe fn require_value<'a>(value: *const PioValueHandle) -> Result<&'a ValueInner, *mut PioError> {
4507 unsafe { PioValueHandle::get(value) }.ok_or_else(|| {
4508 boundary_error(
4509 &codes::BIND_CAPI_NULL_HANDLE,
4510 "PioValueHandle must not be NULL",
4511 )
4512 })
4513}
4514
4515#[unsafe(no_mangle)]
4520pub unsafe extern "C" fn pio_parse(
4521 source: *const PioSource,
4522 format: *const c_char,
4523 format_len: usize,
4524 error: *mut *mut PioError,
4525) -> *mut PioModule {
4526 unsafe {
4527 entry(error, std::ptr::null_mut(), || {
4528 let source = PioSource::get(source).ok_or_else(|| {
4529 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioSource must not be NULL")
4530 })?;
4531 let format = optional_str(format, format_len, "format")?;
4532 let mut options = powerio::ParseOptions::default();
4533 if let Some(format) = format {
4534 options = options
4535 .format(format)
4536 .map_err(|failure| error_from_core(&failure))?;
4537 }
4538 powerio::parse_with_options(source.clone(), &options)
4539 .map(module_handle)
4540 .map_err(|failure| error_from_core(&failure))
4541 })
4542 }
4543}
4544
4545#[unsafe(no_mangle)]
4554pub unsafe extern "C" fn pio_module_deserialize(
4555 source: *const PioSource,
4556 error: *mut *mut PioError,
4557) -> *mut PioModule {
4558 unsafe {
4559 entry(error, std::ptr::null_mut(), || {
4560 let source = PioSource::get(source).ok_or_else(|| {
4561 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioSource must not be NULL")
4562 })?;
4563 powerio::deserialize(source.clone())
4564 .map(module_handle)
4565 .map_err(|failure| error_from_core(&failure))
4566 })
4567 }
4568}
4569
4570unsafe fn transform_module<T>(
4571 module: *const PioModule,
4572 error: *mut *mut PioError,
4573 transform: impl FnOnce(
4574 &powerio::PioModule<PioValue>,
4575 ) -> Result<powerio::PioModule<T>, powerio_core::Error>,
4576 wrap: impl FnOnce(T) -> PioValue,
4577) -> *mut PioModule {
4578 unsafe {
4579 entry(error, std::ptr::null_mut(), || {
4580 let module = PioModule::get(module).ok_or_else(|| {
4581 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4582 })?;
4583 transform(&module.module)
4584 .map(|derived| module_handle(derived.map_value(wrap)))
4585 .map_err(|failure| error_from_core(&failure))
4586 })
4587 }
4588}
4589
4590#[unsafe(no_mangle)]
4595pub unsafe extern "C" fn pio_module_to_dc_pf_instance(
4596 module: *const PioModule,
4597 error: *mut *mut PioError,
4598) -> *mut PioModule {
4599 unsafe {
4600 transform_module(
4601 module,
4602 error,
4603 powerio::transform::to_dc_pf_instance,
4604 PioValue::DcPfInstance,
4605 )
4606 }
4607}
4608
4609#[unsafe(no_mangle)]
4614pub unsafe extern "C" fn pio_module_to_ac_pf_instance(
4615 module: *const PioModule,
4616 error: *mut *mut PioError,
4617) -> *mut PioModule {
4618 unsafe {
4619 transform_module(
4620 module,
4621 error,
4622 powerio::transform::to_ac_pf_instance,
4623 PioValue::AcPfInstance,
4624 )
4625 }
4626}
4627
4628#[unsafe(no_mangle)]
4633pub unsafe extern "C" fn pio_module_to_dc_opf_instance(
4634 module: *const PioModule,
4635 error: *mut *mut PioError,
4636) -> *mut PioModule {
4637 unsafe {
4638 transform_module(
4639 module,
4640 error,
4641 powerio::transform::to_dc_opf_instance,
4642 PioValue::DcOpfInstance,
4643 )
4644 }
4645}
4646
4647#[unsafe(no_mangle)]
4652pub unsafe extern "C" fn pio_module_to_ac_opf_instance(
4653 module: *const PioModule,
4654 error: *mut *mut PioError,
4655) -> *mut PioModule {
4656 unsafe {
4657 transform_module(
4658 module,
4659 error,
4660 powerio::transform::to_ac_opf_instance,
4661 PioValue::AcOpfInstance,
4662 )
4663 }
4664}
4665
4666#[unsafe(no_mangle)]
4672pub unsafe extern "C" fn pio_module_to_mc_ac_pf_instance(
4673 module: *const PioModule,
4674 error: *mut *mut PioError,
4675) -> *mut PioModule {
4676 unsafe {
4677 transform_module(
4678 module,
4679 error,
4680 powerio::transform::to_mc_ac_pf_instance,
4681 PioValue::McAcPfInstance,
4682 )
4683 }
4684}
4685
4686#[unsafe(no_mangle)]
4692pub unsafe extern "C" fn pio_module_to_mc_ac_opf_instance(
4693 module: *const PioModule,
4694 error: *mut *mut PioError,
4695) -> *mut PioModule {
4696 unsafe {
4697 transform_module(
4698 module,
4699 error,
4700 powerio::transform::to_mc_ac_opf_instance,
4701 PioValue::McAcOpfInstance,
4702 )
4703 }
4704}
4705
4706#[unsafe(no_mangle)]
4710pub unsafe extern "C" fn pio_module_to_lindist3flow_opf_instance(
4711 module: *const PioModule,
4712 error: *mut *mut PioError,
4713) -> *mut PioModule {
4714 unsafe {
4715 transform_module(
4716 module,
4717 error,
4718 powerio::transform::to_lindist3flow_opf_instance,
4719 PioValue::LinDist3FlowOpfInstance,
4720 )
4721 }
4722}
4723
4724#[unsafe(no_mangle)]
4731pub unsafe extern "C" fn pio_module_apply_geo_layer(
4732 module: *const PioModule,
4733 layer: *const PioGeoLayer,
4734 out_report: *mut *mut PioGeoApplyReport,
4735 error: *mut *mut PioError,
4736) -> *mut PioModule {
4737 unsafe {
4738 if !out_report.is_null() {
4739 *out_report = std::ptr::null_mut();
4740 }
4741 entry(error, std::ptr::null_mut(), || {
4742 let module = PioModule::get(module).ok_or_else(|| {
4743 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4744 })?;
4745 let layer = PioGeoLayer::get(layer).ok_or_else(|| {
4746 boundary_error(
4747 &codes::BIND_CAPI_NULL_HANDLE,
4748 "PioGeoLayer must not be NULL",
4749 )
4750 })?;
4751 let (mut derived, report) = powerio::apply_geo_layer(&module.module, &layer.layer)
4752 .map_err(|failure| error_from_core(&failure))?;
4753 for diagnostic in &layer.diagnostics {
4754 derived
4755 .add_diagnostic(diagnostic.clone())
4756 .map_err(|failure| error_from_core(&failure))?;
4757 }
4758 if !out_report.is_null() {
4759 *out_report = PioGeoApplyReport::new_raw(report);
4760 }
4761 Ok(module_handle(derived))
4762 })
4763 }
4764}
4765
4766#[unsafe(no_mangle)]
4770pub unsafe extern "C" fn pio_geo_apply_report_matched_buses(
4771 report: *const PioGeoApplyReport,
4772) -> usize {
4773 unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.matched_buses)
4774}
4775
4776#[unsafe(no_mangle)]
4780pub unsafe extern "C" fn pio_geo_apply_report_matched_branches(
4781 report: *const PioGeoApplyReport,
4782) -> usize {
4783 unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.matched_branches)
4784}
4785
4786#[unsafe(no_mangle)]
4790pub unsafe extern "C" fn pio_geo_apply_report_unmatched_features(
4791 report: *const PioGeoApplyReport,
4792) -> usize {
4793 unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.unmatched_features)
4794}
4795
4796#[unsafe(no_mangle)]
4800pub unsafe extern "C" fn pio_geo_apply_report_unlocated_buses(
4801 report: *const PioGeoApplyReport,
4802) -> usize {
4803 unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.unlocated_buses)
4804}
4805
4806#[unsafe(no_mangle)]
4810pub unsafe extern "C" fn pio_geo_apply_report_unlocated_branches(
4811 report: *const PioGeoApplyReport,
4812) -> usize {
4813 unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.unlocated_branches)
4814}
4815
4816#[unsafe(no_mangle)]
4820pub unsafe extern "C" fn pio_geo_apply_report_note_count(
4821 report: *const PioGeoApplyReport,
4822) -> usize {
4823 unsafe { PioGeoApplyReport::get(report) }.map_or(0, |report| report.notes.len())
4824}
4825
4826#[unsafe(no_mangle)]
4830pub unsafe extern "C" fn pio_geo_apply_report_note_at(
4831 report: *const PioGeoApplyReport,
4832 index: usize,
4833 error: *mut *mut PioError,
4834) -> PioStringView {
4835 unsafe {
4836 entry(error, PioStringView::EMPTY, || {
4837 let report = PioGeoApplyReport::get(report).ok_or_else(|| {
4838 boundary_error(
4839 &codes::BIND_CAPI_NULL_HANDLE,
4840 "PioGeoApplyReport must not be NULL",
4841 )
4842 })?;
4843 let note = report.notes.get(index).ok_or_else(|| {
4844 boundary_error(
4845 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4846 format!("geo apply note index {index} is out of range"),
4847 )
4848 })?;
4849 Ok(PioStringView::new(note))
4850 })
4851 }
4852}
4853
4854#[unsafe(no_mangle)]
4859pub unsafe extern "C" fn pio_geo_apply_report_retain(
4860 report: *const PioGeoApplyReport,
4861) -> *mut PioGeoApplyReport {
4862 unsafe { PioGeoApplyReport::retain_raw(report) }
4863}
4864
4865#[unsafe(no_mangle)]
4870pub unsafe extern "C" fn pio_geo_apply_report_release(report: *mut PioGeoApplyReport) {
4871 unsafe { PioGeoApplyReport::release_raw(report) };
4872}
4873
4874#[unsafe(no_mangle)]
4879pub unsafe extern "C" fn pio_module_value(module: *const PioModule) -> *mut PioValueHandle {
4880 unsafe { PioModule::arc(module) }.map_or(std::ptr::null_mut(), |owner| {
4881 value_handle(ValueInner::root(owner))
4882 })
4883}
4884
4885#[unsafe(no_mangle)]
4890pub unsafe extern "C" fn pio_module_diagnostics(module: *const PioModule) -> *mut PioDiagnostics {
4891 unsafe { PioModule::arc(module) }.map_or(std::ptr::null_mut(), |module| {
4892 PioDiagnostics::new_raw(DiagnosticsInner {
4893 owner: DiagnosticsOwner::Module(module),
4894 })
4895 })
4896}
4897
4898fn source_relation_name(relation: powerio_core::SourceRelation) -> &'static str {
4899 match relation {
4900 powerio_core::SourceRelation::Exact => "exact",
4901 powerio_core::SourceRelation::Defaulted => "defaulted",
4902 powerio_core::SourceRelation::Inferred => "inferred",
4903 powerio_core::SourceRelation::ConvertedUnits => "converted_units",
4904 powerio_core::SourceRelation::Aggregated => "aggregated",
4905 powerio_core::SourceRelation::Split => "split",
4906 powerio_core::SourceRelation::Synthetic => "synthetic",
4907 powerio_core::SourceRelation::Transformed => "transformed",
4908 powerio_core::SourceRelation::RetainedExtra => "retained_extra",
4909 _ => "unknown",
4910 }
4911}
4912
4913fn history_kind_name(kind: HistoryKind) -> &'static str {
4914 match kind {
4915 HistoryKind::Parse => "parse",
4916 HistoryKind::Transform => "transform",
4917 HistoryKind::Edit => "edit",
4918 HistoryKind::Repair => "repair",
4919 HistoryKind::Solve => "solve",
4920 _ => "unknown",
4921 }
4922}
4923
4924fn json_value_kind(value: &serde_json::Value) -> &'static str {
4925 match value {
4926 serde_json::Value::Null => "null",
4927 serde_json::Value::Bool(_) => "boolean",
4928 serde_json::Value::Number(_) => "number",
4929 serde_json::Value::String(_) => "string",
4930 serde_json::Value::Array(_) => "array",
4931 serde_json::Value::Object(_) => "object",
4932 }
4933}
4934
4935#[unsafe(no_mangle)]
4940pub unsafe extern "C" fn pio_module_producer(
4941 module: *const PioModule,
4942 output: *mut PioModuleProducerView,
4943 error: *mut *mut PioError,
4944) -> bool {
4945 unsafe {
4946 entry(error, false, || {
4947 let module = PioModule::get(module).ok_or_else(|| {
4948 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4949 })?;
4950 let producer = module.module.producer();
4951 *require_output(output, "output")? = PioModuleProducerView {
4952 name: PioStringView::new(producer.name()),
4953 version: PioStringView::new(producer.version()),
4954 };
4955 Ok(true)
4956 })
4957 }
4958}
4959
4960#[unsafe(no_mangle)]
4964pub unsafe extern "C" fn pio_module_source_count(module: *const PioModule) -> usize {
4965 unsafe { PioModule::get(module) }.map_or(0, |module| module.module.sources().len())
4966}
4967
4968#[unsafe(no_mangle)]
4973pub unsafe extern "C" fn pio_module_source_at(
4974 module: *const PioModule,
4975 index: usize,
4976 output: *mut PioModuleSourceView,
4977 error: *mut *mut PioError,
4978) -> bool {
4979 unsafe {
4980 entry(error, false, || {
4981 let module = PioModule::get(module).ok_or_else(|| {
4982 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
4983 })?;
4984 let source = module.module.sources().get(index).ok_or_else(|| {
4985 boundary_error(
4986 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
4987 format!("module source index {index} is out of range"),
4988 )
4989 })?;
4990 let (format, has_format) = source
4991 .format()
4992 .map_or((PioStringView::EMPTY, false), |format| {
4993 (PioStringView::new(format.as_str()), true)
4994 });
4995 let digest = source.digest();
4996 *require_output(output, "output")? = PioModuleSourceView {
4997 id: PioStringView::new(source.id().as_str()),
4998 name: PioStringView::new(source.name()),
4999 byte_length: source.byte_length(),
5000 format,
5001 has_format,
5002 digest_algorithm: digest.map_or(PioStringView::EMPTY, |digest| {
5003 PioStringView::new(digest.algorithm().as_str())
5004 }),
5005 digest: digest.map_or(PioStringView::EMPTY, |digest| {
5006 PioStringView::new(digest.value())
5007 }),
5008 has_digest: digest.is_some(),
5009 };
5010 Ok(true)
5011 })
5012 }
5013}
5014
5015#[unsafe(no_mangle)]
5019pub unsafe extern "C" fn pio_module_source_map_count(module: *const PioModule) -> usize {
5020 unsafe { PioModule::get(module) }.map_or(0, |module| module.module.source_map().len())
5021}
5022
5023#[unsafe(no_mangle)]
5028pub unsafe extern "C" fn pio_module_source_map_at(
5029 module: *const PioModule,
5030 index: usize,
5031 output: *mut PioModuleSourceMapEntryView,
5032 error: *mut *mut PioError,
5033) -> bool {
5034 unsafe {
5035 entry(error, false, || {
5036 let module = PioModule::get(module).ok_or_else(|| {
5037 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5038 })?;
5039 let source_map = module.module.source_map().get(index).ok_or_else(|| {
5040 boundary_error(
5041 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5042 format!("module source map index {index} is out of range"),
5043 )
5044 })?;
5045 *require_output(output, "output")? = PioModuleSourceMapEntryView {
5046 target: PioStringView::new(source_map.target()),
5047 relation: PioStringView::new(source_relation_name(source_map.relation())),
5048 span_count: source_map.spans().len(),
5049 };
5050 Ok(true)
5051 })
5052 }
5053}
5054
5055#[unsafe(no_mangle)]
5060pub unsafe extern "C" fn pio_module_source_map_span_at(
5061 module: *const PioModule,
5062 entry_index: usize,
5063 span_index: usize,
5064 output: *mut PioSourceSpanView,
5065 error: *mut *mut PioError,
5066) -> bool {
5067 unsafe {
5068 entry(error, false, || {
5069 let module = PioModule::get(module).ok_or_else(|| {
5070 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5071 })?;
5072 let source_map = module.module.source_map().get(entry_index).ok_or_else(|| {
5073 boundary_error(
5074 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5075 format!("module source map index {entry_index} is out of range"),
5076 )
5077 })?;
5078 let span = source_map.spans().get(span_index).ok_or_else(|| {
5079 boundary_error(
5080 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5081 format!(
5082 "source map span index {span_index} is out of range for entry {entry_index}"
5083 ),
5084 )
5085 })?;
5086 *require_output(output, "output")? = PioSourceSpanView {
5087 source: PioStringView::new(span.source().as_str()),
5088 byte_start: span.byte_start(),
5089 byte_end: span.byte_end(),
5090 };
5091 Ok(true)
5092 })
5093 }
5094}
5095
5096#[unsafe(no_mangle)]
5100pub unsafe extern "C" fn pio_module_history_count(module: *const PioModule) -> usize {
5101 unsafe { PioModule::get(module) }.map_or(0, |module| module.module.history().len())
5102}
5103
5104#[unsafe(no_mangle)]
5109pub unsafe extern "C" fn pio_module_history_at(
5110 module: *const PioModule,
5111 index: usize,
5112 output: *mut PioModuleHistoryEntryView,
5113 error: *mut *mut PioError,
5114) -> bool {
5115 unsafe {
5116 entry(error, false, || {
5117 let module = PioModule::get(module).ok_or_else(|| {
5118 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5119 })?;
5120 let history = module.module.history().get(index).ok_or_else(|| {
5121 boundary_error(
5122 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5123 format!("module history index {index} is out of range"),
5124 )
5125 })?;
5126 let (input_type, has_input_type) = optional_string_view(history.input_type());
5127 let (output_type, has_output_type) = optional_string_view(history.output_type());
5128 *require_output(output, "output")? = PioModuleHistoryEntryView {
5129 id: PioStringView::new(history.id().as_str()),
5130 kind: PioStringView::new(history_kind_name(history.kind())),
5131 name: PioStringView::new(history.name()),
5132 input_type,
5133 has_input_type,
5134 output_type,
5135 has_output_type,
5136 parameter_count: history.parameters().len(),
5137 assumption_count: history.assumptions().len(),
5138 loss_count: history.losses().len(),
5139 };
5140 Ok(true)
5141 })
5142 }
5143}
5144
5145#[unsafe(no_mangle)]
5150pub unsafe extern "C" fn pio_module_history_parameter_at(
5151 module: *const PioModule,
5152 history_index: usize,
5153 parameter_index: usize,
5154 output: *mut PioModuleHistoryParameterView,
5155 error: *mut *mut PioError,
5156) -> bool {
5157 unsafe {
5158 entry(error, false, || {
5159 let module = PioModule::get(module).ok_or_else(|| {
5160 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5161 })?;
5162 let history = module.module.history().get(history_index).ok_or_else(|| {
5163 boundary_error(
5164 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5165 format!("module history index {history_index} is out of range"),
5166 )
5167 })?;
5168 let (name, value) = history
5169 .parameters()
5170 .iter()
5171 .nth(parameter_index)
5172 .ok_or_else(|| {
5173 boundary_error(
5174 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5175 format!(
5176 "history parameter index {parameter_index} is out of range for entry {history_index}"
5177 ),
5178 )
5179 })?;
5180 *require_output(output, "output")? = PioModuleHistoryParameterView {
5181 name: PioStringView::new(name),
5182 value_kind: PioStringView::new(json_value_kind(value)),
5183 };
5184 Ok(true)
5185 })
5186 }
5187}
5188
5189#[unsafe(no_mangle)]
5194pub unsafe extern "C" fn pio_module_history_parameter_value_at(
5195 module: *const PioModule,
5196 history_index: usize,
5197 parameter_index: usize,
5198 error: *mut *mut PioError,
5199) -> *mut PioJsonValue {
5200 unsafe {
5201 entry(error, std::ptr::null_mut(), || {
5202 let owner = PioModule::arc(module).ok_or_else(|| {
5203 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5204 })?;
5205 let history = owner.module.history().get(history_index).ok_or_else(|| {
5206 boundary_error(
5207 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5208 format!("module history index {history_index} is out of range"),
5209 )
5210 })?;
5211 if history.parameters().values().nth(parameter_index).is_none() {
5212 return Err(boundary_error(
5213 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5214 format!(
5215 "history parameter index {parameter_index} is out of range for entry {history_index}"
5216 ),
5217 ));
5218 }
5219 Ok(PioJsonValue::new_raw(JsonValueInner {
5220 owner,
5221 root: ModuleJsonRoot::HistoryParameter {
5222 history_index,
5223 parameter_index,
5224 },
5225 steps: Vec::new(),
5226 }))
5227 })
5228 }
5229}
5230
5231#[unsafe(no_mangle)]
5236pub unsafe extern "C" fn pio_module_history_assumption_at(
5237 module: *const PioModule,
5238 history_index: usize,
5239 assumption_index: usize,
5240 error: *mut *mut PioError,
5241) -> PioStringView {
5242 unsafe {
5243 entry(error, PioStringView::EMPTY, || {
5244 let module = PioModule::get(module).ok_or_else(|| {
5245 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5246 })?;
5247 let history = module.module.history().get(history_index).ok_or_else(|| {
5248 boundary_error(
5249 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5250 format!("module history index {history_index} is out of range"),
5251 )
5252 })?;
5253 history
5254 .assumptions()
5255 .get(assumption_index)
5256 .map(|value| PioStringView::new(value))
5257 .ok_or_else(|| {
5258 boundary_error(
5259 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5260 format!(
5261 "history assumption index {assumption_index} is out of range for entry {history_index}"
5262 ),
5263 )
5264 })
5265 })
5266 }
5267}
5268
5269#[unsafe(no_mangle)]
5274pub unsafe extern "C" fn pio_module_history_loss_at(
5275 module: *const PioModule,
5276 history_index: usize,
5277 loss_index: usize,
5278 error: *mut *mut PioError,
5279) -> PioStringView {
5280 unsafe {
5281 entry(error, PioStringView::EMPTY, || {
5282 let module = PioModule::get(module).ok_or_else(|| {
5283 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5284 })?;
5285 let history = module.module.history().get(history_index).ok_or_else(|| {
5286 boundary_error(
5287 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5288 format!("module history index {history_index} is out of range"),
5289 )
5290 })?;
5291 history
5292 .losses()
5293 .get(loss_index)
5294 .map(|value| PioStringView::new(value))
5295 .ok_or_else(|| {
5296 boundary_error(
5297 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5298 format!(
5299 "history loss index {loss_index} is out of range for entry {history_index}"
5300 ),
5301 )
5302 })
5303 })
5304 }
5305}
5306
5307#[unsafe(no_mangle)]
5311pub unsafe extern "C" fn pio_module_extension_count(module: *const PioModule) -> usize {
5312 unsafe { PioModule::get(module) }.map_or(0, |module| module.module.extensions().len())
5313}
5314
5315#[unsafe(no_mangle)]
5320pub unsafe extern "C" fn pio_module_extension_at(
5321 module: *const PioModule,
5322 index: usize,
5323 output: *mut PioModuleExtensionView,
5324 error: *mut *mut PioError,
5325) -> bool {
5326 unsafe {
5327 entry(error, false, || {
5328 let module = PioModule::get(module).ok_or_else(|| {
5329 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5330 })?;
5331 let (namespace, value) =
5332 module
5333 .module
5334 .extensions()
5335 .iter()
5336 .nth(index)
5337 .ok_or_else(|| {
5338 boundary_error(
5339 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5340 format!("module extension index {index} is out of range"),
5341 )
5342 })?;
5343 *require_output(output, "output")? = PioModuleExtensionView {
5344 namespace: PioStringView::new(namespace),
5345 value_kind: PioStringView::new(json_value_kind(value)),
5346 };
5347 Ok(true)
5348 })
5349 }
5350}
5351
5352#[unsafe(no_mangle)]
5357pub unsafe extern "C" fn pio_module_extension_value_at(
5358 module: *const PioModule,
5359 index: usize,
5360 error: *mut *mut PioError,
5361) -> *mut PioJsonValue {
5362 unsafe {
5363 entry(error, std::ptr::null_mut(), || {
5364 let owner = PioModule::arc(module).ok_or_else(|| {
5365 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
5366 })?;
5367 if owner.module.extensions().values().nth(index).is_none() {
5368 return Err(boundary_error(
5369 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5370 format!("module extension index {index} is out of range"),
5371 ));
5372 }
5373 Ok(PioJsonValue::new_raw(JsonValueInner {
5374 owner,
5375 root: ModuleJsonRoot::Extension(index),
5376 steps: Vec::new(),
5377 }))
5378 })
5379 }
5380}
5381
5382fn json_value_view(value: &serde_json::Value) -> PioJsonValueView {
5383 let mut view = PioJsonValueView {
5384 kind: PioStringView::new(json_value_kind(value)),
5385 boolean_value: false,
5386 number_kind: PioStringView::EMPTY,
5387 signed_integer_value: 0,
5388 unsigned_integer_value: 0,
5389 floating_point_value: 0.0,
5390 string_value: PioStringView::EMPTY,
5391 element_count: 0,
5392 };
5393 match value {
5394 serde_json::Value::Bool(value) => view.boolean_value = *value,
5395 serde_json::Value::Number(value) if value.is_i64() => {
5396 view.number_kind = PioStringView::new("signed_integer");
5397 view.signed_integer_value = value.as_i64().unwrap_or_default();
5398 }
5399 serde_json::Value::Number(value) if value.is_u64() => {
5400 view.number_kind = PioStringView::new("unsigned_integer");
5401 view.unsigned_integer_value = value.as_u64().unwrap_or_default();
5402 }
5403 serde_json::Value::Number(value) => {
5404 view.number_kind = PioStringView::new("floating_point");
5405 view.floating_point_value = value.as_f64().unwrap_or(f64::NAN);
5406 }
5407 serde_json::Value::String(value) => view.string_value = PioStringView::new(value),
5408 serde_json::Value::Array(values) => view.element_count = values.len(),
5409 serde_json::Value::Object(values) => view.element_count = values.len(),
5410 serde_json::Value::Null => {}
5411 }
5412 view
5413}
5414
5415#[unsafe(no_mangle)]
5420pub unsafe extern "C" fn pio_json_value_get(
5421 value: *const PioJsonValue,
5422 output: *mut PioJsonValueView,
5423 error: *mut *mut PioError,
5424) -> bool {
5425 unsafe {
5426 entry(error, false, || {
5427 let value = PioJsonValue::get(value).ok_or_else(|| {
5428 boundary_error(
5429 &codes::BIND_CAPI_NULL_HANDLE,
5430 "PioJsonValue must not be NULL",
5431 )
5432 })?;
5433 let value = value.value().ok_or_else(|| {
5434 boundary_error(
5435 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5436 "the structured value path is out of range",
5437 )
5438 })?;
5439 *require_output(output, "output")? = json_value_view(value);
5440 Ok(true)
5441 })
5442 }
5443}
5444
5445#[unsafe(no_mangle)]
5450pub unsafe extern "C" fn pio_json_value_array_at(
5451 value: *const PioJsonValue,
5452 index: usize,
5453 error: *mut *mut PioError,
5454) -> *mut PioJsonValue {
5455 unsafe {
5456 entry(error, std::ptr::null_mut(), || {
5457 let value = PioJsonValue::get(value).ok_or_else(|| {
5458 boundary_error(
5459 &codes::BIND_CAPI_NULL_HANDLE,
5460 "PioJsonValue must not be NULL",
5461 )
5462 })?;
5463 let values = value
5464 .value()
5465 .and_then(serde_json::Value::as_array)
5466 .ok_or_else(|| {
5467 boundary_error(
5468 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5469 "the structured value is not an array",
5470 )
5471 })?;
5472 if values.get(index).is_none() {
5473 return Err(boundary_error(
5474 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5475 format!("structured array index {index} is out of range"),
5476 ));
5477 }
5478 Ok(PioJsonValue::new_raw(
5479 value.child(JsonValueStep::Array(index)),
5480 ))
5481 })
5482 }
5483}
5484
5485#[unsafe(no_mangle)]
5490pub unsafe extern "C" fn pio_json_value_object_entry_at(
5491 value: *const PioJsonValue,
5492 index: usize,
5493 output: *mut PioJsonObjectEntryView,
5494 error: *mut *mut PioError,
5495) -> bool {
5496 unsafe {
5497 entry(error, false, || {
5498 let value = PioJsonValue::get(value).ok_or_else(|| {
5499 boundary_error(
5500 &codes::BIND_CAPI_NULL_HANDLE,
5501 "PioJsonValue must not be NULL",
5502 )
5503 })?;
5504 let values = value
5505 .value()
5506 .and_then(serde_json::Value::as_object)
5507 .ok_or_else(|| {
5508 boundary_error(
5509 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5510 "the structured value is not an object",
5511 )
5512 })?;
5513 let (key, value) = values.iter().nth(index).ok_or_else(|| {
5514 boundary_error(
5515 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5516 format!("structured object index {index} is out of range"),
5517 )
5518 })?;
5519 *require_output(output, "output")? = PioJsonObjectEntryView {
5520 key: PioStringView::new(key),
5521 value_kind: PioStringView::new(json_value_kind(value)),
5522 };
5523 Ok(true)
5524 })
5525 }
5526}
5527
5528#[unsafe(no_mangle)]
5533pub unsafe extern "C" fn pio_json_value_object_value_at(
5534 value: *const PioJsonValue,
5535 index: usize,
5536 error: *mut *mut PioError,
5537) -> *mut PioJsonValue {
5538 unsafe {
5539 entry(error, std::ptr::null_mut(), || {
5540 let value = PioJsonValue::get(value).ok_or_else(|| {
5541 boundary_error(
5542 &codes::BIND_CAPI_NULL_HANDLE,
5543 "PioJsonValue must not be NULL",
5544 )
5545 })?;
5546 let values = value
5547 .value()
5548 .and_then(serde_json::Value::as_object)
5549 .ok_or_else(|| {
5550 boundary_error(
5551 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5552 "the structured value is not an object",
5553 )
5554 })?;
5555 if values.values().nth(index).is_none() {
5556 return Err(boundary_error(
5557 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
5558 format!("structured object index {index} is out of range"),
5559 ));
5560 }
5561 Ok(PioJsonValue::new_raw(
5562 value.child(JsonValueStep::Object(index)),
5563 ))
5564 })
5565 }
5566}
5567
5568#[unsafe(no_mangle)]
5573pub unsafe extern "C" fn pio_json_value_retain(value: *const PioJsonValue) -> *mut PioJsonValue {
5574 unsafe { PioJsonValue::retain_raw(value) }
5575}
5576
5577#[unsafe(no_mangle)]
5582pub unsafe extern "C" fn pio_json_value_release(value: *mut PioJsonValue) {
5583 unsafe { PioJsonValue::release_raw(value) };
5584}
5585
5586#[unsafe(no_mangle)]
5591pub unsafe extern "C" fn pio_module_retain(module: *const PioModule) -> *mut PioModule {
5592 unsafe { PioModule::retain_raw(module) }
5593}
5594
5595#[unsafe(no_mangle)]
5600pub unsafe extern "C" fn pio_module_release(module: *mut PioModule) {
5601 unsafe { PioModule::release_raw(module) };
5602}
5603
5604#[unsafe(no_mangle)]
5609pub unsafe extern "C" fn pio_value_type_name(value: *const PioValueHandle) -> PioStringView {
5610 unsafe { PioValueHandle::get(value) }
5611 .and_then(ValueInner::value)
5612 .map_or(PioStringView::EMPTY, |value| {
5613 PioStringView::new(value.type_name())
5614 })
5615}
5616
5617#[unsafe(no_mangle)]
5622pub unsafe extern "C" fn pio_value_is_type(
5623 value: *const PioValueHandle,
5624 type_name: *const c_char,
5625 type_name_len: usize,
5626) -> bool {
5627 let Some(value) = unsafe { PioValueHandle::get(value) }.and_then(ValueInner::value) else {
5628 return false;
5629 };
5630 let Ok(type_name) = (unsafe { input_str(type_name, type_name_len, "type_name") }) else {
5631 return false;
5632 };
5633 value.type_name() == type_name
5634}
5635
5636#[unsafe(no_mangle)]
5641pub unsafe extern "C" fn pio_value_balanced_network(
5642 value: *const PioValueHandle,
5643 error: *mut *mut PioError,
5644) -> *mut PioBalancedNetwork {
5645 unsafe {
5646 entry(error, std::ptr::null_mut(), || {
5647 let value = require_value(value)?;
5648 if !matches!(value.value(), Some(PioValue::BalancedNetwork(_))) {
5649 return Err(boundary_error(
5650 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5651 "the value is not powerio.BalancedNetwork",
5652 ));
5653 }
5654 Ok(PioBalancedNetwork::new_raw(BalancedNetworkInner {
5655 value: ValueInner {
5656 owner: Arc::clone(&value.owner),
5657 steps: value.steps.clone(),
5658 },
5659 projection: BalancedNetworkProjection::Direct,
5660 }))
5661 })
5662 }
5663}
5664
5665#[unsafe(no_mangle)]
5672pub unsafe extern "C" fn pio_value_geo_layer(
5673 value: *const PioValueHandle,
5674 error: *mut *mut PioError,
5675) -> *mut PioGeoLayer {
5676 unsafe {
5677 entry(error, std::ptr::null_mut(), || {
5678 let value = require_value(value)?;
5679 let Some(PioValue::GeoLayer(layer)) = value.value() else {
5680 return Err(boundary_error(
5681 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5682 "the value is not powerio.GeoLayer",
5683 ));
5684 };
5685 Ok(PioGeoLayer::new_raw(GeoLayerInner {
5686 layer: layer.clone(),
5687 diagnostics: Vec::new(),
5688 }))
5689 })
5690 }
5691}
5692
5693#[unsafe(no_mangle)]
5700pub unsafe extern "C" fn pio_value_contingency_set(
5701 value: *const PioValueHandle,
5702 error: *mut *mut PioError,
5703) -> *mut PioContingencySet {
5704 unsafe {
5705 entry(error, std::ptr::null_mut(), || {
5706 let value = require_value(value)?;
5707 if !matches!(value.value(), Some(PioValue::ContingencySet(_))) {
5708 return Err(boundary_error(
5709 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5710 "the value is not powerio.ContingencySet",
5711 ));
5712 }
5713 Ok(PioContingencySet::new_raw(ContingencySetInner {
5714 source: ContingencySource::Value(ValueInner {
5715 owner: Arc::clone(&value.owner),
5716 steps: value.steps.clone(),
5717 }),
5718 diagnostics: Vec::new(),
5719 }))
5720 })
5721 }
5722}
5723
5724#[unsafe(no_mangle)]
5731pub unsafe extern "C" fn pio_value_subsystem_set(
5732 value: *const PioValueHandle,
5733 error: *mut *mut PioError,
5734) -> *mut PioSubsystemSet {
5735 unsafe {
5736 entry(error, std::ptr::null_mut(), || {
5737 let value = require_value(value)?;
5738 if !matches!(value.value(), Some(PioValue::SubsystemSet(_))) {
5739 return Err(boundary_error(
5740 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5741 "the value is not powerio.SubsystemSet",
5742 ));
5743 }
5744 Ok(PioSubsystemSet::new_raw(SubsystemSetInner {
5745 source: ContingencySource::Value(ValueInner {
5746 owner: Arc::clone(&value.owner),
5747 steps: value.steps.clone(),
5748 }),
5749 }))
5750 })
5751 }
5752}
5753
5754#[unsafe(no_mangle)]
5761pub unsafe extern "C" fn pio_value_monitored_set(
5762 value: *const PioValueHandle,
5763 error: *mut *mut PioError,
5764) -> *mut PioMonitoredSet {
5765 unsafe {
5766 entry(error, std::ptr::null_mut(), || {
5767 let value = require_value(value)?;
5768 if !matches!(value.value(), Some(PioValue::MonitoredSet(_))) {
5769 return Err(boundary_error(
5770 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5771 "the value is not powerio.MonitoredSet",
5772 ));
5773 }
5774 Ok(PioMonitoredSet::new_raw(MonitoredSetInner {
5775 source: ContingencySource::Value(ValueInner {
5776 owner: Arc::clone(&value.owner),
5777 steps: value.steps.clone(),
5778 }),
5779 }))
5780 })
5781 }
5782}
5783
5784#[unsafe(no_mangle)]
5789pub unsafe extern "C" fn pio_value_multiconductor_network(
5790 value: *const PioValueHandle,
5791 error: *mut *mut PioError,
5792) -> *mut PioMulticonductorNetwork {
5793 unsafe {
5794 entry(error, std::ptr::null_mut(), || {
5795 let value = require_value(value)?;
5796 if !matches!(value.value(), Some(PioValue::MulticonductorNetwork(_))) {
5797 return Err(boundary_error(
5798 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5799 "the value is not powerio.MulticonductorNetwork",
5800 ));
5801 }
5802 Ok(PioMulticonductorNetwork::new_raw(
5803 MulticonductorNetworkInner {
5804 value: ValueInner {
5805 owner: Arc::clone(&value.owner),
5806 steps: value.steps.clone(),
5807 },
5808 projection: MulticonductorNetworkProjection::Direct,
5809 },
5810 ))
5811 })
5812 }
5813}
5814
5815#[unsafe(no_mangle)]
5820pub unsafe extern "C" fn pio_value_time_series(
5821 value: *const PioValueHandle,
5822 error: *mut *mut PioError,
5823) -> *mut PioTimeSeriesHandle {
5824 unsafe {
5825 entry(error, std::ptr::null_mut(), || {
5826 let value = require_value(value)?;
5827 if !matches!(value.value(), Some(PioValue::TimeSeries(_))) {
5828 return Err(boundary_error(
5829 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5830 "the value is not powerio.TimeSeries<T>",
5831 ));
5832 }
5833 Ok(PioTimeSeriesHandle::new_raw(ValueInner {
5834 owner: Arc::clone(&value.owner),
5835 steps: value.steps.clone(),
5836 }))
5837 })
5838 }
5839}
5840
5841#[unsafe(no_mangle)]
5846pub unsafe extern "C" fn pio_value_scenario_set(
5847 value: *const PioValueHandle,
5848 error: *mut *mut PioError,
5849) -> *mut PioScenarioSetHandle {
5850 unsafe {
5851 entry(error, std::ptr::null_mut(), || {
5852 let value = require_value(value)?;
5853 if !matches!(value.value(), Some(PioValue::ScenarioSet(_))) {
5854 return Err(boundary_error(
5855 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5856 "the value is not powerio.ScenarioSet<T>",
5857 ));
5858 }
5859 Ok(PioScenarioSetHandle::new_raw(ValueInner {
5860 owner: Arc::clone(&value.owner),
5861 steps: value.steps.clone(),
5862 }))
5863 })
5864 }
5865}
5866
5867#[derive(Clone, Copy)]
5868enum ExpectedValue {
5869 BalancedOperatingPoint,
5870 MulticonductorOperatingPoint,
5871 DcPfInstance,
5872 AcPfInstance,
5873 DcOpfInstance,
5874 AcOpfInstance,
5875 McAcPfInstance,
5876 McAcOpfInstance,
5877 LinDist3FlowOpfInstance,
5878 AcScucInstance,
5879 DcPfSolution,
5880 AcPfSolution,
5881 DcOpfSolution,
5882 AcOpfSolution,
5883 SocwrOpfSolution,
5884 McAcPfSolution,
5885 McAcOpfSolution,
5886 LinDist3FlowOpfSolution,
5887 AcScucSolution,
5888}
5889
5890impl ExpectedValue {
5891 fn matches(self, value: &PioValue) -> bool {
5892 matches!(
5893 (self, value),
5894 (
5895 Self::BalancedOperatingPoint,
5896 PioValue::BalancedOperatingPoint(_)
5897 ) | (
5898 Self::MulticonductorOperatingPoint,
5899 PioValue::MulticonductorOperatingPoint(_)
5900 ) | (Self::DcPfInstance, PioValue::DcPfInstance(_))
5901 | (Self::AcPfInstance, PioValue::AcPfInstance(_))
5902 | (Self::DcOpfInstance, PioValue::DcOpfInstance(_))
5903 | (Self::AcOpfInstance, PioValue::AcOpfInstance(_))
5904 | (Self::McAcPfInstance, PioValue::McAcPfInstance(_))
5905 | (Self::McAcOpfInstance, PioValue::McAcOpfInstance(_))
5906 | (
5907 Self::LinDist3FlowOpfInstance,
5908 PioValue::LinDist3FlowOpfInstance(_)
5909 )
5910 | (Self::AcScucInstance, PioValue::AcScucInstance(_))
5911 | (Self::DcPfSolution, PioValue::DcPfSolution(_))
5912 | (Self::AcPfSolution, PioValue::AcPfSolution(_))
5913 | (Self::DcOpfSolution, PioValue::DcOpfSolution(_))
5914 | (Self::AcOpfSolution, PioValue::AcOpfSolution(_))
5915 | (Self::SocwrOpfSolution, PioValue::SocwrOpfSolution(_))
5916 | (Self::McAcPfSolution, PioValue::McAcPfSolution(_))
5917 | (Self::McAcOpfSolution, PioValue::McAcOpfSolution(_))
5918 | (
5919 Self::LinDist3FlowOpfSolution,
5920 PioValue::LinDist3FlowOpfSolution(_)
5921 )
5922 | (Self::AcScucSolution, PioValue::AcScucSolution(_))
5923 )
5924 }
5925
5926 fn type_name(self) -> &'static str {
5927 match self {
5928 Self::BalancedOperatingPoint => "powerio.OperatingPoint<powerio.BalancedNetwork>",
5929 Self::MulticonductorOperatingPoint => {
5930 "powerio.OperatingPoint<powerio.MulticonductorNetwork>"
5931 }
5932 Self::DcPfInstance => "powerio.DcPfInstance",
5933 Self::AcPfInstance => "powerio.AcPfInstance",
5934 Self::DcOpfInstance => "powerio.DcOpfInstance",
5935 Self::AcOpfInstance => "powerio.AcOpfInstance",
5936 Self::McAcPfInstance => "powerio.McAcPfInstance",
5937 Self::McAcOpfInstance => "powerio.McAcOpfInstance",
5938 Self::LinDist3FlowOpfInstance => "powerio.LinDist3FlowOpfInstance",
5939 Self::AcScucInstance => "powerio.AcScucInstance",
5940 Self::DcPfSolution => "powerio.DcPfSolution",
5941 Self::AcPfSolution => "powerio.AcPfSolution",
5942 Self::DcOpfSolution => "powerio.DcOpfSolution",
5943 Self::AcOpfSolution => "powerio.AcOpfSolution",
5944 Self::SocwrOpfSolution => "powerio.SocwrOpfSolution",
5945 Self::McAcPfSolution => "powerio.McAcPfSolution",
5946 Self::McAcOpfSolution => "powerio.McAcOpfSolution",
5947 Self::LinDist3FlowOpfSolution => "powerio.LinDist3FlowOpfSolution",
5948 Self::AcScucSolution => "powerio.AcScucSolution",
5949 }
5950 }
5951}
5952
5953unsafe fn checked_typed_value(
5954 value: *const PioValueHandle,
5955 expected: ExpectedValue,
5956) -> Result<ValueInner, *mut PioError> {
5957 let value = unsafe { require_value(value) }?;
5958 if !value.value().is_some_and(|value| expected.matches(value)) {
5959 return Err(boundary_error(
5960 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5961 format!("the value is not {}", expected.type_name()),
5962 ));
5963 }
5964 Ok(ValueInner {
5965 owner: Arc::clone(&value.owner),
5966 steps: value.steps.clone(),
5967 })
5968}
5969
5970unsafe fn operating_point_accessor(
5971 value: *const PioValueHandle,
5972 expected: ExpectedValue,
5973 error: *mut *mut PioError,
5974) -> *mut PioOperatingPoint {
5975 unsafe {
5976 entry(error, std::ptr::null_mut(), || {
5977 let value = checked_typed_value(value, expected)?;
5978 let projection = match expected {
5979 ExpectedValue::BalancedOperatingPoint => OperatingPointProjection::DirectBalanced,
5980 ExpectedValue::MulticonductorOperatingPoint => {
5981 OperatingPointProjection::DirectMulticonductor
5982 }
5983 _ => {
5984 return Err(boundary_error(
5985 &codes::REQUEST_CAPI_TYPE_MISMATCH,
5986 "the requested value is not an operating point",
5987 ));
5988 }
5989 };
5990 Ok(PioOperatingPoint::new_raw(OperatingPointInner {
5991 value,
5992 projection,
5993 }))
5994 })
5995 }
5996}
5997
5998unsafe fn instance_accessor(
5999 value: *const PioValueHandle,
6000 expected: ExpectedValue,
6001 error: *mut *mut PioError,
6002) -> *mut PioCalculationInstance {
6003 unsafe {
6004 entry(error, std::ptr::null_mut(), || {
6005 checked_typed_value(value, expected).map(|value| {
6006 PioCalculationInstance::new_raw(CalculationInstanceInner {
6007 value,
6008 projection: CalculationInstanceProjection::Direct,
6009 })
6010 })
6011 })
6012 }
6013}
6014
6015unsafe fn solution_accessor(
6016 value: *const PioValueHandle,
6017 expected: ExpectedValue,
6018 error: *mut *mut PioError,
6019) -> *mut PioCalculationSolution {
6020 unsafe {
6021 entry(error, std::ptr::null_mut(), || {
6022 checked_typed_value(value, expected).map(PioCalculationSolution::new_raw)
6023 })
6024 }
6025}
6026
6027#[unsafe(no_mangle)]
6031pub unsafe extern "C" fn pio_value_balanced_operating_point(
6032 value: *const PioValueHandle,
6033 error: *mut *mut PioError,
6034) -> *mut PioOperatingPoint {
6035 unsafe { operating_point_accessor(value, ExpectedValue::BalancedOperatingPoint, error) }
6036}
6037
6038#[unsafe(no_mangle)]
6042pub unsafe extern "C" fn pio_value_multiconductor_operating_point(
6043 value: *const PioValueHandle,
6044 error: *mut *mut PioError,
6045) -> *mut PioOperatingPoint {
6046 unsafe { operating_point_accessor(value, ExpectedValue::MulticonductorOperatingPoint, error) }
6047}
6048
6049#[unsafe(no_mangle)]
6053pub unsafe extern "C" fn pio_value_dc_pf_instance(
6054 value: *const PioValueHandle,
6055 error: *mut *mut PioError,
6056) -> *mut PioCalculationInstance {
6057 unsafe { instance_accessor(value, ExpectedValue::DcPfInstance, error) }
6058}
6059
6060#[unsafe(no_mangle)]
6064pub unsafe extern "C" fn pio_value_ac_pf_instance(
6065 value: *const PioValueHandle,
6066 error: *mut *mut PioError,
6067) -> *mut PioCalculationInstance {
6068 unsafe { instance_accessor(value, ExpectedValue::AcPfInstance, error) }
6069}
6070
6071#[unsafe(no_mangle)]
6075pub unsafe extern "C" fn pio_value_dc_opf_instance(
6076 value: *const PioValueHandle,
6077 error: *mut *mut PioError,
6078) -> *mut PioCalculationInstance {
6079 unsafe { instance_accessor(value, ExpectedValue::DcOpfInstance, error) }
6080}
6081
6082#[unsafe(no_mangle)]
6086pub unsafe extern "C" fn pio_value_ac_opf_instance(
6087 value: *const PioValueHandle,
6088 error: *mut *mut PioError,
6089) -> *mut PioCalculationInstance {
6090 unsafe { instance_accessor(value, ExpectedValue::AcOpfInstance, error) }
6091}
6092
6093#[unsafe(no_mangle)]
6097pub unsafe extern "C" fn pio_value_mc_ac_pf_instance(
6098 value: *const PioValueHandle,
6099 error: *mut *mut PioError,
6100) -> *mut PioCalculationInstance {
6101 unsafe { instance_accessor(value, ExpectedValue::McAcPfInstance, error) }
6102}
6103
6104#[unsafe(no_mangle)]
6108pub unsafe extern "C" fn pio_value_mc_ac_opf_instance(
6109 value: *const PioValueHandle,
6110 error: *mut *mut PioError,
6111) -> *mut PioCalculationInstance {
6112 unsafe { instance_accessor(value, ExpectedValue::McAcOpfInstance, error) }
6113}
6114
6115#[unsafe(no_mangle)]
6119pub unsafe extern "C" fn pio_value_lindist3flow_opf_instance(
6120 value: *const PioValueHandle,
6121 error: *mut *mut PioError,
6122) -> *mut PioCalculationInstance {
6123 unsafe { instance_accessor(value, ExpectedValue::LinDist3FlowOpfInstance, error) }
6124}
6125
6126#[unsafe(no_mangle)]
6130pub unsafe extern "C" fn pio_value_ac_scuc_instance(
6131 value: *const PioValueHandle,
6132 error: *mut *mut PioError,
6133) -> *mut PioCalculationInstance {
6134 unsafe { instance_accessor(value, ExpectedValue::AcScucInstance, error) }
6135}
6136
6137#[unsafe(no_mangle)]
6141pub unsafe extern "C" fn pio_value_dc_pf_solution(
6142 value: *const PioValueHandle,
6143 error: *mut *mut PioError,
6144) -> *mut PioCalculationSolution {
6145 unsafe { solution_accessor(value, ExpectedValue::DcPfSolution, error) }
6146}
6147
6148#[unsafe(no_mangle)]
6152pub unsafe extern "C" fn pio_value_ac_pf_solution(
6153 value: *const PioValueHandle,
6154 error: *mut *mut PioError,
6155) -> *mut PioCalculationSolution {
6156 unsafe { solution_accessor(value, ExpectedValue::AcPfSolution, error) }
6157}
6158
6159#[unsafe(no_mangle)]
6163pub unsafe extern "C" fn pio_value_dc_opf_solution(
6164 value: *const PioValueHandle,
6165 error: *mut *mut PioError,
6166) -> *mut PioCalculationSolution {
6167 unsafe { solution_accessor(value, ExpectedValue::DcOpfSolution, error) }
6168}
6169
6170#[unsafe(no_mangle)]
6174pub unsafe extern "C" fn pio_value_ac_opf_solution(
6175 value: *const PioValueHandle,
6176 error: *mut *mut PioError,
6177) -> *mut PioCalculationSolution {
6178 unsafe { solution_accessor(value, ExpectedValue::AcOpfSolution, error) }
6179}
6180
6181#[unsafe(no_mangle)]
6185pub unsafe extern "C" fn pio_value_socwr_opf_solution(
6186 value: *const PioValueHandle,
6187 error: *mut *mut PioError,
6188) -> *mut PioCalculationSolution {
6189 unsafe { solution_accessor(value, ExpectedValue::SocwrOpfSolution, error) }
6190}
6191
6192#[unsafe(no_mangle)]
6196pub unsafe extern "C" fn pio_value_mc_ac_pf_solution(
6197 value: *const PioValueHandle,
6198 error: *mut *mut PioError,
6199) -> *mut PioCalculationSolution {
6200 unsafe { solution_accessor(value, ExpectedValue::McAcPfSolution, error) }
6201}
6202
6203#[unsafe(no_mangle)]
6207pub unsafe extern "C" fn pio_value_mc_ac_opf_solution(
6208 value: *const PioValueHandle,
6209 error: *mut *mut PioError,
6210) -> *mut PioCalculationSolution {
6211 unsafe { solution_accessor(value, ExpectedValue::McAcOpfSolution, error) }
6212}
6213
6214#[unsafe(no_mangle)]
6218pub unsafe extern "C" fn pio_value_lindist3flow_opf_solution(
6219 value: *const PioValueHandle,
6220 error: *mut *mut PioError,
6221) -> *mut PioCalculationSolution {
6222 unsafe { solution_accessor(value, ExpectedValue::LinDist3FlowOpfSolution, error) }
6223}
6224
6225#[unsafe(no_mangle)]
6229pub unsafe extern "C" fn pio_value_ac_scuc_solution(
6230 value: *const PioValueHandle,
6231 error: *mut *mut PioError,
6232) -> *mut PioCalculationSolution {
6233 unsafe { solution_accessor(value, ExpectedValue::AcScucSolution, error) }
6234}
6235
6236#[unsafe(no_mangle)]
6240pub unsafe extern "C" fn pio_operating_point_type_name(
6241 point: *const PioOperatingPoint,
6242) -> PioStringView {
6243 unsafe { PioOperatingPoint::get(point) }
6244 .and_then(OperatingPointInner::type_name)
6245 .map_or(PioStringView::EMPTY, PioStringView::new)
6246}
6247
6248#[unsafe(no_mangle)]
6252pub unsafe extern "C" fn pio_calculation_instance_type_name(
6253 instance: *const PioCalculationInstance,
6254) -> PioStringView {
6255 unsafe { PioCalculationInstance::get(instance) }
6256 .and_then(CalculationInstanceInner::type_name)
6257 .map_or(PioStringView::EMPTY, PioStringView::new)
6258}
6259
6260#[unsafe(no_mangle)]
6264pub unsafe extern "C" fn pio_calculation_solution_type_name(
6265 solution: *const PioCalculationSolution,
6266) -> PioStringView {
6267 unsafe { PioCalculationSolution::get(solution) }
6268 .and_then(ValueInner::value)
6269 .map_or(PioStringView::EMPTY, |value| {
6270 PioStringView::new(value.type_name())
6271 })
6272}
6273
6274#[unsafe(no_mangle)]
6280pub unsafe extern "C" fn pio_calculation_solution_instance(
6281 solution: *const PioCalculationSolution,
6282 error: *mut *mut PioError,
6283) -> *mut PioCalculationInstance {
6284 unsafe {
6285 entry(error, std::ptr::null_mut(), || {
6286 let solution = PioCalculationSolution::get(solution).ok_or_else(|| {
6287 boundary_error(
6288 &codes::BIND_CAPI_NULL_HANDLE,
6289 "PioCalculationSolution must not be NULL",
6290 )
6291 })?;
6292 let projection = match solution.value() {
6293 Some(PioValue::DcPfSolution(_)) => CalculationInstanceProjection::DcPfSolution,
6294 Some(PioValue::AcPfSolution(_)) => CalculationInstanceProjection::AcPfSolution,
6295 Some(PioValue::DcOpfSolution(_)) => CalculationInstanceProjection::DcOpfSolution,
6296 Some(PioValue::AcOpfSolution(_)) => CalculationInstanceProjection::AcOpfSolution,
6297 Some(PioValue::SocwrOpfSolution(_)) => {
6298 CalculationInstanceProjection::SocwrOpfSolution
6299 }
6300 Some(PioValue::McAcPfSolution(_)) => CalculationInstanceProjection::McAcPfSolution,
6301 Some(PioValue::McAcOpfSolution(_)) => {
6302 CalculationInstanceProjection::McAcOpfSolution
6303 }
6304 Some(PioValue::LinDist3FlowOpfSolution(_)) => {
6305 CalculationInstanceProjection::LinDist3FlowOpfSolution
6306 }
6307 Some(PioValue::AcScucSolution(_)) => CalculationInstanceProjection::AcScucSolution,
6308 _ => {
6309 return Err(boundary_error(
6310 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6311 "the handle does not refer to a calculation solution",
6312 ));
6313 }
6314 };
6315 Ok(PioCalculationInstance::new_raw(CalculationInstanceInner {
6316 value: ValueInner {
6317 owner: Arc::clone(&solution.owner),
6318 steps: solution.steps.clone(),
6319 },
6320 projection,
6321 }))
6322 })
6323 }
6324}
6325
6326fn make_balanced_network_view(
6327 value: &ValueInner,
6328 projection: BalancedNetworkProjection,
6329) -> *mut PioBalancedNetwork {
6330 PioBalancedNetwork::new_raw(BalancedNetworkInner {
6331 value: ValueInner {
6332 owner: Arc::clone(&value.owner),
6333 steps: value.steps.clone(),
6334 },
6335 projection,
6336 })
6337}
6338
6339fn make_multiconductor_network_view(
6340 value: &ValueInner,
6341 projection: MulticonductorNetworkProjection,
6342) -> *mut PioMulticonductorNetwork {
6343 PioMulticonductorNetwork::new_raw(MulticonductorNetworkInner {
6344 value: ValueInner {
6345 owner: Arc::clone(&value.owner),
6346 steps: value.steps.clone(),
6347 },
6348 projection,
6349 })
6350}
6351
6352#[unsafe(no_mangle)]
6356pub unsafe extern "C" fn pio_operating_point_balanced_network(
6357 point: *const PioOperatingPoint,
6358 error: *mut *mut PioError,
6359) -> *mut PioBalancedNetwork {
6360 unsafe {
6361 entry(error, std::ptr::null_mut(), || {
6362 let point = PioOperatingPoint::get(point).ok_or_else(|| {
6363 boundary_error(
6364 &codes::BIND_CAPI_NULL_HANDLE,
6365 "PioOperatingPoint must not be NULL",
6366 )
6367 })?;
6368 let projection = point.balanced_network_projection().ok_or_else(|| {
6369 boundary_error(
6370 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6371 "the operating point does not use powerio.BalancedNetwork",
6372 )
6373 })?;
6374 Ok(make_balanced_network_view(&point.value, projection))
6375 })
6376 }
6377}
6378
6379#[unsafe(no_mangle)]
6383pub unsafe extern "C" fn pio_operating_point_multiconductor_network(
6384 point: *const PioOperatingPoint,
6385 error: *mut *mut PioError,
6386) -> *mut PioMulticonductorNetwork {
6387 unsafe {
6388 entry(error, std::ptr::null_mut(), || {
6389 let point = PioOperatingPoint::get(point).ok_or_else(|| {
6390 boundary_error(
6391 &codes::BIND_CAPI_NULL_HANDLE,
6392 "PioOperatingPoint must not be NULL",
6393 )
6394 })?;
6395 let projection = point.multiconductor_network_projection().ok_or_else(|| {
6396 boundary_error(
6397 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6398 "the operating point does not use powerio.MulticonductorNetwork",
6399 )
6400 })?;
6401 Ok(make_multiconductor_network_view(&point.value, projection))
6402 })
6403 }
6404}
6405
6406#[unsafe(no_mangle)]
6410pub unsafe extern "C" fn pio_calculation_instance_balanced_network(
6411 instance: *const PioCalculationInstance,
6412 error: *mut *mut PioError,
6413) -> *mut PioBalancedNetwork {
6414 unsafe {
6415 entry(error, std::ptr::null_mut(), || {
6416 let instance = PioCalculationInstance::get(instance).ok_or_else(|| {
6417 boundary_error(
6418 &codes::BIND_CAPI_NULL_HANDLE,
6419 "PioCalculationInstance must not be NULL",
6420 )
6421 })?;
6422 let projection = match instance.projection {
6423 CalculationInstanceProjection::Direct => match instance.instance() {
6424 Some(CalculationInstanceRef::DcPf(_)) => {
6425 BalancedNetworkProjection::DcPfInstance
6426 }
6427 Some(CalculationInstanceRef::AcPf(_)) => {
6428 BalancedNetworkProjection::AcPfInstance
6429 }
6430 Some(CalculationInstanceRef::DcOpf(_)) => {
6431 BalancedNetworkProjection::DcOpfInstance
6432 }
6433 Some(CalculationInstanceRef::AcOpf(_)) => {
6434 BalancedNetworkProjection::AcOpfInstance
6435 }
6436 Some(CalculationInstanceRef::AcScuc(_)) => {
6437 BalancedNetworkProjection::AcScucInstance
6438 }
6439 _ => {
6440 return Err(boundary_error(
6441 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6442 "the calculation instance does not use powerio.BalancedNetwork",
6443 ));
6444 }
6445 },
6446 CalculationInstanceProjection::DcPfSolution => {
6447 BalancedNetworkProjection::DcPfSolution
6448 }
6449 CalculationInstanceProjection::AcPfSolution => {
6450 BalancedNetworkProjection::AcPfSolution
6451 }
6452 CalculationInstanceProjection::DcOpfSolution => {
6453 BalancedNetworkProjection::DcOpfSolution
6454 }
6455 CalculationInstanceProjection::AcOpfSolution => {
6456 BalancedNetworkProjection::AcOpfSolution
6457 }
6458 CalculationInstanceProjection::SocwrOpfSolution => {
6459 BalancedNetworkProjection::SocwrOpfSolution
6460 }
6461 CalculationInstanceProjection::AcScucSolution => {
6462 BalancedNetworkProjection::AcScucSolution
6463 }
6464 _ => {
6465 return Err(boundary_error(
6466 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6467 "the calculation instance does not use powerio.BalancedNetwork",
6468 ));
6469 }
6470 };
6471 Ok(make_balanced_network_view(&instance.value, projection))
6472 })
6473 }
6474}
6475
6476#[unsafe(no_mangle)]
6480pub unsafe extern "C" fn pio_calculation_instance_multiconductor_network(
6481 instance: *const PioCalculationInstance,
6482 error: *mut *mut PioError,
6483) -> *mut PioMulticonductorNetwork {
6484 unsafe {
6485 entry(error, std::ptr::null_mut(), || {
6486 let instance = PioCalculationInstance::get(instance).ok_or_else(|| {
6487 boundary_error(
6488 &codes::BIND_CAPI_NULL_HANDLE,
6489 "PioCalculationInstance must not be NULL",
6490 )
6491 })?;
6492 let projection = match instance.projection {
6493 CalculationInstanceProjection::Direct => match instance.instance() {
6494 Some(CalculationInstanceRef::McAcPf(_)) => {
6495 MulticonductorNetworkProjection::McAcPfInstance
6496 }
6497 Some(CalculationInstanceRef::McAcOpf(_)) => {
6498 MulticonductorNetworkProjection::McAcOpfInstance
6499 }
6500 Some(CalculationInstanceRef::LinDist3FlowOpf(_)) => {
6501 MulticonductorNetworkProjection::LinDist3FlowOpfInstance
6502 }
6503 _ => {
6504 return Err(boundary_error(
6505 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6506 "the calculation instance does not use powerio.MulticonductorNetwork",
6507 ));
6508 }
6509 },
6510 CalculationInstanceProjection::McAcPfSolution => {
6511 MulticonductorNetworkProjection::McAcPfSolution
6512 }
6513 CalculationInstanceProjection::McAcOpfSolution => {
6514 MulticonductorNetworkProjection::McAcOpfSolution
6515 }
6516 CalculationInstanceProjection::LinDist3FlowOpfSolution => {
6517 MulticonductorNetworkProjection::LinDist3FlowOpfSolution
6518 }
6519 _ => {
6520 return Err(boundary_error(
6521 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6522 "the calculation instance does not use powerio.MulticonductorNetwork",
6523 ));
6524 }
6525 };
6526 Ok(make_multiconductor_network_view(
6527 &instance.value,
6528 projection,
6529 ))
6530 })
6531 }
6532}
6533
6534fn branch_susceptance_formula_name(formula: BranchSusceptanceFormula) -> &'static str {
6535 match formula {
6536 BranchSusceptanceFormula::ReactanceOnly => "reactance_only",
6537 BranchSusceptanceFormula::TapAdjustedReactance => "tap_adjusted_reactance",
6538 BranchSusceptanceFormula::SeriesSusceptance => "series_susceptance",
6539 _ => "unknown",
6540 }
6541}
6542
6543unsafe fn require_calculation_instance<'a>(
6544 instance: *const PioCalculationInstance,
6545) -> Result<&'a CalculationInstanceInner, *mut PioError> {
6546 unsafe { PioCalculationInstance::get(instance) }.ok_or_else(|| {
6547 boundary_error(
6548 &codes::BIND_CAPI_NULL_HANDLE,
6549 "PioCalculationInstance must not be NULL",
6550 )
6551 })
6552}
6553
6554fn opf_preparation_units(name: &str) -> Result<Units, *mut PioError> {
6555 match name {
6556 "per_unit" => Ok(Units::PerUnit),
6557 "native" => Ok(Units::Native),
6558 _ => Err(boundary_error(
6559 &codes::BIND_CAPI_INVALID_OPTIONS,
6560 format!("unknown OPF preparation units '{name}'; expected 'per_unit' or 'native'"),
6561 )),
6562 }
6563}
6564
6565fn opf_preparation_units_name(units: Units) -> Option<&'static str> {
6566 match units {
6567 Units::PerUnit => Some("per_unit"),
6568 Units::Native => Some("native"),
6569 _ => None,
6570 }
6571}
6572
6573fn prepared_objective_name(objective: PreparedObjective) -> Option<&'static str> {
6574 match objective {
6575 PreparedObjective::Feasibility => Some("feasibility"),
6576 PreparedObjective::NetworkGeneratorCost => Some("network_generator_cost"),
6577 _ => None,
6578 }
6579}
6580
6581fn opf_analysis_branch_source(
6582 source: AnalysisBranchSource,
6583 preparation: &str,
6584) -> Result<(&'static str, usize, Option<usize>), *mut PioError> {
6585 match source {
6586 AnalysisBranchSource::Branch { row } => Ok(("branch", row, None)),
6587 AnalysisBranchSource::ThreeWindingTransformerWinding {
6588 transformer_row,
6589 winding,
6590 } => Ok((
6591 "three_winding_transformer_winding",
6592 transformer_row,
6593 Some(winding),
6594 )),
6595 _ => Err(boundary_error(
6596 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6597 format!(
6598 "the {preparation} OPF preparation has an analysis branch source unsupported by ABI 7"
6599 ),
6600 )),
6601 }
6602}
6603
6604#[unsafe(no_mangle)]
6609pub unsafe extern "C" fn pio_build_dc_opf_preparation(
6610 instance: *const PioCalculationInstance,
6611 units: *const c_char,
6612 units_len: usize,
6613 skip_zero_impedance: bool,
6614 synthesize_unrated_limits: bool,
6615 correct_angle_difference_bounds: bool,
6616 error: *mut *mut PioError,
6617) -> *mut PioDcOpfPreparation {
6618 unsafe {
6619 entry(error, std::ptr::null_mut(), || {
6620 let instance = require_calculation_instance(instance)?
6621 .dc_opf()
6622 .ok_or_else(|| {
6623 boundary_error(
6624 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6625 "DC OPF preparation requires powerio.DcOpfInstance",
6626 )
6627 })?;
6628 let units = opf_preparation_units(required_str(units, units_len, "units")?)?;
6629 let options = DcOpfAssemblyOptions::default()
6630 .with_units(units)
6631 .with_skip_zero_impedance(skip_zero_impedance)
6632 .with_synthesize_unrated_limits(synthesize_unrated_limits)
6633 .with_correct_angle_difference_bounds(correct_angle_difference_bounds);
6634 build_dc_opf_preparation(instance, &options)
6635 .map(PioDcOpfPreparation::new_raw)
6636 .map_err(|failure| error_from_matrix(&failure))
6637 })
6638 }
6639}
6640
6641#[unsafe(no_mangle)]
6646pub unsafe extern "C" fn pio_dc_opf_preparation_summary(
6647 preparation: *const PioDcOpfPreparation,
6648 output: *mut PioDcOpfPreparationView,
6649 error: *mut *mut PioError,
6650) -> bool {
6651 unsafe {
6652 entry(error, false, || {
6653 let preparation = PioDcOpfPreparation::get(preparation).ok_or_else(|| {
6654 boundary_error(
6655 &codes::BIND_CAPI_NULL_HANDLE,
6656 "PioDcOpfPreparation must not be NULL",
6657 )
6658 })?;
6659 let units = opf_preparation_units_name(preparation.units).ok_or_else(|| {
6660 boundary_error(
6661 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6662 "the DC OPF preparation uses units unsupported by ABI 7",
6663 )
6664 })?;
6665 let objective = prepared_objective_name(preparation.objective).ok_or_else(|| {
6666 boundary_error(
6667 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6668 "the DC OPF preparation uses an objective unsupported by ABI 7",
6669 )
6670 })?;
6671 *require_output(output, "output")? = PioDcOpfPreparationView {
6672 name: PioStringView::new(&preparation.name),
6673 bus_count: preparation.n_buses,
6674 generator_count: preparation.n_generators(),
6675 branch_count: preparation.n_branches(),
6676 source_generator_count: preparation.n_source_generators,
6677 source_branch_count: preparation.n_source_branches,
6678 base_mva: preparation.base_mva,
6679 units: PioStringView::new(units),
6680 branch_susceptance_formula: PioStringView::new(branch_susceptance_formula_name(
6681 preparation.formula,
6682 )),
6683 objective: PioStringView::new(objective),
6684 skip_zero_impedance: preparation.skip_zero_impedance,
6685 synthesize_unrated_limits: preparation.synthesize_unrated_limits,
6686 correct_angle_difference_bounds: preparation.correct_angle_difference_bounds,
6687 reference_bus_count: preparation.reference_buses.len(),
6688 skipped_zero_impedance_count: preparation.branches.skipped_zero_impedance.len(),
6689 };
6690 Ok(true)
6691 })
6692 }
6693}
6694
6695#[unsafe(no_mangle)]
6700pub unsafe extern "C" fn pio_dc_opf_preparation_reference_buses(
6701 preparation: *const PioDcOpfPreparation,
6702) -> PioSizeView {
6703 unsafe { PioDcOpfPreparation::get(preparation) }.map_or(PioSizeView::EMPTY, |value| {
6704 PioSizeView::new(value.reference_buses.as_ref())
6705 })
6706}
6707
6708#[unsafe(no_mangle)]
6713pub unsafe extern "C" fn pio_dc_opf_preparation_skipped_zero_impedance(
6714 preparation: *const PioDcOpfPreparation,
6715) -> PioSizeView {
6716 unsafe { PioDcOpfPreparation::get(preparation) }.map_or(PioSizeView::EMPTY, |value| {
6717 PioSizeView::new(&value.branches.skipped_zero_impedance)
6718 })
6719}
6720
6721#[unsafe(no_mangle)]
6726pub unsafe extern "C" fn pio_dc_opf_preparation_bus_at(
6727 preparation: *const PioDcOpfPreparation,
6728 index: usize,
6729 output: *mut PioDcOpfBusView,
6730 error: *mut *mut PioError,
6731) -> bool {
6732 unsafe {
6733 entry(error, false, || {
6734 let value = PioDcOpfPreparation::get(preparation).ok_or_else(|| {
6735 boundary_error(
6736 &codes::BIND_CAPI_NULL_HANDLE,
6737 "PioDcOpfPreparation must not be NULL",
6738 )
6739 })?;
6740 if index >= value.n_buses {
6741 return Err(boundary_error(
6742 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6743 format!("DC OPF preparation bus index {index} is out of range"),
6744 ));
6745 }
6746 let source_row = value.bus_source_rows[index];
6747 *require_output(output, "output")? = PioDcOpfBusView {
6748 bus_id: value.bus_ids[index].0,
6749 analysis_row: value.bus_analysis_rows[index],
6750 source_row: source_row.unwrap_or(0),
6751 has_source_row: source_row.is_some(),
6752 active_power_demand: value.p_d[index],
6753 shunt_conductance: value.g_s[index],
6754 phase_shift_injection: value.p_shift[index],
6755 };
6756 Ok(true)
6757 })
6758 }
6759}
6760
6761#[unsafe(no_mangle)]
6766pub unsafe extern "C" fn pio_dc_opf_preparation_generator_at(
6767 preparation: *const PioDcOpfPreparation,
6768 index: usize,
6769 output: *mut PioDcOpfGeneratorView,
6770 error: *mut *mut PioError,
6771) -> bool {
6772 unsafe {
6773 entry(error, false, || {
6774 let value = PioDcOpfPreparation::get(preparation).ok_or_else(|| {
6775 boundary_error(
6776 &codes::BIND_CAPI_NULL_HANDLE,
6777 "PioDcOpfPreparation must not be NULL",
6778 )
6779 })?;
6780 if index >= value.n_generators() {
6781 return Err(boundary_error(
6782 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6783 format!("DC OPF preparation generator index {index} is out of range"),
6784 ));
6785 }
6786 let source_row = value.generators.source_rows[index];
6787 let piecewise = value.generators.piecewise_linear[index].as_ref();
6788 *require_output(output, "output")? = PioDcOpfGeneratorView {
6789 component_id: PioStringView::new(&value.generators.identities[index]),
6790 bus_index: value.generators.bus_of_gen[index],
6791 analysis_row: value.generators.analysis_rows[index],
6792 source_row: source_row.unwrap_or(0),
6793 has_source_row: source_row.is_some(),
6794 quadratic_cost: value.generators.q[index],
6795 linear_cost: value.generators.c[index],
6796 constant_cost: value.generators.c0[index],
6797 has_piecewise_linear_cost: piecewise.is_some(),
6798 piecewise_linear_power: piecewise
6799 .map_or(PioF64View::EMPTY, |cost| PioF64View::new(&cost.power)),
6800 piecewise_linear_value: piecewise
6801 .map_or(PioF64View::EMPTY, |cost| PioF64View::new(&cost.value)),
6802 active_power_max: value.generators.pmax[index],
6803 active_power_min: value.generators.pmin[index],
6804 capability_active: value.generators.capability_active[index],
6805 };
6806 Ok(true)
6807 })
6808 }
6809}
6810
6811#[unsafe(no_mangle)]
6816pub unsafe extern "C" fn pio_dc_opf_preparation_branch_at(
6817 preparation: *const PioDcOpfPreparation,
6818 index: usize,
6819 output: *mut PioDcOpfBranchView,
6820 error: *mut *mut PioError,
6821) -> bool {
6822 unsafe {
6823 entry(error, false, || {
6824 let value = PioDcOpfPreparation::get(preparation).ok_or_else(|| {
6825 boundary_error(
6826 &codes::BIND_CAPI_NULL_HANDLE,
6827 "PioDcOpfPreparation must not be NULL",
6828 )
6829 })?;
6830 if index >= value.n_branches() {
6831 return Err(boundary_error(
6832 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6833 format!("DC OPF preparation branch index {index} is out of range"),
6834 ));
6835 }
6836 let (source_kind, source_row, winding) =
6837 opf_analysis_branch_source(value.branches.analysis_sources[index], "DC")?;
6838 *require_output(output, "output")? = PioDcOpfBranchView {
6839 component_id: PioStringView::new(&value.branches.identities[index]),
6840 from_bus_index: value.branches.from_bus[index],
6841 to_bus_index: value.branches.to_bus[index],
6842 susceptance_magnitude: value.branches.susceptance_magnitude[index],
6843 phase_shift_radians: value.branches.shift[index],
6844 active_power_max: value.branches.f_max[index],
6845 angle_difference_min_radians: value.branches.angle_min[index],
6846 angle_difference_max_radians: value.branches.angle_max[index],
6847 analysis_row: value.branches.analysis_rows[index],
6848 source_kind: PioStringView::new(source_kind),
6849 source_row,
6850 winding: winding.unwrap_or(0),
6851 has_winding: winding.is_some(),
6852 thermal_limit_active: value.branches.thermal_limit_active[index],
6853 angle_bound_active: value.branches.angle_bound_active[index],
6854 };
6855 Ok(true)
6856 })
6857 }
6858}
6859
6860#[unsafe(no_mangle)]
6865pub unsafe extern "C" fn pio_build_ac_opf_preparation(
6866 instance: *const PioCalculationInstance,
6867 units: *const c_char,
6868 units_len: usize,
6869 skip_zero_impedance: bool,
6870 synthesize_unrated_limits: bool,
6871 correct_angle_difference_bounds: bool,
6872 error: *mut *mut PioError,
6873) -> *mut PioAcOpfPreparation {
6874 unsafe {
6875 entry(error, std::ptr::null_mut(), || {
6876 let instance = require_calculation_instance(instance)?
6877 .ac_opf()
6878 .ok_or_else(|| {
6879 boundary_error(
6880 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6881 "AC OPF preparation requires powerio.AcOpfInstance",
6882 )
6883 })?;
6884 let units = opf_preparation_units(required_str(units, units_len, "units")?)?;
6885 let options = AcOpfAssemblyOptions::default()
6886 .with_units(units)
6887 .with_skip_zero_impedance(skip_zero_impedance)
6888 .with_synthesize_unrated_limits(synthesize_unrated_limits)
6889 .with_correct_angle_difference_bounds(correct_angle_difference_bounds);
6890 build_ac_opf_preparation(instance, &options)
6891 .map(PioAcOpfPreparation::new_raw)
6892 .map_err(|failure| error_from_matrix(&failure))
6893 })
6894 }
6895}
6896
6897#[unsafe(no_mangle)]
6902pub unsafe extern "C" fn pio_ac_opf_preparation_summary(
6903 preparation: *const PioAcOpfPreparation,
6904 output: *mut PioAcOpfPreparationView,
6905 error: *mut *mut PioError,
6906) -> bool {
6907 unsafe {
6908 entry(error, false, || {
6909 let preparation = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
6910 boundary_error(
6911 &codes::BIND_CAPI_NULL_HANDLE,
6912 "PioAcOpfPreparation must not be NULL",
6913 )
6914 })?;
6915 let units = opf_preparation_units_name(preparation.units).ok_or_else(|| {
6916 boundary_error(
6917 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6918 "the AC OPF preparation uses units unsupported by ABI 7",
6919 )
6920 })?;
6921 let objective = prepared_objective_name(preparation.objective).ok_or_else(|| {
6922 boundary_error(
6923 &codes::REQUEST_CAPI_TYPE_MISMATCH,
6924 "the AC OPF preparation uses an objective unsupported by ABI 7",
6925 )
6926 })?;
6927 *require_output(output, "output")? = PioAcOpfPreparationView {
6928 name: PioStringView::new(&preparation.name),
6929 bus_count: preparation.n_buses,
6930 generator_count: preparation.n_generators(),
6931 storage_count: preparation.n_storage(),
6932 branch_count: preparation.n_branches(),
6933 source_generator_count: preparation.n_source_generators,
6934 source_branch_count: preparation.n_source_branches,
6935 base_mva: preparation.base_mva,
6936 units: PioStringView::new(units),
6937 objective: PioStringView::new(objective),
6938 skip_zero_impedance: preparation.skip_zero_impedance,
6939 synthesize_unrated_limits: preparation.synthesize_unrated_limits,
6940 correct_angle_difference_bounds: preparation.correct_angle_difference_bounds,
6941 reference_bus_count: preparation.reference_buses.len(),
6942 skipped_zero_impedance_count: preparation.branches.skipped_zero_impedance.len(),
6943 };
6944 Ok(true)
6945 })
6946 }
6947}
6948
6949#[unsafe(no_mangle)]
6954pub unsafe extern "C" fn pio_ac_opf_preparation_reference_buses(
6955 preparation: *const PioAcOpfPreparation,
6956) -> PioSizeView {
6957 unsafe { PioAcOpfPreparation::get(preparation) }.map_or(PioSizeView::EMPTY, |value| {
6958 PioSizeView::new(value.reference_buses.as_ref())
6959 })
6960}
6961
6962#[unsafe(no_mangle)]
6967pub unsafe extern "C" fn pio_ac_opf_preparation_skipped_zero_impedance(
6968 preparation: *const PioAcOpfPreparation,
6969) -> PioSizeView {
6970 unsafe { PioAcOpfPreparation::get(preparation) }.map_or(PioSizeView::EMPTY, |value| {
6971 PioSizeView::new(&value.branches.skipped_zero_impedance)
6972 })
6973}
6974
6975#[unsafe(no_mangle)]
6980pub unsafe extern "C" fn pio_ac_opf_preparation_bus_at(
6981 preparation: *const PioAcOpfPreparation,
6982 index: usize,
6983 output: *mut PioAcOpfBusView,
6984 error: *mut *mut PioError,
6985) -> bool {
6986 unsafe {
6987 entry(error, false, || {
6988 let value = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
6989 boundary_error(
6990 &codes::BIND_CAPI_NULL_HANDLE,
6991 "PioAcOpfPreparation must not be NULL",
6992 )
6993 })?;
6994 if index >= value.n_buses {
6995 return Err(boundary_error(
6996 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
6997 format!("AC OPF preparation bus index {index} is out of range"),
6998 ));
6999 }
7000 let source_row = value.bus_source_rows[index];
7001 *require_output(output, "output")? = PioAcOpfBusView {
7002 bus_id: value.bus_ids[index].0,
7003 analysis_row: value.bus_analysis_rows[index],
7004 source_row: source_row.unwrap_or(0),
7005 has_source_row: source_row.is_some(),
7006 active_power_demand: value.buses.p_d[index],
7007 reactive_power_demand: value.buses.q_d[index],
7008 shunt_conductance: value.buses.g_s[index],
7009 shunt_susceptance: value.buses.b_s[index],
7010 voltage_magnitude_min_pu: value.buses.vm_min[index],
7011 voltage_magnitude_max_pu: value.buses.vm_max[index],
7012 initial_voltage_magnitude_pu: value.buses.initial_vm[index],
7013 initial_voltage_angle_radians: value.buses.initial_va[index],
7014 voltage_bound_active: value.buses.voltage_bound_active[index],
7015 };
7016 Ok(true)
7017 })
7018 }
7019}
7020
7021#[unsafe(no_mangle)]
7026pub unsafe extern "C" fn pio_ac_opf_preparation_generator_at(
7027 preparation: *const PioAcOpfPreparation,
7028 index: usize,
7029 output: *mut PioAcOpfGeneratorView,
7030 error: *mut *mut PioError,
7031) -> bool {
7032 unsafe {
7033 entry(error, false, || {
7034 let value = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
7035 boundary_error(
7036 &codes::BIND_CAPI_NULL_HANDLE,
7037 "PioAcOpfPreparation must not be NULL",
7038 )
7039 })?;
7040 if index >= value.n_generators() {
7041 return Err(boundary_error(
7042 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7043 format!("AC OPF preparation generator index {index} is out of range"),
7044 ));
7045 }
7046 let source_row = value.generators.source_rows[index];
7047 let piecewise = value.generators.piecewise_linear[index].as_ref();
7048 *require_output(output, "output")? = PioAcOpfGeneratorView {
7049 component_id: PioStringView::new(&value.generators.identities[index]),
7050 bus_index: value.generators.bus_of_gen[index],
7051 analysis_row: value.generators.analysis_rows[index],
7052 source_row: source_row.unwrap_or(0),
7053 has_source_row: source_row.is_some(),
7054 quadratic_cost: value.generators.q[index],
7055 linear_cost: value.generators.c[index],
7056 constant_cost: value.generators.c0[index],
7057 has_piecewise_linear_cost: piecewise.is_some(),
7058 piecewise_linear_power: piecewise
7059 .map_or(PioF64View::EMPTY, |cost| PioF64View::new(&cost.power)),
7060 piecewise_linear_value: piecewise
7061 .map_or(PioF64View::EMPTY, |cost| PioF64View::new(&cost.value)),
7062 active_power_max: value.generators.pmax[index],
7063 active_power_min: value.generators.pmin[index],
7064 reactive_power_max: value.generators.qmax[index],
7065 reactive_power_min: value.generators.qmin[index],
7066 initial_active_power: value.generators.pg[index],
7067 initial_reactive_power: value.generators.qg[index],
7068 voltage_magnitude_setpoint_pu: value.generators.vg[index],
7069 capability_active: value.generators.capability_active[index],
7070 };
7071 Ok(true)
7072 })
7073 }
7074}
7075
7076#[unsafe(no_mangle)]
7081pub unsafe extern "C" fn pio_ac_opf_preparation_storage_at(
7082 preparation: *const PioAcOpfPreparation,
7083 index: usize,
7084 output: *mut PioAcOpfStorageView,
7085 error: *mut *mut PioError,
7086) -> bool {
7087 unsafe {
7088 entry(error, false, || {
7089 let value = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
7090 boundary_error(
7091 &codes::BIND_CAPI_NULL_HANDLE,
7092 "PioAcOpfPreparation must not be NULL",
7093 )
7094 })?;
7095 if index >= value.n_storage() {
7096 return Err(boundary_error(
7097 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7098 format!("AC OPF preparation storage index {index} is out of range"),
7099 ));
7100 }
7101 let storage = &value.storage;
7102 *require_output(output, "output")? = PioAcOpfStorageView {
7103 component_id: PioStringView::new(&storage.identities[index]),
7104 bus_index: storage.bus_of_storage[index],
7105 source_row: storage.source_rows[index],
7106 initial_active_power: storage.p[index],
7107 initial_reactive_power: storage.q[index],
7108 energy: storage.energy[index],
7109 energy_rating: storage.energy_rating[index],
7110 charge_rating: storage.charge_rating[index],
7111 discharge_rating: storage.discharge_rating[index],
7112 charge_efficiency: storage.charge_efficiency[index],
7113 discharge_efficiency: storage.discharge_efficiency[index],
7114 apparent_power_max: storage.s_max[index],
7115 reactive_power_min: storage.qmin[index],
7116 reactive_power_max: storage.qmax[index],
7117 resistance_pu: storage.r[index],
7118 reactance_pu: storage.x[index],
7119 active_power_loss: storage.p_loss[index],
7120 reactive_power_loss: storage.q_loss[index],
7121 in_service: storage.in_service[index],
7122 };
7123 Ok(true)
7124 })
7125 }
7126}
7127
7128#[unsafe(no_mangle)]
7133pub unsafe extern "C" fn pio_ac_opf_preparation_branch_at(
7134 preparation: *const PioAcOpfPreparation,
7135 index: usize,
7136 output: *mut PioAcOpfBranchView,
7137 error: *mut *mut PioError,
7138) -> bool {
7139 unsafe {
7140 entry(error, false, || {
7141 let value = PioAcOpfPreparation::get(preparation).ok_or_else(|| {
7142 boundary_error(
7143 &codes::BIND_CAPI_NULL_HANDLE,
7144 "PioAcOpfPreparation must not be NULL",
7145 )
7146 })?;
7147 if index >= value.n_branches() {
7148 return Err(boundary_error(
7149 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7150 format!("AC OPF preparation branch index {index} is out of range"),
7151 ));
7152 }
7153 let (source_kind, source_row, winding) =
7154 opf_analysis_branch_source(value.branches.analysis_sources[index], "AC")?;
7155 *require_output(output, "output")? = PioAcOpfBranchView {
7156 component_id: PioStringView::new(&value.branches.identities[index]),
7157 from_bus_index: value.branches.from_bus[index],
7158 to_bus_index: value.branches.to_bus[index],
7159 series_conductance: value.branches.g[index],
7160 series_susceptance: value.branches.b[index],
7161 from_conductance: value.branches.g_fr[index],
7162 from_susceptance: value.branches.b_fr[index],
7163 to_conductance: value.branches.g_to[index],
7164 to_susceptance: value.branches.b_to[index],
7165 tap_ratio: value.branches.tap[index],
7166 phase_shift_radians: value.branches.shift[index],
7167 apparent_power_max: value.branches.s_max[index],
7168 angle_difference_min_radians: value.branches.angle_min[index],
7169 angle_difference_max_radians: value.branches.angle_max[index],
7170 analysis_row: value.branches.analysis_rows[index],
7171 source_kind: PioStringView::new(source_kind),
7172 source_row,
7173 winding: winding.unwrap_or(0),
7174 has_winding: winding.is_some(),
7175 thermal_limit_active: value.branches.thermal_limit_active[index],
7176 angle_bound_active: value.branches.angle_bound_active[index],
7177 };
7178 Ok(true)
7179 })
7180 }
7181}
7182
7183#[unsafe(no_mangle)]
7187pub unsafe extern "C" fn pio_dc_pf_instance_bus_specification_count(
7188 instance: *const PioCalculationInstance,
7189) -> usize {
7190 unsafe { PioCalculationInstance::get(instance) }
7191 .and_then(CalculationInstanceInner::dc_pf)
7192 .map_or(0, |instance| instance.specifications().len())
7193}
7194
7195#[unsafe(no_mangle)]
7200pub unsafe extern "C" fn pio_dc_pf_instance_bus_specification_at(
7201 instance: *const PioCalculationInstance,
7202 index: usize,
7203 output: *mut PioDcBusSpecificationView,
7204 error: *mut *mut PioError,
7205) -> bool {
7206 unsafe {
7207 entry(error, false, || {
7208 let instance = require_calculation_instance(instance)?
7209 .dc_pf()
7210 .ok_or_else(|| {
7211 boundary_error(
7212 &codes::REQUEST_CAPI_TYPE_MISMATCH,
7213 "the calculation instance is not powerio.DcPfInstance",
7214 )
7215 })?;
7216 let specification = instance.specifications().get(index).ok_or_else(|| {
7217 boundary_error(
7218 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7219 format!("DC bus specification index {index} is out of range"),
7220 )
7221 })?;
7222 let bus_id = instance
7223 .network()
7224 .buses()
7225 .get(index)
7226 .ok_or_else(|| {
7227 boundary_error(
7228 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7229 format!("bus specification index {index} has no bus"),
7230 )
7231 })?
7232 .id
7233 .0;
7234 let (kind, net_active_power_mw, voltage_angle_degrees) = match *specification {
7235 powerio_prob::DcBusSpecification::NetActivePower { p_mw } => {
7236 ("net_active_power", p_mw, 0.0)
7237 }
7238 powerio_prob::DcBusSpecification::Reference { va_degrees } => {
7239 ("reference", 0.0, va_degrees)
7240 }
7241 powerio_prob::DcBusSpecification::Isolated => ("isolated", 0.0, 0.0),
7242 _ => ("unknown", 0.0, 0.0),
7243 };
7244 *require_output(output, "output")? = PioDcBusSpecificationView {
7245 bus_id,
7246 kind: PioStringView::new(kind),
7247 net_active_power_mw,
7248 voltage_angle_degrees,
7249 };
7250 Ok(true)
7251 })
7252 }
7253}
7254
7255#[unsafe(no_mangle)]
7259pub unsafe extern "C" fn pio_dc_pf_instance_branch_susceptance_formula(
7260 instance: *const PioCalculationInstance,
7261) -> PioStringView {
7262 unsafe { PioCalculationInstance::get(instance) }
7263 .and_then(CalculationInstanceInner::dc_pf)
7264 .map_or(PioStringView::EMPTY, |instance| {
7265 PioStringView::new(branch_susceptance_formula_name(
7266 instance.branch_susceptance_formula(),
7267 ))
7268 })
7269}
7270
7271#[unsafe(no_mangle)]
7275pub unsafe extern "C" fn pio_ac_pf_instance_bus_specification_count(
7276 instance: *const PioCalculationInstance,
7277) -> usize {
7278 unsafe { PioCalculationInstance::get(instance) }
7279 .and_then(CalculationInstanceInner::ac_pf)
7280 .map_or(0, |instance| instance.specifications().len())
7281}
7282
7283#[unsafe(no_mangle)]
7288pub unsafe extern "C" fn pio_ac_pf_instance_bus_specification_at(
7289 instance: *const PioCalculationInstance,
7290 index: usize,
7291 output: *mut PioAcBusSpecificationView,
7292 error: *mut *mut PioError,
7293) -> bool {
7294 unsafe {
7295 entry(error, false, || {
7296 let instance = require_calculation_instance(instance)?
7297 .ac_pf()
7298 .ok_or_else(|| {
7299 boundary_error(
7300 &codes::REQUEST_CAPI_TYPE_MISMATCH,
7301 "the calculation instance is not powerio.AcPfInstance",
7302 )
7303 })?;
7304 let specification = instance.specifications().get(index).ok_or_else(|| {
7305 boundary_error(
7306 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7307 format!("AC bus specification index {index} is out of range"),
7308 )
7309 })?;
7310 let bus_id = instance
7311 .network()
7312 .buses()
7313 .get(index)
7314 .ok_or_else(|| {
7315 boundary_error(
7316 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7317 format!("bus specification index {index} has no bus"),
7318 )
7319 })?
7320 .id
7321 .0;
7322 let (kind, p, q, vm, va) = match *specification {
7323 powerio_prob::AcBusSpecification::Pq { p, q } => ("pq", p, q, 0.0, 0.0),
7324 powerio_prob::AcBusSpecification::Pv { p, vm } => ("pv", p, 0.0, vm, 0.0),
7325 powerio_prob::AcBusSpecification::Reference { vm, va } => {
7326 ("reference", 0.0, 0.0, vm, va)
7327 }
7328 powerio_prob::AcBusSpecification::Isolated => ("isolated", 0.0, 0.0, 0.0, 0.0),
7329 _ => ("unknown", 0.0, 0.0, 0.0, 0.0),
7330 };
7331 *require_output(output, "output")? = PioAcBusSpecificationView {
7332 bus_id,
7333 kind: PioStringView::new(kind),
7334 net_active_power_mw: p,
7335 net_reactive_power_mvar: q,
7336 voltage_magnitude_pu: vm,
7337 voltage_angle_degrees: va,
7338 };
7339 Ok(true)
7340 })
7341 }
7342}
7343
7344#[unsafe(no_mangle)]
7348pub unsafe extern "C" fn pio_dc_opf_instance_branch_susceptance_formula(
7349 instance: *const PioCalculationInstance,
7350) -> PioStringView {
7351 unsafe { PioCalculationInstance::get(instance) }
7352 .and_then(CalculationInstanceInner::dc_opf)
7353 .map_or(PioStringView::EMPTY, |instance| {
7354 PioStringView::new(branch_susceptance_formula_name(
7355 instance.branch_susceptance_formula(),
7356 ))
7357 })
7358}
7359
7360#[repr(C)]
7362#[derive(Clone, Copy)]
7363pub struct PioLinDist3FlowNodeView {
7364 pub bus: PioStringView,
7365 pub terminal: PioStringView,
7366 pub reference_magnitude: f64,
7367 pub reference_angle: f64,
7368 pub is_root: bool,
7369}
7370
7371#[repr(C)]
7374#[derive(Clone, Copy)]
7375pub struct PioLinDist3FlowConductorView {
7376 pub line: PioStringView,
7377 pub source_line_row: usize,
7378 pub conductor_position: usize,
7379 pub parent_bus: PioStringView,
7380 pub parent_terminal: PioStringView,
7381 pub child_bus: PioStringView,
7382 pub child_terminal: PioStringView,
7383 pub reversed: bool,
7384}
7385
7386impl CalculationInstanceInner {
7387 fn lindist3flow(&self) -> Option<&powerio_prob::LinDist3FlowOpfInstance> {
7388 match self.instance()? {
7389 CalculationInstanceRef::LinDist3FlowOpf(instance) => Some(instance),
7390 _ => None,
7391 }
7392 }
7393}
7394
7395#[unsafe(no_mangle)]
7399pub unsafe extern "C" fn pio_lindist3flow_opf_instance_node_count(
7400 instance: *const PioCalculationInstance,
7401) -> usize {
7402 unsafe { PioCalculationInstance::get(instance) }
7403 .and_then(CalculationInstanceInner::lindist3flow)
7404 .map_or(0, |instance| instance.topology().nodes.len())
7405}
7406
7407#[unsafe(no_mangle)]
7411pub unsafe extern "C" fn pio_lindist3flow_opf_instance_conductor_count(
7412 instance: *const PioCalculationInstance,
7413) -> usize {
7414 unsafe { PioCalculationInstance::get(instance) }
7415 .and_then(CalculationInstanceInner::lindist3flow)
7416 .map_or(0, |instance| instance.topology().conductors.len())
7417}
7418
7419#[unsafe(no_mangle)]
7424pub unsafe extern "C" fn pio_lindist3flow_opf_instance_node_at(
7425 instance: *const PioCalculationInstance,
7426 index: usize,
7427 output: *mut PioLinDist3FlowNodeView,
7428 error: *mut *mut PioError,
7429) -> bool {
7430 unsafe {
7431 entry(error, false, || {
7432 let instance = require_calculation_instance(instance)?
7433 .lindist3flow()
7434 .ok_or_else(|| {
7435 boundary_error(
7436 &codes::REQUEST_CAPI_TYPE_MISMATCH,
7437 "expected a LinDist3Flow OPF instance",
7438 )
7439 })?;
7440 let voltage = instance.reference().voltages.get(index).ok_or_else(|| {
7441 boundary_error(
7442 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7443 format!("node index {index} is out of range"),
7444 )
7445 })?;
7446 *require_output(output, "output")? = PioLinDist3FlowNodeView {
7447 bus: PioStringView::new(&voltage.node.bus),
7448 terminal: PioStringView::new(&voltage.node.terminal),
7449 reference_magnitude: voltage.magnitude,
7450 reference_angle: voltage.angle,
7451 is_root: instance.topology().roots.contains(&voltage.node),
7452 };
7453 Ok(true)
7454 })
7455 }
7456}
7457
7458#[unsafe(no_mangle)]
7463pub unsafe extern "C" fn pio_lindist3flow_opf_instance_conductor_at(
7464 instance: *const PioCalculationInstance,
7465 index: usize,
7466 output: *mut PioLinDist3FlowConductorView,
7467 error: *mut *mut PioError,
7468) -> bool {
7469 unsafe {
7470 entry(error, false, || {
7471 let instance = require_calculation_instance(instance)?
7472 .lindist3flow()
7473 .ok_or_else(|| {
7474 boundary_error(
7475 &codes::REQUEST_CAPI_TYPE_MISMATCH,
7476 "expected a LinDist3Flow OPF instance",
7477 )
7478 })?;
7479 let conductor = instance.topology().conductors.get(index).ok_or_else(|| {
7480 boundary_error(
7481 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7482 format!("conductor index {index} is out of range"),
7483 )
7484 })?;
7485 *require_output(output, "output")? = PioLinDist3FlowConductorView {
7486 line: PioStringView::new(&conductor.line),
7487 source_line_row: conductor.source_line_row,
7488 conductor_position: conductor.conductor_position,
7489 parent_bus: PioStringView::new(&conductor.parent.bus),
7490 parent_terminal: PioStringView::new(&conductor.parent.terminal),
7491 child_bus: PioStringView::new(&conductor.child.bus),
7492 child_terminal: PioStringView::new(&conductor.child.terminal),
7493 reversed: conductor.reversed,
7494 };
7495 Ok(true)
7496 })
7497 }
7498}
7499
7500fn objective(instance: &CalculationInstanceInner) -> Option<&powerio_prob::Objective> {
7501 match instance.instance()? {
7502 CalculationInstanceRef::DcOpf(instance) => Some(instance.objective()),
7503 CalculationInstanceRef::AcOpf(instance) => Some(instance.objective()),
7504 CalculationInstanceRef::McAcOpf(instance) => Some(instance.objective()),
7505 CalculationInstanceRef::LinDist3FlowOpf(instance) => {
7506 Some(instance.base_instance().objective())
7507 }
7508 _ => None,
7509 }
7510}
7511
7512fn objective_term_name(term: &powerio_prob::ObjectiveTerm) -> &'static str {
7513 match term {
7514 powerio_prob::ObjectiveTerm::NetworkGeneratorCost => "network_generator_cost",
7515 powerio_prob::ObjectiveTerm::ActivePowerDispatchCost => "active_power_dispatch_cost",
7516 _ => "unknown",
7517 }
7518}
7519
7520#[unsafe(no_mangle)]
7524pub unsafe extern "C" fn pio_calculation_instance_objective_term_count(
7525 instance: *const PioCalculationInstance,
7526) -> usize {
7527 unsafe { PioCalculationInstance::get(instance) }
7528 .and_then(objective)
7529 .map_or(0, |objective| objective.terms().len())
7530}
7531
7532#[unsafe(no_mangle)]
7537pub unsafe extern "C" fn pio_calculation_instance_objective_term_at(
7538 instance: *const PioCalculationInstance,
7539 index: usize,
7540 output: *mut PioObjectiveTermView,
7541 error: *mut *mut PioError,
7542) -> bool {
7543 unsafe {
7544 entry(error, false, || {
7545 let value = require_calculation_instance(instance)?;
7546 let objective = objective(value).ok_or_else(|| {
7547 boundary_error(
7548 &codes::REQUEST_CAPI_TYPE_MISMATCH,
7549 "the calculation instance has no optimization objective",
7550 )
7551 })?;
7552 let term = objective.terms().get(index).ok_or_else(|| {
7553 boundary_error(
7554 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7555 format!("objective term index {index} is out of range"),
7556 )
7557 })?;
7558 *require_output(output, "output")? = PioObjectiveTermView {
7559 kind: PioStringView::new(objective_term_name(term)),
7560 };
7561 Ok(true)
7562 })
7563 }
7564}
7565
7566fn active_constraint(
7567 instance: &CalculationInstanceInner,
7568 index: usize,
7569) -> Option<(&'static str, &powerio_prob::ConstraintSelection)> {
7570 match instance.instance()? {
7571 CalculationInstanceRef::DcOpf(instance) => match index {
7572 0 => Some((
7573 "generator_capability",
7574 &instance.constraints().generator_capability,
7575 )),
7576 1 => Some(("voltage_bounds", &instance.constraints().voltage_bounds)),
7577 2 => Some(("thermal_limits", &instance.constraints().thermal_limits)),
7578 3 => Some(("angle_bounds", &instance.constraints().angle_bounds)),
7579 _ => None,
7580 },
7581 CalculationInstanceRef::AcOpf(instance) => match index {
7582 0 => Some((
7583 "generator_capability",
7584 &instance.constraints().generator_capability,
7585 )),
7586 1 => Some(("voltage_bounds", &instance.constraints().voltage_bounds)),
7587 2 => Some(("thermal_limits", &instance.constraints().thermal_limits)),
7588 3 => Some(("angle_bounds", &instance.constraints().angle_bounds)),
7589 _ => None,
7590 },
7591 CalculationInstanceRef::McAcOpf(instance) => match index {
7592 0 => Some((
7593 "terminal_voltage_bounds",
7594 &instance.constraints().terminal_voltage_bounds,
7595 )),
7596 1 => Some(("conductor_limits", &instance.constraints().conductor_limits)),
7597 2 => Some((
7598 "generator_capability",
7599 &instance.constraints().generator_capability,
7600 )),
7601 _ => None,
7602 },
7603 CalculationInstanceRef::LinDist3FlowOpf(instance) => match index {
7604 0 => Some((
7605 "terminal_voltage_bounds",
7606 &instance
7607 .base_instance()
7608 .constraints()
7609 .terminal_voltage_bounds,
7610 )),
7611 1 => Some((
7612 "conductor_limits",
7613 &instance.base_instance().constraints().conductor_limits,
7614 )),
7615 2 => Some((
7616 "generator_capability",
7617 &instance.base_instance().constraints().generator_capability,
7618 )),
7619 _ => None,
7620 },
7621 _ => None,
7622 }
7623}
7624
7625fn constraint_selection_parts(
7626 selection: &powerio_prob::ConstraintSelection,
7627) -> (&'static str, &[String]) {
7628 match selection {
7629 powerio_prob::ConstraintSelection::All => ("all", &[]),
7630 powerio_prob::ConstraintSelection::None => ("none", &[]),
7631 powerio_prob::ConstraintSelection::Only(identities) => ("only", identities),
7632 _ => ("unknown", &[]),
7633 }
7634}
7635
7636#[unsafe(no_mangle)]
7640pub unsafe extern "C" fn pio_calculation_instance_active_constraint_count(
7641 instance: *const PioCalculationInstance,
7642) -> usize {
7643 match unsafe { PioCalculationInstance::get(instance) }
7644 .and_then(CalculationInstanceInner::instance)
7645 {
7646 Some(CalculationInstanceRef::DcOpf(_) | CalculationInstanceRef::AcOpf(_)) => 4,
7647 Some(CalculationInstanceRef::McAcOpf(_)) => 3,
7648 Some(CalculationInstanceRef::LinDist3FlowOpf(_)) => 3,
7649 _ => 0,
7650 }
7651}
7652
7653#[unsafe(no_mangle)]
7658pub unsafe extern "C" fn pio_calculation_instance_active_constraint_at(
7659 instance: *const PioCalculationInstance,
7660 index: usize,
7661 output: *mut PioActiveConstraintView,
7662 error: *mut *mut PioError,
7663) -> bool {
7664 unsafe {
7665 entry(error, false, || {
7666 let instance = require_calculation_instance(instance)?;
7667 let (family, selection) = active_constraint(instance, index).ok_or_else(|| {
7668 boundary_error(
7669 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7670 format!("active constraint index {index} is out of range"),
7671 )
7672 })?;
7673 let (selection, identities) = constraint_selection_parts(selection);
7674 *require_output(output, "output")? = PioActiveConstraintView {
7675 family: PioStringView::new(family),
7676 selection: PioStringView::new(selection),
7677 identity_count: identities.len(),
7678 };
7679 Ok(true)
7680 })
7681 }
7682}
7683
7684#[unsafe(no_mangle)]
7689pub unsafe extern "C" fn pio_calculation_instance_active_constraint_identity_at(
7690 instance: *const PioCalculationInstance,
7691 constraint_index: usize,
7692 identity_index: usize,
7693 error: *mut *mut PioError,
7694) -> PioStringView {
7695 unsafe {
7696 entry(error, PioStringView::EMPTY, || {
7697 let instance = require_calculation_instance(instance)?;
7698 let (_, selection) =
7699 active_constraint(instance, constraint_index).ok_or_else(|| {
7700 boundary_error(
7701 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7702 format!("active constraint index {constraint_index} is out of range"),
7703 )
7704 })?;
7705 let (_, identities) = constraint_selection_parts(selection);
7706 let identity = identities.get(identity_index).ok_or_else(|| {
7707 boundary_error(
7708 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7709 format!("constraint identity index {identity_index} is out of range"),
7710 )
7711 })?;
7712 Ok(PioStringView::new(identity))
7713 })
7714 }
7715}
7716
7717#[unsafe(no_mangle)]
7721pub unsafe extern "C" fn pio_calculation_instance_has_initial_point(
7722 instance: *const PioCalculationInstance,
7723) -> bool {
7724 unsafe { PioCalculationInstance::get(instance) }
7725 .is_some_and(CalculationInstanceInner::has_initial_point)
7726}
7727
7728#[unsafe(no_mangle)]
7734pub unsafe extern "C" fn pio_calculation_instance_initial_point(
7735 instance: *const PioCalculationInstance,
7736 error: *mut *mut PioError,
7737) -> *mut PioOperatingPoint {
7738 unsafe {
7739 entry(error, std::ptr::null_mut(), || {
7740 let instance = PioCalculationInstance::get(instance).ok_or_else(|| {
7741 boundary_error(
7742 &codes::BIND_CAPI_NULL_HANDLE,
7743 "PioCalculationInstance must not be NULL",
7744 )
7745 })?;
7746 let has_initial_point = instance.has_initial_point();
7747 let projection = if has_initial_point {
7748 Some(match (instance.projection, instance.instance()) {
7749 (
7750 CalculationInstanceProjection::Direct,
7751 Some(CalculationInstanceRef::DcPf(_)),
7752 ) => OperatingPointProjection::DcPfInitial,
7753 (
7754 CalculationInstanceProjection::Direct,
7755 Some(CalculationInstanceRef::AcPf(_)),
7756 ) => OperatingPointProjection::AcPfInitial,
7757 (
7758 CalculationInstanceProjection::Direct,
7759 Some(CalculationInstanceRef::DcOpf(_)),
7760 ) => OperatingPointProjection::DcOpfInitial,
7761 (
7762 CalculationInstanceProjection::Direct,
7763 Some(CalculationInstanceRef::AcOpf(_)),
7764 ) => OperatingPointProjection::AcOpfInitial,
7765 (
7766 CalculationInstanceProjection::Direct,
7767 Some(CalculationInstanceRef::McAcPf(_)),
7768 ) => OperatingPointProjection::McAcPfInitial,
7769 (
7770 CalculationInstanceProjection::Direct,
7771 Some(CalculationInstanceRef::McAcOpf(_)),
7772 ) => OperatingPointProjection::McAcOpfInitial,
7773 (
7774 CalculationInstanceProjection::Direct,
7775 Some(CalculationInstanceRef::LinDist3FlowOpf(_)),
7776 ) => OperatingPointProjection::LinDist3FlowOpfInitial,
7777 (CalculationInstanceProjection::DcPfSolution, _) => {
7778 OperatingPointProjection::DcPfSolutionInitial
7779 }
7780 (CalculationInstanceProjection::AcPfSolution, _) => {
7781 OperatingPointProjection::AcPfSolutionInitial
7782 }
7783 (CalculationInstanceProjection::DcOpfSolution, _) => {
7784 OperatingPointProjection::DcOpfSolutionInitial
7785 }
7786 (CalculationInstanceProjection::AcOpfSolution, _) => {
7787 OperatingPointProjection::AcOpfSolutionInitial
7788 }
7789 (CalculationInstanceProjection::SocwrOpfSolution, _) => {
7790 OperatingPointProjection::SocwrOpfSolutionInitial
7791 }
7792 (CalculationInstanceProjection::McAcPfSolution, _) => {
7793 OperatingPointProjection::McAcPfSolutionInitial
7794 }
7795 (CalculationInstanceProjection::McAcOpfSolution, _) => {
7796 OperatingPointProjection::McAcOpfSolutionInitial
7797 }
7798 (CalculationInstanceProjection::LinDist3FlowOpfSolution, _) => {
7799 OperatingPointProjection::LinDist3FlowOpfSolutionInitial
7800 }
7801 _ => {
7802 return Err(boundary_error(
7803 &codes::REQUEST_CAPI_TYPE_MISMATCH,
7804 "the handle does not refer to a calculation instance with an operating point",
7805 ));
7806 }
7807 })
7808 } else {
7809 None
7810 };
7811 Ok(projection.map_or(std::ptr::null_mut(), |projection| {
7812 PioOperatingPoint::new_raw(OperatingPointInner {
7813 value: ValueInner {
7814 owner: Arc::clone(&instance.value.owner),
7815 steps: instance.value.steps.clone(),
7816 },
7817 projection,
7818 })
7819 }))
7820 })
7821 }
7822}
7823
7824fn dist_load_voltage_model_name(model: &powerio_dist::DistLoadVoltageModel) -> &'static str {
7825 match model {
7826 powerio_dist::DistLoadVoltageModel::ConstantPower { .. } => "constant_power",
7827 powerio_dist::DistLoadVoltageModel::ConstantCurrent { .. } => "constant_current",
7828 powerio_dist::DistLoadVoltageModel::ConstantImpedance { .. } => "constant_impedance",
7829 powerio_dist::DistLoadVoltageModel::Zip { .. } => "zip",
7830 powerio_dist::DistLoadVoltageModel::Exponential { .. } => "exponential",
7831 _ => "unknown",
7832 }
7833}
7834
7835#[unsafe(no_mangle)]
7839pub unsafe extern "C" fn pio_mc_ac_pf_instance_load_count(
7840 instance: *const PioCalculationInstance,
7841) -> usize {
7842 unsafe { PioCalculationInstance::get(instance) }
7843 .and_then(CalculationInstanceInner::mc_ac_pf)
7844 .map_or(0, |instance| instance.loads().len())
7845}
7846
7847#[unsafe(no_mangle)]
7851pub unsafe extern "C" fn pio_mc_ac_pf_instance_load_at(
7852 instance: *const PioCalculationInstance,
7853 index: usize,
7854 output: *mut PioPrescribedTerminalPowerView,
7855 error: *mut *mut PioError,
7856) -> bool {
7857 unsafe {
7858 entry(error, false, || {
7859 let instance = require_calculation_instance(instance)?
7860 .mc_ac_pf()
7861 .ok_or_else(|| {
7862 boundary_error(
7863 &codes::REQUEST_CAPI_TYPE_MISMATCH,
7864 "the calculation instance is not powerio.McAcPfInstance",
7865 )
7866 })?;
7867 let load = instance.loads().get(index).ok_or_else(|| {
7868 boundary_error(
7869 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7870 format!("prescribed load index {index} is out of range"),
7871 )
7872 })?;
7873 *require_output(output, "output")? = PioPrescribedTerminalPowerView {
7874 load: PioStringView::new(&load.load),
7875 terminal_count: load.terminals.len(),
7876 voltage_model: PioStringView::new(dist_load_voltage_model_name(
7877 &load.voltage_model,
7878 )),
7879 };
7880 Ok(true)
7881 })
7882 }
7883}
7884
7885fn model_value(values: &[f64], index: usize) -> (f64, bool) {
7886 values
7887 .get(index)
7888 .copied()
7889 .map_or((0.0, false), |value| (value, true))
7890}
7891
7892#[unsafe(no_mangle)]
7896pub unsafe extern "C" fn pio_mc_ac_pf_instance_load_terminal_at(
7897 instance: *const PioCalculationInstance,
7898 load_index: usize,
7899 terminal_index: usize,
7900 output: *mut PioTerminalPowerView,
7901 error: *mut *mut PioError,
7902) -> bool {
7903 unsafe {
7904 entry(error, false, || {
7905 let instance = require_calculation_instance(instance)?
7906 .mc_ac_pf()
7907 .ok_or_else(|| {
7908 boundary_error(
7909 &codes::REQUEST_CAPI_TYPE_MISMATCH,
7910 "the calculation instance is not powerio.McAcPfInstance",
7911 )
7912 })?;
7913 let load = instance.loads().get(load_index).ok_or_else(|| {
7914 boundary_error(
7915 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7916 format!("prescribed load index {load_index} is out of range"),
7917 )
7918 })?;
7919 let terminal = load.terminals.get(terminal_index).ok_or_else(|| {
7920 boundary_error(
7921 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
7922 format!("load terminal index {terminal_index} is out of range"),
7923 )
7924 })?;
7925 let p = *load.p_w.get(terminal_index).ok_or_else(|| {
7926 boundary_error(
7927 &codes::REQUEST_CAPI_TYPE_MISMATCH,
7928 "the prescribed load active power does not align with its terminals",
7929 )
7930 })?;
7931 let q = *load.q_var.get(terminal_index).ok_or_else(|| {
7932 boundary_error(
7933 &codes::REQUEST_CAPI_TYPE_MISMATCH,
7934 "the prescribed load reactive power does not align with its terminals",
7935 )
7936 })?;
7937 let mut model_values = (0.0, false, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
7938 match &load.voltage_model {
7939 powerio_dist::DistLoadVoltageModel::ConstantPower { v_nom } => {
7940 let (v, has_v) = model_value(v_nom, terminal_index);
7941 model_values.0 = v;
7942 model_values.1 = has_v;
7943 }
7944 powerio_dist::DistLoadVoltageModel::ConstantCurrent { v_nom } => {
7945 let (v, has_v) = model_value(v_nom, terminal_index);
7946 model_values = (v, has_v, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0);
7947 }
7948 powerio_dist::DistLoadVoltageModel::ConstantImpedance { v_nom } => {
7949 let (v, has_v) = model_value(v_nom, terminal_index);
7950 model_values = (v, has_v, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0);
7951 }
7952 powerio_dist::DistLoadVoltageModel::Zip {
7953 v_nom,
7954 alpha_z,
7955 alpha_i,
7956 alpha_p,
7957 beta_z,
7958 beta_i,
7959 beta_p,
7960 } => {
7961 let (v, has_v) = model_value(v_nom, terminal_index);
7962 model_values = (
7963 v,
7964 has_v,
7965 alpha_z.get(terminal_index).copied().unwrap_or(0.0),
7966 alpha_i.get(terminal_index).copied().unwrap_or(0.0),
7967 alpha_p.get(terminal_index).copied().unwrap_or(0.0),
7968 beta_z.get(terminal_index).copied().unwrap_or(0.0),
7969 beta_i.get(terminal_index).copied().unwrap_or(0.0),
7970 beta_p.get(terminal_index).copied().unwrap_or(0.0),
7971 0.0,
7972 0.0,
7973 );
7974 }
7975 powerio_dist::DistLoadVoltageModel::Exponential {
7976 v_nom,
7977 gamma_p,
7978 gamma_q,
7979 } => {
7980 let (v, has_v) = model_value(v_nom, terminal_index);
7981 model_values = (
7982 v,
7983 has_v,
7984 0.0,
7985 0.0,
7986 0.0,
7987 0.0,
7988 0.0,
7989 0.0,
7990 gamma_p.get(terminal_index).copied().unwrap_or(0.0),
7991 gamma_q.get(terminal_index).copied().unwrap_or(0.0),
7992 );
7993 }
7994 _ => {}
7995 }
7996 *require_output(output, "output")? = PioTerminalPowerView {
7997 terminal: PioStringView::new(terminal),
7998 active_power_w: p,
7999 reactive_power_var: q,
8000 nominal_voltage_v: model_values.0,
8001 has_nominal_voltage: model_values.1,
8002 active_impedance_fraction: model_values.2,
8003 active_current_fraction: model_values.3,
8004 active_power_fraction: model_values.4,
8005 reactive_impedance_fraction: model_values.5,
8006 reactive_current_fraction: model_values.6,
8007 reactive_power_fraction: model_values.7,
8008 active_power_exponent: model_values.8,
8009 reactive_power_exponent: model_values.9,
8010 };
8011 Ok(true)
8012 })
8013 }
8014}
8015
8016#[unsafe(no_mangle)]
8020pub unsafe extern "C" fn pio_mc_ac_pf_instance_source_count(
8021 instance: *const PioCalculationInstance,
8022) -> usize {
8023 unsafe { PioCalculationInstance::get(instance) }
8024 .and_then(CalculationInstanceInner::mc_ac_pf)
8025 .map_or(0, |instance| instance.sources().len())
8026}
8027
8028#[unsafe(no_mangle)]
8032pub unsafe extern "C" fn pio_mc_ac_pf_instance_source_at(
8033 instance: *const PioCalculationInstance,
8034 index: usize,
8035 output: *mut PioPrescribedSourceVoltageView,
8036 error: *mut *mut PioError,
8037) -> bool {
8038 unsafe {
8039 entry(error, false, || {
8040 let instance = require_calculation_instance(instance)?
8041 .mc_ac_pf()
8042 .ok_or_else(|| {
8043 boundary_error(
8044 &codes::REQUEST_CAPI_TYPE_MISMATCH,
8045 "the calculation instance is not powerio.McAcPfInstance",
8046 )
8047 })?;
8048 let source = instance.sources().get(index).ok_or_else(|| {
8049 boundary_error(
8050 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8051 format!("prescribed source index {index} is out of range"),
8052 )
8053 })?;
8054 *require_output(output, "output")? = PioPrescribedSourceVoltageView {
8055 source: PioStringView::new(&source.source),
8056 terminal_count: source.terminals.len(),
8057 };
8058 Ok(true)
8059 })
8060 }
8061}
8062
8063#[unsafe(no_mangle)]
8067pub unsafe extern "C" fn pio_mc_ac_pf_instance_source_terminal_at(
8068 instance: *const PioCalculationInstance,
8069 source_index: usize,
8070 terminal_index: usize,
8071 output: *mut PioTerminalVoltageView,
8072 error: *mut *mut PioError,
8073) -> bool {
8074 unsafe {
8075 entry(error, false, || {
8076 let instance = require_calculation_instance(instance)?
8077 .mc_ac_pf()
8078 .ok_or_else(|| {
8079 boundary_error(
8080 &codes::REQUEST_CAPI_TYPE_MISMATCH,
8081 "the calculation instance is not powerio.McAcPfInstance",
8082 )
8083 })?;
8084 let source = instance.sources().get(source_index).ok_or_else(|| {
8085 boundary_error(
8086 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8087 format!("prescribed source index {source_index} is out of range"),
8088 )
8089 })?;
8090 let terminal = source.terminals.get(terminal_index).ok_or_else(|| {
8091 boundary_error(
8092 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8093 format!("source terminal index {terminal_index} is out of range"),
8094 )
8095 })?;
8096 let magnitude = source.v_magnitude.get(terminal_index).ok_or_else(|| {
8097 boundary_error(
8098 &codes::REQUEST_CAPI_TYPE_MISMATCH,
8099 "the prescribed source magnitudes do not align with its terminals",
8100 )
8101 })?;
8102 let angle = source.v_angle.get(terminal_index).ok_or_else(|| {
8103 boundary_error(
8104 &codes::REQUEST_CAPI_TYPE_MISMATCH,
8105 "the prescribed source angles do not align with its terminals",
8106 )
8107 })?;
8108 *require_output(output, "output")? = PioTerminalVoltageView {
8109 terminal: PioStringView::new(terminal),
8110 magnitude_v: *magnitude,
8111 angle_radians: *angle,
8112 };
8113 Ok(true)
8114 })
8115 }
8116}
8117
8118#[unsafe(no_mangle)]
8122pub unsafe extern "C" fn pio_mc_ac_pf_instance_isolated_terminal_count(
8123 instance: *const PioCalculationInstance,
8124) -> usize {
8125 unsafe { PioCalculationInstance::get(instance) }
8126 .and_then(CalculationInstanceInner::mc_ac_pf)
8127 .map_or(0, |instance| instance.isolated_terminals().len())
8128}
8129
8130#[unsafe(no_mangle)]
8134pub unsafe extern "C" fn pio_mc_ac_pf_instance_isolated_terminal_at(
8135 instance: *const PioCalculationInstance,
8136 index: usize,
8137 output: *mut PioIsolatedTerminalView,
8138 error: *mut *mut PioError,
8139) -> bool {
8140 unsafe {
8141 entry(error, false, || {
8142 let instance = require_calculation_instance(instance)?
8143 .mc_ac_pf()
8144 .ok_or_else(|| {
8145 boundary_error(
8146 &codes::REQUEST_CAPI_TYPE_MISMATCH,
8147 "the calculation instance is not powerio.McAcPfInstance",
8148 )
8149 })?;
8150 let (bus, terminal) = instance.isolated_terminals().get(index).ok_or_else(|| {
8151 boundary_error(
8152 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8153 format!("isolated terminal index {index} is out of range"),
8154 )
8155 })?;
8156 *require_output(output, "output")? = PioIsolatedTerminalView {
8157 bus: PioStringView::new(bus),
8158 terminal: PioStringView::new(terminal),
8159 };
8160 Ok(true)
8161 })
8162 }
8163}
8164
8165#[unsafe(no_mangle)]
8169pub unsafe extern "C" fn pio_mc_ac_pf_instance_active_control_count(
8170 instance: *const PioCalculationInstance,
8171) -> usize {
8172 unsafe { PioCalculationInstance::get(instance) }
8173 .and_then(CalculationInstanceInner::mc_ac_pf)
8174 .map_or(0, |instance| instance.control_modes().len())
8175}
8176
8177#[unsafe(no_mangle)]
8181pub unsafe extern "C" fn pio_mc_ac_pf_instance_active_control_at(
8182 instance: *const PioCalculationInstance,
8183 index: usize,
8184 output: *mut PioActiveControlView,
8185 error: *mut *mut PioError,
8186) -> bool {
8187 unsafe {
8188 entry(error, false, || {
8189 let instance = require_calculation_instance(instance)?
8190 .mc_ac_pf()
8191 .ok_or_else(|| {
8192 boundary_error(
8193 &codes::REQUEST_CAPI_TYPE_MISMATCH,
8194 "the calculation instance is not powerio.McAcPfInstance",
8195 )
8196 })?;
8197 let control = instance.control_modes().get(index).ok_or_else(|| {
8198 boundary_error(
8199 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8200 format!("active control index {index} is out of range"),
8201 )
8202 })?;
8203 let (kind, component_id) = match control {
8204 powerio_prob::ActiveControlMode::RegulatorTap { transformer } => {
8205 ("regulator_tap", transformer.as_str())
8206 }
8207 powerio_prob::ActiveControlMode::CapacitorSteps { capacitor } => {
8208 ("capacitor_steps", capacitor.as_str())
8209 }
8210 _ => ("unknown", ""),
8211 };
8212 *require_output(output, "output")? = PioActiveControlView {
8213 kind: PioStringView::new(kind),
8214 component_id: PioStringView::new(component_id),
8215 };
8216 Ok(true)
8217 })
8218 }
8219}
8220
8221fn scuc_inputs(instance: &CalculationInstanceInner) -> Option<&powerio_prob::ScucInputs> {
8222 instance.ac_scuc().map(powerio_prob::AcScucInstance::inputs)
8223}
8224
8225fn scuc_inputs_or_error(
8226 instance: &CalculationInstanceInner,
8227) -> Result<&powerio_prob::ScucInputs, *mut PioError> {
8228 scuc_inputs(instance).ok_or_else(|| {
8229 boundary_error(
8230 &codes::REQUEST_CAPI_TYPE_MISMATCH,
8231 "the calculation instance is not powerio.AcScucInstance",
8232 )
8233 })
8234}
8235
8236fn component_id_view(id: &ComponentId) -> PioComponentIdView {
8237 PioComponentIdView {
8238 component_type: PioStringView::new(id.component_type()),
8239 local_id: PioStringView::new(id.local_id()),
8240 }
8241}
8242
8243fn empty_component_id_view() -> PioComponentIdView {
8244 PioComponentIdView {
8245 component_type: PioStringView::EMPTY,
8246 local_id: PioStringView::EMPTY,
8247 }
8248}
8249
8250fn optional_component_id_view(value: Option<&ComponentId>) -> (PioComponentIdView, bool) {
8251 value.map_or((empty_component_id_view(), false), |value| {
8252 (component_id_view(value), true)
8253 })
8254}
8255
8256fn terminal_reference_view(
8257 value: Option<&powerio_tx::TerminalReference>,
8258) -> (PioTerminalReferenceView, bool) {
8259 value.map_or(
8260 (
8261 PioTerminalReferenceView {
8262 equipment: empty_component_id_view(),
8263 terminal: 0,
8264 },
8265 false,
8266 ),
8267 |value| {
8268 (
8269 PioTerminalReferenceView {
8270 equipment: component_id_view(&value.equipment),
8271 terminal: value.terminal,
8272 },
8273 true,
8274 )
8275 },
8276 )
8277}
8278
8279fn transformer_control_mode_name(mode: powerio_tx::TransformerControlMode) -> &'static str {
8280 match mode {
8281 powerio_tx::TransformerControlMode::Fixed => "fixed",
8282 powerio_tx::TransformerControlMode::Voltage => "voltage",
8283 powerio_tx::TransformerControlMode::ReactiveFlow => "reactive_flow",
8284 powerio_tx::TransformerControlMode::ActiveFlow => "active_flow",
8285 powerio_tx::TransformerControlMode::DcLineQuantity => "dc_line_quantity",
8286 powerio_tx::TransformerControlMode::AsymmetricActiveFlow => "asymmetric_active_flow",
8287 _ => "unknown",
8288 }
8289}
8290
8291fn transformer_control_view(
8292 value: Option<&powerio_tx::TransformerControl>,
8293) -> (PioTransformerControlView, bool) {
8294 let empty_terminal = terminal_reference_view(None).0;
8295 value.map_or(
8296 (
8297 PioTransformerControlView {
8298 mode: PioStringView::EMPTY,
8299 enabled: false,
8300 controlled_bus_id: 0,
8301 has_controlled_bus: false,
8302 controlled_bus_on_winding_side: false,
8303 regulating_terminal: empty_terminal,
8304 has_regulating_terminal: false,
8305 tap_min: 0.0,
8306 tap_max: 0.0,
8307 band_min: 0.0,
8308 band_max: 0.0,
8309 tap_position_count: 0,
8310 mva_base: 0.0,
8311 winding_connection_angle: 0.0,
8312 has_winding_connection_angle: false,
8313 },
8314 false,
8315 ),
8316 |control| {
8317 let (regulating_terminal, has_regulating_terminal) =
8318 terminal_reference_view(control.regulating_terminal.as_ref());
8319 (
8320 PioTransformerControlView {
8321 mode: PioStringView::new(transformer_control_mode_name(control.mode)),
8322 enabled: control.enabled,
8323 controlled_bus_id: control.controlled_bus.map_or(0, |bus| bus.0),
8324 has_controlled_bus: control.controlled_bus.is_some(),
8325 controlled_bus_on_winding_side: control.controlled_bus_on_winding_side,
8326 regulating_terminal,
8327 has_regulating_terminal,
8328 tap_min: control.tap_min,
8329 tap_max: control.tap_max,
8330 band_min: control.band_min,
8331 band_max: control.band_max,
8332 tap_position_count: control.ntp,
8333 mva_base: control.mva_base,
8334 winding_connection_angle: control.winding_connection_angle.unwrap_or(0.0),
8335 has_winding_connection_angle: control.winding_connection_angle.is_some(),
8336 },
8337 true,
8338 )
8339 },
8340 )
8341}
8342
8343fn scuc_device_kind_name(kind: powerio_prob::ScucDeviceKind) -> &'static str {
8344 match kind {
8345 powerio_prob::ScucDeviceKind::Producer => "producer",
8346 powerio_prob::ScucDeviceKind::Consumer => "consumer",
8347 _ => "unknown",
8348 }
8349}
8350
8351fn scuc_reactive_capability_view(
8352 capability: &powerio_prob::ScucReactiveCapability,
8353) -> PioScucReactiveCapabilityView {
8354 match capability {
8355 powerio_prob::ScucReactiveCapability::None => PioScucReactiveCapabilityView {
8356 kind: PioStringView::new("none"),
8357 reactive_power_at_zero_active_power_pu: 0.0,
8358 reactive_power_at_zero_active_power_min_pu: 0.0,
8359 reactive_power_at_zero_active_power_max_pu: 0.0,
8360 slope: 0.0,
8361 slope_min: 0.0,
8362 slope_max: 0.0,
8363 },
8364 powerio_prob::ScucReactiveCapability::Linear {
8365 reactive_power_at_zero_active_power,
8366 slope,
8367 } => PioScucReactiveCapabilityView {
8368 kind: PioStringView::new("linear"),
8369 reactive_power_at_zero_active_power_pu: *reactive_power_at_zero_active_power,
8370 reactive_power_at_zero_active_power_min_pu: 0.0,
8371 reactive_power_at_zero_active_power_max_pu: 0.0,
8372 slope: *slope,
8373 slope_min: 0.0,
8374 slope_max: 0.0,
8375 },
8376 powerio_prob::ScucReactiveCapability::Bounded {
8377 reactive_power_at_zero_active_power_min,
8378 reactive_power_at_zero_active_power_max,
8379 slope_min,
8380 slope_max,
8381 } => PioScucReactiveCapabilityView {
8382 kind: PioStringView::new("bounded"),
8383 reactive_power_at_zero_active_power_pu: 0.0,
8384 reactive_power_at_zero_active_power_min_pu: *reactive_power_at_zero_active_power_min,
8385 reactive_power_at_zero_active_power_max_pu: *reactive_power_at_zero_active_power_max,
8386 slope: 0.0,
8387 slope_min: *slope_min,
8388 slope_max: *slope_max,
8389 },
8390 _ => PioScucReactiveCapabilityView {
8391 kind: PioStringView::new("unknown"),
8392 reactive_power_at_zero_active_power_pu: 0.0,
8393 reactive_power_at_zero_active_power_min_pu: 0.0,
8394 reactive_power_at_zero_active_power_max_pu: 0.0,
8395 slope: 0.0,
8396 slope_min: 0.0,
8397 slope_max: 0.0,
8398 },
8399 }
8400}
8401
8402fn scuc_reserve_costs_view(costs: &powerio_prob::ScucReserveCosts) -> PioScucReserveCostsView {
8403 PioScucReserveCostsView {
8404 regulation_up: costs.regulation_up,
8405 regulation_down: costs.regulation_down,
8406 synchronized: costs.synchronized,
8407 nonsynchronized: costs.nonsynchronized,
8408 ramping_up_online: costs.ramping_up_online,
8409 ramping_down_online: costs.ramping_down_online,
8410 ramping_up_offline: costs.ramping_up_offline,
8411 ramping_down_offline: costs.ramping_down_offline,
8412 reactive_up: costs.reactive_up,
8413 reactive_down: costs.reactive_down,
8414 }
8415}
8416
8417fn scuc_device_view(device: &powerio_prob::ScucDevice) -> PioScucDeviceView {
8418 PioScucDeviceView {
8419 id: component_id_view(&device.id),
8420 kind: PioStringView::new(scuc_device_kind_name(device.kind)),
8421 initial_on_status: device.initial_on_status,
8422 on_cost: device.on_cost,
8423 startup_cost: device.startup_cost,
8424 shutdown_cost: device.shutdown_cost,
8425 minimum_up_time_hours: device.minimum_up_time,
8426 minimum_down_time_hours: device.minimum_down_time,
8427 ramp_limits: PioScucRampLimitsView {
8428 up_pu_per_hour: device.ramp_limits.up,
8429 down_pu_per_hour: device.ramp_limits.down,
8430 startup_pu_per_hour: device.ramp_limits.startup,
8431 shutdown_pu_per_hour: device.ramp_limits.shutdown,
8432 },
8433 reserve_limits: PioScucReserveLimitsView {
8434 regulation_up_pu: device.reserve_limits.regulation_up,
8435 regulation_down_pu: device.reserve_limits.regulation_down,
8436 synchronized_pu: device.reserve_limits.synchronized,
8437 nonsynchronized_pu: device.reserve_limits.nonsynchronized,
8438 ramping_up_online_pu: device.reserve_limits.ramping_up_online,
8439 ramping_down_online_pu: device.reserve_limits.ramping_down_online,
8440 ramping_up_offline_pu: device.reserve_limits.ramping_up_offline,
8441 ramping_down_offline_pu: device.reserve_limits.ramping_down_offline,
8442 },
8443 initial_commitment: PioScucInitialCommitmentView {
8444 accumulated_up_time_hours: device.initial_commitment.accumulated_up_time,
8445 accumulated_down_time_hours: device.initial_commitment.accumulated_down_time,
8446 },
8447 reactive_capability: scuc_reactive_capability_view(&device.reactive_capability),
8448 period_count: device.periods.len(),
8449 startup_cost_adjustment_count: device.startup_cost_adjustments.len(),
8450 startup_limit_count: device.startup_limits.len(),
8451 energy_upper_bound_count: device.energy_upper_bounds.len(),
8452 energy_lower_bound_count: device.energy_lower_bounds.len(),
8453 }
8454}
8455
8456fn scuc_device_or_error(
8457 inputs: &powerio_prob::ScucInputs,
8458 device_index: usize,
8459) -> Result<&powerio_prob::ScucDevice, *mut PioError> {
8460 inputs.devices.get(device_index).ok_or_else(|| {
8461 boundary_error(
8462 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8463 format!("SCUC device index {device_index} is out of range"),
8464 )
8465 })
8466}
8467
8468#[unsafe(no_mangle)]
8473pub unsafe extern "C" fn pio_ac_scuc_instance_dimensions(
8474 instance: *const PioCalculationInstance,
8475 output: *mut PioScucDimensionsView,
8476 error: *mut *mut PioError,
8477) -> bool {
8478 unsafe {
8479 entry(error, false, || {
8480 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8481 *require_output(output, "output")? = PioScucDimensionsView {
8482 period_count: inputs.interval_durations.len(),
8483 device_count: inputs.devices.len(),
8484 producer_count: inputs.producers().count(),
8485 consumer_count: inputs.consumers().count(),
8486 shunt_count: inputs.shunts.len(),
8487 branch_switching_cost_count: inputs.branch_switching_costs.len(),
8488 transformer_control_count: inputs.transformer_controls.len(),
8489 active_reserve_zone_count: inputs.active_reserve_zones.len(),
8490 reactive_reserve_zone_count: inputs.reactive_reserve_zones.len(),
8491 contingency_count: inputs.contingencies.len(),
8492 };
8493 Ok(true)
8494 })
8495 }
8496}
8497
8498#[unsafe(no_mangle)]
8503pub unsafe extern "C" fn pio_ac_scuc_instance_interval_durations(
8504 instance: *const PioCalculationInstance,
8505) -> PioF64View {
8506 unsafe { PioCalculationInstance::get(instance) }
8507 .and_then(scuc_inputs)
8508 .map_or(PioF64View::EMPTY, |inputs| {
8509 PioF64View::new(&inputs.interval_durations)
8510 })
8511}
8512
8513#[unsafe(no_mangle)]
8518pub unsafe extern "C" fn pio_ac_scuc_instance_violation_costs(
8519 instance: *const PioCalculationInstance,
8520 output: *mut PioScucViolationCostView,
8521 error: *mut *mut PioError,
8522) -> bool {
8523 unsafe {
8524 entry(error, false, || {
8525 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8526 let costs = inputs.violation_costs;
8527 *require_output(output, "output")? = PioScucViolationCostView {
8528 active_power_balance: costs.active_power_balance,
8529 reactive_power_balance: costs.reactive_power_balance,
8530 branch_thermal_limit: costs.branch_thermal_limit,
8531 energy_requirement: costs.energy_requirement,
8532 };
8533 Ok(true)
8534 })
8535 }
8536}
8537
8538#[unsafe(no_mangle)]
8542pub unsafe extern "C" fn pio_ac_scuc_instance_device_count(
8543 instance: *const PioCalculationInstance,
8544) -> usize {
8545 unsafe { PioCalculationInstance::get(instance) }
8546 .and_then(scuc_inputs)
8547 .map_or(0, |inputs| inputs.devices.len())
8548}
8549
8550#[unsafe(no_mangle)]
8554pub unsafe extern "C" fn pio_ac_scuc_instance_device_at(
8555 instance: *const PioCalculationInstance,
8556 index: usize,
8557 output: *mut PioScucDeviceView,
8558 error: *mut *mut PioError,
8559) -> bool {
8560 unsafe {
8561 entry(error, false, || {
8562 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8563 let device = scuc_device_or_error(inputs, index)?;
8564 *require_output(output, "output")? = scuc_device_view(device);
8565 Ok(true)
8566 })
8567 }
8568}
8569
8570#[unsafe(no_mangle)]
8575pub unsafe extern "C" fn pio_ac_scuc_instance_device_get(
8576 instance: *const PioCalculationInstance,
8577 uid: *const c_char,
8578 uid_len: usize,
8579 output: *mut PioScucDeviceView,
8580 error: *mut *mut PioError,
8581) -> bool {
8582 unsafe {
8583 entry(error, false, || {
8584 let uid = required_str(uid, uid_len, "device_uid")?;
8585 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8586 let device = inputs.device(uid).ok_or_else(|| {
8587 boundary_error(
8588 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8589 format!("SCUC device UID '{uid}' does not exist"),
8590 )
8591 })?;
8592 *require_output(output, "output")? = scuc_device_view(device);
8593 Ok(true)
8594 })
8595 }
8596}
8597
8598#[unsafe(no_mangle)]
8602pub unsafe extern "C" fn pio_ac_scuc_instance_device_startup_cost_adjustment_count(
8603 instance: *const PioCalculationInstance,
8604 device_index: usize,
8605) -> usize {
8606 unsafe { PioCalculationInstance::get(instance) }
8607 .and_then(scuc_inputs)
8608 .and_then(|inputs| inputs.devices.get(device_index))
8609 .map_or(0, |device| device.startup_cost_adjustments.len())
8610}
8611
8612#[unsafe(no_mangle)]
8616pub unsafe extern "C" fn pio_ac_scuc_instance_device_startup_cost_adjustment_at(
8617 instance: *const PioCalculationInstance,
8618 device_index: usize,
8619 adjustment_index: usize,
8620 output: *mut PioScucStartupCostAdjustmentView,
8621 error: *mut *mut PioError,
8622) -> bool {
8623 unsafe {
8624 entry(error, false, || {
8625 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8626 let device = scuc_device_or_error(inputs, device_index)?;
8627 let adjustment = device
8628 .startup_cost_adjustments
8629 .get(adjustment_index)
8630 .ok_or_else(|| {
8631 boundary_error(
8632 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8633 format!(
8634 "SCUC device {device_index} startup cost adjustment index {adjustment_index} is out of range"
8635 ),
8636 )
8637 })?;
8638 *require_output(output, "output")? = PioScucStartupCostAdjustmentView {
8639 cost: adjustment.cost,
8640 maximum_down_time_hours: adjustment.maximum_down_time,
8641 };
8642 Ok(true)
8643 })
8644 }
8645}
8646
8647#[unsafe(no_mangle)]
8651pub unsafe extern "C" fn pio_ac_scuc_instance_device_startup_limit_count(
8652 instance: *const PioCalculationInstance,
8653 device_index: usize,
8654) -> usize {
8655 unsafe { PioCalculationInstance::get(instance) }
8656 .and_then(scuc_inputs)
8657 .and_then(|inputs| inputs.devices.get(device_index))
8658 .map_or(0, |device| device.startup_limits.len())
8659}
8660
8661#[unsafe(no_mangle)]
8665pub unsafe extern "C" fn pio_ac_scuc_instance_device_startup_limit_at(
8666 instance: *const PioCalculationInstance,
8667 device_index: usize,
8668 limit_index: usize,
8669 output: *mut PioScucStartupLimitView,
8670 error: *mut *mut PioError,
8671) -> bool {
8672 unsafe {
8673 entry(error, false, || {
8674 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8675 let device = scuc_device_or_error(inputs, device_index)?;
8676 let limit = device.startup_limits.get(limit_index).ok_or_else(|| {
8677 boundary_error(
8678 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8679 format!(
8680 "SCUC device {device_index} startup limit index {limit_index} is out of range"
8681 ),
8682 )
8683 })?;
8684 *require_output(output, "output")? = PioScucStartupLimitView {
8685 start_time_hours: limit.start_time,
8686 end_time_hours: limit.end_time,
8687 maximum_startups: limit.maximum_startups,
8688 };
8689 Ok(true)
8690 })
8691 }
8692}
8693
8694unsafe fn scuc_energy_requirement_count(
8695 instance: *const PioCalculationInstance,
8696 device_index: usize,
8697 upper: bool,
8698) -> usize {
8699 unsafe { PioCalculationInstance::get(instance) }
8700 .and_then(scuc_inputs)
8701 .and_then(|inputs| inputs.devices.get(device_index))
8702 .map_or(0, |device| {
8703 if upper {
8704 device.energy_upper_bounds.len()
8705 } else {
8706 device.energy_lower_bounds.len()
8707 }
8708 })
8709}
8710
8711unsafe fn scuc_energy_requirement_at(
8712 instance: *const PioCalculationInstance,
8713 device_index: usize,
8714 requirement_index: usize,
8715 upper: bool,
8716 output: *mut PioScucEnergyRequirementView,
8717 error: *mut *mut PioError,
8718) -> bool {
8719 unsafe {
8720 entry(error, false, || {
8721 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8722 let device = scuc_device_or_error(inputs, device_index)?;
8723 let requirements = if upper {
8724 &device.energy_upper_bounds
8725 } else {
8726 &device.energy_lower_bounds
8727 };
8728 let requirement = requirements.get(requirement_index).ok_or_else(|| {
8729 boundary_error(
8730 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8731 format!(
8732 "SCUC device {device_index} {} energy requirement index {requirement_index} is out of range",
8733 if upper { "upper" } else { "lower" }
8734 ),
8735 )
8736 })?;
8737 *require_output(output, "output")? = PioScucEnergyRequirementView {
8738 start_time_hours: requirement.start_time,
8739 end_time_hours: requirement.end_time,
8740 energy_pu: requirement.energy,
8741 };
8742 Ok(true)
8743 })
8744 }
8745}
8746
8747#[unsafe(no_mangle)]
8751pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_upper_bound_count(
8752 instance: *const PioCalculationInstance,
8753 device_index: usize,
8754) -> usize {
8755 unsafe { scuc_energy_requirement_count(instance, device_index, true) }
8756}
8757
8758#[unsafe(no_mangle)]
8762pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_upper_bound_at(
8763 instance: *const PioCalculationInstance,
8764 device_index: usize,
8765 requirement_index: usize,
8766 output: *mut PioScucEnergyRequirementView,
8767 error: *mut *mut PioError,
8768) -> bool {
8769 unsafe {
8770 scuc_energy_requirement_at(
8771 instance,
8772 device_index,
8773 requirement_index,
8774 true,
8775 output,
8776 error,
8777 )
8778 }
8779}
8780
8781#[unsafe(no_mangle)]
8785pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_lower_bound_count(
8786 instance: *const PioCalculationInstance,
8787 device_index: usize,
8788) -> usize {
8789 unsafe { scuc_energy_requirement_count(instance, device_index, false) }
8790}
8791
8792#[unsafe(no_mangle)]
8796pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_lower_bound_at(
8797 instance: *const PioCalculationInstance,
8798 device_index: usize,
8799 requirement_index: usize,
8800 output: *mut PioScucEnergyRequirementView,
8801 error: *mut *mut PioError,
8802) -> bool {
8803 unsafe {
8804 scuc_energy_requirement_at(
8805 instance,
8806 device_index,
8807 requirement_index,
8808 false,
8809 output,
8810 error,
8811 )
8812 }
8813}
8814
8815#[unsafe(no_mangle)]
8819pub unsafe extern "C" fn pio_ac_scuc_instance_device_period_at(
8820 instance: *const PioCalculationInstance,
8821 device_index: usize,
8822 period_index: usize,
8823 output: *mut PioScucDevicePeriodView,
8824 error: *mut *mut PioError,
8825) -> bool {
8826 unsafe {
8827 entry(error, false, || {
8828 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8829 let device = scuc_device_or_error(inputs, device_index)?;
8830 let period = device.periods.get(period_index).ok_or_else(|| {
8831 boundary_error(
8832 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8833 format!(
8834 "SCUC device {device_index} period index {period_index} is out of range"
8835 ),
8836 )
8837 })?;
8838 *require_output(output, "output")? = PioScucDevicePeriodView {
8839 on_status_min: period.on_status_min,
8840 on_status_max: period.on_status_max,
8841 active_power_min_pu: period.active_power_min,
8842 active_power_max_pu: period.active_power_max,
8843 reactive_power_min_pu: period.reactive_power_min,
8844 reactive_power_max_pu: period.reactive_power_max,
8845 energy_cost_block_count: period.energy_cost_blocks.len(),
8846 reserve_costs: scuc_reserve_costs_view(&period.reserve_costs),
8847 };
8848 Ok(true)
8849 })
8850 }
8851}
8852
8853#[unsafe(no_mangle)]
8857pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_cost_block_count(
8858 instance: *const PioCalculationInstance,
8859 device_index: usize,
8860 period_index: usize,
8861) -> usize {
8862 unsafe { PioCalculationInstance::get(instance) }
8863 .and_then(scuc_inputs)
8864 .and_then(|inputs| inputs.devices.get(device_index))
8865 .and_then(|device| device.periods.get(period_index))
8866 .map_or(0, |period| period.energy_cost_blocks.len())
8867}
8868
8869#[unsafe(no_mangle)]
8873pub unsafe extern "C" fn pio_ac_scuc_instance_device_energy_cost_block_at(
8874 instance: *const PioCalculationInstance,
8875 device_index: usize,
8876 period_index: usize,
8877 block_index: usize,
8878 output: *mut PioScucEnergyCostBlockView,
8879 error: *mut *mut PioError,
8880) -> bool {
8881 unsafe {
8882 entry(error, false, || {
8883 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8884 let device = scuc_device_or_error(inputs, device_index)?;
8885 let period = device.periods.get(period_index).ok_or_else(|| {
8886 boundary_error(
8887 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8888 format!(
8889 "SCUC device {device_index} period index {period_index} is out of range"
8890 ),
8891 )
8892 })?;
8893 let block = period.energy_cost_blocks.get(block_index).ok_or_else(|| {
8894 boundary_error(
8895 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8896 format!(
8897 "SCUC device {device_index} period {period_index} energy cost block index {block_index} is out of range"
8898 ),
8899 )
8900 })?;
8901 *require_output(output, "output")? = PioScucEnergyCostBlockView {
8902 marginal_cost: block.marginal_cost,
8903 block_size_pu: block.block_size,
8904 };
8905 Ok(true)
8906 })
8907 }
8908}
8909
8910#[unsafe(no_mangle)]
8914pub unsafe extern "C" fn pio_ac_scuc_instance_shunt_count(
8915 instance: *const PioCalculationInstance,
8916) -> usize {
8917 unsafe { PioCalculationInstance::get(instance) }
8918 .and_then(scuc_inputs)
8919 .map_or(0, |inputs| inputs.shunts.len())
8920}
8921
8922fn scuc_shunt_view(shunt: &powerio_prob::ScucShunt) -> PioScucShuntView {
8923 PioScucShuntView {
8924 id: component_id_view(&shunt.id),
8925 conductance_per_step_pu: shunt.conductance_per_step,
8926 susceptance_per_step_pu: shunt.susceptance_per_step,
8927 step_min: shunt.step_min,
8928 step_max: shunt.step_max,
8929 initial_step: shunt.initial_step,
8930 }
8931}
8932
8933#[unsafe(no_mangle)]
8937pub unsafe extern "C" fn pio_ac_scuc_instance_shunt_at(
8938 instance: *const PioCalculationInstance,
8939 index: usize,
8940 output: *mut PioScucShuntView,
8941 error: *mut *mut PioError,
8942) -> bool {
8943 unsafe {
8944 entry(error, false, || {
8945 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8946 let shunt = inputs.shunts.get(index).ok_or_else(|| {
8947 boundary_error(
8948 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8949 format!("SCUC shunt index {index} is out of range"),
8950 )
8951 })?;
8952 *require_output(output, "output")? = scuc_shunt_view(shunt);
8953 Ok(true)
8954 })
8955 }
8956}
8957
8958#[unsafe(no_mangle)]
8963pub unsafe extern "C" fn pio_ac_scuc_instance_shunt_get(
8964 instance: *const PioCalculationInstance,
8965 uid: *const c_char,
8966 uid_len: usize,
8967 output: *mut PioScucShuntView,
8968 error: *mut *mut PioError,
8969) -> bool {
8970 unsafe {
8971 entry(error, false, || {
8972 let uid = required_str(uid, uid_len, "shunt_uid")?;
8973 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
8974 let shunt = inputs.shunt(uid).ok_or_else(|| {
8975 boundary_error(
8976 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
8977 format!("SCUC shunt UID '{uid}' does not exist"),
8978 )
8979 })?;
8980 *require_output(output, "output")? = scuc_shunt_view(shunt);
8981 Ok(true)
8982 })
8983 }
8984}
8985
8986#[unsafe(no_mangle)]
8990pub unsafe extern "C" fn pio_ac_scuc_instance_branch_switching_cost_count(
8991 instance: *const PioCalculationInstance,
8992) -> usize {
8993 unsafe { PioCalculationInstance::get(instance) }
8994 .and_then(scuc_inputs)
8995 .map_or(0, |inputs| inputs.branch_switching_costs.len())
8996}
8997
8998#[unsafe(no_mangle)]
9002pub unsafe extern "C" fn pio_ac_scuc_instance_branch_switching_cost_at(
9003 instance: *const PioCalculationInstance,
9004 index: usize,
9005 output: *mut PioScucBranchSwitchingCostView,
9006 error: *mut *mut PioError,
9007) -> bool {
9008 unsafe {
9009 entry(error, false, || {
9010 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9011 let cost = inputs.branch_switching_costs.get(index).ok_or_else(|| {
9012 boundary_error(
9013 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9014 format!("SCUC branch switching cost index {index} is out of range"),
9015 )
9016 })?;
9017 *require_output(output, "output")? = PioScucBranchSwitchingCostView {
9018 id: component_id_view(&cost.id),
9019 connection_cost: cost.connection_cost,
9020 disconnection_cost: cost.disconnection_cost,
9021 };
9022 Ok(true)
9023 })
9024 }
9025}
9026
9027#[unsafe(no_mangle)]
9031pub unsafe extern "C" fn pio_ac_scuc_instance_transformer_control_count(
9032 instance: *const PioCalculationInstance,
9033) -> usize {
9034 unsafe { PioCalculationInstance::get(instance) }
9035 .and_then(scuc_inputs)
9036 .map_or(0, |inputs| inputs.transformer_controls.len())
9037}
9038
9039#[unsafe(no_mangle)]
9043pub unsafe extern "C" fn pio_ac_scuc_instance_transformer_control_at(
9044 instance: *const PioCalculationInstance,
9045 index: usize,
9046 output: *mut PioScucTransformerControlView,
9047 error: *mut *mut PioError,
9048) -> bool {
9049 unsafe {
9050 entry(error, false, || {
9051 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9052 let control = inputs.transformer_controls.get(index).ok_or_else(|| {
9053 boundary_error(
9054 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9055 format!("SCUC transformer control index {index} is out of range"),
9056 )
9057 })?;
9058 *require_output(output, "output")? = PioScucTransformerControlView {
9059 id: component_id_view(&control.id),
9060 tap_ratio_min: control.tap_ratio_min,
9061 tap_ratio_max: control.tap_ratio_max,
9062 phase_shift_min_radians: control.phase_shift_min,
9063 phase_shift_max_radians: control.phase_shift_max,
9064 };
9065 Ok(true)
9066 })
9067 }
9068}
9069
9070#[unsafe(no_mangle)]
9074pub unsafe extern "C" fn pio_ac_scuc_instance_active_reserve_zone_count(
9075 instance: *const PioCalculationInstance,
9076) -> usize {
9077 unsafe { PioCalculationInstance::get(instance) }
9078 .and_then(scuc_inputs)
9079 .map_or(0, |inputs| inputs.active_reserve_zones.len())
9080}
9081
9082#[unsafe(no_mangle)]
9086pub unsafe extern "C" fn pio_ac_scuc_instance_active_reserve_zone_at(
9087 instance: *const PioCalculationInstance,
9088 index: usize,
9089 output: *mut PioScucActiveReserveZoneView,
9090 error: *mut *mut PioError,
9091) -> bool {
9092 unsafe {
9093 entry(error, false, || {
9094 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9095 let zone = inputs.active_reserve_zones.get(index).ok_or_else(|| {
9096 boundary_error(
9097 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9098 format!("SCUC active reserve zone index {index} is out of range"),
9099 )
9100 })?;
9101 *require_output(output, "output")? = PioScucActiveReserveZoneView {
9102 id: component_id_view(&zone.id),
9103 regulation_up_requirement_fraction: zone.regulation_up_requirement_fraction,
9104 regulation_down_requirement_fraction: zone.regulation_down_requirement_fraction,
9105 synchronized_requirement_fraction: zone.synchronized_requirement_fraction,
9106 nonsynchronized_requirement_fraction: zone.nonsynchronized_requirement_fraction,
9107 regulation_up_violation_cost: zone.regulation_up_violation_cost,
9108 regulation_down_violation_cost: zone.regulation_down_violation_cost,
9109 synchronized_violation_cost: zone.synchronized_violation_cost,
9110 nonsynchronized_violation_cost: zone.nonsynchronized_violation_cost,
9111 ramping_up_violation_cost: zone.ramping_up_violation_cost,
9112 ramping_down_violation_cost: zone.ramping_down_violation_cost,
9113 period_count: zone.ramping_up_requirement.len(),
9114 bus_count: zone.buses.len(),
9115 };
9116 Ok(true)
9117 })
9118 }
9119}
9120
9121#[unsafe(no_mangle)]
9125pub unsafe extern "C" fn pio_ac_scuc_instance_active_reserve_zone_period_at(
9126 instance: *const PioCalculationInstance,
9127 zone_index: usize,
9128 period_index: usize,
9129 output: *mut PioScucActiveReservePeriodView,
9130 error: *mut *mut PioError,
9131) -> bool {
9132 unsafe {
9133 entry(error, false, || {
9134 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9135 let zone = inputs.active_reserve_zones.get(zone_index).ok_or_else(|| {
9136 boundary_error(
9137 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9138 format!("SCUC active reserve zone index {zone_index} is out of range"),
9139 )
9140 })?;
9141 let up = zone
9142 .ramping_up_requirement
9143 .get(period_index)
9144 .copied()
9145 .ok_or_else(|| {
9146 boundary_error(
9147 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9148 format!("SCUC reserve period index {period_index} is out of range"),
9149 )
9150 })?;
9151 let down = zone
9152 .ramping_down_requirement
9153 .get(period_index)
9154 .copied()
9155 .ok_or_else(|| {
9156 boundary_error(
9157 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9158 format!("SCUC reserve period index {period_index} is out of range"),
9159 )
9160 })?;
9161 *require_output(output, "output")? = PioScucActiveReservePeriodView {
9162 ramping_up_requirement_pu: up,
9163 ramping_down_requirement_pu: down,
9164 };
9165 Ok(true)
9166 })
9167 }
9168}
9169
9170#[unsafe(no_mangle)]
9174pub unsafe extern "C" fn pio_ac_scuc_instance_active_reserve_zone_bus_at(
9175 instance: *const PioCalculationInstance,
9176 zone_index: usize,
9177 bus_index: usize,
9178 output: *mut PioComponentIdView,
9179 error: *mut *mut PioError,
9180) -> bool {
9181 unsafe {
9182 entry(error, false, || {
9183 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9184 let zone = inputs.active_reserve_zones.get(zone_index).ok_or_else(|| {
9185 boundary_error(
9186 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9187 format!("SCUC active reserve zone index {zone_index} is out of range"),
9188 )
9189 })?;
9190 let bus = zone.buses.get(bus_index).ok_or_else(|| {
9191 boundary_error(
9192 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9193 format!("SCUC reserve zone bus index {bus_index} is out of range"),
9194 )
9195 })?;
9196 *require_output(output, "output")? = component_id_view(bus);
9197 Ok(true)
9198 })
9199 }
9200}
9201
9202#[unsafe(no_mangle)]
9206pub unsafe extern "C" fn pio_ac_scuc_instance_reactive_reserve_zone_count(
9207 instance: *const PioCalculationInstance,
9208) -> usize {
9209 unsafe { PioCalculationInstance::get(instance) }
9210 .and_then(scuc_inputs)
9211 .map_or(0, |inputs| inputs.reactive_reserve_zones.len())
9212}
9213
9214#[unsafe(no_mangle)]
9218pub unsafe extern "C" fn pio_ac_scuc_instance_reactive_reserve_zone_at(
9219 instance: *const PioCalculationInstance,
9220 index: usize,
9221 output: *mut PioScucReactiveReserveZoneView,
9222 error: *mut *mut PioError,
9223) -> bool {
9224 unsafe {
9225 entry(error, false, || {
9226 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9227 let zone = inputs.reactive_reserve_zones.get(index).ok_or_else(|| {
9228 boundary_error(
9229 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9230 format!("SCUC reactive reserve zone index {index} is out of range"),
9231 )
9232 })?;
9233 *require_output(output, "output")? = PioScucReactiveReserveZoneView {
9234 id: component_id_view(&zone.id),
9235 reactive_up_violation_cost: zone.reactive_up_violation_cost,
9236 reactive_down_violation_cost: zone.reactive_down_violation_cost,
9237 period_count: zone.reactive_up_requirement.len(),
9238 bus_count: zone.buses.len(),
9239 };
9240 Ok(true)
9241 })
9242 }
9243}
9244
9245#[unsafe(no_mangle)]
9249pub unsafe extern "C" fn pio_ac_scuc_instance_reactive_reserve_zone_period_at(
9250 instance: *const PioCalculationInstance,
9251 zone_index: usize,
9252 period_index: usize,
9253 output: *mut PioScucReactiveReservePeriodView,
9254 error: *mut *mut PioError,
9255) -> bool {
9256 unsafe {
9257 entry(error, false, || {
9258 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9259 let zone = inputs
9260 .reactive_reserve_zones
9261 .get(zone_index)
9262 .ok_or_else(|| {
9263 boundary_error(
9264 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9265 format!("SCUC reactive reserve zone index {zone_index} is out of range"),
9266 )
9267 })?;
9268 let up = zone
9269 .reactive_up_requirement
9270 .get(period_index)
9271 .copied()
9272 .ok_or_else(|| {
9273 boundary_error(
9274 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9275 format!("SCUC reserve period index {period_index} is out of range"),
9276 )
9277 })?;
9278 let down = zone
9279 .reactive_down_requirement
9280 .get(period_index)
9281 .copied()
9282 .ok_or_else(|| {
9283 boundary_error(
9284 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9285 format!("SCUC reserve period index {period_index} is out of range"),
9286 )
9287 })?;
9288 *require_output(output, "output")? = PioScucReactiveReservePeriodView {
9289 reactive_up_requirement_pu: up,
9290 reactive_down_requirement_pu: down,
9291 };
9292 Ok(true)
9293 })
9294 }
9295}
9296
9297#[unsafe(no_mangle)]
9301pub unsafe extern "C" fn pio_ac_scuc_instance_reactive_reserve_zone_bus_at(
9302 instance: *const PioCalculationInstance,
9303 zone_index: usize,
9304 bus_index: usize,
9305 output: *mut PioComponentIdView,
9306 error: *mut *mut PioError,
9307) -> bool {
9308 unsafe {
9309 entry(error, false, || {
9310 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9311 let zone = inputs
9312 .reactive_reserve_zones
9313 .get(zone_index)
9314 .ok_or_else(|| {
9315 boundary_error(
9316 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9317 format!("SCUC reactive reserve zone index {zone_index} is out of range"),
9318 )
9319 })?;
9320 let bus = zone.buses.get(bus_index).ok_or_else(|| {
9321 boundary_error(
9322 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9323 format!("SCUC reserve zone bus index {bus_index} is out of range"),
9324 )
9325 })?;
9326 *require_output(output, "output")? = component_id_view(bus);
9327 Ok(true)
9328 })
9329 }
9330}
9331
9332#[unsafe(no_mangle)]
9337pub unsafe extern "C" fn pio_ac_scuc_instance_contingency_count(
9338 instance: *const PioCalculationInstance,
9339) -> usize {
9340 unsafe { PioCalculationInstance::get(instance) }
9341 .and_then(scuc_inputs)
9342 .map_or(0, |inputs| inputs.contingencies.len())
9343}
9344
9345fn scuc_contingency_view(contingency: &powerio_prob::ScucContingency) -> PioScucContingencyView {
9346 PioScucContingencyView {
9347 id: component_id_view(&contingency.id),
9348 component_count: contingency.components.len(),
9349 }
9350}
9351
9352#[unsafe(no_mangle)]
9357pub unsafe extern "C" fn pio_ac_scuc_instance_contingency_at(
9358 instance: *const PioCalculationInstance,
9359 contingency_index: usize,
9360 output: *mut PioScucContingencyView,
9361 error: *mut *mut PioError,
9362) -> bool {
9363 unsafe {
9364 entry(error, false, || {
9365 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9366 let contingency = inputs.contingencies.get(contingency_index).ok_or_else(|| {
9367 boundary_error(
9368 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9369 format!("SCUC contingency index {contingency_index} is out of range"),
9370 )
9371 })?;
9372 *require_output(output, "output")? = scuc_contingency_view(contingency);
9373 Ok(true)
9374 })
9375 }
9376}
9377
9378#[unsafe(no_mangle)]
9383pub unsafe extern "C" fn pio_ac_scuc_instance_contingency_get(
9384 instance: *const PioCalculationInstance,
9385 uid: *const c_char,
9386 uid_len: usize,
9387 output: *mut PioScucContingencyView,
9388 error: *mut *mut PioError,
9389) -> bool {
9390 unsafe {
9391 entry(error, false, || {
9392 let uid = required_str(uid, uid_len, "contingency_uid")?;
9393 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9394 let contingency = inputs.contingency(uid).ok_or_else(|| {
9395 boundary_error(
9396 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9397 format!("SCUC contingency UID '{uid}' does not exist"),
9398 )
9399 })?;
9400 *require_output(output, "output")? = scuc_contingency_view(contingency);
9401 Ok(true)
9402 })
9403 }
9404}
9405
9406#[unsafe(no_mangle)]
9411pub unsafe extern "C" fn pio_ac_scuc_instance_contingency_component_at(
9412 instance: *const PioCalculationInstance,
9413 contingency_index: usize,
9414 component_index: usize,
9415 output: *mut PioScucContingencyComponentView,
9416 error: *mut *mut PioError,
9417) -> bool {
9418 unsafe {
9419 entry(error, false, || {
9420 let inputs = scuc_inputs_or_error(require_calculation_instance(instance)?)?;
9421 let contingency = inputs.contingencies.get(contingency_index).ok_or_else(|| {
9422 boundary_error(
9423 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9424 format!("SCUC contingency index {contingency_index} is out of range"),
9425 )
9426 })?;
9427 let component = contingency.components.get(component_index).ok_or_else(|| {
9428 boundary_error(
9429 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9430 format!(
9431 "SCUC contingency {contingency_index} component index {component_index} is out of range"
9432 ),
9433 )
9434 })?;
9435 *require_output(output, "output")? = PioScucContingencyComponentView {
9436 id: component_id_view(component),
9437 };
9438 Ok(true)
9439 })
9440 }
9441}
9442
9443#[unsafe(no_mangle)]
9447pub unsafe extern "C" fn pio_calculation_solution_balanced_network(
9448 solution: *const PioCalculationSolution,
9449 error: *mut *mut PioError,
9450) -> *mut PioBalancedNetwork {
9451 unsafe {
9452 entry(error, std::ptr::null_mut(), || {
9453 let solution = PioCalculationSolution::get(solution).ok_or_else(|| {
9454 boundary_error(
9455 &codes::BIND_CAPI_NULL_HANDLE,
9456 "PioCalculationSolution must not be NULL",
9457 )
9458 })?;
9459 let projection = match solution.value() {
9460 Some(PioValue::DcPfSolution(_)) => BalancedNetworkProjection::DcPfSolution,
9461 Some(PioValue::AcPfSolution(_)) => BalancedNetworkProjection::AcPfSolution,
9462 Some(PioValue::DcOpfSolution(_)) => BalancedNetworkProjection::DcOpfSolution,
9463 Some(PioValue::AcOpfSolution(_)) => BalancedNetworkProjection::AcOpfSolution,
9464 Some(PioValue::SocwrOpfSolution(_)) => BalancedNetworkProjection::SocwrOpfSolution,
9465 Some(PioValue::AcScucSolution(_)) => BalancedNetworkProjection::AcScucSolution,
9466 _ => {
9467 return Err(boundary_error(
9468 &codes::REQUEST_CAPI_TYPE_MISMATCH,
9469 "the calculation solution does not use powerio.BalancedNetwork",
9470 ));
9471 }
9472 };
9473 Ok(make_balanced_network_view(solution, projection))
9474 })
9475 }
9476}
9477
9478#[unsafe(no_mangle)]
9482pub unsafe extern "C" fn pio_calculation_solution_multiconductor_network(
9483 solution: *const PioCalculationSolution,
9484 error: *mut *mut PioError,
9485) -> *mut PioMulticonductorNetwork {
9486 unsafe {
9487 entry(error, std::ptr::null_mut(), || {
9488 let solution = PioCalculationSolution::get(solution).ok_or_else(|| {
9489 boundary_error(
9490 &codes::BIND_CAPI_NULL_HANDLE,
9491 "PioCalculationSolution must not be NULL",
9492 )
9493 })?;
9494 let projection = match solution.value() {
9495 Some(PioValue::McAcPfSolution(_)) => {
9496 MulticonductorNetworkProjection::McAcPfSolution
9497 }
9498 Some(PioValue::McAcOpfSolution(_)) => {
9499 MulticonductorNetworkProjection::McAcOpfSolution
9500 }
9501 Some(PioValue::LinDist3FlowOpfSolution(_)) => {
9502 MulticonductorNetworkProjection::LinDist3FlowOpfSolution
9503 }
9504 _ => {
9505 return Err(boundary_error(
9506 &codes::REQUEST_CAPI_TYPE_MISMATCH,
9507 "the calculation solution does not use powerio.MulticonductorNetwork",
9508 ));
9509 }
9510 };
9511 Ok(make_multiconductor_network_view(solution, projection))
9512 })
9513 }
9514}
9515
9516#[unsafe(no_mangle)]
9524pub unsafe extern "C" fn pio_operating_point_get_value(
9525 point: *const PioOperatingPoint,
9526 quantity: *const c_char,
9527 quantity_len: usize,
9528 identity: *const c_char,
9529 identity_len: usize,
9530 out_value: *mut f64,
9531 error: *mut *mut PioError,
9532) -> bool {
9533 unsafe {
9534 entry(error, false, || {
9535 if out_value.is_null() {
9536 return Err(boundary_error(
9537 &codes::BIND_CAPI_NULL_ARGUMENT,
9538 "out_value must not be NULL",
9539 ));
9540 }
9541 let quantity = required_str(quantity, quantity_len, "quantity")?;
9542 let identity = required_str(identity, identity_len, "identity")?;
9543 let point = PioOperatingPoint::get(point).ok_or_else(|| {
9544 boundary_error(
9545 &codes::BIND_CAPI_NULL_HANDLE,
9546 "PioOperatingPoint must not be NULL",
9547 )
9548 })?;
9549 let value = if let Some(point) = point.balanced() {
9550 use powerio_prob::{
9551 BalancedOperatingPointFlag as F, BalancedOperatingPointQuantity as Q,
9552 };
9553 match quantity {
9554 "bus_voltage_magnitude" => point.values(Q::BusVoltageMagnitude),
9555 "bus_voltage_angle" => point.values(Q::BusVoltageAngle),
9556 "bus_active_injection" => point.values(Q::BusActiveInjection),
9557 "bus_reactive_injection" => point.values(Q::BusReactiveInjection),
9558 "generator_active_power" => point.values(Q::GeneratorActivePower),
9559 "generator_reactive_power" => point.values(Q::GeneratorReactivePower),
9560 "generator_voltage_setpoint" => point.values(Q::GeneratorVoltageSetpoint),
9561 "load_active_power" => point.values(Q::LoadActivePower),
9562 "load_reactive_power" => point.values(Q::LoadReactivePower),
9563 "branch_tap_ratio" => point.values(Q::BranchTapRatio),
9564 "branch_phase_shift" => point.values(Q::BranchPhaseShift),
9565 "generator_in_service" => {
9566 let found = point
9567 .flags(F::GeneratorInService)
9568 .and_then(|mut values| values.find(|(key, _)| *key == identity))
9569 .map(|(_, value)| if value { 1.0 } else { 0.0 });
9570 if let Some(found) = found {
9571 *out_value = found;
9572 return Ok(true);
9573 }
9574 return Ok(false);
9575 }
9576 "branch_in_service" => {
9577 let found = point
9578 .flags(F::BranchInService)
9579 .and_then(|mut values| values.find(|(key, _)| *key == identity))
9580 .map(|(_, value)| if value { 1.0 } else { 0.0 });
9581 if let Some(found) = found {
9582 *out_value = found;
9583 return Ok(true);
9584 }
9585 return Ok(false);
9586 }
9587 "switch_closed" => {
9588 let found = point
9589 .flags(F::SwitchClosed)
9590 .and_then(|mut values| values.find(|(key, _)| *key == identity))
9591 .map(|(_, value)| if value { 1.0 } else { 0.0 });
9592 if let Some(found) = found {
9593 *out_value = found;
9594 return Ok(true);
9595 }
9596 return Ok(false);
9597 }
9598 _ => {
9599 return Err(boundary_error(
9600 &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
9601 format!("unknown balanced operating point quantity '{quantity}'"),
9602 ));
9603 }
9604 }
9605 .and_then(|mut values| values.find(|(key, _)| *key == identity))
9606 .map(|(_, value)| value)
9607 } else if let Some(point) = point.multiconductor() {
9608 use powerio_prob::{
9609 MulticonductorOperatingPointFlag as F,
9610 MulticonductorOperatingPointQuantity as Q,
9611 };
9612 match quantity {
9613 "terminal_voltage_magnitude" => point.values(Q::TerminalVoltageMagnitude),
9614 "terminal_voltage_angle" => point.values(Q::TerminalVoltageAngle),
9615 "load_active_power" => point.values(Q::LoadActivePower),
9616 "load_reactive_power" => point.values(Q::LoadReactivePower),
9617 "generator_active_power" => point.values(Q::GeneratorActivePower),
9618 "generator_reactive_power" => point.values(Q::GeneratorReactivePower),
9619 "transformer_tap" => point.values(Q::TransformerTap),
9620 "capacitor_steps" => point.values(Q::CapacitorSteps),
9621 "switch_closed" => {
9622 let found = point
9623 .flags(F::SwitchClosed)
9624 .and_then(|mut values| values.find(|(key, _)| *key == identity))
9625 .map(|(_, value)| if value { 1.0 } else { 0.0 });
9626 if let Some(found) = found {
9627 *out_value = found;
9628 return Ok(true);
9629 }
9630 return Ok(false);
9631 }
9632 _ => {
9633 return Err(boundary_error(
9634 &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
9635 format!("unknown multiconductor operating point quantity '{quantity}'"),
9636 ));
9637 }
9638 }
9639 .and_then(|mut values| values.find(|(key, _)| *key == identity))
9640 .map(|(_, value)| value)
9641 } else {
9642 return Err(boundary_error(
9643 &codes::REQUEST_CAPI_TYPE_MISMATCH,
9644 "the handle does not refer to an operating point",
9645 ));
9646 };
9647 if let Some(value) = value {
9648 *out_value = value;
9649 Ok(true)
9650 } else {
9651 Ok(false)
9652 }
9653 })
9654 }
9655}
9656
9657#[unsafe(no_mangle)]
9662pub unsafe extern "C" fn pio_operating_point_retain(
9663 point: *const PioOperatingPoint,
9664) -> *mut PioOperatingPoint {
9665 unsafe { PioOperatingPoint::retain_raw(point) }
9666}
9667
9668#[unsafe(no_mangle)]
9673pub unsafe extern "C" fn pio_operating_point_release(point: *mut PioOperatingPoint) {
9674 unsafe { PioOperatingPoint::release_raw(point) };
9675}
9676
9677#[unsafe(no_mangle)]
9682pub unsafe extern "C" fn pio_calculation_instance_retain(
9683 instance: *const PioCalculationInstance,
9684) -> *mut PioCalculationInstance {
9685 unsafe { PioCalculationInstance::retain_raw(instance) }
9686}
9687
9688#[unsafe(no_mangle)]
9693pub unsafe extern "C" fn pio_calculation_instance_release(instance: *mut PioCalculationInstance) {
9694 unsafe { PioCalculationInstance::release_raw(instance) };
9695}
9696
9697#[unsafe(no_mangle)]
9702pub unsafe extern "C" fn pio_dc_opf_preparation_retain(
9703 preparation: *const PioDcOpfPreparation,
9704) -> *mut PioDcOpfPreparation {
9705 unsafe { PioDcOpfPreparation::retain_raw(preparation) }
9706}
9707
9708#[unsafe(no_mangle)]
9713pub unsafe extern "C" fn pio_dc_opf_preparation_release(preparation: *mut PioDcOpfPreparation) {
9714 unsafe { PioDcOpfPreparation::release_raw(preparation) };
9715}
9716
9717#[unsafe(no_mangle)]
9722pub unsafe extern "C" fn pio_ac_opf_preparation_retain(
9723 preparation: *const PioAcOpfPreparation,
9724) -> *mut PioAcOpfPreparation {
9725 unsafe { PioAcOpfPreparation::retain_raw(preparation) }
9726}
9727
9728#[unsafe(no_mangle)]
9733pub unsafe extern "C" fn pio_ac_opf_preparation_release(preparation: *mut PioAcOpfPreparation) {
9734 unsafe { PioAcOpfPreparation::release_raw(preparation) };
9735}
9736
9737#[unsafe(no_mangle)]
9742pub unsafe extern "C" fn pio_calculation_solution_retain(
9743 solution: *const PioCalculationSolution,
9744) -> *mut PioCalculationSolution {
9745 unsafe { PioCalculationSolution::retain_raw(solution) }
9746}
9747
9748#[unsafe(no_mangle)]
9753pub unsafe extern "C" fn pio_calculation_solution_release(solution: *mut PioCalculationSolution) {
9754 unsafe { PioCalculationSolution::release_raw(solution) };
9755}
9756
9757#[unsafe(no_mangle)]
9762pub unsafe extern "C" fn pio_value_retain(value: *const PioValueHandle) -> *mut PioValueHandle {
9763 unsafe { PioValueHandle::retain_raw(value) }
9764}
9765
9766#[unsafe(no_mangle)]
9771pub unsafe extern "C" fn pio_value_release(value: *mut PioValueHandle) {
9772 unsafe { PioValueHandle::release_raw(value) };
9773}
9774
9775fn time_series(value: &ValueInner) -> Option<&PioTimeSeries> {
9778 match value.value()? {
9779 PioValue::TimeSeries(series) => Some(series),
9780 _ => None,
9781 }
9782}
9783
9784fn scenario_set(value: &ValueInner) -> Option<&PioScenarioSet> {
9785 match value.value()? {
9786 PioValue::ScenarioSet(scenarios) => Some(scenarios),
9787 _ => None,
9788 }
9789}
9790
9791#[unsafe(no_mangle)]
9795pub unsafe extern "C" fn pio_time_series_len(series: *const PioTimeSeriesHandle) -> usize {
9796 unsafe { PioTimeSeriesHandle::get(series) }
9797 .and_then(time_series)
9798 .map_or(0, PioTimeSeries::len)
9799}
9800
9801#[unsafe(no_mangle)]
9805pub unsafe extern "C" fn pio_time_series_element_type(
9806 series: *const PioTimeSeriesHandle,
9807) -> PioStringView {
9808 unsafe { PioTimeSeriesHandle::get(series) }
9809 .and_then(time_series)
9810 .map_or(PioStringView::EMPTY, |series| {
9811 PioStringView::new(series.element_type())
9812 })
9813}
9814
9815#[unsafe(no_mangle)]
9820pub unsafe extern "C" fn pio_time_series_get(
9821 series: *const PioTimeSeriesHandle,
9822 index: usize,
9823 error: *mut *mut PioError,
9824) -> *mut PioValueHandle {
9825 unsafe {
9826 entry(error, std::ptr::null_mut(), || {
9827 let series = PioTimeSeriesHandle::get(series).ok_or_else(|| {
9828 boundary_error(
9829 &codes::BIND_CAPI_NULL_HANDLE,
9830 "PioTimeSeriesHandle must not be NULL",
9831 )
9832 })?;
9833 let values = time_series(series).ok_or_else(|| {
9834 boundary_error(
9835 &codes::REQUEST_CAPI_TYPE_MISMATCH,
9836 "the handle does not refer to a time series",
9837 )
9838 })?;
9839 if values.get(index).is_none() {
9840 return Err(boundary_error(
9841 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9842 format!("time series index {index} is out of range"),
9843 ));
9844 }
9845 Ok(value_handle(series.child(ValueStep::TimeSeries(index))))
9846 })
9847 }
9848}
9849
9850#[unsafe(no_mangle)]
9855pub unsafe extern "C" fn pio_time_series_retain(
9856 series: *const PioTimeSeriesHandle,
9857) -> *mut PioTimeSeriesHandle {
9858 unsafe { PioTimeSeriesHandle::retain_raw(series) }
9859}
9860
9861#[unsafe(no_mangle)]
9866pub unsafe extern "C" fn pio_time_series_release(series: *mut PioTimeSeriesHandle) {
9867 unsafe { PioTimeSeriesHandle::release_raw(series) };
9868}
9869
9870#[unsafe(no_mangle)]
9874pub unsafe extern "C" fn pio_scenario_set_len(set: *const PioScenarioSetHandle) -> usize {
9875 unsafe { PioScenarioSetHandle::get(set) }
9876 .and_then(scenario_set)
9877 .map_or(0, PioScenarioSet::len)
9878}
9879
9880#[unsafe(no_mangle)]
9884pub unsafe extern "C" fn pio_scenario_set_element_type(
9885 set: *const PioScenarioSetHandle,
9886) -> PioStringView {
9887 unsafe { PioScenarioSetHandle::get(set) }
9888 .and_then(scenario_set)
9889 .map_or(PioStringView::EMPTY, |set| {
9890 PioStringView::new(set.element_type())
9891 })
9892}
9893
9894#[unsafe(no_mangle)]
9898pub unsafe extern "C" fn pio_scenario_set_id_at(
9899 set: *const PioScenarioSetHandle,
9900 index: usize,
9901) -> PioStringView {
9902 unsafe { PioScenarioSetHandle::get(set) }
9903 .and_then(scenario_set)
9904 .and_then(|set| set.iter().nth(index))
9905 .map_or(PioStringView::EMPTY, |scenario| {
9906 PioStringView::new(scenario.id().as_str())
9907 })
9908}
9909
9910#[unsafe(no_mangle)]
9915pub unsafe extern "C" fn pio_scenario_set_get_at(
9916 set: *const PioScenarioSetHandle,
9917 index: usize,
9918 error: *mut *mut PioError,
9919) -> *mut PioValueHandle {
9920 unsafe {
9921 entry(error, std::ptr::null_mut(), || {
9922 let set = PioScenarioSetHandle::get(set).ok_or_else(|| {
9923 boundary_error(
9924 &codes::BIND_CAPI_NULL_HANDLE,
9925 "PioScenarioSetHandle must not be NULL",
9926 )
9927 })?;
9928 let values = scenario_set(set).ok_or_else(|| {
9929 boundary_error(
9930 &codes::REQUEST_CAPI_TYPE_MISMATCH,
9931 "the handle does not refer to a scenario set",
9932 )
9933 })?;
9934 if values.get_at(index).is_none() {
9935 return Err(boundary_error(
9936 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9937 format!("scenario position {index} is out of range"),
9938 ));
9939 }
9940 Ok(value_handle(set.child(ValueStep::Scenario(index))))
9941 })
9942 }
9943}
9944
9945#[unsafe(no_mangle)]
9950pub unsafe extern "C" fn pio_scenario_set_get(
9951 set: *const PioScenarioSetHandle,
9952 id: *const c_char,
9953 id_len: usize,
9954 error: *mut *mut PioError,
9955) -> *mut PioValueHandle {
9956 unsafe {
9957 entry(error, std::ptr::null_mut(), || {
9958 let id = required_str(id, id_len, "scenario_id")?;
9959 let set = PioScenarioSetHandle::get(set).ok_or_else(|| {
9960 boundary_error(
9961 &codes::BIND_CAPI_NULL_HANDLE,
9962 "PioScenarioSetHandle must not be NULL",
9963 )
9964 })?;
9965 let values = scenario_set(set).ok_or_else(|| {
9966 boundary_error(
9967 &codes::REQUEST_CAPI_TYPE_MISMATCH,
9968 "the handle does not refer to a scenario set",
9969 )
9970 })?;
9971 let Some(position) = values
9972 .iter()
9973 .position(|scenario| scenario.id().as_str() == id)
9974 else {
9975 return Err(boundary_error(
9976 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
9977 format!("scenario ID '{id}' does not exist"),
9978 ));
9979 };
9980 Ok(value_handle(set.child(ValueStep::Scenario(position))))
9981 })
9982 }
9983}
9984
9985#[unsafe(no_mangle)]
9990pub unsafe extern "C" fn pio_scenario_set_retain(
9991 set: *const PioScenarioSetHandle,
9992) -> *mut PioScenarioSetHandle {
9993 unsafe { PioScenarioSetHandle::retain_raw(set) }
9994}
9995
9996#[unsafe(no_mangle)]
10001pub unsafe extern "C" fn pio_scenario_set_release(set: *mut PioScenarioSetHandle) {
10002 unsafe { PioScenarioSetHandle::release_raw(set) };
10003}
10004
10005#[unsafe(no_mangle)]
10011pub unsafe extern "C" fn pio_balanced_network_name(
10012 network: *const PioBalancedNetwork,
10013) -> PioStringView {
10014 unsafe { PioBalancedNetwork::get(network) }
10015 .and_then(BalancedNetworkInner::network)
10016 .map_or(PioStringView::EMPTY, |network| {
10017 PioStringView::new(network.name())
10018 })
10019}
10020
10021#[unsafe(no_mangle)]
10025pub unsafe extern "C" fn pio_balanced_network_base_mva(network: *const PioBalancedNetwork) -> f64 {
10026 unsafe { PioBalancedNetwork::get(network) }
10027 .and_then(BalancedNetworkInner::network)
10028 .map_or(f64::NAN, BalancedNetwork::base_mva)
10029}
10030
10031#[unsafe(no_mangle)]
10035pub unsafe extern "C" fn pio_balanced_network_base_frequency_hz(
10036 network: *const PioBalancedNetwork,
10037) -> f64 {
10038 unsafe { PioBalancedNetwork::get(network) }
10039 .and_then(BalancedNetworkInner::network)
10040 .map_or(f64::NAN, BalancedNetwork::base_frequency)
10041}
10042
10043#[unsafe(no_mangle)]
10048pub unsafe extern "C" fn pio_balanced_network_geo(
10049 network: *const PioBalancedNetwork,
10050 output: *mut PioBalancedGeoView,
10051 error: *mut *mut PioError,
10052) -> bool {
10053 unsafe {
10054 entry(error, false, || {
10055 let network = require_balanced_network(network)?;
10056 *require_output(output, "output")? = balanced_geo_view(network.geo().as_ref());
10057 Ok(true)
10058 })
10059 }
10060}
10061
10062#[unsafe(no_mangle)]
10066pub unsafe extern "C" fn pio_balanced_network_has_detailed_connectivity(
10067 network: *const PioBalancedNetwork,
10068) -> bool {
10069 unsafe { PioBalancedNetwork::get(network) }
10070 .and_then(BalancedNetworkInner::network)
10071 .is_some_and(|network| network.detailed_connectivity().is_some())
10072}
10073
10074#[unsafe(no_mangle)]
10079pub unsafe extern "C" fn pio_balanced_network_detailed_connectivity(
10080 network: *const PioBalancedNetwork,
10081) -> *mut PioDetailedConnectivity {
10082 let Some(owner) = (unsafe { PioBalancedNetwork::arc(network) }) else {
10083 return std::ptr::null_mut();
10084 };
10085 if owner
10086 .network()
10087 .is_none_or(|network| network.detailed_connectivity().is_none())
10088 {
10089 return std::ptr::null_mut();
10090 }
10091 PioDetailedConnectivity::new_raw(DetailedConnectivityInner { owner })
10092}
10093
10094#[unsafe(no_mangle)]
10099pub unsafe extern "C" fn pio_detailed_connectivity_counts(
10100 details: *const PioDetailedConnectivity,
10101 output: *mut PioDetailedConnectivityCountsView,
10102 error: *mut *mut PioError,
10103) -> bool {
10104 unsafe {
10105 entry(error, false, || {
10106 let details = require_detailed_connectivity(details)?;
10107 *require_output(output, "output")? = PioDetailedConnectivityCountsView {
10108 omitted_fields: details.omitted_fields.len(),
10109 component_metadata: details.component_metadata.len(),
10110 subnetworks: details.subnetworks.len(),
10111 substations: details.substations.len(),
10112 voltage_levels: details.voltage_levels.len(),
10113 bus_breaker_buses: details.bus_breaker_buses.len(),
10114 calculated_buses: details.calculated_buses.len(),
10115 connectivity_nodes: details.connectivity_nodes.len(),
10116 busbar_sections: details.busbar_sections.len(),
10117 junctions: details.junctions.len(),
10118 terminals: details.terminals.len(),
10119 switches: details.switches.len(),
10120 internal_connections: details.internal_connections.len(),
10121 operational_limit_groups: details.operational_limit_groups.len(),
10122 tap_changers: details.tap_changers.len(),
10123 equipment_reactive_limits: details.equipment_reactive_limits.len(),
10124 boundary_lines: details.boundary_lines.len(),
10125 tie_lines: details.tie_lines.len(),
10126 dc_converter_units: details.dc_converter_units.len(),
10127 dc_topological_nodes: details.dc_topological_nodes.len(),
10128 dc_nodes: details.dc_nodes.len(),
10129 dc_grounds: details.dc_grounds.len(),
10130 dc_busbars: details.dc_busbars.len(),
10131 dc_lines: details.dc_lines.len(),
10132 dc_series_devices: details.dc_series_devices.len(),
10133 dc_switches: details.dc_switches.len(),
10134 voltage_source_converters: details.voltage_source_converters.len(),
10135 line_commutated_converters: details.line_commutated_converters.len(),
10136 };
10137 Ok(true)
10138 })
10139 }
10140}
10141
10142fn omitted_field_name(value: powerio_tx::OmittedFieldName) -> &'static str {
10143 match value {
10144 powerio_tx::OmittedFieldName::ActivePower => "active_power",
10145 powerio_tx::OmittedFieldName::ReactivePower => "reactive_power",
10146 powerio_tx::OmittedFieldName::VoltageSetpoint => "voltage_setpoint",
10147 powerio_tx::OmittedFieldName::RatedApparentPower => "rated_apparent_power",
10148 powerio_tx::OmittedFieldName::ShuntConductancePerSection => "shunt_conductance_per_section",
10149 _ => "unknown",
10150 }
10151}
10152
10153fn topology_kind_name(kind: powerio_tx::TopologyKind) -> &'static str {
10154 match kind {
10155 powerio_tx::TopologyKind::BusBreaker => "bus_breaker",
10156 powerio_tx::TopologyKind::NodeBreaker => "node_breaker",
10157 _ => "unknown",
10158 }
10159}
10160
10161fn topology_switch_kind_name(kind: powerio_tx::SwitchKind) -> &'static str {
10162 match kind {
10163 powerio_tx::SwitchKind::Breaker => "breaker",
10164 powerio_tx::SwitchKind::Disconnector => "disconnector",
10165 powerio_tx::SwitchKind::LoadBreakSwitch => "load_break_switch",
10166 _ => "unknown",
10167 }
10168}
10169
10170fn curve_style_name(style: powerio_tx::CurveStyle) -> &'static str {
10171 match style {
10172 powerio_tx::CurveStyle::ConstantYValue => "constant_y_value",
10173 powerio_tx::CurveStyle::StraightLineYValues => "straight_line_y_values",
10174 _ => "unknown",
10175 }
10176}
10177
10178fn empty_reactive_limits_view() -> PioReactiveLimitsView {
10179 PioReactiveLimitsView {
10180 kind: PioStringView::EMPTY,
10181 minimum_reactive_power_mvar: 0.0,
10182 maximum_reactive_power_mvar: 0.0,
10183 has_minimum_and_maximum: false,
10184 curve_style: PioStringView::EMPTY,
10185 has_curve_style: false,
10186 property_count: 0,
10187 point_count: 0,
10188 }
10189}
10190
10191fn reactive_limits_view(
10192 value: Option<&powerio_tx::ReactiveLimits>,
10193) -> (PioReactiveLimitsView, bool) {
10194 match value {
10195 Some(powerio_tx::ReactiveLimits::MinMax(limits)) => (
10196 PioReactiveLimitsView {
10197 kind: PioStringView::new("min_max"),
10198 minimum_reactive_power_mvar: limits.minimum_reactive_power_mvar,
10199 maximum_reactive_power_mvar: limits.maximum_reactive_power_mvar,
10200 has_minimum_and_maximum: true,
10201 curve_style: PioStringView::EMPTY,
10202 has_curve_style: false,
10203 property_count: limits.properties.len(),
10204 point_count: 0,
10205 },
10206 true,
10207 ),
10208 Some(powerio_tx::ReactiveLimits::CapabilityCurve(curve)) => (
10209 PioReactiveLimitsView {
10210 kind: PioStringView::new("capability_curve"),
10211 minimum_reactive_power_mvar: 0.0,
10212 maximum_reactive_power_mvar: 0.0,
10213 has_minimum_and_maximum: false,
10214 curve_style: PioStringView::new(curve_style_name(curve.curve_style)),
10215 has_curve_style: true,
10216 property_count: curve.properties.len(),
10217 point_count: curve.points.len(),
10218 },
10219 true,
10220 ),
10221 Some(_) => (empty_reactive_limits_view(), true),
10222 None => (empty_reactive_limits_view(), false),
10223 }
10224}
10225
10226fn reactive_limit_properties(
10227 limits: &powerio_tx::ReactiveLimits,
10228) -> Result<&std::collections::BTreeMap<String, String>, *mut PioError> {
10229 Ok(match limits {
10230 powerio_tx::ReactiveLimits::MinMax(limits) => &limits.properties,
10231 powerio_tx::ReactiveLimits::CapabilityCurve(curve) => &curve.properties,
10232 _ => {
10233 return Err(boundary_error(
10234 &codes::REQUEST_CAPI_TYPE_MISMATCH,
10235 "unsupported reactive limit form",
10236 ));
10237 }
10238 })
10239}
10240
10241fn reactive_capability_curve(
10242 limits: &powerio_tx::ReactiveLimits,
10243) -> Option<&powerio_tx::ReactiveCapabilityCurve> {
10244 match limits {
10245 powerio_tx::ReactiveLimits::CapabilityCurve(curve) => Some(curve),
10246 _ => None,
10247 }
10248}
10249
10250fn string_property_view(
10251 properties: &std::collections::BTreeMap<String, String>,
10252 index: usize,
10253) -> Result<PioStringPropertyView, *mut PioError> {
10254 let (name, value) = properties.iter().nth(index).ok_or_else(|| {
10255 boundary_error(
10256 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10257 format!("property index {index} is out of range"),
10258 )
10259 })?;
10260 Ok(PioStringPropertyView {
10261 name: PioStringView::new(name),
10262 value: PioStringView::new(value),
10263 })
10264}
10265
10266fn reactive_capability_point_view(
10267 curve: &powerio_tx::ReactiveCapabilityCurve,
10268 index: usize,
10269) -> Result<PioReactiveCapabilityCurvePointView, *mut PioError> {
10270 let point = curve.points.get(index).ok_or_else(|| {
10271 boundary_error(
10272 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10273 format!("reactive capability point index {index} is out of range"),
10274 )
10275 })?;
10276 Ok(PioReactiveCapabilityCurvePointView {
10277 active_power_mw: point.active_power_mw,
10278 minimum_reactive_power_mvar: point.minimum_reactive_power_mvar,
10279 maximum_reactive_power_mvar: point.maximum_reactive_power_mvar,
10280 property_count: point.properties.len(),
10281 })
10282}
10283
10284fn empty_boundary_line_generation_view() -> PioBoundaryLineGenerationView {
10285 PioBoundaryLineGenerationView {
10286 voltage_regulation_on: false,
10287 minimum_active_power_mw: 0.0,
10288 has_minimum_active_power: false,
10289 maximum_active_power_mw: 0.0,
10290 has_maximum_active_power: false,
10291 target_active_power_mw: 0.0,
10292 has_target_active_power: false,
10293 target_reactive_power_mvar: 0.0,
10294 has_target_reactive_power: false,
10295 target_voltage_kv: 0.0,
10296 has_target_voltage: false,
10297 reactive_limits: empty_reactive_limits_view(),
10298 has_reactive_limits: false,
10299 }
10300}
10301
10302fn boundary_line_generation_view(
10303 value: Option<&powerio_tx::BoundaryLineGeneration>,
10304) -> (PioBoundaryLineGenerationView, bool) {
10305 let Some(value) = value else {
10306 return (empty_boundary_line_generation_view(), false);
10307 };
10308 let (reactive_limits, has_reactive_limits) =
10309 reactive_limits_view(value.reactive_limits.as_ref());
10310 (
10311 PioBoundaryLineGenerationView {
10312 voltage_regulation_on: value.voltage_regulation_on,
10313 minimum_active_power_mw: value.minimum_active_power_mw.unwrap_or(0.0),
10314 has_minimum_active_power: value.minimum_active_power_mw.is_some(),
10315 maximum_active_power_mw: value.maximum_active_power_mw.unwrap_or(0.0),
10316 has_maximum_active_power: value.maximum_active_power_mw.is_some(),
10317 target_active_power_mw: value.target_active_power_mw.unwrap_or(0.0),
10318 has_target_active_power: value.target_active_power_mw.is_some(),
10319 target_reactive_power_mvar: value.target_reactive_power_mvar.unwrap_or(0.0),
10320 has_target_reactive_power: value.target_reactive_power_mvar.is_some(),
10321 target_voltage_kv: value.target_voltage_kv.unwrap_or(0.0),
10322 has_target_voltage: value.target_voltage_kv.is_some(),
10323 reactive_limits,
10324 has_reactive_limits,
10325 },
10326 true,
10327 )
10328}
10329
10330fn topology_endpoint_view(
10331 endpoint: &powerio_tx::TopologyEndpoint,
10332) -> (PioStringView, PioComponentIdView) {
10333 match endpoint {
10334 powerio_tx::TopologyEndpoint::Bus(component) => {
10335 (PioStringView::new("bus"), component_id_view(component))
10336 }
10337 powerio_tx::TopologyEndpoint::Node(component) => {
10338 (PioStringView::new("node"), component_id_view(component))
10339 }
10340 _ => (PioStringView::new("unknown"), empty_component_id_view()),
10341 }
10342}
10343
10344fn tap_changer_kind_name(kind: powerio_tx::TapChangerKind) -> &'static str {
10345 match kind {
10346 powerio_tx::TapChangerKind::Ratio => "ratio",
10347 powerio_tx::TapChangerKind::Phase => "phase",
10348 _ => "unknown",
10349 }
10350}
10351
10352fn tap_changer_regulation_mode_name(mode: powerio_tx::TapChangerRegulationMode) -> &'static str {
10353 match mode {
10354 powerio_tx::TapChangerRegulationMode::Voltage => "voltage",
10355 powerio_tx::TapChangerRegulationMode::ReactivePower => "reactive_power",
10356 powerio_tx::TapChangerRegulationMode::ActivePower => "active_power",
10357 powerio_tx::TapChangerRegulationMode::Current => "current",
10358 _ => "unknown",
10359 }
10360}
10361
10362fn dc_polarity_name(value: powerio_tx::DcPolarity) -> &'static str {
10363 match value {
10364 powerio_tx::DcPolarity::Positive => "positive",
10365 powerio_tx::DcPolarity::Middle => "middle",
10366 powerio_tx::DcPolarity::Negative => "negative",
10367 _ => "unknown",
10368 }
10369}
10370
10371fn dc_switch_kind_name(value: powerio_tx::DcSwitchKind) -> &'static str {
10372 match value {
10373 powerio_tx::DcSwitchKind::Switch => "switch",
10374 powerio_tx::DcSwitchKind::Breaker => "breaker",
10375 powerio_tx::DcSwitchKind::Disconnector => "disconnector",
10376 _ => "unknown",
10377 }
10378}
10379
10380fn dc_converter_operation_mode_name(value: powerio_tx::DcConverterOperatingMode) -> &'static str {
10381 match value {
10382 powerio_tx::DcConverterOperatingMode::Bipolar => "bipolar",
10383 powerio_tx::DcConverterOperatingMode::MonopolarGroundReturn => "monopolar_ground_return",
10384 powerio_tx::DcConverterOperatingMode::MonopolarMetallicReturn => {
10385 "monopolar_metallic_return"
10386 }
10387 _ => "unknown",
10388 }
10389}
10390
10391fn ac_dc_converter_control_mode_name(value: powerio_tx::AcDcConverterControlMode) -> &'static str {
10392 match value {
10393 powerio_tx::AcDcConverterControlMode::ActivePowerAtPcc => "active_power_at_pcc",
10394 powerio_tx::AcDcConverterControlMode::DcVoltage => "dc_voltage",
10395 powerio_tx::AcDcConverterControlMode::DcCurrent => "dc_current",
10396 powerio_tx::AcDcConverterControlMode::ActivePowerAtPccAndDcVoltageDroopCurve => {
10397 "active_power_at_pcc_and_dc_voltage_droop_curve"
10398 }
10399 powerio_tx::AcDcConverterControlMode::ActivePowerAtPccAndDcVoltageDroop => {
10400 "active_power_at_pcc_and_dc_voltage_droop"
10401 }
10402 powerio_tx::AcDcConverterControlMode::ActivePowerAtPccAndDcVoltageDroopWithCompensation => {
10403 "active_power_at_pcc_and_dc_voltage_droop_with_compensation"
10404 }
10405 powerio_tx::AcDcConverterControlMode::ActivePowerAtPccAndDcVoltageDroopPilot => {
10406 "active_power_at_pcc_and_dc_voltage_droop_pilot"
10407 }
10408 _ => "unknown",
10409 }
10410}
10411
10412fn line_commutated_converter_reactive_model_name(
10413 value: powerio_tx::LineCommutatedConverterReactiveModel,
10414) -> &'static str {
10415 match value {
10416 powerio_tx::LineCommutatedConverterReactiveModel::FixedPowerFactor => "fixed_power_factor",
10417 powerio_tx::LineCommutatedConverterReactiveModel::CalculatedPowerFactor => {
10418 "calculated_power_factor"
10419 }
10420 _ => "unknown",
10421 }
10422}
10423
10424fn line_commutated_converter_operating_mode_name(
10425 value: powerio_tx::LineCommutatedConverterOperatingMode,
10426) -> &'static str {
10427 match value {
10428 powerio_tx::LineCommutatedConverterOperatingMode::Rectifier => "rectifier",
10429 powerio_tx::LineCommutatedConverterOperatingMode::Inverter => "inverter",
10430 _ => "unknown",
10431 }
10432}
10433
10434fn empty_dc_terminal_view() -> PioDcTerminalView {
10435 PioDcTerminalView {
10436 component: empty_component_id_view(),
10437 has_component: false,
10438 sequence_number: 0,
10439 has_sequence_number: false,
10440 dc_node: empty_component_id_view(),
10441 has_dc_node: false,
10442 dc_topological_node: empty_component_id_view(),
10443 has_dc_topological_node: false,
10444 polarity: PioStringView::EMPTY,
10445 has_polarity: false,
10446 connected: false,
10447 has_connected: false,
10448 active_power_mw: 0.0,
10449 has_active_power: false,
10450 current_a: 0.0,
10451 has_current: false,
10452 }
10453}
10454
10455fn dc_terminal_view(value: &powerio_tx::DcTerminal) -> PioDcTerminalView {
10456 let (component, has_component) = optional_component_id_view(value.component.as_ref());
10457 let (dc_node, has_dc_node) = optional_component_id_view(value.dc_node.as_ref());
10458 let (dc_topological_node, has_dc_topological_node) =
10459 optional_component_id_view(value.dc_topological_node.as_ref());
10460 let (polarity, has_polarity) = value
10461 .polarity
10462 .map_or((PioStringView::EMPTY, false), |polarity| {
10463 (PioStringView::new(dc_polarity_name(polarity)), true)
10464 });
10465 PioDcTerminalView {
10466 component,
10467 has_component,
10468 sequence_number: value.sequence_number.unwrap_or(0),
10469 has_sequence_number: value.sequence_number.is_some(),
10470 dc_node,
10471 has_dc_node,
10472 dc_topological_node,
10473 has_dc_topological_node,
10474 polarity,
10475 has_polarity,
10476 connected: value.connected.unwrap_or(false),
10477 has_connected: value.connected.is_some(),
10478 active_power_mw: value.active_power_mw.unwrap_or(0.0),
10479 has_active_power: value.active_power_mw.is_some(),
10480 current_a: value.current_a.unwrap_or(0.0),
10481 has_current: value.current_a.is_some(),
10482 }
10483}
10484
10485#[allow(clippy::too_many_arguments)]
10486fn dc_equipment_view(
10487 component: &ComponentId,
10488 equipment_container: Option<&ComponentId>,
10489 kind: &'static str,
10490 terminal1: &powerio_tx::DcTerminal,
10491 terminal2: Option<&powerio_tx::DcTerminal>,
10492 rated_dc_voltage_kv: Option<f64>,
10493 resistance_ohm: Option<f64>,
10494 inductance_h: Option<f64>,
10495 capacitance_f: Option<f64>,
10496 length_km: Option<f64>,
10497 switch_kind: Option<powerio_tx::DcSwitchKind>,
10498 open: Option<bool>,
10499) -> PioDcEquipmentView {
10500 let (equipment_container, has_equipment_container) =
10501 optional_component_id_view(equipment_container);
10502 let (switch_kind, has_switch_kind) = switch_kind
10503 .map_or((PioStringView::EMPTY, false), |kind| {
10504 (PioStringView::new(dc_switch_kind_name(kind)), true)
10505 });
10506 PioDcEquipmentView {
10507 component: component_id_view(component),
10508 equipment_container,
10509 has_equipment_container,
10510 kind: PioStringView::new(kind),
10511 terminal_count: usize::from(terminal2.is_some()) + 1,
10512 terminal1: dc_terminal_view(terminal1),
10513 terminal2: terminal2.map_or_else(empty_dc_terminal_view, dc_terminal_view),
10514 rated_dc_voltage_kv: rated_dc_voltage_kv.unwrap_or(0.0),
10515 has_rated_dc_voltage: rated_dc_voltage_kv.is_some(),
10516 resistance_ohm: resistance_ohm.unwrap_or(0.0),
10517 has_resistance: resistance_ohm.is_some(),
10518 inductance_h: inductance_h.unwrap_or(0.0),
10519 has_inductance: inductance_h.is_some(),
10520 capacitance_f: capacitance_f.unwrap_or(0.0),
10521 has_capacitance: capacitance_f.is_some(),
10522 length_km: length_km.unwrap_or(0.0),
10523 has_length: length_km.is_some(),
10524 switch_kind,
10525 has_switch_kind,
10526 open: open.unwrap_or(false),
10527 has_open: open.is_some(),
10528 }
10529}
10530
10531#[unsafe(no_mangle)]
10536pub unsafe extern "C" fn pio_detailed_connectivity_omitted_field_at(
10537 details: *const PioDetailedConnectivity,
10538 index: usize,
10539 output: *mut PioOmittedFieldView,
10540 error: *mut *mut PioError,
10541) -> bool {
10542 unsafe {
10543 entry(error, false, || {
10544 let details = require_detailed_connectivity(details)?;
10545 let field = details.omitted_fields.get(index).ok_or_else(|| {
10546 boundary_error(
10547 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10548 format!("omitted field index {index} is out of range"),
10549 )
10550 })?;
10551 *require_output(output, "output")? = PioOmittedFieldView {
10552 component: component_id_view(&field.component),
10553 field: PioStringView::new(omitted_field_name(field.field)),
10554 };
10555 Ok(true)
10556 })
10557 }
10558}
10559
10560#[unsafe(no_mangle)]
10565pub unsafe extern "C" fn pio_detailed_connectivity_component_metadata_at(
10566 details: *const PioDetailedConnectivity,
10567 index: usize,
10568 output: *mut PioComponentMetadataView,
10569 error: *mut *mut PioError,
10570) -> bool {
10571 unsafe {
10572 entry(error, false, || {
10573 let details = require_detailed_connectivity(details)?;
10574 let metadata = details.component_metadata.get(index).ok_or_else(|| {
10575 boundary_error(
10576 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10577 format!("component metadata index {index} is out of range"),
10578 )
10579 })?;
10580 let (name, has_name) = optional_string_view(metadata.name.as_deref());
10581 let (equipment_container, has_equipment_container) =
10582 optional_component_id_view(metadata.equipment_container.as_ref());
10583 *require_output(output, "output")? = PioComponentMetadataView {
10584 component: component_id_view(&metadata.component),
10585 name,
10586 has_name,
10587 equipment_container,
10588 has_equipment_container,
10589 fictitious: metadata.fictitious,
10590 alias_count: metadata.aliases.len(),
10591 external_identifier_count: metadata.external_identifiers.len(),
10592 property_count: metadata.properties.len(),
10593 };
10594 Ok(true)
10595 })
10596 }
10597}
10598
10599#[unsafe(no_mangle)]
10604pub unsafe extern "C" fn pio_detailed_connectivity_component_alias_at(
10605 details: *const PioDetailedConnectivity,
10606 metadata_index: usize,
10607 alias_index: usize,
10608 output: *mut PioComponentAliasView,
10609 error: *mut *mut PioError,
10610) -> bool {
10611 unsafe {
10612 entry(error, false, || {
10613 let details = require_detailed_connectivity(details)?;
10614 let metadata = details
10615 .component_metadata
10616 .get(metadata_index)
10617 .ok_or_else(|| {
10618 boundary_error(
10619 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10620 format!("component metadata index {metadata_index} is out of range"),
10621 )
10622 })?;
10623 let alias = metadata.aliases.get(alias_index).ok_or_else(|| {
10624 boundary_error(
10625 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10626 format!("component alias index {alias_index} is out of range"),
10627 )
10628 })?;
10629 let (alias_type, has_alias_type) = optional_string_view(alias.alias_type.as_deref());
10630 *require_output(output, "output")? = PioComponentAliasView {
10631 value: PioStringView::new(&alias.value),
10632 alias_type,
10633 has_alias_type,
10634 };
10635 Ok(true)
10636 })
10637 }
10638}
10639
10640#[unsafe(no_mangle)]
10645pub unsafe extern "C" fn pio_detailed_connectivity_external_identifier_at(
10646 details: *const PioDetailedConnectivity,
10647 metadata_index: usize,
10648 identifier_index: usize,
10649 output: *mut PioExternalIdentifierView,
10650 error: *mut *mut PioError,
10651) -> bool {
10652 unsafe {
10653 entry(error, false, || {
10654 let details = require_detailed_connectivity(details)?;
10655 let metadata = details
10656 .component_metadata
10657 .get(metadata_index)
10658 .ok_or_else(|| {
10659 boundary_error(
10660 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10661 format!("component metadata index {metadata_index} is out of range"),
10662 )
10663 })?;
10664 let identifier = metadata
10665 .external_identifiers
10666 .get(identifier_index)
10667 .ok_or_else(|| {
10668 boundary_error(
10669 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10670 format!("external identifier index {identifier_index} is out of range"),
10671 )
10672 })?;
10673 let (authority, has_authority) = optional_string_view(identifier.authority.as_deref());
10674 *require_output(output, "output")? = PioExternalIdentifierView {
10675 value: PioStringView::new(&identifier.value),
10676 authority,
10677 has_authority,
10678 };
10679 Ok(true)
10680 })
10681 }
10682}
10683
10684#[unsafe(no_mangle)]
10689pub unsafe extern "C" fn pio_detailed_connectivity_component_property_at(
10690 details: *const PioDetailedConnectivity,
10691 metadata_index: usize,
10692 property_index: usize,
10693 output: *mut PioStringPropertyView,
10694 error: *mut *mut PioError,
10695) -> bool {
10696 unsafe {
10697 entry(error, false, || {
10698 let details = require_detailed_connectivity(details)?;
10699 let metadata = details
10700 .component_metadata
10701 .get(metadata_index)
10702 .ok_or_else(|| {
10703 boundary_error(
10704 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10705 format!("component metadata index {metadata_index} is out of range"),
10706 )
10707 })?;
10708 let (name, value) =
10709 metadata
10710 .properties
10711 .iter()
10712 .nth(property_index)
10713 .ok_or_else(|| {
10714 boundary_error(
10715 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10716 format!("component property index {property_index} is out of range"),
10717 )
10718 })?;
10719 *require_output(output, "output")? = PioStringPropertyView {
10720 name: PioStringView::new(name),
10721 value: PioStringView::new(value),
10722 };
10723 Ok(true)
10724 })
10725 }
10726}
10727
10728fn case_metadata_view(metadata: &powerio_tx::CaseMetadata) -> PioCaseMetadataView {
10729 let (case_date, has_case_date) = optional_string_view(metadata.case_date.as_deref());
10730 let (source_model_format, has_source_model_format) =
10731 optional_string_view(metadata.source_model_format.as_deref());
10732 let (minimum_validation_level, has_minimum_validation_level) =
10733 optional_string_view(metadata.minimum_validation_level.as_deref());
10734 PioCaseMetadataView {
10735 case_date,
10736 has_case_date,
10737 forecast_distance: metadata.forecast_distance.unwrap_or(0),
10738 has_forecast_distance: metadata.forecast_distance.is_some(),
10739 source_model_format,
10740 has_source_model_format,
10741 minimum_validation_level,
10742 has_minimum_validation_level,
10743 }
10744}
10745
10746#[unsafe(no_mangle)]
10751pub unsafe extern "C" fn pio_detailed_connectivity_subnetwork_at(
10752 details: *const PioDetailedConnectivity,
10753 index: usize,
10754 output: *mut PioSubnetworkView,
10755 error: *mut *mut PioError,
10756) -> bool {
10757 unsafe {
10758 entry(error, false, || {
10759 let details = require_detailed_connectivity(details)?;
10760 let subnetwork = details.subnetworks.get(index).ok_or_else(|| {
10761 boundary_error(
10762 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10763 format!("subnetwork index {index} is out of range"),
10764 )
10765 })?;
10766 *require_output(output, "output")? = PioSubnetworkView {
10767 component: component_id_view(&subnetwork.component),
10768 parent: component_id_view(&subnetwork.parent),
10769 case_metadata: case_metadata_view(&subnetwork.case_metadata),
10770 component_count: subnetwork.components.len(),
10771 };
10772 Ok(true)
10773 })
10774 }
10775}
10776
10777#[unsafe(no_mangle)]
10782pub unsafe extern "C" fn pio_detailed_connectivity_subnetwork_component_at(
10783 details: *const PioDetailedConnectivity,
10784 subnetwork_index: usize,
10785 component_index: usize,
10786 output: *mut PioComponentIdView,
10787 error: *mut *mut PioError,
10788) -> bool {
10789 unsafe {
10790 entry(error, false, || {
10791 let details = require_detailed_connectivity(details)?;
10792 let subnetwork = details.subnetworks.get(subnetwork_index).ok_or_else(|| {
10793 boundary_error(
10794 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10795 format!("subnetwork index {subnetwork_index} is out of range"),
10796 )
10797 })?;
10798 let component = subnetwork.components.get(component_index).ok_or_else(|| {
10799 boundary_error(
10800 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10801 format!("subnetwork component index {component_index} is out of range"),
10802 )
10803 })?;
10804 *require_output(output, "output")? = component_id_view(component);
10805 Ok(true)
10806 })
10807 }
10808}
10809
10810#[unsafe(no_mangle)]
10815pub unsafe extern "C" fn pio_detailed_connectivity_substation_at(
10816 details: *const PioDetailedConnectivity,
10817 index: usize,
10818 output: *mut PioSubstationView,
10819 error: *mut *mut PioError,
10820) -> bool {
10821 unsafe {
10822 entry(error, false, || {
10823 let details = require_detailed_connectivity(details)?;
10824 let substation = details.substations.get(index).ok_or_else(|| {
10825 boundary_error(
10826 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10827 format!("substation index {index} is out of range"),
10828 )
10829 })?;
10830 let (country, has_country) = optional_string_view(substation.country.as_deref());
10831 let (operator_name, has_operator_name) =
10832 optional_string_view(substation.operator.as_deref());
10833 *require_output(output, "output")? = PioSubstationView {
10834 component: component_id_view(&substation.component),
10835 country,
10836 has_country,
10837 operator_name,
10838 has_operator_name,
10839 geographical_tag_count: substation.geographical_tags.len(),
10840 };
10841 Ok(true)
10842 })
10843 }
10844}
10845
10846#[unsafe(no_mangle)]
10851pub unsafe extern "C" fn pio_detailed_connectivity_substation_geographical_tag_at(
10852 details: *const PioDetailedConnectivity,
10853 substation_index: usize,
10854 tag_index: usize,
10855 output: *mut PioStringView,
10856 error: *mut *mut PioError,
10857) -> bool {
10858 unsafe {
10859 entry(error, false, || {
10860 let details = require_detailed_connectivity(details)?;
10861 let substation = details.substations.get(substation_index).ok_or_else(|| {
10862 boundary_error(
10863 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10864 format!("substation index {substation_index} is out of range"),
10865 )
10866 })?;
10867 let tag = substation.geographical_tags.get(tag_index).ok_or_else(|| {
10868 boundary_error(
10869 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10870 format!("geographical tag index {tag_index} is out of range"),
10871 )
10872 })?;
10873 *require_output(output, "output")? = PioStringView::new(tag);
10874 Ok(true)
10875 })
10876 }
10877}
10878
10879#[unsafe(no_mangle)]
10884pub unsafe extern "C" fn pio_detailed_connectivity_voltage_level_at(
10885 details: *const PioDetailedConnectivity,
10886 index: usize,
10887 output: *mut PioVoltageLevelView,
10888 error: *mut *mut PioError,
10889) -> bool {
10890 unsafe {
10891 entry(error, false, || {
10892 let details = require_detailed_connectivity(details)?;
10893 let level = details.voltage_levels.get(index).ok_or_else(|| {
10894 boundary_error(
10895 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10896 format!("voltage level index {index} is out of range"),
10897 )
10898 })?;
10899 let (substation, has_substation) =
10900 optional_component_id_view(level.substation.as_ref());
10901 *require_output(output, "output")? = PioVoltageLevelView {
10902 component: component_id_view(&level.component),
10903 substation,
10904 has_substation,
10905 nominal_voltage_kv: level.nominal_kv,
10906 low_voltage_limit_kv: level.low_voltage_limit_kv.unwrap_or(0.0),
10907 has_low_voltage_limit: level.low_voltage_limit_kv.is_some(),
10908 high_voltage_limit_kv: level.high_voltage_limit_kv.unwrap_or(0.0),
10909 has_high_voltage_limit: level.high_voltage_limit_kv.is_some(),
10910 topology_kind: PioStringView::new(topology_kind_name(level.topology_kind)),
10911 bus_count: level.buses.len(),
10912 };
10913 Ok(true)
10914 })
10915 }
10916}
10917
10918#[unsafe(no_mangle)]
10923pub unsafe extern "C" fn pio_detailed_connectivity_voltage_level_bus_at(
10924 details: *const PioDetailedConnectivity,
10925 voltage_level_index: usize,
10926 bus_index: usize,
10927 output: *mut usize,
10928 error: *mut *mut PioError,
10929) -> bool {
10930 unsafe {
10931 entry(error, false, || {
10932 let details = require_detailed_connectivity(details)?;
10933 let level = details
10934 .voltage_levels
10935 .get(voltage_level_index)
10936 .ok_or_else(|| {
10937 boundary_error(
10938 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10939 format!("voltage level index {voltage_level_index} is out of range"),
10940 )
10941 })?;
10942 let bus = level.buses.get(bus_index).ok_or_else(|| {
10943 boundary_error(
10944 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10945 format!("voltage level bus index {bus_index} is out of range"),
10946 )
10947 })?;
10948 *require_output(output, "output")? = bus.0;
10949 Ok(true)
10950 })
10951 }
10952}
10953
10954#[unsafe(no_mangle)]
10959pub unsafe extern "C" fn pio_detailed_connectivity_bus_breaker_bus_at(
10960 details: *const PioDetailedConnectivity,
10961 index: usize,
10962 output: *mut PioBusBreakerBusView,
10963 error: *mut *mut PioError,
10964) -> bool {
10965 unsafe {
10966 entry(error, false, || {
10967 let details = require_detailed_connectivity(details)?;
10968 let bus = details.bus_breaker_buses.get(index).ok_or_else(|| {
10969 boundary_error(
10970 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
10971 format!("bus breaker bus index {index} is out of range"),
10972 )
10973 })?;
10974 *require_output(output, "output")? = PioBusBreakerBusView {
10975 component: component_id_view(&bus.component),
10976 voltage_level: component_id_view(&bus.voltage_level),
10977 calculated_bus_id: bus.calculated_bus.map_or(0, |value| value.0),
10978 has_calculated_bus: bus.calculated_bus.is_some(),
10979 voltage_kv: bus.voltage_kv.unwrap_or(0.0),
10980 has_voltage: bus.voltage_kv.is_some(),
10981 angle_degrees: bus.angle_degrees.unwrap_or(0.0),
10982 has_angle: bus.angle_degrees.is_some(),
10983 };
10984 Ok(true)
10985 })
10986 }
10987}
10988
10989#[unsafe(no_mangle)]
10994pub unsafe extern "C" fn pio_detailed_connectivity_calculated_bus_at(
10995 details: *const PioDetailedConnectivity,
10996 index: usize,
10997 output: *mut PioCalculatedBusView,
10998 error: *mut *mut PioError,
10999) -> bool {
11000 unsafe {
11001 entry(error, false, || {
11002 let details = require_detailed_connectivity(details)?;
11003 let bus = details.calculated_buses.get(index).ok_or_else(|| {
11004 boundary_error(
11005 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11006 format!("calculated bus index {index} is out of range"),
11007 )
11008 })?;
11009 *require_output(output, "output")? = PioCalculatedBusView {
11010 voltage_level: component_id_view(&bus.voltage_level),
11011 calculated_bus_id: bus.calculated_bus.0,
11012 node_count: bus.nodes.len(),
11013 voltage_kv: bus.voltage_kv.unwrap_or(0.0),
11014 has_voltage: bus.voltage_kv.is_some(),
11015 angle_degrees: bus.angle_degrees.unwrap_or(0.0),
11016 has_angle: bus.angle_degrees.is_some(),
11017 };
11018 Ok(true)
11019 })
11020 }
11021}
11022
11023#[unsafe(no_mangle)]
11028pub unsafe extern "C" fn pio_detailed_connectivity_calculated_bus_node_at(
11029 details: *const PioDetailedConnectivity,
11030 calculated_bus_index: usize,
11031 node_index: usize,
11032 output: *mut PioComponentIdView,
11033 error: *mut *mut PioError,
11034) -> bool {
11035 unsafe {
11036 entry(error, false, || {
11037 let details = require_detailed_connectivity(details)?;
11038 let bus = details
11039 .calculated_buses
11040 .get(calculated_bus_index)
11041 .ok_or_else(|| {
11042 boundary_error(
11043 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11044 format!("calculated bus index {calculated_bus_index} is out of range"),
11045 )
11046 })?;
11047 let node = bus.nodes.get(node_index).ok_or_else(|| {
11048 boundary_error(
11049 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11050 format!("calculated bus node index {node_index} is out of range"),
11051 )
11052 })?;
11053 *require_output(output, "output")? = component_id_view(node);
11054 Ok(true)
11055 })
11056 }
11057}
11058
11059#[unsafe(no_mangle)]
11064pub unsafe extern "C" fn pio_detailed_connectivity_node_at(
11065 details: *const PioDetailedConnectivity,
11066 index: usize,
11067 output: *mut PioConnectivityNodeView,
11068 error: *mut *mut PioError,
11069) -> bool {
11070 unsafe {
11071 entry(error, false, || {
11072 let details = require_detailed_connectivity(details)?;
11073 let node = details.connectivity_nodes.get(index).ok_or_else(|| {
11074 boundary_error(
11075 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11076 format!("connectivity node index {index} is out of range"),
11077 )
11078 })?;
11079 *require_output(output, "output")? = PioConnectivityNodeView {
11080 component: component_id_view(&node.component),
11081 voltage_level: component_id_view(&node.voltage_level),
11082 node_number: node.node_number.unwrap_or(0),
11083 has_node_number: node.node_number.is_some(),
11084 calculated_bus_id: node.calculated_bus.map_or(0, |bus| bus.0),
11085 has_calculated_bus: node.calculated_bus.is_some(),
11086 };
11087 Ok(true)
11088 })
11089 }
11090}
11091
11092#[unsafe(no_mangle)]
11097pub unsafe extern "C" fn pio_detailed_connectivity_busbar_section_at(
11098 details: *const PioDetailedConnectivity,
11099 index: usize,
11100 output: *mut PioBusbarSectionView,
11101 error: *mut *mut PioError,
11102) -> bool {
11103 unsafe {
11104 entry(error, false, || {
11105 let details = require_detailed_connectivity(details)?;
11106 let section = details.busbar_sections.get(index).ok_or_else(|| {
11107 boundary_error(
11108 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11109 format!("busbar section index {index} is out of range"),
11110 )
11111 })?;
11112 *require_output(output, "output")? = PioBusbarSectionView {
11113 component: component_id_view(§ion.component),
11114 voltage_level: component_id_view(§ion.voltage_level),
11115 node: component_id_view(§ion.node),
11116 };
11117 Ok(true)
11118 })
11119 }
11120}
11121
11122#[unsafe(no_mangle)]
11127pub unsafe extern "C" fn pio_detailed_connectivity_junction_at(
11128 details: *const PioDetailedConnectivity,
11129 index: usize,
11130 output: *mut PioJunctionView,
11131 error: *mut *mut PioError,
11132) -> bool {
11133 unsafe {
11134 entry(error, false, || {
11135 let details = require_detailed_connectivity(details)?;
11136 let junction = details.junctions.get(index).ok_or_else(|| {
11137 boundary_error(
11138 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11139 format!("junction index {index} is out of range"),
11140 )
11141 })?;
11142 *require_output(output, "output")? = PioJunctionView {
11143 component: component_id_view(&junction.component),
11144 };
11145 Ok(true)
11146 })
11147 }
11148}
11149
11150#[unsafe(no_mangle)]
11155pub unsafe extern "C" fn pio_detailed_connectivity_terminal_at(
11156 details: *const PioDetailedConnectivity,
11157 index: usize,
11158 output: *mut PioDetailedTerminalView,
11159 error: *mut *mut PioError,
11160) -> bool {
11161 unsafe {
11162 entry(error, false, || {
11163 let details = require_detailed_connectivity(details)?;
11164 let terminal = details.terminals.get(index).ok_or_else(|| {
11165 boundary_error(
11166 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11167 format!("terminal index {index} is out of range"),
11168 )
11169 })?;
11170 let (bus, has_bus) = optional_component_id_view(terminal.bus.as_ref());
11171 let (connectable_bus, has_connectable_bus) =
11172 optional_component_id_view(terminal.connectable_bus.as_ref());
11173 let (node, has_node) = optional_component_id_view(terminal.node.as_ref());
11174 let (component, has_component) =
11175 optional_component_id_view(terminal.component.as_ref());
11176 *require_output(output, "output")? = PioDetailedTerminalView {
11177 component,
11178 has_component,
11179 equipment: component_id_view(&terminal.equipment),
11180 terminal: terminal.terminal,
11181 voltage_level: component_id_view(&terminal.voltage_level),
11182 bus,
11183 has_bus,
11184 connectable_bus,
11185 has_connectable_bus,
11186 node,
11187 has_node,
11188 connected: terminal.connected,
11189 active_power_mw: terminal.active_power_mw.unwrap_or(0.0),
11190 has_active_power: terminal.active_power_mw.is_some(),
11191 reactive_power_mvar: terminal.reactive_power_mvar.unwrap_or(0.0),
11192 has_reactive_power: terminal.reactive_power_mvar.is_some(),
11193 };
11194 Ok(true)
11195 })
11196 }
11197}
11198
11199#[unsafe(no_mangle)]
11204pub unsafe extern "C" fn pio_detailed_connectivity_switch_at(
11205 details: *const PioDetailedConnectivity,
11206 index: usize,
11207 output: *mut PioTopologySwitchView,
11208 error: *mut *mut PioError,
11209) -> bool {
11210 unsafe {
11211 entry(error, false, || {
11212 let details = require_detailed_connectivity(details)?;
11213 let switch = details.switches.get(index).ok_or_else(|| {
11214 boundary_error(
11215 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11216 format!("topology switch index {index} is out of range"),
11217 )
11218 })?;
11219 let (endpoint1_kind, endpoint1) = topology_endpoint_view(&switch.endpoint1);
11220 let (endpoint2_kind, endpoint2) = topology_endpoint_view(&switch.endpoint2);
11221 *require_output(output, "output")? = PioTopologySwitchView {
11222 component: component_id_view(&switch.component),
11223 voltage_level: component_id_view(&switch.voltage_level),
11224 kind: PioStringView::new(topology_switch_kind_name(switch.kind)),
11225 endpoint1_kind,
11226 endpoint1,
11227 endpoint2_kind,
11228 endpoint2,
11229 open: switch.open,
11230 retained: switch.retained,
11231 };
11232 Ok(true)
11233 })
11234 }
11235}
11236
11237#[unsafe(no_mangle)]
11242pub unsafe extern "C" fn pio_detailed_connectivity_internal_connection_at(
11243 details: *const PioDetailedConnectivity,
11244 index: usize,
11245 output: *mut PioInternalConnectionView,
11246 error: *mut *mut PioError,
11247) -> bool {
11248 unsafe {
11249 entry(error, false, || {
11250 let details = require_detailed_connectivity(details)?;
11251 let connection = details.internal_connections.get(index).ok_or_else(|| {
11252 boundary_error(
11253 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11254 format!("internal connection index {index} is out of range"),
11255 )
11256 })?;
11257 *require_output(output, "output")? = PioInternalConnectionView {
11258 voltage_level: component_id_view(&connection.voltage_level),
11259 node1: component_id_view(&connection.node1),
11260 node2: component_id_view(&connection.node2),
11261 };
11262 Ok(true)
11263 })
11264 }
11265}
11266
11267#[unsafe(no_mangle)]
11272pub unsafe extern "C" fn pio_detailed_connectivity_operational_limit_group_at(
11273 details: *const PioDetailedConnectivity,
11274 index: usize,
11275 output: *mut PioOperationalLimitGroupView,
11276 error: *mut *mut PioError,
11277) -> bool {
11278 unsafe {
11279 entry(error, false, || {
11280 let details = require_detailed_connectivity(details)?;
11281 let group = details.operational_limit_groups.get(index).ok_or_else(|| {
11282 boundary_error(
11283 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11284 format!("operational limit group index {index} is out of range"),
11285 )
11286 })?;
11287 let current = group.current_limits.as_ref();
11288 let active = group.active_power_limits.as_ref();
11289 let apparent = group.apparent_power_limits.as_ref();
11290 let (current_name, has_current_name) = optional_string_view(
11291 current.and_then(|limits| limits.permanent_limit_name.as_deref()),
11292 );
11293 let (active_name, has_active_name) = optional_string_view(
11294 active.and_then(|limits| limits.permanent_limit_name.as_deref()),
11295 );
11296 let (apparent_name, has_apparent_name) = optional_string_view(
11297 apparent.and_then(|limits| limits.permanent_limit_name.as_deref()),
11298 );
11299 *require_output(output, "output")? = PioOperationalLimitGroupView {
11300 equipment: component_id_view(&group.equipment),
11301 terminal: group.terminal,
11302 id: PioStringView::new(&group.id),
11303 selected: group.selected,
11304 property_count: group.properties.len(),
11305 has_current_limits: current.is_some(),
11306 current_permanent_limit_a: current
11307 .and_then(|limits| limits.permanent_limit)
11308 .unwrap_or(0.0),
11309 current_permanent_limit_name: current_name,
11310 has_current_permanent_limit: current
11311 .is_some_and(|limits| limits.permanent_limit.is_some()),
11312 has_current_permanent_limit_name: has_current_name,
11313 current_temporary_limit_count: current
11314 .map_or(0, |limits| limits.temporary_limits.len()),
11315 has_active_power_limits: active.is_some(),
11316 active_power_permanent_limit_mw: active
11317 .and_then(|limits| limits.permanent_limit)
11318 .unwrap_or(0.0),
11319 active_power_permanent_limit_name: active_name,
11320 has_active_power_permanent_limit: active
11321 .is_some_and(|limits| limits.permanent_limit.is_some()),
11322 has_active_power_permanent_limit_name: has_active_name,
11323 active_power_temporary_limit_count: active
11324 .map_or(0, |limits| limits.temporary_limits.len()),
11325 has_apparent_power_limits: apparent.is_some(),
11326 apparent_power_permanent_limit_mva: apparent
11327 .and_then(|limits| limits.permanent_limit)
11328 .unwrap_or(0.0),
11329 apparent_power_permanent_limit_name: apparent_name,
11330 has_apparent_power_permanent_limit: apparent
11331 .is_some_and(|limits| limits.permanent_limit.is_some()),
11332 has_apparent_power_permanent_limit_name: has_apparent_name,
11333 apparent_power_temporary_limit_count: apparent
11334 .map_or(0, |limits| limits.temporary_limits.len()),
11335 };
11336 Ok(true)
11337 })
11338 }
11339}
11340
11341#[unsafe(no_mangle)]
11346pub unsafe extern "C" fn pio_detailed_connectivity_operational_limit_group_property_at(
11347 details: *const PioDetailedConnectivity,
11348 group_index: usize,
11349 property_index: usize,
11350 output: *mut PioStringPropertyView,
11351 error: *mut *mut PioError,
11352) -> bool {
11353 unsafe {
11354 entry(error, false, || {
11355 let details = require_detailed_connectivity(details)?;
11356 let group = details
11357 .operational_limit_groups
11358 .get(group_index)
11359 .ok_or_else(|| {
11360 boundary_error(
11361 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11362 format!("operational limit group index {group_index} is out of range"),
11363 )
11364 })?;
11365 *require_output(output, "output")? =
11366 string_property_view(&group.properties, property_index)?;
11367 Ok(true)
11368 })
11369 }
11370}
11371
11372#[unsafe(no_mangle)]
11377pub unsafe extern "C" fn pio_detailed_connectivity_temporary_limit_at(
11378 details: *const PioDetailedConnectivity,
11379 group_index: usize,
11380 quantity: *const c_char,
11381 quantity_len: usize,
11382 limit_index: usize,
11383 output: *mut PioTemporaryLimitView,
11384 error: *mut *mut PioError,
11385) -> bool {
11386 unsafe {
11387 entry(error, false, || {
11388 let details = require_detailed_connectivity(details)?;
11389 let group = details
11390 .operational_limit_groups
11391 .get(group_index)
11392 .ok_or_else(|| {
11393 boundary_error(
11394 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11395 format!("operational limit group index {group_index} is out of range"),
11396 )
11397 })?;
11398 let quantity = required_str(quantity, quantity_len, "quantity")?;
11399 let limits = match quantity {
11400 "current" => group.current_limits.as_ref(),
11401 "active_power" => group.active_power_limits.as_ref(),
11402 "apparent_power" => group.apparent_power_limits.as_ref(),
11403 _ => {
11404 return Err(boundary_error(
11405 &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
11406 "quantity must be current, active_power, or apparent_power",
11407 ));
11408 }
11409 }
11410 .ok_or_else(|| {
11411 boundary_error(
11412 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11413 format!("operational limit group {group_index} has no {quantity} limits"),
11414 )
11415 })?;
11416 let limit = limits.temporary_limits.get(limit_index).ok_or_else(|| {
11417 boundary_error(
11418 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11419 format!("temporary limit index {limit_index} is out of range"),
11420 )
11421 })?;
11422 *require_output(output, "output")? = PioTemporaryLimitView {
11423 name: PioStringView::new(&limit.name),
11424 value: limit.value,
11425 acceptable_duration_seconds: limit.acceptable_duration_seconds,
11426 fictitious: limit.fictitious,
11427 };
11428 Ok(true)
11429 })
11430 }
11431}
11432
11433#[unsafe(no_mangle)]
11438pub unsafe extern "C" fn pio_detailed_connectivity_boundary_line_at(
11439 details: *const PioDetailedConnectivity,
11440 index: usize,
11441 output: *mut PioBoundaryLineView,
11442 error: *mut *mut PioError,
11443) -> bool {
11444 unsafe {
11445 entry(error, false, || {
11446 let details = require_detailed_connectivity(details)?;
11447 let line = details.boundary_lines.get(index).ok_or_else(|| {
11448 boundary_error(
11449 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11450 format!("boundary line index {index} is out of range"),
11451 )
11452 })?;
11453 let (pairing_key, has_pairing_key) = optional_string_view(line.pairing_key.as_deref());
11454 let (generation, has_generation) =
11455 boundary_line_generation_view(line.generation.as_ref());
11456 let (calculation_load, has_calculation_load) =
11457 optional_component_id_view(line.calculation_load.as_ref());
11458 let (calculation_generator, has_calculation_generator) =
11459 optional_component_id_view(line.calculation_generator.as_ref());
11460 *require_output(output, "output")? = PioBoundaryLineView {
11461 component: component_id_view(&line.component),
11462 voltage_level: component_id_view(&line.voltage_level),
11463 active_power_setpoint_mw: line.active_power_setpoint_mw,
11464 reactive_power_setpoint_mvar: line.reactive_power_setpoint_mvar,
11465 resistance_ohm: line.resistance_ohm,
11466 reactance_ohm: line.reactance_ohm,
11467 conductance_siemens: line.conductance_siemens,
11468 susceptance_siemens: line.susceptance_siemens,
11469 pairing_key,
11470 has_pairing_key,
11471 generation,
11472 has_generation,
11473 calculation_load,
11474 has_calculation_load,
11475 calculation_generator,
11476 has_calculation_generator,
11477 };
11478 Ok(true)
11479 })
11480 }
11481}
11482
11483#[unsafe(no_mangle)]
11488pub unsafe extern "C" fn pio_detailed_connectivity_boundary_line_reactive_limit_property_at(
11489 details: *const PioDetailedConnectivity,
11490 boundary_line_index: usize,
11491 property_index: usize,
11492 output: *mut PioStringPropertyView,
11493 error: *mut *mut PioError,
11494) -> bool {
11495 unsafe {
11496 entry(error, false, || {
11497 let details = require_detailed_connectivity(details)?;
11498 let line = details
11499 .boundary_lines
11500 .get(boundary_line_index)
11501 .ok_or_else(|| {
11502 boundary_error(
11503 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11504 format!("boundary line index {boundary_line_index} is out of range"),
11505 )
11506 })?;
11507 let limits = line
11508 .generation
11509 .as_ref()
11510 .and_then(|generation| generation.reactive_limits.as_ref())
11511 .ok_or_else(|| {
11512 boundary_error(
11513 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11514 format!(
11515 "boundary line {boundary_line_index} has no generation reactive limits"
11516 ),
11517 )
11518 })?;
11519 let properties = match limits {
11520 powerio_tx::ReactiveLimits::MinMax(limits) => &limits.properties,
11521 powerio_tx::ReactiveLimits::CapabilityCurve(curve) => &curve.properties,
11522 _ => {
11523 return Err(boundary_error(
11524 &codes::REQUEST_CAPI_TYPE_MISMATCH,
11525 "unsupported reactive limit record",
11526 ));
11527 }
11528 };
11529 let (name, value) = properties.iter().nth(property_index).ok_or_else(|| {
11530 boundary_error(
11531 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11532 format!("reactive limit property index {property_index} is out of range"),
11533 )
11534 })?;
11535 *require_output(output, "output")? = PioStringPropertyView {
11536 name: PioStringView::new(name),
11537 value: PioStringView::new(value),
11538 };
11539 Ok(true)
11540 })
11541 }
11542}
11543
11544#[unsafe(no_mangle)]
11549pub unsafe extern "C" fn pio_detailed_connectivity_boundary_line_reactive_capability_point_at(
11550 details: *const PioDetailedConnectivity,
11551 boundary_line_index: usize,
11552 point_index: usize,
11553 output: *mut PioReactiveCapabilityCurvePointView,
11554 error: *mut *mut PioError,
11555) -> bool {
11556 unsafe {
11557 entry(error, false, || {
11558 let details = require_detailed_connectivity(details)?;
11559 let line = details
11560 .boundary_lines
11561 .get(boundary_line_index)
11562 .ok_or_else(|| {
11563 boundary_error(
11564 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11565 format!("boundary line index {boundary_line_index} is out of range"),
11566 )
11567 })?;
11568 let curve = line
11569 .generation
11570 .as_ref()
11571 .and_then(|generation| generation.reactive_limits.as_ref())
11572 .and_then(|limits| match limits {
11573 powerio_tx::ReactiveLimits::CapabilityCurve(curve) => Some(curve),
11574 _ => None,
11575 })
11576 .ok_or_else(|| {
11577 boundary_error(
11578 &codes::REQUEST_CAPI_TYPE_MISMATCH,
11579 format!(
11580 "boundary line {boundary_line_index} has no generation reactive capability curve"
11581 ),
11582 )
11583 })?;
11584 let point = curve.points.get(point_index).ok_or_else(|| {
11585 boundary_error(
11586 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11587 format!("reactive capability point index {point_index} is out of range"),
11588 )
11589 })?;
11590 *require_output(output, "output")? = PioReactiveCapabilityCurvePointView {
11591 active_power_mw: point.active_power_mw,
11592 minimum_reactive_power_mvar: point.minimum_reactive_power_mvar,
11593 maximum_reactive_power_mvar: point.maximum_reactive_power_mvar,
11594 property_count: point.properties.len(),
11595 };
11596 Ok(true)
11597 })
11598 }
11599}
11600
11601#[unsafe(no_mangle)]
11606pub unsafe extern "C" fn pio_detailed_connectivity_boundary_line_reactive_capability_point_property_at(
11607 details: *const PioDetailedConnectivity,
11608 boundary_line_index: usize,
11609 point_index: usize,
11610 property_index: usize,
11611 output: *mut PioStringPropertyView,
11612 error: *mut *mut PioError,
11613) -> bool {
11614 unsafe {
11615 entry(error, false, || {
11616 let details = require_detailed_connectivity(details)?;
11617 let line = details
11618 .boundary_lines
11619 .get(boundary_line_index)
11620 .ok_or_else(|| {
11621 boundary_error(
11622 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11623 format!("boundary line index {boundary_line_index} is out of range"),
11624 )
11625 })?;
11626 let curve = line
11627 .generation
11628 .as_ref()
11629 .and_then(|generation| generation.reactive_limits.as_ref())
11630 .and_then(|limits| match limits {
11631 powerio_tx::ReactiveLimits::CapabilityCurve(curve) => Some(curve),
11632 _ => None,
11633 })
11634 .ok_or_else(|| {
11635 boundary_error(
11636 &codes::REQUEST_CAPI_TYPE_MISMATCH,
11637 format!(
11638 "boundary line {boundary_line_index} has no generation reactive capability curve"
11639 ),
11640 )
11641 })?;
11642 let point = curve.points.get(point_index).ok_or_else(|| {
11643 boundary_error(
11644 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11645 format!("reactive capability point index {point_index} is out of range"),
11646 )
11647 })?;
11648 let (name, value) = point.properties.iter().nth(property_index).ok_or_else(|| {
11649 boundary_error(
11650 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11651 format!(
11652 "reactive capability point property index {property_index} is out of range"
11653 ),
11654 )
11655 })?;
11656 *require_output(output, "output")? = PioStringPropertyView {
11657 name: PioStringView::new(name),
11658 value: PioStringView::new(value),
11659 };
11660 Ok(true)
11661 })
11662 }
11663}
11664
11665#[unsafe(no_mangle)]
11670pub unsafe extern "C" fn pio_detailed_connectivity_tie_line_at(
11671 details: *const PioDetailedConnectivity,
11672 index: usize,
11673 output: *mut PioTieLineView,
11674 error: *mut *mut PioError,
11675) -> bool {
11676 unsafe {
11677 entry(error, false, || {
11678 let details = require_detailed_connectivity(details)?;
11679 let tie = details.tie_lines.get(index).ok_or_else(|| {
11680 boundary_error(
11681 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11682 format!("tie line index {index} is out of range"),
11683 )
11684 })?;
11685 let (calculation_branch, has_calculation_branch) =
11686 optional_component_id_view(tie.calculation_branch.as_ref());
11687 *require_output(output, "output")? = PioTieLineView {
11688 component: component_id_view(&tie.component),
11689 boundary_line1: component_id_view(&tie.boundary_line1),
11690 boundary_line2: component_id_view(&tie.boundary_line2),
11691 calculation_branch,
11692 has_calculation_branch,
11693 };
11694 Ok(true)
11695 })
11696 }
11697}
11698
11699#[unsafe(no_mangle)]
11704pub unsafe extern "C" fn pio_detailed_connectivity_tap_changer_at(
11705 details: *const PioDetailedConnectivity,
11706 index: usize,
11707 output: *mut PioTapChangerView,
11708 error: *mut *mut PioError,
11709) -> bool {
11710 unsafe {
11711 entry(error, false, || {
11712 let details = require_detailed_connectivity(details)?;
11713 let changer = details.tap_changers.get(index).ok_or_else(|| {
11714 boundary_error(
11715 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11716 format!("tap changer index {index} is out of range"),
11717 )
11718 })?;
11719 let (regulation_mode, has_regulation_mode) =
11720 changer
11721 .regulation_mode
11722 .map_or((PioStringView::EMPTY, false), |mode| {
11723 (
11724 PioStringView::new(tap_changer_regulation_mode_name(mode)),
11725 true,
11726 )
11727 });
11728 let (regulation_terminal, has_regulation_terminal) =
11729 terminal_reference_view(changer.regulation_terminal.as_ref());
11730 let (component, has_component) = optional_component_id_view(changer.component.as_ref());
11731 *require_output(output, "output")? = PioTapChangerView {
11732 component,
11733 has_component,
11734 transformer: component_id_view(&changer.transformer),
11735 winding: changer.winding,
11736 kind: PioStringView::new(tap_changer_kind_name(changer.kind)),
11737 tap_position: changer.tap_position.unwrap_or(0),
11738 has_tap_position: changer.tap_position.is_some(),
11739 solved_tap_position: changer.solved_tap_position.unwrap_or(0),
11740 has_solved_tap_position: changer.solved_tap_position.is_some(),
11741 low_tap_position: changer.low_tap_position,
11742 neutral_tap_position: changer.neutral_tap_position.unwrap_or(0),
11743 has_neutral_tap_position: changer.neutral_tap_position.is_some(),
11744 normal_tap_position: changer.normal_tap_position.unwrap_or(0),
11745 has_normal_tap_position: changer.normal_tap_position.is_some(),
11746 voltage_step_increment_percent: changer
11747 .voltage_step_increment_percent
11748 .unwrap_or(0.0),
11749 has_voltage_step_increment_percent: changer
11750 .voltage_step_increment_percent
11751 .is_some(),
11752 load_tap_changing_capabilities: changer.load_tap_changing_capabilities,
11753 regulating: changer.regulating,
11754 regulation_mode,
11755 has_regulation_mode,
11756 regulation_value: changer.regulation_value.unwrap_or(0.0),
11757 has_regulation_value: changer.regulation_value.is_some(),
11758 target_deadband: changer.target_deadband.unwrap_or(0.0),
11759 has_target_deadband: changer.target_deadband.is_some(),
11760 regulation_terminal,
11761 has_regulation_terminal,
11762 step_count: changer.steps.len(),
11763 };
11764 Ok(true)
11765 })
11766 }
11767}
11768
11769#[unsafe(no_mangle)]
11774pub unsafe extern "C" fn pio_detailed_connectivity_tap_changer_step_at(
11775 details: *const PioDetailedConnectivity,
11776 tap_changer_index: usize,
11777 step_index: usize,
11778 output: *mut PioTapChangerStepView,
11779 error: *mut *mut PioError,
11780) -> bool {
11781 unsafe {
11782 entry(error, false, || {
11783 let details = require_detailed_connectivity(details)?;
11784 let changer = details.tap_changers.get(tap_changer_index).ok_or_else(|| {
11785 boundary_error(
11786 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11787 format!("tap changer index {tap_changer_index} is out of range"),
11788 )
11789 })?;
11790 let step = changer.steps.get(step_index).ok_or_else(|| {
11791 boundary_error(
11792 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11793 format!("tap changer step index {step_index} is out of range"),
11794 )
11795 })?;
11796 *require_output(output, "output")? = PioTapChangerStepView {
11797 position: step.position,
11798 ratio_pu: step.rho,
11799 phase_shift_degrees: step.alpha_degrees,
11800 resistance_deviation_percent: step.resistance_deviation_percent,
11801 reactance_deviation_percent: step.reactance_deviation_percent,
11802 conductance_deviation_percent: step.conductance_deviation_percent,
11803 susceptance_deviation_percent: step.susceptance_deviation_percent,
11804 };
11805 Ok(true)
11806 })
11807 }
11808}
11809
11810#[unsafe(no_mangle)]
11815pub unsafe extern "C" fn pio_detailed_connectivity_equipment_reactive_limits_at(
11816 details: *const PioDetailedConnectivity,
11817 index: usize,
11818 output: *mut PioEquipmentReactiveLimitsView,
11819 error: *mut *mut PioError,
11820) -> bool {
11821 unsafe {
11822 entry(error, false, || {
11823 let details = require_detailed_connectivity(details)?;
11824 let record = details
11825 .equipment_reactive_limits
11826 .get(index)
11827 .ok_or_else(|| {
11828 boundary_error(
11829 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11830 format!("equipment reactive limits index {index} is out of range"),
11831 )
11832 })?;
11833 *require_output(output, "output")? = PioEquipmentReactiveLimitsView {
11834 equipment: component_id_view(&record.equipment),
11835 limits: reactive_limits_view(Some(&record.limits)).0,
11836 };
11837 Ok(true)
11838 })
11839 }
11840}
11841
11842#[unsafe(no_mangle)]
11847pub unsafe extern "C" fn pio_detailed_connectivity_equipment_reactive_limit_property_at(
11848 details: *const PioDetailedConnectivity,
11849 equipment_index: usize,
11850 property_index: usize,
11851 output: *mut PioStringPropertyView,
11852 error: *mut *mut PioError,
11853) -> bool {
11854 unsafe {
11855 entry(error, false, || {
11856 let details = require_detailed_connectivity(details)?;
11857 let record = details
11858 .equipment_reactive_limits
11859 .get(equipment_index)
11860 .ok_or_else(|| {
11861 boundary_error(
11862 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11863 format!(
11864 "equipment reactive limits index {equipment_index} is out of range"
11865 ),
11866 )
11867 })?;
11868 *require_output(output, "output")? =
11869 string_property_view(reactive_limit_properties(&record.limits)?, property_index)?;
11870 Ok(true)
11871 })
11872 }
11873}
11874
11875#[unsafe(no_mangle)]
11880pub unsafe extern "C" fn pio_detailed_connectivity_equipment_reactive_capability_point_at(
11881 details: *const PioDetailedConnectivity,
11882 equipment_index: usize,
11883 point_index: usize,
11884 output: *mut PioReactiveCapabilityCurvePointView,
11885 error: *mut *mut PioError,
11886) -> bool {
11887 unsafe {
11888 entry(error, false, || {
11889 let details = require_detailed_connectivity(details)?;
11890 let record = details
11891 .equipment_reactive_limits
11892 .get(equipment_index)
11893 .ok_or_else(|| {
11894 boundary_error(
11895 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11896 format!(
11897 "equipment reactive limits index {equipment_index} is out of range"
11898 ),
11899 )
11900 })?;
11901 let curve = reactive_capability_curve(&record.limits).ok_or_else(|| {
11902 boundary_error(
11903 &codes::REQUEST_CAPI_TYPE_MISMATCH,
11904 format!(
11905 "equipment reactive limits {equipment_index} is not a capability curve"
11906 ),
11907 )
11908 })?;
11909 *require_output(output, "output")? =
11910 reactive_capability_point_view(curve, point_index)?;
11911 Ok(true)
11912 })
11913 }
11914}
11915
11916#[unsafe(no_mangle)]
11921pub unsafe extern "C" fn pio_detailed_connectivity_equipment_reactive_capability_point_property_at(
11922 details: *const PioDetailedConnectivity,
11923 equipment_index: usize,
11924 point_index: usize,
11925 property_index: usize,
11926 output: *mut PioStringPropertyView,
11927 error: *mut *mut PioError,
11928) -> bool {
11929 unsafe {
11930 entry(error, false, || {
11931 let details = require_detailed_connectivity(details)?;
11932 let record = details
11933 .equipment_reactive_limits
11934 .get(equipment_index)
11935 .ok_or_else(|| {
11936 boundary_error(
11937 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11938 format!(
11939 "equipment reactive limits index {equipment_index} is out of range"
11940 ),
11941 )
11942 })?;
11943 let curve = reactive_capability_curve(&record.limits).ok_or_else(|| {
11944 boundary_error(
11945 &codes::REQUEST_CAPI_TYPE_MISMATCH,
11946 format!(
11947 "equipment reactive limits {equipment_index} is not a capability curve"
11948 ),
11949 )
11950 })?;
11951 let point = curve.points.get(point_index).ok_or_else(|| {
11952 boundary_error(
11953 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11954 format!("reactive capability point index {point_index} is out of range"),
11955 )
11956 })?;
11957 *require_output(output, "output")? =
11958 string_property_view(&point.properties, property_index)?;
11959 Ok(true)
11960 })
11961 }
11962}
11963
11964#[unsafe(no_mangle)]
11969pub unsafe extern "C" fn pio_detailed_connectivity_dc_converter_unit_at(
11970 details: *const PioDetailedConnectivity,
11971 index: usize,
11972 output: *mut PioDcConverterUnitView,
11973 error: *mut *mut PioError,
11974) -> bool {
11975 unsafe {
11976 entry(error, false, || {
11977 let details = require_detailed_connectivity(details)?;
11978 let unit = details.dc_converter_units.get(index).ok_or_else(|| {
11979 boundary_error(
11980 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
11981 format!("DC converter unit index {index} is out of range"),
11982 )
11983 })?;
11984 let (substation, has_substation) = optional_component_id_view(unit.substation.as_ref());
11985 *require_output(output, "output")? = PioDcConverterUnitView {
11986 component: component_id_view(&unit.component),
11987 substation,
11988 has_substation,
11989 operation_mode: PioStringView::new(dc_converter_operation_mode_name(
11990 unit.operation_mode,
11991 )),
11992 };
11993 Ok(true)
11994 })
11995 }
11996}
11997
11998#[unsafe(no_mangle)]
12003pub unsafe extern "C" fn pio_detailed_connectivity_dc_topological_node_at(
12004 details: *const PioDetailedConnectivity,
12005 index: usize,
12006 output: *mut PioDcNodeView,
12007 error: *mut *mut PioError,
12008) -> bool {
12009 unsafe {
12010 entry(error, false, || {
12011 let details = require_detailed_connectivity(details)?;
12012 let node = details.dc_topological_nodes.get(index).ok_or_else(|| {
12013 boundary_error(
12014 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12015 format!("DC topological node index {index} is out of range"),
12016 )
12017 })?;
12018 let (dc_converter_unit, has_dc_converter_unit) =
12019 optional_component_id_view(node.dc_converter_unit.as_ref());
12020 *require_output(output, "output")? = PioDcNodeView {
12021 component: component_id_view(&node.component),
12022 kind: PioStringView::new("topological_node"),
12023 nominal_voltage_kv: 0.0,
12024 has_nominal_voltage: false,
12025 voltage_kv: 0.0,
12026 has_voltage: false,
12027 dc_converter_unit,
12028 has_dc_converter_unit,
12029 dc_topological_node: empty_component_id_view(),
12030 has_dc_topological_node: false,
12031 };
12032 Ok(true)
12033 })
12034 }
12035}
12036
12037#[unsafe(no_mangle)]
12042pub unsafe extern "C" fn pio_detailed_connectivity_dc_node_at(
12043 details: *const PioDetailedConnectivity,
12044 index: usize,
12045 output: *mut PioDcNodeView,
12046 error: *mut *mut PioError,
12047) -> bool {
12048 unsafe {
12049 entry(error, false, || {
12050 let details = require_detailed_connectivity(details)?;
12051 let node = details.dc_nodes.get(index).ok_or_else(|| {
12052 boundary_error(
12053 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12054 format!("DC node index {index} is out of range"),
12055 )
12056 })?;
12057 let (dc_converter_unit, has_dc_converter_unit) =
12058 optional_component_id_view(node.dc_converter_unit.as_ref());
12059 let (dc_topological_node, has_dc_topological_node) =
12060 optional_component_id_view(node.dc_topological_node.as_ref());
12061 *require_output(output, "output")? = PioDcNodeView {
12062 component: component_id_view(&node.component),
12063 kind: PioStringView::new("node"),
12064 nominal_voltage_kv: node.nominal_voltage_kv.unwrap_or(0.0),
12065 has_nominal_voltage: node.nominal_voltage_kv.is_some(),
12066 voltage_kv: node.voltage_kv.unwrap_or(0.0),
12067 has_voltage: node.voltage_kv.is_some(),
12068 dc_converter_unit,
12069 has_dc_converter_unit,
12070 dc_topological_node,
12071 has_dc_topological_node,
12072 };
12073 Ok(true)
12074 })
12075 }
12076}
12077
12078#[unsafe(no_mangle)]
12083pub unsafe extern "C" fn pio_detailed_connectivity_dc_ground_at(
12084 details: *const PioDetailedConnectivity,
12085 index: usize,
12086 output: *mut PioDcEquipmentView,
12087 error: *mut *mut PioError,
12088) -> bool {
12089 unsafe {
12090 entry(error, false, || {
12091 let details = require_detailed_connectivity(details)?;
12092 let record = details.dc_grounds.get(index).ok_or_else(|| {
12093 boundary_error(
12094 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12095 format!("DC ground index {index} is out of range"),
12096 )
12097 })?;
12098 *require_output(output, "output")? = dc_equipment_view(
12099 &record.component,
12100 record.equipment_container.as_ref(),
12101 "ground",
12102 &record.dc_terminal,
12103 None,
12104 record.rated_dc_voltage_kv,
12105 record.resistance_ohm,
12106 record.inductance_h,
12107 None,
12108 None,
12109 None,
12110 None,
12111 );
12112 Ok(true)
12113 })
12114 }
12115}
12116
12117#[unsafe(no_mangle)]
12122pub unsafe extern "C" fn pio_detailed_connectivity_dc_busbar_at(
12123 details: *const PioDetailedConnectivity,
12124 index: usize,
12125 output: *mut PioDcEquipmentView,
12126 error: *mut *mut PioError,
12127) -> bool {
12128 unsafe {
12129 entry(error, false, || {
12130 let details = require_detailed_connectivity(details)?;
12131 let record = details.dc_busbars.get(index).ok_or_else(|| {
12132 boundary_error(
12133 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12134 format!("DC busbar index {index} is out of range"),
12135 )
12136 })?;
12137 *require_output(output, "output")? = dc_equipment_view(
12138 &record.component,
12139 record.equipment_container.as_ref(),
12140 "busbar",
12141 &record.dc_terminal,
12142 None,
12143 record.rated_dc_voltage_kv,
12144 None,
12145 None,
12146 None,
12147 None,
12148 None,
12149 None,
12150 );
12151 Ok(true)
12152 })
12153 }
12154}
12155
12156#[unsafe(no_mangle)]
12161pub unsafe extern "C" fn pio_detailed_connectivity_dc_line_at(
12162 details: *const PioDetailedConnectivity,
12163 index: usize,
12164 output: *mut PioDcEquipmentView,
12165 error: *mut *mut PioError,
12166) -> bool {
12167 unsafe {
12168 entry(error, false, || {
12169 let details = require_detailed_connectivity(details)?;
12170 let record = details.dc_lines.get(index).ok_or_else(|| {
12171 boundary_error(
12172 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12173 format!("DC line index {index} is out of range"),
12174 )
12175 })?;
12176 *require_output(output, "output")? = dc_equipment_view(
12177 &record.component,
12178 record.equipment_container.as_ref(),
12179 "line",
12180 &record.dc_terminal1,
12181 Some(&record.dc_terminal2),
12182 record.rated_dc_voltage_kv,
12183 record.resistance_ohm,
12184 record.inductance_h,
12185 record.capacitance_f,
12186 record.length_km,
12187 None,
12188 None,
12189 );
12190 Ok(true)
12191 })
12192 }
12193}
12194
12195#[unsafe(no_mangle)]
12200pub unsafe extern "C" fn pio_detailed_connectivity_dc_series_device_at(
12201 details: *const PioDetailedConnectivity,
12202 index: usize,
12203 output: *mut PioDcEquipmentView,
12204 error: *mut *mut PioError,
12205) -> bool {
12206 unsafe {
12207 entry(error, false, || {
12208 let details = require_detailed_connectivity(details)?;
12209 let record = details.dc_series_devices.get(index).ok_or_else(|| {
12210 boundary_error(
12211 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12212 format!("DC series device index {index} is out of range"),
12213 )
12214 })?;
12215 *require_output(output, "output")? = dc_equipment_view(
12216 &record.component,
12217 record.equipment_container.as_ref(),
12218 "series_device",
12219 &record.dc_terminal1,
12220 Some(&record.dc_terminal2),
12221 record.rated_dc_voltage_kv,
12222 record.resistance_ohm,
12223 record.inductance_h,
12224 None,
12225 None,
12226 None,
12227 None,
12228 );
12229 Ok(true)
12230 })
12231 }
12232}
12233
12234#[unsafe(no_mangle)]
12239pub unsafe extern "C" fn pio_detailed_connectivity_dc_switch_at(
12240 details: *const PioDetailedConnectivity,
12241 index: usize,
12242 output: *mut PioDcEquipmentView,
12243 error: *mut *mut PioError,
12244) -> bool {
12245 unsafe {
12246 entry(error, false, || {
12247 let details = require_detailed_connectivity(details)?;
12248 let record = details.dc_switches.get(index).ok_or_else(|| {
12249 boundary_error(
12250 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12251 format!("DC switch index {index} is out of range"),
12252 )
12253 })?;
12254 *require_output(output, "output")? = dc_equipment_view(
12255 &record.component,
12256 record.equipment_container.as_ref(),
12257 "switch",
12258 &record.dc_terminal1,
12259 Some(&record.dc_terminal2),
12260 record.rated_dc_voltage_kv,
12261 record.resistance_ohm,
12262 None,
12263 None,
12264 None,
12265 Some(record.kind),
12266 record.open,
12267 );
12268 Ok(true)
12269 })
12270 }
12271}
12272
12273#[unsafe(no_mangle)]
12278pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_at(
12279 details: *const PioDetailedConnectivity,
12280 index: usize,
12281 output: *mut PioAcDcConverterView,
12282 error: *mut *mut PioError,
12283) -> bool {
12284 unsafe {
12285 entry(error, false, || {
12286 let details = require_detailed_connectivity(details)?;
12287 let converter = details
12288 .voltage_source_converters
12289 .get(index)
12290 .ok_or_else(|| {
12291 boundary_error(
12292 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12293 format!("voltage source converter index {index} is out of range"),
12294 )
12295 })?;
12296 let (dc_converter_unit, has_dc_converter_unit) =
12297 optional_component_id_view(converter.dc_converter_unit.as_ref());
12298 let (control_mode, has_control_mode) =
12299 converter
12300 .control_mode
12301 .map_or((PioStringView::EMPTY, false), |mode| {
12302 (
12303 PioStringView::new(ac_dc_converter_control_mode_name(mode)),
12304 true,
12305 )
12306 });
12307 let (pcc_terminal, has_pcc_terminal) =
12308 terminal_reference_view(converter.pcc_terminal.as_ref());
12309 let (reactive_limits, has_reactive_limits) =
12310 reactive_limits_view(converter.reactive_limits.as_ref());
12311 *require_output(output, "output")? = PioAcDcConverterView {
12312 component: component_id_view(&converter.component),
12313 kind: PioStringView::new("voltage_source"),
12314 dc_converter_unit,
12315 has_dc_converter_unit,
12316 dc_terminal1: dc_terminal_view(&converter.dc_terminal1),
12317 dc_terminal2: dc_terminal_view(&converter.dc_terminal2),
12318 base_apparent_power_mva: converter.base_apparent_power_mva.unwrap_or(0.0),
12319 has_base_apparent_power: converter.base_apparent_power_mva.is_some(),
12320 minimum_active_power_mw: converter.minimum_active_power_mw.unwrap_or(0.0),
12321 has_minimum_active_power: converter.minimum_active_power_mw.is_some(),
12322 maximum_active_power_mw: converter.maximum_active_power_mw.unwrap_or(0.0),
12323 has_maximum_active_power: converter.maximum_active_power_mw.is_some(),
12324 minimum_dc_voltage_kv: converter.minimum_dc_voltage_kv.unwrap_or(0.0),
12325 has_minimum_dc_voltage: converter.minimum_dc_voltage_kv.is_some(),
12326 maximum_dc_voltage_kv: converter.maximum_dc_voltage_kv.unwrap_or(0.0),
12327 has_maximum_dc_voltage: converter.maximum_dc_voltage_kv.is_some(),
12328 rated_dc_voltage_kv: converter.rated_dc_voltage_kv.unwrap_or(0.0),
12329 has_rated_dc_voltage: converter.rated_dc_voltage_kv.is_some(),
12330 valve_u0_kv: converter.valve_u0_kv.unwrap_or(0.0),
12331 has_valve_u0: converter.valve_u0_kv.is_some(),
12332 number_of_valves: converter.number_of_valves.unwrap_or(0),
12333 has_number_of_valves: converter.number_of_valves.is_some(),
12334 idle_loss_mw: converter.idle_loss_mw.unwrap_or(0.0),
12335 has_idle_loss: converter.idle_loss_mw.is_some(),
12336 switching_loss_mw_per_ampere: converter.switching_loss_mw_per_ampere.unwrap_or(0.0),
12337 has_switching_loss: converter.switching_loss_mw_per_ampere.is_some(),
12338 resistive_loss_ohm: converter.resistive_loss_ohm.unwrap_or(0.0),
12339 has_resistive_loss: converter.resistive_loss_ohm.is_some(),
12340 control_mode,
12341 has_control_mode,
12342 active_power_at_pcc_mw: converter.active_power_at_pcc_mw.unwrap_or(0.0),
12343 has_active_power_at_pcc: converter.active_power_at_pcc_mw.is_some(),
12344 reactive_power_at_pcc_mvar: converter.reactive_power_at_pcc_mvar.unwrap_or(0.0),
12345 has_reactive_power_at_pcc: converter.reactive_power_at_pcc_mvar.is_some(),
12346 target_active_power_mw: converter.target_active_power_mw.unwrap_or(0.0),
12347 has_target_active_power: converter.target_active_power_mw.is_some(),
12348 target_dc_voltage_kv: converter.target_dc_voltage_kv.unwrap_or(0.0),
12349 has_target_dc_voltage: converter.target_dc_voltage_kv.is_some(),
12350 pcc_terminal,
12351 has_pcc_terminal,
12352 droop_curve_segment_count: converter
12353 .droop_curve
12354 .as_ref()
12355 .map_or(0, |curve| curve.segments.len()),
12356 has_droop_curve: converter.droop_curve.is_some(),
12357 droop: converter.droop.unwrap_or(0.0),
12358 has_droop: converter.droop.is_some(),
12359 droop_compensation: converter.droop_compensation.unwrap_or(0.0),
12360 has_droop_compensation: converter.droop_compensation.is_some(),
12361 q_share: converter.q_share.unwrap_or(0.0),
12362 has_q_share: converter.q_share.is_some(),
12363 maximum_modulation_index: converter.maximum_modulation_index.unwrap_or(0.0),
12364 has_maximum_modulation_index: converter.maximum_modulation_index.is_some(),
12365 maximum_valve_current_a: converter.maximum_valve_current_a.unwrap_or(0.0),
12366 has_maximum_valve_current: converter.maximum_valve_current_a.is_some(),
12367 dc_current_a: converter.dc_current_a.unwrap_or(0.0),
12368 has_dc_current: converter.dc_current_a.is_some(),
12369 ac_voltage_kv: converter.ac_voltage_kv.unwrap_or(0.0),
12370 has_ac_voltage: converter.ac_voltage_kv.is_some(),
12371 dc_voltage_kv: converter.dc_voltage_kv.unwrap_or(0.0),
12372 has_dc_voltage: converter.dc_voltage_kv.is_some(),
12373 voltage_regulator_on: converter.voltage_regulator_on.unwrap_or(false),
12374 has_voltage_regulator_on: converter.voltage_regulator_on.is_some(),
12375 voltage_setpoint_kv: converter.voltage_setpoint_kv.unwrap_or(0.0),
12376 has_voltage_setpoint: converter.voltage_setpoint_kv.is_some(),
12377 reactive_power_setpoint_mvar: converter.reactive_power_setpoint_mvar.unwrap_or(0.0),
12378 has_reactive_power_setpoint: converter.reactive_power_setpoint_mvar.is_some(),
12379 reactive_limits,
12380 has_reactive_limits,
12381 pole_loss_active_power_mw: converter.pole_loss_active_power_mw.unwrap_or(0.0),
12382 has_pole_loss_active_power: converter.pole_loss_active_power_mw.is_some(),
12383 reactive_model: PioStringView::EMPTY,
12384 has_reactive_model: false,
12385 power_factor: 0.0,
12386 has_power_factor: false,
12387 operating_mode: PioStringView::EMPTY,
12388 has_operating_mode: false,
12389 rated_dc_current_a: 0.0,
12390 has_rated_dc_current: false,
12391 minimum_alpha_degrees: 0.0,
12392 has_minimum_alpha: false,
12393 maximum_alpha_degrees: 0.0,
12394 has_maximum_alpha: false,
12395 minimum_gamma_degrees: 0.0,
12396 has_minimum_gamma: false,
12397 maximum_gamma_degrees: 0.0,
12398 has_maximum_gamma: false,
12399 target_alpha_degrees: 0.0,
12400 has_target_alpha: false,
12401 target_gamma_degrees: 0.0,
12402 has_target_gamma: false,
12403 target_dc_current_a: 0.0,
12404 has_target_dc_current: false,
12405 alpha_degrees: 0.0,
12406 has_alpha: false,
12407 gamma_degrees: 0.0,
12408 has_gamma: false,
12409 delta_degrees: converter.delta_degrees.unwrap_or(0.0),
12410 has_delta: converter.delta_degrees.is_some(),
12411 uf_kv: converter.uf_kv.unwrap_or(0.0),
12412 has_uf: converter.uf_kv.is_some(),
12413 uv_kv: converter.uv_kv.unwrap_or(0.0),
12414 has_uv: converter.uv_kv.is_some(),
12415 };
12416 Ok(true)
12417 })
12418 }
12419}
12420
12421#[unsafe(no_mangle)]
12426pub unsafe extern "C" fn pio_detailed_connectivity_line_commutated_converter_at(
12427 details: *const PioDetailedConnectivity,
12428 index: usize,
12429 output: *mut PioAcDcConverterView,
12430 error: *mut *mut PioError,
12431) -> bool {
12432 unsafe {
12433 entry(error, false, || {
12434 let details = require_detailed_connectivity(details)?;
12435 let converter = details
12436 .line_commutated_converters
12437 .get(index)
12438 .ok_or_else(|| {
12439 boundary_error(
12440 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12441 format!("line commutated converter index {index} is out of range"),
12442 )
12443 })?;
12444 let (dc_converter_unit, has_dc_converter_unit) =
12445 optional_component_id_view(converter.dc_converter_unit.as_ref());
12446 let (control_mode, has_control_mode) =
12447 converter
12448 .control_mode
12449 .map_or((PioStringView::EMPTY, false), |mode| {
12450 (
12451 PioStringView::new(ac_dc_converter_control_mode_name(mode)),
12452 true,
12453 )
12454 });
12455 let (reactive_model, has_reactive_model) =
12456 converter
12457 .reactive_model
12458 .map_or((PioStringView::EMPTY, false), |model| {
12459 (
12460 PioStringView::new(line_commutated_converter_reactive_model_name(
12461 model,
12462 )),
12463 true,
12464 )
12465 });
12466 let (operating_mode, has_operating_mode) =
12467 converter
12468 .operating_mode
12469 .map_or((PioStringView::EMPTY, false), |mode| {
12470 (
12471 PioStringView::new(line_commutated_converter_operating_mode_name(mode)),
12472 true,
12473 )
12474 });
12475 let (pcc_terminal, has_pcc_terminal) =
12476 terminal_reference_view(converter.pcc_terminal.as_ref());
12477 *require_output(output, "output")? = PioAcDcConverterView {
12478 component: component_id_view(&converter.component),
12479 kind: PioStringView::new("line_commutated"),
12480 dc_converter_unit,
12481 has_dc_converter_unit,
12482 dc_terminal1: dc_terminal_view(&converter.dc_terminal1),
12483 dc_terminal2: dc_terminal_view(&converter.dc_terminal2),
12484 base_apparent_power_mva: converter.base_apparent_power_mva.unwrap_or(0.0),
12485 has_base_apparent_power: converter.base_apparent_power_mva.is_some(),
12486 minimum_active_power_mw: converter.minimum_active_power_mw.unwrap_or(0.0),
12487 has_minimum_active_power: converter.minimum_active_power_mw.is_some(),
12488 maximum_active_power_mw: converter.maximum_active_power_mw.unwrap_or(0.0),
12489 has_maximum_active_power: converter.maximum_active_power_mw.is_some(),
12490 minimum_dc_voltage_kv: converter.minimum_dc_voltage_kv.unwrap_or(0.0),
12491 has_minimum_dc_voltage: converter.minimum_dc_voltage_kv.is_some(),
12492 maximum_dc_voltage_kv: converter.maximum_dc_voltage_kv.unwrap_or(0.0),
12493 has_maximum_dc_voltage: converter.maximum_dc_voltage_kv.is_some(),
12494 rated_dc_voltage_kv: converter.rated_dc_voltage_kv.unwrap_or(0.0),
12495 has_rated_dc_voltage: converter.rated_dc_voltage_kv.is_some(),
12496 valve_u0_kv: converter.valve_u0_kv.unwrap_or(0.0),
12497 has_valve_u0: converter.valve_u0_kv.is_some(),
12498 number_of_valves: converter.number_of_valves.unwrap_or(0),
12499 has_number_of_valves: converter.number_of_valves.is_some(),
12500 idle_loss_mw: converter.idle_loss_mw.unwrap_or(0.0),
12501 has_idle_loss: converter.idle_loss_mw.is_some(),
12502 switching_loss_mw_per_ampere: converter.switching_loss_mw_per_ampere.unwrap_or(0.0),
12503 has_switching_loss: converter.switching_loss_mw_per_ampere.is_some(),
12504 resistive_loss_ohm: converter.resistive_loss_ohm.unwrap_or(0.0),
12505 has_resistive_loss: converter.resistive_loss_ohm.is_some(),
12506 control_mode,
12507 has_control_mode,
12508 active_power_at_pcc_mw: converter.active_power_at_pcc_mw.unwrap_or(0.0),
12509 has_active_power_at_pcc: converter.active_power_at_pcc_mw.is_some(),
12510 reactive_power_at_pcc_mvar: converter.reactive_power_at_pcc_mvar.unwrap_or(0.0),
12511 has_reactive_power_at_pcc: converter.reactive_power_at_pcc_mvar.is_some(),
12512 target_active_power_mw: converter.target_active_power_mw.unwrap_or(0.0),
12513 has_target_active_power: converter.target_active_power_mw.is_some(),
12514 target_dc_voltage_kv: converter.target_dc_voltage_kv.unwrap_or(0.0),
12515 has_target_dc_voltage: converter.target_dc_voltage_kv.is_some(),
12516 pcc_terminal,
12517 has_pcc_terminal,
12518 droop_curve_segment_count: converter
12519 .droop_curve
12520 .as_ref()
12521 .map_or(0, |curve| curve.segments.len()),
12522 has_droop_curve: converter.droop_curve.is_some(),
12523 droop: 0.0,
12524 has_droop: false,
12525 droop_compensation: 0.0,
12526 has_droop_compensation: false,
12527 q_share: 0.0,
12528 has_q_share: false,
12529 maximum_modulation_index: 0.0,
12530 has_maximum_modulation_index: false,
12531 maximum_valve_current_a: 0.0,
12532 has_maximum_valve_current: false,
12533 dc_current_a: converter.dc_current_a.unwrap_or(0.0),
12534 has_dc_current: converter.dc_current_a.is_some(),
12535 ac_voltage_kv: converter.ac_voltage_kv.unwrap_or(0.0),
12536 has_ac_voltage: converter.ac_voltage_kv.is_some(),
12537 dc_voltage_kv: converter.dc_voltage_kv.unwrap_or(0.0),
12538 has_dc_voltage: converter.dc_voltage_kv.is_some(),
12539 voltage_regulator_on: false,
12540 has_voltage_regulator_on: false,
12541 voltage_setpoint_kv: 0.0,
12542 has_voltage_setpoint: false,
12543 reactive_power_setpoint_mvar: 0.0,
12544 has_reactive_power_setpoint: false,
12545 reactive_limits: empty_reactive_limits_view(),
12546 has_reactive_limits: false,
12547 pole_loss_active_power_mw: converter.pole_loss_active_power_mw.unwrap_or(0.0),
12548 has_pole_loss_active_power: converter.pole_loss_active_power_mw.is_some(),
12549 reactive_model,
12550 has_reactive_model,
12551 power_factor: converter.power_factor.unwrap_or(0.0),
12552 has_power_factor: converter.power_factor.is_some(),
12553 operating_mode,
12554 has_operating_mode,
12555 rated_dc_current_a: converter.rated_dc_current_a.unwrap_or(0.0),
12556 has_rated_dc_current: converter.rated_dc_current_a.is_some(),
12557 minimum_alpha_degrees: converter.minimum_alpha_degrees.unwrap_or(0.0),
12558 has_minimum_alpha: converter.minimum_alpha_degrees.is_some(),
12559 maximum_alpha_degrees: converter.maximum_alpha_degrees.unwrap_or(0.0),
12560 has_maximum_alpha: converter.maximum_alpha_degrees.is_some(),
12561 minimum_gamma_degrees: converter.minimum_gamma_degrees.unwrap_or(0.0),
12562 has_minimum_gamma: converter.minimum_gamma_degrees.is_some(),
12563 maximum_gamma_degrees: converter.maximum_gamma_degrees.unwrap_or(0.0),
12564 has_maximum_gamma: converter.maximum_gamma_degrees.is_some(),
12565 target_alpha_degrees: converter.target_alpha_degrees.unwrap_or(0.0),
12566 has_target_alpha: converter.target_alpha_degrees.is_some(),
12567 target_gamma_degrees: converter.target_gamma_degrees.unwrap_or(0.0),
12568 has_target_gamma: converter.target_gamma_degrees.is_some(),
12569 target_dc_current_a: converter.target_dc_current_a.unwrap_or(0.0),
12570 has_target_dc_current: converter.target_dc_current_a.is_some(),
12571 alpha_degrees: converter.alpha_degrees.unwrap_or(0.0),
12572 has_alpha: converter.alpha_degrees.is_some(),
12573 gamma_degrees: converter.gamma_degrees.unwrap_or(0.0),
12574 has_gamma: converter.gamma_degrees.is_some(),
12575 delta_degrees: 0.0,
12576 has_delta: false,
12577 uf_kv: 0.0,
12578 has_uf: false,
12579 uv_kv: 0.0,
12580 has_uv: false,
12581 };
12582 Ok(true)
12583 })
12584 }
12585}
12586
12587fn droop_curve_segment_view(segment: &powerio_tx::DroopCurveSegment) -> PioDroopCurveSegmentView {
12588 PioDroopCurveSegmentView {
12589 minimum_voltage_kv: segment.minimum_voltage_kv,
12590 maximum_voltage_kv: segment.maximum_voltage_kv,
12591 k: segment.k,
12592 }
12593}
12594
12595#[unsafe(no_mangle)]
12600pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_droop_curve_segment_at(
12601 details: *const PioDetailedConnectivity,
12602 converter_index: usize,
12603 segment_index: usize,
12604 output: *mut PioDroopCurveSegmentView,
12605 error: *mut *mut PioError,
12606) -> bool {
12607 unsafe {
12608 entry(error, false, || {
12609 let details = require_detailed_connectivity(details)?;
12610 let converter = details
12611 .voltage_source_converters
12612 .get(converter_index)
12613 .ok_or_else(|| {
12614 boundary_error(
12615 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12616 format!("voltage source converter index {converter_index} is out of range"),
12617 )
12618 })?;
12619 let curve = converter.droop_curve.as_ref().ok_or_else(|| {
12620 boundary_error(
12621 &codes::REQUEST_CAPI_TYPE_MISMATCH,
12622 format!("voltage source converter {converter_index} has no droop curve"),
12623 )
12624 })?;
12625 let segment = curve.segments.get(segment_index).ok_or_else(|| {
12626 boundary_error(
12627 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12628 format!("droop curve segment index {segment_index} is out of range"),
12629 )
12630 })?;
12631 *require_output(output, "output")? = droop_curve_segment_view(segment);
12632 Ok(true)
12633 })
12634 }
12635}
12636
12637#[unsafe(no_mangle)]
12642pub unsafe extern "C" fn pio_detailed_connectivity_line_commutated_converter_droop_curve_segment_at(
12643 details: *const PioDetailedConnectivity,
12644 converter_index: usize,
12645 segment_index: usize,
12646 output: *mut PioDroopCurveSegmentView,
12647 error: *mut *mut PioError,
12648) -> bool {
12649 unsafe {
12650 entry(error, false, || {
12651 let details = require_detailed_connectivity(details)?;
12652 let converter = details
12653 .line_commutated_converters
12654 .get(converter_index)
12655 .ok_or_else(|| {
12656 boundary_error(
12657 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12658 format!(
12659 "line commutated converter index {converter_index} is out of range"
12660 ),
12661 )
12662 })?;
12663 let curve = converter.droop_curve.as_ref().ok_or_else(|| {
12664 boundary_error(
12665 &codes::REQUEST_CAPI_TYPE_MISMATCH,
12666 format!("line commutated converter {converter_index} has no droop curve"),
12667 )
12668 })?;
12669 let segment = curve.segments.get(segment_index).ok_or_else(|| {
12670 boundary_error(
12671 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12672 format!("droop curve segment index {segment_index} is out of range"),
12673 )
12674 })?;
12675 *require_output(output, "output")? = droop_curve_segment_view(segment);
12676 Ok(true)
12677 })
12678 }
12679}
12680
12681#[unsafe(no_mangle)]
12686pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_reactive_limit_property_at(
12687 details: *const PioDetailedConnectivity,
12688 converter_index: usize,
12689 property_index: usize,
12690 output: *mut PioStringPropertyView,
12691 error: *mut *mut PioError,
12692) -> bool {
12693 unsafe {
12694 entry(error, false, || {
12695 let details = require_detailed_connectivity(details)?;
12696 let converter = details
12697 .voltage_source_converters
12698 .get(converter_index)
12699 .ok_or_else(|| {
12700 boundary_error(
12701 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12702 format!("voltage source converter index {converter_index} is out of range"),
12703 )
12704 })?;
12705 let limits = converter.reactive_limits.as_ref().ok_or_else(|| {
12706 boundary_error(
12707 &codes::REQUEST_CAPI_TYPE_MISMATCH,
12708 format!("voltage source converter {converter_index} has no reactive limits"),
12709 )
12710 })?;
12711 *require_output(output, "output")? =
12712 string_property_view(reactive_limit_properties(limits)?, property_index)?;
12713 Ok(true)
12714 })
12715 }
12716}
12717
12718#[unsafe(no_mangle)]
12723pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_reactive_capability_point_at(
12724 details: *const PioDetailedConnectivity,
12725 converter_index: usize,
12726 point_index: usize,
12727 output: *mut PioReactiveCapabilityCurvePointView,
12728 error: *mut *mut PioError,
12729) -> bool {
12730 unsafe {
12731 entry(error, false, || {
12732 let details = require_detailed_connectivity(details)?;
12733 let converter = details
12734 .voltage_source_converters
12735 .get(converter_index)
12736 .ok_or_else(|| {
12737 boundary_error(
12738 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12739 format!("voltage source converter index {converter_index} is out of range"),
12740 )
12741 })?;
12742 let curve = converter
12743 .reactive_limits
12744 .as_ref()
12745 .and_then(reactive_capability_curve)
12746 .ok_or_else(|| {
12747 boundary_error(
12748 &codes::REQUEST_CAPI_TYPE_MISMATCH,
12749 format!(
12750 "voltage source converter {converter_index} has no reactive capability curve"
12751 ),
12752 )
12753 })?;
12754 *require_output(output, "output")? =
12755 reactive_capability_point_view(curve, point_index)?;
12756 Ok(true)
12757 })
12758 }
12759}
12760
12761#[unsafe(no_mangle)]
12766pub unsafe extern "C" fn pio_detailed_connectivity_voltage_source_converter_reactive_capability_point_property_at(
12767 details: *const PioDetailedConnectivity,
12768 converter_index: usize,
12769 point_index: usize,
12770 property_index: usize,
12771 output: *mut PioStringPropertyView,
12772 error: *mut *mut PioError,
12773) -> bool {
12774 unsafe {
12775 entry(error, false, || {
12776 let details = require_detailed_connectivity(details)?;
12777 let converter = details
12778 .voltage_source_converters
12779 .get(converter_index)
12780 .ok_or_else(|| {
12781 boundary_error(
12782 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12783 format!("voltage source converter index {converter_index} is out of range"),
12784 )
12785 })?;
12786 let curve = converter
12787 .reactive_limits
12788 .as_ref()
12789 .and_then(reactive_capability_curve)
12790 .ok_or_else(|| {
12791 boundary_error(
12792 &codes::REQUEST_CAPI_TYPE_MISMATCH,
12793 format!(
12794 "voltage source converter {converter_index} has no reactive capability curve"
12795 ),
12796 )
12797 })?;
12798 let point = curve.points.get(point_index).ok_or_else(|| {
12799 boundary_error(
12800 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
12801 format!("reactive capability point index {point_index} is out of range"),
12802 )
12803 })?;
12804 *require_output(output, "output")? =
12805 string_property_view(&point.properties, property_index)?;
12806 Ok(true)
12807 })
12808 }
12809}
12810
12811#[unsafe(no_mangle)]
12816pub unsafe extern "C" fn pio_detailed_connectivity_retain(
12817 details: *const PioDetailedConnectivity,
12818) -> *mut PioDetailedConnectivity {
12819 unsafe { PioDetailedConnectivity::retain_raw(details) }
12820}
12821
12822#[unsafe(no_mangle)]
12827pub unsafe extern "C" fn pio_detailed_connectivity_release(details: *mut PioDetailedConnectivity) {
12828 unsafe { PioDetailedConnectivity::release_raw(details) };
12829}
12830
12831#[unsafe(no_mangle)]
12835pub unsafe extern "C" fn pio_balanced_network_bus_count(
12836 network: *const PioBalancedNetwork,
12837) -> usize {
12838 unsafe { PioBalancedNetwork::get(network) }
12839 .and_then(BalancedNetworkInner::network)
12840 .map_or(0, |network| network.buses().len())
12841}
12842
12843#[unsafe(no_mangle)]
12847pub unsafe extern "C" fn pio_balanced_network_branch_count(
12848 network: *const PioBalancedNetwork,
12849) -> usize {
12850 unsafe { PioBalancedNetwork::get(network) }
12851 .and_then(BalancedNetworkInner::network)
12852 .map_or(0, |network| network.branches().len())
12853}
12854
12855#[unsafe(no_mangle)]
12859pub unsafe extern "C" fn pio_balanced_network_load_count(
12860 network: *const PioBalancedNetwork,
12861) -> usize {
12862 unsafe { PioBalancedNetwork::get(network) }
12863 .and_then(BalancedNetworkInner::network)
12864 .map_or(0, |network| network.loads().len())
12865}
12866
12867#[unsafe(no_mangle)]
12871pub unsafe extern "C" fn pio_balanced_network_shunt_count(
12872 network: *const PioBalancedNetwork,
12873) -> usize {
12874 unsafe { PioBalancedNetwork::get(network) }
12875 .and_then(BalancedNetworkInner::network)
12876 .map_or(0, |network| network.shunts().len())
12877}
12878
12879#[unsafe(no_mangle)]
12883pub unsafe extern "C" fn pio_balanced_network_static_var_compensator_count(
12884 network: *const PioBalancedNetwork,
12885) -> usize {
12886 unsafe { PioBalancedNetwork::get(network) }
12887 .and_then(BalancedNetworkInner::network)
12888 .map_or(0, |network| network.static_var_compensators().len())
12889}
12890
12891#[unsafe(no_mangle)]
12895pub unsafe extern "C" fn pio_balanced_network_generator_count(
12896 network: *const PioBalancedNetwork,
12897) -> usize {
12898 unsafe { PioBalancedNetwork::get(network) }
12899 .and_then(BalancedNetworkInner::network)
12900 .map_or(0, |network| network.generators().len())
12901}
12902
12903#[unsafe(no_mangle)]
12907pub unsafe extern "C" fn pio_balanced_network_storage_count(
12908 network: *const PioBalancedNetwork,
12909) -> usize {
12910 unsafe { PioBalancedNetwork::get(network) }
12911 .and_then(BalancedNetworkInner::network)
12912 .map_or(0, |network| network.storage().len())
12913}
12914
12915#[unsafe(no_mangle)]
12919pub unsafe extern "C" fn pio_balanced_network_switch_count(
12920 network: *const PioBalancedNetwork,
12921) -> usize {
12922 unsafe { PioBalancedNetwork::get(network) }
12923 .and_then(BalancedNetworkInner::network)
12924 .map_or(0, |network| network.switches().len())
12925}
12926
12927#[unsafe(no_mangle)]
12931pub unsafe extern "C" fn pio_balanced_network_hvdc_count(
12932 network: *const PioBalancedNetwork,
12933) -> usize {
12934 unsafe { PioBalancedNetwork::get(network) }
12935 .and_then(BalancedNetworkInner::network)
12936 .map_or(0, |network| network.hvdc().len())
12937}
12938
12939#[unsafe(no_mangle)]
12943pub unsafe extern "C" fn pio_balanced_network_three_winding_transformer_count(
12944 network: *const PioBalancedNetwork,
12945) -> usize {
12946 unsafe { PioBalancedNetwork::get(network) }
12947 .and_then(BalancedNetworkInner::network)
12948 .map_or(0, |network| network.transformers_3w().len())
12949}
12950
12951#[unsafe(no_mangle)]
12955pub unsafe extern "C" fn pio_balanced_network_area_count(
12956 network: *const PioBalancedNetwork,
12957) -> usize {
12958 unsafe { PioBalancedNetwork::get(network) }
12959 .and_then(BalancedNetworkInner::network)
12960 .map_or(0, |network| network.areas().len())
12961}
12962
12963unsafe fn require_balanced_network<'a>(
12964 network: *const PioBalancedNetwork,
12965) -> Result<&'a BalancedNetwork, *mut PioError> {
12966 unsafe { PioBalancedNetwork::get(network) }
12967 .and_then(BalancedNetworkInner::network)
12968 .ok_or_else(|| {
12969 boundary_error(
12970 &codes::BIND_CAPI_NULL_HANDLE,
12971 "PioBalancedNetwork must not be NULL",
12972 )
12973 })
12974}
12975
12976unsafe fn require_output<'a, T>(
12977 output: *mut T,
12978 argument: &str,
12979) -> Result<&'a mut T, *mut PioError> {
12980 unsafe { output.as_mut() }.ok_or_else(|| {
12981 boundary_error(
12982 &codes::BIND_CAPI_NULL_ARGUMENT,
12983 format!("{argument} must not be NULL"),
12984 )
12985 })
12986}
12987
12988unsafe fn require_detailed_connectivity<'a>(
12989 details: *const PioDetailedConnectivity,
12990) -> Result<&'a powerio_tx::DetailedConnectivity, *mut PioError> {
12991 unsafe { PioDetailedConnectivity::get(details) }
12992 .and_then(DetailedConnectivityInner::details)
12993 .ok_or_else(|| {
12994 boundary_error(
12995 &codes::BIND_CAPI_NULL_HANDLE,
12996 "PioDetailedConnectivity must not be NULL",
12997 )
12998 })
12999}
13000
13001fn optional_string_view(value: Option<&str>) -> (PioStringView, bool) {
13002 value.map_or((PioStringView::EMPTY, false), |value| {
13003 (PioStringView::new(value), true)
13004 })
13005}
13006
13007fn balanced_coords_kind_name(kind: powerio_tx::CoordsKind) -> &'static str {
13008 match kind {
13009 powerio_tx::CoordsKind::Source => "source",
13010 powerio_tx::CoordsKind::Synthetic => "synthetic",
13011 powerio_tx::CoordsKind::Manual => "manual",
13012 powerio_tx::CoordsKind::Derived => "derived",
13013 _ => "unknown",
13014 }
13015}
13016
13017fn balanced_location_view(location: &powerio_tx::Location) -> PioBalancedLocationView {
13018 let (kind, has_kind) = location.kind.map_or((PioStringView::EMPTY, false), |kind| {
13019 (PioStringView::new(balanced_coords_kind_name(kind)), true)
13020 });
13021 PioBalancedLocationView {
13022 x: location.x,
13023 y: location.y,
13024 kind,
13025 has_kind,
13026 }
13027}
13028
13029fn empty_balanced_location_view() -> PioBalancedLocationView {
13030 PioBalancedLocationView {
13031 x: 0.0,
13032 y: 0.0,
13033 kind: PioStringView::EMPTY,
13034 has_kind: false,
13035 }
13036}
13037
13038fn balanced_geo_view(geo: Option<&powerio_tx::GeoMeta>) -> PioBalancedGeoView {
13039 let Some(geo) = geo else {
13040 return PioBalancedGeoView {
13041 has_geo: false,
13042 space: PioStringView::EMPTY,
13043 crs: PioStringView::EMPTY,
13044 has_crs: false,
13045 kind: PioStringView::EMPTY,
13046 has_kind: false,
13047 has_canvas: false,
13048 canvas_width: 0.0,
13049 has_canvas_width: false,
13050 canvas_height: 0.0,
13051 has_canvas_height: false,
13052 canvas_units: PioStringView::EMPTY,
13053 has_canvas_units: false,
13054 };
13055 };
13056 let (space, crs, has_crs, canvas) = match &geo.space {
13057 powerio_tx::CoordinateSpace::Geographic { crs } => (
13058 "geographic",
13059 crs.as_deref()
13060 .map_or(PioStringView::EMPTY, PioStringView::new),
13061 crs.is_some(),
13062 None,
13063 ),
13064 powerio_tx::CoordinateSpace::Projected { crs } => (
13065 "projected",
13066 crs.as_deref()
13067 .map_or(PioStringView::EMPTY, PioStringView::new),
13068 crs.is_some(),
13069 None,
13070 ),
13071 powerio_tx::CoordinateSpace::Diagram { canvas } => {
13072 ("diagram", PioStringView::EMPTY, false, canvas.as_ref())
13073 }
13074 powerio_tx::CoordinateSpace::Unknown => ("unknown", PioStringView::EMPTY, false, None),
13075 _ => ("unknown", PioStringView::EMPTY, false, None),
13076 };
13077 let (kind, has_kind) = geo.kind.map_or((PioStringView::EMPTY, false), |kind| {
13078 (PioStringView::new(balanced_coords_kind_name(kind)), true)
13079 });
13080 PioBalancedGeoView {
13081 has_geo: true,
13082 space: PioStringView::new(space),
13083 crs,
13084 has_crs,
13085 kind,
13086 has_kind,
13087 has_canvas: canvas.is_some(),
13088 canvas_width: canvas.and_then(|canvas| canvas.width).unwrap_or(0.0),
13089 has_canvas_width: canvas.is_some_and(|canvas| canvas.width.is_some()),
13090 canvas_height: canvas.and_then(|canvas| canvas.height).unwrap_or(0.0),
13091 has_canvas_height: canvas.is_some_and(|canvas| canvas.height.is_some()),
13092 canvas_units: canvas
13093 .and_then(|canvas| canvas.units.as_deref())
13094 .map_or(PioStringView::EMPTY, PioStringView::new),
13095 has_canvas_units: canvas.is_some_and(|canvas| canvas.units.is_some()),
13096 }
13097}
13098
13099fn load_voltage_model_view(load: &powerio_tx::Load) -> PioBalancedLoadVoltageModelView {
13100 let mut view = PioBalancedLoadVoltageModelView {
13101 kind: PioStringView::new("constant_power"),
13102 p_constant_power_mw: load.p,
13103 q_constant_power_mvar: load.q,
13104 p_constant_current_mw: 0.0,
13105 q_constant_current_mvar: 0.0,
13106 p_constant_impedance_mw: 0.0,
13107 q_constant_impedance_mvar: 0.0,
13108 exponential_p_mw: 0.0,
13109 exponential_q_mvar: 0.0,
13110 gamma_p: 0.0,
13111 gamma_q: 0.0,
13112 nominal_voltage_pu: 0.0,
13113 has_nominal_voltage: false,
13114 load_type: 0,
13115 has_load_type: false,
13116 scaling: 0.0,
13117 has_scaling: false,
13118 };
13119 match &load.voltage_model {
13120 None | Some(powerio_tx::LoadVoltageModel::ConstantPower) => {}
13121 Some(powerio_tx::LoadVoltageModel::Zip {
13122 p_constant_power,
13123 q_constant_power,
13124 p_constant_current,
13125 q_constant_current,
13126 p_constant_impedance,
13127 q_constant_impedance,
13128 v_nom,
13129 load_type,
13130 scaling,
13131 }) => {
13132 view.kind = PioStringView::new("zip");
13133 view.p_constant_power_mw = *p_constant_power;
13134 view.q_constant_power_mvar = *q_constant_power;
13135 view.p_constant_current_mw = *p_constant_current;
13136 view.q_constant_current_mvar = *q_constant_current;
13137 view.p_constant_impedance_mw = *p_constant_impedance;
13138 view.q_constant_impedance_mvar = *q_constant_impedance;
13139 if let Some(value) = v_nom {
13140 view.nominal_voltage_pu = *value;
13141 view.has_nominal_voltage = true;
13142 }
13143 if let Some(value) = load_type {
13144 view.load_type = *value;
13145 view.has_load_type = true;
13146 }
13147 if let Some(value) = scaling {
13148 view.scaling = *value;
13149 view.has_scaling = true;
13150 }
13151 }
13152 Some(powerio_tx::LoadVoltageModel::Exponential {
13153 p,
13154 q,
13155 v_nom,
13156 gamma_p,
13157 gamma_q,
13158 }) => {
13159 view.kind = PioStringView::new("exponential");
13160 view.p_constant_power_mw = 0.0;
13161 view.q_constant_power_mvar = 0.0;
13162 view.exponential_p_mw = *p;
13163 view.exponential_q_mvar = *q;
13164 view.gamma_p = *gamma_p;
13165 view.gamma_q = *gamma_q;
13166 if let Some(value) = v_nom {
13167 view.nominal_voltage_pu = *value;
13168 view.has_nominal_voltage = true;
13169 }
13170 }
13171 Some(_) => view.kind = PioStringView::new("unknown"),
13172 }
13173 view
13174}
13175
13176fn switched_shunt_mode_name(mode: powerio_tx::SwitchedShuntMode) -> &'static str {
13177 match mode {
13178 powerio_tx::SwitchedShuntMode::Locked => "locked",
13179 powerio_tx::SwitchedShuntMode::Continuous => "continuous",
13180 powerio_tx::SwitchedShuntMode::Discrete => "discrete",
13181 _ => "unknown",
13182 }
13183}
13184
13185use powerio_tx::network::GEN_EXTRA_KEYS as GENERATOR_CAPABILITY_NAMES;
13186
13187fn static_var_compensator_regulation_mode_name(
13188 mode: powerio_tx::StaticVarCompensatorRegulationMode,
13189) -> &'static str {
13190 match mode {
13191 powerio_tx::StaticVarCompensatorRegulationMode::Voltage => "voltage",
13192 powerio_tx::StaticVarCompensatorRegulationMode::ReactivePower => "reactive_power",
13193 _ => "unknown",
13194 }
13195}
13196
13197fn hvdc_converter_kind_name(kind: powerio_tx::HvdcConverterKind) -> &'static str {
13198 match kind {
13199 powerio_tx::HvdcConverterKind::Vsc => "vsc",
13200 powerio_tx::HvdcConverterKind::Lcc => "lcc",
13201 _ => "unknown",
13202 }
13203}
13204
13205fn hvdc_converters_mode_name(mode: powerio_tx::HvdcConvertersMode) -> &'static str {
13206 match mode {
13207 powerio_tx::HvdcConvertersMode::Side1RectifierSide2Inverter => {
13208 "side1_rectifier_side2_inverter"
13209 }
13210 powerio_tx::HvdcConvertersMode::Side1InverterSide2Rectifier => {
13211 "side1_inverter_side2_rectifier"
13212 }
13213 _ => "unknown",
13214 }
13215}
13216
13217fn hvdc_converter_view(
13218 converter: Option<&powerio_tx::HvdcConverter>,
13219) -> (PioBalancedHvdcConverterView, bool) {
13220 let empty_terminal = terminal_reference_view(None).0;
13221 converter.map_or(
13222 (
13223 PioBalancedHvdcConverterView {
13224 component: empty_component_id_view(),
13225 kind: PioStringView::EMPTY,
13226 loss_factor_percent: 0.0,
13227 voltage_regulator_on: false,
13228 has_voltage_regulator_on: false,
13229 voltage_setpoint_kv: 0.0,
13230 has_voltage_setpoint: false,
13231 reactive_power_setpoint_mvar: 0.0,
13232 has_reactive_power_setpoint: false,
13233 power_factor: 0.0,
13234 has_power_factor: false,
13235 regulating_terminal: empty_terminal,
13236 has_regulating_terminal: false,
13237 },
13238 false,
13239 ),
13240 |converter| {
13241 let (regulating_terminal, has_regulating_terminal) =
13242 terminal_reference_view(converter.regulating_terminal.as_ref());
13243 (
13244 PioBalancedHvdcConverterView {
13245 component: component_id_view(&converter.component),
13246 kind: PioStringView::new(hvdc_converter_kind_name(converter.kind)),
13247 loss_factor_percent: converter.loss_factor_percent,
13248 voltage_regulator_on: converter.voltage_regulator_on.unwrap_or(false),
13249 has_voltage_regulator_on: converter.voltage_regulator_on.is_some(),
13250 voltage_setpoint_kv: converter.voltage_setpoint_kv.unwrap_or(0.0),
13251 has_voltage_setpoint: converter.voltage_setpoint_kv.is_some(),
13252 reactive_power_setpoint_mvar: converter
13253 .reactive_power_setpoint_mvar
13254 .unwrap_or(0.0),
13255 has_reactive_power_setpoint: converter.reactive_power_setpoint_mvar.is_some(),
13256 power_factor: converter.power_factor.unwrap_or(0.0),
13257 has_power_factor: converter.power_factor.is_some(),
13258 regulating_terminal,
13259 has_regulating_terminal,
13260 },
13261 true,
13262 )
13263 },
13264 )
13265}
13266
13267#[unsafe(no_mangle)]
13272pub unsafe extern "C" fn pio_balanced_network_bus_at(
13273 network: *const PioBalancedNetwork,
13274 index: usize,
13275 output: *mut PioBalancedBusView,
13276 error: *mut *mut PioError,
13277) -> bool {
13278 unsafe {
13279 entry(error, false, || {
13280 let network = require_balanced_network(network)?;
13281 let bus = network.buses().get(index).ok_or_else(|| {
13282 boundary_error(
13283 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13284 format!("bus index {index} is out of range"),
13285 )
13286 })?;
13287 let output = require_output(output, "output")?;
13288 let (component_id, has_component_id) = optional_string_view(bus.uid.as_deref());
13289 let (name, has_name) = optional_string_view(bus.name.as_deref());
13290 let (location, has_location) = bus
13291 .location
13292 .as_ref()
13293 .map_or((empty_balanced_location_view(), false), |location| {
13294 (balanced_location_view(location), true)
13295 });
13296 *output = PioBalancedBusView {
13297 component_id,
13298 has_component_id,
13299 id: bus.id.0,
13300 bus_type: PioStringView::new(bus.kind.as_str()),
13301 vm_pu: bus.vm,
13302 va_degrees: bus.va,
13303 base_kv: bus.base_kv,
13304 vmax_pu: bus.vmax,
13305 vmin_pu: bus.vmin,
13306 has_emergency_voltage_limits: bus.evhi.is_some() || bus.evlo.is_some(),
13307 emergency_vmax_pu: bus.evhi.unwrap_or(bus.vmax),
13308 emergency_vmin_pu: bus.evlo.unwrap_or(bus.vmin),
13309 area: bus.area,
13310 zone: bus.zone,
13311 name,
13312 has_name,
13313 location,
13314 has_location,
13315 };
13316 Ok(true)
13317 })
13318 }
13319}
13320
13321#[unsafe(no_mangle)]
13326pub unsafe extern "C" fn pio_balanced_network_load_at(
13327 network: *const PioBalancedNetwork,
13328 index: usize,
13329 output: *mut PioBalancedLoadView,
13330 error: *mut *mut PioError,
13331) -> bool {
13332 unsafe {
13333 entry(error, false, || {
13334 let network = require_balanced_network(network)?;
13335 let load = network.loads().get(index).ok_or_else(|| {
13336 boundary_error(
13337 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13338 format!("load index {index} is out of range"),
13339 )
13340 })?;
13341 let output = require_output(output, "output")?;
13342 let (component_id, has_component_id) = optional_string_view(load.uid.as_deref());
13343 *output = PioBalancedLoadView {
13344 component_id,
13345 has_component_id,
13346 bus_id: load.bus.0,
13347 p_mw: load.p,
13348 q_mvar: load.q,
13349 in_service: load.in_service,
13350 voltage_model: load_voltage_model_view(load),
13351 };
13352 Ok(true)
13353 })
13354 }
13355}
13356
13357#[unsafe(no_mangle)]
13362pub unsafe extern "C" fn pio_balanced_network_shunt_at(
13363 network: *const PioBalancedNetwork,
13364 index: usize,
13365 output: *mut PioBalancedShuntView,
13366 error: *mut *mut PioError,
13367) -> bool {
13368 unsafe {
13369 entry(error, false, || {
13370 let network = require_balanced_network(network)?;
13371 let shunt = network.shunts().get(index).ok_or_else(|| {
13372 boundary_error(
13373 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13374 format!("shunt index {index} is out of range"),
13375 )
13376 })?;
13377 let output = require_output(output, "output")?;
13378 let (component_id, has_component_id) = optional_string_view(shunt.uid.as_deref());
13379 let control = shunt.control.as_ref();
13380 *output = PioBalancedShuntView {
13381 component_id,
13382 has_component_id,
13383 bus_id: shunt.bus.0,
13384 conductance_mw: shunt.g,
13385 susceptance_mvar: shunt.b,
13386 in_service: shunt.in_service,
13387 section_count: shunt.section_count.unwrap_or(0),
13388 has_section_count: shunt.section_count.is_some(),
13389 has_control: control.is_some(),
13390 control_mode: control.map_or(PioStringView::EMPTY, |control| {
13391 PioStringView::new(switched_shunt_mode_name(control.mode))
13392 }),
13393 control_vmax_pu: control.map_or(0.0, |control| control.vhigh),
13394 control_vmin_pu: control.map_or(0.0, |control| control.vlow),
13395 control_bus_id: control
13396 .and_then(|control| control.control_bus)
13397 .map_or(0, |bus| bus.0),
13398 has_control_bus: control.is_some_and(|control| control.control_bus.is_some()),
13399 control_reactive_range_percent: control.map_or(0.0, |control| control.rmpct),
13400 control_block_count: control.map_or(0, |control| control.blocks.len()),
13401 };
13402 Ok(true)
13403 })
13404 }
13405}
13406
13407#[unsafe(no_mangle)]
13412pub unsafe extern "C" fn pio_balanced_network_shunt_block_at(
13413 network: *const PioBalancedNetwork,
13414 shunt_index: usize,
13415 block_index: usize,
13416 output: *mut PioShuntBlockView,
13417 error: *mut *mut PioError,
13418) -> bool {
13419 unsafe {
13420 entry(error, false, || {
13421 let network = require_balanced_network(network)?;
13422 let shunt = network.shunts().get(shunt_index).ok_or_else(|| {
13423 boundary_error(
13424 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13425 format!("shunt index {shunt_index} is out of range"),
13426 )
13427 })?;
13428 let block = shunt
13429 .control
13430 .as_ref()
13431 .and_then(|control| control.blocks.get(block_index))
13432 .ok_or_else(|| {
13433 boundary_error(
13434 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13435 format!(
13436 "shunt block index {block_index} is out of range for shunt {shunt_index}"
13437 ),
13438 )
13439 })?;
13440 *require_output(output, "output")? = PioShuntBlockView {
13441 steps: block.steps,
13442 conductance_mw: block.g,
13443 susceptance_mvar: block.b,
13444 };
13445 Ok(true)
13446 })
13447 }
13448}
13449
13450#[unsafe(no_mangle)]
13455pub unsafe extern "C" fn pio_balanced_network_static_var_compensator_at(
13456 network: *const PioBalancedNetwork,
13457 index: usize,
13458 output: *mut PioBalancedStaticVarCompensatorView,
13459 error: *mut *mut PioError,
13460) -> bool {
13461 unsafe {
13462 entry(error, false, || {
13463 let network = require_balanced_network(network)?;
13464 let svc = network
13465 .static_var_compensators()
13466 .get(index)
13467 .ok_or_else(|| {
13468 boundary_error(
13469 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13470 format!("static VAR compensator index {index} is out of range"),
13471 )
13472 })?;
13473 let (component_id, has_component_id) = optional_string_view(svc.uid.as_deref());
13474 let (regulating_terminal, has_regulating_terminal) =
13475 terminal_reference_view(svc.regulating_terminal.as_ref());
13476 *require_output(output, "output")? = PioBalancedStaticVarCompensatorView {
13477 component_id,
13478 has_component_id,
13479 bus_id: svc.bus.0,
13480 minimum_susceptance_siemens: svc.b_min_siemens,
13481 maximum_susceptance_siemens: svc.b_max_siemens,
13482 voltage_setpoint_kv: svc.voltage_setpoint_kv,
13483 reactive_power_setpoint_mvar: svc.reactive_power_setpoint_mvar,
13484 regulation_mode: PioStringView::new(static_var_compensator_regulation_mode_name(
13485 svc.regulation_mode,
13486 )),
13487 regulating: svc.regulating,
13488 regulating_terminal,
13489 has_regulating_terminal,
13490 active_power_mw: svc.p,
13491 reactive_power_mvar: svc.q,
13492 in_service: svc.in_service,
13493 };
13494 Ok(true)
13495 })
13496 }
13497}
13498
13499#[unsafe(no_mangle)]
13504pub unsafe extern "C" fn pio_balanced_network_branch_at(
13505 network: *const PioBalancedNetwork,
13506 index: usize,
13507 output: *mut PioBalancedBranchView,
13508 error: *mut *mut PioError,
13509) -> bool {
13510 unsafe {
13511 entry(error, false, || {
13512 let network = require_balanced_network(network)?;
13513 let branch = network.branches().get(index).ok_or_else(|| {
13514 boundary_error(
13515 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13516 format!("branch index {index} is out of range"),
13517 )
13518 })?;
13519 let output = require_output(output, "output")?;
13520 let (component_id, has_component_id) = optional_string_view(branch.uid.as_deref());
13521 let (name, has_name) = optional_string_view(branch.name.as_deref());
13522 let charging = branch.calc_terminal_charging();
13523 let current = branch.current_ratings;
13524 let (control, has_control) = transformer_control_view(branch.control.as_ref());
13525 *output = PioBalancedBranchView {
13526 component_id,
13527 has_component_id,
13528 name,
13529 has_name,
13530 from_bus_id: branch.from.0,
13531 to_bus_id: branch.to.0,
13532 resistance_pu: branch.r,
13533 reactance_pu: branch.x,
13534 total_charging_susceptance_pu: branch.b,
13535 terminal_charging_is_explicit: branch.charging.is_some(),
13536 from_conductance_pu: charging.g_fr,
13537 from_susceptance_pu: charging.b_fr,
13538 to_conductance_pu: charging.g_to,
13539 to_susceptance_pu: charging.b_to,
13540 rate_a_mva: branch.rate_a,
13541 rate_b_mva: branch.rate_b,
13542 rate_c_mva: branch.rate_c,
13543 additional_rating_count: branch.rating_sets.len(),
13544 has_current_ratings: current.is_some(),
13545 current_rating_a: current.map_or(0.0, |rating| rating.c_rating_a),
13546 current_rating_b: current.map_or(0.0, |rating| rating.c_rating_b),
13547 current_rating_c: current.map_or(0.0, |rating| rating.c_rating_c),
13548 tap_ratio: branch.tap,
13549 effective_tap_ratio: branch.calc_effective_tap(),
13550 phase_shift_degrees: branch.shift,
13551 in_service: branch.in_service,
13552 angle_min_degrees: branch.angmin,
13553 angle_max_degrees: branch.angmax,
13554 control,
13555 has_control,
13556 route_point_count: branch.route.as_ref().map_or(0, Vec::len),
13557 has_route: branch.route.is_some(),
13558 };
13559 Ok(true)
13560 })
13561 }
13562}
13563
13564#[unsafe(no_mangle)]
13569pub unsafe extern "C" fn pio_balanced_network_branch_route_point_at(
13570 network: *const PioBalancedNetwork,
13571 branch_index: usize,
13572 point_index: usize,
13573 output: *mut PioBalancedLocationView,
13574 error: *mut *mut PioError,
13575) -> bool {
13576 unsafe {
13577 entry(error, false, || {
13578 let network = require_balanced_network(network)?;
13579 let branch = network.branches().get(branch_index).ok_or_else(|| {
13580 boundary_error(
13581 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13582 format!("branch index {branch_index} is out of range"),
13583 )
13584 })?;
13585 let route = branch.route.as_ref().ok_or_else(|| {
13586 boundary_error(
13587 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13588 format!("balanced branch {branch_index} has no route"),
13589 )
13590 })?;
13591 let point = route.get(point_index).ok_or_else(|| {
13592 boundary_error(
13593 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13594 format!("branch route point index {point_index} is out of range"),
13595 )
13596 })?;
13597 *require_output(output, "output")? = balanced_location_view(point);
13598 Ok(true)
13599 })
13600 }
13601}
13602
13603#[unsafe(no_mangle)]
13608pub unsafe extern "C" fn pio_balanced_network_branch_rating_at(
13609 network: *const PioBalancedNetwork,
13610 branch_index: usize,
13611 rating_index: usize,
13612 output: *mut PioBranchRatingView,
13613 error: *mut *mut PioError,
13614) -> bool {
13615 unsafe {
13616 entry(error, false, || {
13617 let network = require_balanced_network(network)?;
13618 let branch = network.branches().get(branch_index).ok_or_else(|| {
13619 boundary_error(
13620 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13621 format!("branch index {branch_index} is out of range"),
13622 )
13623 })?;
13624 let rating = branch.rating_sets.get(rating_index).ok_or_else(|| {
13625 boundary_error(
13626 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13627 format!(
13628 "rating index {rating_index} is out of range for branch {branch_index}"
13629 ),
13630 )
13631 })?;
13632 *require_output(output, "output")? = PioBranchRatingView {
13633 name: PioStringView::new(&rating.name),
13634 rate_mva: rating.rate_mva,
13635 };
13636 Ok(true)
13637 })
13638 }
13639}
13640
13641fn active_power_control_view(
13642 value: Option<&powerio_tx::ActivePowerControl>,
13643) -> (PioActivePowerControlView, bool) {
13644 let Some(value) = value else {
13645 return (
13646 PioActivePowerControlView {
13647 participate: false,
13648 droop_percent: 0.0,
13649 has_droop_percent: false,
13650 participation_factor: 0.0,
13651 has_participation_factor: false,
13652 minimum_target_active_power_mw: 0.0,
13653 has_minimum_target_active_power: false,
13654 maximum_target_active_power_mw: 0.0,
13655 has_maximum_target_active_power: false,
13656 },
13657 false,
13658 );
13659 };
13660 (
13661 PioActivePowerControlView {
13662 participate: value.participate,
13663 droop_percent: value.droop_percent.unwrap_or(0.0),
13664 has_droop_percent: value.droop_percent.is_some(),
13665 participation_factor: value.participation_factor.unwrap_or(0.0),
13666 has_participation_factor: value.participation_factor.is_some(),
13667 minimum_target_active_power_mw: value.minimum_target_active_power_mw.unwrap_or(0.0),
13668 has_minimum_target_active_power: value.minimum_target_active_power_mw.is_some(),
13669 maximum_target_active_power_mw: value.maximum_target_active_power_mw.unwrap_or(0.0),
13670 has_maximum_target_active_power: value.maximum_target_active_power_mw.is_some(),
13671 },
13672 true,
13673 )
13674}
13675
13676fn generator_energy_source_name(value: powerio_tx::GeneratorEnergySource) -> &'static str {
13677 match value {
13678 powerio_tx::GeneratorEnergySource::Hydro => "hydro",
13679 powerio_tx::GeneratorEnergySource::Nuclear => "nuclear",
13680 powerio_tx::GeneratorEnergySource::Wind => "wind",
13681 powerio_tx::GeneratorEnergySource::Thermal => "thermal",
13682 powerio_tx::GeneratorEnergySource::Solar => "solar",
13683 powerio_tx::GeneratorEnergySource::Other => "other",
13684 _ => "unknown",
13685 }
13686}
13687
13688#[unsafe(no_mangle)]
13693pub unsafe extern "C" fn pio_balanced_network_generator_at(
13694 network: *const PioBalancedNetwork,
13695 index: usize,
13696 output: *mut PioBalancedGeneratorView,
13697 error: *mut *mut PioError,
13698) -> bool {
13699 unsafe {
13700 entry(error, false, || {
13701 let network = require_balanced_network(network)?;
13702 let generator = network.generators().get(index).ok_or_else(|| {
13703 boundary_error(
13704 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13705 format!("generator index {index} is out of range"),
13706 )
13707 })?;
13708 let output = require_output(output, "output")?;
13709 let (component_id, has_component_id) = optional_string_view(generator.uid.as_deref());
13710 let cost = generator.cost.as_ref();
13711 let (active_power_control, has_active_power_control) =
13712 active_power_control_view(generator.active_power_control.as_ref());
13713 let (regulating_terminal, has_regulating_terminal) =
13714 terminal_reference_view(generator.regulating_terminal.as_ref());
13715 *output = PioBalancedGeneratorView {
13716 component_id,
13717 has_component_id,
13718 bus_id: generator.bus.0,
13719 energy_source: PioStringView::new(generator_energy_source_name(
13720 generator.energy_source,
13721 )),
13722 active_power_mw: generator.pg,
13723 reactive_power_mvar: generator.qg,
13724 active_power_max_mw: generator.pmax,
13725 active_power_min_mw: generator.pmin,
13726 reactive_power_max_mvar: generator.qmax,
13727 reactive_power_min_mvar: generator.qmin,
13728 voltage_setpoint_pu: generator.vg,
13729 machine_base_mva: generator.mbase,
13730 in_service: generator.in_service,
13731 has_cost: cost.is_some(),
13732 cost: cost.map_or(
13733 PioGeneratorCostView {
13734 model: 0,
13735 startup: 0.0,
13736 shutdown: 0.0,
13737 ncost: 0,
13738 coefficients: PioF64View::EMPTY,
13739 },
13740 |cost| PioGeneratorCostView {
13741 model: cost.model,
13742 startup: cost.startup,
13743 shutdown: cost.shutdown,
13744 ncost: cost.ncost,
13745 coefficients: PioF64View::new(&cost.coeffs),
13746 },
13747 ),
13748 regulated_bus_id: generator.regulated_bus.map_or(0, |bus| bus.0),
13749 has_regulated_bus: generator.regulated_bus.is_some(),
13750 capability_count: GENERATOR_CAPABILITY_NAMES.len(),
13751 active_power_control,
13752 has_active_power_control,
13753 voltage_regulation_on: generator.voltage_regulation_on,
13754 regulating_terminal,
13755 has_regulating_terminal,
13756 };
13757 Ok(true)
13758 })
13759 }
13760}
13761
13762#[unsafe(no_mangle)]
13767pub unsafe extern "C" fn pio_balanced_network_generator_capability_at(
13768 network: *const PioBalancedNetwork,
13769 generator_index: usize,
13770 capability_index: usize,
13771 output: *mut PioGeneratorCapabilityView,
13772 error: *mut *mut PioError,
13773) -> bool {
13774 unsafe {
13775 entry(error, false, || {
13776 let network = require_balanced_network(network)?;
13777 let generator = network.generators().get(generator_index).ok_or_else(|| {
13778 boundary_error(
13779 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13780 format!("generator index {generator_index} is out of range"),
13781 )
13782 })?;
13783 let name = GENERATOR_CAPABILITY_NAMES
13784 .get(capability_index)
13785 .ok_or_else(|| {
13786 boundary_error(
13787 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13788 format!("generator capability index {capability_index} is out of range"),
13789 )
13790 })?;
13791 let value = *generator.caps.get(capability_index).ok_or_else(|| {
13792 boundary_error(
13793 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13794 "generator capability index is out of range",
13795 )
13796 })?;
13797 *require_output(output, "output")? = PioGeneratorCapabilityView {
13798 name: PioStringView::new(name),
13799 value: value.unwrap_or(0.0),
13800 has_value: value.is_some(),
13801 };
13802 Ok(true)
13803 })
13804 }
13805}
13806
13807#[unsafe(no_mangle)]
13812pub unsafe extern "C" fn pio_balanced_network_storage_at(
13813 network: *const PioBalancedNetwork,
13814 index: usize,
13815 output: *mut PioBalancedStorageView,
13816 error: *mut *mut PioError,
13817) -> bool {
13818 unsafe {
13819 entry(error, false, || {
13820 let network = require_balanced_network(network)?;
13821 let storage = network.storage().get(index).ok_or_else(|| {
13822 boundary_error(
13823 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13824 format!("storage index {index} is out of range"),
13825 )
13826 })?;
13827 let output = require_output(output, "output")?;
13828 let (component_id, has_component_id) = optional_string_view(storage.uid.as_deref());
13829 let (active_power_control, has_active_power_control) =
13830 active_power_control_view(storage.active_power_control.as_ref());
13831 *output = PioBalancedStorageView {
13832 component_id,
13833 has_component_id,
13834 bus_id: storage.bus.0,
13835 active_power_mw: storage.ps,
13836 reactive_power_mvar: storage.qs,
13837 energy_mwh: storage.energy,
13838 energy_rating_mwh: storage.energy_rating,
13839 charge_rating_mw: storage.charge_rating,
13840 discharge_rating_mw: storage.discharge_rating,
13841 charge_efficiency: storage.charge_efficiency,
13842 discharge_efficiency: storage.discharge_efficiency,
13843 thermal_rating_mva: storage.thermal_rating,
13844 current_rating: storage.current_rating.unwrap_or(0.0),
13845 has_current_rating: storage.current_rating.is_some(),
13846 reactive_power_min_mvar: storage.qmin,
13847 reactive_power_max_mvar: storage.qmax,
13848 resistance_pu: storage.r,
13849 reactance_pu: storage.x,
13850 active_power_loss_mw: storage.p_loss,
13851 reactive_power_loss_mvar: storage.q_loss,
13852 in_service: storage.in_service,
13853 active_power_control,
13854 has_active_power_control,
13855 };
13856 Ok(true)
13857 })
13858 }
13859}
13860
13861#[unsafe(no_mangle)]
13866pub unsafe extern "C" fn pio_balanced_network_switch_at(
13867 network: *const PioBalancedNetwork,
13868 index: usize,
13869 output: *mut PioBalancedSwitchView,
13870 error: *mut *mut PioError,
13871) -> bool {
13872 unsafe {
13873 entry(error, false, || {
13874 let network = require_balanced_network(network)?;
13875 let switch = network.switches().get(index).ok_or_else(|| {
13876 boundary_error(
13877 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13878 format!("switch index {index} is out of range"),
13879 )
13880 })?;
13881 let (component_id, has_component_id) = optional_string_view(switch.uid.as_deref());
13882 *require_output(output, "output")? = PioBalancedSwitchView {
13883 component_id,
13884 has_component_id,
13885 from_bus_id: switch.from.0,
13886 to_bus_id: switch.to.0,
13887 closed: switch.closed,
13888 thermal_rating_mva: switch.thermal_rating.unwrap_or(0.0),
13889 has_thermal_rating: switch.thermal_rating.is_some(),
13890 current_rating_a: switch.current_rating.unwrap_or(0.0),
13891 has_current_rating: switch.current_rating.is_some(),
13892 from_active_power_mw: switch.pf.unwrap_or(0.0),
13893 has_from_active_power: switch.pf.is_some(),
13894 from_reactive_power_mvar: switch.qf.unwrap_or(0.0),
13895 has_from_reactive_power: switch.qf.is_some(),
13896 to_active_power_mw: switch.pt.unwrap_or(0.0),
13897 has_to_active_power: switch.pt.is_some(),
13898 to_reactive_power_mvar: switch.qt.unwrap_or(0.0),
13899 has_to_reactive_power: switch.qt.is_some(),
13900 };
13901 Ok(true)
13902 })
13903 }
13904}
13905
13906#[unsafe(no_mangle)]
13911pub unsafe extern "C" fn pio_balanced_network_hvdc_at(
13912 network: *const PioBalancedNetwork,
13913 index: usize,
13914 output: *mut PioBalancedHvdcView,
13915 error: *mut *mut PioError,
13916) -> bool {
13917 unsafe {
13918 entry(error, false, || {
13919 let network = require_balanced_network(network)?;
13920 let hvdc = network.hvdc().get(index).ok_or_else(|| {
13921 boundary_error(
13922 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
13923 format!("HVDC index {index} is out of range"),
13924 )
13925 })?;
13926 let (component_id, has_component_id) = optional_string_view(hvdc.uid.as_deref());
13927 let (converter1, has_converter1) = hvdc_converter_view(hvdc.converter1.as_ref());
13928 let (converter2, has_converter2) = hvdc_converter_view(hvdc.converter2.as_ref());
13929 let (converters_mode, has_converters_mode) = hvdc
13930 .converters_mode
13931 .map_or((PioStringView::EMPTY, false), |mode| {
13932 (PioStringView::new(hvdc_converters_mode_name(mode)), true)
13933 });
13934 let cost = hvdc.cost.as_ref();
13935 *require_output(output, "output")? = PioBalancedHvdcView {
13936 component_id,
13937 has_component_id,
13938 from_bus_id: hvdc.from.0,
13939 to_bus_id: hvdc.to.0,
13940 in_service: hvdc.in_service,
13941 from_active_power_mw: hvdc.pf,
13942 to_active_power_mw: hvdc.pt,
13943 from_reactive_power_mvar: hvdc.qf,
13944 to_reactive_power_mvar: hvdc.qt,
13945 from_voltage_pu: hvdc.vf,
13946 to_voltage_pu: hvdc.vt,
13947 minimum_active_power_mw: hvdc.pmin,
13948 maximum_active_power_mw: hvdc.pmax,
13949 minimum_from_reactive_power_mvar: hvdc.qminf,
13950 maximum_from_reactive_power_mvar: hvdc.qmaxf,
13951 minimum_to_reactive_power_mvar: hvdc.qmint,
13952 maximum_to_reactive_power_mvar: hvdc.qmaxt,
13953 constant_loss_mw: hvdc.loss0,
13954 proportional_loss: hvdc.loss1,
13955 resistance_ohm: hvdc.resistance_ohm.unwrap_or(0.0),
13956 has_resistance: hvdc.resistance_ohm.is_some(),
13957 nominal_voltage_kv: hvdc.nominal_voltage_kv.unwrap_or(0.0),
13958 has_nominal_voltage: hvdc.nominal_voltage_kv.is_some(),
13959 converters_mode,
13960 has_converters_mode,
13961 converter1,
13962 has_converter1,
13963 converter2,
13964 has_converter2,
13965 cost: cost.map_or(
13966 PioGeneratorCostView {
13967 model: 0,
13968 startup: 0.0,
13969 shutdown: 0.0,
13970 ncost: 0,
13971 coefficients: PioF64View::EMPTY,
13972 },
13973 |cost| PioGeneratorCostView {
13974 model: cost.model,
13975 startup: cost.startup,
13976 shutdown: cost.shutdown,
13977 ncost: cost.ncost,
13978 coefficients: PioF64View::new(&cost.coeffs),
13979 },
13980 ),
13981 has_cost: cost.is_some(),
13982 };
13983 Ok(true)
13984 })
13985 }
13986}
13987
13988#[unsafe(no_mangle)]
13993pub unsafe extern "C" fn pio_balanced_network_three_winding_transformer_at(
13994 network: *const PioBalancedNetwork,
13995 index: usize,
13996 output: *mut PioBalancedThreeWindingTransformerView,
13997 error: *mut *mut PioError,
13998) -> bool {
13999 unsafe {
14000 entry(error, false, || {
14001 let network = require_balanced_network(network)?;
14002 let transformer = network.transformers_3w().get(index).ok_or_else(|| {
14003 boundary_error(
14004 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14005 format!("three winding transformer index {index} is out of range"),
14006 )
14007 })?;
14008 let (component_id, has_component_id) = optional_string_view(transformer.uid.as_deref());
14009 let (name, has_name) = optional_string_view(transformer.name.as_deref());
14010 *require_output(output, "output")? = PioBalancedThreeWindingTransformerView {
14011 component_id,
14012 has_component_id,
14013 name,
14014 has_name,
14015 winding_count: transformer.windings.len(),
14016 impedance_count: transformer.z.len(),
14017 star_voltage_magnitude_pu: transformer.star_vm,
14018 star_voltage_angle_degrees: transformer.star_va,
14019 magnetizing_conductance_pu: transformer.mag_g,
14020 magnetizing_susceptance_pu: transformer.mag_b,
14021 in_service: transformer.in_service,
14022 };
14023 Ok(true)
14024 })
14025 }
14026}
14027
14028#[unsafe(no_mangle)]
14033pub unsafe extern "C" fn pio_balanced_network_three_winding_transformer_winding_at(
14034 network: *const PioBalancedNetwork,
14035 transformer_index: usize,
14036 winding_index: usize,
14037 output: *mut PioThreeWindingTransformerWindingView,
14038 error: *mut *mut PioError,
14039) -> bool {
14040 unsafe {
14041 entry(error, false, || {
14042 let network = require_balanced_network(network)?;
14043 let transformer = network
14044 .transformers_3w()
14045 .get(transformer_index)
14046 .ok_or_else(|| {
14047 boundary_error(
14048 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14049 format!(
14050 "three winding transformer index {transformer_index} is out of range"
14051 ),
14052 )
14053 })?;
14054 let winding = transformer.windings.get(winding_index).ok_or_else(|| {
14055 boundary_error(
14056 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14057 format!("winding index {winding_index} is out of range"),
14058 )
14059 })?;
14060 let (control, has_control) = transformer_control_view(winding.control.as_ref());
14061 *require_output(output, "output")? = PioThreeWindingTransformerWindingView {
14062 bus_id: winding.bus.0,
14063 tap_ratio: winding.tap,
14064 phase_shift_degrees: winding.shift,
14065 nominal_voltage_kv: winding.nominal_kv,
14066 rating_a_mva: winding.rate_a,
14067 rating_b_mva: winding.rate_b,
14068 rating_c_mva: winding.rate_c,
14069 control,
14070 has_control,
14071 };
14072 Ok(true)
14073 })
14074 }
14075}
14076
14077#[unsafe(no_mangle)]
14082pub unsafe extern "C" fn pio_balanced_network_three_winding_transformer_impedance_at(
14083 network: *const PioBalancedNetwork,
14084 transformer_index: usize,
14085 impedance_index: usize,
14086 output: *mut PioThreeWindingTransformerImpedanceView,
14087 error: *mut *mut PioError,
14088) -> bool {
14089 unsafe {
14090 entry(error, false, || {
14091 let network = require_balanced_network(network)?;
14092 let transformer = network
14093 .transformers_3w()
14094 .get(transformer_index)
14095 .ok_or_else(|| {
14096 boundary_error(
14097 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14098 format!(
14099 "three winding transformer index {transformer_index} is out of range"
14100 ),
14101 )
14102 })?;
14103 let impedance = transformer.z.get(impedance_index).ok_or_else(|| {
14104 boundary_error(
14105 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14106 format!("impedance index {impedance_index} is out of range"),
14107 )
14108 })?;
14109 *require_output(output, "output")? = PioThreeWindingTransformerImpedanceView {
14110 resistance_pu: impedance.r,
14111 reactance_pu: impedance.x,
14112 base_mva: impedance.base_mva,
14113 };
14114 Ok(true)
14115 })
14116 }
14117}
14118
14119#[unsafe(no_mangle)]
14124pub unsafe extern "C" fn pio_balanced_network_area_at(
14125 network: *const PioBalancedNetwork,
14126 index: usize,
14127 output: *mut PioBalancedAreaView,
14128 error: *mut *mut PioError,
14129) -> bool {
14130 unsafe {
14131 entry(error, false, || {
14132 let network = require_balanced_network(network)?;
14133 let area = network.areas().get(index).ok_or_else(|| {
14134 boundary_error(
14135 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14136 format!("area index {index} is out of range"),
14137 )
14138 })?;
14139 let (name, has_name) = optional_string_view(area.name.as_deref());
14140 let (component_id, has_component_id) = optional_string_view(area.uid.as_deref());
14141 let (area_type, has_area_type) = optional_string_view(area.area_type.as_deref());
14142 *require_output(output, "output")? = PioBalancedAreaView {
14143 number: area.number,
14144 slack_bus_id: area.slack_bus.map_or(0, |bus| bus.0),
14145 has_slack_bus: area.slack_bus.is_some(),
14146 net_interchange_mw: area.net_interchange,
14147 tolerance_mw: area.tolerance,
14148 name,
14149 has_name,
14150 component_id,
14151 has_component_id,
14152 area_type,
14153 has_area_type,
14154 };
14155 Ok(true)
14156 })
14157 }
14158}
14159
14160#[unsafe(no_mangle)]
14165pub unsafe extern "C" fn pio_balanced_network_retain(
14166 network: *const PioBalancedNetwork,
14167) -> *mut PioBalancedNetwork {
14168 unsafe { PioBalancedNetwork::retain_raw(network) }
14169}
14170
14171#[unsafe(no_mangle)]
14176pub unsafe extern "C" fn pio_balanced_network_release(network: *mut PioBalancedNetwork) {
14177 unsafe { PioBalancedNetwork::release_raw(network) };
14178}
14179
14180unsafe fn require_multiconductor_network<'a>(
14181 network: *const PioMulticonductorNetwork,
14182) -> Result<&'a powerio_dist::MulticonductorNetwork, *mut PioError> {
14183 unsafe { PioMulticonductorNetwork::get(network) }
14184 .and_then(MulticonductorNetworkInner::network)
14185 .ok_or_else(|| {
14186 boundary_error(
14187 &codes::BIND_CAPI_NULL_HANDLE,
14188 "PioMulticonductorNetwork must not be NULL",
14189 )
14190 })
14191}
14192
14193fn optional_f64_view(value: Option<&[f64]>) -> (PioF64View, bool) {
14194 value.map_or((PioF64View::EMPTY, false), |value| {
14195 (PioF64View::new(value), true)
14196 })
14197}
14198
14199fn dist_coords_kind_name(kind: powerio_dist::DistCoordsKind) -> &'static str {
14200 match kind {
14201 powerio_dist::DistCoordsKind::Source => "source",
14202 powerio_dist::DistCoordsKind::Synthetic => "synthetic",
14203 powerio_dist::DistCoordsKind::Manual => "manual",
14204 powerio_dist::DistCoordsKind::Derived => "derived",
14205 _ => "unknown",
14206 }
14207}
14208
14209fn multiconductor_location_view(
14210 location: &powerio_dist::DistLocation,
14211) -> PioMulticonductorLocationView {
14212 let (kind, has_kind) = location.kind.map_or((PioStringView::EMPTY, false), |kind| {
14213 (PioStringView::new(dist_coords_kind_name(kind)), true)
14214 });
14215 PioMulticonductorLocationView {
14216 x: location.x,
14217 y: location.y,
14218 kind,
14219 has_kind,
14220 }
14221}
14222
14223fn empty_multiconductor_location_view() -> PioMulticonductorLocationView {
14224 PioMulticonductorLocationView {
14225 x: 0.0,
14226 y: 0.0,
14227 kind: PioStringView::EMPTY,
14228 has_kind: false,
14229 }
14230}
14231
14232fn multiconductor_geo_view(geo: Option<&powerio_dist::DistGeoMeta>) -> PioMulticonductorGeoView {
14233 let Some(geo) = geo else {
14234 return PioMulticonductorGeoView {
14235 has_geo: false,
14236 space: PioStringView::EMPTY,
14237 crs: PioStringView::EMPTY,
14238 has_crs: false,
14239 kind: PioStringView::EMPTY,
14240 has_kind: false,
14241 has_canvas: false,
14242 canvas_width: 0.0,
14243 has_canvas_width: false,
14244 canvas_height: 0.0,
14245 has_canvas_height: false,
14246 canvas_units: PioStringView::EMPTY,
14247 has_canvas_units: false,
14248 };
14249 };
14250 let (space, crs, has_crs, canvas) = match &geo.space {
14251 powerio_dist::CoordinateSpace::Geographic { crs } => (
14252 "geographic",
14253 crs.as_deref()
14254 .map_or(PioStringView::EMPTY, PioStringView::new),
14255 crs.is_some(),
14256 None,
14257 ),
14258 powerio_dist::CoordinateSpace::Projected { crs } => (
14259 "projected",
14260 crs.as_deref()
14261 .map_or(PioStringView::EMPTY, PioStringView::new),
14262 crs.is_some(),
14263 None,
14264 ),
14265 powerio_dist::CoordinateSpace::Diagram { canvas } => {
14266 ("diagram", PioStringView::EMPTY, false, canvas.as_ref())
14267 }
14268 powerio_dist::CoordinateSpace::Unknown => ("unknown", PioStringView::EMPTY, false, None),
14269 _ => ("unknown", PioStringView::EMPTY, false, None),
14270 };
14271 let (kind, has_kind) = geo.kind.map_or((PioStringView::EMPTY, false), |kind| {
14272 (PioStringView::new(dist_coords_kind_name(kind)), true)
14273 });
14274 PioMulticonductorGeoView {
14275 has_geo: true,
14276 space: PioStringView::new(space),
14277 crs,
14278 has_crs,
14279 kind,
14280 has_kind,
14281 has_canvas: canvas.is_some(),
14282 canvas_width: canvas.and_then(|canvas| canvas.width).unwrap_or(0.0),
14283 has_canvas_width: canvas.is_some_and(|canvas| canvas.width.is_some()),
14284 canvas_height: canvas.and_then(|canvas| canvas.height).unwrap_or(0.0),
14285 has_canvas_height: canvas.is_some_and(|canvas| canvas.height.is_some()),
14286 canvas_units: canvas
14287 .and_then(|canvas| canvas.units.as_deref())
14288 .map_or(PioStringView::EMPTY, PioStringView::new),
14289 has_canvas_units: canvas.is_some_and(|canvas| canvas.units.is_some()),
14290 }
14291}
14292
14293fn multiconductor_configuration_name(configuration: powerio_dist::Configuration) -> &'static str {
14294 match configuration {
14295 powerio_dist::Configuration::Wye => "wye",
14296 powerio_dist::Configuration::Delta => "delta",
14297 powerio_dist::Configuration::SinglePhase => "single_phase",
14298 _ => "unknown",
14299 }
14300}
14301
14302fn multiconductor_winding_connection_name(
14303 connection: powerio_dist::DistWindingConn,
14304) -> &'static str {
14305 match connection {
14306 powerio_dist::DistWindingConn::Wye => "wye",
14307 powerio_dist::DistWindingConn::Delta => "delta",
14308 _ => "unknown",
14309 }
14310}
14311
14312fn inverter_topology_name(topology: powerio_dist::IbrTopology) -> &'static str {
14313 match topology {
14314 powerio_dist::IbrTopology::SinglePhase => "SINGLE_PHASE",
14315 powerio_dist::IbrTopology::ThreeLeg => "THREE_LEG",
14316 powerio_dist::IbrTopology::FourLeg => "FOUR_LEG",
14317 _ => "UNKNOWN",
14318 }
14319}
14320
14321fn inverter_prime_mover_name(prime_mover: powerio_dist::IbrPrimeMover) -> &'static str {
14322 match prime_mover {
14323 powerio_dist::IbrPrimeMover::Pv => "PV",
14324 powerio_dist::IbrPrimeMover::Battery => "BATTERY",
14325 powerio_dist::IbrPrimeMover::Generic => "GENERIC",
14326 powerio_dist::IbrPrimeMover::Statcom => "STATCOM",
14327 powerio_dist::IbrPrimeMover::Dstatcom => "DSTATCOM",
14328 _ => "UNKNOWN",
14329 }
14330}
14331
14332fn inverter_voltage_aggregation_name(
14333 aggregation: powerio_dist::IbrVoltageAggregation,
14334) -> &'static str {
14335 match aggregation {
14336 powerio_dist::IbrVoltageAggregation::PerPhase => "PER_PHASE",
14337 powerio_dist::IbrVoltageAggregation::Average => "AVERAGE",
14338 _ => "UNKNOWN",
14339 }
14340}
14341
14342fn control_voltage_reference_name(
14343 reference: powerio_dist::ControlVoltageReference,
14344) -> &'static str {
14345 match reference {
14346 powerio_dist::ControlVoltageReference::PnPerPhase => "PN_PER_PHASE",
14347 powerio_dist::ControlVoltageReference::PpPerPhase => "PP_PER_PHASE",
14348 powerio_dist::ControlVoltageReference::PpAveraged => "PP_AVERAGED",
14349 powerio_dist::ControlVoltageReference::PgAveraged => "PG_AVERAGED",
14350 powerio_dist::ControlVoltageReference::PnAveraged => "PN_AVERAGED",
14351 powerio_dist::ControlVoltageReference::PgPerPhase => "PG_PER_PHASE",
14352 _ => "UNKNOWN",
14353 }
14354}
14355
14356fn reactive_power_unit_name(unit: powerio_dist::ReactivePowerUnit) -> &'static str {
14357 match unit {
14358 powerio_dist::ReactivePowerUnit::VaFraction => "VA_FRACTION",
14359 powerio_dist::ReactivePowerUnit::Var => "VAR",
14360 _ => "UNKNOWN",
14361 }
14362}
14363
14364fn active_power_unit_name(unit: powerio_dist::ActivePowerUnit) -> &'static str {
14365 match unit {
14366 powerio_dist::ActivePowerUnit::VaFraction => "VA_FRACTION",
14367 powerio_dist::ActivePowerUnit::W => "W",
14368 _ => "UNKNOWN",
14369 }
14370}
14371
14372fn reactive_power_reference_name(reference: powerio_dist::ReactivePowerReference) -> &'static str {
14373 match reference {
14374 powerio_dist::ReactivePowerReference::VarMax => "VAR_MAX",
14375 powerio_dist::ReactivePowerReference::VarAvailable => "VAR_AVAILABLE",
14376 _ => "UNKNOWN",
14377 }
14378}
14379
14380fn active_power_reference_name(reference: powerio_dist::ActivePowerReference) -> &'static str {
14381 match reference {
14382 powerio_dist::ActivePowerReference::PAvailable => "P_AVAILABLE",
14383 powerio_dist::ActivePowerReference::PMax => "P_MAX",
14384 powerio_dist::ActivePowerReference::SMax => "S_MAX",
14385 _ => "UNKNOWN",
14386 }
14387}
14388
14389fn string_slice_at(
14390 values: &[String],
14391 index: usize,
14392 description: &str,
14393) -> Result<PioStringView, *mut PioError> {
14394 values
14395 .get(index)
14396 .map(|value| PioStringView::new(value))
14397 .ok_or_else(|| {
14398 boundary_error(
14399 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14400 format!("{description} index {index} is out of range"),
14401 )
14402 })
14403}
14404
14405fn conductor_matrix_row(
14406 matrix: &powerio_dist::ConductorMatrix,
14407 row_index: usize,
14408 description: &str,
14409) -> Result<PioF64View, *mut PioError> {
14410 matrix
14411 .get(row_index)
14412 .map(|row| PioF64View::new(row))
14413 .ok_or_else(|| {
14414 boundary_error(
14415 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14416 format!("{description} row index {row_index} is out of range"),
14417 )
14418 })
14419}
14420
14421fn multiconductor_bus_view(bus: &powerio_dist::DistBus) -> PioMulticonductorBusView {
14422 let (vpn_min, has_vpn_min) = optional_f64_view(bus.vpn_min.as_deref());
14423 let (vpn_max, has_vpn_max) = optional_f64_view(bus.vpn_max.as_deref());
14424 let (vpp_min, has_vpp_min) = optional_f64_view(bus.vpp_min.as_deref());
14425 let (vpp_max, has_vpp_max) = optional_f64_view(bus.vpp_max.as_deref());
14426 let (location, has_location) = bus
14427 .location
14428 .as_ref()
14429 .map_or((empty_multiconductor_location_view(), false), |location| {
14430 (multiconductor_location_view(location), true)
14431 });
14432 PioMulticonductorBusView {
14433 id: PioStringView::new(&bus.id),
14434 terminal_count: bus.terminals.len(),
14435 grounded_terminal_count: bus.grounded.len(),
14436 voltage_min_v: bus.v_min.unwrap_or(0.0),
14437 has_voltage_min: bus.v_min.is_some(),
14438 voltage_max_v: bus.v_max.unwrap_or(0.0),
14439 has_voltage_max: bus.v_max.is_some(),
14440 phase_to_ground_voltage_min_v: optional_f64_view(bus.v_min_phase.as_deref()).0,
14441 has_phase_to_ground_voltage_min: bus.v_min_phase.is_some(),
14442 phase_to_ground_voltage_max_v: optional_f64_view(bus.v_max_phase.as_deref()).0,
14443 has_phase_to_ground_voltage_max: bus.v_max_phase.is_some(),
14444 phase_to_neutral_voltage_min_v: vpn_min,
14445 has_phase_to_neutral_voltage_min: has_vpn_min,
14446 phase_to_neutral_voltage_max_v: vpn_max,
14447 has_phase_to_neutral_voltage_max: has_vpn_max,
14448 phase_to_phase_voltage_min_v: vpp_min,
14449 has_phase_to_phase_voltage_min: has_vpp_min,
14450 phase_to_phase_voltage_max_v: vpp_max,
14451 has_phase_to_phase_voltage_max: has_vpp_max,
14452 positive_sequence_voltage_min_v: bus.vpos_min.unwrap_or(0.0),
14453 has_positive_sequence_voltage_min: bus.vpos_min.is_some(),
14454 positive_sequence_voltage_max_v: bus.vpos_max.unwrap_or(0.0),
14455 has_positive_sequence_voltage_max: bus.vpos_max.is_some(),
14456 negative_sequence_voltage_max_v: bus.vneg_max.unwrap_or(0.0),
14457 has_negative_sequence_voltage_max: bus.vneg_max.is_some(),
14458 zero_sequence_voltage_max_v: bus.vzero_max.unwrap_or(0.0),
14459 has_zero_sequence_voltage_max: bus.vzero_max.is_some(),
14460 neutral_to_ground_voltage_max_v: bus.vn_max.unwrap_or(0.0),
14461 has_neutral_to_ground_voltage_max: bus.vn_max.is_some(),
14462 location,
14463 has_location,
14464 }
14465}
14466
14467fn multiconductor_line_code_view(
14468 line_code: &powerio_dist::DistLineCode,
14469) -> PioMulticonductorLineCodeView {
14470 let (current_limit, has_current_limit) = optional_f64_view(line_code.i_max.as_deref());
14471 let (apparent_power_limit, has_apparent_power_limit) =
14472 optional_f64_view(line_code.s_max.as_deref());
14473 let (source, has_source) = optional_string_view(line_code.source.as_deref());
14474 PioMulticonductorLineCodeView {
14475 name: PioStringView::new(&line_code.name),
14476 conductor_count: line_code.n_conductors,
14477 resistance_matrix_row_count: line_code.r_series.len(),
14478 reactance_matrix_row_count: line_code.x_series.len(),
14479 conductance_from_matrix_row_count: line_code.g_from.len(),
14480 susceptance_from_matrix_row_count: line_code.b_from.len(),
14481 conductance_to_matrix_row_count: line_code.g_to.len(),
14482 susceptance_to_matrix_row_count: line_code.b_to.len(),
14483 current_limit_a: current_limit,
14484 has_current_limit,
14485 apparent_power_limit_va: apparent_power_limit,
14486 has_apparent_power_limit,
14487 source,
14488 has_source,
14489 }
14490}
14491
14492fn multiconductor_line_view(line: &powerio_dist::DistLine) -> PioMulticonductorLineView {
14493 let (current_limit, has_current_limit) = optional_f64_view(line.i_max.as_deref());
14494 let (apparent_power_limit, has_apparent_power_limit) = optional_f64_view(line.s_max.as_deref());
14495 PioMulticonductorLineView {
14496 name: PioStringView::new(&line.name),
14497 bus_from: PioStringView::new(&line.bus_from),
14498 bus_to: PioStringView::new(&line.bus_to),
14499 terminal_map_from_count: line.terminal_map_from.len(),
14500 terminal_map_to_count: line.terminal_map_to.len(),
14501 line_code: PioStringView::new(&line.linecode),
14502 length_m: line.length,
14503 route_point_count: line.route.as_ref().map_or(0, Vec::len),
14504 has_route: line.route.is_some(),
14505 current_limit_a: current_limit,
14506 has_current_limit,
14507 apparent_power_limit_va: apparent_power_limit,
14508 has_apparent_power_limit,
14509 }
14510}
14511
14512fn multiconductor_switch_view(switch: &powerio_dist::DistSwitch) -> PioMulticonductorSwitchView {
14513 let (current_limit, has_current_limit) = optional_f64_view(switch.i_max.as_deref());
14514 PioMulticonductorSwitchView {
14515 name: PioStringView::new(&switch.name),
14516 bus_from: PioStringView::new(&switch.bus_from),
14517 bus_to: PioStringView::new(&switch.bus_to),
14518 terminal_map_from_count: switch.terminal_map_from.len(),
14519 terminal_map_to_count: switch.terminal_map_to.len(),
14520 open: switch.open,
14521 current_limit_a: current_limit,
14522 has_current_limit,
14523 }
14524}
14525
14526fn multiconductor_load_view(load: &powerio_dist::DistLoad) -> PioMulticonductorLoadView {
14527 let empty = PioF64View::EMPTY;
14528 let (
14529 voltage_model,
14530 nominal_voltage,
14531 alpha_z,
14532 alpha_i,
14533 alpha_p,
14534 beta_z,
14535 beta_i,
14536 beta_p,
14537 gamma_p,
14538 gamma_q,
14539 ) = match &load.voltage_model {
14540 powerio_dist::DistLoadVoltageModel::ConstantPower { v_nom } => (
14541 "constant_power",
14542 PioF64View::new(v_nom),
14543 empty,
14544 empty,
14545 empty,
14546 empty,
14547 empty,
14548 empty,
14549 empty,
14550 empty,
14551 ),
14552 powerio_dist::DistLoadVoltageModel::ConstantCurrent { v_nom } => (
14553 "constant_current",
14554 PioF64View::new(v_nom),
14555 empty,
14556 empty,
14557 empty,
14558 empty,
14559 empty,
14560 empty,
14561 empty,
14562 empty,
14563 ),
14564 powerio_dist::DistLoadVoltageModel::ConstantImpedance { v_nom } => (
14565 "constant_impedance",
14566 PioF64View::new(v_nom),
14567 empty,
14568 empty,
14569 empty,
14570 empty,
14571 empty,
14572 empty,
14573 empty,
14574 empty,
14575 ),
14576 powerio_dist::DistLoadVoltageModel::Zip {
14577 v_nom,
14578 alpha_z,
14579 alpha_i,
14580 alpha_p,
14581 beta_z,
14582 beta_i,
14583 beta_p,
14584 } => (
14585 "zip",
14586 PioF64View::new(v_nom),
14587 PioF64View::new(alpha_z),
14588 PioF64View::new(alpha_i),
14589 PioF64View::new(alpha_p),
14590 PioF64View::new(beta_z),
14591 PioF64View::new(beta_i),
14592 PioF64View::new(beta_p),
14593 empty,
14594 empty,
14595 ),
14596 powerio_dist::DistLoadVoltageModel::Exponential {
14597 v_nom,
14598 gamma_p,
14599 gamma_q,
14600 } => (
14601 "exponential",
14602 PioF64View::new(v_nom),
14603 empty,
14604 empty,
14605 empty,
14606 empty,
14607 empty,
14608 empty,
14609 PioF64View::new(gamma_p),
14610 PioF64View::new(gamma_q),
14611 ),
14612 _ => (
14613 "unknown", empty, empty, empty, empty, empty, empty, empty, empty, empty,
14614 ),
14615 };
14616 PioMulticonductorLoadView {
14617 name: PioStringView::new(&load.name),
14618 bus: PioStringView::new(&load.bus),
14619 terminal_map_count: load.terminal_map.len(),
14620 configuration: PioStringView::new(multiconductor_configuration_name(load.configuration)),
14621 active_power_nominal_w: PioF64View::new(&load.p_nom),
14622 reactive_power_nominal_var: PioF64View::new(&load.q_nom),
14623 voltage_model: PioStringView::new(voltage_model),
14624 nominal_voltage_v: nominal_voltage,
14625 active_power_constant_impedance: alpha_z,
14626 active_power_constant_current: alpha_i,
14627 active_power_constant_power: alpha_p,
14628 reactive_power_constant_impedance: beta_z,
14629 reactive_power_constant_current: beta_i,
14630 reactive_power_constant_power: beta_p,
14631 active_power_exponent: gamma_p,
14632 reactive_power_exponent: gamma_q,
14633 }
14634}
14635
14636fn multiconductor_generator_view(
14637 generator: &powerio_dist::DistGenerator,
14638) -> PioMulticonductorGeneratorView {
14639 let (p_min, has_p_min) = optional_f64_view(generator.p_min.as_deref());
14640 let (p_max, has_p_max) = optional_f64_view(generator.p_max.as_deref());
14641 let (q_min, has_q_min) = optional_f64_view(generator.q_min.as_deref());
14642 let (q_max, has_q_max) = optional_f64_view(generator.q_max.as_deref());
14643 let (cost, has_cost) = optional_f64_view(generator.cost.as_deref());
14644 let (s_max, has_s_max) = optional_f64_view(generator.s_max.as_deref());
14645 let (i_max, has_i_max) = optional_f64_view(generator.i_max.as_deref());
14646 PioMulticonductorGeneratorView {
14647 name: PioStringView::new(&generator.name),
14648 bus: PioStringView::new(&generator.bus),
14649 terminal_map_count: generator.terminal_map.len(),
14650 configuration: PioStringView::new(multiconductor_configuration_name(
14651 generator.configuration,
14652 )),
14653 active_power_nominal_w: PioF64View::new(&generator.p_nom),
14654 reactive_power_nominal_var: PioF64View::new(&generator.q_nom),
14655 active_power_min_w: p_min,
14656 has_active_power_min: has_p_min,
14657 active_power_max_w: p_max,
14658 has_active_power_max: has_p_max,
14659 reactive_power_min_var: q_min,
14660 has_reactive_power_min: has_q_min,
14661 reactive_power_max_var: q_max,
14662 has_reactive_power_max: has_q_max,
14663 active_power_dispatch_cost_per_kwh: cost,
14664 has_active_power_dispatch_cost: has_cost,
14665 apparent_power_limit_va: s_max,
14666 has_apparent_power_limit: has_s_max,
14667 current_limit_a: i_max,
14668 has_current_limit: has_i_max,
14669 }
14670}
14671
14672fn inverter_based_resource_view(ibr: &powerio_dist::DistIbr) -> PioInverterBasedResourceView {
14673 let (i_max, has_i_max) = optional_f64_view(ibr.i_max.as_deref());
14674 let (p_min, has_p_min) = optional_f64_view(ibr.p_min.as_deref());
14675 let (p_max, has_p_max) = optional_f64_view(ibr.p_max.as_deref());
14676 let (q_min, has_q_min) = optional_f64_view(ibr.q_min.as_deref());
14677 let (q_max, has_q_max) = optional_f64_view(ibr.q_max.as_deref());
14678 let (control_profile, has_control_profile) =
14679 optional_string_view(ibr.control_profile.as_deref());
14680 let (voltage_aggregation, has_voltage_aggregation) =
14681 ibr.voltage_aggregation
14682 .map_or((PioStringView::EMPTY, false), |aggregation| {
14683 (
14684 PioStringView::new(inverter_voltage_aggregation_name(aggregation)),
14685 true,
14686 )
14687 });
14688 PioInverterBasedResourceView {
14689 name: PioStringView::new(&ibr.name),
14690 bus: PioStringView::new(&ibr.bus),
14691 terminal_map_count: ibr.terminal_map.len(),
14692 topology: PioStringView::new(inverter_topology_name(ibr.topology)),
14693 prime_mover: PioStringView::new(inverter_prime_mover_name(ibr.prime_mover)),
14694 apparent_power_limit_va: PioF64View::new(&ibr.s_max),
14695 current_limit_a: i_max,
14696 has_current_limit: has_i_max,
14697 active_power_available_w: ibr.p_avail.unwrap_or(0.0),
14698 has_active_power_available: ibr.p_avail.is_some(),
14699 active_power_min_w: p_min,
14700 has_active_power_min: has_p_min,
14701 active_power_max_w: p_max,
14702 has_active_power_max: has_p_max,
14703 reactive_power_min_var: q_min,
14704 has_reactive_power_min: has_q_min,
14705 reactive_power_max_var: q_max,
14706 has_reactive_power_max: has_q_max,
14707 control_profile,
14708 has_control_profile,
14709 voltage_aggregation,
14710 has_voltage_aggregation,
14711 }
14712}
14713
14714fn control_profile_view(profile: &powerio_dist::DistControlProfile) -> PioControlProfileView {
14715 let power_factor = profile.power_factor.as_ref();
14716 let volt_var = profile.volt_var.as_ref();
14717 let volt_watt = profile.volt_watt.as_ref();
14718 let (vv_voltage_reference, has_vv_voltage_reference) = volt_var
14719 .and_then(|control| control.voltage_reference)
14720 .map_or((PioStringView::EMPTY, false), |reference| {
14721 (
14722 PioStringView::new(control_voltage_reference_name(reference)),
14723 true,
14724 )
14725 });
14726 let (vv_q_unit, has_vv_q_unit) = volt_var
14727 .and_then(|control| control.q_unit)
14728 .map_or((PioStringView::EMPTY, false), |unit| {
14729 (PioStringView::new(reactive_power_unit_name(unit)), true)
14730 });
14731 let (vv_q_ref, has_vv_q_ref) = volt_var.and_then(|control| control.q_ref).map_or(
14732 (PioStringView::EMPTY, false),
14733 |reference| {
14734 (
14735 PioStringView::new(reactive_power_reference_name(reference)),
14736 true,
14737 )
14738 },
14739 );
14740 let (vw_voltage_reference, has_vw_voltage_reference) = volt_watt
14741 .and_then(|control| control.voltage_reference)
14742 .map_or((PioStringView::EMPTY, false), |reference| {
14743 (
14744 PioStringView::new(control_voltage_reference_name(reference)),
14745 true,
14746 )
14747 });
14748 let (vw_p_unit, has_vw_p_unit) = volt_watt
14749 .and_then(|control| control.p_unit)
14750 .map_or((PioStringView::EMPTY, false), |unit| {
14751 (PioStringView::new(active_power_unit_name(unit)), true)
14752 });
14753 let (vw_p_ref, has_vw_p_ref) = volt_watt.and_then(|control| control.p_ref).map_or(
14754 (PioStringView::EMPTY, false),
14755 |reference| {
14756 (
14757 PioStringView::new(active_power_reference_name(reference)),
14758 true,
14759 )
14760 },
14761 );
14762 PioControlProfileView {
14763 name: PioStringView::new(&profile.name),
14764 has_power_factor: power_factor.is_some(),
14765 power_factor: power_factor.map_or(0.0, |control| control.pf),
14766 has_volt_var: volt_var.is_some(),
14767 volt_var_voltage_reference: vv_voltage_reference,
14768 has_volt_var_voltage_reference: has_vv_voltage_reference,
14769 volt_var_breakpoints: volt_var.map_or(PioF64View::EMPTY, |control| {
14770 PioF64View::new(&control.breakpoints)
14771 }),
14772 volt_var_reactive_power_limits: volt_var.map_or(PioF64View::EMPTY, |control| {
14773 PioF64View::new(&control.q_limits)
14774 }),
14775 volt_var_reactive_power_unit: vv_q_unit,
14776 has_volt_var_reactive_power_unit: has_vv_q_unit,
14777 volt_var_reactive_power_reference: vv_q_ref,
14778 has_volt_var_reactive_power_reference: has_vv_q_ref,
14779 volt_var_active_power_min_for_reactive_power_w: volt_var
14780 .and_then(|control| control.p_min_for_q)
14781 .unwrap_or(0.0),
14782 has_volt_var_active_power_min_for_reactive_power: volt_var
14783 .is_some_and(|control| control.p_min_for_q.is_some()),
14784 volt_var_active_power_min_for_max_reactive_power_w: volt_var
14785 .and_then(|control| control.p_min_for_q_max)
14786 .unwrap_or(0.0),
14787 has_volt_var_active_power_min_for_max_reactive_power: volt_var
14788 .is_some_and(|control| control.p_min_for_q_max.is_some()),
14789 has_volt_watt: volt_watt.is_some(),
14790 volt_watt_voltage_reference: vw_voltage_reference,
14791 has_volt_watt_voltage_reference: has_vw_voltage_reference,
14792 volt_watt_breakpoints: volt_watt.map_or(PioF64View::EMPTY, |control| {
14793 PioF64View::new(&control.breakpoints)
14794 }),
14795 volt_watt_active_power_limits: volt_watt.map_or(PioF64View::EMPTY, |control| {
14796 PioF64View::new(&control.p_limits)
14797 }),
14798 volt_watt_active_power_unit: vw_p_unit,
14799 has_volt_watt_active_power_unit: has_vw_p_unit,
14800 volt_watt_active_power_reference: vw_p_ref,
14801 has_volt_watt_active_power_reference: has_vw_p_ref,
14802 }
14803}
14804
14805#[unsafe(no_mangle)]
14809pub unsafe extern "C" fn pio_multiconductor_network_name(
14810 network: *const PioMulticonductorNetwork,
14811) -> PioStringView {
14812 unsafe { PioMulticonductorNetwork::get(network) }
14813 .and_then(MulticonductorNetworkInner::network)
14814 .and_then(|network| network.name().as_deref())
14815 .map_or(PioStringView::EMPTY, PioStringView::new)
14816}
14817
14818#[unsafe(no_mangle)]
14822pub unsafe extern "C" fn pio_multiconductor_network_has_name(
14823 network: *const PioMulticonductorNetwork,
14824) -> bool {
14825 unsafe { PioMulticonductorNetwork::get(network) }
14826 .and_then(MulticonductorNetworkInner::network)
14827 .is_some_and(|network| network.name().is_some())
14828}
14829
14830#[unsafe(no_mangle)]
14834pub unsafe extern "C" fn pio_multiconductor_network_source_format(
14835 network: *const PioMulticonductorNetwork,
14836) -> PioStringView {
14837 unsafe { PioMulticonductorNetwork::get(network) }
14838 .and_then(MulticonductorNetworkInner::network)
14839 .and_then(|network| *network.source_format())
14840 .map_or(PioStringView::EMPTY, |format| {
14841 PioStringView::new(format.name())
14842 })
14843}
14844
14845#[unsafe(no_mangle)]
14849pub unsafe extern "C" fn pio_multiconductor_network_has_source_format(
14850 network: *const PioMulticonductorNetwork,
14851) -> bool {
14852 unsafe { PioMulticonductorNetwork::get(network) }
14853 .and_then(MulticonductorNetworkInner::network)
14854 .is_some_and(|network| network.source_format().is_some())
14855}
14856
14857#[unsafe(no_mangle)]
14862pub unsafe extern "C" fn pio_multiconductor_network_geo(
14863 network: *const PioMulticonductorNetwork,
14864 output: *mut PioMulticonductorGeoView,
14865 error: *mut *mut PioError,
14866) -> bool {
14867 unsafe {
14868 entry(error, false, || {
14869 let network = require_multiconductor_network(network)?;
14870 *require_output(output, "output")? = multiconductor_geo_view(network.geo().as_ref());
14871 Ok(true)
14872 })
14873 }
14874}
14875
14876#[unsafe(no_mangle)]
14882pub unsafe extern "C" fn pio_multiconductor_network_counts(
14883 network: *const PioMulticonductorNetwork,
14884 output: *mut PioMulticonductorNetworkCountsView,
14885 error: *mut *mut PioError,
14886) -> bool {
14887 unsafe {
14888 entry(error, false, || {
14889 let network = require_multiconductor_network(network)?;
14890 *require_output(output, "output")? = PioMulticonductorNetworkCountsView {
14891 buses: network.buses().len(),
14892 line_codes: network.line_codes().len(),
14893 lines: network.lines().len(),
14894 switches: network.switches().len(),
14895 transformers: network.transformers().len(),
14896 loads: network.loads().len(),
14897 generators: network.generators().len(),
14898 inverter_based_resources: network.ibrs().len(),
14899 control_profiles: network.control_profiles().len(),
14900 shunts: network.shunts().len(),
14901 capacitors: network.capacitors().len(),
14902 voltage_sources: network.sources().len(),
14903 untyped_objects: network.untyped_objects().len(),
14904 commands: network.commands().len(),
14905 options: network.options().len(),
14906 };
14907 Ok(true)
14908 })
14909 }
14910}
14911
14912#[unsafe(no_mangle)]
14916pub unsafe extern "C" fn pio_multiconductor_network_base_frequency_hz(
14917 network: *const PioMulticonductorNetwork,
14918) -> f64 {
14919 unsafe { PioMulticonductorNetwork::get(network) }
14920 .and_then(MulticonductorNetworkInner::network)
14921 .map_or(f64::NAN, |network| network.base_frequency())
14922}
14923
14924#[unsafe(no_mangle)]
14928pub unsafe extern "C" fn pio_multiconductor_network_bus_count(
14929 network: *const PioMulticonductorNetwork,
14930) -> usize {
14931 unsafe { PioMulticonductorNetwork::get(network) }
14932 .and_then(MulticonductorNetworkInner::network)
14933 .map_or(0, |network| network.buses().len())
14934}
14935
14936#[unsafe(no_mangle)]
14940pub unsafe extern "C" fn pio_multiconductor_network_line_count(
14941 network: *const PioMulticonductorNetwork,
14942) -> usize {
14943 unsafe { PioMulticonductorNetwork::get(network) }
14944 .and_then(MulticonductorNetworkInner::network)
14945 .map_or(0, |network| network.lines().len())
14946}
14947
14948#[unsafe(no_mangle)]
14952pub unsafe extern "C" fn pio_multiconductor_network_load_count(
14953 network: *const PioMulticonductorNetwork,
14954) -> usize {
14955 unsafe { PioMulticonductorNetwork::get(network) }
14956 .and_then(MulticonductorNetworkInner::network)
14957 .map_or(0, |network| network.loads().len())
14958}
14959
14960#[unsafe(no_mangle)]
14964pub unsafe extern "C" fn pio_multiconductor_network_generator_count(
14965 network: *const PioMulticonductorNetwork,
14966) -> usize {
14967 unsafe { PioMulticonductorNetwork::get(network) }
14968 .and_then(MulticonductorNetworkInner::network)
14969 .map_or(0, |network| network.generators().len())
14970}
14971
14972#[unsafe(no_mangle)]
14978pub unsafe extern "C" fn pio_multiconductor_network_bus_at(
14979 network: *const PioMulticonductorNetwork,
14980 index: usize,
14981 output: *mut PioMulticonductorBusView,
14982 error: *mut *mut PioError,
14983) -> bool {
14984 unsafe {
14985 entry(error, false, || {
14986 let network = require_multiconductor_network(network)?;
14987 let bus = network.buses().get(index).ok_or_else(|| {
14988 boundary_error(
14989 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
14990 format!("multiconductor bus index {index} is out of range"),
14991 )
14992 })?;
14993 *require_output(output, "output")? = multiconductor_bus_view(bus);
14994 Ok(true)
14995 })
14996 }
14997}
14998
14999#[unsafe(no_mangle)]
15003pub unsafe extern "C" fn pio_multiconductor_network_bus_terminal_at(
15004 network: *const PioMulticonductorNetwork,
15005 bus_index: usize,
15006 terminal_index: usize,
15007 output: *mut PioStringView,
15008 error: *mut *mut PioError,
15009) -> bool {
15010 unsafe {
15011 entry(error, false, || {
15012 let network = require_multiconductor_network(network)?;
15013 let bus = network.buses().get(bus_index).ok_or_else(|| {
15014 boundary_error(
15015 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15016 format!("multiconductor bus index {bus_index} is out of range"),
15017 )
15018 })?;
15019 *require_output(output, "output")? =
15020 string_slice_at(&bus.terminals, terminal_index, "bus terminal")?;
15021 Ok(true)
15022 })
15023 }
15024}
15025
15026#[unsafe(no_mangle)]
15030pub unsafe extern "C" fn pio_multiconductor_network_bus_grounded_terminal_at(
15031 network: *const PioMulticonductorNetwork,
15032 bus_index: usize,
15033 terminal_index: usize,
15034 output: *mut PioStringView,
15035 error: *mut *mut PioError,
15036) -> bool {
15037 unsafe {
15038 entry(error, false, || {
15039 let network = require_multiconductor_network(network)?;
15040 let bus = network.buses().get(bus_index).ok_or_else(|| {
15041 boundary_error(
15042 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15043 format!("multiconductor bus index {bus_index} is out of range"),
15044 )
15045 })?;
15046 *require_output(output, "output")? =
15047 string_slice_at(&bus.grounded, terminal_index, "grounded bus terminal")?;
15048 Ok(true)
15049 })
15050 }
15051}
15052
15053#[unsafe(no_mangle)]
15057pub unsafe extern "C" fn pio_multiconductor_network_line_code_at(
15058 network: *const PioMulticonductorNetwork,
15059 index: usize,
15060 output: *mut PioMulticonductorLineCodeView,
15061 error: *mut *mut PioError,
15062) -> bool {
15063 unsafe {
15064 entry(error, false, || {
15065 let network = require_multiconductor_network(network)?;
15066 let line_code = network.line_codes().get(index).ok_or_else(|| {
15067 boundary_error(
15068 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15069 format!("multiconductor line code index {index} is out of range"),
15070 )
15071 })?;
15072 *require_output(output, "output")? = multiconductor_line_code_view(line_code);
15073 Ok(true)
15074 })
15075 }
15076}
15077
15078fn multiconductor_line_code_matrix<'a>(
15079 network: &'a powerio_dist::MulticonductorNetwork,
15080 line_code_index: usize,
15081 matrix: &str,
15082) -> Result<&'a powerio_dist::ConductorMatrix, *mut PioError> {
15083 let line_code = network.line_codes().get(line_code_index).ok_or_else(|| {
15084 boundary_error(
15085 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15086 format!("multiconductor line code index {line_code_index} is out of range"),
15087 )
15088 })?;
15089 Ok(match matrix {
15090 "resistance" => &line_code.r_series,
15091 "reactance" => &line_code.x_series,
15092 "conductance_from" => &line_code.g_from,
15093 "susceptance_from" => &line_code.b_from,
15094 "conductance_to" => &line_code.g_to,
15095 "susceptance_to" => &line_code.b_to,
15096 _ => {
15097 return Err(boundary_error(
15098 &codes::REQUEST_CAPI_TYPE_MISMATCH,
15099 "unsupported line code matrix",
15100 ));
15101 }
15102 })
15103}
15104
15105unsafe fn write_multiconductor_line_code_matrix_row(
15106 network: *const PioMulticonductorNetwork,
15107 line_code_index: usize,
15108 row_index: usize,
15109 matrix: &str,
15110 output: *mut PioF64View,
15111 error: *mut *mut PioError,
15112) -> bool {
15113 unsafe {
15114 entry(error, false, || {
15115 let network = require_multiconductor_network(network)?;
15116 let values = multiconductor_line_code_matrix(network, line_code_index, matrix)?;
15117 *require_output(output, "output")? =
15118 conductor_matrix_row(values, row_index, &format!("line code {matrix} matrix"))?;
15119 Ok(true)
15120 })
15121 }
15122}
15123
15124#[unsafe(no_mangle)]
15128pub unsafe extern "C" fn pio_multiconductor_network_line_code_resistance_matrix_row_at(
15129 network: *const PioMulticonductorNetwork,
15130 line_code_index: usize,
15131 row_index: usize,
15132 output: *mut PioF64View,
15133 error: *mut *mut PioError,
15134) -> bool {
15135 unsafe {
15136 write_multiconductor_line_code_matrix_row(
15137 network,
15138 line_code_index,
15139 row_index,
15140 "resistance",
15141 output,
15142 error,
15143 )
15144 }
15145}
15146
15147#[unsafe(no_mangle)]
15151pub unsafe extern "C" fn pio_multiconductor_network_line_code_reactance_matrix_row_at(
15152 network: *const PioMulticonductorNetwork,
15153 line_code_index: usize,
15154 row_index: usize,
15155 output: *mut PioF64View,
15156 error: *mut *mut PioError,
15157) -> bool {
15158 unsafe {
15159 write_multiconductor_line_code_matrix_row(
15160 network,
15161 line_code_index,
15162 row_index,
15163 "reactance",
15164 output,
15165 error,
15166 )
15167 }
15168}
15169
15170#[unsafe(no_mangle)]
15174pub unsafe extern "C" fn pio_multiconductor_network_line_code_conductance_from_matrix_row_at(
15175 network: *const PioMulticonductorNetwork,
15176 line_code_index: usize,
15177 row_index: usize,
15178 output: *mut PioF64View,
15179 error: *mut *mut PioError,
15180) -> bool {
15181 unsafe {
15182 write_multiconductor_line_code_matrix_row(
15183 network,
15184 line_code_index,
15185 row_index,
15186 "conductance_from",
15187 output,
15188 error,
15189 )
15190 }
15191}
15192
15193#[unsafe(no_mangle)]
15197pub unsafe extern "C" fn pio_multiconductor_network_line_code_susceptance_from_matrix_row_at(
15198 network: *const PioMulticonductorNetwork,
15199 line_code_index: usize,
15200 row_index: usize,
15201 output: *mut PioF64View,
15202 error: *mut *mut PioError,
15203) -> bool {
15204 unsafe {
15205 write_multiconductor_line_code_matrix_row(
15206 network,
15207 line_code_index,
15208 row_index,
15209 "susceptance_from",
15210 output,
15211 error,
15212 )
15213 }
15214}
15215
15216#[unsafe(no_mangle)]
15220pub unsafe extern "C" fn pio_multiconductor_network_line_code_conductance_to_matrix_row_at(
15221 network: *const PioMulticonductorNetwork,
15222 line_code_index: usize,
15223 row_index: usize,
15224 output: *mut PioF64View,
15225 error: *mut *mut PioError,
15226) -> bool {
15227 unsafe {
15228 write_multiconductor_line_code_matrix_row(
15229 network,
15230 line_code_index,
15231 row_index,
15232 "conductance_to",
15233 output,
15234 error,
15235 )
15236 }
15237}
15238
15239#[unsafe(no_mangle)]
15243pub unsafe extern "C" fn pio_multiconductor_network_line_code_susceptance_to_matrix_row_at(
15244 network: *const PioMulticonductorNetwork,
15245 line_code_index: usize,
15246 row_index: usize,
15247 output: *mut PioF64View,
15248 error: *mut *mut PioError,
15249) -> bool {
15250 unsafe {
15251 write_multiconductor_line_code_matrix_row(
15252 network,
15253 line_code_index,
15254 row_index,
15255 "susceptance_to",
15256 output,
15257 error,
15258 )
15259 }
15260}
15261
15262#[unsafe(no_mangle)]
15266pub unsafe extern "C" fn pio_multiconductor_network_line_at(
15267 network: *const PioMulticonductorNetwork,
15268 index: usize,
15269 output: *mut PioMulticonductorLineView,
15270 error: *mut *mut PioError,
15271) -> bool {
15272 unsafe {
15273 entry(error, false, || {
15274 let network = require_multiconductor_network(network)?;
15275 let line = network.lines().get(index).ok_or_else(|| {
15276 boundary_error(
15277 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15278 format!("multiconductor line index {index} is out of range"),
15279 )
15280 })?;
15281 *require_output(output, "output")? = multiconductor_line_view(line);
15282 Ok(true)
15283 })
15284 }
15285}
15286
15287unsafe fn write_multiconductor_line_terminal(
15288 network: *const PioMulticonductorNetwork,
15289 line_index: usize,
15290 terminal_index: usize,
15291 from: bool,
15292 output: *mut PioStringView,
15293 error: *mut *mut PioError,
15294) -> bool {
15295 unsafe {
15296 entry(error, false, || {
15297 let network = require_multiconductor_network(network)?;
15298 let line = network.lines().get(line_index).ok_or_else(|| {
15299 boundary_error(
15300 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15301 format!("multiconductor line index {line_index} is out of range"),
15302 )
15303 })?;
15304 let (terminals, description) = if from {
15305 (&line.terminal_map_from, "line from terminal")
15306 } else {
15307 (&line.terminal_map_to, "line to terminal")
15308 };
15309 *require_output(output, "output")? =
15310 string_slice_at(terminals, terminal_index, description)?;
15311 Ok(true)
15312 })
15313 }
15314}
15315
15316#[unsafe(no_mangle)]
15320pub unsafe extern "C" fn pio_multiconductor_network_line_terminal_from_at(
15321 network: *const PioMulticonductorNetwork,
15322 line_index: usize,
15323 terminal_index: usize,
15324 output: *mut PioStringView,
15325 error: *mut *mut PioError,
15326) -> bool {
15327 unsafe {
15328 write_multiconductor_line_terminal(network, line_index, terminal_index, true, output, error)
15329 }
15330}
15331
15332#[unsafe(no_mangle)]
15336pub unsafe extern "C" fn pio_multiconductor_network_line_terminal_to_at(
15337 network: *const PioMulticonductorNetwork,
15338 line_index: usize,
15339 terminal_index: usize,
15340 output: *mut PioStringView,
15341 error: *mut *mut PioError,
15342) -> bool {
15343 unsafe {
15344 write_multiconductor_line_terminal(
15345 network,
15346 line_index,
15347 terminal_index,
15348 false,
15349 output,
15350 error,
15351 )
15352 }
15353}
15354
15355#[unsafe(no_mangle)]
15359pub unsafe extern "C" fn pio_multiconductor_network_line_route_point_at(
15360 network: *const PioMulticonductorNetwork,
15361 line_index: usize,
15362 point_index: usize,
15363 output: *mut PioMulticonductorLocationView,
15364 error: *mut *mut PioError,
15365) -> bool {
15366 unsafe {
15367 entry(error, false, || {
15368 let network = require_multiconductor_network(network)?;
15369 let line = network.lines().get(line_index).ok_or_else(|| {
15370 boundary_error(
15371 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15372 format!("multiconductor line index {line_index} is out of range"),
15373 )
15374 })?;
15375 let route = line.route.as_ref().ok_or_else(|| {
15376 boundary_error(
15377 &codes::REQUEST_CAPI_TYPE_MISMATCH,
15378 format!("multiconductor line {line_index} has no route"),
15379 )
15380 })?;
15381 let point = route.get(point_index).ok_or_else(|| {
15382 boundary_error(
15383 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15384 format!("line route point index {point_index} is out of range"),
15385 )
15386 })?;
15387 *require_output(output, "output")? = multiconductor_location_view(point);
15388 Ok(true)
15389 })
15390 }
15391}
15392
15393#[unsafe(no_mangle)]
15397pub unsafe extern "C" fn pio_multiconductor_network_switch_at(
15398 network: *const PioMulticonductorNetwork,
15399 index: usize,
15400 output: *mut PioMulticonductorSwitchView,
15401 error: *mut *mut PioError,
15402) -> bool {
15403 unsafe {
15404 entry(error, false, || {
15405 let network = require_multiconductor_network(network)?;
15406 let switch = network.switches().get(index).ok_or_else(|| {
15407 boundary_error(
15408 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15409 format!("multiconductor switch index {index} is out of range"),
15410 )
15411 })?;
15412 *require_output(output, "output")? = multiconductor_switch_view(switch);
15413 Ok(true)
15414 })
15415 }
15416}
15417
15418unsafe fn write_multiconductor_switch_terminal(
15419 network: *const PioMulticonductorNetwork,
15420 switch_index: usize,
15421 terminal_index: usize,
15422 from: bool,
15423 output: *mut PioStringView,
15424 error: *mut *mut PioError,
15425) -> bool {
15426 unsafe {
15427 entry(error, false, || {
15428 let network = require_multiconductor_network(network)?;
15429 let switch = network.switches().get(switch_index).ok_or_else(|| {
15430 boundary_error(
15431 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15432 format!("multiconductor switch index {switch_index} is out of range"),
15433 )
15434 })?;
15435 let (terminals, description) = if from {
15436 (&switch.terminal_map_from, "switch from terminal")
15437 } else {
15438 (&switch.terminal_map_to, "switch to terminal")
15439 };
15440 *require_output(output, "output")? =
15441 string_slice_at(terminals, terminal_index, description)?;
15442 Ok(true)
15443 })
15444 }
15445}
15446
15447#[unsafe(no_mangle)]
15451pub unsafe extern "C" fn pio_multiconductor_network_switch_terminal_from_at(
15452 network: *const PioMulticonductorNetwork,
15453 switch_index: usize,
15454 terminal_index: usize,
15455 output: *mut PioStringView,
15456 error: *mut *mut PioError,
15457) -> bool {
15458 unsafe {
15459 write_multiconductor_switch_terminal(
15460 network,
15461 switch_index,
15462 terminal_index,
15463 true,
15464 output,
15465 error,
15466 )
15467 }
15468}
15469
15470#[unsafe(no_mangle)]
15474pub unsafe extern "C" fn pio_multiconductor_network_switch_terminal_to_at(
15475 network: *const PioMulticonductorNetwork,
15476 switch_index: usize,
15477 terminal_index: usize,
15478 output: *mut PioStringView,
15479 error: *mut *mut PioError,
15480) -> bool {
15481 unsafe {
15482 write_multiconductor_switch_terminal(
15483 network,
15484 switch_index,
15485 terminal_index,
15486 false,
15487 output,
15488 error,
15489 )
15490 }
15491}
15492
15493#[unsafe(no_mangle)]
15497pub unsafe extern "C" fn pio_multiconductor_network_transformer_at(
15498 network: *const PioMulticonductorNetwork,
15499 index: usize,
15500 output: *mut PioMulticonductorTransformerView,
15501 error: *mut *mut PioError,
15502) -> bool {
15503 unsafe {
15504 entry(error, false, || {
15505 let network = require_multiconductor_network(network)?;
15506 let transformer = network.transformers().get(index).ok_or_else(|| {
15507 boundary_error(
15508 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15509 format!("multiconductor transformer index {index} is out of range"),
15510 )
15511 })?;
15512 *require_output(output, "output")? = PioMulticonductorTransformerView {
15513 name: PioStringView::new(&transformer.name),
15514 winding_count: transformer.windings.len(),
15515 short_circuit_reactance_percent: PioF64View::new(&transformer.xsc_pct),
15516 phase_count: transformer.phases,
15517 };
15518 Ok(true)
15519 })
15520 }
15521}
15522
15523#[unsafe(no_mangle)]
15527pub unsafe extern "C" fn pio_multiconductor_network_transformer_winding_at(
15528 network: *const PioMulticonductorNetwork,
15529 transformer_index: usize,
15530 winding_index: usize,
15531 output: *mut PioMulticonductorTransformerWindingView,
15532 error: *mut *mut PioError,
15533) -> bool {
15534 unsafe {
15535 entry(error, false, || {
15536 let network = require_multiconductor_network(network)?;
15537 let transformer = network
15538 .transformers()
15539 .get(transformer_index)
15540 .ok_or_else(|| {
15541 boundary_error(
15542 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15543 format!(
15544 "multiconductor transformer index {transformer_index} is out of range"
15545 ),
15546 )
15547 })?;
15548 let winding = transformer.windings.get(winding_index).ok_or_else(|| {
15549 boundary_error(
15550 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15551 format!("transformer winding index {winding_index} is out of range"),
15552 )
15553 })?;
15554 *require_output(output, "output")? = PioMulticonductorTransformerWindingView {
15555 bus: PioStringView::new(&winding.bus),
15556 terminal_map_count: winding.terminal_map.len(),
15557 connection: PioStringView::new(multiconductor_winding_connection_name(
15558 winding.conn,
15559 )),
15560 rated_voltage_v: winding.v_ref,
15561 apparent_power_rating_va: winding.s_rating,
15562 resistance_percent: winding.r_pct,
15563 tap: winding.tap,
15564 neutral_resistance_ohm: winding.r_neutral.unwrap_or(0.0),
15565 has_neutral_resistance: winding.r_neutral.is_some(),
15566 neutral_reactance_ohm: winding.x_neutral.unwrap_or(0.0),
15567 has_neutral_reactance: winding.x_neutral.is_some(),
15568 };
15569 Ok(true)
15570 })
15571 }
15572}
15573
15574#[unsafe(no_mangle)]
15578pub unsafe extern "C" fn pio_multiconductor_network_transformer_winding_terminal_at(
15579 network: *const PioMulticonductorNetwork,
15580 transformer_index: usize,
15581 winding_index: usize,
15582 terminal_index: usize,
15583 output: *mut PioStringView,
15584 error: *mut *mut PioError,
15585) -> bool {
15586 unsafe {
15587 entry(error, false, || {
15588 let network = require_multiconductor_network(network)?;
15589 let transformer = network
15590 .transformers()
15591 .get(transformer_index)
15592 .ok_or_else(|| {
15593 boundary_error(
15594 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15595 format!(
15596 "multiconductor transformer index {transformer_index} is out of range"
15597 ),
15598 )
15599 })?;
15600 let winding = transformer.windings.get(winding_index).ok_or_else(|| {
15601 boundary_error(
15602 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15603 format!("transformer winding index {winding_index} is out of range"),
15604 )
15605 })?;
15606 *require_output(output, "output")? = string_slice_at(
15607 &winding.terminal_map,
15608 terminal_index,
15609 "transformer winding terminal",
15610 )?;
15611 Ok(true)
15612 })
15613 }
15614}
15615
15616#[derive(Clone, Copy)]
15617enum MulticonductorTerminalTable {
15618 Load,
15619 Generator,
15620 InverterBasedResource,
15621 Shunt,
15622 Capacitor,
15623 VoltageSource,
15624}
15625
15626unsafe fn write_multiconductor_terminal(
15627 network: *const PioMulticonductorNetwork,
15628 table: MulticonductorTerminalTable,
15629 element_index: usize,
15630 terminal_index: usize,
15631 output: *mut PioStringView,
15632 error: *mut *mut PioError,
15633) -> bool {
15634 unsafe {
15635 entry(error, false, || {
15636 let network = require_multiconductor_network(network)?;
15637 let (terminals, description): (&[String], &str) = match table {
15638 MulticonductorTerminalTable::Load => (
15639 &network
15640 .loads()
15641 .get(element_index)
15642 .ok_or_else(|| {
15643 boundary_error(
15644 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15645 format!(
15646 "multiconductor load index {element_index} is out of range"
15647 ),
15648 )
15649 })?
15650 .terminal_map,
15651 "load terminal",
15652 ),
15653 MulticonductorTerminalTable::Generator => (
15654 &network
15655 .generators()
15656 .get(element_index)
15657 .ok_or_else(|| {
15658 boundary_error(
15659 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15660 format!(
15661 "multiconductor generator index {element_index} is out of range"
15662 ),
15663 )
15664 })?
15665 .terminal_map,
15666 "generator terminal",
15667 ),
15668 MulticonductorTerminalTable::InverterBasedResource => (
15669 &network
15670 .ibrs()
15671 .get(element_index)
15672 .ok_or_else(|| {
15673 boundary_error(
15674 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15675 format!(
15676 "inverter based resource index {element_index} is out of range"
15677 ),
15678 )
15679 })?
15680 .terminal_map,
15681 "inverter based resource terminal",
15682 ),
15683 MulticonductorTerminalTable::Shunt => (
15684 &network
15685 .shunts()
15686 .get(element_index)
15687 .ok_or_else(|| {
15688 boundary_error(
15689 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15690 format!(
15691 "multiconductor shunt index {element_index} is out of range"
15692 ),
15693 )
15694 })?
15695 .terminal_map,
15696 "shunt terminal",
15697 ),
15698 MulticonductorTerminalTable::Capacitor => (
15699 &network
15700 .capacitors()
15701 .get(element_index)
15702 .ok_or_else(|| {
15703 boundary_error(
15704 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15705 format!(
15706 "multiconductor capacitor index {element_index} is out of range"
15707 ),
15708 )
15709 })?
15710 .terminal_map,
15711 "capacitor terminal",
15712 ),
15713 MulticonductorTerminalTable::VoltageSource => (
15714 &network
15715 .sources()
15716 .get(element_index)
15717 .ok_or_else(|| {
15718 boundary_error(
15719 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15720 format!("voltage source index {element_index} is out of range"),
15721 )
15722 })?
15723 .terminal_map,
15724 "voltage source terminal",
15725 ),
15726 };
15727 *require_output(output, "output")? =
15728 string_slice_at(terminals, terminal_index, description)?;
15729 Ok(true)
15730 })
15731 }
15732}
15733
15734#[unsafe(no_mangle)]
15738pub unsafe extern "C" fn pio_multiconductor_network_load_at(
15739 network: *const PioMulticonductorNetwork,
15740 index: usize,
15741 output: *mut PioMulticonductorLoadView,
15742 error: *mut *mut PioError,
15743) -> bool {
15744 unsafe {
15745 entry(error, false, || {
15746 let network = require_multiconductor_network(network)?;
15747 let load = network.loads().get(index).ok_or_else(|| {
15748 boundary_error(
15749 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15750 format!("multiconductor load index {index} is out of range"),
15751 )
15752 })?;
15753 *require_output(output, "output")? = multiconductor_load_view(load);
15754 Ok(true)
15755 })
15756 }
15757}
15758
15759#[unsafe(no_mangle)]
15763pub unsafe extern "C" fn pio_multiconductor_network_load_terminal_at(
15764 network: *const PioMulticonductorNetwork,
15765 load_index: usize,
15766 terminal_index: usize,
15767 output: *mut PioStringView,
15768 error: *mut *mut PioError,
15769) -> bool {
15770 unsafe {
15771 write_multiconductor_terminal(
15772 network,
15773 MulticonductorTerminalTable::Load,
15774 load_index,
15775 terminal_index,
15776 output,
15777 error,
15778 )
15779 }
15780}
15781
15782#[unsafe(no_mangle)]
15786pub unsafe extern "C" fn pio_multiconductor_network_generator_at(
15787 network: *const PioMulticonductorNetwork,
15788 index: usize,
15789 output: *mut PioMulticonductorGeneratorView,
15790 error: *mut *mut PioError,
15791) -> bool {
15792 unsafe {
15793 entry(error, false, || {
15794 let network = require_multiconductor_network(network)?;
15795 let generator = network.generators().get(index).ok_or_else(|| {
15796 boundary_error(
15797 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15798 format!("multiconductor generator index {index} is out of range"),
15799 )
15800 })?;
15801 *require_output(output, "output")? = multiconductor_generator_view(generator);
15802 Ok(true)
15803 })
15804 }
15805}
15806
15807#[unsafe(no_mangle)]
15811pub unsafe extern "C" fn pio_multiconductor_network_generator_terminal_at(
15812 network: *const PioMulticonductorNetwork,
15813 generator_index: usize,
15814 terminal_index: usize,
15815 output: *mut PioStringView,
15816 error: *mut *mut PioError,
15817) -> bool {
15818 unsafe {
15819 write_multiconductor_terminal(
15820 network,
15821 MulticonductorTerminalTable::Generator,
15822 generator_index,
15823 terminal_index,
15824 output,
15825 error,
15826 )
15827 }
15828}
15829
15830#[unsafe(no_mangle)]
15834pub unsafe extern "C" fn pio_multiconductor_network_inverter_based_resource_at(
15835 network: *const PioMulticonductorNetwork,
15836 index: usize,
15837 output: *mut PioInverterBasedResourceView,
15838 error: *mut *mut PioError,
15839) -> bool {
15840 unsafe {
15841 entry(error, false, || {
15842 let network = require_multiconductor_network(network)?;
15843 let resource = network.ibrs().get(index).ok_or_else(|| {
15844 boundary_error(
15845 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15846 format!("inverter based resource index {index} is out of range"),
15847 )
15848 })?;
15849 *require_output(output, "output")? = inverter_based_resource_view(resource);
15850 Ok(true)
15851 })
15852 }
15853}
15854
15855#[unsafe(no_mangle)]
15859pub unsafe extern "C" fn pio_multiconductor_network_inverter_based_resource_terminal_at(
15860 network: *const PioMulticonductorNetwork,
15861 resource_index: usize,
15862 terminal_index: usize,
15863 output: *mut PioStringView,
15864 error: *mut *mut PioError,
15865) -> bool {
15866 unsafe {
15867 write_multiconductor_terminal(
15868 network,
15869 MulticonductorTerminalTable::InverterBasedResource,
15870 resource_index,
15871 terminal_index,
15872 output,
15873 error,
15874 )
15875 }
15876}
15877
15878#[unsafe(no_mangle)]
15882pub unsafe extern "C" fn pio_multiconductor_network_control_profile_at(
15883 network: *const PioMulticonductorNetwork,
15884 index: usize,
15885 output: *mut PioControlProfileView,
15886 error: *mut *mut PioError,
15887) -> bool {
15888 unsafe {
15889 entry(error, false, || {
15890 let network = require_multiconductor_network(network)?;
15891 let profile = network.control_profiles().get(index).ok_or_else(|| {
15892 boundary_error(
15893 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15894 format!("control profile index {index} is out of range"),
15895 )
15896 })?;
15897 *require_output(output, "output")? = control_profile_view(profile);
15898 Ok(true)
15899 })
15900 }
15901}
15902
15903#[unsafe(no_mangle)]
15907pub unsafe extern "C" fn pio_multiconductor_network_shunt_at(
15908 network: *const PioMulticonductorNetwork,
15909 index: usize,
15910 output: *mut PioMulticonductorShuntView,
15911 error: *mut *mut PioError,
15912) -> bool {
15913 unsafe {
15914 entry(error, false, || {
15915 let network = require_multiconductor_network(network)?;
15916 let shunt = network.shunts().get(index).ok_or_else(|| {
15917 boundary_error(
15918 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15919 format!("multiconductor shunt index {index} is out of range"),
15920 )
15921 })?;
15922 *require_output(output, "output")? = PioMulticonductorShuntView {
15923 name: PioStringView::new(&shunt.name),
15924 bus: PioStringView::new(&shunt.bus),
15925 terminal_map_count: shunt.terminal_map.len(),
15926 conductance_matrix_row_count: shunt.g.len(),
15927 susceptance_matrix_row_count: shunt.b.len(),
15928 };
15929 Ok(true)
15930 })
15931 }
15932}
15933
15934#[unsafe(no_mangle)]
15938pub unsafe extern "C" fn pio_multiconductor_network_shunt_terminal_at(
15939 network: *const PioMulticonductorNetwork,
15940 shunt_index: usize,
15941 terminal_index: usize,
15942 output: *mut PioStringView,
15943 error: *mut *mut PioError,
15944) -> bool {
15945 unsafe {
15946 write_multiconductor_terminal(
15947 network,
15948 MulticonductorTerminalTable::Shunt,
15949 shunt_index,
15950 terminal_index,
15951 output,
15952 error,
15953 )
15954 }
15955}
15956
15957unsafe fn write_multiconductor_shunt_matrix_row(
15958 network: *const PioMulticonductorNetwork,
15959 shunt_index: usize,
15960 row_index: usize,
15961 conductance: bool,
15962 output: *mut PioF64View,
15963 error: *mut *mut PioError,
15964) -> bool {
15965 unsafe {
15966 entry(error, false, || {
15967 let network = require_multiconductor_network(network)?;
15968 let shunt = network.shunts().get(shunt_index).ok_or_else(|| {
15969 boundary_error(
15970 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
15971 format!("multiconductor shunt index {shunt_index} is out of range"),
15972 )
15973 })?;
15974 let (matrix, description) = if conductance {
15975 (&shunt.g, "shunt conductance matrix")
15976 } else {
15977 (&shunt.b, "shunt susceptance matrix")
15978 };
15979 *require_output(output, "output")? =
15980 conductor_matrix_row(matrix, row_index, description)?;
15981 Ok(true)
15982 })
15983 }
15984}
15985
15986#[unsafe(no_mangle)]
15990pub unsafe extern "C" fn pio_multiconductor_network_shunt_conductance_matrix_row_at(
15991 network: *const PioMulticonductorNetwork,
15992 shunt_index: usize,
15993 row_index: usize,
15994 output: *mut PioF64View,
15995 error: *mut *mut PioError,
15996) -> bool {
15997 unsafe {
15998 write_multiconductor_shunt_matrix_row(network, shunt_index, row_index, true, output, error)
15999 }
16000}
16001
16002#[unsafe(no_mangle)]
16006pub unsafe extern "C" fn pio_multiconductor_network_shunt_susceptance_matrix_row_at(
16007 network: *const PioMulticonductorNetwork,
16008 shunt_index: usize,
16009 row_index: usize,
16010 output: *mut PioF64View,
16011 error: *mut *mut PioError,
16012) -> bool {
16013 unsafe {
16014 write_multiconductor_shunt_matrix_row(network, shunt_index, row_index, false, output, error)
16015 }
16016}
16017
16018#[unsafe(no_mangle)]
16022pub unsafe extern "C" fn pio_multiconductor_network_capacitor_at(
16023 network: *const PioMulticonductorNetwork,
16024 index: usize,
16025 output: *mut PioMulticonductorCapacitorView,
16026 error: *mut *mut PioError,
16027) -> bool {
16028 unsafe {
16029 entry(error, false, || {
16030 let network = require_multiconductor_network(network)?;
16031 let capacitor = network.capacitors().get(index).ok_or_else(|| {
16032 boundary_error(
16033 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
16034 format!("multiconductor capacitor index {index} is out of range"),
16035 )
16036 })?;
16037 *require_output(output, "output")? = PioMulticonductorCapacitorView {
16038 name: PioStringView::new(&capacitor.name),
16039 bus: PioStringView::new(&capacitor.bus),
16040 terminal_map_count: capacitor.terminal_map.len(),
16041 configuration: PioStringView::new(multiconductor_configuration_name(
16042 capacitor.configuration,
16043 )),
16044 rated_reactive_power_var: capacitor.q_rated,
16045 nominal_voltage_v: capacitor.v_nom,
16046 };
16047 Ok(true)
16048 })
16049 }
16050}
16051
16052#[unsafe(no_mangle)]
16056pub unsafe extern "C" fn pio_multiconductor_network_capacitor_terminal_at(
16057 network: *const PioMulticonductorNetwork,
16058 capacitor_index: usize,
16059 terminal_index: usize,
16060 output: *mut PioStringView,
16061 error: *mut *mut PioError,
16062) -> bool {
16063 unsafe {
16064 write_multiconductor_terminal(
16065 network,
16066 MulticonductorTerminalTable::Capacitor,
16067 capacitor_index,
16068 terminal_index,
16069 output,
16070 error,
16071 )
16072 }
16073}
16074
16075#[unsafe(no_mangle)]
16079pub unsafe extern "C" fn pio_multiconductor_network_voltage_source_at(
16080 network: *const PioMulticonductorNetwork,
16081 index: usize,
16082 output: *mut PioVoltageSourceView,
16083 error: *mut *mut PioError,
16084) -> bool {
16085 unsafe {
16086 entry(error, false, || {
16087 let network = require_multiconductor_network(network)?;
16088 let source = network.sources().get(index).ok_or_else(|| {
16089 boundary_error(
16090 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
16091 format!("voltage source index {index} is out of range"),
16092 )
16093 })?;
16094 *require_output(output, "output")? = PioVoltageSourceView {
16095 name: PioStringView::new(&source.name),
16096 bus: PioStringView::new(&source.bus),
16097 terminal_map_count: source.terminal_map.len(),
16098 voltage_magnitude_v: PioF64View::new(&source.v_magnitude),
16099 voltage_angle_rad: PioF64View::new(&source.v_angle),
16100 energy_cost_rate_per_kwh: PioF64View::new(
16101 source.energy_cost_rate.as_deref().unwrap_or(&[]),
16102 ),
16103 has_energy_cost_rate: source.energy_cost_rate.is_some(),
16104 };
16105 Ok(true)
16106 })
16107 }
16108}
16109
16110#[unsafe(no_mangle)]
16114pub unsafe extern "C" fn pio_multiconductor_network_voltage_source_terminal_at(
16115 network: *const PioMulticonductorNetwork,
16116 source_index: usize,
16117 terminal_index: usize,
16118 output: *mut PioStringView,
16119 error: *mut *mut PioError,
16120) -> bool {
16121 unsafe {
16122 write_multiconductor_terminal(
16123 network,
16124 MulticonductorTerminalTable::VoltageSource,
16125 source_index,
16126 terminal_index,
16127 output,
16128 error,
16129 )
16130 }
16131}
16132
16133#[unsafe(no_mangle)]
16137pub unsafe extern "C" fn pio_multiconductor_network_untyped_object_at(
16138 network: *const PioMulticonductorNetwork,
16139 index: usize,
16140 output: *mut PioMulticonductorUntypedObjectView,
16141 error: *mut *mut PioError,
16142) -> bool {
16143 unsafe {
16144 entry(error, false, || {
16145 let network = require_multiconductor_network(network)?;
16146 let object = network.untyped_objects().get(index).ok_or_else(|| {
16147 boundary_error(
16148 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
16149 format!("untyped object index {index} is out of range"),
16150 )
16151 })?;
16152 *require_output(output, "output")? = PioMulticonductorUntypedObjectView {
16153 class_name: PioStringView::new(&object.class),
16154 name: PioStringView::new(&object.name),
16155 property_count: object.props.len(),
16156 };
16157 Ok(true)
16158 })
16159 }
16160}
16161
16162#[unsafe(no_mangle)]
16166pub unsafe extern "C" fn pio_multiconductor_network_untyped_object_property_at(
16167 network: *const PioMulticonductorNetwork,
16168 object_index: usize,
16169 property_index: usize,
16170 output: *mut PioMulticonductorUntypedPropertyView,
16171 error: *mut *mut PioError,
16172) -> bool {
16173 unsafe {
16174 entry(error, false, || {
16175 let network = require_multiconductor_network(network)?;
16176 let object = network.untyped_objects().get(object_index).ok_or_else(|| {
16177 boundary_error(
16178 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
16179 format!("untyped object index {object_index} is out of range"),
16180 )
16181 })?;
16182 let (name, value) = object.props.get(property_index).ok_or_else(|| {
16183 boundary_error(
16184 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
16185 format!("untyped object property index {property_index} is out of range"),
16186 )
16187 })?;
16188 let (name, has_name) = optional_string_view(name.as_deref());
16189 *require_output(output, "output")? = PioMulticonductorUntypedPropertyView {
16190 name,
16191 has_name,
16192 value: PioStringView::new(value),
16193 };
16194 Ok(true)
16195 })
16196 }
16197}
16198
16199#[unsafe(no_mangle)]
16203pub unsafe extern "C" fn pio_multiconductor_network_command_at(
16204 network: *const PioMulticonductorNetwork,
16205 index: usize,
16206 output: *mut PioMulticonductorCommandView,
16207 error: *mut *mut PioError,
16208) -> bool {
16209 unsafe {
16210 entry(error, false, || {
16211 let network = require_multiconductor_network(network)?;
16212 let (verb, args) = network.commands().get(index).ok_or_else(|| {
16213 boundary_error(
16214 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
16215 format!("source command index {index} is out of range"),
16216 )
16217 })?;
16218 *require_output(output, "output")? = PioMulticonductorCommandView {
16219 verb: PioStringView::new(verb),
16220 args: PioStringView::new(args),
16221 };
16222 Ok(true)
16223 })
16224 }
16225}
16226
16227#[unsafe(no_mangle)]
16231pub unsafe extern "C" fn pio_multiconductor_network_option_at(
16232 network: *const PioMulticonductorNetwork,
16233 index: usize,
16234 output: *mut PioStringPropertyView,
16235 error: *mut *mut PioError,
16236) -> bool {
16237 unsafe {
16238 entry(error, false, || {
16239 let network = require_multiconductor_network(network)?;
16240 let (name, value) = network.options().get(index).ok_or_else(|| {
16241 boundary_error(
16242 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
16243 format!("source option index {index} is out of range"),
16244 )
16245 })?;
16246 *require_output(output, "output")? = PioStringPropertyView {
16247 name: PioStringView::new(name),
16248 value: PioStringView::new(value),
16249 };
16250 Ok(true)
16251 })
16252 }
16253}
16254
16255#[unsafe(no_mangle)]
16260pub unsafe extern "C" fn pio_multiconductor_network_retain(
16261 network: *const PioMulticonductorNetwork,
16262) -> *mut PioMulticonductorNetwork {
16263 unsafe { PioMulticonductorNetwork::retain_raw(network) }
16264}
16265
16266#[unsafe(no_mangle)]
16271pub unsafe extern "C" fn pio_multiconductor_network_release(
16272 network: *mut PioMulticonductorNetwork,
16273) {
16274 unsafe { PioMulticonductorNetwork::release_raw(network) };
16275}
16276
16277opaque_handle!(
16280 PioComponentId,
16282 ComponentId
16283);
16284opaque_handle!(
16285 PioActivePower,
16287 ActivePower
16288);
16289opaque_handle!(
16290 PioReactivePower,
16292 ReactivePower
16293);
16294opaque_handle!(
16295 PioApparentPower,
16297 ApparentPower
16298);
16299opaque_handle!(
16300 PioOperatingPointUpdate,
16302 OperatingPointUpdate
16303);
16304opaque_handle!(
16305 PioNetworkUpdate,
16307 NetworkUpdate
16308);
16309opaque_handle!(
16310 PioCalculationUpdate,
16312 CalculationUpdate
16313);
16314
16315struct UpdateReportInner {
16316 changes: Vec<UpdateChange>,
16317 connectivity_changed: bool,
16318}
16319
16320struct UpdateChangeInner {
16321 owner: Arc<UpdateReportInner>,
16322 index: usize,
16323}
16324
16325impl UpdateChangeInner {
16326 fn change(&self) -> Option<&UpdateChange> {
16327 self.owner.changes.get(self.index)
16328 }
16329}
16330
16331opaque_handle!(
16332 PioUpdateReport,
16334 UpdateReportInner
16335);
16336opaque_handle!(
16337 PioUpdateChange,
16339 UpdateChangeInner
16340);
16341
16342unsafe fn require_handle<'a, T>(
16343 handle: *const HandleBox<T>,
16344 name: &str,
16345) -> Result<&'a T, *mut PioError> {
16346 unsafe { handle_get(handle) }.ok_or_else(|| {
16347 boundary_error(
16348 &codes::BIND_CAPI_NULL_HANDLE,
16349 format!("{name} must not be NULL"),
16350 )
16351 })
16352}
16353
16354unsafe fn optional_owned_string(
16355 value: *const c_char,
16356 value_len: usize,
16357 name: &str,
16358) -> Result<Option<String>, *mut PioError> {
16359 unsafe { optional_str(value, value_len, name) }.map(|value| value.map(str::to_owned))
16360}
16361
16362#[unsafe(no_mangle)]
16367pub unsafe extern "C" fn pio_component_id_new(
16368 component_type: *const c_char,
16369 component_type_len: usize,
16370 local_id: *const c_char,
16371 local_id_len: usize,
16372 error: *mut *mut PioError,
16373) -> *mut PioComponentId {
16374 unsafe {
16375 entry(error, std::ptr::null_mut(), || {
16376 let component_type =
16377 required_str(component_type, component_type_len, "component_type")?;
16378 let local_id = required_str(local_id, local_id_len, "local_id")?;
16379 ComponentId::new(component_type, local_id)
16380 .map(PioComponentId::new_raw)
16381 .map_err(|failure| error_from_core(&failure))
16382 })
16383 }
16384}
16385
16386#[unsafe(no_mangle)]
16390pub unsafe extern "C" fn pio_component_id_type(component: *const PioComponentId) -> PioStringView {
16391 unsafe { PioComponentId::get(component) }.map_or(PioStringView::EMPTY, |component| {
16392 PioStringView::new(component.component_type())
16393 })
16394}
16395
16396#[unsafe(no_mangle)]
16400pub unsafe extern "C" fn pio_component_id_local_id(
16401 component: *const PioComponentId,
16402) -> PioStringView {
16403 unsafe { PioComponentId::get(component) }.map_or(PioStringView::EMPTY, |component| {
16404 PioStringView::new(component.local_id())
16405 })
16406}
16407
16408#[unsafe(no_mangle)]
16413pub unsafe extern "C" fn pio_component_id_retain(
16414 component: *const PioComponentId,
16415) -> *mut PioComponentId {
16416 unsafe { PioComponentId::retain_raw(component) }
16417}
16418
16419#[unsafe(no_mangle)]
16424pub unsafe extern "C" fn pio_component_id_release(component: *mut PioComponentId) {
16425 unsafe { PioComponentId::release_raw(component) };
16426}
16427
16428#[unsafe(no_mangle)]
16429pub extern "C" fn pio_active_power_from_watts(value: f64) -> *mut PioActivePower {
16430 PioActivePower::new_raw(ActivePower::from_watts(value))
16431}
16432
16433#[unsafe(no_mangle)]
16434pub extern "C" fn pio_active_power_from_megawatts(value: f64) -> *mut PioActivePower {
16435 PioActivePower::new_raw(ActivePower::from_megawatts(value))
16436}
16437
16438#[unsafe(no_mangle)]
16442pub unsafe extern "C" fn pio_active_power_value(power: *const PioActivePower) -> f64 {
16443 unsafe { PioActivePower::get(power) }.map_or(f64::NAN, |power| power.value())
16444}
16445
16446#[unsafe(no_mangle)]
16450pub unsafe extern "C" fn pio_active_power_unit(power: *const PioActivePower) -> PioStringView {
16451 unsafe { PioActivePower::get(power) }.map_or(PioStringView::EMPTY, |power| {
16452 PioStringView::new(match power.unit() {
16453 ActivePowerUnit::Watts => "watts",
16454 ActivePowerUnit::Megawatts => "megawatts",
16455 _ => "unknown",
16456 })
16457 })
16458}
16459
16460#[unsafe(no_mangle)]
16465pub unsafe extern "C" fn pio_active_power_retain(
16466 power: *const PioActivePower,
16467) -> *mut PioActivePower {
16468 unsafe { PioActivePower::retain_raw(power) }
16469}
16470
16471#[unsafe(no_mangle)]
16476pub unsafe extern "C" fn pio_active_power_release(power: *mut PioActivePower) {
16477 unsafe { PioActivePower::release_raw(power) };
16478}
16479
16480#[unsafe(no_mangle)]
16481pub extern "C" fn pio_reactive_power_from_vars(value: f64) -> *mut PioReactivePower {
16482 PioReactivePower::new_raw(ReactivePower::from_vars(value))
16483}
16484
16485#[unsafe(no_mangle)]
16486pub extern "C" fn pio_reactive_power_from_megavars(value: f64) -> *mut PioReactivePower {
16487 PioReactivePower::new_raw(ReactivePower::from_megavars(value))
16488}
16489
16490#[unsafe(no_mangle)]
16494pub unsafe extern "C" fn pio_reactive_power_value(power: *const PioReactivePower) -> f64 {
16495 unsafe { PioReactivePower::get(power) }.map_or(f64::NAN, |power| power.value())
16496}
16497
16498#[unsafe(no_mangle)]
16502pub unsafe extern "C" fn pio_reactive_power_unit(power: *const PioReactivePower) -> PioStringView {
16503 unsafe { PioReactivePower::get(power) }.map_or(PioStringView::EMPTY, |power| {
16504 PioStringView::new(match power.unit() {
16505 ReactivePowerUnit::Vars => "vars",
16506 ReactivePowerUnit::Megavars => "megavars",
16507 _ => "unknown",
16508 })
16509 })
16510}
16511
16512#[unsafe(no_mangle)]
16517pub unsafe extern "C" fn pio_reactive_power_retain(
16518 power: *const PioReactivePower,
16519) -> *mut PioReactivePower {
16520 unsafe { PioReactivePower::retain_raw(power) }
16521}
16522
16523#[unsafe(no_mangle)]
16528pub unsafe extern "C" fn pio_reactive_power_release(power: *mut PioReactivePower) {
16529 unsafe { PioReactivePower::release_raw(power) };
16530}
16531
16532#[unsafe(no_mangle)]
16533pub extern "C" fn pio_apparent_power_from_volt_amperes(value: f64) -> *mut PioApparentPower {
16534 PioApparentPower::new_raw(ApparentPower::from_volt_amperes(value))
16535}
16536
16537#[unsafe(no_mangle)]
16538pub extern "C" fn pio_apparent_power_from_megavolt_amperes(value: f64) -> *mut PioApparentPower {
16539 PioApparentPower::new_raw(ApparentPower::from_megavolt_amperes(value))
16540}
16541
16542#[unsafe(no_mangle)]
16546pub unsafe extern "C" fn pio_apparent_power_value(power: *const PioApparentPower) -> f64 {
16547 unsafe { PioApparentPower::get(power) }.map_or(f64::NAN, |power| power.value())
16548}
16549
16550#[unsafe(no_mangle)]
16554pub unsafe extern "C" fn pio_apparent_power_unit(power: *const PioApparentPower) -> PioStringView {
16555 unsafe { PioApparentPower::get(power) }.map_or(PioStringView::EMPTY, |power| {
16556 PioStringView::new(match power.unit() {
16557 ApparentPowerUnit::VoltAmperes => "volt_amperes",
16558 ApparentPowerUnit::MegavoltAmperes => "megavolt_amperes",
16559 _ => "unknown",
16560 })
16561 })
16562}
16563
16564#[unsafe(no_mangle)]
16569pub unsafe extern "C" fn pio_apparent_power_retain(
16570 power: *const PioApparentPower,
16571) -> *mut PioApparentPower {
16572 unsafe { PioApparentPower::retain_raw(power) }
16573}
16574
16575#[unsafe(no_mangle)]
16580pub unsafe extern "C" fn pio_apparent_power_release(power: *mut PioApparentPower) {
16581 unsafe { PioApparentPower::release_raw(power) };
16582}
16583
16584unsafe fn component_clone(component: *const PioComponentId) -> Result<ComponentId, *mut PioError> {
16585 unsafe { require_handle(component.cast(), "PioComponentId") }.cloned()
16586}
16587
16588unsafe fn active_power_copy(power: *const PioActivePower) -> Result<ActivePower, *mut PioError> {
16589 unsafe { require_handle(power.cast(), "PioActivePower") }.copied()
16590}
16591
16592unsafe fn reactive_power_copy(
16593 power: *const PioReactivePower,
16594) -> Result<ReactivePower, *mut PioError> {
16595 unsafe { require_handle(power.cast(), "PioReactivePower") }.copied()
16596}
16597
16598unsafe fn apparent_power_copy(
16599 power: *const PioApparentPower,
16600) -> Result<ApparentPower, *mut PioError> {
16601 unsafe { require_handle(power.cast(), "PioApparentPower") }.copied()
16602}
16603
16604#[unsafe(no_mangle)]
16608pub unsafe extern "C" fn pio_operating_point_update_set_load_active_power(
16609 load: *const PioComponentId,
16610 terminal: *const c_char,
16611 terminal_len: usize,
16612 power: *const PioActivePower,
16613 error: *mut *mut PioError,
16614) -> *mut PioOperatingPointUpdate {
16615 unsafe {
16616 entry(error, std::ptr::null_mut(), || {
16617 Ok(PioOperatingPointUpdate::new_raw(
16618 OperatingPointUpdate::LoadActivePower {
16619 load: component_clone(load)?,
16620 terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
16621 p: active_power_copy(power)?,
16622 },
16623 ))
16624 })
16625 }
16626}
16627
16628#[unsafe(no_mangle)]
16632pub unsafe extern "C" fn pio_operating_point_update_set_load_reactive_power(
16633 load: *const PioComponentId,
16634 terminal: *const c_char,
16635 terminal_len: usize,
16636 power: *const PioReactivePower,
16637 error: *mut *mut PioError,
16638) -> *mut PioOperatingPointUpdate {
16639 unsafe {
16640 entry(error, std::ptr::null_mut(), || {
16641 Ok(PioOperatingPointUpdate::new_raw(
16642 OperatingPointUpdate::LoadReactivePower {
16643 load: component_clone(load)?,
16644 terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
16645 q: reactive_power_copy(power)?,
16646 },
16647 ))
16648 })
16649 }
16650}
16651
16652#[unsafe(no_mangle)]
16656pub unsafe extern "C" fn pio_operating_point_update_set_generator_active_power(
16657 generator: *const PioComponentId,
16658 terminal: *const c_char,
16659 terminal_len: usize,
16660 power: *const PioActivePower,
16661 error: *mut *mut PioError,
16662) -> *mut PioOperatingPointUpdate {
16663 unsafe {
16664 entry(error, std::ptr::null_mut(), || {
16665 Ok(PioOperatingPointUpdate::new_raw(
16666 OperatingPointUpdate::GeneratorActivePower {
16667 generator: component_clone(generator)?,
16668 terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
16669 p: active_power_copy(power)?,
16670 },
16671 ))
16672 })
16673 }
16674}
16675
16676#[unsafe(no_mangle)]
16680pub unsafe extern "C" fn pio_operating_point_update_set_generator_reactive_power(
16681 generator: *const PioComponentId,
16682 terminal: *const c_char,
16683 terminal_len: usize,
16684 power: *const PioReactivePower,
16685 error: *mut *mut PioError,
16686) -> *mut PioOperatingPointUpdate {
16687 unsafe {
16688 entry(error, std::ptr::null_mut(), || {
16689 Ok(PioOperatingPointUpdate::new_raw(
16690 OperatingPointUpdate::GeneratorReactivePower {
16691 generator: component_clone(generator)?,
16692 terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
16693 q: reactive_power_copy(power)?,
16694 },
16695 ))
16696 })
16697 }
16698}
16699
16700#[unsafe(no_mangle)]
16704pub unsafe extern "C" fn pio_operating_point_update_set_generator_voltage_magnitude(
16705 generator: *const PioComponentId,
16706 voltage_magnitude_per_unit: f64,
16707 error: *mut *mut PioError,
16708) -> *mut PioOperatingPointUpdate {
16709 unsafe {
16710 entry(error, std::ptr::null_mut(), || {
16711 Ok(PioOperatingPointUpdate::new_raw(
16712 OperatingPointUpdate::GeneratorVoltageMagnitude {
16713 generator: component_clone(generator)?,
16714 vm_pu: voltage_magnitude_per_unit,
16715 },
16716 ))
16717 })
16718 }
16719}
16720
16721#[unsafe(no_mangle)]
16725pub unsafe extern "C" fn pio_operating_point_update_set_generator_in_service(
16726 generator: *const PioComponentId,
16727 in_service: bool,
16728 error: *mut *mut PioError,
16729) -> *mut PioOperatingPointUpdate {
16730 unsafe {
16731 entry(error, std::ptr::null_mut(), || {
16732 Ok(PioOperatingPointUpdate::new_raw(
16733 OperatingPointUpdate::GeneratorInService {
16734 generator: component_clone(generator)?,
16735 in_service,
16736 },
16737 ))
16738 })
16739 }
16740}
16741
16742#[unsafe(no_mangle)]
16746pub unsafe extern "C" fn pio_operating_point_update_set_branch_in_service(
16747 branch: *const PioComponentId,
16748 in_service: bool,
16749 error: *mut *mut PioError,
16750) -> *mut PioOperatingPointUpdate {
16751 unsafe {
16752 entry(error, std::ptr::null_mut(), || {
16753 Ok(PioOperatingPointUpdate::new_raw(
16754 OperatingPointUpdate::BranchInService {
16755 branch: component_clone(branch)?,
16756 in_service,
16757 },
16758 ))
16759 })
16760 }
16761}
16762
16763#[unsafe(no_mangle)]
16767pub unsafe extern "C" fn pio_operating_point_update_set_transformer_tap_ratio(
16768 transformer: *const PioComponentId,
16769 tap_ratio: f64,
16770 error: *mut *mut PioError,
16771) -> *mut PioOperatingPointUpdate {
16772 unsafe {
16773 entry(error, std::ptr::null_mut(), || {
16774 Ok(PioOperatingPointUpdate::new_raw(
16775 OperatingPointUpdate::TransformerTapRatio {
16776 transformer: component_clone(transformer)?,
16777 tap_ratio,
16778 },
16779 ))
16780 })
16781 }
16782}
16783
16784#[unsafe(no_mangle)]
16788pub unsafe extern "C" fn pio_operating_point_update_set_transformer_phase_shift_degrees(
16789 transformer: *const PioComponentId,
16790 phase_shift_degrees: f64,
16791 error: *mut *mut PioError,
16792) -> *mut PioOperatingPointUpdate {
16793 unsafe {
16794 entry(error, std::ptr::null_mut(), || {
16795 Ok(PioOperatingPointUpdate::new_raw(
16796 OperatingPointUpdate::TransformerPhaseShift {
16797 transformer: component_clone(transformer)?,
16798 shift_degrees: phase_shift_degrees,
16799 },
16800 ))
16801 })
16802 }
16803}
16804
16805#[unsafe(no_mangle)]
16809pub unsafe extern "C" fn pio_operating_point_update_set_switch_closed(
16810 switch_id: *const PioComponentId,
16811 closed: bool,
16812 error: *mut *mut PioError,
16813) -> *mut PioOperatingPointUpdate {
16814 unsafe {
16815 entry(error, std::ptr::null_mut(), || {
16816 Ok(PioOperatingPointUpdate::new_raw(
16817 OperatingPointUpdate::SwitchClosed {
16818 switch: component_clone(switch_id)?,
16819 closed,
16820 },
16821 ))
16822 })
16823 }
16824}
16825
16826#[unsafe(no_mangle)]
16830pub unsafe extern "C" fn pio_network_update_set_branch_thermal_rating(
16831 branch: *const PioComponentId,
16832 terminal: *const c_char,
16833 terminal_len: usize,
16834 rating: *const PioApparentPower,
16835 error: *mut *mut PioError,
16836) -> *mut PioNetworkUpdate {
16837 unsafe {
16838 entry(error, std::ptr::null_mut(), || {
16839 Ok(PioNetworkUpdate::new_raw(
16840 NetworkUpdate::BranchThermalRating {
16841 branch: component_clone(branch)?,
16842 terminal: optional_owned_string(terminal, terminal_len, "terminal")?,
16843 rating: apparent_power_copy(rating)?,
16844 },
16845 ))
16846 })
16847 }
16848}
16849
16850#[unsafe(no_mangle)]
16854pub unsafe extern "C" fn pio_calculation_update_from_operating_point(
16855 update: *const PioOperatingPointUpdate,
16856 error: *mut *mut PioError,
16857) -> *mut PioCalculationUpdate {
16858 unsafe {
16859 entry(error, std::ptr::null_mut(), || {
16860 let update = PioOperatingPointUpdate::get(update)
16861 .ok_or_else(|| {
16862 boundary_error(
16863 &codes::BIND_CAPI_NULL_HANDLE,
16864 "PioOperatingPointUpdate must not be NULL",
16865 )
16866 })?
16867 .clone();
16868 Ok(PioCalculationUpdate::new_raw(
16869 CalculationUpdate::OperatingPoint(update),
16870 ))
16871 })
16872 }
16873}
16874
16875#[unsafe(no_mangle)]
16879pub unsafe extern "C" fn pio_calculation_update_from_network(
16880 update: *const PioNetworkUpdate,
16881 error: *mut *mut PioError,
16882) -> *mut PioCalculationUpdate {
16883 unsafe {
16884 entry(error, std::ptr::null_mut(), || {
16885 let update = PioNetworkUpdate::get(update)
16886 .ok_or_else(|| {
16887 boundary_error(
16888 &codes::BIND_CAPI_NULL_HANDLE,
16889 "PioNetworkUpdate must not be NULL",
16890 )
16891 })?
16892 .clone();
16893 Ok(PioCalculationUpdate::new_raw(CalculationUpdate::Network(
16894 update,
16895 )))
16896 })
16897 }
16898}
16899
16900#[unsafe(no_mangle)]
16905pub unsafe extern "C" fn pio_operating_point_update_retain(
16906 update: *const PioOperatingPointUpdate,
16907) -> *mut PioOperatingPointUpdate {
16908 unsafe { PioOperatingPointUpdate::retain_raw(update) }
16909}
16910
16911#[unsafe(no_mangle)]
16916pub unsafe extern "C" fn pio_operating_point_update_release(update: *mut PioOperatingPointUpdate) {
16917 unsafe { PioOperatingPointUpdate::release_raw(update) };
16918}
16919
16920#[unsafe(no_mangle)]
16925pub unsafe extern "C" fn pio_network_update_retain(
16926 update: *const PioNetworkUpdate,
16927) -> *mut PioNetworkUpdate {
16928 unsafe { PioNetworkUpdate::retain_raw(update) }
16929}
16930
16931#[unsafe(no_mangle)]
16936pub unsafe extern "C" fn pio_network_update_release(update: *mut PioNetworkUpdate) {
16937 unsafe { PioNetworkUpdate::release_raw(update) };
16938}
16939
16940#[unsafe(no_mangle)]
16945pub unsafe extern "C" fn pio_calculation_update_retain(
16946 update: *const PioCalculationUpdate,
16947) -> *mut PioCalculationUpdate {
16948 unsafe { PioCalculationUpdate::retain_raw(update) }
16949}
16950
16951#[unsafe(no_mangle)]
16956pub unsafe extern "C" fn pio_calculation_update_release(update: *mut PioCalculationUpdate) {
16957 unsafe { PioCalculationUpdate::release_raw(update) };
16958}
16959
16960fn append_update_report(output: &mut UpdateReportInner, report: powerio_prob::UpdateReport) {
16961 output.connectivity_changed |= report.connectivity_changed();
16962 output.changes.extend_from_slice(report.changes());
16963}
16964
16965fn apply_bus_load_to_typed_module<T>(
16966 module: &mut powerio::PioModule<PioValue>,
16967 bus: powerio_tx::BusId,
16968 total: ActivePower,
16969 allocation: LoadAllocation,
16970 narrow: impl FnOnce(PioValue) -> Result<T, PioValue>,
16971 wrap: impl FnOnce(T) -> PioValue,
16972) -> Result<UpdateReportInner, powerio_core::Error>
16973where
16974 T: powerio_prob::BalancedCalculationInstance,
16975{
16976 let mut typed = module.clone().__try_map_value(narrow).map_err(|value| {
16977 powerio_core::Error::new(
16978 &codes::REQUEST_CAPI_TYPE_MISMATCH,
16979 format!(
16980 "{} does not accept aggregate bus active demand",
16981 value.value().type_name()
16982 ),
16983 )
16984 })?;
16985 let report = apply_bus_load_active_power(&mut typed, bus, total, allocation)?;
16986 *module = typed.map_value(wrap);
16987 let mut output = UpdateReportInner {
16988 changes: Vec::new(),
16989 connectivity_changed: false,
16990 };
16991 append_update_report(&mut output, report);
16992 Ok(output)
16993}
16994
16995#[allow(clippy::result_large_err)]
16998fn apply_dynamic_bus_load_active_power(
16999 module: &mut powerio::PioModule<PioValue>,
17000 bus: powerio_tx::BusId,
17001 total: ActivePower,
17002 allocation: LoadAllocation,
17003) -> Result<UpdateReportInner, powerio_core::Error> {
17004 match &module.value() {
17005 PioValue::DcPfInstance(_) => apply_bus_load_to_typed_module(
17006 module,
17007 bus,
17008 total,
17009 allocation,
17010 |value| match value {
17011 PioValue::DcPfInstance(instance) => Ok(instance),
17012 other => Err(other),
17013 },
17014 PioValue::DcPfInstance,
17015 ),
17016 PioValue::AcPfInstance(_) => apply_bus_load_to_typed_module(
17017 module,
17018 bus,
17019 total,
17020 allocation,
17021 |value| match value {
17022 PioValue::AcPfInstance(instance) => Ok(instance),
17023 other => Err(other),
17024 },
17025 PioValue::AcPfInstance,
17026 ),
17027 PioValue::DcOpfInstance(_) => apply_bus_load_to_typed_module(
17028 module,
17029 bus,
17030 total,
17031 allocation,
17032 |value| match value {
17033 PioValue::DcOpfInstance(instance) => Ok(instance),
17034 other => Err(other),
17035 },
17036 PioValue::DcOpfInstance,
17037 ),
17038 PioValue::AcOpfInstance(_) => apply_bus_load_to_typed_module(
17039 module,
17040 bus,
17041 total,
17042 allocation,
17043 |value| match value {
17044 PioValue::AcOpfInstance(instance) => Ok(instance),
17045 other => Err(other),
17046 },
17047 PioValue::AcOpfInstance,
17048 ),
17049 value => Err(powerio_core::Error::new(
17050 &codes::REQUEST_CAPI_TYPE_MISMATCH,
17051 format!(
17052 "{} does not accept aggregate bus active demand",
17053 value.type_name()
17054 ),
17055 )),
17056 }
17057}
17058
17059fn parse_load_allocation(value: &str) -> Result<LoadAllocation, *mut PioError> {
17060 match value {
17061 "equal" => Ok(LoadAllocation::Equal),
17062 "proportional_to_current_active_power" => {
17063 Ok(LoadAllocation::ProportionalToCurrentActivePower)
17064 }
17065 other => Err(boundary_error(
17066 &codes::REQUEST_CAPI_ALLOCATION_UNKNOWN,
17067 format!("unknown load allocation rule '{other}'"),
17068 )),
17069 }
17070}
17071
17072fn separated_updates(
17073 updates: &[CalculationUpdate],
17074) -> Result<(Vec<OperatingPointUpdate>, Vec<NetworkUpdate>), powerio_core::Error> {
17075 let mut operating = Vec::new();
17076 let mut network = Vec::new();
17077 for update in updates {
17078 match update {
17079 CalculationUpdate::OperatingPoint(update) => operating.push(update.clone()),
17080 CalculationUpdate::Network(update) => network.push(update.clone()),
17081 _ => {
17082 return Err(powerio_core::Error::new(
17083 &codes::REQUEST_CAPI_TYPE_MISMATCH,
17084 "unsupported calculation update",
17085 ));
17086 }
17087 }
17088 }
17089 Ok((operating, network))
17090}
17091
17092fn reject_network_updates(
17093 updates: &[CalculationUpdate],
17094) -> Result<Vec<OperatingPointUpdate>, powerio_core::Error> {
17095 updates
17096 .iter()
17097 .map(|update| match update {
17098 CalculationUpdate::OperatingPoint(update) => Ok(update.clone()),
17099 CalculationUpdate::Network(_) => Err(powerio_core::Error::new(
17100 &codes::REQUEST_CAPI_TYPE_MISMATCH,
17101 "a network update cannot be applied to an operating point",
17102 )),
17103 _ => Err(powerio_core::Error::new(
17104 &codes::REQUEST_CAPI_TYPE_MISMATCH,
17105 "unsupported calculation update",
17106 )),
17107 })
17108 .collect()
17109}
17110
17111fn apply_dynamic_updates(
17112 value: &mut PioValue,
17113 updates: &[CalculationUpdate],
17114) -> Result<UpdateReportInner, powerio_core::Error> {
17115 let mut output = UpdateReportInner {
17116 changes: Vec::new(),
17117 connectivity_changed: false,
17118 };
17119 match value {
17120 PioValue::BalancedNetwork(network) => {
17121 let (operating, physical) = separated_updates(updates)?;
17122 append_update_report(&mut output, apply_updates(network, &operating)?);
17123 append_update_report(&mut output, apply_updates(network, &physical)?);
17124 }
17125 PioValue::MulticonductorNetwork(network) => {
17126 let (operating, physical) = separated_updates(updates)?;
17127 append_update_report(&mut output, apply_updates(network, &operating)?);
17128 append_update_report(&mut output, apply_updates(network, &physical)?);
17129 }
17130 PioValue::BalancedOperatingPoint(point) => {
17131 let operating = reject_network_updates(updates)?;
17132 append_update_report(&mut output, apply_updates(point, &operating)?);
17133 }
17134 PioValue::MulticonductorOperatingPoint(point) => {
17135 let operating = reject_network_updates(updates)?;
17136 append_update_report(&mut output, apply_updates(point, &operating)?);
17137 }
17138 PioValue::DcPfInstance(instance) => {
17139 append_update_report(&mut output, apply_updates(instance, updates)?);
17140 }
17141 PioValue::AcPfInstance(instance) => {
17142 append_update_report(&mut output, apply_updates(instance, updates)?);
17143 }
17144 PioValue::DcOpfInstance(instance) => {
17145 append_update_report(&mut output, apply_updates(instance, updates)?);
17146 }
17147 PioValue::AcOpfInstance(instance) => {
17148 append_update_report(&mut output, apply_updates(instance, updates)?);
17149 }
17150 PioValue::McAcPfInstance(instance) => {
17151 append_update_report(&mut output, apply_updates(instance, updates)?);
17152 }
17153 PioValue::McAcOpfInstance(instance) => {
17154 append_update_report(&mut output, apply_updates(instance, updates)?);
17155 }
17156 _ => {
17157 return Err(powerio_core::Error::new(
17158 &codes::REQUEST_CAPI_TYPE_MISMATCH,
17159 format!("{} does not accept calculation updates", value.type_name()),
17160 ));
17161 }
17162 }
17163 Ok(output)
17164}
17165
17166fn update_history_id(
17167 module: &powerio::PioModule<PioValue>,
17168) -> Result<HistoryId, powerio_core::Error> {
17169 let mut suffix = 1usize;
17170 loop {
17171 let value = if suffix == 1 {
17172 "apply-updates".to_owned()
17173 } else {
17174 format!("apply-updates-{suffix}")
17175 };
17176 if module
17177 .history()
17178 .iter()
17179 .all(|entry| entry.id().as_str() != value)
17180 {
17181 return HistoryId::new(value);
17182 }
17183 suffix += 1;
17184 }
17185}
17186
17187fn apply_module_updates(
17188 module: &mut powerio::PioModule<PioValue>,
17189 updates: &[CalculationUpdate],
17190) -> Result<UpdateReportInner, powerio_core::Error> {
17191 let history_id = update_history_id(module)?;
17192 let mut edit = module.stage_edit();
17193 let report = apply_dynamic_updates(edit.value_mut(), updates)?;
17194 if report.changes.is_empty() {
17195 return Ok(report);
17196 }
17197
17198 let mut parameters = std::collections::BTreeMap::new();
17199 parameters.insert(
17200 "updates".to_owned(),
17201 serde_json::to_value(updates).map_err(|failure| {
17202 powerio_core::Error::new(
17203 &codes::EMIT_CAPI_SERIALIZE_FAILED,
17204 format!("cannot record applied updates: {failure}"),
17205 )
17206 })?,
17207 );
17208 parameters.insert(
17209 "changes".to_owned(),
17210 serde_json::to_value(&report.changes).map_err(|failure| {
17211 powerio_core::Error::new(
17212 &codes::EMIT_CAPI_SERIALIZE_FAILED,
17213 format!("cannot record update changes: {failure}"),
17214 )
17215 })?,
17216 );
17217 parameters.insert(
17218 "connectivity_changed".to_owned(),
17219 serde_json::Value::Bool(report.connectivity_changed),
17220 );
17221 let history = HistoryEntry::new(history_id, HistoryKind::Edit, "apply_updates")?
17222 .with_parameters(parameters)?;
17223 edit.commit(Producer::new("powerio-capi", powerio::VERSION)?, history)?;
17224 Ok(report)
17225}
17226
17227unsafe fn module_make_mut<'a>(
17228 module: *mut PioModule,
17229) -> Result<&'a mut ModuleInner, *mut PioError> {
17230 let handle = unsafe { module.cast::<HandleBox<ModuleInner>>().as_mut() }.ok_or_else(|| {
17231 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
17232 })?;
17233 Ok(Arc::make_mut(&mut handle.inner))
17234}
17235
17236#[unsafe(no_mangle)]
17249pub unsafe extern "C" fn pio_apply_updates(
17250 module: *mut PioModule,
17251 updates: *const *const PioCalculationUpdate,
17252 updates_len: usize,
17253 error: *mut *mut PioError,
17254) -> *mut PioUpdateReport {
17255 unsafe {
17256 entry(error, std::ptr::null_mut(), || {
17257 if updates.is_null() && updates_len != 0 {
17258 return Err(boundary_error(
17259 &codes::BIND_CAPI_NULL_ARGUMENT,
17260 "updates is NULL with a nonzero length",
17261 ));
17262 }
17263 let handles = if updates_len == 0 {
17264 &[]
17265 } else {
17266 std::slice::from_raw_parts(updates, updates_len)
17267 };
17268 let mut values = Vec::with_capacity(handles.len());
17269 for (index, handle) in handles.iter().copied().enumerate() {
17270 let update = PioCalculationUpdate::get(handle).ok_or_else(|| {
17271 boundary_error(
17272 &codes::BIND_CAPI_NULL_HANDLE,
17273 format!("updates[{index}] must not be NULL"),
17274 )
17275 })?;
17276 values.push(update.clone());
17277 }
17278 let module = module_make_mut(module)?;
17279 apply_module_updates(&mut module.module, &values)
17280 .map(PioUpdateReport::new_raw)
17281 .map_err(|failure| error_from_core(&failure))
17282 })
17283 }
17284}
17285
17286#[unsafe(no_mangle)]
17295pub unsafe extern "C" fn pio_apply_bus_load_active_power(
17296 module: *mut PioModule,
17297 bus_id: usize,
17298 power: *const PioActivePower,
17299 allocation: *const c_char,
17300 allocation_len: usize,
17301 error: *mut *mut PioError,
17302) -> *mut PioUpdateReport {
17303 unsafe {
17304 entry(error, std::ptr::null_mut(), || {
17305 let total = active_power_copy(power)?;
17306 let allocation = required_str(allocation, allocation_len, "allocation")?;
17307 let allocation = parse_load_allocation(allocation)?;
17308 let module = module_make_mut(module)?;
17309 apply_dynamic_bus_load_active_power(
17310 &mut module.module,
17311 powerio_tx::BusId::new(bus_id),
17312 total,
17313 allocation,
17314 )
17315 .map(PioUpdateReport::new_raw)
17316 .map_err(|failure| error_from_core(&failure))
17317 })
17318 }
17319}
17320
17321#[unsafe(no_mangle)]
17325pub unsafe extern "C" fn pio_update_report_len(report: *const PioUpdateReport) -> usize {
17326 unsafe { PioUpdateReport::get(report) }.map_or(0, |report| report.changes.len())
17327}
17328
17329#[unsafe(no_mangle)]
17333pub unsafe extern "C" fn pio_update_report_connectivity_changed(
17334 report: *const PioUpdateReport,
17335) -> bool {
17336 unsafe { PioUpdateReport::get(report) }.is_some_and(|report| report.connectivity_changed)
17337}
17338
17339#[unsafe(no_mangle)]
17343pub unsafe extern "C" fn pio_update_report_change(
17344 report: *const PioUpdateReport,
17345 index: usize,
17346 error: *mut *mut PioError,
17347) -> *mut PioUpdateChange {
17348 unsafe {
17349 entry(error, std::ptr::null_mut(), || {
17350 let owner = PioUpdateReport::arc(report).ok_or_else(|| {
17351 boundary_error(
17352 &codes::BIND_CAPI_NULL_HANDLE,
17353 "PioUpdateReport must not be NULL",
17354 )
17355 })?;
17356 if index >= owner.changes.len() {
17357 return Err(boundary_error(
17358 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
17359 format!("update change index {index} is out of range"),
17360 ));
17361 }
17362 Ok(PioUpdateChange::new_raw(UpdateChangeInner { owner, index }))
17363 })
17364 }
17365}
17366
17367#[unsafe(no_mangle)]
17371pub unsafe extern "C" fn pio_update_change_component_id(
17372 change: *const PioUpdateChange,
17373) -> *mut PioComponentId {
17374 unsafe { PioUpdateChange::get(change) }
17375 .and_then(UpdateChangeInner::change)
17376 .map_or(std::ptr::null_mut(), |change| {
17377 PioComponentId::new_raw(change.component_id().clone())
17378 })
17379}
17380
17381fn updated_field_name(field: UpdatedField) -> &'static str {
17382 match field {
17383 UpdatedField::LoadActivePower => "load_active_power",
17384 UpdatedField::LoadReactivePower => "load_reactive_power",
17385 UpdatedField::GeneratorActivePower => "generator_active_power",
17386 UpdatedField::GeneratorReactivePower => "generator_reactive_power",
17387 UpdatedField::GeneratorVoltageMagnitude => "generator_voltage_magnitude",
17388 UpdatedField::GeneratorInService => "generator_in_service",
17389 UpdatedField::BranchThermalRating => "branch_thermal_rating",
17390 UpdatedField::BranchInService => "branch_in_service",
17391 UpdatedField::TransformerTapRatio => "transformer_tap_ratio",
17392 UpdatedField::TransformerPhaseShift => "transformer_phase_shift",
17393 UpdatedField::SwitchClosed => "switch_closed",
17394 _ => "unknown",
17395 }
17396}
17397
17398#[unsafe(no_mangle)]
17402pub unsafe extern "C" fn pio_update_change_field(change: *const PioUpdateChange) -> PioStringView {
17403 unsafe { PioUpdateChange::get(change) }
17404 .and_then(UpdateChangeInner::change)
17405 .map_or(PioStringView::EMPTY, |change| {
17406 PioStringView::new(updated_field_name(change.field()))
17407 })
17408}
17409
17410#[unsafe(no_mangle)]
17414pub unsafe extern "C" fn pio_update_change_terminal(
17415 change: *const PioUpdateChange,
17416) -> PioStringView {
17417 unsafe { PioUpdateChange::get(change) }
17418 .and_then(UpdateChangeInner::change)
17419 .and_then(UpdateChange::terminal)
17420 .map_or(PioStringView::EMPTY, PioStringView::new)
17421}
17422
17423#[unsafe(no_mangle)]
17428pub unsafe extern "C" fn pio_update_report_retain(
17429 report: *const PioUpdateReport,
17430) -> *mut PioUpdateReport {
17431 unsafe { PioUpdateReport::retain_raw(report) }
17432}
17433
17434#[unsafe(no_mangle)]
17439pub unsafe extern "C" fn pio_update_report_release(report: *mut PioUpdateReport) {
17440 unsafe { PioUpdateReport::release_raw(report) };
17441}
17442
17443#[unsafe(no_mangle)]
17448pub unsafe extern "C" fn pio_update_change_retain(
17449 change: *const PioUpdateChange,
17450) -> *mut PioUpdateChange {
17451 unsafe { PioUpdateChange::retain_raw(change) }
17452}
17453
17454#[unsafe(no_mangle)]
17459pub unsafe extern "C" fn pio_update_change_release(change: *mut PioUpdateChange) {
17460 unsafe { PioUpdateChange::release_raw(change) };
17461}
17462
17463struct ArtifactRecord {
17466 name: String,
17467 bytes: Option<Vec<u8>>,
17468}
17469
17470struct EmitResultInner {
17471 layout: &'static str,
17472 fidelity: &'static str,
17473 artifacts: Vec<ArtifactRecord>,
17474 diagnostics: Arc<DiagnosticsInner>,
17475}
17476
17477struct ArtifactInner {
17478 owner: Arc<EmitResultInner>,
17479 index: usize,
17480}
17481
17482impl ArtifactInner {
17483 fn artifact(&self) -> Option<&ArtifactRecord> {
17484 self.owner.artifacts.get(self.index)
17485 }
17486}
17487
17488opaque_handle!(
17489 PioEmitResult,
17491 EmitResultInner
17492);
17493opaque_handle!(
17494 PioArtifact,
17496 ArtifactInner
17497);
17498
17499fn emit_result_handle(result: EmitResult) -> Result<*mut PioEmitResult, *mut PioError> {
17500 let layout = match result.layout() {
17501 powerio::OutputLayout::File => "file",
17502 powerio::OutputLayout::Directory => "directory",
17503 };
17504 let fidelity = match result.fidelity() {
17505 powerio::Fidelity::ExactSameFormat => "exact_same_format",
17506 powerio::Fidelity::Canonical => "canonical",
17507 };
17508 let diagnostics = Arc::new(DiagnosticsInner {
17509 owner: DiagnosticsOwner::Owned(result.diagnostics().to_vec()),
17510 });
17511 let artifacts = match result.into_output() {
17512 EmittedOutput::Memory { artifacts } => artifacts
17513 .into_iter()
17514 .map(|artifact| {
17515 let name = artifact.name().as_str().to_owned();
17516 ArtifactRecord {
17517 name,
17518 bytes: Some(artifact.into_bytes()),
17519 }
17520 })
17521 .collect(),
17522 EmittedOutput::Path { artifacts, .. } => artifacts
17523 .into_iter()
17524 .map(|path| ArtifactRecord {
17525 name: path.to_string_lossy().into_owned(),
17526 bytes: None,
17527 })
17528 .collect(),
17529 _ => {
17530 return Err(boundary_error(
17531 &codes::REQUEST_CAPI_TYPE_MISMATCH,
17532 "unsupported emitted output",
17533 ));
17534 }
17535 };
17536 Ok(PioEmitResult::new_raw(EmitResultInner {
17537 layout,
17538 fidelity,
17539 artifacts,
17540 diagnostics,
17541 }))
17542}
17543
17544unsafe fn run_output_operation(
17545 module: *const PioModule,
17546 destination: *const PioDestination,
17547 error: *mut *mut PioError,
17548 operation: impl FnOnce(
17549 &powerio::PioModule<PioValue>,
17550 Destination,
17551 ) -> Result<EmitResult, *mut PioError>,
17552) -> *mut PioEmitResult {
17553 unsafe {
17554 entry(error, std::ptr::null_mut(), || {
17555 let module = PioModule::get(module).ok_or_else(|| {
17556 boundary_error(&codes::BIND_CAPI_NULL_HANDLE, "PioModule must not be NULL")
17557 })?;
17558 let destination = PioDestination::get(destination).ok_or_else(|| {
17559 boundary_error(
17560 &codes::BIND_CAPI_NULL_HANDLE,
17561 "PioDestination must not be NULL",
17562 )
17563 })?;
17564 let destination = destination
17565 .build()
17566 .map_err(|failure| error_from_core(&failure))?;
17567 operation(&module.module, destination).and_then(emit_result_handle)
17568 })
17569 }
17570}
17571
17572#[unsafe(no_mangle)]
17577pub unsafe extern "C" fn pio_emit(
17578 module: *const PioModule,
17579 format: *const c_char,
17580 format_len: usize,
17581 destination: *const PioDestination,
17582 error: *mut *mut PioError,
17583) -> *mut PioEmitResult {
17584 unsafe {
17585 run_output_operation(module, destination, error, |module, destination| {
17586 let format = required_str(format, format_len, "format")?;
17587 powerio::emit(module, format, destination).map_err(|failure| error_from_core(&failure))
17588 })
17589 }
17590}
17591
17592#[unsafe(no_mangle)]
17597pub unsafe extern "C" fn pio_module_serialize(
17598 module: *const PioModule,
17599 destination: *const PioDestination,
17600 error: *mut *mut PioError,
17601) -> *mut PioEmitResult {
17602 unsafe {
17603 run_output_operation(module, destination, error, |module, destination| {
17604 powerio::serialize(module, destination).map_err(|failure| error_from_core(&failure))
17605 })
17606 }
17607}
17608
17609#[unsafe(no_mangle)]
17613pub unsafe extern "C" fn pio_emit_result_layout(result: *const PioEmitResult) -> PioStringView {
17614 unsafe { PioEmitResult::get(result) }.map_or(PioStringView::EMPTY, |result| {
17615 PioStringView::new(result.layout)
17616 })
17617}
17618
17619#[unsafe(no_mangle)]
17623pub unsafe extern "C" fn pio_emit_result_fidelity(result: *const PioEmitResult) -> PioStringView {
17624 unsafe { PioEmitResult::get(result) }.map_or(PioStringView::EMPTY, |result| {
17625 PioStringView::new(result.fidelity)
17626 })
17627}
17628
17629#[unsafe(no_mangle)]
17633pub unsafe extern "C" fn pio_emit_result_artifact_count(result: *const PioEmitResult) -> usize {
17634 unsafe { PioEmitResult::get(result) }.map_or(0, |result| result.artifacts.len())
17635}
17636
17637#[unsafe(no_mangle)]
17641pub unsafe extern "C" fn pio_emit_result_artifact(
17642 result: *const PioEmitResult,
17643 index: usize,
17644 error: *mut *mut PioError,
17645) -> *mut PioArtifact {
17646 unsafe {
17647 entry(error, std::ptr::null_mut(), || {
17648 let owner = PioEmitResult::arc(result).ok_or_else(|| {
17649 boundary_error(
17650 &codes::BIND_CAPI_NULL_HANDLE,
17651 "PioEmitResult must not be NULL",
17652 )
17653 })?;
17654 if index >= owner.artifacts.len() {
17655 return Err(boundary_error(
17656 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
17657 format!("artifact index {index} is out of range"),
17658 ));
17659 }
17660 Ok(PioArtifact::new_raw(ArtifactInner { owner, index }))
17661 })
17662 }
17663}
17664
17665#[unsafe(no_mangle)]
17669pub unsafe extern "C" fn pio_emit_result_diagnostics(
17670 result: *const PioEmitResult,
17671) -> *mut PioDiagnostics {
17672 unsafe { PioEmitResult::get(result) }.map_or(std::ptr::null_mut(), |result| {
17673 PioDiagnostics::from_arc(Arc::clone(&result.diagnostics))
17674 })
17675}
17676
17677#[unsafe(no_mangle)]
17682pub unsafe extern "C" fn pio_emit_result_retain(
17683 result: *const PioEmitResult,
17684) -> *mut PioEmitResult {
17685 unsafe { PioEmitResult::retain_raw(result) }
17686}
17687
17688#[unsafe(no_mangle)]
17693pub unsafe extern "C" fn pio_emit_result_release(result: *mut PioEmitResult) {
17694 unsafe { PioEmitResult::release_raw(result) };
17695}
17696
17697#[unsafe(no_mangle)]
17701pub unsafe extern "C" fn pio_artifact_name(artifact: *const PioArtifact) -> PioStringView {
17702 unsafe { PioArtifact::get(artifact) }
17703 .and_then(ArtifactInner::artifact)
17704 .map_or(PioStringView::EMPTY, |artifact| {
17705 PioStringView::new(&artifact.name)
17706 })
17707}
17708
17709#[unsafe(no_mangle)]
17715pub unsafe extern "C" fn pio_artifact_bytes(artifact: *const PioArtifact) -> PioByteView {
17716 unsafe { PioArtifact::get(artifact) }
17717 .and_then(ArtifactInner::artifact)
17718 .and_then(|artifact| artifact.bytes.as_deref())
17719 .map_or(PioByteView::EMPTY, PioByteView::new)
17720}
17721
17722#[unsafe(no_mangle)]
17727pub unsafe extern "C" fn pio_artifact_retain(artifact: *const PioArtifact) -> *mut PioArtifact {
17728 unsafe { PioArtifact::retain_raw(artifact) }
17729}
17730
17731#[unsafe(no_mangle)]
17736pub unsafe extern "C" fn pio_artifact_release(artifact: *mut PioArtifact) {
17737 unsafe { PioArtifact::release_raw(artifact) };
17738}
17739
17740struct SparseMatrixInner {
17743 rows: usize,
17744 columns: usize,
17745 row_offsets: Vec<usize>,
17746 column_indices: Vec<usize>,
17747 values: Vec<f64>,
17748}
17749
17750impl From<SparseMatrix> for SparseMatrixInner {
17751 fn from(matrix: SparseMatrix) -> Self {
17752 let matrix = matrix.to_csr();
17753 Self {
17754 rows: matrix.rows(),
17755 columns: matrix.cols(),
17756 row_offsets: matrix.indptr().raw_storage().to_vec(),
17757 column_indices: matrix.indices().to_vec(),
17758 values: matrix.data().to_vec(),
17759 }
17760 }
17761}
17762
17763struct VectorInner {
17764 values: Vec<f64>,
17765}
17766
17767opaque_handle!(
17768 PioSparseMatrix,
17770 SparseMatrixInner
17771);
17772opaque_handle!(
17773 PioVector,
17775 VectorInner
17776);
17777
17778fn termination_name(termination: &powerio_prob::Termination) -> &'static str {
17779 use powerio_prob::Termination;
17780 match termination {
17781 Termination::Converged => "converged",
17782 Termination::IterationLimit => "iteration_limit",
17783 Termination::Infeasible => "infeasible",
17784 Termination::Unbounded => "unbounded",
17785 Termination::Failed => "failed",
17786 Termination::NotReported => "not_reported",
17787 _ => "not_reported",
17788 }
17789}
17790
17791#[unsafe(no_mangle)]
17795pub unsafe extern "C" fn pio_calculation_solution_termination(
17796 solution: *const PioCalculationSolution,
17797) -> PioStringView {
17798 let Some(solution) =
17799 (unsafe { PioCalculationSolution::get(solution) }).and_then(ValueInner::value)
17800 else {
17801 return PioStringView::EMPTY;
17802 };
17803 let termination = match solution {
17804 PioValue::DcPfSolution(solution) => solution.termination(),
17805 PioValue::AcPfSolution(solution) => solution.termination(),
17806 PioValue::DcOpfSolution(solution) => solution.termination(),
17807 PioValue::AcOpfSolution(solution) => solution.termination(),
17808 PioValue::SocwrOpfSolution(solution) => solution.termination(),
17809 PioValue::McAcPfSolution(solution) => solution.termination(),
17810 PioValue::McAcOpfSolution(solution) => solution.termination(),
17811 PioValue::LinDist3FlowOpfSolution(solution) => solution.termination(),
17812 PioValue::AcScucSolution(solution) => solution.termination(),
17813 _ => return PioStringView::EMPTY,
17814 };
17815 PioStringView::new(termination_name(termination))
17816}
17817
17818#[unsafe(no_mangle)]
17824pub unsafe extern "C" fn pio_calculation_solution_get_objective(
17825 solution: *const PioCalculationSolution,
17826 out_objective: *mut f64,
17827) -> bool {
17828 if out_objective.is_null() {
17829 return false;
17830 }
17831 unsafe {
17834 entry(std::ptr::null_mut(), false, || {
17835 let Some(solution) = PioCalculationSolution::get(solution).and_then(ValueInner::value)
17836 else {
17837 return Ok(false);
17838 };
17839 let objective = match solution {
17840 PioValue::DcOpfSolution(solution) => Some(solution.objective()),
17841 PioValue::AcOpfSolution(solution) => Some(solution.objective()),
17842 PioValue::McAcOpfSolution(solution) => Some(solution.objective()),
17843 PioValue::LinDist3FlowOpfSolution(solution) => Some(solution.objective()),
17844 PioValue::AcScucSolution(solution) => solution.objective(),
17845 _ => None,
17846 };
17847 Ok(match objective {
17848 Some(objective) => {
17849 *out_objective = objective;
17850 true
17851 }
17852 None => false,
17853 })
17854 })
17855 }
17856}
17857
17858#[unsafe(no_mangle)]
17862pub unsafe extern "C" fn pio_socwr_opf_solution_get_objective_lower_bound(
17863 solution: *const PioCalculationSolution,
17864 out_lower_bound: *mut f64,
17865) -> bool {
17866 if out_lower_bound.is_null() {
17867 return false;
17868 }
17869 unsafe {
17871 entry(std::ptr::null_mut(), false, || {
17872 let Some(PioValue::SocwrOpfSolution(solution)) =
17873 PioCalculationSolution::get(solution).and_then(ValueInner::value)
17874 else {
17875 return Ok(false);
17876 };
17877 *out_lower_bound = solution.objective_lower_bound();
17878 Ok(true)
17879 })
17880 }
17881}
17882
17883fn balanced_branch_identities(network: &BalancedNetwork) -> impl Iterator<Item = &str> {
17884 network
17885 .branches()
17886 .iter()
17887 .map(|branch| branch.uid.as_deref().expect("facade assigns component IDs"))
17888}
17889
17890fn balanced_generator_identities(network: &BalancedNetwork) -> impl Iterator<Item = &str> {
17891 network.generators().iter().map(|generator| {
17892 generator
17893 .uid
17894 .as_deref()
17895 .expect("facade assigns component IDs")
17896 })
17897}
17898
17899fn collect_solution_values(solution: &PioValue, quantity: &str) -> Result<Vec<f64>, *mut PioError> {
17900 let unknown = || {
17901 boundary_error(
17902 &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
17903 format!(
17904 "{} does not define solution quantity '{quantity}'",
17905 solution.type_name()
17906 ),
17907 )
17908 };
17909 let values = match solution {
17910 PioValue::DcPfSolution(solution) => match quantity {
17911 "bus_voltage_angle" => solution.bus_voltage_angles().to_vec(),
17912 "bus_active_injection" => solution.bus_active_injections().to_vec(),
17913 "branch_from_active_flow" => solution.branch_from_active_flows().to_vec(),
17914 "branch_to_active_flow" => solution.branch_to_active_flows().to_vec(),
17915 "generator_active_power" => solution
17916 .generator_dispatch()
17917 .map(|dispatch| dispatch.p_mw.clone())
17918 .ok_or_else(unknown)?,
17919 "generator_reactive_power" => solution
17920 .generator_dispatch()
17921 .map(|dispatch| dispatch.q_mvar.clone())
17922 .ok_or_else(unknown)?,
17923 _ => return Err(unknown()),
17924 },
17925 PioValue::AcPfSolution(solution) => match quantity {
17926 "bus_voltage_magnitude" => solution
17927 .network()
17928 .buses()
17929 .iter()
17930 .map(|bus| {
17931 solution
17932 .bus_voltage_magnitude(bus.id)
17933 .expect("complete solution")
17934 })
17935 .collect(),
17936 "bus_voltage_angle" => solution
17937 .network()
17938 .buses()
17939 .iter()
17940 .map(|bus| {
17941 solution
17942 .bus_voltage_angle(bus.id)
17943 .expect("complete solution")
17944 })
17945 .collect(),
17946 "bus_active_injection" => solution
17947 .network()
17948 .buses()
17949 .iter()
17950 .map(|bus| {
17951 solution
17952 .bus_active_injection(bus.id)
17953 .expect("complete solution")
17954 })
17955 .collect(),
17956 "bus_reactive_injection" => solution
17957 .network()
17958 .buses()
17959 .iter()
17960 .map(|bus| {
17961 solution
17962 .bus_reactive_injection(bus.id)
17963 .expect("complete solution")
17964 })
17965 .collect(),
17966 "branch_from_active_flow" => balanced_branch_identities(solution.network())
17967 .map(|id| {
17968 solution
17969 .branch_from_active_flow(id)
17970 .expect("complete solution")
17971 })
17972 .collect(),
17973 "branch_from_reactive_flow" => balanced_branch_identities(solution.network())
17974 .map(|id| {
17975 solution
17976 .branch_from_reactive_flow(id)
17977 .expect("complete solution")
17978 })
17979 .collect(),
17980 "branch_to_active_flow" => balanced_branch_identities(solution.network())
17981 .map(|id| {
17982 solution
17983 .branch_to_active_flow(id)
17984 .expect("complete solution")
17985 })
17986 .collect(),
17987 "branch_to_reactive_flow" => balanced_branch_identities(solution.network())
17988 .map(|id| {
17989 solution
17990 .branch_to_reactive_flow(id)
17991 .expect("complete solution")
17992 })
17993 .collect(),
17994 "generator_active_power" => solution
17995 .generator_dispatch()
17996 .map(|dispatch| dispatch.p_mw.clone())
17997 .ok_or_else(unknown)?,
17998 "generator_reactive_power" => solution
17999 .generator_dispatch()
18000 .map(|dispatch| dispatch.q_mvar.clone())
18001 .ok_or_else(unknown)?,
18002 _ => return Err(unknown()),
18003 },
18004 PioValue::DcOpfSolution(solution) => match quantity {
18005 "bus_voltage_angle" => solution
18006 .network()
18007 .buses()
18008 .iter()
18009 .map(|bus| {
18010 solution
18011 .bus_voltage_angle(bus.id)
18012 .expect("complete solution")
18013 })
18014 .collect(),
18015 "bus_active_injection" => solution
18016 .network()
18017 .buses()
18018 .iter()
18019 .map(|bus| {
18020 solution
18021 .bus_active_injection(bus.id)
18022 .expect("complete solution")
18023 })
18024 .collect(),
18025 "branch_from_active_flow" => balanced_branch_identities(solution.network())
18026 .map(|id| {
18027 solution
18028 .branch_from_active_flow(id)
18029 .expect("complete solution")
18030 })
18031 .collect(),
18032 "branch_to_active_flow" => balanced_branch_identities(solution.network())
18033 .map(|id| {
18034 solution
18035 .branch_to_active_flow(id)
18036 .expect("complete solution")
18037 })
18038 .collect(),
18039 "generator_active_power" => balanced_generator_identities(solution.network())
18040 .map(|id| {
18041 solution
18042 .generator_active_power(id)
18043 .expect("complete solution")
18044 })
18045 .collect(),
18046 "bus_active_power_marginal" => solution
18047 .bus_active_power_marginals()
18048 .map(<[f64]>::to_vec)
18049 .ok_or_else(unknown)?,
18050 "branch_from_limit_multiplier" => solution
18051 .branch_from_limit_multipliers()
18052 .map(<[f64]>::to_vec)
18053 .ok_or_else(unknown)?,
18054 "branch_to_limit_multiplier" => solution
18055 .branch_to_limit_multipliers()
18056 .map(<[f64]>::to_vec)
18057 .ok_or_else(unknown)?,
18058 _ => return Err(unknown()),
18059 },
18060 PioValue::AcOpfSolution(solution) => match quantity {
18061 "bus_voltage_magnitude" => solution.bus_voltage_magnitudes().to_vec(),
18062 "bus_voltage_angle" => solution.bus_voltage_angles().to_vec(),
18063 "bus_active_injection" => solution.bus_active_injections().to_vec(),
18064 "bus_reactive_injection" => solution.bus_reactive_injections().to_vec(),
18065 "branch_from_active_flow" => solution.branch_from_active_flows().to_vec(),
18066 "branch_from_reactive_flow" => solution.branch_from_reactive_flows().to_vec(),
18067 "branch_to_active_flow" => solution.branch_to_active_flows().to_vec(),
18068 "branch_to_reactive_flow" => solution.branch_to_reactive_flows().to_vec(),
18069 "generator_active_power" => solution.generator_active_powers().to_vec(),
18070 "generator_reactive_power" => solution.generator_reactive_powers().to_vec(),
18071 "bus_active_power_marginal" => solution
18072 .bus_active_power_marginals()
18073 .map(<[f64]>::to_vec)
18074 .ok_or_else(unknown)?,
18075 "bus_reactive_power_marginal" => solution
18076 .bus_reactive_power_marginals()
18077 .map(<[f64]>::to_vec)
18078 .ok_or_else(unknown)?,
18079 "branch_from_limit_multiplier" => solution
18080 .branch_from_limit_multipliers()
18081 .map(<[f64]>::to_vec)
18082 .ok_or_else(unknown)?,
18083 "branch_to_limit_multiplier" => solution
18084 .branch_to_limit_multipliers()
18085 .map(<[f64]>::to_vec)
18086 .ok_or_else(unknown)?,
18087 _ => return Err(unknown()),
18088 },
18089 PioValue::SocwrOpfSolution(solution) => {
18090 let values = solution.values();
18091 match quantity {
18092 "bus_voltage_magnitude_squared" => values.bus_voltage_magnitude_squared.clone(),
18093 "branch_voltage_product_real" => values.branch_voltage_product_real.clone(),
18094 "branch_voltage_product_imaginary" => {
18095 values.branch_voltage_product_imaginary.clone()
18096 }
18097 "generator_active_power" => values.generator_active_power.clone(),
18098 "generator_reactive_power" => values.generator_reactive_power.clone(),
18099 "branch_from_active_power" => values.branch_from_active_power.clone(),
18100 "branch_from_reactive_power" => values.branch_from_reactive_power.clone(),
18101 "branch_to_active_power" => values.branch_to_active_power.clone(),
18102 "branch_to_reactive_power" => values.branch_to_reactive_power.clone(),
18103 _ => return Err(unknown()),
18104 }
18105 }
18106 PioValue::McAcPfSolution(solution) => match quantity {
18107 "terminal_voltage_magnitude" => solution
18108 .network()
18109 .buses()
18110 .iter()
18111 .flat_map(|bus| {
18112 bus.terminals.iter().map(move |terminal| {
18113 solution
18114 .terminal_voltage_magnitude(&bus.id, terminal)
18115 .expect("complete solution")
18116 })
18117 })
18118 .collect(),
18119 "terminal_voltage_angle" => solution
18120 .network()
18121 .buses()
18122 .iter()
18123 .flat_map(|bus| {
18124 bus.terminals.iter().map(move |terminal| {
18125 solution
18126 .terminal_voltage_angle(&bus.id, terminal)
18127 .expect("complete solution")
18128 })
18129 })
18130 .collect(),
18131 "source_active_injection" => solution.source_active_injections().to_vec(),
18132 _ => return Err(unknown()),
18133 },
18134 PioValue::LinDist3FlowOpfSolution(solution) => match quantity {
18135 "terminal_voltage_magnitude_squared" => {
18136 solution.values().terminal_voltage_magnitude_squared.clone()
18137 }
18138 "line_active_power" => solution.values().line_active_power.clone(),
18139 "line_reactive_power" => solution.values().line_reactive_power.clone(),
18140 "generator_active_power" => solution.values().generator_active_power.clone(),
18141 "generator_reactive_power" => solution.values().generator_reactive_power.clone(),
18142 "source_active_power" => solution.values().source_active_power.clone(),
18143 "source_reactive_power" => solution.values().source_reactive_power.clone(),
18144 _ => return Err(unknown()),
18145 },
18146 PioValue::McAcOpfSolution(solution) => match quantity {
18147 "terminal_voltage_magnitude" => solution
18148 .network()
18149 .buses()
18150 .iter()
18151 .flat_map(|bus| {
18152 bus.terminals.iter().map(move |terminal| {
18153 solution
18154 .terminal_voltage_magnitude(&bus.id, terminal)
18155 .expect("complete solution")
18156 })
18157 })
18158 .collect(),
18159 "terminal_voltage_angle" => solution
18160 .network()
18161 .buses()
18162 .iter()
18163 .flat_map(|bus| {
18164 bus.terminals.iter().map(move |terminal| {
18165 solution
18166 .terminal_voltage_angle(&bus.id, terminal)
18167 .expect("complete solution")
18168 })
18169 })
18170 .collect(),
18171 "source_active_injection" => solution.source_active_injections().to_vec(),
18172 "generator_active_power" => solution.generator_active_powers().to_vec(),
18173 _ => return Err(unknown()),
18174 },
18175 _ => {
18176 return Err(boundary_error(
18177 &codes::REQUEST_CAPI_TYPE_MISMATCH,
18178 "the handle does not refer to a supported calculation solution",
18179 ));
18180 }
18181 };
18182 Ok(values)
18183}
18184
18185#[unsafe(no_mangle)]
18190pub unsafe extern "C" fn pio_calculation_solution_get_values(
18191 solution: *const PioCalculationSolution,
18192 quantity: *const c_char,
18193 quantity_len: usize,
18194 error: *mut *mut PioError,
18195) -> *mut PioVector {
18196 unsafe {
18197 entry(error, std::ptr::null_mut(), || {
18198 let quantity = required_str(quantity, quantity_len, "quantity")?;
18199 let solution = PioCalculationSolution::get(solution)
18200 .and_then(ValueInner::value)
18201 .ok_or_else(|| {
18202 boundary_error(
18203 &codes::BIND_CAPI_NULL_HANDLE,
18204 "PioCalculationSolution must not be NULL",
18205 )
18206 })?;
18207 collect_solution_values(solution, quantity)
18208 .map(|values| PioVector::new_raw(VectorInner { values }))
18209 })
18210 }
18211}
18212
18213#[unsafe(no_mangle)]
18217pub unsafe extern "C" fn pio_ac_scuc_solution_time_count(
18218 solution: *const PioCalculationSolution,
18219) -> usize {
18220 let Some(PioValue::AcScucSolution(solution)) =
18221 (unsafe { PioCalculationSolution::get(solution) }).and_then(ValueInner::value)
18222 else {
18223 return 0;
18224 };
18225 solution.instance().inputs().interval_durations.len()
18226}
18227
18228#[unsafe(no_mangle)]
18233pub unsafe extern "C" fn pio_ac_scuc_solution_get_values_at(
18234 solution: *const PioCalculationSolution,
18235 quantity: *const c_char,
18236 quantity_len: usize,
18237 time_index: usize,
18238 error: *mut *mut PioError,
18239) -> *mut PioVector {
18240 unsafe {
18241 entry(error, std::ptr::null_mut(), || {
18242 let quantity = required_str(quantity, quantity_len, "quantity")?;
18243 let Some(PioValue::AcScucSolution(solution)) =
18244 PioCalculationSolution::get(solution).and_then(ValueInner::value)
18245 else {
18246 return Err(boundary_error(
18247 &codes::REQUEST_CAPI_TYPE_MISMATCH,
18248 "the handle does not refer to powerio.AcScucSolution",
18249 ));
18250 };
18251 let network = solution.network_outputs();
18252 let device = solution.device_outputs();
18253 macro_rules! row {
18254 ($rows:expr, $convert:expr) => {{
18255 $rows
18256 .get(time_index)
18257 .ok_or_else(|| {
18258 boundary_error(
18259 &codes::BIND_CAPI_INDEX_OUT_OF_RANGE,
18260 format!("AC SCUC time index {time_index} is out of range"),
18261 )
18262 })?
18263 .iter()
18264 .copied()
18265 .map($convert)
18266 .collect::<Vec<f64>>()
18267 }};
18268 }
18269 let values = match quantity {
18270 "bus_voltage_magnitude" => row!(&network.bus_vm, |value| value),
18271 "bus_voltage_angle" => row!(&network.bus_va, |value| value),
18272 "shunt_step" => row!(&network.shunt_step, |value| value as f64),
18273 "ac_line_on_status" => row!(&network.ac_line_on_status, f64::from),
18274 "transformer_tap_ratio" => row!(&network.transformer_tm, |value| value),
18275 "transformer_phase_shift" => row!(&network.transformer_ta, |value| value),
18276 "transformer_on_status" => row!(&network.transformer_on_status, f64::from),
18277 "dc_line_from_active_power" => row!(&network.dc_line_pdc_fr, |value| value),
18278 "dc_line_from_reactive_power" => row!(&network.dc_line_qdc_fr, |value| value),
18279 "dc_line_to_reactive_power" => row!(&network.dc_line_qdc_to, |value| value),
18280 "device_on_status" => row!(&device.on_status, f64::from),
18281 "device_startup_status" => row!(&device.startup_status, f64::from),
18282 "device_shutdown_status" => row!(&device.shutdown_status, f64::from),
18283 "device_active_power" => row!(&device.p_on, |value| value),
18284 "device_reactive_power" => row!(&device.q, |value| value),
18285 "regulation_reserve_up" => row!(&device.p_reg_res_up, |value| value),
18286 "regulation_reserve_down" => row!(&device.p_reg_res_down, |value| value),
18287 "synchronized_reserve" => row!(&device.p_syn_res, |value| value),
18288 "nonsynchronized_reserve" => row!(&device.p_nsyn_res, |value| value),
18289 "ramping_reserve_up_online" => row!(&device.p_ramp_res_up_online, |value| value),
18290 "ramping_reserve_up_offline" => {
18291 row!(&device.p_ramp_res_up_offline, |value| value)
18292 }
18293 "ramping_reserve_down_online" => {
18294 row!(&device.p_ramp_res_down_online, |value| value)
18295 }
18296 "ramping_reserve_down_offline" => {
18297 row!(&device.p_ramp_res_down_offline, |value| value)
18298 }
18299 "reactive_reserve_up" => row!(&device.q_res_up, |value| value),
18300 "reactive_reserve_down" => row!(&device.q_res_down, |value| value),
18301 _ => {
18302 return Err(boundary_error(
18303 &codes::REQUEST_CAPI_QUANTITY_UNKNOWN,
18304 format!("unknown AC SCUC solution quantity '{quantity}'"),
18305 ));
18306 }
18307 };
18308 Ok(PioVector::new_raw(VectorInner { values }))
18309 })
18310 }
18311}
18312
18313fn formula(name: Option<&str>) -> Result<BranchSusceptanceFormula, *mut PioError> {
18314 match name.unwrap_or("series_susceptance") {
18315 "series_susceptance" => Ok(BranchSusceptanceFormula::SeriesSusceptance),
18316 "tap_adjusted_reactance" => Ok(BranchSusceptanceFormula::TapAdjustedReactance),
18317 "reactance_only" => Ok(BranchSusceptanceFormula::ReactanceOnly),
18318 other => Err(boundary_error(
18319 &codes::REQUEST_CAPI_UNKNOWN_FORMULA,
18320 format!("unknown branch susceptance formula '{other}'"),
18321 )),
18322 }
18323}
18324
18325unsafe fn dc_operators(
18326 network: *const PioBalancedNetwork,
18327 formula_name: *const c_char,
18328 formula_name_len: usize,
18329) -> Result<DcOperators, *mut PioError> {
18330 let network = unsafe { PioBalancedNetwork::get(network) }
18331 .and_then(BalancedNetworkInner::network)
18332 .ok_or_else(|| {
18333 boundary_error(
18334 &codes::BIND_CAPI_NULL_HANDLE,
18335 "PioBalancedNetwork must not be NULL",
18336 )
18337 })?;
18338 let formula_name = unsafe { optional_str(formula_name, formula_name_len, "formula") }?;
18339 let formula = formula(formula_name)?;
18340 let instance = DcPfInstance::from_network(network.clone())
18341 .map_err(|failure| error_from_core(&failure))?
18342 .with_branch_susceptance_formula(formula);
18343 DcOperators::build(&instance).map_err(|failure| error_from_core(&failure))
18344}
18345
18346struct DcOperatorsInner {
18349 operators: DcOperators,
18350 bus_ids: Vec<usize>,
18351}
18352
18353opaque_handle!(
18354 PioDcOperators,
18358 DcOperatorsInner
18359);
18360
18361#[unsafe(no_mangle)]
18374pub unsafe extern "C" fn pio_calc_dc_operators(
18375 network: *const PioBalancedNetwork,
18376 formula: *const c_char,
18377 formula_len: usize,
18378 skip_zero_impedance: bool,
18379 error: *mut *mut PioError,
18380) -> *mut PioDcOperators {
18381 unsafe {
18382 entry(error, std::ptr::null_mut(), || {
18383 let network = PioBalancedNetwork::get(network)
18384 .and_then(BalancedNetworkInner::network)
18385 .ok_or_else(|| {
18386 boundary_error(
18387 &codes::BIND_CAPI_NULL_HANDLE,
18388 "PioBalancedNetwork must not be NULL",
18389 )
18390 })?;
18391 let formula_name = optional_str(formula, formula_len, "formula")?;
18392 let formula = self::formula(formula_name)?;
18393 let instance = DcPfInstance::from_network(network.clone())
18394 .map_err(|failure| error_from_core(&failure))?
18395 .with_branch_susceptance_formula(formula);
18396 let options = DcOperatorOptions::new().with_skip_zero_impedance(skip_zero_impedance);
18397 let operators = DcOperators::build_with(&instance, &options)
18398 .map_err(|failure| error_from_core(&failure))?;
18399 let bus_ids = operators.bus_ids().iter().map(|bus| bus.0).collect();
18400 Ok(PioDcOperators::new_raw(DcOperatorsInner {
18401 operators,
18402 bus_ids,
18403 }))
18404 })
18405 }
18406}
18407
18408#[unsafe(no_mangle)]
18413pub unsafe extern "C" fn pio_dc_operators_n_buses(operators: *const PioDcOperators) -> usize {
18414 unsafe { PioDcOperators::get(operators) }.map_or(0, |inner| inner.bus_ids.len())
18415}
18416
18417#[unsafe(no_mangle)]
18422pub unsafe extern "C" fn pio_dc_operators_n_branches(operators: *const PioDcOperators) -> usize {
18423 unsafe { PioDcOperators::get(operators) }
18424 .map_or(0, |inner| inner.operators.branch_identities().len())
18425}
18426
18427#[unsafe(no_mangle)]
18432pub unsafe extern "C" fn pio_dc_operators_bus_ids(operators: *const PioDcOperators) -> PioSizeView {
18433 unsafe { PioDcOperators::get(operators) }
18434 .map_or(PioSizeView::EMPTY, |inner| PioSizeView::new(&inner.bus_ids))
18435}
18436
18437#[unsafe(no_mangle)]
18444pub unsafe extern "C" fn pio_dc_operators_branch_rows(
18445 operators: *const PioDcOperators,
18446) -> PioSizeView {
18447 unsafe { PioDcOperators::get(operators) }.map_or(PioSizeView::EMPTY, |inner| {
18448 PioSizeView::new(inner.operators.branch_rows())
18449 })
18450}
18451
18452#[unsafe(no_mangle)]
18458pub unsafe extern "C" fn pio_dc_operators_skipped_branch_rows(
18459 operators: *const PioDcOperators,
18460) -> PioSizeView {
18461 unsafe { PioDcOperators::get(operators) }.map_or(PioSizeView::EMPTY, |inner| {
18462 PioSizeView::new(inner.operators.skipped_branch_rows())
18463 })
18464}
18465
18466#[unsafe(no_mangle)]
18473pub unsafe extern "C" fn pio_dc_operators_branch_identity(
18474 operators: *const PioDcOperators,
18475 index: usize,
18476) -> PioStringView {
18477 unsafe { PioDcOperators::get(operators) }
18478 .and_then(|inner| inner.operators.branch_identities().get(index))
18479 .map_or(PioStringView::EMPTY, |identity| {
18480 PioStringView::new(identity)
18481 })
18482}
18483
18484unsafe fn dc_operators_matrix(
18485 operators: *const PioDcOperators,
18486 error: *mut *mut PioError,
18487 calculation: impl FnOnce(&DcOperators) -> SparseMatrix,
18488) -> *mut PioSparseMatrix {
18489 unsafe {
18490 entry(error, std::ptr::null_mut(), || {
18491 let inner = PioDcOperators::get(operators).ok_or_else(|| {
18492 boundary_error(
18493 &codes::BIND_CAPI_NULL_HANDLE,
18494 "PioDcOperators must not be NULL",
18495 )
18496 })?;
18497 Ok(PioSparseMatrix::new_raw(SparseMatrixInner::from(
18498 calculation(&inner.operators),
18499 )))
18500 })
18501 }
18502}
18503
18504unsafe fn dc_operators_vector(
18505 operators: *const PioDcOperators,
18506 error: *mut *mut PioError,
18507 calculation: impl FnOnce(&DcOperators) -> Vec<f64>,
18508) -> *mut PioVector {
18509 unsafe {
18510 entry(error, std::ptr::null_mut(), || {
18511 let inner = PioDcOperators::get(operators).ok_or_else(|| {
18512 boundary_error(
18513 &codes::BIND_CAPI_NULL_HANDLE,
18514 "PioDcOperators must not be NULL",
18515 )
18516 })?;
18517 Ok(PioVector::new_raw(VectorInner {
18518 values: calculation(&inner.operators),
18519 }))
18520 })
18521 }
18522}
18523
18524unsafe fn dc_operators_vector_from_angles(
18525 operators: *const PioDcOperators,
18526 voltage_angles: *const f64,
18527 voltage_angles_len: usize,
18528 error: *mut *mut PioError,
18529 calculation: impl FnOnce(&DcOperators, &[f64]) -> Result<Vec<f64>, powerio_core::Error>,
18530) -> *mut PioVector {
18531 unsafe {
18532 entry(error, std::ptr::null_mut(), || {
18533 if voltage_angles.is_null() && voltage_angles_len != 0 {
18534 return Err(boundary_error(
18535 &codes::BIND_CAPI_NULL_ARGUMENT,
18536 "voltage_angles is NULL with a nonzero length",
18537 ));
18538 }
18539 let angles = if voltage_angles_len == 0 {
18540 &[]
18541 } else {
18542 std::slice::from_raw_parts(voltage_angles, voltage_angles_len)
18543 };
18544 let inner = PioDcOperators::get(operators).ok_or_else(|| {
18545 boundary_error(
18546 &codes::BIND_CAPI_NULL_HANDLE,
18547 "PioDcOperators must not be NULL",
18548 )
18549 })?;
18550 calculation(&inner.operators, angles)
18551 .map(|values| PioVector::new_raw(VectorInner { values }))
18552 .map_err(|failure| error_from_core(&failure))
18553 })
18554 }
18555}
18556
18557#[unsafe(no_mangle)]
18562pub unsafe extern "C" fn pio_dc_operators_incidence_matrix(
18563 operators: *const PioDcOperators,
18564 error: *mut *mut PioError,
18565) -> *mut PioSparseMatrix {
18566 unsafe { dc_operators_matrix(operators, error, DcOperators::calc_incidence_matrix) }
18567}
18568
18569#[unsafe(no_mangle)]
18574pub unsafe extern "C" fn pio_dc_operators_bus_susceptance_matrix(
18575 operators: *const PioDcOperators,
18576 error: *mut *mut PioError,
18577) -> *mut PioSparseMatrix {
18578 unsafe { dc_operators_matrix(operators, error, DcOperators::calc_bus_susceptance_matrix) }
18579}
18580
18581#[unsafe(no_mangle)]
18586pub unsafe extern "C" fn pio_dc_operators_branch_flow_matrix(
18587 operators: *const PioDcOperators,
18588 error: *mut *mut PioError,
18589) -> *mut PioSparseMatrix {
18590 unsafe { dc_operators_matrix(operators, error, DcOperators::calc_branch_flow_matrix) }
18591}
18592
18593#[unsafe(no_mangle)]
18598pub unsafe extern "C" fn pio_dc_operators_branch_susceptances(
18599 operators: *const PioDcOperators,
18600 error: *mut *mut PioError,
18601) -> *mut PioVector {
18602 unsafe {
18603 dc_operators_vector(operators, error, |operators| {
18604 operators.calc_branch_susceptances().to_vec()
18605 })
18606 }
18607}
18608
18609#[unsafe(no_mangle)]
18614pub unsafe extern "C" fn pio_dc_operators_branch_phase_shift_injection(
18615 operators: *const PioDcOperators,
18616 error: *mut *mut PioError,
18617) -> *mut PioVector {
18618 unsafe {
18619 dc_operators_vector(
18620 operators,
18621 error,
18622 DcOperators::calc_branch_phase_shift_injection,
18623 )
18624 }
18625}
18626
18627#[unsafe(no_mangle)]
18632pub unsafe extern "C" fn pio_dc_operators_bus_phase_shift_injection(
18633 operators: *const PioDcOperators,
18634 error: *mut *mut PioError,
18635) -> *mut PioVector {
18636 unsafe {
18637 dc_operators_vector(
18638 operators,
18639 error,
18640 DcOperators::calc_bus_phase_shift_injection,
18641 )
18642 }
18643}
18644
18645#[unsafe(no_mangle)]
18650pub unsafe extern "C" fn pio_dc_operators_branch_flow_dc(
18651 operators: *const PioDcOperators,
18652 voltage_angles: *const f64,
18653 voltage_angles_len: usize,
18654 error: *mut *mut PioError,
18655) -> *mut PioVector {
18656 unsafe {
18657 dc_operators_vector_from_angles(
18658 operators,
18659 voltage_angles,
18660 voltage_angles_len,
18661 error,
18662 DcOperators::calc_branch_flow_dc,
18663 )
18664 }
18665}
18666
18667#[unsafe(no_mangle)]
18672pub unsafe extern "C" fn pio_dc_operators_bus_injection_dc(
18673 operators: *const PioDcOperators,
18674 voltage_angles: *const f64,
18675 voltage_angles_len: usize,
18676 error: *mut *mut PioError,
18677) -> *mut PioVector {
18678 unsafe {
18679 dc_operators_vector_from_angles(
18680 operators,
18681 voltage_angles,
18682 voltage_angles_len,
18683 error,
18684 DcOperators::calc_bus_injection_dc,
18685 )
18686 }
18687}
18688
18689#[unsafe(no_mangle)]
18694pub unsafe extern "C" fn pio_dc_operators_retain(
18695 operators: *const PioDcOperators,
18696) -> *mut PioDcOperators {
18697 unsafe { PioDcOperators::retain_raw(operators) }
18698}
18699
18700#[unsafe(no_mangle)]
18705pub unsafe extern "C" fn pio_dc_operators_release(operators: *mut PioDcOperators) {
18706 unsafe { PioDcOperators::release_raw(operators) };
18707}
18708
18709unsafe fn dc_matrix(
18710 network: *const PioBalancedNetwork,
18711 formula: *const c_char,
18712 formula_len: usize,
18713 error: *mut *mut PioError,
18714 calculation: impl FnOnce(&DcOperators) -> SparseMatrix,
18715) -> *mut PioSparseMatrix {
18716 unsafe {
18717 entry(error, std::ptr::null_mut(), || {
18718 let operators = dc_operators(network, formula, formula_len)?;
18719 Ok(PioSparseMatrix::new_raw(SparseMatrixInner::from(
18720 calculation(&operators),
18721 )))
18722 })
18723 }
18724}
18725
18726unsafe fn dc_vector(
18727 network: *const PioBalancedNetwork,
18728 formula: *const c_char,
18729 formula_len: usize,
18730 error: *mut *mut PioError,
18731 calculation: impl FnOnce(&DcOperators) -> Vec<f64>,
18732) -> *mut PioVector {
18733 unsafe {
18734 entry(error, std::ptr::null_mut(), || {
18735 let operators = dc_operators(network, formula, formula_len)?;
18736 Ok(PioVector::new_raw(VectorInner {
18737 values: calculation(&operators),
18738 }))
18739 })
18740 }
18741}
18742
18743#[unsafe(no_mangle)]
18747pub unsafe extern "C" fn pio_calc_incidence_matrix(
18748 network: *const PioBalancedNetwork,
18749 formula: *const c_char,
18750 formula_len: usize,
18751 error: *mut *mut PioError,
18752) -> *mut PioSparseMatrix {
18753 unsafe {
18754 dc_matrix(network, formula, formula_len, error, |operators| {
18755 operators.calc_incidence_matrix()
18756 })
18757 }
18758}
18759
18760#[unsafe(no_mangle)]
18764pub unsafe extern "C" fn pio_calc_bus_susceptance_matrix(
18765 network: *const PioBalancedNetwork,
18766 formula: *const c_char,
18767 formula_len: usize,
18768 error: *mut *mut PioError,
18769) -> *mut PioSparseMatrix {
18770 unsafe {
18771 dc_matrix(network, formula, formula_len, error, |operators| {
18772 operators.calc_bus_susceptance_matrix()
18773 })
18774 }
18775}
18776
18777#[unsafe(no_mangle)]
18781pub unsafe extern "C" fn pio_calc_branch_flow_matrix(
18782 network: *const PioBalancedNetwork,
18783 formula: *const c_char,
18784 formula_len: usize,
18785 error: *mut *mut PioError,
18786) -> *mut PioSparseMatrix {
18787 unsafe {
18788 dc_matrix(network, formula, formula_len, error, |operators| {
18789 operators.calc_branch_flow_matrix()
18790 })
18791 }
18792}
18793
18794#[unsafe(no_mangle)]
18798pub unsafe extern "C" fn pio_calc_branch_susceptances(
18799 network: *const PioBalancedNetwork,
18800 formula: *const c_char,
18801 formula_len: usize,
18802 error: *mut *mut PioError,
18803) -> *mut PioVector {
18804 unsafe {
18805 dc_vector(network, formula, formula_len, error, |operators| {
18806 operators.calc_branch_susceptances().to_vec()
18807 })
18808 }
18809}
18810
18811#[unsafe(no_mangle)]
18815pub unsafe extern "C" fn pio_calc_branch_phase_shift_injection(
18816 network: *const PioBalancedNetwork,
18817 formula: *const c_char,
18818 formula_len: usize,
18819 error: *mut *mut PioError,
18820) -> *mut PioVector {
18821 unsafe {
18822 dc_vector(network, formula, formula_len, error, |operators| {
18823 operators.calc_branch_phase_shift_injection()
18824 })
18825 }
18826}
18827
18828#[unsafe(no_mangle)]
18832pub unsafe extern "C" fn pio_calc_bus_phase_shift_injection(
18833 network: *const PioBalancedNetwork,
18834 formula: *const c_char,
18835 formula_len: usize,
18836 error: *mut *mut PioError,
18837) -> *mut PioVector {
18838 unsafe {
18839 dc_vector(network, formula, formula_len, error, |operators| {
18840 operators.calc_bus_phase_shift_injection()
18841 })
18842 }
18843}
18844
18845unsafe fn dc_vector_from_angles(
18846 network: *const PioBalancedNetwork,
18847 formula_name: *const c_char,
18848 formula_name_len: usize,
18849 voltage_angles: *const f64,
18850 voltage_angles_len: usize,
18851 error: *mut *mut PioError,
18852 calculation: impl FnOnce(&DcOperators, &[f64]) -> Result<Vec<f64>, powerio_core::Error>,
18853) -> *mut PioVector {
18854 unsafe {
18855 entry(error, std::ptr::null_mut(), || {
18856 if voltage_angles.is_null() && voltage_angles_len != 0 {
18857 return Err(boundary_error(
18858 &codes::BIND_CAPI_NULL_ARGUMENT,
18859 "voltage_angles is NULL with a nonzero length",
18860 ));
18861 }
18862 let angles = if voltage_angles_len == 0 {
18863 &[]
18864 } else {
18865 std::slice::from_raw_parts(voltage_angles, voltage_angles_len)
18866 };
18867 let operators = dc_operators(network, formula_name, formula_name_len)?;
18868 calculation(&operators, angles)
18869 .map(|values| PioVector::new_raw(VectorInner { values }))
18870 .map_err(|failure| error_from_core(&failure))
18871 })
18872 }
18873}
18874
18875#[unsafe(no_mangle)]
18879pub unsafe extern "C" fn pio_calc_branch_flow_dc(
18880 network: *const PioBalancedNetwork,
18881 formula: *const c_char,
18882 formula_len: usize,
18883 voltage_angles: *const f64,
18884 voltage_angles_len: usize,
18885 error: *mut *mut PioError,
18886) -> *mut PioVector {
18887 unsafe {
18888 dc_vector_from_angles(
18889 network,
18890 formula,
18891 formula_len,
18892 voltage_angles,
18893 voltage_angles_len,
18894 error,
18895 DcOperators::calc_branch_flow_dc,
18896 )
18897 }
18898}
18899
18900#[unsafe(no_mangle)]
18904pub unsafe extern "C" fn pio_calc_bus_injection_dc(
18905 network: *const PioBalancedNetwork,
18906 formula: *const c_char,
18907 formula_len: usize,
18908 voltage_angles: *const f64,
18909 voltage_angles_len: usize,
18910 error: *mut *mut PioError,
18911) -> *mut PioVector {
18912 unsafe {
18913 dc_vector_from_angles(
18914 network,
18915 formula,
18916 formula_len,
18917 voltage_angles,
18918 voltage_angles_len,
18919 error,
18920 DcOperators::calc_bus_injection_dc,
18921 )
18922 }
18923}
18924
18925#[unsafe(no_mangle)]
18929pub unsafe extern "C" fn pio_sparse_matrix_rows(matrix: *const PioSparseMatrix) -> usize {
18930 unsafe { PioSparseMatrix::get(matrix) }.map_or(0, |matrix| matrix.rows)
18931}
18932
18933#[unsafe(no_mangle)]
18937pub unsafe extern "C" fn pio_sparse_matrix_columns(matrix: *const PioSparseMatrix) -> usize {
18938 unsafe { PioSparseMatrix::get(matrix) }.map_or(0, |matrix| matrix.columns)
18939}
18940
18941#[unsafe(no_mangle)]
18945pub unsafe extern "C" fn pio_sparse_matrix_row_offsets(
18946 matrix: *const PioSparseMatrix,
18947) -> PioSizeView {
18948 unsafe { PioSparseMatrix::get(matrix) }.map_or(PioSizeView::EMPTY, |matrix| {
18949 PioSizeView::new(&matrix.row_offsets)
18950 })
18951}
18952
18953#[unsafe(no_mangle)]
18957pub unsafe extern "C" fn pio_sparse_matrix_column_indices(
18958 matrix: *const PioSparseMatrix,
18959) -> PioSizeView {
18960 unsafe { PioSparseMatrix::get(matrix) }.map_or(PioSizeView::EMPTY, |matrix| {
18961 PioSizeView::new(&matrix.column_indices)
18962 })
18963}
18964
18965#[unsafe(no_mangle)]
18969pub unsafe extern "C" fn pio_sparse_matrix_values(matrix: *const PioSparseMatrix) -> PioF64View {
18970 unsafe { PioSparseMatrix::get(matrix) }
18971 .map_or(PioF64View::EMPTY, |matrix| PioF64View::new(&matrix.values))
18972}
18973
18974#[unsafe(no_mangle)]
18979pub unsafe extern "C" fn pio_sparse_matrix_retain(
18980 matrix: *const PioSparseMatrix,
18981) -> *mut PioSparseMatrix {
18982 unsafe { PioSparseMatrix::retain_raw(matrix) }
18983}
18984
18985#[unsafe(no_mangle)]
18990pub unsafe extern "C" fn pio_sparse_matrix_release(matrix: *mut PioSparseMatrix) {
18991 unsafe { PioSparseMatrix::release_raw(matrix) };
18992}
18993
18994#[unsafe(no_mangle)]
18998pub unsafe extern "C" fn pio_vector_values(vector: *const PioVector) -> PioF64View {
18999 unsafe { PioVector::get(vector) }
19000 .map_or(PioF64View::EMPTY, |vector| PioF64View::new(&vector.values))
19001}
19002
19003#[unsafe(no_mangle)]
19008pub unsafe extern "C" fn pio_vector_retain(vector: *const PioVector) -> *mut PioVector {
19009 unsafe { PioVector::retain_raw(vector) }
19010}
19011
19012#[unsafe(no_mangle)]
19017pub unsafe extern "C" fn pio_vector_release(vector: *mut PioVector) {
19018 unsafe { PioVector::release_raw(vector) };
19019}
19020
19021struct StringInner {
19024 text: String,
19025}
19026
19027opaque_handle!(
19028 PioString,
19030 StringInner
19031);
19032
19033#[unsafe(no_mangle)]
19038pub unsafe extern "C" fn pio_schema_report(error: *mut *mut PioError) -> *mut PioString {
19039 unsafe {
19040 entry(error, std::ptr::null_mut(), || {
19041 serde_json::to_string(&serde_json::json!({
19042 "powerio_version": powerio::VERSION,
19043 "abi": PIO_ABI_VERSION,
19044 "powerio_ir": {
19045 "schema": powerio::IR_SCHEMA_NAME,
19046 "version": powerio::IR_VERSION
19047 },
19048 "bmopf_schema": powerio_dist::BMOPF_SCHEMA_VERSION,
19049 "features": {
19053 "matrix": true,
19054 "gridfm": cfg!(feature = "gridfm"),
19055 "dist": true,
19056 "prob": true
19057 },
19058 "foreign_schemas": {
19059 "bmopf": powerio_dist::BMOPF_SCHEMA_VERSION
19060 },
19061 "error_categories": powerio_core::ErrorCategory::TOKENS,
19062 "diagnostic_namespaces": powerio_core::DiagnosticStage::NAMESPACES,
19063 "json_classes": powerio::JSON_CLASSES
19064 }))
19065 .map(|text| PioString::new_raw(StringInner { text }))
19066 .map_err(|failure| {
19067 boundary_error(
19068 &codes::EMIT_CAPI_SERIALIZE_FAILED,
19069 format!("cannot serialize the schema report: {failure}"),
19070 )
19071 })
19072 })
19073 }
19074}
19075
19076#[unsafe(no_mangle)]
19080pub unsafe extern "C" fn pio_string_view(string: *const PioString) -> PioStringView {
19081 unsafe { PioString::get(string) }.map_or(PioStringView::EMPTY, |string| {
19082 PioStringView::new(&string.text)
19083 })
19084}
19085
19086#[unsafe(no_mangle)]
19091pub unsafe extern "C" fn pio_string_retain(string: *const PioString) -> *mut PioString {
19092 unsafe { PioString::retain_raw(string) }
19093}
19094
19095#[unsafe(no_mangle)]
19100pub unsafe extern "C" fn pio_string_release(string: *mut PioString) {
19101 unsafe { PioString::release_raw(string) };
19102}
19103
19104#[cfg(test)]
19105mod tests {
19106 use super::*;
19107
19108 unsafe fn view_text(view: PioStringView) -> String {
19109 if view.data.is_null() {
19110 return String::new();
19111 }
19112 String::from_utf8_lossy(unsafe {
19113 std::slice::from_raw_parts(view.data.cast::<u8>(), view.len)
19114 })
19115 .into_owned()
19116 }
19117
19118 unsafe fn parse_case9() -> *mut PioModule {
19119 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/data/case9.m");
19120 let path = path.to_string_lossy();
19121 let mut error = std::ptr::null_mut();
19122 let source = unsafe { pio_source_open(path.as_ptr().cast(), path.len(), &mut error) };
19123 assert!(!source.is_null(), "{}", unsafe { error_text(error) });
19124 let module = unsafe { pio_parse(source, std::ptr::null(), 0, &mut error) };
19125 unsafe { pio_source_release(source) };
19126 assert!(!module.is_null(), "{}", unsafe { error_text(error) });
19127 module
19128 }
19129
19130 fn parse_xiidm_text(text: &str) -> *mut PioModule {
19131 unsafe {
19132 let name = b"case.xiidm";
19133 let format = b"xiidm";
19134 let mut error = std::ptr::null_mut();
19135 let source = pio_source_from_memory(
19136 name.as_ptr().cast(),
19137 name.len(),
19138 text.as_ptr(),
19139 text.len(),
19140 &mut error,
19141 );
19142 assert!(!source.is_null(), "{}", error_text(error));
19143 let module = pio_parse(source, format.as_ptr().cast(), format.len(), &mut error);
19144 pio_source_release(source);
19145 assert!(!module.is_null(), "{}", error_text(error));
19146 module
19147 }
19148 }
19149
19150 unsafe fn parse_bmopf() -> *mut PioModule {
19151 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
19152 .join("../tests/data/dist/bmopf/example_ieee13.json");
19153 let path = path.to_string_lossy();
19154 let mut error = std::ptr::null_mut();
19155 let source = unsafe { pio_source_open(path.as_ptr().cast(), path.len(), &mut error) };
19156 assert!(!source.is_null(), "{}", unsafe { error_text(error) });
19157 let module = unsafe { pio_parse(source, std::ptr::null(), 0, &mut error) };
19158 unsafe { pio_source_release(source) };
19159 assert!(!module.is_null(), "{}", unsafe { error_text(error) });
19160 module
19161 }
19162
19163 unsafe fn error_text(error: *const PioError) -> String {
19164 let view = unsafe { pio_error_message(error) };
19165 if view.data.is_null() {
19166 return "missing PioError".to_owned();
19167 }
19168 String::from_utf8_lossy(unsafe {
19169 std::slice::from_raw_parts(view.data.cast::<u8>(), view.len)
19170 })
19171 .into_owned()
19172 }
19173
19174 unsafe fn case9_network() -> BalancedNetwork {
19175 let module = unsafe { parse_case9() };
19176 let network = match &unsafe { PioModule::get(module) }.unwrap().module.value() {
19177 PioValue::BalancedNetwork(network) => network.clone(),
19178 value => panic!("expected balanced network, got {}", value.type_name()),
19179 };
19180 unsafe { pio_module_release(module) };
19181 network
19182 }
19183
19184 fn module_with_complete_records() -> *mut PioModule {
19185 use std::collections::BTreeMap;
19186
19187 let mut module = powerio::PioModule::new(PioValue::BalancedNetwork(BalancedNetwork::new(
19188 "metadata", 100.0,
19189 )))
19190 .with_producer(Producer::new("record-test", "1.2.3").unwrap());
19191 let source_id = powerio_core::SourceId::new("source-1").unwrap();
19192 let source =
19193 powerio_core::SourceDescriptor::new(source_id.clone(), "record-test.xiidm", 128)
19194 .unwrap()
19195 .with_format(powerio_core::FormatId::new("xiidm").unwrap())
19196 .with_digest(
19197 powerio_core::Digest::sha256(
19198 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
19199 )
19200 .unwrap(),
19201 );
19202 module.add_source_descriptor(source).unwrap();
19203 module
19204 .add_source_map_entry(
19205 powerio_core::SourceMapEntry::new(
19206 "/value/buses/0",
19207 powerio_core::SourceRelation::Exact,
19208 vec![powerio_core::SourceSpan::new(source_id, 4, 12).unwrap()],
19209 )
19210 .unwrap(),
19211 )
19212 .unwrap();
19213 let parameters = BTreeMap::from([(
19214 "settings".to_owned(),
19215 serde_json::json!({
19216 "items": [null, true, "named", 18446744073709551615u64, 1.25]
19217 }),
19218 )]);
19219 let history = HistoryEntry::new(
19220 HistoryId::new("history-1").unwrap(),
19221 HistoryKind::Transform,
19222 "normalize",
19223 )
19224 .unwrap()
19225 .with_input_type("powerio.BalancedNetwork")
19226 .unwrap()
19227 .with_output_type("powerio.BalancedNetwork")
19228 .unwrap()
19229 .with_parameters(parameters)
19230 .unwrap()
19231 .with_assumption("base power is known")
19232 .unwrap()
19233 .with_loss("source token spelling")
19234 .unwrap();
19235 module.add_history_entry(history).unwrap();
19236 module
19237 .insert_extension("org.example.record", serde_json::json!({"enabled": true}))
19238 .unwrap();
19239 module_handle(module)
19240 }
19241
19242 fn multiconductor_network() -> powerio_dist::MulticonductorNetwork {
19243 let mut network = powerio_dist::MulticonductorNetwork::named("capi-solution-instance");
19244 network.buses_mut().push(powerio_dist::DistBus::new(
19245 "source",
19246 vec!["1".into(), "2".into(), "3".into()],
19247 ));
19248 network.sources_mut().push(powerio_dist::VoltageSource::new(
19249 "source",
19250 "source",
19251 vec!["1".into(), "2".into(), "3".into()],
19252 vec![240.0, 240.0, 240.0],
19253 vec![0.0, -2.094, 2.094],
19254 ));
19255 network
19256 }
19257
19258 fn complete_multiconductor_network() -> powerio_dist::MulticonductorNetwork {
19259 let mut network = powerio_dist::MulticonductorNetwork::named("");
19260 *network.source_format_mut() = Some(powerio_dist::DistSourceFormat::Dss);
19261 *network.geo_mut() = Some(powerio_dist::DistGeoMeta {
19262 space: powerio_dist::CoordinateSpace::Diagram {
19263 canvas: Some(powerio_dist::DistCanvas {
19264 width: Some(640.0),
19265 height: None,
19266 units: Some(String::new()),
19267 }),
19268 },
19269 kind: Some(powerio_dist::DistCoordsKind::Manual),
19270 });
19271
19272 let mut bus = powerio_dist::DistBus::new("bus", vec!["1".into(), "2".into()]);
19273 bus.grounded.push("2".into());
19274 bus.v_min = Some(0.0);
19275 bus.vpn_min = Some(Vec::new());
19276 bus.vpp_max = Some(vec![240.0, 240.0]);
19277 bus.location = Some(powerio_dist::DistLocation {
19278 x: 10.0,
19279 y: 20.0,
19280 kind: None,
19281 });
19282 network.buses_mut().push(bus);
19283
19284 let mut line_code = powerio_dist::DistLineCode::new(
19285 "code",
19286 vec![vec![1.0, 2.0], vec![3.0]],
19287 vec![vec![4.0]],
19288 );
19289 line_code.g_from = vec![vec![5.0]];
19290 line_code.b_from = vec![vec![6.0]];
19291 line_code.g_to = vec![vec![7.0]];
19292 line_code.b_to = vec![vec![8.0]];
19293 line_code.i_max = Some(Vec::new());
19294 line_code.s_max = Some(vec![900.0]);
19295 line_code.source = Some(String::new());
19296 network.line_codes_mut().push(line_code);
19297
19298 let mut line = powerio_dist::DistLine::new(
19299 "line",
19300 "bus",
19301 "bus",
19302 vec!["1".into()],
19303 vec!["2".into()],
19304 "code",
19305 12.0,
19306 );
19307 line.route = Some(Vec::new());
19308 line.i_max = Some(vec![10.0]);
19309 network.lines_mut().push(line);
19310
19311 let mut switch = powerio_dist::DistSwitch::new(
19312 "switch",
19313 "bus",
19314 "bus",
19315 vec!["1".into()],
19316 vec!["2".into()],
19317 true,
19318 );
19319 switch.i_max = Some(Vec::new());
19320 network.switches_mut().push(switch);
19321
19322 let mut winding = powerio_dist::DistWinding::new(
19323 "bus",
19324 vec!["1".into(), "2".into()],
19325 powerio_dist::DistWindingConn::Wye,
19326 240.0,
19327 1_000.0,
19328 );
19329 winding.r_pct = 0.5;
19330 winding.r_neutral = Some(0.0);
19331 network
19332 .transformers_mut()
19333 .push(powerio_dist::DistTransformer::new(
19334 "transformer",
19335 vec![winding],
19336 vec![2.5],
19337 1,
19338 ));
19339
19340 let mut load = powerio_dist::DistLoad::new(
19341 "load",
19342 "bus",
19343 vec!["1".into()],
19344 powerio_dist::Configuration::Wye,
19345 vec![100.0],
19346 vec![20.0],
19347 );
19348 load.voltage_model = powerio_dist::DistLoadVoltageModel::Zip {
19349 v_nom: vec![240.0],
19350 alpha_z: vec![0.1],
19351 alpha_i: vec![0.2],
19352 alpha_p: vec![0.7],
19353 beta_z: vec![0.2],
19354 beta_i: vec![0.3],
19355 beta_p: vec![0.5],
19356 };
19357 network.loads_mut().push(load);
19358
19359 let mut generator = powerio_dist::DistGenerator::new(
19360 "generator",
19361 "bus",
19362 vec!["1".into()],
19363 powerio_dist::Configuration::SinglePhase,
19364 vec![50.0],
19365 vec![5.0],
19366 );
19367 generator.p_min = Some(Vec::new());
19368 generator.cost = Some(vec![0.25]);
19369 network.generators_mut().push(generator);
19370
19371 let mut resource = powerio_dist::DistIbr::new(
19372 "ibr",
19373 "bus",
19374 vec!["1".into()],
19375 powerio_dist::IbrTopology::SinglePhase,
19376 powerio_dist::IbrPrimeMover::Pv,
19377 vec![100.0],
19378 );
19379 resource.i_max = Some(Vec::new());
19380 resource.p_avail = Some(75.0);
19381 resource.control_profile = Some(String::new());
19382 resource.voltage_aggregation = Some(powerio_dist::IbrVoltageAggregation::PerPhase);
19383 network.ibrs_mut().push(resource);
19384
19385 let profile: powerio_dist::DistControlProfile = serde_json::from_value(serde_json::json!({
19386 "name": "profile",
19387 "power_factor": { "pf": 0.97 },
19388 "volt_var": {
19389 "voltage_reference": "PN_PER_PHASE",
19390 "breakpoints": [0.95, 1.05],
19391 "q_limits": [-0.4, 0.4],
19392 "q_unit": "VA_FRACTION",
19393 "q_ref": "VAR_MAX",
19394 "p_min_for_q": 10.0,
19395 "p_min_for_q_max": null
19396 },
19397 "volt_watt": {
19398 "voltage_reference": null,
19399 "breakpoints": [1.0],
19400 "p_limits": [0.8],
19401 "p_unit": "W",
19402 "p_ref": "P_AVAILABLE"
19403 },
19404 "extras": {}
19405 }))
19406 .unwrap();
19407 network.control_profiles_mut().push(profile);
19408
19409 network.shunts_mut().push(powerio_dist::DistShunt::new(
19410 "shunt",
19411 "bus",
19412 vec!["2".into()],
19413 vec![vec![0.01, 0.02], vec![0.03]],
19414 vec![vec![0.04]],
19415 ));
19416 network
19417 .capacitors_mut()
19418 .push(powerio_dist::DistCapacitor::new(
19419 "capacitor",
19420 "bus",
19421 vec!["1".into()],
19422 powerio_dist::Configuration::SinglePhase,
19423 100.0,
19424 240.0,
19425 ));
19426 network.sources_mut().push(powerio_dist::VoltageSource::new(
19427 "source",
19428 "bus",
19429 vec!["1".into()],
19430 vec![240.0],
19431 vec![0.0],
19432 ));
19433 network.sources_mut()[0].energy_cost_rate = Some(vec![0.125]);
19434 network
19435 .untyped_objects_mut()
19436 .push(powerio_dist::UntypedObject::new(
19437 "curve",
19438 "raw",
19439 vec![
19440 (None, "first".into()),
19441 (Some(String::new()), "second".into()),
19442 ],
19443 ));
19444 network
19445 .commands_mut()
19446 .push(("solve".into(), "mode=daily".into()));
19447 network
19448 .options_mut()
19449 .push(("frequency".into(), "60".into()));
19450 network
19451 }
19452
19453 fn goc3_instance(source: Source) -> powerio_prob::AcScucInstance {
19454 let module = powerio::parse_with_options(
19455 source,
19456 &powerio::ParseOptions::default()
19457 .format("goc3-json")
19458 .unwrap(),
19459 )
19460 .unwrap();
19461 let PioValue::AcScucInstance(instance) = module.into_value() else {
19462 panic!("GO Challenge 3 problem did not produce powerio.AcScucInstance");
19463 };
19464 instance
19465 }
19466
19467 #[test]
19468 fn reports_abi_seven() {
19469 assert_eq!(pio_abi_version(), 7);
19470 assert_eq!(PIO_ABI_VERSION, 7);
19471 unsafe {
19472 let mut error = std::ptr::null_mut();
19473 let report = pio_schema_report(&mut error);
19474 assert!(!report.is_null(), "{}", error_text(error));
19475 let parsed: serde_json::Value =
19476 serde_json::from_str(&view_text(pio_string_view(report))).unwrap();
19477 assert_eq!(parsed["abi"], 7);
19478 assert_eq!(parsed["powerio_ir"]["schema"], powerio::IR_SCHEMA_NAME);
19479 assert_eq!(parsed["powerio_ir"]["version"], powerio::IR_VERSION);
19480 assert_eq!(
19481 parsed["bmopf_schema"],
19482 serde_json::json!(powerio_dist::BMOPF_SCHEMA_VERSION)
19483 );
19484 assert_eq!(parsed["foreign_schemas"]["bmopf"], parsed["bmopf_schema"]);
19485 assert_eq!(
19486 parsed["error_categories"],
19487 serde_json::json!(powerio_core::ErrorCategory::TOKENS)
19488 );
19489 assert_eq!(
19490 parsed["diagnostic_namespaces"],
19491 serde_json::json!(powerio_core::DiagnosticStage::NAMESPACES)
19492 );
19493 assert_eq!(
19494 parsed["json_classes"],
19495 serde_json::json!(powerio::JSON_CLASSES)
19496 );
19497 for feature in ["matrix", "gridfm", "dist", "prob"] {
19498 assert!(parsed["features"][feature].is_boolean());
19499 }
19500 pio_string_release(report);
19501 }
19502 }
19503
19504 #[test]
19505 fn structured_diagnostic_fields_keep_their_owner_alive() {
19506 unsafe {
19507 let source = powerio_core::SourceId::new("input").unwrap();
19508 let span = powerio_core::SourceSpan::new(source, 2, 7).unwrap();
19509 let mut details = serde_json::Map::new();
19510 details.insert("count".to_owned(), serde_json::json!(3));
19511 details.insert("nested".to_owned(), serde_json::json!({ "ok": true }));
19512 let diagnostic = Diagnostic::new(
19513 powerio_core::DiagnosticCode::new("PARTNER.TEST.STRUCTURED").unwrap(),
19514 powerio_core::DiagnosticSeverity::Warning,
19515 "structured finding",
19516 )
19517 .with_id(powerio_core::DiagnosticId::new("d1").unwrap())
19518 .with_target("/value/data")
19519 .unwrap()
19520 .with_span(span)
19521 .unwrap()
19522 .with_related(powerio_core::DiagnosticId::new("d0").unwrap())
19523 .unwrap()
19524 .with_details(details)
19525 .unwrap()
19526 .with_suggested_action("fix it");
19527 let diagnostics = PioDiagnostics::new_raw(DiagnosticsInner {
19528 owner: DiagnosticsOwner::Owned(vec![diagnostic]),
19529 });
19530 let retained = pio_diagnostics_retain(diagnostics);
19531 pio_diagnostics_release(diagnostics);
19532
19533 assert_eq!(pio_diagnostics_len(retained), 1);
19534 assert_eq!(
19535 view_text(pio_diagnostic_code(retained, 0)),
19536 "PARTNER.TEST.STRUCTURED"
19537 );
19538 assert_eq!(view_text(pio_diagnostic_severity(retained, 0)), "warning");
19539 assert_eq!(
19540 view_text(pio_diagnostic_message(retained, 0)),
19541 "structured finding"
19542 );
19543 assert!(pio_diagnostic_has_id(retained, 0));
19544 assert_eq!(view_text(pio_diagnostic_id(retained, 0)), "d1");
19545 assert!(pio_diagnostic_has_target(retained, 0));
19546 assert_eq!(view_text(pio_diagnostic_target(retained, 0)), "/value/data");
19547 assert!(pio_diagnostic_has_suggested_action(retained, 0));
19548 assert_eq!(
19549 view_text(pio_diagnostic_suggested_action(retained, 0)),
19550 "fix it"
19551 );
19552
19553 assert_eq!(pio_diagnostic_n_spans(retained, 0), 1);
19554 let mut output = PioDiagnosticSpanView {
19555 source: PioStringView::EMPTY,
19556 byte_start: 0,
19557 byte_end: 0,
19558 };
19559 let mut error = std::ptr::null_mut();
19560 assert!(pio_diagnostic_span(retained, 0, 0, &mut output, &mut error));
19561 assert!(error.is_null());
19562 assert_eq!(view_text(output.source), "input");
19563 assert_eq!(output.byte_start, 2);
19564 assert_eq!(output.byte_end, 7);
19565
19566 assert_eq!(pio_diagnostic_n_related(retained, 0), 1);
19567 assert_eq!(view_text(pio_diagnostic_related(retained, 0, 0)), "d0");
19568
19569 let details_text = pio_diagnostic_details_json(retained, 0, &mut error);
19570 assert!(!details_text.is_null(), "{}", error_text(error));
19571 let details: serde_json::Value =
19572 serde_json::from_str(&view_text(pio_string_view(details_text))).unwrap();
19573 assert_eq!(details["count"], 3);
19574 assert_eq!(details["nested"]["ok"], true);
19575 pio_string_release(details_text);
19576
19577 pio_diagnostics_release(retained);
19578 }
19579 }
19580
19581 #[test]
19582 fn module_records_are_typed_and_structured_values_keep_the_owner_alive() {
19583 unsafe {
19584 let module = module_with_complete_records();
19585 let mut error = std::ptr::null_mut();
19586
19587 let mut producer = std::mem::MaybeUninit::<PioModuleProducerView>::uninit();
19588 assert!(pio_module_producer(
19589 module,
19590 producer.as_mut_ptr(),
19591 &mut error
19592 ));
19593 let producer = producer.assume_init();
19594 assert_eq!(view_text(producer.name), "record-test");
19595 assert_eq!(view_text(producer.version), "1.2.3");
19596
19597 assert_eq!(pio_module_source_count(module), 1);
19598 let mut source = std::mem::MaybeUninit::<PioModuleSourceView>::uninit();
19599 assert!(pio_module_source_at(
19600 module,
19601 0,
19602 source.as_mut_ptr(),
19603 &mut error
19604 ));
19605 let source = source.assume_init();
19606 assert_eq!(view_text(source.id), "source-1");
19607 assert_eq!(view_text(source.name), "record-test.xiidm");
19608 assert_eq!(source.byte_length, 128);
19609 assert!(source.has_format);
19610 assert_eq!(view_text(source.format), "xiidm");
19611 assert!(source.has_digest);
19612 assert_eq!(view_text(source.digest_algorithm), "sha256");
19613 assert_eq!(source.digest.len, 64);
19614
19615 assert_eq!(pio_module_source_map_count(module), 1);
19616 let mut mapping = std::mem::MaybeUninit::<PioModuleSourceMapEntryView>::uninit();
19617 assert!(pio_module_source_map_at(
19618 module,
19619 0,
19620 mapping.as_mut_ptr(),
19621 &mut error
19622 ));
19623 let mapping = mapping.assume_init();
19624 assert_eq!(view_text(mapping.target), "/value/buses/0");
19625 assert_eq!(view_text(mapping.relation), "exact");
19626 assert_eq!(mapping.span_count, 1);
19627 let mut span = std::mem::MaybeUninit::<PioSourceSpanView>::uninit();
19628 assert!(pio_module_source_map_span_at(
19629 module,
19630 0,
19631 0,
19632 span.as_mut_ptr(),
19633 &mut error
19634 ));
19635 let span = span.assume_init();
19636 assert_eq!(view_text(span.source), "source-1");
19637 assert_eq!((span.byte_start, span.byte_end), (4, 12));
19638
19639 assert_eq!(pio_module_history_count(module), 1);
19640 let mut history = std::mem::MaybeUninit::<PioModuleHistoryEntryView>::uninit();
19641 assert!(pio_module_history_at(
19642 module,
19643 0,
19644 history.as_mut_ptr(),
19645 &mut error
19646 ));
19647 let history = history.assume_init();
19648 assert_eq!(view_text(history.id), "history-1");
19649 assert_eq!(view_text(history.kind), "transform");
19650 assert_eq!(view_text(history.name), "normalize");
19651 assert!(history.has_input_type);
19652 assert_eq!(view_text(history.input_type), "powerio.BalancedNetwork");
19653 assert!(history.has_output_type);
19654 assert_eq!(view_text(history.output_type), "powerio.BalancedNetwork");
19655 assert_eq!(history.parameter_count, 1);
19656 assert_eq!(history.assumption_count, 1);
19657 assert_eq!(history.loss_count, 1);
19658 assert_eq!(
19659 view_text(pio_module_history_assumption_at(module, 0, 0, &mut error)),
19660 "base power is known"
19661 );
19662 assert_eq!(
19663 view_text(pio_module_history_loss_at(module, 0, 0, &mut error)),
19664 "source token spelling"
19665 );
19666 let mut parameter = std::mem::MaybeUninit::<PioModuleHistoryParameterView>::uninit();
19667 assert!(pio_module_history_parameter_at(
19668 module,
19669 0,
19670 0,
19671 parameter.as_mut_ptr(),
19672 &mut error
19673 ));
19674 let parameter = parameter.assume_init();
19675 assert_eq!(view_text(parameter.name), "settings");
19676 assert_eq!(view_text(parameter.value_kind), "object");
19677
19678 assert_eq!(pio_module_extension_count(module), 1);
19679 let mut extension = std::mem::MaybeUninit::<PioModuleExtensionView>::uninit();
19680 assert!(pio_module_extension_at(
19681 module,
19682 0,
19683 extension.as_mut_ptr(),
19684 &mut error
19685 ));
19686 let extension = extension.assume_init();
19687 assert_eq!(view_text(extension.namespace), "org.example.record");
19688 assert_eq!(view_text(extension.value_kind), "object");
19689
19690 let parameter_value = pio_module_history_parameter_value_at(module, 0, 0, &mut error);
19691 assert!(!parameter_value.is_null(), "{}", error_text(error));
19692 let items = pio_json_value_object_value_at(parameter_value, 0, &mut error);
19693 assert!(!items.is_null(), "{}", error_text(error));
19694 let unsigned = pio_json_value_array_at(items, 3, &mut error);
19695 assert!(!unsigned.is_null(), "{}", error_text(error));
19696 let retained_unsigned = pio_json_value_retain(unsigned);
19697 let extension_value = pio_module_extension_value_at(module, 0, &mut error);
19698 assert!(!extension_value.is_null(), "{}", error_text(error));
19699
19700 let mut ignored = std::mem::MaybeUninit::<PioModuleSourceView>::uninit();
19701 assert!(!pio_module_source_at(
19702 module,
19703 1,
19704 ignored.as_mut_ptr(),
19705 &mut error
19706 ));
19707 assert!(!error.is_null());
19708 pio_error_release(error);
19709 error = std::ptr::null_mut();
19710
19711 pio_module_release(module);
19712 pio_json_value_release(parameter_value);
19713 pio_json_value_release(items);
19714 pio_json_value_release(unsigned);
19715
19716 let mut number = std::mem::MaybeUninit::<PioJsonValueView>::uninit();
19717 assert!(pio_json_value_get(
19718 retained_unsigned,
19719 number.as_mut_ptr(),
19720 &mut error
19721 ));
19722 let number = number.assume_init();
19723 assert_eq!(view_text(number.kind), "number");
19724 assert_eq!(view_text(number.number_kind), "unsigned_integer");
19725 assert_eq!(number.unsigned_integer_value, u64::MAX);
19726
19727 let enabled = pio_json_value_object_value_at(extension_value, 0, &mut error);
19728 assert!(!enabled.is_null(), "{}", error_text(error));
19729 let mut boolean = std::mem::MaybeUninit::<PioJsonValueView>::uninit();
19730 assert!(pio_json_value_get(
19731 enabled,
19732 boolean.as_mut_ptr(),
19733 &mut error
19734 ));
19735 let boolean = boolean.assume_init();
19736 assert_eq!(view_text(boolean.kind), "boolean");
19737 assert!(boolean.boolean_value);
19738
19739 let mut null_output = std::mem::MaybeUninit::<PioModuleProducerView>::uninit();
19740 assert!(!pio_module_producer(
19741 std::ptr::null(),
19742 null_output.as_mut_ptr(),
19743 &mut error
19744 ));
19745 assert!(!error.is_null());
19746 pio_error_release(error);
19747 pio_json_value_release(enabled);
19748 pio_json_value_release(extension_value);
19749 pio_json_value_release(retained_unsigned);
19750 }
19751 }
19752
19753 #[test]
19754 fn structural_access_keeps_the_module_owner_alive() {
19755 unsafe {
19756 let module = parse_case9();
19757 let diagnostics = pio_module_diagnostics(module);
19758 let value = pio_module_value(module);
19759 assert!(pio_value_is_type(
19760 value,
19761 c"powerio.BalancedNetwork".as_ptr(),
19762 "powerio.BalancedNetwork".len(),
19763 ));
19764 let mut error = std::ptr::null_mut();
19765 let network = pio_value_balanced_network(value, &mut error);
19766 assert!(!network.is_null(), "{}", error_text(error));
19767
19768 pio_module_release(module);
19769 pio_value_release(value);
19770 assert_eq!(pio_balanced_network_bus_count(network), 9);
19771 assert_eq!(pio_balanced_network_branch_count(network), 9);
19772 assert_eq!(pio_diagnostics_len(diagnostics), 0);
19773
19774 pio_balanced_network_release(network);
19775 pio_diagnostics_release(diagnostics);
19776 }
19777 }
19778
19779 #[test]
19780 fn abi_seven_bus_views_preserve_phase_bounds_after_owner_release() {
19781 unsafe {
19782 let mut network = complete_multiconductor_network();
19783 network.buses_mut()[0].v_min = None;
19784 network.buses_mut()[0].v_min_phase = Some(vec![210.0, 215.0]);
19785 let module = module_handle(powerio::PioModule::new(PioValue::from(network)));
19786 let value = pio_module_value(module);
19787 let mut error = std::ptr::null_mut();
19788 let network = pio_value_multiconductor_network(value, &mut error);
19789 assert!(!network.is_null());
19790 let mut bus = std::mem::MaybeUninit::<PioMulticonductorBusView>::uninit();
19791 pio_module_release(module);
19792 pio_value_release(value);
19793 assert!(pio_multiconductor_network_bus_at(
19794 network,
19795 0,
19796 bus.as_mut_ptr(),
19797 &mut error
19798 ));
19799 let bus = bus.assume_init();
19800 assert!(bus.has_phase_to_ground_voltage_min);
19801 assert!(!bus.has_voltage_min);
19802 assert_eq!(
19803 std::slice::from_raw_parts(
19804 bus.phase_to_ground_voltage_min_v.data,
19805 bus.phase_to_ground_voltage_min_v.len
19806 ),
19807 &[210.0, 215.0]
19808 );
19809 pio_multiconductor_network_release(network);
19810 }
19811 }
19812
19813 #[test]
19814 #[allow(clippy::too_many_lines)]
19815 fn multiconductor_typed_views_preserve_ownership_and_optional_values() {
19816 unsafe {
19817 let module = module_handle(powerio::PioModule::new(PioValue::from(
19818 complete_multiconductor_network(),
19819 )));
19820 let value = pio_module_value(module);
19821 let mut error = std::ptr::null_mut();
19822 let network = pio_value_multiconductor_network(value, &mut error);
19823 assert!(!network.is_null(), "{}", error_text(error));
19824 let retained = pio_multiconductor_network_retain(network);
19825 pio_module_release(module);
19826 pio_value_release(value);
19827 pio_multiconductor_network_release(network);
19828
19829 assert!(pio_multiconductor_network_has_name(retained));
19830 assert_eq!(pio_multiconductor_network_name(retained).len, 0);
19831 assert!(pio_multiconductor_network_has_source_format(retained));
19832 assert_eq!(
19833 view_text(pio_multiconductor_network_source_format(retained)),
19834 "dss"
19835 );
19836
19837 let mut geo = std::mem::MaybeUninit::<PioMulticonductorGeoView>::uninit();
19838 assert!(pio_multiconductor_network_geo(
19839 retained,
19840 geo.as_mut_ptr(),
19841 &mut error,
19842 ));
19843 let geo = geo.assume_init();
19844 assert!(geo.has_geo);
19845 assert_eq!(view_text(geo.space), "diagram");
19846 assert!(geo.has_canvas);
19847 assert!(geo.has_canvas_width);
19848 assert!(!geo.has_canvas_height);
19849 assert!(geo.has_canvas_units);
19850 assert_eq!(geo.canvas_units.len, 0);
19851
19852 let mut counts = std::mem::MaybeUninit::<PioMulticonductorNetworkCountsView>::uninit();
19853 assert!(pio_multiconductor_network_counts(
19854 retained,
19855 counts.as_mut_ptr(),
19856 &mut error,
19857 ));
19858 let counts = counts.assume_init();
19859 for count in [
19860 counts.buses,
19861 counts.line_codes,
19862 counts.lines,
19863 counts.switches,
19864 counts.transformers,
19865 counts.loads,
19866 counts.generators,
19867 counts.inverter_based_resources,
19868 counts.control_profiles,
19869 counts.shunts,
19870 counts.capacitors,
19871 counts.voltage_sources,
19872 counts.untyped_objects,
19873 counts.commands,
19874 counts.options,
19875 ] {
19876 assert_eq!(count, 1);
19877 }
19878
19879 let mut bus = std::mem::MaybeUninit::<PioMulticonductorBusView>::uninit();
19880 assert!(pio_multiconductor_network_bus_at(
19881 retained,
19882 0,
19883 bus.as_mut_ptr(),
19884 &mut error,
19885 ));
19886 let bus = bus.assume_init();
19887 assert_eq!(view_text(bus.id), "bus");
19888 assert!(bus.has_phase_to_neutral_voltage_min);
19889 assert_eq!(bus.phase_to_neutral_voltage_min_v.len, 0);
19890 assert!(bus.has_location);
19891 assert!(!bus.location.has_kind);
19892 let mut text = std::mem::MaybeUninit::<PioStringView>::uninit();
19893 assert!(pio_multiconductor_network_bus_terminal_at(
19894 retained,
19895 0,
19896 1,
19897 text.as_mut_ptr(),
19898 &mut error,
19899 ));
19900 assert_eq!(view_text(text.assume_init()), "2");
19901
19902 let mut line_code = std::mem::MaybeUninit::<PioMulticonductorLineCodeView>::uninit();
19903 assert!(pio_multiconductor_network_line_code_at(
19904 retained,
19905 0,
19906 line_code.as_mut_ptr(),
19907 &mut error,
19908 ));
19909 let line_code = line_code.assume_init();
19910 assert_eq!(line_code.resistance_matrix_row_count, 2);
19911 assert!(line_code.has_current_limit);
19912 assert_eq!(line_code.current_limit_a.len, 0);
19913 assert!(line_code.has_source);
19914 assert_eq!(line_code.source.len, 0);
19915 let mut matrix_row = std::mem::MaybeUninit::<PioF64View>::uninit();
19916 assert!(
19917 pio_multiconductor_network_line_code_resistance_matrix_row_at(
19918 retained,
19919 0,
19920 1,
19921 matrix_row.as_mut_ptr(),
19922 &mut error,
19923 )
19924 );
19925 let matrix_row = matrix_row.assume_init();
19926 assert_eq!(matrix_row.len, 1);
19927 assert_eq!(*matrix_row.data, 3.0);
19928
19929 let mut line = std::mem::MaybeUninit::<PioMulticonductorLineView>::uninit();
19930 assert!(pio_multiconductor_network_line_at(
19931 retained,
19932 0,
19933 line.as_mut_ptr(),
19934 &mut error,
19935 ));
19936 let line = line.assume_init();
19937 assert!(line.has_route);
19938 assert_eq!(line.route_point_count, 0);
19939
19940 let mut switch = std::mem::MaybeUninit::<PioMulticonductorSwitchView>::uninit();
19941 assert!(pio_multiconductor_network_switch_at(
19942 retained,
19943 0,
19944 switch.as_mut_ptr(),
19945 &mut error,
19946 ));
19947 let switch = switch.assume_init();
19948 assert!(switch.open);
19949 assert!(switch.has_current_limit);
19950 assert_eq!(switch.current_limit_a.len, 0);
19951
19952 let mut transformer =
19953 std::mem::MaybeUninit::<PioMulticonductorTransformerView>::uninit();
19954 assert!(pio_multiconductor_network_transformer_at(
19955 retained,
19956 0,
19957 transformer.as_mut_ptr(),
19958 &mut error,
19959 ));
19960 assert_eq!(transformer.assume_init().winding_count, 1);
19961 let mut winding =
19962 std::mem::MaybeUninit::<PioMulticonductorTransformerWindingView>::uninit();
19963 assert!(pio_multiconductor_network_transformer_winding_at(
19964 retained,
19965 0,
19966 0,
19967 winding.as_mut_ptr(),
19968 &mut error,
19969 ));
19970 let winding = winding.assume_init();
19971 assert!(winding.has_neutral_resistance);
19972 assert!(!winding.has_neutral_reactance);
19973
19974 let mut load = std::mem::MaybeUninit::<PioMulticonductorLoadView>::uninit();
19975 assert!(pio_multiconductor_network_load_at(
19976 retained,
19977 0,
19978 load.as_mut_ptr(),
19979 &mut error,
19980 ));
19981 let load = load.assume_init();
19982 assert_eq!(view_text(load.voltage_model), "zip");
19983 assert_eq!(*load.active_power_constant_power.data, 0.7);
19984
19985 let mut generator = std::mem::MaybeUninit::<PioMulticonductorGeneratorView>::uninit();
19986 assert!(pio_multiconductor_network_generator_at(
19987 retained,
19988 0,
19989 generator.as_mut_ptr(),
19990 &mut error,
19991 ));
19992 let generator = generator.assume_init();
19993 assert!(generator.has_active_power_min);
19994 assert_eq!(generator.active_power_min_w.len, 0);
19995 assert!(generator.has_active_power_dispatch_cost);
19996
19997 let mut resource = std::mem::MaybeUninit::<PioInverterBasedResourceView>::uninit();
19998 assert!(pio_multiconductor_network_inverter_based_resource_at(
19999 retained,
20000 0,
20001 resource.as_mut_ptr(),
20002 &mut error,
20003 ));
20004 let resource = resource.assume_init();
20005 assert_eq!(view_text(resource.topology), "SINGLE_PHASE");
20006 assert!(resource.has_control_profile);
20007 assert_eq!(resource.control_profile.len, 0);
20008
20009 let mut profile = std::mem::MaybeUninit::<PioControlProfileView>::uninit();
20010 assert!(pio_multiconductor_network_control_profile_at(
20011 retained,
20012 0,
20013 profile.as_mut_ptr(),
20014 &mut error,
20015 ));
20016 let profile = profile.assume_init();
20017 assert!(profile.has_power_factor);
20018 assert!(profile.has_volt_var);
20019 assert!(profile.has_volt_watt);
20020 assert_eq!(
20021 view_text(profile.volt_var_voltage_reference),
20022 "PN_PER_PHASE"
20023 );
20024
20025 let mut shunt = std::mem::MaybeUninit::<PioMulticonductorShuntView>::uninit();
20026 assert!(pio_multiconductor_network_shunt_at(
20027 retained,
20028 0,
20029 shunt.as_mut_ptr(),
20030 &mut error,
20031 ));
20032 assert_eq!(shunt.assume_init().conductance_matrix_row_count, 2);
20033 let mut shunt_matrix_row = std::mem::MaybeUninit::<PioF64View>::uninit();
20034 assert!(pio_multiconductor_network_shunt_conductance_matrix_row_at(
20035 retained,
20036 0,
20037 1,
20038 shunt_matrix_row.as_mut_ptr(),
20039 &mut error,
20040 ));
20041 assert_eq!(shunt_matrix_row.assume_init().len, 1);
20042
20043 let mut capacitor = std::mem::MaybeUninit::<PioMulticonductorCapacitorView>::uninit();
20044 assert!(pio_multiconductor_network_capacitor_at(
20045 retained,
20046 0,
20047 capacitor.as_mut_ptr(),
20048 &mut error,
20049 ));
20050 assert_eq!(capacitor.assume_init().rated_reactive_power_var, 100.0);
20051
20052 let mut source = std::mem::MaybeUninit::<PioVoltageSourceView>::uninit();
20053 assert!(pio_multiconductor_network_voltage_source_at(
20054 retained,
20055 0,
20056 source.as_mut_ptr(),
20057 &mut error,
20058 ));
20059 let source = source.assume_init();
20060 assert_eq!(*source.voltage_magnitude_v.data, 240.0);
20061 assert!(source.has_energy_cost_rate);
20062 assert_eq!(source.energy_cost_rate_per_kwh.len, 1);
20063 assert_eq!(*source.energy_cost_rate_per_kwh.data, 0.125);
20064
20065 let mut object = std::mem::MaybeUninit::<PioMulticonductorUntypedObjectView>::uninit();
20066 assert!(pio_multiconductor_network_untyped_object_at(
20067 retained,
20068 0,
20069 object.as_mut_ptr(),
20070 &mut error,
20071 ));
20072 assert_eq!(object.assume_init().property_count, 2);
20073 let mut property =
20074 std::mem::MaybeUninit::<PioMulticonductorUntypedPropertyView>::uninit();
20075 assert!(pio_multiconductor_network_untyped_object_property_at(
20076 retained,
20077 0,
20078 0,
20079 property.as_mut_ptr(),
20080 &mut error,
20081 ));
20082 assert!(!property.assume_init().has_name);
20083 assert!(pio_multiconductor_network_untyped_object_property_at(
20084 retained,
20085 0,
20086 1,
20087 property.as_mut_ptr(),
20088 &mut error,
20089 ));
20090 let property = property.assume_init();
20091 assert!(property.has_name);
20092 assert_eq!(property.name.len, 0);
20093
20094 let mut command = std::mem::MaybeUninit::<PioMulticonductorCommandView>::uninit();
20095 assert!(pio_multiconductor_network_command_at(
20096 retained,
20097 0,
20098 command.as_mut_ptr(),
20099 &mut error,
20100 ));
20101 assert_eq!(view_text(command.assume_init().verb), "solve");
20102 let mut option = std::mem::MaybeUninit::<PioStringPropertyView>::uninit();
20103 assert!(pio_multiconductor_network_option_at(
20104 retained,
20105 0,
20106 option.as_mut_ptr(),
20107 &mut error,
20108 ));
20109 assert_eq!(view_text(option.assume_init().name), "frequency");
20110
20111 assert!(
20112 std::mem::offset_of!(PioMulticonductorLineView, has_route)
20113 > std::mem::offset_of!(PioMulticonductorLineView, route_point_count)
20114 );
20115 assert!(
20116 std::mem::offset_of!(PioMulticonductorBusView, has_location)
20117 > std::mem::offset_of!(PioMulticonductorBusView, location)
20118 );
20119
20120 let mut null_error = std::ptr::null_mut();
20121 let mut null_output = std::mem::MaybeUninit::<PioMulticonductorBusView>::uninit();
20122 assert!(!pio_multiconductor_network_bus_at(
20123 std::ptr::null(),
20124 0,
20125 null_output.as_mut_ptr(),
20126 &mut null_error,
20127 ));
20128 assert!(!null_error.is_null());
20129 assert_eq!(
20130 view_text(pio_error_code(null_error)),
20131 codes::BIND_CAPI_NULL_HANDLE.code
20132 );
20133 pio_error_release(null_error);
20134 pio_multiconductor_network_release(retained);
20135 }
20136 }
20137
20138 #[test]
20139 fn balanced_geography_is_typed_and_preserves_optional_routes() {
20140 unsafe {
20141 let mut network = case9_network();
20142 *network.geo_mut() = Some(powerio_tx::GeoMeta {
20143 space: powerio_tx::CoordinateSpace::Diagram {
20144 canvas: Some(powerio_tx::Canvas {
20145 width: Some(800.0),
20146 height: None,
20147 units: Some(String::new()),
20148 }),
20149 },
20150 kind: Some(powerio_tx::CoordsKind::Manual),
20151 });
20152 network.buses_mut()[0].location = Some(powerio_tx::Location {
20153 x: 12.0,
20154 y: 34.0,
20155 kind: None,
20156 });
20157 network.branches_mut()[0].route = Some(Vec::new());
20158 network.branches_mut()[1].route = Some(vec![powerio_tx::Location {
20159 x: 56.0,
20160 y: 78.0,
20161 kind: Some(powerio_tx::CoordsKind::Derived),
20162 }]);
20163
20164 let module = module_handle(powerio::PioModule::new(PioValue::BalancedNetwork(network)));
20165 let value = pio_module_value(module);
20166 let mut error = std::ptr::null_mut();
20167 let balanced = pio_value_balanced_network(value, &mut error);
20168 assert!(!balanced.is_null(), "{}", error_text(error));
20169 let retained = pio_balanced_network_retain(balanced);
20170 pio_balanced_network_release(balanced);
20171 pio_value_release(value);
20172 pio_module_release(module);
20173
20174 let mut geo = std::mem::MaybeUninit::<PioBalancedGeoView>::uninit();
20175 assert!(pio_balanced_network_geo(
20176 retained,
20177 geo.as_mut_ptr(),
20178 &mut error
20179 ));
20180 let geo = geo.assume_init();
20181 assert!(geo.has_geo);
20182 assert_eq!(view_text(geo.space), "diagram");
20183 assert!(geo.has_kind);
20184 assert_eq!(view_text(geo.kind), "manual");
20185 assert!(geo.has_canvas);
20186 assert!(geo.has_canvas_width);
20187 assert_eq!(geo.canvas_width, 800.0);
20188 assert!(!geo.has_canvas_height);
20189 assert!(geo.has_canvas_units);
20190 assert_eq!(geo.canvas_units.len, 0);
20191
20192 let mut bus = std::mem::MaybeUninit::<PioBalancedBusView>::uninit();
20193 assert!(pio_balanced_network_bus_at(
20194 retained,
20195 0,
20196 bus.as_mut_ptr(),
20197 &mut error
20198 ));
20199 let bus = bus.assume_init();
20200 assert!(bus.has_location);
20201 assert_eq!((bus.location.x, bus.location.y), (12.0, 34.0));
20202 assert!(!bus.location.has_kind);
20203
20204 let mut branch = std::mem::MaybeUninit::<PioBalancedBranchView>::uninit();
20205 assert!(pio_balanced_network_branch_at(
20206 retained,
20207 0,
20208 branch.as_mut_ptr(),
20209 &mut error
20210 ));
20211 let branch = branch.assume_init();
20212 assert!(branch.has_route);
20213 assert_eq!(branch.route_point_count, 0);
20214
20215 let mut routed_branch = std::mem::MaybeUninit::<PioBalancedBranchView>::uninit();
20216 assert!(pio_balanced_network_branch_at(
20217 retained,
20218 1,
20219 routed_branch.as_mut_ptr(),
20220 &mut error
20221 ));
20222 let routed_branch = routed_branch.assume_init();
20223 assert!(routed_branch.has_route);
20224 assert_eq!(routed_branch.route_point_count, 1);
20225 let mut point = std::mem::MaybeUninit::<PioBalancedLocationView>::uninit();
20226 assert!(pio_balanced_network_branch_route_point_at(
20227 retained,
20228 1,
20229 0,
20230 point.as_mut_ptr(),
20231 &mut error
20232 ));
20233 let point = point.assume_init();
20234 assert_eq!((point.x, point.y), (56.0, 78.0));
20235 assert!(point.has_kind);
20236 assert_eq!(view_text(point.kind), "derived");
20237
20238 assert!(
20239 std::mem::offset_of!(PioBalancedBusView, has_location)
20240 > std::mem::offset_of!(PioBalancedBusView, location)
20241 );
20242 assert!(
20243 std::mem::offset_of!(PioBalancedBranchView, has_route)
20244 > std::mem::offset_of!(PioBalancedBranchView, route_point_count)
20245 );
20246
20247 let mut null_geo = std::mem::MaybeUninit::<PioBalancedGeoView>::uninit();
20248 assert!(!pio_balanced_network_geo(
20249 std::ptr::null(),
20250 null_geo.as_mut_ptr(),
20251 &mut error
20252 ));
20253 assert!(!error.is_null());
20254 pio_error_release(error);
20255 pio_balanced_network_release(retained);
20256 }
20257 }
20258
20259 #[test]
20260 fn lindist3flow_views_and_quantities_keep_their_owner_alive() {
20261 unsafe {
20262 let text = include_bytes!("../../tests/data/dist/micro/lindist3flow-solution.pio.json");
20263 let module = module_handle(
20264 powerio::deserialize(
20265 powerio_core::Source::from_memory("solution.pio.json", text.to_vec()).unwrap(),
20266 )
20267 .unwrap(),
20268 );
20269 let value = pio_module_value(module);
20270 let mut error = std::ptr::null_mut();
20271 assert!(pio_value_mc_ac_opf_solution(value, &mut error).is_null());
20272 assert!(!error.is_null());
20273 pio_error_release(error);
20274 error = std::ptr::null_mut();
20275 let solution = pio_value_lindist3flow_opf_solution(value, &mut error);
20276 assert!(!solution.is_null(), "{}", error_text(error));
20277 let instance = pio_calculation_solution_instance(solution, &mut error);
20278 assert!(!instance.is_null(), "{}", error_text(error));
20279 assert_eq!(
20280 view_text(pio_calculation_instance_type_name(instance)),
20281 "powerio.LinDist3FlowOpfInstance"
20282 );
20283 let network = pio_calculation_instance_multiconductor_network(instance, &mut error);
20284 assert!(!network.is_null(), "{}", error_text(error));
20285 pio_module_release(module);
20286 pio_value_release(value);
20287 assert_eq!(
20288 view_text(pio_calculation_solution_termination(solution)),
20289 "converged"
20290 );
20291 let mut objective = 0.0;
20292 assert!(pio_calculation_solution_get_objective(
20293 solution,
20294 &mut objective
20295 ));
20296 assert_eq!(objective, 1.0);
20297 let quantity = "line_active_power";
20298 let values = pio_calculation_solution_get_values(
20299 solution,
20300 quantity.as_ptr().cast(),
20301 quantity.len(),
20302 &mut error,
20303 );
20304 assert!(!values.is_null(), "{}", error_text(error));
20305 pio_calculation_solution_release(solution);
20306 let values_view = pio_vector_values(values);
20307 assert_eq!(values_view.len, 1);
20308 assert_eq!(values_view.data.as_ref(), Some(&1000.0));
20309 assert_eq!(pio_lindist3flow_opf_instance_node_count(instance), 2);
20310 assert_eq!(pio_lindist3flow_opf_instance_conductor_count(instance), 1);
20311 let mut node = std::mem::MaybeUninit::<PioLinDist3FlowNodeView>::uninit();
20312 assert!(pio_lindist3flow_opf_instance_node_at(
20313 instance,
20314 0,
20315 node.as_mut_ptr(),
20316 &mut error
20317 ));
20318 assert_eq!(node.assume_init().reference_magnitude, 230.0);
20319 let mut conductor = std::mem::MaybeUninit::<PioLinDist3FlowConductorView>::uninit();
20320 assert!(pio_lindist3flow_opf_instance_conductor_at(
20321 instance,
20322 0,
20323 conductor.as_mut_ptr(),
20324 &mut error
20325 ));
20326 let conductor = conductor.assume_init();
20327 assert!(conductor.reversed);
20328 assert_eq!(view_text(conductor.parent_bus), "source");
20329 assert_eq!(view_text(conductor.child_bus), "load");
20330 assert!(!pio_lindist3flow_opf_instance_node_at(
20331 instance,
20332 2,
20333 node.as_mut_ptr(),
20334 &mut error
20335 ));
20336 assert!(!error.is_null());
20337 pio_error_release(error);
20338 pio_vector_release(values);
20339 pio_calculation_instance_release(instance);
20340 assert_eq!(pio_multiconductor_network_bus_count(network), 2);
20341 pio_multiconductor_network_release(network);
20342 }
20343 }
20344
20345 fn check_stored_solution_rejected(document: &serde_json::Value) {
20346 let bytes = serde_json::to_vec(document).unwrap();
20347 unsafe {
20348 let mut error = std::ptr::null_mut();
20349 let source = pio_source_from_memory(
20350 c"input.pio.json".as_ptr(),
20351 14,
20352 bytes.as_ptr(),
20353 bytes.len(),
20354 &mut error,
20355 );
20356 assert!(!source.is_null());
20357 let decoded = pio_module_deserialize(source, &mut error);
20358 assert!(decoded.is_null());
20359 assert!(!error.is_null());
20360 assert_ne!(view_text(pio_error_code(error)), "BIND.CAPI.PANIC");
20361 pio_error_release(error);
20362 pio_source_release(source);
20363 }
20364 }
20365
20366 fn check_stored_solution_columns(module: &powerio::PioModule<PioValue>) {
20367 let result = powerio::serialize(module, Destination::memory("solution").unwrap()).unwrap();
20368 let EmittedOutput::Memory { artifacts } = result.into_output() else {
20369 panic!("memory output required")
20370 };
20371 let document: serde_json::Value = serde_json::from_slice(artifacts[0].bytes()).unwrap();
20372 for key in [
20373 "bus_voltage_magnitude",
20374 "bus_voltage_angle",
20375 "bus_active_injection",
20376 "branch_from_active_flow",
20377 "terminal_voltage_magnitude",
20378 "terminal_voltage_angle",
20379 ] {
20380 let Some(values) = document["value"]["data"][key].as_array() else {
20381 continue;
20382 };
20383 if values.is_empty() {
20384 continue;
20385 }
20386 assert_eq!(
20387 collect_solution_values(module.value(), key).unwrap().len(),
20388 values.len()
20389 );
20390 let mut malformed = document.clone();
20391 malformed["value"]["data"][key]
20392 .as_array_mut()
20393 .unwrap()
20394 .pop();
20395 check_stored_solution_rejected(&malformed);
20396 }
20397 let mut duplicate = document;
20398 let buses = duplicate["value"]["data"]["instance"]["network"]["buses"]
20399 .as_array_mut()
20400 .unwrap();
20401 if buses.len() > 1 {
20402 buses[1]["id"] = buses[0]["id"].clone();
20403 } else {
20404 let terminals = buses[0]["terminals"].as_array_mut().unwrap();
20405 assert!(terminals.len() >= 2);
20406 terminals[1] = terminals[0].clone();
20407 }
20408 check_stored_solution_rejected(&duplicate);
20409 }
20410
20411 #[test]
20412 #[allow(clippy::too_many_lines)]
20413 fn every_solution_exposes_its_owner_rooted_instance() {
20414 unsafe {
20415 let network = case9_network();
20416 let buses = network.buses().len();
20417 let branches = network.branches().len();
20418 let generators = network.generators().len();
20419 let three_winding = network.transformers_3w().len();
20420
20421 let dc_pf_instance =
20422 Arc::new(powerio_prob::DcPfInstance::from_network(network.clone()).unwrap());
20423 let dc_pf = powerio_prob::DcPfSolution::new(
20424 dc_pf_instance,
20425 powerio_prob::Termination::Converged,
20426 vec![0.0; buses],
20427 vec![0.0; buses],
20428 vec![0.0; branches],
20429 vec![0.0; branches],
20430 vec![Default::default(); three_winding],
20431 )
20432 .unwrap();
20433
20434 let ac_pf_instance =
20435 Arc::new(powerio_prob::AcPfInstance::from_network(network.clone()).unwrap());
20436 let ac_pf = powerio_prob::AcPfSolution::new(
20437 ac_pf_instance,
20438 powerio_prob::Termination::Converged,
20439 vec![1.0; buses],
20440 vec![0.0; buses],
20441 vec![0.0; buses],
20442 vec![0.0; buses],
20443 vec![0.0; branches],
20444 vec![0.0; branches],
20445 vec![0.0; branches],
20446 vec![0.0; branches],
20447 vec![Default::default(); three_winding],
20448 )
20449 .unwrap();
20450
20451 let dc_opf_instance =
20452 Arc::new(powerio_prob::DcOpfInstance::from_network(network.clone()).unwrap());
20453 let dc_opf = powerio_prob::DcOpfSolution::new(
20454 dc_opf_instance,
20455 powerio_prob::Termination::Converged,
20456 vec![0.0; buses],
20457 vec![0.0; buses],
20458 vec![0.0; branches],
20459 vec![0.0; branches],
20460 vec![0.0; generators],
20461 0.0,
20462 vec![Default::default(); three_winding],
20463 )
20464 .unwrap();
20465
20466 let ac_opf_instance =
20467 Arc::new(powerio_prob::AcOpfInstance::from_network(network.clone()).unwrap());
20468 let ac_opf = powerio_prob::AcOpfSolution::new(
20469 Arc::clone(&ac_opf_instance),
20470 powerio_prob::Termination::Converged,
20471 vec![1.0; buses],
20472 vec![0.0; buses],
20473 vec![0.0; buses],
20474 vec![0.0; buses],
20475 vec![0.0; branches],
20476 vec![0.0; branches],
20477 vec![0.0; branches],
20478 vec![0.0; branches],
20479 vec![0.0; generators],
20480 vec![0.0; generators],
20481 0.0,
20482 vec![Default::default(); three_winding],
20483 )
20484 .unwrap();
20485
20486 let mut socwr_values = powerio_prob::solution::SocwrOpfValues::default();
20487 socwr_values.bus_voltage_magnitude_squared = vec![1.0; buses];
20488 socwr_values.branch_voltage_product_real = vec![0.0; branches];
20489 socwr_values.branch_voltage_product_imaginary = vec![0.0; branches];
20490 socwr_values.generator_active_power = vec![0.0; generators];
20491 socwr_values.generator_reactive_power = vec![0.0; generators];
20492 socwr_values.branch_from_active_power = vec![0.0; branches];
20493 socwr_values.branch_from_reactive_power = vec![0.0; branches];
20494 socwr_values.branch_to_active_power = vec![0.0; branches];
20495 socwr_values.branch_to_reactive_power = vec![0.0; branches];
20496 socwr_values.three_winding_transformer_terminal_powers =
20497 vec![Default::default(); three_winding];
20498 let socwr = powerio_prob::solution::SocwrOpfSolution::new(
20499 ac_opf_instance,
20500 powerio_prob::Termination::Converged,
20501 socwr_values,
20502 0.0,
20503 )
20504 .unwrap();
20505
20506 let multiconductor = multiconductor_network();
20507 let terminals: usize = multiconductor
20508 .buses()
20509 .iter()
20510 .map(|bus| bus.terminals.len())
20511 .sum();
20512 let source_terminals: usize = multiconductor
20513 .sources()
20514 .iter()
20515 .map(|source| source.terminal_map.len())
20516 .sum();
20517 let generator_terminals: usize = multiconductor
20518 .generators()
20519 .iter()
20520 .map(|generator| generator.terminal_map.len())
20521 .sum();
20522 let mc_pf_instance = Arc::new(
20523 powerio_prob::McAcPfInstance::from_network(multiconductor.clone()).unwrap(),
20524 );
20525 let mc_pf = powerio_prob::McAcPfSolution::new(
20526 mc_pf_instance,
20527 powerio_prob::Termination::Converged,
20528 vec![1.0; terminals],
20529 vec![0.0; terminals],
20530 vec![0.0; source_terminals],
20531 )
20532 .unwrap();
20533 let mc_opf_instance =
20534 Arc::new(powerio_prob::McAcOpfInstance::from_network(multiconductor).unwrap());
20535 let mc_opf = powerio_prob::McAcOpfSolution::new(
20536 mc_opf_instance,
20537 powerio_prob::Termination::Converged,
20538 vec![1.0; terminals],
20539 vec![0.0; terminals],
20540 vec![0.0; source_terminals],
20541 vec![0.0; generator_terminals],
20542 0.0,
20543 )
20544 .unwrap();
20545
20546 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
20547 .join("../tests/data/goc3/goc3_small.json");
20548 let scuc_instance = goc3_instance(Source::open(path).unwrap());
20549 let scuc = powerio_prob::AcScucSolution::new(
20550 Arc::new(scuc_instance),
20551 powerio_prob::Termination::Converged,
20552 powerio_prob::ScucNetworkOutputs::default(),
20553 powerio_prob::ScucDeviceOutputs::default(),
20554 None,
20555 )
20556 .unwrap();
20557
20558 type SolutionAccessor = unsafe extern "C" fn(
20559 *const PioValueHandle,
20560 *mut *mut PioError,
20561 )
20562 -> *mut PioCalculationSolution;
20563 let solutions: [(PioValue, &str, bool, SolutionAccessor); 8] = [
20564 (
20565 PioValue::DcPfSolution(dc_pf),
20566 "powerio.DcPfInstance",
20567 false,
20568 pio_value_dc_pf_solution,
20569 ),
20570 (
20571 PioValue::AcPfSolution(ac_pf),
20572 "powerio.AcPfInstance",
20573 false,
20574 pio_value_ac_pf_solution,
20575 ),
20576 (
20577 PioValue::DcOpfSolution(dc_opf),
20578 "powerio.DcOpfInstance",
20579 false,
20580 pio_value_dc_opf_solution,
20581 ),
20582 (
20583 PioValue::AcOpfSolution(ac_opf),
20584 "powerio.AcOpfInstance",
20585 false,
20586 pio_value_ac_opf_solution,
20587 ),
20588 (
20589 PioValue::SocwrOpfSolution(socwr),
20590 "powerio.AcOpfInstance",
20591 false,
20592 pio_value_socwr_opf_solution,
20593 ),
20594 (
20595 PioValue::McAcPfSolution(mc_pf),
20596 "powerio.McAcPfInstance",
20597 true,
20598 pio_value_mc_ac_pf_solution,
20599 ),
20600 (
20601 PioValue::McAcOpfSolution(mc_opf),
20602 "powerio.McAcOpfInstance",
20603 true,
20604 pio_value_mc_ac_opf_solution,
20605 ),
20606 (
20607 PioValue::AcScucSolution(scuc),
20608 "powerio.AcScucInstance",
20609 false,
20610 pio_value_ac_scuc_solution,
20611 ),
20612 ];
20613
20614 for (value, expected_instance_type, is_multiconductor, accessor) in solutions {
20615 let stored = powerio::PioModule::new(value);
20616 if matches!(
20617 stored.value(),
20618 PioValue::AcPfSolution(_)
20619 | PioValue::DcOpfSolution(_)
20620 | PioValue::McAcPfSolution(_)
20621 | PioValue::McAcOpfSolution(_)
20622 ) {
20623 check_stored_solution_columns(&stored);
20624 }
20625 let module = module_handle(stored);
20626 let value = pio_module_value(module);
20627 let mut error = std::ptr::null_mut();
20628 let solution = accessor(value, &mut error);
20629 assert!(!solution.is_null(), "{}", error_text(error));
20630 let instance = pio_calculation_solution_instance(solution, &mut error);
20631 assert!(!instance.is_null(), "{}", error_text(error));
20632
20633 pio_calculation_solution_release(solution);
20634 pio_value_release(value);
20635 pio_module_release(module);
20636
20637 assert_eq!(
20638 view_text(pio_calculation_instance_type_name(instance)),
20639 expected_instance_type
20640 );
20641 if is_multiconductor {
20642 let network =
20643 pio_calculation_instance_multiconductor_network(instance, &mut error);
20644 assert!(!network.is_null(), "{}", error_text(error));
20645 assert!(pio_multiconductor_network_bus_count(network) > 0);
20646 pio_multiconductor_network_release(network);
20647 } else {
20648 let network = pio_calculation_instance_balanced_network(instance, &mut error);
20649 assert!(!network.is_null(), "{}", error_text(error));
20650 assert!(pio_balanced_network_bus_count(network) > 0);
20651 pio_balanced_network_release(network);
20652 }
20653 pio_calculation_instance_release(instance);
20654 }
20655 }
20656 }
20657
20658 #[test]
20659 fn balanced_rows_are_checked_borrowed_views() {
20660 unsafe {
20661 let module = parse_case9();
20662 let value = pio_module_value(module);
20663 let mut error = std::ptr::null_mut();
20664 let network = pio_value_balanced_network(value, &mut error);
20665 assert!(!network.is_null(), "{}", error_text(error));
20666
20667 pio_module_release(module);
20668 pio_value_release(value);
20669
20670 assert_eq!(pio_balanced_network_base_mva(network), 100.0);
20671 assert_eq!(pio_balanced_network_base_frequency_hz(network), 60.0);
20672 assert_eq!(pio_balanced_network_bus_count(network), 9);
20673 assert_eq!(pio_balanced_network_load_count(network), 3);
20674 assert_eq!(pio_balanced_network_shunt_count(network), 0);
20675 assert_eq!(pio_balanced_network_branch_count(network), 9);
20676 assert_eq!(pio_balanced_network_generator_count(network), 3);
20677 assert_eq!(pio_balanced_network_storage_count(network), 0);
20678
20679 let mut bus = std::mem::MaybeUninit::<PioBalancedBusView>::uninit();
20680 assert!(pio_balanced_network_bus_at(
20681 network,
20682 0,
20683 bus.as_mut_ptr(),
20684 &mut error,
20685 ));
20686 let bus = bus.assume_init();
20687 assert_eq!(bus.id, 1);
20688 assert_eq!(view_text(bus.bus_type), "REF");
20689 assert_eq!(bus.base_kv, 345.0);
20690 assert!(bus.has_component_id);
20691 assert_eq!(view_text(bus.component_id), "1");
20692
20693 let mut load = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
20694 assert!(pio_balanced_network_load_at(
20695 network,
20696 0,
20697 load.as_mut_ptr(),
20698 &mut error,
20699 ));
20700 let load = load.assume_init();
20701 assert_eq!(load.bus_id, 5);
20702 assert_eq!(load.p_mw, 90.0);
20703 assert_eq!(load.q_mvar, 30.0);
20704 assert_eq!(view_text(load.voltage_model.kind), "constant_power");
20705
20706 let mut branch = std::mem::MaybeUninit::<PioBalancedBranchView>::uninit();
20707 assert!(pio_balanced_network_branch_at(
20708 network,
20709 0,
20710 branch.as_mut_ptr(),
20711 &mut error,
20712 ));
20713 let branch = branch.assume_init();
20714 assert_eq!(branch.from_bus_id, 1);
20715 assert_eq!(branch.to_bus_id, 4);
20716 assert_eq!(branch.reactance_pu, 0.0576);
20717 assert_eq!(branch.rate_a_mva, 250.0);
20718 assert_eq!(branch.tap_ratio, 0.0);
20719 assert_eq!(branch.effective_tap_ratio, 1.0);
20720 assert!(!branch.terminal_charging_is_explicit);
20721 assert_eq!(branch.additional_rating_count, 0);
20722
20723 let mut generator = std::mem::MaybeUninit::<PioBalancedGeneratorView>::uninit();
20724 assert!(pio_balanced_network_generator_at(
20725 network,
20726 0,
20727 generator.as_mut_ptr(),
20728 &mut error,
20729 ));
20730 let generator = generator.assume_init();
20731 assert_eq!(generator.bus_id, 1);
20732 assert_eq!(view_text(generator.energy_source), "other");
20733 assert_eq!(generator.active_power_mw, 72.3);
20734 assert!(generator.has_cost);
20735 assert!(generator.voltage_regulation_on);
20736 assert!(!generator.has_regulating_terminal);
20737 assert!(!generator.has_active_power_control);
20738 assert!(!generator.active_power_control.has_droop_percent);
20739 assert_eq!(generator.cost.model, 2);
20740 assert_eq!(generator.cost.ncost, 3);
20741 assert_eq!(generator.cost.coefficients.len, 3);
20742 let coefficients = std::slice::from_raw_parts(
20743 generator.cost.coefficients.data,
20744 generator.cost.coefficients.len,
20745 );
20746 assert_eq!(coefficients, &[0.11, 5.0, 150.0]);
20747
20748 let mut capability = std::mem::MaybeUninit::<PioGeneratorCapabilityView>::uninit();
20749 assert!(pio_balanced_network_generator_capability_at(
20750 network,
20751 0,
20752 0,
20753 capability.as_mut_ptr(),
20754 &mut error,
20755 ));
20756 let capability = capability.assume_init();
20757 assert_eq!(view_text(capability.name), "pc1");
20758 assert!(capability.has_value);
20759 assert_eq!(capability.value, 0.0);
20760
20761 let mut missing = std::mem::MaybeUninit::<PioBalancedBusView>::uninit();
20762 assert!(!pio_balanced_network_bus_at(
20763 network,
20764 9,
20765 missing.as_mut_ptr(),
20766 &mut error,
20767 ));
20768 assert_eq!(
20769 view_text(pio_error_code(error)),
20770 "BIND.CAPI.INDEX_OUT_OF_RANGE"
20771 );
20772 pio_error_release(error);
20773
20774 pio_balanced_network_release(network);
20775 }
20776 }
20777
20778 #[test]
20779 fn balanced_optional_row_fields_are_explicit() {
20780 unsafe {
20781 let mut network = BalancedNetwork::new("optional-fields", 100.0);
20782 network.buses_mut().push(powerio_tx::Bus::new(
20783 powerio_tx::BusId(1),
20784 powerio_tx::BusType::Ref,
20785 115.0,
20786 ));
20787 network.buses_mut().push(powerio_tx::Bus::new(
20788 powerio_tx::BusId(2),
20789 powerio_tx::BusType::Pq,
20790 115.0,
20791 ));
20792
20793 let mut load = powerio_tx::Load::new(powerio_tx::BusId(2), 6.0, 3.0);
20794 load.voltage_model = Some(powerio_tx::LoadVoltageModel::Zip {
20795 p_constant_power: 3.0,
20796 q_constant_power: 1.5,
20797 p_constant_current: 2.0,
20798 q_constant_current: 1.0,
20799 p_constant_impedance: 1.0,
20800 q_constant_impedance: 0.5,
20801 v_nom: Some(1.0),
20802 load_type: Some(7),
20803 scaling: Some(0.9),
20804 });
20805 network.loads_mut().push(load);
20806
20807 let mut shunt = powerio_tx::Shunt::new(powerio_tx::BusId(2), 0.0, 2.0);
20808 let mut control = powerio_tx::SwitchedShuntControl::new(
20809 powerio_tx::SwitchedShuntMode::Discrete,
20810 1.05,
20811 0.95,
20812 vec![powerio_tx::ShuntBlock::with_admittance(4, 0.25, 0.5)],
20813 );
20814 control.control_bus = Some(powerio_tx::BusId(1));
20815 shunt.control = Some(control);
20816 network.shunts_mut().push(shunt);
20817
20818 let mut branch =
20819 powerio_tx::Branch::new(powerio_tx::BusId(1), powerio_tx::BusId(2), 0.01, 0.1);
20820 branch.name = Some("controlled transformer".to_owned());
20821 branch.charging = Some(powerio_tx::BranchCharging::new(0.01, 0.02, 0.03, 0.04));
20822 branch
20823 .rating_sets
20824 .push(powerio_tx::BranchRatingSet::new("LTE", 175.0));
20825 branch.current_ratings = Some(powerio_tx::BranchCurrentRatings::new(1.0, 2.0, 3.0));
20826 branch.tap = 1.05;
20827 branch.shift = 4.0;
20828 branch.angmin = -30.0;
20829 branch.angmax = 30.0;
20830 let mut transformer_control = powerio_tx::TransformerControl::new(
20831 powerio_tx::TransformerControlMode::DcLineQuantity,
20832 );
20833 transformer_control.enabled = false;
20834 transformer_control.controlled_bus = Some(powerio_tx::BusId(2));
20835 transformer_control.controlled_bus_on_winding_side = true;
20836 transformer_control.regulating_terminal = Some(
20837 serde_json::from_value(serde_json::json!({
20838 "equipment": {
20839 "component_type": "transformer",
20840 "local_id": "controlled-transformer"
20841 },
20842 "terminal": 2
20843 }))
20844 .unwrap(),
20845 );
20846 transformer_control.ntp = 17;
20847 transformer_control.winding_connection_angle = Some(12.5);
20848 branch.control = Some(transformer_control);
20849 network.branches_mut().push(branch);
20850
20851 let mut generator = powerio_tx::Generator::new(powerio_tx::BusId(1));
20852 generator.energy_source = powerio_tx::GeneratorEnergySource::Nuclear;
20853 generator.cost = Some(powerio_tx::GenCost::new(2, 10.0, 20.0, vec![1.0, 2.0]));
20854 generator.caps[8] = Some(25.0);
20855 generator.voltage_regulation_on = false;
20856 generator.regulating_terminal = Some(
20857 serde_json::from_value(serde_json::json!({
20858 "equipment": {
20859 "component_type": "load",
20860 "local_id": "regulated-load"
20861 },
20862 "terminal": 1
20863 }))
20864 .unwrap(),
20865 );
20866 generator.regulated_bus = Some(powerio_tx::BusId(2));
20867 let mut generator_control = powerio_tx::ActivePowerControl::new(true);
20868 generator_control.droop_percent = Some(4.0);
20869 generator_control.participation_factor = Some(0.6);
20870 generator_control.minimum_target_active_power_mw = Some(10.0);
20871 generator_control.maximum_target_active_power_mw = Some(100.0);
20872 generator.active_power_control = Some(generator_control);
20873 network.generators_mut().push(generator);
20874
20875 let mut storage = powerio_tx::Storage::new(powerio_tx::BusId(2));
20876 storage.energy = 2.0;
20877 storage.energy_rating = 10.0;
20878 storage.current_rating = Some(100.0);
20879 let mut storage_control = powerio_tx::ActivePowerControl::new(false);
20880 storage_control.participation_factor = Some(0.4);
20881 storage.active_power_control = Some(storage_control);
20882 network.storage_mut().push(storage);
20883
20884 let module = module_handle(powerio::PioModule::new(PioValue::from(network)));
20885 let value = pio_module_value(module);
20886 let mut error = std::ptr::null_mut();
20887 let network = pio_value_balanced_network(value, &mut error);
20888
20889 let mut load = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
20890 assert!(pio_balanced_network_load_at(
20891 network,
20892 0,
20893 load.as_mut_ptr(),
20894 &mut error
20895 ));
20896 let load = load.assume_init();
20897 assert_eq!(view_text(load.voltage_model.kind), "zip");
20898 assert_eq!(load.voltage_model.p_constant_current_mw, 2.0);
20899 assert!(load.voltage_model.has_nominal_voltage);
20900 assert!(load.voltage_model.has_load_type);
20901 assert!(load.voltage_model.has_scaling);
20902
20903 let mut shunt = std::mem::MaybeUninit::<PioBalancedShuntView>::uninit();
20904 assert!(pio_balanced_network_shunt_at(
20905 network,
20906 0,
20907 shunt.as_mut_ptr(),
20908 &mut error
20909 ));
20910 let shunt = shunt.assume_init();
20911 assert!(!shunt.has_section_count);
20912 assert_eq!(shunt.section_count, 0);
20913 assert!(shunt.has_control);
20914 assert_eq!(view_text(shunt.control_mode), "discrete");
20915 assert_eq!(shunt.control_block_count, 1);
20916 assert!(shunt.has_control_bus);
20917 let mut block = std::mem::MaybeUninit::<PioShuntBlockView>::uninit();
20918 assert!(pio_balanced_network_shunt_block_at(
20919 network,
20920 0,
20921 0,
20922 block.as_mut_ptr(),
20923 &mut error,
20924 ));
20925 let block = block.assume_init();
20926 assert_eq!(block.steps, 4);
20927 assert_eq!(block.conductance_mw, 0.25);
20928 assert_eq!(block.susceptance_mvar, 0.5);
20929
20930 let mut branch = std::mem::MaybeUninit::<PioBalancedBranchView>::uninit();
20931 assert!(pio_balanced_network_branch_at(
20932 network,
20933 0,
20934 branch.as_mut_ptr(),
20935 &mut error,
20936 ));
20937 let branch = branch.assume_init();
20938 assert!(branch.has_name);
20939 assert_eq!(view_text(branch.name), "controlled transformer");
20940 assert!(branch.has_control);
20941 assert_eq!(view_text(branch.control.mode), "dc_line_quantity");
20942 assert!(!branch.control.enabled);
20943 assert!(branch.control.has_controlled_bus);
20944 assert_eq!(branch.control.controlled_bus_id, 2);
20945 assert!(branch.control.controlled_bus_on_winding_side);
20946 assert!(branch.control.has_regulating_terminal);
20947 assert_eq!(branch.control.regulating_terminal.terminal, 2);
20948 assert_eq!(branch.control.tap_position_count, 17);
20949 assert!(branch.control.has_winding_connection_angle);
20950 assert_eq!(branch.control.winding_connection_angle, 12.5);
20951 assert!(branch.terminal_charging_is_explicit);
20952 assert_eq!(branch.from_conductance_pu, 0.01);
20953 assert_eq!(branch.to_susceptance_pu, 0.04);
20954 assert!(branch.has_current_ratings);
20955 assert_eq!(branch.current_rating_c, 3.0);
20956 assert_eq!(branch.additional_rating_count, 1);
20957 let mut rating = std::mem::MaybeUninit::<PioBranchRatingView>::uninit();
20958 assert!(pio_balanced_network_branch_rating_at(
20959 network,
20960 0,
20961 0,
20962 rating.as_mut_ptr(),
20963 &mut error,
20964 ));
20965 let rating = rating.assume_init();
20966 assert_eq!(view_text(rating.name), "LTE");
20967 assert_eq!(rating.rate_mva, 175.0);
20968
20969 let mut generator = std::mem::MaybeUninit::<PioBalancedGeneratorView>::uninit();
20970 assert!(pio_balanced_network_generator_at(
20971 network,
20972 0,
20973 generator.as_mut_ptr(),
20974 &mut error,
20975 ));
20976 let generator = generator.assume_init();
20977 assert!(generator.has_regulated_bus);
20978 assert_eq!(generator.regulated_bus_id, 2);
20979 assert!(!generator.voltage_regulation_on);
20980 assert!(generator.has_regulating_terminal);
20981 assert_eq!(view_text(generator.energy_source), "nuclear");
20982 assert_eq!(
20983 view_text(generator.regulating_terminal.equipment.component_type),
20984 "load"
20985 );
20986 assert_eq!(
20987 view_text(generator.regulating_terminal.equipment.local_id),
20988 "regulated-load"
20989 );
20990 assert_eq!(generator.regulating_terminal.terminal, 1);
20991 assert!(generator.has_active_power_control);
20992 assert!(generator.active_power_control.participate);
20993 assert!(generator.active_power_control.has_droop_percent);
20994 assert_eq!(generator.active_power_control.droop_percent, 4.0);
20995 assert!(generator.active_power_control.has_participation_factor);
20996 assert_eq!(generator.active_power_control.participation_factor, 0.6);
20997 assert!(
20998 generator
20999 .active_power_control
21000 .has_minimum_target_active_power
21001 );
21002 assert_eq!(
21003 generator
21004 .active_power_control
21005 .minimum_target_active_power_mw,
21006 10.0
21007 );
21008 assert!(
21009 generator
21010 .active_power_control
21011 .has_maximum_target_active_power
21012 );
21013 assert_eq!(
21014 generator
21015 .active_power_control
21016 .maximum_target_active_power_mw,
21017 100.0
21018 );
21019 let mut capability = std::mem::MaybeUninit::<PioGeneratorCapabilityView>::uninit();
21020 assert!(pio_balanced_network_generator_capability_at(
21021 network,
21022 0,
21023 8,
21024 capability.as_mut_ptr(),
21025 &mut error,
21026 ));
21027 let capability = capability.assume_init();
21028 assert_eq!(view_text(capability.name), "ramp_30");
21029 assert!(capability.has_value);
21030 assert_eq!(capability.value, 25.0);
21031
21032 let mut storage = std::mem::MaybeUninit::<PioBalancedStorageView>::uninit();
21033 assert!(pio_balanced_network_storage_at(
21034 network,
21035 0,
21036 storage.as_mut_ptr(),
21037 &mut error,
21038 ));
21039 let storage = storage.assume_init();
21040 assert_eq!(storage.energy_mwh, 2.0);
21041 assert_eq!(storage.energy_rating_mwh, 10.0);
21042 assert!(storage.has_current_rating);
21043 assert_eq!(storage.current_rating, 100.0);
21044 assert!(storage.has_active_power_control);
21045 assert!(!storage.active_power_control.participate);
21046 assert!(!storage.active_power_control.has_droop_percent);
21047 assert!(storage.active_power_control.has_participation_factor);
21048 assert_eq!(storage.active_power_control.participation_factor, 0.4);
21049
21050 pio_balanced_network_release(network);
21051 pio_value_release(value);
21052 pio_module_release(module);
21053 }
21054 }
21055
21056 #[test]
21057 fn powsybl_balanced_tables_are_borrowed_without_serialization() {
21058 unsafe {
21059 let mut network = BalancedNetwork::new("powsybl-tables", 100.0);
21060 for (id, kind, kv) in [
21061 (1, powerio_tx::BusType::Ref, 400.0),
21062 (2, powerio_tx::BusType::Pq, 225.0),
21063 (3, powerio_tx::BusType::Pq, 63.0),
21064 ] {
21065 network
21066 .buses_mut()
21067 .push(powerio_tx::Bus::new(powerio_tx::BusId(id), kind, kv));
21068 }
21069
21070 let mut shunt = powerio_tx::Shunt::new(powerio_tx::BusId(2), 0.0, 12.0);
21071 shunt.uid = Some("SH".to_owned());
21072 shunt.section_count = Some(2);
21073 network.shunts_mut().push(shunt);
21074
21075 let mut svc = powerio_tx::StaticVarCompensator::new(powerio_tx::BusId(2), -0.02, 0.03);
21076 svc.uid = Some("SVC".to_owned());
21077 svc.regulating = true;
21078 svc.regulation_mode = powerio_tx::StaticVarCompensatorRegulationMode::ReactivePower;
21079 svc.reactive_power_setpoint_mvar = 12.0;
21080 svc.regulating_terminal = Some(
21081 serde_json::from_value(serde_json::json!({
21082 "equipment": {
21083 "component_type": "static_var_compensator",
21084 "local_id": "SVC"
21085 },
21086 "terminal": 1
21087 }))
21088 .unwrap(),
21089 );
21090 network.static_var_compensators_mut().push(svc);
21091
21092 let mut switch =
21093 powerio_tx::Switch::new(powerio_tx::BusId(1), powerio_tx::BusId(2), true);
21094 switch.uid = Some("SW".to_owned());
21095 switch.thermal_rating = Some(500.0);
21096 switch.pf = Some(75.0);
21097 network.switches_mut().push(switch);
21098
21099 let mut hvdc = powerio_tx::Hvdc::new(powerio_tx::BusId(1), powerio_tx::BusId(2));
21100 hvdc.uid = Some("HVDC".to_owned());
21101 hvdc.resistance_ohm = Some(1.5);
21102 hvdc.nominal_voltage_kv = Some(320.0);
21103 hvdc.converters_mode =
21104 Some(powerio_tx::HvdcConvertersMode::Side1RectifierSide2Inverter);
21105 hvdc.converter1 = Some(
21106 serde_json::from_value(serde_json::json!({
21107 "component": {
21108 "component_type": "hvdc_converter",
21109 "local_id": "C1"
21110 },
21111 "kind": "vsc",
21112 "loss_factor_percent": 1.0,
21113 "voltage_regulator_on": true,
21114 "voltage_setpoint_kv": 400.0
21115 }))
21116 .unwrap(),
21117 );
21118 network.hvdc_mut().push(hvdc);
21119
21120 let mut windings = [
21121 powerio_tx::Winding::new(powerio_tx::BusId(1)),
21122 powerio_tx::Winding::new(powerio_tx::BusId(2)),
21123 powerio_tx::Winding::new(powerio_tx::BusId(3)),
21124 ];
21125 let mut winding_control =
21126 powerio_tx::TransformerControl::new(powerio_tx::TransformerControlMode::ActiveFlow);
21127 winding_control.controlled_bus = Some(powerio_tx::BusId(3));
21128 winding_control.regulating_terminal = Some(
21129 serde_json::from_value(serde_json::json!({
21130 "equipment": {
21131 "component_type": "transformer",
21132 "local_id": "T3"
21133 },
21134 "terminal": 3
21135 }))
21136 .unwrap(),
21137 );
21138 winding_control.ntp = 21;
21139 windings[2].control = Some(winding_control);
21140 let mut transformer = powerio_tx::Transformer3W::new(
21141 windings,
21142 [
21143 powerio_tx::Impedance::new(0.01, 0.1, 100.0),
21144 powerio_tx::Impedance::new(0.02, 0.2, 100.0),
21145 powerio_tx::Impedance::new(0.03, 0.3, 100.0),
21146 ],
21147 );
21148 transformer.uid = Some("T3".to_owned());
21149 transformer.name = Some("three winding".to_owned());
21150 network.transformers_3w_mut().push(transformer);
21151
21152 let mut area = powerio_tx::Area::new(7);
21153 area.uid = Some("A7".to_owned());
21154 area.name = Some("control area".to_owned());
21155 area.area_type = Some("control_area".to_owned());
21156 area.slack_bus = Some(powerio_tx::BusId(1));
21157 area.net_interchange = 12.5;
21158 network.areas_mut().push(area);
21159
21160 let module = module_handle(powerio::PioModule::new(PioValue::from(network)));
21161 let value = pio_module_value(module);
21162 let mut error = std::ptr::null_mut();
21163 let network = pio_value_balanced_network(value, &mut error);
21164 pio_value_release(value);
21165 pio_module_release(module);
21166
21167 assert_eq!(
21168 pio_balanced_network_static_var_compensator_count(network),
21169 1
21170 );
21171 assert_eq!(pio_balanced_network_shunt_count(network), 1);
21172 assert_eq!(pio_balanced_network_switch_count(network), 1);
21173 assert_eq!(pio_balanced_network_hvdc_count(network), 1);
21174 assert_eq!(
21175 pio_balanced_network_three_winding_transformer_count(network),
21176 1
21177 );
21178 assert_eq!(pio_balanced_network_area_count(network), 1);
21179
21180 let mut svc = std::mem::MaybeUninit::<PioBalancedStaticVarCompensatorView>::uninit();
21181 assert!(pio_balanced_network_static_var_compensator_at(
21182 network,
21183 0,
21184 svc.as_mut_ptr(),
21185 &mut error,
21186 ));
21187 let svc = svc.assume_init();
21188 assert_eq!(view_text(svc.component_id), "SVC");
21189 assert_eq!(view_text(svc.regulation_mode), "reactive_power");
21190 assert!(svc.has_regulating_terminal);
21191
21192 let mut shunt = std::mem::MaybeUninit::<PioBalancedShuntView>::uninit();
21193 assert!(pio_balanced_network_shunt_at(
21194 network,
21195 0,
21196 shunt.as_mut_ptr(),
21197 &mut error,
21198 ));
21199 let shunt = shunt.assume_init();
21200 assert!(shunt.has_section_count);
21201 assert_eq!(shunt.section_count, 2);
21202
21203 let mut switch = std::mem::MaybeUninit::<PioBalancedSwitchView>::uninit();
21204 assert!(pio_balanced_network_switch_at(
21205 network,
21206 0,
21207 switch.as_mut_ptr(),
21208 &mut error,
21209 ));
21210 let switch = switch.assume_init();
21211 assert!(switch.closed);
21212 assert_eq!(switch.thermal_rating_mva, 500.0);
21213 assert!(switch.has_from_active_power);
21214
21215 let mut hvdc = std::mem::MaybeUninit::<PioBalancedHvdcView>::uninit();
21216 assert!(pio_balanced_network_hvdc_at(
21217 network,
21218 0,
21219 hvdc.as_mut_ptr(),
21220 &mut error,
21221 ));
21222 let hvdc = hvdc.assume_init();
21223 assert!(hvdc.has_converter1);
21224 assert_eq!(view_text(hvdc.converter1.kind), "vsc");
21225 assert_eq!(hvdc.nominal_voltage_kv, 320.0);
21226
21227 let mut transformer =
21228 std::mem::MaybeUninit::<PioBalancedThreeWindingTransformerView>::uninit();
21229 assert!(pio_balanced_network_three_winding_transformer_at(
21230 network,
21231 0,
21232 transformer.as_mut_ptr(),
21233 &mut error,
21234 ));
21235 assert_eq!(transformer.assume_init().winding_count, 3);
21236 let mut winding =
21237 std::mem::MaybeUninit::<PioThreeWindingTransformerWindingView>::uninit();
21238 assert!(pio_balanced_network_three_winding_transformer_winding_at(
21239 network,
21240 0,
21241 2,
21242 winding.as_mut_ptr(),
21243 &mut error,
21244 ));
21245 let winding = winding.assume_init();
21246 assert_eq!(winding.bus_id, 3);
21247 assert!(winding.has_control);
21248 assert_eq!(view_text(winding.control.mode), "active_flow");
21249 assert!(winding.control.enabled);
21250 assert_eq!(winding.control.controlled_bus_id, 3);
21251 assert!(winding.control.has_regulating_terminal);
21252 assert_eq!(winding.control.regulating_terminal.terminal, 3);
21253 assert_eq!(winding.control.tap_position_count, 21);
21254
21255 let mut area = std::mem::MaybeUninit::<PioBalancedAreaView>::uninit();
21256 assert!(pio_balanced_network_area_at(
21257 network,
21258 0,
21259 area.as_mut_ptr(),
21260 &mut error,
21261 ));
21262 let area = area.assume_init();
21263 assert_eq!(view_text(area.component_id), "A7");
21264 assert_eq!(area.slack_bus_id, 1);
21265
21266 pio_balanced_network_release(network);
21267 }
21268 }
21269
21270 #[test]
21271 fn detailed_connectivity_tables_are_owner_rooted_views() {
21272 const HIERARCHY: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
21273<iidm:network xmlns:iidm="http://www.powsybl.org/schema/iidm/1_17" id="details" caseDate="2026-01-01T00:00:00Z" forecastDistance="0" sourceFormat="test" minimumValidationLevel="STEADY_STATE_HYPOTHESIS">
21274 <iidm:substation id="S" country="US" tso="MISO" geographicalTags="east">
21275 <iidm:voltageLevel id="VL1" nominalV="132" topologyKind="BUS_BREAKER"><iidm:busBreakerTopology><iidm:bus id="B1"/></iidm:busBreakerTopology></iidm:voltageLevel>
21276 <iidm:voltageLevel id="VL2" nominalV="33" topologyKind="BUS_BREAKER"><iidm:busBreakerTopology><iidm:bus id="B2"/></iidm:busBreakerTopology></iidm:voltageLevel>
21277 <iidm:voltageLevel id="VL3" nominalV="11" topologyKind="BUS_BREAKER"><iidm:busBreakerTopology><iidm:bus id="B3"/></iidm:busBreakerTopology></iidm:voltageLevel>
21278 <iidm:threeWindingsTransformer id="T3" ratedU0="132" ratedU1="132" ratedU2="33" ratedU3="11" r1="17.424" x1="34.848" r2="1.7424" x2="3.4848" r3="0.8712" x3="1.7424" bus1="B1" connectableBus1="B1" voltageLevelId1="VL1" bus2="B2" connectableBus2="B2" voltageLevelId2="VL2" bus3="B3" connectableBus3="B3" voltageLevelId3="VL3" selectedOperationalLimitsGroupIds1="normal">
21279 <iidm:ratioTapChanger2 tapPosition="0" lowTapPosition="0" loadTapChangingCapabilities="false"><iidm:step rho="1.05"/></iidm:ratioTapChanger2>
21280 <iidm:operationalLimitsGroup1 id="normal"><iidm:apparentPowerLimits permanentLimit="90"><iidm:temporaryLimit name="emergency" acceptableDuration="600" value="100"/></iidm:apparentPowerLimits></iidm:operationalLimitsGroup1>
21281 </iidm:threeWindingsTransformer>
21282 </iidm:substation>
21283</iidm:network>"#;
21284
21285 unsafe {
21286 let module = parse_xiidm_text(HIERARCHY);
21287 let mut identified_network = match &PioModule::get(module).unwrap().module.value() {
21288 PioValue::BalancedNetwork(network) => network.clone(),
21289 value => panic!("expected balanced network, got {}", value.type_name()),
21290 };
21291 let identified_details = Arc::make_mut(
21292 identified_network
21293 .detailed_connectivity_mut()
21294 .as_mut()
21295 .unwrap(),
21296 );
21297 identified_details.terminals[0].component =
21298 Some(ComponentId::new("terminal", "terminal-T3-1").unwrap());
21299 identified_details.tap_changers[0].component =
21300 Some(ComponentId::new("tap_changer", "tap-T3-2").unwrap());
21301 let value = pio_module_value(module);
21302 let mut error = std::ptr::null_mut();
21303 let network = pio_value_balanced_network(value, &mut error);
21304 assert!(pio_balanced_network_has_detailed_connectivity(network));
21305 let details = pio_balanced_network_detailed_connectivity(network);
21306 assert!(!details.is_null());
21307 pio_value_release(value);
21308 pio_module_release(module);
21309 pio_balanced_network_release(network);
21310
21311 let mut counts = std::mem::MaybeUninit::<PioDetailedConnectivityCountsView>::uninit();
21312 assert!(pio_detailed_connectivity_counts(
21313 details,
21314 counts.as_mut_ptr(),
21315 &mut error,
21316 ));
21317 let counts = counts.assume_init();
21318 assert_eq!(counts.substations, 1);
21319 assert_eq!(counts.voltage_levels, 3);
21320 assert_eq!(counts.terminals, 3);
21321 assert_eq!(counts.operational_limit_groups, 1);
21322 assert_eq!(counts.tap_changers, 1);
21323
21324 let mut substation = std::mem::MaybeUninit::<PioSubstationView>::uninit();
21325 assert!(pio_detailed_connectivity_substation_at(
21326 details,
21327 0,
21328 substation.as_mut_ptr(),
21329 &mut error,
21330 ));
21331 let substation = substation.assume_init();
21332 assert_eq!(view_text(substation.component.local_id), "S");
21333 assert_eq!(substation.geographical_tag_count, 1);
21334
21335 let mut level = std::mem::MaybeUninit::<PioVoltageLevelView>::uninit();
21336 assert!(pio_detailed_connectivity_voltage_level_at(
21337 details,
21338 0,
21339 level.as_mut_ptr(),
21340 &mut error,
21341 ));
21342 let level = level.assume_init();
21343 assert_eq!(view_text(level.topology_kind), "bus_breaker");
21344 assert_eq!(level.nominal_voltage_kv, 132.0);
21345
21346 let mut terminal = std::mem::MaybeUninit::<PioDetailedTerminalView>::uninit();
21347 assert!(pio_detailed_connectivity_terminal_at(
21348 details,
21349 0,
21350 terminal.as_mut_ptr(),
21351 &mut error,
21352 ));
21353 let terminal = terminal.assume_init();
21354 assert!(terminal.connected);
21355 assert!(!terminal.has_component);
21356
21357 let mut limit = std::mem::MaybeUninit::<PioOperationalLimitGroupView>::uninit();
21358 assert!(pio_detailed_connectivity_operational_limit_group_at(
21359 details,
21360 0,
21361 limit.as_mut_ptr(),
21362 &mut error,
21363 ));
21364 let limit = limit.assume_init();
21365 assert!(limit.has_apparent_power_limits);
21366 assert_eq!(limit.apparent_power_permanent_limit_mva, 90.0);
21367 assert_eq!(limit.apparent_power_temporary_limit_count, 1);
21368
21369 let mut changer = std::mem::MaybeUninit::<PioTapChangerView>::uninit();
21370 assert!(pio_detailed_connectivity_tap_changer_at(
21371 details,
21372 0,
21373 changer.as_mut_ptr(),
21374 &mut error,
21375 ));
21376 let changer = changer.assume_init();
21377 assert!(!changer.has_component);
21378 assert_eq!(view_text(changer.kind), "ratio");
21379 assert!(changer.has_tap_position);
21380 assert_eq!(changer.tap_position, 0);
21381 assert_eq!(changer.step_count, 1);
21382
21383 pio_detailed_connectivity_release(details);
21384
21385 let module = module_handle(powerio::PioModule::new(PioValue::BalancedNetwork(
21386 identified_network,
21387 )));
21388 let value = pio_module_value(module);
21389 let network = pio_value_balanced_network(value, &mut error);
21390 let details = pio_balanced_network_detailed_connectivity(network);
21391 pio_value_release(value);
21392 pio_module_release(module);
21393 pio_balanced_network_release(network);
21394
21395 let mut terminal = std::mem::MaybeUninit::<PioDetailedTerminalView>::uninit();
21396 assert!(pio_detailed_connectivity_terminal_at(
21397 details,
21398 0,
21399 terminal.as_mut_ptr(),
21400 &mut error,
21401 ));
21402 let terminal = terminal.assume_init();
21403 assert!(terminal.has_component);
21404 assert_eq!(view_text(terminal.component.component_type), "terminal");
21405 assert_eq!(view_text(terminal.component.local_id), "terminal-T3-1");
21406
21407 let mut changer = std::mem::MaybeUninit::<PioTapChangerView>::uninit();
21408 assert!(pio_detailed_connectivity_tap_changer_at(
21409 details,
21410 0,
21411 changer.as_mut_ptr(),
21412 &mut error,
21413 ));
21414 let changer = changer.assume_init();
21415 assert!(changer.has_component);
21416 assert_eq!(view_text(changer.component.component_type), "tap_changer");
21417 assert_eq!(view_text(changer.component.local_id), "tap-T3-2");
21418 pio_detailed_connectivity_release(details);
21419
21420 let without_assigned_tap = HIERARCHY.replace(r#" tapPosition="0""#, "");
21421 let module = parse_xiidm_text(&without_assigned_tap);
21422 let value = pio_module_value(module);
21423 let network = pio_value_balanced_network(value, &mut error);
21424 let details = pio_balanced_network_detailed_connectivity(network);
21425 let mut changer = std::mem::MaybeUninit::<PioTapChangerView>::uninit();
21426 assert!(pio_detailed_connectivity_tap_changer_at(
21427 details,
21428 0,
21429 changer.as_mut_ptr(),
21430 &mut error,
21431 ));
21432 let changer = changer.assume_init();
21433 assert!(!changer.has_tap_position);
21434 assert_eq!(changer.tap_position, 0);
21435 pio_detailed_connectivity_release(details);
21436 pio_balanced_network_release(network);
21437 pio_value_release(value);
21438 pio_module_release(module);
21439 }
21440 }
21441
21442 #[test]
21443 fn subnetworks_boundary_lines_and_tie_lines_are_owner_rooted_views() {
21444 const XIIDM: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
21445<iidm:network xmlns:iidm="http://www.powsybl.org/schema/iidm/1_17" id="Merged" caseDate="2026-01-01T00:00:00Z" forecastDistance="0" sourceFormat="root" minimumValidationLevel="STEADY_STATE_HYPOTHESIS">
21446 <iidm:network id="A" caseDate="2026-01-01T01:00:00Z" forecastDistance="1" sourceFormat="part-a" minimumValidationLevel="STEADY_STATE_HYPOTHESIS">
21447 <iidm:substation id="SA"><iidm:voltageLevel id="VA" nominalV="100" topologyKind="BUS_BREAKER"><iidm:busBreakerTopology><iidm:bus id="BA"/></iidm:busBreakerTopology><iidm:boundaryLine id="DLA" p0="5" q0="6" r="1" x="2" generationVoltageRegulationOn="true" generationMinP="0" generationMaxP="20" generationTargetP="10" generationTargetV="100" bus="BA" connectableBus="BA"><iidm:reactiveCapabilityCurve><iidm:property name="owner" value="RTE"/><iidm:point p="0" minQ="-10" maxQ="10"/><iidm:point p="10" minQ="0" maxQ="20"/></iidm:reactiveCapabilityCurve></iidm:boundaryLine></iidm:voltageLevel></iidm:substation>
21448 </iidm:network>
21449 <iidm:network id="B" caseDate="2026-01-01T02:00:00Z" forecastDistance="2" sourceFormat="part-b" minimumValidationLevel="STEADY_STATE_HYPOTHESIS">
21450 <iidm:substation id="SB"><iidm:voltageLevel id="VB" nominalV="100" topologyKind="BUS_BREAKER"><iidm:busBreakerTopology><iidm:bus id="BB"/></iidm:busBreakerTopology><iidm:boundaryLine id="DLB" p0="-5" q0="-6" r="3" x="4" bus="BB" connectableBus="BB"/></iidm:voltageLevel></iidm:substation>
21451 </iidm:network>
21452 <iidm:tieLine id="TL" boundaryLineId1="DLA" boundaryLineId2="DLB"/>
21453</iidm:network>"#;
21454
21455 unsafe {
21456 let module = parse_xiidm_text(XIIDM);
21457 let value = pio_module_value(module);
21458 let mut error = std::ptr::null_mut();
21459 let network = pio_value_balanced_network(value, &mut error);
21460 let details = pio_balanced_network_detailed_connectivity(network);
21461 pio_value_release(value);
21462 pio_module_release(module);
21463 pio_balanced_network_release(network);
21464
21465 let mut counts = std::mem::MaybeUninit::<PioDetailedConnectivityCountsView>::uninit();
21466 assert!(pio_detailed_connectivity_counts(
21467 details,
21468 counts.as_mut_ptr(),
21469 &mut error,
21470 ));
21471 let counts = counts.assume_init();
21472 assert_eq!(counts.subnetworks, 2);
21473 assert_eq!(counts.boundary_lines, 2);
21474 assert_eq!(counts.tie_lines, 1);
21475
21476 let mut subnetwork = std::mem::MaybeUninit::<PioSubnetworkView>::uninit();
21477 assert!(pio_detailed_connectivity_subnetwork_at(
21478 details,
21479 0,
21480 subnetwork.as_mut_ptr(),
21481 &mut error,
21482 ));
21483 let subnetwork = subnetwork.assume_init();
21484 assert_eq!(view_text(subnetwork.component.local_id), "A");
21485 assert_eq!(view_text(subnetwork.parent.local_id), "Merged");
21486 assert!(subnetwork.case_metadata.has_forecast_distance);
21487 assert_eq!(subnetwork.case_metadata.forecast_distance, 1);
21488 assert_eq!(
21489 view_text(subnetwork.case_metadata.source_model_format),
21490 "part-a"
21491 );
21492 assert!(subnetwork.component_count >= 4);
21493
21494 let mut member = std::mem::MaybeUninit::<PioComponentIdView>::uninit();
21495 assert!(pio_detailed_connectivity_subnetwork_component_at(
21496 details,
21497 0,
21498 0,
21499 member.as_mut_ptr(),
21500 &mut error,
21501 ));
21502 assert_ne!(view_text(member.assume_init().local_id), "");
21503
21504 let mut boundary = std::mem::MaybeUninit::<PioBoundaryLineView>::uninit();
21505 assert!(pio_detailed_connectivity_boundary_line_at(
21506 details,
21507 0,
21508 boundary.as_mut_ptr(),
21509 &mut error,
21510 ));
21511 let boundary = boundary.assume_init();
21512 assert_eq!(view_text(boundary.component.local_id), "DLA");
21513 assert_eq!(boundary.active_power_setpoint_mw, 5.0);
21514 assert!(boundary.has_generation);
21515 assert!(boundary.generation.voltage_regulation_on);
21516 assert_eq!(boundary.generation.target_active_power_mw, 10.0);
21517 assert!(boundary.generation.has_reactive_limits);
21518 assert_eq!(
21519 view_text(boundary.generation.reactive_limits.kind),
21520 "capability_curve"
21521 );
21522 assert_eq!(boundary.generation.reactive_limits.point_count, 2);
21523
21524 let mut property = std::mem::MaybeUninit::<PioStringPropertyView>::uninit();
21525 assert!(
21526 pio_detailed_connectivity_boundary_line_reactive_limit_property_at(
21527 details,
21528 0,
21529 0,
21530 property.as_mut_ptr(),
21531 &mut error,
21532 )
21533 );
21534 let property = property.assume_init();
21535 assert_eq!(view_text(property.name), "owner");
21536 assert_eq!(view_text(property.value), "RTE");
21537
21538 let mut point = std::mem::MaybeUninit::<PioReactiveCapabilityCurvePointView>::uninit();
21539 assert!(
21540 pio_detailed_connectivity_boundary_line_reactive_capability_point_at(
21541 details,
21542 0,
21543 1,
21544 point.as_mut_ptr(),
21545 &mut error,
21546 )
21547 );
21548 let point = point.assume_init();
21549 assert_eq!(point.active_power_mw, 10.0);
21550 assert_eq!(point.maximum_reactive_power_mvar, 20.0);
21551
21552 let mut tie = std::mem::MaybeUninit::<PioTieLineView>::uninit();
21553 assert!(pio_detailed_connectivity_tie_line_at(
21554 details,
21555 0,
21556 tie.as_mut_ptr(),
21557 &mut error,
21558 ));
21559 let tie = tie.assume_init();
21560 assert_eq!(view_text(tie.component.local_id), "TL");
21561 assert_eq!(view_text(tie.boundary_line1.local_id), "DLA");
21562 assert_eq!(view_text(tie.boundary_line2.local_id), "DLB");
21563 assert!(tie.has_calculation_branch);
21564
21565 pio_detailed_connectivity_release(details);
21566 }
21567 }
21568
21569 #[test]
21570 fn connectivity_node_numbers_are_optional_borrowed_fields() {
21571 const XIIDM: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
21572<iidm:network xmlns:iidm="http://www.powsybl.org/schema/iidm/1_17" id="nodes" caseDate="2025-01-01T00:00:00Z" forecastDistance="0" sourceFormat="test" minimumValidationLevel="STEADY_STATE_HYPOTHESIS">
21573 <iidm:substation id="S">
21574 <iidm:voltageLevel id="VL" nominalV="110" topologyKind="NODE_BREAKER">
21575 <iidm:nodeBreakerTopology>
21576 <iidm:bus v="110" angle="0" nodes="0,1,2"/>
21577 <iidm:busbarSection id="BBS" node="2"/>
21578 <iidm:switch id="BR" kind="BREAKER" open="false" node1="1" node2="2"/>
21579 <iidm:internalConnection node1="0" node2="1"/>
21580 </iidm:nodeBreakerTopology>
21581 <iidm:generator id="G" energySource="OTHER" minP="0" maxP="10" voltageRegulatorOn="true" targetP="5" node="0"><iidm:minMaxReactiveLimits minQ="-2" maxQ="2"/></iidm:generator>
21582 </iidm:voltageLevel>
21583 </iidm:substation>
21584</iidm:network>"#;
21585
21586 unsafe {
21587 let module = parse_xiidm_text(XIIDM);
21588 let value = pio_module_value(module);
21589 let mut error = std::ptr::null_mut();
21590 let network = pio_value_balanced_network(value, &mut error);
21591 let details = pio_balanced_network_detailed_connectivity(network);
21592
21593 let mut numbers = Vec::new();
21594 for index in 0..3 {
21595 let mut node = std::mem::MaybeUninit::<PioConnectivityNodeView>::uninit();
21596 assert!(pio_detailed_connectivity_node_at(
21597 details,
21598 index,
21599 node.as_mut_ptr(),
21600 &mut error,
21601 ));
21602 let node = node.assume_init();
21603 assert!(node.has_node_number);
21604 assert!(node.has_calculated_bus);
21605 numbers.push(node.node_number);
21606 }
21607 numbers.sort_unstable();
21608 assert_eq!(numbers, [0, 1, 2]);
21609
21610 pio_detailed_connectivity_release(details);
21611 pio_balanced_network_release(network);
21612 pio_value_release(value);
21613 pio_module_release(module);
21614 }
21615 }
21616
21617 #[test]
21618 fn omitted_fields_and_equipment_reactive_limits_are_typed_views() {
21619 const XIIDM: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
21620<iidm:network xmlns:iidm="http://www.powsybl.org/schema/iidm/equipment/1_12" id="equipment" caseDate="2021-01-03T00:00:00Z" forecastDistance="0" sourceFormat="test" minimumValidationLevel="EQUIPMENT">
21621 <iidm:substation id="S"><iidm:voltageLevel id="VL" nominalV="225" topologyKind="NODE_BREAKER">
21622 <iidm:nodeBreakerTopology><iidm:busbarSection id="BBS" node="0"/></iidm:nodeBreakerTopology>
21623 <iidm:generator id="G" energySource="SOLAR" minP="0" maxP="100" voltageRegulatorOn="true" node="0">
21624 <iidm:reactiveCapabilityCurve><iidm:property name="curve" value="retained"/><iidm:point p="0" minQ="-20" maxQ="20"><iidm:property name="point" value="first"/></iidm:point><iidm:point p="100" minQ="-10" maxQ="10"/></iidm:reactiveCapabilityCurve>
21625 </iidm:generator>
21626 </iidm:voltageLevel></iidm:substation>
21627</iidm:network>"#;
21628
21629 unsafe {
21630 let module = parse_xiidm_text(XIIDM);
21631 let value = pio_module_value(module);
21632 let mut error = std::ptr::null_mut();
21633 let network = pio_value_balanced_network(value, &mut error);
21634 let details = pio_balanced_network_detailed_connectivity(network);
21635
21636 let mut counts = std::mem::MaybeUninit::<PioDetailedConnectivityCountsView>::uninit();
21637 assert!(pio_detailed_connectivity_counts(
21638 details,
21639 counts.as_mut_ptr(),
21640 &mut error,
21641 ));
21642 let counts = counts.assume_init();
21643 assert_eq!(counts.omitted_fields, 4);
21644 assert_eq!(counts.equipment_reactive_limits, 1);
21645
21646 let mut omitted = std::mem::MaybeUninit::<PioOmittedFieldView>::uninit();
21647 assert!(pio_detailed_connectivity_omitted_field_at(
21648 details,
21649 2,
21650 omitted.as_mut_ptr(),
21651 &mut error,
21652 ));
21653 let omitted = omitted.assume_init();
21654 assert_eq!(view_text(omitted.component.local_id), "G");
21655 assert_eq!(view_text(omitted.field), "voltage_setpoint");
21656
21657 let mut limits = std::mem::MaybeUninit::<PioEquipmentReactiveLimitsView>::uninit();
21658 assert!(pio_detailed_connectivity_equipment_reactive_limits_at(
21659 details,
21660 0,
21661 limits.as_mut_ptr(),
21662 &mut error,
21663 ));
21664 let limits = limits.assume_init();
21665 assert_eq!(view_text(limits.equipment.local_id), "G");
21666 assert_eq!(view_text(limits.limits.kind), "capability_curve");
21667 assert_eq!(limits.limits.property_count, 1);
21668 assert_eq!(limits.limits.point_count, 2);
21669
21670 let mut point = std::mem::MaybeUninit::<PioReactiveCapabilityCurvePointView>::uninit();
21671 assert!(
21672 pio_detailed_connectivity_equipment_reactive_capability_point_at(
21673 details,
21674 0,
21675 0,
21676 point.as_mut_ptr(),
21677 &mut error,
21678 )
21679 );
21680 let point = point.assume_init();
21681 assert_eq!(point.minimum_reactive_power_mvar, -20.0);
21682 assert_eq!(point.property_count, 1);
21683
21684 let mut property = std::mem::MaybeUninit::<PioStringPropertyView>::uninit();
21685 assert!(
21686 pio_detailed_connectivity_equipment_reactive_capability_point_property_at(
21687 details,
21688 0,
21689 0,
21690 0,
21691 property.as_mut_ptr(),
21692 &mut error,
21693 )
21694 );
21695 let property = property.assume_init();
21696 assert_eq!(view_text(property.name), "point");
21697 assert_eq!(view_text(property.value), "first");
21698
21699 let mut generator = std::mem::MaybeUninit::<PioBalancedGeneratorView>::uninit();
21700 assert!(pio_balanced_network_generator_at(
21701 network,
21702 0,
21703 generator.as_mut_ptr(),
21704 &mut error,
21705 ));
21706 assert_eq!(view_text(generator.assume_init().energy_source), "solar");
21707
21708 pio_detailed_connectivity_release(details);
21709 pio_balanced_network_release(network);
21710 pio_value_release(value);
21711 pio_module_release(module);
21712 }
21713 }
21714
21715 #[test]
21716 fn detailed_dc_and_converter_records_are_typed_views() {
21717 const DC: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
21718<iidm:network xmlns:iidm="http://www.powsybl.org/schema/iidm/1_17" id="dc" caseDate="2026-01-01T00:00:00Z" forecastDistance="0" sourceFormat="test" minimumValidationLevel="STEADY_STATE_HYPOTHESIS">
21719 <iidm:dcNode id="N1" nominalV="500" v="498"/><iidm:dcNode id="N2" nominalV="500"/>
21720 <iidm:dcSwitch id="S" dcNode1="N1" dcNode2="N2" kind="DISCONNECTOR" open="true" r="0.9"/>
21721 <iidm:dcGround id="G" dcNode="N1" r="0.1" connected="false"/>
21722 <iidm:dcLine id="L" dcNode1="N1" dcNode2="N2" r="4" connected1="true" connected2="true" dcP1="100" dcI1="200" dcP2="-98" dcI2="-195"/>
21723 <iidm:substation id="SUB"><iidm:voltageLevel id="VL" nominalV="400" topologyKind="BUS_BREAKER">
21724 <iidm:busBreakerTopology><iidm:bus id="B1"/><iidm:bus id="B2"/></iidm:busBreakerTopology>
21725 <iidm:voltageSourceConverter id="VSC" dcNode1="N1" dcConnected1="true" dcNode2="N2" dcConnected2="false" idleLoss="2" switchingLoss="0.2" resistiveLoss="0.000002" controlMode="P_PCC_DROOP" targetP="301" targetVdc="502" bus1="B1" connectableBus1="B1" bus2="B2" connectableBus2="B2" voltageRegulatorOn="true" voltageSetpoint="397"><iidm:pccTerminal id="VSC" number="ONE"/><iidm:droopCurve><iidm:segment minV="-100" maxV="100" k="-5"/></iidm:droopCurve><iidm:reactiveCapabilityCurve><iidm:property name="curve" value="retained"/><iidm:point p="-200" minQ="-190" maxQ="192"><iidm:property name="point" value="one"/></iidm:point><iidm:point p="200" minQ="-189" maxQ="191"/></iidm:reactiveCapabilityCurve></iidm:voltageSourceConverter>
21726 </iidm:voltageLevel></iidm:substation>
21727</iidm:network>"#;
21728
21729 unsafe {
21730 let module = parse_xiidm_text(DC);
21731 let value = pio_module_value(module);
21732 let mut error = std::ptr::null_mut();
21733 let network = pio_value_balanced_network(value, &mut error);
21734 let details = pio_balanced_network_detailed_connectivity(network);
21735
21736 let mut counts = std::mem::MaybeUninit::<PioDetailedConnectivityCountsView>::uninit();
21737 assert!(pio_detailed_connectivity_counts(
21738 details,
21739 counts.as_mut_ptr(),
21740 &mut error,
21741 ));
21742 let counts = counts.assume_init();
21743 assert_eq!(counts.dc_nodes, 2);
21744 assert_eq!(counts.dc_grounds, 1);
21745 assert_eq!(counts.dc_lines, 1);
21746 assert_eq!(counts.dc_switches, 1);
21747 assert_eq!(counts.voltage_source_converters, 1);
21748
21749 let mut line = std::mem::MaybeUninit::<PioDcEquipmentView>::uninit();
21750 assert!(pio_detailed_connectivity_dc_line_at(
21751 details,
21752 0,
21753 line.as_mut_ptr(),
21754 &mut error,
21755 ));
21756 let line = line.assume_init();
21757 assert_eq!(view_text(line.kind), "line");
21758 assert_eq!(line.terminal_count, 2);
21759 assert_eq!(line.resistance_ohm, 4.0);
21760 assert_eq!(line.terminal1.current_a, 200.0);
21761
21762 let mut converter = std::mem::MaybeUninit::<PioAcDcConverterView>::uninit();
21763 assert!(pio_detailed_connectivity_voltage_source_converter_at(
21764 details,
21765 0,
21766 converter.as_mut_ptr(),
21767 &mut error,
21768 ));
21769 let converter = converter.assume_init();
21770 assert_eq!(view_text(converter.kind), "voltage_source");
21771 assert_eq!(
21772 view_text(converter.control_mode),
21773 "active_power_at_pcc_and_dc_voltage_droop_curve"
21774 );
21775 assert!(converter.voltage_regulator_on);
21776 assert_eq!(converter.idle_loss_mw, 2.0);
21777 assert!(converter.has_switching_loss);
21778 assert_eq!(converter.switching_loss_mw_per_ampere, 0.2);
21779 assert!(converter.has_resistive_loss);
21780 assert!(converter.has_pcc_terminal);
21781 assert_eq!(converter.pcc_terminal.terminal, 1);
21782 assert!(converter.has_reactive_limits);
21783 assert_eq!(
21784 view_text(converter.reactive_limits.kind),
21785 "capability_curve"
21786 );
21787 assert_eq!(converter.droop_curve_segment_count, 1);
21788 assert!(converter.has_droop_curve);
21789
21790 let mut segment = std::mem::MaybeUninit::<PioDroopCurveSegmentView>::uninit();
21791 assert!(
21792 pio_detailed_connectivity_voltage_source_converter_droop_curve_segment_at(
21793 details,
21794 0,
21795 0,
21796 segment.as_mut_ptr(),
21797 &mut error,
21798 )
21799 );
21800 let segment = segment.assume_init();
21801 assert_eq!(segment.minimum_voltage_kv, -100.0);
21802 assert_eq!(segment.maximum_voltage_kv, 100.0);
21803 assert_eq!(segment.k, -5.0);
21804
21805 let mut point = std::mem::MaybeUninit::<PioReactiveCapabilityCurvePointView>::uninit();
21806 assert!(
21807 pio_detailed_connectivity_voltage_source_converter_reactive_capability_point_at(
21808 details,
21809 0,
21810 0,
21811 point.as_mut_ptr(),
21812 &mut error,
21813 )
21814 );
21815 let point = point.assume_init();
21816 assert_eq!(point.active_power_mw, -200.0);
21817 assert_eq!(point.property_count, 1);
21818
21819 let base_network = match &PioModule::get(module).unwrap().module.value() {
21820 PioValue::BalancedNetwork(network) => network.clone(),
21821 value => panic!("expected balanced network, got {}", value.type_name()),
21822 };
21823
21824 pio_detailed_connectivity_release(details);
21825 pio_balanced_network_release(network);
21826 pio_value_release(value);
21827 pio_module_release(module);
21828
21829 let mut empty_curve_network = base_network.clone();
21830 std::sync::Arc::make_mut(
21831 empty_curve_network
21832 .detailed_connectivity_mut()
21833 .as_mut()
21834 .unwrap(),
21835 )
21836 .voltage_source_converters[0]
21837 .droop_curve = Some(serde_json::from_str(r#"{"segments":[]}"#).unwrap());
21838 let mut absent_curve_network = base_network;
21839 std::sync::Arc::make_mut(
21840 absent_curve_network
21841 .detailed_connectivity_mut()
21842 .as_mut()
21843 .unwrap(),
21844 )
21845 .voltage_source_converters[0]
21846 .droop_curve = None;
21847
21848 for (network, has_droop_curve) in
21849 [(empty_curve_network, true), (absent_curve_network, false)]
21850 {
21851 let module = module_handle(powerio::PioModule::new(PioValue::from(network)));
21852 let value = pio_module_value(module);
21853 let network = pio_value_balanced_network(value, &mut error);
21854 let details = pio_balanced_network_detailed_connectivity(network);
21855 let mut converter = std::mem::MaybeUninit::<PioAcDcConverterView>::uninit();
21856 assert!(pio_detailed_connectivity_voltage_source_converter_at(
21857 details,
21858 0,
21859 converter.as_mut_ptr(),
21860 &mut error,
21861 ));
21862 let converter = converter.assume_init();
21863 assert_eq!(converter.droop_curve_segment_count, 0);
21864 assert_eq!(converter.has_droop_curve, has_droop_curve);
21865 pio_detailed_connectivity_release(details);
21866 pio_balanced_network_release(network);
21867 pio_value_release(value);
21868 pio_module_release(module);
21869 }
21870 }
21871 }
21872
21873 #[test]
21874 fn balanced_calculation_instances_expose_typed_inputs() {
21875 unsafe {
21876 let network = case9_network();
21877 let dc_pf = powerio_prob::DcPfInstance::from_network(network.clone()).unwrap();
21878 let dc_pf_module =
21879 module_handle(powerio::PioModule::new(PioValue::DcPfInstance(dc_pf)));
21880 let dc_pf_value = pio_module_value(dc_pf_module);
21881 let mut error = std::ptr::null_mut();
21882 let dc_pf = pio_value_dc_pf_instance(dc_pf_value, &mut error);
21883 assert!(!dc_pf.is_null(), "{}", error_text(error));
21884 assert_eq!(pio_dc_pf_instance_bus_specification_count(dc_pf), 9);
21885 assert_eq!(
21886 view_text(pio_dc_pf_instance_branch_susceptance_formula(dc_pf)),
21887 "series_susceptance"
21888 );
21889 assert!(!pio_calculation_instance_has_initial_point(dc_pf));
21890 assert!(pio_calculation_instance_initial_point(dc_pf, &mut error).is_null());
21891 assert!(error.is_null());
21892
21893 let mut reference = std::mem::MaybeUninit::<PioDcBusSpecificationView>::uninit();
21894 assert!(pio_dc_pf_instance_bus_specification_at(
21895 dc_pf,
21896 0,
21897 reference.as_mut_ptr(),
21898 &mut error,
21899 ));
21900 let reference = reference.assume_init();
21901 assert_eq!(reference.bus_id, 1);
21902 assert_eq!(view_text(reference.kind), "reference");
21903 assert_eq!(reference.voltage_angle_degrees, 0.0);
21904
21905 let mut load_bus = std::mem::MaybeUninit::<PioDcBusSpecificationView>::uninit();
21906 assert!(pio_dc_pf_instance_bus_specification_at(
21907 dc_pf,
21908 4,
21909 load_bus.as_mut_ptr(),
21910 &mut error,
21911 ));
21912 let load_bus = load_bus.assume_init();
21913 assert_eq!(load_bus.bus_id, 5);
21914 assert_eq!(view_text(load_bus.kind), "net_active_power");
21915 assert_eq!(load_bus.net_active_power_mw, -90.0);
21916
21917 pio_calculation_instance_release(dc_pf);
21918 pio_value_release(dc_pf_value);
21919 pio_module_release(dc_pf_module);
21920
21921 let initial = powerio_prob::BalancedOperatingPointBuilder::for_point(network.clone())
21922 .bus_voltage_magnitudes(vec![1.01; network.buses().len()])
21923 .build_point()
21924 .unwrap();
21925 let dc_pf = powerio_prob::DcPfInstance::from_network(network.clone())
21926 .unwrap()
21927 .with_initial_point(initial);
21928 let dc_pf_module =
21929 module_handle(powerio::PioModule::new(PioValue::DcPfInstance(dc_pf)));
21930 let dc_pf_value = pio_module_value(dc_pf_module);
21931 let dc_pf = pio_value_dc_pf_instance(dc_pf_value, &mut error);
21932 let initial = pio_calculation_instance_initial_point(dc_pf, &mut error);
21933 assert!(!initial.is_null(), "{}", error_text(error));
21934 assert_eq!(
21935 view_text(pio_operating_point_type_name(initial)),
21936 "powerio.OperatingPoint<powerio.BalancedNetwork>"
21937 );
21938 pio_calculation_instance_release(dc_pf);
21939 pio_value_release(dc_pf_value);
21940 pio_module_release(dc_pf_module);
21941 let mut initial_vm = 0.0;
21942 assert!(pio_operating_point_get_value(
21943 initial,
21944 c"bus_voltage_magnitude".as_ptr(),
21945 "bus_voltage_magnitude".len(),
21946 c"1".as_ptr(),
21947 1,
21948 &mut initial_vm,
21949 &mut error,
21950 ));
21951 assert_eq!(initial_vm, 1.01);
21952 let initial_network = pio_operating_point_balanced_network(initial, &mut error);
21953 assert_eq!(pio_balanced_network_bus_count(initial_network), 9);
21954 pio_balanced_network_release(initial_network);
21955 pio_operating_point_release(initial);
21956
21957 let ac_pf = powerio_prob::AcPfInstance::from_network(network.clone()).unwrap();
21958 let ac_pf_module =
21959 module_handle(powerio::PioModule::new(PioValue::AcPfInstance(ac_pf)));
21960 let ac_pf_value = pio_module_value(ac_pf_module);
21961 let ac_pf = pio_value_ac_pf_instance(ac_pf_value, &mut error);
21962 let mut pv = std::mem::MaybeUninit::<PioAcBusSpecificationView>::uninit();
21963 assert!(pio_ac_pf_instance_bus_specification_at(
21964 ac_pf,
21965 1,
21966 pv.as_mut_ptr(),
21967 &mut error,
21968 ));
21969 let pv = pv.assume_init();
21970 assert_eq!(pv.bus_id, 2);
21971 assert_eq!(view_text(pv.kind), "pv");
21972 assert_eq!(pv.net_active_power_mw, 163.0);
21973 assert_eq!(pv.voltage_magnitude_pu, 1.025);
21974 pio_calculation_instance_release(ac_pf);
21975 pio_value_release(ac_pf_value);
21976 pio_module_release(ac_pf_module);
21977
21978 let dc_opf = powerio_prob::DcOpfInstance::from_network(network).unwrap();
21979 let dc_opf_module =
21980 module_handle(powerio::PioModule::new(PioValue::DcOpfInstance(dc_opf)));
21981 let dc_opf_value = pio_module_value(dc_opf_module);
21982 let dc_opf = pio_value_dc_opf_instance(dc_opf_value, &mut error);
21983 assert_eq!(pio_calculation_instance_objective_term_count(dc_opf), 1);
21984 let mut term = std::mem::MaybeUninit::<PioObjectiveTermView>::uninit();
21985 assert!(pio_calculation_instance_objective_term_at(
21986 dc_opf,
21987 0,
21988 term.as_mut_ptr(),
21989 &mut error,
21990 ));
21991 assert_eq!(view_text(term.assume_init().kind), "network_generator_cost");
21992 assert_eq!(pio_calculation_instance_active_constraint_count(dc_opf), 4);
21993 let mut constraint = std::mem::MaybeUninit::<PioActiveConstraintView>::uninit();
21994 assert!(pio_calculation_instance_active_constraint_at(
21995 dc_opf,
21996 2,
21997 constraint.as_mut_ptr(),
21998 &mut error,
21999 ));
22000 let constraint = constraint.assume_init();
22001 assert_eq!(view_text(constraint.family), "thermal_limits");
22002 assert_eq!(view_text(constraint.selection), "all");
22003 assert_eq!(constraint.identity_count, 0);
22004
22005 pio_calculation_instance_release(dc_opf);
22006 pio_value_release(dc_opf_value);
22007 pio_module_release(dc_opf_module);
22008 }
22009 }
22010
22011 #[test]
22012 fn dc_opf_preparation_rows_keep_their_owner_alive() {
22013 unsafe {
22014 let source_module = parse_case9();
22015 let mut error = std::ptr::null_mut();
22016 let instance_module = pio_module_to_dc_opf_instance(source_module, &mut error);
22017 assert!(!instance_module.is_null(), "{}", error_text(error));
22018 let value = pio_module_value(instance_module);
22019 let instance = pio_value_dc_opf_instance(value, &mut error);
22020 assert!(!instance.is_null(), "{}", error_text(error));
22021 let units = "per_unit";
22022 let preparation = pio_build_dc_opf_preparation(
22023 instance,
22024 units.as_ptr().cast(),
22025 units.len(),
22026 false,
22027 false,
22028 true,
22029 &mut error,
22030 );
22031 assert!(!preparation.is_null(), "{}", error_text(error));
22032
22033 pio_calculation_instance_release(instance);
22034 pio_value_release(value);
22035 pio_module_release(instance_module);
22036 pio_module_release(source_module);
22037
22038 let mut summary = std::mem::MaybeUninit::<PioDcOpfPreparationView>::uninit();
22039 assert!(pio_dc_opf_preparation_summary(
22040 preparation,
22041 summary.as_mut_ptr(),
22042 &mut error,
22043 ));
22044 let summary = summary.assume_init();
22045 assert_eq!(summary.bus_count, 9);
22046 assert_eq!(summary.generator_count, 3);
22047 assert_eq!(summary.branch_count, 9);
22048 assert_eq!(summary.base_mva, 100.0);
22049 assert_eq!(view_text(summary.units), "per_unit");
22050 assert!(summary.correct_angle_difference_bounds);
22051 assert_eq!(
22052 view_text(summary.branch_susceptance_formula),
22053 "series_susceptance"
22054 );
22055 assert_eq!(view_text(summary.objective), "network_generator_cost");
22056
22057 let reference = pio_dc_opf_preparation_reference_buses(preparation);
22058 assert_eq!(
22059 std::slice::from_raw_parts(reference.data, reference.len),
22060 &[0]
22061 );
22062
22063 let mut bus = std::mem::MaybeUninit::<PioDcOpfBusView>::uninit();
22064 assert!(pio_dc_opf_preparation_bus_at(
22065 preparation,
22066 4,
22067 bus.as_mut_ptr(),
22068 &mut error,
22069 ));
22070 let bus = bus.assume_init();
22071 assert_eq!(bus.bus_id, 5);
22072 assert_eq!(bus.active_power_demand, 0.9);
22073 assert_eq!(bus.shunt_conductance, 0.0);
22074 assert_eq!(bus.phase_shift_injection, 0.0);
22075
22076 let mut generator = std::mem::MaybeUninit::<PioDcOpfGeneratorView>::uninit();
22077 assert!(pio_dc_opf_preparation_generator_at(
22078 preparation,
22079 0,
22080 generator.as_mut_ptr(),
22081 &mut error,
22082 ));
22083 let generator = generator.assume_init();
22084 assert_eq!(generator.bus_index, 0);
22085 assert_eq!(generator.quadratic_cost, 2200.0);
22086 assert!(!generator.has_piecewise_linear_cost);
22087
22088 let mut branch = std::mem::MaybeUninit::<PioDcOpfBranchView>::uninit();
22089 assert!(pio_dc_opf_preparation_branch_at(
22090 preparation,
22091 0,
22092 branch.as_mut_ptr(),
22093 &mut error,
22094 ));
22095 let branch = branch.assume_init();
22096 assert_eq!(branch.from_bus_index, 0);
22097 assert_eq!(branch.to_bus_index, 3);
22098 assert!(branch.susceptance_magnitude > 0.0);
22099 assert_eq!(view_text(branch.source_kind), "branch");
22100 assert_eq!(branch.source_row, 0);
22101 assert!(!branch.has_winding);
22102
22103 let mut missing = std::mem::MaybeUninit::<PioDcOpfBusView>::uninit();
22104 assert!(!pio_dc_opf_preparation_bus_at(
22105 preparation,
22106 summary.bus_count,
22107 missing.as_mut_ptr(),
22108 &mut error,
22109 ));
22110 assert_eq!(
22111 view_text(pio_error_code(error)),
22112 "BIND.CAPI.INDEX_OUT_OF_RANGE"
22113 );
22114 pio_error_release(error);
22115 pio_dc_opf_preparation_release(preparation);
22116 }
22117 }
22118
22119 #[test]
22120 fn ac_opf_preparation_rows_keep_their_owner_alive() {
22121 unsafe {
22122 let source_module = parse_case9();
22123 let mut error = std::ptr::null_mut();
22124 let instance_module = pio_module_to_ac_opf_instance(source_module, &mut error);
22125 assert!(!instance_module.is_null(), "{}", error_text(error));
22126 let value = pio_module_value(instance_module);
22127 let instance = pio_value_ac_opf_instance(value, &mut error);
22128 assert!(!instance.is_null(), "{}", error_text(error));
22129 let units = "per_unit";
22130 let preparation = pio_build_ac_opf_preparation(
22131 instance,
22132 units.as_ptr().cast(),
22133 units.len(),
22134 false,
22135 false,
22136 true,
22137 &mut error,
22138 );
22139 assert!(!preparation.is_null(), "{}", error_text(error));
22140
22141 pio_calculation_instance_release(instance);
22142 pio_value_release(value);
22143 pio_module_release(instance_module);
22144 pio_module_release(source_module);
22145
22146 let mut summary = std::mem::MaybeUninit::<PioAcOpfPreparationView>::uninit();
22147 assert!(pio_ac_opf_preparation_summary(
22148 preparation,
22149 summary.as_mut_ptr(),
22150 &mut error,
22151 ));
22152 let summary = summary.assume_init();
22153 assert_eq!(summary.bus_count, 9);
22154 assert_eq!(summary.generator_count, 3);
22155 assert_eq!(summary.branch_count, 9);
22156 assert_eq!(summary.base_mva, 100.0);
22157 assert_eq!(view_text(summary.units), "per_unit");
22158 assert!(summary.correct_angle_difference_bounds);
22159 assert_eq!(view_text(summary.objective), "network_generator_cost");
22160
22161 let reference = pio_ac_opf_preparation_reference_buses(preparation);
22162 assert_eq!(
22163 std::slice::from_raw_parts(reference.data, reference.len),
22164 &[0]
22165 );
22166
22167 let mut bus = std::mem::MaybeUninit::<PioAcOpfBusView>::uninit();
22168 assert!(pio_ac_opf_preparation_bus_at(
22169 preparation,
22170 4,
22171 bus.as_mut_ptr(),
22172 &mut error,
22173 ));
22174 let bus = bus.assume_init();
22175 assert_eq!(bus.bus_id, 5);
22176 assert_eq!(bus.active_power_demand, 0.9);
22177 assert_eq!(bus.reactive_power_demand, 0.3);
22178 assert_eq!(bus.initial_voltage_angle_radians, 0.0);
22179
22180 let mut generator = std::mem::MaybeUninit::<PioAcOpfGeneratorView>::uninit();
22181 assert!(pio_ac_opf_preparation_generator_at(
22182 preparation,
22183 0,
22184 generator.as_mut_ptr(),
22185 &mut error,
22186 ));
22187 let generator = generator.assume_init();
22188 assert_eq!(generator.bus_index, 0);
22189 assert_eq!(generator.initial_active_power, 0.723);
22190 assert_eq!(generator.quadratic_cost, 2200.0);
22191 assert!(!generator.has_piecewise_linear_cost);
22192
22193 let mut branch = std::mem::MaybeUninit::<PioAcOpfBranchView>::uninit();
22194 assert!(pio_ac_opf_preparation_branch_at(
22195 preparation,
22196 0,
22197 branch.as_mut_ptr(),
22198 &mut error,
22199 ));
22200 let branch = branch.assume_init();
22201 assert_eq!(branch.from_bus_index, 0);
22202 assert_eq!(branch.to_bus_index, 3);
22203 assert_eq!(view_text(branch.source_kind), "branch");
22204 assert_eq!(branch.source_row, 0);
22205 assert!(!branch.has_winding);
22206
22207 let mut missing = std::mem::MaybeUninit::<PioAcOpfBusView>::uninit();
22208 assert!(!pio_ac_opf_preparation_bus_at(
22209 preparation,
22210 summary.bus_count,
22211 missing.as_mut_ptr(),
22212 &mut error,
22213 ));
22214 assert_eq!(
22215 view_text(pio_error_code(error)),
22216 "BIND.CAPI.INDEX_OUT_OF_RANGE"
22217 );
22218 pio_error_release(error);
22219 pio_ac_opf_preparation_release(preparation);
22220 }
22221 }
22222
22223 #[test]
22224 fn ac_opf_storage_view_preserves_fields_and_units() {
22225 unsafe {
22226 let mut network = case9_network();
22227 let mut storage = powerio_tx::Storage::new(powerio_tx::BusId(5));
22228 storage.uid = Some("battery".into());
22229 storage.ps = 12.0;
22230 storage.qs = -3.0;
22231 storage.energy = 80.0;
22232 storage.energy_rating = 120.0;
22233 storage.charge_rating = 25.0;
22234 storage.discharge_rating = 30.0;
22235 storage.charge_efficiency = 0.91;
22236 storage.discharge_efficiency = 0.88;
22237 storage.thermal_rating = 35.0;
22238 storage.qmin = -14.0;
22239 storage.qmax = 16.0;
22240 storage.r = 0.001;
22241 storage.x = 0.002;
22242 storage.p_loss = 0.4;
22243 storage.q_loss = 0.5;
22244 network.storage_mut().push(storage);
22245 let instance = powerio_prob::AcOpfInstance::from_network(network).unwrap();
22246 let close = |actual: f64, expected: f64| assert!((actual - expected).abs() < 1e-12);
22247
22248 for (units, power_scale) in [(Units::PerUnit, 0.01), (Units::Native, 1.0)] {
22249 let prepared = build_ac_opf_preparation(
22250 &instance,
22251 &AcOpfAssemblyOptions::default().with_units(units),
22252 )
22253 .unwrap();
22254 let preparation = PioAcOpfPreparation::new_raw(prepared);
22255 let mut error = std::ptr::null_mut();
22256 let mut summary = std::mem::MaybeUninit::<PioAcOpfPreparationView>::uninit();
22257 assert!(pio_ac_opf_preparation_summary(
22258 preparation,
22259 summary.as_mut_ptr(),
22260 &mut error,
22261 ));
22262 assert_eq!(summary.assume_init().storage_count, 1);
22263
22264 let mut row = std::mem::MaybeUninit::<PioAcOpfStorageView>::uninit();
22265 assert!(pio_ac_opf_preparation_storage_at(
22266 preparation,
22267 0,
22268 row.as_mut_ptr(),
22269 &mut error,
22270 ));
22271 let row = row.assume_init();
22272 assert_eq!(view_text(row.component_id), "battery");
22273 assert_eq!(row.bus_index, 4);
22274 assert_eq!(row.source_row, 0);
22275 close(row.initial_active_power, 12.0 * power_scale);
22276 close(row.initial_reactive_power, -3.0 * power_scale);
22277 close(row.energy, 80.0 * power_scale);
22278 close(row.energy_rating, 120.0 * power_scale);
22279 close(row.charge_rating, 25.0 * power_scale);
22280 close(row.discharge_rating, 30.0 * power_scale);
22281 assert_eq!(row.charge_efficiency, 0.91);
22282 assert_eq!(row.discharge_efficiency, 0.88);
22283 close(row.apparent_power_max, 35.0 * power_scale);
22284 close(row.reactive_power_min, -14.0 * power_scale);
22285 close(row.reactive_power_max, 16.0 * power_scale);
22286 assert_eq!(row.resistance_pu, 0.001);
22287 assert_eq!(row.reactance_pu, 0.002);
22288 close(row.active_power_loss, 0.4 * power_scale);
22289 close(row.reactive_power_loss, 0.5 * power_scale);
22290 assert!(row.in_service);
22291
22292 pio_ac_opf_preparation_release(preparation);
22293 }
22294 }
22295 }
22296
22297 #[test]
22298 fn module_transforms_build_typed_calculation_modules() {
22299 unsafe {
22300 type Transform =
22301 unsafe extern "C" fn(*const PioModule, *mut *mut PioError) -> *mut PioModule;
22302 let source = parse_case9();
22303 let operations: [(Transform, &str, &str); 4] = [
22304 (
22305 pio_module_to_dc_pf_instance,
22306 "powerio.DcPfInstance",
22307 "to_dc_pf_instance",
22308 ),
22309 (
22310 pio_module_to_ac_pf_instance,
22311 "powerio.AcPfInstance",
22312 "to_ac_pf_instance",
22313 ),
22314 (
22315 pio_module_to_dc_opf_instance,
22316 "powerio.DcOpfInstance",
22317 "to_dc_opf_instance",
22318 ),
22319 (
22320 pio_module_to_ac_opf_instance,
22321 "powerio.AcOpfInstance",
22322 "to_ac_opf_instance",
22323 ),
22324 ];
22325 for (operation, expected_type, expected_history) in operations {
22326 let mut error = std::ptr::null_mut();
22327 let derived = operation(source, &mut error);
22328 assert!(!derived.is_null(), "{}", error_text(error));
22329 let value = pio_module_value(derived);
22330 assert!(pio_value_is_type(
22331 value,
22332 expected_type.as_ptr().cast(),
22333 expected_type.len(),
22334 ));
22335 let derived_module = &PioModule::get(derived).unwrap().module;
22336 assert_eq!(
22337 derived_module.history().last().unwrap().name(),
22338 expected_history
22339 );
22340 assert!(derived_module.source().is_none());
22341 pio_value_release(value);
22342 pio_module_release(derived);
22343 }
22344
22345 let mut error = std::ptr::null_mut();
22346 let instance = pio_module_to_dc_pf_instance(source, &mut error);
22347 let invalid = pio_module_to_ac_pf_instance(instance, &mut error);
22348 assert!(invalid.is_null());
22349 assert_eq!(
22350 view_text(pio_error_code(error)),
22351 "REQUEST.MODULE.WRONG_MODEL_KIND"
22352 );
22353 pio_error_release(error);
22354 pio_module_release(instance);
22355 pio_module_release(source);
22356 }
22357 }
22358
22359 #[test]
22360 fn multiconductor_module_transforms_build_typed_calculation_modules() {
22361 unsafe {
22362 let source = parse_bmopf();
22363 let value = pio_module_value(source);
22364 let network_type = "powerio.MulticonductorNetwork";
22365 assert!(pio_value_is_type(
22366 value,
22367 network_type.as_ptr().cast(),
22368 network_type.len(),
22369 ));
22370 pio_value_release(value);
22371
22372 type Transform =
22373 unsafe extern "C" fn(*const PioModule, *mut *mut PioError) -> *mut PioModule;
22374 let operations: [(Transform, &str, &str); 2] = [
22375 (
22376 pio_module_to_mc_ac_pf_instance,
22377 "powerio.McAcPfInstance",
22378 "to_mc_ac_pf_instance",
22379 ),
22380 (
22381 pio_module_to_mc_ac_opf_instance,
22382 "powerio.McAcOpfInstance",
22383 "to_mc_ac_opf_instance",
22384 ),
22385 ];
22386 for (operation, expected_type, expected_history) in operations {
22387 let mut error = std::ptr::null_mut();
22388 let derived = operation(source, &mut error);
22389 assert!(!derived.is_null(), "{}", error_text(error));
22390 let value = pio_module_value(derived);
22391 assert!(pio_value_is_type(
22392 value,
22393 expected_type.as_ptr().cast(),
22394 expected_type.len(),
22395 ));
22396 let derived_module = &PioModule::get(derived).unwrap().module;
22397 assert_eq!(
22398 derived_module.history().last().unwrap().name(),
22399 expected_history
22400 );
22401 assert!(derived_module.source().is_none());
22402 pio_value_release(value);
22403 pio_module_release(derived);
22404 }
22405 pio_module_release(source);
22406 }
22407 }
22408
22409 #[test]
22410 fn geo_layer_derives_a_new_network_module() {
22411 unsafe {
22412 let source_module = parse_case9();
22413 let geojson = br#"{
22414 "type": "FeatureCollection",
22415 "features": [{
22416 "type": "Feature",
22417 "geometry": {
22418 "type": "Point",
22419 "coordinates": [-83.743, 42.281]
22420 },
22421 "properties": {"bus": "1"}
22422 }]
22423 }"#;
22424 let mut error = std::ptr::null_mut();
22425 let source = pio_source_from_memory(
22426 c"case9.geojson".as_ptr(),
22427 "case9.geojson".len(),
22428 geojson.as_ptr(),
22429 geojson.len(),
22430 &mut error,
22431 );
22432 assert!(!source.is_null(), "{}", error_text(error));
22433 let layer = pio_geo_layer_parse(source, &mut error);
22434 pio_source_release(source);
22435 assert!(!layer.is_null(), "{}", error_text(error));
22436
22437 let diagnostics = pio_geo_layer_diagnostics(layer);
22438 assert_eq!(pio_diagnostics_len(diagnostics), 0);
22439 pio_diagnostics_release(diagnostics);
22440
22441 let mut report = std::ptr::null_mut();
22442 let derived = pio_module_apply_geo_layer(source_module, layer, &mut report, &mut error);
22443 assert!(!derived.is_null(), "{}", error_text(error));
22444 assert!(!report.is_null());
22445 assert_eq!(pio_geo_apply_report_matched_buses(report), 1);
22446 assert_eq!(pio_geo_apply_report_matched_branches(report), 0);
22447 assert_eq!(pio_geo_apply_report_unmatched_features(report), 0);
22448 assert_eq!(pio_geo_apply_report_unlocated_buses(report), 8);
22449 assert_eq!(pio_geo_apply_report_unlocated_branches(report), 9);
22450
22451 let note_count = pio_geo_apply_report_note_count(report);
22452 let note = pio_geo_apply_report_note_at(report, note_count, &mut error);
22453 assert_eq!(note.len, 0);
22454 assert_eq!(
22455 view_text(pio_error_code(error)),
22456 "BIND.CAPI.INDEX_OUT_OF_RANGE"
22457 );
22458 pio_error_release(error);
22459 error = std::ptr::null_mut();
22460
22461 let derived_module = &PioModule::get(derived).unwrap().module;
22462 assert!(derived_module.source().is_none());
22463 assert_eq!(
22464 derived_module.history().last().unwrap().name(),
22465 "apply_geo_layer"
22466 );
22467 let PioValue::BalancedNetwork(derived_network) = &derived_module.value() else {
22468 panic!("geo application changed the value type")
22469 };
22470 let derived_location = derived_network
22471 .buses()
22472 .iter()
22473 .find(|bus| bus.id == powerio_tx::BusId(1))
22474 .and_then(|bus| bus.location)
22475 .unwrap();
22476 assert_eq!((derived_location.x, derived_location.y), (-83.743, 42.281));
22477
22478 let original_module = &PioModule::get(source_module).unwrap().module;
22479 let PioValue::BalancedNetwork(original_network) = &original_module.value() else {
22480 unreachable!()
22481 };
22482 assert!(
22483 original_network
22484 .buses()
22485 .iter()
22486 .find(|bus| bus.id == powerio_tx::BusId(1))
22487 .unwrap()
22488 .location
22489 .is_none()
22490 );
22491
22492 let calculation = pio_module_to_dc_pf_instance(source_module, &mut error);
22493 assert!(!calculation.is_null(), "{}", error_text(error));
22494 let rejected =
22495 pio_module_apply_geo_layer(calculation, layer, std::ptr::null_mut(), &mut error);
22496 assert!(rejected.is_null());
22497 assert_eq!(
22498 view_text(pio_error_code(error)),
22499 "REQUEST.MODULE.WRONG_MODEL_KIND"
22500 );
22501 pio_error_release(error);
22502
22503 pio_module_release(calculation);
22504 pio_geo_apply_report_release(report);
22505 pio_geo_layer_release(layer);
22506 pio_module_release(derived);
22507 pio_module_release(source_module);
22508 }
22509 }
22510
22511 #[test]
22512 fn a_geo_layer_writes_the_canonical_document_it_parses_back() {
22513 unsafe {
22514 let document = br#"{
22515 "type": "FeatureCollection",
22516 "features": [{
22517 "type": "Feature",
22518 "geometry": {
22519 "type": "Point",
22520 "coordinates": [-83.743, 42.281]
22521 },
22522 "properties": {"bus": "1"}
22523 }]
22524 }"#;
22525 let mut error = std::ptr::null_mut();
22526 let source = pio_source_from_memory(
22527 c"case9.geojson".as_ptr(),
22528 "case9.geojson".len(),
22529 document.as_ptr(),
22530 document.len(),
22531 &mut error,
22532 );
22533 assert!(!source.is_null(), "{}", error_text(error));
22534 let layer = pio_geo_layer_parse(source, &mut error);
22535 pio_source_release(source);
22536 assert!(!layer.is_null(), "{}", error_text(error));
22537
22538 let written = pio_geo_layer_to_geojson(layer, &mut error);
22539 assert!(!written.is_null(), "{}", error_text(error));
22540 let written_text = view_text(pio_string_view(written));
22541 pio_string_release(written);
22542 assert!(written_text.contains("FeatureCollection"), "{written_text}");
22543 assert!(written_text.contains("powerio_geo"), "{written_text}");
22544
22545 let echo_source = pio_source_from_memory(
22548 c"case9.geo.json".as_ptr(),
22549 "case9.geo.json".len(),
22550 written_text.as_ptr(),
22551 written_text.len(),
22552 &mut error,
22553 );
22554 assert!(!echo_source.is_null(), "{}", error_text(error));
22555 let echo = pio_geo_layer_parse(echo_source, &mut error);
22556 pio_source_release(echo_source);
22557 assert!(!echo.is_null(), "{}", error_text(error));
22558 let again = pio_geo_layer_to_geojson(echo, &mut error);
22559 assert!(!again.is_null(), "{}", error_text(error));
22560 assert_eq!(view_text(pio_string_view(again)), written_text);
22561 pio_string_release(again);
22562 pio_geo_layer_release(echo);
22563
22564 let missing = pio_geo_layer_to_geojson(std::ptr::null(), &mut error);
22565 assert!(missing.is_null());
22566 assert_eq!(view_text(pio_error_code(error)), "BIND.CAPI.NULL_HANDLE");
22567 pio_error_release(error);
22568
22569 pio_geo_layer_release(layer);
22570 }
22571 }
22572
22573 unsafe fn contingency_fixture(name: &str) -> std::path::PathBuf {
22574 std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
22575 .join("../tests/data/psse/contingency")
22576 .join(name)
22577 }
22578
22579 unsafe fn parse_contingency_fixture(name: &str) -> *mut PioModule {
22580 let path = unsafe { contingency_fixture(name) };
22581 let path = path.to_string_lossy();
22582 let mut error = std::ptr::null_mut();
22583 let source = unsafe { pio_source_open(path.as_ptr().cast(), path.len(), &mut error) };
22584 assert!(!source.is_null(), "{}", unsafe { error_text(error) });
22585 let module = unsafe { pio_parse(source, std::ptr::null(), 0, &mut error) };
22586 unsafe { pio_source_release(source) };
22587 assert!(!module.is_null(), "{name}: {}", unsafe {
22588 error_text(error)
22589 });
22590 module
22591 }
22592
22593 unsafe fn reread_contingency_text(name: &str, format: &str, text: &str) -> *mut PioModule {
22596 let mut error = std::ptr::null_mut();
22597 let source = unsafe {
22598 pio_source_from_memory(
22599 name.as_ptr().cast(),
22600 name.len(),
22601 text.as_ptr(),
22602 text.len(),
22603 &mut error,
22604 )
22605 };
22606 assert!(!source.is_null(), "{}", unsafe { error_text(error) });
22607 let module = unsafe { pio_parse(source, format.as_ptr().cast(), format.len(), &mut error) };
22608 unsafe { pio_source_release(source) };
22609 assert!(!module.is_null(), "{name}: {}", unsafe {
22610 error_text(error)
22611 });
22612 module
22613 }
22614
22615 unsafe fn contingency_network(name: &str) -> (*mut PioModule, *mut PioBalancedNetwork) {
22618 let module = unsafe { parse_contingency_fixture(name) };
22619 let mut error = std::ptr::null_mut();
22620 let value = unsafe { pio_module_value(module) };
22621 let network = unsafe { pio_value_balanced_network(value, &mut error) };
22622 assert!(!network.is_null(), "{}", unsafe { error_text(error) });
22623 unsafe { pio_value_release(value) };
22624 (module, network)
22625 }
22626
22627 #[test]
22628 fn a_contingency_set_binds_every_case_to_the_network_it_names() {
22629 unsafe {
22630 let path = contingency_fixture("resolve_cases.con");
22631 let path_text = path.to_string_lossy();
22632 let mut error = std::ptr::null_mut();
22633 let source = pio_source_open(path_text.as_ptr().cast(), path_text.len(), &mut error);
22634 assert!(!source.is_null(), "{}", error_text(error));
22635 let set = pio_contingency_set_parse(source, &mut error);
22636 pio_source_release(source);
22637 assert!(!set.is_null(), "{}", error_text(error));
22638
22639 assert_eq!(pio_contingency_set_case_count(set), 20);
22640 assert_eq!(
22641 view_text(pio_contingency_set_case_name(set, 0, &mut error)),
22642 "BR_1_2_C1"
22643 );
22644 let past_end = pio_contingency_set_case_name(set, 20, &mut error);
22645 assert_eq!(past_end.len, 0);
22646 assert_eq!(
22647 view_text(pio_error_code(error)),
22648 "BIND.CAPI.INDEX_OUT_OF_RANGE"
22649 );
22650 pio_error_release(error);
22651 error = std::ptr::null_mut();
22652
22653 let notes = pio_contingency_set_diagnostics(set);
22656 assert!(pio_diagnostics_len(notes) > 0);
22657 pio_diagnostics_release(notes);
22658
22659 let (module, network) = contingency_network("resolve_v33.raw");
22660 let resolution = pio_contingency_set_resolve(set, network, &mut error);
22661 assert!(!resolution.is_null(), "{}", error_text(error));
22662
22663 assert_eq!(pio_contingency_resolution_case_count(resolution), 20);
22664 assert_eq!(pio_contingency_resolution_resolved_count(resolution), 17);
22665 assert_eq!(pio_contingency_resolution_unresolved_count(resolution), 3);
22666 assert_eq!(
22667 pio_contingency_resolution_unrecognized_statement_count(resolution),
22668 1
22669 );
22670
22671 assert!(pio_contingency_resolution_case_is_resolved(resolution, 0));
22673 assert_eq!(
22674 view_text(pio_contingency_resolution_case_name(
22675 resolution, 0, &mut error
22676 )),
22677 "BR_1_2_C1"
22678 );
22679 assert_eq!(
22680 pio_contingency_resolution_case_component_count(resolution, 0),
22681 1
22682 );
22683 let mut component = std::mem::MaybeUninit::<PioContingencyComponentView>::uninit();
22684 assert!(pio_contingency_resolution_case_component(
22685 resolution,
22686 0,
22687 0,
22688 component.as_mut_ptr(),
22689 &mut error,
22690 ));
22691 let component = component.assume_init();
22692 assert_eq!(view_text(component.id.component_type), "branch");
22693 assert!(component.in_service);
22694 assert_eq!(
22695 pio_contingency_resolution_case_unresolved_count(resolution, 0),
22696 0
22697 );
22698
22699 let unresolved: Vec<(String, String, String)> = (0
22702 ..pio_contingency_resolution_case_count(resolution))
22703 .filter(|index| !pio_contingency_resolution_case_is_resolved(resolution, *index))
22704 .map(|index| {
22705 assert_eq!(
22706 pio_contingency_resolution_case_unresolved_count(resolution, index),
22707 1
22708 );
22709 (
22710 view_text(pio_contingency_resolution_case_name(
22711 resolution, index, &mut error,
22712 )),
22713 view_text(pio_contingency_resolution_case_unresolved_reason(
22714 resolution, index, 0, &mut error,
22715 )),
22716 view_text(pio_contingency_resolution_case_unresolved_action(
22717 resolution, index, 0, &mut error,
22718 )),
22719 )
22720 })
22721 .collect();
22722 assert_eq!(
22723 unresolved,
22724 vec![
22725 (
22726 "BR_MISSING".to_owned(),
22727 "no_such_branch".to_owned(),
22728 "OPEN LINE FROM BUS 3 TO BUS 4 CIRCUIT 1".to_owned(),
22729 ),
22730 (
22731 "MACHINE_MISSING".to_owned(),
22732 "no_such_machine".to_owned(),
22733 "REMOVE MACHINE 9 FROM BUS 1".to_owned(),
22734 ),
22735 (
22736 "UNRECOGNIZED".to_owned(),
22737 "unrecognized".to_owned(),
22738 "PARALLEL BRANCH FROM BUS 1 TO BUS 2".to_owned(),
22739 ),
22740 ]
22741 );
22742 let past_end =
22743 pio_contingency_resolution_case_unresolved_action(resolution, 0, 0, &mut error);
22744 assert_eq!(past_end.len, 0);
22745 assert_eq!(
22746 view_text(pio_error_code(error)),
22747 "BIND.CAPI.INDEX_OUT_OF_RANGE"
22748 );
22749 pio_error_release(error);
22750 error = std::ptr::null_mut();
22751
22752 let written = pio_contingency_set_to_con(set, &mut error);
22755 assert!(!written.is_null(), "{}", error_text(error));
22756 let written_text = view_text(pio_string_view(written));
22757 pio_string_release(written);
22758 assert!(
22759 written_text.contains(
22760 "CONTINGENCY 'BR_MISSING'\nOPEN LINE FROM BUS 3 TO BUS 4 CIRCUIT 1\nEND\n"
22761 ),
22762 "{written_text}"
22763 );
22764 let echo = pio_source_from_memory(
22765 c"written.con".as_ptr(),
22766 "written.con".len(),
22767 written_text.as_ptr(),
22768 written_text.len(),
22769 &mut error,
22770 );
22771 assert!(!echo.is_null(), "{}", error_text(error));
22772 let again = pio_contingency_set_parse(echo, &mut error);
22773 pio_source_release(echo);
22774 assert!(!again.is_null(), "{}", error_text(error));
22775 assert_eq!(pio_contingency_set_case_count(again), 20);
22776 assert_eq!(
22777 view_text(pio_contingency_set_case_name(again, 19, &mut error)),
22778 "UNRECOGNIZED"
22779 );
22780 pio_contingency_set_release(again);
22781
22782 let retained = pio_contingency_resolution_retain(resolution);
22784 pio_contingency_resolution_release(retained);
22785 assert_eq!(pio_contingency_resolution_case_count(resolution), 20);
22786 let retained_set = pio_contingency_set_retain(set);
22787 pio_contingency_set_release(retained_set);
22788 assert_eq!(pio_contingency_set_case_count(set), 20);
22789
22790 pio_contingency_resolution_release(resolution);
22791 pio_balanced_network_release(network);
22792 pio_module_release(module);
22793 pio_contingency_set_release(set);
22794 }
22795 }
22796
22797 #[test]
22803 fn a_bound_component_states_its_table_and_an_optional_identity() {
22804 unsafe {
22805 let mut error = std::ptr::null_mut();
22806 let module = parse_contingency_fixture("resolve_v33.raw");
22807 let destination = pio_destination_memory(
22808 c"network.pio.json".as_ptr(),
22809 "network.pio.json".len(),
22810 &mut error,
22811 );
22812 assert!(!destination.is_null(), "{}", error_text(error));
22813 let result = pio_module_serialize(module, destination, &mut error);
22814 assert!(!result.is_null(), "{}", error_text(error));
22815 let artifact = pio_emit_result_artifact(result, 0, &mut error);
22816 assert!(!artifact.is_null(), "{}", error_text(error));
22817 let bytes = pio_artifact_bytes(artifact);
22818 let mut document: serde_json::Value =
22819 serde_json::from_slice(std::slice::from_raw_parts(bytes.data, bytes.len)).unwrap();
22820 let loads = document["value"]["data"]["loads"].as_array_mut().unwrap();
22821 assert_eq!(loads.len(), 2);
22822 for load in loads {
22823 load["uid"] = serde_json::Value::String(String::new());
22824 }
22825 let rewritten = serde_json::to_vec(&document).unwrap();
22826 pio_artifact_release(artifact);
22827 pio_emit_result_release(result);
22828 pio_destination_release(destination);
22829 pio_module_release(module);
22830
22831 let source = pio_source_from_memory(
22832 c"network.pio.json".as_ptr(),
22833 "network.pio.json".len(),
22834 rewritten.as_ptr(),
22835 rewritten.len(),
22836 &mut error,
22837 );
22838 assert!(!source.is_null(), "{}", error_text(error));
22839 let decoded = pio_module_deserialize(source, &mut error);
22840 pio_source_release(source);
22841 assert!(!decoded.is_null(), "{}", error_text(error));
22842 let value = pio_module_value(decoded);
22843 let network = pio_value_balanced_network(value, &mut error);
22844 assert!(!network.is_null(), "{}", error_text(error));
22845 pio_value_release(value);
22846
22847 let path = contingency_fixture("resolve_cases.con");
22848 let path_text = path.to_string_lossy();
22849 let cases = pio_source_open(path_text.as_ptr().cast(), path_text.len(), &mut error);
22850 assert!(!cases.is_null(), "{}", error_text(error));
22851 let set = pio_contingency_set_parse(cases, &mut error);
22852 pio_source_release(cases);
22853 assert!(!set.is_null(), "{}", error_text(error));
22854 let resolution = pio_contingency_set_resolve(set, network, &mut error);
22855 assert!(!resolution.is_null(), "{}", error_text(error));
22856
22857 let case_of = |name: &str| {
22858 let mut case_error = std::ptr::null_mut();
22859 (0..pio_contingency_resolution_case_count(resolution))
22860 .find(|index| {
22861 view_text(pio_contingency_resolution_case_name(
22862 resolution,
22863 *index,
22864 &mut case_error,
22865 )) == name
22866 })
22867 .unwrap_or_else(|| panic!("the fixture states a case named {name}"))
22868 };
22869 let read = |case_index: usize, component_index: usize| {
22870 let mut component = std::mem::MaybeUninit::<PioContingencyComponentView>::uninit();
22871 let mut read_error = std::ptr::null_mut();
22872 assert!(
22873 pio_contingency_resolution_case_component(
22874 resolution,
22875 case_index,
22876 component_index,
22877 component.as_mut_ptr(),
22878 &mut read_error,
22879 ),
22880 "{}",
22881 error_text(read_error)
22882 );
22883 component.assume_init()
22884 };
22885
22886 let loads_at_bus = case_of("LOADS_AT_BUS");
22889 assert_eq!(
22890 pio_contingency_resolution_case_component_count(resolution, loads_at_bus),
22891 2
22892 );
22893 for row in 0..2 {
22894 let component = read(loads_at_bus, row);
22895 assert_eq!(view_text(component.id.component_type), "load");
22896 assert_eq!(component.id.local_id.len, 0);
22897 assert_eq!(component.row, row);
22898 assert!(component.in_service);
22899 }
22900
22901 let branch = read(case_of("BR_1_2_C1"), 0);
22904 assert_eq!(view_text(branch.id.component_type), "branch");
22905 assert_eq!(view_text(branch.id.local_id), "1-2");
22906
22907 pio_contingency_resolution_release(resolution);
22908 pio_contingency_set_release(set);
22909 pio_balanced_network_release(network);
22910 pio_module_release(decoded);
22911 }
22912 }
22913
22914 #[test]
22915 fn an_automatic_specification_expands_against_a_subsystem_set() {
22916 unsafe {
22917 let mut error = std::ptr::null_mut();
22918 let cases_module = parse_contingency_fixture("expand.con");
22919 let cases_value = pio_module_value(cases_module);
22920 assert!(pio_value_is_type(
22921 cases_value,
22922 c"powerio.ContingencySet".as_ptr().cast(),
22923 "powerio.ContingencySet".len(),
22924 ));
22925 let set = pio_value_contingency_set(cases_value, &mut error);
22926 assert!(!set.is_null(), "{}", error_text(error));
22927 let notes = pio_contingency_set_diagnostics(set);
22929 assert_eq!(pio_diagnostics_len(notes), 0);
22930 pio_diagnostics_release(notes);
22931
22932 let wrong = pio_value_subsystem_set(cases_value, &mut error);
22934 assert!(wrong.is_null());
22935 assert_eq!(
22936 view_text(pio_error_code(error)),
22937 "REQUEST.CAPI.TYPE_MISMATCH"
22938 );
22939 pio_error_release(error);
22940 error = std::ptr::null_mut();
22941 pio_value_release(cases_value);
22942
22943 pio_module_release(cases_module);
22946 assert_eq!(pio_contingency_set_case_count(set), 1);
22947 let written = pio_contingency_set_to_con(set, &mut error);
22948 assert!(!written.is_null(), "{}", error_text(error));
22949 let written_text = view_text(pio_string_view(written));
22950 assert!(
22951 written_text.contains("CONTINGENCY 'EXPLICIT'"),
22952 "{written_text}"
22953 );
22954 assert!(
22955 written_text.contains("SINGLE BRANCH IN SUBSYSTEM 'NOSUCH'"),
22956 "{written_text}"
22957 );
22958 pio_string_release(written);
22959
22960 let sub_module = parse_contingency_fixture("selectors.sub");
22961 let sub_value = pio_module_value(sub_module);
22962 let subsystems = pio_value_subsystem_set(sub_value, &mut error);
22963 assert!(!subsystems.is_null(), "{}", error_text(error));
22964 pio_value_release(sub_value);
22965 assert_eq!(pio_subsystem_set_count(subsystems), 10);
22966 assert_eq!(
22967 view_text(pio_subsystem_set_name(subsystems, 0, &mut error)),
22968 "A1"
22969 );
22970 let written = pio_subsystem_set_to_sub(subsystems, &mut error);
22971 assert!(!written.is_null(), "{}", error_text(error));
22972 let written_text = view_text(pio_string_view(written));
22973 pio_string_release(written);
22974 let echo = reread_contingency_text("written.sub", "psse-sub", &written_text);
22975 let echo_value = pio_module_value(echo);
22976 let again = pio_value_subsystem_set(echo_value, &mut error);
22977 assert!(!again.is_null(), "{}", error_text(error));
22978 pio_value_release(echo_value);
22979 assert_eq!(pio_subsystem_set_count(again), 10);
22980 assert_eq!(
22981 view_text(pio_subsystem_set_name(again, 7, &mut error)),
22982 "BAREJOIN"
22983 );
22984 pio_subsystem_set_release(again);
22985 pio_module_release(echo);
22986
22987 let (network_module, network) = contingency_network("select_v33.raw");
22988 let mut expansion_notes = std::ptr::null_mut();
22989 let expanded = pio_contingency_set_expand(
22990 set,
22991 network,
22992 subsystems,
22993 &mut expansion_notes,
22994 &mut error,
22995 );
22996 assert!(!expanded.is_null(), "{}", error_text(error));
22997 assert_eq!(pio_contingency_set_case_count(expanded), 8);
22998 let written = pio_contingency_set_to_con(expanded, &mut error);
23000 assert!(!written.is_null(), "{}", error_text(error));
23001 assert!(
23002 view_text(pio_string_view(written)).contains("CONTINGENCY 'T_101_102_103_1'"),
23003 "the expanded text states every generated case"
23004 );
23005 pio_string_release(written);
23006 assert_eq!(
23007 view_text(pio_contingency_set_case_name(expanded, 0, &mut error)),
23008 "EXPLICIT"
23009 );
23010 assert_eq!(
23011 view_text(pio_contingency_set_case_name(expanded, 3, &mut error)),
23012 "T_101_102_103_1"
23013 );
23014 assert_eq!(pio_diagnostics_len(expansion_notes), 1);
23017 pio_diagnostics_release(expansion_notes);
23018
23019 let monitored_module = parse_contingency_fixture("generated.mon");
23020 let monitored_value = pio_module_value(monitored_module);
23021 let monitored = pio_value_monitored_set(monitored_value, &mut error);
23022 assert!(!monitored.is_null(), "{}", error_text(error));
23023 pio_value_release(monitored_value);
23024 assert_eq!(pio_monitored_set_statement_count(monitored), 12);
23025 let written = pio_monitored_set_to_mon(monitored, &mut error);
23026 assert!(!written.is_null(), "{}", error_text(error));
23027 let written_text = view_text(pio_string_view(written));
23028 pio_string_release(written);
23029 let echo = reread_contingency_text("written.mon", "psse-mon", &written_text);
23030 let echo_value = pio_module_value(echo);
23031 let again = pio_value_monitored_set(echo_value, &mut error);
23032 assert!(!again.is_null(), "{}", error_text(error));
23033 pio_value_release(echo_value);
23034 assert_eq!(pio_monitored_set_statement_count(again), 12);
23035 pio_monitored_set_release(again);
23036 pio_module_release(echo);
23037
23038 pio_monitored_set_release(monitored);
23039 pio_module_release(monitored_module);
23040 pio_contingency_set_release(expanded);
23041 pio_balanced_network_release(network);
23042 pio_module_release(network_module);
23043 pio_subsystem_set_release(subsystems);
23044 pio_module_release(sub_module);
23045 pio_contingency_set_release(set);
23046 }
23047 }
23048
23049 #[test]
23050 fn a_named_subsystem_selects_the_buses_of_one_network() {
23051 unsafe {
23052 let mut error = std::ptr::null_mut();
23053 let (network_module, network) = contingency_network("select_v33.raw");
23054 let sub_module = parse_contingency_fixture("selectors.sub");
23055 let sub_value = pio_module_value(sub_module);
23056 let subsystems = pio_value_subsystem_set(sub_value, &mut error);
23057 assert!(!subsystems.is_null(), "{}", error_text(error));
23058 pio_value_release(sub_value);
23059 pio_module_release(sub_module);
23062
23063 for (name, expected) in [
23064 ("A1", &[101.0, 102.0, 103.0][..]),
23065 ("AREARANGE", &[101.0, 102.0, 103.0, 201.0, 202.0][..]),
23066 ("KV", &[101.0, 102.0, 103.0, 201.0][..]),
23067 (" a1 ", &[101.0, 102.0, 103.0][..]),
23070 ] {
23071 let selected = pio_balanced_network_select_subsystem_buses(
23072 network,
23073 subsystems,
23074 name.as_ptr().cast(),
23075 name.len(),
23076 &mut error,
23077 );
23078 assert!(!selected.is_null(), "{name}: {}", error_text(error));
23079 let values = pio_vector_values(selected);
23080 assert_eq!(
23081 std::slice::from_raw_parts(values.data, values.len),
23082 expected,
23083 "{name}"
23084 );
23085 pio_vector_release(selected);
23086 }
23087
23088 let unknown = pio_balanced_network_select_subsystem_buses(
23089 network,
23090 subsystems,
23091 c"NOSUCH".as_ptr(),
23092 "NOSUCH".len(),
23093 &mut error,
23094 );
23095 assert!(unknown.is_null());
23096 assert_eq!(
23097 view_text(pio_error_code(error)),
23098 "BIND.CAPI.INDEX_OUT_OF_RANGE"
23099 );
23100 pio_error_release(error);
23101 error = std::ptr::null_mut();
23102
23103 let no_network = pio_balanced_network_select_subsystem_buses(
23104 std::ptr::null(),
23105 subsystems,
23106 c"A1".as_ptr(),
23107 "A1".len(),
23108 &mut error,
23109 );
23110 assert!(no_network.is_null());
23111 assert_eq!(view_text(pio_error_code(error)), "BIND.CAPI.NULL_HANDLE");
23112 pio_error_release(error);
23113 error = std::ptr::null_mut();
23114
23115 let no_subsystems = pio_balanced_network_select_subsystem_buses(
23116 network,
23117 std::ptr::null(),
23118 c"A1".as_ptr(),
23119 "A1".len(),
23120 &mut error,
23121 );
23122 assert!(no_subsystems.is_null());
23123 assert_eq!(view_text(pio_error_code(error)), "BIND.CAPI.NULL_HANDLE");
23124 pio_error_release(error);
23125 error = std::ptr::null_mut();
23126
23127 let no_name = pio_balanced_network_select_subsystem_buses(
23128 network,
23129 subsystems,
23130 std::ptr::null(),
23131 0,
23132 &mut error,
23133 );
23134 assert!(no_name.is_null());
23135 assert_eq!(view_text(pio_error_code(error)), "BIND.CAPI.NULL_ARGUMENT");
23136 pio_error_release(error);
23137
23138 pio_subsystem_set_release(subsystems);
23139 pio_balanced_network_release(network);
23140 pio_module_release(network_module);
23141 }
23142 }
23143
23144 #[test]
23145 fn each_contingency_file_emits_the_bytes_it_was_read_from() {
23146 unsafe {
23147 for (name, format) in [
23148 ("resolve_cases.con", "psse-con"),
23149 ("selectors.sub", "psse-sub"),
23150 ("generated.mon", "psse-mon"),
23151 ] {
23152 let module = parse_contingency_fixture(name);
23153 let mut error = std::ptr::null_mut();
23154 let destination = pio_destination_memory(c"out".as_ptr(), 3, &mut error);
23155 assert!(!destination.is_null(), "{}", error_text(error));
23156 let result = pio_emit(
23157 module,
23158 format.as_ptr().cast(),
23159 format.len(),
23160 destination,
23161 &mut error,
23162 );
23163 pio_destination_release(destination);
23164 assert!(!result.is_null(), "{name}: {}", error_text(error));
23165 assert_eq!(pio_emit_result_artifact_count(result), 1);
23166 let artifact = pio_emit_result_artifact(result, 0, &mut error);
23167 assert!(!artifact.is_null(), "{name}: {}", error_text(error));
23168 let bytes = pio_artifact_bytes(artifact);
23169 let emitted = std::slice::from_raw_parts(bytes.data, bytes.len).to_vec();
23170 assert_eq!(
23171 emitted,
23172 std::fs::read(contingency_fixture(name)).unwrap(),
23173 "{name}"
23174 );
23175 pio_artifact_release(artifact);
23176 pio_emit_result_release(result);
23177 pio_module_release(module);
23178 }
23179 }
23180 }
23181
23182 #[test]
23183 fn aggregate_bus_active_demand_uses_explicit_proportional_allocation() {
23184 unsafe {
23185 let mut network = case9_network();
23186 let mut second = network
23187 .loads()
23188 .iter()
23189 .find(|load| load.bus == powerio_tx::BusId::new(5))
23190 .unwrap()
23191 .clone();
23192 second.p = 30.0;
23193 second.q = 10.0;
23194 second.uid = Some("extra-load-at-bus-5".to_owned());
23195 network.loads_mut().push(second);
23196 let source = module_handle(powerio::PioModule::new(PioValue::BalancedNetwork(network)));
23197 let mut error = std::ptr::null_mut();
23198 let module = pio_module_to_dc_pf_instance(source, &mut error);
23199 assert!(!module.is_null(), "{}", error_text(error));
23200 pio_module_release(source);
23201
23202 let value_before = pio_module_value(module);
23203 let instance_before = pio_value_dc_pf_instance(value_before, &mut error);
23204 let network_before =
23205 pio_calculation_instance_balanced_network(instance_before, &mut error);
23206
23207 let power = pio_active_power_from_watts(240_000_000.0);
23208 let bad_rule = "first_load";
23209 let bad_report = pio_apply_bus_load_active_power(
23210 module,
23211 5,
23212 power,
23213 bad_rule.as_ptr().cast(),
23214 bad_rule.len(),
23215 &mut error,
23216 );
23217 assert!(bad_report.is_null());
23218 assert_eq!(
23219 view_text(pio_error_code(error)),
23220 "REQUEST.CAPI.ALLOCATION_UNKNOWN"
23221 );
23222 pio_error_release(error);
23223 error = std::ptr::null_mut();
23224
23225 let rule = "proportional_to_current_active_power";
23226 let report = pio_apply_bus_load_active_power(
23227 module,
23228 5,
23229 power,
23230 rule.as_ptr().cast(),
23231 rule.len(),
23232 &mut error,
23233 );
23234 assert!(!report.is_null(), "{}", error_text(error));
23235 assert_eq!(pio_update_report_len(report), 2);
23236 assert!(!pio_update_report_connectivity_changed(report));
23237
23238 let value_after = pio_module_value(module);
23239 let instance_after = pio_value_dc_pf_instance(value_after, &mut error);
23240 let network_after =
23241 pio_calculation_instance_balanced_network(instance_after, &mut error);
23242 let mut first_before = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
23243 let mut second_before = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
23244 let mut first_after = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
23245 let mut second_after = std::mem::MaybeUninit::<PioBalancedLoadView>::uninit();
23246 assert!(pio_balanced_network_load_at(
23247 network_before,
23248 0,
23249 first_before.as_mut_ptr(),
23250 &mut error,
23251 ));
23252 assert!(pio_balanced_network_load_at(
23253 network_before,
23254 3,
23255 second_before.as_mut_ptr(),
23256 &mut error,
23257 ));
23258 assert!(pio_balanced_network_load_at(
23259 network_after,
23260 0,
23261 first_after.as_mut_ptr(),
23262 &mut error,
23263 ));
23264 assert!(pio_balanced_network_load_at(
23265 network_after,
23266 3,
23267 second_after.as_mut_ptr(),
23268 &mut error,
23269 ));
23270 assert_eq!(first_before.assume_init().p_mw, 90.0);
23271 assert_eq!(second_before.assume_init().p_mw, 30.0);
23272 assert_eq!(first_after.assume_init().p_mw, 180.0);
23273 assert_eq!(second_after.assume_init().p_mw, 60.0);
23274 assert_eq!(
23275 PioModule::get(module)
23276 .unwrap()
23277 .module
23278 .history()
23279 .last()
23280 .unwrap()
23281 .name(),
23282 "apply_updates"
23283 );
23284
23285 pio_balanced_network_release(network_before);
23286 pio_calculation_instance_release(instance_before);
23287 pio_value_release(value_before);
23288 pio_balanced_network_release(network_after);
23289 pio_calculation_instance_release(instance_after);
23290 pio_value_release(value_after);
23291 pio_update_report_release(report);
23292 pio_active_power_release(power);
23293 pio_module_release(module);
23294 }
23295 }
23296
23297 #[test]
23298 fn scuc_instance_exposes_typed_scheduling_inputs() {
23299 unsafe {
23300 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
23301 .join("../tests/data/goc3/goc3_small.json");
23302 let source = std::fs::read_to_string(path).unwrap().replacen(
23303 "\"startup_states\": []",
23304 "\"startup_states\": [[2.0, 3.0]]",
23305 1,
23306 );
23307 let parsed =
23308 goc3_instance(Source::from_memory("goc3_small.json", source.into_bytes()).unwrap());
23309 let module = module_handle(powerio::PioModule::new(PioValue::AcScucInstance(parsed)));
23310 let value = pio_module_value(module);
23311 let mut error = std::ptr::null_mut();
23312 let instance = pio_value_ac_scuc_instance(value, &mut error);
23313 assert!(!instance.is_null(), "{}", error_text(error));
23314 pio_value_release(value);
23315 pio_module_release(module);
23316
23317 let mut dimensions = std::mem::MaybeUninit::<PioScucDimensionsView>::uninit();
23318 assert!(pio_ac_scuc_instance_dimensions(
23319 instance,
23320 dimensions.as_mut_ptr(),
23321 &mut error,
23322 ));
23323 let dimensions = dimensions.assume_init();
23324 assert_eq!(dimensions.period_count, 2);
23325 assert_eq!(dimensions.device_count, 2);
23326 assert_eq!(dimensions.producer_count, 1);
23327 assert_eq!(dimensions.consumer_count, 1);
23328 assert_eq!(dimensions.shunt_count, 1);
23329 assert_eq!(dimensions.branch_switching_cost_count, 3);
23330 assert_eq!(dimensions.transformer_control_count, 1);
23331 assert_eq!(dimensions.active_reserve_zone_count, 1);
23332 assert_eq!(dimensions.reactive_reserve_zone_count, 1);
23333 assert_eq!(dimensions.contingency_count, 3);
23334
23335 let durations = pio_ac_scuc_instance_interval_durations(instance);
23336 assert_eq!(
23337 std::slice::from_raw_parts(durations.data, durations.len),
23338 &[1.0, 1.0]
23339 );
23340
23341 let mut violation = std::mem::MaybeUninit::<PioScucViolationCostView>::uninit();
23342 assert!(pio_ac_scuc_instance_violation_costs(
23343 instance,
23344 violation.as_mut_ptr(),
23345 &mut error,
23346 ));
23347 let violation = violation.assume_init();
23348 assert_eq!(violation.active_power_balance, 1.0);
23349 assert_eq!(violation.reactive_power_balance, 1.0);
23350 assert_eq!(violation.branch_thermal_limit, 1.0);
23351 assert_eq!(violation.energy_requirement, 1.0);
23352
23353 let mut producer = std::mem::MaybeUninit::<PioScucDeviceView>::uninit();
23354 assert!(pio_ac_scuc_instance_device_at(
23355 instance,
23356 0,
23357 producer.as_mut_ptr(),
23358 &mut error,
23359 ));
23360 let producer = producer.assume_init();
23361 assert_eq!(view_text(producer.id.component_type), "generator");
23362 assert_eq!(view_text(producer.id.local_id), "sd_00");
23363 assert_eq!(view_text(producer.kind), "producer");
23364 assert!(producer.initial_on_status);
23365 assert_eq!(producer.minimum_up_time_hours, 1.0);
23366 assert_eq!(producer.minimum_down_time_hours, 1.0);
23367 assert_eq!(producer.initial_commitment.accumulated_up_time_hours, 4.0);
23368 assert_eq!(producer.initial_commitment.accumulated_down_time_hours, 0.0);
23369 assert_eq!(producer.startup_cost_adjustment_count, 1);
23370 assert_eq!(producer.startup_limit_count, 1);
23371 assert_eq!(producer.energy_upper_bound_count, 1);
23372 assert_eq!(producer.energy_lower_bound_count, 1);
23373 assert_eq!(producer.period_count, 2);
23374
23375 let uid = "sd_00";
23376 let mut found_device = std::mem::MaybeUninit::<PioScucDeviceView>::uninit();
23377 assert!(pio_ac_scuc_instance_device_get(
23378 instance,
23379 uid.as_ptr().cast(),
23380 uid.len(),
23381 found_device.as_mut_ptr(),
23382 &mut error,
23383 ));
23384 assert_eq!(view_text(found_device.assume_init().id.local_id), "sd_00");
23385
23386 let mut adjustment =
23387 std::mem::MaybeUninit::<PioScucStartupCostAdjustmentView>::uninit();
23388 assert!(pio_ac_scuc_instance_device_startup_cost_adjustment_at(
23389 instance,
23390 0,
23391 0,
23392 adjustment.as_mut_ptr(),
23393 &mut error,
23394 ));
23395 assert_eq!(adjustment.assume_init().maximum_down_time_hours, 3.0);
23396
23397 let mut limit = std::mem::MaybeUninit::<PioScucStartupLimitView>::uninit();
23398 assert!(pio_ac_scuc_instance_device_startup_limit_at(
23399 instance,
23400 0,
23401 0,
23402 limit.as_mut_ptr(),
23403 &mut error,
23404 ));
23405 assert_eq!(limit.assume_init().maximum_startups, 1);
23406
23407 let mut energy = std::mem::MaybeUninit::<PioScucEnergyRequirementView>::uninit();
23408 assert!(pio_ac_scuc_instance_device_energy_upper_bound_at(
23409 instance,
23410 0,
23411 0,
23412 energy.as_mut_ptr(),
23413 &mut error,
23414 ));
23415 assert_eq!(energy.assume_init().energy_pu, 9.0);
23416 assert!(pio_ac_scuc_instance_device_energy_lower_bound_at(
23417 instance,
23418 0,
23419 0,
23420 energy.as_mut_ptr(),
23421 &mut error,
23422 ));
23423 assert_eq!(energy.assume_init().energy_pu, 1.0);
23424
23425 let mut period = std::mem::MaybeUninit::<PioScucDevicePeriodView>::uninit();
23426 assert!(pio_ac_scuc_instance_device_period_at(
23427 instance,
23428 0,
23429 0,
23430 period.as_mut_ptr(),
23431 &mut error,
23432 ));
23433 let period = period.assume_init();
23434 assert!(period.on_status_min);
23435 assert!(period.on_status_max);
23436 assert_eq!(period.active_power_min_pu, 2.0);
23437 assert_eq!(period.active_power_max_pu, 5.0);
23438 assert_eq!(period.energy_cost_block_count, 1);
23439
23440 let mut cost_block = std::mem::MaybeUninit::<PioScucEnergyCostBlockView>::uninit();
23441 assert!(pio_ac_scuc_instance_device_energy_cost_block_at(
23442 instance,
23443 0,
23444 0,
23445 0,
23446 cost_block.as_mut_ptr(),
23447 &mut error,
23448 ));
23449 let cost_block = cost_block.assume_init();
23450 assert_eq!(cost_block.marginal_cost, 10.0);
23451 assert_eq!(cost_block.block_size_pu, 5.0);
23452
23453 let mut shunt = std::mem::MaybeUninit::<PioScucShuntView>::uninit();
23454 assert!(pio_ac_scuc_instance_shunt_at(
23455 instance,
23456 0,
23457 shunt.as_mut_ptr(),
23458 &mut error,
23459 ));
23460 let shunt = shunt.assume_init();
23461 assert_eq!(view_text(shunt.id.local_id), "sh_00");
23462 assert_eq!(shunt.conductance_per_step_pu, 0.0);
23463 assert_eq!(shunt.susceptance_per_step_pu, 3.0);
23464 assert_eq!(
23465 (shunt.step_min, shunt.initial_step, shunt.step_max),
23466 (0, 1, 4)
23467 );
23468 let shunt_uid = "sh_00";
23469 let mut found_shunt = std::mem::MaybeUninit::<PioScucShuntView>::uninit();
23470 assert!(pio_ac_scuc_instance_shunt_get(
23471 instance,
23472 shunt_uid.as_ptr().cast(),
23473 shunt_uid.len(),
23474 found_shunt.as_mut_ptr(),
23475 &mut error,
23476 ));
23477 assert_eq!(view_text(found_shunt.assume_init().id.local_id), "sh_00");
23478
23479 let mut switching = std::mem::MaybeUninit::<PioScucBranchSwitchingCostView>::uninit();
23480 assert!(pio_ac_scuc_instance_branch_switching_cost_at(
23481 instance,
23482 0,
23483 switching.as_mut_ptr(),
23484 &mut error,
23485 ));
23486 let switching = switching.assume_init();
23487 assert_eq!(view_text(switching.id.component_type), "branch");
23488 assert_eq!(view_text(switching.id.local_id), "acl_00");
23489
23490 let mut control = std::mem::MaybeUninit::<PioScucTransformerControlView>::uninit();
23491 assert!(pio_ac_scuc_instance_transformer_control_at(
23492 instance,
23493 0,
23494 control.as_mut_ptr(),
23495 &mut error,
23496 ));
23497 let control = control.assume_init();
23498 assert_eq!(view_text(control.id.component_type), "transformer");
23499 assert_eq!(view_text(control.id.local_id), "xf_00");
23500 assert_eq!((control.tap_ratio_min, control.tap_ratio_max), (1.0, 1.0));
23501
23502 let mut active_zone = std::mem::MaybeUninit::<PioScucActiveReserveZoneView>::uninit();
23503 assert!(pio_ac_scuc_instance_active_reserve_zone_at(
23504 instance,
23505 0,
23506 active_zone.as_mut_ptr(),
23507 &mut error,
23508 ));
23509 assert_eq!(active_zone.assume_init().bus_count, 2);
23510 let mut active_period =
23511 std::mem::MaybeUninit::<PioScucActiveReservePeriodView>::uninit();
23512 assert!(pio_ac_scuc_instance_active_reserve_zone_period_at(
23513 instance,
23514 0,
23515 0,
23516 active_period.as_mut_ptr(),
23517 &mut error,
23518 ));
23519 assert_eq!(active_period.assume_init().ramping_up_requirement_pu, 0.0);
23520 let mut reserve_bus = std::mem::MaybeUninit::<PioComponentIdView>::uninit();
23521 assert!(pio_ac_scuc_instance_active_reserve_zone_bus_at(
23522 instance,
23523 0,
23524 0,
23525 reserve_bus.as_mut_ptr(),
23526 &mut error,
23527 ));
23528 assert_eq!(view_text(reserve_bus.assume_init().local_id), "bus_00");
23529
23530 let mut reactive_zone =
23531 std::mem::MaybeUninit::<PioScucReactiveReserveZoneView>::uninit();
23532 assert!(pio_ac_scuc_instance_reactive_reserve_zone_at(
23533 instance,
23534 0,
23535 reactive_zone.as_mut_ptr(),
23536 &mut error,
23537 ));
23538 assert_eq!(reactive_zone.assume_init().bus_count, 2);
23539 let mut reactive_period =
23540 std::mem::MaybeUninit::<PioScucReactiveReservePeriodView>::uninit();
23541 assert!(pio_ac_scuc_instance_reactive_reserve_zone_period_at(
23542 instance,
23543 0,
23544 0,
23545 reactive_period.as_mut_ptr(),
23546 &mut error,
23547 ));
23548 assert_eq!(
23549 reactive_period.assume_init().reactive_up_requirement_pu,
23550 0.0
23551 );
23552 assert!(pio_ac_scuc_instance_reactive_reserve_zone_bus_at(
23553 instance,
23554 0,
23555 1,
23556 reserve_bus.as_mut_ptr(),
23557 &mut error,
23558 ));
23559 assert_eq!(view_text(reserve_bus.assume_init().local_id), "bus_01");
23560
23561 let mut contingency = std::mem::MaybeUninit::<PioScucContingencyView>::uninit();
23562 assert!(pio_ac_scuc_instance_contingency_at(
23563 instance,
23564 0,
23565 contingency.as_mut_ptr(),
23566 &mut error,
23567 ));
23568 let first_contingency = contingency.assume_init();
23569 assert_eq!(
23570 view_text(first_contingency.id.component_type),
23571 "contingency"
23572 );
23573 assert_eq!(view_text(first_contingency.id.local_id), "ctg_00");
23574 let contingency_uid = "ctg_02";
23575 assert!(pio_ac_scuc_instance_contingency_get(
23576 instance,
23577 contingency_uid.as_ptr().cast(),
23578 contingency_uid.len(),
23579 contingency.as_mut_ptr(),
23580 &mut error,
23581 ));
23582 assert_eq!(view_text(contingency.assume_init().id.local_id), "ctg_02");
23583 let mut component = std::mem::MaybeUninit::<PioScucContingencyComponentView>::uninit();
23584 assert!(pio_ac_scuc_instance_contingency_component_at(
23585 instance,
23586 0,
23587 0,
23588 component.as_mut_ptr(),
23589 &mut error,
23590 ));
23591 let component = component.assume_init();
23592 assert_eq!(view_text(component.id.component_type), "branch");
23593 assert_eq!(view_text(component.id.local_id), "acl_00");
23594
23595 pio_calculation_instance_release(instance);
23596 }
23597 }
23598
23599 #[test]
23600 fn one_parse_reads_a_goc3_problem_and_solution_directory() {
23601 unsafe {
23602 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/data/goc3");
23603 let path = path.to_string_lossy();
23604 let mut error = std::ptr::null_mut();
23605 let source = pio_source_open(path.as_ptr().cast(), path.len(), &mut error);
23606 assert!(!source.is_null(), "{}", error_text(error));
23607 let module = pio_parse(source, std::ptr::null(), 0, &mut error);
23608 pio_source_release(source);
23609 assert!(!module.is_null(), "{}", error_text(error));
23610
23611 let inner = PioModule::get(module).unwrap();
23612 assert_eq!(inner.module.value().type_name(), "powerio.AcScucSolution");
23613 assert_eq!(inner.module.sources().len(), 2);
23614
23615 let value = pio_module_value(module);
23616 let solution = pio_value_ac_scuc_solution(value, &mut error);
23617 pio_value_release(value);
23618 assert!(!solution.is_null(), "{}", error_text(error));
23619 assert_eq!(pio_ac_scuc_solution_time_count(solution), 2);
23620 let quantity = "bus_voltage_magnitude";
23621 let row = pio_ac_scuc_solution_get_values_at(
23622 solution,
23623 quantity.as_ptr().cast(),
23624 quantity.len(),
23625 0,
23626 &mut error,
23627 );
23628 assert!(!row.is_null(), "{}", error_text(error));
23629 let values = pio_vector_values(row);
23630 assert_eq!(
23631 std::slice::from_raw_parts(values.data, values.len),
23632 &[1.0, 0.99]
23633 );
23634 pio_vector_release(row);
23635
23636 for quantity in [
23637 "bus_voltage_magnitude",
23638 "bus_voltage_angle",
23639 "shunt_step",
23640 "ac_line_on_status",
23641 "transformer_tap_ratio",
23642 "transformer_phase_shift",
23643 "transformer_on_status",
23644 "dc_line_from_active_power",
23645 "dc_line_from_reactive_power",
23646 "dc_line_to_reactive_power",
23647 "device_on_status",
23648 "device_startup_status",
23649 "device_shutdown_status",
23650 "device_active_power",
23651 "device_reactive_power",
23652 "regulation_reserve_up",
23653 "regulation_reserve_down",
23654 "synchronized_reserve",
23655 "nonsynchronized_reserve",
23656 "ramping_reserve_up_online",
23657 "ramping_reserve_up_offline",
23658 "ramping_reserve_down_online",
23659 "ramping_reserve_down_offline",
23660 "reactive_reserve_up",
23661 "reactive_reserve_down",
23662 ] {
23663 let vector = pio_ac_scuc_solution_get_values_at(
23664 solution,
23665 quantity.as_ptr().cast(),
23666 quantity.len(),
23667 0,
23668 &mut error,
23669 );
23670 assert!(!vector.is_null(), "{quantity}: {}", error_text(error));
23671 pio_vector_release(vector);
23672 }
23673
23674 pio_calculation_solution_release(solution);
23675 pio_module_release(module);
23676 }
23677 }
23678
23679 #[test]
23680 fn one_parse_and_one_emit_cover_powsybl_formats() {
23681 unsafe {
23682 let module = parse_case9();
23683 let directory = tempfile::tempdir().unwrap();
23684
23685 for (format, output_name) in [
23686 ("xiidm", "case9.xiidm"),
23687 ("cgmes", "case9-cgmes"),
23688 ("psse-rawx", "case9.rawx"),
23689 ("ucte", "case9.uct"),
23690 ] {
23691 let output = directory.path().join(output_name);
23692 let output_text = output.to_string_lossy();
23693 let mut error = std::ptr::null_mut();
23694 let destination = pio_destination_path(
23695 output_text.as_ptr().cast(),
23696 output_text.len(),
23697 &mut error,
23698 );
23699 assert!(!destination.is_null(), "{}", error_text(error));
23700 let result = pio_emit(
23701 module,
23702 format.as_ptr().cast(),
23703 format.len(),
23704 destination,
23705 &mut error,
23706 );
23707 pio_destination_release(destination);
23708 assert!(!result.is_null(), "{format}: {}", error_text(error));
23709 pio_emit_result_release(result);
23710
23711 let source =
23712 pio_source_open(output_text.as_ptr().cast(), output_text.len(), &mut error);
23713 assert!(!source.is_null(), "{format}: {}", error_text(error));
23714 let reparsed = pio_parse(source, std::ptr::null(), 0, &mut error);
23715 pio_source_release(source);
23716 assert!(!reparsed.is_null(), "{format}: {}", error_text(error));
23717 assert_eq!(
23718 PioModule::get(reparsed).unwrap().module.value().type_name(),
23719 "powerio.BalancedNetwork"
23720 );
23721 pio_module_release(reparsed);
23722 }
23723
23724 pio_module_release(module);
23725 }
23726 }
23727
23728 #[test]
23729 fn scuc_solution_exposes_every_device_status_and_offline_reserve() {
23730 unsafe {
23731 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
23732 .join("../tests/data/goc3/goc3_small.json");
23733 let parsed = goc3_instance(Source::open(path).unwrap());
23734 let mut outputs = powerio_prob::ScucDeviceOutputs::default();
23735 outputs.startup_status = vec![vec![false, true], vec![true, false]];
23736 outputs.shutdown_status = vec![vec![true, false], vec![false, true]];
23737 outputs.p_ramp_res_up_offline = vec![vec![1.25, 1.75], vec![1.5, 2.0]];
23738 outputs.p_ramp_res_down_offline = vec![vec![2.25, 2.75], vec![2.5, 3.0]];
23739 let solution = powerio_prob::AcScucSolution::new(
23740 Arc::new(parsed),
23741 powerio_prob::Termination::Converged,
23742 powerio_prob::ScucNetworkOutputs::default(),
23743 outputs,
23744 None,
23745 )
23746 .unwrap();
23747 let module = module_handle(powerio::PioModule::new(PioValue::AcScucSolution(solution)));
23748 let value = pio_module_value(module);
23749 let mut error = std::ptr::null_mut();
23750 let solution = pio_value_ac_scuc_solution(value, &mut error);
23751 assert!(!solution.is_null(), "{}", error_text(error));
23752 pio_value_release(value);
23753 pio_module_release(module);
23754
23755 for (quantity, expected) in [
23756 ("device_startup_status", [0.0, 1.0]),
23757 ("device_shutdown_status", [1.0, 0.0]),
23758 ("ramping_reserve_up_offline", [1.25, 1.75]),
23759 ("ramping_reserve_down_offline", [2.25, 2.75]),
23760 ] {
23761 let vector = pio_ac_scuc_solution_get_values_at(
23762 solution,
23763 quantity.as_ptr().cast(),
23764 quantity.len(),
23765 0,
23766 &mut error,
23767 );
23768 assert!(!vector.is_null(), "{}", error_text(error));
23769 let view = pio_vector_values(vector);
23770 assert_eq!(view.len, 2);
23771 assert_eq!(std::slice::from_raw_parts(view.data, view.len), &expected);
23772 pio_vector_release(vector);
23773 }
23774
23775 pio_calculation_solution_release(solution);
23776 }
23777 }
23778
23779 #[test]
23780 fn update_detaches_from_retained_value_views() {
23781 unsafe {
23782 let module = parse_case9();
23783 let value = pio_module_value(module);
23784 let mut error = std::ptr::null_mut();
23785 let old_network = pio_value_balanced_network(value, &mut error);
23786 pio_value_release(value);
23787 assert!(!old_network.is_null(), "{}", error_text(error));
23788
23789 let old = PioBalancedNetwork::get(old_network)
23790 .and_then(BalancedNetworkInner::network)
23791 .unwrap();
23792 let load = &old.loads()[0];
23793 let old_active_power = load.p;
23794 let local_id = load.uid.as_deref().unwrap().to_owned();
23795
23796 let component = pio_component_id_new(
23797 c"load".as_ptr(),
23798 4,
23799 local_id.as_ptr().cast(),
23800 local_id.len(),
23801 &mut error,
23802 );
23803 let replacement = pio_active_power_from_megawatts(old_active_power + 1.0);
23804 let operating = pio_operating_point_update_set_load_active_power(
23805 component,
23806 std::ptr::null(),
23807 0,
23808 replacement,
23809 &mut error,
23810 );
23811 let update = pio_calculation_update_from_operating_point(operating, &mut error);
23812 let updates = [update.cast_const()];
23813 let report = pio_apply_updates(module, updates.as_ptr(), updates.len(), &mut error);
23814 assert!(!report.is_null(), "{}", error_text(error));
23815 assert_eq!(pio_update_report_len(report), 1);
23816 assert!(!pio_update_report_connectivity_changed(report));
23817
23818 let new_value = pio_module_value(module);
23819 let new_network = pio_value_balanced_network(new_value, &mut error);
23820 let new = PioBalancedNetwork::get(new_network)
23821 .and_then(BalancedNetworkInner::network)
23822 .unwrap();
23823 assert_eq!(new.loads()[0].p, old_active_power + 1.0);
23824 assert_eq!(
23825 PioBalancedNetwork::get(old_network)
23826 .and_then(BalancedNetworkInner::network)
23827 .unwrap()
23828 .loads()[0]
23829 .p,
23830 old_active_power,
23831 );
23832
23833 pio_balanced_network_release(new_network);
23834 pio_value_release(new_value);
23835 pio_update_report_release(report);
23836 pio_calculation_update_release(update);
23837 pio_operating_point_update_release(operating);
23838 pio_active_power_release(replacement);
23839 pio_component_id_release(component);
23840 pio_balanced_network_release(old_network);
23841 pio_module_release(module);
23842 }
23843 }
23844
23845 #[test]
23846 fn dc_calculations_return_named_generic_handles() {
23847 unsafe {
23848 let module = parse_case9();
23849 let value = pio_module_value(module);
23850 let mut error = std::ptr::null_mut();
23851 let network = pio_value_balanced_network(value, &mut error);
23852 let incidence = pio_calc_incidence_matrix(network, std::ptr::null(), 0, &mut error);
23853 assert!(!incidence.is_null(), "{}", error_text(error));
23854 assert_eq!(pio_sparse_matrix_rows(incidence), 9);
23855 assert_eq!(pio_sparse_matrix_columns(incidence), 9);
23856 assert_eq!(pio_sparse_matrix_values(incidence).len, 18);
23857
23858 let branch = pio_calc_branch_susceptances(
23859 network,
23860 c"reactance_only".as_ptr(),
23861 "reactance_only".len(),
23862 &mut error,
23863 );
23864 assert!(!branch.is_null(), "{}", error_text(error));
23865 assert_eq!(pio_vector_values(branch).len, 9);
23866
23867 pio_vector_release(branch);
23868 pio_sparse_matrix_release(incidence);
23869 pio_balanced_network_release(network);
23870 pio_value_release(value);
23871 pio_module_release(module);
23872 }
23873 }
23874
23875 const ZERO_IMPEDANCE_CASE: &str = "\
23877function mpc = case3_zero_impedance
23878mpc.version = '2';
23879mpc.baseMVA = 100;
23880mpc.bus = [
23881 1 3 0 0 0 0 1 1 0 230 1 1.1 0.9;
23882 2 1 50 0 0 0 1 1 0 230 1 1.1 0.9;
23883 3 1 50 0 0 0 1 1 0 230 1 1.1 0.9;
23884];
23885mpc.gen = [
23886 1 100 0 100 -100 1 100 1 200 0;
23887];
23888mpc.branch = [
23889 1 2 0 0 0.02 100 100 100 0 0 1 -60 60;
23890 2 3 0 0.1 0 100 100 100 0 0 1 -60 60;
23891];
23892mpc.gencost = [
23893 2 0 0 3 0.01 10 0;
23894];
23895";
23896
23897 #[test]
23898 fn dc_operators_handle_names_its_axes_and_skips_a_zero_impedance_branch() {
23899 unsafe {
23900 let name = b"zero.m";
23901 let format = b"matpower";
23902 let mut error = std::ptr::null_mut();
23903 let source = pio_source_from_memory(
23904 name.as_ptr().cast(),
23905 name.len(),
23906 ZERO_IMPEDANCE_CASE.as_ptr(),
23907 ZERO_IMPEDANCE_CASE.len(),
23908 &mut error,
23909 );
23910 assert!(!source.is_null(), "{}", error_text(error));
23911 let module = pio_parse(source, format.as_ptr().cast(), format.len(), &mut error);
23912 pio_source_release(source);
23913 assert!(!module.is_null(), "{}", error_text(error));
23914 let value = pio_module_value(module);
23915 let network = pio_value_balanced_network(value, &mut error);
23916 assert!(!network.is_null(), "{}", error_text(error));
23917
23918 let refused = pio_calc_dc_operators(network, std::ptr::null(), 0, false, &mut error);
23920 assert!(refused.is_null());
23921 assert_eq!(
23922 view_text(pio_error_code(error)),
23923 "BUILD.OPERATOR.ZERO_IMPEDANCE"
23924 );
23925 pio_error_release(error);
23926 error = std::ptr::null_mut();
23927
23928 let operators = pio_calc_dc_operators(network, std::ptr::null(), 0, true, &mut error);
23929 assert!(!operators.is_null(), "{}", error_text(error));
23930 assert_eq!(pio_dc_operators_n_buses(operators), 3);
23931 assert_eq!(pio_dc_operators_n_branches(operators), 1);
23932
23933 let bus_ids = pio_dc_operators_bus_ids(operators);
23934 assert_eq!(
23935 std::slice::from_raw_parts(bus_ids.data, bus_ids.len),
23936 &[1, 2, 3]
23937 );
23938 let branch_rows = pio_dc_operators_branch_rows(operators);
23940 assert_eq!(
23941 std::slice::from_raw_parts(branch_rows.data, branch_rows.len),
23942 &[1]
23943 );
23944 let skipped = pio_dc_operators_skipped_branch_rows(operators);
23945 assert_eq!(std::slice::from_raw_parts(skipped.data, skipped.len), &[0]);
23946 assert_ne!(
23947 view_text(pio_dc_operators_branch_identity(operators, 0)),
23948 ""
23949 );
23950 let past_end = pio_dc_operators_branch_identity(operators, 1);
23951 assert!(past_end.data.is_null());
23952 assert_eq!(past_end.len, 0);
23953
23954 let incidence = pio_dc_operators_incidence_matrix(operators, &mut error);
23955 assert!(!incidence.is_null(), "{}", error_text(error));
23956 assert_eq!(pio_sparse_matrix_rows(incidence), 1);
23957 assert_eq!(pio_sparse_matrix_columns(incidence), 3);
23958
23959 let bus_susceptance = pio_dc_operators_bus_susceptance_matrix(operators, &mut error);
23960 assert!(!bus_susceptance.is_null(), "{}", error_text(error));
23961 assert_eq!(pio_sparse_matrix_rows(bus_susceptance), 3);
23962 assert_eq!(pio_sparse_matrix_columns(bus_susceptance), 3);
23963
23964 let branch_flow = pio_dc_operators_branch_flow_matrix(operators, &mut error);
23965 assert!(!branch_flow.is_null(), "{}", error_text(error));
23966 assert_eq!(pio_sparse_matrix_rows(branch_flow), 1);
23967 assert_eq!(pio_sparse_matrix_columns(branch_flow), 3);
23968
23969 let susceptances = pio_dc_operators_branch_susceptances(operators, &mut error);
23970 assert!(!susceptances.is_null(), "{}", error_text(error));
23971 assert_eq!(pio_vector_values(susceptances).len, 1);
23972
23973 let branch_shift = pio_dc_operators_branch_phase_shift_injection(operators, &mut error);
23974 assert!(!branch_shift.is_null(), "{}", error_text(error));
23975 assert_eq!(pio_vector_values(branch_shift).len, 1);
23976
23977 let bus_shift = pio_dc_operators_bus_phase_shift_injection(operators, &mut error);
23978 assert!(!bus_shift.is_null(), "{}", error_text(error));
23979 assert_eq!(pio_vector_values(bus_shift).len, 3);
23980
23981 let angles = [0.0_f64, 0.0, 0.1];
23982 let flows = pio_dc_operators_branch_flow_dc(
23983 operators,
23984 angles.as_ptr(),
23985 angles.len(),
23986 &mut error,
23987 );
23988 assert!(!flows.is_null(), "{}", error_text(error));
23989 assert_eq!(pio_vector_values(flows).len, 1);
23990
23991 let injections = pio_dc_operators_bus_injection_dc(
23992 operators,
23993 angles.as_ptr(),
23994 angles.len(),
23995 &mut error,
23996 );
23997 assert!(!injections.is_null(), "{}", error_text(error));
23998 assert_eq!(pio_vector_values(injections).len, 3);
23999
24000 let retained = pio_dc_operators_retain(operators);
24002 assert!(!retained.is_null());
24003 pio_dc_operators_release(retained);
24004 assert_eq!(pio_dc_operators_n_branches(operators), 1);
24005
24006 assert_eq!(pio_dc_operators_n_buses(std::ptr::null()), 0);
24008 assert_eq!(pio_dc_operators_n_branches(std::ptr::null()), 0);
24009 let empty = pio_dc_operators_bus_ids(std::ptr::null());
24010 assert!(empty.data.is_null());
24011 assert_eq!(empty.len, 0);
24012
24013 pio_vector_release(injections);
24014 pio_vector_release(flows);
24015 pio_vector_release(bus_shift);
24016 pio_vector_release(branch_shift);
24017 pio_vector_release(susceptances);
24018 pio_sparse_matrix_release(branch_flow);
24019 pio_sparse_matrix_release(bus_susceptance);
24020 pio_sparse_matrix_release(incidence);
24021 pio_dc_operators_release(operators);
24022 pio_balanced_network_release(network);
24023 pio_value_release(value);
24024 pio_module_release(module);
24025 }
24026 }
24027
24028 #[test]
24029 fn powerio_ir_uses_serialize_and_deserialize() {
24030 unsafe {
24031 let module = parse_case9();
24032 let mut error = std::ptr::null_mut();
24033 let destination = pio_destination_memory(
24034 c"case.pio.json".as_ptr(),
24035 "case.pio.json".len(),
24036 &mut error,
24037 );
24038 let result = pio_module_serialize(module, destination, &mut error);
24039 assert!(!result.is_null(), "{}", error_text(error));
24040 let artifact = pio_emit_result_artifact(result, 0, &mut error);
24041 let bytes = pio_artifact_bytes(artifact);
24042 let source = pio_source_from_memory(
24043 c"case.pio.json".as_ptr(),
24044 "case.pio.json".len(),
24045 bytes.data,
24046 bytes.len,
24047 &mut error,
24048 );
24049 let decoded = pio_module_deserialize(source, &mut error);
24050 assert!(!decoded.is_null(), "{}", error_text(error));
24051
24052 pio_module_release(decoded);
24053 pio_source_release(source);
24054 pio_artifact_release(artifact);
24055 pio_emit_result_release(result);
24056 pio_destination_release(destination);
24057 pio_module_release(module);
24058 }
24059 }
24060}