Describe the bug
Bugs in SEIRct, SEISct, SWIR, HK, WHK, Profile and ProfileThreshold models
To Reproduce
Steps to reproduce the behavior:
- NDlib version: 5.1.1
- Operating System: Any
- Python version: Any
- Version(s) of NDlib required libraries: Any
Expected behavior
-
In the file ndlib/models/epidemics/SEIR_ct_Model.py,
the function iteration update the self.progress_I of all Inf nodes as 0:
# start from line 45
def iteration(self, node_status=True):
self.clean_initial_status(self.available_statuses.values())
actual_status = {
node: nstatus for node, nstatus in future.utils.iteritems(self.status)
}
self.progress_I = {
node: 0 for node in actual_status if actual_status[node] == 1
}
if self.actual_iteration == 0:
self.actual_iteration += 1
delta, node_count, status_delta = self.status_delta(actual_status)
# ...
I think self.progress_I only can be initiated when self.actual_iteration == 0
def iteration(self, node_status=True):
self.clean_initial_status(self.available_statuses.values())
actual_status = {
node: nstatus for node, nstatus in future.utils.iteritems(self.status)
}
if self.actual_iteration == 0:
self.progress_I = {
node: 0 for node in actual_status if actual_status[node] == 1
}
self.actual_iteration += 1
delta, node_count, status_delta = self.status_delta(actual_status)
# ...
-
The first problem of ndlib/models/epidemics/SEIS_ct_Model.py is the same as above.
The second problem is that self.progress of new Exposed nodes always be 0.
def iteration(self, node_status=True):
# ...
# start form line 99
if self.params["model"]["tp_rate"] == 1:
if eventp < 1 - (1 - self.params["model"]["beta"]) ** len(
infected_neighbors
):
actual_status[u] = 2 # Exposed
self.progress[u] = 0
else:
if eventp < self.params["model"]["beta"] * triggered:
actual_status[u] = 2 # Exposed
self.progress[u] = 0
I think self.progress of new Exposed nodes should be updated by self.actual_iteration:
def iteration(self, node_status=True):
# ...
# start form line 99
if self.params["model"]["tp_rate"] == 1:
if eventp < 1 - (1 - self.params["model"]["beta"]) ** len(
infected_neighbors
):
actual_status[u] = 2 # Exposed
self.progress[u] = self.actual_iteration
else:
if eventp < self.params["model"]["beta"] * triggered:
actual_status[u] = 2 # Exposed
self.progress[u] = self.actual_iteration
-
In the file ndlib/models/epidemics/SWIRModel.py, the random value eventp is not updated in time
def iteration(self, node_status=True):
# ...
# start from line 65
for u in self.graph.nodes:
u_status = self.status[u]
eventp = np.random.random_sample()
neighbors = self.graph.neighbors(u)
if self.graph.directed:
neighbors = self.graph.predecessors(u)
if u_status == 1: # Infected
for neighbor in neighbors:
if self.status[neighbor] == 0: # Susceptible
if eventp < self.params["model"]["kappa"]:
actual_status[neighbor] = 1 # Infected
else:
eventp = np.random.random_sample()
if eventp < self.params["model"]["mu"]:
actual_status[neighbor] = 2 # Weakened
elif self.status[neighbor] == 2: # Weakened
if eventp < self.params["model"]["nu"]:
actual_status[neighbor] = 1 # Infected
actual_status[u] = 3 # Removed
eventp should be updated for each judgment
def iteration(self, node_status=True):
# ...
for u in self.graph.nodes:
u_status = self.status[u]
neighbors = self.graph.neighbors(u)
if self.graph.directed:
neighbors = self.graph.predecessors(u)
if u_status == 1: # Infected
for neighbor in neighbors:
if self.status[neighbor] == 0: # Susceptible
eventp = np.random.random_sample()
if eventp < self.params["model"]["kappa"]:
actual_status[neighbor] = 1 # Infected
else:
eventp = np.random.random_sample()
if eventp < self.params["model"]["mu"]:
actual_status[neighbor] = 2 # Weakened
elif self.status[neighbor] == 2: # Weakened
eventp = np.random.random_sample()
if eventp < self.params["model"]["nu"]:
actual_status[neighbor] = 1 # Infected
actual_status[u] = 3 # Removed
-
In the file ndlib/models/opinions/HKModel.py, the code calculate new opinion for N nodes by actual_status, but only record the final one.
I wonder why not update the opinion of nodes one by one, using the status of previous step.
And it is more intuitive to return the opinion values of all nodes in status.
def iteration(self, node_status=True):
# ...
# start from line 90
for i in range(0, self.graph.number_of_nodes()):
# select a random node
n1 = list(self.graph.nodes)[
np.random.randint(0, self.graph.number_of_nodes())
] # why not update one by one? This operation results in a node possibly being updated multiple times in a time step. It's confused.
# select neighbors of n1
neighbours = list(self.graph.neighbors(n1))
sum_op = 0
count_in_eps = 0
if len(neighbours) == 0:
continue
for neigh in neighbours:
# compute the difference between opinions
diff_opinion = np.abs((actual_status[n1]) - (actual_status[neigh])) # actual_status is the current status
if diff_opinion < self.params["model"]["epsilon"]:
sum_op += actual_status[neigh] # actual_status is the current status
# count_in_eps is the number of neighbors in epsilon
count_in_eps += 1
if count_in_eps > 0:
new_op = sum_op / float(count_in_eps)
else:
# if there aren't neighbors in epsilon, the status of n1 doesn't change
new_op = actual_status[n1] # actual_status is the current status
actual_status[n1] = new_op # only update the last one?
delta, node_count, status_delta = self.status_delta(actual_status)
self.status = actual_status
self.actual_iteration += 1
if node_status:
return {
"iteration": self.actual_iteration - 1,
"status": delta.copy(), # delta only contain nodes that have changed their statuses
"node_count": node_count.copy(),
"status_delta": status_delta.copy(),
}
else:
return {
"iteration": self.actual_iteration - 1,
"status": {},
"node_count": node_count.copy(),
"status_delta": status_delta.copy(),
}
In my opinion, the HK model should update all nodes one by one in one timestep,using the status of previous step and record all.
There should be one more tab before actual_status[n1] = new_op.
Some actual_status should be self.status
def iteration(self, node_status=True):
# ...
for i in range(0, self.graph.number_of_nodes()):
# select a random node
n1 = i
# select neighbors of n1
neighbours = list(self.graph.neighbors(n1))
sum_op = 0
count_in_eps = 0
if len(neighbours) == 0:
continue
for neigh in neighbours:
# compute the difference between opinions
diff_opinion = np.abs((self.status[n1]) - (self.status[neigh])) # use previous status
if diff_opinion < self.params["model"]["epsilon"]:
sum_op += self.status[neigh] # use previous status
# count_in_eps is the number of neighbors in epsilon
count_in_eps += 1
if count_in_eps > 0:
new_op = sum_op / float(count_in_eps)
else:
# if there aren't neighbors in epsilon, the status of n1 doesn't change
new_op = self.status[n1] # use the previous status
actual_status[n1] = new_op # one more Tab
delta, node_count, status_delta = self.status_delta(actual_status)
self.status = actual_status
self.actual_iteration += 1
if node_status:
return {
"iteration": self.actual_iteration - 1,
"status": self.status.copy(), # return all nodes
"node_count": node_count.copy(),
"status_delta": status_delta.copy(),
}
else:
return {
"iteration": self.actual_iteration - 1,
"status": {},
"node_count": node_count.copy(),
"status_delta": status_delta.copy(),
}
-
The questions in the file ndlib/models/opinions/WHKModel.py are the same as above.
But the code doesn't miss tabs and returns all node states.
-
There are two mistakes in the file ndlib/models/epidemics/ProfileModel.py.
Firstly, The effect of the Blocked node should not be considered because its label is -1
def iteration(self, node_status=True):
# ...
# start from line 105
infected = 0
for v in neighbors:
infected += self.status[v]
The Blocked nodes should be excluded for computing infected
def iteration(self, node_status=True):
# ...
infected = 0
for v in neighbors:
infected += self.status[v] if self.status[v] != -1 else 0
AND the code update rule does not match the description,
def iteration(self, node_status=True):
# ...
# start with line 109
if infected > 0 and actual_status[u] == 0:
eventp = np.random.random_sample()
if eventp >= self.params["nodes"]["profile"][u]: # update with probability 1-self.params["nodes"]["profile"][u]
actual_status[u] = 1
else:
if self.params["model"]["blocked"] != 0:
blip = np.random.random_sample()
if blip > self.params["model"]["blocked"]: # update with probability 1-self.params["model"]["blocked"]
actual_status[u] = -1
BUT in the docs, node update with profile or blocked, so:
def iteration(self, node_status=True):
# ...
if infected > 0 and actual_status[u] == 0:
eventp = np.random.random_sample()
if eventp < self.params["nodes"]["profile"][u]: # update with probability self.params["nodes"]["profile"][u]
actual_status[u] = 1
else:
if self.params["model"]["blocked"] != 0:
blip = np.random.random_sample()
if blip < self.params["model"]["blocked"]: # update with probability self.params["model"]["blocked"]
actual_status[u] = -1
-
The mistakes in the file ndlib/models/epidemics/ProfileThresholdModel.py are the same as above.
Additional context
Hoping for your reply.
Describe the bug
Bugs in SEIRct, SEISct, SWIR, HK, WHK, Profile and ProfileThreshold models
To Reproduce
Steps to reproduce the behavior:
Expected behavior
In the file
ndlib/models/epidemics/SEIR_ct_Model.py,the function
iterationupdate theself.progress_Iof all Inf nodes as 0:I think
self.progress_Ionly can be initiated whenself.actual_iteration == 0The first problem of
ndlib/models/epidemics/SEIS_ct_Model.pyis the same as above.The second problem is that
self.progressof new Exposed nodes always be 0.I think
self.progressof new Exposed nodes should be updated byself.actual_iteration:In the file
ndlib/models/epidemics/SWIRModel.py, the random valueeventpis not updated in timeeventpshould be updated for each judgmentIn the file
ndlib/models/opinions/HKModel.py, the code calculate new opinion for N nodes byactual_status, but only record the final one.I wonder why not update the opinion of nodes one by one, using the status of previous step.
And it is more intuitive to return the opinion values of all nodes in status.
In my opinion, the HK model should update all nodes one by one in one timestep,using the status of previous step and record all.
There should be one more
tabbeforeactual_status[n1] = new_op.Some
actual_statusshould beself.statusThe questions in the file
ndlib/models/opinions/WHKModel.pyare the same as above.But the code doesn't miss tabs and returns all node states.
There are two mistakes in the file
ndlib/models/epidemics/ProfileModel.py.Firstly, The effect of the Blocked node should not be considered because its label is -1
The Blocked nodes should be excluded for computing
infectedAND the code update rule does not match the description,
BUT in the docs, node update with
profileorblocked, so:The mistakes in the file
ndlib/models/epidemics/ProfileThresholdModel.pyare the same as above.Additional context
Hoping for your reply.