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