SESIT: Industry carbon tax#94
Conversation
…/macromodel/macroabm-ca into sesit_carbon_tax
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Module and compute_obps docstrings claimed max(0, emissions - limit) but the implementation computes the signed difference with no clamp — sectors below their benchmark receive a rebate (negative cost). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers OutputBasedPriceSystemCAN (positive cost, negative rebate, tightening rate, reference accumulation, disabled/pre-2019 guards) and OBPSCANReader (CSV loading, missing files, optional elec file). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Before I read in detail -- are you sure the negative OBPS case flows smoothly? That there are no leaks, etc? |
jose-moran
left a comment
There was a problem hiding this comment.
minor comments for the moment, I'll check in more detail later
| """ | ||
| self.extra_marginal_taxes_firm = np.zeros(self.firms.n_industries) | ||
|
|
||
| if self.use_obps_reg and self.obps is not None: |
There was a problem hiding this comment.
should you not throw an error or a warning if this function is called but these things are not set?
There was a problem hiding this comment.
Added a logging.warning when use_obps_reg=True but self.obps is None, so misconfiguration is visible at runtime instead of silently doing nothing.
| emission_limit: np.ndarray | ||
| price: np.ndarray | ||
| current_t: int = 0 | ||
| current_year: int = 2014 |
There was a problem hiding this comment.
Renamed reference_emission_intensity → baseline_emission_intensity to make clear it's the fixed 2017–2019 historical baseline, not a current value. Also added initial_year as an explicit field so reset() and the price-loading loop no longer hardcode 2014.
| self.industries = industries | ||
|
|
||
| # Industries regulated under OBPS (federal schedule) | ||
| self.regulated_industries = [ |
There was a problem hiding this comment.
is there a check for the case where you are running this with other industries and you still are using this obps? An error should be thrown I think
There was a problem hiding this comment.
Added two warnings on init:
If some regulated industries are missing from the model's industry list → logs which ones were skipped
If none of the regulated industries match at all → logs that OBPS will have no effect
…tion - Warn when use_obps_reg=True but no OBPS object is set (country.py) - Floor extra_marginal_taxes_firm at -good_prices so a negative rebate cannot push the effective sector price below zero (country.py) - Rename reference_emission_intensity -> baseline_emission_intensity to make clear the value is fixed from the 2017-2019 reference period - Add initial_year field so reset() and price-loading no longer hardcode 2014; current_year remains the mutable simulation state - Warn on init when regulated industries are absent from the model's industry set, with details on which codes were skipped - Update tests to reflect the baseline_emission_intensity rename
Made this addition: |
The post-2022 tightening formula (B - B * tightening_rate * (self.current_year - 2022) becomes negativewhere B = reduction_factor * self.baseline_emission_intensity[industry_idx]once tightening accumulates past the baseline, causing the allowable emission limit to go below zero (~2042 onward for a 2% annual rate). Clamp the returned limit to max(0, ...) so regulated sectors never receive a pathologically negative allowance. Add a test covering the far-future tightening case.
feat: add Output-Based Pricing System (OBPS) (Example Canada Implementation)
Implements an industry-level carbon price signal based on each sector's
emissions relative to an output-based benchmark, as used in Canada's
federal OBPS regulation.
How it works
For each regulated sector i at time t (from 2019 onwards):
limit[i] = production[i] * reduction_factor[i] * reference_intensity[i]
obps_cost[i] = (input_emissions[i] + capital_emissions[i] - limit[i])
* carbon_price[t]
Note here that the cost can be negative: sectors emitting below their benchmark receive
a rebate that lowers their effective price, while sectors above the
benchmark face a positive marginal cost. Dividing by production gives a
per-unit marginal tax passed to price-setting and input-demand logic:
extra_marginal_taxes_firm[i] = obps_cost[i] / production[i]
The emission benchmark is output-based: the limit scales with production
so firms are not penalised for growing, only for exceeding the sector
intensity standard. Reference emission intensities are accumulated from
2017–2019 model periods. Post-2022 a tightening rate gradually lowers
the benchmark.
New components
macromodel/policy/output_based_price_system_can.py
OutputBasedPriceSystemCAN — computes sectoral OBPS cost each
period; accumulates 2017–2019 reference emissions to set
industry-specific intensity benchmarks.
macro_data/readers/policy_data/obps_can_reader.py
OBPSCANReader — reads industry reduction factors, tightening rates,
and the carbon price schedule; produces an OBPSCANData container.
Wiring
period when use_obps_reg is True; called from the planning phase so
the signal feeds into price-setting and input demand.
extra_marginal_taxes to shift the effective sector price.
Relevant tests have also been added.