Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
linear_constraint.h
Go to the documentation of this file.
1// Copyright 2010-2024 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_SAT_LINEAR_CONSTRAINT_H_
15#define OR_TOOLS_SAT_LINEAR_CONSTRAINT_H_
16
17#include <algorithm>
18#include <memory>
19#include <ostream>
20#include <string>
21#include <utility>
22#include <vector>
23
24#include "absl/base/attributes.h"
25#include "absl/strings/str_cat.h"
26#include "absl/types/span.h"
28#include "ortools/sat/integer.h"
29#include "ortools/sat/model.h"
32
33namespace operations_research {
34namespace sat {
35
36// One linear constraint on a set of Integer variables.
37// Important: there should be no duplicate variables.
38//
39// We also assume that we never have integer overflow when evaluating such
40// constraint at the ROOT node. This should be enforced by the checker for user
41// given constraints, and we must enforce it ourselves for the newly created
42// constraint. See ValidateLinearConstraintForOverflow().
44 IntegerValue lb;
45 IntegerValue ub;
46
47 // Rather than using two std::vector<> this class is optimized for memory
48 // consumption, given that most of our LinearConstraint are constructed once
49 // and for all.
50 //
51 // It is however up to clients to maintain the invariants that both vars
52 // and coeffs are properly allocated and of size num_terms.
53 //
54 // Also note that we did not add a copy constructor, to make sure that this is
55 // moved as often as possible. This allowed to optimize a few call site and so
56 // far we never copy this.
57 int num_terms = 0;
58 std::unique_ptr<IntegerVariable[]> vars;
59 std::unique_ptr<IntegerValue[]> coeffs;
60
61 LinearConstraint() = default;
62 LinearConstraint(IntegerValue _lb, IntegerValue _ub) : lb(_lb), ub(_ub) {}
63
64 // Resize the LinearConstraint to have space for num_terms. We always
65 // re-allocate if the size is different to always be tight in memory.
66 void resize(int size) {
67 if (size == num_terms) return;
68 IntegerVariable* tmp_vars = new IntegerVariable[size];
69 IntegerValue* tmp_coeffs = new IntegerValue[size];
70 const int to_copy = std::min(size, num_terms);
71 if (to_copy > 0) {
72 memcpy(tmp_vars, vars.get(), sizeof(IntegerVariable) * to_copy);
73 memcpy(tmp_coeffs, coeffs.get(), sizeof(IntegerValue) * to_copy);
74 }
76 vars.reset(tmp_vars);
77 coeffs.reset(tmp_coeffs);
78 }
79
80 std::string DebugString() const {
81 std::string result;
82 if (lb.value() > kMinIntegerValue) {
83 absl::StrAppend(&result, lb.value(), " <= ");
84 }
85 for (int i = 0; i < num_terms; ++i) {
86 absl::StrAppend(&result, i > 0 ? " " : "",
88 }
89 if (ub.value() < kMaxIntegerValue) {
90 absl::StrAppend(&result, " <= ", ub.value());
91 }
92 return result;
93 }
94
95 bool IsEqualIgnoringBounds(const LinearConstraint& other) const {
96 if (this->num_terms != other.num_terms) return false;
97 if (this->num_terms == 0) return true;
98 if (memcmp(this->vars.get(), other.vars.get(),
99 sizeof(IntegerVariable) * this->num_terms)) {
100 return false;
101 }
102 if (memcmp(this->coeffs.get(), other.coeffs.get(),
103 sizeof(IntegerValue) * this->num_terms)) {
104 return false;
105 }
106 return true;
107 }
108
109 bool operator==(const LinearConstraint& other) const {
110 if (this->lb != other.lb) return false;
111 if (this->ub != other.ub) return false;
112 return IsEqualIgnoringBounds(other);
113 }
114
115 absl::Span<const IntegerVariable> VarsAsSpan() const {
116 return absl::MakeSpan(vars.get(), num_terms);
117 }
118
119 absl::Span<const IntegerValue> CoeffsAsSpan() const {
120 return absl::MakeSpan(coeffs.get(), num_terms);
121 }
122};
123
124inline std::ostream& operator<<(std::ostream& os, const LinearConstraint& ct) {
125 os << ct.DebugString();
126 return os;
127}
128
129// Helper struct to model linear expression for lin_min/lin_max constraints. The
130// canonical expression should only contain positive coefficients.
132 std::vector<IntegerVariable> vars;
133 std::vector<IntegerValue> coeffs;
134 IntegerValue offset = IntegerValue(0);
135
136 // Return[s] the evaluation of the linear expression.
138 lp_values) const;
139
140 IntegerValue LevelZeroMin(IntegerTrail* integer_trail) const;
141
142 // Returns lower bound of linear expression using variable bounds of the
143 // variables in expression.
144 IntegerValue Min(const IntegerTrail& integer_trail) const;
145
146 // Returns upper bound of linear expression using variable bounds of the
147 // variables in expression.
148 IntegerValue Max(const IntegerTrail& integer_trail) const;
149
150 std::string DebugString() const;
151};
152
153// Returns the same expression in the canonical form (all positive
154// coefficients).
156
157// Makes sure that any of our future computation on this constraint will not
158// cause overflow. We use the level zero bounds and use the same definition as
159// in PossibleIntegerOverflow() in the cp_model.proto checker.
160//
161// Namely, the sum of positive terms, the sum of negative terms and their
162// difference shouldn't overflow. Note that we don't validate the rhs, but if
163// the bounds are properly relaxed, then this shouldn't cause any issues.
164//
165// Note(user): We should avoid doing this test too often as it can be slow. At
166// least do not do it more than once on each constraint.
168 const IntegerTrail& integer_trail);
169
170// Preserves canonicality.
172
173// Returns the same expression with positive variables.
175
176// Returns the coefficient of the variable in the expression. Works in linear
177// time.
178// Note: GetCoefficient(NegationOf(var, expr)) == -GetCoefficient(var, expr).
179IntegerValue GetCoefficient(IntegerVariable var, const LinearExpression& expr);
180IntegerValue GetCoefficientOfPositiveVar(IntegerVariable var,
181 const LinearExpression& expr);
182
183// Allow to build a LinearConstraint while making sure there is no duplicate
184// variables. Note that we do not simplify literal/variable that are currently
185// fixed here.
186//
187// All the functions manipulate a linear expression with an offset. The final
188// constraint bounds will include this offset.
189//
190// TODO(user): Rename to LinearExpressionBuilder?
192 public:
193 // We support "sticky" kMinIntegerValue for lb and kMaxIntegerValue for ub
194 // for one-sided constraints.
195 //
196 // Assumes that the 'model' has IntegerEncoder. The bounds can either be
197 // specified at construction or during the Build() call.
199 : encoder_(model->Get<IntegerEncoder>()), lb_(0), ub_(0) {}
201 : encoder_(encoder), lb_(0), ub_(0) {}
202 LinearConstraintBuilder(const Model* model, IntegerValue lb, IntegerValue ub)
203 : encoder_(model->Get<IntegerEncoder>()), lb_(lb), ub_(ub) {}
204 LinearConstraintBuilder(IntegerEncoder* encoder, IntegerValue lb,
205 IntegerValue ub)
206 : encoder_(encoder), lb_(lb), ub_(ub) {}
207
208 // Warning: this version without encoder cannot be used to add literals, so
209 // one shouldn't call AddLiteralTerm() on it. All other functions works.
210 //
211 // TODO(user): Have a subclass so we can enforce that a caller using
212 // AddLiteralTerm() must construct the Builder with an encoder.
213 LinearConstraintBuilder() : encoder_(nullptr), lb_(0), ub_(0) {}
214 LinearConstraintBuilder(IntegerValue lb, IntegerValue ub)
215 : encoder_(nullptr), lb_(lb), ub_(ub) {}
216
217 // Adds the corresponding term to the current linear expression.
218 void AddConstant(IntegerValue value);
219 void AddTerm(IntegerVariable var, IntegerValue coeff);
220 void AddTerm(AffineExpression expr, IntegerValue coeff);
221 void AddLinearExpression(const LinearExpression& expr);
222 void AddLinearExpression(const LinearExpression& expr, IntegerValue coeff);
223
224 // Add the corresponding decomposed products (obtained from
225 // TryToDecomposeProduct). The code assumes all literals to be in an
226 // exactly_one relation.
227 // It returns false if one literal does not have an integer view, as it
228 // actually calls AddLiteralTerm().
229 ABSL_MUST_USE_RESULT bool AddDecomposedProduct(
230 absl::Span<const LiteralValueValue> product);
231
232 // Add literal * coeff to the constaint. Returns false and do nothing if the
233 // given literal didn't have an integer view.
234 ABSL_MUST_USE_RESULT bool AddLiteralTerm(
235 Literal lit, IntegerValue coeff = IntegerValue(1));
236
237 // Add an under linearization of the product of two affine expressions.
238 // If at least one of them is fixed, then we add the exact product (which is
239 // linear). Otherwise, we use McCormick relaxation:
240 // left * right = (left_min + delta_left) * (right_min + delta_right) =
241 // left_min * right_min + delta_left * right_min +
242 // delta_right * left_min + delta_left * delta_right
243 // which is >= (by ignoring the quatratic term)
244 // right_min * left + left_min * right - right_min * left_min
245 //
246 // TODO(user): We could use (max - delta) instead of (min + delta) for each
247 // expression instead. This would depend on the LP value of the left and
248 // right.
250 IntegerTrail* integer_trail,
251 bool* is_quadratic = nullptr);
252
253 // Clears all added terms and constants. Keeps the original bounds.
254 void Clear() {
255 offset_ = IntegerValue(0);
256 terms_.clear();
257 }
258
259 // Reset the bounds passed at construction time.
260 void ResetBounds(IntegerValue lb, IntegerValue ub) {
261 lb_ = lb;
262 ub_ = ub;
263 }
264
265 // Builds and returns the corresponding constraint in a canonical form.
266 // All the IntegerVariable will be positive and appear in increasing index
267 // order.
268 //
269 // The bounds can be changed here or taken at construction.
270 //
271 // TODO(user): this doesn't invalidate the builder object, but if one wants
272 // to do a lot of dynamic editing to the constraint, then then underlying
273 // algorithm needs to be optimized for that.
275 LinearConstraint BuildConstraint(IntegerValue lb, IntegerValue ub);
276
277 // Returns the linear expression part of the constraint only, without the
278 // bounds.
280
281 private:
282 const IntegerEncoder* encoder_;
283 IntegerValue lb_;
284 IntegerValue ub_;
285
286 IntegerValue offset_ = IntegerValue(0);
287
288 // Initially we push all AddTerm() here, and during Build() we merge terms
289 // on the same variable.
290 std::vector<std::pair<IntegerVariable, IntegerValue>> terms_;
291};
292
293// Returns the activity of the given constraint. That is the current value of
294// the linear terms.
295double ComputeActivity(
296 const LinearConstraint& constraint,
298
299// Tests for possible overflow in the given linear constraint used for the
300// linear relaxation. This is a bit relaxed compared to what we require for
301// generic linear constraint that are used in our CP propagators.
302//
303// If this check pass, our constraint should be safe to use in our simplication
304// code, our cut computation, etc...
305bool PossibleOverflow(const IntegerTrail& integer_trail,
306 const LinearConstraint& constraint);
307
308// Returns sqrt(sum square(coeff)).
309double ComputeL2Norm(const LinearConstraint& constraint);
310
311// Returns the maximum absolute value of the coefficients.
312IntegerValue ComputeInfinityNorm(const LinearConstraint& constraint);
313
314// Returns the scalar product of given constraint coefficients. This method
315// assumes that the constraint variables are in sorted order.
316double ScalarProduct(const LinearConstraint& constraint1,
317 const LinearConstraint& constraint2);
318
319// Computes the GCD of the constraint coefficient, and divide them by it. This
320// also tighten the constraint bounds assumming all the variables are integer.
321void DivideByGCD(LinearConstraint* constraint);
322
323// Removes the entries with a coefficient of zero.
324void RemoveZeroTerms(LinearConstraint* constraint);
325
326// Makes all coefficients positive by transforming a variable to its negation.
327void MakeAllCoefficientsPositive(LinearConstraint* constraint);
328
329// Makes all variables "positive" by transforming a variable to its negation.
330void MakeAllVariablesPositive(LinearConstraint* constraint);
331
332// Returns false if duplicate variables are found in ct.
333bool NoDuplicateVariable(const LinearConstraint& ct);
334
335// Sorts and merges duplicate IntegerVariable in the given "terms".
336// Fills the given LinearConstraint or LinearExpression with the result.
338 std::vector<std::pair<IntegerVariable, IntegerValue>>* terms,
339 LinearExpression* output) {
340 output->vars.clear();
341 output->coeffs.clear();
342
343 // Sort and add coeff of duplicate variables. Note that a variable and
344 // its negation will appear one after another in the natural order.
345 std::sort(terms->begin(), terms->end());
346 IntegerVariable previous_var = kNoIntegerVariable;
347 IntegerValue current_coeff(0);
348 for (const std::pair<IntegerVariable, IntegerValue>& entry : *terms) {
349 if (previous_var == entry.first) {
350 current_coeff += entry.second;
351 } else if (previous_var == NegationOf(entry.first)) {
352 current_coeff -= entry.second;
353 } else {
354 if (current_coeff != 0) {
355 output->vars.push_back(previous_var);
356 output->coeffs.push_back(current_coeff);
357 }
358 previous_var = entry.first;
359 current_coeff = entry.second;
360 }
361 }
362 if (current_coeff != 0) {
363 output->vars.push_back(previous_var);
364 output->coeffs.push_back(current_coeff);
365 }
366}
367
369 std::vector<std::pair<IntegerVariable, IntegerValue>>* terms,
370 LinearConstraint* output) {
371 // Sort and add coeff of duplicate variables. Note that a variable and
372 // its negation will appear one after another in the natural order.
373 int new_size = 0;
374 output->resize(terms->size());
375 std::sort(terms->begin(), terms->end());
376 IntegerVariable previous_var = kNoIntegerVariable;
377 IntegerValue current_coeff(0);
378 for (const std::pair<IntegerVariable, IntegerValue>& entry : *terms) {
379 if (previous_var == entry.first) {
380 current_coeff += entry.second;
381 } else if (previous_var == NegationOf(entry.first)) {
382 current_coeff -= entry.second;
383 } else {
384 if (current_coeff != 0) {
385 output->vars[new_size] = previous_var;
386 output->coeffs[new_size] = current_coeff;
387 ++new_size;
388 }
389 previous_var = entry.first;
390 current_coeff = entry.second;
391 }
392 }
393 if (current_coeff != 0) {
394 output->vars[new_size] = previous_var;
395 output->coeffs[new_size] = current_coeff;
396 ++new_size;
397 }
398 output->resize(new_size);
399}
400
401} // namespace sat
402} // namespace operations_research
403
404#endif // OR_TOOLS_SAT_LINEAR_CONSTRAINT_H_
IntegerValue size
void AddQuadraticLowerBound(AffineExpression left, AffineExpression right, IntegerTrail *integer_trail, bool *is_quadratic=nullptr)
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff=IntegerValue(1))
void ResetBounds(IntegerValue lb, IntegerValue ub)
Reset the bounds passed at construction time.
LinearConstraintBuilder(IntegerEncoder *encoder, IntegerValue lb, IntegerValue ub)
void AddTerm(IntegerVariable var, IntegerValue coeff)
void AddLinearExpression(const LinearExpression &expr)
void AddConstant(IntegerValue value)
Adds the corresponding term to the current linear expression.
void Clear()
Clears all added terms and constants. Keeps the original bounds.
LinearConstraintBuilder(const Model *model, IntegerValue lb, IntegerValue ub)
LinearConstraint BuildConstraint(IntegerValue lb, IntegerValue ub)
LinearConstraintBuilder(IntegerValue lb, IntegerValue ub)
ABSL_MUST_USE_RESULT bool AddDecomposedProduct(absl::Span< const LiteralValueValue > product)
const Constraint * ct
int64_t value
IntVar * var
GRBmodel * model
int lit
double ComputeActivity(const LinearConstraint &constraint, const util_intops::StrongVector< IntegerVariable, double > &values)
void DivideByGCD(LinearConstraint *constraint)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
double ComputeL2Norm(const LinearConstraint &ct)
Returns sqrt(sum square(coeff)).
void RemoveZeroTerms(LinearConstraint *constraint)
Removes the entries with a coefficient of zero.
IntegerValue ComputeInfinityNorm(const LinearConstraint &ct)
Returns the maximum absolute value of the coefficients.
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Returns the vector of the negated variables.
Definition integer.cc:51
std::string IntegerTermDebugString(IntegerVariable var, IntegerValue coeff)
Definition integer.h:203
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
bool PossibleOverflow(const IntegerTrail &integer_trail, const LinearConstraint &constraint)
void CleanTermsAndFillConstraint(std::vector< std::pair< IntegerVariable, IntegerValue > > *terms, LinearExpression *output)
void MakeAllCoefficientsPositive(LinearConstraint *constraint)
Makes all coefficients positive by transforming a variable to its negation.
std::ostream & operator<<(std::ostream &os, const BoolVar &var)
Definition cp_model.cc:89
LinearExpression CanonicalizeExpr(const LinearExpression &expr)
LinearExpression PositiveVarExpr(const LinearExpression &expr)
Returns the same expression with positive variables.
IntegerValue GetCoefficient(const IntegerVariable var, const LinearExpression &expr)
void MakeAllVariablesPositive(LinearConstraint *constraint)
Makes all variables "positive" by transforming a variable to its negation.
IntegerValue GetCoefficientOfPositiveVar(const IntegerVariable var, const LinearExpression &expr)
bool ValidateLinearConstraintForOverflow(const LinearConstraint &constraint, const IntegerTrail &integer_trail)
bool NoDuplicateVariable(const LinearConstraint &ct)
Returns false if duplicate variables are found in ct.
double ScalarProduct(const LinearConstraint &ct1, const LinearConstraint &ct2)
In SWIG mode, we don't want anything besides these top-level includes.
std::unique_ptr< IntegerValue[]> coeffs
std::unique_ptr< IntegerVariable[]> vars
absl::Span< const IntegerValue > CoeffsAsSpan() const
absl::Span< const IntegerVariable > VarsAsSpan() const
bool IsEqualIgnoringBounds(const LinearConstraint &other) const
bool operator==(const LinearConstraint &other) const
LinearConstraint(IntegerValue _lb, IntegerValue _ub)
IntegerValue LevelZeroMin(IntegerTrail *integer_trail) const
IntegerValue Min(const IntegerTrail &integer_trail) const
double LpValue(const util_intops::StrongVector< IntegerVariable, double > &lp_values) const
Return[s] the evaluation of the linear expression.
IntegerValue Max(const IntegerTrail &integer_trail) const