Generating precompute tables

Here we precompute the Bragg pulses needed to simulate an SCI.

First we define functions to call mwave.integrate.gbragg() in a parallel loop with the targeted parameters. Also define a test function.

[1]:
from mwave.integrate import make_kvec, make_phi, gbragg, pops_vs_time
from mwave.precompute import write_bragg_precompute, read_bragg_precompute
import numpy as np
from scipy.interpolate import RegularGridInterpolator as RGI
from matplotlib import pyplot as plt
from tqdm import tqdm
from joblib import Parallel, delayed
import h5py

# Define output file name
fname = 'sig0.247.h5'

# Define simulation parameters
n_init = 0
n_bragg = 5
N_bloch = 10
sigma = 0.247

# Define function to perform precomputation
def precompute_gbragg(n0, nf, n_bragg, N_bloch=None):

    # Make kvec
    kvec, n0_idx, nf_idx = make_kvec(n0,nf)

    # Compute over grid
    omegas = np.linspace(0, 43, 800)
    deltas = np.linspace(-2, 2, 400) + 4*n_bragg

    # Create array to store output
    phi = np.full((len(omegas), len(deltas), len(kvec)), np.nan, dtype=np.complex128)

    # Define the grid shape
    grid_shape = [len(omegas), len(deltas)]

    # Define function to compute single Bragg pulse
    def do_gbragg(i):
        idx1, idx2 = np.unravel_index(i, grid_shape)
        if N_bloch is not None:
            sol = gbragg(kvec, make_phi(kvec, n0), 6*sigma, deltas[idx2], omegas[idx1], sigma, omega_mod=8*(N_bloch+n_bragg))
        else:
            sol = gbragg(kvec, make_phi(kvec, n0), 6*sigma, deltas[idx2], omegas[idx1], sigma)
        return sol.y[:,-1]

    # Compute function in parallel
    out = Parallel(n_jobs=-1)(delayed(do_gbragg)(i) for i in tqdm(range(np.prod(grid_shape))))

    # Put all of the output into the phi array
    for i in range(np.prod(grid_shape)):
        idx1, idx2 = np.unravel_index(i, grid_shape)
        phi[idx1, idx2, :] = out[i]

    # Write output to HDF5 file
    write_bragg_precompute(fname, phi, kvec, ((omegas, 'omegas'), (deltas, 'deltas')), n0, nf, n_bragg, N_bloch=N_bloch)

def test_precomp_grid(n0, nf, multifreq=False):
    # Load grid
    phi, kvec, grid = read_bragg_precompute(fname, n0, nf, n_bragg, N_bloch if multifreq else None)
    omegas, deltas = grid[0][0], grid[1][0]

    # Interpolate
    rgi = RGI([omegas, deltas], phi, method='cubic')

    # Create vectorized gbragg function
    def vgbragg(omegas, deltas):
        if len(omegas) != len(deltas):
            raise ValueError('omegas and deltas must have the same length')
        phi = np.full((len(omegas), len(kvec)), np.nan, dtype=np.complex128)
        for i in tqdm(range(len(omegas))):
            if multifreq:
                sol = gbragg(kvec, make_phi(kvec, n0), 6*sigma, deltas[i], omegas[i], sigma, omega_mod=8*(N_bloch+n_bragg))
            else:
                sol = gbragg(kvec, make_phi(kvec, n0), 6*sigma, deltas[i], omegas[i], sigma)
            phi[i, :] = sol.y[:,-1]

        return phi

    # Drop random points on grid
    npoints = 4000
    rng = np.random.default_rng()
    omega_pnts = rng.uniform(omegas[0], omegas[-1], npoints)
    delta_pnts = rng.uniform(deltas[0], deltas[-1], npoints)

    # Compute real value on points
    phi_actual = vgbragg(omega_pnts, delta_pnts)

    # Compute interpolated value on points
    phi_interp = rgi((omega_pnts, delta_pnts))

    # Take the difference
    phi_diff = phi_actual - phi_interp

    # Sum the error along each wavefunction
    err = np.sum(phi_diff, axis=-1)

    # Plot the real and imaginary parts of the error
    plt.figure()
    plt.scatter(omega_pnts, delta_pnts, c=np.real(err))
    cbar = plt.colorbar()
    cbar.ax.set_ylabel('re abs err')
    plt.show()

    plt.figure()
    plt.scatter(omega_pnts, delta_pnts, c=np.imag(err))
    cbar = plt.colorbar()
    cbar.ax.set_ylabel('im abs err')
    plt.show()

    # Plot histograms of the error
    plt.figure()
    plt.hist(np.real(err), range=(-2e-6,2e-6), bins=200)
    plt.xlabel('re abs err')
    plt.show()

    plt.figure()
    plt.hist(np.imag(err), range=(-2e-6,2e-6), bins=200)
    plt.xlabel('im abs err')
    plt.show()

    # Print the error mean and std
    print(f'Re[error] mean={np.mean(np.real(err))}, error std={np.std(np.real(err))}')
    print(f'Im[error] mean={np.mean(np.imag(err))}, error std={np.std(np.imag(err))}')

Precompute the single frequency Bragg pulses

[2]:
n0, nf = n_init, n_init + n_bragg
# Uncomment the below code when you want to perform the computation and/or test the precompute accuracy
# precompute_gbragg(n0, nf, n_bragg)
# test_precomp_grid(n0,nf)
[3]:
n0, nf = n_init + n_bragg, n_init
# Uncomment the below code when you want to perform the computation and/or test the precompute accuracy
# precompute_gbragg(n0, nf, n_bragg)
# test_precomp_grid(n0,nf)

Precompute the multifrequency Bragg pulses

[4]:
n0, nf = -N_bloch, -N_bloch-n_bragg
# Uncomment the below code when you want to perform the computation and/or test the precompute accuracy
# precompute_gbragg(n0, nf, n_bragg, N_bloch=N_bloch)
# test_precomp_grid(n0,nf,multifreq=True)
[5]:
n0, nf = n_bragg+N_bloch, 2*n_bragg+N_bloch
# Uncomment the below code when you want to perform the computation and/or test the precompute accuracy
# precompute_gbragg(n0, nf, n_bragg, N_bloch=N_bloch)
# test_precomp_grid(n0,nf,multifreq=True)
[6]:
n0, nf = 2*n_bragg+N_bloch, n_bragg+N_bloch
# Uncomment the below code when you want to perform the computation and/or test the precompute accuracy
# precompute_gbragg(n0, nf, n_bragg, N_bloch=N_bloch)
# test_precomp_grid(n0,nf,multifreq=True)
[7]:
n0, nf = -n_bragg-N_bloch, -N_bloch
# Uncomment the below code when you want to perform the computation and/or test the precompute accuracy
# precompute_gbragg(n0, nf, n_bragg, N_bloch=N_bloch)
# test_precomp_grid(n0,nf,multifreq=True)