librelane 2.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of librelane might be problematic. Click here for more details.
- librelane/__init__.py +38 -0
- librelane/__main__.py +479 -0
- librelane/__version__.py +43 -0
- librelane/common/__init__.py +63 -0
- librelane/common/cli.py +75 -0
- librelane/common/drc.py +246 -0
- librelane/common/generic_dict.py +319 -0
- librelane/common/metrics/__init__.py +35 -0
- librelane/common/metrics/__main__.py +413 -0
- librelane/common/metrics/library.py +354 -0
- librelane/common/metrics/metric.py +186 -0
- librelane/common/metrics/util.py +279 -0
- librelane/common/misc.py +456 -0
- librelane/common/ring_buffer.py +63 -0
- librelane/common/tcl.py +80 -0
- librelane/common/toolbox.py +549 -0
- librelane/common/tpe.py +41 -0
- librelane/common/types.py +116 -0
- librelane/config/__init__.py +32 -0
- librelane/config/__main__.py +155 -0
- librelane/config/config.py +1025 -0
- librelane/config/flow.py +490 -0
- librelane/config/pdk_compat.py +255 -0
- librelane/config/preprocessor.py +464 -0
- librelane/config/removals.py +45 -0
- librelane/config/variable.py +743 -0
- librelane/container.py +285 -0
- librelane/env_info.py +320 -0
- librelane/examples/spm/config.yaml +33 -0
- librelane/examples/spm/pin_order.cfg +14 -0
- librelane/examples/spm/src/impl.sdc +73 -0
- librelane/examples/spm/src/signoff.sdc +68 -0
- librelane/examples/spm/src/spm.v +73 -0
- librelane/examples/spm/verify/spm_tb.v +106 -0
- librelane/examples/spm-user_project_wrapper/SPM_example.v +286 -0
- librelane/examples/spm-user_project_wrapper/base_sdc_file.sdc +145 -0
- librelane/examples/spm-user_project_wrapper/config-tut.json +12 -0
- librelane/examples/spm-user_project_wrapper/config.json +13 -0
- librelane/examples/spm-user_project_wrapper/defines.v +66 -0
- librelane/examples/spm-user_project_wrapper/template.def +7656 -0
- librelane/examples/spm-user_project_wrapper/user_project_wrapper.v +123 -0
- librelane/flows/__init__.py +24 -0
- librelane/flows/builtins.py +18 -0
- librelane/flows/classic.py +327 -0
- librelane/flows/cli.py +463 -0
- librelane/flows/flow.py +1049 -0
- librelane/flows/misc.py +71 -0
- librelane/flows/optimizing.py +179 -0
- librelane/flows/sequential.py +367 -0
- librelane/flows/synth_explore.py +173 -0
- librelane/help/__main__.py +39 -0
- librelane/logging/__init__.py +40 -0
- librelane/logging/logger.py +323 -0
- librelane/open_pdks_rev +1 -0
- librelane/plugins.py +21 -0
- librelane/py.typed +0 -0
- librelane/scripts/base.sdc +80 -0
- librelane/scripts/klayout/Readme.md +2 -0
- librelane/scripts/klayout/open_design.py +63 -0
- librelane/scripts/klayout/render.py +121 -0
- librelane/scripts/klayout/stream_out.py +176 -0
- librelane/scripts/klayout/xml_drc_report_to_json.py +45 -0
- librelane/scripts/klayout/xor.drc +120 -0
- librelane/scripts/magic/Readme.md +1 -0
- librelane/scripts/magic/common/read.tcl +114 -0
- librelane/scripts/magic/def/antenna_check.tcl +35 -0
- librelane/scripts/magic/def/mag.tcl +19 -0
- librelane/scripts/magic/def/mag_gds.tcl +79 -0
- librelane/scripts/magic/drc.tcl +78 -0
- librelane/scripts/magic/extract_spice.tcl +98 -0
- librelane/scripts/magic/gds/drc_batch.tcl +74 -0
- librelane/scripts/magic/gds/erase_box.tcl +32 -0
- librelane/scripts/magic/gds/extras_mag.tcl +45 -0
- librelane/scripts/magic/gds/mag_with_pointers.tcl +31 -0
- librelane/scripts/magic/get_bbox.tcl +11 -0
- librelane/scripts/magic/lef/extras_maglef.tcl +61 -0
- librelane/scripts/magic/lef/maglef.tcl +26 -0
- librelane/scripts/magic/lef.tcl +57 -0
- librelane/scripts/magic/open.tcl +28 -0
- librelane/scripts/magic/wrapper.tcl +21 -0
- librelane/scripts/netgen/setup.tcl +28 -0
- librelane/scripts/odbpy/apply_def_template.py +49 -0
- librelane/scripts/odbpy/cell_frequency.py +107 -0
- librelane/scripts/odbpy/check_antenna_properties.py +116 -0
- librelane/scripts/odbpy/contextualize.py +109 -0
- librelane/scripts/odbpy/defutil.py +573 -0
- librelane/scripts/odbpy/diodes.py +373 -0
- librelane/scripts/odbpy/disconnected_pins.py +305 -0
- librelane/scripts/odbpy/eco_buffer.py +181 -0
- librelane/scripts/odbpy/eco_diode.py +139 -0
- librelane/scripts/odbpy/filter_unannotated.py +100 -0
- librelane/scripts/odbpy/io_place.py +482 -0
- librelane/scripts/odbpy/ioplace_parser/__init__.py +23 -0
- librelane/scripts/odbpy/ioplace_parser/parse.py +147 -0
- librelane/scripts/odbpy/label_macro_pins.py +277 -0
- librelane/scripts/odbpy/lefutil.py +97 -0
- librelane/scripts/odbpy/placers.py +162 -0
- librelane/scripts/odbpy/power_utils.py +397 -0
- librelane/scripts/odbpy/random_place.py +57 -0
- librelane/scripts/odbpy/reader.py +250 -0
- librelane/scripts/odbpy/remove_buffers.py +173 -0
- librelane/scripts/odbpy/snap_to_grid.py +57 -0
- librelane/scripts/odbpy/wire_lengths.py +93 -0
- librelane/scripts/openroad/antenna_check.tcl +20 -0
- librelane/scripts/openroad/antenna_repair.tcl +31 -0
- librelane/scripts/openroad/basic_mp.tcl +24 -0
- librelane/scripts/openroad/buffer_list.tcl +10 -0
- librelane/scripts/openroad/common/dpl.tcl +24 -0
- librelane/scripts/openroad/common/dpl_cell_pad.tcl +26 -0
- librelane/scripts/openroad/common/grt.tcl +32 -0
- librelane/scripts/openroad/common/io.tcl +540 -0
- librelane/scripts/openroad/common/pdn_cfg.tcl +135 -0
- librelane/scripts/openroad/common/resizer.tcl +103 -0
- librelane/scripts/openroad/common/set_global_connections.tcl +78 -0
- librelane/scripts/openroad/common/set_layer_adjustments.tcl +31 -0
- librelane/scripts/openroad/common/set_power_nets.tcl +30 -0
- librelane/scripts/openroad/common/set_rc.tcl +75 -0
- librelane/scripts/openroad/common/set_routing_layers.tcl +30 -0
- librelane/scripts/openroad/cts.tcl +80 -0
- librelane/scripts/openroad/cut_rows.tcl +24 -0
- librelane/scripts/openroad/dpl.tcl +24 -0
- librelane/scripts/openroad/drt.tcl +37 -0
- librelane/scripts/openroad/fill.tcl +30 -0
- librelane/scripts/openroad/floorplan.tcl +145 -0
- librelane/scripts/openroad/gpl.tcl +88 -0
- librelane/scripts/openroad/grt.tcl +30 -0
- librelane/scripts/openroad/gui.tcl +37 -0
- librelane/scripts/openroad/insert_buffer.tcl +127 -0
- librelane/scripts/openroad/ioplacer.tcl +67 -0
- librelane/scripts/openroad/irdrop.tcl +51 -0
- librelane/scripts/openroad/pdn.tcl +52 -0
- librelane/scripts/openroad/rcx.tcl +32 -0
- librelane/scripts/openroad/repair_design.tcl +70 -0
- librelane/scripts/openroad/repair_design_postgrt.tcl +48 -0
- librelane/scripts/openroad/rsz_timing_postcts.tcl +68 -0
- librelane/scripts/openroad/rsz_timing_postgrt.tcl +70 -0
- librelane/scripts/openroad/sta/check_macro_instances.tcl +53 -0
- librelane/scripts/openroad/sta/corner.tcl +393 -0
- librelane/scripts/openroad/tapcell.tcl +25 -0
- librelane/scripts/openroad/write_views.tcl +27 -0
- librelane/scripts/pyosys/construct_abc_script.py +177 -0
- librelane/scripts/pyosys/json_header.py +84 -0
- librelane/scripts/pyosys/synthesize.py +493 -0
- librelane/scripts/pyosys/ys_common.py +153 -0
- librelane/scripts/tclsh/hello.tcl +1 -0
- librelane/state/__init__.py +24 -0
- librelane/state/__main__.py +61 -0
- librelane/state/design_format.py +195 -0
- librelane/state/state.py +359 -0
- librelane/steps/__init__.py +61 -0
- librelane/steps/__main__.py +510 -0
- librelane/steps/checker.py +637 -0
- librelane/steps/common_variables.py +340 -0
- librelane/steps/cvc_rv.py +169 -0
- librelane/steps/klayout.py +509 -0
- librelane/steps/magic.py +576 -0
- librelane/steps/misc.py +160 -0
- librelane/steps/netgen.py +253 -0
- librelane/steps/odb.py +1088 -0
- librelane/steps/openroad.py +2460 -0
- librelane/steps/openroad_alerts.py +102 -0
- librelane/steps/pyosys.py +640 -0
- librelane/steps/step.py +1571 -0
- librelane/steps/tclstep.py +288 -0
- librelane/steps/verilator.py +222 -0
- librelane/steps/yosys.py +371 -0
- librelane-2.4.0.dist-info/METADATA +169 -0
- librelane-2.4.0.dist-info/RECORD +170 -0
- librelane-2.4.0.dist-info/WHEEL +4 -0
- librelane-2.4.0.dist-info/entry_points.txt +9 -0
librelane/common/drc.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Copyright 2020-2023 Efabless Corporation
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import io
|
|
15
|
+
import re
|
|
16
|
+
import json
|
|
17
|
+
import shlex
|
|
18
|
+
from enum import IntEnum
|
|
19
|
+
from decimal import Decimal, InvalidOperation
|
|
20
|
+
from dataclasses import dataclass, field, asdict
|
|
21
|
+
from typing import List, Optional, Tuple, Dict
|
|
22
|
+
|
|
23
|
+
BoundingBox = Tuple[Decimal, Decimal, Decimal, Decimal] # microns
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class Violation:
|
|
28
|
+
rules: List[Tuple[str, str]] # (layer, rule)
|
|
29
|
+
description: str
|
|
30
|
+
bounding_boxes: List[BoundingBox] = field(default_factory=list)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def layer(self) -> str:
|
|
34
|
+
return self.rules[0][0]
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def rule(self) -> str:
|
|
38
|
+
return self.rules[0][1]
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def category_name(self) -> str:
|
|
42
|
+
return f"{self.layer}.{self.rule}"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
illegal_overlap_rx = re.compile(r"between (\w+) and (\w+)")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class DRC:
|
|
50
|
+
"""
|
|
51
|
+
A primitive database representing DRC violations generated by a design.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
module: str
|
|
55
|
+
violations: Dict[str, Violation]
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def from_magic(
|
|
59
|
+
Self,
|
|
60
|
+
report: io.TextIOWrapper,
|
|
61
|
+
) -> Tuple["DRC", int]:
|
|
62
|
+
"""
|
|
63
|
+
Parses a report generated by Magic into a DRC object.
|
|
64
|
+
|
|
65
|
+
:param report: A **text** input stream containing the report in question.
|
|
66
|
+
You can pass the result of ``open("drc.rpt")``, for example.
|
|
67
|
+
:returns: A tuple of the DRC object and an int representing the number
|
|
68
|
+
of DRC violations.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
class State(IntEnum):
|
|
72
|
+
drc = 0
|
|
73
|
+
data = 1
|
|
74
|
+
header = 10
|
|
75
|
+
|
|
76
|
+
MAGIC_SPLIT_LINE = "-" * 40
|
|
77
|
+
MAGIC_RULE_LINE_PARSER = re.compile(r"^(.+?)(?:\s*\((.+)\))?$")
|
|
78
|
+
MAGIC_RULE_RX = re.compile(r"([\w\-]+)\.([\w\-]+)")
|
|
79
|
+
|
|
80
|
+
violations: Dict[str, Violation] = {}
|
|
81
|
+
|
|
82
|
+
violation: Optional[Violation] = None
|
|
83
|
+
state = State.header
|
|
84
|
+
module = "UNKNOWN"
|
|
85
|
+
counter = 0
|
|
86
|
+
bbox_count = 0
|
|
87
|
+
for i, line in enumerate(report):
|
|
88
|
+
line = line.strip()
|
|
89
|
+
if ("[INFO]" in line) or (line == ""):
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
if MAGIC_SPLIT_LINE in line:
|
|
93
|
+
if state.value > 0:
|
|
94
|
+
violation = None
|
|
95
|
+
state = State.drc
|
|
96
|
+
else:
|
|
97
|
+
state = State.data
|
|
98
|
+
elif state == State.header:
|
|
99
|
+
module = line
|
|
100
|
+
elif state == State.drc:
|
|
101
|
+
match = MAGIC_RULE_LINE_PARSER.match(line)
|
|
102
|
+
assert match is not None, "universal regex did not match string"
|
|
103
|
+
description = match[0]
|
|
104
|
+
rules = []
|
|
105
|
+
if rules_raw := match[2]:
|
|
106
|
+
for match in MAGIC_RULE_RX.finditer(rules_raw):
|
|
107
|
+
layer = match[1]
|
|
108
|
+
rule = match[2]
|
|
109
|
+
rules.append((layer, rule))
|
|
110
|
+
if len(rules) == 0:
|
|
111
|
+
rules = [("UNKNOWN", f"UNKNOWN{counter}")]
|
|
112
|
+
violation = Violation(rules, description)
|
|
113
|
+
counter += 1
|
|
114
|
+
elif state == State.data:
|
|
115
|
+
assert violation is not None, "Parser reached an inconsistent state"
|
|
116
|
+
try:
|
|
117
|
+
coord_list = [Decimal(coord[:-2]) for coord in line.split()]
|
|
118
|
+
except InvalidOperation:
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"invalid bounding box at line {i}: number is invalid"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if len(coord_list) != 4:
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"invalid bounding box at line {i}: bounding box has {len(coord_list)}/4 elements"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
bounding_box: BoundingBox = (
|
|
129
|
+
coord_list[0],
|
|
130
|
+
coord_list[1],
|
|
131
|
+
coord_list[2],
|
|
132
|
+
coord_list[3],
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
violation.bounding_boxes.append(bounding_box)
|
|
136
|
+
violations[violation.category_name] = violation
|
|
137
|
+
bbox_count += 1
|
|
138
|
+
|
|
139
|
+
return (Self(module, violations), bbox_count)
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def from_magic_feedback(
|
|
143
|
+
Self, feedback: io.TextIOWrapper, cif_scale: Decimal, module: str
|
|
144
|
+
) -> Tuple["DRC", int]:
|
|
145
|
+
bbox_count = 0
|
|
146
|
+
violations: Dict[str, Violation] = {}
|
|
147
|
+
last_bounding_box: Optional[BoundingBox] = None
|
|
148
|
+
lex = shlex.shlex(feedback.read(), posix=True)
|
|
149
|
+
components = list(lex)
|
|
150
|
+
while len(components):
|
|
151
|
+
instruction = components.pop(0)
|
|
152
|
+
if instruction == "box":
|
|
153
|
+
if len(components) < 4:
|
|
154
|
+
raise ValueError(
|
|
155
|
+
"Invalid syntax: 'box' command has less than 4 arguments"
|
|
156
|
+
)
|
|
157
|
+
lx, ly, ux, uy = components[0:4]
|
|
158
|
+
last_bounding_box = (
|
|
159
|
+
Decimal(lx) * cif_scale,
|
|
160
|
+
Decimal(ly) * cif_scale,
|
|
161
|
+
Decimal(ux) * cif_scale,
|
|
162
|
+
Decimal(uy) * cif_scale,
|
|
163
|
+
)
|
|
164
|
+
bbox_count += 1
|
|
165
|
+
components = components[4:]
|
|
166
|
+
elif instruction == "feedback":
|
|
167
|
+
try:
|
|
168
|
+
subcmd = components.pop(0)
|
|
169
|
+
except IndexError:
|
|
170
|
+
raise ValueError("feedback not given subcommand")
|
|
171
|
+
if subcmd != "add":
|
|
172
|
+
raise ValueError(f"Unsuppoorted feedback subcommand {subcmd}")
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
rule = components.pop(0)
|
|
176
|
+
_ = components.pop(0)
|
|
177
|
+
except IndexError:
|
|
178
|
+
raise ValueError(
|
|
179
|
+
"Invalid syntax: 'feedback add' command has less than 2 arguments"
|
|
180
|
+
)
|
|
181
|
+
vio_layer = "UNKNOWN"
|
|
182
|
+
vio_rulenum = f"UNKNOWN{len(violations)}"
|
|
183
|
+
if "Illegal overlap" in rule:
|
|
184
|
+
vio_rulenum = "ILLEGAL_OVERLAP"
|
|
185
|
+
if match := illegal_overlap_rx.search(rule):
|
|
186
|
+
vio_layer = "-".join((match[1], match[2]))
|
|
187
|
+
if rule not in violations:
|
|
188
|
+
violations[rule] = Violation([(vio_layer, vio_rulenum)], rule, [])
|
|
189
|
+
if last_bounding_box is None:
|
|
190
|
+
raise ValueError("Attempted to add feedback without a box selected")
|
|
191
|
+
violations[rule].bounding_boxes.append(last_bounding_box)
|
|
192
|
+
violations = {vio.category_name: vio for vio in violations.values()}
|
|
193
|
+
return (Self(module, violations), bbox_count)
|
|
194
|
+
|
|
195
|
+
def dumps(self):
|
|
196
|
+
"""
|
|
197
|
+
:returns: The DRC object as a JSON string.
|
|
198
|
+
"""
|
|
199
|
+
return json.dumps(asdict(self))
|
|
200
|
+
|
|
201
|
+
def to_klayout_xml(self, out: io.BufferedIOBase):
|
|
202
|
+
"""
|
|
203
|
+
Converts the DRC object to a KLayout-compatible XML database.
|
|
204
|
+
|
|
205
|
+
:param out: A **binary** output stream to the target XML file.
|
|
206
|
+
You can pass the result of ``open("drc.xml", "wb")``, for example.
|
|
207
|
+
"""
|
|
208
|
+
from lxml import etree as ET
|
|
209
|
+
|
|
210
|
+
with ET.xmlfile(out, encoding="utf8", buffered=False) as xf:
|
|
211
|
+
xf.write_declaration()
|
|
212
|
+
with xf.element("report-database"):
|
|
213
|
+
# 1. Cells
|
|
214
|
+
with xf.element("cells"):
|
|
215
|
+
with xf.element("cell"):
|
|
216
|
+
name = ET.Element("name")
|
|
217
|
+
name.text = self.module
|
|
218
|
+
xf.write(name)
|
|
219
|
+
# 2. Categories
|
|
220
|
+
with xf.element("categories"):
|
|
221
|
+
for _, violation in self.violations.items():
|
|
222
|
+
with xf.element("category"):
|
|
223
|
+
name = ET.Element("name")
|
|
224
|
+
name.text = violation.category_name
|
|
225
|
+
description = ET.Element("description")
|
|
226
|
+
description.text = violation.description
|
|
227
|
+
xf.write(name, description)
|
|
228
|
+
# 3. Items
|
|
229
|
+
with xf.element("items"):
|
|
230
|
+
for _, violation in self.violations.items():
|
|
231
|
+
for bounding_box in violation.bounding_boxes:
|
|
232
|
+
with xf.element("item"):
|
|
233
|
+
cell = ET.Element("cell")
|
|
234
|
+
cell.text = self.module
|
|
235
|
+
category = ET.Element("category")
|
|
236
|
+
category.text = f"'{violation.category_name}'"
|
|
237
|
+
visited = ET.Element("visited")
|
|
238
|
+
visited.text = "false"
|
|
239
|
+
multiplicity = ET.Element("multiplicity")
|
|
240
|
+
multiplicity.text = str(len(violation.bounding_boxes))
|
|
241
|
+
xf.write(cell, category, visited, multiplicity)
|
|
242
|
+
with xf.element("values"):
|
|
243
|
+
llx, lly, urx, ury = bounding_box
|
|
244
|
+
value = ET.Element("value")
|
|
245
|
+
value.text = f"polygon: ({llx},{lly};{urx},{lly};{urx},{ury};{llx},{ury})"
|
|
246
|
+
xf.write(value)
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# Copyright 2023 Efabless Corporation
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import os
|
|
15
|
+
import json
|
|
16
|
+
import dataclasses
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from decimal import Decimal
|
|
19
|
+
from collections import UserString
|
|
20
|
+
from typing import (
|
|
21
|
+
Any,
|
|
22
|
+
Callable,
|
|
23
|
+
Dict,
|
|
24
|
+
Hashable,
|
|
25
|
+
ItemsView,
|
|
26
|
+
Iterator,
|
|
27
|
+
Mapping,
|
|
28
|
+
Sequence,
|
|
29
|
+
Type,
|
|
30
|
+
TypeVar,
|
|
31
|
+
Tuple,
|
|
32
|
+
Optional,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from .misc import idem
|
|
36
|
+
from .types import is_string
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class GenericDictEncoder(json.JSONEncoder):
|
|
40
|
+
"""
|
|
41
|
+
A JSON encoder for :class:`GenericDict` objects. Also handles some types not
|
|
42
|
+
necessarily handled by the default JSON encoder, i.e., UserString, os.PathLike,
|
|
43
|
+
Decimals, etcetera.
|
|
44
|
+
|
|
45
|
+
It is recommended to use :meth:`GenericDict.to_json` unless you know what
|
|
46
|
+
you're doing.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def default(self, o):
|
|
50
|
+
if isinstance(o, GenericDict):
|
|
51
|
+
return o.to_raw_dict()
|
|
52
|
+
elif isinstance(o, os.PathLike) or isinstance(o, UserString):
|
|
53
|
+
return str(o)
|
|
54
|
+
elif not isinstance(o, type) and dataclasses.is_dataclass(o):
|
|
55
|
+
return dataclasses.asdict(o)
|
|
56
|
+
elif isinstance(o, Enum):
|
|
57
|
+
return o.name
|
|
58
|
+
elif isinstance(o, Decimal):
|
|
59
|
+
if o.is_infinite() or o.as_integer_ratio()[1] != 1:
|
|
60
|
+
return float(o)
|
|
61
|
+
else:
|
|
62
|
+
return int(o)
|
|
63
|
+
return o
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
KT = TypeVar("KT", bound=Hashable)
|
|
67
|
+
VT = TypeVar("VT")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class GenericDict(Mapping[KT, VT]):
|
|
71
|
+
"""
|
|
72
|
+
A dictionary with generic keys and values that is compatible with Python 3.8.1.
|
|
73
|
+
|
|
74
|
+
:param copying: A base Mapping object to copy values from.
|
|
75
|
+
:param overrides: Another mapping object to override the value from `copying`
|
|
76
|
+
with.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
_data: Dict[KT, VT]
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
copying: Optional[Mapping[KT, VT]] = None,
|
|
84
|
+
/,
|
|
85
|
+
overrides: Optional[Mapping[KT, VT]] = None,
|
|
86
|
+
) -> None:
|
|
87
|
+
super().__init__()
|
|
88
|
+
self.__data = {}
|
|
89
|
+
copying = copying or {}
|
|
90
|
+
overrides = overrides or {}
|
|
91
|
+
|
|
92
|
+
for key, value in copying.items():
|
|
93
|
+
self.__data[key] = value
|
|
94
|
+
for key, value in overrides.items():
|
|
95
|
+
self.__data[key] = value
|
|
96
|
+
|
|
97
|
+
def __getitem__(self, key: KT) -> VT:
|
|
98
|
+
return self.__data[key]
|
|
99
|
+
|
|
100
|
+
def __setitem__(self, key: KT, item: VT):
|
|
101
|
+
self.__data[key] = item
|
|
102
|
+
|
|
103
|
+
def __delitem__(self, key: KT):
|
|
104
|
+
del self.__data[key]
|
|
105
|
+
|
|
106
|
+
def __len__(self) -> int:
|
|
107
|
+
return len(self.__data)
|
|
108
|
+
|
|
109
|
+
def __repr__(self) -> str:
|
|
110
|
+
return self.to_raw_dict().__repr__()
|
|
111
|
+
|
|
112
|
+
def __iter__(self) -> Iterator[KT]:
|
|
113
|
+
return iter(self.__data)
|
|
114
|
+
|
|
115
|
+
def __eq__(self, __o: object) -> bool:
|
|
116
|
+
if not (isinstance(__o, GenericDict) or isinstance(__o, dict)):
|
|
117
|
+
raise NotImplementedError()
|
|
118
|
+
|
|
119
|
+
rhs = __o
|
|
120
|
+
if isinstance(__o, GenericDict):
|
|
121
|
+
rhs = __o.to_raw_dict()
|
|
122
|
+
|
|
123
|
+
lhs = self.to_raw_dict()
|
|
124
|
+
|
|
125
|
+
return rhs == lhs
|
|
126
|
+
|
|
127
|
+
# ---
|
|
128
|
+
for key in set(self.keys()).union(__o.keys()):
|
|
129
|
+
if key not in self or key not in __o:
|
|
130
|
+
return False
|
|
131
|
+
if self[key] != __o[key]:
|
|
132
|
+
return False
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
def pop(self, key: KT, /) -> VT:
|
|
136
|
+
"""
|
|
137
|
+
:param key: The key to pop the value for.
|
|
138
|
+
:returns: The value for key. Raises ``IndexError`` if the key does not
|
|
139
|
+
exist. The key/value pair is then deleted from the dictionary.
|
|
140
|
+
"""
|
|
141
|
+
value = self[key]
|
|
142
|
+
del self[key]
|
|
143
|
+
return value
|
|
144
|
+
|
|
145
|
+
T = TypeVar("T", bound="GenericDict")
|
|
146
|
+
|
|
147
|
+
def copy(self: T) -> T:
|
|
148
|
+
"""
|
|
149
|
+
Convenience replacement for `object.__class__(object)`, which would
|
|
150
|
+
create a copy of the ``GenericDict`` object.
|
|
151
|
+
|
|
152
|
+
:returns: The copy
|
|
153
|
+
"""
|
|
154
|
+
return self.__class__(self)
|
|
155
|
+
|
|
156
|
+
def to_raw_dict(self) -> dict:
|
|
157
|
+
"""
|
|
158
|
+
:returns: A copy of the underlying Python built-in ``dict`` for this class.
|
|
159
|
+
"""
|
|
160
|
+
return self.__data.copy()
|
|
161
|
+
|
|
162
|
+
def get_encoder(self) -> Type[GenericDictEncoder]:
|
|
163
|
+
"""
|
|
164
|
+
:returns: A JSON encoder handling GenericDict objects.
|
|
165
|
+
"""
|
|
166
|
+
return GenericDictEncoder
|
|
167
|
+
|
|
168
|
+
def keys(self):
|
|
169
|
+
"""
|
|
170
|
+
:returns: A set-like object providing a view of the keys of the GenericDict object.
|
|
171
|
+
"""
|
|
172
|
+
return self.__data.keys()
|
|
173
|
+
|
|
174
|
+
def values(self):
|
|
175
|
+
"""
|
|
176
|
+
:returns: A set-like object providing a view of the values of the GenericDict object.
|
|
177
|
+
"""
|
|
178
|
+
return self.__data.values()
|
|
179
|
+
|
|
180
|
+
def items(self) -> ItemsView[KT, VT]:
|
|
181
|
+
"""
|
|
182
|
+
:returns: A set-like object providing a view of the GenericDict object as (key, value) tuples.
|
|
183
|
+
"""
|
|
184
|
+
return self.__data.items()
|
|
185
|
+
|
|
186
|
+
def dumps(self, **kwargs) -> str:
|
|
187
|
+
"""
|
|
188
|
+
:param kwargs: Passed to ``json.dumps``.
|
|
189
|
+
:returns: A JSON string representing the the GenericDict object.
|
|
190
|
+
"""
|
|
191
|
+
if "indent" not in kwargs:
|
|
192
|
+
kwargs["indent"] = 4
|
|
193
|
+
return json.dumps(self.to_raw_dict(), cls=self.get_encoder(), **kwargs)
|
|
194
|
+
|
|
195
|
+
def check(self, key: KT, /) -> Tuple[Optional[KT], Optional[VT]]:
|
|
196
|
+
"""
|
|
197
|
+
Checks if a key exists and returns a tuple in the form ``(key, value)``.
|
|
198
|
+
|
|
199
|
+
:param key: The key in question
|
|
200
|
+
:returns: If the key does not exist, the value of ``key`` will be ``None`` and so will
|
|
201
|
+
``value``. If the key exists, ``key`` will be the key being checked for
|
|
202
|
+
existence and ``value`` will be the value assigned to said key in the
|
|
203
|
+
GenericDict object.
|
|
204
|
+
|
|
205
|
+
Do note ``None`` is a valid value for some keys, so simply
|
|
206
|
+
checking if the second element ``is not None`` is insufficient to check
|
|
207
|
+
whether a key exists.
|
|
208
|
+
"""
|
|
209
|
+
return (key if key in self.__data else None, self.get(key))
|
|
210
|
+
|
|
211
|
+
def update(self, incoming: "Mapping[KT, VT]"):
|
|
212
|
+
"""
|
|
213
|
+
A convenience function to update multiple values in the GenericDict object
|
|
214
|
+
at the same time.
|
|
215
|
+
:param incoming: The values to update
|
|
216
|
+
"""
|
|
217
|
+
for key, value in incoming.items():
|
|
218
|
+
self[key] = value
|
|
219
|
+
|
|
220
|
+
def update_reorder(self, incoming: "Mapping[KT, VT]"):
|
|
221
|
+
"""
|
|
222
|
+
A convenience function to update multiple values in the GenericDict object
|
|
223
|
+
at the same time. Pre-existing keys are deleted first so the values in
|
|
224
|
+
incoming are emplaced at the end of the dictionary.
|
|
225
|
+
|
|
226
|
+
:param incoming: The values to update
|
|
227
|
+
"""
|
|
228
|
+
for key, value in incoming.items():
|
|
229
|
+
if key in self:
|
|
230
|
+
del self[key]
|
|
231
|
+
self[key] = value
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class GenericImmutableDict(GenericDict[KT, VT]):
|
|
235
|
+
__lock: bool
|
|
236
|
+
|
|
237
|
+
def __init__(
|
|
238
|
+
self,
|
|
239
|
+
copying: Optional[Mapping[KT, VT]] = None,
|
|
240
|
+
/,
|
|
241
|
+
*args,
|
|
242
|
+
**kwargs,
|
|
243
|
+
) -> None:
|
|
244
|
+
super().__init__(copying, *args, **kwargs)
|
|
245
|
+
self.__lock = True
|
|
246
|
+
|
|
247
|
+
def __setitem__(self, key: KT, item: VT):
|
|
248
|
+
if self.__lock:
|
|
249
|
+
raise TypeError(f"{self.__class__.__name__} is immutable")
|
|
250
|
+
return super().__setitem__(key, item)
|
|
251
|
+
|
|
252
|
+
def __delitem__(self, key: KT):
|
|
253
|
+
if self.__lock:
|
|
254
|
+
raise TypeError(f"{self.__class__.__name__} is immutable")
|
|
255
|
+
return super().__delitem__(key)
|
|
256
|
+
|
|
257
|
+
def __delattr__(self, attr: str):
|
|
258
|
+
if self.__lock:
|
|
259
|
+
raise TypeError(f"{self.__class__.__name__} is immutable")
|
|
260
|
+
return super().__delattr__(attr)
|
|
261
|
+
|
|
262
|
+
def __setattr__(self, attr: str, value: Any):
|
|
263
|
+
try:
|
|
264
|
+
if self.__lock:
|
|
265
|
+
raise TypeError(f"{self.__class__.__name__} is immutable")
|
|
266
|
+
except AttributeError:
|
|
267
|
+
pass
|
|
268
|
+
return super().__setattr__(attr, value)
|
|
269
|
+
|
|
270
|
+
def copy_mut(self) -> GenericDict[KT, VT]:
|
|
271
|
+
return GenericDict(self)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# Screw this, if you can figure out how to type hint mapping in dictionary out
|
|
275
|
+
# and non-mapping in sequence out in Python, be my guest
|
|
276
|
+
def copy_recursive(input, translator: Callable = idem):
|
|
277
|
+
"""
|
|
278
|
+
Copies any arbitrarily-deep nested structure of Mappings and/or Sequences.
|
|
279
|
+
|
|
280
|
+
:param input: The input nested structure
|
|
281
|
+
:param translator: Before an object is appended, this function will be
|
|
282
|
+
called to process the value.
|
|
283
|
+
|
|
284
|
+
By default, :func:`idem` is called.
|
|
285
|
+
:returns: The copy.
|
|
286
|
+
|
|
287
|
+
All sequences will become built-in ``list``\\(s) and all mappings will
|
|
288
|
+
become built-in ``dict``\\(s).
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
def recursive(input, visit_stack: list, *, sequence_cls=list, mapping_cls=dict):
|
|
292
|
+
if id(input) in visit_stack:
|
|
293
|
+
raise ValueError("Circular reference found in target object")
|
|
294
|
+
|
|
295
|
+
visit_stack.append(id(input))
|
|
296
|
+
|
|
297
|
+
result: Any = input
|
|
298
|
+
|
|
299
|
+
if isinstance(input, Mapping): # Mappings are Sequences, but not vice versa
|
|
300
|
+
result = mapping_cls()
|
|
301
|
+
for key, value in input.items():
|
|
302
|
+
result[key] = recursive(value, visit_stack)
|
|
303
|
+
elif dataclasses.is_dataclass(input) and not isinstance(input, type):
|
|
304
|
+
replace = {}
|
|
305
|
+
as_dict = dataclasses.asdict(input)
|
|
306
|
+
for key, value in as_dict.items():
|
|
307
|
+
replace[key] = recursive(value, visit_stack)
|
|
308
|
+
result = dataclasses.replace(input, **replace)
|
|
309
|
+
elif not is_string(input) and isinstance(input, Sequence):
|
|
310
|
+
result = sequence_cls()
|
|
311
|
+
for value in input:
|
|
312
|
+
result.append(recursive(value, visit_stack))
|
|
313
|
+
|
|
314
|
+
visit_stack.pop()
|
|
315
|
+
|
|
316
|
+
result = translator(result)
|
|
317
|
+
return result
|
|
318
|
+
|
|
319
|
+
return recursive(input, [])
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Copyright 2023 Efabless Corporation
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# Copyright 2023 Efabless Corporation
|
|
15
|
+
#
|
|
16
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
17
|
+
# you may not use this file except in compliance with the License.
|
|
18
|
+
# You may obtain a copy of the License at
|
|
19
|
+
#
|
|
20
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
21
|
+
#
|
|
22
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
23
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
24
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
25
|
+
# See the License for the specific language governing permissions and
|
|
26
|
+
# limitations under the License.
|
|
27
|
+
"""
|
|
28
|
+
Metrics Module
|
|
29
|
+
-----------------------
|
|
30
|
+
|
|
31
|
+
Classes and functions for dealing with Metrics based on the `METRICS2.1 <https://github.com/ieee-ceda-datc/datc-rdf-Metrics4ML>`_ standard.
|
|
32
|
+
"""
|
|
33
|
+
from . import library
|
|
34
|
+
from .metric import MetricAggregator, MetricComparisonResult, Metric
|
|
35
|
+
from .util import parse_metric_modifiers, aggregate_metrics, MetricDiff
|