diff --git a/docs/release_notes/upcoming.md b/docs/release_notes/upcoming.md index 263e1dc1f..f3c7abb73 100644 --- a/docs/release_notes/upcoming.md +++ b/docs/release_notes/upcoming.md @@ -39,6 +39,10 @@ ready to be released, carry out the following steps: ## Breaking changes +- Process parameters, flows and availabilities can no longer vary by commission year. To represent + this in MUSE, users should instead create distinct processes for different "vintages" of a + technology (e.g. `WNDFRM_2030`, `WNDFRM_2040`), using the `start_year` and `end_year` fields to + define the range of years over which each vintage can be commissioned. - Changed the default `pricing_strategy` for SED/SVD commodities from "shadow" to "full_average" ([#1281]) - The individual units of divisible assets are no longer treated as separate assets and are instead grouped under a single larger asset, affecting a number of output files. The `group_id` column has diff --git a/examples/circularity/process_availabilities.csv b/examples/circularity/process_availabilities.csv index 652ec21af..95047950b 100644 --- a/examples/circularity/process_availabilities.csv +++ b/examples/circularity/process_availabilities.csv @@ -1,24 +1,24 @@ -process_id,regions,commission_years,time_slice,limits -GASDRV,all,all,annual,..0.9 -OAGRSV,all,all,annual,..0.9 -GASPRC,all,all,annual,..0.9 -OILREF,all,all,annual,..0.9 -OILRF2,all,all,annual,..0.9 -GASCGT,all,all,annual,..0.9 -WNDFRM,all,all,winter.night,..0.486418015 -WNDFRM,all,all,winter.day,..0.543166784 -WNDFRM,all,all,winter.peak,..0.504433498 -WNDFRM,all,all,winter.evening,..0.493173821 -WNDFRM,all,all,peak.night,..0.312697296 -WNDFRM,all,all,peak.day,..0.489120338 -WNDFRM,all,all,peak.peak,..0.454890922 -WNDFRM,all,all,peak.evening,..0.331034483 -WNDFRM,all,all,summer.night,..0.17951141 -WNDFRM,all,all,summer.day,..0.349950739 -WNDFRM,all,all,summer.peak,..0.342294159 -WNDFRM,all,all,summer.evening,..0.202674173 -WNDFRM,all,all,autumn.night,..0.3513019 -WNDFRM,all,all,autumn.day,..0.460745954 -WNDFRM,all,all,autumn.peak,..0.396340605 -WNDFRM,all,all,autumn.evening,..0.364813512 -BIOPLL,all,all,annual,..0.95 +process_id,regions,time_slice,limits +GASDRV,all,annual,..0.9 +OAGRSV,all,annual,..0.9 +GASPRC,all,annual,..0.9 +OILREF,all,annual,..0.9 +OILRF2,all,annual,..0.9 +GASCGT,all,annual,..0.9 +WNDFRM,all,winter.night,..0.486418015 +WNDFRM,all,winter.day,..0.543166784 +WNDFRM,all,winter.peak,..0.504433498 +WNDFRM,all,winter.evening,..0.493173821 +WNDFRM,all,peak.night,..0.312697296 +WNDFRM,all,peak.day,..0.489120338 +WNDFRM,all,peak.peak,..0.454890922 +WNDFRM,all,peak.evening,..0.331034483 +WNDFRM,all,summer.night,..0.17951141 +WNDFRM,all,summer.day,..0.349950739 +WNDFRM,all,summer.peak,..0.342294159 +WNDFRM,all,summer.evening,..0.202674173 +WNDFRM,all,autumn.night,..0.3513019 +WNDFRM,all,autumn.day,..0.460745954 +WNDFRM,all,autumn.peak,..0.396340605 +WNDFRM,all,autumn.evening,..0.364813512 +BIOPLL,all,annual,..0.95 diff --git a/examples/circularity/process_flows.csv b/examples/circularity/process_flows.csv index 51b586741..fe104292f 100644 --- a/examples/circularity/process_flows.csv +++ b/examples/circularity/process_flows.csv @@ -1,45 +1,45 @@ -process_id,commodity_id,regions,commission_years,coeff,type,cost -GASDRV,GASPRD,all,all,1,fixed, -OAGRSV,OILCRD,all,all,1,fixed, -OAGRSV,GASPRD,all,all,0.1,fixed, -GASPRC,GASPRD,all,all,-1.05,fixed, -GASPRC,GASNAT,all,all,1,fixed, -OILREF,OILCRD,all,all,-1.3,fixed, -OILREF,GASOLI,all,all,0.5,fixed, -OILREF,DIESEL,all,all,0.5,fixed, -OILRF2,OILCRD,all,all,-1.31,fixed, -OILRF2,GASOLI,all,all,1,fixed, -WNDFRM,ELCTRI,all,all,1,fixed, -GASCGT,GASNAT,all,all,-1.5,fixed, -GASCGT,ELCTRI,all,all,1,fixed, -H2YGEN,H2YPRD,all,all,-1.5,fixed, -H2YGEN,ELCTRI,all,all,1,fixed, -H2YPRO,ELCTRI,all,all,-1.333,fixed, -H2YPRO,H2YPRD,all,all,1,fixed, -TPETCR,GASOLI,all,all,-2.702702703,fixed, -TPETCR,TPASKM,all,all,1,fixed, -TDIECR,DIESEL,all,all,-2.222222222,fixed, -TDIECR,TPASKM,all,all,1,fixed, -TELCCR,ELCTRI,all,all,-1.754385965,fixed, -TELCCR,TPASKM,all,all,1,fixed, -THYBCR,ELCTRI,all,all,-0.854385965,fixed, -THYBCR,GASOLI,all,all,-1.0,fixed, -THYBCR,TPASKM,all,all,1,fixed, -RGASBR,GASNAT,all,all,-1.15,fixed, -RGASBR,RSHEAT,all,all,1,fixed, -RELCHP,ELCTRI,all,all,-0.33,fixed, -RELCHP,RSHEAT,all,all,1,fixed, -GASDRV,CO2EMT,all,all,5.113,fixed, -OAGRSV,CO2EMT,all,all,7.333,fixed, -GASPRC,CO2EMT,all,all,2.5565,fixed, -OILREF,CO2EMT,all,all,21.999,fixed, -OILRF2,CO2EMT,all,all,22,fixed, -GASCGT,CO2EMT,all,all,76.695,fixed, -TPETCR,CO2EMT,all,all,179.7297297,fixed, -TDIECR,CO2EMT,all,all,154.0222222,fixed, -RGASBR,CO2EMT,all,all,58.7995,fixed, -BIOPRO,BIOPRD,all,all,1.0,fixed, -BIOPLL,BIOPRD,all,all,-1.05,fixed, -BIOPLL,BIOPEL,all,all,1.0,fixed, -RBIOBL,BIOPEL,all,all,-1.2,fixed, -RBIOBL,RSHEAT,all,all,1,fixed, +process_id,commodity_id,regions,coeff,type,cost +GASDRV,GASPRD,all,1,fixed, +OAGRSV,OILCRD,all,1,fixed, +OAGRSV,GASPRD,all,0.1,fixed, +GASPRC,GASPRD,all,-1.05,fixed, +GASPRC,GASNAT,all,1,fixed, +OILREF,OILCRD,all,-1.3,fixed, +OILREF,GASOLI,all,0.5,fixed, +OILREF,DIESEL,all,0.5,fixed, +OILRF2,OILCRD,all,-1.31,fixed, +OILRF2,GASOLI,all,1,fixed, +WNDFRM,ELCTRI,all,1,fixed, +GASCGT,GASNAT,all,-1.5,fixed, +GASCGT,ELCTRI,all,1,fixed, +H2YGEN,H2YPRD,all,-1.5,fixed, +H2YGEN,ELCTRI,all,1,fixed, +H2YPRO,ELCTRI,all,-1.333,fixed, +H2YPRO,H2YPRD,all,1,fixed, +TPETCR,GASOLI,all,-2.702702703,fixed, +TPETCR,TPASKM,all,1,fixed, +TDIECR,DIESEL,all,-2.222222222,fixed, +TDIECR,TPASKM,all,1,fixed, +TELCCR,ELCTRI,all,-1.754385965,fixed, +TELCCR,TPASKM,all,1,fixed, +THYBCR,ELCTRI,all,-0.854385965,fixed, +THYBCR,GASOLI,all,-1.0,fixed, +THYBCR,TPASKM,all,1,fixed, +RGASBR,GASNAT,all,-1.15,fixed, +RGASBR,RSHEAT,all,1,fixed, +RELCHP,ELCTRI,all,-0.33,fixed, +RELCHP,RSHEAT,all,1,fixed, +GASDRV,CO2EMT,all,5.113,fixed, +OAGRSV,CO2EMT,all,7.333,fixed, +GASPRC,CO2EMT,all,2.5565,fixed, +OILREF,CO2EMT,all,21.999,fixed, +OILRF2,CO2EMT,all,22,fixed, +GASCGT,CO2EMT,all,76.695,fixed, +TPETCR,CO2EMT,all,179.7297297,fixed, +TDIECR,CO2EMT,all,154.0222222,fixed, +RGASBR,CO2EMT,all,58.7995,fixed, +BIOPRO,BIOPRD,all,1.0,fixed, +BIOPLL,BIOPRD,all,-1.05,fixed, +BIOPLL,BIOPEL,all,1.0,fixed, +RBIOBL,BIOPEL,all,-1.2,fixed, +RBIOBL,RSHEAT,all,1,fixed, diff --git a/examples/circularity/process_parameters.csv b/examples/circularity/process_parameters.csv index 10f1f88e5..fe828e203 100644 --- a/examples/circularity/process_parameters.csv +++ b/examples/circularity/process_parameters.csv @@ -1,19 +1,19 @@ -process_id,regions,commission_years,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate -GASDRV,GBR,all,10,0.3,2,25,0.1 -OAGRSV,GBR,all,15,0.45,3,25,0.1 -GASPRC,GBR,all,7,0.21,0.5,25,0.1 -OILREF,GBR,all,9,0.27,0.66,25,0.1 -OILRF2,GBR,all,9.1,0.3,0.68,25,0.1 -WNDFRM,GBR,all,1000,30,0.4,25,0.1 -GASCGT,GBR,all,700,21,0.55,30,0.1 -H2YGEN,GBR,all,1200,1,0.6,30,0.1 -H2YPRO,GBR,all,15,0.9,0.6,20,0.1 -TPETCR,GBR,all,2,0.06,0.2,10,0.1 -TDIECR,GBR,all,2.1,0.063,0.18,10,0.1 -TELCCR,GBR,all,4,0.18,0.15,10,0.1 -THYBCR,GBR,all,1,0.17,0.17,10,0.1 -RGASBR,GBR,all,55.56,1.6668,0.16,15,0.1 -RELCHP,GBR,all,138.9,4.167,0.17,15,0.1 -BIOPRO,all,all,1,0.2,0.25,20,0.09 -BIOPLL,all,all,2,0.22,0.26,20,0.1 -RBIOBL,all,all,60,1.05,0.2,20,0.1 +process_id,regions,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate +GASDRV,GBR,10,0.3,2,25,0.1 +OAGRSV,GBR,15,0.45,3,25,0.1 +GASPRC,GBR,7,0.21,0.5,25,0.1 +OILREF,GBR,9,0.27,0.66,25,0.1 +OILRF2,GBR,9.1,0.3,0.68,25,0.1 +WNDFRM,GBR,1000,30,0.4,25,0.1 +GASCGT,GBR,700,21,0.55,30,0.1 +H2YGEN,GBR,1200,1,0.6,30,0.1 +H2YPRO,GBR,15,0.9,0.6,20,0.1 +TPETCR,GBR,2,0.06,0.2,10,0.1 +TDIECR,GBR,2.1,0.063,0.18,10,0.1 +TELCCR,GBR,4,0.18,0.15,10,0.1 +THYBCR,GBR,1,0.17,0.17,10,0.1 +RGASBR,GBR,55.56,1.6668,0.16,15,0.1 +RELCHP,GBR,138.9,4.167,0.17,15,0.1 +BIOPRO,all,1,0.2,0.25,20,0.09 +BIOPLL,all,2,0.22,0.26,20,0.1 +RBIOBL,all,60,1.05,0.2,20,0.1 diff --git a/examples/missing_commodity/process_availabilities.csv b/examples/missing_commodity/process_availabilities.csv index 7b80997c5..95e92ca2e 100644 --- a/examples/missing_commodity/process_availabilities.csv +++ b/examples/missing_commodity/process_availabilities.csv @@ -1,21 +1,21 @@ -process_id,regions,commission_years,time_slice,limits -GASDRV,all,all,annual,..0.9 -GASPRC,all,all,annual,..0.9 -BIOPLL,all,all,annual,..0.95 -GASCGT,all,all,annual,..0.9 -WNDFRM,all,all,winter.night,..0.486418015 -WNDFRM,all,all,winter.day,..0.543166784 -WNDFRM,all,all,winter.peak,..0.504433498 -WNDFRM,all,all,winter.evening,..0.493173821 -WNDFRM,all,all,peak.night,..0.312697296 -WNDFRM,all,all,peak.day,..0.489120338 -WNDFRM,all,all,peak.peak,..0.454890922 -WNDFRM,all,all,peak.evening,..0.331034483 -WNDFRM,all,all,summer.night,..0.17951141 -WNDFRM,all,all,summer.day,..0.349950739 -WNDFRM,all,all,summer.peak,..0.342294159 -WNDFRM,all,all,summer.evening,..0.202674173 -WNDFRM,all,all,autumn.night,..0.3513019 -WNDFRM,all,all,autumn.day,..0.460745954 -WNDFRM,all,all,autumn.peak,..0.396340605 -WNDFRM,all,all,autumn.evening,..0.364813512 +process_id,regions,time_slice,limits +GASDRV,all,annual,..0.9 +GASPRC,all,annual,..0.9 +BIOPLL,all,annual,..0.95 +GASCGT,all,annual,..0.9 +WNDFRM,all,winter.night,..0.486418015 +WNDFRM,all,winter.day,..0.543166784 +WNDFRM,all,winter.peak,..0.504433498 +WNDFRM,all,winter.evening,..0.493173821 +WNDFRM,all,peak.night,..0.312697296 +WNDFRM,all,peak.day,..0.489120338 +WNDFRM,all,peak.peak,..0.454890922 +WNDFRM,all,peak.evening,..0.331034483 +WNDFRM,all,summer.night,..0.17951141 +WNDFRM,all,summer.day,..0.349950739 +WNDFRM,all,summer.peak,..0.342294159 +WNDFRM,all,summer.evening,..0.202674173 +WNDFRM,all,autumn.night,..0.3513019 +WNDFRM,all,autumn.day,..0.460745954 +WNDFRM,all,autumn.peak,..0.396340605 +WNDFRM,all,autumn.evening,..0.364813512 diff --git a/examples/missing_commodity/process_flows.csv b/examples/missing_commodity/process_flows.csv index 6fda05c2b..08c33ace5 100644 --- a/examples/missing_commodity/process_flows.csv +++ b/examples/missing_commodity/process_flows.csv @@ -1,20 +1,20 @@ -process_id,commodity_id,regions,commission_years,coeff,type,cost -GASDRV,GASPRD,all,all,1.0,fixed, -GASPRC,GASPRD,all,all,-1.05,fixed, -GASPRC,GASNAT,all,all,1.0,fixed, -BIOPRO,BIOPRD,all,all,1.0,fixed, -BIOPLL,BIOPRD,all,all,-1.05,fixed, -BIOPLL,BIOPEL,all,all,1.0,fixed, -WNDFRM,ELCTRI,all,all,1.0,fixed, -GASCGT,GASNAT,all,all,-1.5,fixed, -GASCGT,ELCTRI,all,all,1.0,fixed, -RGASBR,GASNAT,all,all,-1.15,fixed, -RGASBR,RSHEAT,all,all,1.0,fixed, -RELCHP,ELCTRI,all,all,-0.33,fixed, -RELCHP,RSHEAT,all,all,1.0,fixed, -GASDRV,CO2EMT,all,all,5.113,fixed, -GASPRC,CO2EMT,all,all,2.5565,fixed, -GASCGT,CO2EMT,all,all,76.695,fixed, -RGASBR,CO2EMT,all,all,58.7995,fixed, -RBIOBL,BIOPEL,all,all,-1.2,fixed, -RBIOBL,RSHEAT,all,all,1,fixed, +process_id,commodity_id,regions,coeff,type,cost +GASDRV,GASPRD,all,1.0,fixed, +GASPRC,GASPRD,all,-1.05,fixed, +GASPRC,GASNAT,all,1.0,fixed, +BIOPRO,BIOPRD,all,1.0,fixed, +BIOPLL,BIOPRD,all,-1.05,fixed, +BIOPLL,BIOPEL,all,1.0,fixed, +WNDFRM,ELCTRI,all,1.0,fixed, +GASCGT,GASNAT,all,-1.5,fixed, +GASCGT,ELCTRI,all,1.0,fixed, +RGASBR,GASNAT,all,-1.15,fixed, +RGASBR,RSHEAT,all,1.0,fixed, +RELCHP,ELCTRI,all,-0.33,fixed, +RELCHP,RSHEAT,all,1.0,fixed, +GASDRV,CO2EMT,all,5.113,fixed, +GASPRC,CO2EMT,all,2.5565,fixed, +GASCGT,CO2EMT,all,76.695,fixed, +RGASBR,CO2EMT,all,58.7995,fixed, +RBIOBL,BIOPEL,all,-1.2,fixed, +RBIOBL,RSHEAT,all,1,fixed, diff --git a/examples/missing_commodity/process_parameters.csv b/examples/missing_commodity/process_parameters.csv index 0504d0d44..4e645945f 100644 --- a/examples/missing_commodity/process_parameters.csv +++ b/examples/missing_commodity/process_parameters.csv @@ -1,10 +1,10 @@ -process_id,regions,commission_years,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate -GASDRV,all,all,10.0,0.3,2.0,25,0.1 -GASPRC,all,all,7.0,0.21,0.5,25,0.1 -BIOPRO,all,all,1.0,0.2,0.25,20,0.09 -BIOPLL,all,all,2.0,0.22,0.26,20,0.1 -WNDFRM,all,all,1000.0,30.0,0.4,25,0.1 -GASCGT,all,all,700.0,21.0,0.55,30,0.1 -RGASBR,all,all,55.56,1.6668,0.16,15,0.1 -RELCHP,all,all,138.9,4.167,0.17,15,0.1 -RBIOBL,all,all,60,1.05,0.2,20,0.1 +process_id,regions,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate +GASDRV,all,10.0,0.3,2.0,25,0.1 +GASPRC,all,7.0,0.21,0.5,25,0.1 +BIOPRO,all,1.0,0.2,0.25,20,0.09 +BIOPLL,all,2.0,0.22,0.26,20,0.1 +WNDFRM,all,1000.0,30.0,0.4,25,0.1 +GASCGT,all,700.0,21.0,0.55,30,0.1 +RGASBR,all,55.56,1.6668,0.16,15,0.1 +RELCHP,all,138.9,4.167,0.17,15,0.1 +RBIOBL,all,60,1.05,0.2,20,0.1 diff --git a/examples/muse1_default/process_availabilities.csv b/examples/muse1_default/process_availabilities.csv index e6d456eca..743b0b5d7 100644 --- a/examples/muse1_default/process_availabilities.csv +++ b/examples/muse1_default/process_availabilities.csv @@ -1,4 +1,4 @@ -process_id,regions,commission_years,time_slice,limits -gassupply1,R1,all,annual,..0.9 -gasCCGT,R1,all,annual,..0.9 -windturbine,R1,all,annual,..0.4 +process_id,regions,time_slice,limits +gassupply1,R1,annual,..0.9 +gasCCGT,R1,annual,..0.9 +windturbine,R1,annual,..0.4 diff --git a/examples/muse1_default/process_flows.csv b/examples/muse1_default/process_flows.csv index 7120b2b8c..a61186e1f 100644 --- a/examples/muse1_default/process_flows.csv +++ b/examples/muse1_default/process_flows.csv @@ -1,12 +1,12 @@ -process_id,commodity_id,regions,commission_years,coeff,type,cost -gassupply1,gas,R1,all,1,fixed, -gasCCGT,gas,R1,all,-1.67,fixed, -gasCCGT,electricity,R1,all,1,fixed, -gasCCGT,CO2f,R1,all,91.67,fixed, -windturbine,wind,R1,all,-1,fixed, -windturbine,electricity,R1,all,1,fixed, -gasboiler,gas,R1,all,-1.16,fixed, -gasboiler,heat,R1,all,1,fixed, -gasboiler,CO2f,R1,all,64.71,fixed, -heatpump,electricity,R1,all,-0.4,fixed, -heatpump,heat,R1,all,1,fixed, +process_id,commodity_id,regions,coeff,type,cost +gassupply1,gas,R1,1,fixed, +gasCCGT,gas,R1,-1.67,fixed, +gasCCGT,electricity,R1,1,fixed, +gasCCGT,CO2f,R1,91.67,fixed, +windturbine,wind,R1,-1,fixed, +windturbine,electricity,R1,1,fixed, +gasboiler,gas,R1,-1.16,fixed, +gasboiler,heat,R1,1,fixed, +gasboiler,CO2f,R1,64.71,fixed, +heatpump,electricity,R1,-0.4,fixed, +heatpump,heat,R1,1,fixed, diff --git a/examples/muse1_default/process_parameters.csv b/examples/muse1_default/process_parameters.csv index cd1bde924..3448aebc2 100644 --- a/examples/muse1_default/process_parameters.csv +++ b/examples/muse1_default/process_parameters.csv @@ -1,6 +1,6 @@ -process_id,regions,commission_years,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate -gassupply1,R1,all,0,0,2.55,60,0.1 -gasCCGT,R1,all,23.78234399,0,0,60,0.1 -windturbine,R1,all,36.30771182,0,0,60,0.1 -gasboiler,R1,all,3.8,0,0,60,0.1 -heatpump,R1,all,8.866667,0,0,60,0.1 +process_id,regions,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate +gassupply1,R1,0,0,2.55,60,0.1 +gasCCGT,R1,23.78234399,0,0,60,0.1 +windturbine,R1,36.30771182,0,0,60,0.1 +gasboiler,R1,3.8,0,0,60,0.1 +heatpump,R1,8.866667,0,0,60,0.1 diff --git a/examples/simple/process_availabilities.csv b/examples/simple/process_availabilities.csv index fba0e914b..83f881d9d 100644 --- a/examples/simple/process_availabilities.csv +++ b/examples/simple/process_availabilities.csv @@ -1,36 +1,36 @@ -process_id,regions,commission_years,time_slice,limits -GASDRV,all,all,annual,..0.9 -GASPRC,all,all,annual,..0.9 -GASCGT,all,all,annual,..0.9 -WNDFRM,all,all,winter.night,..0.486418015 -WNDFRM,all,all,winter.day,..0.543166784 -WNDFRM,all,all,winter.peak,..0.504433498 -WNDFRM,all,all,winter.evening,..0.493173821 -WNDFRM,all,all,peak.night,..0.312697296 -WNDFRM,all,all,peak.day,..0.489120338 -WNDFRM,all,all,peak.peak,..0.454890922 -WNDFRM,all,all,peak.evening,..0.331034483 -WNDFRM,all,all,summer.night,..0.17951141 -WNDFRM,all,all,summer.day,..0.349950739 -WNDFRM,all,all,summer.peak,..0.342294159 -WNDFRM,all,all,summer.evening,..0.202674173 -WNDFRM,all,all,autumn.night,..0.3513019 -WNDFRM,all,all,autumn.day,..0.460745954 -WNDFRM,all,all,autumn.peak,..0.396340605 -WNDFRM,all,all,autumn.evening,..0.364813512 -SLRFRM,all,all,winter.night,..0.0 -SLRFRM,all,all,winter.day,..0.071392212 -SLRFRM,all,all,winter.peak,..0.0 -SLRFRM,all,all,winter.evening,..0.0 -SLRFRM,all,all,peak.night,..0.000354005 -SLRFRM,all,all,peak.day,..0.207715640 -SLRFRM,all,all,peak.peak,..0.001910149 -SLRFRM,all,all,peak.evening,..0.0 -SLRFRM,all,all,summer.night,..0.006640965 -SLRFRM,all,all,summer.day,..0.310185297 -SLRFRM,all,all,summer.peak,..0.019099418 -SLRFRM,all,all,summer.evening,..0.0 -SLRFRM,all,all,autumn.night,..0.000635743 -SLRFRM,all,all,autumn.day,..0.237720679 -SLRFRM,all,all,autumn.peak,..0.003512115 -SLRFRM,all,all,autumn.evening,..0.0 +process_id,regions,time_slice,limits +GASDRV,all,annual,..0.9 +GASPRC,all,annual,..0.9 +GASCGT,all,annual,..0.9 +WNDFRM,all,winter.night,..0.486418015 +WNDFRM,all,winter.day,..0.543166784 +WNDFRM,all,winter.peak,..0.504433498 +WNDFRM,all,winter.evening,..0.493173821 +WNDFRM,all,peak.night,..0.312697296 +WNDFRM,all,peak.day,..0.489120338 +WNDFRM,all,peak.peak,..0.454890922 +WNDFRM,all,peak.evening,..0.331034483 +WNDFRM,all,summer.night,..0.17951141 +WNDFRM,all,summer.day,..0.349950739 +WNDFRM,all,summer.peak,..0.342294159 +WNDFRM,all,summer.evening,..0.202674173 +WNDFRM,all,autumn.night,..0.3513019 +WNDFRM,all,autumn.day,..0.460745954 +WNDFRM,all,autumn.peak,..0.396340605 +WNDFRM,all,autumn.evening,..0.364813512 +SLRFRM,all,winter.night,..0.0 +SLRFRM,all,winter.day,..0.071392212 +SLRFRM,all,winter.peak,..0.0 +SLRFRM,all,winter.evening,..0.0 +SLRFRM,all,peak.night,..0.000354005 +SLRFRM,all,peak.day,..0.207715640 +SLRFRM,all,peak.peak,..0.001910149 +SLRFRM,all,peak.evening,..0.0 +SLRFRM,all,summer.night,..0.006640965 +SLRFRM,all,summer.day,..0.310185297 +SLRFRM,all,summer.peak,..0.019099418 +SLRFRM,all,summer.evening,..0.0 +SLRFRM,all,autumn.night,..0.000635743 +SLRFRM,all,autumn.day,..0.237720679 +SLRFRM,all,autumn.peak,..0.003512115 +SLRFRM,all,autumn.evening,..0.0 diff --git a/examples/simple/process_flows.csv b/examples/simple/process_flows.csv index ca4aeaa96..bb7ee0931 100644 --- a/examples/simple/process_flows.csv +++ b/examples/simple/process_flows.csv @@ -1,16 +1,16 @@ -process_id,commodity_id,regions,commission_years,coeff,type,cost -GASDRV,GASPRD,all,all,1.0,fixed, -GASPRC,GASPRD,all,all,-1.05,fixed, -GASPRC,GASNAT,all,all,1.0,fixed, -WNDFRM,ELCTRI,all,all,1.0,fixed, -GASCGT,GASNAT,all,all,-1.5,fixed, -GASCGT,ELCTRI,all,all,1.0,fixed, -RGASBR,GASNAT,all,all,-1.15,fixed, -RGASBR,RSHEAT,all,all,1.0,fixed, -RELCHP,ELCTRI,all,all,-0.33,fixed, -RELCHP,RSHEAT,all,all,1.0,fixed, -GASDRV,CO2EMT,all,all,5.113,fixed, -GASPRC,CO2EMT,all,all,2.5565,fixed, -GASCGT,CO2EMT,all,all,76.695,fixed, -RGASBR,CO2EMT,all,all,58.7995,fixed, -SLRFRM,ELCTRI,all,all,1.0,fixed, +process_id,commodity_id,regions,coeff,type,cost +GASDRV,GASPRD,all,1.0,fixed, +GASPRC,GASPRD,all,-1.05,fixed, +GASPRC,GASNAT,all,1.0,fixed, +WNDFRM,ELCTRI,all,1.0,fixed, +GASCGT,GASNAT,all,-1.5,fixed, +GASCGT,ELCTRI,all,1.0,fixed, +RGASBR,GASNAT,all,-1.15,fixed, +RGASBR,RSHEAT,all,1.0,fixed, +RELCHP,ELCTRI,all,-0.33,fixed, +RELCHP,RSHEAT,all,1.0,fixed, +GASDRV,CO2EMT,all,5.113,fixed, +GASPRC,CO2EMT,all,2.5565,fixed, +GASCGT,CO2EMT,all,76.695,fixed, +RGASBR,CO2EMT,all,58.7995,fixed, +SLRFRM,ELCTRI,all,1.0,fixed, diff --git a/examples/simple/process_parameters.csv b/examples/simple/process_parameters.csv index 3dbeb8dc2..279aabfb5 100644 --- a/examples/simple/process_parameters.csv +++ b/examples/simple/process_parameters.csv @@ -1,8 +1,8 @@ -process_id,regions,commission_years,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate -GASDRV,all,all,10.0,0.3,2.0,25,0.1 -GASPRC,all,all,7.0,0.21,0.5,25,0.1 -WNDFRM,all,all,1000.0,30.0,0.4,25,0.1 -GASCGT,all,all,700.0,21.0,0.55,30,0.1 -RGASBR,all,all,55.56,1.6668,0.16,15,0.1 -RELCHP,all,all,138.9,4.167,0.17,15,0.1 -SLRFRM,all,all,400,12.0,0.3,25,0.1 +process_id,regions,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate +GASDRV,all,10.0,0.3,2.0,25,0.1 +GASPRC,all,7.0,0.21,0.5,25,0.1 +WNDFRM,all,1000.0,30.0,0.4,25,0.1 +GASCGT,all,700.0,21.0,0.55,30,0.1 +RGASBR,all,55.56,1.6668,0.16,15,0.1 +RELCHP,all,138.9,4.167,0.17,15,0.1 +SLRFRM,all,400,12.0,0.3,25,0.1 diff --git a/examples/two_outputs/process_availabilities.csv b/examples/two_outputs/process_availabilities.csv index 652ec21af..95047950b 100644 --- a/examples/two_outputs/process_availabilities.csv +++ b/examples/two_outputs/process_availabilities.csv @@ -1,24 +1,24 @@ -process_id,regions,commission_years,time_slice,limits -GASDRV,all,all,annual,..0.9 -OAGRSV,all,all,annual,..0.9 -GASPRC,all,all,annual,..0.9 -OILREF,all,all,annual,..0.9 -OILRF2,all,all,annual,..0.9 -GASCGT,all,all,annual,..0.9 -WNDFRM,all,all,winter.night,..0.486418015 -WNDFRM,all,all,winter.day,..0.543166784 -WNDFRM,all,all,winter.peak,..0.504433498 -WNDFRM,all,all,winter.evening,..0.493173821 -WNDFRM,all,all,peak.night,..0.312697296 -WNDFRM,all,all,peak.day,..0.489120338 -WNDFRM,all,all,peak.peak,..0.454890922 -WNDFRM,all,all,peak.evening,..0.331034483 -WNDFRM,all,all,summer.night,..0.17951141 -WNDFRM,all,all,summer.day,..0.349950739 -WNDFRM,all,all,summer.peak,..0.342294159 -WNDFRM,all,all,summer.evening,..0.202674173 -WNDFRM,all,all,autumn.night,..0.3513019 -WNDFRM,all,all,autumn.day,..0.460745954 -WNDFRM,all,all,autumn.peak,..0.396340605 -WNDFRM,all,all,autumn.evening,..0.364813512 -BIOPLL,all,all,annual,..0.95 +process_id,regions,time_slice,limits +GASDRV,all,annual,..0.9 +OAGRSV,all,annual,..0.9 +GASPRC,all,annual,..0.9 +OILREF,all,annual,..0.9 +OILRF2,all,annual,..0.9 +GASCGT,all,annual,..0.9 +WNDFRM,all,winter.night,..0.486418015 +WNDFRM,all,winter.day,..0.543166784 +WNDFRM,all,winter.peak,..0.504433498 +WNDFRM,all,winter.evening,..0.493173821 +WNDFRM,all,peak.night,..0.312697296 +WNDFRM,all,peak.day,..0.489120338 +WNDFRM,all,peak.peak,..0.454890922 +WNDFRM,all,peak.evening,..0.331034483 +WNDFRM,all,summer.night,..0.17951141 +WNDFRM,all,summer.day,..0.349950739 +WNDFRM,all,summer.peak,..0.342294159 +WNDFRM,all,summer.evening,..0.202674173 +WNDFRM,all,autumn.night,..0.3513019 +WNDFRM,all,autumn.day,..0.460745954 +WNDFRM,all,autumn.peak,..0.396340605 +WNDFRM,all,autumn.evening,..0.364813512 +BIOPLL,all,annual,..0.95 diff --git a/examples/two_outputs/process_flows.csv b/examples/two_outputs/process_flows.csv index 743a786f1..b3fee48cb 100644 --- a/examples/two_outputs/process_flows.csv +++ b/examples/two_outputs/process_flows.csv @@ -1,41 +1,41 @@ -process_id,commodity_id,regions,commission_years,coeff,type,cost -GASDRV,GASPRD,all,all,1,fixed, -OAGRSV,OILCRD,all,all,1,fixed, -OAGRSV,GASPRD,all,all,0.1,fixed, -GASPRC,GASPRD,all,all,-1.05,fixed, -GASPRC,GASNAT,all,all,1,fixed, -OILREF,OILCRD,all,all,-1.3,fixed, -OILREF,GASOLI,all,all,0.5,fixed, -OILREF,DIESEL,all,all,0.5,fixed, -OILRF2,OILCRD,all,all,-1.31,fixed, -OILRF2,GASOLI,all,all,1,fixed, -WNDFRM,ELCTRI,all,all,1,fixed, -GASCGT,GASNAT,all,all,-1.5,fixed, -GASCGT,ELCTRI,all,all,1,fixed, -TPETCR,GASOLI,all,all,-2.702702703,fixed, -TPETCR,TPASKM,all,all,1,fixed, -TDIECR,DIESEL,all,all,-2.222222222,fixed, -TDIECR,TPASKM,all,all,1,fixed, -TELCCR,ELCTRI,all,all,-1.754385965,fixed, -TELCCR,TPASKM,all,all,1,fixed, -THYBCR,ELCTRI,all,all,-0.854385965,fixed, -THYBCR,GASOLI,all,all,-1.0,fixed, -THYBCR,TPASKM,all,all,1,fixed, -RGASBR,GASNAT,all,all,-1.15,fixed, -RGASBR,RSHEAT,all,all,1,fixed, -RELCHP,ELCTRI,all,all,-0.33,fixed, -RELCHP,RSHEAT,all,all,1,fixed, -GASDRV,CO2EMT,all,all,5.113,fixed, -OAGRSV,CO2EMT,all,all,7.333,fixed, -GASPRC,CO2EMT,all,all,2.5565,fixed, -OILREF,CO2EMT,all,all,21.999,fixed, -OILRF2,CO2EMT,all,all,22,fixed, -GASCGT,CO2EMT,all,all,76.695,fixed, -TPETCR,CO2EMT,all,all,179.7297297,fixed, -TDIECR,CO2EMT,all,all,154.0222222,fixed, -RGASBR,CO2EMT,all,all,58.7995,fixed, -BIOPRO,BIOPRD,all,all,1.0,fixed, -BIOPLL,BIOPRD,all,all,-1.05,fixed, -BIOPLL,BIOPEL,all,all,1.0,fixed, -RBIOBL,BIOPEL,all,all,-1.2,fixed, -RBIOBL,RSHEAT,all,all,1,fixed, +process_id,commodity_id,regions,coeff,type,cost +GASDRV,GASPRD,all,1,fixed, +OAGRSV,OILCRD,all,1,fixed, +OAGRSV,GASPRD,all,0.1,fixed, +GASPRC,GASPRD,all,-1.05,fixed, +GASPRC,GASNAT,all,1,fixed, +OILREF,OILCRD,all,-1.3,fixed, +OILREF,GASOLI,all,0.5,fixed, +OILREF,DIESEL,all,0.5,fixed, +OILRF2,OILCRD,all,-1.31,fixed, +OILRF2,GASOLI,all,1,fixed, +WNDFRM,ELCTRI,all,1,fixed, +GASCGT,GASNAT,all,-1.5,fixed, +GASCGT,ELCTRI,all,1,fixed, +TPETCR,GASOLI,all,-2.702702703,fixed, +TPETCR,TPASKM,all,1,fixed, +TDIECR,DIESEL,all,-2.222222222,fixed, +TDIECR,TPASKM,all,1,fixed, +TELCCR,ELCTRI,all,-1.754385965,fixed, +TELCCR,TPASKM,all,1,fixed, +THYBCR,ELCTRI,all,-0.854385965,fixed, +THYBCR,GASOLI,all,-1.0,fixed, +THYBCR,TPASKM,all,1,fixed, +RGASBR,GASNAT,all,-1.15,fixed, +RGASBR,RSHEAT,all,1,fixed, +RELCHP,ELCTRI,all,-0.33,fixed, +RELCHP,RSHEAT,all,1,fixed, +GASDRV,CO2EMT,all,5.113,fixed, +OAGRSV,CO2EMT,all,7.333,fixed, +GASPRC,CO2EMT,all,2.5565,fixed, +OILREF,CO2EMT,all,21.999,fixed, +OILRF2,CO2EMT,all,22,fixed, +GASCGT,CO2EMT,all,76.695,fixed, +TPETCR,CO2EMT,all,179.7297297,fixed, +TDIECR,CO2EMT,all,154.0222222,fixed, +RGASBR,CO2EMT,all,58.7995,fixed, +BIOPRO,BIOPRD,all,1.0,fixed, +BIOPLL,BIOPRD,all,-1.05,fixed, +BIOPLL,BIOPEL,all,1.0,fixed, +RBIOBL,BIOPEL,all,-1.2,fixed, +RBIOBL,RSHEAT,all,1,fixed, diff --git a/examples/two_outputs/process_parameters.csv b/examples/two_outputs/process_parameters.csv index 93a4a0207..8aef03c5b 100644 --- a/examples/two_outputs/process_parameters.csv +++ b/examples/two_outputs/process_parameters.csv @@ -1,17 +1,17 @@ -process_id,regions,commission_years,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate -GASDRV,GBR,all,10,0.3,2,25,0.1 -OAGRSV,GBR,all,15,0.45,3,25,0.1 -GASPRC,GBR,all,7,0.21,0.5,25,0.1 -OILREF,GBR,all,9,0.27,0.66,25,0.1 -OILRF2,GBR,all,9.1,0.3,0.68,25,0.1 -WNDFRM,GBR,all,1000,30,0.4,25,0.1 -GASCGT,GBR,all,700,21,0.55,30,0.1 -TPETCR,GBR,all,2,0.06,0.2,10,0.1 -TDIECR,GBR,all,2.1,0.063,0.18,10,0.1 -TELCCR,GBR,all,4,0.18,0.15,10,0.1 -THYBCR,GBR,all,1,0.17,0.17,10,0.1 -RGASBR,GBR,all,55.56,1.6668,0.16,15,0.1 -RELCHP,GBR,all,138.9,4.167,0.17,15,0.1 -BIOPRO,all,all,1,0.2,0.25,20,0.09 -BIOPLL,all,all,2,0.22,0.26,20,0.1 -RBIOBL,all,all,60,1.05,0.2,20,0.1 +process_id,regions,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate +GASDRV,GBR,10,0.3,2,25,0.1 +OAGRSV,GBR,15,0.45,3,25,0.1 +GASPRC,GBR,7,0.21,0.5,25,0.1 +OILREF,GBR,9,0.27,0.66,25,0.1 +OILRF2,GBR,9.1,0.3,0.68,25,0.1 +WNDFRM,GBR,1000,30,0.4,25,0.1 +GASCGT,GBR,700,21,0.55,30,0.1 +TPETCR,GBR,2,0.06,0.2,10,0.1 +TDIECR,GBR,2.1,0.063,0.18,10,0.1 +TELCCR,GBR,4,0.18,0.15,10,0.1 +THYBCR,GBR,1,0.17,0.17,10,0.1 +RGASBR,GBR,55.56,1.6668,0.16,15,0.1 +RELCHP,GBR,138.9,4.167,0.17,15,0.1 +BIOPRO,all,1,0.2,0.25,20,0.09 +BIOPLL,all,2,0.22,0.26,20,0.1 +RBIOBL,all,60,1.05,0.2,20,0.1 diff --git a/examples/two_regions/process_availabilities.csv b/examples/two_regions/process_availabilities.csv index 746f69584..4bec6d149 100644 --- a/examples/two_regions/process_availabilities.csv +++ b/examples/two_regions/process_availabilities.csv @@ -1,4 +1,4 @@ -process_id,regions,commission_years,time_slice,limits -gassupply1,R1;R2,all,annual,..0.9 -gasCCGT,R1;R2,all,annual,..0.9 -windturbine,R1;R2,all,annual,..0.4 +process_id,regions,time_slice,limits +gassupply1,R1;R2,annual,..0.9 +gasCCGT,R1;R2,annual,..0.9 +windturbine,R1;R2,annual,..0.4 diff --git a/examples/two_regions/process_flows.csv b/examples/two_regions/process_flows.csv index 24bbc411b..4d0bdf181 100644 --- a/examples/two_regions/process_flows.csv +++ b/examples/two_regions/process_flows.csv @@ -1,12 +1,12 @@ -process_id,commodity_id,regions,commission_years,coeff -gassupply1,gas,R1;R2,all,1 -gasCCGT,gas,R1;R2,all,-1.67 -gasCCGT,electricity,R1;R2,all,1 -gasCCGT,CO2f,R1;R2,all,91.67 -windturbine,wind,R1;R2,all,-1 -windturbine,electricity,R1;R2,all,1 -gasboiler,gas,R1;R2,all,-1.16 -gasboiler,heat,R1;R2,all,1 -gasboiler,CO2f,R1;R2,all,64.71 -heatpump,electricity,R1;R2,all,-0.4 -heatpump,heat,R1;R2,all,1 +process_id,commodity_id,regions,coeff +gassupply1,gas,R1;R2,1 +gasCCGT,gas,R1;R2,-1.67 +gasCCGT,electricity,R1;R2,1 +gasCCGT,CO2f,R1;R2,91.67 +windturbine,wind,R1;R2,-1 +windturbine,electricity,R1;R2,1 +gasboiler,gas,R1;R2,-1.16 +gasboiler,heat,R1;R2,1 +gasboiler,CO2f,R1;R2,64.71 +heatpump,electricity,R1;R2,-0.4 +heatpump,heat,R1;R2,1 diff --git a/examples/two_regions/process_parameters.csv b/examples/two_regions/process_parameters.csv index a3952433a..38fc3e79f 100644 --- a/examples/two_regions/process_parameters.csv +++ b/examples/two_regions/process_parameters.csv @@ -1,6 +1,6 @@ -process_id,regions,commission_years,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate -gassupply1,R1;R2,all,0,0,2.55,60,0.1 -gasCCGT,R1;R2,all,23.78234399,0,0,60,0.1 -windturbine,R1;R2,all,36.30771182,0,0,60,0.1 -gasboiler,R1;R2,all,3.8,0,0,60,0.1 -heatpump,R1;R2,all,8.866667,0,0,60,0.1 +process_id,regions,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate +gassupply1,R1;R2,0,0,2.55,60,0.1 +gasCCGT,R1;R2,23.78234399,0,0,60,0.1 +windturbine,R1;R2,36.30771182,0,0,60,0.1 +gasboiler,R1;R2,3.8,0,0,60,0.1 +heatpump,R1;R2,8.866667,0,0,60,0.1 diff --git a/schemas/input/assets.yaml b/schemas/input/assets.yaml index ac71914fe..cd623d4b7 100644 --- a/schemas/input/assets.yaml +++ b/schemas/input/assets.yaml @@ -28,4 +28,4 @@ fields: notes: | Optional. This value can be any integer > `commission_year`. If it is set before the simulation starting year, the asset will not be commissioned during the simulation. If not - set, will default to `commission_year` + the `lifetime` of the process in that year. + set, will default to `commission_year` + the `lifetime` of the process. diff --git a/schemas/input/process_availabilities.yaml b/schemas/input/process_availabilities.yaml index 5c47dcace..eb0fc2ca3 100644 --- a/schemas/input/process_availabilities.yaml +++ b/schemas/input/process_availabilities.yaml @@ -8,7 +8,7 @@ notes: up to 90% during winter days, so long as activity does not exceed 50% across winter as a whole. - Incompatible limits will be flagged at the input validation stage. For example, a limit of "..0.01" for "winter" would likely be incompatible with a limit of "0.9.." for "winter.day". - - If a limit is provided for any time slice (or season) for a particular process/region/year, it + - If a limit is provided for any time slice (or season) for a particular process/region, it must be provided for all time slices (or seasons). fields: @@ -19,14 +19,6 @@ fields: type: string description: The region(s) to which this entry applies notes: One or more region IDs, separated by semicolons or the string `all` - - name: commission_years - type: string - description: The milestone year(s) to which this entry applies - notes: - One or more milestone years separated by semicolons, `all` to select all years or a year - range in the form 'start..end' to select all valid years within range, inclusive. Either 'start' - 'end' or both can be omitted, which will set the corresponding limit to the minimum or maximum - valid year, respectively. - name: time_slice type: string description: The time slice(s) to which this entry applies diff --git a/schemas/input/process_flows.yaml b/schemas/input/process_flows.yaml index a56545d51..86960483c 100644 --- a/schemas/input/process_flows.yaml +++ b/schemas/input/process_flows.yaml @@ -3,9 +3,8 @@ description: | The commodity flows for each process notes: - - Commodity flows can vary by region and year. - - For each process, there must be entries covering all the years and regions in which the process - can be commissioned. + - Commodity flows can vary by region. + - For each process, there must be entries covering all regions in which the process can operate. fields: - name: process_id @@ -20,14 +19,6 @@ fields: notes: | One or more region IDs, separated by semicolons or the string `all`. Must be regions in which the process operates. - - name: commission_years - type: string - description: The year(s) to which this entry applies - notes: - One or more milestone years separated by semicolons, `all` to select all years or a year - range in the form 'start..end' to select all valid years within range, inclusive. Either 'start' - 'end' or both can be omitted, which will set the corresponding limit to the minimum or maximum - valid year, respectively. - name: coeff type: number description: The flow for this commodity diff --git a/schemas/input/process_parameters.yaml b/schemas/input/process_parameters.yaml index f7a6feaaf..701a66192 100644 --- a/schemas/input/process_parameters.yaml +++ b/schemas/input/process_parameters.yaml @@ -1,10 +1,10 @@ $schema: https://specs.frictionlessdata.io/schemas/table-schema.json description: | - Parameters for processes which vary by region and year. + Parameters for processes which vary by region. notes: - - For each process, entries must be provided which cover every combination of milestone year and - region in which the process can be commissioned. + - For each process, entries must be provided which cover every region in which the process can + operate. fields: - name: process_id @@ -16,14 +16,6 @@ fields: notes: | One or more region IDs, separated by semicolons or the string `all`. Must be regions in which the process operates. - - name: commission_years - type: string - description: The milestone year(s) to which this entry applies - notes: - One or more milestone years separated by semicolons, `all` to select all years or a year - range in the form 'start..end' to select all valid years within range, inclusive. Either 'start' - 'end' or both can be omitted, which will set the corresponding limit to the minimum or maximum - valid year, respectively. - name: capital_cost type: number description: Overnight capital cost per unit capacity diff --git a/src/asset.rs b/src/asset.rs index 821613de3..ad607ce54 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -96,7 +96,7 @@ pub struct Asset { activity_limits: Arc, /// The commodity flows for this asset flows: Arc>, - /// The [`ProcessParameter`] corresponding to the asset's region and commission year + /// The [`ProcessParameter`] corresponding to the asset's region process_parameter: Arc, /// The region in which the asset is located region_id: RegionID, @@ -226,42 +226,38 @@ impl Asset { "Capacity must be non-negative" ); - // There should be activity limits, commodity flows and process parameters for all - // **milestone** years, but it is possible to have assets that are commissioned before the - // simulation start from assets.csv. We check for the presence of the params lazily to - // prevent users having to supply them for all the possible valid years before the time - // horizon. - let key = (region_id.clone(), commission_year); + // There should be activity limits, commodity flows and process parameters for every + // region in which the process operates. let activity_limits = process .activity_limits - .get(&key) + .get(®ion_id) .with_context(|| { format!( - "No process availabilities supplied for process {} in region {} in year {}. \ + "No process availabilities supplied for process {} in region {}. \ You should update process_availabilities.csv.", - process.id, region_id, commission_year + process.id, region_id ) })? .clone(); let flows = process .flows - .get(&key) + .get(®ion_id) .with_context(|| { format!( - "No commodity flows supplied for process {} in region {} in year {}. \ + "No commodity flows supplied for process {} in region {}. \ You should update process_flows.csv.", - process.id, region_id, commission_year + process.id, region_id ) })? .clone(); let process_parameter = process .parameters - .get(&key) + .get(®ion_id) .with_context(|| { format!( - "No process parameters supplied for process {} in region {} in year {}. \ + "No process parameters supplied for process {} in region {}. \ You should update process_parameters.csv.", - process.id, region_id, commission_year + process.id, region_id ) })? .clone(); @@ -868,7 +864,7 @@ pub fn check_region_year_valid_for_process( region_id ); ensure!( - process.active_for_year(year), + process.can_be_commissioned_in_year(year), "Process {} does not operate in the year {}", process.id, year diff --git a/src/fixture.rs b/src/fixture.rs index 02b98c8f2..08bf83c3b 100644 --- a/src/fixture.rs +++ b/src/fixture.rs @@ -26,7 +26,6 @@ use crate::units::{ use anyhow::Result; use indexmap::indexmap; use indexmap::{IndexMap, IndexSet}; -use itertools::Itertools; use rstest::fixture; use std::collections::HashMap; use std::iter; @@ -73,11 +72,13 @@ macro_rules! patch_and_validate_simple { pub(crate) use patch_and_validate_simple; /// Check whether validation succeeds for simple example with patches +#[allow(unused_macros)] macro_rules! assert_validate_ok_simple { ($file_patches:expr) => { crate::fixture::patch_and_validate_simple!($file_patches).unwrap(); }; } +#[allow(unused_imports)] pub(crate) use assert_validate_ok_simple; /// Check whether validation fails with specific message @@ -243,7 +244,7 @@ pub fn process_parameter() -> ProcessParameter { } #[fixture] -/// Create a `ProcessParameterMap` with the specified parameters for each region and year +/// Create a `ProcessParameterMap` with the specified parameters for each region pub fn process_parameter_map( region_ids: IndexSet, process_parameter: ProcessParameter, @@ -251,8 +252,7 @@ pub fn process_parameter_map( let parameter = Arc::new(process_parameter); region_ids .into_iter() - .cartesian_product(2010..=2020) - .map(|(region_id, year)| ((region_id, year), parameter.clone())) + .map(|region_id| (region_id, parameter.clone())) .collect() } @@ -263,15 +263,14 @@ pub fn process_activity_limits(time_slice_info: TimeSliceInfo) -> ActivityLimits } #[fixture] -/// Create a `ProcessActivityLimitsMap` with full availability for each region and year +/// Create a `ProcessActivityLimitsMap` with full availability for each region pub fn process_activity_limits_map( region_ids: IndexSet, process_activity_limits: ActivityLimits, ) -> ProcessActivityLimitsMap { region_ids .into_iter() - .cartesian_product(2010..=2020) - .map(|(region_id, year)| ((region_id, year), Arc::new(process_activity_limits.clone()))) + .map(|region_id| (region_id, Arc::new(process_activity_limits.clone()))) .collect() } @@ -283,21 +282,20 @@ pub fn process_investment_constraints() -> ProcessInvestmentConstraintsMap { } #[fixture] -/// Create an empty set of `ProcessFlows` for a given region/year +/// Create an empty set of `ProcessFlows` pub fn process_flows() -> Arc> { Arc::new(IndexMap::new()) } #[fixture] -/// Create a `ProcessFlowsMap` with the provided flows for each region/year +/// Create a `ProcessFlowsMap` with the provided flows for each region pub fn process_flows_map( region_ids: IndexSet, process_flows: Arc>, ) -> ProcessFlowsMap { region_ids .into_iter() - .cartesian_product(2010..=2020) - .map(|(region_id, year)| ((region_id, year), process_flows.clone())) + .map(|region_id| (region_id, process_flows.clone())) .collect() } diff --git a/src/graph.rs b/src/graph.rs index def0ddb06..212a348ee 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -53,32 +53,19 @@ pub enum GraphEdge { /// Helper function to return a possible flow operating in the requested year /// -/// We are only interested in the flow directions, which are constant across years. This -/// function checks whether the process can be operating in the target region and year and, if so, -/// returns its flows. It considers both the commission year and the process lifetime, since a -/// process may operate for years after its commission window. If the process cannot be operating -/// in the target region/year, `None` is returned. +/// We are only interested in the flow directions, which are constant across years. This function +/// checks whether the process can be operating in the target region and year and, if so, returns +/// its region-specific flows. A process may operate for years after its commissioning window due +/// to its lifetime; if it cannot operate in the target region/year, `None` is returned. fn get_flow_for_year( process: &Process, target: (RegionID, u32), ) -> Option>> { - // If its already in the map, we return it - if process.flows.contains_key(&target) { - return process.flows.get(&target).cloned(); - } - - // Otherwise we try to find one that operates in the target year. It is assumed that - // parameters are defined for at least the same (region, year) combinations as flows. let (target_region, target_year) = target; - for ((region, year), value) in &process.flows { - if *region != target_region { - continue; - } - if year + process.parameters[&(region.clone(), *year)].lifetime >= target_year { - return Some(value.clone()); - } + if !process.can_operate(&target_region, target_year) { + return None; } - None + process.flows.get(&target_region).cloned() } /// Creates a directed graph of commodity flows for a given region and year. diff --git a/src/graph/validate.rs b/src/graph/validate.rs index b750ae9db..1862de232 100644 --- a/src/graph/validate.rs +++ b/src/graph/validate.rs @@ -31,7 +31,6 @@ fn prepare_commodities_graph_for_validation( // Filter by process availability // We keep edges if the process has availability > 0 in any time slice in the selection - let key = (region_id.clone(), year); filtered_graph.retain_edges(|graph, edge_idx| { // Get the process for the edge let process_id = match graph.edge_weight(edge_idx).unwrap() { @@ -41,7 +40,7 @@ fn prepare_commodities_graph_for_validation( let process = &processes[process_id]; // Check if the process has availability > 0 in any time slice in the selection - can_be_active(process, &key, time_slice_selection) + can_be_active(process, region_id, year, time_slice_selection) }); // Add demand edges @@ -70,32 +69,24 @@ fn prepare_commodities_graph_for_validation( /// Checks if a process can be active for a particular time slice in a given year and region /// -/// It considers all commission years that can lead to a running process in the target region and -/// year, accounting for the process lifetime, and then checks if, for any of those, the process -/// is active in the required time slice. In other words, this checks if there is the _possibility_ -/// of having an active process, although there is no guarantee of that happening since it depends -/// on the investment. +/// It checks whether the process could be operating in the target region and year, accounting for +/// the process lifetime, and then checks whether it is active in the required time slice. In other +/// words, this checks if there is the _possibility_ of having an active process, although there is +/// no guarantee of that happening since it depends on the investment. fn can_be_active( process: &Process, - target: &(RegionID, u32), + region_id: &RegionID, + year: u32, time_slice_selection: &TimeSliceSelection, ) -> bool { - let (target_region, target_year) = target; - - for ((region, year), value) in &process.parameters { - if region != target_region { - continue; - } - if year + value.lifetime >= *target_year { - let Some(limits_map) = process.activity_limits.get(target) else { - continue; - }; - if limits_map.get_limit(time_slice_selection).end() > &Dimensionless(0.0) { - return true; - } - } + if !process.can_operate(region_id, year) { + return false; } - false + + process + .activity_limits + .get(region_id) + .is_some_and(|limits| limits.get_limit(time_slice_selection).end() > &Dimensionless(0.0)) } /// Validates that the commodity graph follows the rules for different commodity types. diff --git a/src/input/agent/search_space.rs b/src/input/agent/search_space.rs index 6216b0072..55ac747d8 100644 --- a/src/input/agent/search_space.rs +++ b/src/input/agent/search_space.rs @@ -244,7 +244,7 @@ fn get_producers_map(agents: &AgentMap, processes: &ProcessMap) -> ProducersMap let producers = processes .values() .filter(move |process| { - process.active_for_year(*year) + process.can_be_commissioned_in_year(*year) && process.primary_output.as_ref() == Some(commodity_id) && process.regions.contains(region_id) }) diff --git a/src/input/asset.rs b/src/input/asset.rs index da8b27798..2e81ee2e5 100644 --- a/src/input/asset.rs +++ b/src/input/asset.rs @@ -79,7 +79,7 @@ where .get_id_value(&asset.process_id)?; let region_id = region_ids.get_id(&asset.region_id)?; - // Validate commission year. It should be within the process valid range... + // Validate the commission year against the process's commissioning range... ensure!( process.years.contains(&asset.commission_year), "Agent {} has asset with commission year {}, not within process {} commission years: {:?}", @@ -90,17 +90,17 @@ where ); // ... and also have associated process parameters and flows ensure!( - process.parameters.contains_key(&(region_id.clone(), asset.commission_year)), - "Parameters for process {} do not contain entry for year {}, required for asset in agent {}", + process.parameters.contains_key(region_id), + "Parameters for process {} do not contain entry for region {}, required for asset in agent {}", process_id, - asset.commission_year, + region_id, asset.agent_id, ); ensure!( - process.flows.contains_key(&(region_id.clone(), asset.commission_year)), - "Flows for process {} do not contain entry for year {}, required for asset in agent {}", + process.flows.contains_key(region_id), + "Flows for process {} do not contain entry for region {}, required for asset in agent {}", process_id, - asset.commission_year, + region_id, asset.agent_id, ); diff --git a/src/input/process.rs b/src/input/process.rs index d86063854..f767a7764 100644 --- a/src/input/process.rs +++ b/src/input/process.rs @@ -63,8 +63,8 @@ pub fn read_processes( ) -> Result { let mut processes = read_processes_file(model_dir, milestone_years, region_ids, commodities)?; let mut activity_limits = read_process_availabilities(model_dir, &processes, time_slice_info)?; - let mut flows = read_process_flows(model_dir, &mut processes, commodities, milestone_years)?; - let mut parameters = read_process_parameters(model_dir, &processes, milestone_years)?; + let mut flows = read_process_flows(model_dir, &mut processes, commodities)?; + let mut parameters = read_process_parameters(model_dir, &processes)?; let mut investment_constraints = read_process_investment_constraints(model_dir, &processes, milestone_years)?; diff --git a/src/input/process/availability.rs b/src/input/process/availability.rs index dadf88eb1..f5dcbcca7 100644 --- a/src/input/process/availability.rs +++ b/src/input/process/availability.rs @@ -1,13 +1,11 @@ //! Code for reading process availabilities from a CSV file. use super::super::{input_err_msg, parse_range, read_csv_optional, try_insert}; use crate::id::GetIDValue; -use crate::input::parse_year_str; use crate::process::{ActivityLimits, ProcessActivityLimitsMap, ProcessID, ProcessMap}; use crate::region::parse_region_str; use crate::time_slice::TimeSliceInfo; use crate::units::{Dimensionless, Year}; use anyhow::{Context, Result}; -use itertools::iproduct; use serde::Deserialize; use std::collections::HashMap; use std::ops::RangeInclusive; @@ -21,7 +19,6 @@ const PROCESS_AVAILABILITIES_FILE_NAME: &str = "process_availabilities.csv"; struct ProcessAvailabilityRaw { process_id: String, regions: String, - commission_years: String, time_slice: String, limits: String, } @@ -108,25 +105,17 @@ where format!("Invalid region for process {id}. Valid regions are {process_regions:?}") })?; - // Get years - let process_years: Vec = process.years.clone().collect(); - let record_years = - parse_year_str(&record.commission_years, &process_years).with_context(|| { - format!("Invalid year for process {id}. Valid years are {process_years:?}") - })?; - // Get time slices let ts_selection = time_slice_info.get_selection(&record.time_slice)?; - // Store the activity limit for each region/year + // Store the activity limit for each region let entries_for_process = entries.get_mut(id).unwrap(); - for (region_id, year) in iproduct!(&record_regions, &record_years) { - let entries_for_process_region_year = entries_for_process - .entry((region_id.clone(), *year)) - .or_default(); + for region_id in record_regions { + let entries_for_process_region = + entries_for_process.entry(region_id.clone()).or_default(); let length = time_slice_info.length_for_selection(&ts_selection)?; try_insert( - entries_for_process_region_year, + entries_for_process_region, &ts_selection, record.to_bounds(length)?, )?; @@ -134,23 +123,23 @@ where } // Create `ProcessActivityLimitsMap`s for each process. - // Maps are created for all regions and years defined for each process, gathering the limits + // Maps are created for all regions defined for each process, gathering the limits // defined in the entries above, or using default limits (full availability) if none were // defined. let mut map = HashMap::new(); for (process_id, process) in processes { let mut inner_map = HashMap::new(); let entries_for_process = &entries[process_id]; - for (region_id, year) in iproduct!(&process.regions, process.years.clone()) { + for region_id in &process.regions { let limits = entries_for_process - .get(&(region_id.clone(), year)) + .get(region_id) .cloned() .unwrap_or_default(); let availabilities = ActivityLimits::new_from_limits(&limits, time_slice_info) .with_context(|| { format!("Error creating activity limits for process {process_id}") })?; - inner_map.insert((region_id.clone(), year), Arc::new(availabilities)); + inner_map.insert(region_id.clone(), Arc::new(availabilities)); } map.insert(process_id.clone(), inner_map); } @@ -168,7 +157,6 @@ mod tests { ProcessAvailabilityRaw { process_id: "process".into(), regions: "region".into(), - commission_years: "2010".into(), time_slice: "day".into(), limits, } diff --git a/src/input/process/flow.rs b/src/input/process/flow.rs index 69a323308..558fe4513 100644 --- a/src/input/process/flow.rs +++ b/src/input/process/flow.rs @@ -2,7 +2,6 @@ use super::super::{input_err_msg, read_csv}; use crate::commodity::{CommodityID, CommodityMap, CommodityType}; use crate::id::GetIDValue; -use crate::input::parse_year_str; use crate::process::{ FlowDirection, FlowType, ProcessFlow, ProcessFlowsMap, ProcessID, ProcessMap, }; @@ -10,7 +9,6 @@ use crate::region::{RegionID, parse_region_str}; use crate::units::{FlowPerActivity, MoneyPerFlow}; use anyhow::{Context, Result, bail, ensure}; use indexmap::{IndexMap, IndexSet}; -use itertools::iproduct; use serde::Deserialize; use std::collections::HashMap; use std::path::Path; @@ -22,7 +20,6 @@ const PROCESS_FLOWS_FILE_NAME: &str = "process_flows.csv"; struct ProcessFlowRaw { process_id: String, commodity_id: String, - commission_years: String, regions: String, coeff: FlowPerActivity, #[serde(default)] @@ -65,7 +62,6 @@ impl ProcessFlowRaw { /// * `model_dir` - Folder containing model configuration files /// * `processes` - Mutable map of known processes (may be updated) /// * `commodities` - Map of known commodities -/// * `milestone_years` - Milestone years used by the model /// /// # Returns /// @@ -74,11 +70,10 @@ pub fn read_process_flows( model_dir: &Path, processes: &mut ProcessMap, commodities: &CommodityMap, - milestone_years: &[u32], ) -> Result> { let file_path = model_dir.join(PROCESS_FLOWS_FILE_NAME); let process_flow_csv = read_csv(&file_path)?; - read_process_flows_from_iter(process_flow_csv, processes, commodities, milestone_years) + read_process_flows_from_iter(process_flow_csv, processes, commodities) .with_context(|| input_err_msg(&file_path)) } @@ -90,10 +85,10 @@ pub fn read_process_flows( fn validate_output_flows_units(flows_map: &HashMap) -> Result<()> { // Collect all validation errors so that the error reported is deterministic, // this is needed because ProcessFlows are stored in a HashMap. - let mut errors: Vec<(ProcessID, RegionID, u32, Vec<&str>)> = Vec::new(); + let mut errors: Vec<(ProcessID, RegionID, Vec<&str>)> = Vec::new(); for (process_id, process_flows) in flows_map { - for ((region_id, year), flows) in process_flows { + for (region_id, flows) in process_flows { let sed_svd_output_units: IndexSet<&str> = flows .values() .filter_map(|flow| { @@ -112,7 +107,6 @@ fn validate_output_flows_units(flows_map: &HashMap) errors.push(( process_id.clone(), region_id.clone(), - *year, sed_svd_output_units.into_iter().collect(), )); } @@ -120,15 +114,13 @@ fn validate_output_flows_units(flows_map: &HashMap) } // Sort errors for deterministic ordering - errors.sort_by_key(|(process_id, region_id, year, _)| { - (process_id.clone(), region_id.clone(), *year) - }); + errors.sort_by_key(|(process_id, region_id, _)| (process_id.clone(), region_id.clone())); // Return first error if any exist - if let Some((process_id, region_id, year, units)) = errors.first() { + if let Some((process_id, region_id, units)) = errors.first() { bail!( "Process {process_id} has SED/SVD outputs with different units: [{}] \ - in region: {region_id} and year: {year}", + in region: {region_id}", units.join(", ") ); } @@ -143,7 +135,6 @@ fn validate_output_flows_units(flows_map: &HashMap) /// * `iter` - Iterator over `ProcessFlowRaw` records /// * `processes` - Mutable map of known processes used for validation and updates /// * `commodities` - Map of known commodities -/// * `milestone_years` - Milestone years used by the model /// /// # Returns /// @@ -152,7 +143,6 @@ fn read_process_flows_from_iter( iter: I, processes: &mut ProcessMap, commodities: &CommodityMap, - milestone_years: &[u32], ) -> Result> where I: Iterator, @@ -171,13 +161,6 @@ where format!("Invalid region for process {id}. Valid regions are {process_regions:?}") })?; - // Get years - let process_years: Vec = process.years.clone().collect(); - let record_years = - parse_year_str(&record.commission_years, &process_years).with_context(|| { - format!("Invalid year for process {id}. Valid years are {process_years:?}") - })?; - // Get commodity let (_, commodity) = commodities.get_id_value(&record.commodity_id)?; @@ -190,27 +173,23 @@ where }; // Insert flow into the map - let region_year_map = flows_map.entry(id.clone()).or_default(); - for (year, region_id) in iproduct!(record_years, record_regions.iter()) { - let flows_map = region_year_map - .entry((region_id.clone(), year)) - .or_default(); + let region_map = flows_map.entry(id.clone()).or_default(); + for region_id in record_regions { + let flows_map = region_map.entry(region_id.clone()).or_default(); let existing = Arc::get_mut(flows_map) .unwrap() // safe: there will only be one copy .insert(commodity.id.clone(), process_flow.clone()) .is_some(); ensure!( !existing, - "Duplicate process flow entry for region {}, year {} and commodity {}", + "Duplicate process flow entry for region {} and commodity {}", region_id, - year, commodity.id ); } } - validate_flows_and_update_primary_output(processes, &flows_map, milestone_years)?; - validate_secondary_flows(processes, &flows_map, milestone_years)?; + validate_flows_and_update_primary_output(processes, &flows_map)?; validate_output_flows_units(&flows_map)?; Ok(flows_map) @@ -219,44 +198,36 @@ where fn validate_flows_and_update_primary_output( processes: &mut ProcessMap, flows_map: &HashMap, - milestone_years: &[u32], ) -> Result<()> { for (process_id, process) in processes.iter_mut() { let map = flows_map .get(process_id) .with_context(|| format!("Missing flows map for process {process_id}"))?; - // Flows are required for all milestone years within the process years of activity - let required_years = milestone_years - .iter() - .filter(|&y| process.years.contains(y)); - let region_year: Vec<(&RegionID, &u32)> = - iproduct!(process.regions.iter(), required_years).collect(); - ensure!( - region_year + process + .regions .iter() - .all(|(region_id, year)| map.contains_key(&((*region_id).clone(), **year))), - "Flows map for process {process_id} does not cover all regions and required years" + .all(|region_id| map.contains_key(region_id)), + "Flows map for process {process_id} does not cover all regions" ); let primary_output = if let Some(primary_output) = &process.primary_output { Some(primary_output.clone()) } else { - let (region_id, year) = region_year[0]; - infer_primary_output(&map[&(region_id.clone(), *year)]).with_context(|| { + let region_id = process.regions.first().unwrap(); + infer_primary_output(&map[region_id]).with_context(|| { format!("Could not infer primary_output for process {process_id}") })? }; - for (region_id, &year) in region_year { - let flows = &map[&(region_id.clone(), year)]; + for region_id in &process.regions { + let flows = &map[region_id]; - // Check that the process has flows for this region/year check_flows_primary_output(flows, primary_output.as_ref()).with_context(|| { format!( "Invalid primary output configuration for process {process_id} \ - (region: {region_id}, year: {year})" + (region: {region_id})" ) })?; } @@ -312,90 +283,19 @@ fn check_flows_primary_output( .values() .all(|x| x.direction() == FlowDirection::Input || x.direction() == FlowDirection::Zero), - "First year is only inputs, but subsequent years have outputs, although no primary \ - output is specified" + "Process has output flows, but no primary output is specified" ); } Ok(()) } -/// Checks that non-primary io are defined for all milestone years, at least, (within a region) and -/// that they are only inputs or only outputs in all years. -fn validate_secondary_flows( - processes: &mut ProcessMap, - flows_map: &HashMap, - milestone_years: &[u32], -) -> Result<()> { - for (process_id, process) in processes.iter() { - // Get the flows for this process - there should be no error, as was checked already - let map = flows_map - .get(process_id) - .with_context(|| format!("Missing flows map for process {process_id}"))?; - - // Flows are required for all milestone years within the process years of activity - let required_years: Vec<&u32> = milestone_years - .iter() - .filter(|&y| process.years.contains(y)) - .collect(); - - // Get the non-primary io flows for all years, if any, arranged by (commodity, region) - let iter = iproduct!(process.years.clone(), process.regions.iter()); - let mut flows: HashMap<(CommodityID, RegionID), Vec<&ProcessFlow>> = HashMap::new(); - let mut number_of_years: HashMap<(CommodityID, RegionID), u32> = HashMap::new(); - for (year, region_id) in iter { - if let Some(commodity_map) = map.get(&(region_id.clone(), year)) { - let flow = commodity_map.iter().filter_map(|(commodity_id, flow)| { - (Some(commodity_id) != process.primary_output.as_ref()) - .then_some(((commodity_id.clone(), region_id.clone()), flow)) - }); - - for (key, value) in flow { - flows.entry(key.clone()).or_default().push(value); - if required_years.contains(&&year) { - *number_of_years.entry(key).or_default() += 1; - } - } - } - } - - // Finally we check that the flows for a given commodity and region are defined for all - // milestone years and that they are all inputs or all outputs. This later check is done - // for all years in the process range, required or not. - for ((commodity_id, region_id), value) in &flows { - ensure!( - number_of_years[&(commodity_id.clone(), region_id.clone())] - == required_years.len().try_into().unwrap(), - "Flow of commodity {commodity_id} in region {region_id} for process {process_id} \ - does not cover all milestone years within the process range of activity." - ); - let input_or_zero = value - .iter() - .all(|&x| [FlowDirection::Input, FlowDirection::Zero].contains(&x.direction())); - let output_or_zero = value - .iter() - .all(|&x| [FlowDirection::Output, FlowDirection::Zero].contains(&x.direction())); - ensure!( - input_or_zero || output_or_zero, - "Flow of commodity {commodity_id} in region {region_id} for process {process_id} \ - behaves as input or output in different years." - ); - } - } - - Ok(()) -} - #[cfg(test)] mod tests { use super::*; use crate::commodity::Commodity; use crate::commodity::{CommodityLevyMap, DemandMap, PricingStrategy}; - use crate::fixture::{ - assert_error, assert_validate_fails_with_simple, assert_validate_ok_simple, - other_commodity, process, sed_commodity, svd_commodity, - }; - use crate::patch::FilePatch; + use crate::fixture::{assert_error, other_commodity, process, sed_commodity, svd_commodity}; use crate::process::{FlowType, Process, ProcessFlow, ProcessMap}; use crate::time_slice::TimeSliceLevel; use crate::units::{FlowPerActivity, MoneyPerFlow}; @@ -418,15 +318,15 @@ mod tests { fn build_maps( process: Process, flows: I, - years: Option>, ) -> (ProcessMap, HashMap) where I: Clone + Iterator, { - let years = years.unwrap_or(process.years.clone().collect()); let map: Arc> = Arc::new(flows.collect()); - let flows_inner = iproduct!(&process.regions, years) - .map(|(region_id, year)| ((region_id.clone(), year), map.clone())) + let flows_inner = process + .regions + .iter() + .map(|region_id| (region_id.clone(), map.clone())) .collect(); let flows = hash_map! {process.id.clone() => flows_inner}; let processes = iter::once((process.id.clone(), process.into())).collect(); @@ -482,7 +382,6 @@ mod tests { (commodity2.id.clone(), flow(commodity2.clone(), 2.0)), ] .into_iter(), - None, ); // Validation should pass since the units are the same @@ -507,7 +406,6 @@ mod tests { (commodity2.id.clone(), flow(commodity2.clone(), 2.0)), ] .into_iter(), - None, ); // Different units should cause validation to fail @@ -516,7 +414,7 @@ mod tests { // this exact error message. assert_error!( result, - "Process process1 has SED/SVD outputs with different units: [PJ, tonnes] in region: GBR and year: 2010" + "Process process1 has SED/SVD outputs with different units: [PJ, tonnes] in region: GBR" ); } @@ -541,7 +439,6 @@ mod tests { (oth_commodity.id.clone(), flow(oth_commodity.clone(), 2.0)), ] .into_iter(), - None, ); // OTH commodity should be ignored, validation should pass @@ -554,7 +451,6 @@ mod tests { let (_, flows_map) = build_maps( process, std::iter::once((commodity.id.clone(), flow(commodity.clone(), 1.0))), - None, ); // Single output should always pass validation @@ -572,7 +468,6 @@ mod tests { (CommodityID("oth2".into()), flow(oth_commodity_2, 2.0)), ] .into_iter(), - None, ); // Processes with only OTH outputs should pass validation @@ -606,7 +501,6 @@ mod tests { (output.id.clone(), flow(output.clone(), 3.0)), ] .into_iter(), - None, ); // Validation should pass because only outputs are checked @@ -615,15 +509,12 @@ mod tests { #[rstest] fn single_output_infer_primary(#[from(svd_commodity)] commodity: Commodity, process: Process) { - let milestone_years = vec![2010, 2020]; let commodity = Arc::new(commodity); let (mut processes, flows_map) = build_maps( process, std::iter::once((commodity.id.clone(), flow(commodity.clone(), 1.0))), - None, ); - validate_flows_and_update_primary_output(&mut processes, &flows_map, &milestone_years) - .unwrap(); + validate_flows_and_update_primary_output(&mut processes, &flows_map).unwrap(); assert_eq!( processes.values().exactly_one().unwrap().primary_output, Some(commodity.id.clone()) @@ -636,7 +527,6 @@ mod tests { #[from(sed_commodity)] commodity2: Commodity, process: Process, ) { - let milestone_years: Vec = vec![2010, 2020]; let commodity1 = Arc::new(commodity1); let commodity2 = Arc::new(commodity2); let (mut processes, flows_map) = build_maps( @@ -646,10 +536,8 @@ mod tests { (commodity2.id.clone(), flow(commodity2.clone(), 2.0)), ] .into_iter(), - None, ); - let res = - validate_flows_and_update_primary_output(&mut processes, &flows_map, &milestone_years); + let res = validate_flows_and_update_primary_output(&mut processes, &flows_map); assert_error!(res, "Could not infer primary_output for process process1"); } @@ -659,7 +547,6 @@ mod tests { #[from(sed_commodity)] commodity2: Commodity, process: Process, ) { - let milestone_years = vec![2010, 2020]; let commodity1 = Arc::new(commodity1); let commodity2 = Arc::new(commodity2); let mut process = process; @@ -671,10 +558,8 @@ mod tests { (commodity2.id.clone(), flow(commodity2.clone(), 2.0)), ] .into_iter(), - None, ); - validate_flows_and_update_primary_output(&mut processes, &flows_map, &milestone_years) - .unwrap(); + validate_flows_and_update_primary_output(&mut processes, &flows_map).unwrap(); assert_eq!( processes.values().exactly_one().unwrap().primary_output, Some(commodity2.id.clone()) @@ -687,7 +572,6 @@ mod tests { #[from(sed_commodity)] commodity2: Commodity, process: Process, ) { - let milestone_years = vec![2010, 2020]; let commodity1 = Arc::new(commodity1); let commodity2 = Arc::new(commodity2); let (mut processes, flows_map) = build_maps( @@ -697,111 +581,11 @@ mod tests { (commodity2.id.clone(), flow(commodity2.clone(), -2.0)), ] .into_iter(), - None, ); - validate_flows_and_update_primary_output(&mut processes, &flows_map, &milestone_years) - .unwrap(); + validate_flows_and_update_primary_output(&mut processes, &flows_map).unwrap(); assert_eq!( processes.values().exactly_one().unwrap().primary_output, None ); } - - #[rstest] - fn flows_not_in_all_milestone_years( - #[from(svd_commodity)] commodity1: Commodity, - #[from(sed_commodity)] commodity2: Commodity, - process: Process, - ) { - let milestone_years = vec![2010, 2015, 2020]; - let flow_years = vec![2010, 2020]; - let commodity1 = Arc::new(commodity1); - let commodity2 = Arc::new(commodity2); - let (mut processes, flows_map) = build_maps( - process, - [ - (commodity1.id.clone(), flow(commodity1.clone(), 1.0)), - (commodity2.id.clone(), flow(commodity2.clone(), 2.0)), - ] - .into_iter(), - Some(flow_years), - ); - let res = - validate_flows_and_update_primary_output(&mut processes, &flows_map, &milestone_years); - assert_error!( - res, - "Flows map for process process1 does not cover all regions and required years" - ); - } - - #[rstest] - fn flows_only_milestone_years( - #[from(svd_commodity)] commodity1: Commodity, - #[from(sed_commodity)] commodity2: Commodity, - process: Process, - ) { - let milestone_years = vec![2010, 2015, 2020]; - let commodity1 = Arc::new(commodity1); - let commodity2 = Arc::new(commodity2); - let (mut processes, flows_map) = build_maps( - process, - [ - (commodity1.id.clone(), flow(commodity1.clone(), 1.0)), - (commodity2.id.clone(), flow(commodity2.clone(), -2.0)), - ] - .into_iter(), - Some(milestone_years.clone()), - ); - validate_flows_and_update_primary_output(&mut processes, &flows_map, &milestone_years) - .unwrap(); - } - - #[test] - fn flows_different_direction_different_years() { - let patch = FilePatch::new("process_flows.csv") - .with_deletion("GASPRC,GASPRD,all,all,-1.05,fixed,") - .with_addition("GASPRC,GASPRD,all,2020;2030,-1.05,fixed,") - .with_addition("GASPRC,GASPRD,all,2040,1.05,fixed,"); - assert_validate_fails_with_simple!( - vec![patch], - "Flow of commodity GASPRD in region GBR for process GASPRC behaves as input or output in different years." - ); - } - - #[test] - fn missing_flow() { - let patch = FilePatch::new("process_flows.csv") - .with_deletion("GASPRC,GASPRD,all,all,-1.05,fixed,") - .with_addition("GASPRC,GASPRD,all,2020;2030,-1.05,fixed,"); - assert_validate_fails_with_simple!( - vec![patch], - "Flow of commodity GASPRD in region GBR for process GASPRC does not cover all milestone years within the process range of activity." - ); - } - - #[test] - fn coeff_zero() { - let patch = FilePatch::new("process_flows.csv") - .with_deletion("GASPRC,GASPRD,all,all,-1.05,fixed,") - .with_addition("GASPRC,GASPRD,all,2020;2030,-1.05,fixed,") - .with_addition("GASPRC,GASPRD,all,2040,0,fixed,"); - assert_validate_ok_simple!(vec![patch]); - } - - #[test] - fn flows_not_needed_before_time_horizon() { - // NB: Time horizon starts at 2020 for simple example - // - // Flows are only needed for milestone years. Check that users can omit them for - // non-milestone years. - let patches = vec![ - FilePatch::new("processes.csv") - .with_deletion("GASDRV,Dry gas extraction,all,GASPRD,2020,2040,1.0,") - .with_addition("GASDRV,Dry gas extraction,all,GASPRD,1980,2040,1.0,"), - FilePatch::new("process_flows.csv") - .with_deletion("GASPRC,GASPRD,all,all,-1.05,fixed,") - .with_addition("GASPRC,GASPRD,all,2020;2030;2040,-1.05,fixed,"), - ]; - assert_validate_ok_simple!(patches); - } } diff --git a/src/input/process/parameter.rs b/src/input/process/parameter.rs index 8b860ec91..6fa2d14c0 100644 --- a/src/input/process/parameter.rs +++ b/src/input/process/parameter.rs @@ -1,7 +1,6 @@ //! Code for reading process parameters from a CSV file use super::super::{format_items_with_cap, input_err_msg, read_csv, try_insert}; use crate::id::GetIDValue; -use crate::input::parse_year_str; use crate::process::{ProcessID, ProcessMap, ProcessParameter, ProcessParameterMap}; use crate::region::parse_region_str; use crate::units::{Dimensionless, MoneyPerActivity, MoneyPerCapacity, MoneyPerCapacityPerYear}; @@ -18,7 +17,6 @@ const PROCESS_PARAMETERS_FILE_NAME: &str = "process_parameters.csv"; struct ProcessParameterRaw { process_id: String, regions: String, - commission_years: String, capital_cost: MoneyPerCapacity, fixed_operating_cost: MoneyPerCapacityPerYear, variable_operating_cost: MoneyPerActivity, @@ -87,18 +85,15 @@ impl ProcessParameterRaw { pub fn read_process_parameters( model_dir: &Path, processes: &ProcessMap, - milestone_years: &[u32], ) -> Result> { let file_path = model_dir.join(PROCESS_PARAMETERS_FILE_NAME); let iter = read_csv::(&file_path)?; - read_process_parameters_from_iter(iter, processes, milestone_years) - .with_context(|| input_err_msg(&file_path)) + read_process_parameters_from_iter(iter, processes).with_context(|| input_err_msg(&file_path)) } fn read_process_parameters_from_iter( iter: I, processes: &ProcessMap, - milestone_years: &[u32], ) -> Result> where I: Iterator, @@ -108,13 +103,6 @@ where // Get process let (id, process) = processes.get_id_value(¶m_raw.process_id)?; - // Get years - let process_years: Vec = process.years.clone().collect(); - let parameter_years = parse_year_str(¶m_raw.commission_years, &process_years) - .with_context(|| { - format!("Invalid year for process {id}. Valid years are {process_years:?}") - })?; - // Get regions let process_regions = &process.regions; let parameter_regions = parse_region_str(¶m_raw.regions, process_regions) @@ -125,23 +113,20 @@ where // Insert parameter into the map let param = Arc::new(param_raw.into_parameter()?); let entry = map.entry(id.clone()).or_default(); - for year in parameter_years { - for region in parameter_regions.clone() { - try_insert(entry, &(region, year), param.clone())?; - } + for region in parameter_regions { + try_insert(entry, ®ion, param.clone())?; } } - check_process_parameters(processes, &map, milestone_years)?; + check_process_parameters(processes, &map)?; Ok(map) } -/// Check parameters cover all years and regions of the process +/// Check parameters cover all regions of the process fn check_process_parameters( processes: &ProcessMap, map: &HashMap, - milestone_years: &[u32], ) -> Result<()> { for (process_id, process) in processes { let parameters = map @@ -150,24 +135,15 @@ fn check_process_parameters( let reference_regions = &process.regions; - // Only give an error for missing parameters in milestone years, so that users are not - // obliged to supply them for every valid year before the time horizon let mut missing_keys = Vec::new(); - for year in process - .years - .clone() - .filter(|y| milestone_years.contains(y)) - { - for region in reference_regions { - let key = (region.clone(), year); - if !parameters.contains_key(&key) { - missing_keys.push(key); - } + for region in reference_regions { + if !parameters.contains_key(region) { + missing_keys.push(region.clone()); } } ensure!( missing_keys.is_empty(), - "Process {process_id} is missing parameters for the following regions and years: {}", + "Process {process_id} is missing parameters for the following regions: {}", format_items_with_cap(&missing_keys) ); } @@ -195,7 +171,6 @@ mod tests { variable_operating_cost: MoneyPerActivity(0.0), lifetime, discount_rate, - commission_years: "all".to_string(), regions: "all".to_string(), } } @@ -234,28 +209,8 @@ mod tests { ) { let mut param_map: HashMap = HashMap::new(); let process_id = processes.keys().next().unwrap().clone(); - let milestone_years: Vec = vec![2010, 2020]; - param_map.insert(process_id, process_parameter_map.clone()); - let result = check_process_parameters(&processes, ¶m_map, &milestone_years); - result.unwrap(); - } - - #[rstest] - fn check_process_parameters_ok_missing_before_base_year( - processes: ProcessMap, - mut process_parameter_map: ProcessParameterMap, - region_id: RegionID, - ) { - let mut param_map: HashMap = HashMap::new(); - let process_id = processes.keys().next().unwrap().clone(); - let milestone_years: Vec = vec![2015, 2020]; - - // Remove one entry before base_year - process_parameter_map.remove(&(region_id, 2012)).unwrap(); - param_map.insert(process_id, process_parameter_map); - - let result = check_process_parameters(&processes, ¶m_map, &milestone_years); + let result = check_process_parameters(&processes, ¶m_map); result.unwrap(); } @@ -267,17 +222,15 @@ mod tests { ) { let mut param_map: HashMap = HashMap::new(); let process_id = processes.keys().next().unwrap().clone(); - let milestone_years: Vec = vec![2010, 2020]; - - // Remove one region-year key to simulate missing parameter - process_parameter_map.remove(&(region_id, 2010)).unwrap(); + // Remove one region key to simulate missing parameter + process_parameter_map.remove(®ion_id).unwrap(); param_map.insert(process_id, process_parameter_map); - let result = check_process_parameters(&processes, ¶m_map, &milestone_years); + let result = check_process_parameters(&processes, ¶m_map); assert_error!( result, - "Process process1 is missing parameters for the following regions and years: \ - [(RegionID(\"GBR\"), 2010)]" + "Process process1 is missing parameters for the following regions: \ + [RegionID(\"GBR\")]" ); } diff --git a/src/process.rs b/src/process.rs index 8cbc38093..6659f4172 100644 --- a/src/process.rs +++ b/src/process.rs @@ -21,16 +21,16 @@ define_id_type! {ProcessID, "process ID"} /// A map of [`Process`]es, keyed by process ID pub type ProcessMap = IndexMap>; -/// A map indicating activity limits for a [`Process`] throughout the year. -pub type ProcessActivityLimitsMap = HashMap<(RegionID, u32), Arc>; +/// A map indicating activity limits for a [`Process`] by region. +pub type ProcessActivityLimitsMap = HashMap>; -/// A map of [`ProcessParameter`]s, keyed by region and year -pub type ProcessParameterMap = HashMap<(RegionID, u32), Arc>; +/// A map of [`ProcessParameter`]s, keyed by region +pub type ProcessParameterMap = HashMap>; -/// A map of process flows, keyed by region and year. +/// A map of process flows, keyed by region. /// /// The value is actually a map itself, keyed by commodity ID. -pub type ProcessFlowsMap = HashMap<(RegionID, u32), Arc>>; +pub type ProcessFlowsMap = HashMap>>; /// Map of process investment constraints, keyed by region and year pub type ProcessInvestmentConstraintsMap = @@ -73,12 +73,21 @@ pub struct Process { impl Process { /// Whether the process can be commissioned in a given year - pub fn active_for_year(&self, year: u32) -> bool { + pub fn can_be_commissioned_in_year(&self, year: u32) -> bool { self.years.contains(&year) } + + /// Whether an asset for this process could be operating in a given region and year + pub fn can_operate(&self, region_id: &RegionID, year: u32) -> bool { + let Some(parameter) = self.parameters.get(region_id) else { + return false; + }; + + year >= *self.years.start() && year <= self.years.end().saturating_add(parameter.lifetime) + } } -/// Defines the activity limits for a process in a given region and year +/// Defines the activity limits for a process in a given region /// /// Activity limits represent the minimum and maximum fraction of the potential annual activity that /// can be undertaken in each time slice, season, or the year as a whole. The limits stored and diff --git a/src/simulation.rs b/src/simulation.rs index 030c7d3ad..0e68fcc96 100644 --- a/src/simulation.rs +++ b/src/simulation.rs @@ -252,7 +252,7 @@ fn candidate_assets_for_next_year( for process in processes .values() - .filter(move |process| process.active_for_year(next_year)) + .filter(move |process| process.can_be_commissioned_in_year(next_year)) { for region_id in &process.regions { candidates.push( diff --git a/src/simulation/prices.rs b/src/simulation/prices.rs index 72a01273c..1ff2922b0 100644 --- a/src/simulation/prices.rs +++ b/src/simulation/prices.rs @@ -1370,7 +1370,6 @@ mod tests { fn build_process( flows: IndexMap, region_id: &RegionID, - year: u32, time_slice_info: &TimeSliceInfo, variable_operating_cost: MoneyPerActivity, fixed_operating_cost: MoneyPerCapacityPerYear, @@ -1379,7 +1378,7 @@ mod tests { discount_rate: Dimensionless, ) -> Process { let mut process_flows_map = HashMap::new(); - process_flows_map.insert((region_id.clone(), year), Arc::new(flows)); + process_flows_map.insert(region_id.clone(), Arc::new(flows)); let mut process_parameter_map = HashMap::new(); let proc_param = ProcessParameter { @@ -1389,11 +1388,11 @@ mod tests { lifetime, discount_rate, }; - process_parameter_map.insert((region_id.clone(), year), Arc::new(proc_param)); + process_parameter_map.insert(region_id.clone(), Arc::new(proc_param)); let mut activity_limits_map = HashMap::new(); activity_limits_map.insert( - (region_id.clone(), year), + region_id.clone(), Arc::new(ActivityLimits::new_with_full_availability(time_slice_info)), ); @@ -1507,7 +1506,6 @@ mod tests { let process = build_process( flows, ®ion_id, - 2015u32, &time_slice_info, MoneyPerActivity(5.0), // variable operating cost MoneyPerCapacityPerYear(0.0), // fixed operating cost @@ -1589,7 +1587,6 @@ mod tests { let process = build_process( flows, ®ion_id, - 2015u32, &time_slice_info, MoneyPerActivity(5.0), // variable operating cost MoneyPerCapacityPerYear(1.0), // fixed operating cost