Systems ======= Numerous dynamic systems are implemented in Underworld. They may be found within the ``systems`` submodule. For specific information on the different system classes, the user is directed to the API reference available at the Underworld documentation site: http://underworld2.readthedocs.io/ We will consider here the basic workflow for creating and configuring an Underworld numerical system. The general process is as follows: 1. Create your mesh. 2. Create any required field(s) on the mesh (such as a temperature field). 3. Create any required boundary condition objects. 4. Create function objects to define any required physical quantities. 5. Create system. 6. Create solver and solve system. Note that for systems that are solved many times (for perhaps time-stepping), you will generally only create the system (and solver) once, and then solve it numerous times. Boundary Conditions ------------------- Boundary conditions form part of your known information for the system you are creating. In Underworld, these *knowns* are either of the Dirichlet (fixed value) or Neumann (fixed gradient) type, and are applied on a per node **and** per degree of freedom (DOF) basis. Note also that the entire domain boundary must be piecemeal either Dirichlet **or** Neumann type, but no section of boundary can be both Dirichlet and Neumann. Sections of the boundary that do not have any BCs explicitly set by the user will implicitly be of Neumann type (with zero gradient). To give this discussion a mathematical grounding, let’s consider the heat equation. The strong form of the boundary value problem is .. math:: \begin{align} q_i =& - \alpha \, u_{,i} & \\ q_{i,i} =& \: f & \text{ in } \Omega \\ u =& \: g & \text{ on } \Gamma_g \\ -q_i n_i =& \: h & \text{ on } \Gamma_h \\ \end{align} where, :math:`\alpha` is the diffusivity, :math:`u` is the temperature, :math:`f` is a source term, :math:`g` is the Dirichlet condition, and :math:`h` is a Neumann condition. The problem boundary, :math:`\Gamma`, admits the decomposition :math:`\Gamma=\Gamma_g\cup\Gamma_h` where :math:`\emptyset=\Gamma_g\cap\Gamma_h`. The Dirichlet Condition ~~~~~~~~~~~~~~~~~~~~~~~ The Dirichlet condition part of the problem specification requires that our solution temperature :math:`u` is equal to the user specified function :math:`g` across the user specified boundary :math:`\Gamma_g`. In Underworld, the Dirichlet boundary :math:`\Gamma_g` is specified through the use of ``IndexSet`` objects. These objects simply contain lists of mesh nodes (or rather the node indices) which the user wishes to flag as boundary nodes. A number of commonly used ``IndexSet`` objects are provided directly through the mesh object’s ``specialSets`` dictionary: .. raw:: html
import underworld as uw
mesh = uw.mesh.FeMesh_Cartesian()
mesh.specialSets.keys()
.. parsed-literal::
['MaxI_VertexSet',
'Top_VertexSet',
'Left_VertexSet',
'MinI_VertexSet',
'AllWalls_VertexSet',
'Bottom_VertexSet',
'Right_VertexSet',
'MinJ_VertexSet',
'MaxJ_VertexSet',
'Empty']
``IndexSet`` objects are discussed further in the mesh section of this
user guide. Importantly, note that the ``specialSets`` objects are
simply provided for convenience, and the user is indeed free to create
any set they wish. As an example, we can easily reproduce a set provided
by the ``specialSets`` dictionary by explicitly adding the required
indices:
.. raw:: html
import underworld as uw
import numpy as np
mesh = uw.mesh.FeMesh_Cartesian()
# first option, use provided convenience set.
leftwall = mesh.specialSets['Left_VertexSet']
# second option, create empty set and add required indices.
leftwall_handcrafted = mesh.specialSets['Empty']
for index,coord in enumerate(mesh.data):
# grab only left wall indices
if coord[0]<0.01:
leftwall_handcrafted.add(index)
# confirm sets are identical
if len(leftwall) != len(leftwall_handcrafted):
raise RuntimeError("Sets should have identical size!")
if not np.allclose(leftwall,leftwall_handcrafted):
raise RuntimeError("Sets should contain identical values!")
For the Dirichlet condition, the user specified function :math:`g` which
provides the required values along the Dirichlet boundary is implicitly
set by directly modifying the solution ``MeshVariable`` at the required
nodes. Note that you can set or modify your required :math:`g` function
at **any** stage before the solve operation.
Scalar Systems
^^^^^^^^^^^^^^
Let’s put all these pieces together to generate a basic heat equation
solution. For this system, we’ll set the bottom boundary to have a
temperature of :math:`u=373`, and the top boundary to have a temperature
:math:`u=273`. We won’t set anything for the side walls, but note that
this will implicitly yield a zero temperature gradient (ie heat flux)
Neumann condition.
.. raw:: html
import underworld as uw
import underworld.visualisation as vis
# 1. Create a mesh.
mesh = uw.mesh.FeMesh_Cartesian(maxCoord=(2.,1.))
# 2. Create the required field(s).
tempField = mesh.add_variable(1)
# 3. Create required boundary condition object.
# 3.a First create the required IndexSet objects to specify the required boundary.
# Note shorthand addition operation.
botSet = mesh.specialSets['Bottom_VertexSet']
topSet = mesh.specialSets[ 'Top_VertexSet']
totSet = botSet + topSet
# 3.b Now we can create the Dirichlet condition object.
condition = uw.conditions.DirichletCondition(tempField,totSet)
# 3.c Let's set the g function now by directly modifying the temperatureField object.
# Note that this operation can be done at any stage!
# Note also the shorthand we use to set the required values.
tempField.data[botSet] = 373.
tempField.data[topSet] = 273.
# 4. Let's go ahead and set a simple diffusivity.
alpha = 1.
# 5. Create the required system.
heatSys = uw.systems.SteadyStateHeat(temperatureField=tempField,
fn_diffusivity=alpha,
conditions=condition)
# 6. Create solver and solve.
solver = uw.systems.Solver(heatSys)
solver.solve()
# vis.
fig = vis.Figure(figsize=(800,400))
fig.append(vis.objects.Surface(mesh, tempField, colours="cubelaw2"))
fig.show()
.. raw:: html
import underworld as uw
import underworld.visualisation as vis
res=32
mesh = uw.mesh.FeMesh_Cartesian(elementRes=(res,res),minCoord=(-0.5,-0.5), maxCoord=(0.5,0.5))
tempField = mesh.add_variable(1)
# create bc sets. first, allwalls.
allWallSet = mesh.specialSets['AllWalls_VertexSet']
# now grab central patch
centralSet = mesh.specialSets['Empty']
for index,coord in enumerate(mesh.data):
if max(coord*coord)<0.03:
centralSet.add(index)
condition = uw.conditions.DirichletCondition(tempField,centralSet+allWallSet)
tempField.data[allWallSet] = 0.
tempField.data[centralSet] = 1.
# for fun, let's also set a random non-bc node to a random value.
# the value will be replaced at solve time by the solver.
tempField.data[res+2] = -999999.
heatSys = uw.systems.SteadyStateHeat(temperatureField=tempField,
fn_diffusivity=1.,
conditions=condition)
solver = uw.systems.Solver(heatSys)
solver.solve()
# grab a copy of solved data for later use, and then let's
# flip the config and re-solve immediately
tempField_orig = tempField.copy(deepcopy=True)
tempField.data[allWallSet] = 1.
tempField.data[centralSet] = 0.
solver.solve()
# vis
fig1 = vis.Figure(figsize=(400,400), title="original")
fig1.append(vis.objects.Surface(mesh, tempField_orig, colours="ocean", colourBar=False, onMesh=False))
fig2 = vis.Figure(figsize=(400,400), title="flipped")
fig2.append(vis.objects.Surface(mesh, tempField , colours="ocean", colourBar=False, onMesh=False))
vis.Figure.show_grid([fig1,fig2])
.. raw:: html
import underworld as uw
import underworld.visualisation as vis
# 1. Create a mesh.
mesh = uw.mesh.FeMesh_Cartesian(elementRes=(32,32))
# 2. Create the required field(s).
velField = mesh.add_variable(2)
preField = mesh.subMesh.add_variable(1)
# 3. Create required boundary condition object.
allWalls = mesh.specialSets['AllWalls_VertexSet']
# Note that we now pass in a tuple of IndexSet objects, representing
# the nodes to flag for (V_x, V_y). For no-slip, we flag both DOFs for
# each node, so therefore the index sets must be identical.
# Note also that we generally do not set any conditions on the pressure
# unknowns.
condition = uw.conditions.DirichletCondition(velField, (allWalls,allWalls) )
# Init the velocity field to zero everywhere. Therefore g=(0,0).
velField.data[:] = (0.,0.)
# 4. Let's create a simple function for the viscosity & bodyforce.
viscosity = 1.
coord = uw.function.coord()
bodyforce = (0.,-coord[0])
# 5. Create the required system.
stokesSys = uw.systems.Stokes(velocityField=velField,
pressureField=preField,
fn_viscosity=viscosity,
fn_bodyforce=bodyforce,
conditions=condition )
# 6. Create solver and solve
solver = uw.systems.Solver(stokesSys)
solver.solve()
# Grab copy of solution.
noslipvel = velField.copy(deepcopy=True)
# As before, we can modify g at any point.
# Create a mobile lid config:
topWall = mesh.specialSets['Top_VertexSet']
velField.data[topWall] = (1.,0.)
# also drop body force
stokesSys.fn_bodyforce = (0.,0.)
solver.solve()
# vis
fignoslip = vis.Figure(figsize=(400,400), title="no slip")
fignoslip.append(vis.objects.VectorArrows(mesh, noslipvel))
fignoslip.append(vis.objects.Surface(mesh, uw.function.math.dot(noslipvel,noslipvel), colours="isorainbow"))
figtraction = vis.Figure(figsize=(400,400), title="mobile lid")
figtraction.append(vis.objects.VectorArrows(mesh, velField))
figtraction.append(vis.objects.Surface(mesh, uw.function.math.dot(velField,velField), colours="isorainbow"))
vis.Figure.show_grid([fignoslip,figtraction])
.. raw:: html
import underworld as uw
import underworld.visualisation as vis
# 1. Create a mesh.
mesh = uw.mesh.FeMesh_Cartesian(elementRes=(32,32))
# 2. Create the required field(s).
velField = mesh.add_variable(2)
preField = mesh.subMesh.add_variable(1)
# 3. Create required boundary condition object.
leftrightWalls = mesh.specialSets['Left_VertexSet'] + mesh.specialSets['Right_VertexSet']
topbottWalls = mesh.specialSets[ 'Top_VertexSet'] + mesh.specialSets['Bottom_VertexSet']
condition = uw.conditions.DirichletCondition(velField, (leftrightWalls,topbottWalls) )
# Init the velocity field to zero everywhere.
velField.data[:] = (0.,0.)
# 4. Let's create a simple function for the viscosity & bodyforce.
viscosity = 1.
coord = uw.function.coord()
bodyforce = (0.,-coord[0])
# 5. Create the required system.
stokesSys = uw.systems.Stokes(velocityField=velField,
pressureField=preField,
fn_viscosity=viscosity,
fn_bodyforce=bodyforce,
conditions=condition )
# 6. Create solver and solve
solver = uw.systems.Solver(stokesSys)
solver.solve()
# Grab copy of solution.
noslipvel = velField.copy(deepcopy=True)
# As before, we can modify g at any point.
# Create inflow/outflow condition
for index in leftrightWalls:
if mesh.data[index][1] < 0.3:
velField.data[index] = (1.,0.)
# ramp up body force
stokesSys.fn_bodyforce = 100.*stokesSys.fn_bodyforce
solver.solve()
# vis
figfreeslip = vis.Figure(figsize=(400,400), title="free slip")
figfreeslip.append(vis.objects.VectorArrows(mesh, noslipvel))
figfreeslip.append(vis.objects.Surface(mesh, uw.function.math.dot(noslipvel,noslipvel), colours="gebco"))
figinflow = vis.Figure(figsize=(400,400), title="free slip with in/out flow")
figinflow.append(vis.objects.VectorArrows(mesh, velField))
figinflow.append(vis.objects.Surface(mesh, uw.function.math.dot(velField,velField), colours="gebco"))
vis.Figure.show_grid([figfreeslip,figinflow])
.. raw:: html
import underworld as uw
import underworld.visualisation as vis
# 1. Create a mesh.
mesh = uw.mesh.FeMesh_Cartesian(elementRes=(32,32))
# 2. Create the required field(s).
tempField = mesh.add_variable(1)
# 3.a Create the Dirichlet Condition
botSet = mesh.specialSets['Bottom_VertexSet']
topSet = mesh.specialSets[ 'Top_VertexSet']
totSet = botSet + topSet
cond_dirichlet = uw.conditions.DirichletCondition(tempField,totSet)
tempField.data[botSet] = 373.
tempField.data[topSet] = 273.
# 3.b Create the Neumann condition
sidewalls = mesh.specialSets['Left_VertexSet'] + mesh.specialSets['Right_VertexSet']
# Create this constant value function explicitly so we can
# modify it later.
flux = uw.function.Function.convert(0.)
cond_neumann = uw.conditions.NeumannCondition(tempField,sidewalls,flux)
# 4. Diffusivity
alpha = 1.
# 5. Create the required system.
heatSys = uw.systems.SteadyStateHeat(temperatureField=tempField,
fn_diffusivity=alpha,
conditions=[cond_dirichlet,cond_neumann])
# 6. Create solver and solve.
solver = uw.systems.Solver(heatSys)
solver.solve()
# grab copy
nofluxTemp = tempField.copy(deepcopy=True)
# now set flux and re-solve
flux.value = -100.
solver.solve()
# vis.
fig_noflux = vis.Figure(figsize=(400,400), title="No Lateral Flux")
fig_noflux.append(vis.objects.Surface(mesh, nofluxTemp, colours="cubelaw2"))
fig_flux = vis.Figure(figsize=(400,400), title="Lateral Flux")
fig_flux.append(vis.objects.Surface(mesh, tempField, colours="cubelaw2"))
vis.Figure.show_grid([fig_noflux,fig_flux])
.. raw:: html
import underworld as uw
import underworld.visualisation as vis
# 1. Create a mesh.
mesh = uw.mesh.FeMesh_Cartesian(elementRes=(32,32))
# 2. Create the required field(s).
tempField = mesh.add_variable(1)
# 3.a Create the Dirichlet Condition
botSet = mesh.specialSets['Bottom_VertexSet']
topSet = mesh.specialSets[ 'Top_VertexSet']
totSet = botSet + topSet
cond_dirichlet = uw.conditions.DirichletCondition(tempField,totSet)
tempField.data[botSet] = 1.
tempField.data[topSet] = 0.
# 4. Diffusivity
alpha = uw.function.math.exp(tempField)
# 5. Create the required system.
heatSys = uw.systems.SteadyStateHeat(temperatureField=tempField,
fn_diffusivity=alpha,
conditions=[cond_dirichlet])
# 6. Create solver and solve.
solver = uw.systems.Solver(heatSys)
# THIS WILL NOT WORK!
# solver.solve()
# THIS WILL
solver.solve(nonLinearIterate=True)
# Use a callback
rms_temp = uw.utils.Integral(tempField*tempField,mesh)
import numpy as np
def mycallback():
print("Average temp is {}".format(np.sqrt(rms_temp.evaluate())))
# reset temp field first for demo
tempField.data[:] = 0.
tempField.data[botSet] = 1.
solver.solve(nonLinearIterate=True, callback_post_solve=mycallback)
.. parsed-literal::
Average temp is [ 0.58463673]
Average temp is [ 0.64936446]
Average temp is [ 0.6476043]
Average temp is [ 0.6476043]