Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 35 additions & 11 deletions ORE-SWIG/OREAnalytics-SWIG/SWIG/orea_cube.i
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@
%shared_ptr(ore::analytics::InMemoryCubeOpt<double>);
%shared_ptr(ore::analytics::JointNPVCube)

// getT0/get carry a default argument on their pure-virtual declarations.
// compactdefaultargs (on base and overrides alike, so SWIG's abstract check
// keeps matching them up) generates wrappers that fill the default and call
// C++ with the full argument list; see the comment at InMemoryCubeOpt.
%feature("compactdefaultargs") ore::analytics::NPVCube::getT0;
%feature("compactdefaultargs") ore::analytics::NPVCube::get;
%feature("compactdefaultargs") ore::analytics::InMemoryCubeOpt::getT0;
%feature("compactdefaultargs") ore::analytics::InMemoryCubeOpt::get;

namespace ore {
namespace analytics {
class NPVCube {
Expand Down Expand Up @@ -107,24 +116,39 @@ public:
}
};

// The overrides below must repeat the exact spelling of the NPVCube
// declarations above, or SWIG treats the base's pure virtuals as
// unimplemented and marks the instantiations abstract, dropping their
// constructors. Two aspects matter: the parameter types have to be the
// bare `Size`/`Real` (the `using QuantLib::Size` that would resolve the
// qualified spelling sits inside a %{ %} block the SWIG parser never
// sees), and getT0/get have to carry the base's default argument, because
// SWIG expands a pure virtual with a default into one required signature
// per arity.
// The compactdefaultargs feature declared above the namespace completes
// the picture: it makes the generated wrappers fill the default themselves
// and always call C++ with the full argument list. Without it SWIG emits a
// reduced-arity call like cube->getT0(i), which does not compile: the C++
// overrides do not repeat the base's default, and default arguments are
// resolved against the static type.
template <typename T> class InMemoryCubeOpt : public ore::analytics::NPVCube {
public:
InMemoryCubeOpt(const QuantLib::Date& asof, const std::set<std::string>& ids,
const std::vector<QuantLib::Date>& dates, QuantLib::Size samples, const T& t = T());
const std::vector<QuantLib::Date>& dates, Size samples, const T& t = T());
InMemoryCubeOpt(const QuantLib::Date& asof, const std::set<std::string>& ids,
const std::vector<QuantLib::Date>& dates, QuantLib::Size samples, QuantLib::Size depth,
const std::vector<QuantLib::Date>& dates, Size samples, Size depth,
const T& t = T());
QuantLib::Size numIds() const override;
QuantLib::Size numDates() const override;
QuantLib::Size samples() const override;
QuantLib::Size depth() const override;
const std::map<std::string, QuantLib::Size>& idsAndIndexes() const override;
Size numIds() const override;
Size numDates() const override;
Size samples() const override;
Size depth() const override;
const std::map<std::string, Size>& idsAndIndexes() const override;
const std::vector<QuantLib::Date>& dates() const override;
QuantLib::Date asof() const override;
QuantLib::Real getT0(QuantLib::Size i, QuantLib::Size d) const override;
void setT0(QuantLib::Real value, QuantLib::Size i, QuantLib::Size d) override;
QuantLib::Real get(QuantLib::Size i, QuantLib::Size j, QuantLib::Size k, QuantLib::Size d) const override;
void set(QuantLib::Real value, QuantLib::Size i, QuantLib::Size j, QuantLib::Size k, QuantLib::Size d) override;
Real getT0(Size i, Size d = 0) const override;
void setT0(Real value, Size i, Size d) override;
Real get(Size i, Size j, Size k, Size d = 0) const override;
void set(Real value, Size i, Size j, Size k, Size d) override;
};

} // namespace analytics
Expand Down
56 changes: 56 additions & 0 deletions ORE-SWIG/test/test_inmemory_cube.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Regression tests for the wrongly abstract in-memory NPV cube classes.

``SinglePrecisionInMemoryCubeN`` and ``DoublePrecisionInMemoryCubeN`` used to
raise ``AttributeError: No constructor defined - class is abstract`` on
construction, leaving Python with no way to build an NPV cube at all. The
overrides in the SWIG interface did not textually match the base's pure
virtuals -- they spelled the parameters ``QuantLib::Size`` (a type the SWIG
parser cannot resolve) and dropped the base's default arguments, which SWIG
expands into one required signature per arity -- so SWIG considered the pure
virtuals unimplemented and dropped the constructors.
"""

import unittest

import ORE


class InMemoryCubeConstructionTest(unittest.TestCase):
"""The in-memory cube instantiations must be concrete."""

CUBE_CLASSES = (
"SinglePrecisionInMemoryCubeN",
"DoublePrecisionInMemoryCubeN",
)

@staticmethod
def _cube_args():
asof = ORE.Date(2, ORE.March, 2026)
ids = {"trade1", "trade2"}
dates = [ORE.Date(2, ORE.March, 2027), ORE.Date(2, ORE.March, 2028)]
return asof, ids, dates, 4

def test_cubes_construct(self) -> None:
"""Both precisions used to raise 'class is abstract' here."""
asof, ids, dates, samples = self._cube_args()
for name in self.CUBE_CLASSES:
with self.subTest(cube=name):
cube = getattr(ORE, name)(asof, ids, dates, samples)
self.assertIsInstance(cube, ORE.NPVCube)
self.assertEqual(cube.numIds(), 2)
self.assertEqual(cube.numDates(), 2)
self.assertEqual(cube.samples(), 4)
self.assertEqual(str(cube.asof()), str(asof))

def test_cube_set_get_roundtrip(self) -> None:
"""Values written into a constructed cube must read back."""
asof, ids, dates, samples = self._cube_args()
cube = ORE.DoublePrecisionInMemoryCubeN(asof, ids, dates, samples)
cube.setT0(123.25, 0, 0)
self.assertEqual(cube.getT0(0), 123.25)
cube.set(7.5, 1, 1, 3, 0)
self.assertEqual(cube.get(1, 1, 3), 7.5)


if __name__ == "__main__":
unittest.main()