@@ -838,32 +838,40 @@ impl Asset {
838838
839839 /// Divides an asset if it is divisible and returns a vector of children
840840 ///
841- /// The children assets are identical to the parent (including state) but with a capacity defined
842- /// by the `unit_size`. Only Future or Selected assets can be divided.
841+ /// The child assets are identical to the parent (including state) but with a capacity
842+ /// defined by the `unit_size`. From a parent asset of capacity `C` and unit size `U`,
843+ /// `n = ceil(C / U)` child assets are created, each with capacity `U`. In other words, the
844+ /// total combined capacity of the children may be larger than that of the parent,
845+ /// if `C` is not an exact multiple of `U`.
846+ ///
847+ /// Only `Future` and `Selected` assets can be divided.
843848 pub fn divide_asset ( & self ) -> Vec < AssetRef > {
844849 assert ! (
845850 matches!(
846851 self . state,
847852 AssetState :: Future { .. } | AssetState :: Selected { .. }
848853 ) ,
849- "Assets with state {0 } cannot be divided. Only Future or Selected assets can be divided" ,
854+ "Assets with state {} cannot be divided. Only Future or Selected assets can be divided" ,
850855 self . state
851856 ) ;
852857
853- // Divide the asset into children until all capacity is allocated
854- let mut capacity = self . capacity ;
855858 let unit_size = self . process . unit_size . expect (
856859 "Only assets corresponding to processes with a unit size defined can be divided" ,
857860 ) ;
858- let mut children = Vec :: new ( ) ;
859- while capacity > Capacity ( 0.0 ) {
860- let mut child = self . clone ( ) ;
861- child. capacity = unit_size. min ( capacity) ;
862- capacity -= child. capacity ;
863- children. push ( child. into ( ) ) ;
864- }
865861
866- children
862+ // Calculate the number of units corresponding to the asset's capacity
863+ // Safe because capacity and unit_size are both positive finite numbers, so their ratio
864+ // must also be positive and finite.
865+ #[ allow( clippy:: cast_possible_truncation, clippy:: cast_sign_loss) ]
866+ let n_units = ( self . capacity / unit_size) . value ( ) . ceil ( ) as usize ;
867+
868+ // Divide the asset into `n_units` children of size `unit_size`
869+ let child_asset = Self {
870+ capacity : unit_size,
871+ ..self . clone ( )
872+ } ;
873+ let child_asset = AssetRef :: from ( Rc :: new ( child_asset) ) ;
874+ std:: iter:: repeat_n ( child_asset, n_units) . collect ( )
867875 }
868876}
869877
@@ -1072,8 +1080,7 @@ impl AssetPool {
10721080
10731081 // If it is divisible, we divide and commission all the children
10741082 if asset. is_divisible ( ) {
1075- let children = asset. divide_asset ( ) ;
1076- for mut child in children {
1083+ for mut child in asset. divide_asset ( ) {
10771084 child. make_mut ( ) . commission (
10781085 AssetID ( self . next_id ) ,
10791086 Some ( AssetGroupID ( self . next_group_id ) ) ,
@@ -1490,31 +1497,49 @@ mod tests {
14901497 }
14911498
14921499 #[ rstest]
1493- fn divide_asset_works ( asset_divisible : Asset ) {
1494- assert ! (
1495- asset_divisible. is_divisible( ) ,
1496- "Divisbile asset cannot be divided!"
1497- ) ;
1500+ #[ case:: exact_multiple( Capacity ( 12.0 ) , Capacity ( 4.0 ) , 3 ) ] // 12 / 4 = 3
1501+ #[ case:: rounded_up( Capacity ( 11.0 ) , Capacity ( 4.0 ) , 3 ) ] // 11 / 4 = 2.75 -> 3
1502+ #[ case:: unit_size_equals_capacity( Capacity ( 4.0 ) , Capacity ( 4.0 ) , 1 ) ] // 4 / 4 = 1
1503+ #[ case:: unit_size_greater_than_capacity( Capacity ( 3.0 ) , Capacity ( 4.0 ) , 1 ) ] // 3 / 4 = 0.75 -> 1
1504+ fn divide_asset (
1505+ mut process : Process ,
1506+ #[ case] capacity : Capacity ,
1507+ #[ case] unit_size : Capacity ,
1508+ #[ case] n_expected_children : usize ,
1509+ ) {
1510+ process. unit_size = Some ( unit_size) ;
1511+ let asset = Asset :: new_future (
1512+ "agent1" . into ( ) ,
1513+ Rc :: new ( process) ,
1514+ "GBR" . into ( ) ,
1515+ capacity,
1516+ 2010 ,
1517+ )
1518+ . unwrap ( ) ;
14981519
1499- // Check number of children
1500- let children = asset_divisible . divide_asset ( ) ;
1501- let expected_children = expected_children_for_divisible ( & asset_divisible ) ;
1520+ assert ! ( asset . is_divisible ( ) , "Asset should be divisible!" ) ;
1521+
1522+ let children = asset . divide_asset ( ) ;
15021523 assert_eq ! (
15031524 children. len( ) ,
1504- expected_children ,
1525+ n_expected_children ,
15051526 "Unexpected number of children"
15061527 ) ;
15071528
1508- // Check capacity of the children
1509- let max_child_capacity = asset_divisible. process . unit_size . unwrap ( ) ;
1529+ // Check all children have capacity equal to unit_size
15101530 for child in children. clone ( ) {
1511- assert ! (
1512- child. capacity <= max_child_capacity ,
1513- "Child capacity is too large! "
1531+ assert_eq ! (
1532+ child. capacity, unit_size ,
1533+ "Child capacity should equal unit_size "
15141534 ) ;
15151535 }
1516- let children_capacity: Capacity = children. iter ( ) . map ( |a| a. capacity ) . sum ( ) ;
1517- assert_eq ! ( asset_divisible. capacity, children_capacity) ;
1536+
1537+ // Check total capacity is >= parent capacity
1538+ let total_child_capacity: Capacity = children. iter ( ) . map ( |child| child. capacity ) . sum ( ) ;
1539+ assert ! (
1540+ total_child_capacity >= asset. capacity,
1541+ "Total capacity should be >= parent capacity"
1542+ ) ;
15181543 }
15191544
15201545 #[ rstest]
@@ -1558,9 +1583,6 @@ mod tests {
15581583 assert ! ( !asset_pool. active. is_empty( ) ) ;
15591584 assert_eq ! ( asset_pool. active. len( ) , expected_children) ;
15601585 assert_eq ! ( asset_pool. next_group_id, 1 ) ;
1561-
1562- let children_capacity: Capacity = asset_pool. active . iter ( ) . map ( |a| a. capacity ) . sum ( ) ;
1563- assert_eq ! ( asset_divisible. capacity, children_capacity) ;
15641586 }
15651587
15661588 #[ rstest]
@@ -2024,8 +2046,8 @@ mod tests {
20242046 #[ test]
20252047 fn commission_year_before_time_horizon ( ) {
20262048 let processes_patch = FilePatch :: new ( "processes.csv" )
2027- . with_deletion ( "GASDRV,Dry gas extraction,all,GASPRD,2020,2040,1.0" )
2028- . with_addition ( "GASDRV,Dry gas extraction,all,GASPRD,1980,2040,1.0" ) ;
2049+ . with_deletion ( "GASDRV,Dry gas extraction,all,GASPRD,2020,2040,1.0, " )
2050+ . with_addition ( "GASDRV,Dry gas extraction,all,GASPRD,1980,2040,1.0, " ) ;
20292051
20302052 // Check we can run model with asset commissioned before time horizon (simple starts in
20312053 // 2020)
@@ -2049,8 +2071,8 @@ mod tests {
20492071 #[ test]
20502072 fn commission_year_after_time_horizon ( ) {
20512073 let processes_patch = FilePatch :: new ( "processes.csv" )
2052- . with_deletion ( "GASDRV,Dry gas extraction,all,GASPRD,2020,2040,1.0" )
2053- . with_addition ( "GASDRV,Dry gas extraction,all,GASPRD,2020,2050,1.0" ) ;
2074+ . with_deletion ( "GASDRV,Dry gas extraction,all,GASPRD,2020,2040,1.0, " )
2075+ . with_addition ( "GASDRV,Dry gas extraction,all,GASPRD,2020,2050,1.0, " ) ;
20542076
20552077 // Check we can run model with asset commissioned after time horizon (simple ends in 2040)
20562078 let patches = vec ! [
0 commit comments