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