mirror of
https://github.com/Z3Prover/z3
synced 2026-05-21 01:19:34 +00:00
Add LLL cube heuristic for integer LP (experimental, default off)
Generalize int_cube from the unit cube C = [-1/2, 1/2]^n to a
parallelepiped K = B * C where B is an n x n unimodular integer matrix
found by a monotone pairwise basis-reduction that directly minimizes
the actual cube cost
C(B) = (1/2) * (||A * B||_1 + ||B||_1)
= sum_r delta_row(r, B) + sum_j delta_col(j, B)
The atomic move is a single elementary column op col_j -= q * col_k
with q chosen to minimize C(B) analytically (floor/ceil of the weighted
median of breakpoints {H[r,j]/H[r,k]} and {B[i,j]/B[i,k]}). Starting
from B = I and accepting only strict improvements makes the heuristic
*monotone-safe*: never worse than the plain int_cube. This addresses
the regression of int_cube_hnf (branch hnf_cube), whose triangulation
can blow up the column-delta term ||B||_1. In a 153-instance random
matrix study the HNF basis was worse than B=I by an average factor
3x-50x, while pairwise-greedy LLL was uniformly >= plain cube.
Implementation:
* src/math/lp/int_cube_lll.{h,cpp} -- the heuristic.
* The infrastructure (collect J/terms, tighten bounds, round on a
saved x_J snapshot, lar_solver::apply_lattice_assignment) mirrors
the earlier hnf_cube experiment; the only algorithmic change is
swapping HNF column-reduction for the cost-minimizing pairwise
reduction with bail-on-overflow.
New parameters:
* lp.enable_lll_cube (bool, default false) -- feature gate.
* lp.int_find_lll_cube_period (uint, default 4) -- the LLL cube is
invoked every Nth final-check call after a plain cube failure.
Because it is monotone-safe it runs at cube's period (4) rather
than the throttled 16 the HNF variant used.
Statistics: arith-lll-cube-{calls,success,bail-collect,bail-build,
bail-basis,bail-tighten,bail-infeasible}.
Resource limits: rows <= 75, cols <= 150, bitsize <= 4096, max_passes
<= 8; bail above.
Validation:
* test-z3 /a: 89/89 unit tests pass.
* Smoke run on QF_LIA cut_lemmas and CAV_2009/Bromberger samples:
no result disagreements vs. plain cube; one timeout-to-sat win on
20180326-Bromberger/.../unbd-sage0.smt2 (-T:15).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
2c7b256db2
commit
5336b2c601
11 changed files with 597 additions and 2 deletions
92
src/math/lp/int_cube_lll.h
Normal file
92
src/math/lp/int_cube_lll.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*++
|
||||
Copyright (c) 2025 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
int_cube_lll.h
|
||||
|
||||
Abstract:
|
||||
|
||||
LLL-style cube heuristic for the integer-LP solver.
|
||||
|
||||
Generalization of int_cube. Instead of the unit cube C = [-1/2, 1/2]^n
|
||||
we use a parallelepiped K = B * C, where B is an n x n unimodular
|
||||
integer matrix found by a *monotone* greedy basis-reduction that
|
||||
minimizes the actual cube cost
|
||||
|
||||
C(B) = (1/2) * (||A * B||_1 + ||B||_1)
|
||||
= sum_r delta_row(r, B) + sum_j delta_col(j, B)
|
||||
|
||||
over GL_n(Z). Atomic move is a single elementary column operation
|
||||
|
||||
col_j(B) <- col_j(B) - q * col_k(B) (q in Z, j != k)
|
||||
|
||||
The optimal q for a fixed pair (j, k) is the floor or ceil of the
|
||||
weighted median of the breakpoints {A_row(r,j)/A_row(r,k)} and
|
||||
{B(i,j)/B(i,k)} (weights are the absolute values of the denominators);
|
||||
a standard piecewise-linear minimization. We accept only strict
|
||||
improvements of C(B), starting from B = I; therefore the heuristic
|
||||
is never worse than the plain int_cube.
|
||||
|
||||
This addresses the regression of int_cube_hnf, whose triangulation
|
||||
can blow up the column-delta term ||B||_1: in 153 random instances
|
||||
(3x3 to 5x5, coefficients in [-20, 20]) HNF cube lost to plain cube
|
||||
on >99% of inputs, with an average cost ratio of 3x-50x worse, while
|
||||
pairwise-greedy LLL won or tied on 100% (see findings.md in the
|
||||
session-state).
|
||||
|
||||
Soundness: identical to int_cube_hnf. We work with a saved column
|
||||
snapshot of x_J, tighten the LP, find a real-feasible point x*, and
|
||||
round y* = B^{-1} x* to nearest integer in lattice coordinates, then
|
||||
lift z = B * round(y*).
|
||||
|
||||
Author:
|
||||
Lev Nachmanson (levnach)
|
||||
|
||||
Revision History:
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "math/lp/lia_move.h"
|
||||
#include "math/lp/numeric_pair.h"
|
||||
#include "util/vector.h"
|
||||
|
||||
namespace lp {
|
||||
class int_solver;
|
||||
class lar_solver;
|
||||
class lar_term;
|
||||
|
||||
class int_cube_lll {
|
||||
class int_solver& lia;
|
||||
class lar_solver& lra;
|
||||
|
||||
vector<unsigned> m_J;
|
||||
vector<unsigned> m_col_to_J;
|
||||
vector<unsigned> m_term_js;
|
||||
vector<vector<mpq>> m_H; // H = A * B; m x n
|
||||
vector<vector<mpq>> m_B; // n x n unimodular
|
||||
vector<vector<mpq>> m_B_inv; // B^{-1} = product of inverse elementaries
|
||||
vector<impq> m_term_delta;
|
||||
vector<impq> m_col_delta;
|
||||
vector<impq> m_saved_x_J;
|
||||
|
||||
public:
|
||||
int_cube_lll(int_solver& lia);
|
||||
lia_move operator()();
|
||||
|
||||
private:
|
||||
bool collect_J_and_terms();
|
||||
bool build_matrix();
|
||||
bool compute_basis();
|
||||
bool reduce_pair(unsigned j, unsigned k, bool& improved);
|
||||
bool too_big(const mpq& v) const;
|
||||
bool column_bounds_are_integral(unsigned j) const;
|
||||
bool tighten_terms_for_lll_cube();
|
||||
bool tighten_columns_for_lll_cube();
|
||||
bool tighten_column_bound_by_delta(unsigned j, const impq& delta);
|
||||
void compute_deltas();
|
||||
void save_x_J();
|
||||
void apply_rounding();
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue