Here are the operating conditions:T0 = 387 K, P0 = 1.47e5 Pa, X = 'CH4:0.0476,H2:0.0290,O2:0.1096,N2:0.8138'
Here is the original code in Matlab:
----------------------------------------------------------------------------------------------------------------------------------
clear; close all; clc;
% set up the variables array
N = 3600;
b = 86/1000;
s = 75/1000;
l = 118/1000;
a = s/2;
Spm = 4.5;
Twall = 400;
ep = 0.65;
sig = 5.67e-8;
var = [N,a,l,b,Spm,Twall,ep,sig];
T = 387;
p = 1.47e5;
% set up the gas object with Cantera
gas = IdealGasMix('gri30.xml');
% set(gas,'T',T,'P',p,'X','H2:1,O2:0.5,N2:1.88')
set(gas,'T',T,'P',p,'X','CH4:0.0476,H2:0.0290,O2:0.1096,N2:0.8138');
% set up the initial conditions array
th = 4.154;
x = 0.0938;
As = 2.827e-2;
V = 3.802e-4;
rho = density(gas);
Vel = 0;
u = intEnergy_mass(gas);
T = temperature(gas);
Mmix = meanMolecularWeight(gas);
R = gasconstant/Mmix;
p = pressure(gas);
Y0 = massFractions(gas);
X0 = moleFractions(gas);
IC = [th,x,As,V,rho,Vel,u,T,Mmix,R,p,Y0',X0'];
% dt = 1.85185e-5;
% tend = 0.02463;
dt = 1.1574e-5;
tend = 0.012315;
options = odeset('RelTol',1e-5,... % relative tolerance
'AbsTol',1e-8,... % absolute tolerance
'MaxStep',dt,... % maximum time step
'Stats','On',... % displays the stats from the iteration
'Refine',1); % produces smoother output
[t,Z] = ode15s(@rxnode,[0 tend],IC,options,gas,var);
for i = 1:11
figure
plot(t,Z(:,i),'linewidth',2)
switch i
case 1
str = 'Crank angle [rad]';
case 2
str = 'Piston position [m]';
case 3
str = 'Surface area [m^2]';
case 4
str = 'Volume [m^3]';
case 5
str = 'Density [kg/m^3]';
case 6
str = 'Velocity [m/s]';
case 7
str = 'Internal energy [J/kg]';
case 8
str = 'Temperature [K]';
case 9
str = 'Mixture weight [gm/mol]';
case 10
str = 'Gas constant [J/kg.K]';
case 11
str = 'Pressure [bar]';
plot(t,Z(:,i)/1e5,'linewidth',2)
end
ylabel(str)
xlabel('Time [s]')
box on, grid on, grid minor
end
figure
hold on
for i = [1,4,6,14]
plot(t,Z(:,11+i),'linewidth',2)
end
hold off
xlabel('Time [s]')
ylabel('Species mass fractions [-]')
legend('H_2','O_2','H_2O','CH_4','location','best')
box on, grid on, grid minor
----------------------------------------------------------------------------------------------------------------------------------
Here is the function file that handles the ODEs:
function out = rxnode(t,y,gas,vars)
NM = nSpecies(gas); % number of species in the gas mixture
Wk = molecularWeights(gas); % species molecular weights [gm/mol or kg/kmol]
% unpack the initial conditions array
q = y(1); % crank angles [rad]
x = y(2); % piston velocity [m]
As = y(3); % surface area [m^2]
V = y(4); % volume [m^3]
rho = y(5); % gas mixture density [kg/m^3]
Vel = y(6); % gas flow velocity [m/s]
u = y(7); % internal energy [J/kg]
T = y(8); % gas temperature [K]
Mmix = y(9); % gas mixture mean molecular weight [gm/mol or kg/kmol]
R = y(10); % specific gas constant [J/kg.K]
p = y(11); % gas pressure [Pa]
Y0 = y(12:12+NM-1); % species mass fractions [-]
X0 = y(12+NM:end); % species mole fractions [-]
% unpack the variable array
N = vars(1); % engine speed [rpm]
a = vars(2); % crank pin radius [m]
l = vars(3); % connecting rod length [m]
b = vars(4); % bore [m]
Spm = vars(5); % mean piston speed [m/s]
Twall = var(6); % engine wall temperature [K]
ep = var(7); % emissivity [-]
sig = var(8); % Stefan-Boltzmann constant [W/m^2.K^4]
set(gas,'T',T,'P',p,'Y',Y0);
% final crank angle
dqdt = 2*pi*N/60;
% final piston position
dxdt = (-a*sin(q)- a^2*sqrt(2)*cos(q)*sin(q)/sqrt(2*l^2-2*a^2*sin(q)^2))*dqdt;
% final surface area
dAsdt = -pi*b*dxdt;
% final volume
dVdt = -pi*b^2/4*dxdt;
% final conservation of mass
drdt = -rho/V*dVdt;
% final conservation of momentum
dVeldt = 0;
% final conservation of species
wdot = netProdRates(gas); % net production or destruction rates [kmol/m^3.s]
dYdt = wdot.*Wk/rho;
% final conservation of energy
hc = 3.26*(V^-0.06)*((p/1000)^0.8)*(T^-0.4)*(Spm+1.40)^0.8; % Hohenberg convective heat transfer coefficient [W/m^2.K]
dudt = -p/V/rho*dVdt - hc*As/V/rho*(T-Twall) - ep*sig*As/rho/V*(T^4-Twall^4);
% dudt = 0;
% % final temperature
% dTdt = dudt/cv_mass(gas);
dTdt = (-temperature(gas)*gasconstant*(enthalpies_RT(gas)-ones(NM,1))'*wdot/density(gas) ...
-p/density(gas)/V*dVdt - hc*As/density(gas)/V*(T-Twall) - ep*sig*As/density(gas)/V*(T^4-Twall^4))/cv_mass(gas);
disp(dTdt)
% final mole fractions
dXdt = zeros(NM,1);
for j = 1:NM
sum1 = 0;
sum2 = 0;
for i = 1:NM
sum1 = sum1 + Y0(i)/Wk(i);
sum2 = sum2 + dYdt(i)/Wk(i);
end
dXdt(j) = (1/Wk(j)*dYdt(j)*sum1 - Y0(j)/Wk(j)*sum2)/sum1^2;
end
% final molecular weight
dMmixdt = 0;
for i = 1:NM
dMmixdt = dMmixdt + Wk(i)*dXdt(i);
end
% final gas constant
dRdt = -gasconstant/Mmix^2*dMmixdt;
% final ideal gas law
dpdt = rho*R*dTdt + R*T*drdt + rho*T*dRdt;
out = [dqdt;dxdt;dAsdt;dVdt;drdt;dVeldt;dudt;dTdt;dMmixdt;dRdt;dpdt;dYdt;dXdt];
----------------------------------------------------------------------------------------------------------------------------------
My Python code looks like this:
----------------------------------------------------------------------------------------------------------------------------------
import cantera as ct
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import numpy as np
from math import *
# define the function for the ODEs
def rxnode(y,t,gas,var):
# extract important information from the gas object
Wk = gas.molecular_weights
NM = gas.n_total_species
N = var[0] # engine speed [rpm]
a = var[1] # crank pin radius [m]
l = var[2] # connecting rod length [m]
b = var[3] # bore [m]
Spm = var[4] # mean piston speed [m/s]
Twall = var[5] # engine wall temperature [K]
ep = var[6] # emissivity [-]
sig = var[7] # Stefan-Boltzmann constant [W/m^2.K^4]
# q,x,As,V,rho,Vel,u,T,Mmix,R,p,Y0,X0 = y
q = y[0] # crank angles [rad]
x = y[1] # piston velocity [m/s]
As = y[2] # surface area [m^2]
V = y[3] # volume [m^3]
rho = y[4] # gas mixture density [kg/m^3]
Vel = y[5] # gas flow velocity [m/s]
u = y[6] # gas (specific) internal energy [J/kg]
T = y[7] # gas temperature [K]
Mmix = y[8] # gas mixture mean molecular weight [gm/mol or kg/kmol]
R = y[9] # specific gas constant [J/kg.K]
p = y[10] # gas pressure [Pa]
Y0 = y[11:11+NM] # species mass fractions [-]
X0 = y[11+NM:] # species mole fractions [-]
# set gas properties
gas.TPX = T, p, X0
## Start writing ODEs below:
# final crank angle
dqdt = 2*pi*N/60
# final piston position
dxdt = (-a*sin(q)- a**2*sqrt(2)*cos(q)*sin(q)/sqrt(2*l**2-2*a**2*sin(q)**2))*dqdt
# final surface area
dAsdt = -pi*b*dxdt
# final volume
dVdt = -pi*b**2/4*dxdt
# final conservation of mass
drdt = -rho/V*dVdt
# final conservation of momentum
dVeldt = 0
# final conservation of species
wdot = gas.net_production_rates
dYdt = wdot*Wk/rho
# final conservation of energy
# internal energy basis
hc = 3.26*(V**(-0.06))*((p/1000)**0.8)*(T**(-0.4))*(Spm+1.40)**(0.8)
dQdt = -p/V/rho*dVdt - hc*As/V/rho*(T-Twall) - ep*sig*As/V/rho*(T**4-Twall**4)
dudt = -p/V/rho*dVdt - hc*As/V/rho*(T-Twall) - ep*sig*As/V/rho*(T**4-Twall**4)
# temperature basis
h = gas.standard_enthalpies_RT
cv = gas.cv_mass
dTdt = 0
for i in range(NM):
dTdt = dTdt - T*ct.gas_constant*(h[i]-1)*wdot[i]/rho
dTdt = (dTdt + dQdt)/cv
# final mole fractions
dXdt = np.zeros((NM))
for j in range(NM):
sum1 = np.dot(Y0,1/Wk)
sum2 = np.dot(dYdt,1/Wk)
dXdt[j] = (1/Wk[j]*dYdt[j]*sum1 - Y0[j]/Wk[j]*sum2)/sum1**2
# final molecular weight
dMmixdt = np.dot(Wk,dXdt)
# final gas constant
Mmix = gas.mean_molecular_weight
dRdt = -ct.gas_constant/Mmix**2*dMmixdt
# final Ideal Gas Law
dpdt = rho*R*dTdt + R*T*drdt + rho*T*dRdt
return np.hstack((dqdt,dxdt,dAsdt,dVdt,drdt,dVeldt,dudt,dTdt,dMmixdt,dRdt,dpdt,dYdt,dXdt))
# write the driver code below
N = 1800 # engine speed [rpm]
b = 86/1000 # engine bore [m]
s = 75/1000 # engine stroke [m]
l = 118/1000 # engine connecting rod length [m]
a = s/2 # engine crank pin radius [m]
Spm = 4.5 # mean piston speed [m/s]
Twall = 400 # engine wall temperature [K]
ep = 0.65 # emissivity of the engine [-]
sig = 5.67e-8 # Stefan-Boltzmann constant [W/m^2.K^4]
var = N, a, l, b, Spm, Twall, ep, sig # package the var array
T = 600 # initial temperature [K]
p = 1e5 # initial pressure [Pa]
gas = ct.Solution('gri30.xml')
gas.TPX = T, p, 'CH4:0.0476,H2:0.0290,O2:0.1096,N2:0.8138'
# setup the initial conditions array
th = 4.154 # initial crank angle [rad]
x = 0.0938 # initial piston position [m]
As = 2.827e-2 # initial combustion surface area [m^2]
V = 3.802e-4 # initial volume [m^3]
rho = gas.density # initial density [kg/m^3]
Vel = 0 # initial gas flow velocity [m/s]
u = gas.int_energy_mass # initial internal energy [J/kg]
T = gas.T # initial gas temperature [K]
Mmix = gas.mean_molecular_weight # initial gas mean molecular weight [gm/mol or kg/kmol]
R = ct.gas_constant/Mmix # initial gas constant [J/kg.K]
p = gas.P # initial pressure [Pa]
Y0 = gas.Y # initial mass fractions [-]
X0 = gas.X # initial mole fractions [-]
y0 = np.hstack((th, x, As, V, rho, Vel, u, T, Mmix, R, p, Y0, X0))
dt = 1.85185e-5 # time step, corresponds to 1 reading per 0.2 deg of crank angle at 1800 RPM
tend = 0.0243 # end time for simulation [s]
t = np.arange(0,tend,dt)
y = odeint(rxnode,y0,t,args=(gas,var,),full_output=1,rtol=1e-5,atol=1e-8,hmax=dt)
----------------------------------------------------------------------------------------------------------------------------------
I get the following output during the simulation: "ODEintWarning: Excess work done on this call (perhaps wrong Dfun type). Run with full_output = 1 to get quantitative information. warnings.warn(warning_msg, ODEintWarning)"
Since I am new to Python, I am not sure what this means. Perhaps, it's related to the scaling of the step size or something, but I would appreciate your inputs in this regard.
Thank you.