Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
diophantine.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 <cstdlib>
19#include <limits>
20#include <numeric>
21#include <vector>
22
23#include "absl/log/check.h"
24#include "absl/numeric/int128.h"
25#include "absl/types/span.h"
26#include "ortools/sat/util.h"
27
29
30namespace {
31
32int64_t Gcd(const absl::Span<const int64_t> coeffs) {
33 DCHECK(coeffs[0] != std::numeric_limits<int64_t>::min());
34 int64_t gcd = std::abs(coeffs[0]);
35 for (int i = 1; i < coeffs.size(); ++i) {
36 DCHECK(coeffs[i] != std::numeric_limits<int64_t>::min());
37 const int64_t abs_coeff = std::abs(coeffs[i]);
38 gcd = std::gcd(gcd, abs_coeff);
39 }
40 return gcd;
41}
42
43} // namespace
44
45void ReduceModuloBasis(absl::Span<const std::vector<absl::int128>> basis,
46 const int elements_to_consider,
47 std::vector<absl::int128>& v) {
48 DCHECK(!basis.empty());
49 for (int i = elements_to_consider - 1; i >= 0; --i) {
50 const int n = static_cast<int>(basis[i].size()) - 1;
51 const absl::int128 leading_coeff = basis[i][n];
52 if (leading_coeff == 0) continue;
53
54 const absl::int128 q =
55 leading_coeff > 0
56 ? FloorOfRatio(v[n] + FloorOfRatio(leading_coeff, absl::int128(2)),
57 leading_coeff)
58 : -FloorOfRatio(
59 v[n] + FloorOfRatio(-leading_coeff, absl::int128(2)),
60 -leading_coeff);
61 if (q == 0) continue;
62 for (int j = 0; j <= n; ++j) v[j] -= q * basis[i][j];
63 }
64}
65
66std::vector<int> GreedyFastDecreasingGcd(
67 const absl::Span<const int64_t> coeffs) {
68 std::vector<int> result;
69 DCHECK(coeffs[0] != std::numeric_limits<int64_t>::min());
70 int64_t min_abs_coeff = std::abs(coeffs[0]);
71 int min_term = 0;
72 int64_t global_gcd = min_abs_coeff;
73 for (int i = 1; i < coeffs.size(); ++i) {
74 DCHECK(coeffs[i] != std::numeric_limits<int64_t>::min());
75 const int64_t abs_coeff = std::abs(coeffs[i]);
76 global_gcd = std::gcd(global_gcd, abs_coeff);
77 if (abs_coeff < min_abs_coeff) {
78 min_abs_coeff = abs_coeff;
79 min_term = i;
80 }
81 }
82 if (min_abs_coeff == global_gcd) return result;
83 int64_t current_gcd = min_abs_coeff;
84 result.reserve(coeffs.size());
85 result.push_back(min_term);
86 while (current_gcd > global_gcd) {
87 // TODO(user): The following is a heuristic to make drop the GCD as fast
88 // as possible. It might be suboptimal in general (as we could miss two
89 // coprime coefficients for instance).
90 int64_t new_gcd = std::gcd(current_gcd, std::abs(coeffs[0]));
91 int term = 0;
92 for (int i = 1; i < coeffs.size(); ++i) {
93 const int64_t gcd = std::gcd(current_gcd, std::abs(coeffs[i]));
94 if (gcd < new_gcd) {
95 term = i;
96 new_gcd = gcd;
97 }
98 }
99 result.push_back(term);
100 current_gcd = new_gcd;
101 }
102 const int initial_count = static_cast<int>(result.size());
103 for (int i = 0; i < coeffs.size(); ++i) {
104 bool seen = false;
105 // initial_count is very small (proven <= 15, usually much smaller).
106 for (int j = 0; j < initial_count; ++j) {
107 if (result[j] == i) {
108 seen = true;
109 break;
110 }
111 }
112 if (seen) continue;
113
114 result.push_back(i);
115 }
116 return result;
117}
118
119DiophantineSolution SolveDiophantine(absl::Span<const int64_t> coeffs,
120 int64_t rhs,
121 absl::Span<const int64_t> var_lbs,
122 absl::Span<const int64_t> var_ubs) {
123 const int64_t global_gcd = Gcd(coeffs);
124
125 if (rhs % global_gcd != 0) return {.has_solutions = false};
126
127 const std::vector<int> pivots = GreedyFastDecreasingGcd(coeffs);
128 if (pivots.empty()) {
129 return {.no_reformulation_needed = true, .has_solutions = true};
130 }
131 int64_t current_gcd = std::abs(coeffs[pivots[0]]);
132
133 // x_i's Satisfying sum(x_i * coeffs[pivots[i]]) = current_gcd.
134 std::vector<absl::int128> special_solution = {current_gcd /
135 coeffs[pivots[0]]};
136 // Z-basis of sum(x_i * arg.coeffs(pivots[i])) = 0.
137 std::vector<std::vector<absl::int128>> kernel_basis;
138 kernel_basis.reserve(coeffs.size() - 1);
139 int i = 1;
140 for (; i < pivots.size() && current_gcd > global_gcd; ++i) {
141 const int64_t coeff = coeffs[pivots[i]];
142 const int64_t new_gcd = std::gcd(current_gcd, std::abs(coeff));
143 kernel_basis.emplace_back(i + 1);
144 kernel_basis.back().back() = current_gcd / new_gcd;
145 for (int i = 0; i < special_solution.size(); ++i) {
146 kernel_basis.back()[i] = -special_solution[i] * coeff / new_gcd;
147 }
148 ReduceModuloBasis(kernel_basis, static_cast<int>(kernel_basis.size()) - 1,
149 kernel_basis.back());
150 // Solves current_gcd * u + coeff * v = new_gcd. Copy the coefficients as
151 // the function below modifies them.
152 int64_t a = current_gcd;
153 int64_t b = coeff;
154 int64_t c = new_gcd;
155 int64_t u, v;
157 for (int i = 0; i < special_solution.size(); ++i) {
158 special_solution[i] *= u;
159 }
160 special_solution.push_back(v);
161 ReduceModuloBasis(kernel_basis, static_cast<int>(kernel_basis.size()),
162 special_solution);
163 current_gcd = new_gcd;
164 }
165 const int replaced_variables_count = i;
166 for (; i < pivots.size(); ++i) {
167 const int64_t coeff = coeffs[pivots[i]];
168 kernel_basis.emplace_back(replaced_variables_count);
169 for (int i = 0; i < special_solution.size(); ++i) {
170 kernel_basis.back()[i] = -special_solution[i] * coeff / global_gcd;
171 }
172 ReduceModuloBasis(kernel_basis, replaced_variables_count - 1,
173 kernel_basis.back());
174 }
175
176 for (int i = 0; i < special_solution.size(); ++i) {
177 special_solution[i] *= rhs / global_gcd;
178 }
179 ReduceModuloBasis(kernel_basis, replaced_variables_count - 1,
180 special_solution);
181
182 // To compute the domains, we use the triangular shape of the basis. The first
183 // one is special as it is controlled by two columns of the basis. Note that
184 // we don't try to compute exact domains as we would need to multiply then
185 // making the number of interval explode.
186 // For i = 0, ..., replaced_variable_count - 1, uses identities
187 // x[i] = special_solution[i]
188 // + sum(linear_basis[k][i]*y[k], max(1, i) <= k < vars.size)
189 // where:
190 // y[k] is a newly created variable if 1 <= k < replaced_variable_count
191 // y[k] = x[pivots[k]] else.
192 // TODO(user): look if there is a natural improvement.
193 std::vector<absl::int128> kernel_vars_lbs(replaced_variables_count - 1);
194 std::vector<absl::int128> kernel_vars_ubs(replaced_variables_count - 1);
195 for (int i = replaced_variables_count - 1; i >= 0; --i) {
196 absl::int128 lb = var_lbs[pivots[i]] - special_solution[i];
197 absl::int128 ub = var_ubs[pivots[i]] - special_solution[i];
198 // Identities 0 and 1 both bound the first element of the basis.
199 const int bounds_to_update = i > 0 ? i - 1 : 0;
200 for (int j = bounds_to_update + 1; j < replaced_variables_count - 1; ++j) {
201 const absl::int128 coeff = kernel_basis[j][i];
202 lb -= coeff * (coeff < 0 ? kernel_vars_lbs[j] : kernel_vars_ubs[j]);
203 ub -= coeff * (coeff < 0 ? kernel_vars_ubs[j] : kernel_vars_lbs[j]);
204 }
205 for (int j = replaced_variables_count - 1; j < pivots.size() - 1; ++j) {
206 const absl::int128 coeff = kernel_basis[j][i];
207 const int64_t lb_var = var_lbs[pivots[j + 1]];
208 const int64_t ub_var = var_ubs[pivots[j + 1]];
209 lb -= coeff * (coeff < 0 ? lb_var : ub_var);
210 ub -= coeff * (coeff < 0 ? ub_var : lb_var);
211 }
212 const absl::int128 coeff = kernel_basis[bounds_to_update][i];
213 const absl::int128 deduced_lb = CeilOfRatio(coeff > 0 ? lb : ub, coeff);
214 const absl::int128 deduced_ub = FloorOfRatio(coeff > 0 ? ub : lb, coeff);
215 if (i > 0) {
216 kernel_vars_lbs[i - 1] = deduced_lb;
217 kernel_vars_ubs[i - 1] = deduced_ub;
218 } else {
219 kernel_vars_lbs[0] = std::max(kernel_vars_lbs[0], deduced_lb);
220 kernel_vars_ubs[0] = std::min(kernel_vars_ubs[0], deduced_ub);
221 }
222 }
223 for (int i = 0; i < replaced_variables_count - 1; ++i) {
224 if (kernel_vars_lbs[i] > kernel_vars_ubs[i])
225 return {.has_solutions = false};
226 }
227 return {.no_reformulation_needed = false,
228 .has_solutions = true,
229 .index_permutation = pivots,
230 .special_solution = special_solution,
231 .kernel_basis = kernel_basis,
232 .kernel_vars_lbs = kernel_vars_lbs,
233 .kernel_vars_ubs = kernel_vars_ubs};
234}
235
236} // namespace operations_research::sat
int64_t a
Definition table.cc:44
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
Definition util.cc:209
IntType CeilOfRatio(IntType numerator, IntType denominator)
Definition util.h:729
DiophantineSolution SolveDiophantine(absl::Span< const int64_t > coeffs, int64_t rhs, absl::Span< const int64_t > var_lbs, absl::Span< const int64_t > var_ubs)
void ReduceModuloBasis(absl::Span< const std::vector< absl::int128 > > basis, const int elements_to_consider, std::vector< absl::int128 > &v)
IntType FloorOfRatio(IntType numerator, IntType denominator)
Definition util.h:734
std::vector< int > GreedyFastDecreasingGcd(const absl::Span< const int64_t > coeffs)