diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 144cb5fb94..78fd3d6f7b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -25,6 +25,7 @@ Since last release * Add random number generator (Mersenne Twister 19937, from boost) and the ability to set the seed in the simulation control block (#1599, #1639) * Allow randomness in request frequency and size through buy policy (#1634) * Adds support for Cython3 (#1636) +* Adds TotalInvTracker, which allows an inventory cap to be set for multiple resource buffers, and is now required for material buy policy (#1646) **Changed:** diff --git a/cli/cycpp.py b/cli/cycpp.py index 5e72815a7d..097f53837c 100755 --- a/cli/cycpp.py +++ b/cli/cycpp.py @@ -102,11 +102,14 @@ ('{0}::toolkit::ResBuf'.format(CYCNS), CYCNS + '::Product'), ('{0}::toolkit::ResBuf'.format(CYCNS), CYCNS + '::Material'), '{0}::toolkit::ResMap'.format(CYCNS), + '{0}::toolkit::TotalInvTracker'.format(CYCNS), + ('{0}::toolkit::TotalInvTracker'.format(CYCNS), '{0}::toolkit::ResBuf'.format(CYCNS), CYCNS + '::Material'), } TEMPLATES = {'std::vector', 'std::set', 'std::list', 'std::pair', 'std::map', '{0}::toolkit::ResBuf'.format(CYCNS), - CYCNS + '::toolkit::ResMap',} + CYCNS + '::toolkit::ResMap', + CYCNS + '::toolkit::TotalInvTracker',} WRANGLERS = { '{0}::Agent'.format(CYCNS), @@ -1079,6 +1082,7 @@ def impl(self, ind=" "): "m->{var}.capacity());\n"), CYCNS + '::toolkit::ResBuf': "{var}.capacity(m->{var}.capacity());\n", CYCNS + '::toolkit::ResMap': '{var}.obj_ids(m->obj_ids());\n', + CYCNS + '::toolkit::TotalInvTracker': "{var}.capacity();\n", } class InitFromDbFilter(CodeGeneratorFilter): @@ -1145,6 +1149,7 @@ def impl(self, ind=" "): CYCNS + '::toolkit::ResBuf': '{var}.capacity({capacity});\n', CYCNS + '::toolkit::ResMap': ( '{var}.obj_ids(qr.GetVal<{tstr}>("{var}"))\n;'), + CYCNS + '::toolkit::TotalInvTracker': '{var}.capacity();\n', } @@ -1893,6 +1898,7 @@ def impl(self, ind=" "): CYCNS + '::toolkit::ResourceBuff': None, CYCNS + '::toolkit::ResBuf': None, CYCNS + '::toolkit::ResMap': '{var}.obj_ids()', + CYCNS + '::toolkit::TotalInvTracker': None, } @@ -1947,6 +1953,7 @@ def impl(self, ind=" "): 'invs[\"{var}\"] = {var}.PopNRes({var}.count());\n' '{var}.Push(invs["{var}"]);\n'), CYCNS + '::toolkit::ResMap': "invs[\"{var}\"] = {var}.ResValues();\n", + CYCNS + '::toolkit::TotalInvTracker': ';\n', } @@ -1995,6 +2002,7 @@ def impl(self, ind=" "): CYCNS + '::toolkit::ResourceBuff': "{var}.PushAll(inv[\"{var}\"]);\n", CYCNS + '::toolkit::ResBuf': "{var}.Push(inv[\"{var}\"]);\n", CYCNS + '::toolkit::ResMap': "{var}.ResValues(inv[\"{var}\"]);\n", + CYCNS + '::toolkit::TotalInvTracker': ";\n", } diff --git a/cyclus/gentypesystem.py b/cyclus/gentypesystem.py index 5bc60fcd21..366a465987 100644 --- a/cyclus/gentypesystem.py +++ b/cyclus/gentypesystem.py @@ -377,6 +377,7 @@ def convert_to_cpp(self, x, t): 'std::list': 'std_list', 'std::vector': 'std_vector', 'cyclus::toolkit::ResBuf': 'cpp_cyclus.ResBuf', + 'cyclus::toolkit::TotalInvTracker': 'cpp_cyclus.TotalInvTracker', 'cyclus::toolkit::ResMap': 'cpp_cyclus.ResMap', } @@ -384,7 +385,7 @@ def convert_to_cpp(self, x, t): RESOURCES = ['MATERIAL', 'PRODUCT'] INVENTORIES = ['cyclus::toolkit::ResourceBuff', 'cyclus::toolkit::ResBuf', - 'cyclus::toolkit::ResMap'] + 'cyclus::toolkit::TotalInvTracker', 'cyclus::toolkit::ResMap'] USE_SHARED_PTR = ('MATERIAL', 'PRODUCT', 'cyclus::Material', 'cyclus::Product') @@ -419,6 +420,7 @@ def convert_to_cpp(self, x, t): 'std::list': 'std_list', 'std::vector': 'std_vector', 'cyclus::toolkit::ResBuf': 'res_buf', + 'cyclus::toolkit::TotalInvTracker': 'total_inv_tracker', 'cyclus::toolkit::ResMap': 'res_map', } @@ -453,6 +455,7 @@ def convert_to_cpp(self, x, t): 'std::list': 'List', 'std::vector': 'Vector', 'cyclus::toolkit::ResBuf': 'ResBuf', + 'cyclus::toolkit::TotalInvTracker': 'total_inv_tracker', 'cyclus::toolkit::ResMap': 'ResMap', } @@ -489,6 +492,7 @@ def convert_to_cpp(self, x, t): 'std::list': ('val',), 'std::vector': ('val',), 'cyclus::toolkit::ResBuf': ('val',), + 'cyclus::toolkit::TotalInvTracker': ('val',), 'cyclus::toolkit::ResMap': ('key', 'val'), } @@ -509,6 +513,7 @@ def convert_to_cpp(self, x, t): 'std::list': 'np.NPY_OBJECT', 'std::vector': 'np.NPY_OBJECT', 'cyclus::toolkit::ResBuf': 'np.NPY_OBJECT', + 'cyclus::toolkit::TotalInvTracker': 'np.NPY_OBJECT', 'cyclus::toolkit::ResMap': 'np.NPY_OBJECT', } @@ -529,6 +534,7 @@ def convert_to_cpp(self, x, t): 'std::list': '[]', 'std::vector': '[]', 'cyclus::toolkit::ResBuf': 'None', + 'cyclus::toolkit::TotalInvTracker': 'None', 'cyclus::toolkit::ResMap': 'None', } @@ -646,6 +652,7 @@ def convert_to_cpp(self, x, t): ' py{var}[i] = {var}_i\n', 'py{var}'), 'cyclus::toolkit::ResBuf': ('', '', 'None'), + 'cyclus::toolkit::TotalInvTracker': ('', '', 'None'), 'cyclus::toolkit::ResMap': ('', '', 'None'), } @@ -758,6 +765,12 @@ def convert_to_cpp(self, x, t): 'py{var} = <_{classname}> {var}\n' 'cpp{var} = deref(py{var}.ptx)\n', 'cpp{var}'), + 'cyclus::toolkit::TotalInvTracker': ( + 'cdef _{classname} py{var}\n' + 'cdef cpp_cyclus.TotalInvTracker[{valtype}] cpp{var}\n', + 'py{var} = <_{classname}> {var}\n' + 'cpp{var} = deref(py{var}.ptx)\n', + 'cpp{var}'), 'cyclus::toolkit::ResMap': ( 'cdef _{classname} py{var}\n' 'cdef cpp_cyclus.ResMap[{keytype}, {valtype}] cpp{var}\n', diff --git a/src/toolkit/matl_buy_policy.cc b/src/toolkit/matl_buy_policy.cc index 7a70a62146..a00a6e0658 100644 --- a/src/toolkit/matl_buy_policy.cc +++ b/src/toolkit/matl_buy_policy.cc @@ -18,8 +18,10 @@ MatlBuyPolicy::MatlBuyPolicy() : name_(""), throughput_(std::numeric_limits::max()), quantize_(-1), - fill_to_(1), - req_when_under_(1), + fill_to_(std::numeric_limits::max()), + req_at_(std::numeric_limits::max()), + cumulative_cap_(-1), + cycle_total_inv_(0), active_dist_(NULL), dormant_dist_(NULL), size_dist_(NULL){ @@ -43,18 +45,50 @@ void MatlBuyPolicy::set_manager(Agent* m) { } } +void MatlBuyPolicy::set_total_inv_tracker(TotalInvTracker* t) { + if (t == NULL){ + std::vector*> bufs = {buf_}; + buf_tracker_->Init(bufs, buf_->capacity()); + } + else if (!t->buf_in_tracker(buf_)) { + std::stringstream ss; + ss << "TotalInvTracker does not contain ResBuf used in buy policy"; + throw ValueError(ss.str()); + } + else { + buf_tracker_ = t; + } +} + +void MatlBuyPolicy::set_inv_policy(std::string inv_policy, double fill, double req_at) { + set_req_at(req_at); + std::transform(inv_policy.begin(), inv_policy.end(), inv_policy.begin(), ::tolower); + if ((inv_policy == "ss")) { + set_fill_to(fill); + } + else if ((inv_policy == "rq") || (inv_policy == "qr")) { + set_quantize(fill); + // maximum amount that an RQ policy could achieve is req_at + fill + set_fill_to(req_at + fill); + } + else { + throw ValueError("Invalid inventory policy"); + } +} + void MatlBuyPolicy::set_fill_to(double x) { - if (x > 1) - x /= buf_->capacity(); - assert(x > 0 && x <= 1.); - fill_to_ = x; + assert(x > 0); + fill_to_ = std::min(x, buf_->capacity()); } -void MatlBuyPolicy::set_req_when_under(double x) { - if (x > 1) - x /= buf_->capacity(); - assert(x > 0 && x <= 1.); - req_when_under_ = x; +void MatlBuyPolicy::set_req_at(double x) { + assert(x >= 0); + req_at_ = std::min(x, buf_->capacity()); +} + +void MatlBuyPolicy::set_cumulative_cap(double x) { + assert(x > 0); + cumulative_cap_ = std::min(x, buf_->capacity()); } void MatlBuyPolicy::set_quantize(double x) { @@ -88,7 +122,11 @@ void MatlBuyPolicy::init_active_dormant() { if (dormant_dist_->sample() < 0) { next_dormant_end_ = -1; LGH(INFO4) << "dormant length -1, always active" << std::endl; - } + } + else if (use_cumulative_capacity()) { + next_dormant_end_ = -1; + LGH(INFO4) << "dormant length set at -1 for first active period of cumulative capacity cycle" << std::endl; + } else { SetNextDormantTime(); LGH(INFO4) << "first dormant time end: " << next_dormant_end_ << std::endl; @@ -96,21 +134,24 @@ void MatlBuyPolicy::init_active_dormant() { } MatlBuyPolicy& MatlBuyPolicy::Init(Agent* manager, ResBuf* buf, - std::string name) { + std::string name, TotalInvTracker* buf_tracker) { set_manager(manager); buf_ = buf; name_ = name; + set_total_inv_tracker(buf_tracker); init_active_dormant(); return *this; } MatlBuyPolicy& MatlBuyPolicy::Init(Agent* manager, ResBuf* buf, - std::string name, double throughput, boost::shared_ptr active_dist, + std::string name, TotalInvTracker* buf_tracker, + double throughput, boost::shared_ptr active_dist, boost::shared_ptr dormant_dist, boost::shared_ptr size_dist) { set_manager(manager); buf_ = buf; name_ = name; + set_total_inv_tracker(buf_tracker); set_throughput(throughput); active_dist_ = active_dist; dormant_dist_ = dormant_dist; @@ -120,32 +161,64 @@ MatlBuyPolicy& MatlBuyPolicy::Init(Agent* manager, ResBuf* buf, } MatlBuyPolicy& MatlBuyPolicy::Init(Agent* manager, ResBuf* buf, - std::string name, - double fill_to, double req_when_under) { + std::string name, TotalInvTracker* buf_tracker, + double throughput, double quantize) { set_manager(manager); buf_ = buf; name_ = name; - set_fill_to(fill_to); - set_req_when_under(req_when_under); + set_total_inv_tracker(buf_tracker); + set_throughput(throughput); + set_quantize(quantize); init_active_dormant(); return *this; } MatlBuyPolicy& MatlBuyPolicy::Init(Agent* manager, ResBuf* buf, - std::string name, double throughput, - double fill_to, double req_when_under, - double quantize) { + std::string name, + TotalInvTracker* buf_tracker, + double throughput, + std::string inv_policy, + double fill_behav, double req_at) { set_manager(manager); buf_ = buf; name_ = name; - set_fill_to(fill_to); - set_req_when_under(req_when_under); - set_quantize(quantize); + set_total_inv_tracker(buf_tracker); + set_inv_policy(inv_policy, fill_behav, req_at); set_throughput(throughput); init_active_dormant(); return *this; } +MatlBuyPolicy& MatlBuyPolicy::Init(Agent* manager, ResBuf* buf, + std::string name, + TotalInvTracker* buf_tracker, + std::string inv_policy, + double fill_behav, double req_at) { + set_manager(manager); + buf_ = buf; + name_ = name; + set_total_inv_tracker(buf_tracker); + set_inv_policy(inv_policy, fill_behav, req_at); + init_active_dormant(); + return *this; +} + +MatlBuyPolicy& MatlBuyPolicy::Init(Agent* manager, ResBuf* buf, + std::string name, + TotalInvTracker* buf_tracker, + double throughput, double cumulative_cap, + boost::shared_ptr dormant_dist) { + set_manager(manager); + buf_ = buf; + name_ = name; + set_total_inv_tracker(buf_tracker); + set_throughput(throughput); + set_cumulative_cap(cumulative_cap); + dormant_dist_ = dormant_dist; + init_active_dormant(); + return *this; +} + MatlBuyPolicy& MatlBuyPolicy::Set(std::string commod) { CompMap c; c[10010000] = 1e-100; @@ -186,31 +259,21 @@ void MatlBuyPolicy::Stop() { std::set::Ptr> MatlBuyPolicy::GetMatlRequests() { rsrc_commods_.clear(); std::set::Ptr> ports; - bool make_req = buf_->quantity() < req_when_under_ * buf_->capacity(); - double amt; + + double amt = 0; int current_time_ = manager()->context()->time(); - if (never_dormant() || current_time_ < next_active_end_) { - // currently in the middle of active buying period - amt = TotalAvailable() * SampleRequestSize(); - } - else if (current_time_ < next_dormant_end_) { - // finished active. starting dormancy and sample/set length of dormant period - amt = 0; - LGH(INFO3) << "in dormant period, no request" << std::endl; - } - // the following is an if rather than if-else statement because if dormant - // length is zero, buy policy should return to active immediately - if (current_time_ == next_dormant_end_) { - // finished dormant. starting buying and sample/set length of active period - amt = TotalAvailable() * SampleRequestSize(); - SetNextActiveTime(); - SetNextDormantTime(); - LGH(INFO4) << "end of dormant period, next active time end: " << next_active_end_ << ", and next dormant time end: " << next_dormant_end_ << std::endl; + double max_request_amt = use_cumulative_capacity() ? cumulative_cap_ - cycle_total_inv_ : std::numeric_limits::max(); + + if (MakeReq() && !dormant(current_time_)) { + amt = std::min(TotalAvailable() * SampleRequestSize(), max_request_amt); } + else { LGH(INFO3) << "in dormant period, no request" << std::endl; } + + CheckActiveDormantCumulativeTimes(); - if (!make_req || amt < eps()) + if (amt < eps()) return ports; bool excl = Excl(); @@ -247,6 +310,18 @@ void MatlBuyPolicy::AcceptMatlTrades( LGH(INFO3) << "got " << it->second->quantity() << " kg of " << it->first.request->commodity() << std::endl; buf_->Push(it->second); + // cumulative capacity handling + if (use_cumulative_capacity()) { + cycle_total_inv_ += it->second->quantity(); + } + } + // check if cumulative cap has been reached. If yes, then sample for dormant + // length and reset cycle_total_inv + if (use_cumulative_capacity() && ( + (cumulative_cap_ - cycle_total_inv_) < eps())) { + SetNextDormantTime(); + LGH(INFO3) << "cycle cumulative inventory has been reached. Dormant period will end at " << next_dormant_end_ << std::endl; + cycle_total_inv_ = 0; } } @@ -256,9 +331,14 @@ void MatlBuyPolicy::SetNextActiveTime() { }; void MatlBuyPolicy::SetNextDormantTime() { - if (next_dormant_end_ < 0) {} - else { - next_dormant_end_ = dormant_dist_->sample() + next_active_end_; + if (use_cumulative_capacity()) { + // need the +1 when not using next_active_end_ + next_dormant_end_ = ( + dormant_dist_->sample() + manager()->context()->time() + 1); + } + else if (next_dormant_end_ >= 0) { + next_dormant_end_ = dormant_dist_->sample() + + std::max(next_active_end_, 1); } return; } @@ -267,6 +347,18 @@ double MatlBuyPolicy::SampleRequestSize() { return size_dist_->sample(); } +void MatlBuyPolicy::CheckActiveDormantCumulativeTimes() { + if (manager()->context()->time() == next_dormant_end_) { + if (use_cumulative_capacity()) { next_dormant_end_ = -1; } + else { + SetNextActiveTime(); + + SetNextDormantTime(); + LGH(INFO4) << "end of dormant period, next active time end: " << next_active_end_ << ", and next dormant time end: " << next_dormant_end_ << std::endl; + } + } + return; +} } // namespace toolkit } // namespace cyclus diff --git a/src/toolkit/matl_buy_policy.h b/src/toolkit/matl_buy_policy.h index 7325e1ddee..79516c7a81 100644 --- a/src/toolkit/matl_buy_policy.h +++ b/src/toolkit/matl_buy_policy.h @@ -7,6 +7,7 @@ #include "composition.h" #include "material.h" #include "res_buf.h" +#include "total_inv_tracker.h" #include "trader.h" #include "random_number_generator.h" @@ -70,11 +71,26 @@ class MatlBuyPolicy : public Trader { /// @param name a unique name identifying this policy /// @param throughput a constraining value for total transaction quantities in /// a single time step - /// @param fill_to the amount or fraction of inventory to order when placing - /// an order. This is equivalent to the S in an (s, S) inventory policy. - /// @param req_when_under place an request when the buf's quantity is less - /// than its capacity * fill_to (as a fraction). This is equivalent to the s - /// in an (s, S) inventory policy. + /// @param inv_policy the inventory policy to use. Options are "sS", "RQ". + /// Each inventory policy options has two additional required parameters. An + /// (s,S) inventory policy orders material only when the buffer is below the + /// minimum value, s, and orders only enough to bring the buffer to the + /// maximum value, S. An (s,S) policy allows partial orders. Set s as req_at + /// and S as fill. An (R,Q) inventory policy requests material when the + /// buffer is below the minimum value, R, and places an exclusive request for + /// size Q. Set R as req_at and Q as fill. + /// @param req_at, the inventory minimum (s, and R) for (s,S) and (R,Q),. If + /// the buffer has less than or equal to value, new material will be + /// requested based on the policy in place. + /// @param fill_behav, the quantity govering the fill strategy for inventory + /// policies. For (s,S), this is the maximum value, and material will be + /// ordered up to this amount. For (R,Q), this is the quantity of material + /// that will be ordered (exclusive). + /// @param cumulative_cap the cumulative capacity of material to be received + /// in one active cycle. A cumulative cap inventory policy allows for a + /// cumulative capacity of material to be received in one active cycle. Once + /// the cumulative capacity is recieved, the agent enters a dormant period. + /// Also requires dormant distributions using dormant_dist. /// @param quantize If quantize is greater than zero, the policy will make /// exclusive, integral quantize kg requests. Otherwise, single requests will /// be sent to fill the buffer's empty space. @@ -102,17 +118,27 @@ class MatlBuyPolicy : public Trader { /// Note that active and dormant periods are note currently compatible with /// (s, S) inventory management /// @{ - MatlBuyPolicy& Init(Agent* manager, ResBuf* buf, std::string name); MatlBuyPolicy& Init(Agent* manager, ResBuf* buf, std::string name, - double throughput, + TotalInvTracker* buf_tracker); + MatlBuyPolicy& Init(Agent* manager, ResBuf* buf, std::string name, + TotalInvTracker* buf_tracker, double throughput, boost::shared_ptr active_dist = NULL, boost::shared_ptr dormant_dist = NULL, boost::shared_ptr size_dist = NULL); MatlBuyPolicy& Init(Agent* manager, ResBuf* buf, std::string name, - double fill_to, double req_when_under); + TotalInvTracker* buf_tracker, double throughput, + double quantize); + MatlBuyPolicy& Init(Agent* manager, ResBuf* buf, std::string name, + TotalInvTracker* buf_tracker, + double throughput, std::string inv_policy, + double fill_behav, double req_at); + MatlBuyPolicy& Init(Agent* manager, ResBuf* buf, std::string name, + TotalInvTracker* buf_tracker, std::string inv_policy, + double fill_behav, double req_at); MatlBuyPolicy& Init(Agent* manager, ResBuf* buf, std::string name, - double throughput, double fill_to, - double req_when_under, double quantize); + TotalInvTracker* buf_tracker, double throughput, + double cumulative_cap, + boost::shared_ptr dormant_dist); /// @} /// Instructs the policy to fill its buffer with requests on the given @@ -144,10 +170,15 @@ class MatlBuyPolicy : public Trader { /// the total amount available to request inline double TotalAvailable() const { - return std::min(throughput_, - (fill_to_ * buf_->capacity()) - buf_->quantity()); + return std::min({throughput_, + fill_to_ - buf_->quantity(), + buf_->space(), + buf_tracker_->space()}); } + /// whether a request can be made + inline bool MakeReq() const { return buf_tracker_->quantity() <= req_at_; } + /// whether trades will be denoted as exclusive or not inline bool Excl() const { return quantize_ > 0; } @@ -169,9 +200,15 @@ class MatlBuyPolicy : public Trader { return rsrc_commods_; }; - inline bool never_dormant() { + inline bool no_cycle_end_time() { return (next_dormant_end_ < 0 || next_active_end_ < 0); - }; + } + + inline bool dormant(int time) { + return (time >= next_active_end_ && time < next_dormant_end_); + } + + inline bool use_cumulative_capacity() { return cumulative_cap_ > 0; } /// Trader Methods /// @{ @@ -183,6 +220,7 @@ class MatlBuyPolicy : public Trader { void SetNextActiveTime(); void SetNextDormantTime(); double SampleRequestSize(); + void CheckActiveDormantCumulativeTimes(); private: struct CommodDetail { @@ -191,17 +229,23 @@ class MatlBuyPolicy : public Trader { }; void set_manager(Agent* m); + void set_total_inv_tracker(TotalInvTracker* t = NULL); + void set_inv_policy(std::string p, double x, + double y = std::numeric_limits::max()); /// requires buf_ already set void set_fill_to(double x); /// requires buf_ already set - void set_req_when_under(double x); + void set_req_at(double x); + void set_cumulative_cap(double x); void set_quantize(double x); void set_throughput(double x); void init_active_dormant(); ResBuf* buf_; - std::string name_; - double fill_to_, req_when_under_, quantize_, throughput_; + TotalInvTracker* buf_tracker_; + std::string name_, inv_policy; + double fill_to_, req_at_, quantize_, throughput_, cumulative_cap_, + cycle_total_inv_; int next_active_end_= 0; int next_dormant_end_= 0; diff --git a/src/toolkit/total_inv_tracker.h b/src/toolkit/total_inv_tracker.h new file mode 100644 index 0000000000..92b1918281 --- /dev/null +++ b/src/toolkit/total_inv_tracker.h @@ -0,0 +1,149 @@ +#ifndef CYCLUS_SRC_TOOLKIT_TOTAL_INV_TRACKER_H_ +#define CYCLUS_SRC_TOOLKIT_TOTAL_INV_TRACKER_H_ + +#include "error.h" +#include "res_buf.h" + +namespace cyclus { +namespace toolkit { + +/// TotalInvTracker is a helper class that tracks the total inventory of +/// multiple ResBufs. This can be useful when a single agent uses multiple +/// buffers to manage their process, but a limit should be placed on their +/// combined inventory, such as a agent- or facility-wide maximum inventory. +/// Like ResBufs, TotalInvTracker has an infinite capacity unless explicitly +/// changed. +/// +/// TotalInvTracker does not hold any inventory itself, it only tracks the +/// quantities of other ResBufs. TotalInvTracker currently tracks quantities +/// alone, not compositions. +/// +/// @code +/// class MyAgent : public cyclus:: Facility { +/// public: +/// EnterNotify() { +/// cyclus::Facility::EnterNotify(); +/// tracker_.Init({&inventory_, &outventory_}, facility_max_inv_size_); +/// +/// cyclus::MatlBuyPolicy p; +/// p.Init(this, &inventory_, "inventory", &tracker_); +/// } +/// protected: +/// cyclus::toolkit::ResBuf inventory_; +/// cyclus::toolkit::ResBuf outventory_; +/// +/// double facility_max_inv_size_ = 1000; +/// cyclus::toolkit::TotalInvTracker tracker_; +/// } + +class TotalInvTracker { + public: + /// Creates an uninitialized tracker. The Init function MUST be called before + /// the tracker is used. + TotalInvTracker() : max_inv_size_(std::numeric_limits::max()), qty_(0) {}; + + TotalInvTracker(std::vector*> bufs, + double max_inv_size = std::numeric_limits::max()) { + Init(bufs, max_inv_size); + } + + ~TotalInvTracker() {}; + + /// Initializes the tracker with the given ResBufs. The tracker will have + /// infinite capacity unless explicitly changed. + void Init(std::vector*> bufs, + double max_inv_size = std::numeric_limits::max()) { + if (bufs.size() == 0) { + throw ValueError("TotalInvTracker must be initialized with at least one ResBuf"); + } + bufs_ = bufs; + if (max_inv_size <= 0) { + throw ValueError("TotalInvTracker must be initialized with a positive capacity"); + } + max_inv_size_ = max_inv_size; + } + + /// Returns the total quantity of all tracked ResBufs. + /// @throws ValueError if the tracker has not been initialized (zero) + inline double quantity() { + int num = num_bufs(); + qty_ = 0; + for (int i = 0; i < num; i++) { + qty_ += bufs_[i]->quantity(); + } + return qty_; + } + + /// Returns the total capacity that could go in the ResBufs. Either the + /// capacity of the TotalInvTracker, or the sum of each ResBuf's capacity, + /// whichever is lower. + inline double capacity() { + return std::min(total_capacity_bufs(), max_inv_size_); + } + + // Returns the sum of the capacities of all buffers. Does not include the + // capacity of the tracker + inline double total_capacity_bufs() { + int num = num_bufs(); + double cap = 0; + for (int i = 0; i < num; i++) { + cap += bufs_[i]->capacity(); + } + return cap; + } + + /// Returns the total capacity of the traker. Does not include ResBufs + inline double tracker_capacity() { return max_inv_size_; } + + /// Returns the remaining facility-wide space across all tracked ResBufs. + inline double space() { return std::max(0.0, capacity() - quantity()); } + + /// Returns the remaining space in the given ResBuf, considering the + /// facility-wide limitations. + inline double constrained_buf_space(ResBuf* buf) { + return std::min(buf->space(), space()); + } + + /// Returns true if there are no resources in any buffer + inline bool empty() { return quantity() == 0; } + + /// Returns number of buffers being tracked + /// @throws ValueError if the tracker has not been initialized (zero) + inline int num_bufs() { + int num = bufs_.size(); + if (num == 0) { + throw ValueError("TotalInvTracker has not been initialized, no buffers to track"); + } + return num; + } + + /// Change the total capacity across all ResBufs. The new capacity must be + /// greater than the current quantity. + /// @throws ValueError if the new capacity is less than the current quantity + void set_capacity(double cap) { + if (quantity() - cap > eps_rsrc()) { + std::stringstream ss; + ss << std::setprecision(17) << "new capacity " << cap << + " lower than existing quantity " << quantity(); + throw ValueError(ss.str()); + } + max_inv_size_ = cap; + } + + bool buf_in_tracker(ResBuf* buf) { + for (int i = 0; i < num_bufs(); i++) { + if (bufs_[i] == buf) { return true; } + } + return false; + } + + private: + double max_inv_size_; + double qty_; + std::vector*> bufs_; +}; + +} // namespace toolkit +} // namespace cyclus + +#endif // CYCLUS_SRC_TOOLKIT_TOTAL_INV_TRACKER_H_ \ No newline at end of file diff --git a/tests/bug1209and1210_test.cc b/tests/bug1209and1210_test.cc index 8d6ff44eab..52dc4d3a08 100644 --- a/tests/bug1209and1210_test.cc +++ b/tests/bug1209and1210_test.cc @@ -22,8 +22,9 @@ class MultiTraderSink: public cyclus::Facility { void EnterNotify() { cyclus::Facility::EnterNotify(); - policy1.Init(this, &inbuf1, "inbuf1").Set("commod1").Start(); - policy2.Init(this, &inbuf2, "inbuf2").Set("commod2").Start(); + buff_tracker.Init({&inbuf1, &inbuf2}); + policy1.Init(this, &inbuf1, "inbuf1", &buff_tracker).Set("commod1").Start(); + policy2.Init(this, &inbuf2, "inbuf2", &buff_tracker).Set("commod2").Start(); } void Tick() {} @@ -33,6 +34,7 @@ class MultiTraderSink: public cyclus::Facility { cyclus::toolkit::ResBuf inbuf1; cyclus::toolkit::MatlBuyPolicy policy2; cyclus::toolkit::ResBuf inbuf2; + cyclus::toolkit::TotalInvTracker buff_tracker; }; // issue 1210 was preventing the 1209 test from passing (causing segfaults), diff --git a/tests/toolkit/matl_buy_policy_tests.cc b/tests/toolkit/matl_buy_policy_tests.cc index 70e758f13e..100104c388 100644 --- a/tests/toolkit/matl_buy_policy_tests.cc +++ b/tests/toolkit/matl_buy_policy_tests.cc @@ -45,10 +45,11 @@ TEST_F(MatlBuyPolicyTests, Init) { double cap = 5; ResBuf buff; buff.capacity(cap); + TotalInvTracker buff_tracker({&buff}); MatlBuyPolicy p; // defaults - p.Init(fac1, &buff, ""); + p.Init(fac1, &buff, "", &buff_tracker); double amt = p.TotalAvailable(); ASSERT_FLOAT_EQ(amt, cap); ASSERT_FLOAT_EQ(p.ReqQty(amt), cap); @@ -56,7 +57,7 @@ TEST_F(MatlBuyPolicyTests, Init) { // throughput double throughput = cap - 1; - p.Init(fac1, &buff, "", throughput, 1, 1, -1); + p.Init(fac1, &buff, "", &buff_tracker, throughput); amt = p.TotalAvailable(); ASSERT_FLOAT_EQ(amt, throughput); ASSERT_FLOAT_EQ(p.ReqQty(amt), throughput); @@ -64,19 +65,28 @@ TEST_F(MatlBuyPolicyTests, Init) { // exclusive orders double quantize = 2.5; - p.Init(fac1, &buff, "", std::numeric_limits::max(), 1, 1, quantize); + p.Init(fac1, &buff, "", &buff_tracker, std::numeric_limits::max(), + quantize); amt = p.TotalAvailable(); ASSERT_FLOAT_EQ(amt, cap); ASSERT_FLOAT_EQ(p.ReqQty(amt), quantize); ASSERT_EQ(p.NReq(amt), static_cast(cap / quantize)); +} + +TEST_F(MatlBuyPolicyTests, Init_sS) { + double cap = 10; + ResBuf buff; + buff.capacity(cap); + TotalInvTracker buff_tracker({&buff}); + MatlBuyPolicy p; - // S,s with nothing in buffer +// S,s with nothing in buffer double S = 4, s = 2; - // reset - p.Init(fac1, &buff, "", std::numeric_limits::max(), 1, 1, -1); + p.Init(fac1, &buff, "", &buff_tracker, std::numeric_limits::max()); // use Ss constructor - p.Init(fac1, &buff, "", S, s); - amt = p.TotalAvailable(); + p.Init(fac1, &buff, "", &buff_tracker, "sS", S, s); + ASSERT_TRUE(p.MakeReq()); + double amt = p.TotalAvailable(); ASSERT_FLOAT_EQ(amt, S); ASSERT_FLOAT_EQ(p.ReqQty(amt), S); ASSERT_EQ(p.NReq(amt), 1); @@ -84,18 +94,61 @@ TEST_F(MatlBuyPolicyTests, Init) { // S,s with something in buffer Composition::Ptr c; buff.Push(Material::CreateUntracked(s, c)); + p.Init(fac1, &buff, "", &buff_tracker, "sS", S, s); + ASSERT_TRUE(p.MakeReq()); amt = p.TotalAvailable(); ASSERT_FLOAT_EQ(amt, S - s); ASSERT_FLOAT_EQ(p.ReqQty(amt), S - s); ASSERT_EQ(p.NReq(amt), 1); + + // S,s with too much in the buffer + buff.Push(Material::CreateUntracked(s, c)); + p.Init(fac1, &buff, "", &buff_tracker, "sS", S, s); + ASSERT_FALSE(p.MakeReq()); +} + +TEST_F(MatlBuyPolicyTests, Init_RQ) { + double cap = 10; + ResBuf buff; + buff.capacity(cap); + TotalInvTracker buff_tracker({&buff}); + MatlBuyPolicy p; + +// R,Q with nothing in buffer + double R = 2, Q = 4; + // reset + p.Init(fac1, &buff, "", &buff_tracker, std::numeric_limits::max()); + // use RQ constructor + p.Init(fac1, &buff, "", &buff_tracker, "RQ", Q, R); + ASSERT_TRUE(p.MakeReq()); + double amt = p.TotalAvailable(); + ASSERT_FLOAT_EQ(amt, 6); + ASSERT_FLOAT_EQ(p.ReqQty(amt), Q); + ASSERT_EQ(p.NReq(amt), 1); + + // R,Q with something in the buffer + Composition::Ptr c; + buff.Push(Material::CreateUntracked(R, c)); + p.Init(fac1, &buff, "", &buff_tracker, "RQ", Q, R); + ASSERT_TRUE(p.MakeReq()); + amt = p.TotalAvailable(); + ASSERT_FLOAT_EQ(amt, 4); + ASSERT_FLOAT_EQ(p.ReqQty(amt), Q); + ASSERT_EQ(p.NReq(amt), 1); + + // R,Q with too much in the buffer + buff.Push(Material::CreateUntracked(R, c)); + p.Init(fac1, &buff, "", &buff_tracker, "RQ", Q, R); + ASSERT_FALSE(p.MakeReq()); } TEST_F(MatlBuyPolicyTests, StartStop) { double cap = 5; ResBuf buff; buff.capacity(cap); + TotalInvTracker buff_tracker({&buff}); MatlBuyPolicy p; - ASSERT_THROW(p.Init(NULL, &buff, ""), ValueError); + ASSERT_THROW(p.Init(NULL, &buff, "", &buff_tracker), ValueError); } // Tests that matlbuypolicy sends out a request properly @@ -103,10 +156,11 @@ TEST_F(MatlBuyPolicyTests, OneReq) { double cap = 5; ResBuf buff; buff.capacity(cap); + TotalInvTracker buff_tracker({&buff}); cyclus::Composition::Ptr c1 = cyclus::Composition::Ptr(new TestComp()); MatlBuyPolicy p; - p.Init(fac1, &buff, "").Set("commod1", c1); + p.Init(fac1, &buff, "", &buff_tracker).Set("commod1", c1); std::set::Ptr> obs = p.GetMatlRequests(); ASSERT_EQ(obs.size(), 1); ASSERT_EQ((*obs.begin())->requests().size(), 1); @@ -120,6 +174,7 @@ TEST_F(MatlBuyPolicyTests, MultipleReqs) { double cap = 5; ResBuf buff; buff.capacity(cap); + TotalInvTracker buff_tracker({&buff}); std::string commod1("foo"), commod2("bar"); double p2 = 2.5; cyclus::Composition::Ptr c1 = cyclus::Composition::Ptr(new TestComp()); @@ -127,7 +182,7 @@ TEST_F(MatlBuyPolicyTests, MultipleReqs) { MatlBuyPolicy p; // two requests - p.Init(fac1, &buff, "").Set(commod1, c1).Set(commod2, c2, p2); + p.Init(fac1, &buff, "", &buff_tracker).Set(commod1, c1).Set(commod2, c2, p2); std::set::Ptr> obs = p.GetMatlRequests(); ASSERT_EQ(obs.size(), 1); ASSERT_EQ((*obs.begin())->requests().size(), 2); @@ -148,12 +203,14 @@ TEST_F(MatlBuyPolicyTests, Quantize) { double cap = 5; ResBuf buff; buff.capacity(cap); + TotalInvTracker buff_tracker({&buff}); double p2 = 2.5; cyclus::Composition::Ptr c1 = cyclus::Composition::Ptr(new TestComp()); MatlBuyPolicy p; double quantize = 2.5; - p.Init(fac1, &buff, "", std::numeric_limits::max(), 1, 1, quantize).Set("commod1", c1); + p.Init(fac1, &buff, "", &buff_tracker, std::numeric_limits::max(), + quantize).Set("commod1", c1); std::set::Ptr> obs = p.GetMatlRequests(); ASSERT_EQ(obs.size(), 2); ASSERT_EQ((*obs.begin())->requests().size(), 1); @@ -167,13 +224,15 @@ TEST_F(MatlBuyPolicyTests, MultiReqQuantize) { double cap = 5; ResBuf buff; buff.capacity(cap); + TotalInvTracker buff_tracker({&buff}); cyclus::Composition::Ptr c1 = cyclus::Composition::Ptr(new TestComp()); cyclus::Composition::Ptr c2 = cyclus::Composition::Ptr(new TestComp()); MatlBuyPolicy p; // two portfolios with quantize double quantize = 2.5; - p.Init(fac1, &buff, "", std::numeric_limits::max(), 1, 1, quantize).Set("commod1", c1).Set("commod2", c2); + p.Init(fac1, &buff, "", &buff_tracker, std::numeric_limits::max(), + quantize).Set("commod1", c1).Set("commod2", c2); std::set::Ptr> obs = p.GetMatlRequests(); ASSERT_EQ(obs.size(), 2); ASSERT_EQ((*obs.begin())->requests().size(), 2); @@ -187,6 +246,53 @@ TEST_F(MatlBuyPolicyTests, MultiReqQuantize) { ASSERT_FLOAT_EQ(req->target()->quantity(), quantize); } +TEST_F(MatlBuyPolicyTests, TotalInvTracker) { + using cyclus::QueryResult; + + int dur = 5; + double throughput = 1; + + cyclus::MockSim sim(dur); + cyclus::Agent* a = new TestFacility(sim.context()); + sim.context()->AddPrototype(a->prototype(), a); + sim.agent = sim.context()->CreateAgent(a->prototype()); + TestFacility* fac = dynamic_cast(sim.agent); + sim.AddSource("commod1").Finalize(); + + cyclus::toolkit::ResBuf inbuf; + inbuf.capacity(10); + cyclus::toolkit::ResBuf otherbuf; + otherbuf.capacity(10); + + cyclus::CompMap cm; + cm[1001] = 1; + double mat_size = 2; + cyclus::Material::Ptr mat = cyclus::Material::Create(fac, mat_size, cyclus::Composition::CreateFromAtom(cm)); + otherbuf.Push(mat); + + double total_capacity = 5; + TotalInvTracker buf_tracker({&inbuf, &otherbuf}, total_capacity); + cyclus::toolkit::MatlBuyPolicy policy; + policy.Init(fac, &inbuf, "inbuf", &buf_tracker, throughput) + .Set("commod1").Start(); + + EXPECT_FALSE(buf_tracker.empty()); + EXPECT_EQ(buf_tracker.quantity(), mat_size); + EXPECT_EQ(buf_tracker.capacity(), total_capacity); + EXPECT_EQ(buf_tracker.space(), total_capacity - mat_size); + + EXPECT_NO_THROW(sim.Run()); + + QueryResult qr = sim.db().Query("Transactions", NULL); + int n_trans = qr.rows.size(); + EXPECT_EQ(n_trans, 3); + + EXPECT_EQ(buf_tracker.quantity(), total_capacity); + EXPECT_EQ(buf_tracker.space(), 0); + EXPECT_EQ(inbuf.space(), inbuf.capacity() - inbuf.quantity()); + EXPECT_EQ(buf_tracker.constrained_buf_space(&inbuf), 0); +} + TEST_F(MatlBuyPolicyTests, DefaultFixedActiveDormant) { using cyclus::QueryResult; @@ -204,8 +310,9 @@ TEST_F(MatlBuyPolicyTests, DefaultFixedActiveDormant) { TestFacility* fac = dynamic_cast(sim.agent); cyclus::toolkit::ResBuf inbuf; + TotalInvTracker buf_tracker({&inbuf}); cyclus::toolkit::MatlBuyPolicy policy; - policy.Init(fac, &inbuf, "inbuf", throughput, a_d_dist, a_d_dist, NULL) + policy.Init(fac, &inbuf, "inbuf", &buf_tracker, throughput, a_d_dist, a_d_dist, NULL) .Set("commod1").Start(); EXPECT_NO_THROW(sim.Run()); @@ -240,8 +347,9 @@ TEST_F(MatlBuyPolicyTests, FixedActiveDormant) { TestFacility* fac = dynamic_cast(sim.agent); cyclus::toolkit::ResBuf inbuf; + TotalInvTracker buf_tracker({&inbuf}); cyclus::toolkit::MatlBuyPolicy policy; - policy.Init(fac, &inbuf, "inbuf", throughput, a_d_dist, a_d_dist, size_dist) + policy.Init(fac, &inbuf, "inbuf", &buf_tracker, throughput, a_d_dist, a_d_dist, size_dist) .Set("commod1").Start(); EXPECT_NO_THROW(sim.Run()); @@ -274,8 +382,9 @@ TEST_F(MatlBuyPolicyTests, FixedActiveDormantMultipleCycles) { TestFacility* fac = dynamic_cast(sim.agent); cyclus::toolkit::ResBuf inbuf; + TotalInvTracker buf_tracker({&inbuf}); cyclus::toolkit::MatlBuyPolicy policy; - policy.Init(fac, &inbuf, "inbuf", throughput, a_dist, d_dist, NULL) + policy.Init(fac, &inbuf, "inbuf", &buf_tracker, throughput, a_dist, d_dist, NULL) .Set("commod1").Start(); EXPECT_NO_THROW(sim.Run()); @@ -314,8 +423,9 @@ TEST_F(MatlBuyPolicyTests, UniformActiveDormant) { TestFacility* fac = dynamic_cast(sim.agent); cyclus::toolkit::ResBuf inbuf; + TotalInvTracker buf_tracker({&inbuf}); cyclus::toolkit::MatlBuyPolicy policy; - policy.Init(fac, &inbuf, "inbuf", throughput, a_dist, d_dist, NULL) + policy.Init(fac, &inbuf, "inbuf", &buf_tracker, throughput, a_dist, d_dist, NULL) .Set("commod1").Start(); EXPECT_NO_THROW(sim.Run()); @@ -352,8 +462,9 @@ TEST_F(MatlBuyPolicyTests, NormalActiveDormant) { TestFacility* fac = dynamic_cast(sim.agent); cyclus::toolkit::ResBuf inbuf; + TotalInvTracker buf_tracker({&inbuf}); cyclus::toolkit::MatlBuyPolicy policy; - policy.Init(fac, &inbuf, "inbuf", throughput, a_dist, d_dist, NULL) + policy.Init(fac, &inbuf, "inbuf", &buf_tracker, throughput, a_dist, d_dist, NULL) .Set("commod1").Start(); EXPECT_NO_THROW(sim.Run()); @@ -390,8 +501,9 @@ TEST_F(MatlBuyPolicyTests, MixedActiveDormant) { TestFacility* fac = dynamic_cast(sim.agent); cyclus::toolkit::ResBuf inbuf; + TotalInvTracker buf_tracker({&inbuf}); cyclus::toolkit::MatlBuyPolicy policy; - policy.Init(fac, &inbuf, "inbuf", throughput, a_dist, d_dist, NULL) + policy.Init(fac, &inbuf, "inbuf", &buf_tracker, throughput, a_dist, d_dist, NULL) .Set("commod1").Start(); EXPECT_NO_THROW(sim.Run()); @@ -424,8 +536,9 @@ TEST_F(MatlBuyPolicyTests, RandomSizeUniform) { TestFacility* fac = dynamic_cast(sim.agent); cyclus::toolkit::ResBuf inbuf; + TotalInvTracker buf_tracker({&inbuf}); cyclus::toolkit::MatlBuyPolicy policy; - policy.Init(fac, &inbuf, "inbuf", throughput, NULL, NULL, size_dist) + policy.Init(fac, &inbuf, "inbuf", &buf_tracker, throughput, NULL, NULL, size_dist) .Set("commod1").Start(); EXPECT_NO_THROW(sim.Run()); @@ -454,8 +567,9 @@ TEST_F(MatlBuyPolicyTests, RandomSizeNormal) { TestFacility* fac = dynamic_cast(sim.agent); cyclus::toolkit::ResBuf inbuf; + TotalInvTracker buf_tracker({&inbuf}); cyclus::toolkit::MatlBuyPolicy policy; - policy.Init(fac, &inbuf, "inbuf", throughput, NULL, NULL, size_dist) + policy.Init(fac, &inbuf, "inbuf", &buf_tracker, throughput, NULL, NULL, size_dist) .Set("commod1").Start(); EXPECT_NO_THROW(sim.Run()); @@ -486,8 +600,9 @@ TEST_F(MatlBuyPolicyTests, RandomSizeAndFrequency) { TestFacility* fac = dynamic_cast(sim.agent); cyclus::toolkit::ResBuf inbuf; + TotalInvTracker buf_tracker({&inbuf}); cyclus::toolkit::MatlBuyPolicy policy; - policy.Init(fac, &inbuf, "inbuf", throughput, a_dist, d_dist, size_dist) + policy.Init(fac, &inbuf, "inbuf", &buf_tracker, throughput, a_dist, d_dist, size_dist) .Set("commod1").Start(); EXPECT_NO_THROW(sim.Run()); @@ -503,5 +618,40 @@ TEST_F(MatlBuyPolicyTests, RandomSizeAndFrequency) { delete a; } +TEST_F(MatlBuyPolicyTests, Cumulative_Cap_Inventory) { + using cyclus::QueryResult; + + double ccap = 2; + boost::shared_ptr d_dist = boost::shared_ptr(new FixedIntDist(2)); + + int dur = 8; + double throughput = 1; + + cyclus::MockSim sim(dur); + cyclus::Agent* a = new TestFacility(sim.context()); + sim.context()->AddPrototype(a->prototype(), a); + sim.agent = sim.context()->CreateAgent(a->prototype()); + sim.AddSource("commod1").Finalize(); + + TestFacility* fac = dynamic_cast(sim.agent); + + cyclus::toolkit::ResBuf inbuf; + TotalInvTracker buf_tracker({&inbuf}); + cyclus::toolkit::MatlBuyPolicy policy; + policy.Init(fac, &inbuf, "inbuf", &buf_tracker, throughput, ccap, d_dist) + .Set("commod1").Start(); + + EXPECT_NO_THROW(sim.Run()); + + // check that transactions happen as expected (at time steps 0 and 1, then 4 and 5) + QueryResult qr = sim.db().Query("Transactions", NULL); + EXPECT_EQ(0, qr.GetVal("Time", 0)); + EXPECT_EQ(1, qr.GetVal("Time", 1)); + EXPECT_EQ(4, qr.GetVal("Time", 2)); + EXPECT_EQ(5, qr.GetVal("Time", 3)); + + delete a; +} + } } diff --git a/tests/toolkit/total_inv_tracker_tests.cc b/tests/toolkit/total_inv_tracker_tests.cc new file mode 100644 index 0000000000..222e855963 --- /dev/null +++ b/tests/toolkit/total_inv_tracker_tests.cc @@ -0,0 +1,62 @@ +#include "total_inv_tracker_tests.h" + +#include + +namespace cyclus { +namespace toolkit { + + +TEST_F(TotalInvTrackerTest, Init) { + EXPECT_THROW(empty_tracker_.Init({}), ValueError); + EXPECT_THROW(empty_tracker_.Init({&buf0_, &buf1_}, 0), ValueError); + EXPECT_THROW(empty_tracker_.Init({&buf0_, &buf1_}, -1), ValueError); +} + +TEST_F(TotalInvTrackerTest, quantity) { + EXPECT_NO_THROW(single_tracker_.quantity()); + EXPECT_NO_THROW(multi_tracker_.quantity()); + EXPECT_EQ(single_tracker_.quantity(), qty1_); + EXPECT_EQ(multi_tracker_.quantity(), qty1_ + qty2_); +} + +TEST_F(TotalInvTrackerTest, capacity) { + EXPECT_EQ(single_tracker_.capacity(), 20); + EXPECT_EQ(single_tracker_.tracker_capacity(), max_inv_size_); + EXPECT_EQ(multi_tracker_.capacity(), max_inv_size_); + EXPECT_EQ(multi_tracker_.tracker_capacity(), max_inv_size_); +} + +TEST_F(TotalInvTrackerTest, space) { + EXPECT_EQ(single_tracker_.space(), 20 - qty1_); + EXPECT_EQ(multi_tracker_.space(), max_inv_size_ - qty1_ - qty2_); +} + +TEST_F(TotalInvTrackerTest, constrained_buf_space) { + EXPECT_EQ(multi_tracker_.constrained_buf_space(&buf1_), buf1_.space()); + EXPECT_EQ(multi_tracker_.constrained_buf_space(&buf2_), + max_inv_size_ - qty1_ - qty2_); +} + +TEST_F(TotalInvTrackerTest, empty) { + EXPECT_THROW(empty_tracker_.empty(), ValueError); + EXPECT_THROW(empty_tracker_.quantity(), ValueError); + EXPECT_THROW(empty_tracker_.capacity(), ValueError); + EXPECT_THROW(empty_tracker_.set_capacity(-1), ValueError); + EXPECT_THROW(empty_tracker_.set_capacity(1000), ValueError); + EXPECT_THROW(empty_tracker_.space(), ValueError); +} + +TEST_F(TotalInvTrackerTest, num_bufs) { + EXPECT_THROW(empty_tracker_.num_bufs(), ValueError); + EXPECT_NO_THROW(multi_tracker_.num_bufs()); + EXPECT_EQ(multi_tracker_.num_bufs(), 3); +} + +TEST_F(TotalInvTrackerTest, set_capacity) { + EXPECT_THROW(multi_tracker_.set_capacity(-1), ValueError); + EXPECT_THROW(multi_tracker_.set_capacity(10), ValueError); + EXPECT_NO_THROW(multi_tracker_.set_capacity(1000)); +} + +} // namespace toolkit +} // namespace cyclus diff --git a/tests/toolkit/total_inv_tracker_tests.h b/tests/toolkit/total_inv_tracker_tests.h new file mode 100644 index 0000000000..d180401a7c --- /dev/null +++ b/tests/toolkit/total_inv_tracker_tests.h @@ -0,0 +1,68 @@ +#ifndef CYCLUS_TESTS_TOOLKIT_TOTAL_INV_TRACKER_TESTS_H +#define CYCLUS_TESTS_TOOLKIT_TOTAL_INV_TRACKER_TESTS_H + +#include + +#include "error.h" +#include "toolkit/total_inv_tracker.h" +#include "toolkit/res_buf.h" +#include "composition.h" +#include "material.h" +#include "test_agents/test_facility.h" + +namespace cyclus { +namespace toolkit { + +class TotalInvTrackerTest : public ::testing::Test { + protected: + cyclus::Timer ti; + cyclus::Recorder rec; + cyclus::Context* ctx; + TestFacility* fac; + + TotalInvTracker empty_tracker_; + TotalInvTracker single_tracker_; + TotalInvTracker multi_tracker_; + ResBuf buf0_, buf1_, buf2_, buf3_; + double max_inv_size_; + Material::Ptr mat0_, mat1_, mat2_; + double qty1_, qty2_; + + virtual void SetUp() { + try { + ctx = new cyclus::Context(&ti, &rec); + fac = new TestFacility(ctx); + + CompMap cm; + cm[1001] = 1.0; + qty1_ = 10; + qty2_ = 50; + mat0_ = Material::Create(fac, qty1_, Composition::CreateFromAtom(cm)); + mat1_ = Material::Create(fac, qty1_, Composition::CreateFromAtom(cm)); + mat2_ = Material::Create(fac, qty2_, Composition::CreateFromAtom(cm)); + + max_inv_size_ = 100; + + buf0_.capacity(20); + buf0_.Push(mat0_); + single_tracker_.Init({&buf0_}, max_inv_size_); + + buf1_.capacity(50); + buf2_.capacity(max_inv_size_); + buf3_.capacity(50); + buf1_.Push(mat1_); + buf2_.Push(mat2_); + multi_tracker_.Init({&buf1_, &buf2_, &buf3_}, max_inv_size_); + } catch (std::exception err) { + FAIL() << "An exception was thrown in the fixture SetUp."; + } + } + virtual void TearDown() { + delete ctx; + } +}; + +} // namespace toolkit +} // namespace cyclus + +#endif // CYCLUS_TESTS_TOOLKIT_TOTAL_INV_TRACKER_TESTS_H