Пример #1
0
def solve_tmm(solar_cell, options):
    """ Calculates the reflection, transmission and absorption of a solar cell object using the transfer matrix method. Internally, it creates an OptiStack and then it calculates the optical properties of the whole structure.

    :param solar_cell: A solar_cell object
    :param options: Options for the solver
    :return: None
    """
    wl = options.wavelength

    # We include the shadowing losses
    initial = (1 - solar_cell.shading) if hasattr(solar_cell, 'shading') else 1

    # Now we calculate the absorbed and transmitted light. We first get all the relevant parameters from the objects
    all_layers = []
    for j, layer_object in enumerate(solar_cell):

        # Attenuation due to absorption in the AR coatings or any layer in the front that is not part of the junction
        if type(layer_object) is Layer:
            all_layers.append(layer_object)

        # For each junction, and layer within the junction, we get the absorption coefficient and the layer width.
        elif type(layer_object) in [TunnelJunction, Junction]:
            for i, layer in enumerate(layer_object):
                all_layers.append(layer)

    # With all the information, we create the optical stack
    no_back_reflexion = options.no_back_reflexion if 'no_back_reflexion' in options.keys() else True
    stack = OptiStack(all_layers, no_back_reflexion=no_back_reflexion)

    #dist = np.logspace(-1, np.log10(solar_cell.width * 1e9), int(1000 * np.log10(solar_cell.width * 1e9)))
    dist = np.arange(0, solar_cell.width * 1e9, 1)
    # print(len(dist))
    position = options.position if 'position' in options.keys() else dist

    print('Calculating RAT...')
    RAT = calculate_rat(stack, wl * 1e9, coherent=True)

    print('Calculating absorption profile...')
    out = calculate_absorption_profile(stack, wl * 1e9, dist=position)

    # With all this information, we are ready to calculate the differential absorption function
    diff_absorption, all_absorbed = calculate_absorption_tmm(out)

    # Each building block (layer or junction) needs to have access to the absorbed light in its region.
    # We update each object with that information.
    for j in range(len(solar_cell)):
        solar_cell[j].diff_absorption = diff_absorption
        solar_cell[j].absorbed = types.MethodType(absorbed, solar_cell[j])

    solar_cell.reflected = RAT['R'] * initial
    solar_cell.transmitted = (1 - RAT['R'] - all_absorbed) * initial
    solar_cell.absorbed = all_absorbed * initial
Пример #2
0
    def __init__(self, stack, coherent=True, coherency_list=None, no_back_reflection=False):

        """ Set up structure for TMM calculations

        :param stack: an OptiStack object.
        :param wavelength: Wavelengths (in nm) in which calculate the data. An array.
        :param angle: Angle (in radians) of the incident light. Default: 0 (normal incidence).
        :param pol: Polarisation of the light: 's', 'p' or 'u'. Default: 'u' (unpolarised).
        :param coherent: If the light is coherent or not. If not, a coherency list must be added.
        :param coherency_list: A list indicating in which layers light should be treated as coeherent ('c') and in which
        incoherent ('i'). It needs as many elements as layers in the structure.
        :param profile: whether or not to calculate the absorption profile
        :param layers: indices of the layers in which to calculate the absorption profile. Layer 0 is the incidence medium.
        :return: A dictionary with the R, A and T at the specified wavelengths and angle.
        """

        if 'OptiStack' in str(type(stack)):
            stack.no_back_reflection = no_back_reflection
        else:
            if hasattr(stack, 'substrate'):
                substrate = stack.substrate
            else:
                substrate = None
            stack = OptiStack(stack, no_back_reflection=no_back_reflection,
                              substrate=substrate)

        if not coherent:
            if coherency_list is not None:
                assert len(coherency_list) == stack.num_layers, \
                    'Error: The coherency list must have as many elements (now {}) as the ' \
                    'number of layers (now {}).'.format(len(coherency_list), stack.num_layers)

                if stack.no_back_reflection:
                    coherency_list = ['i'] + coherency_list + ['i', 'i']
                else:
                    coherency_list = ['i'] + coherency_list + ['i']

            else:
                raise Exception('Error: For incoherent or partly incoherent calculations you must supply the '
                                'coherency_list parameter with as many elements as the number of layers in the '
                                'structure')

        self.stack = stack
        self.coherent = coherent
        self.coherency_list = coherency_list
Пример #3
0
def solve_tmm(solar_cell, options):
    """ Calculates the reflection, transmission and absorption of a solar cell object using the transfer matrix method.
    Internally, it creates an OptiStack and then it calculates the optical properties of the whole structure.
    A substrate can be specified in the SolarCell object, which is treated as a semi-infinite transmission medium.
    Shading can also be specified (as a fraction).

    Relevant options are 'wl' (the wavelengths, in m), the incidence angle 'theta' (in degrees), the polarization 'pol' ('s',
    'p' or 'u'), 'position' (locations in m at which depth-dependent absorption is calculated), 'no_back_reflexion' and 'BL_correction'.
    'no_back_reflexion' sets whether reflections from the back surface are suppressed (if set to True, the default),
    or taken into account (if set to False).

    If 'BL_correction' is set to True, thick layers (thickness > 10*maximum wavelength) are treated incoherently using
    the Beer-Lambert law, to avoid the calculation of unphysical interference oscillations in the R/A/T spectra.

    A coherency_list option can be provided: this should have elements equal to the total number of layers (if a Junction
    contains multiple Layers, each should have its own entry in the coherency list). Each element is either 'c' for coherent
    treatment of that layer or 'i' for incoherent treatment.

    :param solar_cell: A SolarCell object
    :param options: Options for the solver
    :return: None
    """
    wl = options.wavelength
    BL_correction = options.BL_correction if 'BL_correction' in options.keys(
    ) else True
    theta = options.theta if 'theta' in options.keys(
    ) else 0  # angle IN DEGREES
    pol = options.pol if 'pol' in options.keys() else 'u'

    # We include the shadowing losses
    initial = (1 - solar_cell.shading) if hasattr(solar_cell, 'shading') else 1

    # Now we calculate the absorbed and transmitted light. We first get all the relevant parameters from the objects
    all_layers = []
    widths = []
    n_layers_junction = []

    for j, layer_object in enumerate(solar_cell):

        # Attenuation due to absorption in the AR coatings or any layer in the front that is not part of the junction
        if type(layer_object) is Layer:
            all_layers.append(layer_object)
            widths.append(layer_object.width)
            n_layers_junction.append(1)

        # For each junction, and layer within the junction, we get the absorption coefficient and the layer width.
        elif type(layer_object) in [TunnelJunction, Junction]:
            n_layers_junction.append(len(layer_object))
            for i, layer in enumerate(layer_object):
                all_layers.append(layer)
                widths.append(layer.width)

    # With all the information, we create the optical stack
    no_back_reflexion = options.no_back_reflexion if 'no_back_reflexion' in options.keys(
    ) else True

    full_stack = OptiStack(all_layers,
                           no_back_reflexion=no_back_reflexion,
                           substrate=solar_cell.substrate)

    if 'coherency_list' in options.keys():
        coherency_list = options.coherency_list
        coherent = False
        assert len(coherency_list) == full_stack.num_layers, \
            'Error: The coherency list must have as many elements (now {}) as the ' \
            'number of layers (now {}).'.format(len(coherency_list), full_stack.num_layers)
    else:
        coherency_list = None
        coherent = True

    if BL_correction and any(
            widths > 10 *
            np.max(wl)):  # assume it's safe to ignore interference effects
        make_incoherent = np.where(np.array(widths) > 10 * np.max(wl))[0]
        print('Treating layer(s) ' + str(make_incoherent).strip('[]') +
              ' incoherently')
        if not 'coherency_list' in options.keys():
            coherency_list = np.array(len(all_layers) * ['c'])
            coherent = False
        else:
            coherency_list = np.array(coherency_list)
        coherency_list[make_incoherent] = 'i'
        coherency_list = coherency_list.tolist()

    position = options.position * 1e9
    profile_position = position[position < sum(full_stack.widths)]

    print('Calculating RAT...')
    RAT = calculate_rat(full_stack,
                        wl * 1e9,
                        angle=theta,
                        coherent=coherent,
                        coherency_list=coherency_list,
                        no_back_reflexion=no_back_reflexion,
                        pol=pol)

    print('Calculating absorption profile...')
    out = calculate_absorption_profile(full_stack,
                                       wl * 1e9,
                                       dist=profile_position,
                                       angle=theta,
                                       no_back_reflexion=no_back_reflexion,
                                       pol=pol,
                                       coherent=coherent,
                                       coherency_list=coherency_list)

    # With all this information, we are ready to calculate the differential absorption function
    diff_absorption, all_absorbed = calculate_absorption_tmm(out, initial)

    # Each building block (layer or junction) needs to have access to the absorbed light in its region.
    # We update each object with that information.
    layer = 0
    A_per_layer = np.array(
        RAT['A_per_layer'][1:-1])  # first entry is R, last entry is T
    for j in range(len(solar_cell)):

        solar_cell[j].diff_absorption = diff_absorption
        solar_cell[j].absorbed = types.MethodType(absorbed, solar_cell[j])
        solar_cell[j].layer_absorption = initial * np.sum(
            A_per_layer[layer:(layer + n_layers_junction[j])], axis=0)
        layer = layer + n_layers_junction[j]

    solar_cell.reflected = RAT['R'] * initial
    solar_cell.absorbed = sum(
        [solar_cell[x].layer_absorption for x in np.arange(len(solar_cell))])
    solar_cell.transmitted = initial - solar_cell.reflected - solar_cell.absorbed
def initialise_S(stack, size, orders):
    S = S4.New(((size[0], 0), (0, size[1])), orders)

    geom_list = [layer.geometry for layer in stack]
    geom_list.insert(0, {})  # incidence medium
    geom_list.append({})  # transmission medium

    ## Materials for the shapes need to be defined before you can do .SetRegion
    shape_mats = necessary_materials(geom_list)
    Layers = []
    for x in shape_mats:
        Layers.append(Layer(0, x))

    shape_mats_OS = OptiStack(Layers)

    for i1 in range(len(shape_mats)
                    ):  # create the materials needed for all the shapes in S4
        S.SetMaterial('shape_mat_' + str(i1 + 1), 1)

    ## Make the layers
    stack_OS = OptiStack(stack)
    widths = stack_OS.get_widths()

    for i1 in range(
            len(widths)
    ):  # create 'dummy' materials for base layers including incidence and transmission media
        S.SetMaterial(
            'layer_' + str(i1 + 1), 1
        )  # This is not strictly necessary but it means S.SetExcitationPlanewave
        # can be done outside the wavelength loop in calculate_rat_rcwa
    for i1 in range(len(widths)):  # set base layers
        layer_name = 'layer_' + str(i1 + 1)
        if widths[i1] == float('Inf'):
            S.AddLayer(
                layer_name, 0, layer_name
            )  # Solcore4 has incidence and transmission media widths set to Inf;
            # in S4 they have zero width
        else:
            S.AddLayer(layer_name, widths[i1],
                       layer_name)  # keep base unit m, not nm

        geometry = geom_list[i1]

        if bool(geometry):
            for shape in geometry:
                mat_name = 'shape_mat_' + str(
                    shape_mats.index(shape['mat']) + 1)
                if shape['type'] == 'circle':
                    S.SetRegionCircle(layer_name, mat_name, shape['center'],
                                      shape['radius'])
                elif shape['type'] == 'ellipse':
                    S.SetRegionEllipse(layer_name, mat_name, shape['center'],
                                       shape['angle'], shape['halfwidths'])
                elif shape['type'] == 'rectangle':
                    S.SetRegionRectangle(layer_name, mat_name, shape['center'],
                                         shape['angle'], shape['halfwidths'])
                elif shape['type'] == 'polygon':
                    S.SetRegionPolygon(layer_name, mat_name, shape['center'],
                                       shape['angle'], shape['vertices'])

    return S, stack_OS, shape_mats_OS
def calculate_absorption_profile_rcwa(structure,
                                      size,
                                      orders,
                                      wavelength,
                                      rat_output,
                                      z_limit=None,
                                      steps_size=2,
                                      dist=None,
                                      theta=0,
                                      phi=0,
                                      pol='u'):
    """ It calculates the absorbed energy density within the material. From the documentation:

    'In principle this has units of [power]/[volume], but we can express it as a multiple of incoming light power
    density on the material, which has units [power]/[area], so that absorbed energy density has units of 1/[length].'

    Integrating this absorption profile in the whole stack gives the same result that the absorption obtained with
    calculate_rat as long as the spacial mesh (controlled by steps_thinest_layer) is fine enough. If the structure is
    very thick and the mesh not thin enough, the calculation might diverege at short wavelengths.

    For now, it only works for normal incident, coherent light.

    :param structure: A solcore structure with layers and materials.
    :param wavelength: Wavelengths in which calculate the data (in nm). An array-like object.
    :return: A dictionary containing the positions (in nm) and a 2D array with the absorption in the structure as a
    function of the position and the wavelength.
    """

    num_wl = len(wavelength)

    if dist is None:
        if z_limit is None:
            stack = OptiStack(structure)
            z_limit = np.sum(np.array(stack.widths))
        dist = np.arange(0, z_limit, steps_size)

    output = {'position': dist, 'absorption': np.zeros((num_wl, len(dist)))}

    S, stack_OS, shape_mats_OS = initialise_S(structure, size, orders)

    if pol in 'sp':
        if pol == 's':
            s = 1
            p = 0
        elif pol == 'p':
            s = 0
            p = 1

        S.SetExcitationPlanewave((theta, phi), s, p, 0)
        for i, wl in enumerate(wavelength):
            update_epsilon(S, stack_OS, shape_mats_OS, wl)
            S.SetFrequency(1 / wl)
            A = rat_output['A'][i]
            for j, d in enumerate(dist):
                layer, d_in_layer = tmm.find_in_structure_with_inf(
                    stack_OS.get_widths(), d)  # don't need to change this
                layer_name = 'layer_' + str(
                    layer + 1)  # layer_1 is air above so need to add 1
                data = rcwa_position_resolved(S, layer_name, d_in_layer, A)
                output['absorption'][i, j] = data

    else:
        for i, wl in enumerate(
                wavelength):  # set the material values and indices in here
            update_epsilon(S, stack_OS, shape_mats_OS, wl)
            S.SetFrequency(1 / wl)
            A = rat_output['A'][i]

            for j, d in enumerate(dist):
                layer, d_in_layer = tmm.find_in_structure_with_inf(
                    stack_OS.get_widths(), d)  # don't need to change this
                layer_name = 'layer_' + str(
                    layer + 1)  # layer_1 is air above so need to add 1
                S.SetExcitationPlanewave((theta, phi), 0, 1,
                                         0)  # p-polarization
                data_p = rcwa_position_resolved(S, layer_name, d_in_layer, A)
                S.SetExcitationPlanewave((theta, phi), 1, 0,
                                         0)  # p-polarization
                data_s = rcwa_position_resolved(S, layer_name, d_in_layer, A)
                output['absorption'][i, j] = 0.5 * (data_p + data_s)

    return output
Пример #6
0
def calculate_absorption_profile_rcwa(structure,
                                      size,
                                      orders,
                                      wavelength,
                                      rat_output_A,
                                      z_limit=None,
                                      steps_size=2,
                                      dist=None,
                                      theta=0,
                                      phi=0,
                                      pol='u',
                                      incidence=None,
                                      substrate=None,
                                      parallel=False,
                                      n_jobs=-1,
                                      user_options=None):
    """It calculates the absorbed energy density within the material. Integrating this absorption profile in the whole stack gives the same result as the absorption obtained with
    calculate_rat as long as the spatial mesh is fine enough. If the structure is
    very thick and the mesh not thin enough, the calculation might diverge at short wavelengths.
    This function is analogous to calculate_absorption_profile from the transfer_matrix module.

    :param structure: A Solcore structure with layers and materials.
    :param size: a tuple of 2-D vectors in the format ((ux, uy), (vx, vy)) giving the x and y components of the lattice unit vectors in nm.
    :param orders: number of orders to retain in the RCWA calculations.
    :param wavelength: Wavelengths (in nm) in which calculate the data.
    :param rat_output: A_pol' (polarization-dependent layer absorption) output from calculate_rat_rcwa
    :param z_limit: Maximum value in the z direction at which to calculate depth-dependent absorption (nm)
    :param steps_size: if the dist is not specified, the step size in nm to use in the depth-dependent calculation
    :param dist: the positions (in nm) at which to calculate depth-dependent absorption
    :param theta: polar incidence angle (in degrees) of the incident light. Default: 0 (normal incidence)
    :param phi: azimuthal incidence angle in degrees. Default: 0
    :param pol: Polarisation of the light: 's', 'p' or 'u'. Default: 'u' (unpolarised).
    :param substrate: semi-infinite transmission medium
    :return: A dictionary containing the positions (in nm) and a 2D array with the absorption in the structure as a
        function of the position and the wavelength.
    """
    num_wl = len(wavelength)

    geom_list = [layer.geometry for layer in structure]
    geom_list.insert(0, {})  # incidence medium
    geom_list.append({})  # transmission medium
    # write a separate function that makes the OptiStack structure into an S4 object, defined materials etc.
    # write a separate function that makes the OptiStack structure into an S4 object, defined materials etc.

    ## Materials for the shapes need to be defined before you can do .SetRegion
    shape_mats, geom_list_str = necessary_materials(geom_list)

    shapes_oc = np.zeros((len(wavelength), len(shape_mats)), dtype=complex)

    for i1, x in enumerate(shape_mats):
        shapes_oc[:,
                  i1] = (x.n(wavelength / 1e9) + 1j * x.k(wavelength / 1e9))**2

    stack_OS = OptiStack(structure,
                         bo_back_reflection=False,
                         substrate=substrate)
    widths = stack_OS.get_widths()
    layers_oc = np.zeros((len(wavelength / 1e9), len(structure) + 2),
                         dtype=complex)

    if incidence == None:
        layers_oc[:, 0] = 1
    else:
        layers_oc[:, 0] = (incidence.n(
            wavelength / 1e9))**2  # + 1j*incidence.k(wavelengths))**2

    if substrate == None:
        layers_oc[:, -1] = 1
    else:
        layers_oc[:, -1] = (substrate.n(wavelength / 1e9) +
                            1j * substrate.k(wavelength / 1e9))**2

    for i1, x in enumerate(structure):
        layers_oc[:, i1 + 1] = (x.material.n(wavelength / 1e9) +
                                1j * x.material.k(wavelength / 1e9))**2

    shapes_names = [str(x) for x in shape_mats]
    rcwa_options = DEFAULT_OPTIONS

    if user_options is not None:
        rcwa_options.update(user_options)

    if dist is None:
        if z_limit is None:
            stack = OptiStack(structure)
            z_limit = np.sum(np.array(stack.widths))
        dist = np.arange(0, z_limit, steps_size)

    output = {'position': dist, 'absorption': np.zeros((num_wl, len(dist)))}

    if parallel:
        allres = Parallel(n_jobs=n_jobs)(delayed(RCWA_wl_prof)(
            wavelength[i1], rat_output_A[i1], dist, geom_list_str,
            layers_oc[i1], shapes_oc[i1], shapes_names, pol, theta, phi,
            widths, size, orders, rcwa_options) for i1 in range(num_wl))

    else:
        allres = [
            RCWA_wl_prof(wavelength[i1], rat_output_A[i1], dist, geom_list_str,
                         layers_oc[i1], shapes_oc[i1], shapes_names, pol,
                         theta, phi, widths, size, orders, rcwa_options)
            for i1 in range(num_wl)
        ]

    profile = np.stack(allres)
    output['absorption'] = profile

    return output
Пример #7
0
def calculate_rat_rcwa(structure,
                       size,
                       orders,
                       wavelength,
                       incidence,
                       substrate,
                       theta=0,
                       phi=0,
                       pol='u',
                       parallel=False,
                       n_jobs=-1,
                       user_options=None):
    """Calculates the reflected, absorbed and transmitted intensity of the structure for the wavelengths and angles
    defined using an RCWA method implemented using the S4 package.
    This function is analogous to calculate_rat from the transfer_matrix module.

    :param structure: A Solcore Structure object with layers and materials or a OptiStack object.
    :param size: a tuple of 2-D vectors in the format ((ux, uy), (vx, vy)) giving the x and y components of the lattice unit vectors in nm.
    :param orders: number of orders to retain in the RCWA calculations.
    :param wavelength: Wavelengths (in nm) in which calculate the data.
    :param incidence: a Solcore material describing the semi-infinite incidence medium
    :param substrate: a Solcore material describing the semi-infinite transmission medium
    :param theta: polar incidence angle (in degrees) of the incident light. Default: 0 (normal incidence)
    :param phi: azimuthal incidence angle (in degrees). Default: 0
    :param pol: Polarisation of the light: 's', 'p' or 'u'. Default: 'u' (unpolarised).
    :param parallel: whether or not to execute calculation in parallel (over wavelengths), True or False. Default is False
    :param n_jobs: the 'n_jobs' argument passed to Parallel from the joblib package. If set to -1, all available CPUs are used,
            if set to 1 no parallel computing is executed. The number of CPUs used is given by n_cpus + 1 + n_jobs. Default is -1.
    :param user_options: dictionary of options for S4. The list of possible entries and their values is:

            * LatticeTruncation: 'Circular' or 'Parallelogramic' (default 'Circular')
            * DiscretizedEpsilon: True or False (default False)
            * DiscretizationResolution: integer (default value 8)
            * PolarizationDecomposition: True or False (default False)
            * PolarizationBasis: 'Default' or 'Normal' or 'Jones' (default 'Default')
            * LanczosSmoothing: True or False (default False)
            * SubpixelSmoothing: True or False (default False)
            * ConserveMemory: True or False (default False)
            * WeismannFormulation: True or False (default False)

        Further information on the function of these options can be found in the S4 Python API documentation. If no
        options are provided, those from DEFAULT_OPTIONS are used.

    :return: A dictionary with the R, total A and T at the specified wavelengths and angle. A_pol lists total absorption
       at s and p polarizations if pol = 'u' was specified (this information is needed for the absorption profile calculation).
       Otherwise, A_pol is the same as A.
    """
    num_wl = len(wavelength)

    # write a separate function that makes the OptiStack structure into an S4 object, defined materials etc.
    # Materials for the shapes need to be defined before you can do .SetRegion

    geom_list = [layer.geometry for layer in structure]
    geom_list.insert(0, {})  # incidence medium
    geom_list.append({})  # transmission medium

    shape_mats, geom_list_str = necessary_materials(geom_list)

    shapes_oc = np.zeros((len(wavelength), len(shape_mats)), dtype=complex)

    for i1, x in enumerate(shape_mats):
        shapes_oc[:,
                  i1] = (x.n(wavelength / 1e9) + 1j * x.k(wavelength / 1e9))**2

    stack_OS = OptiStack(structure,
                         bo_back_reflection=False,
                         substrate=substrate)
    widths = stack_OS.get_widths()
    layers_oc = np.zeros((len(wavelength / 1e9), len(structure) + 2),
                         dtype=complex)

    if incidence == None:
        layers_oc[:, 0] = 1
    else:
        layers_oc[:, 0] = (incidence.n(
            wavelength / 1e9))**2  # + 1j*incidence.k(wavelengths))**2

    if substrate == None:
        layers_oc[:, -1] = 1
    else:
        layers_oc[:, -1] = (substrate.n(wavelength / 1e9) +
                            1j * substrate.k(wavelength / 1e9))**2

    for i1, x in enumerate(structure):
        layers_oc[:, i1 + 1] = (x.material.n(wavelength / 1e9) +
                                1j * x.material.k(wavelength / 1e9))**2

    shapes_names = [str(x) for x in shape_mats]

    rcwa_options = DEFAULT_OPTIONS
    if user_options is not None:
        rcwa_options.update(user_options)

    if parallel:
        allres = Parallel(n_jobs=n_jobs)(delayed(RCWA_wl)(
            wavelength[i1], geom_list_str, layers_oc[i1], shapes_oc[i1],
            shapes_names, pol, theta, phi, widths, size, orders, rcwa_options)
                                         for i1 in range(num_wl))

    else:
        allres = [
            RCWA_wl(wavelength[i1], geom_list_str, layers_oc[i1],
                    shapes_oc[i1], shapes_names, pol, theta, phi, widths, size,
                    orders, rcwa_options) for i1 in range(num_wl)
        ]

    R = np.stack([item[0] for item in allres])
    T = np.stack([item[1] for item in allres])
    A_mat = np.stack([item[2] for item in allres])
    if pol == 'u':
        A_per_layer = np.mean(A_mat, axis=1)
        A_pol = np.sum(A_mat, 2)
    else:
        A_per_layer = A_mat
        A_pol = np.sum(A_per_layer, 1)

    output = {
        'R': R,
        'A': np.sum(A_per_layer, 1),
        'T': T,
        'A_per_layer': A_per_layer,
        'A_pol': A_pol
    }

    return output
Пример #8
0
def calculate_absorption_profile_rcwa(structure,
                                      size,
                                      orders,
                                      wavelength,
                                      rat_output,
                                      z_limit=None,
                                      steps_size=2,
                                      dist=None,
                                      theta=0,
                                      phi=0,
                                      pol='u',
                                      substrate=None):
    """ It calculates the absorbed energy density within the material. From the documentation:

    'In principle this has units of [power]/[volume], but we can express it as a multiple of incoming light power
    density on the material, which has units [power]/[area], so that absorbed energy density has units of 1/[length].'

    Integrating this absorption profile in the whole stack gives the same result that the absorption obtained with
    calculate_rat as long as the spacial mesh (controlled by steps_thinest_layer) is fine enough. If the structure is
    very thick and the mesh not thin enough, the calculation might diverege at short wavelengths.

    For now, it only works for normal incident, coherent light.

    :param structure: A solcore structure with layers and materials.
    :param size: list with 2 entries, size of the unit cell (right now, can only be rectangular
    :param orders: number of orders to retain in the RCWA calculations.
    :param wavelength: Wavelengths (in nm) in which calculate the data.
    :param rat_output: output from calculate_rat_rcwa
    :param z_limit: Maximum value in the z direction at which to calculate depth-dependent absorption (nm)
    :param steps_size: if the dist is not specified, the step size in nm to use in the depth-dependent calculation
    :param dist: the positions (in nm) at which to calculate depth-dependent absorption
    :param theta: polar incidence angle (in degrees) of the incident light. Default: 0 (normal incidence)
    :param phi: azimuthal incidence angle in degrees. Default: 0
    :param pol: Polarisation of the light: 's', 'p' or 'u'. Default: 'u' (unpolarised).
    :param substrate: semi-infinite transmission medium
    :return: A dictionary containing the positions (in nm) and a 2D array with the absorption in the structure as a
    function of the position and the wavelength.
    """

    num_wl = len(wavelength)

    if dist is None:
        if z_limit is None:
            stack = OptiStack(structure)
            z_limit = np.sum(np.array(stack.widths))
        dist = np.arange(0, z_limit, steps_size)

    output = {'position': dist, 'absorption': np.zeros((num_wl, len(dist)))}

    S, stack_OS, shape_mats_OS = initialise_S(structure, size, orders,
                                              substrate)

    if pol in 'sp':
        if pol == 's':
            s = 1
            p = 0
        elif pol == 'p':
            s = 0
            p = 1

        S.SetExcitationPlanewave((theta, phi), s, p, 0)
        for i, wl in enumerate(wavelength):
            update_epsilon(S, stack_OS, shape_mats_OS, wl)
            S.SetFrequency(1 / wl)
            A = rat_output['A'][i]
            for j, d in enumerate(dist):
                layer, d_in_layer = tmm.find_in_structure_with_inf(
                    stack_OS.get_widths(), d)  # don't need to change this
                layer_name = 'layer_' + str(
                    layer + 1)  # layer_1 is air above so need to add 1
                data = rcwa_position_resolved(S, layer_name, d_in_layer, A)
                output['absorption'][i, j] = data

    else:
        for i, wl in enumerate(
                wavelength):  # set the material values and indices in here
            print(i)
            update_epsilon(S, stack_OS, shape_mats_OS, wl)
            S.SetFrequency(1 / wl)
            A = rat_output['A'][i]

            for j, d in enumerate(dist):
                layer, d_in_layer = tmm.find_in_structure_with_inf(
                    stack_OS.get_widths(), d)  # don't need to change this
                layer_name = 'layer_' + str(
                    layer + 1)  # layer_1 is air above so need to add 1
                S.SetExcitationPlanewave((theta, phi), 0, 1,
                                         0)  # p-polarization
                data_p = rcwa_position_resolved(S, layer_name, d_in_layer, A)
                S.SetExcitationPlanewave((theta, phi), 1, 0,
                                         0)  # p-polarization
                data_s = rcwa_position_resolved(S, layer_name, d_in_layer, A)
                output['absorption'][i, j] = 0.5 * (data_p + data_s)

    return output
Пример #9
0
ax3.set_xlabel('Wavelength (nm)')
ax3.set_ylabel('Reflection / Absorption')
ax3.set_title('c)', loc = 'left')
#plt.legend()
#plt.show()


from solcore.absorption_calculator import calculate_rat, OptiStack


## pure TMM
all_layers = front_materials + [Layer(bulkthick, Ge)] + back_materials

coh_list = len(front_materials)*['c'] + ['i'] + ['c']

OS_layers = OptiStack(all_layers, substrate=Ag, no_back_reflection=False)

TMM_res = calculate_rat(OS_layers, wavelength=wavelengths*1e9,
                        no_back_reflection=False, angle=options['theta_in']*180/np.pi, coherent=False,
                        coherency_list=coh_list, pol=options['pol'])

#ax4.subplot(224)
ax4.plot(options['wavelengths']*1e9, TMM_res['R'], label='R')

ax4.plot(options['wavelengths']*1e9, TMM_res['A_per_layer'][1] + TMM_res['A_per_layer'][2], label='ARC')
ax4.plot(options['wavelengths']*1e9, TMM_res['A_per_layer'][3], label='InGaP')
ax4.plot(options['wavelengths']*1e9, TMM_res['A_per_layer'][4], label='GaAs')
ax4.plot(options['wavelengths']*1e9, TMM_res['A_per_layer'][5], label='SiGeSn')
ax4.plot(options['wavelengths']*1e9, TMM_res['A_per_layer'][len(front_materials)+1], label='Ge')
ax4.plot(options['wavelengths']*1e9, TMM_res['T'], label='T')
ax4.set_xlabel('Wavelength (nm)')
Пример #10
0
#  pure TMM comparison

layers = [
    Layer(60e-9, SiN),
    Layer(50E-9, GaInP),
    Layer(50e-9, GaAs),
    Layer(20e-6, Si),
    Layer(100E-9, GaInP),
    Layer(70e-9, GaAs)
]

from solcore.absorption_calculator import OptiStack, calculate_rat

optist = OptiStack(layers,
                   incidence=Air,
                   substrate=SiN,
                   no_back_reflection=False)
TMM_res = calculate_rat(optist,
                        options['wavelengths'] * 1e9,
                        angle=options['theta_in'] * 180 / np.pi,
                        pol=options['pol'],
                        coherent=False,
                        coherency_list=['c', 'c', 'c', 'i', 'c', 'c'],
                        no_back_reflection=False)

plt.figure()
plt.plot(options['wavelengths'] * 1e9, TMM_res['R'], label='R')
plt.plot(options['wavelengths'] * 1e9, TMM_res['T'], label='T')
plt.plot(options['wavelengths'] * 1e9,
         TMM_res['A_per_layer'][4],
         label='A_bulk (Si)')
Пример #11
0
def solve_tmm(solar_cell, options):
    """ Calculates the reflection, transmission and absorption of a solar cell object using the transfer matrix method

    :param solar_cell:
    :param options:
    :return:
    """
    wl = options.wavelength

    # We include the shadowing losses
    initial = (1 - solar_cell.shading) if hasattr(solar_cell, 'shading') else 1

    # Now we calculate the absorbed and transmitted light. We first get all the relevant parameters from the objects
    widths = []
    offset = 0
    all_layers = []
    for j, layer_object in enumerate(solar_cell):

        # Attenuation due to absorption in the AR coatings or any layer in the front that is not part of the junction
        if type(layer_object) is Layer:
            all_layers.append(layer_object)
            widths.append(layer_object.width)

        # For each junction, and layer within the junction, we get the absorption coefficient and the layer width.
        elif type(layer_object) in [TunnelJunction, Junction]:
            junction_width = 0
            try:
                for i, layer in enumerate(layer_object):
                    all_layers.append(layer)
                    junction_width += layer.width
                    widths.append(layer.width)

                solar_cell[j].width = junction_width

            except TypeError as err:
                print(
                    'ERROR in "solar_cell_solver: TMM solver":\n'
                    '\tNo layers found in Junction or TunnelJunction objects.')
                raise err

        solar_cell[j].offset = offset
        offset += layer_object.width

    # With all the information, we create the optical stack
    no_back_reflexion = options.no_back_reflexion if 'no_back_reflexion' in options.keys(
    ) else True
    stack = OptiStack(all_layers, no_back_reflexion=no_back_reflexion)

    dist = np.logspace(0, np.log10(offset * 1e9),
                       int(300 * np.log10(offset * 1e9)))
    position = options.position if 'position' in options.keys() else dist

    print('Calculating RAT...')
    RAT = calculate_rat(stack, wl * 1e9, coherent=True)

    print('Calculating absorption profile...')
    out = calculate_absorption_profile(stack, wl * 1e9, dist=position)

    # With all this information, we are ready to calculate the differential absorption function
    diff_absorption, all_absorbed = calculate_absorption_tmm(out)

    # Each building block (layer or junction) needs to have access to the absorbed light in its region.
    # We update each object with that information.
    for j in range(len(solar_cell)):
        solar_cell[j].diff_absorption = diff_absorption
        solar_cell[j].absorbed = types.MethodType(absorbed, solar_cell[j])

    solar_cell.reflected = RAT['R'] * initial
    solar_cell.transmitted = (1 - RAT['R'] - all_absorbed) * initial
    solar_cell.absorbed = all_absorbed * initial
Пример #12
0
    def __init__(self, structure, size, orders, options, incidence, substrate):
        """ Calculates the reflected, absorbed and transmitted intensity of the structure for the wavelengths and angles
        defined using an RCWA method implemented using the S4 package.

        :param structure: A solcore Structure object with layers and materials or a OptiStack object.
        :param size: list with 2 entries, size of the unit cell (right now, can only be rectangular
        :param orders: number of orders to retain in the RCWA calculations.

        :param substrate: semi-infinite transmission medium
        :return: A dictionary with the R, A and T at the specified wavelengths and angle.
        """

        wavelengths = options['wavelengths']

        # write a separate function that makes the OptiStack structure into an S4 object, defined materials etc.
        geom_list = [layer.geometry for layer in structure]
        geom_list.insert(0, {})  # incidence medium
        geom_list.append({})  # transmission medium

        ## Materials for the shapes need to be defined before you can do .SetRegion
        shape_mats, geom_list_str = necessary_materials(geom_list)

        shapes_oc = np.zeros((len(wavelengths), len(shape_mats)),
                             dtype=complex)

        for i1, x in enumerate(shape_mats):
            shapes_oc[:, i1] = (x.n(wavelengths) + 1j * x.k(wavelengths))**2

        stack_OS = OptiStack(structure,
                             bo_back_reflection=False,
                             substrate=substrate)
        widths = stack_OS.get_widths()
        layers_oc = np.zeros((len(wavelengths), len(structure) + 2),
                             dtype=complex)

        layers_oc[:, 0] = (
            incidence.n(wavelengths))**2  # + 1j*incidence.k(wavelengths))**2
        layers_oc[:, -1] = (substrate.n(wavelengths) +
                            1j * substrate.k(wavelengths))**2

        for i1, x in enumerate(structure):
            layers_oc[:, i1 + 1] = (x.material.n(wavelengths) +
                                    1j * x.material.k(wavelengths))**2

        shapes_names = [str(x) for x in shape_mats]

        # nm_spacing = options['nm_spacing']

        # RCWA options
        rcwa_options = dict(LatticeTruncation='Circular',
                            DiscretizedEpsilon=False,
                            DiscretizationResolution=8,
                            PolarizationDecomposition=False,
                            PolarizationBasis='Default',
                            LanczosSmoothing=False,
                            SubpixelSmoothing=False,
                            ConserveMemory=False,
                            WeismannFormulation=False)

        user_options = options[
            'rcwa_options'] if 'rcwa_options' in options.keys() else {}
        rcwa_options.update(user_options)

        self.wavelengths = wavelengths
        self.rcwa_options = rcwa_options
        self.options = options
        self.geom_list = geom_list_str
        self.shapes_oc = shapes_oc
        self.shapes_names = shapes_names
        self.widths = widths
        self.orders = orders
        self.size = size
        self.layers_oc = layers_oc
Пример #13
0
def RCWA(structure,
         size,
         orders,
         options,
         incidence,
         transmission,
         only_incidence_angle=False,
         front_or_rear='front',
         surf_name='',
         detail_layer=False,
         save=True):
    """ Calculates the reflected, absorbed and transmitted intensity of the structure for the wavelengths and angles
    defined using an RCWA method implemented using the S4 package.

    :param structure: A solcore Structure object with layers and materials or a OptiStack object.
    :param size: list with 2 entries, size of the unit cell (right now, can only be rectangular
    :param orders: number of orders to retain in the RCWA calculations.
    :param wavelength: Wavelengths (in nm) in which calculate the data.
    :param theta: polar incidence angle (in degrees) of the incident light. Default: 0 (normal incidence)
    :param phi: azimuthal incidence angle in degrees. Default: 0
    :param pol: Polarisation of the light: 's', 'p' or 'u'. Default: 'u' (unpolarised).
    :param transmission: semi-infinite transmission medium
    :return: A dictionary with the R, A and T at the specified wavelengths and angle.
    """
    # TODO: when non-zero incidence angle, not binned correctly in matrix (just goes in theta = 0)
    # TODO: when doing unpolarized, why not just set s=0.5 p=0.5 in S4? (Maybe needs to be normalised differently). Also don't know if this is faster,
    # or if internally it will still do s & p separately
    # TODO: if incidence angle is zero, s and p polarization are the same so no need to do both

    structpath = os.path.join(results_path, options['project_name'])
    if not os.path.isdir(structpath):
        os.mkdir(structpath)

    savepath_RT = os.path.join(structpath,
                               surf_name + front_or_rear + 'RT.npz')
    savepath_A = os.path.join(structpath, surf_name + front_or_rear + 'A.npz')
    prof_mat_path = os.path.join(results_path, options['project_name'],
                                 surf_name + front_or_rear + 'profmat.nc')

    if os.path.isfile(savepath_RT) and save:
        print('Existing angular redistribution matrices found')
        full_mat = load_npz(savepath_RT)
        A_mat = load_npz(savepath_A)

    else:

        wavelengths = options['wavelengths']

        if front_or_rear == 'front':
            layers = structure
            trns = transmission
            inc = incidence

        else:
            layers = structure[::-1]
            trns = incidence
            inc = transmission

        # write a separate function that makes the OptiStack structure into an S4 object, defined materials etc.
        geom_list = [layer.geometry for layer in structure]
        geom_list.insert(0, {})  # incidence medium
        geom_list.append({})  # transmission medium

        ## Materials for the shapes need to be defined before you can do .SetRegion
        shape_mats, geom_list_str = necessary_materials(geom_list)

        shapes_oc = np.zeros((len(wavelengths), len(shape_mats)),
                             dtype=complex)

        for i1, x in enumerate(shape_mats):
            shapes_oc[:, i1] = (x.n(wavelengths) + 1j * x.k(wavelengths))**2

        stack_OS = OptiStack(layers, no_back_reflection=False)
        widths = stack_OS.get_widths()
        layers_oc = np.zeros((len(wavelengths), len(structure) + 2),
                             dtype=complex)

        layers_oc[:, 0] = (inc.n(wavelengths))**2  #+ 1j*inc.k(wavelengths))**2
        layers_oc[:, -1] = (trns.n(wavelengths) + 1j * trns.k(wavelengths))**2

        for i1, x in enumerate(layers):
            layers_oc[:, i1 + 1] = (x.material.n(wavelengths) +
                                    1j * x.material.k(wavelengths))**2

        shapes_names = [str(x) for x in shape_mats]

        #nm_spacing = options['nm_spacing']
        phi_sym = options['phi_symmetry']
        n_theta_bins = options['n_theta_bins']
        c_az = options['c_azimuth']
        pol = options['pol']

        # RCWA options
        rcwa_options = dict(LatticeTruncation='Circular',
                            DiscretizedEpsilon=False,
                            DiscretizationResolution=8,
                            PolarizationDecomposition=False,
                            PolarizationBasis='Default',
                            LanczosSmoothing=False,
                            SubpixelSmoothing=False,
                            ConserveMemory=False,
                            WeismannFormulation=False)

        user_options = options[
            'rcwa_options'] if 'rcwa_options' in options.keys() else {}
        rcwa_options.update(user_options)
        print(rcwa_options)

        theta_intv, phi_intv, angle_vector = make_angle_vector(
            n_theta_bins, phi_sym, c_az)

        if only_incidence_angle:
            thetas_in = np.array([options['theta_in']])
            phis_in = np.array([options['phi_in']])
        else:
            angles_in = angle_vector[:int(len(angle_vector) / 2), :]
            thetas_in = angles_in[:, 1]
            phis_in = angles_in[:, 2]

        # angle in degrees
        thetas_in = thetas_in * 180 / np.pi
        phis_in = phis_in * 180 / np.pi
        # initialise_S has to happen inside parallel job (get Pickle errors otherwise);
        # just pass relevant optical constants for each wavelength, like for RT

        angle_vector_0 = angle_vector[:, 0]

        if front_or_rear == "front":
            side = 1
        else:
            side = -1

        if options['parallel']:
            allres = Parallel(n_jobs=options['n_jobs'])(delayed(RCWA_wl)(
                wavelengths[i1] * 1e9, geom_list, layers_oc[i1], shapes_oc[i1],
                shapes_names, pol, thetas_in, phis_in, widths, size, orders,
                phi_sym, theta_intv, phi_intv, angle_vector_0, rcwa_options,
                detail_layer, side) for i1 in range(len(wavelengths)))

        else:
            allres = [
                RCWA_wl(wavelengths[i1] * 1e9, geom_list, layers_oc[i1],
                        shapes_oc[i1], shapes_names, pol, thetas_in, phis_in,
                        widths, size, orders, phi_sym, theta_intv, phi_intv,
                        angle_vector_0, rcwa_options, detail_layer, side)
                for i1 in range(len(wavelengths))
            ]

        R = np.stack([item[0] for item in allres])
        T = np.stack([item[1] for item in allres])
        A_mat = np.stack([item[2] for item in allres])
        full_mat = stack([item[3] for item in allres])
        int_mat = stack([item[4] for item in allres])
        #T_mat = np.stack([item[4] for item in allres])

        #full_mat = np.hstack((R_mat, T_mat))
        #full_mat = COO(full_mat)
        A_mat = COO(A_mat)

        if save:
            save_npz(savepath_RT, full_mat)
            save_npz(savepath_A, A_mat)

        #R_pfbo = np.stack([item[3] for item in allres])
        #T_pfbo = np.stack([item[4] for item in allres])
        #phi_rt = np.stack([item[5] for item in allres])
        #theta_r = np.stack([item[6] for item in allres])
        #theta_t = np.stack([item[7] for item in allres])
        #R_pfbo_2 = np.stack([item[8] for item in allres])

    #return {'R': R, 'T':T, 'A_layer': A_mat, 'full_mat': full_mat, 'int_mat': int_mat}#'R_pfbo': R_pfbo, 'T_pfbo': T_pfbo, 'phi_rt': phi_rt, 'theta_r': theta_r, 'theta_t': theta_t}#, 'R_pfbo_2': R_pfbo_2}
    return full_mat, A_mat  # , R, T
angle_degrees_in = 8

wavelengths = np.linspace(900, 1200, 30)

sim_fig6 = np.loadtxt('data/optos_fig6_sim.csv', delimiter=',')
sim_fig7 = np.loadtxt('data/optos_fig7_sim.csv', delimiter=',')
sim_fig8 = np.loadtxt('data/optos_fig8_sim.csv', delimiter=',')

rayflare_fig6 = np.loadtxt('fig6_rayflare.txt')
rayflare_fig7 = np.loadtxt('fig7_rayflare.txt')
rayflare_fig8 = np.loadtxt('fig8_rayflare.txt')

# planar
Si = material('Si_OPTOS')()
Air = material('Air')()
struc = OptiStack([Layer(si('200um'), Si)], substrate=Air)

RAT = calculate_rat(struc, wavelength=wavelengths, coherent=True)

fig = plt.figure()
plt.plot(sim_fig6[:,0], sim_fig6[:,1], '--', color=palhf[0], label= 'OPTOS - rear grating (a)')
plt.plot(wavelengths, rayflare_fig6, '-o', color=palhf[0], label='RayFlare - rear grating (a)', fillstyle='none')
plt.plot(sim_fig7[:,0], sim_fig7[:,1], '--', color=palhf[1],  label= 'OPTOS - front pyramids (b)')
plt.plot(wavelengths, rayflare_fig7, '-o', color=palhf[1],  label= 'RayFlare - front pyramids (b)', fillstyle='none')
plt.plot(sim_fig8[:,0], sim_fig8[:,1], '--', color=palhf[2],  label= 'OPTOS - grating + pyramids (c)')
plt.plot(wavelengths, rayflare_fig8, '-o', color=palhf[2],  label= 'RayFlare - grating + pyramids (c)', fillstyle='none')
plt.plot(wavelengths, RAT['A_per_layer'][1], '-k', label='Planar')
plt.legend(loc='lower left')
plt.xlabel('Wavelength (nm)')
plt.ylabel('Absorption in Si')
plt.xlim([900, 1200])
Пример #15
0
def make_TMM_lookuptable(layers,
                         incidence,
                         transmission,
                         surf_name,
                         options,
                         coherent=True,
                         coherency_list=None,
                         prof_layers=None,
                         sides=[1, -1]):

    structpath = os.path.join(results_path, options['project_name'])
    if not os.path.isdir(structpath):
        os.mkdir(structpath)
    savepath = os.path.join(structpath, surf_name + '.nc')
    if os.path.isfile(savepath):
        print('Existing lookup table found')
        allres = xr.open_dataset(savepath)
    else:
        wavelengths = options['wavelengths'] * 1e9  # convert to nm
        #pol = options['pol']
        n_angles = options['lookuptable_angles']
        thetas = np.linspace(0, np.pi / 2, n_angles)
        if prof_layers is not None:
            profile = True
        else:
            profile = False

        n_layers = len(layers)
        optlayers = OptiStack(layers,
                              substrate=transmission,
                              incidence=incidence)
        optlayers_flip = OptiStack(layers[::-1],
                                   substrate=incidence,
                                   incidence=transmission)
        optstacks = [optlayers, optlayers_flip]

        if coherency_list is not None:
            coherency_lists = [coherency_list, coherency_list[::-1]]
        else:
            coherency_lists = [['c'] * n_layers] * 2
        # can calculate by angle, already vectorized over wavelength
        pols = ['s', 'p']

        R = xr.DataArray(np.empty((2, 2, len(wavelengths), n_angles)),
                         dims=['side', 'pol', 'wl', 'angle'],
                         coords={
                             'side': sides,
                             'pol': pols,
                             'wl': wavelengths,
                             'angle': thetas
                         },
                         name='R')
        T = xr.DataArray(np.empty((2, 2, len(wavelengths), n_angles)),
                         dims=['side', 'pol', 'wl', 'angle'],
                         coords={
                             'side': sides,
                             'pol': pols,
                             'wl': wavelengths,
                             'angle': thetas
                         },
                         name='T')
        Alayer = xr.DataArray(np.empty(
            (2, 2, n_angles, len(wavelengths), n_layers)),
                              dims=['side', 'pol', 'angle', 'wl', 'layer'],
                              coords={
                                  'side': sides,
                                  'pol': pols,
                                  'wl': wavelengths,
                                  'angle': thetas,
                                  'layer': range(1, n_layers + 1)
                              },
                              name='Alayer')

        if profile:
            Aprof = xr.DataArray(
                np.empty(
                    (2, 2, n_angles, 6, len(prof_layers), len(wavelengths))),
                dims=['side', 'pol', 'angle', 'coeff', 'layer', 'wl'],
                coords={
                    'side': sides,
                    'pol': pols,
                    'wl': wavelengths,
                    'angle': thetas,
                    'layer': prof_layers,
                    'coeff': ['A1', 'A2', 'A3_r', 'A3_i', 'a1', 'a3']
                },
                name='Aprof')

        for i1, side in enumerate(sides):
            R_loop = np.empty((len(wavelengths), n_angles))
            T_loop = np.empty((len(wavelengths), n_angles))
            Alayer_loop = np.empty((n_angles, len(wavelengths), n_layers),
                                   dtype=np.complex_)
            if profile:
                Aprof_loop = np.empty(
                    (n_angles, 6, len(prof_layers), len(wavelengths)))

            for i2, pol in enumerate(pols):

                for i3, theta in enumerate(thetas):

                    tmm_struct = tmm_structure(
                        optstacks[i1],
                        coherent=coherent,
                        coherency_list=coherency_lists[i1])
                    #print(side, pol, theta)
                    res = tmm_struct.calculate(wavelengths,
                                               angle=theta,
                                               pol=pol,
                                               profile=profile,
                                               layers=prof_layers,
                                               nm_spacing=1e5)
                    R_loop[:, i3] = np.real(res['R'])
                    T_loop[:, i3] = np.real(res['T'])
                    Alayer_loop[i3, :, :] = np.real(res['A_per_layer'].T)
                    if profile:
                        Aprof_loop[i3, :, :, :] = res['profile_coeff']

                # sometimes get very small negative values (like -1e-20)
                R_loop[R_loop < 0] = 0
                T_loop[T_loop < 0] = 0
                Alayer_loop[Alayer_loop < 0] = 0

                if side == -1:
                    Alayer_loop = np.flip(Alayer_loop, axis=2)
                    if profile:
                        Aprof_loop = np.flip(Aprof_loop, axis=2)

                R.loc[dict(side=side, pol=pol)] = R_loop
                T.loc[dict(side=side, pol=pol)] = T_loop
                Alayer.loc[dict(side=side, pol=pol)] = Alayer_loop

                if profile:
                    Aprof.loc[dict(side=side, pol=pol)] = Aprof_loop
                    Aprof.transpose('side', 'pol', 'wl', 'angle', 'layer',
                                    'coeff')

        Alayer = Alayer.transpose('side', 'pol', 'wl', 'angle', 'layer')

        if profile:
            allres = xr.merge([R, T, Alayer, Aprof])
        else:
            allres = xr.merge([R, T, Alayer])

        unpol = allres.reduce(np.mean,
                              'pol').assign_coords(pol='u').expand_dims('pol')
        allres = allres.merge(unpol)

        allres.to_netcdf(savepath)

    return allres
Пример #16
0
plt.figure()
plt.plot(wavelengths * 1e9, results_front[2][:, angle_index], 'r-')
plt.plot(wavelengths * 1e9, results_front[3][:, angle_index], 'r--')
plt.plot(wavelengths * 1e9, results_back[2][:, -(angle_index + 1)], 'k-')
plt.plot(wavelengths * 1e9, results_back[3][:, -(angle_index + 1)], 'k--')
plt.show()

inc_angle = angle_vector[angle_index, 1] * 180 / np.pi

from solcore.absorption_calculator import OptiStack, calculate_rat

layers = [Layer(500e-9, GaAs), Layer(200e-9, Ge)]

OptSt = OptiStack(layers,
                  no_back_reflection=False,
                  substrate=Si,
                  incidence=Air)

RAT = calculate_rat(OptSt,
                    options['wavelengths'] * 1e9,
                    angle=inc_angle,
                    no_back_reflection=False,
                    pol=options['pol'])

Ge_e = np.real((Ge.n(wavelengths) + 1j * Ge.k(wavelengths))**2)
GaAs_e = np.real((GaAs.n(wavelengths) + 1j * GaAs.k(wavelengths))**2)

plt.figure()
plt.plot(wavelengths * 1e9, RAT['A_per_layer'][1] / Af_1, label='A1 TMM/RCWA')
plt.plot(wavelengths * 1e9, RAT['A_per_layer'][2] / Af_2, label='A2 TMM/RCWA')
plt.plot(wavelengths * 1e9, RAT['R'] / Rf, label='R TMM/RCWA')
Пример #17
0
    sio2_data = np.loadtxt(siO2_filename, skiprows=3, unpack=True)

    sio2_data[0] = 1.24 / sio2_data[0]
    n = np.sqrt(sio2_data[1] + sio2_data[2] * 1.j)
    sio2_data[1] = np.real(n)
    sio2_data[2] = np.imag(n)
    model_sio2 = [1.5, sio2_data[0] * 1000, sio2_data[1], sio2_data[2]]

    # gau1 = Gauss(A=2.8468, Ec=0.1299, Br=0.0111)
    # gau2 = Gauss(A=7.192, Ec=0.058331, Br=0.01682)
    # gau3 = Gauss(A=1.9737, Ec=0.13991, Br=0.02144)
    # cau = Cauchy(An=1.5, Bn=0.002, Ak=1, Bk=0.2580645161)

    # model_sio2 = [1.5, DielectricConstantModel(e_inf=3, oscillators=[cau])]

    stack = OptiStack([model_sio2, model_substrate])

    # Fitting stuff
    fit = EllipsometryFitter(data, stack)
    variables = [['width'], ['e_inf', ['An']]]
    fit.set_variables(variables)
    wl = np.logspace(np.log10(300), np.log10(20000), 200)
    fit.set_range(wl)

    fit.plot()

    # fit.fit()
    #
    # print(fit.errors)
    # print(fit.correlations)
Пример #18
0
def TMM(layers, incidence, transmission, surf_name, options,
               coherent=True, coherency_list=None, prof_layers=[], front_or_rear='front', save=True):
    """Function which takes a layer stack and creates an angular redistribution matrix.

        :param layers: A list with one or more layers.
        :param transmission: transmission medium
        :param incidence: incidence medium
        :param surf_name: name of the surface (to save the matrices generated.
        :param options: a list of options
        :param coherent: whether or not the layer stack is coherent. If None, it is assumed to be fully coherent
        :param coherency: a list with the same number of entries as the layers, either 'c' for a coherent layer or
        'i' for an incoherent layer
        :param prof_layers: layers for which the absorption profile should be calculated
        (if None, do not calculate absorption profile at all)
        :param front_or_rear: a string, either 'front' or 'rear'; front incidence on the stack, from the incidence
        medium, or rear incidence on the stack, from the transmission medium.
        :return full_mat: R and T redistribution matrix
        :return A_mat: matrix describing absorption per layer
        """

    def make_matrix_wl(wl):
        # binning into matrix, including phi
        RT_mat = np.zeros((len(theta_bins_in)*2, len(theta_bins_in)))
        A_mat = np.zeros((n_layers, len(theta_bins_in)))

        for i1 in range(len(theta_bins_in)):

            theta = theta_lookup[i1]#angle_vector[i1, 1]

            data = allres.loc[dict(angle=theta, wl=wl)]

            R_prob = np.real(data['R'].data.item(0))
            T_prob = np.real(data['T'].data.item(0))

            Alayer_prob = np.real(data['Alayer'].data)
            phi_out = phis_out[i1]

            #print(R_prob, T_prob)

            # reflection
            phi_int = phi_intv[theta_bins_in[i1]]
            phi_ind = np.digitize(phi_out, phi_int, right=True) - 1
            bin_out_r = np.argmin(abs(angle_vector[:, 0] - theta_bins_in[i1])) + phi_ind

            #print(bin_out_r, i1+offset)

            RT_mat[bin_out_r, i1] = R_prob
            #print(R_prob)
            # transmission
            theta_t = np.abs(-np.arcsin((inc.n(wl * 1e-9) / trns.n(wl * 1e-9)) * np.sin(theta_lookup[i1])) + quadrant)

            #print('angle in, transmitted', angle_vector_th[i1], theta_t)
            # theta switches half-plane (th < 90 -> th >90
            if ~np.isnan(theta_t):

                theta_out_bin = np.digitize(theta_t, theta_intv, right=True) - 1
                phi_int = phi_intv[theta_out_bin]

                phi_ind = np.digitize(phi_out, phi_int, right=True) - 1
                bin_out_t = np.argmin(abs(angle_vector[:, 0] - theta_out_bin)) + phi_ind

                RT_mat[bin_out_t, i1] = T_prob
                #print(bin_out_t, i1+offset)

            # absorption
            A_mat[:, i1] = Alayer_prob


        fullmat = COO(RT_mat)
        A_mat = COO(A_mat)
        return fullmat, A_mat

    structpath = os.path.join(results_path, options['project_name'])
    if not os.path.isdir(structpath):
        os.mkdir(structpath)

    savepath_RT = os.path.join(structpath, surf_name + front_or_rear + 'RT.npz')
    savepath_A = os.path.join(structpath, surf_name + front_or_rear + 'A.npz')
    prof_mat_path = os.path.join(results_path, options['project_name'],
                                 surf_name + front_or_rear + 'profmat.nc')

    if os.path.isfile(savepath_RT) and save:
        print('Existing angular redistribution matrices found')
        fullmat = load_npz(savepath_RT)
        A_mat = load_npz(savepath_A)

        if len(prof_layers) > 0:
            profile = xr.load_dataarray(prof_mat_path)
            return fullmat, A_mat, profile

    else:

        wavelengths = options['wavelengths']*1e9 # convert to nm

        theta_intv, phi_intv, angle_vector = make_angle_vector(options['n_theta_bins'], options['phi_symmetry'], options['c_azimuth'])
        angles_in = angle_vector[:int(len(angle_vector) / 2), :]
        thetas = np.unique(angles_in[:, 1])

        n_angles = len(thetas)

        n_layers = len(layers)

        if front_or_rear == 'front':
            optlayers = OptiStack(layers, substrate=transmission, incidence=incidence)
            trns = transmission
            inc = incidence

        else:
            optlayers = OptiStack(layers[::-1], substrate=incidence, incidence=transmission)
            trns = incidence
            inc = transmission


        if len(prof_layers) > 0:
            profile = True
            z_limit = np.sum(np.array(optlayers.widths))
            full_dist = np.arange(0, z_limit, options['nm_spacing'])
            layer_start = np.insert(np.cumsum(np.insert(optlayers.widths, 0, 0)), 0, 0)
            layer_end = np.cumsum(np.insert(optlayers.widths, 0, 0))

            dist = []

            for l in prof_layers:
                dist = np.hstack((dist, full_dist[np.all((full_dist >= layer_start[l], full_dist < layer_end[l]), 0)]))

        else:
            profile = False

        if options['pol'] == 'u':
            pols = ['s', 'p']
        else:
            pols = [options['pol']]


        R = xr.DataArray(np.empty((len(pols), len(wavelengths), n_angles)),
                         dims=['pol', 'wl', 'angle'],
                         coords={'pol': pols, 'wl': wavelengths, 'angle': thetas},
                         name='R')
        T = xr.DataArray(np.empty((len(pols), len(wavelengths), n_angles)),
                         dims=['pol', 'wl', 'angle'],
                         coords={'pol': pols, 'wl': wavelengths, 'angle': thetas},
                         name='T')


        Alayer = xr.DataArray(np.empty((len(pols), n_angles, len(wavelengths), n_layers)),
                              dims=['pol', 'angle', 'wl', 'layer'],
                              coords={'pol': pols,
                                      'wl': wavelengths,
                                      'angle': thetas,
                                      'layer': range(1, n_layers + 1)}, name='Alayer')


        theta_t = xr.DataArray(np.empty((len(pols), len(wavelengths), n_angles)),
                               dims=['pol', 'wl', 'angle'],
                               coords={'pol': pols, 'wl': wavelengths, 'angle': thetas},
                               name='theta_t')

        if profile:
            Aprof = xr.DataArray(np.empty((len(pols), n_angles, len(wavelengths), len(dist))),
                                 dims=['pol', 'angle', 'wl', 'z'],
                                 coords={'pol': pols,
                                         'wl': wavelengths,
                                         'angle': thetas,
                                         'z': dist}, name='Aprof')

        R_loop = np.empty((len(wavelengths), n_angles))
        T_loop = np.empty((len(wavelengths), n_angles))
        Alayer_loop = np.empty((n_angles, len(wavelengths), n_layers), dtype=np.complex_)
        th_t_loop = np.empty((len(wavelengths), n_angles))

        if profile:
            Aprof_loop = np.empty((n_angles, len(wavelengths), len(dist)))

        tmm_struct = tmm_structure(optlayers, coherent=coherent, coherency_list=coherency_list, no_back_reflection=False)

        for i2, pol in enumerate(pols):

            for i3, theta in enumerate(thetas):

                res = tmm_struct.calculate(wavelengths, angle=theta, pol=pol, profile=profile, layers=prof_layers, nm_spacing = options['nm_spacing'])

                R_loop[:, i3] = np.real(res['R'])
                T_loop[:, i3] = np.real(res['T'])
                Alayer_loop[i3, :, :] = np.real(res['A_per_layer'].T)

                if profile:
                    Aprof_loop[i3, :, :] = res['profile']

            # sometimes get very small negative values (like -1e-20)
            R_loop[R_loop < 0] = 0
            T_loop[T_loop < 0] = 0
            Alayer_loop[Alayer_loop < 0] = 0

            if front_or_rear == 'rear':
                Alayer_loop = np.flip(Alayer_loop, axis=2)
                print('flipping')

            R.loc[dict(pol=pol)] = R_loop
            T.loc[dict(pol=pol)] = T_loop
            Alayer.loc[dict(pol=pol)] = Alayer_loop
            theta_t.loc[dict(pol=pol)] = th_t_loop

            if profile:
                Aprof.loc[dict(pol=pol)] = Aprof_loop
                Aprof.transpose('pol', 'wl', 'angle', 'z')


        Alayer = Alayer.transpose('pol', 'wl', 'angle', 'layer')

        if profile:
            allres = xr.merge([R, T, Alayer, Aprof])
        else:
            allres = xr.merge([R, T, Alayer])

        if options['pol'] == 'u':
            allres = allres.reduce(np.mean, 'pol').assign_coords(pol='u').expand_dims('pol')


        # populate matrices

        if front_or_rear == "front":

            angle_vector_th = angle_vector[:int(len(angle_vector)/2),1]
            angle_vector_phi = angle_vector[:int(len(angle_vector)/2),2]

            phis_out = fold_phi(angle_vector_phi + np.pi, options['phi_symmetry'])
            theta_lookup = angles_in[:,1]
            quadrant = np.pi


        else:
            angle_vector_th = angle_vector[int(len(angle_vector) / 2):, 1]
            angle_vector_phi = angle_vector[int(len(angle_vector) / 2):, 2]

            phis_out = fold_phi(angle_vector_phi + np.pi, options['phi_symmetry'])
            theta_lookup = angles_in[:,1][::-1]
            quadrant = 0

        phis_out[phis_out == 0] = 1e-10

        theta_bins_in = np.digitize(angle_vector_th, theta_intv, right=True) -1

        print(theta_bins_in)
        mats = [make_matrix_wl(wl) for wl in wavelengths]

        fullmat = stack([item[0] for item in mats])
        A_mat = stack([item[1] for item in mats])

        if save:
            save_npz(savepath_RT, fullmat)
            save_npz(savepath_A, A_mat)

    return fullmat, A_mat #, allres