Skip to content
Draft
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
10 changes: 10 additions & 0 deletions src/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,16 @@ impl Asset {
lb..=ub
}

/// Get the activity limits for this asset for a given time slice selection
pub fn get_activity_per_capacity_limits_for_selection(
&self,
time_slice_selection: &TimeSliceSelection,
) -> RangeInclusive<ActivityPerCapacity> {
let limits = self.activity_limits.get_limit(time_slice_selection);
let cap2act = self.process.capacity_to_activity;
(cap2act * *limits.start())..=(cap2act * *limits.end())
}

/// Iterate over activity limits for this asset
pub fn iter_activity_limits(
&self,
Expand Down
76 changes: 58 additions & 18 deletions src/simulation/investment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ use crate::model::Model;
use crate::output::DataWriter;
use crate::region::RegionID;
use crate::simulation::CommodityPrices;
use crate::time_slice::{TimeSliceID, TimeSliceInfo};
use crate::units::{Capacity, Dimensionless, Flow, FlowPerCapacity};
use crate::time_slice::{TimeSliceID, TimeSliceInfo, TimeSliceLevel};
use crate::units::{ActivityPerCapacity, Capacity, Dimensionless, Flow, FlowPerCapacity};
use anyhow::{Context, Result, bail, ensure};
use indexmap::IndexMap;
use itertools::{Itertools, chain};
use log::debug;
use std::collections::{HashMap, HashSet};
use std::fmt::Display;
use strum::IntoEnumIterator;

pub mod appraisal;
use appraisal::coefficients::calculate_coefficients_for_assets;
Expand Down Expand Up @@ -579,23 +580,62 @@ fn get_demand_limiting_capacity(
) -> Capacity {
let coeff = asset.get_flow(&commodity.id).unwrap().coeff;
let mut capacity = Capacity(0.0);

for time_slice_selection in time_slice_info.iter_selections_at_level(commodity.time_slice_level)
{
let demand_for_selection: Flow = time_slice_selection
.iter(time_slice_info)
.map(|(time_slice, _)| demand[time_slice])
.sum();

// Calculate max capacity required for this time slice selection
// For commodities with a coarse time slice level, we have to allow the possibility that all
// of the demand gets served by production in a single time slice
for (time_slice, _) in time_slice_selection.iter(time_slice_info) {
let max_flow_per_cap =
*asset.get_activity_per_capacity_limits(time_slice).end() * coeff;
if max_flow_per_cap != FlowPerCapacity(0.0) {
capacity = capacity.max(demand_for_selection / max_flow_per_cap);
let mut demand_cache: HashMap<_, Flow> = HashMap::new();

// Calculate demand-limiting capacity at each timeslice level and take the max.
// This is necessary because process availability limits at the seasonal/annual level may
// necessitate higher capacity that activity limits at the time slice level.
for level in TimeSliceLevel::iter() {
for selection in time_slice_info.iter_selections_at_level(level) {
// Maximum supply within this selection according to the asset's activity limits.
let max_supply_for_selection = *asset
.get_activity_per_capacity_limits_for_selection(&selection)
.end()
* coeff;

// Selections with zero supply would imply infinite demand-limiting capacity,
// so they do not contribute to the maximum.
if max_supply_for_selection == FlowPerCapacity(0.0) {
continue;
}

// Serviceable demand within this selection.
//
// Demand is effectively grouped into balance buckets at the commodity's
// balance level. A balance bucket contributes if:
// 1. The bucket is contained within this selection, and
// 2. The asset can operate in at least one constituent timeslice
// within that bucket.
//
// Demand within a balance bucket is fungible, so if the asset can serve
// any timeslice in the bucket, all demand in that bucket is considered
// serviceable.
let demand_selection_level = level.max(commodity.time_slice_level);
let demand_selection = selection.containing_selection_at_level(demand_selection_level);
let serviceable_demand_for_selection = *demand_cache
.entry(demand_selection.clone())
.or_insert_with(|| {
demand_selection
.iter_at_level(time_slice_info, commodity.time_slice_level)
.unwrap()
.filter(|(bucket, _)| {
bucket.iter(time_slice_info).any(|(ts, _)| {
*asset.get_activity_per_capacity_limits(ts).end()
> ActivityPerCapacity(0.0)
})
})
.map(|(bucket, _)| {
bucket
.iter(time_slice_info)
.map(|(ts, _)| demand[ts])
.sum::<Flow>()
})
.sum()
});

// Calculate demand-limiting capacity for this selection and take the
// maximum across all selections.
capacity = capacity.max(serviceable_demand_for_selection / max_supply_for_selection);
}
}

Expand Down
38 changes: 34 additions & 4 deletions src/time_slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,28 @@ impl TimeSliceSelection {
}
}

/// Get the [`TimeSliceSelection`] containing this selection at the specified level.
pub fn containing_selection_at_level(&self, level: TimeSliceLevel) -> TimeSliceSelection {
assert!(
level >= self.level(),
"Cannot get containing selection at finer level"
);

let mut selection = self.clone();

while selection.level() < level {
selection = match selection {
TimeSliceSelection::Single(time_slice_id) => {
TimeSliceSelection::Season(time_slice_id.season.clone())
}
TimeSliceSelection::Season(_) => TimeSliceSelection::Annual,
TimeSliceSelection::Annual => unreachable!(),
};
}

selection
}

/// Iterate over the subset of time slices in this selection
pub fn iter<'a>(
&'a self,
Expand Down Expand Up @@ -192,18 +214,26 @@ impl Display for TimeSliceSelection {

/// The time granularity for a particular operation
#[derive(
PartialEq, PartialOrd, Copy, Clone, Debug, DeserializeLabeledStringEnum, strum::EnumIter,
PartialEq,
Eq,
PartialOrd,
Ord,
Copy,
Clone,
Debug,
DeserializeLabeledStringEnum,
strum::EnumIter,
)]
pub enum TimeSliceLevel {
/// Treat individual time slices separately
#[string = "daynight"]
DayNight,
DayNight = 0,
/// Whole seasons
#[string = "season"]
Season,
Season = 1,
/// The whole year
#[string = "annual"]
Annual,
Annual = 2,
}

impl TimeSliceLevel {
Expand Down
67 changes: 32 additions & 35 deletions tests/data/muse1_default/asset_capacities.csv
Original file line number Diff line number Diff line change
@@ -1,49 +1,46 @@
milestone_year,asset_id,group_id,capacity,num_units
2020,0,,24.0,
2020,1,,19.0,
2025,0,,24.0,
2025,2,,23.939952120095757,
2025,3,,9.575980848038302,
2025,4,,5.107189785620431,
2030,0,,24.0,
2025,3,,13.299973400053199,
2025,4,,1.3299973400053235,
2030,2,,23.939952120095757,
2030,3,,9.575980848038302,
2030,4,,5.107189785620431,
2030,3,,13.299973400053199,
2030,5,,5.939988120023763,
2030,6,,5.975988048023905,
2030,6,,3.9839920320159363,
2035,2,,23.939952120095757,
2035,3,,9.575980848038302,
2035,3,,13.299973400053199,
2035,5,,5.939988120023763,
2035,6,,5.975988048023905,
2035,7,,6.119987760024477,
2035,8,,5.875188249623504,
2035,6,,3.9839920320159363,
2035,7,,6.11998776002448,
2035,8,,4.799990400019199,
2040,2,,23.939952120095757,
2040,3,,9.575980848038302,
2040,3,,13.299973400053199,
2040,5,,5.939988120023763,
2040,6,,5.975988048023905,
2040,7,,6.119987760024477,
2040,8,,5.875188249623504,
2040,9,,5.939988120023762,
2040,10,,4.4947110105779755,
2040,6,,3.9839920320159363,
2040,7,,6.11998776002448,
2040,8,,4.799990400019199,
2040,9,,5.939988120023764,
2040,10,,2.91839416321168,
2045,2,,23.939952120095757,
2045,3,,9.575980848038302,
2045,3,,13.299973400053199,
2045,5,,5.939988120023763,
2045,6,,5.975988048023905,
2045,7,,6.119987760024477,
2045,8,,5.875188249623504,
2045,9,,5.939988120023762,
2045,10,,4.4947110105779755,
2045,11,,5.939988120023764,
2045,12,,1.6273887452225102,
2045,6,,3.9839920320159363,
2045,7,,6.11998776002448,
2045,8,,4.799990400019199,
2045,9,,5.939988120023764,
2045,10,,2.91839416321168,
2045,11,,5.939988120023756,
2045,12,,3.8342323315353237,
2050,2,,23.939952120095757,
2050,3,,9.575980848038302,
2050,3,,13.299973400053199,
2050,5,,5.939988120023763,
2050,6,,5.975988048023905,
2050,7,,6.119987760024477,
2050,8,,5.875188249623504,
2050,9,,5.939988120023762,
2050,10,,4.4947110105779755,
2050,11,,5.939988120023764,
2050,12,,1.6273887452225102,
2050,13,,6.1199877600244745,
2050,14,,4.657641884716227,
2050,6,,3.9839920320159363,
2050,7,,6.11998776002448,
2050,8,,4.799990400019199,
2050,9,,5.939988120023764,
2050,10,,2.91839416321168,
2050,11,,5.939988120023756,
2050,12,,3.8342323315353237,
2050,13,,6.1199877600244905,
2050,14,,2.792058415883189,
Loading
Loading