Repository navigation
Python bindings: in-memory NPV cubes are wrongly abstract, no NPVCube is constructible from Python #354
Description
Summary
In the Python bindings, the two in-memory NPV cube classes cannot be instantiated:
>>> import ORE
>>> ORE.DoublePrecisionInMemoryCubeN(asof, {"trade1"}, dates, samples)
AttributeError: No constructor defined - class is abstractSinglePrecisionInMemoryCubeN fails identically. Since NPVCube and AggregationScenarioData are abstract by design and JointNPVCube only joins existing cubes, no NPV cube can be constructed from Python at all — a cube can only be obtained from a completed OREApp run.
Verified on the released 1.8.15.0 and 1.8.16.0 wheels (cp312, macOS arm64) and reproduced from current master with SWIG 4.4.1. This is distinct from #351 / #353 (the two issues stack: #353 makes an existing cube reusable, this one makes cubes constructible).
Root cause
ORE-SWIG/OREAnalytics-SWIG/SWIG/orea_cube.i declares the base's pure virtuals as
virtual Real getT0(Size id, Size depth = 0) const = 0; // :52
virtual Real get(Size id, Size date, Size sample, Size depth = 0) const = 0; // :56while the InMemoryCubeOpt template overrides them as
QuantLib::Real getT0(QuantLib::Size i, QuantLib::Size d) const override; // :124
QuantLib::Real get(QuantLib::Size i, QuantLib::Size j, QuantLib::Size k, QuantLib::Size d) const override; // :126Two independent mismatches make SWIG fail to recognise these as implementations, so it considers the pure virtuals unimplemented, marks the instantiations abstract, and drops the constructors (%template at :133 and the %shared_ptr declarations at :32 are fine):
- Default arguments. SWIG expands a pure virtual with a default argument into one required signature per arity; an override without the default satisfies only the full-arity form. This alone keeps the class abstract even with consistent type spellings.
- Type spelling.
QuantLib::Size/QuantLib::Realare unresolvable to the SWIG parser — theusing QuantLib::Size;that would resolve them (QuantLib-SWIGtypes.i:38) sits inside a%{ %}block the parser never sees, soQuantLib::Sizeand the base'sSize(typedef size_t Size,types.i:63) are unrelated types. This is the same visibility mechanism as Python bindings: getCube/setCube declare different shared_ptr spellings, so in-memory cube reuse raises TypeError (regression in 1.8.16.0) #351.
Bisection in a minimal SWIG 4.4.1 module (base pure virtual vs. derived override):
| base | override | result |
|---|---|---|
getT0(Size, Size depth = 0) = 0 |
getT0(Size, Size) |
abstract |
getT0(Size, Size) = 0 |
getT0(QuantLib::Size, QuantLib::Size) |
abstract |
getT0(Size, Size) = 0 |
getT0(Size, Size) |
constructible |
getT0(Size, Size depth = 0) = 0 |
getT0(Size, Size d = 0) |
constructible |
Fix
In the InMemoryCubeOpt block of orea_cube.i (:110-128), spell the overrides exactly like the base: bare Size/Real, and repeat the base's = 0 default on getT0/get. Regenerating oreanalytics.i with that change, both classes gain their constructors —
__init__(DoublePrecisionInMemoryCubeN self, Date asof, StringSet ids, DateVector dates, Size samples, double t=double())
— with an unchanged warning profile, and independently of whether the #353 alias fix is applied.
Environment
open-source-risk-engine1.8.15.0 and 1.8.16.0 (PyPI wheels, cp312, macOS arm64), Python 3.12- Regenerated from
master(3b62ba2) with SWIG 4.4.1, QuantLib-SWIG at the pinned submodule commitb4fc7fa4
Activity
Make the in-memory NPV cubes constructible from Python (OpenSourceRis…
Make QuantLib::ext::shared_ptr resolvable by SWIG (OpenSourceRisk#351)
dazivo commentedon Aug 18, 2026
Fix: PR #355 — #355 (verified end-to-end by building the bindings; see PR description).