modelflowib 2.86__tar.gz → 2.87__tar.gz
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.
- {modelflowib-2.86 → modelflowib-2.87}/PKG-INFO +1 -1
- {modelflowib-2.86 → modelflowib-2.87}/modelclass.py +53 -16
- {modelflowib-2.86 → modelflowib-2.87}/modelflowib.egg-info/PKG-INFO +1 -1
- {modelflowib-2.86 → modelflowib-2.87}/modelreport.py +189 -81
- {modelflowib-2.86 → modelflowib-2.87}/modelsolver_ng.py +36 -3
- {modelflowib-2.86 → modelflowib-2.87}/pyproject.toml +1 -1
- {modelflowib-2.86 → modelflowib-2.87}/README.md +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/license.md +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelBLfunk.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/model_Excel.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/model_cvx.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/model_dynare.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/model_financial_stability.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/model_latex.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/model_latex_class.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/model_parquet_mixin.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelcausality.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelconstruct.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelconstruct_estimation.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modeldash.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modeldashboot.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modeldashsidebar.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modeldekom.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modeldiff.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modeldisplay.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelestimation.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelestimator_new.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelflowib.egg-info/SOURCES.txt +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelflowib.egg-info/dependency_links.txt +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelflowib.egg-info/requires.txt +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelflowib.egg-info/top_level.txt +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelgrab.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelgrabgdx.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelgrabwf2.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelhelp.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelhtml.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelinput_shiny.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelinvert.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modeljupyter.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modeljupytermagic.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelmacrograb.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelmanipulation.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelmf.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelnet.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelnewton.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelnormalize.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelpattern.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modeluserfunk.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelvis.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelwidget.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelwidget_core.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/modelwidget_input.py +0 -0
- {modelflowib-2.86 → modelflowib-2.87}/setup.cfg +0 -0
|
@@ -7215,9 +7215,14 @@ class Solver_Mixin():
|
|
|
7215
7215
|
if self.previousbase and hasattr(self, 'lastdf'):
|
|
7216
7216
|
self.basedf = self.lastdf.copy(deep=True)
|
|
7217
7217
|
|
|
7218
|
+
# normalized and fully implicit models get their own solvers; a hybrid
|
|
7219
|
+
# model (normalized and ___RES equations mixed) needs a unified Newton
|
|
7220
|
+
# solver, which handles both residual conventions in one system
|
|
7218
7221
|
if self.maxlead >= 1:
|
|
7219
7222
|
if self.normalized:
|
|
7220
7223
|
solverguess = 'newtonstack'
|
|
7224
|
+
elif self.hybrid:
|
|
7225
|
+
solverguess = 'newtonstack_ng'
|
|
7221
7226
|
else:
|
|
7222
7227
|
solverguess = 'newtonstack_un_normalized'
|
|
7223
7228
|
else:
|
|
@@ -7226,6 +7231,8 @@ class Solver_Mixin():
|
|
|
7226
7231
|
solverguess = 'xgenr'
|
|
7227
7232
|
else:
|
|
7228
7233
|
solverguess = 'sim'
|
|
7234
|
+
elif self.hybrid:
|
|
7235
|
+
solverguess = 'newton_ng'
|
|
7229
7236
|
else:
|
|
7230
7237
|
solverguess = 'newton_un_normalized'
|
|
7231
7238
|
|
|
@@ -7390,7 +7397,10 @@ class Solver_Mixin():
|
|
|
7390
7397
|
for i in [j for j in self.allvar.keys() if self.allvar[j]['matrix']]:
|
|
7391
7398
|
# Make sure columns with matrixes are of this type
|
|
7392
7399
|
databank.loc[:, i] = databank.loc[:, i].astype('O')
|
|
7393
|
-
|
|
7400
|
+
# a frame that only lacked model variables (e.g. the X___RES columns
|
|
7401
|
+
# of an implicit model) ends up with the column layout the solver
|
|
7402
|
+
# code was generated for -- that is not new data
|
|
7403
|
+
newdata = not self.eqcolumns(self.genrcolumns, databank.columns)
|
|
7394
7404
|
else:
|
|
7395
7405
|
newdata = False
|
|
7396
7406
|
return newdata, databank
|
|
@@ -8975,7 +8985,15 @@ class Solver_Mixin():
|
|
|
8975
8985
|
- Equation list (rows): endovar (may contain both VAR and VAR___RES)
|
|
8976
8986
|
- Unknowns (columns): declared_endo_list (base VAR names)
|
|
8977
8987
|
- is_residual_eq: mask marking rows using residual form G(y,x)
|
|
8978
|
-
|
|
8988
|
+
|
|
8989
|
+
Which equations are Newton rows:
|
|
8990
|
+
- normalized model: the simultaneous core (coreorder); the recursive
|
|
8991
|
+
prolog / epilog are evaluated in place before / after the Newton
|
|
8992
|
+
iterations of each period
|
|
8993
|
+
- implicit or hybrid model: every equation (solveorder) -- implicit
|
|
8994
|
+
equations can sit outside the core of the dependency graph, which
|
|
8995
|
+
does not see dependencies on a declared unknown
|
|
8996
|
+
|
|
8979
8997
|
Newton step:
|
|
8980
8998
|
residual_i = {
|
|
8981
8999
|
G_i(y,x) if is_residual_eq[i]
|
|
@@ -9034,10 +9052,20 @@ class Solver_Mixin():
|
|
|
9034
9052
|
# ======================================================================
|
|
9035
9053
|
# 1) Setup derivative solver / Jacobian factorization
|
|
9036
9054
|
# ======================================================================
|
|
9037
|
-
|
|
9055
|
+
# Normalized model: Newton on the simultaneous core only; the recursive
|
|
9056
|
+
# prolog / epilog are evaluated in place once before / after the Newton
|
|
9057
|
+
# iterations of each period (as legacy newton).
|
|
9058
|
+
# Implicit or hybrid model: the whole model is one Newton system. The
|
|
9059
|
+
# dependency graph cannot see dependencies on a declared unknown, so
|
|
9060
|
+
# implicit equations can end up in the prolog / epilog -- they must be
|
|
9061
|
+
# Newton rows, and so must the normalized equations around them.
|
|
9062
|
+
core_only = bool(self.normalized and self.use_preorder and len(self.coreorder))
|
|
9063
|
+
newton_endovar = list(self.coreorder if core_only else self.solveorder)
|
|
9064
|
+
|
|
9065
|
+
if (not hasattr(self, 'newton_diff_implicit') or newton_reset
|
|
9066
|
+
or list(self.newton_diff_implicit.endovar) != newton_endovar):
|
|
9038
9067
|
# --- Determine equation list (rows of Jacobian) --------------------
|
|
9039
|
-
endovar =
|
|
9040
|
-
else self.solveorder)
|
|
9068
|
+
endovar = newton_endovar
|
|
9041
9069
|
|
|
9042
9070
|
# Declared endogenous list (true unknowns / columns of Jacobian)
|
|
9043
9071
|
self.declared_endo_list = [ v[:-6] if v.endswith('___RES') else v
|
|
@@ -9127,9 +9155,12 @@ class Solver_Mixin():
|
|
|
9127
9155
|
)
|
|
9128
9156
|
|
|
9129
9157
|
# --------------------------------------------------------------
|
|
9130
|
-
# Recursive "pre" block:
|
|
9158
|
+
# Recursive "pre" block (normalized model): solved exactly, in
|
|
9159
|
+
# place, before the Newton iterations on the core
|
|
9131
9160
|
# --------------------------------------------------------------
|
|
9132
|
-
|
|
9161
|
+
if core_only:
|
|
9162
|
+
self.pronew2d(values, values, row, alfa)
|
|
9163
|
+
|
|
9133
9164
|
# --------------------------------------------------------------
|
|
9134
9165
|
# Newton iterations for this period
|
|
9135
9166
|
# --------------------------------------------------------------
|
|
@@ -9232,14 +9263,15 @@ class Solver_Mixin():
|
|
|
9232
9263
|
values[row, newton_col_unknown] = (
|
|
9233
9264
|
y_old - base_damp * update
|
|
9234
9265
|
)
|
|
9235
|
-
values[row,newton_col_residual] = outvalues[row,newton_col_residual] # TO KEEP THE RES UPDATED
|
|
9236
|
-
|
|
9266
|
+
values[row,newton_col_residual] = outvalues[row,newton_col_residual] # TO KEEP THE RES UPDATED
|
|
9267
|
+
|
|
9237
9268
|
ittotal += 1
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9269
|
+
|
|
9270
|
+
# Recursive "post" block (normalized model): the epilog, in
|
|
9271
|
+
# place, from the converged core
|
|
9272
|
+
if core_only:
|
|
9273
|
+
self.epinew2d(values, values, row, alfa)
|
|
9274
|
+
|
|
9243
9275
|
if newton_conv > newton_absconv:
|
|
9244
9276
|
print(
|
|
9245
9277
|
f'{self.periode} not converged in '
|
|
@@ -9944,8 +9976,13 @@ class Report_Mixin:
|
|
|
9944
9976
|
timeslice (List[int]): Time slice for data display, empty by default.
|
|
9945
9977
|
max_cols (int): Maximum columns when displayed as a string, faulting to the system wide setting.
|
|
9946
9978
|
last_cols (int): Specifies the number of last columns to include in a display slice, particularly in Latex.
|
|
9947
|
-
col_desc (str): text centered on columns
|
|
9948
|
-
|
|
9979
|
+
col_desc (str): text centered on columns
|
|
9980
|
+
scenarios (str): Empty uses basedf/lastdf (the default). '*' uses all keep_solutions,
|
|
9981
|
+
exact names are separated by '|', wildcard patterns by spaces. Impact datatypes compare
|
|
9982
|
+
with the first selected scenario.
|
|
9983
|
+
scenario_layout (str): 'headline' (default) shows the table once per scenario under a
|
|
9984
|
+
headline row ('scenario vs reference' for impacts); 'suffix' appends ' — scenario' to row labels.
|
|
9985
|
+
|
|
9949
9986
|
Returns:
|
|
9950
9987
|
DisplayVarTableDef: Configured table definition object ready for rendering, which includes detailed specifications
|
|
9951
9988
|
such as units and type of transformation based on the datatype.
|
|
@@ -60,8 +60,9 @@ import pandas as pd
|
|
|
60
60
|
import matplotlib.pyplot as plt
|
|
61
61
|
import matplotlib as mpl
|
|
62
62
|
import seaborn as sns
|
|
63
|
-
import fnmatch
|
|
64
|
-
import
|
|
63
|
+
import fnmatch
|
|
64
|
+
import html
|
|
65
|
+
import re
|
|
65
66
|
from matplotlib import dates
|
|
66
67
|
import matplotlib.ticker as ticker
|
|
67
68
|
import matplotlib.gridspec as gridspec
|
|
@@ -253,7 +254,9 @@ class Options:
|
|
|
253
254
|
size (tuple): Tuple specifying the figure size (width, height). Default is (10, 6).
|
|
254
255
|
legend (bool): If True, display legend in plots. Default is True.
|
|
255
256
|
transpose (bool): If True, transpose the data when displaying. Default is False.
|
|
256
|
-
scenarios (str): Text specifying the scenarios for the display. Default is an empty string. if Empty use basedf/lastdf
|
|
257
|
+
scenarios (str): Text specifying the scenarios for the display. Default is an empty string. if Empty use basedf/lastdf
|
|
258
|
+
scenario_layout (str): Scenario tables only. 'headline' (default) groups the whole table under one
|
|
259
|
+
headline row per scenario; 'suffix' appends ' — scenario' to each row label. Transposed tables use 'suffix'.
|
|
257
260
|
smpl (tuple): Tuple specifying start and end periods. Default is ('', '').
|
|
258
261
|
landscape (bool): If True, the table will be displayed in landscape mode. Default is False.
|
|
259
262
|
latex_text (str): Text for a LaTeX output. Default is an empty string.
|
|
@@ -288,6 +291,7 @@ class Options:
|
|
|
288
291
|
legend: bool = True
|
|
289
292
|
transpose : bool = False
|
|
290
293
|
scenarios : str =''
|
|
294
|
+
scenario_layout : str = 'headline'
|
|
291
295
|
smpl : tuple = ('','')
|
|
292
296
|
landscape : bool = False
|
|
293
297
|
|
|
@@ -858,48 +862,123 @@ class DisplayVarTableDef(DisplayDef):
|
|
|
858
862
|
def __post_init__(self):
|
|
859
863
|
super().__post_init__() # Call the parent class's __post_init__
|
|
860
864
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
865
|
+
self.base_last = self.options.scenarios in ('', 'base_last')
|
|
866
|
+
self.unitline = ''
|
|
867
|
+
if not self.base_last:
|
|
868
|
+
# keepswitch falls back to all solutions on an unmatched selector.
|
|
869
|
+
# A report must not silently display a different set of scenarios.
|
|
870
|
+
selector = self.options.scenarios
|
|
871
|
+
sep = ' ' if '*' in selector or '?' in selector else '|'
|
|
872
|
+
patterns = selector.split(sep)
|
|
873
|
+
if not self.mmodel.keep_solutions or any(
|
|
874
|
+
not fnmatch.filter(self.mmodel.keep_solutions, pat)
|
|
875
|
+
for pat in patterns
|
|
876
|
+
):
|
|
877
|
+
raise ValueError(f'No scenarios match part or all of: {selector!r}')
|
|
878
|
+
|
|
879
|
+
with self.mmodel.keepswitch(scenarios=self.options.scenarios,
|
|
880
|
+
base_last=self.base_last):
|
|
881
|
+
self.scenario_names = list(self.mmodel.keep_solutions)
|
|
882
|
+
if not self.base_last and len(self.scenario_names) < 2 and any(
|
|
883
|
+
line.diftype in ('dif', 'difpct') and not line.textlinetype
|
|
884
|
+
for line in self.lines
|
|
885
|
+
):
|
|
886
|
+
raise ValueError('An impact table needs a reference scenario and at least one alternative.')
|
|
887
|
+
with self.mmodel.set_smpl(*self.options.smpl):
|
|
888
|
+
results = [self.make_var_df(line) for line in self.lines]
|
|
889
|
+
self.report_smpl = self.get_report_smpl
|
|
890
|
+
|
|
891
|
+
self.headline = (not self.base_last and not self.options.transpose
|
|
892
|
+
and self.options.scenario_layout == 'headline')
|
|
893
|
+
# display_lines/dfs are the rendered rows: one frame per entry, aligned
|
|
894
|
+
# with the Line that controls its formatting (textline, decimals ...).
|
|
895
|
+
if self.headline:
|
|
896
|
+
self.display_lines, self.dfs = self.make_headline_rows(results)
|
|
897
|
+
else:
|
|
898
|
+
self.display_lines = self.lines
|
|
899
|
+
self.dfs = [self.make_suffix_rows(line, result)
|
|
900
|
+
for line, result in zip(self.lines, results)]
|
|
901
|
+
|
|
866
902
|
if self.options.transpose:
|
|
867
|
-
|
|
868
|
-
|
|
903
|
+
if self.base_last:
|
|
904
|
+
self.df = self.dfs[-1].T
|
|
905
|
+
self.column_decimals = [self.lines[-1].dec] * len(self.df.columns)
|
|
906
|
+
else:
|
|
907
|
+
numeric = [(line, df) for line, df in zip(self.lines, self.dfs)
|
|
908
|
+
if line.textlinetype != 'textline']
|
|
909
|
+
self.df = (pd.concat([df for line, df in numeric]).T if numeric
|
|
910
|
+
else pd.DataFrame(index=self.dfs[0].columns))
|
|
911
|
+
self.column_decimals = [line.dec for line, df in numeric for _ in df.index]
|
|
869
912
|
else:
|
|
870
913
|
self.df = pd.concat( self.dfs )
|
|
871
914
|
# assert 1==2
|
|
872
915
|
return
|
|
873
916
|
|
|
874
|
-
def make_var_df(self, line):
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
else
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
917
|
+
def make_var_df(self, line):
|
|
918
|
+
'''Returns a frame for a textline, else a dict {scenario: frame} with plain row labels'''
|
|
919
|
+
if line.textlinetype == 'textline':
|
|
920
|
+
self.unitline = self.lines[0].centertext
|
|
921
|
+
return pd.DataFrame(np.nan, index=[line.centertext],
|
|
922
|
+
columns=self.mmodel.current_per)
|
|
923
|
+
|
|
924
|
+
results = self.mmodel.keep_get_plotdict_new(
|
|
925
|
+
pat=line.pat, showtype=line.showtype, diftype=line.diftype, by_var=False)
|
|
926
|
+
if self.base_last or line.diftype in ('basedf', 'lastdf'):
|
|
927
|
+
key = next(iter(results)) if line.diftype == 'basedf' else next(reversed(results))
|
|
928
|
+
results = {key: results[key]}
|
|
929
|
+
|
|
930
|
+
return {scenario: self.get_rowdes(df.loc[self.mmodel.current_per].T * line.mul, line).astype('float')
|
|
931
|
+
for scenario, df in results.items()}
|
|
932
|
+
|
|
933
|
+
def make_suffix_rows(self, line, result):
|
|
934
|
+
'''One frame per Line: variable first, scenarios beneath it, labelled "label — scenario"'''
|
|
935
|
+
if line.textlinetype == 'textline':
|
|
936
|
+
return result.astype('float')
|
|
937
|
+
blocks = list(result.values())
|
|
938
|
+
if not self.base_last:
|
|
939
|
+
blocks = [block.set_axis([f'{label} — {scenario}' for label in block.index])
|
|
940
|
+
for scenario, block in result.items()]
|
|
941
|
+
combined = pd.concat(blocks)
|
|
942
|
+
nvars, nscenarios = len(blocks[0]), len(blocks)
|
|
943
|
+
return combined.iloc[[s * nvars + v for v in range(nvars)
|
|
944
|
+
for s in range(nscenarios)]]
|
|
945
|
+
|
|
946
|
+
def make_headline_rows(self, results):
|
|
947
|
+
'''The whole table is repeated once per scenario under a headline row.
|
|
948
|
+
|
|
949
|
+
Leading textlines (heading, unit line) are shown once at the top. A Line
|
|
950
|
+
appears in a scenario block only if it has data for that scenario: base
|
|
951
|
+
datatypes for the reference, impacts for the alternatives. A textline
|
|
952
|
+
inside the table is dropped from a block when the lines it heads are.
|
|
953
|
+
A block with only impact lines is headed "scenario vs reference".
|
|
954
|
+
'''
|
|
955
|
+
first_numeric = next((i for i, line in enumerate(self.lines)
|
|
956
|
+
if line.textlinetype != 'textline'), len(self.lines))
|
|
957
|
+
rows = [(line, result.astype('float')) for line, result in
|
|
958
|
+
zip(self.lines[:first_numeric], results[:first_numeric])]
|
|
959
|
+
reference = self.scenario_names[0]
|
|
960
|
+
|
|
961
|
+
for scenario in self.scenario_names:
|
|
962
|
+
block, pending, after_numeric, impact = [], [], False, set()
|
|
963
|
+
for line, result in zip(self.lines[first_numeric:], results[first_numeric:]):
|
|
964
|
+
if line.textlinetype == 'textline':
|
|
965
|
+
if after_numeric: # a new section starts
|
|
966
|
+
pending, after_numeric = [], False
|
|
967
|
+
pending.append((line, result.astype('float')))
|
|
968
|
+
continue
|
|
969
|
+
after_numeric = True
|
|
970
|
+
if scenario in result:
|
|
971
|
+
block += pending + [(line, result[scenario])]
|
|
972
|
+
pending = []
|
|
973
|
+
impact.add(line.diftype in ('dif', 'difpct'))
|
|
974
|
+
if block:
|
|
975
|
+
headline = f'{scenario} vs {reference}' if impact == {True} else scenario
|
|
976
|
+
headline_line = Line(textlinetype='scenarioline', centertext=headline)
|
|
977
|
+
rows.append((headline_line, pd.DataFrame(np.nan, index=[headline],
|
|
978
|
+
columns=self.mmodel.current_per)))
|
|
979
|
+
rows += block
|
|
980
|
+
|
|
981
|
+
return [line for line, df in rows], [df for line, df in rows]
|
|
903
982
|
|
|
904
983
|
|
|
905
984
|
|
|
@@ -925,36 +1004,43 @@ class DisplayVarTableDef(DisplayDef):
|
|
|
925
1004
|
@property
|
|
926
1005
|
def df_str(self):
|
|
927
1006
|
width = self.options.width
|
|
928
|
-
|
|
1007
|
+
fmt = lambda x, dec: " " * width if pd.isna(x) else f"{x:>{width},.{dec}f}".strip()
|
|
1008
|
+
# The frame is built in one go: assigning rows into a string frame with
|
|
1009
|
+
# iloc fails in pandas 3 (python string storage) when it has one column.
|
|
929
1010
|
if self.options.transpose:
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
# for c in thisdf.columns:
|
|
934
|
-
# df_char.loc[:,c] = thisdf.loc[:,c].apply(lambda x: " " * width if pd.isna(x) else f"{x:>{width},.{dec}f}".strip() )
|
|
935
|
-
for i,_ in enumerate(thisdf.index):
|
|
936
|
-
df_char.iloc[i] = self.df.iloc[i].apply(lambda x: " " * width if pd.isna(x) else f"{x:>{width},.{dec}f}".strip() )
|
|
1011
|
+
thisdf = self.df.loc[self.timeslice,:] if self.timeslice else self.df
|
|
1012
|
+
values = [[fmt(x, dec) for x, dec in zip(row, self.column_decimals)]
|
|
1013
|
+
for row in thisdf.to_numpy()]
|
|
937
1014
|
else:
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
return df_char
|
|
1015
|
+
thisdf = self.df
|
|
1016
|
+
format_decimal = [ line.dec for line,df in zip(self.display_lines,self.dfs) for row in range(len(df))]
|
|
1017
|
+
values = [[fmt(x, dec) for x in row]
|
|
1018
|
+
for row, dec in zip(thisdf.to_numpy(), format_decimal)]
|
|
1019
|
+
df_char = pd.DataFrame(values, index=thisdf.index, columns=thisdf.columns, dtype=object)
|
|
1020
|
+
return df_char
|
|
945
1021
|
|
|
946
1022
|
|
|
947
1023
|
|
|
948
1024
|
@property
|
|
949
1025
|
def df_str_disp(self):
|
|
950
|
-
center = [ (line.textlinetype == 'textline' and line.centertext !='' ) for line,df in zip(self.
|
|
1026
|
+
center = [ (line.textlinetype == 'textline' and line.centertext !='' ) for line,df in zip(self.display_lines,self.dfs) for row in range(len(df))]
|
|
951
1027
|
center_index = [index+1 for index, value in enumerate(center) if value]
|
|
952
1028
|
|
|
953
1029
|
width = self.options.width
|
|
954
|
-
thisdf = self.df_str.loc[:,self.timeslice] if self.timeslice else self.df_str
|
|
955
|
-
|
|
1030
|
+
thisdf = self.df_str.loc[:,self.timeslice] if self.timeslice else self.df_str
|
|
1031
|
+
# Scenario headlines are written across the row afterwards, so a long
|
|
1032
|
+
# name does not widen the label column.
|
|
1033
|
+
rowtypes = [line.textlinetype for line, df in zip(self.display_lines, self.dfs) for row in range(len(df))]
|
|
1034
|
+
headline_index = [index for index, rowtype in enumerate(rowtypes) if rowtype == 'scenarioline']
|
|
1035
|
+
if headline_index:
|
|
1036
|
+
thisdf = thisdf.copy()
|
|
1037
|
+
thisdf.index = ['' if rowtype == 'scenarioline' else label
|
|
1038
|
+
for label, rowtype in zip(thisdf.index, rowtypes)]
|
|
1039
|
+
|
|
956
1040
|
rawdata = thisdf.to_string(max_cols= self.options.max_cols).split('\n')
|
|
957
1041
|
data = center_title_under_years(rawdata,center_index)
|
|
1042
|
+
for index in headline_index:
|
|
1043
|
+
data[index+1] = self.df_str.index[index]
|
|
958
1044
|
# print(*data,sep='\n')
|
|
959
1045
|
# rawdata[0],rawdata[1] = rawdata[1],rawdata[0]
|
|
960
1046
|
out = '\n'.join(data)
|
|
@@ -1026,11 +1112,14 @@ class DisplayVarTableDef(DisplayDef):
|
|
|
1026
1112
|
def tab_to_html(i,df,line):
|
|
1027
1113
|
nonlocal endhtml
|
|
1028
1114
|
out = ''
|
|
1029
|
-
|
|
1115
|
+
style = self.mmodel.ibsstyle(df,use_tooltip=False,dec=line.dec)
|
|
1116
|
+
if not self.base_last:
|
|
1117
|
+
style = style.format_index(escape='html', axis=0)
|
|
1118
|
+
html_all = style.to_html()
|
|
1030
1119
|
splitted_html = HTMLSplitData(html_all)
|
|
1031
1120
|
if i == 0:
|
|
1032
1121
|
caption = f'<caption>{self.mmodel.string_substitution(self.options.title)}</caption>' if self.options.title else ''
|
|
1033
|
-
out = splitted_html.text_before_thead + caption +
|
|
1122
|
+
out = splitted_html.text_before_thead + caption + splitted_html.thead+'\n'
|
|
1034
1123
|
|
|
1035
1124
|
|
|
1036
1125
|
endhtml = splitted_html.text_after_tbody
|
|
@@ -1039,7 +1128,12 @@ class DisplayVarTableDef(DisplayDef):
|
|
|
1039
1128
|
col0 = '<tr><td <th class="row_heading level0 row0" > </td>'
|
|
1040
1129
|
out = out + '\n'+col0 + f"<td colspan='{len(df.columns)}' style='text-align: center;position: sticky; top: 0; background: white; left: 0;'>{line.centertext}</td></tr>"
|
|
1041
1130
|
|
|
1042
|
-
|
|
1131
|
+
elif line.textlinetype == 'scenarioline':
|
|
1132
|
+
# Spans the whole row so a long name does not widen the label column
|
|
1133
|
+
out = (out + '\n' + f"<tr><th colspan='{len(df.columns) + 1}' style='text-align: left; font-weight: bold;'>"
|
|
1134
|
+
+ f"{html.escape(line.centertext)}</th></tr>")
|
|
1135
|
+
|
|
1136
|
+
else:
|
|
1043
1137
|
out = out + splitted_html.tbody
|
|
1044
1138
|
|
|
1045
1139
|
return out
|
|
@@ -1050,6 +1144,8 @@ class DisplayVarTableDef(DisplayDef):
|
|
|
1050
1144
|
|
|
1051
1145
|
|
|
1052
1146
|
outsty = self.make_html_style(thisdf)
|
|
1147
|
+
if not self.base_last:
|
|
1148
|
+
outsty = outsty.format_index(escape='html', axis=1)
|
|
1053
1149
|
|
|
1054
1150
|
if self.options.title:
|
|
1055
1151
|
outsty = outsty.set_caption(self.options.title)
|
|
@@ -1064,11 +1160,11 @@ class DisplayVarTableDef(DisplayDef):
|
|
|
1064
1160
|
|
|
1065
1161
|
else:
|
|
1066
1162
|
thisdfs = [(i,df.loc[:,self.timeslice] if self.timeslice else df, line) for i,(df,line) in
|
|
1067
|
-
enumerate(zip(self.dfs,self.
|
|
1163
|
+
enumerate(zip(self.dfs,self.display_lines))]
|
|
1068
1164
|
|
|
1069
1165
|
out = '\n'.join([tab_to_html(i,df,line) for i,df,line in thisdfs])+'\n'
|
|
1070
1166
|
if self.options.foot:
|
|
1071
|
-
|
|
1167
|
+
foot = f"<tfoot><tr><td colspan='{len(thisdfs[0][1].columns) + 1}' style='text-align: left;'>{self.options.foot}</td></tr></tfoot>"
|
|
1072
1168
|
else:
|
|
1073
1169
|
foot =''
|
|
1074
1170
|
|
|
@@ -1085,11 +1181,19 @@ class DisplayVarTableDef(DisplayDef):
|
|
|
1085
1181
|
def latex(self):
|
|
1086
1182
|
return self.latex_transpose if self.options.transpose else self.latex_straight
|
|
1087
1183
|
|
|
1184
|
+
@staticmethod
|
|
1185
|
+
def _latex_scenario_label(label):
|
|
1186
|
+
# Percent is escaped by the existing final LaTeX replacements below.
|
|
1187
|
+
escapes = {'\\': r'\textbackslash{}', '&': r'\&', '_': r'\_',
|
|
1188
|
+
'#': r'\#', '$': r'\$', '{': r'\{', '}': r'\}',
|
|
1189
|
+
'~': r'\textasciitilde{}', '^': r'\textasciicircum{}'}
|
|
1190
|
+
return ''.join(escapes.get(char, char) for char in str(label))
|
|
1191
|
+
|
|
1088
1192
|
|
|
1089
1193
|
@property
|
|
1090
1194
|
def latex_straight(self):
|
|
1091
1195
|
last_cols = 0
|
|
1092
|
-
rowlines = [ line for line,df in zip(self.
|
|
1196
|
+
rowlines = [ line for line,df in zip(self.display_lines,self.dfs) for row in range(len(df))]
|
|
1093
1197
|
if self.timeslice:
|
|
1094
1198
|
dfs = [self.df_str.loc[:,self.timeslice]]
|
|
1095
1199
|
else:
|
|
@@ -1107,10 +1211,18 @@ class DisplayVarTableDef(DisplayDef):
|
|
|
1107
1211
|
outlist = []
|
|
1108
1212
|
for i,df in enumerate(dfs):
|
|
1109
1213
|
ncol=len(df.columns)
|
|
1110
|
-
|
|
1214
|
+
first_headline = next((line for line in rowlines if line.textlinetype == 'scenarioline'), None)
|
|
1215
|
+
newindex = [fr'&\multicolumn{{{ncol}}}'+'{c}{' + f'{line.latexfont}' + '{' + df.index[i]+'}}'
|
|
1111
1216
|
if line.textlinetype == 'textline'
|
|
1112
|
-
|
|
1113
|
-
|
|
1217
|
+
# spans the whole row, the blank cells are removed with the textline cells below.
|
|
1218
|
+
# Zero width: LaTeX adds any excess width of a multicolumn to the last column.
|
|
1219
|
+
else ('' if line is first_headline else r'\addlinespace ')
|
|
1220
|
+
+ fr'\multicolumn{{{ncol + 1}}}{{l}}{{\makebox[0pt][l]{{\textbf{{'
|
|
1221
|
+
+ self._latex_scenario_label(df.index[i]) + '}}}'
|
|
1222
|
+
if line.textlinetype == 'scenarioline'
|
|
1223
|
+
else (df.index[i] if self.base_last or self.headline
|
|
1224
|
+
else self._latex_scenario_label(df.index[i]))
|
|
1225
|
+
for i, line in enumerate(rowlines)]
|
|
1114
1226
|
|
|
1115
1227
|
df.index = newindex
|
|
1116
1228
|
tabformat = 'l'+'r'*(ncol-last_cols) + ( ('|'+'r'*last_cols) if last_cols else '')
|
|
@@ -1159,6 +1271,9 @@ class DisplayVarTableDef(DisplayDef):
|
|
|
1159
1271
|
def latex_transpose(self):
|
|
1160
1272
|
|
|
1161
1273
|
thisdf = self.df_str.loc[self.timeslice,:] if self.timeslice else self.df_str
|
|
1274
|
+
if not self.base_last:
|
|
1275
|
+
thisdf = thisdf.copy()
|
|
1276
|
+
thisdf.columns = [self._latex_scenario_label(c) for c in thisdf.columns]
|
|
1162
1277
|
tabformat = 'l'+'r'*len(thisdf.columns)
|
|
1163
1278
|
|
|
1164
1279
|
latex_df = (thisdf.style.format(lambda x:x)
|
|
@@ -1525,22 +1640,15 @@ class DisplayKeepFigDef(DisplayDef):
|
|
|
1525
1640
|
return [self.out_html ]
|
|
1526
1641
|
|
|
1527
1642
|
|
|
1528
|
-
@property
|
|
1529
|
-
def fig_tabwidget(self):
|
|
1530
|
-
figlist = {t: htmlwidget_fig(f) for t,f in self.figs.items() }
|
|
1531
|
-
return tabwidget(figlist,tab=False,selected_index=0)
|
|
1532
|
-
|
|
1533
|
-
@property
|
|
1643
|
+
@property
|
|
1534
1644
|
def out_html(self):
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
else:
|
|
1543
|
-
display(self.out_html)
|
|
1645
|
+
figlist = {t: htmlwidget_fig(f) for t,f in self.figs.items() }
|
|
1646
|
+
out = tabwidget(figlist,tab=False,selected_index=0)
|
|
1647
|
+
return out.datawidget
|
|
1648
|
+
|
|
1649
|
+
|
|
1650
|
+
# def _ipython_display_(self):
|
|
1651
|
+
# display(self.out_html)
|
|
1544
1652
|
|
|
1545
1653
|
# display(self.out_html)
|
|
1546
1654
|
|
|
@@ -2467,7 +2467,8 @@ class NewtonStackSolver(SolverBase):
|
|
|
2467
2467
|
|
|
2468
2468
|
The per-period prolog/core/epilog decomposition lifted to the stacked
|
|
2469
2469
|
graph: *core* variables take part in some cycle of the collapsed
|
|
2470
|
-
(variable-level) dependency graph -- or are ``___RES`` equations
|
|
2470
|
+
(variable-level) dependency graph -- or are ``___RES`` equations, or
|
|
2471
|
+
lie on a path from the core back into the core (hybrid models) --
|
|
2471
2472
|
and become the only stacked Newton unknowns; *prolog* variables never
|
|
2472
2473
|
depend on the core at any lag or lead and are solved exactly by one
|
|
2473
2474
|
stacked topological sweep before Newton; *epilog* (reporting)
|
|
@@ -2563,6 +2564,19 @@ class NewtonStackSolver(SolverBase):
|
|
|
2563
2564
|
core |= {v for v, lags in selflags.items()
|
|
2564
2565
|
if min(lags) < 0 and max(lags) > 0}
|
|
2565
2566
|
|
|
2567
|
+
# a variable on a path from the core back into the core (a descendant
|
|
2568
|
+
# AND an ancestor of the core) must be solved with the core: as an
|
|
2569
|
+
# epilog variable it would only be swept after convergence, so the
|
|
2570
|
+
# core equations depending on it would iterate against a stale value
|
|
2571
|
+
# (wrong solution). It happens when the core is not one strongly
|
|
2572
|
+
# connected block -- notably in a hybrid model, where the ___RES
|
|
2573
|
+
# equations forced into the core leave normalized equations between
|
|
2574
|
+
# them
|
|
2575
|
+
if core:
|
|
2576
|
+
desc = set().union(*(nx.descendants(cg, v) for v in core))
|
|
2577
|
+
anc = set().union(*(nx.ancestors(cg, v) for v in core))
|
|
2578
|
+
core |= desc & anc
|
|
2579
|
+
|
|
2566
2580
|
desc = (set().union(*(nx.descendants(cg, v) for v in core))
|
|
2567
2581
|
if core else set())
|
|
2568
2582
|
epi_vars = [v for v in fulldiff.endovar if v not in core and v in desc]
|
|
@@ -2679,14 +2693,22 @@ class NewtonStackSolver(SolverBase):
|
|
|
2679
2693
|
if not silent:
|
|
2680
2694
|
print(f"Iteration {iteration} Max residual "
|
|
2681
2695
|
f"{newton_conv:>{25},.{12}f}")
|
|
2682
|
-
|
|
2696
|
+
# scheduled Jacobian refresh -- skipped when the equations
|
|
2697
|
+
# already hold (max |residual| <= newton_absconv): the
|
|
2698
|
+
# remaining step is tiny and the current Jacobian is good
|
|
2699
|
+
# enough for it, while a rebuild (stacked derivatives +
|
|
2700
|
+
# factorization) costs many times a whole iteration
|
|
2701
|
+
if (iteration != 0 and nonlin and not (iteration % nonlin)
|
|
2702
|
+
and newton_conv > newton_absconv):
|
|
2683
2703
|
with m.timer("Updating solver", timeit):
|
|
2684
2704
|
if not silent:
|
|
2685
2705
|
print(f"Updating solver, iteration {iteration}")
|
|
2686
2706
|
df_now = pd.DataFrame(
|
|
2687
2707
|
values, index=ctx.databank.index,
|
|
2688
2708
|
columns=ctx.databank.columns)
|
|
2709
|
+
buildstart = time.time()
|
|
2689
2710
|
ctx.solver = m.ng_getstacksolver(df_now, resmask)
|
|
2711
|
+
m.ng_stackbuildtime += time.time() - buildstart
|
|
2690
2712
|
m.ng_stacksolver = ctx.solver
|
|
2691
2713
|
ctx.diffcount += 1
|
|
2692
2714
|
|
|
@@ -2748,6 +2770,7 @@ class NewtonStackSolver(SolverBase):
|
|
|
2748
2770
|
f'Setup time (seconds) :{m.setuptime:>15,.4f}',
|
|
2749
2771
|
f'Total model evaluations :{ctx.ittotal:>15,}',
|
|
2750
2772
|
f'Number of solver update :{ctx.diffcount:>15,}',
|
|
2773
|
+
# all builds in this call: in prepare and the nonlin refreshes
|
|
2751
2774
|
f'Jacobian build time (seconds) :{getattr(m, "ng_stackbuildtime", 0.0):>15,.4f}',
|
|
2752
2775
|
f'Simulation time (seconds) :{m.simtime:>15,.4f}',
|
|
2753
2776
|
f'Floating point operations in model : {numberfloats:>15,}',
|
|
@@ -2948,6 +2971,8 @@ class NewtonStackFbminSolver(SolverBase):
|
|
|
2948
2971
|
m.ng_stackfbmin_fb = [(per[t], diff.declared_endo_list[s])
|
|
2949
2972
|
for t, s in zip(fb_t, fb_s)]
|
|
2950
2973
|
|
|
2974
|
+
m.ng_stackfbmin_buildtime = 0.0 # all solver builds in this call
|
|
2975
|
+
|
|
2951
2976
|
if not len(fb):
|
|
2952
2977
|
# the stacked graph is acyclic: one topological sweep solves it
|
|
2953
2978
|
ctx.solver = None
|
|
@@ -2979,10 +3004,12 @@ class NewtonStackFbminSolver(SolverBase):
|
|
|
2979
3004
|
print(f"Creating new stacked fbmin Newton solver "
|
|
2980
3005
|
f"(jacobian={jac_mode!r})")
|
|
2981
3006
|
ctx.diffcount += 1
|
|
3007
|
+
buildstart = time.time()
|
|
2982
3008
|
if jac_mode == "stack":
|
|
2983
3009
|
m.ng_stackfbmin_solver = self._stack_schur_solver(ctx, databank)
|
|
2984
3010
|
else:
|
|
2985
3011
|
m.ng_stackfbmin_solver = self._fd_jacobian_solver(ctx)
|
|
3012
|
+
m.ng_stackfbmin_buildtime = time.time() - buildstart
|
|
2986
3013
|
m.ng_stackfbmin_jacmode = jac_mode
|
|
2987
3014
|
ctx.solver = m.ng_stackfbmin_solver
|
|
2988
3015
|
|
|
@@ -3180,6 +3207,9 @@ class NewtonStackFbminSolver(SolverBase):
|
|
|
3180
3207
|
# the factorization like newtonstack does
|
|
3181
3208
|
nonlin = opt("nonlin", (max(len(self.fb_rows), 2)
|
|
3182
3209
|
if jac_mode == "fd" else False))
|
|
3210
|
+
# no scheduled solver refresh once the feedback equations hold
|
|
3211
|
+
# (max |residual| <= newton_absconv) -- see NewtonStackSolver.iterate
|
|
3212
|
+
newton_absconv = opt("newton_absconv", 0.001)
|
|
3183
3213
|
timeit = opt("timeit", False)
|
|
3184
3214
|
newtonalfa = opt("newtonalfa", 1.0)
|
|
3185
3215
|
newtonnodamp = opt("newtonnodamp", 0)
|
|
@@ -3219,10 +3249,11 @@ class NewtonStackFbminSolver(SolverBase):
|
|
|
3219
3249
|
f"{newton_conv:>25,.6f}")
|
|
3220
3250
|
|
|
3221
3251
|
if (iteration != 0 and nonlin and not (iteration % nonlin)
|
|
3222
|
-
and jac_mode != "gauss"):
|
|
3252
|
+
and jac_mode != "gauss" and newton_conv > newton_absconv):
|
|
3223
3253
|
with m.timer("Updating solver", timeit):
|
|
3224
3254
|
if not silent:
|
|
3225
3255
|
print(f"Updating solver, iteration {iteration}")
|
|
3256
|
+
buildstart = time.time()
|
|
3226
3257
|
if jac_mode == "stack":
|
|
3227
3258
|
df_now = pd.DataFrame(
|
|
3228
3259
|
values, index=ctx.databank.index,
|
|
@@ -3230,6 +3261,7 @@ class NewtonStackFbminSolver(SolverBase):
|
|
|
3230
3261
|
ctx.solver = self._stack_schur_solver(ctx, df_now)
|
|
3231
3262
|
else:
|
|
3232
3263
|
ctx.solver = self._fd_jacobian_solver(ctx)
|
|
3264
|
+
m.ng_stackfbmin_buildtime += time.time() - buildstart
|
|
3233
3265
|
m.ng_stackfbmin_solver = ctx.solver
|
|
3234
3266
|
ctx.diffcount += 1
|
|
3235
3267
|
|
|
@@ -3298,6 +3330,7 @@ class NewtonStackFbminSolver(SolverBase):
|
|
|
3298
3330
|
f'Stacked DAG sweeps :{getattr(ctx, "dag_sweeps", 0):>15,}',
|
|
3299
3331
|
f'Newton steps on the feedback system :{ctx.ittotal:>15,}',
|
|
3300
3332
|
f'Number of solver builds :{ctx.diffcount:>15,}',
|
|
3333
|
+
f'Solver build time (seconds) :{getattr(m, "ng_stackfbmin_buildtime", 0.0):>15,.4f}',
|
|
3301
3334
|
f'Simulation time (seconds) :{m.simtime:>15,.2f}',
|
|
3302
3335
|
]
|
|
3303
3336
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "modelflowib" # PyPI distribution name
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.87" # OVERWRITTEN at build time from MF_VERSION
|
|
8
8
|
# (set_pyproject_version.py / make_pip.py -v)
|
|
9
9
|
description = "A tool to solve and manage dynamic economic and financial models"
|
|
10
10
|
readme = "README.md"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|