thoughtworks / epirust

An agent-based epidemiology simulation framework built in Rust
GNU Affero General Public License v3.0
100 stars 14 forks source link

Moving agents between regions in multi-region simulations #7

Closed akshaydewan closed 4 years ago

akshaydewan commented 4 years ago

This issue is to discuss the approaches we can take to implement travels for agents between regions. This first-cut will be with limited scope.

For the sake of this discussion, let's consider 3 regions A, B, C. We will assume that we are given data for citizens' travels between these regions. To limit the scope, we will consider that the travels will happen once every 24 hours. We will also not consider the spread of the disease amongst the travellers while travelling (e.g. in the same bus). All this complexity can be added subsequently.

Defining travels

Given a travel schedule between regions, we can encode this data into a config file available on the orchestrator. For e.g.

[
    {
      "engine_id": "A",
      "travels": [
        {
          "to": "B",
          "count": 156
        },
        {
          "to": "C",
          "count": 10
        }
      ]
    },
    {
      "engine_id": "B",
      "travels": [
        {
          "to": "A",
          "count": 108
        },
        {
          "to": "C",
          "count": 290
        }
      ]
    },
    {
      "engine_id": "C",
      "travels": [
        {
          "to": "A",
          "count": 90
        },
        {
          "to": "B",
          "count": 75
        }
      ]
    }
]

In the example above, every 24 hours, 156 agents will travel from Region A to Region B, and 10 agents will travel from Region A to Region C. Since this configuration can potentially be large as the number of regions increases, we can keep this separate from the simulation configuration. We can call this a travel schedule or a travel plan

Implementing Travels - Option 1

We will let the orchestrator allow the travels to be controlled. This is to allow future flexibilty and allow interventions to be implemented as needed.

The orchestrator sends ticks to the engines. Along with the ticks, we will include a payload when travels are needed. E.g.

after {"tick": 23}

{
  "tick": 24,
  "travels": [
    "... //the above travel-json goes here"
  ]
}

(Note: Using a different topic can potentially cause synchronization headaches, as we have seen, so we piggyback on ticks)

The engines will receive the tick and parse the travels payload (if present). Each engine will then:

For each outgoing region:
    1. Select n random agents to be sent out (agents in hospital or quarantine will not be selected)
    2. Push a message to Kafka with the state of each agent, addressed to the recipient

For each incoming region:
    1. Wait for a message from this region
    2. Receive the expected number of agents and incorporate them into the grid

Send an Ack to the Orchestrator

We will have to create a topic called travels or something to send the travel data between engines. Engines will have to filter out the data relevant to them.

While this allows the orchestrator to adjust the travel schedule every tick, the travel plan can be a potentially large payload to send every time. Instead an alternative can be considered.

Implementing Travels - Option 2

The orchestrator starts off by sending a message on the simulation_requests topic. Along with this request, the travel plans are sent to all the engines as well.

The engines will implement the same algorithm as before, except that they will trigger travels themselves every 24 ticks:

For each outgoing region:
    1. Select n random agents to be sent out (agents in hospital or quarantine will not be selected)
    2. Push a message to Kafka with the state of each agent, addressed to the recipient

For each incoming region:
    1. Wait for a message from this region
    2. Receive the expected number of agents and incorporate them into the grid

Send an Ack to the Orchestrator

For future implementations, in order to "adjust" the travel plan (e.g. implementing a region-wide lockdown as an intervention), the orchestrator can send an updated travel plan along with its tick. This will look the same as the json defined in Option 1.

{
  "tick": 24,
  "travels": [
    "... //the updated travel-json goes here"
  ]
}

This way, we minimize the number of times we need to send the travel plan payload, but we lose the flexibility of allowing travels on arbitrary ticks in the future.

akshaydewan commented 4 years ago

A matrix would be a more concise way of encoding the travel schedule:

{
  "regions": ["A", "B", "C"],
  "matrix": [
    [0, 156, 10],
    [108, 0, 290],
    [90, 75, 0]
  ]
}
akshaydewan commented 4 years ago

Basic implementation is complete. Closing this issue.