1.8 Exploring the mesh topology

In this tutorial, we learn how to iterate over mesh entities and obtain information about how one mesh entity is connected to others, e.g.: - what edges are connected to a vertex? - what are the edges of an element? - what are points of an element? - what elements are adjacent to a face?

In [1]:
import netgen.gui
%gui tk
from ngsolve import *
from netgen.csg import unit_cube
In [2]:
mesh = Mesh(unit_cube.GenerateMesh(maxh=1))

Iterating over mesh objects

Iterating over vertices:

In [3]:
for v in mesh.vertices:
    print (v, v.point)
V0 (0.0, 0.0, 0.0)
V1 (0.0, 0.0, 1.0)
V2 (1.0, 0.0, 0.0)
V3 (0.0, 1.0, 0.0)
V4 (1.0, 0.0, 1.0)
V5 (0.0, 1.0, 1.0)
V6 (1.0, 1.0, 0.0)
V7 (1.0, 1.0, 1.0)
V8 (0.500000000001051, 0.5000000000013137, 0.5000000000010585)

What is the type of v in mesh.vertices?

In [4]:
for v in mesh.vertices:
    print(type(v))
<class 'ngsolve.comp.MeshNode'>
<class 'ngsolve.comp.MeshNode'>
<class 'ngsolve.comp.MeshNode'>
<class 'ngsolve.comp.MeshNode'>
<class 'ngsolve.comp.MeshNode'>
<class 'ngsolve.comp.MeshNode'>
<class 'ngsolve.comp.MeshNode'>
<class 'ngsolve.comp.MeshNode'>
<class 'ngsolve.comp.MeshNode'>

We’ll shortly return to this MeshNode class.

Iterating over elements:

In [5]:
for el in mesh.Elements(VOL):
    print(type(el))
    print ("vertices: ", el.vertices)   # get vertices of an element
    print ("edges: ", el.edges)         # get edges of an element
<class 'ngsolve.comp.Ngs_Element'>
vertices:  (V4, V5, V8, V7)
edges:  (E19, E21, E25, E17, E20, E22)
<class 'ngsolve.comp.Ngs_Element'>
vertices:  (V0, V1, V8, V2)
edges:  (E1, E6, E12, E0, E5, E9)
<class 'ngsolve.comp.Ngs_Element'>
vertices:  (V1, V2, V4, V8)
edges:  (E9, E12, E20, E6, E7, E10)
<class 'ngsolve.comp.Ngs_Element'>
vertices:  (V0, V3, V8, V5)
edges:  (E3, E13, E22, E2, E5, E16)
<class 'ngsolve.comp.Ngs_Element'>
vertices:  (V1, V4, V5, V8)
edges:  (E9, E20, E22, E7, E8, E17)
<class 'ngsolve.comp.Ngs_Element'>
vertices:  (V0, V2, V8, V6)
edges:  (E4, E11, E24, E1, E5, E12)
<class 'ngsolve.comp.Ngs_Element'>
vertices:  (V0, V3, V6, V8)
edges:  (E5, E16, E24, E2, E4, E14)
<class 'ngsolve.comp.Ngs_Element'>
vertices:  (V0, V1, V5, V8)
edges:  (E5, E9, E22, E0, E3, E8)
<class 'ngsolve.comp.Ngs_Element'>
vertices:  (V2, V4, V8, V6)
edges:  (E11, E18, E24, E10, E12, E20)
<class 'ngsolve.comp.Ngs_Element'>
vertices:  (V4, V6, V7, V8)
edges:  (E20, E24, E25, E18, E19, E23)
<class 'ngsolve.comp.Ngs_Element'>
vertices:  (V3, V6, V8, V7)
edges:  (E15, E23, E25, E14, E16, E24)
<class 'ngsolve.comp.Ngs_Element'>
vertices:  (V3, V5, V7, V8)
edges:  (E16, E22, E25, E13, E15, E21)

NodeId and MeshNodes

An object of type NodeId in NGSolve is just a number together with a type of the mesh entity it describes.

In [6]:
v = NodeId(VERTEX,0)
type(v)
print ("type = ", v.type, "v.nr =", v.nr)
type =  NODE_TYPE.VERTEX v.nr = 0
In [7]:
help(v)
Help on NodeId in module ngsolve.comp object:

class NodeId(pybind11_builtins.pybind11_object)
 |  an node identifier containing node type and node nr
 |
 |  Method resolution order:
 |      NodeId
 |      pybind11_builtins.pybind11_object
 |      builtins.object
 |
 |  Methods defined here:
 |
 |  __eq__(...)
 |      __eq__(self: ngsolve.comp.NodeId, arg0: ngsolve.comp.NodeId) -> bool
 |
 |  __hash__(...)
 |      __hash__(self: ngsolve.comp.NodeId) -> int
 |
 |  __init__(...)
 |      __init__(self: ngsolve.comp.NodeId, arg0: ngsolve.fem.NODE_TYPE, arg1: int) -> None
 |
 |  __ne__(...)
 |      __ne__(self: ngsolve.comp.NodeId, arg0: ngsolve.comp.NodeId) -> bool
 |
 |  __repr__(...)
 |      __repr__(self: ngsolve.comp.NodeId) -> str
 |
 |  __str__(...)
 |      __str__(self: ngsolve.comp.NodeId) -> str
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  nr
 |      the node number
 |
 |  type
 |      the node type
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from pybind11_builtins.pybind11_object:
 |
 |  __new__(*args, **kwargs) from pybind11_builtins.pybind11_type
 |      Create and return a new object.  See help(type) for accurate signature.

NODE_TYPE can be one of the following:

  • VERTEX: dimension 0
  • EDGE: dimension 1
  • FACE: dimension 2
  • CELL: dimension 3
  • ELEMENT: codimension 0
  • FACET: codimension 1

E.g., in \(n\) space dimensions, facets are mesh objects of dimension \(n-1\). When \(n=3\) there are no CELL entities.

Nodes can be associated to existing mesh. Consider the above-defined node

v = NodeId(VERTEX,0)

It can be associated to mesh. Then it becomes an object of type MeshNode which has coordinate information.

In [8]:
meshv = mesh[v]
print ("type = ", type(meshv))
print ("point = ", meshv.point)
type =  <class 'ngsolve.comp.MeshNode'>
point =  (0.0, 0.0, 0.0)
In [9]:
type(v), type(meshv)   # note the differnt types
Out[9]:
(ngsolve.comp.NodeId, ngsolve.comp.MeshNode)
In [10]:
help (meshv)
Help on MeshNode in module ngsolve.comp object:

class MeshNode(NodeId)
 |  a node within a mesh
 |
 |  Method resolution order:
 |      MeshNode
 |      NodeId
 |      pybind11_builtins.pybind11_object
 |      builtins.object
 |
 |  Methods defined here:
 |
 |  __init__(self, /, *args, **kwargs)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  edges
 |      tuple of global edge numbers
 |
 |  elements
 |      tuple of global element-ids
 |
 |  faces
 |      tuple of global face numbers
 |
 |  point
 |      vertex coordinates
 |
 |  vertices
 |      tuple of global vertex numbers
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from NodeId:
 |
 |  __eq__(...)
 |      __eq__(self: ngsolve.comp.NodeId, arg0: ngsolve.comp.NodeId) -> bool
 |
 |  __hash__(...)
 |      __hash__(self: ngsolve.comp.NodeId) -> int
 |
 |  __ne__(...)
 |      __ne__(self: ngsolve.comp.NodeId, arg0: ngsolve.comp.NodeId) -> bool
 |
 |  __repr__(...)
 |      __repr__(self: ngsolve.comp.NodeId) -> str
 |
 |  __str__(...)
 |      __str__(self: ngsolve.comp.NodeId) -> str
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from NodeId:
 |
 |  nr
 |      the node number
 |
 |  type
 |      the node type
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from pybind11_builtins.pybind11_object:
 |
 |  __new__(*args, **kwargs) from pybind11_builtins.pybind11_type
 |      Create and return a new object.  See help(type) for accurate signature.

MeshNode objects like meshv can be queried for topology information.

E.g., what are edges connected to the mesh vertex meshv?

In [11]:
meshv.edges
Out[11]:
(E0, E1, E2, E3, E4, E5, E6, E8, E9, E11, E12, E13, E14, E16, E22, E24)
In [12]:
c = NodeId(CELL, 1)
meshc = mesh[c]
meshc.faces          # faces of a cell
Out[12]:
(F11, F4, F0, F2)
In [13]:
f = NodeId(FACE, 2)
meshf = mesh[f]
meshf.edges, meshf.elements
Out[13]:
((E0, E9, E5),
 (<ngsolve.comp.ElementId at 0x7fe08e27c7a0>,
  <ngsolve.comp.ElementId at 0x7fe08e27c998>))

ElementId and Ngs_Element

An ElementId is made by an object giving the codimension like BND or VOL, together with a number.

In [14]:
ei = ElementId(BND,0)
type(ei)
Out[14]:
ngsolve.comp.ElementId

As with NodeId, we may associate ElementId with a mesh to get an object of type Ngs_Element, which can be queried for topology information.

In [15]:
meshel = mesh[ei]
type(meshel)
print ("type meshel = ", meshel)
print ("vertices =", meshel.vertices)
type meshel =  <ngsolve.comp.Ngs_Element object at 0x7fe08e27c110>
vertices = (V0, V1, V5)

Note that meshel has only three vertices because its a boundary element. Volume elements have the same type as boundary elements.

In [16]:
eiv = ElementId(VOL,0)
meshelv = mesh[eiv]
type(meshelv)
Out[16]:
ngsolve.comp.Ngs_Element
In [17]:
meshelv.vertices, meshelv.edges, meshelv.facets
Out[17]:
((V4, V5, V8, V7), (E19, E21, E25, E17, E20, E22), (F28, F27, F23, F24))

Dofs

Dofs are numbers enumerating the global degrees of freedom of a finite element space. Dofs are associated to mesh entities of the previously described types.

E.g., all dofs of the Lagrange finite element space associated to edges of the mesh can be obtained as follows.

In [18]:
fes = H1(mesh, order=4)
for edge in mesh.edges:
    print ("type = ", type(edge))
    print ("dofs = ", fes.GetDofNrs(edge))
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (9, 10, 11)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (12, 13, 14)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (15, 16, 17)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (18, 19, 20)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (21, 22, 23)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (24, 25, 26)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (27, 28, 29)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (30, 31, 32)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (33, 34, 35)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (36, 37, 38)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (39, 40, 41)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (42, 43, 44)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (45, 46, 47)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (48, 49, 50)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (51, 52, 53)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (54, 55, 56)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (57, 58, 59)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (60, 61, 62)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (63, 64, 65)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (66, 67, 68)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (69, 70, 71)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (72, 73, 74)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (75, 76, 77)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (78, 79, 80)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (81, 82, 83)
type =  <class 'ngsolve.comp.MeshNode'>
dofs =  (84, 85, 86)

From the output, its evident that these edge dofs are associated to MeshNode objects.

What about dofs associated to elements?

In [19]:
for el in fes.Elements(VOL):
    print(type(el))
    print (el.dofs)
<class 'ngsolve.comp.FESpaceElement'>
[4, 5, 8, 7, 66, 67, 68, 72, 73, 74, 84, 85, 86, 60, 61, 62, 69, 70, 71, 75, 76, 77, 171, 172, 173, 168, 169, 170, 156, 157, 158, 159, 160, 161, 177]
<class 'ngsolve.comp.FESpaceElement'>
[0, 1, 8, 2, 12, 13, 14, 27, 28, 29, 45, 46, 47, 9, 10, 11, 24, 25, 26, 36, 37, 38, 120, 121, 122, 99, 100, 101, 87, 88, 89, 93, 94, 95, 178]
<class 'ngsolve.comp.FESpaceElement'>
[1, 2, 4, 8, 36, 37, 38, 45, 46, 47, 69, 70, 71, 27, 28, 29, 30, 31, 32, 39, 40, 41, 135, 136, 137, 126, 127, 128, 120, 121, 122, 117, 118, 119, 179]
<class 'ngsolve.comp.FESpaceElement'>
[0, 3, 8, 5, 18, 19, 20, 48, 49, 50, 75, 76, 77, 15, 16, 17, 24, 25, 26, 57, 58, 59, 144, 145, 146, 111, 112, 113, 102, 103, 104, 108, 109, 110, 180]
<class 'ngsolve.comp.FESpaceElement'>
[1, 4, 5, 8, 36, 37, 38, 69, 70, 71, 75, 76, 77, 30, 31, 32, 33, 34, 35, 60, 61, 62, 159, 160, 161, 129, 130, 131, 126, 127, 128, 123, 124, 125, 181]
<class 'ngsolve.comp.FESpaceElement'>
[0, 2, 8, 6, 21, 22, 23, 42, 43, 44, 81, 82, 83, 12, 13, 14, 24, 25, 26, 45, 46, 47, 138, 139, 140, 114, 115, 116, 96, 97, 98, 99, 100, 101, 182]
<class 'ngsolve.comp.FESpaceElement'>
[0, 3, 6, 8, 24, 25, 26, 57, 58, 59, 81, 82, 83, 15, 16, 17, 21, 22, 23, 51, 52, 53, 150, 151, 152, 114, 115, 116, 108, 109, 110, 105, 106, 107, 183]
<class 'ngsolve.comp.FESpaceElement'>
[0, 1, 5, 8, 24, 25, 26, 36, 37, 38, 75, 76, 77, 9, 10, 11, 18, 19, 20, 33, 34, 35, 129, 130, 131, 111, 112, 113, 93, 94, 95, 90, 91, 92, 184]
<class 'ngsolve.comp.FESpaceElement'>
[2, 4, 8, 6, 42, 43, 44, 63, 64, 65, 81, 82, 83, 39, 40, 41, 45, 46, 47, 69, 70, 71, 165, 166, 167, 138, 139, 140, 132, 133, 134, 135, 136, 137, 185]
<class 'ngsolve.comp.FESpaceElement'>
[4, 6, 7, 8, 69, 70, 71, 81, 82, 83, 84, 85, 86, 63, 64, 65, 66, 67, 68, 78, 79, 80, 174, 175, 176, 168, 169, 170, 165, 166, 167, 162, 163, 164, 186]
<class 'ngsolve.comp.FESpaceElement'>
[3, 6, 8, 7, 54, 55, 56, 78, 79, 80, 84, 85, 86, 51, 52, 53, 57, 58, 59, 81, 82, 83, 174, 175, 176, 153, 154, 155, 147, 148, 149, 150, 151, 152, 187]
<class 'ngsolve.comp.FESpaceElement'>
[3, 5, 7, 8, 57, 58, 59, 75, 76, 77, 84, 85, 86, 48, 49, 50, 54, 55, 56, 72, 73, 74, 171, 172, 173, 153, 154, 155, 144, 145, 146, 141, 142, 143, 188]

The type ngsolve.comp.FESpaceElement appearing in the output is derived from Ngs_Element as can be seen from the documentation:

In [20]:
from ngsolve.comp import FESpaceElement
help(FESpaceElement)
Help on class FESpaceElement in module ngsolve.comp:

class FESpaceElement(Ngs_Element)
 |  Method resolution order:
 |      FESpaceElement
 |      Ngs_Element
 |      pybind11_builtins.pybind11_object
 |      builtins.object
 |
 |  Methods defined here:
 |
 |  GetFE(...)
 |      GetFE(self: ngsolve.comp.FESpaceElement) -> ngsolve.fem.FiniteElement
 |
 |      the finite element containing shape functions
 |
 |  GetTrafo(...)
 |      GetTrafo(self: ngsolve.comp.FESpaceElement) -> ngsolve.fem.ElementTransformation
 |
 |      the transformation from reference element to physical element
 |
 |  __init__(self, /, *args, **kwargs)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  dofs
 |      degrees of freedom of element
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from Ngs_Element:
 |
 |  VB(...)
 |      VB(self: ngsolve.comp.Ngs_Element) -> ngsolve.comp.VorB
 |
 |      VorB of element
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from Ngs_Element:
 |
 |  edges
 |      tuple of global edge numbers
 |
 |  faces
 |      tuple of global face numbers
 |
 |  facets
 |      tuple of global face, edge or vertex numbers
 |
 |  index
 |      material or boundary condition index
 |
 |  mat
 |      material or boundary condition label
 |
 |  nr
 |      the element number
 |
 |  type
 |      geometric shape of element
 |
 |  vertices
 |      tuple of global vertex numbers
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from pybind11_builtins.pybind11_object:
 |
 |  __new__(*args, **kwargs) from pybind11_builtins.pybind11_type
 |      Create and return a new object.  See help(type) for accurate signature.

In later tutorial units, we will have ocasion to use what we learnt here (such as how to iterate over mesh entities) for purposes such as design of smoothing blocks.