# Modelling population movements¶

We can use `go_ward()` to model population movements. There are many mass population movements that occur through the year, e.g. people travelling to holiday destinations in the summer, people travelling home to family at Christmas, and students going to university in the autumn. Each of these have the potential to spread a local disease outbreak and seed it in many new locations.

## Ward 0, the null ward¶

To perform this move, we first need to gather together all of the students, into a single “scratch” or “null” ward, from where they can then be dispersed to the various university wards. We want to do this because it is much more efficient than trying to sample the double loop of from each ward to each university ward.

To help with this, `metawards` provides a single null (or scratch) ward, which is not used in any calculation. This is ward 0. It is at index 0 in the internal arrays used by `metawards`. You can use this ward as a temporary space to hold individuals during the day. However, you must ensure that there are no individuals left in this ward at the end of the day, when `output_core()` is called.

## Moving to university¶

We will model the first day of university, when (in this ideal model), all students will travel from their home ward to live in their university ward. Students will be modelled as workers in the university ward, who commute to the university ward each day.

Create a mover called `move_university.py` and copy in the below;

```from metawards import WardID
from metawards.movers import MoveGenerator, MoveRecord, go_ward
from metawards.utils import Console, ran_int

def move_university(population, **kwargs):

def go_null(network, **kwargs):
record = MoveRecord()
gen = MoveGenerator(to_ward=WardID(0,0), fraction=0.02)

Console.print("Moving students to the null ward...")
go_ward(generator=gen, network=network, record=record, **kwargs)

nstudents = 0

for r in record:
nstudents += r[-1]

Console.print(f"Number of students: {nstudents}")

Console.print(f"Number of infected students: {int(ninfected)}")

def go_university(network, rngs, **kwargs):
# get the first random number generator from the
rng = rngs[0]

# how many wards are there?
nwards = network.nnodes

# generate a list of 50 random wards that
# represent 50 university towns
uni_wards = [ran_int(rng, 1, nwards) for _ in range(0, 50)]

for uni_ward in uni_wards[0:-1]:
nstudents = 0

record = MoveRecord()
gen = MoveGenerator(from_ward=WardID(0,0),
to_ward=WardID(uni_ward, uni_ward),
fraction=0.075)
go_ward(generator=gen, network=network,
rngs=rngs, record=record, **kwargs)

for r in record:
nstudents += r[-1]

Console.print(f"{nstudents} went to university ward {uni_ward}")

# move the remainder of the students to the last university
record = MoveRecord()
gen = MoveGenerator(from_ward=WardID(0,0),
to_ward=WardID(uni_wards[-1], uni_wards[-1]))

go_ward(generator=gen, network=network,
rngs=rngs, record=record, **kwargs)

nstudents = 0

for r in record:
nstudents += r[-1]

Console.print(f"{nstudents} went to university ward {uni_wards[-1]}")

if population.day == 20:
return [go_null, go_university]
else:
return []
```

There is a lot to discuss with this code, so we will explore each bit separately.

This code defines a mover (`move_university`), which, on the 20th day of the outbreak, returns two go functions (`go_null` and `go_university`). This is controlled by the code at the end of the function;

```if population.day == 20:
return [go_null, go_university]
else:
return []
```

## go_null¶

This is the `go_null` function again;

```def go_null(network, **kwargs):
record = MoveRecord()
gen = MoveGenerator(to_ward=WardID(0,0), fraction=0.02)

Console.print("Moving students to the null ward...")
go_ward(generator=gen, network=network, record=record, **kwargs)

nstudents = 0

for r in record:
nstudents += r[-1]

Console.print(f"Number of students: {nstudents}")

Console.print(f"Number of infected students: {int(ninfected)}")
```

This creates a `MoveGenerator` that moves 2% of individuals from every ward and disease stage to become workers in the null ward.

Note

Workers in the null ward have a `WardID` of `WardID(0,0)`, while players have `WardID(0)`, or, simply `0`.

We use a `MoveRecord` to record the moves, and count up the total number of individuals moved, which is the total number of students.

The total number of workers in a ward is held in `network.links.weight`. The current number of susceptible workers in a ward is held in `network.links.suscept`. The null ward is at index 0, and so;

```ninfected = network.links.weight[0] - network.links.suscept[0]
```

gives the number of infected workers in the null ward.

Note

Similarly, `network.nodes.save_play_suscept` is the number of players in a ward, while `network.nodes.play_suscept` is the number of susceptible players in a ward. The difference between these is the number of infected individuals in a ward.

## go_university¶

This is the `go_university` function again;

```def go_university(network, rngs, **kwargs):
# get the first random number generator from the
rng = rngs[0]

# how many wards are there?
nwards = network.nnodes

# generate a list of 50 random wards that
# represent 50 university towns
uni_wards = [ran_int(rng, 1, nwards) for _ in range(0, 50)]

for uni_ward in uni_wards[0:-1]:
nstudents = 0

record = MoveRecord()
gen = MoveGenerator(from_ward=WardID(0,0), to_ward=uni_ward,
fraction=0.075)
go_ward(generator=gen, network=network,
rngs=rngs, record=record, **kwargs)

for r in record:
nstudents += r[-1]

Console.print(f"{nstudents} went to university ward {uni_ward}")

# move the remainder of the students to the last university
record = MoveRecord()
gen = MoveGenerator(from_ward=WardID(0,0), to_ward=uni_wards[-1])

go_ward(generator=gen, network=network,
rngs=rngs, record=record, **kwargs)

nstudents = 0

for r in record:
nstudents += r[-1]

Console.print(f"{nstudents} went to university ward {uni_wards[-1]}")
```

Because this is an illustrative model, we are not using any real world data. As such, we first need to randomly generate the IDs of 50 wards that will be designated as the locations of the 50 universities in this model.

To do this, we use `rngs`, which is a list of random number generators, one for each running thread of `metawards`. As this is a single-threaded function, we will take the first random number generator, and assign that to the variable `rng`, via,

```rng = rngs[0]
```

The 50 random ward IDs are 50 integers randomly picked between 1 and the number of wards in the network, using the `ran_int()` function;

```# how many wards are there?
nwards = network.nnodes

# generate a list of 50 random wards that
# represent 50 university towns
uni_wards = [ran_int(rng, 1, nwards) for _ in range(0, 50)]
```

Note

Note that we haven’t checked for duplicated random ward IDs because this is just meant to be a simple, illustrative model.

Next, we loop over the first 49 of those random wards, and create a `MoveGenerator` that moves 7.5% of the students (on average) from being workers in the null ward (`WardID(0,0)`), to being workers in the university ward (`WardID(uni_ward, uni_ward)`).

```for uni_ward in uni_wards[0:-1]:
nstudents = 0

record = MoveRecord()
gen = MoveGenerator(from_ward=WardID(0,0),
to_ward=WardID(uni_ward, uni_ward),
fraction=0.075)
go_ward(generator=gen, network=network,
rngs=rngs, record=record, **kwargs)

for r in record:
nstudents += r[-1]

Console.print(f"{nstudents} went to university ward {uni_ward}")
```

Finally, we move the remaining students from the null ward to the 50th university;

```# move the remainder of the students to the last university
record = MoveRecord()
gen = MoveGenerator(from_ward=WardID(0,0), to_ward=uni_wards[-1])

go_ward(generator=gen, network=network,
rngs=rngs, record=record, **kwargs)

nstudents = 0

for r in record:
nstudents += r[-1]

Console.print(f"{nstudents} went to university ward {uni_wards[-1]}")
```

50 universities should have 2% of the students each, so why did we use 7.5%? The reason is because `fraction` is used with a random binomial distribution to sample a random number of individuals with that fraction. This underestimates the number as we draw more students. Thus the number of students sampled reduces as we loop through the 49 universities, to the point that far too many are remaining to go into the 50th university. Through trial and error, we found that 7.5% gave a reasonable number remaining for the 50th university.

## Performing the run¶

We will run this model using `lurgy5.json` developed in the last chapter. We can run our mover using;

```metawards -d lurgy5.json -m 2011Data --mover move_university.py -a ExtraSeedsLondon.dat --nsteps 40
```

We are using the 2011Data England and Wales model, and have seeded five infections in London. We’ve limited the run to 40 steps as we are only interested in the change in the outbreak caused by the movement of students to universities which, in this model, occurs on day 20.

You can plot the results using;

```metawards-plot -i output/results.csv.bz2
```

The resulting graph shows that the movement to university had no perceptible impact on the outbreak, e.g.

This is unsurprising when you look at the number of students going to university, as printed in the output. For example, I got;

```Moving students to the null ward...
Number of students: 1122060
Number of infected students: 25
84257 went to university ward 8253
77500 went to university ward 7782
72120 went to university ward 5122
66461 went to university ward 2257
61307 went to university ward 7124
56873 went to university ward 2241
53190 went to university ward 1776
49065 went to university ward 4896
44821 went to university ward 7752
42100 went to university ward 4987
38481 went to university ward 6981
35484 went to university ward 4100
32644 went to university ward 5628
30619 went to university ward 6048
28363 went to university ward 4480
26001 went to university ward 5377
24077 went to university ward 1395
22415 went to university ward 3738
20795 went to university ward 3999
19507 went to university ward 5433
17863 went to university ward 8282
16316 went to university ward 4203
15206 went to university ward 6343
13909 went to university ward 5494
12823 went to university ward 6732
12102 went to university ward 1645
11238 went to university ward 8275
10269 went to university ward 4975
9476 went to university ward 8339
8869 went to university ward 5837
7943 went to university ward 2057
7668 went to university ward 4077
6915 went to university ward 1506
6469 went to university ward 1405
5863 went to university ward 2197
5563 went to university ward 6986
5137 went to university ward 6863
4851 went to university ward 6086
4229 went to university ward 2494
3999 went to university ward 4986
3747 went to university ward 7534
3447 went to university ward 1583
3108 went to university ward 2696
2966 went to university ward 2020
2597 went to university ward 398
2473 went to university ward 2202
2329 went to university ward 5530
2089 went to university ward 8114
1978 went to university ward 3328
24538 went to university ward 2293
```

Of the 1.1M students who moved, only 25 were infected. These 25 were dispersed over 50 wards, and so had little impact on an outbreak that was already spreading rapidly due to normal movements.