-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
affine_relation.h
223 lines (196 loc) · 8 KB
/
affine_relation.h
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
// Copyright 2010-2024 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef OR_TOOLS_UTIL_AFFINE_RELATION_H_
#define OR_TOOLS_UTIL_AFFINE_RELATION_H_
#include <algorithm>
#include <cstdint>
#include <vector>
#include "ortools/base/iterator_adaptors.h"
#include "ortools/base/logging.h"
namespace operations_research {
// Union-Find algorithm to maintain "representative" for relations of the form:
// x = coeff * y + offset, where "coeff" and "offset" are integers. The variable
// x and y are represented by non-negative integer indices. The idea is to
// express variables in affine relation using as little different variables as
// possible (the representatives).
//
// IMPORTANT: If there are relations with std::abs(coeff) != 1, then some
// relations might be ignored. See TryAdd() for more details.
//
// TODO(user): it might be possible to do something fancier and drop less
// relations if all the affine relations are given before hand.
class AffineRelation {
public:
AffineRelation() : num_relations_(0) {}
// Returns the number of relations added to the class and not ignored.
int NumRelations() const { return num_relations_; }
// Adds the relation x = coeff * y + offset to the class.
// Returns true if it wasn't ignored.
//
// This relation will only be taken into account if the representative of x
// and the one of y are different and if the relation can be transformed into
// a similar relation with integer coefficient between the two
// representatives.
//
// That is, given that:
// - x = coeff_x * representative_x + offset_x
// - y = coeff_y * representative_y + offset_y
// we have:
// coeff_x * representative_x + offset_x =
// coeff * coeff_y * representative_y + coeff * offset_y + offset.
// Which can be simplified with the introduction of new variables to:
// coeff_x * representative_x = new_coeff * representative_y + new_offset.
// And we can merge the two if:
// - new_coeff and new_offset are divisible by coeff_x.
// - OR coeff_x and new_offset are divisible by new_coeff.
//
// Checked preconditions: x >=0, y >= 0, coeff != 0 and x != y.
//
// IMPORTANT: we do not check for integer overflow, but that could be added
// if it is needed.
bool TryAdd(int x, int y, int64_t coeff, int64_t offset);
// Same as TryAdd() with the option to disallow the use of a given
// representative.
bool TryAdd(int x, int y, int64_t coeff, int64_t offset, bool allow_rep_x,
bool allow_rep_y);
// Returns a valid relation of the form x = coeff * representative + offset.
// Note that this can return x = x. Non-const because of path-compression.
struct Relation {
int representative;
int64_t coeff;
int64_t offset;
Relation(int r, int64_t c, int64_t o)
: representative(r), coeff(c), offset(o) {}
explicit Relation(int r) : representative(r) {}
bool operator==(const Relation& other) const {
return representative == other.representative && coeff == other.coeff &&
offset == other.offset;
}
};
Relation Get(int x) const;
// Advanced usage. This is a bit hacky and will just decrease the class size
// of a variable without any extra checks. Use with care. In particular when
// this is called, then x should never be used anymore in any of the non const
// calls of this class.
void IgnoreFromClassSize(int x) {
if (x >= size_.size()) return; // never seen here.
CHECK_NE(size_[x], kSizeForRemovedEntry) << x;
const int r = Get(x).representative;
if (r != x) {
CHECK_GT(size_[r], 1);
size_[r]--;
} else {
CHECK_EQ(size_[r], 1);
}
size_[x] = kSizeForRemovedEntry;
}
// Returns the size of the class of x.
int ClassSize(int x) const {
if (x >= representative_.size()) return 1;
return size_[Get(x).representative];
}
private:
const int kSizeForRemovedEntry = 0;
void IncreaseSizeOfMemberVectors(int new_size) {
if (new_size <= representative_.size()) return;
for (int i = representative_.size(); i < new_size; ++i) {
representative_.push_back(i);
}
offset_.resize(new_size, 0);
coeff_.resize(new_size, 1);
size_.resize(new_size, 1);
}
void CompressPath(int x) const {
DCHECK_GE(x, 0);
DCHECK_LT(x, representative_.size());
tmp_path_.clear();
int parent = x;
while (parent != representative_[parent]) {
tmp_path_.push_back(parent);
parent = representative_[parent];
}
for (const int var : ::gtl::reversed_view(tmp_path_)) {
const int old_parent = representative_[var];
offset_[var] += coeff_[var] * offset_[old_parent];
coeff_[var] *= coeff_[old_parent];
representative_[var] = parent;
}
}
int num_relations_;
// The equivalence class representative for each variable index.
mutable std::vector<int> representative_;
// The offset and coefficient such that
// variable[index] = coeff * variable[representative_[index]] + offset;
mutable std::vector<int64_t> coeff_;
mutable std::vector<int64_t> offset_;
// The size of each representative "tree", used to get a good complexity when
// we have the choice of which tree to merge into the other.
//
// TODO(user): Using a "rank" might be faster, but because we sometimes
// need to merge the bad subtree into the better one, it is trickier to
// maintain than in the classic union-find algorithm.
std::vector<int> size_;
// Used by CompressPath() to maintain the coeff/offset during compression.
mutable std::vector<int> tmp_path_;
};
inline bool AffineRelation::TryAdd(int x, int y, int64_t coeff,
int64_t offset) {
return TryAdd(x, y, coeff, offset, true, true);
}
inline bool AffineRelation::TryAdd(int x, int y, int64_t coeff, int64_t offset,
bool allow_rep_x, bool allow_rep_y) {
CHECK_NE(coeff, 0);
CHECK_NE(x, y);
CHECK_GE(x, 0);
CHECK_GE(y, 0);
IncreaseSizeOfMemberVectors(std::max(x, y) + 1);
CHECK_NE(size_[x], kSizeForRemovedEntry) << x;
CHECK_NE(size_[y], kSizeForRemovedEntry) << y;
CompressPath(x);
CompressPath(y);
const int rep_x = representative_[x];
const int rep_y = representative_[y];
if (rep_x == rep_y) return false;
// TODO(user): It should be possible to optimize this code block a bit, for
// instance depending on the magnitude of new_coeff vs coeff_x, we may already
// know that one of the two merge is not possible.
const int64_t coeff_x = coeff_[x];
const int64_t new_coeff = coeff * coeff_[y];
const int64_t new_offset = coeff * offset_[y] + offset - offset_[x];
const bool condition1 =
allow_rep_y && (new_coeff % coeff_x == 0) && (new_offset % coeff_x == 0);
const bool condition2 = allow_rep_x && (coeff_x % new_coeff == 0) &&
(new_offset % new_coeff == 0);
if (condition1 && (!condition2 || size_[x] <= size_[y])) {
representative_[rep_x] = rep_y;
size_[rep_y] += size_[rep_x];
coeff_[rep_x] = new_coeff / coeff_x;
offset_[rep_x] = new_offset / coeff_x;
} else if (condition2) {
representative_[rep_y] = rep_x;
size_[rep_x] += size_[rep_y];
coeff_[rep_y] = coeff_x / new_coeff;
offset_[rep_y] = -new_offset / new_coeff;
} else {
return false;
}
++num_relations_;
return true;
}
inline AffineRelation::Relation AffineRelation::Get(int x) const {
if (x >= representative_.size() || representative_[x] == x) return {x, 1, 0};
CompressPath(x);
return {representative_[x], coeff_[x], offset_[x]};
}
} // namespace operations_research
#endif // OR_TOOLS_UTIL_AFFINE_RELATION_H_