Conversation
lecfab
left a comment
There was a problem hiding this comment.
Thanks for all the dusting and testing that it takes to revive the permit trading!
I left comments here and there, mainly about aesthetics and documentation.
| cm_pemittraderatio "Percentage of restricted permit trading" | ||
| ; | ||
| cm_pemittraderatio = 0.2; !! def = 0.2 | ||
| *' |
There was a problem hiding this comment.
I find the existing names of "permit trades scenarios" a little confusing, in the sense that we would always need to refer to the main.gms to understand what they mean. The big undertaking of reactivating permit trade is possibly a good opportunity to simplify that. Two suggestions (either/or):
- simpler one: rename the "no permit trade" to
cm_permittradescen = 0 - nicer one: remove
cm_permittradescen, because it is redundant withcm_pemittraderatio(0% for no trade, 100% for full trade, and whatever in-between for limited trade)
There was a problem hiding this comment.
In general, I agree! The switch cm_permittradescen was already there and is used in other 41 realizations like PerCapitaConvergence and AbilityToPay. Changing this would require some refactoring there, and currently I don't fully understand the other realizations.
For not having to look into the main.gms I copied it also into the realization
| loop(ttot$(ttot.val ge 2005), | ||
| ***this is a 30$/tCo2eq in 2020 trajectory: | ||
| pm_pvp(ttot,"perm") = 0.11*1.05**(ttot.val-2020) * pm_pvp(ttot,"good"); | ||
| pm_pvp(ttot,"perm") = 0.18*1.05**(ttot.val-2020) * pm_pvp(ttot,"good"); |
There was a problem hiding this comment.
what does this change mean? can you add a comment explaining this number and the whole formula?
Is there a parameter that we can use instead of hard-coded 1.05 @fpiontek @johanneskoch94? I believe in some other places it was decided to use 1.045 instead (but I can't find where, pm_prtp is usually only 1.015)
There was a problem hiding this comment.
Isn't it just the initial guess for the permit price trajectory if no permit price exists in the input gdx?
Shouldn't we adjust the comment above to indicate " ~49$/tCO2eq" instead?
It is an increase in price level expected to reflect that we are moving more towards overshoot scenarios with end of period prices expected to be high?
There was a problem hiding this comment.
Exactly! I increased the start of carbon price from 30$ to 49$. Now I also adjusted the description!
| *' * (2): no permit trade (only domestic mitigation) | ||
| *' * (3): limited trade (certain percentage of regional allowances) | ||
| *' for limited trade use cm_pemittradefinalyr to set the final year until permit trading is allowed | ||
| *' with cm_pemittraderatio set the percentage of allowed trade |
There was a problem hiding this comment.
see comment for main.gms
https://github.com/remindmodel/remind/pull/2285/changes#r2799231804
| vm_Mport.up(t,regi,"perm")$(t.val gt cm_pemittradefinalyr)=0; | ||
| vm_Mport.up(t,regi,"perm")$(t.val le 2025)=0; | ||
| vm_Mport.lo(t,regi,"perm") = 0; | ||
| ); |
There was a problem hiding this comment.
What happens when cm_permittradescen is 1?
Also, should the cap be defined by gross emissions rather than abs(p41_co2eq(t,regi))?
Visual suggestions:
- disactivate
+ deactivate
- cm_pemittradefinalyr cm_pemittraderatio
+ cm_permitTradeFinalYr cm_permitTradeRatio
- ...)=0
+ ...) = 0
- vm_Xport.up(t,regi,"perm")$(t.val le cm_pemittradefinalyr)=abs(cm_pemittraderatio*(p41_co2eq(t,regi)));
- vm_Xport.up(t,regi,"perm")$(t.val gt cm_pemittradefinalyr)=0;
- vm_Xport.up(t,regi,"perm")$(t.val le 2025)=0;
- vm_Xport.lo(t,regi,"perm") = 0;
+ vm_Xport.fx(t,regi,"perm") = 0;
+ vm_Xport.up(t,regi,"perm") $ (t.val > 2025 and t.val <= cm_permitTradeFinalYr) = cm_permitTradeRatio * abs(p41_co2eq(t,regi));There was a problem hiding this comment.
What happens when cm_permittradescen is 1?
Then the reference run is simulated with allocation of emission allowances!
Also, should the cap be defined by gross emissions rather than
abs(p41_co2eq(t,regi))?
Why? The initial idea is to first simulate the reference scenario by allocating the CO2 emission allowances. Other emissions are not covered in the permits.
Visual suggestions:
- disactivate + deactivate - cm_pemittradefinalyr cm_pemittraderatio + cm_pemitTradeFinalYr cm_pemitTradeRatio - ...)=0 + ...) = 0 - vm_Xport.up(t,regi,"perm")$(t.val le cm_pemittradefinalyr)=abs(cm_pemittraderatio*(p41_co2eq(t,regi))); - vm_Xport.up(t,regi,"perm")$(t.val gt cm_pemittradefinalyr)=0; - vm_Xport.up(t,regi,"perm")$(t.val le 2025)=0; - vm_Xport.lo(t,regi,"perm") = 0; + vm_Xport.fx(t,regi,"perm") = 0; + vm_Xport.up(t,regi,"perm") $ (t.val > 2025 and t.val <= cm_pemitTradeFinalYr) = cm_pemitTradeRatio * abs(p41_co2eq(t,regi));
There was a problem hiding this comment.
Thanks for the nice reformulation; I adjusted accordingly!
| *** initialization of pm_shPermit | ||
| pm_shPerm(t,regi) = p41_co2eq(t,regi)/sum(regi2, p41_co2eq(t,regi2)); | ||
| pm_emicapglob(t) = sum(regi, p41_co2eq(t,regi)); | ||
|
|
There was a problem hiding this comment.
Can you give more explanations about what is loaded, and what the ref and bau runs will mean in the calculation of permit allowances?
Also, I'm a bit worried about the shPerm values when a region has negative emissions (in which case I suppose p41_co2eq is negative).
Visual suggestion to avoid calculating the same thing twice:
- pm_shPerm(t,regi) = p41_co2eq(t,regi)/sum(regi2, p41_co2eq(t,regi2));
- pm_emicapglob(t) = sum(regi, p41_co2eq(t,regi));
+ pm_emicapglob(t) = sum(regi, p41_co2eq(t,regi));
+ pm_shPerm(t,regi) = p41_co2eq(t,regi) / pm_emicapglob(t);There was a problem hiding this comment.
Good point! The BAU is not used. I removed it and provided some explanation for the loaded CO2 emissions. The first goal is to replicate the reference run, and in a second stage, add trade to it ;)
Also, the code refreshment is well received and adjusted!
There was a problem hiding this comment.
Regarding the negative values, so far I would say it works. I was able to replicate the reference run see this issue and I have negative values in the p41_co2eq! But I'm also a bit worried when it comes to more extreme scenarios like the ECPC allocation!
benjaminpeeters
left a comment
There was a problem hiding this comment.
Hey @RahelMA
Thanks for the PR.
Added just a few comments from my side, notably on the changes for etaSL.
Not sure this is useful. Hope it is.
I just have another (small) question:
why cm_pemittradefinalyr and cm_pemittraderatio instead of cm_permittradefinalyr and cm_permittraderatio (with an 'r' in 'permit')?
| p80_etaST(tradePe) = 0.3; | ||
| p80_etaST("good") = 0.25; | ||
| p80_etaST("perm") = 0.3; | ||
| p80_etaST("perm") = 0.8; |
There was a problem hiding this comment.
As far as I understand these p.._etaST/LT haven't been changed since the codebase was open-sourced in December 2019 with the explicit indication "These parameters are pretty sensitive" :)
The permit market also does not "benefit" from the adaptive convergence boosting mechanism (the 4x/8x/16x escalation after iterations 15/20/25) -- only applies to tradePe and good if I'm not wrong (see https://github.com/remindmodel/remind/blob/develop/modules/80_optimization/nash/postsolve.gms#L139C1-L173C19).
I wonder:
- what motivate to such a big shift (from 0.3 to 0.8)?
- why
p80_etaST("pebiolc") = 0.8;was already fixed to 0.8 back then? @LaviniaBaumstark - have we try the "adaptative boosting"? wouldn't it be more consistent with the rest of the code? Are there good reasons to treat "perm" differently in that regard (e.g., much smaller fraction of the world exchanges wrt "good"? "pebiolc" does seem to have this boost as well)
- don't we need to be even more strict on
p80_etaSAfor "perm" given the fact that the inter iteration adjustment cost parameter is much lower than for other goods (https://github.com/remindmodel/remind/blob/develop/modules/80_optimization/nash/datainput.gms#L45-L48) and therefore has more risk of oscillation? (not sure) Likewise, wouldn't it lead to better convergence (less oscillation while adjustment) to decrease the adjustment cost even further to not be so aggressive aboutp80_etaST? In other words, instead of forcing the market with heavy-handed price corrections (high etaST), why not make the planners more responsive to prices (lower etaAdj) so the market clears more?
There was a problem hiding this comment.
In general, I had the issue that the surplus was too high and the permit price did not change enough to motivate adjustments. I did not have the issue of oscillation in my runs.
| *** | AGPL-3.0, you are granted additional permissions described in the | ||
| *** | REMIND License Exception, version 1.0 (see LICENSE file). | ||
| *** | Contact: remind@pik-potsdam.de | ||
| *** SOF ./modules/41_emicapregi/AbilityToPay/bounds.gms |
There was a problem hiding this comment.
new files reference AbilityToPay instead of TradingOnRef
| p41_co2eq(t, regi) = p41_co2eq_in(t,regi,"co2"); | ||
|
|
There was a problem hiding this comment.
Is it used anywhere or come from the copy paste from AbilityToPay?
There was a problem hiding this comment.
No, I first load the entire emission as intermediate step p41_co2eq_in = vm_emiAll.l and in a second step only use the CO2 emissions to allocate the permits: p41_co2eq(t, regi) = p41_co2eq_in(t,regi,"co2");
| p80_surplusMaxTolerance(tradePe) = 2* 1.5 * sm_EJ_2_TWa; !! convert EJ/yr into internal unit TWa | ||
| p80_surplusMaxTolerance("good") = 2* 100/1000; !! in internal unit, trillion Dollar | ||
| p80_surplusMaxTolerance("perm") = 2* 300 * 12/44 / 1000; !! convert MtCO2eq into internal unit GtC | ||
| p80_surplusMaxTolerance("perm") = 3* 300 * 12/44 / 1000; !! convert MtCO2eq into internal unit GtC |
There was a problem hiding this comment.
What is the actual relative residual permit surplus at convergence in the test runs? Could you share the p80_surplusMaxRel values? do the iterations converge below the tolerance, or does it tend to stay blocked close to it?
There was a problem hiding this comment.
So an edge case is e.g. if trade is only allowed for a short period in EC650-PC-Trade20-2060 here the surplus goes to 2060 perm 63 -0.24 this was not converging with 2* 300 * 12/44 / 1000= 0.16 now the tolerance is 0.245.
/p/tmp/rahelma/Committed/Equity/permTrading/output/EC650-PC-Trade20-2060_2026-02-06_15.10.31 (trading)$ dumpgdx fulldata.gdx p80_surplusMaxRel perm,63,
perm 63 2030 0.388459318122543
perm 63 2035 0.388459318122543
perm 63 2040 0.388459318122543
perm 63 2045 0.388459318122543
perm 63 2050 0.388459318122543
perm 63 2055 0.388459318122543
perm 63 2060 1.94757151310855
perm 63 2070 1.94757151310855
perm 63 2080 1.94757151310855
perm 63 2090 1.94757151310855
perm 63 2100 1.94757151310855
perm 63 2110 1.94757151310855
perm 63 2130 1.94757151310855
perm 63 2150 1.94757151310855
| loop(ttot$(ttot.val ge 2005), | ||
| ***this is a 30$/tCo2eq in 2020 trajectory: | ||
| pm_pvp(ttot,"perm") = 0.11*1.05**(ttot.val-2020) * pm_pvp(ttot,"good"); | ||
| pm_pvp(ttot,"perm") = 0.18*1.05**(ttot.val-2020) * pm_pvp(ttot,"good"); |
There was a problem hiding this comment.
Isn't it just the initial guess for the permit price trajectory if no permit price exists in the input gdx?
Shouldn't we adjust the comment above to indicate " ~49$/tCO2eq" instead?
It is an increase in price level expected to reflect that we are moving more towards overshoot scenarios with end of period prices expected to be high?
Purpose of this PR
This PR tries to reactivate permit trading ;)
With the new realization
TradingOnRefin41_emicapregi, a reference run is used to allocate emission allowances for each region and timestep.Overall, there are three different types of permit trading:
(1): full permit trade (no restrictions)
(2): no permit trade (only domestic mitigation)
(3): limited trade (certain percentage of regional allowances)
for limited trade use
cm_pemittradefinalyrto set the final year until permit trading is allowedwith
cm_pemittraderatioset the percentage of allowed tradeIn these exemplary runs, I used 20% of permit allocation until 2060 and 2100:
Please note that I increased the surplus tolerance to also allow more aggressive trade patters like full trading or with a very short time frame. Honestly, it's hard for me to judge if it is a reasonable scale:
p80_surplusMaxTolerance("perm") = 2* 300 * 12/44 / 1000; to p80_surplusMaxTolerance("perm") = 3* 300 * 12/44 / 1000;
@LaviniaBaumstark what do you think?
Type of change
Indicate the items relevant for your PR by replacing ◻️ with ☑️.
Do not delete any lines. This makes it easier to understand which areas are affected by your changes and which are not.
Parts concerned
Impact
Checklist
Do not delete any line. Leave unfinished elements unchecked so others know how far along you are.
In the end all checkboxes must be ticked before you can merge.
make test) after my final commit and all tests pass (FAIL 0)remind2if and where it was neededforbiddenColumnNamesin readCheckScenarioConfig.R in case the PR leads to deprecated switchesCHANGELOG.mdcorrectly (added, changed, fixed, removed, input data/calibration)Further information (optional)
/p/tmp/rahelma/Committed/Equity/permTrading/p/tmp/rahelma/Committed/Equity/permTrading/compScen-tradingScen-2026-02-10_20.52.00-H12.pdf