From 44855d792bb8244aee5e88632723c9376a9d8a8d Mon Sep 17 00:00:00 2001 From: Robert Carlsen Date: Fri, 10 Apr 2015 12:01:24 -0500 Subject: [PATCH 1/6] Handle reactor retirement better: * discharge all fuel fuel from core at end of life (and transmute it). * if nearing end of life, only order up to enough fresh fuel to reach end of life. * block decommissioning until all fuel has been removed from core and traded away out of spent inventory. --- src/reactor.cc | 56 +++++++++++++++++++++++++++++++++++++++++--------- src/reactor.h | 5 +++++ 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/reactor.cc b/src/reactor.cc index 26b82a644b..ccb5675667 100644 --- a/src/reactor.cc +++ b/src/reactor.cc @@ -57,7 +57,7 @@ void Reactor::InitFrom(cyclus::QueryableBackend* b) { void Reactor::EnterNotify() { cyclus::Facility::EnterNotify(); - + // If the user ommitted fuel_prefs, we set it to zeros for each fuel // type. Without this segfaults could occur - yuck. if (fuel_prefs.size() == 0) { @@ -98,6 +98,10 @@ void Reactor::EnterNotify() { } } +bool Reactor::CheckDecommissionCondition() { + return core.count() == 0 && spent.count() == 0; +} + void Reactor::Tick() { // The following code must go in the Tick so they fire on the time step // following the cycle_step update - allowing for the all reactor events to @@ -105,10 +109,23 @@ void Reactor::Tick() { // they // can't go at the beginnin of the Tock is so that resource exchange has a // chance to occur after the discharge on this same time step. + + if (retired()) { + Record("RETIRED", ""); + while (core.count() > 0) { + Transmute(); + if (!Discharge()) { + break; + } + } + return; + } + if (cycle_step == cycle_time) { Transmute(); Record("CYCLE_END", ""); } + if (cycle_step >= cycle_time && !discharged) { discharged = Discharge(); } @@ -158,10 +175,25 @@ std::set::Ptr> Reactor::GetMatlRequests() { std::set::Ptr> ports; Material::Ptr m; - int n_assem_order = - n_assem_core - core.count() + n_assem_fresh - fresh.count(); + // second min expression reduces assembles to amount needed until + // retirement if it is near. + int n_assem_order = n_assem_core - core.count() + n_assem_fresh - fresh.count(); + + if (exit_time() != -1) { + int tleft = exit_time() - context()->time(); + int tleftcycle = cycle_time + refuel_time - cycle_step; + double ncyclesleft = (double)(tleft - tleftcycle) / (double)(cycle_time + refuel_time); + if ((int)ncyclesleft < ncyclesleft) { + ncyclesleft = ((int)ncyclesleft) + 1; + } + int nneed = std::max(0.0, ncyclesleft * n_assem_batch - n_assem_fresh); + n_assem_order = std::min(n_assem_order, nneed); + } + if (n_assem_order == 0) { return ports; + } else if (retired()) { + return ports; } for (int i = 0; i < n_assem_order; i++) { @@ -275,6 +307,10 @@ std::set::Ptr> Reactor::GetMatlBids( } void Reactor::Tock() { + if (retired()) { + return; + } + if (cycle_step >= cycle_time + refuel_time && core.count() == n_assem_core) { discharged = false; cycle_step = 0; @@ -299,11 +335,12 @@ void Reactor::Tock() { } void Reactor::Transmute() { - // safe to assume full core. - MatVec old = core.PopN(n_assem_batch); - MatVec tail = core.PopN(core.count()); + MatVec old = core.PopN(std::min(n_assem_batch, core.count())); core.Push(old); - core.Push(tail); + if (core.count() > old.size()) { + // rotate untransmuted mats back to back of buffer + core.Push(core.PopN(core.count() - old.size())); + } std::stringstream ss; ss << old.size() << " assemblies"; @@ -326,13 +363,12 @@ std::map Reactor::PeekSpent() { } bool Reactor::Discharge() { - if (n_assem_spent - spent.count() < n_assem_batch) { + int npop = std::min(n_assem_batch, core.count()); + if (n_assem_spent - spent.count() < npop) { Record("DISCHARGE", "failed"); return false; // not enough room in spent buffer } - int npop = std::min(n_assem_batch, core.count()); - std::stringstream ss; ss << npop << " assemblies"; Record("DISCHARGE", ss.str()); diff --git a/src/reactor.h b/src/reactor.h index 8d9cfcecf4..b90122160b 100644 --- a/src/reactor.h +++ b/src/reactor.h @@ -93,6 +93,7 @@ class Reactor : public cyclus::Facility, virtual void Tick(); virtual void Tock(); virtual void EnterNotify(); + virtual bool CheckDecommissionCondition(); virtual void AcceptMatlTrades(const std::vector, cyclus::Material::Ptr> >& responses); @@ -117,6 +118,10 @@ class Reactor : public cyclus::Facility, std::string fuel_outrecipe(cyclus::Material::Ptr m); double fuel_pref(cyclus::Material::Ptr m); + bool retired() { + return exit_time() != -1 && context()->time() >= exit_time(); + } + /// Store fuel info index for the given resource received on incommod. void index_res(cyclus::Resource::Ptr m, std::string incommod); From 4a845dd6b91b8561e52202fc7690072d414d651f Mon Sep 17 00:00:00 2001 From: Robert Carlsen Date: Mon, 13 Apr 2015 14:42:46 -0500 Subject: [PATCH 2/6] only transmute half of discharged core at retirement --- src/reactor.cc | 18 +++++++++++------- src/reactor.h | 24 +++++++++++++++++++++++- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/reactor.cc b/src/reactor.cc index ccb5675667..845174ce59 100644 --- a/src/reactor.cc +++ b/src/reactor.cc @@ -112,8 +112,11 @@ void Reactor::Tick() { if (retired()) { Record("RETIRED", ""); + + if (context()->time() == exit_time()) { // only need to transmute once + Transmute(ceil(static_cast(n_assem_core) / 2.0)); + } while (core.count() > 0) { - Transmute(); if (!Discharge()) { break; } @@ -182,10 +185,9 @@ std::set::Ptr> Reactor::GetMatlRequests() { if (exit_time() != -1) { int tleft = exit_time() - context()->time(); int tleftcycle = cycle_time + refuel_time - cycle_step; - double ncyclesleft = (double)(tleft - tleftcycle) / (double)(cycle_time + refuel_time); - if ((int)ncyclesleft < ncyclesleft) { - ncyclesleft = ((int)ncyclesleft) + 1; - } + double ncyclesleft = static_cast(tleft - tleftcycle) / + static_cast(cycle_time + refuel_time); + ncyclesleft = ceil(ncyclesleft); int nneed = std::max(0.0, ncyclesleft * n_assem_batch - n_assem_fresh); n_assem_order = std::min(n_assem_order, nneed); } @@ -334,8 +336,10 @@ void Reactor::Tock() { } } -void Reactor::Transmute() { - MatVec old = core.PopN(std::min(n_assem_batch, core.count())); +void Reactor::Transmute() { Transmute(n_assem_batch); } + +void Reactor::Transmute(int n_assem) { + MatVec old = core.PopN(std::min(n_assem, core.count())); core.Push(old); if (core.count() > old.size()) { // rotate untransmuted mats back to back of buffer diff --git a/src/reactor.h b/src/reactor.h index b90122160b..5dd22f1079 100644 --- a/src/reactor.h +++ b/src/reactor.h @@ -42,6 +42,15 @@ namespace cycamore { /// becomes full, the reactor will halt operation at the end of the next cycle /// until there is more room. Each time step, the reactor will try to trade /// away as much of its spent fuel inventory as possible. +/// +/// When the reactor reaches the end of its lifetime, it will discharge all +/// material from its core and trade away all its spent fuel as quickly as +/// possible. Full decommissioning will be delayed until all spent fuel is +/// gone. If the reactor has a full core when it is decommissioned (i.e. is +/// mid-cycle) when the reactor is decommissioned, half (rounded up to nearest +/// int) of its assemblies are transmuted to their respective burnt +/// compositions. + class Reactor : public cyclus::Facility, public cyclus::toolkit::CommodityProducer { #pragma cyclus note { \ @@ -83,7 +92,16 @@ class Reactor : public cyclus::Facility, " operational cycle before the next begins. If the spent fuel inventory" \ " becomes full, the reactor will halt operation at the end of the next cycle" \ " until there is more room. Each time step, the reactor will try to trade" \ - " away as much of its spent fuel inventory as possible.", \ + " away as much of its spent fuel inventory as possible." \ + "\n\n" \ + "When the reactor reaches the end of its lifetime, it will discharge all" \ + " material from its core and trade away all its spent fuel as quickly as" \ + " possible. Full decommissioning will be delayed until all spent fuel is" \ + " gone. If the reactor has a full core when it is decommissioned (i.e. is" \ + " mid-cycle) when the reactor is decommissioned, half (rounded up to nearest" \ + " int) of its assemblies are transmuted to their respective burnt" \ + " compositions." \ + "", \ } public: @@ -136,6 +154,10 @@ class Reactor : public cyclus::Facility, /// fully burnt state as defined by its outrecipe. void Transmute(); + /// Transmute the specified number of assemblies in the core to their + /// fully burnt state as defined by their outrecipe. + void Transmute(int n_assem); + /// Records a reactor event to the output db with the given name and note val. void Record(std::string name, std::string val); From e2b75d64cc9e5171a56bffdc44c9c2483b5a089f Mon Sep 17 00:00:00 2001 From: Robert Carlsen Date: Thu, 16 Apr 2015 13:33:33 -0500 Subject: [PATCH 3/6] fix bug where non-unique fuel outcommods on a reactor resulted in duplicate bid portfolios and over-matching --- src/reactor.cc | 13 ++++++++++--- src/reactor.h | 3 +++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/reactor.cc b/src/reactor.cc index 845174ce59..8c6f0ecdd3 100644 --- a/src/reactor.cc +++ b/src/reactor.cc @@ -223,7 +223,6 @@ void Reactor::GetMatlTrades( using cyclus::Trade; std::map mats = PopSpent(); - std::vector >::const_iterator it; for (int i = 0; i < trades.size(); i++) { std::string commod = trades[i].request->commodity(); Material::Ptr m = mats[commod].back(); @@ -267,8 +266,16 @@ std::set::Ptr> Reactor::GetMatlBids( bool gotmats = false; std::map all_mats; - for (int i = 0; i < fuel_outcommods.size(); i++) { - std::string commod = fuel_outcommods[i]; + + if (uniq_outcommods_.empty()) { + for (int i = 0; i < fuel_outcommods.size(); i++) { + uniq_outcommods_.insert(fuel_outcommods[i]); + } + } + + std::set::iterator it; + for (it = uniq_outcommods_.begin(); it != uniq_outcommods_.end(); ++it) { + std::string commod = *it; std::vector*>& reqs = commod_requests[commod]; if (reqs.size() == 0) { continue; diff --git a/src/reactor.h b/src/reactor.h index 5dd22f1079..a6aa645dbf 100644 --- a/src/reactor.h +++ b/src/reactor.h @@ -362,6 +362,9 @@ class Reactor : public cyclus::Facility, "internal": True \ } std::map res_indexes; + + // populated lazily and no need to persist. + std::set uniq_outcommods_; }; } // namespace cycamore From 85fe5dd0789fdea0ec2a0306ef87400e4d107e06 Mon Sep 17 00:00:00 2001 From: Robert Carlsen Date: Thu, 21 May 2015 15:51:18 -0500 Subject: [PATCH 4/6] add tests and fix bugs --- src/reactor.cc | 13 ++++++++++++- src/reactor_tests.cc | 46 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/reactor.cc b/src/reactor.cc index 8c6f0ecdd3..1d46ddc31d 100644 --- a/src/reactor.cc +++ b/src/reactor.cc @@ -109,6 +109,9 @@ void Reactor::Tick() { // they // can't go at the beginnin of the Tock is so that resource exchange has a // chance to occur after the discharge on this same time step. + std::cout << context()->time() << ": " << "fresh.count=" << fresh.count() << "\n"; + std::cout << " core.count=" << core.count() << "\n"; + std::cout << " spent.count=" << spent.count() << "\n"; if (retired()) { Record("RETIRED", ""); @@ -121,6 +124,12 @@ void Reactor::Tick() { break; } } + // in case a cycle lands exactly on our last time step, we will need to + // burn a batch from fresh inventory on this time step. When retired, + // this batch also needs to be discharged to spent fuel inventory. + while (fresh.count() > 0 && spent.space() >= assem_size) { + spent.Push(fresh.Pop()); + } return; } @@ -183,7 +192,9 @@ std::set::Ptr> Reactor::GetMatlRequests() { int n_assem_order = n_assem_core - core.count() + n_assem_fresh - fresh.count(); if (exit_time() != -1) { - int tleft = exit_time() - context()->time(); + // the +1 accounts for the fact that the reactor is alive and gets to + // operate during its exit_time time step. + int tleft = exit_time() - context()->time() + 1; int tleftcycle = cycle_time + refuel_time - cycle_step; double ncyclesleft = static_cast(tleft - tleftcycle) / static_cast(cycle_time + refuel_time); diff --git a/src/reactor_tests.cc b/src/reactor_tests.cc index 8c743e5cf4..d67d14d897 100644 --- a/src/reactor_tests.cc +++ b/src/reactor_tests.cc @@ -461,6 +461,52 @@ TEST(ReactorTests, RecipeChange) { EXPECT_TRUE(0 < mq.mass(id("H1"))); } +TEST(ReactorTests, Retire) { + std::string config = + " lwr_fresh " + " lwr_spent " + " enriched_u " + " waste " + "" + " 7 " + " 0 " + " 300 " + " 1 " + " 3 " + " 1 " + ""; + + int dur = 50; + int life = 36; + cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Reactor"), config, dur, life); + sim.AddSource("enriched_u").Finalize(); + sim.AddSink("waste").Finalize(); + sim.AddRecipe("lwr_fresh", c_uox()); + sim.AddRecipe("lwr_spent", c_spentuox()); + int id = sim.Run(); + + int ncore = 3; + int nbatch = 1; + + // reactor should stop requesting new fresh fuel as it approaches retirement + int nassem_recv = + static_cast(ceil(static_cast(life) / 7.0)) * nbatch + + (ncore - nbatch); + + std::vector conds; + conds.push_back(Cond("ReceiverId", "==", id)); + QueryResult qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(nassem_recv, qr.rows.size()) + << "failed to stop ordering near retirement"; + + // reactor should discharge all fuel before/by retirement + conds.clear(); + conds.push_back(Cond("SenderId", "==", id)); + qr = sim.db().Query("Transactions", &conds); + EXPECT_EQ(nassem_recv, qr.rows.size()) + << "failed to discharge all material by retirement time"; +} + } // namespace reactortests } // namespace cycamore From 74dac3d9f49918b138ce53da40cd4ab8b7aee379 Mon Sep 17 00:00:00 2001 From: Robert Carlsen Date: Thu, 21 May 2015 15:59:59 -0500 Subject: [PATCH 5/6] remove debug cout --- src/reactor.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/reactor.cc b/src/reactor.cc index 1d46ddc31d..b6bf1e9c78 100644 --- a/src/reactor.cc +++ b/src/reactor.cc @@ -109,9 +109,6 @@ void Reactor::Tick() { // they // can't go at the beginnin of the Tock is so that resource exchange has a // chance to occur after the discharge on this same time step. - std::cout << context()->time() << ": " << "fresh.count=" << fresh.count() << "\n"; - std::cout << " core.count=" << core.count() << "\n"; - std::cout << " spent.count=" << spent.count() << "\n"; if (retired()) { Record("RETIRED", ""); From 67607b970ee2c3f6f5d8e537289093eabe75c794 Mon Sep 17 00:00:00 2001 From: Robert Carlsen Date: Thu, 21 May 2015 16:12:48 -0500 Subject: [PATCH 6/6] fix var name style --- src/reactor.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/reactor.cc b/src/reactor.cc index b6bf1e9c78..e8d30ecb4d 100644 --- a/src/reactor.cc +++ b/src/reactor.cc @@ -191,13 +191,13 @@ std::set::Ptr> Reactor::GetMatlRequests() { if (exit_time() != -1) { // the +1 accounts for the fact that the reactor is alive and gets to // operate during its exit_time time step. - int tleft = exit_time() - context()->time() + 1; - int tleftcycle = cycle_time + refuel_time - cycle_step; - double ncyclesleft = static_cast(tleft - tleftcycle) / + int t_left = exit_time() - context()->time() + 1; + int t_left_cycle = cycle_time + refuel_time - cycle_step; + double n_cycles_left = static_cast(t_left - t_left_cycle) / static_cast(cycle_time + refuel_time); - ncyclesleft = ceil(ncyclesleft); - int nneed = std::max(0.0, ncyclesleft * n_assem_batch - n_assem_fresh); - n_assem_order = std::min(n_assem_order, nneed); + n_cycles_left = ceil(n_cycles_left); + int n_need = std::max(0.0, n_cycles_left * n_assem_batch - n_assem_fresh); + n_assem_order = std::min(n_assem_order, n_need); } if (n_assem_order == 0) {