@@ -6,24 +6,19 @@ use crate::region::RegionID;
66use crate :: time_slice:: TimeSliceID ;
77use anyhow:: { ensure, Context , Result } ;
88use indexmap:: IndexMap ;
9- use std:: collections :: HashSet ;
10- use std:: ops:: RangeInclusive ;
9+ use std:: hash :: { Hash , Hasher } ;
10+ use std:: ops:: { Deref , RangeInclusive } ;
1111use std:: rc:: Rc ;
1212
1313/// A unique identifier for an asset
1414#[ derive( Clone , Copy , Debug , Eq , Hash , Ord , PartialEq , PartialOrd ) ]
1515pub struct AssetID ( u32 ) ;
1616
17- impl AssetID {
18- /// Sentinel value assigned to [`Asset`]s when they are added to the pool
19- pub const INVALID : AssetID = AssetID ( u32:: MAX ) ;
20- }
21-
2217/// An asset controlled by an agent.
2318#[ derive( Clone , Debug , PartialEq ) ]
2419pub struct Asset {
2520 /// A unique identifier for the asset
26- pub id : AssetID ,
21+ id : Option < AssetID > ,
2722 /// A unique identifier for the agent
2823 pub agent_id : AgentID ,
2924 /// The [`Process`] that this asset corresponds to
@@ -41,8 +36,8 @@ pub struct Asset {
4136impl Asset {
4237 /// Create a new [`Asset`].
4338 ///
44- /// The `id` field is initially set to [`AssetID::INVALID`] , but is changed to a unique value
45- /// when the asset is stored in an [`AssetPool`].
39+ /// The `id` field is initially set to `None` , but is changed to a unique value when the asset
40+ /// is stored in an [`AssetPool`].
4641 pub fn new (
4742 agent_id : AgentID ,
4843 process : Rc < Process > ,
@@ -74,7 +69,7 @@ impl Asset {
7469 ) ;
7570
7671 Ok ( Self {
77- id : AssetID :: INVALID ,
72+ id : None ,
7873 agent_id,
7974 process,
8075 process_parameter,
@@ -139,10 +134,62 @@ impl Asset {
139134 }
140135}
141136
137+ /// A wrapper around [`Asset`] for storing references in maps.
138+ ///
139+ /// An [`AssetRef`] is guaranteed to have been commissioned at some point, though it may
140+ /// subsequently have been decommissioned.
141+ ///
142+ /// [`AssetRef`]s must be created from `Rc<Asset>`s. If the asset has not been commissioned, this
143+ /// will panic.
144+ #[ derive( Clone , Debug ) ]
145+ pub struct AssetRef ( Rc < Asset > ) ;
146+
147+ impl From < Rc < Asset > > for AssetRef {
148+ fn from ( value : Rc < Asset > ) -> Self {
149+ assert ! ( value. id. is_some( ) ) ;
150+ Self ( value)
151+ }
152+ }
153+
154+ impl From < Asset > for AssetRef {
155+ fn from ( value : Asset ) -> Self {
156+ Self :: from ( Rc :: new ( value) )
157+ }
158+ }
159+
160+ impl From < AssetRef > for Rc < Asset > {
161+ fn from ( value : AssetRef ) -> Self {
162+ value. 0
163+ }
164+ }
165+
166+ impl Deref for AssetRef {
167+ type Target = Asset ;
168+
169+ fn deref ( & self ) -> & Self :: Target {
170+ & self . 0
171+ }
172+ }
173+
174+ impl PartialEq for AssetRef {
175+ fn eq ( & self , other : & Self ) -> bool {
176+ self . 0 . id == other. 0 . id
177+ }
178+ }
179+
180+ impl Eq for AssetRef { }
181+
182+ impl Hash for AssetRef {
183+ /// Hash asset based purely on its ID
184+ fn hash < H : Hasher > ( & self , state : & mut H ) {
185+ self . 0 . id . unwrap ( ) . hash ( state) ;
186+ }
187+ }
188+
142189/// A pool of [`Asset`]s
143190pub struct AssetPool {
144191 /// The pool of active assets
145- active : Vec < Asset > ,
192+ active : Vec < AssetRef > ,
146193 /// Assets that have not yet been commissioned, sorted by commission year
147194 future : Vec < Asset > ,
148195 /// Next available asset ID number
@@ -162,24 +209,6 @@ impl AssetPool {
162209 }
163210 }
164211
165- /// Add an asset to the active pool (i.e. commission it immediately).
166- ///
167- /// The asset's commission year is ignored by this function.
168- ///
169- /// # Panics
170- ///
171- /// Panics if the asset has already been commissioned.
172- pub fn commission ( & mut self , mut asset : Asset ) {
173- assert ! (
174- asset. id == AssetID :: INVALID ,
175- "Asset has already been commissioned"
176- ) ;
177-
178- asset. id = AssetID ( self . next_id ) ;
179- self . next_id += 1 ;
180- self . active . push ( asset) ;
181- }
182-
183212 /// Commission new assets for the specified milestone year from the input data
184213 pub fn commission_new ( & mut self , year : u32 ) {
185214 // Count the number of assets to move
@@ -191,9 +220,9 @@ impl AssetPool {
191220
192221 // Move assets from future to active
193222 for mut asset in self . future . drain ( 0 ..count) {
194- asset. id = AssetID ( self . next_id ) ;
223+ asset. id = Some ( AssetID ( self . next_id ) ) ;
195224 self . next_id += 1 ;
196- self . active . push ( asset) ;
225+ self . active . push ( asset. into ( ) ) ;
197226 }
198227 }
199228
@@ -206,28 +235,28 @@ impl AssetPool {
206235 ///
207236 /// # Returns
208237 ///
209- /// Reference to an [`Asset `] if found, else `None`. The asset may not be found if it has
210- /// already been decommissioned.
211- pub fn get ( & self , id : AssetID ) -> Option < & Asset > {
238+ /// An [`AssetRef `] if found, else `None`. The asset may not be found if it has already been
239+ /// decommissioned.
240+ pub fn get ( & self , id : AssetID ) -> Option < & AssetRef > {
212241 // The assets in `active` are in order of ID
213242 let idx = self
214243 . active
215- . binary_search_by ( |asset| asset. id . cmp ( & id) )
244+ . binary_search_by ( |asset| asset. id . unwrap ( ) . cmp ( & id) )
216245 . ok ( ) ?;
217246
218247 Some ( & self . active [ idx] )
219248 }
220249
221250 /// Iterate over active assets
222- pub fn iter ( & self ) -> impl Iterator < Item = & Asset > {
251+ pub fn iter ( & self ) -> impl Iterator < Item = & AssetRef > {
223252 self . active . iter ( )
224253 }
225254
226255 /// Iterate over active assets for a particular region
227256 pub fn iter_for_region < ' a > (
228257 & ' a self ,
229258 region_id : & ' a RegionID ,
230- ) -> impl Iterator < Item = & ' a Asset > {
259+ ) -> impl Iterator < Item = & ' a AssetRef > {
231260 self . iter ( ) . filter ( |asset| asset. region_id == * region_id)
232261 }
233262
@@ -237,7 +266,7 @@ impl AssetPool {
237266 & ' a self ,
238267 region_id : & ' a RegionID ,
239268 commodity_id : & ' a CommodityID ,
240- ) -> impl Iterator < Item = & ' a Asset > {
269+ ) -> impl Iterator < Item = & ' a AssetRef > {
241270 self . iter_for_region ( region_id) . filter ( |asset| {
242271 asset. process . contains_commodity_flow (
243272 commodity_id,
@@ -247,20 +276,24 @@ impl AssetPool {
247276 } )
248277 }
249278
250- /// Retain all assets whose IDs are in `assets_to_keep`.
251- ///
252- /// Other assets will be decommissioned. Assets which have not yet been commissioned will not be
253- /// affected.
254- pub fn retain ( & mut self , assets_to_keep : & HashSet < AssetID > ) {
255- // Sanity check: all IDs should be valid. As this check is slow, only do it for debug
256- // builds.
257- debug_assert ! (
258- assets_to_keep. iter( ) . all( |id| self . get( * id) . is_some( ) ) ,
259- "One or more asset IDs were invalid"
260- ) ;
279+ /// Replace the active pool with new and/or already commissioned assets
280+ pub fn replace_active_pool < I > ( & mut self , assets : I )
281+ where
282+ I : IntoIterator < Item = Rc < Asset > > ,
283+ {
284+ let new_pool = assets. into_iter ( ) . map ( |mut asset| {
285+ if asset. id . is_none ( ) {
286+ // Asset is newly created from process so we need to assign an ID
287+ let asset = Rc :: make_mut ( & mut asset) ;
288+ asset. id = Some ( AssetID ( self . next_id ) ) ;
289+ self . next_id += 1 ;
290+ }
291+
292+ asset. into ( )
293+ } ) ;
261294
262- self . active
263- . retain ( |asset| assets_to_keep . contains ( & asset . id ) ) ;
295+ self . active . clear ( ) ;
296+ self . active . extend ( new_pool ) ;
264297 }
265298}
266299
@@ -273,6 +306,7 @@ mod tests {
273306 } ;
274307 use itertools:: { assert_equal, Itertools } ;
275308 use rstest:: { fixture, rstest} ;
309+ use std:: collections:: HashSet ;
276310 use std:: iter;
277311 use std:: ops:: RangeInclusive ;
278312
@@ -285,7 +319,7 @@ mod tests {
285319 let agent_id = AgentID ( "agent1" . into ( ) ) ;
286320 let region_id = RegionID ( "GBR" . into ( ) ) ;
287321 let asset = Asset :: new ( agent_id, process. into ( ) , region_id, capacity, 2015 ) . unwrap ( ) ;
288- assert ! ( asset. id == AssetID :: INVALID ) ;
322+ assert ! ( asset. id. is_none ( ) ) ;
289323 }
290324
291325 #[ rstest]
@@ -443,24 +477,15 @@ mod tests {
443477 assert ! ( asset_pool. iter( ) . next( ) . is_none( ) ) ; // no active assets
444478 }
445479
446- #[ rstest]
447- fn test_asset_pool_commission ( mut asset_pool : AssetPool , process : Process ) {
448- let mut asset =
449- Asset :: new ( "agent2" . into ( ) , process. into ( ) , "USA" . into ( ) , 100.0 , 2015 ) . unwrap ( ) ;
450- asset_pool. commission ( asset. clone ( ) ) ;
451- asset. id = AssetID ( 0 ) ;
452- assert_equal ( asset_pool. iter ( ) , iter:: once ( & asset) ) ;
453- }
454-
455480 #[ rstest]
456481 fn test_asset_pool_decommission_old ( mut asset_pool : AssetPool ) {
457482 asset_pool. commission_new ( 2020 ) ;
458- assert ! ( asset_pool. active. len( ) == 2 ) ;
483+ assert_eq ! ( asset_pool. active. len( ) , 2 ) ;
459484 asset_pool. decommission_old ( 2020 ) ; // should decommission first asset (lifetime == 5)
460- assert ! ( asset_pool. active. len( ) == 1 ) ;
485+ assert_eq ! ( asset_pool. active. len( ) , 1 ) ;
461486 assert_eq ! ( asset_pool. active[ 0 ] . commission_year, 2020 ) ;
462487 asset_pool. decommission_old ( 2022 ) ; // nothing to decommission
463- assert ! ( asset_pool. active. len( ) == 1 ) ;
488+ assert_eq ! ( asset_pool. active. len( ) , 1 ) ;
464489 assert_eq ! ( asset_pool. active[ 0 ] . commission_year, 2020 ) ;
465490 asset_pool. decommission_old ( 2025 ) ; // should decommission second asset
466491 assert ! ( asset_pool. active. is_empty( ) ) ;
@@ -474,26 +499,30 @@ mod tests {
474499 }
475500
476501 #[ rstest]
477- fn test_asset_pool_retain1 ( mut asset_pool : AssetPool ) {
478- // Even though we are retaining no assets, none have been commissioned so the asset pool
479- // should not be changed
480- asset_pool. retain ( & HashSet :: new ( ) ) ;
481- assert ! ( asset_pool. active. is_empty( ) ) ;
482-
483- // Decommission all active assets
484- asset_pool. commission_new ( 2010 ) ; // Commission first asset
502+ fn test_asset_pool_replace_active_pool_existing ( mut asset_pool : AssetPool ) {
503+ asset_pool. commission_new ( 2020 ) ;
504+ assert_eq ! ( asset_pool. active. len( ) , 2 ) ;
505+ asset_pool. replace_active_pool ( iter:: once ( asset_pool. active [ 1 ] . clone ( ) . into ( ) ) ) ;
485506 assert_eq ! ( asset_pool. active. len( ) , 1 ) ;
486- asset_pool. retain ( & HashSet :: new ( ) ) ;
487- assert ! ( asset_pool. active. is_empty( ) ) ;
507+ assert_eq ! ( asset_pool. active[ 0 ] . id, Some ( AssetID ( 1 ) ) ) ;
488508 }
489509
490510 #[ rstest]
491- fn test_asset_pool_retain2 ( mut asset_pool : AssetPool ) {
492- // Decommission single asset
493- asset_pool. commission_new ( 2020 ) ; // Commission all assets
511+ fn test_asset_pool_replace_active_pool_new_asset ( mut asset_pool : AssetPool , process : Process ) {
512+ let asset = Asset :: new (
513+ "some_other_agent" . into ( ) ,
514+ process. into ( ) ,
515+ "GBR" . into ( ) ,
516+ 2.0 ,
517+ 2010 ,
518+ )
519+ . unwrap ( ) ;
520+
521+ asset_pool. commission_new ( 2020 ) ;
494522 assert_eq ! ( asset_pool. active. len( ) , 2 ) ;
495- asset_pool. retain ( & iter:: once ( AssetID ( 1 ) ) . collect ( ) ) ;
523+ asset_pool. replace_active_pool ( iter:: once ( asset . into ( ) ) ) ;
496524 assert_eq ! ( asset_pool. active. len( ) , 1 ) ;
497- assert_eq ! ( asset_pool. active[ 0 ] . id, AssetID ( 1 ) ) ;
525+ assert_eq ! ( asset_pool. active[ 0 ] . id, Some ( AssetID ( 2 ) ) ) ;
526+ assert_eq ! ( asset_pool. active[ 0 ] . agent_id, "some_other_agent" . into( ) ) ;
498527 }
499528}
0 commit comments