modelflowib 2.74__tar.gz → 2.76__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.
Files changed (50) hide show
  1. {modelflowib-2.74 → modelflowib-2.76}/PKG-INFO +1 -1
  2. {modelflowib-2.74 → modelflowib-2.76}/modelconstruct_estimation.py +210 -13
  3. {modelflowib-2.74 → modelflowib-2.76}/modelflowib.egg-info/PKG-INFO +1 -1
  4. {modelflowib-2.74 → modelflowib-2.76}/modelflowib.egg-info/SOURCES.txt +1 -0
  5. {modelflowib-2.74 → modelflowib-2.76}/modelflowib.egg-info/top_level.txt +1 -0
  6. modelflowib-2.76/modelsolver_ng.py +3652 -0
  7. {modelflowib-2.74 → modelflowib-2.76}/pyproject.toml +2 -2
  8. {modelflowib-2.74 → modelflowib-2.76}/README.md +0 -0
  9. {modelflowib-2.74 → modelflowib-2.76}/license.md +0 -0
  10. {modelflowib-2.74 → modelflowib-2.76}/modelBLfunk.py +0 -0
  11. {modelflowib-2.74 → modelflowib-2.76}/model_Excel.py +0 -0
  12. {modelflowib-2.74 → modelflowib-2.76}/model_cvx.py +0 -0
  13. {modelflowib-2.74 → modelflowib-2.76}/model_dynare.py +0 -0
  14. {modelflowib-2.74 → modelflowib-2.76}/model_financial_stability.py +0 -0
  15. {modelflowib-2.74 → modelflowib-2.76}/model_latex.py +0 -0
  16. {modelflowib-2.74 → modelflowib-2.76}/model_latex_class.py +0 -0
  17. {modelflowib-2.74 → modelflowib-2.76}/model_parquet_mixin.py +0 -0
  18. {modelflowib-2.74 → modelflowib-2.76}/modelclass.py +0 -0
  19. {modelflowib-2.74 → modelflowib-2.76}/modelconstruct.py +0 -0
  20. {modelflowib-2.74 → modelflowib-2.76}/modeldash.py +0 -0
  21. {modelflowib-2.74 → modelflowib-2.76}/modeldashboot.py +0 -0
  22. {modelflowib-2.74 → modelflowib-2.76}/modeldashsidebar.py +0 -0
  23. {modelflowib-2.74 → modelflowib-2.76}/modeldekom.py +0 -0
  24. {modelflowib-2.74 → modelflowib-2.76}/modeldiff.py +0 -0
  25. {modelflowib-2.74 → modelflowib-2.76}/modeldisplay.py +0 -0
  26. {modelflowib-2.74 → modelflowib-2.76}/modelestimation.py +0 -0
  27. {modelflowib-2.74 → modelflowib-2.76}/modelestimator_new.py +0 -0
  28. {modelflowib-2.74 → modelflowib-2.76}/modelflowib.egg-info/dependency_links.txt +0 -0
  29. {modelflowib-2.74 → modelflowib-2.76}/modelflowib.egg-info/requires.txt +0 -0
  30. {modelflowib-2.74 → modelflowib-2.76}/modelgrab.py +0 -0
  31. {modelflowib-2.74 → modelflowib-2.76}/modelgrabgdx.py +0 -0
  32. {modelflowib-2.74 → modelflowib-2.76}/modelgrabwf2.py +0 -0
  33. {modelflowib-2.74 → modelflowib-2.76}/modelhelp.py +0 -0
  34. {modelflowib-2.74 → modelflowib-2.76}/modelhtml.py +0 -0
  35. {modelflowib-2.74 → modelflowib-2.76}/modelinvert.py +0 -0
  36. {modelflowib-2.74 → modelflowib-2.76}/modeljupyter.py +0 -0
  37. {modelflowib-2.74 → modelflowib-2.76}/modeljupytermagic.py +0 -0
  38. {modelflowib-2.74 → modelflowib-2.76}/modelmacrograb.py +0 -0
  39. {modelflowib-2.74 → modelflowib-2.76}/modelmanipulation.py +0 -0
  40. {modelflowib-2.74 → modelflowib-2.76}/modelmf.py +0 -0
  41. {modelflowib-2.74 → modelflowib-2.76}/modelnet.py +0 -0
  42. {modelflowib-2.74 → modelflowib-2.76}/modelnewton.py +0 -0
  43. {modelflowib-2.74 → modelflowib-2.76}/modelnormalize.py +0 -0
  44. {modelflowib-2.74 → modelflowib-2.76}/modelpattern.py +0 -0
  45. {modelflowib-2.74 → modelflowib-2.76}/modelreport.py +0 -0
  46. {modelflowib-2.74 → modelflowib-2.76}/modeluserfunk.py +0 -0
  47. {modelflowib-2.74 → modelflowib-2.76}/modelvis.py +0 -0
  48. {modelflowib-2.74 → modelflowib-2.76}/modelwidget.py +0 -0
  49. {modelflowib-2.74 → modelflowib-2.76}/modelwidget_input.py +0 -0
  50. {modelflowib-2.74 → modelflowib-2.76}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modelflowib
3
- Version: 2.74
3
+ Version: 2.76
4
4
  Summary: A tool to solve and manage dynamic economic and financial models
5
5
  Author-email: Ib Hansen <Ib.Hansen.Iv@gmail.com>
6
6
  License-Expression: MIT
@@ -1165,8 +1165,12 @@ def _parse_smpl(smpl, df=None):
1165
1165
  Preferred FRML syntax is::
1166
1166
 
1167
1167
  <smpl=start end>
1168
+ <smpl=(start end)>
1168
1169
 
1169
- where ``start`` and ``end`` are separated by one or more blanks. The
1170
+ where ``start`` and ``end`` are separated by one or more blanks, with the
1171
+ brackets optional. A comma may separate them when the value does not pass
1172
+ through a FRML tag -- ``smpl='2000, 2025'`` as a keyword argument -- but not
1173
+ inside ``<...>``, where the tag parser splits options on commas first. The
1170
1174
  labels are **not** coerced to integers, because the dataframe index may be
1171
1175
  a PeriodIndex, DatetimeIndex, quarterly strings, or another custom index
1172
1176
  type. If ``df`` is supplied, its index is used via ``slice_locs`` to
@@ -1256,16 +1260,28 @@ def _parse_smpl(smpl, df=None):
1256
1260
  if not text:
1257
1261
  return None
1258
1262
 
1263
+ # Accept a parenthesised spelling, so <smpl=(2000 2025)> reads the same as
1264
+ # the Python-side smpl=(2000, 2025) tuple. Note that inside a FRML tag only
1265
+ # the blank-separated form works: kw_frml_name splits the tag's options on
1266
+ # commas without regard for brackets, so <smpl=(2000,2025)> would arrive
1267
+ # here already broken in two.
1268
+ closing = {'(': ')', '[': ']'}
1269
+ if len(text) > 1 and text[0] in closing and text[-1] == closing[text[0]]:
1270
+ text = text[1:-1].strip()
1271
+ if not text:
1272
+ return None
1273
+
1259
1274
  # New preferred syntax: <smpl=start end>. Keep old colon syntax so older
1260
1275
  # notebooks do not break.
1261
- if ':' in text and len(text.split()) == 1:
1276
+ if ':' in text and ',' not in text and len(text.split()) == 1:
1262
1277
  start, end = [p.strip() for p in text.split(':', 1)]
1263
1278
  else:
1264
- parts = text.split()
1279
+ parts = [p for p in re.split(r'[,\s]+', text) if p]
1265
1280
  if len(parts) != 2:
1266
1281
  raise ModelSpecificationError(
1267
- "SMPL must be written as 'start end' separated by blanks "
1268
- f"(or legacy 'start:end'); got {smpl!r}"
1282
+ "SMPL must be written as 'start end' separated by blanks, "
1283
+ "optionally bracketed as '(start end)' (or legacy "
1284
+ f"'start:end'); got {smpl!r}"
1269
1285
  )
1270
1286
  start, end = parts
1271
1287
 
@@ -1631,11 +1647,17 @@ def _estimate_and_bake_expression(
1631
1647
  estimator_name,
1632
1648
  input_df=None,
1633
1649
  smpl=None,
1650
+ smpl_is_local: bool = False,
1634
1651
  caption: Optional[str] = None,
1635
1652
  estimator_kwargs: Optional[dict] = None,
1636
1653
  estimator_classes: Optional[dict] = None,
1637
1654
  ):
1638
- """Instantiate an estimator/factory, run it, and return (baked_expression, estimator_obj)."""
1655
+ """Instantiate an estimator/factory, run it, and return (baked_expression, estimator_obj).
1656
+
1657
+ ``smpl_is_local`` marks a sample that came from this equation's own
1658
+ ``<smpl=...>`` rather than from the model-wide default, which decides
1659
+ whether it may override a model-wide ``estimator_kwargs['smpl']``.
1660
+ """
1639
1661
  estimator_constructor = _get_estimator_class(estimator_name, estimator_classes)
1640
1662
  kwargs = dict(estimator_kwargs or {})
1641
1663
 
@@ -1644,13 +1666,16 @@ def _estimate_and_bake_expression(
1644
1666
  # still override the factory's stored defaults.
1645
1667
  if input_df is not None and 'input_df' not in kwargs:
1646
1668
  kwargs['input_df'] = input_df
1647
- if smpl is not None and 'smpl' not in kwargs:
1669
+ # An equation-local <smpl=...> outranks the model-wide estimator_kwargs; a
1670
+ # model-wide Makemodel.smpl does not.
1671
+ if smpl is not None and (smpl_is_local or 'smpl' not in kwargs):
1648
1672
  kwargs['smpl'] = smpl
1649
1673
  if caption is not None and 'caption' not in kwargs:
1650
1674
  kwargs['caption'] = caption
1651
1675
 
1652
1676
  estimator_obj = _instantiate_estimator(estimator_constructor, expression, kwargs)
1653
- estimator_obj = _require_estimator_backend_instance(estimator_obj, estimator_name)
1677
+ # Validates in place and raises; it returns the same object, so do not rebind.
1678
+ _require_estimator_backend_instance(estimator_obj, estimator_name)
1654
1679
  fit_result = _maybe_run_estimator_fit(estimator_obj)
1655
1680
 
1656
1681
  baked = _extract_expression_from_estimator(estimator_obj, fit_result)
@@ -1768,20 +1793,39 @@ def _markdown_with_estimation_blocks(original_text: str, estimation_records: lis
1768
1793
  """Insert estimation markdown blocks after estimator-tagged source lines.
1769
1794
 
1770
1795
  This preserves the original user-facing Markdown as much as possible. For
1771
- the common notebook syntax, each line beginning with ``>`` and containing
1796
+ the common notebook syntax, each equation beginning with ``>`` and containing
1772
1797
  ``<estimator=...>`` gets the next estimation block inserted immediately
1773
1798
  after it. If template expansion creates more estimated equations than can
1774
1799
  be matched to source lines, the remaining blocks are appended at the end.
1800
+
1801
+ An equation can be spread over several lines: a ``>`` line followed by
1802
+ ``>>`` continuations, which the parser joins into one statement. The block
1803
+ goes after the last of those, so the whole equation is shown before its
1804
+ estimation output rather than being split around it.
1775
1805
  """
1776
1806
  records = list(estimation_records or [])
1777
1807
  if not records:
1778
1808
  return original_text
1779
1809
 
1810
+ lines = original_text.splitlines()
1780
1811
  out = []
1781
1812
  rec_i = 0
1782
- for line in original_text.splitlines():
1813
+ i = 0
1814
+ while i < len(lines):
1815
+ line = lines[i]
1783
1816
  out.append(line)
1784
- if rec_i < len(records) and _line_has_estimator_tag(line):
1817
+ i += 1
1818
+
1819
+ if not _line_has_estimator_tag(line):
1820
+ continue
1821
+
1822
+ # Carry the rest of the equation across before inserting the block.
1823
+ # The tag may sit on the '>' line or on any of its continuations.
1824
+ while i < len(lines) and lines[i].lstrip().startswith(">>"):
1825
+ out.append(lines[i])
1826
+ i += 1
1827
+
1828
+ if rec_i < len(records):
1785
1829
  out.append(_estimation_record_to_markdown(records[rec_i]).rstrip())
1786
1830
  rec_i += 1
1787
1831
 
@@ -1795,6 +1839,34 @@ def _markdown_with_estimation_blocks(original_text: str, estimation_records: lis
1795
1839
  return "\n".join(out)
1796
1840
 
1797
1841
 
1842
+ def _strip_markdown_list_blocks(text: str) -> str:
1843
+ """Remove ``>list`` / ``>tlist`` definition blocks from markdown model text.
1844
+
1845
+ A list block is a line whose content (after the leading ``>``) begins with
1846
+ ``list`` or ``tlist``, together with any immediately following ``>>``
1847
+ continuation lines. Equation lines and their ``>>`` continuations are left
1848
+ untouched, because those continuations follow a ``>`` equation line rather
1849
+ than a ``>list`` line.
1850
+
1851
+ Used by ``markdown_with_estimation_no_list`` to support the
1852
+ ``render_list=0`` rendering path of the ``%%Makemymodel`` magic.
1853
+ """
1854
+ out = []
1855
+ in_list = False
1856
+ for line in text.splitlines():
1857
+ stripped = line.lstrip()
1858
+ if in_list:
1859
+ if stripped.startswith('>>'):
1860
+ # Continuation of the current list definition.
1861
+ continue
1862
+ in_list = False
1863
+ if re.match(r'>\s*(list|tlist)\b', stripped, flags=re.IGNORECASE):
1864
+ in_list = True
1865
+ continue
1866
+ out.append(line)
1867
+ return '\n'.join(out)
1868
+
1869
+
1798
1870
  @dataclass
1799
1871
  class BaseExplode:
1800
1872
  """Common parent for Makemodel and Listmodels."""
@@ -1939,9 +2011,80 @@ class BaseExplode:
1939
2011
 
1940
2012
  @dataclass
1941
2013
  class Makemodel(BaseExplode):
2014
+ """Expand a model template (markdown or modelflow) into solvable FRMLs.
2015
+
2016
+ Every attribute below can be inspected with a ``show`` prefix, which prints
2017
+ it nicely, for instance ``consumption.showpost_sum``. The stages follow the
2018
+ order in which ``__post_init__`` produces them.
2019
+
2020
+ **1. Input and options (what you pass in)**
2021
+
2022
+ | Attribute | Description |
2023
+ |---|---|
2024
+ | `showoriginal_statements` | Input expressions |
2025
+ | `showlist_defs` | Lists definitions |
2026
+ | `showtype_input` | Originaal as type modelflow or markdown |
2027
+ | `showreplacements` | list of string tupels with string replacements |
2028
+ | `showfunks` | List of user specified functions to be used in model |
2029
+ | `showmodelname` | A optional name for this (sub) model |
2030
+ | `showvar_description` | Variable descriptions (user-supplied, merged with descriptions from estimated equations) |
2031
+
2032
+ **2. Estimation setup (used only for `<estimator=...>` equations)**
2033
+
2034
+ | Attribute | Description |
2035
+ |---|---|
2036
+ | `showinput_df` | DataFrame used when tagged equations are estimated |
2037
+ | `showestimator` | Default estimator for tagged equations: method name or callable factory |
2038
+ | `showsmpl` | Model-wide default estimation sample. `None` unless passed to the constructor or the magic -- a per-equation `<smpl=start end>` or a `with_defaults(smpl=...)` factory does not show up here. The sample each equation really used is in `showestimation_records` |
2039
+ | `showestimator_kwargs` | Shared kwargs passed to estimator constructors |
2040
+ | `showestimator_classes` | Optional method-name to estimator-class mapping |
2041
+ | `showestimator_namespace` | Optional namespace for resolving `<estimator=name>`; defaults to caller locals/globals |
2042
+
2043
+ **3. Expansion pipeline (in the order they are produced)**
2044
+
2045
+ | Attribute | Description |
2046
+ |---|---|
2047
+ | `showclean_frml_statements` | With frml and nice lists |
2048
+ | `showpost_doable` | Expanded after doable |
2049
+ | `showpost_do` | Frmls after do expansion |
2050
+ | `showpost_sum` | Frmls after expanding sums |
2051
+ | `showexpanded_frml` | frmls after expanding |
2052
+
2053
+ **4. Lists**
2054
+
2055
+ | Attribute | Description |
2056
+ |---|---|
2057
+ | `showmodellist` | The lists defined in string as a dictionary |
2058
+ | `showlists` | Same as `showmodellist` |
2059
+ | `showlist_specification` | All list specifications in string |
2060
+
2061
+ `modellist` is built between `post_do` and `post_sum` (it feeds the sum
2062
+ expansion), `list_specification` is derived at the very end.
2063
+
2064
+ **5. Normalization and estimation output**
2065
+
2066
+ | Attribute | Description |
2067
+ |---|---|
2068
+ | `shownormal_input_expressions` | Expressions sent to modelnormalize, after optional estimation |
2069
+ | `showestimation_records` | Information about equations estimated during construction |
2070
+ | `shownormal_expressions` | List of normal expressions |
2071
+ | `shownormal_output_frmlnames` | FRML names emitted; estimation flags are preserved |
2072
+ | `shownormal_main` | Normalized frmls |
2073
+ | `shownormal_fit` | Normalized frmls for fitted values |
2074
+ | `shownormal_calc_add` | Normalized frmls to calculate add factors |
2075
+ | `shownormal_frml` | Output normalized expressions (`normal_main` + `normal_fit` + `normal_calc_add`) |
2076
+ | `show` | The finished `normal_frml`, printed |
2077
+
2078
+ **6. Rendering**
2079
+
2080
+ | Attribute | Description |
2081
+ |---|---|
2082
+ | `showmarkdown_model` | As markdown |
2083
+ """
2084
+
1942
2085
  # original_statements : str = field(default="", metadata={"description": "Input expressions"})
1943
2086
  # normal_frml : str = field(default="", metadata={"description": "Output normalized expressions"})
1944
-
2087
+
1945
2088
  normal_main : str = field(init=False, metadata={"description": "Normalized frmls"})
1946
2089
  normal_fit : str = field(init=False, metadata={"description": "Normalized frmls for fitted values"})
1947
2090
  normal_calc_add : str = field(init=False, metadata={"description": "Normalized frmls to calculate add factors"})
@@ -2084,7 +2227,9 @@ class Makemodel(BaseExplode):
2084
2227
  ))
2085
2228
 
2086
2229
  self.normal_expressions = [n for p,n in self.normal ]
2087
-
2230
+
2231
+ self._warn_on_repeated_endogenous()
2232
+
2088
2233
  # udrullet = lagarray_unroll(udrullet,funks=funks )
2089
2234
  # udrullet = creatematrix(udrullet,listin=modellist)
2090
2235
  # udrullet = createarray(udrullet,listin=modellist)
@@ -2115,6 +2260,35 @@ class Makemodel(BaseExplode):
2115
2260
  self.list_specification = self.get_lists()
2116
2261
  return
2117
2262
 
2263
+ def _warn_on_repeated_endogenous(self):
2264
+ """Warn when a variable is defined by more than one kept equation.
2265
+
2266
+ Nothing is removed: every equation stays in the emitted FRML, and
2267
+ ``model()`` resolves the collision by letting the last definition win
2268
+ (``modelclass.py``, where each repeat overwrites the variable's entry in
2269
+ ``allvar``). That is usually what a notebook author means when they
2270
+ re-estimate an equation further down, but it is worth saying out loud,
2271
+ because the earlier equations are then dead weight -- and because a
2272
+ typo in a mnemonic looks exactly like a deliberate redefinition.
2273
+ """
2274
+ equations_by_endo = {}
2275
+ for position, (parts, normal) in enumerate(self.normal, start=1):
2276
+ endo = getattr(normal, 'endo_var', '')
2277
+ if endo:
2278
+ equations_by_endo.setdefault(endo, []).append(position)
2279
+
2280
+ for endo, positions in equations_by_endo.items():
2281
+ if len(positions) < 2:
2282
+ continue
2283
+ listed = ', '.join(str(p) for p in positions)
2284
+ print(
2285
+ f"⚠️ {endo} is defined by {len(positions)} equations "
2286
+ f"(kept equations {listed}). The last of them, equation "
2287
+ f"{positions[-1]}, is the one used when the model is solved; "
2288
+ f"the earlier ones go into the FRML but are never evaluated. "
2289
+ f"Tag any you do not want with <DROP>."+'\n'
2290
+ )
2291
+
2118
2292
  def _expression_after_optional_estimation(self, parts, *, equation_index: Optional[int] = None,
2119
2293
  estimator_flag: Any = ...) -> str:
2120
2294
  """Return parts.expression, or an estimated/baked version when tagged.
@@ -2175,6 +2349,7 @@ class Makemodel(BaseExplode):
2175
2349
  estimator_name=estimator_name,
2176
2350
  input_df=self.input_df,
2177
2351
  smpl=smpl,
2352
+ smpl_is_local=local_smpl is not None,
2178
2353
  caption=local_caption,
2179
2354
  estimator_kwargs=self.estimator_kwargs,
2180
2355
  estimator_classes=self.estimator_classes,
@@ -2263,6 +2438,20 @@ class Makemodel(BaseExplode):
2263
2438
  """Alias for :attr:`markdown_with_estimation`."""
2264
2439
  return self.markdown_with_estimation
2265
2440
 
2441
+ @property
2442
+ def markdown_with_estimation_no_list(self) -> str:
2443
+ """Like :attr:`markdown_with_estimation`, with ``>list`` blocks removed.
2444
+
2445
+ Same inline estimation tables as :attr:`markdown_with_estimation`, but
2446
+ ``>list``/``>tlist`` definition blocks are stripped from the rendered
2447
+ Markdown. Used by the ``%%Makemymodel`` magic when ``render_list=0`` is
2448
+ combined with the default ``render_est`` rendering path.
2449
+ """
2450
+ return _markdown_with_estimation_blocks(
2451
+ _strip_markdown_list_blocks(self.original_statements),
2452
+ self.estimation_records,
2453
+ )
2454
+
2266
2455
  @property
2267
2456
  def clean_frml(self) -> str:
2268
2457
  """Normalized equations with add-factors, exogenization, and fitted-value flags stripped."""
@@ -2630,6 +2819,14 @@ class Listmodels(BaseExplode):
2630
2819
  """Alias for :attr:`markdown_with_estimation`."""
2631
2820
  return self.markdown_with_estimation
2632
2821
 
2822
+ @property
2823
+ def markdown_with_estimation_no_list(self) -> str:
2824
+ """Concatenate member Makemodel markdown-with-estimation-no-list strings."""
2825
+ return "\n\n".join(
2826
+ mex.markdown_with_estimation_no_list
2827
+ for mex in self.makemodels
2828
+ )
2829
+
2633
2830
  # __str__(self):
2634
2831
 
2635
2832
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modelflowib
3
- Version: 2.74
3
+ Version: 2.76
4
4
  Summary: A tool to solve and manage dynamic economic and financial models
5
5
  Author-email: Ib Hansen <Ib.Hansen.Iv@gmail.com>
6
6
  License-Expression: MIT
@@ -35,6 +35,7 @@ modelnewton.py
35
35
  modelnormalize.py
36
36
  modelpattern.py
37
37
  modelreport.py
38
+ modelsolver_ng.py
38
39
  modeluserfunk.py
39
40
  modelvis.py
40
41
  modelwidget.py
@@ -33,6 +33,7 @@ modelnewton
33
33
  modelnormalize
34
34
  modelpattern
35
35
  modelreport
36
+ modelsolver_ng
36
37
  modeluserfunk
37
38
  modelvis
38
39
  modelwidget