Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
linear_model.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 <algorithm>
17#include <cstdint>
18#include <utility>
19#include <vector>
20
21#include "absl/container/flat_hash_map.h"
22#include "absl/container/flat_hash_set.h"
23#include "absl/log/check.h"
25#include "ortools/sat/cp_model.pb.h"
28
29namespace operations_research {
30namespace sat {
31
32namespace {
33
34// This struct stores constraints of the form literal => var ==/!= value.
35// It is meant to be in a sorted vector to detect complimentary equations.
36struct EqualityDetectionHelper {
39 int64_t value;
40 bool is_equality; // false if != instead.
41
42 bool operator<(const EqualityDetectionHelper& o) const {
43 if (PositiveRef(literal) == PositiveRef(o.literal)) {
44 if (value == o.value) return is_equality && !o.is_equality;
45 return value < o.value;
46 }
47 return PositiveRef(literal) < PositiveRef(o.literal);
48 }
49};
50
51// For a given variable. This struct stores the literal that encodes a value, as
52// well as the indices of the two constraints in the model that implement
53// literal <=> var == value.
54struct LitVarEncodingInfo {
55 int lit;
58};
59
60// Struct use to store literal/value attached to a var. It is meant to be sorted
61// by ascending value order.
62struct ValueLiteralCtIndex {
63 int64_t value;
64 int literal;
66
67 bool operator<(const ValueLiteralCtIndex& o) const {
68 return (value < o.value) || (value == o.value && literal < o.literal);
69 }
70};
71
72// All var == value (stored in model_proto_.constraints(ct_index)) for a given
73// literal.
74struct VarValueCtIndex {
75 int var;
76 int64_t value;
77 int ct_index;
78};
79
80} // namespace
81
82LinearModel::LinearModel(const CpModelProto& model_proto)
83 : model_proto_(model_proto),
84 ignored_constraints_(model_proto.constraints_size(), false) {
85 // TODO(user): Do we use the loader code to detect full encodings and
86 // element encodings.
87 absl::flat_hash_set<BoolArgumentProto> exactly_ones_cache;
88 absl::flat_hash_set<LinearConstraintProto> encoding_cache;
89 std::vector<std::vector<EqualityDetectionHelper>> var_to_equalities(
90 model_proto_.variables_size());
91 absl::flat_hash_map<int, std::vector<VarValueCtIndex>>
92 literal_to_var_value_ct_indices;
93
94 // Loop over all constraints and fill var_to_equalities.
95 for (int c = 0; c < model_proto_.constraints_size(); ++c) {
96 const ConstraintProto& ct = model_proto_.constraints(c);
97 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kExactlyOne) {
98 BoolArgumentProto bool_arg = ct.exactly_one();
99 // Sort literals to get a canonical constraint.
100 std::sort(bool_arg.mutable_literals()->begin(),
101 bool_arg.mutable_literals()->end());
102 if (!exactly_ones_cache.insert(bool_arg).second) {
103 ignored_constraints_[c] = true;
104 ++num_ignored_constraints_;
105 }
106 }
107 if (ct.constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
108 continue;
109 }
110 if (ct.enforcement_literal().size() != 1) continue;
111 if (ct.linear().vars_size() != 1) continue;
112
113 // ct is a linear constraint with one term and one enforcement literal.
114 const int enforcement_literal = ct.enforcement_literal(0);
115 const int ref = ct.linear().vars(0);
116 const int var = PositiveRef(ref);
117
118 const Domain domain = ReadDomainFromProto(model_proto_.variables(var));
119 const Domain domain_if_enforced =
120 ReadDomainFromProto(ct.linear())
121 .InverseMultiplicationBy(ct.linear().coeffs(0) *
122 (RefIsPositive(ref) ? 1 : -1));
123
124 // Detect enforcement_literal => (var == value or var != value).
125 //
126 // Note that for domain with 2 values like [0, 1], we will detect both ==
127 // 0 and != 1. Similarly, for a domain in [min, max], we should both
128 // detect (== min) and (<= min), and both detect (== max) and (>= max).
129 {
130 const Domain inter = domain.IntersectionWith(domain_if_enforced);
131 if (!inter.IsEmpty() && inter.IsFixed()) {
132 var_to_equalities[var].push_back(
133 {c, enforcement_literal, inter.FixedValue(), true});
134 literal_to_var_value_ct_indices[enforcement_literal].push_back(
135 {var, inter.FixedValue(), c});
136 }
137 }
138 {
139 const Domain inter =
140 domain.IntersectionWith(domain_if_enforced.Complement());
141 if (!inter.IsEmpty() && inter.Min() == inter.Max()) {
142 var_to_equalities[var].push_back(
143 {c, enforcement_literal, inter.FixedValue(), false});
144 }
145 }
146 }
147
148 // Detect Literal <=> X == value and rebuild full encodings.
149 absl::flat_hash_map<int64_t, LitVarEncodingInfo> value_encodings;
150 std::vector<std::pair<int64_t, int>> value_literal_pairs;
151
152 for (int var = 0; var < var_to_equalities.size(); ++var) {
153 std::vector<EqualityDetectionHelper>& encoding = var_to_equalities[var];
154 if (encoding.empty()) continue;
155
156 std::sort(encoding.begin(), encoding.end());
157 const Domain domain = ReadDomainFromProto(model_proto_.variables(var));
158 value_encodings.clear();
159
160 for (int j = 0; j + 1 < encoding.size(); j++) {
161 if ((encoding[j].value != encoding[j + 1].value) ||
162 (encoding[j].literal != NegatedRef(encoding[j + 1].literal)) ||
163 (encoding[j].is_equality != true) ||
164 (encoding[j + 1].is_equality != false)) {
165 continue;
166 }
167
168 // TODO(user): Deal with/Check double insertion.
169 value_encodings[encoding[j].value] = {encoding[j].literal,
170 encoding[j].constraint_index,
171 encoding[j + 1].constraint_index};
172 }
173 if (value_encodings.size() == domain.Size()) {
174 value_literal_pairs.clear();
175 for (const auto& [value, value_encoding] : value_encodings) {
176 ignored_constraints_[value_encoding.positive_ct_index] = true;
177 ignored_constraints_[value_encoding.negative_ct_index] = true;
178 num_ignored_constraints_ += 2;
179 value_literal_pairs.push_back({value, value_encoding.lit});
180 }
181 // We sort to have a canonical representation with increasing values
182 // and sorted literals.
183 std::sort(value_literal_pairs.begin(), value_literal_pairs.end());
184
185 ConstraintProto var_encoding;
186 ConstraintProto exactly_one;
187 int64_t offset = 0;
188 int64_t min_value = 0;
189 var_encoding.mutable_linear()->add_vars(var);
190 var_encoding.mutable_linear()->add_coeffs(-1);
191 bool first_iteration = true;
192 for (const auto [value, lit] : value_literal_pairs) {
193 // Fill exactly one.
194 exactly_one.mutable_exactly_one()->add_literals(lit);
195
196 // Fill linear encoding.
197 if (first_iteration) { // First iteration.
198 // We shift the var = sum(lit * value) by the min value.
199 offset = value;
200 min_value = value;
201 first_iteration = false;
202 } else {
203 const int64_t delta = value - min_value;
204 DCHECK_GT(delta, 0); // Full encoding: all variables are different.
205 if (RefIsPositive(lit)) {
206 var_encoding.mutable_linear()->add_vars(lit);
207 var_encoding.mutable_linear()->add_coeffs(delta);
208 } else {
209 var_encoding.mutable_linear()->add_vars(PositiveRef(lit));
210 var_encoding.mutable_linear()->add_coeffs(-delta);
211 offset += delta;
212 }
213 }
214 }
215 var_encoding.mutable_linear()->add_domain(-offset);
216 var_encoding.mutable_linear()->add_domain(-offset);
217 if (encoding_cache.insert(var_encoding.linear()).second) {
218 additional_constraints_.push_back(var_encoding);
219 num_full_encodings_++;
220 }
221
222 // Add exactly_one constraint if new.
223 std::sort(exactly_one.mutable_exactly_one()->mutable_literals()->begin(),
224 exactly_one.mutable_exactly_one()->mutable_literals()->end());
225 if (exactly_ones_cache.insert(exactly_one.exactly_one()).second) {
226 additional_constraints_.push_back(exactly_one);
227 num_exactly_ones_++;
228 }
229 }
230 }
231
232 VLOG(1) << "Linear model created:";
233 VLOG(1) << " #model constraints: " << model_proto_.constraints_size();
234 VLOG(1) << " #full encodings detected: " << num_full_encodings_;
235 VLOG(1) << " #exactly_ones added: " << num_exactly_ones_;
236 VLOG(1) << " #constraints ignored: " << num_ignored_constraints_;
237}
238
239} // namespace sat
240} // namespace operations_research
Domain IntersectionWith(const Domain &domain) const
Domain InverseMultiplicationBy(int64_t coeff) const
LinearModel(const CpModelProto &model_proto)
const Constraint * ct
int64_t value
IntVar * var
bool is_equality
int lit
int positive_ct_index
int constraint_index
int negative_ct_index
int literal
int ct_index
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
In SWIG mode, we don't want anything besides these top-level includes.
int64_t delta
Definition resource.cc:1709