From ef8bdaa4ed0553cbd9e4df9cd5d567570d935681 Mon Sep 17 00:00:00 2001 From: momchil Date: Mon, 17 Jan 2022 12:27:23 -0800 Subject: [PATCH 1/2] Renaming Mode->ModeSpec and removing mode_index from it --- tests/test_IO.py | 2 +- tests/test_components.py | 14 +- tests/test_plugins.py | 2 +- tests/utils.py | 2 +- tidy3d/__init__.py | 2 +- tidy3d/components/__init__.py | 2 +- tidy3d/components/data.py | 4 +- tidy3d/components/mode.py | 29 +--- tidy3d/components/monitor.py | 8 +- tidy3d/components/source.py | 22 ++- tidy3d/plugins/mode/mode_solver.py | 268 ++++++++++++++--------------- 11 files changed, 168 insertions(+), 187 deletions(-) diff --git a/tests/test_IO.py b/tests/test_IO.py index c75591c543..7b1c90bd2f 100644 --- a/tests/test_IO.py +++ b/tests/test_IO.py @@ -53,7 +53,7 @@ def test_simulation_preserve_types(): FieldTimeMonitor(size=(1, 1, 1), start=1e-12, interval=3, name="fieldtime"), FluxMonitor(size=(1, 0, 1), freqs=[1, 2, 3], name="flux"), FluxTimeMonitor(size=(1, 0, 1), start=1e-12, interval=3, name="fluxtime"), - ModeMonitor(size=(1, 0, 1), freqs=[1, 2, 3], modes=[Mode(mode_index=1)], name="mode"), + ModeMonitor(size=(1, 0, 1), freqs=[1, 2], mode_spec=ModeSpec(num_modes=3), name="mode"), ], ) diff --git a/tests/test_components.py b/tests/test_components.py index 58f657c82b..7c160f9d81 100644 --- a/tests/test_components.py +++ b/tests/test_components.py @@ -329,12 +329,8 @@ def test_epsilon_eval(): def test_modes(): - m = Mode(mode_index=0) - m = Mode(mode_index=0, num_modes=1) - - # not enough modes - with pytest.raises(SetupError) as e: - m = Mode(mode_index=1, num_modes=1) + m = ModeSpec(num_modes=2) + m = ModeSpec(num_modes=1, target_neff=1.0) """ names """ @@ -483,7 +479,7 @@ def test_source_times(): def test_FieldSource(): g = GaussianPulse(freq0=1, fwidth=0.1) - mode = Mode(mode_index=0) + mode_spec = ModeSpec(num_modes=2) # test we can make planewave s = PlaneWave(size=(0, 1, 1), source_time=g, pol_angle=np.pi / 2, direction="+") @@ -492,7 +488,7 @@ def test_FieldSource(): s = GaussianBeam(size=(0, 1, 1), source_time=g, pol_angle=np.pi / 2, direction="+") # test we can make mode source - s = ModeSource(size=(0, 1, 1), direction="+", source_time=g, mode=mode) + s = ModeSource(size=(0, 1, 1), direction="+", source_time=g, mode_spec=mode_spec, mode_index=0) # test that non-planar geometry crashes plane wave and gaussian beam with pytest.raises(ValidationError) as e_info: @@ -500,7 +496,7 @@ def test_FieldSource(): with pytest.raises(ValidationError) as e_info: s = GaussianBeam(size=(1, 1, 1), source_time=g, pol_angle=np.pi / 2, direction="+") with pytest.raises(ValidationError) as e_info: - s = ModeSource(size=(1, 1, 1), direction="+", source_time=g, mode=mode) + s = ModeSource(size=(1, 1, 1), source_time=g, mode_spec=mode_spec) """ monitors """ diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 009ef10cc9..51e68d4899 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -46,7 +46,7 @@ def test_mode_solver(): simulation = td.Simulation(size=(2, 2, 2), grid_size=(0.1, 0.1, 0.1), structures=[waveguide]) plane = td.Box(center=(0, 0, 0), size=(0, 1, 1)) ms = ModeSolver(simulation=simulation, plane=plane, freq=td.constants.C_0 / 1.5) - modes = ms.solve(mode=td.Mode(mode_index=1)) + modes = ms.solve(mode_spec=td.ModeSpec(num_modes=2)) def test_coeffs(): diff --git a/tests/utils.py b/tests/utils.py index 8604534363..347bcc27a0 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -45,7 +45,7 @@ def prepend_tmp(path): size=(1, 1, 0), center=(0, 0, 0), freqs=[1.90, 2.01, 2.2], - modes=[Mode(mode_index=1)], + mode_spec=ModeSpec(num_modes=3), name="mode", ), ], diff --git a/tidy3d/__init__.py b/tidy3d/__init__.py index 954c7b47a5..5540d08e71 100644 --- a/tidy3d/__init__.py +++ b/tidy3d/__init__.py @@ -24,7 +24,7 @@ from .components import Structure # modes -from .components import Mode +from .components import ModeSpec # sources from .components import GaussianPulse, ContinuousWave diff --git a/tidy3d/components/__init__.py b/tidy3d/components/__init__.py index 27e2aa11a9..06df15d8bb 100644 --- a/tidy3d/components/__init__.py +++ b/tidy3d/components/__init__.py @@ -20,7 +20,7 @@ from .structure import Structure # mode -from .mode import Mode +from .mode import ModeSpec # source from .source import GaussianPulse, ContinuousWave diff --git a/tidy3d/components/data.py b/tidy3d/components/data.py index c8a9c1bd79..8d290a4365 100644 --- a/tidy3d/components/data.py +++ b/tidy3d/components/data.py @@ -12,7 +12,7 @@ from .types import Numpy, Direction, Array, numpy_encoding, Literal, Ax from .base import Tidy3dBaseModel from .simulation import Simulation -from .mode import Mode +from .mode import ModeSpec from .viz import add_ax_if_none from ..log import DataError @@ -605,7 +605,6 @@ class ModeData(PlanarData, FreqData): ------- >>> f = np.linspace(2e14, 3e14, 1001) - >>> modes = [Mode(mode_index=0), Mode(mode_index=1)] >>> values = (1+1j) * np.random.random((1, 2, len(f))) >>> data = ModeData(values=values, direction=['+'], mode_index=np.arange(1, 3), f=f) """ @@ -618,7 +617,6 @@ class ModeData(PlanarData, FreqData): _dims = ("direction", "mode_index", "f") - @abstractmethod def normalize(self, source_freq_amps: Array[complex]) -> None: """normalize the values by the amplitude of the source.""" self.values /= 1j * source_freq_amps # pylint: disable=no-member diff --git a/tidy3d/components/mode.py b/tidy3d/components/mode.py index aa87b1ed3e..3647f0579a 100644 --- a/tidy3d/components/mode.py +++ b/tidy3d/components/mode.py @@ -6,22 +6,18 @@ from .base import Tidy3dBaseModel from .types import Symmetry -from ..log import SetupError -class Mode(Tidy3dBaseModel): +class ModeSpec(Tidy3dBaseModel): """Stores specifications for the mode solver to find an electromagntic mode. Note, the planar axes are found by popping the propagation axis from {x,y,z}. For example, if propagation axis is y, the planar axes are ordered {x,z}. Parameters ---------- - mode_index : int - Return the mode solver output at ``mode_index``. - Must be >= 0. - num_modes : int = None - Number of modes returned by mode solver before selecting mode at ``mode_index``. - Must be > ``mode_index`` to accomodate ``mode_index``-th mode. + + num_modes : int = 1 + Number of modes returned by mode solver. target_neff : float = None Guess for effective index of mode. Must be > 0. @@ -33,23 +29,10 @@ class Mode(Tidy3dBaseModel): Example ------- - >>> mode = Mode(mode_index=1, num_modes=3, target_neff=1.5, symmetries=(1,-1)) + >>> mode_spec = ModeSpec(num_modes=3, target_neff=1.5, symmetries=(1,-1)) """ - mode_index: pd.NonNegativeInt - num_modes: pd.PositiveInt = None + num_modes: pd.PositiveInt = 1 target_neff: pd.PositiveFloat = None symmetries: Tuple[Symmetry, Symmetry] = (0, 0) num_pml: Tuple[pd.NonNegativeInt, pd.NonNegativeInt] = (0, 0) - - @pd.validator("num_modes", always=True) - def check_num_modes(cls, val, values): - """Make sure num_modes is > mode_index or None""" - if val is not None: - mode_index = values.get("mode_index") - if not val > mode_index: - raise SetupError( - "`num_modes` must be greater than `mode_index`" - f"given {val} and {mode_index}, respectively" - ) - return val diff --git a/tidy3d/components/monitor.py b/tidy3d/components/monitor.py index 712aa40197..da6ef0fd9c 100644 --- a/tidy3d/components/monitor.py +++ b/tidy3d/components/monitor.py @@ -7,7 +7,7 @@ from .types import Literal, Ax, Direction, FieldType, EMField, Array from .geometry import Box from .validators import assert_plane, validate_name_str -from .mode import Mode +from .mode import ModeSpec from .viz import add_ax_if_none, MonitorParams from ..log import SetupError, ValidationError @@ -262,16 +262,16 @@ class ModeMonitor(PlanarMonitor, FreqMonitor): Example ------- - >>> modes = [Mode(mode_index=0), Mode(mode_index=1)] + >>> mode_spec = ModeSpec(num_modes=3) >>> monitor = ModeMonitor( ... size=(2,2,0), ... freqs=[200e12, 210e12], - ... modes=modes, + ... mode_spec=mode_spec, ... name='mode_monitor') """ direction: List[Direction] = ["+", "-"] - modes: List[Mode] + mode_spec: ModeSpec type: Literal["ModeMonitor"] = "ModeMonitor" data_type: Literal["ModeData"] = "ModeData" diff --git a/tidy3d/components/source.py b/tidy3d/components/source.py index 4344c7dbd6..f32fc0006e 100644 --- a/tidy3d/components/source.py +++ b/tidy3d/components/source.py @@ -10,7 +10,7 @@ from .types import Direction, Polarization, Ax, FreqBound, Array, Literal from .validators import assert_plane, validate_name_str from .geometry import Box -from .mode import Mode +from .mode import ModeSpec from .viz import add_ax_if_none, SourceParams from ..constants import inf # pylint:disable=unused-import @@ -339,20 +339,30 @@ class ModeSource(FieldSource): direction : str Specifies propagation in the positive or negative direction of the normal axis. Must be in ``{'+', '-'}``. - mode : :class:`Mode` - Specification of the mode being injected by source. + mode_spec :class:`ModeSpec` + Specification for the mode solver to find the mode injected by the source. + mode_index : int + Index of the mode to inject in the collection of modes returned by the solver. If larger + than ``mode_spec.num_modes``, ``num_modes`` in the solver will be set to ``mode_index + 1``. + Must be >= 0. name : str = None Optional name for source. Example ------- >>> pulse = GaussianPulse(freq0=200e12, fwidth=20e12) - >>> mode = Mode(mode_index=1, num_modes=3) - >>> mode_source = ModeSource(size=(10,10,0), source_time=pulse, mode=mode, direction='-') + >>> mode_spec = ModeSpec(target_neff=2.) + >>> mode_source = ModeSource( + ... size=(10,10,0), + ... source_time=pulse, + ... mode_spec=mode_spec, + ... mode_index=1, + ... direction='-') """ type: Literal["ModeSource"] = "ModeSource" - mode: Mode + mode_spec: ModeSpec = ModeSpec() + mode_index: pydantic.NonNegativeInt = 0 class PlaneWave(FieldSource): diff --git a/tidy3d/plugins/mode/mode_solver.py b/tidy3d/plugins/mode/mode_solver.py index c6f3f3be39..acc4f3f031 100644 --- a/tidy3d/plugins/mode/mode_solver.py +++ b/tidy3d/plugins/mode/mode_solver.py @@ -9,10 +9,11 @@ from ...components import Box from ...components import Simulation -from ...components import Mode +from ...components import ModeSpec from ...components import ModeMonitor from ...components import ModeSource, GaussianPulse from ...components.types import Direction +from ...components.data import ScalarFieldData, FieldData from ...log import SetupError from .solver import compute_modes @@ -34,14 +35,17 @@ freqs = td.FreqSampler(freqs=[1, 2]) # frequencies we care about ms = ModeSolver(simulation, plane, freqs) -mode = td.Mode(mode_index=2) # our initial guess for the mode -field = ms.solve(mode=mode) # td.FieldData storing E, H -field.plot() # inspect fields, do they look ok? +mode_spec = td.Mode(num_modes=3) # solve for a number of modes to find the one we want +modes = ms.solve(mode_spec=mode_spec) # list of ModeInfo objects for each mode +mode_index = 1 # initial guess for the mode index +modes[mode_index].field_data.plot() # inspect fields, do they look ok? -mon = ms.export_monitor(mode=mode) # if we're happy with results, return td.ModeMonitor -src = ms.export_src(mode=mode, src_time=...) # or as a td.ModeSource +mon = ms.export_monitor(mode_spec=mode_spec) # if we're happy with results, return td.ModeMonitor +src = ms.export_src(mode_spec=mode_spec, # or as a td.ModeSource + mode_index=mode_index, + src_time=...) -src.to_file('data/my_source.json') # this source /monitor can be saved to file +src.to_file('data/my_source.json') # this source /monitor can be saved to file src = ModeSource.from_file('data/my_source.json') # and loaded in our script """ @@ -61,8 +65,9 @@ class ModeInfo: Imaginary part of the effective refractive index of mode. """ - field_data: xr.Dataset - mode: Mode + field_data: FieldData + mode_spec: ModeSpec + mode_index: int n_eff: float k_eff: float @@ -89,7 +94,7 @@ def __init__(self, simulation: Simulation, plane: Box, freq: float): assert 0.0 in plane.size, "plane must have at least one axis with size=0" - def solve(self, mode: Mode) -> ModeInfo: + def solve(self, mode_spec: ModeSpec) -> List[ModeInfo]: """Solves for modal profile and effective index of ``Mode`` object. Parameters @@ -125,140 +130,129 @@ def solve(self, mode: Mode) -> ModeInfo: # construct eps_cross section to feed to mode solver eps_cross = np.stack((eps_wg_xx, eps_wg_yy, eps_wg_zz)) - Nx, Ny = eps_cross.shape[1:] - if mode.symmetries[0] != 0: - eps_cross = np.stack(tuple(e[Nx // 2, :] for e in eps_cross)) - if mode.symmetries[1] != 0: - eps_cross = np.stack(tuple(e[:, Ny // 2] for e in eps_cross)) - - num_modes = mode.num_modes if mode.num_modes else mode.mode_index + 1 - if num_modes <= mode.mode_index: - - raise SetupError( - f"Mode.mode_index = {mode.mode_index} " - f"is out of bounds for the number of modes given: Mode.num_modes={mode.um_modes}." - ) + # Nx, Ny = eps_cross.shape[1:] + # if mode_spec.symmetries[0] != 0: + # eps_cross = np.stack(tuple(e[Nx // 2, :] for e in eps_cross)) + # if mode_spec.symmetries[1] != 0: + # eps_cross = np.stack(tuple(e[:, Ny // 2] for e in eps_cross)) # note, internally discretizing, need to make consistent. - field, n_eff_complex = compute_modes( + mode_fields, n_eff_complex = compute_modes( eps_cross=eps_cross, freq=self.freq, grid_size=self.simulation.grid_size, - pml_layers=mode.num_pml, - num_modes=num_modes, - target_neff=mode.target_neff, - symmetries=mode.symmetries, + pml_layers=mode_spec.num_pml, + num_modes=mode_spec.num_modes, + target_neff=mode_spec.target_neff, + symmetries=mode_spec.symmetries, coords=None, ) - # Get fields at the Mode.mode_index - field_values = field[..., mode.mode_index] - E, H = field_values - - # Handle symmetries - if mode.symmetries[0] != 0: - E_half = E[:, 1:, ...] - H_half = H[:, 1:, ...] - E = np.concatenate((+E_half[:, ::-1, ...], E_half), axis=1) - H = np.concatenate((-H_half[:, ::-1, ...], H_half), axis=1) - if mode.symmetries[1] != 0: - E_half = E[:, :, 1:, ...] - H_half = H[:, :, 1:, ...] - E = np.concatenate((+E_half[:, :, ::-1, ...], E_half), axis=2) - H = np.concatenate((-H_half[:, :, ::-1, ...], H_half), axis=2) - Ex, Ey, Ez = E[..., None] - Hx, Hy, Hz = H[..., None] - - # add in the normal coordinate for each of the fields - def rotate_field_coords(field_array): + def rotate_field_coords(e_field, h_field): """move the propagation axis=z to the proper order in the array""" - return np.moveaxis(field_array, source=2, destination=normal_axis) - - Ex = rotate_field_coords(Ex) - Ey = rotate_field_coords(Ey) - Ez = rotate_field_coords(Ez) - Hx = rotate_field_coords(Hx) - Hy = rotate_field_coords(Hy) - Hz = rotate_field_coords(Hz) - - # return the fields and coordinates in the original coordinate system - Ex, Ey, Ez = self.simulation.unpop_axis(Ez, (Ex, Ey), axis=normal_axis) - Hx, Hy, Hz = self.simulation.unpop_axis(Hz, (Hx, Hy), axis=normal_axis) - - # apply -1 to H fields if needed, due to how they transform under reflections - if normal_axis == 1: - Hx *= -1 - Hy *= -1 - Hz *= -1 - - # note: from this point on, back in original coordinates - - fields = {"Ex": Ex, "Ey": Ey, "Ez": Ez, "Hx": Hx, "Hy": Hy, "Hz": Hz} - - # note: re-discretizing, need to make consistent. - data_dict = {} - for field_name, field in fields.items(): - plane_grid = self.simulation.discretize(self.plane) - plane_coords = plane_grid[field_name] - coords = { - "x": plane_coords.x, - "y": plane_coords.y, - "z": plane_coords.z, - "f": np.array([self.freq]), - } - data_dict[field_name] = xr.DataArray(field, coords=coords) - - n_eff_complex = n_eff_complex[mode.mode_index] - - mode_info = ModeInfo( - field_data=xr.Dataset(data_dict), - mode=mode, - n_eff=n_eff_complex.real, - k_eff=n_eff_complex.imag, - ) - - return mode_info - - def make_source(self, mode: Mode, fwidth: float, direction: Direction) -> ModeSource: - """Creates ``ModeSource`` from a Mode + additional specifications. - - Parameters - ---------- - mode : Mode - ``Mode`` object containing specifications of mode. - fwidth : float - Standard deviation of ``GaussianPulse`` of source (Hz). - direction : Direction - Whether source will inject in ``"+"`` or ``"-"`` direction relative to plane normal. - - Returns - ------- - ModeSource - Modal source containing specification in ``mode``. - """ - center = self.plane.center - size = self.plane.size - source_time = GaussianPulse(freq0=self.freq, fwidth=fwidth) - return ModeSource( - center=center, size=size, source_time=source_time, mode=mode, direction=direction - ) - - def make_monitor(self, mode: Mode, freqs: List[float], name: str) -> ModeMonitor: - """Creates ``ModeMonitor`` from a Mode + additional specifications. + Ex, Ey, Ez = np.moveaxis(e_field, source=3, destination=1 + normal_axis) + e_rot = np.stack(self.simulation.unpop_axis(Ez, (Ex, Ey), axis=normal_axis), axis=0) + Hx, Hy, Hz = np.moveaxis(h_field, source=3, destination=1 + normal_axis) + h_rot = np.stack(self.simulation.unpop_axis(Hz, (Hx, Hy), axis=normal_axis), axis=0) + return (e_rot, h_rot) + + modes = [] + for mode_index in range(mode_spec.num_modes): + + # Get E and H fields at the current mode_index + E, H = mode_fields[..., mode_index] + + # # Handle symmetries + # if mode.symmetries[0] != 0: + # E_half = E[:, 1:, ...] + # H_half = H[:, 1:, ...] + # E = np.concatenate((+E_half[:, ::-1, ...], E_half), axis=1) + # H = np.concatenate((-H_half[:, ::-1, ...], H_half), axis=1) + # if mode.symmetries[1] != 0: + # E_half = E[:, :, 1:, ...] + # H_half = H[:, :, 1:, ...] + # E = np.concatenate((+E_half[:, :, ::-1, ...], E_half), axis=2) + # H = np.concatenate((-H_half[:, :, ::-1, ...], H_half), axis=2) + + # Rotate back to original coordinates + (Ex, Ey, Ez), (Hx, Hy, Hz) = rotate_field_coords(E, H) + + # apply -1 to H fields if a reflection was involved in the rotation + if normal_axis == 1: + Hx *= -1 + Hy *= -1 + Hz *= -1 + + # note: from this point on, back in original coordinates + fields = {"Ex": Ex, "Ey": Ey, "Ez": Ez, "Hx": Hx, "Hy": Hy, "Hz": Hz} + + # note: re-discretizing, need to make consistent. + data_dict = {} + for field_name, field in fields.items(): + plane_grid = self.simulation.discretize(self.plane) + plane_coords = plane_grid[field_name] + xyz_coords = [plane_coords.x, plane_coords.y, plane_coords.z] + xyz_coords[normal_axis] = [self.plane.center[normal_axis]] + data_dict[field_name] = ScalarFieldData( + x=xyz_coords[0], + y=xyz_coords[1], + z=xyz_coords[2], + f=np.array([self.freq]), + values=field[..., None], + ) + + mode_info = ModeInfo( + field_data=FieldData(data_dict=data_dict).data, + mode_spec=mode_spec, + mode_index=mode_index, + n_eff=n_eff_complex[mode_index].real, + k_eff=n_eff_complex[mode_index].imag, + ) - Parameters - ---------- - mode : Mode - ``Mode`` object containing specifications of mode. - freqs : List[float] - Frequencies to include in Monitor (Hz). - name : str - Required name of monitor. - Returns - ------- - ModeMonitor - Monitor that measures ``Mode`` on ``plane`` at ``freqs``. - """ - center = self.plane.center - size = self.plane.size - return ModeMonitor(center=center, size=size, freqs=freqs, modes=[mode], name=name) + modes.append(mode_info) + + return modes + + # def make_source(self, mode_spec: ModeSpec, fwidth: float, direction: Direction) -> ModeSource: + # """Creates ``ModeSource`` from a Mode + additional specifications. + + # Parameters + # ---------- + # mode : Mode + # ``Mode`` object containing specifications of mode. + # fwidth : float + # Standard deviation of ``GaussianPulse`` of source (Hz). + # direction : Direction + # Whether source will inject in ``"+"`` or ``"-"`` direction relative to plane normal. + + # Returns + # ------- + # ModeSource + # Modal source containing specification in ``mode``. + # """ + # center = self.plane.center + # size = self.plane.size + # source_time = GaussianPulse(freq0=self.freq, fwidth=fwidth) + # return ModeSource( + # center=center, size=size, source_time=source_time, mode=mode, direction=direction + # ) + + # def make_monitor(self, mode: Mode, freqs: List[float], name: str) -> ModeMonitor: + # """Creates ``ModeMonitor`` from a Mode + additional specifications. + + # Parameters + # ---------- + # mode : Mode + # ``Mode`` object containing specifications of mode. + # freqs : List[float] + # Frequencies to include in Monitor (Hz). + # name : str + # Required name of monitor. + # Returns + # ------- + # ModeMonitor + # Monitor that measures ``Mode`` on ``plane`` at ``freqs``. + # """ + # center = self.plane.center + # size = self.plane.size + # return ModeMonitor(center=center, size=size, freqs=freqs, modes=[mode], name=name) From dd5cf2ec29fd97dbbf25cfb530f2f4316a042db8 Mon Sep 17 00:00:00 2001 From: momchil Date: Thu, 13 Jan 2022 15:31:46 -0800 Subject: [PATCH 2/2] Small changes to make tests work --- tests/test_web.py | 16 +++++++++++----- tidy3d/components/source.py | 15 +++++++-------- tidy3d/convert.py | 11 +++++------ tidy3d/web/auth.py | 13 +++++++++---- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/tests/test_web.py b/tests/test_web.py index b3ad0ced72..229ac3d953 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -6,8 +6,9 @@ import pytest import tidy3d as td +from tidy3d.log import DataError import tidy3d.web as web -from tidy3d.web.auth import get_credentials +from tidy3d.web.auth import get_credentials, encode_password from .utils import SIM_CONVERT as sim_original from .utils import clear_tmp @@ -35,7 +36,7 @@ def test_get_email_passwd_auth_key_ok(self, set_authentication_config): os.environ["TIDY3D_USER"] = "mytestuser" os.environ["TIDY3D_PASS"] = "mytestpass" get_credentials() - set_authentication_config.assert_called_with("mytestuser", "mytestpass") + set_authentication_config.assert_called_with("mytestuser", encode_password("mytestpass")) @clear_tmp @@ -109,9 +110,6 @@ def test_source_norm(): sim_data_raw = web.run( simulation=sim_original, task_name="test_webapi", path=PATH_SIM_DATA, normalize_index=None ) - sim_data_norm = web.run( - simulation=sim_original, task_name="test_webapi", path=PATH_SIM_DATA, normalize_index=1 - ) """ Jobs """ @@ -181,6 +179,14 @@ def _test_job_7_delete(): assert task_info.status in ("deleted", "deleting") +def test_job_source_norm(): + """test complete run""" + job = web.Job(simulation=sim_original, task_name="test_job", callback_url=CALLBACK_URL) + sim_data_norm = job.run(path=PATH_SIM_DATA, normalize_index=0) + with pytest.raises(DataError): + sim_data_norm = web.load(task_id=job.task_id, simulation=sim_original, normalize_index=1) + + """ Batches """ diff --git a/tidy3d/components/source.py b/tidy3d/components/source.py index f32fc0006e..cc9382c869 100644 --- a/tidy3d/components/source.py +++ b/tidy3d/components/source.py @@ -337,8 +337,8 @@ class ModeSource(FieldSource): source_time : :class:`GaussianPulse` or :class:`ContinuousWave` Specification of time-dependence of source. direction : str - Specifies propagation in the positive or negative direction of the normal axis. Must be in - ``{'+', '-'}``. + Specifies propagation in the positive or negative direction of the normal axis. + Must be in ``{'+', '-'}``. mode_spec :class:`ModeSpec` Specification for the mode solver to find the mode injected by the source. mode_index : int @@ -387,8 +387,8 @@ class PlaneWave(FieldSource): - ``Ex`` polarization for propagation along ``y``. - ``Ex`` polarization for propagation along ``z``. direction : str - Specifies propagation in the positive or negative direction of the normal axis. Must be in - ``{'+', '-'}``. + Specifies propagation in the positive or negative direction of the normal axis. + Must be in ``{'+', '-'}``. name : str = None Optional name for source. @@ -418,14 +418,13 @@ class GaussianBeam(FieldSource): source_time : :class:`GaussianPulse` or :class:`ContinuousWave` Specification of time-dependence of source. direction : str - Specifies propagation in the positive or negative direction of the normal axis. Must be in - ``{'+', '-'}``. + Specifies propagation in the positive or negative direction of the normal axis. + Must be in ``{'+', '-'}``. waist_radius: float = 1.0 Radius of the beam at the waist (um). Must be positive. waist_distance: float = 0.0 Distance (um) from the beam waist along the propagation direction. - Must be non-negative. angle_theta : float, optional Polar angle from the normal axis (rad). angle_phi : float, optional @@ -453,7 +452,7 @@ class GaussianBeam(FieldSource): """ waist_radius: pydantic.PositiveFloat = 1.0 - waist_distance: pydantic.NonNegativeFloat = 0.0 + waist_distance: float = 0.0 angle_theta: float = 0.0 angle_phi: float = 0.0 pol_angle: float = 0.0 diff --git a/tidy3d/convert.py b/tidy3d/convert.py index 4e14324212..45ea1c1aed 100644 --- a/tidy3d/convert.py +++ b/tidy3d/convert.py @@ -248,8 +248,8 @@ def old_json_sources(sim: Simulation) -> List[Dict]: "amplitude": source.source_time.amplitude, } elif isinstance(source, ModeSource): - mode_ind = source.mode.mode_index - num_modes = source.mode.num_modes if source.mode.num_modes else mode_ind + 1 + mode_ind = source.mode_index + num_modes = max(source.mode_spec.num_modes, mode_ind + 1) direction = "forward" if source.direction == "+" else "backward" src = { "name": name, @@ -260,7 +260,7 @@ def old_json_sources(sim: Simulation) -> List[Dict]: "direction": direction, "amplitude": source.source_time.amplitude, "mode_ind": mode_ind, - "target_neff": source.mode.target_neff, + "target_neff": source.mode_spec.target_neff, "Nmodes": num_modes, } elif isinstance(source, PlaneWave): @@ -386,12 +386,11 @@ def old_json_monitors(sim: Simulation) -> Dict: } ) elif isinstance(monitor, ModeMonitor): - num_modes = max([m.mode_index for m in monitor.modes]) + 1 mnt.update( { "type": "ModeMonitor", - "Nmodes": num_modes, - "target_neff": None, + "Nmodes": monitor.mode_spec.num_modes, + "target_neff": monitor.mode_spec.target_neff, "store": ["mode_amps"], } ) diff --git a/tidy3d/web/auth.py b/tidy3d/web/auth.py index f81e491bce..d54f675c9d 100644 --- a/tidy3d/web/auth.py +++ b/tidy3d/web/auth.py @@ -38,19 +38,24 @@ def set_authentication_config(email: str, password: str) -> None: Config.user = new_keys +def encode_password(password: str) -> str: + """Hash a password.""" + + salt = "5ac0e45f46654d70bda109477f10c299" # TODO: salt should be added server-side + return hashlib.sha512(password.encode("utf-8") + salt.encode("utf-8")).hexdigest() + + def get_credentials() -> None: """Tries to log user in from environment variables, then from file, if not working, prompts user for login info and saves to file.""" - salt = "5ac0e45f46654d70bda109477f10c299" # TODO: salt should be added server-side # if we find credentials in environment variables if "TIDY3D_USER" in os.environ and "TIDY3D_PASS" in os.environ: print("Using Tidy3D credentials from enviornment") email = os.environ["TIDY3D_USER"] password = os.environ["TIDY3D_PASS"] - password = hashlib.sha512(password.encode("utf-8") + salt.encode("utf-8")).hexdigest() try: - set_authentication_config(email, password) + set_authentication_config(email, encode_password(password)) return except Exception: # pylint:disable=broad-except @@ -77,7 +82,7 @@ def get_credentials() -> None: email = input("enter your email registered at tidy3d: ") password = getpass.getpass("enter your password: ") # encrypt - password = hashlib.sha512(password.encode("utf-8") + salt.encode("utf-8")).hexdigest() + password = encode_password(password) try: set_authentication_config(email, password)