modelflowib 2.73__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
modelgrabgdx.py ADDED
@@ -0,0 +1,584 @@
1
+ """Read GAMS GDX files and build ModelFlow model instances.
2
+
3
+ Provides utilities for loading GDX files via gams.transfer, extracting
4
+ free variables indexed by time into wide-format DataFrames, and
5
+ bundling one or more scenarios into a single ModelFlow model object.
6
+
7
+ Key components
8
+ --------------
9
+ GdxDataset
10
+ Dataclass that loads a single GDX file and produces a wide
11
+ timeseries DataFrame of all free variables with a time dimension.
12
+
13
+ model_grab_gdx
14
+ Convenience function that accepts one or more GDX file paths,
15
+ builds GdxDataset instances, finds the common variable set, and
16
+ returns a populated ModelFlow model with all scenarios stored
17
+ in keep_solutions.
18
+
19
+ display_bytype_tables
20
+ Jupyter helper that prints every symbol in a GDX file grouped
21
+ by type (Set, Parameter, Variable, Equation).
22
+
23
+
24
+ requires GAMS instalation and
25
+ pip install "gamsapi[transfer]"
26
+ """
27
+
28
+ from IPython.display import display, Markdown
29
+ import pandas as pd
30
+
31
+ from dataclasses import dataclass, field
32
+ from pathlib import Path
33
+ from concurrent.futures import ThreadPoolExecutor, as_completed
34
+
35
+
36
+ from modelclass import model
37
+
38
+ try:
39
+ from gams import transfer as gt
40
+ HAS_GAMS = True
41
+ except ImportError:
42
+ HAS_GAMS = False
43
+
44
+ def _require_gams(gams_dir):
45
+ if not HAS_GAMS:
46
+ raise ImportError(
47
+ "The 'gams.transfer' package is required but not installed. "
48
+ "Install it with: pip install gams[transfer]"
49
+ )
50
+ if not Path(gams_dir).is_dir():
51
+ raise FileNotFoundError(
52
+ f"GAMS system directory not found: '{gams_dir}'. "
53
+ "Install GAMS from https://www.gams.com or pass the correct gams_dir."
54
+ )
55
+
56
+ def clean_columns(df: pd.DataFrame) -> pd.DataFrame:
57
+ out = df.copy()
58
+ out.columns = (
59
+ out.columns.astype(str)
60
+ .str.replace(r"[()]", "", regex=True) # remove ( )
61
+ .str.replace(r"\s+", "_", regex=True) # blanks/whitespace -> _
62
+ .str.replace(r"[^A-Za-z0-9_]", "_", regex=True) # other chars -> _
63
+ )
64
+ return out
65
+
66
+ def get_gdx_t(filename="baseline.gdx", gams_dir=r"C:\GAMS\47"):
67
+
68
+ _require_gams(gams_dir)
69
+ print(f'\nStart reading {filename}')
70
+ container = gt.Container(filename, system_directory=gams_dir)
71
+ # print(f'Finished reading {filename}')
72
+
73
+ dfs = {}
74
+ bytype = {}
75
+ for name, sym in container:
76
+ tname = type(sym).__name__
77
+ if tname in ("Alias", "UniverseAlias"):
78
+ continue
79
+ bytype.setdefault(tname, []).append(sym) # sym, not the tuple
80
+ dfs[name] = sym.records
81
+ # print(f'Finished tranferring {filename}')
82
+
83
+ return bytype, dfs
84
+
85
+
86
+
87
+
88
+ def display_content(filename="baseline.gdx", gams_dir=r"C:\GAMS\47"):
89
+ """Display one Markdown table per full_typename in Jupyter."""
90
+
91
+ bytype,_ = get_gdx_t(filename=filename, gams_dir=gams_dir)
92
+ for t in sorted(bytype.keys()):
93
+ df = symbols_to_df_t(bytype[t])
94
+ display(Markdown(f"## {t} ({len(df)})"))
95
+ display(Markdown(df.to_markdown(index=False)))
96
+
97
+
98
+
99
+
100
+
101
+
102
+ def bytype_description_to_dict_t(bytype):
103
+ outdict = {
104
+ s.name.upper(): s.description
105
+ for symbollist in bytype.values()
106
+ for s in sorted(symbollist, key=lambda x: x.name)
107
+ }
108
+ return outdict
109
+
110
+
111
+ def symbols_to_df_t(symbols):
112
+ """Convert a list of gams.transfer symbols to a tidy DataFrame."""
113
+ rows = []
114
+ for s in symbols:
115
+ rows.append({
116
+ "name": s.name,
117
+ "dims": s.domain_names,
118
+ "description": s.description,
119
+ })
120
+ return pd.DataFrame(rows).sort_values("name").reset_index(drop=True)
121
+
122
+
123
+
124
+ import numpy as np
125
+
126
+ def free_to_timeseries_t(bytype, dfs, t_name="t", value_col="level", sep="__"):
127
+ var_symbols = bytype.get("Variable", [])
128
+
129
+ selected = [
130
+ s.name for s in var_symbols
131
+ if (s.type == 0 or str(s.type).lower() == "free")
132
+ and s.domain_names
133
+ and s.domain_names[-1] == t_name
134
+ and s.name in dfs
135
+ and dfs[s.name] is not None
136
+ ]
137
+ if not selected:
138
+ return pd.DataFrame()
139
+
140
+ value_cols_set = {"level", "marginal", "lower", "upper", "scale", "value"}
141
+
142
+ chunks = []
143
+ for v in selected:
144
+ df = dfs[v]
145
+ if t_name not in df.columns or len(df) == 0:
146
+ continue
147
+
148
+ col = value_col if value_col in df.columns else next(
149
+ (c for c in df.columns if c.lower() == value_col.lower()), None)
150
+ if col is None:
151
+ continue
152
+
153
+ dims = [c for c in df.columns if c.lower() not in value_cols_set]
154
+ other_dims = [c for c in dims if c != t_name]
155
+
156
+ t_vals = df[t_name].values
157
+ v_vals = df[col].values
158
+
159
+ if other_dims:
160
+ parts = [df[d].values.astype(str) for d in other_dims]
161
+ if len(parts) == 1:
162
+ colkeys = np.char.add(v + sep, parts[0])
163
+ else:
164
+ combined = parts[0]
165
+ for p in parts[1:]:
166
+ combined = np.char.add(np.char.add(combined, sep), p)
167
+ colkeys = np.char.add(v + sep, combined)
168
+ else:
169
+ colkeys = np.full(len(df), v, dtype=object)
170
+
171
+ chunks.append((t_vals, colkeys, v_vals))
172
+
173
+ if not chunks:
174
+ return pd.DataFrame()
175
+
176
+ all_t = np.concatenate([c[0] for c in chunks])
177
+ all_k = np.concatenate([c[1] for c in chunks])
178
+ all_v = np.concatenate([c[2] for c in chunks])
179
+
180
+ out = (
181
+ pd.DataFrame({t_name: all_t, "_k": all_k, "_v": all_v})
182
+ .pivot(index=t_name, columns="_k", values="_v")
183
+ )
184
+ out.index = out.index.astype(int)
185
+ out.columns = out.columns.str.upper()
186
+ out.sort_index(inplace=True)
187
+ out.index.name = t_name
188
+ return out
189
+
190
+
191
+ @dataclass
192
+ class GdxDataset:
193
+ filename: str | Path
194
+ gams_dir: str = r"C:\GAMS\47"
195
+ name: str = field(init=False)
196
+ df: pd.DataFrame = field(init=False)
197
+ var_descriptions: dict[str, str] = field(init=False)
198
+
199
+ def __post_init__(self) -> None:
200
+ self.name = Path(self.filename).stem
201
+ self.bytype, dfs = get_gdx_t(self.filename, self.gams_dir)
202
+ self.df = free_to_timeseries_t(self.bytype, dfs, t_name="t").pipe(clean_columns)
203
+ temp = bytype_description_to_dict_t(self.bytype)
204
+ self.var_descriptions = {
205
+ v: (f"{temp.get(base, base)} [{suffix}]" if sep else temp.get(base, base))
206
+ for v in self.df.columns
207
+ for base, sep, suffix in [v.partition("__")]
208
+ }
209
+ self.info
210
+
211
+
212
+ @property
213
+ def info(self):
214
+ print('\n')
215
+ print(f'From : {self.filename}')
216
+ print(f'Name : {self.name}')
217
+ print(f'Number of variables: {self.df.shape[1]}')
218
+ if len(self.df):
219
+ print(f'Number of periods : {self.df.shape[0]} {self.df.index[0]} to {self.df.index[-1]}')
220
+ else:
221
+ print('Number of periods : 0')
222
+
223
+
224
+ def model_grab_gdx(gdx_files, gams_dir=r"C:\GAMS\47", max_workers=None):
225
+ """Build a model from one or more GDX files.
226
+
227
+ Parameters
228
+ ----------
229
+ gdx_files : str, Path, or list of str/Path
230
+ One or more paths to .gdx files.
231
+ gams_dir : str
232
+ GAMS system directory.
233
+ max_workers : int or None
234
+ Number of threads for parallel GDX loading.
235
+ None = min(len(gdx_files), 8) (auto).
236
+ 1 = sequential (old behaviour, useful for debugging).
237
+ """
238
+ if isinstance(gdx_files, (str, Path)):
239
+ gdx_files = [gdx_files]
240
+
241
+ n_files = len(gdx_files)
242
+
243
+ if n_files == 1 or max_workers == 1:
244
+ # sequential – no thread overhead
245
+ GdxDataset_list = [GdxDataset(f, gams_dir=gams_dir) for f in gdx_files]
246
+ else:
247
+ # parallel – GDX reading releases the GIL (C library),
248
+ # and numpy/pandas pivot work also largely releases it.
249
+ workers = max_workers or min(n_files, 8)
250
+
251
+ # We need to preserve the original file order, so submit
252
+ # with an index and sort afterwards.
253
+ results = [None] * n_files
254
+ with ThreadPoolExecutor(max_workers=workers) as pool:
255
+ futures = {
256
+ pool.submit(GdxDataset, f, gams_dir): i
257
+ for i, f in enumerate(gdx_files)
258
+ }
259
+ for fut in as_completed(futures):
260
+ idx = futures[fut]
261
+ results[idx] = fut.result() # propagates exceptions
262
+ GdxDataset_list = results
263
+
264
+ col_sets = [set(g.df.columns) for g in GdxDataset_list]
265
+ common_cols = sorted(set.intersection(*col_sets))
266
+ common_var_descriptions = {v: GdxDataset_list[0].var_descriptions.get(v, v) for v in common_cols}
267
+
268
+ fmodel = '\n'.join(f'{v}= 42' for v in common_cols)
269
+ mmodel = model(fmodel)
270
+ modelvar_set = set(mmodel.allvar.keys())
271
+ missing = [v for v in common_cols if v not in modelvar_set]
272
+ if missing:
273
+ print(f'Problems with these variable names: \n{missing}')
274
+
275
+ mmodel.basedf = GdxDataset_list[0].df.loc[:, common_cols]
276
+ mmodel.lastdf = GdxDataset_list[0].df.loc[:, common_cols]
277
+ mmodel.keep_solutions = {g.name: g.df.loc[:, common_cols] for g in GdxDataset_list}
278
+ mmodel.smpl()
279
+ mmodel.oldkwargs = {}
280
+ mmodel.var_description = common_var_descriptions
281
+ return mmodel
282
+
283
+
284
+ # =====================================================================
285
+ # Static GDX support - set-indexed models WITHOUT a time dimension
286
+ # (e.g. GAMS CGE models such as JRC DEMETRA).
287
+ #
288
+ # Nothing above this line has been changed: the legacy time-indexed API
289
+ # (get_gdx_t, free_to_timeseries_t, GdxDataset, model_grab_gdx) works
290
+ # exactly as before. The functions below are additions:
291
+ #
292
+ # clean_name - sanitise ONE name with the clean_columns rules
293
+ # static_to_frame - wide DataFrame of ALL variable levels and
294
+ # parameter values, broadcast constant over a
295
+ # year range (no t dimension required)
296
+ # sets_to_lists - ModelFlow LIST statements from GDX sets
297
+ # (subsets -> 0/1 sublists, 2-dim sets ->
298
+ # per-element 0/1 sublists, parameters ->
299
+ # nonzero-pattern sublists, aliases)
300
+ # pair_list - a "pair list" (parallel sublists) for a
301
+ # sparse n-dim set or nonzero parameter,
302
+ # for do PAIRS $ ... {K1} {K2} ... enddo
303
+ # GdxStaticDataset - convenience wrapper bundling the above
304
+ # =====================================================================
305
+
306
+ import re as _re
307
+
308
+ _VALUE_COLS = {"level", "marginal", "lower", "upper", "scale",
309
+ "value", "element_text"}
310
+
311
+
312
+ def clean_name(name) -> str:
313
+ """Sanitise one symbol or set-element name.
314
+
315
+ Same rules as clean_columns (keep the two in sync!):
316
+ remove ( ), whitespace -> _, any other non-alphanumeric -> _,
317
+ upper case. E.g. GAMS SAM account 'i-s' -> 'I_S'.
318
+ """
319
+ out = _re.sub(r"[()]", "", str(name))
320
+ out = _re.sub(r"\s+", "_", out)
321
+ out = _re.sub(r"[^A-Za-z0-9_]", "_", out)
322
+ return out.upper()
323
+
324
+
325
+ def _dims_of(df):
326
+ """Domain columns of a gams.transfer records DataFrame."""
327
+ return [c for c in df.columns if c.lower() not in _VALUE_COLS]
328
+
329
+
330
+ def _records_tuples(dfs, name):
331
+ """The records of a set OR parameter as a set of cleaned tuples.
332
+
333
+ For parameters GDX only stores nonzero records, so membership of a
334
+ tuple == 'parameter is nonzero there' - i.e. a GAMS $-condition.
335
+ """
336
+ df = dfs.get(name)
337
+ if df is None or len(df) == 0:
338
+ return set()
339
+ dims = _dims_of(df)
340
+ if not dims:
341
+ return set()
342
+ return {tuple(clean_name(v) for v in row)
343
+ for row in df[dims].astype(str).values}
344
+
345
+
346
+ def _set_elements(dfs, name):
347
+ """Ordered, cleaned elements of a 1-dim set."""
348
+ df = dfs.get(name)
349
+ if df is None or len(df) == 0:
350
+ return []
351
+ dims = _dims_of(df)
352
+ return [clean_name(v) for v in df[dims[0]].astype(str)]
353
+
354
+
355
+ def static_to_frame(bytype, dfs, years, sep="__",
356
+ include_variables=True, include_parameters=True,
357
+ skip=()):
358
+ """Wide DataFrame of all variable levels and parameter values.
359
+
360
+ Unlike free_to_timeseries_t, no trailing time dimension is
361
+ required: every symbol is flattened to NAME__ELEM1__ELEM2 (same
362
+ sep and cleaning as the legacy path) and broadcast as a constant
363
+ column over `years`. Intended for static GAMS models whose GDX
364
+ dump (execute_unload) holds base-year levels.
365
+
366
+ Parameters
367
+ ----------
368
+ bytype, dfs : output of get_gdx_t
369
+ years : iterable of period labels for the index, e.g. range(2020, 2041)
370
+ skip : symbol names (GAMS spelling) to leave out
371
+ """
372
+ wanted = []
373
+ if include_variables:
374
+ wanted += [(s, "level") for s in bytype.get("Variable", [])]
375
+ if include_parameters:
376
+ wanted += [(s, "value") for s in bytype.get("Parameter", [])]
377
+
378
+ skipset = {str(s).upper() for s in skip}
379
+ data = {}
380
+ for sym, valcol in wanted:
381
+ if sym.name.upper() in skipset:
382
+ continue
383
+ df = dfs.get(sym.name)
384
+ if df is None or len(df) == 0:
385
+ continue
386
+ col = next((c for c in df.columns if c.lower() == valcol), None)
387
+ if col is None:
388
+ continue
389
+ name = clean_name(sym.name)
390
+ dims = _dims_of(df)
391
+ if dims:
392
+ keys = df[dims[0]].astype(str).map(clean_name)
393
+ for d in dims[1:]:
394
+ keys = keys + sep + df[d].astype(str).map(clean_name)
395
+ for k, v in zip(keys, df[col].values):
396
+ data[f"{name}{sep}{k}"] = v
397
+ else: # scalar
398
+ data[name] = df[col].values[0]
399
+
400
+ out = pd.DataFrame(data, index=pd.Index(list(years), name="year"))
401
+ return out
402
+
403
+
404
+ def sets_to_lists(bytype, dfs, base_sets, aliases=None,
405
+ condition_symbols=None, sep="_"):
406
+ """ModelFlow LIST statements generated from the sets of a GDX file.
407
+
408
+ base_sets : GAMS names of the sets that become LISTs. Each yields
409
+
410
+ LIST <B> = <B> : e1 , e2 , ... /
411
+ <SUB> : 1 , 0 , ... / (1-dim conditions)
412
+ <SET2D>_<elem> : 0 , 1 , ... $ (2-dim conditions)
413
+
414
+ condition_symbols : names of sets and/or PARAMETERS to turn into
415
+ sublists (parameters use their nonzero pattern - the GAMS
416
+ $-condition 'par(i) <> 0'). Default: every set in the GDX.
417
+ - 1-dim symbols whose elements all belong to a base set become
418
+ a 0/1 sublist on that base list (possibly on several base
419
+ lists when bases overlap - harmless).
420
+ - 2-dim symbols become per-element 0/1 sublists on BOTH base
421
+ lists: name <SYM>_<element-of-the-other-dimension>.
422
+ - higher dimensions are skipped here: use pair_list for those.
423
+
424
+ aliases : dict alias_name -> base_name (GAMS ALIAS). The alias is
425
+ a FULL copy of the base list block with the key sublist renamed,
426
+ so conditions keep working on the alias (sum(CP CCESN, ...)).
427
+
428
+ Returns one string with all LIST statements.
429
+ """
430
+ all_sets = {s.name: s for s in bytype.get("Set", [])}
431
+ all_params = {s.name: s for s in bytype.get("Parameter", [])}
432
+ if condition_symbols is None:
433
+ condition_symbols = list(all_sets)
434
+
435
+ base_elems = {b: _set_elements(dfs, b) for b in base_sets}
436
+
437
+ def sublines_for(b):
438
+ """The condition sublists belonging to base list b."""
439
+ elems = base_elems[b]
440
+ eset = set(elems)
441
+ lines = []
442
+ for name in condition_symbols:
443
+ sym = all_sets.get(name) or all_params.get(name)
444
+ if sym is None or name in base_sets:
445
+ continue
446
+ members = _records_tuples(dfs, name)
447
+ if not members:
448
+ continue
449
+ nd = len(next(iter(members)))
450
+ cname = clean_name(name)
451
+ if nd == 1:
452
+ mem1 = {t[0] for t in members}
453
+ if mem1 <= eset:
454
+ lines.append(f"{cname} : " + " , ".join(
455
+ "1" if e in mem1 else "0" for e in elems))
456
+ elif nd == 2:
457
+ el1 = {t[0] for t in members}
458
+ el2 = {t[1] for t in members}
459
+ if el2 <= eset: # dim 2 lives on this list
460
+ for e1 in sorted(el1):
461
+ row = {t[1] for t in members if t[0] == e1}
462
+ lines.append(f"{cname}{sep}{e1} : " + " , ".join(
463
+ "1" if e in row else "0" for e in elems))
464
+ if el1 <= eset: # dim 1 lives on this list
465
+ for e2 in sorted(el2):
466
+ row = {t[0] for t in members if t[1] == e2}
467
+ lines.append(f"{cname}{sep}{e2} : " + " , ".join(
468
+ "1" if e in row else "0" for e in elems))
469
+ return lines
470
+
471
+ blocks = {}
472
+ out = []
473
+ for b in base_sets:
474
+ elems = base_elems[b]
475
+ if not elems:
476
+ print(f"sets_to_lists: base set '{b}' empty or missing - skipped")
477
+ continue
478
+ head = f"{clean_name(b)} : " + " , ".join(elems)
479
+ subs = sublines_for(b)
480
+ blocks[b] = (head, subs)
481
+ body = " /\n ".join([head] + subs)
482
+ out.append(f"LIST {clean_name(b)} = {body} $")
483
+
484
+ for alias, b in (aliases or {}).items():
485
+ if b not in blocks:
486
+ print(f"sets_to_lists: alias '{alias}' - base '{b}' missing - skipped")
487
+ continue
488
+ head, subs = blocks[b]
489
+ newhead = f"{clean_name(alias)} : " + head.split(":", 1)[1]
490
+ body = " /\n ".join([newhead] + subs)
491
+ out.append(f"LIST {clean_name(alias)} = {body} $")
492
+
493
+ return "\n\n".join(out)
494
+
495
+
496
+ def pair_list(dfs, name, listname=None, keynames=None):
497
+ """A 'pair list' for a sparse n-dim set or nonzero parameter.
498
+
499
+ One LIST whose parallel sublists hold the tuple components, for
500
+ looping over exactly the active tuples (GAMS sparse domains):
501
+
502
+ LIST FDPAIRS = FF : LAND , LAB , ... /
503
+ A : AMAIZ , AMAIZ , ... $
504
+ do FDPAIRS $ frml <> X__{FF}__{A} = ... $ enddo $
505
+
506
+ Parameters
507
+ ----------
508
+ name : set or parameter name in the GDX (nonzero records)
509
+ listname : LIST name, default = cleaned `name`
510
+ keynames : names of the sublists (the {index} keys); default D1, D2, ...
511
+ """
512
+ members = sorted(_records_tuples(dfs, name))
513
+ if not members:
514
+ return f"! pair_list: '{name}' has no records\n"
515
+ nd = len(members[0])
516
+ keys = [clean_name(k) for k in (keynames or [f"D{i+1}" for i in range(nd)])]
517
+ lname = clean_name(listname or name)
518
+ lines = [f"{k} : " + " , ".join(m[i] for m in members)
519
+ for i, k in enumerate(keys)]
520
+ body = " /\n ".join(lines)
521
+ return f"LIST {lname} = {body} $"
522
+
523
+
524
+ @dataclass
525
+ class GdxStaticDataset:
526
+ """Static (no time dimension) GDX -> ModelFlow building blocks.
527
+
528
+ Wraps get_gdx_t + static_to_frame; sets_to_lists / pair_list are
529
+ exposed as methods. The legacy GdxDataset is untouched - use that
530
+ for time-indexed scenario GDX files.
531
+
532
+ Example
533
+ -------
534
+ >>> g = GdxStaticDataset('10_gdx/demetra_ET_all.gdx',
535
+ ... years=range(2020, 2041))
536
+ >>> print(g.lists(['c', 'a', 'h', 'w'], aliases={'CP': 'c'}))
537
+ >>> basedf = g.df # constant wide frame over the years
538
+ """
539
+ filename: str | Path
540
+ years: object = tuple(range(2021, 2051))
541
+ gams_dir: str = r"C:\GAMS\47"
542
+ name: str = field(init=False)
543
+ df: pd.DataFrame = field(init=False)
544
+ var_descriptions: dict[str, str] = field(init=False)
545
+
546
+ def __post_init__(self) -> None:
547
+ self.name = Path(self.filename).stem
548
+ self.bytype, self.dfs = get_gdx_t(self.filename, self.gams_dir)
549
+ self.df = static_to_frame(self.bytype, self.dfs, self.years)
550
+ temp = bytype_description_to_dict_t(self.bytype)
551
+ self.var_descriptions = {
552
+ v: (f"{temp.get(base, base)} [{suffix}]" if sep_ else temp.get(base, base))
553
+ for v in self.df.columns
554
+ for base, sep_, suffix in [v.partition("__")]
555
+ }
556
+ self.info
557
+
558
+ def lists(self, base_sets, aliases=None, condition_symbols=None):
559
+ return sets_to_lists(self.bytype, self.dfs, base_sets,
560
+ aliases=aliases,
561
+ condition_symbols=condition_symbols)
562
+
563
+ def pairs(self, name, listname=None, keynames=None):
564
+ return pair_list(self.dfs, name, listname=listname,
565
+ keynames=keynames)
566
+
567
+ @property
568
+ def info(self):
569
+ print('\n')
570
+ print(f'From : {self.filename}')
571
+ print(f'Name : {self.name} (static)')
572
+ print(f'Number of columns : {self.df.shape[1]}')
573
+ print(f'Broadcast over : {self.df.index[0]} to {self.df.index[-1]}')
574
+
575
+
576
+ if __name__ == '__main__':
577
+
578
+ #%%
579
+ gdx_files = [
580
+ "non_energy_technology/baseline.gdx",
581
+ "non_energy_technology/shock_carbon_tax.gdx",
582
+ "non_energy_technology/shock_carbon_tax_steps.gdx",
583
+ ]
584
+ mmodel = model_grab_gdx(gdx_files)