Policy - New Renewable Generation
Developing a Policy to Build New Renewable Generation
The Electric_NRCan_SmallCommunities.jl file implements a policy for renewable energy projects. This policy adds exogenous, new capacity for various renewable generation projects (primarily solar PV, hydro, and wind) across Canadian provinces starting in 2025, simulating government-funded or incentivized clean energy deployments in remote or small communities. The additions are applied to "new" plant types (e.g., AB_New_SolarPV for Alberta's new solar PV capacity).
|
|
🛈 |
Note on Unit Creation: New units initiated in policy files (e.g. "AB_New_SolarPV" or "BC_New_Hydro") are pre-created during the model calibration process in RunModel.bat via the UnitCreate_ForPolicies.jl script. This calibration step establishes placeholder units for various plant types across provinces, making them available for policies to add capacity without creating new units dynamically. This ensures consistency and avoids runtime unit creation complexities. |
Variables Changed
The policy modifies the following input variables to add new renewable capacity:
- UnOnLine[unit]: On-line date for units (year).
- Updated to the earliest of the current on-line date or the new capacity addition year (2025 for all additions).
- xUnGCCI[unit, year]: Exogenous generating capacity initiated (kW, scaled by 1000 for MW).
- Set for units added with construction delays, representing capacity starts in the year when construction begins (adjusted for plant-specific delays).
- xUnGCCR[unit, year]: Exogenous generating capacity completion rate (kW, scaled by 1000 for MW).
- Set for units that come online immediately or within the first few years, representing completed capacity additions.
Specific additions in 2025 are made (all in kW, converted to MW in processing), such as:
- AB_New_SolarPV: 2353 kW Solar PV
- BC_New_SolarPV: 2470 kW Solar PV
- ...
- NU_New_OnshoreWind: 1000 kW Onshore Wind
- QC_New_Hydro: 7500 kW Hydro
These changes are written back to the database (EGInput/UnOnLine, EGInput/xUnGCCI, EGInput/xUnGCCR) and represent exogenous investments in renewables for small communities.
Cascading Impact Through the Model Engine Files
The capacity additions propagate through the model's engine components, affecting capacity planning, costs, prices, and generation. Policies are applied early, altering inputs before optimization. The impacts cascade as follows:
- Capacity Expansion (ECapacityExpansion.jl):
- New units increase total available capacity, particularly renewables, which may reduce the need for new fossil fuel builds.
- Affects unit selection and expansion decisions, potentially lowering emissions and shifting the generation mix toward cleaner sources.
- Costs (ECosts.jl):
- New renewable units have low variable costs (near-zero fuel costs), impacting average fixed costs (UnAFC), net assets (UnNA), and depreciation (UnSLDPR).
- Reduces overall system costs by displacing higher-cost generation, affecting fuel price calculations (ECFPFuel, ECFPMonth) and pollution costs.
- Dispatch and Generation (EDispatchCg.jl):
- Additional renewable capacity increases available units for dispatch, prioritizing low-cost renewables in merit order.
- May reduce reliance on fossil fuels, lowering emissions and operational costs.
- Fuel Usage (EFuelUsage.jl):
- New renewables reduce fossil fuel consumption, affecting fuel fraction allocations (UnFlFr) and overall energy balances.
- Retail Power Costs (ERetailPowerCosts.jl):
- Increased renewable generation lowers spot market prices (HMPr, HMPrA), contract costs (UVCost, UCCost), and purchased power costs (PUCT, PPUC).
- Reduces variable and capacity costs (DVCost, DCCost), benefiting consumers through lower electricity prices.
- Electric Prices (ElectricPrice.jl):
- Lower generation costs reduce marketer prices (PE), calculated prices (PECalc), and retail prices for demand sectors.
- Affects revenues (ECCRV), non-power costs, and stranded costs, potentially improving affordability and reducing subsidies needed.
- Broader Model Effects:
- Emissions: Increases renewable share, reducing CO2 and pollutants, supporting decarbonization goals.
- Costs: Lowers total system costs by adding cheap renewables, though initial capital investments are exogenous.
- Energy Security and Reliability: Enhances diversity with distributed renewables, improving resilience in small communities.
- Inter-Sectoral Impacts: Cheaper electricity may boost industrial, residential, and commercial demands, influencing market shares for electric technologies.
- Outputs: Alters generation profiles, cost breakdowns, and price signals in reporting files (e.g., Output/), showing increased renewables penetration and lower prices.
Overall, this policy simulates targeted renewable investments, accelerating the energy transition in under-served areas while reducing costs and emissions. The model optimizes around these additions, ensuring supply reliability and quantifying economic benefits.
Policy File Mechanics
Overall Structure and Flow
- Data Loading and Struct: The EControl struct loads various datasets from a database (e.g., areas, generators, nodes, plants, units, years) using ReadDisk. These are stored as arrays or vectors for efficient lookup. Key variables include unit attributes (e.g., UnArea for unit-to-area mapping) and capacity-related arrays like xUnGCCI (Generating Capacity Initiated, in kW) and xUnGCCR (Exogenous Generating Capacity Completion Rate, in kW).
- Policy Execution: The ElecPolicy function is the main entry point. It calls AddCapacity multiple times to add specific capacities (in MW) to predefined units (e.g., AB_New_SolarPV gets 2353 MW added in 2025). After updates, it writes the modified data back to the database using WriteDisk.
- Execution: The PolicyControl function wraps ElecPolicy and is called if the file is run directly (via if abspath(PROGRAM_FILE) == @__FILE__).
- Key Concepts:
- Units are indexed (e.g., unit = 1 for the first unit), and mappings use string keys (e.g., UnCode is an array of unit codes like "AB_New_SolarPV").
- Select is a utility function (imported from EnergyModel) that finds the index of a string in an array (e.g., Select(Plant, "SolarPV") returns the index of "SolarPV" in the Plant array).
- Time-related constants like HisTime (historical time horizon), ITime (initial time), and Zero (likely an index for the first year) are used for year calculations.
- Capacity additions are in MW but stored in kW (hence the /1000 division).
1) What Does the GetUnitSets Function Do and Why Is It Needed?
The GetUnitSets function is a helper that translates a unit's string-based attributes (stored in arrays like UnArea, UnGenCo) into their corresponding integer indices in the global sets (e.g., Area, GenCo). This is essential for array indexing in Julia, where you can't directly use strings as indices - you need numeric positions.
- Inputs:
- data: The EControl struct containing all loaded data.
- unit: An integer index for a specific unit (e.g., unit = 5 for the 5th unit in the UnCode array).
- What It Does:
- It extracts the relevant global sets from data: Area (list of areas), GenCo (generating companies), Node (transmission nodes), and Plant (plant types).
- It also extracts unit-specific mappings: UnArea[unit] (the area string for this unit), UnGenCo[unit] (the GenCo string), etc.
- For each mapping, it calls Select to find the index:
- plant = Select(Plant,UnPlant[unit]): Finds the index of the plant type (e.g., "SolarPV") in the Plant array.
- Similarly for node, genco, and area.
- It returns the four indices: (plant, node, genco, area).
- Why Is It Needed?
- Indexing Requirement: Julia arrays are 1-based and use integers for indexing. The raw data from the database stores relationships as strings (e.g., a unit belongs to "Alberta" area), but to access multi-dimensional arrays like CD[plant,year] (Construction Delay for a plant in a year), you need numeric indices.
- Decoupling: It separates the logic of mapping strings to indices from the capacity addition logic, making the code modular and reusable. Without it, every function using unit attributes would need to repeat the Select calls.
- Performance: Pre-computing indices avoids repeated string lookups during policy execution.
- Example: If unit = 1 corresponds to "AB_New_SolarPV", and UnPlant[1] = "SolarPV", then plant might be 3 (the position of "SolarPV" in the Plant array). This allows accessing CD[3,some_year].
In short, it's a translation layer from human-readable strings to machine-readable indices, ensuring the model can efficiently reference related data.
2) What Is the AddCapacity Function Doing?
The AddCapacity function simulates adding new generating capacity to a specific unit in a given year. It handles the mechanics of capacity expansion, including construction delays and online timing. This is core to the policy, as it directly implements the NRCan-mandated additions.
- Inputs:
- data: The EControl struct.
- UCode: A string unit code (e.g., "AB_New_SolarPV").
- LocYear: The year the capacity comes online (e.g., 2025).
- CapAdd: The capacity to add in MW (e.g., 2353 for Alberta solar).
- What It Does (Step-by-Step):
- Find the Unit Index: unit = Select(UnCode,UCode) converts the string code to an integer index (e.g., "AB_New_SolarPV" → unit = 5).
- Get Related Indices: Calls GetUnitSets(data,unit) to get plant, node, genco, and area indices. These aren't directly used here but are returned for potential future use (the function could be extended).
- Update Online Year: UnOnLine[unit] = min(UnOnLine[unit],LocYear) ensures the unit's online date is set to the earliest possible year (e.g., if it was planned for 2030 but we're adding capacity in 2025, it moves to 2025).
- Handle Construction Delay:
- Checks if there's time to simulate construction: if LocYear-CD[plant,Zero] > (HisTime+1).
- CD[plant,Zero] is the construction delay (in years) for that plant type (e.g., solar might have a 1-year delay).
- HisTime is the last historical year.
- If true (enough time for construction), it calculates the initiation year: Loc1 = LocYear-CD[plant,Zero]-ITime+1, converts to an integer year, and adds to xUnGCCI[unit,year] (capacity initiated in that year, in kW). This simulates starting construction earlier.
- If false (comes online too soon), it adds directly to xUnGCCR[unit,year] (completion rate in the online year), bypassing construction simulation.
- CapAdd/1000 converts MW to kW for storage.
- No Return: It modifies the arrays in-place and returns nothing.
- Why This Logic?
- Realism: Construction delays ensure capacity isn't added instantaneously. For example, a hydro plant might take years to build, so initiation is tracked separately from completion.
- Model Integration: xUnGCCI and xUnGCCR feed into the broader energy model for capacity planning and simulation.
- Units: MW input but kW storage aligns with the database schema (as noted in comments).
- Example from Policy: AddCapacity(data,"AB_New_SolarPV",2025,2353) adds 2353 MW (2.353 GW) of solar PV capacity in Alberta for 2025. If solar has a 1-year delay and HisTime is 2023, it might initiate in 2024 and complete in 2025.
Final Notes
- Validation: After running, the policy writes back to the database, ensuring changes persist for the model run.
- Assumptions: This assumes familiarity with the energy model's conventions (e.g., Zero as year index 1). See Global Time Constants if you need to deeper dive into constants like HisTime.
- Testing/Running: To see it in action, you'd run the file in the Julia environment with the appropriate DB path set.
If this doesn't fully clarify things or you have follow-up questions (e.g., about specific variables or how it integrates with the larger model), provide more details!