C2070 Delta E+Delta E+ · SFU

CLEWs · NRCan · OSeCAN v0.3

Model

OSeCAN model sheet v10 Last updated 17 October 2026

1
region
13
zones
9
provinces
2021–70
horizon
522
technologies
379
fuels

This page explains how C2070 is built: how Canada is divided, how a run goes from data to an answer, which technologies the model can choose, how it links to the forestry and farm models, and where each piece sits in the code. Pick a block below to open it.

How OSeMOSYS decides

Five rules behind every result

Gas supplyMINNGS Gas Gas power plantPWRNGSin 2.70 → out 1.00 Electricity Demand ≥ ≥ capacity of PWRNGS 2021 existing fleet retiring new builds activity ceiling = capacity × CF total discounted cost capitalfixed variableCO₂

Schematic, not model output.

OSeMOSYS region

One Region

The OSeMOSYS REGION set has a single member, CAN. Every parameter OSeMOSYS indexes by region — discount rate, carbon price, emission limit, reserve margin — is set once for the whole country.

Geography lives in the names instead. A two-character zone code closes every technology and fuel name (PWRHYDB2, ELCB202) and is read back by taking the last two characters. Thirteen zones cover nine provinces; the four largest provinces have two zones each.

BCB1 South Coast · B2 Interior & North
ABA1 Calgary–Edmonton · A2 North (oil sands)
ONO1 Southern · O2 Northern
QCQ1 Southern · Q2 Northern (hydro)
SK · MB · NLS1 · M1 · L1
ATT1 — NB + NS + PE aggregated
NON1 — YT + NT + NU aggregated, no grid links

A province is a grouping used for policy and reporting only. The solver never sees it; zones_of(province) resolves it to zones when constraints are built.

End-use demand

Demand

Demand comes from CER Energy Futures 2026 end-use demand: provincial totals for four sectors (IND, RES, COM, TRA) and their fuels. clean_demand.py maps the thirteen CER regions onto the nine model provinces, then onto zones. National all-fuel demand is 9,328 PJ in 2025, of which electricity is about 2,040 PJ.

How the zone demand share is created

Each provincial total is split across that province's zones by a fixed fraction, ZoneDemandShare in model_structure.py. The fractions are the modeller's estimates of where population and load centres sit, not measurements, and they must sum to 1 within each province. Every sector and fuel in a province uses the same split.

This split, not the CER total, decides where transmission has to deliver and where new generation is worth building.

Reference energy system

What sits inside a zone

The same set of components repeats in each of the 13 zones. Boxes are technologies: they have capacity, cost and activity, and the model decides how much of each to build and run. Rounded tags are fuels: each must balance every year. Hover over any component to bring it forward, or press its i for a plain-language note.

SUPPLYPOWER PLANTS GENERATION BUSDISTRIBUTION GRID BUSEND-USE DEMAND Renewable resources RNW‹f›‹z› · per zone Fossil & uranium supply MIN‹f› · IMP‹f› · national Biomass feedstock SUP‹fs› · WS3 / FABLE Power plants 10 plant types × 13 zones PWR‹arch›‹z› · 130 gas · coal · nuclear · biomass hydro · wind · solar · geothermal switchgrass · woody residue the decision space Generation bus ELC‹z›01 Distribution PWRTRN‹z› · 9.9% loss RE-share meter (UDC) Grid bus ELC‹z›02 Electricity end use DEM‹s›ELC‹z› · 52 Demand ‹s›ELC‹z› · CER Interconnector TRN‹z›‹z′› · 5% loss 24 = 12 links × 2 directions Neighbouring zone ELC‹z′›02 · its grid bus National fuel supply MIN · IMP · RNWBIO · HDG Fuels gas · diesel · gasoline · jet fuel · oil products · biomass · hydrogen no plant, no bus — delivered straight to end use Fuel end use DEM‹s›‹f›‹z› · 234 Demand ‹s›‹f›‹z› · CER technology — capacity, cost, activity fuel — balanced every year another zone, or an external model <z> zone · <s> sector · <f> fuel · <arch> plant type
Electricity takes the upper path: supply, plant, generation bus, distribution, grid bus, end use. Every other fuel takes the lower path and skips the buses. Only TRN crosses from one zone's copy of this diagram to another's. The territories (N1) have no TRN link.

Trade-offs

Subnational Modelling Choices

One region with zones in the names, instead of one OSeMOSYS region per province. What that buys, and what it costs:

Gained

5
  1. Transmission the model can invest in. Each link is a technology with a 5% loss, a capital cost, a 50-year life and a rating, so the model decides whether to build more. Stock OSeMOSYS Trade has none of these: it is free, lossless, unbounded and cannot be built.
  2. Policy groups that need not match the map. A UDC sums over any set of technologies, so Alberta (two zones) and Atlantic (three provinces in one zone) are equally easy to constrain. A region partition fixes one grouping and fights every other.
  3. National constraints are native. With one region, a Canada-wide emission cap is a single AnnualEmissionLimit row, and the discount rate and carbon price are set once, so they cannot drift apart between provinces. With nine regions, stock OSeMOSYS has no constraint that adds emissions across regions, so a national cap would itself need a custom constraint. The gain is consistency and what can be written natively, not a smaller problem for the solver.
  4. A technology exists only where it is named. In a multi-region model every technology is declared in every region and unwanted ones are switched off with zero bounds. Here only the 24 links that physically exist are declared, out of 156 possible ordered zone pairs. The FAST formulation already skips combinations with no activity ratio, so the LP shrinks only modestly; the gain is an input set that lists what exists and nothing else. Not yet used for plants: every plant type is generated in every zone, geothermal in Nunavut included.
  5. The names build the policy groups. build_params.py collects a province's technologies by composing names — PWRTRN{z} and PWR{arch}{z} for each zone in zones_of(province) — so there is no membership table to maintain. Move a zone into a province and every policy for that province includes it on the next build.

Lost or compromised

6
  1. No structural check on the wiring. With regions, an Alberta plant cannot feed an Ontario bus. Here ELCO101 and ELCA101 differ by one character, and the typo gives a feasible, plausible, wrong answer. harmonize.py checks that codes exist, not that a technology's input and output share a zone.
  2. Parsing is load-bearing. ZONE_WIDTH = 2 is assumed by every slice; a three-character zone breaks the cleaners and plots without an error.
  3. One value for everything indexed by region. A province-specific discount rate, reserve margin or carbon price cannot be set without a UDC.
  4. Provincial limits are not first-class. AnnualEmissionLimit and REMinProductionTarget now apply to Canada as a whole, so provincial caps and targets had to be rebuilt as UDC, which needed a patch to the model file.
  5. UDC is non-standard. The block in osemosys_fast_osecan.txt is our addition, reconstructed from the MUIO documentation. It does not ship with stock OSeMOSYS.
  6. Provincial results are string sums, recovered by slicing names, not part of the solution's structure.

Policy lever

User Defined Constraints

Because a province is a group of technologies rather than a region, each provincial policy is one weighted sum over that group:

Σt ( mactt·Activityt + mcapt·TotalCapacityt + mnewt·NewCapacityt ) ≤ UDCConstant

PolicyMultipliers written by build_params
Renewable share
at least share s of a province's generation
+1.11·s on each PWRTRN<z> in the province, −1 on each renewable plant, constant 0. A 50% Alberta target gives PWRTRNA1 = +0.555 and each Alberta renewable −1.
Emission cap
at most L Mt CO₂ a year
Each emitting plant's factor in Mt/PJ, constant L.

The same mechanism reaches, without new model code: capacity floors and ceilings by technology class (TotalCapacity multiplier), phase-out dates, build-rate limits (NewCapacity), minimum intertie use, and a cap on biomass feedstock in chosen zones. Adding one takes a key in ProvincialPolicies, a branch in the UDC loop and a rebuild.

How the names build each group

# build_params.py — renewable share for province p
for z in zones_of(p):                 # "AB" → A1, A2
    mult[f"PWRTRN{z}"] = +1.11 * s
    for arch in RenewableArchetypes:  # HYD WND SOL BIO GEO
        mult[f"PWR{arch}{z}"] = -1

No lookup table says which technologies belong to Alberta; the zone suffix does. This is the practical payoff of the naming convention for policy work.

Check before the first policy run. RenewableArchetypes does not include BSW or BCW, so electricity from the WS3 and FABLE biomass plants does not count toward a renewable share, while generic BIO does. It also excludes nuclear, which most Canadian clean or non-emitting targets include.

Keep UDC_TAG_INEQUALITY = 0. An equality on a renewable share forbids over-achievement and turns a floor into a ceiling.

Status. ProvincialPolicies is empty, so no policy is loaded. The mechanism is verified binding: a stress test held PWRHYDA1 at exactly 100.0 PJ in every year. A UDC that does not bind looks identical to a broken one, so diff every policy run against base.