Relax numpy / pandas / networkx pins to support the modern scientific stack (numpy 2, pandas 2/3, Python 3.14)
Background
causing (latest = 2.4.6) compatibility-pins its dependencies with ~=:
numpy~=1.23 # -> >=1.23,<2
pandas~=1.3 # -> >=1.3,<2
scipy~=1.9 # -> >=1.9,<2
sympy~=1.5 # -> >=1.5,<2
networkx~=2.7 # -> >=2.7,<3
These caps block any downstream project from moving to Python 3.14 / numpy 2 / pandas 2+. In particular pandas<2 is fatal on Python 3.14 because pandas 1.x ships no cp314 wheels, so installation falls back to a source build that fails. This is currently blocking the RealRate-Private uv / Python 3.14 migration and a batch of Dependabot upgrades there.
Evidence the caps are safe to lift
We ran a faithful A/B of causing's evaluate output, old stack vs new stack, in Docker:
|
Old (production) |
New (modern) |
| Python |
3.9 |
3.14.4 |
| numpy / pandas |
1.23.1 / 1.3.4 |
2.4.6 / 3.0.3 |
| scipy / sympy |
1.10.0 / 1.5.1 |
1.17.1 / 1.14.0 |
| networkx |
2.7 |
3.6.1 |
de_life_insurance evaluate, 8 years (2016–2023): 8/8 numerically identical within 1e-10 (scripts/compare_graphs_json.py tolerance). So causing's causal computation is numerically stable across the full stack jump.
Ask
- Replace the
~= upper-bound caps with lower-bound floors (e.g. numpy>=1.23, pandas>=1.3, networkx>=2.7, …) so the modern stack resolves cleanly.
- Cut a new
causing release with the relaxed pins. Downstream can then drop its temporary [tool.uv] override-dependencies workaround.
- (Optional) bump
requires-python accordingly and add CI matrix entries for numpy 2 / Python 3.13–3.14.
One thing worth a look (not a regression)
Under numpy 2, sympy.lambdify output for Max/Min calls np.maximum/np.minimum with >2 positional args, e.g. np.maximum(0, -ZÜ, KA*...). numpy treats the 3rd positional as out, so the third value is discarded. This emits a DeprecationWarning on numpy 2. We confirmed the behavior is identical under numpy 1.23 (3rd positional has always been out), which is why the A/B matches exactly — so it is not a regression. But the generated expression doesn't compute a true 3-way max/min, which may be worth verifying against the intended model semantics.
Related
- Downstream tracking issue: realrate/RealRate-Private#2098
Relax
numpy/pandas/networkxpins to support the modern scientific stack (numpy 2, pandas 2/3, Python 3.14)Background
causing(latest =2.4.6) compatibility-pins its dependencies with~=:These caps block any downstream project from moving to Python 3.14 / numpy 2 / pandas 2+. In particular
pandas<2is fatal on Python 3.14 because pandas 1.x ships nocp314wheels, so installation falls back to a source build that fails. This is currently blocking the RealRate-Private uv / Python 3.14 migration and a batch of Dependabot upgrades there.Evidence the caps are safe to lift
We ran a faithful A/B of
causing'sevaluateoutput, old stack vs new stack, in Docker:de_life_insuranceevaluate, 8 years (2016–2023): 8/8 numerically identical within 1e-10 (scripts/compare_graphs_json.pytolerance). Socausing's causal computation is numerically stable across the full stack jump.Ask
~=upper-bound caps with lower-bound floors (e.g.numpy>=1.23,pandas>=1.3,networkx>=2.7, …) so the modern stack resolves cleanly.causingrelease with the relaxed pins. Downstream can then drop its temporary[tool.uv] override-dependenciesworkaround.requires-pythonaccordingly and add CI matrix entries for numpy 2 / Python 3.13–3.14.One thing worth a look (not a regression)
Under numpy 2,
sympy.lambdifyoutput forMax/Mincallsnp.maximum/np.minimumwith >2 positional args, e.g.np.maximum(0, -ZÜ, KA*...). numpy treats the 3rd positional asout, so the third value is discarded. This emits aDeprecationWarningon numpy 2. We confirmed the behavior is identical under numpy 1.23 (3rd positional has always beenout), which is why the A/B matches exactly — so it is not a regression. But the generated expression doesn't compute a true 3-way max/min, which may be worth verifying against the intended model semantics.Related