-
Notifications
You must be signed in to change notification settings - Fork 16
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
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
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. |
…/SciCompMod/memilio into 646-use-graph-model-with-abms-v2
Performance Measures: Main:
Graph_ABM:
So 5-10% performance loss. |
…/SciCompMod/memilio into 646-use-graph-model-with-abms-v2
…/SciCompMod/memilio into 646-use-graph-model-with-abms-v2
There was a problem hiding this 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.
//Logger | ||
struct Logger : mio::LogAlways { |
There was a problem hiding this comment.
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.
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)); |
There was a problem hiding this comment.
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.
class edge_f = void (*)(Timepoint, Timespan, typename Graph::EdgeProperty&, typename Graph::NodeProperty&, | ||
typename Graph::NodeProperty&), | ||
class node_f = void (*)(Timepoint, Timespan, typename Graph::NodeProperty&)> |
There was a problem hiding this comment.
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.
using GraphSimulationBase<Graph, Timepoint, Timespan, edge_f, node_f>::GraphSimulationBase; | ||
using Base = GraphSimulationBase<Graph, Timepoint, Timespan, edge_f, node_f>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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;
.
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>&)> |
There was a problem hiding this comment.
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
/** | ||
* Get activeness status of all persons in the world. | ||
* @return Activeness vector | ||
*/ | ||
std::vector<bool>& get_activeness_statuses() | ||
{ | ||
return m_activeness_statuses; | ||
} |
There was a problem hiding this comment.
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.
/** | ||
* @brief Invalidate local population and exposure rate cache. | ||
*/ | ||
void invalidate_cache() | ||
{ | ||
m_are_exposure_caches_valid = false; | ||
m_is_local_population_cache_valid = false; | ||
} |
There was a problem hiding this comment.
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.
/** | ||
* @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()]; | ||
} |
There was a problem hiding this comment.
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.
/** | ||
* @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) |
There was a problem hiding this comment.
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.
/** | ||
* @brief Set the Person%s of the World. | ||
* @param[in] persons The Person%s of the World. | ||
*/ | ||
void set_persons(std::vector<Person>& persons) |
There was a problem hiding this comment.
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.
//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(); |
There was a problem hiding this comment.
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.
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(); |
There was a problem hiding this comment.
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?
mio::log_error("Person with index {} has two assigned locations of the same type.", | ||
person_n1.get_id().get()); |
There was a problem hiding this comment.
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?
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:
For 100k agents:
For 200k agents:
|
There was a problem hiding this 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 :-)
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
Checks by code reviewer(s)