Skip to content

Add subannual generic relation constraints#1020

Open
Wegatriespython wants to merge 2 commits into
iiasa:mainfrom
Wegatriespython:enh/subannual-relation-constraints
Open

Add subannual generic relation constraints#1020
Wegatriespython wants to merge 2 commits into
iiasa:mainfrom
Wegatriespython:enh/subannual-relation-constraints

Conversation

@Wegatriespython

Copy link
Copy Markdown

This PR addresses part of #191 (the RELATION_EQUIVALENCE item) and slots under the umbrella #979 by adding subannual generic relation constraints to MESSAGE, so a generic relation can express a per-time-slice bound on a subannual scenario instead of only an annual one.

Today the GAMS core consumes annual relation_activity / relation_upper / relation_lower only. On a timesliced (h1..h4) scenario there is no way to carry a signal that varies by slice through the generic-relation mechanism — it stays annual even where the demand and activity are subannual. This blocks any workflow that needs a season- or slice-specific relation cap.

Rather than add a time dimension to the existing parameters (which would break stored scenarios and is the backwards-compatibility blocker noted on #191), this adds parallel _time variants. New parameters relation_upper_time, relation_lower_time, relation_activity_time, flag sets is_relation_upper_time / is_relation_lower_time, and an auxiliary variable REL_TIME(r, n, y, h), plus three equations RELATION_EQUIVALENCE_TIME, RELATION_CONSTRAINT_UP_TIME, RELATION_CONSTRAINT_LO_TIME in model_core.gms. Annual scenarios are unaffected; a relation may use the annual parameters, the _time parameters, or both.

The is_*_time flag sets are composed in the Python layer (MESSAGE.enforce) from the parent parameter keys — dropping the value and unit columns — rather than derived from parameter values in GAMS. This is key-based, so a zero-valued bound (relation_upper_time = 0, a valid REL_TIME <= 0 constraint) survives instead of being silently dropped, and it mirrors the existing capacity_factor handling. Matching HelperTableInfo entries are added in scenario_data.py for the ixmp4 backend.

Out of scope, and following the annual RELATION_* siblings: the _time constraints carry no %SLACK_RELATION_BOUND_*% hooks, there is no relation_cost_time term in the objective, and RELATION_EQUIVALENCE_TIME is unconditioned over the full time set (which multiplies its equation count by |time| on large scenarios).

How to review

Read the diff in the four GAMS files (parameter_def.gms, sets_maps_def.gms, data_load.gms, model_core.gms) and the Python registration/composition in message.py and scenario_data.py.

The behaviour is exercised by message_ix/tests/test_feature_relation_time.py: a baseline subannual solve, upper- and lower-bound binding via RELATION_CONSTRAINT_UP_TIME / RELATION_CONSTRAINT_LO_TIME, flag composition without an explicit is_*_time set, a zero-bound binding case, an annual-vs-_time equivalence check (annual relation_upper = C against relation_upper_time = C/2 on two equal slices gives equal OBJ and total activity), and a coexistence case where an annual cap and a tighter per-slice cap both bind on the same relation.

PR checklist

  • Continuous integration checks all ✅
  • Add or expand tests; coverage checks both ✅
  • Add, expand, or update documentation.
  • Update release notes.

Extend the generic relation mechanism to subannual time-slice resolution.
The annual relation sums activity over all time slices, so on a time-sliced
scenario it can only bound the annual total; the new variant bounds each
slice independently.

- New parameters relation_upper_time, relation_lower_time and
  relation_activity_time, and flag sets is_relation_upper_time and
  is_relation_lower_time, mirroring the annual trio with a trailing time
  index.
- New GAMS equations RELATION_EQUIVALENCE_TIME, RELATION_CONSTRAINT_UP_TIME
  and RELATION_CONSTRAINT_LO_TIME, plus auxiliary variable
  REL_TIME(r, n, y, h). REL_TIME carries only the activity term; capacity
  factors stay annual because CAP has no subannual dimension.
- Register the new parameters, flag sets and variable in MESSAGE.items so
  they initialize automatically; Scenarios from earlier versions get them
  added empty.
- MESSAGE.enforce composes the is_*_time flag sets from parent-parameter
  keys, so a zero-valued bound is preserved as a valid constraint.
@Wegatriespython Wegatriespython added enh New features & functionality timeslice safe to test labels Jun 10, 2026
@Wegatriespython Wegatriespython changed the title Add subannual generic relation constraints to MESSAGE Add subannual generic relation constraints Jun 10, 2026
The subannual relation items (relation_upper_time, relation_lower_time,
relation_activity_time, is_relation_*_time, REL_TIME) are registered in
MESSAGE.items and initialized on every scenario, so Reporter.from_scenario
auto-derives keys for them. Add the resulting 306 keys to the
test_from_scenario golden set.
@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.0%. Comparing base (b0ded63) to head (a881b2e).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##            main   #1020     +/-   ##
=======================================
- Coverage   92.3%   92.0%   -0.3%     
=======================================
  Files         60      61      +1     
  Lines       5260    5392    +132     
=======================================
+ Hits        4856    4966    +110     
- Misses       404     426     +22     
Files with missing lines Coverage Δ
message_ix/message.py 97.5% <100.0%> (+<0.1%) ⬆️
message_ix/tests/test_feature_relation_time.py 100.0% <100.0%> (ø)
message_ix/util/scenario_data.py 100.0% <ø> (ø)

... and 3 files with indirect coverage changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enh New features & functionality safe to test timeslice

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant