BUG: Fix DLE.compute_sequence under NumPy >= 2.4 (#839)#842
Conversation
The asset-price terms in `DLE.compute_sequence` (`R1_Price`, `R2_Price`, `R5_Price`, and the optional `Pay_Price` / `Pay_Gross`) each evaluate to a size-1 array rather than a Python scalar: `self.beta` is a (1, 1) array and `e1` is a row vector, so the matmul chain reduces to shape (1,) or (1, 1). Assigning such an array into a scalar slot used to work because NumPy silently converted a size-1 `ndim > 0` array to a scalar. That conversion was deprecated in NumPy 1.25 and became a hard error in NumPy 2.4, so the assignments now raise `ValueError: setting an array element with a sequence` (underlying `TypeError: only 0-dimensional arrays can be converted to Python scalars`). NumPy 2.3.x still works, which is why this is a 2.3 -> 2.4 regression. Coerce each price expression to a scalar with `.item()` before assignment. This works regardless of the intermediate shape (unlike redefining `e1` as 1-D, which is insufficient because `self.beta` is itself a (1, 1) array) and reproduces the pre-2.4 values exactly. Also covers the `Pay` branch, which has the same size-1 assignment pattern. Adds regression tests for both the default and `Pay` paths of compute_sequence, which previously had no coverage. Fixes #839 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes a NumPy 2.4 regression in quantecon.DLE.compute_sequence where size-1 arrays (from matmul chains involving (1, 1) beta and a 2-D selector vector) can no longer be implicitly assigned into scalar array slots, causing ValueError: setting an array element with a sequence.
Changes:
- Coerce computed bond-price and Pay-asset expressions to Python scalars via
.item()before assignment (robust across NumPy versions). - Add regression tests covering both the default risk-free price path and the optional
Paybranch. - Anchor risk-free prices at
t=0to the closed-formβ**Jvalues and assert finiteness/shape invariants.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
quantecon/_dle.py |
Extracts scalars with .item() for bond-price and Pay-asset assignments to avoid NumPy 2.4 size-1-array assignment errors. |
quantecon/tests/test_dle.py |
Adds regression tests for compute_sequence (default and Pay branch) validating shapes, finiteness, and closed-form pricing at t=0. |
Per Copilot review: precompute `e1 @ self.Mc` and the matrix powers of `self.A0` once (they do not depend on `i`), and compute the denominator `e1 @ self.Mc @ xp[:, i]` once per iteration instead of three times. The results are bit-identical to the previous code (verified on a fixed trajectory); the change avoids redundant work and clarifies intent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
A note on commit 4737381 (the loop-invariant hoist from Copilot's suggestion): the main driver here is readability, not performance. Pulling |
oyamad
left a comment
There was a problem hiding this comment.
- My opinion is that conceptually scalar objects (such as
beta) should be implemented as scalars; conceptually 1-d vectors should be implemented as 1-d arrays. The whole code in this file is very "matrix-oriented", which is very different from the other part of this library, and to me is very confusing. But this would require a global full re-write of the whole code. - So I understand the preference for keeping this PR local, only applying a minimal "workaround" that removes the error.
Move ``.item()`` outside the division in the R1/R2/R5 price terms so the division is performed by NumPy rather than on extracted Python floats. Dividing two Python floats raises ``ZeroDivisionError`` when the denominator (consumption, ``e1 @ Mc @ x``) is zero, whereas the pre-2.4 code produced ``inf``/``nan`` with a ``RuntimeWarning``. Keeping the division inside NumPy restores that behaviour and makes these lines consistent with the Pay branch. Addresses review feedback from @oyamad on #842. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
Fixes #839.
DLE.compute_sequenceraises under NumPy 2.4 when it computes the asset-price termsR1_Price,R2_Price,R5_Price. The same code runs under NumPy 2.3.x, so this is a 2.3 → 2.4 regression.Root cause
Each price expression evaluates to a size-1 array, not a Python scalar:
self.betais a(1, 1)array ande1is a row vector, so the matmul chain reduces to shape(1,)or(1, 1). Assigning such an array into a scalar slot likeself.R1_Price[i, 0]used to work because NumPy silently converted a size-1ndim > 0array to a scalar. That conversion was deprecated in NumPy 1.25 and became a hard error in NumPy 2.4 (listed under Expired deprecations in the 2.4.0 release notes), so the assignment now raisesValueError: setting an array element with a sequence(underlyingTypeError: only 0-dimensional arrays can be converted to Python scalars).Fix
Coerce each price expression to a scalar with
.item()before assignment. This works regardless of the intermediate shape and reproduces the pre-2.4 values exactly.Note: redefining
e1as 1-D (suggested in the issue discussion) is not sufficient on its own — becauseself.betais itself a(1, 1)array,self.beta * e1 @ …re-introduces a 2-D shape and the result staysndim > 0. I verified this empirically: under both the current 2-De1and the proposed 1-De1,R1is shape(1,)andR2/R5are shape(1, 1)..item()addresses the conversion at the point of assignment and is robust to all of these shapes.The same size-1 assignment pattern also exists in the optional
Paybranch (Pay_Price/Pay_Gross), which is only reached when aPayarray is passed. It is fixed here too, socompute_sequenceis robust regardless of arguments.Verification
The validated MWE from the issue (Hall's permanent-income economy) now runs, and the
.item()path reproduces the pre-regression values exactly:R1_Price[:3] = [0.95238095, …]Tests
compute_sequencepreviously had no test coverage. This PR adds two regression tests toTestDLE:test_compute_sequence— exercises the default (risk-free price) path, checks shapes and finiteness, and anchorsR1/R2/R5_Price[0, 0]to the closed formβ**Jatt = 0.test_compute_sequence_with_pay— exercises thePaybranch (Pay_Price/Pay_Gross).All 10 tests in
quantecon/tests/test_dle.pypass locally (NumPy 2.3.5, Python 3.13).Downstream impact
This unblocks 5 lectures in
lecture-python-advanced.mystunderanaconda=2026.06(numpy 2.4.6):cattle_cycles,growth_in_dles,irfs_in_hall_model,lucas_asset_pricing_dles,permanent_income_dles.🤖 Generated with Claude Code