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

50 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-17 19:08 +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 ExporterOptions, 

90 ExporterUnavailable, 

91 SolverTarget, 

92 export, 

93) 

94from .format_version import ( 

95 FORMAT_VERSION_MAJOR, 

96 FORMAT_VERSION_MINOR, 

97 FORMAT_VERSION_SUFFIX, 

98) 

99from .protobuf import ProtobufNotGeneratedError, kind_for_message 

100from .reader import ReadError, read_pex25d_text 

101from .resolver import ResolveError, resolve 

102from .show import show 

103from .validator import validate 

104from .writer import WriteError, write_pex25d_text 

105 

106read_text = read_pex25d_text 

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

108 

109write_text = write_pex25d_text 

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

111 

112 

113class _Proto: 

114 """ 

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

116 

117 Nothing is listed here: the modules come from 

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

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

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

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

122 package. 

123 """ 

124 

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

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

127 for kind in ArtifactKind: 

128 try: 

129 message_class = protobuf.message_class_for_kind(kind) 

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

131 continue 

132 classes[message_class.DESCRIPTOR.name] = message_class 

133 return classes 

134 

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

136 if name.startswith('_'): 

137 raise AttributeError(name) 

138 

139 if name in protobuf.schema_names(): 

140 return protobuf.schema_module(name) 

141 

142 message_classes = self._message_classes() 

143 if name in message_classes: 

144 return message_classes[name] 

145 

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

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

148 

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

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

151 

152 

153proto = _Proto() 

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

155 

156 

157kind_of = kind_for_message 

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

159 

160 

161def read(path: str, 

162 kind: ArtifactKind = ArtifactKind.AUTO, 

163 format: ArtifactFormat = ArtifactFormat.AUTO, 

164 report: Optional[DiagnosticsReport] = None, 

165 with_source_refs: bool = False) -> Any: 

166 """ 

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

168 

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

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

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

172 (``-`` is stdin). 

173 """ 

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

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

176 

177 

178def write(message: Any, 

179 path: str, 

180 kind: ArtifactKind = ArtifactKind.AUTO, 

181 format: ArtifactFormat = ArtifactFormat.AUTO, 

182 comments: bool = False) -> None: 

183 """ 

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

185 

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

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

188 

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

190 """ 

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

192 default_kind=kind_of(message)) 

193 save_artifact(message, spec, comments=comments) 

194 

195 

196__all__ = [ 

197 # verbs 

198 'read', 

199 'read_text', 

200 'write', 

201 'write_text', 

202 'resolve', 

203 'validate', 

204 'export', 

205 'show', 

206 # artifacts 

207 'ArtifactFormat', 

208 'ArtifactKind', 

209 'ArtifactSpec', 

210 'STDIO_PATH', 

211 'derive_path', 

212 'infer_artifact_spec', 

213 'kind_of', 

214 'load_artifact', 

215 'save_artifact', 

216 # diagnostics 

217 'Diagnostic', 

218 'DiagnosticsFormat', 

219 'DiagnosticsReport', 

220 'ExitCode', 

221 'Severity', 

222 'SourceRef', 

223 'Tier', 

224 # errors 

225 'ArtifactNamingError', 

226 'ExportError', 

227 'ExporterUnavailable', 

228 'ProtobufNotGeneratedError', 

229 'ReadError', 

230 'ResolveError', 

231 'WriteError', 

232 # the rest 

233 'ExporterOptions', 

234 'SolverTarget', 

235 'FORMAT_VERSION_MAJOR', 

236 'FORMAT_VERSION_MINOR', 

237 'FORMAT_VERSION_SUFFIX', 

238 'proto', 

239 # deprecated aliases, use read_text / write_text 

240 'read_pex25d_text', 

241 'write_pex25d_text', 

242]