1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//! # `temex` - Regex-like temporal expressions for evaluating systems that change over time
//!
//! This library implements *temexes*, which
//! are structures that combine formulas of propositional logic with
//! regular expression operators to provide a temporal logic designed
//! for simplicity and ease of use.
//!
//!
//!
//! # Table of contents
//! 1. [Introduction](#introduction)
//! 2. [Overview](#overview)
//! 3. [Temex syntax](#temex-syntax)
//! * [Propositional formula syntax](#propositional-formula-syntax)
//! * [Regex operator syntax](#regex-operator-syntax)
//! 4. [Supported trace formats](#supported-trace-formats)
//! 5. [Usage](#usage)
//! 6. [Examples](#examples)
//! * [Example 1: Testing for a match in a polars DataFrame using
//! `is_match`](#example-1-testing-for-a-match-in-a-polars-dataframe-using-is_match)
//! * [Example 2: Finding the location of a match using
//! `find`](#example-2-finding-the-location-of-a-match-using-find)
//! * [Example 3: Find all nonoverlapping matches using
//! `find_iter`](#example-3-find-all-nonoverlapping-matches-using-find_iter)
//!
//! ---
//!
//! # Introduction
//! Temporal logic has useful applications in a wide range of contexts, in and
//! outside of computer science. A detailed examination
//! is beyond the scope of this document, but a good introduction
//! is available [here](https://plato.stanford.edu/entries/logic-temporal/).
//! Although temporal logic has many use cases, most tools that implement some form of
//! temporal logic are designed for formal methods specialists and logicians,
//! not for a general audience. As a result, they can seem arcane and unapproachable to a layman.
//!
//! The purpose of `temex` is to provide a simple tool
//! that allows generalist software engineers and data analysts to harness the power
//! of temporal logic. It is designed to be easy to understand and use, requiring only
//! a basic knowledge of propositional logic and regular expressions.
//!
//! # Overview
//!
//! A *truth assignment* is a mapping of boolean variables to truth values (i.e., `true` or
//! `false`). When all the boolean variables that occur in a formula of propositional logic are
//! assigned a value, then that formula evaluates to either `true` or `false`. We can think of a
//! formula as defining a set of truth assignments: namely, the truth assignments that make that
//! formula evaluate to `true`.
//!
//! A regular expression is either a character, or it is a composite expression in
//! which subexpressions are joined by regex operators. A regular expression defines a set of sequences
//! of characters: those sequences that match the regular expression.
//!
//! We can think of truth assignments as characters, and we can think of a propositional
//! formula as a regular expression in which the truth assignments it defines are joined by
//! alternation (i.e., the regex operator `|`). So, for instance,
//! the formula `not (p and q)` can be thought of as `{p = true, q = false} | {p = false, q = true}
//! | {p = false, q = false}`.
//!
//! If we consider truth assignments as characters and propositional formulas as alternations over
//! these characters, then if we join propositional formulas with regex operators and interpret the
//! result according to the ordinary semantics of regular expressions we have something that
//! defines a sequence of truth assignments. We call this a *temex*, for temporal expression.
//!
//! We can use a temex to search sequences of truth assignments (henceforth called *traces*) in
//! much the same way we can use regular expressions to search strings. `temex` implements this
//! functionality.
//!
//! # Temex syntax
//!
//! Temex atoms are propositional formulas enclosed in square brackets (`[` and `]`). They may be
//! joined with regex operators in the same ways characters can be joined with regex operators in a
//! regular expression.
//!
//! ### Propositional formula syntax
//! A propositional formula may be a boolean variable (which begins with an alphabetic character and
//! may contain alphabetic or numeric characters as well as underscores), or a composite formula,
//! in which subformulas are joined with propositional connectives. `temex` uses the English words
//! `not`, `and`, and `or` for negation, conjunction, and disjunction; `not` has the highest
//! operator precedence, while `and` and `or` have equal precedence and are right associative.
//! Examples:
//!
//! * `f123`
//! * `is_sunny and not is_raining`
//! * `cpu_GT_80 and not (mem_LT_40 or io_LT_20)`
//!
//! ### Regex operator syntax
//! `temex`'s regex operators inherit the syntax of the [regex](https://crates.io/crates/regex)
//! crate, which `temex` uses to implement its regex operators.
//!
//! In the following forms, `phi`, `phi_1`, and `phi_2` are propositional formulas, and `n` and `m`
//! are natural numbers:
//!
//! <pre class="rust">
//! [phi_1][phi_2] concatenation (phi_1 followed by phi_2)
//! [phi_1]|[phi_2] alternation (phi_1 or phi_2, prefer phi_1)
//! [phi]* zero or more of phi (greedy)
//! [phi]+ one or more of phi (greedy)
//! [phi]? zero or one of phi (greedy)
//! [phi]*? zero or more of phi (ungreedy/lazy)
//! [phi]+? one or more of phi (ungreedy/lazy)
//! [phi]?? zero or one of phi (ungreedy/lazy)
//! [phi]{n,m} at least n phi and at most m phi (greedy)
//! [phi]{n,} at least n phi (greedy)
//! [phi]{n} exactly n phi
//! [phi]{n,m}? at least n phi and at most m phi (ungreedy/lazy)
//! [phi]{n,}? at least n phi (ungreedy/lazy)
//! [phi]{n}? exactly n phi
//! </pre>
//!
//! # Supported trace formats
//! The following trace formats are supported:
//!
//! * [polars](https://crates.io/crates/polars) dataframes in which all of the
//! columns are boolean. Column names are interpreted as the names of boolean variables and the
//! rows of the dataframe are interpreted as truth assignments.
//!
//! * CSVs encoded as UTF8 strings or files. The first line should contain the
//! names of boolean variables and the subsequent lines should contain values of either `1` or `0`,
//! for `true` or `false`, respectively. The lines after the header are interpreted as truth
//! assignments.
//!
//! When matching against a trace, the variable names in the `temex` pattern and the trace must be
//! the same; if there are extra variables in the trace then searching will result in an error.
//!
//! # Usage
//! Prior to performing any search operation, the temex pattern and trace must be compiled.
//! Compiling the temex pattern is done with [Temex::new](Temex::new), while compiling the trace is done
//! with [TemexTrace::try_from](TemexTrace::try_from). Searches may be performed using methods on
//! the temex; a complete listing is available [here](temex::Temex).
//!
//!
//!
//! # Examples
//!
//! ### Example 1: Testing for a match in a polars DataFrame using `is_match`
//!
//! This example shows how to perform a `temex` search in a
//! [polars](https://crates.io/crates/polars) DataFrame. The DataFrame must be converted to a
//! TemexTrace before searching. The example also shows how to convert a TemexTrace back to a
//! DataFrame. Note the use of `^` and `$`, which are anchors that match to the beginning and end
//! of the trace, respectively:
//! ```
//! use polars::df;
//! use polars::prelude::*;
//! use temex::{Temex, TemexTrace};
//! let df = df! [
//! "p1" => [true, true, true],
//! "p2" => [false, false, false],
//! "p3" => [true, false, true]
//! ]
//! .unwrap();
//! let trace = TemexTrace::try_from(df.clone()).unwrap();
//! let te = Temex::new("^[p1 and (p2 or p3)][p1][p1 or p2 and not p3]$").unwrap();
//!
//! assert!(te.is_match(&trace).unwrap());
//!
//! // We can convert the TemexTrace back to a DataFrame
//! let df_from_trace = polars::frame::DataFrame::try_from(trace).unwrap();
//! assert_eq!(df, df_from_trace);
//! ```
//!
//! #### Example 2: Finding the location of a match using `find`
//!
//! Here we use the `find` method to find the range of the left-most first match in the trace:
//! ```
//! # use temex::{Temex, TemexTrace};
//! let trace_str = "CPU_core1_GT_80, CPU_core2_GT_80, mem_usage_GT_40\n\
//! 0, 0, 0\n\
//! 1, 0, 0\n\
//! 0, 1, 0\n\
//! 0, 0, 1\n\
//! 1, 0, 1\n\
//! 0, 1, 1\n\
//! 1, 1, 0\n\
//! 1, 1, 0\n\
//! 1, 1, 0\n";
//!
//! let trace = trace_str.parse::<TemexTrace>().unwrap();
//! let te = Temex::new("[CPU_core1_GT_80 and CPU_core2_GT_80 and not mem_usage_GT_40]+").unwrap();
//!
//! assert_eq!(te.find(&trace).unwrap().unwrap().range(), 6..9);
//! ```
//!
//! #### Example 3: Find all nonoverlapping matches using `find_iter`
//!
//! In this example we see how the `find_iter` method can be used to find multiple nonoverlapping
//! matches within a trace:
//! ```
//! # use temex::{Temex, TemexTrace};
//! let trace_str = "is_sunny, is_windy\n\
//! 1, 0\n\
//! 1, 0\n\
//! 1, 0\n\
//! 0, 0\n\
//! 1, 0\n\
//! 1, 0\n\
//! 1, 0\n\
//! 1, 1\n\
//! 1, 0\n\
//! 1, 0\n\
//! 1, 0\n";
//!
//! let trace = TemexTrace::try_from(trace_str).unwrap();
//! let te = Temex::new("[is_sunny and not is_windy]{3}").unwrap();
//!
//! for te_match in te.find_iter(&trace) {
//! println!("{:?}", te_match.range());
//! }
//!
//! // Output:
//! // 0..3
//! // 4..7
//! // 8..11
//! ```
//!
pub
pub use crateTemex;
pub use crateTemexError;
pub use crateTemexMatch;
pub use crateTemexMatches;
pub use crateTemexTrace;