8000 Implement carbonmonoxide in combustion components by fwitte · Pull Request #674 · oemof/tespy · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Implement carbonmonoxide in combustion components #674

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/whats_new/v0-8-2.rst
10000
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@ API changes
:ref:`in the docs <tespy_subsystems_label>`
(`PR #652 <https://github.com/oemof/tespy/pull/652>`__).

New Features
############
- The combustion based component classes :code:`CombustionChamber`,
:code:`DiabaticCombustionChamber` and :code:`CombustionEngine` can now handle
carbonmonoxide as fuel
(`PR #674 <https://github.com/oemof/tespy/pull/674>`__).

Other Changes
#############
- The partial derivatives for specified temperature are only calculated towards
enthalpy and pressure, not towards the fluid compostion. The reason for this
is, that it is not intended, that the composition of a fluid can be
determined by specifying temperature. Removing this saves a lot of
computational overhead for mixtures
(`PR #674 <https://github.com/oemof/tespy/pull/674>`__).

Contributors
############
- Francesco Witte (`@fwitte <https://github.com/fwitte>`__)
32 changes: 17 additions & 15 deletions src/tespy/components/combustion/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class CombustionChamber(Component):

Available fuels

- methane, ethane, propane, butane, hydrogen
- methane, ethane, propane, butane, hydrogen, carbon monoxide, nDodecane

Inlets/Outlets

Expand Down Expand Up @@ -334,21 +334,22 @@ def calc_lhv(self, f):
hf['propane'] = -103.8
hf['butane'] = -125.7
hf['nDodecane'] = -289.4
hf['CO'] = -110.5
hf[self.o2] = 0
hf[self.co2] = -393.51
# water (gaseous)
hf[self.h2o] = -241.826

key = set(list(hf.keys())).intersection(
set([a.replace(' ', '')
for a in CP.get_aliases(f)]))
set([a.replace(' ', '') for a in CP.get_aliases(f)])
)

val = (
-(
self.fuels[f]['H'] / 2 * hf[self.h2o]
+ self.fuels[f]['C'] * hf[self.co2]
- (
(self.fuels[f]['C'] + self.fuels[f]['H'] / 4) * hf[self.o2]
(self.fuels[f]['C'] + self.fuels[f]['H'] / 4 - self.fuels[f]['O'] / 2) * hf[self.o2]
+ hf[list(key)[0]]
)
) / inl[0].fluid.wrapper[f]._molar_mass * 1000
Expand Down Expand Up @@ -601,17 +602,20 @@ def stoichiometry(self, fluid):
n_oxy_stoich = {}
n_h = 0
n_c = 0
n_o = 0
for f in self.fuel_list:
n_fuel[f] = 0
for i in inl:
n = i.m.val_SI * i.fluid.val[f] / inl[0].fluid.wrapper[f]._molar_mass
n_fuel[f] += n
n_h += n * self.fuels[f]['H']
n_c += n * self.fuels[f]['C']
n_o += n * self.fuels[f]['O']

# stoichiometric oxygen requirement for each fuel
n_oxy_stoich[f] = n_fuel[f] * (
self.fuels[f]['H'] / 4 + self.fuels[f]['C']
- self.fuels[f]['O'] / 2
)

n_oxygen = 0
Expand All @@ -622,7 +626,7 @@ def stoichiometry(self, fluid):

###################################################################
# calculate stoichiometric oxygen
n_oxygen_stoich = n_h / 4 + n_c
n_oxygen_stoich = n_h / 4 + n_c - n_o / 2

###################################################################
# calculate lambda if not set
Expand Down Expand Up @@ -662,7 +666,7 @@ def stoichiometry(self, fluid):
if self.lamb.val < 1:
n_fuel_exc = (
-(n_oxygen / n_oxygen_stoich - 1) * n_oxy_stoich[fluid]
/ (self.fuels[fluid]['H'] / 4 + self.fuels[fluid]['C'])
/ (self.fuels[fluid]['H'] / 4 + self.fuels[fluid]['C'] - self.fuels[fluid]['O'] / 2)
)
else:
n_fuel_exc = 0
Expand Down Expand Up @@ -994,6 +998,7 @@ def calc_lambda(self):

n_h = 0
n_c = 0
n_o = 0
for f in self.fuel_list:
for i in inl:
n_fuel = (
Expand All @@ -1002,14 +1007,15 @@ def calc_lambda(self):
)
n_h += n_fuel * self.fuels[f]['H']
n_c += n_fuel * self.fuels[f]['C']
n_o += n_fuel * self.fuels[f]['O']

n_oxygen = 0
for i in inl:
n_oxygen += (
i.m.val_SI * i.fluid.val[self.o2]
/ inl[0].fluid.wrapper[self.o2]._molar_mass
)
n_oxygen_stoich = n_h / 4 + n_c
n_oxygen_stoich = n_h / 4 + n_c - n_o / 2
return n_oxygen / n_oxygen_stoich


Expand Down Expand Up @@ -1218,16 +1224,12 @@ def convergence_check(self):
outl.fluid.val[f] = 0.05

elif f == self.co2:
if outl.fluid.val[f] > 0.1:
outl.fluid.val[f] = 0.075
if outl.fluid.val[f] < 0.001:
outl.fluid.val[f] = 0.02
if outl.fluid.val[f] > 0.15:
outl.fluid.val[f] = 0.1

elif f == self.h2o:
if outl.fluid.val[f] > 0.1:
outl.fluid.val[f] = 0.075
if outl.fluid.val[f] < 0.001:
EDB7 outl.fluid.val[f] = 0.02
if outl.fluid.val[f] > 0.15:
outl.fluid.val[f] = 0.1

elif f in self.fuel_list:
if outl.fluid.val[f] > 0:
Expand Down
2 changes: 1 addition & 1 deletion src/tespy/components/combustion/diabatic.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class DiabaticCombustionChamber(CombustionChamber):

Available fuels

- methane, ethane, propane, butane, hydrogen
- methane, ethane, propane, butane, hydrogen, carbon monoxide, nDodecane

Inlets/Outlets

Expand Down
2 changes: 1 addition & 1 deletion src/tespy/components/combustion/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class CombustionEngine(CombustionChamber):

Available fuels

- methane, ethane, propane, butane, hydrogen
- methane, ethane, propane, butane, hydrogen, carbon monoxide, nDodecane

Inlets/Outlets

Expand Down
8 changes: 6 additions & 2 deletions src/tespy/connections/bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,14 @@ class Bus:
25819387.0
>>> round(chp.Q1.val + chp.Q2.val, 0)
-8899014.0
>>> round(comb_fgc.T.val, 5)
443.33078
>>> round(fgc_fg.T.val, 5)
120.0
>>> round(fgc_cw.m.val_SI * (fgc_cw.h.val_SI - pu_sp.h.val_SI), 0)
12477091.0
12477089.0
>>> round(heat_bus.P.val, 0)
12477091.0
12477089.0
>>> round(pu.calc_bus_efficiency(power_bus), 2)
0.98
>>> power_bus.set_attr(P=-7.5e6)
Expand Down
13 changes: 1 addition & 12 deletions src/tespy/connections/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@

import numpy as np

from tespy.components.component import Component
from tespy.components import Subsystem
from tespy.components.component import Component
from tespy.tools import fluid_properties as fp
from tespy.tools import logger
from tespy.tools.data_containers import DataContainer as dc
from tespy.tools.data_containers import FluidComposition as dc_flu
from tespy.tools.data_containers import FluidProperties as dc_prop
from tespy.tools.data_containers import ReferencedFluidProperties as dc_ref
Expand All @@ -36,7 +35,6 @@
from tespy.tools.fluid_properties import s_mix_ph
from tespy.tools.fluid_properties import v_mix_ph
from tespy.tools.fluid_properties import viscosity_mix_ph
from tespy.tools.fluid_properties.functions import dT_mix_ph_dfluid
from tespy.tools.fluid_properties.functions import p_sat_T
from tespy.tools.fluid_properties.helpers import get_mixture_temperature_range
from tespy.tools.fluid_properties.helpers import get_number_of_fluids
Expand Down Expand Up @@ -765,10 +763,6 @@ def T_deriv(self, k, **kwargs):
self.jacobian[k, self.h.J_col] = (
dT_mix_pdh(self.p.val_SI, self.h.val_SI, self.fluid_data, self.mixing_rule, self.T.val_SI)
)
for fluid in self.fluid.is_var:
self.jacobian[k, self.fluid.J_col[fluid]] = dT_mix_ph_dfluid(
self.p.val_SI, self.h.val_SI, fluid, self.fluid_data, self.mixing_rule
)

def T_ref_func(self, k, **kwargs):
ref = self.T_ref.ref
Expand All @@ -788,11 +782,6 @@ def T_ref_deriv(self, k, **kwargs):
self.jacobian[k, ref.obj.h.J_col] = -(
dT_mix_pdh(ref.obj.p.val_SI, ref.obj.h.val_SI, ref.obj.fluid_data, ref.obj.mixing_rule)
) * ref.factor
for fluid in ref.obj.fluid.is_var:
if not self._increment_filter[ref.obj.fluid.J_col[fluid]]:
self.jacobian[k, ref.obj.fluid.J_col[fluid]] = -dT_mix_ph_dfluid(
ref.obj.p.val_SI, ref.obj.h.val_SI, fluid, ref.obj.fluid_data, ref.obj.mixing_rule
)

def calc_viscosity(self, T0=None):
try:
Expand Down
5 changes: 4 additions & 1 deletion src/tespy/tools/global_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,7 @@

}

combustion_gases = ['methane', 'ethane', 'propane', 'butane', 'hydrogen', 'nDodecane']
combustion_gases = [
'methane', 'ethane', 'propane', 'butane', 'hydrogen', 'nDodecane',
'CO'
]
56 changes: 44 additions & 12 deletions tests/test_components/test_combustion.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ def setup_method(self):

def setup_CombustionChamber_network(self, instance):

self.c1 = Connection(self.air, 'out1', instance, 'in1')
self.c2 = Connection(self.fuel, 'out1', instance, 'in2')
self.c3 = Connection(instance, 'out1', self.fg, 'in1')
self.c1 = Connection(self.air, 'out1', instance, 'in1', label="air")
self.c2 = Connection(self.fuel, 'out1', instance, 'in2', label="fuel")
self.c3 = Connection(instance, 'out1', self.fg, 'in1', label="fluegas")
self.nw.add_conns(self.c1, self.c2, self.c3)

def setup_CombustionEngine_network(self, instance):
Expand Down Expand Up @@ -79,29 +79,61 @@ def test_CombustionChamber(self):
instance.set_attr(lamb=None)
self.nw.solve('design')
self.nw._convergence_check()
msg = ('Value of thermal input must be ' + str(b.P.val) + ', is ' +
str(instance.ti.val) + '.')
msg = f'Value of thermal input must be {b.P.val}, is {instance.ti.val}.'
assert round(b.P.val, 1) == round(instance.ti.val, 1), msg
b.set_attr(P=None)

# test specified thermal input for CombustionChamber
instance.set_attr(ti=1e6)
self.nw.solve('design')
self.nw._convergence_check()
ti = (self.c2.m.val_SI * self.c2.fluid.val['CH4'] *
instance.fuels['CH4']['LHV'])
msg = ('Value of thermal input must be ' + str(instance.ti.val) +
', is ' + str(ti) + '.')
ti = (
self.c2.m.val_SI * self.c2.fluid.val['CH4']
* instance.fuels['CH4']['LHV']
)
msg = f'Value of thermal input must be {instance.ti.val}, is {ti}.'
assert round(ti, 1) == round(instance.ti.val, 1), msg

# test specified lamb for CombustionChamber
self.c3.set_attr(T=None)
instance.set_attr(lamb=1)
self.nw.solve('design')
self.nw._convergence_check()
msg = ('Value of oxygen in flue gas must be 0.0, is ' +
str(round(self.c3.fluid.val['O2'], 4)) + '.')
assert 0.0 == round(self.c3.fluid.val['O2'], 4), msg
o2 = round(self.c3.fluid.val['O2'], 4)
msg = f'Value of oxygen in flue gas must be 0.0, is {o2}.'
assert 0.0 == o2, msg

def test_CombustionChamberCarbonMonoxide(self):
instance = CombustionChamber('combustion chamber')
self.setup_CombustionChamber_network(instance)

# connection parameter specification
air = {'N2': 0.7556, 'O2': 0.2315, 'Ar': 0.0129}
fuel = {'CO': 1}
self.c1.set_attr(fluid=air, p=1, T=30)
self.c2.set_attr(fluid=fuel, T=30, m=1)
instance.set_attr(lamb=3)

self.nw.solve('design')
self.nw._convergence_check()
assert instance.fuels["CO"]["LHV"] == pytest.approx(10112000, 1e-3)

molar_flow = {}
for c in self.nw.conns["object"]:
molar_flow[c.label] = {
key: value["mass_fraction"]
/ value["wrapper"]._molar_mass * c.m.val_SI
for key, value in c.fluid_data.items()
}
o2 = molar_flow["air"]["O2"]
co2 = molar_flow["fluegas"]["CO2"]
assert o2 == pytest.approx(co2 * 1.5, 1e-3)

self.c3.set_attr(T=1500)
instance.set_attr(lamb=None)
self.nw.solve('design')
self.nw._convergence_check()
assert self.c3.T.val == pytest.approx(1500)

def test_CombustionChamberHighTemperature(self):
instance = CombustionChamber('combustion chamber')
Expand Down
Loading
0