Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
circuit.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 OR_TOOLS_SAT_CIRCUIT_H_
15#define OR_TOOLS_SAT_CIRCUIT_H_
16
17#include <functional>
18#include <utility>
19#include <vector>
20
21#include "absl/container/btree_set.h"
22#include "absl/container/flat_hash_map.h"
23#include "absl/types/span.h"
25#include "ortools/sat/integer.h"
26#include "ortools/sat/model.h"
28#include "ortools/sat/util.h"
29#include "ortools/util/rev.h"
30
31namespace operations_research {
32namespace sat {
33
34// Circuit/sub-circuit constraint.
35//
36// Nodes that are not in the unique allowed sub-circuit must point to themseves.
37// A nodes that has no self-arc must thus be inside the sub-circuit. If there is
38// no self-arc at all, then this constraint forces the circuit to go through all
39// the nodes. Multi-arcs are NOT supported.
40//
41// Important: for correctness, this constraint requires that "exactly one"
42// constraints have been added for all the incoming (resp. outgoing) arcs of
43// each node. Also, such constraint must propagate before this one.
45 public:
46 struct Options {
47 // Hack for the VRP to allow for more than one sub-circuit and forces all
48 // the subcircuits to go through the node zero.
50 };
51
52 // The constraints take a sparse representation of a graph on [0, n). Each arc
53 // being present when the given literal is true.
54 CircuitPropagator(int num_nodes, absl::Span<const int> tails,
55 absl::Span<const int> heads,
56 absl::Span<const Literal> literals, Options options,
57 Model* model);
58
59 // This type is neither copyable nor movable.
62
63 void SetLevel(int level) final;
64 bool Propagate() final;
65 bool IncrementalPropagate(const std::vector<int>& watch_indices) final;
67
68 private:
69 // Updates the structures when the given arc is added to the paths.
70 void AddArc(int tail, int head, LiteralIndex literal_index);
71
72 // Clears and fills reason with the literals of the arcs that form a path from
73 // the given node. The path can be a cycle, but in this case it must end at
74 // start (not like a rho shape).
75 void FillReasonForPath(int start_node, std::vector<Literal>* reason) const;
76
77 const int num_nodes_;
78 const Options options_;
79 Trail* trail_;
80 const VariablesAssignment& assignment_;
81
82 // We use this to query in O(1) for an arc existence. The self-arcs are
83 // accessed often, so we use a more efficient std::vector<> for them. Note
84 // that we do not add self-arcs to graph_.
85 //
86 // TODO(user): for large dense graph, using a matrix is faster and uses less
87 // memory. If the need arise we can have the two implementations.
88 std::vector<LiteralIndex> self_arcs_;
89 absl::flat_hash_map<std::pair<int, int>, Literal> graph_;
90
91 // Data used to interpret the watch indices passed to IncrementalPropagate().
92 struct Arc {
93 int tail;
94 int head;
95 };
96 std::vector<Literal> watch_index_to_literal_;
97 CompactVectorVector<int, Arc> watch_index_to_arcs_;
98
99 // Current partial chains of arc that are present.
100 std::vector<int> next_; // -1 if not assigned yet.
101 std::vector<int> prev_; // -1 if not assigned yet.
102 std::vector<LiteralIndex> next_literal_;
103
104 // Backtrack support for the partial chains of arcs, level_ends_[level] is an
105 // index in added_arcs_;
106 std::vector<int> level_ends_;
107 std::vector<Arc> added_arcs_;
108
109 // Reversible list of node that must be in a cycle. A node must be in a cycle
110 // iff self_arcs_[node] is false. This graph entry can be used as a reason.
111 int rev_must_be_in_cycle_size_ = 0;
112 std::vector<int> must_be_in_cycle_;
113
114 // Temporary vectors.
115 std::vector<bool> processed_;
116 std::vector<bool> in_current_path_;
117};
118
119// Enforce the fact that there is no cycle in the given directed graph.
121 public:
122 NoCyclePropagator(int num_nodes, absl::Span<const int> tails,
123 absl::Span<const int> heads,
124 absl::Span<const Literal> literals, Model* model);
125
126 void SetLevel(int level) final;
127 bool Propagate() final;
128 bool IncrementalPropagate(const std::vector<int>& watch_indices) final;
129
130 private:
131 void RegisterWith(GenericLiteralWatcher* watcher);
132
133 const int num_nodes_;
134 Trail* trail_;
135 const VariablesAssignment& assignment_;
136
137 // The set of all watched literals and to what arc they correspond.
138 std::vector<Literal> watch_index_to_literal_;
139 std::vector<std::vector<std::pair<int, int>>> watch_index_to_arcs_;
140
141 // This will only contains the subgraph with all the arc at true.
142 // We maintain it incrementally and update this on SetLevel()/Propagate().
143 std::vector<std::vector<int>> graph_;
144 std::vector<std::vector<Literal>> graph_literals_;
145
146 // For now we redo a strongly connected component on the graph formed of arcs
147 // at one.
148 //
149 // TODO(user): code a true algo.
150 std::vector<std::vector<int>> components_;
152 std::vector<std::vector<int>>>
153 ssc_;
154
155 // SAT incremental state.
156 std::vector<int> level_ends_;
157 std::vector<int> touched_nodes_;
158};
159
160// This constraint ensures that the graph is a covering of all nodes by
161// circuits and loops, such that all circuits contain exactly one distinguished
162// node. Those distinguished nodes are meant to be depots.
163//
164// This constraint does not need ExactlyOnePerRowAndPerColumn() to be correct,
165// but it does not propagate degree deductions (only fails if a node has more
166// than one outgoing arc or more than one incoming arc), so that adding
167// ExactlyOnePerRowAndPerColumn() should work better.
168//
169// TODO(user): Make distinguished nodes an array of Boolean variables,
170// so this can be used for facility location problems.
172 public:
173 CircuitCoveringPropagator(std::vector<std::vector<Literal>> graph,
174 absl::Span<const int> distinguished_nodes,
175 Model* model);
176
177 void SetLevel(int level) final;
178 bool Propagate() final;
179 bool IncrementalPropagate(const std::vector<int>& watch_indices) final;
180 void RegisterWith(GenericLiteralWatcher* watcher);
181
182 private:
183 // Adds all literals on the path/circuit from tail to head in the graph of
184 // literals set to true.
185 // next_[i] should be filled with a node j s.t. graph_[i][j] is true, or -1.
186 void FillFixedPathInReason(int start, int end, std::vector<Literal>* reason);
187
188 // Input data.
189 const std::vector<std::vector<Literal>> graph_;
190 const int num_nodes_;
191 std::vector<bool> node_is_distinguished_;
192
193 // SAT incremental state.
194 Trail* trail_;
195 std::vector<std::pair<int, int>> watch_index_to_arc_;
196 std::vector<std::pair<int, int>> fixed_arcs_;
197 std::vector<int> level_ends_;
198
199 // Used in Propagate() to represent paths and circuits.
200 std::vector<int> next_;
201 std::vector<int> prev_;
202 std::vector<bool> visited_;
203};
204
205// Changes the node indices so that we get a graph in [0, num_nodes) where every
206// node has at least one incoming or outgoing arc. Returns the number of nodes.
207template <class IntContainer>
208int ReindexArcs(IntContainer* tails, IntContainer* heads,
209 absl::flat_hash_map<int, int>* mapping_output = nullptr) {
210 const int num_arcs = tails->size();
211 if (num_arcs == 0) return 0;
212
213 // Put all nodes in a set.
214 absl::btree_set<int> nodes;
215 for (int arc = 0; arc < num_arcs; ++arc) {
216 nodes.insert((*tails)[arc]);
217 nodes.insert((*heads)[arc]);
218 }
219
220 // Compute the new indices while keeping a stable order.
221 int new_index = 0;
222 absl::flat_hash_map<int, int> mapping;
223 for (const int node : nodes) {
224 mapping[node] = new_index++;
225 }
226
227 // Remap the arcs.
228 for (int arc = 0; arc < num_arcs; ++arc) {
229 (*tails)[arc] = mapping[(*tails)[arc]];
230 (*heads)[arc] = mapping[(*heads)[arc]];
231 }
232
233 if (mapping_output != nullptr) {
234 *mapping_output = std::move(mapping);
235 }
236
237 return nodes.size();
238}
239
240// ============================================================================
241// Model based functions.
242// ============================================================================
243
244// This just wraps CircuitPropagator. See the comment there to see what this
245// does. Note that any nodes with no outgoing or no incoming arc will cause the
246// problem to be UNSAT. One can call ReindexArcs() first to ignore such nodes.
247void LoadSubcircuitConstraint(int num_nodes, absl::Span<const int> tails,
248 absl::Span<const int> heads,
249 absl::Span<const Literal> literals, Model* model,
250 bool multiple_subcircuit_through_zero = false);
251
252// TODO(user): Change to a sparse API like for the function above.
253std::function<void(Model*)> ExactlyOnePerRowAndPerColumn(
254 absl::Span<const std::vector<Literal>> graph);
255std::function<void(Model*)> CircuitCovering(
256 absl::Span<const std::vector<Literal>> graph,
257 absl::Span<const int> distinguished_nodes);
258
259} // namespace sat
260} // namespace operations_research
261
262#endif // OR_TOOLS_SAT_CIRCUIT_H_
Definition model.h:341
CircuitCoveringPropagator(std::vector< std::vector< Literal > > graph, absl::Span< const int > distinguished_nodes, Model *model)
Definition circuit.cc:486
void RegisterWith(GenericLiteralWatcher *watcher)
Definition circuit.cc:498
bool IncrementalPropagate(const std::vector< int > &watch_indices) final
Definition circuit.cc:531
void RegisterWith(GenericLiteralWatcher *watcher)
Definition circuit.cc:113
bool IncrementalPropagate(const std::vector< int > &watch_indices) final
Definition circuit.cc:173
CircuitPropagator & operator=(const CircuitPropagator &)=delete
CircuitPropagator(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > literals, Options options, Model *model)
Definition circuit.cc:38
CircuitPropagator(const CircuitPropagator &)=delete
This type is neither copyable nor movable.
bool IncrementalPropagate(const std::vector< int > &watch_indices) final
Definition circuit.cc:435
NoCyclePropagator(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > literals, Model *model)
Definition circuit.cc:362
void LoadSubcircuitConstraint(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > literals, Model *model, bool multiple_subcircuit_through_zero)
Definition circuit.cc:650
std::function< void(Model *)> CircuitCovering(absl::Span< const std::vector< Literal > > graph, absl::Span< const int > distinguished_nodes)
Definition circuit.cc:709
int ReindexArcs(IntContainer *tails, IntContainer *heads, absl::flat_hash_map< int, int > *mapping_output=nullptr)
Definition circuit.h:208
std::function< void(Model *)> ExactlyOnePerRowAndPerColumn(absl::Span< const std::vector< Literal > > graph)
Definition circuit.cc:630
In SWIG mode, we don't want anything besides these top-level includes.
STL namespace.