Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/api/microwave/component_modeler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ Alternatively, use the ``tidy3d.web.run()`` method to perform all of the above i
# Upload, run simulation, and download data
my_tcm_data = tidy3d.web.run(my_tcm, task_name='my_task_name', path='my/local/download/path')

If ``path`` is omitted, the component modeler results download to ``cm_data.hdf5`` in the working
directory by default.

To get the S-matrix from the results, use the ``smatrix()`` method of the :class:`.TerminalComponentModelerData` object.

.. code-block:: python
Expand Down
21 changes: 11 additions & 10 deletions tidy3d/web/api/autograd/autograd.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from tidy3d.web.api.asynchronous import run_async as run_async_webapi
from tidy3d.web.api.container import BatchData
from tidy3d.web.api.tidy3d_stub import Tidy3dStub
from tidy3d.web.api.webapi import load, restore_simulation_if_cached
from tidy3d.web.api.webapi import default_data_filename, load, restore_simulation_if_cached
from tidy3d.web.api.webapi import run as run_webapi
from tidy3d.web.core.types import PayType

Expand Down Expand Up @@ -104,7 +104,7 @@ def run(
simulation: WorkflowType,
task_name: typing.Optional[str] = None,
folder_name: str = "default",
path: PathLike = "simulation_data.hdf5",
path: typing.Optional[PathLike] = None,
callback_url: typing.Optional[str] = None,
verbose: bool = True,
progress_callback_upload: typing.Optional[typing.Callable[[float], None]] = None,
Expand Down Expand Up @@ -132,8 +132,9 @@ def run(
Name of task. If not provided, a default name will be generated.
folder_name : str = "default"
Name of folder to store task on web UI.
path : PathLike = "simulation_data.hdf5"
Path to download results file (.hdf5), including filename.
path : Optional[PathLike] = None
Path to download results file (.hdf5), including filename. When ``None``, a task-type-
specific default filename is used.
callback_url : str = None
Http PUT url to receive simulation finish event. The body content is a json file with
fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.
Expand Down Expand Up @@ -220,20 +221,20 @@ def run(

lazy = False if lazy is None else bool(lazy)

stub = Tidy3dStub(simulation=simulation)
if task_name is None:
stub = Tidy3dStub(simulation=simulation)
task_name = stub.get_default_task_name()

# component modeler path: route autograd-valid modelers to local run
from tidy3d.plugins.smatrix.component_modelers.types import ComponentModelerType

path = Path(path)
resolved_path = Path(path) if path is not None else Path(default_data_filename(stub.get_type()))

if isinstance(simulation, typing.get_args(ComponentModelerType)):
if any(is_valid_for_autograd(s) for s in simulation.sim_dict.values()):
from tidy3d.plugins.smatrix import run as smatrix_run

path_dir = path.parent
path_dir = resolved_path.parent
return smatrix_run._run_local(
simulation,
path_dir=path_dir,
Expand All @@ -252,7 +253,7 @@ def run(
simulation=simulation,
task_name=task_name,
folder_name=folder_name,
path=path,
path=resolved_path,
callback_url=callback_url,
verbose=verbose,
progress_callback_upload=progress_callback_upload,
Expand All @@ -272,7 +273,7 @@ def run(
simulation=simulation,
task_name=task_name,
folder_name=folder_name,
path=path,
path=resolved_path,
callback_url=callback_url,
verbose=verbose,
progress_callback_upload=progress_callback_upload,
Expand Down Expand Up @@ -563,7 +564,7 @@ def _run_primitive(
)
else:
sim_original = sim_original.updated_copy(simulation_type="autograd_fwd", deep=False)
restored_path, task_id_fwd = restore_simulation_if_cached(
restored_path, task_id_fwd, _ = restore_simulation_if_cached(
simulation=sim_original,
path=run_kwargs.get("path", None),
reduce_simulation=run_kwargs.get("reduce_simulation", "auto"),
Expand Down
8 changes: 6 additions & 2 deletions tidy3d/web/api/autograd/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import Any

import tidy3d as td
from tidy3d.web.api.container import DEFAULT_DATA_PATH, Batch, Job
from tidy3d.web.api.container import Batch, Job

from .io_utils import get_vjp_traced_fields, upload_sim_fields_keys

Expand All @@ -27,7 +27,11 @@ def _run_tidy3d(
if job.simulation_type == "autograd_fwd":
verbose = run_kwargs.get("verbose", False)
upload_sim_fields_keys(run_kwargs["sim_fields_keys"], task_id=job.task_id, verbose=verbose)
path = Path(run_kwargs.get("path", DEFAULT_DATA_PATH))
path_arg = run_kwargs.get("path")
if path_arg is None:
path = Job._resolve_output_path(None, job._task_type_hint())
else:
path = Path(path_arg)
priority = run_kwargs.get("priority")
if task_name.endswith("_adjoint"):
suffixes = "".join(path.suffixes)
Expand Down
65 changes: 48 additions & 17 deletions tidy3d/web/api/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ class Job(WebContainer):

_stash_path: Optional[str] = PrivateAttr(default=None)
_cached_task_id: Optional[TaskId] = PrivateAttr(default=None)
_cached_task_type: Optional[str] = PrivateAttr(default=None)

@cached_property
def _stash_path_for_job(self) -> str:
Expand All @@ -267,6 +268,20 @@ def _stash_path_for_job(self) -> str:
stash_dir.mkdir(parents=True, exist_ok=True)
return str(Path(stash_dir / f"{uuid.uuid4()}.hdf5"))

def _task_type_hint(self) -> Optional[str]:
"""Best-effort task type derived from the simulation for default filename selection."""

try:
return Tidy3dStub(simulation=self.simulation).get_type()
except Exception:
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception handler catches all exceptions and returns None, which may hide legitimate errors like import errors or attribute errors. Consider catching only the specific exception that indicates the task type cannot be determined (e.g., TypeError, AttributeError) to avoid masking unexpected errors.

Suggested change
except Exception:
except (AttributeError, TypeError):

Copilot uses AI. Check for mistakes.
return None

@staticmethod
def _resolve_output_path(path: Optional[PathLike], task_type_hint: Optional[str]) -> Path:
"""Resolve a user-provided or default output path."""

return Path(path) if path is not None else Path(web.default_data_filename(task_type_hint))

def _materialize_from_stash(self, dst_path: os.PathLike) -> None:
"""Atomic copy from stash to requested path."""
tmp = str(dst_path) + ".part"
Expand Down Expand Up @@ -300,15 +315,16 @@ def to_file(self, fname: PathLike) -> None:

def run(
self,
path: PathLike = DEFAULT_DATA_PATH,
path: Optional[PathLike] = None,
priority: Optional[int] = None,
) -> WorkflowDataType:
"""Run :class:`Job` all the way through and return data.

Parameters
----------
path : PathLike = "./simulation_data.hdf5"
Path to download results file (.hdf5), including filename.
path : Optional[PathLike] = None
Path to download results file (.hdf5), including filename. When ``None``, a task-type-
specific default filename is used.
priority: int = None
Priority of the simulation in the Virtual GPU (vGPU) queue (1 = lowest, 10 = highest).
It affects only simulations from vGPU licenses and does not impact simulations using FlexCredits.
Expand All @@ -317,7 +333,8 @@ def run(
:class:`WorkflowDataType`
Object containing simulation results.
"""
self._check_path_dir(path=path)
if path is not None:
self._check_path_dir(path=path)

loaded_from_cache = self.load_if_cached
if not loaded_from_cache:
Expand All @@ -337,15 +354,17 @@ def load_if_cached(self) -> bool:
# use temporary path as final destination is unknown
stash_path = self._stash_path_for_job

restored, cached_task_id = restore_simulation_if_cached(
restored, cached_task_id, cached_task_type = restore_simulation_if_cached(
simulation=self.simulation,
path=stash_path,
reduce_simulation=self.reduce_simulation,
verbose=self.verbose,
)
self._cached_task_id = cached_task_id
self._cached_task_type = cached_task_type

if restored is None:
self._cached_task_type = None
return False

self._stash_path = stash_path
Expand Down Expand Up @@ -437,44 +456,56 @@ def monitor(self) -> None:
return
web.monitor(self.task_id, verbose=self.verbose)

def download(self, path: PathLike = DEFAULT_DATA_PATH) -> None:
def download(self, path: Optional[PathLike] = None) -> None:
"""Download results of simulation.

Parameters
----------
path : PathLike = "./simulation_data.hdf5"
Path to download data as ``.hdf5`` file (including filename).
path : Optional[PathLike] = None
Path to download data as ``.hdf5`` file (including filename). When ``None``, a task-
type-specific default filename is used.

Note
----
To load the data after download, use :meth:`Job.load`.
"""
if self.load_if_cached:
self._materialize_from_stash(path)
target_path = self._resolve_output_path(path, self._cached_task_type)
self._check_path_dir(path=target_path)
self._materialize_from_stash(target_path)
return
self._check_path_dir(path=path)
if path is not None:
self._check_path_dir(path=path)
web.download(task_id=self.task_id, path=path, verbose=self.verbose)

def load(self, path: PathLike = DEFAULT_DATA_PATH) -> WorkflowDataType:
def load(self, path: Optional[PathLike] = None) -> WorkflowDataType:
"""Download job results and load them into a data object.

Parameters
----------
path : PathLike = "./simulation_data.hdf5"
Path to download data as ``.hdf5`` file (including filename).
path : Optional[PathLike] = None
Path to download data as ``.hdf5`` file (including filename). When ``None``, a task-
type-specific default filename is used.

Returns
-------
Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`]
Object containing simulation results.
"""
self._check_path_dir(path=path)
resolved_path = Path(path) if path is not None else None
if self.load_if_cached:
self._materialize_from_stash(path)
task_type_hint = self._cached_task_type or self._task_type_hint()
resolved_path = self._resolve_output_path(resolved_path, task_type_hint)
self._check_path_dir(path=resolved_path)
self._materialize_from_stash(resolved_path)
else:
if resolved_path is None:
resolved_path = self._resolve_output_path(None, self._task_type_hint())
self._check_path_dir(path=resolved_path)

data = web.load(
task_id=None if self.load_if_cached else self.task_id,
path=path,
path=resolved_path,
verbose=self.verbose,
lazy=self.lazy,
)
Expand All @@ -484,7 +515,7 @@ def load(self, path: PathLike = DEFAULT_DATA_PATH) -> WorkflowDataType:
self.task_id,
self.simulation,
data,
path,
resolved_path,
)
self.simulation._patch_data(data=data)

Expand Down
Loading