Note
Go to the end to download the full example code.
Explore cooling water vapor#
How does the volume of water vapor evolves when it is cooled from a temperature
above the boiling point to a temperature that is just above the freezing point
at constant pressure? How fast does the vapor volume drop? This example uses
the GivenPressureBatchReactorFixedTemperature model to explore
the different behaviors between an ideal gas water vapor and
its real gas counterpart.
Import PyChemkin packages and start the logger#
from pathlib import Path
import matplotlib.pyplot as plt # plotting
import numpy as np # number crunching
import ansys.chemkin.core as ck # Chemkin
from ansys.chemkin.core import Color
# chemkin batch reactor model (transient)
from ansys.chemkin.core.batchreactors.batchreactor import (
GivenPressureBatchReactorFixedTemperature,
)
from ansys.chemkin.core.logger import logger
# check working directory
current_dir = str(Path.cwd())
logger.debug("working directory: " + current_dir)
# set interactive mode for plotting the results
# interactive = True: display plot
# interactive = False: save plot as a PNG file
global interactive
interactive = True
Create a chemistry set#
To compare the different behaviors of the water vapor under the ideal gas and
real gas assumptions, you must use a real gas EOS-enabled gas mechanism.
The ‘C2 NOx’ mechanism that includes information about the *Soave cubic Equation
of State (EOS) is suitable for this endeavor. Therefore, the MyMech chemistry
set is created from this gas phase mechanism.
# set mechanism directory (the default Chemkin mechanism data directory)
data_dir = Path(ck.ansys_dir) / "reaction" / "data"
mechanism_dir = data_dir
# create a chemistry set based on C2_NOx using an alternative method
MyMech = ck.Chemistry(label="C2 NOx")
# set mechanism input files individually
# this mechanism file contains all the necessary thermodynamic and transport data
# thus, there is no need to specify thermodynamic and transport data files
MyMech.chemfile = str(Path(mechanism_dir) / "C2_NOx_SRK.inp")
Preprocess the C2 NOx chemistry set#
# preprocess the mechanism file
ierror = MyMech.preprocess()
if ierror == 0:
print(Color.GREEN + ">>> Preprocessing succeeded.", end=Color.END)
else:
print(Color.RED + ">>> Preprocessing failed.", end=Color.END)
exit()
Set up the air/water vapor mixture#
Create the mixture of air and water vapor inside the imaginary strong but elastic container. The initial gas temperature is set to 500 [K] and at a constant pressure of 100 [atm]. At this temperature and pressure, the mixture should be entirely in gas form.
mist = ck.Mixture(MyMech)
# set mole fraction
mist.x = [("H2O", 2.0), ("O2", 1.0), ("N2", 3.76)]
mist.temperature = 500.0 # [K]
mist.pressure = 100.0 * ck.P_ATM
Create the reactor tank to perform the vapor cooling simulation#
Use the GivenPressureBatchReactorFixedTemperature() method to create
a constant-pressure batch reactor (with a given temperature).
Use the mist mixture that you just created to set the initial gas condition
inside the tank reactor.
tank = GivenPressureBatchReactorFixedTemperature(mist, label="tank")
Set up additional reactor model parameters#
You must provide reactor parameters, solver controls, and output instructions before running the simulations. For a batch reactor, the initial volume and simulation end time are required inputs.
# verify initial gas composition inside the reactor
tank.list_composition(mode="mole")
# set other reactor properties
tank.volume = 10.0 # cm3
tank.time = 0.5 # sec
Set the gas temperature profile of the tank reactor#
Create a time-temperature profile by using two arrays. Use the
set_temperature_profile() method to add the profile to
the reactor model.
# number of profile data points
npoints = 3
# position array of the profile data
x = np.zeros(npoints, dtype=np.double)
# value array of the profile data
tpro_profile = np.zeros_like(x, dtype=np.double)
# set tank temperature data points
x = [0.0, 0.2, 2.0] # [sec]
tpro_profile = [500.0, 275.0, 275.0] # [K]
# set the temperature profile
tank.set_temperature_profile(x, tpro_profile)
Switch on the real-gas EOS model#
Use the use_realgas_cubicEOS() method to turn on the real-gas EOS model. For more
information, type either ansys.chemkin.core.help("real gas") for information
on real-gas model usage or ansys.chemkin.core.help("manuals") to access
the online Chemkin Theory manual for descriptions of the real-gas EOS models.
Note
By default the Van der Waals mixing rule is applied to evaluate
thermodynamic properties of a real-gas mixture. You can use
set_realgas_mixing_rule to switch to a different mixing rule.
tank.userealgas_eos(mode=True)
Set output options#
# set timestep between saving solution
tank.timestep_for_saving_solution = 0.01
Set solver controls#
You can overwrite the default solver controls by using solver related methods,
for example, tolerances.
# set tolerances in tuple: (absolute tolerance, relative tolerance)
tank.tolerances = (1.0e-10, 1.0e-8)
# get solver parameters
atol, rtol = tank.tolerances
print(f"default absolute tolerance = {atol}")
print(f"default relative tolerance = {rtol}")
# turn on the force non-negative solutions option in the solver
tank.force_nonnegative = True
Run the vapor cooling simulation with the real gas EOS#
Run the CONP reactor model with given temperature profile with the real gas EOS model switched ON.
runstatus = tank.run()
# check run status
if runstatus != 0:
# run failed!
print(Color.RED + ">>> Run failed. <<<", end=Color.END)
exit()
# run success!
print(Color.GREEN + ">>> Run completed. <<<", end=Color.END)
Postprocess the solution#
The postprocessing step parses the solution and packages the solution values at each time point into a mixture. There are two ways to access the solution profiles:
The raw solution profiles (value as a function of distance) are available for distance, temperature, pressure, volume, and species mass fractions.
- -The mixtures permit the use of all property and rate utilities to extract
information such as viscosity, density, and mole fractions.
You can use the get_solution_variable_profile() method to get
the raw solution profiles. You can get solution mixtures using either
the get_solution_mixture_at_index() method for the solution mixture at
the given saved location or the get_solution_mixture() method for
the solution mixture at the given distance.
(In this case, the mixture is constructed by interpolation.)
Note
Use the getnumbersolutionpoints() method to get the size of
the solution profiles before creating the arrays.
tank.process_solution()
# get the number of solution time points
solutionpoints = tank.getnumbersolutionpoints()
print(f"number of solution points = {solutionpoints}")
# get the time profile
timeprofile = tank.get_solution_variable_profile("time")
# get the temperature profile
temp_profile = tank.get_solution_variable_profile("temperature")
# get the volume profile
vol_profile = tank.get_solution_variable_profile("volume")
# create array for mixture density
den_profile = np.zeros_like(timeprofile, dtype=np.double)
# create array for mixture enthalpy
h_profile = np.zeros_like(timeprofile, dtype=np.double)
# loop over all solution time points
for i in range(solutionpoints):
# get the mixture at the time point
solutionmixture = tank.get_solution_mixture_at_index(solution_index=i)
# get mixture density profile
den_profile[i] = solutionmixture.rho
# get mixture enthalpy profile
h_profile[i] = solutionmixture.hml() / ck.ERGS_PER_JOULE * 1.0e-3
Turn off the real gas EOS model#
Use the use_realgas_cubicEOS() method to turn off the real gas EOS model.
Alternatively, use the use_idealgas_law() method to turn on the ideal
gas law.
tank.userealgas_eos(mode=False)
Run the vapor cooling simulation with the ideal gas law#
Run the CONP reactor model with given temperature profile with the ideal gas law turned back on.
runstatus = tank.run()
# check run status
if runstatus != 0:
# run failed!
print(Color.RED + ">>> Run failed. <<<", end=Color.END)
exit()
# run success!
print(Color.GREEN + ">>> Run completed. <<<", end=Color.END)
Postprocess the ideal gas solution#
postprocess the solutions
tank.process_solution()
# get the number of solution time points
solutionpoints = tank.getnumbersolutionpoints()
print(f"number of solution points = {solutionpoints}")
# get the time profile
timeprofile_ig = tank.get_solution_variable_profile("time")
# get the volume profile
vol_profile_ig = tank.get_solution_variable_profile("volume")
# create array for mixture density
den_profile_ig = np.zeros_like(timeprofile, dtype=np.double)
# create array for mixture enthalpy
h_profile_ig = np.zeros_like(timeprofile, dtype=np.double)
# loop over all solution time points
for i in range(solutionpoints):
# get the mixture at the time point
solutionmixture = tank.get_solution_mixture_at_index(solution_index=i)
# get mixture density profile
den_profile_ig[i] = solutionmixture.rho
# get mixture enthalpy profile
h_profile_ig[i] = solutionmixture.hml() / ck.ERGS_PER_JOULE * 1.0e-3
ck.done()
Plot the RCM solution profiles#
You should observe that the mixture volume obtained by the real gas model is noticeably lower than the ideal gas volume. When water vapor is cooled below the boiling point, formation of liquid water is expected due to condensation. The ideal gas law assumes that the mixture is always in gas phase and is unable to address the phase change phenomenon. The real gas EOS, on the other hand, can capture the formation of the liquid water as indicated by the sharper rise of the mixture density during the cooling process. The real gas mixture also has a lower enthalpy level than that of the ideal gas mixture. The enthalpy differences should largely represent the heat of vaporization of water at the temperature.
# plot the profiles
plt.subplots(2, 2, sharex="col", figsize=(12, 6))
thispres = str(mist.pressure / ck.P_ATM)
thistitle = "Cooling Vapor + Air at " + thispres + " atm"
plt.suptitle(thistitle, fontsize=16)
plt.subplot(221)
plt.plot(timeprofile, temp_profile, "r-")
plt.ylabel("Temperature [K]")
plt.subplot(222)
plt.plot(timeprofile, vol_profile, "b-", label="real gas")
plt.plot(timeprofile_ig, vol_profile_ig, "b--", label="ideal gas")
plt.legend(loc="upper right")
plt.ylabel("Volume [cm3]")
plt.subplot(223)
plt.plot(timeprofile, h_profile, "g-", label="real gas")
plt.plot(timeprofile_ig, h_profile_ig, "g--", label="ideal gas")
plt.legend(loc="upper right")
plt.xlabel("time [sec]")
plt.ylabel("Mixture Enthalpy [kJ/mole]")
plt.subplot(224)
plt.plot(timeprofile, den_profile, "m-", label="real gas")
plt.plot(timeprofile_ig, den_profile_ig, "m--", label="ideal gas")
plt.legend(loc="upper left")
plt.xlabel("time [sec]")
plt.ylabel("Mixture Density [g/cm3]")
# plot results
if interactive:
plt.show()
else:
plt.savefig("plot_vapor_condensation.png", bbox_inches="tight")