159 Commits
0.4.1 ... main

Author SHA1 Message Date
James P. Howard, II
568a5f6f5d Merge branch 'release/0.7.2' 2023-01-22 21:45:26 -05:00
James P. Howard, II
ffe25478c1 chore(version): Bump to version 0.7.2 2023-01-22 21:45:20 -05:00
James P. Howard, II
f10bc39cce docs(sphinx): Fix version numbers in Python requirements 2023-01-22 21:41:02 -05:00
James P. Howard, II
5a0f76f7fa docs(doxygen): Automate version numbering 2023-01-22 21:34:27 -05:00
James P. Howard, II
da5347b102 docs(doxygen): Cleanup doxygen config 2023-01-22 21:26:12 -05:00
James P. Howard, II
dcbfddad13 chore(docs): Update changelog for continuous domains 2023-01-22 21:17:36 -05:00
James P. Howard, II
7e3052bac0 chore(docs): Intro note in changelog 2023-01-22 21:14:54 -05:00
James P. Howard, II
14b244586c Merge branch 'release/0.7.1' into develop 2023-01-22 21:04:50 -05:00
James P. Howard, II
eacac535ab Merge branch 'release/0.7.1' 2023-01-22 21:04:42 -05:00
James P. Howard, II
cdce9feb51 chore(version): Bump to version 0.7.1 2023-01-22 21:04:37 -05:00
James P. Howard, II
4602d87024 docs(pdf): Remove latex build 2023-01-22 21:03:21 -05:00
James P. Howard, II
8f658dc5ca Merge branch 'release/0.7.0' into develop 2023-01-22 20:49:44 -05:00
James P. Howard, II
597bb298eb Merge branch 'release/0.7.0' 2023-01-22 20:49:34 -05:00
James P. Howard, II
60018c2bce chore(version): Bump to version 0.7.0 2023-01-22 20:49:28 -05:00
James P. Howard, II
74905d04a5 style(tests): automatic reformatting 2023-01-22 20:47:29 -05:00
James P. Howard, II
adec433bce style(source): automatic reformatting 2023-01-22 20:46:34 -05:00
James P. Howard, II
431a4ac8cf style(includes): automatic reformatting 2023-01-22 20:45:46 -05:00
James P. Howard, II
e56c257707 style(examples): automatic reformatting 2023-01-22 20:44:55 -05:00
James P. Howard, II
8cf2193f7c docs(changelog): Updates to changelog for tutorial 2023-01-22 20:42:44 -05:00
James P. Howard, II
dacb7d38de Merge branch 'feature/tutorial' into develop 2023-01-22 20:37:37 -05:00
James P. Howard, II
62e80ed547 docs(tutorial): Add the long-awaited tutorial 2023-01-22 20:33:54 -05:00
James P. Howard, II
e02b339055 docs(minimal): Create a minimal example
This is even more elementary than the starter model and is for use in the tutorial
2023-01-22 19:55:37 -05:00
James P. Howard, II
3af8d8b8e0 chore(docs): We use main, not master 2023-01-21 21:56:18 -05:00
James P. Howard, II
2e212ad5c6 chore(admin): Updated copyrights for 2023 2023-01-21 21:54:07 -05:00
James P. Howard, II
426f1b740d docs(examples): Additional updates for documentation 2023-01-21 21:52:54 -05:00
James P. Howard, II
0d75e3b691 docs(examples): Added documentation for each example 2023-01-21 21:50:01 -05:00
James P. Howard, II
9d27ec927d docs: About ABMs section
Created new about ABMs section using ChatGPT
2023-01-21 20:56:50 -05:00
James P. Howard, II
91fdb5fc68 docs(constants): Fix documentation for constants 2023-01-20 10:56:36 -05:00
James P. Howard, II
c08f9509e5 docs(gridx): Remove documentation for nonexistent option 2023-01-20 10:50:51 -05:00
James P. Howard, II
2ead0b30e0 docs(reporters): Add documentation for reporters
Add documentation for reporters, related classes ReporterModel and ReporterAgent, and the associated calls in the schedulers
2023-01-20 10:49:40 -05:00
James P. Howard, II
3e88c87644 docs(exceptions): Document exceptions 2023-01-20 10:48:42 -05:00
James P. Howard, II
003c3e1be0 chore(doxygen): Remove unused options to suppress warnings 2023-01-20 10:37:52 -05:00
James P. Howard, II
f333f96bca fix(hexgrid): Removing hexgrid
Hexgrids are not currently functional, so it is being removed
2023-01-19 08:46:44 -05:00
James P. Howard, II
5d58df48b2 build(cmake): automate selection of sources files
Use globbing to get the sources files for each example and the library
2023-01-18 23:35:21 -05:00
James P. Howard, II
022f9e31aa Added update to changelog 2022-09-21 11:46:19 -04:00
James P. Howard, II
ad9dbf3eb2 Updates to citations 2022-09-21 11:45:30 -04:00
James P. Howard, II
0f1f1ad41a Generalize coordinates to location 2022-09-16 16:51:41 -04:00
James P. Howard, II
f0295e9891 Rename exceptions to follow NounAdjective convention 2022-09-16 16:49:35 -04:00
James P. Howard, II
8f7b6b65c2 Unit test revisions for exception-handling 2022-09-16 16:47:17 -04:00
James P. Howard, II
80ac9571a7 Renamed kami::exception to kami::error to avoid conflicts with std::expection 2022-09-16 16:32:13 -04:00
James P. Howard, II
7a7db768ed Remove code redundancies 2022-09-10 23:07:38 -04:00
James P. Howard, II
9eb47c8656 Move to exception-based error handling 2022-09-09 22:12:42 -04:00
James P. Howard, II
2aeef72f19 Grid distance API changes 2022-09-07 21:46:41 -04:00
James P. Howard, II
552d8fc725 Updated Reporter documentation 2022-09-07 21:46:01 -04:00
James P. Howard, II
8050ce9b8f Remove unnecessary includes from examples 2022-09-03 22:05:33 -04:00
James P. Howard, II
317509ce0b Rename x/y accessors 2022-09-03 22:01:15 -04:00
James P. Howard, II
e1028a377d Grid and domain cleanup and documentation updates 2022-09-03 21:56:32 -04:00
James P. Howard, II
860e367785 Correction to Jenny's Constant 2022-09-03 21:54:31 -04:00
James P. Howard, II
445e10252e Separate all method declarations from definitions 2022-09-03 21:54:31 -04:00
James P. Howard, II
a08b316b96 New step documentation 2022-09-01 11:39:29 -04:00
James P. Howard, II
893c8da8e2 Refactor get_neighborhood 2022-09-01 11:38:51 -04:00
James P. Howard, II
10cb3cf3f3 Added env=Debug to CodeQL 2022-08-29 22:15:28 -04:00
James P. Howard, II
3ed40f4cdb Rework CodeQL build steps based on build-develop 2022-08-29 22:10:43 -04:00
James P. Howard, II
003e7a4d12 Add develop to CodeQL 2022-08-29 22:07:21 -04:00
James P. Howard, II
6751951e84 Add CodeQL analysis 2022-08-29 21:55:56 -04:00
James P. Howard, II
7def091083 Remove the hash method from the coordinate types from the documentation 2022-08-29 15:20:06 -04:00
James P. Howard, II
a872c89b78 Added CFF file 2022-08-29 11:41:40 -04:00
James P. Howard, II
b573a3f676 Add package DOI 2022-08-29 11:06:44 -04:00
James P. Howard, II
d2ccdbea73 Make CTidy happy 2022-08-28 20:14:34 -04:00
James P. Howard, II
b9b1cb12e6 Let's start a Position for generic coords 2022-08-28 18:07:29 -04:00
James P. Howard, II
948f744414 Starting hexgrids 2022-08-28 18:06:25 -04:00
James P. Howard, II
06355bdf67 Updates to the testing code 2022-08-28 17:43:36 -04:00
James P. Howard, II
a215ca7320 Reporter agent test code 2022-08-28 16:56:48 -04:00
James P. Howard, II
097024a6b8 Reworked the agent and staged agent tests 2022-08-28 16:41:47 -04:00
James P. Howard, II
a0c2a7869c Reporter testing 2022-08-28 15:41:22 -04:00
James P. Howard, II
a45b9bf22d This file is gone 2022-08-26 23:48:25 -04:00
James P. Howard, II
7849a49355 Documentation updates: changelog.rst and todo.rst 2022-08-26 23:42:04 -04:00
James P. Howard, II
f86dbd4c24 Implementation of the Bank Reserves Model, including some reworking of everything else necessary, also, a partial unit test 2022-08-26 23:32:23 -04:00
James P. Howard, II
cdc9c56765 Let's add a default step method to the Model class 2022-08-24 13:46:12 -04:00
James P. Howard, II
972e83aeeb Create distance measures for the orthogonal grids 2022-08-24 13:45:39 -04:00
James P. Howard, II
922572973d Add some useful constants 2022-08-24 13:44:53 -04:00
James P. Howard, II
8906825235 We're going back to mt19937 as ranlux24 is slow 2022-08-24 13:44:15 -04:00
James P. Howard, II
64dc35c9c4 First blush on the reporter interface. No tests and no working examples yet... 2022-08-21 19:09:15 -04:00
James P. Howard, II
6c34098b53 Add Chebyshev distance constant 2022-08-21 11:41:47 -04:00
James P. Howard, II
bc0740219f Make shared_ptrs that can be unique, unique 2022-08-20 11:45:42 -04:00
James P. Howard, II
11660de09c Merge branch 'feature/reporters' into develop 2022-08-19 21:51:08 -04:00
James P. Howard, II
3b3a7f0bca Merge branch 'release/0.6.0' into develop 2022-08-19 20:58:14 -04:00
James P. Howard, II
d4db95f925 Merge branch 'release/0.6.0' 2022-08-19 20:57:58 -04:00
James P. Howard, II
0727030bcb Bump the version to 0.6.0 2022-08-19 20:57:47 -04:00
James P. Howard, II
85aede0721 We can be a little less circumspect in the testing 2022-08-19 20:56:12 -04:00
James P. Howard, II
06e98b1f94 Add build=missing to the Github workflow for Conan 2022-08-19 20:44:42 -04:00
James P. Howard, II
8613bc29b3 Added a to do list of things that should be done 2022-08-19 20:41:11 -04:00
James P. Howard, II
67cdf9bc29 Whitespace 2022-08-19 20:28:07 -04:00
James P. Howard, II
e7a5f342bf Updates to the Starter example reflecting Model changes 2022-08-19 20:27:50 -04:00
James P. Howard, II
9333e1c2fa Schedulers tests 2022-08-19 20:27:04 -04:00
James P. Howard, II
f18e2c99da Simplify the interface to random. I really have no idea why I let you set the RNG later, anyway. 2022-08-19 20:26:29 -04:00
James P. Howard, II
d884e603cb You know, there is no reason Model has to define the step/run interface
The user can do that for themselves
2022-08-19 19:21:43 -04:00
James P. Howard, II
78aca204f4 add using namespace kami to unit tests 2022-08-19 15:45:27 -04:00
James P. Howard, II
50b147beca Draft Population unit tests 2022-08-18 22:00:37 -04:00
James P. Howard, II
3dff3afb6e Automatically use all .cc files as tests 2022-08-18 18:17:47 -04:00
James P. Howard, II
80f98bdde0 Correction to examples readme 2022-08-18 16:57:27 -04:00
James P. Howard, II
72f0074d81 Completed first generation of unit tests on the grids 2022-08-18 16:56:16 -04:00
James P. Howard, II
f6967d0ec3 Versions of all supporting packages made current 2022-08-16 20:49:54 -04:00
James P. Howard, II
318f6dfecf Move dependencies for the examples to the examples' cmakelists 2022-08-16 11:34:53 -04:00
James P. Howard, II
05995525f1 Simplify overview in documentation 2022-08-12 13:40:56 -04:00
James P. Howard, II
d1ba71b416 Merge branch 'release/0.5.1' into develop 2022-08-11 22:21:23 -04:00
James P. Howard, II
13185c1e0b Merge branch 'release/0.5.1' 2022-08-11 22:21:14 -04:00
James P. Howard, II
beb6a1d99b Bump version number and date 2022-08-11 22:20:38 -04:00
James P. Howard, II
a801720aa5 Agent reporter sketch 2022-08-11 22:17:01 -04:00
James P. Howard, II
8a1ac91fae Add start for multigrid unit tests 2022-08-11 22:15:11 -04:00
James P. Howard, II
7c6fdd0b11 Add information to the README 2022-08-11 22:11:00 -04:00
James P. Howard, II
82d96676d7 Some cleanups to cmake build process 2022-08-11 22:10:27 -04:00
James P. Howard, II
40cbaced51 Correction to documentation badge 2022-08-08 17:37:27 -04:00
James P. Howard, II
2351e6f23e Add Model to tests 2022-08-08 17:34:20 -04:00
James P. Howard, II
018379554d Fix labels in StagedAgent unit tests 2022-08-08 17:34:20 -04:00
James P. Howard, II
0f8ee56948 Merge branch 'release/0.5.0' 2022-08-08 16:18:44 -04:00
James P. Howard, II
237be8bbe8 Grid modernization 2022-08-08 09:18:56 -04:00
James P. Howard, II
e21da48f5a Change pointer cast to static 2022-08-07 14:59:06 -04:00
James P. Howard, II
e19dc7b449 Add unit testing for Model 2022-08-07 14:56:42 -04:00
James P. Howard, II
d52102615c Cleanup and align return values from Model 2022-08-05 21:43:16 -04:00
James P. Howard, II
d1b6c60ec6 Apparently under Linux, gtest needs pthreads 2022-08-04 22:31:16 -04:00
James P. Howard, II
0d00c3ec40 Fully qualify the spdlog linking in the examples 2022-08-04 22:23:22 -04:00
James P. Howard, II
25b14c8aff Add new unit tests for Agent and AgentID 2022-08-04 22:12:42 -04:00
James P. Howard, II
2d88d32bd6 Comment alignment fix 2022-08-04 22:09:39 -04:00
James P. Howard, II
0015cdbd5a Start building a unit test framework 2022-08-04 22:09:11 -04:00
James P. Howard, II
7a335feae2 Move the version information into an anonymous namespace
This effectively makes it a private member of the class.
2022-08-04 22:06:39 -04:00
James P. Howard, II
8742637ac8 Suppress documentation for include guards and additional
documentation fixes

Did this the long way. The documentation for Doxygen says
I should be able to hide those defines with a single knob.
However, I cannot make it work.  So each one is now filtered
out individually.
2022-08-04 16:57:22 -04:00
James P. Howard, II
2f6c26406a Remove warning about CMAKE_C_COMPILER 2022-08-04 13:31:54 -04:00
James P. Howard, II
cab9c86819 Documentation revisions where appropriate 2022-08-04 12:38:29 -04:00
James P. Howard, II
743c388fb3 autofy version string creation 2022-08-04 11:35:26 -04:00
James P. Howard, II
cc7cf670bb Add dummy c'stor and d'stor to the starter example 2022-08-04 10:07:44 -04:00
James P. Howard, II
c91caf723e Added starter example 2022-08-03 09:28:03 -04:00
James P. Howard, II
c25d73b56e Documentation revisions 2022-08-02 21:01:48 -04:00
James P. Howard, II
f7bf234f6a Make these new style iterators 2022-08-02 20:57:04 -04:00
James P. Howard, II
55b819ac8c Clean up model returns 2022-08-02 20:50:05 -04:00
James P. Howard, II
ec28727ed9 Clean up population returns 2022-08-02 20:25:36 -04:00
James P. Howard, II
a8c4212b94 Remove void returns from scheduler-related classes 2022-08-02 20:07:46 -04:00
James P. Howard, II
fa74943eb5 Remove void returns from agent-related classes 2022-08-02 19:51:33 -04:00
James P. Howard, II
6320f2d6da Cleanups to the staged scheduler documentation 2022-08-02 19:38:19 -04:00
James P. Howard, II
f8e26344c1 Remove an errant internal model from Agent 2022-08-02 19:36:41 -04:00
James P. Howard, II
95165f4d72 Cleanups and updates to all of the documentation 2022-08-02 19:30:57 -04:00
James P. Howard, II
3faa5b4c50 Remove "maybe unused" as that is not helping 2022-08-02 19:09:05 -04:00
James P. Howard, II
cddf317ee6 Remove internal model references in schedulers 2022-08-02 15:29:22 -04:00
James P. Howard, II
f87f9850d9 Remove internal model references from populations 2022-08-02 14:29:20 -04:00
James P. Howard, II
e82b08e8f8 Remove unused stdio.h 2022-08-02 14:26:58 -04:00
James P. Howard, II
10c42ec2fe Remove model from agent, add it to step() and advance() methods 2022-08-02 14:26:10 -04:00
James P. Howard, II
084eda63f3 Updates to population definition 2022-08-02 14:20:57 -04:00
James P. Howard, II
1c60e90514 Reformatting grid.h 2022-08-02 10:33:05 -04:00
James P. Howard, II
9791cd6d8b Make RNG ranlux24 2022-08-02 10:29:40 -04:00
James P. Howard, II
e1c83d9158 Fix the virtualization of the Domain class 2022-07-16 18:38:44 -04:00
James P. Howard, II
9bdde03463 Started rebuilding the interface around Population requirements 2022-07-09 13:48:01 -04:00
James P. Howard, II
f5ae008df5 Rearrange header files and remove using namespaces to comply with Google recommendations 2022-07-01 17:50:48 -04:00
James P. Howard, II
cf0b75c5fb Additional support for semver to query the version 2022-07-01 17:18:10 -04:00
James P. Howard, II
cf35c348ca Updates to changelog 2022-06-18 12:35:56 -04:00
James P. Howard, II
b5abb50892 Remove Travis and Gitlab CIs 2022-06-17 21:25:58 -04:00
James P. Howard, II
1d192c99f6 Remove cppcheck 2022-06-17 21:25:24 -04:00
James P. Howard, II
b9a41e08a3 Revise package copyright years 2022-06-17 21:20:36 -04:00
James P. Howard, II
52191803ef Redo versioning with neargye-semver 2022-06-17 21:13:05 -04:00
James P. Howard, II
9859000182 Make library static by default 2022-06-17 21:12:01 -04:00
James P. Howard, II
cc189f80c5 Example and test building cleanups 2022-06-17 21:09:16 -04:00
James P. Howard, II
ddf96401c5 Correct the URL in conanfile.py 2022-06-16 21:13:53 -04:00
James P. Howard, II
a5d3f8b1ad Add IDEA files to gitconfig 2022-06-16 21:12:19 -04:00
James P. Howard, II
7af8e59b96 Corrected the badge link in the README file 2021-09-25 21:20:42 -04:00
James P. Howard, II
8747b22310 Merge branch 'hotfix/0.4.2' into develop 2021-09-25 21:14:01 -04:00
James P. Howard, II
8d9b217298 Merge branch 'hotfix/0.4.2' 2021-09-25 21:13:31 -04:00
James P. Howard, II
7567ee7f36 Bump version to 0.4.2 2021-09-25 21:13:01 -04:00
James P. Howard, II
c7685f03b7 Patches to changelog 2021-09-25 21:12:35 -04:00
James P. Howard, II
715d4e0f7b Merge branch 'hotfix/0.4.1' into develop 2021-09-25 20:53:04 -04:00
James P. Howard, II
7d53af7bdf Merge branch 'release/0.4.0' into develop 2021-09-25 19:39:12 -04:00
98 changed files with 9293 additions and 2031 deletions

View File

@@ -34,7 +34,7 @@ jobs:
conan profile update settings.compiler.libcxx=libstdc++11 default
- name: Conan Install Dependencies
run: conan install -if build .
run: conan install -if build . --build=missing
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.

View File

@@ -34,7 +34,7 @@ jobs:
conan profile update settings.compiler.libcxx=libstdc++11 default
- name: Conan Install Dependencies
run: conan install -if build .
run: conan install -if build . --build=missing
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.

89
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,89 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main", "develop" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main", "develop" ]
schedule:
- cron: '38 4 * * 1'
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Debug
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'cpp' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
- name: Install Conan
id: conan
uses: turtlebrowser/get-conan@main
- name: Conan The Frogarian
run: conan frogarian
- name: Conan Profile Setup
run: |
conan config init
conan profile update settings.compiler.libcxx=libstdc++11 default
- name: Conan Install Dependencies
run: conan install -if build . --build=missing
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: cmake -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build
# Build your program with the given configuration
run: cmake --build build --config ${{env.BUILD_TYPE}}
- name: Test
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: cmake --build build --target test --config ${{env.BUILD_TYPE}}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

6
.gitignore vendored
View File

@@ -40,4 +40,8 @@ build/*
*.user
# VSCode files
.vscode
.vscode
# IDEA files
.idea
cmake-build-*

View File

@@ -1,159 +0,0 @@
stages:
- analysis
- build
- test
cppcheck:
image: ubuntu:bionic
stage: analysis
before_script:
- apt update
- apt install -y --no-install-recommends cppcheck=1.82-1 python3-pygments python-pygments
- cppcheck --version
script:
- mkdir cppcheck
#- cppcheck . -I include/ --verbose --enable=all --inconclusive --language=c++ --error-exitcode=1
- cppcheck . -I include/ --enable=all --inconclusive --xml-version=2 --force --library=windows,posix,gnu . 2> cppcheck/result.xml
- cppcheck-htmlreport --source-encoding="iso8859-1" --title="my project name" --source-dir . --report-dir=cppcheck --file=cppcheck/result.xml
artifacts:
paths:
- cppcheck/
expire_in: 1 week
.build_template: &job_definition
image: conanio/gcc7
stage: build
before_script:
- env
- sudo apt update
- sudo apt install -y python3-pip
- sudo pip3 install gcovr
- sudo chmod +x ./.ci-files/install_conan.sh
- sudo chmod +x ./.ci-files/install_cmake.sh
- sudo chmod +x ./.ci-files/run_conan_server.sh
- ./.ci-files/install_conan.sh
- ./.ci-files/install_cmake.sh
#
- ./.ci-files/run_conan_server.sh
- conan remote add local http://localhost:9300
- conan remote list
script:
- echo $USER --- $HOME
- echo Working directory $PWD
### Test that we can build the library properly
- mkdir -p build && cd build
- cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/local
- cmake --build .
- ctest
- mkdir -p artifacts/coverage
- gcovr . -r .. --html-details --html -o artifacts/coverage/index.html -e ../test/third_party
- sudo cmake --build . --target install
## Test that we can build the test application
## and the find_package method works correctly.
- rm -rf *
- cmake ../test_cmake_install/cmake -D CMAKE_PREFIX_PATH=$HOME/local
- cmake --build .
- ctest
## Build the Conan package and upload it to the local repo.
- cd ..
- conan user -p demo demo -r=local
- conan create . local/testing
- conan upload foo* -r=local -c --all
- conan search "*" -r=local
artifacts:
paths:
- artifacts/*
expire_in: 1 week
build-gcc5:
<<: *job_definition # Merge the contents of the 'job_definition' alias
image: conanio/gcc5
build-gcc6:
<<: *job_definition # Merge the contents of the 'job_definition' alias
image: conanio/gcc6
build-gcc7:
<<: *job_definition # Merge the contents of the 'job_definition' alias
image: conanio/gcc7
build-gcc8:
<<: *job_definition # Merge the contents of the 'job_definition' alias
image: conanio/gcc8
build-clang60:
<<: *job_definition
image: conanio/clang60
build-clang40:
<<: *job_definition
image: conanio/clang40
build-clang50:
<<: *job_definition
image: conanio/clang50
build-clang7:
<<: *job_definition
image: conanio/clang7
build-clang39:
<<: *job_definition
image: conanio/clang39
###############################################################################
# The test package stage tests whether the different Conan generators work
# correctly with the
###############################################################################
.test_package: &test_package_definition
image: conanio/gcc7
stage: test
dependencies:
- build-gcc5
- build-gcc6
- build-gcc7
- build-gcc8
before_script:
- env
- sudo apt update
- sudo chmod +x ./.ci-files/install_conan.sh
- sudo chmod +x ./.ci-files/install_cmake.sh
- sudo chmod +x ./.ci-files/run_conan_server.sh
- ./.ci-files/install_conan.sh
- ./.ci-files/install_cmake.sh
#
- ./.ci-files/run_conan_server.sh
- conan remote add local http://localhost:9300
- conan remote list
script:
# Create and uplaod the recipe to the repository
- conan user -p demo demo -r=local
- conan create . local/testing
- conan upload foo* -r=local -c --all
# Test the Conan cmake generator
- mkdir build && cd build
- conan install ../test_cmake_install/conan_cmake_generator --build missing
- cmake ../test_cmake_install/conan_cmake_generator
- cmake --build .
- rm -rf *
# Test the Conan cmake_paths generator
- conan install ../test_cmake_install/conan_cmake_paths_generator --build missing
- cmake ../test_cmake_install/conan_cmake_paths_generator
- cmake --build .
- rm -rf *
# Test the Conan find_package generator
- conan install ../test_cmake_install/conan_cmake_find_package_generator --build missing
- cmake ../test_cmake_install/conan_cmake_find_package_generator
- cmake --build .
- rm -rf *
test-package-gcc8:
<<: *test_package_definition # Merge the contents of the 'job_definition' alias
image: conanio/gcc8
test-package-clang60:
<<: *test_package_definition # Merge the contents of the 'job_definition' alias
image: conanio/clang60

View File

@@ -1,89 +0,0 @@
linux: &linux
os: linux
dist: xenial
language: python
python: "3.7"
services:
- docker
osx: &osx
os: osx
language: generic
matrix:
include:
- <<: *linux
env: CONAN_GCC_VERSIONS=4.9 CONAN_DOCKER_IMAGE=conanio/gcc49
- <<: *linux
env: CONAN_GCC_VERSIONS=5 CONAN_DOCKER_IMAGE=conanio/gcc5
- <<: *linux
env: CONAN_GCC_VERSIONS=6 CONAN_DOCKER_IMAGE=conanio/gcc6
- <<: *linux
env: CONAN_GCC_VERSIONS=7 CONAN_DOCKER_IMAGE=conanio/gcc7
- <<: *linux
env: CONAN_GCC_VERSIONS=8 CONAN_DOCKER_IMAGE=conanio/gcc8
- <<: *linux
env: CONAN_CLANG_VERSIONS=3.9 CONAN_DOCKER_IMAGE=conanio/clang39
- <<: *linux
env: CONAN_CLANG_VERSIONS=4.0 CONAN_DOCKER_IMAGE=conanio/clang40
- <<: *linux
env: CONAN_CLANG_VERSIONS=5.0 CONAN_DOCKER_IMAGE=conanio/clang50
- <<: *linux
env: CONAN_CLANG_VERSIONS=6.0 CONAN_DOCKER_IMAGE=conanio/clang60
- <<: *linux
env: CONAN_CLANG_VERSIONS=7.0 CONAN_DOCKER_IMAGE=conanio/clang7
- <<: *osx
osx_image: xcode7.3
env: CONAN_APPLE_CLANG_VERSIONS=7.3
- <<: *osx
osx_image: xcode8.3
env: CONAN_APPLE_CLANG_VERSIONS=8.1
- <<: *osx
osx_image: xcode9
env: CONAN_APPLE_CLANG_VERSIONS=9.0
- <<: *osx
osx_image: xcode9.4
env: CONAN_APPLE_CLANG_VERSIONS=9.1
- <<: *osx
osx_image: xcode10.1
env: CONAN_APPLE_CLANG_VERSIONS=10.0
addons:
apt:
- doxygen
install:
# Get some stuff we need for Sphinx and Conan
- pip3 install -r docs/requirements.txt
- pip3 install conan
# first we create a directory for the CMake binaries
- DEPS_DIR="${TRAVIS_BUILD_DIR}/deps"
- mkdir ${DEPS_DIR} && cd ${DEPS_DIR}
# we use wget to fetch the cmake binaries
- travis_retry wget --no-check-certificate https://github.com/Kitware/CMake/releases/download/v3.18.3/cmake-3.18.3-Linux-x86_64.tar.gz
- travis_retry wget --no-check-certificate https://github.com/Kitware/CMake/releases/download/v3.18.3/cmake-3.18.3-SHA-256.txt
- travis_retry wget --no-check-certificate https://github.com/Kitware/CMake/releases/download/v3.18.3/cmake-3.18.3-SHA-256.txt.asc
# this is optional, but useful:
# do a quick md5 check to ensure that the archive we downloaded did not get compromised, leave off GPG verification for now
- sha256sum --ignore-missing -c cmake-3.18.3-SHA-256.txt
# extract the binaries; the output here is quite lengthy,
# so we swallow it to not clutter up the travis console
- tar -xvf cmake-3.18.3-Linux-x86_64.tar.gz > /dev/null
- mv cmake-3.18.3-Linux-x86_64 cmake-install
# add both the top-level directory and the bin directory from the archive
# to the system PATH. By adding it to the front of the path we hide the
# preinstalled CMake with our own.
- PATH=${DEPS_DIR}/cmake-install:${DEPS_DIR}/cmake-install/bin:$PATH
# don't forget to switch back to the main build directory once you are done
- cd ${TRAVIS_BUILD_DIR}
before_script:
- mkdir build
- cd build
- conan install ..
- cmake ..
script: |
- cmake --build .
- ctest -C Debug
- cmake --build . --target install

25
CITATION.cff Normal file
View File

@@ -0,0 +1,25 @@
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!
cff-version: 1.2.0
title: Kami is Agent-Based Modeling in Modern C++
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
license: MIT
repository-code: "https://github.com/JHUAPL/kami"
authors:
- given-names: James Patrick
family-names: Howard
name-suffix: II
email: james.howard@jhu.edu
affiliation: Johns Hopkins Applied Physics Laboratory
orcid: 'https://orcid.org/0000-0003-4530-1547'
keywords:
- "agent-based modelling"
- research
identifiers:
- description: "This is the collection of archived snapshots of all versions of Kami"
type: doi
value: 10.5281/zenodo.6975259

View File

@@ -1,44 +1,17 @@
#
# This is the Top level CMakelists file which creates the namespace and
# organizes all sublibraries under it.
#
# The project name in this file is considered the "Namespace"
# and any libraries under it will be given a target of
#
# Namespace::library_name
#
#
# This Lists file was modified from https://github.com/forexample/package-example
#
# This file creates project 'Foo' with two library targets 'bar' and 'cat'.
# Target 'cat' depends on 'bar'. After installation this project can be found
# by 'find_package(... CONFIG)' command:
#
# find_package(foo CONFIG REQUIRED)
# target_link_libraries(... foo::bar)
#
# Note that requirements propagated automatically, for example:
# * Foo::baz linked automatically
# * <prefix>/include added to header search path
# * FOO_BAZ_DEBUG=1/FOO_BAR_DEBUG=1 added on Debug
# * FOO_BAZ_DEBUG=0/FOO_BAR_DEBUG=0 added on other configurations
cmake_minimum_required(VERSION 3.13)
####
# Set minimum version of CMake. We need 3.13 at least.
cmake_minimum_required(VERSION 3.13) # GENERATOR_IS_MULTI_CONFIG
set(PROJECT_NAME "kami")
set(KAMI_VERSION_MAJOR 0)
set(KAMI_VERSION_MINOR 4)
set(KAMI_VERSION_PATCH 1)
set(KAMI_VERSION_STRING ${KAMI_VERSION_MAJOR}.${KAMI_VERSION_MINOR}.${KAMI_VERSION_PATCH})
set(VERSION_MAJOR 0)
set(VERSION_MINOR 7)
set(VERSION_PATCH 2)
set(VERSION_STRING ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
################################################################################
# Set variables for the project. The:
# * PROJECT_NAME
# * PROJECT_VERSION
# * PROJECT_NAMESPACE should be the same as the project.
project(kami VERSION ${KAMI_VERSION_STRING}
LANGUAGES CXX)
project(${PROJECT_NAME}
VERSION ${VERSION_STRING}
LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -48,14 +21,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_BINARY_DIR}" CACHE STRING "M
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
set(PROJECT_NAMESPACE kami ) # The project namespace. Library targets
# will be referred by
# foo::bar. This value should usually be
# the same as the project.
################################################################################
find_package(spdlog)
find_package(Threads)
set(PROJECT_NAMESPACE ${PROJECT_NAME})
if(CMAKE_COMPILER_IS_GNUCC)
option(ENABLE_COVERAGE "Enable coverage reporting for gcc/clang" FALSE)
@@ -75,7 +41,6 @@ endif()
include(cmake/functions.cmake)
include(cmake/warnings.cmake)
include(cmake/cppcheck.cmake)
################################################################################
# Sub libraries.
@@ -96,7 +61,7 @@ ENDFOREACH()
################################################################################
# Examples.
#
# Each example will be built as a static or shared library and a
# Each example will be built as a static or shared binary and a
# target will be created for it.
################################################################################
@@ -126,7 +91,7 @@ include(GNUInstallDirs)
# * <prefix>/include/
set(config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated")
set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated")
# Configuration
set(version_config "${generated_dir}/${PROJECT_NAME}ConfigVersion.cmake")
@@ -159,38 +124,32 @@ configure_package_config_file(
# * <prefix>/lib/libbaz.a
# * header location after install: <prefix>/include/foo/Bar.hpp
# * headers can be included by C++ code `#include <foo/Bar.hpp>`
install(
TARGETS
${sub_modules} ${example_modules}
${COVERAGE_INSTALL_TARGET}
EXPORT
"${TARGETS_EXPORT_NAME}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
install(
TARGETS ${sub_modules} ${example_modules} ${COVERAGE_INSTALL_TARGET}
EXPORT "${TARGETS_EXPORT_NAME}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
# Config
# * <prefix>/lib/cmake/Foo/FooConfig.cmake
# * <prefix>/lib/cmake/Foo/FooConfigVersion.cmake
install(
FILES
"${project_config}" "${version_config}"
DESTINATION
"${config_install_dir}"
FILES "${project_config}" "${version_config}"
DESTINATION "${config_install_dir}"
)
# Config
# * <prefix>/lib/cmake/Foo/FooTargets.cmake
install(
EXPORT
"${TARGETS_EXPORT_NAME}"
NAMESPACE
"${namespace}"
DESTINATION
"${config_install_dir}"
EXPORT "${TARGETS_EXPORT_NAME}"
NAMESPACE "${namespace}"
DESTINATION "${config_install_dir}"
)
add_subdirectory(docs)
set(ignoreMe "${CMAKE_C_COMPILER}")

View File

@@ -1,4 +1,4 @@
Copyright (c) 2020 The Johns Hopkins University Applied Physics
Copyright (c) 2020-2023 The Johns Hopkins University Applied Physics
Laboratory LLC
Permission is hereby granted, free of charge, to any person

View File

@@ -1,11 +1,24 @@
[![Build (main)](https://github.com/JHUAPL/kami/actions/workflows/build-main.yml/badge.svg)](https://github.com/JHUAPL/kami/actions/workflows/build-main.yml)
[![Build (develop)](https://github.com/JHUAPL/kami/actions/workflows/build-develop.yml/badge.svg)](https://github.com/JHUAPL/kami/actions/workflows/build-develop.yml)
[![Documentation status](https://readthedocs.org/projects/kami/badge/?version=latest)](https://kami.readthedocs.io/en/latest/?badge=latest)
[![Release status](https://img.shields.io/github/release/JHUAPL/kami.svg)](https://kami.readthedocs.io/en/latest/?badge=latest)
[![Documentation status](https://readthedocs.org/projects/kami/badge/?version=main)](https://kami.readthedocs.io/en/main/)
[![Release status](https://img.shields.io/github/release/JHUAPL/kami.svg)](https://github.com/JHUAPL/kami/releases)
![License](https://img.shields.io/github/license/JHUAPL/kami)
[![DOI](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.6975259-success.svg)](https://doi.org/10.5281/zenodo.6975259)
# Kami is Agent-Based Modeling in Modern C++
Agent-based models (ABMs) are models for simulating the actions of
individual actors within a provided environment to understand the
behavior of the agents, most individually and collectively. ABMs
are particularly suited for addressing problems governed by nonlinear
processes or where there is a wide variety of potential responses
an individual agent may provide depending on the environment and
behavior of other agents. Because of this, ABMs have become powerful
tools in both simulation and modeling, especially in public health
and ecology, where they are also known as individual-based models.
ABMs also provide support in economic, business, robotics, and many
other fields.
## Compiling
```Bash
@@ -40,15 +53,18 @@ conan create . kami/develop
## Direct Dependencies
* [CLI11](https://github.com/CLIUtils/CLI11)
* [Google Test](https://github.com/google/googletest)
* [neargye-semver](https://github.com/Neargye/semver)
* [spdlog](https://github.com/gabime/spdlog)
CLI11 and spdlog are both used extensively in the examples and unit
tests. Neither is used directly by the Kami library.
CLI11 and spdlog are both used extensively in the examples.
Neither is used directly by the Kami library.
## Contribution guidelines
* Use [GitFlow](http://nvie.com/posts/a-successful-git-branching-model/)
* Use [Google Test](https://github.com/google/googletest)
## For more information
* James P. Howard, II <<james.howard@jhu.edu>>

View File

@@ -1,13 +0,0 @@
add_custom_target(cppcheck
#COMMAND mkdir -p coverage
#COMMAND ${CMAKE_MAKE_PROGRAM} test
#WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_command(TARGET cppcheck
COMMAND echo "=================== CPPCHECK ===================="
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/cppcheck
COMMAND cppcheck . -I include/ --enable=all --inconclusive --xml-version=2 --force --library=windows,posix,gnu . --output-file=${CMAKE_BINARY_DIR}/cppcheck/result.xml
COMMAND cppcheck-htmlreport --source-encoding="iso8859-1" --title="mmp2top" --source-dir . --report-dir=${CMAKE_BINARY_DIR}/cppcheck --file=${CMAKE_BINARY_DIR}/cppcheck/result.xml
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} # Need separate command for this line
)

View File

@@ -3,10 +3,10 @@ from conans import ConanFile, CMake
class KamiConan(ConanFile):
name = "kami"
version = "0.4.1"
version = "0.7.2"
license = "MIT"
author = "James P. Howard, II <james.howard@jhu.edu>"
url = "http://github.com/jhuapl/kami"
url = "https://github.com/jhuapl/kami"
description = "Agent-Based Modeling in Modern C++"
topics = ("agent-based modeling", "simulation", "orms")
settings = "os", "compiler", "build_type", "arch"
@@ -14,7 +14,7 @@ class KamiConan(ConanFile):
exports_sources = "*"
options = {"shared": [True, False], "fPIC": [True, False]}
default_options = {"shared": True, "fPIC": True}
default_options = {"shared": False, "fPIC": False}
def _configure_cmake(self):
@@ -44,6 +44,9 @@ class KamiConan(ConanFile):
def requirements(self):
self.requires("fmt/7.1.3")
self.requires("spdlog/1.8.5")
self.requires("cli11/1.9.1")
self.requires("fmt/9.0.0")
self.requires("spdlog/1.10.0")
self.requires("cli11/2.2.0")
self.requires("neargye-semver/0.3.0")
self.requires("gtest/cci.20210126")
self.requires("nlohmann_json/3.11.1")

View File

@@ -38,7 +38,7 @@ PROJECT_NAME = "Kami"
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 0.1.0
PROJECT_NUMBER = "@VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@"
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@@ -837,7 +837,7 @@ 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 = KAMI_SEQUENTIAL_H
EXCLUDE_SYMBOLS = *_H
# 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
@@ -1034,7 +1034,7 @@ IGNORE_PREFIX =
# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
# The default value is: YES.
GENERATE_HTML = NO
GENERATE_HTML = YES
# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
@@ -1425,17 +1425,6 @@ EXT_LINKS_IN_WINDOW = NO
FORMULA_FONTSIZE = 10
# Use the FORMULA_TRANPARENT tag to determine whether or not the images
# generated for formulas are transparent PNGs. Transparent PNGs are not
# supported properly for IE 6.0, but are supported on all modern browsers.
#
# Note that when changing this option you need to delete any form_*.png files in
# the HTML output directory before the changes have effect.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
FORMULA_TRANSPARENT = YES
# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
# http://www.mathjax.org) which uses client side Javascript for the rendering
# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
@@ -1580,166 +1569,6 @@ EXTRA_SEARCH_MAPPINGS =
GENERATE_LATEX = NO
# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: latex.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_OUTPUT = latex
# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
# invoked.
#
# Note that when enabling USE_PDFLATEX this option is only used for generating
# bitmaps for formulas in the HTML output, but not in the Makefile that is
# written to the output directory.
# The default file is: latex.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_CMD_NAME = latex
# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
# index for LaTeX.
# The default file is: makeindex.
# This tag requires that the tag GENERATE_LATEX is set to YES.
MAKEINDEX_CMD_NAME = makeindex
# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
# documents. This may be useful for small projects and may help to save some
# trees in general.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
COMPACT_LATEX = NO
# The PAPER_TYPE tag can be used to set the paper type that is used by the
# printer.
# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
# 14 inches) and executive (7.25 x 10.5 inches).
# The default value is: a4.
# This tag requires that the tag GENERATE_LATEX is set to YES.
PAPER_TYPE = letter
# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
# that should be included in the LaTeX output. The package can be specified just
# by its name or with the correct syntax as to be used with the LaTeX
# \usepackage command. To get the times font for instance you can specify :
# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
# To use the option intlimits with the amsmath package you can specify:
# EXTRA_PACKAGES=[intlimits]{amsmath}
# If left blank no extra packages will be included.
# This tag requires that the tag GENERATE_LATEX is set to YES.
EXTRA_PACKAGES =
# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
# generated LaTeX document. The header should contain everything until the first
# chapter. If it is left blank doxygen will generate a standard header. See
# section "Doxygen usage" for information on how to let doxygen write the
# default header to a separate file.
#
# Note: Only use a user-defined header if you know what you are doing! The
# following commands have a special meaning inside the header: $title,
# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
# string, for the replacement values of the other commands the user is referred
# to HTML_HEADER.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_HEADER =
# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
# generated LaTeX document. The footer should contain everything after the last
# chapter. If it is left blank doxygen will generate a standard footer. See
# LATEX_HEADER for more information on how to generate a default footer and what
# special commands can be used inside the footer.
#
# Note: Only use a user-defined footer if you know what you are doing!
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_FOOTER =
# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
# LaTeX style sheets that are included after the standard style sheets created
# by doxygen. Using this option one can overrule certain style aspects. Doxygen
# will copy the style sheet files to the output directory.
# Note: The order of the extra style sheet files is of importance (e.g. the last
# style sheet in the list overrules the setting of the previous ones in the
# list).
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_EXTRA_STYLESHEET =
# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the LATEX_OUTPUT output
# directory. Note that the files will be copied as-is; there are no commands or
# markers available.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_EXTRA_FILES =
# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
# contain links (just like the HTML output) instead of page references. This
# makes the output suitable for online browsing using a PDF viewer.
# The default value is: YES.
# This tag requires that the tag GENERATE_LATEX is set to YES.
PDF_HYPERLINKS = YES
# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
# the PDF file directly from the LaTeX files. Set this option to YES, to get a
# higher quality PDF documentation.
# The default value is: YES.
# This tag requires that the tag GENERATE_LATEX is set to YES.
USE_PDFLATEX = YES
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
# command to the generated LaTeX files. This will instruct LaTeX to keep running
# if errors occur, instead of asking the user for help. This option is also used
# when generating formulas in HTML.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_BATCHMODE = NO
# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
# index chapters (such as File Index, Compound Index, etc.) in the output.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_HIDE_INDICES = NO
# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
# code with syntax highlighting in the LaTeX output.
#
# Note that which sources are shown also depends on other settings such as
# SOURCE_BROWSER.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_SOURCE_CODE = NO
# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
# bibliography, e.g. plainnat, or ieeetr. See
# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
# The default value is: plain.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_BIB_STYLE = plain
# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
# page will contain the date and time when the page was generated. Setting this
# to NO can help when comparing the output of multiple runs.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_TIMESTAMP = NO
#---------------------------------------------------------------------------
# Configuration options related to the RTF output
#---------------------------------------------------------------------------
@@ -1751,61 +1580,6 @@ LATEX_TIMESTAMP = NO
GENERATE_RTF = NO
# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: rtf.
# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_OUTPUT = rtf
# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
# documents. This may be useful for small projects and may help to save some
# trees in general.
# The default value is: NO.
# This tag requires that the tag GENERATE_RTF is set to YES.
COMPACT_RTF = NO
# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
# contain hyperlink fields. The RTF file will contain links (just like the HTML
# output) instead of page references. This makes the output suitable for online
# browsing using Word or some other Word compatible readers that support those
# fields.
#
# Note: WordPad (write) and others do not support links.
# The default value is: NO.
# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_HYPERLINKS = NO
# Load stylesheet definitions from file. Syntax is similar to doxygen's config
# file, i.e. a series of assignments. You only have to provide replacements,
# missing definitions are set to their default value.
#
# See also section "Doxygen usage" for information on how to generate the
# default style sheet that doxygen normally uses.
# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_STYLESHEET_FILE =
# Set optional variables used in the generation of an RTF document. Syntax is
# similar to doxygen's config file. A template extensions file can be generated
# using doxygen -e rtf extensionFile.
# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_EXTENSIONS_FILE =
# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
# with syntax highlighting in the RTF output.
#
# Note that which sources are shown also depends on other settings such as
# SOURCE_BROWSER.
# The default value is: NO.
# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_SOURCE_CODE = NO
#---------------------------------------------------------------------------
# Configuration options related to the man page output
#---------------------------------------------------------------------------
@@ -1816,40 +1590,6 @@ RTF_SOURCE_CODE = NO
GENERATE_MAN = NO
# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it. A directory man3 will be created inside the directory specified by
# MAN_OUTPUT.
# The default directory is: man.
# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_OUTPUT = man
# The MAN_EXTENSION tag determines the extension that is added to the generated
# man pages. In case the manual section does not start with a number, the number
# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
# optional.
# The default value is: .3.
# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_EXTENSION = .3
# The MAN_SUBDIR tag determines the name of the directory created within
# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
# MAN_EXTENSION with the initial . removed.
# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_SUBDIR =
# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
# will generate one additional man file for each entity documented in the real
# man page(s). These additional files only source the real man page, but without
# them the man command would be unable to find the correct page.
# The default value is: NO.
# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_LINKS = NO
#---------------------------------------------------------------------------
# Configuration options related to the XML output
#---------------------------------------------------------------------------
@@ -1860,23 +1600,6 @@ MAN_LINKS = NO
GENERATE_XML = NO
# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: xml.
# This tag requires that the tag GENERATE_XML is set to YES.
XML_OUTPUT = xml
# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
# listings (including syntax highlighting and cross-referencing information) to
# the XML output. Note that enabling this will significantly increase the size
# of the XML output.
# The default value is: YES.
# This tag requires that the tag GENERATE_XML is set to YES.
XML_PROGRAMLISTING = YES
#---------------------------------------------------------------------------
# Configuration options related to the DOCBOOK output
#---------------------------------------------------------------------------
@@ -1887,23 +1610,6 @@ XML_PROGRAMLISTING = YES
GENERATE_DOCBOOK = NO
# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
# front of it.
# The default directory is: docbook.
# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
DOCBOOK_OUTPUT = docbook
# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
# program listings (including syntax highlighting and cross-referencing
# information) to the DOCBOOK output. Note that enabling this will significantly
# increase the size of the DOCBOOK output.
# The default value is: NO.
# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
DOCBOOK_PROGRAMLISTING = NO
#---------------------------------------------------------------------------
# Configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
@@ -1928,32 +1634,6 @@ GENERATE_AUTOGEN_DEF = NO
GENERATE_PERLMOD = NO
# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
# output from the Perl module output.
# The default value is: NO.
# This tag requires that the tag GENERATE_PERLMOD is set to YES.
PERLMOD_LATEX = NO
# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
# formatted so it can be parsed by a human reader. This is useful if you want to
# understand what is going on. On the other hand, if this tag is set to NO, the
# size of the Perl module output will be much smaller and Perl will parse it
# just the same.
# The default value is: YES.
# This tag requires that the tag GENERATE_PERLMOD is set to YES.
PERLMOD_PRETTY = YES
# The names of the make variables in the generated doxyrules.make file are
# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
# so different doxyrules.make files included by the same Makefile don't
# overwrite each other's variables.
# This tag requires that the tag GENERATE_PERLMOD is set to YES.
PERLMOD_MAKEVAR_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
@@ -2082,15 +1762,6 @@ EXTERNAL_PAGES = YES
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
# NO turns the diagrams off. Note that this option also works with HAVE_DOT
# disabled, but it is recommended to install and use dot, since it yields more
# powerful graphs.
# The default value is: YES.
CLASS_DIAGRAMS = YES
# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
@@ -2123,30 +1794,6 @@ HAVE_DOT = NO
DOT_NUM_THREADS = 0
# When you want a differently looking font in the dot files that doxygen
# generates you can specify the font name using DOT_FONTNAME. You need to make
# sure dot is able to find the font, which can be done by putting it in a
# standard location or by setting the DOTFONTPATH environment variable or by
# setting DOT_FONTPATH to the directory containing the font.
# The default value is: Helvetica.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTNAME = Helvetica
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
# dot graphs.
# Minimum value: 4, maximum value: 24, default value: 10.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTSIZE = 10
# By default doxygen will tell dot to use the default font as specified with
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
# the path where dot can find it using this tag.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTPATH =
# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
# each documented class showing the direct and indirect inheritance relations.
# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
@@ -2347,18 +1994,6 @@ DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 0
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
# background. This is disabled by default, because dot on Windows does not seem
# to support this out of the box.
#
# Warning: Depending on the platform used, enabling this option may lead to
# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
# read).
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_TRANSPARENT = NO
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10) support

40
docs/abm.rst Normal file
View File

@@ -0,0 +1,40 @@
About Agent-Based Models
========================
Agent-based models (ABM) are a type of computational model used
to simulate the behavior of autonomous agents within a system. These
agents can be individuals, groups, organizations, or other entities
that interact with one another and with their environment.
One of the key features of ABMs is that they focus on the micro-level
interactions between individual agents, rather than aggregating
data to study macro-level phenomena. This allows for the examination
of complex behaviors that emerge from the interactions between
agents, such as the spread of a disease or the formation of social
networks.
ABMs are often used in fields such as economics, sociology, and
biology to study the behavior of individuals and groups. They can
also be used to simulate the effects of different policies or
interventions on a system.
In order to create an ABM, the researcher must first define the
agents and their characteristics, such as their behavior, beliefs,
and goals. They must also define the rules of interaction between
the agents and their environment. Once these parameters are set,
the model can be run to simulate the behavior of the agents over
time.
ABMs are a powerful tool for understanding complex systems, but
they also have their limitations. Because they focus on micro-level
interactions, they may not accurately capture macro-level phenomena.
Additionally, they often require a significant amount of computational
resources and can be difficult to validate.
Overall, agent-based models are a valuable tool for understanding
the behavior of complex systems and the emergence of complex behaviors
from the interactions between individuals. However, it is important
to use them in conjunction with other methods to fully understand
the system being studied.
.. toctree::

View File

@@ -1,12 +1,62 @@
Changelog
=========
Below is the consolidated changelog for Kami.
- :feature:`0` Added baseline for continuous domains
- :release:`0.7.2 <2023.01.22>`
- :bug:`0` Streamlined documentation builds
- :release:`0.7.1 <2023.01.22>`
- :bug:`0` Corrected bug in documentation build
- :release:`0.7.0 <2023.01.22>`
- :support:`0` Added a minimal example and tutorial
- :support:`0` Added documentation for each example
- :feature:`0` Readded step() to the Model interface
- :feature:`0` Moved to exception-based error handling
- :feature:`0` Added Bank Reserves model to demonstrate reporting
- :feature:`0` Added data collecting and reporting modules
- :feature:`0` Added some useful constants, for use as random seeds
- :feature:`0` Switched from ranlux24 to mt19937 due to speed
- :feature:`0` Added distance measures to grid coordinate objects
- :release:`0.6.0 <2022.08.19>`
- :feature:`0` Added a to do list to the documentation
- :feature:`0` Completed basic unit tests
- :feature:`0` Removed step()/run() from Model interface
- :feature:`0` Revised interfaces to the grids
- :feature:`0` Updated all support packages to most current versions
- :release:`0.5.1 <2022.08.11>`
- :support:`0` Completed initial unit tests
- :support:`0` Added background info to READ ME
- :release:`0.5.0 <2022.08.08>`
- :feature:`0` Added a barebones "starter" model to build from
- :support:`0` Numerous documentation cleanups
- :bug:`0` Numerous code cleanups to remove void returns and eliminate stored Model references
- :feature:`0` Restructured the entire interface
- :bug:`0` Numerous build process cleanups
- :feature:`0` Added support semver versioning via neargye-semver
- :bug:`0` Make library static by default
- :bug:`0` Corrected the badge link in the README file
- :release:`0.4.2 <2021.09.25>`
- :bug:`0` Fixed Changelog to move docs to support
- :bug:`0` Fixed Changelog with current releases added
- :release:`0.4.1 <2021.09.25>`
- :bug:`0` Fixed README file links
- :release:`0.4.0 <2021.09.25>`
- :bug:`0` Cleaned up numerous issues found by CLion's linter
- :feature:`0` Added a new overview to the documents
- :feature:`0` Added basic installation instructions
- :support:`0` Added a new overview to the documents
- :support:`0` Added basic installation instructions
- :bug:`0` Retagged previous versions using pure Semantic Versioning
- :feature:`0` Documentation for `kami::RandomScheduler`
- :feature:`0` Added a changelog!
- :support:`0` Documentation for `kami::RandomScheduler`
- :support:`0` Added a changelog!
- :release:`0.3.0 <2021.09.20>`
- :feature:`0` Initial public release.

View File

@@ -1,10 +1,11 @@
import subprocess, os
import os
import subprocess
from documenteer.sphinxconfig.utils import form_ltd_edition_name
def configureDoxyfile(input_dir, output_dir):
with open('Doxyfile.in', 'r') as file :
def configureDoxyfile(input_dir, output_dir):
with open('Doxyfile.in', 'r') as file:
filedata = file.read()
filedata = filedata.replace('@DOXYGEN_INPUT_DIR@', input_dir)
@@ -26,7 +27,7 @@ if read_the_docs_build:
# -- Project information -----------------------------------------------------
project = 'Kami'
copyright = '2020-2021 The Johns Hopkins University Applied Physics Laboratory LLC'
copyright = '2020-2023 The Johns Hopkins University Applied Physics Laboratory LLC'
author = 'James P. Howard, II <james.howard@jhu.edu>'
# -- General configuration ---------------------------------------------------
@@ -48,6 +49,7 @@ extensions = [
# 'releases' (changelog) settings
releases_github_path = "JHUAPL/kami"
releases_unstable_prehistory = True
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -92,11 +94,11 @@ highlight_language = 'cpp'
# The short X.Y version.
github_ref = os.getenv("GITHUB_REF", "")
if github_ref == "":
git_ref = "master"
git_ref = "main"
else:
match = re.match(r"refs/(heads|tags|pull)/(?P<ref>.+)", github_ref)
if not match:
git_ref = "master"
git_ref = "main"
else:
git_ref = match.group("ref")
@@ -176,7 +178,7 @@ html_theme_options = {
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'kami', u'Kami: Agent-Based Modeling in Modern C++',
[author], 3)
[author], '3kami')
]
# If true, show URL addresses after external links.

View File

@@ -0,0 +1,56 @@
bankreserves
============
This example provides a two-dimensional bank reserves model (BSM)
as an example of a simple application of the reporter classes for
monitoring the internal functioning of the model.
The BSM is a type of computational model that simulates the behavior
of customers and their interactions with a bank. It is used to study
the dynamics of the money supply and the management of reserves by
the bank.
In a BSM, individuals are represented as autonomous agents that
make decisions about saving, borrowing, and repaying loans based
on their individual objectives and constraints. The bank is also
represented as an agent that maintains accounts for each individual.
The interactions between individuals and the bank are simulated
over time, and the model can be used to study the effects of different
reserve requirements policies on the creation of money, borrowing,
lending, and savings.
One of the main advantages of a BSM is that it allows for the
examination of the micro-level interactions between individuals and
the bank, which can provide a more detailed understanding of the
dynamics of the monetary system.
It is important to note that BSMs are a simplified representation
of the real world and may not capture all the nuances of the monetary
system being studied. It's also important to use this model in
conjunction with other methods to fully understand the monetary
system.
.. list-table::
:widths: 30 70
:header-rows: 1
* - Option
- Description
* - -c *agent_count*
- Set the number of agents
* - -f *output_file_name*
- Set the JSON report file
* - -l *log_level_option*
- Set the logging level
* - -n *max_steps*
- Set the number of steps to run the model
* - -s *initial_seed*
- Set the initial seed
* - -x *x_size*
- Set the number of columns
* - -y *y_size*
- Set the number of rows
* - -w *max_initial_wealth*
- Set the maximum initial agent wealth
.. toctree::

View File

@@ -0,0 +1,60 @@
boltzmann1d
===========
This example provides a one-dimensional Boltzmann wealth model (BWM)
as an example of a simple application of the one-dimensional gridded
system.
The BWM is a type of agent-based model used to study the distribution
of wealth among individuals or agents within a population. The model
is named after the physicist Ludwig Boltzmann, who first proposed
a similar model to study the distribution of energy among particles
in a gas.
In a BWM, agents are assigned a certain amount of wealth, and the
model simulates their interactions over time. These interactions
can include buying and selling goods and services, lending and
borrowing money, and inheriting wealth from other agents.
The key feature of the BWM is that it incorporates a "wealth-exchange
mechanism" which determines the probability of agents making a
wealth exchange with each other. This mechanism is often based on
the difference in wealth between agents, with wealthier agents more
likely to make exchanges with other wealthy agents.
The model can be run for a specified number of time steps, and the
resulting wealth distribution can be analyzed to study the emergence
of wealth inequality and the factors that contribute to it. The
model can also be used to study the effects of different policies
or interventions on the wealth distribution.
The BWM has been used to study a variety of different economic
systems, including capitalist, socialist, and feudal systems.
However, it is important to note that like other agent-based models,
the BWM is a simplified representation of the real world and may
not capture all the nuances of the economic system being studied.
Overall, the BWM is a useful tool for studying the distribution of
wealth and the emergence of wealth inequality in a population. It
can provide insight into the factors that contribute to wealth
inequality and the effects of different policies on the distribution
of wealth.
.. list-table::
:widths: 30 70
:header-rows: 1
* - Option
- Description
* - -c *agent_count*
- Set the number of agents
* - -l *log_level_option*
- Set the logging level
* - -n *max_steps*
- Set the number of steps to run the model
* - -s *initial_seed*
- Set the initial seed
* - -x *x_size*
- Set the number of columns
.. toctree::

View File

@@ -0,0 +1,43 @@
boltzmann2d
===========
This example provides a two-dimensional Boltzmann wealth model (BWM)
as an example of a simple application of the two-dimensional gridded
system.
The BWM is a type of agent-based model used to study the distribution
of wealth among individuals within a population. The model simulates
agents' interactions over time, such as buying and selling goods,
lending and borrowing money, and inheriting wealth. The model is
based on a "wealth-exchange mechanism" which determines the probability
of agents making a wealth exchange with each other, it is often
based on the difference in wealth between agents. The model can be
run for a specified number of time steps, and the resulting wealth
distribution can be analyzed to study the emergence of wealth
inequality and the factors that contribute to it.
For more information on BWMs, please see the boltzmann1d_
example documentation.
.. list-table::
:widths: 30 70
:header-rows: 1
* - Option
- Description
* - -c *agent_count*
- Set the number of agents
* - -l *log_level_option*
- Set the logging level
* - -n *max_steps*
- Set the number of steps to run the model
* - -s *initial_seed*
- Set the initial seed
* - -x *x_size*
- Set the number of columns
* - -y *y_size*
- Set the number of rows
.. _boltzmann1d: boltzmann1d.html
.. toctree::

21
docs/examples/index.rst Normal file
View File

@@ -0,0 +1,21 @@
Examples
========
* bankreserves_
* boltzmann1d_
* boltzmann2d_
* starter_
.. _bankreserves: bankreserves.html
.. _boltzmann1d: boltzmann1d.html
.. _boltzmann2d: boltzmann2d.html
.. _starter: starter.html
.. toctree::
:hidden:
:maxdepth: 1
bankreserves
boltzmann1d
boltzmann2d
starter

24
docs/examples/starter.rst Normal file
View File

@@ -0,0 +1,24 @@
starter
=======
This example provides a starter scaffold for beginning a new
agent-based model (ABM). The agents and models perform no real
functions in the starter and is likely to be the most minimum
functioning model.
.. list-table::
:widths: 30 70
:header-rows: 1
* - Option
- Description
* - -c *agent_count*
- Set the number of agents
* - -l *log_level_option*
- Set the logging level
* - -n *max_steps*
- Set the number of steps to run the model
* - -s *initial_seed*
- Set the initial seed
.. toctree::

View File

@@ -7,24 +7,65 @@ Introduction
.. image:: https://github.com/JHUAPL/kami/actions/workflows/build-develop.yml/badge.svg?branch=develop
:target: https://github.com/JHUAPL/kami/actions/workflows/build-develop.yml
:alt: Build Status (develop)
.. image:: https://readthedocs.org/projects/kami/badge/?version=latest
:target: https://kami.readthedocs.io/en/latest/?badge=latest
.. image:: https://readthedocs.org/projects/kami/badge/?version=main
:target: https://kami.readthedocs.io/en/main/
:alt: Documentation Status
.. image:: https://img.shields.io/github/release/JHUAPL/kami.svg
:target: https://github.com/JHUAPL/kami/releases
:alt: Release Status
.. image:: https://img.shields.io/github/license/JHUAPL/kami
:alt: License Information
.. image:: https://img.shields.io/badge/DOI-10.5281%2Fzenodo.6975259-success.svg
:target: https://doi.org/10.5281/zenodo.6975259
:alt: Package DOI
Kami is Agent-Based Modeling in Modern C++.
Overview
========
Agent-based models (ABMs) are models for simulating the actions of
individual actors within a provided environment to understand the
behavior of the agents, most individually and collectively. ABMs
are particularly suited for addressing problems governed by nonlinear
processes or where there is a wide variety of potential responses
an individual agent may provide depending on the environment and
behavior of other agents. Because of this, ABMs have become powerful
tools in both simulation and modeling, especially in public health
and ecology, where they are also known as individual-based models.
ABMs also provide support in economic, business, robotics, and many
other fields.
Design Objectives
-----------------
Kami provides agent-based modeling modern C++. The objectives in
writing Kami are that it be lightweight, memory-efficient, and fast.
It should be possible to develop a simple working model in under
one hour of C++ development time. Accordingly, the platform is
modeled on the Mesa_ library in Python, which itself was inspired
by the MASON_ library in Java.
Many ABM platforms are designed around interaction and real time
observation of the agent dynamics. Kami does not provide a
visualization interface. Instead, Kami is meant to be used for
ABMs requiring many runs with different starting conditions.
Accordingly, Kami is single-threaded and multiple cores should be
taken advantage of through multiple parallel runs of the supervising
model.
.. _MASON: https://cs.gmu.edu/~eclab/projects/mason/
.. _Mesa: https://mesa.readthedocs.io
.. toctree::
:hidden:
:maxdepth: 2
overview
installation
abm
tutorial
api/library_root
examples/index
changelog
todo
license

View File

@@ -2,18 +2,23 @@
## Requirements
The core of Kami, `libkami`, has no requirements beyond a modern C++ compiler. However, both the examples provided and
the unit tests provided rely on three additional C++ packages:
The core of Kami, `libkami`, has no requirements beyond a modern C++ compiler and `neargye-semver/0.3.0`. However, both the examples provided and
the unit tests provided rely on three additional C++ packages. The full list is:
* cli11/1.9.1
* gtest/cci.20210126
* neargye-semver/0.3.0"
* spdlog/1.8.5
* fmt/7.1.3
[`CLI11`](https://cliutils.github.io/CLI11/) provides a command line interface for each of the utilities that makeup the
examples and test suite. [`spdlog`](https://github.com/gabime/spdlog)
provides a uniform output interface. Coupled with a command line option to set the output level, `spdlog` allows the
unit tests and example programs to provide variable output levels depending on the users needs.
Finally, [`fmt`](https://fmt.dev/) is required by
[`Google Test`](https://github.com/google/googletest) provides a
unit testing framework. [`CLI11`](https://cliutils.github.io/CLI11/)
provides a command line interface for each of the utilities that
makeup the examples. [`spdlog`](https://github.com/gabime/spdlog)
provides a uniform output interface. Coupled with a command line
option to set the output level, `spdlog` allows the unit tests and
example programs to provide variable output levels depending on the
users needs. Finally, [`fmt`](https://fmt.dev/) is required by
`spdlog` for simple and easy string formatting.
## Compiling

View File

@@ -1,7 +1,7 @@
License
=======
Copyright (c) 2020 The Johns Hopkins University Applied Physics
Copyright (c) 2020-2023 The Johns Hopkins University Applied Physics
Laboratory LLC
Permission is hereby granted, free of charge, to any person

View File

@@ -1,37 +0,0 @@
Overview
========
Agent-based models (ABMs) are models for simulating the actions of
individual actors within a provided environment to understand the
behavior of the agents, most individually and collectively. ABMs
are particularly suited for addressing problems governed by nonlinear
processes or where there is a wide variety of potential responses
an individual agent may provide depending on the environment and
behavior of other agents. Because of this, ABMs have become powerful
tools in both simulation and modeling, especially in public health
and ecology, where they are also known as individual-based models.
ABMs also provide support in economic, business, robotics, and many
other fields.
Design Objectives
-----------------
Kami provides agent-based modeling modern C++. The objectives in
writing Kami are that it be lightweight, memory-efficient, and fast.
It should be possible to develop a simple working model in under
one hour of C++ development time. Accordingly, the platform is
modeled on the Mesa_ library in Python, which itself was inspired
by the MASON_ library in Java.
Many ABM platforms are designed around interaction and real time
observation of the agent dynamics. Kami does not provide a
visualization interface. Instead, Kami is meant to be used for
ABMs requiring many runs with different starting conditions.
Accordingly, Kami is single-threaded and multiple cores should be
taken advantage of through multiple parallel runs of the supervising
model.
.. _MASON: https://cs.gmu.edu/~eclab/projects/mason/
.. _Mesa: https://mesa.readthedocs.io
.. toctree::

View File

@@ -1,8 +1,8 @@
breathe
sphinx
exhale
documenteer
myst-parser
sphinx_bootstrap_theme
sphinx_rtd_theme
releases
breathe==4.34.0
Sphinx==4.5.0
exhale==0.3.6
documenteer==0.7.0
myst-parser==0.18.1
sphinx-bootstrap-theme==0.8.1
sphinx-rtd-theme==1.1.1
releases==1.6.3

23
docs/todo.rst Normal file
View File

@@ -0,0 +1,23 @@
To Do List
==========
Must Dos
--------
The list below is a list of things considered necessary before
a 1.0 release. This list is *not* static.
- Network domain
- Hexgrid domain
- Continuous grid domain
Wishlist
--------
The list below is a list of things considered nice to have at
any point.
- Revise unit tests to take advantage of fixtures
- Network Boltzmann model example
- Additional examples as appropriate
- Globe domain, on sphere, with latitude and longitude
.. toctree::

View File

@@ -1,55 +1,108 @@
Tutorial
========
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer
varius est at dignissim sodales. Nullam mauris velit, imperdiet sit
amet neque nec, fringilla consectetur odio. In non erat varius,
fringilla felis ut, sodales orci. Aliquam in turpis ultricies enim
accumsan commodo. Duis at dolor quis dolor tristique suscipit eget
at magna. Integer non eros vitae ipsum pellentesque pharetra ac sed
sapien. Duis justo diam, bibendum ut ullamcorper ac, viverra sit
amet risus. Curabitur blandit nisl ac posuere fermentum. Nulla
convallis purus id velit pellentesque tempus. Pellentesque euismod
augue non diam eleifend fermentum. Vestibulum ante ipsum primis in
faucibus orci luctus et ultrices posuere cubilia curae;
Kami's interface is heavily influenced by Mesa_'s interface. However,
by being written in C++, Kami runs substantially faster. This
allows for faster runs and more runs within a fixed amount of time.
The advantage here is that an agent-based model (ABM) built on the
Kami platform is better suited for statistical and Monte Carlo
approaches to modelling.
Nulla iaculis orci neque, a rhoncus mi vestibulum vitae. Nam ut
gravida magna. Nam vel dignissim lacus, id accumsan orci. Nullam
cursus, dui nec finibus sagittis, nisi purus feugiat tortor, a
aliquet quam metus eget enim. Cras et quam vitae nisi auctor varius
eget vel lacus. In nibh orci, tempus eu odio et, euismod sodales
nulla. Fusce luctus sit amet orci non interdum. Ut cursus volutpat
feugiat. Nulla vitae ultricies augue. Donec orci dolor, convallis
non tincidunt sit amet, consectetur ut nibh. Cras efficitur dictum
eros, faucibus pretium odio rutrum at.
Model Form
----------
Phasellus lobortis ex nec felis iaculis tincidunt. Sed consequat
sagittis urna at lobortis. Cras velit lorem, iaculis non felis et,
sodales tempus erat. Mauris in ultricies metus. Ut bibendum nisl
vel lectus consequat, vel pharetra est ultrices. Aliquam non lobortis
massa. Mauris euismod turpis mi, eu tempor lectus molestie in. Donec
auctor ante sed eros scelerisque volutpat. Morbi semper diam vitae
ante feugiat, eu hendrerit felis aliquet. Sed placerat velit sit
amet odio suscipit, a posuere lectus hendrerit. Nulla felis augue,
cursus a tempus vitae, ullamcorper a ante. Aenean et elit mi.
Suspendisse potenti. Mauris ac enim libero. Donec finibus id enim
ut ullamcorper. Suspendisse eu imperdiet tellus.
Kami-based models have five key components:
Cras commodo vitae massa ac blandit. Donec ut mauris at lectus
congue euismod in eleifend felis. Mauris id sapien orci. Cras ac
enim et lectus fringilla vestibulum. Aliquam varius est mattis
condimentum finibus. Nunc tristique justo nec nunc mollis, sit amet
tempor neque iaculis. Class aptent taciti sociosqu ad litora torquent
per conubia nostra, per inceptos himenaeos. Vestibulum ante ipsum
primis in faucibus orci luctus et ultrices posuere cubilia curae;
In commodo molestie porttitor. Duis blandit ligula a purus bibendum
volutpat id in metus. Cras bibendum vel ex in accumsan. Phasellus
congue ex eu scelerisque consectetur.
1. Agents, which are objects representing the actors within the model
2. Populations, which are collections of Agents
3. Domains, which provide a representation of "physical" space the Agent inhabits
4. Schedulers, which provide a representation of "time" within the model
5. Model, which are objects connecting Populations, Domains, and Schedulers
Maecenas pellentesque eget quam ac pellentesque. Morbi id tempus
urna. In accumsan molestie neque nec imperdiet. Nam ultricies lacinia
magna. Nullam dictum, massa ac fermentum rhoncus, lacus eros
pellentesque ante, sed sollicitudin eros est in dui. Interdum et
malesuada fames ac ante ipsum primis in faucibus. Integer porttitor,
ante id bibendum volutpat, mi nunc mollis eros, sed auctor turpis
mi et sem.
In general, a model should have one scheduler, one domain, and some
number of agents. However, it would not be impossible to have more
than one scheduler or more than one domain. Because this is
implemented in C++, your agents should subclass Agent and your model
should subclass model. The schedulers and domains are sufficient
as is for their purposes though custom schedulers and domains are
not unreasonable.
A Minimal ABM
-------------
The minimal ABM starts with the simplest possible agent. Here, we
create a class called ``MinimalAgent``:
.. code-block:: c++
:linenos:
class MinimalAgent : public kami::Agent {
public:
kami::AgentID step(std::shared_ptr<kami::Model> model) override {
return this->get_agent_id();
}
};
An ``Agent``, and its subclasses, will automatically inherit an ``AgentID``,
which is the unique identifier for the session. The only explicit
requirement on the ``Agent`` subclass is a `step()` method that accepts
a ``shared_ptr`` to a ``Model`` and it must return the ``Agent``'s ``AgentID``.
Obviously, an ``Agent`` should do something useful before returning.
The second component is ``MinimalModel:``
.. code-block:: c++
:linenos:
class MinimalModel: public kami::Model {
public:
MinimalModel() {
auto sched = std::make_shared<kami::SequentialScheduler>();
set_scheduler(sched);
auto pop = std::make_shared<kami::Population>();
set_population(pop);
for (auto i = 0; i < 10; i++) {
auto new_agent = std::make_shared<MinimalAgent>();
pop->add_agent(new_agent);
}
}
};
The ``MinimalModel`` performs some important tasks that important to do
during the setup or soon thereafter. In the constructor, first, a
scheduler is created. The ``SequentialScheduler`` is the simplest
scheduler and has no configuration needed. Using `set_scheduler()`,
part of the Model class, the scheduler is associated with this
model. Second, a `Population` is created and associated with this
model with the `set_population()` method.
After this, the constructor initializes 10 ``MinimalAgents`` and adds
them to the population.
.. code-block:: c++
:linenos:
int main() {
auto model = std::make_shared<MinimalModel>();
for (int i = 0; i < 10; i++)
model->step();
return 0;
}
The last part is our `main()` function. It creates the `MinimalModel`
then executes its `step()` method 10 times. The `step()` method, by
default, calls the `step()` method of the scheduler. In the case of
the ``SequentialScheduler``, it loops over all the ``Agent`` instances in the
``Population`` and executes the associated `step()` method of each ``Agent``.
That is it. It is the simplest minimal model that can be created
using the Kami platform. However, for a basis, it is likely better
to use the starter model, included in the examples directory.
.. _Mesa: https://mesa.readthedocs.io
.. toctree::

View File

@@ -3,5 +3,5 @@
Each folder in here is automatically traversed by the root level cmake list file.
1. Copy one of the existing folders to a new name.
2. The name of the folder will be the name of the library
2. The name of the folder will be the name of the example
3. Change the source file list.

View File

@@ -0,0 +1,22 @@
####
# Set minimum version of CMake.
cmake_minimum_required(VERSION 3.13)
find_package(spdlog)
set(EXAMPLE_NAME "bankreserves")
project(${EXAMPLE_NAME} LANGUAGES CXX)
file(GLOB EXAMPLE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cc")
create_executable(
NAME ${EXAMPLE_NAME}
SOURCES ${EXAMPLE_SOURCES}
PUBLIC_DEFINITIONS USE_DOUBLE_PRECISION=1
PRIVATE_DEFINITIONS DEBUG_VERBOSE
PRIVATE_INCLUDE_PATHS ${CMAKE_SOURCE_DIR}/include
PUBLIC_LINKED_TARGETS fmt spdlog::spdlog kami::libkami
)
set_target_properties(${EXAMPLE_NAME} PROPERTIES VERSION ${VERSION_STRING})

View File

@@ -0,0 +1,106 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "bankreserves.h"
#include <fstream>
#include <list>
#include <map>
#include <memory>
#include <random>
#include <CLI/CLI.hpp>
#include <nlohmann/json.hpp>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <spdlog/stopwatch.h>
#include <kami/agent.h>
#include <kami/multigrid2d.h>
#include <kami/kami.h>
#include <kami/model.h>
#include <kami/population.h>
#include <kami/random.h>
#include <kami/reporter.h>
std::shared_ptr<spdlog::logger> console = nullptr;
std::shared_ptr<std::mt19937> rng = nullptr;
#pragma clang diagnostic push
#pragma ide diagnostic ignored "EmptyDeclOrStmt"
int main(
int argc,
char** argv
) {
std::string ident = "bankreserves";
std::string log_level_option = "info";
std::string output_file_name = ident + ".json";
CLI::App app{ident};
unsigned int
initial_seed = kami::Constants::JENNYS_NUMBER,
max_steps = 100,
x_size = 20,
y_size = 20,
agent_count = x_size * y_size,
max_initial_wealth = 10;
// This exercise is really stupid.
auto levels_list = std::make_unique<std::list<std::string>>();
for (auto& level_name : SPDLOG_LEVEL_NAMES)
levels_list->push_back(std::string(level_name.data(), level_name.size()));
app.add_option("-c", agent_count, "Set the number of agents")->check(CLI::PositiveNumber);
app.add_option("-f", output_file_name, "Set the JSON report file")->check(CLI::ExistingPath);
app.add_option("-l", log_level_option, "Set the logging level")->check(
CLI::IsMember(levels_list.get(), CLI::ignore_case));
app.add_option("-n", max_steps, "Set the number of steps to run the model")->check(CLI::PositiveNumber);
app.add_option("-s", initial_seed, "Set the initial seed")->check(CLI::Number);
app.add_option("-x", x_size, "Set the number of columns")->check(CLI::PositiveNumber);
app.add_option("-y", y_size, "Set the number of rows")->check(CLI::PositiveNumber);
app.add_option("-w", max_initial_wealth, "Set the maximum initial agent wealth")->check(CLI::PositiveNumber);
CLI11_PARSE(app, argc, argv);
console = spdlog::stdout_color_st(ident);
console->set_level(spdlog::level::from_str(log_level_option));
console->info("Compiled with Kami/{}, log level {}", kami::version.to_string(), log_level_option);
console->info("Starting Bank Reserves Model with {} agents for {} steps", agent_count, max_steps);
auto model = std::make_shared<BankReservesModel>(agent_count, x_size, y_size, initial_seed, max_initial_wealth);
spdlog::stopwatch sw;
for (int i = 0; i < max_steps; i++)
model->step();
console->info("Bank Reserves Model simulation complete, requiring {} seconds", sw);
auto rpt = model->report();
std::ofstream output_file(output_file_name);
output_file << *rpt;
console->info("JSON data report written to {}", output_file_name);
console->trace("Done.");
}
#pragma clang diagnostic pop

View File

@@ -0,0 +1,195 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#ifndef BANKRESERVES_H
//! @cond SuppressGuard
#define BANKRESERVES_H
//! @endcond
#include <iostream>
#include <map>
#include <nlohmann/json.hpp>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <spdlog/stopwatch.h>
#include <kami/agent.h>
#include <kami/multigrid2d.h>
#include <kami/kami.h>
#include <kami/random.h>
#include <kami/reporter.h>
extern std::shared_ptr<spdlog::logger> console;
extern std::shared_ptr<std::mt19937> rng;
template<>
struct fmt::formatter<kami::AgentID>
: fmt::formatter<std::string> {
static auto format(
kami::AgentID agent_id,
format_context& ctx
) {
return format_to(ctx.out(), "{}", agent_id.to_string());
}
};
template<>
struct fmt::formatter<kami::GridCoord2D>
: fmt::formatter<std::string> {
static auto format(
const kami::GridCoord2D& coord,
format_context& ctx
) {
return format_to(ctx.out(), "{}", coord.to_string());
}
};
/**
* A starter agent for a starter model
*/
class BankAgent
: public kami::ReporterAgent {
public:
/**
* Constructor
*/
explicit BankAgent(int reserve_percent)
:_reserve_percent(reserve_percent) {
};
inline std::unique_ptr<nlohmann::json> collect() override {
auto ret = std::make_unique<nlohmann::json>();
(*ret)["reserves"] = _reserves;
(*ret)["available"] = _available_to_loan;
return std::move(ret);
}
inline kami::AgentID step(std::shared_ptr<kami::ReporterModel> model) override {
return get_agent_id();
};
int bank_balance() {
_reserves = (_reserve_percent / 100) * _deposits;
return _available_to_loan = _deposits - (_reserves + _bank_loans);
}
private:
double _bank_loans = 0;
double _reserve_percent = 0;
double _deposits = 0;
double _reserves = (_reserve_percent / 100) * _deposits;
double _available_to_loan = 0;
friend class PersonAgent;
};
class PersonAgent
: public kami::ReporterAgent {
public:
/**
* Constructor
*/
explicit PersonAgent(
int wallet,
std::shared_ptr<BankAgent>& bank
)
:
_wallet(wallet), _bank(bank) {
};
inline std::unique_ptr<nlohmann::json> collect() override {
auto ret = std::make_unique<nlohmann::json>();
(*ret)["savings"] = _savings;
(*ret)["loans"] = _loans;
(*ret)["wallet"] = _wallet;
(*ret)["wealth"] = _wealth;
return std::move(ret);
}
/**
* Execute a single time-step for the agent
*/
kami::AgentID step(std::shared_ptr<kami::ReporterModel> model) override;
private:
int _step_counter = 0;
double _savings = 0;
double _loans = 0;
double _wallet = 0;
double _wealth = 0;
std::shared_ptr<BankAgent> _bank;
/**
* Move the agent to a random location on the world
*/
std::optional<kami::GridCoord2D> move_agent(std::shared_ptr<kami::ReporterModel>& model);
std::optional<kami::AgentID> do_business(std::shared_ptr<kami::ReporterModel>& model);
std::optional<int> balance_books(std::shared_ptr<kami::ReporterModel>& model);
kami::AgentID deposit_to_savings(double amount);
kami::AgentID withdraw_from_savings(double amount);
kami::AgentID repay_a_loan(double amount);
kami::AgentID take_out_loan(double amount);
};
/**
* The one-dimensional Boltzmann wealth model
*/
class BankReservesModel
: public kami::ReporterModel {
public:
/**
* Create an instance of the one-dimensional Boltzmann wealth model.
*
* @param[in] number_agents the number of agents to assign to the model.
* @param[in] length_x the length of the one-dimensional world the agents
* occupy.
*/
explicit BankReservesModel(
unsigned int agent_count,
unsigned int x_size,
unsigned int y_size,
unsigned int initial_seed,
unsigned int max_initial_wealth
);
inline std::unique_ptr<nlohmann::json> collect() override {
return nullptr;
}
};
#endif // BANKRESERVES_H

View File

@@ -0,0 +1,76 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "bankreserves.h"
#include <nlohmann/json.hpp>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <spdlog/stopwatch.h>
#include <kami/agent.h>
#include <kami/multigrid2d.h>
#include <kami/kami.h>
#include <kami/random.h>
#include <kami/reporter.h>
BankReservesModel::BankReservesModel(
unsigned int agent_count,
unsigned int x_size,
unsigned int y_size,
unsigned int initial_seed,
unsigned int max_initial_wealth
) {
rng = std::make_shared<std::mt19937>();
rng->seed(initial_seed);
auto domain = std::make_shared<kami::MultiGrid2D>(x_size, y_size, true, true);
auto sched = std::make_shared<kami::RandomScheduler>(rng);
auto pop = std::make_shared<kami::Population>();
std::static_pointer_cast<void>(set_scheduler(sched));
std::static_pointer_cast<void>(set_domain(domain));
std::static_pointer_cast<void>(set_population(pop));
console->debug("Scheduler initiated with seed {}", initial_seed);
auto bank_agent = std::make_shared<BankAgent>(10);
pop->add_agent(bank_agent);
_step_count = 0;
std::uniform_int_distribution<int> dist_x(0, (int) x_size - 1);
std::uniform_int_distribution<int> dist_y(0, (int) y_size - 1);
std::uniform_int_distribution<unsigned int> dist(1, max_initial_wealth);
for (auto i = 0; i < agent_count; i++) {
auto wallet = dist(*rng);
auto new_agent = std::make_shared<PersonAgent>(10, bank_agent);
pop->add_agent(new_agent);
domain->add_agent(new_agent->get_agent_id(), kami::GridCoord2D(dist_x(*rng), dist_x(*rng)));
console->trace("Initialized agent with AgentID {} with wallet {}", new_agent->get_agent_id(), wallet);
}
}

View File

@@ -0,0 +1,183 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "bankreserves.h"
#include <nlohmann/json.hpp>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <spdlog/stopwatch.h>
#include <kami/agent.h>
#include <kami/multigrid2d.h>
#include <kami/kami.h>
#include <kami/random.h>
#include <kami/reporter.h>
kami::AgentID PersonAgent::step(std::shared_ptr<kami::ReporterModel> model) {
console->trace("step() called for agent {}", get_agent_id());
move_agent(model);
do_business(model);
balance_books(model);
_bank->bank_balance();
return get_agent_id();
}
std::optional<kami::GridCoord2D> PersonAgent::move_agent(std::shared_ptr<kami::ReporterModel>& model) {
console->trace("move_agent() called for agent {}", get_agent_id());
auto agent_id = get_agent_id();
auto domain = model->get_domain();
auto world = std::static_pointer_cast<kami::MultiGrid2D>(domain);
auto move_list = world->get_neighborhood(agent_id, false, kami::GridNeighborhoodType::Moore);
std::uniform_int_distribution<int> dist(0, (int) move_list->size() - 1);
auto new_location = *std::next(move_list->begin(), dist(*rng));
console->trace("Moving Agent {} to location {}", agent_id, new_location);
world->move_agent(agent_id, new_location);
return new_location;
}
std::optional<kami::AgentID> PersonAgent::do_business(std::shared_ptr<kami::ReporterModel>& model) {
console->trace("do_business() called for agent {}", get_agent_id());
auto agent_id = get_agent_id();
if (!(_savings > 0 | _wallet > 0 | _bank->_available_to_loan > 0))
return agent_id;
auto world = std::static_pointer_cast<kami::MultiGrid2D>(model->get_domain());
auto population = model->get_population();
auto location = world->get_location_by_agent(agent_id);
auto cell_mates_opt = world->get_location_contents(location);
if (!cell_mates_opt)
return std::nullopt;
// Note, here we reverse the logic from that used in the Mesa
// implementation. We prefer the guard clause to the nested
// if statements. See Fowler.
auto cell_mates = cell_mates_opt;
if (cell_mates->size() < 2)
return std::nullopt;
auto customer_id = agent_id;
std::uniform_int_distribution<int> dist(0, (int) cell_mates->size() - 1);
do { // There aren't enough do loops, ya know?
customer_id = *std::next(cell_mates->begin(), dist(*rng));
} while (customer_id == agent_id);
std::bernoulli_distribution coin_flip(0.5);
if (coin_flip(*rng))
return std::nullopt;
// If true, trade_amount = 5, if false, trade_amount = 2
// Dropping the conditional should make this faster, but
// really, this is just more elegant.
auto trade_amount = (int) std::round(coin_flip(*rng)) * 3 + 2;
auto customer = std::static_pointer_cast<PersonAgent>(population->get_agent_by_id(customer_id));
console->debug("Agent {} trading amount {} with agent {}", agent_id, trade_amount, customer_id);
customer->_wallet += trade_amount;
_wallet -= trade_amount;
return customer_id;
}
std::optional<int> PersonAgent::balance_books(std::shared_ptr<kami::ReporterModel>& model) {
console->debug(
"balance_books() called for agent {} with wallet {}, savings {}, loans {}", get_agent_id(), _wallet,
_savings, _loans);
if (_wallet < 0) {
if (_savings >= -_wallet) {
withdraw_from_savings(-_wallet);
} else {
if (_savings > 0) {
withdraw_from_savings(_savings);
}
auto temp_loan = _bank->_available_to_loan;
if (temp_loan >= -_wallet) {
take_out_loan(-_wallet);
} else {
take_out_loan(temp_loan);
}
}
} else {
deposit_to_savings(_wallet);
}
if (_loans > 0 & _savings > 0) {
if (_savings > _loans) {
withdraw_from_savings(_loans);
repay_a_loan(_loans);
} else {
withdraw_from_savings(_savings);
repay_a_loan(_wallet);
}
}
_wealth = _savings - _loans;
console->debug("balance_books() exiting for agent {}, and wealth {}", get_agent_id(), _wealth);
return _wealth;
}
kami::AgentID PersonAgent::deposit_to_savings(double amount) {
console->debug("deposit_to_savings() called for agent {}, and amount {}", get_agent_id(), amount);
_wallet -= amount;
_savings += amount;
_bank->_deposits += amount;
return get_agent_id();
}
kami::AgentID PersonAgent::withdraw_from_savings(double amount) {
console->debug("withdraw_from_savings() called for agent {}, and amount {}", get_agent_id(), amount);
_wallet += amount;
_savings -= amount;
_bank->_deposits -= amount;
return get_agent_id();
}
kami::AgentID PersonAgent::repay_a_loan(double amount) {
console->debug("repay_a_loan() called for agent {}, and amount {}", get_agent_id(), amount);
_loans -= amount;
_wallet -= amount;
_bank->_available_to_loan += amount;
_bank->_bank_loans -= amount;
return get_agent_id();
}
kami::AgentID PersonAgent::take_out_loan(double amount) {
console->debug("take_out_loan() called for agent {}, and amount {}", get_agent_id(), amount);
_loans += amount;
_wallet += amount;
_bank->_available_to_loan -= amount;
_bank->_bank_loans += amount;
return get_agent_id();
}

View File

@@ -2,24 +2,21 @@
# Set minimum version of CMake.
cmake_minimum_required(VERSION 3.13)
find_package(spdlog)
set(EXAMPLE_NAME "boltzmann1d")
project(${EXAMPLE_NAME} LANGUAGES CXX)
create_executable( NAME ${EXAMPLE_NAME}
SOURCES
boltzmann1d.cc
PUBLIC_DEFINITIONS
USE_DOUBLE_PRECISION=1
PRIVATE_DEFINITIONS
DEBUG_VERBOSE
PRIVATE_INCLUDE_PATHS
${CMAKE_SOURCE_DIR}/include
PUBLIC_LINKED_TARGETS
kami::libkami
fmt
spdlog::spdlog
)
file(GLOB EXAMPLE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cc")
set_target_properties(${EXAMPLE_NAME} PROPERTIES VERSION ${KAMI_VERSION_STRING})
create_executable(
NAME ${EXAMPLE_NAME}
SOURCES ${EXAMPLE_SOURCES}
PUBLIC_DEFINITIONS USE_DOUBLE_PRECISION=1
PRIVATE_DEFINITIONS DEBUG_VERBOSE
PRIVATE_INCLUDE_PATHS ${CMAKE_SOURCE_DIR}/include
PUBLIC_LINKED_TARGETS fmt spdlog::spdlog kami::libkami
)
set_target_properties(${EXAMPLE_NAME} PROPERTIES VERSION ${VERSION_STRING})

View File

@@ -23,137 +23,182 @@
* SOFTWARE.
*/
#include "boltzmann1d.h"
#include <list>
#include <map>
#include <memory>
#include <optional>
#include <random>
#include <kami/agent.h>
#include <kami/kami.h>
#include <kami/multigrid1d.h>
#include <CLI/CLI.hpp>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <spdlog/stopwatch.h>
#include <CLI/App.hpp>
#include <CLI/Config.hpp>
#include <CLI/Formatter.hpp>
#include <kami/agent.h>
#include <kami/kami.h>
#include <kami/multigrid1d.h>
#include <kami/population.h>
#include <kami/random.h>
#include "boltzmann1d.h"
std::shared_ptr<spdlog::logger> console = nullptr;
std::shared_ptr<std::mt19937> rng = nullptr;
using namespace kami;
using namespace std;
MultiGrid1D *MoneyAgent1D::_world = nullptr;
BoltzmannWealthModel1D *MoneyAgent1D::_model = nullptr;
shared_ptr<spdlog::logger> console = nullptr;
shared_ptr<mt19937> rng = nullptr;
template <>
struct fmt::formatter<AgentID> : fmt::formatter<string> {
[[maybe_unused]] static auto format(AgentID agent_id, format_context &ctx) {
template<>
struct fmt::formatter<kami::AgentID>
: fmt::formatter<std::string> {
static auto format(
kami::AgentID agent_id,
format_context& ctx
) {
return format_to(ctx.out(), "{}", agent_id.to_string());
}
};
template <>
struct fmt::formatter<GridCoord1D> : fmt::formatter<string> {
[[maybe_unused]] static auto format(const GridCoord1D& coord, format_context &ctx) {
template<>
struct fmt::formatter<kami::GridCoord1D>
: fmt::formatter<std::string> {
static auto format(
const kami::GridCoord1D& coord,
format_context& ctx
) {
return format_to(ctx.out(), "{}", coord.to_string());
}
};
void MoneyAgent1D::step() {
_step_counter++;
MoneyAgent1D::~MoneyAgent1D() {
console->trace("Deconstructing Agent {} with final wealth {}", get_agent_id(), _agent_wealth);
}
kami::AgentID MoneyAgent1D::step(std::shared_ptr<kami::Model> model) {
this->_step_counter++;
console->trace("Agent {} is moving", this->get_agent_id());
move_agent();
console->trace("Agent {} is giving money", this->get_agent_id());
if (_agent_wealth > 0) give_money();
this->move_agent(model);
if (_agent_wealth > 0)
this->give_money(model);
return this->get_agent_id();
}
void MoneyAgent1D::set_world(MultiGrid1D *world) { _world = world; }
void MoneyAgent1D::set_model(BoltzmannWealthModel1D *model) { _model = model; }
void MoneyAgent1D::move_agent() {
std::optional<kami::GridCoord1D> MoneyAgent1D::move_agent(std::shared_ptr<kami::Model> model) {
console->trace("Entering move_agent");
auto agent_id = get_agent_id();
auto move_list = _world->get_neighborhood(agent_id, false);
std::uniform_int_distribution<int> dist(0, (int)move_list.size() - 1);
auto new_location = move_list[dist(*rng)];
auto domain = model->get_domain();
if (!domain)
throw (std::domain_error("model is missing domain"));
auto world = std::static_pointer_cast<kami::MultiGrid1D>(domain);
auto move_list_opt = world->get_neighborhood(agent_id, false);
if (!move_list_opt)
return std::nullopt;
auto move_list = move_list_opt;
std::uniform_int_distribution<int> dist(0, (int) move_list->size() - 1);
auto new_location = *std::next(move_list->begin(), dist(*rng));
console->trace("Moving Agent {} to location {}", agent_id, new_location);
_world->move_agent(agent_id, new_location);
world->move_agent(agent_id, new_location);
console->trace("Exiting move_agent");
return new_location;
}
void MoneyAgent1D::give_money() {
AgentID agent_id = get_agent_id();
GridCoord1D location = _world->get_location_by_agent(agent_id);
vector<AgentID> *cell_mates = _world->get_location_contents(location);
std::optional<kami::AgentID> MoneyAgent1D::give_money(std::shared_ptr<kami::Model> model) {
console->trace("Entering give_money");
auto agent_id = get_agent_id();
if (cell_mates->size() > 1) {
std::uniform_int_distribution<int> dist(0, (int)cell_mates->size() - 1);
AgentID other_agent_id = cell_mates->at(dist(*rng));
auto other_agent = _model->get_agent_by_id(other_agent_id);
auto domain = model->get_domain();
if (!domain)
throw (std::domain_error("model is missing domain"));
auto world = std::static_pointer_cast<kami::MultiGrid1D>(domain);
console->trace("Agent {} giving unit of wealth to agent {}", agent_id, other_agent_id);
other_agent->_agent_wealth += 1;
_agent_wealth -= 1;
}
auto agents = model->get_population();
if (!agents)
throw (std::domain_error("model is missing population"));
auto population = std::static_pointer_cast<kami::Population>(agents);
auto location = world->get_location_by_agent(agent_id);
auto cell_mates_opt = world->get_location_contents(location);
if (!cell_mates_opt)
return std::nullopt;
auto cell_mates = cell_mates_opt;
if (cell_mates->size() < 2)
return std::nullopt;
std::uniform_int_distribution<int> dist(0, (int) cell_mates->size() - 1);
auto other_agent_id = *std::next(cell_mates->begin(), dist(*rng));
auto other_agent = std::static_pointer_cast<MoneyAgent1D>(population->get_agent_by_id(other_agent_id));
console->trace("Agent {} giving unit of wealth to agent {}", agent_id, other_agent_id);
other_agent->_agent_wealth += 1;
_agent_wealth -= 1;
console->trace("Exiting move_agent");
return other_agent_id;
}
BoltzmannWealthModel1D::BoltzmannWealthModel1D(unsigned int number_agents, unsigned int length_x) {
_world = new MultiGrid1D(length_x, true);
_sched = new RandomScheduler(this, rng);
BoltzmannWealthModel1D::BoltzmannWealthModel1D(
unsigned int number_agents,
unsigned int length_x,
unsigned int new_seed
) {
rng = std::make_shared<std::mt19937>();
rng->seed(new_seed);
auto domain = std::make_shared<kami::MultiGrid1D>(length_x, true);
auto scheduler = std::make_shared<kami::RandomScheduler>(rng);
auto population = std::make_shared<kami::Population>();
this->set_domain(domain);
this->set_scheduler(scheduler);
this->set_population(population);
console->debug("Scheduler initiated with seed {}", new_seed);
_step_count = 0;
MoneyAgent1D::set_world(_world);
MoneyAgent1D::set_model(this);
std::uniform_int_distribution<int> dist(0, (int)length_x - 1);
std::uniform_int_distribution<int> dist(0, (int) length_x - 1);
for (unsigned int i = 0; i < number_agents; i++) {
auto *new_agent = new MoneyAgent1D();
auto new_agent = std::make_shared<MoneyAgent1D>();
_agent_list.insert(pair<AgentID, MoneyAgent1D *>(new_agent->get_agent_id(), new_agent));
_sched->add_agent(new_agent->get_agent_id());
_world->add_agent(new_agent->get_agent_id(), GridCoord1D(dist(*rng)));
console->trace("Initializing agent with AgentID {}", new_agent->get_agent_id());
population->add_agent(new_agent);
domain->add_agent(new_agent->get_agent_id(), kami::GridCoord1D(dist(*rng)));
}
}
BoltzmannWealthModel1D::~BoltzmannWealthModel1D() {
for (auto & agent_pair : _agent_list)
delete agent_pair.second;
delete _sched;
delete _world;
std::shared_ptr<kami::Model> BoltzmannWealthModel1D::step() {
console->trace("Executing model step {}", ++_step_count);
_sched->step(shared_from_this());
return shared_from_this();
}
void BoltzmannWealthModel1D::step() {
_step_count++;
_sched->step();
}
#pragma clang diagnostic push
#pragma ide diagnostic ignored "EmptyDeclOrStmt"
void BoltzmannWealthModel1D::run(unsigned int steps) {
for (auto i = 0; i < steps; i++) step();
}
MoneyAgent1D *BoltzmannWealthModel1D::get_agent_by_id(AgentID _agent_id) const {
MoneyAgent1D *_agent_pair = _agent_list.at(_agent_id);
return _agent_pair;
}
int main(int argc, char **argv) {
string ident = "boltzmann1d";
int main(
int argc,
char** argv
) {
std::string ident = "boltzmann1d";
std::string log_level_option = "info";
CLI::App app{ident};
string log_level_option = "info";
unsigned int x_size = 16, agent_count = x_size, max_steps = 100, initial_seed = 42;
// This exercise is really stupid.
auto levels_list = std::make_unique<std::list<std::string>>();
for (auto& level_name : SPDLOG_LEVEL_NAMES)
levels_list->push_back(std::string(level_name.data(), level_name.size()));
app.add_option("-c", agent_count, "Set the number of agents")->check(CLI::PositiveNumber);
app.add_option("-l", log_level_option, "Set the logging level")->check(CLI::IsMember(SPDLOG_LEVEL_NAMES));
app.add_option("-l", log_level_option, "Set the logging level")->check(
CLI::IsMember(levels_list.get(), CLI::ignore_case));
app.add_option("-n", max_steps, "Set the number of steps to run the model")->check(CLI::PositiveNumber);
app.add_option("-s", initial_seed, "Set the initial seed")->check(CLI::Number);
app.add_option("-x", x_size, "Set the number of columns")->check(CLI::PositiveNumber);
@@ -161,16 +206,18 @@ int main(int argc, char **argv) {
console = spdlog::stdout_color_st(ident);
console->set_level(spdlog::level::from_str(log_level_option));
console->info("Compiled with Kami/{}, log level {}", KAMI_VERSION_STRING, log_level_option);
console->info("Starting Boltzmann Wealth Model with {} agents on a {}-unit grid for {} steps",agent_count, x_size, max_steps);
rng = make_shared<mt19937>(initial_seed);
BoltzmannWealthModel1D model(agent_count, x_size);
console->info("Compiled with Kami/{}, log level {}", kami::version.to_string(), log_level_option);
console->info(
"Starting Boltzmann Wealth Model with {} agents on a {}-unit grid for {} steps", agent_count, x_size,
max_steps);
spdlog::stopwatch sw;
for (int i = 0; i < max_steps; i++) {
console->trace("Initiating model step {}", i);
model.step();
}
auto model = std::make_shared<BoltzmannWealthModel1D>(agent_count, x_size, initial_seed);
for (int i = 0; i < max_steps; i++)
model->step();
console->info("Boltzmann Wealth Model simulation complete, requiring {} seconds", sw);
}
#pragma clang diagnostic pop

View File

@@ -25,65 +25,68 @@
#pragma once
#ifndef BOLTZMANN1D_H
//! @cond SuppressGuard
#define BOLTZMANN1D_H
//! @endcond
#include <iostream>
#include <map>
#include <memory>
#include <optional>
#include <kami/agent.h>
#include <kami/kami.h>
#include <kami/multigrid1d.h>
#include <kami/population.h>
#include <kami/random.h>
using namespace kami;
using namespace std;
/**
* A sample agent for a one-dimensional Boltzmann wealth model
*/
class MoneyAgent1D : public Agent {
class MoneyAgent1D
: public kami::Agent {
public:
/**
* Create the agent
*/
MoneyAgent1D() : _step_counter(0), _agent_wealth(1) {}
MoneyAgent1D()
:_step_counter(0), _agent_wealth(1) {
}
/**
* Deconstruct the agent
*/
~MoneyAgent1D();
/**
* Execute a single time-step for the agent
*/
void step() override;
/**
* Give the agent a reference copy of the domain it is expected to work in
*/
static void set_world(MultiGrid1D *world);
/**
* Give the agent a reference copy of the model it is expected to work in
*/
static void set_model(class BoltzmannWealthModel1D *model);
kami::AgentID step(std::shared_ptr<kami::Model> model) override;
/**
* Move the agent to a random location on the world
*/
void move_agent();
std::optional<kami::GridCoord1D> move_agent(std::shared_ptr<kami::Model> model);
/**
* Give money to a random agent
*/
void give_money();
std::optional<kami::AgentID> give_money(std::shared_ptr<kami::Model> model);
private:
static MultiGrid1D *_world;
static BoltzmannWealthModel1D *_model;
int _step_counter;
int _agent_wealth;
};
/**
* The one-dimensional Boltzmann wealth model
*/
class BoltzmannWealthModel1D : public Model {
class BoltzmannWealthModel1D
: public kami::Model {
public:
/**
* Create an instance of the one-dimensional Boltzmann wealth model.
@@ -92,36 +95,18 @@ public:
* @param[in] length_x the length of the one-dimensional world the agents
* occupy.
*/
explicit BoltzmannWealthModel1D(unsigned int number_agents = 10, unsigned int length_x = 10);
/**
* Destroy the instance
*/
~BoltzmannWealthModel1D();
explicit BoltzmannWealthModel1D(
unsigned int number_agents = 10,
unsigned int length_x = 10,
unsigned int new_seed = 42
);
/**
* Execute a single time-step for the model.
*/
void step() override;
/**
* Execute a number of time-steps for the model.
*
* @param[in] n the number of steps to execute.
*/
void run(unsigned int n) override;
/**
* Get the MoneyAgent instance associated with the given `AgentID`
*
* @returns an pointer to the `MoneyAgent` that was requested.
*/
[[nodiscard]] MoneyAgent1D *get_agent_by_id(AgentID agent_id) const override;
std::shared_ptr<kami::Model> step() final;
private:
map<AgentID, MoneyAgent1D *> _agent_list;
RandomScheduler *_sched;
MultiGrid1D *_world;
unsigned int _step_count;
};

View File

@@ -2,24 +2,21 @@
# Set minimum version of CMake.
cmake_minimum_required(VERSION 3.13)
find_package(spdlog)
set(EXAMPLE_NAME "boltzmann2d")
project(${EXAMPLE_NAME} LANGUAGES CXX)
create_executable( NAME ${EXAMPLE_NAME}
SOURCES
boltzmann2d.cc
PUBLIC_DEFINITIONS
USE_DOUBLE_PRECISION=1
PRIVATE_DEFINITIONS
DEBUG_VERBOSE
PRIVATE_INCLUDE_PATHS
${CMAKE_SOURCE_DIR}/include
PUBLIC_LINKED_TARGETS
kami::libkami
fmt
spdlog::spdlog
)
file(GLOB EXAMPLE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cc")
set_target_properties(${EXAMPLE_NAME} PROPERTIES VERSION ${KAMI_VERSION_STRING})
create_executable(
NAME ${EXAMPLE_NAME}
SOURCES ${EXAMPLE_SOURCES}
PUBLIC_DEFINITIONS USE_DOUBLE_PRECISION=1
PRIVATE_DEFINITIONS DEBUG_VERBOSE
PRIVATE_INCLUDE_PATHS ${CMAKE_SOURCE_DIR}/include
PUBLIC_LINKED_TARGETS fmt spdlog::spdlog kami::libkami
)
set_target_properties(${EXAMPLE_NAME} PROPERTIES VERSION ${VERSION_STRING})

View File

@@ -23,142 +23,184 @@
* SOFTWARE.
*/
#include "boltzmann2d.h"
#include <list>
#include <map>
#include <memory>
#include <optional>
#include <random>
#include <kami/agent.h>
#include <kami/kami.h>
#include <kami/multigrid2d.h>
#include <CLI/CLI.hpp>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <spdlog/stopwatch.h>
#include <CLI/App.hpp>
#include <CLI/Config.hpp>
#include <CLI/Formatter.hpp>
#include <kami/agent.h>
#include <kami/kami.h>
#include <kami/multigrid2d.h>
#include <kami/population.h>
#include <kami/random.h>
#include "boltzmann2d.h"
std::shared_ptr<spdlog::logger> console = nullptr;
std::shared_ptr<std::mt19937> rng = nullptr;
using namespace kami;
using namespace std;
MultiGrid2D *MoneyAgent2D::_world = nullptr;
BoltzmannWealthModel2D *MoneyAgent2D::_model = nullptr;
shared_ptr<spdlog::logger> console = nullptr;
shared_ptr<mt19937> rng = nullptr;
template <>
struct fmt::formatter<AgentID> : fmt::formatter<string> {
[[maybe_unused]] static auto format(AgentID agent_id, format_context &ctx) {
template<>
struct fmt::formatter<kami::AgentID>
: fmt::formatter<std::string> {
static auto format(
kami::AgentID agent_id,
format_context& ctx
) {
return format_to(ctx.out(), "{}", agent_id.to_string());
}
};
template <>
struct fmt::formatter<GridCoord2D> : fmt::formatter<string> {
[[maybe_unused]] static auto format(const GridCoord2D& coord, format_context &ctx) {
template<>
struct fmt::formatter<kami::GridCoord2D>
: fmt::formatter<std::string> {
static auto format(
const kami::GridCoord2D& coord,
format_context& ctx
) {
return format_to(ctx.out(), "{}", coord.to_string());
}
};
void MoneyAgent2D::step() {
_step_counter++;
MoneyAgent2D::~MoneyAgent2D() {
console->trace("Deconstructing Agent {} with final wealth {}", get_agent_id(), _agent_wealth);
}
kami::AgentID MoneyAgent2D::step(std::shared_ptr<kami::Model> model) {
this->_step_counter++;
console->trace("Agent {} is moving", this->get_agent_id());
move_agent();
console->trace("Agent {} is giving money", this->get_agent_id());
if (_agent_wealth > 0) give_money();
this->move_agent(model);
if (_agent_wealth > 0)
this->give_money(model);
return this->get_agent_id();
}
void MoneyAgent2D::set_world(MultiGrid2D *world) { _world = world; }
void MoneyAgent2D::set_model(BoltzmannWealthModel2D *model) { _model = model; }
void MoneyAgent2D::move_agent() {
std::optional<kami::GridCoord2D> MoneyAgent2D::move_agent(const std::shared_ptr<kami::Model>& model) {
console->trace("Entering move_agent");
auto agent_id = get_agent_id();
auto move_list = _world->get_neighborhood(agent_id, GridNeighborhoodType::Moore, false);
std::uniform_int_distribution<int> dist(0, (int) move_list.size() - 1);
auto new_location = move_list[dist(*rng)];
auto domain = model->get_domain();
if (!domain)
throw (std::domain_error("model is missing domain"));
auto world = std::static_pointer_cast<kami::MultiGrid2D>(domain);
auto move_list_opt = world->get_neighborhood(agent_id, false, kami::GridNeighborhoodType::VonNeumann);
if (!move_list_opt)
return std::nullopt;
auto move_list = move_list_opt;
std::uniform_int_distribution<int> dist(0, (int) move_list->size() - 1);
auto new_location = *std::next(move_list->begin(), dist(*rng));
console->trace("Moving Agent {} to location {}", agent_id, new_location);
_world->move_agent(agent_id, new_location);
world->move_agent(agent_id, new_location);
console->trace("Exiting move_agent");
return new_location;
}
void MoneyAgent2D::give_money() {
AgentID agent_id = get_agent_id();
GridCoord2D location = _world->get_location_by_agent(agent_id);
vector<AgentID> *cell_mates = _world->get_location_contents(location);
std::optional<kami::AgentID> MoneyAgent2D::give_money(const std::shared_ptr<kami::Model>& model) {
console->trace("Entering give_money");
auto agent_id = get_agent_id();
if (cell_mates->size() > 1) {
std::uniform_int_distribution<int> dist(0, (int)cell_mates->size() - 1);
AgentID other_agent_id = cell_mates->at(dist(*rng));
auto other_agent = _model->get_agent_by_id(other_agent_id);
auto domain = model->get_domain();
if (!domain)
throw (std::domain_error("model is missing domain"));
auto world = std::static_pointer_cast<kami::MultiGrid2D>(domain);
console->trace("Agent {} giving unit of wealth to agent {}", agent_id, other_agent_id);
other_agent->_agent_wealth += 1;
_agent_wealth -= 1;
}
auto agents = model->get_population();
if (!agents)
throw (std::domain_error("model is missing population"));
auto population = std::static_pointer_cast<kami::Population>(agents);
auto location = world->get_location_by_agent(agent_id);
auto cell_mates_opt = world->get_location_contents(location);
if (!cell_mates_opt)
return std::nullopt;
auto cell_mates = cell_mates_opt;
if (cell_mates->size() < 2)
return std::nullopt;
std::uniform_int_distribution<int> dist(0, (int) cell_mates->size() - 1);
auto other_agent_id = *std::next(cell_mates->begin(), dist(*rng));
auto other_agent = std::static_pointer_cast<MoneyAgent2D>(population->get_agent_by_id(other_agent_id));
console->trace("Agent {} giving unit of wealth to agent {}", agent_id, other_agent_id);
other_agent->_agent_wealth += 1;
_agent_wealth -= 1;
console->trace("Exiting move_agent");
return other_agent_id;
}
BoltzmannWealthModel2D::BoltzmannWealthModel2D(unsigned int number_agents, unsigned int length_x, unsigned int length_y, unsigned int new_seed) {
rng = make_shared<mt19937>();
BoltzmannWealthModel2D::BoltzmannWealthModel2D(
unsigned int number_agents,
unsigned int length_x,
unsigned int length_y,
unsigned int new_seed
) {
rng = std::make_shared<std::mt19937>();
rng->seed(new_seed);
_world = new MultiGrid2D(length_x, length_y, true, true);
_sched = new RandomScheduler(this, rng);
auto domain = std::make_shared<kami::MultiGrid2D>(length_x, length_y, true, true);
auto scheduler = std::make_shared<kami::RandomScheduler>(rng);
auto population = std::make_shared<kami::Population>();
this->set_domain(domain);
this->set_scheduler(scheduler);
this->set_population(population);
console->debug("Scheduler initiated with seed {}", new_seed);
_step_count = 0;
MoneyAgent2D::set_world(_world);
MoneyAgent2D::set_model(this);
std::uniform_int_distribution<int> dist_x(0, (int)length_x - 1);
std::uniform_int_distribution<int> dist_y(0, (int)length_y - 1);
std::uniform_int_distribution<int> dist_x(0, (int) length_x - 1);
std::uniform_int_distribution<int> dist_y(0, (int) length_y - 1);
for (unsigned int i = 0; i < number_agents; i++) {
auto *new_agent = new MoneyAgent2D();
auto new_agent = std::make_shared<MoneyAgent2D>();
_agent_list.insert(pair<AgentID, MoneyAgent2D *>(new_agent->get_agent_id(), new_agent));
_sched->add_agent(new_agent->get_agent_id());
_world->add_agent(new_agent->get_agent_id(), GridCoord2D(dist_x(*rng), dist_x(*rng)));
console->trace("Initializing agent with AgentID {}", new_agent->get_agent_id());
population->add_agent(new_agent);
domain->add_agent(new_agent->get_agent_id(), kami::GridCoord2D(dist_x(*rng), dist_x(*rng)));
}
}
BoltzmannWealthModel2D::~BoltzmannWealthModel2D() {
for (auto & agent_pair : _agent_list)
delete agent_pair.second;
delete _sched;
delete _world;
std::shared_ptr<kami::Model> BoltzmannWealthModel2D::step() {
console->trace("Executing model step {}", _step_count++);
_sched->step(shared_from_this());
return shared_from_this();
}
void BoltzmannWealthModel2D::step() {
_step_count++;
_sched->step();
}
#pragma clang diagnostic push
#pragma ide diagnostic ignored "EmptyDeclOrStmt"
void BoltzmannWealthModel2D::run(unsigned int steps) {
for (auto i = 0; i < steps; i++) step();
}
MoneyAgent2D *BoltzmannWealthModel2D::get_agent_by_id(AgentID agent_id) const {
MoneyAgent2D *agent = _agent_list.at(agent_id);
return agent;
}
int main(int argc, char **argv) {
int main(
int argc,
char** argv
) {
std::string ident = "boltzmann2d";
std::string log_level_option = "info";
CLI::App app{ident};
string log_level_option = "info";
unsigned int x_size = 16, y_size = 16, agent_count = x_size * y_size, max_steps = 100, initial_seed = 42;
// This exercise is really stupid.
auto levels_list = std::make_unique<std::list<std::string>>();
for (auto& level_name : SPDLOG_LEVEL_NAMES)
levels_list->push_back(std::string(level_name.data(), level_name.size()));
app.add_option("-c", agent_count, "Set the number of agents")->check(CLI::PositiveNumber);
app.add_option("-l", log_level_option, "Set the logging level")->check(CLI::IsMember(SPDLOG_LEVEL_NAMES));
app.add_option("-l", log_level_option, "Set the logging level")->check(
CLI::IsMember(levels_list.get(), CLI::ignore_case));
app.add_option("-n", max_steps, "Set the number of steps to run the model")->check(CLI::PositiveNumber);
app.add_option("-s", initial_seed, "Set the initial seed")->check(CLI::Number);
app.add_option("-x", x_size, "Set the number of columns")->check(CLI::PositiveNumber);
@@ -167,15 +209,18 @@ int main(int argc, char **argv) {
console = spdlog::stdout_color_st(ident);
console->set_level(spdlog::level::from_str(log_level_option));
console->info("Compiled with Kami/{}, log level {}", KAMI_VERSION_STRING, log_level_option);
console->info("Starting Boltzmann Wealth Model with {} agents on a {}x{}-unit grid for {} steps", agent_count, x_size, y_size, max_steps);
BoltzmannWealthModel2D model(agent_count, x_size, y_size, initial_seed);
console->info("Compiled with Kami/{}, log level {}", kami::version.to_string(), log_level_option);
console->info(
"Starting Boltzmann Wealth Model with {} agents on a {}x{}-unit grid for {} steps", agent_count,
x_size, y_size, max_steps);
spdlog::stopwatch sw;
for (int i = 0; i < max_steps; i++) {
console->trace("Initiating model step {}", i);
model.step();
}
auto model = std::make_shared<BoltzmannWealthModel2D>(agent_count, x_size, y_size, initial_seed);
for (int i = 0; i < max_steps; i++)
model->step();
console->info("Boltzmann Wealth Model simulation complete, requiring {} seconds", sw);
}
#pragma clang diagnostic pop

View File

@@ -25,57 +25,56 @@
#pragma once
#ifndef BOLTZMANN2D_H
//! @cond SuppressGuard
#define BOLTZMANN2D_H
//! @endcond
#include <iostream>
#include <map>
#include <memory>
#include <optional>
#include <kami/agent.h>
#include <kami/kami.h>
#include <kami/multigrid2d.h>
#include <kami/population.h>
#include <kami/random.h>
#include <iostream>
#include <map>
using namespace kami;
using namespace std;
/**
* A sample agent for a two-dimensional Boltzmann wealth model
*/
class MoneyAgent2D : public Agent {
class MoneyAgent2D
: public kami::Agent {
public:
/**
* Create the agent
*/
MoneyAgent2D() : _step_counter(0), _agent_wealth(1) {}
MoneyAgent2D()
:_step_counter(0), _agent_wealth(1) {
}
/**
* Deconstruct the agent
*/
~MoneyAgent2D();
/**
* Execute a single time-step for the agent
*/
void step() override;
/**
* Give the agent a reference copy of the domain it is expected to work in
*/
static void set_world(MultiGrid2D *world);
/**
* Give the agent a reference copy of the model it is expected to work in
*/
static void set_model(class BoltzmannWealthModel2D *model);
kami::AgentID step(std::shared_ptr<kami::Model> model) override;
/**
* Move the agent to a random location on the world
*/
void move_agent();
std::optional<kami::GridCoord2D> move_agent(const std::shared_ptr<kami::Model>& model);
/**
* Give money to a random agent
*/
void give_money();
std::optional<kami::AgentID> give_money(const std::shared_ptr<kami::Model>& model);
private:
static MultiGrid2D *_world;
static BoltzmannWealthModel2D *_model;
int _step_counter;
int _agent_wealth;
};
@@ -83,7 +82,9 @@ private:
/**
* The two-dimensional Boltzmann wealth model
*/
class BoltzmannWealthModel2D : public Model {
class BoltzmannWealthModel2D
: public kami::Model {
public:
/**
* Create an instance of the two-dimensional Boltzmann wealth model.
@@ -96,36 +97,19 @@ public:
* @param[in] new_seed the initial seed used for the random number
* generator.
*/
explicit BoltzmannWealthModel2D(unsigned int number_agents = 10, unsigned int length_x = 10, unsigned int length_y = 10, unsigned int new_seed = 42);
/**
* Destroy the instance
*/
~BoltzmannWealthModel2D();
explicit BoltzmannWealthModel2D(
unsigned int number_agents = 10,
unsigned int length_x = 10,
unsigned int length_y = 10,
unsigned int new_seed = 42
);
/**
* Execute a single time-step for the model.
*/
void step() override;
/**
* Execute a number of time-steps for the model.
*
* @param[in] n the number of steps to execute.
*/
void run(unsigned int n) override;
/**
* Get the MoneyAgent2D instance associated with the given `AgentID`
*
* @returns an pointer to the `MoneyAgent2D` that was requested.
*/
[[nodiscard]] MoneyAgent2D *get_agent_by_id(AgentID agent_id) const override;
std::shared_ptr<kami::Model> step() final;
private:
map<AgentID, MoneyAgent2D *> _agent_list;
RandomScheduler *_sched;
MultiGrid2D *_world;
unsigned int _step_count;
};

View File

@@ -0,0 +1,22 @@
####
# Set minimum version of CMake.
cmake_minimum_required(VERSION 3.13)
find_package(spdlog)
set(EXAMPLE_NAME "minimal")
project(${EXAMPLE_NAME} LANGUAGES CXX)
file(GLOB EXAMPLE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cc")
create_executable(
NAME ${EXAMPLE_NAME}
SOURCES ${EXAMPLE_SOURCES}
PUBLIC_DEFINITIONS USE_DOUBLE_PRECISION=1
PRIVATE_DEFINITIONS DEBUG_VERBOSE
PRIVATE_INCLUDE_PATHS ${CMAKE_SOURCE_DIR}/include
PUBLIC_LINKED_TARGETS fmt spdlog::spdlog kami::libkami
)
set_target_properties(${EXAMPLE_NAME} PROPERTIES VERSION ${VERSION_STRING})

View File

@@ -0,0 +1,68 @@
/*-
* Copyright (c) 2023 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <list>
#include <map>
#include <memory>
#include <kami/agent.h>
#include <kami/kami.h>
#include <kami/model.h>
#include <kami/population.h>
#include <kami/sequential.h>
class MinimalAgent
: public kami::Agent {
public:
kami::AgentID step(std::shared_ptr<kami::Model> model) override {
return this->get_agent_id();
}
};
class MinimalModel
: public kami::Model {
public:
MinimalModel() {
auto sched = std::make_shared<kami::SequentialScheduler>();
set_scheduler(sched);
auto pop = std::make_shared<kami::Population>();
set_population(pop);
for (auto i = 0; i < 10; i++) {
auto new_agent = std::make_shared<MinimalAgent>();
pop->add_agent(new_agent);
}
}
};
int main() {
auto model = std::make_shared<MinimalModel>();
for (int i = 0; i < 10; i++)
model->step();
return 0;
}

View File

@@ -0,0 +1,22 @@
####
# Set minimum version of CMake.
cmake_minimum_required(VERSION 3.13)
find_package(spdlog)
set(EXAMPLE_NAME "starter")
project(${EXAMPLE_NAME} LANGUAGES CXX)
file(GLOB EXAMPLE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cc")
create_executable(
NAME ${EXAMPLE_NAME}
SOURCES ${EXAMPLE_SOURCES}
PUBLIC_DEFINITIONS USE_DOUBLE_PRECISION=1
PRIVATE_DEFINITIONS DEBUG_VERBOSE
PRIVATE_INCLUDE_PATHS ${CMAKE_SOURCE_DIR}/include
PUBLIC_LINKED_TARGETS fmt spdlog::spdlog kami::libkami
)
set_target_properties(${EXAMPLE_NAME} PROPERTIES VERSION ${VERSION_STRING})

136
examples/starter/starter.cc Normal file
View File

@@ -0,0 +1,136 @@
/*-
* Copyright (c) 2020 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "starter.h"
#include <list>
#include <map>
#include <memory>
#include <random>
#include <CLI/CLI.hpp>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <spdlog/stopwatch.h>
#include <kami/agent.h>
#include <kami/kami.h>
#include <kami/model.h>
#include <kami/population.h>
#include <kami/random.h>
std::shared_ptr<spdlog::logger> console = nullptr;
std::shared_ptr<std::mt19937> rng = nullptr;
template<>
struct fmt::formatter<kami::AgentID>
: fmt::formatter<std::string> {
static auto format(
kami::AgentID agent_id,
format_context& ctx
) {
return format_to(ctx.out(), "{}", agent_id.to_string());
}
};
StarterAgent::StarterAgent() {
console->debug("StarterAgent with ID {} constructed", this->get_agent_id());
}
StarterAgent::~StarterAgent() {
console->debug("StarterAgent with ID {} deconstructed", this->get_agent_id());
}
kami::AgentID StarterAgent::step(std::shared_ptr<kami::Model> model) {
this->_step_counter++;
return this->get_agent_id();
}
StarterModel::StarterModel(
unsigned int number_agents,
unsigned int new_seed
) {
rng = std::make_shared<std::mt19937>();
rng->seed(new_seed);
auto sched = std::make_shared<kami::RandomScheduler>(rng);
auto pop = std::make_shared<kami::Population>();
_sched = sched;
_pop = pop;
console->debug("Scheduler initiated with seed {}", new_seed);
_step_count = 0;
for (auto i = 0; i < number_agents; i++) {
auto new_agent = std::make_shared<StarterAgent>();
console->trace("Initializing agent with AgentID {}", new_agent->get_agent_id());
_pop->add_agent(new_agent);
}
}
std::shared_ptr<kami::Model> StarterModel::step() {
console->trace("Executing model step {}", ++_step_count);
_sched->step(shared_from_this());
return shared_from_this();
}
int main(
int argc,
char** argv
) {
std::string ident = "starter";
std::string log_level_option = "info";
CLI::App app{ident};
unsigned int agent_count = 100, max_steps = 100, initial_seed = 8675309;
// This exercise is really stupid.
auto levels_list = std::make_unique<std::list<std::string>>();
for (auto& level_name : SPDLOG_LEVEL_NAMES)
levels_list->push_back(std::string(level_name.data(), level_name.size()));
app.add_option("-c", agent_count, "Set the number of agents")->check(CLI::PositiveNumber);
app.add_option("-l", log_level_option, "Set the logging level")->check(
CLI::IsMember(levels_list.get(), CLI::ignore_case));
app.add_option("-n", max_steps, "Set the number of steps to run the model")->check(CLI::PositiveNumber);
app.add_option("-s", initial_seed, "Set the initial seed")->check(CLI::Number);
CLI11_PARSE(app, argc, argv);
console = spdlog::stdout_color_st(ident);
console->set_level(spdlog::level::from_str(log_level_option));
console->info("Compiled with Kami/{}, log level {}", kami::version.to_string(), log_level_option);
console->info("Starting Starter Model with {} agents for {} steps", agent_count, max_steps);
auto model = std::make_shared<StarterModel>(agent_count, initial_seed);
spdlog::stopwatch sw;
for (int i = 0; i < max_steps; i++)
model->step();
console->info("Starter Model simulation complete, requiring {} seconds", sw);
}

View File

@@ -0,0 +1,93 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#ifndef STARTER_H
//! @cond SuppressGuard
#define STARTER_H
//! @endcond
#include <iostream>
#include <map>
#include <kami/agent.h>
#include <kami/kami.h>
#include <kami/random.h>
/**
* A starter agent for a starter model
*/
class StarterAgent
: public kami::Agent {
private:
int _step_counter = 0;
public:
/**
* Constructor
*/
StarterAgent();
/**
* Deconstructor
*/
~StarterAgent();
/**
* Execute a single time-step for the agent
*/
kami::AgentID step(std::shared_ptr<kami::Model> model) override;
};
/**
* The one-dimensional Boltzmann wealth model
*/
class StarterModel
: public kami::Model {
private:
unsigned int _step_count;
public:
/**
* Create an instance of the one-dimensional Boltzmann wealth model.
*
* @param[in] number_agents the number of agents to assign to the model.
* @param[in] length_x the length of the one-dimensional world the agents
* occupy.
*/
explicit StarterModel(
unsigned int number_agents = 10,
unsigned int new_seed = 42
);
/**
* Execute a single time-step for the model.
*/
std::shared_ptr<kami::Model> step();
};
#endif // STARTER_H

View File

@@ -25,61 +25,82 @@
#pragma once
#ifndef KAMI_AGENT_H
//! @cond SuppressGuard
#define KAMI_AGENT_H
//! @endcond
#include <iostream>
#include <memory>
#include <string>
#include <kami/kami.h>
#include <kami/model.h>
namespace kami {
/**
* A unique identifier for each `Agent`.
*
* The unique identifier permits ordering to allow `AgentID`s to be used as keys
* for `std::map`. The unique identifier is unique for the session, however,
* `AgentID`s are not guaranteed to be unique from session-to-session.
*
* @see Agent
*/
/**
* @brief A unique identifier for each `Agent`.
*
* @details The unique identifier permits ordering to allow `AgentID`s to be used as keys
* for `std::map`. The unique identifier is unique for the session, however,
* `AgentID`s are not guaranteed to be unique from session-to-session.
*
* @see Agent
*/
class LIBKAMI_EXPORT AgentID {
public:
/**
* Constructs a new unique identifier.
*/
AgentID() : _id(_id_next++){};
private:
inline static long long _id_next = 1;
/**
* Convert the identifier to a human-readable string.
* @brief The unique identifier is a `long long`.
*
* @details The unique identifier is an unsigned integer that increments
* monotonically with each new `AgentID` instantiated. This is
* substantially faster than other potential identifiers, such
* as MD5 hashes or UUID objects.
*/
long long _id;
public:
/**
* @brief Constructs a new unique identifier.
*/
AgentID();
/**
* @brief Convert the identifier to a human-readable string.
*
* @return a human-readable form of the `AgentID` as `std::string`.
*/
[[nodiscard]] std::string to_string() const { return std::to_string(_id); }
[[nodiscard]] std::string to_string() const;
/**
* Test if two `AgentID` instances are equal.
* @brief Test if two `AgentID` instances are equal.
*
* @param lhs is the left-hand side of the equality test.
* @param rhs is the right-hand side of the equality test.
* @return true is they are equal and false if not.
*/
friend bool operator==(const AgentID &lhs, const AgentID &rhs);
friend bool operator==(
const AgentID& lhs,
const AgentID& rhs
);
/**
* Test if two `AgentID` instances are not equal.
* @brief Test if two `AgentID` instances are not equal.
*
* @param lhs is the left-hand side of the equality test.
* @param rhs is the right-hand side of the equality test.
* @return true is they are not equal and false if they are.
*/
friend bool operator!=(const AgentID &lhs, const AgentID &rhs);
friend bool operator!=(
const AgentID& lhs,
const AgentID& rhs
);
/**
* Test if one AgentID is less than another.
* @brief Test if one AgentID is less than another.
*
* Due to the way AgentID instances are used internally,
* @details Due to the way AgentID instances are used internally,
* the AgentID must be orderable. The `<` operator provides a
* basic ordering sufficient for `std::map`.
*
@@ -88,62 +109,62 @@ namespace kami {
* @return true if `lhs` is "less than" `rhs` as determined by the
* underlying implementation of the `AgentID`.
*/
friend bool operator<(const AgentID &lhs, const AgentID &rhs);
friend bool operator<(
const AgentID& lhs,
const AgentID& rhs
);
/**
* Output an AgentID to the specified output stream
* @brief Output an AgentID to the specified output stream
*
* The form of the output will be the same as that produced by the
* @details The form of the output will be the same as that produced by the
* `to_string()` member function.
*
* @param lhs is the stream to output the `AgentID` to
* @param rhs is the `AgentID` to output
* @return the output stream for reuse
*/
friend std::ostream &operator<<(std::ostream &lhs, const AgentID &rhs);
private:
inline static long long _id_next = 1;
/**
* The unique identifier is a `long long`.
*
* The unique identifier is an unsigned integer that increments
* monotonically with each new `AgentID` instantiated. This is
* substantially faster than other potential identifiers, such
* as MD5 hashes or UUID objects.
*/
long long _id;
friend std::ostream& operator<<(
std::ostream& lhs,
const AgentID& rhs
);
};
/**
* A superclass for all agents.
*
* All agents should subclass the `Agent` class. At a minimum, subclasses must
* implement the `step()` function, to execute a single time step for each
* agent.
*
* @see `StagedAgent`
*/
/**
* @brief A superclass for all agents.
*
* @details All agents should subclass the `Agent` class. At a minimum, subclasses must
* implement the `step()` function, to execute a single time step for each
* agent.
*
* @see `ReporterAgent`, `StagedAgent`
*/
class LIBKAMI_EXPORT Agent {
private:
const AgentID _agent_id;
public:
/**
* Get the `Agent`'s `AgentID`.
* @brief Get the `Agent`'s `AgentID`.
*
* @return the `AgentID`
*/
[[nodiscard]] AgentID get_agent_id() const;
/**
* Execute a time-step for the agent
* @brief Execute a time-step for the agent
*
* This function should step the agent instance. Any activities that the
* @details This function should step the agent instance. Any activities that the
* agent should perform as part of its time step should be in this function.
*
* @param model a reference copy of the model
*
* @returns a copy of the AgentID
*/
virtual void step() = 0;
virtual AgentID step(std::shared_ptr<Model> model) = 0;
/**
* Compare if two `Agent`s are the same `Agent`.
* @brief Compare if two `Agent`s are the same `Agent`.
*
* @param lhs is the left-hand side of the equality test.
* @param rhs is the right-hand side of the equality test.
@@ -156,10 +177,13 @@ namespace kami {
* Subclasses of Agent may chose to extend this operator to tighten
* the restrictions on the comparison.
*/
friend bool operator==(const Agent &lhs, const Agent &rhs);
friend bool operator==(
const Agent& lhs,
const Agent& rhs
);
/**
* Compare if two `Agent`s are not the same `Agent`.
* @brief Compare if two `Agent`s are not the same `Agent`.
*
* @param lhs is the left-hand side of the equality test.
* @param rhs is the right-hand side of the equality test.
@@ -172,34 +196,38 @@ namespace kami {
* Subclasses of `Agent` may chose to extend this operator to tighten
* the restrictions on the comparison.
*/
friend bool operator!=(const Agent &lhs, const Agent &rhs);
private:
const AgentID _agent_id;
friend bool operator!=(
const Agent& lhs,
const Agent& rhs
);
};
/**
* A superclass for all staged agents.
*
* Staged agents use a two-phase step to allow agents to take actions without
* updating the state of the model before all agents have been allowed to
* update. All work necessary to advance the `StagedAgent` state should take
* place in the `step()` function. However, the `StagedAgent` should not actually
* update the state, and instead save the results for later use. Finally,
* during the `advance()` stage, the StagedAgent state should update.
*
* `StagedAgents` must implement both the `step()` and `advance()` functions.
*/
class LIBKAMI_EXPORT StagedAgent : public Agent {
/**
* @brief A superclass for all staged agents.
*
* @details Staged agents use a two-phase step to allow agents to take actions without
* updating the state of the model before all agents have been allowed to
* update. All work necessary to advance the `StagedAgent` state should take
* place in the `step()` function. However, the `StagedAgent` should not actually
* update the state, and instead save the results for later use. Finally,
* during the `advance()` stage, the StagedAgent state should update.
*
* `StagedAgents` must implement both the `step()` and `advance()` functions.
*/
class LIBKAMI_EXPORT StagedAgent
: public Agent {
public:
/**
* Post-step advance the agent
* @brief Post-step advance the agent
*
* This method should be called after `step()`. Any updates or cleanups to
* @details This method should be called after `step()`. Any updates or cleanups to
* the agent that must happen for the `StagedAgent` to complete its step must
* happen here.
*
* @param model a reference copy of the model
*
*/
virtual void advance() = 0;
virtual AgentID advance(std::shared_ptr<Model> model) = 0;
};
} // namespace kami

View File

@@ -29,36 +29,40 @@
* In the source code, include "exampleConfig.h" (no .in suffix).
* This header file will be generated, and filled with the
* right definition values. Change the namings as you wish,
* but make sure they match up with whats in CMakeLists.txt.
* but make sure they match up with what's in CMakeLists.txt.
*/
#pragma once
#ifndef KAMI_CONFIG_H
#define KAMI_CONFIG_H
#include <semver.hpp>
/**
* The major version of the Kami library.
*/
#define KAMI_VERSION_MAJOR @KAMI_VERSION_MAJOR @
#define KAMI_VERSION_MAJOR @VERSION_MAJOR@
/**
* The minor version of the Kami library.
*/
#define KAMI_VERSION_MINOR @KAMI_VERSION_MINOR @
#define KAMI_VERSION_MINOR @VERSION_MINOR@
/**
* The patch level of the Kami library.
*/
#define KAMI_VERSION_PATCH @KAMI_VERSION_PATCH @
#define KAMI_VERSION_PATCH @VERSION_PATCH@
/**
* The version of the Kami library as a `char*` constant.
*/
#define KAMI_VERSION_STRING "@KAMI_VERSION_STRING@"
namespace kami {
namespace {
/**
* @brief A reference copy of the current version of Kami
*/
constexpr auto version = semver::version{KAMI_VERSION_MAJOR, KAMI_VERSION_MINOR, KAMI_VERSION_PATCH};
}
} // namespace kami
/**
* The Kami library identifier as a `char *` constant.
*/
#define KAMI_VERSION_ID "Kami/@KAMI_VERSION_STRING@"
#endif // KAMI_CONFIG_H

View File

@@ -25,7 +25,9 @@
#pragma once
#ifndef KAMI_DOMAIN_H
//! @cond SuppressGuard
#define KAMI_DOMAIN_H
//! @endcond
#include <string>
@@ -33,45 +35,57 @@
namespace kami {
/**
* Provides an environment for the agents to participate in.
*
* Implementations of virtual environments are expected to subclass `Domain`.
*/
class LIBKAMI_EXPORT Domain {};
/**
* @brief Provides an environment for the agents to participate in.
*
* @details Implementations of virtual environments are expected to subclass `Domain`.
*/
class LIBKAMI_EXPORT Domain {
protected:
/**
* @brief Constructor.
*
* @details Making this constructor protected makes the class abstract without having
* to create any virtual functions.
*/
Domain() = default;
};
/**
* Provides a coordinate system for each `Domain`.
*
* The coordinate system must be able to produce a human-readable version of the
* coordinates given. For instance, an integer grid in two dimensions would
* provide standard Descartes coordinates like (0, 0) for the origin, or (2, 3)
* for the position that is two units "up" and three units to the "right" of the
* origin. Implementation of a coordinate system is left up to the user, though
* there are several established systems provided.
*
* @see GridCoord
*/
/**
* @brief Provides a coordinate system for each `Domain`.
*
* @details The coordinate system must be able to produce a human-readable version of the
* coordinates given. For instance, an integer grid in two dimensions would
* provide standard Descartes coordinates like (0, 0) for the origin, or (2, 3)
* for the position that is two units "up" and three units to the "right" of the
* origin. Implementation of a coordinate system is left up to the user, though
* there are several established systems provided.
*
* @see GridCoord
*/
class LIBKAMI_EXPORT Coord {
public:
/**
* Convert the coordinate to a human-readable string.
* @brief Convert the coordinate to a human-readable string.
*
* @return a human-readable form of the `Coord` as `std::string`.
*/
[[nodiscard]] virtual std::string to_string() const = 0;
/**
* Output a `Coord` to the specified output stream
* @brief Output a `Coord` to the specified output stream
*
* The form of the output will be the same as that produced by the
* @details The form of the output will be the same as that produced by the
* `to_string()` member function.
*
* @param lhs is the stream to output the `Coord` to
* @param rhs is the `Coord` to output
* @return the output stream for reuse
*/
friend std::ostream &operator<<(std::ostream &lhs, const Coord &rhs);
friend std::ostream& operator<<(
std::ostream& lhs,
const Coord& rhs
);
};
} // namespace kami

136
include/kami/error.h Normal file
View File

@@ -0,0 +1,136 @@
//
// Created by James Howard on 9/9/22.
//
#ifndef KAMI_ERROR_H
//! @cond SuppressGuard
#define KAMI_ERROR_H
//! @endcond
#include <stdexcept>
#include <string>
namespace kami::error {
/**
* @brief Agent was not found
*/
class AgentNotFound
: public std::logic_error {
public:
/**
* @brief Constructor
* @param s text description of the exception
*/
explicit AgentNotFound(const char* s)
:std::logic_error(s) {
};
/**
* @brief Constructor
* @param s text description of the exception
*/
explicit AgentNotFound(const std::string& s)
:std::logic_error(s) {
};
};
/**
* @brief Location specified is invalid
*
* @see `LocationUnavailable`
*/
class LocationInvalid
: public std::domain_error {
public:
/**
* @brief Constructor
* @param s text description of the exception
*/
explicit LocationInvalid(const char* s)
:std::domain_error(s) {
};
/**
* @brief Constructor
* @param s text description of the exception
*/
explicit LocationInvalid(const std::string& s)
:std::domain_error(s) {
};
};
/**
* @brief Location specified is unavailable
*
* @see `LocationInvalid`
*/
class LocationUnavailable
: public std::domain_error {
public:
/**
* @brief Constructor
* @param s text description of the exception
*/
explicit LocationUnavailable(const char* s)
:std::domain_error(s) {
};
/**
* @brief Constructor
* @param s text description of the exception
*/
explicit LocationUnavailable(const std::string& s)
:std::domain_error(s) {
};
};
/**
* @brief The option given is not valid at this time
*/
class OptionInvalid
: public std::invalid_argument {
public:
/**
* @brief Constructor
* @param s text description of the exception
*/
explicit OptionInvalid(const char* s)
:std::invalid_argument(s) {
};
/**
* @brief Constructor
* @param s text description of the exception
*/
explicit OptionInvalid(const std::string& s)
:std::invalid_argument(s) {
};
};
/**
* @brief The resource specified is not available at this time
*/
class ResourceNotAvailable
: public std::logic_error {
public:
/**
* @brief Constructor
* @param s text description of the exception
*/
explicit ResourceNotAvailable(const char* s)
:std::logic_error(s) {
};
/**
* @brief Constructor
* @param s text description of the exception
*/
explicit ResourceNotAvailable(const std::string& s)
:std::logic_error(s) {
};
};
}
#endif //KAMI_ERROR_H

View File

@@ -25,7 +25,9 @@
#pragma once
#ifndef KAMI_GRID_H
//! @cond SuppressGuard
#define KAMI_GRID_H
//! @endcond
#include <string>
@@ -34,14 +36,14 @@
namespace kami {
/**
* @brief Neighborhood types for orthogonal grid domains of cells.
*
* @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.
*/
/**
* @brief Neighborhood types for orthogonal grid domains of cells.
*
* @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 class GridNeighborhoodType {
/**
* @brief Moore neighborhood
@@ -49,7 +51,7 @@ namespace kami {
* @details Moore neighborhood types include diagonally-adjacent cells as
* neighbors.
*/
Moore [[maybe_unused]],
Moore,
/**
* @brief Von Neumann neighborhood
@@ -57,12 +59,12 @@ namespace kami {
* @details Von Neumann neighborhood types do not include
* diagonally-adjacent cells as neighbors.
*/
VonNeumann [[maybe_unused]]
VonNeumann
};
/**
* @brief Distance types for orthogonal grid domains
*/
/**
* @brief Distance types for orthogonal grid domains
*/
enum class GridDistanceType {
/**
* @brief Euclidean distance.
@@ -71,7 +73,7 @@ namespace kami {
* connecting two points. This is commonly called a "beeline" or
* "as the crow flies."
*/
Euclidean [[maybe_unused]],
Euclidean,
/**
* @brief Manhattan distance.
@@ -81,24 +83,58 @@ namespace kami {
* "taxicab distance," "rectilinear distance," or many other [formal
* names](https://en.wikipedia.org/wiki/Taxicab_geometry).
*/
Manhattan [[maybe_unused]]
Manhattan,
/**
* @brief Chebyshev distance.
*
* @details The Chebyshev distance, also called the "chessboard" distance
* is the number of single point jumps necessary to move from one point to
* the next. This can be likened to a king on a chessboard and the number
* of moves necessary to move from a given point to any other given point.
*/
Chebyshev
};
/**
* @brief An abstract domain based on a gridded environment.
*
* @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 {};
/**
* @brief An abstract domain based on a gridded environment.
*
* @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 {
};
/**
* @brief An abstract for gridded coordinates.
*
* @details All gridded coordinates are expected to subclass `GridCoord`.
*/
class LIBKAMI_EXPORT GridCoord : public Coord {};
/**
* @brief An abstract for gridded coordinates.
*
* @details All gridded coordinates are expected to subclass `GridCoord`.
*/
class LIBKAMI_EXPORT GridCoord
: public Coord {
public:
/**
* @brief Find the distance between two points
*
* @details Find the distance between two points using the
* specified metric.
*
* However, the coordinate class is not aware of the
* properties of the `GridDomain` it is operating on. Accordingly,
* if the direct path is measured, without accounting for
* and toroidal wrapping of the underlying `GridDomain`.
*
* @param p the point to measure the distance to
*
* @returns the distance as a `double`
*/
virtual double distance(std::shared_ptr<Coord>& p) const = 0;
};
} // namespace kami

View File

@@ -25,84 +25,158 @@
#pragma once
#ifndef KAMI_GRID1D_H
//! @cond SuppressGuard
#define KAMI_GRID1D_H
//! @endcond
#include <iostream>
#include <map>
#include <memory>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <kami/domain.h>
#include <kami/error.h>
#include <kami/grid.h>
#include <kami/kami.h>
namespace kami {
/**
* One-dimensional coordinates
*/
class LIBKAMI_EXPORT GridCoord1D : public GridCoord {
/**
* @brief One-dimensional coordinates
*/
class LIBKAMI_EXPORT GridCoord1D
: public GridCoord {
public:
/**
* Constructor for one-dimensional coordinates
* @brief Constructor for one-dimensional coordinates
*/
explicit GridCoord1D(int x_coord) : _x_coord(x_coord){};
explicit GridCoord1D(int x_coord);
/**
* Return the `x` coordinate
* @brief Return the `x` coordinate
*/
[[nodiscard]] int get_x_location() const;
[[nodiscard]] int x() const;
/**
* Convert the coordinate to a human-readable string.
* @brief Convert the coordinate to a human-readable string.
*
* @return a human-readable form of the `Coord` as `std::string`.
*/
[[nodiscard]] std::string to_string() const override;
/**
* Test if two coordinates are equal
* @brief Find the distance between two points
*
* @details Find the distance between two points using the
* specified metric. There are three options provided by
* the `GridDistanceType` class. However, of the three
* distance types provided, all provide the same result so
* the value is ignored and the single result is returned.
*
* However, the coordinate class is not aware of the
* properties of the `Grid1D` it is operating on. Accordingly,
* if the direct path is measured, without accounting for
* and toroidal wrapping of the underlying `Grid1D`.
*
* @param p the point to measure the distance to
*
* @returns the distance as a `double`
*/
friend bool operator==(const GridCoord1D &lhs, const GridCoord1D &rhs);
double distance(std::shared_ptr<Coord>& p) const override;
/**
* Test if two coordinates are not equal
* @brief Test if two coordinates are equal
*/
friend bool operator!=(const GridCoord1D &lhs, const GridCoord1D &rhs);
friend bool operator==(
const GridCoord1D& lhs,
const GridCoord1D& rhs
);
/**
* Output a given coordinate to the specified stream
* @brief Test if two coordinates are not equal
*/
friend std::ostream &operator<<(std::ostream &lhs, const GridCoord1D &rhs);
friend bool operator!=(
const GridCoord1D& lhs,
const GridCoord1D& rhs
);
/**
* @brief Output a given coordinate to the specified stream
*/
friend std::ostream& operator<<(
std::ostream& lhs,
const GridCoord1D& rhs
);
/**
* @brief Add two coordinates together
*/
inline friend GridCoord1D operator+(
const GridCoord1D& lhs,
const GridCoord1D& rhs
);
/**
* @brief Subtract one coordinate from another
*/
inline friend GridCoord1D operator-(
const GridCoord1D& lhs,
const GridCoord1D& rhs
);
/**
* @brief Multiply a coordinate by a scalar
*
* @details If any component of the resulting value is not a whole number, it is
* truncated following the same rules as `int`.
*/
inline friend GridCoord1D operator*(
const GridCoord1D& lhs,
double rhs
);
/**
* @brief Multiply a coordinate by a scalar
*
* @details If any component of the resulting value is not a whole number, it is
* truncated following the same rules as `int`.
*/
inline friend GridCoord1D operator*(
double lhs,
const GridCoord1D& rhs
);
private:
int _x_coord;
};
/**
* A one-dimensional grid where each cell may contain agents
*
* The grid is linear and may wrap around in its only dimension.
*
* @see `MultiGrid1D`
* @see `SoloGrid1D`
*/
class LIBKAMI_EXPORT Grid1D : public GridDomain {
/**
* @brief A one-dimensional grid where each cell may contain agents
*
* @details The grid is linear and may wrap around in its only dimension.
*
* @see `MultiGrid1D`
* @see `SoloGrid1D`
*/
class LIBKAMI_EXPORT Grid1D
: public GridDomain {
public:
/**
* Constructor
* @brief Constructor
*
* @param[in] maximum_x the length of the grid.
* @param[in] wrap_x should the grid wrap around on itself.
*/
explicit Grid1D(unsigned int maximum_x, bool wrap_x = false);
explicit Grid1D(
unsigned int maximum_x,
bool wrap_x = false
);
/**
* Deconstructor
*/
virtual ~Grid1D();
/**
* Place agent on the grid at the specified location.
* @brief Place agent on the grid at the specified location.
*
* @param[in] agent_id the `AgentID` of the agent to add.
* @param[in] coord the coordinates of the agent.
@@ -110,37 +184,46 @@ namespace kami {
* @returns false if the agent is not placed at the specified
* location, otherwise, true.
*/
virtual bool add_agent(AgentID agent_id, GridCoord1D coord) = 0;
virtual AgentID add_agent(
AgentID agent_id,
const GridCoord1D& coord
) = 0;
/**
* Remove agent from the grid.
* @brief Remove agent from the grid.
*
* @param[in] agent_id the `AgentID` of the agent to remove.
*
* @returns false if the agent is not removed, otherwise, true.
* @returns the `AgentID` of the `Agent` deleted
*/
[[maybe_unused]] [[maybe_unused]] bool delete_agent(AgentID agent_id);
AgentID delete_agent(AgentID agent_id);
/**
* Remove agent from the grid at the specified location
* @brief Remove agent from the grid at the specified location
*
* @param[in] agent_id the `AgentID` of the agent to remove.
* @param[in] coord the coordinates of the agent.
*
* @returns false if the agent is not removed, otherwise, true.
* @returns the `AgentID` of the `Agent` deleted
*/
bool delete_agent(AgentID agent_id, const GridCoord1D &coord);
AgentID delete_agent(
AgentID agent_id,
const GridCoord1D& coord
);
/**
* Move an agent to the specified location.
* @brief Move an agent to the specified location.
*
* @param[in] agent_id the `AgentID` of the agent to move.
* @param[in] coord the coordinates of the agent.
*/
bool move_agent(AgentID agent_id, GridCoord1D coord);
AgentID move_agent(
AgentID agent_id,
const GridCoord1D& coord
);
/**
* Inquire if the specified location is empty.
* @brief Inquire if the specified location is empty.
*
* @param[in] coord the coordinates of the query.
*
@@ -150,7 +233,7 @@ namespace kami {
[[nodiscard]] bool is_location_empty(const GridCoord1D& coord) const;
/**
* Inquire if the specified location is valid within the grid.
* @brief Inquire if the specified location is valid within the grid.
*
* @param[in] coord the coordinates of the query.
*
@@ -159,78 +242,98 @@ namespace kami {
[[nodiscard]] bool is_location_valid(const GridCoord1D& coord) const;
/**
* Get the location of the specified agent.
* @brief Get the location of the specified agent.
*
* @param[in] agent_id the `AgentID` of the agent in question.
*
* @return the location of the specified `Agent`
*/
[[nodiscard]] GridCoord1D get_location_by_agent(AgentID agent_id) const;
[[nodiscard]] GridCoord1D get_location_by_agent(const AgentID& agent_id) const;
/**
* Get the contents of the specified location.
* @brief Get the contents of the specified location.
*
* @param[in] coord the coordinates of the query.
*
* @return a pointer to a `vector` of `AgentID`s. The pointer is to the
* @return a pointer to a `set` of `AgentID`s. The pointer is to the
* internal copy of the agent list at the location, therefore, any changes
* to that object will update the state of the gird. Further, the pointer
* should not be deleted when no longer used.
*/
[[nodiscard]] std::vector<AgentID> *get_location_contents(const GridCoord1D& coord) const;
[[nodiscard]] std::shared_ptr<std::set<AgentID>>
get_location_contents(const GridCoord1D& coord) const;
/**
* Inquire to whether the grid wraps in the `x` dimension.
* @brief Inquire to whether the grid wraps in the `x` dimension.
*
* @return true if the grid wraps, and false otherwise
*/
[[maybe_unused]] [[maybe_unused]] [[nodiscard]] bool get_wrap_x() const;
[[nodiscard]] bool get_wrap_x() const;
/**
* Return the neighborhood of the specified Agent
* @brief Return the neighborhood of the specified Agent
*
* @param[in] agent_id the `AgentID` of the agent in question
* @param[in] include_center should the center-point, occupied by the agent,
* be in the list.
*
* @return a vector of `GridCoord1D` that includes all of the coordinates
* @return an `unordered_set` of `GridCoord1D` that includes all of the coordinates
* for all adjacent points.
*/
[[nodiscard]] std::vector<GridCoord1D> get_neighborhood(AgentID agent_id, bool include_center) const;
[[nodiscard]] std::shared_ptr<std::unordered_set<GridCoord1D>>
get_neighborhood(
AgentID agent_id,
bool include_center
) const;
/**
* Return the neighborhood of the specified location
* @brief Return the neighborhood of the specified location
*
* @param[in] coord the coordinates of the specified location.
* @param[in] include_center should the center-point, occupied by the agent,
* be in the list.
*
* @return a vector of `GridCoord1D` that includes all of the coordinates
* @return an `unordered_set` of `GridCoord1D` that includes all of the coordinates
* for all adjacent points.
*/
[[nodiscard]] std::vector<GridCoord1D> get_neighborhood(const GridCoord1D& coord, bool include_center) const;
[[nodiscard]] std::shared_ptr<std::unordered_set<GridCoord1D>>
get_neighborhood(
const GridCoord1D& coord,
bool include_center
) const;
/**
* Get the size of the grid in the `x` dimension.
* @brief Get the size of the grid in the `x` dimension.
*
* @return the length of the grid in the `x` dimension
*/
[[maybe_unused]] [[nodiscard]] unsigned int get_maximum_x() const;
[[nodiscard]] unsigned int get_maximum_x() const;
protected:
/**
* A vector containing the `AgentID`s of all agents assigned to this
* @brief Direction coordinates
*
* @details This can be used for addition to coordinates. Direction
* `0` is the first direction clockwise from "vertical." In this
* case, it can be on a vertically-oriented column, upwards, or to
* the right on a horizontally-oriented column. Then the additional
* directions are enumerated clockwise.
*/
const std::vector<GridCoord1D> directions = {GridCoord1D(1), GridCoord1D(-1)};
/**
* @brief An `unordered_set` containing the `AgentID`s of all agents assigned to this
* grid.
*/
std::vector<AgentID> *_agent_grid;
std::unique_ptr<std::unordered_multimap<GridCoord1D, AgentID>> _agent_grid;
/**
* A map containing the grid location of each agent.
* @brief A map containing the grid location of each agent.
*/
std::map<AgentID, GridCoord1D> *_agent_index;
std::unique_ptr<std::map<AgentID, GridCoord1D>> _agent_index;
/**
* Automatically adjust a coordinate location for wrapping.
* @brief Automatically adjust a coordinate location for wrapping.
*
* @param[in] coord the coordinates of the specified location.
*
@@ -245,4 +348,16 @@ namespace kami {
} // namespace kami
//! @cond SuppressHashMethod
#define KAMI_GRID1D_H
namespace std {
template<>
struct hash<kami::GridCoord1D> {
size_t operator()(const kami::GridCoord1D& key) const {
return (hash<int>()(key.x()));
}
};
} // namespace std
//! @endcond
#endif // KAMI_GRID1D_H

View File

@@ -25,10 +25,18 @@
#pragma once
#ifndef KAMI_GRID2D_H
//! @cond SuppressGuard
#define KAMI_GRID2D_H
//! @endcond
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <map>
#include <memory>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <kami/domain.h>
@@ -37,65 +45,184 @@
namespace kami {
/**
* Two-dimensional coordinates
*/
class LIBKAMI_EXPORT GridCoord2D : public GridCoord {
/**
* @brief Two-dimensional coordinates
*/
class LIBKAMI_EXPORT GridCoord2D
: public GridCoord {
public:
/**
* Constructor for two-dimensional coordinates
* @brief Constructor for two-dimensional coordinates
*/
GridCoord2D(int x_coord, int y_coord)
: _x_coord(x_coord), _y_coord(y_coord){};
GridCoord2D(
int x_coord,
int y_coord
);
/**
* Get the coordinate in the first dimension or `x`.
* @brief Get the coordinate in the first dimension or `x`.
*/
[[nodiscard]] int get_x_location() const;
[[nodiscard]] int x() const;
/**
* Get the coordinate in the second dimension or `y`.
* @brief Get the coordinate in the second dimension or `y`.
*/
[[nodiscard]] int get_y_location() const;
[[nodiscard]] int y() const;
/**
* Convert the coordinate to a human-readable string.
* @brief Convert the coordinate to a human-readable string.
*
* @return a human-readable form of the `Coord` as `std::string`.
*/
[[nodiscard]] std::string to_string() const override;
/**
* Test if two coordinates are equal
* @brief Find the distance between two points
*
* @details Find the distance between two points using the
* specified metric.
*
* However, the coordinate class is not aware of the
* properties of the `Grid2D` it is operating on. Accordingly,
* if the direct path is measured, without accounting for
* and toroidal wrapping of the underlying `Grid2D`.
*
* @param p the point to measure the distance to
*
* @returns the distance as a `double`
*/
friend bool operator==(const GridCoord2D &, const GridCoord2D &);
double distance(std::shared_ptr<Coord>& p) const override;
/**
* Test if two coordinates are not equal
* @brief Find the distance between two points
*
* @details Find the distance between two points using the
* specified metric. There are three options provided by
* the `GridDistanceType` class.
*
* However, the coordinate class is not aware of the
* properties of the `Grid2D` it is operating on. Accordingly,
* if the direct path is measured, without accounting for
* and toroidal wrapping of the underlying `Grid2D`.
*
* @param p the point to measure the distance to
* @param distance_type specify the distance type
*
* @returns the distance as a `double`
*/
friend bool operator!=(const GridCoord2D &, const GridCoord2D &);
double
distance(
std::shared_ptr<GridCoord2D>& p,
GridDistanceType distance_type = GridDistanceType::Euclidean
) const;
/**
* Output a given coordinate to the specified stream
* @brief Test if two coordinates are equal
*/
friend std::ostream &operator<<(std::ostream &, const GridCoord2D &);
friend bool operator==(
const GridCoord2D&,
const GridCoord2D&
);
/**
* @brief Test if two coordinates are not equal
*/
friend bool operator!=(
const GridCoord2D&,
const GridCoord2D&
);
/**
* @brief Output a given coordinate to the specified stream
*/
friend std::ostream& operator<<(
std::ostream&,
const GridCoord2D&
);
/**
* @brief Add two coordinates together
*/
inline friend GridCoord2D operator+(
const GridCoord2D& lhs,
const GridCoord2D& rhs
);
/**
* @brief Subtract one coordinate from another
*/
inline friend GridCoord2D operator-(
const GridCoord2D& lhs,
const GridCoord2D& rhs
);
/**
* @brief Multiply a coordinate by a scalar
*
* @details If any component of the resulting value is not a whole number, it is
* truncated following the same rules as `int`.
*/
inline friend GridCoord2D operator*(
const GridCoord2D& lhs,
const double rhs
);
/**
* @brief Multiply a coordinate by a scalar
*
* @details If any component of the resulting value is not a whole number, it is
* truncated following the same rules as `int`.
*/
inline friend GridCoord2D operator*(
const double lhs,
const GridCoord2D& rhs
);
protected:
/**
* @brief Find the distance between two points using the Chebyshev metric
*
* @param p the point to measure the distance to
*
* @returns the distance as a `double`
*/
inline double distance_chebyshev(std::shared_ptr<GridCoord2D>& p) const;
/**
* @brief Find the distance between two points using the Euclidean metric
*
* @param p the point to measure the distance to
*
* @returns the distance as a `double`
*/
inline double distance_euclidean(std::shared_ptr<GridCoord2D>& p) const;
/**
* @brief Find the distance between two points using the Manhattan metric
*
* @param p the point to measure the distance to
*
* @returns the distance as a `double`
*/
inline double distance_manhattan(std::shared_ptr<GridCoord2D>& p) const;
private:
int _x_coord, _y_coord;
};
/**
* A two-dimensional grid where each cell may contain agents
*
* The grid is linear and may wrap around in its only dimension.
*
* @see `MultiGrid2D`
* @see `SoloGrid2D`
*/
class LIBKAMI_EXPORT Grid2D : public GridDomain {
/**
* @brief A two-dimensional grid where each cell may contain agents
*
* @details The grid is linear and may wrap around in its only dimension.
*
* @see `MultiGrid2D`
* @see `SoloGrid2D`
*/
class LIBKAMI_EXPORT Grid2D
: public GridDomain {
public:
/**
* Constructor
* @brief Constructor
*
* @param[in] maximum_x the length of the grid in the first dimension
* @param[in] maximum_y the length of the grid in the second dimension
@@ -104,16 +231,15 @@ namespace kami {
* @param[in] wrap_y should the grid wrap around on itself in the second
* dimension
*/
Grid2D(unsigned int maximum_x, unsigned int maximum_y, bool wrap_x = false,
bool wrap_y = false);
explicit Grid2D(
unsigned int maximum_x,
unsigned int maximum_y,
bool wrap_x = false,
bool wrap_y = false
);
/**
* Deconstructor
*/
virtual ~Grid2D();
/**
* Place agent on the grid at the specified location.
* @brief Place agent on the grid at the specified location.
*
* @param[in] agent_id the `AgentID` of the agent to add.
* @param[in] coord the coordinates of the agent.
@@ -121,37 +247,46 @@ namespace kami {
* @returns false if the agent is not placed at the specified
* location, otherwise, true.
*/
virtual bool add_agent(AgentID agent_id, GridCoord2D coord) = 0;
virtual AgentID add_agent(
AgentID agent_id,
const GridCoord2D& coord
) = 0;
/**
* Remove agent from the grid.
* @brief Remove agent from the grid.
*
* @param[in] agent_id the `AgentID` of the agent to remove.
*
* @returns false if the agent is not removed, otherwise, true.
*/
[[maybe_unused]] bool delete_agent(AgentID agent_id);
AgentID delete_agent(AgentID agent_id);
/**
* Remove agent from the grid at the specified location
* @brief Remove agent from the grid at the specified location
*
* @param[in] agent_id the `AgentID` of the agent to remove.
* @param[in] coord the coordinates of the agent.
*
* @returns false if the agent is not removed, otherwise, true.
*/
bool delete_agent(AgentID agent_id, const GridCoord2D &coord);
AgentID delete_agent(
AgentID agent_id,
const GridCoord2D& coord
);
/**
* Move an agent to the specified location.
* @brief Move an agent to the specified location.
*
* @param[in] agent_id the `AgentID` of the agent to move.
* @param[in] coord the coordinates of the agent.
*/
bool move_agent(AgentID agent_id, GridCoord2D coord);
AgentID move_agent(
AgentID agent_id,
const GridCoord2D& coord
);
/**
* Inquire if the specified location is empty.
* @brief Inquire if the specified location is empty.
*
* @param[in] coord the coordinates of the query.
*
@@ -161,7 +296,7 @@ namespace kami {
[[nodiscard]] bool is_location_empty(const GridCoord2D& coord) const;
/**
* Inquire if the specified location is valid within the grid.
* @brief Inquire if the specified location is valid within the grid.
*
* @param[in] coord the coordinates of the query.
*
@@ -169,99 +304,132 @@ namespace kami {
*/
[[nodiscard]] bool is_location_valid(const GridCoord2D& coord) const;
/**
* Get the location of the specified agent.
virtual /**
* @brief Get the location of the specified agent.
*
* @param[in] agent_id the `AgentID` of the agent in question.
*
* @return the location of the specified `Agent`
*/
[[nodiscard]] GridCoord2D get_location_by_agent(AgentID agent_id) const;
GridCoord2D get_location_by_agent(const AgentID& agent_id) const;
/**
* Get the contents of the specified location.
* @brief Get the contents of the specified location.
*
* @param[in] coord the coordinates of the query.
*
* @return a pointer to a `vector` of `AgentID`s. The pointer is to the
* @return a pointer to a `set` of `AgentID`s. The pointer is to the
* internal copy of the agent list at the location, therefore, any changes
* to that object will update the state of the gird. Further, the pointer
* should not be deleted when no longer used.
*/
[[nodiscard]] std::vector<AgentID> *get_location_contents(const GridCoord2D& coord) const;
[[nodiscard]] std::shared_ptr<std::set<AgentID>>
get_location_contents(const GridCoord2D& coord) const;
/**
* Inquire to whether the grid wraps in the `x` dimension.
* @brief Inquire to whether the grid wraps in the `x` dimension.
*
* @return true if the grid wraps, and false otherwise
*/
[[maybe_unused]] [[nodiscard]] bool get_wrap_x() const;
[[nodiscard]] bool get_wrap_x() const;
/**
* Inquire to whether the grid wraps in the `y` dimension.
* @brief Inquire to whether the grid wraps in the `y` dimension.
*
* @return true if the grid wraps, and false otherwise
*/
[[maybe_unused]] [[nodiscard]] bool get_wrap_y() const;
[[nodiscard]] bool get_wrap_y() const;
/**
* Return the neighborhood of the specified Agent
virtual /**
* @brief Return the neighborhood of the specified Agent
*
* @param[in] agent_id the `AgentID` of the agent in question.
* @param[in] neighborhood_type the neighborhood type.
* @param[in] include_center should the center-point, occupied by the agent,
* be in the list.
*
* @return a vector of `GridCoord1D` that includes all of the coordinates
* @return a set of `GridCoord2D` that includes all of the coordinates
* for all adjacent points.
*
* @see `NeighborhoodType`
*/
[[nodiscard]] std::vector<GridCoord2D> get_neighborhood(AgentID agent_id, GridNeighborhoodType neighborhood_type, bool include_center) const;
std::shared_ptr<std::unordered_set<GridCoord2D>>
get_neighborhood(
AgentID agent_id,
bool include_center,
GridNeighborhoodType neighborhood_type
) const;
/**
* Return the neighborhood of the specified location
* @brief Return the neighborhood of the specified location
*
* @param[in] coord the coordinates of the specified location.
* @param[in] neighborhood_type the neighborhood type.
* @param[in] include_center should the center-point, occupied by the agent,
* be in the list.
*
* @return a vector of `GridCoord1D` that includes all of the coordinates
* @return a set of `GridCoord2D` that includes all of the coordinates
* for all adjacent points.
*
* @see `NeighborhoodType`
*/
[[nodiscard]] std::vector<GridCoord2D> get_neighborhood(const GridCoord2D& coord, GridNeighborhoodType neighborhood_type, bool include_center) const;
[[nodiscard]] std::shared_ptr<std::unordered_set<GridCoord2D>>
get_neighborhood(
const GridCoord2D& coord,
bool include_center,
GridNeighborhoodType neighborhood_type
) const;
/**
* Get the size of the grid in the `x` dimension.
* @brief Get the size of the grid in the `x` dimension.
*
* @return the length of the grid in the `x` dimension
*/
[[maybe_unused]] [[nodiscard]] unsigned int get_maximum_x() const;
[[nodiscard]] unsigned int get_maximum_x() const;
/**
* Get the size of the grid in the `y` dimension.
* @brief Get the size of the grid in the `y` dimension.
*
* @return the length of the grid in the `xy dimension
*/
[[maybe_unused]] [[nodiscard]] unsigned int get_maximum_y() const;
[[nodiscard]] unsigned int get_maximum_y() const;
protected:
/**
* A vector containing the `AgentID`s of all agents assigned to this
* @brief von Neumann neighborhood coordinates
*
* @details This can be used for addition to coordinates. Direction
* `0` is the first direction clockwise from "vertical." Then the additional
* directions are enumerated clockwise.
*/
const std::vector<GridCoord2D> directions_vonneumann = {GridCoord2D(0, 1), GridCoord2D(1, 0),
GridCoord2D(0, -1), GridCoord2D(-1, 0)};
/**
* @brief Moore neighborhood coordinates
*
* @details This can be used for addition to coordinates. Direction
* `0` is the first direction clockwise from "vertical." Then the additional
* directions are enumerated clockwise.
*/
const std::vector<GridCoord2D> directions_moore = {GridCoord2D(0, 1), GridCoord2D(1, 1),
GridCoord2D(1, 0), GridCoord2D(1, -1),
GridCoord2D(0, -1), GridCoord2D(-1, -1),
GridCoord2D(-1, 0), GridCoord2D(-1, 1)};
/**
* @brief A map containing the `AgentID`s of all agents assigned to this
* grid.
*/
std::vector<AgentID> **_agent_grid;
std::unique_ptr<std::unordered_multimap<GridCoord2D, AgentID>> _agent_grid;
/**
* A map containing the grid location of each agent.
* @brief A map containing the grid location of each agent.
*/
std::map<AgentID, GridCoord2D> *_agent_index;
std::unique_ptr<std::map<AgentID, GridCoord2D>> _agent_index;
/**
* Automatically adjust a coordinate location for wrapping.
* @brief Automatically adjust a coordinate location for wrapping.
*
* @param[in] coord the coordinates of the specified location.
*
@@ -276,4 +444,15 @@ namespace kami {
} // namespace kami
//! @cond SuppressHashMethod
namespace std {
template<>
struct hash<kami::GridCoord2D> {
size_t operator()(const kami::GridCoord2D& key) const {
return ((hash<int>()(key.x()) ^ (hash<int>()(key.y()) << 1)) >> 1);
}
};
} // namespace std
//! @endcond
#endif // KAMI_GRID2D_H

View File

@@ -25,9 +25,65 @@
#pragma once
#ifndef KAMI_KAMI_H
//! @cond SuppressGuard
#define KAMI_KAMI_H
//! @endcond
#include <semver.hpp>
#include <kami/KAMI_EXPORT.h>
#include <kami/config.h>
namespace kami {
// Forward declarations to clean up a lot of include-file madness
class Agent;
class AgentID;
class Domain;
class Model;
class Population;
class ReporterAgent;
class ReporterModel;
class Scheduler;
/**
* @brief Get the current version of Kami
*
* @return a `semver::version` object containing version information
*/
inline semver::version get_version() {
return version;
}
/**
* @brief A catalog of handy constants, mostly useful for seeding
* a random number generator
*/
class Constants {
public:
/**
* @brief Life, the Universe, and Everything!
*/
static constexpr auto ADAMS_CONSTANT = 42u;
/**
* @brief Jenny, I've got your number
*/
static constexpr auto JENNYS_NUMBER = 8675309u;
/**
* @brief $(7^(e - 1/e) - 9) * pi^2$
*/
static constexpr auto JENNYS_CONSTANT = 867.530901981;
};
} // namespace kami
#endif // KAMI_KAMI_H

View File

@@ -25,43 +25,105 @@
#pragma once
#ifndef KAMI_MODEL_H
//! @cond SuppressGuard
#define KAMI_MODEL_H
//! @endcond
#include <kami/agent.h>
#include <memory>
#include <kami/domain.h>
#include <kami/kami.h>
#include <kami/population.h>
#include <kami/scheduler.h>
namespace kami {
/**
* An abstract for generic models
*/
class LIBKAMI_EXPORT Model {
/**
* @brief An abstract for generic models
*
* @see `ReporterModel`
*/
class LIBKAMI_EXPORT Model
: public std::enable_shared_from_this<Model> {
public:
/**
* Get a reference to an `Agent` by `AgentID`
* @brief Get the `Domain` associated with this model
*
* @param[in] agent_id the `AgentID` to search for.
*
* @return a reference to the desired `Agent` or `nullptr` if not found.
* @returns a shared pointer to the `Domain`
*/
[[nodiscard]] virtual Agent *get_agent_by_id(AgentID agent_id) const = 0;
std::shared_ptr<Domain> get_domain();
/**
* Execute a fixed number of time-steps for the model.
* @brief Add a `Domain` to this scheduler
*
* This function should execute a fixed number of time-steps for the model.
* @details This method will associate a model with the
* scheduler.
*
* @param[in] n the number of time steps to execute.
* @returns a shared pointer to the `Domain`
*/
[[maybe_unused]] virtual void run(unsigned int n) = 0;
std::shared_ptr<Domain> set_domain(std::shared_ptr<Domain> domain);
/**
* Execute a single time-step for the model.
* @brief Get the `Population` associated with this model
*
* This function should step the model instance. Any activities that the
* model should perform as part of its time step should be in this function.
* @returns a shared pointer to the `Population`
*/
virtual void step() = 0;
std::shared_ptr<Population> get_population();
/**
* @brief Add a `Model` to this scheduler
*
* @details This method will associate a model with the
* scheduler.
*
* @returns a shared pointer to the `Population`
*/
std::shared_ptr<Population> set_population(std::shared_ptr<Population> population);
/**
* @brief Get the `Scheduler` associated with this model
*
* @returns a shared pointer to the `Scheduler`
*/
std::shared_ptr<Scheduler> get_scheduler();
/**
* @brief Add a `Model` to this scheduler
*
* @details This method will associate a model with the
* scheduler.
*
* @returns a shared pointer to the `Scheduler`
*/
std::shared_ptr<Scheduler> set_scheduler(std::shared_ptr<Scheduler> scheduler);
/**
* @brief Execute a single time step of the model
*
* @details This method will collect all the `Agent`s in the `Population` associated
* with model and pass them to the associated `Scheduler` for stepping.
*
* @returns a shared pointer to the model instance
*/
virtual std::shared_ptr<Model> step();
protected:
/**
* @brief Reference copy of the `Domain`
*/
std::shared_ptr<Domain> _domain = nullptr;
/**
* @brief Reference copy of the `Population`
*/
std::shared_ptr<Population> _pop = nullptr;
/**
* @brief Reference copy of the `Scheduler`
*/
std::shared_ptr<Scheduler> _sched = nullptr;
};
} // namespace kami

View File

@@ -25,7 +25,9 @@
#pragma once
#ifndef KAMI_MULTIGRID1D_H
//! @cond SuppressGuard
#define KAMI_MULTIGRID1D_H
//! @endcond
#include <kami/agent.h>
#include <kami/domain.h>
@@ -35,24 +37,30 @@
namespace kami {
/**
* @brief A one-dimensional grid where each cell may contain multiple agents
*
* @details The grid is linear and may wrap around in its only dimension.
*/
class LIBKAMI_EXPORT MultiGrid1D : public Grid1D {
/**
* @brief A one-dimensional grid where each cell may contain multiple agents
*
* @details The grid is linear and may wrap around in its only dimension.
*
* @see `Grid1D`
* @see `SoloGrid1D`
*/
class LIBKAMI_EXPORT MultiGrid1D
: public Grid1D {
public:
/**
* Constructor
* @brief Constructor
*
* @param[in] maximum_x the length of the grid.
* @param[in] wrap_x should the grid wrap around on itself.
*/
MultiGrid1D(unsigned int maximum_x, bool wrap_x)
: Grid1D(maximum_x, wrap_x) {}
MultiGrid1D(
unsigned int maximum_x,
bool wrap_x
);
/**
* Place agent on the grid at the specified location.
* @brief Place agent on the grid at the specified location.
*
* @param[in] agent_id the `AgentID` of the agent to add.
* @param[in] coord the coordinates of the agent.
@@ -60,7 +68,10 @@ namespace kami {
* @returns false if the agent is not placed at the specified
* location, otherwise, true
*/
bool add_agent(AgentID agent_id, GridCoord1D coord) override;
AgentID add_agent(
AgentID agent_id,
const GridCoord1D& coord
) override;
};
} // namespace kami

View File

@@ -25,7 +25,9 @@
#pragma once
#ifndef KAMI_MULTIGRID2D_H
//! @cond SuppressGuard
#define KAMI_MULTIGRID2D_H
//! @endcond
#include <kami/agent.h>
#include <kami/domain.h>
@@ -35,18 +37,19 @@
namespace kami {
/**
* @brief A two-dimensional grid where each cell may contain multiple agents
*
* @details The grid is linear and may wrap around in its only dimension.
*
* @see `Grid2D`
* @see `SoloGrid2D`
*/
class LIBKAMI_EXPORT MultiGrid2D : public Grid2D {
/**
* @brief A two-dimensional grid where each cell may contain multiple agents
*
* @details The grid is linear and may wrap around in either dimension.
*
* @see `Grid2D`
* @see `SoloGrid2D`
*/
class LIBKAMI_EXPORT MultiGrid2D
: public Grid2D {
public:
/**
* Constructor
* @brief Constructor
*
* @param[in] maximum_x the length of the grid in the first dimension
* @param[in] maximum_y the length of the grid in the second dimension
@@ -55,19 +58,25 @@ namespace kami {
* @param[in] wrap_y should the grid wrap around on itself in the second
* dimension
*/
MultiGrid2D(unsigned int maximum_x, unsigned int maximum_y, bool wrap_x, bool wrap_y)
: Grid2D(maximum_x, maximum_y, wrap_x, wrap_y) {};
MultiGrid2D(
unsigned int maximum_x,
unsigned int maximum_y,
bool wrap_x,
bool wrap_y
);
/**
* Place agent on the grid at the specified location.
* @brief Place agent on the grid at the specified location.
*
* @param[in] agent_id the `AgentID` of the agent to add.
* @param[in] coord the coordinates of the agent.
*
* @returns false if the agent is not placed at the specified
* location, otherwise, true
* @returns the `AgentID` of the agent added
*/
bool add_agent(AgentID agent_id, GridCoord2D coord) override;
AgentID add_agent(
AgentID agent_id,
const GridCoord2D& coord
) override;
};
} // namespace kami

92
include/kami/population.h Normal file
View File

@@ -0,0 +1,92 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#ifndef KAMI_POPULATION_H
//! @cond SuppressGuard
#define KAMI_POPULATION_H
//! @endcond
#include <map>
#include <vector>
#include <kami/agent.h>
#include <kami/kami.h>
namespace kami {
/**
* @brief An abstract for generic models
*/
class LIBKAMI_EXPORT Population {
public:
/**
* @brief Get a reference to an `Agent` by `AgentID`
*
* @param[in] agent_id the `AgentID` to search for.
*
* @return a reference to the desired `Agent` or nothing is not found
*/
[[nodiscard]] std::shared_ptr<Agent> get_agent_by_id(AgentID agent_id) const;
/**
* @brief Add an Agent to the Population.
*
* @param agent The Agent to add.
*
* @returns the ID of the agent added
*/
AgentID add_agent(const std::shared_ptr<Agent>& agent) noexcept;
/**
* @brief Remove an Agent from the Population.
*
* @param agent_id The AgentID of the agent to remove.
*
* @returns a shared pointer to the Agent deleted
*/
std::shared_ptr<Agent> delete_agent(AgentID agent_id);
/**
* @brief Returns the agent list.
*
* @returns a `std::vector` of all the `AgentID`'s in the `Population`
*/
[[nodiscard]] std::unique_ptr<std::vector<AgentID>> get_agent_list() const;
protected:
/**
* @brief A mapping of `AgentID` to `Agent` pointers
*
* @details This is the mapping of all `AgentID`s to
* pointers to the corresponding `Agent` in this population.
* This is left exposed as protected should any subclass
* wish to manipulate this mapping directly.
*/
std::map<kami::AgentID, std::shared_ptr<Agent>> _agent_map;
};
} // namespace kami
#endif // KAMI_POPULATION_H

46
include/kami/position.h Normal file
View File

@@ -0,0 +1,46 @@
/*-
* Copyright (c) 2020 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#ifndef KAMI_POSITION_H
//! @cond SuppressGuard
#define KAMI_POSITION_H
//! @endcond
#include <variant>
#include <kami/grid1d.h>
#include <kami/grid2d.h>
namespace kami {
typedef std::variant<
GridCoord1D,
GridCoord2D
> Position;
}
#endif //KAMI_POSITION_H

View File

@@ -25,70 +25,102 @@
#pragma once
#ifndef KAMI_RANDOM_H
//! @cond SuppressGuard
#define KAMI_RANDOM_H
//! @endcond
#include <kami/kami.h>
#include <kami/sequential.h>
#include <algorithm>
#include <memory>
#include <random>
#include <vector>
namespace kami {
#include <kami/kami.h>
#include <kami/scheduler.h>
#include <kami/sequential.h>
/**
* Will execute all agent steps in a random order.
*
* 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 guaranteed not to repeat.
*
* @note First create a Model for the scheduler to live in.
*/
class LIBKAMI_EXPORT RandomScheduler : public SequentialScheduler {
namespace kami {
/**
* @brief Will execute all agent steps in a random order.
*
* @details 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 guaranteed not to repeat.
*/
class LIBKAMI_EXPORT RandomScheduler
: public SequentialScheduler,
std::enable_shared_from_this<RandomScheduler> {
public:
/**
* @brief Constructor.
*/
RandomScheduler() = default;
/**
* @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, std::shared_ptr<std::mt19937> rng);
explicit RandomScheduler(std::shared_ptr<std::mt19937> rng);
/**
* @brief Execute a single time step.
*
* @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.
* @details This method will randomize the list of Agents provided
* then execute the `Agent::step()` method for every Agent listed.
*
* @param model a reference copy of the model
* @param agent_list list of agents to execute the step
*
* @returns returns vector of agents successfully stepped
*/
void step() override;
std::unique_ptr<std::vector<AgentID>>
step(
std::shared_ptr<Model> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) override;
/**
* Set the random number generator used to randomize the order of agent
* @brief Execute a single time step for a `ReporterModel`
*
* @details This method will randomize the list of Agents provided
* then execute the `Agent::step()` method for every Agent listed.
*
* @param model a reference copy of the `ReporterModel`
* @param agent_list list of agents to execute the step
*
* @returns returns vector of agents successfully stepped
*/
std::unique_ptr<std::vector<AgentID>>
step(
std::shared_ptr<ReporterModel> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) override;
/**
* @brief Set the RNG
*
* @details Set the random number generator used to randomize the order of agent
* stepping.
*
* @param rng [in] A uniform random number generator of type `std::mt19937`,
* used as the source of randomness.
*
* @returns a shared pointer to the random number generator
*/
void set_rng(std::shared_ptr<std::mt19937> rng);
std::shared_ptr<std::mt19937> set_rng(std::shared_ptr<std::mt19937> rng);
/**
* Get a reference to the random number generator used to randomize
* @brief Get the RNG
*
* @details Get a reference to the random number generator used to randomize
* the order of agent stepping.
*/
[[maybe_unused]] std::shared_ptr<std::mt19937> get_rng();
std::shared_ptr<std::mt19937> get_rng();
private:
std::shared_ptr<std::mt19937> _rng;
std::shared_ptr<std::mt19937> _rng = nullptr;
};
} // namespace kami

259
include/kami/reporter.h Normal file
View File

@@ -0,0 +1,259 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#ifndef KAMI_REPORTER_H
//! @cond SuppressGuard
#define KAMI_REPORTER_H
//! @endcond
#include <memory>
#include <vector>
#include <nlohmann/json.hpp>
#include <kami/agent.h>
#include <kami/model.h>
namespace kami {
class Reporter;
class ReporterAgent;
class ReporterModel;
/**
* @brief A superclass for all reporting agents
*
* @details All reporting agents should subclass the `ReportingAgent` class. At a minimum,
* subclasses must implement the `step()` function, to execute a single time step for each
* agent.
*
* @see `Agent`, `StagedAgent`
*/
class LIBKAMI_EXPORT ReporterAgent
: public Agent {
public:
/**
* @brief Collect the current state of the agent
*
* @details This function should collect the agent's
* current state. The agent, notably, does not need
* to collect historical state, as the historical state
* is retained by the `Reporter` instance until such
* time as `Reporter::report()` is called. However,
* the implementation of the collect() function is
* up to the end user who can, ultimately, return whatever
* they want.
*
* The only restriction on collect is that it must return
* its data as a [nlohmann::json](https://json.nlohmann.me/)
* JSON object. See `Reporter` for additional details.
*/
virtual std::unique_ptr<nlohmann::json> collect() = 0;
/**
* @brief Execute a time-step for the agent
*
* @details This function should step the agent instance. Any activities that the
* agent should perform as part of its time step should be in this function.
*
* @param model a reference copy of the model
*
* @returns a copy of the AgentID
*/
virtual AgentID step(std::shared_ptr<ReporterModel> model) = 0;
private:
// This should be uncallable, but knocks out the inherited method.
AgentID step(std::shared_ptr<Model> model) override;;
int _step_counter = 0;
};
/**
* @brief An abstract for generic models with a reporting capability
*
* @see `Model`
*/
class LIBKAMI_EXPORT ReporterModel
: public Model {
public:
/**
* @brief Constructor
*/
ReporterModel();
/**
* @brief Collect the current state of the model
*
* @details This function should collect the model's
* current state. The model, notably, does not need
* to collect historical state, as the historical state
* is retained by the `Reporter` instance until such
* time as `Reporter::report()` is called. However,
* the implementation of the collect() function is
* up to the end user who can, ultimately, return whatever
* they want.
*
* This is not expected to return agent data collection,
* as the agents' information is collected separately.
*/
virtual std::unique_ptr<nlohmann::json> collect() = 0;
/**
* @brief Get the step id of the model
*
* @details The step_id should probably be a monotonically
* incrementing integer.
*/
virtual unsigned int get_step_id();
/**
* @brief Execute a single time step of the model
*
* @details This method will collect all the `Agent`s in the `Population` associated
* with the model and pass them to the associated `Scheduler` for stepping. After scheduling,
* this method will run the collect() for the `Reporter` associated with this model.
*
* @returns a shared pointer to the model instance
*/
virtual std::shared_ptr<Model> step() override;
/**
* @brief Get the current report
*
* @details This method will return an object containg the data collected to that
* point in the simulation.
*
* @returns a unique pointer to a `nlohmann::json` object representing the current report
*/
std::unique_ptr<nlohmann::json> report();
protected:
/**
* @brief The current report
*/
std::shared_ptr<Reporter> _rpt;
/**
* @brief The model's current step count
*/
unsigned int _step_count{};
};
/**
* @brief A `Reporter` is a module that works with `ReporterAgent` and `ReporterModel`
* to collect information about the state of the model for later analysis
*/
class LIBKAMI_EXPORT Reporter
: public std::enable_shared_from_this<Reporter> {
public:
/**
* @brief Constructor
*/
Reporter();
/**
* @brief Empty the report
*
* @details Clear all entries from the report; new collection
* operations begin with a blank slate.
*
* @returns a reference copy of the `Reporter`
*/
std::shared_ptr<Reporter> clear();
/**
* @brief Collect the current state of the model
*
* @details This will collect the current state of
* each agent associated with the population returned
* by the `Model`'s `get_population()`.
*
* @param model reference copy of the model
*
* @returns a copy of the current report
*/
std::unique_ptr<nlohmann::json> collect(const std::shared_ptr<ReporterModel>& model);
/**
* @brief Collect the current state of the model
*
* @details This will collect the current state of
* each agent associated with the `Population`.
*
* @param model reference copy of the model
* @param pop Population to collect on
*
* @returns a copy of the current report
*/
std::unique_ptr<nlohmann::json>
collect(
const std::shared_ptr<ReporterModel>& model,
const std::shared_ptr<Population>& pop
);
/**
* @brief Collect the current state of the model
*
* @details This will collect the current state of
* each agent given
*
* @param model reference copy of the model
* @param agent_list a vector agents to report on
*
* @returns a copy of the current report
*/
std::unique_ptr<nlohmann::json>
collect(
const std::shared_ptr<ReporterModel>& model,
const std::unique_ptr<std::vector<AgentID>>& agent_list
);
/**
* @brief Collect the report
*
* @details This will return the aggregate report
* of all the data collected by this `Reporter`.
*
* @param model reference copy of the model
*
* @returns a copy of the current report
*/
std::unique_ptr<nlohmann::json> report(const std::shared_ptr<ReporterModel>& model);
protected:
/**
* @brief A vector of the the report collected so far
*/
std::unique_ptr<std::vector<nlohmann::json>> _report_data = nullptr;
};
}
#endif //KAMI_REPORTER_H

View File

@@ -25,13 +25,18 @@
#pragma once
#ifndef KAMI_SCHEDULER_H
//! @cond SuppressGuard
#define KAMI_SCHEDULER_H
//! @endcond
#include <memory>
#include <vector>
#include <kami/KAMI_EXPORT.h>
#include <kami/agent.h>
#include <kami/kami.h>
#include <kami/model.h>
namespace kami {
/**
* Create a Kami scheduler.
*
@@ -42,25 +47,76 @@ namespace kami {
class LIBKAMI_EXPORT Scheduler {
public:
/**
* Add an Agent to the Scheduler.
* @brief Execute a single time step.
*
* @param agent_id The AgentID of the agent to add.
* @details This method will step through the list of Agents in the
* `Population` associated with `model` and then execute the `Agent::step()`
* method for every Agent assigned to this scheduler in the order
* assigned.
*
* @param model a reference copy of the model
*
* @returns returns vector of agents successfully stepped
*/
virtual void add_agent(AgentID agent_id) = 0;
virtual std::unique_ptr<std::vector<AgentID>> step(std::shared_ptr<Model> model) = 0;
/**
* Remove an Agent from the Scheduler.
* @brief Execute a single time step for a `ReporterModel`
*
* @param agent_id The AgentID of the agent to remove.
* @details This method will step through 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 order
* assigned.
*
* @param model a reference copy of the `ReporterModel`
*
* @returns returns vector of agents successfully stepped
*/
[[maybe_unused]] virtual void delete_agent(AgentID agent_id) = 0;
virtual std::unique_ptr<std::vector<AgentID>> step(std::shared_ptr<ReporterModel> model) = 0;
/**
* Step the Scheduler.
* @brief Execute a single time step.
*
* A generic step function that executes a single time step.
* @details This method will step through the list of Agents
* provided and then execute the `Agent::step()`
* method for every Agent assigned to this scheduler in the order
* assigned.
*
* @param model a reference copy of the model
* @param agent_list list of agents to execute the step
*
* @returns returns vector of agents successfully stepped
*/
virtual void step() = 0;
virtual std::unique_ptr<std::vector<AgentID>>
step(
std::shared_ptr<Model> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) = 0;
/**
* @brief Execute a single time step for a `ReporterModel`
*
* @details This method will step through 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 order
* assigned.
*
* @param model a reference copy of the `ReporterModel`
* @param agent_list list of agents to execute the step
*
* @returns returns vector of agents successfully stepped
*/
virtual std::unique_ptr<std::vector<AgentID>>
step(
std::shared_ptr<ReporterModel> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) = 0;
protected:
/**
* Counter to increment on each step
*/
int _step_counter = 0;
};
} // namespace kami

View File

@@ -25,56 +25,31 @@
#pragma once
#ifndef KAMI_SEQUENTIAL_H
//! @cond SuppressGuard
#define KAMI_SEQUENTIAL_H
//! @endcond
#include <algorithm>
#include <memory>
#include <vector>
#include <kami/KAMI_EXPORT.h>
#include <kami/agent.h>
#include <kami/kami.h>
#include <kami/model.h>
#include <kami/scheduler.h>
namespace kami {
/**
* @brief Will execute all agent steps in a sequential order.
*
* @details A sequential scheduler will iterate over the agents assigned
* to the scheduler and call their `step()` function in a sequential order.
* That order is preserved between calls to `step()` but may be modified by
* `addAgent()` or `deleteAgent()`.
*
* \pre First create a Model for the scheduler to live in.
*/
class LIBKAMI_EXPORT SequentialScheduler : public Scheduler {
/**
* @brief Will execute all agent steps in a sequential order.
*
* @details A sequential scheduler will iterate over the agents assigned
* to the scheduler and call their `step()` function in a sequential order.
* That order is preserved between calls to `step()` but may be modified by
* `addAgent()` or `deleteAgent()`.
*/
class LIBKAMI_EXPORT SequentialScheduler
: public Scheduler {
public:
/**
* @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 Agents in the Model and the Model can be queried for
* a reference to any particular Agent at `step()` time.
*/
explicit SequentialScheduler(Model *model);
/**
* @brief Add an agent to the scheduler.
*
* @details The scheduler maintains a list of all AgentIDs currently
* assigned. This function adds a new Agent to the list.
*/
void add_agent(AgentID agent_id) override;
/**
* @brief Remove an agent from the scheduler.
*
* @details The scheduler maintains a list of all AgentIDs currently
* assigned. This function removes an Agent from the list.
*/
void delete_agent(AgentID agent_id) override;
/**
* @brief Execute a single time step.
*
@@ -82,25 +57,64 @@ namespace kami {
* scheduler's internal queue and then execute the `Agent::step()`
* method for every Agent assigned to this scheduler in the order
* assigned.
*
* @param model a reference copy of the model
*
* @returns returns vector of agents successfully stepped
*/
void step() override;
protected:
/**
* A vector containing the `AgentID`s of all agents assigned to this
* scheduler
*/
std::vector<AgentID> _agent_list;
std::unique_ptr<std::vector<AgentID>> step(std::shared_ptr<Model> model) override;
/**
* A pointer to the `Model` this scheduler belongs to
* @brief Execute a single time step for a `ReporterModel`
*
* @details This method will step through 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 order
* assigned.
*
* @param model a reference copy of the `ReporterModel`
*
* @returns returns vector of agents successfully stepped
*/
Model *_model;
std::unique_ptr<std::vector<AgentID>> step(std::shared_ptr<ReporterModel> model) override;
/**
* Counter to increment on each step
* @brief Execute a single time step.
*
* @details This method will step through the list of Agents
* provided and then execute the `Agent::step()`
* method for every Agent assigned to this scheduler in the order
* assigned.
*
* @param model a reference copy of the model
* @param agent_list list of agents to execute the step
*
* @returns returns vector of agents successfully stepped
*/
int _step_counter;
std::unique_ptr<std::vector<AgentID>>
step(
std::shared_ptr<Model> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) override;
/**
* @brief Execute a single time step for a `ReporterModel`
*
* @details This method will step through 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 order
* assigned.
*
* @param model a reference copy of the `ReporterModel`
* @param agent_list list of agents to execute the step
*
* @returns returns vector of agents successfully stepped
*/
std::unique_ptr<std::vector<AgentID>>
step(
std::shared_ptr<ReporterModel> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) override;
};
} // namespace kami

View File

@@ -25,7 +25,9 @@
#pragma once
#ifndef KAMI_SOLOGRID1D_H
//! @cond SuppressGuard
#define KAMI_SOLOGRID1D_H
//! @endcond
#include <kami/KAMI_EXPORT.h>
#include <kami/agent.h>
@@ -34,24 +36,30 @@
namespace kami {
/**
* @brief A one-dimensional grid where each cell may contain only one Agent
*
* @details The grid is linear and may wrap around in its only dimension.
*/
class LIBKAMI_EXPORT SoloGrid1D : public Grid1D {
/**
* @brief A one-dimensional grid where each cell may contain one agents
*
* @details The grid is linear and may wrap around in its only dimension.
*
* @see `Grid1D`
* @see `MultiGrid1D`
*/
class LIBKAMI_EXPORT SoloGrid1D
: public Grid1D {
public:
/**
* Constructor
* @brief Constructor
*
* @param[in] maximum_x the length of the grid.
* @param[in] wrap_x should the grid wrap around on itself.
*/
SoloGrid1D(unsigned int maximum_x, bool wrap_x)
: Grid1D(maximum_x, wrap_x) {}
SoloGrid1D(
unsigned int maximum_x,
bool wrap_x
);
/**
* Place agent on the grid at the specified location.
* @brief Place agent on the grid at the specified location.
*
* @param[in] agent_id the `AgentID` of the agent to add.
* @param[in] coord the coordinates of the agent.
@@ -59,7 +67,10 @@ namespace kami {
* @returns false if the agent is not placed at the specified
* location, otherwise, true
*/
bool add_agent(AgentID agent_id, GridCoord1D coord) override;
AgentID add_agent(
AgentID agent_id,
const GridCoord1D& coord
) override;
};
} // namespace kami

View File

@@ -25,7 +25,9 @@
#pragma once
#ifndef KAMI_SOLOGRID2D_H
//! @cond SuppressGuard
#define KAMI_SOLOGRID2D_H
//! @endcond
#include <kami/KAMI_EXPORT.h>
#include <kami/agent.h>
@@ -34,29 +36,34 @@
namespace kami {
/**
* @brief A two-dimensional grid where each cell may contain only one Agent
*
* @details The grid is linear and may wrap around in its only dimension.
*
* @see `MultiGrid2D`
* @see `Grid2D`
*/
class LIBKAMI_EXPORT SoloGrid2D : public Grid2D {
/**
* @brief A two-dimensional grid where each cell may contain multiple agents
*
* @details The grid is linear and may wrap around in either dimension.
*
* @see `Grid2D`
* @see `MultiGrid2D`
*/
class LIBKAMI_EXPORT SoloGrid2D
: public Grid2D {
public:
/**
* Constructor
* @details Constructor
*
* @param[in] maximum_x the length of the grid in the first dimension
* @param[in] maximum_y the length of the grid in the second dimension
* @param[in] wrap_x should the grid wrap around on itself in the first dimension
* @param[in] wrap_y should the grid wrap around on itself in the second dimension
*/
SoloGrid2D(unsigned int maximum_x, unsigned int maximum_y, bool wrap_x, bool wrap_y)
: Grid2D(maximum_x, maximum_y, wrap_x, wrap_y) {};
SoloGrid2D(
unsigned int maximum_x,
unsigned int maximum_y,
bool wrap_x,
bool wrap_y
);;
/**
* Place agent on the grid at the specified location.
* @details Place agent on the grid at the specified location.
*
* @param[in] agent_id the `AgentID` of the agent to add.
* @param[in] coord the coordinates of the agent.
@@ -64,7 +71,11 @@ namespace kami {
* @returns false if the agent is not placed at the specified
* location, otherwise, true
*/
bool add_agent(AgentID agent_id, GridCoord2D coord) override;
AgentID add_agent(
AgentID agent_id,
const GridCoord2D& coord
) override;
};
} // namespace kami

View File

@@ -25,73 +25,90 @@
#pragma once
#ifndef KAMI_STAGED_H
//! @cond SuppressGuard
#define KAMI_STAGED_H
//! @endcond
#include <memory>
#include <vector>
#include <kami/agent.h>
#include <kami/model.h>
#include <kami/scheduler.h>
#include <algorithm>
#include <vector>
#include <kami/sequential.h>
namespace kami {
/**
* Will execute all agent steps in a sequential order.
*
* A sequential scheduler will iterate over the agents assigned to the scheduler
* and call their `step()` function in a sequential order. That order is
* preserved between calls to `step()` but may be modified by `add_agent()` or
* `delete_agent()`.
*
* @note First create a Model for the scheduler to live in.
*/
class LIBKAMI_EXPORT StagedScheduler : public Scheduler {
/**
* @brief Will execute all agent steps in a sequential order.
*
* @details A sequential scheduler will iterate over the agents assigned to the scheduler
* and call their `step()` function in a sequential order. That order is
* preserved between calls to `step()` but may be modified by `add_agent()` or
* `delete_agent()`.
*/
class LIBKAMI_EXPORT StagedScheduler
: public SequentialScheduler {
public:
/**
* 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.
*/
explicit StagedScheduler(Model *);
/**
* A deconstructor.
*/
~StagedScheduler() = default;
/**
* Add an agent to the scheduler.
* @brief Execute a single time step
*
* The scheduler maintains a list of all AgentIDs currently assigned. This
* function adds a new Agent to the list.
*/
void add_agent(AgentID agent_id) override;
/**
* Remove an agent from the scheduler.
*
* The scheduler maintains a list of all AgentIDs currently assigned. This
* function removes an Agent from the list.
*/
void delete_agent(AgentID agent_id) override;
/**
* Execute a single time step.
*
* This method will step through the list of Agents in the scheduler's
* @details This method will step through the list of Agents in the scheduler's
* internal queue and execute the `Agent::step()` method for each `Agent`
* in the same order. Finally, it will execute the `Agent::advance()`
* method for each Agent in the same order.
*
* @param model a reference copy of the model
* @param agent_list list of agents to execute the step
*
* @returns returns vector of agents successfully stepped
*/
void step() override;
std::unique_ptr<std::vector<AgentID>>
step(
std::shared_ptr<Model> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) override;
std::unique_ptr<std::vector<AgentID>>
step(
std::shared_ptr<ReporterModel> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) override;
private:
std::vector<AgentID> _agent_list;
Model *_model;
int _step_counter;
/**
* @brief Advance a single time step.
*
* @details This method will step through the list of StagedAgent in the
* scheduler's internal queue and then execute the `StagedAgent::step()`
* method for every StagedAgent assigned to this scheduler in the order
* assigned.
*
* @param model a reference copy of the model
*
* @returns returns vector of agents successfully advanced
*/
std::unique_ptr<std::vector<AgentID>> advance(std::shared_ptr<Model> model);
std::unique_ptr<std::vector<AgentID>> advance(std::shared_ptr<ReporterModel> model);
/**
* @brief Advance a single time step.
*
* @details This method will step through the list of StagedAgent
* provided and then execute the `StagedAgent::advance()`
* method for every StagedAgent assigned to this scheduler in the order
* assigned.
*
* @param model a reference copy of the model
* @param agent_list list of agents to execute the step
*
* @returns returns vector of agents successfully advanced
*/
std::unique_ptr<std::vector<AgentID>>
advance(
std::shared_ptr<Model> model,
std::unique_ptr<std::vector<AgentID>> agent_list
);
};
} // namespace kami

View File

@@ -2,63 +2,61 @@
# Set minimum version of CMake.
cmake_minimum_required(VERSION 3.13)
project(libkami VERSION ${KAMI_VERSION_STRING}
LANGUAGES CXX)
set(LIBRARY_NAME "libkami")
create_library(NAME libkami
NAMESPACE libkami
SOURCES
agent.cc
domain.cc
grid1d.cc
grid2d.cc
multigrid1d.cc
multigrid2d.cc
random.cc
sequential.cc
sologrid1d.cc
sologrid2d.cc
staged.cc
PUBLIC_INCLUDE_PATHS
"$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/generated_headers>"
PRIVATE_LINKED_TARGETS
${COVERAGE_TARGET}
EXPORT_FILE_PATH
"${CMAKE_CURRENT_BINARY_DIR}/generated_headers/kami/KAMI_EXPORT.h"
)
project(${LIBRARY_NAME} LANGUAGES CXX)
project(${LIBRARY_NAME}
VERSION ${VERSION_STRING}
LANGUAGES CXX)
file(GLOB LIBRARY_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cc")
create_library(
NAME ${LIBRARY_NAME}
NAMESPACE ${LIBRARY_NAME}
SOURCES ${LIBRARY_SOURCES}
PUBLIC_INCLUDE_PATHS "$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/generated_headers>"
PRIVATE_LINKED_TARGETS ${COVERAGE_TARGET}
PUBLIC_LINKED_TARGETS fmt
EXPORT_FILE_PATH "${CMAKE_CURRENT_BINARY_DIR}/generated_headers/kami/KAMI_EXPORT.h"
)
configure_file(
"${CMAKE_SOURCE_DIR}/include/kami/config.h.in"
"${CMAKE_CURRENT_BINARY_DIR}/generated_headers/kami/config.h")
"${CMAKE_SOURCE_DIR}/include/kami/config.h.in"
"${CMAKE_CURRENT_BINARY_DIR}/generated_headers/kami/config.h"
)
set_target_properties(libkami PROPERTIES VERSION ${KAMI_VERSION_STRING}
SOVERSION ${KAMI_VERSION_MAJOR}
OUTPUT_NAME kami)
set_target_properties(
${LIBRARY_NAME}
PROPERTIES VERSION ${VERSION_STRING}
SOVERSION ${VERSION_MAJOR}
OUTPUT_NAME kami
)
# Introduce variables:
# * CMAKE_INSTALL_LIBDIR
# * CMAKE_INSTALL_BINDIR
# * CMAKE_INSTALL_INCLUDEDIR
include(GNUInstallDirs)
# Introduce variables:
# * CMAKE_INSTALL_LIBDIR
# * CMAKE_INSTALL_BINDIR
# * CMAKE_INSTALL_INCLUDEDIR
include(GNUInstallDirs)
# Headers:
# * include/foo/bar/bar.h -> <prefix>/include/NAMESPACE/LIBRARY_NAME/*.h
# * include/foo/bar/bar.h -> <prefix>/include/foo/bar/bar.h
install(
DIRECTORY "${CMAKE_SOURCE_DIR}/include/kami"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
FILES_MATCHING PATTERN "*"
)
# Headers:
# * include/foo/bar/bar.h -> <prefix>/include/NAMESPACE/LIBRARY_NAME/*.h
# * include/foo/bar/bar.h -> <prefix>/include/foo/bar/bar.h
install(
DIRECTORY "${CMAKE_SOURCE_DIR}/include/kami"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
FILES_MATCHING PATTERN "*"
)
# Export headers:
# The export header will be stored in:
# <prefix>/include/${NAMESPACE}/LIBRARY_NAME/LIBRARY_NAME_export.h
install(
FILES
"${CMAKE_CURRENT_BINARY_DIR}/generated_headers/kami/KAMI_EXPORT.h"
"${CMAKE_CURRENT_BINARY_DIR}/generated_headers/kami/config.h"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/kami"
)
# Export headers:
# The export header will be stored in:
# <prefix>/include/${NAMESPACE}/LIBRARY_NAME/LIBRARY_NAME_export.h
install(
FILES
"${CMAKE_CURRENT_BINARY_DIR}/generated_headers/kami/KAMI_EXPORT.h"
"${CMAKE_CURRENT_BINARY_DIR}/generated_headers/kami/config.h"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/kami"
)
add_library(kami::libkami ALIAS libkami)
add_library(kami::libkami ALIAS libkami)

View File

@@ -23,35 +23,65 @@
* SOFTWARE.
*/
#include <kami/agent.h>
#include <iostream>
#include <string>
#include <kami/agent.h>
namespace kami {
bool operator==(const AgentID &lhs, const AgentID &rhs) {
AgentID::AgentID()
:_id(_id_next++) {
}
std::string AgentID::to_string() const {
return std::to_string(_id);
}
bool operator==(
const AgentID& lhs,
const AgentID& rhs
) {
return lhs._id == rhs._id;
}
bool operator!=(const AgentID &lhs, const AgentID &rhs) {
bool operator!=(
const AgentID& lhs,
const AgentID& rhs
) {
return !(lhs == rhs);
}
bool operator<(const AgentID &lhs, const AgentID &rhs) {
bool operator<(
const AgentID& lhs,
const AgentID& rhs
) {
return lhs._id < rhs._id;
}
std::ostream &operator<<(std::ostream &lhs, const AgentID &rhs) {
std::ostream& operator<<(
std::ostream& lhs,
const AgentID& rhs
) {
return lhs << rhs.to_string();
}
AgentID Agent::get_agent_id() const { return this->_agent_id; }
AgentID Agent::get_agent_id() const {
return this->_agent_id;
}
bool operator==(const Agent &lhs, const Agent &rhs) {
bool operator==(
const Agent& lhs,
const Agent& rhs
) {
return lhs._agent_id == rhs._agent_id;
}
bool operator!=(const Agent &lhs, const Agent &rhs) { return !(lhs == rhs); }
bool operator!=(
const Agent& lhs,
const Agent& rhs
) {
return !(lhs == rhs);
}
} // namespace kami

View File

@@ -23,14 +23,17 @@
* SOFTWARE.
*/
#include <kami/domain.h>
#include <iostream>
#include <string>
#include <kami/domain.h>
namespace kami {
std::ostream &operator<<(std::ostream &lhs, const Coord &rhs) {
std::ostream& operator<<(
std::ostream& lhs,
const Coord& rhs
) {
return lhs << rhs.to_string();
}

View File

@@ -23,132 +23,202 @@
* SOFTWARE.
*/
#include <map>
#include <memory>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <fmt/format.h>
#include <kami/agent.h>
#include <kami/domain.h>
#include <kami/error.h>
#include <kami/grid1d.h>
#include <map>
#include <utility>
#include <vector>
namespace kami {
int GridCoord1D::get_x_location() const { return _x_coord; }
GridCoord1D::GridCoord1D(int x_coord)
:_x_coord(x_coord) {
}
int GridCoord1D::x() const {
return _x_coord;
}
std::string GridCoord1D::to_string() const {
return std::string("(" + std::to_string(_x_coord) + ")");
}
bool operator==(const GridCoord1D &lhs, const GridCoord1D &rhs) {
double GridCoord1D::distance(std::shared_ptr<Coord>& p) const {
auto p1d = std::static_pointer_cast<GridCoord1D>(p);
return static_cast<double>(abs(_x_coord - p1d->_x_coord));
}
bool operator==(
const GridCoord1D& lhs,
const GridCoord1D& rhs
) {
return (lhs._x_coord == rhs._x_coord);
}
bool operator!=(const GridCoord1D &lhs, const GridCoord1D &rhs) {
bool operator!=(
const GridCoord1D& lhs,
const GridCoord1D& rhs
) {
return !(lhs == rhs);
}
std::ostream &operator<<(std::ostream &lhs, const GridCoord1D &rhs) {
std::ostream& operator<<(
std::ostream& lhs,
const GridCoord1D& rhs
) {
return lhs << rhs.to_string();
}
Grid1D::Grid1D(unsigned int maximum_x, bool wrap_x) {
_agent_grid = new std::vector<AgentID>[maximum_x];
_agent_index = new std::map<AgentID, GridCoord1D>;
GridCoord1D operator+(
const GridCoord1D& lhs,
const GridCoord1D& rhs
) {
return GridCoord1D(lhs._x_coord + rhs._x_coord);
}
GridCoord1D operator-(
const GridCoord1D& lhs,
const GridCoord1D& rhs
) {
return GridCoord1D(lhs._x_coord - rhs._x_coord);
}
GridCoord1D operator*(
const GridCoord1D& lhs,
const double rhs
) {
return GridCoord1D(static_cast<int>(lhs._x_coord * rhs));
}
GridCoord1D operator*(
const double lhs,
const GridCoord1D& rhs
) {
return GridCoord1D(static_cast<int>(rhs._x_coord * lhs));
}
Grid1D::Grid1D(
unsigned int maximum_x,
bool wrap_x
) {
_maximum_x = maximum_x;
_wrap_x = wrap_x;
_agent_grid = std::make_unique<std::unordered_multimap<GridCoord1D, AgentID>>();
_agent_index = std::make_unique<std::map<AgentID, GridCoord1D>>();
}
Grid1D::~Grid1D() {
delete _agent_index;
delete[] _agent_grid;
AgentID Grid1D::delete_agent(AgentID agent_id) {
return delete_agent(agent_id, get_location_by_agent(agent_id));
}
[[maybe_unused]] bool Grid1D::delete_agent(AgentID agent_id) {
GridCoord1D coord = get_location_by_agent(agent_id);
AgentID Grid1D::delete_agent(
AgentID agent_id,
const GridCoord1D& coord
) {
for (auto test_agent_id = _agent_grid->find(coord); test_agent_id != _agent_grid->end(); test_agent_id++)
if (test_agent_id->second == agent_id) {
_agent_grid->erase(test_agent_id);
_agent_index->erase(agent_id);
return agent_id;
}
return delete_agent(agent_id, coord);
throw error::AgentNotFound(
fmt::format("Agent {} not found at location {}", agent_id.to_string(), coord.to_string()));
}
bool Grid1D::is_location_valid(const GridCoord1D& coord) const {
auto x = coord.get_x_location();
auto x = coord.x();
return (x >= 0 && x < static_cast<int>(_maximum_x));
}
bool Grid1D::is_location_empty(const GridCoord1D &coord) const {
auto x = coord.get_x_location();
return _agent_grid[x].empty();
bool Grid1D::is_location_empty(const GridCoord1D& coord) const {
auto grid_location = _agent_grid->equal_range(coord);
return grid_location.first == grid_location.second;
}
bool Grid1D::delete_agent(AgentID agent_id, const GridCoord1D &coord) {
auto agent_list = _agent_grid[static_cast<int>(coord.get_x_location())];
for (auto test_agent_id = agent_list.begin();
test_agent_id < agent_list.end(); test_agent_id++) {
if (*test_agent_id == agent_id) {
agent_list.erase(test_agent_id);
_agent_index->erase(agent_id);
return true;
}
}
return false;
AgentID Grid1D::move_agent(
const AgentID agent_id,
const GridCoord1D& coord
) {
return add_agent(delete_agent(agent_id, get_location_by_agent(agent_id)), coord);
}
bool Grid1D::move_agent(AgentID agent_id, GridCoord1D coord) {
GridCoord1D coord_current = get_location_by_agent(agent_id);
if (delete_agent(agent_id, coord_current))
return add_agent(agent_id, std::move(coord));
return false;
std::shared_ptr<std::unordered_set<GridCoord1D>>
Grid1D::get_neighborhood(
const AgentID agent_id,
const bool include_center
) const {
return std::move(get_neighborhood(get_location_by_agent(agent_id), include_center));
}
std::vector<GridCoord1D> Grid1D::get_neighborhood(AgentID agent_id, bool include_center) const {
GridCoord1D coord = get_location_by_agent(agent_id);
return get_neighborhood(coord, include_center);
}
std::vector<GridCoord1D> Grid1D::get_neighborhood(const GridCoord1D& coord, bool include_center) const {
std::vector<GridCoord1D> neighborhood;
auto x = coord.get_x_location();
std::shared_ptr<std::unordered_set<GridCoord1D>>
Grid1D::get_neighborhood(
const GridCoord1D& coord,
const bool include_center
) const {
auto neighborhood = std::make_shared<std::unordered_set<GridCoord1D>>();
// We assume our starting position is valid
if (include_center) neighborhood.push_back(coord);
if (include_center)
neighborhood->insert(coord);
for (auto& direction : directions) {
auto new_location = coord_wrap(coord + direction);
// E, W
{
auto new_location = coord_wrap(GridCoord1D(x + 1));
if (is_location_valid(new_location))
neighborhood.push_back(coord_wrap(new_location));
}
{
auto new_location = coord_wrap(GridCoord1D(x - 1));
if (is_location_valid(new_location))
neighborhood.push_back(coord_wrap(new_location));
neighborhood->insert(new_location);
}
return neighborhood;
return std::move(neighborhood);
}
std::vector<AgentID> *Grid1D::get_location_contents(const GridCoord1D& coord) const {
if (is_location_valid(coord)) return &_agent_grid[coord.get_x_location()];
return nullptr;
std::shared_ptr<std::set<AgentID>> Grid1D::get_location_contents(const GridCoord1D& coord) const {
auto agent_ids = std::make_shared<std::set<AgentID>>();
if (!is_location_valid(coord))
throw error::LocationUnavailable(fmt::format("Coordinates {} are invalid", coord.to_string()));
if (is_location_empty(coord))
return agent_ids;
auto agent_range = _agent_grid->equal_range(coord);
if (agent_range.first == agent_range.second)
return agent_ids;
for (auto i = agent_range.first; i != agent_range.second; i++)
agent_ids->insert(i->second);
return agent_ids;
}
[[maybe_unused]] bool Grid1D::get_wrap_x() const { return _wrap_x; }
bool Grid1D::get_wrap_x() const {
return _wrap_x;
}
[[maybe_unused]] unsigned int Grid1D::get_maximum_x() const { return _maximum_x; }
unsigned int Grid1D::get_maximum_x() const {
return _maximum_x;
}
GridCoord1D Grid1D::get_location_by_agent(AgentID agent_id) const {
return _agent_index->at(agent_id);
GridCoord1D Grid1D::get_location_by_agent(const AgentID& agent_id) const {
auto coord = _agent_index->find(agent_id);
if (coord == _agent_index->end())
throw error::AgentNotFound(fmt::format("Agent {} not found on grid", agent_id.to_string()));
return coord->second;
}
GridCoord1D Grid1D::coord_wrap(const GridCoord1D& coord) const {
auto x = coord.get_x_location();
auto x = coord.x();
if(_wrap_x)
if (_wrap_x)
x = (x + static_cast<int>(_maximum_x)) % static_cast<int>(_maximum_x);
return GridCoord1D(x);
}

View File

@@ -24,184 +24,260 @@
*/
#include <map>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>
#include <fmt/format.h>
#include <kami/agent.h>
#include <kami/domain.h>
#include <kami/grid.h>
#include <kami/error.h>
#include <kami/grid2d.h>
namespace kami {
int GridCoord2D::get_x_location() const {
int GridCoord2D::x() const {
return _x_coord;
}
int GridCoord2D::get_y_location() const {
int GridCoord2D::y() const {
return _y_coord;
}
std::string GridCoord2D::to_string() const {
return std::string("(" + std::to_string(_x_coord) + ", " +
std::to_string(_y_coord) + ")");
return std::string("(" + std::to_string(_x_coord) + ", " + std::to_string(_y_coord) + ")");
}
bool operator==(const GridCoord2D &lhs, const GridCoord2D &rhs) {
double GridCoord2D::distance(std::shared_ptr<Coord>& p) const {
auto p2d = std::static_pointer_cast<GridCoord2D>(p);
return distance(p2d);
}
double GridCoord2D::distance(
std::shared_ptr<GridCoord2D>& p,
GridDistanceType distance_type
) const {
switch (distance_type) {
case GridDistanceType::Chebyshev:
return distance_chebyshev(p);
case GridDistanceType::Manhattan:
return distance_manhattan(p);
case GridDistanceType::Euclidean:
return distance_euclidean(p);
default:
throw error::OptionInvalid("Unknown distance type given");
}
}
bool operator==(
const GridCoord2D& lhs,
const GridCoord2D& rhs
) {
return (lhs._x_coord == rhs._x_coord && lhs._y_coord == rhs._y_coord);
}
bool operator!=(const GridCoord2D &lhs, const GridCoord2D &rhs) {
bool operator!=(
const GridCoord2D& lhs,
const GridCoord2D& rhs
) {
return !(lhs == rhs);
}
std::ostream &operator<<(std::ostream &lhs, const GridCoord2D &rhs) {
std::ostream& operator<<(
std::ostream& lhs,
const GridCoord2D& rhs
) {
return lhs << rhs.to_string();
}
Grid2D::Grid2D(unsigned int maximum_x, unsigned int maximum_y, bool wrap_x,
bool wrap_y) {
GridCoord2D::GridCoord2D(
int x_coord,
int y_coord
)
:_x_coord(x_coord), _y_coord(y_coord) {
}
double GridCoord2D::distance_chebyshev(std::shared_ptr<GridCoord2D>& p) const {
return static_cast<double>(fmax(abs(_x_coord - p->_x_coord), abs(_x_coord - p->_x_coord)));
}
double GridCoord2D::distance_euclidean(std::shared_ptr<GridCoord2D>& p) const {
return sqrt(pow(_x_coord - p->_x_coord, 2) + pow(_x_coord - p->_x_coord, 2));
}
double GridCoord2D::distance_manhattan(std::shared_ptr<GridCoord2D>& p) const {
return static_cast<double>(abs(_x_coord - p->_x_coord) + abs(_x_coord - p->_x_coord));
}
GridCoord2D operator+(
const GridCoord2D& lhs,
const GridCoord2D& rhs
) {
return {lhs._x_coord + rhs._x_coord, lhs._y_coord + rhs._y_coord};
}
GridCoord2D operator-(
const GridCoord2D& lhs,
const GridCoord2D& rhs
) {
return {lhs._x_coord - rhs._x_coord, lhs._y_coord - rhs._y_coord};
}
GridCoord2D operator*(
const GridCoord2D& lhs,
const double rhs
) {
return {static_cast<int>(lhs._x_coord * rhs), static_cast<int>(lhs._y_coord * rhs)};
}
GridCoord2D operator*(
const double lhs,
const GridCoord2D& rhs
) {
return {static_cast<int>(rhs._x_coord * lhs), static_cast<int>(rhs._y_coord * lhs)};
}
Grid2D::Grid2D(
unsigned int maximum_x,
unsigned int maximum_y,
bool wrap_x,
bool wrap_y
) {
_maximum_x = maximum_x;
_maximum_y = maximum_y;
_wrap_x = wrap_x;
_wrap_y = wrap_y;
_agent_grid = new std::vector<AgentID> *[_maximum_x];
for (auto i = 0; i < _maximum_x; i++)
_agent_grid[i] = new std::vector<AgentID>[_maximum_y];
_agent_index = new std::map<AgentID, GridCoord2D>;
_agent_grid = std::make_unique<std::unordered_multimap<GridCoord2D, AgentID>>();
_agent_index = std::make_unique<std::map<AgentID, GridCoord2D>>();
}
Grid2D::~Grid2D() {
delete _agent_index;
for (auto i = 0; i < _maximum_x; i++) delete[] _agent_grid[i];
delete[] _agent_grid;
AgentID Grid2D::delete_agent(const AgentID agent_id) {
return delete_agent(agent_id, get_location_by_agent(agent_id));
}
[[maybe_unused]] bool Grid2D::delete_agent(AgentID agent_id) {
auto coord = get_location_by_agent(agent_id);
return delete_agent(agent_id, coord);
}
bool Grid2D::delete_agent(AgentID agent_id, const GridCoord2D &coord) {
auto agent_list = _agent_grid[static_cast<int>(coord.get_x_location())][static_cast<int>(coord.get_y_location())];
for (auto test_agent_id = agent_list.begin();
test_agent_id < agent_list.end(); test_agent_id++) {
if (*test_agent_id == agent_id) {
agent_list.erase(test_agent_id);
AgentID Grid2D::delete_agent(
const AgentID agent_id,
const GridCoord2D& coord
) {
for (auto test_agent_id = _agent_grid->find(coord); test_agent_id != _agent_grid->end(); test_agent_id++)
if (test_agent_id->second == agent_id) {
_agent_grid->erase(test_agent_id);
_agent_index->erase(agent_id);
return true;
return agent_id;
}
}
return false;
throw error::AgentNotFound("Agent not found on grid");
}
bool Grid2D::is_location_valid(const GridCoord2D& coord) const {
auto x = coord.get_x_location();
auto y = coord.get_y_location();
auto x = coord.x();
auto y = coord.y();
return (x >= 0 && x < static_cast<int>(_maximum_x) && y >= 0 &&
y < static_cast<int>(_maximum_y));
return (x >= 0 && x < static_cast<int>(_maximum_x) &&
y >= 0 && y < static_cast<int>(_maximum_y));
}
GridCoord2D Grid2D::get_location_by_agent(AgentID agent_id) const {
return _agent_index->at(agent_id);
bool Grid2D::is_location_empty(const GridCoord2D& coord) const {
auto grid_location = _agent_grid->equal_range(coord);
return grid_location.first == grid_location.second;
}
bool Grid2D::move_agent(AgentID agent_id, GridCoord2D coord) {
GridCoord2D current_location = get_location_by_agent(agent_id);
if (delete_agent(agent_id, current_location))
return add_agent(agent_id, std::move(coord));
return false;
AgentID Grid2D::move_agent(
const AgentID agent_id,
const GridCoord2D& coord
) {
return add_agent(delete_agent(agent_id, get_location_by_agent(agent_id)), coord);
}
[[maybe_unused]] bool Grid2D::get_wrap_x() const { return _wrap_x; }
[[maybe_unused]] bool Grid2D::get_wrap_y() const { return _wrap_y; }
std::vector<GridCoord2D> Grid2D::get_neighborhood(
AgentID agent_id, GridNeighborhoodType neighborhood_type,
bool includeCenter) const {
GridCoord2D location = get_location_by_agent(agent_id);
return get_neighborhood(location, neighborhood_type, includeCenter);
std::shared_ptr<std::unordered_set<GridCoord2D>>
Grid2D::get_neighborhood(
const AgentID agent_id,
const bool include_center,
const GridNeighborhoodType neighborhood_type
) const {
return std::move(get_neighborhood(get_location_by_agent(agent_id), include_center, neighborhood_type));
}
std::vector<GridCoord2D> Grid2D::get_neighborhood(const GridCoord2D& location, GridNeighborhoodType neighborhood_type, bool include_center) const {
std::vector<GridCoord2D> neighborhood;
auto x = location.get_x_location();
auto y = location.get_y_location();
std::shared_ptr<std::unordered_set<GridCoord2D>>
Grid2D::get_neighborhood(
const GridCoord2D& coord,
const bool include_center,
const GridNeighborhoodType neighborhood_type
) const {
auto neighborhood = std::make_unique<std::unordered_set<GridCoord2D>>();
std::vector<GridCoord2D> directions;
// We assume our starting position is valid
if (include_center) neighborhood.push_back(location);
switch (neighborhood_type) {
case GridNeighborhoodType::VonNeumann:
directions = directions_vonneumann;
break;
case GridNeighborhoodType::Moore:
directions = directions_moore;
break;
default:
throw error::OptionInvalid(
fmt::format("Invalid neighborhood type {} given", (unsigned int) neighborhood_type));
}
if (include_center and is_location_valid(coord))
neighborhood->insert(coord);
for (auto& direction : directions) {
auto new_location = coord_wrap(coord + direction);
// N, E, S, W
{
auto new_location = coord_wrap(GridCoord2D(x, y - 1));
if (is_location_valid(new_location))
neighborhood.push_back(coord_wrap(new_location));
}
{
auto new_location = coord_wrap(GridCoord2D(x, y + 1));
if (is_location_valid(new_location))
neighborhood.push_back(coord_wrap(new_location));
}
{
auto new_location = coord_wrap(GridCoord2D(x + 1, y));
if (is_location_valid(new_location))
neighborhood.push_back(coord_wrap(new_location));
}
{
auto new_location = coord_wrap(GridCoord2D(x - 1, y));
if (is_location_valid(new_location))
neighborhood.push_back(coord_wrap(new_location));
neighborhood->insert(new_location);
}
if (neighborhood_type == GridNeighborhoodType::Moore) {
// NE, SE, SW, NW
{
auto new_location = coord_wrap(GridCoord2D(x + 1, y - 1));
if (is_location_valid(new_location))
neighborhood.push_back(coord_wrap(new_location));
}
{
auto new_location = coord_wrap(GridCoord2D(x + 1, y + 1));
if (is_location_valid(new_location))
neighborhood.push_back(coord_wrap(new_location));
}
{
auto new_location = coord_wrap(GridCoord2D(x - 1, y + 1));
if (is_location_valid(new_location))
neighborhood.push_back(coord_wrap(new_location));
}
{
auto new_location = coord_wrap(GridCoord2D(x - 1, y - 1));
if (is_location_valid(new_location))
neighborhood.push_back(coord_wrap(new_location));
}
}
return neighborhood;
return std::move(neighborhood);
}
std::vector<AgentID> *Grid2D::get_location_contents(const GridCoord2D& coord) const {
if (is_location_valid(coord) && !is_location_empty(coord))
return &_agent_grid[coord.get_x_location()][coord.get_y_location()];
std::shared_ptr<std::set<AgentID>> Grid2D::get_location_contents(const GridCoord2D& coord) const {
auto agent_ids = std::make_shared<std::set<AgentID>>();
return nullptr;
if (!is_location_valid(coord))
throw error::LocationUnavailable(fmt::format("Coordinates {} are invalid", coord.to_string()));
if (is_location_empty(coord))
return agent_ids;
auto agent_range = _agent_grid->equal_range(coord);
if (agent_range.first == agent_range.second)
return agent_ids;
for (auto i = agent_range.first; i != agent_range.second; i++)
agent_ids->insert(i->second);
return agent_ids;
}
[[maybe_unused]] unsigned int Grid2D::get_maximum_x() const { return _maximum_x; }
[[maybe_unused]] unsigned int Grid2D::get_maximum_y() const { return _maximum_y; }
bool Grid2D::get_wrap_x() const {
return _wrap_x;
}
bool Grid2D::get_wrap_y() const {
return _wrap_y;
}
unsigned int Grid2D::get_maximum_x() const {
return _maximum_x;
}
unsigned int Grid2D::get_maximum_y() const {
return _maximum_y;
}
GridCoord2D Grid2D::get_location_by_agent(const AgentID& agent_id) const {
auto coord = _agent_index->find(agent_id);
if (coord == _agent_index->end())
throw error::AgentNotFound(fmt::format("Agent {} not found on grid", agent_id.to_string()));
return coord->second;
}
GridCoord2D Grid2D::coord_wrap(const GridCoord2D& coord) const {
auto x = coord.get_x_location();
auto y = coord.get_y_location();
auto x = coord.x();
auto y = coord.y();
if (_wrap_x)
x = (x + static_cast<int>(_maximum_x)) % static_cast<int>(_maximum_x);
@@ -210,11 +286,4 @@ namespace kami {
return {x, y};
}
bool Grid2D::is_location_empty(const GridCoord2D& coord) const {
auto x = coord.get_x_location();
auto y = coord.get_y_location();
return _agent_grid[x][y].empty();
}
} // namespace kami

73
src/libkami/model.cc Normal file
View File

@@ -0,0 +1,73 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <memory>
#include <utility>
#include <kami/error.h>
#include <kami/model.h>
#include <kami/scheduler.h>
namespace kami {
std::shared_ptr<Domain> Model::get_domain() {
if (_domain == nullptr)
throw error::ResourceNotAvailable("Domain not found in model");
return _domain;
}
std::shared_ptr<Domain> Model::set_domain(std::shared_ptr<Domain> domain) {
_domain = std::move(domain);
return _domain;
}
std::shared_ptr<Population> Model::get_population() {
if (_pop == nullptr)
throw error::ResourceNotAvailable("Population not found in model");
return _pop;
}
std::shared_ptr<Population> Model::set_population(std::shared_ptr<Population> population) {
_pop = std::move(population);
return _pop;
}
std::shared_ptr<Scheduler> Model::get_scheduler() {
if (_sched == nullptr)
throw error::ResourceNotAvailable("Scheduler not found in model");
return _sched;
}
std::shared_ptr<Scheduler> Model::set_scheduler(std::shared_ptr<Scheduler> scheduler) {
_sched = std::move(scheduler);
return _sched;
}
std::shared_ptr<Model> Model::step() {
_sched->step(shared_from_this());
return shared_from_this();
}
} // namespace kami

View File

@@ -23,21 +23,33 @@
* SOFTWARE.
*/
#include <fmt/format.h>
#include <kami/agent.h>
#include <kami/domain.h>
#include <kami/error.h>
#include <kami/grid1d.h>
#include <kami/multigrid1d.h>
namespace kami {
bool MultiGrid1D::add_agent(AgentID agent_id, GridCoord1D coord) {
if (is_location_valid(coord)) {
_agent_index->insert(std::pair<AgentID, GridCoord1D>(agent_id, coord));
_agent_grid[coord.get_x_location()].push_back(agent_id);
return (true);
}
MultiGrid1D::MultiGrid1D(
unsigned int maximum_x,
bool wrap_x
)
:Grid1D(maximum_x, wrap_x) {
}
return (false);
AgentID MultiGrid1D::add_agent(
const AgentID agent_id,
const GridCoord1D& coord
) {
if (!is_location_valid(coord))
throw error::LocationInvalid(fmt::format("Coordinates {} are invalid", coord.to_string()));
_agent_index->insert(std::pair<AgentID, GridCoord1D>(agent_id, coord));
_agent_grid->insert(std::pair<GridCoord1D, AgentID>(coord, agent_id));
return agent_id;
}
} // namespace kami

View File

@@ -23,22 +23,35 @@
* SOFTWARE.
*/
#include <fmt/format.h>
#include <kami/agent.h>
#include <kami/domain.h>
#include <kami/error.h>
#include <kami/grid2d.h>
#include <kami/multigrid2d.h>
namespace kami {
bool MultiGrid2D::add_agent(AgentID agent_id, GridCoord2D coord) {
if (is_location_valid(coord)) {
_agent_index->insert(std::pair<AgentID, GridCoord2D>(agent_id, coord));
_agent_grid[coord.get_x_location()][coord.get_y_location()].push_back(
agent_id);
return true;
}
MultiGrid2D::MultiGrid2D(
unsigned int maximum_x,
unsigned int maximum_y,
bool wrap_x,
bool wrap_y
)
:Grid2D(maximum_x, maximum_y, wrap_x, wrap_y) {
}
return false;
AgentID MultiGrid2D::add_agent(
const AgentID agent_id,
const GridCoord2D& coord
) {
if (!is_location_valid(coord))
throw error::LocationInvalid(fmt::format("Coordinates {} are invalid", coord.to_string()));
_agent_index->insert(std::pair<AgentID, GridCoord2D>(agent_id, coord));
_agent_grid->insert(std::pair<GridCoord2D, AgentID>(coord, agent_id));
return agent_id;
}
} // namespace kami

73
src/libkami/population.cc Normal file
View File

@@ -0,0 +1,73 @@
/*-
* Copyright (c) 2020 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <algorithm>
#include <utility>
#include <vector>
#include <kami/agent.h>
#include <kami/error.h>
#include <kami/population.h>
namespace kami {
AgentID Population::add_agent(const std::shared_ptr<Agent>& agent) noexcept {
auto agent_id = agent->get_agent_id();
_agent_map.insert(std::pair<AgentID, std::shared_ptr<Agent>>(agent_id, agent));
return agent->get_agent_id();
}
std::shared_ptr<Agent> Population::delete_agent(const AgentID agent_id) {
auto agent_it = _agent_map.find(agent_id);
if (agent_it == _agent_map.end())
throw error::ResourceNotAvailable("Agent not found in population");
auto agent = agent_it->second;
_agent_map.erase(agent_it);
return std::move(agent);
}
std::shared_ptr<Agent> Population::get_agent_by_id(const AgentID agent_id) const {
auto agent_it = _agent_map.find(agent_id);
if (agent_it == _agent_map.end())
throw error::AgentNotFound("Agent not found in population");
return agent_it->second;
}
std::unique_ptr<std::vector<AgentID>> Population::get_agent_list() const {
auto key_selector = [](auto pair)
{
return pair.first;
};
auto agent_ids = std::make_unique<std::vector<AgentID>>(_agent_map.size());
transform(_agent_map.begin(), _agent_map.end(), agent_ids->begin(), key_selector);
return std::move(agent_ids);
}
} // namespace kami

View File

@@ -23,29 +23,54 @@
* SOFTWARE.
*/
#include <algorithm>
#include <memory>
#include <random>
#include <string>
#include <utility>
#include <vector>
#include <kami/error.h>
#include <kami/model.h>
#include <kami/random.h>
#include <kami/sequential.h>
namespace kami {
RandomScheduler::RandomScheduler(Model *model, std::shared_ptr<std::mt19937> rng)
: SequentialScheduler(model) {
this->set_rng(std::move(rng));
}
void RandomScheduler::step() {
shuffle(_agent_list.begin(), _agent_list.end(), *_rng);
this->SequentialScheduler::step();
}
void RandomScheduler::set_rng(std::shared_ptr<std::mt19937> rng) {
RandomScheduler::RandomScheduler(std::shared_ptr<std::mt19937> rng) {
this->_rng = std::move(rng);
}
[[maybe_unused]] std::shared_ptr<std::mt19937> RandomScheduler::get_rng() { return (_rng); }
std::unique_ptr<std::vector<AgentID>>
RandomScheduler::step(
std::shared_ptr<Model> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) {
if (_rng == nullptr)
throw error::ResourceNotAvailable("No random number generator available");
shuffle(agent_list->begin(), agent_list->end(), *_rng);
return std::move(this->SequentialScheduler::step(model, std::move(agent_list)));
}
std::unique_ptr<std::vector<AgentID>>
RandomScheduler::step(
std::shared_ptr<ReporterModel> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) {
if (_rng == nullptr)
throw error::ResourceNotAvailable("No random number generator available");
shuffle(agent_list->begin(), agent_list->end(), *_rng);
return std::move(this->SequentialScheduler::step(model, std::move(agent_list)));
}
std::shared_ptr<std::mt19937> RandomScheduler::set_rng(std::shared_ptr<std::mt19937> rng) {
this->_rng = std::move(rng);
return _rng;
}
std::shared_ptr<std::mt19937> RandomScheduler::get_rng() {
return (this->_rng);
}
} // namespace kami

130
src/libkami/reporter.cc Normal file
View File

@@ -0,0 +1,130 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <algorithm>
#include <memory>
#include <unordered_map>
#include <vector>
#include <nlohmann/json.hpp>
#include <kami/population.h>
#include <kami/reporter.h>
#include <kami/scheduler.h>
namespace kami {
ReporterModel::ReporterModel() {
_step_count = 0;
_rpt = std::make_shared<Reporter>();
}
std::shared_ptr<Model> ReporterModel::step() {
_step_count++;
auto ret = _sched->step(std::static_pointer_cast<ReporterModel>(shared_from_this()));
auto rpt = _rpt->collect(std::static_pointer_cast<ReporterModel>(shared_from_this()));
return shared_from_this();
}
std::unique_ptr<nlohmann::json> ReporterModel::report() {
return std::move(_rpt->report(std::static_pointer_cast<ReporterModel>(shared_from_this())));
}
inline unsigned int ReporterModel::get_step_id() {
return _step_count;
}
Reporter::Reporter() {
_report_data = std::make_unique<std::vector<nlohmann::json>>();
}
std::shared_ptr<Reporter> Reporter::clear() {
// I _can_ do this in one line, but I won't
_report_data.reset();
_report_data = std::make_unique<std::vector<nlohmann::json>>();
return shared_from_this();
}
std::unique_ptr<nlohmann::json>
Reporter::collect(const std::shared_ptr<ReporterModel>& model) {
auto pop = model->get_population();
return collect(model, pop);
}
std::unique_ptr<nlohmann::json>
Reporter::collect(
const std::shared_ptr<ReporterModel>& model,
const std::shared_ptr<Population>& pop
) {
auto agent_list = pop->get_agent_list();
return collect(model, agent_list);
}
std::unique_ptr<nlohmann::json>
Reporter::collect(
const std::shared_ptr<ReporterModel>& model,
const std::unique_ptr<std::vector<AgentID>>& agent_list
) {
auto collection_array = std::vector<nlohmann::json>();
for (auto& agent_id : *agent_list) {
auto agent_data = nlohmann::json();
auto agent = std::static_pointer_cast<ReporterAgent>(model->get_population()->get_agent_by_id(agent_id));
agent_data["agent_id"] = agent_id.to_string();
auto agent_collection = agent->collect();
if (agent_collection)
agent_data["data"] = *agent_collection;
collection_array.push_back(agent_data);
}
auto model_data = model->collect();
auto agent_collection = std::make_unique<nlohmann::json>(collection_array);
auto collection = std::make_unique<nlohmann::json>();
(*collection)["step_id"] = model->get_step_id();
if (model_data)
(*collection)["model_data"] = *model_data;
(*collection)["agent_data"] = *agent_collection;
_report_data->push_back(*collection);
return std::move(collection);
}
std::unique_ptr<nlohmann::json> Reporter::report(const std::shared_ptr<ReporterModel>& model) {
auto json_data = std::make_unique<nlohmann::json>(*_report_data);
return std::move(json_data);
}
AgentID ReporterAgent::step(std::shared_ptr<Model> model) {
return get_agent_id();
}
} // namespace kami

View File

@@ -23,37 +23,61 @@
* SOFTWARE.
*/
#include <string>
#include <memory>
#include <vector>
#include <kami/agent.h>
#include <kami/model.h>
#include <kami/reporter.h>
#include <kami/sequential.h>
namespace kami {
SequentialScheduler::SequentialScheduler(Model *model) {
_step_counter = 0;
_model = model;
std::unique_ptr<std::vector<AgentID>> SequentialScheduler::step(std::shared_ptr<Model> model) {
auto population = model->get_population();
return std::move(this->step(model, population->get_agent_list()));
}
void SequentialScheduler::add_agent(AgentID agent_id) {
_agent_list.push_back(agent_id);
std::unique_ptr<std::vector<AgentID>> SequentialScheduler::step(std::shared_ptr<ReporterModel> model) {
auto population = model->get_population();
return std::move(this->step(model, population->get_agent_list()));
}
void SequentialScheduler::delete_agent(AgentID agent_id) {
for (auto agent_list_iter = _agent_list.begin(); agent_list_iter < _agent_list.end(); agent_list_iter++)
if (*agent_list_iter == agent_id) _agent_list.erase(agent_list_iter);
// ERROR HERE
}
std::unique_ptr<std::vector<AgentID>>
SequentialScheduler::step(
std::shared_ptr<Model> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) {
auto return_agent_list = std::make_unique<std::vector<AgentID>>();
auto population = model->get_population();
void SequentialScheduler::step() {
_step_counter++;
Scheduler::_step_counter++;
for (auto& agent_id : *agent_list) {
auto agent = population->get_agent_by_id(agent_id);
for (auto agent_list_iter = _agent_list.begin();
agent_list_iter < _agent_list.end(); agent_list_iter++) {
Agent *agent = _model->get_agent_by_id(*agent_list_iter);
if (agent != nullptr) agent->step();
agent->step(model);
return_agent_list->push_back(agent_id);
}
return std::move(return_agent_list);
}
std::unique_ptr<std::vector<AgentID>>
SequentialScheduler::step(
std::shared_ptr<ReporterModel> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) {
auto return_agent_list = std::make_unique<std::vector<AgentID>>();
auto population = model->get_population();
Scheduler::_step_counter++;
for (auto& agent_id : *agent_list) {
auto agent = population->get_agent_by_id(agent_id);
agent->step(model);
return_agent_list->push_back(agent_id);
}
return std::move(return_agent_list);
}
} // namespace kami

View File

@@ -23,19 +23,33 @@
* SOFTWARE.
*/
#include <fmt/format.h>
#include <kami/agent.h>
#include <kami/error.h>
#include <kami/sologrid1d.h>
namespace kami {
bool SoloGrid1D::add_agent(AgentID agent_id, GridCoord1D coord) {
if (is_location_valid(coord) & is_location_empty(coord)) {
_agent_index->insert(std::pair<AgentID, GridCoord1D>(agent_id, coord));
_agent_grid[coord.get_x_location()].push_back(agent_id);
return true;
}
SoloGrid1D::SoloGrid1D(
unsigned int maximum_x,
bool wrap_x
)
:Grid1D(maximum_x, wrap_x) {
}
return false;
AgentID SoloGrid1D::add_agent(
const AgentID agent_id,
const GridCoord1D& coord
) {
if (!is_location_valid(coord))
throw error::LocationInvalid(fmt::format("Coordinates {} are invalid", coord.to_string()));
if (!is_location_empty(coord))
throw error::LocationUnavailable(fmt::format("Coordinates {} already occupied", coord.to_string()));
_agent_index->insert(std::pair<AgentID, GridCoord1D>(agent_id, coord));
_agent_grid->insert(std::pair<GridCoord1D, AgentID>(coord, agent_id));
return agent_id;
}
} // namespace kami

View File

@@ -23,21 +23,37 @@
* SOFTWARE.
*/
#include <kami/agent.h>
#include <kami/sologrid2d.h>
#include <vector>
#include <fmt/format.h>
#include <kami/agent.h>
#include <kami/error.h>
#include <kami/sologrid2d.h>
namespace kami {
bool SoloGrid2D::add_agent(AgentID agent_id, GridCoord2D coord) {
if (is_location_valid(coord) & is_location_empty(coord)) {
_agent_index->insert(std::pair<AgentID, GridCoord2D>(agent_id, coord));
_agent_grid[coord.get_x_location()][coord.get_y_location()].push_back(agent_id);
return true;
}
SoloGrid2D::SoloGrid2D(
unsigned int maximum_x,
unsigned int maximum_y,
bool wrap_x,
bool wrap_y
)
:Grid2D(maximum_x, maximum_y, wrap_x, wrap_y) {
}
return false;
AgentID SoloGrid2D::add_agent(
const AgentID agent_id,
const GridCoord2D& coord
) {
if (!is_location_valid(coord))
throw error::LocationInvalid(fmt::format("Coordinates {} are invalid", coord.to_string()));
if (!is_location_empty(coord))
throw error::LocationUnavailable(fmt::format("Coordinates {} already occupied", coord.to_string()));
_agent_index->insert(std::pair<AgentID, GridCoord2D>(agent_id, coord));
_agent_grid->insert(std::pair<GridCoord2D, AgentID>(coord, agent_id));
return agent_id;
}
} // namespace kami

View File

@@ -23,40 +23,61 @@
* SOFTWARE.
*/
#include <string>
#include <memory>
#include <vector>
#include <kami/agent.h>
#include <kami/model.h>
#include <kami/reporter.h>
#include <kami/sequential.h>
#include <kami/staged.h>
namespace kami {
StagedScheduler::StagedScheduler(Model *model) {
_step_counter = 0;
_model = model;
std::unique_ptr<std::vector<AgentID>>
StagedScheduler::step(
std::shared_ptr<Model> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) {
auto stepped_agent_list = this->SequentialScheduler::step(model, std::move(agent_list));
return std::move(this->advance(model, std::move(stepped_agent_list)));
}
void StagedScheduler::add_agent(AgentID agent_id) {
_agent_list.push_back(agent_id);
std::unique_ptr<std::vector<AgentID>>
StagedScheduler::step(
std::shared_ptr<ReporterModel> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) {
auto stepped_agent_list = this->SequentialScheduler::step(model, std::move(agent_list));
return std::move(this->advance(model, std::move(stepped_agent_list)));
}
void StagedScheduler::delete_agent(AgentID agent_id) {
for (auto agent_list_iter = _agent_list.begin(); agent_list_iter < _agent_list.end(); agent_list_iter++)
if (*agent_list_iter == agent_id) _agent_list.erase(agent_list_iter);
std::unique_ptr<std::vector<AgentID>> StagedScheduler::advance(std::shared_ptr<Model> model) {
auto population = model->get_population();
return std::move(this->advance(model, population->get_agent_list()));
}
void StagedScheduler::step() {
_step_counter++;
std::unique_ptr<std::vector<AgentID>> StagedScheduler::advance(std::shared_ptr<ReporterModel> model) {
auto population = model->get_population();
return std::move(this->advance(model, population->get_agent_list()));
}
for (auto agent_list_iter = _agent_list.begin(); agent_list_iter < _agent_list.end(); agent_list_iter++) {
auto *agent = dynamic_cast<StagedAgent *>(_model->get_agent_by_id(*agent_list_iter));
if (agent != nullptr) agent->step();
std::unique_ptr<std::vector<AgentID>>
StagedScheduler::advance(
std::shared_ptr<Model> model,
std::unique_ptr<std::vector<AgentID>> agent_list
) {
auto return_agent_list = std::make_unique<std::vector<AgentID>>();
auto population = model->get_population();
for (auto& agent_id : *agent_list) {
auto agent = std::static_pointer_cast<StagedAgent>(population->get_agent_by_id(agent_id));
agent->advance(model);
return_agent_list->push_back(agent_id);
}
for (auto agent_list_iter = _agent_list.begin(); agent_list_iter < _agent_list.end(); agent_list_iter++) {
auto *agent = dynamic_cast<StagedAgent *>(_model->get_agent_by_id(*agent_list_iter));
if (agent != nullptr) agent->advance();
}
return std::move(return_agent_list);
}
} // namespace kami

View File

@@ -2,34 +2,18 @@
# This CMakeLists.txt contains the build descriptions for unit tests
################################################################################
cmake_minimum_required(VERSION 3.13.0 FATAL_ERROR)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
find_package(nlohmann_json 3.11.1 REQUIRED)
find_package(spdlog)
create_test( NAME
unit-kami-agentid
SOURCES
unit-kami-agentid.cc
PUBLIC_LINKED_TARGETS
kami::libkami
fmt
spdlog::spdlog
${COVERAGE_LIBS}
COMMAND
unit-kami-agentid
PUBLIC_COMPILE_FEATURES
${COVERAGE_FLAGS})
create_test( NAME
unit-kami-agent
SOURCES
unit-kami-agent.cc
PUBLIC_LINKED_TARGETS
kami::libkami
fmt
spdlog::spdlog
${COVERAGE_LIBS}
COMMAND
unit-kami-agent
PUBLIC_COMPILE_FEATURES
${COVERAGE_FLAGS})
file(GLOB test_modules "${CMAKE_CURRENT_SOURCE_DIR}/*.cc")
FOREACH (test_module ${test_modules})
cmake_path(GET test_module STEM test_src)
create_test(
NAME ${test_src}
SOURCES ${test_src}.cc
PUBLIC_LINKED_TARGETS gmock gtest kami::libkami Threads::Threads nlohmann_json::nlohmann_json
COMMAND ${test_src}
PUBLIC_COMPILE_FEATURES ${COVERAGE_FLAGS}
)
ENDFOREACH ()

View File

@@ -1,5 +1,5 @@
/*-
* Copyright (c) 2020 The Johns Hopkins University Applied Physics
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
@@ -23,37 +23,78 @@
* SOFTWARE.
*/
#include <string>
#include <memory>
#include <kami/agent.h>
#include <kami/config.h>
#include <kami/model.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <CLI/App.hpp>
#include <CLI/Config.hpp>
#include <CLI/Formatter.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace kami;
using namespace std;
class TestAgent : public Agent {
void step() override {};
class TestAgent
: public Agent {
public:
AgentID step(shared_ptr<Model> model) override {
return get_agent_id();
}
};
int main(int argc, char **argv) {
string ident = "unit-kami-agent";
CLI::App app{ident};
auto console = spdlog::stdout_color_st(ident);
string logLevelOption = "info";
class TestModel
: public Model {
};
app.add_option("-l", logLevelOption, "Set the logging level")->check(CLI::IsMember(SPDLOG_LEVEL_NAMES));
CLI11_PARSE(app, argc, argv);
class AgentTest
: public ::testing::Test {
protected:
TestAgent agent_foo;
TestAgent agent_bar;
shared_ptr<TestModel> model_world = nullptr;
console->set_level(spdlog::level::from_str(logLevelOption));
console->info("Compiled with Kami/{}, log level {}", KAMI_VERSION_STRING, logLevelOption);
void SetUp() override {
model_world = make_shared<TestModel>();
}
};
TestAgent test_agent;
console->debug("Successfully created Agent with ID {}", test_agent.get_agent_id().to_string());
TEST(Agent, DefaultConstructor) {
EXPECT_NO_THROW(
const TestAgent agent_baz;
const TestAgent agent_qux;
);
}
TEST_F(AgentTest, equivalance) {
EXPECT_EQ(agent_foo, agent_foo);
EXPECT_NE(agent_foo, agent_bar);
}
TEST_F(AgentTest, get_agent_id) {
EXPECT_EQ(agent_foo.get_agent_id(), agent_foo.get_agent_id());
EXPECT_NE(agent_bar.get_agent_id(), agent_foo.get_agent_id());
}
TEST_F(AgentTest, step) {
EXPECT_EQ(agent_foo.get_agent_id(), agent_foo.step(model_world));
EXPECT_NE(agent_bar.get_agent_id(), agent_foo.step(model_world));
}
TEST_F(AgentTest, equality) {
EXPECT_TRUE(agent_foo == agent_foo);
EXPECT_TRUE(agent_bar == agent_bar);
}
TEST_F(AgentTest, inequality) {
EXPECT_TRUE(agent_foo != agent_bar);
EXPECT_FALSE(agent_bar != agent_bar);
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -1,5 +1,5 @@
/*-
* Copyright (c) 2020 The Johns Hopkins University Applied Physics
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
@@ -23,33 +23,51 @@
* SOFTWARE.
*/
#include <string>
#include <kami/agent.h>
#include <kami/config.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <CLI/App.hpp>
#include <CLI/Config.hpp>
#include <CLI/Formatter.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace kami;
using namespace std;
int main(int argc, char **argv) {
string ident = "unit-kami-agentid";
CLI::App app{ident};
auto console = spdlog::stdout_color_st(ident);
string logLevelOption = "info";
class AgentIDTest
: public ::testing::Test {
protected:
AgentID agent_id_foo;
AgentID agent_id_bar;
};
app.add_option("-l", logLevelOption, "Set the logging level")->check(CLI::IsMember(SPDLOG_LEVEL_NAMES));
CLI11_PARSE(app, argc, argv);
console->set_level(spdlog::level::from_str(logLevelOption));
console->info("Compiled with Kami/{}, log level {}", KAMI_VERSION_STRING, logLevelOption);
AgentID testAgentID;
console->debug("Successfully created AgentID with ID {}", testAgentID.to_string());
TEST_F(AgentIDTest, DefaultConstructor) {
EXPECT_EQ(agent_id_foo, agent_id_foo);
EXPECT_NE(agent_id_foo, agent_id_bar);
}
TEST_F(AgentIDTest, to_string) {
EXPECT_THAT(agent_id_foo.to_string(), testing::MatchesRegex("[0-9]+"));
EXPECT_THAT(agent_id_bar.to_string(), testing::MatchesRegex("[0-9]+"));
}
TEST_F(AgentIDTest, equality) {
EXPECT_TRUE(agent_id_foo == agent_id_foo);
EXPECT_TRUE(agent_id_bar == agent_id_bar);
EXPECT_FALSE(agent_id_foo == agent_id_bar);
}
TEST_F(AgentIDTest, inequality) {
EXPECT_TRUE(agent_id_foo != agent_id_bar);
EXPECT_FALSE(agent_id_bar != agent_id_bar);
}
TEST_F(AgentIDTest, ordering) {
EXPECT_TRUE(agent_id_foo < agent_id_bar);
EXPECT_FALSE(agent_id_bar < agent_id_foo);
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,95 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <kami/grid1d.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace kami;
class GridCoord1DTest
: public ::testing::Test {
protected:
GridCoord1D gridcoord1d_foo = GridCoord1D(0);
GridCoord1D gridcoord1d_bar = GridCoord1D(1);
GridCoord1D gridcoord1d_baz = GridCoord1D(-1);
GridCoord1D gridcoord1d_qux = GridCoord1D(0);
};
TEST_F(GridCoord1DTest, DefaultConstructor) {
EXPECT_EQ(gridcoord1d_foo, gridcoord1d_foo);
EXPECT_EQ(gridcoord1d_foo, gridcoord1d_qux);
EXPECT_NE(gridcoord1d_foo, gridcoord1d_bar);
EXPECT_NE(gridcoord1d_foo, gridcoord1d_baz);
EXPECT_NE(gridcoord1d_bar, gridcoord1d_baz);
}
TEST_F(GridCoord1DTest, to_string) {
EXPECT_THAT(gridcoord1d_foo.to_string(), "(0)");
EXPECT_THAT(gridcoord1d_bar.to_string(), "(1)");
EXPECT_THAT(gridcoord1d_baz.to_string(), "(-1)");
}
TEST_F(GridCoord1DTest, equality) {
EXPECT_TRUE(gridcoord1d_foo == gridcoord1d_foo);
EXPECT_TRUE(gridcoord1d_foo == gridcoord1d_qux);
EXPECT_FALSE(gridcoord1d_foo == gridcoord1d_bar);
EXPECT_FALSE(gridcoord1d_foo == gridcoord1d_baz);
EXPECT_FALSE(gridcoord1d_bar == gridcoord1d_baz);
}
TEST_F(GridCoord1DTest, inequality) {
EXPECT_FALSE(gridcoord1d_foo != gridcoord1d_foo);
EXPECT_FALSE(gridcoord1d_foo != gridcoord1d_qux);
EXPECT_TRUE(gridcoord1d_foo != gridcoord1d_bar);
EXPECT_TRUE(gridcoord1d_foo != gridcoord1d_baz);
EXPECT_TRUE(gridcoord1d_bar != gridcoord1d_baz);
}
TEST_F(GridCoord1DTest, x) {
EXPECT_TRUE(gridcoord1d_foo.x() == 0);
EXPECT_TRUE(gridcoord1d_bar.x() == 1);
EXPECT_TRUE(gridcoord1d_baz.x() == -1);
EXPECT_TRUE(gridcoord1d_foo.x() == gridcoord1d_foo.x());
EXPECT_TRUE(gridcoord1d_foo.x() == gridcoord1d_qux.x());
EXPECT_FALSE(gridcoord1d_foo.x() == gridcoord1d_bar.x());
EXPECT_FALSE(gridcoord1d_foo.x() == gridcoord1d_baz.x());
EXPECT_FALSE(gridcoord1d_bar.x() == gridcoord1d_baz.x());
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,155 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <kami/grid2d.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace kami;
class GridCoord2DTest
: public ::testing::Test {
protected:
GridCoord2D gridcoord2d_foo = GridCoord2D(0, 0);
GridCoord2D gridcoord2d_bar = GridCoord2D(1, 1);
GridCoord2D gridcoord2d_baz = GridCoord2D(-1, -1);
GridCoord2D gridcoord2d_qux = GridCoord2D(0, 1);
GridCoord2D gridcoord2d_qu2 = GridCoord2D(1, 0);
};
TEST_F(GridCoord2DTest, DefaultConstructor) {
EXPECT_EQ(gridcoord2d_foo, gridcoord2d_foo);
EXPECT_NE(gridcoord2d_foo, gridcoord2d_bar);
EXPECT_NE(gridcoord2d_foo, gridcoord2d_baz);
EXPECT_NE(gridcoord2d_bar, gridcoord2d_baz);
EXPECT_NE(gridcoord2d_foo, gridcoord2d_qux);
EXPECT_NE(gridcoord2d_foo, gridcoord2d_qu2);
}
TEST_F(GridCoord2DTest, to_string) {
const GridCoord2D gridcoord2d_foo(0, 0);
const GridCoord2D gridcoord2d_bar(1, 1);
const GridCoord2D gridcoord2d_baz(-1, -1);
const GridCoord2D gridcoord2d_qux(0, 1);
const GridCoord2D gridcoord2d_qu2(1, 0);
EXPECT_THAT(gridcoord2d_foo.to_string(), "(0, 0)");
EXPECT_THAT(gridcoord2d_bar.to_string(), "(1, 1)");
EXPECT_THAT(gridcoord2d_baz.to_string(), "(-1, -1)");
EXPECT_THAT(gridcoord2d_qux.to_string(), "(0, 1)");
EXPECT_THAT(gridcoord2d_qu2.to_string(), "(1, 0)");
}
TEST_F(GridCoord2DTest, Equality) {
const GridCoord2D gridcoord2d_foo(0, 0);
const GridCoord2D gridcoord2d_bar(1, 1);
const GridCoord2D gridcoord2d_baz(-1, -1);
const GridCoord2D gridcoord2d_qux(0, 1);
const GridCoord2D gridcoord2d_qu2(1, 0);
EXPECT_TRUE(gridcoord2d_foo == gridcoord2d_foo);
EXPECT_FALSE(gridcoord2d_foo == gridcoord2d_bar);
EXPECT_FALSE(gridcoord2d_foo == gridcoord2d_baz);
EXPECT_FALSE(gridcoord2d_bar == gridcoord2d_baz);
EXPECT_FALSE(gridcoord2d_foo == gridcoord2d_qux);
EXPECT_FALSE(gridcoord2d_qux == gridcoord2d_qu2);
EXPECT_FALSE(gridcoord2d_qux == gridcoord2d_qu2);
}
TEST_F(GridCoord2DTest, Inequality) {
const GridCoord2D gridcoord2d_foo(0, 0);
const GridCoord2D gridcoord2d_bar(1, 1);
const GridCoord2D gridcoord2d_baz(-1, -1);
const GridCoord2D gridcoord2d_qux(0, 1);
const GridCoord2D gridcoord2d_qu2(1, 0);
EXPECT_FALSE(gridcoord2d_foo != gridcoord2d_foo);
EXPECT_TRUE(gridcoord2d_foo != gridcoord2d_qux);
EXPECT_TRUE(gridcoord2d_foo != gridcoord2d_bar);
EXPECT_TRUE(gridcoord2d_foo != gridcoord2d_baz);
EXPECT_TRUE(gridcoord2d_bar != gridcoord2d_baz);
EXPECT_TRUE(gridcoord2d_qux != gridcoord2d_qu2);
}
TEST_F(GridCoord2DTest, x) {
const GridCoord2D gridcoord2d_foo(0, 0);
const GridCoord2D gridcoord2d_bar(1, 1);
const GridCoord2D gridcoord2d_baz(-1, -1);
const GridCoord2D gridcoord2d_qux(0, 1);
const GridCoord2D gridcoord2d_qu2(1, 0);
EXPECT_TRUE(gridcoord2d_foo.x() == 0);
EXPECT_TRUE(gridcoord2d_bar.x() == 1);
EXPECT_TRUE(gridcoord2d_baz.x() == -1);
EXPECT_FALSE(gridcoord2d_qux.x() == -1);
EXPECT_FALSE(gridcoord2d_qu2.x() == -1);
EXPECT_TRUE(gridcoord2d_foo.x() == gridcoord2d_foo.x());
EXPECT_TRUE(gridcoord2d_foo.x() == gridcoord2d_qux.x());
EXPECT_TRUE(gridcoord2d_bar.x() == gridcoord2d_qu2.x());
EXPECT_FALSE(gridcoord2d_foo.x() == gridcoord2d_bar.x());
EXPECT_FALSE(gridcoord2d_foo.x() == gridcoord2d_baz.x());
EXPECT_FALSE(gridcoord2d_bar.x() == gridcoord2d_baz.x());
EXPECT_FALSE(gridcoord2d_foo.x() == gridcoord2d_baz.x());
EXPECT_FALSE(gridcoord2d_qux.x() == gridcoord2d_qu2.x());
}
TEST_F(GridCoord2DTest, y) {
const GridCoord2D gridcoord2d_foo(0, 0);
const GridCoord2D gridcoord2d_bar(1, 1);
const GridCoord2D gridcoord2d_baz(-1, -1);
const GridCoord2D gridcoord2d_qux(0, 1);
const GridCoord2D gridcoord2d_qu2(1, 0);
EXPECT_TRUE(gridcoord2d_foo.y() == 0);
EXPECT_TRUE(gridcoord2d_bar.y() == 1);
EXPECT_TRUE(gridcoord2d_baz.y() == -1);
EXPECT_FALSE(gridcoord2d_qux.y() == -1);
EXPECT_FALSE(gridcoord2d_qu2.y() == -1);
EXPECT_TRUE(gridcoord2d_foo.y() == gridcoord2d_foo.y());
EXPECT_TRUE(gridcoord2d_bar.y() == gridcoord2d_qux.y());
EXPECT_FALSE(gridcoord2d_foo.y() == gridcoord2d_bar.y());
EXPECT_FALSE(gridcoord2d_foo.y() == gridcoord2d_baz.y());
EXPECT_FALSE(gridcoord2d_bar.y() == gridcoord2d_baz.y());
EXPECT_FALSE(gridcoord2d_foo.y() == gridcoord2d_baz.y());
EXPECT_FALSE(gridcoord2d_qux.y() == gridcoord2d_qu2.y());
EXPECT_FALSE(gridcoord2d_bar.y() == gridcoord2d_qu2.y());
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

139
test/unit-kami-model.cc Normal file
View File

@@ -0,0 +1,139 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <memory>
#include <kami/agent.h>
#include <kami/error.h>
#include <kami/model.h>
#include <kami/multigrid2d.h>
#include <kami/population.h>
#include <kami/sequential.h>
#include <gtest/gtest.h>
using namespace kami;
using namespace kami::error;
using namespace std;
class TestAgent
: public Agent {
public:
AgentID step(shared_ptr<Model> model) override {
return get_agent_id();
}
};
class TestModel
: public Model {
public:
shared_ptr<Model> step() final {
return shared_from_this();
}
};
TEST(Model, DefaultConstructor) {
// There is really no way this can go wrong, but
// we add this check anyway in case of future
// changes.
EXPECT_NO_THROW(
const TestModel model_foo
);
}
TEST(Model, set_population) {
auto model_foo = make_shared<TestModel>();
auto pop_foo = make_shared<Population>();
auto pop_bar = model_foo->set_population(pop_foo);
EXPECT_EQ(pop_foo, pop_bar);
}
TEST(Model, get_population) {
auto model_foo = make_shared<TestModel>();
auto pop_foo = make_shared<Population>();
EXPECT_THROW(auto pop_nul = model_foo->get_population(), ResourceNotAvailable);
auto pop_bar = model_foo->set_population(pop_foo);
auto pop_baz = model_foo->get_population();
EXPECT_TRUE(pop_baz);
EXPECT_EQ(pop_foo, pop_baz);
EXPECT_EQ(pop_bar, pop_baz);
}
TEST(Model, set_scheduler) {
auto model_foo = make_shared<TestModel>();
auto sched_foo = make_shared<SequentialScheduler>();
auto sched_bar = model_foo->set_scheduler(sched_foo);
EXPECT_EQ(sched_foo, sched_bar);
}
TEST(Model, get_scheduler) {
auto model_foo = make_shared<TestModel>();
auto sched_foo = make_shared<SequentialScheduler>();
EXPECT_THROW(auto sched_nul = model_foo->get_scheduler(), ResourceNotAvailable);
auto sched_bar = model_foo->set_scheduler(sched_foo);
auto sched_baz = model_foo->get_scheduler();
EXPECT_TRUE(sched_baz);
EXPECT_EQ(sched_foo, sched_baz);
EXPECT_EQ(sched_bar, sched_baz);
}
TEST(Model, set_domain) {
auto model_foo = make_shared<TestModel>();
auto grid2_foo = make_shared<MultiGrid2D>(10, 10, true, true);
auto grid2_bar = model_foo->set_domain(grid2_foo);
EXPECT_EQ(grid2_foo, grid2_bar);
}
TEST(Model, get_domain) {
auto model_foo = make_shared<TestModel>();
auto grid2_foo = make_shared<MultiGrid2D>(10, 10, true, true);
EXPECT_THROW(auto grid2_nul = model_foo->get_domain(), ResourceNotAvailable);
auto grid2_bar = model_foo->set_domain(grid2_foo);
auto grid2_baz = model_foo->get_domain();
EXPECT_TRUE(grid2_baz);
EXPECT_EQ(grid2_foo, grid2_baz);
EXPECT_EQ(grid2_bar, grid2_baz);
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,475 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <algorithm>
#include <iterator>
#include <set>
#include <unordered_set>
#include <kami/agent.h>
#include <kami/multigrid1d.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace kami;
using namespace kami::error;
using namespace std;
TEST(MultiGrid1D, DefaultConstructor) {
// There is really no way this can go wrong, but
// we add this check anyway in case of future
// changes.
EXPECT_NO_THROW(
MultiGrid1D multigrid1d_foo(10, true);
);
}
TEST(MultiGrid1D, add_agent) {
MultiGrid1D multigrid1d_foo(10, true);
const AgentID agent_id_foo, agent_id_bar;
const GridCoord1D coord2(2), coord3(3);
{
auto agent_id_baz = multigrid1d_foo.add_agent(agent_id_foo, coord2);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
{
auto agent_id_baz = multigrid1d_foo.add_agent(agent_id_bar, coord2);
EXPECT_EQ(agent_id_baz, agent_id_bar);
}
{
auto agent_id_baz = multigrid1d_foo.add_agent(agent_id_bar, coord3);
EXPECT_EQ(agent_id_baz, agent_id_bar);
}
}
TEST(MultiGrid1D, delete_agent) {
const AgentID agent_id_foo, agent_id_bar;
const GridCoord1D coord2(2), coord3(3);
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
auto agent_id_baz = multigrid1d_foo.delete_agent(agent_id_foo);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
static_cast<void>(multigrid1d_foo.add_agent(agent_id_bar, coord2));
auto agent_id_baz = multigrid1d_foo.delete_agent(agent_id_foo);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
static_cast<void>(multigrid1d_foo.add_agent(agent_id_bar, coord2));
auto agent_id_baz = multigrid1d_foo.delete_agent(agent_id_bar);
EXPECT_EQ(agent_id_baz, agent_id_bar);
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
auto agent_id_baz = multigrid1d_foo.delete_agent(agent_id_foo, coord2);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
static_cast<void>(multigrid1d_foo.add_agent(agent_id_bar, coord2));
auto agent_id_baz = multigrid1d_foo.delete_agent(agent_id_foo, coord2);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
static_cast<void>(multigrid1d_foo.add_agent(agent_id_bar, coord2));
auto agent_id_baz = multigrid1d_foo.delete_agent(agent_id_bar, coord2);
EXPECT_EQ(agent_id_baz, agent_id_bar);
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_THROW(auto agent_id_baz = multigrid1d_foo.delete_agent(agent_id_foo, coord3), AgentNotFound);
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
static_cast<void>(multigrid1d_foo.add_agent(agent_id_bar, coord2));
EXPECT_THROW(auto agent_id_baz = multigrid1d_foo.delete_agent(agent_id_foo, coord3), AgentNotFound);
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
static_cast<void>(multigrid1d_foo.add_agent(agent_id_bar, coord2));
EXPECT_THROW(auto agent_id_baz = multigrid1d_foo.delete_agent(agent_id_bar, coord3), AgentNotFound);
}
}
TEST(MultiGrid1D, is_location_valid) {
MultiGrid1D multigrid1d_foo(10, true);
const AgentID agent_id_foo, agent_id_bar;
const GridCoord1D coordm1(-1), coord0(0), coord2(2), coord3(3), coord10(10), coord100(100);
{
EXPECT_TRUE(multigrid1d_foo.is_location_valid(coord0));
EXPECT_TRUE(multigrid1d_foo.is_location_valid(coord2));
EXPECT_TRUE(multigrid1d_foo.is_location_valid(coord2));
EXPECT_FALSE(multigrid1d_foo.is_location_valid(coordm1));
EXPECT_FALSE(multigrid1d_foo.is_location_valid(coord10));
EXPECT_FALSE(multigrid1d_foo.is_location_valid(coord100));
}
}
TEST(MultiGrid1D, is_location_empty) {
const AgentID agent_id_foo, agent_id_bar;
const GridCoord1D coord2(2), coord3(3);
{
MultiGrid1D multigrid1d_foo(10, true);
EXPECT_TRUE(multigrid1d_foo.is_location_empty(coord2));
EXPECT_TRUE(multigrid1d_foo.is_location_empty(coord3));
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_FALSE(multigrid1d_foo.is_location_empty(coord2));
EXPECT_TRUE(multigrid1d_foo.is_location_empty(coord3));
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
static_cast<void>(multigrid1d_foo.add_agent(agent_id_bar, coord2));
EXPECT_FALSE(multigrid1d_foo.is_location_empty(coord2));
EXPECT_TRUE(multigrid1d_foo.is_location_empty(coord3));
}
}
TEST(MultiGrid1D, move_agent) {
const AgentID agent_id_foo, agent_id_bar;
const GridCoord1D coord2(2), coord3(3), coord7(7), coord10(10);
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
auto agent_id_baz = multigrid1d_foo.move_agent(agent_id_foo, coord7);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_THROW(auto agent_id_baz = multigrid1d_foo.move_agent(agent_id_foo, coord10), LocationInvalid);
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
static_cast<void>(multigrid1d_foo.add_agent(agent_id_bar, coord2));
auto agent_id_baz = multigrid1d_foo.move_agent(agent_id_foo, coord2);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
static_cast<void>(multigrid1d_foo.add_agent(agent_id_bar, coord2));
auto agent_id_baz = multigrid1d_foo.move_agent(agent_id_foo, coord7);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
}
TEST(MultiGrid1D, get_neighborhood) {
const AgentID agent_id_foo;
const GridCoord1D coord0(0), coord1(1), coord2(2), coord3(3), coord9(9);
{
MultiGrid1D multigrid1d_foo(10, true);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1, coord9
});
auto rval = multigrid1d_foo.get_neighborhood(coord0, true);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, true);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1, coord2
});
auto rval = multigrid1d_foo.get_neighborhood(coord1, true);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, false);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1
});
auto rval = multigrid1d_foo.get_neighborhood(coord0, true);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, false);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1, coord2
});
auto rval = multigrid1d_foo.get_neighborhood(coord1, true);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, true);
auto tval = unordered_set < GridCoord1D > ({
coord1, coord9
});
auto rval = multigrid1d_foo.get_neighborhood(coord0, false);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, true);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord2
});
auto rval = multigrid1d_foo.get_neighborhood(coord1, false);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, false);
auto tval = unordered_set < GridCoord1D > ({
coord1
});
auto rval = multigrid1d_foo.get_neighborhood(coord0, false);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, false);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord2
});
auto rval = multigrid1d_foo.get_neighborhood(coord1, false);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, true);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1, coord9
});
EXPECT_THROW(auto rval = multigrid1d_foo.get_neighborhood(agent_id_foo, true), AgentNotFound);
}
{
MultiGrid1D multigrid1d_foo(10, true);
multigrid1d_foo.add_agent(agent_id_foo, coord0);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1, coord9
});
auto rval = multigrid1d_foo.get_neighborhood(agent_id_foo, true);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, true);
multigrid1d_foo.add_agent(agent_id_foo, coord1);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1, coord2
});
auto rval = multigrid1d_foo.get_neighborhood(agent_id_foo, true);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, false);
multigrid1d_foo.add_agent(agent_id_foo, coord0);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1
});
auto rval = multigrid1d_foo.get_neighborhood(agent_id_foo, true);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, false);
multigrid1d_foo.add_agent(agent_id_foo, coord1);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1, coord2
});
auto rval = multigrid1d_foo.get_neighborhood(agent_id_foo, true);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, true);
multigrid1d_foo.add_agent(agent_id_foo, coord0);
auto tval = unordered_set < GridCoord1D > ({
coord1, coord9
});
auto rval = multigrid1d_foo.get_neighborhood(agent_id_foo, false);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, true);
multigrid1d_foo.add_agent(agent_id_foo, coord1);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord2
});
auto rval = multigrid1d_foo.get_neighborhood(agent_id_foo, false);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, false);
multigrid1d_foo.add_agent(agent_id_foo, coord0);
auto tval = unordered_set < GridCoord1D > ({
coord1
});
auto rval = multigrid1d_foo.get_neighborhood(agent_id_foo, false);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, false);
multigrid1d_foo.add_agent(agent_id_foo, coord1);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord2
});
auto rval = multigrid1d_foo.get_neighborhood(agent_id_foo, false);
EXPECT_EQ(tval, *rval);
}
}
TEST(MultiGrid1D, get_location_by_agent) {
const AgentID agent_id_foo, agent_id_bar;
const GridCoord1D coord2(2), coord3(3);
{
MultiGrid1D multigrid1d_foo(10, true);
EXPECT_THROW(auto loc1 = multigrid1d_foo.get_location_by_agent(agent_id_foo), AgentNotFound);
EXPECT_THROW(auto loc2 = multigrid1d_foo.get_location_by_agent(agent_id_bar), AgentNotFound);
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord2));
auto local = multigrid1d_foo.get_location_by_agent(agent_id_foo);
EXPECT_EQ(local, coord2);
EXPECT_THROW(auto loc = multigrid1d_foo.get_location_by_agent(agent_id_bar), AgentNotFound);
}
}
TEST(MultiGrid1D, get_location_contents) {
const AgentID agent_id_foo, agent_id_bar, agent_id_baz;
const GridCoord1D coord0(0), coord1(1), coord9(9), coord10(10);
{
MultiGrid1D multigrid1d_foo(10, true);
EXPECT_THROW(auto agent_list_foo = multigrid1d_foo.get_location_contents(coord10), LocationUnavailable);
}
{
MultiGrid1D multigrid1d_foo(10, true);
auto agent_list_foo = multigrid1d_foo.get_location_contents(coord1);
EXPECT_TRUE(agent_list_foo);
EXPECT_TRUE(agent_list_foo->empty());
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord1));
static_cast<void>(multigrid1d_foo.add_agent(agent_id_bar, coord1));
static_cast<void>(multigrid1d_foo.add_agent(agent_id_baz, coord1));
auto tval = set < AgentID > ({
agent_id_foo, agent_id_bar, agent_id_baz
});
auto rval = multigrid1d_foo.get_location_contents(coord1);
EXPECT_TRUE(rval);
EXPECT_EQ(tval, *rval);
}
{
MultiGrid1D multigrid1d_foo(10, true);
static_cast<void>(multigrid1d_foo.add_agent(agent_id_foo, coord1));
static_cast<void>(multigrid1d_foo.add_agent(agent_id_bar, coord1));
static_cast<void>(multigrid1d_foo.add_agent(agent_id_baz, coord9));
auto tval = set < AgentID > ({
agent_id_foo, agent_id_bar
});
auto rval = multigrid1d_foo.get_location_contents(coord1);
EXPECT_TRUE(rval);
EXPECT_EQ(tval, *rval);
}
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,204 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <kami/agent.h>
#include <kami/error.h>
#include <kami/population.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace kami;
using namespace kami::error;
using namespace std;
class TestAgent
: public Agent {
private:
int _x;
public:
explicit TestAgent(int x)
:_x(x) {
};
AgentID step(shared_ptr<Model> model) override {
return get_agent_id();
}
int getval() {
return _x;
}
};
TEST(Population, DefaultConstructor) {
// There is really no way this can go wrong, but
// we add this check anyway in case of future
// changes.
EXPECT_NO_THROW(
const Population population_foo;
);
}
TEST(Population, add_agent) {
Population population_foo;
auto agent_foo = make_shared<TestAgent>(8675309);
auto agent_bar = make_shared<TestAgent>(1729);
{
auto agent_id_baz = population_foo.add_agent(agent_foo);
EXPECT_EQ(agent_id_baz, agent_foo->get_agent_id());
}
{
auto agent_id_baz = population_foo.add_agent(agent_bar);
EXPECT_EQ(agent_id_baz, agent_bar->get_agent_id());
}
{
auto agent_id_baz = population_foo.add_agent(agent_foo);
EXPECT_EQ(agent_id_baz, agent_foo->get_agent_id());
auto agent_id_qux = population_foo.add_agent(agent_bar);
EXPECT_EQ(agent_id_qux, agent_bar->get_agent_id());
}
}
TEST(Population, get_agent_by_id) {
auto agent_foo = make_shared<TestAgent>(8675309);
auto agent_bar = make_shared<TestAgent>(1729);
{
Population population_foo;
static_cast<void>(population_foo.add_agent(agent_foo));
auto agent_baz_opt = population_foo.get_agent_by_id(agent_foo->get_agent_id());
EXPECT_TRUE(agent_baz_opt);
auto agent_baz = dynamic_pointer_cast<TestAgent>(agent_baz_opt);
EXPECT_EQ(agent_baz->getval(), 8675309);
}
{
Population population_foo;
static_cast<void>(population_foo.add_agent(agent_foo));
EXPECT_THROW(auto agent_baz_opt = population_foo.get_agent_by_id(agent_bar->get_agent_id()), AgentNotFound);
}
{
Population population_foo;
static_cast<void>(population_foo.add_agent(agent_foo));
static_cast<void>(population_foo.add_agent(agent_bar));
auto agent_baz_opt = population_foo.get_agent_by_id(agent_foo->get_agent_id());
EXPECT_TRUE(agent_baz_opt);
auto agent_baz = dynamic_pointer_cast<TestAgent>(agent_baz_opt);
EXPECT_EQ(agent_baz->getval(), 8675309);
auto agent_qux_opt = population_foo.get_agent_by_id(agent_bar->get_agent_id());
EXPECT_TRUE(agent_qux_opt);
auto agent_qux = dynamic_pointer_cast<TestAgent>(agent_qux_opt);
EXPECT_EQ(agent_qux->getval(), 1729);
}
{
Population population_foo;
static_cast<void>(population_foo.add_agent(agent_foo));
static_cast<void>(population_foo.add_agent(agent_bar));
auto agent_qux_opt = population_foo.get_agent_by_id(agent_bar->get_agent_id());
EXPECT_TRUE(agent_qux_opt);
auto agent_qux = dynamic_pointer_cast<TestAgent>(agent_qux_opt);
EXPECT_EQ(agent_qux->getval(), 1729);
auto agent_baz_opt = population_foo.get_agent_by_id(agent_foo->get_agent_id());
EXPECT_TRUE(agent_baz_opt);
auto agent_baz = dynamic_pointer_cast<TestAgent>(agent_baz_opt);
EXPECT_EQ(agent_baz->getval(), 8675309);
}
}
TEST(Population, get_agent_list) {
auto agent_foo = make_shared<TestAgent>(8675309);
auto agent_bar = make_shared<TestAgent>(1729);
auto agent_baz = make_shared<TestAgent>(4104);
auto agent_qux = make_shared<TestAgent>(196);
{
Population population_foo;
auto tval = make_shared<vector<AgentID>>();
auto rval = population_foo.get_agent_list();
EXPECT_EQ(*tval, *rval);
}
{
Population population_foo;
auto tval = make_shared<vector<AgentID>>();
auto rval = population_foo.get_agent_list();
tval->push_back(agent_foo->get_agent_id());
EXPECT_NE(*tval, *rval);
}
{
Population population_foo;
static_cast<void>(population_foo.add_agent(agent_foo));
static_cast<void>(population_foo.add_agent(agent_bar));
auto tval = make_shared<vector<AgentID>>();
auto uval = make_shared<vector<AgentID>>();
auto rval = population_foo.get_agent_list();
tval->push_back(agent_foo->get_agent_id());
tval->push_back(agent_bar->get_agent_id());
EXPECT_EQ(*tval, *rval);
// Ordering matters
uval->push_back(agent_bar->get_agent_id());
uval->push_back(agent_foo->get_agent_id());
EXPECT_NE(*uval, *rval);
}
{
Population population_foo;
static_cast<void>(population_foo.add_agent(agent_foo));
static_cast<void>(population_foo.add_agent(agent_bar));
static_cast<void>(population_foo.add_agent(agent_baz));
static_cast<void>(population_foo.add_agent(agent_qux));
auto tval = make_shared<vector<AgentID>>();
auto rval = population_foo.get_agent_list();
tval->push_back(agent_foo->get_agent_id());
tval->push_back(agent_bar->get_agent_id());
tval->push_back(agent_baz->get_agent_id());
tval->push_back(agent_qux->get_agent_id());
EXPECT_EQ(*tval, *rval);
}
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,52 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <kami/position.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace kami;
class PositionTest
: public ::testing::Test {
protected:
Position pos_foo = GridCoord1D(5);
Position pos_bar = GridCoord2D(2, 5);
};
TEST_F(PositionTest, DefaultConstructor) {
EXPECT_EQ(pos_foo, pos_foo);
EXPECT_NE(pos_foo, pos_bar);
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

170
test/unit-kami-random.cc Normal file
View File

@@ -0,0 +1,170 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <memory>
#include <random>
#include <set>
#include <utility>
#include <vector>
#include <kami/agent.h>
#include <kami/population.h>
#include <kami/random.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace kami;
using namespace std;
class TestAgent
: public Agent {
public:
AgentID step(shared_ptr<Model> model) override {
return get_agent_id();
}
};
class TestModel
: public Model {
public:
shared_ptr<vector<AgentID>> retval;
shared_ptr<Model> step() override {
retval = _sched->step(shared_from_this());
return shared_from_this();
}
shared_ptr<Model> step(unique_ptr<vector<AgentID>> agent_list) {
retval = _sched->step(shared_from_this(), std::move(agent_list));
return shared_from_this();
}
};
class RandomSchedulerTest
: public ::testing::Test {
protected:
shared_ptr<TestModel> mod = nullptr;
shared_ptr<mt19937> rng = nullptr;
void SetUp() override {
mod = make_shared<TestModel>();
rng = make_shared<mt19937>();
auto popul_foo = make_shared<Population>();
auto sched_foo = make_shared<RandomScheduler>(rng);
// Domain is not required for this test
static_cast<void>(mod->set_population(popul_foo));
static_cast<void>(mod->set_scheduler(sched_foo));
for (auto i = 0; i < 10; i++) {
auto agent_foo = make_shared<TestAgent>();
static_cast<void>(popul_foo->add_agent(agent_foo));
}
}
};
TEST(RandomScheduler, DefaultConstructor) {
// There is really no way this can go wrong, but
// we add this check anyway in case of future
// changes.
EXPECT_NO_THROW(
const RandomScheduler sched_foo;
);
}
TEST_F(RandomSchedulerTest, step_interface1) {
auto tval = mod->get_population()->get_agent_list();
auto aval = mod->get_population()->get_agent_list();
mod->step(std::move(aval));
auto rval = mod->retval;
EXPECT_EQ(rval->size(), 10);
// Sort both return values and just make sure all of them all the same...
// We cannot test permutation since, well, you know...
set < AgentID > tval_set = set(tval->begin(), tval->end());
set < AgentID > rval_set = set(rval->begin(), rval->end());
EXPECT_EQ(tval_set, rval_set);
}
TEST_F(RandomSchedulerTest, step_interface2) {
auto tval = mod->get_population()->get_agent_list();
auto aval = mod->get_population()->get_agent_list();
mod->step(std::move(aval));
auto rval = mod->retval;
EXPECT_TRUE(rval);
EXPECT_EQ(rval->size(), 10);
set < AgentID > tval_set = set(tval->begin(), tval->end());
set < AgentID > rval_set = set(rval->begin(), rval->end());
EXPECT_EQ(tval_set, rval_set);
}
TEST_F(RandomSchedulerTest, step_10000) {
// Do it a lot...
for (auto i = 0; i < 10000; i++) {
auto tval = mod->get_population()->get_agent_list();
auto aval = mod->get_population()->get_agent_list();
mod->step(std::move(aval));
auto rval = mod->retval;
EXPECT_TRUE(rval);
EXPECT_EQ(rval->size(), 10);
set < AgentID > tval_set = set(tval->begin(), tval->end());
set < AgentID > rval_set = set(rval->begin(), rval->end());
EXPECT_EQ(tval_set, rval_set);
}
}
TEST_F(RandomSchedulerTest, get_rng) {
auto rval = static_pointer_cast<RandomScheduler>(mod->get_scheduler())->get_rng();
EXPECT_EQ(rng, rval);
}
TEST_F(RandomSchedulerTest, set_rng) {
auto new_rng = make_shared<mt19937>();
auto rval1 = static_pointer_cast<RandomScheduler>(mod->get_scheduler())->get_rng();
static_cast<void>(static_pointer_cast<RandomScheduler>(mod->get_scheduler())->set_rng(new_rng));
auto rval2 = static_pointer_cast<RandomScheduler>(mod->get_scheduler())->get_rng();
EXPECT_EQ(new_rng, rval2);
EXPECT_NE(new_rng, rval1);
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

133
test/unit-kami-reporter.cc Normal file
View File

@@ -0,0 +1,133 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include <kami/agent.h>
#include <kami/population.h>
#include <kami/reporter.h>
#include <kami/staged.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace kami;
using namespace std;
class TestAgent
: public ReporterAgent {
public:
AgentID step(shared_ptr<ReporterModel> model) override {
return get_agent_id();
}
std::unique_ptr<nlohmann::json> collect() override {
auto json_ret_val = std::make_unique<nlohmann::json>();
(*json_ret_val)["fname"] = "Jesse";
(*json_ret_val)["lname"] = "Pinkman";
return json_ret_val;
}
};
class TestModel
: public ReporterModel {
public:
shared_ptr<vector<AgentID>> retval;
std::unique_ptr<nlohmann::json> collect() override {
auto json_ret_val = std::make_unique<nlohmann::json>();
(*json_ret_val)["fname"] = "Walter";
(*json_ret_val)["lname"] = "White";
return json_ret_val;
}
};
class ReporterModelTest
: public ::testing::Test {
protected:
shared_ptr<TestModel> mod = nullptr;
void SetUp() override {
mod = make_shared<TestModel>();
auto popul_foo = make_shared<Population>();
auto sched_foo = make_shared<SequentialScheduler>();
// Domain is not required for this test
static_cast<void>(mod->set_population(popul_foo));
static_cast<void>(mod->set_scheduler(sched_foo));
for (auto i = 0; i < 3; i++) {
auto agent_foo = make_shared<TestAgent>();
static_cast<void>(popul_foo->add_agent(agent_foo));
}
}
};
TEST(ReporterModel, DefaultConstructor) {
// There is really no way this can go wrong, but
// we add this check anyway in case of future
// changes.
EXPECT_NO_THROW(
const TestModel reporter_foo;
);
}
TEST_F(ReporterModelTest, collect) {
auto aval = mod->get_population()->get_agent_list();
mod->step();
auto rval = mod->collect();
EXPECT_TRUE(rval);
EXPECT_EQ(rval->dump(), "{\"fname\":\"Walter\",\"lname\":\"White\"}");
}
TEST_F(ReporterModelTest, report) {
for (auto i = 0; i < 2; i++) {
auto aval = mod->get_population()->get_agent_list();
mod->step();
auto rval = mod->collect();
}
auto rval = mod->report();
EXPECT_EQ(rval->dump(),
"[{\"agent_data\":[{\"agent_id\":\"13\",\"data\":{\"fname\":\"Jesse\",\"lname\":\"Pinkman\"}},{\"agent_id\":\"14\",\"data\":{\"fname\":\"Jesse\",\"lname\":\"Pinkman\"}},{\"agent_id\":\"15\",\"data\":{\"fname\":\"Jesse\",\"lname\":\"Pinkman\"}}],\"model_data\":{\"fname\":\"Walter\",\"lname\":\"White\"},\"step_id\":1},{\"agent_data\":[{\"agent_id\":\"13\",\"data\":{\"fname\":\"Jesse\",\"lname\":\"Pinkman\"}},{\"agent_id\":\"14\",\"data\":{\"fname\":\"Jesse\",\"lname\":\"Pinkman\"}},{\"agent_id\":\"15\",\"data\":{\"fname\":\"Jesse\",\"lname\":\"Pinkman\"}}],\"model_data\":{\"fname\":\"Walter\",\"lname\":\"White\"},\"step_id\":2}]");
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,119 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <memory>
#include <kami/agent.h>
#include <kami/model.h>
#include <kami/reporter.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace kami;
using namespace std;
class TestAgent
: public ReporterAgent {
public:
AgentID step(shared_ptr<ReporterModel> model) override {
return get_agent_id();
}
std::unique_ptr<nlohmann::json> collect() override {
auto json_ret_val = std::make_unique<nlohmann::json>();
(*json_ret_val)["fname"] = "Gus";
(*json_ret_val)["lname"] = "Fring";
return json_ret_val;
}
};
class TestModel
: public ReporterModel {
public:
std::unique_ptr<nlohmann::json> collect() override {
return std::make_unique<nlohmann::json>();
}
};
class ReporterAgentTest
: public ::testing::Test {
protected:
TestAgent agent_foo;
TestAgent agent_bar;
shared_ptr<TestModel> model_world = nullptr;
void SetUp() override {
model_world = make_shared<TestModel>();
}
};
TEST(ReporterAgent, DefaultConstructor) {
EXPECT_NO_THROW(
const TestAgent agent_baz;
const TestAgent agent_qux;
);
}
TEST_F(ReporterAgentTest, equivalance) {
EXPECT_EQ(agent_foo, agent_foo);
EXPECT_NE(agent_foo, agent_bar);
}
TEST_F(ReporterAgentTest, get_agent_id) {
EXPECT_EQ(agent_foo.get_agent_id(), agent_foo.get_agent_id());
EXPECT_NE(agent_bar.get_agent_id(), agent_foo.get_agent_id());
}
TEST_F(ReporterAgentTest, step) {
EXPECT_EQ(agent_foo.get_agent_id(), agent_foo.step(model_world));
EXPECT_NE(agent_bar.get_agent_id(), agent_foo.step(model_world));
}
TEST_F(ReporterAgentTest, collect) {
EXPECT_EQ(agent_foo.collect()->dump(), "{\"fname\":\"Gus\",\"lname\":\"Fring\"}");
EXPECT_NE(agent_bar.collect()->dump(), "{\"fname\":\"Hank\",\"lname\":\"Schrader\"}");
}
TEST_F(ReporterAgentTest, equality) {
EXPECT_TRUE(agent_foo == agent_foo);
EXPECT_TRUE(agent_bar == agent_bar);
}
TEST_F(ReporterAgentTest, inequality) {
EXPECT_TRUE(agent_foo != agent_bar);
EXPECT_FALSE(agent_bar != agent_bar);
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,140 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <memory>
#include <utility>
#include <vector>
#include <kami/agent.h>
#include <kami/population.h>
#include <kami/sequential.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace kami;
using namespace std;
class TestAgent
: public Agent {
public:
AgentID step(shared_ptr<Model> model) override {
return get_agent_id();
}
};
class TestModel
: public Model {
public:
shared_ptr<vector<AgentID>> retval;
shared_ptr<Model> step() override {
retval = _sched->step(shared_from_this());
return shared_from_this();
}
shared_ptr<Model> step(unique_ptr<vector<AgentID>> agent_list) {
retval = _sched->step(shared_from_this(), std::move(agent_list));
return shared_from_this();
}
};
class SequentialSchedulerTest
: public ::testing::Test {
protected:
shared_ptr<TestModel> mod = nullptr;
void SetUp() override {
mod = make_shared<TestModel>();
auto pop_foo = make_shared<Population>();
auto sched_foo = make_shared<SequentialScheduler>();
// Domain is not required for this test
static_cast<void>(mod->set_population(pop_foo));
static_cast<void>(mod->set_scheduler(sched_foo));
for (auto i = 0; i < 10; i++) {
auto agent_foo = make_shared<TestAgent>();
static_cast<void>(pop_foo->add_agent(agent_foo));
}
}
};
TEST(SequentialScheduler, DefaultConstructor) {
// There is really no way this can go wrong, but
// we add this check anyway in case of future
// changes.
EXPECT_NO_THROW(
const SequentialScheduler sched_foo;
);
}
TEST_F(SequentialSchedulerTest, step_interface1) {
auto tval = mod->get_population()->get_agent_list();
auto aval = mod->get_population()->get_agent_list();
mod->step(std::move(aval));
auto rval = mod->retval;
EXPECT_TRUE(rval);
EXPECT_EQ(rval->size(), 10);
EXPECT_EQ(*rval, *tval);
}
TEST_F(SequentialSchedulerTest, step_interface2) {
auto tval = mod->get_population()->get_agent_list();
auto aval = mod->get_population()->get_agent_list();
mod->step(std::move(aval));
auto rval = mod->retval;
EXPECT_TRUE(rval);
EXPECT_EQ(rval->size(), 10);
EXPECT_EQ(*rval, *tval);
}
TEST_F(SequentialSchedulerTest, step_10000) {
// Do it a lot...
for (auto i = 0; i < 10000; i++) {
auto tval = mod->get_population()->get_agent_list();
auto aval = mod->get_population()->get_agent_list();
mod->step(std::move(aval));
auto rval = mod->retval;
EXPECT_TRUE(rval);
EXPECT_EQ(rval->size(), 10);
EXPECT_EQ(*rval, *tval);
}
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,471 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <algorithm>
#include <iterator>
#include <set>
#include <unordered_set>
#include <kami/agent.h>
#include <kami/error.h>
#include <kami/sologrid1d.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace kami;
using namespace kami::error;
using namespace std;
TEST(SoloGrid1D, DefaultConstructor) {
// There is really no way this can go wrong, but
// we add this check anyway in case of future
// changes.
EXPECT_NO_THROW(
SoloGrid1D sologrid1d_foo(10, true);
);
}
TEST(SoloGrid1D, add_agent) {
SoloGrid1D sologrid1d_foo(10, true);
const AgentID agent_id_foo, agent_id_bar;
const GridCoord1D coord2(2), coord3(3);
{
auto agent_id_baz = sologrid1d_foo.add_agent(agent_id_foo, coord2);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
{
EXPECT_THROW(auto agent_id_baz = sologrid1d_foo.add_agent(agent_id_bar, coord2), LocationUnavailable);
}
{
auto agent_id_baz = sologrid1d_foo.add_agent(agent_id_bar, coord3);
EXPECT_EQ(agent_id_baz, agent_id_bar);
}
}
TEST(SoloGrid1D, delete_agent) {
const AgentID agent_id_foo, agent_id_bar;
const GridCoord1D coord2(2), coord3(3);
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
auto agent_id_baz = sologrid1d_foo.delete_agent(agent_id_foo);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_THROW(static_cast<void>(sologrid1d_foo.add_agent(agent_id_bar, coord2)), LocationUnavailable);
auto agent_id_baz = sologrid1d_foo.delete_agent(agent_id_foo);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_THROW(static_cast<void>(sologrid1d_foo.add_agent(agent_id_bar, coord2)), LocationUnavailable);
EXPECT_THROW(auto agent_id_baz = sologrid1d_foo.delete_agent(agent_id_bar), AgentNotFound);
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
auto agent_id_baz = sologrid1d_foo.delete_agent(agent_id_foo, coord2);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_THROW(static_cast<void>(sologrid1d_foo.add_agent(agent_id_bar, coord2)), LocationUnavailable);
auto agent_id_baz = sologrid1d_foo.delete_agent(agent_id_foo, coord2);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_THROW(static_cast<void>(sologrid1d_foo.add_agent(agent_id_bar, coord2)), LocationUnavailable);
EXPECT_THROW(auto agent_id_baz = sologrid1d_foo.delete_agent(agent_id_bar, coord2), AgentNotFound);
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_THROW(auto agent_id_baz = sologrid1d_foo.delete_agent(agent_id_foo, coord3), AgentNotFound);
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_THROW(static_cast<void>(sologrid1d_foo.add_agent(agent_id_bar, coord2)), LocationUnavailable);
EXPECT_THROW(auto agent_id_baz = sologrid1d_foo.delete_agent(agent_id_foo, coord3), AgentNotFound);
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_THROW(static_cast<void>(sologrid1d_foo.add_agent(agent_id_bar, coord2)), LocationUnavailable);
EXPECT_THROW(auto agent_id_baz = sologrid1d_foo.delete_agent(agent_id_bar, coord3), AgentNotFound);
}
}
TEST(SoloGrid1D, is_location_valid) {
SoloGrid1D sologrid1d_foo(10, true);
const AgentID agent_id_foo, agent_id_bar;
const GridCoord1D coordm1(-1), coord0(0), coord2(2), coord3(3), coord10(10), coord100(100);
{
EXPECT_TRUE(sologrid1d_foo.is_location_valid(coord0));
EXPECT_TRUE(sologrid1d_foo.is_location_valid(coord2));
EXPECT_TRUE(sologrid1d_foo.is_location_valid(coord2));
EXPECT_FALSE(sologrid1d_foo.is_location_valid(coordm1));
EXPECT_FALSE(sologrid1d_foo.is_location_valid(coord10));
EXPECT_FALSE(sologrid1d_foo.is_location_valid(coord100));
}
}
TEST(SoloGrid1D, is_location_empty) {
const AgentID agent_id_foo, agent_id_bar;
const GridCoord1D coord2(2), coord3(3);
{
SoloGrid1D sologrid1d_foo(10, true);
EXPECT_TRUE(sologrid1d_foo.is_location_empty(coord2));
EXPECT_TRUE(sologrid1d_foo.is_location_empty(coord3));
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_FALSE(sologrid1d_foo.is_location_empty(coord2));
EXPECT_TRUE(sologrid1d_foo.is_location_empty(coord3));
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_THROW(static_cast<void>(sologrid1d_foo.add_agent(agent_id_bar, coord2)), LocationUnavailable);
EXPECT_FALSE(sologrid1d_foo.is_location_empty(coord2));
EXPECT_TRUE(sologrid1d_foo.is_location_empty(coord3));
}
}
TEST(SoloGrid1D, move_agent) {
const AgentID agent_id_foo, agent_id_bar;
const GridCoord1D coord2(2), coord3(3), coord7(7), coord10(10);
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
auto agent_id_baz = sologrid1d_foo.move_agent(agent_id_foo, coord7);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_THROW(auto agent_id_baz = sologrid1d_foo.move_agent(agent_id_foo, coord10), LocationInvalid);
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_THROW(static_cast<void>(sologrid1d_foo.add_agent(agent_id_bar, coord2)), LocationUnavailable);
auto agent_id_baz = sologrid1d_foo.move_agent(agent_id_foo, coord2);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
EXPECT_THROW(static_cast<void>(sologrid1d_foo.add_agent(agent_id_bar, coord2)), LocationUnavailable);
auto agent_id_baz = sologrid1d_foo.move_agent(agent_id_foo, coord7);
EXPECT_EQ(agent_id_baz, agent_id_foo);
}
}
TEST(SoloGrid1D, get_neighborhood) {
const AgentID agent_id_foo, agent_id_bar;
const GridCoord1D coord0(0), coord1(1), coord2(2), coord3(3), coord9(9);
{
SoloGrid1D sologrid1d_foo(10, true);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1, coord9
});
auto rval = sologrid1d_foo.get_neighborhood(coord0, true);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, true);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1, coord2
});
auto rval = sologrid1d_foo.get_neighborhood(coord1, true);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, false);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1
});
auto rval = sologrid1d_foo.get_neighborhood(coord0, true);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, false);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1, coord2
});
auto rval = sologrid1d_foo.get_neighborhood(coord1, true);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, true);
auto tval = unordered_set < GridCoord1D > ({
coord1, coord9
});
auto rval = sologrid1d_foo.get_neighborhood(coord0, false);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, true);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord2
});
auto rval = sologrid1d_foo.get_neighborhood(coord1, false);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, false);
auto tval = unordered_set < GridCoord1D > ({
coord1
});
auto rval = sologrid1d_foo.get_neighborhood(coord0, false);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, false);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord2
});
auto rval = sologrid1d_foo.get_neighborhood(coord1, false);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, true);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1, coord9
});
EXPECT_THROW(auto rval = sologrid1d_foo.get_neighborhood(agent_id_foo, true), AgentNotFound);
}
{
SoloGrid1D sologrid1d_foo(10, true);
sologrid1d_foo.add_agent(agent_id_foo, coord0);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1, coord9
});
auto rval = sologrid1d_foo.get_neighborhood(agent_id_foo, true);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, true);
sologrid1d_foo.add_agent(agent_id_foo, coord1);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1, coord2
});
auto rval = sologrid1d_foo.get_neighborhood(agent_id_foo, true);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, false);
sologrid1d_foo.add_agent(agent_id_foo, coord0);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1
});
auto rval = sologrid1d_foo.get_neighborhood(agent_id_foo, true);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, false);
sologrid1d_foo.add_agent(agent_id_foo, coord1);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord1, coord2
});
auto rval = sologrid1d_foo.get_neighborhood(agent_id_foo, true);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, true);
sologrid1d_foo.add_agent(agent_id_foo, coord0);
auto tval = unordered_set < GridCoord1D > ({
coord1, coord9
});
auto rval = sologrid1d_foo.get_neighborhood(agent_id_foo, false);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, true);
sologrid1d_foo.add_agent(agent_id_foo, coord1);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord2
});
auto rval = sologrid1d_foo.get_neighborhood(agent_id_foo, false);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, false);
sologrid1d_foo.add_agent(agent_id_foo, coord0);
auto tval = unordered_set < GridCoord1D > ({
coord1
});
auto rval = sologrid1d_foo.get_neighborhood(agent_id_foo, false);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, false);
sologrid1d_foo.add_agent(agent_id_foo, coord1);
auto tval = unordered_set < GridCoord1D > ({
coord0, coord2
});
auto rval = sologrid1d_foo.get_neighborhood(agent_id_foo, false);
EXPECT_EQ(tval, *rval);
}
}
TEST(SoloGrid1D, get_location_by_agent) {
const AgentID agent_id_foo, agent_id_bar;
const GridCoord1D coord2(2), coord3(3);
{
SoloGrid1D sologrid1d_foo(10, true);
EXPECT_THROW(auto loc1 = sologrid1d_foo.get_location_by_agent(agent_id_foo), AgentNotFound);
EXPECT_THROW(auto loc2 = sologrid1d_foo.get_location_by_agent(agent_id_bar), AgentNotFound);
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord2));
auto local = sologrid1d_foo.get_location_by_agent(agent_id_foo);
EXPECT_EQ(local, coord2);
EXPECT_THROW(auto loc = sologrid1d_foo.get_location_by_agent(agent_id_bar), AgentNotFound);
}
}
TEST(SoloGrid1D, get_location_contents) {
const AgentID agent_id_foo, agent_id_bar, agent_id_baz;
const GridCoord1D coord0(0), coord1(1), coord9(9), coord10(10);
{
SoloGrid1D sologrid1d_foo(10, true);
EXPECT_THROW(auto agent_list_foo = sologrid1d_foo.get_location_contents(coord10), LocationUnavailable);
}
{
SoloGrid1D sologrid1d_foo(10, true);
auto agent_list_foo = sologrid1d_foo.get_location_contents(coord1);
EXPECT_TRUE(agent_list_foo);
EXPECT_TRUE(agent_list_foo->empty());
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord1));
EXPECT_THROW(static_cast<void>(sologrid1d_foo.add_agent(agent_id_bar, coord1)), LocationUnavailable);
EXPECT_THROW(static_cast<void>(sologrid1d_foo.add_agent(agent_id_baz, coord1)), LocationUnavailable);
auto tval = set < AgentID > ({
agent_id_foo
});
auto rval = sologrid1d_foo.get_location_contents(coord1);
EXPECT_TRUE(rval);
EXPECT_EQ(tval, *rval);
}
{
SoloGrid1D sologrid1d_foo(10, true);
static_cast<void>(sologrid1d_foo.add_agent(agent_id_foo, coord1));
EXPECT_THROW(static_cast<void>(sologrid1d_foo.add_agent(agent_id_bar, coord1)), LocationUnavailable);
EXPECT_THROW(static_cast<void>(sologrid1d_foo.add_agent(agent_id_baz, coord1)), LocationUnavailable);
auto tval = set < AgentID > ({
agent_id_foo
});
auto rval = sologrid1d_foo.get_location_contents(coord1);
EXPECT_TRUE(rval);
EXPECT_EQ(tval, *rval);
}
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

1101
test/unit-kami-sologrid2d.cc Normal file

File diff suppressed because it is too large Load Diff

142
test/unit-kami-staged.cc Normal file
View File

@@ -0,0 +1,142 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include <kami/agent.h>
#include <kami/population.h>
#include <kami/staged.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace kami;
using namespace std;
class TestAgent
: public StagedAgent {
public:
AgentID step(shared_ptr<Model> model) override {
return get_agent_id();
}
AgentID advance(shared_ptr<Model> model) override {
return get_agent_id();
}
};
class TestModel
: public Model {
public:
shared_ptr<vector<AgentID>> retval;
shared_ptr<Model> step() override {
retval = _sched->step(shared_from_this());
return shared_from_this();
}
shared_ptr<Model> step(unique_ptr<vector<AgentID>> agent_list) {
retval = _sched->step(shared_from_this(), std::move(agent_list));
return shared_from_this();
}
};
class StagedSchedulerTest
: public ::testing::Test {
protected:
shared_ptr<TestModel> mod = nullptr;
void SetUp() override {
mod = make_shared<TestModel>();
auto popul_foo = make_shared<Population>();
auto sched_foo = make_shared<StagedScheduler>();
// Domain is not required for this test
static_cast<void>(mod->set_population(popul_foo));
static_cast<void>(mod->set_scheduler(sched_foo));
for (auto i = 0; i < 10; i++) {
auto agent_foo = make_shared<TestAgent>();
static_cast<void>(popul_foo->add_agent(agent_foo));
}
}
};
TEST(StagedScheduler, DefaultConstructor) {
// There is really no way this can go wrong, but
// we add this check anyway in case of future
// changes.
EXPECT_NO_THROW(
const StagedScheduler sched_foo;
);
}
TEST_F(StagedSchedulerTest, step_interface1) {
auto tval = mod->get_population()->get_agent_list();
auto aval = mod->get_population()->get_agent_list();
mod->step(std::move(aval));
auto rval = mod->retval;
EXPECT_EQ(rval->size(), 10);
EXPECT_EQ(*rval, *tval);
}
TEST_F(StagedSchedulerTest, step_interface2) {
auto tval = mod->get_population()->get_agent_list();
auto aval = mod->get_population()->get_agent_list();
mod->step(std::move(aval));
auto rval = mod->retval;
EXPECT_EQ(rval->size(), 10);
EXPECT_EQ(*rval, *tval);
}
TEST_F(StagedSchedulerTest, step_10000) {
// Do it a lot...
for (auto i = 0; i < 10000; i++) {
auto tval = mod->get_population()->get_agent_list();
auto aval = mod->get_population()->get_agent_list();
mod->step(std::move(aval));
auto rval = mod->retval;
EXPECT_EQ(rval->size(), 10);
EXPECT_EQ(*rval, *tval);
}
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,109 @@
/*-
* Copyright (c) 2022 The Johns Hopkins University Applied Physics
* Laboratory LLC
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <memory>
#include <kami/agent.h>
#include <kami/model.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace kami;
using namespace std;
class TestAgent
: public StagedAgent {
public:
AgentID advance(shared_ptr<Model> model) override {
return get_agent_id();
}
AgentID step(shared_ptr<Model> model) override {
return get_agent_id();
}
};
class TestModel
: public Model {
};
class StagedAgentTest
: public ::testing::Test {
protected:
TestAgent agent_foo;
TestAgent agent_bar;
shared_ptr<TestModel> model_world = nullptr;
void SetUp() override {
model_world = make_shared<TestModel>();
}
};
TEST(StagedAgent, DefaultConstructor) {
EXPECT_NO_THROW(
const TestAgent agent_baz;
const TestAgent agent_qux;
);
}
TEST_F(StagedAgentTest, equivalance) {
EXPECT_EQ(agent_foo, agent_foo);
EXPECT_NE(agent_foo, agent_bar);
}
TEST_F(StagedAgentTest, get_agent_id) {
EXPECT_EQ(agent_foo.get_agent_id(), agent_foo.get_agent_id());
EXPECT_NE(agent_bar.get_agent_id(), agent_foo.get_agent_id());
}
TEST_F(StagedAgentTest, step) {
EXPECT_EQ(agent_foo.get_agent_id(), agent_foo.step(model_world));
EXPECT_NE(agent_bar.get_agent_id(), agent_foo.step(model_world));
}
TEST_F(StagedAgentTest, advance) {
EXPECT_EQ(agent_foo.get_agent_id(), agent_foo.advance(model_world));
EXPECT_NE(agent_bar.get_agent_id(), agent_foo.advance(model_world));
}
TEST_F(StagedAgentTest, equality) {
EXPECT_TRUE(agent_foo == agent_foo);
EXPECT_TRUE(agent_bar == agent_bar);
}
TEST_F(StagedAgentTest, inequality) {
EXPECT_TRUE(agent_foo != agent_bar);
EXPECT_FALSE(agent_bar != agent_bar);
}
int main(
int argc,
char** argv
) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}