Skip to content
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

Surface reactions #753

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1e686fe
first skeleton of class
RemDelaporteMathurin Apr 10, 2024
2e84a71
ufl
RemDelaporteMathurin Apr 10, 2024
b4edc8d
Merge branch 'robin' into surface-reactions
RemDelaporteMathurin Apr 10, 2024
361d776
initial draft
RemDelaporteMathurin Apr 12, 2024
acff010
example + working implementation
RemDelaporteMathurin Apr 12, 2024
9544d74
refactoring
RemDelaporteMathurin Apr 12, 2024
ef70734
documentation
RemDelaporteMathurin Apr 12, 2024
ce7eed4
Merge branch 'robin' into surface-reactions
RemDelaporteMathurin Apr 18, 2024
b1f2332
fixed tests
RemDelaporteMathurin Apr 18, 2024
936738d
Merge branch 'fenicsx' into surface-reactions
RemDelaporteMathurin May 1, 2024
24225d7
Merge remote-tracking branch 'upstream/fenicsx' into surface-reactions
RemDelaporteMathurin May 28, 2024
4a55c64
added HH and DD reactions to surface
RemDelaporteMathurin May 28, 2024
575323f
added derived quantities
RemDelaporteMathurin May 28, 2024
7695587
added system test
RemDelaporteMathurin May 28, 2024
06dbc34
added plots
RemDelaporteMathurin May 28, 2024
2612b35
add two system tests
RemDelaporteMathurin May 28, 2024
1705786
labels to axes and legend
RemDelaporteMathurin May 28, 2024
5d71100
moved to examples folder
RemDelaporteMathurin May 28, 2024
8d3c4da
time dependent pressure
RemDelaporteMathurin May 28, 2024
00815e7
added additional test
RemDelaporteMathurin May 30, 2024
e93598b
Merge remote-tracking branch 'upstream/fenicsx' into surface-reactions
RemDelaporteMathurin Jun 13, 2024
f8f8513
Merge remote-tracking branch 'upstream/fenicsx' into surface-reactions
RemDelaporteMathurin Oct 12, 2024
ad3b10b
Check if BC is a BC....
RemDelaporteMathurin Oct 15, 2024
0ee90b1
Merge remote-tracking branch 'upstream/fenicsx' into surface-reactions
RemDelaporteMathurin Nov 1, 2024
7b5899c
fix tests
RemDelaporteMathurin Nov 1, 2024
9203ac7
ruff
RemDelaporteMathurin Nov 1, 2024
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
1 change: 1 addition & 0 deletions festim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .boundary_conditions.sieverts_bc import SievertsBC
from .boundary_conditions.henrys_bc import HenrysBC
from .boundary_conditions.flux_bc import FluxBCBase, ParticleFluxBC, HeatFluxBC
from .boundary_conditions.surface_reaction import SurfaceReactionBC

from .material import Material

Expand Down
127 changes: 127 additions & 0 deletions festim/boundary_conditions/surface_reaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import festim as F
from dolfinx import fem
import ufl


class SurfaceReactionBCpartial(F.ParticleFluxBC):
"""Boundary condition representing a surface reaction
A + B <-> C
where A, B are the reactants and C is the product
the forward reaction rate is K_r = k_r0 * exp(-E_kr / (k_B * T))
and the backward reaction rate is K_d = k_d0 * exp(-E_kd / (k_B * T))
The reaction rate is:
K = K_r * C_A * C_B - K_d * P_C
with C_A, C_B the concentration of species A and B,
P_C the partial pressure of species C at the surface.

This class is used to create the flux of a single species entering the surface
Example: The flux of species A entering the surface is K.


Args:
reactant (list): list of F.Species objects representing the reactants
gas_pressure (float, callable): the partial pressure of the product species
k_r0 (float): the pre-exponential factor of the forward reaction rate
E_kr (float): the activation energy of the forward reaction rate (eV)
k_d0 (float): the pre-exponential factor of the backward reaction rate
E_kd (float): the activation energy of the backward reaction rate (eV)
subdomain (F.SurfaceSubdomain): the surface subdomain where the reaction occurs
species (F.Species): the species to which the flux is applied
"""

def __init__(
self,
reactant,
gas_pressure,
k_r0,
E_kr,
k_d0,
E_kd,
subdomain,
species,
):
self.reactant = reactant
self.gas_pressure = gas_pressure
self.k_r0 = k_r0
self.E_kr = E_kr
self.k_d0 = k_d0
self.E_kd = E_kd
super().__init__(subdomain=subdomain, value=None, species=species)

Check warning on line 49 in festim/boundary_conditions/surface_reaction.py

View check run for this annotation

Codecov / codecov/patch

festim/boundary_conditions/surface_reaction.py#L43-L49

Added lines #L43 - L49 were not covered by tests

def create_value_fenics(self, mesh, temperature, t: fem.Constant):
kr = self.k_r0 * ufl.exp(-self.E_kr / (F.k_B * temperature))
kd = self.k_d0 * ufl.exp(-self.E_kd / (F.k_B * temperature))
if callable(self.gas_pressure):
gas_pressure = self.gas_pressure(t=t)

Check warning on line 55 in festim/boundary_conditions/surface_reaction.py

View check run for this annotation

Codecov / codecov/patch

festim/boundary_conditions/surface_reaction.py#L52-L55

Added lines #L52 - L55 were not covered by tests
else:
gas_pressure = self.gas_pressure

Check warning on line 57 in festim/boundary_conditions/surface_reaction.py

View check run for this annotation

Codecov / codecov/patch

festim/boundary_conditions/surface_reaction.py#L57

Added line #L57 was not covered by tests

product_of_reactants = self.reactant[0].concentration
for reactant in self.reactant[1:]:
product_of_reactants *= reactant.concentration

Check warning on line 61 in festim/boundary_conditions/surface_reaction.py

View check run for this annotation

Codecov / codecov/patch

festim/boundary_conditions/surface_reaction.py#L59-L61

Added lines #L59 - L61 were not covered by tests

self.value_fenics = kd * gas_pressure - kr * product_of_reactants

Check warning on line 63 in festim/boundary_conditions/surface_reaction.py

View check run for this annotation

Codecov / codecov/patch

festim/boundary_conditions/surface_reaction.py#L63

Added line #L63 was not covered by tests


class SurfaceReactionBC:
"""Boundary condition representing a surface reaction
A + B <-> C
where A, B are the reactants and C is the product
the forward reaction rate is K_r = k_r0 * exp(-E_kr / (k_B * T))
and the backward reaction rate is K_d = k_d0 * exp(-E_kd / (k_B * T))
The reaction rate is:
K = K_r * C_A * C_B - K_d * P_C
with C_A, C_B the concentration of species A and B,
P_C the partial pressure of species C at the surface.

The flux of species A entering the surface is K.
In the special case where A=B, then the flux of particle entering the surface is 2*K


Args:
reactant (list): list of F.Species objects representing the reactants
gas_pressure (float, callable): the partial pressure of the product species
k_r0 (float): the pre-exponential factor of the forward reaction rate
E_kr (float): the activation energy of the forward reaction rate (eV)
k_d0 (float): the pre-exponential factor of the backward reaction rate
E_kd (float): the activation energy of the backward reaction rate (eV)
subdomain (F.SurfaceSubdomain): the surface subdomain where the reaction occurs
"""

def __init__(
self,
reactant,
gas_pressure,
k_r0,
E_kr,
k_d0,
E_kd,
subdomain,
):

self.reactant = reactant
self.gas_pressure = gas_pressure
self.k_r0 = k_r0
self.E_kr = E_kr
self.k_d0 = k_d0
self.E_kd = E_kd
self.subdomain = subdomain

Check warning on line 108 in festim/boundary_conditions/surface_reaction.py

View check run for this annotation

Codecov / codecov/patch

festim/boundary_conditions/surface_reaction.py#L102-L108

Added lines #L102 - L108 were not covered by tests

# create the flux boundary condition for each reactant
self.flux_bcs = [

Check warning on line 111 in festim/boundary_conditions/surface_reaction.py

View check run for this annotation

Codecov / codecov/patch

festim/boundary_conditions/surface_reaction.py#L111

Added line #L111 was not covered by tests
SurfaceReactionBCpartial(
reactant=self.reactant,
gas_pressure=self.gas_pressure,
k_r0=self.k_r0,
E_kr=self.E_kr,
k_d0=self.k_d0,
E_kd=self.E_kd,
subdomain=self.subdomain,
species=species,
)
for species in self.reactant
]

@property
def time_dependent(self):
return False # no need to update if only using ufl.conditional objects

Check warning on line 127 in festim/boundary_conditions/surface_reaction.py

View check run for this annotation

Codecov / codecov/patch

festim/boundary_conditions/surface_reaction.py#L127

Added line #L127 was not covered by tests
38 changes: 24 additions & 14 deletions festim/hydrogen_transport_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,6 @@
self.define_temperature()
self.define_boundary_conditions()
self.create_source_values_fenics()
self.create_flux_values_fenics()
self.create_initial_conditions()
self.create_formulation()
self.create_solver()
Expand Down Expand Up @@ -512,14 +511,30 @@
)

def define_boundary_conditions(self):
"""Defines the dirichlet boundary conditions of the model"""
"""Create forms for DirichletBC and value_fenics for ParticleFluxBC"""
# @jhdark this all_bcs could be a property
# I just don't want to modify self.boundary_conditions

# create all_bcs which includes all flux bcs from SurfaceReactionBC
all_bcs = self.boundary_conditions.copy()
for bc in self.boundary_conditions:
if isinstance(bc, F.SurfaceReactionBC):
all_bcs += bc.flux_bcs
all_bcs.remove(bc)

Check warning on line 523 in festim/hydrogen_transport_problem.py

View check run for this annotation

Codecov / codecov/patch

festim/hydrogen_transport_problem.py#L522-L523

Added lines #L522 - L523 were not covered by tests
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jhdark here all_bcs could be a property I just don't want to modify self.boundary_conditions


for bc in all_bcs:
if isinstance(bc.species, str):
# if name of species is given then replace with species object
bc.species = F.find_species_from_name(bc.species, self.species)
if isinstance(bc, F.DirichletBC):
form = self.create_dirichletbc_form(bc)
self.bc_forms.append(form)
if isinstance(bc, F.ParticleFluxBC):
bc.create_value_fenics(
mesh=self.mesh.mesh,
temperature=self.temperature_fenics,
t=self.t,
)

def create_dirichletbc_form(self, bc):
"""Creates a dirichlet boundary condition form
Expand Down Expand Up @@ -590,18 +605,6 @@
t=self.t,
)

def create_flux_values_fenics(self):
"""For each particle flux create the value_fenics"""
for bc in self.boundary_conditions:
# create value_fenics for all F.ParticleFluxBC objects
if isinstance(bc, F.ParticleFluxBC):

bc.create_value_fenics(
mesh=self.mesh.mesh,
temperature=self.temperature_fenics,
t=self.t,
)

def create_initial_conditions(self):
"""For each initial condition, create the value_fenics and assign it to
the previous solution of the condition's species"""
Expand Down Expand Up @@ -701,6 +704,13 @@
* bc.species.test_function
* self.ds(bc.subdomain.id)
)
if isinstance(bc, F.SurfaceReactionBC):
for flux_bc in bc.flux_bcs:
self.formulation -= (

Check warning on line 709 in festim/hydrogen_transport_problem.py

View check run for this annotation

Codecov / codecov/patch

festim/hydrogen_transport_problem.py#L708-L709

Added lines #L708 - L709 were not covered by tests
flux_bc.value_fenics
* flux_bc.species.test_function
* self.ds(flux_bc.subdomain.id)
)

# check if each species is defined in all volumes
if not self.settings.transient:
Expand Down
52 changes: 52 additions & 0 deletions surface_reaction_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import festim as F
import numpy as np
import ufl


my_model = F.HydrogenTransportProblem()
my_model.mesh = F.Mesh1D(vertices=np.linspace(0, 1, 1000))
my_mat = F.Material(name="mat", D_0=1, E_D=0)
vol = F.VolumeSubdomain1D(id=1, borders=[0, 1], material=my_mat)
left = F.SurfaceSubdomain1D(id=1, x=0)
right = F.SurfaceSubdomain1D(id=2, x=1)

my_model.subdomains = [vol, left, right]

H = F.Species("H")
D = F.Species("D")
my_model.species = [H, D]

my_model.temperature = 500

surface_reaction = F.SurfaceReactionBC(
reactant=[H, D],
gas_pressure=0,
k_r0=0.01,
E_kr=0,
k_d0=0,
E_kd=0,
subdomain=right,
)

surface_reaction_manual = F.ParticleFluxBC(
subdomain=right,
value=lambda c1, T: -2 * 0.01 * ufl.exp(-0 / F.k_B / T) * c1**2,
species=H,
species_dependent_value={"c1": H},
)

my_model.boundary_conditions = [
F.DirichletBC(subdomain=left, value=5, species=H),
F.DirichletBC(subdomain=left, value=5, species=D),
surface_reaction,
# surface_reaction_manual,
]

my_model.exports = [F.XDMFExport("test.xdmf", H)]

my_model.settings = F.Settings(atol=1e-10, rtol=1e-10, final_time=10, transient=True)

my_model.settings.stepsize = 0.1

my_model.initialise()
my_model.run()
10 changes: 5 additions & 5 deletions test/test_h_transport_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,7 @@ def test_update_time_dependent_values_flux(bc_value, expected_values):
my_model.define_meshtags_and_measures()
my_model.assign_functions_to_species()
my_model.define_temperature()
my_model.create_flux_values_fenics()
my_model.define_boundary_conditions()

for i in range(3):
# RUN
Expand Down Expand Up @@ -1018,7 +1018,7 @@ def test_update_fluxes_with_time_dependent_temperature(
my_model.define_function_spaces()
my_model.assign_functions_to_species()
my_model.define_meshtags_and_measures()
my_model.create_flux_values_fenics()
my_model.define_boundary_conditions()

for i in range(3):
# RUN
Expand All @@ -1031,8 +1031,8 @@ def test_update_fluxes_with_time_dependent_temperature(
assert np.isclose(computed_value, expected_values[i])


def test_create_source_values_fenics_multispecies():
"""Test that the create_flux_values_fenics method correctly sets the value_fenics
def test_define_boundary_conditions_multispecies():
"""Test that the define_boundary_conditions method correctly sets the value_fenics
attribute in a multispecies case"""
# BUILD
my_vol = F.VolumeSubdomain1D(id=1, borders=[0, 4], material=dummy_mat)
Expand All @@ -1056,7 +1056,7 @@ def test_create_source_values_fenics_multispecies():
my_model.define_temperature()

# RUN
my_model.create_flux_values_fenics()
my_model.define_boundary_conditions()

# TEST
assert np.isclose(my_model.boundary_conditions[0].value_fenics.value, 5)
Expand Down
Loading