Skip to content

Commit 5bb5ec3

Browse files
authored
Merge pull request #966 from EnergySystemsModellingLab/add-optional-decommission-year-for-assets
add max_decommission year to asset file
2 parents 1c696c0 + 25b0131 commit 5bb5ec3

3 files changed

Lines changed: 98 additions & 7 deletions

File tree

schemas/input/assets.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,10 @@ fields:
2222
This value can be any integer >=0. If it is before the start of the simulation, it will
2323
already be commissioned in the first year and if it after the end of the simulation then it
2424
will never be commissioned.
25+
- name: max_decommission_year
26+
type: integer
27+
description: The latest year in which this asset can be decommissioned
28+
notes: |
29+
This value can be any integer that satisfies max_decommission_year >= commission_year >=0.
30+
If it is set before the simulation starting year, the asset will not be commissioned during the
31+
simulation.

src/asset.rs

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ pub struct Asset {
9595
capacity: Capacity,
9696
/// The year the asset was/will be commissioned
9797
commission_year: u32,
98+
/// The maximum year that the asset could be decommissioned
99+
max_decommission_year: u32,
98100
}
99101

100102
impl Asset {
@@ -111,6 +113,7 @@ impl Asset {
111113
region_id,
112114
capacity,
113115
commission_year,
116+
None,
114117
)
115118
}
116119

@@ -125,12 +128,13 @@ impl Asset {
125128
}
126129

127130
/// Create a new future asset
128-
pub fn new_future(
131+
pub fn new_future_with_max_decommission(
129132
agent_id: AgentID,
130133
process: Rc<Process>,
131134
region_id: RegionID,
132135
capacity: Capacity,
133136
commission_year: u32,
137+
max_decommission_year: Option<u32>,
134138
) -> Result<Self> {
135139
check_capacity_valid_for_asset(capacity)?;
136140
Self::new_with_state(
@@ -139,6 +143,25 @@ impl Asset {
139143
region_id,
140144
capacity,
141145
commission_year,
146+
max_decommission_year,
147+
)
148+
}
149+
150+
/// Create a new future asset
151+
pub fn new_future(
152+
agent_id: AgentID,
153+
process: Rc<Process>,
154+
region_id: RegionID,
155+
capacity: Capacity,
156+
commission_year: u32,
157+
) -> Result<Self> {
158+
Self::new_future_with_max_decommission(
159+
agent_id,
160+
process,
161+
region_id,
162+
capacity,
163+
commission_year,
164+
None,
142165
)
143166
}
144167

@@ -160,6 +183,7 @@ impl Asset {
160183
region_id,
161184
capacity,
162185
commission_year,
186+
None,
163187
)
164188
}
165189

@@ -170,6 +194,7 @@ impl Asset {
170194
region_id: RegionID,
171195
capacity: Capacity,
172196
commission_year: u32,
197+
max_decommission_year: Option<u32>,
173198
) -> Result<Self> {
174199
check_region_year_valid_for_process(&process, &region_id, commission_year)?;
175200
ensure!(capacity >= Capacity(0.0), "Capacity must be non-negative");
@@ -214,6 +239,13 @@ impl Asset {
214239
})?
215240
.clone();
216241

242+
let max_decommission_year =
243+
max_decommission_year.unwrap_or(commission_year + process_parameter.lifetime);
244+
ensure!(
245+
max_decommission_year >= commission_year,
246+
"Max decommission year must be after/same as commission year"
247+
);
248+
217249
Ok(Self {
218250
state,
219251
process,
@@ -223,6 +255,7 @@ impl Asset {
223255
region_id,
224256
capacity,
225257
commission_year,
258+
max_decommission_year,
226259
})
227260
}
228261

@@ -238,7 +271,7 @@ impl Asset {
238271

239272
/// The last year in which this asset should be decommissioned
240273
pub fn max_decommission_year(&self) -> u32 {
241-
self.commission_year + self.process_parameter.lifetime
274+
self.max_decommission_year
242275
}
243276

244277
/// Get the activity limits for this asset in a particular time slice
@@ -1503,8 +1536,8 @@ mod tests {
15031536
}
15041537

15051538
#[rstest]
1506-
#[case::early_decommission_within_lifetime(2024, 2024)]
1507-
#[case::decommission_at_maximum_year(2026, 2025)]
1539+
#[case::commission_during_process_lifetime(2024, 2024)]
1540+
#[case::decommission_after_process_lifetime_ends(2026, 2025)]
15081541
fn test_asset_decommission(
15091542
#[case] requested_decommission_year: u32,
15101543
#[case] expected_decommission_year: u32,
@@ -1530,6 +1563,38 @@ mod tests {
15301563
assert_eq!(asset.decommission_year(), Some(expected_decommission_year));
15311564
}
15321565

1566+
#[rstest]
1567+
#[case::decommission_after_predefined_max_year(2026, 2025, Some(2025))]
1568+
#[case::decommission_before_predefined_max_year(2024, 2024, Some(2025))]
1569+
#[case::decommission_during_process_lifetime_end_no_max_year(2024, 2024, None)]
1570+
#[case::decommission_after_process_lifetime_end_no_max_year(2026, 2025, None)]
1571+
fn test_asset_decommission_with_max_decommission_year_predefined(
1572+
#[case] requested_decommission_year: u32,
1573+
#[case] expected_decommission_year: u32,
1574+
#[case] max_decommission_year: Option<u32>,
1575+
process: Process,
1576+
) {
1577+
// Test successful commissioning of Future asset
1578+
let process_rc = Rc::new(process);
1579+
let mut asset = Asset::new_future_with_max_decommission(
1580+
"agent1".into(),
1581+
Rc::clone(&process_rc),
1582+
"GBR".into(),
1583+
Capacity(1.0),
1584+
2020,
1585+
max_decommission_year,
1586+
)
1587+
.unwrap();
1588+
asset.commission(AssetID(1), "");
1589+
assert!(asset.is_commissioned());
1590+
assert_eq!(asset.id(), Some(AssetID(1)));
1591+
1592+
// Test successful decommissioning
1593+
asset.decommission(requested_decommission_year, "");
1594+
assert!(!asset.is_commissioned());
1595+
assert_eq!(asset.decommission_year(), Some(expected_decommission_year));
1596+
}
1597+
15331598
#[rstest]
15341599
#[should_panic(expected = "Assets with state Candidate cannot be commissioned")]
15351600
fn test_commission_wrong_states(process: Process) {

src/input/asset.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ use std::rc::Rc;
1515

1616
const ASSETS_FILE_NAME: &str = "assets.csv";
1717

18-
#[derive(Deserialize, PartialEq)]
18+
#[derive(Default, Deserialize, PartialEq)]
1919
struct AssetRaw {
2020
process_id: String,
2121
region_id: String,
2222
agent_id: String,
2323
capacity: Capacity,
2424
commission_year: u32,
25+
#[serde(default)]
26+
max_decommission_year: Option<u32>,
2527
}
2628

2729
/// Read assets CSV file from model directory.
@@ -76,12 +78,13 @@ where
7678
.with_context(|| format!("Invalid process ID: {}", &asset.process_id))?;
7779
let region_id = region_ids.get_id(&asset.region_id)?;
7880

79-
Asset::new_future(
81+
Asset::new_future_with_max_decommission(
8082
agent_id.clone(),
8183
Rc::clone(process),
8284
region_id.clone(),
8385
asset.capacity,
8486
asset.commission_year,
87+
asset.max_decommission_year,
8588
)
8689
})
8790
.try_collect()
@@ -102,7 +105,10 @@ mod tests {
102105
}
103106

104107
#[rstest]
108+
#[case::max_decommission_year_provided(Some(2015))]
109+
#[case::max_decommission_year_not_provided(None)]
105110
fn test_read_assets_from_iter_valid(
111+
#[case] max_decommission_year: Option<u32>,
106112
agent_ids: IndexSet<AgentID>,
107113
processes: ProcessMap,
108114
region_ids: IndexSet<RegionID>,
@@ -113,13 +119,15 @@ mod tests {
113119
region_id: "GBR".into(),
114120
capacity: Capacity(1.0),
115121
commission_year: 2010,
122+
max_decommission_year: max_decommission_year,
116123
};
117-
let asset_out = Asset::new_future(
124+
let asset_out = Asset::new_future_with_max_decommission(
118125
"agent1".into(),
119126
Rc::clone(processes.values().next().unwrap()),
120127
"GBR".into(),
121128
Capacity(1.0),
122129
2010,
130+
max_decommission_year,
123131
)
124132
.unwrap();
125133
assert_equal(
@@ -136,20 +144,31 @@ mod tests {
136144
region_id: "GBR".into(),
137145
capacity: Capacity(1.0),
138146
commission_year: 2010,
147+
max_decommission_year: None,
139148
})]
140149
#[case(AssetRaw { // Bad agent ID
141150
agent_id: "agent2".into(),
142151
process_id: "process1".into(),
143152
region_id: "GBR".into(),
144153
capacity: Capacity(1.0),
145154
commission_year: 2010,
155+
max_decommission_year: None,
146156
})]
147157
#[case(AssetRaw { // Bad region ID: not in region_ids
148158
agent_id: "agent1".into(),
149159
process_id: "process1".into(),
150160
region_id: "FRA".into(),
151161
capacity: Capacity(1.0),
152162
commission_year: 2010,
163+
max_decommission_year: None,
164+
})]
165+
#[case(AssetRaw { // Bad max_decommission_year: before commission_year
166+
agent_id: "agent1".into(),
167+
process_id: "process1".into(),
168+
region_id: "GBR".into(),
169+
capacity: Capacity(1.0),
170+
commission_year: 2010,
171+
max_decommission_year: Some(2005),
153172
})]
154173
fn test_read_assets_from_iter_invalid(
155174
#[case] asset: AssetRaw,

0 commit comments

Comments
 (0)