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

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 * 

28 

29from ..log import ( 

30 info, 

31 # warning, 

32 rule, 

33 subproc, 

34) 

35from ..common.capacitance_matrix import CapacitanceMatrix 

36from .fastercap_error import FasterCapError 

37 

38 

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} 

53 

54 

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 ] 

74 

75 if ooc_condition is not None: 

76 args += [f"-f{ooc_condition}"] 

77 

78 if auto_preconditioner: 

79 args += ['-ap'] 

80 

81 if galerkin_scheme: 

82 args += ['-g'] 

83 

84 if jacobi_preconditioner: 

85 args += ['-pj'] 

86 

87 args += [ 

88 lst_file_path 

89 ] 

90 info(f"Calling FasterCap") 

91 subproc(f"{' '.join(args)}, output file: {log_path}") 

92 

93 rule('FasterCap Output') 

94 start = time.time() 

95 

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() 

112 

113 duration = time.time() - start 

114 

115 rule() 

116 

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}") 

124 

125 

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() 

130 

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 

149 

150 raise FasterCapError(f"Could not extract capacitance matrix from FasterCap log file {log_path}")