Skip to content

Commit ea689e1

Browse files
Add subannual timeslice functionality for nexus module
- Add build_nexus_subannual.py: Integration script for building MESSAGEix-Nexus with monthly timesteps - Add timeslice.py: Modular implementation of subannual timeslice addition - Add Excel template for 12 monthly timeslices (input_data_12_R12.xlsx) - Add monthly water demand data for R12 regions (ssp2_m_water_demands.csv) Enables water-energy nexus modeling with monthly temporal resolution for improved representation of seasonal water availability and energy demand patterns.
1 parent 8e41b67 commit ea689e1

File tree

4 files changed

+166402
-0
lines changed

4 files changed

+166402
-0
lines changed

build_nexus_subannual.py

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
"""
2+
Build MESSAGEix-Nexus scenario with subannual (monthly) timeslices.
3+
4+
This script implements a full pipeline for creating a water-energy nexus model
5+
with monthly temporal resolution:
6+
1. Load base annual scenario
7+
2. Add monthly timeslices
8+
3. Validate timesliced scenario
9+
4. Build nexus module on timesliced scenario
10+
5. Solve final subannual nexus model
11+
"""
12+
13+
import logging
14+
15+
from message_ix_models import Context
16+
from message_ix_models.model.water.build import main as build_nexus
17+
from message_ix_models.model.water.cli import water_ini
18+
from message_ix_models.project.alps.timeslice import add_timeslices
19+
20+
# Configure logging
21+
logging.basicConfig(
22+
level=logging.INFO,
23+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
24+
)
25+
log = logging.getLogger(__name__)
26+
27+
# ========== CONFIGURATION ==========
28+
MODEL = "MESSAGE_GLOBIOM_SSP2_v6.1"
29+
BASE_SCEN = "baseline"
30+
N_TIME = 12 # 12 monthly timeslices
31+
REGIONS = "R12"
32+
33+
# Nexus configuration
34+
RCP = "7p0"
35+
SDG = "baseline"
36+
REL = "low"
37+
SSP = "SSP2"
38+
39+
# Basin filtering for reduced nexus
40+
REDUCED_BASIN = True
41+
FILTER_LIST = [
42+
"10|SAS",
43+
"115|SAS",
44+
"124|SAS",
45+
"12|SAS",
46+
"141|SAS",
47+
"148|SAS",
48+
"15|SAS",
49+
"24|SAS",
50+
"30|SAS",
51+
"50|SAS",
52+
"53|SAS",
53+
"65|SAS",
54+
"66|SAS",
55+
"67|SAS",
56+
"70|SAS",
57+
]
58+
59+
# Timeslice configuration
60+
REMOVE_COOLING = True # Remove cooling techs before adding timeslices (water module adds them back)
61+
UPDATE_RESERVE_MARGIN = False # Set to True if you have peak_demand data in Excel
62+
63+
# Solve configuration
64+
SOLVE_OPTIONS = {
65+
"lpmethod": "4",
66+
"scaind": "-1",
67+
"threads": "16",
68+
"iis": "1"
69+
}
70+
71+
def main():
72+
"""Execute the full subannual nexus pipeline."""
73+
74+
# ========== STEP 1: Load Base Scenario ==========
75+
log.info("=" * 80)
76+
log.info("STEP 1: Loading base scenario")
77+
log.info("=" * 80)
78+
79+
ctx = Context()
80+
ctx.handle_cli_args(url=f"ixmp://ixmp_dev/{MODEL}/{BASE_SCEN}")
81+
82+
sc_base = ctx.get_scenario()
83+
log.info(f"Loaded base scenario: {sc_base.model}/{sc_base.scenario} v{sc_base.version}")
84+
85+
# ========== STEP 2: Clone and Add Timeslices ==========
86+
log.info("=" * 80)
87+
log.info(f"STEP 2: Adding {N_TIME} monthly timeslices")
88+
log.info("=" * 80)
89+
90+
sc_timeslice = sc_base.clone(
91+
model=MODEL,
92+
scenario=f"{BASE_SCEN}_t{N_TIME}",
93+
keep_solution=False
94+
)
95+
log.info(f"Cloned to: {sc_timeslice.model}/{sc_timeslice.scenario} v{sc_timeslice.version}")
96+
97+
# Add timeslices
98+
ctx.regions = REGIONS
99+
sc_timeslice = add_timeslices(
100+
scenario=sc_timeslice,
101+
context=ctx,
102+
n_time=N_TIME,
103+
regions=REGIONS,
104+
remove_cooling_tec=REMOVE_COOLING,
105+
update_reserve_margin=UPDATE_RESERVE_MARGIN,
106+
)
107+
108+
log.info(f"Timeslices added successfully")
109+
110+
# Verify time structure
111+
times = sc_timeslice.set("time")
112+
log.info(f"Time slices in scenario: {list(times)}")
113+
114+
# ========== STEP 3: Validate Timesliced Scenario ==========
115+
log.info("=" * 80)
116+
log.info("STEP 3: Validating timesliced scenario (optional solve)")
117+
log.info("=" * 80)
118+
119+
validate_timeslice = input("Solve timesliced scenario to validate? (y/n): ").lower()
120+
if validate_timeslice == 'y':
121+
log.info("Solving timesliced scenario...")
122+
sc_timeslice.set_as_default()
123+
try:
124+
sc_timeslice.solve(solve_options=SOLVE_OPTIONS)
125+
log.info("Timesliced scenario solved successfully!")
126+
except Exception as e:
127+
log.error(f"Failed to solve timesliced scenario: {e}")
128+
log.error("Continuing anyway to build nexus structure...")
129+
else:
130+
log.info("Skipping validation solve")
131+
132+
# ========== STEP 4: Initialize Water Context ==========
133+
log.info("=" * 80)
134+
log.info("STEP 4: Initializing water/nexus context")
135+
log.info("=" * 80)
136+
137+
ctx.ssp = SSP
138+
water_ini(ctx, regions=REGIONS, time=None) # Will auto-detect monthly timeslices
139+
140+
log.info(f"Detected time structure: {ctx.time}")
141+
142+
# Configure nexus
143+
ctx.nexus_set = "nexus"
144+
ctx.RCP = RCP
145+
ctx.SDG = SDG
146+
ctx.REL = REL
147+
ctx.reduced_basin = REDUCED_BASIN
148+
ctx.filter_list = FILTER_LIST
149+
150+
log.info(f"Nexus configuration:")
151+
log.info(f" SSP: {ctx.ssp}")
152+
log.info(f" RCP: {ctx.RCP}")
153+
log.info(f" SDG: {ctx.SDG}")
154+
log.info(f" REL: {ctx.REL}")
155+
log.info(f" Reduced basins: {ctx.reduced_basin}")
156+
if ctx.reduced_basin:
157+
log.info(f" Filtered basins: {len(ctx.filter_list)} basins")
158+
159+
# ========== STEP 5: Build Nexus on Timesliced Scenario ==========
160+
log.info("=" * 80)
161+
log.info("STEP 5: Building nexus module on timesliced scenario")
162+
log.info("=" * 80)
163+
164+
sc_nexus = sc_timeslice.clone(
165+
model=MODEL,
166+
scenario=f"{BASE_SCEN}_t{N_TIME}_nexus_reduced",
167+
keep_solution=False
168+
)
169+
log.info(f"Cloned to: {sc_nexus.model}/{sc_nexus.scenario} v{sc_nexus.version}")
170+
171+
log.info("Adding nexus structure and data...")
172+
build_nexus(ctx, sc_nexus)
173+
log.info("Nexus build completed")
174+
175+
# ========== STEP 6: Solve Final Model ==========
176+
log.info("=" * 80)
177+
log.info("STEP 6: Solving final subannual nexus model")
178+
log.info("=" * 80)
179+
180+
sc_nexus.set_as_default()
181+
182+
log.info(f"Scenario: {sc_nexus.model}/{sc_nexus.scenario} v{sc_nexus.version}")
183+
log.info(f"Solve options: {SOLVE_OPTIONS}")
184+
185+
proceed = input("Proceed with solve? (y/n): ").lower()
186+
if proceed == 'y':
187+
log.info("Starting solve...")
188+
try:
189+
sc_nexus.solve(solve_options=SOLVE_OPTIONS)
190+
log.info("=" * 80)
191+
log.info("SUCCESS! Subannual nexus model solved successfully")
192+
log.info("=" * 80)
193+
log.info(f"Final scenario: {sc_nexus.model}/{sc_nexus.scenario} v{sc_nexus.version}")
194+
except Exception as e:
195+
log.error(f"Solve failed: {e}")
196+
log.error("Check solver logs for details")
197+
raise
198+
else:
199+
log.info("Solve skipped by user")
200+
log.info(f"Scenario ready for solving: {sc_nexus.model}/{sc_nexus.scenario} v{sc_nexus.version}")
201+
202+
return sc_nexus
203+
204+
205+
if __name__ == "__main__":
206+
try:
207+
scenario = main()
208+
print("\n" + "=" * 80)
209+
print("Pipeline completed successfully!")
210+
print("=" * 80)
211+
except Exception as e:
212+
log.error(f"Pipeline failed: {e}", exc_info=True)
213+
raise
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:3b008bb965b1c5b07a11712886a6c65152da191984a66c732bfbc59dcf892e3b
3+
size 7627

0 commit comments

Comments
 (0)