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);
//! 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.
/*!
* @param in_file input file containing species and reactions

View file

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

View file

@ -281,10 +281,15 @@ XML_Node* Application::get_XML_File(const std::string& file, int debug)
return xmlfiles[ff];
}
/*
* Ok, we didn't find the processed XML tree. Do the conversion
* to xml, possibly overwriting the file, ff, in the process.
* Ok, we didn't find the processed XML tree. Do the conversion from cti
*/
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 {
ff = path;
}

View file

@ -52,53 +52,84 @@ static string pypath()
}
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
/*
* Section to bomb out if python is not
* present in the computation environment.
*/
string ppath = file;
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");
#endif
string python_output;
string python_output, error_output;
int python_exit_code;
try {
exec_stream_t python;
python.set_wait_timeout(exec_stream_t::s_all, 1800000); // 30 minutes
python.start(pypath(), "-i");
stringstream output_stream;
python.in() <<
"if True:\n" << // Use this so that the rest is a single block
" import sys\n" <<
" sys.stderr = sys.stdout\n" <<
" try:\n" <<
" from cantera import ctml_writer\n" <<
" except ImportError:\n" <<
" print('sys.path: ' + repr(sys.path) + '\\n')\n" <<
" raise\n" <<
" ctml_writer.convert(r'" << file << "')\n" <<
" sys.exit(0)\n\n" <<
"sys.exit(7)\n";
python.close_in();
stringstream output_stream, error_stream;
std::vector<string> args;
args.push_back("-c");
args.push_back(
"from __future__ import print_function\n"
"import sys\n"
"try:\n"
" from cantera import ctml_writer\n"
"except ImportError:\n"
" print('sys.path: ' + repr(sys.path) + '\\n', file=sys.stderr)\n"
" raise\n"
"ctml_writer.convert(r'" + file + "', 'STDOUT')\n"
"sys.exit(0)\n");
python.start(pypath(), args.begin(), args.end());
std::string line;
while (python.out().good()) {
std::getline(python.out(), line);
output_stream << line << std::endl;;
while (true) {
if (python.out().good()) {
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_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) {
// Report failure to execute Python
stringstream message;
message << "Error executing python while converting input file:\n";
message << "Python command was: '" << pypath() << "'\n";
message << err.what() << std::endl;
throw CanteraError("ct2ctml", message.str());
throw CanteraError("ct2ctml_string", message.str());
}
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 << "Python command was: '" << pypath() << "'\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 << python_output << std::endl;
message << error_output << std::endl;
message << "--------------- end of converter log ---------------";
} else {
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
stringstream message;
message << "Warning: Unexpected output from CTI converter\n";
message << "-------------- start of converter log --------------\n";
message << python_output << std::endl;
message << error_output << std::endl;
message << "--------------- end of converter log ---------------\n";
writelog(message.str());
}
return python_output;
}
void ck2cti(const std::string& in_file, const std::string& thermo_file,
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)
{
std::string ff, ext = "";
std::string ext = "";
// find the input file on the Cantera search path
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());
}
if (ext != ".xml" && ext != ".ctml") {
try {
ctml::ct2ctml(inname.c_str(), debug);
} catch (std::exception& err) {
writelog("get_CTML_Tree: caught an exception:\n");
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;
string phase_xml = ctml::ct2ctml_string(inname);
stringstream s(phase_xml);
rootPtr->build(s);
return;
}
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) {
throw
CanteraError("get_CTML_Tree",
"XML file " + ff + " not found");
"XML file " + inname + " not found");
}
rootPtr->build(fin);
fin.close();

View file

@ -35,23 +35,25 @@ def testRunner(target, source, env):
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
and resetting the test.
"""
program = localenv.Program(pjoin(subdir, progName),
mglob(localenv, subdir, 'cpp'))
testenv = localenv.Clone()
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))
PASSED_FILES[progName] = str(passedFile)
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-%s' % progName, run_program)
env['testNames'].append(progName)
if os.path.exists(passedFile.abspath):
Alias('test-reset', localenv.Command('reset-%s%s' % (subdir, progName),
[], [Delete(passedFile.abspath)]))
Alias('test-reset', testenv.Command('reset-%s%s' % (subdir, progName),
[], [Delete(passedFile.abspath)]))
def addPythonTest(testname, subdir, script, interpreter, outfile,
@ -156,8 +158,18 @@ def addMatlabTest(script, testName, dependencies=None, env_vars=()):
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
addTestProgram('thermo', 'thermo')
addTestProgram('thermo', 'thermo', env_vars=python_env_vars)
addTestProgram('kinetics', 'kinetics')
python_subtests = ['']
@ -191,18 +203,9 @@ if localenv['python3_package'] == 'y':
make_python_tests(3)
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',
dependencies=mglob(localenv, 'matlab', 'm'),
env_vars=env_vars)
env_vars=python_env_vars)
localenv.Alias('test-matlab', matlabTest)
env['testNames'].append('matlab')

View file

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

View file

@ -1,6 +1,7 @@
#include "gtest/gtest.h"
#include "cantera/thermo/FixedChemPotSSTP.h"
#include "cantera/thermo/ThermoFactory.h"
#include "cantera/base/ctml.h"
namespace Cantera
{
@ -28,4 +29,38 @@ TEST_F(FixedChemPotSstpConstructorTest, SimpleConstructor)
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

View file

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