From 49deb76b7b3255d7a19cdfa64a66e4229e205879 Mon Sep 17 00:00:00 2001 From: Guido Tack Date: Sat, 21 Jan 2017 20:56:45 +1100 Subject: [PATCH 1/5] Check process exit status: avoids crashes when mzn2fzn crashes, prints error messages, and doesn't run solver when mzn2fzn crashed. Fixes #28. --- MiniZincIDE/mainwindow.cpp | 26 +++++++++++++++----------- MiniZincIDE/mainwindow.h | 4 ++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/MiniZincIDE/mainwindow.cpp b/MiniZincIDE/mainwindow.cpp index 7db69d0..3966dbb 100755 --- a/MiniZincIDE/mainwindow.cpp +++ b/MiniZincIDE/mainwindow.cpp @@ -1445,9 +1445,9 @@ QString MainWindow::getMznDistribPath(void) const { return mznDistribPath; } -void MainWindow::checkArgsFinished(int exitcode) +void MainWindow::checkArgsFinished(int exitcode, QProcess::ExitStatus exitstatus) { - if (processWasStopped) + if (processWasStopped || exitstatus==QProcess::CrashExit) return; QString additionalCmdlineParams; QString additionalDataFile; @@ -1499,7 +1499,7 @@ void MainWindow::checkArgs(QString filepath) process->setWorkingDirectory(QFileInfo(filepath).absolutePath()); process->setProcessChannelMode(QProcess::MergedChannels); connect(process, SIGNAL(readyRead()), this, SLOT(checkArgsOutput())); - connect(process, SIGNAL(finished(int)), this, SLOT(checkArgsFinished(int))); + connect(process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(checkArgsFinished(int,QProcess::ExitStatus))); connect(process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(procError(QProcess::ProcessError))); @@ -1507,6 +1507,7 @@ void MainWindow::checkArgs(QString filepath) args << "--instance-check-only" << "--output-to-stdout"; args << filepath; compileErrors = ""; + elapsedTime.start(); process->start(mzn2fzn_executable,args,getMznDistribPath()); } @@ -1550,7 +1551,7 @@ void MainWindow::on_actionRun_triggered() if (curEditor->filepath.endsWith(".fzn")) { currentFznTarget = curEditor->filepath; runSolns2Out = false; - runCompiledFzn(0); + runCompiledFzn(0,QProcess::NormalExit); } else { compileOnly = false; checkArgs(curEditor->filepath); @@ -1574,7 +1575,6 @@ QString MainWindow::setElapsedTime() elapsed += QString().number(seconds)+"s"; if (hours==0 && minutes==0) elapsed += " "+QString().number(msec)+"msec"; - QString timeLimit; if (project.timeLimit() > 0) { timeLimit += " / "; @@ -1757,7 +1757,7 @@ void MainWindow::compileAndRun(const QString& modelPath, const QString& addition } else if (standalone) { connect(process, SIGNAL(finished(int)), this, SLOT(procFinished(int))); } else { - connect(process, SIGNAL(finished(int)), this, SLOT(runCompiledFzn(int))); + connect(process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(runCompiledFzn(int,QProcess::ExitStatus))); } connect(process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(procError(QProcess::ProcessError))); @@ -1802,10 +1802,10 @@ void MainWindow::compileAndRun(const QString& modelPath, const QString& addition compiling += ", additional arguments " + additionalCmdlineParams; } addOutput("
"+compiling+"

"); - process->start(processName,args,getMznDistribPath()); time = 0; timer->start(500); elapsedTime.start(); + process->start(processName,args,getMznDistribPath()); } } @@ -1903,25 +1903,29 @@ void MainWindow::procFinished(int, bool showTime) { delete tmpDir; tmpDir = NULL; outputBuffer = NULL; + compileErrors = ""; emit(finished()); } void MainWindow::procError(QProcess::ProcessError e) { + if (!compileErrors.isEmpty()) { + addOutput(compileErrors,false); + } + procFinished(1); if (e==QProcess::FailedToStart) { QMessageBox::critical(this, "MiniZinc IDE", "Failed to start '"+processName+"'. Check your path settings."); } else { QMessageBox::critical(this, "MiniZinc IDE", "Unknown error while executing the MiniZinc interpreter `"+processName+"': error code "+QString().number(e)); } - procFinished(0); } void MainWindow::outputProcError(QProcess::ProcessError e) { + procFinished(1); if (e==QProcess::FailedToStart) { QMessageBox::critical(this, "MiniZinc IDE", "Failed to start 'solns2out'. Check your path settings."); } else { QMessageBox::critical(this, "MiniZinc IDE", "Unknown error while executing the MiniZinc solution processor."); } - procFinished(0); } void MainWindow::saveFile(CodeEditor* ce, const QString& f) @@ -2097,11 +2101,11 @@ void MainWindow::openCompiledFzn(int exitcode) procFinished(exitcode); } -void MainWindow::runCompiledFzn(int exitcode) +void MainWindow::runCompiledFzn(int exitcode, QProcess::ExitStatus exitstatus) { if (processWasStopped) return; - if (exitcode==0) { + if (exitcode==0 && exitstatus==QProcess::NormalExit) { readOutput(); QStringList args = parseConf(false,true); Solver s = solvers[ui->conf_solver->itemData(ui->conf_solver->currentIndex()).toInt()]; diff --git a/MiniZincIDE/mainwindow.h b/MiniZincIDE/mainwindow.h index 043e195..612bbeb 100755 --- a/MiniZincIDE/mainwindow.h +++ b/MiniZincIDE/mainwindow.h @@ -157,7 +157,7 @@ private slots: void checkArgs(QString filepath); void checkArgsOutput(); - void checkArgsFinished(int exitcode); + void checkArgsFinished(int exitcode, QProcess::ExitStatus exitstatus); void readOutput(); @@ -179,7 +179,7 @@ private slots: void openCompiledFzn(int); - void runCompiledFzn(int); + void runCompiledFzn(int,QProcess::ExitStatus); void on_actionSave_as_triggered(); From e3c2151225b6a4e971c13f0a29bbaf1715295342 Mon Sep 17 00:00:00 2001 From: Guido Tack Date: Sat, 21 Jan 2017 21:22:28 +1100 Subject: [PATCH 2/5] Change meaning of "User-defined behaviour" options, clearly distinguishing between optimisation and satisfaction problems. Fixes #25. --- MiniZincIDE/mainwindow.cpp | 28 +++++++++++++++++++++------- MiniZincIDE/mainwindow.ui | 14 +++++++------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/MiniZincIDE/mainwindow.cpp b/MiniZincIDE/mainwindow.cpp index 3966dbb..5af9bf1 100755 --- a/MiniZincIDE/mainwindow.cpp +++ b/MiniZincIDE/mainwindow.cpp @@ -1374,21 +1374,37 @@ QStringList MainWindow::parseConf(bool compileOnly, bool useDataFile) } if (compileOnly && useDataFile && project.currentDataFile()!="None") ret << "-d" << project.currentDataFile(); - if (!compileOnly && project.defaultBehaviour()) { + bool isOptimisationProblem = true; + { QFile fznFile(currentFznTarget); if (fznFile.open(QIODevice::ReadOnly | QIODevice::Text)) { int seekSize = strlen("satisfy;\n\n"); if (fznFile.size() >= seekSize) { fznFile.seek(fznFile.size()-seekSize); QString line = fznFile.readLine(); - if (!line.contains("satisfy;")) + if (line.contains("satisfy;")) + isOptimisationProblem = false; + } + } + } + + if (!compileOnly) { + if (project.defaultBehaviour()) { + if (isOptimisationProblem) + ret << "-a"; + } else { + if (isOptimisationProblem) { + if (project.printAll()) + ret << "-a"; + } else { + if (project.n_solutions() == 0) ret << "-a"; + else if (project.n_solutions() > 1) + ret << "-n" << QString::number(project.n_solutions()); } } - } else { - if (!compileOnly && project.printAll()) - ret << "-a"; } + if (!compileOnly && project.printStats()) ret << "-s"; if (!compileOnly && project.n_threads() > 1) @@ -1400,8 +1416,6 @@ QStringList MainWindow::parseConf(bool compileOnly, bool useDataFile) project.solverFlags().split(" ", QString::SkipEmptyParts); ret << solverArgs; } - if (!compileOnly && !project.defaultBehaviour() && project.n_solutions() != 1) - ret << "-n" << QString::number(project.n_solutions()); Solver s = solvers[ui->conf_solver->itemData(ui->conf_solver->currentIndex()).toInt()]; if (compileOnly && !s.mznlib.isEmpty()) ret << s.mznlib; diff --git a/MiniZincIDE/mainwindow.ui b/MiniZincIDE/mainwindow.ui index bd64f16..9fa357c 100755 --- a/MiniZincIDE/mainwindow.ui +++ b/MiniZincIDE/mainwindow.ui @@ -89,8 +89,8 @@ 0 - -225 - 595 + 0 + 607 818 @@ -270,7 +270,7 @@ - <html><head/><body><p><span style=" font-style:italic;">Optimization problems: print all solutions</span></p></body></html> + <html><head/><body><p><span style=" font-style:italic;">Optimization problems: print all intermediate solutions</span></p></body></html> Qt::RichText @@ -286,7 +286,7 @@ - <html><head/><body><p><span style=" font-style:italic;">Satisfaction problems: print first solution and stop</span></p></body></html> + <html><head/><body><p><span style=" font-style:italic;">Satisfaction problems: stop after first solution</span></p></body></html> @@ -322,7 +322,7 @@ - Print all solutions + Print intermediate solutions (for optimization problems) @@ -331,7 +331,7 @@ - Stop after this many solutions: + Stop after this many solutions (0 means "all"): @@ -348,7 +348,7 @@ - (not relevant for optimization problems) + (for satisfaction problems) From 67681ce6e9b95e0c16e8f05337657c3885e0f008 Mon Sep 17 00:00:00 2001 From: Guido Tack Date: Sun, 22 Jan 2017 11:32:18 +1100 Subject: [PATCH 3/5] Make sure error output from mzn2fzn doesn't get lost --- MiniZincIDE/mainwindow.cpp | 21 +++++++++++++++++---- MiniZincIDE/mainwindow.h | 1 + 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/MiniZincIDE/mainwindow.cpp b/MiniZincIDE/mainwindow.cpp index 5af9bf1..a3355fe 100755 --- a/MiniZincIDE/mainwindow.cpp +++ b/MiniZincIDE/mainwindow.cpp @@ -1190,7 +1190,7 @@ void MainWindow::closeEvent(QCloseEvent* e) { } if (process) { disconnect(process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(procError(QProcess::ProcessError))); + this, 0); process->kill(); } for (int i=0; itabWidget->count(); i++) { @@ -1515,7 +1515,7 @@ void MainWindow::checkArgs(QString filepath) connect(process, SIGNAL(readyRead()), this, SLOT(checkArgsOutput())); connect(process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(checkArgsFinished(int,QProcess::ExitStatus))); connect(process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(procError(QProcess::ProcessError))); + this, SLOT(checkArgsError(QProcess::ProcessError))); QStringList args = parseConf(true, true); args << "--instance-check-only" << "--output-to-stdout"; @@ -1933,6 +1933,19 @@ void MainWindow::procError(QProcess::ProcessError e) { } } +void MainWindow::checkArgsError(QProcess::ProcessError e) { + checkArgsOutput(); + if (!compileErrors.isEmpty()) { + addOutput(compileErrors,false); + } + procFinished(1); + if (e==QProcess::FailedToStart) { + QMessageBox::critical(this, "MiniZinc IDE", "Failed to start '"+processName+"'. Check your path settings."); + } else { + QMessageBox::critical(this, "MiniZinc IDE", "Unknown error while executing the MiniZinc interpreter `"+processName+"': error code "+QString().number(e)); + } +} + void MainWindow::outputProcError(QProcess::ProcessError e) { procFinished(1); if (e==QProcess::FailedToStart) { @@ -2044,8 +2057,8 @@ void MainWindow::on_actionStop_triggered() if (outputProcess) pipeOutput(); disconnect(process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(procError(QProcess::ProcessError))); - disconnect(process, SIGNAL(finished(int)), this, SLOT(procFinished(int))); + this, 0); + disconnect(process, SIGNAL(finished(int)), this, 0); processWasStopped = true; #ifdef Q_OS_WIN diff --git a/MiniZincIDE/mainwindow.h b/MiniZincIDE/mainwindow.h index 612bbeb..d79e9f1 100755 --- a/MiniZincIDE/mainwindow.h +++ b/MiniZincIDE/mainwindow.h @@ -168,6 +168,7 @@ private slots: void outputProcFinished(int, bool showTime=true); void procError(QProcess::ProcessError); + void checkArgsError(QProcess::ProcessError); void outputProcError(QProcess::ProcessError); void on_actionSave_triggered(); From f5422ec8d6a9b7504137b1525b2c7d2285925339 Mon Sep 17 00:00:00 2001 From: Guido Tack Date: Wed, 25 Jan 2017 16:37:33 +1100 Subject: [PATCH 4/5] Pipe directly into solns2out, and suppress output after configurable number of solutions (to avoid overloading the IDE output box). Fixes #24. Fixes #27. --- MiniZincIDE/mainwindow.cpp | 61 +++++++++++++++++++++++++++++--------- MiniZincIDE/mainwindow.h | 6 ++-- MiniZincIDE/mainwindow.ui | 49 +++++++++++++++++++++++++++++- MiniZincIDE/project.cpp | 19 ++++++++++++ MiniZincIDE/project.h | 3 ++ 5 files changed, 121 insertions(+), 17 deletions(-) diff --git a/MiniZincIDE/mainwindow.cpp b/MiniZincIDE/mainwindow.cpp index a3355fe..3a1a55d 100755 --- a/MiniZincIDE/mainwindow.cpp +++ b/MiniZincIDE/mainwindow.cpp @@ -1646,6 +1646,19 @@ void MainWindow::readOutput() } JSONOutput.append(sl); } else { + if (l.trimmed() == "----------") { + solutionCount++; + if ( solutionCount > solutionLimit || !hiddenSolutions.isEmpty()) { + if (hiddenSolutions.isEmpty()) { + solutionCount = 0; + if (!curJSONHandler || hadNonJSONOutput) + addOutput(l,false); + } + else + hiddenSolutions.back() += l; + hiddenSolutions.append(""); + } + } if (curJSONHandler > 0 && l.trimmed() == "----------") { openJSONViewer(); JSONOutput.clear(); @@ -1659,7 +1672,26 @@ void MainWindow::readOutput() } else { if (outputBuffer) (*outputBuffer) << l; - addOutput(l,false); + if (!hiddenSolutions.isEmpty()) { + if (l.trimmed() != "----------") { + hiddenSolutions.back() += l; + } + if (solutionCount == solutionLimit) { + addOutput("
[ "+QString().number(solutionLimit)+" more solutions ]

"); + solutionCount = 0; + solutionLimit *= 2; + } + } else { + addOutput(l,false); + } + if (!hiddenSolutions.isEmpty() && l.trimmed() == "==========") { + if (solutionCount!=solutionLimit && solutionCount > 1) { + addOutput("
[ "+QString().number(solutionCount-1)+" more solutions ]

"); + } + for (int i=hiddenSolutions.size()-2; iwrite(process->readAllStandardOutput()); -} - void MainWindow::outputProcFinished(int, bool showTime) { readOutput(); updateUiProcessRunning(false); @@ -1901,8 +1928,6 @@ void MainWindow::outputProcFinished(int, bool showTime) { void MainWindow::procFinished(int, bool showTime) { if (outputProcess) { connect(outputProcess, SIGNAL(finished(int)), this, SLOT(outputProcFinished(int))); - if (process) - pipeOutput(); outputProcess->closeWriteChannel(); return; } @@ -2054,8 +2079,6 @@ void MainWindow::on_actionStop_triggered() { ui->actionStop->setEnabled(false); if (process) { - if (outputProcess) - pipeOutput(); disconnect(process, SIGNAL(error(QProcess::ProcessError)), this, 0); disconnect(process, SIGNAL(finished(int)), this, 0); @@ -2166,6 +2189,9 @@ void MainWindow::runCompiledFzn(int exitcode, QProcess::ExitStatus exitstatus) tmpDir = NULL; procFinished(exitcode); } else { + solutionCount = 0; + solutionLimit = project.defaultBehaviour() ? 100 : project.n_compress_solutions(); + hiddenSolutions.clear(); if (runSolns2Out) { outputProcess = new MznProcess(this); inJSONHandler = false; @@ -2182,16 +2208,13 @@ void MainWindow::runCompiledFzn(int exitcode, QProcess::ExitStatus exitstatus) connect(outputProcess, SIGNAL(readyReadStandardError()), this, SLOT(readOutput())); connect(outputProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(outputProcError(QProcess::ProcessError))); - QStringList outargs; - outargs << currentFznTarget.left(currentFznTarget.length()-4)+".ozn"; - outputProcess->start("solns2out",outargs,getMznDistribPath()); } process = new MznProcess(this); processName = s.executable; processWasStopped = false; process->setWorkingDirectory(QFileInfo(curFilePath).absolutePath()); if (runSolns2Out) { - connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(pipeOutput())); + process->setStandardOutputProcess(outputProcess); } else { connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(readOutput())); } @@ -2221,6 +2244,11 @@ void MainWindow::runCompiledFzn(int exitcode, QProcess::ExitStatus exitstatus) addOutput("
"+cmdline+"

"); } process->start(executable,args,getMznDistribPath()); + if (runSolns2Out) { + QStringList outargs; + outargs << currentFznTarget.left(currentFznTarget.length()-4)+".ozn"; + outputProcess->start("solns2out",outargs,getMznDistribPath()); + } time = 0; timer->start(500); } @@ -2666,6 +2694,7 @@ void MainWindow::saveProject(const QString& f) out << projectFilesRelPath; out << project.defaultBehaviour(); out << project.mzn2fznPrintStats(); + out << project.n_compress_solutions(); project.setModified(false, true); } else { @@ -2776,6 +2805,10 @@ void MainWindow::loadProject(const QString& filepath) in >> p_b; project.mzn2fznPrintStats(p_b, true); } + if (version==104 && !in.atEnd()) { + in >> p_i; + project.n_compress_solutions(p_i, true); + } for (int i=0; i hiddenSolutions; int curJSONHandler; bool inJSONHandler; bool hadNonJSONOutput; diff --git a/MiniZincIDE/mainwindow.ui b/MiniZincIDE/mainwindow.ui index 9fa357c..344f597 100755 --- a/MiniZincIDE/mainwindow.ui +++ b/MiniZincIDE/mainwindow.ui @@ -91,7 +91,7 @@ 0 0 607 - 818 + 878
@@ -290,6 +290,13 @@ + + + + <html><head/><body><p><span style=" font-style:italic;">Compress solution output after 100 solutions</span></p></body></html> + + + @@ -367,6 +374,46 @@ + + + + + + Compress solution output after this many solutions: + + + + + + + 100 + + + 2000 + + + 100 + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + diff --git a/MiniZincIDE/project.cpp b/MiniZincIDE/project.cpp index 29dda6a..a4f5415 100644 --- a/MiniZincIDE/project.cpp +++ b/MiniZincIDE/project.cpp @@ -307,6 +307,7 @@ void Project::setModified(bool flag, bool files) mzn2fznOptimize(mzn2fznOptimize(),true); currentSolver(currentSolver(),true); n_solutions(n_solutions(),true); + n_compress_solutions(n_compress_solutions(),true); printAll(printAll(),true); defaultBehaviour(defaultBehaviour(),true); printStats(printStats(),true); @@ -381,6 +382,10 @@ int Project::n_solutions(void) const { return ui->conf_nsol->value(); } +int Project::n_compress_solutions(void) const +{ + return ui->conf_compressSolutionLimit->value(); +} bool Project::printAll(void) const { return ui->conf_printall->isChecked(); @@ -538,6 +543,16 @@ void Project::n_solutions(int n, bool init) } } +void Project::n_compress_solutions(int n, bool init) +{ + if (init) { + _compressSolutionLimit = n; + ui->conf_compressSolutionLimit->setValue(n); + } else { + checkModified(); + } +} + void Project::printAll(bool b, bool init) { if (init) { @@ -684,6 +699,10 @@ void Project::checkModified() setModified(true); return; } + if (n_compress_solutions() != _compressSolutionLimit) { + setModified(true); + return; + } if (printAll() != _printAll) { setModified(true); return; diff --git a/MiniZincIDE/project.h b/MiniZincIDE/project.h index 690a2fb..ce3af0d 100644 --- a/MiniZincIDE/project.h +++ b/MiniZincIDE/project.h @@ -75,6 +75,7 @@ class Project : public QStandardItemModel bool mzn2fznOptimize(void) const; QString currentSolver(void) const; int n_solutions(void) const; + int n_compress_solutions(void) const; bool printAll(void) const; bool defaultBehaviour(void) const; bool printStats(void) const; @@ -99,6 +100,7 @@ public slots: void mzn2fznOptimize(bool b, bool init=false); void currentSolver(const QString& s, bool init=false); void n_solutions(int n, bool init=false); + void n_compress_solutions(int n, bool init=false); void printAll(bool b, bool init=false); void defaultBehaviour(bool b, bool init=false); void printStats(bool b, bool init=false); @@ -143,6 +145,7 @@ public slots: bool _haveSeed; QString _seed; int _timeLimit; + int _compressSolutionLimit; bool _solverVerbose; CourseraProject* _courseraProject; From 9b71947ce368464854293f485b7e0372d34d43f6 Mon Sep 17 00:00:00 2001 From: Guido Tack Date: Thu, 2 Feb 2017 15:45:16 +1100 Subject: [PATCH 5/5] Bump version and add change log --- MiniZincIDE/CHANGES | 10 ++++++++++ MiniZincIDE/MiniZincIDE.pro | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/MiniZincIDE/CHANGES b/MiniZincIDE/CHANGES index 424ceb6..2881a39 100644 --- a/MiniZincIDE/CHANGES +++ b/MiniZincIDE/CHANGES @@ -1,3 +1,13 @@ +2017-02-06 + v2.1.3 + - Update to MiniZinc 2.1.3. + - Avoid crashes and print error messages when mzn2fzn subprocess crashes. + - Changed meaning of "User-defined behavior" options, to have a clear + distinction between optimisation and satisfaction problems. + - Fix buffering of error output from mzn2fzn process (which would sometimes + not be printed to the output window). + - Suppress output after configurable number of solutions (to avoid + overloading the IDE output box). 2016-12-20 v2.1.2 - Update to MiniZinc 2.1.2. diff --git a/MiniZincIDE/MiniZincIDE.pro b/MiniZincIDE/MiniZincIDE.pro index cf411f8..34d7ade 100644 --- a/MiniZincIDE/MiniZincIDE.pro +++ b/MiniZincIDE/MiniZincIDE.pro @@ -19,7 +19,7 @@ greaterThan(QT_MAJOR_VERSION, 4): { TARGET = MiniZincIDE TEMPLATE = app -VERSION = 2.1.2 +VERSION = 2.1.3 DEFINES += MINIZINC_IDE_VERSION=\\\"$$VERSION\\\" bundled {