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.

Files changed (170) hide show
  1. librelane/__init__.py +38 -0
  2. librelane/__main__.py +479 -0
  3. librelane/__version__.py +43 -0
  4. librelane/common/__init__.py +63 -0
  5. librelane/common/cli.py +75 -0
  6. librelane/common/drc.py +246 -0
  7. librelane/common/generic_dict.py +319 -0
  8. librelane/common/metrics/__init__.py +35 -0
  9. librelane/common/metrics/__main__.py +413 -0
  10. librelane/common/metrics/library.py +354 -0
  11. librelane/common/metrics/metric.py +186 -0
  12. librelane/common/metrics/util.py +279 -0
  13. librelane/common/misc.py +456 -0
  14. librelane/common/ring_buffer.py +63 -0
  15. librelane/common/tcl.py +80 -0
  16. librelane/common/toolbox.py +549 -0
  17. librelane/common/tpe.py +41 -0
  18. librelane/common/types.py +116 -0
  19. librelane/config/__init__.py +32 -0
  20. librelane/config/__main__.py +155 -0
  21. librelane/config/config.py +1025 -0
  22. librelane/config/flow.py +490 -0
  23. librelane/config/pdk_compat.py +255 -0
  24. librelane/config/preprocessor.py +464 -0
  25. librelane/config/removals.py +45 -0
  26. librelane/config/variable.py +743 -0
  27. librelane/container.py +285 -0
  28. librelane/env_info.py +320 -0
  29. librelane/examples/spm/config.yaml +33 -0
  30. librelane/examples/spm/pin_order.cfg +14 -0
  31. librelane/examples/spm/src/impl.sdc +73 -0
  32. librelane/examples/spm/src/signoff.sdc +68 -0
  33. librelane/examples/spm/src/spm.v +73 -0
  34. librelane/examples/spm/verify/spm_tb.v +106 -0
  35. librelane/examples/spm-user_project_wrapper/SPM_example.v +286 -0
  36. librelane/examples/spm-user_project_wrapper/base_sdc_file.sdc +145 -0
  37. librelane/examples/spm-user_project_wrapper/config-tut.json +12 -0
  38. librelane/examples/spm-user_project_wrapper/config.json +13 -0
  39. librelane/examples/spm-user_project_wrapper/defines.v +66 -0
  40. librelane/examples/spm-user_project_wrapper/template.def +7656 -0
  41. librelane/examples/spm-user_project_wrapper/user_project_wrapper.v +123 -0
  42. librelane/flows/__init__.py +24 -0
  43. librelane/flows/builtins.py +18 -0
  44. librelane/flows/classic.py +327 -0
  45. librelane/flows/cli.py +463 -0
  46. librelane/flows/flow.py +1049 -0
  47. librelane/flows/misc.py +71 -0
  48. librelane/flows/optimizing.py +179 -0
  49. librelane/flows/sequential.py +367 -0
  50. librelane/flows/synth_explore.py +173 -0
  51. librelane/help/__main__.py +39 -0
  52. librelane/logging/__init__.py +40 -0
  53. librelane/logging/logger.py +323 -0
  54. librelane/open_pdks_rev +1 -0
  55. librelane/plugins.py +21 -0
  56. librelane/py.typed +0 -0
  57. librelane/scripts/base.sdc +80 -0
  58. librelane/scripts/klayout/Readme.md +2 -0
  59. librelane/scripts/klayout/open_design.py +63 -0
  60. librelane/scripts/klayout/render.py +121 -0
  61. librelane/scripts/klayout/stream_out.py +176 -0
  62. librelane/scripts/klayout/xml_drc_report_to_json.py +45 -0
  63. librelane/scripts/klayout/xor.drc +120 -0
  64. librelane/scripts/magic/Readme.md +1 -0
  65. librelane/scripts/magic/common/read.tcl +114 -0
  66. librelane/scripts/magic/def/antenna_check.tcl +35 -0
  67. librelane/scripts/magic/def/mag.tcl +19 -0
  68. librelane/scripts/magic/def/mag_gds.tcl +79 -0
  69. librelane/scripts/magic/drc.tcl +78 -0
  70. librelane/scripts/magic/extract_spice.tcl +98 -0
  71. librelane/scripts/magic/gds/drc_batch.tcl +74 -0
  72. librelane/scripts/magic/gds/erase_box.tcl +32 -0
  73. librelane/scripts/magic/gds/extras_mag.tcl +45 -0
  74. librelane/scripts/magic/gds/mag_with_pointers.tcl +31 -0
  75. librelane/scripts/magic/get_bbox.tcl +11 -0
  76. librelane/scripts/magic/lef/extras_maglef.tcl +61 -0
  77. librelane/scripts/magic/lef/maglef.tcl +26 -0
  78. librelane/scripts/magic/lef.tcl +57 -0
  79. librelane/scripts/magic/open.tcl +28 -0
  80. librelane/scripts/magic/wrapper.tcl +21 -0
  81. librelane/scripts/netgen/setup.tcl +28 -0
  82. librelane/scripts/odbpy/apply_def_template.py +49 -0
  83. librelane/scripts/odbpy/cell_frequency.py +107 -0
  84. librelane/scripts/odbpy/check_antenna_properties.py +116 -0
  85. librelane/scripts/odbpy/contextualize.py +109 -0
  86. librelane/scripts/odbpy/defutil.py +573 -0
  87. librelane/scripts/odbpy/diodes.py +373 -0
  88. librelane/scripts/odbpy/disconnected_pins.py +305 -0
  89. librelane/scripts/odbpy/eco_buffer.py +181 -0
  90. librelane/scripts/odbpy/eco_diode.py +139 -0
  91. librelane/scripts/odbpy/filter_unannotated.py +100 -0
  92. librelane/scripts/odbpy/io_place.py +482 -0
  93. librelane/scripts/odbpy/ioplace_parser/__init__.py +23 -0
  94. librelane/scripts/odbpy/ioplace_parser/parse.py +147 -0
  95. librelane/scripts/odbpy/label_macro_pins.py +277 -0
  96. librelane/scripts/odbpy/lefutil.py +97 -0
  97. librelane/scripts/odbpy/placers.py +162 -0
  98. librelane/scripts/odbpy/power_utils.py +397 -0
  99. librelane/scripts/odbpy/random_place.py +57 -0
  100. librelane/scripts/odbpy/reader.py +250 -0
  101. librelane/scripts/odbpy/remove_buffers.py +173 -0
  102. librelane/scripts/odbpy/snap_to_grid.py +57 -0
  103. librelane/scripts/odbpy/wire_lengths.py +93 -0
  104. librelane/scripts/openroad/antenna_check.tcl +20 -0
  105. librelane/scripts/openroad/antenna_repair.tcl +31 -0
  106. librelane/scripts/openroad/basic_mp.tcl +24 -0
  107. librelane/scripts/openroad/buffer_list.tcl +10 -0
  108. librelane/scripts/openroad/common/dpl.tcl +24 -0
  109. librelane/scripts/openroad/common/dpl_cell_pad.tcl +26 -0
  110. librelane/scripts/openroad/common/grt.tcl +32 -0
  111. librelane/scripts/openroad/common/io.tcl +540 -0
  112. librelane/scripts/openroad/common/pdn_cfg.tcl +135 -0
  113. librelane/scripts/openroad/common/resizer.tcl +103 -0
  114. librelane/scripts/openroad/common/set_global_connections.tcl +78 -0
  115. librelane/scripts/openroad/common/set_layer_adjustments.tcl +31 -0
  116. librelane/scripts/openroad/common/set_power_nets.tcl +30 -0
  117. librelane/scripts/openroad/common/set_rc.tcl +75 -0
  118. librelane/scripts/openroad/common/set_routing_layers.tcl +30 -0
  119. librelane/scripts/openroad/cts.tcl +80 -0
  120. librelane/scripts/openroad/cut_rows.tcl +24 -0
  121. librelane/scripts/openroad/dpl.tcl +24 -0
  122. librelane/scripts/openroad/drt.tcl +37 -0
  123. librelane/scripts/openroad/fill.tcl +30 -0
  124. librelane/scripts/openroad/floorplan.tcl +145 -0
  125. librelane/scripts/openroad/gpl.tcl +88 -0
  126. librelane/scripts/openroad/grt.tcl +30 -0
  127. librelane/scripts/openroad/gui.tcl +37 -0
  128. librelane/scripts/openroad/insert_buffer.tcl +127 -0
  129. librelane/scripts/openroad/ioplacer.tcl +67 -0
  130. librelane/scripts/openroad/irdrop.tcl +51 -0
  131. librelane/scripts/openroad/pdn.tcl +52 -0
  132. librelane/scripts/openroad/rcx.tcl +32 -0
  133. librelane/scripts/openroad/repair_design.tcl +70 -0
  134. librelane/scripts/openroad/repair_design_postgrt.tcl +48 -0
  135. librelane/scripts/openroad/rsz_timing_postcts.tcl +68 -0
  136. librelane/scripts/openroad/rsz_timing_postgrt.tcl +70 -0
  137. librelane/scripts/openroad/sta/check_macro_instances.tcl +53 -0
  138. librelane/scripts/openroad/sta/corner.tcl +393 -0
  139. librelane/scripts/openroad/tapcell.tcl +25 -0
  140. librelane/scripts/openroad/write_views.tcl +27 -0
  141. librelane/scripts/pyosys/construct_abc_script.py +177 -0
  142. librelane/scripts/pyosys/json_header.py +84 -0
  143. librelane/scripts/pyosys/synthesize.py +493 -0
  144. librelane/scripts/pyosys/ys_common.py +153 -0
  145. librelane/scripts/tclsh/hello.tcl +1 -0
  146. librelane/state/__init__.py +24 -0
  147. librelane/state/__main__.py +61 -0
  148. librelane/state/design_format.py +195 -0
  149. librelane/state/state.py +359 -0
  150. librelane/steps/__init__.py +61 -0
  151. librelane/steps/__main__.py +510 -0
  152. librelane/steps/checker.py +637 -0
  153. librelane/steps/common_variables.py +340 -0
  154. librelane/steps/cvc_rv.py +169 -0
  155. librelane/steps/klayout.py +509 -0
  156. librelane/steps/magic.py +576 -0
  157. librelane/steps/misc.py +160 -0
  158. librelane/steps/netgen.py +253 -0
  159. librelane/steps/odb.py +1088 -0
  160. librelane/steps/openroad.py +2460 -0
  161. librelane/steps/openroad_alerts.py +102 -0
  162. librelane/steps/pyosys.py +640 -0
  163. librelane/steps/step.py +1571 -0
  164. librelane/steps/tclstep.py +288 -0
  165. librelane/steps/verilator.py +222 -0
  166. librelane/steps/yosys.py +371 -0
  167. librelane-2.4.0.dist-info/METADATA +169 -0
  168. librelane-2.4.0.dist-info/RECORD +170 -0
  169. librelane-2.4.0.dist-info/WHEEL +4 -0
  170. librelane-2.4.0.dist-info/entry_points.txt +9 -0
@@ -0,0 +1,354 @@
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
+ from .metric import Metric, sum_aggregator, min_aggregator, max_aggregator
15
+
16
+
17
+ # Area and Counts
18
+ Metric(
19
+ "design__core__area",
20
+ higher_is_better=False,
21
+ )
22
+ Metric(
23
+ "design__die__area",
24
+ higher_is_better=False,
25
+ )
26
+ Metric(
27
+ "design__instance__area",
28
+ higher_is_better=False,
29
+ )
30
+ Metric(
31
+ "design__instance__count",
32
+ higher_is_better=False,
33
+ )
34
+
35
+ # Power
36
+ Metric(
37
+ "ir__drop__avg",
38
+ higher_is_better=False,
39
+ )
40
+ Metric(
41
+ "ir__drop__worst",
42
+ aggregator=max_aggregator,
43
+ higher_is_better=False,
44
+ )
45
+ Metric(
46
+ "ir__voltage__worst",
47
+ aggregator=min_aggregator,
48
+ higher_is_better=True,
49
+ )
50
+ Metric(
51
+ "design_powergrid__drop__average",
52
+ higher_is_better=False,
53
+ )
54
+ Metric(
55
+ "design_powergrid__drop__worst",
56
+ aggregator=max_aggregator,
57
+ higher_is_better=False,
58
+ )
59
+ Metric(
60
+ "design_powergrid__voltage__worst",
61
+ aggregator=min_aggregator,
62
+ higher_is_better=True,
63
+ )
64
+ Metric(
65
+ "power__internal__total",
66
+ aggregator=sum_aggregator,
67
+ higher_is_better=False,
68
+ )
69
+ Metric(
70
+ "power__leakage__total",
71
+ aggregator=sum_aggregator,
72
+ higher_is_better=False,
73
+ )
74
+ Metric(
75
+ "power__switching__total",
76
+ aggregator=sum_aggregator,
77
+ higher_is_better=False,
78
+ )
79
+ Metric(
80
+ "power__total",
81
+ aggregator=sum_aggregator,
82
+ higher_is_better=False,
83
+ )
84
+
85
+ # Timing
86
+ Metric(
87
+ "timing__hold_vio__count",
88
+ aggregator=sum_aggregator,
89
+ higher_is_better=False,
90
+ critical=True,
91
+ )
92
+ Metric(
93
+ "timing__hold_r2r_vio__count",
94
+ aggregator=sum_aggregator,
95
+ higher_is_better=False,
96
+ critical=True,
97
+ )
98
+ Metric(
99
+ "timing__setup_vio__count",
100
+ aggregator=sum_aggregator,
101
+ higher_is_better=False,
102
+ critical=True,
103
+ )
104
+ Metric(
105
+ "timing__setup_r2r_vio__count",
106
+ aggregator=sum_aggregator,
107
+ higher_is_better=False,
108
+ critical=True,
109
+ )
110
+ Metric(
111
+ "timing__hold__ws",
112
+ aggregator=min_aggregator,
113
+ higher_is_better=True,
114
+ )
115
+ Metric(
116
+ "timing__hold_r2r__ws",
117
+ aggregator=min_aggregator,
118
+ higher_is_better=True,
119
+ )
120
+ Metric(
121
+ "timing__setup__ws",
122
+ aggregator=min_aggregator,
123
+ higher_is_better=True,
124
+ )
125
+
126
+ Metric(
127
+ "timing__setup_r2r__ws",
128
+ aggregator=min_aggregator,
129
+ higher_is_better=True,
130
+ )
131
+ Metric(
132
+ "timing__hold__wns",
133
+ aggregator=min_aggregator,
134
+ higher_is_better=True,
135
+ critical=True,
136
+ )
137
+ Metric(
138
+ "timing__setup__wns",
139
+ aggregator=min_aggregator,
140
+ higher_is_better=True,
141
+ critical=True,
142
+ )
143
+ Metric(
144
+ "timing__hold__tns",
145
+ aggregator=min_aggregator,
146
+ higher_is_better=True,
147
+ critical=True,
148
+ )
149
+ Metric(
150
+ "timing__unannotated_net__count",
151
+ aggregator=max_aggregator,
152
+ higher_is_better=False,
153
+ )
154
+ Metric(
155
+ "timing__unannotated_net_filtered__count",
156
+ aggregator=max_aggregator,
157
+ higher_is_better=False,
158
+ )
159
+ Metric(
160
+ "timing__setup__tns",
161
+ aggregator=min_aggregator,
162
+ higher_is_better=True,
163
+ critical=True,
164
+ )
165
+ Metric(
166
+ "clock__skew__worst_hold",
167
+ aggregator=max_aggregator,
168
+ higher_is_better=False,
169
+ )
170
+ Metric(
171
+ "clock__skew__worst_setup",
172
+ aggregator=min_aggregator,
173
+ higher_is_better=True,
174
+ )
175
+
176
+ # Constraint Violation
177
+ Metric(
178
+ "design__max_slew_violation__count",
179
+ aggregator=max_aggregator,
180
+ higher_is_better=False,
181
+ )
182
+ Metric(
183
+ "design__max_fanout_violation__count",
184
+ aggregator=max_aggregator,
185
+ higher_is_better=False,
186
+ )
187
+ Metric(
188
+ "design__max_cap_violation__count",
189
+ aggregator=max_aggregator,
190
+ higher_is_better=False,
191
+ )
192
+
193
+ # Placement and Routing
194
+ Metric(
195
+ "route__wirelength",
196
+ aggregator=sum_aggregator,
197
+ higher_is_better=False,
198
+ dont_aggregate=["iter"],
199
+ )
200
+ Metric(
201
+ "route__wirelength__estimated",
202
+ aggregator=sum_aggregator,
203
+ higher_is_better=False,
204
+ dont_aggregate=["iter"],
205
+ )
206
+ Metric(
207
+ "route__wirelength__max",
208
+ aggregator=max_aggregator,
209
+ higher_is_better=False,
210
+ dont_aggregate=["iter"],
211
+ )
212
+ Metric(
213
+ "route__antenna_violation__count",
214
+ aggregator=sum_aggregator,
215
+ higher_is_better=False,
216
+ dont_aggregate=["iter"],
217
+ )
218
+ Metric(
219
+ "design__instance__displacement__max",
220
+ aggregator=max_aggregator,
221
+ higher_is_better=False,
222
+ )
223
+ Metric(
224
+ "design__instance__displacement__total",
225
+ aggregator=sum_aggregator,
226
+ higher_is_better=False,
227
+ )
228
+ Metric(
229
+ "design__instance__utilization",
230
+ higher_is_better=True,
231
+ )
232
+
233
+ # Potential Issues
234
+ Metric(
235
+ "design__lint_warning__count",
236
+ aggregator=sum_aggregator,
237
+ higher_is_better=False,
238
+ )
239
+ Metric(
240
+ "design__lint_error__count",
241
+ aggregator=sum_aggregator,
242
+ higher_is_better=False,
243
+ critical=True,
244
+ )
245
+ Metric(
246
+ "design__lint_timing_construct__count",
247
+ aggregator=sum_aggregator,
248
+ higher_is_better=False,
249
+ )
250
+ Metric(
251
+ "antenna__violating__nets",
252
+ aggregator=sum_aggregator,
253
+ higher_is_better=False,
254
+ )
255
+ Metric(
256
+ "antenna__violating__pins",
257
+ aggregator=sum_aggregator,
258
+ higher_is_better=False,
259
+ )
260
+ Metric(
261
+ "design__instance_unmapped__count",
262
+ aggregator=sum_aggregator,
263
+ higher_is_better=False,
264
+ critical=True,
265
+ )
266
+ Metric(
267
+ "design__disconnected_pin__count",
268
+ aggregator=sum_aggregator,
269
+ higher_is_better=False,
270
+ critical=True,
271
+ )
272
+ Metric(
273
+ "design__inferred_latch__count",
274
+ aggregator=sum_aggregator,
275
+ higher_is_better=False,
276
+ )
277
+ Metric(
278
+ "design__violations",
279
+ aggregator=sum_aggregator,
280
+ higher_is_better=False,
281
+ )
282
+ Metric(
283
+ "design__xor_difference__count",
284
+ aggregator=sum_aggregator,
285
+ higher_is_better=False,
286
+ critical=True,
287
+ )
288
+ Metric(
289
+ "route__drc_errors",
290
+ aggregator=sum_aggregator,
291
+ higher_is_better=False,
292
+ dont_aggregate=["iter"],
293
+ critical=True,
294
+ )
295
+ Metric(
296
+ "magic__drc_error__count",
297
+ aggregator=sum_aggregator,
298
+ higher_is_better=False,
299
+ critical=True,
300
+ )
301
+ Metric(
302
+ "magic__illegal_overlap__count",
303
+ aggregator=sum_aggregator,
304
+ higher_is_better=False,
305
+ critical=True,
306
+ )
307
+ Metric(
308
+ "synthesis__check_error__count",
309
+ aggregator=sum_aggregator,
310
+ higher_is_better=False,
311
+ critical=True,
312
+ )
313
+ Metric(
314
+ "design__lvs_error__count",
315
+ aggregator=sum_aggregator,
316
+ higher_is_better=False,
317
+ critical=True,
318
+ )
319
+ Metric(
320
+ "design__lvs_device_difference__count",
321
+ aggregator=sum_aggregator,
322
+ higher_is_better=False,
323
+ critical=True,
324
+ )
325
+ Metric(
326
+ "design__lvs_net_difference__count",
327
+ aggregator=sum_aggregator,
328
+ higher_is_better=False,
329
+ critical=True,
330
+ )
331
+ Metric(
332
+ "design__lvs_property_fail__count",
333
+ aggregator=sum_aggregator,
334
+ higher_is_better=False,
335
+ critical=True,
336
+ )
337
+ Metric(
338
+ "design__lvs_unmatched_device__count",
339
+ aggregator=sum_aggregator,
340
+ higher_is_better=False,
341
+ critical=True,
342
+ )
343
+ Metric(
344
+ "design__lvs_unmatched_net__count",
345
+ aggregator=sum_aggregator,
346
+ higher_is_better=False,
347
+ critical=True,
348
+ )
349
+ Metric(
350
+ "design__lvs_unmatched_pin__count",
351
+ aggregator=sum_aggregator,
352
+ higher_is_better=False,
353
+ critical=True,
354
+ )
@@ -0,0 +1,186 @@
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
+ from math import inf
15
+ from decimal import Decimal
16
+ from dataclasses import dataclass
17
+ from typing import Any, Callable, Iterable, Mapping, Optional, Tuple, ClassVar, Dict
18
+
19
+
20
+ from ..types import Number, is_number, is_real_number
21
+
22
+ MetricAggregator = Tuple[Number, Callable[[Iterable[Number]], Number]]
23
+
24
+ sum_aggregator: MetricAggregator = (0, lambda x: sum(x))
25
+ min_aggregator: MetricAggregator = (inf, min)
26
+ max_aggregator: MetricAggregator = (-inf, max)
27
+
28
+
29
+ @dataclass
30
+ class MetricComparisonResult:
31
+ """
32
+ :param metric_name: The name of the metric that has been compared
33
+ :param gold: The "gold" value being compared against
34
+ :param new: The new value being evaluated
35
+ :param delta: ``None`` if and only if ``before`` - ``after`` is an invalid number.
36
+ Evaluates to ``after - before``\\.
37
+ :param delta_pct: ``None`` if ``delta`` is None or before is zero.
38
+ Otherwise, evaluates to ``delta / before * 100``\\.
39
+ :param better: Whether the change in the value is considered a good thing or
40
+ not. ``None`` if ``delta`` is None or has no value set for
41
+ ``Metric.higher_is_better``\\.
42
+ :param critical: Whether this change of value very likely results in a dead
43
+ chip, i.e., an increase in DRC values, or an inexplicable change in
44
+ the number of I/O pins.
45
+
46
+ """
47
+
48
+ metric_name: str
49
+ gold: Any
50
+ new: Any
51
+ delta: Optional[Number]
52
+ delta_pct: Optional[Number]
53
+ better: Optional[bool]
54
+ critical: bool
55
+ significant_figures: Optional[int]
56
+
57
+ def is_changed(self) -> bool:
58
+ return (self.delta is not None and self.delta != 0) or self.gold != self.new
59
+
60
+ def format_values(self) -> Tuple[str, str, str]:
61
+ before_str = str(self.gold)
62
+ if isinstance(self.gold, float) or isinstance(self.gold, Decimal):
63
+ before_str = str(f"{self.gold:.{self.significant_figures}f}")
64
+
65
+ after_str = str(self.new)
66
+ if isinstance(self.new, float) or isinstance(self.new, Decimal):
67
+ after_str = str(f"{self.new:.{self.significant_figures}f}")
68
+
69
+ delta_str = "N/A"
70
+ if self.delta is not None:
71
+ delta_str = str(round(self.delta, 6))
72
+ if isinstance(self.delta, float) or isinstance(self.delta, Decimal):
73
+ delta_str = str(f"{self.delta:.{self.significant_figures}f}")
74
+ if self.delta_pct is not None:
75
+ delta_pct_str = str(f"{self.delta_pct:.{self.significant_figures}f}")
76
+ if self.delta_pct > 0:
77
+ delta_pct_str = f"+{delta_pct_str}"
78
+ delta_str = f"{delta_str} ({delta_pct_str}%)"
79
+
80
+ return before_str, after_str, delta_str
81
+
82
+
83
+ @dataclass
84
+ class Metric(object):
85
+ """
86
+ An object storing data about a metric as defined in METRICS2.1.
87
+
88
+ :param name: The string name of the metric.
89
+ :param aggregator: A tuple of:
90
+ - A starting value for an accumulator
91
+ - A reduction function
92
+
93
+ The aim is the ability to aggregate values from various sub-metrics,
94
+ i.e., for the metric ``timing__hold_vio__count``, the sub-metrics:
95
+
96
+ - ``timing__hold_vio__count__corner:A``
97
+ - ``timing__hold_vio__count__corner:B``
98
+
99
+ Would be summed up to generate the value for ``timing__hold_vio__count``.
100
+ :param higher_is_better: At a high level, whether a higher numeric value for
101
+ this metric is considered "good" (such as: better utilization) or "bad"
102
+ (such as: more antenna violations.)
103
+ :param critical: A critical metric is always watched for any change.
104
+
105
+ """
106
+
107
+ name: str
108
+ aggregator: Optional[MetricAggregator] = None
109
+ higher_is_better: Optional[bool] = None
110
+ dont_aggregate: Optional[Iterable[str]] = None
111
+ critical: bool = False
112
+
113
+ by_name: ClassVar[Dict[str, "Metric"]] = {}
114
+
115
+ def __post_init__(self):
116
+ Metric.by_name[self.name] = self
117
+
118
+ def modified_name(self, modifiers: Mapping[str, str]) -> str:
119
+ """
120
+ :param modifiers: Modifiers of a metric (i.e. the elements postfixed to the metric in the format {key}:{value})
121
+ :returns: The name with the modifiers added
122
+ """
123
+ return "__".join([self.name] + [f"{k}:{v}" for k, v in modifiers.items()])
124
+
125
+ def compare(
126
+ self,
127
+ gold: Any,
128
+ new: Any,
129
+ significant_figures: int,
130
+ modifiers: Optional[Mapping[str, str]] = None,
131
+ ) -> MetricComparisonResult:
132
+ """
133
+ :param gold: The "gold-standard" value for this metric to compare against
134
+ :param new: The new value for this metric being evaluated
135
+ :param modifier: The modifiers that were parsed from the metric name
136
+ (if applicable)- used to set the ``metric_name`` property of
137
+ :class:`MetricComparisonResult`.
138
+ :returns: The result of comparing two values for this metric.
139
+ """
140
+ is_better = None
141
+ is_critical = self.critical and (gold != new)
142
+ delta = None
143
+ delta_pct = None
144
+
145
+ if modifiers is None:
146
+ modifiers = {}
147
+
148
+ if is_real_number(gold) and is_real_number(new):
149
+ if isinstance(gold, float) or isinstance(new, float):
150
+ gold = Decimal(gold)
151
+ new = Decimal(new)
152
+ delta = new - gold
153
+
154
+ if gold == 0:
155
+ if new == 0:
156
+ delta_pct = Decimal(0)
157
+ else:
158
+ delta_pct = Decimal((delta / gold) * 100)
159
+ if delta_pct == 0:
160
+ delta_pct = Decimal(0) # Fix negative zero
161
+
162
+ if self.higher_is_better is not None:
163
+ if self.higher_is_better:
164
+ is_better = delta >= 0
165
+ else:
166
+ is_better = delta <= 0
167
+ elif self.higher_is_better is not None:
168
+ if not is_number(gold):
169
+ raise TypeError(
170
+ f"'{gold}' is not a number for metric {self.name} with non-None 'higher_is_better' field"
171
+ )
172
+ if not is_number(new):
173
+ raise TypeError(
174
+ f"'{new}' is not a number for metric {self.name} with non-None 'higher_is_better' field"
175
+ )
176
+
177
+ return MetricComparisonResult(
178
+ self.modified_name(modifiers),
179
+ gold,
180
+ new,
181
+ delta,
182
+ delta_pct,
183
+ is_better,
184
+ is_critical,
185
+ significant_figures,
186
+ )