Ejemplo n.º 1
0
def S_T(At, Bt):
    '''
    function to create scattering matrices in the transmission regions
    different from S_layer because these regions only have one boundary condition to satisfy
    :param At:
    :param Bt:
    :return:
    '''
    # assert type(At) == np.matrixlib.defmatrix.matrix, 'not np.matrix'
    # assert type(Bt) == np.matrixlib.defmatrix.matrix, 'not np.matrix'

    # S11 = (Bt) * np.linalg.inv(At);
    # S21 = 2*np.linalg.inv(At);
    # S12 = 0.5*(At - Bt * np.linalg.inv(At) * Bt);
    # S22 = - np.linalg.inv(At)*Bt
    S11 = (Bt) @ np.linalg.inv(At)
    S21 = 2 * np.linalg.inv(At)
    S12 = 0.5 * (At - Bt @ bslash(At, Bt))
    S22 = -bslash(At, Bt)
    S_dict = {
        'S11': S11,
        'S22': S22,
        'S12': S12,
        'S21': S21
    }
    S = np.block([[S11, S12], [S21, S22]])
    return S, S_dict
Ejemplo n.º 2
0
def S_R(Ar, Br):
    '''
    function to create scattering matrices in the reflection regions
    different from S_layer because these regions only have one boundary condition to satisfy
    :param Ar:
    :param Br:
    :return:
    '''
    # assert type(Ar) == np.matrixlib.defmatrix.matrix, 'not np.matrix'
    # assert type(Br) == np.matrixlib.defmatrix.matrix, 'not np.matrix'

    #
    # S11 = -np.linalg.inv(Ar) * Br;
    # S12 = 2*np.linalg.inv(Ar);
    # S21 = 0.5*(Ar - Br * np.linalg.inv(Ar) * Br);
    # S22 = Br * np.linalg.inv(Ar)

    S11 = -bslash(Ar, Br)
    S12 = 2 * np.linalg.inv(Ar)
    S21 = 0.5 * (Ar - Br @ bslash(Ar, Br))
    S22 = Br @ np.linalg.inv(Ar)
    S_dict = {
        'S11': S11,
        'S22': S22,
        'S12': S12,
        'S21': S21
    }
    S = np.block([[S11, S12], [S21, S22]])
    return S, S_dict
Ejemplo n.º 3
0
def P_matrix(Kx, Ky, e_conv, mu_conv):
    assert type(Kx) == np.ndarray, 'not array'
    assert type(Ky) == np.ndarray, 'not array'
    assert type(e_conv) == np.ndarray, 'not array'

    P = np.block(
        [[Kx @ bslash(e_conv, Ky), mu_conv - Kx @ bslash(e_conv, Kx)],
         [Ky @ bslash(e_conv, Ky) - mu_conv, -Ky @ bslash(e_conv, Kx)]])
    return P
def RedhefferStar(SA, SB):  #SA and SB are both 2x2 block matrices;
    '''
    RedhefferStar for arbitrarily sized 2x2 block matrices for RCWA
    :param SA: dictionary containing the four sub-blocks
    :param SB: dictionary containing the four sub-blocks,
    keys are 'S11', 'S12', 'S21', 'S22'
    :return:
    '''

    assert type(SA) == dict, 'not dict'
    assert type(SB) == dict, 'not dict'

    # once we break every thing like this, we should still have matrices
    SA_11 = SA['S11']
    SA_12 = SA['S12']
    SA_21 = SA['S21']
    SA_22 = SA['S22']
    SB_11 = SB['S11']
    SB_12 = SB['S12']
    SB_21 = SB['S21']
    SB_22 = SB['S22']
    N = len(SA_11)  #SA_11 should be square so length is fine
    I = np.matrix(np.identity(N))

    # D = np.linalg.inv(I-SB_11*SA_22);
    # F = np.linalg.inv(I-SA_22*SB_11);
    #
    # SAB_11 = SA_11 + SA_12*D*SB_11*SA_21;
    # SAB_12 = SA_12*D*SB_12;
    # SAB_21 = SB_21*F*SA_21;
    # SAB_22 = SB_22 + SB_21*F*SA_22*SB_12;

    D = (I - SB_11 @ SA_22)
    F = (I - SA_22 @ SB_11)

    SAB_11 = SA_11 + SA_12 @ bslash(D, SB_11) @ SA_21
    SAB_12 = SA_12 @ bslash(D, SB_12)
    SAB_21 = SB_21 @ bslash(F, SA_21)
    SAB_22 = SB_22 + SB_21 @ bslash(F, SA_22) @ SB_12

    SAB = np.block([[SAB_11, SAB_12], [SAB_21, SAB_22]])
    SAB_dict = {
        'S11': SAB_11,
        'S22': SAB_22,
        'S12': SAB_12,
        'S21': SAB_21
    }

    return SAB, SAB_dict
Ejemplo n.º 5
0
def Q_matrix(Kx, Ky, e_conv, mu_conv):
    '''
    pressently assuming non-magnetic material so mu_conv = I
    :param Kx: now a matrix (NM x NM)
    :param Ky: now a matrix
    :param e_conv: (NM x NM) matrix containing the 2d convmat
    :return:
    '''

    assert type(Kx) == np.ndarray, 'not array'
    assert type(Ky) == np.ndarray, 'not array'
    assert type(e_conv) == np.ndarray, 'not array'

    return np.block(
        [[Kx @ bslash(mu_conv, Ky), e_conv - Kx @ bslash(mu_conv, Kx)],
         [Ky @ bslash(mu_conv, Ky) - e_conv, -Ky @ bslash(mu_conv, Kx)]])
Ejemplo n.º 6
0
def B(W_layer, Wg, V_layer, Vg):  #MINUS SIGN
    '''
    :param W_layer: layer E-modes
    :param Wg: gap E-field modes
    :param V_layer: layer H_modes
    :param Vg: gap H-field modes
    # the numbering is just 1 and 2 because the order differs if we're in the structure
    # or outsid eof it
    :return:
    '''

    # assert type(W_layer) == np.matrixlib.defmatrix.matrix, 'not np.matrix'
    # assert type(Wg) == np.matrixlib.defmatrix.matrix, 'not np.matrix'
    # assert type(V_layer) == np.matrixlib.defmatrix.matrix, 'not np.matrix'
    # assert type(Vg) == np.matrixlib.defmatrix.matrix, 'not np.matrix'

    #B = np.linalg.inv(W_layer) * Wg - np.linalg.inv(V_layer) * Vg;
    B = bslash(W_layer, Wg) - bslash(V_layer, Vg)

    return B
Ejemplo n.º 7
0
def A(W_layer, Wg, V_layer, Vg):  # PLUS SIGN
    '''
    OFFICIAL EMLAB prescription
    inv(W_layer)*W_gap
    :param W_layer: layer E-modes
    :param Wg: gap E-field modes
    :param V_layer: layer H_modes
    :param Vg: gap H-field modes
    # the numbering is just 1 and 2 because the order differs if we're in the structure
    # or outsid eof it
    :return:
    '''
    # assert type(W_layer) == np.matrixlib.defmatrix.matrix, 'not np.matrix'
    # assert type(Wg) == np.matrixlib.defmatrix.matrix, 'not np.matrix'
    # assert type(V_layer) == np.matrixlib.defmatrix.matrix, 'not np.matrix'
    # assert type(Vg) == np.matrixlib.defmatrix.matrix, 'not np.matrix'

    #A = np.linalg.inv(W_layer) * Wg + np.linalg.inv(V_layer) * Vg;
    A = bslash(W_layer, Wg) + bslash(V_layer, Vg)

    return A
Ejemplo n.º 8
0
def S_layer(A, B, Li, k0, modes):
    '''
    function to create scatter matrix in the ith layer of the uniform layer structure
    we assume that gap layers are used so we need only one A and one B
    :param A: function A =
    :param B: function B
    :param k0 #free -space wavevector magnitude (normalization constant) in Si Units
    :param Li #length of ith layer (in Si units)
    :param modes, eigenvalue matrix
    :return: S (4x4 scatter matrix) and Sdict, which contains the 2x2 block matrix as a dictionary
    '''
    # assert type(A) == np.matrixlib.defmatrix.matrix, 'not np.matrix'
    # assert type(B) == np.matrixlib.defmatrix.matrix, 'not np.matrix'

    #sign convention (EMLAB is exp(-1i*k\dot r))
    X_i = np.diag(np.exp(-np.diag(modes) * Li * k0))
    #never use expm

    #term1 = (A - X_i * B * A.I * X_i * B).I
    # S11 = term1 * (X_i * B * A.I * X_i * A - B);
    # S12 = term1 * (X_i) * (A - B * A.I * B);
    # S22 = S11;
    # S21 = S12;
    term1 = (A - X_i @ B @ bslash(A, X_i) @ B)
    S11 = bslash(term1, (X_i @ B @ bslash(A, X_i) @ A - B))
    S12 = bslash(term1, (X_i) @ (A - B @ bslash(A, B)))
    S22 = S11
    S21 = S12

    S_dict = {
        'S11': S11,
        'S22': S22,
        'S12': S12,
        'S21': S21
    }
    S = np.block([[S11, S12], [S21, S22]])
    return S, S_dict
    ## IMPLEMENT SCALING: these are the fourier orders of the x-direction decomposition.
    KX = np.diag((k_xi/k0)); #singular since we have a n=0, m= 0 order and incidence is normal

    # PQ_block = np.block([[zeros, np.linalg.inv(E_conv_inv)],[KX@bslash(E, KX) - I, zeros]])
    # # plt.imshow(np.abs(PQ_block));
    # # plt.show();
    # print('condition of PQ block: '+str(np.linalg.cond(PQ_block)))
    # big_eigenvals, bigW = LA.eig(PQ_block);
    # print((bigW.shape, big_eigenvals.shape))
    # Wp = bigW[0:PQ, PQ:]
    # plt.imshow(abs(bigW))
    # plt.show();
    ## construct matrix of Gamma^2 ('constant' term in ODE):

    ## one thing that isn't obvious is that are we doing element by element division or is it matricial
    B = (KX@bslash(Ezz, KX) - I);
    bE = np.linalg.inv(E_conv_inv) + bslash(Ezz,(Exz@Ezx)); #/Ezz;
    G = j*bslash(Ezz,Ezx) @ KX;
    H = j*KX @bslash(Ezz, Exz);
    #print((G,H))
    print((bE.shape,G.shape, H.shape))
    print((np.linalg.cond(B), np.linalg.cond(bE)))


    M = np.linalg.inv(bE);
    K = -(B + [email protected](bE)@G);
    C = -np.linalg.inv(bE)@G - [email protected](bE);
    Z = np.zeros_like(M);
    I = np.eye(M.shape[0], M.shape[1]);
    OA = np.block([[M, Z],[Z, I]])
    OB = np.block(np.block([[C, K],[-I, Z]]))
    ## Region 2; transmission
    n2 = 1

    #from the kx_components given the indices and wvln
    #2 * np.pi * np.arange(-N_p, N_p + 1) / (k0 * a_x)
    indices = np.arange(-num_ord, num_ord + 1)
    kx_array = (n1 * np.sin(theta_inc) + indices * (lam0 / lattice_constant))
    #0 is one of them, k0*lam0 = 2*pi

    ## IMPLEMENT SCALING: these are the fourier orders of the x-direction decomposition.
    KX = np.diag(kx_array)
    #singular since we have a n=0, m= 0 order and incidence is normal

    ## construct matrix of Gamma^2 ('constant' term in ODE):
    #use fast fourier factorization rules here...
    Q = I - KX @ bslash(E_conv, KX)
    PQ = -bslash(E_conv_inv, Q)

    ## ================================================================================================##
    eigenvals, W = LA.eig(PQ)
    #A is NOT HERMITIAN
    #we should be gauranteed that all eigenvals are REAL
    eigenvals = eigenvals.astype('complex')
    lambda_matrix = np.diag((np.sqrt((eigenvals))))

    ## THIS NEGATIVE SIGN IS CRUCIAL, but I'm not sure why
    V = -em.eigen_V(Q, W, lambda_matrix)
    kz_inc = n1
    ## ================================================================================================##

    ## scattering matrix needed for 'gap medium'
Ejemplo n.º 11
0
    ## =====================STRUCTURE======================##

    ## Region I: reflected region (half space)
    n1 = 1;#cmath.sqrt(-1)*1e-12; #apparently small complex perturbations are bad in Region 1, these shouldn't be necessary

    ## Region 2; transmitted region
    n2 = 1;

    #from the kx_components given the indices and wvln
    kx_array = k0*(n1*np.sin(theta) + indices*(lam0 / lattice_constant)); #0 is one of them, k0*lam0 = 2*pi
    k_xi = kx_array;
    ## IMPLEMENT SCALING: these are the fourier orders of the x-direction decomposition.
    KX = np.diag((k_xi/k0)); #singular since we have a n=0, m= 0 order and incidence is normal

    ## construct matrix of Gamma^2 ('constant' term in ODE):
    A = bslash(E_conv_inv,KX*bslash(E, KX) - I); #conditioning of this matrix is not bad, A SHOULD BE SYMMETRIC
    A = bslash(E_conv_inv,KX*bslash(E, KX) - I); #conditioning of this matrix is not bad, A SHOULD BE SYMMETRIC

    #sum of a symmetric matrix and a diagonal matrix should be symmetric;

    ##
    # when we calculate eigenvals, how do we know the eigenvals correspond to each particular fourier order?
    eigenvals, W = LA.eigh(A); #A should be symmetric or hermitian
    #we should be gauranteed that all eigenvals are REAL
    eigenvals = eigenvals.astype('complex');
    Q = np.diag(np.sqrt(eigenvals)); #Q should only be positive square root of eigenvals
    V = bslash(E,W)@Q; #H modes

    ## this is the great typo which has killed us all this time
    X = np.diag(np.exp(-k0*np.diag(Q)*d)); #this is poorly conditioned because exponentiation
    ## pointwise exponentiation vs exponentiating a matrix
def RWCA_1D_TM(E, E_conv_inv, lattice_constant, theta, num_ord, wavelength_scan,d):
    '''
    :param E: [e]
    :param E_conv_inv: [1/e]
    :param lattice_constant:
    :param theta:
    :param num_ord:
    :param wavelength_scan:
    :return:
    '''
    I = np.identity(2 * num_ord + 1)
    indices = np.arange(-num_ord, num_ord + 1);
    spectra_R = []; spectra_T = []; #arrays to hold reflectivity and transmissitivity
    PQ = 2*num_ord+1;
    # E is now the convolution of fourier amplitudes
    for wvlen in wavelength_scan:
        j = cmath.sqrt(-1);
        lam0 = wvlen;     k0 = 2 * np.pi / lam0; #free space wavelength in SI units
        print('wavelength: ' + str(wvlen));
        ## =====================STRUCTURE======================##

        ## Region I: reflected region (half space)
        n1 = 1;#cmath.sqrt(-1)*1e-12; #apparently small complex perturbations are bad in Region 1, these shouldn't be necessary

        ## Region 2; transmitted region
        n2 = 1;

        #from the kx_components given the indices and wvln
        kx_array = k0*(n1*np.sin(theta) + indices*(lam0 / lattice_constant)); #0 is one of them, k0*lam0 = 2*pi
        k_xi = kx_array;
        ## IMPLEMENT SCALING: these are the fourier orders of the x-direction decomposition.
        KX = np.diag((k_xi/k0)); #singular since we have a n=0, m= 0 order and incidence is normal

        ## construct matrix of Gamma^2 ('constant' term in ODE):
        A = np.linalg.inv(E_conv_inv)@(KX@bslash(E, KX) - I); #conditioning of this matrix is not bad, A SHOULD BE SYMMETRIC
        # when we calculate eigenvals, how do we know the eigenvals correspond to each particular fourier order?
        #eigenvals, W = LA.eigh(A); #A should be symmetric or hermitian, which won't be the case in the TM mode
        eigenvals, W = LA.eig(A);
        #we should be gauranteed that all eigenvals are REAL
        eigenvals = eigenvals.astype('complex');
        Q = np.diag(np.sqrt(eigenvals)); #Q should only be positive square root of eigenvals
        V = E_conv_inv@(W@Q); #H modes

        ## this is the great typo which has killed us all this time
        X = np.diag(np.exp(-k0*np.diag(Q)*d)); #this is poorly conditioned because exponentiation
        ## pointwise exponentiation vs exponentiating a matrix

        ## observation: almost everything beyond this point is worse conditioned
        k_I = k0**2*(n1**2 - (k_xi/k0)**2);                 #k_z in reflected region k_I,zi
        k_II = k0**2*(n2**2 - (k_xi/k0)**2);   #k_z in transmitted region
        k_I = k_I.astype('complex'); k_I = np.sqrt(k_I);
        k_II = k_II.astype('complex'); k_II = np.sqrt(k_II);
        Z_I = np.diag(k_I / (n1**2 * k0 ));
        Z_II = np.diag(k_II /(n2**2 * k0));
        delta_i0 = np.zeros((len(kx_array),1));
        delta_i0[num_ord] = 1;
        n_delta_i0 = delta_i0*j*np.cos(theta)/n1;

        ## design auxiliary variables: SEE derivation in notebooks: RCWA_note.ipynb
        # we want to design the computation to avoid operating with X, particularly with inverses
        # since X is the worst conditioned thing

        O = np.block([
            [W, W],
            [V,-V]
        ]); #this is much better conditioned than S..
        f = I;
        g = j * Z_II; #all matrices
        fg = np.concatenate((f,g),axis = 0)
        ab = np.matmul(np.linalg.inv(O),fg);
        a = ab[0:PQ,:];
        b = ab[PQ:,:];

        term = X @ a @ np.linalg.inv(b) @ X;
        f = W @ (I+term);
        g = V@(-I+term);
        T = np.linalg.inv(np.matmul(j*Z_I, f) + g);
        T = np.dot(T, (np.dot(j*Z_I, delta_i0) + n_delta_i0));
        R = np.dot(f,T)-delta_i0; #shouldn't change
        T = np.dot(np.matmul(np.linalg.inv(b),X),T)

        ## calculate diffraction efficiencies
        #I would expect this number to be real...
        DE_ri = R*np.conj(R)*np.real(np.expand_dims(k_I,1))/(k0*n1*np.cos(theta));
        DE_ti = T*np.conj(T)*np.real(np.expand_dims(k_II,1)/n2**2)/(k0*np.cos(theta)/n1);

        #print(np.sum(DE_ri))
        spectra_R.append(np.sum(DE_ri)); #spectra_T.append(T);
        spectra_T.append(np.sum(DE_ti))

    return spectra_R, spectra_T;
    n1 = 1
    #cmath.sqrt(-1)*1e-12; #apparently small complex perturbations are bad in Region 1, these shouldn't be necessary

    ## Region 2; transmitted region
    n2 = 1

    #from the kx_components given the indices and wvln
    kx_array = k0 * (n1 * np.sin(theta) + indices * (lam0 / lattice_constant))
    #0 is one of them, k0*lam0 = 2*pi
    k_xi = kx_array
    ## IMPLEMENT SCALING: these are the fourier orders of the x-direction decomposition.
    KX = np.diag((k_xi / k0))
    #singular since we have a n=0, m= 0 order and incidence is normal

    ## construct matrix of Gamma^2 ('constant' term in ODE):
    A = np.linalg.inv(E_conv_inv) @ (KX @ bslash(E, KX) - I)
    #conditioning of this matrix is not bad, A SHOULD BE SYMMETRIC

    #sum of a symmetric matrix and a diagonal matrix should be symmetric;

    ##
    # when we calculate eigenvals, how do we know the eigenvals correspond to each particular fourier order?
    #eigenvals, W = LA.eigh(A); #A should be symmetric or hermitian, which won't be the case in the TM mode
    eigenvals, W = LA.eig(A)
    #we should be gauranteed that all eigenvals are REAL
    eigenvals = eigenvals.astype('complex')
    Q = np.diag(np.sqrt(eigenvals))
    #Q should only be positive square root of eigenvals
    V = E_conv_inv @ (W @ Q)
    #H modes