mirror of https://github.com/CGAL/cgal
573 lines
18 KiB
C++
573 lines
18 KiB
C++
// Copyright (c) 1997-2001 ETH Zurich (Switzerland).
|
|
// All rights reserved.
|
|
//
|
|
// This file is part of CGAL (www.cgal.org).
|
|
//
|
|
// $URL$
|
|
// $Id$
|
|
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
|
|
//
|
|
//
|
|
// Author(s) : Kaspar Fischer <fischerk@inf.ethz.ch>
|
|
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <locale>
|
|
#include <map>
|
|
#include <cstdlib>
|
|
#include <CGAL/config.h>
|
|
|
|
#ifndef CGAL_USE_GMP
|
|
#include <CGAL/MP_Float.h> // with normalization switched on
|
|
#else
|
|
#include <CGAL/Gmpz.h>
|
|
#include <CGAL/Gmpzf.h>
|
|
#endif
|
|
#include <CGAL/Exact_rational.h>
|
|
|
|
#include <CGAL/QP_solver/QP_solver.h>
|
|
#include <CGAL/QP_solver/QP_full_exact_pricing.h>
|
|
#include <CGAL/QP_solver/QP_exact_bland_pricing.h>
|
|
#include <CGAL/QP_solver/QP_partial_exact_pricing.h>
|
|
#include <CGAL/QP_solver/QP_full_filtered_pricing.h>
|
|
#include <CGAL/QP_solver/QP_partial_filtered_pricing.h>
|
|
#include <CGAL/QP_functions.h>
|
|
#include <CGAL/QP_models.h>
|
|
|
|
// Note: The following #define's allow faster compilation for individual
|
|
// test cases. For instance, to only compile code for a solver
|
|
// specialized for linear, nonsymmetric, has-equalities-only-and-full-rank,
|
|
// and standard form with internal integer arithmetic, pass
|
|
//
|
|
// -DQP_L -DQP_NOT_S -DQP_R -DQP_F -DQP_INT
|
|
//
|
|
// to GNU g++. (Or even simpler, work with test_MPS.cpp.)
|
|
#if (!defined(QP_L) && !defined(QP_S) && !defined(QP_R) && !defined(QP_F) && \
|
|
!defined(QP_NOT_L) && !defined(QP_NOT_S) && !defined(QP_NOT_R) && \
|
|
!defined(QP_NOTF))
|
|
#define QP_L true
|
|
#define QP_S true
|
|
#define QP_R true
|
|
#define QP_F true
|
|
#define QP_NOT_L true
|
|
#define QP_NOT_S true
|
|
#define QP_NOT_R true
|
|
#define QP_NOT_F true
|
|
#endif
|
|
#if (!defined(QP_INT) && !defined(QP_DOUBLE) && !defined(QP_RATIONAL))
|
|
#define QP_INT true
|
|
#define QP_DOUBLE true
|
|
#define QP_RATIONAL true
|
|
#endif
|
|
|
|
enum Input_type { Int_type, Double_type, Rational_type };
|
|
using CGAL::Tag_true;
|
|
using CGAL::Tag_false;
|
|
typedef std::map<std::string,int>::const_iterator Key_const_iterator;
|
|
typedef std::map<std::string,int>::iterator Key_iterator;
|
|
typedef std::pair<std::string,int> Arg;
|
|
|
|
#ifdef CGAL_USE_GMP
|
|
typedef CGAL::Gmpzf Float;
|
|
typedef CGAL::Gmpz Integer;
|
|
#else
|
|
typedef CGAL::MP_Float Float;
|
|
typedef CGAL::MP_Float Integer;
|
|
//typedef CGAL::Quotient<CGAL::MP_Float> Rational; // too slow
|
|
#endif
|
|
typedef CGAL::Exact_rational Rational;
|
|
|
|
template<typename T>
|
|
bool is_double(const T&) { return false; }
|
|
bool is_double(const double&) { return true; }
|
|
template<typename T>
|
|
bool is_int(const T&) { return false; }
|
|
bool is_int(const int&) { return true; }
|
|
template<typename T>
|
|
bool is_Integer(const T&) { return false; }
|
|
bool is_Integer(const Integer&) { return true; }
|
|
template<typename T>
|
|
bool is_rational(const T&) { return false; }
|
|
bool is_rational(const Rational&) { return true; }
|
|
|
|
int string_to_int(const std::string& s) {
|
|
std::stringstream strm(s);
|
|
int t;
|
|
strm >> t;
|
|
return t;
|
|
}
|
|
|
|
// Replaces the first occurrence of '%' in msg by replacement.
|
|
std::string replace1(const std::string& msg,const std::string& replacement)
|
|
{
|
|
std::string result(msg);
|
|
const std::string::size_type pos = result.find('%');
|
|
assert(pos < result.size());
|
|
result.replace(pos,1,replacement);
|
|
return result;
|
|
}
|
|
|
|
void bailout(const char *msg)
|
|
{
|
|
std::cout << "Error: " << msg << '.' << std::endl;
|
|
std::exit(1);
|
|
}
|
|
|
|
void bailout1(const char *msg,const std::string& param)
|
|
{
|
|
std::cout << "Error: " << replace1(msg,param) << '.' << std::endl;
|
|
std::exit(1);
|
|
}
|
|
|
|
void usage()
|
|
{
|
|
using std::cout;
|
|
using std::endl;
|
|
cout << "Normal usage: ./test_solver < test_solver.cin\n"
|
|
<< "Alternative usage: ./test_solver verb strategy file [options]\n"
|
|
<< "\n"
|
|
<< "In the second form, verb is an integer (0-5), strategy is from\n"
|
|
<< "{fe,ff,pe,pf}, and file is a path to a MPS file. In addition,\n"
|
|
<< "you can specify any of the following additional options:\n"
|
|
<< " +l use the dedicated LP-solver on this instance\n"
|
|
<< " +f use the dedicated solver for instances in standard\n"
|
|
<< " form (i.e., all variables have bounds [0,+inf]).\n"
|
|
<< " int assume the numbers in the MPS file are ints and use\n"
|
|
<< " arbitrary-precision integers internally\n"
|
|
<< " double assume the numbers in the MPS file are doubles and\n"
|
|
<< " use arbitrary-precision doubles internally\n"
|
|
<< " rational assume the numbers in the MPS file are rationals\n"
|
|
<< " use arbitrary-precision rationals internally\n"
|
|
<< "If any option is not specified, the program will test all\n"
|
|
<< "possible combinations (e.g., if '+l' is not specified, it will\n"
|
|
<< "check whether the problem is linear and run both, the dedicated\n"
|
|
<< "solver for the linear case and the non-dedicated solver on the\n"
|
|
<< " instance).\n"
|
|
<< "You can also negate the options 'l'or 'f' by\n"
|
|
<< "replacing the '-' by a '+'.\n";
|
|
std::exit(1);
|
|
}
|
|
|
|
namespace Token { // A simple token reader with put-back facility.
|
|
|
|
bool have_put_back_ = false;
|
|
std::string put_back_token;
|
|
|
|
std::string token(std::istream& in)
|
|
{
|
|
if (have_put_back_) {
|
|
have_put_back_ = false;
|
|
return put_back_token;
|
|
}
|
|
std::string t;
|
|
in >> t;
|
|
return t;
|
|
}
|
|
|
|
void put_token_back(const std::string& t)
|
|
{
|
|
have_put_back_ = true;
|
|
put_back_token = t;
|
|
}
|
|
|
|
} // namespace Token
|
|
|
|
// Clears options and reads from in the arguments as specified in usage().
|
|
// Returns true iff a new set of options could be parsed; if so, filename
|
|
// receives the name of the MPS file to solve.
|
|
bool parse_options(std::istream& in,std::map<std::string,int>& options,
|
|
std::string &filename)
|
|
{
|
|
options.clear();
|
|
std::ws(in); // read white-space
|
|
if (in.eof())
|
|
return false;
|
|
|
|
// read verbosity:
|
|
std::string t = Token::token(in);
|
|
const int v = string_to_int(t);
|
|
if (v<0 || v>5)
|
|
bailout("illegal verbosity");
|
|
options.insert(Arg("Verbosity",v));
|
|
|
|
// read strategy:
|
|
std::string st = Token::token(in);
|
|
CGAL::Quadratic_program_pricing_strategy type = CGAL::QP_CHOOSE_DEFAULT;
|
|
if (st=="fe")
|
|
type = CGAL::QP_DANTZIG;
|
|
else if (st=="eb")
|
|
type = CGAL::QP_BLAND;
|
|
else if (st=="ff")
|
|
type = CGAL::QP_FILTERED_DANTZIG;
|
|
else if (st=="pe")
|
|
type = CGAL::QP_PARTIAL_DANTZIG;
|
|
else if (st=="pf")
|
|
type = CGAL::QP_PARTIAL_FILTERED_DANTZIG;
|
|
else
|
|
bailout1("illegal pricing strategy '%'",st);
|
|
options.insert(Arg("Strategy",static_cast<int>(type)));
|
|
|
|
// read file name:
|
|
in >> filename;
|
|
if (filename.size() == 0)
|
|
bailout("no filename specified");
|
|
|
|
// read additional options:
|
|
std::ws(in);
|
|
bool eof = in.eof();
|
|
t = Token::token(in);
|
|
while (!eof && !isdigit(t[0])) {
|
|
// process:
|
|
bool good;
|
|
if (t == "+l")
|
|
good = options.insert(Arg("Is linear",1)).second;
|
|
else if (t == "-l")
|
|
good = options.insert(Arg("Is linear",0)).second;
|
|
else if (t == "+f")
|
|
good = options.insert(Arg("Is in standard form",1)).second;
|
|
else if (t == "-f")
|
|
good = options.insert(Arg("Is in standard form",0)).second;
|
|
else {
|
|
Input_type input = Int_type; // to kill warnings
|
|
if (t == "int")
|
|
input = Int_type;
|
|
else if (t == "double")
|
|
input = Double_type;
|
|
else if (t == "rational")
|
|
input = Rational_type;
|
|
else
|
|
bailout1("unknown input number type '%'",t);
|
|
good = options.insert(Arg("Input type",static_cast<int>(input))).second;
|
|
}
|
|
|
|
// process duplicate errors:
|
|
if (!good)
|
|
bailout1("duplicate/contradictory option for '%'",t);
|
|
|
|
// advance:
|
|
ws(in);
|
|
eof = in.eof();
|
|
t = Token::token(in);
|
|
}
|
|
Token::put_token_back(t);
|
|
|
|
// output:
|
|
const int Width = 15;
|
|
using std::cout;
|
|
using std::left;
|
|
using std::setw;
|
|
using std::endl;
|
|
cout << endl << left << setw(Width) << "Processing:" << filename << endl;
|
|
for (Key_const_iterator it = options.begin();
|
|
it != options.end(); ++it) {
|
|
cout << " " << it->first << left << setw(Width-it->first.size()-1) << ":";
|
|
if (it-> first == "Strategy")
|
|
cout << st << endl;
|
|
else
|
|
cout << it->second << endl;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template<typename Is_linear,
|
|
typename Is_nonnegative,
|
|
typename IT,
|
|
typename ET>
|
|
bool process(const std::string& filename,
|
|
const std::map<std::string,int>& options)
|
|
{
|
|
using std::cout;
|
|
using std::endl;
|
|
|
|
// extract verbosity:
|
|
const int verbosity = options.find("Verbosity")->second;
|
|
|
|
// read QP instance
|
|
std::ifstream in(filename.c_str());
|
|
if (!in)
|
|
bailout1("could not open file '%'",filename);
|
|
typedef CGAL::Quadratic_program_from_mps <IT> QP_instance;
|
|
QP_instance qp(in);
|
|
in.close();
|
|
|
|
std::string comment = qp.get_comment();
|
|
|
|
// check for the number-type:
|
|
Input_type type;
|
|
std::string number_type;
|
|
if (comment.find("Number-type: integer") < comment.size()) {
|
|
type = Int_type;
|
|
number_type = "Gmpz";
|
|
} else if (comment.find("Number-type: rational") < comment.size()) {
|
|
type = Rational_type;
|
|
number_type = "Gmpq";
|
|
} else { // "Number-type: floating-point" or none specifier:
|
|
type = Double_type;
|
|
number_type = "double";
|
|
}
|
|
// now, some combinations of IT and the file's number-type are
|
|
// incomaptible:
|
|
// file's input type | input type IT to be used in parsing the file
|
|
// -----------------------------------------------------------------
|
|
// double | int (can't convert double to it)
|
|
// int |
|
|
// rational | int + double (can't convert rational to them)
|
|
if ((type==Double_type && is_int(IT()) ) ||
|
|
(type==Rational_type && (is_double(IT()) || is_int(IT()))))
|
|
return true;
|
|
|
|
// also, Is_linear/Is_nonnegative tags are incompatible with
|
|
// nonlinear/nonnegative programs
|
|
if ((check_tag(Is_linear()) && !qp.is_linear()) ||
|
|
(check_tag(Is_nonnegative()) && !qp.is_nonnegative()))
|
|
return true;
|
|
|
|
// finally, filtered pricing strategies are only to be used if the input
|
|
// type is double
|
|
Key_const_iterator it = options.find("Strategy");
|
|
if (!is_double(IT()) &&
|
|
(it->second == CGAL::QP_FILTERED_DANTZIG ||
|
|
it->second == CGAL::QP_PARTIAL_FILTERED_DANTZIG))
|
|
return true;
|
|
|
|
if (verbosity > 0)
|
|
cout << "- Running a solver specialized for: "
|
|
<< (check_tag(Is_linear())? "linear " : "")
|
|
<< (check_tag(Is_nonnegative())? "standard-form " : "")
|
|
<< "file-IT=" << number_type << ' '
|
|
<< "IT=" << (is_double(IT())? "double" :
|
|
(is_rational(IT())? "Gmpq" : "int")) << ' '
|
|
<< "ET=" << (is_Integer(ET())? "Gmpz" :
|
|
(is_rational(ET())? "Gmpq" : "Double"))
|
|
<< endl;
|
|
|
|
// check for format errors in MPS file:
|
|
if (!qp.is_valid()) {
|
|
cout << "Input is not a valid MPS file." << endl
|
|
<< "Error: " << qp.get_error() << endl;
|
|
return false;
|
|
}
|
|
|
|
// check name <-> index mapping
|
|
for (int j=0; j<qp.get_n(); ++j)
|
|
if (j != qp.variable_index_by_name (qp.variable_name_by_index (j))) {
|
|
cout << "incorrect name <-> index mapping (variables)" << endl;
|
|
return false;
|
|
}
|
|
for (int i=0; i<qp.get_m(); ++i)
|
|
if (i != qp.constraint_index_by_name (qp.constraint_name_by_index (i))) {
|
|
cout << "incorrect name <-> index mapping (constraints)" << endl;
|
|
return false;
|
|
}
|
|
|
|
// print program (using QMATRIX format), read it back in and check
|
|
// whether it still agrees with the original program
|
|
std::stringstream inout;
|
|
// if we have doubles, adjust precision to accommodate high-precision doubles
|
|
if (is_double(IT())) inout << std::setprecision (12);
|
|
CGAL::QP_functions_detail::print_program
|
|
(inout, qp, std::string("test_io_mps"),
|
|
Is_linear(),Is_nonnegative());
|
|
|
|
// test readers
|
|
CGAL::Quadratic_program_from_mps<IT> qp2(inout);
|
|
assert (qp2.is_valid());
|
|
if (!CGAL::QP_functions_detail::are_equal_qp (qp, qp2)) {
|
|
cout << "Warning: MPS reader (QP) and MPS writer disagree.\n" << endl;
|
|
}
|
|
|
|
// now copy from the iterators, check for equality
|
|
CGAL::Quadratic_program<IT>
|
|
qp4 (qp.get_n(), qp.get_m(), qp.get_a(), qp.get_b(), qp.get_r(),
|
|
qp.get_fl(), qp.get_l(), qp.get_fu(), qp.get_u(),
|
|
qp.get_d(), qp.get_c(), qp.get_c0());
|
|
if (!CGAL::QP_functions_detail::are_equal_qp (qp, qp4)) {
|
|
cout << "Program not correctly copied.\n" << endl;
|
|
return false;
|
|
}
|
|
// test consistency of types
|
|
if (qp.is_linear() != qp4.is_linear()) {
|
|
cout << "Program types inconsistent (linearity): "
|
|
<< qp.is_linear() << " vs. " << qp4.is_linear()
|
|
<< "\n"<< endl;
|
|
return false;
|
|
}
|
|
if (qp.is_nonnegative() != qp4.is_nonnegative()) {
|
|
cout << "Program types inconsistent (nonnegativity): "
|
|
<< qp.is_nonnegative() << " vs. " << qp4.is_nonnegative()
|
|
<< "\n"<< endl;
|
|
return false;
|
|
}
|
|
|
|
|
|
// now comes the actual, output-generating run
|
|
// make options
|
|
CGAL::Quadratic_program_options solver_options;
|
|
solver_options.set_verbosity(verbosity);
|
|
solver_options.set_pricing_strategy
|
|
(static_cast<CGAL::Quadratic_program_pricing_strategy>
|
|
(options.find("Strategy")->second));
|
|
solver_options.set_auto_validation(true);
|
|
|
|
CGAL::Quadratic_program_solution<ET> solution =
|
|
CGAL::QP_functions_detail::solve_program
|
|
(qp, ET(0), Is_linear(), Is_nonnegative(), solver_options);
|
|
// output solution + number of iterations
|
|
cout << CGAL::to_double(solution.objective_value()) << "("
|
|
<< solution.number_of_iterations() << ") ";
|
|
bool is_valid = solution.is_valid();
|
|
|
|
// the last step: solve poblem from copied QP, if full exact pricing
|
|
// and general form
|
|
if (options.find("Strategy")->second == CGAL::QP_DANTZIG &&
|
|
!check_tag(Is_linear()) &&
|
|
!check_tag(Is_nonnegative()))
|
|
{
|
|
// general form
|
|
typedef CGAL::Quadratic_program<IT>
|
|
LocalQP;
|
|
CGAL::Quadratic_program_options local_options;
|
|
local_options.set_verbosity(0);
|
|
local_options.set_pricing_strategy(CGAL::QP_DANTZIG);
|
|
local_options.set_auto_validation(true);
|
|
LocalQP qplocal (qp.get_n(), qp.get_m(), qp.get_a(), qp.get_b(),
|
|
qp.get_r(),
|
|
qp.get_fl(), qp.get_l(), qp.get_fu(), qp.get_u(),
|
|
qp.get_d(), qp.get_c(), qp.get_c0());
|
|
CGAL::solve_quadratic_program (qplocal, ET(), local_options);
|
|
std::cout << "(c) ";
|
|
}
|
|
|
|
if (verbosity > 0 || !is_valid)
|
|
cout << " Solution is valid: " << is_valid << endl;
|
|
return is_valid;
|
|
}
|
|
|
|
template<typename Is_linear,
|
|
typename Is_nonnegative>
|
|
bool processType(const std::string& filename,
|
|
const std::map<std::string,int>& options)
|
|
{
|
|
// look up value:
|
|
Key_const_iterator it = options.find("Input type");
|
|
const bool processOnlyOneValue = it != options.end();
|
|
Input_type value = Int_type;
|
|
if (processOnlyOneValue)
|
|
value = static_cast<Input_type>(it->second);
|
|
|
|
// do only this particular value or all possibilities:
|
|
bool success = true;
|
|
|
|
#ifdef QP_INT
|
|
if (!processOnlyOneValue || value==Int_type)
|
|
if (!process<Is_linear,Is_nonnegative,
|
|
int,Integer>(filename,options))
|
|
success = false;
|
|
#endif
|
|
#ifdef QP_DOUBLE
|
|
if (!processOnlyOneValue || value==Double_type)
|
|
if (!process<Is_linear,Is_nonnegative,
|
|
double,Float>(filename,options))
|
|
success = false;
|
|
#endif
|
|
#ifdef QP_RATIONAL
|
|
if (!processOnlyOneValue || value==Rational_type)
|
|
if (!process<Is_linear,Is_nonnegative,
|
|
Rational,Rational>(filename,options))
|
|
success = false;
|
|
#endif
|
|
|
|
return success;
|
|
}
|
|
|
|
template<typename Is_linear>
|
|
bool processFType(const std::string& filename,
|
|
const std::map<std::string,int>& options)
|
|
{
|
|
Key_const_iterator it = options.find("Is in standard form");
|
|
const bool processOnlyOneValue = it != options.end();
|
|
bool value = false;
|
|
if (processOnlyOneValue)
|
|
value = it->second > 0;
|
|
bool success = true;
|
|
#ifdef QP_F
|
|
if (!processOnlyOneValue || value==true)
|
|
if (!processType<Is_linear,Tag_true>(filename,options))
|
|
success = false;
|
|
#endif
|
|
#ifdef QP_NOT_F
|
|
if (!processOnlyOneValue || value==false)
|
|
if (!processType<Is_linear,Tag_false>(filename,options))
|
|
success = false;
|
|
#endif
|
|
return success;
|
|
}
|
|
|
|
bool processLFType(const std::string& filename,
|
|
const std::map<std::string,int>& options)
|
|
{
|
|
std::cout << " Solution: ";
|
|
Key_const_iterator it = options.find("Is linear");
|
|
const bool processOnlyOneValue = it != options.end();
|
|
bool value = false;
|
|
if (processOnlyOneValue)
|
|
value = it->second > 0;
|
|
bool success = true;
|
|
#ifdef QP_L
|
|
if (!processOnlyOneValue || value==true)
|
|
if (!processFType<Tag_true>(filename,options))
|
|
success = false;
|
|
#endif
|
|
#ifdef QP_NOT_L
|
|
if (!processOnlyOneValue || value==false)
|
|
if (!processFType<Tag_false>(filename,options))
|
|
success = false;
|
|
#endif
|
|
return success;
|
|
}
|
|
|
|
int main(const int ac,const char **av) {
|
|
using std::cout;
|
|
using std::endl;
|
|
using CGAL::Tag_true;
|
|
using CGAL::Tag_false;
|
|
|
|
std::cerr.precision(17);
|
|
std::cout.precision(17);
|
|
|
|
// determine in which mode we run:
|
|
const bool readFromStdIn = ac == 1;
|
|
std::stringstream args;
|
|
if (!readFromStdIn) {
|
|
if (ac < 4)
|
|
usage();
|
|
for (int i=1; i<ac; ++i) // convert av to a stream
|
|
args << ' ' << av[i];
|
|
cout << "Reading arguments from command line..." << endl;
|
|
} else
|
|
cout << "Reading from standard in..." << endl;
|
|
std::istream in(readFromStdIn? std::cin.rdbuf() : args.rdbuf());
|
|
|
|
// process input file(s):
|
|
std::map<std::string,int> options;
|
|
std::string filename;
|
|
bool success = true;
|
|
while (parse_options(in,options,filename))
|
|
if (!processLFType(filename,options))
|
|
success = false;
|
|
|
|
// final output:
|
|
cout << endl;
|
|
if (!success) {
|
|
cout << "Warning: some test cases were not handled correctly." << endl;
|
|
return 1;
|
|
} else {
|
|
cout << "All test cases were successfully passed." << endl;
|
|
return 0;
|
|
}
|
|
}
|