hwcomponents 1.0.81__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.
- hwcomponents/__init__.py +10 -0
- hwcomponents/_logging.py +99 -0
- hwcomponents/_model_wrapper.py +461 -0
- hwcomponents/_util.py +14 -0
- hwcomponents/_version.py +34 -0
- hwcomponents/_version_scheme.py +23 -0
- hwcomponents/find_models.py +250 -0
- hwcomponents/hwcomponents.py +77 -0
- hwcomponents/model.py +547 -0
- hwcomponents/scaling/__init__.py +7 -0
- hwcomponents/scaling/scalefuncs.py +119 -0
- hwcomponents/scaling/techscaling.py +185 -0
- hwcomponents/select_models.py +466 -0
- hwcomponents-1.0.81.dist-info/METADATA +34 -0
- hwcomponents-1.0.81.dist-info/RECORD +19 -0
- hwcomponents-1.0.81.dist-info/WHEEL +5 -0
- hwcomponents-1.0.81.dist-info/entry_points.txt +3 -0
- hwcomponents-1.0.81.dist-info/top_level.txt +1 -0
- hwcomponents-1.0.81.dist-info/zip-safe +1 -0
hwcomponents/model.py
ADDED
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import inspect
|
|
3
|
+
from numbers import Number
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import Any, Callable, List, Type, Union, TypeVar
|
|
6
|
+
from hwcomponents._logging import ListLoggable
|
|
7
|
+
from hwcomponents._util import parse_float
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T", bound="ComponentModel")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def action(
|
|
13
|
+
func: Callable[..., Union[float, tuple[float, float]]] = None,
|
|
14
|
+
bits_per_action: str = None,
|
|
15
|
+
pipelined_subcomponents: bool = False,
|
|
16
|
+
) -> Callable[..., tuple]:
|
|
17
|
+
"""
|
|
18
|
+
Decorator that adds an action to an energy/area model. If the component has no
|
|
19
|
+
subcomponents, then the action is expected to return a tuple of (energy, latency)
|
|
20
|
+
where energy is in Joules and latency is in seconds. If the component has
|
|
21
|
+
subcomponents, then None return values are assumed to be (0, 0), and subcomponent
|
|
22
|
+
actions that occur during the component's action will be added to the component's
|
|
23
|
+
energy and latency. The energy and latency of the subcomponents will NOT be scaled
|
|
24
|
+
by the component's energy_scale and latency_scale; to scale these, set the
|
|
25
|
+
subcomponents' scaling factors directly.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
func : Callable | None
|
|
30
|
+
The function to decorate.
|
|
31
|
+
bits_per_action : str
|
|
32
|
+
The attribute of the model that contains the number of bits per action. If
|
|
33
|
+
this is set and a bits_per_action is passed to the function, the energy and
|
|
34
|
+
latency will be scaled by the number of bits. For example, if
|
|
35
|
+
bits_per_action is set to "width", the function is called with
|
|
36
|
+
bits_per_action=10, and the model has a width attribute of 5, then the
|
|
37
|
+
energy and latency will be scaled by 2.
|
|
38
|
+
pipelined_subcomponents : bool
|
|
39
|
+
Whether the subcomponents are pipelined. If True, then the latency of is the
|
|
40
|
+
max of the latency returned and all subcomponent latencies. Otherwise, it is
|
|
41
|
+
the sum. Does nothing if there are no subcomponents.
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
The decorated function.
|
|
45
|
+
"""
|
|
46
|
+
if func is None:
|
|
47
|
+
return lambda func: action(func, bits_per_action)
|
|
48
|
+
|
|
49
|
+
additional_kwargs = set()
|
|
50
|
+
if bits_per_action is not None:
|
|
51
|
+
additional_kwargs.add("bits_per_action")
|
|
52
|
+
|
|
53
|
+
@wraps(func)
|
|
54
|
+
def wrapper(self: "ComponentModel", *args, **kwargs):
|
|
55
|
+
for subcomponent in self.subcomponents:
|
|
56
|
+
subcomponent._energy_used = 0
|
|
57
|
+
subcomponent._latency_used = 0
|
|
58
|
+
scale = 1
|
|
59
|
+
if bits_per_action is not None and "bits_per_action" in kwargs:
|
|
60
|
+
nominal_bits = None
|
|
61
|
+
try:
|
|
62
|
+
nominal_bits = getattr(self, bits_per_action)
|
|
63
|
+
except:
|
|
64
|
+
pass
|
|
65
|
+
if nominal_bits is None:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f"{self.__name__} has no attribute {bits_per_action}. "
|
|
68
|
+
f"Ensure that the attributes referenced in @action "
|
|
69
|
+
f"are defined in the class."
|
|
70
|
+
)
|
|
71
|
+
scale = kwargs["bits_per_action"] / nominal_bits
|
|
72
|
+
kwargs = {k: v for k, v in kwargs.items() if k not in additional_kwargs}
|
|
73
|
+
returned_value = func(self, *args, **kwargs)
|
|
74
|
+
# Normalize return to (energy, latency)
|
|
75
|
+
if returned_value is None:
|
|
76
|
+
if not self._subcomponents_set and not self.subcomponents:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
f"@action function {func.__name__} did not return a value. "
|
|
79
|
+
f"This is permitted if and only if the component has no "
|
|
80
|
+
f"subcomponents. Please either initialize subcomponents or ensure "
|
|
81
|
+
f"that the @action function returns a tuple of (energy, latency)."
|
|
82
|
+
)
|
|
83
|
+
energy_val, latency_val = 0.0, 0.0
|
|
84
|
+
elif isinstance(returned_value, (tuple, list)) and len(returned_value) == 2:
|
|
85
|
+
energy_val, latency_val = returned_value
|
|
86
|
+
else:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f"@action function {func.__name__} returned an invalid value. "
|
|
89
|
+
f"Expected a tuple of (energy, latency), got {returned_value}."
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
energy_val *= self.energy_scale
|
|
93
|
+
for subcomponent in self.subcomponents:
|
|
94
|
+
energy_val += subcomponent._energy_used
|
|
95
|
+
subcomponent._energy_used = 0
|
|
96
|
+
energy_val *= scale
|
|
97
|
+
self._energy_used += energy_val
|
|
98
|
+
|
|
99
|
+
latency_val *= self.latency_scale
|
|
100
|
+
target_func = max if pipelined_subcomponents else sum
|
|
101
|
+
for subcomponent in self.subcomponents:
|
|
102
|
+
latency_val = target_func((latency_val, subcomponent._latency_used))
|
|
103
|
+
subcomponent._latency_used = 0
|
|
104
|
+
latency_val *= scale
|
|
105
|
+
self._latency_used += latency_val
|
|
106
|
+
|
|
107
|
+
return energy_val, latency_val
|
|
108
|
+
|
|
109
|
+
wrapper._is_component_action = True
|
|
110
|
+
wrapper._original_function = func
|
|
111
|
+
wrapper._additional_kwargs = additional_kwargs
|
|
112
|
+
return wrapper
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ComponentModel(ListLoggable, ABC):
|
|
116
|
+
"""
|
|
117
|
+
ComponentModel base class. ComponentModel class must have "name" attribute,
|
|
118
|
+
"priority" attribute, and "get_area" method. ComponentModels may have any number of
|
|
119
|
+
methods that are decorated with @action.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
component_name: str | list[str] | None
|
|
124
|
+
The name of the component. Must be a string or list/tuple of strings. Can be
|
|
125
|
+
omitted if the component name is the same as the class name.
|
|
126
|
+
priority: float
|
|
127
|
+
The priority of the model. Higher priority models are used first. Must be a
|
|
128
|
+
number between 0 and 1.
|
|
129
|
+
leak_power: float | None
|
|
130
|
+
The leakage power of the component in Watts. Must be set if subcomponents is
|
|
131
|
+
not set.
|
|
132
|
+
area: float | None
|
|
133
|
+
The area of the component in m^2. Must be set if subcomponents is not set.
|
|
134
|
+
energy_scale: float
|
|
135
|
+
A scale factor for the energy. All calls to @action will be scaled by this
|
|
136
|
+
factor.
|
|
137
|
+
area_scale: float
|
|
138
|
+
A scale factor for the area. All calls to area will be scaled by this
|
|
139
|
+
factor.
|
|
140
|
+
latency_scale: float
|
|
141
|
+
A scale factor for the latency. All calls to @action will be scaled by this
|
|
142
|
+
factor.
|
|
143
|
+
leak_power_scale: float
|
|
144
|
+
A scale factor for the leakage power. All calls to leak_power will be scaled
|
|
145
|
+
by this factor.
|
|
146
|
+
subcomponents: list[ComponentModel] | None
|
|
147
|
+
A list of subcomponents. If set, the area and leak power of the
|
|
148
|
+
subcomponents will be added to the area and leak power of the component. All
|
|
149
|
+
calls to @action functions will be added to the energy and latency of the
|
|
150
|
+
component if they occur during one of the component's actions. The area,
|
|
151
|
+
energy, latency, and leak power of subcomponents WILL NOT BE scaled by the
|
|
152
|
+
component's energy_scale, area_scale, or leak_power_scale; if you want to
|
|
153
|
+
scale the subcomponents, multiply their energy_scale, area_scale,
|
|
154
|
+
latency_scale, or leak_power_scale by the desired scale factor.
|
|
155
|
+
|
|
156
|
+
Attributes
|
|
157
|
+
----------
|
|
158
|
+
component_name: The name of the component. Must be a string or list/tuple of
|
|
159
|
+
strings. Can be omitted if the component name is the same as the class name.
|
|
160
|
+
priority: The priority of the model. Higher priority models are used first.
|
|
161
|
+
Must be a number between 0 and 1.
|
|
162
|
+
energy_scale: A scale factor for the energy. All calls to action
|
|
163
|
+
will be scaled by this factor.
|
|
164
|
+
area_scale: A scale factor for the area. All calls to area
|
|
165
|
+
will be scaled by this factor.
|
|
166
|
+
latency_scale: A scale factor for the latency. All calls to @action
|
|
167
|
+
will be scaled by this factor.
|
|
168
|
+
leak_power_scale: A scale factor for the leakage power. All calls to leak_power
|
|
169
|
+
will be scaled by this factor.
|
|
170
|
+
subcomponents: A list of subcomponents. If set, the area and leak power of the
|
|
171
|
+
subcomponents will be added to the area and leak power of the component. All
|
|
172
|
+
calls to @action functions will be added to the energy and latency of the
|
|
173
|
+
component if they occur during one of the component's actions. The area,
|
|
174
|
+
energy, latency, and leak power of subcomponents WILL NOT BE scaled by the
|
|
175
|
+
component's energy_scale, area_scale, or leak_power_scale; if you want to
|
|
176
|
+
scale the subcomponents, multiply their energy_scale, area_scale,
|
|
177
|
+
latency_scale, or leak_power_scale by the desired scale factor.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
component_name: Union[str, List[str], None] = None
|
|
181
|
+
"""
|
|
182
|
+
Name of the component. Must be a string or list/tuple of strings. Can be omitted if
|
|
183
|
+
the component name is the same as the class name.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
priority: Number = None
|
|
187
|
+
"""
|
|
188
|
+
Priority determines which model is used when multiple models are available for a
|
|
189
|
+
given component. Higher priority models are used first. Must be a number between 0
|
|
190
|
+
and 1.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
@abstractmethod
|
|
194
|
+
def __init__(
|
|
195
|
+
self,
|
|
196
|
+
leak_power: float | None = None,
|
|
197
|
+
area: float | None = None,
|
|
198
|
+
subcomponents: list["ComponentModel"] | None = None,
|
|
199
|
+
):
|
|
200
|
+
if subcomponents is None:
|
|
201
|
+
if leak_power is None or area is None:
|
|
202
|
+
raise ValueError(
|
|
203
|
+
f"Either subcomponents must be set, or BOTH leak_power and area"
|
|
204
|
+
f"must be set."
|
|
205
|
+
)
|
|
206
|
+
super().__init__()
|
|
207
|
+
self.area_scale: float = 1
|
|
208
|
+
self.energy_scale: float = 1
|
|
209
|
+
self.latency_scale: float = 1
|
|
210
|
+
self.leak_power_scale: float = 1
|
|
211
|
+
self._leak_power: float = leak_power if leak_power is not None else 0
|
|
212
|
+
self._area: float = area if area is not None else 0
|
|
213
|
+
self.subcomponents: list["ComponentModel"] = (
|
|
214
|
+
[] if subcomponents is None else subcomponents
|
|
215
|
+
)
|
|
216
|
+
self._subcomponents_set = subcomponents is not None
|
|
217
|
+
self._energy_used: float = 0
|
|
218
|
+
self._latency_used: float = 0
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def leak_power(self) -> Number:
|
|
222
|
+
"""
|
|
223
|
+
Returns the leakage power of the component in Watts.
|
|
224
|
+
|
|
225
|
+
Returns
|
|
226
|
+
-------
|
|
227
|
+
The leakage power in Watts.
|
|
228
|
+
"""
|
|
229
|
+
return self._leak_power * self.leak_power_scale + sum(
|
|
230
|
+
s.leak_power for s in self.subcomponents
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def area(self) -> Number:
|
|
235
|
+
"""
|
|
236
|
+
Returns the area in m^2 of the component.
|
|
237
|
+
|
|
238
|
+
Returns
|
|
239
|
+
-------
|
|
240
|
+
The area in m^2 of the component.
|
|
241
|
+
"""
|
|
242
|
+
return self._area * self.area_scale + sum(s.area for s in self.subcomponents)
|
|
243
|
+
|
|
244
|
+
@classmethod
|
|
245
|
+
def _component_name(cls) -> str:
|
|
246
|
+
if cls.component_name is None:
|
|
247
|
+
return cls.__name__
|
|
248
|
+
return cls.component_name
|
|
249
|
+
|
|
250
|
+
def scale(
|
|
251
|
+
self,
|
|
252
|
+
key: str,
|
|
253
|
+
target: float,
|
|
254
|
+
default: float,
|
|
255
|
+
area_scale_function: Callable[[float, float], float] | None = None,
|
|
256
|
+
energy_scale_function: Callable[[float, float], float] | None = None,
|
|
257
|
+
latency_scale_function: Callable[[float, float], float] | None = None,
|
|
258
|
+
leak_power_scale_function: Callable[[float, float], float] | None = None,
|
|
259
|
+
) -> float:
|
|
260
|
+
"""
|
|
261
|
+
Scales this model's area, energy, latency, and leak power to the given target.
|
|
262
|
+
|
|
263
|
+
Parameters
|
|
264
|
+
----------
|
|
265
|
+
key: str
|
|
266
|
+
The name of the parameter to scale. Used for logging.
|
|
267
|
+
target: float
|
|
268
|
+
The target value of the parameter. The value is scaled to this from the
|
|
269
|
+
default.
|
|
270
|
+
default: float
|
|
271
|
+
The default value of the parameter. The value is scaled to the target
|
|
272
|
+
from this.
|
|
273
|
+
area_scale_function: Callable[[float, float], float]
|
|
274
|
+
The function to use to scale the area. None if no scaling should be
|
|
275
|
+
done.
|
|
276
|
+
energy_scale_function: Callable[[float, float], float]
|
|
277
|
+
The function to use to scale the energy. None if no scaling should be
|
|
278
|
+
done.
|
|
279
|
+
latency_scale_function: Callable[[float, float], float]
|
|
280
|
+
The function to use to scale the latency. None if no scaling should be
|
|
281
|
+
done.
|
|
282
|
+
leak_power_scale_function: Callable[[float, float], float]
|
|
283
|
+
The function to use to scale the leak power. None if no scaling should
|
|
284
|
+
be done.
|
|
285
|
+
"""
|
|
286
|
+
super()._init_logger(f"{self._component_name()}")
|
|
287
|
+
if target == default:
|
|
288
|
+
return target
|
|
289
|
+
|
|
290
|
+
for attr, callfunc in [
|
|
291
|
+
("area_scale", area_scale_function),
|
|
292
|
+
("energy_scale", energy_scale_function),
|
|
293
|
+
("latency_scale", latency_scale_function),
|
|
294
|
+
("leak_power_scale", leak_power_scale_function),
|
|
295
|
+
]:
|
|
296
|
+
try:
|
|
297
|
+
if callfunc is None:
|
|
298
|
+
continue
|
|
299
|
+
prev_val = getattr(self, attr)
|
|
300
|
+
scale = callfunc(target, default)
|
|
301
|
+
setattr(self, attr, prev_val * scale)
|
|
302
|
+
self.logger.info(
|
|
303
|
+
f"Scaled {key} from {default} to {target}: {attr} multiplied by {scale}"
|
|
304
|
+
)
|
|
305
|
+
except:
|
|
306
|
+
target_float = parse_float(target, f"{self._component_name()}.{key}")
|
|
307
|
+
default_float = parse_float(default, f"{self._component_name()}.{key}")
|
|
308
|
+
scale = callfunc(target_float, default_float)
|
|
309
|
+
setattr(self, attr, prev_val * scale)
|
|
310
|
+
self.logger.info(
|
|
311
|
+
f"Scaled {key} from {default} to {target}: {attr} multiplied by {scale}"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
return target
|
|
315
|
+
|
|
316
|
+
@classmethod
|
|
317
|
+
def get_action_names(cls) -> List[str]:
|
|
318
|
+
"""
|
|
319
|
+
Returns the names of the actions supported by the model.
|
|
320
|
+
|
|
321
|
+
Returns
|
|
322
|
+
-------
|
|
323
|
+
List[str]
|
|
324
|
+
The names of the actions supported by the model.
|
|
325
|
+
"""
|
|
326
|
+
names = set()
|
|
327
|
+
for base in cls.__mro__:
|
|
328
|
+
for name, func in base.__dict__.items():
|
|
329
|
+
if getattr(func, "_is_component_action", False):
|
|
330
|
+
names.add(name)
|
|
331
|
+
return sorted(names)
|
|
332
|
+
|
|
333
|
+
def required_arguments(self, action_name: str | None = None) -> List[str]:
|
|
334
|
+
"""
|
|
335
|
+
Returns the required arguments for the given action. If no action is given,
|
|
336
|
+
returns the required arguments for the __init__ method.
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
----------
|
|
340
|
+
action_name : str | None
|
|
341
|
+
The name of the action to get the required arguments for.
|
|
342
|
+
If None, returns the required arguments for the __init__ method.
|
|
343
|
+
Returns
|
|
344
|
+
-------
|
|
345
|
+
list[str]
|
|
346
|
+
The required arguments for the given action.
|
|
347
|
+
"""
|
|
348
|
+
action_name = "__init__" if action_name is None else action_name
|
|
349
|
+
try:
|
|
350
|
+
action_func = getattr(self, action_name)
|
|
351
|
+
except:
|
|
352
|
+
raise ValueError(
|
|
353
|
+
f"{self.__class__.__name__} has no action {action_name}. "
|
|
354
|
+
f"Supported actions are: {self.get_action_names()}"
|
|
355
|
+
)
|
|
356
|
+
return inspect.signature(action_func).parameters.keys()
|
|
357
|
+
|
|
358
|
+
@classmethod
|
|
359
|
+
def try_init_arbitrary_args(
|
|
360
|
+
cls: Type[T], _return_estimation_object: bool = False, **kwargs
|
|
361
|
+
) -> T:
|
|
362
|
+
"""
|
|
363
|
+
Tries to initialize the model with the given arguments.
|
|
364
|
+
|
|
365
|
+
Parameters
|
|
366
|
+
----------
|
|
367
|
+
**kwargs : dict
|
|
368
|
+
The arguments with which to initialize the model.
|
|
369
|
+
_return_estimation_object : bool
|
|
370
|
+
Whether to return the Estimation object instead of the model.
|
|
371
|
+
Returns
|
|
372
|
+
-------
|
|
373
|
+
The initialized model. If the model cannot be initialized with the given
|
|
374
|
+
arguments, an exception is raised.
|
|
375
|
+
"""
|
|
376
|
+
from hwcomponents._model_wrapper import ModelQuery, ComponentModelWrapper
|
|
377
|
+
|
|
378
|
+
wrapper = ComponentModelWrapper(cls, cls.component_name)
|
|
379
|
+
cname = cls.component_name
|
|
380
|
+
query = ModelQuery(
|
|
381
|
+
component_name=cname if isinstance(cname, str) else cname[0],
|
|
382
|
+
component_attributes=kwargs,
|
|
383
|
+
)
|
|
384
|
+
value = wrapper.get_initialized_subclass(query)
|
|
385
|
+
if _return_estimation_object:
|
|
386
|
+
return value
|
|
387
|
+
return value.value
|
|
388
|
+
|
|
389
|
+
def try_call_arbitrary_action(
|
|
390
|
+
self: T, action_name: str, _return_estimation_object: bool = False, **kwargs
|
|
391
|
+
) -> T:
|
|
392
|
+
"""
|
|
393
|
+
Tries to call the given action with the given arguments.
|
|
394
|
+
|
|
395
|
+
Parameters
|
|
396
|
+
----------
|
|
397
|
+
action_name : str
|
|
398
|
+
The name of the action to call.
|
|
399
|
+
**kwargs : dict
|
|
400
|
+
The arguments with which to call the action.
|
|
401
|
+
"""
|
|
402
|
+
from hwcomponents._model_wrapper import ModelQuery, ComponentModelWrapper
|
|
403
|
+
|
|
404
|
+
wrapper = ComponentModelWrapper(type(self), self.component_name)
|
|
405
|
+
query = ModelQuery(
|
|
406
|
+
component_name=self.component_name,
|
|
407
|
+
component_attributes={},
|
|
408
|
+
action_name=action_name,
|
|
409
|
+
action_arguments=kwargs,
|
|
410
|
+
)
|
|
411
|
+
value = wrapper.get_action_energy_latency(query, initialized_obj=self)
|
|
412
|
+
if _return_estimation_object:
|
|
413
|
+
return value
|
|
414
|
+
return value.value
|
|
415
|
+
|
|
416
|
+
def assert_int(self, name: str, value: int | float | Any) -> int:
|
|
417
|
+
"""
|
|
418
|
+
Checks that the value is an integer, and if so, returns it as an integer.
|
|
419
|
+
Otherwise, raises a ValueError.
|
|
420
|
+
|
|
421
|
+
Parameters
|
|
422
|
+
----------
|
|
423
|
+
name: str
|
|
424
|
+
The name of the attribute to check. Used for error messages.
|
|
425
|
+
value: int | float | Any
|
|
426
|
+
The value to check.
|
|
427
|
+
Returns
|
|
428
|
+
-------
|
|
429
|
+
int
|
|
430
|
+
The value as an integer.
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
if isinstance(value, int):
|
|
434
|
+
return value
|
|
435
|
+
if isinstance(value, float) and value.is_integer():
|
|
436
|
+
return int(value)
|
|
437
|
+
if isinstance(value, str):
|
|
438
|
+
try:
|
|
439
|
+
value = int(value)
|
|
440
|
+
except:
|
|
441
|
+
pass
|
|
442
|
+
raise ValueError(f"{name} must be an integer. Got {value}.")
|
|
443
|
+
|
|
444
|
+
def assert_match(
|
|
445
|
+
self,
|
|
446
|
+
value_a: int | float | Any | None,
|
|
447
|
+
value_b: int | float | Any | None,
|
|
448
|
+
name_a: str,
|
|
449
|
+
name_b: str,
|
|
450
|
+
) -> int:
|
|
451
|
+
"""
|
|
452
|
+
Checks that the two values are equal, and if so, returns the matched value. If
|
|
453
|
+
one value is None, returns the other value. Raise an error if the two values are
|
|
454
|
+
not equal, or if both are None.
|
|
455
|
+
|
|
456
|
+
Parameters
|
|
457
|
+
----------
|
|
458
|
+
value_a: int | float | Any
|
|
459
|
+
The first value to check.
|
|
460
|
+
value_b: int | float | Any
|
|
461
|
+
The second value to check.
|
|
462
|
+
name_a: str
|
|
463
|
+
The name of the first value. Used for error messages.
|
|
464
|
+
name_b: str
|
|
465
|
+
The name of the second value. Used for error messages.
|
|
466
|
+
|
|
467
|
+
Returns
|
|
468
|
+
-------
|
|
469
|
+
int
|
|
470
|
+
The matched value.
|
|
471
|
+
"""
|
|
472
|
+
if value_a is None and value_b is None:
|
|
473
|
+
raise ValueError(
|
|
474
|
+
f"Both {name_a} and {name_b} are None. At least one must be provided."
|
|
475
|
+
)
|
|
476
|
+
if value_a is None:
|
|
477
|
+
return value_b
|
|
478
|
+
if value_b is None:
|
|
479
|
+
return value_a
|
|
480
|
+
|
|
481
|
+
if value_a != value_b:
|
|
482
|
+
raise ValueError(
|
|
483
|
+
f"Mismatch between {name_a} and {name_b}. Got {value_a} and {value_b}."
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
return value_a
|
|
487
|
+
|
|
488
|
+
def resolve_multiple_ways_to_calculate_value(
|
|
489
|
+
self, name: str, *args: tuple[str, Callable[[Any], Any], dict[str, Any]]
|
|
490
|
+
) -> Any:
|
|
491
|
+
"""
|
|
492
|
+
Parses multiple possible ways to set an attribute, raising errors if the values
|
|
493
|
+
are not consistent.
|
|
494
|
+
|
|
495
|
+
Each possible argument is a tuple containing a function and a dictionary of
|
|
496
|
+
keyword arguments. A function fails if any keyword arguments are None, if the
|
|
497
|
+
function raises an error, or if the function returns None.
|
|
498
|
+
|
|
499
|
+
The outputs of all non-failing functions are compared, and an error is raised if
|
|
500
|
+
they are not equal.
|
|
501
|
+
|
|
502
|
+
Parameters
|
|
503
|
+
----------
|
|
504
|
+
name: str
|
|
505
|
+
The name of the attribute to set.
|
|
506
|
+
*args: tuple[str, Callable[[Any], Any], dict[str, Any]]
|
|
507
|
+
The possible ways to set the attribute. Each tuple contains a name, a
|
|
508
|
+
function that takes the current value and returns the new value, and a
|
|
509
|
+
dictionary of keyword arguments to pass to the function.
|
|
510
|
+
Returns
|
|
511
|
+
-------
|
|
512
|
+
The value of the attribute.
|
|
513
|
+
"""
|
|
514
|
+
|
|
515
|
+
error_messages = []
|
|
516
|
+
|
|
517
|
+
success_values = []
|
|
518
|
+
|
|
519
|
+
for fname, func, kwargs in args:
|
|
520
|
+
for key, value in kwargs.items():
|
|
521
|
+
fname = f"{fname}({', '.join(f'{k}={v}' for k, v in kwargs.items())})"
|
|
522
|
+
if value is None:
|
|
523
|
+
error_messages.append(f"{fname}: {key} is None.")
|
|
524
|
+
try:
|
|
525
|
+
new_value = func(**kwargs)
|
|
526
|
+
except Exception as e:
|
|
527
|
+
error_messages.append(f"{fname} raised {e}")
|
|
528
|
+
if new_value is None:
|
|
529
|
+
error_messages.append(f"{fname} returned None.")
|
|
530
|
+
else:
|
|
531
|
+
success_values.append((fname, new_value))
|
|
532
|
+
|
|
533
|
+
values = set(v[-1] for v in success_values)
|
|
534
|
+
|
|
535
|
+
if len(values) == 0:
|
|
536
|
+
raise ValueError(
|
|
537
|
+
f"Could not set {name} with any of the following options:\n\t"
|
|
538
|
+
+ "\n\t".join(error_messages)
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
if len(values) > 1:
|
|
542
|
+
raise ValueError(
|
|
543
|
+
f"Different ways to set {name} returned conflicting values:\n\t"
|
|
544
|
+
+ "\n\t".join(f"{fname}: {value}" for fname, value in success_values)
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
return next(iter(values))
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# =============================================================================
|
|
6
|
+
# General scaling functions
|
|
7
|
+
# =============================================================================
|
|
8
|
+
def linear(target: float, scalefrom: float) -> float:
|
|
9
|
+
"""
|
|
10
|
+
Linear scaling function. Returns target / scalefrom.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
target: The target value.
|
|
14
|
+
scalefrom: The value to scale from.
|
|
15
|
+
Returns:
|
|
16
|
+
The scaled value.
|
|
17
|
+
"""
|
|
18
|
+
return target / scalefrom
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def reciprocal(target: float, scalefrom: float) -> float:
|
|
22
|
+
"""
|
|
23
|
+
Reciprocal scaling function. Returns 1 / (target / scalefrom).
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
target: The target value.
|
|
27
|
+
scalefrom: The value to scale from.
|
|
28
|
+
Returns:
|
|
29
|
+
The scaled value.
|
|
30
|
+
"""
|
|
31
|
+
return 1 / (target / scalefrom)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def pow_base(power: float) -> Callable[[float, float], float]:
|
|
35
|
+
"""
|
|
36
|
+
Power scaling function. Returns a lambda that computes (target - scalefrom) **
|
|
37
|
+
power.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
power: The power to scale by.
|
|
41
|
+
Returns:
|
|
42
|
+
A lambda that computes (target - scalefrom) ** power.
|
|
43
|
+
"""
|
|
44
|
+
return lambda target, scalefrom: (target - scalefrom) ** power
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def quadratic(target: float, scalefrom: float) -> float:
|
|
48
|
+
"""Quadratic scaling function. Returns (target / scalefrom) ** 2."""
|
|
49
|
+
return (target / scalefrom) ** 2
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def nlog_base(power: float) -> Callable[[float, float], float]:
|
|
53
|
+
"""
|
|
54
|
+
Logarithmic scaling function. Returns a lambda that computes (target *
|
|
55
|
+
math.log(target, power)) / (scalefrom * math.log(scalefrom, power)).
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
power: The power to scale by.
|
|
59
|
+
Returns:
|
|
60
|
+
A lambda that computes (target * math.log(target, power)) / (scalefrom *
|
|
61
|
+
math.log(scalefrom, power)).
|
|
62
|
+
"""
|
|
63
|
+
return lambda target, scalefrom: (target * math.log(target, power)) / (
|
|
64
|
+
scalefrom * math.log(scalefrom, power)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def nlog2n(target: float, scalefrom: float) -> float:
|
|
69
|
+
"""
|
|
70
|
+
Logarithmic scaling function. Returns (target / scalefrom) * math.log(target /
|
|
71
|
+
scalefrom, 2).
|
|
72
|
+
"""
|
|
73
|
+
return (target / scalefrom) * math.log(target / scalefrom, 2)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def cacti_depth_energy(target: float, scalefrom: float) -> float:
|
|
77
|
+
"""
|
|
78
|
+
CACTI depth scaling. Based on empirical measurement of CACTI, for which energy
|
|
79
|
+
scales with depth to the power of (1.56 / 2).
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
target: The target depth.
|
|
83
|
+
scalefrom: The depth to scale from.
|
|
84
|
+
Returns:
|
|
85
|
+
The scaled energy.
|
|
86
|
+
"""
|
|
87
|
+
return (target / scalefrom) ** (1.56 / 2) # Based on CACTI scaling
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def cacti_depth_area(target: float, scalefrom: float) -> float:
|
|
91
|
+
"""
|
|
92
|
+
CACTI depth scaling. Based on empirical measurement of CACTI, for which area scales
|
|
93
|
+
linearly with depth.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
target: The target depth.
|
|
97
|
+
scalefrom: The depth to scale from.
|
|
98
|
+
Returns:
|
|
99
|
+
The scaled area.
|
|
100
|
+
"""
|
|
101
|
+
return target / scalefrom # Based on CACTI scaling
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def cacti_depth_leak(target: float, scalefrom: float) -> float:
|
|
105
|
+
"""
|
|
106
|
+
CACTI depth scaling. Based on empirical measurement of CACTI, for which leakage
|
|
107
|
+
power scales linearly with depth.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
target: The target depth.
|
|
111
|
+
scalefrom: The depth to scale from.
|
|
112
|
+
Returns:
|
|
113
|
+
The scaled leakage power.
|
|
114
|
+
"""
|
|
115
|
+
return target / scalefrom # Based on CACTI scaling
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def noscale(target: float, scalefrom: float) -> float:
|
|
119
|
+
return 1
|