Skip to content

Commit

Permalink
Merge pull request cyclus#565 from nuclearkatie/random_sink_frequency
Browse files Browse the repository at this point in the history
Random sink frequency
  • Loading branch information
gonuke authored Dec 1, 2023
2 parents b0fb13a + 1fbd9ef commit 9514f92
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 12 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:**
Expand Down
54 changes: 47 additions & 7 deletions src/sink.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,20 @@ void Sink::EnterNotify() {
/// Create first requestAmt. Only used in testing, as a simulation will
/// overwrite this on Tick()
SetRequestAmt();
SetNextBuyTime();

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_type != "None") {
LOG(cyclus::LEV_INFO4, "SnkFac") << "Sink " << this->id()
<< " is using random behavior "
<< random_frequency_type
<< " for determining request frequency.";
}
RecordPosition();
}

Expand Down Expand Up @@ -170,21 +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 {";

SetRequestAmt();
if (nextBuyTime == -1) {
SetRequestAmt();
}
else if (nextBuyTime == context()->time()) {
SetRequestAmt();
SetNextBuyTime();

LOG(cyclus::LEV_INFO3, "SnkFac") << prototype() << " has default request amount " << requestAmt;
LOG(cyclus::LEV_INFO4, "SnkFac") << "Sink " << this->id()
<< " has reached buying time. The next buy time will be time step " << nextBuyTime;
}
else {
requestAmt = 0;
}

// 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<string>::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<double>("demand"+*commod, this,
requestAmt);
Expand Down Expand Up @@ -242,6 +266,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);
Expand Down
59 changes: 58 additions & 1 deletion src/sink.h
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand Down Expand Up @@ -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", \
Expand Down Expand Up @@ -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, \
Expand Down
173 changes: 170 additions & 3 deletions src/sink_tests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,9 @@ TEST_F(SinkTest, PositionInitialize2) {

}

TEST_F(SinkTest, RandomUniform) {
// 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;

Expand All @@ -361,10 +363,13 @@ TEST_F(SinkTest, RandomUniform) {

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<double>("Quantity"), 9.41273, 0.0001);
}

TEST_F(SinkTest, RandomNormal) {
// 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;

Expand All @@ -383,10 +388,13 @@ TEST_F(SinkTest, RandomNormal) {

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<double>("Quantity"), 9.60929, 0.0001);
}

TEST_F(SinkTest, RandomNormalWithMeanSttdev) {
// 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;

Expand All @@ -407,9 +415,168 @@ TEST_F(SinkTest, RandomNormalWithMeanSttdev) {

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<double>("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;

std::string config =
" <in_commods>"
" <val>commods_1</val>"
" </in_commods>"
" <capacity>10</capacity>"
" <random_frequency_type>UniformInt</random_frequency_type> "
" <random_frequency_min>2</random_frequency_min> "
" <random_frequency_max>4</random_frequency_max> ";

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);
// 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<int>("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;

std::string config =
" <in_commods>"
" <val>commods_1</val>"
" </in_commods>"
" <capacity>10</capacity>"
" <random_frequency_type>NormalInt</random_frequency_type> ";

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);
// 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<int>("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;

std::string config =
" <in_commods>"
" <val>commods_1</val>"
" </in_commods>"
" <capacity>10</capacity>"
" <random_frequency_type>NormalInt</random_frequency_type> "
" <random_frequency_mean>2</random_frequency_mean> "
" <random_frequency_stddev>0.2</random_frequency_stddev> ";

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);
// 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<int>("Time", 0);
// Given the PRNG with default seed, this time should be time step 2
EXPECT_EQ(trans_time, 2);
}

// Check that multiple buying cycles set by random number execute as expected
TEST_F(SinkTest, RandomNormalFreqMultipleCycles) {
using cyclus::QueryResult;
using cyclus::Cond;

std::string config =
" <in_commods>"
" <val>commods_1</val>"
" </in_commods>"
" <capacity>10</capacity>"
" <random_frequency_type>NormalInt</random_frequency_type> "
" <random_frequency_mean>4</random_frequency_mean> "
" <random_frequency_stddev>1</random_frequency_stddev> ";

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);
// three transaction should have occurred
EXPECT_EQ(3, qr.rows.size());
// check multiple cycles execute at the expected time
// 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<int>("Time", 0);
EXPECT_EQ(5, first_trans_time);
int second_trans_time = qr.GetVal<int>("Time", 1);
EXPECT_EQ(7, second_trans_time);
int third_trans_time = qr.GetVal<int>("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 =
" <in_commods>"
" <val>commods_1</val>"
" </in_commods>"
" <capacity>10</capacity>"
" <random_size_type>NormalReal</random_size_type>"
" <random_size_mean>0.8</random_size_mean>"
" <random_size_stddev>0.2</random_size_stddev>"
" <random_frequency_type>UniformInt</random_frequency_type> "
" <random_frequency_min>2</random_frequency_min> "
" <random_frequency_max>4</random_frequency_max> ";

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);
// two transactions should have occurred
EXPECT_EQ(2, tqr.rows.size());
// check multiple cycles execute at the expected time
int trans_time = tqr.GetVal<int>("Time", 0);
EXPECT_EQ(3, trans_time);
int res_id = tqr.GetVal<int>("ResourceId", 0);
QueryResult rqr = sim.db().Query("Resources", NULL);
double quantity = rqr.GetVal<double>("Quantity", 0);
EXPECT_NEAR(6.54143, quantity, 0.00001);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cyclus::Agent* SinkConstructor(cyclus::Context* ctx) {
return new cycamore::Sink(ctx);
Expand Down

0 comments on commit 9514f92

Please sign in to comment.