Do implicit CTI to XML conversions without generating .xml files

When instantiating a phase from a .cti file, do the conversion in memory,
without writing the XML representation to disk. This eliminates the unrequrested
XML files that Cantera normally generates, and also avoids errors when running
Cantera from a directory where the user does not have write permissons.
This commit is contained in:
Ray Speth 2014-01-30 00:47:37 +00:00
parent aae8982924
commit ff9584105b
8 changed files with 156 additions and 75 deletions

View file

@ -818,6 +818,15 @@ Cantera::XML_Node getCtmlTree(const std::string& file);
*/ */
void ct2ctml(const char* file, const int debug = 0); void ct2ctml(const char* file, const int debug = 0);
//! Get a string with the ctml representation of a cti file.
/*!
* @param file Path to the input file in CTI format
* @return String containing the xml representation of the input file
*
* @ingroup inputfiles
*/
std::string ct2ctml_string(const std::string& file);
//! Convert a Chemkin-format mechanism into a CTI file. //! Convert a Chemkin-format mechanism into a CTI file.
/*! /*!
* @param in_file input file containing species and reactions * @param in_file input file containing species and reactions

View file

@ -18,6 +18,8 @@
from __future__ import print_function from __future__ import print_function
import sys
class CTI_Error(Exception): class CTI_Error(Exception):
"""Exception raised if an error is encountered while """Exception raised if an error is encountered while
parsing the input file. parsing the input file.
@ -130,8 +132,11 @@ class XMLnode(object):
s = ['<?xml version="1.0"?>\n'] s = ['<?xml version="1.0"?>\n']
self._write(s, 0) self._write(s, 0)
s.append('\n') s.append('\n')
with open(filename, 'w') as f: if isinstance(filename, str):
f.write(''.join(s)) with open(filename, 'w') as f:
f.write(''.join(s))
else:
filename.write(''.join(s))
def write_comment(self, s, level): def write_comment(self, s, level):
s.append('\n'+indent[level]+'<!--') s.append('\n'+indent[level]+'<!--')
@ -332,7 +337,9 @@ def write(outName=None):
for rx in _reactions: for rx in _reactions:
rx.build(r) rx.build(r)
if outName is not None: if outName == 'STDOUT':
x.write(sys.stdout)
elif outName is not None:
x.write(outName) x.write(outName)
elif _name != 'noname': elif _name != 'noname':
x.write(_name+'.xml') x.write(_name+'.xml')
@ -2583,7 +2590,7 @@ class Lindemann(object):
validate() validate()
def convert(filename, outName=None): def convert(filename, outName=None):
import os, sys import os
base = os.path.basename(filename) base = os.path.basename(filename)
root, _ = os.path.splitext(base) root, _ = os.path.splitext(base)
dataset(root) dataset(root)

View file

@ -281,10 +281,15 @@ XML_Node* Application::get_XML_File(const std::string& file, int debug)
return xmlfiles[ff]; return xmlfiles[ff];
} }
/* /*
* Ok, we didn't find the processed XML tree. Do the conversion * Ok, we didn't find the processed XML tree. Do the conversion from cti
* to xml, possibly overwriting the file, ff, in the process.
*/ */
ctml::ct2ctml(path.c_str(),debug); string phase_xml = ctml::ct2ctml_string(path);
XML_Node* x = new XML_Node("doc");
std::stringstream s(phase_xml);
x->build(s);
x->lock();
xmlfiles[ff] = x;
return x;
} else { } else {
ff = path; ff = path;
} }

View file

@ -52,53 +52,84 @@ static string pypath()
} }
void ct2ctml(const char* file, const int debug) void ct2ctml(const char* file, const int debug)
{
string xml = ct2ctml_string(file);
string out_name = file;
#ifdef _WIN32
// For Windows, make the path POSIX compliant so code looking for directory
// separators is simpler. Just look for '/' not both '/' and '\\'
std::replace_if(out_name.begin(), out_name.end(),
std::bind2nd(std::equal_to<char>(), '\\'), '/') ;
#endif
size_t idir = out_name.rfind('/');
if (idir != npos) {
out_name = out_name.substr(idir+1, out_name.size());
}
size_t idot = out_name.rfind('.');
if (idot != npos) {
out_name = out_name.substr(0, idot) + ".xml";
} else {
out_name += ".xml";
}
std::ofstream out(out_name.c_str());
out << xml;
}
std::string ct2ctml_string(const std::string& file)
{ {
#ifdef HAS_NO_PYTHON #ifdef HAS_NO_PYTHON
/* /*
* Section to bomb out if python is not * Section to bomb out if python is not
* present in the computation environment. * present in the computation environment.
*/ */
string ppath = file;
throw CanteraError("ct2ctml", throw CanteraError("ct2ctml",
"python cti to ctml conversion requested for file, " + ppath + "python cti to ctml conversion requested for file, " + file +
", but not available in this computational environment"); ", but not available in this computational environment");
#endif #endif
string python_output; string python_output, error_output;
int python_exit_code; int python_exit_code;
try { try {
exec_stream_t python; exec_stream_t python;
python.set_wait_timeout(exec_stream_t::s_all, 1800000); // 30 minutes python.set_wait_timeout(exec_stream_t::s_all, 1800000); // 30 minutes
python.start(pypath(), "-i"); stringstream output_stream, error_stream;
stringstream output_stream; std::vector<string> args;
python.in() << args.push_back("-c");
"if True:\n" << // Use this so that the rest is a single block args.push_back(
" import sys\n" << "from __future__ import print_function\n"
" sys.stderr = sys.stdout\n" << "import sys\n"
" try:\n" << "try:\n"
" from cantera import ctml_writer\n" << " from cantera import ctml_writer\n"
" except ImportError:\n" << "except ImportError:\n"
" print('sys.path: ' + repr(sys.path) + '\\n')\n" << " print('sys.path: ' + repr(sys.path) + '\\n', file=sys.stderr)\n"
" raise\n" << " raise\n"
" ctml_writer.convert(r'" << file << "')\n" << "ctml_writer.convert(r'" + file + "', 'STDOUT')\n"
" sys.exit(0)\n\n" << "sys.exit(0)\n");
"sys.exit(7)\n";
python.close_in(); python.start(pypath(), args.begin(), args.end());
std::string line; std::string line;
while (python.out().good()) { while (true) {
std::getline(python.out(), line); if (python.out().good()) {
output_stream << line << std::endl;; std::getline(python.out(), line);
output_stream << line << std::endl;
} else if (python.err().good()) {
std::getline(python.err(), line);
error_stream << line << std::endl;
} else {
break;
}
} }
python.close(); python.close();
python_exit_code = python.exit_code(); python_exit_code = python.exit_code();
python_output = stripws(output_stream.str()); error_output = stripws(error_stream.str());
python_output = output_stream.str();
} catch (std::exception& err) { } catch (std::exception& err) {
// Report failure to execute Python // Report failure to execute Python
stringstream message; stringstream message;
message << "Error executing python while converting input file:\n"; message << "Error executing python while converting input file:\n";
message << "Python command was: '" << pypath() << "'\n"; message << "Python command was: '" << pypath() << "'\n";
message << err.what() << std::endl; message << err.what() << std::endl;
throw CanteraError("ct2ctml", message.str()); throw CanteraError("ct2ctml_string", message.str());
} }
if (python_exit_code != 0) { if (python_exit_code != 0) {
@ -107,27 +138,29 @@ void ct2ctml(const char* file, const int debug)
message << "Error converting input file \"" << file << "\" to CTML.\n"; message << "Error converting input file \"" << file << "\" to CTML.\n";
message << "Python command was: '" << pypath() << "'\n"; message << "Python command was: '" << pypath() << "'\n";
message << "The exit code was: " << python_exit_code << "\n"; message << "The exit code was: " << python_exit_code << "\n";
if (python_output.size() > 0) { if (error_output.size() > 0) {
message << "-------------- start of converter log --------------\n"; message << "-------------- start of converter log --------------\n";
message << python_output << std::endl; message << error_output << std::endl;
message << "--------------- end of converter log ---------------"; message << "--------------- end of converter log ---------------";
} else { } else {
message << "The command did not produce any output." << endl; message << "The command did not produce any output." << endl;
} }
throw CanteraError("ct2ctml", message.str()); throw CanteraError("ct2ctml_string", message.str());
} }
if (python_output.size() > 0) { if (error_output.size() > 0) {
// Warn if there was any output from the conversion process // Warn if there was any output from the conversion process
stringstream message; stringstream message;
message << "Warning: Unexpected output from CTI converter\n"; message << "Warning: Unexpected output from CTI converter\n";
message << "-------------- start of converter log --------------\n"; message << "-------------- start of converter log --------------\n";
message << python_output << std::endl; message << error_output << std::endl;
message << "--------------- end of converter log ---------------\n"; message << "--------------- end of converter log ---------------\n";
writelog(message.str()); writelog(message.str());
} }
return python_output;
} }
void ck2cti(const std::string& in_file, const std::string& thermo_file, void ck2cti(const std::string& in_file, const std::string& thermo_file,
const std::string& transport_file, const std::string& id_tag) const std::string& transport_file, const std::string& id_tag)
{ {
@ -219,7 +252,7 @@ void ck2cti(const std::string& in_file, const std::string& thermo_file,
void get_CTML_Tree(Cantera::XML_Node* rootPtr, const std::string& file, const int debug) void get_CTML_Tree(Cantera::XML_Node* rootPtr, const std::string& file, const int debug)
{ {
std::string ff, ext = ""; std::string ext = "";
// find the input file on the Cantera search path // find the input file on the Cantera search path
std::string inname = findInputFile(file); std::string inname = findInputFile(file);
@ -238,27 +271,18 @@ void get_CTML_Tree(Cantera::XML_Node* rootPtr, const std::string& file, const in
ext = inname.substr(idot, inname.size()); ext = inname.substr(idot, inname.size());
} }
if (ext != ".xml" && ext != ".ctml") { if (ext != ".xml" && ext != ".ctml") {
try { string phase_xml = ctml::ct2ctml_string(inname);
ctml::ct2ctml(inname.c_str(), debug); stringstream s(phase_xml);
} catch (std::exception& err) { rootPtr->build(s);
writelog("get_CTML_Tree: caught an exception:\n"); return;
writelog(err.what());
}
string ffull = inname.substr(0,idot) + ".xml";
ff = "./" + getBaseName(ffull) + ".xml";
if (debug > 0) {
writelogf("ffull name = %s\n", ffull.c_str());
writelogf("ff name = %s\n", ff.c_str());
}
} else {
ff = inname;
} }
writelog("Attempting to parse xml file " + ff + "\n", debug);
ifstream fin(ff.c_str()); writelog("Attempting to parse xml file " + inname + "\n", debug);
ifstream fin(inname.c_str());
if (!fin) { if (!fin) {
throw throw
CanteraError("get_CTML_Tree", CanteraError("get_CTML_Tree",
"XML file " + ff + " not found"); "XML file " + inname + " not found");
} }
rootPtr->build(fin); rootPtr->build(fin);
fin.close(); fin.close();

View file

@ -35,23 +35,25 @@ def testRunner(target, source, env):
testResults.failed[passedFile.name] = program testResults.failed[passedFile.name] = program
def addTestProgram(subdir, progName): def addTestProgram(subdir, progName, env_vars={}):
""" """
Compile a test program and create a targets for running Compile a test program and create a targets for running
and resetting the test. and resetting the test.
""" """
program = localenv.Program(pjoin(subdir, progName), testenv = localenv.Clone()
mglob(localenv, subdir, 'cpp')) testenv['ENV'].update(env_vars)
program = testenv.Program(pjoin(subdir, progName),
mglob(testenv, subdir, 'cpp'))
passedFile = File(pjoin(str(program[0].dir), '%s.passed' % program[0].name)) passedFile = File(pjoin(str(program[0].dir), '%s.passed' % program[0].name))
PASSED_FILES[progName] = str(passedFile) PASSED_FILES[progName] = str(passedFile)
testResults.tests[passedFile.name] = program testResults.tests[passedFile.name] = program
run_program = localenv.Command(passedFile, program, testRunner) run_program = testenv.Command(passedFile, program, testRunner)
Alias('test', run_program) Alias('test', run_program)
Alias('test-%s' % progName, run_program) Alias('test-%s' % progName, run_program)
env['testNames'].append(progName) env['testNames'].append(progName)
if os.path.exists(passedFile.abspath): if os.path.exists(passedFile.abspath):
Alias('test-reset', localenv.Command('reset-%s%s' % (subdir, progName), Alias('test-reset', testenv.Command('reset-%s%s' % (subdir, progName),
[], [Delete(passedFile.abspath)])) [], [Delete(passedFile.abspath)]))
def addPythonTest(testname, subdir, script, interpreter, outfile, def addPythonTest(testname, subdir, script, interpreter, outfile,
@ -156,8 +158,18 @@ def addMatlabTest(script, testName, dependencies=None, env_vars=()):
return run_program return run_program
if localenv['python_package'] in ('full', 'minimal'):
python_env_vars = {'PYTHONPATH': Dir('#build/python2').abspath,
'PYTHON_CMD': localenv.subst('$python_cmd')}
elif localenv['python3_package'] == 'y':
python_env_vars = {'PYTHONPATH': Dir('#build/python3').abspath,
'PYTHON_CMD': localenv.subst('$python3_cmd')}
else:
python_env_vars = {} # Tests calling ck2cti or ctml_writer will fail
# Instantiate tests # Instantiate tests
addTestProgram('thermo', 'thermo') addTestProgram('thermo', 'thermo', env_vars=python_env_vars)
addTestProgram('kinetics', 'kinetics') addTestProgram('kinetics', 'kinetics')
python_subtests = [''] python_subtests = ['']
@ -191,18 +203,9 @@ if localenv['python3_package'] == 'y':
make_python_tests(3) make_python_tests(3)
if localenv['matlab_toolbox'] == 'y': if localenv['matlab_toolbox'] == 'y':
if localenv['python_package'] in ('full', 'minimal'):
env_vars = {'PYTHONPATH': Dir('#build/python2').abspath,
'PYTHON_CMD': localenv.subst('$python_cmd')}
elif localenv['python3_package'] == 'y':
env_vars = {'PYTHONPATH': Dir('#build/python3').abspath,
'PYTHON_CMD': localenv.subst('$python3_cmd')}
else:
env_vars = {} # ck2cti test is likely to fail
matlabTest = addMatlabTest('runCanteraTests.m', 'matlab', matlabTest = addMatlabTest('runCanteraTests.m', 'matlab',
dependencies=mglob(localenv, 'matlab', 'm'), dependencies=mglob(localenv, 'matlab', 'm'),
env_vars=env_vars) env_vars=python_env_vars)
localenv.Alias('test-matlab', matlabTest) localenv.Alias('test-matlab', matlabTest)
env['testNames'].append('matlab') env['testNames'].append('matlab')

View file

@ -9,4 +9,3 @@ assertEqual(length(dkm), nSpecies(gas))
function testImportCTI function testImportCTI
gas = importPhase('h2o2.cti'); gas = importPhase('h2o2.cti');
assertEqual(temperature(gas), 300) assertEqual(temperature(gas), 300)
delete('h2o2.xml');

View file

@ -1,6 +1,7 @@
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "cantera/thermo/FixedChemPotSSTP.h" #include "cantera/thermo/FixedChemPotSSTP.h"
#include "cantera/thermo/ThermoFactory.h" #include "cantera/thermo/ThermoFactory.h"
#include "cantera/base/ctml.h"
namespace Cantera namespace Cantera
{ {
@ -28,4 +29,38 @@ TEST_F(FixedChemPotSstpConstructorTest, SimpleConstructor)
ASSERT_FLOAT_EQ(-2.3e7, mu); ASSERT_FLOAT_EQ(-2.3e7, mu);
} }
#ifndef HAS_NO_PYTHON // skip these tests if the Python converter is unavailable
class InputFileConversionTest : public testing::Test
{
public:
InputFileConversionTest() {
appdelete();
}
ThermoPhase* p1;
ThermoPhase* p2;
void compare()
{
ASSERT_EQ(p1->nSpecies(), p2->nSpecies());
for (size_t i = 0; i < p1->nSpecies(); i++) {
ASSERT_EQ(p1->speciesName(i), p2->speciesName(i));
ASSERT_EQ(p1->molecularWeight(i), p2->molecularWeight(i));
}
}
};
TEST_F(InputFileConversionTest, ExplicitConversion) {
p1 = newPhase("../data/air-no-reactions.xml", "");
ctml::ct2ctml("../data/air-no-reactions.cti");
p2 = newPhase("air-no-reactions.xml", "");
compare();
}
TEST_F(InputFileConversionTest, ImplicitConversion) {
p1 = newPhase("../data/air-no-reactions.xml", "");
p2 = newPhase("../data/air-no-reactions.cti", "");
compare();
}
#endif
} // namespace Cantera } // namespace Cantera

View file

@ -279,8 +279,7 @@ CompileAndTest('pecosTransport', 'PecosTransport', 'pecosTransport', 'output_ble
CompileAndTest('printUtil', 'printUtilUnitTest', 'pUtest', 'output_blessed.txt') CompileAndTest('printUtil', 'printUtilUnitTest', 'pUtest', 'output_blessed.txt')
CompileAndTest('pureFluid', 'pureFluidTest', 'testPureWater', 'output_blessed.txt') CompileAndTest('pureFluid', 'pureFluidTest', 'testPureWater', 'output_blessed.txt')
if haveConverters: if haveConverters:
CompileAndTest('rankine_democxx', 'rankine_democxx', 'rankine', 'output_blessed.txt', CompileAndTest('rankine_democxx', 'rankine_democxx', 'rankine', 'output_blessed.txt')
artifacts=['liquidvapor.xml'])
CompileAndTest('silane_equil', 'silane_equil', 'silane_equi', 'output_blessed.txt') CompileAndTest('silane_equil', 'silane_equil', 'silane_equi', 'output_blessed.txt')
# spectroscopy is incomplete # spectroscopy is incomplete
CompileAndTest('simpleTransport', 'simpleTransport', 'simpleTransport', CompileAndTest('simpleTransport', 'simpleTransport', 'simpleTransport',
@ -291,12 +290,12 @@ CompileAndTest('surfkin', 'surfkin', 'surfdemo', 'output_blessed.txt')
CompileAndTest('surfSolver', 'surfSolverTest', 'surfaceSolver', None, CompileAndTest('surfSolver', 'surfSolverTest', 'surfaceSolver', None,
arguments='haca2.xml', arguments='haca2.xml',
comparisons=[('results_blessed.txt', 'results.txt')], comparisons=[('results_blessed.txt', 'results.txt')],
artifacts=['results.txt', 'diamond.xml'], artifacts=['results.txt'],
extensions=['^surfaceSolver.cpp']) extensions=['^surfaceSolver.cpp'])
CompileAndTest('surfSolver2', 'surfSolverTest', 'surfaceSolver2', None, CompileAndTest('surfSolver2', 'surfSolverTest', 'surfaceSolver2', None,
arguments='haca2.xml', arguments='haca2.xml',
comparisons=[('results2_blessed.txt', 'results2.txt')], comparisons=[('results2_blessed.txt', 'results2.txt')],
artifacts=['results2.txt', 'diamond.xml'], artifacts=['results2.txt'],
extensions=['^surfaceSolver2.cpp']) extensions=['^surfaceSolver2.cpp'])
CompileAndTest('VCS-NaCl', pjoin('VCSnonideal', 'NaCl_equil'), CompileAndTest('VCS-NaCl', pjoin('VCSnonideal', 'NaCl_equil'),
'nacl_equil', 'good_out.txt', 'nacl_equil', 'good_out.txt',