22from typing import TYPE_CHECKING , Any , Literal , TypeVar , Union , cast
33
44import pandas as pd
5-
6- if TYPE_CHECKING :
7- from ixmp4 import Run
8- from ixmp4 .core import IndexSet , Parameter , Table
9-
10- from message_ix .core import Scenario
11-
125from ixmp import Platform
136from ixmp .util .ixmp4 import is_ixmp4backend
147
1811 DEFAULT_TABLE_DATA ,
1912)
2013
14+ if TYPE_CHECKING :
15+ from ixmp4 import Run
16+ from ixmp4 .core import IndexSet , Parameter , Table
17+ from ixmp4 .data .backend .base import Backend
18+
19+ from message_ix .core import Scenario
20+
2121log = logging .getLogger (__name__ )
2222
2323
7575
7676def add_default_data (scenario : "Scenario" ) -> None :
7777 """Add default data expected in a MESSAGEix Scenario."""
78- if not is_ixmp4backend (scenario .platform ._backend ):
78+ backend = scenario .platform ._backend
79+
80+ if not is_ixmp4backend (backend ):
7981 return
8082
8183 # Get the Run associated with the Scenario
82- run = cast ("Run" , scenario .platform ._backend .index [scenario ])
84+ run = cast ("Run" , backend .index [scenario ])
85+
86+ ixmp4_backend = backend ._backend
8387
8488 # Add IndexSet data
8589 for indexset_data_info in DEFAULT_INDEXSET_DATA :
86- indexset = run .optimization .indexsets .get (name = indexset_data_info .name )
90+ indexset = ixmp4_backend .optimization .indexsets .get (
91+ run_id = run .id , name = indexset_data_info .name
92+ )
8793
8894 # Only add default data if they are missing
8995 # NOTE this works because all DEFAULT_INDEXSET_DATA items have str-type data
9096 if missing := [
9197 item for item in indexset_data_info .data if item not in indexset .data
9298 ]:
93- indexset .add (data = list (missing ))
99+ ixmp4_backend .optimization .indexsets .add_data (
100+ id = indexset .id , data = list (missing )
101+ )
94102
95103 # Add Table data
96104 for table_data_info in DEFAULT_TABLE_DATA :
97- table = run .optimization .tables .get (name = table_data_info .name )
105+ table = ixmp4_backend .optimization .tables .get (
106+ run_id = run .id , name = table_data_info .name
107+ )
98108
99109 # Only add default data if they are missing
100110 # NOTE this works because all DEFAULT_TABLE_DATA items have just one row
101111 if not pd .DataFrame (table_data_info .data ).isin (table .data ).all (axis = None ):
102- table .add (data = table_data_info .data )
112+ ixmp4_backend .optimization .tables .add_data (
113+ id = table .id , data = table_data_info .data
114+ )
103115
104116 # Add Parameter data
105117 for parameter_data_info in DEFAULT_PARAMETER_DATA :
@@ -108,12 +120,16 @@ def add_default_data(scenario: "Scenario") -> None:
108120 # NOTE parameter_data_info.data *must* contain a 'unit' column
109121 check_existence_of_units (platform = scenario .platform , data = parameter_df )
110122
111- parameter = run .optimization .parameters .get (name = parameter_data_info .name )
123+ parameter = ixmp4_backend .optimization .parameters .get (
124+ run_id = run .id , name = parameter_data_info .name
125+ )
112126
113127 # Only add default data if they are missing
114128 # NOTE this works because all DEFAULT_PARAMETER_DATA items have just one row
115129 if not parameter_df .isin (parameter .data ).all (axis = None ):
116- parameter .add (data = parameter_data_info .data )
130+ ixmp4_backend .optimization .parameters .add_data (
131+ id = parameter .id , data = parameter_data_info .data
132+ )
117133
118134
119135# TODO Should this really be a ValueError?
@@ -144,7 +160,9 @@ def ensure_required_indexsets_have_data(scenario: "Scenario") -> None:
144160
145161
146162def _maybe_add_to_table (
147- table : "Table" , data : Union [dict [str , Any ], pd .DataFrame ]
163+ table : "Table" ,
164+ data : Union [dict [str , Any ], pd .DataFrame ],
165+ backend : "Backend" ,
148166) -> None :
149167 """Add (parts of) `data` to `table` if they are missing."""
150168 # NOTE This function doesn't handle empty data as internally, this won't happen
@@ -157,7 +175,7 @@ def _maybe_add_to_table(
157175 new_data = data [~ data .isin (table .data ).all (axis = 1 )]
158176
159177 # Add new rows to table data
160- table . add ( data = new_data )
178+ backend . optimization . tables . add_data ( id = table . id , data = new_data )
161179
162180
163181def compose_dimension_map (
@@ -242,44 +260,55 @@ def _find_all_descendants(parent: T) -> list[T]:
242260 new_map_df = data .merge (pd .DataFrame (descendant_data ), how = "outer" )
243261
244262 # Add new rows to map_{dimension} data
245- _maybe_add_to_table (table = map_parameter , data = new_map_df )
263+ _maybe_add_to_table (
264+ table = map_parameter ,
265+ data = new_map_df ,
266+ backend = scenario .platform ._backend ._backend ,
267+ )
246268
247269
248270def _maybe_add_single_item_to_indexset (
249- indexset : "IndexSet" , data : Union [float , int , str ]
271+ indexset : "IndexSet" , data : Union [float , int , str ], backend : "Backend"
250272) -> None :
251273 """Add `data` to `indexset` if it is missing."""
252274 if data not in list (indexset .data ):
253- indexset . add ( data = data )
275+ backend . optimization . indexsets . add_data ( id = indexset . id , data = data )
254276
255277
256278def _maybe_add_list_to_indexset (
257- indexset : "IndexSet" , data : Union [list [float ], list [int ], list [str ]]
279+ indexset : "IndexSet" ,
280+ data : Union [list [float ], list [int ], list [str ]],
281+ backend : "Backend" ,
258282) -> None :
259283 """Add missing parts of `data` to `indexset`."""
260284 # NOTE missing will always only have one type, but how to tell mypy?
261285 # NOTE mypy recognizes missing as set[float | str]. If int indexsets mysteriously
262286 # turn to float indexsets, look here
263287 if missing := set (data ) - set (indexset .data ):
264- indexset . add ( list (missing )) # type: ignore[arg-type]
288+ backend . optimization . indexsets . add_data ( id = indexset . id , data = list (missing )) # type: ignore[arg-type]
265289
266290
267291def _maybe_add_to_indexset (
268292 indexset : "IndexSet" ,
269293 data : Union [float , int , str , list [float ], list [int ], list [str ]],
294+ backend : "Backend" ,
270295) -> None :
271296 """Add (parts of) `data` to `indexset` if they are missing."""
272297 # NOTE This function doesn't handle empty data as internally, this won't happen
273298 if not isinstance (data , list ):
274- _maybe_add_single_item_to_indexset (indexset = indexset , data = data )
299+ _maybe_add_single_item_to_indexset (
300+ indexset = indexset , data = data , backend = backend
301+ )
275302 else :
276- _maybe_add_list_to_indexset (indexset = indexset , data = data )
303+ _maybe_add_list_to_indexset (indexset = indexset , data = data , backend = backend )
277304
278305
279306# NOTE this could be combined with `_maybe_add_to_table()`, but that function would be
280307# slower than necessary (though likely not by much). Is the maintenance effort worth it?
281308def _maybe_add_to_parameter (
282- parameter : "Parameter" , data : Union [dict [str , Any ], pd .DataFrame ]
309+ parameter : "Parameter" ,
310+ data : Union [dict [str , Any ], pd .DataFrame ],
311+ backend : "Backend" ,
283312) -> None :
284313 """Add (parts of) `data` to `parameter` if they are missing."""
285314 # NOTE This function doesn't handle empty data as internally, this won't happen
@@ -299,7 +328,7 @@ def _maybe_add_to_parameter(
299328 )
300329
301330 # Add new rows to table data
302- parameter . add ( data = new_data )
331+ backend . optimization . parameters . add_data ( id = parameter . id , data = new_data )
303332
304333
305334def compose_maps (scenario : "Scenario" ) -> None :
@@ -324,16 +353,18 @@ def compose_period_map(scenario: "Scenario") -> None:
324353
325354 This covers `assignPeriodMaps()` from ixmp_source.
326355 """
327- if not is_ixmp4backend (scenario .platform ._backend ):
356+ backend = scenario .platform ._backend
357+ if not is_ixmp4backend (backend ):
328358 return
329359
330360 # Get the Run associated with the Scenario
331- run = cast ("Run" , scenario .platform ._backend .index [scenario ])
361+ run = cast ("Run" , backend .index [scenario ])
362+ ixmp4_backend = backend ._backend
332363
333364 # TODO Included here in ixmp_source; this should likely move to add_default_data
334365 # Add one default item to 'type_year'
335366 type_year = run .optimization .indexsets .get (name = "type_year" )
336- _maybe_add_to_indexset (indexset = type_year , data = "cumulative" )
367+ _maybe_add_to_indexset (indexset = type_year , data = "cumulative" , backend = ixmp4_backend )
337368
338369 cat_year = run .optimization .tables .get (name = "cat_year" )
339370 cat_year_df = pd .DataFrame (cat_year .data )
@@ -363,17 +394,20 @@ def compose_period_map(scenario: "Scenario") -> None:
363394 # Ensure that years are sorted
364395 sorted_years = sorted (years )
365396 if years != sorted_years :
366- year . remove ( data = years )
367- year . add ( data = sorted_years )
397+ ixmp4_backend . optimization . indexsets . remove_data ( id = year . id , data = years )
398+ ixmp4_backend . optimization . indexsets . add_data ( id = year . id , data = sorted_years )
368399
369400 # Store years within the model horizon
370401 for y in sorted_years :
371402 if first_model_year is None or first_model_year <= y :
372403 y_str = str (y )
373- _maybe_add_to_indexset (indexset = type_year , data = y_str )
404+ _maybe_add_to_indexset (
405+ indexset = type_year , data = y_str , backend = ixmp4_backend
406+ )
374407 _maybe_add_to_table (
375408 table = cat_year ,
376409 data = {"type_year" : ["cumulative" , y_str ], "year" : [y_str , y_str ]},
410+ backend = ixmp4_backend ,
377411 )
378412
379413 # Initialize duration_period with this data
@@ -394,6 +428,7 @@ def compose_period_map(scenario: "Scenario") -> None:
394428 "values" : durations ,
395429 "units" : ["y" ] * len (sorted_years ),
396430 },
431+ backend = ixmp4_backend ,
397432 )
398433
399434
0 commit comments