Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
rins.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
14#include "ortools/sat/rins.h"
15
16#include <algorithm>
17#include <cmath>
18#include <cstdint>
19#include <limits>
20#include <random>
21#include <string>
22#include <utility>
23#include <vector>
24
25#include "absl/log/check.h"
26#include "absl/random/bit_gen_ref.h"
27#include "absl/random/distributions.h"
28#include "absl/types/span.h"
30#include "ortools/sat/integer.h"
32#include "ortools/sat/model.h"
34
35namespace operations_research {
36namespace sat {
37
39 auto* lp_solutions = model->Mutable<SharedLPSolutionRepository>();
40 if (lp_solutions == nullptr) return;
41
42 auto* mapping = model->GetOrCreate<CpModelMapping>();
43 auto* lp_values = model->GetOrCreate<ModelLpValues>();
44
45 // TODO(user): The default of ::infinity() for variable for which we do not
46 // have any LP solution is weird and inconsistent with ModelLpValues default
47 // which is zero. Fix. Note that in practice, at linearization level 2, all
48 // variable will eventually have an lp relaxation value, so it shoulnd't
49 // matter much to just use zero in RINS/RENS.
50 std::vector<double> relaxation_values(
51 mapping->NumProtoVariables(), std::numeric_limits<double>::infinity());
52
53 // We only loop over the positive variables.
54 const int size = lp_values->size();
55 for (IntegerVariable var(0); var < size; var += 2) {
56 const int proto_var = mapping->GetProtoVariableFromIntegerVariable(var);
57 if (proto_var != -1) {
58 relaxation_values[proto_var] = (*lp_values)[var];
59 }
60 }
61
62 lp_solutions->NewLPSolution(std::move(relaxation_values));
63}
64
65namespace {
66
67std::vector<double> GetLPRelaxationValues(
68 const SharedLPSolutionRepository* lp_solutions, absl::BitGenRef random) {
69 std::vector<double> relaxation_values;
70
71 if (lp_solutions == nullptr || lp_solutions->NumSolutions() == 0) {
72 return relaxation_values;
73 }
74
75 const SharedSolutionRepository<double>::Solution lp_solution =
76 lp_solutions->GetRandomBiasedSolution(random);
77
78 for (int model_var = 0; model_var < lp_solution.variable_values.size();
79 ++model_var) {
80 relaxation_values.push_back(lp_solution.variable_values[model_var]);
81 }
82 return relaxation_values;
83}
84
85std::vector<double> GetIncompleteSolutionValues(
86 SharedIncompleteSolutionManager* incomplete_solutions) {
87 std::vector<double> empty_solution_values;
88
89 if (incomplete_solutions == nullptr || !incomplete_solutions->HasSolution()) {
90 return empty_solution_values;
91 }
92
93 return incomplete_solutions->PopLast();
94}
95
96static double kEpsilon = 1e-7;
97
98struct VarWeight {
100 // Variables with minimum weight will be fixed in the neighborhood.
101 double weight;
102
103 // Comparator with tolerance and random tie breaking.
104 bool operator<(const VarWeight& o) const { return weight < o.weight; }
105};
106
107void FillRinsNeighborhood(absl::Span<const int64_t> solution,
108 absl::Span<const double> relaxation_values,
109 double difficulty, absl::BitGenRef random,
110 ReducedDomainNeighborhood& reduced_domains) {
111 std::vector<VarWeight> var_lp_gap_pairs;
112 for (int model_var = 0; model_var < relaxation_values.size(); ++model_var) {
113 const double relaxation_value = relaxation_values[model_var];
114 if (relaxation_value == std::numeric_limits<double>::infinity()) continue;
115
116 const int64_t best_solution_value = solution[model_var];
117 const double pertubation = absl::Uniform(random, -kEpsilon, kEpsilon);
118 var_lp_gap_pairs.push_back({
119 model_var,
120 std::abs(relaxation_value - static_cast<double>(best_solution_value)) +
121 pertubation,
122 });
123 }
124 std::sort(var_lp_gap_pairs.begin(), var_lp_gap_pairs.end());
125
126 const int target_size = std::min(
127 static_cast<int>(std::round(
128 static_cast<double>(relaxation_values.size()) * (1.0 - difficulty))),
129 static_cast<int>(var_lp_gap_pairs.size()));
130 for (int i = 0; i < target_size; ++i) {
131 const int model_var = var_lp_gap_pairs[i].model_var;
132 reduced_domains.fixed_vars.push_back({model_var, solution[model_var]});
133 }
134}
135
136void FillRensNeighborhood(absl::Span<const double> relaxation_values,
137 double difficulty, absl::BitGenRef random,
138 ReducedDomainNeighborhood& reduced_domains) {
139 std::vector<VarWeight> var_fractionality_pairs;
140 for (int model_var = 0; model_var < relaxation_values.size(); ++model_var) {
141 const double relaxation_value = relaxation_values[model_var];
142 if (relaxation_value == std::numeric_limits<double>::infinity()) continue;
143
144 const double pertubation = absl::Uniform(random, -kEpsilon, kEpsilon);
145 var_fractionality_pairs.push_back(
146 {model_var, std::abs(std::round(relaxation_value) - relaxation_value) +
147 pertubation});
148 }
149 std::sort(var_fractionality_pairs.begin(), var_fractionality_pairs.end());
150 const int target_size = static_cast<int>(std::round(
151 static_cast<double>(relaxation_values.size()) * (1.0 - difficulty)));
152 for (int i = 0; i < var_fractionality_pairs.size(); ++i) {
153 const int model_var = var_fractionality_pairs[i].model_var;
154 const double relaxation_value = relaxation_values[model_var];
155 if (i < target_size) {
156 // Fix the variable.
157 reduced_domains.fixed_vars.push_back(
158 {model_var, static_cast<int64_t>(std::round(relaxation_value))});
159 } else {
160 // Important: the LP relaxation doesn't know about holes in the variable
161 // domains, so the intersection of [domain_lb, domain_ub] with the
162 // initial variable domain might be empty.
163 const int64_t domain_lb =
164 static_cast<int64_t>(std::floor(relaxation_value));
165 // TODO(user): Use the domain here.
166 reduced_domains.reduced_domain_vars.push_back(
167 {model_var, {domain_lb, domain_lb + 1}});
168 }
169 }
170}
171
172} // namespace
173
175 const SharedResponseManager* response_manager,
176 const SharedLPSolutionRepository* lp_solutions,
177 SharedIncompleteSolutionManager* incomplete_solutions, double difficulty,
178 absl::BitGenRef random) {
179 ReducedDomainNeighborhood reduced_domains;
180 CHECK(lp_solutions != nullptr);
181 CHECK(incomplete_solutions != nullptr);
182 const bool lp_solution_available = lp_solutions->NumSolutions() > 0;
183 const bool incomplete_solution_available =
184 incomplete_solutions->HasSolution();
185
186 if (!lp_solution_available && !incomplete_solution_available) {
187 return reduced_domains; // Not generated.
188 }
189
190 // Using a partial LP relaxation computed by feasibility_pump, and a full lp
191 // relaxation periodically dumped by linearization=2 workers is equiprobable.
192 std::bernoulli_distribution random_bool(0.5);
193
194 const bool use_lp_relaxation =
195 lp_solution_available && incomplete_solution_available
196 ? random_bool(random)
197 : lp_solution_available;
198
199 const std::vector<double> relaxation_values =
200 use_lp_relaxation ? GetLPRelaxationValues(lp_solutions, random)
201 : GetIncompleteSolutionValues(incomplete_solutions);
202 if (relaxation_values.empty()) return reduced_domains; // Not generated.
203
204 std::bernoulli_distribution three_out_of_four(0.75);
205
206 if (response_manager != nullptr &&
207 response_manager->SolutionsRepository().NumSolutions() > 0 &&
208 three_out_of_four(random)) { // Rins.
209 const std::vector<int64_t> solution =
210 response_manager->SolutionsRepository()
212 .variable_values;
213 FillRinsNeighborhood(solution, relaxation_values, difficulty, random,
214 reduced_domains);
215 reduced_domains.source_info = "rins_";
216 } else { // Rens.
217 FillRensNeighborhood(relaxation_values, difficulty, random,
218 reduced_domains);
219 reduced_domains.source_info = "rens_";
220 }
221
222 absl::StrAppend(&reduced_domains.source_info,
223 use_lp_relaxation ? "lp" : "pump", "_lns");
224 return reduced_domains;
225}
226
227} // namespace sat
228} // namespace operations_research
IntegerValue size
const SharedSolutionRepository< int64_t > & SolutionsRepository() const
Solution GetRandomBiasedSolution(absl::BitGenRef random) const
Returns a random solution biased towards good solutions.
IntVar * var
GRBmodel * model
double solution
constexpr double kEpsilon
Epsilon for type Fractional, i.e. the smallest e such that 1.0 + e != 1.0 .
Definition lp_types.h:92
ReducedDomainNeighborhood GetRinsRensNeighborhood(const SharedResponseManager *response_manager, const SharedLPSolutionRepository *lp_solutions, SharedIncompleteSolutionManager *incomplete_solutions, double difficulty, absl::BitGenRef random)
Definition rins.cc:174
void RecordLPRelaxationValues(Model *model)
Adds the current LP solution to the pool.
Definition rins.cc:38
In SWIG mode, we don't want anything besides these top-level includes.
int64_t weight
Definition pack.cc:510
int model_var
Definition rins.cc:99