Overview:
Keywords: checkpointing, utilities, volume integrals, surface integrals, xdmf
The Integral class constructs the volume integral
for some function $f_i$ (specified by a Function object), over some domain $V$ (specified by an FeMesh object), or the surface integral
for some surface $\Gamma$ (specified via an IndexSet object on the mesh).
# setup some objects for demonstration
import underworld as uw
from underworld import function as fn
import glucifer
import math
# setup required objects
mesh = uw.mesh.FeMesh_Cartesian(minCoord=(-1,-1), elementRes=(32,32))
temperatureField = mesh.add_variable( 1 )
velocityField = mesh.add_variable( 2 )
# init
temperatureField.data[:,0] = -mesh.data[:,1]/2. + 0.5
velocityField.data[:,0] = -mesh.data[:,1]
velocityField.data[:,1] = mesh.data[:,0]
# viz
fig1 = glucifer.Figure()
velmagfield = uw.function.math.sqrt( uw.function.math.dot( velocityField, velocityField ) )
fig1.append( glucifer.objects.VectorArrows(mesh, velocityField, arrowHead=0.2, scaling=0.1) )
fig1.append( glucifer.objects.Surface( mesh, temperatureField ) )
fig1.show()
In the following example, we will calculate a root mean square velocity defined as: $$ v_{rms} = \sqrt{ \frac{ \int_V (\mathbf{v}.\mathbf{v}) \, \mathrm{d}V } {\int_V \, \mathrm{d}V} } $$
The same result can be achieved through a number of paths.
integrate() method on a Function object.integrate() method on an FeMesh object.Integral class object, and call its integrate() method. Note that all three methods result in identical calculations.
# define required function
vdotv = fn.math.dot( velocityField, velocityField )
# evaluate area (domain) integrals on the function
# 2 methods available
v2sum_1 = vdotv.integrate( mesh ) # ... option 1
v2sum_2 = mesh.integrate( vdotv ) # ... option 2
volume = mesh.integrate( 1.0 )
# finally, calculate RMS
v_rms = math.sqrt( v2sum_1[0] )/volume[0]
print('Option 1 - RMS velocity = {0:.3f}'.format(v_rms))
v_rms = math.sqrt( v2sum_2[0] )/volume[0]
print('Option 2 - RMS velocity = {0:.3f}'.format(v_rms))
# option 3
# create integral objects, passing in functions
v2sum_integral = uw.utils.Integral( mesh=mesh, fn=vdotv )
volume_integral = uw.utils.Integral( mesh=mesh, fn=1. )
# evaluate integrals
v2sum = v2sum_integral.evaluate()
volume = volume_integral.evaluate()
# finally, calculate RMS
v_rms = math.sqrt( v2sum[0] )/volume[0]
print('RMS velocity = {0:.3f}'.format(v_rms))
To evaluate an integral over a subdomain the fn.branching.conditional class may be useful:
# create circle function
radius = 1.
coord = fn.coord()
fn_sphere = fn.math.dot( coord, coord ) < radius**2
# setup a function that is 1 if the coordinates, are inside the circle, and zero otherwise.
conditions = [ ( fn_sphere , 1.0),
( True , 0.0) ]
kernelFunction = fn.branching.conditional( conditions )
# create and evaluate integral
volume = mesh.integrate( kernelFunction )
print('Area from integral = {0:6.8e}'.format(volume[0]))
In the following example, we will calculate the Nusselt number defined by
$$ Nu = -h \frac{ \oint_{\Gamma_{t}} \partial_z T (\mathbf{x}) \, \mathrm{d}\Gamma}{ \int_{\Gamma_{b}} T (\mathbf{x}) \, \mathrm{d}\Gamma} $$where $h$ is the height of the domain, and $\Gamma_t$ and $\Gamma_b$ are the top and bottom surfaces respectively.
NB. Surface integrals must still be implemented using the old implementation mesh of creating a uw.utils.Integral object
nuTop = uw.utils.Integral( fn=temperatureField.fn_gradient[1],
mesh=mesh, integrationType='Surface',
surfaceIndexSet=mesh.specialSets["MaxJ_VertexSet"])
nuBottom = uw.utils.Integral( fn=temperatureField,
mesh=mesh, integrationType='Surface',
surfaceIndexSet=mesh.specialSets["MinJ_VertexSet"])
Once again we activate these integrals using the evaluate function.
nu = - nuTop.evaluate()[0]/nuBottom.evaluate()[0]
print('Nusselt number = {0:.6f}'.format(nu))
Checkpointing is the process of saving sufficient data to facilitate restarting your simulations at a later stage. Note that we do not provide explicit checkpointing functionality, but instead provide the tools required for the loading and saving of heavy data. Which data items are required for restart will depend on the systems you have used and how you have constructed your models.
The following Underworld data structures have load/save functionality:
SwarmVariables SwarmMeshVariablesMeshAll files are saved in HDF5 format.
Note: When saving a SwarmVariable, if you wish to reload the SwarmVariable data at a later stage, you must also save the Swarm for the corresponding state (generally, the same timestep). This requirement is due to the population control mechanisms swarms generally used for swarms, and also due to particles crossing process boundaries. When you come to reload the Swarm and SwarmVariable, you must load the Swarm first. Note again that the Swarm and SwarmVariable must be of corresponding state for successful reload.
outputPath = 'checkpointing/'
# Make output directory if necessary
import os
if uw.rank()==0:
if not os.path.exists(outputPath):
os.makedirs(outputPath)
SwarmVariable
Below a Swarm and a SwarmVariable, are created and saved to disk, then a new swarm loads the data from disk.
save() method on the Swarm and SwarmVariable objects.save() method. This is currently used for xdmf() operation, see below.load() method on the Swarm and SwarmVariable objectsswarm1 = uw.swarm.Swarm(mesh)
swarm1var = swarm1.add_variable(dataType='int', count=1)
layout = uw.swarm.layouts.GlobalSpaceFillerLayout(swarm1,particlesPerCell=5)
swarm1.populate_using_layout(layout)
# evaluate kernalFunction for each particle in swarm1, record result in swarmvar1
swarm1var.data[:] = kernelFunction.evaluate(swarm1.particleCoordinates.data)
fig = glucifer.Figure()
fig.append( glucifer.objects.Mesh(mesh) )
fig.append( glucifer.objects.Points(swarm1, fn_colour=swarm1var, pointSize=6.0 ) )
fig.show()
s1Hnd = swarm1.save(outputPath+'swarm.h5')
s1vHnd = swarm1var.save(outputPath+'swarmvar.h5')
# new swarm
swarm2 = uw.swarm.Swarm(mesh)
swarm2var = swarm2.add_variable(dataType='int', count=1)
swarm2.load(outputPath+'swarm.h5')
swarm2var.load(outputPath+'swarmvar.h5')
import numpy as np
print "Are the swarm variables close? ...", np.allclose( swarm2var.data[:], swarm1var.data[:] )
print "Are the swarm particle coordinates close? ... ", np.allclose( swarm2.particleCoordinates.data[:], swarm1.particleCoordinates.data[:])
MeshVariables
The MeshVariable object behaves similarly to SwarmVariable object with the save() and load() functionality.
mHnd = mesh.save(outputPath+'mesh.h5')
velHnd = velocityField.save(outputPath+'velocity.h5', mHnd) # 2nd arg, mHnd, is optional
newField = mesh.add_variable( nodeDofCount=velocityField.nodeDofCount )
newField.load(outputPath+'velocity.h5')
print "Are the mesh variables close ? ...", np.allclose( newField.data, velocityField.data )
The XDMF file format brings together data and geometric information in a format the can be viewed with ParaView.
The handlers that were returned after the save() operations above specify this information in hdf5 format.
MeshVariable and SwarmVariableMesh and SwarmThe handlers are passed to Underworld's XDMF methods, along with textual names to give the Variable in the .xdmf file.
velocityField.xdmf(outputPath+'velocity.xdmf', velHnd, "MyField", mHnd, "TheMesh", modeltime=0.0)
swarm1var.xdmf(outputPath+'swarmvar.xdmf', s1vHnd, "SwarmVariable", s1Hnd, "TheSwarm", modeltime=0.1)
Write XDMF file
For more details on using XDMF write and to see it in a dynamical simulation context, see the example 1_06_Rayleigh_Taylor.
if uw.rank() == 0:
import os
if os.path.exists(outputPath):
os.remove(outputPath+'mesh.h5')
os.remove(outputPath+'velocity.h5')
os.remove(outputPath+'swarm.h5')
os.remove(outputPath+'swarmvar.h5')
os.remove(outputPath+'velocity.xdmf')
os.remove(outputPath+'swarmvar.xdmf')
os.rmdir(outputPath)