Ejemplo n.º 1
0
    def __call__(self, tspan, y0, dt=.05):
        influxes = super().__call__(tspan, y0, dt=dt)
        t = influxes.t

        for key, (src, prob, dist) in self.readouts.items():
            influxes.y[key] = dist.convolve_pdf(t, influxes.y[src], prob)

        sol = SimulationResult(t, {})

        for key, val in influxes.y.items():
            if key not in ["susceptible", "population"]:
                sol.y[key] = np.cumsum(val, axis=0)
            else:
                sol.y[key] = val

        infectious_dist = GammaDistribution(mean=5, std=2)
        sol.y['infectious'] = infectious_dist.convolve_survival(
            t, influxes.y['infected']
        )
        sol.y["critical"] = sol.y["icu"] - sol.y["dead"] - sol.y["recovered"]
        sol.y['ventilators'] = .73 * sol.y['critical']

        i = np.searchsorted(sol.t, sol.t[0] + 5)
        sol.y["hospitalized"] = np.zeros_like(sol.y['critical'])
        sol.y["hospitalized"][:-i] = 2.7241 * sol.y['critical'][i:]
        sol.y["hospitalized"][-i:] = np.nan

        return sol
Ejemplo n.º 2
0
    def __init__(self, mitigation=None, *, age_distribution, ifr=0.003,
                 r0=3.2, serial_dist=default_serial,
                 seasonal_forcing_amp=.2, peak_day=15,
                 incubation_dist=GammaDistribution(5.5, 2), p_observed=1,
                 icu_dist=GammaDistribution(11, 5), p_icu=1,
                 dead_dist=GammaDistribution(7.5, 7.5), p_dead=1,
                 recovered_dist=GammaDistribution(7.5, 7.5),
                 **kwargs):
        """
        In addition to the arguments recognized by
        :class:`~pydemic.models.seirpp.NonMarkovianSEIRSimulationBase`, the
        following keyword-only arguments are recognized:

        :arg age_distribution:

        :arg ifr:

        :arg incubation_dist:

        :arg p_observed:

        :arg icu_dist:

        :arg p_icu:

        :arg dead_dist:

        :arg p_dead:

        :arg recovered_dist:
        """

        super().__init__(
            mitigation=mitigation, r0=r0,
            serial_dist=serial_dist,
            seasonal_forcing_amp=seasonal_forcing_amp, peak_day=peak_day
        )

        p_symptomatic = 1.
        p_symptomatic = np.array(p_symptomatic)
        p_observed = np.array(p_observed)

        # FIXME: this is a kludge-y way to set the target ifr
        # (infection, not just symptomatic)
        p_dead_all = p_symptomatic * p_observed * p_icu * p_dead
        synthetic_ifr = (p_dead_all * age_distribution).sum()
        p_symptomatic *= ifr / synthetic_ifr

        self.readouts = {
            "observed": ('infected', p_observed * p_symptomatic, incubation_dist),
            "icu": ('observed', p_icu, icu_dist),
            "dead": ('icu', p_dead, dead_dist),
            "recovered": ('icu', (1 - p_dead), recovered_dist),
        }
Ejemplo n.º 3
0
    def __call__(self, tspan, y0, dt=.05):
        influxes = super().__call__(tspan, y0, dt=dt)
        t = influxes.t

        for key, (src, prob, dist) in self.readouts.items():
            influxes.y[key] = dist.convolve_pdf(t, influxes.y[src], prob)

        sol = SimulationResult(t, {})

        for key, val in influxes.y.items():
            if key not in ["susceptible", "population"]:
                sol.y[key] = np.cumsum(val, axis=0)
            else:
                sol.y[key] = val

        infectious_dist = GammaDistribution(mean=5, std=2)
        sol.y['infectious'] = infectious_dist.convolve_survival(
            t, influxes.y['infected']
        )

        return sol
Ejemplo n.º 4
0
    def __init__(self, mitigation=None, *,
                 r0=3.2, serial_dist=default_serial,
                 seasonal_forcing_amp=.2, peak_day=15,
                 p_symptomatic=1,
                 incubation_dist=GammaDistribution(5.5, 2), p_observed=1,
                 dead_dist=GammaDistribution(7.5, 7.5), p_dead=1,
                 **kwargs):
        """
        In addition to the arguments recognized by
        :class:`~pydemic.models.seirpp.NonMarkovianSEIRSimulationBase`, the
        following keyword-only arguments are recognized:

        :arg p_symptomatic:

        :arg incubation_dist:

        :arg p_observed:

        :arg dead_dist:

        :arg p_dead:
        """

        super().__init__(
            mitigation=mitigation, r0=r0,
            serial_dist=serial_dist,
            seasonal_forcing_amp=seasonal_forcing_amp, peak_day=peak_day
        )

        p_symptomatic = np.array(p_symptomatic)
        p_observed = np.array(p_observed)
        p_dead = np.array(p_dead)

        self.readouts = {
            "observed": ('infected', p_observed * p_symptomatic, incubation_dist),
            "dead": ('observed', p_dead, dead_dist),
        }
Ejemplo n.º 5
0
    def __call__(self, tspan, y0, dt=.05):
        influxes = super().__call__(tspan, y0, dt=dt)
        t = influxes.t

        for key, (src, prob, dist) in self.readouts.items():
            influxes.y[key] = dist.convolve_pdf(t, influxes.y[src], prob)

        sol = SimulationResult(t, {})

        for key, val in influxes.y.items():
            if key not in ["susceptible", "population"]:
                sol.y[key] = np.cumsum(val, axis=0)
            else:
                sol.y[key] = val

        infectious_dist = GammaDistribution(mean=5, std=2)
        sol.y['infectious'] = infectious_dist.convolve_survival(
            t, influxes.y['infected']
        )

        sol.y['critical'] = sol.y['icu'] - sol.y['general_ward'] - sol.y['dead']
        sol.y['ventilators'] = .73 * sol.y['critical']
        sol.y['hospitalized'] = (
            sol.y['admitted_to_hospital']
            - sol.y['hospital_recovered'] - sol.y['icu']
        )
        sol.y['hospitalized'] += (sol.y['general_ward']
                                  - sol.y['general_ward_recovered'])

        sol.y['total_discharged'] = (
            sol.y['hospital_recovered'] + sol.y['general_ward_recovered']
        )
        sol.y['recovered'] = (
            sol.y['infected'] - sol.y['infectious'] - sol.y['all_dead']
        )

        return sol
Ejemplo n.º 6
0
     initial_cases=10,
     ifr=.007,
     p_symptomatic=np.array([.1, .3, .5, .9]),
     p_critical=.9,
     p_dead=.9,
     p_positive=np.array([.4, .5, .6, .7]),
 ),
 "change_all_params":
 dict(
     mitigation=MitigationModel(*tspan, [70, 80], [1., .4]),
     age_distribution=np.array([.2, .3, .4, .1]),
     total_population=1e6,
     initial_cases=9,
     ifr=.008,
     r0=2.5,
     serial_dist=GammaDistribution(4, 3.3),
     seasonal_forcing_amp=.1,
     peak_day=7,
     incubation_dist=GammaDistribution(5.3, 4),
     p_symptomatic=np.array([.2, .4, .5, .8]),
     p_positive=.9 * np.array([.2, .4, .5, .8]),
     hospitalized_dist=GammaDistribution(8, 4),
     p_hospitalized=np.array([.4, .6, .7, .8]),
     discharged_dist=GammaDistribution(7, 3),
     critical_dist=GammaDistribution(4, 1),
     p_critical=np.array([.3, .3, .7, .9]),
     dead_dist=GammaDistribution(4, 3),
     p_dead=np.array([.4, .4, .7, .9]),
     recovered_dist=GammaDistribution(8, 2.5),
     all_dead_dist=GammaDistribution(2., 1.5),
     all_dead_multiplier=1.3,
Ejemplo n.º 7
0
plt.rcParams['font.size'] = 16

state = "Illinois"
from pydemic.data.united_states import nyt, get_population, get_age_distribution
data = nyt(state)
total_population = get_population(state)
age_distribution = get_age_distribution()

tspan = ('2020-02-15', '2020-05-30')

from pydemic.distributions import GammaDistribution

parameters = dict(
    ifr=.003,
    r0=2.3,
    serial_dist=GammaDistribution(mean=4, std=3.25),
    seasonal_forcing_amp=.1,
    peak_day=15,
    incubation_dist=GammaDistribution(5.5, 2),
    p_symptomatic=np.array(
        [0.057, 0.054, 0.294, 0.668, 0.614, 0.83, 0.99, 0.995, 0.999]),
    # p_positive=1.5,
    hospitalized_dist=GammaDistribution(6.5, 1.6),
    p_hospitalized=np.array(
        [0.001, 0.003, 0.012, 0.032, 0.049, 0.102, 0.166, 0.243, 0.273]),
    discharged_dist=GammaDistribution(9, 6),
    critical_dist=GammaDistribution(3, 1),
    p_critical=.9 *
    np.array([0.05, 0.05, 0.05, 0.05, 0.063, 0.122, 0.274, 0.432, 0.709]),
    dead_dist=GammaDistribution(7.5, 5.),
    p_dead=1.2 * np.array([0.3, 0.3, 0.3, 0.3, 0.3, 0.4, 0.4, 0.5, 0.5]),
Ejemplo n.º 8
0
    def __init__(self, mitigation=None, *, age_distribution, ifr=None,
                 r0=3.2, serial_dist=default_serial,
                 seasonal_forcing_amp=.2, peak_day=15,
                 incubation_dist=GammaDistribution(5.5, 2),
                 p_symptomatic=1., p_positive=1.,
                 hospitalized_dist=GammaDistribution(6.5, 4), p_hospitalized=1.,
                 discharged_dist=GammaDistribution(6, 4),
                 critical_dist=GammaDistribution(2, 2), p_critical=1.,
                 dead_dist=GammaDistribution(7.5, 7.5), p_dead=1.,
                 recovered_dist=GammaDistribution(7.5, 7.5),
                 all_dead_dist=GammaDistribution(2.5, 2.5), all_dead_multiplier=1.):
        """
        In addition to the arguments recognized by
        :class:`~pydemic.models.seirpp.NonMarkovianSEIRSimulationBase`, the
        following keyword-only arguments are recognized:

        :arg age_distribution: A :class:`numpy.ndarray` specifying the relative
            fraction of the population in various age groups.

        :arg ifr: The infection fatality ratio, i.e., the proportion of the infected
            population who eventually die.
            If not *None*, will rescale ``p_symptomatic`` to effect the passed value.

        :arg p_symptomatic: The distribution of the proportion of infected
            individuals who become symptomatic.

        :arg incubation_dist: The delay-time distribution
            for developing symptoms after being infected.

        :arg p_positive: The fraction of symptomatic individuals who are tested and
            test positive.

        :arg p_hospitalized: The distribution of the proportion of symptomatic
            individuals who enter the hospital.

        :arg hospitalized_dist: The delay-time distribution of
            entering the hospital after becoming symptomatic.

        :arg discharged_dist: The delay-time distribution of
            survivors being discharged after entering the hospital.

        :arg p_critical: The distribution of the proportion of
            hospitalized individuals who become critical.

        :arg critical_dist: The delay-time distribution
            of hospitalized individuals entering the ICU.

        :arg p_dead: The distribution of the proportion of
            ICU patients who die.

        :arg dead_dist: The delay-time distribution of ICU patients dying.

        :arg recovered_dist: The delay-time distribution
            of ICU patients recovering and returning to the general ward.

        :arg all_dead_multiplier: The ratio of total deaths to deaths occurring
            in the ICU.

        :arg all_dead_dist: The delay-time distribution
            between ICU deaths and all reported deaths.
        """

        super().__init__(
            mitigation=mitigation, r0=r0,
            serial_dist=serial_dist,
            seasonal_forcing_amp=seasonal_forcing_amp, peak_day=peak_day
        )

        age_distribution = np.array(age_distribution)
        p_symptomatic = np.array(p_symptomatic)
        p_hospitalized = np.array(p_hospitalized)
        p_critical = np.array(p_critical)
        p_dead = np.array(p_dead)

        # if p_symptomatic is None, set so that
        # p_symptomatic * p_hospitalized * p_critical * p_dead
        # weighted by the age distribution, achieves ifr
        if ifr is not None:
            p_dead_net = (
                p_symptomatic * p_hospitalized * p_critical
                * p_dead * all_dead_multiplier
            )
            weighted_sum = (p_dead_net * age_distribution).sum()
            p_symptomatic *= ifr / weighted_sum

        # check that none of the probabilties are too large
        from pydemic.sampling import InvalidParametersError

        p_progression = [age_distribution]
        for prob in (p_symptomatic, p_hospitalized, p_critical, p_dead):
            p_progression.append(p_progression[-1] * prob)

        names = ('p_symptomatic', 'p_hospitalized', 'p_critical', 'p_dead')
        for i, name in enumerate(names):
            if p_progression[i].sum() < p_progression[i+1].sum():
                raise InvalidParametersError("%s is too large" % name)

        self.readouts = {
            "symptomatic": ('infected', p_symptomatic, incubation_dist),
            "positive": ('infected', p_positive * p_symptomatic, incubation_dist),
            "admitted_to_hospital": ('symptomatic', p_hospitalized,
                                     hospitalized_dist),
            "icu": ('admitted_to_hospital', p_critical, critical_dist),
            "dead": ('icu', p_dead, dead_dist),
            "general_ward": ('icu', 1.-p_dead, recovered_dist),
            "hospital_recovered": ('admitted_to_hospital', 1.-p_critical,
                                   discharged_dist),
            "general_ward_recovered": ('general_ward', 1., discharged_dist),
            "all_dead": ('dead', all_dead_multiplier, all_dead_dist),
        }
Ejemplo n.º 9
0
    def get_model_data(cls, t, **kwargs):
        """
        A wrapper to :meth:`__init__` and :meth:`__call__` for initializing and
        running a simulation from keyword arguments only (i.e., as used by
        :class:`~pydemic.LikelihoodEstimator`.)

        :arg t: A :class:`pandas.DatetimeIndex` (or :class:`numpy.ndarray` of
            times in days since 2020/1/1) of times at which to evaluate the
            solution.

        The following keyword arguments are required:

        :arg start_day: The day (relative to 2020/1/1) at which to begin the
            simulation---i.e., the day corresponding to the initial condition
            generated by :meth:`get_y0`.

        :arg total_population: The total size of the population.

        :arg initial_cases: The total numnber of initial cases.

        :arg age_distribution: A :class:`numpy.ndarray` specifying the relative
            fraction of the population in various age groups.

        In addition, a :class:`~pydemic.MitigationModel` is created from
        passed keyword arguments via
        :meth:`~pydemic.MitigationModel.init_from_kwargs`.

        The following optional keyword arguments are also recognized:

        :arg min_mitigation_spacing: The minimum number of days of separation
            between mitigation events.
            Defaults to ``5``.

        All remaining keyword arguments are passed to :meth:`__init__`.

        :raises InvalidParametersError: if ``t`` specifies any days of evaluation
            which are not at least one day after ``start_day``.

        :raises InvalidParametersError: if mitigation events are not ordered and
            separated by ``min_mitigation_spacing``.

        :returns: A :class:`pandas.DataFrame` of simulation results.
        """

        if isinstance(t, pd.DatetimeIndex):
            t_eval = (t - pd.to_datetime('2020-01-01')) / pd.Timedelta('1D')
        else:
            t_eval = t

        t0 = kwargs.pop('start_day')
        tf = t_eval[-1] + 2

        from pydemic.sampling import InvalidParametersError

        if (t_eval < t0 + 1).any():
            raise InvalidParametersError(
                "Must start simulation at least one day before result evaluation."
            )

        if 'log_ifr' in kwargs:
            if 'ifr' in kwargs:
                raise InvalidParametersError("Can't pass both ifr and log_ifr.")
            kwargs['ifr'] = np.exp(kwargs.pop('log_ifr'))

        try:
            from pydemic.mitigation import MitigationModel
            mitigation = MitigationModel.init_from_kwargs(t0, tf, **kwargs)
            for key in list(kwargs.keys()):
                if 'mitigation_t' in key or 'mitigation_factor' in key:
                    _ = kwargs.pop(key)
        except ValueError:  # raised by PchipInterpolator when times aren't ordered
            raise InvalidParametersError(
                "Mitigation times must be ordered within t0 and tf."
            )

        if any(np.diff(mitigation.times) < kwargs.pop('min_mitigation_spacing', 5)):
            raise InvalidParametersError(
                "Mitigation times must be spaced by at least min_mitigation_spacing."
                " Decrease min_mitigation_spacing to prevent this check."
            )

        age_distribution = kwargs.pop('age_distribution')

        for key in ('serial', 'incubation', 'hospitalized', 'discharged',
                    'critical', 'dead', 'recovered', 'all_dead'):
            mean = kwargs.pop(key+'_mean', None)
            std = kwargs.pop(key+'_std', None)
            shape = kwargs.pop(key+'_k', None)
            # only pass key_dist if key_mean and one of key_std/key_k are passed
            if mean is not None and shape is not None:
                kwargs[key+'_dist'] = GammaDistribution(mean=mean, shape=shape)
            elif mean is not None and std is not None:
                kwargs[key+'_dist'] = GammaDistribution(mean=mean, std=std)
            elif mean is not None:
                raise InvalidParametersError(
                    "Must pass either %s+_shape or %s+_std." % (key, key)
                )

        for key in ('symptomatic', 'positive', 'hospitalized', 'critical', 'dead'):
            # FIXME: decide on default behavior here
            prefactor = kwargs.pop('p_'+key+'_prefactor', None)
            if prefactor is not None:
                prob = np.array(kwargs.get('p_'+key, 1.)).copy()
                kwargs['p_'+key] = prefactor * prob

        total_population = kwargs.pop('total_population')
        initial_cases = kwargs.pop('initial_cases')

        sim = cls(
            mitigation=mitigation, age_distribution=age_distribution, **kwargs
        )

        y0 = sim.get_y0(total_population, initial_cases, age_distribution)
        result = sim((t0, tf), y0)

        y = {}
        for key, val in result.y.items():
            y[key] = interp1d(result.t, val.sum(axis=-1), axis=0)(t_eval)

        for key in cls.increment_keys:
            if key in result.y.keys():
                spline = interp1d(result.t, result.y[key].sum(axis=-1), axis=0)
                y[key+'_incr'] = spline(t_eval) - spline(t_eval - 1)

        _t = pd.to_datetime(t_eval, origin='2020-01-01', unit='D')
        return pd.DataFrame(y, index=_t)
Ejemplo n.º 10
0
"""

import numpy as np
import pandas as pd
from scipy.interpolate import interp1d
# pylint: disable=no-member


class SimulationResult:
    def __init__(self, time, y):
        self.t = time
        self.y = y


from pydemic.distributions import GammaDistribution
default_serial = GammaDistribution(mean=4, std=3.25)


class NonMarkovianSEIRSimulationBase:
    """
    Main driver for non-Markovian simulations, used as a base class for
    SEIR++ variants.

    .. automethod:: __init__
    .. automethod:: get_y0
    .. automethod:: __call__
    .. automethod:: get_model_data
    """

    increment_keys = ('infected', 'dead')