1use crate::agent::AgentID;
3use crate::commodity::CommodityID;
4use crate::process::{Process, ProcessFlow, ProcessParameter};
5use crate::region::RegionID;
6use crate::time_slice::TimeSliceID;
7use crate::units::{Activity, Capacity};
8use anyhow::{ensure, Context, Result};
9use indexmap::IndexMap;
10use serde::{Deserialize, Serialize};
11use std::hash::{Hash, Hasher};
12use std::ops::{Deref, RangeInclusive};
13use std::rc::Rc;
14
15#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
17pub struct AssetID(u32);
18
19#[derive(Clone, Debug, PartialEq)]
21pub struct Asset {
22 pub id: Option<AssetID>,
24 pub agent_id: AgentID,
26 pub process: Rc<Process>,
28 pub process_parameter: Rc<ProcessParameter>,
30 pub region_id: RegionID,
32 pub capacity: Capacity,
34 pub commission_year: u32,
36}
37
38impl Asset {
39 pub fn new(
44 agent_id: AgentID,
45 process: Rc<Process>,
46 region_id: RegionID,
47 capacity: Capacity,
48 commission_year: u32,
49 ) -> Result<Self> {
50 ensure!(
51 process.regions.contains(®ion_id),
52 "Region {} is not one of the regions in which process {} operates",
53 region_id,
54 process.id
55 );
56
57 let process_parameter = process
58 .parameters
59 .get(&(region_id.clone(), commission_year))
60 .with_context(|| {
61 format!(
62 "Process {} does not operate in the year {}",
63 process.id, commission_year
64 )
65 })?
66 .clone();
67
68 ensure!(
69 capacity.is_finite() && capacity > Capacity(0.0),
70 "Capacity must be a finite, positive number"
71 );
72
73 Ok(Self {
74 id: None,
75 agent_id,
76 process,
77 process_parameter,
78 region_id,
79 capacity,
80 commission_year,
81 })
82 }
83
84 pub fn decommission_year(&self) -> u32 {
86 self.commission_year + self.process_parameter.lifetime
87 }
88
89 pub fn get_activity_limits(&self, time_slice: &TimeSliceID) -> RangeInclusive<Activity> {
91 let limits = self
92 .process
93 .activity_limits
94 .get(&(
95 self.region_id.clone(),
96 self.commission_year,
97 time_slice.clone(),
98 ))
99 .unwrap();
100 let max_act = self.maximum_activity();
101
102 (max_act * *limits.start())..=(max_act * *limits.end())
104 }
105
106 pub fn maximum_activity(&self) -> Activity {
108 self.capacity * self.process_parameter.capacity_to_activity
109 }
110
111 pub fn get_flow(&self, commodity_id: &CommodityID) -> Option<&ProcessFlow> {
113 self.get_flows_map().get(commodity_id)
114 }
115
116 fn get_flows_map(&self) -> &IndexMap<CommodityID, ProcessFlow> {
118 self.process
119 .flows
120 .get(&(self.region_id.clone(), self.commission_year))
121 .unwrap()
122 }
123
124 pub fn iter_flows(&self) -> impl Iterator<Item = &ProcessFlow> {
126 self.get_flows_map().values()
127 }
128}
129
130#[derive(Clone, Debug)]
137pub struct AssetRef(Rc<Asset>);
138
139impl From<Rc<Asset>> for AssetRef {
140 fn from(value: Rc<Asset>) -> Self {
141 Self(value)
142 }
143}
144
145impl From<Asset> for AssetRef {
146 fn from(value: Asset) -> Self {
147 Self::from(Rc::new(value))
148 }
149}
150
151impl From<AssetRef> for Rc<Asset> {
152 fn from(value: AssetRef) -> Self {
153 value.0
154 }
155}
156
157impl Deref for AssetRef {
158 type Target = Asset;
159
160 fn deref(&self) -> &Self::Target {
161 &self.0
162 }
163}
164
165impl PartialEq for AssetRef {
166 fn eq(&self, other: &Self) -> bool {
167 if self.0.id.is_some() {
168 self.0.id == other.0.id
169 } else {
170 other.0.id.is_none()
171 && self.0.agent_id == other.0.agent_id
172 && Rc::ptr_eq(&self.0.process, &other.0.process)
173 && self.0.region_id == other.0.region_id
174 && self.0.commission_year == other.0.commission_year
175 }
176 }
177}
178
179impl Eq for AssetRef {}
180
181impl Hash for AssetRef {
182 fn hash<H: Hasher>(&self, state: &mut H) {
184 if let Some(id) = self.0.id {
185 id.hash(state);
186 } else {
187 self.0.agent_id.hash(state);
188 self.0.process.id.hash(state);
189 self.0.region_id.hash(state);
190 self.0.commission_year.hash(state);
191 }
192 }
193}
194
195impl PartialOrd for AssetRef {
196 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
197 Some(self.cmp(other))
198 }
199}
200
201impl Ord for AssetRef {
202 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
203 self.id.unwrap().cmp(&other.id.unwrap())
204 }
205}
206
207pub struct AssetPool {
209 active: Vec<AssetRef>,
211 future: Vec<Asset>,
213 next_id: u32,
215}
216
217impl AssetPool {
218 pub fn new(mut assets: Vec<Asset>) -> Self {
220 assets.sort_by(|a, b| a.commission_year.cmp(&b.commission_year));
222
223 Self {
224 active: Vec::new(),
225 future: assets,
226 next_id: 0,
227 }
228 }
229
230 pub fn as_slice(&self) -> &[AssetRef] {
232 &self.active
233 }
234
235 pub fn commission_new(&mut self, year: u32) {
237 let count = self
239 .future
240 .iter()
241 .take_while(|asset| asset.commission_year <= year)
242 .count();
243
244 for mut asset in self.future.drain(0..count) {
246 asset.id = Some(AssetID(self.next_id));
247 self.next_id += 1;
248 self.active.push(asset.into());
249 }
250 }
251
252 pub fn decommission_old(&mut self, year: u32) {
254 self.active.retain(|asset| asset.decommission_year() > year);
255 }
256
257 pub fn get(&self, id: AssetID) -> Option<&AssetRef> {
264 let idx = self
266 .active
267 .binary_search_by(|asset| asset.id.unwrap().cmp(&id))
268 .ok()?;
269
270 Some(&self.active[idx])
271 }
272
273 pub fn iter(&self) -> std::slice::Iter<AssetRef> {
275 self.active.iter()
276 }
277
278 pub fn replace_active_pool<I>(&mut self, assets: I)
280 where
281 I: IntoIterator<Item = Rc<Asset>>,
282 {
283 let new_pool = assets.into_iter().map(|mut asset| {
284 if asset.id.is_none() {
285 let asset = Rc::make_mut(&mut asset);
287 asset.id = Some(AssetID(self.next_id));
288 self.next_id += 1;
289 }
290
291 asset.into()
292 });
293
294 self.active.clear();
295 self.active.extend(new_pool);
296
297 self.active.sort();
299 }
300}
301
302pub trait AssetIterator<'a> {
304 fn filter_region(self, region_id: &'a RegionID) -> impl Iterator<Item = &'a AssetRef> + 'a;
306
307 fn flows_for_commodity(
309 self,
310 commodity_id: &'a CommodityID,
311 ) -> impl Iterator<Item = (&'a AssetRef, &'a ProcessFlow)> + 'a;
312}
313
314impl<'a, I> AssetIterator<'a> for I
315where
316 I: Iterator<Item = &'a AssetRef> + 'a,
317{
318 fn filter_region(self, region_id: &'a RegionID) -> impl Iterator<Item = &'a AssetRef> + 'a {
319 self.filter(move |asset| asset.region_id == *region_id)
320 }
321
322 fn flows_for_commodity(
323 self,
324 commodity_id: &'a CommodityID,
325 ) -> impl Iterator<Item = (&'a AssetRef, &'a ProcessFlow)> + 'a {
326 self.filter_map(|asset| Some((asset, asset.get_flow(commodity_id)?)))
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333 use crate::fixture::{assert_error, process};
334 use crate::process::{
335 Process, ProcessActivityLimitsMap, ProcessFlowsMap, ProcessParameter, ProcessParameterMap,
336 };
337 use crate::units::{
338 ActivityPerCapacity, Dimensionless, MoneyPerActivity, MoneyPerCapacity,
339 MoneyPerCapacityPerYear,
340 };
341 use indexmap::IndexSet;
342 use itertools::{assert_equal, Itertools};
343 use rstest::{fixture, rstest};
344 use std::iter;
345 use std::ops::RangeInclusive;
346
347 #[rstest]
348 #[case(Capacity(0.01))]
349 #[case(Capacity(0.5))]
350 #[case(Capacity(1.0))]
351 #[case(Capacity(100.0))]
352 fn test_asset_new_valid(process: Process, #[case] capacity: Capacity) {
353 let agent_id = AgentID("agent1".into());
354 let region_id = RegionID("GBR".into());
355 let asset = Asset::new(agent_id, process.into(), region_id, capacity, 2015).unwrap();
356 assert!(asset.id.is_none());
357 }
358
359 #[rstest]
360 #[case(Capacity(0.0))]
361 #[case(Capacity(-0.01))]
362 #[case(Capacity(-1.0))]
363 #[case(Capacity(f64::NAN))]
364 #[case(Capacity(f64::INFINITY))]
365 #[case(Capacity(f64::NEG_INFINITY))]
366 fn test_asset_new_invalid_capacity(process: Process, #[case] capacity: Capacity) {
367 let agent_id = AgentID("agent1".into());
368 let region_id = RegionID("GBR".into());
369 assert_error!(
370 Asset::new(agent_id, process.into(), region_id, capacity, 2015),
371 "Capacity must be a finite, positive number"
372 );
373 }
374
375 #[rstest]
376 fn test_asset_new_invalid_commission_year(process: Process) {
377 let agent_id = AgentID("agent1".into());
378 let region_id = RegionID("GBR".into());
379 assert_error!(
380 Asset::new(agent_id, process.into(), region_id, Capacity(1.0), 2009),
381 "Process process1 does not operate in the year 2009"
382 );
383 }
384
385 #[rstest]
386 fn test_asset_new_invalid_region(process: Process) {
387 let agent_id = AgentID("agent1".into());
388 let region_id = RegionID("FRA".into());
389 assert_error!(
390 Asset::new(agent_id, process.into(), region_id, Capacity(1.0), 2015),
391 "Region FRA is not one of the regions in which process process1 operates"
392 );
393 }
394
395 #[fixture]
396 fn asset_pool() -> AssetPool {
397 let process_param = Rc::new(ProcessParameter {
398 capital_cost: MoneyPerCapacity(5.0),
399 fixed_operating_cost: MoneyPerCapacityPerYear(2.0),
400 variable_operating_cost: MoneyPerActivity(1.0),
401 lifetime: 5,
402 discount_rate: Dimensionless(0.9),
403 capacity_to_activity: ActivityPerCapacity(1.0),
404 });
405 let years = RangeInclusive::new(2010, 2020).collect_vec();
406 let process_parameter_map: ProcessParameterMap = years
407 .iter()
408 .map(|&year| (("GBR".into(), year), process_param.clone()))
409 .collect();
410 let process = Rc::new(Process {
411 id: "process1".into(),
412 description: "Description".into(),
413 years: vec![2010, 2020],
414 activity_limits: ProcessActivityLimitsMap::new(),
415 flows: ProcessFlowsMap::new(),
416 parameters: process_parameter_map,
417 regions: IndexSet::from(["GBR".into()]),
418 });
419 let future = [2020, 2010]
420 .map(|year| {
421 Asset::new(
422 "agent1".into(),
423 Rc::clone(&process),
424 "GBR".into(),
425 Capacity(1.0),
426 year,
427 )
428 .unwrap()
429 })
430 .into_iter()
431 .collect_vec();
432
433 AssetPool::new(future)
434 }
435
436 #[test]
437 fn test_asset_get_activity_limits() {
438 let time_slice = TimeSliceID {
439 season: "winter".into(),
440 time_of_day: "day".into(),
441 };
442 let process_param = Rc::new(ProcessParameter {
443 capital_cost: MoneyPerCapacity(5.0),
444 fixed_operating_cost: MoneyPerCapacityPerYear(2.0),
445 variable_operating_cost: MoneyPerActivity(1.0),
446 lifetime: 5,
447 discount_rate: Dimensionless(0.9),
448 capacity_to_activity: ActivityPerCapacity(3.0),
449 });
450 let years = RangeInclusive::new(2010, 2020).collect_vec();
451 let process_parameter_map: ProcessParameterMap = years
452 .iter()
453 .map(|&year| (("GBR".into(), year), process_param.clone()))
454 .collect();
455 let fraction_limits = Dimensionless(1.0)..=Dimensionless(f64::INFINITY);
456 let mut activity_limits = ProcessActivityLimitsMap::new();
457 for year in [2010, 2020] {
458 activity_limits.insert(
459 ("GBR".into(), year, time_slice.clone()),
460 fraction_limits.clone(),
461 );
462 }
463 let process = Rc::new(Process {
464 id: "process1".into(),
465 description: "Description".into(),
466 years: vec![2010, 2020],
467 activity_limits,
468 flows: ProcessFlowsMap::new(),
469 parameters: process_parameter_map,
470 regions: IndexSet::from(["GBR".into()]),
471 });
472 let asset = Asset::new(
473 "agent1".into(),
474 Rc::clone(&process),
475 "GBR".into(),
476 Capacity(2.0),
477 2010,
478 )
479 .unwrap();
480
481 assert_eq!(
482 asset.get_activity_limits(&time_slice),
483 Activity(6.0)..=Activity(f64::INFINITY)
484 );
485 }
486
487 #[rstest]
488 fn test_asset_pool_new(asset_pool: AssetPool) {
489 assert!(asset_pool.active.is_empty());
491 assert!(asset_pool.future.len() == 2);
492 assert!(asset_pool.future[0].commission_year == 2010);
493 assert!(asset_pool.future[1].commission_year == 2020);
494 }
495
496 #[rstest]
497 fn test_asset_pool_commission_new1(mut asset_pool: AssetPool) {
498 asset_pool.commission_new(2010);
500 assert_equal(asset_pool.iter(), iter::once(&asset_pool.active[0]));
501 }
502
503 #[rstest]
504 fn test_asset_pool_commission_new2(mut asset_pool: AssetPool) {
505 asset_pool.commission_new(2011);
507 assert_equal(asset_pool.iter(), iter::once(&asset_pool.active[0]));
508 }
509
510 #[rstest]
511 fn test_asset_pool_commission_new3(mut asset_pool: AssetPool) {
512 asset_pool.commission_new(2000);
514 assert!(asset_pool.iter().next().is_none()); }
516
517 #[rstest]
518 fn test_asset_pool_decommission_old(mut asset_pool: AssetPool) {
519 asset_pool.commission_new(2020);
520 assert_eq!(asset_pool.active.len(), 2);
521 asset_pool.decommission_old(2020); assert_eq!(asset_pool.active.len(), 1);
523 assert_eq!(asset_pool.active[0].commission_year, 2020);
524 asset_pool.decommission_old(2022); assert_eq!(asset_pool.active.len(), 1);
526 assert_eq!(asset_pool.active[0].commission_year, 2020);
527 asset_pool.decommission_old(2025); assert!(asset_pool.active.is_empty());
529 }
530
531 #[rstest]
532 fn test_asset_pool_get(mut asset_pool: AssetPool) {
533 asset_pool.commission_new(2020);
534 assert_eq!(asset_pool.get(AssetID(0)), Some(&asset_pool.active[0]));
535 assert_eq!(asset_pool.get(AssetID(1)), Some(&asset_pool.active[1]));
536 }
537
538 #[rstest]
539 fn test_asset_pool_replace_active_pool_existing(mut asset_pool: AssetPool) {
540 asset_pool.commission_new(2020);
541 assert_eq!(asset_pool.active.len(), 2);
542 asset_pool.replace_active_pool(iter::once(asset_pool.active[1].clone().into()));
543 assert_eq!(asset_pool.active.len(), 1);
544 assert_eq!(asset_pool.active[0].id, Some(AssetID(1)));
545 }
546
547 #[rstest]
548 fn test_asset_pool_replace_active_pool_new_asset(mut asset_pool: AssetPool, process: Process) {
549 let asset = Asset::new(
550 "some_other_agent".into(),
551 process.into(),
552 "GBR".into(),
553 Capacity(2.0),
554 2010,
555 )
556 .unwrap();
557
558 asset_pool.commission_new(2020);
559 assert_eq!(asset_pool.active.len(), 2);
560 asset_pool.replace_active_pool(iter::once(asset.into()));
561 assert_eq!(asset_pool.active.len(), 1);
562 assert_eq!(asset_pool.active[0].id, Some(AssetID(2)));
563 assert_eq!(asset_pool.active[0].agent_id, "some_other_agent".into());
564 }
565
566 #[rstest]
567 fn test_asset_pool_replace_active_pool_out_of_order(
568 mut asset_pool: AssetPool,
569 process: Process,
570 ) {
571 let new_asset = Asset::new(
572 "some_other_agent".into(),
573 process.into(),
574 "GBR".into(),
575 Capacity(2.0),
576 2010,
577 )
578 .unwrap();
579
580 asset_pool.commission_new(2020);
581 assert_eq!(asset_pool.active.len(), 2);
582 let mut new_pool: Vec<Rc<Asset>> = asset_pool
583 .iter()
584 .map(|asset| asset.clone().into())
585 .collect();
586 new_pool.push(new_asset.into());
587 new_pool.reverse();
588
589 asset_pool.replace_active_pool(new_pool);
590 assert_equal(asset_pool.iter().map(|asset| asset.id.unwrap().0), 0..3);
591 assert_eq!(asset_pool.active[2].id, Some(AssetID(2)));
592 assert_eq!(asset_pool.active[2].agent_id, "some_other_agent".into());
593 }
594}