Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added configurable local simulation result caching with checksum validation, eviction limits, and per-call overrides across `web.run`, `web.load`, and job workflows.
- Added `DirectivityMonitorSpec` for automated creation and configuration of directivity radiation monitors in `TerminalComponentModeler`.
- Added multimode support to `WavePort` in the smatrix plugin, allowing multiple modes to be analyzed per port.
- Added support for `.lydrc` files for design rule checking in the `klayout` plugin.

### Breaking Changes
- Edge singularity correction at PEC and lossy metal edges defaults to `True`.
Expand Down
64 changes: 54 additions & 10 deletions tests/test_plugins/klayout/drc/test_drc.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,25 @@ def good_drcrunset_content():
report("DRC results", $resultsfile)
"""

@staticmethod
def wrap_drc_to_lydrc(body: str):
"""Return the XML-wrapped .lydrc runset content."""
xml = f"""\
<?xml version="1.0" encoding="utf-8"?>
<klayout-macro>
<description>Test DRC runset</description>
<version/>
<category>drc</category>
<prolog/>
<epilog/>
<text>
{body}
</text>
</klayout-macro>
"""

return xml

@staticmethod
@pytest.fixture(scope="class")
def bad_drcrunset_content_source():
Expand Down Expand Up @@ -147,22 +166,34 @@ def mock_run_drc_on_gds(config):
)

@pytest.mark.parametrize("verbose", [True, False])
@pytest.mark.parametrize("drc_file_suffix", [".drc", ".lydrc"])
def test_valid_run_on_gds(
self, monkeypatch, tmp_path, verbose, geom, geom_to_gds_kwargs, good_drcrunset_content
self,
monkeypatch,
tmp_path,
verbose,
geom,
geom_to_gds_kwargs,
good_drcrunset_content,
drc_file_suffix,
):
"""Test that no error is raised when runs on a gds are valid"""
geom.to_gds_file(tmp_path / "test.gds", **geom_to_gds_kwargs)
self.write_drcrunset(tmp_path, "good_drcfile.drc", good_drcrunset_content)
drc_content = good_drcrunset_content
if drc_file_suffix == ".lydrc":
drc_content = TestDRCRunner.wrap_drc_to_lydrc(drc_content)
self.write_drcrunset(tmp_path, f"good_drcfile{drc_file_suffix}", drc_content)
self.run(
monkeypatch=monkeypatch,
drc_runsetfile=tmp_path / "good_drcfile.drc",
drc_runsetfile=tmp_path / f"good_drcfile{drc_file_suffix}",
verbose=verbose,
source=tmp_path / "test.gds",
td_object_gds_savefile=tmp_path / "test.gds",
resultsfile=filepath / "drc_results.lyrdb",
)

@pytest.mark.parametrize("verbose", [True, False])
@pytest.mark.parametrize("drc_file_suffix", [".drc", ".lydrc"])
@pytest.mark.parametrize(
"td_object, obj_to_gds_kwargs",
[
Expand All @@ -180,12 +211,16 @@ def test_valid_run_on_td_object(
td_object,
obj_to_gds_kwargs,
good_drcrunset_content,
drc_file_suffix,
):
"""Test that no error is raised when runs on a Geometry, Structure, or Simulation are valid"""
self.write_drcrunset(tmp_path, "good_drcfile.drc", good_drcrunset_content)
drc_content = good_drcrunset_content
if drc_file_suffix == ".lydrc":
drc_content = TestDRCRunner.wrap_drc_to_lydrc(drc_content)
self.write_drcrunset(tmp_path, f"good_drcfile{drc_file_suffix}", drc_content)
self.run(
monkeypatch=monkeypatch,
drc_runsetfile=tmp_path / "good_drcfile.drc",
drc_runsetfile=tmp_path / f"good_drcfile{drc_file_suffix}",
verbose=verbose,
source=request.getfixturevalue(td_object),
td_object_gds_savefile=tmp_path / "test.gds",
Expand All @@ -196,18 +231,27 @@ def test_valid_run_on_td_object(
@pytest.mark.parametrize(
"bad_drcrunset_content", ["bad_drcrunset_content_source", "bad_drcrunset_content_report"]
)
@pytest.mark.parametrize("drc_file_suffix", [".drc", ".lydrc"])
def test_check_drcfile_format_invalid(
self, request, monkeypatch, tmp_path, geom, geom_to_gds_kwargs, bad_drcrunset_content
self,
request,
monkeypatch,
tmp_path,
geom,
geom_to_gds_kwargs,
bad_drcrunset_content,
drc_file_suffix,
):
"""Tests that ValidationError is raised when the drc file content is invalid"""
geom.to_gds_file(tmp_path / "test.gds", **geom_to_gds_kwargs)
self.write_drcrunset(
tmp_path, "bad_drcrunset.drc", request.getfixturevalue(bad_drcrunset_content)
)
drc_content = request.getfixturevalue(bad_drcrunset_content)
if drc_file_suffix == ".lydrc":
drc_content = TestDRCRunner.wrap_drc_to_lydrc(drc_content)
self.write_drcrunset(tmp_path, f"bad_drcrunset{drc_file_suffix}", drc_content)
with pytest.raises(pd.ValidationError) as e:
self.run(
monkeypatch=monkeypatch,
drc_runsetfile=tmp_path / "bad_drcrunset.drc",
drc_runsetfile=tmp_path / f"bad_drcrunset{drc_file_suffix}",
verbose=True,
source=tmp_path / "test.gds",
td_object_gds_savefile=None,
Expand Down
10 changes: 7 additions & 3 deletions tidy3d/plugins/klayout/drc/drc.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
from tidy3d.plugins.klayout.drc.results import DRCResults
from tidy3d.plugins.klayout.util import check_installation

SUPPORTED_DRC_SUFFIXES: frozenset[str] = frozenset({".drc", ".lydrc"})


class DRCConfig(Tidy3dBaseModel):
"""Configuration for KLayout DRC."""
Expand Down Expand Up @@ -54,9 +56,11 @@ def _validate_gdsfile_filetype(cls, v: pd.FilePath) -> pd.FilePath:

@validator("drc_runset")
def _validate_drc_runset_filetype(cls, v: pd.FilePath) -> pd.FilePath:
"""Check DRC runset filetype is ``.drc``."""
if v.suffix != ".drc":
raise ValidationError(f"DRC runset file '{v}' must end with '.drc'.")
"""Check DRC runset filetype is ``.drc`` or ``.lydrc``."""
if v.suffix not in SUPPORTED_DRC_SUFFIXES:
raise ValidationError(
f"DRC runset file '{v}' must end with one of {', '.join(SUPPORTED_DRC_SUFFIXES)}."
)
return v

@validator("drc_runset")
Expand Down