OR-Tools  9.6
affine_relation.h
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 #ifndef OR_TOOLS_UTIL_AFFINE_RELATION_H_
15 #define OR_TOOLS_UTIL_AFFINE_RELATION_H_
16 
17 #include <algorithm>
18 #include <vector>
19 
21 #include "ortools/base/logging.h"
22 #include "ortools/base/macros.h"
23 
24 namespace operations_research {
25 
26 // Union-Find algorithm to maintain "representative" for relations of the form:
27 // x = coeff * y + offset, where "coeff" and "offset" are integers. The variable
28 // x and y are represented by non-negative integer indices. The idea is to
29 // express variables in affine relation using as little different variables as
30 // possible (the representatives).
31 //
32 // IMPORTANT: If there are relations with std::abs(coeff) != 1, then some
33 // relations might be ignored. See TryAdd() for more details.
34 //
35 // TODO(user): it might be possible to do something fancier and drop less
36 // relations if all the affine relations are given before hand.
38  public:
39  AffineRelation() : num_relations_(0) {}
40 
41  // Returns the number of relations added to the class and not ignored.
42  int NumRelations() const { return num_relations_; }
43 
44  // Adds the relation x = coeff * y + offset to the class.
45  // Returns true if it wasn't ignored.
46  //
47  // This relation will only be taken into account if the representative of x
48  // and the one of y are different and if the relation can be transformed into
49  // a similar relation with integer coefficient between the two
50  // representatives.
51  //
52  // That is, given that:
53  // - x = coeff_x * representative_x + offset_x
54  // - y = coeff_y * representative_y + offset_y
55  // we have:
56  // coeff_x * representative_x + offset_x =
57  // coeff * coeff_y * representative_y + coeff * offset_y + offset.
58  // Which can be simplified with the introduction of new variables to:
59  // coeff_x * representative_x = new_coeff * representative_y + new_offset.
60  // And we can merge the two if:
61  // - new_coeff and new_offset are divisible by coeff_x.
62  // - OR coeff_x and new_offset are divisible by new_coeff.
63  //
64  // Checked preconditions: x >=0, y >= 0, coeff != 0 and x != y.
65  //
66  // IMPORTANT: we do not check for integer overflow, but that could be added
67  // if it is needed.
68  bool TryAdd(int x, int y, int64_t coeff, int64_t offset);
69 
70  // Same as TryAdd() with the option to disallow the use of a given
71  // representative.
72  bool TryAdd(int x, int y, int64_t coeff, int64_t offset, bool allow_rep_x,
73  bool allow_rep_y);
74 
75  // Returns a valid relation of the form x = coeff * representative + offset.
76  // Note that this can return x = x. Non-const because of path-compression.
77  struct Relation {
79  int64_t coeff;
80  int64_t offset;
81 
82  Relation(int r, int64_t c, int64_t o)
83  : representative(r), coeff(c), offset(o) {}
84  explicit Relation(int r) : representative(r) {}
85 
86  const bool operator==(const Relation& other) const {
87  return representative == other.representative && coeff == other.coeff &&
88  offset == other.offset;
89  }
90  };
91  Relation Get(int x) const;
92 
93  // Advanced usage. This is a bit hacky and will just decrease the class size
94  // of a variable without any extra checks. Use with care. In particular when
95  // this is called, then x should never be used anymore in any of the non const
96  // calls of this class.
97  void IgnoreFromClassSize(int x) {
98  if (x >= size_.size()) return; // never seen here.
99  CHECK_NE(size_[x], kSizeForRemovedEntry) << x;
100  const int r = Get(x).representative;
101  if (r != x) {
102  CHECK_GT(size_[r], 1);
103  size_[r]--;
104  } else {
105  CHECK_EQ(size_[r], 1);
106  }
107  size_[x] = kSizeForRemovedEntry;
108  }
109 
110  // Returns the size of the class of x.
111  int ClassSize(int x) const {
112  if (x >= representative_.size()) return 1;
113  return size_[Get(x).representative];
114  }
115 
116  private:
117  const int kSizeForRemovedEntry = 0;
118 
119  void IncreaseSizeOfMemberVectors(int new_size) {
120  if (new_size <= representative_.size()) return;
121  for (int i = representative_.size(); i < new_size; ++i) {
122  representative_.push_back(i);
123  }
124  offset_.resize(new_size, 0);
125  coeff_.resize(new_size, 1);
126  size_.resize(new_size, 1);
127  }
128 
129  void CompressPath(int x) const {
130  DCHECK_GE(x, 0);
131  DCHECK_LT(x, representative_.size());
132  tmp_path_.clear();
133  int parent = x;
134  while (parent != representative_[parent]) {
135  tmp_path_.push_back(parent);
136  parent = representative_[parent];
137  }
138  for (const int var : ::gtl::reversed_view(tmp_path_)) {
139  const int old_parent = representative_[var];
140  offset_[var] += coeff_[var] * offset_[old_parent];
141  coeff_[var] *= coeff_[old_parent];
142  representative_[var] = parent;
143  }
144  }
145 
146  int num_relations_;
147 
148  // The equivalence class representative for each variable index.
149  mutable std::vector<int> representative_;
150 
151  // The offset and coefficient such that
152  // variable[index] = coeff * variable[representative_[index]] + offset;
153  mutable std::vector<int64_t> coeff_;
154  mutable std::vector<int64_t> offset_;
155 
156  // The size of each representative "tree", used to get a good complexity when
157  // we have the choice of which tree to merge into the other.
158  //
159  // TODO(user): Using a "rank" might be faster, but because we sometimes
160  // need to merge the bad subtree into the better one, it is trickier to
161  // maintain than in the classic union-find algorithm.
162  std::vector<int> size_;
163 
164  // Used by CompressPath() to maintain the coeff/offset during compression.
165  mutable std::vector<int> tmp_path_;
166 };
167 
168 inline bool AffineRelation::TryAdd(int x, int y, int64_t coeff,
169  int64_t offset) {
170  return TryAdd(x, y, coeff, offset, true, true);
171 }
172 
173 inline bool AffineRelation::TryAdd(int x, int y, int64_t coeff, int64_t offset,
174  bool allow_rep_x, bool allow_rep_y) {
175  CHECK_NE(coeff, 0);
176  CHECK_NE(x, y);
177  CHECK_GE(x, 0);
178  CHECK_GE(y, 0);
179  IncreaseSizeOfMemberVectors(std::max(x, y) + 1);
180  CHECK_NE(size_[x], kSizeForRemovedEntry) << x;
181  CHECK_NE(size_[y], kSizeForRemovedEntry) << y;
182  CompressPath(x);
183  CompressPath(y);
184  const int rep_x = representative_[x];
185  const int rep_y = representative_[y];
186  if (rep_x == rep_y) return false;
187 
188  // TODO(user): It should be possible to optimize this code block a bit, for
189  // instance depending on the magnitude of new_coeff vs coeff_x, we may already
190  // know that one of the two merge is not possible.
191  const int64_t coeff_x = coeff_[x];
192  const int64_t new_coeff = coeff * coeff_[y];
193  const int64_t new_offset = coeff * offset_[y] + offset - offset_[x];
194  const bool condition1 =
195  allow_rep_y && (new_coeff % coeff_x == 0) && (new_offset % coeff_x == 0);
196  const bool condition2 = allow_rep_x && (coeff_x % new_coeff == 0) &&
197  (new_offset % new_coeff == 0);
198  if (condition1 && (!condition2 || size_[x] <= size_[y])) {
199  representative_[rep_x] = rep_y;
200  size_[rep_y] += size_[rep_x];
201  coeff_[rep_x] = new_coeff / coeff_x;
202  offset_[rep_x] = new_offset / coeff_x;
203  } else if (condition2) {
204  representative_[rep_y] = rep_x;
205  size_[rep_x] += size_[rep_y];
206  coeff_[rep_y] = coeff_x / new_coeff;
207  offset_[rep_y] = -new_offset / new_coeff;
208  } else {
209  return false;
210  }
211  ++num_relations_;
212  return true;
213 }
214 
216  if (x >= representative_.size() || representative_[x] == x) return {x, 1, 0};
217  CompressPath(x);
218  return {representative_[x], coeff_[x], offset_[x]};
219 }
220 
221 } // namespace operations_research
222 
223 #endif // OR_TOOLS_UTIL_AFFINE_RELATION_H_
int64_t max
Definition: alldiff_cst.cc:140
bool TryAdd(int x, int y, int64_t coeff, int64_t offset)
IntVar * var
Definition: expr_array.cc:1874
ReverseView< Container > reversed_view(const Container &c)
Collection of objects used to extend the Constraint Solver library.
const bool operator==(const Relation &other) const