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.
- modelBLfunk.py +180 -0
- model_Excel.py +332 -0
- model_cvx.py +139 -0
- model_dynare.py +173 -0
- model_financial_stability.py +88 -0
- model_latex.py +497 -0
- model_latex_class.py +808 -0
- model_parquet_mixin.py +424 -0
- modelclass.py +9828 -0
- modelconstruct.py +1496 -0
- modelconstruct_estimation.py +2872 -0
- modeldash.py +265 -0
- modeldashboot.py +202 -0
- modeldashsidebar.py +456 -0
- modeldekom.py +651 -0
- modeldiff.py +561 -0
- modeldisplay.py +550 -0
- modelestimation.py +1776 -0
- modelestimator_new.py +2613 -0
- modelflowib-2.73.dist-info/METADATA +156 -0
- modelflowib-2.73.dist-info/RECORD +44 -0
- modelflowib-2.73.dist-info/WHEEL +5 -0
- modelflowib-2.73.dist-info/licenses/license.md +10 -0
- modelflowib-2.73.dist-info/top_level.txt +39 -0
- modelgrab.py +318 -0
- modelgrabgdx.py +584 -0
- modelgrabwf2.py +1107 -0
- modelhelp.py +543 -0
- modelhtml.py +606 -0
- modelinvert.py +250 -0
- modeljupyter.py +824 -0
- modeljupytermagic.py +813 -0
- modelmacrograb.py +98 -0
- modelmanipulation.py +1461 -0
- modelmf.py +349 -0
- modelnet.py +114 -0
- modelnewton.py +2178 -0
- modelnormalize.py +430 -0
- modelpattern.py +428 -0
- modelreport.py +2187 -0
- modeluserfunk.py +97 -0
- modelvis.py +1038 -0
- modelwidget.py +718 -0
- modelwidget_input.py +1933 -0
model_parquet_mixin.py
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Parquet_Mixin: Optional fast model dump/load using Feather for large models.
|
|
4
|
+
|
|
5
|
+
By default ``modeldump`` uses the original JSON/gzip format (``.pcim``).
|
|
6
|
+
For large models (CGE etc.) pass ``large=True`` to get the fast
|
|
7
|
+
Feather/zip format (``.pcimz``).
|
|
8
|
+
|
|
9
|
+
``modelload`` auto-detects the format — no extra parameter needed.
|
|
10
|
+
|
|
11
|
+
Archive layout for large=True::
|
|
12
|
+
|
|
13
|
+
model.pcimz
|
|
14
|
+
├── metadata.json # all scalar/dict fields (no DataFrames)
|
|
15
|
+
├── current_per.json # current_per series (small, keep as JSON)
|
|
16
|
+
├── lastdf.feather # the lastdf DataFrame
|
|
17
|
+
├── keep/Baseline.feather # one file per keep_solutions entry
|
|
18
|
+
└── keep/Scenario1.feather
|
|
19
|
+
|
|
20
|
+
Requires: pyarrow (``pip install pyarrow``) — only when large=True.
|
|
21
|
+
|
|
22
|
+
Usage
|
|
23
|
+
-----
|
|
24
|
+
Put ``Parquet_Mixin`` *before* ``Zip_Mixin`` and ``Json_Mixin`` in the MRO::
|
|
25
|
+
|
|
26
|
+
class model(Parquet_Mixin, Zip_Mixin, Json_Mixin, ...):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
# normal models — old behaviour, .pcim
|
|
30
|
+
mmodel.modeldump('mymodel/mymodel', keep=True)
|
|
31
|
+
|
|
32
|
+
# large CGE models — fast feather, .pcimz
|
|
33
|
+
mmodel.modeldump('mymodel/mymodel', keep=True, large=True)
|
|
34
|
+
|
|
35
|
+
# load — auto-detects format
|
|
36
|
+
mmodel, df = model.modelload('mymodel/mymodel')
|
|
37
|
+
|
|
38
|
+
@author: Claude / Ib
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
import json
|
|
42
|
+
import zipfile
|
|
43
|
+
import io
|
|
44
|
+
from pathlib import Path
|
|
45
|
+
from io import BytesIO, StringIO
|
|
46
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
47
|
+
|
|
48
|
+
import pandas as pd
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
# helpers
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
def _period_series_to_json(series):
|
|
56
|
+
"""Serialize a PeriodIndex-like Series to JSON, handling Pandas 2.x."""
|
|
57
|
+
def period_to_dict(period):
|
|
58
|
+
return {
|
|
59
|
+
"day": period.day,
|
|
60
|
+
"day_of_week": period.day_of_week,
|
|
61
|
+
"day_of_year": period.day_of_year,
|
|
62
|
+
"dayofweek": period.day_of_week,
|
|
63
|
+
"dayofyear": period.day_of_year,
|
|
64
|
+
"days_in_month": period.days_in_month,
|
|
65
|
+
"daysinmonth": period.days_in_month,
|
|
66
|
+
"end_time": int(period.end_time.timestamp() * 1000),
|
|
67
|
+
"freqstr": period.freqstr,
|
|
68
|
+
"hour": period.hour,
|
|
69
|
+
"is_leap_year": period.is_leap_year,
|
|
70
|
+
"minute": period.minute,
|
|
71
|
+
"month": period.month,
|
|
72
|
+
"ordinal": period.ordinal,
|
|
73
|
+
"quarter": period.quarter,
|
|
74
|
+
"qyear": period.qyear,
|
|
75
|
+
"second": period.second,
|
|
76
|
+
"start_time": int(period.start_time.timestamp() * 1000),
|
|
77
|
+
"week": period.week,
|
|
78
|
+
"weekday": period.weekday,
|
|
79
|
+
"weekofyear": period.week,
|
|
80
|
+
"year": period.year,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
result_dict = {str(idx): period_to_dict(p) for idx, p in enumerate(series)}
|
|
84
|
+
return json.dumps(result_dict)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _df_to_feather_bytes(df):
|
|
88
|
+
"""Serialize a DataFrame to in-memory Feather/Arrow IPC bytes.
|
|
89
|
+
|
|
90
|
+
Written uncompressed so the outer zip archive can apply deflate
|
|
91
|
+
in a single pass for a good compression ratio. No Thrift layer,
|
|
92
|
+
so no size-limit issues like Parquet has.
|
|
93
|
+
|
|
94
|
+
The index is stored as a regular column with a marker prefix
|
|
95
|
+
so that PeriodIndex and other non-default index types survive
|
|
96
|
+
the round-trip. RangeIndex is left as-is (feather handles it).
|
|
97
|
+
"""
|
|
98
|
+
buf = BytesIO()
|
|
99
|
+
|
|
100
|
+
if isinstance(df.index, pd.RangeIndex):
|
|
101
|
+
# feather handles RangeIndex natively — nothing to do
|
|
102
|
+
df.to_feather(buf, compression='uncompressed')
|
|
103
|
+
return buf.getvalue()
|
|
104
|
+
|
|
105
|
+
df_out = df.reset_index()
|
|
106
|
+
idx_col = df_out.columns[0]
|
|
107
|
+
|
|
108
|
+
if hasattr(df.index, 'freqstr'):
|
|
109
|
+
# PeriodIndex → convert to string, encode freq in column name
|
|
110
|
+
new_name = f'__period_index__|{df.index.freqstr}|{df.index.name or ""}'
|
|
111
|
+
df_out = df_out.rename(columns={idx_col: new_name})
|
|
112
|
+
df_out[new_name] = df_out[new_name].astype(str)
|
|
113
|
+
else:
|
|
114
|
+
# integer/datetime/other index → mark with prefix
|
|
115
|
+
new_name = f'__index__|{idx_col if idx_col is not None else ""}'
|
|
116
|
+
df_out = df_out.rename(columns={idx_col: new_name})
|
|
117
|
+
|
|
118
|
+
df_out.to_feather(buf, compression='uncompressed')
|
|
119
|
+
return buf.getvalue()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _df_from_feather_bytes(raw_bytes):
|
|
123
|
+
"""Deserialize a DataFrame from in-memory Feather bytes,
|
|
124
|
+
restoring the original index (including PeriodIndex)."""
|
|
125
|
+
buf = BytesIO(raw_bytes)
|
|
126
|
+
df = pd.read_feather(buf)
|
|
127
|
+
|
|
128
|
+
first_col = df.columns[0]
|
|
129
|
+
if not isinstance(first_col, str):
|
|
130
|
+
return df
|
|
131
|
+
|
|
132
|
+
if first_col.startswith('__period_index__'):
|
|
133
|
+
parts = first_col.split('|')
|
|
134
|
+
freq = parts[1]
|
|
135
|
+
idx_name = parts[2] if parts[2] else None
|
|
136
|
+
df.index = pd.PeriodIndex(df[first_col], freq=freq, name=idx_name)
|
|
137
|
+
df = df.drop(columns=[first_col])
|
|
138
|
+
elif first_col.startswith('__index__'):
|
|
139
|
+
parts = first_col.split('|', 1)
|
|
140
|
+
idx_name = parts[1] if parts[1] else None
|
|
141
|
+
df = df.set_index(first_col)
|
|
142
|
+
df.index.name = idx_name
|
|
143
|
+
|
|
144
|
+
# no prefix → RangeIndex, leave as-is
|
|
145
|
+
return df
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
# Mixin
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
class Parquet_Mixin:
|
|
153
|
+
"""Optional fast dump/load for large models.
|
|
154
|
+
|
|
155
|
+
``modeldump``
|
|
156
|
+
*large=False* (default) → delegates to the original JSON/gzip
|
|
157
|
+
path via ``Zip_Mixin.modeldump`` / ``Json_Mixin.modeldump_base``.
|
|
158
|
+
*large=True* → writes a ``.pcimz`` feather/zip archive.
|
|
159
|
+
|
|
160
|
+
``modelload``
|
|
161
|
+
Auto-detects format. Zip archive → fast feather path.
|
|
162
|
+
Anything else → legacy JSON/gzip path.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
# ── dump ──────────────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
def modeldump(self, file_path='', keep=False, large=False, compact=False, **kwargs):
|
|
168
|
+
"""Dump model to disk.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
file_path : str
|
|
173
|
+
Destination path.
|
|
174
|
+
keep : bool
|
|
175
|
+
If True, also persist ``self.keep_solutions``.
|
|
176
|
+
large : bool
|
|
177
|
+
If False (default), use the original JSON/gzip ``.pcim`` format.
|
|
178
|
+
If True, use the fast Feather/zip ``.pcimz`` format —
|
|
179
|
+
recommended for large CGE models with many variables.
|
|
180
|
+
compact : bool
|
|
181
|
+
If True (only effective when large=True), store float64
|
|
182
|
+
columns as float32 to roughly halve file size.
|
|
183
|
+
On load the data is restored to float64.
|
|
184
|
+
"""
|
|
185
|
+
if not large:
|
|
186
|
+
# ── original JSON/gzip path ──────────────────────────────────
|
|
187
|
+
return super().modeldump(file_path=file_path, keep=keep, **kwargs)
|
|
188
|
+
|
|
189
|
+
# ── fast feather/zip path ────────────────────────────────────────
|
|
190
|
+
pathname = Path(file_path)
|
|
191
|
+
if not pathname.suffix:
|
|
192
|
+
pathname = pathname.with_suffix('.pcimz')
|
|
193
|
+
pathname.parent.mkdir(parents=True, exist_ok=True)
|
|
194
|
+
|
|
195
|
+
# --- serialise current_per (small – keep as JSON) ----------------
|
|
196
|
+
try:
|
|
197
|
+
current_per_json = pd.Series(self.current_per).to_json()
|
|
198
|
+
except Exception:
|
|
199
|
+
current_per_json = _period_series_to_json(
|
|
200
|
+
pd.Series(self.current_per)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# --- metadata dict (everything *except* DataFrames) --------------
|
|
204
|
+
metadata = {
|
|
205
|
+
'version': '2.00',
|
|
206
|
+
'format': 'feather_zip',
|
|
207
|
+
'frml': self.equations,
|
|
208
|
+
'modelname': self.name,
|
|
209
|
+
'oldkwargs': self.oldkwargs,
|
|
210
|
+
'var_description': self.var_description,
|
|
211
|
+
'equations_latex': self.equations_latex,
|
|
212
|
+
'wb_MFMSAOPTIONS': (
|
|
213
|
+
self.wb_MFMSAOPTIONS
|
|
214
|
+
if hasattr(self, 'wb_MFMSAOPTIONS')
|
|
215
|
+
else ''
|
|
216
|
+
),
|
|
217
|
+
'var_groups': self.var_groups,
|
|
218
|
+
'reports': self.reports,
|
|
219
|
+
'model_description': self.model_description,
|
|
220
|
+
'eviews_dict': self.eviews_dict,
|
|
221
|
+
'substitution': self.substitution,
|
|
222
|
+
'keep_names': list(self.keep_solutions.keys()) if keep else [],
|
|
223
|
+
'compact': compact,
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# --- optionally downcast to float32 for storage -------------------
|
|
227
|
+
def _maybe_compact(df):
|
|
228
|
+
if not compact:
|
|
229
|
+
return df
|
|
230
|
+
return pd.DataFrame(
|
|
231
|
+
df.values.astype('float32'), index=df.index, columns=df.columns
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# --- write the zip archive ---------------------------------------
|
|
235
|
+
# compact already halves raw size; skip deflate to save CPU on load
|
|
236
|
+
zip_compression = zipfile.ZIP_STORED if compact else zipfile.ZIP_DEFLATED
|
|
237
|
+
with zipfile.ZipFile(
|
|
238
|
+
pathname, 'w', compression=zip_compression
|
|
239
|
+
) as zf:
|
|
240
|
+
# metadata
|
|
241
|
+
zf.writestr('metadata.json', json.dumps(metadata))
|
|
242
|
+
# current_per
|
|
243
|
+
zf.writestr('current_per.json', current_per_json)
|
|
244
|
+
# lastdf → feather
|
|
245
|
+
zf.writestr('lastdf.feather', _df_to_feather_bytes(_maybe_compact(self.lastdf)))
|
|
246
|
+
# keep_solutions → one feather each, indexed by position
|
|
247
|
+
if keep:
|
|
248
|
+
for i, (name, df) in enumerate(self.keep_solutions.items()):
|
|
249
|
+
zf.writestr(
|
|
250
|
+
f'keep/{i}.feather',
|
|
251
|
+
_df_to_feather_bytes(_maybe_compact(df)),
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
print(f'Model dumped (feather/zip): {pathname}')
|
|
255
|
+
|
|
256
|
+
# ── load ──────────────────────────────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
@classmethod
|
|
259
|
+
def modelload(
|
|
260
|
+
cls,
|
|
261
|
+
infile,
|
|
262
|
+
funks=[],
|
|
263
|
+
run=False,
|
|
264
|
+
keep_json=False,
|
|
265
|
+
default_url=(
|
|
266
|
+
r'https://raw.githubusercontent.com/IbHansen/'
|
|
267
|
+
r'modelflow-manual/main/model_repo/'
|
|
268
|
+
),
|
|
269
|
+
**kwargs,
|
|
270
|
+
):
|
|
271
|
+
"""Load a model — auto-detects ``.pcimz`` vs ``.pcim`` format.
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
infile : str
|
|
276
|
+
File name or URL.
|
|
277
|
+
funks : list
|
|
278
|
+
User-supplied functions for the model.
|
|
279
|
+
run : bool
|
|
280
|
+
If True, simulate after loading.
|
|
281
|
+
keep_json : bool
|
|
282
|
+
If True, stash the raw metadata dict on the model instance.
|
|
283
|
+
**kwargs
|
|
284
|
+
Forwarded to the simulation when *run=True*.
|
|
285
|
+
|
|
286
|
+
Returns
|
|
287
|
+
-------
|
|
288
|
+
(model, DataFrame)
|
|
289
|
+
"""
|
|
290
|
+
import datetime
|
|
291
|
+
|
|
292
|
+
pinfile = Path(infile.replace('\\', '/'))
|
|
293
|
+
if not pinfile.suffix:
|
|
294
|
+
# try .pcimz first, fall back to .pcim
|
|
295
|
+
if pinfile.with_suffix('.pcimz').exists():
|
|
296
|
+
pinfile = pinfile.with_suffix('.pcimz')
|
|
297
|
+
else:
|
|
298
|
+
pinfile = pinfile.with_suffix('.pcim')
|
|
299
|
+
|
|
300
|
+
# ── detect format ────────────────────────────────────────────────
|
|
301
|
+
if pinfile.exists() and zipfile.is_zipfile(pinfile):
|
|
302
|
+
return cls._load_feather_zip(
|
|
303
|
+
pinfile, funks=funks, run=run,
|
|
304
|
+
keep_json=keep_json, **kwargs,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# ── fallback to legacy JSON/gzip path ────────────────────────────
|
|
308
|
+
return super(Parquet_Mixin, cls).modelload(
|
|
309
|
+
infile, funks=funks, run=run,
|
|
310
|
+
keep_json=keep_json, default_url=default_url,
|
|
311
|
+
**kwargs,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# ── internal: load from feather/zip ──────────────────────────────────
|
|
315
|
+
|
|
316
|
+
@classmethod
|
|
317
|
+
def _load_feather_zip(cls, pinfile, funks=[], run=False,
|
|
318
|
+
keep_json=False, **kwargs):
|
|
319
|
+
"""Read a .pcimz archive and reconstruct the model.
|
|
320
|
+
|
|
321
|
+
Deserialization of feather DataFrames runs in threads
|
|
322
|
+
(pyarrow C code releases the GIL) overlapped with model
|
|
323
|
+
construction.
|
|
324
|
+
"""
|
|
325
|
+
import datetime
|
|
326
|
+
|
|
327
|
+
def make_current_from_quarters(base, json_current_per):
|
|
328
|
+
start, end = json_current_per[[0, -1]]
|
|
329
|
+
start_per = datetime.datetime(
|
|
330
|
+
start['qyear'], start['month'], start['day']
|
|
331
|
+
)
|
|
332
|
+
end_per = datetime.datetime(
|
|
333
|
+
end['qyear'], end['month'], end['day']
|
|
334
|
+
)
|
|
335
|
+
current_dates = pd.period_range(
|
|
336
|
+
start_per, end_per, freq=start['freqstr']
|
|
337
|
+
)
|
|
338
|
+
base_dates = pd.period_range(
|
|
339
|
+
base.index[0], base.index[-1], freq=start['freqstr']
|
|
340
|
+
)
|
|
341
|
+
base.index = base_dates
|
|
342
|
+
return base, current_dates
|
|
343
|
+
|
|
344
|
+
print(f'Feather/zip file read: {pinfile}')
|
|
345
|
+
|
|
346
|
+
# ── phase 1: read raw bytes from zip (sequential, fast) ──────────
|
|
347
|
+
with zipfile.ZipFile(pinfile, 'r') as zf:
|
|
348
|
+
meta = json.loads(zf.read('metadata.json'))
|
|
349
|
+
current_per_bytes = zf.read('current_per.json')
|
|
350
|
+
lastdf_bytes = zf.read('lastdf.feather')
|
|
351
|
+
keep_bytes = {}
|
|
352
|
+
for i, name in enumerate(meta.get('keep_names', [])):
|
|
353
|
+
keep_bytes[name] = zf.read(f'keep/{i}.feather')
|
|
354
|
+
|
|
355
|
+
# ── phase 2: deserialize in parallel, overlap with model build ───
|
|
356
|
+
current_per = pd.read_json(
|
|
357
|
+
StringIO(current_per_bytes.decode('utf-8')),
|
|
358
|
+
typ='series',
|
|
359
|
+
).values
|
|
360
|
+
|
|
361
|
+
with ThreadPoolExecutor(max_workers=max(1, 1 + len(keep_bytes))) as pool:
|
|
362
|
+
# submit all DataFrame deserializations
|
|
363
|
+
lastdf_future = pool.submit(_df_from_feather_bytes, lastdf_bytes)
|
|
364
|
+
keep_futures = {
|
|
365
|
+
name: pool.submit(_df_from_feather_bytes, raw)
|
|
366
|
+
for name, raw in keep_bytes.items()
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
# while threads work, build the model object (parses equations)
|
|
370
|
+
frml = meta['frml']
|
|
371
|
+
modelname = meta['modelname']
|
|
372
|
+
mmodel = cls(frml, modelname=modelname, funks=funks, **kwargs)
|
|
373
|
+
mmodel.oldkwargs = meta.get('oldkwargs', {})
|
|
374
|
+
mmodel.json_current_per = current_per
|
|
375
|
+
mmodel.set_var_description(meta.get('var_description', {}))
|
|
376
|
+
mmodel.equations_latex = meta.get('equations_latex', '')
|
|
377
|
+
|
|
378
|
+
if meta.get('wb_MFMSAOPTIONS', None):
|
|
379
|
+
mmodel.wb_MFMSAOPTIONS = meta['wb_MFMSAOPTIONS']
|
|
380
|
+
|
|
381
|
+
mmodel.var_groups = meta.get('var_groups', {})
|
|
382
|
+
mmodel.reports = meta.get('reports', {})
|
|
383
|
+
mmodel.model_description = meta.get('model_description', '')
|
|
384
|
+
mmodel.eviews_dict = meta.get('eviews_dict', {})
|
|
385
|
+
mmodel.substitution = meta.get('substitution', {})
|
|
386
|
+
|
|
387
|
+
# collect deserialized DataFrames
|
|
388
|
+
lastdf = lastdf_future.result()
|
|
389
|
+
mmodel.keep_solutions = {
|
|
390
|
+
name: fut.result() for name, fut in keep_futures.items()
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if keep_json:
|
|
394
|
+
mmodel.json_keep = meta
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
lastdf, current_per = make_current_from_quarters(
|
|
398
|
+
lastdf, current_per
|
|
399
|
+
)
|
|
400
|
+
except Exception:
|
|
401
|
+
pass
|
|
402
|
+
|
|
403
|
+
if mmodel.model_description:
|
|
404
|
+
print(f'Model: {mmodel.model_description}')
|
|
405
|
+
|
|
406
|
+
if run:
|
|
407
|
+
if (start := kwargs.get('start', False)) and (
|
|
408
|
+
end := kwargs.get('end', False)
|
|
409
|
+
):
|
|
410
|
+
current_per = mmodel.smpl(start, end, lastdf)
|
|
411
|
+
newkwargs = {
|
|
412
|
+
k: v
|
|
413
|
+
for k, v in kwargs.items()
|
|
414
|
+
if k not in {'start', 'end'}
|
|
415
|
+
}
|
|
416
|
+
else:
|
|
417
|
+
newkwargs = kwargs
|
|
418
|
+
|
|
419
|
+
res = mmodel(lastdf, current_per[0], current_per[-1], **newkwargs)
|
|
420
|
+
return mmodel, res
|
|
421
|
+
else:
|
|
422
|
+
mmodel.current_per = current_per
|
|
423
|
+
mmodel.basedf = lastdf.copy()
|
|
424
|
+
return mmodel, lastdf
|