Esempio n. 1
0
    def test_Ez_reverse(self):

        print('\ttesting reverse-mode Ez in FDFD_MF')
        f = fdfd_mf_ez(self.omega, self.dL, self.eps_r, self.omega_mod, self.delta, self.phi, self.Nsb, self.pml)

        def J_fdfd(params):
            eps_r = params[:self.N].reshape((self.Nx, self.Ny))
            delta = params[self.N:(self.Nfreq+1)*self.N].reshape((self.Nfreq, self.Nx, self.Ny))
            phi = params[(self.Nfreq+1)*self.N:].reshape((self.Nfreq, self.Nx, self.Ny))
            # set the permittivity, modulation depth, and modulation phase
            f.eps_r = eps_r
            f.delta = delta
            f.phi = phi
            # set the source amplitude to the permittivity at that point
            Hx, Hy, Ez = f.solve((eps_r + npa.sum(delta*npa.exp(1j*phi),axis=0))* self.source_ez)

            return npa.sum(npa.square(npa.abs(Ez))) \
                 + npa.sum(npa.square(npa.abs(Hx))) \
                 + npa.sum(npa.square(npa.abs(Hy)))

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

        if VERBOSE:
            print('\ttesting epsilon, delta and phi.')
            print('\tobjective function value: ', J_fdfd(self.params))
            print('\tgrad (auto):  \n\t\t', grad_autograd_rev)
            print('\tgrad (num):   \n\t\t', grad_numerical)

        self.check_gradient_error(grad_numerical, grad_autograd_rev)
Esempio n. 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)
Esempio n. 3
0
    def test_Ez_forward(self):
 
        print('\ttesting forward-mode Ez in FDFD_MF')

        f = fdfd_mf_ez(self.omega, self.dL, self.eps_r, self.omega_mod, self.delta, self.phi, self.Nsb, self.pml)

        def J_fdfd(c):

            # set the permittivity, modulation depth, and modulation phase
            f.eps_r = c * self.eps_r
            f.delta = c * self.delta
            f.phi = c * self.phi
            # set the source amplitude to the permittivity at that point
            Hx, Hy, Ez = f.solve(c * self.eps_r * self.source_ez)

            return npa.square(npa.abs(Ez)) \
                 + npa.square(npa.abs(Hx)) \
                 + npa.square(npa.abs(Hy))

        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)
Esempio n. 4
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)
Esempio n. 5
0
    def test_mult_x(self):
        def fn_mult_x(x):
            # sparse matrix multiplication (Ax = b) as a function of dense vector 'x'
            b = sp_mult(self.entries_const, self.indices_const, x)
            return self.out_fn(b)

        x = make_rand_complex(self.N)

        grad_rev = ceviche.jacobian(fn_mult_x, mode='reverse')(x)[0]
        grad_for = ceviche.jacobian(fn_mult_x, mode='forward')(x)[0]
        grad_true = grad_num(fn_mult_x, x)

        np.testing.assert_almost_equal(grad_rev,
                                       grad_true,
                                       decimal=DECIMAL,
                                       err_msg=self.err_msg(
                                           'fn_mult_x', 'reverse'))
        np.testing.assert_almost_equal(grad_for,
                                       grad_true,
                                       decimal=DECIMAL,
                                       err_msg=self.err_msg(
                                           'fn_mult_x', 'forward'))
Esempio n. 6
0
    def test_solve_b(self):
        def fn_solve_b(b):
            # sparse matrix solve (x = A^{-1}b) as a function of source 'b'
            x = sp_solve(self.entries_const, self.indices_const, b)
            return self.out_fn(x)

        b = make_rand_complex(self.N)

        grad_rev = ceviche.jacobian(fn_solve_b, mode='reverse')(b)[0]
        grad_for = ceviche.jacobian(fn_solve_b, mode='forward')(b)[0]
        grad_true = grad_num(fn_solve_b, b)

        np.testing.assert_almost_equal(grad_rev,
                                       grad_true,
                                       decimal=DECIMAL,
                                       err_msg=self.err_msg(
                                           'fn_solve_b', 'reverse'))
        np.testing.assert_almost_equal(grad_for,
                                       grad_true,
                                       decimal=DECIMAL,
                                       err_msg=self.err_msg(
                                           'fn_solve_b', 'forward'))
Esempio n. 7
0
    def test_solve_entries(self):
        def fn_solve_entries(entries):
            # sparse matrix solve (x = A^{-1}b) as a function of matrix entries 'A(entries)'
            x = sp_solve(entries, self.indices_const, self.b_const)
            return self.out_fn(x)

        entries = make_rand_complex(self.M)

        grad_rev = ceviche.jacobian(fn_solve_entries,
                                    mode='reverse')(entries)[0]
        grad_for = ceviche.jacobian(fn_solve_entries,
                                    mode='forward')(entries)[0]
        grad_true = grad_num(fn_solve_entries, entries)

        np.testing.assert_almost_equal(grad_rev,
                                       grad_true,
                                       decimal=DECIMAL,
                                       err_msg=self.err_msg(
                                           'fn_solve_entries', 'reverse'))
        np.testing.assert_almost_equal(grad_for,
                                       grad_true,
                                       decimal=DECIMAL,
                                       err_msg=self.err_msg(
                                           'fn_solve_entries', 'forward'))
Esempio n. 8
0
    def test_spmut_entries(self):
        def fn_spsp_entries_a(entries):
            # sparse matrix - sparse matrix dot procut as function of entries into first matrix (A)
            entries_c, indices_c = spsp_mult(entries,
                                             self.indices_const,
                                             self.entries_const2,
                                             self.indices_const2,
                                             N=self.N)
            # and then as a function of entries in the second matrix (X)
            entries_d, indices_d = spsp_mult(self.entries_const,
                                             self.indices_const,
                                             entries_c,
                                             indices_c,
                                             N=self.N)
            # do a sparse linear solve using the resulting matrix and return out_fn of result
            x = sp_solve(entries_d, indices_d, self.b_const)
            return self.out_fn(x)

        entries = make_rand_complex(self.M)

        grad_rev = ceviche.jacobian(fn_spsp_entries_a,
                                    mode='reverse')(entries)[0]
        grad_for = ceviche.jacobian(fn_spsp_entries_a,
                                    mode='forward')(entries)[0]
        grad_true = grad_num(fn_spsp_entries_a, entries)

        np.testing.assert_almost_equal(grad_rev,
                                       grad_true,
                                       decimal=DECIMAL,
                                       err_msg=self.err_msg(
                                           'fn_solve_entries', 'reverse'))
        np.testing.assert_almost_equal(grad_for,
                                       grad_true,
                                       decimal=DECIMAL,
                                       err_msg=self.err_msg(
                                           'fn_solve_entries', 'forward'))
Esempio n. 9
0
# defines the intensity on the other side of the box as a function of the relative permittivity grid
def intensity(eps_arr):

    eps_r = eps_arr.reshape((Nx, Ny))
    # set the permittivity of the FDFD and solve the fields
    F.eps_r = eps_r
    Ex, Ey, Hz = F.solve(source)

    # compute the gradient and normalize if you want
    I = npa.sum(npa.square(npa.abs(Hz * probe)))
    return -I / I_H0


# define the gradient for autograd
grad_I = jacobian(intensity, mode='reverse')

# initialize the design region with some eps
eps_r[box_region == 1] = eps_max

from scipy.optimize import minimize
bounds = [(1, eps_max) if box_region.flatten()[i] == 1 else (1, 1)
          for i in range(eps_r.size)]

minimize(intensity,
         eps_r,
         args=(),
         method='L-BFGS-B',
         jac=grad_I,
         bounds=bounds,
         tol=None,
Esempio n. 10
0

# defines the acceleration gradient as a function of the relative permittivity grid
def accel_gradient(eps_arr):

    # set the permittivity of the FDFD and solve the fields
    F.eps_r = eps_arr.reshape((Nx, Ny))
    Ex, Ey, Hz = F.solve(source)

    # compute the gradient and normalize if you want
    G = npa.sum(Ey * eta / Ny)
    return -np.abs(G)  # / Emax(Ex, Ey, eps_r)


# define the gradient for autograd
grad_g = jacobian(accel_gradient)

# optimization
NIter = 200
bounds_eps = [(1, eps_max) if design_region.flatten()[i] == 1 else (1, 1)
              for i in range(eps_r.size)]
minimize(accel_gradient,
         eps_r.flatten(),
         args=(),
         method='L-BFGS-B',
         jac=grad_g,
         bounds=bounds_eps,
         tol=None,
         callback=None,
         options={
             'disp': True,
    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
    E_mag = npa.sqrt(npa.square(npa.abs(Ex)) + npa.square(npa.abs(Ey)))
    material_density = (eps_r - 1) / (epsr_max - 1)
    max_field = npa.max(E_mag * material_density)
    
    return G/max_field

# Optimization run
N_opts = 50

objective_jac = jacobian(objective, mode='reverse')
(rho_optimum, loss) = adam_optimize(objective, rho_init.flatten(), objective_jac, Nsteps=N_opts, direction='max', step_size=1e-2)

## Visualization of result
eps_r = convert_rho_epsr(design_region * rho_optimum.reshape((Nx, Ny)))
G = objective(rho_optimum)
F.eps_r = eps_r
Ex, Ey, Hz = F.solve(source)          # calculate the fields for our source term


f, ax = plt.subplots(1, 1, tight_layout=True, figsize=(Nx/40+1,Ny/40+0.5))
max_val = np.max(np.abs(Ey))
plt.contour(eps_r.T, levels=[(epsr_max+1)/2])
im = plt.imshow(np.real(Ey.T*np.exp(1j*3)), cmap='RdBu', aspect='auto', vmin=-max_val, vmax=max_val)
divider = make_axes_locatable(ax)
cax = divider.append_axes('right', size='5%', pad=0.05)
Esempio n. 12
0
measure_pos = np.zeros((Nx, Ny, Nz))
measure_pos[-npml-10, Ny//2, Nz//2] = 1

def objective(eps_space):
    F.eps_r *= eps_space
    measured = []
    for t_index in range(steps):
        fields = F.forward(Jz=source(t_index))
        measured.append(npa.sum(fields['Ez'] * measure_pos))
    measured_f = my_fft(npa.array(measured))
    spectral_power = npa.square(npa.abs(measured_f))
    return spectral_power

eps_space = 1.0
spectral_power = objective(eps_space)
jac_power = jacobian(objective, mode='forward')(eps_space)
jac_power_num = jacobian(objective, mode='numerical')(eps_space)

n_disp = 140

fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
delta_f = 1 / steps / dt
freq_x = np.arange(n_disp) * delta_f
ax1.plot(freq_x, spectral_power[:n_disp], 'k-')
ax2.plot(freq_x, jac_power[:n_disp,0], 'g-', label='FMD')
ax2.plot(freq_x, jac_power_num[:n_disp,0], 'bo', label='numerical')
ax1.set_ylabel('spectral power', color='k')
ax2.set_ylabel('dP/depsilon', color='g')
ax2.spines['right'].set_color('g')
ax2.legend()
Esempio n. 13
0
                np.sum(probes[j] *
                       Hz_i))) / powers[i]  # compute the power at the probe
            if i == j:
                power_ii += power_ij  # add to sum
            else:
                power_cross += power_ij  # add to sum
        obj_total += power_ii / (power_ii + power_cross)

        # power_total += powers_ii / powers_ij
    return -np.log(
        obj_total / Nw
    )  # return negative (for maximizing) and normalize by number of wavelengths (starting objective = 1)


# define the gradient for autograd
grad_J = jacobian(objective)
"""  optimization loop """

NIter = 100  # max number of iterations

# set the material bounds to (1, eps_max) in design region, (1,1) outside
bounds = [(1, eps_max) if design_region.flatten()[i] == 1 else (1, 1)
          for i in range(eps_r.size)]

# call a constrained optimization function from Scipy (progress prints to terminal)
minimize(objective,
         eps_r,
         args=(),
         method='L-BFGS-B',
         jac=grad_J,
         bounds=bounds,
Esempio n. 14
0
        return npa.abs(npa.sum(output_vector))

    def fn_spsp_entries(entries):
        # sparse matrix multiplication (Ax = b) as a function of matrix entries 'A(entries)'
        entries_c, indices_c = spsp_mult(entries_const,
                                         indices_const,
                                         entries,
                                         indices_const,
                                         N=N)
        x = sp_solve(entries_c, indices_c, b_const)
        return out_fn(x)

    entries = make_rand_complex(M)

    # doesnt pass yet
    grad_rev = ceviche.jacobian(fn_spsp_entries, mode='reverse')(entries)[0]
    grad_true = grad_num(fn_spsp_entries, entries)
    npa.testing.assert_almost_equal(grad_rev, grad_true, decimal=DECIMAL)

    # Testing Gradients of 'Sparse-Sparse Multiply entries Forward-mode'

    grad_for = ceviche.jacobian(fn_spsp_entries, mode='forward')(entries)[0]
    grad_true = grad_num(fn_spsp_entries, entries)
    npa.testing.assert_almost_equal(grad_for, grad_true, decimal=DECIMAL)

    ## TESTS SPARSE MATRX CREATION

    A = make_rand_sparse(N, M)
    B = make_rand_sparse(N, M)
    C_true = A.dot(B).todense()
Esempio n. 15
0
# total powers of the input and measured
P_in = np.sum(spect_in[:steps // 4])
P_out = np.sum(spect[:steps // 4])

# max power, for normalization
P_in_max = np.max(spect_in[:steps // 4])

coupling_efficiency = P_out / P_in
print('calculated a coupling efficiency of {} %'.format(100 *
                                                        coupling_efficiency))
""" DIFFERENTIATION W.R.T. FILL FACTOR """

# compute jacobians
print('-> FMD-ing')

jac_FMD = jacobian(spectral_power, mode='forward')(ff)
do_num_jac = False  # whether to compute numerical jacobian as well
if do_num_jac:
    jac_num = jacobian(spectral_power, mode='numerical')(ff)

delta_f = 1 / steps / dt  # frequency spacing in the FFT output
freq_x = np.arange(n_disp) * delta_f  # frequency range

# plot derivatives along with spectral power
if plot_all:
    right_color = '#c23b22'
    fig, ax1 = plt.subplots()
    ax1.plot(freq_x / 1e12, spect_in[:n_disp] / P_in_max, label='input')
    ax1.plot(freq_x / 1e12, spect[:n_disp] / P_in_max, label='output')
    ax1.set_ylabel('normalized power (P)', color='k')
    ax1.set_xlabel('frequency (THz)')