In [1]:
import io
import math
import os
import pathlib
import holoviews as hv
import hvplot.pandas
import numpy as np
import pandas as pd
import pymap3d
import pyposeidon.mesh as pmesh
hv.extension("bokeh")
np.set_printoptions(suppress=True)
In [2]:
def parse_hgrid_nodes(path: os.PathLike[str] | str) -> pd.DataFrame:
with open(path, "rb") as fd:
_ = fd.readline()
_, no_points = map(int, fd.readline().strip().split(b" "))
content = io.BytesIO(b''.join(next(fd) for _ in range(no_points)))
nodes = pd.read_csv(
content,
engine="pyarrow",
sep="\t",
header=None,
names=["lon", "lat", "depth"],
index_col=0
)
nodes = nodes.reset_index(drop=True)
return nodes
def parse_hgrid_elements3(path: os.PathLike[str] | str) -> pd.DataFrame:
with open(path, "rb") as fd:
_ = fd.readline()
no_elements, no_points = map(int, fd.readline().strip().split(b" "))
for _ in range(no_points):
next(fd)
content = io.BytesIO(b''.join(next(fd) for _ in range(no_elements)))
elements = pd.read_csv(
content,
engine="pyarrow",
sep="\t",
header=None,
names=["no_nodes", "n1", "n2", "n3"],
index_col=0
)
elements = elements.assign(
n1=elements.n1 - 1,
n2=elements.n2 - 1,
n3=elements.n3 - 1,
).reset_index(drop=True)
return elements
In [3]:
def get_skews_and_base_cfls(lons, lats, depths) -> np.ndarray:
# The shape of each one of the input arrays needs to be (3, <no_triangles>)
#ell = pymap3d.Ellipsoid.from_name("wgs84")
ell = pymap3d.Ellipsoid(6378206.4, 6378206.4, "schism", "schism")
local_x, local_y, _ = pymap3d.geodetic2enu(lats, lons, depths, lats[0], lons[0], depths[0], ell=ell)
areas = (local_x[1] * local_y[2] - local_x[2] * local_y[1]) * 0.5
rhos = np.sqrt(areas / np.pi)
max_sides = np.maximum(
np.sqrt(local_x[1] ** 2 + local_y[1] ** 2),
np.sqrt(local_x[2] ** 2 + local_y[2] ** 2),
np.sqrt((local_x[2] - local_x[1]) ** 2 + (local_y[2] - local_y[1]) ** 2),
)
skews = max_sides / rhos
base_cfls = np.sqrt(9.81 * np.maximum(0.1, depths.mean(axis=0))) / rhos / 2
return skews, base_cfls
def get_skews_and_base_cfls_from_path(path: os.PathLike[str] | str) -> np.ndarray:
nodes = parse_hgrid_nodes(path)
elements = parse_hgrid_elements3(path)
tri = elements[["n1", "n2", "n3"]].values
lons = nodes.lon.values[tri].T
lats = nodes.lat.values[tri].T
depths = nodes.depth.values[tri].T
skews, base_cfls = get_skews_and_base_cfls(lons=lons, lats=lats, depths=depths)
return skews, base_cfls
In [4]:
path = "/home/panos/Prog/poseidon/seareport_meshes/meshes/global-v0.1.gr3"
path = "/home/panos/Prog/poseidon/seareport_meshes/meshes/global-v0.gr3"
path = "/home/panos/Prog/git/schism/src/Utility/Grid_Scripts/hgrid.gr3"
path = "/home/panos/Prog/poseidon/seareport_meshes/meshes/global-v0.2.gr3"
skews, base_cfls = get_skews_and_base_cfls_from_path(path)
In [5]:
CFL_THRESHOLD = 0.4
for dt in (1, 50, 75, 100, 120, 150, 200, 300, 400, 600, 900, 1200, 1800, 3600):
violations = (base_cfls * dt < CFL_THRESHOLD).sum()
print(f"{dt:>4d} {violations:>12d} {violations / len(base_cfls) * 100:>8.2f}%")
1 716169 100.00% 50 438858 61.28% 75 173842 24.27% 100 122625 17.12% 120 107230 14.97% 150 87782 12.26% 200 67136 9.37% 300 39294 5.49% 400 21114 2.95% 600 564 0.08% 900 0 0.00% 1200 0 0.00% 1800 0 0.00% 3600 0 0.00%
In [6]:
pd.DataFrame({"skew": skews}).describe()
Out[6]:
skew | |
---|---|
count | 716169.000000 |
mean | 2.819506 |
std | 0.144765 |
min | 2.507587 |
25% | 2.721086 |
50% | 2.780963 |
75% | 2.885701 |
max | 6.666141 |
In [7]:
df = pd.DataFrame({"cfl": base_cfls * 400})
df[df.cfl < 0.4].describe()
df[df.cfl < 0.4].hvplot.hist()
Out[7]:
cfl | |
---|---|
count | 21114.000000 |
mean | 0.330807 |
std | 0.036809 |
min | 0.199615 |
25% | 0.302369 |
50% | 0.327259 |
75% | 0.360951 |
max | 0.400000 |
Out[7]:
In [8]:
nodes = parse_hgrid_nodes(path)
elements = parse_hgrid_elements3(path)
elements = elements.assign(base_cfl=base_cfls)
elements.head()
Out[8]:
no_nodes | n1 | n2 | n3 | base_cfl | |
---|---|---|---|---|---|
0 | 3 | 189444 | 0 | 244205 | 0.003675 |
1 | 3 | 0 | 189444 | 335357 | 0.004649 |
2 | 3 | 0 | 207657 | 218301 | 0.007291 |
3 | 3 | 207657 | 0 | 319429 | 0.006633 |
4 | 3 | 0 | 218301 | 361609 | 0.006373 |
In [9]:
min_cfl_per_node = pd.concat([
elements[["n1", "base_cfl"]].groupby(["n1"]).base_cfl.min(),
elements[["n2", "base_cfl"]].groupby(["n2"]).base_cfl.min(),
elements[["n3", "base_cfl"]].groupby(["n3"]).base_cfl.min(),
], axis=1).min(axis=1)
min_cfl_per_node.head()
Out[9]:
0 0.003675 1 0.007158 2 0.009419 3 0.002360 4 0.007654 dtype: float64
In [15]:
dt = 600
df = nodes.assign(
cfl=min_cfl_per_node * dt,
# CFL_violation nodes have a value of 1 if there is no violation and 4 if there is a violation.
# We do this in order to plot the points with a different size
cfl_violation=((min_cfl_per_node * dt < CFL_THRESHOLD) * 3) + 1
)
df.head()
Out[15]:
lon | lat | depth | cfl | cfl_violation | |
---|---|---|---|---|---|
0 | 0.000000 | 90.000000 | 4228.0 | 2.205227 | 1 |
1 | -23.447414 | -19.913775 | 4603.0 | 4.294965 | 1 |
2 | 118.483781 | -15.726201 | 5477.0 | 5.651691 | 1 |
3 | 15.495643 | -28.939963 | 187.0 | 1.416196 | 1 |
4 | 94.479055 | -14.068985 | 5710.0 | 4.592690 | 1 |
In [16]:
df.hvplot.points(
'lon',
'lat',
c="cfl_violation",
cmap="colorblind",
geo=True,
tiles=True,
).options(
width=900, height=600
).opts(
hv.opts.Points(size=hv.dim('cfl_violation'))
)
Out[16]: