Update ic_engine example using new cantera capabilities

This commit is contained in:
Ingmar Schoegl 2019-08-05 18:02:30 -05:00 committed by Ray Speth
parent eea04255fd
commit eff23b6f82

View file

@ -2,27 +2,36 @@
"""
Simulation of a (gaseous) Diesel-type internal combustion engine.
The use of pure propane as fuel requires an unrealistically high compression
ratio.
The simulation uses n-Dodecane as fuel, which is injected close to top dead
center. Note that this example uses numerous simplifying assumptions and
thus serves for illustration purposes only.
"""
import cantera as ct
import numpy as np
#######################################################################
from scipy.integrate import trapz
import matplotlib.pyplot as plt
#########################################################################
# Input Parameters
#######################################################################
#########################################################################
# reaction mechanism, kinetics type and compositions
reaction_mechanism = 'nDodecane_Reitz.xml'
phase_name = 'nDodecane_IG'
comp_air = 'o2:1, n2:3.76'
comp_fuel = 'c12h26:1'
f = 3000. / 60. # engine speed [1/s] (3000 rpm)
V_H = .5e-3 # displaced volume [m**3]
epsilon = 50. # compression ratio [-]
epsilon = 20. # compression ratio [-]
d_piston = 0.083 # piston diameter [m]
# turbocharger temperature, pressure, and composition
T_inlet = 300. # K
p_inlet = 1.3e5 # Pa
comp_inlet = 'O2:1, N2:3.76'
comp_inlet = comp_air
# outlet pressure
p_outlet = 1.2e5 # Pa
@ -30,15 +39,12 @@ p_outlet = 1.2e5 # Pa
# fuel properties (gaseous!)
T_injector = 300. # K
p_injector = 1600e5 # Pa
comp_injector = 'C3H8:1'
comp_injector = comp_fuel
# ambient properties
T_ambient = 300. # K
p_ambient = 1e5 # Pa
comp_ambient = 'O2:1, N2:3.76'
# Reaction mechanism name
reaction_mechanism = 'gri30.xml'
comp_ambient = comp_air
# Inlet valve friction coefficient, open and close timings
inlet_valve_coeff = 1.e-6
@ -54,200 +60,203 @@ outlet_close = 18. / 180. * np.pi
injector_open = 350. / 180. * np.pi
injector_close = 365. / 180. * np.pi
injector_mass = 3.2e-5 # kg
injector_t_open = (injector_close - injector_open) / 2. / np.pi / f
# Simulation time and resolution
sim_n_revolutions = 8.
sim_n_timesteps = 100000.
# Simulation time and parameters
sim_n_revolutions = 8
delta_T_max = 20.
rtol = 1.e-12
atol = 1.e-16
###################################################################
#####################################################################
# Set up IC engine Parameters and Functions
#####################################################################
V_oT = V_H / (epsilon - 1.)
A_piston = .25 * np.pi * d_piston ** 2
stroke = V_H / A_piston
def crank_angle(t):
"""Convert time to crank angle"""
return np.remainder(2 * np.pi * f * t, 4 * np.pi)
def piston_speed(t):
"""Approximate piston speed with sinusoidal velocity profile"""
return - stroke / 2 * 2 * np.pi * f * np.sin(crank_angle(t))
#####################################################################
# Set up Reactor Network
#####################################################################
# load reaction mechanism
gas = ct.Solution(reaction_mechanism)
gas = ct.Solution(reaction_mechanism, phase_name)
# define initial state
# define initial state and set up reactor
gas.TPX = T_inlet, p_inlet, comp_inlet
r = ct.IdealGasReactor(gas)
cyl = ct.IdealGasReactor(gas)
cyl.volume = V_oT
# define inlet state
gas.TPX = T_inlet, p_inlet, comp_inlet
inlet = ct.Reservoir(gas)
# inlet valve
inlet_valve = ct.Valve(inlet, cyl)
inlet_delta = np.mod(inlet_close - inlet_open, 4 * np.pi)
inlet_valve.valve_coeff = inlet_valve_coeff
inlet_valve.set_time_function(
lambda t: np.mod(crank_angle(t) - inlet_open, 4 * np.pi) < inlet_delta)
# define injector state (gaseous!)
gas.TPX = T_injector, p_injector, comp_injector
injector = ct.Reservoir(gas)
# injector is modeled as a mass flow controller
injector_mfc = ct.MassFlowController(injector, cyl)
injector_delta = np.mod(injector_close - injector_open, 4 * np.pi)
injector_t_open = (injector_close - injector_open) / 2. / np.pi / f
injector_mfc.mass_flow_coeff = injector_mass / injector_t_open
injector_mfc.set_time_function(
lambda t: np.mod(crank_angle(t) - injector_open, 4 * np.pi) < injector_delta)
# define outlet pressure (temperature and composition don't matter)
gas.TPX = T_ambient, p_outlet, comp_ambient
outlet = ct.Reservoir(gas)
# outlet valve
outlet_valve = ct.Valve(cyl, outlet)
outlet_delta = np.mod(outlet_close - outlet_open, 4 * np.pi)
outlet_valve.valve_coeff = outlet_valve_coeff
outlet_valve.set_time_function(
lambda t: np.mod(crank_angle(t) - outlet_open, 4 * np.pi) < outlet_delta)
# define ambient pressure (temperature and composition don't matter)
gas.TPX = T_ambient, p_ambient, comp_ambient
ambient_air = ct.Reservoir(gas)
# set up connecting devices
inlet_valve = ct.Valve(inlet, r)
injector_mfc = ct.MassFlowController(injector, r)
outlet_valve = ct.Valve(r, outlet)
piston = ct.Wall(ambient_air, r)
# convert time to crank angle
def crank_angle(t):
return np.remainder(2 * np.pi * f * t, 4 * np.pi)
# set up IC engine parameters
V_oT = V_H / (epsilon - 1.)
A_piston = .25 * np.pi * d_piston ** 2
stroke = V_H / A_piston
r.volume = V_oT
# piston is modeled as a moving wall
piston = ct.Wall(ambient_air, cyl)
piston.area = A_piston
def piston_speed(t):
return - stroke / 2 * 2 * np.pi * f * np.sin(crank_angle(t))
piston.set_velocity(piston_speed)
# create a reactor network containing the cylinder
sim = ct.ReactorNet([r])
# create a reactor network containing the cylinder and limit advance step
sim = ct.ReactorNet([cyl])
sim.rtol, sim.atol = rtol, atol
cyl.set_advance_limit('temperature', delta_T_max)
#####################################################################
# Run Simulation
#####################################################################
# set up output data arrays
states = ct.SolutionArray(r.thermo)
t_sim = sim_n_revolutions / f
t = (np.arange(sim_n_timesteps) + 1) / sim_n_timesteps * t_sim
V = np.zeros_like(t)
m = np.zeros_like(t)
test = np.zeros_like(t)
mdot_in = np.zeros_like(t)
mdot_out = np.zeros_like(t)
d_W_v_d_t = np.zeros_like(t)
heat_release_rate = np.zeros_like(t)
states = ct.SolutionArray(cyl.thermo,
extra=('t', 'ca', 'V', 'm',
'mdot_in', 'mdot_out',
'dWv_dt', 'heat_release_rate'))
# set parameters for the automatic time step refinement
n_last_refinement = -np.inf # for initialization only
n_wait_coarsening = 10
# simulate with a maximum resolution of 1 deg crank angle
dt = 1. / (360 * f)
t_stop = sim_n_revolutions / f
while sim.time < t_stop:
# do simulation
for n1, t_i in enumerate(t):
# define opening and closing of valves and injector
if (np.mod(crank_angle(t_i) - inlet_open, 4 * np.pi) <
np.mod(inlet_close - inlet_open, 4 * np.pi)):
inlet_valve.valve_coeff = inlet_valve_coeff
test[n1] = 1
else:
inlet_valve.valve_coeff = 0.
if (np.mod(crank_angle(t_i) - outlet_open, 4 * np.pi) <
np.mod(outlet_close - outlet_open, 4 * np.pi)):
outlet_valve.valve_coeff = outlet_valve_coeff
else:
outlet_valve.valve_coeff = 0.
if (np.mod(crank_angle(t_i) - injector_open, 4 * np.pi) <
np.mod(injector_close - injector_open, 4 * np.pi)):
injector_mfc.set_mass_flow_rate(injector_mass / injector_t_open)
else:
injector_mfc.set_mass_flow_rate(0)
# perform time integration
sim.advance(sim.time + dt)
# perform time integration, refine time step if necessary
solved = False
for n2 in range(4):
try:
sim.advance(t_i)
solved = True
break
except ct.CanteraError:
sim.set_max_time_step(1e-6 * 10. ** -n2)
n_last_refinement = n1
if not solved:
raise ct.CanteraError('Refinement limit reached')
# coarsen time step if too long ago
if n1 - n_last_refinement == n_wait_coarsening:
sim.set_max_time_step(1e-5)
# calculate results to be stored
dWv_dt = - (cyl.thermo.P - ambient_air.thermo.P) * A_piston * \
piston_speed(sim.time)
heat_release_rate = - cyl.volume * ct.gas_constant * cyl.T * \
np.sum(gas.standard_enthalpies_RT * cyl.thermo.net_production_rates, 0)
# write output data
states.append(r.thermo.state)
V[n1] = r.volume
m[n1] = r.mass
mdot_in[n1] = inlet_valve.mdot(0)
mdot_out[n1] = outlet_valve.mdot(0)
d_W_v_d_t[n1] = - (r.thermo.P - ambient_air.thermo.P) * A_piston * \
piston_speed(t_i)
heat_release_rate[n1] = - r.volume * ct.gas_constant * r.T * \
np.sum(gas.standard_enthalpies_RT * r.thermo.net_production_rates, 0)
# append output data
states.append(cyl.thermo.state,
t=sim.time, ca=crank_angle(sim.time),
V=cyl.volume, m=cyl.mass,
mdot_in=inlet_valve.mdot(sim.time),
mdot_out=outlet_valve.mdot(sim.time),
dWv_dt=dWv_dt,
heat_release_rate=heat_release_rate)
#####################################################################
#######################################################################
# Plot Results in matplotlib
#####################################################################
#######################################################################
import matplotlib.pyplot as plt
def ca_ticks(t):
"""Helper function converts time to rounded crank angle."""
return np.round(crank_angle(t) * 180 / np.pi, decimals=1)
t = states.t
# pressure and temperature
plt.clf()
plt.subplot(211)
plt.plot(t, states.P / 1.e5)
plt.ylabel('$p$ [bar]')
plt.xlabel('$\phi$ [deg]')
plt.xticks(plt.xticks()[0], [])
plt.subplot(212)
plt.plot(t, states.T)
plt.ylabel('$T$ [K]')
plt.xlabel('$\phi$ [deg]')
plt.xticks(plt.xticks()[0], crank_angle(plt.xticks()[0]) * 180 / np.pi,
rotation=17)
fig, ax = plt.subplots(nrows=2)
ax[0].plot(t, states.P / 1.e5)
ax[0].set_ylabel('$p$ [bar]')
ax[0].set_xlabel('$\phi$ [deg]')
ax[0].set_xticklabels([])
ax[1].plot(t, states.T)
ax[1].set_ylabel('$T$ [K]')
ax[1].set_xlabel('$\phi$ [deg]')
ax[1].set_xticklabels(ca_ticks(ax[1].get_xticks()))
plt.show()
plt.savefig('ic_engine_t_p_T.png')
# p-V diagram
plt.clf()
plt.plot(V[t > 0.04] * 1000, states.P[t > 0.04] / 1.e5)
plt.xlabel('$V$ [l]')
plt.ylabel('$p$ [bar]')
fig, ax = plt.subplots()
ax.plot(states.V[t > 0.04] * 1000, states.P[t > 0.04] / 1.e5)
ax.set_xlabel('$V$ [l]')
ax.set_ylabel('$p$ [bar]')
plt.show()
plt.savefig('ic_engine_p_V.png')
# T-S diagram
plt.clf()
plt.plot(m[t > 0.04] * states.s[t > 0.04], states.T[t > 0.04])
plt.xlabel('$S$ [J/K]')
plt.ylabel('$T$ [K]')
fig, ax = plt.subplots()
ax.plot(states.m[t > 0.04] * states.s[t > 0.04], states.T[t > 0.04])
ax.set_xlabel('$S$ [J/K]')
ax.set_ylabel('$T$ [K]')
plt.show()
plt.savefig('ic_engine_T_S.png')
# heat of reaction and expansion work
plt.clf()
plt.plot(t, heat_release_rate, label='$\dot{Q}$')
plt.plot(t, d_W_v_d_t, label='$\dot{W}_v$')
plt.ylim(-1e5, 1e6)
plt.legend(loc=0)
plt.ylabel('[W]')
plt.xlabel('$\phi$ [deg]')
plt.xticks(plt.xticks()[0], crank_angle(plt.xticks()[0]) * 180 / np.pi,
rotation=17)
fig, ax = plt.subplots()
ax.plot(t, 1.e-3 * states.heat_release_rate, label='$\dot{Q}$')
ax.plot(t, 1.e-3 * states.dWv_dt, label='$\dot{W}_v$')
ax.set_ylim(-1e2, 1e3)
ax.legend(loc=0)
ax.set_ylabel('[kW]')
ax.set_xlabel('$\phi$ [deg]')
ax.set_xticklabels(ca_ticks(ax.get_xticks()))
plt.show()
plt.savefig('ic_engine_Q_W.png')
# gas composition
plt.clf()
plt.plot(t, states('O2').X, label='O2')
plt.plot(t, states('CO2').X, label='CO2')
plt.plot(t, states('CO').X, label='CO')
plt.plot(t, states('C3H8').X * 10, label='C3H8 x10')
plt.legend(loc=0)
plt.ylabel('$X_i$ [-]')
plt.xlabel('$\phi$ [deg]')
plt.xticks(plt.xticks()[0], crank_angle(plt.xticks()[0]) * 180 / np.pi,
rotation=17)
fig, ax = plt.subplots()
ax.plot(t, states('o2').X, label='O2')
ax.plot(t, states('co2').X, label='CO2')
ax.plot(t, states('co').X, label='CO')
ax.plot(t, states('c12h26').X * 10, label='n-Dodecane x10')
ax.legend(loc=0)
ax.set_ylabel('$X_i$ [-]')
ax.set_xlabel('$\phi$ [deg]')
ax.set_xticklabels(ca_ticks(ax.get_xticks()))
plt.show()
plt.savefig('ic_engine_t_X.png')
#####################################################################
######################################################################
# Integral Results
#####################################################################
######################################################################
from scipy.integrate import trapz
Q = trapz(heat_release_rate, t)
W = trapz(d_W_v_d_t, t)
# heat release
Q = trapz(states.heat_release_rate, t)
print('{:45s}{:>4.1f} kW'.format('Heat release rate per cylinder (estimate):',
Q / t[-1] / 1000.))
# expansion power
W = trapz(states.dWv_dt, t)
print('{:45s}{:>4.1f} kW'.format('Expansion power per cylinder (estimate):',
W / t[-1] / 1000.))
# efficiency
eta = W / Q
print('{:45s}{:>4.1f} %'.format('Efficiency (estimate):',
eta * 100.))
# CO emissions
MW = states.mean_molecular_weight
CO_emission = trapz(MW * mdot_out * states('CO').X[:,0], t) / trapz(MW * mdot_out, t)
print('Heat release rate per cylinder (estimate):\t' +
format(Q / t_sim / 1000., ' 2.1f') + ' kW')
print('Expansion power per cylinder (estimate):\t' +
format(W / t_sim / 1000., ' 2.1f') + ' kW')
print('Efficiency (estimate):\t\t\t' + format(eta * 100., ' 2.1f') + ' %')
print('CO emission (estimate):\t\t' + format(CO_emission * 1.e6, ' 2.1f') +
' ppm')
CO_emission = trapz(MW * states.mdot_out * states('CO').X[:, 0], t)
CO_emission /= trapz(MW * states.mdot_out, t)
print('{:45s}{:>4.1f} ppm'.format('CO emission (estimate):',
CO_emission * 1.e6))