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>) -> &[AssetRef] {
34 let start = self.assets.len();
35 let to_commission = user_assets.extract_if(.., |asset| asset.commission_year <= year);
36
37 for asset in to_commission {
38 if asset.max_decommission_year() <= year {
40 warn!(
41 "User asset '{}' with commission year {} with maximum decommission year {} \
42 was decommissioned before start of the simulation",
43 asset.process_id(),
44 asset.commission_year,
45 asset.max_decommission_year
46 );
47 continue;
48 }
49
50 self.commission(asset, "user input");
51 }
52
53 &self.assets[start..]
54 }
55
56 fn commission(&mut self, asset: AssetRef, reason: &str) {
58 asset.into_for_each_child(&mut self.next_group_id, |parent, mut child| {
59 child
60 .make_mut()
61 .commission(AssetID(self.next_id), parent.cloned(), reason);
62 self.next_id += 1;
63 self.assets.push(child);
64 });
65 }
66
67 pub fn decommission_old(&mut self, year: u32) {
69 self.assets
70 .extract_if(.., |asset| asset.max_decommission_year() <= year)
71 .for_each(|mut asset| {
72 asset.make_mut().decommission("end of life");
73 });
74 }
75
76 pub fn decommission_mothballed(&mut self, year: u32, mothball_years: u32) {
78 self.assets
79 .extract_if(.., |asset| {
80 asset
81 .get_mothballed_year()
82 .is_some_and(|myear| myear <= year - min(mothball_years, year))
83 })
84 .for_each(|mut asset| {
85 asset.make_mut().decommission(&format!(
86 "The asset has not been used for the set mothball years ({mothball_years} \
87 years)."
88 ));
89 });
90 }
91
92 pub fn mothball_unretained<I>(&mut self, assets: I, year: u32)
104 where
105 I: IntoIterator<Item = AssetRef>,
106 {
107 for mut asset in assets {
108 let in_pool = match asset.state {
109 AssetState::Commissioned { .. } => !self.assets.contains(&asset),
110 _ => panic!("Cannot mothball asset that has not been commissioned"),
111 };
112
113 if in_pool {
114 if asset.get_mothballed_year().is_none() {
117 asset.make_mut().mothball(year);
118 }
119
120 self.assets.push(asset);
123 }
124 }
125 self.assets.sort();
126 }
127
128 pub fn get(&self, id: AssetID) -> Option<&AssetRef> {
135 let idx = self
137 .assets
138 .binary_search_by(|asset| match &asset.state {
139 AssetState::Commissioned { id: asset_id, .. } => asset_id.cmp(&id),
140 _ => panic!("Active pool should only contain commissioned assets"),
141 })
142 .ok()?;
143
144 Some(&self.assets[idx])
145 }
146
147 #[allow(clippy::iter_without_into_iter)]
149 pub fn iter(&self) -> slice::Iter<'_, AssetRef> {
150 self.assets.iter()
151 }
152
153 pub fn take(&mut self) -> Vec<AssetRef> {
155 std::mem::take(&mut self.assets)
156 }
157
158 pub fn extend<I>(&mut self, assets: I) -> &[AssetRef]
162 where
163 I: IntoIterator<Item = AssetRef>,
164 {
165 let first_new_id = self.next_id;
166
167 for mut asset in assets {
170 match &asset.state {
171 AssetState::Commissioned { .. } => {
172 asset.make_mut().unmothball();
173 self.assets.push(asset);
174 }
175 AssetState::Selected { .. } => {
176 self.commission(asset, "selected");
177 }
178 _ => panic!(
179 "Cannot extend asset pool with asset in state {}. Only assets in \
180 Commissioned or Selected states are allowed.",
181 asset.state
182 ),
183 }
184 }
185
186 self.assets.sort();
188
189 debug_assert_eq!(self.assets.iter().unique().count(), self.assets.len());
191
192 let new_start = self.assets.partition_point(|a| match &a.state {
195 AssetState::Commissioned { id, .. } => id.0 < first_new_id,
196 _ => panic!("Active pool should only contain commissioned assets"),
197 });
198 &self.assets[new_start..]
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::super::Asset;
205 use super::*;
206 use crate::fixture::{asset, asset_divisible, process, process_parameter_map};
207 use crate::process::{Process, ProcessParameter};
208 use crate::units::{
209 Capacity, Dimensionless, MoneyPerActivity, MoneyPerCapacity, MoneyPerCapacityPerYear,
210 };
211 use itertools::{Itertools, assert_equal};
212 use rstest::{fixture, rstest};
213 use std::iter;
214 use std::rc::Rc;
215
216 #[fixture]
217 fn user_assets(mut process: Process) -> Vec<AssetRef> {
218 let process_param = ProcessParameter {
220 capital_cost: MoneyPerCapacity(5.0),
221 fixed_operating_cost: MoneyPerCapacityPerYear(2.0),
222 variable_operating_cost: MoneyPerActivity(1.0),
223 lifetime: 20,
224 discount_rate: Dimensionless(0.9),
225 };
226 let process_parameter_map = process_parameter_map(process.regions.clone(), process_param);
227 process.parameters = process_parameter_map;
228
229 let rc_process = Rc::new(process);
230 [2020, 2010]
231 .map(|year| {
232 Asset::new_future(
233 "agent1".into(),
234 Rc::clone(&rc_process),
235 "GBR".into(),
236 Capacity(1.0),
237 year,
238 )
239 .unwrap()
240 .into()
241 })
242 .into_iter()
243 .collect_vec()
244 }
245
246 #[rstest]
247 fn asset_pool_new() {
248 assert!(AssetPool::new().assets.is_empty());
249 }
250
251 #[rstest]
252 fn asset_pool_commission_new1(mut user_assets: Vec<AssetRef>) {
253 let mut asset_pool = AssetPool::new();
255 asset_pool.commission_new(2010, &mut user_assets);
256 assert_equal(asset_pool.iter(), iter::once(&asset_pool.assets[0]));
257 }
258
259 #[rstest]
260 fn asset_pool_commission_new2(mut user_assets: Vec<AssetRef>) {
261 let mut asset_pool = AssetPool::new();
263 asset_pool.commission_new(2011, &mut user_assets);
264 assert_equal(asset_pool.iter(), iter::once(&asset_pool.assets[0]));
265 }
266
267 #[rstest]
268 fn asset_pool_commission_new3(mut user_assets: Vec<AssetRef>) {
269 let mut asset_pool = AssetPool::new();
271 asset_pool.commission_new(2000, &mut user_assets);
272 assert!(asset_pool.iter().next().is_none()); }
274
275 #[allow(clippy::cast_possible_truncation)]
277 #[allow(clippy::cast_sign_loss)]
278 fn expected_children_for_divisible(asset: &Asset) -> usize {
279 (asset.total_capacity() / asset.process.unit_size.expect("Asset is not divisible"))
280 .value()
281 .ceil() as usize
282 }
283
284 #[rstest]
285 fn asset_pool_commission_new_divisible(asset_divisible: Asset) {
286 let commission_year = asset_divisible.commission_year;
287 let expected_children = expected_children_for_divisible(&asset_divisible);
288 let mut asset_pool = AssetPool::new();
289 let mut user_assets = vec![asset_divisible.into()];
290 assert!(asset_pool.assets.is_empty());
291 asset_pool.commission_new(commission_year, &mut user_assets);
292 assert!(user_assets.is_empty());
293 assert!(!asset_pool.assets.is_empty());
294 assert_eq!(asset_pool.assets.len(), expected_children);
295 assert_eq!(asset_pool.next_group_id, 1);
296 }
297
298 #[rstest]
299 #[allow(clippy::cast_possible_truncation)]
300 fn asset_pool_commission_new_returns_only_assets_from_call(asset_divisible: Asset) {
301 let year = asset_divisible.commission_year();
302 let expected_children = expected_children_for_divisible(&asset_divisible);
303 let mut asset_pool = AssetPool::new();
304
305 let mut user_assets = vec![asset_divisible.clone().into()];
307 let first_batch = asset_pool.commission_new(year, &mut user_assets).to_vec();
308 assert_eq!(first_batch.len(), expected_children);
309 assert!(first_batch.iter().all(|asset| asset.parent().is_some()));
310
311 let n = expected_children as u32;
313 assert_equal(first_batch.iter().map(|a| a.id().unwrap().0), 0..n);
314
315 let mut later_assets = vec![asset_divisible.into()];
317 let second_batch = asset_pool
318 .commission_new(year + 1, &mut later_assets)
319 .to_vec();
320 assert_eq!(asset_pool.assets.len(), expected_children * 2);
321 assert!(
322 second_batch
323 .iter()
324 .all(|asset| !first_batch.iter().any(|old| old == asset))
325 );
326
327 assert_equal(second_batch.iter().map(|a| a.id().unwrap().0), n..n * 2);
329 }
330
331 #[rstest]
332 fn asset_pool_commission_already_decommissioned(asset: Asset) {
333 let year = asset.max_decommission_year();
334 let mut asset_pool = AssetPool::new();
335 assert!(asset_pool.assets.is_empty());
336 asset_pool.commission_new(year, &mut vec![asset.into()]);
337 assert!(asset_pool.assets.is_empty());
338 }
339
340 #[rstest]
341 fn asset_pool_decommission_old(mut user_assets: Vec<AssetRef>) {
342 let mut asset_pool = AssetPool::new();
343 asset_pool.commission_new(2020, &mut user_assets);
344 assert!(user_assets.is_empty());
345 assert_eq!(asset_pool.assets.len(), 2);
346
347 asset_pool.decommission_old(2030);
349 assert_eq!(asset_pool.assets.len(), 1);
350 assert_eq!(asset_pool.assets[0].commission_year, 2020);
351
352 asset_pool.decommission_old(2032);
354 assert_eq!(asset_pool.assets.len(), 1);
355 assert_eq!(asset_pool.assets[0].commission_year, 2020);
356
357 asset_pool.decommission_old(2040);
359 assert!(asset_pool.assets.is_empty());
360 }
361
362 #[rstest]
363 fn asset_pool_get(mut user_assets: Vec<AssetRef>) {
364 let mut asset_pool = AssetPool::new();
365 asset_pool.commission_new(2020, &mut user_assets);
366 assert_eq!(asset_pool.get(AssetID(0)), Some(&asset_pool.assets[0]));
367 assert_eq!(asset_pool.get(AssetID(1)), Some(&asset_pool.assets[1]));
368 }
369
370 #[rstest]
371 fn asset_pool_extend_empty(mut user_assets: Vec<AssetRef>) {
372 let mut asset_pool = AssetPool::new();
374 asset_pool.commission_new(2020, &mut user_assets);
375 let original_count = asset_pool.assets.len();
376
377 asset_pool.extend(Vec::<AssetRef>::new());
379
380 assert_eq!(asset_pool.assets.len(), original_count);
381 }
382
383 #[rstest]
384 fn asset_pool_extend_existing_assets(mut user_assets: Vec<AssetRef>) {
385 let mut asset_pool = AssetPool::new();
387 asset_pool.commission_new(2020, &mut user_assets);
388 assert_eq!(asset_pool.assets.len(), 2);
389 let existing_assets = asset_pool.take();
390
391 asset_pool.extend(existing_assets.clone());
393
394 assert_eq!(asset_pool.assets.len(), 2);
395 assert_eq!(asset_pool.assets[0].id(), Some(AssetID(0)));
396 assert_eq!(asset_pool.assets[1].id(), Some(AssetID(1)));
397 }
398
399 #[rstest]
400 fn asset_pool_extend_new_assets(mut user_assets: Vec<AssetRef>, process: Process) {
401 let mut asset_pool = AssetPool::new();
403 asset_pool.commission_new(2020, &mut user_assets);
404 let original_count = asset_pool.assets.len();
405
406 let process_rc = Rc::new(process);
408 let new_assets = vec![
409 Asset::new_selected(
410 "agent2".into(),
411 Rc::clone(&process_rc),
412 "GBR".into(),
413 Capacity(1.5),
414 2015,
415 )
416 .unwrap()
417 .into(),
418 Asset::new_selected(
419 "agent3".into(),
420 Rc::clone(&process_rc),
421 "GBR".into(),
422 Capacity(2.5),
423 2020,
424 )
425 .unwrap()
426 .into(),
427 ];
428
429 asset_pool.extend(new_assets);
430
431 assert_eq!(asset_pool.assets.len(), original_count + 2);
432 assert_eq!(asset_pool.assets[original_count].id(), Some(AssetID(2)));
434 assert_eq!(asset_pool.assets[original_count + 1].id(), Some(AssetID(3)));
435 assert_eq!(
436 asset_pool.assets[original_count].agent_id(),
437 Some(&"agent2".into())
438 );
439 assert_eq!(
440 asset_pool.assets[original_count + 1].agent_id(),
441 Some(&"agent3".into())
442 );
443 }
444
445 #[rstest]
446 fn asset_pool_extend_new_divisible_assets(
447 mut user_assets: Vec<AssetRef>,
448 mut process: Process,
449 ) {
450 let mut asset_pool = AssetPool::new();
452 asset_pool.commission_new(2020, &mut user_assets);
453 let original_count = asset_pool.assets.len();
454
455 process.unit_size = Some(Capacity(4.0));
457 let process_rc = Rc::new(process);
458 let new_assets: Vec<AssetRef> = vec![
459 Asset::new_selected(
460 "agent2".into(),
461 Rc::clone(&process_rc),
462 "GBR".into(),
463 Capacity(11.0),
464 2015,
465 )
466 .unwrap()
467 .into(),
468 ];
469 let expected_children = expected_children_for_divisible(&new_assets[0]);
470 asset_pool.extend(new_assets);
471 assert_eq!(asset_pool.assets.len(), original_count + expected_children);
472 }
473
474 #[rstest]
475 #[allow(clippy::cast_possible_truncation)]
476 fn asset_pool_extend_returns_only_newly_commissioned_assets(
477 mut user_assets: Vec<AssetRef>,
478 mut process: Process,
479 ) {
480 let mut asset_pool = AssetPool::new();
481
482 let initial_assets = asset_pool.commission_new(2020, &mut user_assets);
484 let initial_count = initial_assets.len();
485 let existing_assets = asset_pool.take();
486
487 process.unit_size = Some(Capacity(4.0));
489 let process_rc = Rc::new(process);
490 let selected_divisible: AssetRef = Asset::new_selected(
491 "agent_selected".into(),
492 Rc::clone(&process_rc),
493 "GBR".into(),
494 Capacity(11.0),
495 2020,
496 )
497 .unwrap()
498 .into();
499 let expected_new = expected_children_for_divisible(&selected_divisible);
500
501 let returned = asset_pool
503 .extend(
504 existing_assets
505 .iter()
506 .cloned()
507 .chain(iter::once(selected_divisible)),
508 )
509 .to_vec();
510
511 assert_eq!(returned.len(), expected_new);
513 assert_eq!(asset_pool.assets.len(), initial_count + expected_new);
514 assert!(returned.iter().all(|asset| asset.parent().is_some()));
515
516 let start = initial_count as u32;
518 let end = (initial_count + expected_new) as u32;
519 assert_equal(returned.iter().map(|a| a.id().unwrap().0), start..end);
520 }
521
522 #[rstest]
523 fn asset_pool_extend_mixed_assets(mut user_assets: Vec<AssetRef>, process: Process) {
524 let mut asset_pool = AssetPool::new();
526 asset_pool.commission_new(2020, &mut user_assets);
527
528 let new_asset = Asset::new_selected(
530 "agent_new".into(),
531 process.into(),
532 "GBR".into(),
533 Capacity(3.0),
534 2015,
535 )
536 .unwrap()
537 .into();
538
539 asset_pool.extend(vec![new_asset]);
541
542 assert_eq!(asset_pool.assets.len(), 3);
543 assert!(asset_pool.assets.iter().any(|a| a.id() == Some(AssetID(0))));
545 assert!(asset_pool.assets.iter().any(|a| a.id() == Some(AssetID(1))));
546 assert!(asset_pool.assets.iter().any(|a| a.id() == Some(AssetID(2))));
547 assert!(
549 asset_pool
550 .assets
551 .iter()
552 .any(|a| a.agent_id() == Some(&"agent_new".into()))
553 );
554 }
555
556 #[rstest]
557 fn asset_pool_extend_maintains_sort_order(mut user_assets: Vec<AssetRef>, process: Process) {
558 let mut asset_pool = AssetPool::new();
560 asset_pool.commission_new(2020, &mut user_assets);
561
562 let process_rc = Rc::new(process);
564 let new_assets = vec![
565 Asset::new_selected(
566 "agent_high_id".into(),
567 Rc::clone(&process_rc),
568 "GBR".into(),
569 Capacity(1.0),
570 2010,
571 )
572 .unwrap()
573 .into(),
574 Asset::new_selected(
575 "agent_low_id".into(),
576 Rc::clone(&process_rc),
577 "GBR".into(),
578 Capacity(1.0),
579 2015,
580 )
581 .unwrap()
582 .into(),
583 ];
584
585 asset_pool.extend(new_assets);
586
587 let ids: Vec<u32> = asset_pool.iter().map(|a| a.id().unwrap().0).collect();
589 assert_equal(ids, 0..4);
590 }
591
592 #[rstest]
593 fn asset_pool_extend_no_duplicates_expected(mut user_assets: Vec<AssetRef>) {
594 let mut asset_pool = AssetPool::new();
596 asset_pool.commission_new(2020, &mut user_assets);
597 let original_count = asset_pool.assets.len();
598
599 asset_pool.extend(Vec::new());
602
603 assert_eq!(asset_pool.assets.len(), original_count);
604 assert_eq!(
606 asset_pool.assets.iter().unique().count(),
607 asset_pool.assets.len()
608 );
609 }
610
611 #[rstest]
612 fn asset_pool_extend_increments_next_id(mut user_assets: Vec<AssetRef>, process: Process) {
613 let mut asset_pool = AssetPool::new();
615 asset_pool.commission_new(2020, &mut user_assets);
616 assert_eq!(asset_pool.next_id, 2); let process_rc = Rc::new(process);
620 let new_assets = vec![
621 Asset::new_selected(
622 "agent1".into(),
623 Rc::clone(&process_rc),
624 "GBR".into(),
625 Capacity(1.0),
626 2015,
627 )
628 .unwrap()
629 .into(),
630 Asset::new_selected(
631 "agent2".into(),
632 Rc::clone(&process_rc),
633 "GBR".into(),
634 Capacity(1.0),
635 2020,
636 )
637 .unwrap()
638 .into(),
639 ];
640
641 asset_pool.extend(new_assets);
642
643 assert_eq!(asset_pool.next_id, 4);
645 assert_eq!(asset_pool.assets[2].id(), Some(AssetID(2)));
646 assert_eq!(asset_pool.assets[3].id(), Some(AssetID(3)));
647 }
648
649 #[rstest]
650 fn asset_pool_mothball_unretained(mut user_assets: Vec<AssetRef>) {
651 let mut asset_pool = AssetPool::new();
653 asset_pool.commission_new(2020, &mut user_assets);
654 assert_eq!(asset_pool.assets.len(), 2);
655
656 let removed_asset = asset_pool.assets.remove(0);
658 assert_eq!(asset_pool.assets.len(), 1);
659
660 let assets_to_check = vec![removed_asset.clone(), asset_pool.assets[0].clone()];
662 asset_pool.mothball_unretained(assets_to_check, 2025);
663
664 assert_eq!(asset_pool.assets.len(), 2); assert_eq!(asset_pool.assets[0].get_mothballed_year(), Some(2025));
667 }
668
669 #[rstest]
670 fn asset_pool_decommission_unused(mut user_assets: Vec<AssetRef>) {
671 let mut asset_pool = AssetPool::new();
673 asset_pool.commission_new(2020, &mut user_assets);
674 assert_eq!(asset_pool.assets.len(), 2);
675
676 let mothball_years: u32 = 10;
678 asset_pool.assets[0]
679 .make_mut()
680 .mothball(2025 - mothball_years);
681
682 assert_eq!(
683 asset_pool.assets[0].get_mothballed_year(),
684 Some(2025 - mothball_years)
685 );
686
687 asset_pool.decommission_mothballed(2025, mothball_years);
689
690 assert_eq!(asset_pool.assets.len(), 1); }
693
694 #[rstest]
695 fn asset_pool_decommission_if_not_active_none_active(mut user_assets: Vec<AssetRef>) {
696 let mut asset_pool = AssetPool::new();
698 asset_pool.commission_new(2020, &mut user_assets);
699 let all_assets = asset_pool.assets.clone();
700
701 asset_pool.assets.clear();
703
704 asset_pool.mothball_unretained(all_assets.clone(), 2025);
706
707 assert_eq!(asset_pool.assets.len(), 2);
709 assert_eq!(asset_pool.assets[0].id(), all_assets[0].id());
710 assert_eq!(asset_pool.assets[0].get_mothballed_year(), Some(2025));
711 assert_eq!(asset_pool.assets[1].id(), all_assets[1].id());
712 assert_eq!(asset_pool.assets[1].get_mothballed_year(), Some(2025));
713 }
714
715 #[rstest]
716 #[should_panic(expected = "Cannot mothball asset that has not been commissioned")]
717 fn asset_pool_decommission_if_not_active_non_commissioned_asset(process: Process) {
718 let non_commissioned_asset = Asset::new_future(
720 "agent_new".into(),
721 process.into(),
722 "GBR".into(),
723 Capacity(1.0),
724 2015,
725 )
726 .unwrap()
727 .into();
728
729 let mut asset_pool = AssetPool::new();
731 asset_pool.mothball_unretained(vec![non_commissioned_asset], 2025);
732 }
733}