#include "Debug/Assertion.hpp"
#include "Lib/Environment.hpp"
#include "Lib/Int.hpp"
#include "Lib/Portability.hpp"
#include "Lib/Stack.hpp"
#include "Lib/System.hpp"
#include "Lib/ScopedLet.hpp"
#include "Debug/TimeProfiling.hpp"
#include "Lib/Timer.hpp"
#include "Lib/Sys/Multiprocessing.hpp"
#include "Shell/Options.hpp"
#include "Shell/Statistics.hpp"
#include "Shell/UIHelper.hpp"
#include "Shell/Normalisation.hpp"
#include "Shell/Shuffling.hpp"
#include "Shell/TheoryFinder.hpp"
#include <sys/wait.h>
#include <limits>
#include <unistd.h>
#include <signal.h>
#include <fstream>
#include <cstdio>
#include <random>
#include <filesystem>
#include <thread>
#include "Saturation/ProvingHelper.hpp"
#include "Kernel/Problem.hpp"
#include "Schedules.hpp"
#include "PortfolioMode.hpp"
using namespace Lib;
using namespace CASC;
using Lib::Sys::Multiprocessing;
using std::cout;
using std::cerr;
using std::endl;
namespace fs = std::filesystem;
PortfolioMode::PortfolioMode(Problem* problem) : _prb(problem), _slowness(env.options->slowness()) {
unsigned cores = std::thread::hardware_concurrency();
cores = cores < 1 ? 1 : cores;
_numWorkers = std::min(cores, env.options->multicore());
if(!_numWorkers)
{
_numWorkers = cores >= 8 ? cores - 2 : cores;
}
auto pathGiven = env.options->printProofToFile();
if(pathGiven.empty())
_path = fs::temp_directory_path() / ("vampire-proof-" + Int::toString(getpid()));
else
_path = fs::path(pathGiven);
try {
fs::remove(_path);
} catch(const fs::filesystem_error &remove_error) {
std::cerr
<< "WARNING: could not synchronise on " << _path
<< " (will output to stdout, but proof may be garbled)\n"
<< remove_error.what()
<< std::endl;
_path.clear();
}
}
bool PortfolioMode::perform(Problem* problem)
{
PortfolioMode pm(problem);
bool resValue;
try {
resValue = pm.searchForProof();
} catch (Exception& exc) {
cerr << "% Exception at proof search level" << endl;
exc.cry(cerr);
System::terminateImmediately(1); }
if (outputAllowed()) {
if (resValue) {
addCommentSignForSZS(cout);
cout<<"Success in time "<<Timer::msToSecondsString(Timer::elapsedMilliseconds())<<endl;
}
else {
addCommentSignForSZS(cout);
cout<<"Proof not found in time "<<Timer::msToSecondsString(Timer::elapsedMilliseconds())<<endl;
if (env.remainingTime()/100>0) {
addCommentSignForSZS(cout);
cout<<"SZS status GaveUp for "<<env.options->problemName()<<endl;
}
else {
addCommentSignForSZS(cout);
cout<<"SZS status Timeout for "<<env.options->problemName()<<endl;
}
}
#if VTIME_PROFILING
if (env.options && env.options->timeStatistics()) {
TimeTrace::instance().printPretty(cout);
}
#endif }
return resValue;
}
bool PortfolioMode::searchForProof()
{
Shell::Property* property = _prb->getProperty();
{
TIME_TRACE(TimeTrace::PREPROCESSING);
ScopedLet<ExecutionPhase> phaseLet(env.statistics->phase,ExecutionPhase::NORMALIZATION);
if (env.options->normalize()) { Normalisation().normalise(*_prb);
}
if (env.options->shuffleInput()) {
Shuffling().shuffle(*_prb);
}
if(!env.getMainProblem()->hasPolymorphicSym()){
TheoryFinder(_prb->units(),property).search();
}
}
Timer::disableLimitEnforcement();
return prepareScheduleAndPerform(*property);
}
bool PortfolioMode::prepareScheduleAndPerform(const Shell::Property& prop)
{
Schedule schedule;
Schedule main;
Schedule champions;
getSchedules(prop,main,champions);
auto additionsSinceTheLastSpiderings = [&prop](const Schedule& sOrig, Schedule& sWithExtras) {
addScheduleExtra(sOrig,sWithExtras,"si=on:rtra=on:rawr=on:rp=on"); addScheduleExtra(sOrig,sWithExtras,"sp=frequency"); addScheduleExtra(sOrig,sWithExtras,"avsq=on:plsq=on"); addScheduleExtra(sOrig,sWithExtras,"av=on:atotf=0.5");
if(!prop.higherOrder()){
addScheduleExtra(sOrig,sWithExtras,"bsd=on:fsd=on"); addScheduleExtra(sOrig,sWithExtras,"to=lpo"); }
if(prop.props() & (Property::PR_HAS_INTEGERS | Property::PR_HAS_RATS | Property::PR_HAS_REALS)){
addScheduleExtra(sOrig,sWithExtras,"gve=cautious:asg=cautious:canc=cautious:ev=cautious:pum=on"); addScheduleExtra(sOrig,sWithExtras,"gve=force:asg=force:canc=force:ev=force:pum=on"); addScheduleExtra(sOrig,sWithExtras,"sos=theory:sstl=5"); addScheduleExtra(sOrig,sWithExtras,"thsq=on"); addScheduleExtra(sOrig,sWithExtras,"thsq=on:thsqd=16"); }
if(prop.props() & Property::PR_HAS_DT_CONSTRUCTORS){
addScheduleExtra(sOrig,sWithExtras,"gtg=exists_all:ind=struct");
addScheduleExtra(sOrig,sWithExtras,"ind=struct:sik=one:indgen=on:indoct=on:drc=off");
addScheduleExtra(sOrig,sWithExtras,"ind=struct:sik=one:indgen=on");
addScheduleExtra(sOrig,sWithExtras,"ind=struct:sik=one:indoct=on");
addScheduleExtra(sOrig,sWithExtras,"ind=struct:sik=all:indmd=1");
}
if(env.options->schedule() == Options::Schedule::SMTCOMP){
addScheduleExtra(sOrig,sWithExtras,"gtg=exists_all:tgt=full");
}
else
{
addScheduleExtra(sOrig,sWithExtras,"slsq=on");
addScheduleExtra(sOrig,sWithExtras,"tgt=full");
}
};
if (env.options->schedule() == Options::Schedule::SMTCOMP) {
schedule.loadFromIterator(main.iterFifo());
additionsSinceTheLastSpiderings(main,schedule);
} else { schedule = std::move(main);
}
if (schedule.isEmpty()) {
USER_ERROR("The schedule is empty.");
}
unsigned numChamps = _numWorkers / 2;
while (champions.size() > numChamps) {
champions.pop();
}
champions.loadFromIterator(schedule.iterFifo());
return runScheduleAndRecoverProof(std::move(champions));
};
void PortfolioMode::rescaleScheduleLimits(const Schedule& sOld, Schedule& sNew, float limit_multiplier)
{
ASS(limit_multiplier >= 0)
Schedule::BottomFirstIterator it(sOld);
auto scale = [&](auto v) {
unsigned newV = v * limit_multiplier;
return limit_multiplier > 0 && newV < v
? std::numeric_limits<unsigned>::max()
: newV;
};
while(it.hasNext()){
std::string s = it.next();
size_t bidx = s.rfind(":i=");
if (bidx == std::string::npos) {
bidx = s.rfind("_i=");
}
if (bidx != std::string::npos) {
bidx += 3; size_t eidx = s.find_first_of(":_",bidx); ASS_NEQ(eidx,std::string::npos);
std::string instrStr = s.substr(bidx,eidx-bidx);
unsigned oldInstr;
ALWAYS(Int::stringToUnsignedInt(instrStr,oldInstr));
s = s.substr(0,bidx) + Lib::Int::toString(scale(oldInstr)) + s.substr(eidx);
}
std::string ts = s.substr(s.find_last_of("_")+1,std::string::npos);
unsigned oldTime;
ALWAYS(Lib::Int::stringToUnsignedInt(ts,oldTime));
std::string prefix = s.substr(0,s.find_last_of("_"));
std::string new_time_suffix = Lib::Int::toString(scale(oldTime));
sNew.push(prefix + "_" + new_time_suffix);
}
}
void PortfolioMode::addScheduleExtra(const Schedule& sOld, Schedule& sNew, std::string extra)
{
Schedule::BottomFirstIterator it(sOld);
while(it.hasNext()){
std::string s = it.next();
auto idx = s.find_last_of("_");
std::string prefix = s.substr(0,idx);
std::string suffix = s.substr(idx,std::string::npos);
std::string new_s = prefix + ((prefix.back() != '_') ? ":" : "") + extra + suffix;
sNew.push(new_s);
}
}
void PortfolioMode::getSchedules(const Property& prop, Schedule& quick, Schedule& champions)
{
switch(env.options->schedule()) {
case Options::Schedule::FILE:
Schedules::getScheduleFromFile(env.options->scheduleFile(), quick);
break;
case Options::Schedule::SNAKE_TPTP_UNS:
Schedules::getSnakeTptpUnsSchedule(prop,quick);
break;
case Options::Schedule::SNAKE_TPTP_SAT:
Schedules::getSnakeTptpSatSchedule(prop,quick);
break;
case Options::Schedule::CASC_2025:
case Options::Schedule::CASC:
Schedules::getCasc2025Schedule(prop,quick,champions);
break;
case Options::Schedule::CASC_SAT_2025:
case Options::Schedule::CASC_SAT:
Schedules::getCascSat2025Schedule(prop,quick,champions);
break;
case Options::Schedule::CASC_2024:
Schedules::getCasc2024Schedule(prop,quick);
break;
case Options::Schedule::CASC_SAT_2024:
Schedules::getCascSat2024Schedule(prop,quick);
break;
case Options::Schedule::SMTCOMP:
case Options::Schedule::SMTCOMP_2018:
Schedules::getSmtcomp2018Schedule(prop,quick);
break;
case Options::Schedule::LTB_HH4_2017:
Schedules::getLtb2017Hh4Schedule(prop,quick);
break;
case Options::Schedule::LTB_HLL_2017:
Schedules::getLtb2017HllSchedule(prop,quick);
break;
case Options::Schedule::LTB_ISA_2017:
Schedules::getLtb2017IsaSchedule(prop,quick);
break;
case Options::Schedule::LTB_MZR_2017:
Schedules::getLtb2017MzrSchedule(prop,quick);
break;
case Options::Schedule::LTB_DEFAULT_2017:
Schedules::getLtb2017DefaultSchedule(prop,quick);
break;
case Options::Schedule::INDUCTION:
Schedules::getInductionSchedule(prop,quick);
break;
case Options::Schedule::INTEGER_INDUCTION:
Schedules::getIntegerInductionSchedule(prop,quick);
break;
case Options::Schedule::INTIND_OEIS:
Schedules::getIntindOeisSchedule(prop,quick);
break;
case Options::Schedule::STRUCT_INDUCTION:
Schedules::getStructInductionSchedule(prop,quick);
break;
case Options::Schedule::STRUCT_INDUCTION_TIP:
Schedules::getStructInductionTipSchedule(prop,quick);
break;
}
}
bool PortfolioMode::runSchedule(Schedule schedule) {
TIME_TRACE("run schedule");
Schedule::BottomFirstIterator it(schedule);
Set<pid_t> processes;
bool success = false;
int remainingTime;
bool scheduleRepeat = false;
while(remainingTime = env.remainingTime() / 100, remainingTime > 0)
{
while(processes.size() < _numWorkers)
{
if(!it.hasNext()) {
Schedule next;
rescaleScheduleLimits(schedule, next, 2.0);
scheduleRepeat = true;
schedule = std::move(next);
it = Schedule::BottomFirstIterator(schedule);
}
ALWAYS(it.hasNext());
std::string code = it.next();
pid_t process = Multiprocessing::instance()->fork();
ASS_NEQ(process, -1);
if(process == 0)
{
TIME_TRACE_NEW_ROOT("child process")
runSlice(code, remainingTime, scheduleRepeat);
ASSERTION_VIOLATION; }
ALWAYS(processes.insert(process));
}
bool exited, signalled;
int code;
pid_t process = Multiprocessing::instance()->poll_children(exited, signalled, code);
if(exited)
{
ALWAYS(processes.remove(process));
if(!code)
{
success = true;
break;
}
} else if (signalled) {
Shell::addCommentSignForSZS(cout);
cout<<"Child killed by signal " << code << endl;
ALWAYS(processes.remove(process));
}
}
{
decltype(processes)::Iterator killIt(processes);
while(killIt.hasNext())
Multiprocessing::instance()->kill(killIt.next(), SIGINT);
}
{
decltype(processes)::Iterator killIt(processes);
while(killIt.hasNext()) {
waitpid(killIt.next(), NULL, 0);
}
}
return success;
}
bool PortfolioMode::runScheduleAndRecoverProof(Schedule schedule)
{
if (schedule.size() == 0)
return false;
UIHelper::portfolioParent = true;
bool result = runSchedule(std::move(schedule));
if(result && env.options->printProofToFile().empty()){
std::ifstream input(_path);
bool openSucceeded = !input.fail();
if (openSucceeded) {
cout << input.rdbuf();
} else {
if (outputAllowed()) {
addCommentSignForSZS(cout) << "Failed to restore proof from tempfile " << _path << endl;
}
}
if(openSucceeded){
fs::remove(_path);
}
}
return result;
}
unsigned PortfolioMode::getSliceTime(const std::string &sliceCode)
{
unsigned pos = sliceCode.find_last_of('_');
std::string sliceTimeStr = sliceCode.substr(pos+1);
unsigned sliceTime;
ALWAYS(Int::stringToUnsignedInt(sliceTimeStr,sliceTime));
if (sliceTime == 0 && !Timer::instructionLimitingInPlace()) {
if (outputAllowed()) {
addCommentSignForSZS(cout);
cout << "WARNING: time unlimited strategy and instruction limiting not in place - attempting to translate instructions to time" << endl;
}
size_t bidx = sliceCode.find(":i=");
if (bidx == std::string::npos) {
bidx = sliceCode.find("_i=");
if (bidx == std::string::npos) {
return 0; }
} bidx += 3; size_t eidx = sliceCode.find_first_of(":_",bidx); ASS_NEQ(eidx,std::string::npos);
std::string sliceInstrStr = sliceCode.substr(bidx,eidx-bidx);
unsigned sliceInstr;
ALWAYS(Int::stringToUnsignedInt(sliceInstrStr,sliceInstr));
sliceTime = 1 + sliceInstr / 200; }
ASS(_slowness > 0)
unsigned res = _slowness * sliceTime;
return _slowness >= 1 && res < sliceTime
? std::numeric_limits<unsigned>::max()
: res;
}
void PortfolioMode::runSlice(std::string sliceCode, int timeLimitInDeciseconds, bool scheduleRepeat)
{
TIME_TRACE("run slice");
int sliceTime = getSliceTime(sliceCode);
if (sliceTime > timeLimitInDeciseconds
|| !sliceTime) {
sliceTime = timeLimitInDeciseconds;
}
ASS_GE(sliceTime,0);
try
{
Options& opt = *env.options;
if (env.options->randomizeSeedForPortfolioWorkers()) {
opt.setRandomSeed(std::random_device()());
}
if (scheduleRepeat && env.options->shuffleOnScheduleRepeats()) {
opt.enableShuffling();
}
opt.readFromEncodedOptions(sliceCode);
opt.setTimeLimitInDeciseconds(sliceTime);
int stl = opt.simulatedTimeLimit();
if (stl) {
opt.setSimulatedTimeLimit(int(stl * _slowness));
}
runSlice(opt);
}
catch(Exception &e)
{
if(outputAllowed())
{
cerr << "% Exception at run slice level" << endl;
e.cry(cerr);
}
System::terminateImmediately(1); }
}
void PortfolioMode::runSlice(Options& opt)
{
System::registerForSIGHUPOnParentDeath();
UIHelper::portfolioParent=false;
opt.setNormalize(false);
opt.setForcedOptionValues();
opt.checkGlobalOptionConstraints();
if (outputAllowed()) {
addCommentSignForSZS(cout) << opt.generateEncodedOptions() << " on " << opt.problemName() <<
" for (" << opt.timeLimitInDeciseconds() << "ds"<<
#if VAMPIRE_PERF_EXISTS
"/" << opt.instructionLimit() << "Mi" <<
#endif
")" << endl;
}
Timer::reinitialise(Timer::instructionLimitingInPlace());
Saturation::ProvingHelper::runVampire(*_prb, opt);
bool succeeded =
env.statistics->terminationReason == TerminationReason::REFUTATION ||
env.statistics->terminationReason == TerminationReason::SATISFIABLE;
if(!succeeded) {
if(outputAllowed())
UIHelper::outputResult(cout);
exit(EXIT_FAILURE);
}
bool outputResult = false;
FILE *checkExists;
if(_path.empty())
outputResult = true;
else if((checkExists = std::fopen(_path.c_str(), "wx"))) {
std::fclose(checkExists);
outputResult = true;
}
else if(errno != EEXIST) {
std::cerr
<< "WARNING: could not open proof file << " << _path
<< " - printing to stdout." << std::endl;
_path.clear();
outputResult = true;
}
if(!outputResult) {
if (Lib::env.options && Lib::env.options->multicore() != 1)
addCommentSignForSZS(cout) << "Also succeeded, but the first one will report." << endl;
exit(EXIT_FAILURE);
}
ASS(succeeded && outputResult)
if (outputAllowed() && env.options->multicore() != 1)
addCommentSignForSZS(cout) << "First to succeed." << endl;
if (_path.empty()) {
UIHelper::outputResult(cout);
} else {
std::ofstream output(_path);
if(output.fail()) {
addCommentSignForSZS(cout) << "Solution printing to a file '" << _path << "' failed. Outputting to stdout" << endl;
UIHelper::outputResult(cout);
} else {
UIHelper::outputResult(output);
if(outputAllowed())
addCommentSignForSZS(cout) << "Solution written to " << _path << endl;
}
}
exit(EXIT_SUCCESS);
}