Skip to main content

muse2/simulation/investment/appraisal/
constraints.rs

1//! Constraints for the optimisation problem.
2use super::DemandMap;
3use super::optimisation::Variable;
4use crate::asset::AssetRef;
5use crate::commodity::Commodity;
6use crate::time_slice::{TimeSliceID, TimeSliceInfo};
7use crate::units::Flow;
8use highs::RowProblem as Problem;
9use indexmap::IndexMap;
10
11/// Adds activity constraints to the problem.
12///
13/// Constrains the activity variables to be within the asset's activity limits.
14///
15/// The asset's per-capacity activity limits are scaled by the asset's capacity to give
16/// absolute bounds, and a single bounded constraint is added per time-slice selection covering the
17/// sum of activity in that selection.
18pub fn add_activity_constraints(
19    problem: &mut Problem,
20    asset: &AssetRef,
21    activity_vars: &IndexMap<TimeSliceID, Variable>,
22    time_slice_info: &TimeSliceInfo,
23) {
24    let capacity = asset.capacity().total_capacity();
25    for (ts_selection, limits) in asset.iter_activity_per_capacity_limits() {
26        let limits = (capacity * *limits.start()).value()..=(capacity * *limits.end()).value();
27
28        // Collect activity terms for the time slices in this selection
29        let terms = ts_selection
30            .iter(time_slice_info)
31            .map(|(time_slice, _)| (*activity_vars.get(time_slice).unwrap(), 1.0))
32            .collect::<Vec<_>>();
33
34        // Constraint: sum of activities in selection within limits
35        problem.add_row(limits, &terms);
36    }
37}
38
39/// Adds demand constraints to the problem.
40///
41/// Constrains supply to be less than or equal to demand. One inequality constraint is added per
42/// time-slice selection at the commodity's balance level, capping the sum of activity (scaled by
43/// flow coefficients) to the total demand for that selection.
44pub fn add_demand_constraints(
45    problem: &mut Problem,
46    asset: &AssetRef,
47    commodity: &Commodity,
48    time_slice_info: &TimeSliceInfo,
49    demand: &DemandMap,
50    activity_vars: &IndexMap<TimeSliceID, Variable>,
51) {
52    for ts_selection in time_slice_info.iter_selections_at_level(commodity.time_slice_level) {
53        let mut demand_for_ts_selection = Flow(0.0);
54        let mut terms = Vec::new();
55        for (time_slice, _) in ts_selection.iter(time_slice_info) {
56            demand_for_ts_selection += demand[time_slice];
57            let flow_coeff = asset.get_flow(&commodity.id).unwrap().coeff;
58            terms.push((activity_vars[time_slice], flow_coeff.value()));
59        }
60        problem.add_row(0.0..=demand_for_ts_selection.value(), terms);
61    }
62}