### Install from source Source: https://pyelli.readthedocs.io/en/latest/_sources/installation.rst.txt Clones the repository and installs the package in development mode. ```shell git clone --recurse-submodules https://github.com/PyEllips/pyElli cd pyElli pip install -e . ``` -------------------------------- ### Install and run sphinx-lint Source: https://pyelli.readthedocs.io/en/latest/contributing.html Install and execute the linter for docstrings. ```bash pip install sphinx-lint sphinx-lint ``` -------------------------------- ### Install development dependencies Source: https://pyelli.readthedocs.io/en/latest/contributing.html Install the package in editable mode with development and fitting requirements. ```bash pip install -e ".[fitting,dev]" ``` -------------------------------- ### Install development version Source: https://pyelli.readthedocs.io/en/latest/_sources/installation.rst.txt Installs the latest development version directly from the GitHub repository. ```shell pip install git+https://github.com/pyEllips/pyElli.git ``` -------------------------------- ### Install pyElli via pip Source: https://pyelli.readthedocs.io/en/latest/_sources/installation.rst.txt Installs the package with optional fitting capabilities and interactive widgets. ```shell pip install "pyElli[fitting]" ``` -------------------------------- ### Define a RepeatedLayers structure Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/structure.html Example usage for configuring a repeated layer sequence with optional leading and trailing layers. ```python Example : For layers [1,2,3] with n=2, before=1 and after=0, the structure will be 3123123. ``` -------------------------------- ### Configure materials and fit parameters Source: https://pyelli.readthedocs.io/en/latest/_downloads/179c39baed2ee634643248e3a209fc98/plot_03_custom_fitting.ipynb Sets up the substrate material and defines the initial parameters and constraints for the Cauchy SiO2 layer. ```python rii_db = elli.db.RII() Si = rii_db.get_mat("Si", "Aspnes") params = ParamsHist() params.add("SiO2_n0", value=1.452, min=-100, max=100, vary=True) params.add("SiO2_n1", value=36.0, min=-40000, max=40000, vary=True) params.add("SiO2_n2", value=0, min=-40000, max=40000, vary=False) params.add("SiO2_k0", value=0, min=-100, max=100, vary=False) params.add("SiO2_k1", value=0, min=-40000, max=40000, vary=False) params.add("SiO2_k2", value=0, min=-40000, max=40000, vary=False) params.add("SiO2_d", value=20, min=0, max=40000, vary=True) ``` -------------------------------- ### Import required libraries Source: https://pyelli.readthedocs.io/en/latest/_downloads/179c39baed2ee634643248e3a209fc98/plot_03_custom_fitting.ipynb Initializes the environment with necessary numerical, ellipsometry, and optimization modules. ```python import numpy as np import elli from elli.fitting import ParamsHist from lmfit import minimize, fit_report import matplotlib.pyplot as plt ``` -------------------------------- ### Initialize environment Source: https://pyelli.readthedocs.io/en/latest/_downloads/6241747599c6d57ef81d820eee481957/plot_bragg_mirror.ipynb Import necessary libraries and configure numpy output settings. ```python import elli import elli.plot as elliplot import matplotlib.pyplot as plt import numpy as np np.set_printoptions(suppress=True, precision=3) ``` -------------------------------- ### Build documentation locally Source: https://pyelli.readthedocs.io/en/latest/contributing.html Generate HTML documentation to verify changes. ```bash python setup.py build_sphinx ``` -------------------------------- ### Set Layers Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/structure.html Sets the sequence of layers starting from z=0. ```python def set_layers(self, layers: List[Layer]) -> None: """Sets sequence of layers. Args: layers (List[Layer]): List of Layers, starting from z=0 """ if not isinstance(layers, list): raise TypeError("Provide a list of layers.") for layer in layers: if not isinstance(layer, AbstractLayer): raise TypeError( f"Expected a list of Layer objects but found an item of type {type(layer)}." ) self.layers = layers ``` -------------------------------- ### Import pyElli and dependencies Source: https://pyelli.readthedocs.io/en/latest/_downloads/c379b43dfec1432b228d47dda83b8ca5/plot_cholesteric_lq.ipynb Initializes the environment with necessary libraries for simulation and plotting. ```python import elli import elli.plot as elliplot import matplotlib.pyplot as plt import numpy as np from scipy.constants import c, pi ``` -------------------------------- ### FitMuellerMatrix.get_model_data(params=None, append_exp_data=False) Source: https://pyelli.readthedocs.io/en/latest/plot.html Gets the data from the provided model with the provided parameters. ```APIDOC ## FitMuellerMatrix.get_model_data ### Description Gets the data from the provided model with the provided parameters. If no parameters are provided, the fitted parameters are used. ### Parameters - **params** (Parameters, optional) - The parameters to calculate the model with. Defaults to None. - **append_exp_data** (bool, optional) - Appends the experimental data if set to True. Defaults to False. ``` -------------------------------- ### Import ellipsometric data Source: https://pyelli.readthedocs.io/en/latest/_downloads/179c39baed2ee634643248e3a209fc98/plot_03_custom_fitting.ipynb Loads psi and delta spectra from a NeXus file and filters by wavelength range. ```python data = elli.read_nexus_psi_delta("SiO2onSi.ellips.nxs").loc[ (slice(None), slice(210, 800)), : ] lbda = data.loc[50].index.get_level_values("Wavelength").to_numpy() data ``` -------------------------------- ### Get Permittivity Profile Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/structure.html Returns the permittivity profile of the complete structure for given wavelengths. ```python def get_permittivity_profile( self, lbda: npt.ArrayLike ) -> List[Tuple[float, npt.NDArray]]: """Returns the permittivity profile of the complete structure for the given wavelengths. Args: lbda (npt.ArrayLike): Single value or array of wavelengths (in nm). Returns: List[Tuple[float, npt.NDArray]]: Returns list of tuples [(thickness, dielectric tensor), ...] """ permittivity_profile = [(np.inf, self.front_material.get_tensor(lbda))] for layer in self.layers: permittivity_profile.extend(layer.get_permittivity_profile(lbda)) permittivity_profile.extend([(np.inf, self.back_material.get_tensor(lbda))]) return permittivity_profile ``` -------------------------------- ### plot_mmatrix Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/plot/mueller_matrix.html Plots one or more Mueller matrix dataframes. This function requires the plotly library to be installed. ```APIDOC ## plot_mmatrix(dataframes, colors=None, dashes=None, names=None, single=True, full_scale=False, sharex=False) ### Description Takes multiple Mueller matrix dataframes with columns Mxy for matrix position x,y and plots them together. Returns a plotly figure object. ### Parameters - **dataframes** (Union[pd.DataFrame, List[pd.DataFrame]]) - Required - A dataframe or a list of dataframes containing data of the same index. - **colors** (Optional[List[str]]) - Optional - A list of colors which are cycled for each dataframe index. Defaults to None. - **dashes** (Optional[List[str]]) - Optional - A list of dash line styles which are cycled for each dataframe index. Defaults to None. - **names** (Optional[List[str]]) - Optional - A name for each dataframe index. Defaults to None. - **single** (bool) - Optional - Uses a single plot if set and a grid if not set. Defaults to True. - **full_scale** (bool) - Optional - Sets the y-axis limits to [-1, 1] if set. Defaults to False. - **sharex** (bool) - Optional - Ties the zooming of the x-axis together for each plot in grid view. Defaults to False. ### Returns - **go.Figure** - A plotly figure containing the data from dataframes as a grid or single view. ``` -------------------------------- ### Create parameter estimation widgets Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/fitting/decorator_psi_delta.html Initializes the UI components including dropdowns, checkboxes, and buttons for parameter estimation. ```python def create_widgets(self) -> None: """Create ipywidgets for parameter estimation""" self.param_widgets = {} self.selector = widgets.Dropdown( options=["Psi/Delta", "Rho", "Pseudo Diel.", "Residual"], value="Psi/Delta", description="Display: ", disabled=False, ) self.selector.observe(self.update_selection, names="value") checkboxes = [] for param in self.params.valuesdict(): curr_checkbox = widgets.Checkbox( value=self.params[param].vary, description_tooltip=param, layout=widgets.Layout(width="15px"), indent=False, ) curr_checkbox.observe(self.set_vary_param, names="value") curr_widget = widgets.BoundedFloatText( self.params[param], min=( float_info.min if self.params[param].min == -float("inf") else self.params[param].min ), max=( float_info.max if self.params[param].max == float("inf") else self.params[param].max ), description=param, continuous_update=False, ) curr_widget.observe(self.update_params, names="value") checkboxes.append(curr_checkbox) self.param_widgets[param] = curr_widget fit_button = widgets.Button(description="Fit") fit_button.on_click(lambda _: self.fit_button_clicked()) button_list = [self.selector, fit_button] if isinstance(self.params, ParamsHist): undo_button = widgets.Button(description="Undo") undo_button.on_click(self.re_undo_button_clicked) redo_button = widgets.Button(description="Redo") redo_button.on_click(self.re_undo_button_clicked) init_button = widgets.Button(description="Set initial") init_button.on_click(lambda _: self.reset_to_init_params()) button_list.append(undo_button) button_list.append(redo_button) button_list.append(init_button) combo_widget = [ widgets.HBox([v, w]) for v, w in zip(list(self.param_widgets.values()), checkboxes) ] if is_in_notebook(): display( widgets.VBox( [ widgets.HBox( combo_widget + button_list, layout=widgets.Layout( width="100%", display="inline-flex", flex_flow="row wrap", ), ), self.fig, ] ) ) ``` -------------------------------- ### get(name) Source: https://pyelli.readthedocs.io/en/latest/result.html Retrieves the data for a requested variable by name, such as reflection coefficients or ellipsometry parameters. ```APIDOC ## get(name) ### Description Returns the data for the requested variable name. Examples include 'r_sp', 'T_pp', 'psi', 'delta', and 'rho'. ### Parameters - **name** (str) - Required - Variable name to return. ### Returns - **npt.NDArray** - Array of data. ``` -------------------------------- ### Get Twisted Layer Permittivity Tensor Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/structure.html Calculates the permittivity tensor for a specific position and wavelength. ```python def get_tensor(self, z: float, lbda: npt.ArrayLike) -> npt.NDArray: """Gets permittivity tensor matrix for position 'z' and wavelength 'lbda'. Args: z (float): Position in the layer (in nm) lbda (npt.ArrayLike): Single value or array of wavelengths (in nm). Returns: npt.NDArray: Permittivity tensor for position 'z' and wavelength 'lbda'. """ epsilon = self.material.get_tensor(lbda) m_r = rotation_v_theta(E_Z, self.angle * z / self.thickness) return m_r @ epsilon @ m_r.T ``` -------------------------------- ### Initialize Solver4x4 Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/solver4x4.html Constructor for the solver, requiring an experiment and an optional propagator. ```python def __init__( self, experiment: "Experiment", propagator: Propagator = PropagatorExpm() ) -> None: super().__init__(experiment) self.propagator = propagator ``` -------------------------------- ### Plot Mueller Matrix Data Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/plot/mueller_matrix.html Visualizes one or more Mueller matrix dataframes. Requires the plotly library to be installed. ```python """Plotting functions for Mueller matrices""" # Encoding: utf-8 from typing import List, Union, Optional import pandas as pd try: import plotly.graph_objects as go from plotly.subplots import make_subplots except ImportError as e: raise ImportError( "Optional dependency plotly is not installed. This module will not work properly.\n" "Try installing this package with the additional fitting requirement, " "i.e. pip install pyElli[fitting]" ) from e COLORS = [ "#636EFA", "#EF553B", "#00CC96", "#AB63FA", "#FFA15A", "#19D3F3", "#FF6692", "#B6E880", "#FF97FF", "#FECB52", ] [docs] def plot_mmatrix( dataframes: Union[pd.DataFrame, List[pd.DataFrame]], colors: Optional[List[str]] = None, dashes: Optional[List[str]] = None, names: Optional[List[str]] = None, single: bool = True, full_scale: bool = False, sharex: bool = False, ) -> go.Figure: """Takes multiple Mueller matrix dataframes with columns Mxy for matrix postion x,y and plots them together. Needs plotly as additional requirement to work. Args: dataframes (Union[pd.Dataframe, List[pd.DataFrame]]): A dataframe or a list of dataframes containing data of the same index. colors (Optional[List[str]], optional): A list of colors which are cycled for each dataframes index. Defaults to None. dashes (Optional[List[str]], optional): A list of dash line styles which are cycled for each dataframes index. Defaults to None. names (Optional[List[str]], optional): A name for each dataframe index. Defaults to None. single (bool, optional): Uses a single plot if set and a grid if not set. Defaults to True. full_scale (bool, optional): Sets the y-axis limits to [-1, 1] if set. Defaults to False. sharex (bool, optional): Ties the zooming of the x-axis together for each plot in grid view. Defaults to False. Returns: go.Figure: A plotly figure containing the data from dataframes as a grid or single view. """ if isinstance(dataframes, pd.DataFrame): dataframes = [dataframes] if colors is None: colors = COLORS if dashes is None: dashes = ["solid", "dash", "dot"] if names is None: names = ["", "theory"] if single: fig = go.Figure() fig.update_layout( yaxis_title="Müller Matrix Elements", xaxis_title="Wavelength (nm)" ) else: fig = make_subplots(rows=4, cols=4) if full_scale: fig.update_yaxes(range=[-1, 1]) for i, melem in enumerate(dataframes[0]): coli = colors[i % len(colors)] for j, mueller_df in enumerate(dataframes): dashi = dashes[j % len(dashes)] namesi = names[j % len(names)] if single: fig.add_trace( go.Scatter( x=mueller_df.index, y=mueller_df[melem], name=f"{melem} {namesi}", line=dict(color=coli, dash=dashi), ) ) else: fig.add_trace( go.Scatter( x=mueller_df.index, y=mueller_df[melem], name=f"{melem} {namesi}", line=dict(color=coli, dash=dashi), ), row=1 if single else i // 4 + 1, col=1 if single else i % 4 + 1, ) if sharex: fig.update_xaxes(matches="x") return go.FigureWidget(fig) ``` -------------------------------- ### Initialize Structure Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/structure.html Constructor for defining the front material, layer sequence, and back material. ```python def __init__( self, front: IsotropicMaterial, layers: List[Layer], back: Material ) -> None: """ Args: front (IsotropicMaterial): IsotropicMaterial used as front half space layers (List[Layer]): List of Layers, starting from z=0 back (Material): Material used as back half space """ self.set_front_material(front) self.set_layers(layers) self.set_back_material(back) ``` -------------------------------- ### TableEpsilon Class Implementation Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/dispersions/table_epsilon.html The core implementation of the TableEpsilon dispersion model, including initialization, parameter validation, and interpolation setup. ```python # Encoding: utf-8 """Dispersion specified by a table of wavelengths (nm) and dielectric function values.""" from typing import Any, Dict, Union import numpy.typing as npt import scipy.interpolate from .epsilon_inf import EpsilonInf from .base_dispersion import Dispersion, InvalidParameters, DispersionSum [docs] class TableEpsilon(Dispersion): r"""Dispersion specified by a table of wavelengths (nm) and dielectric function values. Please not that this model will produce errors for wavelengths outside the provided wavelength range. Single parameters: :lbda (list): Wavelengths in nm. This value must be provided. :epsilon: Complex dielectric function values in the convention ε1 + iε2. This value must be provided. :kind: Type of interpolation (see scipy.interpolate.interp1d for more information). Defaults to 'linear'. Repeated parameters: -- Output: The interpolation in the given wavelength range. """ single_params_template = {"lbda": None, "epsilon": None} rep_params_template: Dict[str, Any] = {} def __init__(self, *args, **kwargs) -> None: self.kind = kwargs.pop("kind", "linear") super().__init__(*args, **kwargs) if len(self.single_params.get("lbda")) == 0: raise InvalidParameters("Wavelength array cannot be of length zero.") if len(self.single_params.get("epsilon")) != len( self.single_params.get("lbda") ): raise InvalidParameters( "Wavelength and epsilon arrays must have the same length." ) self.interpolation = scipy.interpolate.interp1d( self.single_params.get("lbda"), self.single_params.get("epsilon"), kind=self.kind, ) self.default_lbda_range = self.single_params.get("lbda") def __add__(self, other: Union[int, float, "Dispersion"]) -> "DispersionSum": if isinstance(other, (int, float)): return DispersionSum(self, EpsilonInf(eps=other)) if isinstance(other, TableEpsilon): raise NotImplementedError( "Adding of tabular dispersions is not yet supported" ) if isinstance(other, Dispersion): return DispersionSum(self, other) if isinstance(other, DispersionSum): other.dispersions.append(self) return other raise TypeError( f"unsupported operand type(s) for +: '{type(self)}' and '{type(other)}'" ) [docs] def dielectric_function(self, lbda: npt.ArrayLike) -> npt.NDArray: return self.interpolation(lbda) ``` -------------------------------- ### Visualize initial model parameters Source: https://pyelli.readthedocs.io/en/latest/_downloads/a06e226018fed45ba24e9f48848ccf20/plot_02_TiO2_multilayer.ipynb Displays the current model state to assess deviation from experimental data before fitting. ```python model.plot() ``` -------------------------------- ### get(name: str) -> npt.NDArray Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/result.html Retrieves data for a specific variable by name, such as reflection coefficients, transmission coefficients, or ellipsometry parameters. ```APIDOC ## get(name: str) ### Description Returns the data for the requested variable name. Supported names include reflection coefficients (e.g., 'r_sp'), circular polarization reflection (e.g., 'r_LR'), power transmission coefficients (e.g., 'T_pp'), and ellipsometry parameters (e.g., 'Ψ_ps', 'Δ_pp', 'psi', 'delta', 'rho'). ### Parameters - **name** (str) - Required - The name of the variable to retrieve. ### Returns - **npt.NDArray** - An array containing the requested data. ``` -------------------------------- ### TableSpectraRay.__init__ Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/dispersions/table_spectraray.html Initializes the TableSpectraRay helper class with the directory path containing Spectraray files. ```APIDOC ## TableSpectraRay(path: str) ### Description Initializes the helper class to load Spectraray tabulated dielectric functions from a specified directory. ### Parameters - **path** (str) - Required - The folder path where the Spectraray files are saved. ``` -------------------------------- ### Get Varying Mixture Layer Permittivity Tensor Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/structure.html Calculates the permittivity tensor for a specific position and wavelength using the modulation function. ```python def get_tensor(self, z: float, lbda: npt.ArrayLike) -> npt.NDArray: """Gets permittivity tensor matrix for position 'z' and wavelength 'lbda'. Args: z (float): Position in the layer (in nm) lbda (npt.ArrayLike): Single value or array of wavelengths (in nm). Returns: npt.NDArray: Permittivity tensor for position 'z' and wavelength 'lbda'. """ epsilon = self.material.get_tensor_fraction( lbda, self.fraction_modulation(z / self.thickness) ) return epsilon ``` -------------------------------- ### Run unit tests Source: https://pyelli.readthedocs.io/en/latest/contributing.html Verify that changes do not break existing functionality. ```bash pytest ``` -------------------------------- ### Create a virtual environment with virtualenv Source: https://pyelli.readthedocs.io/en/latest/contributing.html Use this to isolate project dependencies using virtualenv. ```bash virtualenv source /bin/activate ``` -------------------------------- ### Get Quarter Wave Plate Thickness Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/utils.html Calculates the required thickness in nm for a material to act as a quarter wave plate at a specific wavelength. ```python [docs] def get_qwp_thickness(material: "Material", lbda: float) -> float: """Return the thickness of a material in nm for a quarter wave plate at wavelength 'lbda'. Args: material (Material): Material object of the quarter wave plate lbda (float): Wavelength (in nm) at which the quarter wave plate is calculated Returns: float: Thickness (in nm) of quarter wave plate """ nr = np.real(material.get_refractive_index(lbda)[0, 0, 0]) return lbda / (4.0 * nr) ``` -------------------------------- ### Clone the repository Source: https://pyelli.readthedocs.io/en/latest/contributing.html Download the project source code and submodules. ```bash git clone --recurse-submodules https://github.com/PyEllips/pyElli cd pyElli ``` -------------------------------- ### Initialize RII Database Source: https://pyelli.readthedocs.io/en/latest/database.html Initialize the RII class to load the entire catalog from the refractiveindex.info database. ```python RII = elli.db.RII() ``` -------------------------------- ### Initialize fitting UI widgets Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/fitting/decorator_mmatrix.html Creates interactive widgets for undo, redo, initial parameter reset, and residual display within a Jupyter notebook environment. ```python if isinstance(self.params, ParamsHist): undo_button = widgets.Button(description="Undo") undo_button.on_click(self.re_undo_button_clicked) redo_button = widgets.Button(description="Redo") redo_button.on_click(self.re_undo_button_clicked) init_button = widgets.Button(description="Set initial") init_button.on_click(lambda _: self.reset_to_init_params()) button_list.append(undo_button) button_list.append(redo_button) button_list.append(init_button) residual_checkbox = widgets.Checkbox( value=self.show_residual, description="Display Residual" ) residual_checkbox.observe(self.update_residual, names="value") combo_widget = [ widgets.HBox([v, w]) for v, w in zip(list(self.param_widgets.values()), checkboxes) ] if is_in_notebook(): display( widgets.VBox( [ widgets.HBox( combo_widget + button_list + [residual_checkbox], layout=widgets.Layout( width="100%", display="inline-flex", flex_flow="row wrap", ), ), self.fig, ] ) ) ``` -------------------------------- ### Importing Required Modules Source: https://pyelli.readthedocs.io/en/latest/_downloads/4262f3154d85a6d2f8fa08d00ef99314/plot_SiO2_Si_MM.ipynb Initial imports for the elli library and fitting utilities. ```python import elli from elli.fitting import ParamsHist, fit_mueller_matrix ``` -------------------------------- ### Manage mixture material constituents and fractions Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/materials.html Handles the creation of material mixtures by validating host/guest materials and ensuring the guest fraction is within the [0, 1] range. ```python [docs] class MixtureMaterial(Material): """Abstract Class for mixed materials.""" host_material = None guest_material = None fraction = None def __init__( self, host_material: Material, guest_material: Material, fraction: float ) -> None: """Creates a material mixture from two materials. Args: host_material (Material): Host Material. guest_material (Material): Material incorporated in the host. fraction (float): Fraction of the guest material (Range 0 - 1). """ self.set_constituents(host_material, guest_material) self.set_fraction(fraction) [docs] def set_constituents( self, host_material: Material, guest_material: Material ) -> None: """Sets Materials in the mixture. Args: host_material (Material): Host Material. guest_material (Material): Material incorporated in the host. """ if not isinstance(host_material, Material): raise TypeError( f"Expected material to be an Material object but found type {type(host_material)}." ) if not isinstance(guest_material, Material): raise TypeError( f"Expected material to be an Material object but found type {type(guest_material)}." ) self.host_material = host_material self.guest_material = guest_material [docs] def set_fraction(self, fraction: float) -> None: """Sets fraction and checks if fraction is in range from 0 to 1. Args: fraction (float): Fraction of the guest material (Range 0 - 1). """ if not 0 <= fraction <= 1: raise ValueError("Fraction is not in range from 0 to 1") self.fraction = fraction ``` -------------------------------- ### Load silicon dispersion from database Source: https://pyelli.readthedocs.io/en/latest/auto_examples/plot_01_basic_usage.html Initializes the refractiveindex.info database interface and retrieves silicon material data. ```python rii_db = elli.db.RII() Si = rii_db.get_mat("Si", "Aspnes") ``` -------------------------------- ### Create a virtual environment with Miniconda Source: https://pyelli.readthedocs.io/en/latest/contributing.html Use this to create a dedicated environment using Miniconda. ```bash conda create -n pyElli python=3 six virtualenv pytest pytest-cov conda activate pyElli ``` -------------------------------- ### Configure experiment parameters Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/experiment.html Methods to update the structure, incident light vector, angle, and wavelengths for an existing experiment instance. ```python def set_structure(self, structure: "Structure") -> None: """Defines the Structure to evaluate. Args: structure (Structure): Structure object to evaluate. """ self.structure = structure def set_vector(self, vector: npt.ArrayLike) -> None: """Defines the Jones or Stokes vector of the incident Light. Jones: [1, 0]: horizontal polarized [0, 1]: vertical polarized [1/sqrt(2), 1/sqrt(2)]: diagonal polarized [1/sqrt(2),-1/sqrt(2)]: anti-diagonal polarized Stokes: [1,0,0,0]: unpolarized light [1,1,0,0]: horizontal polarized [1,-1,0,0]: vertical polarized [1,0,1,0]: diagonal polarized [1,0,-1,0]: anti-diagonal polarized Args: vector (npt.ArrayLike): Jones or Stokes vector of incident light. """ if vector is None: vector = [1, 0, 1, 0] vector = np.asarray(vector) if vector.shape == (2,): self.jones_vector = vector self.stokes_vector = np.array( [ np.abs(self.jones_vector[0]) ** 2 + np.abs(self.jones_vector[1]) ** 2, np.abs(self.jones_vector[0]) ** 2 - np.abs(self.jones_vector[1]) ** 2, 2 * np.real( self.jones_vector[0] * np.conjugate(self.jones_vector[1]) ), -2 * np.imag( self.jones_vector[0] * np.conjugate(self.jones_vector[1]) ), ] ) elif vector.shape == (4,): self.stokes_vector = vector p = ( np.sqrt( self.stokes_vector[1] ** 2 + self.stokes_vector[2] ** 2 + self.stokes_vector[3] ** 2 ) / self.stokes_vector[0] ) Q = self.stokes_vector[1] / (self.stokes_vector[0] * p) U = self.stokes_vector[2] / (self.stokes_vector[0] * p) V = self.stokes_vector[3] / (self.stokes_vector[0] * p) if Q == -1: a = 0 b = 1 else: a = np.sqrt((1 + Q) / 2) b = U / (2 * a) - 1j * V / (2 * a) self.jones_vector = np.array([a, b]) def set_theta(self, theta_i: float) -> None: """Set incident angle to evaluate. Args: theta_i (float): Incident angle (in degrees). """ self.theta_i = theta_i def set_lbda(self, lbda: npt.ArrayLike) -> None: """Set experiment wavelengths. Args: lbda (npt.ArrayLike): single value or array of wavelengths (in nm). """ lbda_array = np.asarray(lbda) if np.shape(lbda_array) == (): lbda_array = np.asarray([lbda]) self.lbda = lbda_array ``` -------------------------------- ### Initialize fitting class Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/fitting/decorator_psi_delta.html Constructor for the Psi/Delta fitting class. ```python def __init__( self, exp_data: pd.DataFrame, params: Parameters, model: Callable[[npt.NDArray, Parameters], Result], angle: float = 70, **kwargs, ) -> None: """Intialize the psi/delta fitting class ``` -------------------------------- ### Simulate interface with pyElli Source: https://pyelli.readthedocs.io/en/latest/_downloads/23e6e78dd54cf5d9d570dd5588354f84/plot_interface_reflection.ipynb Evaluates the optical structure using pyElli's evaluate method and aggregates results. ```python data = elli.ResultList([s.evaluate(lbda, Phi_i) for Phi_i in Phi_list]) R_pp = data.R_pp R_ss = data.R_ss T_pp = data.T_pp T_ss = data.T_ss t2_pp = np.abs(data.t_pp) ** 2 t2_ss = np.abs(data.t_ss) ** 2 ``` -------------------------------- ### Create Parameter Estimation Widgets Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/fitting/decorator_mmatrix.html Generates ipywidgets for interactive parameter adjustment and fitting control within a notebook environment. ```python def create_widgets(self) -> None: """Create ipywidgets for parameter estimation""" self.param_widgets = {} checkboxes = [] for param in self.params.valuesdict(): curr_checkbox = widgets.Checkbox( value=self.params[param].vary, description_tooltip=param, layout=widgets.Layout(width="15px"), indent=False, ) curr_checkbox.observe(self.set_vary_param, names="value") curr_widget = widgets.BoundedFloatText( self.params[param], min=self.params[param].min, max=self.params[param].max, description=param, continuous_update=False, ) curr_widget.observe(self.update_params, names=("value", "owner")) self.param_widgets[param] = curr_widget checkboxes.append(curr_checkbox) fit_button = widgets.Button(description="Fit") fit_button.on_click(lambda _: self.fit_button_clicked()) button_list = [fit_button] ``` -------------------------------- ### Execute the Fit Source: https://pyelli.readthedocs.io/en/latest/_sources/auto_examples/plot_03_custom_fitting.rst.txt Runs the minimization process and prints the resulting fit statistics. ```Python out = minimize(fit_function, params, args=(lbda, data), method="leastsq") print(fit_report(out)) ``` ```none [[Fit Statistics]] # fitting method = leastsq # function evals = 119 # data points = 3546 # variables = 3 chi-square = 157.748841 reduced chi-square = 0.04452409 Akaike info crit = -11031.1779 Bayesian info crit = -11012.6572 [[Variables]] SiO2_n0: 1.63549687 +/- 0.03012997 (1.84%) (init = 1.452) SiO2_n1: 27.6408771 +/- 8.75248298 (31.66%) (init = 36) SiO2_n2: 0 (fixed) SiO2_k0: 0 (fixed) SiO2_k1: 0 (fixed) SiO2_k2: 0 (fixed) SiO2_d: 1.75831329 +/- 0.02404272 (1.37%) (init = 20) [[Correlations]] (unreported correlations are < 0.100) C(SiO2_n0, SiO2_d) = -0.9916 C(SiO2_n0, SiO2_n1) = -0.9596 C(SiO2_n1, SiO2_d) = +0.9259 ``` -------------------------------- ### Initialize Twisted Layer Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/structure.html Creates a layer with a twisted material, setting thickness, divisions, and rotation angle. ```python def __init__( self, material: Material, thickness: float, div: int, angle: float ) -> None: """Creates a layer with a twisted material. Args: material (Material): Material object thickness (float): Thickness of layer (in nm) div (int): Number of slices for the layer angle (float): rotation angle over the distance 'd' (in degrees) """ self.set_material(material) self.set_thickness(thickness) self.set_divisions(div) self.set_angle(angle) ``` -------------------------------- ### Build and evaluate the model Source: https://pyelli.readthedocs.io/en/latest/_downloads/a06e226018fed45ba24e9f48848ccf20/plot_02_TiO2_multilayer.ipynb Defines the physical model structure using Cauchy layers and evaluates it using the 2x2 solver. ```python @fit(tss, params) def model(lbda, params): SiO2 = elli.Cauchy( params["SiO2_n0"], params["SiO2_n1"], params["SiO2_n2"], params["SiO2_k0"], params["SiO2_k1"], params["SiO2_k2"], ).get_mat() TiO2 = elli.Cauchy( params["TiO2_n0"], params["TiO2_n1"], params["TiO2_n2"], params["TiO2_k0"], params["TiO2_k1"], params["TiO2_k2"], ).get_mat() Layer = [elli.Layer(TiO2, params["TiO2_d"]), elli.Layer(SiO2, params["SiO2_d"])] return elli.Structure(elli.AIR, Layer, Si).evaluate(lbda, 70, solver=elli.Solver2x2) # Alternative: Use 4x4 Solver with scipy propagator # return elli.Structure(elli.AIR, Layer, Si).evaluate(lbda, 70, solver=elli.Solver4x4, propagator=elli.PropagatorExpm()) ``` -------------------------------- ### Define materials Source: https://pyelli.readthedocs.io/en/latest/_downloads/6241747599c6d57ef81d820eee481957/plot_bragg_mirror.ipynb Set up the refractive indices for the incident medium, substrate, and mirror materials. ```python air = elli.AIR glass = elli.ConstantRefractiveIndex(1.5).get_mat() SiO2 = elli.ConstantRefractiveIndex(1.47).get_mat() TiO2 = elli.ConstantRefractiveIndex(2.23 + 1j * 5.2e-4).get_mat() ``` -------------------------------- ### Initialize Mueller Matrix Fitting Class Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/fitting/decorator_mmatrix.html Constructor for the Mueller matrix fitting class, setting up experimental data, model parameters, and initial visualization. ```python def __init__( self, exp_mm: pd.DataFrame, params: Parameters, model: Callable[[npt.NDArray, Parameters], Result], **kwargs, ) -> None: """Intialize the mueller matrix fitting class Args: exp_mm (pd.DataFrame): The dataframe containing an experimental mueller matrix. It should contain 16 columns with labels Mxy, where xy are the matrix positions. params (Parameters): Fitting start parameters model (Callable[[npt.NDArray, Parameters], Result]): A function taking wavelengths as first parameter and fitting parameters as second, which returns a pyEllis Result object. This function contains the actual model which should be fitted **display_single (bool): Returns a figure containing a single graph, if set to true. Returns a grid of figures otherwise. **sharex (bool): Ties the zoom of the x-axes together for grid view. **full_scale (bool): Sets the y-axis scale to [-1, 1] if set to True. """ super().__init__() self.exp_mm = exp_mm self.params = params self.fitted_params = params.copy() self.initial_params = params.copy() self.model = model self.param_widgets = {} self.show_residual = False mm_kwargs = ("display_single", "sharex", "full_scale") for kwarg in mm_kwargs: if kwarg in kwargs: setattr(self, kwarg, kwargs[kwarg]) del kwargs[kwarg] self.fit_kwargs = kwargs model_df = mmatrix_to_dataframe( exp_mm, model(exp_mm.index.values, params).mueller_matrix ) self.fig = plot_mmatrix( [exp_mm, model_df], single=self.display_single, sharex=self.sharex, full_scale=self.full_scale, ) self.create_widgets() ``` -------------------------------- ### Define simulation parameters Source: https://pyelli.readthedocs.io/en/latest/_downloads/c379b43dfec1432b228d47dda83b8ca5/plot_cholesteric_lq.ipynb Sets the wavelength range for the simulation based on the reflection band center. ```python # Define wavelength range in nm for the calculation lbda_min, lbda_max = 800, 1200 lbda_B = p * n_med # Expected center of the reflection band lbda_list = np.linspace(lbda_min, lbda_max, 100) ``` -------------------------------- ### Run pyElli simulation Source: https://pyelli.readthedocs.io/en/latest/_sources/auto_examples/plot_cholesteric_lq.rst.txt Execute the optical simulation to obtain Jones matrix elements. ```Python # Run the full optical simulation for the defined structure and wavelength list data = s.evaluate(lbda_list, 0) ``` -------------------------------- ### Perform fitting with specified method Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/fitting/decorator_psi_delta.html Executes the fitting process using a specified method, defaulting to 'leastsq'. ```python rho = calc_rho(self.exp_data) res = minimize( self.fit_function, self.params, args=(rho.index.to_numpy(), rho.values.real, rho.values.imag), method=method, ) self.fitted_params = res.params return res ``` -------------------------------- ### Experiment Class Constructor Source: https://pyelli.readthedocs.io/en/latest/experiment.html Creates a virtual experiment to simulate the behavior of a structure. ```APIDOC ## Constructor: elli.experiment.Experiment(structure, lbda, theta_i, vector=None) ### Description Creates a virtual experiment to simulate the behavior of a structure. ### Parameters - **structure** (Structure) - Required - Structure object to evaluate. - **lbda** (npt.ArrayLike) - Required - Single value or array of wavelengths (in nm). - **theta_i** (float) - Required - Incident angle (in degrees). - **vector** (npt.ArrayLike) - Optional - Jones or Stokes vector of incident light. Defaults to diagonal polarization ([1, 0, 1, 0]). ``` -------------------------------- ### Define fit parameters Source: https://pyelli.readthedocs.io/en/latest/_downloads/a06e226018fed45ba24e9f48848ccf20/plot_02_TiO2_multilayer.ipynb Configures the Cauchy model parameters for SiO2 and TiO2 layers, setting fixed values for the substrate and variable values for the TiO2 layer. ```python params = ParamsHist() params.add("SiO2_n0", value=1.452, min=-100, max=100, vary=False) params.add("SiO2_n1", value=36.0, min=-40000, max=40000, vary=False) params.add("SiO2_n2", value=0, min=-40000, max=40000, vary=False) params.add("SiO2_k0", value=0, min=-100, max=100, vary=False) params.add("SiO2_k1", value=0, min=-40000, max=40000, vary=False) params.add("SiO2_k2", value=0, min=-40000, max=40000, vary=False) params.add("SiO2_d", value=276.36, min=0, max=40000, vary=False) params.add("TiO2_n0", value=2.236, min=-100, max=100, vary=True) params.add("TiO2_n1", value=451, min=-40000, max=40000, vary=True) params.add("TiO2_n2", value=251, min=-40000, max=40000, vary=True) params.add("TiO2_k0", value=0, min=-100, max=100, vary=False) params.add("TiO2_k1", value=0, min=-40000, max=40000, vary=False) params.add("TiO2_k2", value=0, min=-40000, max=40000, vary=False) params.add("TiO2_d", value=20, min=0, max=40000, vary=True) ``` -------------------------------- ### create_widgets(self) Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/fitting/decorator_psi_delta.html Initializes and displays the ipywidgets for parameter estimation, including dropdowns, checkboxes, and buttons. ```APIDOC ## create_widgets() ### Description Creates and renders the ipywidgets interface for parameter estimation, including parameter input fields, checkboxes for varying parameters, and control buttons like Fit, Undo, and Redo. ``` -------------------------------- ### fit(method: str = 'leastsq') -> MinimizerResult Source: https://pyelli.readthedocs.io/en/latest/_modules/elli/fitting/decorator_mmatrix.html Executes the lmfit minimization process using the current fitting parameters. ```APIDOC ## fit(method: str = 'leastsq') ### Description Executes lmfit with the current fitting parameters to minimize the fitting problem. ### Parameters - **method** (str) - Optional - The fitting method to use. Any method supported by scipy's curve_fit is allowed. Defaults to 'leastsq'. ### Returns - **MinimizerResult** - The result of the fitting process. ``` -------------------------------- ### Defining Cauchy Dispersion Parameters Source: https://pyelli.readthedocs.io/en/latest/_downloads/4262f3154d85a6d2f8fa08d00ef99314/plot_SiO2_Si_MM.ipynb Sets initial values and constraints for the SiO2 layer thickness and Cauchy dispersion coefficients. ```python params = ParamsHist() params.add("SiO2_n0", value=1.452, min=-100, max=100, vary=True) params.add("SiO2_n1", value=36.0, min=-40000, max=40000, vary=True) params.add("SiO2_n2", value=0, min=-40000, max=40000, vary=True) params.add("SiO2_k0", value=0, min=-100, max=100, vary=True) params.add("SiO2_k1", value=0, min=-40000, max=40000, vary=True) params.add("SiO2_k2", value=0, min=-40000, max=40000, vary=True) params.add("SiO2_d", value=120, min=0, max=40000, vary=True) ``` -------------------------------- ### Define model parameters Source: https://pyelli.readthedocs.io/en/latest/_downloads/c25feab16df63f0d6559214960d80f23/plot_01_basic_usage.ipynb Sets up the Cauchy model parameters for the SiO2 layer using the ParamsHist class. ```python params = ParamsHist() params.add("SiO2_n0", value=1.452, min=-100, max=100, vary=False) params.add("SiO2_n1", value=36.0, min=-40000, max=40000, vary=False) params.add("SiO2_n2", value=0, min=-40000, max=40000, vary=False) params.add("SiO2_k0", value=0, min=-100, max=100, vary=False) params.add("SiO2_k1", value=0, min=-40000, max=40000, vary=False) params.add("SiO2_k2", value=0, min=-40000, max=40000, vary=False) params.add("SiO2_d", value=20, min=0, max=40000, vary=True) ``` -------------------------------- ### Calculate layer thicknesses Source: https://pyelli.readthedocs.io/en/latest/_downloads/6241747599c6d57ef81d820eee481957/plot_bragg_mirror.ipynb Determine the quarter-wavelength thickness for SiO2 and TiO2 at 1550 nm. ```python lbda0 = 1550 d_SiO2 = elli.get_qwp_thickness(SiO2, lbda0) d_TiO2 = elli.get_qwp_thickness(TiO2, lbda0) print("Thickness of the SiO2 QWP: {} nm".format(d_SiO2)) print("Thickness of the TiO2 QWP: {} nm".format(d_TiO2)) L_SiO2 = elli.Layer(SiO2, d_SiO2) L_TiO2 = elli.Layer(TiO2, d_TiO2) ``` -------------------------------- ### Initialize single dispersion parameters Source: https://pyelli.readthedocs.io/en/latest/_sources/dispersions.rst.txt Set fixed parameters for a dispersion model directly within the class constructor. ```python Cauchy(n0=1.458, n1=3.54e-3, n2=0, k0=0, k1=0, k2=0) ``` -------------------------------- ### Load experimental data Source: https://pyelli.readthedocs.io/en/latest/_downloads/a06e226018fed45ba24e9f48848ccf20/plot_02_TiO2_multilayer.ipynb Reads ellipsometric data from a file and filters by angle and spectral range. ```python tss = elli.read_spectraray_psi_delta("TiO2_400cycles.txt").loc[70.06].loc[400:800] ``` -------------------------------- ### Load silicon substrate dispersion Source: https://pyelli.readthedocs.io/en/latest/_downloads/a06e226018fed45ba24e9f48848ccf20/plot_02_TiO2_multilayer.ipynb Retrieves the refractive index data for Silicon from the built-in database. ```python rii_db = elli.db.RII() Si = rii_db.get_mat("Si", "Aspnes") ```