use super::VariableMap;
use crate::asset::{AssetCapacity, AssetIterator, AssetRef};
use crate::commodity::{CommodityID, CommodityType};
use crate::model::Model;
use crate::region::RegionID;
use crate::time_slice::{TimeSliceInfo, TimeSliceSelection};
use crate::units::UnitType;
use highs::RowProblem as Problem;
use indexmap::IndexMap;
pub struct KeysWithOffset<T> {
offset: usize,
keys: Vec<T>,
}
impl<T> KeysWithOffset<T> {
pub fn zip_duals<'a, U>(&'a self, duals: &'a [f64]) -> impl Iterator<Item = (&'a T, U)>
where
U: UnitType,
{
assert!(
self.offset + self.keys.len() <= duals.len(),
"Bad constraint keys: dual rows out of range"
);
self.keys
.iter()
.zip(duals[self.offset..].iter().copied().map(U::new))
}
}
pub type CommodityBalanceKeys = KeysWithOffset<(CommodityID, RegionID, TimeSliceSelection)>;
pub type ActivityKeys = KeysWithOffset<(AssetRef, TimeSliceSelection)>;
pub struct ConstraintKeys {
pub commodity_balance_keys: CommodityBalanceKeys,
pub activity_keys: ActivityKeys,
}
pub fn add_model_constraints<'a, I>(
problem: &mut Problem,
variables: &VariableMap,
model: &'a Model,
assets: &I,
markets_to_balance: &'a [(CommodityID, RegionID)],
year: u32,
) -> ConstraintKeys
where
I: Iterator<Item = &'a AssetRef> + Clone + 'a,
{
let commodity_balance_keys = add_commodity_balance_constraints(
problem,
variables,
model,
assets,
markets_to_balance,
year,
);
let activity_keys =
add_activity_constraints(problem, variables, &model.time_slice_info, assets.clone());
ConstraintKeys {
commodity_balance_keys,
activity_keys,
}
}
fn add_commodity_balance_constraints<'a, I>(
problem: &mut Problem,
variables: &VariableMap,
model: &'a Model,
assets: &I,
markets_to_balance: &'a [(CommodityID, RegionID)],
year: u32,
) -> CommodityBalanceKeys
where
I: Iterator<Item = &'a AssetRef> + Clone + 'a,
{
let offset = problem.num_rows();
let mut keys = Vec::new();
let mut terms = Vec::new();
for (commodity_id, region_id) in markets_to_balance {
let commodity = &model.commodities[commodity_id];
if !matches!(
commodity.kind,
CommodityType::SupplyEqualsDemand | CommodityType::ServiceDemand
) {
continue;
}
for ts_selection in model
.time_slice_info
.iter_selections_at_level(commodity.time_slice_level)
{
for (asset, flow) in assets
.clone()
.filter_region(region_id)
.flows_for_commodity(commodity_id)
{
for (time_slice, _) in ts_selection.iter(&model.time_slice_info) {
let var = variables.get_activity_var(asset, time_slice);
terms.push((var, flow.coeff.value()));
}
}
if terms.is_empty() {
continue;
}
if !variables.unmet_demand_var_idx.is_empty() {
for (time_slice, _) in ts_selection.iter(&model.time_slice_info) {
let var = variables.get_unmet_demand_var(commodity_id, region_id, time_slice);
terms.push((var, 1.0));
}
}
let min = if commodity.kind == CommodityType::ServiceDemand {
commodity.demand[&(region_id.clone(), year, ts_selection.clone())].value()
} else {
0.0
};
problem.add_row(min.., terms.drain(..));
keys.push((
commodity_id.clone(),
region_id.clone(),
ts_selection.clone(),
));
}
}
CommodityBalanceKeys { offset, keys }
}
fn add_activity_constraints<'a, I>(
problem: &mut Problem,
variables: &VariableMap,
time_slice_info: &TimeSliceInfo,
assets: I,
) -> ActivityKeys
where
I: Iterator<Item = &'a AssetRef> + 'a,
{
let offset = problem.num_rows();
let mut keys = Vec::new();
let capacity_vars: IndexMap<&AssetRef, highs::Col> = variables.iter_capacity_vars().collect();
for asset in assets {
if let Some(&capacity_var) = capacity_vars.get(asset) {
for (ts_selection, limits) in asset.iter_activity_per_capacity_limits() {
let mut upper_limit = limits.end().value();
let mut lower_limit = limits.start().value();
if let AssetCapacity::Discrete(_, unit_size) = asset.capacity() {
upper_limit *= unit_size.value();
lower_limit *= unit_size.value();
}
let mut terms_upper = vec![(capacity_var, -upper_limit)];
let mut terms_lower = vec![(capacity_var, -lower_limit)];
for (time_slice, _) in ts_selection.iter(time_slice_info) {
let var = variables.get_activity_var(asset, time_slice);
terms_upper.push((var, 1.0));
terms_lower.push((var, 1.0));
}
problem.add_row(..=0.0, &terms_upper);
problem.add_row(0.0.., &terms_lower);
keys.push((asset.clone(), ts_selection.clone()));
keys.push((asset.clone(), ts_selection.clone()));
}
} else {
for (ts_selection, limits) in asset.iter_activity_limits() {
let limits = limits.start().value()..=limits.end().value();
let terms = ts_selection
.iter(time_slice_info)
.map(|(time_slice, _)| (variables.get_activity_var(asset, time_slice), 1.0))
.collect::<Vec<_>>();
problem.add_row(limits, &terms);
keys.push((asset.clone(), ts_selection.clone()));
}
}
}
ActivityKeys { offset, keys }
}