Skip to content

Commit e962a31

Browse files
committed
Worked out how to do additional seeds in a demographic way. Also now working on the multi-demographic population object and output
1 parent 9ecb514 commit e962a31

File tree

6 files changed

+127
-28
lines changed

6 files changed

+127
-28
lines changed

.env

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
PYTHONPATH=build/lib.macosx-10.9-x86_64-3.7:$PYTHONPATH
2+

src/metawards/_demographics.py

+37-4
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,46 @@ def add(self, demographic: Demographic):
8585
"""Add a demographic to the set to be modelled"""
8686
if demographic.name in self._names:
8787
raise ValueError(
88-
f"There is already a demographic called "
89-
f"{demographic.name} in this set. Please rename "
90-
f"and try again.")
88+
f"There is already a demographic called "
89+
f"{demographic.name} in this set. Please rename "
90+
f"and try again.")
9191

9292
self.demographics.append(demographic)
9393
self._names[demographic.name] = len(self.demographics) - 1
9494

95+
def get_name(self, item):
96+
"""Return the name of the demographic at 'item'"""
97+
return self.demographics[self.get_index(item)].name
98+
99+
def get_index(self, item):
100+
"""Return the index of the passed item"""
101+
try:
102+
item = int(item)
103+
except Exception:
104+
pass
105+
106+
if isinstance(item, str):
107+
try:
108+
return self._names[item]
109+
except Exception:
110+
pass
111+
112+
elif isinstance(item, int):
113+
try:
114+
if self.demographics[item] is not None:
115+
return item
116+
except Exception:
117+
pass
118+
119+
elif isinstance(item, Demographic):
120+
for i, d in enumerate(self.demographics):
121+
if item == d:
122+
return i
123+
124+
# haven't found the item
125+
raise KeyError(f"There is no demographic is this set that "
126+
f"matches {item}.")
127+
95128
@staticmethod
96129
def load(name: str = None,
97130
repository: str = None,
@@ -177,7 +210,7 @@ def load(name: str = None,
177210
play_ratios = data.get("play_ratios", [])
178211

179212
if (len(demographics) != len(work_ratios) or
180-
len(demographics) != len(play_ratios)):
213+
len(demographics) != len(play_ratios)):
181214
raise ValueError(
182215
f"The number of work_ratios ({len(work_ratios)}) must "
183216
f"equal to number of play_ratios "

src/metawards/_population.py

+26
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,32 @@ def __str__(self):
7575
else:
7676
return s
7777

78+
def summary(self, demographics=None):
79+
"""Return a short summary string that is suitable to be printed
80+
out during a model run
81+
82+
Returns
83+
-------
84+
summary: str
85+
The short summary string
86+
"""
87+
summary = f"S: {self.susceptibles} E: {self.latent} " \
88+
f"I: {self.total} R: {self.recovereds} " \
89+
f"IW: {self.n_inf_wards} T_POP: {self.population}"
90+
91+
if self.subpops is None or len(self.subpops) == 0:
92+
return summary
93+
94+
subs = []
95+
for i, subpop in enumerate(self.subpops):
96+
if demographics is not None:
97+
name = demographics.get_name(i)
98+
subs.append(f"name: {name} {subpop.summary()}")
99+
else:
100+
subs.append(f"index: {i} {subpop.summary()}")
101+
102+
return f"{summary}\n " + "\n ".join(subs)
103+
78104

79105
@_dataclass
80106
class Populations:

src/metawards/extractors/_output_core.pyx

+3-7
Original file line numberDiff line numberDiff line change
@@ -713,13 +713,6 @@ def output_core_serial(network: Network, population: Population,
713713
f"{S} vs {susceptibles}, {E} vs {latent}, {I} vs {total}, "
714714
f"{R} vs {recovereds}")
715715

716-
print(f"S: {susceptibles} ", end="")
717-
print(f"E: {latent} ", end="")
718-
print(f"I: {total} ", end="")
719-
print(f"R: {recovereds} ", end="")
720-
print(f"IW: {n_inf_wards[0]} ", end="")
721-
print(f"TOTAL POPULATION {susceptibles+latent+total+recovereds}")
722-
723716
if population is not None:
724717
population.susceptibles = susceptibles
725718
population.total = total
@@ -772,6 +765,7 @@ def output_core(network: _Union[Network, Networks],
772765
if isinstance(network, Network):
773766
output_func(network=network, population=population,
774767
workspace=workspace, infections=infections)
768+
print(population.summary())
775769

776770
elif isinstance(network, Networks):
777771
if population.subpops is None:
@@ -784,6 +778,8 @@ def output_core(network: _Union[Network, Networks],
784778
infections=infections.subinfs[i],
785779
**kwargs)
786780

781+
print(population.summary(demographics=network.demographics))
782+
787783
# aggregate the infection information from across
788784
# the different demographics
789785
network.aggregate(infections=infections)

src/metawards/iterators/_advance_additional.py

+43-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11

2+
from typing import Union as _Union
3+
24
from .._network import Network
5+
from .._networks import Networks
36
from .._population import Population
47
from ..utils._profiler import Profiler
58
from .._infections import Infections
@@ -25,7 +28,13 @@ def _load_additional_seeds(filename: str):
2528

2629
# yes, this is really the order of the seeds - "t num loc"
2730
# is in the file as "t loc num"
28-
seeds.append((int(words[0]), int(words[2]), int(words[1])))
31+
if len(words) == 4:
32+
seeds.append((int(words[0]), int(words[2]),
33+
int(words[1]), words[3]))
34+
else:
35+
seeds.append((int(words[0]), int(words[2]),
36+
int(words[1]), None))
37+
2938
print(seeds[-1])
3039
line = FILE.readline()
3140

@@ -40,7 +49,7 @@ def _load_additional_seeds(filename: str):
4049
_additional_seeds = None
4150

4251

43-
def setup_additional_seeds(network: Network,
52+
def setup_additional_seeds(network: _Union[Network, Networks],
4453
profiler: Profiler,
4554
**kwargs):
4655
"""Setup function that reads in the additional seeds held
@@ -51,7 +60,7 @@ def setup_additional_seeds(network: Network,
5160
5261
Parameters
5362
----------
54-
network: Network
63+
network: Network or Networks
5564
The network to be seeded
5665
profiler: Profiler
5766
Profiler used to profile this function
@@ -70,7 +79,7 @@ def setup_additional_seeds(network: Network,
7079
p = p.stop()
7180

7281

73-
def advance_additional_serial(network: Network,
82+
def advance_additional_serial(network: _Union[Network, Networks],
7483
population: Population,
7584
infections: Infections,
7685
profiler: Profiler,
@@ -80,7 +89,7 @@ def advance_additional_serial(network: Network,
8089
8190
Parameters
8291
----------
83-
network: Network
92+
network: Network or Networks
8493
The network being modelled
8594
population: Population
8695
The population experiencing the outbreak - also contains the day
@@ -93,24 +102,44 @@ def advance_additional_serial(network: Network,
93102
Arguments that aren't used by this advancer
94103
"""
95104

96-
wards = network.nodes
97-
98-
play_infections = infections.play
99-
infections = infections.work
100-
101105
# The 'setup_additional_seeds' function should have loaded
102106
# all additional seeds into this global '_additional_seeds' variable
103107
global _additional_seeds
104108

105109
p = profiler.start("additional_seeds")
106110
for seed in _additional_seeds:
107111
if seed[0] == population.day:
108-
if wards.play_suscept[seed[1]] < seed[2]:
112+
ward = seed[1]
113+
num = seed[2]
114+
115+
if isinstance(network, Networks):
116+
demographic = seed[3]
117+
118+
if demographic is None:
119+
# have to choose a demographic randomly - choose based
120+
# on the relative populations of the demographics
121+
demographic = 0
122+
else:
123+
demographic = network.demographics.get_index(demographic)
124+
125+
wards = network.subnets[demographic]
126+
play_infections = infections.subinfs[demographic].play
127+
else:
128+
demographic = None
129+
wards = network.wards
130+
play_infections = infections.play
131+
132+
if wards.play_suscept[ward] < num:
109133
print(f"Not enough susceptibles in ward for seeding")
110134
else:
111-
wards.play_suscept[seed[1]] -= seed[2]
112-
print(f"seeding play_infections[0][{seed[1]}] += {seed[2]}")
113-
play_infections[0][seed[1]] += seed[2]
135+
wards.play_suscept[ward] -= num
136+
if demographic is not None:
137+
print(f"seeding demographic {demographic} "
138+
f"play_infections[0][{ward}] += {num}")
139+
else:
140+
print(f"seeding play_infections[0][{ward}] += {num}")
141+
142+
play_infections[0][ward] += num
114143
p.stop()
115144

116145

src/metawards/iterators/_setup_imports.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ def setup_seed_specified_ward(network: _Union[Network, Networks],
2929
"""
3030
if isinstance(network, Networks):
3131
# only seed the overall ward
32-
network = network.overall
32+
raise NotImplementedError(
33+
"Seeding a specific ward for a multi-demographic Network is "
34+
"not yet supported. If you need this please raise an issue "
35+
"at https://github.com/metawards/MetaWards/issues and we will "
36+
"work as quickly as we can to implement it.")
3337

3438
wards = network.nodes
3539
links = network.links
@@ -66,12 +70,21 @@ def setup_seed_all_wards(network: _Union[Network, Networks],
6670
in params.daily_imports
6771
"""
6872
if isinstance(network, Networks):
69-
# only seed the overall network
70-
network = network.overall
73+
if network.overall.params.daily_imports == 0:
74+
return
75+
76+
raise NotImplementedError(
77+
"Daily seeding of a multi-demographic network is not yet "
78+
"supported. If you need this, please raise an issue on "
79+
"https://github.com/metawards/MetaWards/issues and we will "
80+
"work as quickly as we can to implement it.")
7181

7282
wards = network.nodes
7383
params = network.params
7484

85+
if params.daily_imports == 0:
86+
return
87+
7588
play_infections = infections.play
7689
infections = infections.work
7790

0 commit comments

Comments
 (0)