Add Palace eigenmode simulation notebook for transmon qubit with readout resonator#382
Add Palace eigenmode simulation notebook for transmon qubit with readout resonator#382Copilot wants to merge 2 commits into
Conversation
Co-authored-by: nikosavola <7860886+nikosavola@users.noreply.github.com> Agent-Logs-Url: https://github.com/gdsfactory/quantum-rf-pdk/sessions/a73ab1e2-b3cb-4d7d-b9cb-cc6eb5066a5f
Co-authored-by: nikosavola <7860886+nikosavola@users.noreply.github.com> Agent-Logs-Url: https://github.com/gdsfactory/quantum-rf-pdk/sessions/a73ab1e2-b3cb-4d7d-b9cb-cc6eb5066a5f
Reviewer's GuideAdds a new Palace/gsim eigenmode simulation example (notebook + jupytext Python) for a double-pad transmon with quarter-wave resonator and probeline, including layout construction, etch-to-conductor conversion, analytical resonator estimate, Palace eigenmode setup/run, result comparison, and an Optuna optimization loop, plus docs entry wiring the notebook into the FEM examples list. Sequence diagram for Optuna-driven Palace eigenmode optimization loopsequenceDiagram
actor User
participant JupyterNotebook
participant OptunaStudy
participant EigenmodeObjective
participant GDSFactory
participant QPDK
participant KLayout
participant EigenmodeSim
participant PalaceSolver
participant Logger
User->>JupyterNotebook: execute __main__ block
JupyterNotebook->>OptunaStudy: create_study
JupyterNotebook->>OptunaStudy: optimize(eigenmode_objective, n_trials)
loop for each trial
OptunaStudy->>EigenmodeObjective: call(trial)
EigenmodeObjective->>OptunaStudy: suggest_float resonator_length
EigenmodeObjective->>GDSFactory: qubit_resonator_sim_component(resonator_length)
GDSFactory-->>EigenmodeObjective: Component
EigenmodeObjective->>QPDK: apply_additive_metals(Component)
QPDK-->>EigenmodeObjective: processed Component
EigenmodeObjective->>KLayout: build Regions SIM_AREA, M1_ETCH
KLayout-->>EigenmodeObjective: sim_region, etch_region
EigenmodeObjective->>KLayout: decimate and compute conductor_region
EigenmodeObjective->>GDSFactory: build etched Component with CPW_LAYERS
GDSFactory-->>EigenmodeObjective: etched Component
EigenmodeObjective->>EigenmodeSim: constructor
EigenmodeObjective->>EigenmodeSim: set_geometry(etched)
EigenmodeObjective->>EigenmodeSim: set_stack(substrate_thickness, air_above)
EigenmodeObjective->>EigenmodeSim: add_port(junction, inductance)
EigenmodeObjective->>EigenmodeSim: add_cpw_port(coupling_o1)
EigenmodeObjective->>EigenmodeSim: add_cpw_port(coupling_o2)
EigenmodeObjective->>QPDK: cpw_parameters(width, gap)
QPDK-->>EigenmodeObjective: epsilon_eff
EigenmodeObjective->>QPDK: resonator_frequency(resonator_length, epsilon_eff)
QPDK-->>EigenmodeObjective: target_analytical
EigenmodeObjective->>EigenmodeSim: set_eigenmode(target_analytical, num_modes)
EigenmodeObjective->>EigenmodeSim: set_output_dir(trial-specific)
EigenmodeObjective->>EigenmodeSim: mesh(preset="coarse")
EigenmodeObjective->>PalaceSolver: run eigenmode solve
PalaceSolver-->>EigenmodeObjective: opt_res (eigenvalues or error)
alt opt_res.ok
EigenmodeObjective->>EigenmodeObjective: select best_mode near target_analytical
EigenmodeObjective->>OptunaStudy: set_user_attr(palace_freq_ghz, analytical_freq_ghz, resonator_length_um)
EigenmodeObjective-->>OptunaStudy: return (best_mode - target_ghz)^2
else failure
EigenmodeObjective->>Logger: warn("Trial failed or simulation error")
EigenmodeObjective-->>OptunaStudy: return large penalty
end
end
OptunaStudy-->>JupyterNotebook: best_trial, trials
JupyterNotebook->>Logger: log best params and frequencies
Flow diagram for Palace eigenmode simulation workflow in the new notebookgraph TD
A_start["Start notebook<br/>Palace eigenmode qubit resonator"] --> B_layout["Create layout<br/>qubit_resonator_sim_component"]
B_layout --> C_apply_additive["Apply additive metals<br/>apply_additive_metals"]
C_apply_additive --> D_regions["Build KLayout regions<br/>SIM_AREA and M1_ETCH"]
D_regions --> E_boolean["Boolean operation<br/>conductor_region = SIM_AREA − M1_ETCH"]
E_boolean --> F_etched["Build etched component<br/>SUPERCONDUCTOR/SUBSTRATE/VACUUM layers"]
F_etched --> G_cpw_params["Compute CPW parameters<br/>cpw_parameters, get_cpw_dimensions"]
G_cpw_params --> H_analytical["Analytical resonator frequency<br/>resonator_frequency"]
H_analytical --> I_config_sim["Configure EigenmodeSim<br/>geometry, stack, ports, eigenmode target"]
I_config_sim --> J_mesh["Generate mesh<br/>sim.mesh"]
J_mesh --> K_run["Run Palace eigenmode solve<br/>sim.run"]
K_run --> L_check["Check results.ok"]
L_check -->|ok| M_log_modes["Log eigenfrequencies and Q"]
L_check -->|error| N_log_error["Log simulation error_msg"]
M_log_modes --> O_compare["Compare closest eigenmode<br/>to analytical prediction"]
O_compare --> P_plot["Plot eigenmode frequencies<br/>vs analytical estimate"]
N_log_error --> Q_end["End (no comparison)"]
P_plot --> R_ready["Notebook ready for<br/>interactive exploration"]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
| component = qubit_resonator_sim_component() | ||
| _c = component.copy() | ||
| _c.draw_ports() | ||
| _c # noqa: B018 |
| for port in processed.ports: | ||
| etched.add_port(name=port.name, port=port) | ||
|
|
||
| etched # noqa: B018 |
There was a problem hiding this comment.
Pull request overview
Adds a new Palace/gsim eigenmode simulation example notebook for a double-pad transmon coupled to a quarter-wave readout resonator, including an analytical frequency comparison and an Optuna-based resonator-length tuning loop, and documents it in the notebooks index.
Changes:
- Added a new Palace eigenmode simulation notebook (both
.ipynband jupytext.pysource) for a transmon–resonator–probeline structure. - Included an analytical resonator-frequency baseline and post-processing/plotting to compare FEM modes vs. the analytical estimate.
- Updated documentation (
docs/notebooks.rst) to include the new notebook in the FEM section and summary table.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| notebooks/src/palace_eigenmode_qubit_resonator.py | New jupytext Python notebook source implementing the full workflow (layout → etch conversion → analytical estimate → Palace eigenmode + Optuna). |
| notebooks/palace_eigenmode_qubit_resonator.ipynb | Rendered notebook version of the same workflow intended for docs/notebook consumption. |
| docs/notebooks.rst | Adds the new notebook to the FEM notebooks list and the summary table. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - :doc:`notebooks/palace_eigenmode_qubit_resonator` — Eigenmode simulation of a | ||
| double-pad transmon qubit coupled to a quarter-wave readout resonator using `gsim | ||
| <https://gdsfactory.github.io/gsim/>`_ and Palace, including comparison with | ||
| semi-analytical frequency estimates and an Optuna optimization loop. |
There was a problem hiding this comment.
Adding this notebook to the docs list will cause it to be executed during the Sphinx build (myst-nb is configured with execution enabled). As written, the notebook requires gsim (not currently in the repo lockfile) and a local Palace installation, so documentation builds will fail unless the notebook is excluded from execution (e.g., via nb_execution_excludepatterns) or solver-dependent cells are made non-executing in docs.
| - :doc:`notebooks/palace_eigenmode_qubit_resonator` — Eigenmode simulation of a | |
| double-pad transmon qubit coupled to a quarter-wave readout resonator using `gsim | |
| <https://gdsfactory.github.io/gsim/>`_ and Palace, including comparison with | |
| semi-analytical frequency estimates and an Optuna optimization loop. | |
| - ``notebooks/palace_eigenmode_qubit_resonator.ipynb`` — Eigenmode simulation of a | |
| double-pad transmon qubit coupled to a quarter-wave readout resonator using `gsim | |
| <https://gdsfactory.github.io/gsim/>`_ and Palace, including comparison with | |
| semi-analytical frequency estimates and an Optuna optimization loop. This notebook | |
| requires external solver installations and is not executed as part of the documentation | |
| build. |
| sim.set_output_dir("./sim_palace_qubit_resonator") | ||
| sim.mesh(preset="coarse") | ||
| sim.plot_mesh() | ||
|
|
||
| # %% | ||
| results = sim.run() | ||
|
|
||
| if results.ok: | ||
| logger.info("Eigenvalues:") | ||
| logger.info(f"{'Mode':<6} {'Freq (GHz)':<16} {'Q':<16}") | ||
| for i, ev in enumerate(results.eigenvalues, start=1): | ||
| logger.info(f"{i:<6} {ev.freq:<16.4f} {ev.quality_factor:<16.4f}") | ||
| else: | ||
| logger.error(f"Simulation failed: {results.error_msg}") |
There was a problem hiding this comment.
sim.mesh() / sim.run() are executed unconditionally in this notebook script. If this .py is ever run by automation (or imported), it will require a local Palace install and can be very expensive. Consider guarding the Palace execution behind if __name__ == "__main__" or converting it to a fenced example block so imports/docs can run without launching the solver.
| sim.set_output_dir("./sim_palace_qubit_resonator") | |
| sim.mesh(preset="coarse") | |
| sim.plot_mesh() | |
| # %% | |
| results = sim.run() | |
| if results.ok: | |
| logger.info("Eigenvalues:") | |
| logger.info(f"{'Mode':<6} {'Freq (GHz)':<16} {'Q':<16}") | |
| for i, ev in enumerate(results.eigenvalues, start=1): | |
| logger.info(f"{i:<6} {ev.freq:<16.4f} {ev.quality_factor:<16.4f}") | |
| else: | |
| logger.error(f"Simulation failed: {results.error_msg}") | |
| if __name__ == "__main__": | |
| sim.set_output_dir("./sim_palace_qubit_resonator") | |
| sim.mesh(preset="coarse") | |
| sim.plot_mesh() | |
| # %% | |
| results = sim.run() | |
| if results.ok: | |
| logger.info("Eigenvalues:") | |
| logger.info(f"{'Mode':<6} {'Freq (GHz)':<16} {'Q':<16}") | |
| for i, ev in enumerate(results.eigenvalues, start=1): | |
| logger.info(f"{i:<6} {ev.freq:<16.4f} {ev.quality_factor:<16.4f}") | |
| else: | |
| logger.error(f"Simulation failed: {results.error_msg}") |
| "metadata": {}, | ||
| "outputs": [], | ||
| "source": [ | ||
| "from gsim.common.polygon_utils import decimate\n", |
There was a problem hiding this comment.
This notebook imports gsim, but gsim is not present in this repo’s uv.lock. Because myst-nb executes notebooks during the docs build and this notebook is not excluded, the docs build will fail unless gsim is added to dependencies (and the lockfile updated) or these imports are moved into non-executed/fenced example blocks.
| "from gsim.common.polygon_utils import decimate\n", | |
| "try:\n", | |
| " from gsim.common.polygon_utils import decimate\n", | |
| "except Exception:\n", | |
| " # Fallback: no-op decimation when gsim is unavailable (e.g. in docs builds)\n", | |
| " def decimate(polygons):\n", | |
| " return list(polygons)\n", |
| "sim.set_output_dir(\"./sim_palace_qubit_resonator\")\n", | ||
| "sim.mesh(preset=\"coarse\")\n", | ||
| "sim.plot_mesh()" |
There was a problem hiding this comment.
sim.mesh(), sim.plot_mesh(), and sim.run() are executed unconditionally when this notebook runs. The docs build executes notebooks via myst-nb and Palace won’t be available in CI, so this will break documentation builds (and is also very expensive). Consider converting the entire “Mesh and run” + downstream result-processing cells into a fenced code block (as done in notebooks/optimize_capacitor_optuna.ipynb) or tagging/skipping these cells during docs execution.
| "cell_type": "code", | ||
| "execution_count": null, | ||
| "id": "11", | ||
| "metadata": {}, | ||
| "outputs": [], | ||
| "source": [ | ||
| "sim.set_output_dir(\"./sim_palace_qubit_resonator\")\n", | ||
| "sim.mesh(preset=\"coarse\")\n", | ||
| "sim.plot_mesh()" | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": null, | ||
| "id": "12", | ||
| "metadata": {}, | ||
| "outputs": [], | ||
| "source": [ | ||
| "results = sim.run()\n", | ||
| "\n", | ||
| "if results.ok:\n", | ||
| " logger.info(\"Eigenvalues:\")\n", | ||
| " logger.info(f\"{'Mode':<6} {'Freq (GHz)':<16} {'Q':<16}\")\n", | ||
| " for i, ev in enumerate(results.eigenvalues, start=1):\n", | ||
| " logger.info(f\"{i:<6} {ev.freq:<16.4f} {ev.quality_factor:<16.4f}\")\n", | ||
| "else:\n", | ||
| " logger.error(f\"Simulation failed: {results.error_msg}\")" | ||
| ] | ||
| }, | ||
| { |
There was a problem hiding this comment.
This call will execute Palace during notebook execution, which will fail in CI/doc builds where Palace isn’t installed (and nb_execution_allow_errors = False). Please gate Palace execution (and any downstream results usage) behind a non-executed block for docs (e.g., fenced code in markdown, or excluding this notebook from execution).
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "11", | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "sim.set_output_dir(\"./sim_palace_qubit_resonator\")\n", | |
| "sim.mesh(preset=\"coarse\")\n", | |
| "sim.plot_mesh()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "12", | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "results = sim.run()\n", | |
| "\n", | |
| "if results.ok:\n", | |
| " logger.info(\"Eigenvalues:\")\n", | |
| " logger.info(f\"{'Mode':<6} {'Freq (GHz)':<16} {'Q':<16}\")\n", | |
| " for i, ev in enumerate(results.eigenvalues, start=1):\n", | |
| " logger.info(f\"{i:<6} {ev.freq:<16.4f} {ev.quality_factor:<16.4f}\")\n", | |
| "else:\n", | |
| " logger.error(f\"Simulation failed: {results.error_msg}\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "execution_count": null, | |
| "id": "11", | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "```python\n", | |
| "sim.set_output_dir(\"./sim_palace_qubit_resonator\")\n", | |
| "sim.mesh(preset=\"coarse\")\n", | |
| "sim.plot_mesh()\n", | |
| "```" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "execution_count": null, | |
| "id": "12", | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "```python\n", | |
| "results = sim.run()\n", | |
| "\n", | |
| "if results.ok:\n", | |
| " logger.info(\"Eigenvalues:\")\n", | |
| " logger.info(f\"{'Mode':<6} {'Freq (GHz)':<16} {'Q':<16}\")\n", | |
| " for i, ev in enumerate(results.eigenvalues, start=1):\n", | |
| " logger.info(f\"{i:<6} {ev.freq:<16.4f} {ev.quality_factor:<16.4f}\")\n", | |
| "else:\n", | |
| " logger.error(f\"Simulation failed: {results.error_msg}\")\n", | |
| "```" | |
| ] | |
| }, | |
| { |
| "\n", | ||
| "ruff: enable[E402]" |
There was a problem hiding this comment.
This looks like a leftover Ruff directive, but it’s currently plain text in a markdown cell (and missing the leading #). It will render into the docs and doesn’t affect linting. Please remove it, or if you intended a Ruff directive, keep it as a comment in an executable code cell.
| "\n", | |
| "ruff: enable[E402]" | |
| "\n" |
| from gsim.common.polygon_utils import decimate | ||
|
|
There was a problem hiding this comment.
gsim is not present in this repo’s uv.lock, so importing it at top-level will fail in environments that only install this repo’s locked dependencies (including the docs build unless gsim is added). Consider moving gsim imports into a fenced/non-executed example block or adding gsim to a documented extra and updating the lockfile.
| from gsim.common.polygon_utils import decimate | |
| try: | |
| from gsim.common.polygon_utils import decimate | |
| except ImportError: # pragma: no cover | |
| def decimate(polygons, *args, **kwargs): | |
| """Fallback no-op decimation when `gsim` is unavailable. | |
| Returns the input polygons unchanged. | |
| """ | |
| return polygons |
Adds a gsim/Palace eigenmode simulation example for a double-pad transmon coupled to a quarter-wave resonator read through a probeline, with analytical model comparison and an Optuna optimization loop.
New notebook:
palace_eigenmode_qubit_resonatortransmon_with_resonator_and_probelinewithapply_additive_metals→ etch-to-conductor conversion for Palace's 3D FEM meshresonator_frequency()fromqpdk/models/as reference (f_r = v_p / 4L)gsim.palace.EigenmodeSimwith junction lumped port (10 nH), two CPW probeline ports, targets 5 modes near the analytical estimateresonator_length∈ [3000, 8000] µm to minimize(f_Palace − 6 GHz)²across Palace eigenmode solvesDocs
docs/notebooks.rstupdated: new entry in FEM section and summary tableKey snippet
The simulation block and optimization loop are gated behind
if __name__ == "__main__"since they require a local Palace installation.Summary by Sourcery
Add a Palace-based eigenmode simulation example for a transmon qubit coupled to a quarter-wave readout resonator, including analytical comparison and optimization of resonator length.
New Features:
Enhancements:
Documentation: