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/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,7 @@
1
+ from hwcomponents.scaling.scalefuncs import *
2
+ from hwcomponents.scaling.techscaling import (
3
+ tech_node_area,
4
+ tech_node_energy,
5
+ tech_node_latency,
6
+ tech_node_leak,
7
+ )
@@ -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