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}
25
26/// The complete typed objective of one OPF instance: a sum of terms.
27#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
28#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
29pub struct Objective {
30    terms: Vec<ObjectiveTerm>,
31}
32
33impl Objective {
34    /// The empty objective; a feasibility problem.
35    #[must_use]
36    pub const fn none() -> Self {
37        Self { terms: Vec::new() }
38    }
39
40    /// The default OPF objective: the network's generator cost curves.
41    #[must_use]
42    pub fn network_generator_cost() -> Self {
43        Self {
44            terms: vec![ObjectiveTerm::NetworkGeneratorCost],
45        }
46    }
47
48    /// The default multiconductor OPF objective: active power dispatch cost.
49    #[must_use]
50    pub fn active_power_dispatch_cost() -> Self {
51        Self {
52            terms: vec![ObjectiveTerm::ActivePowerDispatchCost],
53        }
54    }
55
56    /// Append one term, consuming the objective.
57    #[must_use]
58    pub fn with_term(mut self, term: ObjectiveTerm) -> Self {
59        self.terms.push(term);
60        self
61    }
62
63    /// The terms, in the order they were stated.
64    #[must_use]
65    pub fn terms(&self) -> &[ObjectiveTerm] {
66        &self.terms
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn an_objective_states_its_terms_in_order() {
76        let objective =
77            Objective::network_generator_cost().with_term(ObjectiveTerm::ActivePowerDispatchCost);
78        assert_eq!(objective.terms().len(), 2);
79        assert_eq!(objective.terms()[0], ObjectiveTerm::NetworkGeneratorCost);
80        let serialized = serde_json::to_value(&objective).unwrap();
81        assert_eq!(serialized["terms"][1]["term"], "active_power_dispatch_cost");
82    }
83}