Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
all_different.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_ALL_DIFFERENT_H_
15#define ORTOOLS_SAT_ALL_DIFFERENT_H_
16
17#include <cstdint>
18#include <functional>
19#include <utility>
20#include <vector>
21
22#include "absl/log/check.h"
23#include "absl/types/span.h"
27#include "ortools/sat/integer.h"
29#include "ortools/sat/model.h"
31#include "ortools/sat/util.h"
32#include "ortools/util/bitset.h"
34
35namespace operations_research {
36namespace sat {
37
38// Enforces that the given tuple of variables takes different values. This fully
39// encodes all the variables and simply enforces a <= 1 constraint on each
40// possible values.
41std::function<void(Model*)> AllDifferentBinary(
42 absl::Span<const IntegerVariable> vars);
43
44// Enforces that the given tuple of variables takes different values.
45// Same as AllDifferentBinary() but use a different propagator that only enforce
46// the so called "bound consistency" on the variable domains.
47//
48// Compared to AllDifferentBinary() this doesn't require fully encoding the
49// variables and it is also quite fast. Note that the propagation is different,
50// this will not remove already taken values from inside a domain, but it will
51// propagates more the domain bounds.
52std::function<void(Model*)> AllDifferentOnBounds(
53 absl::Span<const IntegerVariable> vars);
54std::function<void(Model*)> AllDifferentOnBounds(
55 absl::Span<const Literal> enforcement_literals,
56 absl::Span<const AffineExpression> expressions);
57
58// This constraint forces all variables to take different values. This is meant
59// to be used as a complement to an alldifferent decomposition like
60// AllDifferentBinary(): DO NOT USE WITHOUT ONE. Doing the filtering that the
61// decomposition can do with an appropriate algorithm should be cheaper and
62// yield more accurate explanations.
63//
64// It uses the matching algorithm described in Regin at AAAI1994:
65// "A filtering algorithm for constraints of difference in CSPs".
66//
67// This will fully encode variables.
68std::function<void(Model*)> AllDifferentAC(
69 absl::Span<const IntegerVariable> variables);
70
71// Implementation of AllDifferentAC().
73 public:
74 AllDifferentConstraint(absl::Span<const IntegerVariable> variables,
75 Model* model);
76
77 // In a circuit, the successor of all node must be "different".
78 // Thus this propagator can also be used in this context.
79 AllDifferentConstraint(int num_nodes, absl::Span<const int> tails,
80 absl::Span<const int> heads,
81 absl::Span<const Literal> literals, Model* model);
82
83 bool Propagate() final;
85
86 private:
87 // MakeAugmentingPath() is a step in Ford-Fulkerson's augmenting path
88 // algorithm. It changes its current internal state (see vectors below)
89 // to assign a value to the start vertex using an augmenting path.
90 // If it is not possible, it keeps variable_to_value_[start] to -1 and returns
91 // false, otherwise it modifies the current assignment and returns true.
92 // It uses value/variable_visited to mark the nodes it visits during its
93 // search: one can use this information to generate an explanation of failure,
94 // or manipulate it to create what-if scenarios without modifying successor_.
95 bool MakeAugmentingPath(int start);
96
97 // This caches all literals of the fully encoded variables.
98 // Values of a given variable are 0-indexed using offsets variable_min_value_,
99 // the set of all values is globally offset using offset min_all_values_.
100 // TODO(user): compare this encoding to a sparser hash_map encoding.
101 const int num_variables_;
102 const std::vector<IntegerVariable> variables_;
103
104 // Note that we remap all value into [0, num_values_) in a "dense" way.
105 std::vector<std::vector<std::pair<int, Literal>>>
106 variable_to_possible_values_;
107 int64_t num_values_;
108
109 // Internal state of MakeAugmentingPath().
110 // value_to_variable_ and variable_to_value_ represent the current assignment;
111 // -1 means not assigned. Otherwise,
112 // variable_to_value_[var] = value <=> value_to_variable_[value] = var.
113 CompactVectorVector<int> successor_;
114 std::vector<bool> value_visited_;
115 std::vector<bool> variable_visited_;
116 std::vector<int> value_to_variable_;
117 std::vector<int> variable_to_value_;
118 std::vector<int> prev_matching_;
119 std::vector<int> visiting_;
120 std::vector<int> variable_visited_from_;
121
122 // Internal state of ComputeSCCs().
123 // Variable nodes are indexed by [0, num_variables_),
124 // value nodes by [num_variables_, num_variables_ + num_all_values_),
125 // and a dummy node with index num_variables_ + num_all_values_ is added.
126 // The graph passed to ComputeSCCs() is the residual of the possible graph
127 // by the current matching, i.e. its arcs are:
128 // _ (var, val) if val \in dom(var) and var not matched to val,
129 // _ (val, var) if var matched to val,
130 // _ (val, dummy) if val not matched to any variable,
131 // _ (dummy, var) for all variables.
132 // In the original paper, forbidden arcs are identified by detecting that they
133 // are not in any alternating cycle or alternating path starting at a
134 // free vertex. Adding the dummy node allows to factor the alternating path
135 // part in the alternating cycle, and filter with only the SCC decomposition.
136 // When num_variables_ == num_all_values_, the dummy node is useless,
137 // we add it anyway to simplify the code.
138 CompactVectorVector<int> residual_graph_successors_;
139 std::vector<int> component_number_;
140
141 Trail* trail_;
142 IntegerTrail* integer_trail_;
143};
144
145// Implements the all different bound consistent propagator with explanation.
146// That is, given n affine expressions that must take different values, this
147// propagates the bounds of each expression as much as possible. The key is to
148// detect the so called Hall intervals which are intervals of size k that
149// contain the domain of k expressions. Because all the variables must take
150// different values, we can deduce that the domain of the other variables cannot
151// contain such Hall interval.
152//
153// We use a "fast" O(n log n) algorithm.
154//
155// TODO(user): It might be difficult to find something faster than what is
156// implemented here. Some related reference:
157// https://cs.uwaterloo.ca/~vanbeek/Publications/ijcai03_TR.pdf
159 public:
160 AllDifferentBoundsPropagator(absl::Span<const Literal> enforcement_literals,
161 absl::Span<const AffineExpression> expressions,
162 Model* model);
163
164 // This type is neither copyable nor movable.
167 delete;
168
169 bool Propagate() final;
170
171 private:
172 // We locally cache the lb/ub for faster sorting and to guarantee some
173 // invariant when we push bounds.
174 struct CachedBounds {
175 AffineExpression expr;
176 IntegerValue lb;
177 IntegerValue ub;
178 };
179
180 int RegisterWith(GenericLiteralWatcher* watcher);
181
182 // Fills integer_reason_ with the reason why we have the given hall interval.
183 void FillHallReason(IntegerValue hall_lb, IntegerValue hall_ub);
184
185 // Do half the job of Propagate(). This will split the variable into
186 // independent subset, and call PropagateLowerBoundsInternal() on each of
187 // them.
188 bool PropagateLowerBounds();
189 bool PropagateLowerBoundsInternal(IntegerValue min_lb,
190 absl::Span<CachedBounds> bounds);
191
192 // Internally, we will maintain a set of non-consecutive integer intervals of
193 // the form [start, end]. Each point (i.e. IntegerValue) of such interval will
194 // be associated to an unique input expression and via an union-find algorithm
195 // point to its start. The end only make sense for representative.
196 //
197 // TODO(user): Because we don't use rank, we have a worst case complexity of
198 // O(n log n). We could try a normal Union-find data structure, but then we
199 // also have to maintain a start vector.
200 //
201 // Note that during the execution of the algorithm we start from empty
202 // intervals and finish with a set of points of size num_vars.
203 //
204 // The list of all points are maintained in the dense vectors index_to_*_
205 // where we have remapped values to indices (with GetIndex()) to make sure it
206 // always fall into the correct range.
207 int FindStartIndexAndCompressPath(int index);
208
209 int GetIndex(IntegerValue value) const {
210 DCHECK_GE(value, base_);
211 DCHECK_LT(value - base_, index_to_start_index_.size());
212 return (value - base_).value();
213 }
214
215 IntegerValue GetValue(int index) const { return base_ + IntegerValue(index); }
216
217 const IntegerTrail& integer_trail_;
218 EnforcementHelper& enforcement_helper_;
219 EnforcementId enforcement_id_;
220
221 // These vector will be either sorted by lb or by -ub.
222 std::vector<CachedBounds> bounds_;
223 std::vector<CachedBounds> negated_bounds_;
224
225 // The list of Hall intervals detected so far, sorted.
226 std::vector<IntegerValue> hall_starts_;
227 std::vector<IntegerValue> hall_ends_;
228
229 // Non-consecutive intervals related data-structures.
230 IntegerValue base_;
231 std::vector<int> index_to_start_index_;
232 std::vector<int> index_to_end_index_;
233 SparseBitset<int> index_is_present_;
234 std::vector<AffineExpression> index_to_expr_;
235
236 // Temporary integer reason.
237 std::vector<IntegerLiteral> integer_reason_;
238};
239
240} // namespace sat
241} // namespace operations_research
242
243#endif // ORTOOLS_SAT_ALL_DIFFERENT_H_
AllDifferentBoundsPropagator(absl::Span< const Literal > enforcement_literals, absl::Span< const AffineExpression > expressions, Model *model)
AllDifferentBoundsPropagator(const AllDifferentBoundsPropagator &)=delete
AllDifferentBoundsPropagator & operator=(const AllDifferentBoundsPropagator &)=delete
AllDifferentConstraint(absl::Span< const IntegerVariable > variables, Model *model)
void RegisterWith(GenericLiteralWatcher *watcher)
std::function< void(Model *)> AllDifferentBinary(absl::Span< const IntegerVariable > vars)
std::function< void(Model *)> AllDifferentOnBounds(absl::Span< const Literal > enforcement_literals, absl::Span< const AffineExpression > expressions)
std::function< void(Model *)> AllDifferentAC(absl::Span< const IntegerVariable > variables)
OR-Tools root namespace.