From 0c11dc49da3c7347997884bd29f65b3ecf450449 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Mon, 5 Aug 2019 18:02:30 -0500 Subject: [PATCH 1/2] Update ic_engine example using new cantera capabilities --- .../cantera/examples/reactors/ic_engine.py | 333 +++++++++--------- 1 file changed, 171 insertions(+), 162 deletions(-) diff --git a/interfaces/cython/cantera/examples/reactors/ic_engine.py b/interfaces/cython/cantera/examples/reactors/ic_engine.py index 15a939a22b..bee0e63f14 100644 --- a/interfaces/cython/cantera/examples/reactors/ic_engine.py +++ b/interfaces/cython/cantera/examples/reactors/ic_engine.py @@ -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 @@ # 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 @@ 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) -# 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) - -# set parameters for the automatic time step refinement -n_last_refinement = -np.inf # for initialization only -n_wait_coarsening = 10 - -# 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, 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) - - # 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) +##################################################################### +# Run Simulation +##################################################################### +# set up output data arrays +states = ct.SolutionArray(cyl.thermo, + extra=('t', 'ca', 'V', 'm', + 'mdot_in', 'mdot_out', + 'dWv_dt', 'heat_release_rate')) + +# 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: + + # perform time integration + sim.advance(sim.time + dt) + + # 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) + + # 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)) From a58a2b55959823e50679de6687982796166044fa Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Thu, 8 Aug 2019 07:32:34 -0500 Subject: [PATCH 2/2] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 529981cb86..098561c4be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ doc/ctdeploy_key *~ +*# *.o *.so *.os