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
119 changes: 26 additions & 93 deletions src/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ impl Asset {
) -> RangeInclusive<Activity> {
let activity_per_capacity_limits = self.activity_limits.get_limit(time_slice_selection);
let cap2act = self.process.capacity_to_activity;
let max_activity = self.total_capacity() * cap2act;
let max_activity = self.active_capacity() * cap2act;
let lb = max_activity * *activity_per_capacity_limits.start();
let ub = max_activity * *activity_per_capacity_limits.end();
lb..=ub
Expand Down Expand Up @@ -557,7 +557,7 @@ impl Asset {

/// Maximum activity for this asset
pub fn max_activity(&self) -> Activity {
self.total_capacity() * self.process.capacity_to_activity
self.active_capacity() * self.process.capacity_to_activity
}

/// Get a specific process flow
Expand Down Expand Up @@ -846,6 +846,11 @@ impl Asset {
self.num_tranches() - self.get_num_mothballed_tranches()
}

/// Get the active (non-mothballed) capacity for this asset
pub fn active_capacity(&self) -> Capacity {
self.capacity().tranche_size() * Dimensionless(self.get_num_nonmothballed_tranches() as f64)
}

/// The number of tranches this asset represents
pub fn num_tranches(&self) -> u32 {
self.capacity().num_tranches()
Expand Down Expand Up @@ -982,58 +987,17 @@ impl AssetRef {
}
}

/// Get an [`AssetRef`] representing a subset of this asset's tranches.
///
/// If some of the asset's tranches are mothballed, these are discarded before non-mothballed
/// tranches. For example, if an asset has seven tranches of which four are mothballed and we are
/// reducing the number of tranches to four, the new asset will have one mothballed tranche.
///
/// # Panics
///
/// Panics if `new_num_tranches` is zero or exceeds the total capacity of this asset.
pub fn with_subset_of_tranches(self, new_num_tranches: u32) -> Self {
if new_num_tranches == self.num_tranches() {
return self;
}

assert!(
new_num_tranches > 0,
"Cannot make an asset with zero tranches"
);

let max_num_tranches = self.capacity().num_tranches();
let tranche_size = self.capacity().tranche_size();

assert!(
new_num_tranches <= max_num_tranches,
"Cannot make an asset with more tranches than original"
);

// Make a new Asset with fewer tranches. If there are more mothballed tranches than the new asset
// will have, we reduce this number to avoid there being more mothballed tranches than the new
// asset has, which would be a logic error. We discard mothballed before non-mothballed
// tranches.
let new_num_mothballed =
new_num_tranches.saturating_sub(self.get_num_nonmothballed_tranches());
let mut asset = self.with_mothballed_tranches(new_num_mothballed, None);
asset
.make_mut()
.set_capacity(AssetCapacity::new(new_num_tranches, tranche_size));
asset
}

/// Get an [`AssetRef`] representing a single tranche of this asset.
pub fn as_single_tranche(self) -> Self {
assert!(
self.num_tranches() > 0,
"Cannot convert an asset with zero tranches to a single tranche"
);
let new_num_tranches = 1;
let tranche_size = self.capacity().tranche_size();
let mut asset = self.with_no_mothballed_tranches();
asset
.make_mut()
.set_capacity(AssetCapacity::new(new_num_tranches, tranche_size));
.set_capacity(AssetCapacity::new(1, tranche_size));
asset
}

Expand Down Expand Up @@ -1070,10 +1034,18 @@ impl AssetRef {
return None;
}

// `with_subset_of_tranches` discards the oldest mothballed tranches first, which are exactly the
// ones being decommissioned here.
log_decommissioning(&self, tranches_to_remove, &reason);
Some(self.with_subset_of_tranches(new_num_tranches))

// Retain the newest mothball events, discarding the oldest events that have just been
// decommissioned. The capacity is reduced after trimming the event history.
let tranche_size = self.capacity().tranche_size();
let new_num_mothballed =
new_num_tranches.saturating_sub(self.get_num_nonmothballed_tranches());
let mut asset = self.with_mothballed_tranches(new_num_mothballed, None);
asset
.make_mut()
.set_capacity(AssetCapacity::new(new_num_tranches, tranche_size));
Some(asset)
}

/// Return a new [`AssetRef`] with the specified number of tranches mothballed.
Expand Down Expand Up @@ -1511,39 +1483,6 @@ mod tests {
);
}

#[rstest]
#[case::subset(2, false)]
#[case::all_tranches(3, true)]
fn with_subset_of_tranches(
multi_tranche_asset: Asset,
#[case] num_tranches: u32,
#[case] expect_same_asset: bool,
) {
let asset = AssetRef::from(multi_tranche_asset);
let asset_subset = asset.clone().with_subset_of_tranches(num_tranches);

assert_eq!(
asset_subset.capacity(),
AssetCapacity::new(num_tranches, Capacity(4.0))
);
assert_eq!(asset_subset.id(), asset.id());
assert_eq!(asset_subset.agent_id(), asset.agent_id());
assert_eq!(Arc::ptr_eq(&asset_subset.0, &asset.0), expect_same_asset);
assert_eq!(asset.capacity(), AssetCapacity::new(3, Capacity(4.0)));
}

#[rstest]
#[should_panic(expected = "Cannot make an asset with zero tranches")]
fn with_subset_of_tranches_panics_for_zero_tranches(commissioned_multi_tranche: AssetRef) {
commissioned_multi_tranche.with_subset_of_tranches(0);
}

#[rstest]
#[should_panic(expected = "Cannot make an asset with more tranches than original")]
fn with_subset_of_tranches_panics_for_too_many_tranches(commissioned_multi_tranche: AssetRef) {
commissioned_multi_tranche.with_subset_of_tranches(4);
}

#[rstest]
fn asset_commission(process: Process) {
// Test successful commissioning of Ready asset
Expand Down Expand Up @@ -1781,18 +1720,6 @@ mod tests {
assert!(Arc::ptr_eq(&asset.0, &same.0));
}

#[rstest]
fn with_subset_of_tranches_caps_mothballed(commissioned_multi_tranche: AssetRef) {
// Mothball all 3 tranches
let asset = commissioned_multi_tranche.with_mothballed_tranches(3, Some(2020));
assert_eq!(asset.get_num_mothballed_tranches(), 3);

// Taking a subset of 2 tranches caps the mothballed count at the new number of tranches
let subset = asset.with_subset_of_tranches(2);
assert_eq!(subset.num_tranches(), 2);
assert_eq!(subset.get_num_mothballed_tranches(), 2);
}

#[rstest]
fn with_decommission_mothballed_nothing_old_enough(commissioned_multi_tranche: AssetRef) {
let asset = commissioned_multi_tranche.with_mothballed_tranches(1, Some(2020));
Expand All @@ -1812,8 +1739,14 @@ mod tests {
.with_mothballed_tranches(2, Some(2020));

// With a threshold of 2015, only the 2010 event is old enough to decommission
let result = asset.with_decommission_mothballed(2025, 10).unwrap();
let result = asset
.clone()
.with_decommission_mothballed(2025, 10)
.unwrap();
assert_eq!(result.num_tranches(), 2);
assert_eq!(result.capacity(), AssetCapacity::new(2, Capacity(4.0)));
assert_eq!(result.id(), asset.id());
assert_eq!(result.agent_id(), asset.agent_id());
assert_eq!(result.get_num_mothballed_tranches(), 1);
assert_equal(
result.get_mothball_events().unwrap().iter(),
Expand Down
5 changes: 4 additions & 1 deletion src/asset/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,10 @@ mod tests {
fn asset_pool_mothball_unretained_partial(commissioned_multi_tranche: AssetRef) {
// The full asset has three tranches; only two of them were retained in the pool
let full = commissioned_multi_tranche;
let retained = full.clone().with_subset_of_tranches(2);
let mut retained = full.clone();
retained
.make_mut()
.set_capacity(AssetCapacity::new(2, Capacity(4.0)));
Comment on lines +632 to +635

let mut asset_pool = AssetPool::new();
asset_pool.assets.push(retained);
Expand Down
21 changes: 0 additions & 21 deletions src/simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,27 +178,6 @@ fn run_dispatch_for_year(
debug_assert!(assets.iter().all(|asset| !asset.is_candidate()));
debug_assert!(candidates.iter().all(|asset| asset.is_candidate()));

// Only include non-mothballed tranches
let assets_vec: Vec<AssetRef>;
let assets = if assets
.iter()
.any(|asset| asset.has_any_mothballed_tranches())
{
assets_vec = assets
.iter()
.cloned()
.filter_map(|asset| {
// Exclude fully mothballed assets entirely. If assets are partially mothballed,
// get a new asset without the mothballed tranches.
let num_tranches = asset.get_num_nonmothballed_tranches();
(num_tranches > 0).then(|| asset.with_subset_of_tranches(num_tranches))
})
.collect();
&assets_vec
} else {
assets
};

// Run dispatch optimisation with existing assets only, if there are any. If not, then assume no
// flows (i.e. all are zero)
let (solution_existing, flow_map) = if assets.is_empty() {
Comment thread
tsmbland marked this conversation as resolved.
Expand Down
5 changes: 2 additions & 3 deletions src/simulation/investment/appraisal/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ pub fn add_activity_constraints(
activity_vars: &IndexMap<TimeSliceID, Variable>,
time_slice_info: &TimeSliceInfo,
) {
let capacity = asset.total_capacity();
for (ts_selection, limits) in asset.iter_activity_per_capacity_limits() {
let limits = (capacity * *limits.start()).value()..=(capacity * *limits.end()).value();
for (ts_selection, limits) in asset.iter_activity_limits() {
let limits = limits.start().value()..=limits.end().value();

// Collect activity terms for the time slices in this selection
let terms = ts_selection
Expand Down
128 changes: 128 additions & 0 deletions tests/data/simple_mothball/commodity_flows.csv
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,54 @@ milestone_year,asset_id,commodity_id,time_slice,flow
2030,2,ELCTRI,autumn.day,6.001752635595889
2030,2,ELCTRI,autumn.peak,1.5488393825638174
2030,2,ELCTRI,autumn.evening,1.9008483513729917
2030,3,GASNAT,winter.night,0.0
2030,3,ELCTRI,winter.night,-0.0
2030,3,CO2EMT,winter.night,-0.0
2030,3,GASNAT,winter.day,-0.0
2030,3,ELCTRI,winter.day,0.0
2030,3,CO2EMT,winter.day,0.0
2030,3,GASNAT,winter.peak,-0.0
2030,3,ELCTRI,winter.peak,0.0
2030,3,CO2EMT,winter.peak,0.0
2030,3,GASNAT,winter.evening,-0.0
2030,3,ELCTRI,winter.evening,0.0
2030,3,CO2EMT,winter.evening,0.0
2030,3,GASNAT,peak.night,0.0
2030,3,ELCTRI,peak.night,-0.0
2030,3,CO2EMT,peak.night,-0.0
2030,3,GASNAT,peak.day,-0.0
2030,3,ELCTRI,peak.day,0.0
2030,3,CO2EMT,peak.day,0.0
2030,3,GASNAT,peak.peak,-0.0
2030,3,ELCTRI,peak.peak,0.0
2030,3,CO2EMT,peak.peak,0.0
2030,3,GASNAT,peak.evening,0.0
2030,3,ELCTRI,peak.evening,-0.0
2030,3,CO2EMT,peak.evening,-0.0
2030,3,GASNAT,summer.night,-0.0
2030,3,ELCTRI,summer.night,0.0
2030,3,CO2EMT,summer.night,0.0
2030,3,GASNAT,summer.day,-0.0
2030,3,ELCTRI,summer.day,0.0
2030,3,CO2EMT,summer.day,0.0
2030,3,GASNAT,summer.peak,-0.0
2030,3,ELCTRI,summer.peak,0.0
2030,3,CO2EMT,summer.peak,0.0
2030,3,GASNAT,summer.evening,-0.0
2030,3,ELCTRI,summer.evening,0.0
2030,3,CO2EMT,summer.evening,0.0
2030,3,GASNAT,autumn.night,0.0
2030,3,ELCTRI,autumn.night,-0.0
2030,3,CO2EMT,autumn.night,-0.0
2030,3,GASNAT,autumn.day,-0.0
2030,3,ELCTRI,autumn.day,0.0
2030,3,CO2EMT,autumn.day,0.0
2030,3,GASNAT,autumn.peak,0.0
2030,3,ELCTRI,autumn.peak,-0.0
2030,3,CO2EMT,autumn.peak,-0.0
2030,3,GASNAT,autumn.evening,0.0
2030,3,ELCTRI,autumn.evening,-0.0
2030,3,CO2EMT,autumn.evening,-0.0
2030,4,GASNAT,winter.night,-39.00834276911883
2030,4,RSHEAT,winter.night,33.920298060103335
2030,4,CO2EMT,winter.night,1994.4965657850462
Expand Down Expand Up @@ -591,6 +639,22 @@ milestone_year,asset_id,commodity_id,time_slice,flow
2040,1,GASPRD,autumn.evening,-50.47700532224177
2040,1,GASNAT,autumn.evening,48.07333840213502
2040,1,CO2EMT,autumn.evening,122.8994896250582
2040,2,ELCTRI,winter.night,0.0
2040,2,ELCTRI,winter.day,0.0
2040,2,ELCTRI,winter.peak,0.0
2040,2,ELCTRI,winter.evening,0.0
2040,2,ELCTRI,peak.night,0.0
2040,2,ELCTRI,peak.day,0.0
2040,2,ELCTRI,peak.peak,0.0
2040,2,ELCTRI,peak.evening,0.0
2040,2,ELCTRI,summer.night,0.0
2040,2,ELCTRI,summer.day,0.0
2040,2,ELCTRI,summer.peak,0.0
2040,2,ELCTRI,summer.evening,0.0
2040,2,ELCTRI,autumn.night,0.0
2040,2,ELCTRI,autumn.day,0.0
2040,2,ELCTRI,autumn.peak,0.0
2040,2,ELCTRI,autumn.evening,0.0
2040,6,GASNAT,winter.night,-7.589000616341825
2040,6,RSHEAT,winter.night,6.5991309707320225
2040,6,CO2EMT,winter.night,388.02560151355755
Expand Down Expand Up @@ -639,6 +703,70 @@ milestone_year,asset_id,commodity_id,time_slice,flow
2040,6,GASNAT,autumn.evening,-3.7797267573329014
2040,6,RSHEAT,autumn.evening,3.2867189194199145
2040,6,CO2EMT,autumn.evening,193.25742910243127
2040,7,ELCTRI,winter.night,0.0
2040,7,ELCTRI,winter.day,0.0
2040,7,ELCTRI,winter.peak,0.0
2040,7,ELCTRI,winter.evening,0.0
2040,7,ELCTRI,peak.night,0.0
2040,7,ELCTRI,peak.day,0.0
2040,7,ELCTRI,peak.peak,0.0
2040,7,ELCTRI,peak.evening,0.0
2040,7,ELCTRI,summer.night,0.0
2040,7,ELCTRI,summer.day,0.0
2040,7,ELCTRI,summer.peak,0.0
2040,7,ELCTRI,summer.evening,0.0
2040,7,ELCTRI,autumn.night,0.0
2040,7,ELCTRI,autumn.day,0.0
2040,7,ELCTRI,autumn.peak,0.0
2040,7,ELCTRI,autumn.evening,0.0
2040,8,GASNAT,winter.night,-0.0
2040,8,ELCTRI,winter.night,0.0
2040,8,CO2EMT,winter.night,0.0
2040,8,GASNAT,winter.day,-0.0
2040,8,ELCTRI,winter.day,0.0
2040,8,CO2EMT,winter.day,0.0
2040,8,GASNAT,winter.peak,-0.0
2040,8,ELCTRI,winter.peak,0.0
2040,8,CO2EMT,winter.peak,0.0
2040,8,GASNAT,winter.evening,-0.0
2040,8,ELCTRI,winter.evening,0.0
2040,8,CO2EMT,winter.evening,0.0
2040,8,GASNAT,peak.night,-0.0
2040,8,ELCTRI,peak.night,0.0
2040,8,CO2EMT,peak.night,0.0
2040,8,GASNAT,peak.day,-0.0
2040,8,ELCTRI,peak.day,0.0
2040,8,CO2EMT,peak.day,0.0
2040,8,GASNAT,peak.peak,-0.0
2040,8,ELCTRI,peak.peak,0.0
2040,8,CO2EMT,peak.peak,0.0
2040,8,GASNAT,peak.evening,-0.0
2040,8,ELCTRI,peak.evening,0.0
2040,8,CO2EMT,peak.evening,0.0
2040,8,GASNAT,summer.night,-0.0
2040,8,ELCTRI,summer.night,0.0
2040,8,CO2EMT,summer.night,0.0
2040,8,GASNAT,summer.day,-0.0
2040,8,ELCTRI,summer.day,0.0
2040,8,CO2EMT,summer.day,0.0
2040,8,GASNAT,summer.peak,-0.0
2040,8,ELCTRI,summer.peak,0.0
2040,8,CO2EMT,summer.peak,0.0
2040,8,GASNAT,summer.evening,-0.0
2040,8,ELCTRI,summer.evening,0.0
2040,8,CO2EMT,summer.evening,0.0
2040,8,GASNAT,autumn.night,-0.0
2040,8,ELCTRI,autumn.night,0.0
2040,8,CO2EMT,autumn.night,0.0
2040,8,GASNAT,autumn.day,-0.0
2040,8,ELCTRI,autumn.day,0.0
2040,8,CO2EMT,autumn.day,0.0
2040,8,GASNAT,autumn.peak,-0.0
2040,8,ELCTRI,autumn.peak,0.0
2040,8,CO2EMT,autumn.peak,0.0
2040,8,GASNAT,autumn.evening,-0.0
2040,8,ELCTRI,autumn.evening,0.0
2040,8,CO2EMT,autumn.evening,0.0
2040,9,GASNAT,winter.night,-77.32841770402716
2040,9,RSHEAT,winter.night,67.24210235132797
2040,9,CO2EMT,winter.night,3953.801997206909
Expand Down
Loading