def get_version_pars(version, verbose=True): ''' Function for loading parameters from the specified version. Parameters will be loaded for Covasim 'as at' the requested version i.e. the most recent set of parameters that is <= the requested version. Available parameter values are stored in the regression folder. If parameters are available for versions 1.3, and 1.4, then this function will return the following - If parameters for version '1.3' are requested, parameters will be returned from '1.3' - If parameters for version '1.3.5' are requested, parameters will be returned from '1.3', since Covasim at version 1.3.5 would have been using the parameters defined at version 1.3. - If parameters for version '1.4' are requested, parameters will be returned from '1.4' Args: version (str): the version to load parameters from Returns: Dictionary of parameters from that version ''' # Construct a sorted list of available parameters based on the files in the regression folder regression_folder = sc.thisdir(__file__, 'regression', aspath=True) available_versions = [ x.stem.replace('pars_v', '') for x in regression_folder.iterdir() if x.suffix == '.json' ] available_versions = sorted(available_versions, key=LooseVersion) # Find the highest parameter version that is <= the requested version version_comparison = [ sc.compareversions(version, v) >= 0 for v in available_versions ] try: target_version = available_versions[sc.findlast(version_comparison)] except IndexError: errormsg = f"Could not find a parameter version that was less than or equal to '{version}'. Available versions are {available_versions}" raise ValueError(errormsg) # Load the parameters pars = sc.loadjson(filename=regression_folder / f'pars_v{target_version}.json', folder=regression_folder) if verbose: print(f'Loaded parameters from {target_version}') return pars
def plot(): fig = pl.figure(num='Fig. 2: Transmission dynamics', figsize=(20,14)) piey, tsy, r3y = 0.68, 0.50, 0.07 piedx, tsdx, r3dx = 0.2, 0.9, 0.25 piedy, tsdy, r3dy = 0.2, 0.47, 0.35 pie1x, pie2x = 0.12, 0.65 tsx = 0.07 dispx, cumx, sympx = tsx, 0.33+tsx, 0.66+tsx ts_ax = pl.axes([tsx, tsy, tsdx, tsdy]) pie_ax1 = pl.axes([pie1x, piey, piedx, piedy]) pie_ax2 = pl.axes([pie2x, piey, piedx, piedy]) symp_ax = pl.axes([sympx, r3y, r3dx, r3dy]) disp_ax = pl.axes([dispx, r3y, r3dx, r3dy]) cum_ax = pl.axes([cumx, r3y, r3dx, r3dy]) off = 0.06 txtdispx, txtcumx, txtsympx = dispx-off, cumx-off, sympx-off+0.02 tsytxt = tsy+tsdy r3ytxt = r3y+r3dy labelsize = 40-wf pl.figtext(txtdispx, tsytxt, 'a', fontsize=labelsize) pl.figtext(txtdispx, r3ytxt, 'b', fontsize=labelsize) pl.figtext(txtcumx, r3ytxt, 'c', fontsize=labelsize) pl.figtext(txtsympx, r3ytxt, 'd', fontsize=labelsize) #%% Fig. 2A -- Time series plot layer_keys = list(sim.layer_keys()) layer_mapping = {k:i for i,k in enumerate(layer_keys)} n_layers = len(layer_keys) colors = sc.gridcolors(n_layers) layer_counts = np.zeros((sim.npts, n_layers)) for source_ind, target_ind in tt.count_transmissions(): dd = tt.detailed[target_ind] date = dd['date'] layer_num = layer_mapping[dd['layer']] layer_counts[date, layer_num] += sim.rescale_vec[date] mar12 = cv.date('2020-03-12') mar23 = cv.date('2020-03-23') mar12d = sim.day(mar12) mar23d = sim.day(mar23) labels = ['Household', 'School', 'Workplace', 'Community', 'LTCF'] for l in range(n_layers): ts_ax.plot(sim.datevec, layer_counts[:,l], c=colors[l], lw=3, label=labels[l]) sc.setylim(ax=ts_ax) sc.boxoff(ax=ts_ax) ts_ax.set_ylabel('Transmissions per day') ts_ax.set_xlim([sc.readdate('2020-01-18'), sc.readdate('2020-06-09')]) ts_ax.xaxis.set_major_formatter(mdates.DateFormatter('%b-%d')) ts_ax.set_xticks([sim.date(d, as_date=True) for d in np.arange(0, sim.day('2020-06-09'), 14)]) ts_ax.legend(frameon=False, bbox_to_anchor=(0.85,0.1)) color = [0.2, 0.2, 0.2] ts_ax.axvline(mar12, c=color, linestyle='--', alpha=0.4, lw=3) ts_ax.axvline(mar23, c=color, linestyle='--', alpha=0.4, lw=3) yl = ts_ax.get_ylim() labely = yl[1]*1.015 ts_ax.text(mar12, labely, 'Schools close ', color=color, alpha=0.9, style='italic', horizontalalignment='center') ts_ax.text(mar23, labely, ' Stay-at-home', color=color, alpha=0.9, style='italic', horizontalalignment='center') #%% Fig. 2A inset -- Pie charts pre_counts = layer_counts[0:mar12d, :].sum(axis=0) post_counts = layer_counts[mar23d:, :].sum(axis=0) pre_counts = pre_counts/pre_counts.sum()*100 post_counts = post_counts/post_counts.sum()*100 lpre = [ f'Household\n{pre_counts[0]:0.1f}%', f'School\n{pre_counts[1]:0.1f}% ', f'Workplace\n{pre_counts[2]:0.1f}% ', f'Community\n{pre_counts[3]:0.1f}%', f'LTCF\n{pre_counts[4]:0.1f}%', ] lpost = [ f'Household\n{post_counts[0]:0.1f}%', f'School\n{post_counts[1]:0.1f}%', f'Workplace\n{post_counts[2]:0.1f}%', f'Community\n{post_counts[3]:0.1f}%', f'LTCF\n{post_counts[4]:0.1f}%', ] pie_ax1.pie(pre_counts, colors=colors, labels=lpre, **pieargs) pie_ax2.pie(post_counts, colors=colors, labels=lpost, **pieargs) pie_ax1.text(0, 1.75, 'Transmissions by layer\nbefore schools closed', style='italic', horizontalalignment='center') pie_ax2.text(0, 1.75, 'Transmissions by layer\nafter stay-at-home', style='italic', horizontalalignment='center') #%% Fig. 2B -- histogram by overdispersion # Process targets n_targets = tt.count_targets(end_day=mar12) # Handle bins max_infections = n_targets.max() edges = np.arange(0, max_infections+2) # Analysis counts = np.histogram(n_targets, edges)[0] bins = edges[:-1] # Remove last bin since it's an edge norm_counts = counts/counts.sum() raw_counts = counts*bins total_counts = raw_counts/raw_counts.sum()*100 n_bins = len(bins) index = np.linspace(0, 100, len(n_targets)) sorted_arr = np.sort(n_targets) sorted_sum = np.cumsum(sorted_arr) sorted_sum = sorted_sum/sorted_sum.max()*100 change_inds = sc.findinds(np.diff(sorted_arr) != 0) pl.set_cmap('Spectral_r') sscolors = sc.vectocolor(n_bins) width = 1.0 for i in range(n_bins): disp_ax.bar(bins[i], total_counts[i], width=width, facecolor=sscolors[i]) disp_ax.set_xlabel('Number of transmissions per case') disp_ax.set_ylabel('Proportion of transmissions (%)') sc.boxoff() disp_ax.set_xlim([0.5, 32.5]) disp_ax.set_xticks(np.arange(0, 32.5, 4)) sc.boxoff(ax=disp_ax) dpie_ax = pl.axes([dispx+0.05, 0.20, 0.2, 0.2]) trans1 = total_counts[1:3].sum() trans2 = total_counts[3:5].sum() trans3 = total_counts[5:8].sum() trans4 = total_counts[8:].sum() labels = [ f'1-2:\n{trans1:0.0f}%', f' 3-4:\n {trans2:0.0f}%', f'5-7: \n{trans3:0.0f}%\n', f'>7: \n{trans4:0.0f}%\n', ] dpie_args = sc.mergedicts(pieargs, dict(labeldistance=1.2)) # Slightly smaller label distance dpie_ax.pie([trans1, trans2, trans3, trans4], labels=labels, colors=sscolors[[0,4,7,12]], **dpie_args) #%% Fig. 2C -- cumulative distribution function rev_ind = 100 - index n_change_inds = len(change_inds) change_bins = bins[counts>0][1:] for i in range(n_change_inds): ib = int(change_bins[i]) ci = change_inds[i] ici = index[ci] sci = sorted_sum[ci] color = sscolors[ib] if i>0: cim1 = change_inds[i-1] icim1 = index[cim1] scim1 = sorted_sum[cim1] cum_ax.plot([icim1, ici], [scim1, sci], lw=4, c=color) cum_ax.scatter([ici], [sci], s=150, zorder=50-i, c=[color], edgecolor='w', linewidth=0.2) if ib<=6 or ib in [8, 10, 25]: xoff = 5 - 2*(ib==1) + 3*(ib>=10) + 1*(ib>=20) yoff = 2*(ib==1) cum_ax.text(ici-xoff, sci+yoff, ib, fontsize=18-wf, color=color) cum_ax.set_xlabel('Proportion of primary infections (%)') cum_ax.set_ylabel('Proportion of transmissions (%)') xmin = -2 ymin = -2 cum_ax.set_xlim([xmin, 102]) cum_ax.set_ylim([ymin, 102]) sc.boxoff(ax=cum_ax) # Draw horizontal lines and annotations ancol1 = [0.2, 0.2, 0.2] ancol2 = sscolors[0] ancol3 = sscolors[6] i01 = sc.findlast(sorted_sum==0) i20 = sc.findlast(sorted_sum<=20) i50 = sc.findlast(sorted_sum<=50) cum_ax.plot([xmin, index[i01]], [0, 0], '--', lw=2, c=ancol1) cum_ax.plot([xmin, index[i20], index[i20]], [20, 20, ymin], '--', lw=2, c=ancol2) cum_ax.plot([xmin, index[i50], index[i50]], [50, 50, ymin], '--', lw=2, c=ancol3) # Compute mean number of transmissions for 80% and 50% thresholds q80 = sc.findfirst(np.cumsum(total_counts)>20) # Count corresponding to 80% of cumulative infections (100-80) q50 = sc.findfirst(np.cumsum(total_counts)>50) # Count corresponding to 50% of cumulative infections n80, n50 = [sum(bins[q:]*norm_counts[q:]/norm_counts[q:].sum()) for q in [q80, q50]] # Plot annotations kw = dict(bbox=dict(facecolor='w', alpha=0.9, lw=0), fontsize=20-wf) cum_ax.text(2, 3, f'{index[i01]:0.0f}% of infections\ndo not transmit', c=ancol1, **kw) cum_ax.text(8, 23, f'{rev_ind[i20]:0.0f}% of infections cause\n80% of transmissions\n(mean: {n80:0.1f} per infection)', c=ancol2, **kw) cum_ax.text(14, 53, f'{rev_ind[i50]:0.0f}% of infections cause\n50% of transmissions\n(mean: {n50:0.1f} per infection)', c=ancol3, **kw) #%% Fig. 2D -- histogram by date of symptom onset # Calculate asymp_count = 0 symp_counts = {} minind = -5 maxind = 15 for _, target_ind in tt.transmissions: dd = tt.detailed[target_ind] date = dd['date'] delta = sim.rescale_vec[date] # Increment counts by this much if dd['s']: if tt.detailed[dd['source']]['date'] <= date: # Skip dynamical scaling reinfections sdate = dd['s']['date_symptomatic'] if np.isnan(sdate): asymp_count += delta else: ind = int(date - sdate) if ind not in symp_counts: symp_counts[ind] = 0 symp_counts[ind] += delta # Convert to an array xax = np.arange(minind-1, maxind+1) sympcounts = np.zeros(len(xax)) for i,val in symp_counts.items(): if i<minind: ind = 0 elif i>maxind: ind = -1 else: ind = sc.findinds(xax==i)[0] sympcounts[ind] += val # Plot total_count = asymp_count + sympcounts.sum() sympcounts = sympcounts/total_count*100 presymp = sc.findinds(xax<=0)[-1] colors = ['#eed15b', '#ee943a', '#c3211a'] asymp_frac = asymp_count/total_count*100 pre_frac = sympcounts[:presymp].sum() symp_frac = sympcounts[presymp:].sum() symp_ax.bar(xax[0]-2, asymp_frac, label='Asymptomatic', color=colors[0]) symp_ax.bar(xax[:presymp], sympcounts[:presymp], label='Presymptomatic', color=colors[1]) symp_ax.bar(xax[presymp:], sympcounts[presymp:], label='Symptomatic', color=colors[2]) symp_ax.set_xlabel('Days since symptom onset') symp_ax.set_ylabel('Proportion of transmissions (%)') symp_ax.set_xticks([minind-3, 0, 5, 10, maxind]) symp_ax.set_xticklabels(['Asymp.', '0', '5', '10', f'>{maxind}']) sc.boxoff(ax=symp_ax) spie_ax = pl.axes([sympx+0.05, 0.20, 0.2, 0.2]) labels = [f'Asymp-\ntomatic\n{asymp_frac:0.0f}%', f' Presymp-\n tomatic\n {pre_frac:0.0f}%', f'Symp-\ntomatic\n{symp_frac:0.0f}%'] spie_ax.pie([asymp_frac, pre_frac, symp_frac], labels=labels, colors=colors, **pieargs) return fig
def apply(self, sim): ''' Perform vaccination ''' t = sim.t if t < self.start_day: return elif self.end_day is not None and t > self.end_day: return # Check that there are still vaccines rel_t = t - self.start_day if rel_t < len(self.daily_vaccines): n_vaccines = sc.randround( self.daily_vaccines[rel_t] / sim.rescale_vec[t] ) # Correct for scaling that may be applied by rounding to the nearest number of tests if not (n_vaccines and np.isfinite(n_vaccines) ): # If there are no doses today, abort early return else: if sim.rescale_vec[t] != sim['pop_scale']: raise RuntimeError('bad rescale time') else: return vacc_probs = np.ones( sim.n ) # Begin by assigning equal vaccine weight (converted to a probability) to everyone # age priority ppl = sim.people priority_stage = sc.findlast(self.priority_days <= t) not_target_inds = cv.true( np.logical_or(ppl.age < self.age_priority[priority_stage], ppl.dead)) vacc_probs[not_target_inds] = 0.0 # Handle scheduling # first dose: vacc_probs[self.vaccinations == 0] *= self.dose_priority[0] # time between first and second dose: no_dose = [ sim.t < (d[0] + self.delay) if len(d) > 0 else False for d in self.vaccination_dates ] vacc_probs[no_dose] *= 0 # time available for second dose: second_dose = [ sim.t >= (d[0] + self.delay) if len(d) > 0 else False for d in self.vaccination_dates ] vacc_probs[second_dose] *= self.dose_priority[1] # Don't give dose 2 people who have had more than 1 vacc_inds = cvu.true(self.vaccinations > 1) vacc_probs[vacc_inds] = 0.0 # Now choose who gets vaccinated and vaccinate them n_vaccines = min( n_vaccines, (vacc_probs != 0).sum() ) # Don't try to vaccinate more people than have nonzero vaccination probability all_vacc_inds = cvu.choose_w(probs=vacc_probs, n=n_vaccines, unique=True) # Choose who actually tests sim.results['new_doses'][t] += len(all_vacc_inds) # Did the vaccine take? vacc_take_inds = all_vacc_inds[np.logical_or( cvu.n_binomial(self.take_prob, len(all_vacc_inds)) & (self.vaccinations[all_vacc_inds] == 0), self.vaccine_take[all_vacc_inds] & (self.vaccinations[all_vacc_inds] == 1))] self.vaccine_take[vacc_take_inds] = True # Calculate the effect per person vacc_doses = self.vaccinations[ vacc_take_inds] # Calculate current doses eff_doses = np.minimum(vacc_doses, len(self.cumulative) - 1) # Convert to a valid index vacc_eff = self.cumulative[ eff_doses] # Pull out corresponding effect sizes rel_trans_eff = (1.0 - vacc_eff) + vacc_eff * self.rel_trans rel_symp_eff = (1.0 - vacc_eff) + vacc_eff * self.rel_symp # Apply the vaccine to people # schedule dose effect for intv in sim['interventions']: if isinstance(intv, dose_scheduler): schedule = { 'inds': vacc_take_inds, 'rel_trans': self.orig_rel_trans[vacc_take_inds] * rel_trans_eff, 'symp_prob': self.orig_symp_prob[vacc_take_inds] * rel_symp_eff } schedule_day = sim.t + self.dose_delay if schedule_day not in intv.scheduler: intv.scheduler.update({schedule_day: [schedule]}) else: intv.scheduler[schedule_day].append(schedule) self.vaccinations[all_vacc_inds] += 1 for v_ind in all_vacc_inds: self.vaccination_dates[v_ind].append(sim.t) return