Coverage for klayout_pex/fastercap/fastercap_runner.py: 93%
60 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 13:14 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 13:14 +0000
1#
2# --------------------------------------------------------------------------------
3# SPDX-FileCopyrightText: 2024-2025 Martin Jan Köhler and Harald Pretl
4# Johannes Kepler University, Institute for Integrated Circuits.
5#
6# This file is part of KPEX
7# (see https://github.com/iic-jku/klayout-pex).
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21# SPDX-License-Identifier: GPL-3.0-or-later
22# --------------------------------------------------------------------------------
23#
24import re
25import subprocess
26import time
27from typing import *
29from ..log import (
30 info,
31 # warning,
32 rule,
33 subproc,
34)
35from ..common.capacitance_matrix import CapacitanceMatrix
36from .fastercap_error import FasterCapError
39# NOTE: the exit codes of FasterCap 6.2 (see the README of iic-jku/FasterCap), earlier versions exit 0 or 1.
40# 75 fails the run too, as the matrix it prints, the one of the last iteration, misses the tolerance
41FASTERCAP_EXIT_CODE_MEANINGS: Dict[int, str] = {
42 1: "other error",
43 64: "wrong command line",
44 65: "the matrix has NaN or infinite entries",
45 66: "the input file can't be opened",
46 71: "out of memory",
47 74: "the input can't be read",
48 75: "GMRES didn't converge",
49 97: "the run can't go out-of-core",
50 98: "unexpected exception",
51 125: "stopped, e.g. by Ctrl-C or SIGTERM",
52}
55def run_fastercap(exe_path: str,
56 lst_file_path: str,
57 log_path: str,
58 tolerance: float,
59 d_coeff: float,
60 mesh_refinement_value: float,
61 ooc_condition: Optional[int],
62 auto_preconditioner: bool,
63 galerkin_scheme: bool,
64 jacobi_preconditioner: bool):
65 args = [
66 exe_path,
67 '-b', # console mode, without GUI
68 '-i', # Dump detailed time and memory information
69 '-v', # Verbose output
70 f"-a{tolerance}", # stop when relative error lower than threshold
71 f"-d{d_coeff}", # Direct potential interaction coefficient to mesh refinement ratio
72 f"-m{mesh_refinement_value}", # Mesh relative refinement value
73 ]
75 if ooc_condition is not None:
76 args += [f"-f{ooc_condition}"]
78 if auto_preconditioner:
79 args += ['-ap']
81 if galerkin_scheme:
82 args += ['-g']
84 if jacobi_preconditioner:
85 args += ['-pj']
87 args += [
88 lst_file_path
89 ]
90 info(f"Calling FasterCap")
91 subproc(f"{' '.join(args)}, output file: {log_path}")
93 rule('FasterCap Output')
94 start = time.time()
96 proc = subprocess.Popen(args,
97 stdin=subprocess.DEVNULL,
98 stdout=subprocess.PIPE,
99 stderr=subprocess.STDOUT,
100 universal_newlines=True,
101 text=True,
102 encoding='utf-8',
103 errors='replace')
104 with open(log_path, 'w', encoding='utf-8') as f:
105 while True:
106 line = proc.stdout.readline()
107 if not line:
108 break
109 subproc(line[:-1]) # remove newline
110 f.writelines([line])
111 proc.wait()
113 duration = time.time() - start
115 rule()
117 if proc.returncode == 0:
118 info(f"FasterCap succeeded after {'%.4g' % duration}s")
119 else:
120 meaning = FASTERCAP_EXIT_CODE_MEANINGS.get(proc.returncode, None)
121 raise FasterCapError(f"FasterCap failed with exit code {proc.returncode}"
122 f"{f' ({meaning})' if meaning else ''} after {'%.4g' % duration}s, "
123 f"see log file: {log_path}")
126def fastercap_parse_capacitance_matrix(log_path: str) -> CapacitanceMatrix:
127 with open(log_path, 'r', encoding='utf-8') as f:
128 rlines = f.readlines()
129 rlines.reverse()
131 # multiple iterations possible, find the last matrix
132 for idx, line in enumerate(rlines):
133 if line.strip() == "Capacitance matrix is:":
134 m = re.match(r'^Dimension (\d+) x (\d+)$', rlines[idx-1])
135 if not m:
136 raise FasterCapError(f"Could not parse capacitor matrix dimensions")
137 dim = int(m.group(1))
138 conductor_names: List[str] = []
139 rows: List[List[float]] = []
140 for i in reversed(range(idx-1-dim, idx-1)):
141 line = rlines[i].strip()
142 cells = [cell.strip() for cell in line.split(' ')]
143 cells = list(filter(lambda c: len(c) >= 1, cells))
144 conductor_names.append(cells[0])
145 row = [float(cell)/1e6 for cell in cells[1:]]
146 rows.append(row)
147 cm = CapacitanceMatrix(conductor_names=conductor_names, rows=rows)
148 return cm
150 raise FasterCapError(f"Could not extract capacitance matrix from FasterCap log file {log_path}")