Skip to content

Latest commit

 

History

History
369 lines (272 loc) · 16.8 KB

File metadata and controls

369 lines (272 loc) · 16.8 KB

AI Agent Best Practices for QPDK

This is a Python-based superconducting microwave Process Design Kit (PDK) built on gdsfactory for designing quantum devices and circuits. It provides components like transmons, resonators, couplers, and quantum layouts. Please follow these guidelines when contributing:

General Agent Behavior

Behavioral guidelines to reduce common LLM coding mistakes. Adapted from Andrej Karpathy's observations on LLM coding pitfalls (source). These guidelines bias toward caution over speed — for trivial tasks, use judgment.

Think Before Coding

Don't assume. Don't hide confusion. Surface tradeoffs.

  • State your assumptions explicitly. If uncertain, ask.
  • If multiple interpretations exist, present them — don't pick silently.
  • If a simpler approach exists, say so. Push back when warranted.
  • If something is unclear, stop. Name what's confusing. Ask.

Simplicity First

Minimum code that solves the problem. Nothing speculative.

  • No features beyond what was asked.
  • No abstractions for single-use code.
  • No "flexibility" or "configurability" that wasn't requested.
  • No error handling for impossible scenarios.
  • If you write 200 lines and it could be 50, rewrite it.

Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.

Surgical Changes

Touch only what you must. Clean up only your own mess.

When editing existing code:

  • Don't "improve" adjacent code, comments, or formatting.
  • Don't refactor things that aren't broken.
  • Match existing style, even if you'd do it differently.
  • If you notice unrelated dead code, mention it — don't delete it.

When your changes create orphans:

  • Remove imports/variables/functions that YOUR changes made unused.
  • Don't remove pre-existing dead code unless asked.

The test: every changed line should trace directly to the user's request.

Goal-Driven Execution

Define success criteria. Loop until verified.

Transform tasks into verifiable goals:

  • "Add validation" → "Write tests for invalid inputs, then make them pass"
  • "Fix the bug" → "Write a test that reproduces it, then make it pass"
  • "Refactor X" → "Ensure tests pass before and after"

For multi-step tasks, state a brief plan:

1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]

Example for this PDK:

1. Add new cell in qpdk/cells/ → verify: pre-commit hooks pass
2. Register in qpdk/cells/__init__.py → verify: component importable
3. Generate regression tests → verify: just test-gds passes

Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.

Code Standards

Required Before Each Commit

  • ALWAYS run pre-commit hooks before committing any changes by using uvx prek run --all-files
  • Pre-commit hooks will automatically run code formatting (ruff), linting, YAML formatting, and other quality checks
  • All pre-commit hooks must pass before any changes can be committed

Development Flow

  • Install dependencies: just install (uses uv sync --all-extras)
  • Test: just test (runs uv run pytest -n auto with parallel execution)
  • Test GDS components: just test-gds (runs only GDS regression tests in tests/test_pdk.py)
  • Regenerate GDS regression tests: just test-gds-force (regenerates reference GDS files)
  • Test with fail-fast: just test-gds-fail-fast (stops at first test failure for debugging)
  • Run pre-commit hooks: just run-pre (runs all pre-commit hooks on all files)
  • Update pre-commit hooks: just update-pre (updates pre-commit hooks to latest versions)
  • Build package: just build (creates distribution packages)
  • Build documentation: just docs (builds Sphinx documentation, this needs to succeed in order to be mergeable to main)

Repository Structure

  • qpdk/: Core Python package containing quantum device components and PDK configuration
  • qpdk/cells/: Device cell, or gdsfactory component, definitions (transmons, resonators, couplers, etc.)
  • qpdk/models/: S-parameter and circuit models for quantum RF components
  • qpdk/klayout/: KLayout technology files and layer definitions
  • qpdk/tech.py: Layer stack and main technology cross sections etc.
  • tests/: Test suite using pytest
  • docs/: Documentation built with Sphinx
  • install_tech.py: Script to symlink technology files to KLayout, rarely needed for AI agents

Technology and Tools

  • Python versions: Specified in pyproject.toml (currently >=3.11,<3.15)
  • Package manager: uv (preferred over pip/conda)
  • Main dependencies: gdsfactory (>=9.15.0,<9.21.0), doroutes (>=0.2.0)
  • Testing: pytest with regression testing using pytest_regressions, hypothesis for property-based testing
  • Linting: ruff for Python code formatting and linting, pyrefly for type checking
  • Layout tool: KLayout for viewing and editing GDS layouts
  • Documentation: Sphinx for building documentation, jupytext for notebook management
  • Simulation tools (optional): sax, scqubits, jaxellip (install with uv sync --extra models)

Git and Version Control

  • Git LFS Required: This repository uses Git LFS (Large File Storage) for test data files. Install Git LFS before cloning or testing: https://git-lfs.github.com/
  • Binary files: GDS and OAS files are tracked as binary in .gitattributes to prevent merge conflicts
  • Test data with LFS: CSV files in tests/models/data/ are stored with Git LFS (filter=lfs diff=lfs merge=lfs)
  • Branching: Work on feature branches, not directly on main. Pull requests are required for merging to main
  • Commit messages: Write clear, concise commit messages. Use imperative mood (e.g., "Add component" not "Added component")

CI/CD and Automation

GitHub Actions Workflows

The repository uses several automated workflows:

  • test.yml: Runs on every PR and push to main. Executes pre-commit hooks and pytest test suite
  • pages.yml: Builds HTML and PDF documentation, deploys to GitHub Pages on push to main
  • build.yml: Builds Python package distribution files
  • release.yml: Publishes to PyPI when a version tag (e.g., v0.0.3) is pushed

Automated Checks

All PRs must pass:

  1. Pre-commit hooks (linting, formatting, type checking, etc.)
  2. Full test suite (203+ tests including regression tests)
  3. Documentation build (both HTML and PDF must build successfully)

Release Process

  1. Bump the version with uv version --bump patch The version should be updated in pyproject.toml, qpdk/__init__.py, and README.md
  2. Create and push a version tag. For example: git tag v0.0.X && git push origin v0.0.X
  3. GitHub Actions automatically builds and publishes to PyPI
  4. Draft release is automatically published on GitHub

Key Guidelines

  1. Follow quantum device design patterns: This PDK is specifically for superconducting quantum circuits
  2. Maintain layer stack consistency: All layer definitions must match between qpdk/layers.yaml and qpdk/tech.py
  3. Use regression testing: New components should have regression tests for both settings and netlists, the files are generated automatically with just test-gds-force
  4. Prefer Justfile commands: Use just commands instead of direct tool invocation if possible
  5. Write comprehensive tests: Add tests for new functionality following existing patterns in tests/
  6. Document quantum-specific behavior: Include docstrings explaining the quantum physics and device characteristics, ideally with citations. Use :math: notation for inline LaTeX math (avoid $ or $$).
  7. Expose new components to PDK: Import new all components from new files with the form from ... import * in qpdk/cells/__init__.py
  8. Use predefined layers: Prefer predefined layers from the LAYER enumerable in qpdk/tech.py. An agent rarely needs to create a new layer.
  9. Use centralized physical constants: Prefer using physical constants (like e, h, Φ_0, ε_0) from qpdk/models/constants.py instead of defining them locally.
  10. Use the centralized logger: ALWAYS prefer using the fancy qpdk.logger instead of raw print statements for reporting information, warnings, or errors. Import it as from qpdk import logger.
  11. Use JAX for analytical models: When implementing analytical models for S-parameters, prefer using JAX-compatible functions (e.g., jnp instead of np, jaxellip for elliptic integrals) and enable JIT compilation with @partial(jax.jit, inline=True) for helper functions.
  12. Manage bibliography entries cleanly: When editing the bibliography.bib file, ensure that if a reference contains a valid doi field, it should not include a url or urldate field to avoid redundant citation information.
  13. Python imports: Place imports at the top of the file, not inside functions, unless the dependency is missing from pyproject.toml or is exceptionally heavy without lazy loading.

Testing Guidelines

  • Component tests: Each new component should be added to the cell registry with @gf.cell and categorized with tags.
  • Netlist validation: Components must generate valid netlists that can be round-tripped (component -> netlist -> component). This is tested by test_netlists in tests/test_pdk.py.
  • Model tests: New models should have unit tests in tests/models/ verifying their behavior, passivity, and reciprocity.
  • Prefer using the hypothesis library: Use the hypothesis library to generate tests with generic arguments with appropriate types. Note the following gotchas when using hypothesis:
    • Do NOT use @staticmethod: When using the @given decorator from Hypothesis, do not add @staticmethod to the test function. Hypothesis inspects the __code__ attribute of the decorated function during test collection, which causes AttributeError on staticmethod descriptors.
    • Handle JAX JIT compilation overhead: When testing JAX JIT-compiled functions with Hypothesis, the first run incurs a compilation overhead which often causes DeadlineExceeded errors. Add @settings(deadline=None) to Hypothesis @given tests that call JIT-compiled code to prevent flaky tests.

Creating New Components

When adding new quantum device components:

  1. Define the component in qpdk/cells/
  2. Categorize the component using the tags parameter in the @gf.cell decorator (e.g., @gf.cell(tags=("qubits", "transmons"))).
  3. Add it to the cells registry in qpdk/cells/__init__.py, with from ... import *
  4. Create appropriate layer assignments using the defined LAYER enum from qpdk/tech.py
  5. Add regression tests following the existing parametrized test pattern, generated by running just test-gds-force multiple times
  6. Ensure the component works with the netlist extraction system
  7. Document the quantum device physics and design parameters

Models and Simulation

The qpdk/models/ directory contains S-parameter and circuit models for quantum RF components:

  • Models available: Resonators, couplers, waveguides, generic components
  • Simulation frameworks: Uses sax for S-parameter simulations, scqubits for quantum circuit analysis
  • Media definitions: qpdk/models/cpw.py defines coplanar waveguide (CPW) parameters
  • Installing simulation tools: Run uv sync --extra models to install optional simulation dependencies
  • Model structure: Each model typically provides functions that return S-parameters or network parameters as functions of frequency
  • Integration with components: Models can be linked to layout components through the netlist system

Example Model Usage

Models are typically used in notebooks and sample scripts (see notebooks/ and qpdk/samples/simulate_resonator.py) for examples of:

  • Calculating resonator frequencies
  • Simulating S-parameters for coupled systems
  • Optimizing component parameters
  • Comparing simulated results with measurements

Notebooks and Samples

Jupytext Workflow

  • Notebook source: Notebook source files live in notebooks/src/. Python notebooks use .py; the MATLAB-kernel notebook uses .m (see notebooks/src/matlab_integration.m)
  • Conversion: Run just convert-notebooks or ./.github/convert-notebooks.sh to convert source files to .ipynb
  • Pre-commit hook: Notebooks are automatically converted when .py or .m files in notebooks/src/ are modified
  • Format: Python notebooks use the "percent" format with # %% cell markers; MATLAB notebooks use % %% markers
  • Documentation: Converted notebooks are copied to docs/notebooks/ during documentation build. Notebooks that require external tooling (HFSS, MATLAB) are listed in nb_execution_excludepatterns in docs/conf.py so the docs build does not try to execute them

Samples Directory

  • Location: qpdk/samples/ contains example layout and simulation scripts
  • Purpose: Demonstrates how to use PDK components to create complete designs
  • Examples include:
    • Simple component demonstrations (sample0.py through sample6.py)
    • Resonator test chips (resonator_test_chip.py)
    • Filled test chips with multiple components (filled_test_chip.py)
    • Routing examples with airbridges (route_with_airbridges.py)
    • Simulation workflows (simulate_resonator.py)
  • YAML files: Some samples use .pic.yml and .scm.yml for pictorial and schematic component definitions
  • Testing: Samples are tested to ensure they generate valid GDS files (see tests/test_pdk.py)

Creating New Notebooks or Samples

  1. For notebooks: Create .py file in notebooks/src/ using jupytext percent format with proper YAML header. Ensure the first code cell contains the Google Colab installation snippet with tags=["hide-input", "hide-output"] so it can be run online without cluttering the docs.
  2. For samples: Create .py file in qpdk/samples/ following existing examples
  3. Include docstrings and comments explaining the design and physics
  4. Add citations where appropriate using Sphinx citation syntax
  5. Test that the script runs without errors
  6. For samples, ensure the generated component is added to the test suite
  7. Update docs/notebooks.rst when adding or removing notebooks — this page describes each notebook and categorizes it by simulation approach

Pre-commit Hooks Details

The repository uses extensive pre-commit hooks Including, but not limited to:

  • ruff: Python code formatting and linting
  • yamlfmt: YAML file formatting
  • codespell: Spell checking
  • nbstripout: Jupyter notebook cleaning
  • actionlint: GitHub Actions validation
  • uv-lock: Lock file validation

Common Pitfalls and Troubleshooting

Git LFS Issues

Problem: Tests fail with "File not found" errors for CSV data files, or GDS files appear as text pointers

Solution: Install Git LFS and run git lfs pull to download large files

# Install Git LFS (varies by OS)
# Ubuntu/Debian:
sudo apt-get install git-lfs
# RHEL/Rocky
sudo dnf install git-lfs
# macOS:
brew install git-lfs

# Initialize and pull
git lfs install
git lfs pull

Pre-commit Hooks Not Running

Problem: CI fails with formatting or linting errors

Solution: Always run pre-commit hooks before committing

just run-pre
# or
uvx prek run --all-files

Layer Consistency Issues

Problem: Components render incorrectly in KLayout or documentation

Solution: Ensure layers are defined in both qpdk/layers.yaml AND qpdk/tech.py. Layer numbers and datatypes must match exactly.

Documentation Build Failures

Problem: just docs fails with import errors or missing cells

Solution:

  1. Ensure all dependencies are installed: just install
  2. Run just setup-ipython-config to set up IPython configuration
  3. Check that all components are properly registered in qpdk/cells/__init__.py
  4. Verify that notebooks in notebooks/src/ are valid Python files with correct jupytext format

Regression Test Failures

Problem: GDS regression tests fail after making changes

Solution:

  1. If changes are intentional, regenerate reference files: just test-gds-force
  2. Review the diff to ensure changes are expected
  3. Commit both code and updated reference files in tests/gds_ref/
  4. Note: GDS reference files may change between gdsfactory versions

Import Errors in Models

Problem: ModuleNotFoundError when importing simulation modules

Solution: Install optional model dependencies: uv sync --extra models

Type Checking Errors

Problem: Pyrefly or mypy report type errors in pre-commit

Solution:

  1. Review the specific error message
  2. Add type hints where missing
  3. Use cast() for type conversions only when absolutely necessary
  4. Check pyproject.toml for any type checking configuration that may be ignoring specific errors