Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Random sink frequency #565

Merged
merged 4 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
nuclearkatie marked this conversation as resolved.
Show resolved Hide resolved
// 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