Skip to content

Commit 3de5af3

Browse files
fix bug in bound simplification in Gomory for mixed integer linear cuts, enable fixed variable redution after bugfix, add notes that rewriting bounds does not work
Signed-off-by: Nikolaj Bjorner <[email protected]>
1 parent aa9c791 commit 3de5af3

File tree

5 files changed

+121
-261
lines changed

5 files changed

+121
-261
lines changed

src/math/lp/gomory.cpp

+87-122
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ class create_cut {
3232
unsigned m_inf_col; // a basis column which has to be an integer but has a non integral value
3333
const row_strip<mpq>& m_row;
3434
int_solver& lia;
35-
mpq m_lcm_den = { mpq(1) };
3635
mpq m_f;
3736
mpq m_one_minus_f;
3837
mpq m_fj;
@@ -131,120 +130,7 @@ class create_cut {
131130
// conflict 0 >= k where k is positive
132131
return lia_move::conflict;
133132
}
134-
135-
void divd(mpq& r, mpq const& d) {
136-
r /= d;
137-
if (!r.is_int())
138-
r = ceil(r);
139-
}
140-
141-
bool can_divide_by(vector<std::pair<mpq, lpvar>> const& p, mpq const& d) {
142-
mpq lhs(0), rhs(m_k);
143-
mpq max_c(abs(m_k));
144-
for (auto const& [c, v] : p) {
145-
auto c1 = c;
146-
max_c = std::max(max_c, abs(c1));
147-
divd(c1, d);
148-
if (c1 == 0)
149-
return false;
150-
VERIFY(lia.get_value(v).y == 0);
151-
lhs += c1 * lia.get_value(v).x;
152-
}
153-
if (max_c == 1)
154-
return false;
155-
divd(rhs, d);
156-
return lhs < rhs;
157-
}
158133

159-
void adjust_term_and_k_for_some_ints_case_gomory() {
160-
lp_assert(!m_t.is_empty());
161-
// k = 1 + sum of m_t at bounds
162-
lar_term t = lia.lra.unfold_nested_subterms(m_t);
163-
auto pol = t.coeffs_as_vector();
164-
m_t.clear();
165-
if (pol.size() == 1) {
166-
TRACE("gomory_cut_detail", tout << "pol.size() is 1" << std::endl;);
167-
auto const& [a, v] = pol[0];
168-
lp_assert(is_int(v));
169-
if (a.is_pos()) { // we have av >= k
170-
divd(m_k, a);
171-
m_t.add_monomial(mpq(1), v);
172-
}
173-
else {
174-
// av >= k
175-
// a/-a*v >= k / - a
176-
// -v >= k / - a
177-
// -v >= ceil(k / -a)
178-
divd(m_k, -a);
179-
m_t.add_monomial(-mpq(1), v);
180-
}
181-
}
182-
else {
183-
m_lcm_den = denominator(m_k);
184-
for (auto const& [c, v] : pol)
185-
m_lcm_den = lcm(m_lcm_den, denominator(c));
186-
lp_assert(m_lcm_den.is_pos());
187-
TRACE("gomory_cut_detail", tout << "pol.size() > 1 den: " << m_lcm_den << std::endl;);
188-
if (!m_lcm_den.is_one()) {
189-
// normalize coefficients of integer parameters to be integers.
190-
for (auto & [c,v]: pol) {
191-
c *= m_lcm_den;
192-
SASSERT(!is_int(v) || c.is_int());
193-
}
194-
m_k *= m_lcm_den;
195-
}
196-
#if 0
197-
unsigned j = 0, i = 0;
198-
for (auto & [c, v] : pol) {
199-
if (lia.is_fixed(v)) {
200-
push_explanation(column_lower_bound_constraint(v));
201-
push_explanation(column_upper_bound_constraint(v));
202-
m_k -= c;
203-
IF_VERBOSE(0, verbose_stream() << "got fixed " << v << "\n");
204-
}
205-
else
206-
pol[j++] = pol[i];
207-
++i;
208-
}
209-
pol.shrink(j);
210-
#endif
211-
212-
// gcd reduction is loss-less:
213-
mpq g(1);
214-
for (const auto & [c, v] : pol)
215-
g = gcd(g, c);
216-
217-
if (g != 1) {
218-
for (auto & [c, v] : pol)
219-
c /= g;
220-
divd(m_k, g);
221-
}
222-
223-
#if 0
224-
// TODO: create self-contained rounding mode to weaken cuts
225-
// whose cofficients are considered too large
226-
// (larger than bounds from the input)
227-
mpq min_c = abs(m_k);
228-
for (const auto & [c, v] : pol)
229-
min_c = std::min(min_c, abs(c));
230-
231-
if (min_c > 1 && can_divide_by(pol, min_c)) {
232-
for (auto& [c, v] : pol)
233-
divd(c, min_c);
234-
divd(m_k, min_c);
235-
}
236-
#endif
237-
238-
for (const auto & [c, v]: pol)
239-
m_t.add_monomial(c, v);
240-
VERIFY(m_t.size() > 0);
241-
}
242-
243-
TRACE("gomory_cut_detail", tout << "k = " << m_k << std::endl;);
244-
lp_assert(m_k.is_int());
245-
246-
247-
}
248134

249135
std::string var_name(unsigned j) const {
250136
return std::string("x") + std::to_string(j);
@@ -359,12 +245,12 @@ class create_cut {
359245
// gomory will be t >= k and the current solution has a property t < k
360246
m_k = 1;
361247
m_t.clear();
362-
bool some_int_columns = false;
363248
mpq m_f = fractional_part(get_value(m_inf_col));
364249
TRACE("gomory_cut_detail", tout << "m_f: " << m_f << ", ";
365250
tout << "1 - m_f: " << 1 - m_f << ", get_value(m_inf_col).x - m_f = " << get_value(m_inf_col).x - m_f << "\n";);
366251
lp_assert(m_f.is_pos() && (get_value(m_inf_col).x - m_f).is_int());
367252

253+
bool some_int_columns = false;
368254
#if SMALL_CUTS
369255
m_abs_max = 0;
370256
for (const auto & p : m_row) {
@@ -408,13 +294,7 @@ class create_cut {
408294
if (m_t.is_empty())
409295
return report_conflict_from_gomory_cut();
410296
if (some_int_columns)
411-
adjust_term_and_k_for_some_ints_case_gomory();
412-
if (!lia.current_solution_is_inf_on_cut()) {
413-
m_ex->clear();
414-
m_t.clear();
415-
m_k = 1;
416-
return lia_move::undef;
417-
}
297+
simplify_inequality();
418298
lp_assert(lia.current_solution_is_inf_on_cut()); // checks that indices are columns
419299
TRACE("gomory_cut", print_linear_combination_of_column_indices_only(m_t.coeffs_as_vector(), tout << "gomory cut: "); tout << " >= " << m_k << std::endl;);
420300
TRACE("gomory_cut_detail", dump_cut_and_constraints_as_smt_lemma(tout);
@@ -423,6 +303,91 @@ class create_cut {
423303
return lia_move::cut;
424304
}
425305

306+
// TODO: use this also for HNF cuts?
307+
mpq m_lcm_den = { mpq(1) };
308+
309+
void simplify_inequality() {
310+
311+
auto divd = [](mpq& r, mpq const& d) {
312+
r /= d;
313+
if (!r.is_int())
314+
r = ceil(r);
315+
};
316+
SASSERT(!lia.m_upper);
317+
lp_assert(!m_t.is_empty());
318+
// k = 1 + sum of m_t at bounds
319+
lar_term t = lia.lra.unfold_nested_subterms(m_t);
320+
auto pol = t.coeffs_as_vector();
321+
m_t.clear();
322+
if (pol.size() == 1) {
323+
TRACE("gomory_cut_detail", tout << "pol.size() is 1" << std::endl;);
324+
auto const& [a, v] = pol[0];
325+
lp_assert(is_int(v));
326+
if (a.is_pos()) { // we have av >= k
327+
divd(m_k, a);
328+
m_t.add_monomial(mpq(1), v);
329+
}
330+
else {
331+
// av >= k
332+
// a/-a*v >= k / - a
333+
// -v >= k / - a
334+
// -v >= ceil(k / -a)
335+
divd(m_k, -a);
336+
m_t.add_monomial(-mpq(1), v);
337+
}
338+
}
339+
else {
340+
m_lcm_den = denominator(m_k);
341+
for (auto const& [c, v] : pol)
342+
m_lcm_den = lcm(m_lcm_den, denominator(c));
343+
lp_assert(m_lcm_den.is_pos());
344+
bool int_row = true;
345+
TRACE("gomory_cut_detail", tout << "pol.size() > 1 den: " << m_lcm_den << std::endl;);
346+
if (!m_lcm_den.is_one()) {
347+
// normalize coefficients of integer parameters to be integers.
348+
for (auto & [c,v]: pol) {
349+
c *= m_lcm_den;
350+
SASSERT(!is_int(v) || c.is_int());
351+
int_row &= is_int(v);
352+
}
353+
m_k *= m_lcm_den;
354+
}
355+
unsigned j = 0, i = 0;
356+
for (auto & [c, v] : pol) {
357+
if (lia.is_fixed(v)) {
358+
push_explanation(column_lower_bound_constraint(v));
359+
push_explanation(column_upper_bound_constraint(v));
360+
m_k -= c * lower_bound(v).x;
361+
}
362+
else
363+
pol[j++] = pol[i];
364+
++i;
365+
}
366+
pol.shrink(j);
367+
368+
// gcd reduction is loss-less:
369+
mpq g(1);
370+
for (const auto & [c, v] : pol)
371+
g = gcd(g, c);
372+
if (!int_row)
373+
g = gcd(g, m_k);
374+
375+
if (g != 1) {
376+
for (auto & [c, v] : pol)
377+
c /= g;
378+
divd(m_k, g);
379+
}
380+
381+
for (const auto & [c, v]: pol)
382+
m_t.add_monomial(c, v);
383+
VERIFY(m_t.size() > 0);
384+
}
385+
386+
TRACE("gomory_cut_detail", tout << "k = " << m_k << std::endl;);
387+
lp_assert(m_k.is_int());
388+
}
389+
390+
426391
create_cut(lar_term & t, mpq & k, explanation* ex, unsigned basic_inf_int_j, const row_strip<mpq>& row, int_solver& lia) :
427392
m_t(t),
428393
m_k(k),

src/math/lp/hnf_cutter.cpp

+16-24
Original file line numberDiff line numberDiff line change
@@ -83,31 +83,29 @@ namespace lp {
8383
// consider return from here if b[i] is not an integer and return i
8484
}
8585
}
86-
86+
8787
vector<mpq> hnf_cutter::create_b(const svector<unsigned> & basis_rows) {
8888
if (basis_rows.size() == m_right_sides.size())
8989
return m_right_sides;
9090
vector<mpq> b;
91-
for (unsigned i : basis_rows) {
92-
b.push_back(m_right_sides[i]);
93-
}
91+
for (unsigned i : basis_rows)
92+
b.push_back(m_right_sides[i]);
9493
return b;
9594
}
9695

9796
int hnf_cutter::find_cut_row_index(const vector<mpq> & b) {
9897
int ret = -1;
9998
int n = 0;
10099
for (int i = 0; i < static_cast<int>(b.size()); i++) {
101-
if (is_integer(b[i])) continue;
102-
if (n == 0 ) {
100+
if (is_integer(b[i]))
101+
continue;
102+
if (n == 0) {
103103
lp_assert(ret == -1);
104104
n = 1;
105105
ret = i;
106-
} else {
107-
if (m_settings.random_next() % (++n) == 0) {
108-
ret = i;
109-
}
110106
}
107+
else if (m_settings.random_next() % (++n) == 0)
108+
ret = i;
111109
}
112110
return ret;
113111
}
@@ -240,10 +238,7 @@ branch y_i >= ceil(y0_i) is impossible.
240238
}
241239

242240
bool hnf_cutter::hnf_has_var_with_non_integral_value() const {
243-
for (unsigned j : vars())
244-
if (!lia.get_value(j).is_int())
245-
return true;
246-
return false;
241+
return any_of(vars(), [&](unsigned j) { return !lia.get_value(j).is_int(); });
247242
}
248243

249244
bool hnf_cutter::init_terms_for_hnf_cut() {
@@ -252,17 +247,15 @@ branch y_i >= ceil(y0_i) is impossible.
252247
try_add_term_to_A_for_hnf(tv::term(i));
253248
return hnf_has_var_with_non_integral_value();
254249
}
255-
250+
256251
lia_move hnf_cutter::make_hnf_cut() {
257-
if (!init_terms_for_hnf_cut()) {
252+
if (!init_terms_for_hnf_cut())
258253
return lia_move::undef;
259-
}
260254
lia.settings().stats().m_hnf_cutter_calls++;
261255
TRACE("hnf_cut", tout << "settings().stats().m_hnf_cutter_calls = " << lia.settings().stats().m_hnf_cutter_calls << "\n";
262-
for (u_dependency* d : constraints_for_explanation()) {
263-
for (auto ci : lra.flatten(d))
264-
lra.constraints().display(tout, ci);
265-
}
256+
for (u_dependency* d : constraints_for_explanation())
257+
for (auto ci : lra.flatten(d))
258+
lra.constraints().display(tout, ci);
266259
tout << lra.constraints();
267260
);
268261
#ifdef Z3DEBUG
@@ -273,14 +266,14 @@ branch y_i >= ceil(y0_i) is impossible.
273266
, x0
274267
#endif
275268
);
276-
269+
277270
if (r == lia_move::cut) {
278271
TRACE("hnf_cut",
279272
lra.print_term(lia.m_t, tout << "cut:");
280273
tout << " <= " << lia.m_k << std::endl;
281274
for (auto* dep : constraints_for_explanation())
282275
for (auto ci : lra.flatten(dep))
283-
lra.constraints().display(tout, ci);
276+
lra.constraints().display(tout, ci);
284277
);
285278
lp_assert(lia.current_solution_is_inf_on_cut());
286279
lia.settings().stats().m_hnf_cuts++;
@@ -291,5 +284,4 @@ branch y_i >= ceil(y0_i) is impossible.
291284
}
292285
return r;
293286
}
294-
295287
}

0 commit comments

Comments
 (0)