f1bdc502fd06be51f99c9f10b7158dbbb9cbe0f7
[csit.git] / resources / libraries / python / jumpavg / BitCountingGroup.py
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:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
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.
13
14 """Module holding BitCountingGroup class."""
15
16 import copy
17
18 from .AvgStdevStats import AvgStdevStats
19 from .BitCountingStats import BitCountingStats
20
21
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.
25
26     This class contains methods that mutate the internal state,
27     use copy() method to save the previous state.
28
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.
32
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).
36
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.
39     """
40
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.
44
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.
49
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.
52
53         It is not verified whether the user provided values are valid,
54         e.g. whether the stats and bits values reflect the runs.
55
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]
67         :type comment: str
68         """
69         self.run_list = copy.deepcopy(run_list) if run_list else list()
70         self.stats = stats
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)
77
78     def __str__(self):
79         """Return string with human readable description of the group.
80
81         :returns: Readable description.
82         :rtype: str
83         """
84         return f"stats={self.stats} bits={self.cached_bits}"
85
86     def __repr__(self):
87         """Return string executable as Python constructor call.
88
89         :returns: Executable constructor call.
90         :rtype: str
91         """
92         return (
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})"
96         )
97
98     def __getitem__(self, index):
99         """Return the run at the index.
100
101         :param index: Index of the run to return.
102         :type index: int
103         :returns: The run at the index.
104         :rtype: Run
105         """
106         return self.run_list[index]
107
108     def __len__(self):
109         """Return the number of runs in the group.
110
111         :returns: The Length of run_list.
112         :rtype: int
113         """
114         return len(self.run_list)
115
116     def copy(self):
117         """Return a new instance with copied internal state.
118
119         :returns: The copied instance.
120         :rtype: BitCountingGroup
121         """
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)
127
128     @property
129     def bits(self):
130         """Return overall bit content of the group list.
131
132         If not cached, compute from stats and cache.
133
134         :returns: The overall information content in bits.
135         :rtype: float
136         """
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
141
142     def append(self, run):
143         """Mutate to add the new run, return self.
144
145         Stats are updated, but old bits value is deleted from cache.
146
147         :param run: The run value to add to the group.
148         :type value: Run
149         :returns: The updated self.
150         :rtype: BitCountingGroup
151         """
152         self.run_list.append(run)
153         self.stats = AvgStdevStats.for_runs([self.stats, run])
154         self.cached_bits = None
155         return self
156
157     def extend(self, runs):
158         """Mutate to add the new runs, return self.
159
160         This is saves small amount of computation
161         compared to adding runs one by one in a loop.
162
163         Stats are updated, but old bits value is deleted from cache.
164
165         :param runs: The runs to add to the group.
166         :type value: Iterable[Run]
167         :returns: The updated self.
168         :rtype: BitCountingGroup
169         """
170         self.run_list.extend(runs)
171         self.stats = AvgStdevStats.for_runs([self.stats] + runs)
172         self.cached_bits = None
173         return self