I’m solving a fourth order problem with a mixed formulation approach, and although the computation runs without crashing, I am not able to visualise the result due to inconsistent shape of the solution with the mesh (ValueError: data length of (195770) != required length (19729)
).
The only thing I know is that when I solve a 2nd order problem without a mixed formulation but with similar code, it works.
Formulation
I need to solve the equation \left(\Delta^2 - \Delta + \kappa^2\right)u=0 on a disk domain \Omega with three circular holes which I call “channels”. The boundaries are noted \Gamma_i where i=0 on the external boundary and i=1,2,3 on the channels.
I use a two-fields formulation:
\begin{cases}
\sigma - \Delta u = 0 \text{ on } \Omega\\
\Delta \sigma - \sigma + \kappa^2u = 0 \text{ on } \Omega\\
u = u_i \text{ on } \Gamma_i\\
\sigma = 0 \text{ on } \Gamma_i\\
\end{cases}
The variational formulation is
\int_\Omega \left( -\nabla\sigma\nabla v - \sigma v + \kappa^2uv + \sigma\tau + \nabla u\nabla\tau \right)\mathrm{d}\Omega = 0
with (\tau,v) being the test functions.
Implementation
import numpy as np
import gmsh
from basix.ufl import element, mixed_element
from dolfinx.fem.petsc import LinearProblem
from mpi4py import MPI
import ufl
from dolfinx import fem, plot, default_scalar_type
from dolfinx.io import gmshio
from dolfinx.mesh import locate_entities, meshtags
import pyvista
from ufl import TrialFunctions, TestFunctions
domain_radius=30.0
channel_radius=0.500
channels_locations=[(-1.8, 0), (1.8, 0), (0, 1)]
boundary_conditions=[-0.5, -0.5, 1, ]
kappa=1.0
# Mesh #
gmsh.initialize()
gmsh.option.setNumber('General.Verbosity', 1)
## Domain
ext_boundary = gmsh.model.occ.addCircle(0, 0, 0, domain_radius)
ext_boundary_loop = gmsh.model.occ.addCurveLoop([ext_boundary])
## Channels
channels_loops = []
for (x, y) in channels_locations:
channel = gmsh.model.occ.addCircle(x, y, 0, channel_radius)
channels_loops.append(gmsh.model.occ.addCurveLoop([channel]))
membrane = gmsh.model.occ.addPlaneSurface([ext_boundary_loop] + channels_loops)
gmsh.model.occ.synchronize()
gdim = 2
gmsh.model.addPhysicalGroup(gdim, [membrane], 1)
## Mesh generation
gmsh.option.setNumber("Mesh.CharacteristicLengthMin", channel_radius/32)
gmsh.option.setNumber("Mesh.CharacteristicLengthMax", domain_radius/8)
gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 128)
gmsh.model.mesh.generate(gdim)
gmsh_model_rank = 0
mesh_comm = MPI.COMM_WORLD
mesh_model = gmshio.model_to_mesh(gmsh.model, mesh_comm, gmsh_model_rank, gdim=gdim)
gmsh.finalize()
domain = mesh_model[0]
# Boundaries #
def external_boundary_locator(x) -> bool:
return np.isclose(x[0]**2 + x[1]**2, domain_radius**2)
def channel_i_boundary_locator(x, i) -> bool:
return np.isclose((x[0] - channels_locations[i][0])**2 + (x[1] - channels_locations[i][1])**2, channel_radius**2)
def boundary_locator(x) -> bool:
return np.logical_or.reduce([external_boundary_locator(x)] + [channel_i_boundary_locator(x, i) for i in range(len(channels_locations))])
boundaries = list(enumerate([external_boundary_locator] + [lambda x, _i=i: channel_i_boundary_locator(x, _i) for i in range(len(channels_locations))]))
facet_indices, facet_markers = [], []
fdim = domain.topology.dim - 1
for (marker, locator) in boundaries:
facets = locate_entities(domain, fdim, locator)
facet_indices.append(facets)
facet_markers.append(np.full_like(facets, marker))
facet_indices = np.hstack(facet_indices).astype(np.int32)
facet_markers = np.hstack(facet_markers).astype(np.int32)
sorted_facets = np.argsort(facet_indices)
facet_tag = meshtags(domain, fdim, facet_indices[sorted_facets], facet_markers[sorted_facets])
# Solution #
k = 3
mixed_el = mixed_element([
element("Lagrange", domain.basix_cell(), k),
element("Lagrange", domain.basix_cell(), k - 2),
])
V = fem.functionspace(domain, mixed_el)
V_sigma, V_u = V.sub(0), V.sub(1)
V_sigma0, _ = V_sigma.collapse()
V_u0, _ = V_u.collapse()
## Dirichlet conditions
zero_constant = fem.Constant(domain, default_scalar_type(0))
### External boundary
_, locator_0 = boundaries[0]
dofs_dirichlet_sigma = fem.locate_dofs_geometrical((V_sigma, V_sigma0), boundary_locator)
dofs_dirichlet_u_0 = fem.locate_dofs_geometrical((V_u, V_u0), locator_0)
bcs_dirichlet = [
fem.dirichletbc(zero_constant, dofs_dirichlet_sigma[0], V_sigma),
fem.dirichletbc(zero_constant, dofs_dirichlet_u_0[0], V_u),
]
### Channels
for i, (marker, locator) in enumerate(boundaries[1:]):
dofs_dirichlet_u = fem.locate_dofs_geometrical((V_u, V_u0), locator)
value_dirichlet_u = fem.Constant(domain, default_scalar_type(boundary_conditions[i]))
bc_u = fem.dirichletbc(value_dirichlet_u, dofs_dirichlet_u[0], V_u)
bcs_dirichlet.append(bc_u)
## Variational formulation
(sigma, u) = TrialFunctions(V)
(tau, v) = TestFunctions(V)
a = (-ufl.dot(ufl.grad(sigma), ufl.grad(tau)) - sigma * tau + u * tau + sigma * v + ufl.dot(ufl.grad(u), ufl.grad(v))) * ufl.dx
L = zero_constant * v * ufl.dx
problem = LinearProblem(a, L, bcs=bcs_dirichlet, petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
wh = problem.solve()
_, uh = wh.split()
# Visualisation #
mesh = uh.function_space.mesh
cells, types, x = plot.vtk_mesh(mesh)
grid = pyvista.UnstructuredGrid(cells, types, x)
grid.point_data["u"] = uh.x.array.real
grid.set_active_scalars("u")
plotter = pyvista.Plotter()
plotter.add_mesh(grid, show_edges=True)
warped = grid.warp_by_scalar()
plotter.add_mesh(warped)
if pyvista.OFF_SCREEN:
pyvista.start_xvfb(wait=0.1)
plotter.screenshot("figures/uh_membrane.png")
else:
plotter.show()
Traceback
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[4], line 136
134 cells, types, x = plot.vtk_mesh(mesh)
135 grid = pyvista.UnstructuredGrid(cells, types, x)
--> 136 grid.point_data["u"] = uh.x.array.real
137 grid.set_active_scalars("u")
138 plotter = pyvista.Plotter()
File ~/miniforge3/envs/fenicsx-env/lib/python3.12/site-packages/pyvista/core/datasetattributes.py:227, in DataSetAttributes.__setitem__(self, key, value)
224 raise TypeError('Only strings are valid keys for DataSetAttributes.')
226 has_arr = key in self
--> 227 self.set_array(value, name=key)
229 # do not make array active if it already exists. This covers
230 # an inplace update like self.point_data[key] += 1
231 if has_arr:
File ~/miniforge3/envs/fenicsx-env/lib/python3.12/site-packages/pyvista/core/datasetattributes.py:579, in DataSetAttributes.set_array(self, data, name, deep_copy)
576 if not isinstance(name, str):
577 raise TypeError('`name` must be a string')
--> 579 vtk_arr = self._prepare_array(data, name, deep_copy)
580 self.VTKObject.AddArray(vtk_arr)
581 self.VTKObject.Modified()
File ~/miniforge3/envs/fenicsx-env/lib/python3.12/site-packages/pyvista/core/datasetattributes.py:737, in DataSetAttributes._prepare_array(self, data, name, deep_copy)
735 data = tmparray
736 if data.shape[0] != array_len:
--> 737 raise ValueError(f'data length of ({data.shape[0]}) != required length ({array_len})')
739 # attempt to reuse the existing pointer to underlying VTK data
740 if isinstance(data, pyvista_ndarray):
741 # pyvista_ndarray already contains the reference to the vtk object
742 # pyvista needs to use the copy of this object rather than wrapping
743 # the array (which leaves a C++ pointer uncollected.
ValueError: data length of (195770) != required length (19729)
Code that works with another equation:
import numpy as np
import gmsh
from basix.ufl import element, mixed_element
from dolfinx.fem.petsc import LinearProblem
from mpi4py import MPI
import ufl
from dolfinx import fem, plot, default_scalar_type
from dolfinx.io import gmshio
from dolfinx.mesh import locate_entities, meshtags
import pyvista
from ufl import TrialFunctions, TestFunctions
domain_radius=30.0
channel_radius=0.500
channels_locations=[(-1.8, 0), (1.8, 0), (0, 1)]
boundary_conditions=[-0.5, -0.5, 1, ]
# Mesh #
gmsh.initialize()
gmsh.option.setNumber('General.Verbosity', 1)
## Domain
ext_boundary = gmsh.model.occ.addCircle(0, 0, 0, domain_radius)
ext_boundary_loop = gmsh.model.occ.addCurveLoop([ext_boundary])
## Channels
channels_loops = []
for (x, y) in channels_locations:
channel = gmsh.model.occ.addCircle(x, y, 0, channel_radius)
channels_loops.append(gmsh.model.occ.addCurveLoop([channel]))
membrane = gmsh.model.occ.addPlaneSurface([ext_boundary_loop] + channels_loops)
gmsh.model.occ.synchronize()
gdim = 2
gmsh.model.addPhysicalGroup(gdim, [membrane], 1)
## Mesh generation
gmsh.option.setNumber("Mesh.CharacteristicLengthMin", channel_radius/32)
gmsh.option.setNumber("Mesh.CharacteristicLengthMax", domain_radius/8)
gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 128)
gmsh.model.mesh.generate(gdim)
gmsh_model_rank = 0
mesh_comm = MPI.COMM_WORLD
mesh_model = gmshio.model_to_mesh(gmsh.model, mesh_comm, gmsh_model_rank, gdim=gdim)
gmsh.finalize()
domain = mesh_model[0]
# Boundaries #
def external_boundary_locator(x) -> bool:
return np.isclose(x[0]**2 + x[1]**2, domain_radius**2)
def channel_i_boundary_locator(x, i) -> bool:
return np.isclose((x[0] - channels_locations[i][0])**2 + (x[1] - channels_locations[i][1])**2, channel_radius**2)
def boundary_locator(x) -> bool:
return np.logical_or.reduce([external_boundary_locator(x)] + [channel_i_boundary_locator(x, i) for i in range(len(channels_locations))])
boundaries = list(enumerate([external_boundary_locator] + [lambda x, _i=i: channel_i_boundary_locator(x, _i) for i in range(len(channels_locations))]))
facet_indices, facet_markers = [], []
fdim = domain.topology.dim - 1
for (marker, locator) in boundaries:
facets = locate_entities(domain, fdim, locator)
facet_indices.append(facets)
facet_markers.append(np.full_like(facets, marker))
facet_indices = np.hstack(facet_indices).astype(np.int32)
facet_markers = np.hstack(facet_markers).astype(np.int32)
sorted_facets = np.argsort(facet_indices)
facet_tag = meshtags(domain, fdim, facet_indices[sorted_facets], facet_markers[sorted_facets])
# Solution #
## Function space
V = fem.functionspace(domain, ("Lagrange", 1, ))
## Dirichlet conditions
bcs_dirichlet = []
for i, (marker, locator) in enumerate(boundaries[1:]):
dofs_dirichlet = fem.locate_dofs_geometrical(V, locator)
value_dirichlet = fem.Constant(domain, default_scalar_type(boundary_conditions[i]))
bc = fem.dirichletbc(value_dirichlet, dofs_dirichlet, V)
bcs_dirichlet.append(bc)
## Variational formulation
ds = ufl.Measure("ds", domain=domain, subdomain_data=facet_tag)
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
a = ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx + ufl.dot(u, v) * ds(0) + ufl.dot(u, v) * ufl.dx
L = fem.Constant(v.ufl_function_space().mesh, default_scalar_type(0)) * v * ufl.dx
problem = LinearProblem(a, L, bcs=bcs_dirichlet, petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
uh = problem.solve()
# Visualisation #
mesh = uh.function_space.mesh
cells, types, x = plot.vtk_mesh(mesh)
grid = pyvista.UnstructuredGrid(cells, types, x)
grid.point_data["u"] = uh.x.array.real
grid.set_active_scalars("u")
plotter = pyvista.Plotter()
plotter.add_mesh(grid, show_edges=True)
warped = grid.warp_by_scalar()
plotter.add_mesh(warped)
if pyvista.OFF_SCREEN:
pyvista.start_xvfb(wait=0.1)
plotter.screenshot("figures/uh_membrane.png")
else:
plotter.show()