Skip to main content

powerio_prob/instance/
objective.rs

1//! Typed objective terms for the optimal power flow instances.
2//!
3//! A term is a typed reference to costs or penalties stored on the network or
4//! the calculation record; the numerical curves themselves stay on the
5//! network so power flow and other calculations reuse them. A solver never
6//! adds a term silently: changing the mathematical objective constructs a
7//! different instance, and a derived instance or a stored document can state
8//! every term and its weight exactly.
9
10use serde::{Deserialize, Serialize};
11
12/// One typed objective term.
13#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
14#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
15#[serde(rename_all = "snake_case", tag = "term")]
16#[non_exhaustive]
17pub enum ObjectiveTerm {
18    /// The generator cost curves the network states, summed over the in
19    /// service generators the instance dispatches.
20    NetworkGeneratorCost,
21    /// Active power dispatch costs on the resources represented by a
22    /// multiconductor calculation record (the BMOPF objective).
23    ActivePowerDispatchCost,
24    /// The opening and closing costs of the branch status changes an AC
25    /// optimal transmission switching instance chooses, against its initial
26    /// topology.
27    BranchSwitchingCost,
28}
29
30/// The complete typed objective of one OPF instance: a sum of terms.
31#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
32#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
33pub struct Objective {
34    terms: Vec<ObjectiveTerm>,
35}
36
37impl Objective {
38    /// The empty objective; a feasibility problem.
39    #[must_use]
40    pub const fn none() -> Self {
41        Self { terms: Vec::new() }
42    }
43
44    /// The default OPF objective: the network's generator cost curves.
45    #[must_use]
46    pub fn network_generator_cost() -> Self {
47        Self {
48            terms: vec![ObjectiveTerm::NetworkGeneratorCost],
49        }
50    }
51
52    /// The default multiconductor OPF objective: active power dispatch cost.
53    #[must_use]
54    pub fn active_power_dispatch_cost() -> Self {
55        Self {
56            terms: vec![ObjectiveTerm::ActivePowerDispatchCost],
57        }
58    }
59
60    /// Append one term, consuming the objective.
61    #[must_use]
62    pub fn with_term(mut self, term: ObjectiveTerm) -> Self {
63        self.terms.push(term);
64        self
65    }
66
67    /// The terms, in the order they were stated.
68    #[must_use]
69    pub fn terms(&self) -> &[ObjectiveTerm] {
70        &self.terms
71    }
72
73    /// Whether the objective states `term`.
74    #[must_use]
75    pub fn contains(&self, term: &ObjectiveTerm) -> bool {
76        self.terms.contains(term)
77    }
78
79    /// This objective without any occurrence of `term`.
80    #[must_use]
81    pub(crate) fn without(&self, term: &ObjectiveTerm) -> Self {
82        Self {
83            terms: self
84                .terms
85                .iter()
86                .filter(|stated| *stated != term)
87                .cloned()
88                .collect(),
89        }
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn an_objective_states_its_terms_in_order() {
99        let objective =
100            Objective::network_generator_cost().with_term(ObjectiveTerm::ActivePowerDispatchCost);
101        assert_eq!(objective.terms().len(), 2);
102        assert_eq!(objective.terms()[0], ObjectiveTerm::NetworkGeneratorCost);
103        let serialized = serde_json::to_value(&objective).unwrap();
104        assert_eq!(serialized["terms"][1]["term"], "active_power_dispatch_cost");
105    }
106}