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.
- {quantecon-0.11.2 → quantecon-0.11.4}/CHANGELOG.md +8 -0
- quantecon-0.11.4/CONTRIBUTING.md +11 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/PKG-INFO +1 -1
- {quantecon-0.11.2 → quantecon-0.11.4}/environment_np2.yml +1 -1
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/__init__.py +1 -1
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_compute_fp.py +1 -1
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_dle.py +25 -10
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_gridtools.py +1 -1
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/repeated_game.py +33 -11
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_repeated_game.py +23 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/vertex_enumeration.py +7 -3
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/core.py +45 -7
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/ddp.py +95 -68
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/test_approximation.py +2 -2
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/test_core.py +107 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/test_ddp.py +156 -1
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/utilities.py +4 -2
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/pivoting.py +15 -2
- quantecon-0.11.4/quantecon/optimize/tests/test_pivoting.py +50 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/numba.py +1 -1
- {quantecon-0.11.2 → quantecon-0.11.4}/CITATION.cff +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/LICENSE +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/MANIFEST.in +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/README.md +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/pyproject.toml +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/pytest.ini +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_arma.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_ce_util.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_discrete_rv.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_ecdf.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_estspec.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_filter.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_graph_tools.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_inequality.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_ivp.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_kalman.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_lae.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_lqcontrol.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_lqnash.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_lss.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_matrix_eqn.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_quadsums.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_rank_nullspace.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/_robustlq.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/arma.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/ce_util.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/compute_fp.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/discrete_rv.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/distributions.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/dle.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/ecdf.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/estspec.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/filter.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/__init__.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/brd.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/fictplay.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/game_converters.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/game_generators/__init__.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/game_generators/bimatrix_generators.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/game_generators/tests/__init__.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/game_generators/tests/test_bimatrix_generators.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/howson_lcp.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/lemke_howson.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/localint.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/logitdyn.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/mclennan_tourky.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/normal_form_game.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/polymatrix_game.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/pure_nash.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/random.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/support_enumeration.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/__init__.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/game_files/big_polym.gam +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/game_files/minimum_effort_game.gam +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/game_files/triggers_back_case.gam +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_brd.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_fictplay.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_game_converters.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_howson_lcp.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_lemke_howson.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_localint.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_logitdyn.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_mclennan_tourky.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_normal_form_game.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_polymatrix_game.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_pure_nash.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_random.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_support_enumeration.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_utilities.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/tests/test_vertex_enumeration.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/game_theory/utilities.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/graph_tools.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/gridtools.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/inequality.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/ivp.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/kalman.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/lae.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/lqcontrol.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/lqnash.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/lss.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/__init__.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/_ddp_linprog_simplex.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/approximation.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/estimate.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/gth_solve.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/random.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/__init__.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/test_estimate.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/test_gth_solve.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/test_random.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/markov/tests/test_utilities.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/matrix_eqn.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/__init__.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/lcp_lemke.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/linprog_simplex.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/minmax.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/nelder_mead.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/root_finding.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/scalar_maximization.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/tests/__init__.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/tests/test_lcp_lemke.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/tests/test_linprog_simplex.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/tests/test_minmax.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/tests/test_nelder_mead.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/tests/test_root_finding.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/optimize/tests/test_scalar_max.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/quad.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/quadsums.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/random/__init__.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/random/tests/__init__.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/random/tests/test_utilities.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/random/utilities.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/rank_nullspace.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/robustlq.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/timings/__init__.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/timings/timings.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/__init__.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/array.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/combinatorics.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/common_messages.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/compat.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/notebooks.py +0 -0
- {quantecon-0.11.2 → quantecon-0.11.4}/quantecon/util/random.py +0 -0
- {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.
|
|
@@ -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
|
-
|
|
216
|
-
|
|
217
|
-
self.
|
|
218
|
-
|
|
219
|
-
self.R5_Price[i, 0] = self.beta**5 * (
|
|
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
|
-
|
|
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):
|
|
@@ -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=
|
|
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
|
|
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
|
-
#
|
|
146
|
-
|
|
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
|
|
171
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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)
|