LearnTA  0.0.1
constraint.hh
1 #pragma once
2 
3 #include <algorithm>
4 #include <cstdint>
5 #include <ostream>
6 #include <vector>
7 #include <unordered_map>
8 
9 #include <boost/unordered_map.hpp>
10 #include <boost/unordered_set.hpp>
11 #include <boost/log/trivial.hpp>
12 #include <boost/log/core.hpp>
13 #include <boost/log/expressions.hpp>
14 
15 #include "common_types.hh"
16 #include "bounds.hh"
17 
18 #define NO_EIGEN_CONSTRAINT
19 
20 namespace learnta {
21 
23  enum class Order {
24  LT, EQ, GT
25  };
26 
27  inline bool toBool(Order odr) { return odr == Order::EQ; }
28 
29  using IntBounds = std::pair<int, bool>;
30 
32  struct Constraint {
33  enum class Order {
34  lt, le, ge, gt
35  };
36 
37  ClockVariables x;
38  Order odr;
39  int c;
40 
41  [[nodiscard]] bool satisfy(double d) const {
42  switch (odr) {
43  case Order::lt:
44  return d < c;
45  case Order::le:
46  return d <= c;
47  case Order::gt:
48  return d > c;
49  case Order::ge:
50  return d >= c;
51  }
52  return false;
53  }
54 
55  [[nodiscard]] bool satisfy(std::vector<double> valuation) const {
56  return satisfy(valuation.at(x));
57  }
58 
59  using Interpretation = std::vector<double>;
60 
61  learnta::Order operator()(Interpretation val) const {
62  if (satisfy(val.at(x))) {
63  return learnta::Order::EQ;
64  } else if (odr == Order::lt || odr == Order::le) {
65  return learnta::Order::GT;
66  } else {
67  return learnta::Order::LT;
68  }
69  }
70 
71  bool operator==(Constraint another) const {
72  return this->odr == another.odr && this->c == another.c && this->x == another.x;
73  }
74 
75  [[nodiscard]] inline bool isUpperBound() const {
76  return this->odr == Order::le || this->odr == Order::lt;
77  }
78 
85  [[nodiscard]] bool isWeaker(const Constraint &another) const {
86  if (this->x != another.x) {
87  return false;
88  }
89  const bool thisLess = this->isUpperBound();
90  const bool anotherLess = another.isUpperBound();
91  const Bounds thisBounds = this->toDBMBound();
92  const Bounds anotherBounds = another.toDBMBound();
93 
94  return thisLess == anotherLess && anotherBounds <= thisBounds;
95  }
96 
97  [[nodiscard]] Constraint negate() const {
98  switch (odr) {
99  case Order::lt:
100  return Constraint{x, learnta::Constraint::Order::ge, c};
101  case Order::le:
102  return Constraint{x, learnta::Constraint::Order::gt, c};
103  case Order::gt:
104  return Constraint{x, learnta::Constraint::Order::le, c};
105  case Order::ge:
106  return Constraint{x, learnta::Constraint::Order::lt, c};
107  default:
108  throw std::range_error("Invalid order is used");
109  }
110  }
111 
112  [[nodiscard]] inline Bounds lowerBoundDurationToSatisfy(std::vector<double> valuation) const {
113  switch (odr) {
114  case Order::lt:
115  case Order::le:
116  // Bounded from above
117  return valuation.at(this->x) <= this->c ? Bounds{0.0, true} :
118  Bounds{-std::numeric_limits<double>::infinity(), false};
119  case Order::ge:
120  // Bounded from below
121  return std::min(Bounds{0.0, true}, Bounds{-this->c + valuation.at(this->x), true});
122  case Order::gt:
123  // Bounded from below
124  return std::min(Bounds{0.0, true}, Bounds{-this->c + valuation.at(this->x), false});
125  }
126  return Bounds{-std::numeric_limits<double>::infinity(), false};
127  }
128 
129  [[nodiscard]] IntBounds toBound() const {
130  switch (odr) {
131  case learnta::Constraint::Order::le:
132  case learnta::Constraint::Order::ge:
133  return IntBounds{c, true};
134  case learnta::Constraint::Order::lt:
135  case learnta::Constraint::Order::gt:
136  return IntBounds{c, false};
137  default:
138  throw std::range_error("Invalid order is used");
139  }
140  };
141 
142 
143  [[nodiscard]] Bounds toDBMBound() const {
144  switch (odr) {
145  case learnta::Constraint::Order::le:
146  return Bounds{c, true};
147  case learnta::Constraint::Order::ge:
148  return Bounds{-c, true};
149  case learnta::Constraint::Order::lt:
150  return Bounds{c, false};
151  case learnta::Constraint::Order::gt:
152  return Bounds{-c, false};
153  default:
154  throw std::range_error("Invalid order is used");
155  }
156  };
157  };
158 
159  inline int orderToInt(learnta::Constraint::Order order) {
160  switch (order) {
161  case learnta::Constraint::Order::lt:
162  return 0;
163  case learnta::Constraint::Order::le:
164  return 1;
165  case learnta::Constraint::Order::ge:
166  return 2;
167  case learnta::Constraint::Order::gt:
168  return 3;
169  default:
170  return 4;
171  }
172  }
173 
174  inline std::size_t hash_value(const Constraint guard) {
175  return boost::hash_value(std::make_tuple(guard.c, orderToInt(guard.odr), guard.x));
176  }
177 
178  static inline bool isWeaker(const std::vector<Constraint> &left, const std::vector<Constraint> &right) {
179  return std::all_of(left.begin(), left.end(), [&](const auto &leftGuard) {
180  return std::any_of(right.begin(), right.end(), [&](const auto &rightGuard) {
181  return leftGuard.isWeaker(rightGuard);
182  });
183  });
184  }
185 
186  static inline std::ostream &operator<<(std::ostream &os, const Constraint::Order &odr) {
187  switch (odr) {
188  case Constraint::Order::lt:
189  os << "<";
190  break;
191  case Constraint::Order::le:
192  os << "<=";
193  break;
194  case Constraint::Order::ge:
195  os << ">=";
196  break;
197  case Constraint::Order::gt:
198  os << ">";
199  break;
200  }
201  return os;
202  }
203 
204  static inline std::ostream &operator<<(std::ostream &os, const Constraint &p) {
205  os << "x" << int(p.x) << " " << p.odr << " " << p.c;
206  return os;
207  }
208 
209 // An interface to write an inequality constraint easily
211  ClockVariables x;
212 
213  public:
214  explicit ConstraintMaker(ClockVariables x) : x(x) {}
215 
216  Constraint operator<(int c) const {
217  return Constraint{x, Constraint::Order::lt, c};
218  }
219 
220  Constraint operator<=(int c) const {
221  return Constraint{x, Constraint::Order::le, c};
222  }
223 
224  Constraint operator>(int c) const {
225  return Constraint{x, Constraint::Order::gt, c};
226  }
227 
228  Constraint operator>=(int c) const {
229  return Constraint{x, Constraint::Order::ge, c};
230  }
231 
232  explicit operator ClockVariables() const {
233  return x;
234  }
235  };
236 
240  static inline void widen(std::vector<Constraint> &guard) {
241  guard.erase(std::remove_if(guard.begin(), guard.end(),
242  [](Constraint g) {
243  return g.odr == Constraint::Order::ge ||
244  g.odr == Constraint::Order::gt;
245  }),
246  guard.end());
247  }
248 
249  inline std::vector<Constraint> negateAll(const std::vector<Constraint> &constraints) {
250  std::vector<Constraint> result;
251  result.reserve(constraints.size());
252  std::transform(constraints.begin(), constraints.end(), std::back_inserter(result), [&](const auto &constraint) {
253  return constraint.negate();
254  });
255 
256  return result;
257  }
258 
259  inline std::vector<Constraint> conjunction(const std::vector<Constraint> &left,
260  const std::vector<Constraint> &right) {
261  std::vector<Constraint> result = left;
262  result.reserve(left.size() + right.size());
263  result.insert(result.end(), right.begin(), right.end());
264 
265  return result;
266  }
267 
268  inline auto toBounds(const std::vector<Constraint> &constraints) {
269  std::vector<IntBounds> upperBounds;
270  std::vector<IntBounds> lowerBounds;
271  const auto resizeBounds = [&](const ClockVariables x) {
272  if (x >= upperBounds.size()) {
273  const auto oldSize = upperBounds.size();
274  upperBounds.resize(x + 1);
275  lowerBounds.resize(x + 1);
276  for (auto i = oldSize; i <= x; ++i) {
277  upperBounds.at(i) = IntBounds(std::numeric_limits<int>::max(), false);
278  lowerBounds.at(i) = IntBounds(0, true);
279  }
280  }
281  };
282 
283  for (const auto &constraint: constraints) {
284  resizeBounds(constraint.x);
285  const auto bound = constraint.toBound();
286 
287  if (constraint.isUpperBound()) {
288  if (upperBounds.at(constraint.x) > bound) {
289  upperBounds.at(constraint.x) = bound;
290  }
291  } else {
292  if (-lowerBounds.at(constraint.x) > -bound) {
293  lowerBounds.at(constraint.x) = bound;
294  }
295  }
296  }
297 
298  return std::make_pair(upperBounds, lowerBounds);
299  }
300 
306  inline std::vector<ClockVariables> simpleVariables(const std::vector<Constraint> &constraints) {
307  std::vector<ClockVariables> result;
308  const auto &[upperBounds, lowerBounds] = toBounds(constraints);
309  assert(upperBounds.size() == lowerBounds.size());
310  result.reserve(upperBounds.size());
311  for (std::size_t i = 0; i < upperBounds.size(); ++i) {
312  if (isSimple(upperBounds.at(i), -lowerBounds.at(i))) {
313  result.push_back(i);
314  }
315  }
316 
317  assert(is_ascending(result));
318  return result;
319  }
320 
321  inline bool satisfiable(const std::vector<Constraint> &constraints) {
322  const auto &[upperBounds, lowerBounds] = toBounds(constraints);
323 
324  for (std::size_t i = 0; i < lowerBounds.size(); ++i) {
325  if (lowerBounds.at(i).first > upperBounds.at(i).first ||
326  (lowerBounds.at(i).first == upperBounds.at(i).first &&
327  !(lowerBounds.at(i).second && upperBounds.at(i).second))) {
328  return false;
329  }
330  }
331 
332  return true;
333  }
334 
335  inline std::vector<Constraint> simplify(const std::vector<Constraint> &constraints) {
336  const auto &[upperBounds, lowerBounds] = toBounds(constraints);
337  std::vector<Constraint> result;
338  result.reserve(upperBounds.size() + lowerBounds.size());
339 
340  for (std::size_t i = 0; i < lowerBounds.size(); ++i) {
341  if (lowerBounds.at(i) != IntBounds(0, true)) {
342  if (lowerBounds.at(i).second) {
343  result.push_back(ConstraintMaker(i) >= lowerBounds.at(i).first);
344  } else {
345  result.push_back(ConstraintMaker(i) > lowerBounds.at(i).first);
346  }
347  }
348  }
349 
350  for (std::size_t i = 0; i < upperBounds.size(); ++i) {
351  if (upperBounds.at(i) != IntBounds(std::numeric_limits<int>::max(), false)) {
352  if (upperBounds.at(i).second) {
353  result.push_back(ConstraintMaker(i) <= upperBounds.at(i).first);
354  } else {
355  result.push_back(ConstraintMaker(i) < upperBounds.at(i).first);
356  }
357  }
358  }
359 
360  return result;
361  }
362 
363  [[nodiscard]] inline Bounds lowerBoundDurationToSatisfy(const std::vector<Constraint> &guard,
364  const std::vector<double> &valuation) {
365  std::vector<Bounds> lowerBoundDurations;
366  lowerBoundDurations.reserve(guard.size());
367  std::transform(guard.begin(), guard.end(), std::back_inserter(lowerBoundDurations), [&](const auto &constraint) {
368  return constraint.lowerBoundDurationToSatisfy(valuation);
369  });
370 
371  return *std::min_element(lowerBoundDurations.begin(), lowerBoundDurations.end());
372  }
373 
374  static inline std::ostream &operator<<(std::ostream &os, const std::vector<Constraint> &guards) {
375  bool isFirst = true;
376  for (const Constraint &guard: guards) {
377  if (!isFirst) {
378  os << ", ";
379  }
380  os << guard;
381  isFirst = false;
382  }
383 
384  return os;
385  }
386 
387 #ifdef NO_EIGEN_CONSTRAINT
388 
389  inline std::vector<std::vector<Constraint>> negate(const std::vector<std::vector<Constraint>> &dnfConstraints) {
390  std::vector<std::vector<Constraint>> cnfNegated;
391  cnfNegated.reserve(dnfConstraints.size());
392  // Negation as CNF
393  std::transform(dnfConstraints.begin(), dnfConstraints.end(), std::back_inserter(cnfNegated), negateAll);
394  std::vector<std::vector<Constraint>> dnfNegated;
395  // Transform CNF to DNF
396  bool initial = true;
397  for (const auto &disjunct: cnfNegated) {
398  if (disjunct.empty()) {
399  continue;
400  }
401  if (initial) {
402  dnfNegated.reserve(disjunct.size());
403  std::transform(disjunct.begin(), disjunct.end(), std::back_inserter(dnfNegated), [](const auto &constraint) {
404  return std::vector<Constraint>{constraint};
405  });
406  initial = false;
407  } else {
408  std::vector<std::vector<Constraint>> newDnfNegated;
409  for (const auto &constraint: disjunct) {
410  for (auto conjunct: dnfNegated) {
411  conjunct.push_back(constraint);
412  if (satisfiable(conjunct)) {
413  newDnfNegated.push_back(simplify(conjunct));
414  }
415  }
416  }
417  for (auto it = newDnfNegated.begin(); it != newDnfNegated.end();) {
418  if (std::any_of(it + 1, newDnfNegated.end(), [&](const auto &constraints) {
419  return isWeaker(constraints, *it);
420  }) || std::any_of(newDnfNegated.begin(), it, [&](const auto &constraints) {
421  return isWeaker(constraints, *it);
422  })) {
423  it = newDnfNegated.erase(it);
424  } else {
425  ++it;
426  }
427  }
428  dnfNegated = std::move(newDnfNegated);
429  }
430  }
431 
432  return dnfNegated;
433  }
434 
435 #endif
436 
440  static inline auto unionHull(const std::vector<std::vector<Constraint>> &guards) {
441  boost::unordered_map<std::pair<ClockVariables, bool>, Bounds> guardsAsBounds;
442  bool initial = true;
443  for (const auto &guard: guards) {
444  boost::unordered_set<std::pair<ClockVariables, bool>> boundedKeys;
445  for (const auto &constraint: guard) {
446  const auto clock = constraint.x;
447  const auto upperBound = constraint.isUpperBound();
448  boundedKeys.emplace(clock, upperBound);
449  auto it = guardsAsBounds.find(std::make_pair(clock, upperBound));
450  if (it == guardsAsBounds.end()) {
451  if (initial) {
452  guardsAsBounds[std::make_pair(clock, upperBound)] = constraint.toDBMBound();
453  }
454  } else {
455  it->second = std::max(it->second, constraint.toDBMBound());
456  }
457  }
458  // We have unbounded variables
459  for (auto it = guardsAsBounds.begin(); it != guardsAsBounds.end();) {
460  if (boundedKeys.find(it->first) == boundedKeys.end()) {
461  it = guardsAsBounds.erase(it);
462  } else {
463  ++it;
464  }
465  }
466  initial = false;
467  }
468 
469  std::vector<Constraint> result;
470  result.reserve(guardsAsBounds.size());
471  std::transform(guardsAsBounds.begin(), guardsAsBounds.end(), std::back_inserter(result), [](const auto p) {
472  const auto clock = p.first.first;
473  const auto upperBound = p.first.second;
474  const auto bound = p.second;
475  if (upperBound) {
476  if (bound.second) {
477  return ConstraintMaker(clock) <= bound.first;
478  } else {
479  return ConstraintMaker(clock) < bound.first;
480  }
481  } else {
482  if (bound.second) {
483  return ConstraintMaker(clock) >= -bound.first;
484  } else {
485  return ConstraintMaker(clock) > -bound.first;
486  }
487  }
488  });
489 
490  // Assert the weakness
491  assert(std::all_of(guards.begin(), guards.end(), [&](const auto guard) {
492  return isWeaker(result, guard);
493  }));
494 
495  return result;
496  }
497 
501  static inline auto unionHull(const std::vector<Constraint> &left, const std::vector<Constraint> &right) {
502  return unionHull(std::vector<std::vector<Constraint>>{left, right});
503  }
504 
505  inline void addUpperBound(std::vector<Constraint> &guard) {
506  std::unordered_map<ClockVariables, std::vector<Constraint>> mapFromClock;
507  for (const auto &constraint: guard) {
508  auto it = mapFromClock.find(constraint.x);
509  if (it == mapFromClock.end()) {
510  mapFromClock[constraint.x] = {constraint};
511  } else {
512  mapFromClock.at(constraint.x).push_back(constraint);
513  }
514  }
515  for (const auto &[clock, constraints]: mapFromClock) {
516  // assume that the guard is non-redundant
517  if (constraints.size() == 1 && !constraints.front().isUpperBound()) {
518  if (constraints.front().odr == Constraint::Order::le) {
519  guard.push_back(ConstraintMaker(clock) <= constraints.front().c);
520  } else {
521  guard.push_back(ConstraintMaker(clock) < constraints.front().c + 1);
522  }
523  }
524  }
525  }
526 
530  static inline bool adjacent(const std::vector<Constraint> &left, const std::vector<Constraint> &right) {
531  auto [leftUpperBound, leftLowerBound] = toBounds(left);
532  auto [rightUpperBound, rightLowerBound] = toBounds(right);
533  if (leftUpperBound.size() != rightUpperBound.size()) {
534  return false;
535  }
536  std::vector<std::size_t> differentIndices;
537  const auto maxBound = IntBounds{std::numeric_limits<int>::max(), false};
538  for (std::size_t i = 0; i < leftUpperBound.size(); ++i) {
539  if (leftUpperBound.at(i) == maxBound && rightUpperBound.at(i) != maxBound) {
540  leftUpperBound.at(i) = leftLowerBound.at(i).second ? leftLowerBound.at(i) :
541  IntBounds{leftLowerBound.at(i).first + 1, leftLowerBound.at(i).second};
542  } else if (leftUpperBound.at(i) != maxBound && rightUpperBound.at(i) == maxBound) {
543  rightUpperBound.at(i) = rightLowerBound.at(i).second ? rightLowerBound.at(i) :
544  IntBounds{rightLowerBound.at(i).first + 1, rightLowerBound.at(i).second};
545  }
546  if (leftUpperBound.at(i) == rightUpperBound.at(i) && leftLowerBound.at(i) == rightLowerBound.at(i)) {
547  continue;
548  }
549  differentIndices.push_back(i);
550  // Check if they are adjacent
551  if (leftUpperBound.at(i).first == rightLowerBound.at(i).first) {
552  if (leftUpperBound.at(i).second == rightLowerBound.at(i).second) {
553  return false;
554  }
555  } else if (rightUpperBound.at(i).first == leftLowerBound.at(i).first) {
556  if (rightUpperBound.at(i).second == leftLowerBound.at(i).second) {
557  return false;
558  }
559  } else {
560  return false;
561  }
562  }
563  if (differentIndices.size() <= 1) {
564  return true;
565  }
566 
567  // Check the consistency of the adjacent kinds
568  enum class Kinds {
569  DEFAULT,
570  LEFT_LOWER_EQ,
571  RIGHT_LOWER_EQ,
572  LEFT_UPPER_EQ,
573  RIGHT_UPPER_EQ
574  };
575 
576  Kinds kind = Kinds::DEFAULT;
577 
578  for (const auto index: differentIndices) {
579  if (leftLowerBound.at(index).second) {
580  if (kind == Kinds::DEFAULT) {
581  kind = Kinds::LEFT_LOWER_EQ;
582  } else if (kind != Kinds::LEFT_LOWER_EQ) {
583  return false;
584  }
585  } else if (rightLowerBound.at(index).second) {
586  if (kind == Kinds::DEFAULT) {
587  kind = Kinds::RIGHT_LOWER_EQ;
588  } else if (kind != Kinds::RIGHT_LOWER_EQ) {
589  return false;
590  }
591  } else if (leftUpperBound.at(index).second) {
592  if (kind == Kinds::DEFAULT) {
593  kind = Kinds::LEFT_UPPER_EQ;
594  } else if (kind != Kinds::LEFT_UPPER_EQ) {
595  return false;
596  }
597  } else if (rightUpperBound.at(index).second) {
598  if (kind == Kinds::DEFAULT) {
599  kind = Kinds::RIGHT_UPPER_EQ;
600  } else if (kind != Kinds::RIGHT_UPPER_EQ) {
601  return false;
602  }
603  }
604  }
605 
606  return true;
607  }
608 }
Definition: constraint.hh:210
Definition: experiment_runner.hh:23
std::vector< ClockVariables > simpleVariables(const std::vector< Constraint > &constraints)
Return the variables that are bounded by a simple constraint, i.e., c < x < c + 1 or x = c.
Definition: constraint.hh:306
static bool adjacent(const std::vector< Constraint > &left, const std::vector< Constraint > &right)
Check if two guards are adjacent.
Definition: constraint.hh:530
bool is_ascending(const std::vector< T > &container)
Check if the elements are sorted in the ascending order.
Definition: common_types.hh:39
Order
The return values of comparison of two values. Similar to strcmp.
Definition: constraint.hh:23
static void widen(std::vector< Constraint > &guard)
remove any inequality x > c or x >= c
Definition: constraint.hh:240
static auto unionHull(const std::vector< std::vector< Constraint >> &guards)
Return the strongest guard that is weaker than all of the given guards.
Definition: constraint.hh:440
A constraint in a guard of transitions.
Definition: constraint.hh:32
bool isWeaker(const Constraint &another) const
Check if this constraint is weaker than the given one.
Definition: constraint.hh:85