Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 13 additions & 63 deletions src/commodity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ define_id_type! {CommodityID}
/// A map of [`Commodity`]s, keyed by commodity ID
pub type CommodityMap = IndexMap<CommodityID, Rc<Commodity>>;

/// A map of [`CommodityCost`]s, keyed by region ID, year and time slice ID
pub type CommodityCostMap = HashMap<(RegionID, u32, TimeSliceID), CommodityCost>;

/// A map of demand values, keyed by region ID, year and time slice ID
pub type DemandMap = HashMap<(RegionID, u32, TimeSliceID), f64>;

/// A commodity within the simulation. Represents a substance (e.g. CO2) or form of energy (e.g.
/// electricity) that can be produced and/or consumed by technologies in the model.
#[derive(PartialEq, Debug, Deserialize)]
Expand Down Expand Up @@ -54,38 +60,6 @@ pub struct CommodityCost {
pub value: f64,
}

/// A data structure for easy lookup of [`CommodityCost`]s
#[derive(PartialEq, Debug, Default, Clone)]
pub struct CommodityCostMap(HashMap<(RegionID, u32, TimeSliceID), CommodityCost>);

impl CommodityCostMap {
/// Create a new, empty [`CommodityCostMap`]
pub fn new() -> Self {
Self(HashMap::new())
}

/// Insert a [`CommodityCost`] into the map
pub fn insert(
&mut self,
region_id: RegionID,
year: u32,
time_slice: TimeSliceID,
value: CommodityCost,
) -> Option<CommodityCost> {
self.0.insert((region_id, year, time_slice), value)
}

/// Retrieve a [`CommodityCost`] from the map
pub fn get(
&self,
region_id: &RegionID,
year: u32,
time_slice: &TimeSliceID,
) -> Option<&CommodityCost> {
self.0.get(&(region_id.clone(), year, time_slice.clone()))
}
}

/// Commodity balance type
#[derive(PartialEq, Debug, DeserializeLabeledStringEnum)]
pub enum CommodityType {
Expand All @@ -99,33 +73,6 @@ pub enum CommodityType {
OutputCommodity,
}

/// A map relating region, year and time slice to demand (in real units, not a fraction).
///
/// This data type is exported as this is the way in we want to look up demand outside of this
/// module.
#[derive(PartialEq, Debug, Clone, Default)]
pub struct DemandMap(HashMap<(RegionID, u32, TimeSliceID), f64>);

impl DemandMap {
/// Create a new, empty [`DemandMap`]
pub fn new() -> DemandMap {
DemandMap::default()
}

/// Retrieve the demand for the specified region, year and time slice
pub fn get(&self, region_id: &RegionID, year: u32, time_slice: &TimeSliceID) -> f64 {
self.0
.get(&(region_id.clone(), year, time_slice.clone()))
.copied()
.unwrap_or_else(|| panic!("Missing demand entry: {region_id}, {year}, {time_slice}"))
}

/// Insert a new demand entry for the specified region, year and time slice
pub fn insert(&mut self, region_id: RegionID, year: u32, time_slice: TimeSliceID, demand: f64) {
self.0.insert((region_id, year, time_slice), demand);
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -138,9 +85,12 @@ mod tests {
};
let value = 0.25;
let mut map = DemandMap::new();
map.insert("North".into(), 2020, time_slice.clone(), value);
map.insert(("North".into(), 2020, time_slice.clone()), value);

assert_eq!(map.get(&"North".into(), 2020, &time_slice), value)
assert_eq!(
map.get(&("North".into(), 2020, time_slice)).unwrap(),
&value
)
}

#[test]
Expand All @@ -155,8 +105,8 @@ mod tests {
};
let mut map = CommodityCostMap::new();
assert!(map
.insert("GBR".into(), 2010, ts.clone(), value.clone())
.insert(("GBR".into(), 2010, ts.clone()), value.clone())
.is_none());
assert_eq!(map.get(&"GBR".into(), 2010, &ts).unwrap(), &value);
assert_eq!(map.get(&("GBR".into(), 2010, ts)).unwrap(), &value);
}
}
6 changes: 3 additions & 3 deletions src/input/commodity/cost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ where
};

ensure!(
map.insert(region_id.clone(), cost.year, time_slice.clone(), value)
map.insert((region_id.clone(), cost.year, time_slice.clone()), value)
.is_none(),
"Commodity cost entry covered by more than one time slice \
(region: {}, year: {}, time slice: {})",
Expand Down Expand Up @@ -189,8 +189,8 @@ mod tests {
value: cost2.value,
};
let mut map = CommodityCostMap::new();
map.insert("GBR".into(), cost1.year, time_slice.clone(), value1);
map.insert("FRA".into(), cost2.year, time_slice.clone(), value2);
map.insert(("GBR".into(), cost1.year, time_slice.clone()), value1);
map.insert(("FRA".into(), cost2.year, time_slice.clone()), value2);
let expected = HashMap::from_iter([("commodity".into(), map)]);
assert_eq!(
read_commodity_costs_iter(
Expand Down
4 changes: 1 addition & 3 deletions src/input/commodity/demand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,7 @@ fn compute_demand_maps(

// Add a new demand entry
map.insert(
region_id.clone(),
*year,
time_slice.clone(),
(region_id.clone(), *year, time_slice.clone()),
annual_demand * demand_fraction,
);
}
Expand Down
9 changes: 6 additions & 3 deletions src/input/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,11 @@ fn validate_svd_commodity(
for region_id in params.region_ids.iter() {
for year in params.milestone_years.iter().copied() {
for time_slice in params.time_slice_info.iter_ids() {
let demand = commodity.demand.get(region_id, year, time_slice);
if demand > 0.0 {
let demand = commodity
.demand
.get(&(region_id.clone(), year, time_slice.clone()))
.unwrap();
if demand > &0.0 {
let mut has_producer = false;

// We must check for producers in every time slice, region, and year.
Expand Down Expand Up @@ -412,7 +415,7 @@ mod tests {
for region in data.region_ids.iter() {
for year in milestone_years {
for time_slice in time_slice_info.iter_ids() {
demand_map.insert(region.clone(), year, time_slice.clone(), 0.5);
demand_map.insert((region.clone(), year, time_slice.clone()), 0.5);
}
}
}
Expand Down
17 changes: 11 additions & 6 deletions src/simulation/optimisation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,13 @@ fn calculate_cost_coefficient(
coeff += asset.process.parameter.variable_operating_cost
}

// If there is a user-provided commodity cost for this combination of parameters, include it
if let Some(cost) = flow.commodity.costs.get(&asset.region_id, year, time_slice) {
// If there is a user-provided cost for this commodity, include it
if !flow.commodity.costs.is_empty() {
let cost = flow
.commodity
.costs
.get(&(asset.region_id.clone(), year, time_slice.clone()))
.unwrap();
let apply_cost = match cost.balance_type {
BalanceType::Net => true,
BalanceType::Consumption => flow.flow < 0.0,
Expand Down Expand Up @@ -344,7 +349,7 @@ mod tests {
value: 2.0,
};
let mut costs = CommodityCostMap::new();
costs.insert("GBR".into(), 2010, time_slice.clone(), cost);
costs.insert(("GBR".into(), 2010, time_slice.clone()), cost);
check_coeff!(1.0, false, costs.clone(), 3.0);
check_coeff!(-1.0, false, costs, -1.0);

Expand All @@ -354,7 +359,7 @@ mod tests {
value: 2.0,
};
let mut costs = CommodityCostMap::new();
costs.insert("GBR".into(), 2010, time_slice.clone(), cost);
costs.insert(("GBR".into(), 2010, time_slice.clone()), cost);
check_coeff!(1.0, false, costs.clone(), 3.0);
check_coeff!(-1.0, false, costs, -3.0);

Expand All @@ -364,7 +369,7 @@ mod tests {
value: 2.0,
};
let mut costs = CommodityCostMap::new();
costs.insert("GBR".into(), 2010, time_slice.clone(), cost);
costs.insert(("GBR".into(), 2010, time_slice.clone()), cost);
check_coeff!(1.0, false, costs.clone(), 1.0);
check_coeff!(-1.0, false, costs, -3.0);

Expand All @@ -374,7 +379,7 @@ mod tests {
value: 2.0,
};
let mut costs = CommodityCostMap::new();
costs.insert("GBR".into(), 2010, time_slice.clone(), cost);
costs.insert(("GBR".into(), 2010, time_slice.clone()), cost);
check_coeff!(1.0, true, costs.clone(), 4.0);
check_coeff!(-1.0, true, costs, -2.0);
}
Expand Down
7 changes: 4 additions & 3 deletions src/simulation/optimisation/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,10 @@ fn add_commodity_balance_constraints(
CommodityType::SupplyEqualsDemand => 0.0,
CommodityType::ServiceDemand => {
match ts_selection {
TimeSliceSelection::Single(ref ts) => {
commodity.demand.get(region_id, year, ts)
}
TimeSliceSelection::Single(ref ts) => *commodity
.demand
.get(&(region_id.clone(), year, ts.clone()))
.unwrap(),
// We currently only support specifying demand at the time slice level:
// https://github.com/EnergySystemsModellingLab/MUSE_2.0/issues/391
_ => panic!(
Expand Down