Summary
This is a defensive / robustness suggestion, not a live bug. The calibration cell in lectures/un_insure.md passes a bare Python scalar as the initial guess to scipy.optimize.fsolve, and its residual function returns array-valued expressions when fsolve hands it a 1-element array. That pattern executes fine on the current scientific stack, but it briefly broke notebook execution downstream under a dependency-version drift, so it is worth hardening.
Where
In lectures/un_insure.md, the autarky calibration uses (around lines 613, 632 and 635):
# inside r_error(self, r)
Vu_star = sp.optimize.fsolve(Vu_error_Λ, 15000, args=(r,))[0]
...
r_calibrated = sp.optimize.brentq(r_error_Λ, 1e-10, 1 - 1e-10)
Vu_aut = sp.optimize.fsolve(Vu_error_Λ, 15000, args=(r_calibrated,))[0]
fsolve internally does x0 = asarray(x0).flatten(), so the iterate Vu arrives at Vu_error as a shape-(1,) array, and the residual is computed as a 1-element array.
Background
The QuantEcon build container (quantecon-build) had its scientific stack unpinned, so it drifted ahead of the anaconda=2025.12 baseline this repo pins. During that drift (≈ March 2026) un_insure.md failed with TypeError: only 0-dimensional arrays can be converted to Python scalars in the brentq → fsolve path, on repos building against the lean container. That is tracked in QuantEcon/actions#28 and the container has now been pinned back to the 2025.12 baseline in QuantEcon/actions#84, which resolves the CI symptom.
The underlying trigger class is real: numpy 2.4.0 promoted converting an ndim > 0 array to a Python scalar (via float() / int()) from a deprecation to a hard TypeError.
Why it is not a live bug
I re-ran the actual calibration cell across four numpy/scipy combinations and it passes on all of them:
| numpy |
scipy |
result |
| 2.3.5 |
1.16.3 (anaconda 2025.12) |
✅ |
| 2.4.0 |
1.15.3 |
✅ |
| 2.4.0 |
1.16.3 |
✅ |
| 2.4.6 |
1.17.1 (anaconda 2026.06) |
✅ |
So the specific breaking combination has since been patched upstream and is no longer reproducible. The code is simply fragile to this class of change.
Suggested hardening
Make the calibration robust to the iterate arriving as a 1-element array and ensure scalars are returned, e.g. normalise the input in Vu_error:
def Vu_error(self, Vu, r):
β, Ve = self.β, self.Ve
Vu = np.asarray(Vu).item() # treat the fsolve iterate as a scalar
a = max(0.0, invp_prime(1 / (β * (Ve - Vu)), r))
return u(self, 0) - a + β * (p(a, r) * Ve + (1 - p(a, r)) * Vu) - Vu
(Equivalently, pass x0 as a 1-element array and extract results with float(result[0]) / .item().) I verified this variant produces identical results (Vu_aut ≈ 16758.7, p ≈ 0.1) on all four stacks above.
Priority
Low — defensive only. Good first issue. No action is required for current builds; this just prevents a recurrence if the dependency stack moves again.
Summary
This is a defensive / robustness suggestion, not a live bug. The calibration cell in
lectures/un_insure.mdpasses a bare Python scalar as the initial guess toscipy.optimize.fsolve, and its residual function returns array-valued expressions whenfsolvehands it a 1-element array. That pattern executes fine on the current scientific stack, but it briefly broke notebook execution downstream under a dependency-version drift, so it is worth hardening.Where
In
lectures/un_insure.md, the autarky calibration uses (around lines 613, 632 and 635):fsolveinternally doesx0 = asarray(x0).flatten(), so the iterateVuarrives atVu_erroras a shape-(1,)array, and the residual is computed as a 1-element array.Background
The QuantEcon build container (
quantecon-build) had its scientific stack unpinned, so it drifted ahead of theanaconda=2025.12baseline this repo pins. During that drift (≈ March 2026)un_insure.mdfailed withTypeError: only 0-dimensional arrays can be converted to Python scalarsin thebrentq → fsolvepath, on repos building against the lean container. That is tracked in QuantEcon/actions#28 and the container has now been pinned back to the 2025.12 baseline in QuantEcon/actions#84, which resolves the CI symptom.The underlying trigger class is real: numpy 2.4.0 promoted converting an
ndim > 0array to a Python scalar (viafloat()/int()) from a deprecation to a hardTypeError.Why it is not a live bug
I re-ran the actual calibration cell across four numpy/scipy combinations and it passes on all of them:
So the specific breaking combination has since been patched upstream and is no longer reproducible. The code is simply fragile to this class of change.
Suggested hardening
Make the calibration robust to the iterate arriving as a 1-element array and ensure scalars are returned, e.g. normalise the input in
Vu_error:(Equivalently, pass
x0as a 1-element array and extract results withfloat(result[0])/.item().) I verified this variant produces identical results (Vu_aut ≈ 16758.7,p ≈ 0.1) on all four stacks above.Priority
Low — defensive only. Good first issue. No action is required for current builds; this just prevents a recurrence if the dependency stack moves again.