From 2cf8b1626d45442167569fcb1b33a4ecd2f1276b Mon Sep 17 00:00:00 2001 From: Katie Mummah Date: Thu, 30 Nov 2023 09:59:23 -0700 Subject: [PATCH 1/4] randomness in sink buying frequency --- src/sink.cc | 35 ++++++++++- src/sink.h | 59 ++++++++++++++++++- src/sink_tests.cc | 145 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 234 insertions(+), 5 deletions(-) diff --git a/src/sink.cc b/src/sink.cc index d1fe861e0..85a9b3d94 100644 --- a/src/sink.cc +++ b/src/sink.cc @@ -56,6 +56,7 @@ void Sink::EnterNotify() { /// Create first requestAmt. Only used in testing, as a simulation will /// overwrite this on Tick() SetRequestAmt(); + SetNextBuyTime(); RecordPosition(); } @@ -172,7 +173,23 @@ void Sink::Tick() { using std::vector; LOG(cyclus::LEV_INFO3, "SnkFac") << prototype() << " is ticking {"; - SetRequestAmt(); + if (nextBuyTime == -1) { + std::cerr << "not randomizing at time " << context()->time() << "\n"; + SetRequestAmt(); + std::cerr << "request amt is " << requestAmt << "\n"; + } + else if (nextBuyTime == context()->time()) { + std::cerr << "buy time is now, " << context()->time() << "\n"; + SetRequestAmt(); + SetNextBuyTime(); + std::cerr << "request amt is " << requestAmt << "\n"; + std::cerr << "next buy time is " << nextBuyTime << "\n"; + } + else { + std::cerr << "next buy time is " << nextBuyTime << " and current time is " << context()->time() << ". no buying yet\n"; + requestAmt = 0; + } + LOG(cyclus::LEV_INFO3, "SnkFac") << prototype() << " has default request amount " << requestAmt; @@ -242,6 +259,22 @@ void Sink::SetRequestAmt() { return; } +void Sink::SetNextBuyTime() { + if (random_frequency_type == "None") { + nextBuyTime = -1; + } + else if (random_frequency_type == "UniformInt") { + nextBuyTime = context()->time() + context()->random_uniform_int(random_frequency_min, random_frequency_max); + } + else if (random_frequency_type == "NormalInt") { + nextBuyTime = context()->time() + context()->random_normal_int(random_frequency_mean, random_frequency_stddev, random_frequency_min, random_frequency_max); + } + else { + nextBuyTime = -1; + } + return; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - extern "C" cyclus::Agent* ConstructSink(cyclus::Context* ctx) { return new Sink(ctx); diff --git a/src/sink.h b/src/sink.h index e9e4de0d2..8de16975d 100644 --- a/src/sink.h +++ b/src/sink.h @@ -72,6 +72,9 @@ class Sink /// @brief SinkFacilities update request amount using random behavior virtual void SetRequestAmt(); + /// @brief SinkFacilities update request time using random behavior + virtual void SetNextBuyTime(); + /// add a commodity to the set of input commodities /// @param name the commodity name inline void AddCommodity(std::string name) { in_commods.push_back(name); } @@ -111,6 +114,7 @@ class Sink private: double requestAmt; + int nextBuyTime; /// all facilities must have at least one input commodity #pragma cyclus var {"tooltip": "input commodities", \ "doc": "commodities that the sink facility accepts", \ @@ -195,8 +199,61 @@ class Sink "space to use as the standard deviation. Default 0.1"} double random_size_stddev; - // random status (frequencing/timing of request) + #pragma cyclus var {"default": "None", \ + "tooltip": "type of random behavior when setting the " \ + "timing of the request", \ + "uitype": "combobox", \ + "uilabel": "Random Timing", \ + "categorical": ["None", "UniformInt", "NormalInt"], \ + "doc": "type of random behavior to use. Default None, " \ + "other options are, 'UniformInt', and 'NormalInt'. " \ + "When using 'UniformInt', also set "\ + "'random_frequency_min' and 'random_frequency_max'. " \ + "For 'NormalInt', set 'random_frequency_mean' and " \ + "'random_fequency_stddev', min and max values are " \ + "optional. "} + std::string random_frequency_type; + + // random frequency mean + #pragma cyclus var {"default": 1, \ + "tooltip": "mean of the random frequency", \ + "uilabel": "Random Frequency Mean", \ + "uitype": "range", \ + "range": [0.0, 1e299], \ + "doc": "When a normal distribution is used to determine the " \ + "frequency of the request, this is the mean. Default 1"} + double random_frequency_mean; + + // random frequency std dev + #pragma cyclus var {"default": 1, \ + "tooltip": "std dev of the random frequency", \ + "uilabel": "Random Frequency Std Dev", \ + "uitype": "range", \ + "range": [0.0, 1e299], \ + "doc": "When a normal distribution is used to determine the " \ + "frequency of the request, this is the standard deviation. Default 1"} + double random_frequency_stddev; + + // random frequency lower bound + #pragma cyclus var {"default": 1, \ + "tooltip": "lower bound of the random frequency", \ + "uilabel": "Random Frequency Lower Bound", \ + "uitype": "range", \ + "range": [1, 1e299], \ + "doc": "When a random distribution is used to determine the " \ + "frequency of the request, this is the lower bound. Default 1"} + int random_frequency_min; + + // random frequency upper bound + #pragma cyclus var {"default": 1e299, \ + "tooltip": "upper bound of the random frequency", \ + "uilabel": "Random Frequency Upper Bound", \ + "uitype": "range", \ + "range": [1, 1e299], \ + "doc": "When a random distribution is used to determine the " \ + "frequency of the request, this is the upper bound. Default 1e299"} + int random_frequency_max; #pragma cyclus var { \ "default": 0.0, \ diff --git a/src/sink_tests.cc b/src/sink_tests.cc index 630db4f5f..04a6c07a5 100644 --- a/src/sink_tests.cc +++ b/src/sink_tests.cc @@ -342,7 +342,7 @@ TEST_F(SinkTest, PositionInitialize2) { } -TEST_F(SinkTest, RandomUniform) { +TEST_F(SinkTest, RandomUniformSize) { using cyclus::QueryResult; using cyclus::Cond; @@ -364,7 +364,7 @@ TEST_F(SinkTest, RandomUniform) { EXPECT_NEAR(qr.GetVal("Quantity"), 9.41273, 0.0001); } -TEST_F(SinkTest, RandomNormal) { +TEST_F(SinkTest, RandomNormalSize) { using cyclus::QueryResult; using cyclus::Cond; @@ -386,7 +386,7 @@ TEST_F(SinkTest, RandomNormal) { EXPECT_NEAR(qr.GetVal("Quantity"), 9.60929, 0.0001); } -TEST_F(SinkTest, RandomNormalWithMeanSttdev) { +TEST_F(SinkTest, RandomNormalSizeWithMeanSttdev) { using cyclus::QueryResult; using cyclus::Cond; @@ -410,6 +410,145 @@ TEST_F(SinkTest, RandomNormalWithMeanSttdev) { EXPECT_NEAR(qr.GetVal("Quantity"), 1.52979, 0.0001); } +TEST_F(SinkTest, RandomUniformFreq) { + using cyclus::QueryResult; + using cyclus::Cond; + + std::string config = + " " + " commods_1" + " " + " 10" + " UniformInt " + " 2 " + " 4 "; + + int simdur = 3; + cyclus::MockSim sim(cyclus::AgentSpec + (":cycamore:Sink"), config, simdur); + sim.AddSource("commods_1").capacity(10).Finalize(); + int id = sim.Run(); + + QueryResult qr = sim.db().Query("Transactions", NULL); + EXPECT_EQ(qr.rows.size(), 1); + int trans_time = qr.GetVal("Time", 0); + EXPECT_EQ(trans_time, 2); +} + +TEST_F(SinkTest, RandomNormalFreq) { + using cyclus::QueryResult; + using cyclus::Cond; + + std::string config = + " " + " commods_1" + " " + " 10" + " NormalInt "; + + int simdur = 3; + cyclus::MockSim sim(cyclus::AgentSpec + (":cycamore:Sink"), config, simdur); + sim.AddSource("commods_1").capacity(10).Finalize(); + int id = sim.Run(); + + QueryResult qr = sim.db().Query("Transactions", NULL); + EXPECT_EQ(qr.rows.size(), 1); + int trans_time = qr.GetVal("Time", 0); + EXPECT_EQ(trans_time, 2); +} + +TEST_F(SinkTest, RandomNormalFreqWithMeanSttdev) { + using cyclus::QueryResult; + using cyclus::Cond; + + std::string config = + " " + " commods_1" + " " + " 10" + " NormalInt " + " 2 " + " 0.2 "; + + int simdur = 3; + cyclus::MockSim sim(cyclus::AgentSpec + (":cycamore:Sink"), config, simdur); + sim.AddSource("commods_1").capacity(10).Finalize(); + int id = sim.Run(); + + QueryResult qr = sim.db().Query("Transactions", NULL); + EXPECT_EQ(qr.rows.size(), 1); + int trans_time = qr.GetVal("Time", 0); + EXPECT_EQ(trans_time, 2); +} + +// Make sure that multiple random +TEST_F(SinkTest, RandomNormalFreqMultipleCycles) { + using cyclus::QueryResult; + using cyclus::Cond; + + std::string config = + " " + " commods_1" + " " + " 10" + " NormalInt " + " 4 " + " 1 "; + + int simdur = 12; + cyclus::MockSim sim(cyclus::AgentSpec + (":cycamore:Sink"), config, simdur); + sim.AddSource("commods_1").capacity(10).Finalize(); + int id = sim.Run(); + + QueryResult qr = sim.db().Query("Transactions", NULL); + EXPECT_EQ(3, qr.rows.size()); + // check multiple cycles execute at the expected time + // Buy times should occur at timestep 5, 7, and 10 + int first_trans_time = qr.GetVal("Time", 0); + EXPECT_EQ(5, first_trans_time); + int second_trans_time = qr.GetVal("Time", 1); + EXPECT_EQ(7, second_trans_time); + int third_trans_time = qr.GetVal("Time", 2); + EXPECT_EQ(10, third_trans_time); +} + +// Check that randomness can be implemented in both size of request and +// request frequency at the same time +TEST_F(SinkTest, RandomNormalSizeUniformFreq) { + using cyclus::QueryResult; + using cyclus::Cond; + + std::string config = + " " + " commods_1" + " " + " 10" + " NormalReal" + " 0.8" + " 0.2" + " UniformInt " + " 2 " + " 4 "; + + int simdur = 6; + cyclus::MockSim sim(cyclus::AgentSpec + (":cycamore:Sink"), config, simdur); + sim.AddSource("commods_1").capacity(20).Finalize(); + int id = sim.Run(); + + QueryResult tqr = sim.db().Query("Transactions", NULL); + EXPECT_EQ(2, tqr.rows.size()); + // check multiple cycles execute at the expected time + int trans_time = tqr.GetVal("Time", 0); + EXPECT_EQ(3, trans_time); + int res_id = tqr.GetVal("ResourceId", 0); + QueryResult rqr = sim.db().Query("Resources", NULL); + double quantity = rqr.GetVal("Quantity", 0); + EXPECT_NEAR(6.54143, quantity, 0.00001); +} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - cyclus::Agent* SinkConstructor(cyclus::Context* ctx) { return new cycamore::Sink(ctx); From f7d4b83ce064453dbb4f87fb9bb5f5b002243e34 Mon Sep 17 00:00:00 2001 From: Katie Mummah Date: Thu, 30 Nov 2023 10:14:07 -0700 Subject: [PATCH 2/4] logging --- src/sink.cc | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/sink.cc b/src/sink.cc index 85a9b3d94..d31a67d84 100644 --- a/src/sink.cc +++ b/src/sink.cc @@ -58,6 +58,18 @@ void Sink::EnterNotify() { SetRequestAmt(); SetNextBuyTime(); + if (random_frequency_size != "None") { + LOG(cyclus::LEV_INFO4, "SnkFac") << "Sink " << this->id() + << " is using random behavior " + << random_size_type + << " for determining request size."; + } + if (random_frequency_size != "None") { + LOG(cyclus::LEV_INFO4, "SnkFac") << "Sink " << this->id() + << " is using random behavior " + << random_frequency_type + << " for determining request frequency."; + RecordPosition(); } @@ -171,37 +183,32 @@ void Sink::AcceptGenRsrcTrades( void Sink::Tick() { using std::string; using std::vector; - LOG(cyclus::LEV_INFO3, "SnkFac") << prototype() << " is ticking {"; + LOG(cyclus::LEV_INFO3, "SnkFac") << "Sink " << this->id() << " is ticking {"; if (nextBuyTime == -1) { - std::cerr << "not randomizing at time " << context()->time() << "\n"; SetRequestAmt(); - std::cerr << "request amt is " << requestAmt << "\n"; } else if (nextBuyTime == context()->time()) { - std::cerr << "buy time is now, " << context()->time() << "\n"; SetRequestAmt(); SetNextBuyTime(); - std::cerr << "request amt is " << requestAmt << "\n"; - std::cerr << "next buy time is " << nextBuyTime << "\n"; + + LOG(cyclus::LEV_INFO4, "SnkFac") << "Sink " << this->id() + << " has reached buying time. The next buy time will be time step " << nextBuyTime; } else { - std::cerr << "next buy time is " << nextBuyTime << " and current time is " << context()->time() << ". no buying yet\n"; requestAmt = 0; } - - - LOG(cyclus::LEV_INFO3, "SnkFac") << prototype() << " has default request amount " << requestAmt; // inform the simulation about what the sink facility will be requesting if (requestAmt > cyclus::eps()) { - LOG(cyclus::LEV_INFO4, "SnkFac") << prototype() - << " has request amount " << requestAmt - << " kg of " << in_commods[0] << "."; + LOG(cyclus::LEV_INFO4, "SnkFac") << "Sink " << this->id() + << " has request amount " << requestAmt + << " kg of " << in_commods[0] << "."; for (vector::iterator commod = in_commods.begin(); commod != in_commods.end(); commod++) { - LOG(cyclus::LEV_INFO4, "SnkFac") << " will request " << requestAmt + LOG(cyclus::LEV_INFO4, "SnkFac") << "Sink " << this->id() + << " will request " << requestAmt << " kg of " << *commod << "."; cyclus::toolkit::RecordTimeSeries("demand"+*commod, this, requestAmt); From a44602b2cedb06a40424570355fd63194fb0a08d Mon Sep 17 00:00:00 2001 From: Katie Mummah Date: Thu, 30 Nov 2023 11:17:05 -0700 Subject: [PATCH 3/4] oops I actually have to change the changelog --- CHANGELOG.rst | 2 +- src/sink.cc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3e6f06817..15eee6de8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,7 +6,7 @@ cycamore Change Log **Added:** * GitHub workflows for building/testing on a PR and push to `main` (#549, #564) -* Add functionality for random behavior on the size of a sink (#550) +* Add functionality for random behavior on the size (#550) and frequency (#565) of a sink * GitHub workflow to check that the CHANGELOG has been updated (#562) **Changed:** diff --git a/src/sink.cc b/src/sink.cc index d31a67d84..05fdc3311 100644 --- a/src/sink.cc +++ b/src/sink.cc @@ -58,18 +58,18 @@ void Sink::EnterNotify() { SetRequestAmt(); SetNextBuyTime(); - if (random_frequency_size != "None") { + if (random_size_type != "None") { LOG(cyclus::LEV_INFO4, "SnkFac") << "Sink " << this->id() << " is using random behavior " << random_size_type << " for determining request size."; } - if (random_frequency_size != "None") { + if (random_frequency_type != "None") { LOG(cyclus::LEV_INFO4, "SnkFac") << "Sink " << this->id() << " is using random behavior " << random_frequency_type << " for determining request frequency."; - + } RecordPosition(); } From 1fbd9efece2f170384950645866bf2348e2eae80 Mon Sep 17 00:00:00 2001 From: Katie Mummah Date: Fri, 1 Dec 2023 08:47:08 -0700 Subject: [PATCH 4/4] more comments in testing --- src/sink_tests.cc | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/sink_tests.cc b/src/sink_tests.cc index 04a6c07a5..2d727a30f 100644 --- a/src/sink_tests.cc +++ b/src/sink_tests.cc @@ -342,6 +342,8 @@ TEST_F(SinkTest, PositionInitialize2) { } +// A random number pulled from a uniform integer distribution can be +// implemented as the request size TEST_F(SinkTest, RandomUniformSize) { using cyclus::QueryResult; using cyclus::Cond; @@ -361,9 +363,12 @@ TEST_F(SinkTest, RandomUniformSize) { QueryResult qr = sim.db().Query("Resources", NULL); EXPECT_EQ(qr.rows.size(), 1); + // Given the PRNG with default seed, the resource should have mass 9.41273 EXPECT_NEAR(qr.GetVal("Quantity"), 9.41273, 0.0001); } +// A random number pulled from a normal int distribution with default mean and +// stddev can be implemented as the request size TEST_F(SinkTest, RandomNormalSize) { using cyclus::QueryResult; using cyclus::Cond; @@ -383,9 +388,12 @@ TEST_F(SinkTest, RandomNormalSize) { QueryResult qr = sim.db().Query("Resources", NULL); EXPECT_EQ(qr.rows.size(), 1); + // Given the PRNG with default seed, the resource should have mass 9.60929 EXPECT_NEAR(qr.GetVal("Quantity"), 9.60929, 0.0001); } +// A random number pulled from a normal int distribution with user-defined mean +// and stddev can be implemented as the request size TEST_F(SinkTest, RandomNormalSizeWithMeanSttdev) { using cyclus::QueryResult; using cyclus::Cond; @@ -407,9 +415,12 @@ TEST_F(SinkTest, RandomNormalSizeWithMeanSttdev) { QueryResult qr = sim.db().Query("Resources", NULL); EXPECT_EQ(qr.rows.size(), 1); + // Given the PRNG with default seed, the resource should have mass 1.52979 EXPECT_NEAR(qr.GetVal("Quantity"), 1.52979, 0.0001); } +// A random number pulled from a uniform integer distribution can be +// implemented as the buying frequency TEST_F(SinkTest, RandomUniformFreq) { using cyclus::QueryResult; using cyclus::Cond; @@ -430,11 +441,16 @@ TEST_F(SinkTest, RandomUniformFreq) { int id = sim.Run(); QueryResult qr = sim.db().Query("Transactions", NULL); + // only one transaction has occurred EXPECT_EQ(qr.rows.size(), 1); + // Get the time from the first transaction in the database (0th entry) int trans_time = qr.GetVal("Time", 0); + // Given the PRNG with default seed , this time should be time step 2 EXPECT_EQ(trans_time, 2); } +// A random number pulled from a normal int distribution with default mean and +// stddev can be implemented as the buying frequency TEST_F(SinkTest, RandomNormalFreq) { using cyclus::QueryResult; using cyclus::Cond; @@ -453,11 +469,16 @@ TEST_F(SinkTest, RandomNormalFreq) { int id = sim.Run(); QueryResult qr = sim.db().Query("Transactions", NULL); + // only one transaction has occurred EXPECT_EQ(qr.rows.size(), 1); + // Get the time from the first transaction in the database (0th entry) int trans_time = qr.GetVal("Time", 0); + // Given the PRNG with default seed , this time should be time step 2 EXPECT_EQ(trans_time, 2); } +// A random number pulled from a normal int distribution with user-defined mean +// and stddev can be implemented as the buying frequency TEST_F(SinkTest, RandomNormalFreqWithMeanSttdev) { using cyclus::QueryResult; using cyclus::Cond; @@ -478,12 +499,15 @@ TEST_F(SinkTest, RandomNormalFreqWithMeanSttdev) { int id = sim.Run(); QueryResult qr = sim.db().Query("Transactions", NULL); + // only one transaction has occurred EXPECT_EQ(qr.rows.size(), 1); + // Get the time from the first transaction in the database (0th entry) int trans_time = qr.GetVal("Time", 0); + // Given the PRNG with default seed, this time should be time step 2 EXPECT_EQ(trans_time, 2); } -// Make sure that multiple random +// Check that multiple buying cycles set by random number execute as expected TEST_F(SinkTest, RandomNormalFreqMultipleCycles) { using cyclus::QueryResult; using cyclus::Cond; @@ -504,9 +528,12 @@ TEST_F(SinkTest, RandomNormalFreqMultipleCycles) { int id = sim.Run(); QueryResult qr = sim.db().Query("Transactions", NULL); + // three transaction should have occurred EXPECT_EQ(3, qr.rows.size()); // check multiple cycles execute at the expected time - // Buy times should occur at timestep 5, 7, and 10 + // Get the time from the first, second, and third transactions in the + // database (0th, 1st, and 2nd entry) + // Given the PRNG with default seed, buy times on time step 5, 7, and 10 int first_trans_time = qr.GetVal("Time", 0); EXPECT_EQ(5, first_trans_time); int second_trans_time = qr.GetVal("Time", 1); @@ -540,6 +567,7 @@ TEST_F(SinkTest, RandomNormalSizeUniformFreq) { int id = sim.Run(); QueryResult tqr = sim.db().Query("Transactions", NULL); + // two transactions should have occurred EXPECT_EQ(2, tqr.rows.size()); // check multiple cycles execute at the expected time int trans_time = tqr.GetVal("Time", 0);