Dear Dokken,
Thank you dor your quick response, please find the schematic of the problem below;
I would like to solve:
\frac{\partial \phi_1}{\partial t} = D_1\nabla \cdot \nabla \phi_1 \quad \text{in $\Omega_1$},
and
\frac{\partial \phi_2}{\partial t} = D_2\nabla \cdot \nabla \phi_2 + s\phi_2 (1-\phi_2/\phi_0) \quad \text{in $\Omega_2$},
at the same time. Both domains have symmetry (Neumann) BC on the left. Blue domain has a BC of \phi_2(right, t)=1.0 on the right end and zero elsewhere. As time progress, blue domain fills up with 1.0s and the cells in \Omega_2 with value 1.0 should become Neumann BC for \Omega_1.
What I made so far is to manually shrink the blue domain, as 2D MWE for full domain shows below:
from dolfinx.fem import functionspace, Constant, Function, form, assemble_matrix
from dolfinx.fem.petsc import assemble_matrix, apply_lifting, set_bc, assemble_vector, create_vector
from petsc4py import PETSc
from dolfinx.io.gmshio import model_to_mesh
from mpi4py import MPI
from dolfinx import default_scalar_type
from ufl import TrialFunction, TestFunction, Measure, dot, grad
from dolfinx.io import XDMFFile
from dolfinx import fem
import numpy as np
import sys
import os
import gmsh
projectRoot = os.path.join(os.getcwd())
resultsDir = os.path.join(projectRoot,'ResultsDir')
screenshotsDir = os.path.join(resultsDir,'screenshots')
test_name = "transient_2d.xdmf"
xdmf_filename = "/"+test_name
def twoDimHydro(lx, ly, l_drug, drug_start_x, lc=0.005):
path = os.path.dirname(os.path.abspath(__file__))
mesh_dir = "/MeshDir"
mesh_name = "/twodim"
gmsh.initialize()
gmsh.option.setNumber("General.Terminal", 0)
gmsh.model.add(__name__)
'''
p_drug_start p_drug_end
1-------------|--------------|-------------------2
| l1 l_drug l_2 |
| |
| |
| l_4 | l_3
| |
|-----------------------------------------------|
4 l_wound 3
'''
p_1 = gmsh.model.occ.addPoint(0, ly, 0)
p_2 = gmsh.model.occ.addPoint(lx, ly, 0)
p_drug_start = gmsh.model.occ.addPoint(drug_start_x, ly, 0)
p_drug_end = gmsh.model.occ.addPoint(drug_start_x+l_drug, ly, 0)
p_3 = gmsh.model.occ.addPoint(lx, 0, 0, lc/5)
p_4 = gmsh.model.occ.addPoint(0, 0, 0, lc/5)
l_1 = gmsh.model.occ.addLine(p_1, p_drug_start)
l_drug = gmsh.model.occ.addLine(p_drug_start, p_drug_end)
l_2 = gmsh.model.occ.addLine(p_drug_end, p_2)
l_3 = gmsh.model.occ.addLine(p_2, p_3)
l_wound = gmsh.model.occ.addLine(p_3, p_4)
l_4 = gmsh.model.occ.addLine(p_4, p_1)
cl_1 = gmsh.model.occ.addCurveLoop([l_1,l_drug,l_2, l_3, l_wound, l_4])
s_1 = gmsh.model.occ.addPlaneSurface([cl_1])
gmsh.model.occ.synchronize()
gmsh.model.addPhysicalGroup(1, [l_1,l_2,l_3,l_4], tag=1) # all other tag
gmsh.model.addPhysicalGroup(1, [l_drug], tag=2) # drug tag
gmsh.model.addPhysicalGroup(1, [l_wound], tag=3) # wound tag
gmsh.model.addPhysicalGroup(2, [1], tag=1) # hydrogel tag
gmsh.option.setNumber("Mesh.MeshSizeMax", lc)
gmsh.option.setNumber("Mesh.Optimize", 1)
gmsh.model.mesh.generate(2)
if "-nopopup" not in sys.argv:
gmsh.fltk.run()
return gmsh.model
lx=1
ly=1/2
l_drug=0.05
drug_start_x=lx/2-l_drug/2
gmsh_model = twoDimHydro(lx, ly, l_drug, drug_start_x, lc=0.01)
mesh, subdomains, facet_tags = model_to_mesh(gmsh_model, MPI.COMM_WORLD, rank=0, gdim=2)
drug_concentration = +1.0
wound_concentration = 0.5
other_concentration = 0.0
other_flux = 0.0
degree = 1
V = functionspace(mesh, ("Lagrange", degree))
u = TrialFunction(V)
# Static/tagged BCs (use your existing tags 1 and 2)
u_other = Function(V)
u_other.x.array[:] = other_concentration
u_drug = Function(V)
u_drug.x.array[:] = drug_concentration
bc_other = fem.dirichletbc(
u_other,
fem.locate_dofs_topological(V, mesh.topology.dim-1, facet_tags.find(1))
)
bc_drug = fem.dirichletbc(
u_drug,
fem.locate_dofs_topological(V, mesh.topology.dim-1, facet_tags.find(2))
)
def build_wound_bc(V, l_wound, value, lx, x_center=None, y_bottom=0.0, atol=1e-12):
"""
Return a Dirichlet BC for the wound that shrinks symmetrically toward the middle.
Args:
V: Function space.
l_wound: Remaining wound length along the bottom edge.
value: Dirichlet value to impose on the wound segment.
lx: Total domain width in x.
x_center: Center of the wound segment (defaults to lx/2).
y_bottom: y-coordinate of the bottom boundary.
atol: tolerance for y≈y_bottom check.
Behavior:
- If l_wound <= 0 -> returns None (no wound left).
- If l_wound > lx -> clamps to lx.
- Selects bottom boundary dofs with x in [x_left, x_right], where
x_left = x_center - l_wound/2 and x_right = x_center + l_wound/2.
"""
if x_center is None:
x_center = 0.5 * lx
# Clamp length and short-circuit if nothing left
L = float(np.clip(l_wound, 0.0, lx))
if L <= 0.0:
return None
x_left = x_center - 0.5 * L
x_right = x_center + 0.5 * L
# Pick boundary dofs on bottom (y≈y_bottom) & within [x_left, x_right]
def on_wound(x):
y_ok = np.isclose(x[1], y_bottom, atol=atol)
x_ok = np.logical_and(x[0] >= x_left, x[0] <= x_right)
return np.logical_and(y_ok, x_ok)
dofs = fem.locate_dofs_geometrical(V, on_wound)
if dofs.size == 0:
return None # no wound region (e.g., very small L wrt mesh)
# Constant Dirichlet value (fast + robust):
return fem.dirichletbc(PETSc.ScalarType(value), dofs, V)
l_wound = lx
bc_wound = build_wound_bc(V, l_wound, wound_concentration, lx)
xdmf = XDMFFile(V.mesh.comm, resultsDir+xdmf_filename, "w")
xdmf.write_mesh(V.mesh)
# Define initial condition
u_n = Function(V)
u_n.x.array[:] = 0.5
u_n.name = "u_n"
# Define temporal parameters
t = 0 # Start time
T = 1200 # Final time
num_steps = int(T/2)
dt = T / num_steps # time step size
D = default_scalar_type(2.6e-5)
u, v = TrialFunction(V), TestFunction(V)
dx = Measure("dx", domain=V.mesh, subdomain_data=subdomains)
uh = Function(V)
uh.name = "phi"
uh.interpolate(u_n)
xdmf.write_function(uh, t)
dt = T / num_steps # time step size
f = Constant(V.mesh, PETSc.ScalarType(0))
a = u * v * dx + dt * D * dot(grad(u), grad(v)) * dx
L = (u_n + dt * f) * v * dx
bcs = [bc_other, bc_drug] + ([bc_wound] if bc_wound is not None else [])
bilinear_form = form(a)
linear_form = form(L)
A = assemble_matrix(bilinear_form, bcs=bcs)
A.assemble()
b = create_vector(linear_form)
# Solve
print("\n- Transient problem is assembled.")
print("\n- Starting the solution..")
solver = PETSc.KSP().create(V.mesh.comm)
solver.setOperators(A)
solver.setType(PETSc.KSP.Type.PREONLY)
solver.getPC().setType(PETSc.PC.Type.LU)
for i in range(num_steps):
t += dt
# Update the right hand side reusing the initial vector
with b.localForm() as loc_b:
loc_b.set(0)
assemble_vector(b, linear_form)
# e.g., shrink every 30 steps (just an example):
if (i+1) % 1 == 0:
l_wound = max(0.0, l_wound - 0.01/6)
bc_wound = build_wound_bc(V, l_wound, wound_concentration, lx)
# Build list of BCs for this step
bcs = [bc_other, bc_drug] + ([bc_wound] if bc_wound is not None else [])
# Apply Dirichlet boundary condition to the vector
apply_lifting(b, [bilinear_form], [bcs])
b.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
set_bc(b, bcs)
# Solve linear problem
solver.solve(b, uh.x.petsc_vec)
uh.x.scatter_forward()
print("Iteration: ", i, "Time:", t)
# Update solution at previous time step (u_n)
u_n.x.array[:] = uh.x.array
# Write solution to file
xdmf.write_function(uh, t)
xdmf.close()
solution = uh
gives;
at t=32 and
at t=340 and
at the end, t=1200.
What I want is to do that shrinking with the solution of 1D diffusion.
What would you recommend me to handle this?