muse2/simulation/investment/appraisal/
constraints.rs1use super::DemandMap;
3use super::optimisation::Variable;
4use crate::asset::{AssetRef, AssetState};
5use crate::commodity::Commodity;
6use crate::time_slice::{TimeSliceID, TimeSliceInfo};
7use crate::units::{Capacity, Flow};
8use highs::RowProblem as Problem;
9use indexmap::IndexMap;
10
11pub fn add_capacity_constraint(
17 problem: &mut Problem,
18 asset: &AssetRef,
19 max_capacity: Option<Capacity>,
20 capacity_var: Variable,
21) {
22 let capacity = max_capacity.unwrap_or(asset.capacity());
23 let bounds = match asset.state() {
24 AssetState::Commissioned { .. } => {
25 capacity.value()..=capacity.value()
27 }
28 AssetState::Candidate => {
29 0.0..=capacity.value()
31 }
32 _ => panic!(
33 "add_capacity_constraint should only be called with Commissioned or Candidate assets"
34 ),
35 };
36 problem.add_row(bounds, [(capacity_var, 1.0)]);
37}
38
39pub fn add_activity_constraints(
50 problem: &mut Problem,
51 asset: &AssetRef,
52 capacity_var: Variable,
53 activity_vars: &IndexMap<TimeSliceID, Variable>,
54) {
55 match asset.state() {
56 AssetState::Commissioned { .. } => {
57 add_activity_constraints_for_existing(problem, asset, activity_vars)
58 }
59 AssetState::Candidate => {
60 add_activity_constraints_for_candidate(problem, asset, capacity_var, activity_vars)
61 }
62 _ => panic!(
63 "add_activity_constraints should only be called with Commissioned or Candidate assets"
64 ),
65 }
66}
67
68fn add_activity_constraints_for_existing(
69 problem: &mut Problem,
70 asset: &AssetRef,
71 activity_vars: &IndexMap<TimeSliceID, Variable>,
72) {
73 for (time_slice, var) in activity_vars.iter() {
74 let limits = asset.get_activity_limits(time_slice);
75 let limits = limits.start().value()..=limits.end().value();
76 problem.add_row(limits, [(*var, 1.0)]);
77 }
78}
79
80fn add_activity_constraints_for_candidate(
81 problem: &mut Problem,
82 asset: &AssetRef,
83 capacity_var: Variable,
84 activity_vars: &IndexMap<TimeSliceID, Variable>,
85) {
86 for (time_slice, activity_var) in activity_vars.iter() {
87 let limits = asset.get_activity_per_capacity_limits(time_slice);
88 let lower_limit = limits.start().value();
89 let upper_limit = limits.end().value();
90
91 problem.add_row(..=0.0, [(*activity_var, 1.0), (capacity_var, -upper_limit)]);
93
94 problem.add_row(..=0.0, [(*activity_var, -1.0), (capacity_var, lower_limit)]);
96 }
97}
98
99pub fn add_demand_constraints(
104 problem: &mut Problem,
105 asset: &AssetRef,
106 commodity: &Commodity,
107 time_slice_info: &TimeSliceInfo,
108 demand: &DemandMap,
109 activity_vars: &IndexMap<TimeSliceID, Variable>,
110 unmet_demand_vars: &IndexMap<TimeSliceID, Variable>,
111) {
112 for ts_selection in time_slice_info.iter_selections_at_level(commodity.time_slice_level) {
113 let mut demand_for_ts_selection = Flow(0.0);
114 let mut terms = Vec::new();
115 for (time_slice, _) in ts_selection.iter(time_slice_info) {
116 demand_for_ts_selection += *demand.get(time_slice).unwrap();
117 let flow_coeff = asset.get_flow(&commodity.id).unwrap().coeff;
118 terms.push((*activity_vars.get(time_slice).unwrap(), flow_coeff.value()));
119 terms.push((*unmet_demand_vars.get(time_slice).unwrap(), 1.0));
120 }
121 problem.add_row(
122 demand_for_ts_selection.value()..=demand_for_ts_selection.value(),
123 terms,
124 );
125 }
126}