diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index 4bcb40a..bff44a2 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -837,7 +837,8 @@ EXCLUDE_PATTERNS = # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = *_H \ + *_HPP # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include diff --git a/docs/conf.py b/docs/conf.py index b8d5362..d22851d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -72,8 +72,6 @@ exhale_args = { "doxygenStripFromPath": "..", # Suggested optional arguments "createTreeView": True, - # TIP: if using the sphinx-bootstrap-theme, you need - # "treeViewIsBootstrap": True, "exhaleExecutesDoxygen": True, "exhaleDoxygenStdin": "INPUT = ../include" } diff --git a/docs/index.rst b/docs/index.rst index 2e96ef7..da78148 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,10 @@ -Docs -==== +Kami is Agent-Based Modeling in Modern C++ +========================================== + +|Build| + +.. |Build| image:: https://github.com/k3jph/kami/actions/workflows/build.yml/badge.svg + :target: https://github.com/k3jph/kami/actions/workflows/build.yml .. toctree:: :maxdepth: 2 diff --git a/examples/boltzmann1d/boltzmann1d.cc b/examples/boltzmann1d/boltzmann1d.cc index e0253a7..cb20615 100644 --- a/examples/boltzmann1d/boltzmann1d.cc +++ b/examples/boltzmann1d/boltzmann1d.cc @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include using namespace kami; @@ -46,7 +48,8 @@ using namespace std; MultiGrid1D *MoneyAgent1D::_world = nullptr; BoltzmannWealthModel1D *MoneyAgent1D::_model = nullptr; -shared_ptr console; +shared_ptr console = nullptr; +shared_ptr rng = nullptr; template <> struct fmt::formatter : fmt::formatter { @@ -79,8 +82,8 @@ void MoneyAgent1D::move_agent() { console->trace("Entering move_agent"); auto agent_id = get_agent_id(); auto move_list = _world->get_neighborhood(agent_id, false); - auto new_location = - move_list[static_cast(rand()) % move_list.size()]; + std::uniform_int_distribution dist(0, move_list.size() - 1); + auto new_location = move_list[dist(*rng)]; console->trace("Moving Agent {} to location {}", agent_id, new_location); _world->move_agent(agent_id, new_location); @@ -97,8 +100,8 @@ void MoneyAgent1D::give_money() { vector *cell_mates = _world->get_location_contents(location); if (cell_mates->size() > 1) { - AgentID other_agent_id = cell_mates->at( - static_cast(rand()) % cell_mates->size()); + std::uniform_int_distribution dist(0, cell_mates->size() - 1); + AgentID other_agent_id = cell_mates->at(dist(*rng)); auto other_agent = _model->get_agent_by_id(other_agent_id); console->trace("Agent {} giving unit of wealth to agent {}", agent_id, @@ -111,23 +114,27 @@ void MoneyAgent1D::give_money() { BoltzmannWealthModel1D::BoltzmannWealthModel1D(unsigned int number_agents, unsigned int length_x, unsigned int new_seed) { - _world = new MultiGrid1D(length_x, true); - _sched = new RandomScheduler(this, new_seed); + rng = make_shared(); + rng->seed(new_seed); - console->debug("Scheduler initiated with seed {}", _sched->get_seed()); + _world = new MultiGrid1D(length_x, true); + _sched = new RandomScheduler(this, rng); + + console->debug("Scheduler initiated with seed {}", new_seed); _step_count = 0; MoneyAgent1D::set_world(_world); MoneyAgent1D::set_model(this); + std::uniform_int_distribution dist(0, length_x - 1); + for (unsigned int i = 0; i < number_agents; i++) { MoneyAgent1D *new_agent = new MoneyAgent1D(); _agent_list.insert(pair( new_agent->get_agent_id(), new_agent)); _sched->add_agent(new_agent->get_agent_id()); - _world->add_agent(new_agent->get_agent_id(), - GridCoord1D(rand() % static_cast(length_x))); + _world->add_agent(new_agent->get_agent_id(), GridCoord1D(dist(*rng))); } } diff --git a/examples/boltzmann2d/boltzmann2d.cc b/examples/boltzmann2d/boltzmann2d.cc index 229ce99..1a7db04 100644 --- a/examples/boltzmann2d/boltzmann2d.cc +++ b/examples/boltzmann2d/boltzmann2d.cc @@ -23,10 +23,8 @@ * SOFTWARE. */ -#include "boltzmann2d.h" - #include -#include +#include #include #include #include @@ -39,14 +37,19 @@ #include #include #include +#include +#include #include +#include "boltzmann2d.h" + using namespace kami; using namespace std; MultiGrid2D *MoneyAgent2D::_world = nullptr; BoltzmannWealthModel2D *MoneyAgent2D::_model = nullptr; -shared_ptr console; +shared_ptr console = nullptr; +shared_ptr rng = nullptr; template <> struct fmt::formatter : fmt::formatter { @@ -79,8 +82,8 @@ void MoneyAgent2D::move_agent() { console->trace("Entering move_agent"); auto agent_id = get_agent_id(); auto move_list = _world->get_neighborhood(agent_id, GridNeighborhoodType::Moore, false); - auto new_location = - move_list[static_cast(rand()) % move_list.size()]; + std::uniform_int_distribution dist(0, move_list.size() - 1); + auto new_location = move_list[dist(*rng)]; console->trace("Moving Agent {} to location {}", agent_id, new_location); _world->move_agent(agent_id, new_location); @@ -97,8 +100,8 @@ void MoneyAgent2D::give_money() { vector *cell_mates = _world->get_location_contents(location); if (cell_mates->size() > 1) { - AgentID other_agent_id = cell_mates->at( - static_cast(rand()) % cell_mates->size()); + std::uniform_int_distribution dist(0, cell_mates->size() - 1); + AgentID other_agent_id = cell_mates->at(dist(*rng)); auto other_agent = _model->get_agent_by_id(other_agent_id); console->trace("Agent {} giving unit of wealth to agent {}", agent_id, @@ -112,15 +115,21 @@ BoltzmannWealthModel2D::BoltzmannWealthModel2D(unsigned int number_agents, unsigned int length_x, unsigned int length_y, unsigned int new_seed) { - _world = new MultiGrid2D(length_x, length_y, true, true); - _sched = new RandomScheduler(this, new_seed); + rng = make_shared(); + rng->seed(new_seed); - console->debug("Scheduler initiated with seed {}", _sched->get_seed()); + _world = new MultiGrid2D(length_x, length_y, true, true); + _sched = new RandomScheduler(this, rng); + + console->debug("Scheduler initiated with seed {}", new_seed); _step_count = 0; MoneyAgent2D::set_world(_world); MoneyAgent2D::set_model(this); + std::uniform_int_distribution dist_x(0, length_x - 1); + std::uniform_int_distribution dist_y(0, length_y - 1); + for (unsigned int i = 0; i < number_agents; i++) { MoneyAgent2D *new_agent = new MoneyAgent2D(); @@ -128,8 +137,7 @@ BoltzmannWealthModel2D::BoltzmannWealthModel2D(unsigned int number_agents, new_agent->get_agent_id(), new_agent)); _sched->add_agent(new_agent->get_agent_id()); _world->add_agent(new_agent->get_agent_id(), - GridCoord2D(rand() % static_cast(length_x), - rand() % static_cast(length_y))); + GridCoord2D(dist_x(*rng), dist_x(*rng))); } } @@ -158,7 +166,7 @@ MoneyAgent2D *BoltzmannWealthModel2D::get_agent_by_id(AgentID agent_id) const { } int main(int argc, char **argv) { - std::string ident = "boltzmann1d"; + std::string ident = "boltzmann2d"; CLI::App app{ident}; string log_level_option = "info"; unsigned int x_size = 16, y_size = 16, agent_count = x_size * y_size, diff --git a/include/kami/grid.h b/include/kami/grid.h index 985852c..b7d8a7d 100644 --- a/include/kami/grid.h +++ b/include/kami/grid.h @@ -35,26 +35,27 @@ namespace kami { /** - * Neighborhood types for orthogonal grid domains of cells. + * @brief Neighborhood types for orthogonal grid domains of cells. * - * Orthogonal grid domains are those that provide cells equidistant along a - * standard Cartesian grid. `GridNeighborhoodType` allows for the distinction - * between those neighborhoods that include those cells touching on the corners - * or diagonally and those neighborhoods that do not. + * @details Orthogonal grid domains are those that provide cells equidistant + * along a standard Cartesian grid. `GridNeighborhoodType` allows for the + * distinction between those neighborhoods that include those cells touching on + * the corners or diagonally and those neighborhoods that do not. */ -enum LIBKAMI_EXPORT GridNeighborhoodType { +enum class GridNeighborhoodType { /** - * Moore neighborhood + * @brief Moore neighborhood * - * Moore neighborhood types include diagonally-adjacent cells as neighbors. + * @details Moore neighborhood types include diagonally-adjacent cells as + * neighbors. */ Moore, /** - * Von Neumann neighborhood + * @brief Von Neumann neighborhood * - * Von Neumann neighborhood types do not include diagonally-adjacent cells - * as neighbors. + * @details Von Neumann neighborhood types do not include + * diagonally-adjacent cells as neighbors. */ VonNeumann }; @@ -62,38 +63,40 @@ enum LIBKAMI_EXPORT GridNeighborhoodType { /** * @brief Distance types for orthogonal grid domains */ -enum LIBKAMI_EXPORT GridDistanceType { +enum class GridDistanceType { /** - * Euclidean distance. + * @brief Euclidean distance. * - * The Euclidean distance is the length of the line segment connecting two - * points. This is commonly called a "beeline" or "as the crow flies." + * @details The Euclidean distance is the length of the line segment + * connecting two points. This is commonly called a "beeline" or + * "as the crow flies." */ Euclidean, /** - * Manhattan distance. + * @brief Manhattan distance. * - * The Manhattan distance is the sum of the absolute value of the - * differences of the elements. This is commonly called the "taxicab - * distance," "rectilinear distance," or many other [formal + * @details The Manhattan distance is the sum of the absolute value of the + * differences of the elements. This is commonly called the + * "taxicab distance," "rectilinear distance," or many other [formal * names](https://en.wikipedia.org/wiki/Taxicab_geometry). */ Manhattan }; /** - * An abstract domain based on a gridded environment. + * @brief An abstract domain based on a gridded environment. * - * All gridded domains are expected to consist of cells in a rectilinear - * grid where the cells are equal size and laid out in an ordered fashion. + * @details All gridded domains are expected to consist of cells in a + * rectilinear grid where the cells are equal size and laid out in an ordered + * fashion. */ class LIBKAMI_EXPORT GridDomain : public Domain {}; /** - * An abstract for gridded coordinates. + * @brief An abstract for gridded coordinates. * - * All gridded coordinates are expected to subclass `GridCoord`. + * @details All gridded coordinates are expected to subclass `GridCoord`. */ class LIBKAMI_EXPORT GridCoord : public Coord {}; diff --git a/include/kami/random.h b/include/kami/random.h index 6ab44ad..02e16c8 100644 --- a/include/kami/random.h +++ b/include/kami/random.h @@ -31,70 +31,75 @@ #include #include +#include #include #include namespace kami { -/** +/* * Will execute all agent steps in a random order. - * -* A random scheduler will iterate over the agents assigned + * + * A random scheduler will iterate over the agents assigned * to the scheduler and call their `step()` function in a random order. * That order should be different for each subsequent call to `step()`, * but is not gauranteed not to repeat. -* @note First create a Model for the scheduler to live in. -*/ + * @note First create a Model for the scheduler to live in. + */ class LIBKAMI_EXPORT RandomScheduler : public SequentialScheduler { public: /** - * Constructor. + * @brief Constructor. * - * The Model parameter is used by the scheduler to get - * access to an Agent. The Model is presumed to maintain a master - * list of all Agents in the Model and the Model can be queried for - * a reference to any particular Agent at `step()` time. + * @details The `model` parameter is used by the scheduler to get + * access to an `Agent`. The `Model` is presumed to maintain a master + * list of all `Agent`s in the `Model` and the `Model` can be queried for + * a reference to any particular `Agent` at `step()` time. + * + * @param model [in] A reference to the model the scheduler is timing. */ RandomScheduler(Model *model); /** - * Constructor. - * - * The Model parameter is used by the scheduler to get - * access to an Agent. The Model is presumed to maintain a master - * list of all Agents in the Model and the Model can be queried for - * a reference to any particular Agent at `step()` time. + * @brief Constructor. + * + * @details The `model` parameter is used by the scheduler to get + * access to an `Agent`. The `Model` is presumed to maintain a master + * list of all `Agent`s in the `Model` and the `Model` can be queried for + * a reference to any particular `Agent` at `step()` time. + * + * @param model [in] A reference to the model the scheduler is timing. + * @param rng [in] A uniform random number generator of type + * `std::mt19937`, used as the source of randomness. */ - RandomScheduler(Model *model, int seed); + RandomScheduler(Model *model, std::shared_ptr rng); /** - * Execute a single time step. + * @brief Execute a single time step. * - * This method will randomize the list of Agents in the scheduler's internal - * queue and then execute the `Agent::step()` method for every Agent - * assigned to this scheduler in the randomized order. + * @details This method will randomize the list of Agents in the scheduler's + * internal queue and then execute the `Agent::step()` method for every + * Agent assigned to this scheduler in the randomized order. */ void step(); /** - * Get the seed used. + * Set the random number generator used to randomize the order of agent + * stepping. * - * Returns the seed used to initialize the random number - * generator. + * @param rng [in] A uniform random number generator of type `std::mt19937`, + * used as the source of randomness. */ - int get_seed(void) const; + void set_rng(std::shared_ptr rng); /** - * Set the seed used to initialize the random number generator. - * - * @param seed The random number seed + * Get a reference to the random number generator used to randomize + * the order of agent stepping. */ - void set_seed(int seed); + std::shared_ptr get_rng(); private: - std::random_device _rd; - std::mt19937 _rng{_rd()}; - int _original_seed; + std::shared_ptr _rng; }; }; // namespace kami diff --git a/src/libkami/random.cc b/src/libkami/random.cc index bc16287..c16a332 100644 --- a/src/libkami/random.cc +++ b/src/libkami/random.cc @@ -35,31 +35,23 @@ namespace kami { -RandomScheduler::RandomScheduler(Model *model) : SequentialScheduler(model) { - // First, let's get a seed, since we do not have one - using namespace std::chrono; +RandomScheduler::RandomScheduler(Model *model) : SequentialScheduler(model) {} - auto time_now = system_clock::now(); - auto time_seconds = time_point_cast(time_now); - auto seed = time_seconds.time_since_epoch().count(); - - this->set_seed(seed); -} - -RandomScheduler::RandomScheduler(Model *model, int seed) +RandomScheduler::RandomScheduler(Model *model, + std::shared_ptr rng) : SequentialScheduler(model) { - this->set_seed(seed); + this->set_rng(rng); } void RandomScheduler::step() { - shuffle(_agent_list.begin(), _agent_list.end(), _rng); + shuffle(_agent_list.begin(), _agent_list.end(), *_rng); this->SequentialScheduler::step(); } -void RandomScheduler::set_seed(int seed) { - this->_rng.seed(this->_original_seed = seed); +void RandomScheduler::set_rng(std::shared_ptr rng) { + this->_rng = rng; } -int RandomScheduler::get_seed(void) const { return (this->_original_seed); } +std::shared_ptr RandomScheduler::get_rng() { return (_rng); } } // namespace kami