Skip to content

Bugs in SEIRct, SEISct, SWIR, HK, WHK, Profile and ProfileThreshold models #263

@Allen-Ciel

Description

@Allen-Ciel

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

  1. 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)
            # ...
  2. 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
  3. 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
  4. 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(),
            }
  5. 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.

  6. 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
  7. The mistakes in the file ndlib/models/epidemics/ProfileThresholdModel.py are the same as above.

Additional context
Hoping for your reply.

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions