Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
df7a834
Allowing an arbitrary product number to be present in a reaction.
Allentro Apr 1, 2024
f4c4903
Updating docstrings and typehints
Allentro Apr 1, 2024
0001121
Enabling ability for an arbitrary number of reactants.
Allentro Apr 1, 2024
6f5457d
Raise ValueError if len(reactant)=0
Allentro Apr 1, 2024
ad3b1b4
Updating doc-strings and type hints
Allentro Apr 1, 2024
9514d61
Updating current tests
Allentro Apr 1, 2024
6d40e35
Updating the example files.
Allentro Apr 1, 2024
0ba493d
Added tests for A -> None reactions
Allentro Apr 1, 2024
d75f3f5
Black formatting
Allentro Apr 1, 2024
3c1381d
Fixing error in tests
Allentro Apr 1, 2024
dec7172
Fixing product iterator
Allentro Apr 1, 2024
a4bc1c5
Fixing failed tets
Allentro Apr 1, 2024
7bf193e
Updating call to reaction in species
Allentro Apr 1, 2024
47da437
Black formatting
Allentro Apr 1, 2024
82a4784
Prevent formulation of ImplicitSpecies
Allentro Apr 1, 2024
ecd905a
Updating type hints of Reaction class
Allentro Apr 1, 2024
bdf4f12
Updating typehint or reactant input of Reaction class
Allentro Apr 1, 2024
fe65d63
Fixing product in test function.
Allentro Apr 2, 2024
c5bf901
Update examples/multi_isotope_trapping_example.py
Allentro Apr 3, 2024
1567668
Update examples/multi_isotope_trapping_example.py
Allentro Apr 3, 2024
875a6d1
Update festim/hydrogen_transport_problem.py
Allentro Apr 3, 2024
a6afaec
Update festim/hydrogen_transport_problem.py
Allentro Apr 3, 2024
6a5397f
Update festim/species.py
Allentro Apr 3, 2024
42a899a
Default self.product = product or []
Allentro Apr 3, 2024
e540546
Update festim/reaction.py
Allentro Apr 3, 2024
e958619
Removing superfluous list conversion
Allentro Apr 3, 2024
c2f8d5c
Removing superfluous conversion to list
Allentro Apr 3, 2024
f9491bd
Removing check for reactant being a list
Allentro Apr 3, 2024
f5e2fae
Removing None when no products are present
Allentro Apr 3, 2024
de7ac34
Adding a test against reactant=[]
Allentro Apr 3, 2024
b094043
Merging with upstream edits
Allentro Apr 3, 2024
6f5fb29
Checking E_p and p_0 are zero with no products
Allentro Apr 3, 2024
f0978e3
Removing None from str() and repr() when no product is present.
Allentro Apr 3, 2024
daa8c5c
Fix to test
Allentro Apr 3, 2024
07879fb
Test fix
Allentro Apr 3, 2024
1c62b57
Fixing tests
Allentro Apr 3, 2024
bbd1ceb
Fixing tests
Allentro May 5, 2024
5caf272
Fixing reactant
Allentro May 5, 2024
f862481
Adding reaction product tests
Allentro May 5, 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
18 changes: 6 additions & 12 deletions examples/multi_isotope_trapping_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,53 +62,47 @@
E_k=0.39,
p_0=1e13,
E_p=1.2,
reactant1=mobile_H,
reactant2=empty_trap,
reactant=[mobile_H, empty_trap],
product=trapped_H1,
),
F.Reaction(
k_0=4.1e-7 / (1.1e-10**2 * 6 * w_atom_density),
E_k=0.39,
p_0=1e13,
E_p=1.0,
reactant1=mobile_H,
reactant2=trapped_H1,
reactant=[mobile_H, trapped_H1],
product=trapped_H2,
),
F.Reaction(
k_0=4.1e-7 / (1.1e-10**2 * 6 * w_atom_density),
E_k=0.39,
p_0=1e13,
E_p=1.2,
reactant1=mobile_D,
reactant2=empty_trap,
reactant=[mobile_D, empty_trap],
product=trapped_D1,
),
F.Reaction(
k_0=4.1e-7 / (1.1e-10**2 * 6 * w_atom_density),
E_k=0.39,
p_0=1e13,
E_p=1.2,
reactant1=mobile_D,
reactant2=trapped_D1,
reactant=[mobile_D, trapped_D1],
product=trapped_D2,
),
F.Reaction(
k_0=4.1e-7 / (1.1e-10**2 * 6 * w_atom_density),
E_k=0.39,
p_0=1e13,
E_p=1.0,
reactant1=mobile_H,
reactant2=trapped_D1,
reactant=[mobile_H, trapped_D1],
product=trapped_HD,
),
F.Reaction(
k_0=4.1e-7 / (1.1e-10**2 * 6 * w_atom_density),
E_k=0.39,
p_0=1e13,
E_p=1.0,
reactant1=mobile_D,
reactant2=trapped_H1,
reactant=[mobile_D, trapped_H1],
product=trapped_HD,
),
]
Expand Down
6 changes: 2 additions & 4 deletions examples/tds_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@
E_k=0.39,
p_0=1e13,
E_p=0.87,
reactant1=mobile_H,
reactant2=empty_trap1,
reactant=[mobile_H, empty_trap1],
product=trapped_H1,
volume=my_subdomain,
),
Expand All @@ -62,8 +61,7 @@
E_k=0.39,
p_0=1e13,
E_p=1.0,
reactant1=mobile_H,
reactant2=empty_trap2,
reactant=[mobile_H, empty_trap2],
product=trapped_H2,
volume=my_subdomain,
),
Expand Down
3 changes: 1 addition & 2 deletions examples/trapping_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@
E_p=1.2,
k_0=3.8e-17,
E_k=0.2,
reactant1=mobile_H,
reactant2=empty_trap,
reactant=[mobile_H, empty_trap],
product=trapped_H,
)
]
Expand Down
22 changes: 8 additions & 14 deletions festim/hydrogen_transport_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,20 +655,14 @@ def create_formulation(self):
self.formulation += ((u - u_n) / self.dt) * v * self.dx(vol.id)

for reaction in self.reactions:
# reactant 1
if isinstance(reaction.reactant1, F.Species):
self.formulation += (
reaction.reaction_term(self.temperature_fenics)
* reaction.reactant1.test_function
* self.dx(reaction.volume.id)
)
# reactant 2
if isinstance(reaction.reactant2, F.Species):
self.formulation += (
reaction.reaction_term(self.temperature_fenics)
* reaction.reactant2.test_function
* self.dx(reaction.volume.id)
)
for reactant in reaction.reactant:
if isinstance(reactant, F.Species):
self.formulation += (
reaction.reaction_term(self.temperature_fenics)
* reactant.test_function
* self.dx(reaction.volume.id)
)

# product
if isinstance(reaction.product, list):
products = reaction.product
Expand Down
123 changes: 74 additions & 49 deletions festim/reaction.py
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If product is optional, shouldn't p_0 and E_p be optional too?

And maybe we can raise an error if no products are given but p_0 and E_p are given and vice versa.

What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think an implementation that makes sense would be allowing
A -> None
In that case, k_0 and E_k are None (optional)
If users provide values for k_0 and E_k, an error is raised ("you provided a k but you didn't provide a product)

I don't think it makes sense to require users to set k=0 explicitely in this case

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

k_0 and E_k are the forward components. Is it not the case that if product=None, p_0 and E_p should both be 0?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sorry, in all I said replace k by p (the backward rate is None if there are no products)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Okay makes sense. I've added that into the PR with tests.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It seems like p_0 and E_p are still not optional.

I would rather have users though

Reaction(reactant=..., k_0=..., E_k=...)

Than

Reaction(reactant=..., k_0=..., E_k=..., p_0=0, E_p=0)

It's easier and more straightforward for the same output.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import festim as F
from typing import Union
from typing import Union, Optional, List

from ufl import exp

Expand All @@ -8,19 +8,17 @@ class Reaction:
"""A reaction between two species, with a forward and backward rate.

Arguments:
reactant1 (Union[F.Species, F.ImplicitSpecies]): The first reactant.
reactant2 (Union[F.Species, F.ImplicitSpecies]): The second reactant.
product (F.Species): The product.
reactant (Union[F.Species, F.ImplicitSpecies], List[Union[F.Species, F.ImplicitSpecies]]): The reactant.
product (Optional[Union[F.Species, List[F.Species]]]): The product.
k_0 (float): The forward rate constant pre-exponential factor.
E_k (float): The forward rate constant activation energy.
p_0 (float): The backward rate constant pre-exponential factor.
E_p (float): The backward rate constant activation energy.
volume (F.VolumeSubdomain1D): The volume subdomain where the reaction takes place.

Attributes:
reactant1 (Union[F.Species, F.ImplicitSpecies]): The first reactant.
reactant2 (Union[F.Species, F.ImplicitSpecies]): The second reactant.
product (F.Species): The product.
reactant (Union[F.Species, F.ImplicitSpecies], List[Union[F.Species, F.ImplicitSpecies]]): The reactant.
product (Optional[Union[F.Species, List[F.Species]]]): The product.
k_0 (float): The forward rate constant pre-exponential factor.
E_k (float): The forward rate constant activation energy.
p_0 (float): The backward rate constant pre-exponential factor.
Expand All @@ -29,14 +27,13 @@ class Reaction:

Usage:
>>> # create two species
>>> species1 = F.Species("A")
>>> species2 = F.Species("B")
>>> reactant = [F.Species("A"), F.Species("B")]

>>> # create a product species
>>> product = F.Species("C")

>>> # create a reaction between the two species
>>> reaction = Reaction(species1, species2, product, k_0=1.0, E_k=0.2, p_0=0.1, E_p=0.3)
>>> reaction = Reaction(reactant, product, k_0=1.0, E_k=0.2, p_0=0.1, E_p=0.3)
>>> print(reaction)
A + B <--> C

Expand All @@ -48,81 +45,109 @@ class Reaction:

def __init__(
self,
reactant1: Union[F.Species, F.ImplicitSpecies],
reactant2: Union[F.Species, F.ImplicitSpecies],
product: F.Species,
reactant: Union[
F.Species, F.ImplicitSpecies, List[Union[F.Species, F.ImplicitSpecies]]
],
k_0: float,
E_k: float,
p_0: float,
E_p: float,
volume: F.VolumeSubdomain1D,
product: Optional[Union[F.Species, List[F.Species]]] = [],
p_0: float = None,
E_p: float = None,
) -> None:
self.reactant1 = reactant1
self.reactant2 = reactant2
self.product = product
self.k_0 = k_0
self.E_k = E_k
self.p_0 = p_0
self.E_p = E_p
self.reactant = reactant
self.product = product
self.volume = volume

@property
def reactant1(self):
return self._reactant1

@reactant1.setter
def reactant1(self, value):
if not isinstance(value, (F.Species, F.ImplicitSpecies)):
raise TypeError(
f"reactant1 must be an F.Species or F.ImplicitSpecies, not {type(value)}"
def reactant(self):
return self._reactant

@reactant.setter
def reactant(self, value):
if not isinstance(value, list):
value = [value]
if len(value) == 0:
raise ValueError(
Comment thread
Allentro marked this conversation as resolved.
f"reactant must be an entry of one or more species objects, not an empty list."
)
self._reactant1 = value

@property
def reactant2(self):
return self._reactant2

@reactant2.setter
def reactant2(self, value):
if not isinstance(value, (F.Species, F.ImplicitSpecies)):
raise TypeError(
f"reactant2 must be an F.Species or F.ImplicitSpecies, not {type(value)}"
)
self._reactant2 = value
for i in value:
if not isinstance(i, (F.Species, F.ImplicitSpecies)):
raise TypeError(
f"reactant must be an F.Species or F.ImplicitSpecies, not {type(i)}"
)
self._reactant = value

def __repr__(self) -> str:
reactants = " + ".join([str(reactant) for reactant in self.reactant])

if isinstance(self.product, list):
products = " + ".join([str(product) for product in self.product])
else:
products = self.product
return f"Reaction({self.reactant1} + {self.reactant2} <--> {products}, {self.k_0}, {self.E_k}, {self.p_0}, {self.E_p})"
return f"Reaction({reactants} <--> {products}, {self.k_0}, {self.E_k}, {self.p_0}, {self.E_p})"

def __str__(self) -> str:
reactants = " + ".join([str(reactant) for reactant in self.reactant])
if isinstance(self.product, list):
products = " + ".join([str(product) for product in self.product])
else:
products = self.product
return f"{self.reactant1} + {self.reactant2} <--> {products}"
return f"{reactants} <--> {products}"

def reaction_term(self, temperature):
"""Compute the reaction term at a given temperature.

Arguments:
temperature (): The temperature at which the reaction term is computed.
"""

if self.product == []:
if self.p_0 is not None:
raise ValueError(
f"p_0 must be None, not {self.p_0} when no products are present."
)
if self.E_p is not None:
raise ValueError(
f"E_p must be None, not {self.E_p} when no products are present."
)
else:
if self.p_0 == None:
raise ValueError(
f"p_0 cannot be None when reaction products are present."
)
elif self.E_p == None:
raise ValueError(
f"E_p cannot be None when reaction products are present."
)

k = self.k_0 * exp(-self.E_k / (F.k_B * temperature))
p = self.p_0 * exp(-self.E_p / (F.k_B * temperature))

c_A = self.reactant1.concentration
c_B = self.reactant2.concentration
if self.p_0 and self.E_p:
p = self.p_0 * exp(-self.E_p / (F.k_B * temperature))
elif self.p_0:
p = self.p_0
else:
p = 0

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

if isinstance(self.product, list):
products = self.product
else:
products = [self.product]

products_of_product = products[0].solution
for product in products[1:]:
products_of_product *= product.solution

return k * c_A * c_B - p * products_of_product
if len(products) > 0:
product_of_products = products[0].solution
for product in products[1:]:
product_of_products *= product.solution
else:
product_of_products = 0
Comment thread
RemDelaporteMathurin marked this conversation as resolved.
return k * product_of_reactants - (p * product_of_products)
6 changes: 2 additions & 4 deletions festim/species.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ class Trap(Species):
ct = F.Species("trapped")
trap_sites = F.ImplicitSpecies(n=1, others=[ct])
trap_reaction = F.Reaction(
reactant1=cm,
reactant2=trap_sites,
reactant=[cm, trap_sites],
product=ct,
k_0=1,
E_k=1,
Expand Down Expand Up @@ -153,8 +152,7 @@ def create_species_and_reaction(self):
trap_site = F.ImplicitSpecies(n=self.n, others=[self.trapped_concentration])

self.reaction = F.Reaction(
reactant1=self.mobile_species,
reactant2=trap_site,
reactant=[self.mobile_species, trap_site],
product=self.trapped_concentration,
k_0=self.k_0,
E_k=self.E_k,
Expand Down
9 changes: 3 additions & 6 deletions test/system_tests/test_1_mobile_1_trap.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ def v_exact(mod):

my_model.reactions = [
F.Reaction(
reactant1=mobile,
reactant2=traps,
reactant=[mobile, traps],
product=trapped,
k_0=k_0,
E_k=E_k,
Expand Down Expand Up @@ -273,8 +272,7 @@ def v_exact(mod):

my_model.reactions = [
F.Reaction(
reactant1=mobile,
reactant2=traps,
reactant=[mobile, traps],
product=trapped,
k_0=k_0,
E_k=E_k,
Expand Down Expand Up @@ -403,8 +401,7 @@ def v_exact(mod):

my_model.reactions = [
F.Reaction(
reactant1=mobile,
reactant2=traps,
reactant=[mobile, traps],
product=trapped,
k_0=k_0,
E_k=E_k,
Expand Down
3 changes: 1 addition & 2 deletions test/system_tests/test_reaction_not_in_every_volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ def test_sim_reaction_not_in_every_volume():
trap = F.ImplicitSpecies(n=8.19e25, others=[ct])
my_model.reactions = [
F.Reaction(
reactant1=cm,
reactant2=trap,
reactant=[cm, trap],
product=ct,
k_0=8.9e-17,
E_k=0.39,
Expand Down
3 changes: 1 addition & 2 deletions test/test_complex_reaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ def model_test_reaction(stepsize=1, k=350e-4, p=120e-4, c_A_0=1):
E_k=0,
p_0=p,
E_p=0,
reactant1=species_A,
reactant2=species_B,
reactant=[species_A, species_B],
product=[species_C, species_D],
volume=my_subdomain,
),
Expand Down
Loading