3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-12 12:08:18 +00:00

fix hilbert_basis tests and add heap_trie index

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
Nikolaj Bjorner 2013-02-16 22:45:37 -08:00
parent 47342e5d0c
commit 306855ba55
6 changed files with 579 additions and 74 deletions

355
src/muz_qe/heap_trie.h Normal file
View file

@ -0,0 +1,355 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
heap_trie.h
Abstract:
Heap trie structure.
Structure that lets you retrieve point-wise smaller entries
of a tuple. A lookup is to identify entries whose keys
are point-wise dominated by the lookup key.
Author:
Nikolaj Bjorner (nbjorner) 2013-02-15.
Notes:
tries are unordered vectors of keys. This could be enhanced to use either
heaps or sorting. The problem with using the heap implementation directly is that there is no way to
retrieve elements less or equal to a key that is not already in the heap.
If nodes have only a few elements, then this would also be a bloated data-structure to maintain.
Nodes are not de-allocated. Their reference count indicates if they are valid.
Possibly, add garbage collection.
Maintaining sorted ranges for larger domains is another option.
--*/
#ifndef _HEAP_TRIE_H_
#define _HEAP_TRIE_H_
#include "map.h"
#include "vector.h"
#include "statistics.h"
template<typename Key, typename Value>
class heap_trie {
struct stats {
unsigned m_num_inserts;
unsigned m_num_removes;
unsigned m_num_find_eq;
unsigned m_num_find_le;
unsigned m_num_find_le_nodes;
stats() { reset(); }
void reset() { memset(this, 0, sizeof(*this)); }
};
enum node_t {
trie_t,
leaf_t
};
class node {
node_t m_type;
unsigned m_ref;
public:
node(node_t t): m_type(t), m_ref(0) {}
virtual ~node() {}
node_t type() const { return m_type; }
void inc_ref() { ++m_ref; }
void dec_ref() { SASSERT(m_ref > 0); --m_ref; }
unsigned ref_count() const { return m_ref; }
virtual void display(std::ostream& out, unsigned indent) const = 0;
virtual unsigned size() const = 0;
};
class leaf : public node {
Value m_value;
public:
leaf(): node(leaf_t) {}
virtual ~leaf() {}
Value const& get_value() const { return m_value; }
void set_value(Value const& v) { m_value = v; }
virtual void display(std::ostream& out, unsigned indent) const {
out << " value: " << m_value;
}
virtual unsigned size() const { return 1; }
};
// lean trie node
class trie : public node {
vector<std::pair<Key,node*> > m_nodes;
public:
trie(): node(trie_t) {}
virtual ~trie() {
for (unsigned i = 0; i < m_nodes.size(); ++i) {
dealloc(m_nodes[i].second);
}
}
node* find_or_insert(Key k, node* n) {
for (unsigned i = 0; i < m_nodes.size(); ++i) {
if (m_nodes[i].first == k) {
return m_nodes[i].second;
}
}
m_nodes.push_back(std::make_pair(k, n));
return n;
}
bool find(Key k, node*& n) const {
for (unsigned i = 0; i < m_nodes.size(); ++i) {
if (m_nodes[i].first == k) {
n = m_nodes[i].second;
return n->ref_count() > 0;
}
}
return false;
}
// push nodes whose keys are <= key into vector.
void find_le(Key key, ptr_vector<node>& nodes) {
for (unsigned i = 0; i < m_nodes.size(); ++i) {
if (m_nodes[i].first <= key) {
node* n = m_nodes[i].second;
if (n->ref_count() > 0){
nodes.push_back(n);
}
}
}
}
virtual void display(std::ostream& out, unsigned indent) const {
for (unsigned j = 0; j < m_nodes.size(); ++j) {
out << "\n";
for (unsigned i = 0; i < indent; ++i) {
out << " ";
}
node* n = m_nodes[j].second;
out << m_nodes[j].first << " count: " << n->ref_count();
n->display(out, indent + 1);
}
}
virtual unsigned size() const {
unsigned sz = 1;
for (unsigned j = 0; j < m_nodes.size(); ++j) {
sz += m_nodes[j].second->size();
}
return sz;
}
private:
bool contains(Key k) {
for (unsigned j = 0; j < m_nodes.size(); ++j) {
if (m_nodes[j].first == k) {
return true;
}
}
return false;
}
};
unsigned m_num_keys;
node* m_root;
stats m_stats;
node* m_spare_leaf;
node* m_spare_trie;
vector<ptr_vector<node> > m_children;
public:
heap_trie():
m_num_keys(0),
m_root(0),
m_spare_leaf(0),
m_spare_trie(0)
{}
~heap_trie() {
dealloc(m_root);
dealloc(m_spare_leaf);
dealloc(m_spare_trie);
}
unsigned size() const {
return m_root->size();
}
void reset(unsigned num_keys) {
dealloc(m_root);
dealloc(m_spare_leaf);
dealloc(m_spare_trie);
m_num_keys = num_keys;
m_root = mk_trie();
m_spare_trie = mk_trie();
m_spare_leaf = mk_leaf();
}
void insert(Key const* keys, Value const& val) {
++m_stats.m_num_inserts;
insert(m_root, num_keys(), keys, val);
}
bool find_eq(Key const* keys, Value& value) {
++m_stats.m_num_find_eq;
node* n = m_root;
node* m;
for (unsigned i = 0; i < num_keys(); ++i) {
if (!to_trie(n)->find(keys[i], m)) {
return false;
}
n = m;
}
value = to_leaf(n)->get_value();
return true;
}
void find_le(Key const* keys, vector<Value>& values) {
++m_stats.m_num_find_le;
ptr_vector<node> todo[2];
todo[0].push_back(m_root);
bool index = false;
for (unsigned i = 0; i < num_keys(); ++i) {
for (unsigned j = 0; j < todo[index].size(); ++j) {
++m_stats.m_num_find_le_nodes;
to_trie(todo[index][j])->find_le(keys[i], todo[!index]);
}
todo[index].reset();
index = !index;
}
for (unsigned j = 0; j < todo[index].size(); ++j) {
values.push_back(to_leaf(todo[index][j])->get_value());
}
}
// callback based find function
class check_value {
public:
virtual bool operator()(Value const& v) = 0;
};
bool find_le(Key const* keys, check_value& check) {
++m_stats.m_num_find_le;
if (m_children.size() < num_keys()) {
m_children.resize(num_keys());
}
return find_le(m_root, 0, keys, check);
}
bool find_le(node* n, unsigned index, Key const* keys, check_value& check) {
++m_stats.m_num_find_le_nodes;
if (index == num_keys()) {
SASSERT(n->ref_count() > 0);
return check(to_leaf(n)->get_value());
}
else {
m_children[index].reset();
to_trie(n)->find_le(keys[index], m_children[index]);
for (unsigned i = 0; i < m_children[index].size(); ++i) {
if (find_le(m_children[index][i], index + 1, keys, check)) {
return true;
}
}
return false;
}
}
void remove(Key const* keys) {
++m_stats.m_num_removes;
// assumption: key is in table.
node* n = m_root;
node* m;
for (unsigned i = 0; i < num_keys(); ++i) {
n->dec_ref();
VERIFY (to_trie(n)->find(keys[i], m));
n = m;
}
n->dec_ref();
}
void reset_statistics() {
m_stats.reset();
}
void collect_statistics(statistics& st) const {
st.update("heap_trie.num_inserts", m_stats.m_num_inserts);
st.update("heap_trie.num_removes", m_stats.m_num_removes);
st.update("heap_trie.num_find_eq", m_stats.m_num_find_eq);
st.update("heap_trie.num_find_le", m_stats.m_num_find_le);
st.update("heap_trie.num_find_le_nodes", m_stats.m_num_find_le_nodes);
}
void display(std::ostream& out) {
m_root->display(out, 0);
out << "\n";
}
private:
unsigned num_keys() const {
return m_num_keys;
}
void insert(node* n, unsigned num_keys, Key const* keys, Value const& val) {
// assumption: key is not in table.
while (true) {
n->inc_ref();
if (num_keys == 0) {
to_leaf(n)->set_value(val);
SASSERT(n->ref_count() == 1);
break;
}
else {
n = insert_key(to_trie(n), (num_keys == 1), keys[0]);
--num_keys;
++keys;
}
}
}
node* insert_key(trie* n, bool is_leaf, Key const& key) {
node* m1 = is_leaf?m_spare_leaf:m_spare_trie;
node* m2 = n->find_or_insert(key, m1);
if (m1 == m2) {
if (is_leaf) {
m_spare_leaf = mk_leaf();
}
else {
m_spare_trie = mk_trie();
}
}
return m2;
}
leaf* mk_leaf() {
return alloc(leaf);
}
trie* mk_trie() {
return alloc(trie);
}
trie* to_trie(node* n) {
SASSERT(n->type() == trie_t);
return static_cast<trie*>(n);
}
leaf* to_leaf(node* n) {
SASSERT(n->type() == leaf_t);
return static_cast<leaf*>(n);
}
};
#endif

View file

@ -20,12 +20,13 @@ Revision History:
#include "hilbert_basis.h"
#include "heap.h"
#include "map.h"
#include "heap_trie.h"
template<typename Value>
class rational_map : public map<rational, Value, rational::hash_proc, rational::eq_proc> {};
class hilbert_basis::value_index {
class hilbert_basis::value_index1 {
struct stats {
unsigned m_num_comparisons;
unsigned m_num_hit;
@ -41,7 +42,7 @@ class hilbert_basis::value_index {
stats m_stats;
public:
value_index(hilbert_basis& hb):
value_index1(hilbert_basis& hb):
hb(hb)
{}
@ -128,12 +129,112 @@ private:
}
};
class hilbert_basis::value_index2 {
struct checker : public heap_trie<numeral, unsigned>::check_value {
hilbert_basis* hb;
offset_t m_value;
offset_t m_found;
checker(): hb(0) {}
virtual bool operator()(unsigned const& v) {
if (m_value.m_offset != v && hb->is_subsumed(m_value, offset_t(v))) {
m_found = offset_t(v);
return true;
}
else {
return false;
}
}
};
hilbert_basis& hb;
heap_trie<numeral, unsigned> m_trie;
vector<unsigned> m_found;
bool m_init;
checker m_checker;
public:
value_index2(hilbert_basis& hb): hb(hb), m_init(false) {
m_checker.hb = &hb;
}
void insert(offset_t idx, values const& vs) {
init();
m_trie.insert(vs()-1, idx.m_offset);
}
void remove(offset_t idx, values const& vs) {
m_trie.remove(vs()-1);
}
void reset() {
m_trie.reset(hb.get_num_vars());
}
bool find(offset_t idx, values const& vs, offset_t& found_idx) {
init();
m_found.reset();
m_checker.m_value = idx;
if (!m_trie.find_le(vs()-1, m_checker)) {
return false;
}
else {
found_idx = m_checker.m_found;
return true;
}
}
bool find_old(offset_t idx, values const& vs, offset_t& found_idx) {
init();
m_found.reset();
m_trie.find_le(vs()-1, m_found);
std::cout << m_found.size() << " - ";
for (unsigned i = 0; i < m_found.size(); ++i) {
found_idx = offset_t(m_found[i]);
std::cout << i << " ";
if (m_found[i] != idx.m_offset && hb.is_subsumed(idx, found_idx)) {
TRACE("hilbert_basis",
hb.display(tout << "found:" , idx);
hb.display(tout << "-> ", found_idx);
m_trie.display(tout););
std::cout << "\n";
return true;
}
}
std::cout << "\n";
TRACE("hilbert_basis", tout << "not found: "; hb.display(tout, idx); );
return false;
}
void collect_statistics(statistics& st) const {
m_trie.collect_statistics(st);
}
void reset_statistics() {
m_trie.reset_statistics();
}
unsigned size() const {
return m_trie.size();
}
private:
void init() {
if (!m_init) {
reset();
m_init = true;
}
}
};
class hilbert_basis::index {
// for each non-positive weight a separate index.
// for positive weights a shared value index.
typedef value_index1 value_index;
// typedef value_index2 value_index;
struct stats {
unsigned m_num_find;
unsigned m_num_insert;
@ -184,7 +285,7 @@ public:
bool find(offset_t idx, values const& vs, offset_t& found_idx) {
++m_stats.m_num_find;
if (vs.weight().is_pos()) {
return m_pos.find(idx, vs, found_idx);
return m_pos.find(idx, vs, found_idx);
}
else if (vs.weight().is_zero()) {
return m_zero.find(idx, vs, found_idx);
@ -198,12 +299,13 @@ public:
}
void reset() {
m_pos.reset();
m_neg.reset();
value_map::iterator it = m_neg.begin(), end = m_neg.end();
for (; it != end; ++it) {
it->m_value->reset();
}
m_pos.reset();
m_zero.reset();
m_neg.reset();
}
void collect_statistics(statistics& st) const {
@ -257,7 +359,7 @@ class hilbert_basis::passive {
lt m_lt;
heap<lt> m_heap; // binary heap over weights
numeral get_value(offset_t idx) const {
numeral sum_abs(offset_t idx) const {
numeral w(0);
unsigned nv = hb.get_num_vars();
for (unsigned i = 0; i < nv; ++i) {
@ -325,7 +427,6 @@ public:
iterator operator++(int) { iterator tmp = *this; ++*this; return tmp; }
bool operator==(iterator const& it) const {return m_idx == it.m_idx; }
bool operator!=(iterator const& it) const {return m_idx != it.m_idx; }
};
iterator begin() {
@ -336,42 +437,11 @@ public:
return iterator(*this, m_passive.size());
}
public:
/**
Prefer positive weights to negative.
If both weights are positive, prefer the smallest weight.
If weights are the same, prefer the one that has smallest sum of values.
*/
bool operator()(int v1, int v2) const {
offset_t idx1 = m_passive[v1];
offset_t idx2 = m_passive[v2];
return get_value(idx1) < get_value(idx2);
#if 0
values const& vec1 = hb.vec(idx1);
values const& vec2 = hb.vec(idx2);
numeral const& w1 = vec1.weight();
numeral const& w2 = vec2.weight();
SASSERT(!w1.is_zero());
SASSERT(!w2.is_zero());
if (w1.is_pos()) {
if (w2.is_neg()) {
return true;
}
if (w1 != w2) {
return w1 < w2;
}
}
else {
if (w2.is_pos()) {
return false;
}
}
SASSERT(w1 == w2);
return get_value(idx1) < get_value(idx2);
#endif
return sum_abs(idx1) < sum_abs(idx2);
}
};
hilbert_basis::hilbert_basis():
@ -420,7 +490,7 @@ void hilbert_basis::reset_statistics() {
}
void hilbert_basis::add_ge(num_vector const& v, numeral const& b) {
SASSERT(m_ineqs.empty() || v.size() + 1 == get_num_vars());
SASSERT(m_ineqs.empty() || v.size() + 1 == m_ineqs.back().size());
num_vector w;
w.push_back(-b);
w.append(v);
@ -437,7 +507,7 @@ void hilbert_basis::add_le(num_vector const& v, numeral const& b) {
}
void hilbert_basis::add_eq(num_vector const& v, numeral const& b) {
SASSERT(m_ineqs.empty() || v.size() + 1 == get_num_vars());
SASSERT(m_ineqs.empty() || v.size() + 1 == m_ineqs.back().size());
num_vector w;
w.push_back(-b);
w.append(v);
@ -474,6 +544,7 @@ unsigned hilbert_basis::get_num_vars() const {
return 0;
}
else {
SASSERT(m_ineqs.back().size() > 1);
return m_ineqs.back().size();
}
}
@ -486,8 +557,8 @@ void hilbert_basis::init_basis() {
m_basis.reset();
m_store.reset();
m_free_list.reset();
unsigned num_vars = get_num_vars();
for (unsigned i = 0; i < num_vars; ++i) {
unsigned nv = get_num_vars();
for (unsigned i = 0; i < nv; ++i) {
add_unit_vector(i, numeral(1));
}
for (unsigned i = 0; i < m_ints.size(); ++i) {
@ -595,7 +666,7 @@ void hilbert_basis::get_basis_solution(unsigned i, num_vector& v, bool& is_initi
void hilbert_basis::get_ge(unsigned i, num_vector& v, numeral& b, bool& is_eq) {
v.reset();
v.append(get_num_vars()-1, m_ineqs[i].c_ptr() + 1);
v.append(m_ineqs[i].size() - 1, m_ineqs[i].c_ptr() + 1);
b = -m_ineqs[i][0];
is_eq = m_iseq[i];
}
@ -715,9 +786,7 @@ bool hilbert_basis::can_resolve(offset_t i, offset_t j) const {
}
values const& v1 = vec(i);
values const& v2 = vec(j);
// index 0 is reserved for the constant coefficient.
// The value of it should either be 0 or 1.
if (abs(v1[0] + v2[0]) > numeral(1)) {
if (v1[0].is_one() && v2[0].is_one()) {
return false;
}
for (unsigned i = 0; i < m_ints.size(); ++i) {
@ -798,8 +867,8 @@ void hilbert_basis::display(std::ostream& out, values const& v) const {
}
void hilbert_basis::display_ineq(std::ostream& out, num_vector const& v, bool is_eq) const {
unsigned nv = get_num_vars();
for (unsigned j = 0; j < nv; ++j) {
unsigned nv = v.size();
for (unsigned j = 1; j < nv; ++j) {
if (!v[j].is_zero()) {
if (j > 0) {
if (v[j].is_pos()) {
@ -815,14 +884,14 @@ void hilbert_basis::display_ineq(std::ostream& out, num_vector const& v, bool is
if (!v[j].is_one() && !v[j].is_minus_one()) {
out << abs(v[j]) << "*";
}
out << "x" << j;
out << "x" << j;
}
}
if (is_eq) {
out << " = 0\n";
out << " = " << -v[0] << "\n";
}
else {
out << " >= 0\n";
out << " >= " << -v[0] << "\n";
}
}
@ -862,7 +931,7 @@ bool hilbert_basis::is_subsumed(offset_t i, offset_t j) const {
numeral const& m = w.weight();
bool r =
i.m_offset != j.m_offset &&
n >= m && (!m.is_nonpos() || n == m) &&
n >= m && (!m.is_neg() || n == m) &&
is_geq(v, w);
for (unsigned k = 0; r && k < m_current_ineq; ++k) {
r = get_weight(vec(i), m_ineqs[k]) >= get_weight(vec(j), m_ineqs[k]);

View file

@ -32,7 +32,8 @@ public:
typedef rational numeral;
typedef vector<numeral> num_vector;
private:
class value_index;
class value_index1;
class value_index2;
class index;
class passive;
struct offset_t {
@ -59,6 +60,7 @@ private:
numeral& operator[](unsigned i) { return m_values[i+1]; } // value of x_i
numeral const& weight() const { return m_values[0]; } // value of a*x
numeral const& operator[](unsigned i) const { return m_values[i+1]; } // value of x_i
numeral const* operator()() const { return m_values + 1; }
};
vector<num_vector> m_ineqs; // set of asserted inequalities

52
src/test/heap_trie.cpp Normal file
View file

@ -0,0 +1,52 @@
#include "heap_trie.h"
typedef heap_trie<unsigned, unsigned > heap_trie_t;
static void find_le(heap_trie_t& ht, unsigned num_keys, unsigned const* keys) {
statistics st;
vector<unsigned> vals;
ht.find_le(keys, vals);
std::cout << "find_le: ";
for (unsigned i = 0; i < num_keys; ++i) {
std::cout << keys[i] << " ";
}
std::cout << " |-> ";
for (unsigned i = 0; i < vals.size(); ++i) {
std::cout << vals[i] << " ";
}
std::cout << "\n";
ht.collect_statistics(st);
st.display(std::cout);
}
void tst_heap_trie() {
heap_trie_t ht;
ht.reset(3);
unsigned keys1[3] = { 1, 2, 3};
ht.insert(keys1, 1);
unsigned keys2[3] = { 2, 2, 3};
ht.insert(keys2, 2);
unsigned keys3[3] = { 1, 1, 3};
ht.insert(keys3, 3);
unsigned keys4[3] = { 2, 1, 3};
unsigned keys5[3] = { 2, 3, 3};
unsigned val;
VERIFY (ht.find_eq(keys1, val) && val == 1);
VERIFY (ht.find_eq(keys2, val) && val == 2);
VERIFY (ht.find_eq(keys3, val) && val == 3);
VERIFY (!ht.find_eq(keys4, val));
find_le(ht, 3, keys1);
find_le(ht, 3, keys2);
find_le(ht, 3, keys3);
find_le(ht, 3, keys4);
find_le(ht, 3, keys5);
ht.display(std::cout);
}

View file

@ -16,6 +16,18 @@ class hilbert_basis_validate {
void validate_solution(hilbert_basis& hb, vector<rational> const& v, bool is_initial);
rational eval_ineq(hilbert_basis& hb, unsigned idx, vector<rational> const& v, bool& is_eq) {
vector<rational> w;
rational bound;
hb.get_ge(idx, w, bound, is_eq);
rational sum(0);
for (unsigned j = 0; j < v.size(); ++j) {
sum += w[j]*v[j];
}
sum -= bound;
return sum;
}
public:
hilbert_basis_validate(ast_manager& m);
@ -35,37 +47,47 @@ void hilbert_basis_validate::validate_solution(hilbert_basis& hb, vector<rationa
for (unsigned i = 0; i < sz; ++i) {
bool is_eq;
vector<rational> w;
rational bound;
hb.get_ge(i, w, bound, is_eq);
rational sum(0);
for (unsigned j = 0; j < v.size(); ++j) {
sum += w[j]*v[j];
}
if (bound > sum ||
(is_eq && bound != sum)) {
// validation failed.
std::cout << "validation failed for inequality\n";
for (unsigned j = 0; j < v.size(); ++j) {
std::cout << v[j] << " ";
}
std::cout << "\n";
for (unsigned j = 0; j < w.size(); ++j) {
std::cout << w[j] << " ";
}
std::cout << (is_eq?" = ":" >= ") << bound << "\n";
std::cout << "is initial: " << (is_initial?"true":"false") << "\n";
std::cout << "sum: " << sum << "\n";
}
}
if (sum >= bound && !is_eq) {
continue;
}
if (sum == bound && is_eq) {
continue;
}
// homogeneous solutions should be non-negative.
if (!is_initial && sum.is_nonneg()) {
continue;
}
// validation failed.
std::cout << "validation failed\n";
std::cout << "constraint: ";
for (unsigned j = 0; j < v.size(); ++j) {
std::cout << v[j] << " ";
}
std::cout << (is_eq?" = ":" >= ") << bound << "\n";
std::cout << "vector: ";
for (unsigned j = 0; j < w.size(); ++j) {
std::cout << w[j] << " ";
}
std::cout << "\n";
std::cout << "sum: " << sum << "\n";
}
}
expr_ref hilbert_basis_validate::mk_validate(hilbert_basis& hb) {
arith_util a(m);
unsigned sz = hb.get_basis_size();
vector<rational> v;
bool is_initial;
// check that claimed solution really satisfies inequalities:
for (unsigned i = 0; i < sz; ++i) {
bool is_initial;
hb.get_basis_solution(i, v, is_initial);
validate_solution(hb, v, is_initial);
}
@ -83,12 +105,14 @@ expr_ref hilbert_basis_validate::mk_validate(hilbert_basis& hb) {
for (unsigned i = 0; i < sz; ++i) {
bool is_initial;
hb.get_basis_solution(i, v, is_initial);
for (unsigned j = 0; xs.size() < v.size(); ++j) {
xs.push_back(m.mk_fresh_const("x", a.mk_int()));
}
if (is_initial) {
expr_ref_vector tmp(m);
for (unsigned j = 0; j < v.size(); ++j) {
@ -200,6 +224,8 @@ static void validate_sat(hilbert_basis& hb) {
expr_ref fml = val.mk_validate(hb);
return;
std::cout << mk_pp(fml, m) << "\n";
fml = m.mk_not(fml);
@ -221,7 +247,7 @@ static void saturate_basis(hilbert_basis& hb) {
case l_true:
std::cout << "sat\n";
hb.display(std::cout);
// validate_sat(hb);
validate_sat(hb);
break;
case l_false:
std::cout << "unsat\n";
@ -449,7 +475,7 @@ static void tst11() {
// Sigma_9 table 1, Ajili, Contejean
static void tst12() {
hilbert_basis hb;
hb.add_eq(vec( 1,-2,-4,4), R(0));
hb.add_eq(vec( 1,-2,-3,4), R(0));
hb.add_le(vec(100,45,-78,-67), R(0));
saturate_basis(hb);
}
@ -465,7 +491,7 @@ static void tst13() {
static void tst14() {
hilbert_basis hb;
hb.add_eq(vec(1, 0, -4, 8), R(2));
hb.add_le(vec(12,19,-11,7), R(-7));
hb.add_le(vec(12,19,-11,-7), R(-7));
saturate_basis(hb);
}

View file

@ -208,6 +208,7 @@ int main(int argc, char ** argv) {
TST(model2expr);
TST(rcf);
TST(hilbert_basis);
TST(heap_trie);
}
void initialize_mam() {}