this repo has no description
at develop 18 kB view raw
1/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ 2 3/* 4 * Main authors: 5 * Guido Tack <guido.tack@monash.edu> 6 * Gleb Belov <gleb.belov@monash.edu> 7 */ 8 9/* This Source Code Form is subject to the terms of the Mozilla Public 10 * License, v. 2.0. If a copy of the MPL was ! distributed with this 11 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 12 13#ifdef _MSC_VER 14#define _CRT_SECURE_NO_WARNINGS 15#endif 16 17#include <minizinc/solns2out.hh> 18#include <minizinc/solver.hh> 19 20#include <fstream> 21 22using namespace std; 23using namespace MiniZinc; 24 25void Solns2Out::printHelp(ostream& os) { 26 os << "Solution output options:" << std::endl 27 << " --ozn-file <file>\n Read output specification from ozn file." << std::endl 28 << " -o <file>, --output-to-file <file>\n Filename for generated output." << std::endl 29 << " -i <n>, --ignore-lines <n>, --ignore-leading-lines <n>\n Ignore the first <n> lines " 30 "in the FlatZinc solution stream." 31 << std::endl 32 << " --soln-sep <s>, --soln-separator <s>, --solution-separator <s>\n Specify the string " 33 "printed after each solution (as a separate line).\n The default is to use the same as " 34 "FlatZinc, \"----------\"." 35 << std::endl 36 << " --soln-comma <s>, --solution-comma <s>\n Specify the string used to separate " 37 "solutions.\n The default is the empty string." 38 << std::endl 39 << " --unsat-msg (--unsatisfiable-msg), --unbounded-msg, --unsatorunbnd-msg,\n" 40 " --unknown-msg, --error-msg, --search-complete-msg <msg>\n" 41 " Specify solution status messages. The defaults:\n" 42 " \"=====UNSATISFIABLE=====\", \"=====UNSATorUNBOUNDED=====\", " 43 "\"=====UNBOUNDED=====\",\n" 44 " \"=====UNKNOWN=====\", \"=====ERROR=====\", \"==========\", respectively." 45 << std::endl 46 << " --non-unique\n Allow duplicate solutions.\n" 47 << " -c, --canonicalize\n Canonicalize the output solution stream (i.e., buffer and " 48 "sort).\n" 49 << " --output-non-canonical <file>\n Non-buffered solution output file in case of " 50 "canonicalization.\n" 51 << " --output-raw <file>\n File to dump the solver's raw output (not for hard-linked " 52 "solvers)\n" 53 // Unclear how to exit then: 54 // << " --number-output <n>\n Maximal number of different solutions printed." << 55 // std::endl 56 << " --no-output-comments\n Do not print comments in the FlatZinc solution stream." 57 << std::endl 58 << " --output-time\n Print timing information in the FlatZinc solution stream." 59 << std::endl 60 << " --no-flush-output\n Don't flush output stream after every line." << std::endl; 61} 62 63bool Solns2Out::processOption(int& i, std::vector<std::string>& argv) { 64 CLOParser cop(i, argv); 65 std::string oznfile; 66 if (cop.getOption("--ozn-file", &oznfile)) { 67 initFromOzn(oznfile); 68 } else if (cop.getOption("-o --output-to-file", &_opt.flag_output_file)) { 69 } else if (cop.getOption("--no-flush-output")) { 70 _opt.flag_output_flush = false; 71 } else if (cop.getOption("--no-output-comments")) { 72 _opt.flag_output_comments = false; 73 } else if (cop.getOption("-i --ignore-lines --ignore-leading-lines", &_opt.flag_ignore_lines)) { 74 } else if (cop.getOption("--output-time")) { 75 _opt.flag_output_time = true; 76 } else if (cop.getOption("--soln-sep --soln-separator --solution-separator", 77 &_opt.solution_separator)) { 78 } else if (cop.getOption("--soln-comma --solution-comma", &_opt.solution_comma)) { 79 } else if (cop.getOption("--unsat-msg --unsatisfiable-msg", &_opt.unsatisfiable_msg)) { 80 } else if (cop.getOption("--unbounded-msg", &_opt.unbounded_msg)) { 81 } else if (cop.getOption("--unsatorunbnd-msg", &_opt.unsatorunbnd_msg)) { 82 } else if (cop.getOption("--unknown-msg", &_opt.unknown_msg)) { 83 } else if (cop.getOption("--error-msg", &_opt.error_msg)) { 84 } else if (cop.getOption("--search-complete-msg", &_opt.search_complete_msg)) { 85 } else if (cop.getOption("--unique")) { 86 _opt.flag_unique = true; 87 } else if (cop.getOption("--non-unique")) { 88 _opt.flag_unique = false; 89 } else if (cop.getOption("-c --canonicalize")) { 90 _opt.flag_canonicalize = true; 91 } else if (cop.getOption("--output-non-canonical --output-non-canon", 92 &_opt.flag_output_noncanonical)) { 93 } else if (cop.getOption("--output-raw", &_opt.flag_output_raw)) { 94 // } else if ( cop.getOption( "--number-output", &_opt.flag_number_output ) ) { 95 } else if (_opt.flag_standaloneSolns2Out) { 96 std::string oznfile(argv[i]); 97 if (oznfile.length() <= 4) { 98 return false; 99 } 100 size_t last_dot = oznfile.find_last_of('.'); 101 if (last_dot == string::npos) { 102 return false; 103 } 104 std::string extension = oznfile.substr(last_dot, string::npos); 105 if (extension == ".ozn") { 106 initFromOzn(oznfile); 107 return true; 108 } 109 return false; 110 } else { 111 return false; 112 } 113 return true; 114} 115 116bool Solns2Out::initFromEnv(Env* pE) { 117 assert(pE); 118 pEnv = pE; 119 init(); 120 return true; 121} 122 123void Solns2Out::initFromOzn(const std::string& filename) { 124 std::vector<string> filenames(1, filename); 125 126 includePaths.push_back(stdlibDir + "/std/"); 127 128 for (unsigned int i = 0; i < includePaths.size(); i++) { 129 if (!FileUtils::directory_exists(includePaths[i])) { 130 std::cerr << "solns2out: cannot access include directory " << includePaths[i] << "\n"; 131 std::exit(EXIT_FAILURE); 132 } 133 } 134 135 { 136 pEnv = new Env(); 137 std::stringstream errstream; 138 if ((pOutput = parse(*pEnv, filenames, std::vector<std::string>(), "", "", includePaths, false, 139 false, false, errstream))) { 140 std::vector<TypeError> typeErrors; 141 pEnv->model(pOutput); 142 MZN_ASSERT_HARD_MSG(pEnv, "solns2out: could not allocate Env"); 143 pEnv_guard.reset(pEnv); 144 MiniZinc::typecheck(*pEnv, pOutput, typeErrors, false, false); 145 MiniZinc::registerBuiltins(*pEnv); 146 pEnv->envi().swap_output(); 147 init(); 148 } else { 149 throw Error(errstream.str()); 150 } 151 } 152} 153 154void Solns2Out::createOutputMap() { 155 for (unsigned int i = 0; i < getModel()->size(); i++) { 156 if (VarDeclI* vdi = (*getModel())[i]->dyn_cast<VarDeclI>()) { 157 GCLock lock; 158 declmap.insert( 159 pair<std::string, DE>(vdi->e()->id()->str().str(), DE(vdi->e(), vdi->e()->e()))); 160 } else if (OutputI* oi = (*getModel())[i]->dyn_cast<OutputI>()) { 161 MZN_ASSERT_HARD_MSG(outputExpr == oi->e(), 162 "solns2out_base: <=1 output items allowed currently TODO?"); 163 } 164 } 165} 166 167Solns2Out::DE& Solns2Out::findOutputVar(ASTString id) { 168 declNewOutput(); 169 if (declmap.empty()) createOutputMap(); 170 auto it = declmap.find(id.str()); 171 MZN_ASSERT_HARD_MSG(declmap.end() != it, "solns2out_base: unexpected id in output: " << id); 172 return it->second; 173} 174 175void Solns2Out::restoreDefaults() { 176 for (unsigned int i = 0; i < getModel()->size(); i++) { 177 if (VarDeclI* vdi = (*getModel())[i]->dyn_cast<VarDeclI>()) { 178 GCLock lock; 179 auto& de = findOutputVar(vdi->e()->id()->str()); 180 vdi->e()->e(de.second()); 181 vdi->e()->evaluated(false); 182 } 183 } 184 fNewSol2Print = false; 185} 186 187void Solns2Out::parseAssignments(string& solution) { 188 std::vector<SyntaxError> se; 189 unique_ptr<Model> sm(parseFromString(*pEnv, solution, "solution received from solver", 190 includePaths, true, false, false, log, se)); 191 if (sm.get() == NULL) throw Error("solns2out_base: could not parse solution"); 192 solution = ""; 193 for (unsigned int i = 0; i < sm->size(); i++) { 194 if (AssignI* ai = (*sm)[i]->dyn_cast<AssignI>()) { 195 auto& de = findOutputVar(ai->id()); 196 ai->e()->type(de.first->type()); 197 ai->decl(de.first); 198 typecheck(*pEnv, getModel(), ai); 199 if (Call* c = ai->e()->dyn_cast<Call>()) { 200 // This is an arrayXd call, make sure we get the right builtin 201 assert(c->arg(c->n_args() - 1)->isa<ArrayLit>()); 202 for (unsigned int i = 0; i < c->n_args(); i++) c->arg(i)->type(Type::parsetint()); 203 c->arg(c->n_args() - 1)->type(de.first->type()); 204 c->decl(getModel()->matchFn(pEnv->envi(), c, false)); 205 } 206 de.first->e(ai->e()); 207 } 208 } 209 declNewOutput(); 210} 211 212void Solns2Out::declNewOutput() { 213 fNewSol2Print = true; 214 status = SolverInstance::SAT; 215} 216 217bool Solns2Out::evalOutput(const string& s_ExtraInfo) { 218 if (!fNewSol2Print) return true; 219 ostringstream oss; 220 if (!__evalOutput(oss)) return false; 221 if (!checkerModel.empty()) checkSolution(oss); 222 bool fNew = true; 223 if (_opt.flag_unique || _opt.flag_canonicalize) { 224 auto res = sSolsCanon.insert(oss.str()); 225 if (!res.second) // repeated solution 226 fNew = false; 227 } 228 if (fNew) { 229 ++nSolns; 230 if (_opt.flag_canonicalize) { 231 if (pOfs_non_canon.get()) 232 if (pOfs_non_canon->good()) { 233 (*pOfs_non_canon) << oss.str(); 234 (*pOfs_non_canon) << comments; 235 if (s_ExtraInfo.size()) { 236 (*pOfs_non_canon) << s_ExtraInfo; 237 if ('\n' != s_ExtraInfo.back()) /// TODO is this enough to check EOL? 238 (*pOfs_non_canon) << '\n'; 239 } 240 if (_opt.flag_output_time) 241 (*pOfs_non_canon) << "% time elapsed: " << starttime.stoptime() << "\n"; 242 if (!_opt.solution_separator.empty()) 243 (*pOfs_non_canon) << _opt.solution_separator << '\n'; 244 if (_opt.flag_output_flush) pOfs_non_canon->flush(); 245 } 246 } else { 247 if (_opt.solution_comma.size() && nSolns > 1) getOutput() << _opt.solution_comma << '\n'; 248 getOutput() << oss.str(); 249 } 250 } 251 getOutput() << comments; // print them now ???? 252 comments = ""; 253 if (s_ExtraInfo.size()) { 254 getOutput() << s_ExtraInfo; 255 if ('\n' != s_ExtraInfo.back()) /// TODO is this enough to check EOL? 256 getOutput() << '\n'; 257 } 258 if (fNew && _opt.flag_output_time) 259 getOutput() << "% time elapsed: " << starttime.stoptime() << "\n"; 260 if (fNew && !_opt.flag_canonicalize && !_opt.solution_separator.empty()) 261 getOutput() << _opt.solution_separator << '\n'; 262 if (_opt.flag_output_flush) getOutput().flush(); 263 restoreDefaults(); // cleans data. evalOutput() should not be called again w/o assigning new 264 // data. 265 return true; 266} 267 268void Solns2Out::checkSolution(std::ostream& os) { 269#ifdef HAS_GECODE 270 271 std::ostringstream checker; 272 checker << checkerModel; 273 { 274 GCLock lock; 275 for (unsigned int i = 0; i < getModel()->size(); i++) { 276 if (VarDeclI* vdi = (*getModel())[i]->dyn_cast<VarDeclI>()) { 277 if (vdi->e()->ann().contains(constants().ann.mzn_check_var)) { 278 if (Call* cev = vdi->e()->ann().getCall(constants().ann.mzn_check_enum_var)) { 279 checker << vdi->e()->id()->str() << "= to_enum(" << *cev->arg(0)->cast<Id>() << "," 280 << *eval_par(getEnv()->envi(), vdi->e()->e()) << ");"; 281 } else { 282 checker << vdi->e()->id()->str() << "=" << *eval_par(getEnv()->envi(), vdi->e()->e()) 283 << ";"; 284 } 285 } 286 } 287 } 288 } 289 290 std::ostringstream oss_err; 291 assert(false); 292 MznSolver slv; 293 slv.s2out._opt.solution_separator = ""; 294 try { 295 std::vector<std::string> args({"--solver", "org.minizinc.gecode_presolver"}); 296 // slv.run(args, checker.str(), "minizinc", "checker.mzc"); 297 } catch (const LocationException& e) { 298 oss_err << e.loc() << ":" << std::endl; 299 oss_err << e.what() << ": " << e.msg() << std::endl; 300 } catch (const Exception& e) { 301 std::string what = e.what(); 302 oss_err << what << (what.empty() ? "" : ": ") << e.msg() << std::endl; 303 } catch (const exception& e) { 304 oss_err << e.what() << std::endl; 305 } catch (...) { 306 oss_err << " UNKNOWN EXCEPTION." << std::endl; 307 } 308 std::istringstream iss(oss_err.str()); 309 std::string line; 310 os << "% Solution checker report:\n"; 311 while (std::getline(iss, line)) { 312 os << "% " << line << "\n"; 313 } 314 315#else 316 os << "% solution checking not supported (need built-in Gecode)" << std::endl; 317#endif 318} 319 320bool Solns2Out::__evalOutput(ostream& fout) { 321 if (0 != outputExpr) { 322 pEnv->envi().evalOutput(fout); 323 } 324 return true; 325} 326 327bool Solns2Out::evalStatus(SolverInstance::Status status) { 328 if (_opt.flag_canonicalize) __evalOutputFinal(_opt.flag_output_flush); 329 __evalStatusMsg(status); 330 fStatusPrinted = 1; 331 return true; 332} 333 334bool Solns2Out::__evalOutputFinal(bool) { 335 /// Print the canonical list 336 for (auto& sol : sSolsCanon) { 337 if (_opt.solution_comma.size() && &sol != &*sSolsCanon.begin()) 338 getOutput() << _opt.solution_comma << '\n'; 339 getOutput() << sol; 340 if (!_opt.solution_separator.empty()) getOutput() << _opt.solution_separator << '\n'; 341 } 342 return true; 343} 344 345bool Solns2Out::__evalStatusMsg(SolverInstance::Status status) { 346 std::map<SolverInstance::Status, string> stat2msg; 347 stat2msg[SolverInstance::OPT] = _opt.search_complete_msg; 348 stat2msg[SolverInstance::UNSAT] = _opt.unsatisfiable_msg; 349 stat2msg[SolverInstance::UNBND] = _opt.unbounded_msg; 350 stat2msg[SolverInstance::UNSATorUNBND] = _opt.unsatorunbnd_msg; 351 stat2msg[SolverInstance::UNKNOWN] = _opt.unknown_msg; 352 stat2msg[SolverInstance::ERROR] = _opt.error_msg; 353 stat2msg[SolverInstance::NONE] = ""; 354 auto it = stat2msg.find(status); 355 if (stat2msg.end() != it) { 356 getOutput() << comments; 357 if (!it->second.empty()) getOutput() << it->second << '\n'; 358 if (_opt.flag_output_time) getOutput() << "% time elapsed: " << starttime.stoptime() << "\n"; 359 if (_opt.flag_output_flush) getOutput().flush(); 360 Solns2Out::status = status; 361 } else { 362 getOutput() << comments; 363 if (_opt.flag_output_flush) getOutput().flush(); 364 MZN_ASSERT_HARD_MSG(SolverInstance::SAT == status, // which is ignored 365 "solns2out_base: undefined solution status code " << status); 366 Solns2Out::status = SolverInstance::SAT; 367 } 368 comments = ""; 369 return true; 370} 371 372void Solns2Out::init() { 373 for (unsigned int i = 0; i < getModel()->size(); i++) { 374 if (OutputI* oi = (*getModel())[i]->dyn_cast<OutputI>()) { 375 outputExpr = oi->e(); 376 break; 377 } 378 if (VarDeclI* vdi = (*getModel())[i]->dyn_cast<VarDeclI>()) { 379 if (vdi->e()->id()->idn() == -1 && vdi->e()->id()->v() == "_mzn_solution_checker") { 380 checkerModel = eval_string(getEnv()->envi(), vdi->e()->e()); 381 if (checkerModel.size() > 0 && checkerModel[0] == '@') { 382 checkerModel = FileUtils::decodeBase64(checkerModel); 383 FileUtils::inflateString(checkerModel); 384 } 385 } 386 } 387 } 388 389 /// Main output file 390 if (0 == pOut) { 391 if (_opt.flag_output_file.size()) { 392 pOut.reset(new ofstream(_opt.flag_output_file)); 393 MZN_ASSERT_HARD_MSG(pOut.get(), 394 "solns2out_base: could not allocate stream object for file output into " 395 << _opt.flag_output_file); 396 checkIOStatus(pOut->good(), _opt.flag_output_file); 397 } 398 } 399 /// Non-canonical output 400 if (_opt.flag_canonicalize && _opt.flag_output_noncanonical.size()) { 401 pOfs_non_canon.reset(new ofstream(_opt.flag_output_noncanonical)); 402 MZN_ASSERT_HARD_MSG(pOfs_non_canon.get(), 403 "solns2out_base: could not allocate stream object for non-canon output"); 404 checkIOStatus(pOfs_non_canon->good(), _opt.flag_output_noncanonical, 0); 405 } 406 /// Raw output 407 if (_opt.flag_output_raw.size()) { 408 pOfs_raw.reset(new ofstream(_opt.flag_output_raw)); 409 MZN_ASSERT_HARD_MSG(pOfs_raw.get(), 410 "solns2out_base: could not allocate stream object for raw output"); 411 checkIOStatus(pOfs_raw->good(), _opt.flag_output_raw, 0); 412 } 413 /// Assume all options are set before 414 nLinesIgnore = _opt.flag_ignore_lines; 415} 416 417Solns2Out::Solns2Out(std::ostream& os0, std::ostream& log0, const std::string& stdlibDir0) 418 : os(os0), log(log0), stdlibDir(stdlibDir0) {} 419 420Solns2Out::~Solns2Out() { 421 getOutput() << comments; 422 if (_opt.flag_output_flush) getOutput() << flush; 423} 424 425ostream& Solns2Out::getOutput() { return ((pOut.get() && pOut->good()) ? *pOut : os); } 426 427ostream& Solns2Out::getLog() { return log; } 428 429bool Solns2Out::feedRawDataChunk(const char* data) { 430 istringstream solstream(data); 431 while (solstream.good()) { 432 string line; 433 getline(solstream, line); 434 if (line_part.size()) { 435 line = line_part + line; 436 line_part.clear(); 437 } 438 if (solstream.eof()) { // wait next chunk 439 line_part = line; 440 break; // to get to raw output 441 } 442 if (line.size()) 443 if ('\r' == line.back()) line.pop_back(); // For WIN files 444 if (nLinesIgnore > 0) { 445 --nLinesIgnore; 446 continue; 447 } 448 if (mapInputStatus.empty()) createInputMap(); 449 auto it = mapInputStatus.find(line); 450 if (mapInputStatus.end() != it) { 451 if (SolverInstance::SAT == it->second) { 452 parseAssignments(solution); 453 // evalOutput(); 454 } else { 455 evalStatus(it->second); 456 } 457 } else { 458 solution += line + '\n'; 459 if (_opt.flag_output_comments) { 460 std::istringstream iss(line); 461 char c = '_'; 462 iss >> skipws >> c; 463 if (iss.good() && '%' == c) { 464 // Feed comments directly 465 getOutput() << line << '\n'; 466 if (_opt.flag_output_flush) getOutput().flush(); 467 if (pOfs_non_canon.get()) 468 if (pOfs_non_canon->good()) (*pOfs_non_canon) << line << '\n'; 469 } 470 } 471 } 472 } 473 if (pOfs_raw.get()) { 474 (*pOfs_raw.get()) << data; 475 if (_opt.flag_output_flush) pOfs_raw->flush(); 476 } 477 return true; 478} 479 480void Solns2Out::createInputMap() { 481 mapInputStatus[_opt.search_complete_msg_00] = SolverInstance::OPT; 482 mapInputStatus[_opt.solution_separator_00] = SolverInstance::SAT; 483 mapInputStatus[_opt.unsatisfiable_msg_00] = SolverInstance::UNSAT; 484 mapInputStatus[_opt.unbounded_msg_00] = SolverInstance::UNBND; 485 mapInputStatus[_opt.unsatorunbnd_msg_00] = SolverInstance::UNSATorUNBND; 486 mapInputStatus[_opt.unknown_msg_00] = SolverInstance::UNKNOWN; 487 mapInputStatus[_opt.error_msg] = SolverInstance::ERROR; 488} 489 490void Solns2Out::printStatistics(ostream& os) { os << "%%%mzn-stat: nSolutions=" << nSolns << "\n"; }