Preliminaries. Given two partial orders \((A,\preceq )\) and \((B, \preceq )\), a Galois Connection is a pair of monotone functions \(\alpha : A \rightarrow B\) and \(\gamma : B \rightarrow A \) such that, for all \(a \in A\) and \(b \in B\), \(\alpha (a) \preceq b\) if and only if \(a \preceq \gamma (b)\). Let \((A,\preceq )\) be a partial order, \(f: A \rightarrow A\) a monotone function and \(\gamma : B \rightarrow A\) a function. The function \(f^\#: B \rightarrow B\) is an abstraction of f whenever, for all \(b \in B\), \(f(\gamma (b)) \preceq \gamma (f^\#(b))\). If \((\alpha , \gamma )\) is a Galois Connection between A and B, the function \(f^\#: B \rightarrow B\) such that \(f^\#(b) := \alpha (f(\gamma (b))\) is a perfect abstraction of f.
In this section we define the abstract counterparts of the TeSSLa operators, listed in Sect. 2. A data abstraction of a data domain \(\mathbb {D}\) is an abstract domain \(\mathbb {D}^\#\) with an element \(\top \in \mathbb {D}^\#\) and an associated concretisation function \(\gamma : \mathbb {D}^\# \rightarrow 2^\mathbb {D}\) with \(\gamma (\top ) = \mathbb {D}\). The abstract value \(\top \) represents any possible value from the data domain and can be used to model an event with known timestamp but unknown value. A gap is a segment of an abstract event stream that represents all combinations of events that could possibly occur in that segment (both in terms of timestamps and values). Hence an abstract event stream consists of an event stream over a data abstraction and an associated set of known timestamps:
Definition 1
(Abstract Event Stream). Given a time domain \(\mathbb {T}\), an abstract event stream over a data domain \(\mathbb {D}\) is a pair \((s, \varDelta )\) with \(s \in \mathcal {S}_\mathbb {D}^{\#}\) and \(\varDelta \subseteq \mathbb {T}\) such that \(\varDelta \) can be represented as union of intervals whose (inclusive or exclusive) boundaries are indicated by events in an event stream. Further, we require \(s(t) \ne \bot \Rightarrow t \in \varDelta \). The set of all abstract event streams over \(\mathbb {D}\) is denoted as \(\mathcal {P}_\mathbb {D}\). The concretisation function \(\gamma : \mathcal {P}_\mathbb {D} \rightarrow 2^{\mathcal {S}_\mathbb {D}}\) is defined as
$$\begin{aligned} \gamma ((s,\varDelta )) = \{ s'\,|\, \forall _{{t\in ticks (s)}} s(t) \in \gamma (s'(t)) \wedge \forall _{t\in \varDelta \setminus ticks (s)} s(t) = s'(t)\} \end{aligned}$$
If the data abstraction is defined in terms of a Galois Connection a refinement ordering and abstraction function can be obtained. The refinement ordering \((\mathcal {P}_\mathbb {D}, \preceq )\) is defined as \((s_1, \varDelta _1) \preceq (s_2, \varDelta _2)\) iff \(\varDelta _1 \supseteq \varDelta _2\) and \(\forall _{t\in ticks (s_2)} s_1(t) \preceq s_2(t) \wedge \forall _{t\in \varDelta _2\setminus ticks (s_2)} s_1(t) = s_2(t)\). The abstraction function \(\alpha : 2^{\mathcal {S}_\mathbb {D}} \rightarrow \mathcal {P}_\mathbb {D}\) is defined as \(\alpha (S) = \mathsf {sup}\{{(s, \mathbb {T}) | s \in S}\}\). Note, if the data abstraction is defined in terms of a Galois Connection, \((\alpha , \gamma )\) is a Galois Connection between \(2^{\mathcal {S}_\mathbb {D}}\) and \(\mathcal {P}_\mathbb {D}\).
An abstract event stream \(s = (s',\varDelta ) \in \mathcal {P}_\mathbb {D}\) can also be seen as a function \(s: \mathbb {T} \rightarrow \mathbb {D}^\# \cup \{\mathrm {?},\bot ,{{\,\mathrm{\smallsmile }\,}}\}\) with \(s(t) = s'(t)\) if \(t \in \varDelta \) and \(s(t) = {{\,\mathrm{\smallsmile }\,}}\) otherwise. A particular point t of an abstract event stream s can be either (a) directly at an event (\(s(t) \in \mathbb D\)), (b) in a gap (\(s(t) = {{\,\mathrm{\smallsmile }\,}}\)), (c) in a gapless segment without an event at t (\(s(t) = \bot \)), or (d) after the known end of the stream (\(s(t) = \mathrm {?}\)).
We denote \(\mathbb {D}^\#_\bot {{\,\mathrm{{\mathop {=}\limits ^{\text {def}}}}\,}}\mathbb {D}^\# \cup \{\bot , {{\,\mathrm{\smallsmile }\,}}\}\). If \(\mathbb {D}^\#\) is a data abstraction of a data domain \(\mathbb {D}\) with an associated concretisation function \(\gamma \), then \(\mathbb {D}^\#_\bot \) is a data abstraction of \(\mathbb {D}_\bot \) with an associated concretisation function \(\gamma _\bot : \mathbb {D}_\bot ^\# \rightarrow 2^{\mathbb {D} \cup \{\bot \}}\) with
The above diagram shows a possible data abstraction \(\mathbb {B}^\#\) of \(\mathbb {B}\) and the corresponding data abstraction \(\mathbb {B}^\#_\bot \). Using the functional representation of an abstract event stream we can now define the abstract counterparts of the TeSSLa operators:
-
\(\blacktriangleright \) \(\mathbf {nil}^\# =(\infty ,\mathbb T) \in \mathcal {P}_\emptyset \) is the empty abstract stream without any gaps.
-
\(\blacktriangleright \)
is the abstract stream without any gaps and a single event at timestamp 0.
-
\(\blacktriangleright \) \(\mathbf {time}^\#: \mathcal {P}_\mathbb {D} \rightarrow \mathcal {P}_\mathbb {T}, \mathbf {time}^\# (s) := z\) is equivalent to its concrete counterpart; only the data domain is extended: \(z(t) = t\) if \(t \in ticks (s)\) and \(z(t) = s(t)\) otherwise.
-
\(\blacktriangleright \) \(\mathbf {lift}^\#: ({\mathbb {D}_1}^\#_\bot \times \dots \times {\mathbb {D}_n}^\#_\bot \rightarrow \mathbb {D}^\#_\bot ) \rightarrow (\mathcal {P}_{\mathbb {D}_1} \times \dots \times \mathcal {P}_{\mathbb {D}_n} \rightarrow \mathcal {P}_\mathbb {D}),\) \(\mathbf {lift}^\# (f^\#)(s_1, \ldots , s_n) := z\) can be defined similarly to its concrete counterpart, because the abstract function \(f^\#\) takes care of the gaps:
$$ z(t) ={\left\{ \begin{array}{ll} f^\#(s_1(t), \ldots , s_n(t)) &{} \text {if }s_1\ne {?},\ldots , s_n\ne {?} \\ ? &{} \text {otherwise} \end{array}\right. } $$
The operator \(\mathbf {lift}^\# \) is restricted to those functions \(f^\#\) that are an abstraction of functions f that can be used in \(\mathbf {lift}\), that is, \(f(\bot ,\dots ,\bot ) = \bot \). Using the abstract lift we can derive the abstract counterparts of \(\textsf {const}\) and \(\textsf {merge}\):
-
\(\vartriangleright \) \(\textsf {const}^\#(c)(a) := \mathbf {lift}^\# (f_c)(a)\) with \(f_c(d) := c\) if \(d \ne {{\,\mathrm{\smallsmile }\,}}\) and \(f_c({{\,\mathrm{\smallsmile }\,}}) := {{\,\mathrm{\smallsmile }\,}}\) otherwise maps all events’ values to a constant while preserving the gaps. Using \(\textsf {const}^\#\) we can define constant signals without any gaps, e.g., \(\textsf {true}^\# := \textsf {const}^\#(\textsf {true})(\mathbf {unit}^\#)\) or \(\textsf {zero}^\# := \textsf {const}^\#(0)(\mathbf {unit}^\#)\).
-
\(\vartriangleright \) \(\textsf {merge}^\#(x, y) := \mathbf {lift}^\# (f)(x, y)\) with \(f(a \not \in \{{{\,\mathrm{\smallsmile }\,}},\bot \}, b) = a\), \(f(\bot , b) = b\), \(f({{\,\mathrm{\smallsmile }\,}}, b \in \{{{\,\mathrm{\smallsmile }\,}},\bot \}) = {{\,\mathrm{\smallsmile }\,}}\), and \(f({{\,\mathrm{\smallsmile }\,}}, b \not \in \{{{\,\mathrm{\smallsmile }\,}},\bot \}) = \top \).
The diagram on the right shows an example trace merging the events of the streams x and y. The symbol \(\circ \) indicates a point-wise gap. Note how an event on the first stream takes precedence over a gap on the second stream, but not the other way round, similarly to how events from the first stream are prioritized if both streams have an event at the same timestamp.
-
\(\blacktriangleright \) \(\mathbf {last}^\#: \mathcal {P}_{\mathbb {D}_1} \times \mathcal {P}_{\mathbb {D}_2} \rightarrow \mathcal {P}_{\mathbb {D}_1}, \mathbf {last}^\# (v,r) := z\) has three major extensions over its concrete counterpart:
-
(1)
\(\top \) is added as an output in case an event on r occurs and there were events on the stream v of values but all followed by a gap.
-
(2)
\({{\,\mathrm{\smallsmile }\,}}\) is outputted for all gaps on the stream r of trigger events if there have been events on the stream v of values.
-
(3)
\({{\,\mathrm{\smallsmile }\,}}\) can also be output if an event occurs on r and no event occurred on v before except for a gap.
The parts similar to the concrete operator are typeset in gray:
The trace diagram on the right shows an example trace covering most edge cases of the abstract last. The output stream z is a point-wise gap if triggered after initial gaps (3); z is \(\top \) if triggered after non-initial gaps (1); z is an event if triggered after a gapless sequence (d); and z inherits all gaps from the stream of trigger events (2).
We can now combine the \(\mathbf {last}^\# \) and the \(\mathbf {lift}^\# \) operators to realize:
-
\(\vartriangleright \) abstract signal lift for total functions \(f: \mathbb D \times \mathbb D' \rightarrow \mathbb D''\) as \(\textsf {slift}^\#(f)(x, y) := \mathbf {lift}^\# (g_f)(x', y')\) with \(x' := \textsf {merge}^\#(x, \mathbf {last} ^\#(x, y))\) and \(y' := \textsf {merge}^\#(y, \mathbf {last} ^\#(y, x))\), as well as \(g_f(a \not \in \{{{\,\mathrm{\smallsmile }\,}},\bot \}, b \not \in \{{{\,\mathrm{\smallsmile }\,}},\bot \}) = f(a, b)\), \(g_f(\bot , b) = g_f(a, \bot ) = \bot \), \(g_f({{\,\mathrm{\smallsmile }\,}}, {{\,\mathrm{\smallsmile }\,}}) = {{\,\mathrm{\smallsmile }\,}}\), and \(g_f({{\,\mathrm{\smallsmile }\,}}, b \not \in \{{{\,\mathrm{\smallsmile }\,}},\bot \}) = g_f(a \not \in \{{{\,\mathrm{\smallsmile }\,}},\bot \}, {{\,\mathrm{\smallsmile }\,}}) = {{\,\mathrm{\smallsmile }\,}}\).
Example 2 By replacing every TeSSLa operator in Example 1 with their abstract counterparts and applying it to the abstract input streams \(\textsf {values} \in \mathcal {P}_{\mathbb Z}\) and \(\textsf {resets} \in \mathcal {P}_{\mathbb U}\), we derive the abstract stream \(\textsf {cond} \in \mathcal {P}_{\mathbb B}\) and the recursively derived abstract stream \(\textsf {sum} \in \mathcal {P}_{\mathbb Z}\): After the large gap on \(\textsf {values}\), the \(\textsf {sum}\) stream eventually recovers completely. The first reset after the point-wise gap does not lead to full recovery, because at that point the last event on values cannot be accessed, because of the prior gap. The next reset falls into the gap, so again \(\textsf {cond}\) cannot be evaluated. In a similar fashion one can define an abstract \(\mathbf {delay}^\#\) operator as counterpart of the concrete \(\mathbf {delay}\). See [19] for details.
Following from the definitions of the abstract TeSSLa operators we get:
Theorem 1
Every abstract TeSSLa operator is an abstraction of its concrete counterpart.
Theorem 1 implies that abstract TeSSLa operators are sound in the following way. Let o be a concrete TeSSLa operator with the abstract counterpart \(o^\#\) and let \(s \in \mathcal P_{\mathbb D}\) be an abstract event stream with a concretization function \(\gamma \). Then, \( o(\gamma (s)) \preceq \gamma (o^\#(s)). \) Since abstract interpretation is compositional we can directly follow from the above theorem:
Corollary 1
If a concrete TeSSLa specification \(\varphi \) is transformed into a specification \(\psi \) by replacing every concrete operator in \(\varphi \) with its abstract counterpart, then \(\psi \) is an abstraction of \(\varphi \).
Theorem 1 guarantees that applying abstract TeSSLa operators to the abstract event stream is still sound regarding the underlying set of possible concrete event streams. However, we have established no result so far about the accuracy of the abstract TeSSLa operators. The abstraction returning only the completely unknown stream (\(\varDelta = \emptyset \)) is sound but useless. The following theorem states, that our abstract TeSSLa operators are optimal in terms of accuracy. Using a perfect abstraction guarantees the abstract TeSSLa operators preserve as much information as can possibly be encoded in the resulting abstract event streams.
Theorem 2
Every abstract TeSSLa operator is a perfect abstraction of its concrete counterpart.
Given a concrete TeSSLa operator o and its abstract counterpart \(o^\#\), and any abstract event stream \(s \in \mathcal P_{\mathbb D}\) with the Galois Connection \((\alpha ,\gamma )\) between \(2^{\mathcal S_{\mathbb D}}\) and \(\mathcal P_{\mathbb D}\) one can show that \( o^\#(s) = \alpha (o(\gamma (s)). \) Applying the abstract operator on the abstract event stream is as good as applying the concrete operator on every possible event stream represented by the abstract event stream. Thus \(o^\#\) is a perfect abstraction of o. (The detailed proof can be found in [19].) Note that we assume that \(f^\#\) is a perfect abstraction of f to conclude that \(\mathbf {lift}^\# (f^\#)\) is a perfect abstraction of \(\mathbf {lift} (f)\).
In Corollary 1 we have shown that a specification \(\psi \) (generated by replacing the concrete TeSSLa operator in \(\varphi \) with their abstract counterparts) is an abstraction of \(\varphi \). Note that \(\psi \) is in general not a perfect abstraction of \(\varphi \). We study some special cases of perfect abstractions of compositional specifications in Sect. 4.
The next result states that the abstract operators can be defined in terms of concrete TeSSLa operators. Realizing the abstract operators in TeSSLa does not require an enhancement in the expressivity of TeSSLa.
Theorem 3
The semantics of the abstract TeSSLa operators can be encoded in TeSSLa using only the concrete operators.
Proof
One can observe that the abstract TeSSLa operators are monotone and future independent (the output stream up to t only depends on the input streams up to t.) As shown in [7], TeSSLa can express every such function. \(\square \)
3.1 Fixpoint Calculations Ensuring Well-Formedness
A concrete TeSSLa specification consists of stream variables and possibly recursive equations applying concrete TeSSLa operators to the stream variables. Theorem 1 and Corollary 1 guarantee that a concrete TeSSLa specification can be transformed into an abstract TeSSLa specification, which is able to handle gaps in the input streams. Additionally, Theorem 3 states that the abstract TeSSLa operators can be implemented using concrete TeSSLa operators. Combining these two results, one can transform a given concrete specification \(\varphi \) into a corresponding specification \(\psi \), which realizes the abstract TeSSLa semantics of the operators in \(\varphi \), but only uses concrete TeSSLa operators.
However, using the realization of the abstract TeSSLa operators in TeSSLa adds additional cyclic dependencies in \(\psi \) between the stream variables. A TeSSLa specification is well-formed if every cycle of its dependency graph contains at least one edge guarded by a last (or a delay) operator, which is required to guarantee the existence of a unique fixed-point and hence computability (see [7]).
Consider the trace diagram on the right showing \(\mathbf {last}^\# (v,r)\). If v is used in a recursive manner, i.e., v is defined in terms of \(\mathbf {last}^\# (v,r)\), then the first event on v could start a gap on \(\mathbf {last}^\# (v,r)\) that could start a gap on v at the same timestamp. As a result v has an unguarded cyclic dependency and hence the specification is not well-formed. To overcome this issue one can split up the value and gap calculation sequentially, reintroducing guards in the cyclic dependency:
Definition 2
(Unrolled Abstract Last). We define two variants of the abstract last, \(\mathbf {last}^\#_\bot \) and \(\mathbf {last}^\#_{{{\,\mathrm{\smallsmile }\,}}} \) as follows. Let \(z = \mathbf {last}^\# (v,r)\), then \(\mathbf {last}^\#_\bot (v,r) := z_\bot \) and \(\mathbf {last}^\#_{{{\,\mathrm{\smallsmile }\,}}} (v,r,d) := z_{{{\,\mathrm{\smallsmile }\,}}}\).
$$z_\bot (t) = {\left\{ \begin{array}{ll} z(t) &{} \text {if } z(t) \ne {{\,\mathrm{\smallsmile }\,}}\\ \bot &{} \text {otherwise} \end{array}\right. } \qquad z_{{{\,\mathrm{\smallsmile }\,}}}(t) = {\left\{ \begin{array}{ll} d(t) &{} \text {if } t \in ticks (d) \\ {{\,\mathrm{\smallsmile }\,}}&{} \text {if } t \notin ticks (d) \wedge z(t) = {{\,\mathrm{\smallsmile }\,}}\\ \bot &{} \text {otherwise} \end{array}\right. }$$
Function \(\mathbf {last}^\#_\bot \) executes a normal calculation of the events, in the same way an abstract last would do, but neglecting gaps and outputting \(\bot \) as long as there is no event. Function \(\mathbf {last}^\#_{{{\,\mathrm{\smallsmile }\,}}}\) takes a third input stream and outputs its events directly, but calculates gaps correctly as \(\mathbf {last}^\# \) would do.
Since the trigger input of a \(\mathbf {last}\) operator cannot be recursive in a well-formed specification, a recursive equation using one last has the form \(x = \mathbf {last}^\# (v,r)\) and \(v = f(x,\varvec{c})\), where \(\varvec{c}\) is a vector of streams not involved in the recursion and f does not introduce further last (or delay) operators. Now, this equation system can be rewritten in the following equivalent form:
$$ x' = \mathbf {last}^\#_\bot (v,r) \qquad v' = f(x',\varvec{c}) \qquad x = \mathbf {last}^\#_{{{\,\mathrm{\smallsmile }\,}}} (v',r,x') \qquad v = f(x,\varvec{c}) $$
This pattern can be repeated if multiple recursive abstract lasts are used and can also be applied in a similar fashion to mutually recursive equations and the delay operator.