pelib  2.0.0
src/AmplSolver.cpp
Go to the documentation of this file.
00001 /*
00002  Copyright 2015 Nicolas Melot
00003 
00004  This file is part of Pelib.
00005 
00006  Pelib is free software: you can redistribute it and/or modify
00007  it under the terms of the GNU General Public License as published by
00008  the Free Software Foundation, either version 3 of the License, or
00009  (at your option) any later version.
00010 
00011  Pelib is distributed in the hope that it will be useful,
00012  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00014  GNU General Public License for more details.
00015 
00016  You should have received a copy of the GNU General Public License
00017  along with Pelib. If not, see <http://www.gnu.org/licenses/>.
00018 */
00019 
00020 
00021 #include <sys/types.h>
00022 #include <sys/wait.h>
00023 #include <ext/stdio_filebuf.h>
00024 #include <boost/regex.hpp>
00025 
00026 #include <pelib/PelibException.hpp>
00027 #include <pelib/AmplSolver.hpp>
00028 #include <pelib/AmplInput.hpp>
00029 #include <pelib/AmplOutput.hpp>
00030 
00031 #ifdef debug
00032 #undef debug
00033 #endif
00034 
00035 #define debug(var) cout << "[" << __FILE__ << ":" << __FUNCTION__ << ":" << __LINE__ << "] " << #var << " = \"" << (var) << "\"" << endl;
00036 
00037 using namespace std;
00038 using namespace boost;
00039 using namespace pelib;
00040 
00041 static
00042 string
00043 istream_to_string(istream &in)
00044 {
00045         istreambuf_iterator<char> eos;
00046         return string(istreambuf_iterator<char>(in), eos);
00047 }
00048 
00049 void
00050 AmplSolver::initModelFromStream(istream &model, const string &model_filename, istream &run, bool showOutput, bool showError)
00051 {
00052         this->model = istream_to_string(model);
00053         this->run = istream_to_string(run);
00054         this->model_file = model_filename;
00055         this->showOutput = showOutput;
00056         this->showError = showError;
00057 }
00058 
00059 AmplSolver::AmplSolver(istream &model, const string &model_filename, istream &run, bool showOutput, bool showError)
00060 {
00061         initModelFromStream(model, model_filename, run, showOutput, showError);
00062 }
00063 
00064 AmplSolver::AmplSolver(istream &model, const string &model_filename, istream &run, const map<const string, const Algebra> &data, bool showOutput, bool showError)
00065 {
00066         initModelFromStream(model, model_filename, run, showOutput, showError);
00067         this->data = data;
00068 }
00069 
00070 const Algebra*
00071 AmplSolver::solve() const
00072 {
00073         map<const string, double> statistics;
00074         return new Algebra(this->solve(this->data, statistics));
00075 }
00076 
00077 const Algebra*
00078 AmplSolver::solve(map<const string, double> &statistics) const
00079 {
00080         return new Algebra(this->solve(this->data, statistics));
00081 }
00082 
00083 // From http://codereview.stackexchange.com/questions/71793/function-to-grab-stdin-stdout-stderr-of-a-child-process
00084 static
00085 int
00086 opencmd(string cmd, vector<string> argv, istream &stdin, string &out, string &err)
00087 {
00088         int infd[2];
00089         int outfd[2];
00090         int errfd[2];
00091         pid_t pid;
00092 
00093         argv.insert(argv.begin(), cmd);
00094         char **c_argv = (char**)malloc(sizeof(char*) * (argv.size() + 1));
00095         c_argv[argv.size()] = NULL;
00096         //debug("Hello world!");
00097         //cout << cmd << " ";
00098         for(vector<string>::iterator i = argv.begin(); i != argv.end(); i++)
00099         {
00100                 size_t ii = distance(argv.begin(), i);
00101                 c_argv[ii] = (char*)i->c_str();
00102                 //cout << "'" << c_argv[ii] << "' ";
00103         }
00104         //cout << endl;
00105         //debug("Hello world!");
00106 
00107         /*
00108          * Creating pipes in between ...
00109          */
00110         if(pipe(infd) != 0)
00111         {
00112                 return -1;
00113         }
00114 
00115         if(pipe(outfd) != 0)
00116         {
00117                 close(infd[0]);
00118                 close(infd[1]);
00119 
00120                 return -1;
00121         }
00122 
00123         if(pipe(errfd))
00124         {
00125                 close(outfd[0]);
00126                 close(outfd[1]);
00127                 close(infd[0]);
00128                 close(infd[1]);
00129 
00130                 return -1;
00131         }
00132 
00133         /*
00134          * Starting actual processing ...
00135          */
00136         pid = fork();
00137         switch (pid) {
00138                 case -1:       /* Error */
00139                         close(errfd[0]);
00140                         close(errfd[1]);
00141                         close(outfd[0]);
00142                         close(outfd[1]);
00143                         close(infd[0]);
00144                         close(infd[1]);
00145                 
00146                         return -1;
00147                 break;
00148                 case 0:        /* Child */
00149                         close(infd[1]);
00150                         close(outfd[0]);
00151                         close(errfd[0]);
00152 
00153                         dup2(infd[0], 0);    /* redirect child stdin to in[0] */
00154                         dup2(outfd[1], 1);   /* redirect child stdout to out[1] */
00155                         dup2(errfd[1], 2);   /* redirect child stderr to err[1] */
00156                         execvp(cmd.c_str(), c_argv); /* actual command execution */
00157 
00158                         return -1; /* shall never be executed */
00159                 break;
00160                 default:       /* Parent */
00161                         close(infd[0]);  /* no need to read its stdin */
00162                         close(outfd[1]); /* no need to write to its stdout */
00163                         close(errfd[1]); /* no need to write to its stderr */
00164 
00165                         // Turn fd into string stream
00166                         // hack seen here:
00167                         // http://stackoverflow.com/questions/2746168/how-to-construct-a-c-fstream-from-a-posix-file-descriptor
00168                         __gnu_cxx::stdio_filebuf<char> filein(infd[1], ios::out);
00169                         ostream pstdin(&filein);
00170 
00171                         __gnu_cxx::stdio_filebuf<char> fileout(outfd[0], ios::in);
00172                         istream pstdout(&fileout);
00173                         __gnu_cxx::stdio_filebuf<char> fileerr(errfd[0], ios::in);
00174                         istream pstderr(&fileerr);
00175 
00176                         // Write stdin to process' stdin
00177                         string string = istream_to_string(stdin);
00178                         pstdin << string;
00179 
00180                         // This will not run before the solver is done
00181                         out = istream_to_string(pstdout);
00182                         err = istream_to_string(pstderr);
00183 
00184                         // Wait for child process to terminate
00185                         int status;
00186                         waitpid(pid, &status, WUNTRACED);
00187 
00188                         /*
00189                         debug(WIFSIGNALED(status));
00190                         debug(WIFEXITED(status));
00191                         debug(WEXITSTATUS(status));
00192                         */
00193 
00194                         if(WIFEXITED(status) == 1 && WIFSIGNALED(status) == 0)
00195                         {
00196                                 status = WEXITSTATUS(status);
00197                                 //debug(status);
00198                         }
00199                         else
00200                         {
00201                                 status = 0;
00202                                 //debug(status);
00203                         }
00204 
00205                         // Close streams
00206                         filein.close();
00207                         fileout.close();
00208                         fileerr.close();
00209 
00210                         // Free argument list
00211                         free(c_argv);
00212                         return status;
00213                 break;
00214         }
00215 }
00216 
00217 Algebra
00218 AmplSolver::solve(const std::map<const std::string, const Algebra> &data) const
00219 {
00220         map<const string, double> statistics;
00221         return solve(data, statistics);
00222 }
00223 
00224 Algebra
00225 AmplSolver::solve(const std::map<const std::string, const Algebra> &data, map<const string, double> &statistics) const
00226 {
00227         if(this->model.compare("") != 0 && this->run.compare("") != 0)
00228         {
00229                 // Do solving
00230                 stringstream in;
00231                 string out, err;
00232                 vector<string> args;
00233 
00234                 size_t counter = 1;
00235                 stringstream str;
00236                 str << counter;
00237                 string newrun = run;
00238                 newrun = boost::regex_replace(newrun, regex("\\\\"), string("\\\\\\\\\\\\\\\\\\\\\\\\"));
00239                 newrun = boost::regex_replace(newrun, regex("\\$"), string("\\\\\\\\\\\\$"));
00240                 newrun = boost::regex_replace(newrun, regex("^\\s*model\\s+" + this->model_file + "\\s*;"), string("model \\\\\\$" + str.str() + ";"));
00241                 counter++;
00242 
00243                 string data_args;
00244 
00245                 for(map<string, const Algebra>::const_iterator i = data.begin(); i != data.end(); i++)
00246                 {
00247                         stringstream str;
00248                         str << counter;
00249 
00250                         // Replace data statement in model
00251                         newrun = boost::regex_replace(newrun, regex("^\\s*data\\s+" + i->first + "\\s*;"), string("data \\\\\\$" + str.str() + ";"));
00252 
00253                         // Build input data sources
00254                         stringstream data;
00255                         AmplInput(AmplInput::intFloatHandlers()).dump(data, i->second);
00256                         // Fixes possible quote and backslashes issues in input data file
00257                         string data_str = boost::regex_replace(data.str(), regex("\""), string("\"'\"'\""));
00258                         data_str = boost::regex_replace(data_str, regex("\\\\"), string("\\\\"));
00259                         data_str = boost::regex_replace(data_str, regex("\\$"), string("\\$"));
00260 
00261                         data_args += " <(echo '" + data_str + "')";
00262                         counter++;
00263                 }
00264 
00265                 // Fix possible quotes and backslashes issues in input data file
00266                 newrun = boost::regex_replace(newrun, regex("'"), string("\\'"));
00267                 newrun = boost::regex_replace(newrun, regex("\""), string("\\\\\"'\\\\\"'\\\\\""));
00268 
00269                 args.push_back("-c");
00270                 args.push_back("bash <(echo -e \"#!/bin/bash\nampl <(echo -e \\\"" + newrun + "\\\");\") <(echo -e \"" + model + "\")" + data_args + "");
00271                 int success = opencmd("bash", args, in, out, err);
00272 
00273                 bool optimal = out.find("time limit") == std::string::npos && out.find("optimal solution") != std::string::npos;
00274                 bool feasible = !((out.find("infeasible") != std::string::npos) || (out.find(" cannot hold") != std::string::npos) || (out.find("bailing out") != std::string::npos) || (err.find(" but lower bound") != std::string::npos) || (out.find("No primal or dual variables returned") != std::string::npos) || (err.find("Logical constraint is always false") != std::string::npos));
00275                 
00276                 statistics.insert(pair<string, double>(string("optimal"), optimal ? 1 : 0));
00277                 statistics.insert(pair<string, double>(string("feasible"), feasible ? 1 : 0));
00278 
00279                 if(showError)
00280                 {
00281                         cerr << err;
00282                 }
00283                 
00284                 if(showOutput)
00285                 {
00286                         cout << out;
00287                 }
00288 
00289                 // If the solver didn't fail, but determined that their is no possible solution, then report output
00290                 if(success == 0 || !feasible)
00291                 {
00292                         out = boost::regex_replace(out, regex("^\\s*absmipgap[^\\n]*\\n"), string(""));
00293                         stringstream outstr(out);
00294                         Algebra solution = AmplOutput(AmplOutput::floatHandlers()).parse(outstr);
00295 
00296                         if(solution.find<Scalar<float> >("_total_solve_elapsed_time") != NULL)
00297                         {
00298                                 Scalar<float> time = *solution.find<Scalar<float> >("_total_solve_elapsed_time");
00299                                 statistics.insert(pair<const string, double>(string("time"), time.getValue()));
00300                         }
00301                         return solution;
00302                 }
00303                 else
00304                 {       
00305                         stringstream ss;
00306                         ss << success;
00307                         throw PelibException(string("Ampl exited with value ") + ss.str() + string("."));
00308                 }
00309         }
00310         else
00311         {
00312                 throw PelibException(string("Missing model or run script to run with Ampl."));
00313         }
00314 }
00315