1. Capacitance extraction of a coaxial cable

1.1. Requirements

1.1.1. Software components

  • QTCAD

  • Gmsh

1.1.2. Geometry file

  • qtcad/examples/tutorials/meshes/coaxial_cable.py

1.1.3. Python script

  • qtcad/examples/tutorials/cap_coaxial_cable.py

1.1.4. References

1.2. Briefing

In this tutorial, we explain how to perform capacitance extraction for a coaxial cable. It is assumed that the user is familiar with basic Python. It is also assumed that the user has access to the script that uses Gmsh to generate the mesh for the coaxial cable device. Such files for all the QTCAD tutorials are stored in the folder examples/tutorials/meshes. Some of the files are written in Gmsh’s scripting language and have an extension .geo, while the .py` files use Gmsh’s Python interface.

The outer surface of the cable is a signal conductor (labeled by the physical group "signal"). The ground conductor ("gnd") is on the inside. The medium between the two conductors is called "domain".

The full code may be found at the bottom of this page, or in qtcad/examples/tutorials/cap_coaxial_cable.py.

Do not hesitate to look into the API reference for detailed descriptions of the inputs and outputs of each class, method, or function used in the tutorials. The API reference also contains documentation for many other useful features not described here.

1.3. Mesh generation

The mesh is generated by executing the script qtcad/examples/tutorials/meshes/coaxial_cable.py.

This wil produce a .msh file in the same directory as the mesh generation script. See Arbitrary geometry definition with Gmsh for more information about interfacing with Gmsh.

1.4. Setting up the device and extracting the capacitance

1.4.2. Input parameters

# input parameters

eps_rel = 10 # relative permittivity of the dielectric

length = 50e-3 # length of the cable (in meters)
r_inner = 2e-3 # inner radius of the cable
r_outer = 5e-3 # outer radius of the cable

The first parameter is the relative permittivity of the dielectric that will be used later to define the medium in the domain region. The next three parameters describe the geometry of the dielectric and are the same ones in qtcad/examples/tutorials/meshes/coaxial_cable.py, except written in SI units as opposed to millimeters. These values are used later to compute the theoretical value of the capacitance.

1.4.3. Directories and file paths

The code below defines paths to the input mesh file and the folder where solver results are to be saved. It also checks that the mesh file exists.

# directories and file paths
script_dir = Path(__file__).parent.resolve()
mesh_dir = script_dir / "meshes"  # for the mesh file
result_dir = script_dir / "output" / Path(__file__).stem  # for results
fpath_mesh = mesh_dir / "coaxial_cable.msh"  # mesh file

# code to check that the mesh file exists
if not os.path.isfile(fpath_mesh):
   raise Exception(
      "Please run %s/coaxial_cable.py to generate the mesh and raw geometry files."
      % (mesh_dir)
   )

1.4.4. Defining the mesh

The mesh is defined by loading the mesh file produced by Gmsh and setting units of length.

# scale in the Gmsh files
scale = 1e-3  # Gmsh files are in mm

# parse the mesh
mesh = Mesh(scale, fpath_mesh)

The two arguments of the constructor of the Mesh class are scaling and fpath_mesh. Argument fpath_mesh is the string or pathlib.Path object pointing to the mesh file to use. Argument scaling indicates a number by which all coordinates in the mesh file should be multiplied. Since QTCAD uses SI units, the value chosen here (1e-3) indicates that lengths in the mesh file are millimeters. It is recommended to choose the units in the mesh file that avoid very small or very large values for the dimensions.

The mesh may also be displayed using:

# Plot the mesh
mesh.show()

This will call the Visualization ToolKit (VTK) through its Python wrapper (PyVista) to produce a 3D representation of the mesh, like this:

Mesh illustration

Fig. 1.4.11 Mesh illustration

1.4.5. Creating the device

The device to simulate is then initialized on the mesh defined above:

# Create device from mesh.
dvc = Device(mesh)

1.4.6. Specifying physical regions and boundaries

It is now time to specify the value of device parameters (permittivity for the case of capacitance solver) at each point of the mesh over which the device is build. This may be done explicitly for each element or node of the mesh; however this approach is tedious and produces code that is prone to bugs and difficult to read. A better way is to leverage physical volumes and surfaces defined in the geometry file to define regions and boundaries.

Regions are a collection of elements that belong to a given 3D physical group. A physical group is a set of elements associated with an integer or string label in the geometry file. It is possible to define regions based on physical groups, and specify a material for such a region. In the present example, this is done as follows.

First, we define a material with relative permittivity of 10.

# define the medium in the problem
material_properties = dict(name="arbitrary_medium", eps=eps_rel * ct.eps0)
material = mt.Material(material_properties)

The materials module contains material properties for many commonly used media, such as vacuum, silicon, silicon dioxide, and others. A table of materials with their properties is available in the Material list section. For example, in order to use silicon in place of the arbitrary medium above, one can replace the second line above with material = mt.Si.

The next step is to assign material to the "domain" region.

# Create regions first (here only one region),
# Make sure each element is assigned to a region
dvc.new_region("domain", material)

Remark: When writing geometry files, it is important to make sure that all elements in a given mesh belong to one and only one region. If an element belongs to two or more regions at the same time, an exception will be raised.

In addition to 3D physical groups (physical volumes), Gmsh supports 2D physical groups (physical surfaces), which also associate certain two-dimensional elements to a label. These may be leveraged to define boundaries as follows:

# Set potential to zero on the surfaces of each conductor (including ground)
dvc.new_dirichlet_bnd("gnd", 0.0)
dvc.new_dirichlet_bnd("signal", 0.0)

In the code above, we associate the Gmsh physical surfaces gnd and signal with homogeneous Dirichlet boundary conditions. In the capacitance solver, boundaries with this boundary condition are treated as perfect electric conductors.

Note

The two flat annular surfaces on both ends of the coaxial cable have no boundary conditions or Gmsh physical surfaces associated with them. These boundaries will be treated as per natural boundary conditions explained in the capacitance solver theory and linear Poisson equation solver theory.

1.4.7. Creating and running the capacitance solver

Next, we define the signal conductors (see Signal and ground conductors). In this case, we have a single signal conductor signal and a single ground conductor gnd. Hence, the capacitance matrix will be a 1 × 1 matrix, containing the capacitance between the two conductors. See Capacitance Matrix for more information.

# Set up signal conductors
signal_conductors = ["signal"]

Signal conductors can also include multiple surfaces (see Signal and ground conductors for details).

Next, we create the solver object and run the solver:

# setup and run the solver
cap_solver = cap.Solver(dvc, signal_conductors)
c = cap_solver.solve()

As the arguments to the Solver constructor, we specify the device and signal conductor created earlier. Here, the solver is created with default parameters, which is why the SolverParams class is not used. In the next tutorial you will find details on using this class to specify parameters of the solver.

The output of the solver is a capacitance matrix, represented as a dictionary. The following code displays the solver result, along with the theoretical value.

# print the capacitance
print(
   "Capacitance between the signal and ground lines: %.2f pF"
   % (c["signal", "signal"] * 1e12)
)

# compute and print the theoretical value
# See Pozar, David M. "Microwave engineering." Fourth Edition, John Wiley & Sons, Inc (2012).
theor_value = 2 * math.pi * material.eps * length / math.log(r_outer / r_inner)
print("Theoretical value of capacitance is            : %.2f pF" % (theor_value * 1e12))

The resulting values are shown below:

Capacitance between the signal and ground lines: 30.06 pF
Theoretical value of capacitance is            : 30.36 pF

1.5. Full code

__copyright__ = "Copyright 2022-2025, Nanoacademic Technologies Inc."

import qtcad.device.capacitance as cap
from qtcad.device.mesh3d import Mesh
from qtcad.device import Device
from qtcad.device import materials as mt
from qtcad.device import constants as ct
from pathlib import Path
import os
import math

# input parameters

eps_rel = 10 # relative permittivity of the dielectric

length = 50e-3 # length of the cable (in meters)
r_inner = 2e-3 # inner radius of the cable
r_outer = 5e-3 # outer radius of the cable

# directories and file paths
script_dir = Path(__file__).parent.resolve()
mesh_dir = script_dir / "meshes"  # for the mesh file
result_dir = script_dir / "output" / Path(__file__).stem  # for results
fpath_mesh = mesh_dir / "coaxial_cable.msh"  # mesh file

# code to check that the mesh file exists
if not os.path.isfile(fpath_mesh):
   raise Exception(
      "Please run %s/coaxial_cable.py to generate the mesh and raw geometry files."
      % (mesh_dir)
   )


# scale in the Gmsh files
scale = 1e-3  # Gmsh files are in mm

# parse the mesh
mesh = Mesh(scale, fpath_mesh)


# Plot the mesh
mesh.show()

# Create device from mesh.
dvc = Device(mesh)


# define the medium in the problem
material_properties = dict(name="arbitrary_medium", eps=eps_rel * ct.eps0)
material = mt.Material(material_properties)

# Create regions first (here only one region), 
# Make sure each element is assigned to a region
dvc.new_region("domain", material)

# Set potential to zero on the surfaces of each conductor (including ground)
dvc.new_dirichlet_bnd("gnd", 0.0)
dvc.new_dirichlet_bnd("signal", 0.0)

# Set up signal conductors
signal_conductors = ["signal"]

# setup and run the solver
cap_solver = cap.Solver(dvc, signal_conductors)
c = cap_solver.solve()

# print the capacitance
print(
   "Capacitance between the signal and ground lines: %.2f pF"
   % (c["signal", "signal"] * 1e12)
)

# compute and print the theoretical value
# See Pozar, David M. "Microwave engineering." Fourth Edition, John Wiley & Sons, Inc (2012).
theor_value = 2 * math.pi * material.eps * length / math.log(r_outer / r_inner)
print("Theoretical value of capacitance is            : %.2f pF" % (theor_value * 1e12))