Esempio n. 1
0
def compute_overlap_e_angled(
    E: field_t,
    H: field_t,
    axis: int,
    omega: complex,
    dxes: dx_lists_t,
    slices: List[slice],
    mu: field_t = None,
) -> field_t:
    """
    Given an eigenmode obtained by solve_waveguide_mode, calculates overlap_e for the
    mode orthogonality relation Integrate(((E x H_mode) + (E_mode x H)) dot dn)
    [assumes reflection symmetry].

    overlap_e makes use of the e2h operator to collapse the above expression into
     (vec(E) @ vec(overlap_e)), allowing for simple calculation of the mode overlap.

    :param E: E-field of the mode
    :param H: H-field of the mode (advanced by half of a Yee cell from E)
    :axis: propagation axis
    :param omega: Angular frequency of the simulation
    :param dxes: Grid parameters [dx_e, dx_h] as described in fdfd_tools.operators header
    :param slices: epsilon[tuple(slices)] is used to select the portion of the grid to use
        as the waveguide cross-section. slices[axis] should select only one
    :param mu: Magnetic permeability (default 1 everywhere)
    :return: overlap_e for calculating the mode overlap
    """
    if (slices[axis].stop - slices[axis].start) != 1:
        raise ValueError('The slice in the axis direction is not 1 wide.')
    # Write out the operator product for the mode orthogonality integral
    domain = np.zeros_like(E)
    domain[[slice(0, 3)] + slices] = 1

    dn = np.zeros_like(E)
    dn[axis] = np.ones_like(E[axis])
    dn_vec = vec(dn)

    e2h = operators.e2h(omega, dxes, mu)
    ds = sparse.diags(vec(domain))
    h_cross_ = operators.poynting_h_cross(np.conj(vec(H)), dxes)
    e_cross_ = operators.poynting_e_cross(np.conj(vec(E)), dxes)

    overlap_e = dn_vec @ ds @ (-h_cross_ + e_cross_ @ e2h)

    # Normalize
    norm_factor = np.abs(overlap_e @ vec(E))
    overlap_e /= norm_factor

    return unvec(overlap_e, E[0].shape)
Esempio n. 2
0
def compute_overlap_annulus(
    E: field_t,
    omega: complex,
    dxes: dx_lists_t,
    axis: int,
    mu: field_t = None,
) -> field_t:
    """This is hopefully going to calculate the overlap """

    # need to extract the size of dxes to adjust the size of mask and the E field
    len_dxes = np.concatenate(dxes, axis=0)

    # want to extract the absolute value, x=0, y=1, z=2

    # Domain is all zero
    domain = np.zeros_like(E[0], dtype=int)

    # Then set the slices part equal to 1 - may need to use our mask
    t = np.abs(len_dxes[0].size)

    # TODO adjust these values, may call from problem
    mask = annulus(t, t, [0, 0], 0.3 * t, 0.2 * t)
    mask = mask[..., newaxis]  # adds the z to mask
    domain[mask] = 1

    npts = E[0].size
    dn = np.zeros(npts * 3, dtype=int)
    dn[0:npts] = 1
    dn = np.roll(dn, npts * axis)

    e2h = operators.e2h(omega, dxes, mu)
    # Hopefully this works with the mask
    ds = sparse.diags(vec([domain] * 3))

    e_cross_ = operators.poynting_e_cross(vec(E), dxes)

    # Difference is, we have no H field
    overlap_e = dn @ ds @ (e_cross_ @ e2h)

    # Normalize
    norm_factor = np.abs(overlap_e @ vec(E))
    overlap_e /= norm_factor

    return unvec(overlap_e, E[0].shape)
def scalar2rotated_vector_fields(eps_grid: Grid,
                                 scalar_field_function: Callable,
                                 mu: List[np.ndarray],
                                 omega: float,
                                 axis: int,
                                 slices: List[slice],
                                 theta: float = 0,
                                 psi: float = 0,
                                 polarization_angle: float = 0,
                                 polarity: int = 1,
                                 power: float = 1,
                                 full_fields: bool = False):
    """
    Given a function that evaluates the scalar field that propagates in the z-direction,
    this function will make the vector field taking into account three rotations.

    Args:
        eps_grid: gridlock.grid with the permittivity distribution.
        mu: Permeability distribution.
        omega: The frequency of the mode.
        axis: Direction of propagation.
        slices: Source slice which define the position of the source in the grid.
        theta: Rotation around the default E-component.
        psi: Rotation around the source plane normal.
        polarization_angle: Rotation around the propagation direction.
        polarity: 1 if forward propagating. -1 if backward propagating.
        power: power
        full_fields: True gives you the field in the entire simulation space.
            False gives only the fields in the slices, the rest of the simulation space
            will be zero.

    Returns:
        Results: dict with the wavevector, the e-field and the h-field.

    """
    if mu is None:
        mu = [np.ones(eps_grid.shape)] * 3
    dxes = [eps_grid.dxyz, eps_grid.autoshifted_dxyz()]
    epsilon = eps_grid.grids
    xyz_shift = [eps_grid.shifted_xyz(which_shifts=i) for i in range(3)]

    # Define rotation to set z as propagation direction.
    order = np.roll(range(3), 2 - axis)
    reverse_order = np.roll(range(3), axis - 2)

    #Make grid points
    xyz = xyz_shift[order[0]]
    x_Ex, y_Ex, z_Ex = np.meshgrid(xyz[order[0]],
                                   xyz[order[1]],
                                   xyz[order[2]],
                                   indexing='ij')
    xyz = xyz_shift[order[1]]
    x_Ey, y_Ey, z_Ey = np.meshgrid(xyz[order[0]],
                                   xyz[order[1]],
                                   xyz[order[2]],
                                   indexing='ij')
    xyz = xyz_shift[order[2]]
    x_Ez, y_Ez, z_Ez = np.meshgrid(xyz[order[0]],
                                   xyz[order[1]],
                                   xyz[order[2]],
                                   indexing='ij')

    #Make total rotation matrix
    k = np.array([0, 0, polarity])
    e0 = np.array([1, 0, 0])
    R_theta = rotation_matrix(e0, theta)
    R_psi = rotation_matrix(k, psi)
    k = R_psi @ R_theta @ k
    R_pol = rotation_matrix(k, polarization_angle)
    R = R_pol @ R_psi @ R_theta
    e0_rot = R @ e0

    #Make wavevector
    ref_index = np.sqrt(np.real(np.average(epsilon[0][tuple(slices)])))
    bloch_vector = (k * omega * ref_index)

    #Evaluate fields.
    ex_mag = scalar_field_function(x_Ex, y_Ex, z_Ex,
                                   np.linalg.inv(R)) * e0_rot[0]
    ey_mag = scalar_field_function(x_Ey, y_Ey, z_Ey,
                                   np.linalg.inv(R)) * e0_rot[1]
    ez_mag = scalar_field_function(x_Ez, y_Ez, z_Ez,
                                   np.linalg.inv(R)) * e0_rot[2]
    E_fields = [ex_mag, ey_mag, ez_mag]

    #Make H fields.
    dxes = [[dx[i] for i in order] for dx in dxes]
    e_vec = vec(E_fields)
    h_vec = operators.e2h(omega=omega,
                          dxes=dxes,
                          mu=vec([mu[i].transpose(order) for i in order]),
                          bloch_vec=bloch_vector) @ e_vec
    H_fields = unvec(h_vec, E_fields[0].shape)

    #Normalize fields.
    #Roll back
    E = [None] * 3
    H = [None] * 3
    wavevector = np.zeros(3)
    for a, o in enumerate(reverse_order):
        E[a] = np.zeros_like(epsilon[0], dtype=complex)
        H[a] = np.zeros_like(epsilon[0], dtype=complex)

        if full_fields:
            E[a] = np.sqrt(power) * E_fields[o].transpose(reverse_order)
            H[a] = np.sqrt(power) * H_fields[o].transpose(reverse_order)
        else:
            E[a][tuple(slices)] = np.sqrt(power) * E_fields[o][tuple(
                [slices[i] for i in order])].transpose(reverse_order)
            H[a][tuple(slices)] = np.sqrt(power) * H_fields[o][tuple(
                [slices[i] for i in order])].transpose(reverse_order)

        wavevector[a] = bloch_vector[o]

    results = {
        'wavevector': wavevector,
        'H': H,
        'E': E,
    }

    return results
Esempio n. 4
0
def make_near2farfield_matrix(points: np.array,
                              omegas: float,
                              grid,
                              dxes,
                              pos: np.array,
                              width: np.array,
                              polarity: int,
                              eps_0: float,
                              mu_0=1.0,
                              spherical_axis=2) -> sparse.spmatrix:
    '''
    This function returns a matrix that transforms the fields on a plane 
    defined by pos and width to the farfield.
    The fields are calculated on a sphere centered in the origin.

    input:
    - points: points in the far field
    - omega: omega
    - grid: the grid object of the simulation
    - dxes: the dxes of the simulation
    - pos: center position of the plane you want to project out
    - width: size of the plane, (the normal vector is calculated based on 
                the 0 value of this vector, e.g. if the width is [100,0,100] the
                normal is [0,1,0])
    - polarity: direction in which you want to project
    - eps_0
    - mu_0
    - spherical_axis: orientation of the spherical coordinate system
    output:
    - sparse tranformation matrix 

    '''
    # prepare normal
    axis = abs(width).argmin()
    normal = np.array([0, 0, 0])
    normal[axis] = polarity

    # prepare the grid
    x = grid.xyz[0]
    y = grid.xyz[1]
    z = grid.xyz[2]
    shape = [x.size, y.size, z.size]

    # make E-fields to H-fields matrix
    arg = {'omega': omegas, 'dxes': dxes}
    e2h = operators.e2h(**arg)

    # matrix to move all the vector components to the H component
    move2H_E, move2H_H = move2H_matrix(axis, shape)

    # make the n_cross
    normal_grid = [
        normal[0] * np.ones(shape), normal[1] * np.ones(shape),
        normal[2] * np.ones(shape)
    ]
    normal_vec = fdfd_tools.vec(normal_grid)

    cross_normal = operators.vec_cross(normal_vec)

    # matrix to reduce the fields to the region defined by the slices
    find_ind = lambda x, vec: np.abs(x - vec).argmin()
    x_slice = slice(find_ind(pos[0] - width[0] / 2, x),
                    find_ind(pos[0] + width[0] / 2, x) + abs(normal[0]), 1)
    y_slice = slice(find_ind(pos[1] - width[1] / 2, y),
                    find_ind(pos[1] + width[1] / 2, y) + abs(normal[1]), 1)
    z_slice = slice(find_ind(pos[2] - width[2] / 2, z),
                    find_ind(pos[2] + width[2] / 2, z) + abs(normal[2]), 1)
    x_crop, y_crop, z_crop, dx_crop, dy_crop, dz_crop, fos = fields_on_slice(
        x, y, z, dxes, x_slice, y_slice, z_slice)

    # Calculate the area elements
    d_crop = [dx_crop, dy_crop, dz_crop]
    d_crop[axis] = np.ones_like(d_crop[axis])
    d_area = d_crop[0] * d_crop[1] * d_crop[2]

    # make the Fourier matrix
    farfield_radius = 1
    fourier_matrix = make_fourier_matrix(
        x_crop, y_crop, z_crop, d_area,
        farfield_radius * np.squeeze(points[0:, 0]),
        farfield_radius * np.squeeze(points[0:, 1]),
        farfield_radius * np.squeeze(points[0:, 2]), omegas)
    # make the transformation matrix in cartesian coordinates
    k = omegas * points
    k_vec = k.flatten('F')
    cross_k = operators.vec_cross(k_vec)

    A = fourier_matrix @ fos @ cross_normal @ move2H_H @ e2h
    F = fourier_matrix @ fos @ (-cross_normal) @ move2H_E

    t_cart = -1j*omegas*(mu_0/(4*np.pi*farfield_radius)*np.exp(-1j*omegas*farfield_radius)*A)\
             -(-1j)/eps_0*eps_0/(4*np.pi*farfield_radius)*np.exp(-1j*omegas*farfield_radius)*(cross_k @ F)

    # transform to spherical coordinates
    cart2sp = cart2spheric_matrix(points[0:, 0], points[0:, 1], points[0:, 2],
                                  spherical_axis)

    t_sp = cart2sp @ t_cart

    # remove the radial components
    n_k = int(k_vec.shape[0] / 3)
    Id = sparse.eye(n_k)
    zeros = sparse.csr_matrix((n_k, n_k))
    rm_radial = sparse.bmat([[zeros, zeros, zeros], [zeros, Id, zeros],
                             [zeros, zeros, Id]])

    t = rm_radial @ t_sp

    return t
Esempio n. 5
0
def compute_overlap_e(
    E: field_t,
    H: field_t,
    wavenumber: complex,
    omega: complex,
    dxes: dx_lists_t,
    axis: int,
    polarity: int,
    slices: List[slice],
    mu: field_t = None,
) -> field_t:
    """
    Given an eigenmode obtained by solve_waveguide_mode, calculates overlap_e for the
    mode orthogonality relation Integrate(((E x H_mode) + (E_mode x H)) dot dn)
    [assumes reflection symmetry].

    overlap_e makes use of the e2h operator to collapse the above expression into
     (vec(E) @ vec(overlap_e)), allowing for simple calculation of the mode overlap.

    :param E: E-field of the mode
    :param H: H-field of the mode (advanced by half of a Yee cell from E)
    :param wavenumber: Wavenumber of the mode
    :param omega: Angular frequency of the simulation
    :param dxes: Grid parameters [dx_e, dx_h] as described in fdfd_tools.operators header
    :param axis: Propagation axis (0=x, 1=y, 2=z)
    :param polarity: Propagation direction (+1 for +ve, -1 for -ve)
    :param slices: epsilon[tuple(slices)] is used to select the portion of the grid to use
        as the waveguide cross-section. slices[axis] should select only one
    :param mu: Magnetic permeability (default 1 everywhere)
    :return: overlap_e for calculating the mode overlap
    """
    cross_plane = [slice(None)] * 3
    cross_plane[axis] = slices[axis]

    # Determine phase factors for parallel slices
    a_shape = np.roll([-1, 1, 1], axis)
    a_E = np.real(dxes[0][axis]).cumsum()
    a_H = np.real(dxes[1][axis]).cumsum()
    iphi = -polarity * 1j * wavenumber
    phase_E = np.exp(iphi * (a_E - a_E[slices[axis]])).reshape(a_shape)
    phase_H = np.exp(iphi * (a_H - a_H[slices[axis]])).reshape(a_shape)

    # Expand our slice to the entire grid using the calculated phase factors
    Ee = [None] * 3
    He = [None] * 3
    for k in range(3):
        Ee[k] = phase_E * E[k][tuple(cross_plane)]
        He[k] = phase_H * H[k][tuple(cross_plane)]

    # Write out the operator product for the mode orthogonality integral
    domain = np.zeros_like(E[0], dtype=int)
    domain[tuple(slices)] = 1

    npts = E[0].size
    dn = np.zeros(npts * 3, dtype=int)
    dn[0:npts] = 1
    dn = np.roll(dn, npts * axis)

    e2h = operators.e2h(omega, dxes, mu)
    ds = sparse.diags(vec([domain] * 3))
    h_cross_ = operators.poynting_h_cross(vec(He), dxes)
    e_cross_ = operators.poynting_e_cross(vec(Ee), dxes)

    overlap_e = dn @ ds @ (-h_cross_ + e_cross_ @ e2h)

    # Normalize
    norm_factor = np.abs(overlap_e @ vec(Ee))
    overlap_e /= norm_factor

    return unvec(overlap_e, E[0].shape)
Esempio n. 6
0
def mode_solver(bloch_vec: np.ndarray,
                omega_appx: float,
                num_modes: int,
                dxes: dx_lists_t,
                epsilon: np.ndarray,
                op_type: str,
                set_init_cond: bool,
                init_vec: np.ndarray = None,
                mu: np.ndarray = None,
                shift_orthogonal=np.zeros((3, 3))):

    if mu is None:
        mu = np.ones_like(epsilon)

    # Setting up the operator
    if op_type == 'efield':
        op, eps_norm, eps_un_norm = efield_operator(bloch_vec, dxes,
                                                    vec(epsilon), vec(mu),
                                                    shift_orthogonal)

    elif op_type == 'hfield':
        op, eps_norm, eps_norm = hfield_operator(bloch_vec, dxes, vec(epsilon),
                                                 vec(mu), shift_orthogonal)
    else:
        raise ValueError('Undefined operator type')

    if set_init_cond and init_vec is None:

        # Starting with initial condition with a FDFD simulation
        J = np.zeros_like(epsilon)
        J[0][J.shape[1] // 2, J.shape[2] // 2, J.shape[3] // 2] = 1.0
        J[1][J.shape[1] // 2, J.shape[2] // 2, J.shape[3] // 2] = 1.0
        #J[2][J.shape[1]//2, J.shape[2]//2,J.shape[3]//2] = 1.0
        solver = DirectSolver()
        sim_args = {
            'omega': omega_appx,
            'dxes': dxes,
            'epsilon': vec(epsilon),
            'mu': vec(mu),
            'J': vec(J),
            'bloch_vec': bloch_vec
        }

        E = solver.solve(**sim_args)
        elec_field = unvec(E, J[0].shape)

        if op_type == 'efield':
            mode_estimate = eps_un_norm @ E
        elif op_type == 'hfield':
            op_e2h = operators.e2h(omega=1.0,
                                   dxes=dxes,
                                   mu=vec(mu),
                                   bloch_vec=bloch_vec)
            mode_estimate = op_e2h @ E
        else:
            raise ValueError('Undefined operator type')

        # Solving for eigen values
        eig_value, mode_field = scipy.sparse.linalg.eigs(op,
                                                         num_modes,
                                                         sigma=omega_appx**2,
                                                         v0=mode_estimate)

    elif init_vec is not None:
        eig_value, mode_field = scipy.sparse.linalg.eigs(op,
                                                         1,
                                                         sigma=omega_appx**2,
                                                         v0=init_vec)

    else:
        eig_value, mode_field = scipy.sparse.linalg.eigs(op,
                                                         num_modes,
                                                         sigma=omega_appx**2)
    omega = np.sqrt(eig_value)

    if op_type == 'efield':
        if len(eig_value) == 1:
            mode_field = eps_norm @ mode_field
        else:
            mode_field = [eps_norm @ mode for mode in mode_field]

    return omega, mode_field.transpose()