Commit 8243ff92 authored by Jason Rhinelander's avatar Jason Rhinelander

Switched to virtual CRT trampoline class inheritance

Trampoline inheritance is something of a nuissance because all of the
base class methods need to be repeated.  I was doing this with some
defines that shoehorned common methods into each Member subclass, but
that was messy.

This approach (using "Curiously Recursive Templates") is nicer: each
PyWhatever class is templated with its base class, so that
PyFirmNoProd<FirmNoProd> ends up with inheritance path:

    PyFirmNoProd<FirmNoProd> : PyFirm<FirmNoProd> : PyAgent<FirmNoProd> :
    PyMember<FirmNoProd> : FirmNoProd : Firm : Agent : Member

This is still a lot of duplication, but it's mainly at the compiler
level which makes maintenance easier and keeps the code a bit cleaner.
parent 8bdcbe71
...@@ -4,7 +4,7 @@ namespace pyeris { namespace core { ...@@ -4,7 +4,7 @@ namespace pyeris { namespace core {
void bind_firm(py::module &m) { void bind_firm(py::module &m) {
py::class_<Firm, SharedMember<Firm>, PyFirm> firm(m, "Firm", py::base<Agent>()); py::class_<Firm, SharedMember<Firm>, PyFirm<>> firm(m, "Firm", py::base<Agent>());
py::class_<Firm::Reservation> reservation(firm, "Reservation"); py::class_<Firm::Reservation> reservation(firm, "Reservation");
reservation reservation
...@@ -23,22 +23,20 @@ void bind_firm(py::module &m) { ...@@ -23,22 +23,20 @@ void bind_firm(py::module &m) {
.def("supply", &Firm::supply, "bundle"_a, "assets"_a, "Supplies the given quantities immediately; this is equivalent to calling `firm.reserve(bundle).transfer(assets)`. Returns the (completed) Reservation") .def("supply", &Firm::supply, "bundle"_a, "assets"_a, "Supplies the given quantities immediately; this is equivalent to calling `firm.reserve(bundle).transfer(assets)`. Returns the (completed) Reservation")
.def_readwrite("epsilon", &Firm::epsilon, "The relative tolerance by which to adjust transfers; negative adjustments can be made to satisfy constraints; positive adjustments can be made to avoid leaving nearly-0 amounts behind. The default is 1e-10") .def_readwrite("epsilon", &Firm::epsilon, "The relative tolerance by which to adjust transfers; negative adjustments can be made to satisfy constraints; positive adjustments can be made to avoid leaving nearly-0 amounts behind. The default is 1e-10")
.def("_create_reservation", &PyFirm::_create_reservation, "bundle"_a, "Creates a Firm.Reservation object and returns it. For Firm subclasses only: external callers should use reserve() instead") .def("_create_reservation", &PyFirm<>::_create_reservation, "bundle"_a, "Creates a Firm.Reservation object and returns it. For Firm subclasses only: external callers should use reserve() instead")
.def("_can_produce", &PyFirm::canProduce, "bundle"_a, "Returns True if the firm is able to produce the requested bundle. Default implementation simply returns `firm._can_produce_any(b) >= 1`") .def("_can_produce", &PyFirm<>::canProduce, "bundle"_a, "Returns True if the firm is able to produce the requested bundle. Default implementation simply returns `firm._can_produce_any(b) >= 1`")
.def("_can_produce_any", &PyFirm::canProduceAny, "bundle"_a, "Abstract method that must be provided by a Firm subclass. Analogous to (and called by) can_supply_any(), this method returns a value in [0, 1] indicating the multiple of the bundle that the firm is able to produce instantly if needed. Subclasses may, but are not required, to return a value > 1 when more than the requested bundle quantities can be produced.") .def("_can_produce_any", &PyFirm<>::canProduceAny, "bundle"_a, "Abstract method that must be provided by a Firm subclass. Analogous to (and called by) can_supply_any(), this method returns a value in [0, 1] indicating the multiple of the bundle that the firm is able to produce instantly if needed. Subclasses may, but are not required, to return a value > 1 when more than the requested bundle quantities can be produced.")
.def("_produces", &PyFirm::produces, "bundle"_a, "Returns true if the firm is able to produce some positive multiple of the requested bundle. Equivalent to `firm._can_produce_any(bundle) > 0`, but may be more efficient when the precise multiple is not needed") .def("_produces", &PyFirm<>::produces, "bundle"_a, "Returns true if the firm is able to produce some positive multiple of the requested bundle. Equivalent to `firm._can_produce_any(bundle) > 0`, but may be more efficient when the precise multiple is not needed")
.def("_reserve_production", &PyFirm::reserveProduction, "bundle"_a, "Abstract method: must be implemented by Firm implementations. Reserves the given bundle to be produced by a subsequent _produce call. Can throw a FirmSupplyFailure exception (or exception subclass) if production is impossible. Otherwise, this method must increase _reserved_production by the requested output bundle; if production of that bundle will result in any excess production, the excess quantities should be added to _excess_production") .def("_reserve_production", &PyFirm<>::reserveProduction, "bundle"_a, "Abstract method: must be implemented by Firm implementations. Reserves the given bundle to be produced by a subsequent _produce call. Can throw a FirmSupplyFailure exception (or exception subclass) if production is impossible. Otherwise, this method must increase _reserved_production by the requested output bundle; if production of that bundle will result in any excess production, the excess quantities should be added to _excess_production")
.def("_produce", &PyFirm::produce, "bundle"_a, "Abstract method called when completing a reservation that requires production to satisfy some or all of the required output. `bundle` is the output required; returns the produced bundle that must be >= `bundle`. This method should not update _reserved_production, _excess_production, or the firm's assets: those will be updated appropriately based on the produced bundle") .def("_produce", &PyFirm<>::produce, "bundle"_a, "Abstract method called when completing a reservation that requires production to satisfy some or all of the required output. `bundle` is the output required; returns the produced bundle that must be >= `bundle`. This method should not update _reserved_production, _excess_production, or the firm's assets: those will be updated appropriately based on the produced bundle")
.def("_reduce_excess_production", &PyFirm::reduceExcessProduction, "Abstract method called after cancelling a reserved transfer. This is essentially the opposite of reserve_production_: it should reduce any reserved production output as much as possible according to the excess planned production currently in excess_production_. For example, supposed a firm produces multiples of Bundle({ good1: 1, good2: 4 }) units, and has an internal variable indicating that 30 units may still be reserved for production before hitting a capacity constraint; if this is called with _excess_production containing 5 units each of good1 and good2, the firm would increase its available capacity by 1.25 and subtract Bundle({ good1 : 1.25, good2 : 5 }) from _excess_production.") .def("_reduce_excess_production", &PyFirm<>::reduceExcessProduction, "Abstract method called after cancelling a reserved transfer. This is essentially the opposite of reserve_production_: it should reduce any reserved production output as much as possible according to the excess planned production currently in excess_production_. For example, supposed a firm produces multiples of Bundle({ good1: 1, good2: 4 }) units, and has an internal variable indicating that 30 units may still be reserved for production before hitting a capacity constraint; if this is called with _excess_production containing 5 units each of good1 and good2, the firm would increase its available capacity by 1.25 and subtract Bundle({ good1 : 1.25, good2 : 5 }) from _excess_production.")
DEF_MEMBER_COMMON_VIRTUALS(PyFirm) .def_readwrite("_reserves", &PyFirm<>::_reserves, py::return_value_policy::reference_internal, "This contains quantities removed from the assets() bundle that are covered by currently-pending reservations.")
.def_readwrite("_reserved_production", &PyFirm<>::_reserved_production, py::return_value_policy::reference_internal, "This contains quantities scheduled for production (as a result of currently-pending reservations) that will be produced when current reservations are completed.")
.def_readwrite("_reserves", &PyFirm::_reserves, py::return_value_policy::reference_internal, "This contains quantities removed from the assets() bundle that are covered by currently-pending reservations.") .def_readwrite("_excess_production", &PyFirm<>::_excess_production, py::return_value_policy::reference_internal, "This contains the amounts that will be produced to satisfy reserved production (due to reservations) but that is not actually part of the transferred quantity. Typically this results from a multiple-good production where the requested output units are not a perfect multiple of the production unit")
.def_readwrite("_reserved_production", &PyFirm::_reserved_production, py::return_value_policy::reference_internal, "This contains quantities scheduled for production (as a result of currently-pending reservations) that will be produced when current reservations are completed.")
.def_readwrite("_excess_production", &PyFirm::_excess_production, py::return_value_policy::reference_internal, "This contains the amounts that will be produced to satisfy reserved production (due to reservations) but that is not actually part of the transferred quantity. Typically this results from a multiple-good production where the requested output units are not a perfect multiple of the production unit")
; ;
py::class_<FirmNoProd, SharedMember<FirmNoProd>, PyFirmNoProd> firm_noprod(m, "FirmNoProd", firm); py::class_<FirmNoProd, SharedMember<FirmNoProd>, PyFirmNoProd<>> firm_noprod(m, "FirmNoProd", firm);
firm_noprod firm_noprod
.def("ensure_next", &FirmNoProd::ensureNext, "bundle"_a, "Can be called during an inter-period optimization stage to ensure that (at least) the requested bundle will be available in the subsequent period. This calls _product_next with any quantity that isn't satisfiable from the firm's current assets()") .def("ensure_next", &FirmNoProd::ensureNext, "bundle"_a, "Can be called during an inter-period optimization stage to ensure that (at least) the requested bundle will be available in the subsequent period. This calls _product_next with any quantity that isn't satisfiable from the firm's current assets()")
.def("_produce_next", &FirmNoProd::produceNext, "bundle"_a, "Called internally by ensure_next with the amount that needs to be produced and added to assets() to satisfy the required bundle amount. This abstract method must be provided by a subclass.") .def("_produce_next", &FirmNoProd::produceNext, "bundle"_a, "Called internally by ensure_next with the amount that needs to be produced and added to assets() to satisfy the required bundle amount. This abstract method must be provided by a subclass.")
......
...@@ -8,31 +8,33 @@ using namespace eris::agent; ...@@ -8,31 +8,33 @@ using namespace eris::agent;
namespace pyeris { namespace core { namespace pyeris { namespace core {
// Trampoline class for (abstract) Firm // Trampoline class for (abstract) Firm
class PyFirm : public eris::Firm { template <class Base = Firm>
class PyFirm : public PyAgent<Base> {
public: public:
// Inherit constructors // Inherit constructors
using Firm::Firm; using Firm::Firm;
// Virtual functions (these must have the same name, since they are overrides): // Virtual functions (these must have the same name, since they are overrides):
bool canSupply(const Bundle &b) const override { PYBIND11_OVERLOAD_NAME(bool, Firm, "can_supply", canSupply, b); } virtual bool canSupply(const Bundle &b) const override { PYBIND11_OVERLOAD_NAME(bool, Firm, "can_supply", canSupply, b); }
double canSupplyAny(const Bundle &b) const override { PYBIND11_OVERLOAD_NAME(double, Firm, "can_supply_any", canSupplyAny, b); } virtual double canSupplyAny(const Bundle &b) const override { PYBIND11_OVERLOAD_NAME(double, Firm, "can_supply_any", canSupplyAny, b); }
bool supplies(const Bundle &b) const override { PYBIND11_OVERLOAD(bool, Firm, supplies, b); } virtual bool supplies(const Bundle &b) const override { PYBIND11_OVERLOAD(bool, Firm, supplies, b); }
bool canProduce(const Bundle &b) const override { PYBIND11_OVERLOAD_NAME(bool, Firm, "_can_produce", canProduce, b); } virtual bool canProduce(const Bundle &b) const override { PYBIND11_OVERLOAD_NAME(bool, Firm, "_can_produce", canProduce, b); }
double canProduceAny(const Bundle &b) const override { PYBIND11_OVERLOAD_NAME(double, Firm, "_can_produce_any", canProduceAny, b); } virtual double canProduceAny(const Bundle &b) const override { PYBIND11_OVERLOAD_NAME(double, Firm, "_can_produce_any", canProduceAny, b); }
bool produces(const Bundle &b) const override { PYBIND11_OVERLOAD_NAME(bool, Firm, "_produces", produces, b); } virtual bool produces(const Bundle &b) const override { PYBIND11_OVERLOAD_NAME(bool, Firm, "_produces", produces, b); }
Reservation reserve(const BundleNegative &reserve) override { PYBIND11_OVERLOAD(Reservation, Firm, reserve, reserve); } virtual Firm::Reservation reserve(const BundleNegative &reserve) override { PYBIND11_OVERLOAD(Firm::Reservation, Firm, reserve, reserve); }
Bundle produce(const Bundle &b) override { PYBIND11_OVERLOAD_PURE(Bundle, Firm, produce, b); } virtual Bundle produce(const Bundle &b) override { PYBIND11_OVERLOAD_PURE(Bundle, Firm, produce, b); }
void reserveProduction(const Bundle &reserve) override { PYBIND11_OVERLOAD_NAME(void, Firm, "_reserve_production", reserveProduction, reserve); } virtual void reserveProduction(const Bundle &reserve) override { PYBIND11_OVERLOAD_NAME(void, Firm, "_reserve_production", reserveProduction, reserve); }
void reduceExcessProduction() override { PYBIND11_OVERLOAD_PURE_NAME(void, Firm, "_reduce_excess_production", reduceExcessProduction); } virtual void reduceExcessProduction() override { PYBIND11_OVERLOAD_PURE_NAME(void, Firm, "_reduce_excess_production", reduceExcessProduction); }
VIRTUAL_MEMBER_METHODS(Firm)
// These are non-virtual, but are protected and need to be exposed, but the name can differ // These are non-virtual, but are protected and need to be exposed, but the name can differ
Reservation _create_reservation(BundleNegative bundle) { return Firm::createReservation(std::move(bundle)); } Firm::Reservation _create_reservation(BundleNegative bundle) { return Firm::createReservation(std::move(bundle)); }
using PyAgent<Base>::reserves_;
using PyAgent<Base>::reserved_production_;
using PyAgent<Base>::excess_production_;
// Access the bundles stored in the parent // Access the bundles stored in the parent
Bundle* _reserves{&reserves_}; Bundle* _reserves{&reserves_};
Bundle* _reserved_production{&reserved_production_}; Bundle* _reserved_production{&reserved_production_};
...@@ -40,9 +42,10 @@ public: ...@@ -40,9 +42,10 @@ public:
}; };
// Trampoline class for (abstract) FirmNoProd // Trampoline class for (abstract) FirmNoProd
class PyFirmNoProd : public FirmNoProd, public PyFirm { template <class Base = FirmNoProd>
class PyFirmNoProd : public PyFirm<Base> {
public: public:
void produceNext(const Bundle &b) { PYBIND11_OVERLOAD_PURE_NAME(void, Firm, "_produce_next", produceNext, b); } virtual void produceNext(const Bundle &b) override { PYBIND11_OVERLOAD_PURE_NAME(void, Firm, "_produce_next", produceNext, b); }
}; };
......
...@@ -4,7 +4,7 @@ namespace pyeris { namespace core { ...@@ -4,7 +4,7 @@ namespace pyeris { namespace core {
void bind_market(py::module &m) { void bind_market(py::module &m) {
py::class_<Market, SharedMember<Market>, PyMarket> market(m, "Market", py::base<Member>()); py::class_<Market, SharedMember<Market>, PyMarket<>> market(m, "Market", py::base<Member>());
py::class_<Market::price_info> market_price(market, "PriceInfo"); py::class_<Market::price_info> market_price(market, "PriceInfo");
market_price market_price
...@@ -54,15 +54,13 @@ void bind_market(py::module &m) { ...@@ -54,15 +54,13 @@ void bind_market(py::module &m) {
.def("reserve", &Market::reserve, "agent"_a, "quantity"_a, "maximum"_a = std::numeric_limits<double>::infinity(), .def("reserve", &Market::reserve, "agent"_a, "quantity"_a, "maximum"_a = std::numeric_limits<double>::infinity(),
"Takes an agent, quantity, and maximum total price; reserves a given quantity of the market output, removing its payment from the agent and making the quantity unavailable to other agents. If the reservation is completed the quantity will be transferred to the agent's assets; if aborted, the payment will be returned. " "Takes an agent, quantity, and maximum total price; reserves a given quantity of the market output, removing its payment from the agent and making the quantity unavailable to other agents. If the reservation is completed the quantity will be transferred to the agent's assets; if aborted, the payment will be returned. "
"Throws: MarketOutputInfeasible if q units of the output bundle are not available; MarketLowPrice if the output is available, but the cost would exceed the specified maximum; MarketInsufficientAssets if the agent's assets doesn't cover the required payment.") "Throws: MarketOutputInfeasible if q units of the output bundle are not available; MarketLowPrice if the output is available, but the cost would exceed the specified maximum; MarketInsufficientAssets if the agent's assets doesn't cover the required payment.")
.def("_create_reservation", &PyMarket::_create_reservation, "agent"_a, "quantity"_a, "maximum"_a, .def("_create_reservation", &PyMarket<>::_create_reservation, "agent"_a, "quantity"_a, "maximum"_a,
"Creates a Reservation object for the given agent, quantity, and maximum expenditure. This method is for internal use by Market subclasses to actually create the object; external callers should call reserve() instead. This method removes the payment from the agent ") "Creates a Reservation object for the given agent, quantity, and maximum expenditure. This method is for internal use by Market subclasses to actually create the object; external callers should call reserve() instead. This method removes the payment from the agent ")
.def("add_firm", &Market::addFirm, "Adds the given firm to the firms supplying output units in this market.") .def("add_firm", &Market::addFirm, "Adds the given firm to the firms supplying output units in this market.")
.def("remove_firm", &Market::removeFirm, "Removes the given firm from the firms supplying output units in this market. This only needs to be called explicitly when the firm remains in the simulation; firms removed from the simulation will have this called automatically.") .def("remove_firm", &Market::removeFirm, "Removes the given firm from the firms supplying output units in this market. This only needs to be called explicitly when the firm remains in the simulation; firms removed from the simulation will have this called automatically.")
.def("firms", &Market::firms, "Returns the set of firms that supply this market") .def("firms", &Market::firms, "Returns the set of firms that supply this market")
.def_readonly("output_unit", &Market::output_unit, "The bundle in terms of which output quantities are measured.") .def_readonly("output_unit", &Market::output_unit, "The bundle in terms of which output quantities are measured.")
.def_readonly("price_unit", &Market::price_unit, "The bundle in terms of which output prices are measured.") .def_readonly("price_unit", &Market::price_unit, "The bundle in terms of which output prices are measured.")
DEF_MEMBER_COMMON_VIRTUALS(PyMarket)
; ;
py::register_exception<Market::output_infeasible>(m, "MarketOutputInfeasible"); py::register_exception<Market::output_infeasible>(m, "MarketOutputInfeasible");
......
...@@ -7,27 +7,26 @@ ...@@ -7,27 +7,26 @@
namespace pyeris { namespace core { namespace pyeris { namespace core {
// Trampoline class for (abstract) Market // Trampoline class for (abstract) Market
class PyMarket : public Market { template <class Base = Market>
class PyMarket : public PyMember<Base> {
public: public:
// Inherit constructors // Inherit constructors
using Market::Market; using PyMember<Base>::PyMember;
price_info price(double q) const override { PYBIND11_OVERLOAD_PURE(price_info, Market, price, q); } virtual Market::price_info price(double q) const override { PYBIND11_OVERLOAD_PURE(Market::price_info, Market, price, q); }
quantity_info quantity(double p) const override { PYBIND11_OVERLOAD_PURE(quantity_info, Market, quantity, p); } virtual Market::quantity_info quantity(double p) const override { PYBIND11_OVERLOAD_PURE(Market::quantity_info, Market, quantity, p); }
// Python doesn't have move constructors, so we need a special version of this that returns a // Python doesn't have move constructors, so we need a special version of this that returns a
// pointer instead of an instances. (We don't need to enforce non-copying on the Python side, // pointer instead of an instances. (We don't need to enforce non-copying on the Python side,
// since Python references rather than copies unless you add an explicit copying operation). // since Python references rather than copies unless you add an explicit copying operation).
Reservation reserve(SharedMember<Agent> agent, double q, double p_max = std::numeric_limits<double>::infinity()) override { virtual Market::Reservation reserve(SharedMember<Agent> agent, double q, double p_max = std::numeric_limits<double>::infinity()) override {
PYBIND11_OVERLOAD_PURE(Reservation, Market, reserve, agent, q, p_max); PYBIND11_OVERLOAD_PURE(Market::Reservation, Market, reserve, agent, q, p_max);
} }
void buy(Reservation &res) override { PYBIND11_OVERLOAD(void, Market, buy, res); } virtual void buy(Market::Reservation &res) override { PYBIND11_OVERLOAD(void, Market, buy, res); }
void addFirm(SharedMember<Firm> f) override { PYBIND11_OVERLOAD(void, Market, addFirm, f); } virtual void addFirm(SharedMember<Firm> f) override { PYBIND11_OVERLOAD(void, Market, addFirm, f); }
void removeFirm(eris_id_t fid) override { PYBIND11_OVERLOAD(void, Market, removeFirm, fid); } virtual void removeFirm(eris_id_t fid) override { PYBIND11_OVERLOAD(void, Market, removeFirm, fid); }
VIRTUAL_MEMBER_METHODS(Market)
// Not virtual, but needs to be exposed publically // Not virtual, but needs to be exposed publically
Reservation _create_reservation(const SharedMember<Agent> &agent, double q, double p) { return createReservation(agent, q, p); } Market::Reservation _create_reservation(const SharedMember<Agent> &agent, double q, double p) { return PyMember<Base>::createReservation(agent, q, p); }
}; };
void bind_market(py::module &m); void bind_market(py::module &m);
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
namespace pyeris { namespace core { namespace pyeris { namespace core {
void bind_members(py::module &m) { void bind_members(py::module &m) {
py::class_<Member, SharedMember<Member>, PyMember> member(m, "Member"); py::class_<Member, SharedMember<Member>, PyMember<>> member(m, "Member");
member member
.def(py::init<>()) .def(py::init<>())
.def("id", &Member::id, "Returns the unique (per simulation) id of this member. Returns 0 if the object is not part of a simulation. Note that a Member-derived object can also be implicitly converted to its integer id, and so can often be used directly wherever an id is required.") .def("id", &Member::id, "Returns the unique (per simulation) id of this member. Returns 0 if the object is not part of a simulation. Note that a Member-derived object can also be implicitly converted to its integer id, and so can often be used directly wherever an id is required.")
...@@ -17,20 +17,17 @@ void bind_members(py::module &m) { ...@@ -17,20 +17,17 @@ void bind_members(py::module &m) {
// templated type casting, but really aren't that useful in Python since types are dynamic // templated type casting, but really aren't that useful in Python since types are dynamic
.def("depends_on", &Member::dependsOn, "Records a dependency with the member's simulation; typically invoked in the _added() method.") .def("depends_on", &Member::dependsOn, "Records a dependency with the member's simulation; typically invoked in the _added() method.")
.def("depends_weakly_on", &Member::dependsWeaklyOn, "Records a weak dependency with the member's simulation; typically invoked in the _added() method.") .def("depends_weakly_on", &Member::dependsWeaklyOn, "Records a weak dependency with the member's simulation; typically invoked in the _added() method.")
// Virtual methods (also protected, which is why we reference PyMember):
DEF_MEMBER_COMMON_VIRTUALS(PyMember)
; ;
py::implicitly_convertible<Member, eris_id_t>(); py::implicitly_convertible<Member, eris_id_t>();
py::class_<Agent, SharedMember<Agent>, PyAgent> agent(m, "Agent", member); py::class_<Agent, SharedMember<Agent>, PyAgent<>> agent(m, "Agent", member);
agent agent
.def(py::init<>()) .def(py::init<>())
.def_readwrite("assets", &Agent::assets, "The agent's bundle of assets") .def_readwrite("assets", &Agent::assets, "The agent's bundle of assets")
; ;
py::class_<Good, SharedMember<Good>, PyGood> good(m, "Good", member); py::class_<Good, SharedMember<Good>, PyGood<>> good(m, "Good", member);
good.def(py::init<std::string>(), py::arg("name") = ""); good.def(py::init<std::string>(), py::arg("name") = "");
bind_firm(m); bind_firm(m);
......
...@@ -15,36 +15,36 @@ ...@@ -15,36 +15,36 @@
namespace pyeris { namespace core { namespace pyeris { namespace core {
#define VIRTUAL_MEMBER_METHODS(Class) \ template <class Base = Member>
virtual void added() override { \ class PyMember : public Base {
OptimizerGlue::applyGlue(this->simulation(), this->sharedSelf(), py::cast(this)); \ // Inherit constructors
PYBIND11_OVERLOAD(void, Class, added); \ using Base::Base;
} \
virtual void removed() override { \ virtual void added() override {
/* Simulation takes a reference during add(), we release it during member.removed() */ \ OptimizerGlue::applyGlue(this->simulation(), this->sharedSelf(), py::cast(this));
py::cast(this).dec_ref(); \ PYBIND11_OVERLOAD(void, Base, added);
PYBIND11_OVERLOAD(void, Member, removed); \
} \
virtual void weakDepRemoved(SharedMember<Member> removed, eris_id_t old_id) override { \
PYBIND11_OVERLOAD_NAME(void, Member, "weak_dep_removed", weakDepRemoved, removed, old_id); \
} }
virtual void removed() override {
#define DEF_MEMBER_COMMON_VIRTUALS(TrampolineClass) \ /* Simulation takes a reference during add(), we release it during member.removed() */
.def("added", &TrampolineClass::added, "Called to notify the object that it has just been added to a simulation. The simulation() and id() methods access the simulation object and the assigned id, respectively.") \ py::cast(this).dec_ref();
.def("removed", &TrampolineClass::removed, "Called to notify the object that it has just been removed from a simulation. The simulation() and id() methods still work until this method returns, however the simulation no longer considers the object to belong to itself.") \ PYBIND11_OVERLOAD(void, Base, removed);
.def("weak_dep_removed", &TrampolineClass::weakDepRemoved, "Called when a weak dependency of this object is removed from the simulation.") }
virtual void weakDepRemoved(SharedMember<Member> removed, eris_id_t old_id) override {
PYBIND11_OVERLOAD_NAME(void, Base, "weak_dep_removed", weakDepRemoved, removed, old_id);
#define SIMPLE_MEMBER_TRAMPOLINE(Class) \ }
class Py##Class : public Class { \ };
public: \
using Class::Class; \ template <class Base = Agent>
VIRTUAL_MEMBER_METHODS(Class) \ class PyAgent : public PyMember<Base> {
} // Inherit constructors
using PyMember<Base>::PyMember;
SIMPLE_MEMBER_TRAMPOLINE(Member); };
SIMPLE_MEMBER_TRAMPOLINE(Agent);
SIMPLE_MEMBER_TRAMPOLINE(Good); template <class Base = Good>
class PyGood : public PyMember<Base> {
// Inherit constructors
using PyMember<Base>::PyMember;
};
void bind_members(py::module &m); void bind_members(py::module &m);
......
...@@ -11,8 +11,8 @@ PYBIND11_PLUGIN(position) { ...@@ -11,8 +11,8 @@ PYBIND11_PLUGIN(position) {
py::module m("eris.position", "eris interface for Python -- position and positional member code"); py::module m("eris.position", "eris interface for Python -- position and positional member code");
position::bind_position(m); position::bind_position(m);
auto positional = position::bind_positional(m); position::bind_positional(m);
position::bind_wrapped_positional(m, positional); position::bind_wrapped_positional(m);
return m.ptr(); return m.ptr();
} }
......
...@@ -9,8 +9,9 @@ namespace pyeris { namespace position { ...@@ -9,8 +9,9 @@ namespace pyeris { namespace position {
// support isn't trivial: it will have to manage some intermediate extension class). For now will // support isn't trivial: it will have to manage some intermediate extension class). For now will
// still provide it, but the best a Python class can do is "have" a Positional object rather than // still provide it, but the best a Python class can do is "have" a Positional object rather than
// "be" a Positional object. // "be" a Positional object.
PBClass bind_positional(py::module &m) { void bind_positional(py::module &m) {
PBClass positional(m, "Positional", "A positional instance wraps a Position and an optional bounding box. It includes various methods to adjust and move the position. Unlike the eris C++ interface, it is not currently possible to inherit from this and another base class."); py::class_<PositionalBase, std::unique_ptr<PositionalBase>, PyPositional<>> positional(m, "Positional",
"A positional instance wraps a Position and an optional bounding box. It includes various methods to adjust and move the position. Unlike the eris C++ interface, it is not currently possible to inherit from this and another base class.");
positional positional
.def(py::init<const Position &, const Position &, const Position &>(), "position"_a, "vertex1"_a, "vertex2"_a, "Constructs a Positional member bounding box defined by the two vertices") .def(py::init<const Position &, const Position &, const Position &>(), "position"_a, "vertex1"_a, "vertex2"_a, "Constructs a Positional member bounding box defined by the two vertices")
.def(py::init<const Position &, double, double>(), "position"_a, "b1"_a, "b2"_a, "Constructs a Positional member with the given boundaries in each dimension") .def(py::init<const Position &, double, double>(), "position"_a, "b1"_a, "b2"_a, "Constructs a Positional member with the given boundaries in each dimension")
...@@ -30,11 +31,10 @@ PBClass bind_positional(py::module &m) { ...@@ -30,11 +31,10 @@ PBClass bind_positional(py::module &m) {
.def("move_to", &PositionalBase::moveTo, "Moves to the given position. If the position is outside the bounding box, this either moves to the nearest boundary point (if move_to_boundary() returns true) or throws a PositionalBoundaryError (if move_to_boundary() is false).") .def("move_to", &PositionalBase::moveTo, "Moves to the given position. If the position is outside the bounding box, this either moves to the nearest boundary point (if move_to_boundary() returns true) or throws a PositionalBoundaryError (if move_to_boundary() is false).")
.def("move_by", &PositionalBase::moveBy, "Moves by the given relative amount. This is simply a shortcut for calling move_to with the current position plus the given relative amount") .def("move_by", &PositionalBase::moveBy, "Moves by the given relative amount. This is simply a shortcut for calling move_to with the current position plus the given relative amount")
.def("to_boundary", &PositionalBase::toBoundary, "Returns a Position that is as close to the given Position as possible, but within the boundary. If the point is inside the bounding box, the returned value will equal the given position; otherwise it will be a boundary point") .def("to_boundary", &PositionalBase::toBoundary, "Returns a Position that is as close to the given Position as possible, but within the boundary. If the point is inside the bounding box, the returned value will equal the given position; otherwise it will be a boundary point")
.def("truncate", &PyPositional<>::truncate, "pos"_a, "raise_on_truncation"_a = false, "Checks the given Position for any needed truncation and either updates it (if raise_on_truncation is false) or raises a PositionalBoundaryError exception (if raise_on_truncation is true)")
; ;
py::register_exception<PositionalBoundaryError>(m, "PositionalBoundaryError"); py::register_exception<PositionalBoundaryError>(m, "PositionalBoundaryError");
return positional;
} }
}} }}
...@@ -5,29 +5,33 @@ ...@@ -5,29 +5,33 @@
namespace pyeris { namespace position { namespace pyeris { namespace position {
class PyPositional : public PositionalBase { template <class Base = PositionalBase>
class PyPositional : public Base {
public: public:
// Inherit constructors
using Base::Base;
// Exposing protected constructors (the C++ instance goes through a templated sub class) // Exposing protected constructors (the C++ instance goes through a templated sub class)
PyPositional(const Position &p, const Position &boundary1, const Position &boundary2) PyPositional(const Position &p, const Position &boundary1, const Position &boundary2)
: PositionalBase(p, boundary1, boundary2) {} : Base(p, boundary1, boundary2) {}
PyPositional(const Position &p, double b1, double b2) PyPositional(const Position &p, double b1, double b2)
: PositionalBase(p, b1, b2) {} : Base(p, b1, b2) {}
PyPositional(const Position &p) : PositionalBase(p) {} PyPositional(const Position &p) : Base(p) {}
// Virtual methods: // Virtual methods:
Position vectorTo(const Position &pos) const override { PYBIND11_OVERLOAD_NAME(Position, PositionalBase, "vector_to", vectorTo, pos); } virtual Position vectorTo(const Position &pos) const override { PYBIND11_OVERLOAD_NAME(Position, PositionalBase, "vector_to", vectorTo, pos); }
bool bounded() const override { PYBIND11_OVERLOAD(bool, PositionalBase, bounded); } virtual bool bounded() const override { PYBIND11_OVERLOAD(bool, PositionalBase, bounded); }
bool binding() const override { PYBIND11_OVERLOAD(bool, PositionalBase, binding); } virtual bool binding() const override { PYBIND11_OVERLOAD(bool, PositionalBase, binding); }
bool bindingLower() const override { PYBIND11_OVERLOAD_NAME(bool, PositionalBase, "binding_lower", bindingLower); } virtual bool bindingLower() const override { PYBIND11_OVERLOAD_NAME(bool, PositionalBase, "binding_lower", bindingLower); }
bool bindingUpper() const override { PYBIND11_OVERLOAD_NAME(bool, PositionalBase, "binding_upper", bindingUpper); } virtual bool bindingUpper() const override { PYBIND11_OVERLOAD_NAME(bool, PositionalBase, "binding_upper", bindingUpper); }
Position lowerBound() const override { PYBIND11_OVERLOAD_NAME(Position, PositionalBase, "lower_bound", lowerBound); } virtual Position lowerBound() const override { PYBIND11_OVERLOAD_NAME(Position, PositionalBase, "lower_bound", lowerBound); }
Position upperBound() const override { PYBIND11_OVERLOAD_NAME(Position, PositionalBase, "upper_bound", upperBound); } virtual Position upperBound() const override { PYBIND11_OVERLOAD_NAME(Position, PositionalBase, "upper_bound", upperBound); }
bool moveTo(Position p) override { PYBIND11_OVERLOAD_NAME(bool, PositionalBase, "move_to", moveTo, p); } virtual bool moveTo(Position p) override { PYBIND11_OVERLOAD_NAME(bool, PositionalBase, "move_to", moveTo, p); }
Position toBoundary(Position pos) const override { PYBIND11_OVERLOAD_NAME(Position, PositionalBase, "to_boundary", toBoundary, pos); } virtual Position toBoundary(Position pos) const override { PYBIND11_OVERLOAD_NAME(Position, PositionalBase, "to_boundary", toBoundary, pos); }
virtual bool truncate(Position &pos, bool throw_on_truncation = false) const override { PYBIND11_OVERLOAD(bool, PositionalBase, truncate, pos, throw_on_truncation); }
}; };
typedef py::class_<PositionalBase, std::unique_ptr<PositionalBase>, PyPositional> PBClass;
PBClass bind_positional(py::module &m); void bind_positional(py::module &m);
}} }}
...@@ -4,9 +4,10 @@ namespace pyeris { namespace position { ...@@ -4,9 +4,10 @@ namespace pyeris { namespace position {
void bind_wrapped_positional(py::module &m, PBClass &posal) { void bind_wrapped_positional(py::module &m) {
py::class_<WrappedPositionalBase, std::unique_ptr<WrappedPositionalBase>, PyWrappedPositional> wpositional(m, "WrappedPositional", posal, py::class_<WrappedPositionalBase, std::unique_ptr<WrappedPositionalBase>, PyWrappedPositional<>> wpositional(
m, "WrappedPositional", py::base<PositionalBase>(),
"A WrappedPositional instance wraps a Position and an optional bounding box where some or all of the dimensions of the bounding box wrap. Crossing a wrapped boundary moves to the opposite boundary (with uncross boundary coordinates remaining the same). In one dimension, such a space resembles the circumference of a circle; in two dimensions, a torus (i.e. the surface of a donut); in two dimensions with only one dimension wrapping, the outer surface of a cylinder. The class includes various methods to adjust and move the position, taking the wrapping into account. Unlike the eris C++ interface, it is not currently possible to inherit from this and another base class."); "A WrappedPositional instance wraps a Position and an optional bounding box where some or all of the dimensions of the bounding box wrap. Crossing a wrapped boundary moves to the opposite boundary (with uncross boundary coordinates remaining the same). In one dimension, such a space resembles the circumference of a circle; in two dimensions, a torus (i.e. the surface of a donut); in two dimensions with only one dimension wrapping, the outer surface of a cylinder. The class includes various methods to adjust and move the position, taking the wrapping into account. Unlike the eris C++ interface, it is not currently possible to inherit from this and another base class.");
wpositional wpositional
.def(py::init<const Position &, const Position &, const Position &>(), "position"_a, "vertex1"_a, "vertex2"_a, "Constructs a WrappedPositional member with the bounding box defined by the two vertices. The bounding box is wrapped in every dimension (that is, moving beyond any boundary moves to a point inside the opposite boundary); in 1-dimension, this is the circumference of a circle; in 2-dimensions, a torus; in higher dimensions a hypertorus") .def(py::init<const Position &, const Position &, const Position &>(), "position"_a, "vertex1"_a, "vertex2"_a, "Constructs a WrappedPositional member with the bounding box defined by the two vertices. The bounding box is wrapped in every dimension (that is, moving beyond any boundary moves to a point inside the opposite boundary); in 1-dimension, this is the circumference of a circle; in 2-dimensions, a torus; in higher dimensions a hypertorus")
...@@ -15,7 +16,7 @@ void bind_wrapped_positional(py::module &m, PBClass &posal) { ...@@ -15,7 +16,7 @@ void bind_wrapped_positional(py::module &m, PBClass &posal) {
.def(py::init<const Position &, const Position &, const Position &, const std::unordered_set<size_t> &>(), "position"_a, "vertex1"_a, "vertex2"_a, "wrap_dimensions"_a, "Constructs a WrappedPositional member with bounds defined by the two vertices and a given set of dimension indices on which to wrap. Wrapping occurs at the boundaries of the given dimensions; for other dimensions the boundary is an actual boundary that the object cannot move beyond") .def(py::init<const Position &, const Position &, const Position &, const std::unordered_set<size_t> &>(), "position"_a, "vertex1"_a, "vertex2"_a, "wrap_dimensions"_a, "Constructs a WrappedPositional member with bounds defined by the two vertices and a given set of dimension indices on which to wrap. Wrapping occurs at the boundaries of the given dimensions; for other dimensions the boundary is an actual boundary that the object cannot move beyond")
.def("vector_to", (Position (WrappedPositionalBase::*)(const PositionalBase &) const) &WrappedPositionalBase::vectorTo, "other"_a, "Returns the shortest vector that leads from this member to the position of the given positional member; the returned vector may cross one or more wrapped boundaries. Note that the vector is calculated as if both `self` and `other` have the wrapping boundary applied to `self`, which can give unexpected results if `self` and `other` have different boundaries") .def("vector_to", (Position (WrappedPositionalBase::*)(const PositionalBase &) const) &WrappedPositionalBase::vectorTo, "other"_a, "Returns the shortest vector that leads from this member to the position of the given positional member; the returned vector may cross one or more wrapped boundaries. Note that the vector is calculated as if both `self` and `other` have the wrapping boundary applied to `self`, which can give unexpected results if `self` and `other` have different boundaries")
.def("vector_to", (Position (WrappedPositionalBase::*)(const Position &) const) &WrappedPositionalBase::vectorTo, "pos"_a, "Returns the shortest vector that leads from this member to the given position; the returned vector may cross one or more wrapped boundaries") .def("vector_to", (Position (WrappedPositionalBase::*)(const Position &) const) &WrappedPositionalBase::vectorTo, "pos"_a, "Returns the shortest vector that leads from this member to the given position; the returned vector may cross one or more wrapped boundaries")
.def("vector_to", [](PyWrappedPositional &self, std::vector<double> coordinates) { return self.vectorTo(Position(std::move(coordinates))); }, "coordinates"_a, "This is a shortcut for calling .vector_to(Position(coordinates)), that is, it constructs the Position from a list of coordinates") .def("vector_to", [](WrappedPositionalBase &self, std::vector<double> coordinates) { return self.vectorTo(Position(std::move(coordinates))); }, "coordinates"_a, "This is a shortcut for calling .vector_to(Position(coordinates)), that is, it constructs the Position from a list of coordinates")
.def("wrapped", &WrappedPositionalBase::wrapped, "dim"_a, "Returns true if the given dimension is wrapped, false otherwise") .def("wrapped", &WrappedPositionalBase::wrapped, "dim"_a, "Returns true if the given dimension is wrapped, false otherwise")
.def("wrap", (void (WrappedPositionalBase::*)(size_t)) &WrappedPositionalBase::wrap, "dim"_a, "Enables wrapping on the given dimension. This will fail silently if the dimension has an infinite boundary, or if the two boundaries are identical (i.e. the bounded dimension has size 0)") .def("wrap", (void (WrappedPositionalBase::*)(size_t)) &WrappedPositionalBase::wrap, "dim"_a, "Enables wrapping on the given dimension. This will fail silently if the dimension has an infinite boundary, or if the two boundaries are identical (i.e. the bounded dimension has size 0)")
.def("wrap", (void (WrappedPositionalBase::*)(const std::unordered_set<size_t> &)) &WrappedPositionalBase::wrap, "dims"_a, "Enables wrapping on each dimension index in the given set. Any dimension with an infinite boundary or zero size will be ignored") .def("wrap", (void (WrappedPositionalBase::*)(const std::unordered_set<size_t> &)) &WrappedPositionalBase::wrap, "dims"_a, "Enables wrapping on each dimension index in the given set. Any dimension with an infinite boundary or zero size will be ignored")
......
...@@ -6,34 +6,38 @@ ...@@ -6,34 +6,38 @@
namespace pyeris { namespace position { namespace pyeris { namespace position {
class PyWrappedPositional : public WrappedPositionalBase { template <class Base = WrappedPositionalBase>
class PyWrappedPositional : public PyPositional<Base> {
public: public:
using PyPositional<Base>::PyPositional;
// Exposing protected constructors (the C++ instance goes through a templated sub class) // Exposing protected constructors (the C++ instance goes through a templated sub class)
PyWrappedPositional(const Position &p, const Position &boundary1, const Position &boundary2) PyWrappedPositional(const Position &p, const Position &boundary1, const Position &boundary2)
: WrappedPositionalBase(p, boundary1, boundary2) {} : PyPositional<Base>(p, boundary1, boundary2) {}
PyWrappedPositional(const Position &p, double b1, double b2) PyWrappedPositional(const Position &p, double b1, double b2)
: WrappedPositionalBase(p, b1, b2) {} : PyPositional<Base>(p, b1, b2) {}
PyWrappedPositional(const Position &p) : WrappedPositionalBase(p) {} PyWrappedPositional(const Position &p) : PyPositional<Base>(p) {}
PyWrappedPositional(const Position &p, const Position &b1, const Position &b2, const std::unordered_set<size_t> &wrap_dims) PyWrappedPositional(const Position &p, const Position &b1, const Position &b2, const std::unordered_set<size_t> &wrap_dims)
: WrappedPositionalBase(p, b1, b2, wrap_dims) {} : PyPositional<Base>(p, b1, b2, wrap_dims) {}
// Virtual methods: // Virtual methods:
Position vectorTo(const Position &pos) const override { PYBIND11_OVERLOAD_NAME(Position, WrappedPositionalBase, "vector_to", vectorTo, pos); } virtual Position vectorTo(const Position &pos) const override { PYBIND11_OVERLOAD_NAME(Position, WrappedPositionalBase, "vector_to", vectorTo, pos); }
bool wrapped(size_t dim) const override { PYBIND11_OVERLOAD(bool, WrappedPositionalBase, wrapped, dim); } virtual bool wrapped(size_t dim) const override { PYBIND11_OVERLOAD(bool, WrappedPositionalBase, wrapped, dim); }
void wrap(size_t dim) override { PYBIND11_OVERLOAD(void, WrappedPositionalBase, wrap, dim); } virtual void wrap(size_t dim) override { PYBIND11_OVERLOAD(void, WrappedPositionalBase, wrap, dim); }
void wrap(Position &p) const override { PYBIND11_OVERLOAD(void, WrappedPositionalBase, wrap, p); } virtual void wrap(Position &p) const override { PYBIND11_OVERLOAD(void, WrappedPositionalBase, wrap, p); }
void unwrap(size_t dim) override { PYBIND11_OVERLOAD(void, WrappedPositionalBase, unwrap, dim); } virtual void unwrap(size_t dim) override { PYBIND11_OVERLOAD(void, WrappedPositionalBase, unwrap, dim); }
bool bounded() const override { PYBIND11_OVERLOAD(bool, WrappedPositionalBase, bounded); } virtual bool bounded() const override { PYBIND11_OVERLOAD(bool, WrappedPositionalBase, bounded); }
bool binding() const override { PYBIND11_OVERLOAD(bool, WrappedPositionalBase, binding); } virtual bool binding() const override { PYBIND11_OVERLOAD(bool, WrappedPositionalBase, binding); }
bool bindingLower() const override { PYBIND11_OVERLOAD_NAME(bool, WrappedPositionalBase, "binding_lower", bindingLower); } virtual bool bindingLower() const override { PYBIND11_OVERLOAD_NAME(bool, WrappedPositionalBase, "binding_lower", bindingLower); }
bool bindingUpper() const override { PYBIND11_OVERLOAD_NAME(bool, WrappedPositionalBase, "binding_upper", bindingUpper); } virtual bool bindingUpper() const override { PYBIND11_OVERLOAD_NAME(bool, WrappedPositionalBase, "binding_upper", bindingUpper); }
Position lowerBound() const override { PYBIND11_OVERLOAD_NAME(Position, WrappedPositionalBase, "lower_bound", lowerBound); } virtual Position lowerBound() const override { PYBIND11_OVERLOAD_NAME(Position, WrappedPositionalBase, "lower_bound", lowerBound); }
Position upperBound() const override { PYBIND11_OVERLOAD_NAME(Position, WrappedPositionalBase, "upper_bound", upperBound); } virtual Position upperBound() const override { PYBIND11_OVERLOAD_NAME(Position, WrappedPositionalBase, "upper_bound", upperBound); }
Position wrapLowerBound() const override { PYBIND11_OVERLOAD_NAME(Position, WrappedPositionalBase, "wrap_lower_bound", wrapLowerBound); } virtual Position wrapLowerBound() const override { PYBIND11_OVERLOAD_NAME(Position, WrappedPositionalBase, "wrap_lower_bound", wrapLowerBound); }
Position wrapUpperBound() const override { PYBIND11_OVERLOAD_NAME(Position, WrappedPositionalBase, "wrap_upper_bound", wrapUpperBound); } virtual Position wrapUpperBound() const override { PYBIND11_OVERLOAD_NAME(Position, WrappedPositionalBase, "wrap_upper_bound", wrapUpperBound); }
virtual bool truncate(Position &pos, bool throw_on_truncation = false) const override { PYBIND11_OVERLOAD(bool, WrappedPositionalBase, truncate, pos, throw_on_truncation); }
}; };
void bind_wrapped_positional(py::module &m, PBClass &posal); void bind_wrapped_positional(py::module &m);
}} }}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment