use std::collections::BTreeSet;
use crate::component_category::CategoryPredicates;
use crate::{ComponentGraph, ComponentGraphConfig, Edge, Error, Node};
pub(super) fn parent_meters<N: Node, E: Edge>(
graph: &ComponentGraph<N, E>,
id: u64,
) -> Result<Option<BTreeSet<u64>>, Error> {
if !is_measurable_component(graph.component(id)?, &graph.config) {
return Ok(None);
}
let meters: BTreeSet<u64> = graph
.predecessors(id)?
.filter(|predecessor| predecessor.is_meter())
.map(|predecessor| predecessor.component_id())
.collect();
if meters.is_empty() {
return Ok(None);
}
for &meter in &meters {
let meter = graph.component(meter)?;
if is_grid_meter(graph, meter)? || !meter.provides_telemetry() {
return Ok(None);
}
}
Ok(Some(meters))
}
pub(super) fn reached_only_through<N: Node, E: Edge>(
graph: &ComponentGraph<N, E>,
id: u64,
meters: &BTreeSet<u64>,
) -> Result<bool, Error> {
reached_only_through_inner(graph, id, meters, &mut BTreeSet::new())
}
fn reached_only_through_inner<N: Node, E: Edge>(
graph: &ComponentGraph<N, E>,
id: u64,
meters: &BTreeSet<u64>,
checked: &mut BTreeSet<u64>,
) -> Result<bool, Error> {
for predecessor in graph.predecessors(id)? {
let predecessor_id = predecessor.component_id();
if meters.contains(&predecessor_id) || !checked.insert(predecessor_id) {
continue;
}
if !predecessor.is_meter()
|| !reached_only_through_inner(graph, predecessor_id, meters, checked)?
{
return Ok(false);
}
}
Ok(true)
}
pub(super) fn reaches_any_below<N: Node, E: Edge>(
graph: &ComponentGraph<N, E>,
id: u64,
set: &BTreeSet<u64>,
) -> Result<bool, Error> {
graph.reaches_any(
id,
|node| node.component_id() != id && set.contains(&node.component_id()),
petgraph::Direction::Outgoing,
)
}
pub(crate) fn is_grid_meter<N: Node, E: Edge>(
graph: &ComponentGraph<N, E>,
component: &N,
) -> Result<bool, Error> {
is_grid_meter_inner(graph, component, &mut BTreeSet::new())
}
fn is_grid_meter_inner<N: Node, E: Edge>(
graph: &ComponentGraph<N, E>,
component: &N,
checking: &mut BTreeSet<u64>,
) -> Result<bool, Error> {
let id = component.component_id();
if !checking.insert(id) || !component.is_meter() || graph.is_component_meter(id)? {
return Ok(false);
}
let has_no_siblings = graph.siblings_from_predecessors(id)?.next().is_none();
for predecessor in graph.predecessors(id)? {
if predecessor.is_grid()
|| (has_no_siblings && is_grid_meter_inner(graph, predecessor, checking)?)
{
return Ok(true);
}
}
Ok(false)
}
pub(super) fn is_measurable_component<N: Node>(node: &N, config: &ComponentGraphConfig) -> bool {
node.is_battery_inverter(config)
|| node.is_chp()
|| node.is_pv_inverter()
|| node.is_ev_charger()
|| node.is_wind_turbine()
|| node.is_steam_boiler()
}
pub(crate) fn ids_with_telemetry<N: Node, E: Edge>(
graph: &ComponentGraph<N, E>,
ids: impl IntoIterator<Item = u64>,
) -> Result<Vec<u64>, Error> {
let mut kept = Vec::new();
for id in ids {
if graph.component(id)?.provides_telemetry() {
kept.push(id);
}
}
Ok(kept)
}