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