pelib
2.0.0
|
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