1#    Copyright 2015-2016 ARM Limited
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14#
15
16"""
17**Signals**
18
19    - Definition
20
21        A signal is a string representation of a TRAPpy event and the
22        column in the same event. The signal can be of two types:
23
24            - *Pivoted Signal*
25
26                A pivoted signal has a pivot specified in its event class.
27                This means that the signal in the event is a concatenation of different
28                signals which belong to different **pivot** nodes. The analysis for pivoted
29                signals must be done by decomposing them into pivoted signals for each node.
30
31                For example, an even that represents the load of the CPU can be pivoted on
32                :code:`"cpu"` which should be a column in the event's `DataFrame`
33
34            - *Non-Pivoted Signal*
35
36                A non pivoted signal has an event that has no pivot value associated with it.
37                This probably means that signal has one component and can be analysed without
38                decomposing it into smaller signals.
39
40    - Representation
41
42        The following are valid representations of a signal
43
44        - :code:`"event_name:event_column"`
45        - :code:`"trappy.event.class:event_column"`
46
47"""
48
49from trappy.stats.grammar import Parser
50from trappy.stats import StatConf
51from bart.common.Utils import area_under_curve, interval_sum
52
53# pylint: disable=invalid-name
54# pylint: disable=anomalous-backslash-in-string
55
56class SignalCompare(object):
57
58    """
59    :param data: TRAPpy FTrace Object
60    :type data: :mod:`trappy.ftrace.FTrace`
61
62    :param sig_a: The first signal
63    :type sig_a: str
64
65    :param sig_b: The first signal
66    :type sig_b: str
67
68    :param config: A dictionary of variables, classes
69        and functions that can be used in the statements
70    :type config: dict
71
72    :param method: The method to be used for reindexing data
73        This can be one of the standard :mod:`pandas.DataFrame`
74        methods (eg. pad, bfill, nearest). The default is pad
75        or use the last valid observation.
76    :type method: str
77
78    :param limit: The number of indices a value will be propagated
79        when reindexing. The default is None
80    :type limit: int
81
82    :param fill: Whether to fill the NaNs in the data.
83        The default value is True.
84    :type fill: bool
85
86    .. note::
87
88        Both the signals must have the same pivots. For example:
89
90            - Signal A has a pivot as :code:`"cpu"` which means that
91              the trappy event (:mod:`trappy.base.Base`) has a pivot
92              parameter which is equal to :code:`"cpu"`. Then the signal B
93              should also have :code:`"cpu"` as it's pivot.
94
95            - Signal A and B can both have undefined or None
96              as their pivots
97    """
98
99    def __init__(self, data, sig_a, sig_b, **kwargs):
100
101        self._parser = Parser(
102            data,
103            config=kwargs.pop(
104                "config",
105                None),
106            **kwargs)
107        self._a = sig_a
108        self._b = sig_b
109        self._pivot_vals, self._pivot = self._get_signal_pivots()
110
111        # Concatenate the indices by doing any operation (say add)
112        self._a_data = self._parser.solve(sig_a)
113        self._b_data = self._parser.solve(sig_b)
114
115    def _get_signal_pivots(self):
116        """Internal function to check pivot conditions and
117        return an intersection of pivot on the signals"""
118
119        sig_a_info = self._parser.inspect(self._a)
120        sig_b_info = self._parser.inspect(self._b)
121
122        if sig_a_info["pivot"] != sig_b_info["pivot"]:
123            raise RuntimeError("The pivot column for both signals" +
124                               "should be same (%s,%s)"
125                               % (sig_a_info["pivot"], sig_b_info["pivot"]))
126
127        if sig_a_info["pivot"]:
128            pivot_vals = set(
129                sig_a_info["pivot_values"]).intersection(sig_b_info["pivot_values"])
130            pivoted = sig_a_info["pivot"]
131        else:
132            pivot_vals = [StatConf.GRAMMAR_DEFAULT_PIVOT]
133            pivoted = False
134
135        return pivot_vals, pivoted
136
137    def conditional_compare(self, condition, **kwargs):
138        """Conditionally compare two signals
139
140        The conditional comparison of signals has two components:
141
142        - **Value Coefficient** :math:`\\alpha_{v}` which measures the difference in values of
143          of the two signals when the condition is true:
144
145          .. math::
146
147                \\alpha_{v} = \\frac{area\_under\_curve(S_A\ |\ C(t)\ is\ true)}
148                {area\_under\_curve(S_B\ |\ C(t)\ is\ true)} \\\\
149
150                \\alpha_{v} = \\frac{\int S_A(\{t\ |\ C(t)\})dt}{\int S_B(\{t\ |\ C(t)\})dt}
151
152        - **Time Coefficient** :math:`\\alpha_{t}` which measures the time during which the
153          condition holds true.
154
155          .. math::
156
157                \\alpha_{t} = \\frac{T_{valid}}{T_{total}}
158
159        :param condition: A condition that returns a truth value and obeys the grammar syntax
160            ::
161
162                "event_x:sig_a > event_x:sig_b"
163
164        :type condition: str
165
166        :param method: The method for area calculation. This can
167            be any of the integration methods supported in `numpy`
168            or `rect`
169        :type param: str
170
171        :param step: The step behaviour for area and time
172            summation calculation
173        :type step: str
174
175        Consider the two signals A and B as follows:
176
177            .. code::
178
179                A = [0, 0, 0, 3, 3, 0, 0, 0]
180                B = [0, 0, 2, 2, 2, 2, 1, 1]
181
182
183            .. code::
184
185
186                                                     A = xxxx
187                3                 *xxxx*xxxx+        B = ----
188                                  |         |
189                2            *----*----*----+
190                             |    |         |
191                1            |    |         *----*----+
192                             |    |         |
193                0  *x-x-*x-x-+xxxx+         +xxxx*xxxx+
194                   0    1    2    3    4    5    6    7
195
196        The condition:
197
198        .. math::
199
200            A > B
201
202        is valid between T=3 and T=5. Therefore,
203
204        .. math::
205
206            \\alpha_v=1.5 \\\\
207            \\alpha_t=\\frac{2}{7}
208
209        :returns: There are two cases:
210
211            - **Pivoted Signals**
212              ::
213
214                    {
215                        "pivot_name" : {
216                                "pval_1" : (v1,t1),
217                                "pval_2" : (v2, t2)
218                        }
219                    }
220            - **Non Pivoted Signals**
221
222              The tuple of :math:`(\\alpha_v, \\alpha_t)`
223        """
224
225        if self._pivot:
226            result = {self._pivot: {}}
227
228        mask = self._parser.solve(condition)
229        step = kwargs.get("step", "post")
230
231        for pivot_val in self._pivot_vals:
232
233            a_piv = self._a_data[pivot_val]
234            b_piv = self._b_data[pivot_val]
235
236            area = area_under_curve(a_piv[mask[pivot_val]], **kwargs)
237            try:
238                area /= area_under_curve(b_piv[mask[pivot_val]], **kwargs)
239            except ZeroDivisionError:
240                area = float("nan")
241
242            duration = min(a_piv.last_valid_index(), b_piv.last_valid_index())
243            duration -= max(a_piv.first_valid_index(),
244                            b_piv.first_valid_index())
245            duration = interval_sum(mask[pivot_val], step=step) / duration
246
247            if self._pivot:
248                result[self._pivot][pivot_val] = area, duration
249            else:
250                result = area, duration
251
252        return result
253
254    def get_overshoot(self, **kwargs):
255        """Special case for :func:`conditional_compare`
256        where the condition is:
257        ::
258
259            "sig_a > sig_b"
260
261        :param method: The method for area calculation. This can
262            be any of the integration methods supported in `numpy`
263            or `rect`
264        :type param: str
265
266        :param step: The step behaviour for calculation of area
267            and time summation
268        :type step: str
269
270        .. seealso::
271
272            :func:`conditional_compare`
273        """
274
275        condition = " ".join([self._a, ">", self._b])
276        return self.conditional_compare(condition, **kwargs)
277
278    def get_undershoot(self, **kwargs):
279        """Special case for :func:`conditional_compare`
280        where the condition is:
281        ::
282
283            "sig_a < sig_b"
284
285        :param method: The method for area calculation. This can
286            be any of the integration methods supported in `numpy`
287            or `rect`
288        :type param: str
289
290        :param step: The step behaviour for calculation of area
291            and time summation
292        :type step: str
293
294        .. seealso::
295
296            :func:`conditional_compare`
297        """
298
299        condition = " ".join([self._a, "<", self._b])
300        return self.conditional_compare(condition, **kwargs)
301