def separate(self, date, name="Main", population=None, **kwargs): """ Create a new phase with the change point. New phase name will be automatically determined. Args: date (str): change point, i.e. start date of the new phase name (str): scenario name population (int): population value of the change point kwargs: keyword arguments of PhaseUnit.set_ode() Returns: covsirphy.Scenario: self """ series = self._ensure_name(name) try: phase, old = [(self.num2str(i), unit) for (i, unit) in enumerate(series) if date in unit][0] except IndexError: raise IndexError(f"Phase on @date ({date}) is not registered.") try: new_pre = PhaseUnit(old.start_date, self.yesterday(date), old.population) except ValueError: raise ValueError( f"{name} scenario cannot be separated on {date} because this date is registered as a start date." ) from None new_pre.set_ode(**old.to_dict()) new_fol = PhaseUnit(date, old.end_date, population or old.population) new_fol.set_ode(**kwargs) self._series_dict[name].replaces(phase, [new_pre, new_fol]) return self
def separate(self, date, population=None, **kwargs): """ Create a new phase with the change point. New phase name will be automatically determined. Args: date (str): change point, i.e. start date of the new phase population (int): population value of the change point kwargs: keyword arguments of PhaseUnit.set_ode() if update is necessary Returns: covsirphy.PhaseSeries """ phase, old = self.find_phase(date) if date in self.near_change_dates(): raise ValueError( f"Cannot be separated on {date} because this date is too close to registered change dates." ) new_pre = PhaseUnit(old.start_date, self.yesterday(date), old.population) setting_dict = old.to_dict() setting_dict.update(kwargs) new_pre.set_ode(**setting_dict) new_fol = PhaseUnit(date, old.end_date, population or old.population) new_fol.set_ode(model=old.model, **setting_dict) self._series.replaces(phase, [new_pre, new_fol]) return self._series
def combine(self, phases, name="Main", population=None, **kwargs): """ Combine the sequential phases as one phase. New phase name will be automatically determined. Args: phases (list[str]): list of phases name (str, optional): name of phase series population (int): population value of the start date kwargs: keyword arguments to save as phase information Raises: TypeError: @phases is not a list Returns: covsirphy.Scenario: self """ series = self._ensure_name(name) # Sort and check @phase is a list if not isinstance(phases, list): raise TypeError("@phases must be a list of phase names.") phases = list(set(phases)) if "last" in phases: last_phase = "last" phases.remove("last") phases = sorted(phases, key=self.str2num, reverse=False) else: phases = sorted(phases, key=self.str2num, reverse=False) last_phase = phases[-1] # Setting of the new phase start_date = series.unit(phases[0]).start_date end_date = series.unit(last_phase).end_date population = population or series.unit(last_phase).population new_unit = PhaseUnit(start_date, end_date, population) new_unit.set_ode(**kwargs) # Phases to keep kept_units = [ unit for unit in series if unit < start_date or unit > end_date ] # Replace units self._series_dict[name].replaces(phase=None, new_list=kept_units + [new_unit], keep_old=False) return self
def delete(self, phase="last"): """ Delete a phase. The phase will be combined to the previous phase. Args: phase (str): phase name, like 0th, 1st, 2nd... or 'last' Returns: covsirphy.PhaseSeries: self Note: When @phase is '0th', disable 0th phase. 0th phase will not be deleted. When @phase is 'last', the last phase will be deleted. """ if phase == "0th": self.disable("0th") return self if self.unit(phase) == self.unit("last"): self._units = self._units[:-1] return self phase_pre = self.num2str(self.str2num(phase) - 1) unit_pre, unit_fol = self.unit(phase_pre), self.unit(phase) if unit_pre <= self.last_date and unit_fol >= self.last_date: phase_next = self.num2str(self.str2num(phase) + 1) unit_next = self.unit(phase_next) model = unit_next.model param_dict = { k: v for (k, v) in unit_next.to_dict().items() if k in model.PARAMETERS } unit_new = PhaseUnit(unit_fol.start_date, unit_next.end_date, unit_next.population) unit_new.set_ode(model=model, tau=unit_next.tau, **param_dict) return self unit_new = PhaseUnit(unit_pre.start_date, unit_fol.end_date, unit_pre.population) model = unit_pre.model if model is None: param_dict = {} else: param_dict = { k: v for (k, v) in unit_pre.to_dict().items() if k in model.PARAMETERS } unit_new.set_ode(model=model, tau=unit_pre.tau, **param_dict) units = [ unit for unit in [unit_new, *self._units] if unit not in [unit_pre, unit_fol] ] self._units = sorted(units) return self
def combine(self, phases, population=None, **kwargs): """ Combine the sequential phases as one phase. New phase name will be automatically determined. Args: phases (list[str]): list of phases population (int): population value of the start date kwargs: keyword arguments to save as phase information Raises: TypeError: @phases is not a list Returns: covsirphy.Scenario: self """ all_phases = self.all_phases() if "last" in set(phases): phases.remove("last") phases = sorted(phases, key=self.str2num, reverse=False) last_phase = "last" else: phases = sorted(phases, key=self.str2num, reverse=False) last_phase = phases[-1] self._ensure_list(phases, candidates=all_phases, name="phases") # Setting of the new phase start_date = self._series.unit(phases[0]).start_date end_date = self._series.unit(last_phase).end_date population = population or self._series.unit(last_phase).population new_unit = PhaseUnit(start_date, end_date, population) new_unit.set_ode(**kwargs) # Phases to keep kept_units = [ unit for unit in self.series if unit < start_date or unit > end_date ] # Replace units self._series.replaces(phase=None, new_list=kept_units + [new_unit], keep_old=False) return self._series
def unit(self, phase="last"): """ Return the unit of the phase. Args: phase (str): phase name (1st etc.) or "last" Returns: covsirphy.PhaseUnit: the unit of the phase Note: When @phase is 'last' and no phases were registered, returns A phase with the start/end dates are the previous date of the first date and initial population value. """ if phase == "last": if self._units: return self._units[-1] pre_date = self.yesterday(self.first_date) return PhaseUnit(pre_date, pre_date, self.init_population) num = self.str2num(phase) try: return self._units[num] except IndexError: raise KeyError(f"{phase} phase is not registered.")
def add(self, end_date=None, days=None, population=None, model=None, tau=None, **kwargs): """ Add a past phase. Args: end_date (str): end date of the past phase, like 22Jan2020 days (int or None): the number of days to add population (int or None): population value model (covsirphy.ModelBase): ODE model tau (int or None): tau value [min], a divisor of 1440 (prioritize the previous value) kwargs: keyword arguments of model parameters Returns: covsirphy.PhaseSeries: self Note: If @population is None, the previous initial value will be used. When addition of past phases was not completed and the new phase is future phase, fill in the blank. """ last_unit = self.unit(phase="last") # Basic information start_date = self.tomorrow(last_unit.end_date) end_date = self._calc_end_date(start_date, end_date=end_date, days=days) population = self._ensure_population(population or last_unit.population) model = model or last_unit.model tau = last_unit.tau or tau if model is None: param_dict = {} else: param_dict = { k: v for (k, v) in { **last_unit.to_dict(), **kwargs }.items() if k in model.PARAMETERS } # Create PhaseUnit unit = PhaseUnit(start_date, end_date, population) # Add phase if the last date is not included if self.last_date not in unit or unit <= self.last_date: unit.set_ode(model=model, tau=tau, **param_dict) self._units.append(unit) return self # Fill in the blank of past dates filling = PhaseUnit(start_date, self.last_date, population) filling.set_ode(model=model, tau=tau, **param_dict) target = PhaseUnit(self.tomorrow(self.last_date), end_date, population) target.set_ode(model=model, tau=tau, **param_dict) # Add new phase self._units.extend([filling, target]) return self