diff --git a/OFsolvers/OF13/birdmultiphaseEuler/Allwmake b/OFsolvers/OF13/birdmultiphaseEuler/Allwmake
index fc432ad1..30e5094b 100755
--- a/OFsolvers/OF13/birdmultiphaseEuler/Allwmake
+++ b/OFsolvers/OF13/birdmultiphaseEuler/Allwmake
@@ -13,5 +13,6 @@ wmake $targetType
wmake -j 20 $targetType fvModels
wmake -j 20 $targetType functionObjects
+wmake -j 20 $targetType boundaryConditions
#------------------------------------------------------------------------------
diff --git a/OFsolvers/OF13/birdmultiphaseEuler/boundaryConditions/Make/files b/OFsolvers/OF13/birdmultiphaseEuler/boundaryConditions/Make/files
new file mode 100644
index 00000000..9279f1a1
--- /dev/null
+++ b/OFsolvers/OF13/birdmultiphaseEuler/boundaryConditions/Make/files
@@ -0,0 +1,3 @@
+recirculatedSpecies/recirculatedSpeciesFvPatchScalarField.C
+
+LIB = $(FOAM_USER_LIBBIN)/libbirdBoundaryConditions
diff --git a/OFsolvers/OF13/birdmultiphaseEuler/boundaryConditions/Make/options b/OFsolvers/OF13/birdmultiphaseEuler/boundaryConditions/Make/options
new file mode 100644
index 00000000..ad2b36e5
--- /dev/null
+++ b/OFsolvers/OF13/birdmultiphaseEuler/boundaryConditions/Make/options
@@ -0,0 +1,14 @@
+EXE_INC = \
+ -I$(LIB_SRC)/finiteVolume/lnInclude \
+ -I$(LIB_SRC)/meshTools/lnInclude \
+ -I../phaseSystem/lnInclude \
+ -I$(LIB_SRC)/physicalProperties/lnInclude \
+ -I$(LIB_SRC)/thermophysicalModels/basic/lnInclude \
+ -I$(LIB_SRC)/twoPhaseModels/twoPhaseMixture/lnInclude \
+
+
+LIB_LIBS = \
+ -lfiniteVolume \
+ -lmeshTools \
+ -L$(FOAM_USER_LIBBIN) \
+ -lbirdphaseSystem
diff --git a/OFsolvers/OF13/birdmultiphaseEuler/boundaryConditions/recirculatedSpecies/recirculatedSpeciesFvPatchScalarField.C b/OFsolvers/OF13/birdmultiphaseEuler/boundaryConditions/recirculatedSpecies/recirculatedSpeciesFvPatchScalarField.C
new file mode 100644
index 00000000..aafc13f9
--- /dev/null
+++ b/OFsolvers/OF13/birdmultiphaseEuler/boundaryConditions/recirculatedSpecies/recirculatedSpeciesFvPatchScalarField.C
@@ -0,0 +1,226 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration | Website: https://openfoam.org
+ \\ / A nd | Copyright (C) 2026 OpenFOAM Foundation
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+\*---------------------------------------------------------------------------*/
+
+#include "recirculatedSpeciesFvPatchScalarField.H"
+#include "addToRunTimeSelectionTable.H"
+#include "fieldMapper.H"
+#include "volFields.H"
+#include "surfaceFields.H"
+
+// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
+
+Foam::scalar Foam::recirculatedSpeciesFvPatchScalarField::t() const
+{
+ return db().time().value();
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+Foam::recirculatedSpeciesFvPatchScalarField::
+recirculatedSpeciesFvPatchScalarField
+(
+ const fvPatch& p,
+ const DimensionedField& iF,
+ const dictionary& dict
+)
+:
+ fixedValueFvPatchScalarField(p, iF),
+ Y0_
+ (
+ Function1::New
+ (
+ "Y0",
+ db().time().userUnits(),
+ unitAny,
+ dict
+ )
+ ),
+ r_
+ (
+ Function1::New
+ (
+ "r",
+ db().time().userUnits(),
+ unitAny,
+ dict
+ )
+ ),
+ phaseName_(dict.lookupOrDefault("phase","gas")),
+ otherPatchName_(dict.lookup("otherPatch"))
+{
+
+
+// fixedValueFvPatchScalarField::evaluate();
+
+
+ // Initialise with the value entry if evaluation is not possible
+ fvPatchScalarField::operator=
+ (
+ scalarField("value", iF.dimensions(), dict, p.size())
+ );
+
+}
+
+
+Foam::recirculatedSpeciesFvPatchScalarField::
+recirculatedSpeciesFvPatchScalarField
+(
+ const recirculatedSpeciesFvPatchScalarField& ptf,
+ const fvPatch& p,
+ const DimensionedField& iF,
+ const fieldMapper& mapper
+)
+:
+ fixedValueFvPatchScalarField(ptf, p, iF, mapper),
+ Y0_(ptf.Y0_, false),
+ r_(ptf.r_, false),
+ phaseName_(ptf.phaseName_),
+ otherPatchName_(ptf.otherPatchName_)
+{}
+
+
+Foam::recirculatedSpeciesFvPatchScalarField::
+recirculatedSpeciesFvPatchScalarField
+(
+ const recirculatedSpeciesFvPatchScalarField& ptf,
+ const DimensionedField& iF
+)
+:
+ fixedValueFvPatchScalarField(ptf, iF),
+ Y0_(ptf.Y0_, false),
+ r_(ptf.r_, false),
+ phaseName_(ptf.phaseName_),
+ otherPatchName_(ptf.otherPatchName_)
+{}
+
+
+
+// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
+
+void Foam::recirculatedSpeciesFvPatchScalarField::map
+(
+ const fvPatchScalarField& ptf,
+ const fieldMapper& mapper
+)
+{
+ fixedValueFvPatchScalarField::map(ptf, mapper);
+}
+
+
+void Foam::recirculatedSpeciesFvPatchScalarField::reset
+(
+ const fvPatchScalarField& ptf
+)
+{
+ fixedValueFvPatchScalarField::reset(ptf);
+}
+
+
+void Foam::recirculatedSpeciesFvPatchScalarField::updateCoeffs()
+{
+ if (updated())
+ {
+ return;
+ }
+
+ const phaseModel& phase
+ (
+ db().lookupObject
+ (
+ IOobject::groupName("alpha", phaseName_)
+ )
+ );
+
+
+ if ( !db().foundObject("alphaRhoPhi." + phase.name()) )
+ {
+ return;
+ }
+
+ // Since we're inside initEvaluate/evaluate there might be processor
+ // comms underway. Change the tag we use.
+ int oldTag = UPstream::msgType();
+ UPstream::msgType() = oldTag+1;
+
+ const fvMesh& mesh = this->internalField().mesh();
+
+ label patchID = mesh.boundaryMesh().findIndex(otherPatchName_);
+
+ scalar alphaRhoPhiYr = 0.;
+ scalar alphaRhoPhiTot = 1e-16;
+
+ if ( patchID != 1 )
+ {
+ const fvPatch& nbrPatch = mesh.boundary()[patchID];
+ const scalarField& alphaRhoPhip = nbrPatch.lookupPatchField("alphaRhoPhi." + phase.name());
+ const scalarField& Yp = nbrPatch.lookupPatchField(this->internalField().name());
+
+ alphaRhoPhiTot = max( mag( sum(alphaRhoPhip)),1e-16 );
+ alphaRhoPhiYr = mag( sum(alphaRhoPhip*Yp) );
+ }
+
+ reduce(alphaRhoPhiTot, sumOp());
+ reduce(alphaRhoPhiYr, sumOp());
+
+ const scalar Yr = min(max( alphaRhoPhiYr/alphaRhoPhiTot, 0.), 1.);
+
+ scalar rf = min(max(r_->value(t()),0.),1.);
+
+ // Restore tag
+ UPstream::msgType() = oldTag;
+
+ fixedValueFvPatchScalarField::operator==( (1. - rf)*Y0_->value(t()) + rf*Yr );
+
+ fixedValueFvPatchScalarField::updateCoeffs();
+}
+
+
+void Foam::recirculatedSpeciesFvPatchScalarField::write
+(
+ Ostream& os
+) const
+{
+ fvPatchScalarField::write(os);
+ writeEntry(os, db().time().userUnits(), unitAny, Y0_());
+ writeEntry(os, db().time().userUnits(), unitAny, r_());
+ writeEntry(os, "phase", phaseName_);
+ writeEntry(os, "otherPatch", otherPatchName_);
+ writeEntry(os, "value", *this);
+}
+
+
+// * * * * * * * * * * * * * * Build Macro Function * * * * * * * * * * * * //
+
+namespace Foam
+{
+ makePatchTypeField
+ (
+ fvPatchScalarField,
+ recirculatedSpeciesFvPatchScalarField
+ );
+}
+
+// ************************************************************************* //
diff --git a/OFsolvers/OF13/birdmultiphaseEuler/boundaryConditions/recirculatedSpecies/recirculatedSpeciesFvPatchScalarField.H b/OFsolvers/OF13/birdmultiphaseEuler/boundaryConditions/recirculatedSpecies/recirculatedSpeciesFvPatchScalarField.H
new file mode 100644
index 00000000..ab8f9052
--- /dev/null
+++ b/OFsolvers/OF13/birdmultiphaseEuler/boundaryConditions/recirculatedSpecies/recirculatedSpeciesFvPatchScalarField.H
@@ -0,0 +1,203 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration | Website: https://openfoam.org
+ \\ / A nd | Copyright (C) 2026 OpenFOAM Foundation
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+Class
+ Foam::recirculatedSpeciesFvPatchScalarField
+
+Description
+ This boundary condition provides a recirculatedSpecies condition,
+ calculated as:
+
+ \f[
+ Y = Y_{0}*(1-r) + r*Y_{r}
+ \f]
+ and
+ \f[
+ Y_{r} = sum(\alphaRhoPhi_{pr} Y_{pr}) / sum(\alphaRhoPhi_{pr})
+ \f]
+
+ where
+ \vartable
+ r | recirculation factor [-]
+ Y_{0} | base patch value [-]
+ Y_{r} | recirculating patch mean [-]
+ alphaRhoPhi_{pr}| mass flow rate at recirculating patch [Kg/s]
+ Y_{pr}| mass fraction at recirculating patch [Kg/Kg]
+ \endtable
+
+Usage
+ \table
+ Property | Description | Req'd? | Default
+ Y0 | base value | yes |
+ r | recirculation factor | yes |
+ otherPatch | name of other patch | yes |
+ \endtable
+
+ Example of the boundary condition specification:
+ \verbatim
+
+ {
+ type recirculatedSpecies;
+ Y0 1; //Initial stream only has this species
+ r table (
+ (0 0)
+ (1 1)
+ ); // Switch to full recirculation after one second
+ otherPatch outlet;
+ value uniform 1; // optional initial value
+ }
+ \endverbatim
+
+SourceFiles
+ recirculatedSpeciesFvPatchScalarField.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef BC_H
+#define BC_H
+
+#include "fixedValueFvPatchFields.H"
+#include "Function1.H"
+#include "phaseModel.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+ Class recirculatedSpeciesFvPatchScalarField Declaration
+\*---------------------------------------------------------------------------*/
+
+class recirculatedSpeciesFvPatchScalarField
+:
+ public fixedValueFvPatchScalarField
+{
+ // Private Data
+
+ //- Base value
+ autoPtr> Y0_;
+
+ //- Recirculation factor
+ autoPtr> r_;
+
+ word phaseName_;
+
+ word otherPatchName_;
+
+
+ // Private Member Functions
+
+ //- Return current time
+ scalar t() const;
+
+
+public:
+
+ //- Runtime type information
+ TypeName("recirculatedSpecies");
+
+
+ // Constructors
+
+ //- Construct from patch, internal field and dictionary
+ recirculatedSpeciesFvPatchScalarField
+ (
+ const fvPatch&,
+ const DimensionedField&,
+ const dictionary&
+ );
+
+ //- Construct by mapping given fixedValueTypeFvPatchField
+ // onto a new patch
+ recirculatedSpeciesFvPatchScalarField
+ (
+ const recirculatedSpeciesFvPatchScalarField&,
+ const fvPatch&,
+ const DimensionedField&,
+ const fieldMapper&
+ );
+
+ //- Disallow copy without setting internal field reference
+ recirculatedSpeciesFvPatchScalarField
+ (
+ const recirculatedSpeciesFvPatchScalarField&
+ ) = delete;
+
+ //- Copy constructor setting internal field reference
+ recirculatedSpeciesFvPatchScalarField
+ (
+ const recirculatedSpeciesFvPatchScalarField&,
+ const DimensionedField&
+ );
+
+ //- Construct and return a clone setting internal field reference
+ virtual tmp clone
+ (
+ const DimensionedField& iF
+ ) const
+ {
+ return tmp
+ (
+ new recirculatedSpeciesFvPatchScalarField
+ (
+ *this,
+ iF
+ )
+ );
+ }
+
+
+ // Member Functions
+
+ // Mapping functions
+
+ //- Map the given fvPatchField onto this fvPatchField
+ virtual void map(const fvPatchScalarField&, const fieldMapper&);
+
+ //- Reset the fvPatchField to the given fvPatchField
+ // Used for mesh to mesh mapping
+ virtual void reset(const fvPatchScalarField&);
+
+
+ // Evaluation functions
+
+ //- Update the coefficients associated with the patch field
+ virtual void updateCoeffs();
+
+
+ //- Write
+ virtual void write(Ostream&) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+
+#endif
+
+// ************************************************************************* //
diff --git a/OFsolvers/OF13/birdmultiphaseEuler/phaseSystem/phaseModels/BioReactingPhaseModel/BioReactingPhaseModel.C b/OFsolvers/OF13/birdmultiphaseEuler/phaseSystem/phaseModels/BioReactingPhaseModel/BioReactingPhaseModel.C
index 695c4892..5caab092 100644
--- a/OFsolvers/OF13/birdmultiphaseEuler/phaseSystem/phaseModels/BioReactingPhaseModel/BioReactingPhaseModel.C
+++ b/OFsolvers/OF13/birdmultiphaseEuler/phaseSystem/phaseModels/BioReactingPhaseModel/BioReactingPhaseModel.C
@@ -43,7 +43,6 @@ Foam::BioReactingPhaseModel::BioReactingPhaseModel
species_(dict_.lookup("species")),
maxUptakeRate_(),
K_(),
- M_(),
URSp_()
// DCW_
// (
@@ -63,9 +62,7 @@ Foam::BioReactingPhaseModel::BioReactingPhaseModel
dictionary subsp(dict_.subDict(species_[specieI]));
maxUptakeRate_.append(readScalar(subsp.lookup("maxUptakeRate")));
- K_.append(readScalar(subsp.lookup("K")));
- M_.append(readScalar(subsp.lookup("molWeight")));
-
+ K_.append(readScalar(subsp.lookup("K"))),
URSp_.append(
new volScalarField::Internal
(
@@ -100,7 +97,9 @@ void Foam::BioReactingPhaseModel::correctReactions()
scalar kg_to_g = 1000.;
scalar hr_to_sec = 3600.;
- const volScalarField& rho = this->thermo().rho();
+ const volScalarField& rho = this->rho();
+ const volScalarField& alpha = *this;
+ const volScalarField alphaRho = alpha * rho;
forAll(this->Y(), speciei)
{
@@ -109,7 +108,8 @@ void Foam::BioReactingPhaseModel::correctReactions()
if ( this->Y()[speciei].name() == IOobject::groupName(species_[speciej], this->name()) )
{
Info << "Updating enzymatic reaction of species " << this->Y()[speciei].name() << endl;
- scalar kg_m3_to_mol_m3 = kg_to_g / M_[speciej];
+
+ scalar kg_m3_to_mol_m3 = kg_to_g / this->thermo_->Wi(speciei).value();
scalar mol_m3_to_kg_m3 = 1./kg_m3_to_mol_m3;
scalar Kp = K_[speciej] * mol_m3_to_kg_m3;
scalar maxUR = maxUptakeRate_[speciej] * mol_m3_to_kg_m3 / hr_to_sec;
@@ -117,9 +117,8 @@ void Foam::BioReactingPhaseModel::correctReactions()
volScalarField::Internal& URSp = URSp_[speciej];
forAll(URSp, cellI)
{
- URSp[cellI] = - maxUR / ( Kp + rho[cellI] * this->Y()[speciei][cellI] );
- }
-
+ URSp[cellI] = - ( maxUR * alphaRho[cellI] ) / ( Kp + alphaRho[cellI] * this->Y()[speciei][cellI] );
+ }
}
}
}
diff --git a/OFsolvers/OF13/birdmultiphaseEuler/phaseSystem/phaseModels/BioReactingPhaseModel/BioReactingPhaseModel.H b/OFsolvers/OF13/birdmultiphaseEuler/phaseSystem/phaseModels/BioReactingPhaseModel/BioReactingPhaseModel.H
index fc7dd8d7..54afdee3 100644
--- a/OFsolvers/OF13/birdmultiphaseEuler/phaseSystem/phaseModels/BioReactingPhaseModel/BioReactingPhaseModel.H
+++ b/OFsolvers/OF13/birdmultiphaseEuler/phaseSystem/phaseModels/BioReactingPhaseModel/BioReactingPhaseModel.H
@@ -81,9 +81,6 @@ protected:
// Half saturation constant
List K_;
- // Molar weights
- List M_;
-
// Uptake rates Sp
PtrList URSp_;
diff --git a/tutorial_cases/OF13/bubble_column/0/CO2.gas b/tutorial_cases/OF13/bubble_column/0/CO2.gas
index 9da26d4c..253399df 100644
--- a/tutorial_cases/OF13/bubble_column/0/CO2.gas
+++ b/tutorial_cases/OF13/bubble_column/0/CO2.gas
@@ -25,7 +25,10 @@ boundaryField
inlet
{
- type fixedValue;
+ type recirculatedSpecies;
+ Y0 $CO2_in;
+ otherPatch outlet;
+ r 0.5;
value uniform $CO2_in;
}
diff --git a/tutorial_cases/OF13/bubble_column/0/N2.gas b/tutorial_cases/OF13/bubble_column/0/N2.gas
index 61cacdbd..18df0be0 100644
--- a/tutorial_cases/OF13/bubble_column/0/N2.gas
+++ b/tutorial_cases/OF13/bubble_column/0/N2.gas
@@ -25,8 +25,10 @@ boundaryField
inlet
{
- type fixedValue;
+ type recirculatedSpecies;
value uniform $N2_in;
+ Y0 $N2_in;
+ r 0.5;
}
outlet
diff --git a/tutorial_cases/OF13/bubble_column/0/O2.gas b/tutorial_cases/OF13/bubble_column/0/O2.gas
index c79b66c7..6f0957da 100644
--- a/tutorial_cases/OF13/bubble_column/0/O2.gas
+++ b/tutorial_cases/OF13/bubble_column/0/O2.gas
@@ -25,8 +25,11 @@ boundaryField
inlet
{
- type fixedValue;
+ type recirculatedSpecies;
value uniform $O2_in;
+ Y0 $O2_in;
+ otherPatch outlet;
+ r 0.5;
}
outlet
diff --git a/tutorial_cases/OF13/bubble_column/README.md b/tutorial_cases/OF13/bubble_column/README.md
index 250c354b..4ebc8027 100644
--- a/tutorial_cases/OF13/bubble_column/README.md
+++ b/tutorial_cases/OF13/bubble_column/README.md
@@ -5,6 +5,7 @@ Features demonstrated:
* Basic bubble column settings with LES and bubble size distribution
* Air sparged in water
* Oxygen mass transfer
+* 50% recirculation (half of the sparged gas is recirculated)
Used for:
* Bubble column simulations with mass transfer
diff --git a/tutorial_cases/OF13/bubble_column/system/controlDict b/tutorial_cases/OF13/bubble_column/system/controlDict
index ee70fba0..752da6e5 100644
--- a/tutorial_cases/OF13/bubble_column/system/controlDict
+++ b/tutorial_cases/OF13/bubble_column/system/controlDict
@@ -14,7 +14,7 @@ FoamFile
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-libs ( "libbirdmultiphaseEuler.so" );
+libs ( "libbirdmultiphaseEuler.so" "libbirdBoundaryConditions.so" );
solver birdmultiphaseEuler;