Пример #1
0
    def compute_fom_and_gradient_hz(self, omega_idx, device_permittivity):
        omega = self.omega_values[omega_idx]

        fwd_Ex, fwd_Ey, fwd_Hz = self.compute_forward_fields_hz(
            omega, device_permittivity)

        fom = (1. / self.hz_transmission_normalization[omega_idx]
               ) * self.compute_fom_from_fields_hz(
                   fwd_Ex, fwd_Ey, fwd_Hz,
                   self.src_scattered_Ex_by_omega[omega_idx, :],
                   self.src_scattered_Ey_by_omega[omega_idx, :],
                   self.src_scattered_Hz_by_omega[omega_idx, :])

        scattered_Ex = (
            fwd_Ex[self.transmission_x_bounds[0]:self.transmission_x_bounds[1],
                   self.transmission_y] -
            self.src_scattered_Ex_by_omega[omega_idx, :])

        adj_source = np.zeros(
            (self.simulation_width_voxels, self.simulation_height_voxels),
            dtype=np.complex)
        adj_source[self.transmission_x_bounds[0]:self.transmission_x_bounds[1],
                   self.transmission_y] = -np.conj(scattered_Ex)

        simulation = ceviche.fdfd_hz(omega, self.mesh_size_m,
                                     self.rel_eps_simulation,
                                     [self.pml_voxels, self.pml_voxels])
        adj_Ex, adj_Ey, adj_Hz = simulation.solve(adj_source)

        gradient = (1. / self.hz_transmission_normalization[omega_idx]
                    ) * 2 * np.real(omega * eps_nought *
                                    (fwd_Ex * adj_Ex + fwd_Ey * adj_Ey) / (1j))

        return fom, gradient
Пример #2
0
    def test_Hz_reverse(self):

        print('\ttesting reverse-mode Hz in FDFD')

        f = fdfd_hz(self.omega, self.dL, self.eps_r, self.pml)

        def J_fdfd(eps_arr):

            eps_r = eps_arr.reshape((self.Nx, self.Ny))

            # set the permittivity
            f.eps_r = eps_r

            # set the source amplitude to the permittivity at that point
            Ex, Ey, Hz = f.solve(eps_r * self.source_hz)

            return npa.sum(npa.square(npa.abs(Hz))) \
                 + npa.sum(npa.square(npa.abs(Ex))) \
                 + npa.sum(npa.square(npa.abs(Ey)))

        grad_autograd_rev = jacobian(J_fdfd, mode='reverse')(self.eps_arr)
        grad_numerical = jacobian(J_fdfd, mode='numerical')(self.eps_arr)

        if VERBOSE:
            print('\tobjective function value: ', J_fdfd(self.eps_arr))
            print('\tgrad (auto):  \n\t\t', grad_autograd_rev)
            print('\tgrad (num):   \n\t\t\n', grad_numerical)

        self.check_gradient_error(grad_numerical, grad_autograd_rev)
Пример #3
0
    def test_Hz_forward(self):

        print('\ttesting forward-mode Hz in FDFD')

        f = fdfd_hz(self.omega, self.dL, self.eps_r, self.pml)

        def J_fdfd(c):

            # set the permittivity
            f.eps_r = c * self.eps_r

            # set the source amplitude to the permittivity at that point
            Ex, Ey, Hz = f.solve(c * self.eps_r * self.source_hz)

            return npa.square(npa.abs(Hz)) \
                 + npa.square(npa.abs(Ex)) \
                 + npa.square(npa.abs(Ey))

        grad_autograd_for = jacobian(J_fdfd, mode='forward')(1.0)
        grad_numerical = jacobian(J_fdfd, mode='numerical')(1.0)

        if VERBOSE:
            print('\tobjective function value: ', J_fdfd(1.0))
            print('\tgrad (auto):  \n\t\t', grad_autograd_for)
            print('\tgrad (num):   \n\t\t', grad_numerical)

        self.check_gradient_error(grad_numerical, grad_autograd_for)
Пример #4
0
    def test_Hz(self):
        print('\ttesting Hz')

        F = fdfd_hz(self.omega, self.dL, self.eps_r, self.npml)
        Ex, Ey, Hz = F.solve(self.source)
        Hz_max = np.max(np.abs(Hz))
        plt.imshow(np.real(Hz), cmap='RdBu', vmin=-Hz_max / 5, vmax=Hz_max / 5)
        plt.show()
Пример #5
0
    def test_Hz(self):
        print('\ttesting Hz')

        F = fdfd_hz(self.omega, self.dL, self.eps_r, self.npml)
        Ex, Ey, Hz = F.solve(self.source)
        plot_component = Hz
        field_max = np.max(np.abs(plot_component))
        plt.imshow(np.real(plot_component),
                   cmap='RdBu',
                   vmin=-field_max / 5,
                   vmax=field_max / 5)
        plt.show()
Пример #6
0
    def compute_forward_fields_hz(self,
                                  omega,
                                  device_permittivity,
                                  normalization_permittivity=False):
        self.rel_eps_simulation[self.device_width_start:self.device_width_end,
                                self.device_height_start:self.
                                device_height_end] = device_permittivity

        choose_permittivity = self.rel_eps_simulation

        if normalization_permittivity:
            choose_permittivity = np.ones(self.rel_eps_simulation.shape)

        simulation = ceviche.fdfd_hz(omega, self.mesh_size_m,
                                     choose_permittivity,
                                     [self.pml_voxels, self.pml_voxels])
        fwd_Ex, fwd_Ey, fwd_Hz = simulation.solve(self.fwd_source_hz)

        return fwd_Ex, fwd_Ey, fwd_Hz
Пример #7
0
def data_generation(full_pattern: np.array) -> np.array:

    # Grating pattern
    eps_r[space_x + pml_x:space_x + pml_x + device_pxls_x,
          space_y + pml_y:space_y + pml_y + device_pxls_y] = full_pattern
    #ceviche.viz.abs(eps_r[:, int(2431.25/dL):int(2800/dL)], cbar=True)
    #ceviche.viz.abs(np.hstack((np.ones([256,5])*2.1025,full_pattern[0,5,5])), cbar=True)
    #ceviche.viz.abs(np.hstack((np.ones([int(1600/dL),int(31.25/dL)])*2.1025,full_pattern[0,5,5])), cbar=True)

    # Set up the FDFD simulation for TM
    F = fdfd_hz(omega, dL[0] * 1e-9, eps_r, npml)

    # Source
    source_amp = 64e9 / dL[0] / dL[1]
    random_source = np.zeros(grid_shape, dtype=complex)
    k0 = 2 * np.pi / wavelength
    n_angles = 4  # number of different angles in each line source

    #bottom source
    bottom_source_loc_y = pml_y + int(space_y / 2)
    source_amp_x = np.zeros(int(1 / 2 * Nx), dtype=complex)
    for j in range(n_angles):
        angle_deg = random.randint(-90, 90)

        angle_rad = angle_deg * np.pi / 180
        # Compute the wave vector
        kx = k0 * np.cos(angle_rad)
        ky = k0 * np.sin(angle_rad)

        # Get an array of the x positions across the simulation domain
        Lx = Nx * dL[0] * 1e-9
        x_vec = np.linspace(-Lx / 4, Lx / 4, int(1 / 2 * Nx))

        # Make a new source where source[x] ~ exp(i * kx * x) to simulate an angle
        phase = 2 * np.pi * random.random()
        source_amp_x += np.exp(1j * kx * x_vec + phase)

    source_amp_x *= 1 / n_angles
    # print("shapes: ", random_source[int(1/4*Nx):int(3/4*Nx), bottom_source_loc_y].shape, source_amp_x.shape)
    random_source[int(1 / 4 * Nx):int(3 / 4 * Nx),
                  bottom_source_loc_y] = source_amp * source_amp_x

    #top source
    top_source_loc_y = Ny - 1 - pml_y - int(space_y / 2)
    source_amp_x = np.zeros(int(1 / 2 * Nx), dtype=complex)
    for j in range(n_angles):
        angle_deg = random.randint(-90, 90)

        angle_rad = angle_deg * np.pi / 180
        # Compute the wave vector
        kx = k0 * np.cos(angle_rad)
        ky = k0 * np.sin(angle_rad)

        # Get an array of the x positions across the simulation domain
        Lx = Nx * dL[0] * 1e-9
        x_vec = np.linspace(-Lx / 4, Lx / 4, int(1 / 2 * Nx))

        # Make a new source where source[x] ~ exp(i * kx * x) to simulate an angle
        phase = 2 * np.pi * random.random()
        source_amp_x += np.exp(1j * kx * x_vec + phase)

    source_amp_x *= 1 / n_angles
    random_source[int(1 / 4 * Nx):int(3 / 4 * Nx),
                  top_source_loc_y] = source_amp * source_amp_x

    #left source
    left_source_loc_x = pml_x + int(space_x / 2)
    source_amp_y = np.zeros(int(1 / 2 * Ny), dtype=complex)
    for j in range(n_angles):
        angle_deg = random.randint(-90, 90)

        angle_rad = angle_deg * np.pi / 180
        # Compute the wave vector
        kx = k0 * np.cos(angle_rad)
        ky = k0 * np.sin(angle_rad)

        # Get an array of the x positions across the simulation domain
        Ly = Ny * dL[1] * 1e-9
        y_vec = np.linspace(-Ly / 4, Ly / 4, int(1 / 2 * Ny))

        # Make a new source where source[x] ~ exp(i * kx * x) to simulate an angle
        phase = 2 * np.pi * random.random()
        source_amp_y += np.exp(1j * ky * y_vec + phase)

    source_amp_y *= 1 / n_angles
    random_source[left_source_loc_x,
                  int(1 / 4 * Ny):int(3 / 4 * Ny)] = source_amp * source_amp_y

    #right source
    right_source_loc_x = Nx - 1 - pml_x - int(space_x / 2)
    source_amp_y = np.zeros(int(1 / 2 * Ny), dtype=complex)
    for j in range(n_angles):
        angle_deg = random.randint(-90, 90)

        angle_rad = angle_deg * np.pi / 180
        # Compute the wave vector
        kx = k0 * np.cos(angle_rad)
        ky = k0 * np.sin(angle_rad)

        # Get an array of the x positions across the simulation domain
        Ly = Ny * dL[1] * 1e-9
        y_vec = np.linspace(-Ly / 4, Ly / 4, int(1 / 2 * Ny))

        # Make a new source where source[x] ~ exp(i * kx * x) to simulate an angle
        phase = 2 * np.pi * random.random()
        source_amp_y += np.exp(1j * ky * y_vec + phase)

    source_amp_y *= 1 / n_angles
    random_source[right_source_loc_x,
                  int(1 / 4 * Ny):int(3 / 4 * Ny)] = source_amp * source_amp_y

    # Solve the FDFD simulation for the fields, offset the phase such that Ex has 0 phase at the center of the bottom row of the window
    Ex_forward, Ey_forward, Hz_forward = F.solve(random_source)
    #ceviche.viz.real(Ex_forward[:, int(2400/dL):int(2800/dL)], outline = eps_r[:, int(2400/dL):int(2800/dL)], cbar = True)
    #ceviche.viz.real(Hz_out_forward, outline = np.hstack((np.ones([int(1600/dL),int(31.25/dL)])*2.1025,full_pattern[0,5,5])), cbar = True)

    return eps_r, Hz_forward, Ex_forward, Ey_forward, random_source
Пример #8
0
# make design region
box_region = np.zeros((Nx, Ny))
box_region[npml + 2 * spc:npml + 2 * spc + int(L / dL),
           npml + 2 * spc:npml + 2 * spc + int(L / dL)] = 1

# make the accelration probe
probe = np.zeros((Nx, Ny), dtype=np.complex128)
probe[-npml - spc, Ny // 2] = 1

# plot the probe through channel
if PLOT:
    plt.imshow(np.abs(imarr(probe + box_region + source)))
    plt.show()

# vacuum test, get normalization
F = fdfd_hz(omega, dL, eps_r, [npml, npml])
Ex, Ey, Hz = F.solve(source)
E_mag = np.sqrt(np.square(np.abs(Ex)) + np.square(np.abs(Ey)))
H_mag = np.abs(Hz)
I_E0 = np.abs(np.square(np.sum(E_mag * probe)))
I_H0 = np.abs(np.square(np.sum(H_mag * probe)))

print('I_H0 = {}'.format(I_H0))

# plot the vacuum fields
if PLOT:
    plt.imshow(np.real(imarr(Hz)), cmap='RdBu')
    plt.title('real(Hz)')
    plt.xlabel('y')
    plt.ylabel('x')
    plt.colorbar()
Пример #9
0
eps_r[design_region == 1] = eps_max

# make the accelration probe
eta = np.zeros((Nx, Ny), dtype=np.complex128)
channel_ys = np.arange(Ny)
eta[Nx // 2, :] = np.exp(1j * 2 * np.pi * channel_ys / Ny)

# plot the probe through channel
if PLOT:
    plt.plot(np.real(imarr(eta[Nx // 2, :])), label='RE\{eta\}')
    plt.xlabel('position along channel (y)')
    plt.ylabel('eta (y)')
    plt.show()

# vacuum test, get normalization
F = fdfd_hz(omega, dL, eps_r, npml)
Ex, Ey, Hz = F.solve(source)
E_mag = np.sqrt(np.square(np.abs(Ex)) + np.square(np.abs(Ey)))
E0 = np.max(E_mag[spc:-spc, :])
print('E0 = {} V/m'.format(E0))

# plot the vacuum fields
if PLOT:
    plt.imshow(np.real(Ey) / E0, cmap='RdBu')
    plt.title('E_y / E0 (<-)')
    plt.xlabel('y')
    plt.ylabel('x')
    plt.colorbar()
    plt.show()

    return npa.divide(npa.tanh(gamma * eta) + npa.tanh(gamma * (rho - eta)), npa.tanh(gamma * eta) + npa.tanh(gamma * (1 - eta)))

def convert_rho_epsr(rho):
    """ Helper function to convert the material density rho to permittivity eps_r """
    return epsr_min + (epsr_max-epsr_min)*operator_proj(make_rho(rho, design_region, blur_radius, Ny), 0.5, gamma)

rho_init = 0.5 * design_region
eps_r = convert_rho_epsr(rho_init)

### Source term
source = np.zeros((Nx, Ny))
source[npml[0]+10, :] = 1     # use just this line for single side drive
source[-npml[0]-10-1, :] = 1  # add this for dual side drive

# Setup simulation
F = fdfd_hz(omega, dL, eps_r, npml)   # create an object that stores our parameters and current structure.
Ex, Ey, Hz = F.solve(source)          # calculate the fields for our source term

# Objective function
probe = np.zeros((Nx, Ny), dtype=np.complex128)
probe[spc+pillar_width+gap//2:spc+pillar_width+gap//2+2,:] = np.exp(-1j * 2*np.pi * np.arange(Ny)/Ny)

def objective(rho):
    """ Objective function measuring the acceleration gradient """
    eps_arr = convert_rho_epsr(design_region*rho.reshape((Nx, Ny)))
    F.eps_r = eps_arr
    Ex, Ey, Hz = F.solve(source)
    
    G = npa.abs(npa.sum(Ey*probe))  # calculate objective value G
    
    # normalize G by maximum field
def data_generation(full_pattern: np.array) -> np.array:
    
    # Grating pattern
    eps_r[:, int(2431.25/dL):int(2800/dL)] = full_pattern;
    #ceviche.viz.abs(eps_r[:, int(2431.25/dL):int(2800/dL)], cbar=True);
    #ceviche.viz.abs(np.hstack((np.ones([256,5])*2.1025,full_pattern[0,5,5])), cbar=True);
    #ceviche.viz.abs(np.hstack((np.ones([int(1600/dL),int(31.25/dL)])*2.1025,full_pattern[0,5,5])), cbar=True);
    
    # Set up the FDFD simulation for TM
    F = fdfd_hz(omega, dL*1e-9, eps_r, npml);
    
    # Source
    source_amp = 64e9/dL/dL;
    source_loc_y = int(2320/dL);
    
    # Define the source as just a constant source along x at `y = source_loc_y`, modeling plane wave
    source = np.zeros(grid_shape, dtype=complex);
    source[:, source_loc_y] = source_amp;
    
    # Add a source directly behind to cancel back traveling wave (for TFSF effect)
    source[:, source_loc_y-1] = source[:, source_loc_y] * np.exp(-1j * k_sub * dL*1e-9 - 1j * np.pi);
    
    # Solve the FDFD simulation for the fields, offset the phase such that Ex has 0 phase at the center of the bottom row of the window
    Ex_forward, Ey_forward, Hz_forward = F.solve(source);
    Hz_out_forward = Hz_forward[:, int(2400/dL):int(2800/dL)]*np.exp(-1j*0.784368210509431);
    Ex_out_forward = Ex_forward[:, int(2400/dL):int(2800/dL)]*np.exp(-1j*0.784368210509431);
    Ey_out_forward = Ey_forward[:, int(2400/dL):int(2800/dL)]*np.exp(-1j*0.784368210509431);
    #ceviche.viz.real(Ex_forward[:, int(2400/dL):int(2800/dL)], outline = eps_r[:, int(2400/dL):int(2800/dL)], cbar = True);
    #ceviche.viz.real(Hz_out_forward, outline = np.hstack((np.ones([int(1600/dL),int(31.25/dL)])*2.1025,full_pattern[0,5,5])), cbar = True);
    
    # Adjoint simulation
    k0 = 2 * np.pi / wavelength; # Wavenumber in air
    # Oblique incident angle, described below
    # Forward:
    #            y
    #            ^   /
    #            |  /
    #            | /
    #            |/  angle theta
    #----------------------->x
    #            |
    #            |
    #            |
    #            |
    # Backward:
    #             |
    #             |  
    #             |
    #             |
    # x<--------------------------
    #    angle  / |
    #          /  |
    #         /   |
    #        /    |
    #             y
    
    angle_deg = 90 - np.arcsin(wavelength/period)*180/np.pi;
    angle_rad = angle_deg * np.pi / 180;
    source_loc_y_adj = int(3200/dL);
    
    # Compute the wave vector
    kx = k0 * np.cos(angle_rad);
    ky = k0 * np.sin(angle_rad);
    #k_vector = [kx, ky];
    
    # Get an array of the x positions across the simulation domain
    Lx = Nx * dL*1e-9;
    x_vec = np.linspace(-Lx / 2, Lx / 2, Nx);
    
    # Make a new source where source[x] ~ exp(i * kx * x) to simulate an angle
    source_amp_x = np.exp(1j * kx * x_vec);
    source_angle = np.zeros(grid_shape, dtype=complex);
    source_angle[:, source_loc_y_adj] = source_amp * source_amp_x;
    
    # Add another source panel directly behind to cancel the back-traveling wave
    source_angle[:, source_loc_y_adj + 1] = source_angle[:, source_loc_y_adj] * np.exp(-1j * ky * dL*1e-9 - 1j * np.pi);
    
    # Add a bloch phase of kx * Lx across boundary to compensate for plane wave
    F_Bloch = fdfd_hz(omega, dL*1e-9, eps_r, npml, bloch_phases=[kx * Lx, 0]);
    
    # Solve the adjoint fields, offset the phase such that Ey has 0 phase at the center of the top row of the window
    Ex_adjoint, Ey_adjoint, Hz_adjoint = F_Bloch.solve(source_angle);
    Hz_out_adjoint = Hz_adjoint[:, int(2400/dL):int(2800/dL)]*np.exp(-1j*(-0.27908708565765883));
    Ex_out_adjoint = Ex_adjoint[:, int(2400/dL):int(2800/dL)]*np.exp(-1j*(-0.27908708565765883));
    Ey_out_adjoint = Ey_adjoint[:, int(2400/dL):int(2800/dL)]*np.exp(-1j*(-0.27908708565765883));
    #ceviche.viz.real(Ex_adjoint[:, 300:350], outline = eps_r[:, 300:350], cbar = True);
    
    return -Hz_out_forward, Ex_out_forward, Ey_out_forward, -Hz_out_adjoint, Ex_out_adjoint, Ey_out_adjoint;
Пример #12
0
# make a list of probe locations, evenly spaced in x and at probe_index_y
probes = []
space_x = Nx / float(Nw)  # even spacing between probes
for i in range(Nw):
    index_i = int(space_x * (1 / 2 + i))  # the x index of the ith probe
    probe_i = np.zeros((Nx, Ny))  # ith probe
    probe_i[index_i, probe_index_y] = 1  # define at this point
    probes.append(probe_i)  # add to list

# make a list of FDFDs at each wavelength and their power normalizations
fdfds = []
powers = []
for i, lam in enumerate(wavelengths):
    omega_i = 2 * np.pi * C_0 / lam  # the angular frequency
    fdfd_i = fdfd_hz(omega_i, dL, eps_r,
                     npml=[0, npml])  # make an FDFD simulation
    fdfds.append(fdfd_i)  # add it to the list
    Ex, Ey, Hz = fdfd_i.solve(source)  # solve the fields
    powers.append(np.sum(
        np.square(np.abs(Hz) *
                  probes[i])))  # compute the power at its probe, add to list

# plot the domain (design region + probes + source)
if PLOT:
    plt.imshow((sum(probes) + source + design_region).T, cmap='Greys')
    plt.title('domain')
    plt.xlabel('x')
    plt.ylabel('y')
    plt.colorbar()
    plt.show()