1use super::{AssetID, AssetRef, AssetState, UserAsset};
3use itertools::Itertools;
4use log::warn;
5
6#[derive(Default, derive_more::Deref)]
8pub struct AssetPool {
9 #[deref]
11 assets: Vec<AssetRef>,
12 next_id: u32,
14}
15
16impl AssetPool {
17 pub fn new() -> Self {
19 Self::default()
20 }
21
22 pub fn commission_new(&mut self, year: u32, user_assets: &mut Vec<UserAsset>) -> &[AssetRef] {
26 let start = self.assets.len();
27 let to_commission = user_assets.extract_if(.., |asset| asset.commission_year <= year);
28
29 for asset in to_commission {
30 if asset.max_decommission_year() <= year {
32 warn!(
33 "User asset '{}' with commission year {} with maximum decommission year {} \
34 was decommissioned before start of the simulation",
35 asset.process_id(),
36 asset.commission_year,
37 asset.max_decommission_year
38 );
39 continue;
40 }
41
42 self.commission(asset.into());
43 }
44
45 &self.assets[start..]
46 }
47
48 fn commission(&mut self, mut asset: AssetRef) {
50 asset.make_mut().commission(AssetID(self.next_id));
51 self.next_id += 1;
52 self.assets.push(asset);
53 }
54
55 pub fn decommission_old(&mut self, year: u32) {
57 self.assets
58 .extract_if(.., |asset| asset.max_decommission_year() <= year)
59 .for_each(|asset| {
60 asset.decommission("end of life");
61 });
62 }
63
64 pub fn decommission_mothballed(&mut self, year: u32, mothball_years: u32) {
66 self.assets = std::mem::take(&mut self.assets)
70 .into_iter()
71 .filter_map(|asset| asset.with_decommission_mothballed(year, mothball_years))
72 .collect();
73 }
74
75 pub fn mothball_unretained<I>(&mut self, assets: I, year: u32)
87 where
88 I: IntoIterator<Item = AssetRef>,
89 {
90 for old_asset in assets {
91 let id = old_asset
92 .id()
93 .expect("Cannot mothball asset that has not been commissioned");
94
95 if let Some(new_asset) = self
98 .assets
99 .iter_mut()
100 .find(|asset| asset.id().unwrap() == id)
101 {
102 let num_mothballed = old_asset
105 .num_units()
106 .checked_sub(new_asset.num_units())
107 .expect("Number of units has increased");
108 *new_asset = old_asset.with_mothballed_units(num_mothballed, Some(year));
109 } else {
110 let num_mothballed = old_asset.num_units();
113 self.assets
114 .push(old_asset.with_mothballed_units(num_mothballed, Some(year)));
115 }
116 }
117 self.assets.sort();
118 }
119
120 pub fn get(&self, id: AssetID) -> Option<&AssetRef> {
127 let idx = self
129 .assets
130 .binary_search_by(|asset| match &asset.state {
131 AssetState::Commissioned { id: asset_id, .. } => asset_id.cmp(&id),
132 _ => panic!("Active pool should only contain commissioned assets"),
133 })
134 .ok()?;
135
136 Some(&self.assets[idx])
137 }
138
139 pub fn take(&mut self) -> Vec<AssetRef> {
141 std::mem::take(&mut self.assets)
142 }
143
144 pub fn extend<I>(&mut self, assets: I) -> &[AssetRef]
148 where
149 I: IntoIterator<Item = AssetRef>,
150 {
151 let first_new_id = self.next_id;
152
153 for asset in assets {
156 match &asset.state {
157 AssetState::Commissioned { .. } => {
158 self.assets.push(asset);
159 }
160 AssetState::Ready { .. } => {
161 self.commission(asset);
162 }
163 AssetState::Candidate => panic!(
164 "Cannot extend asset pool with asset in state {}. Only assets in \
165 Commissioned or Ready states are allowed.",
166 asset.state
167 ),
168 }
169 }
170
171 self.assets.sort();
173
174 debug_assert_eq!(self.assets.iter().unique().count(), self.assets.len());
176
177 let new_start = self.assets.partition_point(|a| match &a.state {
180 AssetState::Commissioned { id, .. } => id.0 < first_new_id,
181 _ => panic!("Active pool should only contain commissioned assets"),
182 });
183 &self.assets[new_start..]
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::super::Asset;
190 use super::*;
191 use crate::asset::MothballEvent;
192 use crate::fixture::{asset, asset_divisible, process, process_parameter_map};
193 use crate::process::{Process, ProcessParameter};
194 use crate::units::{
195 Capacity, Dimensionless, MoneyPerActivity, MoneyPerCapacity, MoneyPerCapacityPerYear,
196 };
197 use itertools::{Itertools, assert_equal};
198 use rstest::{fixture, rstest};
199 use std::iter;
200 use std::rc::Rc;
201
202 #[fixture]
203 fn user_assets(mut process: Process) -> Vec<UserAsset> {
204 let process_param = ProcessParameter {
206 capital_cost: MoneyPerCapacity(5.0),
207 fixed_operating_cost: MoneyPerCapacityPerYear(2.0),
208 variable_operating_cost: MoneyPerActivity(1.0),
209 lifetime: 20,
210 discount_rate: Dimensionless(0.9),
211 };
212 let process_parameter_map = process_parameter_map(process.regions.clone(), process_param);
213 process.parameters = process_parameter_map;
214
215 let rc_process = Rc::new(process);
216 [2020, 2010]
217 .map(|year| {
218 UserAsset::new(
219 "agent1".into(),
220 Rc::clone(&rc_process),
221 "GBR".into(),
222 Capacity(1.0),
223 year,
224 None,
225 )
226 .unwrap()
227 })
228 .into_iter()
229 .collect_vec()
230 }
231
232 #[rstest]
233 fn asset_pool_new() {
234 assert!(AssetPool::new().assets.is_empty());
235 }
236
237 #[rstest]
238 fn asset_pool_commission_new1(mut user_assets: Vec<UserAsset>) {
239 let mut asset_pool = AssetPool::new();
241 asset_pool.commission_new(2010, &mut user_assets);
242 assert_equal(asset_pool.iter(), iter::once(&asset_pool.assets[0]));
243 }
244
245 #[rstest]
246 fn asset_pool_commission_new2(mut user_assets: Vec<UserAsset>) {
247 let mut asset_pool = AssetPool::new();
249 asset_pool.commission_new(2011, &mut user_assets);
250 assert_equal(asset_pool.iter(), iter::once(&asset_pool.assets[0]));
251 }
252
253 #[rstest]
254 fn asset_pool_commission_new3(mut user_assets: Vec<UserAsset>) {
255 let mut asset_pool = AssetPool::new();
257 asset_pool.commission_new(2000, &mut user_assets);
258 assert!(asset_pool.iter().next().is_none()); }
260
261 #[rstest]
262 fn asset_pool_commission_new_divisible(asset_divisible: Asset) {
263 let commission_year = asset_divisible.commission_year;
264 let mut asset_pool = AssetPool::new();
265 let mut user_assets = vec![asset_divisible.into()];
266 assert!(asset_pool.assets.is_empty());
267 asset_pool.commission_new(commission_year, &mut user_assets);
268 assert!(user_assets.is_empty());
269 assert_eq!(asset_pool.assets.len(), 1);
270 assert_eq!(asset_pool.next_id, 1);
271 }
272
273 #[rstest]
274 fn asset_pool_commission_already_decommissioned(asset: Asset) {
275 let year = asset.max_decommission_year();
276 let mut asset_pool = AssetPool::new();
277 assert!(asset_pool.assets.is_empty());
278 asset_pool.commission_new(year, &mut vec![asset.into()]);
279 assert!(asset_pool.assets.is_empty());
280 }
281
282 #[rstest]
283 fn asset_pool_decommission_old(mut user_assets: Vec<UserAsset>) {
284 let mut asset_pool = AssetPool::new();
285 asset_pool.commission_new(2020, &mut user_assets);
286 assert!(user_assets.is_empty());
287 assert_eq!(asset_pool.assets.len(), 2);
288
289 asset_pool.decommission_old(2030);
291 assert_eq!(asset_pool.assets.len(), 1);
292 assert_eq!(asset_pool.assets[0].commission_year, 2020);
293
294 asset_pool.decommission_old(2032);
296 assert_eq!(asset_pool.assets.len(), 1);
297 assert_eq!(asset_pool.assets[0].commission_year, 2020);
298
299 asset_pool.decommission_old(2040);
301 assert!(asset_pool.assets.is_empty());
302 }
303
304 #[rstest]
305 fn asset_pool_get(mut user_assets: Vec<UserAsset>) {
306 let mut asset_pool = AssetPool::new();
307 asset_pool.commission_new(2020, &mut user_assets);
308 assert_eq!(asset_pool.get(AssetID(0)), Some(&asset_pool.assets[0]));
309 assert_eq!(asset_pool.get(AssetID(1)), Some(&asset_pool.assets[1]));
310 }
311
312 #[rstest]
313 fn asset_pool_extend_empty(mut user_assets: Vec<UserAsset>) {
314 let mut asset_pool = AssetPool::new();
316 asset_pool.commission_new(2020, &mut user_assets);
317 let original_count = asset_pool.assets.len();
318
319 asset_pool.extend(Vec::<AssetRef>::new());
321
322 assert_eq!(asset_pool.assets.len(), original_count);
323 }
324
325 #[rstest]
326 fn asset_pool_extend_existing_assets(mut user_assets: Vec<UserAsset>) {
327 let mut asset_pool = AssetPool::new();
329 asset_pool.commission_new(2020, &mut user_assets);
330 assert_eq!(asset_pool.assets.len(), 2);
331 let existing_assets = asset_pool.take();
332
333 asset_pool.extend(existing_assets.clone());
335
336 assert_eq!(asset_pool.assets.len(), 2);
337 assert_eq!(asset_pool.assets[0].id(), Some(AssetID(0)));
338 assert_eq!(asset_pool.assets[1].id(), Some(AssetID(1)));
339 }
340
341 #[rstest]
342 fn asset_pool_extend_new_assets(mut user_assets: Vec<UserAsset>, process: Process) {
343 let mut asset_pool = AssetPool::new();
345 asset_pool.commission_new(2020, &mut user_assets);
346 let original_count = asset_pool.assets.len();
347
348 let process_rc = Rc::new(process);
350 let new_assets = vec![
351 Asset::new_ready(
352 "agent2".into(),
353 Rc::clone(&process_rc),
354 "GBR".into(),
355 Capacity(1.5),
356 2015,
357 )
358 .unwrap()
359 .into(),
360 Asset::new_ready(
361 "agent3".into(),
362 Rc::clone(&process_rc),
363 "GBR".into(),
364 Capacity(2.5),
365 2020,
366 )
367 .unwrap()
368 .into(),
369 ];
370
371 asset_pool.extend(new_assets);
372
373 assert_eq!(asset_pool.assets.len(), original_count + 2);
374 assert_eq!(asset_pool.assets[original_count].id(), Some(AssetID(2)));
376 assert_eq!(asset_pool.assets[original_count + 1].id(), Some(AssetID(3)));
377 assert_eq!(
378 asset_pool.assets[original_count].agent_id(),
379 Some(&"agent2".into())
380 );
381 assert_eq!(
382 asset_pool.assets[original_count + 1].agent_id(),
383 Some(&"agent3".into())
384 );
385 }
386
387 #[rstest]
388 fn asset_pool_extend_mixed_assets(mut user_assets: Vec<UserAsset>, process: Process) {
389 let mut asset_pool = AssetPool::new();
391 asset_pool.commission_new(2020, &mut user_assets);
392
393 let new_asset = Asset::new_ready(
395 "agent_new".into(),
396 process.into(),
397 "GBR".into(),
398 Capacity(3.0),
399 2015,
400 )
401 .unwrap()
402 .into();
403
404 asset_pool.extend(vec![new_asset]);
406
407 assert_eq!(asset_pool.assets.len(), 3);
408 assert!(asset_pool.assets.iter().any(|a| a.id() == Some(AssetID(0))));
410 assert!(asset_pool.assets.iter().any(|a| a.id() == Some(AssetID(1))));
411 assert!(asset_pool.assets.iter().any(|a| a.id() == Some(AssetID(2))));
412 assert!(
414 asset_pool
415 .assets
416 .iter()
417 .any(|a| a.agent_id() == Some(&"agent_new".into()))
418 );
419 }
420
421 #[rstest]
422 fn asset_pool_extend_maintains_sort_order(mut user_assets: Vec<UserAsset>, process: Process) {
423 let mut asset_pool = AssetPool::new();
425 asset_pool.commission_new(2020, &mut user_assets);
426
427 let process_rc = Rc::new(process);
429 let new_assets = vec![
430 Asset::new_ready(
431 "agent_high_id".into(),
432 Rc::clone(&process_rc),
433 "GBR".into(),
434 Capacity(1.0),
435 2010,
436 )
437 .unwrap()
438 .into(),
439 Asset::new_ready(
440 "agent_low_id".into(),
441 Rc::clone(&process_rc),
442 "GBR".into(),
443 Capacity(1.0),
444 2015,
445 )
446 .unwrap()
447 .into(),
448 ];
449
450 asset_pool.extend(new_assets);
451
452 let ids: Vec<u32> = asset_pool.iter().map(|a| a.id().unwrap().0).collect();
454 assert_equal(ids, 0..4);
455 }
456
457 #[rstest]
458 fn asset_pool_extend_no_duplicates_expected(mut user_assets: Vec<UserAsset>) {
459 let mut asset_pool = AssetPool::new();
461 asset_pool.commission_new(2020, &mut user_assets);
462 let original_count = asset_pool.assets.len();
463
464 asset_pool.extend(Vec::new());
467
468 assert_eq!(asset_pool.assets.len(), original_count);
469 assert_eq!(
471 asset_pool.assets.iter().unique().count(),
472 asset_pool.assets.len()
473 );
474 }
475
476 #[rstest]
477 fn asset_pool_extend_increments_next_id(mut user_assets: Vec<UserAsset>, process: Process) {
478 let mut asset_pool = AssetPool::new();
480 asset_pool.commission_new(2020, &mut user_assets);
481 assert_eq!(asset_pool.next_id, 2); let process_rc = Rc::new(process);
485 let new_assets = vec![
486 Asset::new_ready(
487 "agent1".into(),
488 Rc::clone(&process_rc),
489 "GBR".into(),
490 Capacity(1.0),
491 2015,
492 )
493 .unwrap()
494 .into(),
495 Asset::new_ready(
496 "agent2".into(),
497 Rc::clone(&process_rc),
498 "GBR".into(),
499 Capacity(1.0),
500 2020,
501 )
502 .unwrap()
503 .into(),
504 ];
505
506 asset_pool.extend(new_assets);
507
508 assert_eq!(asset_pool.next_id, 4);
510 assert_eq!(asset_pool.assets[2].id(), Some(AssetID(2)));
511 assert_eq!(asset_pool.assets[3].id(), Some(AssetID(3)));
512 }
513
514 #[rstest]
515 fn asset_pool_mothball_unretained(mut user_assets: Vec<UserAsset>) {
516 let mut asset_pool = AssetPool::new();
518 asset_pool.commission_new(2020, &mut user_assets);
519 assert_eq!(asset_pool.assets.len(), 2);
520
521 let removed_asset = asset_pool.assets.remove(0);
523 assert_eq!(asset_pool.assets.len(), 1);
524
525 let assets_to_check = vec![removed_asset.clone(), asset_pool.assets[0].clone()];
527 asset_pool.mothball_unretained(assets_to_check, 2025);
528
529 assert_eq!(asset_pool.assets.len(), 2); assert_equal(
532 asset_pool.assets[0].get_mothball_events().unwrap().iter(),
533 &[MothballEvent {
534 year: 2025,
535 num_units: 1,
536 }],
537 );
538 }
539
540 #[rstest]
541 fn asset_pool_decommission_unused(mut user_assets: Vec<UserAsset>) {
542 let mut asset_pool = AssetPool::new();
544 asset_pool.commission_new(2020, &mut user_assets);
545 assert_eq!(asset_pool.assets.len(), 2);
546
547 let mothball_years: u32 = 10;
549 asset_pool.assets[0] = asset_pool[0]
550 .clone()
551 .with_mothballed_units(1, Some(2025 - mothball_years));
552
553 assert_equal(
554 asset_pool.assets[0].get_mothball_events().unwrap().iter(),
555 &[MothballEvent {
556 year: 2025 - mothball_years,
557 num_units: 1,
558 }],
559 );
560
561 asset_pool.decommission_mothballed(2025, mothball_years);
563
564 assert_eq!(asset_pool.assets.len(), 1); }
567
568 #[rstest]
569 fn asset_pool_decommission_if_not_active_none_active(mut user_assets: Vec<UserAsset>) {
570 let mut asset_pool = AssetPool::new();
572 asset_pool.commission_new(2020, &mut user_assets);
573 let all_assets = asset_pool.assets.clone();
574
575 asset_pool.assets.clear();
577
578 asset_pool.mothball_unretained(all_assets.clone(), 2025);
580
581 assert_eq!(asset_pool.assets.len(), 2);
583 assert_eq!(asset_pool.assets[0].id(), all_assets[0].id());
584 assert_equal(
585 asset_pool.assets[0].get_mothball_events().unwrap().iter(),
586 &[MothballEvent {
587 year: 2025,
588 num_units: 1,
589 }],
590 );
591 assert_eq!(asset_pool.assets[1].id(), all_assets[1].id());
592 assert_equal(
593 asset_pool.assets[1].get_mothball_events().unwrap().iter(),
594 &[MothballEvent {
595 year: 2025,
596 num_units: 1,
597 }],
598 );
599 }
600
601 #[rstest]
602 #[should_panic(expected = "Cannot mothball asset that has not been commissioned")]
603 fn asset_pool_decommission_if_not_active_non_commissioned_asset(process: Process) {
604 let non_commissioned_asset = Asset::new_ready(
606 "agent_new".into(),
607 process.into(),
608 "GBR".into(),
609 Capacity(1.0),
610 2015,
611 )
612 .unwrap()
613 .into();
614
615 let mut asset_pool = AssetPool::new();
617 asset_pool.mothball_unretained(vec![non_commissioned_asset], 2025);
618 }
619
620 #[fixture]
622 fn commissioned_divisible(mut asset_divisible: Asset) -> AssetRef {
623 asset_divisible.commission(AssetID(0));
624 assert_eq!(asset_divisible.num_units(), 3);
625 AssetRef::from(asset_divisible)
626 }
627
628 #[rstest]
629 fn asset_pool_mothball_unretained_partial(commissioned_divisible: AssetRef) {
630 let full = commissioned_divisible;
632 let retained = full.clone().with_subset_of_units(2);
633
634 let mut asset_pool = AssetPool::new();
635 asset_pool.assets.push(retained);
636
637 asset_pool.mothball_unretained(vec![full], 2025);
638
639 assert_eq!(asset_pool.assets.len(), 1);
641 let asset = &asset_pool.assets[0];
642 assert_eq!(asset.num_units(), 3);
643 assert_eq!(asset.get_num_mothballed_units(), 1);
644 assert_equal(
645 asset.get_mothball_events().unwrap().iter(),
646 &[MothballEvent {
647 year: 2025,
648 num_units: 1,
649 }],
650 );
651 }
652
653 #[rstest]
654 fn asset_pool_decommission_mothballed_partial(commissioned_divisible: AssetRef) {
655 let asset = commissioned_divisible
657 .with_mothballed_units(1, Some(2010))
658 .with_mothballed_units(2, Some(2020));
659
660 let mut asset_pool = AssetPool::new();
661 asset_pool.assets.push(asset);
662
663 asset_pool.decommission_mothballed(2025, 10);
665
666 assert_eq!(asset_pool.assets.len(), 1);
667 let asset = &asset_pool.assets[0];
668 assert_eq!(asset.num_units(), 2);
669 assert_eq!(asset.get_num_mothballed_units(), 1);
670 assert_equal(
671 asset.get_mothball_events().unwrap().iter(),
672 &[MothballEvent {
673 year: 2020,
674 num_units: 1,
675 }],
676 );
677 }
678
679 #[rstest]
680 fn asset_pool_decommission_mothballed_removes_fully_mothballed(
681 commissioned_divisible: AssetRef,
682 ) {
683 let asset = commissioned_divisible.with_mothballed_units(3, Some(2010));
685
686 let mut asset_pool = AssetPool::new();
687 asset_pool.assets.push(asset);
688
689 asset_pool.decommission_mothballed(2025, 10);
690
691 assert!(asset_pool.assets.is_empty());
692 }
693}