972 lines
34 KiB
C++
972 lines
34 KiB
C++
|
|
/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
|
|
|
/*
|
|
* Main authors:
|
|
* Guido Tack <guido.tack@monash.edu>
|
|
* Gleb Belov <gleb.belov@monash.edu>
|
|
*/
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/* This (main) file coordinates flattening and solving.
|
|
* The corresponding modules are flexibly plugged in
|
|
* as derived classes, prospectively from DLLs.
|
|
* A flattening module should provide MinZinc::GetFlattener()
|
|
* A solving module should provide an object of a class derived from SolverFactory.
|
|
* Need to get more flexible for multi-pass & multi-solving stuff TODO
|
|
*/
|
|
|
|
#ifdef _MSC_VER
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#endif
|
|
|
|
#include <minizinc/param_config.hh>
|
|
#include <minizinc/solver.hh>
|
|
|
|
#include <chrono>
|
|
#include <cstdlib>
|
|
#include <ctime>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <ratio>
|
|
|
|
#ifdef HAS_GUROBI
|
|
#include <minizinc/solvers/MIP/MIP_gurobi_solverfactory.hh>
|
|
#endif
|
|
#ifdef HAS_CPLEX
|
|
#include <minizinc/solvers/MIP/MIP_cplex_solverfactory.hh>
|
|
#endif
|
|
#ifdef HAS_OSICBC
|
|
#include <minizinc/solvers/MIP/MIP_osicbc_solverfactory.hh>
|
|
#endif
|
|
#ifdef HAS_XPRESS
|
|
#include <minizinc/solvers/MIP/MIP_xpress_solverfactory.hh>
|
|
#endif
|
|
#ifdef HAS_GECODE
|
|
#include <minizinc/solvers/gecode_solverfactory.hh>
|
|
#endif
|
|
#ifdef HAS_GEAS
|
|
#include <minizinc/solvers/geas_solverfactory.hh>
|
|
#endif
|
|
#ifdef HAS_SCIP
|
|
#include <minizinc/solvers/MIP/MIP_scip_solverfactory.hh>
|
|
#endif
|
|
#include <minizinc/solvers/fzn_solverfactory.hh>
|
|
#include <minizinc/solvers/fzn_solverinstance.hh>
|
|
#include <minizinc/solvers/mzn_solverfactory.hh>
|
|
#include <minizinc/solvers/mzn_solverinstance.hh>
|
|
#include <minizinc/solvers/nl/nl_solverfactory.hh>
|
|
#include <minizinc/solvers/nl/nl_solverinstance.hh>
|
|
|
|
using namespace std;
|
|
using namespace MiniZinc;
|
|
|
|
SolverInitialiser::SolverInitialiser() {
|
|
#ifdef HAS_GUROBI
|
|
GurobiSolverFactoryInitialiser _gurobi_init;
|
|
#endif
|
|
#ifdef HAS_CPLEX
|
|
static CplexSolverFactoryInitialiser _cplex_init;
|
|
#endif
|
|
#ifdef HAS_OSICBC
|
|
static OSICBCSolverFactoryInitialiser _osicbc_init;
|
|
#endif
|
|
#ifdef HAS_XPRESS
|
|
static XpressSolverFactoryInitialiser _xpress_init;
|
|
#endif
|
|
#ifdef HAS_GECODE
|
|
static GecodeSolverFactoryInitialiser _gecode_init;
|
|
#endif
|
|
#ifdef HAS_GEAS
|
|
static GeasSolverFactoryInitialiser _geas_init;
|
|
#endif
|
|
#ifdef HAS_SCIP
|
|
static SCIPSolverFactoryInitialiser _scip_init;
|
|
#endif
|
|
static FZNSolverFactoryInitialiser _fzn_init;
|
|
static MZNSolverFactoryInitialiser _mzn_init;
|
|
static NLSolverFactoryInitialiser _nl_init;
|
|
}
|
|
|
|
MZNFZNSolverFlag MZNFZNSolverFlag::std(const std::string& n0) {
|
|
const std::string argFlags("-I -n -p -r -n-o");
|
|
if (argFlags.find(n0) != std::string::npos) {
|
|
return MZNFZNSolverFlag(FT_ARG, n0);
|
|
}
|
|
return MZNFZNSolverFlag(FT_NOARG, n0);
|
|
}
|
|
|
|
MZNFZNSolverFlag MZNFZNSolverFlag::extra(const SolverConfig::ExtraFlag& ef) {
|
|
return MZNFZNSolverFlag(
|
|
ef.flagType == SolverConfig::ExtraFlag::FlagType::T_BOOL && ef.range.empty() ? FT_NOARG
|
|
: FT_ARG,
|
|
ef.flag);
|
|
}
|
|
|
|
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
SolverRegistry* MiniZinc::get_global_solver_registry() {
|
|
static SolverRegistry sr;
|
|
return &sr;
|
|
}
|
|
|
|
void SolverRegistry::addSolverFactory(SolverFactory* pSF) {
|
|
assert(pSF);
|
|
_sfstorage.push_back(pSF);
|
|
}
|
|
|
|
void SolverRegistry::removeSolverFactory(SolverFactory* pSF) {
|
|
auto it = find(_sfstorage.begin(), _sfstorage.end(), pSF);
|
|
assert(pSF);
|
|
_sfstorage.erase(it);
|
|
}
|
|
|
|
void SolverRegistry::addFactoryFlag(const std::string& flag, SolverFactory* sf) {
|
|
assert(sf);
|
|
_factoryFlagStorage.push_back(std::make_pair(flag, sf));
|
|
}
|
|
|
|
void SolverRegistry::removeFactoryFlag(const std::string& flag, SolverFactory* sf) {
|
|
assert(sf);
|
|
auto it = find(_factoryFlagStorage.begin(), _factoryFlagStorage.end(), std::make_pair(flag, sf));
|
|
_factoryFlagStorage.erase(it);
|
|
}
|
|
|
|
/// Function createSI also adds each SI to the local storage
|
|
SolverInstanceBase* SolverFactory::createSI(Env& env, std::ostream& log,
|
|
SolverInstanceBase::Options* opt) {
|
|
SolverInstanceBase* pSI = doCreateSI(env, log, opt);
|
|
if (pSI == nullptr) {
|
|
throw InternalError("SolverFactory: failed to initialize solver " + getDescription());
|
|
}
|
|
_sistorage.resize(_sistorage.size() + 1);
|
|
_sistorage.back().reset(pSI);
|
|
return pSI;
|
|
}
|
|
|
|
/// also providing a destroy function for a DLL or just special allocator etc.
|
|
void SolverFactory::destroySI(SolverInstanceBase* pSI) {
|
|
auto it = _sistorage.begin();
|
|
for (; it != _sistorage.end(); ++it) {
|
|
if (it->get() == pSI) {
|
|
break;
|
|
}
|
|
}
|
|
if (_sistorage.end() == it) {
|
|
cerr << " SolverFactory: failed to remove solver at " << pSI << endl;
|
|
throw InternalError(" SolverFactory: failed to remove solver");
|
|
}
|
|
_sistorage.erase(it);
|
|
}
|
|
|
|
MznSolver::MznSolver(std::ostream& os0, std::ostream& log0)
|
|
: _solverConfigs(log0),
|
|
_flt(os0, log0, _solverConfigs.mznlibDir()),
|
|
_executableName("<executable>"),
|
|
_os(os0),
|
|
_log(log0),
|
|
s2out(os0, log0, _solverConfigs.mznlibDir()) {}
|
|
|
|
MznSolver::~MznSolver() {
|
|
// if (si) // first the solver
|
|
// CleanupSolverInterface(si);
|
|
// TODO cleanup the used solver interfaces
|
|
_si = nullptr;
|
|
_siOpt = nullptr;
|
|
GC::trigger();
|
|
}
|
|
|
|
bool MznSolver::ifMzn2Fzn() const { return _isMzn2fzn; }
|
|
|
|
bool MznSolver::ifSolns2out() const { return s2out.opt.flagStandaloneSolns2Out; }
|
|
|
|
void MznSolver::addSolverInterface(SolverFactory* sf) {
|
|
_si = sf->createSI(*_flt.getEnv(), _log, _siOpt);
|
|
assert(_si);
|
|
if (s2out.getEnv() == nullptr) {
|
|
s2out.initFromEnv(_flt.getEnv());
|
|
}
|
|
_si->setSolns2Out(&s2out);
|
|
if (flagCompilerVerbose) {
|
|
_log
|
|
// << " ---------------------------------------------------------------------------\n"
|
|
<< " % SOLVING PHASE\n"
|
|
<< sf->getDescription(_siOpt) << endl;
|
|
}
|
|
}
|
|
|
|
void MznSolver::addSolverInterface() {
|
|
GCLock lock;
|
|
if (_sf == nullptr) {
|
|
if (get_global_solver_registry()->getSolverFactories().empty()) {
|
|
_log << " MznSolver: NO SOLVER FACTORIES LINKED." << endl;
|
|
assert(0);
|
|
}
|
|
_sf = get_global_solver_registry()->getSolverFactories().back();
|
|
}
|
|
addSolverInterface(_sf);
|
|
}
|
|
|
|
void MznSolver::printUsage() {
|
|
_os << _executableName << ": ";
|
|
if (ifMzn2Fzn()) {
|
|
_os << "MiniZinc to FlatZinc converter.\n"
|
|
<< "Usage: " << _executableName
|
|
<< " [<options>] [-I <include path>] <model>.mzn [<data>.dzn ...]" << std::endl;
|
|
} else if (ifSolns2out()) {
|
|
_os << "Solutions to output translator.\n"
|
|
<< "Usage: " << _executableName << " [<options>] <model>.ozn" << std::endl;
|
|
} else {
|
|
_os << "MiniZinc driver.\n"
|
|
<< "Usage: " << _executableName
|
|
<< " [<options>] [-I <include path>] <model>.mzn [<data>.dzn ...] or just <flat>.fzn"
|
|
<< std::endl;
|
|
}
|
|
}
|
|
|
|
void MznSolver::printHelp(const std::string& selectedSolver) {
|
|
printUsage();
|
|
_os << "General options:" << std::endl
|
|
<< " --help, -h\n Print this help message." << std::endl
|
|
<< " --version\n Print version information." << std::endl
|
|
<< " --solvers\n Print list of available solvers." << std::endl
|
|
<< " --time-limit <ms>\n Stop after <ms> milliseconds (includes compilation and solving)."
|
|
<< std::endl
|
|
<< " --solver <solver id>, --solver <solver config file>.msc\n Select solver to use."
|
|
<< std::endl
|
|
<< " --help <solver id>\n Print help for a particular solver." << std::endl
|
|
<< " -v, -l, --verbose\n Print progress/log statements. Note that some solvers may log "
|
|
"to "
|
|
"stdout."
|
|
<< std::endl
|
|
<< " --verbose-compilation\n Print progress/log statements for compilation." << std::endl
|
|
<< " -s, --statistics\n Print statistics." << std::endl
|
|
<< " --compiler-statistics\n Print statistics for compilation." << std::endl
|
|
<< " -c, --compile\n Compile only (do not run solver)." << std::endl
|
|
<< " --config-dirs\n Output configuration directories." << std::endl
|
|
<< " --param-file <file>\n Load parameters from the given JSON file." << std::endl;
|
|
|
|
if (selectedSolver.empty()) {
|
|
_flt.printHelp(_os);
|
|
_os << endl;
|
|
if (!ifMzn2Fzn()) {
|
|
Solns2Out::printHelp(_os);
|
|
_os << endl;
|
|
}
|
|
_os << "Available solvers (get help using --help <solver id>):" << endl;
|
|
std::vector<std::string> solvers = _solverConfigs.solvers();
|
|
if (solvers.empty()) {
|
|
cout << " none.\n";
|
|
}
|
|
for (auto& solver : solvers) {
|
|
cout << " " << solver << endl;
|
|
}
|
|
} else {
|
|
const SolverConfig& sc = _solverConfigs.config(selectedSolver);
|
|
string solverId = sc.executable().empty() ? sc.id()
|
|
: (sc.supportsMzn() ? string("org.minizinc.mzn-mzn")
|
|
: string("org.minizinc.mzn-fzn"));
|
|
bool found = false;
|
|
for (auto it = get_global_solver_registry()->getSolverFactories().rbegin();
|
|
it != get_global_solver_registry()->getSolverFactories().rend(); ++it) {
|
|
if ((*it)->getId() == solverId) {
|
|
_os << endl;
|
|
(*it)->printHelp(_os);
|
|
if (!sc.executable().empty() && !sc.extraFlags().empty()) {
|
|
_os << "Extra solver flags (use with ";
|
|
_os << (sc.supportsMzn() ? "--mzn-flags" : "--fzn-flags") << ")" << endl;
|
|
for (const SolverConfig::ExtraFlag& ef : sc.extraFlags()) {
|
|
_os << " " << ef.flag << endl << " " << ef.description << endl;
|
|
}
|
|
}
|
|
found = true;
|
|
}
|
|
}
|
|
if (!found) {
|
|
_os << "No help found for solver " << selectedSolver << endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
void add_flags(const std::string& sep, const std::vector<std::string>& in_args,
|
|
std::vector<std::string>& out_args) {
|
|
for (const std::string& arg : in_args) {
|
|
out_args.push_back(sep);
|
|
out_args.push_back(arg);
|
|
}
|
|
}
|
|
|
|
MznSolver::OptionStatus MznSolver::processOptions(std::vector<std::string>& argv) {
|
|
_executableName = argv[0];
|
|
_executableName = _executableName.substr(_executableName.find_last_of("/\\") + 1);
|
|
size_t lastdot = _executableName.find_last_of('.');
|
|
if (lastdot != std::string::npos) {
|
|
_executableName = _executableName.substr(0, lastdot);
|
|
}
|
|
string solver;
|
|
bool load_params = false;
|
|
bool mzn2fzn_exe = (_executableName == "mzn2fzn");
|
|
if (mzn2fzn_exe) {
|
|
_isMzn2fzn = true;
|
|
} else if (_executableName == "solns2out") {
|
|
s2out.opt.flagStandaloneSolns2Out = true;
|
|
flagIsSolns2out = true;
|
|
}
|
|
bool compileSolutionChecker = false;
|
|
int i = 1;
|
|
int j = 1;
|
|
int argc = static_cast<int>(argv.size());
|
|
std::vector<std::string> workingDirs = {""};
|
|
if (argc < 2) {
|
|
return OPTION_ERROR;
|
|
}
|
|
|
|
// Add params from a file if necessary
|
|
std::vector<std::string> paramFiles;
|
|
for (i = 1; i < argc; ++i) {
|
|
string paramFile;
|
|
bool usedFlag = false;
|
|
bool pushWorkingDir = true;
|
|
|
|
if (argv[i] == "--param-file") {
|
|
usedFlag = true;
|
|
++i;
|
|
if (i == argc) {
|
|
_log << "Argument required for --param-file" << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
paramFile = argv[i];
|
|
} else if (argv[i] == "--param-file-no-push") {
|
|
usedFlag = true;
|
|
pushWorkingDir = false;
|
|
++i;
|
|
if (i == argc) {
|
|
_log << "Argument required for --param-file-no-push" << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
paramFile = argv[i];
|
|
} else if (argv[i] == "--push-working-directory") {
|
|
++i;
|
|
workingDirs.push_back(argv[i]);
|
|
} else if (argv[i] == "--pop-working-directory") {
|
|
workingDirs.pop_back();
|
|
} else {
|
|
size_t last_dot = argv[i].find_last_of('.');
|
|
if (last_dot != string::npos && argv[i].substr(last_dot, string::npos) == ".mpc") {
|
|
paramFile = argv[i];
|
|
}
|
|
}
|
|
|
|
if (!paramFile.empty()) {
|
|
try {
|
|
auto paramFilePath = FileUtils::file_path(paramFile, workingDirs.back());
|
|
if (std::find(paramFiles.begin(), paramFiles.end(), paramFilePath) != paramFiles.end()) {
|
|
throw ParamException("Cyclic parameter configuration file");
|
|
}
|
|
// add parameter file arguments
|
|
ParamConfig pc;
|
|
pc.blacklist(
|
|
{"--solvers", "--solvers-json", "--solver-json", "--help", "-h", "--config-dirs"});
|
|
pc.negatedFlag("-i", "-n-i");
|
|
pc.negatedFlag("--intermediate", "--no-intermediate");
|
|
pc.negatedFlag("--intermediate-solutions", "--no-intermediate-solutions");
|
|
pc.negatedFlag("--all-satisfaction", "--disable-all-satisfaction");
|
|
pc.load(paramFilePath);
|
|
|
|
// Insert the new options
|
|
auto toInsert = pc.argv();
|
|
auto remove = argv.begin() + (usedFlag ? i - 1 : i);
|
|
auto position = argv.erase(remove, argv.begin() + i + 1);
|
|
if (pushWorkingDir) {
|
|
position =
|
|
argv.insert(position, {"--push-working-directory",
|
|
FileUtils::file_path(FileUtils::dir_name(paramFilePath))}) +
|
|
2;
|
|
}
|
|
position = argv.insert(position, toInsert.begin(), toInsert.end()) + toInsert.size();
|
|
if (pushWorkingDir) {
|
|
position = argv.insert(position, "--pop-working-directory") + 1;
|
|
}
|
|
paramFiles.push_back(paramFilePath);
|
|
argc = argv.size();
|
|
|
|
// Have to process the newly added options
|
|
if (usedFlag) {
|
|
i -= 2;
|
|
} else {
|
|
i--;
|
|
}
|
|
} catch (ParamException& e) {
|
|
_log << "Solver parameter exception: " << e.msg() << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process any registered factory flags
|
|
const auto& factoryFlags = get_global_solver_registry()->getFactoryFlags();
|
|
std::unordered_map<std::string, std::vector<std::string>> reducedSolverDefaults;
|
|
if (!factoryFlags.empty()) {
|
|
// Process solver default factory flags
|
|
for (const auto& factoryFlag : factoryFlags) {
|
|
auto factoryId = factoryFlag.second->getId();
|
|
if (reducedSolverDefaults.count(factoryId) == 0) {
|
|
reducedSolverDefaults.insert({factoryId, _solverConfigs.defaultOptions(factoryId)});
|
|
}
|
|
auto& defaultArgs = reducedSolverDefaults.find(factoryId)->second;
|
|
std::vector<std::string> keep;
|
|
for (i = 0; i < defaultArgs.size(); i++) {
|
|
if (defaultArgs[i] != factoryFlag.first ||
|
|
!factoryFlag.second->processFactoryOption(i, defaultArgs)) {
|
|
keep.push_back(defaultArgs[i]);
|
|
}
|
|
}
|
|
defaultArgs = keep;
|
|
}
|
|
// Process command line factory flags
|
|
std::vector<std::string> remaining = {argv[0]};
|
|
for (i = 1; i < argc; i++) {
|
|
bool ok = false;
|
|
if (argv[i] == "--push-working-directory") {
|
|
remaining.push_back(argv[i]);
|
|
i++;
|
|
workingDirs.push_back(argv[i]);
|
|
} else if (argv[i] == "--pop-working-directory") {
|
|
workingDirs.pop_back();
|
|
} else {
|
|
for (const auto& factoryFlag : factoryFlags) {
|
|
if (argv[i] == factoryFlag.first &&
|
|
factoryFlag.second->processFactoryOption(i, argv, workingDirs.back())) {
|
|
ok = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!ok) {
|
|
remaining.push_back(argv[i]);
|
|
}
|
|
}
|
|
argv = remaining;
|
|
argc = remaining.size();
|
|
}
|
|
for (auto* sf : get_global_solver_registry()->getSolverFactories()) {
|
|
// Notify solver factories that factory flags are done
|
|
sf->factoryOptionsFinished();
|
|
}
|
|
|
|
// After this point all solver configurations must be available
|
|
_solverConfigs.populate(_log);
|
|
|
|
for (i = 1; i < argc; ++i) {
|
|
if (argv[i] == "-h" || argv[i] == "--help") {
|
|
if (argc > i + 1) {
|
|
printHelp(argv[i + 1]);
|
|
} else {
|
|
printHelp();
|
|
}
|
|
return OPTION_FINISH;
|
|
}
|
|
if (argv[i] == "--version") {
|
|
Flattener::printVersion(cout);
|
|
return OPTION_FINISH;
|
|
}
|
|
if (argv[i] == "--solvers") {
|
|
cout << "MiniZinc driver.\nAvailable solver configurations:\n";
|
|
std::vector<std::string> solvers = _solverConfigs.solvers();
|
|
if (solvers.empty()) {
|
|
cout << " none.\n";
|
|
}
|
|
for (auto& solver : solvers) {
|
|
cout << " " << solver << endl;
|
|
}
|
|
cout << "Search path for solver configurations:\n";
|
|
for (const string& p : _solverConfigs.solverConfigsPath()) {
|
|
cout << " " << p << endl;
|
|
}
|
|
return OPTION_FINISH;
|
|
}
|
|
if (argv[i] == "--solvers-json") {
|
|
cout << _solverConfigs.solverConfigsJSON();
|
|
return OPTION_FINISH;
|
|
}
|
|
if (argv[i] == "--solver-json") {
|
|
++i;
|
|
if (i == argc) {
|
|
_log << "Argument required for --solver-json" << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
if (!solver.empty() && solver != argv[i]) {
|
|
_log << "Only one --solver-json option allowed" << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
solver = argv[i];
|
|
const SolverConfig& sc = _solverConfigs.config(solver);
|
|
cout << sc.toJSON(_solverConfigs);
|
|
return OPTION_FINISH;
|
|
}
|
|
if (argv[i] == "--config-dirs") {
|
|
GCLock lock;
|
|
cout << "{\n";
|
|
cout << " \"globalConfigFile\" : \""
|
|
<< Printer::escapeStringLit(FileUtils::global_config_file()) << "\",\n";
|
|
cout << " \"userConfigFile\" : \"" << Printer::escapeStringLit(FileUtils::user_config_file())
|
|
<< "\",\n";
|
|
cout << " \"userSolverConfigDir\" : \""
|
|
<< Printer::escapeStringLit(FileUtils::user_config_dir()) << "/solvers\",\n";
|
|
cout << " \"mznStdlibDir\" : \"" << Printer::escapeStringLit(_solverConfigs.mznlibDir())
|
|
<< "\"\n";
|
|
cout << "}\n";
|
|
return OPTION_FINISH;
|
|
}
|
|
if (argv[i] == "--time-limit") {
|
|
++i;
|
|
if (i == argc) {
|
|
_log << "Argument required for --time-limit" << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
flagOverallTimeLimit = atoi(argv[i].c_str());
|
|
} else if (argv[i] == "--solver") {
|
|
++i;
|
|
if (i == argc) {
|
|
_log << "Argument required for --solver" << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
if (!solver.empty() && solver != argv[i]) {
|
|
_log << "Only one --solver option allowed" << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
solver = argv[i];
|
|
} else if (argv[i] == "-c" || argv[i] == "--compile") {
|
|
_isMzn2fzn = true;
|
|
} else if (argv[i] == "-v" || argv[i] == "--verbose" || argv[i] == "-l") {
|
|
flagVerbose = true;
|
|
flagCompilerVerbose = true;
|
|
} else if (argv[i] == "--verbose-compilation") {
|
|
flagCompilerVerbose = true;
|
|
} else if (argv[i] == "-s" || argv[i] == "--statistics") {
|
|
flagStatistics = true;
|
|
flagCompilerStatistics = true;
|
|
} else if (argv[i] == "--compiler-statistics") {
|
|
flagCompilerStatistics = true;
|
|
} else {
|
|
if ((argv[i] == "--fzn-cmd" || argv[i] == "--flatzinc-cmd") && solver.empty()) {
|
|
solver = "org.minizinc.mzn-fzn";
|
|
}
|
|
if (argv[i] == "--compile-solution-checker") {
|
|
compileSolutionChecker = true;
|
|
}
|
|
if (argv[i] == "--ozn-file") {
|
|
flagIsSolns2out = true;
|
|
}
|
|
argv[j++] = argv[i];
|
|
}
|
|
}
|
|
argv.resize(j);
|
|
argc = j;
|
|
|
|
if ((mzn2fzn_exe || compileSolutionChecker) && solver.empty()) {
|
|
solver = "org.minizinc.mzn-fzn";
|
|
}
|
|
|
|
if (flagVerbose) {
|
|
argv.emplace_back("--verbose-solving");
|
|
argc++;
|
|
}
|
|
if (flagStatistics) {
|
|
argv.emplace_back("--solver-statistics");
|
|
argc++;
|
|
}
|
|
|
|
_flt.setFlagOutputByDefault(ifMzn2Fzn());
|
|
|
|
bool isMznMzn = false;
|
|
|
|
if (!flagIsSolns2out) {
|
|
try {
|
|
const SolverConfig& sc = _solverConfigs.config(solver);
|
|
string solverId;
|
|
if (sc.executable().empty()) {
|
|
solverId = sc.id();
|
|
} else if (sc.supportsMzn()) {
|
|
solverId = "org.minizinc.mzn-mzn";
|
|
} else if (sc.supportsFzn()) {
|
|
solverId = "org.minizinc.mzn-fzn";
|
|
} else if (sc.supportsNL()) {
|
|
solverId = "org.minizinc.mzn-nl";
|
|
} else {
|
|
_log << "Selected solver does not support MiniZinc, FlatZinc or NL input." << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
|
|
// Check support of -a and -i
|
|
for (const auto& flag : sc.stdFlags()) {
|
|
if (flag == "-a") {
|
|
_supportsA = true;
|
|
} else if (flag == "-i") {
|
|
_supportsI = true;
|
|
}
|
|
}
|
|
|
|
for (auto* it : get_global_solver_registry()->getSolverFactories()) {
|
|
if (it->getId() ==
|
|
solverId) { /// TODO: also check version (currently assumes all ids are unique)
|
|
_sf = it;
|
|
delete _siOpt;
|
|
_siOpt = _sf->createOptions();
|
|
if (!sc.executable().empty() || solverId == "org.minizinc.mzn-fzn" ||
|
|
solverId == "org.minizinc.mzn-nl") {
|
|
std::vector<MZNFZNSolverFlag> acceptedFlags;
|
|
for (const auto& sf : sc.stdFlags()) {
|
|
acceptedFlags.push_back(MZNFZNSolverFlag::std(sf));
|
|
}
|
|
for (const auto& ef : sc.extraFlags()) {
|
|
acceptedFlags.push_back(MZNFZNSolverFlag::extra(ef));
|
|
}
|
|
|
|
// Collect arguments required for underlying exe
|
|
vector<string> fzn_mzn_flags;
|
|
if (sc.needsStdlibDir()) {
|
|
fzn_mzn_flags.emplace_back("--stdlib-dir");
|
|
fzn_mzn_flags.push_back(FileUtils::share_directory());
|
|
}
|
|
if (sc.needsMznExecutable()) {
|
|
fzn_mzn_flags.emplace_back("--minizinc-exe");
|
|
fzn_mzn_flags.push_back(FileUtils::progpath() + "/" + _executableName);
|
|
}
|
|
|
|
if (sc.supportsMzn()) {
|
|
isMznMzn = true;
|
|
MZNSolverFactory::setAcceptedFlags(_siOpt, acceptedFlags);
|
|
std::vector<std::string> additionalArgs_s;
|
|
additionalArgs_s.emplace_back("-m");
|
|
if (!sc.executableResolved().empty()) {
|
|
additionalArgs_s.push_back(sc.executableResolved());
|
|
} else {
|
|
additionalArgs_s.push_back(sc.executable());
|
|
}
|
|
|
|
if (!fzn_mzn_flags.empty()) {
|
|
add_flags("--mzn-flag", fzn_mzn_flags, additionalArgs_s);
|
|
}
|
|
|
|
// This should maybe be moved to fill in fzn_mzn_flags when
|
|
// --find-muses is implemented (these arguments will be passed
|
|
// through to the subsolver of findMUS)
|
|
if (!sc.mznlib().empty()) {
|
|
if (sc.mznlib().substr(0, 2) == "-G") {
|
|
additionalArgs_s.emplace_back("--mzn-flag");
|
|
additionalArgs_s.push_back(sc.mznlib());
|
|
} else {
|
|
additionalArgs_s.emplace_back("--mzn-flag");
|
|
additionalArgs_s.emplace_back("-I");
|
|
additionalArgs_s.emplace_back("--mzn-flag");
|
|
std::string _mznlib;
|
|
if (!sc.mznlibResolved().empty()) {
|
|
_mznlib = sc.mznlibResolved();
|
|
} else {
|
|
_mznlib = sc.mznlib();
|
|
}
|
|
additionalArgs_s.push_back(_mznlib);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < additionalArgs_s.size(); ++i) {
|
|
bool success = _sf->processOption(_siOpt, i, additionalArgs_s);
|
|
if (!success) {
|
|
_log << "Solver backend " << solverId << " does not recognise option "
|
|
<< additionalArgs_s[i] << "." << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
}
|
|
} else {
|
|
// supports fzn or nl
|
|
std::vector<std::string> additionalArgs;
|
|
if (sc.supportsFzn()) {
|
|
FZNSolverFactory::setAcceptedFlags(_siOpt, acceptedFlags);
|
|
additionalArgs.emplace_back("--fzn-cmd");
|
|
} else {
|
|
// supports nl
|
|
additionalArgs.emplace_back("--nl-cmd");
|
|
}
|
|
if (!sc.executableResolved().empty()) {
|
|
additionalArgs.push_back(sc.executableResolved());
|
|
} else {
|
|
additionalArgs.push_back(sc.executable());
|
|
}
|
|
if (!fzn_mzn_flags.empty()) {
|
|
if (sc.supportsFzn()) {
|
|
add_flags("--fzn-flag", fzn_mzn_flags, additionalArgs);
|
|
} else {
|
|
add_flags("--nl-flag", fzn_mzn_flags, additionalArgs);
|
|
}
|
|
}
|
|
if (sc.needsPathsFile()) {
|
|
// Instruct flattener to hold onto paths
|
|
int i = 0;
|
|
vector<string> args{"--keep-paths"};
|
|
_flt.processOption(i, args);
|
|
|
|
// Instruct FznSolverInstance to write a path file
|
|
// and pass it to the executable with --paths arg
|
|
additionalArgs.emplace_back("--fzn-needs-paths");
|
|
}
|
|
if (!sc.needsSolns2Out()) {
|
|
additionalArgs.emplace_back("--fzn-output-passthrough");
|
|
}
|
|
int i = 0;
|
|
for (i = 0; i < additionalArgs.size(); ++i) {
|
|
bool success = _sf->processOption(_siOpt, i, additionalArgs);
|
|
if (!success) {
|
|
_log << "Solver backend " << solverId << " does not recognise option "
|
|
<< additionalArgs[i] << "." << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!sc.mznlib().empty()) {
|
|
if (sc.mznlib().substr(0, 2) == "-G") {
|
|
std::vector<std::string> additionalArgs({sc.mznlib()});
|
|
int i = 0;
|
|
if (!_flt.processOption(i, additionalArgs)) {
|
|
_log << "Flattener does not recognise option " << sc.mznlib() << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
} else {
|
|
std::vector<std::string> additionalArgs(2);
|
|
additionalArgs[0] = "-I";
|
|
if (!sc.mznlibResolved().empty()) {
|
|
additionalArgs[1] = sc.mznlibResolved();
|
|
} else {
|
|
additionalArgs[1] = sc.mznlib();
|
|
}
|
|
int i = 0;
|
|
if (!_flt.processOption(i, additionalArgs)) {
|
|
_log << "Flattener does not recognise option -I." << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
}
|
|
}
|
|
auto reducedDefaultFlags = reducedSolverDefaults.find(sc.id());
|
|
const auto& defaultFlags = reducedDefaultFlags == reducedSolverDefaults.end()
|
|
? sc.defaultFlags()
|
|
: reducedDefaultFlags->second;
|
|
if (!defaultFlags.empty()) {
|
|
std::vector<std::string> addedArgs;
|
|
addedArgs.push_back(argv[0]); // excutable name
|
|
for (const auto& df : defaultFlags) {
|
|
addedArgs.push_back(df);
|
|
}
|
|
for (int i = 1; i < argv.size(); i++) {
|
|
addedArgs.push_back(argv[i]);
|
|
}
|
|
argv = addedArgs;
|
|
argc = addedArgs.size();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
} catch (ConfigException& e) {
|
|
_log << "Config exception: " << e.msg() << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
|
|
if (_sf == nullptr) {
|
|
_log << "Solver " << solver << " not found." << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
|
|
CLOParser cop(i, argv); // For special handling of -a, -i and -n-i
|
|
for (i = 1; i < argc; ++i) {
|
|
if (argv[i] == "--push-working-directory") {
|
|
i++;
|
|
workingDirs.push_back(argv[i]);
|
|
} else if (argv[i] == "--pop-working-directory") {
|
|
workingDirs.pop_back();
|
|
} else if (!ifMzn2Fzn() ? s2out.processOption(i, argv, workingDirs.back())
|
|
: false) { // NOLINT: Allow repeated empty if
|
|
// Processed by Solns2Out
|
|
} else if ((!isMznMzn || _isMzn2fzn) &&
|
|
_flt.processOption(i, argv,
|
|
workingDirs.back())) { // NOLINT: Allow repeated empty if
|
|
// Processed by Flattener
|
|
} else if ((_supportsA || _supportsI) && cop.get("-a --all --all-solns --all-solutions")) {
|
|
_flagAllSatisfaction = true;
|
|
_flagIntermediate = true;
|
|
} else if ((_supportsA || _supportsI) &&
|
|
cop.get("-i --intermediate --intermediate-solutions")) {
|
|
_flagIntermediate = true;
|
|
} else if (cop.getOption("-n-i --no-intermediate --no-intermediate-solutions")) {
|
|
_flagIntermediate = false;
|
|
} else if (_supportsA && cop.get("--all-satisfaction")) {
|
|
_flagAllSatisfaction = true;
|
|
} else if (cop.get("--disable-all-satisfaction")) {
|
|
_flagAllSatisfaction = false;
|
|
} else if (_sf != nullptr &&
|
|
_sf->processOption(_siOpt, i, argv)) { // NOLINT: Allow repeated empty if
|
|
// Processed by Solver Factory
|
|
} else {
|
|
std::string executable_name(argv[0]);
|
|
executable_name = executable_name.substr(executable_name.find_last_of("/\\") + 1);
|
|
_log << executable_name << ": Unrecognized option or bad format `" << argv[i] << "'"
|
|
<< endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
}
|
|
return OPTION_OK;
|
|
}
|
|
for (i = 1; i < argc; ++i) {
|
|
if (argv[i] == "--push-working-directory") {
|
|
i++;
|
|
workingDirs.push_back(argv[i]);
|
|
} else if (argv[i] == "--pop-working-directory") {
|
|
workingDirs.pop_back();
|
|
} else if (s2out.processOption(i, argv, workingDirs.back())) {
|
|
// Processed by Solns2Out
|
|
} else {
|
|
std::string executable_name(argv[0]);
|
|
executable_name = executable_name.substr(executable_name.find_last_of("/\\") + 1);
|
|
_log << executable_name << ": Unrecognized option or bad format `" << argv[i] << "'" << endl;
|
|
return OPTION_ERROR;
|
|
}
|
|
}
|
|
return OPTION_OK;
|
|
}
|
|
|
|
void MznSolver::flatten(const std::string& modelString, const std::string& modelName) {
|
|
_flt.setFlagVerbose(flagCompilerVerbose);
|
|
_flt.setFlagStatistics(flagCompilerStatistics);
|
|
_flt.setFlagTimelimit(flagOverallTimeLimit);
|
|
_flt.flatten(modelString, modelName);
|
|
}
|
|
|
|
SolverInstance::Status MznSolver::solve() {
|
|
{ // To be able to clean up flatzinc after PrcessFlt()
|
|
GCLock lock;
|
|
getSI()->processFlatZinc();
|
|
}
|
|
SolverInstance::Status status = getSI()->solve();
|
|
GCLock lock;
|
|
if (!getSI()->getSolns2Out()->fStatusPrinted) {
|
|
getSI()->getSolns2Out()->evalStatus(status);
|
|
}
|
|
if (_siOpt->printStatistics) {
|
|
getSI()->printStatistics();
|
|
}
|
|
if (flagStatistics) {
|
|
getSI()->getSolns2Out()->printStatistics(_os);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
SolverInstance::Status MznSolver::run(const std::vector<std::string>& args0,
|
|
const std::string& model, const std::string& exeName,
|
|
const std::string& modelName) {
|
|
using namespace std::chrono;
|
|
steady_clock::time_point startTime = steady_clock::now();
|
|
std::vector<std::string> args = {exeName};
|
|
for (const auto& a : args0) {
|
|
args.push_back(a);
|
|
}
|
|
switch (processOptions(args)) {
|
|
case OPTION_FINISH:
|
|
return SolverInstance::NONE;
|
|
case OPTION_ERROR:
|
|
printUsage();
|
|
_os << "More info with \"" << exeName << " --help\"\n";
|
|
return SolverInstance::ERROR;
|
|
case OPTION_OK:
|
|
break;
|
|
}
|
|
if (flagIsSolns2out &&
|
|
(ifMzn2Fzn() || _sf == nullptr || _sf->getId() != "org.minizinc.mzn-mzn") &&
|
|
!_flt.hasInputFiles() && model.empty()) {
|
|
// We are in solns2out mode
|
|
while (std::cin.good()) {
|
|
string line;
|
|
getline(std::cin, line);
|
|
line += '\n'; // need eols as in t=raw stream
|
|
s2out.feedRawDataChunk(line.c_str());
|
|
}
|
|
return SolverInstance::NONE;
|
|
}
|
|
|
|
if (!ifMzn2Fzn() && _sf->getId() == "org.minizinc.mzn-mzn") {
|
|
Env env;
|
|
_si = _sf->createSI(env, _log, _siOpt);
|
|
_si->setSolns2Out(&s2out);
|
|
{ // To be able to clean up flatzinc after PrcessFlt()
|
|
GCLock lock;
|
|
_si->options()->verbose = getFlagVerbose();
|
|
_si->options()->printStatistics = getFlagStatistics();
|
|
}
|
|
_si->solve();
|
|
return SolverInstance::NONE;
|
|
}
|
|
|
|
try {
|
|
flatten(model, modelName);
|
|
} catch (Timeout&) {
|
|
s2out.evalStatus(SolverInstance::UNKNOWN);
|
|
return SolverInstance::UNKNOWN;
|
|
}
|
|
|
|
if (!ifMzn2Fzn() && flagOverallTimeLimit != 0) {
|
|
steady_clock::time_point afterFlattening = steady_clock::now();
|
|
milliseconds passed = duration_cast<milliseconds>(afterFlattening - startTime);
|
|
milliseconds time_limit(flagOverallTimeLimit);
|
|
if (passed > time_limit) {
|
|
s2out.evalStatus(getFltStatus());
|
|
return SolverInstance::UNKNOWN;
|
|
}
|
|
int time_left = (time_limit - passed).count();
|
|
std::vector<std::string> timeoutArgs(2);
|
|
timeoutArgs[0] = "--solver-time-limit";
|
|
std::ostringstream oss;
|
|
oss << time_left;
|
|
timeoutArgs[1] = oss.str();
|
|
int i = 0;
|
|
_sf->processOption(_siOpt, i, timeoutArgs);
|
|
}
|
|
|
|
if (SolverInstance::UNKNOWN == getFltStatus()) {
|
|
if (!ifMzn2Fzn()) { // only then
|
|
// Special handling of basic stdFlags
|
|
auto* solve_item = _flt.getEnv()->model()->solveItem();
|
|
bool is_sat_problem =
|
|
solve_item != nullptr ? solve_item->st() == SolveI::SolveType::ST_SAT : true;
|
|
if (is_sat_problem && _flagAllSatisfaction) {
|
|
if (_supportsA) {
|
|
std::vector<std::string> a_flag = {"-a"};
|
|
int i = 0;
|
|
_sf->processOption(_siOpt, i, a_flag);
|
|
} else {
|
|
// Solver does not support -a
|
|
_log << "WARNING: Solver does not support all solutions for satisfaction problems."
|
|
<< endl;
|
|
}
|
|
}
|
|
if (!is_sat_problem && _flagIntermediate) {
|
|
std::vector<std::string> i_flag(1);
|
|
i_flag[0] = _supportsI ? "-i" : "-a"; // Fallback to -a if -i is not supported
|
|
int i = 0;
|
|
_sf->processOption(_siOpt, i, i_flag);
|
|
}
|
|
|
|
// GCLock lock; // better locally, to enable cleanup after ProcessFlt()
|
|
addSolverInterface();
|
|
return solve();
|
|
}
|
|
return SolverInstance::NONE;
|
|
}
|
|
if (!ifMzn2Fzn()) {
|
|
s2out.evalStatus(getFltStatus());
|
|
}
|
|
return getFltStatus();
|
|
// Add evalOutput() here? TODO
|
|
}
|