Add single-row cut generator

This commit is contained in:
2017-04-28 22:15:10 -04:00
parent 43894daa81
commit 85bddc4e87
168 changed files with 8370 additions and 12 deletions

View File

@@ -0,0 +1,533 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cassert>
#include <cstdio>
#include <iostream>
#include <map>
#include <stdexcept>
#include <algorithm>
#include <ilcplex/cplex.h>
#include <qxx/rational.hpp>
#include <string>
#include <time.h>
#include <onerow/single_row_cut_generator.hpp>
#include <onerow/cplex_helper.hpp>
#include <onerow/geometry.hpp>
#include <onerow/stats.hpp>
#include <onerow/params.hpp>
using std::cout;
using std::endl;
using std::string;
static bool debug = false;
CplexHelper::CplexHelper(CPXENVptr _env, CPXLPptr _lp) :
env(_env), lp(_lp), is_integer(0), n_cuts(0), n_rows(0), ub(0), lb(0),
cstat(0), cplex_rows(0), first_solution(0), current_solution(0),
optimal_solution(0), current_round(0), n_good_rows(-1)
{
}
CplexHelper::~CplexHelper()
{
if (cplex_rows)
{
for (int i = 0; i < n_rows; i++)
{
delete[] cplex_rows[i].pi;
delete[] cplex_rows[i].indices;
}
delete[] cplex_rows;
}
if(ub) delete[] ub;
if(lb) delete[] lb;
if(cstat) delete[] cstat;
if (is_integer)
delete[] is_integer;
}
#define return_if_neq(a,b) if((a)<(b)) return true; if((a)>(b)) return false;
bool CplexRow::operator<(const CplexRow &other) const
{
return_if_neq(fabs(pi_zero-0.5), fabs(other.pi_zero-0.5));
return_if_neq(nz, other.nz);
for (int i = 0; i < nz; i++)
{
return_if_neq(indices[i], other.indices[i]);
double diff = pi[i] - other.pi[i];
if(diff < -ZERO_CUTOFF) return true;
}
return false;
}
void CplexRow::print(double *solution)
{
for (int i = 0; i < nz; i++)
{
printf("%lf x%d ", pi[i], indices[i]);
if (solution)
printf("(%lf) ", solution[indices[i]]);
}
printf("<= %lf ", pi_zero);
if (solution)
printf("violation=%lf", get_violation(solution));
printf("\n");
}
double CplexRow::get_violation(double *solution)
{
double v = 0;
for (int i = 0; i < nz; i++)
{
v += pi[i] * solution[indices[i]];
}
v -= pi_zero;
return v;
}
Constraint CplexHelper::cplex_row_to_constraint(const CplexRow &cplex_row)
{
Constraint constraint;
constraint.pi.resize(n_cols);
rational pi_zero = rational(cplex_row.pi_zero);
for (int j = 0; j < cplex_row.nz; j++)
{
int index = cplex_row.indices[j];
rational pij = rational(cplex_row.pi[j]);
if (cstat[index] == CPX_AT_LOWER)
pi_zero -= rational(lb[index]) * pij;
if (cstat[index] == CPX_AT_UPPER)
{
pi_zero -= rational(ub[index]) * pij;
pij = -pij;
}
constraint.pi.push_nz(cplex_row.indices[j], pij.reduce(REDUCE_FACTOR_COEFFICIENT));
}
constraint.pi_zero = rational(pi_zero).reduce(REDUCE_FACTOR_RHS);
return constraint;
}
CplexRow CplexHelper::constraint_to_cplex_row(const Constraint &cut)
{
CplexRow cplex_row;
cplex_row.pi = new double[n_cols];
cplex_row.indices = new int[cut.pi.nz()];
cplex_row.pi_zero = cut.pi_zero.get_double();
cplex_row.depth = cut.depth;
cplex_row.nz = cut.pi.nz();
double max_pi = -INFINITY;
double min_pi = INFINITY;
int cut_nz = cut.pi.nz();
for (int j = 0; j < cut_nz; j++)
{
int index = cut.pi.index(j);
double pij = cut.pi.value(j).get_double();
if (fabs(pij) < ZERO_CUTOFF)
pij = 0;
max_pi = std::max(max_pi, fabs(pij));
if (fabs(pij) > 0)
min_pi = std::min(min_pi, fabs(pij));
if (cstat[index] == CPX_AT_LOWER)
cplex_row.pi_zero += lb[index] * pij;
if (cstat[index] == CPX_AT_UPPER)
{
pij = -pij;
cplex_row.pi_zero += ub[index] * pij;
}
cplex_row.indices[j] = index;
cplex_row.pi[j] = pij;
}
cplex_row.dynamism = max_pi / min_pi;
return cplex_row;
}
void CplexHelper::add_cut(Constraint *cut)
{
CplexRow cplex_row = constraint_to_cplex_row(*cut);
double violation = cplex_row.get_violation(current_solution);
if (optimal_solution)
assert(cplex_row.get_violation(optimal_solution) <= MIN_CUT_VIOLATION);
if (first_solution)
assert(cplex_row.get_violation(first_solution) >= MIN_CUT_VIOLATION);
#pragma omp critical
if (cplex_row.dynamism < MAX_CUT_DYNAMISM && violation >= MIN_CUT_VIOLATION)
{
auto p = cut_buffer.insert(cplex_row);
// duplicate cut
if (!p.second)
{
delete[] cplex_row.pi;
delete[] cplex_row.indices;
}
if (cut_buffer.size() >= MAX_CUT_BUFFER_SIZE)
{
flush_cuts();
solve(false);
}
}
// rejected cut
else
{
delete[] cplex_row.pi;
delete[] cplex_row.indices;
}
delete cut;
}
void CplexHelper::flush_cuts()
{
int begin = 0;
char sense = 'L';
for (CplexRow cplex_row : cut_buffer)
{
Stats::add_cut(cplex_row.depth);
total_cuts++;
CPXaddrows(env, lp, 0, 1, cplex_row.nz, &cplex_row.pi_zero, &sense,
&begin, cplex_row.indices, cplex_row.pi, NULL, NULL);
delete[] cplex_row.pi;
delete[] cplex_row.indices;
}
cut_buffer.clear();
}
Row* CplexHelper::get_tableau_row(int index)
{
Row *row = new Row;
row->basic_var_index = cplex_rows[index].head;
row->is_integer = is_integer;
row->c = cplex_row_to_constraint(cplex_rows[index]);
if (optimal_solution)
assert(cplex_rows[index].get_violation(optimal_solution) <= 0.001);
if (debug)
{
printf("ROW %d\n", index);
cplex_rows[index].print(optimal_solution);
}
return row;
}
int CplexHelper::solve(bool should_end_round)
{
// Optimize
int status = CPXlpopt(env, lp);
if (status)
{
fprintf(stderr, "Could not optimize (%d)\n", status);
throw std::runtime_error("CplexHelper::solve_mip");
}
// Get status
char buffer[512];
double objval;
status = CPXgetstat(env, lp);
CPXgetstatstring(env, status, buffer);
CPXgetobjval(env, lp, &objval);
time_printf(" %.6lf [%s] \n", objval, buffer);
assert(status == 1);
// Store current solution
if (!current_solution)
current_solution = new double[n_cols];
CPXgetx(env, lp, current_solution, 0, n_cols - 1);
// During first round, store basis and fractional solution
if (current_round == 0)
{
read_basis();
first_solution = new double[n_cols];
for (int i = 0; i < n_cols; i++)
first_solution[i] = current_solution[i];
}
if (should_end_round)
Stats::set_solution(current_round++, objval, string(buffer));
return objval;
}
void CplexHelper::read_columns()
{
n_rows = CPXgetnumrows(env, lp);
n_cols = CPXgetnumcols(env, lp);
is_integer = new bool[n_cols];
char ctype[n_cols];
CPXgetctype(env, lp, ctype, 0, n_cols - 1);
for (int i = 0; i < n_cols; i++)
{
switch (ctype[i])
{
case CPX_BINARY:
case CPX_INTEGER:
case CPX_SEMIINT:
is_integer[i] = true;
break;
default:
is_integer[i] = false;
break;
}
}
time_printf("Fetched %d rows, %d cols.\n", n_rows, n_cols);
}
void CplexHelper::read_basis()
{
time_printf("Reading basis...\n");
ub = new double[n_cols];
lb = new double[n_cols];
cstat = new int[n_cols];
int *head = new int[n_rows];
int *rstat = new int[n_rows];
double *rhs = new double[n_rows];
CPXgetbhead(env, lp, head, rhs);
CPXgetub(env, lp, ub, 0, n_cols - 1);
CPXgetlb(env, lp, lb, 0, n_cols - 1);
CPXgetbase(env, lp, cstat, rstat);
cplex_rows = new CplexRow[n_rows];
assert(cplex_rows != 0);
eta_reset();
eta_count = 0;
eta_total = n_rows;
std::thread eta(&CplexHelper::eta_print, this);
for (int i = 0; i < n_rows; i++)
{
int nz = 0;
double pi[n_cols];
CPXbinvarow(env, lp, i, pi);
for (int j = 0; j < n_cols; j++)
{
if (fabs(pi[j]) < ZERO_CUTOFF)
continue;
if (cstat[j] == CPX_AT_LOWER)
rhs[i] += lb[j] * pi[j];
if (cstat[j] == CPX_AT_UPPER)
rhs[i] += ub[j] * pi[j];
nz++;
}
cplex_rows[i].nz = nz;
cplex_rows[i].depth = 0;
cplex_rows[i].pi = new double[nz];
cplex_rows[i].indices = new int[nz];
cplex_rows[i].pi_zero = rhs[i];
cplex_rows[i].head = head[i];
if(fabs(cplex_rows[i].pi_zero) < ZERO_CUTOFF)
cplex_rows[i].pi_zero = 0;
int k = 0;
for (int j = 0; j < n_cols; j++)
{
if (fabs(pi[j]) < ZERO_CUTOFF)
continue;
cplex_rows[i].pi[k] = pi[j];
cplex_rows[i].indices[k++] = j;
}
eta_count++;
}
eta.join();
delete[] head;
delete[] rstat;
delete[] rhs;
}
void CplexHelper::print_basis()
{
int n_rows = CPXgetnumrows(env, lp);
int n_cols = CPXgetnumcols(env, lp);
double y[n_rows];
double ub[n_cols];
double lb[n_cols];
double dj[n_cols];
CPXgetub(env, lp, ub, 0, n_cols - 1);
CPXgetlb(env, lp, lb, 0, n_cols - 1);
CPXgetdj(env, lp, dj, 0, n_cols - 1);
cout << "basis inverse:" << endl;
for (int i = 0; i < n_rows; i++)
{
CPXbinvrow(env, lp, i, y);
for (int k = 0; k < n_rows; k++)
cout << rational(y[k]) << " ";
cout << endl;
}
int cstat[n_cols];
int rstat[n_rows];
CPXgetbase(env, lp, cstat, rstat);
cout << "column status:" << endl;
for (int i = 0; i < n_cols; i++)
{
cout << i << ": " << cstat[i] << " " << lb[i] << "..." << ub[i] << " "
<< dj[i] << " " << (is_integer[i] ? "int" : "cont") << endl;
}
}
void CplexHelper::print_solution(double *x)
{
for (int i = 0; i < n_cols; i++)
if (fabs(x[i]) > ZERO_CUTOFF)
time_printf(" x%d = %.6lf\n", i, x[i]);
}
void CplexHelper::eta_reset()
{
time(&eta_start);
}
void CplexHelper::eta_print()
{
while (true)
{
sleep(ETA_UPDATE_INTERVAL);
if (eta_count == 0)
{
printf("\r\r%3.0f%% ETA: unknown\r", 0.0);
fflush(stdout);
continue;
}
if (eta_count >= eta_total)
break;
time_t eta_now;
time(&eta_now);
double diff = difftime(eta_now, eta_start);
double eta = diff / eta_count * eta_total;
time_t eta_date = eta_start + eta;
tm *ttm = localtime(&eta_date);
printf("\r%3.0f%% ", 100.0 * eta_count / eta_total);
printf("ETA: %04d-%02d-%02d %02d:%02d:%02d %d / %d\r",
ttm->tm_year + 1900, ttm->tm_mon + 1, ttm->tm_mday,
ttm->tm_hour, ttm->tm_min, ttm->tm_sec,
eta_count, eta_total);
fflush(stdout);
}
printf("\r \r");
}
void CplexHelper::find_good_rows()
{
n_good_rows = 0;
good_rows = new int[n_rows];
time_printf("Finding interesting rows...\n");
#pragma omp parallel for schedule(dynamic)
for (int i = 0; i < n_rows; i++)
{
Row *row = get_tableau_row(i);
if (row->c.pi_zero.frac() != 0 &&
row->is_integer[row->basic_var_index])
{
good_rows[n_good_rows++] = i;
}
delete row;
}
time_printf(" %d rows found\n", n_good_rows, n_rows);
}

View File

@@ -0,0 +1,77 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <onerow/geometry.hpp>
Point::Point() :
x(0), y(0)
{
}
Point::Point(rational _x, rational _y) :
x(_x), y(_y)
{
}
Point Point::operator+(const Point& p) const
{
return Point(x + p.x, y + p.y);
}
Point Point::operator-(const Point& p) const
{
return Point(x - p.x, y - p.y);
}
rational Point::operator*(const Point& p) const
{
return x * p.x + y * p.y;
}
Point Point::operator*(rational scale) const
{
return Point(scale * x, scale * y);
}
Line::Line()
{
}
Line::Line(Point _p1, Point _p2) :
p1(_p1), p2(_p2)
{
}
Line::Line(rational x1, rational y1, rational x2, rational y2) :
p1(Point(x1, y1)), p2(Point(x2, y2))
{
}
std::ostream& operator<<(std::ostream& os, const Point &p)
{
os << "(" << p.x << "," << p.y << ")";
return os;
}
std::ostream& operator<<(std::ostream& os, const Line &l)
{
os << l.p1 << "--" << l.p2;
return os;
}

View File

@@ -0,0 +1,50 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdexcept>
#include <onerow/gomory_cut_generator.hpp>
GomoryCutGenerator::GomoryCutGenerator(Row &r) :
SingleRowCutGenerator(r), finished(false)
{
}
GomoryCutGenerator::~GomoryCutGenerator()
{
}
bool GomoryCutGenerator::has_next()
{
return !finished;
}
Constraint* GomoryCutGenerator::next()
{
if (!has_next())
throw std::out_of_range("");
Constraint *cut = new Constraint;
int nz = row.c.pi.nz();
cut->pi_zero = row.c.pi_zero.floor();
cut->pi.resize(row.c.pi.size());
for (int i = 0; i < nz; i++)
cut->pi.push(row.c.pi.index(i), row.c.pi.value(i).floor());
finished = true;
return cut;
}

View File

@@ -0,0 +1,248 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdio>
#include <cstdlib>
#include <qxx/rational.hpp>
#include <qxx/dlu.hpp>
#include <onerow/knapsack2.hpp>
#include <onerow/geometry.hpp>
using std::endl;
using std::cout;
// **************************************************************************
//
// **************************************************************************
//#define DEBUG(s...) gmp_printf(s)
#define DEBUG(s...)
// **************************************************************************
//
// **************************************************************************
Knapsack2::Knapsack2()
{
}
Knapsack2::Knapsack2(rational f, rational r1)
{
eval(f, r1);
}
Knapsack2::~Knapsack2()
{
}
// **************************************************************************
//
// **************************************************************************
void Knapsack2::clear()
{
list.clear();
}
// **************************************************************************
//
// **************************************************************************
void Knapsack2::push(int side,
const q::dvec &l, const q::dvec &u, const q::dvec &o)
{
Knapsack2Vertex v;
v.side = side;
v.lower = l;
v.upper = u;
v.opposed = o;
v.lower.resize(2);
v.upper.resize(2);
v.opposed.resize(2);
list.push_back(v);
}
//int hit_count = 0;
//int miss_count = 0;
// **************************************************************************
//
// **************************************************************************
void Knapsack2::eval(rational fx, rational r1x)
{
clear();
rational fr1frac, fr1floor, hslope;
q::dvec a(3), b(3), p(3), f(3), g(3), h(3), r1(3), wy(3), wa(3), wb(3);
q::dmat w(3, 3), u(3, 3), ta(3, 3), tb(3, 3), tx(3, 3);
a[0] = 0; a[1] = 0; a[2] = 1;
b[0] = 1; b[1] = 0; b[2] = 1;
p[2] = 1;
f[0] = fx.frac(); f[1] = 0; f[2] = 1;
g[2] = 1;
h[2] = 1;
r1[0] = r1x; r1[1] = 1; r1[2] = 0;
w.set_identity();
w(0, 2) = fx.floor();
tb(0, 1) = 1; tb(0, 2) = 1; tb(1, 2) = 1;
tb(2, 0) = tb(2, 1) = tb(2, 2) = 1;
ta(0, 1) = 1; ta(1, 2) = 1;
ta(2, 0) = ta(2, 1) = ta(2, 2) = 1;
int it = 0;
wa = w * a;
DEBUG("-: A vertex (0, 0)\t\t--> v A (%Qd, %Qd)\n",
wa[0].v, wa[1].v);
wb = w * b;
DEBUG("-: B vertex (1, 0)\t\t--> v B (%Qd, %Qd)\n",
wb[0].v, wb[1].v);
while (1) {
DEBUG("%d: -----------------------------\n", it);
DEBUG("%d: f = (%Qd, %Qd, %Qd)\n",
it, f[0].v, f[1].v, f[2].v);
DEBUG("%d: r1 = (%Qd, %Qd, %Qd)\n",
it, r1[0].v, r1[1].v, r1[2].v);
DEBUG("%d: a = (%Qd, %Qd, %Qd)\n",
it, a[0].v, a[1].v, a[2].v);
DEBUG("%d: b = (%Qd, %Qd, %Qd)\n",
it, b[0].v, b[1].v, b[2].v);
// Step 1
fr1frac = (f[0] + r1[0]).frac();
fr1floor = (f[0] + r1[0]).floor();
if (f[0] == fr1frac) {
wy = w * r1;
DEBUG("%d: AB ray (%Qd, %Qd)"
"\t\t--> r AB (%Qd, %Qd)\n",
it, r1[0].v, r1[1].v, wy[0].v, wy[1].v);
push(KNAPSACK2_RAY, wa, wy, wb);
break;
} else if (f[0] < fr1frac) {
DEBUG("%d: hit right\n", it);
g[1] = (q::mpq(1) - f[0]) / (fr1frac - f[0]);
g[0] = q::mpq(1) + fr1floor * g[1];
b[1] = g[1].floor();
b[0] = q::mpq(1) + fr1floor * b[1];
p[1] = b[1] + 1;
p[0] = q::mpq(1) + fr1floor * p[1];
if (b[1] == g[1]) {
wy = w * b;
DEBUG("%d: AB vertex (%Qd, %Qd)"
"\t\t--> v AB (%Qd, %Qd)\n",
it, b[0].v, b[1].v, wy[0].v, wy[1].v);
push(KNAPSACK2_BOTH, wa, wy, wb);
break;
}
tx(0, 0) = 0;
tx(1, 0) = 0;
tx(2, 0) = 1;
tx.set_col(1, b);
tx.set_col(2, p);
u = tb * tx.inv();
//tb.dump("tb");
//tx.dump("tx");
//u.dump("u");
wy = w * b;
push(KNAPSACK2_RIGHT, wb, wy, wa);
wb = wy;
DEBUG("%d: B vertex (%Qd, %Qd)"
"\t\t--> v B (%Qd, %Qd)"
" opposed (%Qd, %Qd)\n",
it, b[0].v, b[1].v, wb[0].v, wb[1].v,
wa[0].v, wa[1].v);
hslope = b[0] / b[1];
h[1] = f[0] / (hslope - r1[0]);
h[0] = hslope * h[1];
} else {
DEBUG("%d: hit left\n", it);
g[1] = f[0] / (f[0] - fr1frac);
g[0] = fr1floor * g[1];
a[1] = g[1].floor();
a[0] = fr1floor * a[1];
p[1] = a[1] + 1;
p[0] = fr1floor * p[1];
if (a[1] == g[1]) {
wy = w * a;
DEBUG("%d: AB vertex (%Qd, %Qd)"
"\t\t--> v AB (%Qd, %Qd)\n",
it, a[0].v, a[1].v, wy[0].v, wy[1].v);
push(KNAPSACK2_BOTH, wa, wy, wb);
break;
}
tx.set_col(0, a);
tx(0, 1) = 1;
tx(1, 1) = 0;
tx(2, 1) = 1;
tx.set_col(2, p);
u = ta * tx.inv();
//tb.dump("ta");
//u.dump("u");
wy = w * a;
push(KNAPSACK2_LEFT, wa, wy, wb);
wa = wy;
DEBUG("%d: A vertex (%Qd, %Qd)"
"\t\t--> v A (%Qd, %Qd)"
" opposed (%Qd, %Qd)\n",
it, a[0].v, a[1].v, wa[0].v, wa[1].v,
wb[0].v, wb[1].v);
hslope = (a[0] - 1) / a[1];
h[1] = (f[0] - 1) / (hslope - r1[0]);
h[0] = q::mpq(1) + (hslope * h[1]);
}
a = u * a;
b = u * b;
f = u * h;
r1 = u * r1;
r1[0] /= r1[1];
r1[1] = 1;
w = w * u.inv();
it++;
}
}

169
onerow/library/src/main.cpp Normal file
View File

@@ -0,0 +1,169 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdio>
#include <cstdlib>
#include <string>
#include <unistd.h>
#include <vector>
#include <ilcplex/cplex.h>
#include "cplex_helper.hpp"
#include "cplex_helper.tpp"
#include "stats.hpp"
#include <valgrind/callgrind.h>
#include <gperftools/heap-profiler.h>
#include "gomory_cut_generator.hpp"
#include "mir_cut_generator.hpp"
#include "wedge_cut_generator.hpp"
using namespace std;
char *input_filename = 0;
char *stats_filename = 0;
char *sol_filename = 0;
bool enable_gomory_cuts = false;
bool enable_wedge_cuts = false;
bool enable_mir_cuts = false;
int c;
extern char *optarg;
char usage[] = "usage: %s [-gmw] -f model.mps [-s stats.yaml]\n";
void read_params(int argc, char **argv)
{
while ((c = getopt(argc, argv, "gwmf:s:x:")) != -1)
{
switch (c)
{
case 'g':
enable_gomory_cuts = true;
break;
case 'w':
enable_wedge_cuts = true;
break;
case 'm':
enable_mir_cuts = true;
break;
case 'f':
input_filename = optarg;
break;
case 's':
stats_filename = optarg;
break;
case 'x':
sol_filename = optarg;
break;
}
}
if (!input_filename)
{
fprintf(stderr, "%s: missing model filename\n", argv[0]);
fprintf(stderr, usage, argv[0]);
exit(1);
}
}
int main(int argc, char **argv)
{
read_params(argc, argv);
Stats::init();
int status;
CPXENVptr env = CPXopenCPLEX(&status);
CPXLPptr lp = CPXcreateprob(env, &status, "");
CplexHelper cplexHelper(env, lp);
CPXsetlogfile(env, NULL);
CPXsetintparam(env, CPX_PARAM_PREIND, CPX_OFF); // disable presolve
CPXsetintparam(env, CPX_PARAM_DATACHECK, CPX_ON); // check consistency
CPXsetintparam(env, CPX_PARAM_NUMERICALEMPHASIS, CPX_ON); // numerical precision
Stats::set_input_filename(string(input_filename));
// reads input file
time_printf("Reading input file: %s...\n", input_filename);
status = CPXreadcopyprob(env, lp, input_filename, NULL);
if (status)
{
fprintf(stderr, "could not read input file (%d)\n", status);
return 1;
}
cplexHelper.read_columns();
// read solution
if(sol_filename)
{
time_printf("Reading solution file: %s...\n", sol_filename);
FILE *f = fopen(sol_filename, "r");
if(!f)
{
fprintf(stderr, "Could not open solution file (%s).", sol_filename);
return 1;
}
double *solution = new double[cplexHelper.n_cols];
for(int i=0; i<cplexHelper.n_cols; i++)
fscanf(f, "%lf", &solution[i]);
cplexHelper.optimal_solution = solution;
}
// relaxes integrality
CPXchgprobtype(env, lp, CPXPROB_LP);
time_printf("Solving first relaxation...\n");
cplexHelper.solve(true);
if (enable_gomory_cuts)
{
time_printf("Generating Gomory cuts...\n");
cplexHelper.add_single_row_cuts<GomoryCutGenerator>();
cplexHelper.solve(true);
}
if (enable_mir_cuts)
{
time_printf("Generating MIR cuts...\n");
cplexHelper.add_single_row_cuts<MIRCutGenerator>();
cplexHelper.solve(true);
}
if (enable_wedge_cuts)
{
time_printf("Generating wedge cuts...\n");
cplexHelper.add_single_row_cuts<WedgeCutGenerator>();
cplexHelper.solve(true);
}
if(stats_filename != 0)
{
time_printf("Writting stats: %s...\n", stats_filename);
Stats::write_stats(string(stats_filename));
}
time_printf("Done.\n");
CPXcloseCPLEX(&env);
return 0;
}

View File

@@ -0,0 +1,74 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdexcept>
#include <onerow/stats.hpp>
#include <onerow/mir_cut_generator.hpp>
MIRCutGenerator::MIRCutGenerator(Row &r) :
SingleRowCutGenerator(r), finished(false)
{
}
MIRCutGenerator::~MIRCutGenerator()
{
}
bool MIRCutGenerator::has_next()
{
return !finished;
}
rational MIRCutGenerator::h(rational a)
{
if (a > 0) return a;
else return 0;
}
rational MIRCutGenerator::f(rational a, rational b)
{
if (a.frac() <= b.frac())
return b.frac() * a.floor() + a.frac();
else
return b.frac() * a.ceil();
}
Constraint* MIRCutGenerator::next()
{
if (!has_next())
throw std::out_of_range("");
Constraint *cut = new Constraint;
int nz = row.c.pi.nz();
cut->pi.resize(row.c.pi.size());
cut->pi_zero = -row.c.pi_zero.frac() * row.c.pi_zero.ceil();
for (int i = 0; i < nz; i++)
{
int idx = row.c.pi.index(i);
if (row.is_integer[idx])
cut->pi.push(idx, -f(row.c.pi.value(i), row.c.pi_zero));
else
cut->pi.push(idx, -h(row.c.pi.value(i)));
}
cut->depth = 0;
finished = true;
return cut;
}

View File

@@ -0,0 +1,72 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <onerow/single_row_cut_generator.hpp>
SingleRowCutGenerator::SingleRowCutGenerator(Row &r) : row(r)
{
}
#define return_if_neq(a,b) if((a)<(b)) return true; if((a)>(b)) return false;
bool Constraint::operator<(const Constraint &other) const
{
return_if_neq(pi.nz(), other.pi.nz());
return_if_neq(pi_zero, other.pi_zero);
int nz = pi.nz();
for (int i = 0; i < nz; i++)
{
return_if_neq(pi.index(i), other.pi.index(i));
return_if_neq(pi.value(i), other.pi.value(i));
}
return false;
}
bool Constraint::operator==(const Constraint &other) const
{
return !(operator<(other) || other.operator<(*this));
}
rational Constraint::get_violation(const rational *x)
{
rational v(0);
int nz = pi.nz();
for (int i = 0; i < nz; i++)
v += pi.value(i) * x[pi.index(i)];
v -= pi_zero;
return v;
}
std::ostream& operator<<(std::ostream& os, const Constraint &c)
{
int nz = c.pi.nz();
for (int k = 0; k < nz; k++)
{
os << c.pi.value(k) << " x" << c.pi.index(k) << " ";
}
os << "<= " << c.pi_zero;
return os;
}
std::ostream& operator<<(std::ostream& os, const Row &r)
{
os << "[" << r.basic_var_index << "] " << r.c;
return os;
}

View File

@@ -0,0 +1,226 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdio>
#include <cmath>
#include <cstdarg>
#include <cassert>
#include <sys/resource.h>
#include <sys/time.h>
#include <onerow/params.hpp>
#include <onerow/stats.hpp>
namespace Stats
{
const int MAX_ROUNDS = 100;
const int MAX_TIMERS = 100;
string input_filename;
double opt_value[MAX_ROUNDS];
string opt_sol_status[MAX_ROUNDS];
unsigned long n_cuts_total = 0;
unsigned long n_cuts_depth[MAX_CUT_DEPTH] = { 0 };
unsigned long n_generated_cuts_total = 0;
unsigned long n_generated_cuts_round[MAX_CUT_DEPTH] = { 0 };
unsigned long n_generated_cuts_depth[MAX_CUT_DEPTH] = { 0 };
unsigned long trivial_lifting_m_count = 0;
unsigned long trivial_lifting_m_sum = 0;
unsigned long trivial_lifting_m_max = 0;
unsigned long n_coefficients = 0;
unsigned long n_integral_coefficients = 0;
int n_timers = 0;
double current_timer_start;
double timers[MAX_TIMERS] = {0};
void init()
{
input_filename = "";
for (int i = 0; i < MAX_ROUNDS; i++)
{
opt_value[i] = INFINITY;
opt_sol_status[i] = "";
}
}
void add_cut(int depth)
{
n_cuts_total++;
n_cuts_depth[depth]++;
}
void add_generated_cut(int round, int depth)
{
n_generated_cuts_total++;
n_generated_cuts_round[round]++;
n_generated_cuts_depth[depth]++;
}
void add_trivial_lifting_m(unsigned long m)
{
trivial_lifting_m_count++;
trivial_lifting_m_sum += m;
trivial_lifting_m_max = std::max(trivial_lifting_m_max, m);
}
void add_coefficient(bool integral)
{
n_coefficients++;
if(integral) n_integral_coefficients++;
}
void set_solution(int round, double sol, string status)
{
opt_value[round] = sol;
opt_sol_status[round] = status;
}
void set_input_filename(string n)
{
input_filename = n;
}
void write_stats(string f)
{
FILE *out = fopen(f.c_str(), "w");
fprintf(out, "input_file:\n %s\n", input_filename.c_str());
// solution value and status
fprintf(out, "sol_value:\n");
for (int i = 0; i < MAX_ROUNDS; i++)
if (opt_value[i] < INFINITY)
fprintf(out, " %d: %-.6f\n", i, opt_value[i]);
fprintf(out, "sol_status:\n");
for (int i = 0; i < MAX_ROUNDS; i++)
if (opt_value[i] < INFINITY)
fprintf(out, " %d: %s\n", i, opt_sol_status[i].c_str());
// added cuts
if(n_cuts_total > 0)
{
fprintf(out, "n_added_cuts:\n");
fprintf(out, " total:\n %ld\n", n_cuts_total);
fprintf(out, " depth:\n");
for (int i = 0; i < MAX_CUT_DEPTH; i++)
if (n_cuts_depth[i] > 0)
fprintf(out, " %d: %ld\n", i, n_cuts_depth[i]);
}
// generated cuts
if(n_generated_cuts_total > 0)
{
fprintf(out, "n_generated_cuts:\n");
fprintf(out, " total:\n %ld\n", n_generated_cuts_total);
fprintf(out, " round:\n");
for (int i = 0; i < MAX_ROUNDS; i++)
if (n_generated_cuts_round[i] > 0)
fprintf(out, " %d: %ld\n", i, n_generated_cuts_round[i]);
fprintf(out, " depth:\n");
for (int i = 0; i < MAX_CUT_DEPTH; i++)
if (n_generated_cuts_depth[i] > 0)
fprintf(out, " %d: %ld\n", i, n_generated_cuts_depth[i]);
}
// trivial lifting
if(trivial_lifting_m_count > 0)
{
double integral_coefficients = ((double) n_integral_coefficients) / n_coefficients;
double average_m = ((double) (trivial_lifting_m_sum))
/ trivial_lifting_m_count;
double slowdown = integral_coefficients * average_m;
fprintf(out, "trivial_lifting:\n");
fprintf(out, " max_m: %ld\n", trivial_lifting_m_max);
fprintf(out, " average_m: %.6lf\n", average_m);
fprintf(out, " integral_coefficients: %.6lf\n", integral_coefficients);
fprintf(out, " slowdown: %.6lf\n", slowdown);
}
if(n_timers > 0)
{
fprintf(out, "timers:\n");
for(int i=0; i<n_timers; i++)
fprintf(out, " %d: %.4lf\n", i+1, timers[i]);
}
fprintf(out, "cut_speed:\n");
for(int i=0; i<n_timers; i++)
fprintf(out, " round_%d: %.4lf\n", i+1, timers[i] / n_generated_cuts_round[i+1]);
}
void start_timer()
{
assert(current_timer_start == 0);
time_printf("Starting timer %d...\n", n_timers+1);
current_timer_start = get_current_time();
timers[n_timers] = 0;
}
void end_timer()
{
timers[n_timers] = get_current_time() - current_timer_start;
time_printf("Ending timer %d: %.2lfs\n ", n_timers+1, timers[n_timers]);
current_timer_start = 0;
n_timers++;
}
}
double get_current_time(void)
{
struct rusage ru;
getrusage (RUSAGE_SELF, &ru);
return ((double) ru.ru_utime.tv_sec) +
((double) ru.ru_utime.tv_usec)/1000001.0;
}
double initial_time = 0;
void time_printf(const char *fmt, ...)
{
if(initial_time == 0)
initial_time = get_current_time();
printf("[%10.2lf] ", get_current_time() - initial_time);
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
fflush(stdout);
}

View File

@@ -0,0 +1,390 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdexcept>
#include <cassert>
#include <cmath>
#include <algorithm>
#include <set>
#include <gmp.h>
#include <onerow/wedge_cut_generator.hpp>
#include <onerow/stats.hpp>
#include <onerow/params.hpp>
using std::cout;
using std::endl;
using std::min;
static bool debug = false;
IntersectionCut::IntersectionCut(Point _f, int _n, const Line *l) :
f(_f), n_faces(_n), pre_lifting_ready(false)
{
d = new Point[n_faces];
if (l != 0)
for (int i = 0; i < n_faces; i++)
set_face(i, l[i]);
}
IntersectionCut::~IntersectionCut()
{
delete[] d;
}
void IntersectionCut::set_face(int index, Line line)
{
if (index < 0 || index >= n_faces)
throw std::out_of_range("");
rational x1 = line.p1.x;
rational y1 = line.p1.y;
rational x2 = line.p2.x;
rational y2 = line.p2.y;
rational h[3] = { -y2 + y1, x2 - x1, x1 * y2 - x2 * y1 };
rational rhs = -(h[2] + h[0] * f.x + h[1] * f.y);
if (rhs == rational(0))
throw std::invalid_argument("");
d[index].x = h[0] / rhs;
d[index].y = h[1] / rhs;
}
rational IntersectionCut::get_continuous_coefficient(rational rx, rational ry)
{
#ifdef ENABLE_EXTENDED_STATISTICS
Stats::add_coefficient(false);
#endif
rational max_coeff(-100000, 1);
for (int i = 0; i < n_faces; i++)
{
rational coeff = rx * d[i].x + ry * d[i].y;
if (coeff > max_coeff)
max_coeff = coeff;
}
return max_coeff;
}
void IntersectionCut::pre_lifting()
{
rational r0y;
rational fx = f.x, fy = f.y;
rational d0x = d[0].x, d0y = d[0].y;
rational d1x = d[1].x, d1y = d[1].y;
rational apex_x, apex_y;
rational w1 = rational(1) + (d0x * fx + d0y * fy);
rational w2 = rational(1) + (d1x * fx + d1y * fy);
rational m = (d0x*d1y-d0y*d1x);
if (m == 0)
{
r0x = -d0y/d0x;
r0y = 1;
}
else
{
apex_x = (d1y * w1 - d0y * w2) / m;
apex_y = (-d1x * w1 + d0x * w2) / m;
r0x = apex_x - fx;
r0y = apex_y - fy;
r0x /= r0y;
r0y = 1;
}
assert(d0x < 0);
assert(d1x > 0);
p = r0x*d0x + r0y*d0y;
assert(p == r0x*d1x + r0y*d1y);
assert(p >= 0);
this->d_p = p.get_double();
this->d_d1x = d1x.get_double();
this->d_d1y = d1y.get_double();
this->d_d0x = d0x.get_double();
this->d_d0y = d0y.get_double();
this->d_r0x = r0x.get_double();
pre_lifting_ready = true;
}
double IntersectionCut::get_trivial_lifting_coefficient_double(double rx, double ry)
{
if(!pre_lifting_ready) pre_lifting();
#ifdef ENABLE_EXTENDED_STATISTICS
Stats::add_coefficient(true);
#endif
double a = 100000;
unsigned long k2 = 0;
unsigned long M = 10000;
while (k2 < M)
{
double b = (k2*d_r0x-rx);
double a1 = d_d1x * (rx + ceil(b)) + d_d1y * k2;
double a2 = d_d0x * (rx + floor(b)) + d_d0y * k2;
assert(rx + ceil(b) + 0.0001 >= d_r0x*k2);
assert(rx + floor(b) - 0.0001 <= d_r0x*k2);
assert(a1 >= 0);
assert(a2 >= 0);
if(a1 < a) a = a1;
if(a2 < a) a = a2;
if(fabs(d_p) < ZERO_CUTOFF) break;
M = ceil(a/d_p);
k2++;
}
#ifdef ENABLE_EXTENDED_STATISTICS
Stats::add_trivial_lifting_m(k2);
#endif
assert(a >= 0);
return a;
}
rational IntersectionCut::get_trivial_lifting_coefficient(rational rx, rational ry)
{
if(!pre_lifting_ready) pre_lifting();
#ifdef ENABLE_EXTENDED_STATISTICS
Stats::add_coefficient(true);
#endif
rational a = rational(100000, 1);
unsigned long k2 = 0;
unsigned long M = 10000;
rational b = -rx;
while (k2 < M)
{
rational r_k2((signed) k2);
rational a1 = d[1].x*(rx + b.ceil()) + d[1].y * r_k2;
rational a2 = d[0].x*(rx + b.floor()) + d[0].y * r_k2;
assert(rx + b.ceil() >= r0x*r_k2);
assert(rx + b.floor() <= r0x*r_k2);
assert(a1 >= 0);
assert(a2 >= 0);
if(a1 < a) a = a1;
if(a2 < a) a = a2;
if(p == 0) break;
M = (a/p).ceil().get_double();
k2++;
b += r0x;
}
return a;
}
WedgeCut::WedgeCut(Point _f, Point left, Point apex, Point right) :
IntersectionCut(_f, 2)
{
set_face(0, Line(left, apex));
set_face(1, Line(apex, right));
}
SplitCut::SplitCut(Point _f, Point left, Point right, Point direction) :
IntersectionCut(_f, 2)
{
set_face(0, Line(left, left + direction));
set_face(1, Line(right, right + direction));
}
WedgeCutGenerator::WedgeCutGenerator(Row &r) :
SingleRowCutGenerator(r), finished(false),
f(2), r1(2),
r1_offset(-1),
cur_facet(-1),
n_knapsacks(0)
{
f[0] = r.c.pi_zero.frac();
f[1] = 0;
cut = new Constraint;
eval_next();
}
WedgeCutGenerator::~WedgeCutGenerator()
{
delete cut;
}
bool WedgeCutGenerator::has_next()
{
return !finished;
}
Constraint* WedgeCutGenerator::next()
{
if (!has_next())
throw std::out_of_range("");
Constraint *old_cut = cut;
cut = new Constraint;
eval_next();
return(old_cut);
}
q::dvec WedgeCutGenerator::intersection(
q::dvec a, q::dvec b, q::dvec c, q::dvec d)
{
q::dmat g(2,2);
g.set_col(0, b - a);
g.set_col(1, c - d);
q::dvec mult = g.inv() * (c - a);
q::dvec x = a + (b - a) * mult[0];
assert(x == c + (d - c) * mult[1]);
return(x);
}
void WedgeCutGenerator::eval_next()
{
while (true)
{
if (0 <= cur_facet && cur_facet < (int) knapsack.list.size() && cur_facet <= MAX_CUT_DEPTH)
break;
r1_offset++;
if (r1_offset >= row.c.pi.nz())
{
finished = true;
return;
}
int r1_index = row.c.pi.index(r1_offset);
r1[0] = -row.c.pi.value(r1_offset).reduce(REDUCE_FACTOR_R1);
r1[1] = 1;
if (r1_index == row.basic_var_index)
continue;
if (!row.is_integer[r1_index])
continue;
if (r1[0] == 0)
continue;
knapsack.clear();
knapsack.eval(f[0], r1[0]);
n_knapsacks++;
cur_facet = 0;
}
Line lines[2];
int side = knapsack.list[cur_facet].side;
q::dvec &a1 = knapsack.list[cur_facet].lower;
q::dvec &a2 = knapsack.list[cur_facet].upper;
q::dvec &o = knapsack.list[cur_facet].opposed;
if (side == KNAPSACK2_RAY)
{
a2 = o;
q::dvec b1 = a1 + r1;
q::dvec b2 = a2 + r1;
lines[0] = Line(a1[0], a1[1], b1[0], b1[1]);
lines[1] = Line(a2[0], a2[1], b2[0], b2[1]);
}
else
{
q::dvec apex = intersection(a1, a2, f, f + r1);
if(side == KNAPSACK2_RIGHT)
{
lines[1] = Line(a1[0], a1[1], a2[0], a2[1]);
lines[0] = Line(o[0], o[1], apex[0], apex[1]);
}
else
{
lines[0] = Line(a1[0], a1[1], a2[0], a2[1]);
lines[1] = Line(o[0], o[1], apex[0], apex[1]);
}
}
if(debug)
cout << endl << lines[0] << " " << lines[1] << endl;
cur_facet++;
IntersectionCut ic(Point(f[0], f[1]), 2, lines);
cut->pi.clear();
cut->pi_zero = -1;
cut->pi.resize(row.c.pi.size());
cut->depth = cur_facet-1;
Point ray;
rational alpha, rx, ry;
for (int l = 0; l < row.c.pi.nz(); l++)
{
int j = row.c.pi.index(l);
if (j == row.basic_var_index)
continue;
rx = -row.c.pi.value(l);
ry = (l == r1_offset ? 1 : 0);
if (row.is_integer[j] && l != r1_offset)
{
#ifdef INTERSECTION_CUT_USE_DOUBLE
cut->pi.push(j, -ic.get_trivial_lifting_coefficient_double(
rx.get_double(), ry.get_double()));
#else
cut->pi.push(j, -ic.get_trivial_lifting_coefficient(rx, ry));
#endif
}
else
{
cut->pi.push(j, -ic.get_continuous_coefficient(rx, ry));
}
if(debug)
cout << "r" << j << ": (" << rx << ", " << ry << ") " << cut->pi[j] << endl;
}
}