Skip to content

Relax numpy/pandas/networkx pins to support numpy 2 / pandas 2+ / Python 3.14 #101

@bbkrr

Description

@bbkrr

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

  1. 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.
  2. Cut a new causing release with the relaxed pins. Downstream can then drop its temporary [tool.uv] override-dependencies workaround.
  3. (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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions