1use super::{AssetID, AssetRef, AssetState};
3use itertools::Itertools;
4use log::warn;
5use std::cmp::min;
6use std::slice;
7
8#[derive(Default)]
10pub struct AssetPool {
11 assets: Vec<AssetRef>,
13 next_id: u32,
15 next_group_id: u32,
17}
18
19impl AssetPool {
20 pub fn new() -> Self {
22 Self::default()
23 }
24
25 pub fn as_slice(&self) -> &[AssetRef] {
27 &self.assets
28 }
29
30 pub fn commission_new(&mut self, year: u32, user_assets: &mut Vec<AssetRef>) {
32 let to_commission = user_assets.extract_if(.., |asset| asset.commission_year <= year);
33
34 for asset in to_commission {
35 if asset.max_decommission_year() <= year {
37 warn!(
38 "Asset '{}' with commission year {} and lifetime {} was decommissioned before \
39 the start of the simulation",
40 asset.process_id(),
41 asset.commission_year,
42 asset.process_parameter.lifetime
43 );
44 continue;
45 }
46
47 self.commission(asset, "user input");
48 }
49 }
50
51 fn commission(&mut self, asset: AssetRef, reason: &str) {
53 asset.into_for_each_child(&mut self.next_group_id, |parent, mut child| {
54 child
55 .make_mut()
56 .commission(AssetID(self.next_id), parent.cloned(), reason);
57 self.next_id += 1;
58 self.assets.push(child);
59 });
60 }
61
62 pub fn decommission_old<E: Extend<AssetRef>>(&mut self, year: u32, decommissioned: &mut E) {
64 let to_decommission = self
65 .assets
66 .extract_if(.., move |asset| asset.max_decommission_year() <= year)
67 .map(move |mut asset| {
68 asset.make_mut().decommission(year, "end of life");
69 asset
70 });
71 decommissioned.extend(to_decommission);
72 }
73
74 pub fn decommission_mothballed<E: Extend<AssetRef>>(
76 &mut self,
77 year: u32,
78 mothball_years: u32,
79 decommissioned: &mut E,
80 ) {
81 let to_decommission = self
82 .assets
83 .extract_if(.., move |asset| {
84 asset
85 .get_mothballed_year()
86 .is_some_and(|myear| myear <= year - min(mothball_years, year))
87 })
88 .map(move |mut asset| {
89 let decommissioned = asset.get_mothballed_year().unwrap() + mothball_years;
90 asset.make_mut().decommission(
91 decommissioned,
92 &format!(
93 "The asset has not been used for the set mothball years ({mothball_years} \
94 years)."
95 ),
96 );
97 asset
98 });
99 decommissioned.extend(to_decommission);
100 }
101
102 pub fn mothball_unretained<I>(&mut self, assets: I, year: u32)
114 where
115 I: IntoIterator<Item = AssetRef>,
116 {
117 for mut asset in assets {
118 let in_pool = match asset.state {
119 AssetState::Commissioned { .. } => !self.assets.contains(&asset),
120 _ => panic!("Cannot mothball asset that has not been commissioned"),
121 };
122
123 if in_pool {
124 if asset.get_mothballed_year().is_none() {
127 asset.make_mut().mothball(year);
128 }
129
130 self.assets.push(asset);
133 }
134 }
135 self.assets.sort();
136 }
137
138 pub fn get(&self, id: AssetID) -> Option<&AssetRef> {
145 let idx = self
147 .assets
148 .binary_search_by(|asset| match &asset.state {
149 AssetState::Commissioned { id: asset_id, .. } => asset_id.cmp(&id),
150 _ => panic!("Active pool should only contain commissioned assets"),
151 })
152 .ok()?;
153
154 Some(&self.assets[idx])
155 }
156
157 #[allow(clippy::iter_without_into_iter)]
159 pub fn iter(&self) -> slice::Iter<'_, AssetRef> {
160 self.assets.iter()
161 }
162
163 pub fn take(&mut self) -> Vec<AssetRef> {
165 std::mem::take(&mut self.assets)
166 }
167
168 pub fn extend<I>(&mut self, assets: I)
170 where
171 I: IntoIterator<Item = AssetRef>,
172 {
173 for mut asset in assets {
176 match &asset.state {
177 AssetState::Commissioned { .. } => {
178 asset.make_mut().unmothball();
179 self.assets.push(asset);
180 }
181 AssetState::Selected { .. } => {
182 self.commission(asset, "selected");
183 }
184 _ => panic!(
185 "Cannot extend asset pool with asset in state {}. Only assets in \
186 Commissioned or Selected states are allowed.",
187 asset.state
188 ),
189 }
190 }
191
192 self.assets.sort();
194
195 debug_assert_eq!(self.assets.iter().unique().count(), self.assets.len());
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::super::Asset;
203 use super::*;
204 use crate::fixture::{asset, asset_divisible, process, process_parameter_map};
205 use crate::process::{Process, ProcessParameter};
206 use crate::units::{
207 Capacity, Dimensionless, MoneyPerActivity, MoneyPerCapacity, MoneyPerCapacityPerYear,
208 };
209 use itertools::{Itertools, assert_equal};
210 use rstest::{fixture, rstest};
211 use std::iter;
212 use std::rc::Rc;
213
214 #[fixture]
215 fn user_assets(mut process: Process) -> Vec<AssetRef> {
216 let process_param = ProcessParameter {
218 capital_cost: MoneyPerCapacity(5.0),
219 fixed_operating_cost: MoneyPerCapacityPerYear(2.0),
220 variable_operating_cost: MoneyPerActivity(1.0),
221 lifetime: 20,
222 discount_rate: Dimensionless(0.9),
223 };
224 let process_parameter_map = process_parameter_map(process.regions.clone(), process_param);
225 process.parameters = process_parameter_map;
226
227 let rc_process = Rc::new(process);
228 [2020, 2010]
229 .map(|year| {
230 Asset::new_future(
231 "agent1".into(),
232 Rc::clone(&rc_process),
233 "GBR".into(),
234 Capacity(1.0),
235 year,
236 )
237 .unwrap()
238 .into()
239 })
240 .into_iter()
241 .collect_vec()
242 }
243
244 #[rstest]
245 fn asset_pool_new() {
246 assert!(AssetPool::new().assets.is_empty());
247 }
248
249 #[rstest]
250 fn asset_pool_commission_new1(mut user_assets: Vec<AssetRef>) {
251 let mut asset_pool = AssetPool::new();
253 asset_pool.commission_new(2010, &mut user_assets);
254 assert_equal(asset_pool.iter(), iter::once(&asset_pool.assets[0]));
255 }
256
257 #[rstest]
258 fn asset_pool_commission_new2(mut user_assets: Vec<AssetRef>) {
259 let mut asset_pool = AssetPool::new();
261 asset_pool.commission_new(2011, &mut user_assets);
262 assert_equal(asset_pool.iter(), iter::once(&asset_pool.assets[0]));
263 }
264
265 #[rstest]
266 fn asset_pool_commission_new3(mut user_assets: Vec<AssetRef>) {
267 let mut asset_pool = AssetPool::new();
269 asset_pool.commission_new(2000, &mut user_assets);
270 assert!(asset_pool.iter().next().is_none()); }
272
273 #[allow(clippy::cast_possible_truncation)]
275 #[allow(clippy::cast_sign_loss)]
276 fn expected_children_for_divisible(asset: &Asset) -> usize {
277 (asset.total_capacity() / asset.process.unit_size.expect("Asset is not divisible"))
278 .value()
279 .ceil() as usize
280 }
281
282 #[rstest]
283 fn asset_pool_commission_new_divisible(asset_divisible: Asset) {
284 let commission_year = asset_divisible.commission_year;
285 let expected_children = expected_children_for_divisible(&asset_divisible);
286 let mut asset_pool = AssetPool::new();
287 let mut user_assets = vec![asset_divisible.into()];
288 assert!(asset_pool.assets.is_empty());
289 asset_pool.commission_new(commission_year, &mut user_assets);
290 assert!(user_assets.is_empty());
291 assert!(!asset_pool.assets.is_empty());
292 assert_eq!(asset_pool.assets.len(), expected_children);
293 assert_eq!(asset_pool.next_group_id, 1);
294 }
295
296 #[rstest]
297 fn asset_pool_commission_already_decommissioned(asset: Asset) {
298 let year = asset.max_decommission_year();
299 let mut asset_pool = AssetPool::new();
300 assert!(asset_pool.assets.is_empty());
301 asset_pool.commission_new(year, &mut vec![asset.into()]);
302 assert!(asset_pool.assets.is_empty());
303 }
304
305 #[rstest]
306 fn asset_pool_decommission_old(mut user_assets: Vec<AssetRef>) {
307 let mut asset_pool = AssetPool::new();
308 asset_pool.commission_new(2020, &mut user_assets);
309 assert!(user_assets.is_empty());
310 assert_eq!(asset_pool.assets.len(), 2);
311 let mut decommissioned = Vec::new();
312
313 asset_pool.decommission_old(2030, &mut decommissioned);
315 assert_eq!(asset_pool.assets.len(), 1);
316 assert_eq!(asset_pool.assets[0].commission_year, 2020);
317 assert_eq!(decommissioned.len(), 1);
318 assert_eq!(decommissioned[0].commission_year, 2010);
319 assert_eq!(decommissioned[0].decommission_year(), Some(2030));
320
321 decommissioned.clear();
323 asset_pool.decommission_old(2032, &mut decommissioned);
324 assert_eq!(asset_pool.assets.len(), 1);
325 assert_eq!(asset_pool.assets[0].commission_year, 2020);
326
327 decommissioned.clear();
329 asset_pool.decommission_old(2040, &mut decommissioned);
330 assert!(asset_pool.assets.is_empty());
331 assert_eq!(decommissioned.len(), 1);
332 assert_eq!(decommissioned[0].commission_year, 2020);
333 assert_eq!(decommissioned[0].decommission_year(), Some(2040));
334 }
335
336 #[rstest]
337 fn asset_pool_get(mut user_assets: Vec<AssetRef>) {
338 let mut asset_pool = AssetPool::new();
339 asset_pool.commission_new(2020, &mut user_assets);
340 assert_eq!(asset_pool.get(AssetID(0)), Some(&asset_pool.assets[0]));
341 assert_eq!(asset_pool.get(AssetID(1)), Some(&asset_pool.assets[1]));
342 }
343
344 #[rstest]
345 fn asset_pool_extend_empty(mut user_assets: Vec<AssetRef>) {
346 let mut asset_pool = AssetPool::new();
348 asset_pool.commission_new(2020, &mut user_assets);
349 let original_count = asset_pool.assets.len();
350
351 asset_pool.extend(Vec::<AssetRef>::new());
353
354 assert_eq!(asset_pool.assets.len(), original_count);
355 }
356
357 #[rstest]
358 fn asset_pool_extend_existing_assets(mut user_assets: Vec<AssetRef>) {
359 let mut asset_pool = AssetPool::new();
361 asset_pool.commission_new(2020, &mut user_assets);
362 assert_eq!(asset_pool.assets.len(), 2);
363 let existing_assets = asset_pool.take();
364
365 asset_pool.extend(existing_assets.clone());
367
368 assert_eq!(asset_pool.assets.len(), 2);
369 assert_eq!(asset_pool.assets[0].id(), Some(AssetID(0)));
370 assert_eq!(asset_pool.assets[1].id(), Some(AssetID(1)));
371 }
372
373 #[rstest]
374 fn asset_pool_extend_new_assets(mut user_assets: Vec<AssetRef>, process: Process) {
375 let mut asset_pool = AssetPool::new();
377 asset_pool.commission_new(2020, &mut user_assets);
378 let original_count = asset_pool.assets.len();
379
380 let process_rc = Rc::new(process);
382 let new_assets = vec![
383 Asset::new_selected(
384 "agent2".into(),
385 Rc::clone(&process_rc),
386 "GBR".into(),
387 Capacity(1.5),
388 2015,
389 )
390 .unwrap()
391 .into(),
392 Asset::new_selected(
393 "agent3".into(),
394 Rc::clone(&process_rc),
395 "GBR".into(),
396 Capacity(2.5),
397 2020,
398 )
399 .unwrap()
400 .into(),
401 ];
402
403 asset_pool.extend(new_assets);
404
405 assert_eq!(asset_pool.assets.len(), original_count + 2);
406 assert_eq!(asset_pool.assets[original_count].id(), Some(AssetID(2)));
408 assert_eq!(asset_pool.assets[original_count + 1].id(), Some(AssetID(3)));
409 assert_eq!(
410 asset_pool.assets[original_count].agent_id(),
411 Some(&"agent2".into())
412 );
413 assert_eq!(
414 asset_pool.assets[original_count + 1].agent_id(),
415 Some(&"agent3".into())
416 );
417 }
418
419 #[rstest]
420 fn asset_pool_extend_new_divisible_assets(
421 mut user_assets: Vec<AssetRef>,
422 mut process: Process,
423 ) {
424 let mut asset_pool = AssetPool::new();
426 asset_pool.commission_new(2020, &mut user_assets);
427 let original_count = asset_pool.assets.len();
428
429 process.unit_size = Some(Capacity(4.0));
431 let process_rc = Rc::new(process);
432 let new_assets: Vec<AssetRef> = vec![
433 Asset::new_selected(
434 "agent2".into(),
435 Rc::clone(&process_rc),
436 "GBR".into(),
437 Capacity(11.0),
438 2015,
439 )
440 .unwrap()
441 .into(),
442 ];
443 let expected_children = expected_children_for_divisible(&new_assets[0]);
444 asset_pool.extend(new_assets);
445 assert_eq!(asset_pool.assets.len(), original_count + expected_children);
446 }
447
448 #[rstest]
449 fn asset_pool_extend_mixed_assets(mut user_assets: Vec<AssetRef>, process: Process) {
450 let mut asset_pool = AssetPool::new();
452 asset_pool.commission_new(2020, &mut user_assets);
453
454 let new_asset = Asset::new_selected(
456 "agent_new".into(),
457 process.into(),
458 "GBR".into(),
459 Capacity(3.0),
460 2015,
461 )
462 .unwrap()
463 .into();
464
465 asset_pool.extend(vec![new_asset]);
467
468 assert_eq!(asset_pool.assets.len(), 3);
469 assert!(asset_pool.assets.iter().any(|a| a.id() == Some(AssetID(0))));
471 assert!(asset_pool.assets.iter().any(|a| a.id() == Some(AssetID(1))));
472 assert!(asset_pool.assets.iter().any(|a| a.id() == Some(AssetID(2))));
473 assert!(
475 asset_pool
476 .assets
477 .iter()
478 .any(|a| a.agent_id() == Some(&"agent_new".into()))
479 );
480 }
481
482 #[rstest]
483 fn asset_pool_extend_maintains_sort_order(mut user_assets: Vec<AssetRef>, process: Process) {
484 let mut asset_pool = AssetPool::new();
486 asset_pool.commission_new(2020, &mut user_assets);
487
488 let process_rc = Rc::new(process);
490 let new_assets = vec![
491 Asset::new_selected(
492 "agent_high_id".into(),
493 Rc::clone(&process_rc),
494 "GBR".into(),
495 Capacity(1.0),
496 2010,
497 )
498 .unwrap()
499 .into(),
500 Asset::new_selected(
501 "agent_low_id".into(),
502 Rc::clone(&process_rc),
503 "GBR".into(),
504 Capacity(1.0),
505 2015,
506 )
507 .unwrap()
508 .into(),
509 ];
510
511 asset_pool.extend(new_assets);
512
513 let ids: Vec<u32> = asset_pool.iter().map(|a| a.id().unwrap().0).collect();
515 assert_equal(ids, 0..4);
516 }
517
518 #[rstest]
519 fn asset_pool_extend_no_duplicates_expected(mut user_assets: Vec<AssetRef>) {
520 let mut asset_pool = AssetPool::new();
522 asset_pool.commission_new(2020, &mut user_assets);
523 let original_count = asset_pool.assets.len();
524
525 asset_pool.extend(Vec::new());
528
529 assert_eq!(asset_pool.assets.len(), original_count);
530 assert_eq!(
532 asset_pool.assets.iter().unique().count(),
533 asset_pool.assets.len()
534 );
535 }
536
537 #[rstest]
538 fn asset_pool_extend_increments_next_id(mut user_assets: Vec<AssetRef>, process: Process) {
539 let mut asset_pool = AssetPool::new();
541 asset_pool.commission_new(2020, &mut user_assets);
542 assert_eq!(asset_pool.next_id, 2); let process_rc = Rc::new(process);
546 let new_assets = vec![
547 Asset::new_selected(
548 "agent1".into(),
549 Rc::clone(&process_rc),
550 "GBR".into(),
551 Capacity(1.0),
552 2015,
553 )
554 .unwrap()
555 .into(),
556 Asset::new_selected(
557 "agent2".into(),
558 Rc::clone(&process_rc),
559 "GBR".into(),
560 Capacity(1.0),
561 2020,
562 )
563 .unwrap()
564 .into(),
565 ];
566
567 asset_pool.extend(new_assets);
568
569 assert_eq!(asset_pool.next_id, 4);
571 assert_eq!(asset_pool.assets[2].id(), Some(AssetID(2)));
572 assert_eq!(asset_pool.assets[3].id(), Some(AssetID(3)));
573 }
574
575 #[rstest]
576 fn asset_pool_mothball_unretained(mut user_assets: Vec<AssetRef>) {
577 let mut asset_pool = AssetPool::new();
579 asset_pool.commission_new(2020, &mut user_assets);
580 assert_eq!(asset_pool.assets.len(), 2);
581
582 let removed_asset = asset_pool.assets.remove(0);
584 assert_eq!(asset_pool.assets.len(), 1);
585
586 let assets_to_check = vec![removed_asset.clone(), asset_pool.assets[0].clone()];
588 asset_pool.mothball_unretained(assets_to_check, 2025);
589
590 assert_eq!(asset_pool.assets.len(), 2); assert_eq!(asset_pool.assets[0].get_mothballed_year(), Some(2025));
593 }
594
595 #[rstest]
596 fn asset_pool_decommission_unused(mut user_assets: Vec<AssetRef>) {
597 let mut asset_pool = AssetPool::new();
599 asset_pool.commission_new(2020, &mut user_assets);
600 assert_eq!(asset_pool.assets.len(), 2);
601
602 let mothball_years: u32 = 10;
604 asset_pool.assets[0]
605 .make_mut()
606 .mothball(2025 - mothball_years);
607
608 assert_eq!(
609 asset_pool.assets[0].get_mothballed_year(),
610 Some(2025 - mothball_years)
611 );
612
613 let mut decommissioned = Vec::new();
615 asset_pool.decommission_mothballed(2025, mothball_years, &mut decommissioned);
616
617 assert_eq!(asset_pool.assets.len(), 1); assert_eq!(decommissioned.len(), 1);
620 assert_eq!(decommissioned[0].decommission_year(), Some(2025));
621 }
622
623 #[rstest]
624 fn asset_pool_decommission_if_not_active_none_active(mut user_assets: Vec<AssetRef>) {
625 let mut asset_pool = AssetPool::new();
627 asset_pool.commission_new(2020, &mut user_assets);
628 let all_assets = asset_pool.assets.clone();
629
630 asset_pool.assets.clear();
632
633 asset_pool.mothball_unretained(all_assets.clone(), 2025);
635
636 assert_eq!(asset_pool.assets.len(), 2);
638 assert_eq!(asset_pool.assets[0].id(), all_assets[0].id());
639 assert_eq!(asset_pool.assets[0].get_mothballed_year(), Some(2025));
640 assert_eq!(asset_pool.assets[1].id(), all_assets[1].id());
641 assert_eq!(asset_pool.assets[1].get_mothballed_year(), Some(2025));
642 }
643
644 #[rstest]
645 #[should_panic(expected = "Cannot mothball asset that has not been commissioned")]
646 fn asset_pool_decommission_if_not_active_non_commissioned_asset(process: Process) {
647 let non_commissioned_asset = Asset::new_future(
649 "agent_new".into(),
650 process.into(),
651 "GBR".into(),
652 Capacity(1.0),
653 2015,
654 )
655 .unwrap()
656 .into();
657
658 let mut asset_pool = AssetPool::new();
660 asset_pool.mothball_unretained(vec![non_commissioned_asset], 2025);
661 }
662}