Skip to content

Commit 3676c05

Browse files
committed
Using zipped solver data
1 parent fc2ba3e commit 3676c05

File tree

10 files changed

+149
-52
lines changed

10 files changed

+149
-52
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
### Fixed
1616

1717

18+
### Changed
19+
- All solver output is now compressed. However, it is automatically unpacked to the same `simulation_data.hdf5` by default when loading simulation data from the server.
20+
21+
### Fixed
22+
23+
1824
## [2.5.0] - 2023-12-13
1925

2026
### Added

tests/test_plugins/test_mode_solver.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def mock_remote_api(monkeypatch):
3838
def void(*args, **kwargs):
3939
return None
4040

41-
def mock_download(task_id, remote_path, to_file, *args, **kwargs):
41+
def mock_download(resource_id, remote_filename, to_file, *args, **kwargs):
4242
simulation = td.Simulation(
4343
size=SIM_SIZE,
4444
grid_spec=td.GridSpec(wavelength=1.0),
@@ -68,6 +68,7 @@ def mock_download(task_id, remote_path, to_file, *args, **kwargs):
6868
monkeypatch.setattr(httputil, "api_key", lambda: "api_key")
6969
monkeypatch.setattr(httputil, "get_version", lambda: td.version.__version__)
7070
monkeypatch.setattr("tidy3d.web.api.mode.upload_file", void)
71+
monkeypatch.setattr("tidy3d.web.api.mode.download_gz_file", mock_download)
7172
monkeypatch.setattr("tidy3d.web.api.mode.download_file", mock_download)
7273

7374
responses.add(

tests/test_web/test_tidy3d_task.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def mock_download(*args, **kwargs):
8989
to_file = kwargs["to_file"]
9090
sim.to_file(to_file)
9191

92-
monkeypatch.setattr("tidy3d.web.core.task_core.download_file", mock_download)
92+
monkeypatch.setattr("tidy3d.web.core.task_core.download_gz_file", mock_download)
9393

9494
responses.add(
9595
responses.GET,

tests/test_web/test_webapi.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ def _mock_download(*args, **kwargs):
225225
with open(file_path, "w") as f:
226226
f.write("0.3,5.7")
227227

228+
monkeypatch.setattr(f"{task_core_path}.download_gz_file", _mock_download)
228229
monkeypatch.setattr(f"{task_core_path}.download_file", _mock_download)
229230
download(TASK_ID, str(tmp_path / "web_test_tmp.json"))
230231
with open(str(tmp_path / "web_test_tmp.json")) as f:
@@ -356,7 +357,7 @@ def mock_download(*args, **kwargs):
356357
def get_str(*args, **kwargs):
357358
return sim.json().encode("utf-8")
358359

359-
monkeypatch.setattr(f"{task_core_path}.download_file", mock_download)
360+
monkeypatch.setattr(f"{task_core_path}.download_gz_file", mock_download)
360361
monkeypatch.setattr(f"{task_core_path}.read_simulation_from_hdf5", get_str)
361362

362363
fname_tmp = str(tmp_path / "web_test_tmp.json")

tests/test_web/test_webapi_heat.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pytest
44
import responses
55
from _pytest import monkeypatch
6+
from botocore.exceptions import ClientError
67

78
import tidy3d as td
89
from responses import matchers
@@ -170,6 +171,12 @@ def _mock_download(*args, **kwargs):
170171
with open(file_path, "w") as f:
171172
f.write("0.3,5.7")
172173

174+
def _mock_download_gz(*args, **kwargs):
175+
raise ClientError(
176+
error_response={"Error": {"Message": "File not found"}}, operation_name="download"
177+
)
178+
179+
monkeypatch.setattr(f"{task_core_path}.download_gz_file", _mock_download_gz)
173180
monkeypatch.setattr(f"{task_core_path}.download_file", _mock_download)
174181
download(TASK_ID, str(tmp_path / "web_test_tmp.json"))
175182
with open(str(tmp_path / "web_test_tmp.json"), "r") as f:
@@ -256,7 +263,7 @@ def mock_download(*args, **kwargs):
256263
def get_str(*args, **kwargs):
257264
return sim.json().encode("utf-8")
258265

259-
monkeypatch.setattr(f"{task_core_path}.download_file", mock_download)
266+
monkeypatch.setattr(f"{task_core_path}.download_gz_file", mock_download)
260267
monkeypatch.setattr(f"{task_core_path}.read_simulation_from_hdf5", get_str)
261268

262269
fname_tmp = str(tmp_path / "web_test_tmp.json")

tidy3d/version.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
"""Defines the front end version of tidy3d"""
2-
32
__version__ = "2.6.0rc1"

tidy3d/web/api/mode.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@
1010
import time
1111

1212
import pydantic.v1 as pydantic
13+
from botocore.exceptions import ClientError
14+
1315
from ...components.simulation import Simulation
1416
from ...components.data.monitor_data import ModeSolverData
1517
from ...exceptions import WebError
1618
from ...log import log, get_logging_console
19+
from ..core.core_config import get_logger_console
1720
from ..core.http_util import http
18-
from ..core.s3utils import download_file, upload_file
21+
from ..core.s3utils import download_file, download_gz_file, upload_file
1922
from ..core.task_core import Folder
2023
from ..core.types import ResourceLifecycle, Submittable
2124

@@ -31,6 +34,7 @@
3134

3235
MODESOLVER_LOG = "output/result.log"
3336
MODESOLVER_RESULT = "output/result.hdf5"
37+
MODESOLVER_RESULT_GZ = "output/mode_solver_data.hdf5.gz"
3438

3539

3640
def run(
@@ -424,18 +428,36 @@ def get_result(
424428
:class:`.ModeSolverData`
425429
Mode solver data with the calculated results.
426430
"""
431+
432+
file = None
427433
try:
428-
download_file(
429-
self.solver_id,
430-
MODESOLVER_RESULT,
434+
file = download_gz_file(
435+
resource_id=self.task_id,
436+
remote_filename=MODESOLVER_RESULT_GZ,
431437
to_file=to_file,
432438
verbose=verbose,
433439
progress_callback=progress_callback,
434440
)
435-
except Exception:
436-
raise WebError(
437-
f"Failed to download file '{MODESOLVER_RESULT}' from server. Please confirm that the task was successful."
438-
)
441+
except ClientError:
442+
if verbose:
443+
console = get_logger_console()
444+
console.log(f"Unable to download '{MODESOLVER_RESULT}'.")
445+
446+
if not file:
447+
try:
448+
file = download_file(
449+
resource_id=self.task_id,
450+
remote_filename=MODESOLVER_RESULT,
451+
to_file=to_file,
452+
verbose=verbose,
453+
progress_callback=progress_callback,
454+
)
455+
except Exception as e:
456+
raise WebError(
457+
"Failed to download the simulation data file from the server. "
458+
"Please confirm that the task was successfully run."
459+
) from e
460+
439461
data = ModeSolverData.from_hdf5(to_file)
440462
data = data.copy(
441463
update={"monitor": self.mode_solver.to_mode_solver_monitor(name=MODE_MONITOR_NAME)}

tidy3d/web/core/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
SIMULATION_JSON = "simulation.json"
2323
SIMULATION_DATA_HDF5 = "output/monitor_data.hdf5"
24+
SIMULATION_DATA_HDF5_GZ = "output/simulation_data.hdf5.gz"
2425
RUNNING_INFO = "output/solver_progress.csv"
2526
SIM_LOG_FILE = "output/tidy3d.log"
2627
SIM_FILE_HDF5 = "simulation.hdf5"

tidy3d/web/core/s3utils.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"""handles filesystem, storage
22
"""
3+
import os
4+
import tempfile
5+
36
import pathlib
47
import urllib
58
from datetime import datetime
@@ -12,8 +15,10 @@
1215
from rich.progress import TextColumn, Progress, BarColumn, DownloadColumn
1316
from rich.progress import TransferSpeedColumn, TimeRemainingColumn
1417
from .http_util import http
18+
from .file_util import extract_gzip_file
1519
from .environment import Env
1620
from .core_config import get_logger_console
21+
from .exceptions import WebError
1722

1823

1924
class _UserCredential(BaseModel):
@@ -283,8 +288,8 @@ def download_file(
283288
----------
284289
resource_id : str
285290
The resource id, e.g. task id.
286-
content : str
287-
The content of the file
291+
remote_filename : str
292+
Path to the remote file.
288293
to_file : str = None
289294
Local filename to save to, if not specified, use the remote_filename.
290295
verbose : bool = True
@@ -346,3 +351,58 @@ def _callback(bytes_in_chunk):
346351
_download(lambda bytes_in_chunk: None)
347352

348353
return to_file
354+
355+
356+
def download_gz_file(
357+
resource_id: str,
358+
remote_filename: str,
359+
to_file: str = None,
360+
verbose: bool = True,
361+
progress_callback: Callable[[float], None] = None,
362+
) -> pathlib.Path:
363+
"""Download a ``.gz`` file and unzip it into ``to_file``, unless ``to_file`` itself
364+
ends in .gz
365+
366+
Parameters
367+
----------
368+
resource_id : str
369+
The resource id, e.g. task id.
370+
remote_filename : str
371+
Path to the remote file.
372+
to_file : str = None
373+
Local filename to save to, if not specified, use the remote_filename.
374+
verbose : bool = True
375+
Whether to display a progressbar for the upload
376+
progress_callback : Callable[[float], None] = None
377+
User-supplied callback function with ``bytes_in_chunk`` as argument.
378+
"""
379+
380+
# If to_file is a gzip extension, just download
381+
if to_file.lower().endswith(".gz"):
382+
return download_file(
383+
resource_id,
384+
remote_filename,
385+
to_file=to_file,
386+
verbose=verbose,
387+
progress_callback=progress_callback,
388+
)
389+
390+
# Otherwise, download and unzip
391+
# The tempfile is set as ``hdf5.gz`` so that the mock download in the webapi tests works
392+
tmp_file, tmp_file_path = tempfile.mkstemp(".hdf5.gz")
393+
os.close(tmp_file)
394+
try:
395+
download_file(
396+
resource_id,
397+
remote_filename,
398+
to_file=tmp_file_path,
399+
verbose=verbose,
400+
progress_callback=progress_callback,
401+
)
402+
if os.path.exists(tmp_file_path):
403+
extract_gzip_file(tmp_file_path, to_file)
404+
else:
405+
raise WebError(f"Failed to download and extract '{remote_filename}'.")
406+
finally:
407+
os.unlink(tmp_file_path)
408+
return to_file

tidy3d/web/core/task_core.py

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,21 @@
88
from typing import List, Optional, Callable, Tuple
99
import pydantic.v1 as pd
1010
from pydantic.v1 import Extra, Field, parse_obj_as
11+
from botocore.exceptions import ClientError
1112

1213
from . import http_util
1314
from .core_config import get_logger_console
1415
from .exceptions import WebError
1516

1617
from .cache import FOLDER_CACHE
1718
from .http_util import http
18-
from .s3utils import download_file, upload_file
19+
from .s3utils import download_file, download_gz_file, upload_file
1920
from .stub import TaskStub
2021
from .types import Queryable, ResourceLifecycle, Submittable
2122
from .types import Tidy3DResource
2223

23-
24-
from .constants import SIM_FILE_HDF5_GZ, SIMULATION_DATA_HDF5, SIM_LOG_FILE
25-
from .file_util import extract_gzip_file, read_simulation_from_hdf5
24+
from .constants import SIM_FILE_HDF5_GZ, SIMULATION_DATA_HDF5, SIMULATION_DATA_HDF5_GZ, SIM_LOG_FILE
25+
from .file_util import read_simulation_from_hdf5
2626

2727

2828
class Folder(Tidy3DResource, Queryable, extra=Extra.allow):
@@ -453,7 +453,7 @@ def estimate_cost(self, solver_version=None) -> float:
453453
def get_sim_data_hdf5(
454454
self, to_file: str, verbose: bool = True, progress_callback: Callable[[float], None] = None
455455
) -> pathlib.Path:
456-
"""Get output/monitor_data.hdf5 file from Server.
456+
"""Get simulation data file from Server.
457457
458458
Parameters
459459
----------
@@ -472,18 +472,36 @@ def get_sim_data_hdf5(
472472
if not self.task_id:
473473
raise WebError("Expected field 'task_id' is unset.")
474474

475+
file = None
475476
try:
476-
return download_file(
477-
self.task_id,
478-
SIMULATION_DATA_HDF5,
477+
file = download_gz_file(
478+
resource_id=self.task_id,
479+
remote_filename=SIMULATION_DATA_HDF5_GZ,
479480
to_file=to_file,
480481
verbose=verbose,
481482
progress_callback=progress_callback,
482483
)
483-
except Exception:
484-
raise WebError(
485-
f"Failed to download file '{SIMULATION_DATA_HDF5}' from server. Please confirm that the task was successful."
486-
)
484+
except ClientError:
485+
if verbose:
486+
console = get_logger_console()
487+
console.log(f"Unable to download '{SIMULATION_DATA_HDF5_GZ}'.")
488+
489+
if not file:
490+
try:
491+
file = download_file(
492+
resource_id=self.task_id,
493+
remote_filename=SIMULATION_DATA_HDF5,
494+
to_file=to_file,
495+
verbose=verbose,
496+
progress_callback=progress_callback,
497+
)
498+
except Exception as e:
499+
raise WebError(
500+
"Failed to download the simulation data file from the server. "
501+
"Please confirm that the task was successfully run."
502+
) from e
503+
504+
return file
487505

488506
def get_simulation_hdf5(
489507
self, to_file: str, verbose: bool = True, progress_callback: Callable[[float], None] = None
@@ -507,31 +525,13 @@ def get_simulation_hdf5(
507525
if not self.task_id:
508526
raise WebError("Expected field 'task_id' is unset.")
509527

510-
if to_file.lower().endswith(".gz"):
511-
download_file(
512-
self.task_id,
513-
SIM_FILE_HDF5_GZ,
514-
to_file=to_file,
515-
verbose=verbose,
516-
progress_callback=progress_callback,
517-
)
518-
else:
519-
hdf5_gz_file, hdf5_gz_file_path = tempfile.mkstemp(".hdf5.gz")
520-
os.close(hdf5_gz_file)
521-
try:
522-
download_file(
523-
self.task_id,
524-
SIM_FILE_HDF5_GZ,
525-
to_file=hdf5_gz_file_path,
526-
verbose=verbose,
527-
progress_callback=progress_callback,
528-
)
529-
if os.path.exists(hdf5_gz_file_path):
530-
extract_gzip_file(hdf5_gz_file_path, to_file)
531-
else:
532-
raise WebError("Failed to download simulation.hdf5")
533-
finally:
534-
os.unlink(hdf5_gz_file_path)
528+
return download_gz_file(
529+
resource_id=self.task_id,
530+
remote_filename=SIM_FILE_HDF5_GZ,
531+
to_file=to_file,
532+
verbose=verbose,
533+
progress_callback=progress_callback,
534+
)
535535

536536
def get_running_info(self) -> Tuple[float, float]:
537537
"""Gets the % done and field_decay for a running task.

0 commit comments

Comments
 (0)