From 84bbe3ac81d1c145e7e81956a2c957718bda0dbe Mon Sep 17 00:00:00 2001 From: Roy Smart Date: Mon, 11 Mar 2024 01:16:04 -0600 Subject: [PATCH 1/4] Added `optika.vectors.AbstractPolarizationVectorArray.average` property. --- optika/vectors/_tests/test_vectors_polarization.py | 3 +++ optika/vectors/_vectors_polarization.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/optika/vectors/_tests/test_vectors_polarization.py b/optika/vectors/_tests/test_vectors_polarization.py index bffe389..d25415d 100644 --- a/optika/vectors/_tests/test_vectors_polarization.py +++ b/optika/vectors/_tests/test_vectors_polarization.py @@ -33,6 +33,9 @@ def test_s(self, array: optika.vectors.AbstractPolarizationVectorArray): def test_p(self, array: optika.vectors.AbstractPolarizationVectorArray): assert isinstance(na.as_named_array(array.p), na.AbstractScalar) + def test_average(self, array: optika.vectors.AbstractPolarizationVectorArray): + assert isinstance(na.as_named_array(array.average), na.AbstractScalar) + @pytest.mark.xfail(raises=NotImplementedError) def test_matrix(self, array: optika.vectors.AbstractPolarizationVectorArray): return super().test_matrix(array=array) diff --git a/optika/vectors/_vectors_polarization.py b/optika/vectors/_vectors_polarization.py index 9974aba..8160688 100644 --- a/optika/vectors/_vectors_polarization.py +++ b/optika/vectors/_vectors_polarization.py @@ -32,6 +32,10 @@ def p(self) -> float | na.AbstractScalar: The component of the electric field parallel to the plane of incidence. """ + @property + def average(self) -> float | na.AbstractScalar: + return (self.s + self.p) / 2 + @property def type_abstract(self) -> type[AbstractPolarizationVectorArray]: return AbstractPolarizationVectorArray From a9800bc2f6708bc3c12f559796705e7a5ac2e5df Mon Sep 17 00:00:00 2001 From: Roy Smart Date: Mon, 11 Mar 2024 01:24:01 -0600 Subject: [PATCH 2/4] Modified `optika.materials.matrices.refraction()` to use a `polarized_s` boolean argument instead of a `polarization` string argument for simplicity. --- optika/materials/_layers.py | 22 ++-- optika/materials/_multilayers.py | 121 +++++++++----------- optika/materials/_tests/test_layers.py | 14 +-- optika/materials/_tests/test_matrices.py | 12 +- optika/materials/_tests/test_multilayers.py | 8 +- optika/materials/matrices.py | 49 +++----- optika/sensors/_materials/_materials.py | 2 +- 7 files changed, 100 insertions(+), 128 deletions(-) diff --git a/optika/materials/_layers.py b/optika/materials/_layers.py index 15ab3d9..7af9855 100644 --- a/optika/materials/_layers.py +++ b/optika/materials/_layers.py @@ -39,7 +39,7 @@ def transfer( self, wavelength: u.Quantity | na.AbstractScalar, direction: na.AbstractCartesian3dVectorArray, - polarization: Literal["s", "p"], + polarized_s: bool | na.AbstractScalar, n: float | na.AbstractScalar, normal: na.AbstractCartesian3dVectorArray, ) -> tuple[na.AbstractScalar, na.Cartesian3dVectorArray, na.Cartesian2dMatrixArray]: @@ -56,9 +56,9 @@ def transfer( direction The propagation direction of the incident light in the medium before this layer. - polarization - Flag controlling whether the incident light is :math:`s` or :math:`p` - polarized. + polarized_s + If :obj:`True`, the incident light is :math:`s`-polarized. + If :obj:`False`, the incident light is :math:`p`-polarized. n The complex index of refraction of the medium before this layer. normal @@ -175,7 +175,7 @@ def transfer( self, wavelength: u.Quantity | na.AbstractScalar, direction: na.AbstractCartesian3dVectorArray, - polarization: Literal["s", "p"], + polarized_s: bool | na.AbstractScalar, n: float | na.AbstractScalar, normal: na.AbstractCartesian3dVectorArray, ) -> tuple[na.AbstractScalar, na.Cartesian3dVectorArray, na.Cartesian2dMatrixArray]: @@ -194,7 +194,7 @@ def transfer( wavelength=wavelength, direction_left=direction, direction_right=direction_internal, - polarization=polarization, + polarized_s=polarized_s, n_left=n, n_right=n_internal, normal=normal, @@ -377,7 +377,7 @@ def transfer( self, wavelength: u.Quantity | na.AbstractScalar, direction: na.AbstractCartesian3dVectorArray, - polarization: Literal["s", "p"], + polarized_s: bool | na.AbstractScalar, n: float | na.AbstractScalar, normal: na.AbstractCartesian3dVectorArray, ) -> tuple[na.AbstractScalar, na.Cartesian3dVectorArray, na.Cartesian2dMatrixArray]: @@ -388,7 +388,7 @@ def transfer( n, direction, matrix_transfer = layer.transfer( wavelength=wavelength, direction=direction, - polarization=polarization, + polarized_s=polarized_s, n=n, normal=normal, ) @@ -486,7 +486,7 @@ def transfer( self, wavelength: u.Quantity | na.AbstractScalar, direction: na.AbstractCartesian3dVectorArray, - polarization: Literal["s", "p"], + polarized_s: bool | na.AbstractScalar, n: float | na.AbstractScalar, normal: na.AbstractCartesian3dVectorArray, ) -> tuple[na.AbstractScalar, na.Cartesian3dVectorArray, na.Cartesian2dMatrixArray]: @@ -496,7 +496,7 @@ def transfer( n, direction, start = period.transfer( wavelength=wavelength, direction=direction, - polarization=polarization, + polarized_s=polarized_s, n=n, normal=normal, ) @@ -504,7 +504,7 @@ def transfer( n, direction, periodic = period.transfer( wavelength=wavelength, direction=direction, - polarization=polarization, + polarized_s=polarized_s, n=n, normal=normal, ) diff --git a/optika/materials/_multilayers.py b/optika/materials/_multilayers.py index 867c457..875cacb 100644 --- a/optika/materials/_multilayers.py +++ b/optika/materials/_multilayers.py @@ -31,12 +31,14 @@ def multilayer_efficiency( wavelength: u.Quantity | na.AbstractScalar, direction: None | na.AbstractCartesian3dVectorArray = None, - polarization: None | Literal["s", "p"] = None, n: float | na.AbstractScalar = 1, layers: Sequence[AbstractLayer] | optika.materials.AbstractLayer = None, substrate: None | Layer = None, normal: None | na.AbstractCartesian3dVectorArray = None, -) -> tuple[na.AbstractScalar, na.AbstractScalar]: +) -> tuple[ + optika.vectors.PolarizationVectorArray, + optika.vectors.PolarizationVectorArray, +]: r""" Calculate the reflectivity and transmissivity of a multilayer film or coating using the method in :cite:t:`Windt1998`. @@ -49,9 +51,6 @@ def multilayer_efficiency( The propagation direction of incident light in the ambient medium. If :obj:`None`, the propagation direction is assumed to be :math:`+\hat{z}` - polarization - The polarization state of the incident light. - If :obj:`None`, the light is assumed to be unpolarized. n The complex index of refraction of the ambient medium. layers @@ -100,7 +99,7 @@ def multilayer_efficiency( fig, ax = plt.subplots() na.plt.plot( wavelength, - transmissivity, + transmissivity.average, ax=ax, axis="wavelength", label="Zr", @@ -155,7 +154,7 @@ def multilayer_efficiency( fig, ax = plt.subplots() na.plt.plot( wavelength, - reflectivity, + reflectivity.average, ax=ax, axis="wavelength", label=rf"Si/Mo $\times$ {layers.num_periods}", @@ -211,7 +210,7 @@ def multilayer_efficiency( fig, ax = plt.subplots() na.plt.plot( wavelength, - reflectivity, + reflectivity.average, ax=ax, axis="wavelength", label=r"$\Gamma=" + thickness_ratio.astype(str).astype(object) + "$", @@ -381,65 +380,59 @@ def multilayer_efficiency( cos_theta_ambient = direction_ambient @ normal cos_theta_substrate = direction_substrate @ normal - if polarization is None: - polarization = ["s", "p"] - elif polarization == "s" or polarization == "p": - polarization = [polarization] - else: # pragma: nocover - raise ValueError( - f"Unrecognized polarization state '{polarization}'." - f"Only 's', 'p', or `None` is supported." - ) + polarized_s = na.ScalarArray( + ndarray=np.array([True, False]), + axes="_polarization", + ) if layers is None: layers = [] if not isinstance(layers, optika.materials.AbstractLayer): layers = optika.materials.LayerSequence(layers) - reflectivity = [] - transmissivity = [] - - for pol in polarization: - n, direction, m = layers.transfer( - wavelength=wavelength, - direction=direction_ambient, - polarization=pol, - n=n_ambient, - normal=normal, - ) - m_substrate = matrices.refraction( - wavelength=wavelength, - direction_left=direction, - direction_right=direction_substrate, - polarization=pol, - n_left=n, - n_right=n_substrate, - normal=normal, - interface=interface_substrate, - ) - m = m @ m_substrate - - r = m.y.x / m.x.x - t = 1 / m.x.x - - if pol == "s": - q_ambient = cos_theta_ambient * n_ambient - q_substrate = cos_theta_substrate * n_substrate - else: - q_ambient = cos_theta_ambient / n_ambient - q_substrate = cos_theta_substrate / n_substrate - - R = np.square(np.abs(r)) - T = np.square(np.abs(t)) * np.real(q_substrate / q_ambient) - - reflectivity.append(R) - transmissivity.append(T) - - reflectivity = na.stack(reflectivity, axis="polarization") - transmissivity = na.stack(transmissivity, axis="polarization") - - reflectivity = reflectivity.mean("polarization") - transmissivity = transmissivity.mean("polarization") + n, direction, m = layers.transfer( + wavelength=wavelength, + direction=direction_ambient, + polarized_s=polarized_s, + n=n_ambient, + normal=normal, + ) + m_substrate = matrices.refraction( + wavelength=wavelength, + direction_left=direction, + direction_right=direction_substrate, + polarized_s=polarized_s, + n_left=n, + n_right=n_substrate, + normal=normal, + interface=interface_substrate, + ) + m = m @ m_substrate + + r = m.y.x / m.x.x + t = 1 / m.x.x + + impedance_ambient = np.where(polarized_s, n_ambient, 1 / n_ambient) + impedance_substrate = np.where(polarized_s, n_substrate, 1 / n_substrate) + + q_ambient = cos_theta_ambient * impedance_ambient + q_substrate = cos_theta_substrate * impedance_substrate + + reflectivity = np.square(np.abs(r)) + transmissivity = np.square(np.abs(t)) * np.real(q_substrate / q_ambient) + + index_s = dict(_polarization=0) + index_p = dict(_polarization=1) + + reflectivity = optika.vectors.PolarizationVectorArray( + s=reflectivity[index_s], + p=reflectivity[index_p], + ) + + transmissivity = optika.vectors.PolarizationVectorArray( + s=transmissivity[index_s], + p=transmissivity[index_p], + ) return reflectivity, transmissivity @@ -531,13 +524,12 @@ def efficiency( reflectivity, transmissivity = multilayer_efficiency( wavelength=wavelength, direction=rays.direction, - polarization=None, n=n, layers=self.layers, substrate=None, normal=normal, ) - return transmissivity + return transmissivity.average def plot_layers( self, @@ -589,12 +581,11 @@ def efficiency( reflectivity, transmissivity = multilayer_efficiency( wavelength=wavelength, direction=rays.direction, - polarization=None, n=n, layers=self.layers, substrate=self.substrate, ) - return reflectivity + return reflectivity.average def plot_layers( self, diff --git a/optika/materials/_tests/test_layers.py b/optika/materials/_tests/test_layers.py index 439805b..6333096 100644 --- a/optika/materials/_tests/test_layers.py +++ b/optika/materials/_tests/test_layers.py @@ -26,7 +26,7 @@ def test_thickness( ], ) @pytest.mark.parametrize("direction", [na.Cartesian3dVectorArray(0, 0, 1)]) - @pytest.mark.parametrize("polarization", ["s", "p"]) + @pytest.mark.parametrize("polarized_s", [False, True]) @pytest.mark.parametrize("n", [1]) @pytest.mark.parametrize("normal", [na.Cartesian3dVectorArray(0, 0, -1)]) class TestMatrixTransfer: @@ -35,14 +35,14 @@ def test_transfer( a: optika.materials.AbstractLayer, wavelength: u.Quantity | na.AbstractScalar, direction: na.AbstractCartesian3dVectorArray, - polarization: Literal["s", "p"], + polarized_s: bool | na.AbstractScalar, n: float | na.AbstractScalar, normal: na.AbstractCartesian3dVectorArray, ): n, direction, result = a.transfer( wavelength=wavelength, direction=direction, - polarization=polarization, + polarized_s=polarized_s, n=n, normal=normal, ) @@ -167,7 +167,7 @@ def test_transfer( a: optika.materials.PeriodicLayerSequence, wavelength: u.Quantity | na.AbstractScalar, direction: na.AbstractCartesian3dVectorArray, - polarization: Literal["s", "p"], + polarized_s: bool | na.AbstractScalar, n: float | na.AbstractScalar, normal: na.AbstractCartesian3dVectorArray, ): @@ -175,7 +175,7 @@ def test_transfer( a=a, wavelength=wavelength, direction=direction, - polarization=polarization, + polarized_s=polarized_s, n=n, normal=normal, ) @@ -185,7 +185,7 @@ def test_transfer( n_test, direction_test, result_test = a.transfer( wavelength=wavelength, direction=direction, - polarization=polarization, + polarized_s=polarized_s, n=n, normal=normal, ) @@ -193,7 +193,7 @@ def test_transfer( n_expected, direction_expected, result_expected = b.transfer( wavelength=wavelength, direction=direction, - polarization=polarization, + polarized_s=polarized_s, n=n, normal=normal, ) diff --git a/optika/materials/_tests/test_matrices.py b/optika/materials/_tests/test_matrices.py index b986c6f..df2529f 100644 --- a/optika/materials/_tests/test_matrices.py +++ b/optika/materials/_tests/test_matrices.py @@ -11,7 +11,7 @@ @pytest.mark.parametrize("wavelength", [_wavelength]) @pytest.mark.parametrize("direction_left", [na.Cartesian3dVectorArray(0, 0, 1)]) @pytest.mark.parametrize("direction_right", [na.Cartesian3dVectorArray(0, 0, 1)]) -@pytest.mark.parametrize("polarization", ["s", "p"]) +@pytest.mark.parametrize("polarized_s", [False, True]) @pytest.mark.parametrize("n_left", [1]) @pytest.mark.parametrize("n_right", [optika.chemicals.Chemical("Si").n(_wavelength)]) @pytest.mark.parametrize("normal", [na.Cartesian3dVectorArray(0, 0, -1)]) @@ -23,7 +23,7 @@ def test_refraction( wavelength: u.Quantity | na.AbstractScalar, direction_left: na.AbstractCartesian3dVectorArray, direction_right: na.AbstractCartesian3dVectorArray, - polarization: Literal["s", "p"], + polarized_s: bool | na.AbstractScalar, n_left: float | na.AbstractScalar, n_right: float | na.AbstractScalar, normal: na.AbstractCartesian3dVectorArray, @@ -33,7 +33,7 @@ def test_refraction( wavelength=wavelength, direction_left=direction_left, direction_right=direction_right, - polarization=polarization, + polarized_s=polarized_s, n_left=n_left, n_right=n_right, normal=normal, @@ -68,14 +68,14 @@ def test_propagation( @pytest.mark.parametrize("wavelength", [_wavelength]) @pytest.mark.parametrize("direction", [na.Cartesian3dVectorArray(0, 0, 1)]) -@pytest.mark.parametrize("polarization", ["s", "p"]) +@pytest.mark.parametrize("polarized_s", [False, True]) @pytest.mark.parametrize("thickness", [10 * u.nm]) @pytest.mark.parametrize("n", [optika.chemicals.Chemical("Si").n(_wavelength)]) @pytest.mark.parametrize("normal", [na.Cartesian3dVectorArray(0, 0, -1)]) def test_transfer( wavelength: u.Quantity | na.AbstractScalar, direction: na.AbstractCartesian3dVectorArray, - polarization: Literal["s", "p"], + polarized_s: bool | na.AbstractScalar, thickness: u.Quantity | na.AbstractScalar, n: float | na.AbstractScalar, normal: na.AbstractCartesian3dVectorArray, @@ -83,7 +83,7 @@ def test_transfer( result = optika.materials.matrices.transfer( wavelength=wavelength, direction=direction, - polarization=polarization, + polarized_s=polarized_s, thickness=thickness, n=n, normal=normal, diff --git a/optika/materials/_tests/test_multilayers.py b/optika/materials/_tests/test_multilayers.py index 80a1ace..b22bca7 100644 --- a/optika/materials/_tests/test_multilayers.py +++ b/optika/materials/_tests/test_multilayers.py @@ -25,7 +25,6 @@ ), ], ) -@pytest.mark.parametrize("polarization", [None, "s"]) @pytest.mark.parametrize("n", [1]) @pytest.mark.parametrize( argnames="layers", @@ -52,7 +51,6 @@ def test_multilayer_efficiency( wavelength: u.Quantity | na.AbstractScalar, direction: None | na.AbstractCartesian3dVectorArray, - polarization: None | Literal["s", "p"], n: float | na.AbstractScalar, layers: Sequence[AbstractLayer] | AbstractLayer, substrate: None | Layer, @@ -61,7 +59,6 @@ def test_multilayer_efficiency( reflected, transmitted = optika.materials.multilayer_efficiency( wavelength=wavelength, direction=direction, - polarization=polarization, n=n, layers=layers, substrate=substrate, @@ -176,7 +173,6 @@ def test_multilayer_efficiency_vs_file( reflectivity, transmissivity = optika.materials.multilayer_efficiency( wavelength=wavelength, direction=direction, - polarization=None, n=n, layers=layers, substrate=substrate, @@ -184,9 +180,9 @@ def test_multilayer_efficiency_vs_file( ) if is_mirror: - efficiency = reflectivity + efficiency = reflectivity.average else: - efficiency = transmissivity + efficiency = transmissivity.average assert np.allclose(efficiency, efficiency_file, rtol=1e-4) diff --git a/optika/materials/matrices.py b/optika/materials/matrices.py index fb5ae19..46420ac 100644 --- a/optika/materials/matrices.py +++ b/optika/materials/matrices.py @@ -4,7 +4,6 @@ """ from __future__ import annotations -from typing import Literal import numpy as np import astropy.units as u import named_arrays as na @@ -23,7 +22,7 @@ def refraction( wavelength: u.Quantity | na.AbstractScalar, direction_left: na.AbstractCartesian3dVectorArray, direction_right: na.AbstractCartesian3dVectorArray, - polarization: Literal["s", "p"], + polarized_s: bool | na.AbstractScalar, n_left: float | na.AbstractScalar, n_right: float | na.AbstractScalar, normal: na.AbstractCartesian3dVectorArray, @@ -41,9 +40,9 @@ def refraction( The direction of the incident light on the left side of the interface. direction_right The direction of the incident light on the right side of the interface. - polarization - Flag controlling whether the incident light is :math:`s`- or - :math:`p`-polarized. + polarized_s + If :obj:`True`, the incident light is :math:`s`-polarized. + If :obj:`False`, the incident light is :math:`p`-polarized. n_left The complex index of refraction on the left side of the interface. n_right @@ -76,7 +75,7 @@ def refraction( wavelength=wavelength, direction_left=na.Cartesian3dVectorArray(0, 0, 1), direction_right=na.Cartesian3dVectorArray(0, 0, 1), - polarization="s", + polarized_s=True, n_left=1, n_right=sio2.n(wavelength), normal=na.Cartesian3dVectorArray(0, 0, -1), @@ -137,17 +136,11 @@ def refraction( cos_theta_i = -direction_i @ normal cos_theta_j = -direction_j @ normal - if polarization == "s": - q_i = cos_theta_i * n_i - q_j = cos_theta_j * n_j - elif polarization == "p": - q_i = cos_theta_i / n_i - q_j = cos_theta_j / n_j - else: # pragma: nocover - raise ValueError( - f"Invalid polarization state '{polarization}', only 's' and 'p'" - f"are valid polarization states." - ) + impedance_i = np.where(polarized_s, n_i, 1 / n_i) + impedance_j = np.where(polarized_s, n_j, 1 / n_j) + + q_i = cos_theta_i * impedance_i + q_j = cos_theta_j * impedance_j a_ij = q_i + q_j @@ -155,14 +148,6 @@ def refraction( t_ij = 2 * q_i / a_ij if interface is not None: - t_ij = t_ij * interface.transmissivity( - wavelength=wavelength, - direction_before=direction_i, - direction_after=direction_j, - n_before=n_i, - n_after=n_j, - normal=normal, - ) r_ij = r_ij * interface.reflectivity( wavelength=wavelength, direction=direction_i, @@ -272,7 +257,7 @@ def propagation( def transfer( wavelength: u.Quantity | na.AbstractScalar, direction: na.AbstractCartesian3dVectorArray, - polarization: Literal["s", "p"], + polarized_s: bool | na.ScalarArray, thickness: u.Quantity | na.AbstractScalar, n: float | na.AbstractScalar, normal: na.AbstractCartesian3dVectorArray, @@ -287,9 +272,9 @@ def transfer( The wavelength of the incident light in vacuum. direction The propagation direction of the incident light in vacuum. - polarization - Flag controlling whether the incident light is :math:`s`- or - :math:`p`-polarized. + polarized_s + If :obj:`True`, the incident light is :math:`s`-polarized. + If :obj:`False`, the incident light is :math:`p`-polarized. thickness The thickness of the homogenous slab. n @@ -319,7 +304,7 @@ def transfer( optika.materials.matrices.transfer( wavelength=wavelength, direction=na.Cartesian3dVectorArray(0, 0, 1), - polarization="s", + polarized_s=True, thickness=10 * u.nm, n=sio2.n(wavelength), normal=na.Cartesian3dVectorArray(0, 0, -1), @@ -353,7 +338,7 @@ def transfer( wavelength=wavelength, direction_left=direction, direction_right=direction_internal, - polarization=polarization, + polarized_s=polarized_s, n_left=1, n_right=n, normal=normal, @@ -371,7 +356,7 @@ def transfer( wavelength=wavelength, direction_left=direction_internal, direction_right=direction, - polarization=polarization, + polarized_s=polarized_s, n_left=n, n_right=1, normal=normal, diff --git a/optika/sensors/_materials/_materials.py b/optika/sensors/_materials/_materials.py index b9e15cd..23d466a 100644 --- a/optika/sensors/_materials/_materials.py +++ b/optika/sensors/_materials/_materials.py @@ -376,7 +376,7 @@ def quantum_efficiency_effective( term_2 = ((1 - cce_backsurface) / z0) * (1 - exp_z0) term_3 = -np.exp(-absorption_substrate * thickness_substrate / cos_theta) - result = transmissivity * (term_1 + term_2 + term_3) + result = transmissivity.average * (term_1 + term_2 + term_3) return result From 2b0bb030f09b1361d99717b345f5002258ec8ba7 Mon Sep 17 00:00:00 2001 From: Roy Smart Date: Wed, 13 Mar 2024 14:12:09 -0600 Subject: [PATCH 3/4] Modified the `optika.materials.matrices` module to use the dot product of the direction and normal vectors, instead of both individual vectors. --- optika/materials/_layers.py | 47 ++++------- optika/materials/_multilayers.py | 58 ++++---------- optika/materials/_tests/test_layers.py | 15 +--- optika/materials/_tests/test_matrices.py | 18 ++--- optika/materials/_tests/test_multilayers.py | 31 +++----- optika/materials/_tests/test_profiles.py | 31 ++------ optika/materials/matrices.py | 46 +++++------ optika/materials/profiles.py | 86 +++++++-------------- optika/sensors/_materials/_materials.py | 3 +- 9 files changed, 104 insertions(+), 231 deletions(-) diff --git a/optika/materials/_layers.py b/optika/materials/_layers.py index 7af9855..679813f 100644 --- a/optika/materials/_layers.py +++ b/optika/materials/_layers.py @@ -38,11 +38,10 @@ def thickness(self) -> u.Quantity | na.AbstractScalar: def transfer( self, wavelength: u.Quantity | na.AbstractScalar, - direction: na.AbstractCartesian3dVectorArray, + direction: float | na.AbstractScalar, polarized_s: bool | na.AbstractScalar, n: float | na.AbstractScalar, - normal: na.AbstractCartesian3dVectorArray, - ) -> tuple[na.AbstractScalar, na.Cartesian3dVectorArray, na.Cartesian2dMatrixArray]: + ) -> tuple[na.AbstractScalar, na.AbstractScalar, na.Cartesian2dMatrixArray]: """ Compute the index of refraction, internal propagation direction, and the transfer matrix for this layer, @@ -54,15 +53,13 @@ def transfer( wavelength The wavelength of the incident light in the medium before this layer. direction - The propagation direction of the incident light in the medium before - this layer. + The component of the incident light's propagation direction in the + medium before this layer antiparallel to the surface normal. polarized_s If :obj:`True`, the incident light is :math:`s`-polarized. If :obj:`False`, the incident light is :math:`p`-polarized. n The complex index of refraction of the medium before this layer. - normal - The vector perpendicular to the surface of this layer. """ def plot( @@ -165,30 +162,27 @@ def _chemical(self) -> optika.chemicals.AbstractChemical: def n( self, wavelength: u.Quantity | na.AbstractScalar, - ) -> na.AbstractScalar: + ) -> float | na.AbstractScalar: """ The complex index of refraction of this layer. """ - return self._chemical.n(wavelength) + if self.chemical is None: + return 1 + else: + return self._chemical.n(wavelength) def transfer( self, wavelength: u.Quantity | na.AbstractScalar, - direction: na.AbstractCartesian3dVectorArray, + direction: float | na.AbstractScalar, polarized_s: bool | na.AbstractScalar, n: float | na.AbstractScalar, - normal: na.AbstractCartesian3dVectorArray, - ) -> tuple[na.AbstractScalar, na.Cartesian3dVectorArray, na.Cartesian2dMatrixArray]: + ) -> tuple[na.AbstractScalar, na.AbstractScalar, na.Cartesian2dMatrixArray]: n_internal = self.n(wavelength) - direction_internal = snells_law( - wavelength=wavelength / np.real(n), - direction=direction, - index_refraction=np.real(n), - index_refraction_new=np.real(n_internal), - normal=normal, - ) + angle_internal = np.arcsin(n * np.sin(np.arccos(direction)) / n_internal) + direction_internal = np.cos(angle_internal) refraction = matrices.refraction( wavelength=wavelength, @@ -197,7 +191,6 @@ def transfer( polarized_s=polarized_s, n_left=n, n_right=n_internal, - normal=normal, interface=self.interface, ) @@ -206,7 +199,6 @@ def transfer( direction=direction_internal, thickness=self.thickness, n=n_internal, - normal=normal, ) transfer = refraction @ propagation @@ -376,11 +368,10 @@ def thickness(self) -> u.Quantity | na.AbstractScalar: def transfer( self, wavelength: u.Quantity | na.AbstractScalar, - direction: na.AbstractCartesian3dVectorArray, + direction: float | na.AbstractScalar, polarized_s: bool | na.AbstractScalar, n: float | na.AbstractScalar, - normal: na.AbstractCartesian3dVectorArray, - ) -> tuple[na.AbstractScalar, na.Cartesian3dVectorArray, na.Cartesian2dMatrixArray]: + ) -> tuple[na.AbstractScalar, na.AbstractScalar, na.Cartesian2dMatrixArray]: result = na.Cartesian2dIdentityMatrixArray() @@ -390,7 +381,6 @@ def transfer( direction=direction, polarized_s=polarized_s, n=n, - normal=normal, ) result = result @ matrix_transfer @@ -485,11 +475,10 @@ def thickness(self) -> u.Quantity | na.AbstractScalar: def transfer( self, wavelength: u.Quantity | na.AbstractScalar, - direction: na.AbstractCartesian3dVectorArray, + direction: float | na.AbstractScalar, polarized_s: bool | na.AbstractScalar, n: float | na.AbstractScalar, - normal: na.AbstractCartesian3dVectorArray, - ) -> tuple[na.AbstractScalar, na.Cartesian3dVectorArray, na.Cartesian2dMatrixArray]: + ) -> tuple[na.AbstractScalar, na.AbstractScalar, na.Cartesian2dMatrixArray]: period = LayerSequence(self.layers) @@ -498,7 +487,6 @@ def transfer( direction=direction, polarized_s=polarized_s, n=n, - normal=normal, ) n, direction, periodic = period.transfer( @@ -506,7 +494,6 @@ def transfer( direction=direction, polarized_s=polarized_s, n=n, - normal=normal, ) periodic = periodic.power(self.num_periods - 1) diff --git a/optika/materials/_multilayers.py b/optika/materials/_multilayers.py index 875cacb..23f9f0a 100644 --- a/optika/materials/_multilayers.py +++ b/optika/materials/_multilayers.py @@ -30,11 +30,10 @@ def multilayer_efficiency( wavelength: u.Quantity | na.AbstractScalar, - direction: None | na.AbstractCartesian3dVectorArray = None, + direction: float | na.AbstractScalar = 1, n: float | na.AbstractScalar = 1, layers: Sequence[AbstractLayer] | optika.materials.AbstractLayer = None, substrate: None | Layer = None, - normal: None | na.AbstractCartesian3dVectorArray = None, ) -> tuple[ optika.vectors.PolarizationVectorArray, optika.vectors.PolarizationVectorArray, @@ -48,9 +47,9 @@ def multilayer_efficiency( wavelength The wavelength of the incident light in vacuum. direction - The propagation direction of incident light in the ambient medium. - If :obj:`None`, the propagation direction is assumed to be - :math:`+\hat{z}` + The component of the incident light's propagation direction in the + ambient medium antiparallel to the surface normal. + Default is to assume normal incidence. n The complex index of refraction of the ambient medium. layers @@ -61,9 +60,6 @@ def multilayer_efficiency( A layer representing the substrate supporting the multilayer stack. The thickness of this layer is ignored. If :obj:`None`, then the substrate is assumed to be a vacuum. - normal - A vector perpendicular to the interface between successive layers. - If :obj:`None`, the normal vector is assumed to be :math:`-\hat{z}` Examples -------- @@ -352,33 +348,13 @@ def multilayer_efficiency( The :class:`tuple` :math:`(R, T)` is the quantity returned by this function. """ - if direction is None: - direction_ambient = na.Cartesian3dVectorArray(0, 0, 1) - else: - direction_ambient = direction - - if normal is None: - normal = na.Cartesian3dVectorArray(0, 0, -1) - + direction_ambient = direction n_ambient = n if substrate is not None: - n_substrate = substrate.n(wavelength) - direction_substrate = snells_law( - wavelength=wavelength / np.real(n_ambient), - direction=direction_ambient, - index_refraction=np.real(n_ambient), - index_refraction_new=np.real(n_substrate), - normal=normal, - ) - interface_substrate = substrate.interface + substrate = dataclasses.replace(substrate, thickness=0 * u.nm) else: - n_substrate = 1 - direction_substrate = direction_ambient - interface_substrate = None - - cos_theta_ambient = direction_ambient @ normal - cos_theta_substrate = direction_substrate @ normal + substrate = optika.materials.Layer(thickness=0 * u.nm) polarized_s = na.ScalarArray( ndarray=np.array([True, False]), @@ -395,17 +371,12 @@ def multilayer_efficiency( direction=direction_ambient, polarized_s=polarized_s, n=n_ambient, - normal=normal, ) - m_substrate = matrices.refraction( + n_substrate, direction_substrate, m_substrate = substrate.transfer( wavelength=wavelength, - direction_left=direction, - direction_right=direction_substrate, + direction=direction, polarized_s=polarized_s, - n_left=n, - n_right=n_substrate, - normal=normal, - interface=interface_substrate, + n=n, ) m = m @ m_substrate @@ -415,8 +386,8 @@ def multilayer_efficiency( impedance_ambient = np.where(polarized_s, n_ambient, 1 / n_ambient) impedance_substrate = np.where(polarized_s, n_substrate, 1 / n_substrate) - q_ambient = cos_theta_ambient * impedance_ambient - q_substrate = cos_theta_substrate * impedance_substrate + q_ambient = direction_ambient * impedance_ambient + q_substrate = direction_substrate * impedance_substrate reflectivity = np.square(np.abs(r)) transmissivity = np.square(np.abs(t)) * np.real(q_substrate / q_ambient) @@ -523,11 +494,10 @@ def efficiency( reflectivity, transmissivity = multilayer_efficiency( wavelength=wavelength, - direction=rays.direction, + direction=-rays.direction @ normal, n=n, layers=self.layers, substrate=None, - normal=normal, ) return transmissivity.average @@ -580,7 +550,7 @@ def efficiency( reflectivity, transmissivity = multilayer_efficiency( wavelength=wavelength, - direction=rays.direction, + direction=-rays.direction @ normal, n=n, layers=self.layers, substrate=self.substrate, diff --git a/optika/materials/_tests/test_layers.py b/optika/materials/_tests/test_layers.py index 6333096..9f193a3 100644 --- a/optika/materials/_tests/test_layers.py +++ b/optika/materials/_tests/test_layers.py @@ -25,29 +25,26 @@ def test_thickness( na.geomspace(100, 1000, axis="wavelength", num=4) * u.AA, ], ) - @pytest.mark.parametrize("direction", [na.Cartesian3dVectorArray(0, 0, 1)]) + @pytest.mark.parametrize("direction", [1]) @pytest.mark.parametrize("polarized_s", [False, True]) @pytest.mark.parametrize("n", [1]) - @pytest.mark.parametrize("normal", [na.Cartesian3dVectorArray(0, 0, -1)]) class TestMatrixTransfer: def test_transfer( self, a: optika.materials.AbstractLayer, wavelength: u.Quantity | na.AbstractScalar, - direction: na.AbstractCartesian3dVectorArray, + direction: float | na.AbstractScalar, polarized_s: bool | na.AbstractScalar, n: float | na.AbstractScalar, - normal: na.AbstractCartesian3dVectorArray, ): n, direction, result = a.transfer( wavelength=wavelength, direction=direction, polarized_s=polarized_s, n=n, - normal=normal, ) assert np.all(n > 0) - assert isinstance(direction, na.AbstractCartesian3dVectorArray) + assert isinstance(direction, na.AbstractScalar) assert isinstance(result, na.AbstractCartesian2dMatrixArray) assert np.all(result.determinant != 0) assert np.all(np.isfinite(result)) @@ -166,10 +163,9 @@ def test_transfer( self, a: optika.materials.PeriodicLayerSequence, wavelength: u.Quantity | na.AbstractScalar, - direction: na.AbstractCartesian3dVectorArray, + direction: float | na.AbstractScalar, polarized_s: bool | na.AbstractScalar, n: float | na.AbstractScalar, - normal: na.AbstractCartesian3dVectorArray, ): super().test_transfer( a=a, @@ -177,7 +173,6 @@ def test_transfer( direction=direction, polarized_s=polarized_s, n=n, - normal=normal, ) b = optika.materials.LayerSequence(list(a.layers) * a.num_periods) @@ -187,7 +182,6 @@ def test_transfer( direction=direction, polarized_s=polarized_s, n=n, - normal=normal, ) n_expected, direction_expected, result_expected = b.transfer( @@ -195,7 +189,6 @@ def test_transfer( direction=direction, polarized_s=polarized_s, n=n, - normal=normal, ) assert np.allclose(n_test, n_expected) diff --git a/optika/materials/_tests/test_matrices.py b/optika/materials/_tests/test_matrices.py index df2529f..8919115 100644 --- a/optika/materials/_tests/test_matrices.py +++ b/optika/materials/_tests/test_matrices.py @@ -9,24 +9,22 @@ @pytest.mark.parametrize("wavelength", [_wavelength]) -@pytest.mark.parametrize("direction_left", [na.Cartesian3dVectorArray(0, 0, 1)]) -@pytest.mark.parametrize("direction_right", [na.Cartesian3dVectorArray(0, 0, 1)]) +@pytest.mark.parametrize("direction_left", [1]) +@pytest.mark.parametrize("direction_right", [1]) @pytest.mark.parametrize("polarized_s", [False, True]) @pytest.mark.parametrize("n_left", [1]) @pytest.mark.parametrize("n_right", [optika.chemicals.Chemical("Si").n(_wavelength)]) -@pytest.mark.parametrize("normal", [na.Cartesian3dVectorArray(0, 0, -1)]) @pytest.mark.parametrize( argnames="interface", argvalues=[optika.materials.profiles.ErfInterfaceProfile(5 * u.nm)], ) def test_refraction( wavelength: u.Quantity | na.AbstractScalar, - direction_left: na.AbstractCartesian3dVectorArray, - direction_right: na.AbstractCartesian3dVectorArray, + direction_left: float | na.AbstractScalar, + direction_right: float | na.AbstractScalar, polarized_s: bool | na.AbstractScalar, n_left: float | na.AbstractScalar, n_right: float | na.AbstractScalar, - normal: na.AbstractCartesian3dVectorArray, interface: None | optika.materials.profiles.AbstractInterfaceProfile, ): result = optika.materials.matrices.refraction( @@ -36,7 +34,6 @@ def test_refraction( polarized_s=polarized_s, n_left=n_left, n_right=n_right, - normal=normal, interface=interface, ) assert isinstance(result, na.AbstractCartesian2dMatrixArray) @@ -44,23 +41,20 @@ def test_refraction( @pytest.mark.parametrize("wavelength", [_wavelength]) -@pytest.mark.parametrize("direction", [na.Cartesian3dVectorArray(0, 0, 1)]) +@pytest.mark.parametrize("direction", [1]) @pytest.mark.parametrize("thickness", [10 * u.nm]) @pytest.mark.parametrize("n", [optika.chemicals.Chemical("Si").n(_wavelength)]) -@pytest.mark.parametrize("normal", [na.Cartesian3dVectorArray(0, 0, -1)]) def test_propagation( wavelength: u.Quantity | na.AbstractScalar, - direction: na.AbstractCartesian3dVectorArray, + direction: float | na.AbstractScalar, thickness: u.Quantity | na.AbstractScalar, n: u.Quantity | na.AbstractScalar, - normal: na.AbstractVectorArray, ): result = optika.materials.matrices.propagation( wavelength=wavelength, direction=direction, thickness=thickness, n=n, - normal=normal, ) assert isinstance(result, na.AbstractCartesian2dMatrixArray) assert np.all(result.determinant != 0) diff --git a/optika/materials/_tests/test_multilayers.py b/optika/materials/_tests/test_multilayers.py index b22bca7..3377c37 100644 --- a/optika/materials/_tests/test_multilayers.py +++ b/optika/materials/_tests/test_multilayers.py @@ -17,12 +17,8 @@ @pytest.mark.parametrize( argnames="direction", argvalues=[ - na.Cartesian3dVectorArray(0, 0, 1), - na.Cartesian3dVectorArray( - x=np.sin(na.linspace(-1, 1, axis="angle", num=5)), - y=0, - z=np.cos(na.linspace(-1, 1, axis="angle", num=5)), - ), + 1, + np.cos(na.linspace(-1, 1, axis="angle", num=5)) ], ) @pytest.mark.parametrize("n", [1]) @@ -42,19 +38,12 @@ Layer("SiO2"), ], ) -@pytest.mark.parametrize( - argnames="normal", - argvalues=[ - None, - ], -) def test_multilayer_efficiency( wavelength: u.Quantity | na.AbstractScalar, - direction: None | na.AbstractCartesian3dVectorArray, + direction: float | na.AbstractScalar, n: float | na.AbstractScalar, layers: Sequence[AbstractLayer] | AbstractLayer, substrate: None | Layer, - normal: None | na.AbstractCartesian3dVectorArray, ): reflected, transmitted = optika.materials.multilayer_efficiency( wavelength=wavelength, @@ -62,7 +51,6 @@ def test_multilayer_efficiency( n=n, layers=layers, substrate=substrate, - normal=normal, ) assert np.all(reflected >= 0) @@ -86,7 +74,7 @@ def test_multilayer_efficiency( argvalues=[ ( pathlib.Path(__file__).parent / "_data/Si.txt", - None, + 1, 1, None, Layer("Si"), @@ -94,7 +82,7 @@ def test_multilayer_efficiency( ), ( pathlib.Path(__file__).parent / "_data/SiO2.txt", - None, + 1, 1, Layer("SiO2", thickness=50 * u.AA), Layer("Si"), @@ -102,7 +90,7 @@ def test_multilayer_efficiency( ), ( pathlib.Path(__file__).parent / "_data/SiO2_100A.txt", - None, + 1, 1, Layer("SiO2", thickness=100 * u.AA), Layer("Si"), @@ -110,7 +98,7 @@ def test_multilayer_efficiency( ), ( pathlib.Path(__file__).parent / "_data/SiC_Cr.txt", - None, + 1, 1, [ Layer("SiC", thickness=25 * u.nm), @@ -121,7 +109,7 @@ def test_multilayer_efficiency( ), pytest.param( pathlib.Path(__file__).parent / "_data/SiC_Cr_Rough.txt", - None, + 1, 1, [ Layer( @@ -148,7 +136,7 @@ def test_multilayer_efficiency( ) def test_multilayer_efficiency_vs_file( file: pathlib.Path, - direction: None | na.AbstractCartesian3dVectorArray, + direction: float | na.AbstractScalar, n: float | na.AbstractScalar, layers: Sequence[AbstractLayer] | optika.materials.AbstractLayer, substrate: None | Layer, @@ -176,7 +164,6 @@ def test_multilayer_efficiency_vs_file( n=n, layers=layers, substrate=substrate, - normal=None, ) if is_mirror: diff --git a/optika/materials/_tests/test_profiles.py b/optika/materials/_tests/test_profiles.py index 83e4e94..8830540 100644 --- a/optika/materials/_tests/test_profiles.py +++ b/optika/materials/_tests/test_profiles.py @@ -40,27 +40,22 @@ def test__call__( ) @pytest.mark.parametrize( argnames="direction_before", - argvalues=[na.Cartesian3dVectorArray(0, 0, 1)], + argvalues=[1], ) @pytest.mark.parametrize( argnames="direction_after", - argvalues=[na.Cartesian3dVectorArray(0, 0, 1)], + argvalues=[1], ) @pytest.mark.parametrize("n_before", [1]) @pytest.mark.parametrize("n_after", [1.5]) - @pytest.mark.parametrize( - argnames="normal", - argvalues=[na.Cartesian3dVectorArray(0, 0, -1)], - ) def test_transmissivity( self, a: optika.materials.profiles.AbstractInterfaceProfile, wavelength: u.Quantity | na.AbstractScalar, - direction_before: na.AbstractCartesian3dVectorArray, - direction_after: na.AbstractCartesian3dVectorArray, + direction_before: float | na.AbstractScalar, + direction_after: float | na.AbstractScalar, n_before: float | na.AbstractScalar, n_after: float | na.AbstractScalar, - normal: na.AbstractCartesian3dVectorArray, ): result = a.transmissivity( wavelength=wavelength, @@ -68,7 +63,6 @@ def test_transmissivity( direction_after=direction_after, n_before=n_before, n_after=n_after, - normal=normal, ) assert na.unit_normalized(result).is_equivalent(u.dimensionless_unscaled) assert np.all(result >= 0) @@ -84,33 +78,22 @@ def test_transmissivity( @pytest.mark.parametrize( argnames="direction", argvalues=[ - na.Cartesian3dVectorArray(0, 0, 1), - na.Cartesian3dYRotationMatrixArray( - na.linspace(-90, 90, axis="angle", num=5) * u.deg, - ) - @ na.Cartesian3dVectorArray(0, 0, 1), + 1, + np.cos(na.linspace(-90, 90, axis="angle", num=5) * u.deg), ], ) @pytest.mark.parametrize("n", [1.5]) - @pytest.mark.parametrize( - argnames="normal", - argvalues=[ - na.Cartesian3dVectorArray(0, 0, 1), - ], - ) def test_reflectivity( self, a: optika.materials.profiles.AbstractInterfaceProfile, wavelength: u.Quantity | na.AbstractScalar, - direction: na.AbstractCartesian3dVectorArray, + direction: float | na.AbstractScalar, n: float | na.AbstractScalar, - normal: na.AbstractCartesian3dVectorArray, ): result = a.reflectivity( wavelength=wavelength, direction=direction, n=n, - normal=normal, ) assert na.unit_normalized(result).is_equivalent(u.dimensionless_unscaled) assert np.all(result >= 0) diff --git a/optika/materials/matrices.py b/optika/materials/matrices.py index 46420ac..3e6ce0d 100644 --- a/optika/materials/matrices.py +++ b/optika/materials/matrices.py @@ -20,12 +20,11 @@ def refraction( wavelength: u.Quantity | na.AbstractScalar, - direction_left: na.AbstractCartesian3dVectorArray, - direction_right: na.AbstractCartesian3dVectorArray, + direction_left: float | na.AbstractScalar, + direction_right: float | na.AbstractScalar, polarized_s: bool | na.AbstractScalar, n_left: float | na.AbstractScalar, n_right: float | na.AbstractScalar, - normal: na.AbstractCartesian3dVectorArray, interface: None | optika.materials.profiles.AbstractInterfaceProfile = None, ) -> na.Cartesian2dMatrixArray: r""" @@ -37,9 +36,11 @@ def refraction( wavelength The wavelength of the incident light in vacuum direction_left - The direction of the incident light on the left side of the interface. + The component of the incident light's propagation direction before + the interface antiparallel to the surface normal. direction_right - The direction of the incident light on the right side of the interface. + The component of the incident light's propagation direction after + the interface antiparallel to the surface normal. polarized_s If :obj:`True`, the incident light is :math:`s`-polarized. If :obj:`False`, the incident light is :math:`p`-polarized. @@ -47,8 +48,6 @@ def refraction( The complex index of refraction on the left side of the interface. n_right The complex index of refraction on the right side of the interface. - normal - The vector perpendicular to the surface of this layer. interface The interface profile between the left side and the right side. @@ -73,12 +72,11 @@ def refraction( # Compute the refractive matrix optika.materials.matrices.refraction( wavelength=wavelength, - direction_left=na.Cartesian3dVectorArray(0, 0, 1), - direction_right=na.Cartesian3dVectorArray(0, 0, 1), + direction_left=1, + direction_right=1, polarized_s=True, n_left=1, n_right=sio2.n(wavelength), - normal=na.Cartesian3dVectorArray(0, 0, -1), ) Notes @@ -133,14 +131,11 @@ def refraction( n_i = n_left n_j = n_right - cos_theta_i = -direction_i @ normal - cos_theta_j = -direction_j @ normal - impedance_i = np.where(polarized_s, n_i, 1 / n_i) impedance_j = np.where(polarized_s, n_j, 1 / n_j) - q_i = cos_theta_i * impedance_i - q_j = cos_theta_j * impedance_j + q_i = direction_i * impedance_i + q_j = direction_j * impedance_j a_ij = q_i + q_j @@ -152,7 +147,6 @@ def refraction( wavelength=wavelength, direction=direction_i, n=n_i, - normal=normal, ) result = na.Cartesian2dMatrixArray( @@ -167,10 +161,9 @@ def refraction( def propagation( wavelength: u.Quantity | na.AbstractScalar, - direction: na.AbstractCartesian3dVectorArray, + direction: float | na.AbstractScalar, thickness: u.Quantity | na.AbstractScalar, n: u.Quantity | na.AbstractScalar, - normal: na.AbstractVectorArray, ) -> na.Cartesian2dMatrixArray: r""" Compute the propagation matrix, which propagates the electric field @@ -181,13 +174,12 @@ def propagation( wavelength The wavelength of the incident light in vacuum. direction - The propagation direction of the light within the material. + The component of the incident light's propagation direction + antiparallel to the surface normal. thickness The thickness of the material. n The complex index of refraction of the material - normal - The vector perpendicular to the surface of the material. Examples -------- @@ -210,10 +202,9 @@ def propagation( # Compute the propagation matrix optika.materials.matrices.propagation( wavelength=wavelength, - direction=na.Cartesian3dVectorArray(0, 0, 1), + direction=1, thickness=10 * u.nm, n=sio2.n(wavelength), - normal=na.Cartesian3dVectorArray(0, 0, -1), ) Notes @@ -244,9 +235,8 @@ def propagation( and :math:`\theta` is the angle between the surface normal and the propagation direction of the incident light. """ - cos_theta = -direction @ normal - beta = 2 * np.pi * thickness * n * cos_theta / wavelength + beta = 2 * np.pi * thickness * n * direction / wavelength return na.Cartesian2dMatrixArray( x=na.Cartesian2dVectorArray(np.exp(-1j * beta), 0), @@ -334,6 +324,9 @@ def transfer( normal=normal, ) + direction = -direction @ normal + direction_internal = -direction_internal @ normal + matrix_refractive_left = refraction( wavelength=wavelength, direction_left=direction, @@ -341,7 +334,6 @@ def transfer( polarized_s=polarized_s, n_left=1, n_right=n, - normal=normal, ) matrix_propagation = propagation( @@ -349,7 +341,6 @@ def transfer( direction=direction_internal, thickness=thickness, n=n, - normal=normal, ) matrix_refractive_right = refraction( @@ -359,7 +350,6 @@ def transfer( polarized_s=polarized_s, n_left=n, n_right=1, - normal=normal, ) return matrix_refractive_left @ matrix_propagation @ matrix_refractive_right diff --git a/optika/materials/profiles.py b/optika/materials/profiles.py index 5327bf7..84dfd74 100644 --- a/optika/materials/profiles.py +++ b/optika/materials/profiles.py @@ -64,11 +64,10 @@ def _derivative_fourier_transform( def transmissivity( self, wavelength: u.Quantity | na.AbstractScalar, - direction_before: na.AbstractCartesian3dVectorArray, - direction_after: na.AbstractCartesian3dVectorArray, + direction_before: float | na.AbstractScalar, + direction_after: float | na.AbstractScalar, n_before: float | na.AbstractScalar, n_after: float | na.AbstractScalar, - normal: na.AbstractCartesian3dVectorArray, ) -> na.AbstractScalar: """ The specular transmission amplitude for this interface profile. @@ -78,32 +77,31 @@ def transmissivity( wavelength The wavelength of the incident light in vacuum. direction_before - The direction of the incident light before the interface. + The component of the incident light's propagation direction before + the interface antiparallel to the surface normal. direction_after - The direction of the refracted light after the interface + The component of the incident light's propagation direction after + the interface antiparallel to the surface normal. n_before The complex index of refraction of the medium before the interface. n_after The complex index of refraction of the medium after the interface. - normal - The vector normal to the interface surface. Notes ----- The specular transmission amplitude is given by :cite:t:`Stearns1989` Equation 42. """ - k_before = 2 * np.pi * n_before * (direction_before @ normal) / wavelength - k_after = 2 * np.pi * n_after * (direction_after @ normal) / wavelength + k_before = -2 * np.pi * n_before * direction_before / wavelength + k_after = -2 * np.pi * n_after * direction_after / wavelength s = np.real(k_after - k_before) return self._derivative_fourier_transform(s) def reflectivity( self, wavelength: u.Quantity | na.AbstractScalar, - direction: na.AbstractCartesian3dVectorArray, + direction: float | na.AbstractScalar, n: float | na.AbstractScalar, - normal: na.AbstractCartesian3dVectorArray, ) -> na.AbstractScalar: """ Calculate the loss of the reflectivity due to this interface profile. @@ -113,14 +111,14 @@ def reflectivity( wavelength the wavelength of the incident light in vacuum direction - the propagation direction of the incident light, expressed in - direction cosines. + The component of the incident light's propagation direction + antiparallel to the surface normal. n The complex index of refraction of the medium before the interface. normal the vector perpendicular to the optical surface """ - k = 2 * np.pi * n * (direction @ normal) / wavelength + k = -2 * np.pi * n * direction / wavelength s = np.real(-2 * k) return self._derivative_fourier_transform(s) @@ -179,21 +177,14 @@ class ErfInterfaceProfile( # Define an array of incidence angles angle = na.linspace(-90, 90, axis="angle", num=101) * u.deg - # define an array of direction cosines based off of the incidence angles - direction = na.Cartesian3dVectorArray( - x=np.sin(angle), - y=0, - z=np.cos(angle), - ) + # Define an array of direction cosines based off of the incidence angles + direction = np.cos(angle) # Define the index of refraction of the current medium n = 1 - # Define the vector normal to the optical surface - normal = na.Cartesian3dVectorArray(0, 0, 1) - - # calculate the reflectivity for the given angles - reflectivity = p.reflectivity(wavelength, direction, n, normal) + # Calculate the reflectivity for the given angles + reflectivity = p.reflectivity(wavelength, direction, n) # Plot the reflectivity of the interface profile as a function of # incidence angle @@ -279,21 +270,14 @@ class ExponentialInterfaceProfile( # Define an array of incidence angles angle = na.linspace(-90, 90, axis="angle", num=101) * u.deg - # define an array of direction cosines based off of the incidence angles - direction = na.Cartesian3dVectorArray( - x=np.sin(angle), - y=0, - z=np.cos(angle), - ) + # Define an array of direction cosines based off of the incidence angles + direction = np.cos(angle) # Define the index of refraction of the current medium n = 1 - # Define the vector normal to the optical surface - normal = na.Cartesian3dVectorArray(0, 0, 1) - - # calculate the reflectivity for the given angles - reflectivity = p.reflectivity(wavelength, direction, n, normal) + # Calculate the reflectivity for the given angles + reflectivity = p.reflectivity(wavelength, direction, n) # Plot the reflectivity of the interface profile as a function of # incidence angle @@ -383,21 +367,14 @@ class LinearInterfaceProfile( # Define an array of incidence angles angle = na.linspace(-90, 90, axis="angle", num=101) * u.deg - # define an array of direction cosines based off of the incidence angles - direction = na.Cartesian3dVectorArray( - x=np.sin(angle), - y=0, - z=np.cos(angle), - ) + # Define an array of direction cosines based off of the incidence angles + direction = np.cos(angle) # Define the index of refraction of the current medium n = 1 - # Define the vector normal to the optical surface - normal = na.Cartesian3dVectorArray(0, 0, 1) - - # calculate the reflectivity for the given angles - reflectivity = p.reflectivity(wavelength, direction, n, normal) + # Calculate the reflectivity for the given angles + reflectivity = p.reflectivity(wavelength, direction, n) # Plot the reflectivity of the interface profile as a function of # incidence angle @@ -492,21 +469,14 @@ class SinusoidalInterfaceProfile( # Define an array of incidence angles angle = na.linspace(-90, 90, axis="angle", num=101) * u.deg - # define an array of direction cosines based off of the incidence angles - direction = na.Cartesian3dVectorArray( - x=np.sin(angle), - y=0, - z=np.cos(angle), - ) + # Define an array of direction cosines based off of the incidence angles + direction = np.cos(angle) # Define the index of refraction of the current medium n = 1 - # Define the vector normal to the optical surface - normal = na.Cartesian3dVectorArray(0, 0, 1) - - # calculate the reflectivity for the given angles - reflectivity = p.reflectivity(wavelength, direction, n, normal) + # Calculate the reflectivity for the given angles + reflectivity = p.reflectivity(wavelength, direction, n) # Plot the reflectivity of the interface profile as a function of # incidence angle diff --git a/optika/sensors/_materials/_materials.py b/optika/sensors/_materials/_materials.py index 23d466a..ee771b1 100644 --- a/optika/sensors/_materials/_materials.py +++ b/optika/sensors/_materials/_materials.py @@ -343,7 +343,7 @@ def quantum_efficiency_effective( reflectivity, transmissivity = optika.materials.multilayer_efficiency( wavelength=wavelength, - direction=direction, + direction=-direction @ normal, n=n, layers=optika.materials.Layer( chemical=chemical_oxide, @@ -352,7 +352,6 @@ def quantum_efficiency_effective( substrate=optika.materials.Layer( chemical=chemical_substrate, ), - normal=normal, ) n_substrate = chemical_substrate.n(wavelength) From 615d4381eca5f2372d4392b66964417d609f88db Mon Sep 17 00:00:00 2001 From: Roy Smart Date: Wed, 13 Mar 2024 14:13:34 -0600 Subject: [PATCH 4/4] Fixing Black errors. --- optika/materials/_tests/test_multilayers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optika/materials/_tests/test_multilayers.py b/optika/materials/_tests/test_multilayers.py index 3377c37..11a4841 100644 --- a/optika/materials/_tests/test_multilayers.py +++ b/optika/materials/_tests/test_multilayers.py @@ -18,7 +18,7 @@ argnames="direction", argvalues=[ 1, - np.cos(na.linspace(-1, 1, axis="angle", num=5)) + np.cos(na.linspace(-1, 1, axis="angle", num=5)), ], ) @pytest.mark.parametrize("n", [1])