Closed corykinney closed 2 years ago
Hi @corykinney. I believe you can already more or less do this manually, e.g.
gas.set_equivalence_ratio(1, fuel='CH4:1.', oxidizer='O2:0.2, Ar:0.7')
where you specify the composition of the oxidizer
(including the diluent
) directly.
@ischoegl you could try to specify the diluent
directly in the oxidizer
, but you can only give mole fractions relative to the other components of the oxidizer
and not relative to the resultant mixture. So while your example works with phi
of 1:
>>> gas.set_equivalence_ratio(1, fuel='CH4:1.', oxidizer='O2:0.2, Ar:0.7')
>>> gas.mole_fraction_dict()
{'AR': 0.6999999999999998, 'CH4': 0.1, 'O2': 0.2}
it only happens to work because for an O2 concentration of 0.2, the corresponding CH4 concentration is 0.1, which cancels out the normalization of the oxidizer. If I were to use a different equivalence ratio (or diluent concentration) it would give an unintended result:
>>> gas.set_equivalence_ratio(0.5, fuel='CH4:1.', oxidizer='O2:0.2, Ar:0.7')
>>> gas.mole_fraction_dict()
{'AR': 0.736842105263158, 'CH4': 0.052631578947368425, 'O2': 0.2105263157894737}
@corrykinney ... got it. Personally, I'd have two suggestions to tweak your original proposal to
>>> gas.set_equivalence_ratio(0.5, fuel='CH4', oxidizer='O2', diluent='Ar', mole_fraction_diluent=0.7)
as diluent
is somewhat more descriptive, and 'concentration' is not sufficiently specific (a corresponding mass_fraction_diluent
would also be possible). I also used key-word arguments for clarity.
@ischoegl that makes sense. I would suppose my revised proposal for the Python function signature would be:
set_equivalence_ratio(phi, fuel, oxidizer, diluent=None, *, fuel_mole_fraction=None, diluent_mole_fraction=None)
Maybe with the fuel_mass_fraction
and diluent_mass_fraction
as you suggested as well. Where the diluent
argument is optional and its use would require a single keyword argument to be specified (since they're all mutually exclusive). This is just a suggested Python function signature since I'm not sure how to write the underlying C++ function.
This is just a suggested Python function signature since I'm not sure how to write the underlying C++ function.
One first step (I think) could be to just do the conversion in the Python function to calculate the oxidizer mole fraction that includes the diluent. Since this is mainly a convenience feature to offload the calculation from the user. Then the python function could call the underlying C++, using the same signature as before?
If this is the approach, we'd want to make a parallel change in the Matlab toolbox.
I agree that things could be limited to Python. As an alternative, I could also see a new function set_dilution
with a signature
set_dilution(diluent, diluent_mole_fraction=None)
This has the advantage that it would keep set_equivalence_ratio
simple, but the caveat that specifying fuel_mole_fraction
would be more complex.
Regarding MATLAB, I don't think that an implementation is necessary. The MATLAB interface is incomplete as it stands, and (as far as I am aware of) there are some ongoing attempts to replace it by a new approach, making any new feature implementation potentially short-lived.
I have a couple ideas here...
I think the way that CHEMKIN and possibly also CEA do this is by allowing you to specify a set of "extra" species whose mole fractions are known. That would solve the diluent issue, and should be pretty easy to implement. A Python interface like:
set_equivalence_ratio(1.0, 'CH4:1,C3H8:1', 'O2:1,N2:3.76', extra='AR:0.7')
would work nicely, IMO. That would set the mole fraction (by default) of argon to 0.7 and scale everything else accordingly. This should be relatively simple to do in C++ so it can probably be added to every interface.
As far as setting the fuel concentration, that API seems a little trickier to fit in with set_equivalence_ratio()
. One option is to allow the extra
parameter to include the fuel species, which would set the fuel concentration appropriately. That would require a means to specify the diluent/balance species, perhaps with another keyword argument? Something like
set_equivalence_ratio(1.0, 'CH4:1', 'O2:1,N2:3.76', extra='CH4:0.04', diluent='AR')
or perhaps a specific name like fuel
to allow multicomponent mixtures:
set_equivalence_ratio(1.0, 'CH4:1,C3H8:1', 'O2:1,N2:3.76', extra='fuel:0.7', diluent='AR')
@bryanwweber Yeah I'm not 100% sure how to get the features to fit with the existing set_equivalence_ratio
function, and even then the function name wouldn't be fully descriptive of the extra functionality.
If we went the route of using the extra
keyword parameter, we would definitely need the specific name fuel
as you suggested for multicomponent mixtures. Would it be possible to use an interface like this, or would it be inconsistent with how Cantera code is normally called:
set_equivalence_ratio(1.0, 'CH4: 1', 'O2: 1, N2: 3.76', extra='fuel:0.04, CO2: 0.50, Ar')
with the mole fraction for Ar
left out to indicate dilution? With
set_equivalence_ratio(1.0, 'CH4: 1', 'O2: 1, N2: 3.76', extra='CO2, 0.30, Ar: 0.40')
to make a stoichiometric mixture out of the remaining 30%.
Hi @corykinney! I don't understand your first case... The CO2 should have a mole fraction of 0.4, the fuel (in this case methane) at 0.04, o2 and n2 in the air proportion, and with their mole fraction set to the stoichiometric ratio with the fuel, and the balance argon? Honestly, that seems too complicated for this function, and since it's meant to support a particular class of experiments, I'd suggest creating a separate toolbox based on Cantera to do that math.
The second case, though, fits neatly in what I was thinking, in terms of the extra
parameter. That's a relatively simple scaling of the existing computation.
@bryanwweber I can understand that it would be beyond the scope of the function. For my purposes, I'll just have to make some custom functionality for that. But I do think the extra
parameter with specified mole fractions would be helpful and more within the scope of the set_equivalence_ratio
function!
@corykinney and @bryanwweber ... getting the interface right is probably the most difficult aspect of this. To throw yet another suggestion into the mix, what about
set_equivalence_ratio(1.0, fuel="CH4", oxidizer="O2: 1, N2: 3.76", diluent="Ar",
fraction="diluent:0.7", basis="mass")
This would just add two additional parameters, and cover all cases (fraction
could use fuel
, diluent
and even oxidizer
).
I like @ischoegl's last suggestion, as I think it offers a lot of flexibility without adding too many specialized arguments. I'm not sure what the best format for the fraction
argument is. The string-based option shown is easy to use, but I think in that case we'd also want to accept a dict like {"diluent": 0.7}
as we do for composition strings.
If I understand correctly, with @ischoegl 's interface, the only change (at least in the python interface) would be to add the following code snippet at the end of the current set_equivalence_ratio function (after https://github.com/Cantera/cantera/blob/38bbc66e24a34bd9650a553e2d96994020890aac/interfaces/cython/cantera/thermo.pyx#L779):
if fraction is not None and diluent is not None:
# parse the fraction argument
if isinstance(fraction,str):
if ':' not in fraction:
raise ValueError("The fraction argument requires a value in the form of e.g. fraction='Ar:0.7'")
colon_pos = fraction.rfind(":")
fraction_type = fraction[:colon_pos]
fraction_value = float(fraction[colon_pos+1:])
elif isinstance(fraction,dict):
fraction_type = fraction.keys()[0]
fraction_value = float(fraction.values()[0])
else:
raise ValueError("fraction argument must be provided as string or dictionary.")
# if 'fraction' is specified for diluent, just scale the mass or mole fractions of the fuel/ox mixture accordingly
if fraction_type == 'diluent':
if basis == 'mole': # is there a nicer way to avoid code duplication?
X_fuelox = self.X
self.X = diluent
self.X = (1-fraction_value)*X_fuelox + fraction_value*self.X
else:
Y_fuelox = self.Y
self.Y = diluent
self.Y = (1-fraction_value)*Y_fuelox + fraction_value*self.Y
return
if fraction_type not 'fuel' and fraction_type not 'oxidizer':
raise ValueError("fraction must be related to 'diluent', 'fuel' or 'oxidizer'.")
# get the mixture fraction before scaling / diluent addition
Z_fuel = self.mixture_fraction(gas,oxidizer,basis)
if basis == 'mass': # for mass basis, it is quite straight forward
if fraction_type == 'fuel':
Z = Z_fuel
else:
Z = 1-Z_fuel
if fraction_value > Z:
raise ValueError("fraction cannot be higher than fraction in the mixture.")
Y_mix = self.Y
self.Y = diluent
factor = fraction_value / Z
self.Y = factor*Y_mix + (1-factor)*self.Y
else:
# convert mass based mixture fraction to molar one, Z = kg fuel / kg mixture
X_mix = self.X
M_mix = self.mean_molecular_weight
self.X = fuel
M_fuel = self.mean_molecular_weight
Z_fuel_mole = Z * M_mix / M_fuel # mol fuel / mol mix
if fraction_type == 'fuel':
Z = Z_fuel_mole
else:
Z = 1-Z_fuel_mole
if fraction_value > Z:
raise ValueError("fraction cannot be higher than fraction in the mixture.")
self.X = diluent
factor = fraction_value / Z
self.X = factor*X_mix + (1-factor)*self.X
if (fraction is None and diluent is not None) or (fraction is not None and diluent is None):
raise ValueError("if dilution is used, both fraction and diluent parameters are required.")
I didn't test the code above, but conceptionally, this should do the requested calculations, right?
Closed via Cantera/cantera#1206
Abstract
Currently the
ThermoPhase.set_equivalence_ratio
function includes parameters forphi
,fuel
, andoxidizer
. The proposed change would add optional parameters to increase utility.Motivation
In shock tube/combustion research it is common to define mixtures using equivalence ratio, fuel concentration, and a corresponding bath gas/diluent. This is very straightforward to do manually with simple mixtures, but with blends of fuels (e.g. natural gas) and oxidizers (e.g. air) a built-in method would be helpful.
Possible Solutions
Add additional parameters
bath
/diluent
/balance
(not sure what standard terminology would be) for gas that will fill the remainder of the mixture, and EITHERfuel_concentration
to specify the total mole fraction of all fuel constituents in the resultant mixture, ORbath_concentration
to specify the total mole fraction of the bath gas in the resultant mixtureAn example of the suggested implementation to make a mixture with a desired fuel concentration would be:
and for a desired bath gas concentration:
Don't add anything
If this doesn't seem that it would be useful enough for inclusion, obviously it is possible to do this with the existing function and a bit of extra manipulation:
set_equivalence_ratio
as isIt would just be useful to have this functionality standard.