Commit cf2fef5f authored by Jason Rhinelander's avatar Jason Rhinelander

Properly reference python member instances

Simulation takes a reference to them during add(), and the
Member.removed() overload releases it (immediately before calling any
overloaded removed() methods).

This also adds the virtual added/removed/weak_dep_removed methods to
all of the Member subclasses, since each one needs its own trampoline
class (to be able to intercept the removed() call).

Also added a _glue method, which is the complement to others--it
exposes the internal optimizer glue members that others() hides.
parent 5f091356
......@@ -10,5 +10,5 @@ using namespace eris;
using namespace pybind11::literals;
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>);
PYBIND11_DECLARE_HOLDER_TYPE(T, eris::SharedMember<T>);
PYBIND11_DECLARE_HOLDER_TYPE(T, SharedMember<T>);
......@@ -17,14 +17,6 @@ PYBIND11_PLUGIN(core) {
core::bind_members(m);
// Expose the individual inter/intra-optimizer interfaces. pybind11 doesn't handle C++ multiple
// inheritance, but that's okay: we can do the multiple inheritance on the python side
// (inheriting from both Member and one or more of these).
//py::class<interopt::Begin, std::shared_ptr<Inter
return m.ptr();
}
......
......@@ -2,9 +2,9 @@
namespace pyeris { namespace core {
void bind_firm(py::module &m, py::class_<Agent, std::shared_ptr<Agent>> &agent) {
void bind_firm(py::module &m) {
py::class_<Firm, std::shared_ptr<Firm>, PyFirm> firm(m, "Firm", agent);
py::class_<Firm, SharedMember<Firm>, PyFirm> firm(m, "Firm", py::base<Agent>());
py::class_<Firm::Reservation> reservation(firm, "Reservation");
reservation
......@@ -31,12 +31,14 @@ void bind_firm(py::module &m, py::class_<Agent, std::shared_ptr<Agent>> &agent)
.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_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("_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, std::shared_ptr<FirmNoProd>, PyFirmNoProd> firm_noprod(m, "FirmNoProd", firm);
py::class_<FirmNoProd, SharedMember<FirmNoProd>, PyFirmNoProd> firm_noprod(m, "FirmNoProd", firm);
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("_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.")
......
#pragma once
#include "pyeris/common.hpp"
#include "pyeris/core/members.hpp"
#include <eris/Firm.hpp>
using namespace eris::agent;
......@@ -27,6 +28,8 @@ public:
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_MEMBER_METHODS(Firm)
// 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)); }
......@@ -37,12 +40,12 @@ public:
};
// Trampoline class for (abstract) FirmNoProd
class PyFirmNoProd : public PyFirm {
class PyFirmNoProd : public FirmNoProd, public PyFirm {
public:
void produceNext(const Bundle &b) { PYBIND11_OVERLOAD_PURE_NAME(void, Firm, "_produce_next", produceNext, b); }
};
void bind_firm(py::module &m, py::class_<Agent, std::shared_ptr<Agent>> &agent);
void bind_firm(py::module &m);
}}
......@@ -2,9 +2,9 @@
namespace pyeris { namespace core {
void bind_market(py::module &m, py::class_<Member, std::shared_ptr<Member>, PyMember> &member) {
void bind_market(py::module &m) {
py::class_<Market, std::shared_ptr<Market>, PyMarket> market(m, "Market", member);
py::class_<Market, SharedMember<Market>, PyMarket> market(m, "Market", py::base<Member>());
py::class_<Market::price_info> market_price(market, "PriceInfo");
market_price
......@@ -61,6 +61,8 @@ void bind_market(py::module &m, py::class_<Member, std::shared_ptr<Member>, PyMe
.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("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");
......
......@@ -7,7 +7,7 @@
namespace pyeris { namespace core {
// Trampoline class for (abstract) Market
class PyMarket : public eris::Market {
class PyMarket : public Market {
public:
// Inherit constructors
using Market::Market;
......@@ -24,10 +24,12 @@ public:
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_MEMBER_METHODS(Market)
// Not virtual, but needs to be exposed publically
Reservation _create_reservation(const SharedMember<Agent> &agent, double q, double p) { return createReservation(agent, q, p); }
};
void bind_market(py::module &m, py::class_<Member, std::shared_ptr<Member>, PyMember> &member);
void bind_market(py::module &m);
}}
#include "pyeris/core/Simulation.hpp"
#include "pyeris/core/optimizer-glue.hpp"
#include <pybind11/stl.h>
#include <pybind11/functional.h>
namespace pyeris { namespace core {
......@@ -21,7 +22,10 @@ void bind_simulation(py::module &m) {
.def("has_hood", &Simulation::hasGood, "Returns whether the simulation has a good with the given id")
.def("has_market", &Simulation::hasMarket, "Returns whether the simulation has a market with the given id")
.def("has_other", &Simulation::hasOther, "Returns whether the simulation has a non-agent/good/market member with the given id")
.def("add", &Simulation::add, "Adds a member instance to the simulation. The member is returned, to encourage usage such as: `member = sim.add(MemberObject(...))`")
.def("add", [](Simulation &s, SharedMember<Member> member) {
py::cast(member).inc_ref();
return s.add(member);
}, "Adds a member instance to the simulation. The member is returned, to encourage usage such as: `member = sim.add(MemberObject(...))`")
// Remove can work either by id or on a Member; in eris, Member is implicitly convertible to
// an id, but pybind11 doesn't support that implicit conversion (see pybind11 issue #259),
// so we defined both for the python interface:
......@@ -33,24 +37,29 @@ void bind_simulation(py::module &m) {
// we only support filtering by lambda (the lambda can, of course, do its own subtype
// filtering).
.def("agents", [](const Simulation &s) { return s.agents(); }, "Returns a list of all simulation agents.")
.def("agents", &Simulation::agents<>, "Returns a list of simulation agents filtered by the supplied lambda.")
.def("agents", &Simulation::agents<>, "Returns a list of simulation agents filtered by the supplied function/lambda.")
.def("goods", [](const Simulation &s) { return s.goods(); }, "Returns a list of all simulation goods.")
.def("goods", &Simulation::goods<>, "Returns a list of simulation goods filtered by the supplied lambda.")
.def("goods", &Simulation::goods<>, "Returns a list of simulation goods filtered by the supplied function/lambda.")
.def("markets", [](const Simulation &s) { return s.markets(); }, "Returns a list of all simulation markets.")
.def("markets", &Simulation::markets<>, "Returns a list of simulation markets filtered by the supplied lambda.")
.def("markets", &Simulation::markets<>, "Returns a list of simulation markets filtered by the supplied function/lambda.")
// others is a bit trickier: we want to exclude optimizer glue instances, since they are
// supposed to be hidden from the python interface (they are basically just implementation
// details of other members).
.def("others", [](const Simulation &sim) {
return sim.others<Member>([](const Member &other) -> bool {
return not dynamic_cast<const OptimizerGlue*>(&other);
return sim.others<Member>([](SharedMember<Member> other) -> bool {
return not dynamic_cast<const OptimizerGlue*>(other.get());
});
}, "Returns a list of all non-agent/good/market members in the simulation.")
.def("others", [](const Simulation &sim, std::function<bool(const Member&)> filter) {
return sim.others<Member>([&filter](const Member &other) -> bool {
return not dynamic_cast<const OptimizerGlue*>(&other) and filter(other);
.def("others", [](const Simulation &sim, std::function<bool(SharedMember<Member>)> filter) {
return sim.others<Member>([&filter](SharedMember<Member> other) -> bool {
return not dynamic_cast<const OptimizerGlue*>(other.get()) and filter(other);
});
}, "Returns a list of non-agent/good/market members in the simulation filtered by the supplied lambda.")
.def("_glue", [](const Simulation &sim) {
return sim.others<Member>(
[](SharedMember<Member> other) -> bool { return (bool) dynamic_cast<const OptimizerGlue*>(other.get()); }
);
}, "Debugging use only: returns a list of internal optimizer glue classes. These internal classes are created automatically to bridge Python classes with optimizer methods to internal C++ Optimizer classes and can safely be ignored from Python code.")
.def("register_dependency", &Simulation::registerDependency, "Records a dependency between simulation members; if the depended-upon member is removed, the depending member will also be removed")
.def("register_weak_dependency", &Simulation::registerWeakDependency, "Records a weak dependency between members: unlike registerDependency, if the depended-upon member is removed, the depending member is notified rather than being removed")
......
#include "pyeris/core/members.hpp"
#include "pyeris/core/optimizer-glue.hpp"
#include "pyeris/core/Market.hpp"
#include "pyeris/core/Firm.hpp"
#include <eris/Good.hpp>
......@@ -7,21 +6,8 @@
namespace pyeris { namespace core {
void PyMember::added() {
OptimizerGlue::applyGlue(this->simulation(), this->sharedSelf(), py::cast(this));
PYBIND11_OVERLOAD(void, Member, added);
}
void PyMember::removed() {
PYBIND11_OVERLOAD(void, Member, removed);
}
void PyMember::weakDepRemoved(eris::SharedMember<eris::Member> removed, eris_id_t old_id) {
PYBIND11_OVERLOAD(void, Member, weakDepRemoved, removed, old_id);
}
void bind_members(py::module &m) {
py::class_<Member, std::shared_ptr<Member>, PyMember> member(m, "Member");
py::class_<Member, SharedMember<Member>, PyMember> member(m, "Member");
member
.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.")
......@@ -31,25 +17,25 @@ void bind_members(py::module &m) {
// 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_weakly_on", &Member::dependsWeaklyOn, "Records a weak dependency with the member's simulation; typically invoked in the _added() method.")
.def("added", &PyMember::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.")
.def("removed", &PyMember::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.")
.def("weak_dep_removed", &PyMember::weakDepRemoved, "Called when a weak dependency of this object is removed from the simulation.")
// Virtual methods (also protected, which is why we reference PyMember):
DEF_MEMBER_COMMON_VIRTUALS(PyMember)
;
py::implicitly_convertible<Member, eris_id_t>();
py::class_<Agent, std::shared_ptr<Agent>> agent(m, "Agent", member);
py::class_<Agent, SharedMember<Agent>, PyAgent> agent(m, "Agent", member);
agent
.def(py::init<>())
.def_readwrite("assets", &Agent::assets, "The agent's bundle of assets")
;
py::class_<Good, std::shared_ptr<Good>> good(m, "Good", member);
py::class_<Good, SharedMember<Good>, PyGood> good(m, "Good", member);
good.def(py::init<std::string>(), py::arg("name") = "");
bind_firm(m, agent);
bind_firm(m);
bind_market(m, member);
bind_market(m);
}
}}
#pragma once
#include "pyeris/common.hpp"
#include "pyeris/core/optimizer-glue.hpp"
#include <pybind11/functional.h>
#include <eris/Member.hpp>
#include <eris/Agent.hpp>
#include <eris/Good.hpp>
// Basic member base classes support (Member, Agent, Good; Market is in Market.hpp)
//
......@@ -12,21 +15,36 @@
namespace pyeris { namespace core {
class PyMember : public Member {
public:
// Default constructor
PyMember() = default;
// The python interface uses duck typing for period optimizers, which means we need to look for
// them here, and if found, set up dependent optimizer glue instances.
virtual void added() override;
// Expose removed()
virtual void removed() override;
// Expose weakDepRemoved()
virtual void weakDepRemoved(eris::SharedMember<eris::Member> removed, eris_id_t old_id) override;
};
#define VIRTUAL_MEMBER_METHODS(Class) \
virtual void added() override { \
OptimizerGlue::applyGlue(this->simulation(), this->sharedSelf(), py::cast(this)); \
PYBIND11_OVERLOAD(void, Class, added); \
} \
virtual void removed() override { \
/* Simulation takes a reference during add(), we release it during member.removed() */ \
py::cast(this).dec_ref(); \
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); \
}
#define DEF_MEMBER_COMMON_VIRTUALS(TrampolineClass) \
.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.") \
.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.") \
.def("weak_dep_removed", &TrampolineClass::weakDepRemoved, "Called when a weak dependency of this object is removed from the simulation.")
#define SIMPLE_MEMBER_TRAMPOLINE(Class) \
class Py##Class : public Class { \
public: \
using Class::Class; \
VIRTUAL_MEMBER_METHODS(Class) \
}
SIMPLE_MEMBER_TRAMPOLINE(Member);
SIMPLE_MEMBER_TRAMPOLINE(Agent);
SIMPLE_MEMBER_TRAMPOLINE(Good);
void bind_members(py::module &m);
......
#pragma once
#include "pyeris/common.hpp"
#include <eris/Member.hpp>
#include <eris/Optimize.hpp>
......
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