clock_zones/
zones.rs

1use std::cmp::min;
2use std::iter::IntoIterator;
3
4use crate::bounds::*;
5use crate::clocks::*;
6use crate::constants::*;
7use crate::storage::*;
8
9/// A *clock constraint* bounding the difference between two clocks.
10pub struct Constraint<B: Bound> {
11    pub(crate) left: Clock,
12    pub(crate) right: Clock,
13    pub(crate) bound: B,
14}
15
16impl<B: Bound> Constraint<B> {
17    /// Returns the left-hand side of the difference.
18    pub fn left(&self) -> Clock {
19        self.left
20    }
21
22    /// Returns the right-hand side of the difference.
23    pub fn right(&self) -> Clock {
24        self.right
25    }
26
27    /// Returns the bound.
28    pub fn bound(&self) -> &B {
29        &self.bound
30    }
31
32    /// Constructs a new constraint.
33    pub fn new(left: impl AnyClock, right: impl AnyClock, bound: B) -> Self {
34        Self {
35            left: left.as_clock(),
36            right: right.as_clock(),
37            bound,
38        }
39    }
40
41    /// Constructs a new constraint of the form `clock ≤ constant`.
42    pub fn new_le(clock: impl AnyClock, constant: B::Constant) -> Self {
43        Constraint {
44            left: clock.as_clock(),
45            right: Clock::ZERO,
46            bound: B::new_le(constant),
47        }
48    }
49
50    /// Constructs a new constraint of the form `clock < constant`.
51    pub fn new_lt(clock: impl AnyClock, constant: B::Constant) -> Self {
52        Constraint {
53            left: clock.as_clock(),
54            right: Clock::ZERO,
55            bound: B::new_lt(constant),
56        }
57    }
58
59    /// Constructs a new constraint of the form `clock ≥ constant`.
60    ///
61    /// Panics in case the constant cannot be negated without an overflow.
62    pub fn new_ge(clock: impl AnyClock, constant: B::Constant) -> Self {
63        Constraint {
64            left: Clock::ZERO,
65            right: clock.as_clock(),
66            bound: B::new_le(
67                constant
68                    .checked_neg()
69                    .expect("overflow while negating constant"),
70            ),
71        }
72    }
73
74    /// Constructs a new constraint of the form `clock > constant`.
75    ///
76    /// Panics in case the constant cannot be negated without an overflow.
77    pub fn new_gt(clock: impl AnyClock, constant: B::Constant) -> Self {
78        Constraint {
79            left: Clock::ZERO,
80            right: clock.as_clock(),
81            bound: B::new_lt(
82                constant
83                    .checked_neg()
84                    .expect("overflow while negating constant"),
85            ),
86        }
87    }
88    /// Constructs a new constraint of the form `left - right ≤ constant`.
89    pub fn new_diff_le(left: impl AnyClock, right: impl AnyClock, constant: B::Constant) -> Self {
90        Constraint {
91            left: left.as_clock(),
92            right: right.as_clock(),
93            bound: B::new_le(constant),
94        }
95    }
96
97    /// Constructs a new constraint of the form `left - right < constant`.
98    pub fn new_diff_lt(left: impl AnyClock, right: impl AnyClock, constant: B::Constant) -> Self {
99        Constraint {
100            left: left.as_clock(),
101            right: right.as_clock(),
102            bound: B::new_lt(constant),
103        }
104    }
105
106    /// Constructs a new constraint of the form `left - right ≥ constant`.
107    ///
108    /// Panics in case the constant cannot be negated without an overflow.
109    pub fn new_diff_ge(left: impl AnyClock, right: impl AnyClock, constant: B::Constant) -> Self {
110        Constraint {
111            // the swapped order is intentional
112            left: right.as_clock(),
113            right: left.as_clock(),
114            bound: B::new_le(
115                constant
116                    .checked_neg()
117                    .expect("overflow while negating constant"),
118            ),
119        }
120    }
121
122    /// Constructs a new constraint of the form `left - right > constant`.
123    ///
124    /// Panics in case the constant cannot be negated without an overflow.
125    pub fn new_diff_gt(left: impl AnyClock, right: impl AnyClock, constant: B::Constant) -> Self {
126        Constraint {
127            // the swapped order is intentional
128            left: right.as_clock(),
129            right: left.as_clock(),
130            bound: B::new_lt(
131                constant
132                    .checked_neg()
133                    .expect("overflow while negating constant"),
134            ),
135        }
136    }
137}
138
139/// Represents a zone with a specific *[bound type][Bound]*.
140pub trait Zone {
141    type Bound: Bound;
142
143    /// Constructs a new zone without any constraints besides clocks being positive.
144    fn new_unconstrained(num_variables: usize) -> Self;
145    /// Constructs a new zone where all clocks are set to zero.
146    fn new_zero(num_variables: usize) -> Self;
147
148    /// Constructs a new zone from the given iterable of [constraints][Constraint].
149    fn with_constraints<U>(num_variables: usize, constraints: U) -> Self
150    where
151        U: IntoIterator<Item = Constraint<Self::Bound>>;
152
153    /// Returns the number of clock variables of this zone.
154    fn num_variables(&self) -> usize;
155
156    /// Returns the number of clocks of this zone.
157    ///
158    /// Note: This is always `num_variables + 1` as there is the constant
159    /// zero clock plus the clock variables.
160    fn num_clocks(&self) -> usize;
161
162    /// Retrieves the difference bound for `left - right`.
163    fn get_bound(&self, left: impl AnyClock, right: impl AnyClock) -> &Self::Bound;
164
165    /// Checks whether the zone is empty.
166    fn is_empty(&self) -> bool;
167
168    /// Adds a [constraint][Constraint] to the zone.
169    fn add_constraint(&mut self, constraint: Constraint<Self::Bound>);
170    /// Adds all [constraints][Constraint] from the given iterable to the zone.
171    fn add_constraints<U>(&mut self, constraints: U)
172    where
173        U: IntoIterator<Item = Constraint<Self::Bound>>;
174
175    /// Intersects two zones.
176    fn intersect(&mut self, other: &Self);
177
178    /// Computes the *future* of the zone by removing all upper bounds.
179    ///
180    /// This operation is sometimes also known as *up*.
181    fn future(&mut self);
182    /// Computes the *past* of the zone by removing all lower bounds.
183    ///
184    /// This operation is sometimes also known as *down*.
185    fn past(&mut self);
186
187    /// Resets the given clock variable to the specified constant.
188    fn reset(&mut self, clock: Variable, value: <Self::Bound as Bound>::Constant);
189
190    /// Checks whether the value of the given clock is unbounded.
191    fn is_unbounded(&self, clock: impl AnyClock) -> bool;
192
193    /// Returns the upper bound for the value of the given clock.
194    fn get_upper_bound(&self, clock: impl AnyClock) -> Option<<Self::Bound as Bound>::Constant>;
195    /// Returns the lower bound for the value of the given clock.
196    fn get_lower_bound(&self, clock: impl AnyClock) -> Option<<Self::Bound as Bound>::Constant>;
197
198    /// Checks whether the given constraint is satisfied by the zone.
199    fn is_satisfied(&self, constraint: Constraint<Self::Bound>) -> bool;
200
201    /// Checks whether the zone includes the other zone.
202    fn includes(&self, other: &Self) -> bool;
203
204    /// Creates a resized copy of the zone by adding or removing clocks.
205    ///
206    /// Added clocks will be unconstrained.
207    fn resize(&self, num_variables: usize) -> Self;
208}
209
210/// Returns an iterator over the clocks of a zone.
211#[inline(always)]
212pub fn clocks<Z: Zone>(zone: &Z) -> impl Iterator<Item = Clock> {
213    (0..zone.num_clocks()).map(Clock)
214}
215
216/// Returns an iterator over the variables of a zone.
217#[inline(always)]
218pub fn variables<Z: Zone>(zone: &Z) -> impl Iterator<Item = Variable> {
219    (1..zone.num_clocks()).map(Variable)
220}
221
222/// An implementation of [Zone] as *difference bound matrix*.
223///
224/// Uses [LinearLayout] as the default [storage layout][Layout].
225#[derive(Eq, PartialEq, Hash, Clone, Debug)]
226pub struct Dbm<B: Bound, L: Layout<B> = LinearLayout<B>> {
227    /// The dimension of the matrix.
228    dimension: usize,
229    /// The internal representation using the given layout.
230    layout: L,
231
232    _phantom_bound: std::marker::PhantomData<B>,
233}
234
235impl<B: Bound, L: Layout<B>> Dbm<B, L> {
236    fn new(num_variables: usize, default: B) -> Self {
237        let dimension = num_variables + 1;
238        let mut layout = L::new(num_variables, default);
239        layout.set(Clock::ZERO, Clock::ZERO, B::le_zero());
240        for index in 1..dimension {
241            let clock = Clock::from_index(index);
242            layout.set(Clock::ZERO, clock, B::le_zero());
243            layout.set(clock, clock, B::le_zero());
244        }
245        Dbm {
246            dimension,
247            layout,
248            _phantom_bound: std::marker::PhantomData,
249        }
250    }
251
252    /// Canonicalize the DBM given that a particular clock has been touched.
253    #[inline(always)]
254    fn canonicalize_touched(&mut self, touched: Clock) {
255        for index in 0..self.dimension {
256            let left = Clock::from_index(index);
257            for index in 0..self.dimension {
258                let right = Clock::from_index(index);
259                let bound = self
260                    .layout
261                    .get(left, touched)
262                    .add(self.layout.get(touched, right))
263                    .expect("overflow while adding bounds");
264                if bound.is_tighter_than(self.layout.get(left, right)) {
265                    self.layout.set(left, right, bound);
266                }
267            }
268        }
269    }
270
271    fn canonicalize(&mut self) {
272        for index in 0..self.dimension {
273            let touched = Clock::from_index(index);
274            self.canonicalize_touched(touched);
275        }
276    }
277}
278
279impl<B: Bound, L: Layout<B>> Zone for Dbm<B, L> {
280    type Bound = B;
281
282    fn new_unconstrained(num_variables: usize) -> Self {
283        Dbm::new(num_variables, B::unbounded())
284    }
285    fn new_zero(num_variables: usize) -> Self {
286        Dbm::new(num_variables, B::le_zero())
287    }
288
289    fn with_constraints<U>(num_variables: usize, constraints: U) -> Self
290    where
291        U: IntoIterator<Item = Constraint<B>>,
292    {
293        let mut zone = Self::new_unconstrained(num_variables);
294        for constraint in constraints {
295            zone.layout
296                .set(constraint.left, constraint.right, constraint.bound.clone());
297        }
298        zone.canonicalize();
299        zone
300    }
301
302    #[inline(always)]
303    fn num_variables(&self) -> usize {
304        self.dimension - 1
305    }
306
307    #[inline(always)]
308    fn num_clocks(&self) -> usize {
309        self.dimension
310    }
311
312    #[inline(always)]
313    fn get_bound(&self, left: impl AnyClock, right: impl AnyClock) -> &B {
314        self.layout.get(left, right)
315    }
316
317    #[inline(always)]
318    fn is_empty(&self) -> bool {
319        self.layout
320            .get(Clock::ZERO, Clock::ZERO)
321            .is_tighter_than(&B::le_zero())
322    }
323
324    fn add_constraint(&mut self, constraint: Constraint<B>) {
325        let bound = self.layout.get(constraint.left, constraint.right);
326        if constraint.bound.is_tighter_than(&bound) {
327            self.layout
328                .set(constraint.left, constraint.right, constraint.bound);
329            self.canonicalize_touched(constraint.left);
330            self.canonicalize_touched(constraint.right);
331        }
332    }
333
334    fn add_constraints<U>(&mut self, constraints: U)
335    where
336        U: IntoIterator<Item = Constraint<B>>,
337    {
338        for constraint in constraints {
339            self.add_constraint(constraint);
340        }
341    }
342
343    fn intersect(&mut self, other: &Self) {
344        assert_eq!(
345            self.dimension, other.dimension,
346            "unable to intersect zones of different dimension"
347        );
348        for index in 0..self.dimension {
349            let left = Clock::from_index(index);
350            for index in 0..self.dimension {
351                let right = Clock::from_index(index);
352                if other
353                    .layout
354                    .get(left, right)
355                    .is_tighter_than(&self.layout.get(left, right))
356                {
357                    self.layout
358                        .set(left, right, other.layout.get(left, right).clone());
359                }
360            }
361        }
362        self.canonicalize();
363    }
364
365    fn future(&mut self) {
366        for index in 1..self.dimension {
367            let left = Clock::from_index(index);
368            self.layout.set(left, Clock::ZERO, B::unbounded());
369        }
370    }
371    fn past(&mut self) {
372        for index in 1..self.dimension {
373            let right = Clock::from_index(index);
374            self.layout.set(Clock::ZERO, right, B::le_zero());
375            for index in 1..self.dimension {
376                let left = Clock::from_index(index);
377                if self
378                    .layout
379                    .get(left, right)
380                    .is_tighter_than(self.layout.get(Clock::ZERO, right))
381                {
382                    self.layout
383                        .set(Clock::ZERO, right, self.layout.get(left, right).clone());
384                }
385            }
386        }
387    }
388
389    fn reset(&mut self, clock: Variable, value: B::Constant) {
390        let le_pos_value = B::new_le(value.clone());
391        let le_neg_value = B::new_le(value.checked_neg().unwrap());
392        for index in 0..self.dimension {
393            let other = Clock::from_index(index);
394            self.layout.set(
395                clock,
396                other,
397                self.layout
398                    .get(Clock::ZERO, other)
399                    .add(&le_pos_value)
400                    .unwrap(),
401            );
402            self.layout.set(
403                other,
404                clock,
405                self.layout
406                    .get(other, Clock::ZERO)
407                    .add(&le_neg_value)
408                    .unwrap(),
409            );
410        }
411    }
412
413    fn is_unbounded(&self, clock: impl AnyClock) -> bool {
414        self.layout.get(clock, Clock::ZERO).is_unbounded()
415    }
416
417    fn get_upper_bound(&self, clock: impl AnyClock) -> Option<B::Constant> {
418        self.layout.get(clock, Clock::ZERO).constant()
419    }
420
421    fn get_lower_bound(&self, clock: impl AnyClock) -> Option<B::Constant> {
422        Some(
423            self.layout
424                .get(Clock::ZERO, clock)
425                .constant()?
426                .checked_neg()
427                .unwrap(),
428        )
429    }
430
431    fn is_satisfied(&self, constraint: Constraint<B>) -> bool {
432        !constraint
433            .bound
434            .is_tighter_than(self.layout.get(constraint.left, constraint.right))
435    }
436
437    fn includes(&self, other: &Self) -> bool {
438        for index in 0..self.dimension {
439            let left = Clock::from_index(index);
440            for index in 0..self.dimension {
441                let right = Clock::from_index(index);
442                if self
443                    .layout
444                    .get(left, right)
445                    .is_tighter_than(other.layout.get(left, right))
446                {
447                    return false;
448                }
449            }
450        }
451        true
452    }
453
454    fn resize(&self, num_variables: usize) -> Self {
455        let mut other = Self::new_unconstrained(num_variables);
456        for index in 0..min(self.dimension, other.dimension) {
457            let left = Clock::from_index(index);
458            for index in 0..min(self.dimension, other.dimension) {
459                let right = Clock::from_index(index);
460                let bound = self.layout.get(left, right);
461                other.layout.set(left, right, bound.clone());
462            }
463        }
464        other.canonicalize();
465        other
466    }
467}
468
469/// A 32-bit signed integer zone.
470pub type ZoneI32 = Dbm<i32>;
471
472/// A 64-bit signed integer zone.
473pub type ZoneI64 = Dbm<i64>;
474
475/// A 32-bit floating-point zone.
476#[cfg(feature = "float")]
477pub type ZoneF32 = Dbm<ConstantBound<ordered_float::NotNan<f32>>>;
478
479/// A 64-bit floating-point zone.
480#[cfg(feature = "float")]
481pub type ZoneF64 = Dbm<ConstantBound<ordered_float::NotNan<f64>>>;
482
483#[cfg(test)]
484mod tests {
485    use super::*;
486
487    #[test]
488    fn test_basics() {
489        let mut zone: Dbm<i64> = Dbm::new_zero(3);
490        let x = Clock::variable(0);
491        let y = Clock::variable(1);
492
493        assert_eq!(zone.get_lower_bound(x), Some(0));
494        assert_eq!(zone.get_upper_bound(x), Some(0));
495        zone.future();
496        assert_eq!(zone.get_lower_bound(x), Some(0));
497        assert_eq!(zone.get_upper_bound(x), None);
498        assert!(zone.is_unbounded(x));
499        let mut copy = zone.clone();
500        zone.add_constraint(Constraint::new_lt(x, 4));
501        assert!(!zone.is_unbounded(x));
502        assert_eq!(zone.get_lower_bound(x), Some(0));
503        assert_eq!(zone.get_upper_bound(x), Some(4));
504        assert!(copy.includes(&zone));
505        copy.add_constraint(Constraint::new_le(x, 4));
506        assert!(copy.includes(&zone));
507        copy.add_constraint(Constraint::new_lt(x, 3));
508        assert!(!copy.includes(&zone));
509        assert!(zone.includes(&copy));
510        zone.intersect(&copy);
511        assert!(zone.includes(&copy));
512        assert!(copy.includes(&zone));
513        copy.add_constraint(Constraint::new_diff_lt(x, y, 4));
514        copy.add_constraint(Constraint::new_diff_gt(x, y, 5));
515        assert!(copy.is_empty());
516    }
517}