Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

646 Use graph model with abms #1065

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open

Conversation

jubicker
Copy link
Member

@jubicker jubicker commented Jul 9, 2024

Changes and Information

  • Create simulation graph nodes and mobility edges for ABM

  • Add world id to world and location and include id in functions where necesarry

  • Add activeness vector for persons to world

  • Add tests and example for abm graph model

  • The performance has slightly decreased (5-10%), because an additional check in some functions is now necessary whether a person is active in the current world (e.g. in migration and interaction function). For more information about performance, see the comment below

Merge Request - Guideline Checklist

Please check our git workflow. Use the draft feature if the Pull Request is not yet ready to review.

Checks by code author

  • Every addressed issue is linked (use the "Closes #ISSUE" keyword below)
  • New code adheres to coding guidelines
  • No large data files have been added (files should in sum not exceed 100 KB, avoid PDFs, Word docs, etc.)
  • Tests are added for new functionality and a local test run was successful (with and without OpenMP)
  • Appropriate documentation for new functionality has been added (Doxygen in the code and Markdown files if necessary)
  • Proper attention to licenses, especially no new third-party software with conflicting license has been added
  • (For ABM development) Checked benchmark results and ran and posted a local test above from before and after development to ensure performance is monitored.

Checks by code reviewer(s)

  • Corresponding issue(s) is/are linked and addressed
  • Code is clean of development artifacts (no deactivated or commented code lines, no debugging printouts, etc.)
  • Appropriate unit tests have been added, CI passes, code coverage and performance is acceptable (did not decrease)
  • No large data files added in the whole history of commits(files should in sum not exceed 100 KB, avoid PDFs, Word docs, etc.)
  • On merge, add 2-5 lines with the changes (main added features, changed items, or corrected bugs) to the merge-commit-message. This can be taken from the briefly-list-the-changes above (best case) or the separate commit messages (worst case).

@jubicker jubicker linked an issue Jul 9, 2024 that may be closed by this pull request
2 tasks
Copy link

codecov bot commented Jul 9, 2024

Codecov Report

Attention: Patch coverage is 79.61165% with 42 lines in your changes missing coverage. Please review.

Project coverage is 95.87%. Comparing base (d59fbd7) to head (1f85548).
Report is 2 commits behind head on main.

Files Patch % Lines
cpp/models/abm/world.h 62.79% 16 Missing ⚠️
cpp/models/graph_abm/graph_abm_mobility.h 77.41% 14 Missing ⚠️
cpp/models/graph_abm/mobility_rules.cpp 56.25% 7 Missing ⚠️
cpp/models/abm/world.cpp 89.13% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1065      +/-   ##
==========================================
- Coverage   96.08%   95.87%   -0.22%     
==========================================
  Files         131      133       +2     
  Lines       11048    11188     +140     
==========================================
+ Hits        10616    10726     +110     
- Misses        432      462      +30     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jubicker
Copy link
Member Author

jubicker commented Jul 24, 2024

Performance Measures:
The following are the mean results of 10 executions of the abm benchmark for a single thread (OMP_NUM_THREADS=1)

Main:

---------------------------------------------------------------------------
Benchmark                                 Time             CPU
---------------------------------------------------------------------------
abm_benchmark/abm_benchmark_50k        3178,6 ms      3177,1 ms
abm_benchmark/abm_benchmark_100k      7128,8 ms      7125,8 ms
abm_benchmark/abm_benchmark_200k      14771 ms       14766,3 ms

Graph_ABM:

---------------------------------------------------------------------------
Benchmark                                 Time             CPU
---------------------------------------------------------------------------
abm_benchmark/abm_benchmark_50k        3537,5 ms      3535,5 ms
abm_benchmark/abm_benchmark_100k      7611 ms         7608 ms
abm_benchmark/abm_benchmark_200k      15674,8 ms    15669,6 ms

So 5-10% performance loss.

@jubicker jubicker requested a review from reneSchm July 25, 2024 08:42
Copy link
Member

@reneSchm reneSchm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not entirely gone through the graph_abm and tests directories yet, so take this as a preliminary review.

Comment on lines +38 to +39
//Logger
struct Logger : mio::LogAlways {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A slightly more descriptive name would be neat. It is fine as long as this stays in the example.

Comment on lines +59 to +61
location_information.push_back(std::make_tuple(loc.get_world_id(), loc.get_type(), loc.get_id(),
sim.get_world().get_number_persons(loc.get_id()),
persons_per_infection_state));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this causes too many reallocations, you could reserve #worlds * #locations in world 0 vector entries.

Comment on lines +88 to +90
class edge_f = void (*)(Timepoint, Timespan, typename Graph::EdgeProperty&, typename Graph::NodeProperty&,
typename Graph::NodeProperty&),
class node_f = void (*)(Timepoint, Timespan, typename Graph::NodeProperty&)>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we store these functions, we should preferably use std::functions instead of function pointers. I don't think this will cause too much overhead, and it allows using e.g. a lambda as node function.

Comment on lines +93 to +94
using GraphSimulationBase<Graph, Timepoint, Timespan, edge_f, node_f>::GraphSimulationBase;
using Base = GraphSimulationBase<Graph, Timepoint, Timespan, edge_f, node_f>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using GraphSimulationBase<Graph, Timepoint, Timespan, edge_f, node_f>::GraphSimulationBase;
using Base = GraphSimulationBase<Graph, Timepoint, Timespan, edge_f, node_f>;
using Base = GraphSimulationBase<Graph, Timepoint, Timespan, edge_f, node_f>;
using Base::GraphSimulationBase;

to avoid rewriting all template params, or even using Base::Base;.

Comment on lines +528 to +530
GraphSimulation<Graph<SimulationNode<Sim>, MigrationEdge<FP>>, FP, FP,
void (*)(double, double, mio::MigrationEdge<>&, mio::SimulationNode<Sim>&, mio::SimulationNode<Sim>&),
void (*)(double, double, mio::SimulationNode<Sim>&)>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should be an std::function as well

Comment on lines +320 to +327
/**
* Get activeness status of all persons in the world.
* @return Activeness vector
*/
std::vector<bool>& get_activeness_statuses()
{
return m_activeness_statuses;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think something like set_person_active(PersonId person_id, bool status) would be better than exposing a private member.

Comment on lines +533 to +540
/**
* @brief Invalidate local population and exposure rate cache.
*/
void invalidate_cache()
{
m_are_exposure_caches_valid = false;
m_is_local_population_cache_valid = false;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am pretty certain this function needs to go. Whatever call causes the need to invalidate caches should already invalidate them.

Comment on lines +473 to +480
/**
* @brief Flip activeness status of a person in the world.
* @param[in] person_id PersonId of Person whose activeness status is fipped.
*/
void change_activeness(PersonId person_id)
{
m_activeness_statuses[person_id.get()] = !m_activeness_statuses[person_id.get()];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function requires already knowing the activeness state of a person. I prefer using the "set_person_active" function I propose above.

Comment on lines +482 to +488
/**
* @brief Copy the persons from another World to this World.
* If the persons are at a location in this world they are activated, otherwise they are deactivated.
* If necessary the person ids are changed such that they correspond to the index in this world's m_persons vector.
* @param[in] other The World the Person%s are copied from.
*/
void copy_persons_from_other_world(const World& other)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer adding an (optional) world_id to World::add_person and make this an external function using add_person.

Comment on lines +506 to +510
/**
* @brief Set the Person%s of the World.
* @param[in] persons The Person%s of the World.
*/
void set_persons(std::vector<Person>& persons)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this too behaves very similar to for (p : persons) add_person(p) I would prefer making this an external function.
Maybe we should consider adding a builder class for World or even the entire World-Graph.

Comment on lines +245 to +250
//change person's location in all nodes
mio::abm::migrate(person_n1, target_location);
mio::abm::migrate(person_n2, target_location);
// invalidate both worlds' cache
node_to.get_simulation().get_world().invalidate_cache();
node_from.get_simulation().get_world().invalidate_cache();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. We could implement a World::migrate that takes IDs, similar to World::interact. This could also take care of the caches.

Comment on lines +231 to +233
auto& target_world = (target_world_id == node_from.get_simulation().get_world().get_id())
? node_from.get_simulation().get_world()
: node_to.get_simulation().get_world();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the ternary condition is true, then why do we not stop here? The person won't travel along the edge, and just stay in its world - so this should be covered by World::migration, shouldn't it?

Comment on lines +240 to +241
mio::log_error("Person with index {} has two assigned locations of the same type.",
person_n1.get_id().get());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message seems to be misleading. An instance of Person cannot have two assigned locations of the same type, since they are stored in a vector with type as its index. So I am not sure what can actually trigger this error, maybe setting the initial location as an unassigned location?

@jubicker
Copy link
Member Author

jubicker commented Jul 31, 2024

Some more Benchmarks:

I got the following results for 5 executions of the abm benchmarks for one single thread. For every benchmark I added inactive agents and compared the results for num_inactive_agents=0, num_agents, 2x num_agents, 3x num_agents.

For 50k agents:

---------------------------------------------------------------------------
Benchmark                        Time         Deviation from no inactive
---------------------------------------------------------------------------
abm_benchmark_50k_0_inactive     3568,8 ms     0%
abm_benchmark_50k_1_inactive     4072,6 ms     14%
abm_benchmark_50k_2_inactive     4584 ms       28%
abm_benchmark_50k_3_inactive     5093,8 ms     43%

For 100k agents:

---------------------------------------------------------------------------
Benchmark                        Time         Deviation from no inactive
---------------------------------------------------------------------------
abm_benchmark_100k_0_inactive     7707,2 ms     0%
abm_benchmark_100k_1_inactive     8705 ms       13%
abm_benchmark_100k_2_inactive     9699,6 ms     26%
abm_benchmark_100k_3_inactive     10720,2 ms    39%

For 200k agents:

---------------------------------------------------------------------------
Benchmark                        Time         Deviation from no inactive
---------------------------------------------------------------------------
abm_benchmark_200k_0_inactive     15850 ms     0%
abm_benchmark_200k_1_inactive     17898,6 ms   13%
abm_benchmark_200k_2_inactive     19949,2 ms   26%
abm_benchmark_200k_3_inactive     22033,6 ms   39%

Copy link
Member

@xsaschako xsaschako left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm way more confident that this is okay after i went through it :-).

Just one comment before i do a full review:
Look out for a way to handle a default "all persons are active" so that maybe there is a way that this doesn't affect the performance in a normal abm at all :-)

@jubicker jubicker removed a link to an issue Aug 1, 2024
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants