def test_log_medium_issue():
    logger = mock.MagicMock()
    issues = []
    log_issue(logger, "hi", IssueSeverity.MEDIUM, issues)

    assert issues == [standard_api.Issue(description="hi", severity=5)]
    logger.warning.assert_called_once_with("hi")
def test_log_low_issue():
    logger = mock.MagicMock()
    issues = []
    log_issue(logger, "hi", IssueSeverity.LOW, issues)

    assert issues == [standard_api.Issue(description="hi", severity=1)]
    logger.info.assert_called_once_with("hi")
def test_log_high_issue():
    logger = mock.MagicMock()
    issues = []
    log_issue(logger, "hi", IssueSeverity.HIGH, issues)

    assert issues == [standard_api.Issue(description="hi", severity=10)]
    logger.error.assert_called_once_with("hi")
Ejemplo n.º 4
0
def distributeContactsOverAges(
    nodeState: Dict[Tuple[Age, Compartment], float],
    newContacts: float,
    stochastic: bool,
    random_state: Optional[np.random.Generator],
    issues: Optional[List[standard_api.Issue]] = None,
) -> Dict[Age, float]:
    r"""
    Distribute the number of new contacts across a region. There are two possible implementations:

    1. Deterministic (``stochastic=False``) -- We simply allocate newContacts by proportion of people in each age groups
    2. Stochastic (``stochastic=True``) -- newContacts are sampled randomly from the population from a Multinomial
       distribution, with: :math:`k=\text{new contacts}`, :math:`p=\text{proportion of people in age groups}`

    Note: fractional people will come out of this.

    :param nodeState: The disease status of the population stratified by age.
    :param newContacts: The number of new contacts to be distributed across age ranges.
    :param stochastic: Whether to run the model in a stochastic or deterministic mode
    :param random_state: Random number generator used for the model
    :param issues: if any issues are found and a list is passed as this parameter, append the issues to it. Logging will
                   happen regardless.
    :return: The number of new infections in each age group.
    """
    ageToSus = {}
    totalSus = 0.0
    for age in getAges(nodeState):
        sus = getSusceptibles(age, nodeState)
        ageToSus[age] = sus
        totalSus += sus

    if totalSus < newContacts:
        log_issue(
            logger,
            f"totalSus < incoming contacts ({totalSus} < {newContacts}) - adjusting to totalSus",
            IssueSeverity.HIGH,
            issues if issues is not None else [],
        )
        newContacts = totalSus

    if totalSus > 0:
        if stochastic:
            newInfectionsByAge = _distributeContactsOverAgesStochastic(
                ageToSus, totalSus, newContacts, random_state)
        else:
            newInfectionsByAge = _distributeContactsOverAgesDeterministic(
                ageToSus, totalSus, newContacts)
    else:
        newInfectionsByAge = {age: 0 for age in ageToSus}

    return newInfectionsByAge
Ejemplo n.º 5
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,
    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)
def main(argv):
    """
    Main function to run the network of populations simulation
    """
    t0 = time.time()

    args = build_args(argv)
    setup_logger(args)
    logger.info("Parameters\n%s",
                "\n".join(f"\t{key}={value}"
                          for key, value in args._get_kwargs()))  # pylint: disable=protected-access

    issues: List[standard_api.Issue] = []

    info = common.get_repo_info()
    if not info.git_sha:
        log_issue(
            logger,
            "Not running from a git repo, so no git_sha associated with the run",
            IssueSeverity.HIGH,
            issues,
        )
    elif info.is_dirty:
        log_issue(logger, "Running out of a dirty git repo",
                  IssueSeverity.HIGH, issues)
    with standard_api.StandardAPI.from_config(args.data_pipeline_config,
                                              uri=info.uri,
                                              git_sha=info.git_sha) as store:
        network, new_issues = ss.createNetworkOfPopulation(
            store.read_table("human/compartment-transition",
                             "compartment-transition"),
            store.read_table("human/population", "population"),
            store.read_table("human/commutes", "commutes"),
            store.read_table("human/mixing-matrix", "mixing-matrix"),
            store.read_table("human/infectious-compartments",
                             "infectious-compartments"),
            store.read_table("human/infection-probability",
                             "infection-probability"),
            store.read_table("human/initial-infections", "initial-infections"),
            store.read_table("human/trials", "trials"),
            store.read_table("human/start-end-date", "start-end-date"),
            store.read_table("human/movement-multipliers",
                             "movement-multipliers")
            if args.use_movement_multipliers else None,
            store.read_table("human/stochastic-mode", "stochastic-mode"),
        )
        issues.extend(new_issues)

        random_seed = loaders.readRandomSeed(
            store.read_table("human/random-seed", "random-seed"))
        results = runSimulation(
            network,
            random_seed,
            issues=issues,
            max_workers=None if not args.workers else args.workers)
        aggregated = aggregateResults(results)

        logger.info("Writing output")
        store.write_table(
            "output/simple_network_sim/outbreak-timeseries",
            "outbreak-timeseries",
            _convert_category_to_str(aggregated.output),
            issues=issues,
            description=aggregated.description,
        )
        for i, result in enumerate(results):
            store.write_table(
                "output/simple_network_sim/outbreak-timeseries",
                f"run-{i}",
                _convert_category_to_str(result.output),
                issues=result.issues,
                description=result.description,
            )

        logger.info("Took %.2fs to run the simulation.", time.time() - t0)
        logger.info(
            "Use `python -m simple_network_sim.network_of_populations.visualisation -h` to find out how to take "
            "a peak what you just ran. You will need use the access-<hash>.yaml file that was created by this run."
        )