493 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			493 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| Various operators and helper functions for solving for waveguide modes.
 | |
| 
 | |
| Assuming a z-dependence of the from exp(-i * wavenumber * z), we can simplify Maxwell's
 | |
|  equations in the absence of sources to the form
 | |
| 
 | |
| A @ [H_x, H_y] = wavenumber**2 * [H_x, H_y]
 | |
| 
 | |
| with A =
 | |
| omega**2 * epsilon * mu +
 | |
| epsilon * [[-Dy], [Dx]] / epsilon * [-Dy, Dx] +
 | |
| [[Dx], [Dy]] / mu * [Dx, Dy] * mu
 | |
| 
 | |
| which is the form used in this file.
 | |
| 
 | |
| As the z-dependence is known, all the functions in this file assume a 2D grid
 | |
|  (ie. dxes = [[[dx_e_0, dx_e_1, ...], [dy_e_0, ...]], [[dx_h_0, ...], [dy_h_0, ...]]])
 | |
|  with propagation along the z axis.
 | |
| """
 | |
| # TODO update module docs
 | |
| 
 | |
| from typing import List, Tuple
 | |
| import numpy
 | |
| from numpy.linalg import norm
 | |
| import scipy.sparse as sparse
 | |
| 
 | |
| from .. import vec, unvec, dx_lists_t, field_t, vfield_t
 | |
| from . import operators
 | |
| 
 | |
| 
 | |
| __author__ = 'Jan Petykiewicz'
 | |
| 
 | |
| 
 | |
| def operator_e(omega: complex,
 | |
|                dxes: dx_lists_t,
 | |
|                epsilon: vfield_t,
 | |
|                mu: vfield_t = None,
 | |
|                ) -> sparse.spmatrix:
 | |
|     if numpy.any(numpy.equal(mu, None)):
 | |
|         mu = numpy.ones_like(epsilon)
 | |
| 
 | |
|     Dfx, Dfy = operators.deriv_forward(dxes[0])
 | |
|     Dbx, Dby = operators.deriv_back(dxes[1])
 | |
| 
 | |
|     eps_parts = numpy.split(epsilon, 3)
 | |
|     eps_xy = sparse.diags(numpy.hstack((eps_parts[0], eps_parts[1])))
 | |
|     eps_z_inv = sparse.diags(1 / eps_parts[2])
 | |
| 
 | |
|     mu_parts = numpy.split(mu, 3)
 | |
|     mu_yx = sparse.diags(numpy.hstack((mu_parts[1], mu_parts[0])))
 | |
|     mu_z_inv = sparse.diags(1 / mu_parts[2])
 | |
| 
 | |
|     op = omega * omega * mu_yx @ eps_xy + \
 | |
|         mu_yx @ sparse.vstack((-Dby, Dbx)) @ mu_z_inv @ sparse.hstack((-Dfy, Dfx)) + \
 | |
|         sparse.vstack((Dfx, Dfy)) @ eps_z_inv @ sparse.hstack((Dbx, Dby)) @ eps_xy
 | |
|     return op
 | |
| 
 | |
| 
 | |
| def operator_h(omega: complex,
 | |
|                dxes: dx_lists_t,
 | |
|                epsilon: vfield_t,
 | |
|                mu: vfield_t = None,
 | |
|                ) -> sparse.spmatrix:
 | |
|     """
 | |
|     Waveguide operator of the form
 | |
| 
 | |
|     omega**2 * epsilon * mu +
 | |
|     epsilon * [[-Dy], [Dx]] / epsilon * [-Dy, Dx] +
 | |
|     [[Dx], [Dy]] / mu * [Dx, Dy] * mu
 | |
| 
 | |
|     for use with a field vector of the form [H_x, H_y].
 | |
| 
 | |
|     This operator can be used to form an eigenvalue problem of the form
 | |
|     A @ [H_x, H_y] = wavenumber**2 * [H_x, H_y]
 | |
| 
 | |
|     which can then be solved for the eigenmodes of the system (an exp(-i * wavenumber * z)
 | |
|     z-dependence is assumed for the fields).
 | |
| 
 | |
|     :param omega: The angular frequency of the system
 | |
|     :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
 | |
|     :param epsilon: Vectorized dielectric constant grid
 | |
|     :param mu: Vectorized magnetic permeability grid (default 1 everywhere)
 | |
|     :return: Sparse matrix representation of the operator
 | |
|     """
 | |
|     if numpy.any(numpy.equal(mu, None)):
 | |
|         mu = numpy.ones_like(epsilon)
 | |
| 
 | |
|     Dfx, Dfy = operators.deriv_forward(dxes[0])
 | |
|     Dbx, Dby = operators.deriv_back(dxes[1])
 | |
| 
 | |
|     eps_parts = numpy.split(epsilon, 3)
 | |
|     eps_yx = sparse.diags(numpy.hstack((eps_parts[1], eps_parts[0])))
 | |
|     eps_z_inv = sparse.diags(1 / eps_parts[2])
 | |
| 
 | |
|     mu_parts = numpy.split(mu, 3)
 | |
|     mu_xy = sparse.diags(numpy.hstack((mu_parts[0], mu_parts[1])))
 | |
|     mu_z_inv = sparse.diags(1 / mu_parts[2])
 | |
| 
 | |
|     op = omega * omega * eps_yx @ mu_xy + \
 | |
|         eps_yx @ sparse.vstack((-Dfy, Dfx)) @ eps_z_inv @ sparse.hstack((-Dby, Dbx)) + \
 | |
|         sparse.vstack((Dbx, Dby)) @ mu_z_inv @ sparse.hstack((Dfx, Dfy)) @ mu_xy
 | |
| 
 | |
|     return op
 | |
| 
 | |
| 
 | |
| def normalized_fields_e(e_xy: numpy.ndarray,
 | |
|                         wavenumber: complex,
 | |
|                         omega: complex,
 | |
|                         dxes: dx_lists_t,
 | |
|                         epsilon: vfield_t,
 | |
|                         mu: vfield_t = None,
 | |
|                         prop_phase: float = 0,
 | |
|                         ) -> Tuple[vfield_t, vfield_t]:
 | |
|     """
 | |
|     Given a vector e_xy containing the vectorized E_x and E_y fields,
 | |
|      returns normalized, vectorized E and H fields for the system.
 | |
| 
 | |
|     :param e_xy: Vector containing E_x and E_y fields
 | |
|     :param wavenumber: Wavenumber satisfying `operator_e(...) @ e_xy == wavenumber**2 * e_xy`
 | |
|     :param omega: The angular frequency of the system
 | |
|     :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
 | |
|     :param epsilon: Vectorized dielectric constant grid
 | |
|     :param mu: Vectorized magnetic permeability grid (default 1 everywhere)
 | |
|     :param prop_phase: Phase shift (dz * corrected_wavenumber) over 1 cell in propagation direction.
 | |
|         Default 0 (continuous propagation direction, i.e. dz->0).
 | |
|     :return: Normalized, vectorized (e, h) containing all vector components.
 | |
|     """
 | |
|     e = exy2e(wavenumber=wavenumber, dxes=dxes, epsilon=epsilon) @ e_xy
 | |
|     h = exy2h(wavenumber=wavenumber, omega=omega, dxes=dxes, epsilon=epsilon, mu=mu) @ e_xy
 | |
|     e_norm, h_norm = _normalized_fields(e=e, h=h, omega=omega, dxes=dxes, epsilon=epsilon,
 | |
|                                         mu=mu, prop_phase=prop_phase)
 | |
|     return e_norm, h_norm
 | |
| 
 | |
| 
 | |
| def normalized_fields_h(h_xy: numpy.ndarray,
 | |
|                         wavenumber: complex,
 | |
|                         omega: complex,
 | |
|                         dxes: dx_lists_t,
 | |
|                         epsilon: vfield_t,
 | |
|                         mu: vfield_t = None,
 | |
|                         prop_phase: float = 0,
 | |
|                         ) -> Tuple[vfield_t, vfield_t]:
 | |
|     """
 | |
|     Given a vector e_xy containing the vectorized E_x and E_y fields,
 | |
|      returns normalized, vectorized E and H fields for the system.
 | |
| 
 | |
|     :param e_xy: Vector containing E_x and E_y fields
 | |
|     :param wavenumber: Wavenumber satisfying `operator_e(...) @ e_xy == wavenumber**2 * e_xy`
 | |
|     :param omega: The angular frequency of the system
 | |
|     :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
 | |
|     :param epsilon: Vectorized dielectric constant grid
 | |
|     :param mu: Vectorized magnetic permeability grid (default 1 everywhere)
 | |
|     :param dxes_prop: Grid cell width in the propagation direction. Default 0 (continuous).
 | |
|     :return: Normalized, vectorized (e, h) containing all vector components.
 | |
|     """
 | |
|     e = hxy2e(wavenumber=wavenumber, omega=omega, dxes=dxes, epsilon=epsilon, mu=mu) @ h_xy
 | |
|     h = hxy2h(wavenumber=wavenumber, dxes=dxes, mu=mu) @ h_xy
 | |
|     e_norm, h_norm = _normalized_fields(e=e, h=h, omega=omega, dxes=dxes, epsilon=epsilon,
 | |
|                                         mu=mu, prop_phase=prop_phase)
 | |
|     return e_norm, h_norm
 | |
| 
 | |
| 
 | |
| def _normalized_fields(e: numpy.ndarray,
 | |
|                        h: numpy.ndarray,
 | |
|                        omega: complex,
 | |
|                        dxes: dx_lists_t,
 | |
|                        epsilon: vfield_t,
 | |
|                        mu: vfield_t = None,
 | |
|                        prop_phase: float = 0,
 | |
|                        ) -> Tuple[vfield_t, vfield_t]:
 | |
|     # TODO documentation
 | |
|     shape = [s.size for s in dxes[0]]
 | |
|     dxes_real = [[numpy.real(d) for d in numpy.meshgrid(*dxes[v], indexing='ij')] for v in (0, 1)]
 | |
| 
 | |
|     E = unvec(e, shape)
 | |
|     H = unvec(h, shape)
 | |
| 
 | |
|     # Find time-averaged Sz and normalize to it
 | |
|     # H phase is adjusted by a half-cell forward shift for Yee cell, and 1-cell reverse shift for Poynting
 | |
|     phase = numpy.exp(-1j * -prop_phase / 2)
 | |
|     Sz_a = E[0] * numpy.conj(H[1] * phase) * dxes_real[0][1] * dxes_real[1][0]
 | |
|     Sz_b = E[1] * numpy.conj(H[0] * phase) * dxes_real[0][0] * dxes_real[1][1]
 | |
|     Sz_tavg = numpy.real(Sz_a.sum() - Sz_b.sum()) * 0.5       # 0.5 since E, H are assumed to be peak (not RMS) amplitudes
 | |
|     assert Sz_tavg > 0, 'Found a mode propagating in the wrong direction! Sz_tavg={}'.format(Sz_tavg)
 | |
| 
 | |
|     energy = epsilon * e.conj() * e
 | |
| 
 | |
|     norm_amplitude = 1 / numpy.sqrt(Sz_tavg)
 | |
|     norm_angle = -numpy.angle(e[energy.argmax()])       # Will randomly add a negative sign when mode is symmetric
 | |
| 
 | |
|     # Try to break symmetry to assign a consistent sign [experimental TODO]
 | |
|     E_weighted = unvec(e * energy * numpy.exp(1j * norm_angle), shape)
 | |
|     sign = numpy.sign(E_weighted[:, :max(shape[0]//2, 1), :max(shape[1]//2, 1)].real.sum())
 | |
| 
 | |
|     norm_factor = sign * norm_amplitude * numpy.exp(1j * norm_angle)
 | |
| 
 | |
|     e *= norm_factor
 | |
|     h *= norm_factor
 | |
| 
 | |
|     return e, h
 | |
| 
 | |
| 
 | |
| def exy2h(wavenumber: complex,
 | |
|           omega: complex,
 | |
|           dxes: dx_lists_t,
 | |
|           epsilon: vfield_t,
 | |
|           mu: vfield_t = None
 | |
|           ) -> sparse.spmatrix:
 | |
|     """
 | |
|     Operator which transforms the vector e_xy containing the vectorized E_x and E_y fields,
 | |
|      into a vectorized H containing all three H components
 | |
| 
 | |
|     :param wavenumber: Wavenumber satisfying `operator_e(...) @ e_xy == wavenumber**2 * e_xy`
 | |
|     :param omega: The angular frequency of the system
 | |
|     :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
 | |
|     :param epsilon: Vectorized dielectric constant grid
 | |
|     :param mu: Vectorized magnetic permeability grid (default 1 everywhere)
 | |
|     :return: Sparse matrix representing the operator
 | |
|     """
 | |
|     e2hop = e2h(wavenumber=wavenumber, omega=omega, dxes=dxes, mu=mu)
 | |
|     return e2hop @ exy2e(wavenumber=wavenumber, dxes=dxes, epsilon=epsilon)
 | |
| 
 | |
| 
 | |
| def hxy2e(wavenumber: complex,
 | |
|           omega: complex,
 | |
|           dxes: dx_lists_t,
 | |
|           epsilon: vfield_t,
 | |
|           mu: vfield_t = None
 | |
|           ) -> sparse.spmatrix:
 | |
|     """
 | |
|     Operator which transforms the vector h_xy containing the vectorized H_x and H_y fields,
 | |
|      into a vectorized E containing all three E components
 | |
| 
 | |
|     :param wavenumber: Wavenumber satisfying `operator_h(...) @ h_xy == wavenumber**2 * h_xy`
 | |
|     :param omega: The angular frequency of the system
 | |
|     :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
 | |
|     :param epsilon: Vectorized dielectric constant grid
 | |
|     :param mu: Vectorized magnetic permeability grid (default 1 everywhere)
 | |
|     :return: Sparse matrix representing the operator
 | |
|     """
 | |
|     h2eop = h2e(wavenumber=wavenumber, omega=omega, dxes=dxes, epsilon=epsilon)
 | |
|     return h2eop @ hxy2h(wavenumber=wavenumber, dxes=dxes, mu=mu)
 | |
| 
 | |
| 
 | |
| def hxy2h(wavenumber: complex,
 | |
|           dxes: dx_lists_t,
 | |
|           mu: vfield_t = None
 | |
|           ) -> sparse.spmatrix:
 | |
|     """
 | |
|     Operator which transforms the vector h_xy containing the vectorized H_x and H_y fields,
 | |
|      into a vectorized H containing all three H components
 | |
| 
 | |
|     :param wavenumber: Wavenumber satisfying `operator_h(...) @ h_xy == wavenumber**2 * h_xy`
 | |
|     :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
 | |
|     :param mu: Vectorized magnetic permeability grid (default 1 everywhere)
 | |
|     :return: Sparse matrix representing the operator
 | |
|     """
 | |
|     Dfx, Dfy = operators.deriv_forward(dxes[0])
 | |
|     hxy2hz = sparse.hstack((Dfx, Dfy)) / (1j * wavenumber)
 | |
| 
 | |
|     if not numpy.any(numpy.equal(mu, None)):
 | |
|         mu_parts = numpy.split(mu, 3)
 | |
|         mu_xy = sparse.diags(numpy.hstack((mu_parts[0], mu_parts[1])))
 | |
|         mu_z_inv = sparse.diags(1 / mu_parts[2])
 | |
| 
 | |
|         hxy2hz = mu_z_inv @ hxy2hz @ mu_xy
 | |
| 
 | |
|     n_pts = dxes[1][0].size * dxes[1][1].size
 | |
|     op = sparse.vstack((sparse.eye(2 * n_pts),
 | |
|                         hxy2hz))
 | |
|     return op
 | |
| 
 | |
| 
 | |
| def exy2e(wavenumber: complex,
 | |
|           dxes: dx_lists_t,
 | |
|           epsilon: vfield_t,
 | |
|           ) -> sparse.spmatrix:
 | |
|     """
 | |
|     Operator which transforms the vector e_xy containing the vectorized E_x and E_y fields,
 | |
|      into a vectorized E containing all three E components
 | |
| 
 | |
|     :param wavenumber: Wavenumber satisfying `operator_e(...) @ e_xy == wavenumber**2 * e_xy`
 | |
|     :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
 | |
|     :param epsilon: Vectorized dielectric constant grid
 | |
|     :return: Sparse matrix representing the operator
 | |
|     """
 | |
|     Dbx, Dby = operators.deriv_back(dxes[1])
 | |
|     exy2ez = sparse.hstack((Dbx, Dby)) / (1j * wavenumber)
 | |
| 
 | |
|     if not numpy.any(numpy.equal(epsilon, None)):
 | |
|         epsilon_parts = numpy.split(epsilon, 3)
 | |
|         epsilon_xy = sparse.diags(numpy.hstack((epsilon_parts[0], epsilon_parts[1])))
 | |
|         epsilon_z_inv = sparse.diags(1 / epsilon_parts[2])
 | |
| 
 | |
|         exy2ez = epsilon_z_inv @ exy2ez @ epsilon_xy
 | |
| 
 | |
|     n_pts = dxes[0][0].size * dxes[0][1].size
 | |
|     op = sparse.vstack((sparse.eye(2 * n_pts),
 | |
|                         exy2ez))
 | |
|     return op
 | |
| 
 | |
| 
 | |
| def e2h(wavenumber: complex,
 | |
|         omega: complex,
 | |
|         dxes: dx_lists_t,
 | |
|         mu: vfield_t = None
 | |
|         ) -> sparse.spmatrix:
 | |
|     """
 | |
|     Returns an operator which, when applied to a vectorized E eigenfield, produces
 | |
|      the vectorized H eigenfield.
 | |
| 
 | |
|     :param wavenumber: Wavenumber satisfying A @ v == wavenumber**2 * v
 | |
|     :param omega: The angular frequency of the system
 | |
|     :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
 | |
|     :param mu: Vectorized magnetic permeability grid (default 1 everywhere)
 | |
|     :return: Sparse matrix representation of the operator
 | |
|     """
 | |
|     op = curl_e(wavenumber, dxes) / (-1j * omega)
 | |
|     if not numpy.any(numpy.equal(mu, None)):
 | |
|         op = sparse.diags(1 / mu) @ op
 | |
|     return op
 | |
| 
 | |
| 
 | |
| def h2e(wavenumber: complex,
 | |
|         omega: complex,
 | |
|         dxes: dx_lists_t,
 | |
|         epsilon: vfield_t
 | |
|         ) -> sparse.spmatrix:
 | |
|     """
 | |
|     Returns an operator which, when applied to a vectorized H eigenfield, produces
 | |
|      the vectorized E eigenfield.
 | |
| 
 | |
|     :param wavenumber: Wavenumber satisfying A @ v == wavenumber**2 * v
 | |
|     :param omega: The angular frequency of the system
 | |
|     :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
 | |
|     :param epsilon: Vectorized dielectric constant grid
 | |
|     :return: Sparse matrix representation of the operator
 | |
|     """
 | |
|     op = sparse.diags(1 / (1j * omega * epsilon)) @ curl_h(wavenumber, dxes)
 | |
|     return op
 | |
| 
 | |
| 
 | |
| def curl_e(wavenumber: complex, dxes: dx_lists_t) -> sparse.spmatrix:
 | |
|     """
 | |
|     Discretized curl operator for use with the waveguide E field.
 | |
| 
 | |
|     :param wavenumber: Wavenumber satisfying A @ v == wavenumber**2 * v
 | |
|     :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
 | |
|     :return: Sparse matrix representation of the operator
 | |
|     """
 | |
|     n = 1
 | |
|     for d in dxes[0]:
 | |
|         n *= len(d)
 | |
| 
 | |
|     Bz = -1j * wavenumber * sparse.eye(n)
 | |
|     Dfx, Dfy = operators.deriv_forward(dxes[0])
 | |
|     return operators.cross([Dfx, Dfy, Bz])
 | |
| 
 | |
| 
 | |
| def curl_h(wavenumber: complex, dxes: dx_lists_t) -> sparse.spmatrix:
 | |
|     """
 | |
|     Discretized curl operator for use with the waveguide H field.
 | |
| 
 | |
|     :param wavenumber: Wavenumber satisfying A @ v == wavenumber**2 * v
 | |
|     :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
 | |
|     :return: Sparse matrix representation of the operator
 | |
|     """
 | |
|     n = 1
 | |
|     for d in dxes[1]:
 | |
|         n *= len(d)
 | |
| 
 | |
|     Bz = -1j * wavenumber * sparse.eye(n)
 | |
|     Dbx, Dby = operators.deriv_back(dxes[1])
 | |
|     return operators.cross([Dbx, Dby, Bz])
 | |
| 
 | |
| 
 | |
| def h_err(h: vfield_t,
 | |
|           wavenumber: complex,
 | |
|           omega: complex,
 | |
|           dxes: dx_lists_t,
 | |
|           epsilon: vfield_t,
 | |
|           mu: vfield_t = None
 | |
|           ) -> float:
 | |
|     """
 | |
|     Calculates the relative error in the H field
 | |
| 
 | |
|     :param h: Vectorized H field
 | |
|     :param wavenumber: Wavenumber satisfying A @ v == wavenumber**2 * v
 | |
|     :param omega: The angular frequency of the system
 | |
|     :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
 | |
|     :param epsilon: Vectorized dielectric constant grid
 | |
|     :param mu: Vectorized magnetic permeability grid (default 1 everywhere)
 | |
|     :return: Relative error norm(OP @ h) / norm(h)
 | |
|     """
 | |
|     ce = curl_e(wavenumber, dxes)
 | |
|     ch = curl_h(wavenumber, dxes)
 | |
| 
 | |
|     eps_inv = sparse.diags(1 / epsilon)
 | |
| 
 | |
|     if numpy.any(numpy.equal(mu, None)):
 | |
|         op = ce @ eps_inv @ ch @ h - omega ** 2 * h
 | |
|     else:
 | |
|         op = ce @ eps_inv @ ch @ h - omega ** 2 * (mu * h)
 | |
| 
 | |
|     return norm(op) / norm(h)
 | |
| 
 | |
| 
 | |
| def e_err(e: vfield_t,
 | |
|           wavenumber: complex,
 | |
|           omega: complex,
 | |
|           dxes: dx_lists_t,
 | |
|           epsilon: vfield_t,
 | |
|           mu: vfield_t = None
 | |
|           ) -> float:
 | |
|     """
 | |
|     Calculates the relative error in the E field
 | |
| 
 | |
|     :param e: Vectorized E field
 | |
|     :param wavenumber: Wavenumber satisfying A @ v == wavenumber**2 * v
 | |
|     :param omega: The angular frequency of the system
 | |
|     :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
 | |
|     :param epsilon: Vectorized dielectric constant grid
 | |
|     :param mu: Vectorized magnetic permeability grid (default 1 everywhere)
 | |
|     :return: Relative error norm(OP @ e) / norm(e)
 | |
|     """
 | |
|     ce = curl_e(wavenumber, dxes)
 | |
|     ch = curl_h(wavenumber, dxes)
 | |
| 
 | |
|     if numpy.any(numpy.equal(mu, None)):
 | |
|         op = ch @ ce @ e - omega ** 2 * (epsilon * e)
 | |
|     else:
 | |
|         mu_inv = sparse.diags(1 / mu)
 | |
|         op = ch @ mu_inv @ ce @ e - omega ** 2 * (epsilon * e)
 | |
| 
 | |
|     return norm(op) / norm(e)
 | |
| 
 | |
| 
 | |
| def cylindrical_operator(omega: complex,
 | |
|                          dxes: dx_lists_t,
 | |
|                          epsilon: vfield_t,
 | |
|                          r0: float,
 | |
|                          ) -> sparse.spmatrix:
 | |
|     """
 | |
|     Cylindrical coordinate waveguide operator of the form
 | |
| 
 | |
|     TODO
 | |
| 
 | |
|     for use with a field vector of the form [E_r, E_y].
 | |
| 
 | |
|     This operator can be used to form an eigenvalue problem of the form
 | |
|     A @ [E_r, E_y] = wavenumber**2 * [E_r, E_y]
 | |
| 
 | |
|     which can then be solved for the eigenmodes of the system (an exp(-i * wavenumber * theta)
 | |
|      theta-dependence is assumed for the fields).
 | |
| 
 | |
|     :param omega: The angular frequency of the system
 | |
|     :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
 | |
|     :param epsilon: Vectorized dielectric constant grid
 | |
|     :param r0: Radius of curvature for the simulation. This should be the minimum value of
 | |
|         r within the simulation domain.
 | |
|     :return: Sparse matrix representation of the operator
 | |
|     """
 | |
| 
 | |
|     Dfx, Dfy = operators.deriv_forward(dxes[0])
 | |
|     Dbx, Dby = operators.deriv_back(dxes[1])
 | |
| 
 | |
|     rx = r0 + numpy.cumsum(dxes[0][0])
 | |
|     ry = r0 + dxes[0][0]/2.0 + numpy.cumsum(dxes[1][0])
 | |
|     tx = rx/r0
 | |
|     ty = ry/r0
 | |
| 
 | |
|     Tx = sparse.diags(vec(tx[:, None].repeat(dxes[0][1].size, axis=1)))
 | |
|     Ty = sparse.diags(vec(ty[:, None].repeat(dxes[1][1].size, axis=1)))
 | |
| 
 | |
|     eps_parts = numpy.split(epsilon, 3)
 | |
|     eps_x = sparse.diags(eps_parts[0])
 | |
|     eps_y = sparse.diags(eps_parts[1])
 | |
|     eps_z_inv = sparse.diags(1 / eps_parts[2])
 | |
| 
 | |
|     pa = sparse.vstack((Dfx, Dfy)) @ Tx @ eps_z_inv @ sparse.hstack((Dbx, Dby))
 | |
|     pb = sparse.vstack((Dfx, Dfy)) @ Tx @ eps_z_inv @ sparse.hstack((Dby, Dbx))
 | |
|     a0 = Ty @ eps_x + omega**-2 * Dby @ Ty @ Dfy
 | |
|     a1 = Tx @ eps_y + omega**-2 * Dbx @ Ty @ Dfx
 | |
|     b0 = Dbx @ Ty @ Dfy
 | |
|     b1 = Dby @ Ty @ Dfx
 | |
| 
 | |
|     diag = sparse.block_diag
 | |
|     op = (omega**2 * diag((Tx, Ty)) + pa) @ diag((a0, a1)) + \
 | |
|         - (sparse.bmat(((None, Ty), (Tx, None))) + omega**-2 * pb) @ diag((b0, b1))
 | |
| 
 | |
|     return op
 | |
| 
 |