1
0
This repository has been archived on 2025-03-06. You can view files and clone it, but cannot push or open issues or pull requests.

312 lines
9.9 KiB
C++

/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/* 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/. */
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <minizinc/file_utils.hh>
#include <minizinc/process.hh>
#include <minizinc/solvers/nl/nl_file.hh>
#include <minizinc/solvers/nl/nl_solreader.hh>
#include <minizinc/solvers/nl/nl_solverinstance.hh>
#include <cstdio>
#include <cstring>
#include <fstream>
using namespace std;
namespace MiniZinc {
/** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
* **/
// Solver Factory
NLSolverFactory::NLSolverFactory() {
SolverConfig sc("org.minizinc.mzn-nl",
MZN_VERSION_MAJOR "." MZN_VERSION_MINOR "." MZN_VERSION_PATCH);
sc.name("Generic Non Linear driver");
sc.mznlibVersion(1);
sc.description("MiniZinc generic Non Linear solver plugin");
sc.supportsFzn(false);
sc.supportsNL(true);
sc.requiredFlags({"--nl-cmd"});
sc.tags({"__internal__"});
SolverConfigs::registerBuiltinSolver(sc);
}
string NLSolverFactory::getDescription(SolverInstanceBase::Options* /*opt*/) {
string v = "NL solver plugin, compiled " __DATE__ " " __TIME__;
return v;
}
string NLSolverFactory::getVersion(SolverInstanceBase::Options* /*opt*/) {
return MZN_VERSION_MAJOR;
}
string NLSolverFactory::getId() { return "org.minizinc.mzn-nl"; }
void NLSolverFactory::printHelp(ostream& os) {
os << "MZN-NL plugin options" << std::endl
<< " --nl-cmd , --nonlinear-cmd <exe>\n The backend solver filename.\n"
<< " --nl-flags <options>, --backend-flags <options>\n"
" Specify option to be passed to the NL solver.\n"
<< " --nl-flag <option>, --backend-flag <option>\n"
" As above, but for a single option string that needs to be quoted in a shell.\n"
<< " --hexafloat\n Use hexadecimal format when communicating floating points with the "
"solver.\n"
<< " --keepfile\n Write the nl and sol files next to the input file and don't remove "
"them.\n"
// << " --nl-sigint\n Send SIGINT instead of SIGTERM.\n"
// << " -t <ms>, --solver-time-limit <ms>, --fzn-time-limit <ms>\n Set time limit (in
// milliseconds) for solving.\n"
// << " -p <n>, --parallel <n>\n Use <n> threads during search. The default is
// solver-dependent.\n"
// << " -r <n>, --seed <n>, --random-seed <n>\n For compatibility only: use solver flags
// instead.\n"
;
}
SolverInstanceBase::Options* NLSolverFactory::createOptions() { return new NLSolverOptions; }
SolverInstanceBase* NLSolverFactory::doCreateSI(Env& env, std::ostream& log,
SolverInstanceBase::Options* opt) {
return new NLSolverInstance(env, log, opt);
}
bool NLSolverFactory::processOption(SolverInstanceBase::Options* opt, int& i,
std::vector<std::string>& argv, const std::string& workingDir) {
auto& _opt = static_cast<NLSolverOptions&>(*opt);
CLOParser cop(i, argv);
string buffer;
int nn = -1;
if (cop.getOption("--nl-cmd --nonlinear-cmd", &buffer)) {
_opt.nlSolver = buffer;
} else if (cop.getOption("--hexafloat")) {
_opt.doHexafloat = true;
} else if (cop.getOption("--nl-flags --backend-flags", &buffer)) {
auto args = FileUtils::parse_cmd_line(buffer);
for (const auto& arg : args) {
_opt.nlFlags.push_back(arg);
}
} else if (cop.getOption("--nl-flag --backend-flag", &buffer)) {
_opt.nlFlags.push_back(buffer);
} else if (cop.getOption("--keepfile")) {
_opt.doKeepfile = true;
} else if (cop.getOption("-s --solver-statistics")) {
// ignore statistics flags for now
} else if (cop.getOption("-v --verbose-solving")) {
_opt.verbose = true;
} else {
for (auto& fznf : _opt.nlSolverFlags) {
if (fznf.t == MZNFZNSolverFlag::FT_ARG && cop.getOption(fznf.n.c_str(), &buffer)) {
_opt.nlFlags.push_back(fznf.n);
_opt.nlFlags.push_back(buffer);
return true;
}
if (fznf.t == MZNFZNSolverFlag::FT_NOARG && cop.getOption(fznf.n.c_str())) {
_opt.nlFlags.push_back(fznf.n);
return true;
}
}
return false;
}
return true;
}
/** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
* **/
// Solver Instance
NLSolverInstance::NLSolverInstance(Env& env, std::ostream& log,
SolverInstanceBase::Options* options)
: SolverInstanceBase(env, log, options), _fzn(env.flat()), _ozn(env.output()) {}
NLSolverInstance::~NLSolverInstance() {}
void NLSolverInstance::processFlatZinc() {}
void NLSolverInstance::resetSolver() {}
SolverInstance::Status NLSolverInstance::solve() {
// Get the options
auto& opt = static_cast<NLSolverOptions&>(*_options);
// --- --- --- Prepare the files
string file_nl; // Output for the NL, will be the input for the solver
string file_sol; // Ouput of the solver
FileUtils::TmpDir* tmpdir = nullptr;
if (opt.doKeepfile) {
// Keep file: output next to original file
ASTString file_mzn = _env.envi().originalModel != nullptr
? _env.envi().originalModel->filepath()
: _env.envi().model->filepath();
string file_base = std::string(file_mzn.substr(0, file_mzn.findLastOf('.')));
file_nl = file_base + ".nl";
file_sol = file_base + ".sol";
} else {
// Don't keep file: create a temp directory
tmpdir = new FileUtils::TmpDir();
file_nl = tmpdir->name() + "/model.nl";
file_sol = tmpdir->name() + "/model.sol";
}
std::ofstream outfile(FILE_PATH(file_nl));
// Configure floating point output
if (opt.doHexafloat) {
outfile << hexfloat;
} else {
outfile.precision(numeric_limits<double>::digits10 + 2);
}
// --- --- --- Result of the try/catch block
// Use to talk back to minizinc
auto* out = getSolns2Out();
// Manage status
int exitStatus = -1;
// --- --- --- All the NL operations in one try/catch
try {
// --- --- --- Create the NL data
// Analyse the variable declarations
for (VarDeclIterator it = _fzn->vardecls().begin(); it != _fzn->vardecls().end(); ++it) {
if (!it->removed()) {
Item& item = *it;
analyse(&item);
}
}
// Analyse the contraints
for (ConstraintIterator it = _fzn->constraints().begin(); it != _fzn->constraints().end();
++it) {
if (!it->removed()) {
Item& item = *it;
analyse(&item);
}
}
// Analyse the goal
analyse(_fzn->solveItem());
// Phase 2
_nlFile.phase2();
// Print to the files
_nlFile.printToStream(outfile);
// --- --- --- Call the solver
NLSolns2Out s2o = NLSolns2Out(out, _nlFile, opt.verbose);
vector<string> cmd_line;
if (opt.nlSolver.empty()) {
delete tmpdir;
tmpdir = nullptr;
outfile.close();
throw InternalError("No NL solver specified");
}
cmd_line.push_back(opt.nlSolver);
cmd_line.push_back(file_nl);
cmd_line.emplace_back("-AMPL");
for (const auto& arg : opt.nlFlags) {
cmd_line.push_back(arg);
}
Process<NLSolns2Out> proc(cmd_line, &s2o, 0, true);
exitStatus = proc.run();
if (exitStatus == 0) {
// Parse the result
s2o.parseSolution(file_sol);
}
} catch (const NLException& e) {
out->getLog() << e.what();
exitStatus = -2;
}
// --- --- --- Cleanup and exit
delete tmpdir;
tmpdir = nullptr;
outfile.close();
return exitStatus == 0 ? out->status : Status::ERROR;
}
// Unused
Expression* NLSolverInstance::getSolutionValue(Id* id) {
assert(false);
return nullptr;
}
/** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
* **/
/** Analyse an item
* An item is a "top node" in the ast.
* In flatzinc, we can only have the cases 'variable declaration' and 'constraint'.
*/
void NLSolverInstance::analyse(const Item* i) {
// Guard
if (i == nullptr) {
return;
}
// Switch on the id of item
switch (i->iid()) {
case Item::II_INC: {
should_not_happen("include \"" << i->cast<IncludeI>()->f() << "\")");
} break;
// Case of the variable declaration.
// Because it is a variable declaration, the expression associated to the item is necessary a
// VarDecl. From the VarDecl, we can obtain the type and the RHS expression. Use this to analyse
// further.
case Item::II_VD: {
DEBUG_MSG("II_VD: Variable Declaration.");
const VarDecl& vd = *i->cast<VarDeclI>()->e();
const TypeInst& ti = *vd.ti()->cast<TypeInst>();
const Expression& rhs = *vd.e();
_nlFile.addVarDecl(vd, ti, rhs);
} break;
case Item::II_ASN: {
should_not_happen("item II_ASN should not be present in NL's input.");
} break;
// Case of the constraint.
// Constraint are expressed through builtin calls.
// Hence, the expression associated to the item must be a E_CALL.
case Item::II_CON: {
DEBUG_MSG("II_CON: Constraint.");
Expression* e = i->cast<ConstraintI>()->e();
if (e->eid() == Expression::E_CALL) {
const Call& c = *e->cast<Call>();
DEBUG_MSG(" " << c.id() << " ");
_nlFile.analyseConstraint(c);
} else {
DEBUG_MSG(" Contraint is not a builtin call.");
assert(false);
}
} break;
// Case of the 'solve' directive
case Item::II_SOL: {
const SolveI& si = *i->cast<SolveI>();
_nlFile.addSolve(si.st(), si.e());
} break;
case Item::II_OUT: {
should_not_happen("Item II_OUT should not be present in NL's input.");
} break;
case Item::II_FUN: {
should_not_happen("Item II_FUN should not be present in NL's input.");
} break;
} // END OF SWITCH
}
} // namespace MiniZinc