From 7f1ede70e167ee3a0c512e642292605bc20184f7 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Fri, 27 Mar 2026 22:29:21 -0400 Subject: [PATCH 1/5] added ContactResistance class --- src/festim/__init__.py | 2 +- src/festim/subdomain/interface.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/festim/__init__.py b/src/festim/__init__.py index 823d519d9..f8a9dc5a2 100644 --- a/src/festim/__init__.py +++ b/src/festim/__init__.py @@ -69,7 +69,7 @@ from .source import HeatSource, ParticleSource, SourceBase from .species import ImplicitSpecies, Species, find_species_from_name from .stepsize import Stepsize -from .subdomain.interface import Interface, InterfaceMethod +from .subdomain.interface import Interface, InterfaceMethod, ContactResistance from .subdomain.surface_subdomain import ( SurfaceSubdomain, SurfaceSubdomain1D, diff --git a/src/festim/subdomain/interface.py b/src/festim/subdomain/interface.py index c37c870d1..f135d84b2 100644 --- a/src/festim/subdomain/interface.py +++ b/src/festim/subdomain/interface.py @@ -300,3 +300,34 @@ def mixed_term(u, v, n): ) return F_0, F_1 + + +class ContactResistance(Interface): + def __init__( + self, + id: int, + subdomains: list[VolumeSubdomain], + contact_resistance: float, + penalty_term: float = 10.0, + ): + self.contact_resistance = contact_resistance + super().__init__(id, subdomains, penalty_term) + + def set_formulation(self, dInterface, method, species, temperature): + subdomain_0, subdomain_1 = self.subdomains + res = self.restriction + + for spe in species: + assert subdomain_0 in spe.subdomains and subdomain_1 in spe.subdomains, ( + f"Species {spe.name} must be defined in both subdomains of the " + "interface for the interface conditions to be applied" + ) + v_0 = spe.subdomain_to_test_function[subdomain_0](res[0]) + v_1 = spe.subdomain_to_test_function[subdomain_1](res[1]) + + u_0 = spe.subdomain_to_solution[subdomain_0](res[0]) + u_1 = spe.subdomain_to_solution[subdomain_1](res[1]) + F0 = -(u_1 - u_0) / self.contact_resistance * v_0 * dInterface(self.id) + F1 = (u_1 - u_0) / self.contact_resistance * v_1 * dInterface(self.id) + subdomain_0.F += F0 + subdomain_1.F += F1 From 6d1455729b89b296b80f042787ca7650a7855063 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Mon, 30 Mar 2026 16:02:23 -0400 Subject: [PATCH 2/5] update --- src/festim/subdomain/interface.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/festim/subdomain/interface.py b/src/festim/subdomain/interface.py index a69183f2a..6c3db8aa9 100644 --- a/src/festim/subdomain/interface.py +++ b/src/festim/subdomain/interface.py @@ -162,7 +162,7 @@ def get_formulation( dInterface: ufl.Measure, # NOTE should this be called dS? method: InterfaceMethod, species: list["Species"], - temperature, + temperature=None, ) -> tuple[ufl.Form, ufl.Form]: pass @@ -314,7 +314,7 @@ def mixed_term(u, v, n): return F_0, F_1 -class ContactResistance(Interface): +class ContactResistance(InterfaceBase): def __init__( self, id: int, @@ -325,7 +325,7 @@ def __init__( self.contact_resistance = contact_resistance super().__init__(id, subdomains, penalty_term) - def set_formulation(self, dInterface, method, species, temperature): + def get_formulation(self, dInterface, method, species, temperature=None): subdomain_0, subdomain_1 = self.subdomains res = self.restriction @@ -341,5 +341,5 @@ def set_formulation(self, dInterface, method, species, temperature): u_1 = spe.subdomain_to_solution[subdomain_1](res[1]) F0 = -(u_1 - u_0) / self.contact_resistance * v_0 * dInterface(self.id) F1 = (u_1 - u_0) / self.contact_resistance * v_1 * dInterface(self.id) - subdomain_0.F += F0 - subdomain_1.F += F1 + + return F0, F1 From 3227abb43b48dd09099d4a1b5a90b90dbaad4b3c Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Mon, 30 Mar 2026 16:11:47 -0400 Subject: [PATCH 3/5] update with refactoring --- src/festim/subdomain/interface.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/festim/subdomain/interface.py b/src/festim/subdomain/interface.py index e4aafbad6..b6930f7ee 100644 --- a/src/festim/subdomain/interface.py +++ b/src/festim/subdomain/interface.py @@ -335,6 +335,7 @@ def __init__( def get_formulation(self, dInterface, method, species, temperature=None): subdomain_0, subdomain_1 = self.subdomains res = self.restriction + _F_0, _F_1 = dolfinx.fem.form(0), dolfinx.fem.form(0) for spe in species: assert subdomain_0 in spe.subdomains and subdomain_1 in spe.subdomains, ( @@ -348,5 +349,7 @@ def get_formulation(self, dInterface, method, species, temperature=None): u_1 = spe.subdomain_to_solution[subdomain_1](res[1]) F0 = -(u_1 - u_0) / self.contact_resistance * v_0 * dInterface(self.id) F1 = (u_1 - u_0) / self.contact_resistance * v_1 * dInterface(self.id) + _F_0 += F0 + _F_1 += F1 - return F0, F1 + return _F_0, _F_1 From fc59ac2a861b7e723b1a9bb5b24a1c08cc9937c6 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Mon, 30 Mar 2026 16:35:41 -0400 Subject: [PATCH 4/5] update branch --- src/festim/subdomain/interface.py | 52 +++++++++++-------------------- 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/src/festim/subdomain/interface.py b/src/festim/subdomain/interface.py index f5c8e9bf3..5c13d77f5 100644 --- a/src/festim/subdomain/interface.py +++ b/src/festim/subdomain/interface.py @@ -195,11 +195,9 @@ def Ks(self, species: "Species", temperature): for i, subdomain in enumerate(self.subdomains) ) - # TODO this should be a method of a subclass of Interface since we want to support - # other types of interfaces in the future def get_formulation( self, - dInterface: ufl.Measure, # NOTE should this be called dS? + dS: ufl.Measure, method: InterfaceMethod, species: list["Species"], temperature, @@ -209,7 +207,7 @@ def get_formulation( the `.F` attribute of the subdomains. Args: - dInterface: the measure corresponding to the interface, with the correct + dS: the measure corresponding to the interface, with the correct integration data method: the method to use to enforce the interface conditions species: the species for which the interface conditions should be applied. @@ -237,13 +235,13 @@ def get_formulation( f"Species {spe.name} must be defined in both subdomains of the " "interface for the interface conditions to be applied" ) - _F_0, _F_1 = method_to_function[method](dInterface, spe, temperature) + _F_0, _F_1 = method_to_function[method](dS, spe, temperature) F_0 += _F_0 F_1 += _F_1 return F_0, F_1 - def penalty_method(self, dInterface, species, temperature): + def penalty_method(self, dS, species, temperature): subdomain_0, subdomain_1 = self.subdomains u_0, u_1 = self.us(species) v_0, v_1 = self.vs(species) @@ -276,12 +274,12 @@ def penalty_method(self, dInterface, species, temperature): equality = right - left - F_0 = self.penalty_term * ufl.inner(equality, v_0) * dInterface(self.id) - F_1 = -self.penalty_term * ufl.inner(equality, v_1) * dInterface(self.id) + F_0 = self.penalty_term * ufl.inner(equality, v_0) * dS + F_1 = -self.penalty_term * ufl.inner(equality, v_1) * dS return F_0, F_1 - def nitsche_method(self, dInterface, species, temperature): + def nitsche_method(self, dS, species, temperature): u_0, u_1 = self.us(species) K_0, K_1 = self.Ks(species, temperature) v_0, v_1 = self.vs(species) @@ -290,35 +288,21 @@ def mixed_term(u, v, n): return ufl.dot(ufl.grad(u), n) * v res = self.restriction - n = ufl.FacetNormal(dInterface.ufl_domain()) - cr = ufl.Circumradius(dInterface.ufl_domain()) + n = ufl.FacetNormal(dS.ufl_domain()) + cr = ufl.Circumradius(dS.ufl_domain()) n_0 = n(res[0]) h_0 = 2 * cr(res[0]) h_1 = 2 * cr(res[1]) gamma = self.penalty_term - F_0 = -0.5 * mixed_term((u_0 + u_1), v_0, n_0) * dInterface( - self.id - ) - 0.5 * mixed_term(v_0, (u_0 / K_0 - u_1 / K_1), n_0) * dInterface(self.id) - - F_1 = +0.5 * mixed_term((u_0 + u_1), v_1, n_0) * dInterface( - self.id - ) - 0.5 * mixed_term(v_1, (u_0 / K_0 - u_1 / K_1), n_0) * dInterface(self.id) - F_0 += ( - 2 - * gamma - / (h_0 + h_1) - * (u_0 / K_0 - u_1 / K_1) - * v_0 - * dInterface(self.id) - ) - F_1 += ( - -2 - * gamma - / (h_0 + h_1) - * (u_0 / K_0 - u_1 / K_1) - * v_1 - * dInterface(self.id) - ) + F_0 = -0.5 * mixed_term((u_0 + u_1), v_0, n_0) * dS(self.id) - 0.5 * mixed_term( + v_0, (u_0 / K_0 - u_1 / K_1), n_0 + ) * dS(self.id) + + F_1 = +0.5 * mixed_term((u_0 + u_1), v_1, n_0) * dS(self.id) - 0.5 * mixed_term( + v_1, (u_0 / K_0 - u_1 / K_1), n_0 + ) * dS(self.id) + F_0 += 2 * gamma / (h_0 + h_1) * (u_0 / K_0 - u_1 / K_1) * v_0 * dS(self.id) + F_1 += -2 * gamma / (h_0 + h_1) * (u_0 / K_0 - u_1 / K_1) * v_1 * dS(self.id) return F_0, F_1 From fe314361ec2340d5782bff8c87dfb14605cfcebe Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 31 Mar 2026 12:11:48 -0400 Subject: [PATCH 5/5] update branch + fix init --- src/festim/subdomain/interface.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/festim/subdomain/interface.py b/src/festim/subdomain/interface.py index 38479f95b..2adf03065 100644 --- a/src/festim/subdomain/interface.py +++ b/src/festim/subdomain/interface.py @@ -336,12 +336,11 @@ def __init__( id: int, subdomains: list[VolumeSubdomain], contact_resistance: float, - penalty_term: float = 10.0, ): self.contact_resistance = contact_resistance - super().__init__(id, subdomains, penalty_term) + super().__init__(id, subdomains) - def get_formulation(self, dS, method, species, temperature=None): + def get_formulation(self, dS, species, temperature=None): subdomain_0, subdomain_1 = self.subdomains res = self.restriction _F_0, _F_1 = dolfinx.fem.form(0), dolfinx.fem.form(0)