diff --git a/CHANGELOG.md b/CHANGELOG.md index 611790d2e..b99160c73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +# Changed +- Source validation happens before simulation upload and raises an error if no source is present. + ## [2.3.0] - 2023-6-30 ### Added diff --git a/tests/test_web/test_webapi.py b/tests/test_web/test_webapi.py index 026cbf969..36801e9c7 100644 --- a/tests/test_web/test_webapi.py +++ b/tests/test_web/test_webapi.py @@ -9,7 +9,7 @@ from responses import matchers -from tidy3d import Simulation +from tidy3d.exceptions import SetupError from tidy3d.web.environment import Env from tidy3d.web.webapi import delete, delete_old, download, download_json, run from tidy3d.web.webapi import download_log, estimate_cost, get_info, get_run_info, get_tasks @@ -32,7 +32,14 @@ def make_sim(): """Makes a simulation.""" - return td.Simulation(size=(1, 1, 1), grid_spec=td.GridSpec.auto(wavelength=1.0), run_time=1e-12) + pulse = td.GaussianPulse(freq0=200e12, fwidth=20e12) + pt_dipole = td.PointDipole(source_time=pulse, polarization="Ex") + return td.Simulation( + size=(1, 1, 1), + grid_spec=td.GridSpec.auto(wavelength=1.0), + run_time=1e-12, + sources=[pt_dipole], + ) @pytest.fixture @@ -226,6 +233,14 @@ def mock_webapi( """Mocks all webapi operation.""" +@responses.activate +def test_source_validation(mock_upload): + sim = make_sim().copy(update={"sources": []}) + assert upload(sim, TASK_NAME, PROJECT_NAME, source_required=False) + with pytest.raises(SetupError): + upload(sim, TASK_NAME, PROJECT_NAME) + + @responses.activate def test_upload(mock_upload): sim = make_sim() @@ -453,7 +468,7 @@ def test_main(mock_webapi, monkeypatch, mock_job_status): # batch_data = run_async(sims, folder_name=PROJECT_NAME) def save_sim_to_path(path: str) -> None: - sim = Simulation(size=(1, 1, 1), grid_spec=td.GridSpec.auto(wavelength=1.0), run_time=1e-12) + sim = make_sim() sim.to_file(path) monkeypatch.setattr("builtins.input", lambda _: "Y") diff --git a/tidy3d/components/simulation.py b/tidy3d/components/simulation.py index 3db87ea3b..c51ca9673 100644 --- a/tidy3d/components/simulation.py +++ b/tidy3d/components/simulation.py @@ -579,7 +579,7 @@ def _warn_monitor_simulation_frequency_range(cls, val, values): source_ranges = [source.source_time.frequency_range() for source in values["sources"]] if not source_ranges: - log.warning("No sources in simulation.") + log.info("No sources in simulation.") return val freq_min = min((freq_range[0] for freq_range in source_ranges), default=0.0) @@ -890,14 +890,22 @@ def _validate_tfsf_nonuniform_grid(self) -> None: """ Pre submit validation (before web.upload()) """ - def validate_pre_upload(self) -> None: - """Validate the fully initialized simulation is ok for upload to our servers.""" + def validate_pre_upload(self, source_required: bool = True) -> None: + """Validate the fully initialized simulation is ok for upload to our servers. + + Parameters + ---------- + source_required: bool = True + If ``True``, validation will fail in case no sources are found in the simulation. + """ self._validate_size() self._validate_monitor_size() self._validate_datasets_not_none() self._validate_tfsf_structure_intersections() # self._validate_run_time() _ = self.volumetric_structures + if source_required and len(self.sources) == 0: + raise SetupError("No sources in simulation.") def _validate_size(self) -> None: """Ensures the simulation is within size limits before simulation is uploaded.""" diff --git a/tidy3d/plugins/mode/web.py b/tidy3d/plugins/mode/web.py index 8aadf7beb..16847a8f2 100644 --- a/tidy3d/plugins/mode/web.py +++ b/tidy3d/plugins/mode/web.py @@ -189,7 +189,7 @@ def create( mode_spec = mode_solver.mode_spec.copy(update={"precision": "single"}) mode_solver = mode_solver.copy(update={"mode_spec": mode_spec}) - mode_solver.simulation.validate_pre_upload() + mode_solver.simulation.validate_pre_upload(source_required=False) resp = http.post( MODESOLVER_API, { diff --git a/tidy3d/web/webapi.py b/tidy3d/web/webapi.py index df738fbf4..61c0f79d4 100644 --- a/tidy3d/web/webapi.py +++ b/tidy3d/web/webapi.py @@ -142,6 +142,7 @@ def upload( # pylint:disable=too-many-locals,too-many-arguments progress_callback: Callable[[float], None] = None, simulation_type: str = "tidy3d", parent_tasks: List[str] = None, + source_required: bool = True, ) -> TaskId: """Upload simulation to server, but do not start running :class:`.Simulation`. @@ -164,6 +165,8 @@ def upload( # pylint:disable=too-many-locals,too-many-arguments Type of simulation being uploaded. parent_tasks : List[str] List of related task ids. + source_required: bool = True + If ``True``, simulations without sources will raise an error before being uploaded. Returns ------- @@ -175,7 +178,7 @@ def upload( # pylint:disable=too-many-locals,too-many-arguments To start the simulation running, must call :meth:`start` after uploaded. """ - simulation.validate_pre_upload() + simulation.validate_pre_upload(source_required=source_required) log.debug("Creating task.") task = SimulationTask.create(