1 // Copyright 2010-2021 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 #include "ortools/sat/linear_constraint.h"
15
16 #include <cstdint>
17
18 #include "ortools/base/mathutil.h"
19 #include "ortools/base/strong_vector.h"
20 #include "ortools/sat/integer.h"
21
22 namespace operations_research {
23 namespace sat {
24
AddTerm(IntegerVariable var,IntegerValue coeff)25 void LinearConstraintBuilder::AddTerm(IntegerVariable var, IntegerValue coeff) {
26 if (coeff == 0) return;
27 // We can either add var or NegationOf(var), and we always choose the
28 // positive one.
29 if (VariableIsPositive(var)) {
30 terms_.push_back({var, coeff});
31 } else {
32 terms_.push_back({NegationOf(var), -coeff});
33 }
34 }
35
AddTerm(AffineExpression expr,IntegerValue coeff)36 void LinearConstraintBuilder::AddTerm(AffineExpression expr,
37 IntegerValue coeff) {
38 if (coeff == 0) return;
39 // We can either add var or NegationOf(var), and we always choose the
40 // positive one.
41 if (expr.var != kNoIntegerVariable) {
42 if (VariableIsPositive(expr.var)) {
43 terms_.push_back({expr.var, coeff * expr.coeff});
44 } else {
45 terms_.push_back({NegationOf(expr.var), -coeff * expr.coeff});
46 }
47 }
48 offset_ += coeff * expr.constant;
49 }
50
AddLinearExpression(const LinearExpression & expr)51 void LinearConstraintBuilder::AddLinearExpression(
52 const LinearExpression& expr) {
53 AddLinearExpression(expr, IntegerValue(1));
54 }
55
AddLinearExpression(const LinearExpression & expr,IntegerValue coeff)56 void LinearConstraintBuilder::AddLinearExpression(const LinearExpression& expr,
57 IntegerValue coeff) {
58 for (int i = 0; i < expr.vars.size(); ++i) {
59 // We must use positive variables.
60 if (VariableIsPositive(expr.vars[i])) {
61 terms_.push_back({expr.vars[i], expr.coeffs[i] * coeff});
62 } else {
63 terms_.push_back({NegationOf(expr.vars[i]), -expr.coeffs[i] * coeff});
64 }
65 }
66 offset_ += expr.offset * coeff;
67 }
68
AddQuadraticLowerBound(AffineExpression left,AffineExpression right,IntegerTrail * integer_trail)69 void LinearConstraintBuilder::AddQuadraticLowerBound(
70 AffineExpression left, AffineExpression right,
71 IntegerTrail* integer_trail) {
72 if (integer_trail->IsFixed(left)) {
73 AddTerm(right, integer_trail->FixedValue(left));
74 } else if (integer_trail->IsFixed(right)) {
75 AddTerm(left, integer_trail->FixedValue(right));
76 } else {
77 const IntegerValue left_min = integer_trail->LowerBound(left);
78 const IntegerValue right_min = integer_trail->LowerBound(right);
79 AddTerm(left, right_min);
80 AddTerm(right, left_min);
81 // Substract the energy counted twice.
82 AddConstant(-left_min * right_min);
83 }
84 }
85
AddConstant(IntegerValue value)86 void LinearConstraintBuilder::AddConstant(IntegerValue value) {
87 offset_ += value;
88 }
89
AddLiteralTerm(Literal lit,IntegerValue coeff)90 ABSL_MUST_USE_RESULT bool LinearConstraintBuilder::AddLiteralTerm(
91 Literal lit, IntegerValue coeff) {
92 bool has_direct_view = encoder_.GetLiteralView(lit) != kNoIntegerVariable;
93 bool has_opposite_view =
94 encoder_.GetLiteralView(lit.Negated()) != kNoIntegerVariable;
95
96 // If a literal has both views, we want to always keep the same
97 // representative: the smallest IntegerVariable. Note that AddTerm() will
98 // also make sure to use the associated positive variable.
99 if (has_direct_view && has_opposite_view) {
100 if (encoder_.GetLiteralView(lit) <=
101 encoder_.GetLiteralView(lit.Negated())) {
102 has_opposite_view = false;
103 } else {
104 has_direct_view = false;
105 }
106 }
107 if (has_direct_view) {
108 AddTerm(encoder_.GetLiteralView(lit), coeff);
109 return true;
110 }
111 if (has_opposite_view) {
112 AddTerm(encoder_.GetLiteralView(lit.Negated()), -coeff);
113 offset_ += coeff;
114 return true;
115 }
116 return false;
117 }
118
Build()119 LinearConstraint LinearConstraintBuilder::Build() {
120 return BuildConstraint(lb_, ub_);
121 }
122
BuildConstraint(IntegerValue lb,IntegerValue ub)123 LinearConstraint LinearConstraintBuilder::BuildConstraint(IntegerValue lb,
124 IntegerValue ub) {
125 LinearConstraint result;
126 result.lb = lb > kMinIntegerValue ? lb - offset_ : lb;
127 result.ub = ub < kMaxIntegerValue ? ub - offset_ : ub;
128 CleanTermsAndFillConstraint(&terms_, &result);
129 return result;
130 }
131
BuildExpression()132 LinearExpression LinearConstraintBuilder::BuildExpression() {
133 LinearExpression result;
134 CleanTermsAndFillConstraint(&terms_, &result);
135 result.offset = offset_;
136 return result;
137 }
138
ComputeActivity(const LinearConstraint & constraint,const absl::StrongVector<IntegerVariable,double> & values)139 double ComputeActivity(
140 const LinearConstraint& constraint,
141 const absl::StrongVector<IntegerVariable, double>& values) {
142 double activity = 0;
143 for (int i = 0; i < constraint.vars.size(); ++i) {
144 const IntegerVariable var = constraint.vars[i];
145 const IntegerValue coeff = constraint.coeffs[i];
146 activity += coeff.value() * values[var];
147 }
148 return activity;
149 }
150
ComputeL2Norm(const LinearConstraint & constraint)151 double ComputeL2Norm(const LinearConstraint& constraint) {
152 double sum = 0.0;
153 for (const IntegerValue coeff : constraint.coeffs) {
154 sum += ToDouble(coeff) * ToDouble(coeff);
155 }
156 return std::sqrt(sum);
157 }
158
ComputeInfinityNorm(const LinearConstraint & constraint)159 IntegerValue ComputeInfinityNorm(const LinearConstraint& constraint) {
160 IntegerValue result(0);
161 for (const IntegerValue coeff : constraint.coeffs) {
162 result = std::max(result, IntTypeAbs(coeff));
163 }
164 return result;
165 }
166
ScalarProduct(const LinearConstraint & constraint1,const LinearConstraint & constraint2)167 double ScalarProduct(const LinearConstraint& constraint1,
168 const LinearConstraint& constraint2) {
169 DCHECK(std::is_sorted(constraint1.vars.begin(), constraint1.vars.end()));
170 DCHECK(std::is_sorted(constraint2.vars.begin(), constraint2.vars.end()));
171 double scalar_product = 0.0;
172 int index_1 = 0;
173 int index_2 = 0;
174 while (index_1 < constraint1.vars.size() &&
175 index_2 < constraint2.vars.size()) {
176 if (constraint1.vars[index_1] == constraint2.vars[index_2]) {
177 scalar_product += ToDouble(constraint1.coeffs[index_1]) *
178 ToDouble(constraint2.coeffs[index_2]);
179 index_1++;
180 index_2++;
181 } else if (constraint1.vars[index_1] > constraint2.vars[index_2]) {
182 index_2++;
183 } else {
184 index_1++;
185 }
186 }
187 return scalar_product;
188 }
189
190 namespace {
191
192 // TODO(user): Template for any integer type and expose this?
ComputeGcd(const std::vector<IntegerValue> & values)193 IntegerValue ComputeGcd(const std::vector<IntegerValue>& values) {
194 if (values.empty()) return IntegerValue(1);
195 int64_t gcd = 0;
196 for (const IntegerValue value : values) {
197 gcd = MathUtil::GCD64(gcd, std::abs(value.value()));
198 if (gcd == 1) break;
199 }
200 if (gcd < 0) return IntegerValue(1); // Can happen with kint64min.
201 return IntegerValue(gcd);
202 }
203
204 } // namespace
205
DivideByGCD(LinearConstraint * constraint)206 void DivideByGCD(LinearConstraint* constraint) {
207 if (constraint->coeffs.empty()) return;
208 const IntegerValue gcd = ComputeGcd(constraint->coeffs);
209 if (gcd == 1) return;
210
211 if (constraint->lb > kMinIntegerValue) {
212 constraint->lb = CeilRatio(constraint->lb, gcd);
213 }
214 if (constraint->ub < kMaxIntegerValue) {
215 constraint->ub = FloorRatio(constraint->ub, gcd);
216 }
217 for (IntegerValue& coeff : constraint->coeffs) coeff /= gcd;
218 }
219
RemoveZeroTerms(LinearConstraint * constraint)220 void RemoveZeroTerms(LinearConstraint* constraint) {
221 int new_size = 0;
222 const int size = constraint->vars.size();
223 for (int i = 0; i < size; ++i) {
224 if (constraint->coeffs[i] == 0) continue;
225 constraint->vars[new_size] = constraint->vars[i];
226 constraint->coeffs[new_size] = constraint->coeffs[i];
227 ++new_size;
228 }
229 constraint->vars.resize(new_size);
230 constraint->coeffs.resize(new_size);
231 }
232
MakeAllCoefficientsPositive(LinearConstraint * constraint)233 void MakeAllCoefficientsPositive(LinearConstraint* constraint) {
234 const int size = constraint->vars.size();
235 for (int i = 0; i < size; ++i) {
236 const IntegerValue coeff = constraint->coeffs[i];
237 if (coeff < 0) {
238 constraint->coeffs[i] = -coeff;
239 constraint->vars[i] = NegationOf(constraint->vars[i]);
240 }
241 }
242 }
243
MakeAllVariablesPositive(LinearConstraint * constraint)244 void MakeAllVariablesPositive(LinearConstraint* constraint) {
245 const int size = constraint->vars.size();
246 for (int i = 0; i < size; ++i) {
247 const IntegerVariable var = constraint->vars[i];
248 if (!VariableIsPositive(var)) {
249 constraint->coeffs[i] = -constraint->coeffs[i];
250 constraint->vars[i] = NegationOf(var);
251 }
252 }
253 }
254
LpValue(const absl::StrongVector<IntegerVariable,double> & lp_values) const255 double LinearExpression::LpValue(
256 const absl::StrongVector<IntegerVariable, double>& lp_values) const {
257 double result = ToDouble(offset);
258 for (int i = 0; i < vars.size(); ++i) {
259 result += ToDouble(coeffs[i]) * lp_values[vars[i]];
260 }
261 return result;
262 }
263
DebugString() const264 std::string LinearExpression::DebugString() const {
265 std::string result;
266 for (int i = 0; i < vars.size(); ++i) {
267 absl::StrAppend(&result, i > 0 ? " " : "",
268 IntegerTermDebugString(vars[i], coeffs[i]));
269 }
270 if (offset != 0) {
271 absl::StrAppend(&result, " + ", offset.value());
272 }
273 return result;
274 }
275
276 // TODO(user): it would be better if LinearConstraint natively supported
277 // term and not two separated vectors. Fix?
278 //
279 // TODO(user): This is really similar to CleanTermsAndFillConstraint(), maybe
280 // we should just make the later switch negative variable to positive ones to
281 // avoid an extra linear scan on each new cuts.
CanonicalizeConstraint(LinearConstraint * ct)282 void CanonicalizeConstraint(LinearConstraint* ct) {
283 std::vector<std::pair<IntegerVariable, IntegerValue>> terms;
284
285 const int size = ct->vars.size();
286 for (int i = 0; i < size; ++i) {
287 if (VariableIsPositive(ct->vars[i])) {
288 terms.push_back({ct->vars[i], ct->coeffs[i]});
289 } else {
290 terms.push_back({NegationOf(ct->vars[i]), -ct->coeffs[i]});
291 }
292 }
293 std::sort(terms.begin(), terms.end());
294
295 ct->vars.clear();
296 ct->coeffs.clear();
297 for (const auto& term : terms) {
298 ct->vars.push_back(term.first);
299 ct->coeffs.push_back(term.second);
300 }
301 }
302
NoDuplicateVariable(const LinearConstraint & ct)303 bool NoDuplicateVariable(const LinearConstraint& ct) {
304 absl::flat_hash_set<IntegerVariable> seen_variables;
305 const int size = ct.vars.size();
306 for (int i = 0; i < size; ++i) {
307 if (VariableIsPositive(ct.vars[i])) {
308 if (!seen_variables.insert(ct.vars[i]).second) return false;
309 } else {
310 if (!seen_variables.insert(NegationOf(ct.vars[i])).second) return false;
311 }
312 }
313 return true;
314 }
315
CanonicalizeExpr(const LinearExpression & expr)316 LinearExpression CanonicalizeExpr(const LinearExpression& expr) {
317 LinearExpression canonical_expr;
318 canonical_expr.offset = expr.offset;
319 for (int i = 0; i < expr.vars.size(); ++i) {
320 if (expr.coeffs[i] < 0) {
321 canonical_expr.vars.push_back(NegationOf(expr.vars[i]));
322 canonical_expr.coeffs.push_back(-expr.coeffs[i]);
323 } else {
324 canonical_expr.vars.push_back(expr.vars[i]);
325 canonical_expr.coeffs.push_back(expr.coeffs[i]);
326 }
327 }
328 return canonical_expr;
329 }
330
LinExprLowerBound(const LinearExpression & expr,const IntegerTrail & integer_trail)331 IntegerValue LinExprLowerBound(const LinearExpression& expr,
332 const IntegerTrail& integer_trail) {
333 IntegerValue lower_bound = expr.offset;
334 for (int i = 0; i < expr.vars.size(); ++i) {
335 DCHECK_GE(expr.coeffs[i], 0) << "The expression is not canonicalized";
336 lower_bound += expr.coeffs[i] * integer_trail.LowerBound(expr.vars[i]);
337 }
338 return lower_bound;
339 }
340
LinExprUpperBound(const LinearExpression & expr,const IntegerTrail & integer_trail)341 IntegerValue LinExprUpperBound(const LinearExpression& expr,
342 const IntegerTrail& integer_trail) {
343 IntegerValue upper_bound = expr.offset;
344 for (int i = 0; i < expr.vars.size(); ++i) {
345 DCHECK_GE(expr.coeffs[i], 0) << "The expression is not canonicalized";
346 upper_bound += expr.coeffs[i] * integer_trail.UpperBound(expr.vars[i]);
347 }
348 return upper_bound;
349 }
350
351 // TODO(user): Avoid duplication with PossibleIntegerOverflow() in the checker?
352 // At least make sure the code is the same.
ValidateLinearConstraintForOverflow(const LinearConstraint & constraint,const IntegerTrail & integer_trail)353 bool ValidateLinearConstraintForOverflow(const LinearConstraint& constraint,
354 const IntegerTrail& integer_trail) {
355 int64_t positive_sum(0);
356 int64_t negative_sum(0);
357 for (int i = 0; i < constraint.vars.size(); ++i) {
358 const IntegerVariable var = constraint.vars[i];
359 const IntegerValue coeff = constraint.coeffs[i];
360 const IntegerValue lb = integer_trail.LevelZeroLowerBound(var);
361 const IntegerValue ub = integer_trail.LevelZeroUpperBound(var);
362
363 int64_t min_prod = CapProd(coeff.value(), lb.value());
364 int64_t max_prod = CapProd(coeff.value(), ub.value());
365 if (min_prod > max_prod) std::swap(min_prod, max_prod);
366
367 positive_sum = CapAdd(positive_sum, std::max(int64_t{0}, max_prod));
368 negative_sum = CapAdd(negative_sum, std::min(int64_t{0}, min_prod));
369 }
370
371 const int64_t limit = std::numeric_limits<int64_t>::max();
372 if (positive_sum >= limit) return false;
373 if (negative_sum <= -limit) return false;
374 if (CapSub(positive_sum, negative_sum) >= limit) return false;
375
376 return true;
377 }
378
NegationOf(const LinearExpression & expr)379 LinearExpression NegationOf(const LinearExpression& expr) {
380 LinearExpression result;
381 result.vars = NegationOf(expr.vars);
382 result.coeffs = expr.coeffs;
383 result.offset = -expr.offset;
384 return result;
385 }
386
PositiveVarExpr(const LinearExpression & expr)387 LinearExpression PositiveVarExpr(const LinearExpression& expr) {
388 LinearExpression result;
389 result.offset = expr.offset;
390 for (int i = 0; i < expr.vars.size(); ++i) {
391 if (VariableIsPositive(expr.vars[i])) {
392 result.vars.push_back(expr.vars[i]);
393 result.coeffs.push_back(expr.coeffs[i]);
394 } else {
395 result.vars.push_back(NegationOf(expr.vars[i]));
396 result.coeffs.push_back(-expr.coeffs[i]);
397 }
398 }
399 return result;
400 }
401
GetCoefficient(const IntegerVariable var,const LinearExpression & expr)402 IntegerValue GetCoefficient(const IntegerVariable var,
403 const LinearExpression& expr) {
404 for (int i = 0; i < expr.vars.size(); ++i) {
405 if (expr.vars[i] == var) {
406 return expr.coeffs[i];
407 } else if (expr.vars[i] == NegationOf(var)) {
408 return -expr.coeffs[i];
409 }
410 }
411 return IntegerValue(0);
412 }
413
GetCoefficientOfPositiveVar(const IntegerVariable var,const LinearExpression & expr)414 IntegerValue GetCoefficientOfPositiveVar(const IntegerVariable var,
415 const LinearExpression& expr) {
416 CHECK(VariableIsPositive(var));
417 for (int i = 0; i < expr.vars.size(); ++i) {
418 if (expr.vars[i] == var) {
419 return expr.coeffs[i];
420 }
421 }
422 return IntegerValue(0);
423 }
424
425 } // namespace sat
426 } // namespace operations_research
427