From c46afec49f0d2394d38e206c7001ffc2f5ac29e0 Mon Sep 17 00:00:00 2001 From: Katie Mummah Date: Tue, 28 May 2024 19:37:42 -0500 Subject: [PATCH] transport unit fill and max shippable transport units --- src/context.cc | 60 ++++++++++---------- src/context.h | 13 +++++ src/package.cc | 57 ++++++++++--------- src/package.h | 4 +- src/xml_file_loader.cc | 1 + tests/context_tests.cc | 12 ++++ tests/package_tests.cc | 123 +++++++++++++++++++++++++++++++++++++++-- 7 files changed, 208 insertions(+), 62 deletions(-) diff --git a/src/context.cc b/src/context.cc index c736a9db5a..156357d3bf 100644 --- a/src/context.cc +++ b/src/context.cc @@ -224,39 +224,41 @@ Package::Ptr Context::GetPackage(std::string name) { return packages_[name]; } -Package::Ptr Context::GetPackageById(int id) { - if (id == Package::unpackaged_id()) { - return Package::unpackaged(); - } - if (id < 0) { - throw ValueError("Invalid package id " + std::to_string(id)); - } - // iterate through the list of packages to get the one package with the correct id - std::map::iterator it; - for (it = packages_.begin(); it != packages_.end(); ++it) { - if (it->second->id() == id) { - return it->second; - } +void Context::AddTransportUnit(std::string name, int fill_min, int fill_max, + std::string strategy) { + if (transport_units_.count(name) == 0) { + TransportUnit::Ptr tu = TransportUnit::Create(name, fill_min, fill_max, + strategy); + transport_units_[name] = tu; + RecordTransportUnit(tu); + } else { + throw KeyError("TransportUnit " + name + " already exists!"); } - throw ValueError("Invalid package id " + std::to_string(id)); } - void AddTransportUnit(std::string name, int fill_min, int fill_max, - std::string strategy) { - transport_units_[name] = TransportUnit::Create(name, fill_min, fill_max, strategy); - NewDatum("TransportUnit") - ->AddVal("TransportUnit", name) - ->AddVal("FillMin", fill_min) - ->AddVal("FillMax", fill_max) - ->AddVal("Strategy", strategy) - ->AddVal("Id", transport_units_[name]->id()) - ->Record(); - } +void Context::RecordTransportUnit(TransportUnit::Ptr tu) { + NewDatum("TransportUnit") + ->AddVal("TransportUnitName", tu->name()) + ->AddVal("FillMin", tu->fill_min()) + ->AddVal("FillMax", tu->fill_max()) + ->AddVal("Strategy", tu->strategy()) + ->Record(); +} - /// Retrieve a registered transport unit - TransportUnit::Ptr GetTransportUnitByName(std::string name); +/// Retrieve a registered transport unit +TransportUnit::Ptr Context::GetTransportUnit(std::string name) { + if (name == TransportUnit::unrestricted_name()) { + return TransportUnit::unrestricted(); + } + if (transport_units_.size() == 0 ) { + throw KeyError("No user-created transport units exist"); + } + if (transport_units_.count(name) == 0) { + throw KeyError("Invalid transport unit name " + name); + } + return transport_units_[name]; +} - TransportUnit::Ptr GetTransportUnitById(int id); void Context::InitSim(SimInfo si) { NewDatum("Info") @@ -311,7 +313,7 @@ void Context::InitSim(SimInfo si) { int Context::time() { return ti_->time(); } -LoadPacka + int Context::random() { return rng_->random(); } diff --git a/src/context.h b/src/context.h index f245cf22f0..3892006f58 100644 --- a/src/context.h +++ b/src/context.h @@ -262,6 +262,19 @@ class Context { /// Retrieve a registered package. Package::Ptr GetPackage(std::string name); + /// Adds a transport unit type to a simulation-wide accessible list. + /// Agents should NOT add their own transport units. + void AddTransportUnit(std::string name, int fill_min = 0, + int fill_max = std::numeric_limits::max(), + std::string strategy = "first"); + + /// Records transport unit information. Should be used first on unrestricted, + /// then to record user-declared transport units + void RecordTransportUnit(TransportUnit::Ptr); + + /// Retrieve a registered transport unit. + TransportUnit::Ptr GetTransportUnit(std::string name); + int random(); /// Generates a random number on the range [0,1)] diff --git a/src/package.cc b/src/package.cc index e6ad848813..fd2234db9a 100644 --- a/src/package.cc +++ b/src/package.cc @@ -62,8 +62,6 @@ Package::Package(std::string name, double fill_min, double fill_max, } } -// unrestricted id is 1, so start the user-declared transport id at 2 -int TransportUnit::next_package_id_ = 2; TransportUnit::Ptr TransportUnit::unrestricted_ = NULL; TransportUnit::Ptr TransportUnit::Create(std::string name, int fill_min, int fill_max, std::string strategy) { @@ -94,31 +92,41 @@ int TransportUnit::GetTransportUnitFill(int qty) { return 0; } - int fill_mass; if (strategy_ == "first") { - fill_mass = fill_max_; + return fill_max_; } else if (strategy_ == "equal") { - int num_min_fill = std::floor(qty / fill_min_); - int num_max_fill = std::ceil(qty / fill_max_); - if (num_min_fill >= num_max_fill) { - // all material can fit in a package - int fill_mass = qty / num_max_fill; - } else { - // some material will remain unrestricted, fill up as many transport - // units as possible - fill_mass = fill_max_; + // int division automatically rounds down. don't need floor in min, and + // get ceil by hand instead + int num_at_min_fill = qty / fill_min_; + int num_at_max_fill = (qty + fill_max_ - 1) / fill_max_; + + if (num_at_min_fill >= num_at_max_fill) { + // all material *might* fit transport units. However, this is more + // challenging than packages because transport units are discrete. check: + + double dbl_fill_mass = (double)qty / (double)num_at_max_fill; + return std::floor(dbl_fill_mass); } + // some material will remain unrestricted, fill up as many transport + // units as possible. Or, perfect fill is possible but not with integer + // fill (see above). Should also start filling to max until last partial + // filled transport unit + return fill_max_; } - return fill_mass; } -int TransportUnit:TotalShippablePackages(int pkgs) { - int fill = GetTransportUnitFill(pkgs); - int shippable = std::floor(pkgs / fill) * fill; +int TransportUnit::MaxShippablePackages(int pkgs) { + int TU_fill; + int shippable = 0; - int remainder = pkgs % fill; - if (remainder > 0 && remainder >= fill_min_) { - shippable += remainder; + if (pkgs == 0 && pkgs < fill_min_) { + return 0; + } + + while (pkgs > 0 && pkgs >= fill_min_) { + TU_fill = GetTransportUnitFill(pkgs); + shippable += TU_fill; + pkgs -= TU_fill; } return shippable; } @@ -126,13 +134,8 @@ int TransportUnit:TotalShippablePackages(int pkgs) { TransportUnit::TransportUnit(std::string name, int fill_min, int fill_max, std::string strategy) : name_(name), fill_min_(fill_min), fill_max_(fill_max), strategy_(strategy) { if (name == unrestricted_name_) { - if (unrestricted_) { - throw ValueError("can't create a new transport unit with name 'unrestricted'"); - } - id_ = unrestricted_id_; - } else { - id_ = next_transport_unit_id_++; - } + throw ValueError("can't create a new transport unit with name 'unrestricted'"); } +} } // namespace cyclus diff --git a/src/package.h b/src/package.h index 946c112466..7ad89f8310 100644 --- a/src/package.h +++ b/src/package.h @@ -86,7 +86,7 @@ class TransportUnit { int GetTransportUnitFill(int qty); /// Returns the max number of packages that can be shipped - int TotalShippablePackages(int pkgs); + int MaxShippablePackages(int pkgs); // returns package id int id() const { return id_; } @@ -115,7 +115,7 @@ class TransportUnit { std::string strategy = "first"); static const int unrestricted_id_ = 1; - static constexpr char unrestricted_name_[11] = "unrestricted"; + static constexpr char unrestricted_name_[13] = "unrestricted"; static Ptr unrestricted_; static int next_tranport_unit_id_; diff --git a/src/xml_file_loader.cc b/src/xml_file_loader.cc index 7754010fd5..aff0d57790 100644 --- a/src/xml_file_loader.cc +++ b/src/xml_file_loader.cc @@ -365,6 +365,7 @@ void XMLFileLoader::LoadTransportUnits() { std::string strategy = cyclus::OptionalQuery(qe, "strategy", "first"); ctx_->AddTransportUnit(name, fill_min, fill_max, strategy); + } } void XMLFileLoader::LoadSpecs() { diff --git a/tests/context_tests.cc b/tests/context_tests.cc index a6437c28cc..f9b96047e0 100644 --- a/tests/context_tests.cc +++ b/tests/context_tests.cc @@ -134,5 +134,17 @@ TEST_F(ContextTests, DoublePackageNameThrow) { ASSERT_THROW(ctx->AddPackage("foo"), cyclus::KeyError); + delete ctx; + } + +TEST_F(ContextTests, DoubleTransportUnitNameThrow) { + Timer ti; + Recorder rec; + Context* ctx = new Context(&ti, &rec); + + ctx->AddTransportUnit("foo"); + + ASSERT_THROW(ctx->AddTransportUnit("foo"), cyclus::KeyError); + delete ctx; } \ No newline at end of file diff --git a/tests/package_tests.cc b/tests/package_tests.cc index 5b05e7f237..3bee694f2b 100644 --- a/tests/package_tests.cc +++ b/tests/package_tests.cc @@ -8,8 +8,9 @@ #include "test_agents/test_facility.h" using cyclus::Package; +using cyclus::TransportUnit; -TEST(PackageTests, Create) { +TEST(PackageTests, CreatePackage) { std::string p_exp_name = "foo"; double exp_min = 0.1; @@ -32,7 +33,7 @@ TEST(PackageTests, Create) { } -TEST(PackageTests, UnpackagedID) { +TEST(PackageTests, Unpackaged) { EXPECT_EQ("unpackaged", Package::unpackaged_name()); } @@ -46,14 +47,14 @@ TEST(PackageTests, InvalidPackage) { EXPECT_THROW(Package::Create("foo", 100, 1, "first"), cyclus::ValueError); } -TEST(PackageTests, GetFillMass) { +TEST(PackageTests, GetPackageFillMass) { double min = 0.3; double max = 0.9; double tight_min = 0.85; Package::Ptr p = Package::Create("foo", min, max, "first"); Package::Ptr q = Package::Create("bar", min, max, "equal"); - Package::Ptr r = Package::Create("bar", tight_min, max, "equal"); + Package::Ptr r = Package::Create("baz", tight_min, max, "equal"); double exp; @@ -75,4 +76,118 @@ TEST(PackageTests, GetFillMass) { EXPECT_EQ(max, p->GetFillMass(two_packages)); exp = two_packages / 2; EXPECT_EQ(exp, q->GetFillMass(two_packages)); +} + +TEST(PackageTests, CreateTransportUnit) { + + std::string p_exp_name = "foo"; + int exp_min = 0.1; + int exp_max = 0.9; + std::string exp_strat = "first"; + + TransportUnit::Ptr p = TransportUnit::Create(p_exp_name, exp_min, exp_max, + exp_strat); + + EXPECT_EQ(p_exp_name, p->name()); + EXPECT_EQ(exp_min, p->fill_min()); + EXPECT_EQ(exp_max, p->fill_max()); + EXPECT_EQ(exp_strat, p->strategy()); + + EXPECT_NE(TransportUnit::unrestricted_name(), p->name()); + + std::string q_exp_name = "bar"; + TransportUnit::Ptr q = TransportUnit::Create(q_exp_name, exp_min, exp_max, + exp_strat); + EXPECT_NE(TransportUnit::unrestricted_name(), q->name()); + EXPECT_NE(q->name(), p->name()); +} + +TEST(PackageTests, Unrestricted) { + EXPECT_EQ("unrestricted", TransportUnit::unrestricted_name()); +} + +TEST(PackageTests, InvalidTransportUnit) { + // can't create package with name "unrestricted" + EXPECT_THROW(TransportUnit::Create("unrestricted", 0, 1, "first"), + cyclus::ValueError); + // can't have negative min/max + EXPECT_THROW(TransportUnit::Create("foo", -1, 1, "first"), + cyclus::ValueError); + EXPECT_THROW(TransportUnit::Create("foo", 0, -1, "first"), + cyclus::ValueError); + // can't have min bigger than max + EXPECT_THROW(TransportUnit::Create("foo", 100, 1, "first"), + cyclus::ValueError); +} + +TEST(PackageTests, GetTransportUnitFillMass) { + int min = 3; + int max = 9; + int tight_min = 8; + + TransportUnit::Ptr p = TransportUnit::Create("foo", min, max, "first"); + TransportUnit::Ptr q = TransportUnit::Create("bar", min, max, "equal"); + TransportUnit::Ptr r = TransportUnit::Create("baz", tight_min, max, "equal"); + + double exp; + + int no_fit = 1; + EXPECT_EQ(0, p->GetTransportUnitFill(no_fit)); + EXPECT_EQ(0, q->GetTransportUnitFill(no_fit)); + EXPECT_EQ(0, r->GetTransportUnitFill(no_fit)); + + int perfect_fit = 9; + EXPECT_EQ(perfect_fit, p->GetTransportUnitFill(perfect_fit)); + EXPECT_EQ(perfect_fit, q->GetTransportUnitFill(perfect_fit)); + EXPECT_EQ(perfect_fit, r->GetTransportUnitFill(perfect_fit)); + + int two_full_packages = 18; + EXPECT_EQ(max, p->GetTransportUnitFill(perfect_fit)); + EXPECT_EQ(max, q->GetTransportUnitFill(perfect_fit)); + EXPECT_EQ(max, r->GetTransportUnitFill(perfect_fit)); + + int partial_fit = 11; + EXPECT_EQ(max, p->GetTransportUnitFill(partial_fit)); + exp = std::floor(partial_fit / 2); + EXPECT_EQ(exp, q->GetTransportUnitFill(partial_fit)); + EXPECT_EQ(max, r->GetTransportUnitFill(partial_fit)); + + int two_partial_packages = 17; + EXPECT_EQ(max, p->GetTransportUnitFill(two_partial_packages)); + exp = std::floor(two_partial_packages / 2); + EXPECT_EQ(exp, q->GetTransportUnitFill(two_partial_packages)); +} + +TEST(PackageTests, MaxShippablePackages) { + int pq_min = 3; + int pq_max = 4; + int r_min = 6; + int r_max = 8; + TransportUnit::Ptr p = TransportUnit::Create("foo", pq_min, pq_max, "first"); + TransportUnit::Ptr q = TransportUnit::Create("bar", pq_min, pq_max, "equal"); + TransportUnit::Ptr r = TransportUnit::Create("baz", r_min, r_max, "equal"); + + int exp; + + int none_fit = 2; + EXPECT_EQ(0, p->MaxShippablePackages(none_fit)); + EXPECT_EQ(0, q->MaxShippablePackages(none_fit)); + EXPECT_EQ(0, r->MaxShippablePackages(none_fit)); + + int all_fit = 8; + EXPECT_EQ(all_fit, p->MaxShippablePackages(all_fit)); + EXPECT_EQ(all_fit, q->MaxShippablePackages(all_fit)); + EXPECT_EQ(all_fit, r->MaxShippablePackages(all_fit)); + + // all can ship for p/q, but will go 4-3-3, only 8 ship for r + int partial = 10; + EXPECT_EQ(pq_max * 2, p->MaxShippablePackages(partial)); + EXPECT_EQ(partial, q->MaxShippablePackages(partial)); + EXPECT_EQ(r_max, r->MaxShippablePackages(partial)); + + int partial2 = 14; + EXPECT_EQ(pq_max * 3, p->MaxShippablePackages(partial2)); + EXPECT_EQ(partial2, q->MaxShippablePackages(partial2)); + EXPECT_EQ(partial2, r->MaxShippablePackages(partial2)); + } \ No newline at end of file