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/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from hwcomponents.model import ComponentModel, action
|
|
2
|
+
from hwcomponents.find_models import get_models
|
|
3
|
+
import hwcomponents.scaling as scaling
|
|
4
|
+
from hwcomponents.select_models import (
|
|
5
|
+
get_area,
|
|
6
|
+
get_energy,
|
|
7
|
+
get_latency,
|
|
8
|
+
get_leak_power,
|
|
9
|
+
get_model,
|
|
10
|
+
)
|
hwcomponents/_logging.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import queue
|
|
3
|
+
from typing import List, Union
|
|
4
|
+
from logging.handlers import QueueHandler
|
|
5
|
+
|
|
6
|
+
logging.basicConfig(
|
|
7
|
+
format="%(levelname)-8s%(message)s",
|
|
8
|
+
)
|
|
9
|
+
if logging.getLogger().level == logging.NOTSET:
|
|
10
|
+
logging.getLogger().setLevel(logging.INFO)
|
|
11
|
+
|
|
12
|
+
LOG_QUEUES = {}
|
|
13
|
+
NAME2LOGGER = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def queue_from_logger(logger: Union[logging.Logger, str]) -> List[str]:
|
|
17
|
+
if isinstance(logger, str) and logger in LOG_QUEUES:
|
|
18
|
+
return LOG_QUEUES[logger]
|
|
19
|
+
for name, other_logger in NAME2LOGGER.items():
|
|
20
|
+
if other_logger is logger and name in LOG_QUEUES:
|
|
21
|
+
return LOG_QUEUES[name]
|
|
22
|
+
raise ValueError(f"Logger {logger} not found")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def messages_from_logger(logger: Union[logging.Logger, str]) -> List[str]:
|
|
26
|
+
return [m.getMessage() for m in queue_from_logger(logger).queue]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_logger(name: str) -> logging.Logger:
|
|
30
|
+
logger = logging.getLogger(name)
|
|
31
|
+
logger.propagate = False
|
|
32
|
+
NAME2LOGGER[name] = logger
|
|
33
|
+
if name not in LOG_QUEUES:
|
|
34
|
+
LOG_QUEUES[name] = queue.Queue()
|
|
35
|
+
logger.addHandler(QueueHandler(LOG_QUEUES[name]))
|
|
36
|
+
return logger
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def move_queue_from_one_logger_to_another(
|
|
40
|
+
src: Union[logging.Logger, str], dest: Union[logging.Logger, str]
|
|
41
|
+
):
|
|
42
|
+
src_queue = queue_from_logger(src)
|
|
43
|
+
dest_queue = queue_from_logger(dest)
|
|
44
|
+
if src_queue is dest_queue:
|
|
45
|
+
return
|
|
46
|
+
while not src_queue.empty():
|
|
47
|
+
dest_queue.put(src_queue.get())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def pop_all_messages(logger: Union[logging.Logger, str]) -> List[str]:
|
|
51
|
+
messages = messages_from_logger(logger)
|
|
52
|
+
queue_from_logger(logger).queue.clear()
|
|
53
|
+
return messages
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def print_messages(logger: Union[logging.Logger, str]):
|
|
57
|
+
for message in messages_from_logger(logger):
|
|
58
|
+
print(message)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ListLoggable:
|
|
62
|
+
def __init__(self, name: str = None):
|
|
63
|
+
self._init_logger(name)
|
|
64
|
+
|
|
65
|
+
def _init_logger(self, name: str = None):
|
|
66
|
+
if self.logger is not None:
|
|
67
|
+
return
|
|
68
|
+
if name is None:
|
|
69
|
+
if hasattr(self, "__name__"):
|
|
70
|
+
name = self.__name__
|
|
71
|
+
else:
|
|
72
|
+
name = self.__class__.__name__
|
|
73
|
+
self.logger = get_logger(name)
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def logger(self) -> logging.Logger:
|
|
77
|
+
if getattr(self, "_logger", None) is None:
|
|
78
|
+
if hasattr(self, "__name__"):
|
|
79
|
+
self._logger = get_logger(self.__name__)
|
|
80
|
+
else:
|
|
81
|
+
self._logger = get_logger(self.__class__.__name__)
|
|
82
|
+
self._logger.setLevel(logging.INFO)
|
|
83
|
+
return self._logger
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def log_all_lines(logger_name, level, to_split):
|
|
87
|
+
logger = logging.getLogger(logger_name)
|
|
88
|
+
if isinstance(level, str):
|
|
89
|
+
logfunc = getattr(logger, level)
|
|
90
|
+
for s in to_split.splitlines():
|
|
91
|
+
logfunc(s)
|
|
92
|
+
else:
|
|
93
|
+
for s in to_split.splitlines():
|
|
94
|
+
logging.getLogger(logger_name).log(level, s)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def clear_logs():
|
|
98
|
+
for name, queue in LOG_QUEUES.items():
|
|
99
|
+
queue.queue.clear()
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
from numbers import Number
|
|
4
|
+
from types import ModuleType
|
|
5
|
+
from typing import Any, Callable, Dict, List, Optional, Set, Union
|
|
6
|
+
from .model import ComponentModel
|
|
7
|
+
from ._logging import (
|
|
8
|
+
move_queue_from_one_logger_to_another,
|
|
9
|
+
ListLoggable,
|
|
10
|
+
pop_all_messages,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EstimatorError(Exception):
|
|
15
|
+
def __str__(self):
|
|
16
|
+
return self.message
|
|
17
|
+
|
|
18
|
+
def __repr__(self):
|
|
19
|
+
return str(self)
|
|
20
|
+
|
|
21
|
+
def __init__(self, message: str):
|
|
22
|
+
super().__init__(message)
|
|
23
|
+
self.message = message
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PrintableCall:
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
name: str,
|
|
30
|
+
args: List[str] = (),
|
|
31
|
+
defaults: Dict[str, Any] = None,
|
|
32
|
+
):
|
|
33
|
+
self.name = name
|
|
34
|
+
self.args = args
|
|
35
|
+
self.defaults = defaults or {}
|
|
36
|
+
|
|
37
|
+
def __str__(self):
|
|
38
|
+
n = self.name
|
|
39
|
+
args = [str(a) for a in self.args] + [
|
|
40
|
+
f"{k}={v}" for k, v in self.defaults.items()
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
return f"{n}({', '.join(args)})"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ModelQuery:
|
|
47
|
+
"""A query to an ComponentModel."""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
component_name: str,
|
|
52
|
+
component_attributes: Dict[str, Any],
|
|
53
|
+
action_name: str = None,
|
|
54
|
+
action_arguments: Dict[str, Any] = None,
|
|
55
|
+
required_actions: List[str] = (),
|
|
56
|
+
):
|
|
57
|
+
self.component_name = component_name
|
|
58
|
+
self.action_name = action_name
|
|
59
|
+
# Attributes and arguments are only included if they are not None
|
|
60
|
+
action_arguments = {} if action_arguments is None else action_arguments
|
|
61
|
+
self.action_arguments = {
|
|
62
|
+
k: v for k, v in action_arguments.items() if v is not None
|
|
63
|
+
}
|
|
64
|
+
self.component_attributes = {
|
|
65
|
+
k: v for k, v in component_attributes.items() if v is not None
|
|
66
|
+
}
|
|
67
|
+
self.required_actions = required_actions
|
|
68
|
+
|
|
69
|
+
def __str__(self):
|
|
70
|
+
attrs_stringified = ", ".join(
|
|
71
|
+
[f"{k}={v}" for k, v in self.component_attributes.items()]
|
|
72
|
+
)
|
|
73
|
+
s = f"{self.component_name}({attrs_stringified})"
|
|
74
|
+
if self.action_name:
|
|
75
|
+
args_stringified = ", ".join(
|
|
76
|
+
[f"{k}={v}" for k, v in self.action_arguments.items()]
|
|
77
|
+
)
|
|
78
|
+
s += f".{self.action_name}({args_stringified})"
|
|
79
|
+
return s
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Estimation:
|
|
83
|
+
value: Union[int, float, ComponentModel]
|
|
84
|
+
success: bool
|
|
85
|
+
messages: List[str]
|
|
86
|
+
model_name: Optional[str]
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
value: Union[int, float, ComponentModel],
|
|
91
|
+
success: bool = True,
|
|
92
|
+
messages: List[str] = (),
|
|
93
|
+
model_name: Optional[str] = None,
|
|
94
|
+
):
|
|
95
|
+
self.value = value
|
|
96
|
+
self.success = success
|
|
97
|
+
self.messages = list(messages)
|
|
98
|
+
self.model_name = model_name
|
|
99
|
+
|
|
100
|
+
def add_messages(self, messages: Union[List[str], str]):
|
|
101
|
+
"""
|
|
102
|
+
Adds messages to the internal message list. The messages are reported
|
|
103
|
+
depending on model selections and verbosity level.
|
|
104
|
+
"""
|
|
105
|
+
if isinstance(messages, str):
|
|
106
|
+
self.add_messages([messages])
|
|
107
|
+
else:
|
|
108
|
+
self.messages += messages
|
|
109
|
+
|
|
110
|
+
def fail(self, message: str):
|
|
111
|
+
"""Marks this estimation as failed and adds the message."""
|
|
112
|
+
self.success = False
|
|
113
|
+
self.add_messages(message)
|
|
114
|
+
|
|
115
|
+
def lastmessage(self) -> str:
|
|
116
|
+
"""Returns the last message in the message list. If no messages, returns
|
|
117
|
+
a default."""
|
|
118
|
+
if self.messages:
|
|
119
|
+
return self.messages[-1]
|
|
120
|
+
else:
|
|
121
|
+
return f"No messages found."
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class FloatEstimation(Estimation):
|
|
125
|
+
def __init__(
|
|
126
|
+
self,
|
|
127
|
+
value: Union[int, float, tuple[Union[int, float], ...]],
|
|
128
|
+
success: bool = True,
|
|
129
|
+
messages: List[str] = [],
|
|
130
|
+
model_name: Optional[str] = None,
|
|
131
|
+
):
|
|
132
|
+
super().__init__(value, success, messages, model_name)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class ModelEstimation(Estimation):
|
|
136
|
+
def __init__(
|
|
137
|
+
self,
|
|
138
|
+
value: Union[int, float],
|
|
139
|
+
success: bool = True,
|
|
140
|
+
messages: List[str] = [],
|
|
141
|
+
model_name: Optional[str] = None,
|
|
142
|
+
):
|
|
143
|
+
super().__init__(value, success, messages, model_name)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class CallableFunction:
|
|
147
|
+
"""Wrapper for a function to provide error checking and argument
|
|
148
|
+
matching."""
|
|
149
|
+
|
|
150
|
+
def __init__(
|
|
151
|
+
self,
|
|
152
|
+
function: Callable,
|
|
153
|
+
logger: logging.Logger,
|
|
154
|
+
force_name_override: str = None,
|
|
155
|
+
is_init: bool = False,
|
|
156
|
+
):
|
|
157
|
+
if not isinstance(function, Callable):
|
|
158
|
+
raise TypeError(
|
|
159
|
+
f"Function {function.__name__} must be an instance of Callable, not {type(function)}"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
self.function = function
|
|
163
|
+
self.additional_kwargs = getattr(function, "_additional_kwargs", set())
|
|
164
|
+
if is_init:
|
|
165
|
+
function = function.__init__
|
|
166
|
+
elif getattr(function, "_is_component_action", False):
|
|
167
|
+
function = function._original_function
|
|
168
|
+
|
|
169
|
+
args = function.__code__.co_varnames[1 : function.__code__.co_argcount]
|
|
170
|
+
default_length = (
|
|
171
|
+
len(function.__defaults__) if function.__defaults__ is not None else 0
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
self.function_name = function.__name__
|
|
175
|
+
if force_name_override is not None:
|
|
176
|
+
self.function_name = force_name_override
|
|
177
|
+
self.non_default_args = args[: len(args) - default_length]
|
|
178
|
+
self.default_args = args[len(args) - default_length :]
|
|
179
|
+
self.default_arg_values = (
|
|
180
|
+
function.__defaults__ if function.__defaults__ is not None else []
|
|
181
|
+
)
|
|
182
|
+
self.logger = logger
|
|
183
|
+
|
|
184
|
+
def get_error_message_for_name_match(self, name: str, component_name: str = ""):
|
|
185
|
+
if self.function_name != name:
|
|
186
|
+
return f"Function name {self.function_name} does not match my name {component_name}.{name}"
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
def get_error_message_for_non_default_arg_match(
|
|
190
|
+
self, kwags: dict, component_name: str = ""
|
|
191
|
+
) -> Optional[str]:
|
|
192
|
+
for arg in self.non_default_args:
|
|
193
|
+
if kwags.get(arg) is None:
|
|
194
|
+
return (
|
|
195
|
+
f"Argument for {component_name}.{self.function_name} is missing: {arg}. "
|
|
196
|
+
f'Arguments provided: {", ".join(kwags.keys())}'
|
|
197
|
+
)
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
def get_call_error_message(
|
|
201
|
+
self, name: str, kwargs: dict, component_name: str = ""
|
|
202
|
+
) -> Optional[str]:
|
|
203
|
+
name_error = self.get_error_message_for_name_match(name, component_name)
|
|
204
|
+
if name_error is not None:
|
|
205
|
+
return name_error
|
|
206
|
+
arg_error = self.get_error_message_for_non_default_arg_match(
|
|
207
|
+
kwargs, component_name
|
|
208
|
+
)
|
|
209
|
+
if arg_error is not None:
|
|
210
|
+
return arg_error
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
def call(
|
|
214
|
+
self,
|
|
215
|
+
kwargs: dict,
|
|
216
|
+
component_name: str = "",
|
|
217
|
+
call_function_on_object: object = None,
|
|
218
|
+
) -> Any:
|
|
219
|
+
kwags_included = {
|
|
220
|
+
k: v
|
|
221
|
+
for k, v in kwargs.items()
|
|
222
|
+
if k in self.non_default_args
|
|
223
|
+
or k in self.default_args
|
|
224
|
+
or k in self.additional_kwargs
|
|
225
|
+
}
|
|
226
|
+
self.logger.info(
|
|
227
|
+
f"Calling {self.function_name} with arguments {kwags_included}"
|
|
228
|
+
)
|
|
229
|
+
unneeded_args = [k for k in kwargs.keys() if k not in kwags_included]
|
|
230
|
+
if unneeded_args:
|
|
231
|
+
self.logger.warn(
|
|
232
|
+
f"Unused arguments for {component_name}.{self.function_name}: "
|
|
233
|
+
f'({", ".join(unneeded_args)}) '
|
|
234
|
+
f'Arguments used: ({", ".join(kwags_included.keys())})'
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if call_function_on_object is not None:
|
|
238
|
+
return self.function(call_function_on_object, **kwags_included)
|
|
239
|
+
return self.function(**kwags_included)
|
|
240
|
+
|
|
241
|
+
def __str__(self):
|
|
242
|
+
return str(
|
|
243
|
+
PrintableCall(
|
|
244
|
+
self.function_name if self.function_name != "__init__" else "",
|
|
245
|
+
self.non_default_args,
|
|
246
|
+
{a: b for a, b in zip(self.default_args, self.default_arg_values)},
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class ComponentModelWrapper(ListLoggable):
|
|
252
|
+
def __init__(self, model_cls: type, component_name: str):
|
|
253
|
+
check_for_valid_model_attrs(model_cls)
|
|
254
|
+
self.model_cls = model_cls
|
|
255
|
+
self.model_name = component_name
|
|
256
|
+
cls_component_name = model_cls._component_name()
|
|
257
|
+
if isinstance(cls_component_name, str):
|
|
258
|
+
cls_component_name = [cls_component_name]
|
|
259
|
+
if not isinstance(cls_component_name, list):
|
|
260
|
+
raise ValueError(
|
|
261
|
+
f"component_name must be a string or list of strings, not {type(cls_component_name)}"
|
|
262
|
+
)
|
|
263
|
+
self.component_name = [c.lower() for c in cls_component_name]
|
|
264
|
+
super().__init__(name=self.get_name())
|
|
265
|
+
|
|
266
|
+
self.priority = model_cls.priority
|
|
267
|
+
if self.priority < 0 or self.priority > 1:
|
|
268
|
+
raise ValueError(f"Priority must be between 0 and 1, not {self.priority}")
|
|
269
|
+
self.init_function = CallableFunction(model_cls, self.logger, is_init=True)
|
|
270
|
+
|
|
271
|
+
self.actions = [
|
|
272
|
+
CallableFunction(getattr(model_cls, a), self.logger)
|
|
273
|
+
for a in dir(model_cls)
|
|
274
|
+
if getattr(
|
|
275
|
+
getattr(model_cls, a),
|
|
276
|
+
"_is_component_action",
|
|
277
|
+
False,
|
|
278
|
+
)
|
|
279
|
+
]
|
|
280
|
+
# self.actions.append(CallableFunction(model_cls.leak, self.logger))
|
|
281
|
+
logging.debug(
|
|
282
|
+
f"Added model {self.model_name} that for component {self.component_name}"
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def get_action_names(self) -> List[str]:
|
|
286
|
+
return [a.function_name for a in self.actions]
|
|
287
|
+
|
|
288
|
+
def fail_missing(self, missing: str):
|
|
289
|
+
raise AttributeError(
|
|
290
|
+
f"Primitive component {self.component_name} " f"must have {missing}"
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def is_component_supported(
|
|
294
|
+
self, query: ModelQuery, relaxed_component_name_selection: bool = False
|
|
295
|
+
) -> bool:
|
|
296
|
+
if query.component_name.lower() in self.component_name:
|
|
297
|
+
pass
|
|
298
|
+
elif query.component_name.lower().replace("_", "") in self.component_name:
|
|
299
|
+
if relaxed_component_name_selection:
|
|
300
|
+
pass
|
|
301
|
+
else:
|
|
302
|
+
self.logger.error(
|
|
303
|
+
f"Component name is similar to supported component names, but not "
|
|
304
|
+
f"supported. Did you mean {self.component_name}?"
|
|
305
|
+
)
|
|
306
|
+
return False
|
|
307
|
+
else:
|
|
308
|
+
self.logger.error(
|
|
309
|
+
f"Component name {query.component_name} is not supported. "
|
|
310
|
+
f"Supported component names: {self.component_name}"
|
|
311
|
+
)
|
|
312
|
+
return False
|
|
313
|
+
|
|
314
|
+
init_error = self.init_function.get_call_error_message(
|
|
315
|
+
"__init__", query.component_attributes, self.component_name
|
|
316
|
+
)
|
|
317
|
+
if init_error is not None:
|
|
318
|
+
self.logger.error(init_error)
|
|
319
|
+
raise EstimatorError(init_error)
|
|
320
|
+
return True
|
|
321
|
+
|
|
322
|
+
def get_initialized_subclass(self, query: ModelQuery) -> ComponentModel:
|
|
323
|
+
self.logger.info(
|
|
324
|
+
f"Initializing {self.model_cls.__name__} from {self.model_cls.__module__}"
|
|
325
|
+
)
|
|
326
|
+
subclass = self.init_function.call(
|
|
327
|
+
query.component_attributes, self.component_name
|
|
328
|
+
)
|
|
329
|
+
subclass._init_logger()
|
|
330
|
+
return subclass
|
|
331
|
+
|
|
332
|
+
def get_matching_actions(self, query: ModelQuery) -> List[CallableFunction]:
|
|
333
|
+
# Find actions that match the name
|
|
334
|
+
name_matches = [a for a in self.actions if a.function_name == query.action_name]
|
|
335
|
+
if len(name_matches) == 0:
|
|
336
|
+
raise AttributeError(
|
|
337
|
+
f"No action with name {query.action_name} found in {self.component_name}. "
|
|
338
|
+
f'Actions supported: {", ".join(self.get_action_names())}'
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# Find actions that match the arguments
|
|
342
|
+
matching_name_and_arg_actions = [
|
|
343
|
+
a
|
|
344
|
+
for a in name_matches
|
|
345
|
+
if a.get_call_error_message(query.action_name, query.action_arguments)
|
|
346
|
+
is None
|
|
347
|
+
]
|
|
348
|
+
if len(matching_name_and_arg_actions) == 0:
|
|
349
|
+
matching_func_strings = [
|
|
350
|
+
(
|
|
351
|
+
f"{a.function_name}("
|
|
352
|
+
+ ", ".join(
|
|
353
|
+
list(a.non_default_args)
|
|
354
|
+
+ ["OPTIONAL " + b for b in a.default_args]
|
|
355
|
+
)
|
|
356
|
+
)
|
|
357
|
+
+ ")"
|
|
358
|
+
for a in name_matches
|
|
359
|
+
]
|
|
360
|
+
args_provided = (
|
|
361
|
+
query.action_arguments.keys() if query.action_arguments else ["<none>"]
|
|
362
|
+
)
|
|
363
|
+
raise AttributeError(
|
|
364
|
+
f"Action with name {query.action_name} found in {self.component_name}, "
|
|
365
|
+
f"but provided arguments do not match.\n\t"
|
|
366
|
+
f'Arguments provided: {", ".join(args_provided)}\n\t'
|
|
367
|
+
f"Possible actions:\n\t\t" + "\n\t\t".join(matching_func_strings)
|
|
368
|
+
)
|
|
369
|
+
return matching_name_and_arg_actions
|
|
370
|
+
|
|
371
|
+
def get_action_energy_latency(
|
|
372
|
+
self, query: ModelQuery, initialized_obj: ComponentModel = None
|
|
373
|
+
) -> Estimation:
|
|
374
|
+
"""Returns the energy and latency estimation for the given action."""
|
|
375
|
+
if initialized_obj is None:
|
|
376
|
+
initialized_obj = self.get_initialized_subclass(query)
|
|
377
|
+
move_queue_from_one_logger_to_another(initialized_obj.logger, self.logger)
|
|
378
|
+
supported_actions = self.get_matching_actions(query)
|
|
379
|
+
if len(supported_actions) == 0:
|
|
380
|
+
raise AttributeError(
|
|
381
|
+
f"No action with name {query.action_name} found in "
|
|
382
|
+
f"{self.component_name}. Actions supported: "
|
|
383
|
+
f"{', '.join(self.get_action_names())}"
|
|
384
|
+
)
|
|
385
|
+
try:
|
|
386
|
+
result = supported_actions[0].call(
|
|
387
|
+
query.action_arguments, self.component_name, initialized_obj
|
|
388
|
+
)
|
|
389
|
+
energy_value, latency_value = result
|
|
390
|
+
estimation = FloatEstimation(
|
|
391
|
+
value=(energy_value, latency_value),
|
|
392
|
+
)
|
|
393
|
+
except Exception as e:
|
|
394
|
+
move_queue_from_one_logger_to_another(initialized_obj.logger, self.logger)
|
|
395
|
+
raise e
|
|
396
|
+
estimation.add_messages(pop_all_messages(initialized_obj.logger))
|
|
397
|
+
estimation.model_name = self.model_name
|
|
398
|
+
return estimation
|
|
399
|
+
|
|
400
|
+
def get_area(self, query: ModelQuery) -> Estimation:
|
|
401
|
+
"""Returns the area estimation for the given action."""
|
|
402
|
+
subclass = self.get_initialized_subclass(query)
|
|
403
|
+
return FloatEstimation(
|
|
404
|
+
value=subclass.area,
|
|
405
|
+
success=True,
|
|
406
|
+
model_name=self.model_name,
|
|
407
|
+
messages=pop_all_messages(subclass.logger),
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
def get_leak_power(self, query: ModelQuery) -> Estimation:
|
|
411
|
+
"""Returns the leak power estimation for the given action."""
|
|
412
|
+
subclass = self.get_initialized_subclass(query)
|
|
413
|
+
return FloatEstimation(
|
|
414
|
+
value=subclass.leak_power,
|
|
415
|
+
success=True,
|
|
416
|
+
model_name=self.model_name,
|
|
417
|
+
messages=pop_all_messages(subclass.logger),
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
def get_name(self) -> str:
|
|
421
|
+
return self.model_name
|
|
422
|
+
|
|
423
|
+
def get_component_names(self) -> List[str]:
|
|
424
|
+
return (
|
|
425
|
+
[self.component_name]
|
|
426
|
+
if isinstance(self.component_name, str)
|
|
427
|
+
else self.component_name
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
@staticmethod
|
|
431
|
+
def print_action(action: CallableFunction) -> str:
|
|
432
|
+
return action.function_name
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def check_for_valid_model_attrs(model: ComponentModel):
|
|
436
|
+
# Check for valid component_name. Must be a string or list of strings
|
|
437
|
+
component_name = model._component_name()
|
|
438
|
+
if not isinstance(component_name, str) and not (
|
|
439
|
+
isinstance(component_name, (list, tuple))
|
|
440
|
+
and all(isinstance(n, str) for n in component_name)
|
|
441
|
+
):
|
|
442
|
+
raise AttributeError(
|
|
443
|
+
f"ComponentModel {model} component_name must be a string or list/tuple of strings"
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Check for valid priority. Must be a number between 0 and 100
|
|
447
|
+
if getattr(model, "priority", None) is None:
|
|
448
|
+
raise AttributeError(
|
|
449
|
+
f'ComponentModel for {component_name} must have a "priority" ' f"attribute."
|
|
450
|
+
)
|
|
451
|
+
priority = model.priority
|
|
452
|
+
if not isinstance(priority, Number):
|
|
453
|
+
raise AttributeError(
|
|
454
|
+
f"ComponentModel for {component_name} priority must be a "
|
|
455
|
+
f"number. It is currently a {type(priority)}"
|
|
456
|
+
)
|
|
457
|
+
if priority < 0 or priority > 1:
|
|
458
|
+
raise AttributeError(
|
|
459
|
+
f"ComponentModel for {component_name} priority must be "
|
|
460
|
+
f"between 0 and 1 inclusive."
|
|
461
|
+
)
|
hwcomponents/_util.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def parse_float(s: str, context: str = "") -> float:
|
|
5
|
+
"""Parses a string into a float. Handles scientific notation."""
|
|
6
|
+
# Remove leading and trailing non-numeric characters
|
|
7
|
+
s = str(s)
|
|
8
|
+
numeric_re_sub = r"[^0-9eE\-\+\.]"
|
|
9
|
+
s_trimmed = re.sub(f"^{numeric_re_sub}+", "", s)
|
|
10
|
+
s_trimmed = re.sub(f"{numeric_re_sub}+$", "", s_trimmed)
|
|
11
|
+
try:
|
|
12
|
+
return float(s_trimmed)
|
|
13
|
+
except (ValueError, TypeError) as e:
|
|
14
|
+
raise ValueError(f'Could not parse "{s}" from "{context}" as a float.') from e
|
hwcomponents/_version.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '1.0.81'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 0, 81)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Version scheme for setuptools-scm - creates post-release versions."""
|
|
2
|
+
|
|
3
|
+
from setuptools_scm.version import guess_next_version
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def post_version(version):
|
|
7
|
+
"""Create post-release versions instead of dev versions."""
|
|
8
|
+
if version.exact:
|
|
9
|
+
return version.format_with("{tag}").lstrip("v")
|
|
10
|
+
|
|
11
|
+
base = (
|
|
12
|
+
str(version.tag).lstrip("v")
|
|
13
|
+
if version.tag
|
|
14
|
+
else (guess_next_version(version) or "1.0")
|
|
15
|
+
)
|
|
16
|
+
distance = version.distance or 0
|
|
17
|
+
|
|
18
|
+
return f"{base}.{distance}" if distance > 0 else base
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def no_local(version):
|
|
22
|
+
"""No local version identifier."""
|
|
23
|
+
return ""
|