Skip to content

KLayout DRC integration #2586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open

KLayout DRC integration #2586

wants to merge 1 commit into from

Conversation

bzhangflex
Copy link
Contributor

@bzhangflex bzhangflex commented Jun 19, 2025

Adds a new plugin that integrates KLayout's DRC.

Features

  • Run DRC checks on Tidy3D objects that support exporting to GDS (Geometry, Structure, Simulation)
  • Basic DRC result parsing (check if DRC clean, gather violation counts)

Main functions

  1. run_drc(): Main function to run DRC on Tidy3D objects
  2. is_drc_clean(): Check if a DRC result file has no violations
  3. count_drc_violations(): Get a count of DRC violations by category

Usage Example

import tidy3d as td
from tidy3d.plugins.klayout.drc import run_drc

# Create a Tidy3D geometry
vertices = [(-2, 0), (-1, 1), (0, 0.5), (1, 1), (2, 0), (0, -1)]
geom = td.PolySlab(vertices=vertices, slab_bounds=(0, 0.22), axis=2)

# Run DRC
run_drc(
    geom,                # Tidy3D object
    "output.gds",       # Output GDS file
    "rules.drc",        # DRC rules file
    "results.lyrdb",    # Results file
    z=0.1,             # Additional GDS export parameters
    gds_layer=0,
    gds_dtype=0
)

# Check results
if is_drc_clean("results.lyrdb"):
    print("DRC passed!")
else:
    violations = count_drc_violations("results.lyrdb")
    print("DRC violations found:", violations)

Documentation

A README is included with detailed instructions and usage examples. More documentation can be added elsewhere as needed (perhaps we add an example notebook as well)

Greptile Summary

Adds KLayout Design Rule Check (DRC) integration plugin, enabling geometry validation for Tidy3D objects that support GDS export (Geometry, Structure, Simulation).

  • Implements core functions run_drc(), is_drc_clean(), and count_drc_violations() for DRC execution and result analysis
  • Adds comprehensive test suite with fixtures validating both clean and violation cases in tests/test_plugins/klayout/drc/
  • Provides detailed documentation and usage examples in plugin README
  • Includes proper error handling for missing dependencies and malformed files
  • Follows best practices with type hints, docstrings, and relative imports in package structure

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

8 files reviewed, 2 comments
Edit PR Review Bot Settings | Greptile

@bzhangflex bzhangflex requested review from groberts-flex and removed request for flex-greg June 19, 2025 20:39
@bzhangflex bzhangflex self-assigned this Jun 19, 2025
@bzhangflex bzhangflex force-pushed the bzhangflex/klayoutdrc branch 2 times, most recently from 4ed94cf to 8974b6d Compare June 19, 2025 20:53
Copy link
Contributor

github-actions bot commented Jun 19, 2025

Diff Coverage

Diff: origin/develop...HEAD, staged and unstaged changes

  • tidy3d/plugins/klayout/init.py (100%)
  • tidy3d/plugins/klayout/drc/init.py (100%)
  • tidy3d/plugins/klayout/drc/drc.py (85.3%): Missing lines 64,88-94,274,394-397,400,438-439,491-493,552,555

Summary

  • Total: 149 lines
  • Missing: 21 lines
  • Coverage: 85%

tidy3d/plugins/klayout/drc/drc.py

  60     )
  61 
  62     def __str__(self) -> str:
  63         """Get a nice string representation of the DRC violation."""
! 64         return f"{self.category}: {self.count}"
  65 
  66 
  67 class DRCResults(Tidy3dBaseModel):
  68     """A class for loading and storing KLayout DRC results."""

  84         return self.violations[category]
  85 
  86     def __str__(self) -> str:
  87         """Get a nice string representation of the DRC results."""
! 88         str = "DRC results summary\n"
! 89         str += "--------------------------------\n"
! 90         str += f"Total violations: {sum(violation.count for violation in self.violations.values())}\n\n"
! 91         str += "Violations by category:\n"
! 92         for violation in self.violations.values():
! 93             str += violation.__str__() + "\n"
! 94         return str
  95 
  96     @staticmethod
  97     def parse_edge(value: str) -> EdgeMarker:
  98         """

  270         -------
  271         dict[str, int]
  272             A dictionary of violation counts for each category.
  273         """
! 274         return {category: violation.count for category, violation in self.violations.items()}
  275 
  276     def is_drc_clean(self) -> bool:
  277         """Returns ``True`` if the DRC is clean (no violations).

  390         SyntaxError
  391             If the design rule file is not formatted correctly.
  392         """
  393         # checks
! 394         self._check_gdsfile_exists()
! 395         self._check_designrulefile_exists()
! 396         self._check_drcfile_format()
! 397         self._check_klayout_exists()
  398 
  399         # run drc
! 400         run(
  401             [
  402                 "klayout",
  403                 "-b",
  404                 "-r",

  434         Notes
  435         -----
  436         The user is encouraged to use the function ``run_drc`` instead of this class.
  437         """
! 438         td_object.to_gds_file(self.gdsfile, **to_gds_file_kwargs)
! 439         self.run_drc_on_gds()
  440 
  441 
  442 def run_drc(
  443     td_object: Union[Geometry, Structure, Simulation],

  487     >>> geom = td.PolySlab(vertices=vertices, slab_bounds=(0, 0.22), axis=2)
  488     >>> results = run_drc(geom, "geom.gds", "test.drc", "geom_drc.lyrdb", z=0.1, gds_layer=0, gds_dtype=0)
  489     >>> print(results)
  490     """
! 491     drc = DRCRunner(gdsfile=gdsfile, designrulefile=designrulefile, resultsfile=resultsfile)
! 492     drc.run_drc(td_object, **to_gds_file_kwargs)
! 493     return DRCResults.from_file(resultsfile)
  494 
  495 
  496 def load_drc_results(resultsfile: Union[str, Path]) -> DRCResults:
  497     """Loads a KLayout DRC results file and returns the results as a :class:`.DRCResults` object.

  548     >>> from tidy3d.plugins.klayout.drc import run_drc_on_gds
  549     >>> results = run_drc_on_gds("test.gds", "test.drc", "drc_results.lyrdb")
  550     >>> print(results)
  551     """
! 552     DRCRunner(
  553         gdsfile=gdsfile, designrulefile=designrulefile, resultsfile=resultsfile
  554     ).run_drc_on_gds()
! 555     return DRCResults.from_file(resultsfile)

Copy link
Collaborator

@yaugenst-flex yaugenst-flex left a comment

Choose a reason for hiding this comment

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

Thanks @bzhangflex this is great! Note the plugin should also be added to the docs here: https://github.com/flexcompute/tidy3d/tree/develop/docs/api/plugins

A question on the interface: Would it make sense to provide default paths for the resultsfile in is_drc_clean() and the gdsfile in run_drc()?

Also, currently there seems to be no direct way of running DRC and getting the results in a single call. The workflow is distinctly separated into a "run DRC" step, and then you explicitly load the results of that to do further analysis. I think this is a well-structured workflow, but as a user I'd probably prefer getting the results of the DRC right away (in some well-defined data structure), so essentially have run_drc() return something like a DRCResults data structure. To spin that thought a bit further, I think there should be a higher-level object-oriented interface (something that derives from Tidy3dBaseModel). Generally this would put things more in line with established conventions in the codebase but also allow us to serialize these things and e.g. run them in GUI (eventually, maybe). Note that I think this can all be implemented without changing the current functions, we'd just be wrapping them. Keeping the functional interface around is a good idea.

@bzhangflex
Copy link
Contributor Author

Thank you @yaugenst-flex for the detailed review!

A question on the interface: Would it make sense to provide default paths for the resultsfile in is_drc_clean() and the gdsfile in run_drc()?

Hmm I'm not sure, the user might end up loading the wrong results unknowingly or overwrite an important gds. What do you think?

Also, currently there seems to be no direct way of running DRC and getting the results in a single call. The workflow is distinctly separated into a "run DRC" step, and then you explicitly load the results of that to do further analysis. I think this is a well-structured workflow, but as a user I'd probably prefer getting the results of the DRC right away (in some well-defined data structure), so essentially have run_drc() return something like a DRCResults data structure.

Very good point, will work towards that.

To spin that thought a bit further, I think there should be a higher-level object-oriented interface (something that derives from Tidy3dBaseModel). Generally this would put things more in line with established conventions in the codebase but also allow us to serialize these things and e.g. run them in GUI (eventually, maybe). Note that I think this can all be implemented without changing the current functions, we'd just be wrapping them. Keeping the functional interface around is a good idea.

Would this be something like all of the sub methods get moved into a DRC object, and run_drc() remains a standalone function but creates and calls a DRC object within it?

@yaugenst-flex
Copy link
Collaborator

A question on the interface: Would it make sense to provide default paths for the resultsfile in is_drc_clean() and the gdsfile in run_drc()?

Hmm I'm not sure, the user might end up loading the wrong results unknowingly or overwrite an important gds. What do you think?

Yes, but: We have a default file for simulation results too, i.e., if you don't specify a path in web.run(), you'll get a simulation_data.hdf5. If you think it's a problem, we can also skip adding a default for now. Generally I think it's better to reduce the number of required arguments, but we can leave it required for now and just see how it goes.

To spin that thought a bit further, I think there should be a higher-level object-oriented interface (something that derives from Tidy3dBaseModel). Generally this would put things more in line with established conventions in the codebase but also allow us to serialize these things and e.g. run them in GUI (eventually, maybe). Note that I think this can all be implemented without changing the current functions, we'd just be wrapping them. Keeping the functional interface around is a good idea.

Would this be something like all of the sub methods get moved into a DRC object, and run_drc() remains a standalone function but creates and calls a DRC object within it?

The other way round actually: Create a class that exposes these DRC methods, but just wraps the current functions. Somewhat similar to how it's done in the autograd plugin: https://github.com/flexcompute/tidy3d/blob/develop/tidy3d/plugins/autograd/invdes/filters.py

@bzhangflex bzhangflex force-pushed the bzhangflex/klayoutdrc branch from 8974b6d to 3a0dd3f Compare June 26, 2025 19:54
@bzhangflex
Copy link
Contributor Author

Thanks @yaugenst-flex, I've made quite a few changes. Now run_drc() returns a DRCResults data structure that includes all of the DRC violation markers.

Example usage:

from tidy3d.plugins.klayout.drc import run_drc

# Create a simple polygon geometry
vertices = [(-2, 0), (-1, 1), (0, 0.5), (1, 1), (2, 0), (0, -1)]
geom = td.PolySlab(vertices=vertices, slab_bounds=(0, 0.22), axis=2)

# Run DRC and get results
results = run_drc(geom, "geom.gds", "test.drc", "drc_results.lyrdb", 
                 z=0.1, gds_layer=0, gds_dtype=0)

# Check if DRC passed
if results.is_drc_clean():
    print("DRC passed!")
else:
    print(results)  # Prints violation summary

# Violations are indexed by category
print(results['min_width'].violations)

@bzhangflex bzhangflex requested a review from yaugenst-flex June 26, 2025 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants