# Q2D Cross-Section Impedance of a Coplanar Waveguide

This notebook demonstrates how to extract the characteristic impedance
$Z_0$ of a coplanar waveguide (CPW) cross-section using the
Ansys 2D Extractor (Q2D) quasi-static field solver via PyAEDT.

The Q2D solver computes per-unit-length RLGC parameters from the
cross-sectional geometry, from which the characteristic impedance
can be obtained as a function of frequency.  We compare the
full-wave Q2D result against the analytical conformal-mapping estimate
from [`cpw_parameters()`](https://gdsfactory.github.io/quantum-rf-pdk/models.html.md#qpdk.models.cpw_parameters).

**Prerequisites:**

- Ansys Electronics Desktop installed (requires license)

**References:**

- PyAEDT Documentation: [https://aedt.docs.pyansys.com/](https://aedt.docs.pyansys.com/)
- Q2D Coplanar Waveguide Example: [https://examples.aedt.docs.pyansys.com/version/dev/examples/high_frequency/radiofrequency_mmwave/coplanar_waveguide.html](https://examples.aedt.docs.pyansys.com/version/dev/examples/high_frequency/radiofrequency_mmwave/coplanar_waveguide.html)
- Simons, *Coplanar Waveguide Circuits, Components, and Systems* [[Sim01](#id63)]

![CPW cross-section with center width and ground gaps labelled](notebooks/figures/cpw-cross-section.svg)

## Setup and Imports

```ipython3
import sys

if "google.colab" in sys.modules:
    import subprocess

    print("Running in Google Colab. Installing QPDK...")
    subprocess.check_call([
        sys.executable,
        "-m",
        "pip",
        "install",
        "-q",
        "qpdk[models,hfss] @ git+https://github.com/gdsfactory/quantum-rf-pdk.git",
    ])
```

```ipython3
import os
import tempfile
import time
from pathlib import Path
from typing import cast

import matplotlib.pyplot as plt
import numpy as np
from ansys.aedt.core import Q2d, settings
from ansys.aedt.core.visualization.post.post_common_3d import PostProcessor3D
from gplugins.ansys import Q2D, detach_desktop_logging, fit_view
from IPython.display import Image, display

from qpdk import PDK
from qpdk.config import PATH
from qpdk.models.cpw import cpw_parameters, get_cpw_dimensions
from qpdk.tech import LAYER_STACK, coplanar_waveguide, material_properties

PDK.activate()

# CPW dimensions
cpw_width = 10  # µm
cpw_gap = 6  # µm
cross_section = coplanar_waveguide(width=cpw_width, gap=cpw_gap)

# Analytical impedance estimate
ep_eff_analytical, z0_analytical = cpw_parameters(cpw_width, cpw_gap)

print(f"CPW dimensions: width = {cpw_width} µm, gap = {cpw_gap} µm")
print(
    f"Analytical estimate: Z₀ = {z0_analytical:.2f} Ω, ε_eff = {ep_eff_analytical:.4f}"
)
```

## Initialize Q2D Project

Set up an Ansys 2D Extractor project.  The Q2D solver uses a quasi-static
approach to compute per-unit-length transmission-line parameters from the
2D cross-section.

**Note:** This section requires Ansys Electronics Desktop to be installed
and licensed.

```ipython3
# Configuration for Q2D simulation
Q2D_CONFIG = {
    "sweep_start_ghz": 1.0,  # Sweep from 1 GHz
    "sweep_stop_ghz": 10.0,  # to 10 GHz
    "sweep_step_ghz": 0.1,  # 100 MHz step
}
```

```ipython3
# Ensure Ansys path is set so PyAEDT can find it
ansys_default_path = "/usr/ansys_inc/v252/AnsysEM"
if "ANSYSEM_ROOT252" not in os.environ and Path(ansys_default_path).exists():
    os.environ["ANSYSEM_ROOT252"] = ansys_default_path

settings.use_grpc_uds = False  # pyrefly: ignore[missing-attribute]  # not in pyaedt 1.9

# Create temporary directory for project
temp_dir = tempfile.TemporaryDirectory(suffix=".ansys_qpdk")
project_path = Path(temp_dir.name) / "cpw_q2d.aedt"

# Initialize Q2D
q2d = Q2d(
    project=str(project_path),
    design="CPW_Impedance",
    non_graphical=False,
    new_desktop=True,
    version="2025.2",
)
# PyAEDT logs through the desktop by default; reading it per message can drop the session.
detach_desktop_logging(q2d)

print(f"Q2D project created: {q2d.project_file}")
print(f"Design name: {q2d.design_name}")
```

```myst-ansi
PyAEDT INFO: Python version 3.12.13 (main, Mar  3 2026, 14:59:34) [Clang 21.1.4 ].
```

```myst-ansi
PyAEDT INFO: PyAEDT version 0.26.2.
```

```myst-ansi
PyAEDT INFO: Initializing new Desktop session.
```

```myst-ansi
PyAEDT INFO: AEDT version 2025.2.
```

```myst-ansi
PyAEDT INFO: New AEDT session is starting on gRPC port 57449.
```

```myst-ansi
PyAEDT INFO: Starting new AEDT gRPC session on port 57449.
```

```myst-ansi
PyAEDT INFO: Launching AEDT server with gRPC transport mode: TransportMode.UDS
```

```myst-ansi
PyAEDT INFO: Electronics Desktop started on gRPC port 57449 after 9.2 seconds.
```

```myst-ansi
PyAEDT INFO: AEDT installation Path /usr/ansys_inc/v252/AnsysEM
```

```myst-ansi
PyAEDT INFO: Connected to AEDT gRPC session on port 57449.
```

```myst-ansi
PyAEDT WARNING: Service Pack is not detected. PyAEDT is currently connecting in Insecure Mode.
```

```myst-ansi
PyAEDT WARNING: Please download and install latest Service Pack to use connect to AEDT in Secure Mode.
```

```myst-ansi
PyAEDT INFO: Project cpw_q2d has been created.
```

```myst-ansi
PyAEDT INFO: Added design 'CPW_Impedance' of type 2D Extractor.
```

```myst-ansi
PyAEDT INFO: AEDT objects correctly read
```

```myst-ansi
Q2D project created: /tmp/tmp70dmm41z.ansys_qpdk/cpw_q2d.aedt
Design name: CPW_Impedance
```

## Build CPW Cross-Section Geometry

Read the CPW width and gap back from the gdsfactory cross-section with
`get_cpw_dimensions()`, then build the CPW geometry
(signal conductor, ground planes, substrate) with
`create_2d_cpw()` on the `Substrate` and `M1`
levels of the QPDK layer stack.

```ipython3

# Create the Q2D simulation
q2d_sim = Q2D(q2d, material_properties=material_properties)

# Create the 2D cross-section geometry
xs_width, xs_gap = get_cpw_dimensions(cross_section)
object_names = q2d_sim.create_2d_cpw(
    xs_width,
    xs_gap,
    LAYER_STACK,
    substrate_level="Substrate",
    conductor_level="M1",
    ground_width=30,
)

print("Created Q2D geometry:")
for role, name in object_names.items():
    print(f"  {role}: {name}")
```

```myst-ansi
PyAEDT INFO: Materials class has been initialized! Elapsed time: 0m 0sec
```

```myst-ansi
PyAEDT INFO: Adding new material to the Project Library: Nb
```

```myst-ansi
PyAEDT INFO: Material has been added in Desktop.
```

```myst-ansi
PyAEDT INFO: Adding new material to the Project Library: Si
```

```myst-ansi
PyAEDT INFO: Material has been added in Desktop.
```

```myst-ansi
PyAEDT INFO: Adding new material to the Project Library: AlOx/Al
```

```myst-ansi
PyAEDT INFO: Material has been added in Desktop.
```

```myst-ansi
PyAEDT INFO: Adding new material to the Project Library: In
```

```myst-ansi
PyAEDT INFO: Material has been added in Desktop.
```

```myst-ansi
PyAEDT INFO: Modeler2D class has been initialized!
```

```myst-ansi
PyAEDT INFO: Modeler class has been initialized! Elapsed time: 0m 0sec
```

```myst-ansi
PyAEDT INFO: Boundary SignalLine signal has been created.
```

```myst-ansi
PyAEDT INFO: Boundary ReferenceGround gnd has been created.
```

```myst-ansi
PyAEDT INFO: Mesh class has been initialized! Elapsed time: 0m 0sec
```

```myst-ansi
PyAEDT INFO: Mesh class has been initialized! Elapsed time: 0m 0sec
```

```myst-ansi
Created Q2D geometry:
  signal: signal
  gnd_left: gnd_left
  gnd_right: gnd_right
  substrate: substrate
```

### Q2D Cross-Section Geometry

Here is the 2D geometry of the CPW cross-section in Ansys 2D Extractor.

![Q2D geometry](_static/images/q2d_cpw_impedance_geom.jpg)

```ipython3
# Ensure Q2D model fits the screen
fit_view(q2d)

# Save screenshot
img_dir = PATH.repo / "docs" / "_static" / "images"
img_dir.mkdir(parents=True, exist_ok=True)
q2d_img_path = img_dir / "q2d_cpw_impedance_geom.jpg"
cast(PostProcessor3D, q2d.post).export_model_picture(
    full_name=str(q2d_img_path), show_axis=True, show_grid=False, show_ruler=True
)

# Display in notebook
display(Image(filename=str(q2d_img_path)))
```

```myst-ansi
PyAEDT INFO: Parsing /tmp/tmp70dmm41z.ansys_qpdk/cpw_q2d.aedt.
```

```myst-ansi
PyAEDT INFO: File /tmp/tmp70dmm41z.ansys_qpdk/cpw_q2d.aedt correctly loaded. Elapsed time: 0m 0sec
```

```myst-ansi
PyAEDT INFO: aedt file load time 0.002233743667602539
```

```myst-ansi
PyAEDT INFO: PostProcessor class has been initialized! Elapsed time: 0m 0sec
```

```myst-ansi
PyAEDT INFO: Post class has been initialized! Elapsed time: 0m 0sec
```

```myst-ansi
<IPython.core.display.Image object>
```

## Configure Q2D Analysis

Set up the solution with a frequency sweep from 1 GHz to 10 GHz to compute
the characteristic impedance across the frequency range.

```ipython3
# Create setup
setup = q2d.create_setup(name="Q2DSetup")

# Add frequency sweep
sweep = setup.add_sweep(name="FrequencySweep")
sweep.props["RangeType"] = "LinearStep"
sweep.props["RangeStart"] = f"{Q2D_CONFIG['sweep_start_ghz']}GHz"
sweep.props["RangeStep"] = f"{Q2D_CONFIG['sweep_step_ghz']}GHz"
sweep.props["RangeEnd"] = f"{Q2D_CONFIG['sweep_stop_ghz']}GHz"
sweep.props["Type"] = "Interpolating"
sweep.update()

print("Q2D setup configured:")
print(
    f"  - Sweep range: {Q2D_CONFIG['sweep_start_ghz']} – {Q2D_CONFIG['sweep_stop_ghz']} GHz"
)
print(f"  - Step size: {Q2D_CONFIG['sweep_step_ghz']} GHz")
```

```myst-ansi
Q2D setup configured:
  - Sweep range: 1.0 – 10.0 GHz
  - Step size: 0.1 GHz
```

## Run Simulation

Execute the Q2D analysis.

```ipython3
print("Starting Q2D analysis...")
print("(This may take a few minutes)")

# Save project before analysis
q2d.save_project()

# Run the analysis
start_time = time.time()
success = q2d.analyze(cores=4)
elapsed = time.time() - start_time

if not success:
    raise RuntimeError("Q2D simulation failed. Check the AEDT log for details.")
else:
    print(f"Analysis completed in {elapsed:.1f} seconds")
```

```myst-ansi
Starting Q2D analysis...
(This may take a few minutes)
PyAEDT INFO: Project cpw_q2d Saved correctly
```

```myst-ansi
PyAEDT INFO: Project cpw_q2d Saved correctly
```

```myst-ansi
PyAEDT INFO: Key Desktop/ActiveDSOConfigurations/2D Extractor correctly changed.
```

```myst-ansi
PyAEDT INFO: Solving all design setups. Analysis started...
```

```myst-ansi
PyAEDT INFO: Design setup None solved correctly in 0.0h 0.0m 8.0s
```

```myst-ansi
PyAEDT INFO: Key Desktop/ActiveDSOConfigurations/2D Extractor correctly changed.
```

```myst-ansi
Analysis completed in 8.4 seconds
```

## Extract and Plot Impedance

Extract the characteristic impedance $Z_0$ from Q2D and compare it
with the analytical conformal-mapping estimate.  The analytical value is
shown as a horizontal dashed line.

```ipython3
# Extract Z0 from Q2D
data = q2d.post.get_solution_data(
    expressions="Z0(signal,signal)",
    context="Original",
    setup_sweep_name="Q2DSetup : FrequencySweep",
)
if isinstance(data, bool):
    raise TypeError(f"AEDT returned {data!r} instead of the Z0(signal,signal) data")

frequencies_ghz = np.array(data.primary_sweep_values)
_, z0_real = data.get_expression_data(formula="real")
z0_q2d = np.array(z0_real)

# --- Plot ---
fig, ax = plt.subplots(figsize=(10, 5))

# Q2D result
ax.plot(frequencies_ghz, z0_q2d, "b-", linewidth=2, label="Q2D (quasi-static)")

# Analytical estimate as a horizontal line
ax.axhline(
    y=float(z0_analytical),
    color="r",
    linestyle="--",
    linewidth=1.5,
    label=f"Analytical (conformal mapping): {z0_analytical:.2f} Ω",
)

ax.set_xlabel("Frequency (GHz)")
ax.set_ylabel("Characteristic Impedance $Z_0$ (Ω)")
ax.set_title("CPW Characteristic Impedance: Q2D vs. Analytical Estimate")
ax.legend()
ax.grid(True)
ax.set_xlim(Q2D_CONFIG["sweep_start_ghz"], Q2D_CONFIG["sweep_stop_ghz"])

plt.tight_layout()
plt.show()

# --- Numerical comparison ---
z0_mean_q2d = np.mean(z0_q2d)
relative_diff = (z0_mean_q2d - z0_analytical) / z0_analytical * 100

print("\n=== Impedance Comparison ===")
print("-" * 45)
print(f"Analytical Z₀ (conformal mapping): {z0_analytical:.2f} Ω")
print(f"Q2D Z₀ (mean over frequency):      {z0_mean_q2d:.2f} Ω")
print(f"Relative difference:                {relative_diff:+.2f}%")
print("-" * 45)
```

```myst-ansi
PyAEDT WARNING: No report category provided. Automatically identified Matrix
```

```myst-ansi
PyAEDT INFO: Solution Correctly loaded. Elapsed time: 0m 0sec
```

```myst-ansi
PyAEDT INFO: Solution Correctly parsed. Elapsed time: 0m 0sec
```

```myst-ansi
~/dev/quantum-rf-pdk/.venv/lib/python3.12/site-packages/ansys/aedt/core/visualization/post/solution_data.py:648: UserWarning: Method `data_real` is deprecated. Use :func:`get_expression_data` property instead.
  warnings.warn("Method `data_real` is deprecated. Use :func:`get_expression_data` property instead.")
```

```myst-ansi
<Figure size 1000x500 with 1 Axes>
```

```myst-ansi

=== Impedance Comparison ===
---------------------------------------------
Analytical Z₀ (conformal mapping): 49.31 Ω
Q2D Z₀ (mean over frequency):      44.72 Ω
Relative difference:                -9.32%
---------------------------------------------
```

## Cleanup

Close the Q2D session and clean up temporary files.

```ipython3
# Save and close
q2d.save_project()
# q2d.release_desktop()  # Uncomment to close the AEDT desktop session
time.sleep(2)

# Clean up temp directory
temp_dir.cleanup()
print("Q2D session closed and temporary files cleaned up")
```

```myst-ansi
PyAEDT INFO: Project cpw_q2d Saved correctly
```

```myst-ansi
Q2D session closed and temporary files cleaned up
```

## Summary

This notebook demonstrated:

1. **Cross-Section Definition**: Using QPDK’s `coplanar_waveguide` cross-section
   to define CPW geometry (10 µm width, 6 µm gap)
2. **Q2D Setup**: Initializing Ansys 2D Extractor via PyAEDT and building the
   cross-sectional geometry using
   `create_2d_cpw()`
3. **Impedance Extraction**: Running the Q2D quasi-static solver to compute
   $Z_0$ as a function of frequency from 1 to 10 GHz
4. **Analytical Validation**: Comparing the Q2D result with the conformal-mapping
   analytical estimate from [`cpw_parameters()`](https://gdsfactory.github.io/quantum-rf-pdk/models.html.md#qpdk.models.cpw_parameters)

**Key Points for CPW Design:**

- The Q2D solver gives frequency-dependent impedance including dispersion effects
- The analytical conformal-mapping model provides a good quasi-static estimate
- For superconducting CPWs (PEC conductors), the impedance is nearly
  frequency-independent in the low-GHz range
- No backplate metallisation is used, matching typical superconducting fabrication

**Next Steps:**

- Vary CPW dimensions to study impedance sensitivity
- Compare with HFSS 3D driven-modal simulations
- Study the effect of conductor thickness on impedance

## References
