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
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), }
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
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), }
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
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,
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]),
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), }
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)
""" 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')