diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 96485f0ab0..850c2a4262 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -457,7 +457,6 @@ jobs: - name: Run the examples # See https://unix.stackexchange.com/a/392973 for an explanation of the -exec part. # Skip 1D_packed_bed.py due to difficulty installing scikits.odes. - # Skip continuous_reactor.py due to thermo warnings # Increase figure limit to handle flame_speed_convergence_analysis.py. run: | ln -s libcantera_shared.so build/lib/libcantera_shared.so.3 @@ -471,7 +470,8 @@ jobs: # The pyparsing ignore setting is due to a new warning introduced in Matplotlib==3.6.0 # @todo: Remove the trapz-related ignore when dropping support for NumPy 1.x # and replacing np.trapz with np.trapezoid - PYTHONWARNINGS: "error,ignore:warn_name_set_on_empty_Forward::pyparsing,ignore:datetime.datetime.utcfromtimestamp:DeprecationWarning:,ignore:`trapz`:DeprecationWarning" + # Ignore NasaPoly2 warnings from n-hexane-NUIG-2015.yaml + PYTHONWARNINGS: "error,ignore:warn_name_set_on_empty_Forward::pyparsing,ignore:datetime.datetime.utcfromtimestamp:DeprecationWarning:,ignore:`trapz`:DeprecationWarning,ignore:NasaPoly2:UserWarning:" MPLBACKEND: Agg - name: Save the results file for inspection uses: actions/upload-artifact@v4 diff --git a/doc/SConscript b/doc/SConscript index f30acfb942..79f9e5147e 100644 --- a/doc/SConscript +++ b/doc/SConscript @@ -54,10 +54,14 @@ if localenv['sphinx_docs']: "#CONTRIBUTING.md", Copy("$TARGET", "$SOURCE")) + copy_switcher = localenv.Command("#build/doc/html/dev/_static/doc-versions.json", + "sphinx/_static/doc-versions.json", + Copy("$TARGET", "$SOURCE")) + sphinxdocs = build(localenv.Command('#build/doc/html/index.html', 'sphinx/conf.py', build_sphinx)) env.Alias('sphinx', sphinxdocs) - env.Depends(sphinxdocs, [copy_sphinx, copy_contrib]) + env.Depends(sphinxdocs, [copy_sphinx, copy_contrib, copy_switcher]) env.Depends(sphinxdocs, [copy_python_samples, copy_matlab_ex_samples]) env.Depends(sphinxdocs, env['python_module']) diff --git a/doc/doxygen/Doxyfile b/doc/doxygen/Doxyfile index 878bdd2db7..250c985f41 100644 --- a/doc/doxygen/Doxyfile +++ b/doc/doxygen/Doxyfile @@ -1355,7 +1355,7 @@ HTML_EXTRA_FILES = ext/doxygen-awesome-css/doxygen-awesome-darkmode-toggle # The default value is: AUTO_LIGHT. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE = AUTO_LIGHT +HTML_COLORSTYLE = LIGHT # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to diff --git a/doc/sphinx/_static/custom.css b/doc/sphinx/_static/custom.css index cc79008e90..d16aa5a2fe 100644 --- a/doc/sphinx/_static/custom.css +++ b/doc/sphinx/_static/custom.css @@ -47,3 +47,10 @@ div.admonition.tip>.admonition-title:after { color:var(--pst-color-info); content:var(--pst-icon-admonition-tip) } + +div.highlight pre { + /* Reduced padding around code blocks from default of 1 rem */ + padding: .5rem; + /* Slight reduction in font size so 80 characters fit on most display sizes */ + font-size: 0.78rem; +} diff --git a/doc/sphinx/conf.py b/doc/sphinx/conf.py index 3f9ac7557f..7cbe02f7d8 100644 --- a/doc/sphinx/conf.py +++ b/doc/sphinx/conf.py @@ -57,7 +57,7 @@ 'ignore_pattern': r'(__.*__\.py|test_examples\.m)', 'image_srcset': ["2x"], 'remove_config_comments': True, - 'ignore_repr_types': r'matplotlib\.(text|axes|legend)', + 'ignore_repr_types': r'(matplotlib\.(text|axes|legend)|graphviz\.(sources\.Source|graphs\.Graph|graphs\.Digraph))', 'image_scrapers': ('matplotlib', ctutils.GraphvizScraper()), 'examples_dirs': [ '../samples/python/', @@ -79,8 +79,6 @@ '../samples/python/transport', '../samples/python/reactors', '../samples/python/onedim', - '../samples/python/surface_chemistry', - '../samples/python/multiphase', ]), 'reference_url': { 'cantera': None, # 'None' means the locally-documented module @@ -98,6 +96,13 @@ "multiprocessing_viscosity.py", } +# Installing scikits.odes is challenging on lots of systems (such as Apple Silicon), so +# just skip running this example if it's not present. +try: + import scikits.odes +except ImportError: + skip_run.add("1D_packed_bed.py") + def executable_script(src_file, gallery_conf): """Validate if script has to be run according to gallery configuration. @@ -325,7 +330,6 @@ def escape_splats(app, what, name, obj, options, lines): "primary_sidebar_end": ["numfocus"], "switcher": { "json_url": "/dev/_static/doc-versions.json", - # "json_url": "https://cantera.org/doc-versions.json", "version_match": version, }, "check_switcher": False, diff --git a/doc/sphinx/ctutils.py b/doc/sphinx/ctutils.py index e5d9b9a997..e9a8fb149e 100644 --- a/doc/sphinx/ctutils.py +++ b/doc/sphinx/ctutils.py @@ -41,9 +41,9 @@ def __call__(self, block, block_vars, gallery_conf): # a path to a file name that adheres to Sphinx-Gallery naming convention. image_path_iterator = block_vars['image_path_iterator'] - # Define a list of our already-created figure objects. + graph_types = (graphviz.Source, graphviz.graphs.Digraph, graphviz.graphs.Graph) for obj in block_vars["example_globals"].values(): - if isinstance(obj, graphviz.Source) and id(obj) not in self.processed: + if isinstance(obj, graph_types) and id(obj) not in self.processed: self.processed.add(id(obj)) image_path = Path(next(image_path_iterator)).with_suffix(".svg") obj.format = "svg" diff --git a/doc/sphinx/develop/doc-formatting.md b/doc/sphinx/develop/doc-formatting.md index 3243b3fa5e..13dcecbb79 100644 --- a/doc/sphinx/develop/doc-formatting.md +++ b/doc/sphinx/develop/doc-formatting.md @@ -33,6 +33,8 @@ This page provides some notes on useful syntax for writing in these various form - `[](/absolute-subdir/docname)` (automatically get the text from `docname`'s title) - `[link text](relative-subdir/docname)` (explicitly specified link text) - source file extension is optional +- Linking to examples: + - ``` [`example_name.py`](/examples/python/subdir/example_name) ``` - Linking to a labeled section: - `[Build Commands](sec-build-commands)` - Labeling a section: Above the heading, write: diff --git a/doc/sphinx/examples/index.md b/doc/sphinx/examples/index.md index e107cc446e..4f8e9a8c54 100644 --- a/doc/sphinx/examples/index.md +++ b/doc/sphinx/examples/index.md @@ -5,6 +5,13 @@ particular programming language, select the corresponding category below. Or, to all examples covering a particular topic, regardless of programming language, select from the {ref}`list of example tags `. +```{seealso} +If you're just getting started with Cantera, see our [](/userguide/python-tutorial) for +an introduction to the Cantera Python interface, or the +[C++ Tutorial](/userguide/cxx-tutorial) for some guidance on using Cantera's C++ +interface. +``` + ## Python Examples ````{grid} 2 2 2 3 @@ -40,18 +47,6 @@ from the {ref}`list of example tags `. :text-align: center ``` -```{grid-item-card} Surface chemistry -:link: python/surface_chemistry/index -:link-type: doc -:text-align: center -``` - -```{grid-item-card} Multiphase -:link: python/multiphase/index -:link-type: doc -:text-align: center -``` - ```` ## Examples in Other Languages diff --git a/doc/sphinx/reference/glossary.md b/doc/sphinx/reference/glossary.md index 1af65699d5..a61df930b4 100644 --- a/doc/sphinx/reference/glossary.md +++ b/doc/sphinx/reference/glossary.md @@ -7,6 +7,10 @@ the names of variables and classes: CK : Chemkin +{.glossary} +CSTR +: [Continuous stirred tank reactor](/examples/python/reactors/continuous_reactor) + {.glossary} CT : Cantera @@ -19,6 +23,14 @@ CTI CTML : Cantera Markup Language *(Removed in Cantera 3.0)* +{.glossary} +DAE +: Differential algebraic equation + +{.glossary} +EOS / EoS +: Equation of state + {.glossary} HKFT : Helgeson-Kirkham-Flowers-Tanger @@ -31,14 +43,34 @@ HMW IAPWS : International Association for the Properties of Water and Steam +{.glossary} +IDT +: Ignition delay time + {.glossary} MFTP : Mixture Fugacity ThermoPhase +{.glossary} +NTC +: Negative temperature coefficient + +{.glossary} +ODE +: Ordinary differential equation + {.glossary} PDSS : Pressure-dependent standard state +{.glossary} +PFR +: [Plug flow reactor](./reactors/pfr) + +{.glossary} +PSR +: [Perfectly stirred reactor](/examples/python/reactors/continuous_reactor) + {.glossary} RT : Product of the universal gas constant (R) and the temperature @@ -78,3 +110,7 @@ VPSSTP {.glossary} wrt : with respect to + +{.glossary} +WSR +: [Well-stirred reactor](/examples/python/reactors/continuous_reactor) diff --git a/doc/sphinx/reference/reactors/pfr.md b/doc/sphinx/reference/reactors/pfr.md index ac1066c15b..07e7b4369b 100644 --- a/doc/sphinx/reference/reactors/pfr.md +++ b/doc/sphinx/reference/reactors/pfr.md @@ -124,4 +124,4 @@ $du/dz$, $dp/dz$, $dT/dz$, and $dY_k/dz$ at $z=0$. - [Partial oxidation of methane over a platinum catalyst](/examples/python/reactors/surf_pfr) - [silicon nitride (Si3N4) deposition from ammonia (NH3) and silicon tetrafluoride - (SiF4)](/examples/python/surface_chemistry/1D_pfr_surfchem) + (SiF4)](/examples/python/reactors/1D_pfr_surfchem) diff --git a/doc/sphinx/reference/releasenotes/v3.0.md b/doc/sphinx/reference/releasenotes/v3.0.md index b5996189ff..fb2cfa0069 100644 --- a/doc/sphinx/reference/releasenotes/v3.0.md +++ b/doc/sphinx/reference/releasenotes/v3.0.md @@ -11,7 +11,7 @@ The Cantera development team is pleased to announce the availability of Cantera - Introduce a C++ `SolutionArray` class, which is now used as the backend for the Python `SolutionArray` class ([E#137](https://github.com/Cantera/enhancements/issues/137), [#1385](https://github.com/Cantera/cantera/pull/1385), [#1426](https://github.com/Cantera/cantera/pull/1426), [#1458](https://github.com/Cantera/cantera/pull/1458), [#1462](https://github.com/Cantera/cantera/pull/1462), [#1464](https://github.com/Cantera/cantera/pull/1464), [#1508](https://github.com/Cantera/cantera/pull/1508), [#1520](https://github.com/Cantera/cantera/pull/1520), [#1547](https://github.com/Cantera/cantera/pull/1547), [#1585](https://github.com/Cantera/cantera/pull/1585)) - Add "native" support for HDF5 as an input/output format for `SolutionArray` and 1D flames ([E#166](https://github.com/Cantera/enhancements/issues/166), [`flame_initial_guess.py`](/examples/python/onedim/flame_initial_guess)) - Add a new thermo model, `coverage-dependent-surface` for representing surfaces where the enthalpy, entropy, and heat capacity of each species may depend on its coverage and the coverage of other species in the phase. (class {ct}`CoverageDependentSurfPhase`, [E#59](https://github.com/Cantera/enhancements/issues/59), [#1135](https://github.com/Cantera/cantera/pull/1135), [#1471](https://github.com/Cantera/cantera/pull/1471), [#1529](https://github.com/Cantera/cantera/pull/1529), [#1583](https://github.com/Cantera/cantera/pull/1583)) -- Add support for surface chemistry to the plug flow reactor model (class {py:class}`~cantera.FlowReactor`, [`1D_pfr_surfchem.py`](/examples/python/surface_chemistry/1D_pfr_surfchem), [`surf_pfr.py`](/examples/python/reactors/surf_pfr), [#1490](https://github.com/Cantera/cantera/pull/1490)) +- Add support for surface chemistry to the plug flow reactor model (class {py:class}`~cantera.FlowReactor`, [`1D_pfr_surfchem.py`](/examples/python/reactors/1D_pfr_surfchem), [`surf_pfr.py`](/examples/python/reactors/surf_pfr), [#1490](https://github.com/Cantera/cantera/pull/1490)) - Add support for using real gas models (Redlich-Kwong and Peng-Robinson) with the 1D flame solver ([E#109](https://github.com/Cantera/enhancements/issues/109), [#1079](https://github.com/Cantera/cantera/pull/1079)) - Introduction of the `yaml2ck` script, which converts Cantera's YAML mechanism format into the legacy Chemkin format. ([`yaml2ck.py`](/yaml/yaml2ck), [E#52](https://github.com/Cantera/enhancements/issues/52), [#1286](https://github.com/Cantera/cantera/pull/1286), [#1365](https://github.com/Cantera/cantera/pull/1365), [#1420](https://github.com/Cantera/cantera/pull/1420)) - Add an experimental Python module where Cantera returns dimensional values as quantities using the `pint` package. This is currently available only for thermo methods ([module `cantera.with_units`](/python/units), [`isentropic_units.py`](/examples/python/thermo/isentropic_units), [`rankine_units.py`](/examples/python/thermo/rankine_units), [`sound_speed_units.py`](/examples/python/thermo/sound_speed_units), [E#3](https://github.com/Cantera/enhancements/issues/3), [#991](https://github.com/Cantera/cantera/pull/991), [#1569](https://github.com/Cantera/cantera/pull/1569)) diff --git a/doc/sphinx/userguide/ck2yaml-tutorial.md b/doc/sphinx/userguide/ck2yaml-tutorial.md index dd74c0c62b..bccc77aa4b 100644 --- a/doc/sphinx/userguide/ck2yaml-tutorial.md +++ b/doc/sphinx/userguide/ck2yaml-tutorial.md @@ -58,6 +58,7 @@ thermo file. For example: ck2yaml --thermo=therm.dat ``` +(sec-debugging-chemkin)= ## Debugging common errors in CK files When `ck2yaml` encounters an error, it attempts to print the surrounding information to diff --git a/doc/sphinx/userguide/faq.md b/doc/sphinx/userguide/faq.md index 629031f048..7a6a88aacd 100644 --- a/doc/sphinx/userguide/faq.md +++ b/doc/sphinx/userguide/faq.md @@ -1,12 +1,237 @@ # Frequently Asked Questions -```{caution} -This page is a work in progress. Sorry. +```{hint} +If your question isn’t answered here, consider asking us on the +[Cantera Users' Group](https://groups.google.com/g/cantera-users). ``` (sec-faq-installation)= ## Installation +:::{dropdown} How do I determine the cause of the error "DLL load failed while importing _cantera: The specified module can not be found."? +:color: primary + +1. Install the [Dependencies](https://github.com/lucasg/Dependencies) tool. +2. *If you are using Conda*: Run this tool from a terminal where your Cantera Conda + environment is active. That is, run the command: + ``` + start "C:\Program Files\Dependencies\DependenciesGUI.exe" + ``` + from that terminal, replacing the path with the path to `DependenciesGUI.exe` on your + computer. This step is necessary to have the `PATH` environment variable set + correctly. +3. Use Dependencies to open the Cantera Python extension module. If you installed + Mambaforge at `C:\mambaforge` and Cantera in an environment named `ct-env`, then this + file would be named something like + `C:\mambaforge\envs\ct-env\Lib\site-packages\cantera\_cantera.cp312-win_amd64.pyd`. +4. Identify any DLLs that the tool is unable to find, or if there are methods that are + not being resolved. One thing to keep an eye out for is if there are any libraries + that are being loaded from unexpected locations (for example, other than from your + conda installation and the Windows system directories). +::: + ## Input Files -* Where can I find chemical mechanisms to use with Cantera? -* How do I fix errors converting Chemkin input files to Cantera's format? + +:::{dropdown} Where can I find chemical mechanisms to use with Cantera? +:color: primary + +There are a few sites that distribute mechanisms in the Cantera YAML format: +- The [Caltech Explosion Dynamics Laboratory](https://shepherd.caltech.edu/EDL/PublicResources/sdt/cti_mech.html) + provides a number of mechanisms for combustion applications. +- [CollectionOfMechanisms](https://github.com/jiweiqi/CollectionOfMechanisms) is a + user-maintained GitHub repository of mechanisms that have been obtained from + scientific publications and other sources. + +Many research groups maintain pages with mechanisms they developed, provided in the + Chemkin format. These mechanisms can be converted to the Cantera YAML format using the + [`ck2yaml`](/userguide/ck2yaml-tutorial) tool. The following is an incomplete list: + +- The [University of Galway Combustion Chemistry Centre](https://www.universityofgalway.ie/combustionchemistrycentre/mechanismdownloads/) + provides mechanisms for combustion +- [DETCHEM](https://www.detchem.com/mechanisms) provides a variety of mechanisms for + catalytic systems that were developed by the research group of Olaf Deutschmann at the + Karlsruhe Institute of Technology. +- [Lawrence Livermore National Laboratory ](https://combustion.llnl.gov/mechanisms) + provides a number of combustion mechanisms + +```{caution} +The inclusion of a site in the above lists does not constitute an endorsement of the +chemical mechanisms provided there. You must use your own judgement to determine if a +mechanism is appropriate for any particular scientific or engineering purpose. +``` + +::: + + +:::{dropdown} How do I fix errors converting Chemkin input files to Cantera's format? +:color: primary + +See our documentation on [](sec-debugging-chemkin). If you're encountering an issue not +described there, please post a question on the +[Cantera Users' Group](https://groups.google.com/g/cantera-users). +::: + +## Thermodynamics & Equilibrium + +:::{dropdown} Why don't the enthalpy and internal energy values calculated by Cantera match data from other sources (CoolProp / NIST / steam tables)? Why does my mixture have a negative enthalpy? +:color: primary + +Reference states in thermodynamics are arbitrary, since the observable quantities of +interest (work done, heat transferred) do not depend on absolute quantities but on +differences between states. Different data sources use different conventions for the +reference state. Cantera uses the convention commonly used in reaction thermodynamics, +where pure elements in their natural state (for example, oxygen as O₂ gas and carbon as +graphite) have zero enthalpy at a temperature of 298.15 K and a pressure of 1 atm. This +convention provides a consistent treatment for the change in enthalpy associated with +chemical reactions. In contrast, it is common for tabulated data for a single pure +substance to use a different reference state, such as CO₂ where the enthalpy may be +referenced to the saturated liquid at 273.15 K. +::: + +:::{dropdown} Why doesn't the heat capacity calculated by Cantera match what I get from CEA? +:color: primary + +Cantera’s definition of $c_p$ is the partial derivative of enthalpy with respect to +temperature, with pressure and composition held constant. The value returned by CEA is +the derivative while holding the system at chemical equilibrium. The former definition +is the one that appears in governing equations for multi-species systems, such as +[well-stirred reactors](/reference/reactors/ideal-gas-constant-pressure-reactor). While +the latter property is of interest in certain physical situations, it is of somewhat +limited use computationally, since it is *only* defined for mixtures at equilibrium. + +You can use Cantera to compute a value like what’s returned by CEA by computing $dh/dT$ +using a finite difference method where the mixture is equilibrated at constant $T$ and +$P$ at each point before calculating the enthalpy. +[`sound_speed.py`](/examples/python/thermo/sound_speed) presents a related example. + +CEA provides a value consistent with Cantera's definition which it calls the $c_p$ "with +frozen reactions." +::: + +:::{dropdown} Why doesn't changing reactions affect the equilibrium state calculated by Cantera? How does Cantera find the equilibrium state even when no reactions are defined? +:color: primary +Chemical equilibrium is not defined in terms of reactions. For whatever set of species +are allowed in a phase or multi-phase mixture, it is defined as the composition that +minimizes a particular thermodynamic state variable while holding two others constant +(for example, for equilibrium at constant temperature and pressure, the Gibbs free +energy is minimized). This optimization is independent of any reactions that are +defined, and the resulting composition may be different from what you would get, for +example, from integrating a reactor network using a set of irreversible chemical +reactions or a set of reactions that does not form a complete basis set for the defined +species. +::: + +## Reactor Networks + +:::{dropdown} How do I set the residence time for a reactor? +:color: primary + +Cantera defines reactor flows in terms of inlet and outlet mass flow rates, with +residence time being an output that can be calculated: + +$$ t_\t{res} = \frac{m_\t{combustor}}{\dot{m}_\t{in}} $$ + +To achieve a specified residence time, you can define the mass flow rate to be a +function of the combustor state. For example, the following approach is used in the +example [`combustor.py`](/examples/python/reactors/combustor): + +```py +def mdot(t): + return combustor.mass / residence_time + +inlet_mfc = ct.MassFlowController(inlet, combustor, mdot=mdot) +outlet_mfc = ct.PressureController(combustor, exhaust, primary=inlet_mfc, K=0.01) +``` +::: + +:::{dropdown} How do I understand errors from ReactorNet that give a list of "components with largest weighted error estimates"? +:color: primary + +Calls to `ReactorNet.step` or `ReactorNet.advance` may result in error messages like the +following: +``` + CanteraError thrown by CVodesIntegrator::integrate: +CVodes error encountered. Error code: -4 +At t = 0.151127 and h = 1.61901e-09, the corrector convergence test failed repeatedly or with |h| = hmin. + +Components with largest weighted error estimates: +834: 297.28141792959997 +762: -273.0999483219796 +8: 1.8673611455956203 +99: -0.018812031839692638 +100: 0.01850247743429419 +2: -0.002587710808893717 +771: 0.0007865229822411536 +843: -0.0007861126902119126 +184: -0.0006244451494927259 +231: 0.0006129536140016525 +``` + +The list labeled "Components with largest weighted error estimates" provides a list of +component indices followed by their corresponding error estimates. Large error estimates +tend to be associated with rapidly changing variables which require very small time +steps. If you are using the Python interface and the `ReactorNet` object is named `net`, +you can identify the variables associated with these indices: + +```pycon +>>> net.component_name(834) +'ABC' +>>> net.component_name(762) +'XYZ' +``` + +It is sometimes the case that a reaction mechanism will contain one or more reactions +where at some temperatures, either the forward or reverse rate constant becomes high and +nonphysical, particularly if the mechanism has not been designed for use at in a +particular temperature range. For this example, you can find reactions involving the +species with the highest errors, assuming a `Solution` object named `gas`: + +```pycon +>>> for i, R in enumerate(gas.reactions()): +... spec = R.reactants | R.products +... if 'ABC' in spec and 'XYZ' in spec: +... print(i, R) +244 ABC <=> XYZ +``` + +For this example, resolution of the problem would then involve investigating the rate +parameterization for reaction 244 or the thermodynamic data for species ABC and XYZ, +which determine the reverse rate constant. + +In other cases, the time reached by the integrator (here, `t = 0.151127`) can provide a +hint at the source of the problem. For example, if this time is near the time of a +discontinuity in the inputs to a reactor network, such as a valve opening or closing, +then the problem might be solved by using a smoother time function or decreasing the +maximum integrator timestep. +::: + +## 1D Reacting Flows + + +:::{dropdown} Why can't I calculate the flame speed for a mixture with an inlet temperature of ~1000 K or higher? +:color: primary + +Two failure modes are common under these conditions: + +1. when the `auto=True` option is specified to `FreeFlame.solve`, the solver keeps +expanding the domain and never finds a solution satisfying its tolerances +2. the solver converges but the solution is a strong function of the distance from the +inlet to the flame. + +The cause of both these problems is that at high inlet temperatures, reactions are +already starting to occur at that inlet temperature. This violates the standard boundary +conditions for the laminar flame problem, where the reaction rates need to go to zero +for the inlet mixture. Here, however, finite rates at an inlet that is a finite distance +from the flame mean that the mixture has changed before it even reaches the flame, and +the computed flame speed becomes a function of that distance. Cantera's solver (with the +"auto" option enabled) tries to keep the inlet boundary far enough away from the flame +to avoid problems with non-zero diffusive fluxes across the inlet, and that leads it to +further widening the domain as the temperature continues to go up. + +Considering a domain with the temperature fixed point at a distance $d$ from the inlet, +the ignition delay time $\tau_\t{ig}$ sets a lower bound on the calculated flame speed. +That is, even in the absence of any diffusion of heat or radicals into the unburned +mixture, the calculated flame speed will be $S_u \approx d / \tau_\t{ig}$ or, accounting +for transport, $S_u > d / \tau_\t{ig}$. A correct flame speed calculation can only be +obtained when $S_u$ is independent of $d$ and $d$ is larger than the flame thickness. +::: diff --git a/doc/sphinx/userguide/index.md b/doc/sphinx/userguide/index.md index 6cbe83ea5f..13e3f045e3 100644 --- a/doc/sphinx/userguide/index.md +++ b/doc/sphinx/userguide/index.md @@ -38,7 +38,7 @@ conditions, or calculating the voltage of a Lithium-ion battery as it is dischar - [](heating-value) ### Electrochemistry Calculations -- [](/examples/python/surface_chemistry/lithium_ion_battery) +- [](/examples/python/kinetics/lithium_ion_battery) ### Implementing Custom Models @@ -76,7 +76,7 @@ legacy2yaml-tutorial flame-temperature heating-value -/examples/python/surface_chemistry/lithium_ion_battery +/examples/python/kinetics/lithium_ion_battery extensible-reactor ``` diff --git a/doc/sphinx/userguide/python-tutorial.md b/doc/sphinx/userguide/python-tutorial.md index 9788c0c42a..29f0d237f0 100644 --- a/doc/sphinx/userguide/python-tutorial.md +++ b/doc/sphinx/userguide/python-tutorial.md @@ -202,21 +202,15 @@ ct.add_directory('~/cantera/my_data_files') Cantera input files are plain text files, and can be created with any text editor. See the page [](input-tutorial) for more information. -A Cantera input file may contain more than one phase specification, or may contain -specifications of interfaces (surfaces). Here we import definitions of two bulk phases -and the interface between them from file `diamond.yaml`: +A Cantera input file may contain more than one phase specification, and may contain +specifications of interfaces (surfaces and edges). Here we import a surface phase and +the two adjacent bulk phases from the file `diamond.yaml`: -% TODO: Demonstrate the syntax that starts with creating the Interface ```{code-cell} python -gas2 = ct.Solution('diamond.yaml', 'gas') -diamond = ct.Solution('diamond.yaml', 'diamond') -diamond_surf = ct.Interface('diamond.yaml' , 'diamond_100', - [gas2, diamond]) +diamond_surf = ct.Interface('diamond.yaml' , 'diamond_100') +diamond_surf.adjacent ``` -Note that the bulk (3D) phases that participate in the surface reactions must also be -passed as arguments to {py:class}`Interface`. - ## Converting CK-format files See the page [](ck2yaml-tutorial) for information on how to convert from CK-format to @@ -446,4 +440,15 @@ Or about 16% of the total heat release rate. Congratulations! You have finished the Cantera Python tutorial. You should now be ready to begin using Cantera on your own. Please see additional sections of the [Cantera User Guide](/userguide/index) for assistance with intermediate and advanced Cantera -functionality. Good luck! +functionality. You can also take a look at our [Example Gallery](/examples/python/index) +for ideas on how you can use Cantera. Here are a few examples that may be of particular +interest: + +* [](/userguide/flame-temperature) +* [](/userguide/heating-value) +* [](/examples/python/thermo/equations_of_state) +* [](/examples/python/kinetics/lithium_ion_battery) +* [](/examples/python/reactors/continuous_reactor) +* [](/examples/python/reactors/NonIdealShockTube) +* [](/examples/python/onedim/flamespeed_sensitivity) +* [](/examples/python/onedim/premixed_counterflow_twin_flame) diff --git a/include/cantera/equil/MultiPhase.h b/include/cantera/equil/MultiPhase.h index 6b8bf2ebdc..13657d6d48 100644 --- a/include/cantera/equil/MultiPhase.h +++ b/include/cantera/equil/MultiPhase.h @@ -28,6 +28,11 @@ class ThermoPhase; * thermodynamic object, in contrast to the ThermoPhase object, which is an * "intrinsic" thermodynamic object. * + * @warning The multiphase equilibrium solvers currently have a number of problems that + * lead to solver failures or incorrect results for some inputs. See the + * [list of issues on GitHub](https://github.com/Cantera/cantera/issues?q=is%3Aopen+is%3Aissue+label%3AEquilibrium) + * for more information. + * * MultiPhase may be considered to be "upstream" of the ThermoPhase objects in * the sense that setting a property within MultiPhase, such as temperature, * pressure, or species mole number, affects the underlying ThermoPhase diff --git a/interfaces/cython/cantera/drawnetwork.py b/interfaces/cython/cantera/drawnetwork.py index 0fe96344ea..246e79030a 100644 --- a/interfaces/cython/cantera/drawnetwork.py +++ b/interfaces/cython/cantera/drawnetwork.py @@ -291,7 +291,7 @@ def draw_walls(walls, graph=None, graph_attr=None, node_attr=None, edge_attr=Non assert r_in.name != r_out.name, "All reactors must have unique names when drawn." # display wall velocity as arrow indicating the wall's movement - v = w.velocity + v = w.expansion_rate / w.area if v != 0 and show_wall_velocity: if v > 0: inflow_name, outflow_name = r_in.name, r_out.name diff --git a/interfaces/cython/cantera/mixture.pyx b/interfaces/cython/cantera/mixture.pyx index 8263ae01e7..74985b0c6d 100644 --- a/interfaces/cython/cantera/mixture.pyx +++ b/interfaces/cython/cantera/mixture.pyx @@ -10,7 +10,6 @@ from ._utils cimport * cdef class Mixture: """ - Class Mixture represents mixtures of one or more phases of matter. To construct a mixture, supply a list of phases to the constructor, each paired with the number of moles for that phase:: @@ -30,6 +29,17 @@ cdef class Mixture: this phase. Mixture objects, on the other hand, represent the full extensive state. + .. caution:: + The Mixture class exists mainly for the purpose of providing input for multiphase + equilibrium calculations. Its functionality for modifying the state of the + mixture and computing properties is quite limited. Mixture objects cannot be used + in conjunction with reactor networks. + + Furthermore, the multiphase equilibrium solvers currently have a number of + problems that lead to solver failures or incorrect results for some inputs. See + the `list of issues on GitHub `_ + for more information. + Mixture objects are 'lightweight' in the sense that they do not store parameters needed to compute thermodynamic or kinetic properties of the phases. These are contained in the ('heavyweight') phase objects. Multiple diff --git a/samples/cxx/README.rst b/samples/cxx/README.rst index 3bf4d017c6..31f9feb910 100644 --- a/samples/cxx/README.rst +++ b/samples/cxx/README.rst @@ -1,2 +1,7 @@ C++ Examples ============ + +.. seealso:: + + For some guidance getting started with the Cantera C++ interface, please see the + :doc:`C++ Tutorial `. diff --git a/samples/python/README.rst b/samples/python/README.rst index 182e35f70d..a04ccbeef0 100644 --- a/samples/python/README.rst +++ b/samples/python/README.rst @@ -5,3 +5,6 @@ Python Examples To find examples covering a specific topic, see the :ref:`index of example tags `. + + If you're just getting started with Cantera, see our + :doc:`/userguide/python-tutorial` for an introduction to the Cantera Python interface. diff --git a/samples/python/surface_chemistry/diamond_cvd.py b/samples/python/kinetics/diamond_cvd.py similarity index 92% rename from samples/python/surface_chemistry/diamond_cvd.py rename to samples/python/kinetics/diamond_cvd.py index 2b1047cb75..60354a42f5 100644 --- a/samples/python/surface_chemistry/diamond_cvd.py +++ b/samples/python/kinetics/diamond_cvd.py @@ -6,8 +6,8 @@ version of a particular published growth mechanism (see file ``diamond.yaml`` for details). Only the surface coverage equations are solved here; the gas composition is fixed. (For an example of coupled gas-phase and surface, see -:doc:`catalytic_combustion.py `.) Atomic hydrogen plays an -important role in diamond CVD, and this example computes the growth rate and surface +:doc:`catalytic_combustion.py <../onedim/catalytic_combustion>`.) Atomic hydrogen plays +an important role in diamond CVD, and this example computes the growth rate and surface coverages as a function of [H] at the surface for fixed temperature and [CH3]. Requires: cantera >= 2.6.0, pandas >= 0.25.0, matplotlib >= 2.0 diff --git a/samples/python/reactors/interactive_path_diagram.py b/samples/python/kinetics/interactive_path_diagram.py similarity index 100% rename from samples/python/reactors/interactive_path_diagram.py rename to samples/python/kinetics/interactive_path_diagram.py diff --git a/samples/python/surface_chemistry/lithium_ion_battery.py b/samples/python/kinetics/lithium_ion_battery.py similarity index 100% rename from samples/python/surface_chemistry/lithium_ion_battery.py rename to samples/python/kinetics/lithium_ion_battery.py diff --git a/samples/python/kinetics/reaction_path.py b/samples/python/kinetics/reaction_path.py index 3b68f7cc14..0e37b29b1e 100644 --- a/samples/python/kinetics/reaction_path.py +++ b/samples/python/kinetics/reaction_path.py @@ -7,17 +7,21 @@ Graphviz can be obtained from https://www.graphviz.org/ or (possibly) installed using your operating system's package manager. -Requires: cantera >= 2.5.0 +Requires: cantera >= 2.5.0, graphviz .. tags:: Python, kinetics, reaction path analysis, pollutant formation """ from subprocess import run -import sys from pathlib import Path +import graphviz import cantera as ct +# %% +# Generate a Solution with a state where reactions are occurring +# -------------------------------------------------------------- +# # these lines can be replaced by any commands that generate # an object of a class derived from class Kinetics in some state. gas = ct.Solution('gri30.yaml') @@ -29,12 +33,18 @@ net.step() T = r.T +# %% +# Create a reaction path diagram following nitrogen +# ------------------------------------------------- element = 'N' diagram = ct.ReactionPathDiagram(gas, element) diagram.title = 'Reaction path diagram following {0}'.format(element) diagram.label_threshold = 0.01 +# %% +# Save the reaction path diagram +# ------------------------------ dot_file = 'rxnpath.dot' img_file = 'rxnpath.png' img_path = Path.cwd().joinpath(img_file) @@ -42,11 +52,12 @@ diagram.write_dot(dot_file) print(diagram.get_data()) -print("Wrote graphviz input file to '{0}'.".format(Path.cwd().joinpath(dot_file))) +print(f"Wrote graphviz input file to '{Path.cwd().joinpath(dot_file)}'.") -run('dot {0} -Tpng -o{1} -Gdpi=200'.format(dot_file, img_file).split()) -print("Wrote graphviz output file to '{0}'.".format(img_path)) +run(f"dot {dot_file} -Tpng -o{img_file} -Gdpi=200".split()) +print(f"Wrote graphviz output file to '{img_path}'.") -if "-view" in sys.argv: - import webbrowser - webbrowser.open(f"file:///{img_path}") +# %% +# View the reaction path diagram +# ------------------------------ +graphviz.Source(diagram.get_dot()) diff --git a/samples/python/surface_chemistry/sofc.py b/samples/python/kinetics/sofc.py similarity index 81% rename from samples/python/surface_chemistry/sofc.py rename to samples/python/kinetics/sofc.py index 06a2146f6f..0775b4d4d7 100644 --- a/samples/python/surface_chemistry/sofc.py +++ b/samples/python/kinetics/sofc.py @@ -21,14 +21,17 @@ It is recommended that you read input file ``sofc.yaml`` before reading or running this script. -Requires: cantera >= 2.6.0 +Requires: cantera >= 2.6.0, scipy, pandas .. tags:: Python, kinetics, electrochemistry, surface chemistry, fuel cell """ import cantera as ct import math -import csv +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt +from scipy.optimize import newton ct.add_module_directory() @@ -62,38 +65,9 @@ def equil_OCV(gas1, gas2): return (-ct.gas_constant * gas1.T * math.log(gas1['O2'].X[0] / gas2['O2'].X[0]) / (4.0 * ct.faraday)) - -def NewtonSolver(f, xstart, C=0.0): - """ - Solve f(x) = C by Newton iteration. - - xstart starting point for Newton iteration - - C constant - """ - f0 = f(xstart) - C - x0 = xstart - dx = 1.0e-6 - n = 0 - while n < 200: - ff = f(x0 + dx) - C - dfdx = (ff - f0)/dx - step = - f0/dfdx - - # avoid taking steps too large - if abs(step) > 0.1: - step = 0.1*step/abs(step) - - x0 += step - emax = 0.00001 # 0.01 mV tolerance - if abs(f0) < emax and n > 8: - return x0 - f0 = f(x0) - C - n += 1 - raise Exception('no root!') - - -##################################################################### +# %% # Anode-side phases -##################################################################### +# ----------------- # import the anode-side triple phase boundary and adjacent phases tpb_a = ct.Interface("sofc.yaml", "tpb") @@ -106,8 +80,8 @@ def NewtonSolver(f, xstart, C=0.0): anode_surf.name = 'anode surface' oxide_surf_a.name = 'anode-side oxide surface' -# this function is defined to use with NewtonSolver to invert the current- -# voltage function. NewtonSolver requires a function of one variable, so the +# this function is defined to use with a Newton solver to invert the current- +# voltage function. The Newton solver requires a function of one variable, so the # other objects are accessed through the global namespace. def anode_curr(E): """ @@ -134,10 +108,10 @@ def anode_curr(E): return ct.faraday * w[electron_index] * TPB_length_per_area -##################################################################### +# %% # Cathode-side phases -##################################################################### - +# ------------------- +# # Here for simplicity we are using the same phase and interface models for the # cathode as we used for the anode. In a more realistic simulation, separate # models would be used for the cathode, with a different reaction mechanism. @@ -178,8 +152,9 @@ def cathode_curr(E): # being drawn from the cathode (that is, negative production rate). return -ct.faraday * w[electron_index] * TPB_length_per_area - -# initialization +# %% +# Initialization +# -------------- # set the gas compositions, and temperatures of all phases gas_a.TPX = T, P, anode_gas_X @@ -193,6 +168,7 @@ def cathode_curr(E): for p in phases: p.TP = T, P +# %% # now bring the surface coverages into steady state with these gas # compositions. Note that the coverages are held fixed at these values - we do # NOT consider the change in coverages due to TPB reactions. For that, a more @@ -202,29 +178,30 @@ def cathode_curr(E): s.advance_coverages(tss) show_coverages(s) -# find open circuit potentials by solving for the E values that give -# zero current. -Ea0 = NewtonSolver(anode_curr, xstart=-0.51) -Ec0 = NewtonSolver(cathode_curr, xstart=0.51) +# %% +# Find open circuit potentials by solving for the E values that give zero current. +Ea0 = newton(anode_curr, x0=-0.51) +Ec0 = newton(cathode_curr, x0=0.51) print('\nocv from zero current is: ', Ec0 - Ea0) print('OCV from thermo equil is: ', equil_OCV(gas_a, gas_c)) print('Ea0 = ', Ea0) print('Ec0 = ', Ec0) -print() -# do polarization curve for anode overpotentials from -250 mV -# (cathodic) to +250 mV (anodic) +# %% +# Polarization curve +# ------------------ +# +# Vary the anode overpotentials from -250 mV (cathodic) to +250 mV (anodic) + Ea_min = Ea0 - 0.25 Ea_max = Ea0 + 0.25 +Ec = 1.0 # initial guess for Newton solver output_data = [] -# vary the anode overpotential, from cathodic to anodic polarization -for n in range(100): - Ea = Ea_min + 0.005*n - +for Ea in np.linspace(Ea_min, Ea_max, 100): # set the electrode potential. Note that the anode-side electrolyte is # held fixed at 0 V. anode_bulk.electric_potential = Ea @@ -248,7 +225,7 @@ def cathode_curr(E): # Find the value of the cathode potential relative to the cathode-side # electrolyte that yields the same current density as the anode current # density - Ec = NewtonSolver(cathode_curr, xstart=Ec0+0.1, C=curr) + Ec = newton(lambda E: cathode_curr(E) - curr, x0=Ec) cathode_bulk.electric_potential = phi_oxide_c + Ec @@ -258,9 +235,26 @@ def cathode_curr(E): cathode_bulk.electric_potential - anode_bulk.electric_potential]) -with open("sofc.csv", "w", newline="") as csvfile: - writer = csv.writer(csvfile) - writer.writerow(['i (mA/cm2)', 'eta_a', 'eta_c', 'eta_ohmic', 'Eload']) - writer.writerows(output_data) +# %% +# Collect data and save as CSV +# ---------------------------- +df = pd.DataFrame.from_records( + output_data, + index='i (mA/cm2)', + columns=['i (mA/cm2)', 'eta_a', 'eta_c', 'eta_ohmic', 'Eload'] +) +df.to_csv("sofc.csv") print('polarization curve data written to file sofc.csv') + +# %% +# Plot results +# ------------ + +fig, ax = plt.subplots() +ax.plot(df.index, df.eta_a, label=r'$\eta_{anode}$') +ax.plot(df.index, df.eta_c, label=r'$\eta_{cathode}$') +ax.plot(df.index, df.eta_ohmic, label=r'$\eta_{ohmic}$') +ax.plot(df.index, df.Eload, label=r'$E_{load}$') +ax.set(xlabel='current [mA/cm²]', ylabel='Voltage [V]') +ax.legend() diff --git a/samples/python/multiphase/README.rst b/samples/python/multiphase/README.rst deleted file mode 100644 index b6b2d91eff..0000000000 --- a/samples/python/multiphase/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -Multiphase ----------- diff --git a/samples/python/surface_chemistry/catalytic_combustion.py b/samples/python/onedim/catalytic_combustion.py similarity index 100% rename from samples/python/surface_chemistry/catalytic_combustion.py rename to samples/python/onedim/catalytic_combustion.py diff --git a/samples/python/reactors/1D_packed_bed.py b/samples/python/reactors/1D_packed_bed.py index 96d1d8d752..e6dd150b9a 100644 --- a/samples/python/reactors/1D_packed_bed.py +++ b/samples/python/reactors/1D_packed_bed.py @@ -140,10 +140,9 @@ # Import the reaction mechanism for Ammonia synthesis/decomposition on Ru-Ba/YSZ catalyst mechfile = "example_data/ammonia-Ru-Ba-YSZ-CSM-2019.yaml" -# Import the models for gas-phase -gas = ct.Solution(mechfile, "gas") -# Import the model for surface-phase -surf = ct.Interface(mechfile, "Ru_surface", [gas]) +# Import the models for surface-phase and gas +surf = ct.Interface(mechfile, "Ru_surface") +gas = surf.adjacent["gas"] # Other parameters n_gas = gas.n_species # number of gas species diff --git a/samples/python/surface_chemistry/1D_pfr_surfchem.py b/samples/python/reactors/1D_pfr_surfchem.py similarity index 100% rename from samples/python/surface_chemistry/1D_pfr_surfchem.py rename to samples/python/reactors/1D_pfr_surfchem.py diff --git a/samples/python/reactors/continuous_reactor.py b/samples/python/reactors/continuous_reactor.py index 258237a552..b2c42ebc95 100644 --- a/samples/python/reactors/continuous_reactor.py +++ b/samples/python/reactors/continuous_reactor.py @@ -230,15 +230,20 @@ mdot=stirred_reactor.mass / residence_time, ) pressure_regulator = ct.PressureController( - upstream=stirred_reactor, downstream=exhaust, primary=mass_flow_controller + upstream=stirred_reactor, downstream=exhaust, primary=mass_flow_controller, + K=1e-3, ) reactor_network = ct.ReactorNet([stirred_reactor]) # Re-run the isothermal simulations tic = time.time() - reactor_network.advance(max_simulation_time) + counter = 0 + while reactor_network.time < max_simulation_time: + reactor_network.step() + counter += 1 toc = time.time() - print(f"Simulation at T={reactor_temperature}K took {toc-tic:3.2f}s to compute") + print(f"Simulation at T={reactor_temperature} K took {toc-tic:3.2f} s to compute " + f"with {counter} steps") concentrations = stirred_reactor.thermo.X temp_dependence.append(stirred_reactor.thermo.state) diff --git a/samples/python/reactors/mix1.py b/samples/python/reactors/mix1.py index 9cb34feb79..9cc2ad97e3 100644 --- a/samples/python/reactors/mix1.py +++ b/samples/python/reactors/mix1.py @@ -16,19 +16,23 @@ Compare this approach for the transient problem to the method used for the steady-state problem in :doc:`mixing.py <../thermo/mixing>`. -Requires: cantera >= 2.5.0 +Requires: cantera >= 3.1.0, graphviz .. tags:: Python, thermodynamics, reactor network, mixture """ import cantera as ct +# %% +# Set up the reactor network +# -------------------------- +# # Use air for stream a. gas_a = ct.Solution('air.yaml') gas_a.TPX = 300.0, ct.one_atm, 'O2:0.21, N2:0.78, AR:0.01' rho_a = gas_a.density - +# %% # Use GRI-Mech 3.0 for stream b (methane) and for the mixer. If it is desired # to have a pure mixer, with no chemistry, use instead a reaction mechanism # for gas_b that has no reactions. @@ -36,36 +40,48 @@ gas_b.TPX = 300.0, ct.one_atm, 'CH4:1' rho_b = gas_b.density +# %% # Create reservoirs for the two inlet streams and for the outlet stream. The -# upsteam reservoirs could be replaced by reactors, which might themselves be +# upstream reservoirs could be replaced by reactors, which might themselves be # connected to reactors further upstream. The outlet reservoir could be # replaced with a reactor with no outlet, if it is desired to integrate the # composition leaving the mixer in time, or by an arbitrary network of # downstream reactors. -res_a = ct.Reservoir(gas_a) -res_b = ct.Reservoir(gas_b) -downstream = ct.Reservoir(gas_b) +res_a = ct.Reservoir(gas_a, name='air') +res_b = ct.Reservoir(gas_b, name='fuel') +downstream = ct.Reservoir(gas_b, name='outlet') +# %% # Create a reactor for the mixer. A reactor is required instead of a # reservoir, since the state will change with time if the inlet mass flow # rates change or if there is chemistry occurring. gas_b.TPX = 300.0, ct.one_atm, 'O2:0.21, N2:0.78, AR:0.01' -mixer = ct.IdealGasReactor(gas_b) +mixer = ct.IdealGasReactor(gas_b, name='mixer') -# create two mass flow controllers connecting the upstream reservoirs to the +# %% +# Create two mass flow controllers connecting the upstream reservoirs to the # mixer, and set their mass flow rates to values corresponding to # stoichiometric combustion. mfc1 = ct.MassFlowController(res_a, mixer, mdot=rho_a*2.5/0.21) mfc2 = ct.MassFlowController(res_b, mixer, mdot=rho_b*1.0) -# connect the mixer to the downstream reservoir with a valve. +# %% +# Connect the mixer to the downstream reservoir with a valve. outlet = ct.Valve(mixer, downstream, K=10.0) sim = ct.ReactorNet([mixer]) -# Since the mixer is a reactor, we need to integrate in time to reach steady -# state +# %% +# Get the mixed state +# ------------------- +# +# Since the mixer is a reactor, we need to integrate in time to reach steady state. sim.advance_to_steady_state() # view the state of the gas in the mixer print(mixer.thermo.report()) + +# %% +# Show the network structure +# -------------------------- +diagram = sim.draw(print_state=True, species="X") diff --git a/samples/python/reactors/preconditioned_integration.py b/samples/python/reactors/preconditioned_integration.py index a177d4825d..2f791d690f 100644 --- a/samples/python/reactors/preconditioned_integration.py +++ b/samples/python/reactors/preconditioned_integration.py @@ -3,7 +3,7 @@ ======================================================================== Ideal gas, constant-pressure, adiabatic kinetics simulation that compares preconditioned -and non-preconditioned integration of nDodecane. +and non-preconditioned integration of n-hexane. Requires: cantera >= 3.0.0, matplotlib >= 2.0 @@ -12,26 +12,26 @@ import cantera as ct import numpy as np import matplotlib.pyplot as plt +plt.rcParams['figure.constrained_layout.use'] = True from timeit import default_timer -def integrate_reactor(n_reactors=1, preconditioner=True): - """ - Compare the integrations of a preconditioned reactor network and a - non-preconditioned reactor network. Increase the number of reactors in the - network with the keyword argument `n_reactors` to see greater differences in - performance. - """ - # Use a reduced n-dodecane mechanism with PAH formation pathways - gas = ct.Solution('nDodecane_Reitz.yaml', 'nDodecane_IG') - # Create multiple reactors and set initial contents +# %% +# Simulation setup +# ---------------- +# +# Create a reactor network for simulating the constant pressure ignition of a +# stoichiometric n-hexane/air mixture, with or without the use of the preconditioned +# solver. +def integrate_reactor(preconditioner=True): + # Use a detailed n-hexane mechanism with 1268 species + gas = ct.Solution('example_data/n-hexane-NUIG-2015.yaml') gas.TP = 1000, ct.one_atm - gas.set_equivalence_ratio(1, 'c12h26', 'n2:3.76, o2:1.0') - reactors = [ct.IdealGasConstPressureMoleReactor(gas) for n in range(n_reactors)] + gas.set_equivalence_ratio(1, 'NC6H14', 'N2:3.76, O2:1.0') + reactor = ct.IdealGasConstPressureMoleReactor(gas) # set volume for reactors - for r in reactors: - r.volume = 0.1 + reactor.volume = 0.1 # Create reactor network - sim = ct.ReactorNet(reactors) + sim = ct.ReactorNet([reactor]) # Add preconditioner if preconditioner: sim.derivative_settings = {"skip-third-bodies":True, "skip-falloff":True} @@ -40,10 +40,10 @@ def integrate_reactor(n_reactors=1, preconditioner=True): # Advance to steady state integ_time = default_timer() # solution array for state data - states = ct.SolutionArray(reactors[0].thermo, extra=['time']) + states = ct.SolutionArray(reactor.thermo, extra=['time']) # advance to steady state manually while (sim.time < 0.1): - states.append(reactors[0].thermo.state, time=sim.time) + states.append(reactor.thermo.state, time=sim.time) sim.step() integ_time = default_timer() - integ_time # Return time to integrate @@ -53,35 +53,41 @@ def integrate_reactor(n_reactors=1, preconditioner=True): print(f"Non-preconditioned Integration Time: {integ_time:f}") # Get and output solver stats for key, value in sim.solver_stats.items(): - print(key, value) + print(f"{key:>24s}: {value}") print("\n") # return some variables for plotting - return states.time, states.T, states('CO2').Y, states('C12H26').Y + return states.time, states.T, states('CO2').Y, states('NC6H14').Y -if __name__ == "__main__": - # integrate both to steady state - timep, Tp, CO2p, C12H26p = integrate_reactor(preconditioner=True) - timenp, Tnp, CO2np, C12H26np = integrate_reactor(preconditioner=False) - # plot selected state variables - fig, axs = plt.subplots(1, 3) - fig.tight_layout() - ax1, ax2, ax3 = axs - # temperature plot - ax1.set_xlabel("Time") - ax1.set_ylabel("Temperature") - ax1.plot(timenp, Tnp, linewidth=2) - ax1.plot(timep, Tp, linewidth=2, linestyle=":") - ax1.legend(["Normal", "Preconditioned"]) - # CO2 plot - ax2.set_xlabel("Time") - ax2.set_ylabel("CO2") - ax2.plot(timenp, CO2np, linewidth=2) - ax2.plot(timep, CO2p, linewidth=2, linestyle=":") - ax2.legend(["Normal", "Preconditioned"]) - # C12H26 plot - ax3.set_xlabel("Time") - ax3.set_ylabel("C12H26") - ax3.plot(timenp, C12H26np, linewidth=2) - ax3.plot(timep, C12H26p, linewidth=2, linestyle=":") - ax3.legend(["Normal", "Preconditioned"]) - plt.show() +# %% +# Integrate with sparse, preconditioned solver +# -------------------------------------------- +timep, Tp, CO2p, NC6H14p = integrate_reactor(preconditioner=True) + +# %% +# Integrate with direct linear solver +# ----------------------------------- +timenp, Tnp, CO2np, NC6H14np = integrate_reactor(preconditioner=False) + +# %% +# Plot selected state variables +# ----------------------------- +fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(5, 8)) +# temperature plot +ax1.set_xlabel("Time") +ax1.set_ylabel("Temperature") +ax1.plot(timenp, Tnp, linewidth=2) +ax1.plot(timep, Tp, linewidth=2, linestyle=":") +ax1.legend(["Normal", "Preconditioned"]) +# CO2 plot +ax2.set_xlabel("Time") +ax2.set_ylabel("CO2") +ax2.plot(timenp, CO2np, linewidth=2) +ax2.plot(timep, CO2p, linewidth=2, linestyle=":") +ax2.legend(["Normal", "Preconditioned"]) +# C12H26 plot +ax3.set_xlabel("Time") +ax3.set_ylabel("NC6H14") +ax3.plot(timenp, NC6H14np, linewidth=2) +ax3.plot(timep, NC6H14p, linewidth=2, linestyle=":") +ax3.legend(["Normal", "Preconditioned"]) +plt.show() diff --git a/samples/python/reactors/reactor2.py b/samples/python/reactors/reactor2.py index 0f2460faa4..ad5febc625 100644 --- a/samples/python/reactors/reactor2.py +++ b/samples/python/reactors/reactor2.py @@ -5,10 +5,10 @@ Two reactors connected with a piston, with heat loss to the environment This script simulates the following situation. A closed cylinder with volume 2 -m^3 is divided into two equal parts by a massless piston that moves with speed +m³ is divided into two equal parts by a massless piston that moves with speed proportional to the pressure difference between the two sides. It is initially held in place in the middle. One side is filled with 1000 K argon at -20 atm, and the other with a combustible 500 K methane/air mixture at 0.1 atm +20 atm, and the other with a combustible 500 K methane/air mixture at 0.2 atm (:math:`\phi = 1.1`). At :math:`t = 0`, the piston is released and begins to move due to the large pressure difference, compressing and heating the methane/air mixture, which eventually explodes. At the same time, the argon cools as it expands. @@ -18,17 +18,21 @@ Note that this simulation, being zero-dimensional, takes no account of shock wave propagation. It is somewhat artificial, but nevertheless instructive. -Requires: cantera >= 2.5.0, matplotlib >= 2.0 +Requires: cantera >= 2.5.0, matplotlib >= 2.0, pandas .. tags:: combustion, reactor network, plotting """ -import sys -import os -import csv +import pandas as pd +import matplotlib.pyplot as plt +plt.rcParams['figure.constrained_layout.use'] = True import cantera as ct +# %% +# Set up the simulation +# --------------------- +# # First create each gas needed, and a reactor or reservoir for each one. # create an argon gas object and set its state @@ -36,18 +40,18 @@ ar.TPX = 1000.0, 20.0 * ct.one_atm, "AR:1" # create a reactor to represent the side of the cylinder filled with argon -r1 = ct.IdealGasReactor(ar) +r1 = ct.IdealGasReactor(ar, name="Argon partition") # create a reservoir for the environment, and fill it with air. -env = ct.Reservoir(ct.Solution('air.yaml')) +env = ct.Reservoir(ct.Solution('air.yaml'), name="Environment") # use GRI-Mech 3.0 for the methane/air mixture, and set its initial state gas = ct.Solution('gri30.yaml') gas.TP = 500.0, 0.2 * ct.one_atm -gas.set_equivalence_ratio(1.1, 'CH4:1.0', 'O2:2, N2:7.52') +gas.set_equivalence_ratio(1.1, 'CH4:1.0', 'O2:1, N2:3.76') # create a reactor for the methane/air side -r2 = ct.IdealGasReactor(gas) +r2 = ct.IdealGasReactor(gas, name="Reacting partition") # Now couple the reactors by defining common walls that may move (a piston) or # conduct heat @@ -62,60 +66,56 @@ sim = ct.ReactorNet([r1, r2]) +# %% +# Show the initial state +# ---------------------- +sim.draw(print_state=True, species="X") + +# %% # Now the problem is set up, and we're ready to solve it. -print('finished setup, begin solution...') time = 0.0 n_steps = 300 -output_data = [] states1 = ct.SolutionArray(ar, extra=['t', 'V']) states2 = ct.SolutionArray(gas, extra=['t', 'V']) for n in range(n_steps): time += 4.e-4 - print(n, time, r2.T) sim.advance(time) states1.append(r1.thermo.state, t=time, V=r1.volume) states2.append(r2.thermo.state, t=time, V=r2.volume) - output_data.append( - [time, r1.thermo.T, r1.thermo.P, r1.volume, r2.thermo.T, - r2.thermo.P, r2.volume] - ) - -with open('piston.csv', 'w', newline="") as outfile: - csvfile = csv.writer(outfile) - csvfile.writerow( - ['time (s)', 'T1 (K)', 'P1 (Bar)', 'V1 (m3)', 'T2 (K)', - 'P2 (Bar)', 'V2 (m3)'] - ) - csvfile.writerows(output_data) - -print('Output written to file piston.csv') -print('Directory: '+os.getcwd()) - -if '--plot' in sys.argv: - import matplotlib.pyplot as plt - plt.clf() - plt.subplot(2, 2, 1) - h = plt.plot(states1.t, states1.T, 'g-', states2.t, states2.T, 'b-') - # plt.legend(['Reactor 1','Reactor 2'], 2) - plt.xlabel('Time (s)') - plt.ylabel('Temperature (K)') - - plt.subplot(2, 2, 2) - plt.plot(states1.t, states1.P / 1e5, 'g-', states2.t, states2.P / 1e5, 'b-') - # plt.legend(['Reactor 1','Reactor 2'], 2) - plt.xlabel('Time (s)') - plt.ylabel('Pressure (Bar)') - - plt.subplot(2, 2, 3) - plt.plot(states1.t, states1.V, 'g-', states2.t, states2.V, 'b-') - # plt.legend(['Reactor 1','Reactor 2'], 2) - plt.xlabel('Time (s)') - plt.ylabel('Volume (m$^3$)') - - plt.figlegend(h, ['Reactor 1', 'Reactor 2'], loc='lower right') - plt.tight_layout() - plt.show() -else: - print("To view a plot of these results, run this script with the option --plot") + +# %% +# Combine the results and save for later processing or plotting +df = pd.DataFrame.from_dict({ + 'time (s)': states1.t, + 'T1 (K)': states1.T, + 'P1 (bar)': states1.P / 1e5, + 'V1 (m3)': states1.V, + 'T2 (K)': states2.T, + 'P2 (bar)': states2.P / 1e5, + 'V2 (m3)': states2.V, +}) +df.to_csv("pistion.csv") +df + +# %% +# Plot results +# ------------ +fig, ax = plt.subplots(3, 1, figsize=(5,8)) + +ax[0].plot(states1.t, states1.T, 'g-', label='Argon partition') +ax[0].plot(states2.t, states2.T, 'b-', label='Reacting partition') +ax[0].set(xlabel='Time (s)', ylabel='Temperature (K)') +ax[0].legend() + +ax[1].plot(states1.t, states1.P / 1e5, 'g-', states2.t, states2.P / 1e5, 'b-') +ax[1].set(xlabel='Time (s)', ylabel='Pressure (Bar)') + +ax[2].plot(states1.t, states1.V, 'g-', states2.t, states2.V, 'b-') +_ = ax[2].set(xlabel='Time (s)', ylabel='Volume (m$^3$)') + +# %% +# Show the final state +# -------------------- +sim.draw(print_state=True, species="X") diff --git a/samples/python/reactors/surf_pfr_chain.py b/samples/python/reactors/surf_pfr_chain.py index 5398d7e829..d076441d7a 100644 --- a/samples/python/reactors/surf_pfr_chain.py +++ b/samples/python/reactors/surf_pfr_chain.py @@ -46,13 +46,11 @@ t = tc + 273.15 # convert to Kelvin -# import the gas model and set the initial conditions -gas = ct.Solution(yaml_file, 'gas') -gas.TPX = t, ct.one_atm, 'CH4:1, O2:1.5, AR:0.1' - -# import the surface model -surf = ct.Interface(yaml_file, 'Pt_surf', [gas]) +# import the phase models and set the initial conditions +surf = ct.Interface(yaml_file, 'Pt_surf') surf.TP = t, ct.one_atm +gas = surf.adjacent['gas'] +gas.TPX = t, ct.one_atm, 'CH4:1, O2:1.5, AR:0.1' rlen = length/(NReactors-1) rvol = area * rlen * porosity diff --git a/samples/python/surface_chemistry/README.rst b/samples/python/surface_chemistry/README.rst deleted file mode 100644 index baaa67d544..0000000000 --- a/samples/python/surface_chemistry/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -Surface chemistry ------------------ diff --git a/samples/python/multiphase/adiabatic.py b/samples/python/thermo/adiabatic.py similarity index 100% rename from samples/python/multiphase/adiabatic.py rename to samples/python/thermo/adiabatic.py diff --git a/samples/python/multiphase/plasma_equilibrium.py b/samples/python/thermo/plasma_equilibrium.py similarity index 89% rename from samples/python/multiphase/plasma_equilibrium.py rename to samples/python/thermo/plasma_equilibrium.py index 04a6d286ef..3c5b654412 100644 --- a/samples/python/multiphase/plasma_equilibrium.py +++ b/samples/python/thermo/plasma_equilibrium.py @@ -7,6 +7,11 @@ Requires: cantera >= 2.5.0, matplotlib >= 2.0 +.. warning:: + The results of this example are incorrect due to known issues with the multiphase + equilibrium solver. See GitHub Issue + `#270 `_. + .. tags:: Python, equilibrium, multiphase, plasma, saving output """ diff --git a/test/python/test_composite.py b/test/python/test_composite.py index 95c9e000ce..260d09c2eb 100644 --- a/test/python/test_composite.py +++ b/test/python/test_composite.py @@ -157,9 +157,7 @@ def test_pickle_gas_with_transport(self): self.assertEqual(gas2.transport_model, "multicomponent") def test_pickle_interface(self): - gas = ct.Solution("diamond.yaml", "gas") - solid = ct.Solution("diamond.yaml", "diamond") - interface = ct.Interface("diamond.yaml", "diamond_100", (gas, solid)) + interface = ct.Interface("diamond.yaml", "diamond_100") with self.assertRaises(NotImplementedError): with open(self.test_work_path / "interface.pkl", "wb") as pkl: diff --git a/test/python/test_equilibrium.py b/test/python/test_equilibrium.py index 00e1a228e5..a99c744048 100644 --- a/test/python/test_equilibrium.py +++ b/test/python/test_equilibrium.py @@ -157,6 +157,10 @@ def test_equil_TP(self): data[i,1:] = self.mix.species_moles + # The reference values for this test are all completely non-physical, due to the + # VCS solver extrapolating thermo polynomials outside of their valid range. See + # https://github.com/Cantera/cantera/issues/270. The results show ice at + # temperatures of over 1000 K, and liquid water for temperatures of 2000-5000 K. self.compare(data, self.test_data_path / "koh-equil-TP.csv") @utilities.slow_test diff --git a/test/python/test_kinetics.py b/test/python/test_kinetics.py index 340d0ba7a5..4d17cd3360 100644 --- a/test/python/test_kinetics.py +++ b/test/python/test_kinetics.py @@ -220,8 +220,8 @@ def test_idealgas(self): gas2.net_production_rates) def test_surface(self): - gas = ct.Solution("ptcombust.yaml", "gas") - surf1 = ct.Interface("ptcombust.yaml", "Pt_surf", [gas]) + surf1 = ct.Interface("ptcombust.yaml", "Pt_surf") + gas = surf1.adjacent["gas"] surf_species = ct.Species.list_from_file("ptcombust.yaml") reactions = ct.Reaction.list_from_file("ptcombust.yaml", surf1) @@ -295,8 +295,7 @@ def test_add_reaction(self): gas2.net_production_rates) def test_coverage_dependence_flags(self): - gas = ct.Solution("ptcombust.yaml", "gas") - surf = ct.Interface("ptcombust.yaml", "Pt_surf", [gas]) + surf = ct.Interface("ptcombust.yaml", "Pt_surf") surf.TP = 900, ct.one_atm surf.coverages = {"PT(S)":1} with self.assertRaises(NotImplementedError): @@ -306,11 +305,7 @@ def test_coverage_dependence_flags(self): surf.net_rates_of_progress_ddCi def test_electrochemistry_flags(self): - # Phases - mech = "lithium_ion_battery.yaml" - anode, cathode, metal, electrolyte = ct.import_phases( - mech, ["anode", "cathode", "electron", "electrolyte"]) - anode_int = ct.Interface(mech, "edge_anode_electrolyte", adjacent=[anode, metal, electrolyte]) + anode_int = ct.Interface("lithium_ion_battery.yaml", "edge_anode_electrolyte") with self.assertRaises(NotImplementedError): anode_int.net_rates_of_progress_ddCi # set skip and try to get jacobian again @@ -537,8 +532,7 @@ def test_sticking_coeff_err(self): for err in err_msg: with pytest.warns(UserWarning, match=err): - gas = ct.Solution("sticking_coeff_check.yaml") - ct.Interface("sticking_coeff_check.yaml", "Pt_surf", [gas]) + ct.Interface("sticking_coeff_check.yaml", "Pt_surf") def check_raises(yaml, err_msg, line): @@ -958,19 +952,20 @@ def newton_solve(f, xstart, C=0.0): return x0 # Anode-side phases - gas_a, anode_bulk, oxide_a = ct.import_phases(mech, - ['gas', 'metal', 'oxide_bulk',]) - anode_surf = ct.Interface(mech, 'metal_surface', [gas_a]) - oxide_surf_a = ct.Interface(mech, 'oxide_surface', [gas_a, oxide_a]) - tpb_a = ct.Interface(mech, 'tpb', [anode_bulk, anode_surf, oxide_surf_a]) + tpb_a = ct.Interface(mech, "tpb") + anode_surf = tpb_a.adjacent["metal_surface"] + gas_a = anode_surf.adjacent["gas"] + oxide_surf_a = tpb_a.adjacent["oxide_surface"] + oxide_a = oxide_surf_a.adjacent["oxide_bulk"] + anode_bulk = tpb_a.adjacent["metal"] # Cathode-side phases - gas_c, cathode_bulk, oxide_c = ct.import_phases(mech, - ['gas', 'metal', 'oxide_bulk']) - cathode_surf = ct.Interface(mech, 'metal_surface', [gas_c]) - oxide_surf_c = ct.Interface(mech, 'oxide_surface', [gas_c, oxide_c]) - tpb_c = ct.Interface(mech, 'tpb', [cathode_bulk, cathode_surf, - oxide_surf_c]) + tpb_c = ct.Interface(mech, "tpb") + cathode_surf = tpb_c.adjacent["metal_surface"] + gas_c = cathode_surf.adjacent["gas"] + oxide_surf_c = tpb_c.adjacent["oxide_surface"] + oxide_c = oxide_surf_c.adjacent["oxide_bulk"] + cathode_bulk = tpb_c.adjacent["metal"] kElectron_a = tpb_a.kinetics_species_index("electron") def anode_curr(E): @@ -1127,13 +1122,10 @@ def cathode_current(phi_s, phi_l, X_Li_cathode): assert np.allclose(data, ref, rtol=1e-7) def test_interface_current(self): - file = "lithium_ion_battery.yaml" - - # The 'elde' electrode phase is needed as a source/sink for electrons: - anode = ct.Solution(file, "anode") - elect = ct.Solution(file, "electron") - elyte = ct.Solution(file, "electrolyte") - anode_int = ct.Interface(file, "edge_anode_electrolyte", [anode, elect, elyte]) + anode_int = ct.Interface("lithium_ion_battery.yaml", "edge_anode_electrolyte") + anode = anode_int.adjacent["anode"] + elect = anode_int.adjacent["electron"] + elyte = anode_int.adjacent["electrolyte"] anode.X = [0.9, 0.1] elyte.X = [0.4, 0.3, 0.15, 0.15] @@ -1554,8 +1546,8 @@ def test_Blowers_Masel_change_enthalpy(self): def test_interface(self): surf_species = ct.Species.list_from_file("ptcombust.yaml") - gas = ct.Solution("ptcombust.yaml", "gas") - surf1 = ct.Interface("ptcombust.yaml", "Pt_surf", [gas]) + surf1 = ct.Interface("ptcombust.yaml", "Pt_surf") + gas = surf1.adjacent["gas"] rate = ct.InterfaceArrheniusRate(3.7e20, 0, 67.4e6) rate.coverage_dependencies = {'H(S)': (0, 0, -6e6)} @@ -1733,10 +1725,8 @@ def test_modify_BlowersMasel(self): self.assertNear(A2 * T**b2 * np.exp(-Ta2 / T), gas.forward_rate_constants[0]) def test_modify_interface(self): - gas = ct.Solution("ptcombust.yaml", "gas") - surf = ct.Interface("ptcombust.yaml", "Pt_surf", [gas]) + surf = ct.Interface("ptcombust.yaml", "Pt_surf") surf.coverages = 'O(S):0.1, PT(S):0.5, H(S):0.4' - gas.TP = surf.TP R = surf.reaction(1) R.rate.coverage_dependencies = {'O(S)': (0.0, 0.0, -3e6)} @@ -1762,10 +1752,8 @@ def test_invalid_sticking(self): surf.add_reaction(rxn) def test_modify_sticking(self): - gas = ct.Solution("ptcombust.yaml", "gas") - surf = ct.Interface("ptcombust.yaml", "Pt_surf", [gas]) + surf = ct.Interface("ptcombust.yaml", "Pt_surf") surf.coverages = "O(S):0.1, PT(S):0.5, H(S):0.4" - gas.TP = surf.TP R = surf.reaction(2) R.rate = ct.StickingArrheniusRate(0.25, 0, 0) # original sticking coefficient = 1.0 @@ -1777,14 +1765,11 @@ def test_modify_sticking(self): def test_motz_wise(self): # Motz & Wise off for all reactions - gas1 = ct.Solution("ptcombust.yaml", "gas") - surf1 = ct.Interface("ptcombust.yaml", "Pt_surf", [gas1]) + surf1 = ct.Interface("ptcombust.yaml", "Pt_surf") surf1.coverages = 'O(S):0.1, PT(S):0.5, H(S):0.4' - gas1.TP = surf1.TP # Motz & Wise correction on for some reactions - gas2 = ct.Solution("ptcombust-motzwise.yaml", "gas") - surf2 = ct.Interface("ptcombust-motzwise.yaml", "Pt_surf", [gas2]) + surf2 = ct.Interface("ptcombust-motzwise.yaml", "Pt_surf") surf2.TPY = surf1.TPY k1 = surf1.forward_rate_constants @@ -1843,15 +1828,11 @@ def test_modify_BMsticking(self): def test_BMmotz_wise(self): # Motz & Wise off for all reactions - gas1 = ct.Solution("blowers-masel.yaml", "gas", transport_model=None) - gas1.TPX = 300, ct.one_atm, {"CH4": 0.095, "O2": 0.21, "AR": 0.79} - surf1 = ct.Interface("blowers-masel.yaml", "Pt_surf", [gas1]) + surf1 = ct.Interface("blowers-masel.yaml", "Pt_surf") surf1.coverages = 'O(S):0.1, PT(S):0.5, H(S):0.4' - gas1.TP = surf1.TP # Motz & Wise correction on for some reactions - gas2 = ct.Solution("blowers-masel.yaml", "gas") - surf2 = ct.Interface("blowers-masel.yaml", "Pt_motz_wise", [gas2]) + surf2 = ct.Interface("blowers-masel.yaml", "Pt_motz_wise") surf2.TPY = surf1.TPY k1 = surf1.forward_rate_constants diff --git a/test/python/test_onedim.py b/test/python/test_onedim.py index 14612090ee..cb0bd46572 100644 --- a/test/python/test_onedim.py +++ b/test/python/test_onedim.py @@ -1713,8 +1713,8 @@ def check_save_restore(self, jet): class TestImpingingJet(utilities.CanteraTest): def setUp(self): - self.gas = ct.Solution("ptcombust-simple.yaml", "gas") - self.surf_phase = ct.Interface("ptcombust-simple.yaml", "Pt_surf", [self.gas]) + self.surf_phase = ct.Interface("ptcombust-simple.yaml", "Pt_surf") + self.gas = self.surf_phase.adjacent["gas"] def create_reacting_surface(self, comp, tsurf, tinlet, width): self.gas.TPX = tinlet, ct.one_atm, comp diff --git a/test/python/test_reactor.py b/test/python/test_reactor.py index d9c50ca050..0fc5bb9c88 100644 --- a/test/python/test_reactor.py +++ b/test/python/test_reactor.py @@ -1965,10 +1965,8 @@ class TestSurfaceKinetics(utilities.CanteraTest): def make_reactors(self): self.net = ct.ReactorNet() - self.gas = ct.Solution('diamond.yaml', 'gas') - self.solid = ct.Solution('diamond.yaml', 'diamond') - self.interface = ct.Interface('diamond.yaml', 'diamond_100', - (self.gas, self.solid)) + self.interface = ct.Interface('diamond.yaml', 'diamond_100') + self.gas = self.interface.adjacent['gas'] self.gas.TPX = None, 1.0e3, 'H:0.002, H2:1, CH4:0.01, CH3:0.0002' self.r1 = ct.IdealGasReactor(self.gas) self.r1.volume = 0.01 @@ -2094,9 +2092,8 @@ def test_sensitivities1(self): def test_sensitivities2(self): net = ct.ReactorNet() - gas1 = ct.Solution("diamond.yaml", "gas") - solid = ct.Solution("diamond.yaml", "diamond") - interface = ct.Interface("diamond.yaml", "diamond_100", (gas1, solid)) + interface = ct.Interface("diamond.yaml", "diamond_100") + gas1 = interface.adjacent["gas"] r1 = ct.IdealGasReactor(gas1) net.add_reactor(r1) net.atol_sensitivity = 1e-10 @@ -2256,9 +2253,8 @@ def integrate(r, net): @utilities.slow_test def test_parameter_order3(self): # Test including reacting surfaces - gas1 = ct.Solution("diamond.yaml", "gas") - solid = ct.Solution("diamond.yaml", "diamond") - interface = ct.Interface("diamond.yaml", "diamond_100", (gas1, solid)) + interface = ct.Interface("diamond.yaml", "diamond_100") + gas1 = interface.adjacent["gas"] gas2 = ct.Solution('h2o2.yaml', transport_model=None) @@ -2659,8 +2655,8 @@ def test_ConstPressureReactor(self): class AdvanceCoveragesTest(utilities.CanteraTest): def setup(self, model="ptcombust.yaml", gas_phase="gas", interface_phase="Pt_surf"): # create gas and interface - self.gas = ct.Solution(model, gas_phase) - self.surf = ct.Interface(model, interface_phase, [self.gas]) + self.surf = ct.Interface(model, interface_phase) + self.gas = self.surf.adjacent["gas"] def test_advance_coverages_parameters(self): # create gas and interface diff --git a/test/python/test_thermo.py b/test/python/test_thermo.py index 1c2ab424db..3f29eb12fe 100644 --- a/test/python/test_thermo.py +++ b/test/python/test_thermo.py @@ -1114,10 +1114,7 @@ def test_nondimensional(self): class TestInterfacePhase(utilities.CanteraTest): def setUp(self): - self.gas = ct.Solution("diamond.yaml", "gas") - self.solid = ct.Solution("diamond.yaml", "diamond") - self.interface = ct.Interface("diamond.yaml", "diamond_100", - (self.gas, self.solid)) + self.interface = ct.Interface("diamond.yaml", "diamond_100") def test_properties(self): self.interface.site_density = 100