powerio-prob 0.11.0

Problem instance builders for power system analysis and optimization.
Documentation
//! Typed objective terms for the optimal power flow instances.
//!
//! A term is a typed reference to costs or penalties stored on the network or
//! the calculation record; the numerical curves themselves stay on the
//! network so power flow and other calculations reuse them. A solver never
//! adds a term silently: changing the mathematical objective constructs a
//! different instance, and a derived instance or a stored document can state
//! every term and its weight exactly.

use serde::{Deserialize, Serialize};

/// One typed objective term.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case", tag = "term")]
#[non_exhaustive]
pub enum ObjectiveTerm {
    /// The generator cost curves the network states, summed over the in
    /// service generators the instance dispatches.
    NetworkGeneratorCost,
    /// Active power dispatch costs on the resources represented by a
    /// multiconductor calculation record (the BMOPF objective).
    ActivePowerDispatchCost,
}

/// The complete typed objective of one OPF instance: a sum of terms.
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Objective {
    terms: Vec<ObjectiveTerm>,
}

impl Objective {
    /// The empty objective; a feasibility problem.
    #[must_use]
    pub const fn none() -> Self {
        Self { terms: Vec::new() }
    }

    /// The default OPF objective: the network's generator cost curves.
    #[must_use]
    pub fn network_generator_cost() -> Self {
        Self {
            terms: vec![ObjectiveTerm::NetworkGeneratorCost],
        }
    }

    /// The default multiconductor OPF objective: active power dispatch cost.
    #[must_use]
    pub fn active_power_dispatch_cost() -> Self {
        Self {
            terms: vec![ObjectiveTerm::ActivePowerDispatchCost],
        }
    }

    /// Append one term, consuming the objective.
    #[must_use]
    pub fn with_term(mut self, term: ObjectiveTerm) -> Self {
        self.terms.push(term);
        self
    }

    /// The terms, in the order they were stated.
    #[must_use]
    pub fn terms(&self) -> &[ObjectiveTerm] {
        &self.terms
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn an_objective_states_its_terms_in_order() {
        let objective =
            Objective::network_generator_cost().with_term(ObjectiveTerm::ActivePowerDispatchCost);
        assert_eq!(objective.terms().len(), 2);
        assert_eq!(objective.terms()[0], ObjectiveTerm::NetworkGeneratorCost);
        let serialized = serde_json::to_value(&objective).unwrap();
        assert_eq!(serialized["terms"][1]["term"], "active_power_dispatch_cost");
    }
}