From 018ef9b3f1305c889f76913a4aea19bc10e06914 Mon Sep 17 00:00:00 2001 From: Renato-Rodrigues Date: Wed, 3 Jun 2026 19:18:16 +0200 Subject: [PATCH 01/11] bugfix: resolve index bug in convergence iteration tracking --- modules/47_regipol/regiCarbonPrice/postsolve.gms | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/47_regipol/regiCarbonPrice/postsolve.gms b/modules/47_regipol/regiCarbonPrice/postsolve.gms index 3f92dfdf1b..fcfb59c4c6 100644 --- a/modules/47_regipol/regiCarbonPrice/postsolve.gms +++ b/modules/47_regipol/regiCarbonPrice/postsolve.gms @@ -303,7 +303,7 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), / (pm_taxemiMkt_iteration(iteration,ttot2,regi,emiMkt) - pm_taxemiMkt_iteration("1",ttot2,regi,emiMkt)); *** else if there is a previous iteration calculated slope, repeat the previous iteration slope - elseif((iteration.val gt 1) and (p47_slopeReferenceIteration_iter(iteration,ttot,ext_regi) - p47_slopeReferenceIteration_iter(iteration-1,ttot,ext_regi) eq 0)), + elseif((iteration.val gt 1) and (p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi) - p47_slopeReferenceIteration_iter(iteration-1,ttot2,ext_regi) eq 0)), regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_repeatPrev") = YES; p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) = p47_factorRescaleSlope_iter(iteration-1,ttot,ttot2,ext_regi,emiMktExt); *** else slope is not available, set the rescale factor based on remaining deviation @@ -317,7 +317,7 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), if(p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) gt 0, regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,rescaleType) = NO; *** if there is a previous iteration calculated slope, repeat the previous iteration slope to avoid the positive value because we assume a trade-off between tax and emission levels - if((iteration.val gt 1) and (p47_slopeReferenceIteration_iter(iteration,ttot,ext_regi) - p47_slopeReferenceIteration_iter(iteration-1,ttot,ext_regi) eq 0), + if((iteration.val gt 1) and (p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi) - p47_slopeReferenceIteration_iter(iteration-1,ttot2,ext_regi) eq 0), regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_repeatPrev_positiveSlope") = YES; p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) = p47_factorRescaleSlope_iter(iteration-1,ttot,ttot2,ext_regi,emiMktExt); *** else slope is not available, set the rescale factor based on remaining deviation From da3f5ba5e86cda1c3dd5b1cd188dfbfdd1e3ccd4 Mon Sep 17 00:00:00 2001 From: Renato-Rodrigues Date: Wed, 3 Jun 2026 19:50:15 +0200 Subject: [PATCH 02/11] improvement - adding slope degeneration detection, where very small emissions numerators on the slope calculation are identified and the iteration is avoided as reference iteration --- modules/47_regipol/regiCarbonPrice/datainput.gms | 4 ++++ .../47_regipol/regiCarbonPrice/declarations.gms | 1 + modules/47_regipol/regiCarbonPrice/postsolve.gms | 15 ++++++++++++++- modules/47_regipol/regiCarbonPrice/sets.gms | 8 ++++---- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/modules/47_regipol/regiCarbonPrice/datainput.gms b/modules/47_regipol/regiCarbonPrice/datainput.gms index 358d7f883b..48129d09cc 100644 --- a/modules/47_regipol/regiCarbonPrice/datainput.gms +++ b/modules/47_regipol/regiCarbonPrice/datainput.gms @@ -19,6 +19,10 @@ pm_emiMktTarget_dev(ttot,ttot2,ext_regi,emiMktExt) = 0; ***-------------------------------------------------- $IFTHEN.emiMkt not "%cm_emiMktTarget%" == "off" +*** minimum emission change (as fraction of 2005 emissions) required for slope_refIteration to be considered numerically reliable; +*** below this the reference is treated as degenerate (e.g. run initialised from near-net-zero scenario, and tagretting a net-zero scenario) +s47_slopeDegenerateThreshold = 1e-2; + *** Auxiliar parameters based on emission targets information loop((ttot,ttot2,ext_regi,emiMktExt,target_type_47,emi_type_47)$pm_emiMktTarget(ttot,ttot2,ext_regi,emiMktExt,target_type_47,emi_type_47), !!calculated sets that depends on data parameter regiEmiMktTarget(ext_regi) = yes; !! assigning values to set containing extended regions that have regional emission targets diff --git a/modules/47_regipol/regiCarbonPrice/declarations.gms b/modules/47_regipol/regiCarbonPrice/declarations.gms index 071a9337b2..50c0991da9 100644 --- a/modules/47_regipol/regiCarbonPrice/declarations.gms +++ b/modules/47_regipol/regiCarbonPrice/declarations.gms @@ -13,6 +13,7 @@ Parameter s47_firstFreeYear "value of first free year for the carbon price trajectory" s47_prefreeYear "value of the last non-free year for the carbon price trajectory" + s47_slopeDegenerateThreshold "minimum emission change percentage relative to 2005 emissions required for a reliable slope; below this the slope numerator is treated as degenerate [fraction]" pm_emiLULUCF_GrassiShift(ttot,all_regi) "difference between Magpie land-use change emissions and UNFCCC emissions in 2015 to correct for national accounting in emissions targets [GtC]" pm_emiMktTarget_dev(ttot,ttot2,ext_regi,emiMktExt) "deviation of emissions of current iteration from target emissions, for budget target this is the difference normalized by target emissions, while for year targets this is the difference normalized by 2005 emissions [%]" pm_taxemiMkt(ttot,all_regi,all_emiMkt) "CO2 tax path per region and emissions market [T$/GtC]" diff --git a/modules/47_regipol/regiCarbonPrice/postsolve.gms b/modules/47_regipol/regiCarbonPrice/postsolve.gms index fcfb59c4c6..08877f235f 100644 --- a/modules/47_regipol/regiCarbonPrice/postsolve.gms +++ b/modules/47_regipol/regiCarbonPrice/postsolve.gms @@ -142,7 +142,7 @@ p47_emiTargetMkt_iter(iteration,ttot,regi, emiMktExt,emi_type_47) = p47_emiTarge *** Emission markets (EU Emission trading system and Effort Sharing) ***-------------------------------------------------- -$IFTHEN.emiMkt not "%cm_emiMktTarget%" == "off" +$IFTHEN.emiMkt not "%cm_emiMktTarget%" == "off" *** Removing economy wide co2 tax parameters for regions within the emiMKt controlled targets (this is necessary here to remove any calculation made in other modules after the last run in the postsolve) loop(ext_regi$regiEmiMktTarget(ext_regi), @@ -344,6 +344,19 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), ); ); ); +*** if slope numerator is negligibly small relative to 2005 emissions, the reference iteration is degenerate (e.g. initialised from near-net-zero scenario where emi_ref ≈ emi_current ≈ 0). +*** fall back to squareDev and reset reference so next iteration uses a fresh slope. It only applies to year targets (pm_emiMktRefYear > 0) + if((regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_refIteration") + OR regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_firstIteration")) + AND (pm_emiMktRefYear(ttot,ttot2,ext_regi,emiMktExt) gt 0) + AND (abs(p47_emiMktCurrent_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) + - p47_emiMktCurrent_iter(iteration2,ttot,ttot2,ext_regi,emiMktExt)) + lt s47_slopeDegenerateThreshold * pm_emiMktRefYear(ttot,ttot2,ext_regi,emiMktExt)), + regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,rescaleType) = NO; + regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_degenerateSlope") = YES; + pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt) = power(1+pm_emiMktTarget_dev(ttot,ttot2,ext_regi,emiMktExt), 2); + p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi) = ord(iteration); + ); *** dampen if rescale oscillates if( (iteration.val > 3) , if ( ( diff --git a/modules/47_regipol/regiCarbonPrice/sets.gms b/modules/47_regipol/regiCarbonPrice/sets.gms index 61ca8a8ca2..c2455570e9 100644 --- a/modules/47_regipol/regiCarbonPrice/sets.gms +++ b/modules/47_regipol/regiCarbonPrice/sets.gms @@ -21,10 +21,10 @@ $ifThen.emiMkt not "%cm_emiMktTarget%" == "off" regiEmiMktTarget(ext_regi) "regions with emiMkt targets" / / regiANDperiodEmiMktTarget_47(ttot,ext_regi) "regions and periods with emiMkt targets" / / regiEmiMktTarget2regi_47(ext_regi,all_regi) "regions controlled by emiMkt market set to ext_regi" / / - rescaleType "emi mkt carbon price scaling factor calculation methods" / - "squareDev_firstIteration", "squareDev_perfectMatch", "squareDev_smallChange", "squareDev_noChange", - "slope_refIteration", "slope_firstIteration", "slope_repeatPrev", "slope_repeatPrev_positiveSlope", - "squareDev_noSlope", "squareDev_noNonPositiveSlope"/ + rescaleType "emi mkt carbon price scaling factor calculation methods" / + "squareDev_firstIteration", "squareDev_perfectMatch", "squareDev_smallChange", "squareDev_noChange", + "slope_refIteration", "slope_firstIteration", "slope_repeatPrev", "slope_repeatPrev_positiveSlope", + "squareDev_noSlope", "squareDev_noNonPositiveSlope", "squareDev_degenerateSlope"/ regiEmiMktRescaleType(iteration,ttot,ttot,ext_regi,emiMktExt,rescaleType) "saving scaling type used in iteration" / / convergenceType "emiMkt target non convergence reason" / "lowerThanTolerance", "smallPrice" / regiEmiMktconvergenceType(iteration,ttot,ttot,ext_regi,emiMktExt,convergenceType) "saving convergence type in iteration" / / From df8fe9afbb37ca19c604eb88706649c5fd828d9b Mon Sep 17 00:00:00 2001 From: Renato-Rodrigues Date: Wed, 3 Jun 2026 19:57:06 +0200 Subject: [PATCH 03/11] removing slope_repeatPrev and squareDev_noSlope because squareDev_smallChange always pre-empts it. Remove --- .../47_regipol/regiCarbonPrice/postsolve.gms | 61 ++++++++----------- modules/47_regipol/regiCarbonPrice/sets.gms | 4 +- 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/modules/47_regipol/regiCarbonPrice/postsolve.gms b/modules/47_regipol/regiCarbonPrice/postsolve.gms index 08877f235f..97e6b344b2 100644 --- a/modules/47_regipol/regiCarbonPrice/postsolve.gms +++ b/modules/47_regipol/regiCarbonPrice/postsolve.gms @@ -302,45 +302,34 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), (p47_emiMktCurrent_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) - p47_emiMktCurrent_iter("1",ttot,ttot2,ext_regi,emiMktExt)) / (pm_taxemiMkt_iteration(iteration,ttot2,regi,emiMkt) - pm_taxemiMkt_iteration("1",ttot2,regi,emiMkt)); -*** else if there is a previous iteration calculated slope, repeat the previous iteration slope - elseif((iteration.val gt 1) and (p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi) - p47_slopeReferenceIteration_iter(iteration-1,ttot2,ext_regi) eq 0)), - regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_repeatPrev") = YES; - p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) = p47_factorRescaleSlope_iter(iteration-1,ttot,ttot2,ext_regi,emiMktExt); -*** else slope is not available, set the rescale factor based on remaining deviation - else - regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_noSlope") = YES; - pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt) = power(1+pm_emiMktTarget_dev(ttot,ttot2,ext_regi,emiMktExt), 2); ); -*** if we are using the slope - if(NOT(regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_noSlope")), -*** if the slope is positive - if(p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) gt 0, - regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,rescaleType) = NO; -*** if there is a previous iteration calculated slope, repeat the previous iteration slope to avoid the positive value because we assume a trade-off between tax and emission levels - if((iteration.val gt 1) and (p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi) - p47_slopeReferenceIteration_iter(iteration-1,ttot2,ext_regi) eq 0), - regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_repeatPrev_positiveSlope") = YES; - p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) = p47_factorRescaleSlope_iter(iteration-1,ttot,ttot2,ext_regi,emiMktExt); -*** else slope is not available, set the rescale factor based on remaining deviation - else - regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_noNonPositiveSlope") = YES; - pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt) = power(1+pm_emiMktTarget_dev(ttot,ttot2,ext_regi,emiMktExt), 2); - ); +*** if the slope is positive + if(p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) gt 0, + regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,rescaleType) = NO; +*** if there is a previous iteration calculated slope, repeat the previous iteration slope to avoid the positive value because we assume a trade-off between tax and emission levels + if((iteration.val gt 1) and (p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi) - p47_slopeReferenceIteration_iter(iteration-1,ttot2,ext_regi) eq 0), + regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_repeatPrev_positiveSlope") = YES; + p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) = p47_factorRescaleSlope_iter(iteration-1,ttot,ttot2,ext_regi,emiMktExt); +*** else slope is not available, set the rescale factor based on remaining deviation + else + regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_noNonPositiveSlope") = YES; + pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt) = power(1+pm_emiMktTarget_dev(ttot,ttot2,ext_regi,emiMktExt), 2); ); -*** if we are still using the slope - if(NOT(regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_noNonPositiveSlope")), -*** clamp slopes values to avoid extreme changes (or no change) on a single iteration (avoid corner cases where other parts of the model changes causing undesirable fluctuations on the calculated slope) - if((p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) gt -0.3) OR (p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) lt -5), - p47_clampedRescaleSlope_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) = p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt); - ); - p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) = max(-5,min(-0.3, p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt))); -*** calculate the tax rescale factor using the above calculated slope - pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt) = - ( - (pm_emiMktTarget(ttot,ttot2,ext_regi,emiMktExt,target_type_47,emi_type_47) - p47_emiMktCurrent_iter(iteration,ttot,ttot2,ext_regi,emiMktExt)) - / - (p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) * pm_taxemiMkt_iteration(iteration,ttot2,regi,emiMkt)) - ) + 1; + ); +*** if we are still using the slope + if(NOT(regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_noNonPositiveSlope")), +*** clamp slopes values to avoid extreme changes (or no change) on a single iteration (avoid corner cases where other parts of the model changes causing undesirable fluctuations on the calculated slope) + if((p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) gt -0.3) OR (p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) lt -5), + p47_clampedRescaleSlope_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) = p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt); ); + p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) = max(-5,min(-0.3, p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt))); +*** calculate the tax rescale factor using the above calculated slope + pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt) = + ( + (pm_emiMktTarget(ttot,ttot2,ext_regi,emiMktExt,target_type_47,emi_type_47) - p47_emiMktCurrent_iter(iteration,ttot,ttot2,ext_regi,emiMktExt)) + / + (p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) * pm_taxemiMkt_iteration(iteration,ttot2,regi,emiMkt)) + ) + 1; ); ); ); diff --git a/modules/47_regipol/regiCarbonPrice/sets.gms b/modules/47_regipol/regiCarbonPrice/sets.gms index c2455570e9..8415cde69f 100644 --- a/modules/47_regipol/regiCarbonPrice/sets.gms +++ b/modules/47_regipol/regiCarbonPrice/sets.gms @@ -23,8 +23,8 @@ $ifThen.emiMkt not "%cm_emiMktTarget%" == "off" regiEmiMktTarget2regi_47(ext_regi,all_regi) "regions controlled by emiMkt market set to ext_regi" / / rescaleType "emi mkt carbon price scaling factor calculation methods" / "squareDev_firstIteration", "squareDev_perfectMatch", "squareDev_smallChange", "squareDev_noChange", - "slope_refIteration", "slope_firstIteration", "slope_repeatPrev", "slope_repeatPrev_positiveSlope", - "squareDev_noSlope", "squareDev_noNonPositiveSlope", "squareDev_degenerateSlope"/ + "slope_refIteration", "slope_firstIteration", "slope_repeatPrev_positiveSlope", + "squareDev_noNonPositiveSlope", "squareDev_degenerateSlope"/ regiEmiMktRescaleType(iteration,ttot,ttot,ext_regi,emiMktExt,rescaleType) "saving scaling type used in iteration" / / convergenceType "emiMkt target non convergence reason" / "lowerThanTolerance", "smallPrice" / regiEmiMktconvergenceType(iteration,ttot,ttot,ext_regi,emiMktExt,convergenceType) "saving convergence type in iteration" / / From 514c0b9808b6a684bcdc1921d86e61c396b2ec93 Mon Sep 17 00:00:00 2001 From: Renato-Rodrigues Date: Wed, 3 Jun 2026 20:03:58 +0200 Subject: [PATCH 04/11] avoid division by zero case for unprobably case where markets emission sums are zero --- modules/47_regipol/regiCarbonPrice/postsolve.gms | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/47_regipol/regiCarbonPrice/postsolve.gms b/modules/47_regipol/regiCarbonPrice/postsolve.gms index 97e6b344b2..f7733f1d37 100644 --- a/modules/47_regipol/regiCarbonPrice/postsolve.gms +++ b/modules/47_regipol/regiCarbonPrice/postsolve.gms @@ -429,7 +429,12 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), pm_taxemiMkt(t,regi,emiMkt)$((t.val gt ttot2.val) AND (t.val lt ttot3.val)) = pm_taxemiMkt(ttot2,regi,emiMkt) + ((pm_taxemiMkt(ttot3,regi,emiMkt) - pm_taxemiMkt(ttot2,regi,emiMkt))/(ttot3.val-ttot2.val))*(t.val-ttot2.val); !! price in between current target year and next target year *** else if next target was never executed by the algorithm yet, initialize next target value as weighted average convergence price between current target terminal year (ttot2.val) and next target year (p47_nextConvergencePeriod) else - p47_averagetaxemiMkt(t,regi) = +*** default: simple average across markets (guards against division by zero when total +*** net emissions are negligibly small, zero, or net-negative with cancellation) + p47_averagetaxemiMkt(t,regi) = + (pm_taxemiMkt(t,regi,"ETS") + pm_taxemiMkt(t,regi,"ES") + pm_taxemiMkt(t,regi,"other")) / 3; +*** override with emission-weighted average when total net emissions are large enough + p47_averagetaxemiMkt(t,regi)$(abs(p47_emiTargetMkt(t,regi,"ETS",emi_type_47) + p47_emiTargetMkt(t,regi,"ESR",emi_type_47) + p47_emiTargetMkt(t,regi,"other",emi_type_47)) gt 1e-6) = (pm_taxemiMkt(t,regi,"ETS")*p47_emiTargetMkt(t,regi,"ETS",emi_type_47) + pm_taxemiMkt(t,regi,"ES")*p47_emiTargetMkt(t,regi,"ESR",emi_type_47) + pm_taxemiMkt(t,regi,"other")*p47_emiTargetMkt(t,regi,"other",emi_type_47)) / (p47_emiTargetMkt(t,regi,"ETS",emi_type_47) + p47_emiTargetMkt(t,regi,"ESR",emi_type_47) + p47_emiTargetMkt(t,regi,"other",emi_type_47)); From 5bc59f582f004de290faabcb8911a2a4d0c5ccd9 Mon Sep 17 00:00:00 2001 From: Renato-Rodrigues Date: Wed, 3 Jun 2026 21:29:00 +0200 Subject: [PATCH 05/11] add s47_slopeMaxWindow (squareDev_outsideWindow) condition to focus on the local abatment slope instead of long one --- .../47_regipol/regiCarbonPrice/datainput.gms | 6 ++--- .../regiCarbonPrice/declarations.gms | 1 + .../47_regipol/regiCarbonPrice/postsolve.gms | 22 ++++++++++++++----- modules/47_regipol/regiCarbonPrice/sets.gms | 2 +- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/modules/47_regipol/regiCarbonPrice/datainput.gms b/modules/47_regipol/regiCarbonPrice/datainput.gms index 48129d09cc..262c63b422 100644 --- a/modules/47_regipol/regiCarbonPrice/datainput.gms +++ b/modules/47_regipol/regiCarbonPrice/datainput.gms @@ -19,9 +19,9 @@ pm_emiMktTarget_dev(ttot,ttot2,ext_regi,emiMktExt) = 0; ***-------------------------------------------------- $IFTHEN.emiMkt not "%cm_emiMktTarget%" == "off" -*** minimum emission change (as fraction of 2005 emissions) required for slope_refIteration to be considered numerically reliable; -*** below this the reference is treated as degenerate (e.g. run initialised from near-net-zero scenario, and tagretting a net-zero scenario) -s47_slopeDegenerateThreshold = 1e-2; +*** slope calculation algorithm parameters +s47_slopeDegenerateThreshold = 1e-2; !! minimum emission change (fraction of 2005 emissions) for a reliable slope numerator +s47_slopeMaxWindow = 5; !! maximum iterations between reference and current; beyond this the reference is reset to stay local on the abatement cost curve *** Auxiliar parameters based on emission targets information loop((ttot,ttot2,ext_regi,emiMktExt,target_type_47,emi_type_47)$pm_emiMktTarget(ttot,ttot2,ext_regi,emiMktExt,target_type_47,emi_type_47), !!calculated sets that depends on data parameter diff --git a/modules/47_regipol/regiCarbonPrice/declarations.gms b/modules/47_regipol/regiCarbonPrice/declarations.gms index 50c0991da9..d7077480c5 100644 --- a/modules/47_regipol/regiCarbonPrice/declarations.gms +++ b/modules/47_regipol/regiCarbonPrice/declarations.gms @@ -14,6 +14,7 @@ Parameter s47_firstFreeYear "value of first free year for the carbon price trajectory" s47_prefreeYear "value of the last non-free year for the carbon price trajectory" s47_slopeDegenerateThreshold "minimum emission change percentage relative to 2005 emissions required for a reliable slope; below this the slope numerator is treated as degenerate [fraction]" + s47_slopeMaxWindow "maximum number of iterations between the reference iteration and the current iteration for slope calculation; beyond this the reference is reset to force a local slope [#]" pm_emiLULUCF_GrassiShift(ttot,all_regi) "difference between Magpie land-use change emissions and UNFCCC emissions in 2015 to correct for national accounting in emissions targets [GtC]" pm_emiMktTarget_dev(ttot,ttot2,ext_regi,emiMktExt) "deviation of emissions of current iteration from target emissions, for budget target this is the difference normalized by target emissions, while for year targets this is the difference normalized by 2005 emissions [%]" pm_taxemiMkt(ttot,all_regi,all_emiMkt) "CO2 tax path per region and emissions market [T$/GtC]" diff --git a/modules/47_regipol/regiCarbonPrice/postsolve.gms b/modules/47_regipol/regiCarbonPrice/postsolve.gms index f7733f1d37..b68a2b7a31 100644 --- a/modules/47_regipol/regiCarbonPrice/postsolve.gms +++ b/modules/47_regipol/regiCarbonPrice/postsolve.gms @@ -251,6 +251,8 @@ loop((ext_regi,ttot)$regiANDperiodEmiMktTarget_47(ttot,ext_regi), p47_slopeReferenceIteration_iter(iteration,ttot,ext_regi) = 1; elseif(NOT(p47_currentConvergence_iter(iteration,ttot,ext_regi) eq p47_currentConvergence_iter(iteration-1,ttot,ext_regi))), !! reset the iteration reference for slope calculation if the target that is being analyzed changes p47_slopeReferenceIteration_iter(iteration,ttot,ext_regi) = ord(iteration); + elseif((ord(iteration) - p47_slopeReferenceIteration_iter(iteration-1,ttot,ext_regi)) gt s47_slopeMaxWindow), !! reset if reference is too old to represent the local abatement cost curve + p47_slopeReferenceIteration_iter(iteration,ttot,ext_regi) = ord(iteration); else p47_slopeReferenceIteration_iter(iteration,ttot,ext_regi) = p47_slopeReferenceIteration_iter(iteration-1,ttot,ext_regi); ); @@ -297,11 +299,18 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), (pm_taxemiMkt_iteration(iteration,ttot2,regi,emiMkt) - pm_taxemiMkt_iteration(iteration2,ttot2,regi,emiMkt)); *** else if denominator in relation to first iteration is not close to zero, calculate the price slope in relation to the first iteration mitigation and price levels instead elseif(NOT(abs(pm_taxemiMkt_iteration(iteration,ttot2,regi,emiMkt) - pm_taxemiMkt_iteration("1",ttot2,regi,emiMkt)) lt 1e-2)), - regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_firstIteration") = YES; - p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) = - (p47_emiMktCurrent_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) - p47_emiMktCurrent_iter("1",ttot,ttot2,ext_regi,emiMktExt)) - / - (pm_taxemiMkt_iteration(iteration,ttot2,regi,emiMkt) - pm_taxemiMkt_iteration("1",ttot2,regi,emiMkt)); +*** only use iteration 1 as reference if it falls within the slope window; otherwise iteration 1 is too old +*** to represent the local abatement cost and squareDev is more appropriate + if((iteration.val - 1) le s47_slopeMaxWindow, + regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_firstIteration") = YES; + p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) = + (p47_emiMktCurrent_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) - p47_emiMktCurrent_iter("1",ttot,ttot2,ext_regi,emiMktExt)) + / + (pm_taxemiMkt_iteration(iteration,ttot2,regi,emiMkt) - pm_taxemiMkt_iteration("1",ttot2,regi,emiMkt)); + else + regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_outsideWindow") = YES; + pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt) = power(1+pm_emiMktTarget_dev(ttot,ttot2,ext_regi,emiMktExt), 2); + ); ); *** if the slope is positive if(p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) gt 0, @@ -317,7 +326,8 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), ); ); *** if we are still using the slope - if(NOT(regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_noNonPositiveSlope")), + if(NOT(regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_noNonPositiveSlope")) + AND NOT(regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_outsideWindow")), *** clamp slopes values to avoid extreme changes (or no change) on a single iteration (avoid corner cases where other parts of the model changes causing undesirable fluctuations on the calculated slope) if((p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) gt -0.3) OR (p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) lt -5), p47_clampedRescaleSlope_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) = p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt); diff --git a/modules/47_regipol/regiCarbonPrice/sets.gms b/modules/47_regipol/regiCarbonPrice/sets.gms index 8415cde69f..0a0bab52d7 100644 --- a/modules/47_regipol/regiCarbonPrice/sets.gms +++ b/modules/47_regipol/regiCarbonPrice/sets.gms @@ -24,7 +24,7 @@ $ifThen.emiMkt not "%cm_emiMktTarget%" == "off" rescaleType "emi mkt carbon price scaling factor calculation methods" / "squareDev_firstIteration", "squareDev_perfectMatch", "squareDev_smallChange", "squareDev_noChange", "slope_refIteration", "slope_firstIteration", "slope_repeatPrev_positiveSlope", - "squareDev_noNonPositiveSlope", "squareDev_degenerateSlope"/ + "squareDev_noNonPositiveSlope", "squareDev_degenerateSlope", "squareDev_outsideWindow"/ regiEmiMktRescaleType(iteration,ttot,ttot,ext_regi,emiMktExt,rescaleType) "saving scaling type used in iteration" / / convergenceType "emiMkt target non convergence reason" / "lowerThanTolerance", "smallPrice" / regiEmiMktconvergenceType(iteration,ttot,ttot,ext_regi,emiMktExt,convergenceType) "saving convergence type in iteration" / / From a47cdc001edb72fac27c6ccb131e66c482fc8706 Mon Sep 17 00:00:00 2001 From: Renato-Rodrigues Date: Wed, 3 Jun 2026 21:56:21 +0200 Subject: [PATCH 06/11] fix bracket issue --- modules/47_regipol/regiCarbonPrice/postsolve.gms | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/47_regipol/regiCarbonPrice/postsolve.gms b/modules/47_regipol/regiCarbonPrice/postsolve.gms index b68a2b7a31..bd9fed45bf 100644 --- a/modules/47_regipol/regiCarbonPrice/postsolve.gms +++ b/modules/47_regipol/regiCarbonPrice/postsolve.gms @@ -342,7 +342,6 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), ) + 1; ); ); - ); *** if slope numerator is negligibly small relative to 2005 emissions, the reference iteration is degenerate (e.g. initialised from near-net-zero scenario where emi_ref ≈ emi_current ≈ 0). *** fall back to squareDev and reset reference so next iteration uses a fresh slope. It only applies to year targets (pm_emiMktRefYear > 0) if((regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_refIteration") @@ -356,6 +355,7 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt) = power(1+pm_emiMktTarget_dev(ttot,ttot2,ext_regi,emiMktExt), 2); p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi) = ord(iteration); ); + ); *** dampen if rescale oscillates if( (iteration.val > 3) , if ( ( From 873eed612e7729743e1c8db0c7158e98f3f4a622 Mon Sep 17 00:00:00 2001 From: Renato-Rodrigues Date: Thu, 4 Jun 2026 05:26:05 +0200 Subject: [PATCH 07/11] add support for adaptive upper clamp --- .../regiCarbonPrice/declarations.gms | 3 ++ .../47_regipol/regiCarbonPrice/postsolve.gms | 47 +++++++++++++++---- modules/47_regipol/regiCarbonPrice/sets.gms | 2 +- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/modules/47_regipol/regiCarbonPrice/declarations.gms b/modules/47_regipol/regiCarbonPrice/declarations.gms index d7077480c5..e207f832da 100644 --- a/modules/47_regipol/regiCarbonPrice/declarations.gms +++ b/modules/47_regipol/regiCarbonPrice/declarations.gms @@ -64,6 +64,9 @@ $endif.emiMktTargetType p47_factorRescaleemiMktCO2Tax_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) "parameter to save rescale factor across iterations for debugging purposes [%]" p47_clampedRescaleSlope_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) "auxiliary parameter to save the slope value before clamping. Useful for debugging purposes [#]" p47_dampedFactorRescaleemiMktCO2Tax_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) "auxiliary parameter to save the rescale factor value before dampening. Useful for debugging purposes [#]" + p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt) "adaptive upper clamp bound for rescale slope; starts at -0.3 and halves after two consecutive triggers at the same level [#]" + p47_slopeUpperClampBound_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) "adaptive upper clamp bound per iteration for debugging [#]" + p47_upperClampActive_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) "1 if the upper slope clamp fired in this iteration, 0 otherwise [0 or 1]" *** Parameters necessary to define the CO2 tax curve shape p47_targetConverged(ttot,ext_regi) "boolean to store if emission target has converged [0 or 1]" diff --git a/modules/47_regipol/regiCarbonPrice/postsolve.gms b/modules/47_regipol/regiCarbonPrice/postsolve.gms index bd9fed45bf..2a3e75f26f 100644 --- a/modules/47_regipol/regiCarbonPrice/postsolve.gms +++ b/modules/47_regipol/regiCarbonPrice/postsolve.gms @@ -328,18 +328,45 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), *** if we are still using the slope if(NOT(regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_noNonPositiveSlope")) AND NOT(regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_outsideWindow")), -*** clamp slopes values to avoid extreme changes (or no change) on a single iteration (avoid corner cases where other parts of the model changes causing undesirable fluctuations on the calculated slope) - if((p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) gt -0.3) OR (p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) lt -5), - p47_clampedRescaleSlope_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) = p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt); +*** initialize or reset adaptive upper clamp bound when reference iteration changes + if((p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt) eq 0) + OR ((iteration.val gt 1) AND (p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi) ne p47_slopeReferenceIteration_iter(iteration-1,ttot2,ext_regi))), + p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt) = -0.3; ); - p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) = max(-5,min(-0.3, p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt))); + p47_slopeUpperClampBound_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) = p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt); +*** if upper clamp fires for second consecutive time at minimum level (-0.075), immediately override to squareDev and reset + if((p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) gt p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt)) + AND (p47_upperClampActive_iter(iteration-1,ttot,ttot2,ext_regi,emiMktExt) eq 1) + AND (p47_slopeUpperClampBound_iter(iteration-1,ttot,ttot2,ext_regi,emiMktExt) eq p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt)) + AND (p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt) lt -0.07), + regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,rescaleType) = NO; + regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_adaptiveClamp") = YES; + pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt) = power(1+pm_emiMktTarget_dev(ttot,ttot2,ext_regi,emiMktExt), 2); + p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt) = -0.3; + p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi) = ord(iteration); + else +*** clamp slopes values to avoid extreme changes (or no change) on a single iteration + if((p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) gt p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt)) OR (p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) lt -5), + p47_clampedRescaleSlope_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) = p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt); + ); + p47_upperClampActive_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) = (p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) gt p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt)); + p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) = max(-5, min(p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt), p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt))); *** calculate the tax rescale factor using the above calculated slope - pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt) = - ( - (pm_emiMktTarget(ttot,ttot2,ext_regi,emiMktExt,target_type_47,emi_type_47) - p47_emiMktCurrent_iter(iteration,ttot,ttot2,ext_regi,emiMktExt)) - / - (p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) * pm_taxemiMkt_iteration(iteration,ttot2,regi,emiMkt)) - ) + 1; + pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt) = + ( + (pm_emiMktTarget(ttot,ttot2,ext_regi,emiMktExt,target_type_47,emi_type_47) - p47_emiMktCurrent_iter(iteration,ttot,ttot2,ext_regi,emiMktExt)) + / + (p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) * pm_taxemiMkt_iteration(iteration,ttot2,regi,emiMkt)) + ) + 1; +*** halve adaptive bound after two consecutive upper-clamp triggers at the same level; reset if clamp not triggered + if((p47_upperClampActive_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) eq 1) + AND (p47_upperClampActive_iter(iteration-1,ttot,ttot2,ext_regi,emiMktExt) eq 1) + AND (p47_slopeUpperClampBound_iter(iteration-1,ttot,ttot2,ext_regi,emiMktExt) eq p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt)), + p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt) = p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt) / 2; + elseif(p47_upperClampActive_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) eq 0), + p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt) = -0.3; + ); + ); ); ); *** if slope numerator is negligibly small relative to 2005 emissions, the reference iteration is degenerate (e.g. initialised from near-net-zero scenario where emi_ref ≈ emi_current ≈ 0). diff --git a/modules/47_regipol/regiCarbonPrice/sets.gms b/modules/47_regipol/regiCarbonPrice/sets.gms index 0a0bab52d7..29d1e791fa 100644 --- a/modules/47_regipol/regiCarbonPrice/sets.gms +++ b/modules/47_regipol/regiCarbonPrice/sets.gms @@ -24,7 +24,7 @@ $ifThen.emiMkt not "%cm_emiMktTarget%" == "off" rescaleType "emi mkt carbon price scaling factor calculation methods" / "squareDev_firstIteration", "squareDev_perfectMatch", "squareDev_smallChange", "squareDev_noChange", "slope_refIteration", "slope_firstIteration", "slope_repeatPrev_positiveSlope", - "squareDev_noNonPositiveSlope", "squareDev_degenerateSlope", "squareDev_outsideWindow"/ + "squareDev_noNonPositiveSlope", "squareDev_degenerateSlope", "squareDev_outsideWindow", "squareDev_adaptiveClamp"/ regiEmiMktRescaleType(iteration,ttot,ttot,ext_regi,emiMktExt,rescaleType) "saving scaling type used in iteration" / / convergenceType "emiMkt target non convergence reason" / "lowerThanTolerance", "smallPrice" / regiEmiMktconvergenceType(iteration,ttot,ttot,ext_regi,emiMktExt,convergenceType) "saving convergence type in iteration" / / From 99f327846dfaaa211f10226bff2c984a136c9036 Mon Sep 17 00:00:00 2001 From: Renato-Rodrigues Date: Fri, 5 Jun 2026 15:28:28 +0200 Subject: [PATCH 08/11] only check degenerated slopes if there is a reference iteration different from the current one. --- modules/47_regipol/regiCarbonPrice/postsolve.gms | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/47_regipol/regiCarbonPrice/postsolve.gms b/modules/47_regipol/regiCarbonPrice/postsolve.gms index 2a3e75f26f..e27ef12e8b 100644 --- a/modules/47_regipol/regiCarbonPrice/postsolve.gms +++ b/modules/47_regipol/regiCarbonPrice/postsolve.gms @@ -373,6 +373,7 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), *** fall back to squareDev and reset reference so next iteration uses a fresh slope. It only applies to year targets (pm_emiMktRefYear > 0) if((regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_refIteration") OR regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_firstIteration")) + AND NOT((iteration.val - iteration2.val) eq 0) AND (pm_emiMktRefYear(ttot,ttot2,ext_regi,emiMktExt) gt 0) AND (abs(p47_emiMktCurrent_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) - p47_emiMktCurrent_iter(iteration2,ttot,ttot2,ext_regi,emiMktExt)) From 2025e4c167084a3cbb8e7dec5a28a5df29fa4936 Mon Sep 17 00:00:00 2001 From: Renato-Rodrigues Date: Fri, 5 Jun 2026 17:01:21 +0200 Subject: [PATCH 09/11] intializing parameters --- modules/47_regipol/regiCarbonPrice/datainput.gms | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/47_regipol/regiCarbonPrice/datainput.gms b/modules/47_regipol/regiCarbonPrice/datainput.gms index 262c63b422..76272b4478 100644 --- a/modules/47_regipol/regiCarbonPrice/datainput.gms +++ b/modules/47_regipol/regiCarbonPrice/datainput.gms @@ -26,7 +26,8 @@ s47_slopeMaxWindow = 5; !! maximum iterations between reference and *** Auxiliar parameters based on emission targets information loop((ttot,ttot2,ext_regi,emiMktExt,target_type_47,emi_type_47)$pm_emiMktTarget(ttot,ttot2,ext_regi,emiMktExt,target_type_47,emi_type_47), !!calculated sets that depends on data parameter regiEmiMktTarget(ext_regi) = yes; !! assigning values to set containing extended regions that have regional emission targets - regiANDperiodEmiMktTarget_47(ttot2,ext_regi) = yes; !! assigning values to set containing extended regions and terminal years of regional emission targets + regiANDperiodEmiMktTarget_47(ttot2,ext_regi) = yes; !! assigning values to set containing extended regions and terminal years of regional emission targets + p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt) = -0.3; !! initialize upper clamp bound for rescale slope ); *** Calculating set containing regions that should be controlled by a given regional emission target. @@ -110,6 +111,8 @@ p47_taxemiMkt_init(ttot,regi,emiMkt)$(p47_taxCO2eq_ref(ttot,regi) and (NOT(p47_t p47_factorRescaleSlope_iter("1","2020","2030",ext_regi,emiMktExt) = 0; *** initialize required parameter for cm_emiMktTarget rescale oscillation dampening. p47_factorRescaleemiMktCO2Tax_iter("1","2020","2030",ext_regi,emiMktExt) = 0; +*** initialize required parameter to track if the upper slope clamp fired. +p47_upperClampActive_iter("1","2020","2030",ext_regi,emiMktExt) = 0; $ENDIF.emiMkt From 5b16453004b371e929376cc900115f954d25b387 Mon Sep 17 00:00:00 2001 From: Renato-Rodrigues Date: Fri, 5 Jun 2026 22:14:51 +0200 Subject: [PATCH 10/11] bugfix - clear regiEmiMktRescaleType before iteration evaluation --- modules/47_regipol/regiCarbonPrice/postsolve.gms | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/47_regipol/regiCarbonPrice/postsolve.gms b/modules/47_regipol/regiCarbonPrice/postsolve.gms index e27ef12e8b..98a496f354 100644 --- a/modules/47_regipol/regiCarbonPrice/postsolve.gms +++ b/modules/47_regipol/regiCarbonPrice/postsolve.gms @@ -268,6 +268,8 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), loop((ttot,emiMktExt,target_type_47,emi_type_47)$(pm_emiMktTarget(ttot,ttot2,ext_regi,emiMktExt,target_type_47,emi_type_47)), loop(emiMkt$emiMktGroup(emiMktExt,emiMkt), loop(regi$regiEmiMktTarget2regi_47(ext_regi,regi), +*** reset the rescale type tracking for the current iteration to avoid overlapping flags + regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,rescaleType) = NO; *** if rescale factor was already calculated for ext_regi, there is no need to recalculate it continue$(pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt)); *** calculating the rescale factor From 464cd0ab96f3f43af2029b7523f45760f3d7d8de Mon Sep 17 00:00:00 2001 From: Renato-Rodrigues Date: Wed, 22 Jul 2026 22:10:19 +0200 Subject: [PATCH 11/11] resetting regiEmiMktRescaleType and adding documentation to rescale types --- modules/47_regipol/module.gms | 16 ++++++++++++ .../47_regipol/regiCarbonPrice/postsolve.gms | 25 ++++++++++++------- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/modules/47_regipol/module.gms b/modules/47_regipol/module.gms index 781e7685b1..ea2c857744 100644 --- a/modules/47_regipol/module.gms +++ b/modules/47_regipol/module.gms @@ -34,6 +34,22 @@ *' These bounds either serve to align REMIND with historic and near-term data in specific regions or represent regional policies (like national coal phase-out plans) that can be activated via switches. *' Finally, there are some regional adjustments in the datainput file that modify input data for specific regions for cases which have not been generalized for all REMIND regions yet. *' +*' ## Glossary of Carbon Price Rescaling Terms +*' +*' * **Target Deviation**: The fractional difference between current iteration emissions and the specified emissions target, normalized by a reference year's emissions. The algorithm drives this deviation toward zero (within a specified tolerance band) by adjusting the carbon tax. +*' * **Rescale Factor**: The multiplicative factor applied to the regional carbon tax at the end of each outer (Nash) iteration. A value of 1.0 means no change; > 1 raises the tax; < 1 lowers it. +*' * **Rescale Slope**: The estimated marginal sensitivity of regional emissions to the carbon tax in a given emission market (change in emissions / change in price). Expected to be negative (higher price leads to lower emissions). Computed from the current iteration and a baseline reference iteration. +*' * **Slope Reference Iteration**: The baseline outer iteration used to compute the Rescale Slope. It is reset when the target changes, when the slope window is exceeded, or when a degenerate slope is detected to ensure the computed slope reflects the local abatement cost curve. +*' * **Slope Window**: The maximum allowed number of iterations between the current outer iteration and the Slope Reference Iteration. If the reference becomes older than this threshold, it is forced to reset. +*' * **SquareDev Fallback**: A robust fallback method that bypasses the Rescale Slope entirely and computes the tax rescale factor quadratically as `(1 + target_deviation)^2`. Used when reliable slope information is unavailable or degenerate. +*' * **Degenerate Slope**: A condition where the change in emissions (slope numerator) is negligibly small relative to the reference year's emissions (e.g., in near-net-zero scenarios). Triggers a SquareDev Fallback and resets the reference iteration. +*' * **Positive Slope**: An anomalous scenario where the computed Rescale Slope is positive, falsely implying that a higher carbon tax increased emissions. Addressed by either repeating a valid historical slope or triggering a SquareDev Fallback. +*' * **Slope Clamp**: Hard bounds applied to the Rescale Slope before it is used. Prevents a single iteration from making an extreme price adjustment due to noisy slope estimates. +*' * **Upper Clamp Bound**: The maximum (least-negative) permitted value for the Rescale Slope (default: -0.3). Prevents the algorithm from over-adjusting when the slope is too flat. +*' * **Lower Clamp Bound**: The minimum (most-negative) permitted value for the Rescale Slope (default: -5). Prevents extreme tax jumps when the slope is too steep. +*' * **Adaptive Upper Clamp Bound**: An extension of the Upper Clamp Bound that halves the limit when the clamp triggers in consecutive outer iterations, indicating persistent flat-slope behavior. Resets to the default when the clamp is no longer triggered. +*' * **Oscillation Dampener**: A safeguard that fires when the Rescale Factor alternates between > 1 and < 1 for three consecutive outer iterations. When triggered, the factor's adjustment distance from 1.0 is halved to dampen volatility. +*' *' @authors Renato Rodrigues, Felix Schreyer *###################### R SECTION START (MODULETYPES) ########################## diff --git a/modules/47_regipol/regiCarbonPrice/postsolve.gms b/modules/47_regipol/regiCarbonPrice/postsolve.gms index 98a496f354..2eba50dd4b 100644 --- a/modules/47_regipol/regiCarbonPrice/postsolve.gms +++ b/modules/47_regipol/regiCarbonPrice/postsolve.gms @@ -261,6 +261,7 @@ loop((ext_regi,ttot)$regiANDperiodEmiMktTarget_47(ttot,ext_regi), *** resetting rescale factor for the next iteration p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) = 0; pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt) = 0; +regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,rescaleType) = NO; *** Calculating the emissions tax rescale factor based on previous iterations emission reduction for current targets loop(ext_regi$regiEmiMktTarget(ext_regi), loop((ttot2)$(ttot2.val eq p47_currentConvergencePeriod(ext_regi)), @@ -268,10 +269,8 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), loop((ttot,emiMktExt,target_type_47,emi_type_47)$(pm_emiMktTarget(ttot,ttot2,ext_regi,emiMktExt,target_type_47,emi_type_47)), loop(emiMkt$emiMktGroup(emiMktExt,emiMkt), loop(regi$regiEmiMktTarget2regi_47(ext_regi,regi), -*** reset the rescale type tracking for the current iteration to avoid overlapping flags - regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,rescaleType) = NO; *** if rescale factor was already calculated for ext_regi, there is no need to recalculate it - continue$(pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt)); + continue$(sum(rescaleType$regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,rescaleType), 1)); *** calculating the rescale factor loop(iteration2$((iteration2.val le iteration.val) and (iteration2.val eq p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi))), !!reference iteration for slope calculation *** if it is the first iteration or the reference iteration changed, initialize the rescale factor based on remaining deviation @@ -318,7 +317,7 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), if(p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) gt 0, regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,rescaleType) = NO; *** if there is a previous iteration calculated slope, repeat the previous iteration slope to avoid the positive value because we assume a trade-off between tax and emission levels - if((iteration.val gt 1) and (p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi) - p47_slopeReferenceIteration_iter(iteration-1,ttot2,ext_regi) eq 0), + if((iteration.val gt 1) and (p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi) - p47_slopeReferenceIteration_iter(iteration-1,ttot2,ext_regi) eq 0) and (p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt)), regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_repeatPrev_positiveSlope") = YES; p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) = p47_factorRescaleSlope_iter(iteration-1,ttot,ttot2,ext_regi,emiMktExt); *** else slope is not available, set the rescale factor based on remaining deviation @@ -345,7 +344,6 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_adaptiveClamp") = YES; pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt) = power(1+pm_emiMktTarget_dev(ttot,ttot2,ext_regi,emiMktExt), 2); p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt) = -0.3; - p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi) = ord(iteration); else *** clamp slopes values to avoid extreme changes (or no change) on a single iteration if((p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) gt p47_slopeUpperClampBound(ttot,ttot2,ext_regi,emiMktExt)) OR (p47_factorRescaleSlope(ttot,ttot2,ext_regi,emiMktExt) lt -5), @@ -371,21 +369,30 @@ loop(ext_regi$regiEmiMktTarget(ext_regi), ); ); ); -*** if slope numerator is negligibly small relative to 2005 emissions, the reference iteration is degenerate (e.g. initialised from near-net-zero scenario where emi_ref ≈ emi_current ≈ 0). -*** fall back to squareDev and reset reference so next iteration uses a fresh slope. It only applies to year targets (pm_emiMktRefYear > 0) +*** if slope numerator is negligibly small both relative to 2005 emissions AND relative to the remaining gap to close, the slope is degenerate (e.g. initialised from near-net-zero scenario where emi_ref ≈ emi_current ≈ target ≈ 0). +*** fall back to squareDev. It only applies to year targets (pm_emiMktRefYear > 0). +*** The relative-to-gap condition prevents squareDev from firing when emissions are merely close to the target in absolute terms but the slope is still useful for Newton: e.g. a 5 MtCO2 change is below 1% of 2005 emissions but is 25% of a 20 MtCO2 remaining gap, so Newton should be used. if((regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_refIteration") OR regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"slope_firstIteration")) AND NOT((iteration.val - iteration2.val) eq 0) AND (pm_emiMktRefYear(ttot,ttot2,ext_regi,emiMktExt) gt 0) AND (abs(p47_emiMktCurrent_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) - p47_emiMktCurrent_iter(iteration2,ttot,ttot2,ext_regi,emiMktExt)) - lt s47_slopeDegenerateThreshold * pm_emiMktRefYear(ttot,ttot2,ext_regi,emiMktExt)), + lt s47_slopeDegenerateThreshold * pm_emiMktRefYear(ttot,ttot2,ext_regi,emiMktExt)) + AND (abs(p47_emiMktCurrent_iter(iteration,ttot,ttot2,ext_regi,emiMktExt) + - p47_emiMktCurrent_iter(iteration2,ttot,ttot2,ext_regi,emiMktExt)) + lt s47_slopeDegenerateThreshold * abs(pm_emiMktTarget(ttot,ttot2,ext_regi,emiMktExt,target_type_47,emi_type_47) + - p47_emiMktCurrent_iter(iteration,ttot,ttot2,ext_regi,emiMktExt))), regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,rescaleType) = NO; regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_degenerateSlope") = YES; pm_factorRescaleemiMktCO2Tax(ttot,ttot2,ext_regi,emiMktExt) = power(1+pm_emiMktTarget_dev(ttot,ttot2,ext_regi,emiMktExt), 2); - p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi) = ord(iteration); ); ); +*** reset slope reference iteration after squareDev_adaptiveClamp, moved outside loop(iteration2) to prevent the loop from re-evaluating its condition with the updated reference and executing a spurious second body run that sets squareDev_firstIteration alongside the intended type. +*** squareDev_degenerateSlope intentionally does NOT reset the reference: resetting after degeneracy creates a 1-step window on the next iteration that is immediately degenerate again, locking the algorithm out of Newton. Instead, the reference is allowed to age naturally so emissions accumulate enough history for a reliable slope. The slopeMaxWindow mechanism resets the reference after s47_slopeMaxWindow iterations if needed. + if(regiEmiMktRescaleType(iteration,ttot,ttot2,ext_regi,emiMktExt,"squareDev_adaptiveClamp"), + p47_slopeReferenceIteration_iter(iteration,ttot2,ext_regi) = ord(iteration); + ); *** dampen if rescale oscillates if( (iteration.val > 3) , if ( (