Local damaged elastic material#
Initialize#
For parallel testing#
Create a server controlling n>1 MPI engines (consider OpenMPI as MPI backend)
#nopy
import ipyparallel as ipp
nw=2
cluster = ipp.Cluster(engines="mpi", n=nw)
rc = cluster.start_and_connect_sync()
view=rc[:]
/dolfinx-env/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
from .autonotebook import tqdm as notebook_tqdm
[stderr:0]
/dolfinx-env/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
from .autonotebook import tqdm as notebook_tqdm
[stderr:1]
/dolfinx-env/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
from .autonotebook import tqdm as notebook_tqdm
DOLFINx version: 0.11.0.post0 based on GIT commit: fefdb2201b80a8f59527de2d461b9056906661d8
Import TwoScale library#
%%px --local
from twoscale import core
from twoscale import linear
from twoscale import util
Use Twoscale dolfinx implementation
Use Twoscale dolfinx implementation
[stdout:0]
Use Twoscale dolfinx implementation
Use Twoscale dolfinx implementation
[stdout:1]
Use Twoscale dolfinx implementation
Use Twoscale dolfinx implementation
Basic test case#
2026-09-23 15:48:34.563 ( 1.009s) [ 7F8185B21140]vtkXOpenGLRenderWindow.:1450 WARN| bad X server connection. DISPLAY=
[stderr:0] 2026-09-23 15:48:35.011 ( 1.606s) [ 7F1801CA4140]vtkXOpenGLRenderWindow.:1450 WARN| bad X server connection. DISPLAY=
[output:0]
Choose a mesh refinement localization criterion#
finest mesh in an oblique band in the middle
%%px --local
delta=3*L/(ref*4*s)
def crit1(coords,level):
return np.logical_and(coords[0]<L*(coords[1]/B+s)/(2*s)+ref*delta/3,coords[0]>L*(coords[1]/B+s)/(2*s)-ref*delta/3)
Choose macro node to enrich#
select central region around x=L/2 +/- L/8
%%px --local
def enriched(coords):
return np.logical_and(coords[0]>=3*L/8, coords[0]<=5*L/8)
Scale jump construction#
%%px
j=core.topDown(domain,enriched,crit1,ref)
%%px --local
fdomain=j.getFineMesh
cdomain=j.getCoarseMesh
In red, requested enriched nodes. In orange, extra enriched node related to the mesh transition
[output:0]
Show patches#
Show master#
[output:0]
[stderr:1] 2026-09-23 15:48:36.978 ( 3.451s) [ 7F5BDBD78140]vtkXOpenGLRenderWindow.:1450 WARN| bad X server connection. DISPLAY=
[output:1]
Damage#
damage function: hat function with damage=1 on \(x=\frac{L}{2s}(\frac{y}{B}+s)\) and damage=0 on \(x=\frac{L}{2s}(\frac{y}{B}+s ) \pm \Delta\)
%%px --local
def dam(x):
res=np.zeros(len(x[0]))
t1=L*(x[1]/B+s)/(2*s)
planep=np.logical_and(np.greater(x[0],t1),np.less(x[0],t1+delta))
res[planep]=L*x[1][planep]/(2*delta*s*B)-x[0][planep]/delta+1+L/(2*delta)
planem=np.logical_and(np.less_equal(x[0],t1),np.greater(x[0],t1-delta))
res[planem]=-L*x[1][planem]/(2*delta*s*B)+x[0][planem]/delta+1-L/(2*delta)
return res
damage space and field
%%px --local
element_scal = basix.ufl.element("Lagrange", "triangle", 1)
space_scal = fem.functionspace(fdomain, element_scal)
space_scalc = fem.functionspace(cdomain, element_scal)
d=fem.Function(space_scal,name="d")
d.interpolate(dam)
dc=fem.Function(space_scalc,name="dc")
dc.interpolate(dam)
Linear problem directly on fine mesh#
Simple elasticity problem description#
Space#
%%px --local
space = fem.functionspace(fdomain, fdomain.ufl_domain().ufl_coordinate_element())
Lamé#
%%px --local
E=5.e+6
nu=0.3
mu=fem.Constant(fdomain,E / (2.0 * (1.0 + nu)))
lmbda = fem.Constant(fdomain,E * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)))
Formulation#
%%px --local
u = ufl.TrialFunction(space)
v = ufl.TestFunction(space)
%%px --local
def eps(v):
return 0.5*(ufl.grad(v) + ufl.grad(v).T)
%%px --local
def sigma(strain):
return (1-d)*(2.0*mu*strain + lmbda*ufl.tr(strain)*ufl.Identity(dim))
%%px --local
a_ufl=ufl.inner(sigma(eps(u)), eps(v)) * ufl.dx
f=fem.Function(space,name='load')
f.x.array[:]=0.
b_ufl=ufl.dot(f,v)*ufl.dx
Dirichlet#
%%px --local
if trac==True:
imp=0.01
imp2=-0.09
imp3=0.08
else:
imp=-0.01
imp2=-0.09
imp3=0.0
if D1d:
clamp=np.array([0.])
loading=np.array([imp])
else:
if D2d:
clamp=np.array([0.,0.])
loading=np.array([imp,imp2])
else:
clamp=np.array([0.,0.,0.])
loading=np.array([imp,imp2,imp3])
bc_nodes=mesh.locate_entities(fdomain, 0, lambda x: np.isclose(x[0], 0.))
bdofs=fem.locate_dofs_topological(space,0,bc_nodes)
bcb=fem.dirichletbc(clamp,bdofs,space)
imp_nodes=mesh.locate_entities(fdomain, 0, lambda x: np.isclose(x[0], L))
idofs=fem.locate_dofs_topological(space,0,imp_nodes)
bci=fem.dirichletbc(loading,idofs,space)
bcs=[bcb,bci]
Field#
%%px --local
disp=fem.Function(space,name='disp_fine')
Generate Multipoint constraint#
%%px --local
mpc=core.generateMPC(j,space)
Solve without mpc#
%%px --local
problem = petsc.LinearProblem(a_ufl, b_ufl,bcs=bcs,petsc_options=petsc_options,petsc_options_prefix="lpnmpc_")
%%px --local
disp = problem.solve()
Solve with mpc#
%%px --local
problemw = LinearProblem_mpc(a_ufl, b_ufl,mpc,bcs=bcs,petsc_options=petsc_options,petsc_options_prefix="lpmpc_")
%%px --local
dispw = problemw.solve()
plot displacement solution at fine scale with or without MPC#
[output:0]
Two-scale approach#
Use nested strategy
Enriched space#
%%px --local
el_mixed = basix.ufl.mixed_element([cdomain.ufl_domain().ufl_coordinate_element(), cdomain.ufl_domain().ufl_coordinate_element()])
coarse_enriched_space = fem.functionspace(cdomain, el_mixed)
std_=coarse_enriched_space.sub(0)
std, std_to_mix=std_.collapse()
enr_=coarse_enriched_space.sub(1)
enr, enr_to_mix=enr_.collapse()
std_to_mix_np=np.array(std_to_mix[0],dtype=int)
disp_ce=fem.Function(coarse_enriched_space,name='disp_coarse_enriched')
Solve coarse non enriched problem (standard part) to init two scale loop#
Formulation#
%%px --local
muc=fem.Constant(cdomain,E / (2.0 * (1.0 + nu)))
lmbdac = fem.Constant(cdomain,E * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)))
ucs = ufl.TrialFunction(std)
vcs = ufl.TestFunction(std)
def sigmac(strain):
return (1-dc)*(2.0*muc*strain + lmbdac*ufl.tr(strain)*ufl.Identity(dim))
acs=ufl.inner(sigmac(eps(ucs)), eps(vcs)) * ufl.dx
fcs=fem.Function(std,name='load')
fcs.x.array[:]=0.
bcst=ufl.dot(fcs,vcs)*ufl.dx
Boundary condition#
%%px
bc_nodesc=mesh.locate_entities(cdomain, 0, lambda x: np.isclose(x[0], 0.))
bdofscs=fem.locate_dofs_topological(std,0,bc_nodesc)
bcbcs=fem.dirichletbc(clamp,bdofscs,std)
imp_nodesc=mesh.locate_entities(cdomain, 0, lambda x: np.isclose(x[0], L))
idofscs=fem.locate_dofs_topological(std,0,imp_nodesc)
bcics=fem.dirichletbc(loading,idofscs,std)
bcscs=[bcbcs,bcics]
Resolution#
%%px
problemcs = petsc.LinearProblem(acs, bcst,bcs=bcscs,petsc_options=petsc_options,petsc_options_prefix="lptsc_")
dispcs = problemcs.solve()
[output:0]
Store coarse standard in coarse enriched field#
%%px
if nested_strategy:
disp_ce=dispcs
else:
disp_ce.x.array[std_to_mix_np]=dispcs.x.array
#print(disp_ce.x.array)
Twoscale loop#
Choose an enriched function#
%%px --local
enriched_shift=core.generateEnrichedShiftFunction(disp)
Resolution with linearBasicLoop (i.e. scale loop resolution)#
Dirichlet boundary condition must be defined on mixed space and merged into one Dirichlet BC object
%%px
if nested_strategy:
BC_ce=bcscs
else:
bc_nodesc=mesh.locate_entities(cdomain, 0, lambda x: np.isclose(x[0], 0.))
imp_nodesc=mesh.locate_entities(cdomain, 0, lambda x: np.isclose(x[0], L))
clamp_std_dofs=fem.locate_dofs_topological(std_,0,bc_nodesc)
all_BC_values=fem.Function(coarse_enriched_space)
imp_std_dof_x=fem.locate_dofs_topological(std.sub(0),0,imp_nodesc)
imp_std_dof_x_ce=std_to_mix_np[imp_std_dof_x]
if D1d:
all_BC_dof=np.concat((clamp_std_dofs,imp_std_dof_x_ce))
all_BC_values.x.array[imp_std_dof_x_ce]=imp
else:
if D2d:
imp_std_dof_y=fem.locate_dofs_topological(std.sub(1),0,imp_nodesc)
imp_std_dof_y_ce=std_to_mix_np[imp_std_dof_y]
all_BC_dof=np.concat((clamp_std_dofs,imp_std_dof_x_ce,imp_std_dof_y_ce))
all_BC_values.x.array[imp_std_dof_x_ce]=imp
all_BC_values.x.array[imp_std_dof_y_ce]=imp2
else:
imp_std_dof_y=fem.locate_dofs_topological(std.sub(1),0,imp_nodesc)
imp_std_dof_y_ce=std_to_mix_np[imp_std_dof_y]
imp_std_dof_z=fem.locate_dofs_topological(std.sub(2),0,imp_nodesc)
imp_std_dof_z_ce=std_to_mix_np[imp_std_dof_z]
all_BC_dof=np.concat((clamp_std_dofs,imp_std_dof_x_ce,imp_std_dof_y_ce,imp_std_dof_z_ce))
all_BC_values.x.array[imp_std_dof_x_ce]=imp
all_BC_values.x.array[imp_std_dof_y_ce]=imp2
all_BC_values.x.array[imp_std_dof_z_ce]=imp3
all_BC_dof.sort()
BC_ce=[fem.dirichletbc(all_BC_values,all_BC_dof)]
Systems are created and TS resolution is done:
%%px
#--local
[A,AD,BND,BD]=util.createFineScaleSytems(a_ufl,b_ufl,bcs=bcs,MPC=mpc)
[dispf, r,nm, it,hrb,hrr]=linear.linearBasicLoop(j,dispw.function_space,A,AD,BND,BD,enriched_shift,disp_ce,mpc,BC_ce,itmax,epsr)
Solutions with direct solver and TS solver are plotted bellow with their differences
[output:0]
[output:0]
The following residual evolution histories are plotted below:
TS residual fine system relative to rhs \(\frac{|AD.S_{ts_i}-BD|}{|BD|}\)
TS residual fine system relative to first residual \(\frac{|AD.S_{ts_i}-BD|}{|AD.S_{ts_0}-BD|}\)
TS relative solution difference evolution \(\frac{|S_{ts_i}-S_{ts_{i-1}})|}{|S_{ts_0}|}\)
Text(0, 0.5, 'Criterion')
The TS solver solution iterations are:
Text(0, 0.5, 'Criterion')