Cantera  2.3.0
ct2ctml.cpp
Go to the documentation of this file.
1 /**
2  * @file ct2ctml.cpp
3  * Driver for the system call to the python executable that converts
4  * cti files to ctml files (see \ref inputfiles).
5  */
6 
7 // This file is part of Cantera. See License.txt in the top-level directory or
8 // at http://www.cantera.org/license.txt for license and copyright information.
9 
10 #include "cantera/base/ctml.h"
12 #include "../../ext/libexecstream/exec-stream.h"
13 
14 #include <cstdio>
15 #include <fstream>
16 #include <sstream>
17 #include <functional>
18 
19 #ifdef _WIN32
20 #include <windows.h>
21 #else
22 #include <unistd.h>
23 #endif
24 
25 using namespace std;
26 
27 namespace Cantera
28 {
29 
30 //! return the full path to the Python interpreter.
31 /*!
32  * Use the environment variable PYTHON_CMD if it is set. If not, return
33  * the string 'python'.
34  *
35  * Note, there are hidden problems here that really direct us to use a full
36  * pathname for the location of python. Basically the system call will use the
37  * shell /bin/sh, in order to launch python. This default shell may not be the
38  * shell that the user is employing. Therefore, the default path to python may
39  * be different during a system call than during the default user shell
40  * environment. This is quite a headache. The answer is to always set the
41  * PYTHON_CMD environmental variable in the user environment to an absolute path
42  * to locate the python executable. Then this issue goes away.
43  */
44 static string pypath()
45 {
46  string s = "python";
47  const char* py = getenv("PYTHON_CMD");
48 
49  if (py) {
50  string sp = ba::trim_copy(string(py));
51  if (sp.size() > 0) {
52  s = sp;
53  }
54  }
55  return s;
56 }
57 
58 void ct2ctml(const char* file, const int debug)
59 {
60  string xml = ct2ctml_string(file);
61  string out_name = file;
62 #ifdef _WIN32
63  // For Windows, make the path POSIX compliant so code looking for directory
64  // separators is simpler. Just look for '/' not both '/' and '\\'
65  std::replace_if(out_name.begin(), out_name.end(),
66  std::bind2nd(std::equal_to<char>(), '\\'), '/');
67 #endif
68  size_t idir = out_name.rfind('/');
69  if (idir != npos) {
70  out_name = out_name.substr(idir+1, out_name.size());
71  }
72  size_t idot = out_name.rfind('.');
73  if (idot != npos) {
74  out_name = out_name.substr(0, idot) + ".xml";
75  } else {
76  out_name += ".xml";
77  }
78  std::ofstream out(out_name);
79  out << xml;
80 }
81 
82 static std::string call_ctml_writer(const std::string& text, bool isfile)
83 {
84  std::string file, arg;
85  bool temp_file_created = false;
86  std::string temp_cti_file_name = std::tmpnam(nullptr);
87 
88  if (temp_cti_file_name.find('\\') == 0) {
89  // Some versions of MinGW give paths in the root directory. Using the
90  // current directory is more likely to succeed.
91  temp_cti_file_name = "." + temp_cti_file_name;
92  }
93 
94  if (isfile) {
95  file = text;
96  arg = "r'" + text + "'";
97  } else {
98  file = "<string>";
99  arg = "text=r'''" + text + "'''";
100  }
101 
102  // If the user wants to convert a mechanism using a text passed via the
103  // source="""..."""
104  // argument in python, then we have to make sure that it is short enough
105  // to fit in the command line when routed to python as:
106  // python -c ...
107  // statement downstream in the code
108 
109  // So, check the max size of a string that can be passed on the command line
110  // This is OS Specific. *nix systems have the sysconf() function that tells
111  // us the largest argument we can pass. Since such a function does not exist
112  // for Windows, we set a safe limit of 32 kB
113 
114 #ifdef _WIN32
115  long int max_argv_size = 32768;
116 #else
117  long int max_argv_size = sysconf(_SC_ARG_MAX);
118 #endif
119 
120  if (text.size() > static_cast<size_t>(max_argv_size) - 500) {
121  // If the file is too big to be passed as a command line argument later
122  // in the file, then create a temporary file and execute this function
123  // as though an input file was specified as the source.
124  // We assume the text passed + 500 chars = total size of argv
125 
126  ofstream temp_cti_file(temp_cti_file_name);
127 
128  if (temp_cti_file) {
129  temp_cti_file << text;
130  file = temp_cti_file_name;
131  arg = "r'" + file + "'";
132  temp_file_created = true;
133  } else {
134  // If we are here, then a temp file could not be created
135  throw CanteraError("call_ctml_writer", "Very long source argument. "
136  "Error creating temporary file '{}'", temp_cti_file_name);
137  }
138  }
139 
140 #ifdef HAS_NO_PYTHON
141  //! Section to bomb out if python is not present in the computation
142  //! environment.
143  throw CanteraError("ct2ctml",
144  "python cti to ctml conversion requested for file, " + file +
145  ", but not available in this computational environment");
146 #endif
147 
148  string python_output, error_output;
149  int python_exit_code;
150  try {
151  exec_stream_t python;
152  python.set_wait_timeout(exec_stream_t::s_all, 1800000); // 30 minutes
153  stringstream output_stream, error_stream;
154  std::vector<string> args;
155  args.push_back("-c");
156 
157  args.push_back(
158  "from __future__ import print_function\n"
159  "import sys\n"
160  "try:\n"
161  " from cantera import ctml_writer\n"
162  "except ImportError:\n"
163  " print('sys.path: ' + repr(sys.path) + '\\n', file=sys.stderr)\n"
164  " raise\n"
165  "ctml_writer.convert(" + arg + ", outName='STDOUT')\n"
166  "sys.exit(0)\n");
167 
168  python.start(pypath(), args.begin(), args.end());
169  std::string line;
170 
171  while (python.out().good()) {
172  std::getline(python.out(), line);
173  output_stream << line << std::endl;
174  }
175 
176 #ifdef _WIN32
177  // Sleeping for 1 ms prevents a (somewhat inexplicable) deadlock while
178  // reading from the stream.
179  Sleep(1);
180 #endif
181  while (python.err().good()) {
182  std::getline(python.err(), line);
183  error_stream << line << std::endl;
184  }
185  python.close();
186  python_exit_code = python.exit_code();
187  error_output = ba::trim_copy(error_stream.str());
188  python_output = output_stream.str();
189  } catch (std::exception& err) {
190  // Report failure to execute Python
191  stringstream message;
192  message << "Error executing python while converting input file:\n";
193  message << "Python command was: '" << pypath() << "'\n";
194  message << err.what() << std::endl;
195  throw CanteraError("ct2ctml_string", message.str());
196  }
197 
198  if (python_exit_code != 0) {
199  // Report a failure in the conversion process
200  stringstream message;
201  message << "Error converting input file \"" << file << "\" to CTML.\n";
202  message << "Python command was: '" << pypath() << "'\n";
203  message << "The exit code was: " << python_exit_code << "\n";
204  if (error_output.size() > 0) {
205  message << "-------------- start of converter log --------------\n";
206  message << error_output << std::endl;
207  message << "--------------- end of converter log ---------------";
208  } else {
209  message << "The command did not produce any output." << endl;
210  }
211  throw CanteraError("ct2ctml_string", message.str());
212  }
213 
214  if (error_output.size() > 0) {
215  // Warn if there was any output from the conversion process
216  stringstream message;
217  message << "Warning: Unexpected output from CTI converter\n";
218  message << "-------------- start of converter log --------------\n";
219  message << error_output << std::endl;
220  message << "--------------- end of converter log ---------------\n";
221  writelog(message.str());
222  }
223 
224  if (temp_file_created) {
225  // A temp file was created and has to be removed
226  bool status = std::remove(temp_cti_file_name.c_str());
227  if (status) {
228  writelog("WARNING: Error removing tmp file {}\n", temp_cti_file_name);
229  }
230  }
231 
232  return python_output;
233 }
234 
235 std::string ct2ctml_string(const std::string& file)
236 {
237  return call_ctml_writer(file, true);
238 }
239 
240 std::string ct_string2ctml_string(const std::string& cti)
241 {
242  return call_ctml_writer(cti, false);
243 }
244 
245 void ck2cti(const std::string& in_file, const std::string& thermo_file,
246  const std::string& transport_file, const std::string& id_tag)
247 {
248 #ifdef HAS_NO_PYTHON
249  //! Section to bomb out if python is not present in the computation
250  //! environment.
251  string ppath = in_file;
252  throw CanteraError("ct2ctml",
253  "python ck to cti conversion requested for file, " + ppath +
254  ", but not available in this computational environment");
255 #endif
256 
257  string python_output;
258  int python_exit_code;
259  try {
260  exec_stream_t python;
261  python.set_wait_timeout(exec_stream_t::s_all, 1800000); // 30 minutes
262  python.start(pypath(), "-i");
263  stringstream output_stream;
264 
265  ostream& pyin = python.in();
266  pyin << "if True:\n" << // Use this so that the rest is a single block
267  " import sys\n" <<
268  " sys.stderr = sys.stdout\n" <<
269  " try:\n" <<
270  " from cantera import ck2cti\n" <<
271  " except ImportError:\n" <<
272  " print('sys.path: ' + repr(sys.path))\n" <<
273  " raise\n"
274  " ck2cti.Parser().convertMech(r'" << in_file << "',";
275  if (thermo_file != "" && thermo_file != "-") {
276  pyin << " thermoFile=r'" << thermo_file << "',";
277  }
278  if (transport_file != "" && transport_file != "-") {
279  pyin << " transportFile=r'" << transport_file << "',";
280  }
281  pyin << " phaseName='" << id_tag << "',";
282  pyin << " permissive=True,";
283  pyin << " quiet=True)\n";
284  pyin << " sys.exit(0)\n\n";
285  pyin << "sys.exit(7)\n";
286  python.close_in();
287 
288  std::string line;
289  while (python.out().good()) {
290  std::getline(python.out(), line);
291  output_stream << line << std::endl;;
292  }
293  python.close();
294  python_exit_code = python.exit_code();
295  python_output = ba::trim_copy(output_stream.str());
296  } catch (std::exception& err) {
297  // Report failure to execute Python
298  stringstream message;
299  message << "Error executing python while converting input file:\n";
300  message << "Python command was: '" << pypath() << "'\n";
301  message << err.what() << std::endl;
302  throw CanteraError("ct2ctml", message.str());
303  }
304 
305  if (python_exit_code != 0) {
306  // Report a failure in the conversion process
307  stringstream message;
308  message << "Error converting input file \"" << in_file << "\" to CTI.\n";
309  message << "Python command was: '" << pypath() << "'\n";
310  message << "The exit code was: " << python_exit_code << "\n";
311  if (python_output.size() > 0) {
312  message << "-------------- start of converter log --------------\n";
313  message << python_output << std::endl;
314  message << "--------------- end of converter log ---------------";
315  } else {
316  message << "The command did not produce any output." << endl;
317  }
318  throw CanteraError("ck2cti", message.str());
319  }
320 
321  if (python_output.size() > 0) {
322  // Warn if there was any output from the conversion process
323  stringstream message;
324  message << "Warning: Unexpected output from CTI converter\n";
325  message << "-------------- start of converter log --------------\n";
326  message << python_output << std::endl;
327  message << "--------------- end of converter log ---------------\n";
328  writelog(message.str());
329  }
330 }
331 
332 }
std::string ct2ctml_string(const std::string &file)
Get a string with the ctml representation of a cti file.
Definition: ct2ctml.cpp:235
CTML ("Cantera Markup Language") is the variant of XML that Cantera uses to store data...
const size_t npos
index returned by functions to indicate "no position"
Definition: ct_defs.h:165
void writelog(const std::string &fmt, const Args &... args)
Write a formatted message to the screen.
Definition: global.h:179
STL namespace.
void ct2ctml(const char *file, const int debug)
Convert a cti file into a ctml file.
Definition: ct2ctml.cpp:58
void ck2cti(const std::string &in_file, const std::string &thermo_file, const std::string &transport_file, const std::string &id_tag)
Convert a Chemkin-format mechanism into a CTI file.
Definition: ct2ctml.cpp:245
Base class for exceptions thrown by Cantera classes.
Definition: ctexceptions.h:65
std::string ct_string2ctml_string(const std::string &cti)
Get a string with the ctml representation of a cti input string.
Definition: ct2ctml.cpp:240
static string pypath()
return the full path to the Python interpreter.
Definition: ct2ctml.cpp:44
Contains declarations for string manipulation functions within Cantera.
Namespace for the Cantera kernel.
Definition: application.cpp:29