use crate::codes;
use crate::{BalancedNetwork, GeoApplyReport, GeoLayer};
use powerio_core::{Diagnostic, HistoryEntry, HistoryId, HistoryKind};
use powerio_dist::{MulticonductorNetwork, NeutralKronOptions, NeutralKronReport};
#[cfg(test)]
use powerio_tnd::transform::capped_history_notes;
#[doc(inline)]
pub use powerio_tnd::transform::{
MulticonductorToBalancedError, MulticonductorToBalancedOptions, MulticonductorToBalancedReport,
MulticonductorToBalancedTransformation, SequenceTransformConvention, to_balanced_network,
to_balanced_network_report,
};
use std::collections::BTreeMap;
pub fn to_balanced_report(
module: &powerio_core::PioModule<crate::PioValue>,
options: MulticonductorToBalancedOptions,
) -> Result<MulticonductorToBalancedReport, powerio_core::Error> {
let crate::PioValue::MulticonductorNetwork(net) = &module.value() else {
return Err(wrong_kind_error(module.value()));
};
Ok(to_balanced_network_report(net, options))
}
#[allow(clippy::result_large_err)]
pub fn to_balanced(
module: powerio_core::PioModule<crate::PioValue>,
options: MulticonductorToBalancedOptions,
) -> Result<
powerio_core::PioModule<crate::PioValue>,
(
powerio_core::PioModule<crate::PioValue>,
Box<MulticonductorToBalancedError>,
),
> {
let crate::PioValue::MulticonductorNetwork(net) = &module.value() else {
let error = MulticonductorToBalancedError::new(
options,
&[Diagnostic::of(
&codes::TRANSFORM_MULTI_TO_BALANCED_WRONG_MODEL_KIND,
format!(
"the module carries a {} value; the balanced lowering takes a \
multiconductor network",
module.value().type_name()
),
)],
);
return Err((module, Box::new(error)));
};
let lowering = match to_balanced_network(net, options) {
Ok(lowering) => lowering,
Err(error) => return Err((module, Box::new(error))),
};
let MulticonductorToBalancedTransformation {
network,
diagnostics,
history,
..
} = lowering;
let diagnostics_room =
powerio_core::limits::MAX_MODULE_DIAGNOSTICS.saturating_sub(module.diagnostics().len());
let history_room =
powerio_core::limits::MAX_MODULE_HISTORY_ENTRIES.saturating_sub(module.history().len());
if diagnostics.len() > diagnostics_room || history_room == 0 {
let error = MulticonductorToBalancedError::new(
options,
&[Diagnostic::of(
&codes::TRANSFORM_MULTI_TO_BALANCED_RECORD_CAP,
"the module cannot hold the lowering's findings and history entry; export a \
fresh module before lowering"
.to_string(),
)],
);
return Err((module, Box::new(error)));
}
let mut module = module
.map_value(|_| crate::PioValue::BalancedNetwork(network))
.sever_source();
module.sever_value_targets();
for diagnostic in &diagnostics {
let mut diagnostic = diagnostic.clone();
diagnostic.clear_target();
module
.add_diagnostic(diagnostic)
.expect("room was checked; pass diagnostics carry no identity and no span");
}
let entry = copy_history_with_id(
&history,
unused_history_id(&module, "multiconductor-to-balanced"),
);
module
.add_history_entry(entry)
.expect("room was checked and the history id is unique by construction");
Ok(module)
}
fn derive_balanced_calculation<I>(
module: &powerio_core::PioModule<crate::PioValue>,
operation: &'static str,
output_type: &'static str,
build: impl FnOnce(BalancedNetwork) -> Result<I, powerio_core::Error>,
) -> Result<powerio_core::PioModule<I>, powerio_core::Error> {
if !matches!(module.value(), crate::PioValue::BalancedNetwork(_)) {
return Err(powerio_core::Error::new(
&codes::REQUEST_MODULE_WRONG_MODEL_KIND,
format!(
"{operation} requires powerio.BalancedNetwork; the module contains {}",
module.value().type_name()
),
));
}
let history = HistoryEntry::new(
unused_history_id(module, operation),
HistoryKind::Transform,
operation,
)?
.with_input_type("powerio.BalancedNetwork")?
.with_output_type(output_type)?;
let producer = powerio_core::Producer::new("powerio", crate::VERSION)?;
module.clone().try_derive_value(producer, history, |value| {
let crate::PioValue::BalancedNetwork(network) = value else {
unreachable!("the value type was checked before derivation")
};
build(network)
})
}
fn derive_calculation<I>(
module: &powerio_core::PioModule<crate::PioValue>,
operation: &'static str,
output_type: &'static str,
value: Result<I, powerio_core::Error>,
) -> Result<powerio_core::PioModule<I>, powerio_core::Error> {
let value = value?;
let history = HistoryEntry::new(
unused_history_id(module, operation),
HistoryKind::Transform,
operation,
)?
.with_input_type(module.value().type_name())?
.with_output_type(output_type)?;
let producer = powerio_core::Producer::new("powerio", crate::VERSION)?;
module
.clone()
.try_derive_value(producer, history, |_| Ok(value))
}
fn derive_multiconductor_calculation<I>(
module: &powerio_core::PioModule<crate::PioValue>,
operation: &'static str,
output_type: &'static str,
build: impl FnOnce(MulticonductorNetwork) -> Result<I, powerio_core::Error>,
) -> Result<powerio_core::PioModule<I>, powerio_core::Error> {
if !matches!(module.value(), crate::PioValue::MulticonductorNetwork(_)) {
return Err(powerio_core::Error::new(
&codes::REQUEST_MODULE_WRONG_MODEL_KIND,
format!(
"{operation} requires powerio.MulticonductorNetwork; the module contains {}",
module.value().type_name()
),
));
}
let history = HistoryEntry::new(
unused_history_id(module, operation),
HistoryKind::Transform,
operation,
)?
.with_input_type("powerio.MulticonductorNetwork")?
.with_output_type(output_type)?;
let producer = powerio_core::Producer::new("powerio", crate::VERSION)?;
module.clone().try_derive_value(producer, history, |value| {
let crate::PioValue::MulticonductorNetwork(network) = value else {
unreachable!("the value type was checked before derivation")
};
build(network)
})
}
pub fn apply_geo_layer(
module: &powerio_core::PioModule<crate::PioValue>,
layer: &GeoLayer,
) -> Result<(powerio_core::PioModule<crate::PioValue>, GeoApplyReport), powerio_core::Error> {
let type_name = match &module.value() {
crate::PioValue::BalancedNetwork(_) => "powerio.BalancedNetwork",
crate::PioValue::MulticonductorNetwork(_) => "powerio.MulticonductorNetwork",
value => {
return Err(powerio_core::Error::new(
&codes::REQUEST_MODULE_WRONG_MODEL_KIND,
format!(
"apply_geo_layer requires powerio.BalancedNetwork or \
powerio.MulticonductorNetwork; the module contains {}",
value.type_name()
),
));
}
};
let history = HistoryEntry::new(
unused_history_id(module, "apply-geo-layer"),
HistoryKind::Transform,
"apply_geo_layer",
)?
.with_input_type(type_name)?
.with_output_type(type_name)?;
let producer = powerio_core::Producer::new("powerio", crate::VERSION)?;
let mut report = None;
let derived = module
.clone()
.try_derive_value(producer, history, |mut value| {
let applied = match &mut value {
crate::PioValue::BalancedNetwork(network) => network.apply_geo_layer(layer),
crate::PioValue::MulticonductorNetwork(network) => {
crate::dist_geo::apply_dist_geo_layer(network, layer)
}
value => {
return Err(powerio_core::Error::new(
&codes::REQUEST_MODULE_WRONG_MODEL_KIND,
format!(
"apply_geo_layer requires powerio.BalancedNetwork or \
powerio.MulticonductorNetwork; the module contains {}",
value.type_name()
),
));
}
};
report = Some(applied);
Ok(value)
})?;
let report = report.ok_or_else(|| {
powerio_core::Error::new(
&codes::REQUEST_MODULE_WRONG_MODEL_KIND,
"apply_geo_layer did not receive a network value",
)
})?;
Ok((derived, report))
}
pub fn neutral_kron(
module: &powerio_core::PioModule<crate::PioValue>,
) -> Result<(powerio_core::PioModule<crate::PioValue>, NeutralKronReport), powerio_core::Error> {
neutral_kron_with_options(module, &NeutralKronOptions::default())
}
pub fn neutral_kron_with_options(
module: &powerio_core::PioModule<crate::PioValue>,
options: &NeutralKronOptions,
) -> Result<(powerio_core::PioModule<crate::PioValue>, NeutralKronReport), powerio_core::Error> {
let crate::PioValue::MulticonductorNetwork(network) = module.value() else {
return Err(powerio_core::Error::new(
&codes::REQUEST_MODULE_WRONG_MODEL_KIND,
format!(
"neutral_kron requires powerio.MulticonductorNetwork; the module contains {}",
module.value().type_name()
),
));
};
let reduction = powerio_dist::neutral_kron_reduce(network, options).map_err(|cause| {
powerio_core::Error::new(cause.code(), cause.to_string()).with_cause(cause)
})?;
let (network, report) = reduction.into_parts();
let neutral_terminals = options
.neutral_terminals
.iter()
.map(|(bus, terminal)| (bus.clone(), serde_json::Value::String(terminal.clone())))
.collect();
let parameters = BTreeMap::from([
(
"allow_forced_ideal_ground".to_owned(),
serde_json::Value::Bool(options.allow_forced_ideal_ground),
),
(
"neutral_terminals".to_owned(),
serde_json::Value::Object(neutral_terminals),
),
]);
let history = HistoryEntry::new(
unused_history_id(module, "neutral_kron"),
HistoryKind::Transform,
"neutral_kron",
)?
.with_input_type("powerio.MulticonductorNetwork")?
.with_output_type("powerio.MulticonductorNetwork")?
.with_parameters(parameters)?;
let producer = powerio_core::Producer::new("powerio", crate::VERSION)?;
let mut derived = module.clone().try_derive_value(
producer,
history,
move |value| match value {
crate::PioValue::MulticonductorNetwork(_) => {
Ok(crate::PioValue::MulticonductorNetwork(network))
}
value => Err(powerio_core::Error::new(
&codes::REQUEST_MODULE_WRONG_MODEL_KIND,
format!(
"neutral_kron requires powerio.MulticonductorNetwork; the module contains {}",
value.type_name()
),
)),
},
)?;
for diagnostic in &report.diagnostics {
let mut diagnostic = diagnostic.clone();
diagnostic.clear_target();
derived.add_diagnostic(diagnostic)?;
}
Ok((derived, report))
}
#[must_use]
pub fn network_with_operating_point(
point: &powerio_prob::OperatingPoint<BalancedNetwork>,
) -> BalancedNetwork {
crate::write::network_with_balanced_operating_point(point, "network").0
}
pub fn to_dc_pf_instance(
module: &powerio_core::PioModule<crate::PioValue>,
) -> Result<powerio_core::PioModule<powerio_prob::DcPfInstance>, powerio_core::Error> {
if matches!(module.value(), crate::PioValue::DcPfInstance(_)) {
return Ok(module.clone().map_value(|value| match value {
crate::PioValue::DcPfInstance(instance) => instance,
_ => unreachable!("the value type was checked before extraction"),
}));
}
derive_balanced_calculation(
module,
"to_dc_pf_instance",
"powerio.DcPfInstance",
powerio_prob::DcPfInstance::from_network,
)
}
pub fn to_ac_pf_instance(
module: &powerio_core::PioModule<crate::PioValue>,
) -> Result<powerio_core::PioModule<powerio_prob::AcPfInstance>, powerio_core::Error> {
if matches!(module.value(), crate::PioValue::AcPfInstance(_)) {
return Ok(module.clone().map_value(|value| match value {
crate::PioValue::AcPfInstance(instance) => instance,
_ => unreachable!("the value type was checked before extraction"),
}));
}
derive_balanced_calculation(
module,
"to_ac_pf_instance",
"powerio.AcPfInstance",
powerio_prob::AcPfInstance::from_network,
)
}
pub fn to_dc_opf_instance(
module: &powerio_core::PioModule<crate::PioValue>,
) -> Result<powerio_core::PioModule<powerio_prob::DcOpfInstance>, powerio_core::Error> {
if matches!(module.value(), crate::PioValue::DcOpfInstance(_)) {
return Ok(module.clone().map_value(|value| match value {
crate::PioValue::DcOpfInstance(instance) => instance,
_ => unreachable!("the value type was checked before extraction"),
}));
}
derive_balanced_calculation(
module,
"to_dc_opf_instance",
"powerio.DcOpfInstance",
powerio_prob::DcOpfInstance::from_network,
)
}
pub fn to_ac_opf_instance(
module: &powerio_core::PioModule<crate::PioValue>,
) -> Result<powerio_core::PioModule<powerio_prob::AcOpfInstance>, powerio_core::Error> {
if matches!(module.value(), crate::PioValue::AcOpfInstance(_)) {
return Ok(module.clone().map_value(|value| match value {
crate::PioValue::AcOpfInstance(instance) => instance,
_ => unreachable!("the value type was checked before extraction"),
}));
}
derive_balanced_calculation(
module,
"to_ac_opf_instance",
"powerio.AcOpfInstance",
powerio_prob::AcOpfInstance::from_network,
)
}
pub fn to_ac_ots_instance(
module: &powerio_core::PioModule<crate::PioValue>,
) -> Result<powerio_core::PioModule<powerio_prob::AcOtsInstance>, powerio_core::Error> {
match module.value() {
crate::PioValue::AcOtsInstance(_) => Ok(module.clone().map_value(|value| match value {
crate::PioValue::AcOtsInstance(instance) => instance,
_ => unreachable!("the value type was checked before extraction"),
})),
crate::PioValue::AcOpfInstance(base) => derive_calculation(
module,
"to_ac_ots_instance",
"powerio.AcOtsInstance",
powerio_prob::AcOtsInstance::from_ac_opf(base.clone()),
),
_ => derive_balanced_calculation(
module,
"to_ac_ots_instance",
"powerio.AcOtsInstance",
powerio_prob::AcOtsInstance::from_network,
),
}
}
pub fn to_ac_ots_instance_with_options(
module: &powerio_core::PioModule<crate::PioValue>,
options: powerio_prob::AcOtsOptions,
) -> Result<powerio_core::PioModule<powerio_prob::AcOtsInstance>, powerio_core::Error> {
let configure = |instance: Result<powerio_prob::AcOtsInstance, powerio_core::Error>| {
instance.map(powerio_prob::AcOtsInstance::with_switching_objective)
};
match module.value() {
crate::PioValue::AcOtsInstance(instance) => derive_calculation(
module,
"to_ac_ots_instance",
"powerio.AcOtsInstance",
configure(instance.clone().with_options(options)),
),
crate::PioValue::AcOpfInstance(base) => derive_calculation(
module,
"to_ac_ots_instance",
"powerio.AcOtsInstance",
configure(powerio_prob::AcOtsInstance::new(base.clone(), options)),
),
_ => derive_balanced_calculation(
module,
"to_ac_ots_instance",
"powerio.AcOtsInstance",
|network| {
configure(powerio_prob::AcOtsInstance::new(
powerio_prob::AcOpfInstance::from_network(network)?,
options,
))
},
),
}
}
pub fn ac_ots_instance_from_base(
module: &powerio_core::PioModule<crate::PioValue>,
base: &powerio_prob::AcOpfInstance,
options: powerio_prob::AcOtsOptions,
) -> Result<powerio_core::PioModule<powerio_prob::AcOtsInstance>, powerio_core::Error> {
derive_calculation(
module,
"ac_ots_instance",
"powerio.AcOtsInstance",
powerio_prob::AcOtsInstance::new(base.clone(), options),
)
}
pub fn fix_ac_ots_topology(
module: &powerio_core::PioModule<crate::PioValue>,
instance: &powerio_prob::AcOtsInstance,
branch_closed: &[bool],
) -> Result<powerio_core::PioModule<powerio_prob::AcOpfInstance>, powerio_core::Error> {
let (fixed, diagnostics) = instance.fix_topology(branch_closed)?;
let mut derived = derive_calculation(
module,
"fix_ac_ots_topology",
"powerio.AcOpfInstance",
Ok(fixed),
)?;
for mut diagnostic in diagnostics {
diagnostic.clear_target();
derived.add_diagnostic(diagnostic)?;
}
Ok(derived)
}
pub fn ac_ots_solution(
module: &powerio_core::PioModule<crate::PioValue>,
instance: &powerio_prob::AcOtsInstance,
branch_closed: Vec<bool>,
ac_solution: powerio_prob::AcOpfSolution,
) -> Result<powerio_core::PioModule<powerio_prob::AcOtsSolution>, powerio_core::Error> {
derive_calculation(
module,
"ac_ots_solution",
"powerio.AcOtsSolution",
powerio_prob::AcOtsSolution::new(
std::sync::Arc::new(instance.clone()),
branch_closed,
ac_solution,
),
)
}
pub fn materialize_ac_ots_solution(
module: &powerio_core::PioModule<crate::PioValue>,
solution: &powerio_prob::AcOtsSolution,
) -> Result<powerio_core::PioModule<BalancedNetwork>, powerio_core::Error> {
let mut network = crate::write::network_with_ac_opf_solution(solution.ac_solution());
network.assign_missing_component_ids();
derive_calculation(
module,
"materialize_ac_ots_solution",
"powerio.BalancedNetwork",
Ok(network),
)
}
pub fn to_mc_ac_pf_instance(
module: &powerio_core::PioModule<crate::PioValue>,
) -> Result<powerio_core::PioModule<powerio_prob::McAcPfInstance>, powerio_core::Error> {
if matches!(module.value(), crate::PioValue::McAcPfInstance(_)) {
return Ok(module.clone().map_value(|value| match value {
crate::PioValue::McAcPfInstance(instance) => instance,
_ => unreachable!("the value type was checked before extraction"),
}));
}
derive_multiconductor_calculation(
module,
"to_mc_ac_pf_instance",
"powerio.McAcPfInstance",
powerio_prob::McAcPfInstance::from_network,
)
}
pub fn to_mc_ac_opf_instance(
module: &powerio_core::PioModule<crate::PioValue>,
) -> Result<powerio_core::PioModule<powerio_prob::McAcOpfInstance>, powerio_core::Error> {
if matches!(module.value(), crate::PioValue::McAcOpfInstance(_)) {
return Ok(module.clone().map_value(|value| match value {
crate::PioValue::McAcOpfInstance(instance) => instance,
_ => unreachable!("the value type was checked before extraction"),
}));
}
derive_multiconductor_calculation(
module,
"to_mc_ac_opf_instance",
"powerio.McAcOpfInstance",
powerio_prob::McAcOpfInstance::from_network,
)
}
pub fn to_lindist3flow_opf_instance(
module: &powerio_core::PioModule<crate::PioValue>,
) -> Result<powerio_core::PioModule<powerio_prob::LinDist3FlowOpfInstance>, powerio_core::Error> {
to_lindist3flow_opf_instance_with_options(
module,
powerio_prob::LinDist3FlowBuildOptions::default(),
)
}
pub fn to_lindist3flow_opf_instance_with_options(
module: &powerio_core::PioModule<crate::PioValue>,
options: powerio_prob::LinDist3FlowBuildOptions,
) -> Result<powerio_core::PioModule<powerio_prob::LinDist3FlowOpfInstance>, powerio_core::Error> {
if matches!(module.value(), crate::PioValue::LinDist3FlowOpfInstance(_)) {
return Ok(module.clone().map_value(|value| match value {
crate::PioValue::LinDist3FlowOpfInstance(instance) => instance,
_ => unreachable!("the value type was checked before extraction"),
}));
}
if !matches!(module.value(), crate::PioValue::MulticonductorNetwork(_)) {
return Err(powerio_core::Error::new(
&codes::REQUEST_MODULE_WRONG_MODEL_KIND,
format!(
"to_lindist3flow_opf_instance requires powerio.MulticonductorNetwork; the module contains {}",
module.value().type_name()
),
));
}
let options_value = serde_json::to_value(options).map_err(|cause| {
powerio_core::Error::new(
&codes::TRANSFORM_LINDIST3FLOW_OPTIONS_SERIALIZE_FAILED,
"could not record LinDist3Flow build options in transform history",
)
.with_cause(cause)
})?;
let serde_json::Value::Object(parameters) = options_value else {
return Err(powerio_core::Error::new(
&codes::TRANSFORM_LINDIST3FLOW_OPTIONS_SERIALIZE_FAILED,
"LinDist3Flow build options did not serialize as an object",
));
};
let history = HistoryEntry::new(
unused_history_id(module, "to_lindist3flow_opf_instance"),
HistoryKind::Transform,
"to_lindist3flow_opf_instance",
)?
.with_input_type("powerio.MulticonductorNetwork")?
.with_output_type("powerio.LinDist3FlowOpfInstance")?
.with_parameters(parameters.into_iter().collect())?;
let producer = powerio_core::Producer::new("powerio", crate::VERSION)?;
module.clone().try_derive_value(producer, history, |value| {
let crate::PioValue::MulticonductorNetwork(network) = value else {
unreachable!("the value type was checked before derivation")
};
powerio_prob::LinDist3FlowOpfInstance::from_network(network, options)
})
}
pub fn to_lindist3flow_pf_instance(
module: &powerio_core::PioModule<crate::PioValue>,
) -> Result<powerio_core::PioModule<powerio_prob::LinDist3FlowPfInstance>, powerio_core::Error> {
to_lindist3flow_pf_instance_with_options(
module,
powerio_prob::LinDist3FlowBuildOptions::default(),
)
}
pub fn to_lindist3flow_pf_instance_with_options(
module: &powerio_core::PioModule<crate::PioValue>,
options: powerio_prob::LinDist3FlowBuildOptions,
) -> Result<powerio_core::PioModule<powerio_prob::LinDist3FlowPfInstance>, powerio_core::Error> {
if matches!(module.value(), crate::PioValue::LinDist3FlowPfInstance(_)) {
return Ok(module.clone().map_value(|value| match value {
crate::PioValue::LinDist3FlowPfInstance(instance) => instance,
_ => unreachable!("the value type was checked before extraction"),
}));
}
if !matches!(module.value(), crate::PioValue::MulticonductorNetwork(_)) {
return Err(powerio_core::Error::new(
&codes::REQUEST_MODULE_WRONG_MODEL_KIND,
format!(
"to_lindist3flow_pf_instance requires powerio.MulticonductorNetwork; the module contains {}",
module.value().type_name()
),
));
}
let options_value = serde_json::to_value(options).map_err(|cause| {
powerio_core::Error::new(
&codes::TRANSFORM_LINDIST3FLOW_OPTIONS_SERIALIZE_FAILED,
"could not record LinDist3Flow build options in transform history",
)
.with_cause(cause)
})?;
let serde_json::Value::Object(parameters) = options_value else {
return Err(powerio_core::Error::new(
&codes::TRANSFORM_LINDIST3FLOW_OPTIONS_SERIALIZE_FAILED,
"LinDist3Flow build options did not serialize as an object",
));
};
let history = HistoryEntry::new(
unused_history_id(module, "to_lindist3flow_pf_instance"),
HistoryKind::Transform,
"to_lindist3flow_pf_instance",
)?
.with_input_type("powerio.MulticonductorNetwork")?
.with_output_type("powerio.LinDist3FlowPfInstance")?
.with_parameters(parameters.into_iter().collect())?;
let producer = powerio_core::Producer::new("powerio", crate::VERSION)?;
module.clone().try_derive_value(producer, history, |value| {
let crate::PioValue::MulticonductorNetwork(network) = value else {
unreachable!("the value type was checked before derivation")
};
powerio_prob::LinDist3FlowPfInstance::from_network(network, options)
})
}
fn copy_history_with_id(history: &HistoryEntry, id: HistoryId) -> HistoryEntry {
let mut copied = HistoryEntry::new(id, history.kind(), history.name())
.expect("the existing history name is valid")
.with_parameters(history.parameters().clone())
.expect("the existing parameter set is valid");
if let Some(type_name) = history.input_type() {
copied = copied
.with_input_type(type_name)
.expect("the existing input type is valid");
}
if let Some(type_name) = history.output_type() {
copied = copied
.with_output_type(type_name)
.expect("the existing output type is valid");
}
for assumption in history.assumptions() {
copied = copied
.with_assumption(assumption.clone())
.expect("the existing assumption list is valid");
}
for loss in history.losses() {
copied = copied
.with_loss(loss.clone())
.expect("the existing loss list is valid");
}
copied
}
fn unused_history_id(
module: &powerio_core::PioModule<crate::PioValue>,
base: &str,
) -> powerio_core::HistoryId {
use powerio_core::HistoryId;
let taken: std::collections::BTreeSet<&str> = module
.history()
.iter()
.map(|entry| entry.id().as_str())
.collect();
if !taken.contains(base) {
return HistoryId::new(base).expect("static id is valid");
}
let mut counter = 2usize;
loop {
let candidate = format!("{base}-{counter}");
if !taken.contains(candidate.as_str()) {
return HistoryId::new(candidate).expect("numbered id is valid");
}
counter += 1;
}
}
fn wrong_kind_error(value: &crate::PioValue) -> powerio_core::Error {
powerio_core::Error::new(
&codes::TRANSFORM_MULTI_TO_BALANCED_WRONG_MODEL_KIND,
format!(
"the module carries a {} value; the balanced lowering takes a multiconductor \
network",
value.type_name()
),
)
}
#[cfg(test)]
mod history_record_tests {
use super::{capped_history_notes, unused_history_id};
#[test]
fn note_overflow_is_stated_within_the_cap() {
let cap = powerio_core::limits::MAX_HISTORY_NOTES;
let notes: Vec<String> = (0..cap + 40).map(|index| format!("note {index}")).collect();
let kept = capped_history_notes(notes, "assumptions");
assert_eq!(kept.len(), cap);
assert_eq!(kept.last().unwrap(), "41 more assumptions elided");
let short: Vec<String> = (0..3).map(|index| format!("note {index}")).collect();
assert_eq!(capped_history_notes(short.clone(), "assumptions"), short);
}
#[test]
fn the_history_id_is_minted_unused() {
use powerio_core::{HistoryEntry, HistoryId, HistoryKind, PioModule};
let mut module = PioModule::new(crate::PioValue::BalancedNetwork(
crate::BalancedNetwork::in_memory("t", 100.0, Vec::new(), Vec::new()),
));
assert_eq!(
unused_history_id(&module, "multiconductor-to-balanced").as_str(),
"multiconductor-to-balanced"
);
for id in ["multiconductor-to-balanced", "multiconductor-to-balanced-2"] {
module
.add_history_entry(
HistoryEntry::new(
HistoryId::new(id).unwrap(),
HistoryKind::Transform,
"to_balanced",
)
.unwrap(),
)
.unwrap();
}
assert_eq!(
unused_history_id(&module, "multiconductor-to-balanced").as_str(),
"multiconductor-to-balanced-3"
);
}
}