Skip to content

Commit 6e1ff22

Browse files
marcorudolphflexyaugenst-flex
authored andcommitted
make task names optional
1 parent d7bb1a9 commit 6e1ff22

File tree

10 files changed

+128
-37
lines changed

10 files changed

+128
-37
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010

1111
### Added
1212
- New `MediumMonitor` that returns both permittivity and permeability profiles.
13+
- Task names are now optional when using `run(sim)` or `Job`. When running multiple jobs (via `run_async` or `Batch`), you can also provide simulations as a list without specifying task names. The previous dictionary-based format with explicit task names is still supported.
14+
1315
### Changed
1416

1517
### Fixed

tests/test_components/autograd/test_autograd.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -860,7 +860,8 @@ def objective(*args):
860860

861861

862862
@pytest.mark.parametrize("structure_key, monitor_key", args)
863-
def test_autograd_async(use_emulated_run, structure_key, monitor_key):
863+
@pytest.mark.parametrize("use_task_names", [True, False])
864+
def test_autograd_async(use_emulated_run, structure_key, monitor_key, use_task_names):
864865
"""Test an objective function through tidy3d autograd."""
865866

866867
fn_dict = get_functions(structure_key, monitor_key)
@@ -870,7 +871,10 @@ def test_autograd_async(use_emulated_run, structure_key, monitor_key):
870871
task_names = {"test_a", "adjoint", "_test"}
871872

872873
def objective(*args):
873-
sims = {task_name: make_sim(*args) for task_name in task_names}
874+
if use_task_names:
875+
sims = {task_name: make_sim(*args) for task_name in task_names}
876+
else:
877+
sims = [make_sim(*args)] * len(task_names)
874878
batch_data = run_async(sims, verbose=False)
875879
value = 0.0
876880
for _, sim_data in batch_data.items():

tests/test_web/test_tidy3d_stub.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from tidy3d.components.source.time import GaussianPulse
1616
from tidy3d.web.api.tidy3d_stub import Tidy3dStub, Tidy3dStubData
1717
from tidy3d.web.core.environment import Env, EnvironmentConfig
18+
from tidy3d.web.core.types import TaskType
1819

1920
test_env = EnvironmentConfig(
2021
name="test",
@@ -119,3 +120,10 @@ def test_stub_data_postprocess_logs(tmp_path):
119120
file_path = os.path.join(tmp_path, "test_warnings.hdf5")
120121
sim_data.to_file(file_path)
121122
Tidy3dStubData.postprocess(file_path)
123+
124+
125+
def test_default_task_name():
126+
sim = make_sim()
127+
stub = Tidy3dStub(simulation=sim)
128+
default_task_name = stub.get_default_task_name()
129+
assert default_task_name.startswith(TaskType.FDTD.name.lower())

tests/test_web/test_webapi.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from tidy3d.components.source.current import PointDipole
2121
from tidy3d.components.source.time import GaussianPulse
2222
from tidy3d.exceptions import SetupError
23+
from tidy3d.web import common
2324
from tidy3d.web.api.asynchronous import run_async
2425
from tidy3d.web.api.container import Batch, Job
2526
from tidy3d.web.api.webapi import (
@@ -53,6 +54,7 @@
5354
FLEX_UNIT = 1.0
5455
EST_FLEX_UNIT = 11.11
5556
FILE_SIZE_GB = 4.0
57+
common.CONNECTION_RETRY_TIME = 0.1
5658

5759
task_core_path = "tidy3d.web.core.task_core"
5860
api_path = "tidy3d.web.api.webapi"
@@ -132,12 +134,12 @@ def mock_upload(monkeypatch, set_api_key):
132134
matchers.json_params_matcher(
133135
{
134136
"taskType": TaskType.FDTD.name,
135-
"taskName": TASK_NAME,
136137
"callbackUrl": None,
137138
"simulationType": "tidy3d",
138139
"parentTasks": None,
139140
"fileType": "Gz",
140-
}
141+
},
142+
strict_match=False,
141143
)
142144
],
143145
json={
@@ -520,12 +522,13 @@ def test_get_tasks(set_api_key):
520522

521523

522524
@responses.activate
523-
def test_run(mock_webapi, monkeypatch, tmp_path):
525+
@pytest.mark.parametrize("task_name", [TASK_NAME, None])
526+
def test_run(mock_webapi, monkeypatch, tmp_path, task_name):
524527
sim = make_sim()
525528
monkeypatch.setattr(f"{api_path}.load", lambda *args, **kwargs: True)
526529
assert run(
527530
sim,
528-
task_name=TASK_NAME,
531+
task_name=task_name,
529532
folder_name=PROJECT_NAME,
530533
path=str(tmp_path / "web_test_tmp.json"),
531534
)
@@ -584,10 +587,11 @@ def test_abort_task(set_api_key):
584587

585588

586589
@responses.activate
587-
def test_job(mock_webapi, monkeypatch, tmp_path):
590+
@pytest.mark.parametrize("task_name", [TASK_NAME, None])
591+
def test_job(mock_webapi, monkeypatch, tmp_path, task_name):
588592
monkeypatch.setattr("tidy3d.web.api.container.Job.load", lambda *args, **kwargs: True)
589593
sim = make_sim()
590-
j = Job(simulation=sim, task_name=TASK_NAME, folder_name=PROJECT_NAME)
594+
j = Job(simulation=sim, task_name=task_name, folder_name=PROJECT_NAME)
591595

592596
fname = str(tmp_path / "web_test_tmp.json")
593597

@@ -610,11 +614,15 @@ def mock_job_status(monkeypatch):
610614

611615

612616
@responses.activate
613-
def test_batch(mock_webapi, mock_job_status, mock_load, tmp_path):
617+
@pytest.mark.parametrize("task_name", [TASK_NAME, None])
618+
def test_batch(mock_webapi, mock_job_status, mock_load, tmp_path, task_name):
614619
# monkeypatch.setattr("tidy3d.web.api.container.Batch.monitor", lambda self: time.sleep(0.1))
615620
# monkeypatch.setattr("tidy3d.web.api.container.Job.status", property(lambda self: "success"))
621+
if task_name is None:
622+
sims = [make_sim()]
623+
else:
624+
sims = {TASK_NAME: make_sim()}
616625

617-
sims = {TASK_NAME: make_sim()}
618626
b = Batch(simulations=sims, folder_name=PROJECT_NAME)
619627

620628
fname = str(tmp_path / "batch.json")
@@ -696,9 +704,10 @@ def mock_start_interrupt(self, *args, **kwargs):
696704

697705

698706
@responses.activate
699-
def test_async(mock_webapi, mock_job_status):
707+
@pytest.mark.parametrize("task_name", [TASK_NAME, None])
708+
def test_async(mock_webapi, mock_job_status, task_name):
700709
# monkeypatch.setattr("tidy3d.web.api.container.Job.status", property(lambda self: "success"))
701-
sims = {TASK_NAME: make_sim()}
710+
sims = {TASK_NAME: make_sim()} if task_name else [make_sim()]
702711
_ = run_async(sims, folder_name=PROJECT_NAME)
703712

704713

tidy3d/web/api/asynchronous.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313

1414
def run_async(
15-
simulations: dict[str, WorkflowType],
15+
simulations: Union[dict[str, WorkflowType], tuple[WorkflowType], list[WorkflowType]],
1616
folder_name: str = "default",
1717
path_dir: str = DEFAULT_DATA_DIR,
1818
callback_url: Optional[str] = None,
@@ -32,8 +32,8 @@ def run_async(
3232
3333
Parameters
3434
----------
35-
simulations : Dict[str, Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]]
36-
Mapping of task name to simulation.
35+
simulations : Union[Dict[str, Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]], tuple[Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]], list[Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]]]
36+
Mapping of task name to simulation or list of simulations.
3737
folder_name : str = "default"
3838
Name of folder to store each task on web UI.
3939
path_dir : str

tidy3d/web/api/autograd/autograd.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from tidy3d.web.api.asynchronous import DEFAULT_DATA_DIR
2929
from tidy3d.web.api.asynchronous import run_async as run_async_webapi
3030
from tidy3d.web.api.container import DEFAULT_DATA_PATH, Batch, BatchData, Job
31+
from tidy3d.web.api.tidy3d_stub import Tidy3dStub
3132
from tidy3d.web.api.webapi import run as run_webapi
3233
from tidy3d.web.core.s3utils import download_file, upload_file
3334
from tidy3d.web.core.types import PayType
@@ -96,7 +97,7 @@ def is_valid_for_autograd_async(simulations: dict[str, td.Simulation]) -> bool:
9697

9798
def run(
9899
simulation: WorkflowType,
99-
task_name: str,
100+
task_name: typing.Optional[str] = None,
100101
folder_name: str = "default",
101102
path: str = "simulation_data.hdf5",
102103
callback_url: typing.Optional[str] = None,
@@ -121,8 +122,8 @@ def run(
121122
----------
122123
simulation : Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`, :class:`.ModalComponentModeler`, :class:`.TerminalComponentModeler`]
123124
Simulation to upload to server.
124-
task_name : str
125-
Name of task.
125+
task_name : Optional[str] = None
126+
Name of task. If not provided, a default name will be generated.
126127
folder_name : str = "default"
127128
Name of folder to store task on web UI.
128129
path : str = "simulation_data.hdf5"
@@ -200,6 +201,10 @@ def run(
200201
if priority is not None and (priority < 1 or priority > 10):
201202
raise ValueError("Priority must be between '1' and '10' if specified.")
202203

204+
if task_name is None:
205+
stub = Tidy3dStub(simulation=simulation)
206+
task_name = stub.get_default_task_name()
207+
203208
# component modeler path: route autograd-valid modelers to local run
204209
from tidy3d.plugins.smatrix.component_modelers.types import ComponentModelerType
205210

@@ -261,7 +266,7 @@ def run(
261266

262267

263268
def run_async(
264-
simulations: dict[str, td.Simulation],
269+
simulations: typing.Union[dict[str, td.Simulation], tuple[td.Simulation], list[td.Simulation]],
265270
folder_name: str = "default",
266271
path_dir: str = DEFAULT_DATA_DIR,
267272
callback_url: typing.Optional[str] = None,
@@ -283,8 +288,8 @@ def run_async(
283288
284289
Parameters
285290
----------
286-
simulations : Dict[str, Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]]
287-
Mapping of task name to simulation.
291+
simulations : Union[Dict[str, Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]], tuple[Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]], list[Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]]]
292+
Mapping of task name to simulation or list of simulations.
288293
folder_name : str = "default"
289294
Name of folder to store each task on web UI.
290295
path_dir : str
@@ -329,6 +334,13 @@ def run_async(
329334
if priority is not None and (priority < 1 or priority > 10):
330335
raise ValueError("Priority must be between '1' and '10' if specified.")
331336

337+
if isinstance(simulations, (tuple, list)):
338+
sim_dict = {}
339+
for i, sim in enumerate(simulations, 1):
340+
task_name = Tidy3dStub(simulation=sim).get_default_task_name() + f"_{i}"
341+
sim_dict[task_name] = sim
342+
simulations = sim_dict
343+
332344
if is_valid_for_autograd_async(simulations):
333345
return _run_async(
334346
simulations=simulations,

tidy3d/web/api/connect_util.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import time
66
from functools import wraps
7+
from typing import Optional
78

89
from requests import ReadTimeout
910
from requests.exceptions import ConnectionError as ConnErr
@@ -12,13 +13,14 @@
1213

1314
from tidy3d.exceptions import WebError
1415
from tidy3d.log import log
15-
from tidy3d.web.common import CONNECTION_RETRY_TIME, REFRESH_TIME
16+
from tidy3d.web import common
17+
from tidy3d.web.common import REFRESH_TIME
1618

1719

18-
def wait_for_connection(decorated_fn=None, wait_time_sec: float = CONNECTION_RETRY_TIME):
20+
def wait_for_connection(decorated_fn=None, wait_time_sec: Optional[float] = None):
1921
"""Causes function to ignore connection errors and retry for ``wait_time_sec`` secs."""
2022

21-
def decorator(web_fn):
23+
def decorator(web_fn, wait_time_sec=wait_time_sec):
2224
"""Decorator returned by @wait_for_connection()"""
2325

2426
@wraps(web_fn)
@@ -27,12 +29,14 @@ def web_fn_wrapped(*args, **kwargs):
2729
time_start = time.time()
2830
warned_previously = False
2931

30-
while (time.time() - time_start) < wait_time_sec:
32+
timeout = common.CONNECTION_RETRY_TIME if wait_time_sec is None else wait_time_sec
33+
34+
while (time.time() - time_start) < timeout:
3135
try:
3236
return web_fn(*args, **kwargs)
3337
except (ConnErr, ConnectionError, NewConnectionError, ReadTimeout, JSONDecodeError):
3438
if not warned_previously:
35-
log.warning(f"No connection: Retrying for {wait_time_sec} seconds.")
39+
log.warning(f"No connection: Retrying for {timeout} seconds.")
3640
warned_previously = True
3741
time.sleep(REFRESH_TIME)
3842

@@ -41,7 +45,7 @@ def web_fn_wrapped(*args, **kwargs):
4145
return web_fn_wrapped
4246

4347
if decorated_fn:
44-
return decorator(decorated_fn)
48+
return decorator(decorated_fn, wait_time_sec=wait_time_sec)
4549

4650
return decorator
4751

tidy3d/web/api/container.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from abc import ABC
99
from collections.abc import Mapping
1010
from concurrent.futures import ThreadPoolExecutor
11-
from typing import Literal, Optional
11+
from typing import Literal, Optional, Union
1212

1313
import pydantic.v1 as pd
1414
from rich.progress import BarColumn, Progress, TaskProgressColumn, TextColumn, TimeElapsedColumn
@@ -20,6 +20,7 @@
2020
from tidy3d.exceptions import DataError
2121
from tidy3d.log import get_logging_console, log
2222
from tidy3d.web.api import webapi as web
23+
from tidy3d.web.api.tidy3d_stub import Tidy3dStub
2324
from tidy3d.web.core.constants import TaskId, TaskName
2425
from tidy3d.web.core.task_core import Folder
2526
from tidy3d.web.core.task_info import RunInfo, TaskInfo
@@ -151,7 +152,11 @@ class Job(WebContainer):
151152
discriminator="type",
152153
)
153154

154-
task_name: TaskName = pd.Field(..., title="Task Name", description="Unique name of the task.")
155+
task_name: TaskName = pd.Field(
156+
None,
157+
title="Task Name",
158+
description="Unique name of the task. Will be auto-generated if not provided.",
159+
)
155160

156161
folder_name: str = pd.Field(
157162
"default", title="Folder Name", description="Name of folder to store task on web UI."
@@ -421,6 +426,17 @@ def _check_path_dir(path: str) -> None:
421426
if len(parent_dir) > 0 and not os.path.exists(parent_dir):
422427
os.makedirs(parent_dir, exist_ok=True)
423428

429+
@pd.root_validator(pre=True)
430+
def set_task_name_if_none(cls, values):
431+
"""
432+
Auto-assign a task_name if user did not provide one.
433+
"""
434+
if values.get("task_name") is None:
435+
sim = values.get("simulation")
436+
stub = Tidy3dStub(simulation=sim)
437+
values["task_name"] = stub.get_default_task_name()
438+
return values
439+
424440

425441
class BatchData(Tidy3dBaseModel, Mapping):
426442
"""
@@ -532,7 +548,9 @@ class Batch(WebContainer):
532548
* `Inverse taper edge coupler <../../notebooks/EdgeCoupler.html>`_
533549
"""
534550

535-
simulations: dict[TaskName, annotate_type(WorkflowType)] = pd.Field(
551+
simulations: Union[
552+
dict[TaskName, annotate_type(WorkflowType)], tuple[annotate_type(WorkflowType)]
553+
] = pd.Field(
536554
...,
537555
title="Simulations",
538556
description="Mapping of task names to Simulations to run as a batch.",
@@ -663,12 +681,21 @@ def jobs(self) -> dict[TaskName, Job]:
663681
if self.jobs_cached is not None:
664682
return self.jobs_cached
665683

684+
if isinstance(self.simulations, tuple):
685+
simulations = {}
686+
for i, sim in enumerate(self.simulations, 1):
687+
stub = Tidy3dStub(simulation=sim)
688+
task_name = stub.get_default_task_name() + f"_{i}"
689+
simulations[task_name] = sim
690+
else:
691+
simulations = self.simulations
692+
666693
# the type of job to upload (to generalize to subclasses)
667694
JobType = self._job_type
668695
self_dict = self.dict()
669696

670697
jobs = {}
671-
for task_name, simulation in self.simulations.items():
698+
for task_name, simulation in simulations.items():
672699
job_kwargs = {}
673700

674701
for key in JobType._upload_fields:

0 commit comments

Comments
 (0)