diff --git a/eo/src/eo b/eo/src/eo index 39109bdb6..89fac3d0d 100644 --- a/eo/src/eo +++ b/eo/src/eo @@ -78,6 +78,7 @@ #include "eoEvalCounterThrowException.h" #include "eoEvalTimeThrowException.h" #include "eoEvalUserTimeThrowException.h" +#include "eoEvalCmd.h" // Continuators - all include eoContinue.h #include "eoCombinedContinue.h" diff --git a/eo/src/eoEvalCmd.h b/eo/src/eoEvalCmd.h new file mode 100644 index 000000000..57d9a9712 --- /dev/null +++ b/eo/src/eoEvalCmd.h @@ -0,0 +1,142 @@ +/* +(c) Thales group, 2020 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; + version 2 of the License. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Contact: http://eodev.sourceforge.net + +Authors: +Johann Dréo +*/ + +#ifndef eoEvalCmd_H +#define eoEvalCmd_H + +#include +#include + +#include "eoEvalFunc.h" +#include "eoExceptions.h" + + +/** Call a system command to evaluate an individual. + * + * @note Tested only under Unix systems, may not be portable as is. + * + * Use the default string serialization of the EOT + * and pass it as command line arguments of the command. + * The command is expected to output on stdout a string + * that can be interpreted as a float by `atof`. + * + * @todo Use the serialization of fitness instead of atof. + * + * For example, an eoReal would lead to a call like: + * ```>&2 cmd INVALID 3 1.0 2.1 3.2``` + * + * Throw an eoSystemError exception if the command exited with + * a return code different from zero. + * + *@ingroup Evaluation + */ +template +class eoEvalCmd : public eoEvalFunc< EOT > +{ +public: + using Fitness = typename EOT::Fitness; + + /** Constructor + * + * @note The prefix and suffix are automatically + * separated from the command by a space. + * + * The formated command looks like: `prefix cmd infix sol suffix` + * + * The default prefix allows to redirect any output to stdout under Unix. + * + * @param cmd The command to run. + * @param prefix Inserted before the command + * @param suffix Inserted between cmd and the serialized solution. + * @param suffix Append after the solution. + */ + eoEvalCmd( const std::string cmd, + const std::string prefix = ">&1", + const std::string infix = "", + const std::string suffix = "" + ) : + _cmd(cmd), + _suffix(suffix), + _infix(infix), + _prefix(prefix), + _last_call("") + {} + + virtual void operator()( EOT& sol ) + { + // Any modification to sol would makes it invalid, + // it is thus useless to evaluate it, if it is not invalid. + if(not sol.invalid()) { + return; + } + + sol.fitness( call( sol ) ); + } + + //! Return the last command string that was called. + std::string last_call() const + { + return _last_call; + } + +private: + const std::string _cmd; + const std::string _prefix; + const std::string _infix; + const std::string _suffix; + std::string _last_call; + + Fitness call( EOT& sol ) + { + std::array buffer; + std::string result; + + std::ostringstream cmd; + + cmd << _prefix << " " << _cmd << " " + << _infix << " " << sol << " " << _suffix; + + // Keep track of the built command for debugging purpose. + _last_call = cmd.str(); + + FILE* pipe = popen(cmd.str().c_str(), "r"); + if(not pipe) { + throw eoSystemError(cmd.str()); + } + while(fgets(buffer.data(), BUFFER_SIZE, pipe) != NULL) { + result += buffer.data(); + } + auto return_code = pclose(pipe); + + if(return_code != 0) { + throw eoSystemError(cmd.str(), return_code, result); + } + + // FIXME Use serialized input for the fitness instead of atof. + Fitness f = std::atof(result.c_str()); + + return f; + } +}; + +#endif // eoEvalCmd_H diff --git a/eo/test/CMakeLists.txt b/eo/test/CMakeLists.txt index aa4f17035..d776237f6 100644 --- a/eo/test/CMakeLists.txt +++ b/eo/test/CMakeLists.txt @@ -70,6 +70,7 @@ set (TEST_LIST #t-eoDualFitness t-eoParser t-eoPartiallyMappedXover + t-eoEvalCmd ) diff --git a/eo/test/t-eoEvalCmd.cpp b/eo/test/t-eoEvalCmd.cpp new file mode 100644 index 000000000..d9e812275 --- /dev/null +++ b/eo/test/t-eoEvalCmd.cpp @@ -0,0 +1,29 @@ +#include + +#include +#include + +using namespace std; + +int main(int /*argc*/, char* /*argv[]*/) +{ + typedef eoReal EOT; + + // Build something like: ">&1 echo 1.2; # INVALID 2 1 2" + eoEvalCmd eval("echo 1.2", ">&1", "; #"); + + EOT sol = {1,2}; + std::clog << sol << std::endl; + + try { + eval(sol); + } catch(eoSystemError& e) { + std::cerr << e.what() << std::endl; + exit(1); + } + + std::clog << eval.last_call() << std::endl; + EOT::Fitness f = sol.fitness(); + std::clog << "fitness: " << f << std::endl; + assert(f = 1.2); +}