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