Skip to content

Commit 8ffac00

Browse files
committed
Merge branch 'develop' into momchil/merge_develop_in_26
2 parents ca6389b + 366c667 commit 8ffac00

34 files changed

+405
-142
lines changed

CHANGELOG.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
- `poetry` based installation. Removal of `setup.py` and `requirements.txt`.
1919
- Upgrade to sphinx 6 for the documentation build, and change of theme.
2020
- Remote mode solver web api automatically reduces the associated `Simulation` object to the mode solver plane before uploading it to server.
21+
- 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.
22+
- Internal refactor of `adjoint` plugin to separate `jax`-traced fields from regular `tidy3d` fields.
2123

2224
### Fixed
2325

26+
## [2.5.1] - 2024-01-08
27+
28+
### Added
29+
- `ModeData.dispersion` and `ModeSolverData.dispersion` are calculated together with the group index.
30+
- String matching feature `contains_str` to `assert_log_level` testing utility.
31+
- Warning in automatic grid generation if a structure has a non-zero size along a given direction that is too small compared to a single mesh step.
32+
- `assert_log_level` adds string matching with `contains_str` and ensures no higher log level recorded than expected.
33+
- `AssertLogLevel` context manager for testing log level and automatically clearing capture.
34+
- More robust validation for boundary conditions and symmetry in 1D and 2D simulations.
2435

2536
### Changed
26-
- 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.
27-
- Internal refactor of `adjoint` plugin to separate `jax`-traced fields from regular `tidy3d` fields.
37+
- `jax` and `jaxlib` versions bumped to `0.4.*`.
38+
- Improved and unified warning message for validation failure of dependency fields in validators.
2839

2940
### Fixed
30-
41+
- Error in automatic grid generation in specific cases with multiple thin structures.
3142

3243
## [2.5.0] - 2023-12-13
3344

@@ -1063,7 +1074,8 @@ which fields are to be projected is now determined automatically based on the me
10631074
- Job and Batch classes for better simulation handling (eventually to fully replace webapi functions).
10641075
- A large number of small improvements and bug fixes.
10651076

1066-
[Unreleased]: https://github.com/flexcompute/tidy3d/compare/v2.5.0...develop
1077+
[Unreleased]: https://github.com/flexcompute/tidy3d/compare/v2.5.1...develop
1078+
[2.5.1]: https://github.com/flexcompute/tidy3d/compare/v2.5.0...v2.5.1
10671079
[2.5.0]: https://github.com/flexcompute/tidy3d/compare/v2.4.3...v2.5.0
10681080
[2.4.3]: https://github.com/flexcompute/tidy3d/compare/v2.4.2...v2.4.3
10691081
[2.4.2]: https://github.com/flexcompute/tidy3d/compare/v2.4.1...v2.4.2

tests/test_components/test_meshgenerate.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55

66
import tidy3d as td
77
from tidy3d.constants import fp_eps
8-
98
from tidy3d.components.grid.mesher import GradedMesher
109

10+
from ..utils import assert_log_level, log_capture
11+
1112
np.random.seed(4)
1213

1314
MESHER = GradedMesher()
@@ -648,6 +649,52 @@ def test_mesher_timeout():
648649
_ = sim.grid
649650

650651

652+
def test_small_structure_size(log_capture):
653+
"""Test that a warning is raised if a structure size is small during the auto meshing"""
654+
box_size = 0.03
655+
medium = td.Medium(permittivity=4)
656+
box = td.Structure(geometry=td.Box(size=(box_size, td.inf, td.inf)), medium=medium)
657+
src = td.UniformCurrentSource(
658+
source_time=td.GaussianPulse(freq0=2e14, fwidth=1e13),
659+
size=(0, 0, 0),
660+
polarization="Ex",
661+
)
662+
sim = td.Simulation(
663+
size=(10, 10, 10),
664+
sources=[src],
665+
structures=[box],
666+
run_time=1e-12,
667+
grid_spec=td.GridSpec.auto(wavelength=1),
668+
)
669+
670+
# Warning raised as structure is too thin
671+
assert_log_level(log_capture, "WARNING")
672+
673+
# Warning not raised if structure is higher index
674+
log_capture.clear()
675+
box2 = box.updated_copy(medium=td.Medium(permittivity=300))
676+
sim2 = sim.updated_copy(structures=[box2])
677+
assert len(log_capture) == 0
678+
679+
# Warning not raised if structure is covered by an override structure
680+
log_capture.clear()
681+
override = td.MeshOverrideStructure(geometry=box.geometry, dl=(box_size, td.inf, td.inf))
682+
sim3 = sim.updated_copy(grid_spec=sim.grid_spec.updated_copy(override_structures=[override]))
683+
assert len(log_capture) == 0
684+
# Also check that the structure boundaries are in the grid
685+
ind_mid_cell = int(sim3.grid.num_cells[0] // 2)
686+
bounds = [-box_size / 2, box_size / 2]
687+
assert np.allclose(bounds, sim3.grid.boundaries.x[ind_mid_cell : ind_mid_cell + 2])
688+
689+
# Test that the error coming from two thin slabs on top of each other is resolved
690+
log_capture.clear()
691+
box3 = td.Structure(
692+
geometry=td.Box(center=(box_size, 0, 0), size=(box_size, td.inf, td.inf)), medium=medium
693+
)
694+
sim4 = sim.updated_copy(structures=[box3, box])
695+
assert_log_level(log_capture, "WARNING")
696+
697+
651698
def test_shapely_strtree_warnings():
652699
with warnings.catch_warnings():
653700
warnings.simplefilter("error")

tests/test_components/test_simulation.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -500,11 +500,11 @@ def test_validate_components_none():
500500
assert SIM._source_homogeneous_isotropic(val=None, values=SIM.dict()) is None
501501

502502

503-
def test_sources_edge_case_validation():
503+
def test_sources_edge_case_validation(log_capture):
504504
values = SIM.dict()
505505
values.pop("sources")
506-
with pytest.raises(ValidationError):
507-
SIM._warn_monitor_simulation_frequency_range(val="test", values=values)
506+
SIM._warn_monitor_simulation_frequency_range(val="test", values=values)
507+
assert_log_level(log_capture, "WARNING")
508508

509509

510510
def test_validate_size_run_time(monkeypatch):
@@ -1826,6 +1826,27 @@ def test_error_large_monitors():
18261826
s.validate_pre_upload()
18271827

18281828

1829+
@pytest.mark.parametrize("start, log_level", [(1e-12, None), (1, "WARNING")])
1830+
def test_warn_time_monitor_outside_run_time(log_capture, start, log_level):
1831+
"""Make sure we get a warning if the mode monitor grid is too large."""
1832+
1833+
sim = td.Simulation(
1834+
size=(2.0, 2.0, 2.0),
1835+
grid_spec=td.GridSpec.uniform(dl=0.1),
1836+
run_time=1e-12,
1837+
sources=[
1838+
td.ModeSource(
1839+
size=(0.1, 0.1, 0),
1840+
direction="+",
1841+
source_time=td.GaussianPulse(freq0=1e12, fwidth=0.1e12),
1842+
)
1843+
],
1844+
monitors=[td.FieldTimeMonitor(size=(td.inf, 0, td.inf), start=start, name="test")],
1845+
)
1846+
with AssertLogLevel(log_capture, log_level_expected=log_level, contains_str="start time"):
1847+
sim.validate_pre_upload()
1848+
1849+
18291850
def test_dt():
18301851
"""make sure dt is reduced when there is a medium with eps_inf < 1."""
18311852
sim = td.Simulation(

tests/test_components/test_source.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import tidy3d as td
77
from tidy3d.exceptions import SetupError
88
from tidy3d.components.source import DirectionalSource, CHEB_GRID_WIDTH
9-
from ..utils import assert_log_level, log_capture
9+
from ..utils import assert_log_level, log_capture, AssertLogLevel
1010

1111
ST = td.GaussianPulse(freq0=2e14, fwidth=1e14)
1212
S = td.PointDipole(source_time=ST, polarization="Ex")
@@ -309,12 +309,12 @@ def test_custom_source_time(log_capture):
309309
assert_log_level(log_capture, None)
310310

311311
# test normalization warning
312-
sim = sim.updated_copy(normalize_index=0)
313-
assert_log_level(log_capture, "WARNING")
314-
log_capture.clear()
315-
source = source.updated_copy(source_time=td.ContinuousWave(freq0=freq0, fwidth=0.1e12))
316-
sim = sim.updated_copy(sources=[source])
317-
assert_log_level(log_capture, "WARNING")
312+
with AssertLogLevel(log_capture, "WARNING"):
313+
sim = sim.updated_copy(normalize_index=0)
314+
315+
with AssertLogLevel(log_capture, "WARNING"):
316+
source = source.updated_copy(source_time=td.ContinuousWave(freq0=freq0, fwidth=0.1e12))
317+
sim = sim.updated_copy(sources=[source])
318318

319319
# test single value validation error
320320
with pytest.raises(pydantic.ValidationError):

tests/test_package/test_log.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from tidy3d.exceptions import Tidy3dError
1010
from tidy3d.log import DEFAULT_LEVEL, _get_level_int, set_logging_level
1111

12+
from ..utils import assert_log_level, log_capture, AssertLogLevel
13+
1214

1315
def test_log():
1416
td.log.debug("debug test")
@@ -268,3 +270,29 @@ def test_log_suppression():
268270
assert td.log._counts is None
269271

270272
td.config.log_suppression = True
273+
274+
275+
def test_assert_log_level(log_capture):
276+
"""Test features of the assert_log_level"""
277+
278+
# log was captured
279+
with AssertLogLevel(log_capture, "WARNING", contains_str="ABC"):
280+
td.log.warning("ABC")
281+
282+
# string was not matched (manually clear because not sure any other way using context manager)
283+
td.log.warning("ABC")
284+
with pytest.raises(Exception):
285+
assert_log_level(log_capture, "WARNING", contains_str="DEF")
286+
log_capture.clear()
287+
288+
# string was matched at the wrong level
289+
td.log.info("ABC")
290+
with pytest.raises(Exception):
291+
assert_log_level(log_capture, "WARNING", contains_str="ABC")
292+
log_capture.clear()
293+
294+
# log exceeds expected level
295+
td.log.warning("ABC")
296+
with pytest.raises(Exception):
297+
assert_log_level(log_capture, "INFO")
298+
log_capture.clear()

tests/test_plugins/test_adjoint.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ def make_sim(
325325
output_monitors=(output_mnt1, output_mnt2, output_mnt3, output_mnt4),
326326
sources=[src],
327327
boundary_spec=td.BoundarySpec.pml(x=False, y=False, z=False),
328-
symmetry=(0, 1, -1),
328+
symmetry=(1, 0, -1),
329329
)
330330

331331
return sim

tidy3d/components/apodization.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pydantic.v1 as pd
44
import numpy as np
55

6-
from .base import Tidy3dBaseModel
6+
from .base import Tidy3dBaseModel, skip_if_fields_missing
77
from ..constants import SECOND
88
from ..exceptions import SetupError
99
from .types import ArrayFloat1D, Ax
@@ -40,6 +40,7 @@ class ApodizationSpec(Tidy3dBaseModel):
4040
)
4141

4242
@pd.validator("end", always=True, allow_reuse=True)
43+
@skip_if_fields_missing(["start"])
4344
def end_greater_than_start(cls, val, values):
4445
"""Ensure end is greater than or equal to start."""
4546
start = values.get("start")
@@ -48,6 +49,7 @@ def end_greater_than_start(cls, val, values):
4849
return val
4950

5051
@pd.validator("width", always=True, allow_reuse=True)
52+
@skip_if_fields_missing(["start", "end"])
5153
def width_provided(cls, val, values):
5254
"""Check that width is provided if either start or end apodization is requested."""
5355
start = values.get("start")

tidy3d/components/base.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,28 @@ def _get_valid_extension(fname: str) -> str:
8686
)
8787

8888

89+
def skip_if_fields_missing(fields: List[str]):
90+
"""Decorate ``validator`` to check that other fields have passed validation."""
91+
92+
def actual_decorator(validator):
93+
@wraps(validator)
94+
def _validator(cls, val, values):
95+
"""New validator function."""
96+
for field in fields:
97+
if field not in values:
98+
log.warning(
99+
f"Could not execute validator '{validator.__name__}' because field "
100+
f"'{field}' failed validation."
101+
)
102+
return val
103+
104+
return validator(cls, val, values)
105+
106+
return _validator
107+
108+
return actual_decorator
109+
110+
89111
class Tidy3dBaseModel(pydantic.BaseModel):
90112
"""Base pydantic model that all Tidy3d components inherit from.
91113
Defines configuration for handling data structures

tidy3d/components/base_sim/data/sim_data.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from ..simulation import AbstractSimulation
1313
from ...data.dataset import UnstructuredGridDatasetType
1414
from ...base import Tidy3dBaseModel
15+
from ...base import skip_if_fields_missing
1516
from ...types import FieldVal
1617
from ....exceptions import DataError, Tidy3dKeyError, ValidationError
1718

@@ -51,13 +52,13 @@ def monitor_data(self) -> Dict[str, AbstractMonitorData]:
5152
return {monitor_data.monitor.name: monitor_data for monitor_data in self.data}
5253

5354
@pd.validator("data", always=True)
55+
@skip_if_fields_missing(["simulation"])
5456
def data_monitors_match_sim(cls, val, values):
5557
"""Ensure each :class:`AbstractMonitorData` in ``.data`` corresponds to a monitor in
5658
``.simulation``.
5759
"""
5860
sim = values.get("simulation")
59-
if sim is None:
60-
raise ValidationError("'.simulation' failed validation, can't validate data.")
61+
6162
for mnt_data in val:
6263
try:
6364
monitor_name = mnt_data.monitor.name
@@ -70,14 +71,11 @@ def data_monitors_match_sim(cls, val, values):
7071
return val
7172

7273
@pd.validator("data", always=True)
74+
@skip_if_fields_missing(["simulation"])
7375
def validate_no_ambiguity(cls, val, values):
7476
"""Ensure all :class:`AbstractMonitorData` entries in ``.data`` correspond to different
7577
monitors in ``.simulation``.
7678
"""
77-
sim = values.get("simulation")
78-
if sim is None:
79-
raise ValidationError("'.simulation' failed validation, can't validate data.")
80-
8179
names = [mnt_data.monitor.name for mnt_data in val]
8280

8381
if len(set(names)) != len(names):

tidy3d/components/base_sim/simulation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from .monitor import AbstractMonitor
1111

12-
from ..base import cached_property
12+
from ..base import cached_property, skip_if_fields_missing
1313
from ..validators import assert_unique_names, assert_objects_in_sim_bounds
1414
from ..geometry.base import Box
1515
from ..types import Ax, Bound, Axis, Symmetry, TYPE_TAG_STR
@@ -196,6 +196,7 @@ class AbstractSimulation(Box, ABC):
196196
_structures_in_bounds = assert_objects_in_sim_bounds("structures", error=False)
197197

198198
@pd.validator("structures", always=True)
199+
@skip_if_fields_missing(["size", "center"])
199200
def _structures_not_at_edges(cls, val, values):
200201
"""Warn if any structures lie at the simulation boundaries."""
201202

0 commit comments

Comments
 (0)