quantecon 0.11.2__tar.gz → 0.11.4__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 (144) hide show
  1. {quantecon-0.11.2 → quantecon-0.11.4}/CHANGELOG.md +8 -0
  2. quantecon-0.11.4/CONTRIBUTING.md +11 -0
  3. {quantecon-0.11.2 → quantecon-0.11.4}/PKG-INFO +1 -1
  4. {quantecon-0.11.2 → quantecon-0.11.4}/environment_np2.yml +1 -1
  5. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/__init__.py +1 -1
  6. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_compute_fp.py +1 -1
  7. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_dle.py +25 -10
  8. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_gridtools.py +1 -1
  9. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/repeated_game.py +33 -11
  10. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_repeated_game.py +23 -0
  11. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/vertex_enumeration.py +7 -3
  12. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/core.py +45 -7
  13. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/ddp.py +95 -68
  14. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/test_approximation.py +2 -2
  15. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/test_core.py +107 -0
  16. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/test_ddp.py +156 -1
  17. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/utilities.py +4 -2
  18. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/pivoting.py +15 -2
  19. quantecon-0.11.4/quantecon/optimize/tests/test_pivoting.py +50 -0
  20. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/numba.py +1 -1
  21. {quantecon-0.11.2 → quantecon-0.11.4}/CITATION.cff +0 -0
  22. {quantecon-0.11.2 → quantecon-0.11.4}/LICENSE +0 -0
  23. {quantecon-0.11.2 → quantecon-0.11.4}/MANIFEST.in +0 -0
  24. {quantecon-0.11.2 → quantecon-0.11.4}/README.md +0 -0
  25. {quantecon-0.11.2 → quantecon-0.11.4}/pyproject.toml +0 -0
  26. {quantecon-0.11.2 → quantecon-0.11.4}/pytest.ini +0 -0
  27. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_arma.py +0 -0
  28. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_ce_util.py +0 -0
  29. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_discrete_rv.py +0 -0
  30. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_ecdf.py +0 -0
  31. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_estspec.py +0 -0
  32. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_filter.py +0 -0
  33. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_graph_tools.py +0 -0
  34. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_inequality.py +0 -0
  35. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_ivp.py +0 -0
  36. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_kalman.py +0 -0
  37. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_lae.py +0 -0
  38. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_lqcontrol.py +0 -0
  39. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_lqnash.py +0 -0
  40. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_lss.py +0 -0
  41. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_matrix_eqn.py +0 -0
  42. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_quadsums.py +0 -0
  43. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_rank_nullspace.py +0 -0
  44. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_robustlq.py +0 -0
  45. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/arma.py +0 -0
  46. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/ce_util.py +0 -0
  47. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/compute_fp.py +0 -0
  48. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/discrete_rv.py +0 -0
  49. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/distributions.py +0 -0
  50. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/dle.py +0 -0
  51. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/ecdf.py +0 -0
  52. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/estspec.py +0 -0
  53. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/filter.py +0 -0
  54. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/__init__.py +0 -0
  55. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/brd.py +0 -0
  56. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/fictplay.py +0 -0
  57. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/game_converters.py +0 -0
  58. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/game_generators/__init__.py +0 -0
  59. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/game_generators/bimatrix_generators.py +0 -0
  60. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/game_generators/tests/__init__.py +0 -0
  61. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/game_generators/tests/test_bimatrix_generators.py +0 -0
  62. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/howson_lcp.py +0 -0
  63. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/lemke_howson.py +0 -0
  64. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/localint.py +0 -0
  65. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/logitdyn.py +0 -0
  66. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/mclennan_tourky.py +0 -0
  67. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/normal_form_game.py +0 -0
  68. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/polymatrix_game.py +0 -0
  69. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/pure_nash.py +0 -0
  70. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/random.py +0 -0
  71. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/support_enumeration.py +0 -0
  72. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/__init__.py +0 -0
  73. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/game_files/big_polym.gam +0 -0
  74. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/game_files/minimum_effort_game.gam +0 -0
  75. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/game_files/triggers_back_case.gam +0 -0
  76. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_brd.py +0 -0
  77. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_fictplay.py +0 -0
  78. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_game_converters.py +0 -0
  79. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_howson_lcp.py +0 -0
  80. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_lemke_howson.py +0 -0
  81. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_localint.py +0 -0
  82. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_logitdyn.py +0 -0
  83. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_mclennan_tourky.py +0 -0
  84. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_normal_form_game.py +0 -0
  85. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_polymatrix_game.py +0 -0
  86. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_pure_nash.py +0 -0
  87. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_random.py +0 -0
  88. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_support_enumeration.py +0 -0
  89. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_utilities.py +0 -0
  90. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_vertex_enumeration.py +0 -0
  91. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/utilities.py +0 -0
  92. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/graph_tools.py +0 -0
  93. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/gridtools.py +0 -0
  94. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/inequality.py +0 -0
  95. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/ivp.py +0 -0
  96. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/kalman.py +0 -0
  97. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/lae.py +0 -0
  98. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/lqcontrol.py +0 -0
  99. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/lqnash.py +0 -0
  100. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/lss.py +0 -0
  101. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/__init__.py +0 -0
  102. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/_ddp_linprog_simplex.py +0 -0
  103. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/approximation.py +0 -0
  104. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/estimate.py +0 -0
  105. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/gth_solve.py +0 -0
  106. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/random.py +0 -0
  107. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/__init__.py +0 -0
  108. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/test_estimate.py +0 -0
  109. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/test_gth_solve.py +0 -0
  110. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/test_random.py +0 -0
  111. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/test_utilities.py +0 -0
  112. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/matrix_eqn.py +0 -0
  113. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/__init__.py +0 -0
  114. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/lcp_lemke.py +0 -0
  115. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/linprog_simplex.py +0 -0
  116. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/minmax.py +0 -0
  117. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/nelder_mead.py +0 -0
  118. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/root_finding.py +0 -0
  119. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/scalar_maximization.py +0 -0
  120. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/tests/__init__.py +0 -0
  121. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/tests/test_lcp_lemke.py +0 -0
  122. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/tests/test_linprog_simplex.py +0 -0
  123. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/tests/test_minmax.py +0 -0
  124. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/tests/test_nelder_mead.py +0 -0
  125. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/tests/test_root_finding.py +0 -0
  126. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/tests/test_scalar_max.py +0 -0
  127. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/quad.py +0 -0
  128. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/quadsums.py +0 -0
  129. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/random/__init__.py +0 -0
  130. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/random/tests/__init__.py +0 -0
  131. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/random/tests/test_utilities.py +0 -0
  132. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/random/utilities.py +0 -0
  133. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/rank_nullspace.py +0 -0
  134. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/robustlq.py +0 -0
  135. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/timings/__init__.py +0 -0
  136. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/timings/timings.py +0 -0
  137. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/__init__.py +0 -0
  138. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/array.py +0 -0
  139. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/combinatorics.py +0 -0
  140. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/common_messages.py +0 -0
  141. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/compat.py +0 -0
  142. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/notebooks.py +0 -0
  143. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/random.py +0 -0
  144. {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/timing.py +0 -0
@@ -2,6 +2,14 @@
2
2
 
3
3
  Future CHANGELOG entries will be documented under the [release notes on GitHub](https://github.com/QuantEcon/QuantEcon.py/releases)
4
4
 
5
+ ## Ver 0.11.4 (14th-July-2026)
6
+
7
+ See [release notes](https://github.com/QuantEcon/QuantEcon.py/releases/tag/v0.11.4)
8
+
9
+ ## Ver 0.11.3 (8th-July-2026)
10
+
11
+ See [release notes](https://github.com/QuantEcon/QuantEcon.py/releases/tag/v0.11.3)
12
+
5
13
  ## Ver 0.11.2 (6th-April-2026)
6
14
 
7
15
  See [release notes](https://github.com/QuantEcon/QuantEcon.py/releases/tag/v0.11.2)
@@ -0,0 +1,11 @@
1
+ # Contributing to QuantEcon.py
2
+
3
+ Thanks for your interest in contributing to QuantEcon.py.
4
+
5
+ The contribution guide is maintained in the project documentation:
6
+
7
+ - [Rendered guide on Read the Docs](https://quanteconpy.readthedocs.io/en/latest/contributing.html)
8
+ - [Source file in this repository](docs/source/contributing.rst)
9
+
10
+ The guide covers where to start, how to set up a development environment,
11
+ and expectations for tests and documentation.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantecon
3
- Version: 0.11.2
3
+ Version: 0.11.4
4
4
  Summary: Import the main names to top level.
5
5
  Keywords: quantitative,economics
6
6
  Author-email: QuantEcon Project <admin@quantecon.org>
@@ -10,7 +10,7 @@ dependencies:
10
10
  - numpy>=2
11
11
  - scipy
12
12
  - pandas
13
- - numba>=0.64
13
+ - numba=0.66.0rc1
14
14
  - sympy
15
15
  - ipython
16
16
  - flake8
@@ -3,7 +3,7 @@
3
3
  Import the main names to top level.
4
4
  """
5
5
 
6
- __version__ = '0.11.2'
6
+ __version__ = '0.11.4'
7
7
 
8
8
  try:
9
9
  import numba
@@ -357,7 +357,7 @@ def _square_sum(a): # pragma: no cover
357
357
  pass
358
358
 
359
359
 
360
- @overload(_square_sum, jit_options={'cache':True})
360
+ @overload(_square_sum, jit_options={'cache': True})
361
361
  def _square_sum_ol(a):
362
362
  if isinstance(a, types.Number):
363
363
  return lambda a: a**2
@@ -211,13 +211,26 @@ class DLE(object):
211
211
  self.R1_Price = np.empty((ts_length + 1, 1))
212
212
  self.R2_Price = np.empty((ts_length + 1, 1))
213
213
  self.R5_Price = np.empty((ts_length + 1, 1))
214
+ # Hoist the loop-invariant terms out of the loop: ``e1 @ self.Mc`` and
215
+ # the matrix powers of ``self.A0`` do not depend on ``i``.
216
+ eMc = e1 @ self.Mc
217
+ A0_1 = np.linalg.matrix_power(self.A0, 1)
218
+ A0_2 = np.linalg.matrix_power(self.A0, 2)
219
+ A0_5 = np.linalg.matrix_power(self.A0, 5)
220
+ # Each price expression evaluates to a size-1 array (``self.beta`` is a
221
+ # (1, 1) array and ``eMc`` is a row vector). Coerce the size-1 result to
222
+ # a scalar with ``.item()`` *after* dividing, so assignment works under
223
+ # NumPy >= 2.4 (which raises instead of silently converting an
224
+ # ``ndim > 0`` array to a scalar on assignment). Keeping the division
225
+ # inside NumPy preserves the pre-2.4 behaviour when ``denom == 0`` (an
226
+ # ``inf``/``nan`` with a warning, rather than a ``ZeroDivisionError``).
227
+ # See #839.
214
228
  for i in range(ts_length + 1):
215
- self.R1_Price[i, 0] = self.beta * e1 @ self.Mc @ np.linalg.matrix_power(
216
- self.A0, 1) @ xp[:, i] / (e1 @ self.Mc @ xp[:, i])
217
- self.R2_Price[i, 0] = self.beta**2 * (e1 @ self.Mc @
218
- np.linalg.matrix_power(self.A0, 2) @ xp[:, i]) / (e1 @ self.Mc @ xp[:, i])
219
- self.R5_Price[i, 0] = self.beta**5 * (e1 @ self.Mc @
220
- np.linalg.matrix_power(self.A0, 5) @ xp[:, i]) / (e1 @ self.Mc @ xp[:, i])
229
+ xi = xp[:, i]
230
+ denom = eMc @ xi
231
+ self.R1_Price[i, 0] = (self.beta * eMc @ A0_1 @ xi / denom).item()
232
+ self.R2_Price[i, 0] = (self.beta**2 * (eMc @ A0_2 @ xi) / denom).item()
233
+ self.R5_Price[i, 0] = (self.beta**5 * (eMc @ A0_5 @ xi) / denom).item()
221
234
 
222
235
  # === Gross rates of return on 1-period risk-free bonds === #
223
236
  self.R1_Gross = 1 / self.R1_Price
@@ -239,12 +252,14 @@ class DLE(object):
239
252
  self.Pay_Price = np.empty((ts_length + 1, 1))
240
253
  self.Pay_Gross = np.empty((ts_length + 1, 1))
241
254
  self.Pay_Gross[0, 0] = np.nan
255
+ # As above, coerce each size-1 result to a scalar with ``.item()``
256
+ # so assignment works under NumPy >= 2.4. See #839.
242
257
  for i in range(ts_length + 1):
243
- self.Pay_Price[i, 0] = (xp[:, i].T @ self.Q @
244
- xp[:, i] + self.q) / (e1 @ self.Mc @ xp[:, i])
258
+ self.Pay_Price[i, 0] = ((xp[:, i].T @ self.Q @
259
+ xp[:, i] + self.q) / (e1 @ self.Mc @ xp[:, i])).item()
245
260
  for i in range(ts_length):
246
- self.Pay_Gross[i + 1, 0] = self.Pay_Price[i + 1,
247
- 0] / (self.Pay_Price[i, 0] - Pay @ xp[:, i])
261
+ self.Pay_Gross[i + 1, 0] = (self.Pay_Price[i + 1,
262
+ 0] / (self.Pay_Price[i, 0] - Pay @ xp[:, i])).item()
248
263
  return
249
264
 
250
265
  def irf(self, ts_length=100, shock=None):
@@ -254,7 +254,7 @@ def _cartesian_index(indices, nums_grids):
254
254
  n = len(indices)
255
255
  idx = 0
256
256
  de_cumprod = 1
257
- for i in range(1,n+1):
257
+ for i in range(1, n+1):
258
258
  idx += de_cumprod * indices[n-i]
259
259
  de_cumprod *= nums_grids[n-i]
260
260
  return idx
@@ -60,7 +60,9 @@ class RepeatedGame:
60
60
  max_iter : scalar(int)
61
61
  Maximum number of iterations.
62
62
  u_init : ndarray(float, ndim=1)
63
- The initial guess of threat points.
63
+ The initial guess of threat points. By default, the
64
+ minimum payoff of each player is used. A supplied
65
+ guess is clipped from below by the minimum payoffs.
64
66
 
65
67
  Returns
66
68
  -------
@@ -88,7 +90,7 @@ class RepeatedGame:
88
90
 
89
91
 
90
92
  def _equilibrium_payoffs_abreu_sannikov(rpg, tol=1e-12, max_iter=500,
91
- u_init=np.zeros(2)):
93
+ u_init=None):
92
94
  """
93
95
  Using 'abreu_sannikov' algorithm to compute the set of payoff pairs
94
96
  of all pure-strategy subgame-perfect equilibria with public randomization
@@ -106,8 +108,11 @@ def _equilibrium_payoffs_abreu_sannikov(rpg, tol=1e-12, max_iter=500,
106
108
  max_iter : scalar(int), optional(default=500)
107
109
  Maximum number of iterations.
108
110
 
109
- u_init : ndarray(float, ndim=1), optional(default=np.zeros(2))
110
- The initial guess of threat points.
111
+ u_init : ndarray(float, ndim=1), optional
112
+ The initial guess of threat points. It must not exceed the
113
+ threat points of the equilibrium payoff set. By default, the
114
+ minimum payoff of each player is used. A supplied guess is
115
+ clipped from below by the minimum payoffs.
111
116
 
112
117
  Returns
113
118
  -------
@@ -142,8 +147,14 @@ def _equilibrium_payoffs_abreu_sannikov(rpg, tol=1e-12, max_iter=500,
142
147
  # count the new points generated in each iteration
143
148
  n_new_pt = 0
144
149
 
145
- # copy the threat points
146
- u = np.copy(u_init)
150
+ # initial threat points: the minimum payoffs by default; a supplied
151
+ # guess is clipped from below by the minimum payoffs
152
+ payoff_mins = np.array([sg.payoff_arrays[0].min(),
153
+ sg.payoff_arrays[1].min()], dtype=float)
154
+ if u_init is None:
155
+ u = payoff_mins
156
+ else:
157
+ u = np.maximum(np.asarray(u_init, dtype=float), payoff_mins)
147
158
 
148
159
  # initialization
149
160
  payoff_pts = \
@@ -163,18 +174,29 @@ def _equilibrium_payoffs_abreu_sannikov(rpg, tol=1e-12, max_iter=500,
163
174
  hull.equations, u, IC, action_profile_payoff,
164
175
  extended_payoff, new_pts, W_new)
165
176
 
177
+ if n_new_pt == 0:
178
+ msg = ("no incentive-compatible payoff vector found; the game "
179
+ "may have no pure-action subgame perfect equilibrium "
180
+ "for this value of delta")
181
+ raise ValueError(msg)
182
+
166
183
  n_iter += 1
167
184
  if n_iter >= max_iter:
168
185
  break
169
186
 
170
- # check convergence
171
- if n_new_pt == n_old_pt:
187
+ # update threat points; done before the convergence check, since
188
+ # the set has not converged while the threat points are moving
189
+ u_old = np.copy(u)
190
+ _update_u(u, W_new[:n_new_pt])
191
+
192
+ # check convergence; u is updated only by assignment from payoff
193
+ # values in _update_u, so exact comparison is appropriate, and
194
+ # stopping while u is still moving by sub-tol amounts can leave
195
+ # near-duplicate vertices in the hull
196
+ if n_new_pt == n_old_pt and np.array_equal(u, u_old):
172
197
  if np.linalg.norm(W_new[:n_new_pt] - W_old[:n_new_pt]) < tol:
173
198
  break
174
199
 
175
- # update threat points
176
- _update_u(u, W_new[:n_new_pt])
177
-
178
200
  hull = ConvexHull(W_new[:n_new_pt])
179
201
 
180
202
  return hull
@@ -3,6 +3,7 @@ Tests for repeated_game.py
3
3
 
4
4
  """
5
5
  import numpy as np
6
+ import pytest
6
7
  from numpy.testing import assert_allclose
7
8
  from quantecon.game_theory import NormalFormGame, RepeatedGame
8
9
 
@@ -47,3 +48,25 @@ class TestAS():
47
48
  hull = rpg.equilibrium_payoffs(method=method,
48
49
  options={'u_init': d['u']})
49
50
  assert_allclose(hull.points[hull.vertices], d['vertices'])
51
+
52
+ def test_abreu_sannikov_default_u_init(self):
53
+ for d in self.game_dicts:
54
+ rpg = RepeatedGame(d['sg'], d['delta'])
55
+ hull = rpg.equilibrium_payoffs()
56
+ assert_allclose(hull.points[hull.vertices], d['vertices'])
57
+
58
+ def test_abreu_sannikov_low_u_init(self):
59
+ # A u_init below the threat points must not stop the iteration
60
+ # before the first threat point update
61
+ for d in self.game_dicts:
62
+ rpg = RepeatedGame(d['sg'], d['delta'])
63
+ hull = rpg.equilibrium_payoffs(options={'u_init': np.zeros(2)})
64
+ assert_allclose(hull.points[hull.vertices], d['vertices'])
65
+
66
+ def test_abreu_sannikov_no_pure_action_spe(self):
67
+ # Game with no pure-action subgame perfect equilibrium at low delta
68
+ bimatrix = [[(1, -1), (-1, 2)],
69
+ [(-2, 2), (1, 0)]]
70
+ rpg = RepeatedGame(NormalFormGame(bimatrix), 0.05)
71
+ with pytest.raises(ValueError):
72
+ rpg.equilibrium_payoffs()
@@ -136,18 +136,22 @@ def _vertex_enumeration_gen(labelings_bits_tup, equations_tup, trans_recips):
136
136
  m, n = equations_tup[0].shape[1] - 1, equations_tup[1].shape[1] - 1
137
137
  num_vertices0, num_vertices1 = \
138
138
  equations_tup[0].shape[0], equations_tup[1].shape[0]
139
- ZERO_LABELING0_BITS = (np.uint64(1) << np.uint64(m)) - np.uint64(1)
140
139
  COMPLETE_LABELING_BITS = (np.uint64(1) << np.uint64(m+n)) - np.uint64(1)
140
+ ZERO_LABELING1_BITS = \
141
+ ((np.uint64(1) << np.uint64(n)) - np.uint64(1)) << np.uint64(m)
141
142
 
142
143
  labelings_bits_dict1 = Dict.empty(key_type=types.uint64,
143
144
  value_type=types.intp)
144
145
  for j in range(num_vertices1):
145
146
  labelings_bits_dict1[labelings_bits_tup[1][j]] = j
146
147
 
148
+ try:
149
+ del labelings_bits_dict1[ZERO_LABELING1_BITS]
150
+ except Exception:
151
+ pass
152
+
147
153
  for i in range(num_vertices0):
148
154
  bits0 = labelings_bits_tup[0][i]
149
- if bits0 == ZERO_LABELING0_BITS:
150
- continue
151
155
  complement0 = bits0 ^ COMPLETE_LABELING_BITS
152
156
  try:
153
157
  j = labelings_bits_dict1[complement0]
@@ -223,7 +223,7 @@ class MarkovChain:
223
223
  return msg.format(self.P, self._stationary_dists)
224
224
 
225
225
  def __str__(self):
226
- return str(self.__repr__)
226
+ return self.__repr__()
227
227
 
228
228
  @property
229
229
  def state_values(self):
@@ -463,7 +463,9 @@ class MarkovChain:
463
463
 
464
464
  init : int or array_like(int, ndim=1), optional
465
465
  Initial state(s). If None, the initial state is randomly
466
- drawn.
466
+ drawn. Negative values count from the end, as with Python
467
+ indexing; the returned paths contain the equivalent
468
+ nonnegative indices.
467
469
 
468
470
  num_reps : scalar(int), optional(default=None)
469
471
  Number of repetitions of simulation.
@@ -516,6 +518,13 @@ class MarkovChain:
516
518
  'init must be int, array_like of ints, or None'
517
519
  )
518
520
 
521
+ # Map negative indices into {0, ..., n-1}: the sparse kernel does
522
+ # not support wraparound indexing, and, intentionally, the
523
+ # returned paths then contain nonnegative indices only (a path
524
+ # with init=-k starts with n-k, not -k)
525
+ init_states = np.where(init_states < 0, init_states + self.n,
526
+ init_states)
527
+
519
528
  # === set up array to store output === #
520
529
  X = np.empty((k, ts_length), dtype=int)
521
530
 
@@ -614,11 +623,20 @@ def _generate_sample_paths(P_cdfs, init_states, random_values, out):
614
623
 
615
624
  """
616
625
  num_reps, ts_length = out.shape
626
+ n = P_cdfs.shape[0]
617
627
 
618
628
  for i in range(num_reps):
619
629
  out[i, 0] = init_states[i]
620
630
  for t in range(ts_length-1):
621
- out[i, t+1] = np.searchsorted(P_cdfs[out[i, t]], random_values[i, t], side='right')
631
+ k = np.searchsorted(P_cdfs[out[i, t]], random_values[i, t],
632
+ side='right')
633
+ if k == n:
634
+ # random value beyond the last CDF value due to rounding:
635
+ # fall back to the last state with positive probability,
636
+ # i.e., the first position attaining the final CDF value
637
+ k = np.searchsorted(P_cdfs[out[i, t]],
638
+ P_cdfs[out[i, t], n-1], side='left')
639
+ out[i, t+1] = k
622
640
 
623
641
 
624
642
  @jit(nopython=True)
@@ -662,9 +680,17 @@ def _generate_sample_paths_sparse(P_cdfs1d, indices, indptr, init_states,
662
680
  for i in range(num_reps):
663
681
  out[i, 0] = init_states[i]
664
682
  for t in range(ts_length-1):
665
- k = np.searchsorted(P_cdfs1d[indptr[out[i, t]]:indptr[out[i, t]+1]],
666
- random_values[i, t], side='right')
667
- out[i, t+1] = indices[indptr[out[i, t]]+k]
683
+ lo, hi = indptr[out[i, t]], indptr[out[i, t]+1]
684
+ k = np.searchsorted(P_cdfs1d[lo:hi], random_values[i, t],
685
+ side='right')
686
+ if k == hi - lo:
687
+ # random value beyond the last CDF value due to rounding:
688
+ # fall back to the last state with positive probability
689
+ # (explicitly stored zeros do not increase the CDF), i.e.,
690
+ # the first position attaining the final CDF value
691
+ k = np.searchsorted(P_cdfs1d[lo:hi], P_cdfs1d[hi-1],
692
+ side='left')
693
+ out[i, t+1] = indices[lo+k]
668
694
 
669
695
 
670
696
  def mc_compute_stationary(P):
@@ -713,14 +739,26 @@ def mc_sample_path(P, init=0, sample_size=1000, random_state=None):
713
739
 
714
740
  """
715
741
  random_state = check_random_state(random_state)
742
+ mc = MarkovChain(P)
716
743
 
717
744
  if isinstance(init, numbers.Integral):
718
745
  X_0 = init
719
746
  else:
747
+ init = np.asarray(init)
748
+ if init.ndim != 1 or init.shape[0] != mc.n:
749
+ raise ValueError('init must be an int or a 1-dim array of '
750
+ 'length n')
751
+ if (init < 0).any() or not np.allclose(init.sum(), 1):
752
+ raise ValueError('init must be a probability distribution '
753
+ '(nonnegative, summing to 1)')
720
754
  cdf0 = np.cumsum(init)
721
755
  u_0 = random_state.random()
722
756
  X_0 = np.searchsorted(cdf0, u_0, side='right')
757
+ if X_0 == mc.n:
758
+ # u_0 beyond the last CDF value due to rounding: fall back to
759
+ # the last state with positive probability, as in
760
+ # _generate_sample_paths
761
+ X_0 = np.searchsorted(cdf0, cdf0[-1], side='left')
723
762
 
724
- mc = MarkovChain(P)
725
763
  return mc.simulate(ts_length=sample_size, init=X_0,
726
764
  random_state=random_state)