Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add python version the set of functions of cell integral and their test functions #420

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
cb58dc1
Merge pull request #2 from clawpack/master
RunxinNi Dec 5, 2019
97767d0
Merge pull request #3 from clawpack/master
RunxinNi Dec 15, 2019
2cdf4ee
Add `bilinearintegral` function
RunxinNi Dec 15, 2019
c64127e
Update `bilinearintegral_s` function
RunxinNi Dec 15, 2019
7e5133d
Add `topointegral` function
RunxinNi Dec 15, 2019
76a4465
Add `intersection` function
RunxinNi Dec 15, 2019
d88cfba
Add `recurintegral` function
RunxinNi Dec 15, 2019
3f8b55b
Add `cellintegral` function
RunxinNi Dec 15, 2019
07f2c06
Add `patch_value` function
RunxinNi Dec 15, 2019
7037bb4
Add `patch` class
RunxinNi Dec 15, 2019
2509931
from clawpack.geoclaw.data import data
RunxinNi Dec 15, 2019
833a321
Fix `patch_value` docstring format
RunxinNi Dec 15, 2019
ab347e9
Add `test_integral`
RunxinNi Dec 15, 2019
4cd81d2
Add `test_integral_plot` for `test_integral`
RunxinNi Dec 15, 2019
66211e6
Add import
RunxinNi Dec 15, 2019
2aa438b
Update import
RunxinNi Dec 15, 2019
5f66683
Update the set of functions for cell integral
RunxinNi Dec 20, 2019
90810d7
Update the test function for cell integral
RunxinNi Dec 20, 2019
74681c3
Fix warning error in the test
RunxinNi Dec 26, 2019
6b23e33
Import nquad
RunxinNi Dec 26, 2019
e5a3389
Change patch to a dictionary
RunxinNi Dec 26, 2019
56cd613
Change patch to a dictionary for test function
RunxinNi Dec 26, 2019
7474022
Update topotools.py
RunxinNi Jan 11, 2021
c0b1601
Merge pull request #4 from clawpack/master
RunxinNi Jan 11, 2021
7254e49
Update test_topotools.py
RunxinNi Jan 11, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
377 changes: 377 additions & 0 deletions src/python/geoclaw/topotools.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import clawpack.geoclaw.data
import six
from six.moves import range
from clawpack.geoclaw.data import Rearth, DEG2RAD, RAD2DEG

# ==============================================================================
# Topography Related Functions
Expand Down Expand Up @@ -1672,7 +1673,383 @@ def make_shoreline_xy(self, sea_level=0):
shoreline_xy = numpy.vstack((shoreline_xy, \
numpy.array([numpy.nan,numpy.nan]), c.allsegs[0][k]))
return shoreline_xy


def intersection(rect1, rect2):
r"""
Whether two domains, rect1 and rect2 intersect. If they intersect,
find the intersection's boundary and area.
:Input:
- *rect1* (ndarray(:))
- *rect2* (ndarray(:))
:Output:
- *mark* (bool) If rect1 and rect2 intersect, mark = True. Otherwise,
mark = False.
- *area* (float) The area of the intersection of rect1 and rect2.
- *bound* (ndarray(:)) The boundary of the intersection of rect1 and rect2.
"""

# Set boundary for rect1
x1_low = rect1[0]; x1hi = rect1[1]
y1_low = rect1[2]; y1hi = rect1[3]

# Set boundary for rect2
x2low = rect2[0]; x2hi = rect2[1]
y2low = rect2[2]; y2hi = rect2[3]

# Boundary of the intersection part
xintlow = max(x1_low, x2low)
xinthi = min(x1hi, x2hi)
yintlow = max(y1_low, y2low)
yinthi = min(y1hi, y2hi)

# Whether rect1 and rect2 intersect
if xinthi > xintlow and yinthi > yintlow:
area = (xinthi - xintlow) * (yinthi - yintlow)
mark = True
bound = [xintlow, xinthi, yintlow, yinthi]
else:
area = 0.0
mark = False
bound = [-1, -1, -1, -1]

return mark, area, bound


def bilinearintegral(domain, topodomain, corners):
r"""
bilinearintegral integrates a surface over a rectangular region which is the
intersection of the surface defined by "domain" and the surface defined by
"topodomain". The surface integrated is defined by the function obtained by
bilinear interpolation through corners of the Cartesian grid.

Suppose corners are (x1, y1), (x1, y2), (x2, y1), (x1, y2). Get a function,
f(xi, eta) = a * (xi - x1) / (x2 - x1) + b * (eta - y1) / (y2 - y1)
+ c * (xi - x1) * (eta - y1) / {(x2 - x1) * (y2 - y1)} + d,
to represent topo of topomain by bilinear interpolation with four corner
points. Then integrate f(xi, eta) on the rectangular domain which is the
intersection of domain and topodomain.

:Input:
- *domain* (ndarray(:))
- *topodomain* (ndarray(:))
- *corners* (ndarray(:, :)) Four corner points of the topodomain.
:Output:
- *bilinearintegral* (float) The integral of the function, f(xi, eta), on
the intersection of domain and topodomain.
"""

# Set boundary for integral
bound = intersection(domain, topodomain)[2]

# Find terms for the integration
deltax = topodomain[1] - topodomain[0]
deltay = topodomain[3] - topodomain[2]
area = (bound[3] - bound[2]) * (bound[1] - bound[0])
sumxi = (bound[1] + bound[0] - 2.0 * topodomain[0]) / deltax
sumeta = (bound[3] + bound[2] - 2.0 * topodomain[2]) / deltay

# Find coefficients of the function, f(xi, eta)
a = corners[1][0] - corners[0][0]
b = corners[0][1] - corners[0][0]
c = corners[1][1] - corners[1][0] - corners[0][1] + corners[0][0]
d = corners[0][0]

bilinearintegral = (0.5 * (a * sumxi + b * sumeta) +
0.25 * c * sumxi * sumeta + d) * area

return bilinearintegral


def bilinearintegral_s(domain, topodomain, corners):
r"""
bilinearintegral_s integrates a surface over a sphere which is the intersection
of the surface defined by "domain" and the surface defined by "topodomain".
The surface integrated is defined by the function obtained by bilinear
interpolation through corners of the polar grid.

Suppose corners are (x1, y1), (x1, y2), (x2, y1), (x1, y2). Get a function,
f(theta, phi) = a * (theta - x1) + b * (phi - y1)
+ c * (theta - x1) * (phi - y1) + d,
where theta is longitute and phi is latitude, to represent the topo of
topomain by bilinear interpolation with four corner points. Then integrate
f(theta, phi) on the domain which is the intersection of the domain
and the topodomain on the sphere.

:Input:
- *domain* (ndarray(:))
- *topodomain* (ndarray(:))
- *corners* (ndarray(:, :)) Four corner points of the topodomain.
:Output:
- *bilinearintegral* (float) The integral of the function, f(xi, eta), on
the intersection of domain and topodomain.
"""

#Set boundary parameter
bound = intersection(domain, topodomain)[2]

# Find terms for the integration
xdiffhi = bound[1] - topodomain[0]
xdifflow = bound[0] - topodomain[0]
ydiffhi = bound[3] - topodomain[2]
ydifflow = bound[2] - topodomain[2]
xdiff2 = 0.5 * (xdiffhi**2 - xdifflow**2)
intdx = bound[1] - bound[0]
deltax = topodomain[1] - topodomain[0]
deltay = topodomain[3] - topodomain[2]

d2r = DEG2RAD; r2d = RAD2DEG

cbsinint = (r2d * numpy.cos(d2r * bound[3]) + ydiffhi * numpy.sin(d2r * bound[3]))
- (r2d * numpy.cos(d2r * bound[2]) + ydifflow * numpy.sin(d2r * bound[2]))

adsinint = r2d * (numpy.sin(d2r * bound[3]) - numpy.sin(d2r * bound[2]))

# Find coefficients of the function, f(theta, phi)
a = (corners[1][0] - corners[0][0]) / deltax
b = (corners[0][1] - corners[0][0]) / deltay
c = (corners[1][1] - corners[1][0] - corners[0][1] + corners[0][0]) / (deltax * deltay)
d = corners[0][0]

bilinearintegral_s = ((a * xdiff2 + d * intdx) * adsinint +
r2d * (c * xdiff2 + b * intdx) * cbsinint) * (Rearth * d2r)**2

return bilinearintegral_s


def topointegral(domain, topoparam, z, intmethod, coordinate_system):
r"""
topointegral integrates a surface over a rectangular region which is
the intersection of the surface defined by "domain" and the surface
defined by "topoparam". The surface integrated is defined by the function
obtained by bilinear interpolation through nodes of the Cartesian grid.

:Input:
- *domain* (ndarray(:))
- *topoparam* (ndarray(:)) topoparam includes topo's x and y coordinates'
minimum and maximum, and topo's grid size, dx and dy.
- *z* (ndarray(:, :)) Topo data of domain represented by topoparam.
- *intmethod* (int) The method of integral.
- *coordinate_system* (int) The type of coordinate system.
:Output:
- *theintegral* (float) The integral of a surface over a rectangular
region which is the intersection of the domain and "topoparam".
"""

theintegral = 0.0

# Set topo's parameter
x1 = topoparam[0]; x2 = topoparam[1]
y1 = topoparam[2]; y2 = topoparam[3]
topodx = topoparam[4]; topody = topoparam[5]
topomx = numpy.array(z).shape[1]
topomy = numpy.array(z).shape[0]

# Find the intersection of the domain and topo
bound = intersection(domain, topoparam)[2]
xlow = bound[0]; xhi = bound[1]
ylow = bound[2]; yhi = bound[3]

if intmethod == 1:

# Find topo's start and end points for integral
# The x coodinate of the topo's start point
istart = -1
for i in range(topomx):
if (x1 + i * topodx) > xlow:
istart = max(i-1, 0)
break

# The y coodinate of the topo's start point
jstart = -1
for j in range(topomy):
if (y1 + j * topody) > ylow:
jstart = max(j-1, 0)
break

# The x coodinate of the topo's end point
iend = topomx
for m in range(topomx):
if (x1 + m * topodx) >= xhi:
iend = m
break

# The y coodinate of the topo's end point
jend = topomy
for n in range(topomy):
if (y1 + n * topody) >= yhi:
jend = n
break

# Prevent overflow
jstart = max(jstart, 0)
istart = max(istart, 0)
jend = min(jend, topomy - 1)
iend = min(iend, topomx - 1)

# Topo integral
for jj in range(jstart, jend):
yint1 = y1 + jj * topody
yint2 = y1 + (jj + 1) * topody

for ii in range(istart, iend):
xint1 = x1 + ii * topodx
xint2 = x1 + (ii + 1) * topodx

# Four corners of topo's grid
corners = numpy.empty((2, 2))
corners[0][0] = z[jj][ii]
corners[0][1] = z[jj+1][ii]
corners[1][0] = z[jj][ii+1]
corners[1][1] = z[jj+1][ii+1]

# Parameters of topo's grid integral
topointparam = [xint1, xint2, yint1, yint2]

if coordinate_system == 1:
theintegral += bilinearintegral(domain, topointparam, corners)
elif coordinate_system == 2:
theintegral += bilinearintegral_s(domain, topointparam, corners)
else:
print("TOPOINTEGRAL: coordinate_system error")

else:
print("TOPOINTEGRAL: only intmethod = 1,2 is supported")

return theintegral


def recurintegral(domain, mtopoorder, mtopofiles, m, topo):
r"""
Compute the integral of topo over the rectangle domain defined by "domain".
Find the index of the topo whose resolution is m by "mtopoorder". Use this
index to find corresponding topo object in the list "topo".
The main call to this recursive function has corners of a grid cell for the
rectangle and m = 1 in order to compute the integral over the cell using all
topo objects.
The recursive strategy is to first compute the integral using only the topo
object of m+1 resolution. Then apply corrections due to adding m resolution
topo object.

Corrections are needed if the new topo object intersects the grid cell.
Let the intersection be represented by mark2[2]. Two corrections are needed.
First, subtract out the integral over the rectangle "mark2[2]" computed with
topo objects mtopoorder(mtopofiles) to mtopoorder(m+1), and then adding in
the integral over this same region using the topo object mtopoorder(m).

:Input:
- *domain* (ndarray(:)) The specific surface where the integral is computed.
- *mtopoorder* (ndarray(:)) The order of the topo objects' resolutions.
- *mtopofiles* (int) The number of the topo objects.
- *m* (int) Resolution of the topo objects.
- *topo* (list) The list of topo objects.
:Output:
- *integral* (float) Integral.
"""

# The index of the topo object whose resolution is m
mfid = mtopoorder[m]

# The parameters of the m resolution topo object
topoparam_mfid = [topo[mfid].extent[0], topo[mfid].extent[1], topo[mfid].extent[2],
topo[mfid].extent[3], topo[mfid].delta[0], topo[mfid].delta[1]]

# Innermost step of recursion reaches this point, only using coarsest topo grid
# compute directly
if m == mtopofiles - 1:
mark1 = intersection(domain, topoparam_mfid)
if mark1[0]:
integral = topointegral(mark1[2], topoparam_mfid, topo[mfid].Z, 1, 1)
else:
integral = 0.0
else:
int1 = recurintegral(domain, mtopoorder, mtopofiles, m+1, topo)

# Whether new topo object intersects the grid cell
mark2 = intersection(domain, topoparam_mfid)

# The new topo object intersects the grid cell. Corrections are needed
if mark2[1] > 0:
int2 = recurintegral(mark2[2], mtopoorder, mtopofiles, m+1, topo)
int3 = topointegral(mark2[2], topoparam_mfid, topo[mfid].Z, 1, 1)
integral = int1 - int2 + int3
else:
integral = int1
return integral


def cellintegral(cell, mtopoorder, mtopofiles, topo):
r"""
Compute the integral on the surface defined by "cell" with the topo objects
list "topo".

:Input:
- *cell* (ndarray(:)) The boundary of specific surface where the integral
is computed.
- *mtopoorder* (ndarray(:)) The order of the topo objects' resolutions.
- *mtopofiles* (int) The number of the topo objects.
- *topo* (list) The list of topo objects.
:Output:
- *topoint* (float) The integral on the cell, "cell".
"""

# Initialization
topoint = 0.0

for m in range(mtopofiles):

# The index of the topo file object resolution is m
mfid = mtopoorder[m]
cellarea = (cell[1] - cell[0]) * (cell[3] - cell[2])

# The parameters of this topo object
topoparam_mfid = [topo[mfid].extent[0], topo[mfid].extent[1], topo[mfid].extent[2],
topo[mfid].extent[3], topo[mfid].delta[0], topo[mfid].delta[1]]

# Whether m-th topo interects with cell
mark = intersection(cell, topoparam_mfid)
if mark[0]:

# Whether the cell is completely overlapped by m-th topo
if mark[1] == cellarea:
topoint = topoint + topointegral(mark[2], topoparam_mfid, topo[mfid].Z, 1, 1)
return topoint
else:

# If the cell is not completely overlapped by m-th topo, use recursion
# to compute the integral on the cell
topoint = recurintegral(cell, mtopoorder, mtopofiles, m, topo)
return topoint
return topoint


def cell_average_patch(patch, mtopoorder, mtopofiles, topo):
r"""
Compute every cell's average of the specific patch given by "patch".
:Input:
- *patch* (Dictionary) Patch.
- *mtopoorder* (ndarray(:)) The order of the topo objects' resolutions.
- *mtopofiles* (int) The number of the topo objects.
- *topo* (list) The list of topo objects.
:Output:
- *cell_average* (ndarray(:, :)) Patch's every cell average.
"""

x_num = len(patch['x']) - 1
y_num = len(patch['y']) - 1
cell_average = numpy.empty((y_num, x_num))

for i in range(y_num):
for j in range(x_num):

# Set cell's parameter
cell = [patch['x'][j], patch['x'][j+1], patch['y'][i], patch['y'][i+1]]
area = (patch['x'][j+1] - patch['x'][j]) * (patch['y'][i+1] - patch['y'][i])

# Calculate every cell average
cell_average[i, j] = cellintegral(cell, mtopoorder, mtopofiles, topo) / area

return cell_average


# Define convenience dictionary of URLs for some online DEMs in netCDF form:
Expand Down
Loading