cgal/QP_solver/test/QP_solver/test_solver.C

618 lines
19 KiB
C

// Copyright (c) 1997-2001 ETH Zurich (Switzerland).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org); you may redistribute it under
// the terms of the Q Public License version 1.0.
// See the file LICENSE.QPL distributed with CGAL.
//
// Licensees holding a valid commercial license may use this file in
// accordance with the commercial license agreement provided with the software.
//
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//
// $URL$
// $Id$
//
//
// Author(s) : Kaspar Fischer <fischerk@inf.ethz.ch>
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <locale>
#include <cstdlib>
#include <CGAL/QP_solver/gmp_double.h>
#include <CGAL/QP_solver.h>
#include <CGAL/QP_full_exact_pricing.h>
#include <CGAL/QP_partial_exact_pricing.h>
#include <CGAL/QP_full_filtered_pricing.h>
#include <CGAL/QP_partial_filtered_pricing.h>
#include <CGAL/QP_solver/Double.h>
#include <CGAL/Gmpq.h>
#include <CGAL/Gmpz.h>
#include <CGAL/QP_solver/MPS.h> // should to into QP_solver.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.C.)
#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 Pricing_strategy_type { FE, FF, PE, PF };
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;
// The following selector is used to derive the "cheap" number-type
// used during the pricing:
template<typename ET>
struct NT_selector {};
template<>
struct NT_selector<CGAL::Gmpq> {
typedef CGAL::Gmpq NT;
};
template<>
struct NT_selector<CGAL::Double> {
typedef double NT;
};
template<>
struct NT_selector<CGAL::Gmpz> {
typedef CGAL::Gmpz NT;
};
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 CGAL::Gmpz&) { return true; }
template<typename T>
bool is_rational(const T&) { return false; }
bool is_rational(const CGAL::Gmpq&) { return true; }
template <typename T>
T string_to(const std::string& s) {
std::stringstream strm(s);
T 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('%');
CGAL_qpe_assertion(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"
<< " +s use the dedicated solver for instances whose\n"
<< " D matrix is symmetric\n"
<< " +r use the dedicated solver for instances that only\n"
<< " have equality constraints and whose coefficient\n"
<< " matrix has full row rank\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 '+s' is not specified, it will\n"
<< "check whether D is symmetric and run both, the dedicated solver\n"
<< "for symmetric and the non-dedicated solver on the instance).\n"
<< "You can also negate the options 'l', 's', 'r', 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);
Pricing_strategy_type type = FE; // to kill warnings
if (st=="fe")
type = FE;
else if (st=="ff")
type = FF;
else if (st=="pe")
type = PE;
else if (st=="pf")
type = PF;
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 == "+s")
good = options.insert(Arg("Is symmetric",1)).second;
else if (t == "-s")
good = options.insert(Arg("Is symmetric",0)).second;
else if (t == "+r")
good = options.insert(Arg("Has equalities and full rank",1)).second;
else if (t == "-r")
good = options.insert(Arg("Has equalities and full rank",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 << 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 Traits>
CGAL::QP_pricing_strategy<Traits> *
create_strategy(const std::map<std::string,int>& options)
{
Key_const_iterator it = options.find("Strategy");
CGAL::QP_pricing_strategy<Traits> *strat = 0;
typedef typename NT_selector<typename Traits::ET>::NT NT;
switch (it->second) {
case FE:
strat = new CGAL::QP_full_exact_pricing<Traits>;
break;
case FF:
strat = new CGAL::QP_full_filtered_pricing<Traits,NT>;
break;
case PE:
strat = new CGAL::QP_partial_exact_pricing<Traits>;
break;
case PF:
strat = new CGAL::QP_partial_filtered_pricing<Traits,NT>;
}
return strat;
}
// template<typename IT>
// std::string print_IT (IT t)
// {
// using std::cout;
// if (is_int(t)) return "int";
// if (is_rational(t)) return "rational";
// if (is_double(t)) return "double";
// return "unknown";
// }
template<typename Is_linear,
typename Is_symmetric,
typename Has_equalities_only_and_full_rank,
typename Is_in_standard_form,
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::QP_MPS_instance<IT,ET> QP_instance;
QP_instance qp(in,true,verbosity);
in.close();
// check whether we should compute the rank of the coefficient
// matrix:
std::string comment = qp.comment();
const bool dontComputeRank =
comment.find("dont-compute-row-rank") < comment.size();
// 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";
}
if (type==Double_type && (is_int(IT()) || is_rational(IT())) ||
type==Int_type && is_rational(IT()) ||
type==Rational_type && (is_double(IT()) || is_int(IT())))
return true;
// construct a zero D matrix if needed:
if (qp.is_linear() && !check_tag(Is_linear()))
// Note: Revision 1.1 of this file uses qp's zero_D() routine and
// a special traits class for the solver for this case. But as
// this more than doubles the compilation time, I removed it
// again...
qp.make_zero_D();
// check which properties the loaded QP has, and break if they are
// in contradiction to the routine's compile-time flags:
if (check_tag(Is_linear()) && !qp.is_linear() ||
check_tag(Is_symmetric()) && !qp.is_symmetric() ||
check_tag(Is_in_standard_form()) && !qp.is_in_standard_form() ||
check_tag(Has_equalities_only_and_full_rank()) && dontComputeRank ||
check_tag(Has_equalities_only_and_full_rank()) &&
!qp.has_equalities_only_and_full_rank())
return true;
if (verbosity > 0)
cout << "- Running a solver specialized for: "
<< (check_tag(Is_linear())? "linear " : "")
<< (check_tag(Is_symmetric())? "symmetric " : "")
<< (check_tag(Is_in_standard_form())? "standard-form " : "")
<< (check_tag(Has_equalities_only_and_full_rank())?
"has-equalities-only-and-full-rank " : "")
<< "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.error() << endl;
return false;
}
if (verbosity > 3)
cout << endl << qp;
typedef CGAL::QP_solver_MPS_traits_d<QP_instance,
Is_linear,Is_symmetric,Has_equalities_only_and_full_rank,
Is_in_standard_form,IT,ET,
typename QP_instance::D_iterator> Traits;
// temporary (todo): exit if pricing strategy is different from FE
// and nonstandard form solver is being used:
if (!check_tag(Is_in_standard_form()) &&
options.find("Strategy")->second != FE)
return true;
// solve:
CGAL::QP_pricing_strategy<Traits> *s = create_strategy<Traits>(options);
CGAL::QP_solver<Traits> solver(qp.number_of_variables(),
qp.number_of_constraints(),
qp.A(),qp.b(),qp.c(),qp.D(),
qp.row_types(),
qp.fl(),qp.l(),qp.fu(),qp.u(),
s,verbosity);
const bool is_valid = solver.is_valid();
delete s;
if (verbosity > 0 || !is_valid)
cout << " Solution is valid: " << is_valid << endl;
return is_valid;
}
template<typename Is_linear,
typename Is_symmetric,
typename Has_equalities_only_and_full_rank,
typename Is_in_standard_form>
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_symmetric,
Has_equalities_only_and_full_rank,Is_in_standard_form,
int,CGAL::Gmpz>(filename,options))
success = false;
#endif
#ifdef QP_DOUBLE
if (!processOnlyOneValue || value==Double_type)
if (!process<Is_linear,Is_symmetric,
Has_equalities_only_and_full_rank,Is_in_standard_form,
double,CGAL::Double>(filename,options))
success = false;
#endif
#ifdef QP_RATIONAL
if (!processOnlyOneValue || value==Rational_type)
if (!process<Is_linear,Is_symmetric,
Has_equalities_only_and_full_rank,Is_in_standard_form,
CGAL::Gmpq,CGAL::Gmpq>(filename,options))
success = false;
#endif
return success;
}
template<typename Is_linear,
typename Is_symmetric,
typename Has_equalities_only_and_full_rank>
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,Is_symmetric,
Has_equalities_only_and_full_rank,Tag_true>(filename,options))
success = false;
#endif
#ifdef QP_NOT_F
if (!processOnlyOneValue || value==false)
if (!processType<Is_linear,Is_symmetric,
Has_equalities_only_and_full_rank,Tag_false>(filename,options))
success = false;
#endif
return success;
}
template<typename Is_linear,
typename Is_symmetric>
bool processRFType(const std::string& filename,
const std::map<std::string,int>& options)
{
Key_const_iterator it = options.find("Has equalities and full rank");
const bool processOnlyOneValue = it != options.end();
bool value = false;
if (processOnlyOneValue)
value = it->second > 0;
bool success = true;
#ifdef QP_R
if (!processOnlyOneValue || value==true)
if (!processFType<Is_linear,Is_symmetric,Tag_true>(filename,options))
success = false;
#endif
#ifdef QP_NOT_R
if (!processOnlyOneValue || value==false)
if (!processFType<Is_linear,Is_symmetric,Tag_false>(filename,options))
success = false;
#endif
return success;
}
template<typename Is_linear>
bool processSRFType(const std::string& filename,
const std::map<std::string,int>& options)
{
Key_const_iterator it = options.find("Is symmetric");
const bool processOnlyOneValue = it != options.end();
bool value = false;
if (processOnlyOneValue)
value = it->second > 0;
bool success = true;
#ifdef QP_S
if (!processOnlyOneValue || value==true)
if (!processRFType<Is_linear,Tag_true>(filename,options))
success = false;
#endif
#ifdef QP_NOT_S
if (!processOnlyOneValue || value==false)
if (!processRFType<Is_linear,Tag_false>(filename,options))
success = false;
#endif
return success;
}
bool processLSRFType(const std::string& filename,
const std::map<std::string,int>& options)
{
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 (!processSRFType<Tag_true>(filename,options))
success = false;
#endif
#ifdef QP_NOT_L
if (!processOnlyOneValue || value==false)
if (!processSRFType<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;
// 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 (!processLSRFType(filename,options))
success = false;
// final output:
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;
}
}