Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
presolve.cc
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
15
16#include <cstdint>
17#include <map>
18#include <set>
19#include <string>
20#include <vector>
21
22#include "absl/container/flat_hash_set.h"
23#include "absl/flags/flag.h"
24#include "absl/log/check.h"
25#include "absl/strings/string_view.h"
26#include "absl/types/span.h"
29
30ABSL_FLAG(bool, fz_floats_are_ints, false,
31 "Interpret floats as integers in all variables and constraints.");
32
33namespace operations_research {
34namespace fz {
35namespace {
36enum PresolveState { ALWAYS_FALSE, ALWAYS_TRUE, UNDECIDED };
37
38template <class T>
39bool IsArrayBoolean(const std::vector<T>& values) {
40 for (int i = 0; i < values.size(); ++i) {
41 if (values[i] != 0 && values[i] != 1) {
42 return false;
43 }
44 }
45 return true;
46}
47
48template <class T>
49bool AtMostOne0OrAtMostOne1(const std::vector<T>& values) {
50 CHECK(IsArrayBoolean(values));
51 int num_zero = 0;
52 int num_one = 0;
53 for (T val : values) {
54 if (val) {
55 num_one++;
56 } else {
57 num_zero++;
58 }
59 if (num_one > 1 && num_zero > 1) {
60 return false;
61 }
62 }
63 return true;
64}
65
66template <class T>
67void AppendIfNotInSet(T* value, absl::flat_hash_set<T*>* s,
68 std::vector<T*>* vec) {
69 if (s->insert(value).second) {
70 vec->push_back(value);
71 }
72 DCHECK_EQ(s->size(), vec->size());
73}
74
75} // namespace
76
77// Note on documentation
78//
79// In order to document presolve rules, we will use the following naming
80// convention:
81// - x, x1, xi, y, y1, yi denote integer variables
82// - b, b1, bi denote boolean variables
83// - c, c1, ci denote integer constants
84// - t, t1, ti denote boolean constants
85// - => x after a constraint denotes the target variable of this constraint.
86// Arguments are listed in order.
87
88// Propagates cast constraint.
89// Rule 1:
90// Input: bool2int(b, c) or bool2int(t, x)
91// Output: int_eq(...)
92//
93// Rule 2:
94// Input: bool2int(b, x)
95// Action: Replace all instances of x by b.
96// Output: inactive constraint
97void Presolver::PresolveBool2Int(Constraint* ct) {
98 DCHECK_EQ(ct->type, "bool2int");
99 if (ct->arguments[0].HasOneValue() || ct->arguments[1].HasOneValue()) {
100 // Rule 1.
101 UpdateRuleStats("bool2int: rename to int_eq");
102 ct->type = "int_eq";
103 } else {
104 // Rule 2.
105 UpdateRuleStats("bool2int: merge boolean and integer variables.");
106 AddVariableSubstitution(ct->arguments[1].Var(), ct->arguments[0].Var());
107 ct->MarkAsInactive();
108 }
109}
110
111// Propagates cast constraint.
112// Rule 1:
113// Input: int2float(x, y)
114// Action: Replace all instances of y by x.
115// Output: inactive constraint
116void Presolver::PresolveInt2Float(Constraint* ct) {
117 DCHECK_EQ(ct->type, "int2float");
118 // Rule 1.
119 UpdateRuleStats("int2float: merge integer and floating point variables.");
120 AddVariableSubstitution(ct->arguments[1].Var(), ct->arguments[0].Var());
121 ct->MarkAsInactive();
122}
123
124// Minizinc flattens 2d element constraints (x = A[y][z]) into 1d element
125// constraint with an affine mapping between y, z and the new index.
126// This rule stores the mapping to reconstruct the 2d element constraint.
127// This mapping can involve 1 or 2 variables depending if y or z in A[y][z]
128// is a constant in the model).
129void Presolver::PresolveStoreAffineMapping(Constraint* ct) {
130 CHECK_EQ(2, ct->arguments[1].variables.size());
131 Variable* const var0 = ct->arguments[1].variables[0];
132 Variable* const var1 = ct->arguments[1].variables[1];
133 const int64_t coeff0 = ct->arguments[0].values[0];
134 const int64_t coeff1 = ct->arguments[0].values[1];
135 const int64_t rhs = ct->arguments[2].Value();
136 if (coeff0 == -1 && !affine_map_.contains(var0)) {
137 affine_map_[var0] = AffineMapping(var1, coeff0, -rhs, ct);
138 UpdateRuleStats("int_lin_eq: store affine mapping");
139 } else if (coeff1 == -1 && !affine_map_.contains(var1)) {
140 affine_map_[var1] = AffineMapping(var0, coeff0, -rhs, ct);
141 UpdateRuleStats("int_lin_eq: store affine mapping");
142 }
143}
144
145void Presolver::PresolveStoreFlatteningMapping(Constraint* ct) {
146 CHECK_EQ(3, ct->arguments[1].variables.size());
147 Variable* const var0 = ct->arguments[1].variables[0];
148 Variable* const var1 = ct->arguments[1].variables[1];
149 Variable* const var2 = ct->arguments[1].variables[2];
150 const int64_t coeff0 = ct->arguments[0].values[0];
151 const int64_t coeff1 = ct->arguments[0].values[1];
152 const int64_t coeff2 = ct->arguments[0].values[2];
153 const int64_t rhs = ct->arguments[2].Value();
154 if (coeff0 == -1 && coeff2 == 1 && !array2d_index_map_.contains(var0)) {
155 array2d_index_map_[var0] =
156 Array2DIndexMapping(var1, coeff1, var2, -rhs, ct);
157 UpdateRuleStats("int_lin_eq: store 2d flattening mapping");
158 } else if (coeff0 == -1 && coeff1 == 1 &&
159 !array2d_index_map_.contains(var0)) {
160 array2d_index_map_[var0] =
161 Array2DIndexMapping(var2, coeff2, var1, -rhs, ct);
162 UpdateRuleStats("int_lin_eq: store 2d flattening mapping");
163 } else if (coeff2 == -1 && coeff1 == 1 &&
164 !array2d_index_map_.contains(var2)) {
165 array2d_index_map_[var2] =
166 Array2DIndexMapping(var0, coeff0, var1, -rhs, ct);
167 UpdateRuleStats("int_lin_eq: store 2d flattening mapping");
168 } else if (coeff2 == -1 && coeff0 == 1 &&
169 !array2d_index_map_.contains(var2)) {
170 array2d_index_map_[var2] =
171 Array2DIndexMapping(var1, coeff1, var0, -rhs, ct);
172 UpdateRuleStats("int_lin_eq: store 2d flattening mapping");
173 }
174}
175
176namespace {
177bool IsIncreasingAndContiguous(absl::Span<const int64_t> values) {
178 for (int i = 0; i < values.size() - 1; ++i) {
179 if (values[i + 1] != values[i] + 1) {
180 return false;
181 }
182 }
183 return true;
184}
185
186bool AreOnesFollowedByMinusOne(const std::vector<int64_t>& coeffs) {
187 CHECK(!coeffs.empty());
188 for (int i = 0; i < coeffs.size() - 1; ++i) {
189 if (coeffs[i] != 1) {
190 return false;
191 }
192 }
193 return coeffs.back() == -1;
194}
195
196template <class T>
197bool IsStrictPrefix(const std::vector<T>& v1, const std::vector<T>& v2) {
198 if (v1.size() >= v2.size()) {
199 return false;
200 }
201 for (int i = 0; i < v1.size(); ++i) {
202 if (v1[i] != v2[i]) {
203 return false;
204 }
205 }
206 return true;
207}
208} // namespace
209
210// Rewrite array element: array_int_element:
211//
212// Rule 1:
213// Input : array_int_element(x0, [c1, .., cn], y) with x0 = a * x + b
214// Output: array_int_element(x, [c_a1, .., c_am], b) with a * i = b = ai
215//
216// Rule 2:
217// Input : array_int_element(x, [c1, .., cn], y) with x = a * x1 + x2 + b
218// Output: array_int_element([x1, x2], [c_a1, .., c_am], b, [a, b])
219// to be interpreted by the extraction process.
220//
221// Rule 3:
222// Input: array_int_element(x, [c1, .., cn], y)
223// Output array_int_element(x, [c1, .., c{max(x)}], y)
224//
225// Rule 4:
226// Input : array_int_element(x, [c1, .., cn], y) with x0 ci = c0 + i
227// Output: int_lin_eq([-1, 1], [y, x], 1 - c) (e.g. y = x + c - 1)
228void Presolver::PresolveSimplifyElement(Constraint* ct) {
229 if (ct->arguments[0].variables.size() != 1) return;
230 Variable* const index_var = ct->arguments[0].Var();
231
232 // Rule 1.
233 if (affine_map_.contains(index_var)) {
234 const AffineMapping& mapping = affine_map_[index_var];
235 const Domain& domain = mapping.variable->domain;
236 if (domain.is_interval && domain.values.empty()) {
237 // Invalid case. Ignore it.
238 return;
239 }
240 if (domain.values[0] == 0 && mapping.coefficient == 1 &&
241 mapping.offset > 1 && index_var->domain.is_interval) {
242 // Simple translation
243 const int offset = mapping.offset - 1;
244 const int size = ct->arguments[1].values.size();
245 for (int i = 0; i < size - offset; ++i) {
246 ct->arguments[1].values[i] = ct->arguments[1].values[i + offset];
247 }
248 ct->arguments[1].values.resize(size - offset);
249 affine_map_[index_var].constraint->arguments[2].values[0] = -1;
250 affine_map_[index_var].offset = 1;
251 index_var->domain.values[0] -= offset;
252 index_var->domain.values[1] -= offset;
253 UpdateRuleStats("array_int_element: simplify using affine mapping.");
254 return;
255 } else if (mapping.offset + mapping.coefficient > 0 &&
256 domain.values[0] > 0) {
257 const std::vector<int64_t>& values = ct->arguments[1].values;
258 std::vector<int64_t> new_values;
259 for (int64_t i = 1; i <= domain.values.back(); ++i) {
260 const int64_t index = i * mapping.coefficient + mapping.offset - 1;
261 if (index < 0) {
262 return;
263 }
264 if (index > values.size()) {
265 break;
266 }
267 new_values.push_back(values[index]);
268 }
269 // Rewrite constraint.
270 UpdateRuleStats("array_int_element: simplify using affine mapping.");
271 ct->arguments[0].variables[0] = mapping.variable;
272 ct->arguments[0].variables[0]->domain.IntersectWithInterval(
273 1, new_values.size());
274 // TODO(user): Encapsulate argument setters.
275 ct->arguments[1].values.swap(new_values);
276 if (ct->arguments[1].values.size() == 1) {
277 ct->arguments[1].type = Argument::INT_VALUE;
278 }
279 // Reset propagate flag.
280 ct->presolve_propagation_done = false;
281 // Mark old index var and affine constraint as presolved out.
282 mapping.constraint->MarkAsInactive();
283 index_var->active = false;
284 return;
285 }
286 }
287
288 // Rule 2.
289 if (array2d_index_map_.contains(index_var)) {
290 UpdateRuleStats("array_int_element: rewrite as a 2d element");
291 const Array2DIndexMapping& mapping = array2d_index_map_[index_var];
292 // Rewrite constraint.
293 ct->arguments[0] =
294 Argument::VarRefArray({mapping.variable1, mapping.variable2});
295 std::vector<int64_t> coefs;
296 coefs.push_back(mapping.coefficient);
297 coefs.push_back(1);
298 ct->arguments.push_back(Argument::IntegerList(coefs));
299 ct->arguments.push_back(Argument::IntegerValue(mapping.offset));
300 index_var->active = false;
301 mapping.constraint->MarkAsInactive();
302 return;
303 }
304
305 // Rule 3.
306 if (index_var->domain.Max() < ct->arguments[1].values.size()) {
307 // Reduce array of values.
308 ct->arguments[1].values.resize(index_var->domain.Max());
309 ct->presolve_propagation_done = false;
310 UpdateRuleStats("array_int_element: reduce array");
311 return;
312 }
313
314 // Rule 4.
315 if (IsIncreasingAndContiguous(ct->arguments[1].values) &&
316 ct->arguments[2].type == Argument::VAR_REF) {
317 const int64_t start = ct->arguments[1].values.front();
318 Variable* const index = ct->arguments[0].Var();
319 Variable* const target = ct->arguments[2].Var();
320 UpdateRuleStats("array_int_element: rewrite as a linear constraint");
321
322 if (start == 1) {
323 ct->type = "int_eq";
324 ct->RemoveArg(1);
325 } else {
326 // Rewrite constraint into a int_lin_eq
327 ct->type = "int_lin_eq";
328 ct->arguments[0] = Argument::IntegerList({-1, 1});
329 ct->arguments[1] = Argument::VarRefArray({target, index});
330 ct->arguments[2] = Argument::IntegerValue(1 - start);
331 }
332 }
333}
334
335// Simplifies array_var_int_element
336//
337// Input : array_var_int_element(x0, [x1, .., xn], y) with x0 = a * x + b
338// Output: array_var_int_element(x, [x_a1, .., x_an], b) with a * i = b = ai
339void Presolver::PresolveSimplifyExprElement(Constraint* ct) {
340 if (ct->arguments[0].variables.size() != 1) return;
341
342 Variable* const index_var = ct->arguments[0].Var();
343 if (affine_map_.contains(index_var)) {
344 const AffineMapping& mapping = affine_map_[index_var];
345 const Domain& domain = mapping.variable->domain;
346 if ((domain.is_interval && domain.values.empty()) ||
347 domain.values[0] != 1 || mapping.offset + mapping.coefficient <= 0) {
348 // Invalid case. Ignore it.
349 return;
350 }
351 const std::vector<Variable*>& vars = ct->arguments[1].variables;
352 std::vector<Variable*> new_vars;
353 for (int64_t i = domain.values.front(); i <= domain.values.back(); ++i) {
354 const int64_t index = i * mapping.coefficient + mapping.offset - 1;
355 if (index < 0) {
356 return;
357 }
358 if (index >= vars.size()) {
359 break;
360 }
361 new_vars.push_back(vars[index]);
362 }
363 // Rewrite constraint.
364 UpdateRuleStats("array_var_int_element: simplify using affine mapping.");
365 ct->arguments[0].variables[0] = mapping.variable;
366 // TODO(user): Encapsulate argument setters.
367 ct->arguments[1].variables.swap(new_vars);
368 // Mark old index var and affine constraint as presolved out.
369 mapping.constraint->MarkAsInactive();
370 index_var->active = false;
371 } else if (index_var->domain.is_interval &&
372 index_var->domain.values.size() == 2 &&
373 index_var->domain.Max() < ct->arguments[1].variables.size()) {
374 // Reduce array of variables.
375 ct->arguments[1].variables.resize(index_var->domain.Max());
376 UpdateRuleStats("array_var_int_element: reduce array");
377 }
378}
379
381 // Should rewrite float constraints.
382 if (absl::GetFlag(FLAGS_fz_floats_are_ints)) {
383 // Treat float variables as int variables, convert constraints to int.
384 for (Constraint* const ct : model->constraints()) {
385 const std::string& id = ct->type;
386 if (id == "int2float") {
387 ct->type = "int_eq";
388 } else if (id == "float_lin_le") {
389 ct->type = "int_lin_le";
390 } else if (id == "float_lin_eq") {
391 ct->type = "int_lin_eq";
392 }
393 }
394 }
395
396 // Regroup increasing sequence of int_lin_eq([1,..,1,-1], [x1, ..., xn, yn])
397 // into sequence of int_plus(x1, x2, y2), int_plus(y2, x3, y3)...
398 std::vector<Variable*> current_variables;
399 Variable* target_variable = nullptr;
400 Constraint* first_constraint = nullptr;
401 for (Constraint* const ct : model->constraints()) {
402 if (target_variable == nullptr) {
403 if (ct->type == "int_lin_eq" && ct->arguments[0].values.size() == 3 &&
404 AreOnesFollowedByMinusOne(ct->arguments[0].values) &&
405 ct->arguments[1].values.empty() && ct->arguments[2].Value() == 0) {
406 current_variables = ct->arguments[1].variables;
407 target_variable = current_variables.back();
408 current_variables.pop_back();
409 first_constraint = ct;
410 }
411 } else {
412 if (ct->type == "int_lin_eq" &&
413 AreOnesFollowedByMinusOne(ct->arguments[0].values) &&
414 ct->arguments[0].values.size() == current_variables.size() + 2 &&
415 IsStrictPrefix(current_variables, ct->arguments[1].variables)) {
416 current_variables = ct->arguments[1].variables;
417 // Rewrite ct into int_plus.
418 ct->type = "int_plus";
419 ct->arguments.clear();
420 ct->arguments.push_back(Argument::VarRef(target_variable));
421 ct->arguments.push_back(
422 Argument::VarRef(current_variables[current_variables.size() - 2]));
423 ct->arguments.push_back(Argument::VarRef(current_variables.back()));
424 target_variable = current_variables.back();
425 current_variables.pop_back();
426
427 // We clean the first constraint too.
428 if (first_constraint != nullptr) {
429 first_constraint = nullptr;
430 }
431 } else {
432 current_variables.clear();
433 target_variable = nullptr;
434 }
435 }
436 }
437
438 // First pass.
439 for (Constraint* const ct : model->constraints()) {
440 if (ct->active && ct->type == "bool2int") {
441 PresolveBool2Int(ct);
442 } else if (ct->active && ct->type == "int2float") {
443 PresolveInt2Float(ct);
444 } else if (ct->active && ct->type == "int_lin_eq" &&
445 ct->arguments[1].variables.size() == 2 &&
446 ct->strong_propagation) {
447 PresolveStoreAffineMapping(ct);
448 } else if (ct->active && ct->type == "int_lin_eq" &&
449 ct->arguments[1].variables.size() == 3 &&
450 ct->strong_propagation) {
451 PresolveStoreFlatteningMapping(ct);
452 }
453 }
454 if (!var_representative_map_.empty()) {
455 // Some new substitutions were introduced. Let's process them.
456 SubstituteEverywhere(model);
457 var_representative_map_.clear();
458 var_representative_vector_.clear();
459 }
460
461 // Second pass.
462 for (Constraint* const ct : model->constraints()) {
463 if (ct->type == "array_int_element" || ct->type == "array_bool_element") {
464 PresolveSimplifyElement(ct);
465 }
466 if (ct->type == "array_var_int_element" ||
467 ct->type == "array_var_bool_element") {
468 PresolveSimplifyExprElement(ct);
469 }
470 }
471
472 // Third pass: process objective with floating point coefficients.
473 Variable* float_objective_var = nullptr;
474 for (Variable* var : model->variables()) {
475 if (!var->active) continue;
476 if (var->domain.is_float) {
477 CHECK(float_objective_var == nullptr);
478 float_objective_var = var;
479 }
480 }
481
482 Constraint* float_objective_ct = nullptr;
483 if (float_objective_var != nullptr) {
484 for (Constraint* ct : model->constraints()) {
485 if (!ct->active) continue;
486 if (ct->type == "float_lin_eq") {
487 CHECK(float_objective_ct == nullptr);
488 float_objective_ct = ct;
489 break;
490 }
491 }
492 }
493
494 if (float_objective_ct != nullptr || float_objective_var != nullptr) {
495 CHECK(float_objective_ct != nullptr);
496 CHECK(float_objective_var != nullptr);
497 const int arity = float_objective_ct->arguments[0].Size();
498 CHECK_EQ(float_objective_ct->arguments[1].variables[arity - 1],
499 float_objective_var);
500 CHECK_EQ(float_objective_ct->arguments[0].floats[arity - 1], -1.0);
501 for (int i = 0; i + 1 < arity; ++i) {
502 model->AddFloatingPointObjectiveTerm(
503 float_objective_ct->arguments[1].variables[i],
504 float_objective_ct->arguments[0].floats[i]);
505 }
506 model->SetFloatingPointObjectiveOffset(
507 -float_objective_ct->arguments[2].floats[0]);
508 model->ClearObjective();
509 float_objective_var->active = false;
510 float_objective_ct->active = false;
511 }
512
513 // Report presolve rules statistics.
514 if (!successful_rules_.empty()) {
515 for (const auto& rule : successful_rules_) {
516 if (rule.second == 1) {
517 SOLVER_LOG(logger_, " - rule '", rule.first, "' was applied 1 time");
518 } else {
519 SOLVER_LOG(logger_, " - rule '", rule.first, "' was applied ",
520 rule.second, " times");
521 }
522 }
523 }
524}
525
526// ----- Substitution support -----
527
528void Presolver::AddVariableSubstitution(Variable* from, Variable* to) {
529 CHECK(from != nullptr);
530 CHECK(to != nullptr);
531 // Apply the substitutions, if any.
532 from = FindRepresentativeOfVar(from);
533 to = FindRepresentativeOfVar(to);
534 if (to->temporary) {
535 // Let's switch to keep a non temporary as representative.
536 Variable* tmp = to;
537 to = from;
538 from = tmp;
539 }
540 if (from != to) {
541 CHECK(to->Merge(from->name, from->domain, from->temporary));
542 from->active = false;
543 var_representative_map_[from] = to;
544 var_representative_vector_.push_back(from);
545 }
546}
547
548Variable* Presolver::FindRepresentativeOfVar(Variable* var) {
549 if (var == nullptr) return nullptr;
550 Variable* start_var = var;
551 // First loop: find the top parent.
552 for (;;) {
553 const auto& it = var_representative_map_.find(var);
554 Variable* parent = it == var_representative_map_.end() ? var : it->second;
555 if (parent == var) break;
556 var = parent;
557 }
558 // Second loop: attach all the path to the top parent.
559 while (start_var != var) {
560 Variable* const parent = var_representative_map_[start_var];
561 var_representative_map_[start_var] = var;
562 start_var = parent;
563 }
564 const auto& iter = var_representative_map_.find(var);
565 return iter == var_representative_map_.end() ? var : iter->second;
566}
567
568void Presolver::SubstituteEverywhere(Model* model) {
569 // Rewrite the constraints.
570 for (Constraint* const ct : model->constraints()) {
571 if (ct != nullptr && ct->active) {
572 for (int i = 0; i < ct->arguments.size(); ++i) {
573 Argument& argument = ct->arguments[i];
574 switch (argument.type) {
577 for (int i = 0; i < argument.variables.size(); ++i) {
578 Variable* const old_var = argument.variables[i];
579 Variable* const new_var = FindRepresentativeOfVar(old_var);
580 if (new_var != old_var) {
581 argument.variables[i] = new_var;
582 }
583 }
584 break;
585 }
586 default: {
587 }
588 }
589 }
590 }
591 }
592 // Rewrite the search.
593 for (Annotation* const ann : model->mutable_search_annotations()) {
594 SubstituteAnnotation(ann);
595 }
596 // Rewrite the output.
597 for (SolutionOutputSpecs* const output : model->mutable_output()) {
598 output->variable = FindRepresentativeOfVar(output->variable);
599 for (int i = 0; i < output->flat_variables.size(); ++i) {
600 output->flat_variables[i] =
601 FindRepresentativeOfVar(output->flat_variables[i]);
602 }
603 }
604 // Do not forget to merge domain that could have evolved asynchronously
605 // during presolve.
606 for (const auto& iter : var_representative_map_) {
607 iter.second->domain.IntersectWithDomain(iter.first->domain);
608 }
609
610 // Change the objective variable.
611 Variable* const current_objective = model->objective();
612 if (current_objective == nullptr) return;
613 Variable* const new_objective = FindRepresentativeOfVar(current_objective);
614 if (new_objective != current_objective) {
615 model->SetObjective(new_objective);
616 }
617}
618
619void Presolver::SubstituteAnnotation(Annotation* ann) {
620 // TODO(user): Remove recursion.
621 switch (ann->type) {
624 for (int i = 0; i < ann->annotations.size(); ++i) {
625 SubstituteAnnotation(&ann->annotations[i]);
626 }
627 break;
628 }
631 for (int i = 0; i < ann->variables.size(); ++i) {
632 ann->variables[i] = FindRepresentativeOfVar(ann->variables[i]);
633 }
634 break;
635 }
636 default: {
637 }
638 }
639}
640
641} // namespace fz
642} // namespace operations_research
IntegerValue size
const Constraint * ct
int64_t value
IntVar * var
GRBmodel * model
int index
In SWIG mode, we don't want anything besides these top-level includes.
bool IsArrayBoolean(const std::vector< T > &values)
trees with all degrees equal to
ABSL_FLAG(bool, fz_floats_are_ints, false, "Interpret floats as integers in all variables and constraints.")
int64_t num_zero
int64_t start
static Argument IntegerValue(int64_t value)
--— Argument --—
Definition model.cc:497
static Argument IntegerList(std::vector< int64_t > values)
Definition model.cc:512
static Argument VarRef(Variable *var)
Definition model.cc:526
static Argument VarRefArray(std::vector< Variable * > vars)
Definition model.cc:533
std::vector< Argument > arguments
Definition model.h:240
#define SOLVER_LOG(logger,...)
Definition logging.h:109