power_systems 0.1.0

Structures for power system modeling, simulation and analysis
Documentation
use convert_case::{Case, Casing};
use gtmpl_derive::Gtmpl;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::io::Write;
use std::path::PathBuf;

#[derive(Deserialize, Serialize, Gtmpl)]
pub struct PowerSystemStructs {
    pub auto_generated_structs: Vec<AutoGeneratedStruct>,
}

#[derive(Clone, Deserialize, Serialize, Gtmpl)]
pub struct AutoGeneratedStruct {
    pub struct_name: String,
    pub docstring: Option<String>,
    pub fields: Vec<Field>,
    pub supertype: String,

    pub struct_name_snake: Option<String>,
}

#[derive(Clone, Deserialize, Serialize, Gtmpl)]
pub struct Field {
    pub name: String,
    pub comment: Option<String>,
    // pub null_value: Option<String>,
    pub data_type: String,

    pub rename: Option<String>,
}

const TEMPLATE: &str = r#"
//use crate::common::*;
use serde::{Deserialize, Serialize};

{{ range $st := .auto_generated_structs }}
{{ with .docstring }}/// {{.}}{{ end }}
#[derive(Clone, Deserialize, Serialize, Default, PartialEq, Debug)]
pub struct {{ .struct_name }} {
    #[serde(rename = "__metadata__")]
    pub metadata: Metadata,
{{- range $fld := .fields }}
    {{- with .comment }}
    /// {{.}}
    {{- end}}
    {{- with .rename }}
    #[serde(rename = "{{.}}")]
    {{- end}}
    pub {{ .name }}: {{ .data_type }},
{{- end }}
}

impl {{ .struct_name }} {
    pub const SUPER_TYPE: &'static str = "{{ .supertype }}";
}
{{ end }}

#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
pub enum ComponentType {
{{- range $st := .auto_generated_structs }}
    {{ .struct_name }},
{{- end }}
    //DynamicGenerator,
}

// #[derive(Deserialize, Serialize, Default)]
#[derive(Serialize, Default)]
pub struct SystemData {
    pub time_series_in_memory: Option<bool>,
    pub time_series_compression_enabled: Option<bool>,
    //pub time_series_params: Option<TimeSeriesParameters>,

{{- range $st := .auto_generated_structs }}
    pub {{ .struct_name_snake }}_vec: Vec<{{ .struct_name }}>,
{{- end }}
    //pub dynamic_generator_vec: Vec<DynamicGenerator>,
}

pub fn push_component(data: &mut SystemData, kind: ComponentType, value: serde_json::Value) {
    match kind {
{{- range $st := .auto_generated_structs }}
        ComponentType::{{ .struct_name }} => data.{{ .struct_name_snake }}_vec.push({{ .struct_name }}::deserialize(value).unwrap()),
{{- end }}
        //ComponentType::DynamicGenerator => data.dynamic_generator_vec.push(DynamicGenerator::deserialize(value).unwrap()),
    }
}"#;

fn generate_structs() -> Result<(), String> {
    let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    d.push("descriptors/power_system_structs.json");

    let mut dt_map: HashMap<String, String> = HashMap::new();
    dt_map.insert("Int".to_string(), "usize".to_string());
    dt_map.insert("Float64".to_string(), "f64".to_string());
    dt_map.insert("String".to_string(), "String".to_string());
    dt_map.insert("Bool".to_string(), "bool".to_string());
    dt_map.insert("Complex{Float64}".to_string(), "Cmplx64".to_string());
    dt_map.insert(
        "VariableCost".to_string(),
        "VariableCost<(f64,f64)>".to_string(),
    );
    dt_map.insert(
        "Dict{String, Any}".to_string(),
        "serde_json::Value".to_string(),
    );
    dt_map.insert("Array{Float64,2}".to_string(), "[f64; 2]".to_string());
    dt_map.insert("Vector{Float64}".to_string(), "Vec<f64>".to_string());
    dt_map.insert("Vector{Float64}".to_string(), "Vec<f64>".to_string());
    dt_map.insert(
        "Vector{Tuple{Float64,Float64}}".to_string(),
        "Vec<(f64,f64)>".to_string(),
    );
    dt_map.insert("Vector{Symbol}".to_string(), "Vec<String>".to_string());
    dt_map.insert("Vector{Service}".to_string(), "Vec<Service>".to_string());
    dt_map.insert(
        "Vector{StateTypes}".to_string(),
        "Vec<StateTypes>".to_string(),
    );
    dt_map.insert(
        "Union{Nothing, Min_Max}".to_string(),
        "Option<MinMax<f64>>".to_string(),
    );
    dt_map.insert(
        "Union{Nothing, Float64}".to_string(),
        "Option<f64>".to_string(),
    );
    dt_map.insert(
        "Union{Nothing, Area}".to_string(),
        "Option<UUID>".to_string(),
    );
    dt_map.insert(
        "Union{Nothing, LoadZone}".to_string(),
        "Option<UUID>".to_string(),
    );
    dt_map.insert(
        "Union{Nothing, OperationalCost}".to_string(),
        "Option<UUID>".to_string(),
    );
    dt_map.insert(
        "Union{Nothing, DynamicInjection}".to_string(),
        "Option<UUID>".to_string(),
    );
    dt_map.insert(
        "Union{Nothing, IS.TimeSeriesKey}".to_string(),
        "Option<TimeSeriesKey>".to_string(),
    );
    dt_map.insert(
        "Union{Nothing, BusTypes}".to_string(),
        "Option<BusTypes>".to_string(),
    );
    dt_map.insert(
        "Union{Nothing, LoadModels}".to_string(),
        "Option<LoadModels>".to_string(),
    );
    dt_map.insert(
        "Union{Nothing, NamedTuple{(:min, :max), Tuple{Float64, Float64}}}".to_string(),
        "Option<MinMax<f64>>".to_string(),
    );
    dt_map.insert(
        "Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}".to_string(),
        "Option<UpDown<f64>>".to_string(),
    );
    dt_map.insert(
        "Union{Nothing, NamedTuple{(:startup, :shutdown), Tuple{Float64, Float64}}}".to_string(),
        "Option<StartupShutdown<f64>>".to_string(),
    );
    dt_map.insert(
        "Union{Nothing, NamedTuple{(:hot, :warm, :cold), Tuple{Float64, Float64, Float64}}}"
            .to_string(),
        "Option<HotWarmCold<f64>>".to_string(),
    );

    dt_map.insert(
        "Tuple{Float64, Float64}".to_string(),
        "(f64,f64)".to_string(),
    );
    dt_map.insert(
        "NamedTuple{(:from, :to), Tuple{Float64, Float64}}".to_string(),
        "FromTo<f64>".to_string(),
    );
    dt_map.insert(
        "NamedTuple{(:from_to, :to_from), Tuple{Float64, Float64}}".to_string(),
        "FlowLimit<f64>".to_string(),
    );
    dt_map.insert(
        "NamedTuple{(:min, :max), Tuple{Float64, Float64}}".to_string(),
        "MinMax<f64>".to_string(),
    );
    dt_map.insert(
        "NamedTuple{(:in, :out), Tuple{Float64, Float64}}".to_string(),
        "Efficiency<f64>".to_string(),
    );
    dt_map.insert(
        "NamedTuple{(:up, :down), Tuple{Float64, Float64}}".to_string(),
        "UpDown<f64>".to_string(),
    );
    dt_map.insert(
        "NamedTuple{(:l0, :l1), Tuple{Float64, Float64}}".to_string(),
        "L0L1<f64>".to_string(),
    );
    dt_map.insert(
        "NamedTuple{(:hot, :warm, :cold), NTuple{3, Float64}}".to_string(),
        "HotWarmCold<f64>".to_string(),
    );
    dt_map.insert(
        "InfrastructureSystems.TimeSeriesContainer".to_string(),
        "TimeSeriesContainer".to_string(),
    );

    dt_map.insert("Bus".to_string(), "UUID".to_string());
    dt_map.insert("Arc".to_string(), "UUID".to_string());

    let file = fs::File::open(d).unwrap();
    let mut st: PowerSystemStructs = serde_json::from_reader(file).expect("file should be JSON");

    for ags in st.auto_generated_structs.iter_mut() {
        if let Some(docstring) = ags.docstring.as_ref() {
            ags.docstring = Some(docstring.replace("\n ", "\n/// ").replace("\n", "\n/// "));
        }
        ags.struct_name_snake = Some(ags.struct_name.to_case(Case::Snake));
        for fld in ags.fields.iter_mut() {
            let lower = fld.name.to_lowercase();
            if fld.name != lower {
                fld.rename = Some(fld.name.to_string());
                fld.name = fld.name.to_lowercase();
            }
            if ags.struct_name == "HydroTurbineGov" && fld.name == "r" && fld.rename.is_some() {
                fld.name = "droop".to_string();
            }
            if dt_map.contains_key(&fld.data_type.to_string()) {
                fld.data_type = dt_map.get(&fld.data_type).unwrap().to_string();
            }
            if ags.struct_name == "GeneralGovModel" && fld.name == "rselect" && fld.rename.is_some()
            {
                fld.data_type = "isize".to_string();
            }

            if let Some(comment) = fld.comment.as_ref() {
                fld.comment = Some(comment.replace("\n", "\n    /// "));
            }
        }
    }

    let output = gtmpl::template(TEMPLATE, st);

    let mut d = PathBuf::from(std::env::var("OUT_DIR").unwrap());
    d.push("generated.rs");
    let mut file = fs::File::create(d).unwrap();
    if let Err(err) = file.write(output.unwrap().as_bytes()) {
        return Err(err.to_string());
    }
    Ok(())
}

fn main() {
    std::process::exit(match generate_structs() {
        Err(err) => {
            eprintln!("error: {:?}", err);
            1
        }
        Ok(_) => 0,
    })
}