use super::DemandMap;
use super::ObjectiveCoefficients;
use super::constraints::{
add_activity_constraints, add_capacity_constraint, add_demand_constraints,
};
use crate::asset::{AssetCapacity, AssetRef};
use crate::commodity::Commodity;
use crate::simulation::optimisation::solve_optimal;
use crate::time_slice::{TimeSliceID, TimeSliceInfo};
use crate::units::{Activity, Capacity, Flow};
use anyhow::Result;
use highs::{RowProblem as Problem, Sense};
use indexmap::IndexMap;
pub type Variable = highs::Col;
struct VariableMap {
capacity_var: Variable,
activity_vars: IndexMap<TimeSliceID, Variable>,
unmet_demand_vars: IndexMap<TimeSliceID, Variable>,
}
impl VariableMap {
fn add_to_problem(
problem: &mut Problem,
cost_coefficients: &ObjectiveCoefficients,
capacity_unit_size: Option<Capacity>,
) -> Self {
let capacity_coefficient = cost_coefficients.capacity_coefficient.value();
let capacity_var = match capacity_unit_size {
Some(unit_size) => {
problem.add_integer_column(capacity_coefficient * unit_size.value(), 0.0..)
}
None => {
problem.add_column(capacity_coefficient, 0.0..)
}
};
let mut activity_vars = IndexMap::new();
for (time_slice, cost) in &cost_coefficients.activity_coefficients {
let var = problem.add_column(cost.value(), 0.0..);
activity_vars.insert(time_slice.clone(), var);
}
let mut unmet_demand_vars = IndexMap::new();
for time_slice in cost_coefficients.activity_coefficients.keys() {
let var = problem.add_column(cost_coefficients.unmet_demand_coefficient.value(), 0.0..);
unmet_demand_vars.insert(time_slice.clone(), var);
}
Self {
capacity_var,
activity_vars,
unmet_demand_vars,
}
}
}
pub struct ResultsMap {
pub capacity: AssetCapacity,
pub activity: IndexMap<TimeSliceID, Activity>,
pub unmet_demand: DemandMap,
}
fn add_constraints(
problem: &mut Problem,
asset: &AssetRef,
max_capacity: Option<AssetCapacity>,
commodity: &Commodity,
variables: &VariableMap,
demand: &DemandMap,
time_slice_info: &TimeSliceInfo,
) {
add_capacity_constraint(problem, asset, max_capacity, variables.capacity_var);
add_activity_constraints(
problem,
asset,
variables.capacity_var,
&variables.activity_vars,
time_slice_info,
);
add_demand_constraints(
problem,
asset,
commodity,
time_slice_info,
demand,
&variables.activity_vars,
&variables.unmet_demand_vars,
);
}
pub fn perform_optimisation(
asset: &AssetRef,
max_capacity: Option<AssetCapacity>,
commodity: &Commodity,
coefficients: &ObjectiveCoefficients,
demand: &DemandMap,
time_slice_info: &TimeSliceInfo,
sense: Sense,
) -> Result<ResultsMap> {
let mut problem = Problem::default();
let variables = VariableMap::add_to_problem(&mut problem, coefficients, asset.unit_size());
add_constraints(
&mut problem,
asset,
max_capacity,
commodity,
&variables,
demand,
time_slice_info,
);
let solution = solve_optimal(problem.optimise(sense))?.get_solution();
let solution_values = solution.columns();
Ok(ResultsMap {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
capacity: match asset.unit_size() {
Some(unit_size) => {
AssetCapacity::Discrete(solution_values[0].round() as u32, unit_size)
}
None => AssetCapacity::Continuous(Capacity::new(solution_values[0])),
},
activity: variables
.activity_vars
.keys()
.zip(solution_values[1..].iter())
.map(|(time_slice, &value)| (time_slice.clone(), Activity::new(value)))
.collect(),
unmet_demand: variables
.unmet_demand_vars
.keys()
.zip(solution_values[variables.activity_vars.len() + 1..].iter())
.map(|(time_slice, &value)| (time_slice.clone(), Flow::new(value)))
.collect(),
})
}