Skip to content

Commit 41ae7db

Browse files
committed
Avoid RunLockRequired error by using backend functions
1 parent 8f55fcf commit 41ae7db

File tree

2 files changed

+67
-33
lines changed

2 files changed

+67
-33
lines changed

message_ix/util/ixmp4.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import ixmp
2-
import ixmp.backend
32
from ixmp.util.ixmp4 import is_ixmp4backend
43

54

message_ix/util/scenario_setup.py

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,6 @@
22
from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, cast
33

44
import 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-
125
from ixmp import Platform
136
from ixmp.util.ixmp4 import is_ixmp4backend
147

@@ -18,6 +11,13 @@
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+
2121
log = logging.getLogger(__name__)
2222

2323

@@ -75,31 +75,43 @@
7575

7676
def 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

146162
def _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

163181
def 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

248270
def _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

256278
def _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

267291
def _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?
281308
def _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

305334
def 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

Comments
 (0)