rustpower 0.4.1

An experimental ECS world snapshot system built on Bevy, featuring structured archetype storage and manifest-based serialization.
Documentation
//! ECS definitions and utilities for representing power system buses.
//!
//! Includes voltage state components, identity markers, unit-aware limits,
//! and snapshot-compatible serialization logic.

use std::marker::PhantomData;

use bevy_archive::prelude::SnapshotRegistry;
use bevy_ecs::prelude::*;
use const_format::concatcp;
use derive_more::derive::{Deref, DerefMut, From, Into};
use nalgebra::Complex;

use crate::{define_snapshot, io::pandapower::Bus};

use super::units::*;

use bevy_ecs::name::Name;
#[derive(Component, Clone, serde::Serialize, serde::Deserialize)]
pub struct VBusPu(pub Complex<f64>);
#[derive(Component, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct SBusInjPu(pub Complex<f64>);
impl Default for VBusPu {
    fn default() -> Self {
        VBusPu(Complex::new(1.0, 0.0))
    }
}

#[derive(Component, Clone, serde::Serialize, serde::Deserialize)]
#[component(storage = "SparseSet")]
pub struct OutOfService;
#[derive(Component, Eq, Ord, PartialEq, PartialOrd)]
#[require(VNominal)]
#[derive(serde::Serialize, serde::Deserialize, Default)]
pub struct BusID(pub i64);
#[derive(Component, serde::Serialize, serde::Deserialize)]
pub struct BusType(pub String);

#[derive(Component, Clone, From, Into, Deref, DerefMut, serde::Serialize, serde::Deserialize)]
pub struct VmLimit<T: UnitTrait>(pub Pair<Limit<f64>, T>);
impl Default for VmLimit<PerUnit> {
    fn default() -> Self {
        Self(Pair(Limit { min: 0.9, max: 1.1 }, PhantomData))
    }
}

impl<T: UnitTrait> VmLimit<T> {
    pub fn new(min: f64, max: f64) -> Self {
        VmLimit(Pair(Limit { min, max }, PhantomData::default()))
    }
    pub fn max(&self) -> f64 {
        self.max
    }
    pub fn min(&self) -> f64 {
        self.min
    }
}
#[derive(Component, serde::Serialize, serde::Deserialize)]
pub struct VNominal(pub Pair<f64, KV>);
impl Default for VNominal {
    fn default() -> Self {
        VNominal(Pair(110.0, PhantomData::default()))
    }
}

#[derive(Component, Default, serde::Serialize, serde::Deserialize)]
pub struct Zone(pub i64);
#[derive(Bundle, Default)]
pub struct BusBundle {
    pub name: Name,
    pub bus_id: BusID,
    pub vm_pu: VmLimit<PerUnit>,
    pub vn_kv: VNominal,
    pub zone: Zone,
}

impl From<&Bus> for BusBundle {
    fn from(bus: &Bus) -> Self {
        Self {
            name: Name::new(
                bus.name
                    .as_ref()
                    .map(|x| x.clone())
                    .unwrap_or_else(|| format!("bus_{}", bus.index)),
            ),
            bus_id: BusID(bus.index),
            vm_pu: VmLimit::new(bus.min_vm_pu.unwrap_or(0.9), bus.max_vm_pu.unwrap_or(1.1)),
            vn_kv: VNominal(Pair(bus.vn_kv, PhantomData)),
            zone: Zone(bus.zone.unwrap_or(0)),
        }
    }
}
// Type alias for VmLimit<PerUnit> to use in macro
type VmLimitPerUnit = VmLimit<PerUnit>;

define_snapshot!(VmLimitPerUnit, "vm_range", PerUnit);
define_snapshot!(VNominal, "Vn", KV);
pub trait SnaptShotRegGroup {
    fn register_snap_shot(_reg: &mut SnapshotRegistry) {}
}
/// Registers all bus-related ECS components for serialization into snapshots.
///
/// Includes identity (ID, zone), metadata (`Name` via wrapper),
/// and voltage attributes (`VmLimit`, `VNominal`) via customized serialization.
pub struct BusSnapShotReg;
impl SnaptShotRegGroup for BusSnapShotReg {
    fn register_snap_shot(reg: &mut SnapshotRegistry) {
        reg.register::<BusID>();
        reg.register::<Zone>();
        reg.register_with::<Name, NameWrapper>();
        VmLimitPerUnit::register_snap_shot(reg);
        VNominal::register_snap_shot(reg);
    }
}
#[derive(Component, Default, serde::Serialize, serde::Deserialize)]
pub struct NameWrapper(pub String);
impl From<&Name> for NameWrapper {
    fn from(value: &Name) -> Self {
        NameWrapper(value.into())
    }
}
impl From<NameWrapper> for Name {
    fn from(v: NameWrapper) -> Name {
        Name::new(v.0)
    }
}
pub mod systems {

    use crate::basic::ecs::elements::NodeLookup;

    use super::*;
    /// Initializes the [`NodeLookup`] resource from all existing [`BusID`] components.
    ///
    /// Also inserts default values for [`SBusInjPu`] and [`VBusPu`] into each bus entity.

    pub fn init_node_lookup(mut cmd: Commands, bus_ids: Query<(Entity, &BusID)>) {
        let mut node_lookup = NodeLookup::default();
        bus_ids.iter().for_each(|(entity, bus_id)| {
            node_lookup.insert(bus_id.0, entity);
            cmd.entity(entity)
                .insert((SBusInjPu::default(), VBusPu::default()));
        });
        cmd.insert_resource(node_lookup);
    }
    /// Updates the [`NodeLookup`] in response to changes in [`BusID`] components.
    ///
    /// Handles both changes and removals, ensuring consistent ID ↔ entity mapping.

    pub fn update_node_lookup(
        mut lookup: ResMut<NodeLookup>,
        changed: Query<(Entity, &BusID), Changed<BusID>>,
        mut removed: RemovedComponents<BusID>,
    ) {
        for entity in removed.read() {
            lookup.remove_entity(entity);
        }

        for (entity, bus_id) in changed.iter() {
            if lookup.contains_entity(entity) {
                lookup.remove_entity(entity);
            }
            lookup.insert(bus_id.0, entity);
        }
    }
}
#[cfg(test)]
mod tests {
    use std::{env, fs::remove_file};

    use bevy_archive::prelude::{SnapshotRegistry, save_world_manifest};

    use crate::{
        basic::ecs::network::{DataOps, PowerGrid},
        io::pandapower::{Network, load_csv_zip},
    };

    use super::*;
    fn load_net() -> Network {
        let dir = env::var("CARGO_MANIFEST_DIR").unwrap();
        let folder = format!("{}/cases/IEEE118", dir);
        let name = folder.to_owned() + "/data.zip";
        let net = load_csv_zip(&name).unwrap();
        net
    }
    #[test]
    fn test_snapshot() {
        let net = load_net();
        let buses: Vec<BusBundle> = net.bus.iter().map(|x| x.into()).collect();
        let mut pf_net = PowerGrid::default();
        let mut cmd = pf_net.world_mut().commands();
        cmd.spawn_batch(buses);

        pf_net.world_mut().flush();
        let mut registry = SnapshotRegistry::default();
        BusSnapShotReg::register_snap_shot(&mut registry);
        let world = pf_net.world();
        let a = save_world_manifest(world, &registry).unwrap();
        a.to_file("test_bus.toml", None).unwrap();
        remove_file("test_bus.toml").unwrap();
    }
}