1.c Coordinate Geometry¶
Quagmire mesh variables are numerically differentiable and the various operators defined in the quagmire functions module also support limited (symbolic) differentiation
This is to allow the construction of more complicated operators that are equivalent on the flat plane and the surface of the spehre.
This functionality is supported through the quagmire.function.geometry
package.
import numpy as np
from quagmire.function import display
from quagmire import QuagMesh
from quagmire import tools as meshtools
from quagmire import function as fn
from quagmire.function import display
from mpi4py import MPI
# import lavavu
import stripy
comm = MPI.COMM_WORLD
import matplotlib.pyplot as plt
from matplotlib import cm
%matplotlib inline
Naked coordinate systems¶
Quagmire supports flat 2D meshes and logically 2D meshes on the surface of a sphere. When a mesh is defined, it attaches a geometry to itself which is the starting point for computing any differential operators that include mesh variables. It is also possible to access the coordinate system definitions directly to see how the various operators are defined. Let us first access a simple, 2D, Cartesian Coordinate Geometry
CartesianCoordinates = fn.geometry.CartesianCoordinates2D()
SphericalCoordinates = fn.geometry.SphericalSurfaceLonLat2D()
# These are the coordinate directions is symbolic form. They are also functions that
# mask the appropriate member of a coordinate tuple
x = CartesianCoordinates.xi0
y = CartesianCoordinates.xi1
print(x.evaluate(1.0,2.0), y.evaluate(1.0,2.0))
# Another example:
points = np.zeros((20,2))
points[:,0] = np.linspace(0.0,2.0*np.pi,20)
print(x.evaluate(points))
S = fn.math.sin(x)
# These are equivalent
print(S.evaluate(points) - np.sin(points[:,0]))
1.0 2.0
[0. 0.33069396 0.66138793 0.99208189 1.32277585 1.65346982
1.98416378 2.31485774 2.64555171 2.97624567 3.30693964 3.6376336
3.96832756 4.29902153 4.62971549 4.96040945 5.29110342 5.62179738
5.95249134 6.28318531]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
xi0 = SphericalCoordinates.xi0
xi1 = SphericalCoordinates.xi1
Vector operators¶
Partial differential equations are often the balancing of gradients of different quantities. The physical gradients may be very different when expressed in different coordinate systems and therefore we have to be careful to develop coordinate-independent expression for gradients that appear in PDEs. We do this by constructing operators such as div, grad, curl and the Laplacian that are expressed independently of the geometry but understand the underlying coordinate system.
When we calculate a derivative, they should be symbolically equivalent but the gradients are not.
for CoordinateSystem in [CartesianCoordinates, SphericalCoordinates]:
xi0 = CoordinateSystem.xi0
xi1 = CoordinateSystem.xi1
## Derivatives:
A = xi0 * fn.math.sin(xi0**2) + xi1 * fn.math.sin(xi1**2) + xi0 * xi1
print("Derivatives")
ddx0 = A.derivative(0)
ddx1 = A.derivative(1)
display(ddx0)
display(ddx1)
gradA = CoordinateSystem.grad(A)
print("Grad 0")
gradA[0].display()
print("Grad 1")
gradA[1].display()
print("div.grad")
CoordinateSystem.div(gradA, expand=True).display()
print("Laplacian")
CoordinateSystem.laplacian(A, 1, expand=True).display() # Note this is written for variable coefficient problems
CoordinateSystem.laplacian(A, xi0*xi1, expand=False).display() ## Neater for display purposes only
Derivatives
Grad 0
Grad 1
div.grad
Laplacian
Derivatives
Grad 0
Grad 1
div.grad
Laplacian
Symbols¶
These are placeholders for developing a function that you can then substitute later. This is useful as an aid to debugging functions or for providing an equation template
k0 = fn.symbol(name="k_0", lname="k_0")
k1 = fn.symbol(name="k_1", lname="k_1")
display((k0, k1))
H = x * fn.math.sin(k0*x + k0/(k1+x))
display(H)
try:
H.evaluate((0.1,1.0))
except:
print("Cannot evaluate H ... try making a subsitution")
k0.substitute(fn.parameter(4))
k1.substitute(fn.parameter(0.0001))
display(k1)
display(H)
print("H evaluated at (0.1,1.0): ",H.evaluate((0.1,1.0)))
k0.substitute(x)
k1.substitute(fn.parameter(0.01))
display(k1)
display(H)
Cannot evaluate H ... try making a subsitution
H evaluated at (0.1,1.0): [0.04623685]
You can “finalise” a symbol which means that it is no longer possible to make a substitution and it will generally behave like a normal quagmire function (including the fact that it no longer displays the substitution). The machinery for differentiation and evaluation is still a little more complicated than that of a regular function, so there may be some performance issues.
display(k0)
k0.finalise()
display(k0)
The same sort of thing but with some mesh variables¶
Mesh variables have an associated geometry that they obtain from their mesh and they have some numerical differentiation routines that are used when a derivative or gradient needs to be evaluated.
## Add a mesh
st0 = stripy.spherical_meshes.icosahedral_mesh(refinement_levels=6, include_face_points=True)
dm = meshtools.create_spherical_DMPlex(np.degrees(st0.lons), np.degrees(st0.lats), st0.simplices)
mesh = QuagMesh(dm, downhill_neighbours=1, permute=True)
lon = mesh.geometry.xi0
lat = mesh.geometry.xi1
Underlying Mesh type: sTriMesh
0 - Delaunay triangulation 0.21779666599999814s
0 - Calculate node weights and area 0.03310762499999953s
0 - Find boundaries 0.014876750000002659s
0 - cKDTree 0.01549533399999703s
0 - Construct neighbour cloud arrays 0.7254632500000007s, (0.5542094169999991s + 0.1712229160000014s)
0 - Construct rbf weights 0.045508500000000396s
F = mesh.add_variable("F", lname="\cal{F}")
l0 = fn.parameter(5.0)
l1 = fn.parameter(2.0)
G = fn.math.cos(l0 * fn.math.radians(lon)) * fn.math.sin(l1 * fn.math.radians(lat))
F.data = G.evaluate(mesh)
F.display()
G.display()
display(G.fn_gradient(0))
display(G.derivative(0))
mesh.geometry.grad(G)[0]
## Something with symbols we can substitute
m0 = fn.symbol(name="m_0", lname="m_0")
m1 = fn.symbol(name="m_1", lname="m_1")
H = fn.math.cos(m0 * fn.math.radians(lon)) * fn.math.sin(m1 * fn.math.radians(lat))
H.display()
m0.substitute(F**2)
m1.substitute(F**4)
H.display()
m0.substitute(F+1)
m1.substitute(F-1)
H.display()
# G.fn_gradient(1)
H.evaluate(mesh)
array([-1. , -0.09259669, 0.03942575, ..., 0.91069208,
0.03873612, 0.03113295])
H.derivative(1)
m0.finalise()
m1.finalise()
H.derivative(1)