Ejemplo n.º 1
0
def covid_psi(I, K_F, K_S, delta=0, EPS=1E-15, reflect=False):
    """
    COVID delay ratio function: psi(t, delta T) = (I * K_F)(t + delta T) / (I * K_S)(t),
    where * means convolution.
    
    Convolution is calculated here via discrete convolution.
    
    Args:
        I     : cumulative infections distribution
        K_F   : sampled delay kernel function in the numerator
        K_S   : sampled delay kernel function in the denominator
        delta : numerator time shift [indices of t]
    Returns:
        Ratio function
    """
    N = len(I)

    # Handle the long tail by zero padding.
    # Kernel array should be a long enough, because it provides reference.
    I_zp = tools.zeropad_after(x=I, reference=K_F)

    # Compute via discrete convolutions
    # Kernel normalization to sum=1 guarantees count normalization
    IKF = tools.conv_(I_zp, K_F / np.sum(K_F))[0:N]
    IKS = tools.conv_(I_zp, K_S / np.sum(K_S))[0:N]

    # Numerator is read out at t + deltaT
    psi = IKF[delta:] / np.maximum(IKS[0:N - delta], EPS)
    out = np.zeros(N)
    out[0:len(psi)] = psi

    if reflect:  # Continue with the boundary value
        out[len(psi):] = psi[-1]
    return out
Ejemplo n.º 2
0
# ** Normalize discretized kernel to sum to one
# => count conservation with discrete convolutions **
kernel_REV = copy.deepcopy(kernel_C_REV)
kernel_REV /= np.sum(kernel_REV)

# Plot kernels
fig, ax = plt.subplots()
plt.plot(t, kernel_C)
plt.plot(t, kernel_C_REV)
#plt.show()

# ------------------------------------------------------------------------
# Discrete convolution

# Seroconversion counts
dI_conv = tools.conv_(dI, kernel)

# Seroreversion decay of converted counts
dI_conv_REV = tools.conv_(dI_conv, kernel_REV)

# Cumulative sum
I_S = tools.conv_(I, kernel)
I_RS = tools.conv_(I_S, kernel_REV)

# Observed counts
I_tilde = I_S - I_RS

# ------------------------------------------------------------------------
# Plots

XLIM = 300
Ejemplo n.º 3
0
# ========================================================================
# Capture ratios

# True IFR value
IFR_true = 0.5 * 1e-2

# Synthetic input
I = 1 / (1 + np.exp(-0.25*(t-20)))

# ** Unit normalized kernel for a discrete convolution **
KF_kernel = copy.deepcopy(K['F']);
KF_kernel /= np.sum(KF_kernel)

# Discrete convolution
F = IFR_true * tools.conv_(I, KF_kernel)

# -----------------------------------------

fig,ax = plt.subplots()
plt.plot(t, I, label='$I$')
plt.plot(t, F / IFR_true, label='$(I \\ast K_F)(t) / \\langle IFR \\rangle$')
plt.xlabel('$t$ [days]')
plt.title('diagnostics')
plt.ylim([0, 1.2])
plt.xlim([0, None])
plt.legend()

os.makedirs(f'{plotfolder}', exist_ok = True)
plt.savefig(f'{plotfolder}/conv_diagnostic.pdf', bbox_inches='tight')
Ejemplo n.º 4
0
def covid_deconvolve(Cdiff,
                     Fdiff,
                     kp,
                     t=None,
                     mode='C',
                     alpha=1,
                     BS=1000,
                     TSPAN=200,
                     data_poisson=True,
                     kernel_syst=True):
    """
    COVID time-series deconvolution.
    
    Args:
        Cdiff:         observed daily cases array
        Fdiff:         observed daily fatalities array
        kp:            kernel parameters dictionary
        t:             time points array, default None (constructed automatically)
        mode:          use 'C' for invertion based on cases or 'F' for fatality based
        alpha:         regularization strength for the deconvolution
        BS:            number of bootstrap/MC samples
        TSPAN:         minimum convolution domain span of the kernel
        data_poisson:  measurement statistical bootstrap fluctuation on / off
        kernel_syst:   kernel systematic uncertainties on / off

    Returns:
        Id_hat:        daily infections estimate obtained via deconvolution
        Fd_hat:        daily fatalities from push-forward of the estimate: (K_F * dI/dt)(t)
    """
    print(__name__ + '.covid_deconvolve: Running ...')

    if len(Cdiff) != len(Fdiff):
        raise Exception('covid_deconvolve: input C length != F length')

    if t is None:
        # Construct convolution domain
        t = np.arange(0, len(Fdiff) + TSPAN)

    # Monte Carlo re-sampling of perturbed kernels
    Id_hat = np.zeros((BS, len(Cdiff)))
    Fd_hat = np.zeros((BS, len(Fdiff)))

    for i in tqdm(range(BS)):

        # Generate new kernel
        if kernel_syst:
            K = covid_kernels(t=t,
                              mu=kp['mu'],
                              sigma=kp['sigma'],
                              mu_std=kp['mu_std'],
                              sigma_std=kp['sigma_std'])
        else:
            K = covid_kernels(t=t,
                              mu=kp['mu'],
                              sigma=kp['sigma'],
                              mu_std=None,
                              sigma_std=None)

        # Deconvolution
        if mode == 'C':
            if data_poisson:
                y = np.random.poisson(Cdiff)  # Poisson fluctuate measurement
            else:
                y = Cdiff

            y_zp = tools.zeropad_after(
                x=y, reference=K['C'])  # Take care of the tail unrolling
            output, _, _ = tools.nneg_tikhonov_deconv(y=y_zp,
                                                      kernel=K['C'],
                                                      alpha=alpha,
                                                      mass_conserve=True)
            Id_hat[i, :] = output[0:len(y)]

        elif mode == 'F':
            if data_poisson:
                y = np.random.poisson(Fdiff)  # Poisson fluctuate measurement
            else:
                y = Fdiff

            y_zp = tools.zeropad_after(
                x=y, reference=K['F'])  # Take care the tail unrolling
            output, _, _ = tools.nneg_tikhonov_deconv(y=y_zp,
                                                      kernel=K['F'],
                                                      alpha=alpha,
                                                      mass_conserve=True)
            Id_hat[i, :] = output[0:len(y)]

        else:
            raise Except(__name__ + '.covid_deconvolve: Error: unknown mode.')

        # Push-forward, take care of the tail unrolling by zeropad
        N = len(Id_hat[i, :])
        I_zp = tools.zeropad_after(x=Id_hat[i, :], reference=K['F'])
        F_diff_hat = tools.conv_(I_zp, K['F'])[0:N]

        # Normalization for visualization and comparisons
        # (absolute normalization not obtained by convolution alone)
        if np.sum(Id_hat[i, :]) > 0:
            Id_hat[i, :] /= np.sum(Id_hat[i, :])

        if np.sum(F_diff_hat) > 0:
            F_diff_hat /= np.sum(F_diff_hat)

        Id_hat[i, :] *= np.sum(Cdiff)
        F_diff_hat *= np.sum(Fdiff)
        Fd_hat[i, :] = F_diff_hat

    return Id_hat, Fd_hat
Ejemplo n.º 5
0
qval = [1.35, 1.15, 0.95, 0.75, 0.5, 0.25]
k    = 0

for key in tqdm(I.keys()):

	fig,ax = plt.subplots()

	for q in qval:
		
		# Unit normalization of the kernel for discrete convolution
		kernelfunc = copy.deepcopy(K['F']);
		kernelfunc /= np.sum(kernelfunc)

		# Discrete convolution
		I_del      = tools.conv_(I[key], kernelfunc)

		print(f'max(I) = {np.max(I[key])}, max(I_del) = {np.max(I_del)}')

		y = tools.find_delay(t=t, F=I[key], Fd=I_del, rho=q)
		plt.plot(t, y, label=f'$\\epsilon = {q:0.2f}$', color=cm.RdBu(1-q/np.max(qval)))

	plt.legend(loc=1)
	plt.ylim([0,None])
	plt.xlim([0,100])
	plt.xticks(np.arange(0,110,10))
	plt.ylabel('$\\Delta t$ [days] | $\\frac{(K \\ast I)(t + \\Delta t)}{I(t)} = \\epsilon$')
	plt.xlabel('$t$ [days]')
	plt.title(f'${key}$')