Multiple Jurisdictions

With our credible model for dynamic transmission at the contact level and our ability to size environments with approriate contact distributions, we can show the true power of RKnot in modelling complex scenarios.

We have previously shown that contact restrictions and social distancing in any one community should be effective in eliminating sars-cov-2 relatively quickly. Across the world, however, many states, provinces, and countries, have experienced second and third waves spaced out months apart despite various forms of strict quarantines and lockdowns.

So why haven’t they been effective?

In part, this has resulted from the many exceptions made to these restrictions, in particular, schools remaining open in many jurisdictions.

We must also consider the impact of multiple jurisdictions that have unique characteristics, set policy independently, and, importantly, allow travel between them.

We will focus on two jurisdictions to start, building to four, and building in complexity along the way.

Mixed

To start, we will simply double the size of the environment used in our dynamic transmission risk models. Thus,

  • n = 20,000 and 4 initial infections
  • 8 groups, split evenly into two states: “East State” and “West State”
  • the states will be exact reflections of each other:
    • same size, n = 10,000
    • 2 initial infections each
    • same demographic mix
    • same number of events
  • the event structrue for each state is detailed in Sizing (and imported from a pickled object as per below).
  • each state will have its own vbox. The East State vbox must be reassigned and the events adjusted to point to the correct groups.
from copy import deepcopy

from rknot import Sim, Chart
from rknot.events import Restriction, Quarantine
from rknot.dots.fhutch import tmr
from rknot.sims import us_w_load_18.events

group1 = dict(name='W0-19', n=2700, n_inf=0, ifr=0.00003, mover=.98)
group2 = dict(name='W20-49', n=4100, n_inf=1, ifr=0.0002, mover=.98)
group3 = dict(name='W50-69', n=2300, n_inf=1, ifr=0.005, mover=.98)
group4 = dict(name='W70+', n=900, n_inf=0, ifr=0.054, mover=.98)
wstate = [group1, group2, group3, group4]

wbox = {'label': 'W Main', 'box': 344}
wevents = deepcopy(us_w_load_18.events)
wrsxns = deepcopy(us_w_load_18.rsxns)

group5 = dict(name='E0-19', n=2700, n_inf=0, ifr=0.00003, mover=.98)
group6 = dict(name='E20-49', n=4100, n_inf=1, ifr=0.0002, mover=.98)
group7 = dict(name='E50-69', n=2300, n_inf=1, ifr=0.005, mover=.98)
group8 = dict(name='E70+', n=900, n_inf=0, ifr=0.054, mover=.98)
estate =  [group5, group6, group7, group8]

ebox = {'label': 'E Main', 'box': 344}
eevents = deepcopy(us_w_load_18.events)

for e in eevents:
    e.groups = [4,5,6,7]
    e.vbox = 1

groups = wstate + estate
vboxes = [wbox, ebox]
events = wevents + eevents

params = {
    'groups': groups, 'density': 1, 'days': 365, 'tmr_curve': tmr,
    'vboxes': vboxes, 'events': events
}

sim = Sim(**params)
sim.run()

Below, we show the arrangement of all of the dots mixed throughout the space. The only difference from the single jurisdiction structure is the presence of two vboxes (instead of one).

On a proportionate basis, this structure is indistinguishable from the n=10,000, 4 group structure used in the viral load simulations. And it should result in the same outbreak curve, on average.

To test, we ran 50 sims for a quick comparison to the base Events structure that we are building from.

Mixed
$R_0$>0
Events
w Load
$R_0$>0
n, $R_0$ > 02868
n50250
Peak23.5%32.7%
HIT38.0%40.6%
Total53.3%51.5%
Fatalities0.32%0.30%
%>7079.1%6.5%
IFR0.60%0.60%
Days to Peak9574

As expected, we can see above that, on average, the spread results generated in this structure match those of the Events structure from the viral load analysis. Notice, however, that an outbreak (\(R_0\) > 0) occured much more frequently: 56% vs. 27%. This is likely a result of the greater number of initial infections (4 vs. 2).

Also notice that the average Peak is lower and Days to Peak higher in our new scenario despite all other metrics being the same.

In the distribution below, we can see that, in this scenario, more simulations result in outbreaks, but when they do, they concentrated around the same level of total infection.

_images/multi_9_0.png

We can see this in the sample simulation below:

Above, we can see a “wave” effect occuring with two peaks forming, the first around 70 days and second around 115 days. We can seperate the curve among the constituent states to see how each state’s curve might have added to the aggregate.

From above, we can see that what looked like a single curve and a single outbreak was, in fact, two outbreaks among the two “states”, West and East.

Remarkably, the dots from both states are free to mix in the main grid. The separate waves result simply from isolated event spaces where only the largest events (3+ capacity) occur within the state groups.

Next we will show the impact of further isolating the two states.


Borders

Now we split the environment into two halves, separating the subjects of the West and East states by a border.

They cannot interact in any way.

This can be accomplished by simply assign a separate box to the groups in each state, as per below:

for w in wstate:
    w['box'] = {'bounds': [1, 72, 1, 144], 'label': 'West State'}

for e in estate:
    e['box'] = {'bounds': [73, 144, 1, 144], 'label': 'East State'}

groups = wstate + estate
params['groups'] = groups

sim = Sim(**params)
sim.run(dotlog=True)

This results in the environment shown below:

We ran 50 simulations to compare with the Mixed environment:

Borders
$R_0$>0
Mixed
$R_0$>0
n, $R_0$ > 02628
n5050
Peak19.1%23.5%
HIT25.0%38.0%
Total31.5%53.3%
Fatalities0.20%0.32%
%>7078.9%79.1%
IFR0.61%0.60%
Days to Peak7895

We can see:

  • an outbreak occurs with about the same frequency; likely b/c both environments have the same number of initial infections
  • the size of the outbreaks is significantly lower in the split environment
  • the peaks are significantly lower in the split environment

Contact Mix

Importantly, doubling the number of subjects does not change the number of contacts. Instead, it changes who is contacted. And so when the environment is split and the contact mix becomes more heterogeneous, spread is curtailed.

Again, we see below that the contact distribution of the Borders environment is quite different from the Mixed environment.

_images/multi_24_0.png

Here, the same number of outbreaks occur, but the virus cannot cross the border so at worst it can only spread amongst half the population. As a result, we see two local peaks form, representing when an outbreak occurs in only one state or in both.

In a uniform environment, there are only two outcomes:

  1. Peak
  2. No Peak

When we split the space, there are now 4 distinct possible outcomes:

  1. No Peaks
  2. One Peak: West
  3. One Peak: East
  4. Two Peaks

We can even get a quick sense for how often these different outcomes might occur, as per below.

No
Peak
West
Peak
East
Peak
Double
Peak
Number of
Occurences
2411105

As per the piechart above,only 10% of outbreaks resulted in a Double Peak, while more than 40% resulted in an outbreak in either state. Both states were equally likely to have an outbreak on their own.

Below we see a representative simulation that evidences the border separating the two states. We can also see in the grid on the left that the outbreak was entirely isolated to the West state and did not seep into the East state at all. This resulted in the shorter, more muted peak.

Finally, we also show a typical Double Peak scenario:

Here we see both states develop outbreaks independently and simultaneously, both with very similar spread curves, peaking just after 60 days and reaching ~17% peaks. The aggregate curve is inline with expectations for a well-mixed environment.


Border Crossings

Now that we have controlled borders between isolated states, we can explore the impact of border crossings on spread.

To do so, we will create Travel events allowing subjects from each state to cross over into the other state, as follows:

  • total of 20 crossings per day
    • 10 crossings per day West to East
    • 10 crossings per day East to West
  • each group will have equal likelihood to participate in the crossings

The crossing amount was roughly determined based on international travel statistics out of the US:

\[\begin{split}\\ \frac{\text{100MM visitors annually}}{\text{330MM actual population}} * \frac{\text{10,000 pop}}{\text{state}} = \frac{\text{3,031 sim visitors annually}}{\text{state}} \\\; \\\frac{\text{3,031 sim visitors}}{\text{365 days}} = \textit{9 visitors } \text{per day per state}\end{split}\]

To do this, we will create 8 random locations for each event (within each state’s box), then assign them to our 8 travel events.

from rknot.events import Travel

xs = np.random.randint(19, 120, size=4)
ys = np.random.randint(20, 120, size=4)
w_locs = np.vstack((xs, ys)).T

xs = np.random.randint(74, 120, size=4)
ys = np.random.randint(10, 120, size=4)
e_locs = np.vstack((xs, ys)).T

crosses = []
for i in range(9):
    crosses += [Travel(name=f'WtoE_{i}', xy=w_locs[i], start_tick=1,
        groups=[0,1,2,3], capacity=1, duration=1, recurring=1
    )]
    crosses += [Travel(
        name=f'EtoW_{i}', xy=e_locs[i], start_tick=1,
        groups=[4,5,6,7], capacity=1, duration=1, recurring=1
    )]
params['events'] += crosses

The results of 50 simulations are shown below:

Border
Crossings
$R_0$>0
Borders
$R_0$>0
n, $R_0$ > 01726
n5050
Peak17.6%19.1%
HIT23.5%25.0%
Total32.7%31.5%
Fatalities0.19%0.20%
%>7080.0%78.9%
IFR0.55%0.61%
Days to Peak7878
_images/multi_37_0.png

We see only a limited impact from border travel as constructed. The structure actually resulted in fewer outbreaks and resulted in similar single and double peak occurences.

No
Peak
West
Peak
East
Peak
Double
Peak
Number of
Occurences
32675

Below we show a typical Double Peak outcome. The outbreak is almost entirely extinguished and in decline in the East state, but then spread takes hold independently in the West State and creates a second peak.


Crossings with Event Access

The above simulation has major omission. Each traveller only visits the other state for a single tick, only ever land on a single location in the other state’s main grid, and so have almost no contact with anyone while travelling.

In reality, travellers often have extended stays of 3/5/7/15/30+ days and, while in the other jurisidiction, they often attend events / have contacts in that jurisdiction like any other local person.

We can mimick this more realistic mixing approach by utilizing the MultiTravel event object. This object also a travel event greater than a single tick and allows any participants to attend events in their new region.

We must also implementing a few changes to the existing events. Each event will now pull participants from its own main grid as well as any box created to support cross border travel into the state.

We assume:

  • 9 visits per day per state, consistent with the prior scenario
  • Respective durations of the 9 visits:
    • 4 visits are for 3 days
    • 3 visits are for 5 days
    • 1 visits is for 7 days
    • 1 visit is for 11 days

The code looks like this:

from rknot.events import MultiTravel

for e in params['events']:
    e.groups = np.arange(8)
    if 'W' in e.name:
        e.from_boxes = ['West State', 'EtoW']
        e.no_events = False
    if 'E' in e.name:
        e.from_boxes = ['East State', 'WtoE']
        e.no_events = False

With the existing events restructured, we assign each event to locations in their box at random.

n_cross = 9
xs = np.random.randint(19, 120, size=n_cross)
ys = np.random.randint(20, 120, size=n_cross)
w_locs = np.vstack((xs, ys)).T

xs = np.random.randint(74, 120, size=n_cross)
ys = np.random.randint(10, 120, size=n_cross)
e_locs = np.vstack((xs, ys)).T

crosses = []
for i, dur in zip(range(n_cross), (3,3,3,3,5,5,5,7,11)):
    crosses += [MultiTravel(name=f'WtoE_{dur}', xy=e_locs[i], start_tick=1,
        groups=[0,1,2,3], capacity=1, recurring=1, duration=dur
    )]
    crosses += [MultiTravel(
        name=f'EtoW_{dur}', xy=w_locs[i], start_tick=1,
        groups=[4,5,6,7], capacity=1, recurring=1, duration=dur
    )]

params['events'] += crosses

50 iterations of the above scenario lead to the following:

Crossings
w Events
$R_0$>0
Mixed
$R_0$>0
Crossings
$R_0$>0
n, $R_0$ > 0212817
n525050
Peak23.4%23.5%17.6%
HIT35.6%38.0%23.5%
Total51.8%53.3%32.7%
Fatalities0.33%0.32%0.19%
%>7075.8%79.1%80.0%
IFR0.64%0.60%0.55%
Days to Peak869578
_images/multi_47_0.png

Incredibly, we see that just 10 border crossings per day with travellers mixing normally leads to (almost) the same the results as though there were no border at all.

We can also see that the peak distribution has inverted almost completely. Now a Double Peak event is far more likely than a single peak in either state.

No
Peak
Single
Peak
Double
Peak
Number of
Occurences
31120

We can use some additional tools to confirm that mixing between the states was in fact the culprit in increased prevalence of double peaks. The easiest way is to find simulations where a state reaches zero infections, effectively eradicating the virus in prior scenarios, only to have the virus re-emerge.

This can ONLY occur if the virus was transported in from the other state.


{0: 'East', 17: 'East', 21: 'West', 43: 'West', 45: 'East'}
The above tool tells us that in *5* simulations a state inherited an infection *after* it had already eradicated the virus.

It also tells us the index position of those states in our grouping of simulations. We will isolate simulation # 21, where West State inherited an outbreak.

We can isolate the specific subject that carried the virus.

[26]:
sim = xesims[21]
id_groups = [westids, eastids]
west_inf, east_inf = find_group_infs(sim, id_groups, westids, eastids)
westtrim = np.trim_zeros(west_inf, 'fb')

Above is the number of current infections in the West State at each tick during the simulation. We can clearly see an entire 14-day period during that simulation where the West State had zero infections.

Then, on Tick 44, a single new infection emerges. We can easily isolate which subject was infected.

[27]:
from rknot.dots import MATRIX_COL_LABELS as ML
erads = np.argwhere(westtrim == 0)
i_erad = erads[-1, 0]

dots = sim.dotlog[i_erad + 1]
westmask = np.isin(dots[:, ML['group_id']], westids)
west = dots[westmask]
west[west[:, ML['is_inf']] == 1]
[27]:
array([[  5210,      1,      1,      0,      0,      1,      1,  15569,
           109,     18,  15569,    109,     18,      1,      2,      0,
        224729,      5,     98,    100,     20,     44,     -1,     75,
           441,      1,      1]], dtype=int32)

The array above shows the state of subject 5210 as of Tick 44. We can expand the dot matrix to ticks both before and after to get a sense of this subjects movement.

[28]:
id_new = west[west[:, ML['is_inf']] == 1][0,0]
dottrace = sim.dotlog[i_erad - 4 : i_erad + 4, id_new]

We can take the array above and put it in a table for easier viewing:

[29]:
ticks = np.arange(i_erad - 4, i_erad + 4)
dtrace = np.zeros(shape=(8,28), dtype=np.int32)
dtrace[:, 0] = ticks
dtrace[:, 1:] = dottrace
tickidgroup_idis_infxyhomexhomeyevent_id
40521010181385410535205
415210105410554105-1
425210105410554105-1
435210105410510918224729
4452101012813910918214892
455210111091810918224729
465210115410554105-1
475210115410554105-1

From the table above we can see the following:

  • Subject 5210 was inside the West State box and not infected in the days prior to Tick 44.
  • On Tick 42, the subject travelled to East State via 3-day MultiTravel event.
  • On Tick 43, the subject attended an event at location (128, 139), where it was infected.
  • On Tick 45, the subject returned to West State and is at least partly responsible for the re-emergence of the virus in that state.

We can even confirm this visually by isolating that specific dot in the animation by passing highlight=[5210] at the Chart instantiation. We also slowed down the frame rate. You can see at Tick 44, the subject jumps from West to East and back very quickly.

chart = Chart(sim, highlight=[5210], show_intro=True, show_restricted=True)
chart.to_html5_video()

Finally above, we show the full outcome of the sim, which pesents almost as a single prolonged peak.


Care Homes

With a few modifications, we can incorporate features of the Care Home to further increase realism.

Again, we simply double the number of groups, double the number of events, and assign a set of cross border events. In this sim, the 70+G group in each state is restricted from cross border travel.

import numpy as np
from copy import deepcopy

from rknot import Sim, Chart
from rknot.events import Restriction, Quarantine, Travel, MultiTravel
from rknot.dots.fhutch import tmr
from rknot.sims import us_w_load_18

group1 = dict(name='W0-19', n=2700, n_inf=0, ifr=0.00003, mover=0.982)
group2a = dict(name='W20-49', n=4034, n_inf=1, ifr=0.0002, mover=0.982)
group2b = dict(name='WCHW', n=66, n_inf=0, ifr=0.0002, mover=0.982)
group3 = dict(name='W50-69', n=2300, n_inf=1, ifr=0.005, mover=0.982)
group4a = dict(name='W70+', n=600, n_inf=0, ifr=0.042, mover=0.982)
group4b = dict(name='W70+G', n=300, n_inf=0, ifr=0.0683, mover='local')

wstate = [group1, group2a, group2b, group3, group4a, group4b]

group5 = dict(name='E0-19', n=2700, n_inf=0, ifr=0.00003, mover=0.982)
group6a = dict(name='E20-49', n=4034, n_inf=1, ifr=0.0002, mover=0.982)
group6b = dict(name='ECHW', n=66, n_inf=0, ifr=0.0002, mover=0.982)
group7 = dict(name='E50-69', n=2300, n_inf=1, ifr=0.005, mover=0.982)
group8a = dict(name='E70+', n=600, n_inf=0, ifr=0.042, mover=0.982)
group8b = dict(name='E70+G', n=300, n_inf=0, ifr=0.0683, mover='local')
estate =  [group5, group6a, group6b, group7, group8a, group8b]

for w in wstate:
    w['box'] = {'bounds': [1, 72, 1, 144], 'label': 'West State'}
group4b['box'] = [1,6,1,6]

for e in estate:
    e['box'] = {'bounds': [73, 144, 1, 144], 'label': 'East State'}
group8b['box'] = [139,144,1,6]

groups = wstate + estate

wbox = {'label': 'W Main', 'box': 344}
ebox = {'label': 'E Main', 'box': 344}

wevents = deepcopy(us_w_load_18.events_gated)
eevents = deepcopy(us_w_load_18.events_gated)

# Care Home Events
i_ch = -34
for e in wevents[i_ch:]:
    w.name = f'W_{w.name}'

for e in eevents[i_ch:]:
    e.name = f'E_{e.name}'

# Main Events
for w in wevents[:i_ch]:
    w.name = f'W{e.name}'
    w.from_boxes = ['West State', 'EtoW']
    e.groups = np.arange(8)
    e.no_events = False

for e in eevents[:i_ch]:
    e.name = f'W{e.name}'
    e.from_boxes = ['East State', 'WtoE']
    e.vbox = 1
    e.groups = np.arange(8)
    e.no_events = False

groups = wstate + estate
vboxes = [wbox, ebox]
events = wevents + eevents

# Add border crossings
xs = np.random.randint(19, 120, size=4)
ys = np.random.randint(20, 120, size=4)
w_locs = np.vstack((xs, ys)).T

xs = np.random.randint(74, 120, size=4)
ys = np.random.randint(10, 120, size=4)
e_locs = np.vstack((xs, ys)).T

crosses = []
for i, rec in zip(range(4), (3,3,5,5)):
    crosses += [MultiTravel(name=f'WtoE_{i}', xy=e_locs[i], start_tick=1,
        groups=[0,1,2,3,4], capacity=1, recurring=rec
    )]
    crosses += [MultiTravel(
        name=f'EtoW_{i}', xy=w_locs[i], start_tick=1,
        groups=[6,7,8,9,10], capacity=1, recurring=rec
    )]

events_ch += crosses
rsxns = []
quars = []

params_ch = {
    'groups': groups, 'density': 1, 'days': 365,
    'tmr_curve': tmr, 'vboxes': vboxes,
    'events': events_ch, 'rsxns': rsxns, 'quars': quars,
}
sim = Sim(**params_ch)
sim.run(dotlog=True)

The results of 60 simulation show outcomes very similar to the Crossing scenario.

Care Homes
$R_0$ > 0
Crossings
$R_0$ > 0
n, $R_0$ > 02921
n6052
Peak26.1%23.4%
HIT37.6%35.6%
Total55.3%51.8%
Fatalities0.40%0.33%
%>7084.6%75.8%
IFR0.73%0.64%
Days to Peak8486
_images/multi_71_0.png

We can also per below that in these 60 simulations, not a single outbreak was isolated to only one state.

No
Peak
Single
Peak
Double
Peak
Number of
Occurences
30129

Finally, we show a sample simulation.


Care Homes - 10Max

From the Care Home environment, we will begin to investigate the impact of policy restrictions in the two jurisdictions on spread. The restrictions are derived from those used previously in the SIR and Dynamic Transmission.

In our first scenario, both West State and East State will simultaneously impose a restriction as follows:

  • no events with more than 10 subjects
  • commences on Day 30
  • lasting 120 days
  • restriction covers all groups in the respective state
  • there is 90% adherence to the restriction

Reminder that there are 10 subjects per day travelling between states.

From our prior implementation, we simply add the following code to the rsxns keyword of our parameters.

wcap = Restriction(
    name='large', start_tick=30, duration=120,
    criteria={'capacity': 10, 'groups': [0,1,2,3,4,5]}, adherence=.9
)
ecap = Restriction(
    name='large', start_tick=30, duration=120,
    criteria={'capacity': 10, 'groups': [6,7,8,9,10,11]}, adherence=.9
)
params_ch['rsxns'] = [wcap, ecap]

sim = Sim(**params_ch)
sim.run(dotlog=True)

The results of 48 simulations are shown below:

Care Homes
10Max
$R_0$ > 0
Care Homes
$R_0$ > 0
n, $R_0$ > 01629
n4860
Peak4.9%26.1%
HIT5.4%37.6%
Total5.5%55.3%
Fatalities0.20%0.40%
%>7093.2%84.6%
IFR3.60%0.73%
Days to Peak5284
_images/multi_81_0.png

We can see from the above that a 10 max daily contact restriction remains highly effective at suppressing spread and eradicating the virus, this despite no restrictions on cross-border travel.

This is because, even if subjects travel cross border, their ability to mix with the population in the visiting state is severly limited.

We can see in the peak distribution below that isolated outbreaks in either state re-emerge somewhat, however, the level of total infections in all scenarios is substantially lower.

No
Peak
Single
Peak
Double
Peak
Number of
Occurences
27615

Below we show a sample simulation of a single peak outbreak. Notice that spread is materially curtailed and that fatalities are isolated almost exclusively to the 70+G group in the care homes.


Care Homes - 25Max

We will augment the prior scenario only slightly, replacing the 10 max capacity restriction with a 25 max capacity restrction in each state and tuning the adherence down to 75%.

The code is as follows:

```python wcap = Restriction( name=’large’, start_tick=30, duration=120, criteria={‘capacity’: 25, ‘groups’: [0,1,2,3,4,5]}, adherence=.75 ) ecap = Restriction( name=’large’, start_tick=30, duration=120, criteria={‘capacity’: 25, ‘groups’: [6,7,8,9,10,11]}, adherence=.75 ) params_ch[‘rsxns’] = [wcap, ecap]

sim = Sim(**params_ch) sim.run(dotlog=True) ```

The results of 50 simulations are below:

Care Homes
25Max
$R_0$ > 0
Care Homes
10Max
$R_0$ > 0
n, $R_0$ > 02216
n5048
Peak8.1%4.9%
HIT9.6%5.4%
Total11.3%5.5%
Fatalities0.21%0.20%
%>7089.9%93.2%
IFR2.11%3.60%
Days to Peak6752
_images/multi_91_0.png

As expected, and inline with the same restriction in previous environments, spread impact is substantial, though less than a 10max restrction.

Also, note in the distribution above that there was a single simulation with total infections of ~58%. So, despite the best efforts of the populace under this policy, the virus was able to survive the lockdown and being spread unabated thereafter.

We can see below that peaks in both regions was the prevailing outcome when there was spread, although isolated spread did occur.

Our sample simulations will first focus on that single outcome where total infections reached 59%.

We can see in the spread curve that both regions had infections essentially driven down to zero during the restriction period. Even as restrictions are lifted, spread remains very limited, only begins to rip higher in both states almost simultaeously at around 210 days. This is a full 60 days after the restrictions were lifted.

We can see just how close the virus was to be eradicated in the sim logs.

Scipy can help us quickly find local minimums in the curr_inf log via argrelextrema.

[50]:
from scipy.signal import argrelextrema

sim = ch25sims[6]
mins, = argrelextrema(sim.log['curr_inf'], np.less)

lowest = sim.log['curr_inf'][mins].min()
i_lowest = np.argwhere(sim.log["curr_inf"] == 44).ravel()[0]
print (f'Low: {lowest}')
print (f'Day of Low: {i_lowest}')
Low: 44.0
Day of Low: 203
So we know the lowest number of infections reached during the midpoint of the outbreak was 44 on Day 203. We can prove this by isolating them in the dotlog.
[52]:
from rknot.dots import MATRIX_COL_LABELS as ML
infmask = sim.dotlog[i_lowest][:, ML['is_inf']] == 1
infdots = sim.dotlog[i_lowest][infmask]
print (infdots.shape)
(44, 27)

And we can show that all infected subjects were in the West State:

[53]:
np.all(infdots[:, ML['group_id']] <= 5)
[53]:
True

Finally, below we show the more typical outcome from this restriction, which is a quick run up in infections prior to and just after restrictions are put in place. Then, as the restrictions take hold, spread deflates and is eliminated very quickly.


Independent Policy

Now, we will explore some more complex structures.

In the dynamic transmission model, we have seen policy restrictions can be successful at supressing spread and ultimately eradicating the virus (see here and here). In fact, these policy measures have proven fairly resilient even in the face of low adherence.

But what happens when there are is mixing between multiple jurisdictions that take independent approaches to policy?

We will build from the Care Homes scenario above.

In this sim, West State will be the more aggressive actor, implementing faster and more imposing policy. The East State will act more slowly and with less imposition. Accordingly, the resrictions will be:

West State:

  • Maximum 10 contacts, from Day 30 to Day 60
  • Maximum 25 contacts, from Day 60 to Day 105
  • Maximum 75 contacts, from Day 105 to Day 150

*This was a common approach for many of the northern US states and Canadian provinces in the Spring of 2020.*

East State:

  • Maximum 25 contacts, from Day 75 to Day 105
  • Maximum 75 contacts, from Day 105 to Day 135

Under isolated circumstances, the West State would most often experience no outbreak and at worst only a limited one. When bordering with a less rigourous neighbor, the results prove to be very different.

Below is the code adding these restrictions.

wcap1 = Restriction(
    name='wcap1', start_tick=30, duration=30,
    criteria={'capacity': 10, 'from_boxes': ['West State', 'EtoW']}, adherence=.9
)
wcap2 = Restriction(
    name='wcap2', start_tick=30, duration=45,
    criteria={'capacity': 25, 'from_boxes': ['West State', 'EtoW']}, adherence=.75
)
wcap3 = Restriction(
    name='wcap3', start_tick=75, duration=45,
    criteria={'capacity': 75, 'from_boxes': ['West State', 'EtoW']}, adherence=.75
)


ecap1 = Restriction(
    name='ecap1', start_tick=75, duration=30,
    criteria={'capacity': 25, 'from_boxes': ['East State', 'WtoE']}, adherence=.5
)
ecap2 = Restriction(
    name='ecap2', start_tick=105, duration=30,
    criteria={'capacity': 75, 'from_boxes': ['East State', 'WtoE']}, adherence=.5
)

params['rsxns'] = [wcap1, wcap2, wcap3, ecap1, ecap2]
sim = Sim(**params)
sim.run(dotlog=True)

The results of 50 simulations are shown below:

Care Homes
Uncoordinated
$R_0$ > 0
Care Homes
25Max
$R_0$ > 0
Care Homes
$R_0$ > 0
n, $R_0$ > 0222229
n505060
Peak16.1%8.1%26.1%
HIT25.9%9.6%37.6%
Total37.7%11.3%55.3%
Fatalities0.32%0.21%0.40%
%>7085.2%89.9%84.6%
IFR1.02%2.11%0.73%
Days to Peak946784
_images/multi_114_0.png

Spread comes back with a vengenance in this scenario, showing very limited impact from the implementations, although the time to peak does increase for reasons we’ll see below.

In the histrogram above, note that two local maxima have once again formed, suggesting outcomes with both single peaks and double peaks, including a maxima around ~55% total infections, which is consistent with full outbreaks across both states (despite the strict implementations int eh West!).

We can the prevalence of double peaks below.

No
Peak
Single
Peak
Double
Peak
Number of
Occurences
27221

We can also so the composition of spread amongst the two regions in the bar chart below:

_images/multi_119_0.png

Of the 22 simulations where spread was greater than 1%, the West State accounted for about half of all infections about half the time. So, where its implementations in isolation would be expected to reduce the risk of an outbreak to only 1%, here its risk remains 25%.

Below we show an example of such a double peak.

Spread begins in the East and is eventually carried into the West. The East State’s policies have essentially zero impact on spread, as the peak appears to occur before its 25 max restriction is implemented.

We can see a very modest peak in the West early in the sim and, essentially on queue with the loosening of restrictions, around Day 110 spread takes hold and rips through the West State.

We could map this onto a real world scenario where:

  • A new virus is discovered. Both states are unsure how widespread it may be.
  • West State locks down as a preventative measure, driven by a focus on public health. East State does not, driven by a focus on personal freedoms / responsibility / etc.
  • West State derides East State for its lack of action.
  • West State, believeing its outbreak is totally under control and seeing the outbreak on the downside, beings to reopen.
  • Because the virus is not fully eradicated, West State inherits the virus from East State and, because West State does not have herd immunity, the virus spreads unabated.
  • West State’s policy actions are for not.

Finally, we also show the example of a single peak where the West avoids the 2nd wave of spread.