Dune-Functions 2.11
Loading...
Searching...
No Matches
poisson-mfem.py

This is the raw source code of the poisson-mfem.py example. There is also a commented version of this example in the Examples section.

1# SPDX-FileCopyrightText: Copyright © DUNE Project contributors, see file AUTHORS.md
2# SPDX-License-Identifier: LicenseRef-GPL-2.0-only-with-DUNE-exception OR LGPL-3.0-or-later
3
4#! [imports]
5import numpy
6import scipy.sparse.linalg
7from scipy.sparse import lil_matrix
8
9import dune.geometry
10import dune.grid as grid
11import dune.functions as functions
12#! [imports]
13
14# Compute the stiffness matrix for a single element
15#! [getLocalMatrix]
16def getLocalMatrix(localView):
17
18 # Get the grid element from the local FE basis view
19 element = localView.element()
20
21 # Make dense element stiffness matrix
22 n = len(localView) # Number of local degrees of freedom (gradient + value)
23 elementMatrix = numpy.zeros((n,n))
24
25 # Get set of shape functions for this element
26 fluxLocalFiniteElement = localView.tree().child(0).finiteElement()
27 pressureLocalFiniteElement = localView.tree().child(1).finiteElement()
28
29 # The actual shape functions on the reference element
30 localFluxBasis = fluxLocalFiniteElement.localBasis
31 localPressureBasis = pressureLocalFiniteElement.localBasis
32
33 nFlux = len(fluxLocalFiniteElement)
34 nPressure = len(pressureLocalFiniteElement)
35
36 # Get a quadrature rule
37 fluxOrder = element.mydimension * fluxLocalFiniteElement.localBasis.order
38 pressureOrder = element.mydimension * pressureLocalFiniteElement.localBasis.order
39 quadOrder = numpy.max((2*fluxOrder, (fluxOrder-1)+pressureOrder))
40
41 quadRule = dune.geometry.quadratureRule(element.type, quadOrder)
42
43 # Loop over all quadrature points
44 for quadPoint in quadRule:
45
46 # Position of the current quadrature point in the reference element
47 quadPos = quadPoint.position
48
49 # The inverse Jacobian of the map from the reference element to the element
50 geometryJacobianInverse = element.geometry.jacobianInverse(quadPos)
51
52 # The multiplicative factor in the integral transformation formula
53 integrationElement = element.geometry.integrationElement(quadPos)
54
55 # --------------------------------------------------------------
56 # Shape functions - gradient variable
57 # --------------------------------------------------------------
58
59 # Values of the gradient variable shape functions on the current element
60 fluxValues = localFluxBasis.evaluateFunction(quadPos)
61
62 # Gradients of the gradient variable shape functions on the reference element
63 fluxReferenceJacobians = localFluxBasis.evaluateJacobian(quadPos)
64
65 fluxDivergence = numpy.zeros(nFlux)
66
67 # Domain transformation of Jacobians and computation of div = trace(Jacobian)
68 # TODO: Extend the Dune Python interface to allow to do this without casting to numpy.array
69 for i in range(nFlux):
70 fluxDivergence[i] = numpy.trace(numpy.array(fluxReferenceJacobians[i]) * geometryJacobianInverse)
71
72 # --------------------------------------------------------------
73 # Shape functions - value variable
74 # --------------------------------------------------------------
75
76 # Values of the pressure shape functions
77 pressureValues = localPressureBasis.evaluateFunction(quadPos)
78
79 # --------------------------------------------------------------
80 # Gradient-variable--gradient-variable coupling
81 # --------------------------------------------------------------
82
83 for i in range(nFlux):
84
85 row = localView.tree().child(0).localIndex(i)
86
87 for j in range(nFlux):
88
89 col = localView.tree().child(0).localIndex(j)
90
91 # Compute the actual matrix entry contribution
92 elementMatrix[row,col] += numpy.dot(fluxValues[i], fluxValues[j]) * quadPoint.weight * integrationElement
93
94 # --------------------------------------------------------------
95 # Flux--pressure coupling
96 # --------------------------------------------------------------
97
98 for i in range(nFlux):
99
100 fluxIndex = localView.tree().child(0).localIndex(i)
101
102 for j in range(nPressure):
103
104 pressureIndex = localView.tree().child(1).localIndex(j)
105
106 # Matrix entry contribution
107 tmp = (fluxDivergence[i] * pressureValues[j][0]) * quadPoint.weight * integrationElement
108
109 # Add to both off-diagonal block matrices
110 elementMatrix[fluxIndex, pressureIndex] += tmp
111 elementMatrix[pressureIndex, fluxIndex] += tmp
112
113 return elementMatrix
114#! [getLocalMatrix]
115
116
117# Compute the right-hand side for a single element
118#! [getLocalVolumeTerm]
119def getVolumeTerm(localView, localVolumeTerm):
120
121 # Get the grid element from the local FE basis view
122 element = localView.element()
123
124 # Construct an empty vector of the correct size
125 localRhs = numpy.zeros(len(localView))
126
127 # Get set of shape functions for this element
128 # Only the pressure part has a non-zero right-hand side
129 pressureLocalFiniteElement = localView.tree().child(1).finiteElement()
130
131 # A quadrature rule
132 quadOrder = 2*element.mydimension*pressureLocalFiniteElement.localBasis.order
133 quadRule = dune.geometry.quadratureRule(element.type, quadOrder)
134
135 nPressure = len(pressureLocalFiniteElement)
136
137 # Loop over all quadrature points
138 for quadPoint in quadRule:
139
140 # Position of the current quadrature point in the reference element
141 quadPos = quadPoint.position
142
143 # The multiplicative factor in the integral transformation formula
144 integrationElement = element.geometry.integrationElement(quadPos)
145
146 # Evaluate the load density at the quadrature point
147 functionValue = localVolumeTerm(quadPos)
148
149 # Evaluate all shape function values at this point
150 pressureValues = pressureLocalFiniteElement.localBasis.evaluateFunction(quadPos)
151
152 # Actually compute the vector entries
153 for j in range(nPressure):
154 pressureIndex = localView.tree().child(1).localIndex(j)
155 localRhs[pressureIndex] += - pressureValues[j][0] * functionValue * quadPoint.weight * integrationElement
156
157 return localRhs
158#! [getLocalVolumeTerm]
159
160
161# Assemble the stiffness matrix for the mixed Poisson problem with the given basis
162#! [assembleMixedPoissonMatrix]
163def assembleMixedPoissonMatrix(basis):
164
165 # Total number of DOFs (both FE spaces together)
166 n = len(basis)
167
168 # Make an empty stiffness matrix
169 stiffnessMatrix = lil_matrix( (n,n) )
170
171 # A view on the composite FE basis on a single element
172 localView = basis.localView()
173
174 # A loop over all elements of the grid
175 for element in basis.gridView.elements:
176
177 # Bind the local basis view to the current element
178 localView.bind(element)
179
180 # Assemble the element stiffness matrix
181 elementMatrix = getLocalMatrix(localView)
182
183 # Add element stiffness matrix onto the global stiffness matrix
184 for i in range(len(localView)):
185
186 # The global index of the i-th local degree of freedom of the element
187 row = localView.index(i)[0] # The index is an array of length 1
188
189 for j in range(len(localView)):
190
191 # The global index of the j-th local degree of freedom of the element
192 col = localView.index(j)[0]
193 stiffnessMatrix[row,col] += elementMatrix[i, j];
194
195 # Transform the stiffness matrix to CSR format, and return it
196 return stiffnessMatrix.tocsr()
197#! [assembleMixedPoissonMatrix]
198
199
200# Assemble the right-hand side vector of the mixed Poisson problem with the given basis
201#! [assembleMixedPoissonRhs]
202def assembleMixedPoissonRhs(basis, volumeTerm):
203
204 # Get the basis for the pressure variable
205 pressureBasis = functions.subspaceBasis(basis, 1)
206
207 # Represent the volume term function as a FE function in the pressure space
208 volumeTermCoeff = numpy.zeros(len(basis))
209 pressureBasis.interpolate(volumeTermCoeff, volumeTerm)
210 volumeTermGF = pressureBasis.asFunction(volumeTermCoeff)
211
212 # A view on a single element
213 localVolumeTerm = volumeTermGF.localFunction()
214
215 # Create empty vector of correct size
216 b = numpy.zeros(len(basis))
217
218 # A view on the FE basis on a single element
219 localView = basis.localView()
220
221 # A loop over all elements of the grid
222 for element in basis.gridView.elements:
223
224 # Bind the local FE basis view to the current element
225 localView.bind(element)
226
227 # Get the local contribution to the right-hand side vector
228 localVolumeTerm.bind(element)
229 localRhs = getVolumeTerm(localView, localVolumeTerm)
230
231 # Add the local vector to the global one
232 for i in range(len(localRhs)):
233 # The global index of the i-th degree of freedom of the element
234 row = localView.index(i)[0] # The index is an array of length 1.
235 b[row] += localRhs[i]
236
237 return b
238#! [assembleMixedPoissonRhs]
239
240
241# Mark all DOFs associated to entities for which # the boundary intersections center
242# is marked by the given indicator functions.
243#
244# This method simply calls the corresponding C++ code. A more Pythonic solution
245# is planned to appear eventually...
246#! [markBoundaryDOFsByIndicator]
247def markBoundaryDOFsByIndicator(basis, vector, indicator):
248 from io import StringIO
249 code="""
250 #include<utility>
251 #include<functional>
252 #include<dune/common/fvector.hh>
253 #include<dune/functions/functionspacebases/boundarydofs.hh>
254 template<class Basis, class Vector, class Indicator>
255 void run(const Basis& basis, Vector& vector, const Indicator& indicator)
256 {
257 auto vectorBackend = vector.mutable_unchecked();
258 Dune::Functions::forEachBoundaryDOF(basis, [&] (auto&& localIndex, const auto& localView, const auto& intersection) {
259 if (indicator(intersection.geometry().center()).template cast<bool>())
260 vectorBackend[localView.index(localIndex)] = true;
261 });
262 }
263 """
264 dune.generator.algorithm.run("run",StringIO(code), basis, vector, indicator)
265#! [markBoundaryDOFsByIndicator]
266
267
268# This incorporates essential constraints into matrix # and rhs of a linear system.
269# The mask vector isConstrained # indicates which DOFs should be constrained,
270# x contains the desired values of these DOFs. Other entries of x # are ignored.
271# Note that this implements the symmetrized approach to modify the matrix.
272#! [incorporateEssentialConstraints]
273def incorporateEssentialConstraints(A, b, isConstrained, x):
274 b -= A*(x*isConstrained)
275 rows, cols = A.nonzero()
276 for i,j in zip(rows, cols):
277 if isConstrained[i] or isConstrained[j]:
278 A[i,j] = 0
279 for i in range(len(b)):
280 if isConstrained[i]:
281 A[i,i] = 1
282 b[i] = x[i]
283#! [incorporateEssentialConstraints]
284
285
286
287
288
289#! [createGrid]
290upperRight = [1, 1] # Upper right corner of the domain
291elements = [50, 50] # Number of grid elements per direction
292
293# Create a grid of the unit square
294gridView = grid.structuredGrid([0,0],upperRight,elements)
295#! [createGrid]
296
297# Construct a pair of finite element space bases
298# Note: In contrast to the corresponding C++ example we are using a single matrix with scalar entries,
299# and plain numbers to index it (no multi-digit multi-indices).
300#! [createBasis]
301basis = functions.defaultGlobalBasis(gridView,
302 functions.Composite(functions.RaviartThomas(order=0),
303 functions.Lagrange(order=0)))
304
305fluxBasis = functions.subspaceBasis(basis, 0);
306pressureBasis = functions.subspaceBasis(basis, 1);
307#! [createBasis]
308
309#! [assembly]
310# Compute the stiffness matrix
311stiffnessMatrix = assembleMixedPoissonMatrix(basis)
312
313# Compute the load vector
314volumeTerm = lambda x : 2
315
316b = assembleMixedPoissonRhs(basis, volumeTerm)
317#! [assembly]
318
319#! [dirichletDOFs]
320# This marks the top and bottom boundary of the domain
321fluxDirichletIndicator = lambda x : 1.*((x[1] > upperRight[1] - 1e-8) or (x[1] < 1e-8))
322
323isDirichlet = numpy.zeros(len(basis))
324
325# Mark all DOFs located in a boundary intersection marked
326# by the fluxDirichletIndicator function.
327markBoundaryDOFsByIndicator(fluxBasis, isDirichlet, fluxDirichletIndicator);
328#! [dirichletDOFs]
329
330
336
337#! [dirichletValues]
338normal = lambda x : numpy.array([0.,1.]) if numpy.abs(x[1]-1) < 1e-8 else numpy.array([0., -1.])
339fluxDirichletValues = lambda x : numpy.sin(2.*numpy.pi*x[0]) * normal(x)
340
341# TODO: This should be constrained to boundary DOFs
342fluxDirichletCoefficients = numpy.zeros(len(basis))
343fluxBasis.interpolate(fluxDirichletCoefficients, fluxDirichletValues);
344#! [dirichletValues]
345
346# //////////////////////////////////////////
347# Modify Dirichlet rows
348# //////////////////////////////////////////
349#! [dirichletIntegration]
350incorporateEssentialConstraints(stiffnessMatrix, b, isDirichlet, fluxDirichletCoefficients)
351#! [dirichletIntegration]
352
353# //////////////////////////
354# Compute solution
355# //////////////////////////
356#! [solving]
357x = scipy.sparse.linalg.spsolve(stiffnessMatrix, b)
358#! [solving]
359
360# ////////////////////////////////////////////////////////////////////////////////////////////
361# Write result to VTK file
362# ////////////////////////////////////////////////////////////////////////////////////////////
363
364#! [vtkWriting]
365# TODO: Improve file writing. Currently this simply projects everything
366# onto a first-order Lagrange space
367vtkWriter = gridView.vtkWriter(subsampling=2)
368
369fluxFunction = fluxBasis.asFunction(x)
370fluxFunction.addToVTKWriter("flux", vtkWriter, grid.DataType.PointVector)
371
372pressureFunction = pressureBasis.asFunction(x)
373pressureFunction.addToVTKWriter("pressure", vtkWriter, grid.DataType.PointData)
374
375vtkWriter.write("poisson-mfem-result")
376#! [vtkWriting]