The CounterFlowDiffusionFlame (CFDF) code is able to perform more general cases of npflame_init for multiple species fuel and oxidizer streams. The stoichiometric mixture fraction in the CFDF code uses the Bilger definition of mixture fraction, using the conservation of elements C, H, and O. This method is used in the python module, but not the MATLAB npflame_init function. Also, the CFDF code uses the fuel stream density to calculate the fuel stream velocity and the oxidizer stream density to calculate the oxidizer stream velocity, where as the npflame_init code uses the fuel density for both velocity calculations. The elementMassFraction code is a MATLAB version of the python function: elemental_mass_fraction, which is needed to run the CFDF code. Update the diffflame.m example to use the more general CFDF function since the input parameters are different than the npflame_init function. This example is the same as the diffusion_flame.py sample in the Python module.
299 lines
12 KiB
Python
299 lines
12 KiB
Python
from buildutils import *
|
|
import ast
|
|
|
|
Import('env', 'build', 'install')
|
|
|
|
localenv = env.Clone()
|
|
|
|
from collections import namedtuple
|
|
Page = namedtuple('Page', ['name', 'title', 'objects'])
|
|
|
|
def extract_python_docstring(pyfile, summary_only=True):
|
|
""" Returns the docstring from a Python script """
|
|
with open(pyfile) as f:
|
|
mod = ast.parse(f.read())
|
|
doc = ''
|
|
for node in mod.body:
|
|
if isinstance(node, ast.Expr) and isinstance(node.value, ast.Str):
|
|
doc = node.value.s
|
|
|
|
doc = doc.strip()
|
|
|
|
if summary_only:
|
|
doc = doc.split('\n\n')[0]
|
|
|
|
return doc
|
|
|
|
def extract_matlab_summary(mfile):
|
|
""" Returns a one-line summary comment from a .m file """
|
|
doc = ''
|
|
with open(mfile) as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if line.startswith('%'):
|
|
doc = line.strip('%').strip()
|
|
if doc:
|
|
break
|
|
name = os.path.basename(mfile)[:-2].replace('_', ' ')
|
|
if doc.lower().replace('_', ' ').startswith(name):
|
|
doc = doc[len(name):].strip()
|
|
return doc
|
|
|
|
|
|
# Set up functions to pseudo-autodoc the MATLAB toolbox
|
|
def extract_matlab_docstring(mfile, level):
|
|
"""
|
|
Return the docstring from mfile, assuming that it consists of the
|
|
first uninterrupted comment block.
|
|
|
|
:param mfile:
|
|
File name of the matlab file from which the documentation will be
|
|
read
|
|
:param level:
|
|
Level of documentation. Class = 0, Function = 1
|
|
"""
|
|
# Set the start of the docstring based on the level passed in. This is only
|
|
# necessary for the old-style MATLAB classes, where each method is its own
|
|
# file.
|
|
if level == 0:
|
|
docstring = ".. mat:class:: "
|
|
elif level == 1:
|
|
docstring = " .. mat:function:: "
|
|
else:
|
|
print "Unknown level for MATLAB documentation."
|
|
sys.exit(1)
|
|
|
|
# The leader is the number of spaces at the beginning of a regular line
|
|
# of documentation.
|
|
leader = ' '*4*(level + 1)
|
|
|
|
with open(mfile, 'r') as in_file:
|
|
# The function name is read from the first line
|
|
docstring += get_function_name(in_file.readline()) + '\n'
|
|
|
|
# By convention, the second line (called H1 in the MATLAB documentation)
|
|
# is read by various MATLAB functions, so it should be in the format
|
|
# MATLAB expects - FUNCTIONNAME Summary. We read in this line and
|
|
# add the Summary to the docstring. If the line doesn't match the
|
|
# format, just write it to the docstring as is.
|
|
line = in_file.readline()
|
|
try:
|
|
docstring += leader + line.split(' ')[1] + '\n'
|
|
except IndexError:
|
|
docstring += line + '\n'
|
|
|
|
# Skip the next line, which is a duplicate of the first. It is here
|
|
# because MATLAB doesn't show the function definition in its help.
|
|
in_file.readline()
|
|
|
|
# For the rest of the lines in the file, get the line if it is
|
|
# in the first unbroken comment section and add it to the docstring.
|
|
for line in in_file.readlines():
|
|
try:
|
|
if line.lstrip().startswith('%'):
|
|
docstring += leader + line.lstrip()[2:-1] + '\n'
|
|
else:
|
|
break
|
|
except IndexError:
|
|
docstring += '\n'
|
|
|
|
return docstring + '\n'
|
|
|
|
def get_function_name(str):
|
|
"""
|
|
Return the function or classdef signature, assuming that
|
|
the string starts with either 'function ' or 'classdef '.
|
|
"""
|
|
if str.startswith('function '):
|
|
sig = str[len('function '):]
|
|
elif str.startswith('classdef '):
|
|
sig = str[len('classdef '):]
|
|
else:
|
|
print "Unknown function declaration in MATLAB document", str
|
|
|
|
# Split the function signature on the equals sign, if it exists.
|
|
# We don't care about what comes before the equals sign, since
|
|
# if a function returns, the docs will tell us. If there is no
|
|
# =, return the whole signature.
|
|
if '=' in sig:
|
|
idx = sig.index('=')
|
|
return sig[idx+2:]
|
|
else:
|
|
return sig
|
|
|
|
if localenv['doxygen_docs']:
|
|
docs = build(localenv.Command('#build/docs/doxygen/html/index.html',
|
|
'doxygen/Doxyfile', 'doxygen $SOURCE'))
|
|
env.Depends(docs, env.Glob('#doc/doxygen/*') +
|
|
mglob(env, '#include/cantera', 'h') +
|
|
mglob(env, '#include/cantera/*', 'h') +
|
|
mglob(env, '#src/cantera/*', 'h', 'cpp'))
|
|
|
|
env.Alias('doxygen', docs)
|
|
install('$inst_docdir/doxygen/html',
|
|
mglob(localenv, '#/build/docs/doxygen/html', 'html', 'svg', 'css', 'png'))
|
|
|
|
if localenv['sphinx_docs']:
|
|
localenv['SPHINXBUILD'] = Dir('#build/docs/sphinx')
|
|
localenv['SPHINXSRC'] = Dir('sphinx')
|
|
|
|
sphinxdocs = build(localenv.Command('${SPHINXBUILD}/html/index.html',
|
|
'sphinx/conf.py',
|
|
'${sphinx_cmd} -b html -d ${SPHINXBUILD}/doctrees ${SPHINXSRC} ${SPHINXBUILD}/html'))
|
|
env.Alias('sphinx', sphinxdocs)
|
|
|
|
# Python examples: Create individual documentation pages with the source
|
|
# for each example
|
|
example_root = Dir('#interfaces/cython/cantera/examples').abspath
|
|
indexenv = env.Clone()
|
|
for subdir in subdirs(example_root):
|
|
summaries = []
|
|
for f in mglob(env, pjoin(example_root, subdir), 'py'):
|
|
docname = 'examples/{0}_{1}'.format(subdir, f.name[:-3])
|
|
summaries.append(':doc:`{0} <{1}>`:'.format(f.name, docname))
|
|
summaries.append(extract_python_docstring(f.abspath))
|
|
summaries.append('')
|
|
|
|
tmpenv = env.Clone()
|
|
tmpenv['script_name'] = f.name
|
|
tmpenv['script_path'] = '../../../../interfaces/cython/cantera/examples/%s/%s' % (subdir, f.name)
|
|
b = tmpenv.SubstFile('#doc/sphinx/cython/{0}.rst'.format(docname),
|
|
'#doc/sphinx/cython/example-script.rst.in')
|
|
build(b)
|
|
localenv.Depends(sphinxdocs, b)
|
|
indexenv['python_{0}_examples'.format(subdir)] = '\n'.join(summaries)
|
|
b = indexenv.SubstFile('#doc/sphinx/cython/examples.rst',
|
|
'#doc/sphinx/cython/examples.rst.in')
|
|
build(b)
|
|
localenv.Depends(sphinxdocs, b)
|
|
|
|
# Create a list of MATLAB classes to document. This uses the NamedTuple
|
|
# structure defined at the top of the file. The @Data and @Utilities
|
|
# classes are fake classes for the purposes of documentation only. Each
|
|
# Page represents one html page of the documentation.
|
|
pages = [
|
|
Page('importing', 'Importing Phase Objects',
|
|
['@Solution', '@Mixture',]
|
|
),
|
|
Page('thermodynamics', 'Thermodynamic Properties',
|
|
['@ThermoPhase']
|
|
),
|
|
Page('kinetics', 'Chemical Kinetics', ['@Kinetics']),
|
|
Page('transport', 'Transport Properties', ['@Transport']),
|
|
Page('zero-dim', 'Zero-Dimensional Reactor Networks',
|
|
['@Func', '@Reactor', '@ReactorNet', '@FlowDevice', '@Wall']
|
|
),
|
|
Page('one-dim', 'One-Dimensional Reacting Flows',
|
|
['1D/@Domain1D', '1D/@Stack']
|
|
),
|
|
Page('data', 'Built-In Thermochemical Data',
|
|
['@Data']
|
|
),
|
|
Page('utilities', 'Utility Functions',
|
|
['@Utilities', '@XML_Node']
|
|
),
|
|
Page('interface', 'Interfaces', ['@Interface']),
|
|
]
|
|
|
|
# Create a dictionary of extra files associated with each class. These
|
|
# files are listed relative to the top directory interfaces/matlab/cantera
|
|
extra = {
|
|
'@Solution': ['IdealGasMix.m'],
|
|
'@Func': ['gaussian.m', 'polynom.m'],
|
|
'@Reactor': ['ConstPressureReactor.m',
|
|
'FlowReactor.m', 'IdealGasConstPressureReactor.m',
|
|
'IdealGasReactor.m', 'Reservoir.m'],
|
|
'@FlowDevice': ['MassFlowController.m', 'Valve.m'],
|
|
'1D/@Domain1D': ['1D/AxiStagnFlow.m', '1D/AxisymmetricFlow.m',
|
|
'1D/Inlet.m', '1D/Outlet.m', '1D/OutletRes.m',
|
|
'1D/Surface.m', '1D/SymmPlane.m'],
|
|
'1D/@Stack': ['1D/FreeFlame.m', '1D/npflame_init.m',
|
|
'1D/CounterFlowDiffusionFlame.m'],
|
|
'@Interface': ['importEdge.m', 'importInterface.m'],
|
|
'@Data': ['Air.m', 'gasconstant.m', 'GRI30.m',
|
|
'Hydrogen.m', 'Methane.m', 'Nitrogen.m', 'oneatm.m',
|
|
'Oxygen.m', 'Water.m'],
|
|
'@Utilities': ['adddir.m', 'ck2cti.m', 'cleanup.m', 'geterr.m',
|
|
'getDataDirectories.m', 'canteraVersion.m',
|
|
'canteraGitCommit.m']
|
|
}
|
|
|
|
# These files do not need to be documented in the MATLAB classes because they
|
|
# are generics that are overloaded per-class. Since the loop checks for these
|
|
# strings in each file name, hndl.m is the same as *hndl.m* (to use globbing
|
|
# notation).
|
|
nodoc_matlab_files = ['clear.m', 'display.m', 'hndl.m', 'private', 'subsref.m']
|
|
|
|
# Loop through the pages list to document each class
|
|
for page in pages:
|
|
tempenv = env.Clone()
|
|
|
|
# Set the title header
|
|
title = page.title
|
|
tempenv['title'] = '='*len(title) + '\n' + title + '\n' + '='*len(title)
|
|
doc = ''
|
|
|
|
# The base directory of the MATLAB toolbox relative to the sphinx build directory
|
|
base = '../interfaces/matlab/toolbox'
|
|
for obj in page.objects:
|
|
all_files = []
|
|
# Set the subheader based on the class name
|
|
doc += obj.split('@')[1] + '\n' + '-'*len(obj.split('@')[1]) + '\n\n'
|
|
if os.path.isdir(pjoin(base,obj)):
|
|
# Get a list of the functions in this class as long as its a file we care about
|
|
functions = [name for name in os.listdir(pjoin(base,obj)) if not any(x in name for x in nodoc_matlab_files)]
|
|
|
|
# Add the docstring for the class name at level 0
|
|
class_file = functions.pop(functions.index(obj.split('@')[1]+'.m'))
|
|
doc += extract_matlab_docstring(os.path.relpath(pjoin(base,obj,class_file)), 0)
|
|
|
|
# Get the extra files from the extra dictionary and sort them with
|
|
# the regular functions.
|
|
extra_files = extra.get(obj,[])
|
|
all_files += sorted(functions + extra_files)
|
|
else:
|
|
all_files = extra.get(obj,[])
|
|
|
|
for file in all_files:
|
|
if file in functions:
|
|
doc += extract_matlab_docstring(os.path.relpath(pjoin(base,obj,file)), 1)
|
|
else:
|
|
doc += extract_matlab_docstring(os.path.relpath(pjoin(base,file)), 1)
|
|
|
|
tempenv['matlab_docstrings'] = doc
|
|
# Substitute the docstrings into the proper file. Since the docs change
|
|
# every time the source is changed, we don't want to have to commit the
|
|
# change in the rst file as well as the source - too much code churn. So
|
|
# we use a template and a SubstFile directive.
|
|
c = tempenv.SubstFile('#doc/sphinx/matlab/code-docs/%s.rst' % page.name,
|
|
'#doc/sphinx/matlab/matlab-template.rst.in')
|
|
build(c)
|
|
localenv.Depends(sphinxdocs, c)
|
|
|
|
# Matlab examples: create individual documentation pages with the source
|
|
# for each example
|
|
examples = []
|
|
tutorials = []
|
|
for f in mglob(env, '#samples/matlab', 'm'):
|
|
tmpenv = env.Clone()
|
|
tmpenv['script_name'] = f.name
|
|
tmpenv['script_path'] = '../../../../samples/matlab/%s' % f.name
|
|
b = tmpenv.SubstFile('#doc/sphinx/matlab/examples/%s.rst' % f.name[:-2],
|
|
'#doc/sphinx/matlab/example-script.rst.in')
|
|
build(b)
|
|
localenv.Depends(sphinxdocs, b)
|
|
summary = [':doc:`{0} <examples/{1}>`:'.format(f.name, f.name[:-2]),
|
|
extract_matlab_summary(f.abspath),
|
|
'']
|
|
if f.name.startswith('tut'):
|
|
tutorials.extend(summary)
|
|
else:
|
|
examples.extend(summary)
|
|
|
|
localenv['matlab_tutorials'] = '\n'.join(tutorials)
|
|
localenv['matlab_examples'] = '\n'.join(examples)
|
|
b = localenv.SubstFile('#doc/sphinx/matlab/examples.rst',
|
|
'#doc/sphinx/matlab/examples.rst.in')
|
|
build(b)
|
|
localenv.Depends(sphinxdocs, b)
|
|
localenv.AlwaysBuild(sphinxdocs)
|