Transform dolfinx assemble matrix to numpy array

Hello everyone, I want to transform dolfinx assembled matrix to numpy array, then to calculate the eigenvalue problem using scipy. After searching on the internet, I found a method to do it. However it reports an error:

A = assemble_matrix(a, [bc])
B = assemble_matrix(b, [bc])
A.assemble()
C = A.convert('dense')
C.getDenseArray()

Error                                     Traceback (most recent call last)
<ipython-input-79-574e6642601e> in <module>
      1 A.assemble()
----> 2 C = A.convert('dense')

PETSc/Mat.pyx in petsc4py.PETSc.Mat.convert()

Error: error code 85
[0] MatConvert() line 4148 in /tmp/petsc-src/src/mat/interface/matrix.c
[0] MatConvert_SeqAIJ_SeqDense() line 224 in /tmp/petsc-src/src/mat/impls/dense/seq/dense.c
[0] MatSeqDenseSetPreallocation() line 2671 in /tmp/petsc-src/src/mat/impls/dense/seq/dense.c
[0] MatSeqDenseSetPreallocation_SeqDense() line 2694 in /tmp/petsc-src/src/mat/impls/dense/seq/dense.c
[0] PetscMallocA() line 422 in /tmp/petsc-src/src/sys/memory/mal.c
[0] PetscTrMallocDefault() line 164 in /tmp/petsc-src/src/sys/memory/mtr.c
[0] PetscMallocAlign() line 51 in /tmp/petsc-src/src/sys/memory/mal.c
[0] PetscMemzero() line 1669 in /tmp/petsc-src/include/petscsys.h
[0] Null argument, when expecting valid pointer
[0] Trying to zero at a null pointer

Can any one help me? Thank you.

You need to supply the full code, as the following works for me:

import dolfinx
import ufl
from ufl import ds, dx, grad, inner

mesh = dolfinx.UnitSquareMesh(dolfinx.MPI.comm_world, 10, 10)
V = dolfinx.FunctionSpace(mesh, ("Lagrange", 1))

u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
a = inner(grad(u), grad(v)) * dx
A = dolfinx.fem.assemble_matrix(a)
A.assemble()
B = A.convert("dense")
C = A.getDenseArray()
print(C)

Hi dokken, it looks like the mesh generated by gmsh caused the problem. However I don’t know why. Here is my mesh and some relevant code. Sorry to trouble you again. Since the mesh cannot be uploaded due to my slow internet, I upload the geo file insteady, you can generate the mesh from strip.geo.

strip.geo file:

//+
d_w = DefineNumber[ 0.0127, Name "Parameters/d_w" ];
//+
d_h = DefineNumber[ 1.27e-3, Name "Parameters/d_h" ];
//+
s_w = DefineNumber[ 1.27e-3, Name "Parameters/s_w" ];
//+
s_h = DefineNumber[ 1.27e-4, Name "Parameters/s_h" ];
//+
Point(1) = {0, 0, 0, 0.0005};
//+
Point(2) = {d_w, 0, 0, 0.0005};
//+
Point(3) = {d_w, d_h, 0, 0.0005};
//+
Point(4) = {d_w, d_w, 0, 0.0005};
//+
Point(5) = {0, d_w, 0, 0.0005};
//+
Point(6) = {0, d_h, 0, 0.0005};
//+
Point(7) = {d_w/2-s_w/2, d_h, 0, 0.00005};
//+
Point(8) = {d_w/2+s_w/2, d_h, 0, 0.00005};
//+
Point(9) = {d_w/2+s_w/2, d_h+s_h, 0, 0.00005};
//+
Point(10) = {d_w/2-s_w/2, d_h+s_h, 0, 0.00005};
//+
Line(1) = {1, 2};
//+
Line(2) = {2, 3};
//+
Line(3) = {3, 4};
//+
Line(4) = {4, 5};
//+
Line(5) = {5, 6};
//+
Line(6) = {6, 1};
//+
Line(7) = {6, 7};
//+
Line(8) = {7, 8};
//+
Line(9) = {8, 3};
//+
Line(10) = {8, 9};
//+
Line(11) = {9, 10};
//+
Line(12) = {10, 7};
//+
Curve Loop(1) = {5, 7, -12, -11, -10, 9, 3, 4};
//+
Plane Surface(1) = {1};
//+
Curve Loop(2) = {7, 8, 9, -2, -1, -6};
//+
Plane Surface(2) = {2};
//+
Physical Curve("PEC") = {5, 6, 1, 2, 3, 4, 11, 12, 8, 10};
//+
Physical Surface("Air") = {1};
//+
Physical Surface("Dielectric") = {2};

mesh->xdmf:

import pygmsh
import meshio
import numpy as np

filename = 'strip.msh'
mesh = meshio.read(
    filename,  # string, os.PathLike, or a buffer/open file
)
points, cells, cell_data, field_data = mesh.points, mesh.cells, mesh.cell_data, mesh.field_data

triangle_data = None
line_data = None
tri_cell_idx = []
line_cell_idx = []

for i, cell in enumerate(cells):
    if cell.type == 'triangle':
        print(i, 'triangle')
        tri_cell_idx.append(i)
        if triangle_data is None:
            triangle_data = cell.data
        else:
            triangle_data = np.vstack([triangle_data, cell.data])
    else:
        print(i, 'line')
        line_cell_idx.append(i)
        if line_data is None:
            line_data = cell.data
        else:
            line_data = np.vstack([line_data, cell.data])


meshio.xdmf.write('tag_triangle.xdmf', meshio.Mesh(
    points=points,
    cells={'triangle': triangle_data},
    cell_data={
        'triangle': [np.concatenate([cell_data['gmsh:physical'][i] for i in tri_cell_idx])]
    },
    field_data=field_data
))
meshio.xdmf.write("tag_all.xdmf", meshio.Mesh(
    points=points,
    cells={"line": line_data},
    cell_data={
        "line": [np.concatenate([cell_data['gmsh:physical'][i] for i in line_cell_idx])]
    }
))

xdmf->dolfinx:

import numpy as np
import pygmsh
import meshio
from dolfinx.io import XDMFFile
from dolfinx import (MPI, FacetNormal, Function, FunctionSpace, 
                     has_petsc_complex, DirichletBC, cpp)
from dolfinx.fem.assemble import assemble_scalar, assemble_matrix
from dolfinx.fem import locate_dofs_topological
from ufl import (TestFunctions, TrialFunctions, grad, inner, curl, split,
                  Measure, FiniteElement, dx)
import dolfinx
from scipy.constants import c

with XDMFFile(MPI.comm_world, "tag_triangle.xdmf") as xdmf_infile:
    mesh = xdmf_infile.read_mesh(cpp.mesh.GhostMode.none)
    mvc_subdomain = xdmf_infile.read_mvc_size_t(mesh)
    # tag_info = xdmf_infile.read_information_int()

with XDMFFile(MPI.comm_world, "tag_all.xdmf") as xdmf_infile:
    mvc_boundaries = xdmf_infile.read_mvc_size_t(mesh)

mf_triangle = cpp.mesh.MeshFunctionSizet(mesh, mvc_subdomain, 0)
mf_line = cpp.mesh.MeshFunctionSizet(mesh, mvc_boundaries, 0)
V = FiniteElement('N1curl', mesh.ufl_cell(), 2)
Q = FiniteElement('P', mesh.ufl_cell(), 3)
W = FunctionSpace(mesh, V*Q)
S = FunctionSpace(mesh, ('DG', 0))
e_r = Function(S)
x = S.tabulate_dof_coordinates()

for i in range(x.shape[0]):
    if mf_triangle.values[i] == 2:
        e_r.vector.setValueLocal(i, 8.875)
    else:
        e_r.vector.setValueLocal(i, 1)
u, p = TrialFunctions(W)
v, q = TestFunctions(W)

f = 10e9
k0_sqr = (2*np.pi*f / c)**2

electric_wall = Function(W)

with electric_wall.vector.localForm() as bc_local:
    bc_local.set(0)

bndry_facets = np.where(mf_line.values == 1)[0]
bdofs = locate_dofs_topological(W, 1, bndry_facets)
bc= DirichletBC(electric_wall, bdofs)

a = inner(curl(u), curl(v))*dx - k0_sqr*e_r*inner(u, v)*dx 
b = inner(grad(p), v)*dx + inner(u, v)*dx + inner(grad(p), grad(q))*dx + inner(u, grad(q))*dx - k0_sqr*e_r*inner(p, q)*dx
A = assemble_matrix(a, [bc])
B = assemble_matrix(b, [bc])
A.assemble()
C = A.convert('dense')

I cannot reproduce your error with the latest dolfinx docker image:

docker pull dolfinx/real
docker run -ti -v $(pwd):/home/fenics/shared -w /home/fenics/shared/ --rm dolfinx/real

hi dokken, where is the newest dolfinx. I tried to open the link you gave me in a post and cannot open it now. Did you use the dolfinx without complex number supporting? I’m confused by the docker name with suffix “complex” or “real”.

I ran the real version of Dolfin. The newest docker images can be found at:
https://hub.docker.com/u/dolfinx
I get the same problem when trying to run your code with dolfinx/complex.

@wwshunan I’ve been able to reduce your example to a way smaller code:

import numpy as np
import pygmsh
import meshio
from dolfinx.io import XDMFFile
from dolfinx import (MPI, FunctionSpace,
                     cpp, UnitSquareMesh)
from dolfinx.fem.assemble import assemble_matrix
from dolfinx.fem import locate_dofs_topological
from ufl import (TestFunctions, TrialFunctions, inner,
                 FiniteElement, dx, TrialFunction, TestFunction)
import dolfinx

mesh = UnitSquareMesh(MPI.comm_world, 35, 35)
V = FiniteElement('N1curl', mesh.ufl_cell(), 2)
Q = FiniteElement('P', mesh.ufl_cell(), 3)
W = FunctionSpace(mesh, V*Q)

u,p = TrialFunctions(W)
v,q = TestFunctions(W)
a =  inner(u, v)*dx + inner(p, q)*dx
A = assemble_matrix(a)
A.assemble()
print(W.dim())
C = A.convert('dense')
print(C.getDenseArray())

This works code runs for a 30 x 30 UnitSquareMesh, but not a 35x35 UnitSquareMesh, indicating that there are memory issues when converting large systems (The matrix will be ~40 000x40 000). Which is a crazy large matrix to store in python.

Thank you, dokken. Is it then impossible to transform the matrix to numpy array in case dolfinx now don’t provide SLEPcEigenSolver?

The PETScEigen solver is still in dolfinx. It is just not exposed to the Python layer through pybind. This is something that you could do yourself, or make an issue on the dolfinx issue tracker and hope that someone does it for you.

dokken, thank you. It’s a bit hard for me to bind to python and you said dolfinx/real have no problem. I’ll try it.

It’s probably the dense mode that’s killing you. You can convert to a sparse CSR matrix using something like

A = assemble_matrix(a, [bc])
A.assemble()
assert isinstance(A, PETSc.Mat)
ai, aj, av = A.getValuesCSR()
Asp = csr_matrix((av, aj, ai))

I also found the silimar solution on the internet, but not aware of what it means. Thank you.

CSR means compressed-sparse-row. So it’s just storing the column index, row index and the value. I’m sure you can find some scipy examples online that will help with your understanding if required.

OK, I see. :grin: :grin: :grin:

Hi everyone, I generated the mesh using the gmsh software tool and used meshio to create the xdmf files. We can see the generated physical boundaries are not correct. What’s the problem in the transform file? Thank you.

You Need to supply us with a Minimal Working Example (MWE) for anyone to answer this question.

Dear dokken, sorry for not pointing out the example was at the top of this post. I’ll repeat the code with problem for convenience.

gmsh geo file:

//+
d_w = DefineNumber[ 0.0127, Name "Parameters/d_w" ];
//+
d_h = DefineNumber[ 1.27e-3, Name "Parameters/d_h" ];
//+
s_w = DefineNumber[ 1.27e-3, Name "Parameters/s_w" ];
//+
s_h = DefineNumber[ 1.27e-4, Name "Parameters/s_h" ];
//+
Point(1) = {0, 0, 0, 0.0005};
//+
Point(2) = {d_w, 0, 0, 0.0005};
//+
Point(3) = {d_w, d_h, 0, 0.0005};
//+
Point(4) = {d_w, d_w, 0, 0.0005};
//+
Point(5) = {0, d_w, 0, 0.0005};
//+
Point(6) = {0, d_h, 0, 0.0005};
//+
Point(7) = {d_w/2-s_w/2, d_h, 0, 0.00005};
//+
Point(8) = {d_w/2+s_w/2, d_h, 0, 0.00005};
//+
Point(9) = {d_w/2+s_w/2, d_h+s_h, 0, 0.00005};
//+
Point(10) = {d_w/2-s_w/2, d_h+s_h, 0, 0.00005};
//+
Line(1) = {1, 2};
//+
Line(2) = {2, 3};
//+
Line(3) = {3, 4};
//+
Line(4) = {4, 5};
//+
Line(5) = {5, 6};
//+
Line(6) = {6, 1};
//+
Line(7) = {6, 7};
//+
Line(8) = {7, 8};
//+
Line(9) = {8, 3};
//+
Line(10) = {8, 9};
//+
Line(11) = {9, 10};
//+
Line(12) = {10, 7};
//+
Curve Loop(1) = {5, 7, -12, -11, -10, 9, 3, 4};
//+
Plane Surface(1) = {1};
//+
Curve Loop(2) = {7, 8, 9, -2, -1, -6};
//+
Plane Surface(2) = {2};
//+
Physical Curve("PEC") = {5, 6, 1, 2, 3, 4, 11, 12, 8, 10};
//+
Physical Surface("Air") = {1};
//+
Physical Surface("Dielectric") = {2};

geo mesh to xdmf:

import pygmsh
import meshio
import numpy as np

filename = 'strip.msh'
mesh = meshio.read(
    filename,  # string, os.PathLike, or a buffer/open file
)
points, cells, cell_data, field_data = mesh.points, mesh.cells, mesh.cell_data, mesh.field_data

triangle_data = None
line_data = None
tri_cell_idx = []
line_cell_idx = []

for i, cell in enumerate(cells):
    if cell.type == 'triangle':
        print(i, 'triangle')
        tri_cell_idx.append(i)
        if triangle_data is None:
            triangle_data = cell.data
        else:
            triangle_data = np.vstack([triangle_data, cell.data])
    else:
        print(i, 'line')
        line_cell_idx.append(i)
        if line_data is None:
            line_data = cell.data
        else:
            line_data = np.vstack([line_data, cell.data])


meshio.xdmf.write('tag_triangle.xdmf', meshio.Mesh(
    points=points,
    cells={'triangle': triangle_data},
    cell_data={
        'triangle': [np.concatenate([cell_data['gmsh:physical'][i] for i in tri_cell_idx])]
    },
    field_data=field_data
))
meshio.xdmf.write("tag_all.xdmf", meshio.Mesh(
    points=points,
    cells={"line": line_data},
    cell_data={
        "line": [np.concatenate([cell_data['gmsh:physical'][i] for i in line_cell_idx])]
    }
))

@wwshunan I do not understand what you mean by saying the physical boundaries are not correct? I’ve opened the mesh in its geo format, and the xdmf format matches the output of the geo format. The triangular mesh consists of two markers, indicating each domain, while you only have one physical tag for the whole boundary.

dokken, did you use the option “XDMF Reader” of paraview 5.8.0 to open the tag_triangle.xdmf file? I used the generated mesh to solve the eigenvalue problem and got the incorrect eigenvalues. Then I created the geometry with the pygmsh module point by point and finally generated the mesh using its generate_mesh function. Everything work. However it’s not convenient to create the geometry by code.

I am using Paraview 5.7 and can use either “XDMF Reader” or “XDMF3ReaderS” to visualize the mesh.
It is also not clear to me what your problem when visualizing the tag_triangle.xdmf is, as you have not explained that in detail.

You need to be able to make a minimal example illustrating that something is wrong. As you have a point by point geometry, you should be able to compare the two meshes and look for differences.
This can be done by using meshio.xdmf.write("filename.xdmf", mesh, data_format="XML"). Then you can compare to similar meshes in plaintext (use coarse meshes).