1 # Copyright (c) 2021 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
14 """Module holding BitCountingGroup class."""
18 from .AvgStdevStats import AvgStdevStats
19 from .BitCountingStats import BitCountingStats
22 class BitCountingGroup:
23 # TODO: Inherit from collections.abc.Sequence in Python 3.
24 """Group of runs which tracks bit count in an efficient manner.
26 This class contains methods that mutate the internal state,
27 use copy() method to save the previous state.
29 The Sequence-like access is related to the list of runs,
30 for example group[0] returns the first run in the list.
31 Writable list-like methods are not implemented.
33 As the group bit count depends on previous average
34 and overall maximal value, those values are assumed
35 to be known beforehand (and immutable).
37 As the caller is allowed to divide runs into groups in any way,
38 a method to add a single run in an efficient manner is provided.
41 def __init__(self, run_list=None, stats=None, bits=None,
42 max_value=None, prev_avg=None, comment="unknown"):
43 """Set the internal state and partially the stats.
45 A "group" stands for an Iterable of runs, where "run" is either
46 a float value, or a stats-like object (only size, avg and stdev
47 are accessed). Run is a hypothetical abstract class,
48 defining it in Python 2 is too much hassle.
50 Only a copy of the run list argument value is stored in the instance,
51 so it is not a problem if the value object is mutated afterwards.
53 It is not verified whether the user provided values are valid,
54 e.g. whether the stats and bits values reflect the runs.
56 :param run_list: List of run to compose into this group. Default: empty.
57 :param stats: Stats object used for computing bits.
58 :param bits: Cached value of information content.
59 :param max_value: Maximal sample value to be used for computing.
60 :param prev_avg: Average of the previous group, affects bits.
61 :param comment: Any string giving more info, e.g. "regression".
62 :type run_list: Iterable[Run]
63 :type stats: Optional[AvgStdevStats]
64 :type bits: Optional[float]
65 :type max_value: float
66 :type prev_avg: Optional[float]
69 self.run_list = copy.deepcopy(run_list) if run_list else list()
71 self.cached_bits = bits
72 self.max_value = max_value
73 self.prev_avg = prev_avg
74 self.comment = comment
75 if self.stats is None:
76 self.stats = AvgStdevStats.for_runs(self.run_list)
79 """Return string with human readable description of the group.
81 :returns: Readable description.
84 return f"stats={self.stats} bits={self.cached_bits}"
87 """Return string executable as Python constructor call.
89 :returns: Executable constructor call.
93 f"BitCountingGroup(run_list={self.run_list!r},stats={self.stats!r}"
94 f",bits={self.cached_bits!r},max_value={self.max_value!r}"
95 f",prev_avg={self.prev_avg!r},comment={self.comment!r})"
98 def __getitem__(self, index):
99 """Return the run at the index.
101 :param index: Index of the run to return.
103 :returns: The run at the index.
106 return self.run_list[index]
109 """Return the number of runs in the group.
111 :returns: The Length of run_list.
114 return len(self.run_list)
117 """Return a new instance with copied internal state.
119 :returns: The copied instance.
120 :rtype: BitCountingGroup
122 stats = AvgStdevStats.for_runs([self.stats])
123 return self.__class__(
124 run_list=self.run_list, stats=stats, bits=self.cached_bits,
125 max_value=self.max_value, prev_avg=self.prev_avg,
126 comment=self.comment)
130 """Return overall bit content of the group list.
132 If not cached, compute from stats and cache.
134 :returns: The overall information content in bits.
137 if self.cached_bits is None:
138 self.cached_bits = BitCountingStats.for_runs(
139 [self.stats], self.max_value, self.prev_avg).bits
140 return self.cached_bits
142 def append(self, run):
143 """Mutate to add the new run, return self.
145 Stats are updated, but old bits value is deleted from cache.
147 :param run: The run value to add to the group.
149 :returns: The updated self.
150 :rtype: BitCountingGroup
152 self.run_list.append(run)
153 self.stats = AvgStdevStats.for_runs([self.stats, run])
154 self.cached_bits = None
157 def extend(self, runs):
158 """Mutate to add the new runs, return self.
160 This is saves small amount of computation
161 compared to adding runs one by one in a loop.
163 Stats are updated, but old bits value is deleted from cache.
165 :param runs: The runs to add to the group.
166 :type value: Iterable[Run]
167 :returns: The updated self.
168 :rtype: BitCountingGroup
170 self.run_list.extend(runs)
171 self.stats = AvgStdevStats.for_runs([self.stats] + runs)
172 self.cached_bits = None