Coverage for klayout_pex/pex25d/__init__.py: 98%

51 statements  

« prev     ^ index     » next       coverage.py v7.17.2, created at 2026-11-06 13:14 +0000

1# 

2# -------------------------------------------------------------------------------- 

3# SPDX-FileCopyrightText: 2024-2026 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# 

24""" 

25The PEX25D Module 

26----------------- 

27 

28Support for the **PEX25D** interchange format for 2.5D parasitic extraction 

29(see https://github.com/iic-jku/klayout-pex/issues/184). 

30 

31The pipeline is:: 

32 

33 PEX25D (text) –[reader]→ PEX25DFile (protobuf) –[resolver]→ PEX25DScene (protobuf) –[adapter]→ solver 

34 

35One import covers it:: 

36 

37 from klayout_pex import pex25d 

38 

39 file = pex25d.read('cell.pex25d') 

40 scene = pex25d.resolve(file) 

41 report = pex25d.validate(file, scene=scene) 

42 pex25d.write(scene, 'cell.pex25d.scene.pb') 

43 

44``read`` and ``write`` take a path in any of the encodings the format defines, 

45``read_text`` and ``write_text`` the bytes of the text format. Everything named 

46in ``__all__`` is the supported surface; the submodules stay importable for the 

47classes behind the verbs (``resolver.Resolver``, ``validator.Validator``). 

48 

49Dependency rule 

50~~~~~~~~~~~~~~~ 

51 

52This package depends on ``protobuf`` only — no ``klayout`` import, directly or 

53transitively. That is what lets the standalone ``pex25d`` tool be installed and 

54run by other groups as a reference validator, without dragging in the whole LVS 

55machinery. Generating PEX25D from a layout does need that machinery, and lives 

56in :mod:`klayout_pex.klayout.pex25d_builder` instead. 

57 

58The generated ``*_pb2`` modules are build output, so nothing here imports one 

59while this module is imported: :data:`proto` and the verbs reach them on demand 

60and report a missing build as :class:`ProtobufNotGeneratedError`. 

61""" 

62 

63from __future__ import annotations 

64 

65from typing import * 

66 

67from . import protobuf 

68from .artifact import ( 

69 ArtifactFormat, 

70 ArtifactKind, 

71 ArtifactNamingError, 

72 ArtifactSpec, 

73 STDIO_PATH, 

74 derive_path, 

75 infer_artifact_spec, 

76) 

77from .codec import load_artifact, save_artifact 

78from .diagnostics import ( 

79 Diagnostic, 

80 DiagnosticsFormat, 

81 DiagnosticsReport, 

82 ExitCode, 

83 Severity, 

84 SourceRef, 

85 Tier, 

86) 

87from .exporters import ( 

88 ExportError, 

89 ExporterExecutionError, 

90 ExporterUnavailable, 

91 export, 

92 export_with_backend, 

93 exporter_registry, 

94) 

95from .importers import ( 

96 ImporterError, 

97 ImporterExecutionError, 

98 ImporterUnavailable, 

99 import_file, 

100 importer_registry, 

101) 

102from .format_version import ( 

103 FORMAT_VERSION_MAJOR, 

104 FORMAT_VERSION_MINOR, 

105 FORMAT_VERSION_SUFFIX, 

106) 

107from .protobuf import ProtobufNotGeneratedError, kind_for_message 

108from .reader import ReadError, read_pex25d_text 

109from .resolver import ResolveError, resolve 

110from .show import show 

111from .validator import validate 

112from .writer import WriteError, write_pex25d_text 

113 

114read_text = read_pex25d_text 

115"""Parse the bytes of the PEX25D text format into a ``PEX25DFile``.""" 

116 

117write_text = write_pex25d_text 

118"""Render a ``PEX25DFile`` or ``PEX25DScene`` as PEX25D text bytes.""" 

119 

120 

121class _Proto: 

122 """ 

123 The generated PEX25D protobuf modules and their top-level messages. 

124 

125 Nothing is listed here: the modules come from 

126 :func:`protobuf.schema_names` and the messages from 

127 :func:`protobuf.message_class_for_kind`, so a schema change that adds, 

128 renames or removes a ``.proto`` needs no edit. Attribute access imports on 

129 demand, so that a tree without the generated modules still imports this 

130 package. 

131 """ 

132 

133 def _message_classes(self) -> Dict[str, Any]: 

134 classes: Dict[str, Any] = {} 

135 for kind in ArtifactKind: 

136 try: 

137 message_class = protobuf.message_class_for_kind(kind) 

138 except ValueError: # a kind that describes no message, i.e. AUTO 

139 continue 

140 classes[message_class.DESCRIPTOR.name] = message_class 

141 return classes 

142 

143 def __getattr__(self, name: str) -> Any: 

144 if name.startswith('_'): 

145 raise AttributeError(name) 

146 

147 if name in protobuf.schema_names(): 

148 return protobuf.schema_module(name) 

149 

150 message_classes = self._message_classes() 

151 if name in message_classes: 

152 return message_classes[name] 

153 

154 raise AttributeError(f"PEX25D has no protobuf module or message " 

155 f"'{name}', expected one of {', '.join(dir(self))}") 

156 

157 def __dir__(self) -> List[str]: 

158 return sorted([*protobuf.schema_names(), *self._message_classes()]) 

159 

160 

161proto = _Proto() 

162"""The generated messages, e.g. ``proto.PEX25DFile()`` or ``proto.dielectric``.""" 

163 

164 

165kind_of = kind_for_message 

166"""The :class:`ArtifactKind` a generated message holds.""" 

167 

168 

169def read(path: str, 

170 kind: ArtifactKind = ArtifactKind.AUTO, 

171 format: ArtifactFormat = ArtifactFormat.AUTO, 

172 report: Optional[DiagnosticsReport] = None, 

173 with_source_refs: bool = False) -> Any: 

174 """ 

175 Read a PEX25D artifact and return the message it holds. 

176 

177 Kind and encoding come from the path's suffix, which is why the 

178 conventional suffixes matter: ``.pex25d.scene.pb`` is a scene, ``.pex25d`` 

179 a file in text format. State them explicitly for a path that has none 

180 (``-`` is stdin). 

181 """ 

182 spec = infer_artifact_spec(path, kind=kind, format=format) 

183 return load_artifact(spec, report=report, with_source_refs=with_source_refs) 

184 

185 

186def write(message: Any, 

187 path: str, 

188 kind: ArtifactKind = ArtifactKind.AUTO, 

189 format: ArtifactFormat = ArtifactFormat.AUTO, 

190 comments: bool = False) -> None: 

191 """ 

192 Write a ``PEX25DFile`` or ``PEX25DScene`` to ``path``. 

193 

194 The encoding comes from the suffix; the kind is the message's own, so that 

195 a scene is never written under a spec that says file (``-`` is stdout). 

196 

197 :param comments: emit the specification's syntax hints. Text format only. 

198 """ 

199 spec = infer_artifact_spec(path, kind=kind, format=format, 

200 default_kind=kind_of(message)) 

201 save_artifact(message, spec, comments=comments) 

202 

203 

204__all__ = [ 

205 # verbs 

206 'read', 

207 'read_text', 

208 'write', 

209 'write_text', 

210 'resolve', 

211 'validate', 

212 'export', 

213 'export_with_backend', 

214 'exporter_registry', 

215 'import_file', 

216 'importer_registry', 

217 'show', 

218 # artifacts 

219 'ArtifactFormat', 

220 'ArtifactKind', 

221 'ArtifactSpec', 

222 'STDIO_PATH', 

223 'derive_path', 

224 'infer_artifact_spec', 

225 'kind_of', 

226 'load_artifact', 

227 'save_artifact', 

228 # diagnostics 

229 'Diagnostic', 

230 'DiagnosticsFormat', 

231 'DiagnosticsReport', 

232 'ExitCode', 

233 'Severity', 

234 'SourceRef', 

235 'Tier', 

236 # errors 

237 'ArtifactNamingError', 

238 'ExportError', 

239 'ExporterExecutionError', 

240 'ExporterUnavailable', 

241 'ImporterError', 

242 'ImporterExecutionError', 

243 'ImporterUnavailable', 

244 'ProtobufNotGeneratedError', 

245 'ReadError', 

246 'ResolveError', 

247 'WriteError', 

248 # the rest 

249 'FORMAT_VERSION_MAJOR', 

250 'FORMAT_VERSION_MINOR', 

251 'FORMAT_VERSION_SUFFIX', 

252 'proto', 

253 # deprecated aliases, use read_text / write_text 

254 'read_pex25d_text', 

255 'write_pex25d_text', 

256]