def test_readParametersAgeStructured_less_than_100_percent(): with pytest.raises(AssertionError): df = pd.DataFrame([ { "age": "70+", "src": "A", "dst": "A", "rate": 0.5 }, { "age": "70+", "src": "A", "dst": "I", "rate": 0.2 }, ]) loaders.readCompartmentRatesByAge(df)
def test_readParametersAgeStructured_invalid_float(): with pytest.raises(AssertionError): df = pd.DataFrame([ { "age": "70+", "src": "A", "dst": "A", "rate": 1.5 }, { "age": "70+", "src": "A", "dst": "I", "rate": -0.5 }, ]) loaders.readCompartmentRatesByAge(df)
def test_readCompartmentRatesByAge_approximately_one(): result = loaders.readCompartmentRatesByAge( pd.DataFrame([{ "age": "70+", "src": "A", "dst": "A", "rate": 0.999999999 }])) assert result == {"70+": {"A": {"A": 0.999999999}}}
def test_readCompartmentRatesByAge(data_api): result = loaders.readCompartmentRatesByAge( data_api.read_table("human/compartment-transition")) assert result == { "70+": { "E": { "E": pytest.approx(0.573), "A": pytest.approx(0.427) }, "A": { "A": pytest.approx(0.803), "I": pytest.approx(0.0197), "R": pytest.approx(0.1773) }, "I": { "I": pytest.approx(0.67), "D": pytest.approx(0.0165), "H": pytest.approx(0.0495), "R": pytest.approx(0.264) }, "H": { "H": pytest.approx(0.9), "D": pytest.approx(0.042), "R": pytest.approx(0.058) }, "R": { "R": pytest.approx(1.0) }, "D": { "D": pytest.approx(1.0) }, }, "[17,70)": { "E": { "E": pytest.approx(0.573), "A": pytest.approx(0.427) }, "A": { "A": pytest.approx(0.803), "I": pytest.approx(0.0197), "R": pytest.approx(0.1773) }, "I": { "I": pytest.approx(0.67), "D": pytest.approx(0.0165), "H": pytest.approx(0.0495), "R": pytest.approx(0.264) }, "H": { "H": pytest.approx(0.9), "D": pytest.approx(0.042), "R": pytest.approx(0.058) }, "R": { "R": pytest.approx(1.0) }, "D": { "D": pytest.approx(1.0) }, }, "[0,17)": { "E": { "E": pytest.approx(0.573), "A": pytest.approx(0.427) }, "A": { "A": pytest.approx(0.803), "I": pytest.approx(0.0197), "R": pytest.approx(0.1773) }, "I": { "I": pytest.approx(0.67), "D": pytest.approx(0.0165), "H": pytest.approx(0.0495), "R": pytest.approx(0.264) }, "H": { "H": pytest.approx(0.9), "D": pytest.approx(0.042), "R": pytest.approx(0.058) }, "R": { "R": pytest.approx(1.0) }, "D": { "D": pytest.approx(1.0) }, }, }
def createNetworkOfPopulation( compartment_transition_table: pd.DataFrame, population_table: pd.DataFrame, commutes_table: pd.DataFrame, mixing_matrix_table: pd.DataFrame, infectious_states: pd.DataFrame, infection_prob: pd.DataFrame, initial_infections: pd.DataFrame, trials: pd.DataFrame, movement_multipliers_table: pd.DataFrame = None, stochastic_mode: pd.DataFrame = None, random_seed: pd.DataFrame = None) -> NetworkOfPopulation: """Create the network of the population, loading data from files. :param compartment_transition_table: pd.Dataframe specifying the transition rates between infected compartments. :param population_table: pd.Dataframe with the population size in each region by gender and age. :param commutes_table: pd.Dataframe with the movements between regions. :param mixing_matrix_table: pd.Dataframe with the age infection matrix. :param movement_multipliers_table: pd.Dataframe with the movement multipliers. This may be None, in which case no multipliers are applied to the movements. :param infectious_states: States that are considered infectious :param infection_prob: Probability that a given contact will result in an infection :param initial_infections: Initial infections of the population at time 0 :param trials: Number of trials for the model :param stochastic_mode: Use stochastic mode for the model :param random_seed: Random number generator seed used for stochastic mode :return: The constructed network """ infection_prob = loaders.readInfectionProbability(infection_prob) infectious_states = loaders.readInfectiousStates(infectious_states) initial_infections = loaders.readInitialInfections(initial_infections) trials = loaders.readTrials(trials) stochastic_mode = loaders.readStochasticMode(stochastic_mode) random_seed = loaders.readRandomSeed(random_seed) # diseases progression matrix progression = loaders.readCompartmentRatesByAge( compartment_transition_table) # population census data population = loaders.readPopulationAgeStructured(population_table) # Check some requirements for this particular model to work with the progression matrix all_states = set() for states in progression.values(): assert SUSCEPTIBLE_STATE not in states, "progression from susceptible state is not allowed" for state, nextStates in states.items(): for nextState in nextStates: all_states.add(state) all_states.add(nextState) assert state == nextState or nextState != EXPOSED_STATE, \ "progression into exposed state is not allowed other than in self reference" assert (set(infectious_states) - all_states) == set(), \ f"mismatched infectious states and states {infectious_states} {set(progression.keys())}" # people movement's graph graph = loaders.genGraphFromContactFile(commutes_table) # movement multipliers (dampening or heightening) if movement_multipliers_table is not None: movementMultipliers = loaders.readMovementMultipliers( movement_multipliers_table) else: movementMultipliers: Dict[int, loaders.Multiplier] = {} # age-based infection matrix mixingMatrix = loaders.MixingMatrix(mixing_matrix_table) agesInInfectionMatrix = set(mixingMatrix) for age in mixingMatrix: assert agesInInfectionMatrix == set( mixingMatrix[age]), "infection matrix columns/rows mismatch" # Checks across datasets assert agesInInfectionMatrix == set( progression.keys()), "infection matrix and progression ages mismatch" assert agesInInfectionMatrix == {age for region in population.values() for age in region}, \ "infection matrix and population ages mismatch" assert set(graph.nodes()) == set( population.keys()), "regions mismatch between graph and population" state0: Dict[str, Dict[Tuple[str, str], float]] = {} for node in list(graph.nodes()): region = state0.setdefault(node, {}) for age, compartments in progression.items(): region[(age, SUSCEPTIBLE_STATE)] = population[node][age] for compartment in compartments: region[(age, compartment)] = 0 logger.info("Nodes: %s, Ages: %s, States: %s", len(state0), agesInInfectionMatrix, all_states) return NetworkOfPopulation(progression=progression, graph=graph, initialState=state0, mixingMatrix=mixingMatrix, movementMultipliers=movementMultipliers, infectiousStates=infectious_states, infectionProb=infection_prob, initialInfections=initial_infections, trials=trials, stochastic=stochastic_mode, randomState=np.random.default_rng(random_seed))
def createNetworkOfPopulation( compartment_transition_table: pd.DataFrame, population_table: pd.DataFrame, commutes_table: pd.DataFrame, mixing_matrix_table: pd.DataFrame, infectious_states: pd.DataFrame, infection_prob: pd.DataFrame, initial_infections: pd.DataFrame, trials: pd.DataFrame, start_end_date: pd.DataFrame, movement_multipliers_table: pd.DataFrame = None, stochastic_mode: pd.DataFrame = None, ) -> Tuple[NetworkOfPopulation, List[standard_api.Issue]]: """Create the network of the population, loading data from files. :param compartment_transition_table: pd.Dataframe specifying the transition rates between infected compartments. :param population_table: pd.Dataframe with the population size in each region by gender and age. :param commutes_table: pd.Dataframe with the movements between regions. :param mixing_matrix_table: pd.Dataframe with the age infection matrix. :param infectious_states: States that are considered infectious :param infection_prob: Probability that a given contact will result in an infection :param initial_infections: Initial infections of the population at time 0 :param trials: Number of trials for the model :param start_end_date: Starting and ending dates of the model :param movement_multipliers_table: pd.Dataframe with the movement multipliers. This may be None, in which case no multipliers are applied to the movements. :param stochastic_mode: Use stochastic mode for the model :return: A tuple with the constructed network and a list of any issues found. Although it may look convenient, storing this list inside of NetworkOfPopulation is not a good idea, as that may give the functions that receive it the false impression they can just append new issues there """ issues: List[standard_api.Issue] = [] infection_prob = loaders.readInfectionProbability(infection_prob) infectious_states = loaders.readInfectiousStates(infectious_states) initial_infections = loaders.readInitialInfections(initial_infections) trials = loaders.readTrials(trials) start_date, end_date = loaders.readStartEndDate(start_end_date) stochastic_mode = loaders.readStochasticMode(stochastic_mode) # diseases progression matrix progression = loaders.readCompartmentRatesByAge( compartment_transition_table) # population census data population = loaders.readPopulationAgeStructured(population_table) # Check some requirements for this particular model to work with the progression matrix all_states = set() for states in progression.values(): assert SUSCEPTIBLE_STATE not in states, "progression from susceptible state is not allowed" for state, nextStates in states.items(): for nextState in nextStates: all_states.add(state) all_states.add(nextState) assert state == nextState or nextState != EXPOSED_STATE, \ "progression into exposed state is not allowed other than in self reference" assert (set(infectious_states) - all_states) == set(), \ f"mismatched infectious states and states {infectious_states} {all_states}" # people movement's graph graph = loaders.genGraphFromContactFile(commutes_table) # movement multipliers (dampening or heightening) if movement_multipliers_table is not None: movementMultipliers = loaders.readMovementMultipliers( movement_multipliers_table) else: movementMultipliers = {} # age-based infection matrix mixingMatrix = loaders.MixingMatrix(mixing_matrix_table) agesInInfectionMatrix = set(mixingMatrix) for age in mixingMatrix: assert agesInInfectionMatrix == set( mixingMatrix[age]), "infection matrix columns/rows mismatch" # Checks across datasets assert agesInInfectionMatrix == set( progression.keys()), "infection matrix and progression ages mismatch" assert agesInInfectionMatrix == {age for region in population.values() for age in region}, \ "infection matrix and population ages mismatch" disconnected_nodes = set(population.keys()) - set(graph.nodes()) if disconnected_nodes: log_issue( logger, f"These nodes have no contacts in the current network: {','.join(disconnected_nodes)}", IssueSeverity.MEDIUM, issues, ) state0: Dict[str, Dict[Tuple[str, str], float]] = {} for node in list(graph.nodes()): region = state0.setdefault(node, {}) for age, compartments in progression.items(): if node not in population: log_issue( logger, f"Node {node} is not in the population table, assuming population of 0 for all ages", IssueSeverity.MEDIUM, issues, ) pop = 0.0 else: pop = population[node][age] region[(age, SUSCEPTIBLE_STATE)] = pop for compartment in compartments: region[(age, compartment)] = 0 logger.info("Nodes: %s, Ages: %s, States: %s", len(state0), agesInInfectionMatrix, all_states) nop = NetworkOfPopulation( progression=progression, graph=graph, initialState=state0, mixingMatrix=mixingMatrix, movementMultipliers=movementMultipliers, infectiousStates=infectious_states, infectionProb=infection_prob, initialInfections=initial_infections, trials=trials, startDate=start_date, endDate=end_date, stochastic=stochastic_mode, ) return (nop, issues)