From 0607026a17f9a951d0426305092a10afee4ec90e Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Sun, 14 Apr 2024 08:25:55 -0700 Subject: [PATCH 01/58] change nodata_value to -99999 in topotools Previous value -9999 did not have enough digits since new etopo 2022 data has this as an actual topography value in the Mariana Trench (when written with `Z_format='%.0f'`). --- src/python/geoclaw/topotools.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/python/geoclaw/topotools.py b/src/python/geoclaw/topotools.py index 55bae6c38..1743e1ae6 100644 --- a/src/python/geoclaw/topotools.py +++ b/src/python/geoclaw/topotools.py @@ -428,7 +428,7 @@ def __init__(self, path=None, topo_type=None, topo_func=None, self.topo_type = topo_type self.unstructured = unstructured - self.no_data_value = -9999 + self.no_data_value = -99999 # Data storage for only calculating array shapes when needed self._z = None @@ -1004,7 +1004,7 @@ def write(self, path, topo_type=None, no_data_value=None, fill_value=None, # Default to this object's no_data_value if the passed is None, # otherwise the argument will override the object's value or it will - # default to -9999 (default for the class) + # default to -99999 (default for the class) if no_data_value is None: no_data_value = self.no_data_value @@ -1304,7 +1304,7 @@ def plot(self, axes=None, contour_levels=None, contour_kwargs={}, def interp_unstructured(self, fill_topo, extent=None, method='nearest', delta=None, delta_limit=20.0, - no_data_value=-9999, buffer_length=100.0, + no_data_value=-99999, buffer_length=100.0, proximity_radius=100.0, resolution_limit=2000): r"""Interpolate unstructured data on to regular grid. @@ -1340,7 +1340,7 @@ def interp_unstructured(self, fill_topo, extent=None, method='nearest', - *delta_limit* (float) - Limit of finest horizontal resolution, default is 20 meters. - *no_data_value* (float) - Value to use if no data was found to fill in a - missing value, ignored if `method = 'nearest'`. Default is `-9999`. + missing value, ignored if `method = 'nearest'`. Default is `-99999`. - *buffer_length* (float) - Buffer around bounding box, only applicable when *extent* is None. Default is `100.0` meters. - *proximity_radius* (float) - Radius every unstructured data point From 481b2b9836d4bacc5045bb9108dd01ff7d3e7c67 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 19 Apr 2024 15:17:10 -0400 Subject: [PATCH 02/58] Fix bug related to tracking pressure at gauges --- src/2d/shallow/gauges_module.f90 | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/2d/shallow/gauges_module.f90 b/src/2d/shallow/gauges_module.f90 index 95a67b17b..82b20e2a7 100644 --- a/src/2d/shallow/gauges_module.f90 +++ b/src/2d/shallow/gauges_module.f90 @@ -669,11 +669,7 @@ subroutine update_gauges(q, aux, xlow, ylow, num_eqn, mitot, mjtot, num_aux, & gauges(i)%level(n) = level gauges(i)%data(1,n) = tgrid do j = 1, gauges(i)%num_out_vars - if (j .eq. pressure_index) then - gauges(i)%data(1 + j, n) = var(j)/ambient_pressure - else - gauges(i)%data(1 + j, n) = var(j) - endif + gauges(i)%data(1 + j, n) = var(j) end do gauges(i)%buffer_index = n + 1 From d87ca9946b6864b657451e4c0d581109a8dd4021 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Fri, 26 Apr 2024 16:10:38 -0700 Subject: [PATCH 03/58] simplify make_fgout_animation.py use of update_artists These do not need to be passed into update, unpacked, and repacked, since the objects created in the script will be used into update. If blit==False then they are not needed at all. --- .../make_fgout_animation.py | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py b/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py index 4f5cb1252..5b64c8e92 100644 --- a/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py +++ b/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py @@ -63,21 +63,27 @@ ax.set_ylim(plot_extent[2:]) -# The artists that will be updated for subsequent frames: -update_artists = (eta_plot, title_text) - +blit = True +if blit: + # The artists that will be updated for subsequent frames: + update_artists = (eta_plot, title_text) + +# Note that update_artists is only needed if blit==True in call to +# animation.FuncAnimation below. +# Using blit==False does not seem to slow down creation of the animation by much +# and slightly simplifies modification of this script to situations where more +# artists are updated. -def update(fgframeno, *update_artists): +def update(fgframeno): """ Update an exisiting plot with solution from fgout frame fgframeno. - The artists in update_artists must have new data assigned. + Note: Even if blit==True in call to animation.FuncAnimation, + the update_artists do not need to be passed in, unpacked, and repacked + as in an earlier version of this example (version <= 5.10.0). """ fgout = fgout_grid.read_frame(fgframeno) print('Updating plot at time %s' % timedelta(seconds=fgout.t)) - - # unpack update_artists (must agree with definition above): - eta_plot, title_text = update_artists # reset title to current time: title_text.set_text('Surface at time %s' % timedelta(seconds=fgout.t)) @@ -86,8 +92,8 @@ def update(fgframeno, *update_artists): eta = ma.masked_where(fgout.h<0.001, fgout.eta) eta_plot.set_array(eta.T.flatten()) - update_artists = (eta_plot, title_text) - return update_artists + if blit: + return update_artists def plot_fgframe(fgframeno): """ @@ -95,15 +101,13 @@ def plot_fgframe(fgframeno): But if you use this function in IPython and then try to make the animation, it may get into an infinite loop (not sure why). Close the figure to abort. """ - update(fgframeno, *update_artists) + update(fgframeno) def make_anim(): print('Making anim...') - anim = animation.FuncAnimation(fig, update, - frames=fgframes, - fargs=update_artists, - interval=200, blit=True) + anim = animation.FuncAnimation(fig, update, frames=fgframes, + interval=200, blit=blit) return anim if __name__ == '__main__': From e01df84168052d62304ecfe1b5b765368613ea64 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Fri, 26 Apr 2024 16:14:10 -0700 Subject: [PATCH 04/58] use image backend Agg in make_fgout_animation.py so animation size agrees with specified figure size --- .../tsunami/chile2010_fgmax-fgout/make_fgout_animation.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py b/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py index 5b64c8e92..36f7a85e0 100644 --- a/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py +++ b/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py @@ -8,6 +8,9 @@ """ +import matplotlib +matplotlib.use('Agg') # Use an image backend + from pylab import * import os from clawpack.visclaw import plottools, geoplot @@ -24,7 +27,7 @@ fgframes = range(1,26) # frames of fgout solution to use in animation -figsize = (10,8) +figsize = (8,7) # Instantiate object for reading fgout frames: fgout_grid = fgout_tools.FGoutGrid(fgno, outdir, format) From 5c6ea17241ba88ecf7f407b5f653ee0a6261331d Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Sat, 27 Apr 2024 09:42:40 -0700 Subject: [PATCH 05/58] set blit=False and auto-detect number of fgout frames --- .../make_fgout_animation.py | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py b/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py index 36f7a85e0..c4d20e64f 100644 --- a/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py +++ b/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py @@ -6,13 +6,26 @@ The tuple update_artists contains the list of Artists that must be changed in update. Modify this as needed. +Make the animation via: + python make_fgout_animation.py + +If this script is executed in IPython or a notebook it may go into +an infinite loop for reasons unknown. If so, close the figure to halt. + +To view individual fgout frames interactively, this should work: + import make_fgout_animation + fgframeno = # set to desired fgout frame number + make_fgout_animation.update(fgframeno) """ -import matplotlib -matplotlib.use('Agg') # Use an image backend - +import sys +if 'matplotlib' not in sys.modules: + # Use an image backend to insure animation has size specified by figsize + import matplotlib + matplotlib.use('Agg') + from pylab import * -import os +import os, glob from clawpack.visclaw import plottools, geoplot from clawpack.visclaw import animation_tools from matplotlib import animation, colors @@ -25,7 +38,15 @@ outdir = '_output' format = 'binary' # format of fgout grid output -fgframes = range(1,26) # frames of fgout solution to use in animation +if 1: + # all frames found in outdir: + fgout_frames = glob.glob(os.path.join(outdir, \ + 'fgout%s.t*' % str(fgno).zfill(4))) + nout = len(fgout_frames) + fgframes = range(1, nout+1) + print('Found %i fgout frames in %s' % (nout,outdir)) +else: + fgframes = range(1,26) # frames of fgout solution to use in animation figsize = (8,7) @@ -66,7 +87,7 @@ ax.set_ylim(plot_extent[2:]) -blit = True +blit = False if blit: # The artists that will be updated for subsequent frames: update_artists = (eta_plot, title_text) @@ -98,14 +119,6 @@ def update(fgframeno): if blit: return update_artists -def plot_fgframe(fgframeno): - """ - Convenience function for plotting one frame. - But if you use this function in IPython and then try to make the animation, - it may get into an infinite loop (not sure why). Close the figure to abort. - """ - update(fgframeno) - def make_anim(): print('Making anim...') From 5b175625e3181e800a378c38c57fd208fa525d2f Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Sat, 27 Apr 2024 09:50:41 -0700 Subject: [PATCH 06/58] clean up make_fgout_animation.py --- .../make_fgout_animation.py | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py b/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py index c4d20e64f..790e8b746 100644 --- a/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py +++ b/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py @@ -3,8 +3,6 @@ This is done in a way that makes the animation quickly and with minimum storage required, by making one plot and then defining an update function that only changes the parts of the plot that change in each frame. -The tuple update_artists contains the list of Artists that must be changed -in update. Modify this as needed. Make the animation via: python make_fgout_animation.py @@ -14,8 +12,8 @@ To view individual fgout frames interactively, this should work: import make_fgout_animation - fgframeno = # set to desired fgout frame number - make_fgout_animation.update(fgframeno) + make_fgout_animation.update(fgframeno) # for desired fgout frame no + """ import sys @@ -39,17 +37,16 @@ format = 'binary' # format of fgout grid output if 1: - # all frames found in outdir: + # use all fgout frames in outdir: fgout_frames = glob.glob(os.path.join(outdir, \ 'fgout%s.t*' % str(fgno).zfill(4))) nout = len(fgout_frames) fgframes = range(1, nout+1) print('Found %i fgout frames in %s' % (nout,outdir)) else: + # set explicitly, e.g. to test with only a few frames fgframes = range(1,26) # frames of fgout solution to use in animation -figsize = (8,7) - # Instantiate object for reading fgout frames: fgout_grid = fgout_tools.FGoutGrid(fgno, outdir, format) @@ -60,10 +57,9 @@ fgout1 = fgout_grid.read_frame(fgframes[0]) plot_extent = fgout1.extent_edges - ylat = fgout1.Y.mean() # for aspect ratio of plots -fig,ax = subplots(figsize=figsize) +fig,ax = subplots(figsize=(8,7)) ax.set_xlim(plot_extent[:2]) ax.set_ylim(plot_extent[2:]) @@ -103,7 +99,7 @@ def update(fgframeno): Update an exisiting plot with solution from fgout frame fgframeno. Note: Even if blit==True in call to animation.FuncAnimation, the update_artists do not need to be passed in, unpacked, and repacked - as in an earlier version of this example (version <= 5.10.0). + as in an earlier version of this example (Clawpack version <= 5.10.0). """ fgout = fgout_grid.read_frame(fgframeno) @@ -120,15 +116,11 @@ def update(fgframeno): return update_artists -def make_anim(): +if __name__ == '__main__': + print('Making anim...') anim = animation.FuncAnimation(fig, update, frames=fgframes, interval=200, blit=blit) - return anim - -if __name__ == '__main__': - - anim = make_anim() # Output files: name = 'fgout_animation' @@ -148,7 +140,4 @@ def make_anim(): if fname_html: # html version: animation_tools.make_html(anim, file_name=fname_html, title=name) - - - - + From 7917432e24878d3e91bd87ee1764fbc7c82da5c6 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Sat, 27 Apr 2024 11:00:51 -0700 Subject: [PATCH 07/58] added new chile2010_fgmax-fgout/make_fgout_animation_with_transect.py --- .../tsunami/chile2010_fgmax-fgout/Makefile | 3 +- .../tsunami/chile2010_fgmax-fgout/README.rst | 16 ++ .../make_fgout_animation_with_transect.py | 238 ++++++++++++++++++ .../chile2010_fgmax-fgout/setplot_fgout.py | 6 + 4 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation_with_transect.py diff --git a/examples/tsunami/chile2010_fgmax-fgout/Makefile b/examples/tsunami/chile2010_fgmax-fgout/Makefile index 165f47797..67b5a9e84 100644 --- a/examples/tsunami/chile2010_fgmax-fgout/Makefile +++ b/examples/tsunami/chile2010_fgmax-fgout/Makefile @@ -64,7 +64,8 @@ topo: fgout_plots: $(MAKE) plots SETPLOT_FILE=setplot_fgout.py PLOTDIR=_plots_fgout $(CLAW_PYTHON) make_fgout_animation.py - mv fgout_animation.* _plots_fgout/ + $(CLAW_PYTHON) make_fgout_animation_transect.py + mv fgout_animation* _plots_fgout/ all: $(MAKE) topo diff --git a/examples/tsunami/chile2010_fgmax-fgout/README.rst b/examples/tsunami/chile2010_fgmax-fgout/README.rst index 765843887..bcad75167 100644 --- a/examples/tsunami/chile2010_fgmax-fgout/README.rst +++ b/examples/tsunami/chile2010_fgmax-fgout/README.rst @@ -148,6 +148,22 @@ The use of fgout grids provides a way to produce frequent outputs on a fixed grid resolution, as often desired for making smooth animations of a portion of the computational domain. +**New in v5.10.1:** + +As of v5.10.1, the make_fgout_animation.py script has been simplified a bit +and cleaned up. See the docstring for more information. In addition, a new +script has been added to make an animation that also shows a transect of +the surface elevation and the bottom topography from the fgout frames. +Note that the topography changes as the tsunami propagates based on +where the finer AMR grids are located. To make this animation: + + python make_fgout_animation_with_transect.py + +or it is also created by `make fgout_plots`, in which case the resulting +`fgout_animation_with_transect.*` files are moved into `_plots_fgout` +and linked from `_plots_fgout/_PlotIndex.html`. + + Saving a sequence of fgout frames to a single netCDF file ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation_with_transect.py b/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation_with_transect.py new file mode 100644 index 000000000..4b32c40da --- /dev/null +++ b/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation_with_transect.py @@ -0,0 +1,238 @@ +""" +Make an mp4 animation of fgout grid results that also includes a transect +of the solution, as often desired in animations. + +This is done in a way that makes the animation quickly and with minimum +storage required, by making one plot and then defining an update function +that only changes the parts of the plot that change in each frame. + +Make the animation via: + python make_fgout_animation_transect.py + +If this script is executed in IPython or a notebook it may go into +an infinite loop for reasons unknown. If so, close the figure to halt. + +To view individual fgout frames interactively, this should work: + import make_fgout_animation_transect + make_fgout_animation_transect.update(fgframeno) # for desired fgout frameno + +Uses blit==False so that update_artists tuple does not need to be returned +from the update function. +""" + +import sys +if 'matplotlib' not in sys.modules: + # Use an image backend to insure animation has size specified by figsize + import matplotlib + matplotlib.use('Agg') + +from pylab import * +import os, glob +from clawpack.visclaw import plottools, geoplot, gridtools, animation_tools +from clawpack.geoclaw import util +from matplotlib import animation, colors +from datetime import timedelta + +from clawpack.geoclaw import fgout_tools + +fgno = 1 # which fgout grid + +outdir = '_output' +format = 'binary' # format of fgout grid output + +if 1: + # use all fgout frames in outdir: + fgout_frames = glob.glob(os.path.join(outdir, \ + 'fgout%s.t*' % str(fgno).zfill(4))) + nout = len(fgout_frames) + fgframes = range(1, nout+1) + print('Found %i fgout frames in %s' % (nout,outdir)) +else: + # set explicitly, e.g. to test with only a few frames + fgframes = range(1,26) # frames of fgout solution to use in animation + +# Instantiate object for reading fgout frames: +fgout_grid = fgout_tools.FGoutGrid(fgno, outdir, format) + + +# Plot one frame of fgout data and define the Artists that will need to +# be updated in subsequent frames: + +fgout = fgout_grid.read_frame(fgframes[0]) + +plot_extent = fgout.extent_edges +ylat = fgout.Y.mean() # for aspect ratio of plots + +fig = figure(figsize=(12,7)) + +# --------------------------------- +# axis for planview plot of ocean: +ax = axes([.1,.1,.4,.8]) + +ax.set_xlim(plot_extent[:2]) +ax.set_ylim(plot_extent[2:]) + +plottools.pcolorcells(fgout.X,fgout.Y,fgout.B, cmap=geoplot.land_colors) +clim(0,100) + +eta = ma.masked_where(fgout.h<0.001, fgout.eta) + +eta_plot = plottools.pcolorcells(fgout.X,fgout.Y,eta, + cmap=geoplot.tsunami_colormap) +clim(-0.3,0.3) +cb = colorbar(eta_plot, extend='both', shrink=0.5, + orientation='horizontal', anchor=(0.4,1)) +cb.set_label('meters') +title_text = title('Surface at time %s' % timedelta(seconds=fgout.t)) + +ax.set_aspect(1./cos(ylat*pi/180.)) +ticklabel_format(useOffset=False) +xticks(rotation=20) +ax.set_xlim(plot_extent[:2]) +ax.set_ylim(plot_extent[2:]) + +# --------------------------------- +# Transect: + +x1trans = -110.; y1trans = -20. +x2trans = -72.; y2trans = -35. + +plot([x1trans,x2trans], [y1trans,y2trans],'k-',linewidth=0.8) +text(x1trans+1,y1trans+1,'Transect', ha='center', fontsize=10) + +# define points on transect: +npts = 1000 # number of points on transect +if 0: + # straight line on longitude-latitude plane: + xtrans = linspace(x1trans,x2trans,npts) + ytrans = linspace(y1trans,y2trans,npts) +else: + # great circle on earth: + xtrans,ytrans = util.gctransect(x1trans,y1trans,x2trans,y2trans,npts) + + +def extract_transect(fgout_soln,xtrans,ytrans): + """ + Interpolate from fgout_solution to points on the transect, taking value + from cell the fgout point lies in, giving piecewise constant interpolant + when npts is large. + Alternatively could use method='linear' for linear interpolation. + """ + + eta1d = gridtools.grid_eval_2d(fgout_soln.X, fgout_soln.Y, + fgout_soln.eta, xtrans, ytrans, + method='nearest') + B1d = gridtools.grid_eval_2d(fgout_soln.X, fgout_soln.Y, + fgout_soln.B, xtrans, ytrans, + method='nearest') + eta1d = where(B1d>0, nan, eta1d) # mask onshore region + return B1d, eta1d + +# ---------------------------------------- +# Axes for transect of surface elevation: + +axtrans1 = axes([.55,.6,.4,.3]) + +axtrans1.set_title('Surface on transect') + +axtrans1.set_xlim(x1trans,x2trans) +axtrans1.set_ylim(-1,1) + +Btrans1, etatrans1 = extract_transect(fgout,xtrans,ytrans) +#import pdb; pdb.set_trace() + +Btrans, etatrans = Btrans1, etatrans1 + +# surface plot: +etatrans1_plot, = axtrans1.plot(xtrans, etatrans, 'b') +axtrans1.grid(True) + +# ---------------------------------------- +# Axes for transect of topography: + +axtrans2 = axes([.55,.1,.4,.3]) + +axtrans2.set_title('Topography on transect') + +axtrans2.set_xlim(x1trans,x2trans) +axtrans2.set_ylim(-5000,1000) + +Btrans, etatrans = extract_transect(fgout,xtrans,ytrans) + +# filled regions: +Bfill_plot = axtrans2.fill_between(xtrans, Btrans-1e5, Btrans, + color=[.7,1,.7,1]) # light green +etafill_plot = axtrans2.fill_between(xtrans, Btrans, etatrans, + color=[.7,.7,1,1]) # light blue + +# surface and topo solid lines: +etatrans2_plot, = axtrans2.plot(xtrans, etatrans, 'b') +Btrans2_plot, = axtrans2.plot(xtrans, Btrans, 'g') + + +# create a dummy figure and axes, only needed to update fill_between artists: +figdummy,axdummy = subplots() + + +def update(fgframeno): + """ + Update an exisiting plot with solution from fgout frame fgframeno. + Assumes blit==False in call to animation.FuncAnimation below, + so tuple of update_artists does not need to be returned. + """ + + fgout = fgout_grid.read_frame(fgframeno) + print('Updating plot at time %s' % timedelta(seconds=fgout.t)) + + # reset title to current time: + title_text.set_text('Surface at time %s' % timedelta(seconds=fgout.t)) + + # reset surface eta to current state: + eta = ma.masked_where(fgout.h<0.001, fgout.eta) + eta_plot.set_array(eta.T.flatten()) + + # update transect data: + Btrans, etatrans = extract_transect(fgout,xtrans,ytrans) + + # update lines plotted: + etatrans1_plot.set_data(xtrans,etatrans) + etatrans2_plot.set_data(xtrans,etatrans) + Btrans2_plot.set_data(xtrans,Btrans) + + # update the PolyCollections for fill_between plots: + # There doesn't seem to be an easier way to do this... + dummy = axdummy.fill_between(xtrans, Btrans-1e5, Btrans, color=[.5,1,.5,1]) + dp = dummy.get_paths()[0] + dummy.remove() + Bfill_plot.set_paths([dp.vertices]) + + dummy = axdummy.fill_between(xtrans, Btrans, etatrans, color=[.5,.5,1,1]) + dp = dummy.get_paths()[0] + dummy.remove() + etafill_plot.set_paths([dp.vertices]) + + +if __name__ == '__main__': + + print('Making anim...') + anim = animation.FuncAnimation(fig, update, frames=fgframes, + interval=200, blit=False) + + # Output files: + name = 'fgout_animation_with_transect' + + fname_mp4 = name + '.mp4' + + #fname_html = None + fname_html = name + '.html' + + if fname_mp4: + fps = 5 + print('Making mp4...') + writer = animation.writers['ffmpeg'](fps=fps) + anim.save(fname_mp4, writer=writer) + print("Created %s" % fname_mp4) + + if fname_html: + # html version: + animation_tools.make_html(anim, file_name=fname_html, title=name) diff --git a/examples/tsunami/chile2010_fgmax-fgout/setplot_fgout.py b/examples/tsunami/chile2010_fgmax-fgout/setplot_fgout.py index f35a7684b..666766dc0 100644 --- a/examples/tsunami/chile2010_fgmax-fgout/setplot_fgout.py +++ b/examples/tsunami/chile2010_fgmax-fgout/setplot_fgout.py @@ -164,6 +164,12 @@ def add_zeroline(current_data): otherfigure = plotdata.new_otherfigure(name='fgout_animation.html', fname='fgout_animation.html') + otherfigure = plotdata.new_otherfigure(name='fgout_animation_with_transect.mp4', + fname='fgout_animation_with_transect.mp4') + + otherfigure = plotdata.new_otherfigure(name='fgout_animation_with_transect.html', + fname='fgout_animation_with_transect.html') + #----------------------------------------- From 19ba0ffde8afacfe99de61ecb331facd5c68c9ca Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Sat, 27 Apr 2024 12:59:17 -0700 Subject: [PATCH 08/58] Add dZ_format parameter to DTopography.write function The default is now '%.3f', millimeter resolution, making smaller dtopo files than previously. --- src/python/geoclaw/dtopotools.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/python/geoclaw/dtopotools.py b/src/python/geoclaw/dtopotools.py index a5e3a02f2..37abc634f 100644 --- a/src/python/geoclaw/dtopotools.py +++ b/src/python/geoclaw/dtopotools.py @@ -408,14 +408,14 @@ def read(self, path=None, dtopo_type=None, verbose=False): " given %s." % dtopo_type) - def write(self, path=None, dtopo_type=None): + def write(self, path=None, dtopo_type=None, dZ_format="%.3f"): r"""Write out subfault resulting dtopo to file at *path*. :input: - *path* (path) - Path to the output file to written to. - - *dtopo_type* (int) - Type of topography file to write out. Default - is 3. + - *dtopo_type* (int) - Type of topography file to write out. Default 3. + - *dZ_format* (str) - format for dZ values printed. Default '%.3f' """ @@ -447,9 +447,10 @@ def write(self, path=None, dtopo_type=None): Y_flipped = numpy.flipud(self.Y) dZ_flipped = numpy.flipud(self.dZ[-1,:,:]) + format_string = "%s %s %s\n" % (dZ_format,dZ_format,dZ_format) for j in range(self.Y.shape[0]): for i in range(self.X.shape[1]): - data_file.write("%s %s %s\n" % self.X[j,i], + data_file.write(format_string % self.X[j,i], Y_flipped[j,i], dZ_flipped[j,i]) elif dtopo_type == 1: @@ -462,9 +463,11 @@ def write(self, path=None, dtopo_type=None): #dZ_flipped = numpy.flipud(alpha * self.dZ[:,:]) dZ_flipped = numpy.flipud(self.dZ[n,:,:]) + format_string = "%s %s %s %s\n" \ + % ('%g',dZ_format,dZ_format,dZ_format) for j in range(self.Y.shape[0]): for i in range(self.X.shape[1]): - data_file.write("%s %s %s %s\n" % (self.times[n], + data_file.write(format_string % (self.times[n], self.X[j,i], Y_flipped[j,i], dZ_flipped[j,i])) elif dtopo_type == 2 or dtopo_type == 3: @@ -489,7 +492,7 @@ def write(self, path=None, dtopo_type=None): for (n, time) in enumerate(self.times): #alpha = (time - self.t[0]) / (self.t[-1]) for j in range(self.Y.shape[0]-1, -1, -1): - data_file.write(self.X.shape[1] * '%012.6e ' + data_file.write(self.X.shape[1] * (dZ_format + ' ') % tuple(self.dZ[n,j,:])) data_file.write("\n") From dc82bc03438892679f9df7066d999e74578cc208 Mon Sep 17 00:00:00 2001 From: Ian Bolliger Date: Wed, 1 May 2024 13:24:22 -0700 Subject: [PATCH 09/58] handle 0 radius --- src/2d/shallow/surge/model_storm_module.f90 | 94 +++++++++++++++------ 1 file changed, 69 insertions(+), 25 deletions(-) diff --git a/src/2d/shallow/surge/model_storm_module.f90 b/src/2d/shallow/surge/model_storm_module.f90 index 098aa9d55..c06057079 100644 --- a/src/2d/shallow/surge/model_storm_module.f90 +++ b/src/2d/shallow/surge/model_storm_module.f90 @@ -251,6 +251,22 @@ real(kind=8) function storm_direction(t, storm) result(theta) end function storm_direction + ! ========================================================================== + ! set_pressure + ! Set pressure at a radius r + ! ========================================================================== + pure real(kind=8) function set_pressure(Pc, r, dp, mwr, r, B) result(pres) + implicit none + + real(kind=8), intent(in) :: Pc, r, dp, mwr, r, B + if (r == 0) then + pres = Pc + else + pres = Pc + dp * exp(-(mwr / r)**B) + endif + + end function set_pressure + ! ========================================================================== ! storm_index(t,storm) ! Finds the index of the next storm data point @@ -620,12 +636,17 @@ subroutine set_holland_1980_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = Pc + dp * exp(-(mwr / r)**B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) - ! Speed of wind at this point - wind = sqrt((mwr / r)**B & + ! Set speed of wind at this point, handling case of grid cell centroid + ! at eye of storm + if (r == 0) then + wind = 0 + else + wind = sqrt((mwr / r)**B & * exp(1.d0 - (mwr / r)**B) * mod_mws**2.d0 & + (r * f)**2.d0 / 4.d0) - r * f / 2.d0 + endif call post_process_wind_estimate(maux, mbc, mx, my, i, j, wind, aux, & wind_index, pressure_index, r, radius, tv, mod_mws, theta, & @@ -687,12 +708,17 @@ subroutine set_holland_2008_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = Pc + dp * exp(-(mwr / r)**B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) - ! Speed of wind at this point - wind = sqrt((mwr / r)**B & + ! Set speed of wind at this point, handling case of grid cell centroid + ! at eye of storm + if (r == 0) then + wind = 0 + else + wind = sqrt((mwr / r)**B & * exp(1.d0 - (mwr / r)**B) * mod_mws**2.d0 & + (r * f)**2.d0 / 4.d0) - r * f / 2.d0 + endif call post_process_wind_estimate(maux, mbc, mx, my, i, j, wind, aux, & wind_index, pressure_index, r, radius, tv, mod_mws, theta, & @@ -770,15 +796,21 @@ subroutine set_holland_2010_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = Pc + dp * exp(-(mwr / r)**B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) - ! # HOLLAND 2010 WIND SPEED CALCULATION - if (r <= mwr) then - xx = 0.5 + ! Set speed of wind at this point, handling case of grid cell centroid + ! at eye of storm + if (r == 0) then + wind = 0 else - xx = 0.5 + (r - mwr) * (xn - 0.5) / (rn - mwr) + ! # HOLLAND 2010 WIND SPEED CALCULATION + if (r <= mwr) then + xx = 0.5 + else + xx = 0.5 + (r - mwr) * (xn - 0.5) / (rn - mwr) + endif + wind = mod_mws * ((mwr / r)**B * exp(1.d0 - (mwr / r)**B))**xx endif - wind = mod_mws * ((mwr / r)**B * exp(1.d0 - (mwr / r)**B))**xx call post_process_wind_estimate(maux, mbc, mx, my, i, j, wind, aux, & wind_index, pressure_index, r, radius, tv, mod_mws, theta, & @@ -1205,7 +1237,7 @@ subroutine set_SLOSH_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = Pc + dp * exp(-(mwr / r)**B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) ! Speed of wind at this point ! Note that in reality SLOSH does not directly input mws but instead @@ -1279,13 +1311,19 @@ subroutine set_rankine_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = Pc + dp * exp(-(mwr / r)**B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) - ! Speed of wind at this point - if (r < mwr) then - wind = mws * (r / mwr) + ! Set speed of wind at this point, handling case of grid cell centroid + ! at eye of storm + if (r == 0) then + wind = 0 else - wind = mws * (mwr / r) + ! Speed of wind at this point + if (r < mwr) then + wind = mws * (r / mwr) + else + wind = mws * (mwr / r) + endif endif call post_process_wind_estimate(maux, mbc, mx, my, i, j, wind, aux, & @@ -1361,13 +1399,19 @@ subroutine set_modified_rankine_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = Pc + dp * exp(-(mwr / r)**B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) - ! Speed of wind at this point - if (r < mwr) then - wind = mws * (r / mwr) + ! Set speed of wind at this point, handling case of grid cell centroid + ! at eye of storm + if (r == 0) then + wind = 0 else - wind = mws * (mwr / r)**alpha + ! Speed of wind at this point + if (r < mwr) then + wind = mws * (r / mwr) + else + wind = mws * (mwr / r)**alpha + endif endif call post_process_wind_estimate(maux, mbc, mx, my, i, j, wind, aux, & @@ -1433,7 +1477,7 @@ subroutine set_deMaria_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = Pc + dp * exp(-(mwr / r)**B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) ! Speed of wind at this point ! Commented out version has 2 more free parameters that we would need @@ -1516,7 +1560,7 @@ subroutine set_willoughby_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = Pc + dp * exp(-(mwr / r)**B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) ! Speed of wind at this point ! (.9 and 1.1 are chosen as R1 and R2 rather than fit ) From 556e18280bf403f0971c1492b035b1cdb09161dc Mon Sep 17 00:00:00 2001 From: Ian Bolliger Date: Wed, 1 May 2024 14:18:35 -0700 Subject: [PATCH 10/58] fix type on set_pressure --- src/2d/shallow/surge/model_storm_module.f90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/2d/shallow/surge/model_storm_module.f90 b/src/2d/shallow/surge/model_storm_module.f90 index c06057079..733b3b099 100644 --- a/src/2d/shallow/surge/model_storm_module.f90 +++ b/src/2d/shallow/surge/model_storm_module.f90 @@ -255,7 +255,7 @@ end function storm_direction ! set_pressure ! Set pressure at a radius r ! ========================================================================== - pure real(kind=8) function set_pressure(Pc, r, dp, mwr, r, B) result(pres) + real(kind=8) pure function set_pressure(Pc, r, dp, mwr, r, B) result(pres) implicit none real(kind=8), intent(in) :: Pc, r, dp, mwr, r, B From 4dce9de16991f1611d0fe45352363f09000894d8 Mon Sep 17 00:00:00 2001 From: Ian Bolliger Date: Wed, 1 May 2024 14:21:21 -0700 Subject: [PATCH 11/58] fix duplicate r --- src/2d/shallow/surge/model_storm_module.f90 | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/2d/shallow/surge/model_storm_module.f90 b/src/2d/shallow/surge/model_storm_module.f90 index 733b3b099..2ed1203be 100644 --- a/src/2d/shallow/surge/model_storm_module.f90 +++ b/src/2d/shallow/surge/model_storm_module.f90 @@ -255,10 +255,10 @@ end function storm_direction ! set_pressure ! Set pressure at a radius r ! ========================================================================== - real(kind=8) pure function set_pressure(Pc, r, dp, mwr, r, B) result(pres) + real(kind=8) pure function set_pressure(Pc, r, dp, mwr, B) result(pres) implicit none - real(kind=8), intent(in) :: Pc, r, dp, mwr, r, B + real(kind=8), intent(in) :: Pc, r, dp, mwr, B if (r == 0) then pres = Pc else @@ -636,7 +636,7 @@ subroutine set_holland_1980_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, B) ! Set speed of wind at this point, handling case of grid cell centroid ! at eye of storm @@ -708,7 +708,7 @@ subroutine set_holland_2008_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, B) ! Set speed of wind at this point, handling case of grid cell centroid ! at eye of storm @@ -796,7 +796,7 @@ subroutine set_holland_2010_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, B) ! Set speed of wind at this point, handling case of grid cell centroid ! at eye of storm @@ -1237,7 +1237,7 @@ subroutine set_SLOSH_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, B) ! Speed of wind at this point ! Note that in reality SLOSH does not directly input mws but instead @@ -1311,7 +1311,7 @@ subroutine set_rankine_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, B) ! Set speed of wind at this point, handling case of grid cell centroid ! at eye of storm @@ -1399,7 +1399,7 @@ subroutine set_modified_rankine_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, B) ! Set speed of wind at this point, handling case of grid cell centroid ! at eye of storm @@ -1477,7 +1477,7 @@ subroutine set_deMaria_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, B) ! Speed of wind at this point ! Commented out version has 2 more free parameters that we would need @@ -1560,7 +1560,7 @@ subroutine set_willoughby_fields(maux, mbc, mx, my, xlower, ylower, & call calculate_polar_coordinate(x, y, sloc, r, theta) ! Set pressure field - aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, r, B) + aux(pressure_index,i,j) = set_pressure(Pc, r, dp, mwr, B) ! Speed of wind at this point ! (.9 and 1.1 are chosen as R1 and R2 rather than fit ) From 5cd442488d34d0973c99950c6e5fe0561ca51675 Mon Sep 17 00:00:00 2001 From: Ian Bolliger Date: Fri, 3 May 2024 11:37:07 -0700 Subject: [PATCH 12/58] make comparison fp-safe --- src/2d/shallow/surge/model_storm_module.f90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/2d/shallow/surge/model_storm_module.f90 b/src/2d/shallow/surge/model_storm_module.f90 index 2ed1203be..70ac1ee06 100644 --- a/src/2d/shallow/surge/model_storm_module.f90 +++ b/src/2d/shallow/surge/model_storm_module.f90 @@ -259,7 +259,7 @@ real(kind=8) pure function set_pressure(Pc, r, dp, mwr, B) result(pres) implicit none real(kind=8), intent(in) :: Pc, r, dp, mwr, B - if (r == 0) then + if (r < 1d-16) then pres = Pc else pres = Pc + dp * exp(-(mwr / r)**B) From d683a2079200dd1c2fad7a6baf10b5ca4ad7354b Mon Sep 17 00:00:00 2001 From: Ian Bolliger Date: Fri, 3 May 2024 17:36:42 -0700 Subject: [PATCH 13/58] handle underflow --- src/2d/shallow/surge/model_storm_module.f90 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/2d/shallow/surge/model_storm_module.f90 b/src/2d/shallow/surge/model_storm_module.f90 index 70ac1ee06..f941a8d83 100644 --- a/src/2d/shallow/surge/model_storm_module.f90 +++ b/src/2d/shallow/surge/model_storm_module.f90 @@ -259,7 +259,10 @@ real(kind=8) pure function set_pressure(Pc, r, dp, mwr, B) result(pres) implicit none real(kind=8), intent(in) :: Pc, r, dp, mwr, B - if (r < 1d-16) then + + ! for any situation that could raise an underflow error, we set the + ! second term to 0 + if ((mwr / r)**B > 100) then pres = Pc else pres = Pc + dp * exp(-(mwr / r)**B) From 843a0e89662cb1af56006c0d3b004f8b1d3fc2f7 Mon Sep 17 00:00:00 2001 From: Ian Bolliger Date: Tue, 7 May 2024 08:25:52 -0700 Subject: [PATCH 14/58] avoid underflow in wind setting too --- src/2d/shallow/surge/model_storm_module.f90 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/2d/shallow/surge/model_storm_module.f90 b/src/2d/shallow/surge/model_storm_module.f90 index f941a8d83..cce7445a9 100644 --- a/src/2d/shallow/surge/model_storm_module.f90 +++ b/src/2d/shallow/surge/model_storm_module.f90 @@ -643,7 +643,7 @@ subroutine set_holland_1980_fields(maux, mbc, mx, my, xlower, ylower, & ! Set speed of wind at this point, handling case of grid cell centroid ! at eye of storm - if (r == 0) then + if ((mwr / r)**B > 100) then wind = 0 else wind = sqrt((mwr / r)**B & @@ -715,7 +715,7 @@ subroutine set_holland_2008_fields(maux, mbc, mx, my, xlower, ylower, & ! Set speed of wind at this point, handling case of grid cell centroid ! at eye of storm - if (r == 0) then + if ((mwr / r)**B > 100) then wind = 0 else wind = sqrt((mwr / r)**B & @@ -803,7 +803,7 @@ subroutine set_holland_2010_fields(maux, mbc, mx, my, xlower, ylower, & ! Set speed of wind at this point, handling case of grid cell centroid ! at eye of storm - if (r == 0) then + if ((mwr / r)**B > 100) then wind = 0 else ! # HOLLAND 2010 WIND SPEED CALCULATION @@ -1318,7 +1318,7 @@ subroutine set_rankine_fields(maux, mbc, mx, my, xlower, ylower, & ! Set speed of wind at this point, handling case of grid cell centroid ! at eye of storm - if (r == 0) then + if ((mwr / r)**B > 100) then wind = 0 else ! Speed of wind at this point @@ -1406,7 +1406,7 @@ subroutine set_modified_rankine_fields(maux, mbc, mx, my, xlower, ylower, & ! Set speed of wind at this point, handling case of grid cell centroid ! at eye of storm - if (r == 0) then + if ((mwr / r)**B > 100) then wind = 0 else ! Speed of wind at this point From 8b6bc84ebf64891947c2fdc4051fb68da2381018 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Tue, 7 May 2024 17:45:10 -0700 Subject: [PATCH 15/58] point to riemann/src for Riemann solvers --- src/2d/bouss/Makefile.bouss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/2d/bouss/Makefile.bouss b/src/2d/bouss/Makefile.bouss index 225e7a539..07a4e6c01 100644 --- a/src/2d/bouss/Makefile.bouss +++ b/src/2d/bouss/Makefile.bouss @@ -167,6 +167,6 @@ COMMON_SOURCES += \ $(BOUSSLIB)/resetBoussStuff.f \ $(BOUSSLIB)/simpleBound.f90 \ $(BOUSSLIB)/umfpack_support.f \ - $(BOUSSLIB)/rpn2_geoclaw.f \ - $(BOUSSLIB)/rpt2_geoclaw_sym.f \ - $(BOUSSLIB)/geoclaw_riemann_utils.f \ + $(CLAW)/riemann/src/rpn2_geoclaw.f \ + $(CLAW)/riemann/src/rpt2_geoclaw.f \ + $(CLAW)/riemann/src/geoclaw_riemann_utils.f \ From 4148a02ebb42656fe76c05f6f62c0a9e24ed1281 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Tue, 7 May 2024 17:51:55 -0700 Subject: [PATCH 16/58] Remove Riemann solvers from src/2d/bouss, Makefile.common points to riemann/src Note that rpn2_geoclaw.f and geoclaw_riemann_utils.f were update in riemann/src to handle 5 waves and rpt2_geoclaw_sym.f was discarded in favor of standard rpt2.f, which works fine with 5 waves. --- src/2d/bouss/geoclaw_riemann_utils.f | 658 --------------------------- src/2d/bouss/rpn2_geoclaw.f | 305 ------------- src/2d/bouss/rpt2_geoclaw_sym.f | 260 ----------- 3 files changed, 1223 deletions(-) delete mode 100644 src/2d/bouss/geoclaw_riemann_utils.f delete mode 100644 src/2d/bouss/rpn2_geoclaw.f delete mode 100644 src/2d/bouss/rpt2_geoclaw_sym.f diff --git a/src/2d/bouss/geoclaw_riemann_utils.f b/src/2d/bouss/geoclaw_riemann_utils.f deleted file mode 100644 index 4745cf43d..000000000 --- a/src/2d/bouss/geoclaw_riemann_utils.f +++ /dev/null @@ -1,658 +0,0 @@ -c----------------------------------------------------------------------- - subroutine riemann_aug_JCP(maxiter,meqn,mwaves,hL,hR,huL,huR, - & hvL,hvR,bL,bR,uL,uR,vL,vR,phiL,phiR,pL,pR,sE1,sE2,drytol,g,rho, - & sw,fw) - - ! solve shallow water equations given single left and right states - ! This solver is described in J. Comput. Phys. (6): 3089-3113, March 2008 - ! Augmented Riemann Solvers for the Shallow Equations with Steady States and Inundation - - ! To use the original solver call with maxiter=1. - - ! This solver allows iteration when maxiter > 1. The iteration seems to help with - ! instabilities that arise (with any solver) as flow becomes transcritical over variable topo - ! due to loss of hyperbolicity. - - implicit none - - !input - integer meqn,mwaves,maxiter - double precision fw(meqn,mwaves) - double precision sw(mwaves) - double precision hL,hR,huL,huR,bL,bR,uL,uR,phiL,phiR,sE1,sE2 - double precision hvL,hvR,vL,vR,pL,pR - double precision drytol,g,rho - - - !local - integer m,mw,k,iter - double precision A(3,3) - double precision r(3,3) - double precision lambda(3) - double precision del(3) - double precision beta(3) - - double precision delh,delhu,delphi,delb,delnorm - double precision rare1st,rare2st,sdelta,raremin,raremax - double precision criticaltol,convergencetol,raretol - double precision criticaltol_2, hustar_interface - double precision s1s2bar,s1s2tilde,hbar,hLstar,hRstar,hustar - double precision huRstar,huLstar,uRstar,uLstar,hstarHLL - double precision deldelh,deldelphi,delP - double precision s1m,s2m,hm - double precision det1,det2,det3,determinant - - logical rare1,rare2,rarecorrector,rarecorrectortest,sonic - - !determine del vectors - delh = hR-hL - delhu = huR-huL - delphi = phiR-phiL - delb = bR-bL - delP = pR - pL - delnorm = delh**2 + delphi**2 - - call riemanntype(hL,hR,uL,uR,hm,s1m,s2m,rare1,rare2, - & 1,drytol,g) - - - lambda(1)= min(sE1,s2m) !Modified Einfeldt speed - lambda(3)= max(sE2,s1m) !Modified Eindfeldt speed - sE1=lambda(1) - sE2=lambda(3) - lambda(2) = 0.d0 ! ### Fix to avoid uninitialized value in loop on mw -- Correct?? ### - - - hstarHLL = max((huL-huR+sE2*hR-sE1*hL)/(sE2-sE1),0.d0) ! middle state in an HLL solve - -c !determine the middle entropy corrector wave------------------------ - rarecorrectortest=.false. - rarecorrector=.false. - if (rarecorrectortest) then - sdelta=lambda(3)-lambda(1) - raremin = 0.5d0 - raremax = 0.9d0 - if (rare1.and.sE1*s1m.lt.0.d0) raremin=0.2d0 - if (rare2.and.sE2*s2m.lt.0.d0) raremin=0.2d0 - if (rare1.or.rare2) then - !see which rarefaction is larger - rare1st=3.d0*(sqrt(g*hL)-sqrt(g*hm)) - rare2st=3.d0*(sqrt(g*hR)-sqrt(g*hm)) - if (max(rare1st,rare2st).gt.raremin*sdelta.and. - & max(rare1st,rare2st).lt.raremax*sdelta) then - rarecorrector=.true. - if (rare1st.gt.rare2st) then - lambda(2)=s1m - elseif (rare2st.gt.rare1st) then - lambda(2)=s2m - else - lambda(2)=0.5d0*(s1m+s2m) - endif - endif - endif - if (hstarHLL.lt.min(hL,hR)/5.d0) rarecorrector=.false. - endif - -c ## Is this correct 2-wave when rarecorrector == .true. ?? - do mw=1,mwaves - r(1,mw)=1.d0 - r(2,mw)=lambda(mw) - r(3,mw)=(lambda(mw))**2 - enddo - if (.not.rarecorrector) then - lambda(2) = 0.5d0*(lambda(1)+lambda(3)) -c lambda(2) = max(min(0.5d0*(s1m+s2m),sE2),sE1) - r(1,2)=0.d0 - r(2,2)=0.d0 - r(3,2)=1.d0 - endif -c !--------------------------------------------------- - - -c !determine the steady state wave ------------------- - !criticaltol = 1.d-6 - ! MODIFIED: - criticaltol = max(drytol*g, 1d-6) - criticaltol_2 = sqrt(criticaltol) - deldelh = -delb - deldelphi = -0.5d0 * (hR + hL) * (g * delb + delp / rho) - -c !determine a few quanitites needed for steady state wave if iterated - hLstar=hL - hRstar=hR - uLstar=uL - uRstar=uR - huLstar=uLstar*hLstar - huRstar=uRstar*hRstar - - !iterate to better determine the steady state wave - convergencetol=1.d-6 - do iter=1,maxiter - !determine steady state wave (this will be subtracted from the delta vectors) - if (min(hLstar,hRstar).lt.drytol.and.rarecorrector) then - rarecorrector=.false. - hLstar=hL - hRstar=hR - uLstar=uL - uRstar=uR - huLstar=uLstar*hLstar - huRstar=uRstar*hRstar - lambda(2) = 0.5d0*(lambda(1)+lambda(3)) -c lambda(2) = max(min(0.5d0*(s1m+s2m),sE2),sE1) - r(1,2)=0.d0 - r(2,2)=0.d0 - r(3,2)=1.d0 - endif - - hbar = max(0.5d0*(hLstar+hRstar),0.d0) - s1s2bar = 0.25d0*(uLstar+uRstar)**2 - g*hbar - s1s2tilde= max(0.d0,uLstar*uRstar) - g*hbar - -c !find if sonic problem - ! MODIFIED from 5.3.1 version - sonic = .false. - if (abs(s1s2bar) <= criticaltol) then - sonic = .true. - else if (s1s2bar*s1s2tilde <= criticaltol**2) then - sonic = .true. - else if (s1s2bar*sE1*sE2 <= criticaltol**2) then - sonic = .true. - else if (min(abs(sE1),abs(sE2)) < criticaltol_2) then - sonic = .true. - else if (sE1 < criticaltol_2 .and. s1m > -criticaltol_2) then - sonic = .true. - else if (sE2 > -criticaltol_2 .and. s2m < criticaltol_2) then - sonic = .true. - else if ((uL+dsqrt(g*hL))*(uR+dsqrt(g*hR)) < 0.d0) then - sonic = .true. - else if ((uL- dsqrt(g*hL))*(uR- dsqrt(g*hR)) < 0.d0) then - sonic = .true. - end if - -c !find jump in h, deldelh - if (sonic) then - deldelh = -delb - else - deldelh = delb*g*hbar/s1s2bar - endif -c !find bounds in case of critical state resonance, or negative states - if (sE1.lt.-criticaltol.and.sE2.gt.criticaltol) then - deldelh = min(deldelh,hstarHLL*(sE2-sE1)/sE2) - deldelh = max(deldelh,hstarHLL*(sE2-sE1)/sE1) - elseif (sE1.ge.criticaltol) then - deldelh = min(deldelh,hstarHLL*(sE2-sE1)/sE1) - deldelh = max(deldelh,-hL) - elseif (sE2.le.-criticaltol) then - deldelh = min(deldelh,hR) - deldelh = max(deldelh,hstarHLL*(sE2-sE1)/sE2) - endif - -c ! adjust deldelh for well-balancing of atmospheric pressure difference - deldelh = deldelh - delP/(rho*g) - -c !find jump in phi, deldelphi - if (sonic) then - deldelphi = -g*hbar*delb - else - deldelphi = -delb*g*hbar*s1s2tilde/s1s2bar - endif -c !find bounds in case of critical state resonance, or negative states - deldelphi=min(deldelphi,g*max(-hLstar*delb,-hRstar*delb)) - deldelphi=max(deldelphi,g*min(-hLstar*delb,-hRstar*delb)) - deldelphi = deldelphi - hbar * delp / rho - - del(1)=delh-deldelh - del(2)=delhu - del(3)=delphi-deldelphi - -c !Determine determinant of eigenvector matrix======== - det1=r(1,1)*(r(2,2)*r(3,3)-r(2,3)*r(3,2)) - det2=r(1,2)*(r(2,1)*r(3,3)-r(2,3)*r(3,1)) - det3=r(1,3)*(r(2,1)*r(3,2)-r(2,2)*r(3,1)) - determinant=det1-det2+det3 - -c !solve for beta(k) using Cramers Rule================= - do k=1,3 - do mw=1,3 - A(1,mw)=r(1,mw) - A(2,mw)=r(2,mw) - A(3,mw)=r(3,mw) - enddo - A(1,k)=del(1) - A(2,k)=del(2) - A(3,k)=del(3) - det1=A(1,1)*(A(2,2)*A(3,3)-A(2,3)*A(3,2)) - det2=A(1,2)*(A(2,1)*A(3,3)-A(2,3)*A(3,1)) - det3=A(1,3)*(A(2,1)*A(3,2)-A(2,2)*A(3,1)) - beta(k)=(det1-det2+det3)/determinant - enddo - - !exit if things aren't changing - if (abs(del(1)**2+del(3)**2-delnorm).lt.convergencetol) exit - delnorm = del(1)**2+del(3)**2 - !find new states qLstar and qRstar on either side of interface - hLstar=hL - hRstar=hR - uLstar=uL - uRstar=uR - huLstar=uLstar*hLstar - huRstar=uRstar*hRstar - do mw=1,mwaves - if (lambda(mw).lt.0.d0) then - hLstar= hLstar + beta(mw)*r(1,mw) - huLstar= huLstar + beta(mw)*r(2,mw) - endif - enddo - do mw=mwaves,1,-1 - if (lambda(mw).gt.0.d0) then - hRstar= hRstar - beta(mw)*r(1,mw) - huRstar= huRstar - beta(mw)*r(2,mw) - endif - enddo - - if (hLstar.gt.drytol) then - uLstar=huLstar/hLstar - else - hLstar=max(hLstar,0.d0) - uLstar=0.d0 - endif - if (hRstar.gt.drytol) then - uRstar=huRstar/hRstar - else - hRstar=max(hRstar,0.d0) - uRstar=0.d0 - endif - - enddo ! end iteration on Riemann problem - - fw(:,:) = 0.d0 ! initialize before setting some waves - - do mw=1,mwaves - sw(mw)=lambda(mw) - fw(1,mw)=beta(mw)*r(2,mw) - fw(2,mw)=beta(mw)*r(3,mw) - fw(3,mw)=beta(mw)*r(2,mw) - enddo - !find transverse components (ie huv jumps). - ! MODIFIED from 5.3.1 version - fw(3,1)=fw(3,1)*vL - fw(3,3)=fw(3,3)*vR - fw(3,2)= 0.d0 - - hustar_interface = huL + fw(1,1) ! = huR - fw(1,3) - if (hustar_interface <= 0.0d0) then - fw(3,1) = fw(3,1) + (hR*uR*vR - hL*uL*vL - fw(3,1)- fw(3,3)) - else - fw(3,3) = fw(3,3) + (hR*uR*vR - hL*uL*vL - fw(3,1)- fw(3,3)) - end if - - - return - - end !subroutine riemann_aug_JCP------------------------------------------------- - - -c----------------------------------------------------------------------- - subroutine riemann_ssqfwave(maxiter,meqn,mwaves,hL,hR,huL,huR, - & hvL,hvR,bL,bR,uL,uR,vL,vR,phiL,phiR,pL,pR,sE1,sE2,drytol,g, - & rho,sw,fw) - - ! solve shallow water equations given single left and right states - ! steady state wave is subtracted from delta [q,f]^T before decomposition - - implicit none - - !input - integer meqn,mwaves,maxiter - - double precision hL,hR,huL,huR,bL,bR,uL,uR,phiL,phiR,sE1,sE2 - double precision vL,vR,hvL,hvR,pL,pR - double precision drytol,g,rho - - !local - integer iter - - logical sonic - - double precision delh,delhu,delphi,delb,delhdecomp,delphidecomp - double precision s1s2bar,s1s2tilde,hbar,hLstar,hRstar,hustar - double precision uRstar,uLstar,hstarHLL - double precision deldelh,deldelphi,delP - double precision alpha1,alpha2,beta1,beta2,delalpha1,delalpha2 - double precision criticaltol,convergencetol - double precision sL,sR - double precision uhat,chat,sRoe1,sRoe2 - - double precision sw(mwaves) - double precision fw(meqn,mwaves) - - !determine del vectors - delh = hR-hL - delhu = huR-huL - delphi = phiR-phiL - delb = bR-bL - delP = pR - pL - - convergencetol= 1.d-16 - criticaltol = 1.d-99 - - deldelh = -delb - deldelphi = -0.5d0 * (hR + hL) * (g * delb + delP / rho) - -! !if no source term, skip determining steady state wave - if (abs(delb).gt.0.d0) then -! - !determine a few quanitites needed for steady state wave if iterated - hLstar=hL - hRstar=hR - uLstar=uL - uRstar=uR - hstarHLL = max((huL-huR+sE2*hR-sE1*hL)/(sE2-sE1),0.d0) ! middle state in an HLL solve - - alpha1=0.d0 - alpha2=0.d0 - -! !iterate to better determine Riemann problem - do iter=1,maxiter - - !determine steady state wave (this will be subtracted from the delta vectors) - hbar = max(0.5d0*(hLstar+hRstar),0.d0) - s1s2bar = 0.25d0*(uLstar+uRstar)**2 - g*hbar - s1s2tilde= max(0.d0,uLstar*uRstar) - g*hbar - - -c !find if sonic problem - sonic=.false. - if (abs(s1s2bar).le.criticaltol) sonic=.true. - if (s1s2bar*s1s2tilde.le.criticaltol) sonic=.true. - if (s1s2bar*sE1*sE2.le.criticaltol) sonic = .true. - if (min(abs(sE1),abs(sE2)).lt.criticaltol) sonic=.true. - -c !find jump in h, deldelh - if (sonic) then - deldelh = -delb - else - deldelh = delb*g*hbar/s1s2bar - endif -! !bounds in case of critical state resonance, or negative states - if (sE1.lt.-criticaltol.and.sE2.gt.criticaltol) then - deldelh = min(deldelh,hstarHLL*(sE2-sE1)/sE2) - deldelh = max(deldelh,hstarHLL*(sE2-sE1)/sE1) - elseif (sE1.ge.criticaltol) then - deldelh = min(deldelh,hstarHLL*(sE2-sE1)/sE1) - deldelh = max(deldelh,-hL) - elseif (sE2.le.-criticaltol) then - deldelh = min(deldelh,hR) - deldelh = max(deldelh,hstarHLL*(sE2-sE1)/sE2) - endif - -c !find jump in phi, deldelphi - if (sonic) then - deldelphi = -g*hbar*delb - else - deldelphi = -delb*g*hbar*s1s2tilde/s1s2bar - endif -! !bounds in case of critical state resonance, or negative states - deldelphi=min(deldelphi,g*max(-hLstar*delb,-hRstar*delb)) - deldelphi=max(deldelphi,g*min(-hLstar*delb,-hRstar*delb)) - -!---------determine fwaves ------------------------------------------ - -! !first decomposition - delhdecomp = delh-deldelh - delalpha1 = (sE2*delhdecomp - delhu)/(sE2-sE1)-alpha1 - alpha1 = alpha1 + delalpha1 - delalpha2 = (delhu - sE1*delhdecomp)/(sE2-sE1)-alpha2 - alpha2 = alpha2 + delalpha2 - - !second decomposition - delphidecomp = delphi - deldelphi - beta1 = (sE2*delhu - delphidecomp)/(sE2-sE1) - beta2 = (delphidecomp - sE1*delhu)/(sE2-sE1) - - if ((delalpha2**2+delalpha1**2).lt.convergencetol**2) then - exit - endif -! - if (sE2.gt.0.d0.and.sE1.lt.0.d0) then - hLstar=hL+alpha1 - hRstar=hR-alpha2 -c hustar=huL+alpha1*sE1 - hustar = huL + beta1 - elseif (sE1.ge.0.d0) then - hLstar=hL - hustar=huL - hRstar=hR - alpha1 - alpha2 - elseif (sE2.le.0.d0) then - hRstar=hR - hustar=huR - hLstar=hL + alpha1 + alpha2 - endif -! - if (hLstar.gt.drytol) then - uLstar=hustar/hLstar - else - hLstar=max(hLstar,0.d0) - uLstar=0.d0 - endif -! - if (hRstar.gt.drytol) then - uRstar=hustar/hRstar - else - hRstar=max(hRstar,0.d0) - uRstar=0.d0 - endif - - enddo - endif - - delhdecomp = delh - deldelh - delphidecomp = delphi - deldelphi - - !first decomposition - alpha1 = (sE2*delhdecomp - delhu)/(sE2-sE1) - alpha2 = (delhu - sE1*delhdecomp)/(sE2-sE1) - - !second decomposition - beta1 = (sE2*delhu - delphidecomp)/(sE2-sE1) - beta2 = (delphidecomp - sE1*delhu)/(sE2-sE1) - - ! 1st nonlinear wave - fw(1,1) = alpha1*sE1 - fw(2,1) = beta1*sE1 - fw(3,1) = fw(1,1)*vL - ! 2nd nonlinear wave - fw(1,3) = alpha2*sE2 - fw(2,3) = beta2*sE2 - fw(3,3) = fw(1,3)*vR - ! advection of transverse wave - fw(1,2) = 0.d0 - fw(2,2) = 0.d0 - fw(3,2) = hR*uR*vR - hL*uL*vL -fw(3,1)-fw(3,3) - !speeds - sw(1)=sE1 - sw(2)=0.5d0*(sE1+sE2) - sw(3)=sE2 - - return - - end subroutine !------------------------------------------------- - - -c----------------------------------------------------------------------- - subroutine riemann_fwave(meqn,mwaves,hL,hR,huL,huR,hvL,hvR, - & bL,bR,uL,uR,vL,vR,phiL,phiR,pL,pR,s1,s2,drytol,g,rho, - & sw,fw) - - ! solve shallow water equations given single left and right states - ! solution has two waves. - ! flux - source is decomposed. - - implicit none - - !input - integer meqn,mwaves - - double precision hL,hR,huL,huR,bL,bR,uL,uR,phiL,phiR,s1,s2 - double precision hvL,hvR,vL,vR,pL,pR - double precision drytol,g,rho - - double precision sw(mwaves) - double precision fw(meqn,mwaves) - - !local - double precision delh,delhu,delphi,delb,delhdecomp,delphidecomp - double precision deldelh,deldelphi,delP - double precision beta1,beta2 - - - !determine del vectors - delh = hR-hL - delhu = huR-huL - delphi = phiR-phiL - delb = bR-bL - delP = pR - pL - - deldelphi = -0.5d0 * (hR + hL) * (g * delb + delP / rho) - delphidecomp = delphi - deldelphi - - !flux decomposition - beta1 = (s2*delhu - delphidecomp)/(s2-s1) - beta2 = (delphidecomp - s1*delhu)/(s2-s1) - - sw(1)=s1 - sw(2)=0.5d0*(s1+s2) - sw(3)=s2 - ! 1st nonlinear wave - fw(1,1) = beta1 - fw(2,1) = beta1*s1 - fw(3,1) = beta1*vL - ! 2nd nonlinear wave - fw(1,3) = beta2 - fw(2,3) = beta2*s2 - fw(3,3) = beta2*vR - ! advection of transverse wave - fw(1,2) = 0.d0 - fw(2,2) = 0.d0 - fw(3,2) = hR*uR*vR - hL*uL*vL -fw(3,1)-fw(3,3) - return - - end !subroutine ------------------------------------------------- - - - - - -c============================================================================= - subroutine riemanntype(hL,hR,uL,uR,hm,s1m,s2m,rare1,rare2, - & maxiter,drytol,g) - - !determine the Riemann structure (wave-type in each family) - - - implicit none - - !input - double precision hL,hR,uL,uR,drytol,g - integer maxiter - - !output - double precision s1m,s2m - logical rare1,rare2 - - !local - double precision hm,u1m,u2m,um,delu - double precision h_max,h_min,h0,F_max,F_min,dfdh,F0,slope,gL,gR - integer iter - - - -c !Test for Riemann structure - - h_min=min(hR,hL) - h_max=max(hR,hL) - delu=uR-uL - - if (h_min.le.drytol) then - hm=0.d0 - um=0.d0 - s1m=uR+uL-2.d0*sqrt(g*hR)+2.d0*sqrt(g*hL) - s2m=uR+uL-2.d0*sqrt(g*hR)+2.d0*sqrt(g*hL) - if (hL.le.0.d0) then - rare2=.true. - rare1=.false. - else - rare1=.true. - rare2=.false. - endif - - else - F_min= delu+2.d0*(sqrt(g*h_min)-sqrt(g*h_max)) - F_max= delu + - & (h_max-h_min)*(sqrt(.5d0*g*(h_max+h_min)/(h_max*h_min))) - - if (F_min.gt.0.d0) then !2-rarefactions - - hm=(1.d0/(16.d0*g))* - & max(0.d0,-delu+2.d0*(sqrt(g*hL)+sqrt(g*hR)))**2 - um=sign(1.d0,hm)*(uL+2.d0*(sqrt(g*hL)-sqrt(g*hm))) - - s1m=uL+2.d0*sqrt(g*hL)-3.d0*sqrt(g*hm) - s2m=uR-2.d0*sqrt(g*hR)+3.d0*sqrt(g*hm) - - rare1=.true. - rare2=.true. - - elseif (F_max.le.0.d0) then !2 shocks - -c !root finding using a Newton iteration on sqrt(h)=== - h0=h_max - do iter=1,maxiter - gL=sqrt(.5d0*g*(1/h0 + 1/hL)) - gR=sqrt(.5d0*g*(1/h0 + 1/hR)) - F0=delu+(h0-hL)*gL + (h0-hR)*gR - dfdh=gL-g*(h0-hL)/(4.d0*(h0**2)*gL)+ - & gR-g*(h0-hR)/(4.d0*(h0**2)*gR) - slope=2.d0*sqrt(h0)*dfdh - h0=(sqrt(h0)-F0/slope)**2 - enddo - hm=h0 - u1m=uL-(hm-hL)*sqrt((.5d0*g)*(1/hm + 1/hL)) - u2m=uR+(hm-hR)*sqrt((.5d0*g)*(1/hm + 1/hR)) - um=.5d0*(u1m+u2m) - - s1m=u1m-sqrt(g*hm) - s2m=u2m+sqrt(g*hm) - rare1=.false. - rare2=.false. - - else !one shock one rarefaction - h0=h_min - - do iter=1,maxiter - F0=delu + 2.d0*(sqrt(g*h0)-sqrt(g*h_max)) - & + (h0-h_min)*sqrt(.5d0*g*(1/h0+1/h_min)) - slope=(F_max-F0)/(h_max-h_min) - h0=h0-F0/slope - enddo - - hm=h0 - if (hL.gt.hR) then - um=uL+2.d0*sqrt(g*hL)-2.d0*sqrt(g*hm) - s1m=uL+2.d0*sqrt(g*hL)-3.d0*sqrt(g*hm) - s2m=uL+2.d0*sqrt(g*hL)-sqrt(g*hm) - rare1=.true. - rare2=.false. - else - s2m=uR-2.d0*sqrt(g*hR)+3.d0*sqrt(g*hm) - s1m=uR-2.d0*sqrt(g*hR)+sqrt(g*hm) - um=uR-2.d0*sqrt(g*hR)+2.d0*sqrt(g*hm) - rare2=.true. - rare1=.false. - endif - endif - endif - - return - - end ! subroutine riemanntype---------------------------------------------------------------- diff --git a/src/2d/bouss/rpn2_geoclaw.f b/src/2d/bouss/rpn2_geoclaw.f deleted file mode 100644 index 076fcd79f..000000000 --- a/src/2d/bouss/rpn2_geoclaw.f +++ /dev/null @@ -1,305 +0,0 @@ -c====================================================================== - subroutine rpn2(ixy,maxm,meqn,mwaves,maux,mbc,mx, - & ql,qr,auxl,auxr,fwave,s,amdq,apdq) -c====================================================================== -c -c Solves normal Riemann problems for the 2D SHALLOW WATER equations -c with topography: -c # h_t + (hu)_x + (hv)_y = 0 # -c # (hu)_t + (hu^2 + 0.5gh^2)_x + (huv)_y = -ghb_x # -c # (hv)_t + (huv)_x + (hv^2 + 0.5gh^2)_y = -ghb_y # - -c On input, ql contains the state vector at the left edge of each cell -c qr contains the state vector at the right edge of each cell -c -c This data is along a slice in the x-direction if ixy=1 -c or the y-direction if ixy=2. - -c Note that the i'th Riemann problem has left state qr(i-1,:) -c and right state ql(i,:) -c From the basic clawpack routines, this routine is called with -c ql = qr -c -c -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -! ! -! # This Riemann solver is for the shallow water equations. ! -! ! -! It allows the user to easily select a Riemann solver in ! -! riemannsolvers_geo.f. this routine initializes all the variables ! -! for the shallow water equations, accounting for wet dry boundary ! -! dry cells, wave speeds etc. ! -! ! -! David George, Vancouver WA, Feb. 2009 ! -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - use geoclaw_module, only: g => grav, drytol => dry_tolerance, rho - use geoclaw_module, only: earth_radius, deg2rad - use amr_module, only: mcapa - - use storm_module, only: pressure_forcing, pressure_index - - implicit none - - !input - integer maxm,meqn,maux,mwaves,mbc,mx,ixy - - double precision fwave(meqn, mwaves, 1-mbc:maxm+mbc) - double precision s(mwaves, 1-mbc:maxm+mbc) - double precision ql(meqn, 1-mbc:maxm+mbc) - double precision qr(meqn, 1-mbc:maxm+mbc) - double precision apdq(meqn,1-mbc:maxm+mbc) - double precision amdq(meqn,1-mbc:maxm+mbc) - double precision auxl(maux,1-mbc:maxm+mbc) - double precision auxr(maux,1-mbc:maxm+mbc) - - !local only - integer m,i,mw,maxiter,mu,nv - double precision wall(3) - double precision fw(meqn,3) - double precision sw(3) - - double precision hR,hL,huR,huL,uR,uL,hvR,hvL,vR,vL,phiR,phiL,pL,pR - double precision bR,bL,sL,sR,sRoe1,sRoe2,sE1,sE2,uhat,chat - double precision s1m,s2m - double precision hstar,hstartest,hstarHLL,sLtest,sRtest - double precision tw,dxdc - - logical :: debug - - logical rare1,rare2 - - ! In case there is no pressure forcing - pL = 0.d0 - pR = 0.d0 - debug = .false. - - ! initialize all components to 0 - fw(:,:) = 0.d0 - fwave(:,:,:) = 0.d0 - s(:,:) = 0.d0 - amdq(:,:) = 0.d0 - apdq(:,:) = 0.d0 - - !loop through Riemann problems at each grid cell - do i=2-mbc,mx+mbc - -!-----------------------Initializing----------------------------------- - !inform of a bad riemann problem from the start - if((qr(1,i-1).lt.0.d0).or.(ql(1,i) .lt. 0.d0)) then - write(*,*) 'Negative input: hl,hr,i=',qr(1,i-1),ql(1,i),i - endif - - -c !set normal direction - if (ixy.eq.1) then - mu=2 - nv=3 - else - mu=3 - nv=2 - endif - - !zero (small) negative values if they exist - if (qr(1,i-1).lt.0.d0) then - qr(1,i-1)=0.d0 - qr(2,i-1)=0.d0 - qr(3,i-1)=0.d0 - endif - - if (ql(1,i).lt.0.d0) then - ql(1,i)=0.d0 - ql(2,i)=0.d0 - ql(3,i)=0.d0 - endif - - !skip problem if in a completely dry area - if (qr(1,i-1) <= drytol .and. ql(1,i) <= drytol) then - go to 30 - endif - - !Riemann problem variables - hL = qr(1,i-1) - hR = ql(1,i) - huL = qr(mu,i-1) - huR = ql(mu,i) - bL = auxr(1,i-1) - bR = auxl(1,i) - if (pressure_forcing) then - pL = auxr(pressure_index, i-1) - pR = auxl(pressure_index, i) - end if - - hvL=qr(nv,i-1) - hvR=ql(nv,i) - - !check for wet/dry boundary - if (hR.gt.drytol) then - uR=huR/hR - vR=hvR/hR - phiR = 0.5d0*g*hR**2 + huR**2/hR - else - hR = 0.d0 - huR = 0.d0 - hvR = 0.d0 - uR = 0.d0 - vR = 0.d0 - phiR = 0.d0 - endif - - if (hL.gt.drytol) then - uL=huL/hL - vL=hvL/hL - phiL = 0.5d0*g*hL**2 + huL**2/hL - else - hL=0.d0 - huL=0.d0 - hvL=0.d0 - uL=0.d0 - vL=0.d0 - phiL = 0.d0 - endif - - wall(1) = 1.d0 - wall(2) = 1.d0 - wall(3) = 1.d0 - if (hR.le.drytol) then - call riemanntype(hL,hL,uL,-uL,hstar,s1m,s2m, - & rare1,rare2,1,drytol,g) - hstartest=max(hL,hstar) - if (hstartest+bL.lt.bR) then !right state should become ghost values that mirror left for wall problem -c bR=hstartest+bL - wall(2)=0.d0 - wall(3)=0.d0 - hR=hL - huR=-huL - bR=bL - phiR=phiL - uR=-uL - vR=vL - elseif (hL+bL.lt.bR) then - bR=hL+bL - endif - elseif (hL.le.drytol) then ! right surface is lower than left topo - call riemanntype(hR,hR,-uR,uR,hstar,s1m,s2m, - & rare1,rare2,1,drytol,g) - hstartest=max(hR,hstar) - if (hstartest+bR.lt.bL) then !left state should become ghost values that mirror right -c bL=hstartest+bR - wall(1)=0.d0 - wall(2)=0.d0 - hL=hR - huL=-huR - bL=bR - phiL=phiR - uL=-uR - vL=vR - elseif (hR+bR.lt.bL) then - bL=hR+bR - endif - endif - - !determine wave speeds - sL=uL-sqrt(g*hL) ! 1 wave speed of left state - sR=uR+sqrt(g*hR) ! 2 wave speed of right state - - uhat=(sqrt(g*hL)*uL + sqrt(g*hR)*uR)/(sqrt(g*hR)+sqrt(g*hL)) ! Roe average - chat=sqrt(g*0.5d0*(hR+hL)) ! Roe average - sRoe1=uhat-chat ! Roe wave speed 1 wave - sRoe2=uhat+chat ! Roe wave speed 2 wave - - sE1 = min(sL,sRoe1) ! Eindfeldt speed 1 wave - sE2 = max(sR,sRoe2) ! Eindfeldt speed 2 wave - - !--------------------end initializing...finally---------- - !solve Riemann problem. - - maxiter = 1 - - call riemann_aug_JCP(maxiter,meqn,mwaves,hL,hR,huL, - & huR,hvL,hvR,bL,bR,uL,uR,vL,vR,phiL,phiR,pL,pR,sE1,sE2, - & drytol,g,rho,sw,fw) - -C call riemann_ssqfwave(maxiter,meqn,mwaves,hL,hR,huL,huR, -C & hvL,hvR,bL,bR,uL,uR,vL,vR,phiL,phiR,pL,pR,sE1,sE2,drytol,g, -C & rho,sw,fw) - -C call riemann_fwave(meqn,mwaves,hL,hR,huL,huR,hvL,hvR, -C & bL,bR,uL,uR,vL,vR,phiL,phiR,pL,pR,sE1,sE2,drytol,g,rho,sw, -C & fw) - -c !eliminate ghost fluxes for wall - do mw=1,3 - sw(mw)=sw(mw)*wall(mw) - - fw(1,mw)=fw(1,mw)*wall(mw) - fw(2,mw)=fw(2,mw)*wall(mw) - fw(3,mw)=fw(3,mw)*wall(mw) - enddo - - do mw=1,mwaves - s(mw,i)=sw(mw) - fwave(1,mw,i)=fw(1,mw) - fwave(mu,mw,i)=fw(2,mw) - fwave(nv,mw,i)=fw(3,mw) -! write(51,515) sw(mw),fw(1,mw),fw(2,mw),fw(3,mw) -!515 format("++sw",4e25.16) - enddo - - 30 continue - enddo - - -c==========Capacity for mapping from latitude longitude to physical space==== - if (mcapa.gt.0) then - do i=2-mbc,mx+mbc - if (ixy.eq.1) then - dxdc=(earth_radius*deg2rad) - else - dxdc=earth_radius*cos(auxl(3,i))*deg2rad - endif - - do mw=1,mwaves -c if (s(mw,i) .gt. 316.d0) then -c # shouldn't happen unless h > 10 km! -c write(6,*) 'speed > 316: i,mw,s(mw,i): ',i,mw,s(mw,i) -c endif - s(mw,i)=dxdc*s(mw,i) - fwave(1,mw,i)=dxdc*fwave(1,mw,i) - fwave(2,mw,i)=dxdc*fwave(2,mw,i) - fwave(3,mw,i)=dxdc*fwave(3,mw,i) - enddo - enddo - endif - -c=============================================================================== - - -c============= compute fluctuations============================================= - - do i=2-mbc,mx+mbc - do mw=1,mwaves - if (s(mw,i) < 0.d0) then - amdq(1:3,i) = amdq(1:3,i) + fwave(1:3,mw,i) - else if (s(mw,i) > 0.d0) then - apdq(1:3,i) = apdq(1:3,i) + fwave(1:3,mw,i) - else - amdq(1:3,i) = amdq(1:3,i) + 0.5d0 * fwave(1:3,mw,i) - apdq(1:3,i) = apdq(1:3,i) + 0.5d0 * fwave(1:3,mw,i) - endif - enddo - enddo - - if (debug) then - do i=2-mbc,mx+mbc - do m=1,meqn - write(51,151) m,i,amdq(m,i),apdq(m,i) - write(51,152) fwave(m,1,i),fwave(m,2,i),fwave(m,3,i) - 151 format("+++ ampdq ",2i4,2e25.15) - 152 format("+++ fwave ",8x,3e25.15) - enddo - enddo - endif - - return - end subroutine diff --git a/src/2d/bouss/rpt2_geoclaw_sym.f b/src/2d/bouss/rpt2_geoclaw_sym.f deleted file mode 100644 index ce51c0d3c..000000000 --- a/src/2d/bouss/rpt2_geoclaw_sym.f +++ /dev/null @@ -1,260 +0,0 @@ -! ===================================================== - subroutine rpt2(ixy,imp,maxm,meqn,mwaves,maux,mbc,mx, - & ql,qr,aux1,aux2,aux3,asdq,bmasdq,bpasdq) -! ===================================================== - use geoclaw_module, only: g => grav, tol => dry_tolerance - use geoclaw_module, only: coordinate_system,earth_radius,deg2rad - - implicit none -! -! # Riemann solver in the transverse direction using an einfeldt -! Jacobian. - -!-----------------------last modified 1/10/05---------------------- - - integer ixy,maxm,meqn,maux,mwaves,mbc,mx,imp - - double precision ql(meqn,1-mbc:maxm+mbc) - double precision qr(meqn,1-mbc:maxm+mbc) - double precision asdq(meqn,1-mbc:maxm+mbc) - double precision bmasdq(meqn,1-mbc:maxm+mbc) - double precision bpasdq(meqn,1-mbc:maxm+mbc) - double precision aux1(maux,1-mbc:maxm+mbc) - double precision aux2(maux,1-mbc:maxm+mbc) - double precision aux3(maux,1-mbc:maxm+mbc) - - double precision s(mwaves) - double precision r(meqn,mwaves) - double precision beta(mwaves) - double precision abs_tol - double precision hl,hr,hul,hur,hvl,hvr,vl,vr,ul,ur,bl,br - double precision uhat,vhat,hhat,roe1,roe3,s1,s2,s3,s1down,s3up - double precision delf1,delf2,delf3,dxdcd,dxdcu - double precision dxdcm,dxdcp,topo1,topo3,eta - - integer i,m,mw,mu,mv - - !write(83,*) 'rpt2, imp = ',imp - - ! initialize all components to 0: - bmasdq(:,:) = 0.d0 - bpasdq(:,:) = 0.d0 - - abs_tol=tol - - if (ixy.eq.1) then - mu = 2 - mv = 3 - else - mu = 3 - mv = 2 - endif - - do i=2-mbc,mx+mbc - - hl=qr(1,i-1) - hr=ql(1,i) - hul=qr(mu,i-1) - hur=ql(mu,i) - hvl=qr(mv,i-1) - hvr=ql(mv,i) - -!===========determine velocity from momentum=========================== - if (hl.lt.abs_tol) then - hl=0.d0 - ul=0.d0 - vl=0.d0 - else - ul=hul/hl - vl=hvl/hl - endif - - if (hr.lt.abs_tol) then - hr=0.d0 - ur=0.d0 - vr=0.d0 - else - ur=hur/hr - vr=hvr/hr - endif - - do mw=1,mwaves - s(mw)=0.d0 - beta(mw)=0.d0 - do m=1,meqn - r(m,mw)=0.d0 - enddo - enddo - dxdcp = 1.d0 - dxdcm = 1.d0 - - if (hl <= tol .and. hr <= tol) go to 90 - -* !check and see if cell that transverse waves are going in is high and dry - if (imp.eq.1) then - eta = qr(1,i-1) + aux2(1,i-1) - topo1 = aux1(1,i-1) - topo3 = aux3(1,i-1) -c s1 = vl-sqrt(g*hl) -c s3 = vl+sqrt(g*hl) -c s2 = 0.5d0*(s1+s3) - else - eta = ql(1,i) + aux2(1,i) - topo1 = aux1(1,i) - topo3 = aux3(1,i) -c s1 = vr-sqrt(g*hr) -c s3 = vr+sqrt(g*hr) -c s2 = 0.5d0*(s1+s3) - endif - if (eta.lt.max(topo1,topo3)) go to 90 - - if (coordinate_system.eq.2) then - if (ixy.eq.2) then - dxdcp=(earth_radius*deg2rad) - dxdcm = dxdcp - else - if (imp.eq.1) then - dxdcp = earth_radius*cos(aux3(3,i-1))*deg2rad - dxdcm = earth_radius*cos(aux1(3,i-1))*deg2rad - else - dxdcp = earth_radius*cos(aux3(3,i))*deg2rad - dxdcm = earth_radius*cos(aux1(3,i))*deg2rad - endif - endif - endif - -c=====Determine some speeds necessary for the Jacobian================= -c vhat=(vr*dsqrt(hr))/(dsqrt(hr)+dsqrt(hl)) + -c & (vl*dsqrt(hl))/(dsqrt(hr)+dsqrt(hl)) -c -c uhat=(ur*dsqrt(hr))/(dsqrt(hr)+dsqrt(hl)) + -c & (ul*dsqrt(hl))/(dsqrt(hr)+dsqrt(hl)) -c hhat=(hr+hl)/2.d0 - - ! Note that we used left right states to define Roe averages, - ! which is consistent with those used in rpn2. - ! But now we are computing upgoing, downgoing waves either in - ! cell on left (if imp==1) or on right (if imp==2) so we - ! should perhaps use Roe averages based on values above or below, - ! but these aren't available. - - !roe1=vhat-dsqrt(g*hhat) - !roe3=vhat+dsqrt(g*hhat) - - ! modified to at least use hl,vl or hr,vr properly based on imp: - ! (since s1 and s3 are now vertical velocities, - ! it made no sense to use h,v in cell i-1 for downgoing - ! and cell i for upgoing) - - if (imp == 1) then - ! asdq is leftgoing, use q from cell i-1: - if (hl <= tol) go to 90 - s1 = vl-dsqrt(g*hl) - s3 = vl+dsqrt(g*hl) - s2 = vl - uhat = ul - hhat = hl - else - ! asdq is rightgoing, use q from cell i: - if (hr <= tol) go to 90 - s1 = vr-dsqrt(g*hr) - s3 = vr+dsqrt(g*hr) - s2 = vr - uhat = ur - hhat = hr - endif - - ! don't use Roe averages: - !s1=dmin1(roe1,s1down) - !s3=dmax1(roe3,s3up) - - !s2=0.5d0*(s1+s3) - - s(1)=s1 - s(2)=s2 - s(3)=s3 -c=======================Determine asdq decomposition (beta)============ - delf1=asdq(1,i) - delf2=asdq(mu,i) - delf3=asdq(mv, i) - - ! fixed bug in beta(2): uhat in place of s(2) - beta(1) = (s3*delf1/(s3-s1))-(delf3/(s3-s1)) - beta(2) = -uhat*delf1 + delf2 - beta(3) = (delf3/(s3-s1))-(s1*delf1/(s3-s1)) -c======================End ================================================= - -c=====================Set-up eigenvectors=================================== - r(1,1) = 1.d0 - r(2,1) = uhat ! fixed bug, uhat not s2 - r(3,1) = s1 - - r(1,2) = 0.d0 - r(2,2) = 1.d0 - r(3,2) = 0.d0 - - r(1,3) = 1.d0 - r(2,3) = uhat ! fixed bug, uhat not s2 - r(3,3) = s3 -c============================================================================ -90 continue -c============= compute fluctuations========================================== - - bmasdq(1,i)=0.0d0 - bpasdq(1,i)=0.0d0 - bmasdq(2,i)=0.0d0 - bpasdq(2,i)=0.0d0 - bmasdq(3,i)=0.0d0 - bpasdq(3,i)=0.0d0 - - do mw=1,3 - if ((abs(s(mw)) > 0.d0) .and. - & (abs(s(mw)) < 0.001d0*dsqrt(g*hhat))) then - ! split correction symmetrically if nearly zero - ! Note wave drops out if s(mw)==0 exactly, so don't split - bmasdq(1,i) =bmasdq(1,i) + - & 0.5d0*dxdcm*s(mw)*beta(mw)*r(1,mw) - bmasdq(mu,i)=bmasdq(mu,i)+ - & 0.5d0*dxdcm*s(mw)*beta(mw)*r(2,mw) - bmasdq(mv,i)=bmasdq(mv,i)+ - & 0.5d0*dxdcm*s(mw)*beta(mw)*r(3,mw) - bpasdq(1,i) =bpasdq(1,i) + - & 0.5d0*dxdcp*s(mw)*beta(mw)*r(1,mw) - bpasdq(mu,i)=bpasdq(mu,i)+ - & 0.5d0*dxdcp*s(mw)*beta(mw)*r(2,mw) - bpasdq(mv,i)=bpasdq(mv,i)+ - & 0.5d0*dxdcp*s(mw)*beta(mw)*r(3,mw) - elseif (s(mw).lt.0.d0) then - bmasdq(1,i) =bmasdq(1,i) + dxdcm*s(mw)*beta(mw)*r(1,mw) - bmasdq(mu,i)=bmasdq(mu,i)+ dxdcm*s(mw)*beta(mw)*r(2,mw) - bmasdq(mv,i)=bmasdq(mv,i)+ dxdcm*s(mw)*beta(mw)*r(3,mw) - elseif (s(mw).gt.0.d0) then - bpasdq(1,i) =bpasdq(1,i) + dxdcp*s(mw)*beta(mw)*r(1,mw) - bpasdq(mu,i)=bpasdq(mu,i)+ dxdcp*s(mw)*beta(mw)*r(2,mw) - bpasdq(mv,i)=bpasdq(mv,i)+ dxdcp*s(mw)*beta(mw)*r(3,mw) - endif - enddo - - !if ((i>3) .and. (i<6)) then - if (.false.) then - ! DEBUG - write(83,*) 'i = ',i - write(83,831) s(1),s(2),s(3) - write(83,831) beta(1),beta(2),beta(3) - do m=1,3 - write(83,831) r(m,1),r(m,2),r(m,3) - enddo - do m=1,3 - write(83,831) asdq(m,i), bmasdq(m,i), bpasdq(m,i) - 831 format(3d20.12) - enddo - endif -c======================================================================== - enddo ! do i loop -c - - -c - - return - end From 924237bcde01a451734c30917d521827c29f6a76 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 10 May 2024 10:37:54 -0400 Subject: [PATCH 17/58] Initial CI GitHub action script --- .github/workflows/testing.yml | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/testing.yml diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 000000000..7b0cf8db0 --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,49 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python application + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Checkout clawpack + uses: actions/checkout@v4.1.5 + with: + repository: https://github.com/clawpack/clawpack.git + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install gfortran liblapack-pic liblapack-dev libnetcdf-dev libnetcdff-dev + python -m pip install --upgrade pip + pip install flake8 pytest + - name: Setup clawpack + run: | + git submodule init + git submodule update + cd geoclaw + echo ${{ github.GITHUB_REF_NAME }} + # - name: Lint with flake8 + # run: | + # # stop the build if there are Python syntax errors or undefined names + # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + # - name: Test with pytest + # run: | + # pytest From 08cfa95bc62291e51548ac49dbc2f6952d8b39ad Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 10 May 2024 10:38:52 -0400 Subject: [PATCH 18/58] Update testing.yml --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 7b0cf8db0..5a8f37d1c 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -25,7 +25,7 @@ jobs: - name: Checkout clawpack uses: actions/checkout@v4.1.5 with: - repository: https://github.com/clawpack/clawpack.git + repository: clawpack/clawpack - name: Install dependencies run: | sudo apt-get update From 322831e992c6cce2fe8767150b9b7ce32309637a Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 10 May 2024 11:04:40 -0400 Subject: [PATCH 19/58] Add checking out of clawpack and geoclaw --- .github/workflows/testing.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 5a8f37d1c..47ebd675a 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Set up Python 3.10 - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Checkout clawpack @@ -32,12 +32,15 @@ jobs: sudo apt-get install gfortran liblapack-pic liblapack-dev libnetcdf-dev libnetcdff-dev python -m pip install --upgrade pip pip install flake8 pytest - - name: Setup clawpack + - name: Setup clawpack super repository run: | git submodule init git submodule update + pip install --user -e . + - name: Setup geoclaw + run: | cd geoclaw - echo ${{ github.GITHUB_REF_NAME }} + git checkout ${{ github.ref }} # - name: Lint with flake8 # run: | # # stop the build if there are Python syntax errors or undefined names From 56fa6386d6367d4dc4812d34e058f50fd1aaabb9 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 10 May 2024 11:07:17 -0400 Subject: [PATCH 20/58] Add lint and testing to CI --- .github/workflows/testing.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 47ebd675a..15f2b4956 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -41,12 +41,12 @@ jobs: run: | cd geoclaw git checkout ${{ github.ref }} - # - name: Lint with flake8 - # run: | - # # stop the build if there are Python syntax errors or undefined names - # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - # - name: Test with pytest - # run: | - # pytest + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest From 2181d25a93a6d498b4daba430968d169f4cf9e61 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 10 May 2024 11:41:04 -0400 Subject: [PATCH 21/58] Disable linting for the time being --- .github/workflows/testing.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 15f2b4956..914277f19 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -41,12 +41,13 @@ jobs: run: | cd geoclaw git checkout ${{ github.ref }} - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + # - name: Lint with flake8 + # run: | + # # stop the build if there are Python syntax errors or undefined names + # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | + cd geoclaw pytest From cf0f3c9dfcf8d5a89971735df323f8258ea30c8e Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 10 May 2024 11:43:53 -0400 Subject: [PATCH 22/58] Re-enable linting but only for geoclaw --- .github/workflows/testing.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 914277f19..9b1e98998 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -41,12 +41,13 @@ jobs: run: | cd geoclaw git checkout ${{ github.ref }} - # - name: Lint with flake8 - # run: | - # # stop the build if there are Python syntax errors or undefined names - # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Lint with flake8 + run: | + cd geoclaw + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | cd geoclaw From 80e0804463ebcf03681fd3d65c0cce710a9ada16 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 10 May 2024 13:45:18 -0400 Subject: [PATCH 23/58] Exclude old_topotools.py Do not want to touch the old topotools file for reference. --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 9b1e98998..b4cedfc69 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -45,7 +45,7 @@ jobs: run: | cd geoclaw # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude old_dtopotools.py # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest From 1bd6508185b9545a075bb363935cb24410539a63 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Wed, 22 May 2024 15:58:53 -0400 Subject: [PATCH 24/58] Fix up gauge plotting for storm surge Includes a couple of minor bugs related to gauge plotting. Major change involves how we now plot the gauge data. --- examples/storm-surge/ike/setplot.py | 39 +++------ examples/storm-surge/isaac/setplot.py | 112 +++++++++++++++++--------- examples/storm-surge/isaac/setrun.py | 17 ++-- src/python/geoclaw/surge/plot.py | 12 +-- src/python/geoclaw/util.py | 3 +- 5 files changed, 100 insertions(+), 83 deletions(-) diff --git a/examples/storm-surge/ike/setplot.py b/examples/storm-surge/ike/setplot.py index 5784f9ce5..50f4168d5 100644 --- a/examples/storm-surge/ike/setplot.py +++ b/examples/storm-surge/ike/setplot.py @@ -155,38 +155,19 @@ def friction_after_axes(cd): plotfigure.show = True plotfigure.clf_each_gauge = True - # Set up for axes in this figure: plotaxes = plotfigure.new_plotaxes() - plotaxes.xlimits = [-2, 1] - # plotaxes.xlabel = "Days from landfall" - # plotaxes.ylabel = "Surface (m)" - plotaxes.ylimits = [-1, 5] - plotaxes.title = 'Surface' - - def gauge_afteraxes(cd): - - axes = plt.gca() - surgeplot.plot_landfall_gauge(cd.gaugesoln, axes) - - # Fix up plot - in particular fix time labels - axes.set_title('Station %s' % cd.gaugeno) - axes.set_xlabel('Days relative to landfall') - axes.set_ylabel('Surface (m)') - axes.set_xlim([-2, 1]) - axes.set_ylim([-1, 5]) - axes.set_xticks([-2, -1, 0, 1]) - axes.set_xticklabels([r"$-2$", r"$-1$", r"$0$", r"$1$"]) - axes.grid(True) - plotaxes.afteraxes = gauge_afteraxes - - # Plot surface as blue curve: + plotaxes.time_scale = 1 / (24 * 60**2) + plotaxes.grid = True + plotaxes.xlimits = 'auto' + plotaxes.ylimits = 'auto' + plotaxes.title = "Surface" + plotaxes.ylabel = "Surface (m)" + plotaxes.time_label = "Days relative to landfall" + plotitem = plotaxes.new_plotitem(plot_type='1d_plot') - # plotitem.plot_var = 3 - # plotitem.plotstyle = 'b-' - - # + plotitem.plot_var = surgeplot.gauge_surface + # Gauge Location Plot - # def gauge_location_afteraxes(cd): plt.subplots_adjust(left=0.12, bottom=0.06, right=0.97, top=0.97) surge_afteraxes(cd) diff --git a/examples/storm-surge/isaac/setplot.py b/examples/storm-surge/isaac/setplot.py index 5fb7aebc3..094b48396 100644 --- a/examples/storm-surge/isaac/setplot.py +++ b/examples/storm-surge/isaac/setplot.py @@ -1,20 +1,18 @@ - -from __future__ import absolute_import -from __future__ import print_function +#!/usr/bin/env python import os +import warnings +import datetime -import numpy +import numpy as np import matplotlib.pyplot as plt -import datetime import clawpack.visclaw.colormaps as colormap import clawpack.visclaw.gaugetools as gaugetools import clawpack.clawutil.data as clawutil import clawpack.amrclaw.data as amrclaw import clawpack.geoclaw.data as geodata - - +import clawpack.geoclaw.util as geoutil import clawpack.geoclaw.surge.plot as surgeplot try: @@ -88,9 +86,9 @@ def friction_after_axes(cd): regions = {"Gulf": {"xlimits": (clawdata.lower[0], clawdata.upper[0]), "ylimits": (clawdata.lower[1], clawdata.upper[1]), "figsize": (6.4, 4.8)}, - "Louisiana": {"xlimits": (-92, -83), - "ylimits": (27.5, 30.5), - "figsize": (8, 2.7)}} + "Louisiana": {"xlimits": (-92, -83), + "ylimits": (27.5, 30.5), + "figsize": (8, 2.7)}} for (name, region_dict) in regions.items(): @@ -175,40 +173,78 @@ def friction_after_axes(cd): # ======================================================================== # Figures for gauges # ======================================================================== + def plot_observed(current_data): + """Fetch and plot gauge data for gauges used.""" + + # Map GeoClaw gauge number to NOAA gauge number and location/name + gauge_mapping = {1: [8761724, "Grand Isle, LA"], + 2: [8760922, 'Pilots Station East, SW Pass, LA']} + + station_id, station_name = gauge_mapping[current_data.gaugesoln.id] + landfall_time = np.datetime64(datetime.datetime(2012, 8, 29, 0)) + begin_date = datetime.datetime(2012, 8, 27) + end_date = datetime.datetime(2012, 8, 31) + + # Fetch data if needed + date_time, water_level, tide = geoutil.fetch_noaa_tide_data(station_id, + begin_date, + end_date) + if water_level is None: + print("*** Could not fetch gauge {}.".format(station_id)) + else: + # Convert to seconds relative to landfall + t = (date_time - landfall_time) / np.timedelta64(1, 's') + t /= (24 * 60**2) + + # Detide + water_level -= tide + + # Plot data + ax = plt.gca() + ax.plot(t, water_level, color='lightgray', marker='x') + ax.set_title(station_name) + ax.legend(['Computed', "Observed"]) + + plotfigure = plotdata.new_plotfigure(name='Gauge Surfaces', figno=300, type='each_gauge') plotfigure.show = True plotfigure.clf_each_gauge = True - # Set up for axes in this figure: plotaxes = plotfigure.new_plotaxes() - plotaxes.xlimits = [-2, 1] - # plotaxes.xlabel = "Days from landfall" - # plotaxes.ylabel = "Surface (m)" - plotaxes.ylimits = [-1, 5] - plotaxes.title = 'Surface' - - def gauge_afteraxes(cd): - - axes = plt.gca() - landfall = 0. - surgeplot.plot_landfall_gauge(cd.gaugesoln, axes, landfall=landfall) - - # Fix up plot - in particular fix time labels - axes.set_title('Station %s' % cd.gaugeno) - axes.set_xlabel('Days relative to landfall') - axes.set_ylabel('Surface (m)') - axes.set_xlim([-2, 1]) - axes.set_ylim([-1, 5]) - axes.set_xticks([-2, -1, 0, 1]) - axes.set_xticklabels([r"$-2$", r"$-1$", r"$0$", r"$1$"]) - axes.grid(True) - plotaxes.afteraxes = gauge_afteraxes - - # Plot surface as blue curve: + plotaxes.time_scale = 1 / (24 * 60**2) + plotaxes.grid = True + plotaxes.xlimits = [-2, 1.5] + plotaxes.ylimits = [-0.25, 1] + plotaxes.title = "Surface" + plotaxes.ylabel = "Surface (m)" + plotaxes.time_label = "Days relative to landfall" + plotaxes.afteraxes = plot_observed + plotitem = plotaxes.new_plotitem(plot_type='1d_plot') - plotitem.plot_var = 3 - plotitem.plotstyle = 'b-' + plotitem.plot_var = surgeplot.gauge_surface + + # Gauge Location Plot + def gauge_location_afteraxes(cd): + plt.subplots_adjust(left=0.12, bottom=0.06, right=0.97, top=0.97) + surge_afteraxes(cd) + gaugetools.plot_gauge_locations(cd.plotdata, gaugenos='all', + format_string='ko', add_labels=True) + + plotfigure = plotdata.new_plotfigure(name="Gauge Locations") + plotfigure.show = True + + # Set up for axes in this figure: + plotaxes = plotfigure.new_plotaxes() + plotaxes.title = 'Gauge Locations' + plotaxes.scaled = True + plotaxes.xlimits = regions['Louisiana']['xlimits'] + plotaxes.ylimits = regions['Louisiana']['ylimits'] + plotaxes.afteraxes = gauge_location_afteraxes + surgeplot.add_surface_elevation(plotaxes, bounds=surface_limits) + surgeplot.add_land(plotaxes, bounds=[0.0, 20.0]) + plotaxes.plotitem_dict['surface'].amr_patchedges_show = [0] * 10 + plotaxes.plotitem_dict['land'].amr_patchedges_show = [0] * 10 # ----------------------------------------- # Parameters used only when creating html and/or latex hardcopy @@ -217,7 +253,7 @@ def gauge_afteraxes(cd): plotdata.printfigs = True # print figures plotdata.print_format = 'png' # file format plotdata.print_framenos = 'all' # list of frames to print - plotdata.print_gaugenos = [1, 2, 3, 4] # list of gauges to print + plotdata.print_gaugenos = 'all' # list of gauges to print plotdata.print_fignos = 'all' # list of figures to print plotdata.html = True # create html files of plots? plotdata.latex = True # create latex file of plots? diff --git a/examples/storm-surge/isaac/setrun.py b/examples/storm-surge/isaac/setrun.py index d6d63eb93..4e8299286 100644 --- a/examples/storm-surge/isaac/setrun.py +++ b/examples/storm-surge/isaac/setrun.py @@ -313,17 +313,16 @@ def setrun(claw_pkg='geoclaw'): regions = rundata.regiondata.regions # to specify regions of refinement append lines of the form # [minlevel,maxlevel,t1,t2,x1,x2,y1,y2] - # Gauges from Ike AWR paper (2011 Dawson et al) - rundata.gaugedata.gauges.append([1, -95.04, 29.07, - rundata.clawdata.t0, - rundata.clawdata.tfinal]) - rundata.gaugedata.gauges.append([2, -94.71, 29.28, - rundata.clawdata.t0, - rundata.clawdata.tfinal]) - rundata.gaugedata.gauges.append([3, -94.39, 29.49, + + # Pilots Station East, S.W. Pass, LA - 28°55.9429'N, 89°24.4445'W + # https://tidesandcurrents.noaa.gov/stationhome.html?id=8760922 + rundata.gaugedata.gauges.append([1, -89.40740833, 28.93238167, rundata.clawdata.t0, rundata.clawdata.tfinal]) - rundata.gaugedata.gauges.append([4, -94.13, 29.58, + + # Grand Isle, LA - 29°15.8761'N 89°57.4960'W + # https://tidesandcurrents.noaa.gov/stationhome.html?id=8761724 + rundata.gaugedata.gauges.append([2, -89.95826667, 29.26460167, rundata.clawdata.t0, rundata.clawdata.tfinal]) diff --git a/src/python/geoclaw/surge/plot.py b/src/python/geoclaw/surge/plot.py index 254d74f7b..9752f8428 100644 --- a/src/python/geoclaw/surge/plot.py +++ b/src/python/geoclaw/surge/plot.py @@ -84,12 +84,9 @@ def gauge_locations(current_data, gaugenos='all'): yoffset=0.02) -def gaugetopo(current_data): - q = current_data.q - h = q[0, :] - eta = q[3, :] - topo = eta - h - return topo +def gauge_surface(cd): + """Sea surface at gauge masked when dry.""" + return np.ma.masked_where(cd.gaugesoln.q[0, :] < 0.0, cd.gaugesoln.q[3, :]) def plot_landfall_gauge(gauge, axes, landfall=0.0, style='b', kwargs={}): @@ -97,6 +94,9 @@ def plot_landfall_gauge(gauge, axes, landfall=0.0, style='b', kwargs={}): This will transform the plot so that it is relative to the landfall value provided. + + This can be done using `plotaxes.time_scale` instead so this function will + be deprecated and removed in a future release. """ axes = plt.gca() diff --git a/src/python/geoclaw/util.py b/src/python/geoclaw/util.py index 816043f05..b358c9546 100644 --- a/src/python/geoclaw/util.py +++ b/src/python/geoclaw/util.py @@ -361,7 +361,8 @@ def get_cache_path(product): end_date.strftime(cache_date_fmt)) filename = '{}_{}_{}'.format(time_zone, datum, units) abs_cache_dir = os.path.abspath(cache_dir) - return os.path.join(abs_cache_dir, product, station, dates, filename) + return os.path.join(abs_cache_dir, product, str(station), dates, + filename) def save_to_cache(cache_path, data): # make parent directories if they do not exist From c678e337d27919132b8dcff37e7aa2172b62f9c2 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Wed, 22 May 2024 17:22:14 -0400 Subject: [PATCH 25/58] Add dry gauge plotting --- examples/storm-surge/ike/setplot.py | 4 ++++ examples/storm-surge/isaac/setplot.py | 4 ++++ src/python/geoclaw/surge/plot.py | 9 +++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/examples/storm-surge/ike/setplot.py b/examples/storm-surge/ike/setplot.py index 50f4168d5..865093be4 100644 --- a/examples/storm-surge/ike/setplot.py +++ b/examples/storm-surge/ike/setplot.py @@ -166,6 +166,10 @@ def friction_after_axes(cd): plotitem = plotaxes.new_plotitem(plot_type='1d_plot') plotitem.plot_var = surgeplot.gauge_surface + # Plot red area if gauge is dry + plotitem = plotaxes.new_plotitem(plot_type='1d_plot') + plotitem.plot_var = surgeplot.gauge_dry_regions + plotitem.kwargs = {"color":'lightcoral', "linewidth":5} # Gauge Location Plot def gauge_location_afteraxes(cd): diff --git a/examples/storm-surge/isaac/setplot.py b/examples/storm-surge/isaac/setplot.py index 094b48396..8fe715b15 100644 --- a/examples/storm-surge/isaac/setplot.py +++ b/examples/storm-surge/isaac/setplot.py @@ -223,6 +223,10 @@ def plot_observed(current_data): plotitem = plotaxes.new_plotitem(plot_type='1d_plot') plotitem.plot_var = surgeplot.gauge_surface + # Plot red area if gauge is dry + plotitem = plotaxes.new_plotitem(plot_type='1d_plot') + plotitem.plot_var = surgeplot.gauge_dry_regions + plotitem.kwargs = {"color":'lightcoral', "linewidth":5} # Gauge Location Plot def gauge_location_afteraxes(cd): diff --git a/src/python/geoclaw/surge/plot.py b/src/python/geoclaw/surge/plot.py index 9752f8428..372ca1073 100644 --- a/src/python/geoclaw/surge/plot.py +++ b/src/python/geoclaw/surge/plot.py @@ -83,10 +83,15 @@ def gauge_locations(current_data, gaugenos='all'): add_labels=True, xoffset=0.02, yoffset=0.02) +def gauge_dry_regions(cd, dry_tolerance=1e-16): + """Masked array of zeros where gauge is dry.""" + return np.ma.masked_where(np.abs(cd.gaugesoln.q[0, :]) > dry_tolerance, + np.zeros(cd.gaugesoln.q[0,:].shape)) -def gauge_surface(cd): +def gauge_surface(cd, dry_tolerance=1e-16): """Sea surface at gauge masked when dry.""" - return np.ma.masked_where(cd.gaugesoln.q[0, :] < 0.0, cd.gaugesoln.q[3, :]) + return np.ma.masked_where(np.abs(cd.gaugesoln.q[0, :]) < dry_tolerance, + cd.gaugesoln.q[3, :]) def plot_landfall_gauge(gauge, axes, landfall=0.0, style='b', kwargs={}): From 51c0d2716134d476bee80477df3b80d0342dbf0c Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Wed, 29 May 2024 17:41:45 -0700 Subject: [PATCH 26/58] create gauge filenames that allow more than 5 digits in gauge number If fewer than 5 digits, still add zero padding e.g. gauge00012.txt but no longer throws an error for large gauge numbers, e.g. gauge1234567.txt --- src/2d/shallow/gauges_module.f90 | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/2d/shallow/gauges_module.f90 b/src/2d/shallow/gauges_module.f90 index 82b20e2a7..b00b11a41 100644 --- a/src/2d/shallow/gauges_module.f90 +++ b/src/2d/shallow/gauges_module.f90 @@ -58,8 +58,8 @@ module gauges_module integer :: gauge_num integer :: gdata_bytes - character(len=14) :: file_name ! for header (and data if 'ascii') - character(len=14) :: file_name_bin ! used if file_format='binary' + character(len=24) :: file_name ! for header (and data if 'ascii') + character(len=24) :: file_name_bin ! used if file_format='binary' ! Location in time and space real(kind=8) :: x, y, t_start, t_end @@ -116,6 +116,7 @@ subroutine set_gauges(restart, num_eqn, num_aux, fname) integer, parameter :: UNIT = 7 character(len=128) :: header_1 character(len=40) :: q_column, aux_column + character(len=15) :: numstr if (.not.module_setup) then @@ -206,18 +207,22 @@ subroutine set_gauges(restart, num_eqn, num_aux, fname) end do ! Create gauge output files + ! with format gauge00012.txt or gauge1234567.txt do i = 1, num_gauges - gauges(i)%file_name = 'gaugexxxxx.txt' ! ascii num = gauges(i)%gauge_num - do pos = 10, 6, -1 - digit = mod(num,10) - gauges(i)%file_name(pos:pos) = char(ichar('0') + digit) - num = num / 10 - end do + ! convert num to string numstr: + if (num > 100000) then + ! > 5 digits, no padding + write (numstr,'(I0)') num + else + ! <= 5 digits, add zero padding + write (numstr,'(I5.5)') num + endif + + gauges(i)%file_name = 'gauge'//trim(numstr)//'.txt' if (gauges(i)%file_format >= 2) then - gauges(i)%file_name_bin = gauges(i)%file_name - gauges(i)%file_name_bin(12:14) = 'bin' + gauges(i)%file_name_bin = 'gauge'//trim(numstr)//'.bin' endif @@ -243,7 +248,7 @@ subroutine set_gauges(restart, num_eqn, num_aux, fname) if (.not. restart) then ! Write header to .txt file: - header_1 = "('# gauge_id= ',i5,' " // & + header_1 = "('# gauge_id= ',i0,' " // & "location=( ',1e17.10,' ',1e17.10,' ) " // & "num_var= ',i2)" write(OUTGAUGEUNIT, header_1) gauges(i)%gauge_num, & From 794e75199bba070083971a0930a58536b83d1abe Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Wed, 29 May 2024 17:46:07 -0700 Subject: [PATCH 27/58] cleaner way to zero pad only if fewer than 5 digits using I0.5 format --- src/2d/shallow/gauges_module.f90 | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/2d/shallow/gauges_module.f90 b/src/2d/shallow/gauges_module.f90 index b00b11a41..5c60833d2 100644 --- a/src/2d/shallow/gauges_module.f90 +++ b/src/2d/shallow/gauges_module.f90 @@ -207,18 +207,12 @@ subroutine set_gauges(restart, num_eqn, num_aux, fname) end do ! Create gauge output files - ! with format gauge00012.txt or gauge1234567.txt do i = 1, num_gauges num = gauges(i)%gauge_num - ! convert num to string numstr: - if (num > 100000) then - ! > 5 digits, no padding - write (numstr,'(I0)') num - else - ! <= 5 digits, add zero padding - write (numstr,'(I5.5)') num - endif + ! convert num to string numstr with zero padding if <5 digits + ! since we want format gauge00012.txt or gauge1234567.txt: + write (numstr,'(I0.5)') num gauges(i)%file_name = 'gauge'//trim(numstr)//'.txt' if (gauges(i)%file_format >= 2) then From aabfe2480aaa66d6ab1bc93d3004f22965dc3bf1 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Thu, 30 May 2024 12:28:27 -0400 Subject: [PATCH 28/58] Cleanup and reimplement reading of ATCF and writing of GeoClaw storms --- src/python/geoclaw/surge/storm.py | 324 ++++++++++++++++-------------- 1 file changed, 172 insertions(+), 152 deletions(-) diff --git a/src/python/geoclaw/surge/storm.py b/src/python/geoclaw/surge/storm.py index 6e48eacc8..c7f4ac65b 100644 --- a/src/python/geoclaw/surge/storm.py +++ b/src/python/geoclaw/surge/storm.py @@ -37,7 +37,8 @@ import argparse import datetime -import numpy +import numpy as np +import pandas as pd import clawpack.geoclaw.units as units import clawpack.clawutil.data @@ -136,25 +137,26 @@ class Storm(object): *TODO:* Add description of unit handling :Attributes: - - *t* (list(datetime.datetiem)) Contains the time at which each entry of - the other arrays are at. These are expected to be *datetime* objects. - Note that when written some formats require a *time_offset* to be set. - - *eye_location* (ndarray(:, :)) location of the eye of the storm. - Default units are in signed decimcal longitude and latitude. + - *t* (list(float) or list(datetime.datetiem)) Contains the time at which + each entry of the other arrays are at. These are expected to + be *datetime* objects. Note that when written some formats require + a *time_offset* to be set. + - *eye_location* (ndarray(:, :)) location of the eye of the storm. Default + units are in signed decimal longitude and latitude. - *max_wind_speed* (ndarray(:)) Maximum wind speed. Default units are meters/second. - *max_wind_radius* (ndarray(:)) Radius at which the maximum wind speed occurs. Default units are meters. - - *central_pressure* (ndarray(:)) Central pressure of storm. Default - units are Pascals. + - *central_pressure* (ndarray(:)) Central pressure of storm. Default units + are Pascals. - *storm_radius* (ndarray(:)) Radius of storm, often defined as the last closed iso-bar of pressure. Default units are meters. - *time_offset* (datetime.datetime) A date time that as an offset for the - simulation time. This will default to the beginning of the first of the - year that the first time point is found in. + simulation time. This will default to the beginning of the first of + the year that the first time point is found in. - *wind_speeds* (ndarray(:, :)) Wind speeds defined in every record, such - as 34kt, 50kt, 64kt, etc and their radii. Default units are meters/second - and meters. + as 34kt, 50kt, 64kt, etc and their radii. Default units are + meters/second and meters. :Initialization: 1. Read in existing file at *path*. @@ -194,7 +196,7 @@ def __init__(self, path=None, file_format="ATCF", **kwargs): self.wind_speeds = None # Storm descriptions - not all formats provide these - self.name = None + self.name = None # Possibly a list of a storm's names self.basin = None # Basin containing storm self.ID = None # ID code - depends on format self.classification = None # Classification of storm (e.g. HU) @@ -270,15 +272,16 @@ def read_geoclaw(self, path, verbose=False): - *verbose* (bool) Output more info regarding reading. """ + # Read header with open(path, 'r') as data_file: num_casts = int(data_file.readline()) self.time_offset = datetime.datetime.strptime( data_file.readline()[:19], '%Y-%m-%dT%H:%M:%S') - - data = numpy.loadtxt(path, skiprows=3) + # Read rest of data + data = np.loadtxt(path, skiprows=3) num_forecasts = data.shape[0] - self.eye_location = numpy.empty((2, num_forecasts)) + self.eye_location = np.empty((2, num_forecasts)) assert(num_casts == num_forecasts) self.t = [self.time_offset + datetime.timedelta(seconds=data[i, 0]) for i in range(num_forecasts)] @@ -299,14 +302,24 @@ def read_atcf(self, path, verbose=False): - *path* (string) Path to the file to be read. - *verbose* (bool) Output more info regarding reading. """ - try: - import pandas as pd - except ImportError as e: - print("read_atcf currently requires pandas to work.") - raise e # See here for the ATCF format documentation: # https://www.nrlmry.navy.mil/atcf_web/docs/database/new/abdeck.txt + + # Slightly more robust converter for ATCF data fields that can be + # missing + def num_converter(x): + if isinstance(x, str): + if len(x.strip()) == 0: + # Only whitespace + return np.nan + else: + # Assume this is still a number + return float(x) + elif x is None: + return np.nan + return float(x) + df = pd.read_csv(path, engine="python", sep=",+", names=[ "BASIN", "CY", "YYYYMMDDHH", "TECHNUM", "TECH", "TAU", "LAT", "LON", "VMAX", "MSLP", "TY", @@ -326,26 +339,31 @@ def read_atcf(self, path, verbose=False): "TAU": lambda d: datetime.timedelta(hours=int(d)), "LAT": lambda d: (-.1 if d[-1] == "S" else .1) * int(d.strip("NS ")), "LON": lambda d: (-.1 if d[-1] == "W" else .1) * int(d.strip("WE ")), + "RAD": num_converter, + "RAD": num_converter, + "RAD1": num_converter, + "RAD2": num_converter, + "RAD3": num_converter, + "RAD4": num_converter, + "ROUTER": num_converter, + "RMW": num_converter, + "STORMNAME": lambda d: (d.strip() if isinstance(d, str) else d) }, dtype={ "BASIN": str, "CY": int, "VMAX": float, "MSLP": float, - "TY": str, - "RAD": float, - "RAD1": float, - "RAD2": float, - "RAD3": float, - "RAD4": float, - "ROUTER": float, - "RMW": float, + "TY": str }) # Grab data regarding basin and cyclone number from first row self.basin = ATCF_basins[df["BASIN"][0]] self.ID = df["CY"][0] + # Keep around the name as an array + self.name = df["STORMNAME"].to_numpy() + # Take forecast period TAU into consideration df['DATE'] = df["YYYYMMDDHH"] + df["TAU"] df = df[["DATE", "TAU", "TY", "LAT", "LON", "VMAX", "MSLP", @@ -356,7 +374,7 @@ def read_atcf(self, path, verbose=False): # For each DATE, choose best (smallest TAU) available data for c in ["LAT", "LON", "VMAX", "MSLP", "ROUTER", "RMW", "RAD", "RAD1", "RAD2", "RAD3", "RAD4"]: - df[c] = df[c].where(df[c] != 0, numpy.nan) # value 0 means NaN + df[c] = df[c].where(df[c] != 0, np.nan) # value 0 means NaN df[c] = df.groupby("DATE")[c].bfill() df = df.groupby("DATE").first() @@ -388,18 +406,6 @@ def read_atcf(self, path, verbose=False): self.wind_speeds[:,0] = units.convert(self.wind_speeds[:,0], 'knots', 'm/s') self.wind_speeds[:,1] = units.convert(self.wind_speeds[:,1], 'nmi', 'm') - # Set NaNs to -1 to mark them as missing - for ar in [self.max_wind_speed, self.central_pressure, - self.max_wind_radius, self.storm_radius, self.wind_speeds]: - ar[numpy.isnan(ar)] = -1. - - if self.max_wind_speed.min() == -1: - warnings.warn('Some timesteps have missing max wind speed. These will not be written' - ' out to geoclaw format.') - if self.central_pressure.min() == -1: - warnings.warn('Some timesteps have missing central pressure. These will not be written' - ' out to geoclaw format.') - def read_hurdat(self, path, verbose=False): r"""Read in HURDAT formatted storm file @@ -437,13 +443,13 @@ def read_hurdat(self, path, verbose=False): # Parse data block self.t = [] - self.event = numpy.empty(num_lines, dtype=str) - self.classification = numpy.empty(num_lines, dtype=str) - self.eye_location = numpy.empty((num_lines, 2)) - self.max_wind_speed = numpy.empty(num_lines) - self.central_pressure = numpy.empty(num_lines) - self.max_wind_radius = numpy.empty(num_lines) - self.storm_radius = numpy.empty(num_lines) + self.event = np.empty(num_lines, dtype=str) + self.classification = np.empty(num_lines, dtype=str) + self.eye_location = np.empty((num_lines, 2)) + self.max_wind_speed = np.empty(num_lines) + self.central_pressure = np.empty(num_lines) + self.max_wind_radius = np.empty(num_lines) + self.storm_radius = np.empty(num_lines) for (i, line) in enumerate(data_block): if len(line) == 0: @@ -570,7 +576,7 @@ def read_ibtracs(self, path, sid=None, storm_name=None, year=None, start_date=No assert start_date is not None, ValueError('Multiple storms identified and no start_date specified.') start_times = ds.time.isel(date_time=0) - start_date = numpy.datetime64(start_date) + start_date = np.datetime64(start_date) # find storm with start date closest to provided storm_ix = abs(start_times - start_date).argmin() @@ -672,14 +678,14 @@ def map_val_to_ix(a): ## time offset if (self.event=='L').any(): # if landfall, use last landfall - self.time_offset = numpy.array(self.t)[self.event=='L'][-1] + self.time_offset = np.array(self.t)[self.event=='L'][-1] else: #if no landfall, use last time of storm self.time_offset = self.t[-1] # Classification, note that this is not the category of the storm self.classification = ds.usa_status.values - self.eye_location = numpy.array([ds.lon,ds.lat]).T + self.eye_location = np.array([ds.lon,ds.lat]).T # Intensity information - for now, including only common, basic intensity # info. @@ -726,13 +732,13 @@ def read_jma(self, path, verbose=False): # Parse data block self.t = [] - self.event = numpy.empty(num_lines, dtype=str) - self.classification = numpy.empty(num_lines, dtype=str) - self.eye_location = numpy.empty((num_lines, 2)) - self.max_wind_speed = numpy.empty(num_lines) - self.central_pressure = numpy.empty(num_lines) - self.max_wind_radius = numpy.empty(num_lines) - self.storm_radius = numpy.empty(num_lines) + self.event = np.empty(num_lines, dtype=str) + self.classification = np.empty(num_lines, dtype=str) + self.eye_location = np.empty((num_lines, 2)) + self.max_wind_speed = np.empty(num_lines) + self.central_pressure = np.empty(num_lines) + self.max_wind_radius = np.empty(num_lines) + self.storm_radius = np.empty(num_lines) for (i, line) in enumerate(data_block): if len(line) == 0: break @@ -801,12 +807,12 @@ def read_tcvitals(self, path, verbose=False): # Central_pressure - convert from mbar to Pa - 100.0 # Radius of last isobar contour - convert from km to m - 1000.0 self.t = [] - self.classification = numpy.empty(num_lines, dtype=str) - self.eye_location = numpy.empty((num_lines, 2)) - self.max_wind_speed = numpy.empty(num_lines) - self.central_pressure = numpy.empty(num_lines) - self.max_wind_radius = numpy.empty(num_lines) - self.storm_radius = numpy.empty(num_lines) + self.classification = np.empty(num_lines, dtype=str) + self.eye_location = np.empty((num_lines, 2)) + self.max_wind_speed = np.empty(num_lines) + self.central_pressure = np.empty(num_lines) + self.max_wind_radius = np.empty(num_lines) + self.storm_radius = np.empty(num_lines) for (i, data) in enumerate(data_block): # End at an empty lines - skips lines at the bottom of a file @@ -865,109 +871,123 @@ def write(self, path, file_format="geoclaw", **kwargs): getattr(self, 'write_%s' % file_format.lower())(path, **kwargs) - def write_geoclaw(self, path, verbose=False, max_wind_radius_fill=None, - storm_radius_fill=None): + def write_geoclaw(self, path, force=False, skip=True, verbose=False, + fill_dict={}, **kwargs): r"""Write out a GeoClaw formatted storm file GeoClaw storm files are read in by the GeoClaw Fortran code. :Input: - *path* (string) Path to the file to be written. + - *skip* (bool) Skip a time if NaNs are found and are not replaced. + Default is `True`. + - *force* (bool) Force output of storm even if there is missing data. + Default is `False`. - *verbose* (bool) Print out additional information when writing. - - *max_wind_radius_fill* (func) Function that can be used to fill in - missing data for `max_wind_radius` values. This defaults to simply - setting the value to -1. The function signature should be - `max_wind_radius(t, storm)` where t is the time of the forecast and - `storm` is the storm object. Note that if this or `storm_radius` - field remains -1 that this data line will be assumed to be redundant - and not be written out. - - *storm_radius_fill* (func) Function that can be used to fill in - missing data for `storm_radius` values. This defaults to simply - setting the value to -1. The function signature should be - `storm_radius(t, storm)` where t is the time of the forecast and - `storm` is the storm object. Note that if this or `max_wind_radius` - field remains -1 that this data line will be assumed to be redundant - and not be written + Default is `False`. + - *fill_dict* (dict) Dictionary of functions to use to fill in missing + data represented by NaNs. The keys are the field to be filled and + the function signature should be `my_func(t, storm)` where t is the + time of the forecast and `storm` is the storm object. If the + field remains a NaN or a function is not provided these lines will + be assumed redundant and will be ommitted. Note that the older + keyword arguments are put in this dictionary. Currently the one + default function is for `storm_radius`, which sets the value to + 500 km. """ - if max_wind_radius_fill is None: - max_wind_radius_fill = lambda t, storm: -1 - if storm_radius_fill is None: - storm_radius_fill = lambda t, storm: -1 - - # Create list for output - # Leave this first line blank as we need to count the actual valid lines - # that will be left in the file below + # If a filling function is not provided we will provide some defaults + fill_dict.update({"storm_radius": lambda t, storm: 500e3}) + # Handle older interface that had specific fill functions + if "max_wind_radius_fill" in kwargs.keys(): + fill_dict.update({"max_wind_radius": max_wind_radius_fill}) + if "storm_radius_fill" in kwargs.keys(): + fill_dict.update({"storm_radius": storm_radius_fill}) + + # Loop through each line of data and if the line is valid, perform the + # necessary work to write it out. Otherwise either raise an exception + # or skip it num_casts = 0 - data_string = [""] - if self.time_offset is None: - data_string.append("None") - self.time_offset = self.t[0] - else: - data_string.append("%s\n\n" % self.time_offset.isoformat()) + data = [] for n in range(len(self.t)): - # Remove duplicate times - if n > 0: - if self.t[n] == self.t[n - 1]: - continue - - format_string = ("{:19,.8e} " * 7)[:-1] + "\n" - data = [] - if not isinstance(self.time_offset, float): - data.append((self.t[n] - self.time_offset).total_seconds()) - else: - data.append(self.t[n] - self.time_offset) - data.append(self.eye_location[n, 0]) - data.append(self.eye_location[n, 1]) - - if self.max_wind_speed[n] == -1: + if self.t[n] == self.t[n - 1]: + # Skip this time continue - data.append(self.max_wind_speed[n]) - - # Allow custom function to set max wind radius if not - # available - if self.max_wind_radius[n] == -1: - new_wind_radius = max_wind_radius_fill(self.t[n], self) - if new_wind_radius == -1: - continue - else: - data.append(new_wind_radius) - else: - data.append(self.max_wind_radius[n]) - if self.central_pressure[n] == -1: + # Check each value we need for this time to make sure it is valid + valid = True + for name in ["max_wind_speed", "central_pressure", + "max_wind_radius", "storm_radius"]: + if np.isnan(getattr(self, name)[n]): + if name in fill_dict.keys(): + # Fill value with function provided + getattr(self, name)[n] = fill_dict[name](self.t[n], self) + elif skip: + # Skip this line + valid = False + if verbose: + # Just warn that a NaN was found but continue + msg = ("*** WARNING: The value {} at {} is a " + + "NaN. Skipping this line.") + warnings.warn(msg.format(name, self.t[n])) + elif not force: + # If we are not asked to force to write raise an + # exception given the NaN + msg = ("The value {} at {} is a NaN and the storm " + + "will not be written in GeoClaw format. If " + + "you want to fill in the value provide a " + + "function or set `force=True`.") + raise ValueError(msg.format(name, self.t[n])) + if not valid: continue - data.append(self.central_pressure[n]) - - # Allow custom function to set storm radius if not available - if self.storm_radius[n] == -1: - new_storm_radius = storm_radius_fill(self.t[n], self) - if new_storm_radius == -1: - continue - else: - data.append(new_storm_radius) - else: - data.append(self.storm_radius[n]) - data_string.append(format_string.format(*data)) + # Succeeded, add this time to the output num_casts += 1 + data.append(np.empty(7)) + # If we do not have a time offset use the first valid row as the + # offset time + if self.time_offset is None: + self.time_offset = self.t[n] - # Write to actual file now that we know exactly how many lines it will - # contain + # Time + if not isinstance(self.time_offset, float): + data[-1][0] = (self.t[n] - self.time_offset).total_seconds() + else: + data[-1][0] = self.t[n] - self.time_offset + # Eye-location + data[-1][1:3] = self.eye_location[n, :] + # Max wind speed + data[-1][3] = self.max_wind_speed[n] + # Max wind radius + data[-1][4] = self.max_wind_radius[n] + # Central pressure + data[-1][5] = self.central_pressure[n] + # Outer storm radius + data[-1][6] = self.storm_radius[n] + + # Write out file + format_string = ("{:19,.8e} " * 7)[:-1] + "\n" try: - # Update number of forecasts here - data_string[0] = "%s\n" % num_casts with open(path, "w") as data_file: - for data_line in data_string: - data_file.write(data_line) + # Write header + data_file.write(f"{num_casts}\n") + if isinstance(self.time_offset, datetime.datetime): + data_file.write(f"{self.time_offset.isoformat()}\n\n") + else: + data_file.write(f"{str(self.time_offset)}\n\n") + + # Write data lines + for line in data: + data_file.write(format_string.format(*line)) except Exception as e: - # Remove possibly partially generated file if not successful + # If an exception occurs clean up a partially generated file if os.path.exists(path): os.remove(path) raise e + def write_atcf(self, path, verbose=False): r"""Write out a ATCF formatted storm file @@ -1026,15 +1046,15 @@ def write_hurdat(self, path, verbose=False): # Convert latitude to proper Hurdat format e.g 12.0N if latitude > 0: - latitude = str(numpy.abs(latitude)) + 'N' + latitude = str(np.abs(latitude)) + 'N' else: - latitude = str(numpy.abs(latitude)) + 'S' + latitude = str(np.abs(latitude)) + 'S' # Convert longitude to proper Hurdat format e.g 12.0W if longitude > 0: - longitude = str(numpy.abs(longitude)) + 'E' + longitude = str(np.abs(longitude)) + 'E' else: - longitude = str(numpy.abs(longitude)) + 'W' + longitude = str(np.abs(longitude)) + 'W' data_file.write("".join(("%s" % self.seconds2date( self.t[n])[0:-2], @@ -1145,7 +1165,7 @@ def category(self, categorization="NHC", cat_names=False): if categorization.upper() == "BEAUFORT": # Beaufort scale below uses knots speeds = units.convert(self.max_wind_speed, "m/s", "knots") - category = (numpy.zeros(speeds.shape) + + category = (np.zeros(speeds.shape) + (speeds >= 1) * (speeds < 4) * 1 + (speeds >= 4) * (speeds < 7) * 2 + (speeds >= 7) * (speeds < 11) * 3 + @@ -1177,7 +1197,7 @@ def category(self, categorization="NHC", cat_names=False): # Definitely not in the correct format now # TODO: Add TD and TS designations speeds = units.convert(self.max_wind_speed, "m/s", "knots") - category = (numpy.zeros(speeds.shape) + + category = (np.zeros(speeds.shape) + (speeds < 30) * -1 + (speeds >= 64) * (speeds < 83) * 1 + (speeds >= 83) * (speeds < 96) * 2 + @@ -1319,7 +1339,7 @@ def fill_rad_w_other_source(t, storm_targ, storm_fill, var, interp_kwargs={}): dims = ('t',)) # convert -1 to nan - fill_da = fill_da.where(fill_da>0,numpy.nan) + fill_da = fill_da.where(fill_da>0,np.nan) # if not all missing, try using storm_fill to fill if fill_da.notnull().any(): @@ -1335,19 +1355,19 @@ def fill_rad_w_other_source(t, storm_targ, storm_fill, var, interp_kwargs={}): # try replacing with storm_fill # (assuming atcf has more data points than ibtracs) - if not numpy.isnan(fill_interp): + if not np.isnan(fill_interp): return fill_interp # next, try just interpolating other ibtracs values targ_da = xr.DataArray(getattr(storm_targ,var), coords = {'t': getattr(storm_targ,'t')}, dims = ('t',)) - targ_da = targ_da.where(targ_da>0,numpy.nan) + targ_da = targ_da.where(targ_da>0,np.nan) if targ_da.notnull().any(): targ_da = targ_da.groupby('t').first() targ_da = targ_da.dropna('t') targ_interp = targ_da.interp(t=[t], kwargs=interp_kwargs).item() - if not numpy.isnan(targ_interp): + if not np.isnan(targ_interp): return targ_interp # if nothing worked, return the missing value (-1) From e2dde7473fee8d43fc458d3789934b1527cc9ffa Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Thu, 30 May 2024 12:29:15 -0400 Subject: [PATCH 29/58] Minor string comparison bug fix --- src/python/geoclaw/surge/plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/geoclaw/surge/plot.py b/src/python/geoclaw/surge/plot.py index 372ca1073..a0e4b23a7 100644 --- a/src/python/geoclaw/surge/plot.py +++ b/src/python/geoclaw/surge/plot.py @@ -529,7 +529,7 @@ def add_track(Storm, axes, plot_package=None, category_color=None, legend_loc='b categories_legend = [] - if intensity and categorization is "NHC": + if intensity and categorization == "NHC": categories_legend = [] # plotitem = plotaxes.new_plotitem(name='category', plot_type='1d_plot') From 457955470bcb339b500ce0430974f840f6815894 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Thu, 30 May 2024 14:10:37 -0400 Subject: [PATCH 30/58] Extract name of storm from file name --- src/python/geoclaw/surge/storm.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/python/geoclaw/surge/storm.py b/src/python/geoclaw/surge/storm.py index c7f4ac65b..8984bd93e 100644 --- a/src/python/geoclaw/surge/storm.py +++ b/src/python/geoclaw/surge/storm.py @@ -272,6 +272,10 @@ def read_geoclaw(self, path, verbose=False): - *verbose* (bool) Output more info regarding reading. """ + # Attempt to get name from file if is follows the convention name.storm + if ".storm" in os.path.splitext(path): + self.name = os.path.splitext(os.path.basename(path))[0] + # Read header with open(path, 'r') as data_file: num_casts = int(data_file.readline()) From 860ba2365db79c2dc3a3f678833f07cc232b442b Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Mon, 3 Jun 2024 14:07:23 -0400 Subject: [PATCH 31/58] Minor tweaks and fixes --- src/python/geoclaw/data.py | 3 ++- src/python/geoclaw/surge/storm.py | 28 +++++++++++++++------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/python/geoclaw/data.py b/src/python/geoclaw/data.py index e3d514ffc..927e73aee 100755 --- a/src/python/geoclaw/data.py +++ b/src/python/geoclaw/data.py @@ -546,8 +546,9 @@ class SurgeData(clawpack.clawutil.data.ClawData): 'rankine': 5, 'modified-rankine': 6, 'DeMaria': 7 + 'willoughby': 9, } - storm_spec_not_implemented = ['CLE'] + storm_spec_not_implemented = ['CLE', 'willoughby'] def __init__(self): super(SurgeData,self).__init__() diff --git a/src/python/geoclaw/surge/storm.py b/src/python/geoclaw/surge/storm.py index 8984bd93e..e1c46bd08 100644 --- a/src/python/geoclaw/surge/storm.py +++ b/src/python/geoclaw/surge/storm.py @@ -62,12 +62,12 @@ TCVitals_Basins = {"L": "North Atlantic", "E": "North East Pacific", "C": "North Central Pacific", - "W": "North West Pacific", - "B": "Bay of Bengal (North Indian Ocean)", - "A": "Arabian Sea (North Indian Ocean)", - "Q": "South Atlantic", - "P": "South Pacific", - "S": "South Indian Ocean"} + "W": "North West Pacific", + "B": "Bay of Bengal (North Indian Ocean)", + "A": "Arabian Sea (North Indian Ocean)", + "Q": "South Atlantic", + "P": "South Pacific", + "S": "South Indian Ocean"} # Tropical Cyclone Designations # see https://www.nrlmry.navy.mil/atcf_web/docs/database/new/abrdeck.html @@ -209,10 +209,12 @@ def __init__(self, path=None, file_format="ATCF", **kwargs): # Basic object support def __str__(self): r"""""" - output = "Name: %s" % self.name - output = "\n".join((output, "Dates: %s - %s" % (self.t[0].isoformat(), - self.t[-1].isoformat()) - )) + output = f"Name: {self.name}\n" + if isinstance(self.t[0], datetime.datetime): + output += f"Dates: {self.t[0].isoformat()}" + output += f" - {self.t[-1].isoformat()}" + else: + output += f"Dates: {self.t[0]} - {self.t[-1]}" return output def __repr__(self): @@ -379,7 +381,7 @@ def num_converter(x): for c in ["LAT", "LON", "VMAX", "MSLP", "ROUTER", "RMW", "RAD", "RAD1", "RAD2", "RAD3", "RAD4"]: df[c] = df[c].where(df[c] != 0, np.nan) # value 0 means NaN - df[c] = df.groupby("DATE")[c].bfill() + df[c] = df.groupby("DATE")[c].fillna(methos="bfill") df = df.groupby("DATE").first() # Wind profile (occasionally missing for older ATCF storms) @@ -904,9 +906,9 @@ def write_geoclaw(self, path, force=False, skip=True, verbose=False, fill_dict.update({"storm_radius": lambda t, storm: 500e3}) # Handle older interface that had specific fill functions if "max_wind_radius_fill" in kwargs.keys(): - fill_dict.update({"max_wind_radius": max_wind_radius_fill}) + fill_dict.update({"max_wind_radius": kwargs['max_wind_radius_fill']}) if "storm_radius_fill" in kwargs.keys(): - fill_dict.update({"storm_radius": storm_radius_fill}) + fill_dict.update({"storm_radius": kwargs['storm_radius_fill']}) # Loop through each line of data and if the line is valid, perform the # necessary work to write it out. Otherwise either raise an exception From 37d24dcc611adccf544be453cf88bd4fba101fb7 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Mon, 3 Jun 2024 15:13:33 -0400 Subject: [PATCH 32/58] Fix missing comma --- src/python/geoclaw/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/geoclaw/data.py b/src/python/geoclaw/data.py index 927e73aee..0e61ff9e1 100755 --- a/src/python/geoclaw/data.py +++ b/src/python/geoclaw/data.py @@ -545,7 +545,7 @@ class SurgeData(clawpack.clawutil.data.ClawData): 'SLOSH': 4, 'rankine': 5, 'modified-rankine': 6, - 'DeMaria': 7 + 'DeMaria': 7, 'willoughby': 9, } storm_spec_not_implemented = ['CLE', 'willoughby'] From a716f4c2a6e1b387a65ea1780d83238a0e8882be Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Mon, 3 Jun 2024 16:12:51 -0400 Subject: [PATCH 33/58] AutoPEP8 surge code --- src/python/geoclaw/surge/plot.py | 60 +++-- src/python/geoclaw/surge/quality.py | 147 ++++++----- src/python/geoclaw/surge/storm.py | 376 +++++++++++++++------------- 3 files changed, 307 insertions(+), 276 deletions(-) diff --git a/src/python/geoclaw/surge/plot.py b/src/python/geoclaw/surge/plot.py index a0e4b23a7..c2ada663f 100644 --- a/src/python/geoclaw/surge/plot.py +++ b/src/python/geoclaw/surge/plot.py @@ -41,6 +41,7 @@ surge_data = geodata.SurgeData() + class track_data(object): """Read in storm track data from run output""" @@ -83,14 +84,16 @@ def gauge_locations(current_data, gaugenos='all'): add_labels=True, xoffset=0.02, yoffset=0.02) + def gauge_dry_regions(cd, dry_tolerance=1e-16): """Masked array of zeros where gauge is dry.""" - return np.ma.masked_where(np.abs(cd.gaugesoln.q[0, :]) > dry_tolerance, - np.zeros(cd.gaugesoln.q[0,:].shape)) + return np.ma.masked_where(np.abs(cd.gaugesoln.q[0, :]) > dry_tolerance, + np.zeros(cd.gaugesoln.q[0, :].shape)) + def gauge_surface(cd, dry_tolerance=1e-16): """Sea surface at gauge masked when dry.""" - return np.ma.masked_where(np.abs(cd.gaugesoln.q[0, :]) < dry_tolerance, + return np.ma.masked_where(np.abs(cd.gaugesoln.q[0, :]) < dry_tolerance, cd.gaugesoln.q[3, :]) @@ -497,19 +500,21 @@ def plot_track(t, x, y, wind_radius, wind_speed, Pc, name=None): # Returns axes # Storm with category plotting function # ======================================================================== -def add_track(Storm, axes, plot_package=None, category_color=None, legend_loc='best', + + +def add_track(Storm, axes, plot_package=None, category_color=None, legend_loc='best', intensity=False, categorization="NHC", limits=None, track_color='red'): if category_color is None: - category_color = {5: 'red', - 4: 'orange', - 3: 'yellow', - 2: 'blue', # edit color - 1: 'violet', - 0: 'black', - -1: 'gray'} + category_color = {5: 'red', + 4: 'orange', + 3: 'yellow', + 2: 'blue', # edit color + 1: 'violet', + 0: 'black', + -1: 'gray'} category = Storm.category(categorization=categorization) - + # make it if intensity = true # basic plotting @@ -525,52 +530,55 @@ def add_track(Storm, axes, plot_package=None, category_color=None, legend_loc='b axes.set_xlabel("Longitude") axes.set_ylabel("Latitude") - - categories_legend = [] - if intensity and categorization == "NHC": + if intensity and categorization == "NHC": categories_legend = [] - # plotitem = plotaxes.new_plotitem(name='category', plot_type='1d_plot') + # plotitem = plotaxes.new_plotitem(name='category', plot_type='1d_plot') if (-1 in category): - negativeone = mlines.Line2D([], [], color=category_color[-1], marker='s', ls='', label="Tropical Depression") + negativeone = mlines.Line2D( + [], [], color=category_color[-1], marker='s', ls='', label="Tropical Depression") categories_legend.append(negativeone) if (0 in category): - zero = mlines.Line2D([], [], color=category_color[0], marker='s', ls='', label="Tropical Storn") + zero = mlines.Line2D( + [], [], color=category_color[0], marker='s', ls='', label="Tropical Storn") categories_legend.append(zero) if (1 in category): - one = mlines.Line2D([], [], color=category_color[1], marker='s', ls='', label="Category 1") + one = mlines.Line2D([], [], color=category_color[1], + marker='s', ls='', label="Category 1") categories_legend.append(one) if (2 in category): - two = mlines.Line2D([], [], color=category_color[2], marker='s', ls='', label="Category 2") + two = mlines.Line2D([], [], color=category_color[2], + marker='s', ls='', label="Category 2") categories_legend.append(two) if (3 in category): - three = mlines.Line2D([], [], color=category_color[3], marker='s', ls='', label="Category 3") + three = mlines.Line2D( + [], [], color=category_color[3], marker='s', ls='', label="Category 3") categories_legend.append(three) if (4 in category): - four = mlines.Line2D([], [], color=category_color[4], marker='s', ls='', label="Category 4") + four = mlines.Line2D( + [], [], color=category_color[4], marker='s', ls='', label="Category 4") categories_legend.append(four) if (5 in category): - five = mlines.Line2D([], [], color=category_color[5], marker='s', ls='', label="Category 5") + five = mlines.Line2D( + [], [], color=category_color[5], marker='s', ls='', label="Category 5") categories_legend.append(five) plt.legend(handles=categories_legend, loc=legend_loc) - + # if bounds is not None: # plotitem.pcolor_cmin = bounds[0] # plotitem.pcolor_cmax = bounds[1] return axes - - # if plot_type == 'pcolor' or plot_type == 'imshow': # plotitem = plotaxes.new_plotitem(name='wind', plot_type='2d_pcolor') # plotitem.plot_var = wind_speed diff --git a/src/python/geoclaw/surge/quality.py b/src/python/geoclaw/surge/quality.py index 6584efbdb..e62db2c98 100644 --- a/src/python/geoclaw/surge/quality.py +++ b/src/python/geoclaw/surge/quality.py @@ -9,13 +9,14 @@ class InstabilityError(Exception): pass + def quality_check(model_output_dir, - regions_to_check = [[-81,22,-40,55], # atlantic - [-100,15,-82,32], # gulf - [-88.5,13.25,-70,19.75]], # carribean - frames_to_check = 4, - mean_tol_abs = .01, - min_depth = 300): + regions_to_check=[[-81, 22, -40, 55], # atlantic + [-100, 15, -82, 32], # gulf + [-88.5, 13.25, -70, 19.75]], # carribean + frames_to_check=4, + mean_tol_abs=.01, + min_depth=300): """Run a simple check to flag runs that were potentially numerically unstable. This function looks through the final *frames_to_check* frames of a model output and checks all cells in AMR level 1 that are below *min_depth* and @@ -23,7 +24,7 @@ def quality_check(model_output_dir, to encompass individual basins). If the absolute value of the mean surface height for these cells, which should not really have much surge, is above/mean_tol_abs, it raises an error. - + :Input: - *model_output_dir* (str) Path to the output directory for a given model - *regions_to_check* (list of lists of floats) A list of 4-element lists @@ -33,17 +34,17 @@ def quality_check(model_output_dir, height within any region in *regions_to_check* is above this value (meters). - *min_depth* (float) Only cells that are below *min_depth* (meters) are checked, in order to avoid including cells that potentially should have large surge values. - + :Raises: - InstabilityError if the model is flagged as potentially unstable in one of the regions. """ - + # find which frame is last - output_files = glob(join(model_output_dir,'fort.b*')) + output_files = glob(join(model_output_dir, 'fort.b*')) last_frame = int(output_files[-1].split('/')[-1][-4:]) # get sl_init - with open(join(model_output_dir,'geoclaw.data'),'r') as f: + with open(join(model_output_dir, 'geoclaw.data'), 'r') as f: for l in f: if '=: sea_level' in l: sl_init = float(l.strip().split()[0]) @@ -51,44 +52,45 @@ def quality_check(model_output_dir, # bring in solution soln = Solution() for frame in range(last_frame-frames_to_check+1, last_frame+1): - soln.read(frame, path = model_output_dir, file_format='binary', read_aux=False) + soln.read(frame, path=model_output_dir, + file_format='binary', read_aux=False) for r in regions_to_check: all_vals = np.array([]) for s in soln.states: # only looking at lowest AMR level - if s.patch.level >1: + if s.patch.level > 1: continue x = s.grid.c_centers[0] y = s.grid.c_centers[1] - eta = s.q[3,:,:] - topo = eta - s.q[0,:,:] - - # only count - eta = np.where(topo<(-min_depth),eta,np.nan) - in_reg = eta[np.ix_((x[:,0]>=r[0]) & (x[:,0]<=r[2]), - (y[0,:]>=r[1]) & (y[0,:]<=r[3]))] - all_vals = np.concatenate([all_vals,in_reg.flatten()]) + eta = s.q[3, :, :] + topo = eta - s.q[0, :, :] + + # only count + eta = np.where(topo < (-min_depth), eta, np.nan) + in_reg = eta[np.ix_((x[:, 0] >= r[0]) & (x[:, 0] <= r[2]), + (y[0, :] >= r[1]) & (y[0, :] <= r[3]))] + all_vals = np.concatenate([all_vals, in_reg.flatten()]) # adjust for sl_init all_vals = all_vals - sl_init - if all_vals.shape[0]>0: + if all_vals.shape[0] > 0: if abs(np.nanmean(all_vals)) > mean_tol_abs: raise InstabilityError("Model possibly unstable due to large magnitude deep " "ocean surge at end of run ({:.1f} cm in region {})".format( - np.nanmean(all_vals)*100, r)) + np.nanmean(all_vals)*100, r)) - -def get_max_boundary_fluxes(model_output_dir, max_refinement_depth_to_check = None, - frames_to_check = 1): + +def get_max_boundary_fluxes(model_output_dir, max_refinement_depth_to_check=None, + frames_to_check=1): """A common cause of instability is a persistant normal flux at the boundary. This code checks the last frame(s) of a model output and returns the maximum magnitude normal fluxes and currents at each boundary, as well as the maximum magnitude fluxes and currents observed within the entire domain. - + :Input: - *model_output_dir* (str) Path to the output directory for a given model - *max_refinement_depth_to_check* (int or None, optional) How many refinement levels to @@ -100,20 +102,20 @@ def get_max_boundary_fluxes(model_output_dir, max_refinement_depth_to_check = No """ # find which frame is last - output_files = glob(join(model_output_dir,'fort.q*')) + output_files = glob(join(model_output_dir, 'fort.q*')) frames = [int(i.split('/')[-1][-4:]) for i in output_files] last_frame = max(frames) - + # get domain and file format - with open(join(model_output_dir,'claw.data'),'r') as f: + with open(join(model_output_dir, 'claw.data'), 'r') as f: for l in f: if '=: lower' in l: - xl,yl = [float(i) for i in l.strip().split()[:2]] + xl, yl = [float(i) for i in l.strip().split()[:2]] elif '=: upper' in l: - xu,yu = [float(i) for i in l.strip().split()[:2]] + xu, yu = [float(i) for i in l.strip().split()[:2]] elif '=: output_format' in l: of = int(l.strip().split()[0]) - 1 - file_format = ['ascii','netcdf','binary'][of] + file_format = ['ascii', 'netcdf', 'binary'][of] maxhxl = -np.inf maxhyl = -np.inf @@ -128,10 +130,11 @@ def get_max_boundary_fluxes(model_output_dir, max_refinement_depth_to_check = No maxcurrx_overall = -np.inf maxcurry_overall = -np.inf - for f in range(last_frame+1-frames_to_check,last_frame+1): + for f in range(last_frame+1-frames_to_check, last_frame+1): soln = Solution() - soln.read(f, path = model_output_dir, file_format=file_format, read_aux=False) + soln.read(f, path=model_output_dir, + file_format=file_format, read_aux=False) for s in soln.states: @@ -139,50 +142,56 @@ def get_max_boundary_fluxes(model_output_dir, max_refinement_depth_to_check = No if max_refinement_depth_to_check is not None: if s.patch.level > max_refinement_depth_to_check: continue - + # get rounding error tolerance delta = s.grid.dimensions[0].delta edge_tol = delta * .001 - + x = s.grid.c_centers[0] xedges = s.grid.c_nodes[0] y = s.grid.c_centers[1] yedges = s.grid.c_nodes[1] - eta = s.q[3,:,:] - h = s.q[0,:,:] - hx = s.q[1,:,:] + eta = s.q[3, :, :] + h = s.q[0, :, :] + hx = s.q[1, :, :] curr_x = hx / h - hy = s.q[2,:,:] + hy = s.q[2, :, :] curr_y = hy / h - topo = eta - s.q[0,:,:] - maxhx_overall = np.nanmax([np.abs(hx).max(),maxhx_overall]) - maxhy_overall = np.nanmax([np.abs(hy).max(),maxhy_overall]) - maxcurrx_overall = np.nanmax([np.nanmax(np.abs(curr_x)),maxcurrx_overall]) - maxcurry_overall = np.nanmax([np.nanmax(np.abs(curr_y)),maxcurry_overall]) - - if abs(xedges[0,0] - xl) < edge_tol: - maxhxl = np.nanmax([maxhxl,np.nanmax(np.abs(hx[0,:]))]) - maxcurrxl = np.nanmax([maxcurrxl,np.nanmax(np.abs(curr_x[0,:]))]) - if abs(xedges[-1,0] - xu) < edge_tol: - maxhxu = np.nanmax([maxhxu,np.nanmax(np.abs(hx[-1,:]))]) - maxcurrxu = np.nanmax([maxcurrxu,np.nanmax(np.abs(curr_x[-1,:]))]) - if abs(yedges[0,0] - yl) < edge_tol: - maxhyl = np.nanmax([maxhyl,np.nanmax(np.abs(hy[:,0]))]) - maxcurryl = np.nanmax([maxcurryl,np.nanmax(np.abs(curr_y[:,0]))]) - if abs(yedges[0,-1] - yu) < edge_tol: - maxhyu = np.nanmax([maxhyu,np.nanmax(np.abs(hy[:,-1]))]) - maxcurryu = np.nanmax([maxcurryu,np.nanmax(np.abs(curr_y[:,-1]))]) - - return ({'max_normal_fluxes':{'W': maxhxl, - 'E': maxhxu, - 'N': maxhyu, - 'S': maxhyl}, - 'max_normal_currents':{'W': maxcurrxl, - 'E': maxcurrxu, - 'N': maxcurryu, - 'S': maxcurryl}, + topo = eta - s.q[0, :, :] + maxhx_overall = np.nanmax([np.abs(hx).max(), maxhx_overall]) + maxhy_overall = np.nanmax([np.abs(hy).max(), maxhy_overall]) + maxcurrx_overall = np.nanmax( + [np.nanmax(np.abs(curr_x)), maxcurrx_overall]) + maxcurry_overall = np.nanmax( + [np.nanmax(np.abs(curr_y)), maxcurry_overall]) + + if abs(xedges[0, 0] - xl) < edge_tol: + maxhxl = np.nanmax([maxhxl, np.nanmax(np.abs(hx[0, :]))]) + maxcurrxl = np.nanmax( + [maxcurrxl, np.nanmax(np.abs(curr_x[0, :]))]) + if abs(xedges[-1, 0] - xu) < edge_tol: + maxhxu = np.nanmax([maxhxu, np.nanmax(np.abs(hx[-1, :]))]) + maxcurrxu = np.nanmax( + [maxcurrxu, np.nanmax(np.abs(curr_x[-1, :]))]) + if abs(yedges[0, 0] - yl) < edge_tol: + maxhyl = np.nanmax([maxhyl, np.nanmax(np.abs(hy[:, 0]))]) + maxcurryl = np.nanmax( + [maxcurryl, np.nanmax(np.abs(curr_y[:, 0]))]) + if abs(yedges[0, -1] - yu) < edge_tol: + maxhyu = np.nanmax([maxhyu, np.nanmax(np.abs(hy[:, -1]))]) + maxcurryu = np.nanmax( + [maxcurryu, np.nanmax(np.abs(curr_y[:, -1]))]) + + return ({'max_normal_fluxes': {'W': maxhxl, + 'E': maxhxu, + 'N': maxhyu, + 'S': maxhyl}, + 'max_normal_currents': {'W': maxcurrxl, + 'E': maxcurrxu, + 'N': maxcurryu, + 'S': maxcurryl}, 'domain_maxs': {'hu': maxhx_overall, 'hv': maxhy_overall, 'u': maxcurrx_overall, - 'v': maxcurry_overall}}) \ No newline at end of file + 'v': maxcurry_overall}}) diff --git a/src/python/geoclaw/surge/storm.py b/src/python/geoclaw/surge/storm.py index e1c46bd08..5ef601bb4 100644 --- a/src/python/geoclaw/surge/storm.py +++ b/src/python/geoclaw/surge/storm.py @@ -41,7 +41,6 @@ import pandas as pd import clawpack.geoclaw.units as units -import clawpack.clawutil.data # ============================================================================= # Common acronyms across formats @@ -114,6 +113,7 @@ missing_necessary_data_warning_str = """No storm points in the input file had both a max wind speed and a central pressure observation.""" + class NoDataError(ValueError): """Exception to raise when no valid data in input file""" pass @@ -282,8 +282,8 @@ def read_geoclaw(self, path, verbose=False): with open(path, 'r') as data_file: num_casts = int(data_file.readline()) self.time_offset = datetime.datetime.strptime( - data_file.readline()[:19], - '%Y-%m-%dT%H:%M:%S') + data_file.readline()[:19], + '%Y-%m-%dT%H:%M:%S') # Read rest of data data = np.loadtxt(path, skiprows=3) num_forecasts = data.shape[0] @@ -311,7 +311,7 @@ def read_atcf(self, path, verbose=False): # See here for the ATCF format documentation: # https://www.nrlmry.navy.mil/atcf_web/docs/database/new/abdeck.txt - + # Slightly more robust converter for ATCF data fields that can be # missing def num_converter(x): @@ -327,18 +327,18 @@ def num_converter(x): return float(x) df = pd.read_csv(path, engine="python", sep=",+", names=[ - "BASIN", "CY", "YYYYMMDDHH", "TECHNUM", "TECH", "TAU", - "LAT", "LON", "VMAX", "MSLP", "TY", - "RAD", "WINDCODE", "RAD1", "RAD2", "RAD3", "RAD4", - "POUTER", "ROUTER", "RMW", "GUSTS", "EYE", "SUBREGION", - "MAXSEAS", "INITIALS", "DIR", "SPEED", "STORMNAME", "DEPTH", - "SEAS", "SEASCODE", "SEAS1", "SEAS2", "SEAS3", "SEAS4", - "USERDEFINE1", "userdata1", - "USERDEFINE2", "userdata2", - "USERDEFINE3", "userdata3", - "USERDEFINE4", "userdata4", - "USERDEFINE5", "userdata5", - ], + "BASIN", "CY", "YYYYMMDDHH", "TECHNUM", "TECH", "TAU", + "LAT", "LON", "VMAX", "MSLP", "TY", + "RAD", "WINDCODE", "RAD1", "RAD2", "RAD3", "RAD4", + "POUTER", "ROUTER", "RMW", "GUSTS", "EYE", "SUBREGION", + "MAXSEAS", "INITIALS", "DIR", "SPEED", "STORMNAME", "DEPTH", + "SEAS", "SEASCODE", "SEAS1", "SEAS2", "SEAS3", "SEAS4", + "USERDEFINE1", "userdata1", + "USERDEFINE2", "userdata2", + "USERDEFINE3", "userdata3", + "USERDEFINE4", "userdata4", + "USERDEFINE5", "userdata5", + ], converters={ "YYYYMMDDHH": lambda d: datetime.datetime( int(d[1:5]), int(d[5:7]), int(d[7:9]), int(d[9:11])), @@ -354,14 +354,14 @@ def num_converter(x): "ROUTER": num_converter, "RMW": num_converter, "STORMNAME": lambda d: (d.strip() if isinstance(d, str) else d) - }, + }, dtype={ "BASIN": str, "CY": int, "VMAX": float, "MSLP": float, "TY": str - }) + }) # Grab data regarding basin and cyclone number from first row self.basin = ATCF_basins[df["BASIN"][0]] @@ -373,10 +373,9 @@ def num_converter(x): # Take forecast period TAU into consideration df['DATE'] = df["YYYYMMDDHH"] + df["TAU"] df = df[["DATE", "TAU", "TY", "LAT", "LON", "VMAX", "MSLP", - "ROUTER", "RMW", "RAD", "RAD1", "RAD2", "RAD3", "RAD4",]] + "ROUTER", "RMW", "RAD", "RAD1", "RAD2", "RAD3", "RAD4", ]] df = df.sort_values(by=["DATE", "TAU"]).reset_index(drop=True) - # For each DATE, choose best (smallest TAU) available data for c in ["LAT", "LON", "VMAX", "MSLP", "ROUTER", "RMW", "RAD", "RAD1", "RAD2", "RAD3", "RAD4"]: @@ -386,7 +385,8 @@ def num_converter(x): # Wind profile (occasionally missing for older ATCF storms) # Wind speeds and their radii - df["RAD_MEAN"] = df[["RAD1", "RAD2", "RAD3", "RAD4"]].mean(axis=1, skipna=True) + df["RAD_MEAN"] = df[["RAD1", "RAD2", "RAD3", "RAD4"]].mean( + axis=1, skipna=True) df = df.drop(["TAU", "RAD1", "RAD2", "RAD3", "RAD4"], axis=1) df = df.dropna(how="any", subset=["LAT", "LON"]) @@ -404,13 +404,17 @@ def num_converter(x): # max_wind_radius - convert from nm to m - 1.8520000031807990 * 1000.0 # central_pressure - convert from mbar to Pa - 100.0 # Radius of last isobar contour - convert from nm to m - 1.852000003180799d0 * 1000.0 - self.max_wind_speed = units.convert(df["VMAX"].to_numpy(), 'knots', 'm/s') - self.central_pressure = units.convert(df["MSLP"].to_numpy(), 'mbar', 'Pa') + self.max_wind_speed = units.convert( + df["VMAX"].to_numpy(), 'knots', 'm/s') + self.central_pressure = units.convert( + df["MSLP"].to_numpy(), 'mbar', 'Pa') self.max_wind_radius = units.convert(df["RMW"].to_numpy(), 'nmi', 'm') self.storm_radius = units.convert(df["ROUTER"].to_numpy(), 'nmi', 'm') - self.wind_speeds = df[["RAD","RAD_MEAN"]].to_numpy() - self.wind_speeds[:,0] = units.convert(self.wind_speeds[:,0], 'knots', 'm/s') - self.wind_speeds[:,1] = units.convert(self.wind_speeds[:,1], 'nmi', 'm') + self.wind_speeds = df[["RAD", "RAD_MEAN"]].to_numpy() + self.wind_speeds[:, 0] = units.convert( + self.wind_speeds[:, 0], 'knots', 'm/s') + self.wind_speeds[:, 1] = units.convert( + self.wind_speeds[:, 1], 'nmi', 'm') def read_hurdat(self, path, verbose=False): r"""Read in HURDAT formatted storm file @@ -492,28 +496,30 @@ def read_hurdat(self, path, verbose=False): # Intensity information - radii are not included directly in this # format and instead radii of winds above a threshold are included - self.max_wind_speed[i] = units.convert(float(data[6]), 'knots', 'm/s') - self.central_pressure[i] = units.convert(float(data[7]), 'mbar', 'Pa') + self.max_wind_speed[i] = units.convert( + float(data[6]), 'knots', 'm/s') + self.central_pressure[i] = units.convert( + float(data[7]), 'mbar', 'Pa') warnings.warn(missing_data_warning_str) self.max_wind_radius[i] = -1 self.storm_radius[i] = -1 def read_ibtracs(self, path, sid=None, storm_name=None, year=None, start_date=None, - agency_pref = ['wmo', - 'usa', - 'tokyo', - 'newdelhi', - 'reunion', - 'bom', - 'nadi', - 'wellington', - 'cma', - 'hko', - 'ds824', - 'td9636', - 'td9635', - 'neumann', - 'mlc']): + agency_pref=['wmo', + 'usa', + 'tokyo', + 'newdelhi', + 'reunion', + 'bom', + 'nadi', + 'wellington', + 'cma', + 'hko', + 'ds824', + 'td9636', + 'td9635', + 'neumann', + 'mlc']): r"""Read in IBTrACS formatted storm file This reads in the netcdf-formatted IBTrACS v4 data. You must either pass @@ -555,7 +561,8 @@ def read_ibtracs(self, path, sid=None, storm_name=None, year=None, start_date=No # only allow one method for specifying storms if (sid is not None) and ((storm_name is not None) or (year is not None)): - raise ValueError('Cannot specify both *sid* and *storm_name* or *year*.') + raise ValueError( + 'Cannot specify both *sid* and *storm_name* or *year*.') with xr.open_dataset(path) as ds: @@ -566,7 +573,7 @@ def read_ibtracs(self, path, sid=None, storm_name=None, year=None, start_date=No else: storm_name = storm_name.upper() # in case storm is unnamed - if storm_name.upper() in ['UNNAMED','NO-NAME']: + if storm_name.upper() in ['UNNAMED', 'NO-NAME']: storm_name = 'NOT_NAMED' storm_match = (ds.name == storm_name.encode()) year_match = (ds.time.dt.year == year).any(dim='date_time') @@ -579,7 +586,8 @@ def read_ibtracs(self, path, sid=None, storm_name=None, year=None, start_date=No raise ValueError('Storm/year not found in provided file') else: # see if a date was provided for multiple unnamed storms - assert start_date is not None, ValueError('Multiple storms identified and no start_date specified.') + assert start_date is not None, ValueError( + 'Multiple storms identified and no start_date specified.') start_times = ds.time.isel(date_time=0) start_date = np.datetime64(start_date) @@ -595,36 +603,35 @@ def read_ibtracs(self, path, sid=None, storm_name=None, year=None, start_date=No raise ValueError('No valid wind speeds found for this storm.') ds = ds.sel(date_time=valid_t) - # list of the agencies that correspond to 'usa_*' variables usa_agencies = [b'atcf', b'hurdat_atl', b'hurdat_epa', b'jtwc_ep', - b'nhc_working_bt', b'tcvightals', b'tcvitals'] + b'nhc_working_bt', b'tcvightals', b'tcvitals'] - - ## Create mapping from wmo_ or usa_agency - ## to the appropriate variable - agency_map = {b'':agency_pref.index('wmo')} + # Create mapping from wmo_ or usa_agency + # to the appropriate variable + agency_map = {b'': agency_pref.index('wmo')} # account for multiple usa agencies for a in usa_agencies: agency_map[a] = agency_pref.index('usa') # map all other agencies to themselves - for i in [a for a in agency_pref if a not in ['wmo','usa']]: + for i in [a for a in agency_pref if a not in ['wmo', 'usa']]: agency_map[i.encode('utf-8')] = agency_pref.index(i) # fill in usa as provider if usa_agency is # non-null when wmo_agency is null - provider = ds.wmo_agency.where(ds.wmo_agency!=b'',ds.usa_agency) + provider = ds.wmo_agency.where(ds.wmo_agency != b'', ds.usa_agency) # get index into from agency that is wmo_provider def map_val_to_ix(a): - func = lambda x: agency_map[x] - return xr.apply_ufunc(func,a, vectorize=True) - pref_agency_ix=map_val_to_ix(provider) + def func(x): return agency_map[x] + return xr.apply_ufunc(func, a, vectorize=True) + pref_agency_ix = map_val_to_ix(provider) - ## GET MAX WIND SPEED and PRES + # GET MAX WIND SPEED and PRES pref_vals = {} - for v in ['wind','pres']: - all_vals = ds[['{}_{}'.format(i,v) for i in agency_pref]].to_array(dim='agency') + for v in ['wind', 'pres']: + all_vals = ds[['{}_{}'.format(i, v) for i in agency_pref]].to_array( + dim='agency') # get wmo value val_pref = ds['wmo_'+v] @@ -642,29 +649,26 @@ def map_val_to_ix(a): # add to dict pref_vals[v] = val_pref - - ## THESE CANNOT BE MISSING SO DROP - ## IF EITHER MISSING + # THESE CANNOT BE MISSING SO DROP + # IF EITHER MISSING valid = pref_vals['wind'].notnull() & pref_vals['pres'].notnull() if not valid.any(): raise NoDataError(missing_necessary_data_warning_str) ds = ds.sel(date_time=valid) - for i in ['wind','pres']: + for i in ['wind', 'pres']: pref_vals[i] = pref_vals[i].sel(date_time=valid) - - ## GET RMW and ROCI - ## (these can be missing) - for r in ['rmw','roci']: - order = ['{}_{}'.format(i,r) for i in agency_pref if - '{}_{}'.format(i,r) in ds.data_vars.keys()] + # GET RMW and ROCI + # (these can be missing) + for r in ['rmw', 'roci']: + order = ['{}_{}'.format(i, r) for i in agency_pref if + '{}_{}'.format(i, r) in ds.data_vars.keys()] vals = ds[order].to_array(dim='agency') best_ix = vals.notnull().argmax(dim='agency') val_pref = vals.isel(agency=best_ix) pref_vals[r] = val_pref - - ## CONVERT TO GEOCLAW FORMAT + # CONVERT TO GEOCLAW FORMAT # assign basin to be the basin where track originates # in case track moves across basins @@ -676,36 +680,40 @@ def map_val_to_ix(a): self.t = [] for d in ds.time: t = d.dt - self.t.append(datetime.datetime(t.year,t.month,t.day,t.hour,t.minute,t.second)) + self.t.append(datetime.datetime(t.year, t.month, + t.day, t.hour, t.minute, t.second)) - ## events + # events self.event = ds.usa_record.values.astype(str) - ## time offset - if (self.event=='L').any(): + # time offset + if (self.event == 'L').any(): # if landfall, use last landfall - self.time_offset = np.array(self.t)[self.event=='L'][-1] + self.time_offset = np.array(self.t)[self.event == 'L'][-1] else: - #if no landfall, use last time of storm + # if no landfall, use last time of storm self.time_offset = self.t[-1] # Classification, note that this is not the category of the storm self.classification = ds.usa_status.values - self.eye_location = np.array([ds.lon,ds.lat]).T + self.eye_location = np.array([ds.lon, ds.lat]).T # Intensity information - for now, including only common, basic intensity # info. # TODO: add more detailed info for storms that have it - self.max_wind_speed = units.convert(pref_vals['wind'],'knots','m/s').where(pref_vals['wind'].notnull(),-1).values - self.central_pressure = units.convert(pref_vals['pres'],'mbar','Pa').where(pref_vals['pres'].notnull(),-1).values - self.max_wind_radius = units.convert(pref_vals['rmw'],'nmi','m').where(pref_vals['rmw'].notnull(),-1).values - self.storm_radius = units.convert(pref_vals['roci'],'nmi','m').where(pref_vals['roci'].notnull(),-1).values + self.max_wind_speed = units.convert( + pref_vals['wind'], 'knots', 'm/s').where(pref_vals['wind'].notnull(), -1).values + self.central_pressure = units.convert(pref_vals['pres'], 'mbar', 'Pa').where( + pref_vals['pres'].notnull(), -1).values + self.max_wind_radius = units.convert(pref_vals['rmw'], 'nmi', 'm').where( + pref_vals['rmw'].notnull(), -1).values + self.storm_radius = units.convert(pref_vals['roci'], 'nmi', 'm').where( + pref_vals['roci'].notnull(), -1).values # warn if you have missing vals for RMW or ROCI if (self.max_wind_radius.max()) == -1 or (self.storm_radius.max() == -1): warnings.warn(missing_data_warning_str) - def read_jma(self, path, verbose=False): r"""Read in JMA formatted storm file @@ -766,13 +774,14 @@ def read_jma(self, path, verbose=False): # Intensity information - current the radii are not directly given # Available data includes max/min of radius of winds of 50 and # 30 kts instead - self.central_pressure[i] = units.convert(float(data[5]), 'hPa', 'Pa') - self.max_wind_speed[i] = units.convert(float(data[6]), 'knots', 'm/s') + self.central_pressure[i] = units.convert( + float(data[5]), 'hPa', 'Pa') + self.max_wind_speed[i] = units.convert( + float(data[6]), 'knots', 'm/s') warnings.warn(missing_data_warning_str) self.max_wind_radius[i] = -1 self.storm_radius[i] = -1 - def read_imd(self, path, verbose=False): r"""Extract relevant hurricane data from IMD file and update storm fields with proper values. @@ -786,7 +795,6 @@ def read_imd(self, path, verbose=False): "implemented yet but is planned for a ", "future release.")) - def read_tcvitals(self, path, verbose=False): r"""Extract relevant hurricane data from TCVITALS file and update storm fields with proper values. @@ -848,13 +856,14 @@ def read_tcvitals(self, path, verbose=False): # Intensity Information self.max_wind_speed[i] = float(data[12]) - self.central_pressure[i] = units.convert(float(data[9]), 'mbar', 'Pa') + self.central_pressure[i] = units.convert( + float(data[9]), 'mbar', 'Pa') self.max_wind_radius[i] = units.convert(float(data[13]), 'km', 'm') self.storm_radius[i] = units.convert(float(data[11]), 'km', 'm') - # ========================================================================= # Write Routines + def write(self, path, file_format="geoclaw", **kwargs): r"""Write out the storm data to *path* in format *file_format* @@ -877,8 +886,8 @@ def write(self, path, file_format="geoclaw", **kwargs): getattr(self, 'write_%s' % file_format.lower())(path, **kwargs) - def write_geoclaw(self, path, force=False, skip=True, verbose=False, - fill_dict={}, **kwargs): + def write_geoclaw(self, path, force=False, skip=True, verbose=False, + fill_dict={}, **kwargs): r"""Write out a GeoClaw formatted storm file GeoClaw storm files are read in by the GeoClaw Fortran code. @@ -906,7 +915,8 @@ def write_geoclaw(self, path, force=False, skip=True, verbose=False, fill_dict.update({"storm_radius": lambda t, storm: 500e3}) # Handle older interface that had specific fill functions if "max_wind_radius_fill" in kwargs.keys(): - fill_dict.update({"max_wind_radius": kwargs['max_wind_radius_fill']}) + fill_dict.update( + {"max_wind_radius": kwargs['max_wind_radius_fill']}) if "storm_radius_fill" in kwargs.keys(): fill_dict.update({"storm_radius": kwargs['storm_radius_fill']}) @@ -922,12 +932,13 @@ def write_geoclaw(self, path, force=False, skip=True, verbose=False, # Check each value we need for this time to make sure it is valid valid = True - for name in ["max_wind_speed", "central_pressure", + for name in ["max_wind_speed", "central_pressure", "max_wind_radius", "storm_radius"]: if np.isnan(getattr(self, name)[n]): if name in fill_dict.keys(): # Fill value with function provided - getattr(self, name)[n] = fill_dict[name](self.t[n], self) + getattr(self, name)[n] = fill_dict[name]( + self.t[n], self) elif skip: # Skip this line valid = False @@ -941,7 +952,7 @@ def write_geoclaw(self, path, force=False, skip=True, verbose=False, # exception given the NaN msg = ("The value {} at {} is a NaN and the storm " + "will not be written in GeoClaw format. If " + - "you want to fill in the value provide a " + + "you want to fill in the value provide a " + "function or set `force=True`.") raise ValueError(msg.format(name, self.t[n])) if not valid: @@ -951,7 +962,7 @@ def write_geoclaw(self, path, force=False, skip=True, verbose=False, num_casts += 1 data.append(np.empty(7)) - # If we do not have a time offset use the first valid row as the + # If we do not have a time offset use the first valid row as the # offset time if self.time_offset is None: self.time_offset = self.t[n] @@ -982,7 +993,7 @@ def write_geoclaw(self, path, force=False, skip=True, verbose=False, data_file.write(f"{self.time_offset.isoformat()}\n\n") else: data_file.write(f"{str(self.time_offset)}\n\n") - + # Write data lines for line in data: data_file.write(format_string.format(*line)) @@ -993,7 +1004,6 @@ def write_geoclaw(self, path, force=False, skip=True, verbose=False, os.remove(path) raise e - def write_atcf(self, path, verbose=False): r"""Write out a ATCF formatted storm file @@ -1007,24 +1017,24 @@ def write_atcf(self, path, verbose=False): with open(path, 'w') as data_file: for n in range(len(self.t)): data_file.write("".join((", " * 2, - "%s" % seconds2date(self.t[n]), - ", " * 4, - "%s" % (int(self.eye_location[n, 0] * - 10.0)), - ", ", - "%s" % (int(self.eye_location[n, 1] * - 10.0)), - ", ", - "%s" % self.max_wind_speed[n], - ", ", - "%s" % self.central_pressure[n], - ", ", - ", " * 8, - "%s" % self.storm_radius[n], - ", ", - "%s" % self.max_wind_radius[n], - ", " * 10, - "\n"))) + "%s" % seconds2date(self.t[n]), + ", " * 4, + "%s" % (int(self.eye_location[n, 0] * + 10.0)), + ", ", + "%s" % (int(self.eye_location[n, 1] * + 10.0)), + ", ", + "%s" % self.max_wind_speed[n], + ", ", + "%s" % self.central_pressure[n], + ", ", + ", " * 8, + "%s" % self.storm_radius[n], + ", ", + "%s" % self.max_wind_radius[n], + ", " * 10, + "\n"))) except Exception as e: # Remove possiblly partially generated file if not successful if os.path.exists(path): @@ -1063,23 +1073,23 @@ def write_hurdat(self, path, verbose=False): longitude = str(np.abs(longitude)) + 'W' data_file.write("".join(("%s" % self.seconds2date( - self.t[n])[0:-2], - "%s00" % self.seconds2date( - self.t[n])[-2:], - ", " * 3, - "%s" % (latitude), - ", ", - "%s" % (longitude), - ", ", - "%s" % self.max_wind_speed[n], - ", ", - "%s" % self.central_pressure[n], - ", ", - "%s" % self.storm_radius[n], - ", ", - "%s" % self.max_wind_radius[n], - ", " * 10, - "\n"))) + self.t[n])[0:-2], + "%s00" % self.seconds2date( + self.t[n])[-2:], + ", " * 3, + "%s" % (latitude), + ", ", + "%s" % (longitude), + ", ", + "%s" % self.max_wind_speed[n], + ", ", + "%s" % self.central_pressure[n], + ", ", + "%s" % self.storm_radius[n], + ", ", + "%s" % self.max_wind_radius[n], + ", " * 10, + "\n"))) except Exception as e: # Remove possiblly partially generated file if not successful if os.path.exists(path): @@ -1099,23 +1109,23 @@ def write_jma(self, path, verbose=False): with open(path, 'w') as data_file: for n in range(self.t.shape[0]): data_file.write("".join(("%s" % self.seconds2date(self.t[n]), - " " * 4, - "%s" % (int(self.eye_location[n, 0] * - 10.0)), - ", ", - "%s" % (int(self.eye_location[n, 1] * - 10.0)), - ", ", - "%s" % self.max_wind_speed[n], - ", ", - "%s" % self.central_pressure[n], - ", ", - ", " * 8, - "%s" % self.storm_radius[n], - ", ", - "%s" % self.max_wind_radius[n], - ", " * 10, - "\n"))) + " " * 4, + "%s" % (int(self.eye_location[n, 0] * + 10.0)), + ", ", + "%s" % (int(self.eye_location[n, 1] * + 10.0)), + ", ", + "%s" % self.max_wind_speed[n], + ", ", + "%s" % self.central_pressure[n], + ", ", + ", " * 8, + "%s" % self.storm_radius[n], + ", ", + "%s" % self.max_wind_radius[n], + ", " * 10, + "\n"))) except Exception as e: # Remove possiblly partially generated file if not successful if os.path.exists(path): @@ -1146,7 +1156,7 @@ def write_tcvitals(self, path, verbose=False): # ========================================================================= # Other Useful Routines - + def category(self, categorization="NHC", cat_names=False): r"""Categorizes storm based on relevant storm data @@ -1184,16 +1194,16 @@ def category(self, categorization="NHC", cat_names=False): (speeds >= 48) * (speeds < 56) * 10 + (speeds >= 56) * (speeds < 64) * 11 + (speeds >= 64) * 12) - cat_map = { 0: "Calm", - 1: "Light air", - 2: "Light breeze", - 3: "Gentle breeze", - 4: "Moderate breeze", - 5: "Fresh breeze", - 6: "Strong breeze", - 7: "High wind", - 8: "Gale", - 9: "Strong gale", + cat_map = {0: "Calm", + 1: "Light air", + 2: "Light breeze", + 3: "Gentle breeze", + 4: "Moderate breeze", + 5: "Fresh breeze", + 6: "Strong breeze", + 7: "High wind", + 8: "Gale", + 9: "Strong gale", 10: "Whole gale", 11: "Violent storm", 12: "Hurricane"} @@ -1211,12 +1221,12 @@ def category(self, categorization="NHC", cat_names=False): (speeds >= 113) * (speeds < 135) * 4 + (speeds >= 135) * 5) cat_map = {-1: "Tropical Depression", - 0: "Tropical Storm", - 1: "Category 1 Hurricane", - 2: "Category 2 Hurricane", - 3: "Category 3 Hurricane", - 4: "Category 4 Hurricane", - 5: "Category 5 Hurricane"} + 0: "Tropical Storm", + 1: "Category 1 Hurricane", + 2: "Category 2 Hurricane", + 3: "Category 3 Hurricane", + 4: "Category 4 Hurricane", + 5: "Category 5 Hurricane"} elif categorization.upper() == "JTWC": raise NotImplementedError("JTWC categorization not implemented.") @@ -1340,17 +1350,17 @@ def fill_rad_w_other_source(t, storm_targ, storm_fill, var, interp_kwargs={}): print("fill_rad_w_other_source currently requires xarray to work.") raise e - fill_da = xr.DataArray(getattr(storm_fill,var), - coords = {'t': getattr(storm_fill,'t')}, - dims = ('t',)) + fill_da = xr.DataArray(getattr(storm_fill, var), + coords={'t': getattr(storm_fill, 't')}, + dims=('t',)) # convert -1 to nan - fill_da = fill_da.where(fill_da>0,np.nan) + fill_da = fill_da.where(fill_da > 0, np.nan) # if not all missing, try using storm_fill to fill if fill_da.notnull().any(): - #remove duplicates + # remove duplicates fill_da = fill_da.groupby('t').first() # remove NaNs @@ -1365,10 +1375,10 @@ def fill_rad_w_other_source(t, storm_targ, storm_fill, var, interp_kwargs={}): return fill_interp # next, try just interpolating other ibtracs values - targ_da = xr.DataArray(getattr(storm_targ,var), - coords = {'t': getattr(storm_targ,'t')}, - dims = ('t',)) - targ_da = targ_da.where(targ_da>0,np.nan) + targ_da = xr.DataArray(getattr(storm_targ, var), + coords={'t': getattr(storm_targ, 't')}, + dims=('t',)) + targ_da = targ_da.where(targ_da > 0, np.nan) if targ_da.notnull().any(): targ_da = targ_da.groupby('t').first() targ_da = targ_da.dropna('t') @@ -1418,19 +1428,23 @@ def make_multi_structure(path): fileWrite.writelines(line) else: fileWrite.close() - stormDict[curTime].update({curTrack: Storm(path=os.path.join(os.path.expandvars(os.getcwd()), "Clipped_ATCFs", curTime, curTrack), file_format="ATCF")}) + stormDict[curTime].update({curTrack: Storm(path=os.path.join(os.path.expandvars( + os.getcwd()), "Clipped_ATCFs", curTime, curTrack), file_format="ATCF")}) curTrack = lineArr[4] - fileWrite = open("Clipped_ATCFs/" + curTime + "/" + curTrack, 'w') + fileWrite = open("Clipped_ATCFs/" + + curTime + "/" + curTrack, 'w') fileWrite.writelines(line) else: if curTime != "test": fileWrite.close() - stormDict[curTime].update({curTrack: Storm(path=os.path.join(os.path.expandvars(os.getcwd()), "Clipped_ATCFs", curTime, curTrack), file_format="ATCF")}) + stormDict[curTime].update({curTrack: Storm(path=os.path.join(os.path.expandvars( + os.getcwd()), "Clipped_ATCFs", curTime, curTrack), file_format="ATCF")}) curTime = lineArr[2] curTrack = lineArr[4] stormDict[curTime] = {} os.mkdir("Clipped_ATCFs/" + curTime) - fileWrite = open("Clipped_ATCFs/" + curTime + "/" + curTrack, 'w') + fileWrite = open("Clipped_ATCFs/" + + curTime + "/" + curTrack, 'w') fileWrite.writelines(line) return stormDict From ff1ce7cfc67cea59c735c97d9ac98c44b90274b4 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Wed, 5 Jun 2024 14:17:29 -0800 Subject: [PATCH 34/58] add fgout output_style parameter and support for array of output_times --- src/2d/shallow/fgout_module.f90 | 105 +++++++++++++++++------------- src/python/geoclaw/fgout_tools.py | 30 +++++++-- 2 files changed, 83 insertions(+), 52 deletions(-) diff --git a/src/2d/shallow/fgout_module.f90 b/src/2d/shallow/fgout_module.f90 index bd9effda3..8f422730e 100644 --- a/src/2d/shallow/fgout_module.f90 +++ b/src/2d/shallow/fgout_module.f90 @@ -10,7 +10,7 @@ module fgout_module real(kind=8), pointer :: late(:,:,:) ! Geometry - integer :: num_vars(2),mx,my,point_style,fgno,output_format + integer :: num_vars(2),mx,my,point_style,fgno,output_format,output_style real(kind=8) :: dx,dy,x_low,x_hi,y_low,y_hi ! Time Tracking and output types @@ -84,18 +84,27 @@ subroutine set_fgout(rest,fname) fg => FGOUT_fgrids(i) ! Read in this grid's data read(unit,*) fg%fgno - read(unit,*) fg%start_time - read(unit,*) fg%end_time + read(unit,*) fg%output_style read(unit,*) fg%num_output + allocate(fg%output_times(fg%num_output)) + allocate(fg%output_frames(fg%num_output)) + + if (fg%output_style == 1) then + read(unit,*) fg%start_time + read(unit,*) fg%end_time + else if (fg%output_style == 2) then + read(unit,*) (fg%output_times(k), k=1,fg%num_output) + fg%start_time = fg%output_times(1) + fg%end_time = fg%output_times(fg%num_output) + endif + read(unit,*) fg%point_style read(unit,*) fg%output_format read(unit,*) fg%mx, fg%my read(unit,*) fg%x_low, fg%y_low read(unit,*) fg%x_hi, fg%y_hi - allocate(fg%output_times(fg%num_output)) - allocate(fg%output_frames(fg%num_output)) - + ! Initialize next_output_index ! (might be reset below in case of a restart) fg%next_output_index = 1 @@ -104,49 +113,55 @@ subroutine set_fgout(rest,fname) print *, 'set_fgout: ERROR, unrecognized point_style = ',\ fg%point_style endif - - ! Setup data for this grid - ! Set dtfg (the timestep length between outputs) for each grid - if (fg%end_time <= fg%start_time) then - if (fg%num_output > 1) then - print *,'set_fgout: ERROR for fixed grid', i - print *,'start_time <= end_time yet num_output > 1' - print *,'set end_time > start_time or set num_output = 1' - stop + + if (fg%output_style == 1) then + ! Setup data for this grid + ! Set fg%dt (the timestep length between outputs) + if (fg%end_time <= fg%start_time) then + if (fg%num_output > 1) then + print *,'set_fgout: ERROR for fixed grid', i + print *,'start_time <= end_time yet num_output > 1' + print *,'set end_time > start_time or set num_output = 1' + stop + else + ! only a single fgout time: + fg%dt = 0.d0 + endif + else + if (fg%num_output < 2) then + print *,'set_fgout: ERROR for fixed grid', i + print *,'end_time > start_time, yet num_output = 1' + print *,'set num_output > 2' + stop + else + fg%dt = (fg%end_time - fg%start_time) & + / (fg%num_output - 1) + do k=1,fg%num_output + fg%output_times(k) = fg%start_time + (k-1)*fg%dt + enddo + endif + endif + endif + + do k=1,fg%num_output + if (rest) then + ! don't write initial time or earlier + ts = tstart_thisrun+FGOUT_ttol else - fg%dt = 0.d0 + ! do write initial time + ts = tstart_thisrun-FGOUT_ttol endif - else - if (fg%num_output < 2) then - print *,'set_fgout: ERROR for fixed grid', i - print *,'end_time > start_time, yet num_output = 1' - print *,'set num_output > 2' - stop + + if (fg%output_times(k) < ts) then + ! will not output this time in this run + ! (might have already be done when restarting) + fg%output_frames(k) = -2 + fg%next_output_index = k+1 else - fg%dt = (fg%end_time - fg%start_time) & - / (fg%num_output - 1) - do k=1,fg%num_output - fg%output_times(k) = fg%start_time + (k-1)*fg%dt - if (rest) then - ! don't write initial time or earlier - ts = tstart_thisrun+FGOUT_ttol - else - ! do write initial time - ts = tstart_thisrun-FGOUT_ttol - endif - - if (fg%output_times(k) < ts) then - ! will not output this time in this run - ! (might have already be done when restarting) - fg%output_frames(k) = -2 - fg%next_output_index = k+1 - else - ! will be reset to frameno when this is written - fg%output_frames(k) = -1 - endif - enddo + ! will be reset to frameno when this is written + fg%output_frames(k) = -1 endif - endif + enddo diff --git a/src/python/geoclaw/fgout_tools.py b/src/python/geoclaw/fgout_tools.py index 11494b124..b140c98e2 100644 --- a/src/python/geoclaw/fgout_tools.py +++ b/src/python/geoclaw/fgout_tools.py @@ -178,6 +178,7 @@ def __init__(self,fgno=None,outdir=None,output_format=None): self.npts = None self.nx = None self.ny = None + self.output_style = 1 self.tstart = None self.tend = None self.nout = None @@ -372,18 +373,33 @@ def write_to_fgout_data(self, fid): errmsg = "fgout output_format must be ascii, binary32, or binary64" raise NotImplementedError(errmsg) - assert self.tstart is not None, 'Need to set tstart' - assert self.tend is not None, 'Need to set tend' - assert self.nout is not None, 'Need to set nout' - assert self.point_style is not None, 'Need to set point_style' + # write header, independent of point_style: #fid = open(self.input_file_name,'w') fid.write("\n") fid.write("%i # fgno\n" % self.fgno) - fid.write("%16.10e # tstart\n" % self.tstart) - fid.write("%16.10e # tend\n" % self.tend) - fid.write("%i %s # nout\n" % (self.nout, 11*" ")) + + fid.write("%i # output_style\n" \ + % self.output_style) + + if self.output_style == 1: + assert self.tstart is not None, 'Need to set tstart' + assert self.tend is not None, 'Need to set tend' + assert self.nout is not None, 'Need to set nout' + fid.write("%i %s # nout\n" % (self.nout, 11*" ")) + fid.write("%16.10e # tstart\n" % self.tstart) + fid.write("%16.10e # tend\n" % self.tend) + elif self.output_style == 2: + self.nout = len(self.output_times) + fid.write("%i %s # nout\n" % (self.nout, 11*" ")) + + # remove [] and , from list of times: + output_times_str = repr(list(self.output_times))[1:-1] + output_times_str = output_times_str.replace(',','') + fid.write("%s # output_times\n" % output_times_str) + else: + raise ValueError('fgout output_style must be 1 or 2') fid.write("%i %s # point_style\n" \ % (self.point_style,12*" ")) fid.write("%i %s # output_format\n" \ From 564cbbc69f0214bc9e39e6f1e56fa658d5a01c19 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Wed, 5 Jun 2024 14:31:21 -0800 Subject: [PATCH 35/58] remove trailing whitespaces in fgout_module.f990 --- src/2d/shallow/fgout_module.f90 | 258 ++++++++++++++++---------------- 1 file changed, 129 insertions(+), 129 deletions(-) diff --git a/src/2d/shallow/fgout_module.f90 b/src/2d/shallow/fgout_module.f90 index 8f422730e..69cce221b 100644 --- a/src/2d/shallow/fgout_module.f90 +++ b/src/2d/shallow/fgout_module.f90 @@ -8,18 +8,18 @@ module fgout_module ! Grid data real(kind=8), pointer :: early(:,:,:) real(kind=8), pointer :: late(:,:,:) - + ! Geometry integer :: num_vars(2),mx,my,point_style,fgno,output_format,output_style real(kind=8) :: dx,dy,x_low,x_hi,y_low,y_hi - + ! Time Tracking and output types integer :: num_output,next_output_index real(kind=8) :: start_time,end_time,dt integer, allocatable :: output_frames(:) real(kind=8), allocatable :: output_times(:) - end type fgout_grid + end type fgout_grid logical, private :: module_setup = .false. @@ -32,27 +32,27 @@ module fgout_module contains - - + + ! Setup routine that reads in the fixed grids data file and sets up the ! appropriate data structures - + subroutine set_fgout(rest,fname) use amr_module, only: parmunit, tstart_thisrun implicit none - + ! Subroutine arguments logical :: rest ! restart? character(len=*), optional, intent(in) :: fname - + ! Local storage integer, parameter :: unit = 7 integer :: i,k type(fgout_grid), pointer :: fg real(kind=8) :: ts - + if (.not.module_setup) then @@ -75,7 +75,7 @@ subroutine set_fgout(rest,fname) write(parmunit,*) ' No fixed grids specified for output' return endif - + ! Allocate fixed grids (not the data yet though) allocate(FGOUT_fgrids(FGOUT_num_grids)) @@ -97,28 +97,28 @@ subroutine set_fgout(rest,fname) fg%start_time = fg%output_times(1) fg%end_time = fg%output_times(fg%num_output) endif - + read(unit,*) fg%point_style read(unit,*) fg%output_format read(unit,*) fg%mx, fg%my read(unit,*) fg%x_low, fg%y_low read(unit,*) fg%x_hi, fg%y_hi - - + + ! Initialize next_output_index ! (might be reset below in case of a restart) fg%next_output_index = 1 - + if (fg%point_style .ne. 2) then print *, 'set_fgout: ERROR, unrecognized point_style = ',\ fg%point_style endif - - if (fg%output_style == 1) then + + if (fg%output_style == 1) then ! Setup data for this grid ! Set fg%dt (the timestep length between outputs) if (fg%end_time <= fg%start_time) then - if (fg%num_output > 1) then + if (fg%num_output > 1) then print *,'set_fgout: ERROR for fixed grid', i print *,'start_time <= end_time yet num_output > 1' print *,'set end_time > start_time or set num_output = 1' @@ -142,7 +142,7 @@ subroutine set_fgout(rest,fname) endif endif endif - + do k=1,fg%num_output if (rest) then ! don't write initial time or earlier @@ -151,7 +151,7 @@ subroutine set_fgout(rest,fname) ! do write initial time ts = tstart_thisrun-FGOUT_ttol endif - + if (fg%output_times(k) < ts) then ! will not output this time in this run ! (might have already be done when restarting) @@ -184,30 +184,30 @@ subroutine set_fgout(rest,fname) else print *,'set_fgout: ERROR for fixed grid', i print *,'y grid points my <= 0, set my >= 1' - endif - + endif + ! set the number of variables stored for each grid ! this should be (the number of variables you want to write out + 1) fg%num_vars(1) = 6 - + ! Allocate new fixed grid data array allocate(fg%early(fg%num_vars(1), fg%mx,fg%my)) fg%early = nan() - + allocate(fg%late(fg%num_vars(1), fg%mx,fg%my)) fg%late = nan() - + enddo close(unit) - + FGOUT_tcfmax=-1.d16 module_setup = .true. end if end subroutine set_fgout - - + + !=====================FGOUT_INTERP======================================= ! This routine interpolates q and aux on a computational grid ! to an fgout grid not necessarily aligned with the computational grid @@ -216,10 +216,10 @@ end subroutine set_fgout subroutine fgout_interp(fgrid_type,fgrid, & t,q,meqn,mxc,myc,mbc,dxc,dyc,xlowc,ylowc, & maux,aux) - - use geoclaw_module, only: dry_tolerance + + use geoclaw_module, only: dry_tolerance implicit none - + ! Subroutine arguments integer, intent(in) :: fgrid_type type(fgout_grid), intent(inout) :: fgrid @@ -227,9 +227,9 @@ subroutine fgout_interp(fgrid_type,fgrid, & real(kind=8), intent(in) :: t,dxc,dyc,xlowc,ylowc real(kind=8), intent(in) :: q(meqn,1-mbc:mxc+mbc,1-mbc:myc+mbc) real(kind=8), intent(in) :: aux(maux,1-mbc:mxc+mbc,1-mbc:myc+mbc) - + integer, parameter :: method = 0 ! interpolate in space? - + ! Indices integer :: ifg,jfg,m,ic1,ic2,jc1,jc2 integer :: bathy_index,eta_index @@ -240,18 +240,18 @@ subroutine fgout_interp(fgrid_type,fgrid, & ! Geometry real(kind=8) :: xfg,yfg,xc1,xc2,yc1,yc2,xhic,yhic real(kind=8) :: geometry(4) - + real(kind=8) :: points(2,2), eta_tmp - + ! Work arrays for eta interpolation real(kind=8) :: eta(2,2),h(2,2) - - + + ! Alias to data in fixed grid integer :: num_vars real(kind=8), pointer :: fg_data(:,:,:) - - + + ! Setup aliases for specific fixed grid if (fgrid_type == 1) then num_vars = fgrid%num_vars(1) @@ -264,103 +264,103 @@ subroutine fgout_interp(fgrid_type,fgrid, & stop ! fgrid_type==3 is deprecated, use fgmax grids instead endif - - xhic = xlowc + dxc*mxc - yhic = ylowc + dyc*myc - + + xhic = xlowc + dxc*mxc + yhic = ylowc + dyc*myc + ! Find indices of various quantities in the fgrid arrays - bathy_index = 4 ! works for both shallow and bouss + bathy_index = 4 ! works for both shallow and bouss eta_index = 5 ! works for both shallow and bouss - + !write(59,*) '+++ ifg,jfg,eta,geometry at t = ',t - - ! Primary interpolation loops + + ! Primary interpolation loops do ifg=1,fgrid%mx xfg=fgrid%x_low + (ifg-0.5d0)*fgrid%dx ! cell centers do jfg=1,fgrid%my yfg=fgrid%y_low + (jfg-0.5d0)*fgrid%dy ! cell centers - + ! Check to see if this coordinate is inside of this grid if (.not.((xfg < xlowc.or.xfg > xhic) & .or.(yfg < ylowc.or.yfg > yhic))) then - - ! find where xfg,yfg is in the computational grid and + + ! find where xfg,yfg is in the computational grid and ! compute the indices ! (Note: may be subject to rounding error if fgout point ! is right on a cell edge!) ic1 = int((xfg - xlowc + dxc)/dxc) jc1 = int((yfg - ylowc + dyc)/dyc) - + if (method == 0) then - + ! piecewise constant: take values from cell (ic1,jc1): - + forall (m=1:meqn) fg_data(m,ifg,jfg) = q(m,ic1,jc1) end forall - + fg_data(bathy_index,ifg,jfg) = aux(1,ic1,jc1) - + ! for pw constant we take B, h, eta from same cell, ! so setting eta = h+B should be fine even near shore: fg_data(eta_index,ifg,jfg) = fg_data(1,ifg,jfg) & + fg_data(bathy_index,ifg,jfg) - - + + else if (method == 1) then - + ! bilinear used to interpolate to xfg,yfg ! (not recommended) - + ! define constant parts of bilinear if (ic1.eq.mxc) ic1=mxc-1 - if (jc1.eq.myc) jc1=myc-1 + if (jc1.eq.myc) jc1=myc-1 ic2 = ic1 + 1 jc2 = jc1 + 1 - + xc1 = xlowc + dxc * (ic1 - 0.5d0) yc1 = ylowc + dyc * (jc1 - 0.5d0) xc2 = xlowc + dxc * (ic2 - 0.5d0) yc2 = ylowc + dyc * (jc2 - 0.5d0) - + geometry = [(xfg - xc1) / dxc, & (yfg - yc1) / dyc, & (xfg - xc1) * (yfg - yc1) / (dxc*dyc), & 1.d0] - - + + ! Interpolate all conserved quantities and bathymetry forall (m=1:meqn) fg_data(m,ifg,jfg) = & - interpolate(q(m,ic1:ic2,jc1:jc2), geometry) + interpolate(q(m,ic1:ic2,jc1:jc2), geometry) end forall - fg_data(bathy_index,ifg,jfg) = & + fg_data(bathy_index,ifg,jfg) = & interpolate(aux(1,ic1:ic2,jc1:jc2),geometry) - + ! surface eta = h + B: - + ! Note that for pw bilinear interp there may ! be a problem interpolating each separately since ! interpolated h + interpolated B may be much larger ! than eta should be offshore. eta = q(1,ic1:ic2,jc1:jc2) + aux(1,ic1:ic2,jc1:jc2) fg_data(eta_index,ifg,jfg) = interpolate(eta,geometry) - ! NEED TO FIX + ! NEED TO FIX endif - - + + ! save the time this fgout point was computed: fg_data(num_vars,ifg,jfg) = t - - + + endif ! if fgout point is on this grid enddo ! fgout y-coordinate loop enddo ! fgout x-coordinte loop - + end subroutine fgout_interp - + !================ fgout_write ========================================== ! This routine interpolates in time and then outputs a grid at @@ -372,12 +372,12 @@ subroutine fgout_write(fgrid,out_time,out_index) use geoclaw_module, only: dry_tolerance implicit none - + ! Subroutine arguments type(fgout_grid), intent(inout) :: fgrid real(kind=8), intent(in) :: out_time integer, intent(in) :: out_index - + ! I/O integer, parameter :: unit = 87 character(len=15) :: fg_filename @@ -385,14 +385,14 @@ subroutine fgout_write(fgrid,out_time,out_index) character(len=8) :: file_format integer :: grid_number,ipos,idigit,out_number,columns integer :: ifg,ifg1, iframe,iframe1 - + integer, parameter :: method = 0 ! interpolate in time? - - ! Output format strings + + ! Output format strings ! These are now the same as in outval for frame data, for compatibility ! For fgout grids there is only a single grid (ngrids=1) ! and we set AMR_level=0, naux=0, nghost=0 (so no extra cells in binary) - + character(len=*), parameter :: header_format = & "(i6,' grid_number',/," // & "i6,' AMR_level',/," // & @@ -402,7 +402,7 @@ subroutine fgout_write(fgrid,out_time,out_index) "e26.16,' ylow', /," // & "e26.16,' dx', /," // & "e26.16,' dy',/)" - + character(len=*), parameter :: t_file_format = "(e18.8,' time', /," // & "i6,' meqn'/," // & "i6,' ngrids'/," // & @@ -410,64 +410,64 @@ subroutine fgout_write(fgrid,out_time,out_index) "i6,' ndim'/," // & "i6,' nghost'/," // & "a10,' format'/,/)" - + ! Other locals integer :: i,j,m real(kind=8) :: t0,tf,tau, qaug(6) real(kind=8), allocatable :: qeta(:,:,:) real(kind=4), allocatable :: qeta4(:,:,:) real(kind=8) :: h_early,h_late,topo_early,topo_late - + allocate(qeta(4, fgrid%mx, fgrid%my)) ! to store h,hu,hv,eta - - - ! Interpolate the grid in time, to the output time, using - ! the solution in fgrid1 and fgrid2, which represent the + + + ! Interpolate the grid in time, to the output time, using + ! the solution in fgrid1 and fgrid2, which represent the ! solution on the fixed grid at the two nearest computational times do j=1,fgrid%my do i=1,fgrid%mx - + ! Check for small numbers forall(m=1:fgrid%num_vars(1)-1,abs(fgrid%early(m,i,j)) < 1d-90) fgrid%early(m,i,j) = 0.d0 end forall if (method == 0) then - + ! no interpolation in time, use solution from full step: qaug = fgrid%early(:,i,j) - + ! note that CFL condition ==> waves can't move more than 1 ! cell per time step on each level, so solution from nearest ! full step should be correct to within a cell width ! Better to use early than late since for refinement tracking ! wave moving out into still water. - + else if (method == 1) then - + ! interpolate in time. May have problems near shore? - - ! Fetch times for interpolation, this is done per grid point + + ! Fetch times for interpolation, this is done per grid point ! since each grid point may come from a different source t0 = fgrid%early(fgrid%num_vars(1),i,j) tf = fgrid%late(fgrid%num_vars(1),i,j) tau = (out_time - t0) / (tf - t0) - + ! check for small values: forall(m=1:fgrid%num_vars(1)-1,abs(fgrid%late(m,i,j)) < 1d-90) fgrid%late(m,i,j) = 0.d0 end forall - + ! linear interpolation: qaug = (1.d0-tau)*fgrid%early(:,i,j) + tau*fgrid%late(:,i,j) - + ! If resolution changed between early and late time, may be ! problems near shore when interpolating B, h, eta ! separately (at least in case when B changed and point ! was dry at one time and wet the other). ! Switch back to fgrid%early values, only in this case. ! This is implemented below but not extensively tested. - + if (qaug(1) > 0.d0) then topo_early = fgrid%early(4,i,j) topo_late = fgrid%late(4,i,j) @@ -485,13 +485,13 @@ subroutine fgout_write(fgrid,out_time,out_index) endif endif endif - + ! Output the conserved quantities and eta value qeta(1,i,j) = qaug(1) ! h qeta(2,i,j) = qaug(2) ! hu qeta(3,i,j) = qaug(3) ! hv qeta(4,i,j) = qaug(5) ! eta - + enddo enddo @@ -514,8 +514,8 @@ subroutine fgout_write(fgrid,out_time,out_index) cframeno(ipos:ipos) = char(ichar('0') + idigit) iframe1 = iframe1/10 enddo - - fg_filename = 'fgout' // cfgno // '.q' // cframeno + + fg_filename = 'fgout' // cfgno // '.q' // cframeno open(unit,file=fg_filename,status='unknown',form='formatted') @@ -524,17 +524,17 @@ subroutine fgout_write(fgrid,out_time,out_index) if (fgrid%num_vars(2) > 1) then columns = columns + 2 endif - + !write(6,*) '+++ fgout out_time = ',out_time !write(6,*) '+++ fgrid%num_vars: ',fgrid%num_vars(1),fgrid%num_vars(2) - + ! Write out header in .q file: !write(unit,header_format) out_time,fgrid%mx,fgrid%my, & ! fgrid%x_low,fgrid%y_low, fgrid%x_hi,fgrid%y_hi, columns write(unit,header_format) fgrid%fgno, 0, fgrid%mx,fgrid%my, & fgrid%x_low,fgrid%y_low, fgrid%dx, fgrid%dy - + if (fgrid%output_format == 1) then ! ascii output added to .q file: do j=1,fgrid%my @@ -544,20 +544,20 @@ subroutine fgout_write(fgrid,out_time,out_index) enddo write(unit,*) ' ' ! blank line required between rows enddo - endif - + endif + close(unit) - + if (fgrid%output_format == 3) then ! binary output goes in .b file as full 8-byte (float64): - fg_filename = 'fgout' // cfgno // '.b' // cframeno + fg_filename = 'fgout' // cfgno // '.b' // cframeno open(unit=unit, file=fg_filename, status="unknown", & access='stream') write(unit) qeta close(unit) else if (fgrid%output_format == 2) then ! binary output goes in .b file as 4-byte (float32): - fg_filename = 'fgout' // cfgno // '.b' // cframeno + fg_filename = 'fgout' // cfgno // '.b' // cframeno open(unit=unit, file=fg_filename, status="unknown", & access='stream') allocate(qeta4(4, fgrid%mx, fgrid%my)) ! for 4-byte binary output @@ -566,44 +566,44 @@ subroutine fgout_write(fgrid,out_time,out_index) deallocate(qeta4) close(unit) endif - + deallocate(qeta) ! time info .t file: - + if (fgrid%output_format == 1) then file_format = 'ascii' else if (fgrid%output_format == 2) then file_format = 'binary32' else if (fgrid%output_format == 3) then file_format = 'binary64' - else + else write(6,*) '*** unrecognized fgrid%output_format = ', & fgrid%output_format write(6,*) '*** should be ascii, binary32, or binary64' endif - - fg_filename = 'fgout' // cfgno // '.t' // cframeno + + fg_filename = 'fgout' // cfgno // '.t' // cframeno open(unit=unit, file=fg_filename, status='unknown', form='formatted') ! time, num_eqn+1, num_grids, num_aux, num_dim, num_ghost: write(unit, t_file_format) out_time, 4, 1, 0, 2, 0, file_format close(unit) - + print "(a,i4,a,i4,a,e18.8)",'Writing fgout grid #',fgrid%fgno, & ' frame ',out_index,' at time =',out_time - + ! Index into qeta for binary output ! Note that this implicitly assumes that we are outputting only h, hu, hv ! and will not output more (change num_eqn parameter above) - + end subroutine fgout_write - - + + ! ========================================================================= ! Utility functions for this module ! Returns back a NaN - + real(kind=8) function nan() real(kind=8) dnan integer inan(2) @@ -612,21 +612,21 @@ real(kind=8) function nan() inan(2)=2147483647 nan=dnan end function nan - + ! Interpolation function (in space) ! Given 4 points (points) and geometry from x,y,and cross terms - + real(kind=8) pure function interpolate(points,geometry) result(interpolant) - + implicit none - + ! Function signature real(kind=8), intent(in) :: points(2,2) real(kind=8), intent(in) :: geometry(4) integer :: icell, jcell - + ! pw bilinear - ! This is set up as a dot product between the approrpriate terms in + ! This is set up as a dot product between the approrpriate terms in ! the input data. This routine could be vectorized or a BLAS routine ! used instead of the intrinsics to ensure that the fastest routine ! possible is being used @@ -634,8 +634,8 @@ real(kind=8) pure function interpolate(points,geometry) result(interpolant) points(1,2)-points(1,1), & points(1,1) + points(2,2) - (points(2,1) + points(1,2)), & points(1,1)] * geometry) - + end function interpolate - + end module fgout_module From bcc4288b9a740cbe8e5c7d92a4eac447cb2f0fcd Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Wed, 5 Jun 2024 18:41:00 -0800 Subject: [PATCH 36/58] Refactor fgout_module.f90 so it works for either GeoClaw or D-Claw and support in fgout_tools.py for new dclaw attribute dclaw to set in setrun.py to indicate D-Claw, in which case 7 components of q are output instead of 4. Support for eventually indicating fewer components to output. --- src/2d/shallow/fgout_module.f90 | 181 ++++++++++++++++++++++-------- src/python/geoclaw/fgout_tools.py | 2 + 2 files changed, 136 insertions(+), 47 deletions(-) diff --git a/src/2d/shallow/fgout_module.f90 b/src/2d/shallow/fgout_module.f90 index 69cce221b..981d11ac3 100644 --- a/src/2d/shallow/fgout_module.f90 +++ b/src/2d/shallow/fgout_module.f90 @@ -10,7 +10,7 @@ module fgout_module real(kind=8), pointer :: late(:,:,:) ! Geometry - integer :: num_vars(2),mx,my,point_style,fgno,output_format,output_style + integer :: num_vars,mx,my,point_style,fgno,output_format,output_style real(kind=8) :: dx,dy,x_low,x_hi,y_low,y_hi ! Time Tracking and output types @@ -19,6 +19,12 @@ module fgout_module integer, allocatable :: output_frames(:) real(kind=8), allocatable :: output_times(:) + + integer :: nqout ! number of q components to output (+1 for eta) + logical, allocatable :: iqout(:) ! which components to output + integer :: bathy_index,eta_index + logical :: dclaw ! False for GeoClaw + end type fgout_grid @@ -103,6 +109,7 @@ subroutine set_fgout(rest,fname) read(unit,*) fg%mx, fg%my read(unit,*) fg%x_low, fg%y_low read(unit,*) fg%x_hi, fg%y_hi + read(unit,*) fg%dclaw ! Initialize next_output_index @@ -186,15 +193,72 @@ subroutine set_fgout(rest,fname) print *,'y grid points my <= 0, set my >= 1' endif - ! set the number of variables stored for each grid - ! this should be (the number of variables you want to write out + 1) - fg%num_vars(1) = 6 + + ! For now, hard-wire with defaults for either GeoClaw or D-Claw + ! need to save q plus topo, eta, t for interp in space-time + + if (fg%dclaw) then + ! For D-Claw: + fg%num_vars = 10 + ! for h, hu, hv, hm, pb, hchi, b_eroded, bathy, eta, time + else + ! GeoClaw: + fg%num_vars = 6 + ! for h, hu, hv, bathy, eta, time + endif + + ! specify which components of q (plus eta?) to output: + ! (eventually this should be set from user input) + + if (fg%num_vars == 6) then + ! GeoClaw + ! indexes used in early and late arrays: + ! 1:3 are q variables, 6 is time + fg%bathy_index = 4 + fg%eta_index = 5 + + allocate(fg%iqout(4)) + fg%iqout(1) = .true. ! output h? + fg%iqout(2) = .true. ! output hu? + fg%iqout(3) = .true. ! output hv? + fg%iqout(4) = .true. ! output eta? + fg%nqout = 0 + do k=1,4 + if (fg%iqout(k)) fg%nqout = fg%nqout + 1 + enddo + endif + + if (fg%num_vars == 10) then + ! D-Claw: + ! indexes used in early and late arrays: + ! 1:7 are q variables, 10 is time + fg%bathy_index = 8 + fg%eta_index = 9 + + allocate(fg%iqout(8)) + fg%iqout(1) = .true. ! output h? + fg%iqout(2) = .true. ! output hu? + fg%iqout(3) = .true. ! output hv? + fg%iqout(4) = .true. ! output hm? + fg%iqout(5) = .true. ! output pb? + fg%iqout(6) = .true. ! output hchi? + fg%iqout(7) = .true. ! output beroded? + fg%iqout(8) = .true. ! output eta? + fg%nqout = 0 + do k=1,8 + if (fg%iqout(k)) fg%nqout = fg%nqout + 1 + enddo + endif + + write(6,*) '+++ nqout = ',fg%nqout - ! Allocate new fixed grid data array - allocate(fg%early(fg%num_vars(1), fg%mx,fg%my)) + ! Allocate new fixed grid data arrays at early, late time: + ! dimension (10,:,:) to work for either GeoClaw or D-Claw + + allocate(fg%early(10, fg%mx,fg%my)) fg%early = nan() - allocate(fg%late(fg%num_vars(1), fg%mx,fg%my)) + allocate(fg%late(10, fg%mx,fg%my)) fg%late = nan() enddo @@ -232,7 +296,6 @@ subroutine fgout_interp(fgrid_type,fgrid, & ! Indices integer :: ifg,jfg,m,ic1,ic2,jc1,jc2 - integer :: bathy_index,eta_index ! Tolerances real(kind=8) :: total_depth,depth_indicator,nan_check @@ -254,10 +317,10 @@ subroutine fgout_interp(fgrid_type,fgrid, & ! Setup aliases for specific fixed grid if (fgrid_type == 1) then - num_vars = fgrid%num_vars(1) + num_vars = fgrid%num_vars fg_data => fgrid%early else if (fgrid_type == 2) then - num_vars = fgrid%num_vars(1) + num_vars = fgrid%num_vars fg_data => fgrid%late else write(6,*) '*** Unexpected fgrid_type = ', fgrid_type @@ -268,9 +331,6 @@ subroutine fgout_interp(fgrid_type,fgrid, & xhic = xlowc + dxc*mxc yhic = ylowc + dyc*myc - ! Find indices of various quantities in the fgrid arrays - bathy_index = 4 ! works for both shallow and bouss - eta_index = 5 ! works for both shallow and bouss !write(59,*) '+++ ifg,jfg,eta,geometry at t = ',t @@ -299,12 +359,12 @@ subroutine fgout_interp(fgrid_type,fgrid, & fg_data(m,ifg,jfg) = q(m,ic1,jc1) end forall - fg_data(bathy_index,ifg,jfg) = aux(1,ic1,jc1) + fg_data(fgrid%bathy_index,ifg,jfg) = aux(1,ic1,jc1) ! for pw constant we take B, h, eta from same cell, ! so setting eta = h+B should be fine even near shore: - fg_data(eta_index,ifg,jfg) = fg_data(1,ifg,jfg) & - + fg_data(bathy_index,ifg,jfg) + fg_data(fgrid%eta_index,ifg,jfg) = fg_data(1,ifg,jfg) & + + fg_data(fgrid%bathy_index,ifg,jfg) else if (method == 1) then @@ -335,7 +395,7 @@ subroutine fgout_interp(fgrid_type,fgrid, & interpolate(q(m,ic1:ic2,jc1:jc2), geometry) end forall - fg_data(bathy_index,ifg,jfg) = & + fg_data(fgrid%bathy_index,ifg,jfg) = & interpolate(aux(1,ic1:ic2,jc1:jc2),geometry) @@ -346,7 +406,7 @@ subroutine fgout_interp(fgrid_type,fgrid, & ! interpolated h + interpolated B may be much larger ! than eta should be offshore. eta = q(1,ic1:ic2,jc1:jc2) + aux(1,ic1:ic2,jc1:jc2) - fg_data(eta_index,ifg,jfg) = interpolate(eta,geometry) + fg_data(fgrid%eta_index,ifg,jfg) = interpolate(eta,geometry) ! NEED TO FIX endif @@ -412,14 +472,14 @@ subroutine fgout_write(fgrid,out_time,out_index) "a10,' format'/,/)" ! Other locals - integer :: i,j,m - real(kind=8) :: t0,tf,tau, qaug(6) + integer :: i,j,m,iq,k + real(kind=8) :: t0,tf,tau, qaug(10) real(kind=8), allocatable :: qeta(:,:,:) real(kind=4), allocatable :: qeta4(:,:,:) real(kind=8) :: h_early,h_late,topo_early,topo_late - allocate(qeta(4, fgrid%mx, fgrid%my)) ! to store h,hu,hv,eta - + allocate(qeta(fgrid%nqout, fgrid%mx, fgrid%my)) ! to store h,hu,hv,eta + ! or subset ! Interpolate the grid in time, to the output time, using ! the solution in fgrid1 and fgrid2, which represent the @@ -428,7 +488,7 @@ subroutine fgout_write(fgrid,out_time,out_index) do i=1,fgrid%mx ! Check for small numbers - forall(m=1:fgrid%num_vars(1)-1,abs(fgrid%early(m,i,j)) < 1d-90) + forall(m=1:fgrid%num_vars-1,abs(fgrid%early(m,i,j)) < 1d-90) fgrid%early(m,i,j) = 0.d0 end forall @@ -449,12 +509,12 @@ subroutine fgout_write(fgrid,out_time,out_index) ! Fetch times for interpolation, this is done per grid point ! since each grid point may come from a different source - t0 = fgrid%early(fgrid%num_vars(1),i,j) - tf = fgrid%late(fgrid%num_vars(1),i,j) + t0 = fgrid%early(fgrid%num_vars,i,j) + tf = fgrid%late(fgrid%num_vars,i,j) tau = (out_time - t0) / (tf - t0) ! check for small values: - forall(m=1:fgrid%num_vars(1)-1,abs(fgrid%late(m,i,j)) < 1d-90) + forall(m=1:fgrid%num_vars-1,abs(fgrid%late(m,i,j)) < 1d-90) fgrid%late(m,i,j) = 0.d0 end forall @@ -487,11 +547,51 @@ subroutine fgout_write(fgrid,out_time,out_index) endif ! Output the conserved quantities and eta value - qeta(1,i,j) = qaug(1) ! h - qeta(2,i,j) = qaug(2) ! hu - qeta(3,i,j) = qaug(3) ! hv - qeta(4,i,j) = qaug(5) ! eta - + iq = 1 + ! qaug(1:3) are h,hu,hv for both GeoClaw and D-Claw: + if (fgrid%iqout(1)) then + qeta(iq,i,j) = qaug(1) ! h + iq = iq+1 + endif + if (fgrid%iqout(2)) then + qeta(iq,i,j) = qaug(2) ! hu + iq = iq+1 + endif + if (fgrid%iqout(3)) then + qeta(iq,i,j) = qaug(3) ! hv + iq = iq+1 + endif + + if (fgrid%num_vars == 6) then + ! GeoClaw: + if (fgrid%iqout(4)) then + qeta(iq,i,j) = qaug(5) ! eta since qaug(4)=topo + iq = iq+1 + endif + + else if (fgrid%num_vars == 10) then + ! D-Claw: + if (fgrid%iqout(4)) then + qeta(iq,i,j) = qaug(4) ! hm + iq = iq+1 + endif + if (fgrid%iqout(5)) then + qeta(iq,i,j) = qaug(5) ! pb + iq = iq+1 + endif + if (fgrid%iqout(6)) then + qeta(iq,i,j) = qaug(6) ! hchi + iq = iq+1 + endif + if (fgrid%iqout(7)) then + qeta(iq,i,j) = qaug(7) ! b_eroded + iq = iq+1 + endif + if (fgrid%iqout(8)) then + qeta(iq,i,j) = qaug(9) ! eta since qaug(8)=topo + iq = iq+1 + endif + endif enddo enddo @@ -519,18 +619,6 @@ subroutine fgout_write(fgrid,out_time,out_index) open(unit,file=fg_filename,status='unknown',form='formatted') - ! Determine number of columns that will be written out - columns = fgrid%num_vars(1) - 1 - if (fgrid%num_vars(2) > 1) then - columns = columns + 2 - endif - - !write(6,*) '+++ fgout out_time = ',out_time - !write(6,*) '+++ fgrid%num_vars: ',fgrid%num_vars(1),fgrid%num_vars(2) - - ! Write out header in .q file: - !write(unit,header_format) out_time,fgrid%mx,fgrid%my, & - ! fgrid%x_low,fgrid%y_low, fgrid%x_hi,fgrid%y_hi, columns write(unit,header_format) fgrid%fgno, 0, fgrid%mx,fgrid%my, & fgrid%x_low,fgrid%y_low, fgrid%dx, fgrid%dy @@ -539,8 +627,7 @@ subroutine fgout_write(fgrid,out_time,out_index) ! ascii output added to .q file: do j=1,fgrid%my do i=1,fgrid%mx - write(unit, "(50e26.16)") qeta(1,i,j),qeta(2,i,j), & - qeta(3,i,j),qeta(4,i,j) + write(unit, "(50e26.16)") (qeta(k,i,j), k=1,fgrid%nqout) enddo write(unit,*) ' ' ! blank line required between rows enddo @@ -560,7 +647,7 @@ subroutine fgout_write(fgrid,out_time,out_index) fg_filename = 'fgout' // cfgno // '.b' // cframeno open(unit=unit, file=fg_filename, status="unknown", & access='stream') - allocate(qeta4(4, fgrid%mx, fgrid%my)) ! for 4-byte binary output + allocate(qeta4(fgrid%nqout, fgrid%mx, fgrid%my)) qeta4 = real(qeta, kind=4) write(unit) qeta4 deallocate(qeta4) @@ -587,7 +674,7 @@ subroutine fgout_write(fgrid,out_time,out_index) fg_filename = 'fgout' // cfgno // '.t' // cframeno open(unit=unit, file=fg_filename, status='unknown', form='formatted') ! time, num_eqn+1, num_grids, num_aux, num_dim, num_ghost: - write(unit, t_file_format) out_time, 4, 1, 0, 2, 0, file_format + write(unit, t_file_format) out_time, fgrid%nqout, 1, 0, 2, 0,file_format close(unit) print "(a,i4,a,i4,a,e18.8)",'Writing fgout grid #',fgrid%fgno, & diff --git a/src/python/geoclaw/fgout_tools.py b/src/python/geoclaw/fgout_tools.py index b140c98e2..87ae51aa3 100644 --- a/src/python/geoclaw/fgout_tools.py +++ b/src/python/geoclaw/fgout_tools.py @@ -185,6 +185,7 @@ def __init__(self,fgno=None,outdir=None,output_format=None): self.fgno = fgno self.outdir = outdir self.output_format = output_format + self.dclaw = False self.drytol = 1e-3 # used for computing u,v from hu,hv @@ -430,6 +431,7 @@ def write_to_fgout_data(self, fid): print(" upper right = (%15.10f,%15.10f)" % (x2,y2)) print(" dx = %15.10e, dy = %15.10e" % (dx,dy)) + fid.write("%s # dclaw" % self.dclaw) def read_frame(self, frameno): From 3d3db467a2d71b3b1a255470572a080b6c90320c Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Thu, 6 Jun 2024 10:45:56 -0400 Subject: [PATCH 37/58] Use geoclaw module rho --- src/2d/shallow/src2.f90 | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/2d/shallow/src2.f90 b/src/2d/shallow/src2.f90 index dc603c36e..376daf7e0 100644 --- a/src/2d/shallow/src2.f90 +++ b/src/2d/shallow/src2.f90 @@ -6,7 +6,7 @@ subroutine src2(meqn,mbc,mx,my,xlower,ylower,dx,dy,q,maux,aux,t,dt) use geoclaw_module, only: manning_break, num_manning use geoclaw_module, only: spherical_distance, coordinate_system use geoclaw_module, only: RAD2DEG, pi, dry_tolerance, DEG2RAD - use geoclaw_module, only: rho_air + use geoclaw_module, only: rho_air, rho use geoclaw_module, only: earth_radius, sphere_source use storm_module, only: wind_forcing, pressure_forcing, wind_drag @@ -40,10 +40,6 @@ subroutine src2(meqn,mbc,mx,my,xlower,ylower,dx,dy,q,maux,aux,t,dt) ! friction source term real(kind=8), parameter :: depth_tolerance = 1.0d-30 - ! Physics - ! Nominal density of water - real(kind=8), parameter :: rho = 1025.d0 - ! ---------------------------------------------------------------- ! Spherical geometry source term(s) ! @@ -154,7 +150,7 @@ subroutine src2(meqn,mbc,mx,my,xlower,ylower,dx,dy,q,maux,aux,t,dt) endif wind_speed = sqrt(aux(wind_index,i,j)**2 & + aux(wind_index+1,i,j)**2) - tau = wind_drag(wind_speed, phi) * rho_air * wind_speed / rho + tau = wind_drag(wind_speed, phi) * rho_air * wind_speed / rho(1) q(2,i,j) = q(2,i,j) + dt * tau * aux(wind_index,i,j) q(3,i,j) = q(3,i,j) + dt * tau * aux(wind_index+1,i,j) endif @@ -195,8 +191,8 @@ subroutine src2(meqn,mbc,mx,my,xlower,ylower,dx,dy,q,maux,aux,t,dt) ! ! Modify momentum in each layer ! if (h > dry_tolerance) then - ! q(2, i, j) = q(2, i, j) - dt * h * P_gradient(1) / rho - ! q(3, i, j) = q(3, i, j) - dt * h * P_gradient(2) / rho + ! q(2, i, j) = q(2, i, j) - dt * h * P_gradient(1) / rho(1) + ! q(3, i, j) = q(3, i, j) - dt * h * P_gradient(2) / rho(1) ! end if ! enddo ! enddo From f2a1c93e198bf6a745dba8b5d8887c07b8ce5789 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Thu, 6 Jun 2024 11:05:04 -0400 Subject: [PATCH 38/58] Remove module level parameters that were not needed or were specific These mostly pertained to the CLE code. --- src/2d/shallow/surge/model_storm_module.f90 | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/2d/shallow/surge/model_storm_module.f90 b/src/2d/shallow/surge/model_storm_module.f90 index cce7445a9..3ad784a53 100644 --- a/src/2d/shallow/surge/model_storm_module.f90 +++ b/src/2d/shallow/surge/model_storm_module.f90 @@ -68,12 +68,6 @@ module model_storm_module ! track to be close to but not equal the start time of the simulation real(kind=8), parameter :: TRACKING_TOLERANCE = 1d-10 - ! Global constants #TODO: Some of these are in geoclaw already - real(kind=8) :: pi=3.1415927 - real(kind=8) :: omega=7.2921e-5 - real(kind=8) :: chi=1.0 - real(kind=8) :: alpha=1.0 - contains @@ -974,6 +968,7 @@ real(kind=8) function evaluate_inner_derivative(f,r_m,v_m,r_a,v_a) result(dMa) ! Variables real(kind=8) :: denominator, M_a + real(kind=8), parameter :: alpha=1.0 ! Calculate necessary components of the derivative to make ! calculations easier @@ -992,6 +987,7 @@ real(kind=8) function evaluate_v_a(f,r_m,v_m,r_a) result(v_a) ! Input real(kind=8), intent(in) :: f, r_m, v_m, r_a + real(kind=8), parameter :: alpha=1.0 v_a = ((2.0*(r_a/r_m)**2)/(2.0-alpha+alpha*(r_a/r_m)**2))**(1.0/(2.0-alpha)) v_a = v_a*(0.5*f*r_m**2 + r_m*v_m) @@ -1061,6 +1057,7 @@ subroutine integrate_m_out(f,r_0,r_a,res,m_out) ! Parameters and Other variables real(kind=8) :: V_m, dr, r_guess, r_p integer :: i + real(kind=8), parameter :: chi=1.0 ! Intialize m_out(res) = 0.5*f*r_0**2 @@ -1104,6 +1101,8 @@ subroutine solve_hurricane_wind_parameters(f, r_m, v_m, res, r_0, r_a) real(kind=8) :: dr, r real(kind=8) :: inner_res, outer_res real(kind=8), dimension(1:res) :: v_out, v_in + real(kind=8), parameter :: chi=1.0 + real(kind=8), parameter :: alpha=1.0 ! Initialize guesses for merge and r_0 r_a = 2.0*r_m From 12f4233b03cfa5f24e021411ec9b02d2c97ffb07 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Thu, 6 Jun 2024 14:33:48 -0400 Subject: [PATCH 39/58] Initial implementation of rotation control --- src/2d/shallow/surge/model_storm_module.f90 | 31 +++++++++++++++------ src/2d/shallow/surge/storm_module.f90 | 28 ++++++++++++++++++- src/python/geoclaw/data.py | 17 ++++++----- 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/src/2d/shallow/surge/model_storm_module.f90 b/src/2d/shallow/surge/model_storm_module.f90 index 3ad784a53..a9fdd2a69 100644 --- a/src/2d/shallow/surge/model_storm_module.f90 +++ b/src/2d/shallow/surge/model_storm_module.f90 @@ -51,6 +51,19 @@ module model_storm_module end type model_storm_type + ! How to deterimine which way a storm should be made to spin + ! The default defers simply to assuming y is a latitude + ! Here either an integer or bool can be returned but as implemented 1 + ! refers to the Northern Hemisphere and therefore causes the storm to spin + ! in a counter-clockwise direction. + abstract interface + logical pure function rotation_def(x, y) + implicit none + real(kind=8), intent(in) :: x, y + end function rotation_def + end interface + procedure(rotation_def), pointer :: rotation + ! Interal tracking variables for storm integer, private :: last_storm_index @@ -647,7 +660,7 @@ subroutine set_holland_1980_fields(maux, mbc, mx, my, xlower, ylower, & call post_process_wind_estimate(maux, mbc, mx, my, i, j, wind, aux, & wind_index, pressure_index, r, radius, tv, mod_mws, theta, & - convert_height, y >= 0) + convert_height, rotation(x, y)) enddo enddo @@ -719,7 +732,7 @@ subroutine set_holland_2008_fields(maux, mbc, mx, my, xlower, ylower, & call post_process_wind_estimate(maux, mbc, mx, my, i, j, wind, aux, & wind_index, pressure_index, r, radius, tv, mod_mws, theta, & - convert_height, y >= 0) + convert_height, rotation(x, y)) enddo enddo @@ -811,7 +824,7 @@ subroutine set_holland_2010_fields(maux, mbc, mx, my, xlower, ylower, & call post_process_wind_estimate(maux, mbc, mx, my, i, j, wind, aux, & wind_index, pressure_index, r, radius, tv, mod_mws, theta, & - convert_height, y >= 0) + convert_height, rotation(x, y)) enddo enddo @@ -1254,8 +1267,8 @@ subroutine set_SLOSH_fields(maux, mbc, mx, my, xlower, ylower, & trans_speed_x = tv(1) * mwr * r / (mwr**2.d0 + r**2.d0) trans_speed_y = tv(2) * mwr * r / (mwr**2.d0 + r**2.d0) - aux(wind_index,i,j) = wind * merge(-1, 1, y >= 0) * sin(theta) + trans_speed_x - aux(wind_index+1,i,j) = wind * merge(1, -1, y >= 0) * cos(theta) + trans_speed_y + aux(wind_index,i,j) = wind * merge(-1, 1, rotation(x, y)) * sin(theta) + trans_speed_x + aux(wind_index+1,i,j) = wind * merge(1, -1, rotation(x, y)) * cos(theta) + trans_speed_y enddo enddo @@ -1330,7 +1343,7 @@ subroutine set_rankine_fields(maux, mbc, mx, my, xlower, ylower, & call post_process_wind_estimate(maux, mbc, mx, my, i, j, wind, aux, & wind_index, pressure_index, r, radius, tv, mod_mws, theta, & - convert_height, y >= 0) + convert_height, rotation(x, y)) enddo enddo @@ -1418,7 +1431,7 @@ subroutine set_modified_rankine_fields(maux, mbc, mx, my, xlower, ylower, & call post_process_wind_estimate(maux, mbc, mx, my, i, j, wind, aux, & wind_index, pressure_index, r, radius, tv, mod_mws, theta, & - convert_height, y >= 0) + convert_height, rotation(x, y)) enddo enddo @@ -1491,7 +1504,7 @@ subroutine set_deMaria_fields(maux, mbc, mx, my, xlower, ylower, & call post_process_wind_estimate(maux, mbc, mx, my, i, j, wind, aux, & wind_index, pressure_index, r, radius, tv, mod_mws, theta, & - convert_height, y >= 0) + convert_height, rotation(x, y)) enddo enddo @@ -1583,7 +1596,7 @@ subroutine set_willoughby_fields(maux, mbc, mx, my, xlower, ylower, & call post_process_wind_estimate(maux, mbc, mx, my, i, j, wind, aux, & wind_index, pressure_index, r, radius, tv, mod_mws, theta, & - convert_height, y >= 0) + convert_height, rotation(x, y)) enddo enddo diff --git a/src/2d/shallow/surge/storm_module.f90 b/src/2d/shallow/surge/storm_module.f90 index 7af8b46ae..0fdcad6e3 100644 --- a/src/2d/shallow/surge/storm_module.f90 +++ b/src/2d/shallow/surge/storm_module.f90 @@ -11,7 +11,7 @@ module storm_module - use model_storm_module, only: model_storm_type + use model_storm_module, only: model_storm_type, rotation use data_storm_module, only: data_storm_type implicit none @@ -29,6 +29,7 @@ module storm_module ! Source term control and parameters logical :: wind_forcing, pressure_forcing + integer :: rotation_override ! Wind drag law support abstract interface @@ -153,6 +154,15 @@ subroutine set_storm(data_file) stop "*** ERROR *** Invalid wind drag law." end select read(unit,*) pressure_forcing + read(unit,*) rotation_override + select case(rotation_override) + case(0) + rotation => hemisphere_rotation + case(1:2) + rotation => user_rotation + case default + stop " *** ERROR *** Roation override invalid." + end select read(unit,*) ! Set some parameters @@ -480,4 +490,20 @@ subroutine output_storm_location(t) end subroutine output_storm_location + ! ========================================================================== + ! Default to assuming y is a latitude and if y >= 0 we are want to spin + ! counter-clockwise + ! ========================================================================== + logical pure function hemisphere_rotation(x, y) result(rotation) + implicit none + real(kind=8), intent(in) :: x, y + rotation = (y >= 0.d0) + end function hemisphere_rotation + ! This version just returns the user defined direction + logical pure function user_rotation(x, y) result(rotation) + implicit none + real(kind=8), intent(in) :: x, y + rotation = (rotation_override == 1) + end function user_rotation + end module storm_module diff --git a/src/python/geoclaw/data.py b/src/python/geoclaw/data.py index e3d514ffc..22ff2ef51 100755 --- a/src/python/geoclaw/data.py +++ b/src/python/geoclaw/data.py @@ -550,12 +550,13 @@ class SurgeData(clawpack.clawutil.data.ClawData): storm_spec_not_implemented = ['CLE'] def __init__(self): - super(SurgeData,self).__init__() + super(SurgeData, self).__init__() # Source term controls - self.add_attribute('wind_forcing',False) - self.add_attribute('drag_law',1) - self.add_attribute('pressure_forcing',False) + self.add_attribute('wind_forcing', False) + self.add_attribute('drag_law', 1) + self.add_attribute('pressure_forcing', False) + self.add_attribute('rotation', 0) # Algorithm parameters - Indexing is python based self.add_attribute("wind_index", 4) @@ -563,8 +564,8 @@ def __init__(self): self.add_attribute("display_landfall_time", False) # AMR parameters - self.add_attribute('wind_refine',[20.0,40.0,60.0]) - self.add_attribute('R_refine',[60.0e3,40e3,20e3]) + self.add_attribute('wind_refine', [20.0,40.0,60.0]) + self.add_attribute('R_refine', [60.0e3,40e3,20e3]) # Storm parameters self.add_attribute('storm_type', None) # Backwards compatibility @@ -572,7 +573,7 @@ def __init__(self): self.add_attribute("storm_file", None) # File(s) containing data - def write(self,out_file='surge.data',data_source="setrun.py"): + def write(self, out_file='surge.data', data_source="setrun.py"): """Write out the data file to the path given""" # print "Creating data file %s" % out_file @@ -582,6 +583,8 @@ def write(self,out_file='surge.data',data_source="setrun.py"): self.data_write('drag_law', description='(Type of drag law to use)') self.data_write('pressure_forcing', description="(Pressure source term used)") + self.data_write('rotation', + description="(type of rotation storms should have)") self.data_write() self.data_write("wind_index", value=self.wind_index + 1, From c122f6a8cb4b1ef7b74049964d340dc77926161c Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Thu, 6 Jun 2024 17:09:54 -0400 Subject: [PATCH 40/58] Minor bugfixes and rearranging --- src/2d/shallow/surge/storm_module.f90 | 21 ++++++++++++++------- src/python/geoclaw/data.py | 17 ++++++++++++++--- src/python/geoclaw/surge/storm.py | 2 +- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/2d/shallow/surge/storm_module.f90 b/src/2d/shallow/surge/storm_module.f90 index 0fdcad6e3..632cbff90 100644 --- a/src/2d/shallow/surge/storm_module.f90 +++ b/src/2d/shallow/surge/storm_module.f90 @@ -29,7 +29,6 @@ module storm_module ! Source term control and parameters logical :: wind_forcing, pressure_forcing - integer :: rotation_override ! Wind drag law support abstract interface @@ -127,7 +126,7 @@ subroutine set_storm(data_file) ! Locals integer, parameter :: unit = 13 - integer :: i, drag_law + integer :: i, drag_law, rotation_override character(len=200) :: storm_file_path, line if (.not.module_setup) then @@ -158,8 +157,10 @@ subroutine set_storm(data_file) select case(rotation_override) case(0) rotation => hemisphere_rotation - case(1:2) - rotation => user_rotation + case(1) + rotation => N_rotation + case(2) + rotation => S_rotation case default stop " *** ERROR *** Roation override invalid." end select @@ -500,10 +501,16 @@ logical pure function hemisphere_rotation(x, y) result(rotation) rotation = (y >= 0.d0) end function hemisphere_rotation ! This version just returns the user defined direction - logical pure function user_rotation(x, y) result(rotation) + logical pure function N_rotation(x, y) result(rotation) + implicit none + real(kind=8), intent(in) :: x, y + rotation = .true. + end function N_rotation + ! This version just returns the user defined direction + logical pure function S_rotation(x, y) result(rotation) implicit none real(kind=8), intent(in) :: x, y - rotation = (rotation_override == 1) - end function user_rotation + rotation = .false. + end function S_rotation end module storm_module diff --git a/src/python/geoclaw/data.py b/src/python/geoclaw/data.py index 3c2544fd7..83d9fa769 100755 --- a/src/python/geoclaw/data.py +++ b/src/python/geoclaw/data.py @@ -557,7 +557,7 @@ def __init__(self): self.add_attribute('wind_forcing', False) self.add_attribute('drag_law', 1) self.add_attribute('pressure_forcing', False) - self.add_attribute('rotation', 0) + self.add_attribute('rotation_override', 0) # Algorithm parameters - Indexing is python based self.add_attribute("wind_index", 4) @@ -584,8 +584,19 @@ def write(self, out_file='surge.data', data_source="setrun.py"): self.data_write('drag_law', description='(Type of drag law to use)') self.data_write('pressure_forcing', description="(Pressure source term used)") - self.data_write('rotation', - description="(type of rotation storms should have)") + if isinstance(self.rotation_override, str): + if self.rotation_override.lower() == "normal": + self.rotation_override = 0 + elif "n" in self.rotation_override.lower(): + self.rotation_override = 1 + elif "s" in self.rotation_override.lower(): + self.rotation_override = 2 + else: + raise ValueError("Unknown rotation_override specification.") + else: + self.rotation_override = int(self.rotation_override) + self.data_write('rotation_override', + description="(Override storm rotation)") self.data_write() self.data_write("wind_index", value=self.wind_index + 1, diff --git a/src/python/geoclaw/surge/storm.py b/src/python/geoclaw/surge/storm.py index 5ef601bb4..dbcfad3ff 100644 --- a/src/python/geoclaw/surge/storm.py +++ b/src/python/geoclaw/surge/storm.py @@ -380,7 +380,7 @@ def num_converter(x): for c in ["LAT", "LON", "VMAX", "MSLP", "ROUTER", "RMW", "RAD", "RAD1", "RAD2", "RAD3", "RAD4"]: df[c] = df[c].where(df[c] != 0, np.nan) # value 0 means NaN - df[c] = df.groupby("DATE")[c].fillna(methos="bfill") + df[c] = df.groupby("DATE")[c].bfill() df = df.groupby("DATE").first() # Wind profile (occasionally missing for older ATCF storms) From e7b24e5fd0210789fa1b7a9810c84007ccf114a1 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 7 Jun 2024 15:03:03 -0400 Subject: [PATCH 41/58] Fix bugs in the non-spherical coordinates for storms" --- src/2d/shallow/surge/model_storm_module.f90 | 60 +++++++++++---------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/2d/shallow/surge/model_storm_module.f90 b/src/2d/shallow/surge/model_storm_module.f90 index a9fdd2a69..b49e98120 100644 --- a/src/2d/shallow/surge/model_storm_module.f90 +++ b/src/2d/shallow/surge/model_storm_module.f90 @@ -160,8 +160,8 @@ subroutine set_storm(storm_data_path, storm, storm_spec_type, log_unit) 0.5d0 * (x(1) + y(1)), y(2)) storm%velocity(2, i) = sign(ds / dt, y(2) - x(2)) else - storm%velocity(1, i) = abs(x(2) - x(1)) / dt - storm%velocity(2, i) = abs(y(2) - y(1)) / dt + storm%velocity(1, i) = (y(1) - x(1)) / dt + storm%velocity(2, i) = (y(2) - x(2)) / dt end if end do @@ -345,7 +345,7 @@ subroutine get_storm_data(t, storm, location, velocity, max_wind_radius, & max_wind_speed, central_pressure, & radius, central_pressure_change) - use geoclaw_module, only: deg2rad, latlon2xy, xy2latlon + use geoclaw_module, only: deg2rad, latlon2xy, xy2latlon, coordinate_system implicit none @@ -389,13 +389,20 @@ subroutine get_storm_data(t, storm, location, velocity, max_wind_radius, & ! Convert coordinates temporarily to meters so that we can use ! the pre-calculated m/s velocities from before - x = latlon2xy(storm%track(2:3,i),storm%track(2:3,i)) - x = x + (t - storm%track(1,i)) * storm%velocity(:,i) - - fn = [xy2latlon(x,storm%track(2:3,i)), & - storm%velocity(:,i), storm%max_wind_radius(i), & - storm%max_wind_speed(i), storm%central_pressure(i), & - storm%radius(i), storm%central_pressure_change(i)] + if (coordinate_system == 2) then + x = latlon2xy(storm%track(2:3,i),storm%track(2:3,i)) + x = x + (t - storm%track(1,i)) * storm%velocity(:,i) + fn = [xy2latlon(x,storm%track(2:3,i)), & + storm%velocity(:,i), storm%max_wind_radius(i), & + storm%max_wind_speed(i), storm%central_pressure(i), & + storm%radius(i), storm%central_pressure_change(i)] + else + x = x + (t - storm%track(1,i)) * storm%velocity(:,i) + fn = [x, & + storm%velocity(:,i), storm%max_wind_radius(i), & + storm%max_wind_speed(i), storm%central_pressure(i), & + storm%radius(i), storm%central_pressure_change(i)] + end if else ! Inbetween two forecast time points (the function storm_index ! ensures that we are not before the first data point, i.e. i > 1) @@ -437,11 +444,8 @@ end subroutine get_storm_data pure subroutine adjust_max_wind(tv, mws, mod_mws, convert_height) real (kind=8), intent(inout) :: tv(2) - real (kind=8), intent(in) :: mws - logical, intent(in) :: convert_height - real (kind=8), intent(out) :: mod_mws real (kind=8) :: trans_speed, trans_mod @@ -528,21 +532,21 @@ end subroutine post_process_wind_estimate ! ========================================================================== pure subroutine calculate_polar_coordinate(x, y, sloc, r, theta) - use geoclaw_module, only: deg2rad, coordinate_system - use geoclaw_module, only: spherical_distance - - real(kind=8), intent(in) :: x, y, sloc(2) - real(kind=8), intent(out) :: r, theta - - if (coordinate_system == 2) then - ! lat-long coordinates, uses Haversine formula - r = spherical_distance(x, y, sloc(1), sloc(2)) - theta = atan2((y - sloc(2)) * DEG2RAD,(x - sloc(1)) * DEG2RAD) - else - ! Strictly cartesian - r = sqrt( (x - sloc(1))**2 + (y - sloc(2))**2) - theta = atan2(y - sloc(2), x - sloc(1)) - end if + use geoclaw_module, only: deg2rad, coordinate_system + use geoclaw_module, only: spherical_distance + + real(kind=8), intent(in) :: x, y, sloc(2) + real(kind=8), intent(out) :: r, theta + + if (coordinate_system == 2) then + ! lat-long coordinates, uses Haversine formula + r = spherical_distance(x, y, sloc(1), sloc(2)) + theta = atan2((y - sloc(2)),(x - sloc(1))) + else + ! Strictly cartesian + r = sqrt( (x - sloc(1))**2 + (y - sloc(2))**2) + theta = atan2((y - sloc(2)), (x - sloc(1))) + end if end subroutine calculate_polar_coordinate From 5fa65610e7c57543f3c4afbc64de1cbbd275e907 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Wed, 19 Jun 2024 08:31:40 -0700 Subject: [PATCH 42/58] fix fgout_tools.FGoutGrid.read_fgout_grids_data for time array Now that `output_style==2` is supported for fgout grids (an array of times, see https://github.com/clawpack/geoclaw/pull/617), the function fgout_tools.FGoutGrid.read_fgout_grids_data needs to be fixed to properly read in the new format of `fgout_grids.data`, also note that `nout` now comes before `tstart` and `tend` for `output_style==1`. --- src/python/geoclaw/fgout_tools.py | 46 +++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/python/geoclaw/fgout_tools.py b/src/python/geoclaw/fgout_tools.py index 87ae51aa3..ed8f675c6 100644 --- a/src/python/geoclaw/fgout_tools.py +++ b/src/python/geoclaw/fgout_tools.py @@ -326,11 +326,32 @@ def read_fgout_grids_data(self, fgno=None, data_file='fgout_grids.data'): raise ValueError('fgout grid fgno = %i not found in %s' \ % (fgno, data_file)) - self.tstart = float(fgout_input[0].split()[0]) - self.tend = float(fgout_input[1].split()[0]) - self.nout = int(fgout_input[2].split()[0]) - self.point_style = point_style = int(fgout_input[3].split()[0]) - output_format = int(fgout_input[4].split()[0]) + lineno = 0 # next input line + self.output_style = int(fgout_input[lineno].split()[lineno]) + lineno += 1 + if (self.output_style == 1): + # equally spaced times: + self.nout = int(fgout_input[lineno].split()[0]) + lineno += 1 + self.tstart = float(fgout_input[lineno].split()[0]) + lineno += 1 + self.tend = float(fgout_input[lineno].split()[0]) + lineno += 1 + self.times = numpy.linspace(self.tstart, self.tend, self.nout) + elif (self.output_style == 2): + # list of times: + self.nout = int(fgout_input[lineno].split()[0]) + lineno += 1 + times_str = fgout_input[lineno].split()[:self.nout] + self.times = numpy.array([float(ts) for ts in times_str]) + lineno += 1 + else: + raise ValueError('Unrecognized fgout output_style: %s' \ + % self.output_style) + self.point_style = point_style = int(fgout_input[lineno].split()[0]) + lineno += 1 + output_format = int(fgout_input[lineno].split()[0]) + lineno += 1 if output_format == 1: self.output_format = 'ascii' elif output_format == 3: @@ -338,12 +359,15 @@ def read_fgout_grids_data(self, fgno=None, data_file='fgout_grids.data'): print('Reading input for fgno=%i, point_style = %i ' \ % (self.fgno, self.point_style)) if point_style == 2: - self.nx = nx = int(fgout_input[5].split()[0]) - self.ny = ny = int(fgout_input[5].split()[1]) - self.x1 = float(fgout_input[6].split()[0]) - self.y1 = float(fgout_input[6].split()[1]) - self.x2 = float(fgout_input[7].split()[0]) - self.y2 = float(fgout_input[7].split()[1]) + self.nx = nx = int(fgout_input[lineno].split()[0]) + self.ny = ny = int(fgout_input[lineno].split()[1]) + lineno += 1 + self.x1 = float(fgout_input[lineno].split()[0]) + self.y1 = float(fgout_input[lineno].split()[1]) + lineno += 1 + self.x2 = float(fgout_input[lineno].split()[0]) + self.y2 = float(fgout_input[lineno].split()[1]) + lineno += 1 else: raise NotImplementedError("fgout not implemented for point_style %i" \ % point_style) From 918a6964ac7a8068582dc1bbaf1f4b41cded1b74 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 28 Jun 2024 13:45:04 -0400 Subject: [PATCH 43/58] Add kwargs to Topography reading --- src/python/geoclaw/topotools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/geoclaw/topotools.py b/src/python/geoclaw/topotools.py index 1743e1ae6..db6b6410c 100644 --- a/src/python/geoclaw/topotools.py +++ b/src/python/geoclaw/topotools.py @@ -414,7 +414,7 @@ def delta(self): def __init__(self, path=None, topo_type=None, topo_func=None, - unstructured=False): + unstructured=False, **kwargs): r"""Topography initialization routine. See :class:`Topography` for more info. @@ -444,7 +444,7 @@ def __init__(self, path=None, topo_type=None, topo_func=None, if path: self.read(path=path, topo_type=topo_type, - unstructured=unstructured) + unstructured=unstructured, **kwargs) def set_xyZ(self, X, Y, Z): r""" From dc0c37e03ee9e29093be6c1d547c059a843b884e Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 28 Jun 2024 13:47:28 -0400 Subject: [PATCH 44/58] Add radii to plotting options --- src/python/geoclaw/surge/plot.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/python/geoclaw/surge/plot.py b/src/python/geoclaw/surge/plot.py index c2ada663f..b67ecf546 100644 --- a/src/python/geoclaw/surge/plot.py +++ b/src/python/geoclaw/surge/plot.py @@ -13,6 +13,9 @@ from __future__ import absolute_import from __future__ import print_function + +import warnings + import numpy as np import matplotlib.pyplot as plt import matplotlib.colors as colors @@ -23,6 +26,7 @@ import clawpack.visclaw.gaugetools as gaugetools import clawpack.visclaw.geoplot as geoplot import clawpack.geoclaw.data as geodata +# import clawpack.geoclaw.surge.storm # TODO: Assign these based on data files bathy_index = 0 @@ -41,7 +45,9 @@ surge_data = geodata.SurgeData() - +# ============================== +# Track Plotting Functionality +# ============================== class track_data(object): """Read in storm track data from run output""" @@ -68,8 +74,8 @@ def get_track(self, frame): # Check to make sure that this fixed the problem if self._data.shape[0] < frame + 1: - print(" *** WARNING *** Could not find track data for ", - "frame %s." % frame) + warnings.warn(f" *** WARNING *** Could not find track data", + " for frame {frame}.") return None, None, None return self._data[frame, 1:] @@ -165,8 +171,15 @@ def pressure(cd): # The division by 100.0 is to convert from Pa to millibars return cd.aux[pressure_field, :, :] / 100.0 -# def category(Storm, cd): -# return cd.aux[Storm.category, :, :] + +def storm_radius(cd, track): + """Distance from center of storm""" + track_data = track.get_track(cd.frameno) + + if track_data[0] is not None and track_data[1] is not None: + return np.sqrt((cd.x - track_data[0])**2 + (cd.y - track_data[1])**2) + else: + return None # ======================================================================== @@ -435,6 +448,15 @@ def add_bathy_contours(plotaxes, contour_levels=None, color='k'): plotitem.patchedges_show = 0 +def add_storm_radii(plotaxes, track, radii=[100e3], color='r'): + """Add radii to plots based on storm position""" + plotitem = plotaxes.new_plotitem(name="storm radius", + plot_type="2d_contour") + plotitem.plot_var = lambda cd: storm_radius(cd, track) + plotitem.contour_levels = radii + plotitem.contour_colors = color + + # ===== Storm related plotting ======= def sec2days(seconds): """Converst seconds to days.""" From 25c5ea6653bc461a366f05ecdd916c4a6d2e8543 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 28 Jun 2024 13:49:11 -0400 Subject: [PATCH 45/58] Add plotting of storm tracks --- src/python/geoclaw/surge/storm.py | 125 ++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 8 deletions(-) diff --git a/src/python/geoclaw/surge/storm.py b/src/python/geoclaw/surge/storm.py index dbcfad3ff..4fe670ed1 100644 --- a/src/python/geoclaw/surge/storm.py +++ b/src/python/geoclaw/surge/storm.py @@ -281,18 +281,25 @@ def read_geoclaw(self, path, verbose=False): # Read header with open(path, 'r') as data_file: num_casts = int(data_file.readline()) - self.time_offset = datetime.datetime.strptime( - data_file.readline()[:19], - '%Y-%m-%dT%H:%M:%S') + time = data_file.readline()[:19] + try: + self.time_offset = datetime.datetime.strptime( + time, '%Y-%m-%dT%H:%M:%S') + except ValueError: + self.time_offset = float(time) # Read rest of data data = np.loadtxt(path, skiprows=3) num_forecasts = data.shape[0] - self.eye_location = np.empty((2, num_forecasts)) + self.eye_location = np.empty((num_forecasts, 2)) assert(num_casts == num_forecasts) - self.t = [self.time_offset + datetime.timedelta(seconds=data[i, 0]) - for i in range(num_forecasts)] - self.eye_location[0, :] = data[:, 1] - self.eye_location[1, :] = data[:, 2] + if isinstance(self.time_offset, datetime.datetime): + self.t = np.array([self.time_offset + + datetime.timedelta(seconds=data[i, 0]) + for i in range(num_forecasts)]) + else: + self.t = data[:, 0] + self.eye_location[:, 0] = data[:, 1] + self.eye_location[:, 1] = data[:, 2] self.max_wind_speed = data[:, 3] self.max_wind_radius = data[:, 4] self.central_pressure = data[:, 5] @@ -1154,6 +1161,108 @@ def write_tcvitals(self, path, verbose=False): "implemented yet but is planned for a ", "future release.")) + # ================ + # Track Plotting + # ================ + def plot(self, ax, radius=None, t_range=None, coordinate_system=2, track_style='ko--', + categorization="NHC", fill_alpha=0.25, fill_color='red'): + """TO DO: Write doc-string""" + + import matplotlib.pyplot as plt + + if isinstance(track_style, str): + style = track_style + + # Extract information for plotting the track/swath + t = self.t + x = self.eye_location[:, 0] + y = self.eye_location[:, 1] + if t_range is not None: + t = np.ma.masked_outside(t, t_range[0], t_range[1]) + x = np.ma.array(x, mask=t.mask).compressed() + y = np.ma.array(y, mask=t.mask).compressed() + t = t.compressed() + + # Plot track + if isinstance(track_style, str): + # Plot the track as a simple line with the given style + ax.plot(x, y, track_style) + elif isinstance(track_style, dict): + if self.max_wind_speed is None: + raise ValueError("Maximum wind speed not available so " + "plotting catgories is not available.") + + # Plot the track using the colors provided in the dictionary + cat_color_defaults = {5: 'red', 4: 'yellow', 3: 'orange', 2: 'green', + 1: 'blue', 0: 'gray', -1: 'lightgray'} + colors = [track_style.get(category, cat_color_defaults[category]) + for category in self.category(categorization=categorization)] + for i in range(t.shape[0] - 1): + ax.plot(x[i:i+2], y[i:i+2], color=colors[i], marker="o") + + else: + raise ValueError("The `track_style` should be a string or dict.") + + # Plot swath + if (isinstance(radius, float) or isinstance(radius, np.ndarray) + or radius is None): + + if radius is None: + # Default behavior + if self.storm_radius is None: + raise ValueError("Cannot use storm radius for plotting " + "the swath as the data is not available.") + else: + if coordinate_system == 1: + _radius = self.storm_radius + elif coordinate_system == 2: + _radius = units.convert(self.storm_radius, + 'm', 'lat-long') + else: + raise ValueError(f"Unknown coordinate system " + f"{coordinate_system} provided.") + + elif isinstance(radius, float): + # Only one value for the radius was given, replicate + _radius = np.ones(self.t.shape) * radius + elif isinstance(radius, np.ndarray): + # The array passed is the array to use + _radius = radius + else: + raise ValueError("Invalid input argument for radius. Should " + "be a float or None") + + # Draw first and last points + ax.add_patch(plt.Circle( + (x[0], y[0]), _radius[0], color=fill_color)) + if t.shape[0] > 1: + ax.add_patch(plt.Circle((x[-1], y[-1]), _radius[-1], + color=fill_color)) + + # Draw path around inner points + if t.shape[0] > 2: + for i in range(t.shape[0] - 1): + p = np.array([(x[i], y[i]), (x[i + 1], y[i + 1])]) + v = p[1] - p[0] + if abs(v[1]) > 1e-16: + n = np.array([1, -v[0] / v[1]], dtype=float) + elif abs(v[0]) > 1e-16: + n = np.array([-v[1] / v[0], 1], dtype=float) + else: + raise Exception("Zero-vector given") + n /= np.linalg.norm(n) + n *= _radius[i] + + ax.fill((p[0, 0] + n[0], p[0, 0] - n[0], + p[1, 0] - n[0], + p[1, 0] + n[0]), + (p[0, 1] + n[1], p[0, 1] - n[1], + p[1, 1] - n[1], + p[1, 1] + n[1]), + facecolor=fill_color, alpha=fill_alpha) + ax.add_patch(plt.Circle((p[1][0], p[1, 1]), _radius[i], + color=fill_color, alpha=fill_alpha)) + # ========================================================================= # Other Useful Routines From 35dd12d2229ac4481910158df14164fe18d3f111 Mon Sep 17 00:00:00 2001 From: Ian Bolliger Date: Mon, 8 Jul 2024 20:12:04 -0400 Subject: [PATCH 46/58] try fixing testing --- .github/workflows/testing.yml | 7 ++++- .travis.yml | 48 ----------------------------------- 2 files changed, 6 insertions(+), 49 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 4d1d669d4..99779c0b4 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -40,7 +40,12 @@ jobs: - name: Setup geoclaw run: | cd geoclaw - git checkout ${{ github.ref }} + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + git fetch origin pull/${{ github.event.pull_request.number }}/merge:PR + git checkout PR + else + git checkout ${{ github.ref_name }} + fi - name: Lint with flake8 run: | cd geoclaw diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9b7540911..000000000 --- a/.travis.yml +++ /dev/null @@ -1,48 +0,0 @@ -language: python -python: - - 3.8 -env: - - BUILD_TYPE="Release" - -before_install: - - sudo apt-get update - - sudo apt-get install gfortran liblapack-pic liblapack-dev libnetcdf-dev libnetcdff-dev - - pip install netCDF4 - - pip install xarray - - pip install scipy - - pip install matplotlib - - git clone --branch=master --depth=100 --quiet git://github.com/clawpack/clawpack - - cd clawpack - - git submodule init - - git submodule update - - rm -rf geoclaw - - ln -s ../ geoclaw - # Print versions being used - - python -c "import numpy; print(numpy.__version__)" - - python -c "import netCDF4; print(netCDF4.__version__)" - -install: - - export PYTHONPATH=${PWD}:$PYTHONPATH - - export CLAW=${PWD} - - export FC=gfortran - - export NETCDF4_DIR=/usr - -before_script: - # Print CPU info for debugging purposes: - - cat /proc/cpuinfo - - gfortran -v - - /usr/bin/f95 -v - - echo "PYTHONPATH="$PYTHONPATH - - echo "CLAW="$CLAW - - echo "FC="$FC - - echo "NETCDF4_DIR="$NETCDF4_DIR - -script: - - cd $CLAW/geoclaw - - nosetests -sv - -after_failure: - - for failed_test_path in *_output ; do cat $failed_test_path/run_output.txt ; cat $failed_test_path/error_output.txt ; done - -notifications: - email: false From 556c769cd04e10357473be9b750d5939918f2ef7 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Sat, 13 Jul 2024 18:17:38 -0700 Subject: [PATCH 47/58] Allow specifying which components of q to output on each fgout grid Modifications to fgout_module.f90 and fgout_tools.py, primarily. Can now set `fgout.q_out_vars` to a list of which components of q to output for each fgout frame (Fortran indexing). The default is `fgout.q_out_vars = [1,2,3,4]` for all components of q[1:3] and also eta (4), consistent with the previous behavior. The user could also/instead ask to output the topo B as component 5. (If two out of three of h, eta, B are output then the other can be computed from these.) Note that this list is written out to `fgout_grids.data` rather than a boolean list of True/False values for each possible component as is done in other places in GeoClaw. --- src/2d/shallow/amr2.f90 | 4 +- src/2d/shallow/fgout_module.f90 | 198 ++++++++++-------------------- src/python/geoclaw/data.py | 2 +- src/python/geoclaw/fgout_tools.py | 122 ++++++++++++------ 4 files changed, 147 insertions(+), 179 deletions(-) diff --git a/src/2d/shallow/amr2.f90 b/src/2d/shallow/amr2.f90 index 139e01f1e..7dea01763 100644 --- a/src/2d/shallow/amr2.f90 +++ b/src/2d/shallow/amr2.f90 @@ -491,7 +491,7 @@ program amr2 call read_dtopo_settings() ! specifies file with dtopo from earthquake call read_topo_settings(rest) ! specifies topography (bathymetry) files call set_qinit() ! specifies file with dh if this used instead - call set_fgout(rest) ! Fixed grid settings + call set_fgout(rest,nvar) ! Fixed grid settings call setup_variable_friction() ! Variable friction parameter !call set_multilayer() ! Set multilayer SWE parameters call set_storm() ! Set storm parameters @@ -526,7 +526,7 @@ program amr2 call read_dtopo_settings() ! specifies file with dtopo from earthquake call read_topo_settings(rest) ! specifies topography (bathymetry) files call set_qinit() ! specifies file with dh if this used instead - call set_fgout(rest) ! Fixed grid settings + call set_fgout(rest,nvar) ! Fixed grid settings call setup_variable_friction() ! Variable friction parameter call set_multilayer() ! Set multilayer SWE parameters call set_storm() ! Set storm parameters diff --git a/src/2d/shallow/fgout_module.f90 b/src/2d/shallow/fgout_module.f90 index 981d11ac3..b583df59e 100644 --- a/src/2d/shallow/fgout_module.f90 +++ b/src/2d/shallow/fgout_module.f90 @@ -10,7 +10,7 @@ module fgout_module real(kind=8), pointer :: late(:,:,:) ! Geometry - integer :: num_vars,mx,my,point_style,fgno,output_format,output_style + integer :: mx,my,point_style,fgno,output_format,output_style real(kind=8) :: dx,dy,x_low,x_hi,y_low,y_hi ! Time Tracking and output types @@ -19,11 +19,13 @@ module fgout_module integer, allocatable :: output_frames(:) real(kind=8), allocatable :: output_times(:) - + + integer :: num_vars ! number of variables to interpolate (num_eqn+3) integer :: nqout ! number of q components to output (+1 for eta) - logical, allocatable :: iqout(:) ! which components to output - integer :: bathy_index,eta_index - logical :: dclaw ! False for GeoClaw + logical, allocatable :: q_out_vars(:) ! which components to print + + !logical, allocatable :: iqout(:) ! which components to output + integer :: bathy_index,eta_index,time_index end type fgout_grid @@ -43,14 +45,16 @@ module fgout_module ! Setup routine that reads in the fixed grids data file and sets up the ! appropriate data structures - subroutine set_fgout(rest,fname) + subroutine set_fgout(rest,num_eqn,fname) use amr_module, only: parmunit, tstart_thisrun + use utility_module, only: parse_values implicit none ! Subroutine arguments - logical :: rest ! restart? + logical, intent(in) :: rest ! restart? + integer, intent(in) :: num_eqn character(len=*), optional, intent(in) :: fname ! Local storage @@ -59,6 +63,9 @@ subroutine set_fgout(rest,fname) type(fgout_grid), pointer :: fg real(kind=8) :: ts + integer :: n, iq + real(kind=8) :: values(num_eqn+2) + character(len=80) :: str if (.not.module_setup) then @@ -109,7 +116,25 @@ subroutine set_fgout(rest,fname) read(unit,*) fg%mx, fg%my read(unit,*) fg%x_low, fg%y_low read(unit,*) fg%x_hi, fg%y_hi - read(unit,*) fg%dclaw + + allocate(fg%q_out_vars(num_eqn+2)) ! for q + eta, topo + do iq=1,num_eqn+2 + fg%q_out_vars(iq) = .false. + enddo + read(unit,'(a)') str + call parse_values(str, n, values) + do k=1,n + iq = nint(values(k)) + fg%q_out_vars(iq) = .true. + enddo + write(6,*) '+++ q_out_vars:', fg%q_out_vars + + fg%num_vars = num_eqn + 3 ! also interp topo,eta,time + fg%nqout = 0 ! count how many are to be output + do k=1,num_eqn+2 + if (fg%q_out_vars(k)) fg%nqout = fg%nqout + 1 + enddo + !fg%nqout = fg%nqout + 1 ! for eta ! Initialize next_output_index @@ -193,68 +218,15 @@ subroutine set_fgout(rest,fname) print *,'y grid points my <= 0, set my >= 1' endif - - ! For now, hard-wire with defaults for either GeoClaw or D-Claw - ! need to save q plus topo, eta, t for interp in space-time - - if (fg%dclaw) then - ! For D-Claw: - fg%num_vars = 10 - ! for h, hu, hv, hm, pb, hchi, b_eroded, bathy, eta, time - else - ! GeoClaw: - fg%num_vars = 6 - ! for h, hu, hv, bathy, eta, time - endif - - ! specify which components of q (plus eta?) to output: - ! (eventually this should be set from user input) - - if (fg%num_vars == 6) then - ! GeoClaw - ! indexes used in early and late arrays: - ! 1:3 are q variables, 6 is time - fg%bathy_index = 4 - fg%eta_index = 5 - - allocate(fg%iqout(4)) - fg%iqout(1) = .true. ! output h? - fg%iqout(2) = .true. ! output hu? - fg%iqout(3) = .true. ! output hv? - fg%iqout(4) = .true. ! output eta? - fg%nqout = 0 - do k=1,4 - if (fg%iqout(k)) fg%nqout = fg%nqout + 1 - enddo - endif + fg%bathy_index = num_eqn + 2 + fg%eta_index = num_eqn + 1 + fg%time_index = num_eqn + 3 - if (fg%num_vars == 10) then - ! D-Claw: - ! indexes used in early and late arrays: - ! 1:7 are q variables, 10 is time - fg%bathy_index = 8 - fg%eta_index = 9 - - allocate(fg%iqout(8)) - fg%iqout(1) = .true. ! output h? - fg%iqout(2) = .true. ! output hu? - fg%iqout(3) = .true. ! output hv? - fg%iqout(4) = .true. ! output hm? - fg%iqout(5) = .true. ! output pb? - fg%iqout(6) = .true. ! output hchi? - fg%iqout(7) = .true. ! output beroded? - fg%iqout(8) = .true. ! output eta? - fg%nqout = 0 - do k=1,8 - if (fg%iqout(k)) fg%nqout = fg%nqout + 1 - enddo - endif - write(6,*) '+++ nqout = ',fg%nqout ! Allocate new fixed grid data arrays at early, late time: ! dimension (10,:,:) to work for either GeoClaw or D-Claw - + allocate(fg%early(10, fg%mx,fg%my)) fg%early = nan() @@ -412,7 +384,7 @@ subroutine fgout_interp(fgrid_type,fgrid, & ! save the time this fgout point was computed: - fg_data(num_vars,ifg,jfg) = t + fg_data(fgrid%time_index,ifg,jfg) = t endif ! if fgout point is on this grid @@ -441,7 +413,7 @@ subroutine fgout_write(fgrid,out_time,out_index) ! I/O integer, parameter :: unit = 87 character(len=15) :: fg_filename - character(len=4) :: cfgno, cframeno + character(len=8) :: cfgno, cframeno character(len=8) :: file_format integer :: grid_number,ipos,idigit,out_number,columns integer :: ifg,ifg1, iframe,iframe1 @@ -472,7 +444,7 @@ subroutine fgout_write(fgrid,out_time,out_index) "a10,' format'/,/)" ! Other locals - integer :: i,j,m,iq,k + integer :: i,j,m,iq,k,jq,num_eqn real(kind=8) :: t0,tf,tau, qaug(10) real(kind=8), allocatable :: qeta(:,:,:) real(kind=4), allocatable :: qeta4(:,:,:) @@ -546,76 +518,34 @@ subroutine fgout_write(fgrid,out_time,out_index) endif endif - ! Output the conserved quantities and eta value + ! Output the requested quantities and eta value: + iq = 1 - ! qaug(1:3) are h,hu,hv for both GeoClaw and D-Claw: - if (fgrid%iqout(1)) then - qeta(iq,i,j) = qaug(1) ! h - iq = iq+1 - endif - if (fgrid%iqout(2)) then - qeta(iq,i,j) = qaug(2) ! hu - iq = iq+1 - endif - if (fgrid%iqout(3)) then - qeta(iq,i,j) = qaug(3) ! hv - iq = iq+1 - endif - - if (fgrid%num_vars == 6) then - ! GeoClaw: - if (fgrid%iqout(4)) then - qeta(iq,i,j) = qaug(5) ! eta since qaug(4)=topo - iq = iq+1 - endif - - else if (fgrid%num_vars == 10) then - ! D-Claw: - if (fgrid%iqout(4)) then - qeta(iq,i,j) = qaug(4) ! hm - iq = iq+1 - endif - if (fgrid%iqout(5)) then - qeta(iq,i,j) = qaug(5) ! pb - iq = iq+1 - endif - if (fgrid%iqout(6)) then - qeta(iq,i,j) = qaug(6) ! hchi - iq = iq+1 - endif - if (fgrid%iqout(7)) then - qeta(iq,i,j) = qaug(7) ! b_eroded - iq = iq+1 - endif - if (fgrid%iqout(8)) then - qeta(iq,i,j) = qaug(9) ! eta since qaug(8)=topo + num_eqn = size(fgrid%q_out_vars) - 2 ! last components eta,B + do jq=1,num_eqn+2 + if (fgrid%q_out_vars(jq)) then + qeta(iq,i,j) = qaug(jq) iq = iq+1 endif - endif + enddo + + ! now append eta value: (this is now included in loop above) + !qeta(iq,i,j) = qaug(fgrid%eta_index) + ! NOTE: qaug(fgrid%bathy_index) contains topo B value + enddo enddo - ! Make the file names and open output files - cfgno = '0000' - ifg = fgrid%fgno - ifg1 = ifg - do ipos=4,1,-1 - idigit = mod(ifg1,10) - cfgno(ipos:ipos) = char(ichar('0') + idigit) - ifg1 = ifg1/10 - enddo + ! convert fgrid%fgno to a string of length at least 4 with leading 0's + ! e.g. '0001' or '12345': + write(cfgno, '(I0.4)') fgrid%fgno - cframeno = '0000' - iframe = out_index - iframe1 = iframe - do ipos=4,1,-1 - idigit = mod(iframe1,10) - cframeno(ipos:ipos) = char(ichar('0') + idigit) - iframe1 = iframe1/10 - enddo + ! convert out_index to a string of length at least 4 with leading 0's + write(cframeno, '(I0.4)') out_index - fg_filename = 'fgout' // cfgno // '.q' // cframeno + + fg_filename = 'fgout' // trim(cfgno) // '.q' // trim(cframeno) open(unit,file=fg_filename,status='unknown',form='formatted') @@ -637,14 +567,14 @@ subroutine fgout_write(fgrid,out_time,out_index) if (fgrid%output_format == 3) then ! binary output goes in .b file as full 8-byte (float64): - fg_filename = 'fgout' // cfgno // '.b' // cframeno + fg_filename = 'fgout' // trim(cfgno) // '.b' // trim(cframeno) open(unit=unit, file=fg_filename, status="unknown", & access='stream') write(unit) qeta close(unit) else if (fgrid%output_format == 2) then ! binary output goes in .b file as 4-byte (float32): - fg_filename = 'fgout' // cfgno // '.b' // cframeno + fg_filename = 'fgout' // trim(cfgno) // '.b' // trim(cframeno) open(unit=unit, file=fg_filename, status="unknown", & access='stream') allocate(qeta4(fgrid%nqout, fgrid%mx, fgrid%my)) @@ -671,7 +601,7 @@ subroutine fgout_write(fgrid,out_time,out_index) write(6,*) '*** should be ascii, binary32, or binary64' endif - fg_filename = 'fgout' // cfgno // '.t' // cframeno + fg_filename = 'fgout' // trim(cfgno) // '.t' // trim(cframeno) open(unit=unit, file=fg_filename, status='unknown', form='formatted') ! time, num_eqn+1, num_grids, num_aux, num_dim, num_ghost: write(unit, t_file_format) out_time, fgrid%nqout, 1, 0, 2, 0,file_format @@ -680,10 +610,6 @@ subroutine fgout_write(fgrid,out_time,out_index) print "(a,i4,a,i4,a,e18.8)",'Writing fgout grid #',fgrid%fgno, & ' frame ',out_index,' at time =',out_time - ! Index into qeta for binary output - ! Note that this implicitly assumes that we are outputting only h, hu, hv - ! and will not output more (change num_eqn parameter above) - end subroutine fgout_write diff --git a/src/python/geoclaw/data.py b/src/python/geoclaw/data.py index 83d9fa769..01c132400 100755 --- a/src/python/geoclaw/data.py +++ b/src/python/geoclaw/data.py @@ -9,7 +9,7 @@ - GeoClawData - RefinementData - TopographyData - - FixedGridData + - FGoutData - FGmaxData - DTopoData - QinitData diff --git a/src/python/geoclaw/fgout_tools.py b/src/python/geoclaw/fgout_tools.py index ed8f675c6..9c6c4d2cb 100644 --- a/src/python/geoclaw/fgout_tools.py +++ b/src/python/geoclaw/fgout_tools.py @@ -41,6 +41,10 @@ def __init__(self, fgout_grid, frameno=None): self.frameno = frameno self.t = None + # mapping from variable names to possible values in q_out_vars + # default for GeoClaw: + self.qmap = {'h':1, 'hu':2, 'hv':3, 'eta':4, 'B':5} + # private attributes for those that are only created if # needed by the user: self._h = None @@ -96,16 +100,31 @@ def drytol(self): def h(self): """depth""" if self._h is None: - #print('+++ setting _h...') - self._h = self.q[0,:,:] - #print('+++ getting _h...') + q_out_vars = self.fgout_grid.q_out_vars + try: + i_h = q_out_vars.index(self.qmap['h']) + self._h = self.q[i_h,:,:] + except: + try: + i_eta = q_out_vars.index(self.qmap['eta']) + i_B = q_out_vars.index(self.qmap['B']) + self._h = self.q[i_eta,:,:] - self.q[i_B,:,:] + except: + print('*** Could not find h or eta-B in q_out_vars') + raise return self._h @property def hu(self): """momentum h*u""" if self._hu is None: - self._hu = self.q[1,:,:] + q_out_vars = self.fgout_grid.q_out_vars + try: + i_hu = q_out_vars.index(self.qmap['hu']) + self._hu = self.q[i_hu,:,:] + except: + print('*** Could not find hu in q_out_vars') + raise return self._hu @property @@ -121,7 +140,13 @@ def u(self): def hv(self): """momentum h*v""" if self._hv is None: - self._hv = self.q[2,:,:] + q_out_vars = self.fgout_grid.q_out_vars + try: + i_hv = q_out_vars.index(self.qmap['hv']) + self._hv = self.q[i_hv,:,:] + except: + print('*** Could not find hv in q_out_vars') + raise return self._hv @property @@ -137,14 +162,36 @@ def v(self): def eta(self): """surface eta = h+B""" if self._eta is None: - self._eta = self.q[-1,:,:] + q_out_vars = self.fgout_grid.q_out_vars + try: + i_eta = q_out_vars.index(self.qmap['eta']) + self._eta = self.q[i_eta,:,:] + except: + try: + i_h = q_out_vars.index(self.qmap['h']) + i_B = q_out_vars.index(self.qmap['B']) + self._eta = self.q[i_h,:,:] + self.q[i_B,:,:] + except: + print('*** Could not find eta or h+B in q_out_vars') + raise return self._eta @property def B(self): """topography""" if self._B is None: - self._B = self.q[-1,:,:] - self.q[0,:,:] + q_out_vars = self.fgout_grid.q_out_vars + try: + i_B = q_out_vars.index(self.qmap['B']) + self._B = self.q[i_B,:,:] + except: + try: + i_h = q_out_vars.index(self.qmap['h']) + i_eta = q_out_vars.index(self.qmap['eta']) + self._B = self.q[i_eta,:,:] - self.q[i_h,:,:] + except: + print('*** Could not find B or eta-h in q_out_vars') + raise return self._B @property @@ -169,7 +216,7 @@ class FGoutGrid(object): fgout input data and the output generated by a GeoClaw run. """ - def __init__(self,fgno=None,outdir=None,output_format=None): + def __init__(self,fgno=None,outdir='.',output_format=None): # GeoClaw input values: @@ -185,7 +232,9 @@ def __init__(self,fgno=None,outdir=None,output_format=None): self.fgno = fgno self.outdir = outdir self.output_format = output_format - self.dclaw = False + self.q_out_vars = [1,2,3,4] # list of which components to print + # default: h,hu,hv,eta (5=topo) + self.nqout = None # number of vars to print out self.drytol = 1e-3 # used for computing u,v from hu,hv @@ -312,6 +361,9 @@ def read_fgout_grids_data(self, fgno=None, data_file='fgout_grids.data'): self.fgno = fgno assert self.fgno is not None, '*** fgno must be set' + data_path = os.path.join(self.outdir, data_file) + print('Reading fgout grid info from \n %s' % data_path) + with open(data_file) as filep: lines = filep.readlines() fgout_input = None @@ -371,6 +423,13 @@ def read_fgout_grids_data(self, fgno=None, data_file='fgout_grids.data'): else: raise NotImplementedError("fgout not implemented for point_style %i" \ % point_style) + tokens = fgout_input[lineno].split() + self.q_out_vars = [] + for token in tokens: + try: + self.q_out_vars.append(int(token)) + except: + break def write_to_fgout_data(self, fid): @@ -455,7 +514,11 @@ def write_to_fgout_data(self, fid): print(" upper right = (%15.10f,%15.10f)" % (x2,y2)) print(" dx = %15.10e, dy = %15.10e" % (dx,dy)) - fid.write("%s # dclaw" % self.dclaw) + # q_out_vars is a list of q components to print, e.g. [1,4] + + format = len(self.q_out_vars) * '%s ' + fid.write(format % tuple(self.q_out_vars)+ " # q_out_vars\n") + fid.write('\n') def read_frame(self, frameno): @@ -465,6 +528,14 @@ def read_frame(self, frameno): from datetime import timedelta + try: + fgoutX = self.X + fgoutY = self.Y + except: + self.read_fgout_grids_data() + fgoutX = self.X + fgoutY = self.Y + try: fr = self.plotdata.getframe(frameno) except: @@ -483,36 +554,7 @@ def read_frame(self, frameno): fgout_frame.frameno = frameno - X,Y = patch.grid.p_centers[:2] - - try: - fgoutX = self.X - fgoutY = self.Y - except: - # presumably x,y, etc. were not set for this fgout_grid - # (e.g. by read_fgout_grids_data) so infer from this frame and set - - # reset most attributes including private _x etc to None: - self.__init__(fgno=self.fgno,outdir=self.outdir, - output_format=self.output_format) - - print(' Setting grid attributes based on Frame %i:' % frameno) - x = X[:,0] - y = Y[0,:] - dx = x[1] - x[0] - dy = y[1] - y[0] - self.x1 = x[0] - dx/2 - self.x2 = x[-1] + dx/2 - self.y1 = y[0] - dy/2 - self.y2 = y[-1] + dy/2 - self.nx = len(x) - self.ny = len(y) - fgoutX = self.X # will be computed from info above - fgoutY = self.Y # will be computed from info above - print(' Grid edges: ',self.extent_edges) - print(' with %i cells in x and %i cells in y' \ - % (self.nx,self.ny)) - + X,Y = patch.grid.p_centers[:2] if not numpy.allclose(X, fgoutX): errmsg = '*** X read from output does not match fgout_grid.X' From b5c898a0a5189e2e0fe0e8463e0454b9f04bcb21 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Tue, 16 Jul 2024 22:01:51 -0700 Subject: [PATCH 48/58] working on dclaw variables --- src/python/geoclaw/fgout_tools.py | 44 +++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/src/python/geoclaw/fgout_tools.py b/src/python/geoclaw/fgout_tools.py index 9c6c4d2cb..dfedda3fc 100644 --- a/src/python/geoclaw/fgout_tools.py +++ b/src/python/geoclaw/fgout_tools.py @@ -36,14 +36,25 @@ class FGoutFrame(object): and stored only when needed by the user. """ - def __init__(self, fgout_grid, frameno=None): + def __init__(self, fgout_grid, frameno=None, qmap='geoclaw'): self.fgout_grid = fgout_grid self.frameno = frameno self.t = None # mapping from variable names to possible values in q_out_vars # default for GeoClaw: - self.qmap = {'h':1, 'hu':2, 'hv':3, 'eta':4, 'B':5} + if type(qmap) is dict: + self.qmap = qmap + elif qmap == 'geoclaw': + self.qmap = {'h':1, 'hu':2, 'hv':3, 'eta':4, 'B':5} + elif qmap == 'geoclaw-bouss': + self.qmap = {'h':1, 'hu':2, 'hv':3, 'huc':4, 'hvc':5, + 'eta':4, 'B':5} + elif qmap == 'dclaw': + self.qmap = {'h':1, 'hu':2, 'hv':3, 'hm':4, + 'eta':8, 'B':9} + else: + raise InputError('Invalid qmap: %s' % qmap) # private attributes for those that are only created if # needed by the user: @@ -56,6 +67,7 @@ def __init__(self, fgout_grid, frameno=None): self._v = None self._s = None self._hss = None + self._hm = None # Define shortcuts to attributes of self.fgout_grid that are the same # for all frames (e.g. X,Y) to avoid storing grid for every frame. @@ -166,6 +178,8 @@ def eta(self): try: i_eta = q_out_vars.index(self.qmap['eta']) self._eta = self.q[i_eta,:,:] + print('+++qmap["eta"] = %i' % self.qmap["eta"]) + print('+++i_eta = %i' % i_eta) except: try: i_h = q_out_vars.index(self.qmap['h']) @@ -184,11 +198,16 @@ def B(self): try: i_B = q_out_vars.index(self.qmap['B']) self._B = self.q[i_B,:,:] + print('+++qmap["B"] = %i' % self.qmap["B"]) + print('+++i_B = %i' % i_B) except: try: i_h = q_out_vars.index(self.qmap['h']) i_eta = q_out_vars.index(self.qmap['eta']) self._B = self.q[i_eta,:,:] - self.q[i_h,:,:] + print('+++ computing B: i_h = %i, i_eta = %i' % (i_h,i_eta)) + print('+++qmap["h"] = %i' % self.qmap["h"]) + print('+++qmap["eta"] = %i' % self.qmap["eta"]) except: print('*** Could not find B or eta-h in q_out_vars') raise @@ -208,6 +227,21 @@ def hss(self): self._hss = self.h * self.s**2 return self._hss + @property + def hm(self): + """dclaw: h * mass fraction""" + if self._hm is None: + q_out_vars = self.fgout_grid.q_out_vars + try: + i_hm = q_out_vars.index(self.qmap['hm']) + self._hm = self.q[i_hm,:,:] + print('+++qmap["hm"] = %i' % self.qmap["hm"]) + print('+++i_hm = %i' % i_hm) + except: + print('*** Could not find hm in q_out_vars') + raise + return self._hm + class FGoutGrid(object): @@ -376,7 +410,7 @@ def read_fgout_grids_data(self, fgno=None, data_file='fgout_grids.data'): if fgout_input is None: raise ValueError('fgout grid fgno = %i not found in %s' \ - % (fgno, data_file)) + % (self.fgno, data_file)) lineno = 0 # next input line self.output_style = int(fgout_input[lineno].split()[lineno]) @@ -521,7 +555,7 @@ def write_to_fgout_data(self, fid): fid.write('\n') - def read_frame(self, frameno): + def read_frame(self, frameno, qmap='geoclaw'): """ Read a single frame of fgout data. """ @@ -545,7 +579,7 @@ def read_frame(self, frameno): state = fr.states[0] # only 1 AMR grid patch = state.patch - fgout_frame = FGoutFrame(self, frameno) + fgout_frame = FGoutFrame(self, frameno, qmap) fgout_frame.fgout_grid = self fgout_frame.q = state.q From 70ee91e85dbb40d3959f7577183b909b3b525c13 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Wed, 17 Jul 2024 17:53:45 -0700 Subject: [PATCH 49/58] Fix fgout_tools.py to support fgout.q_out_vars also for Boussinesq or D-Claw When instantiating fgout_tools.FGoutGrid object, new qmap parameter can be set to 'geoclaw', 'geoclaw-bouss', 'dclaw', or a custom dictionary. (Eventually add 'dclaw-bouss' option also). This dictionary has the form `{'h':1, 'hu':2, etc}` with mapping from fgout variable names to Fortran indices of q in the code creating the fgout output. --- src/python/geoclaw/fgout_tools.py | 129 +++++++++++++++++++++++------- 1 file changed, 101 insertions(+), 28 deletions(-) diff --git a/src/python/geoclaw/fgout_tools.py b/src/python/geoclaw/fgout_tools.py index dfedda3fc..c53a0240e 100644 --- a/src/python/geoclaw/fgout_tools.py +++ b/src/python/geoclaw/fgout_tools.py @@ -36,26 +36,11 @@ class FGoutFrame(object): and stored only when needed by the user. """ - def __init__(self, fgout_grid, frameno=None, qmap='geoclaw'): + def __init__(self, fgout_grid, frameno=None): self.fgout_grid = fgout_grid self.frameno = frameno self.t = None - # mapping from variable names to possible values in q_out_vars - # default for GeoClaw: - if type(qmap) is dict: - self.qmap = qmap - elif qmap == 'geoclaw': - self.qmap = {'h':1, 'hu':2, 'hv':3, 'eta':4, 'B':5} - elif qmap == 'geoclaw-bouss': - self.qmap = {'h':1, 'hu':2, 'hv':3, 'huc':4, 'hvc':5, - 'eta':4, 'B':5} - elif qmap == 'dclaw': - self.qmap = {'h':1, 'hu':2, 'hv':3, 'hm':4, - 'eta':8, 'B':9} - else: - raise InputError('Invalid qmap: %s' % qmap) - # private attributes for those that are only created if # needed by the user: self._h = None @@ -103,6 +88,10 @@ def extent_edges(self): @property def drytol(self): return self.fgout_grid.drytol + + @property + def qmap(self): + return self.fgout_grid.qmap # Define attributes such as h as @properties with lazy evaluation: # the corresponding array is created and stored only when first @@ -178,8 +167,8 @@ def eta(self): try: i_eta = q_out_vars.index(self.qmap['eta']) self._eta = self.q[i_eta,:,:] - print('+++qmap["eta"] = %i' % self.qmap["eta"]) - print('+++i_eta = %i' % i_eta) + #print('+++qmap["eta"] = %i' % self.qmap["eta"]) + #print('+++i_eta = %i' % i_eta) except: try: i_h = q_out_vars.index(self.qmap['h']) @@ -198,16 +187,16 @@ def B(self): try: i_B = q_out_vars.index(self.qmap['B']) self._B = self.q[i_B,:,:] - print('+++qmap["B"] = %i' % self.qmap["B"]) - print('+++i_B = %i' % i_B) + #print('+++qmap["B"] = %i' % self.qmap["B"]) + #print('+++i_B = %i' % i_B) except: try: i_h = q_out_vars.index(self.qmap['h']) i_eta = q_out_vars.index(self.qmap['eta']) self._B = self.q[i_eta,:,:] - self.q[i_h,:,:] - print('+++ computing B: i_h = %i, i_eta = %i' % (i_h,i_eta)) - print('+++qmap["h"] = %i' % self.qmap["h"]) - print('+++qmap["eta"] = %i' % self.qmap["eta"]) + #print('+++ computing B: i_h = %i, i_eta = %i' % (i_h,i_eta)) + #print('+++qmap["h"] = %i' % self.qmap["h"]) + #print('+++qmap["eta"] = %i' % self.qmap["eta"]) except: print('*** Could not find B or eta-h in q_out_vars') raise @@ -227,6 +216,32 @@ def hss(self): self._hss = self.h * self.s**2 return self._hss + @property + def huc(self): + """huc - Boussinesq correction to hu""" + if self._huc is None: + q_out_vars = self.fgout_grid.q_out_vars + try: + i_huc = q_out_vars.index(self.qmap['huc']) + self._huc = self.q[i_huc,:,:] + except: + print('*** Could not find huc in q_out_vars') + raise + return self._huc + + @property + def hvc(self): + """hvc - Boussinesq correction to hv""" + if self._hvc is None: + q_out_vars = self.fgout_grid.q_out_vars + try: + i_hvc = q_out_vars.index(self.qmap['hvc']) + self._hvc = self.q[i_hvc,:,:] + except: + print('*** Could not find hvc in q_out_vars') + raise + return self._hvc + @property def hm(self): """dclaw: h * mass fraction""" @@ -235,13 +250,52 @@ def hm(self): try: i_hm = q_out_vars.index(self.qmap['hm']) self._hm = self.q[i_hm,:,:] - print('+++qmap["hm"] = %i' % self.qmap["hm"]) - print('+++i_hm = %i' % i_hm) + #print('+++qmap["hm"] = %i' % self.qmap["hm"]) + #print('+++i_hm = %i' % i_hm) except: print('*** Could not find hm in q_out_vars') raise return self._hm + @property + def pb(self): + """dclaw variable """ + if self._pb is None: + q_out_vars = self.fgout_grid.q_out_vars + try: + i_pb = q_out_vars.index(self.qmap['pb']) + self._pb = self.q[i_pb,:,:] + except: + print('*** Could not find pb in q_out_vars') + raise + return self._pb + + @property + def hchi(self): + """dclaw variable """ + if self._hchi is None: + q_out_vars = self.fgout_grid.q_out_vars + try: + i_hchi = q_out_vars.index(self.qmap['hchi']) + self._hchi = self.q[i_hchi,:,:] + except: + print('*** Could not find hchi in q_out_vars') + raise + return self._hchi + + @property + def bdif(self): + """dclaw variable """ + if self._bdif is None: + q_out_vars = self.fgout_grid.q_out_vars + try: + i_bdif = q_out_vars.index(self.qmap['bdif']) + self._bdif = self.q[i_bdif,:,:] + except: + print('*** Could not find bdif in q_out_vars') + raise + return self._bdif + class FGoutGrid(object): @@ -250,9 +304,25 @@ class FGoutGrid(object): fgout input data and the output generated by a GeoClaw run. """ - def __init__(self,fgno=None,outdir='.',output_format=None): + def __init__(self,fgno=None,outdir='.',output_format=None, + qmap='geoclaw'): + # mapping from variable names to possible values in q_out_vars + if type(qmap) is dict: + self.qmap = qmap + elif qmap == 'geoclaw': + # default for GeoClaw: + self.qmap = {'h':1, 'hu':2, 'hv':3, 'eta':4, 'B':5} + elif qmap == 'geoclaw-bouss': + self.qmap = {'h':1, 'hu':2, 'hv':3, 'huc':4, 'hvc':5, + 'eta':4, 'B':5} + elif qmap == 'dclaw': + self.qmap = {'h':1, 'hu':2, 'hv':3, 'hm':4, 'pb':5, 'hchi':6, + 'bdif':7, 'eta':8, 'B':9} + else: + raise InputError('Invalid qmap: %s' % qmap) + # GeoClaw input values: self.id = '' # identifier, optional self.point_style = 2 # only option currently supported @@ -464,6 +534,9 @@ def read_fgout_grids_data(self, fgno=None, data_file='fgout_grids.data'): self.q_out_vars.append(int(token)) except: break + print('Found fgout grid q_out_vars = ',self.q_out_vars) + print('Using this mapping to fgout variable names: ') + print(' qmap = ',self.qmap) def write_to_fgout_data(self, fid): @@ -555,7 +628,7 @@ def write_to_fgout_data(self, fid): fid.write('\n') - def read_frame(self, frameno, qmap='geoclaw'): + def read_frame(self, frameno): """ Read a single frame of fgout data. """ @@ -579,7 +652,7 @@ def read_frame(self, frameno, qmap='geoclaw'): state = fr.states[0] # only 1 AMR grid patch = state.patch - fgout_frame = FGoutFrame(self, frameno, qmap) + fgout_frame = FGoutFrame(self, frameno) fgout_frame.fgout_grid = self fgout_frame.q = state.q From 7f4d10cc3bb03e28e058ab06c491254298958dca Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Thu, 18 Jul 2024 15:05:54 -0400 Subject: [PATCH 50/58] Update topo_module.f90 The `character` variables that were storing variable names and IDs for NetCDF topography reading were only length 10. This just makes them 64 characters long. Probably too long but not really a big deal. --- src/2d/shallow/topo_module.f90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/2d/shallow/topo_module.f90 b/src/2d/shallow/topo_module.f90 index 8bd1af908..2713e1694 100644 --- a/src/2d/shallow/topo_module.f90 +++ b/src/2d/shallow/topo_module.f90 @@ -439,7 +439,7 @@ subroutine read_topo_file(mx,my,topo_type,fname,xll,yll,topo) integer(kind=8) :: i, j, mtot ! NetCDF Support - character(len=10) :: direction, x_dim_name, x_var_name, y_dim_name, & + character(len=64) :: direction, x_dim_name, x_var_name, y_dim_name, & y_var_name, z_var_name, var_name ! character(len=1) :: axis_string real(kind=8), allocatable :: nc_buffer(:, :), xlocs(:), ylocs(:) @@ -744,8 +744,8 @@ subroutine read_topo_header(fname,topo_type,mx,my,xll,yll,xhi,yhi,dx,dy) logical, allocatable :: x_in_dom(:),y_in_dom(:) integer(kind=4) :: dim_ids(2), num_dims, var_type, num_vars, num_dims_tot integer(kind=4), allocatable :: var_ids(:) - character(len=10) :: var_name, x_var_name, y_var_name, z_var_name - character(len=10) :: x_dim_name, y_dim_name + character(len=64) :: var_name, x_var_name, y_var_name, z_var_name + character(len=64) :: x_dim_name, y_dim_name integer(kind=4) :: x_var_id, y_var_id, z_var_id, x_dim_id, y_dim_id verbose = .false. From 981ab8c1904a2b88c8554a7b6edb70cc1f6c29a3 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Wed, 24 Jul 2024 14:40:31 -0700 Subject: [PATCH 51/58] fix data_file -> data_path --- src/python/geoclaw/fgout_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/geoclaw/fgout_tools.py b/src/python/geoclaw/fgout_tools.py index c53a0240e..ab8d6f977 100644 --- a/src/python/geoclaw/fgout_tools.py +++ b/src/python/geoclaw/fgout_tools.py @@ -468,7 +468,7 @@ def read_fgout_grids_data(self, fgno=None, data_file='fgout_grids.data'): data_path = os.path.join(self.outdir, data_file) print('Reading fgout grid info from \n %s' % data_path) - with open(data_file) as filep: + with open(data_path) as filep: lines = filep.readlines() fgout_input = None for lineno,line in enumerate(lines): From 9378f12ffc5e876ecfec5cfc96ac7c0699ac4f96 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Mon, 29 Jul 2024 06:49:22 -0700 Subject: [PATCH 52/58] add FGoutGrid.read_fgout_grids_data_pre511 for reading legacy fgout_grids.data from before v5.11 --- src/python/geoclaw/fgout_tools.py | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/python/geoclaw/fgout_tools.py b/src/python/geoclaw/fgout_tools.py index ab8d6f977..4ea277ab7 100644 --- a/src/python/geoclaw/fgout_tools.py +++ b/src/python/geoclaw/fgout_tools.py @@ -538,6 +538,59 @@ def read_fgout_grids_data(self, fgno=None, data_file='fgout_grids.data'): print('Using this mapping to fgout variable names: ') print(' qmap = ',self.qmap) + def read_fgout_grids_data_pre511(self, fgno=None, + data_file='fgout_grids.data'): + """ + For backward compatibility, this reads fgout_grids.data files + in the format used prior to v5.11. + + Read input info for fgout grid number fgno from the data file + fgout_grids.data, which should have been created by setrun.py. + This file now contains info about all fgout grids. + """ + + if fgno is not None: + self.fgno = fgno + assert self.fgno is not None, '*** fgno must be set' + + data_path = os.path.join(self.outdir, data_file) + print('Reading fgout grid info from \n %s' % data_path) + + with open(data_path) as filep: + lines = filep.readlines() + fgout_input = None + for lineno,line in enumerate(lines): + if 'fgno' in line: + if int(line.split()[0]) == self.fgno: + fgout_input = lines[lineno+1:] + #print('Found line %i: %s' % (lineno,line)) + break + + if fgout_input is None: + raise ValueError('fgout grid fgno = %i not found in %s' \ + % (fgno, data_file)) + + self.tstart = float(fgout_input[0].split()[0]) + self.tend = float(fgout_input[1].split()[0]) + self.nout = int(fgout_input[2].split()[0]) + self.point_style = point_style = int(fgout_input[3].split()[0]) + output_format = int(fgout_input[4].split()[0]) + if output_format == 1: + self.output_format = 'ascii' + elif output_format == 3: + self.output_format = 'binary' + print('Reading input for fgno=%i, point_style = %i ' \ + % (self.fgno, self.point_style)) + if point_style == 2: + self.nx = nx = int(fgout_input[5].split()[0]) + self.ny = ny = int(fgout_input[5].split()[1]) + self.x1 = float(fgout_input[6].split()[0]) + self.y1 = float(fgout_input[6].split()[1]) + self.x2 = float(fgout_input[7].split()[0]) + self.y2 = float(fgout_input[7].split()[1]) + else: + raise NotImplementedError("fgout not implemented for point_style %i" \ + % point_style) def write_to_fgout_data(self, fid): """ From 665bbdadbb5146b9476d8de38aa920ce5b1fa06f Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Mon, 29 Jul 2024 07:05:26 -0700 Subject: [PATCH 53/58] add to doc string for pre511 --- src/python/geoclaw/fgout_tools.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/python/geoclaw/fgout_tools.py b/src/python/geoclaw/fgout_tools.py index 4ea277ab7..a648791aa 100644 --- a/src/python/geoclaw/fgout_tools.py +++ b/src/python/geoclaw/fgout_tools.py @@ -544,6 +544,11 @@ def read_fgout_grids_data_pre511(self, fgno=None, For backward compatibility, this reads fgout_grids.data files in the format used prior to v5.11. + In this case, the following values are used, as set in __init__(): + self.output_style = 1 + self.qmap = 'qmap' + self.q_out_vars = [1,2,3,4] # h,hu,hv,eta + Read input info for fgout grid number fgno from the data file fgout_grids.data, which should have been created by setrun.py. This file now contains info about all fgout grids. From f373e231005f4df496967c6462d9cc99decc0278 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Mon, 29 Jul 2024 07:33:31 -0700 Subject: [PATCH 54/58] print more helpful error msg if fgout_grids.data seems to be pre511 --- src/python/geoclaw/fgout_tools.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/python/geoclaw/fgout_tools.py b/src/python/geoclaw/fgout_tools.py index a648791aa..f4e0eb4d3 100644 --- a/src/python/geoclaw/fgout_tools.py +++ b/src/python/geoclaw/fgout_tools.py @@ -483,7 +483,14 @@ def read_fgout_grids_data(self, fgno=None, data_file='fgout_grids.data'): % (self.fgno, data_file)) lineno = 0 # next input line - self.output_style = int(fgout_input[lineno].split()[lineno]) + next_value = fgout_input[lineno].split()[lineno] # a string + next_value_int = ('.' not in next_value) # should be True in v5.11 + err_msg = '\n*** Expecting integer output_style next in %s' \ + % data_file \ + + '\n If this is fgout data from before v5.11, try using' \ + + '\n read_fgout_grids_data_pre511' + assert next_value_int, err_msg + self.output_style = int(next_value) lineno += 1 if (self.output_style == 1): # equally spaced times: From 6c849f2448339e5da303e3d50aeb0eb45608cdd0 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Mon, 29 Jul 2024 07:35:16 -0700 Subject: [PATCH 55/58] force user to call read_fgout_grids_data before read_frame --- src/python/geoclaw/fgout_tools.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/python/geoclaw/fgout_tools.py b/src/python/geoclaw/fgout_tools.py index f4e0eb4d3..71b30e4f1 100644 --- a/src/python/geoclaw/fgout_tools.py +++ b/src/python/geoclaw/fgout_tools.py @@ -704,6 +704,14 @@ def read_frame(self, frameno): fgoutX = self.X fgoutY = self.Y except: + msg = '\n*** Before reading frame, you must set FGoutGrid data,' \ + '\n*** Typically by calling read_fgout_grids_data' + raise ValueError(msg) + + # prior to v5.11, self.read_fgout_grids_data() called here + # rather than raising exception... + print(msg) + print('*** Calling read_fgout_grids_data...') self.read_fgout_grids_data() fgoutX = self.X fgoutY = self.Y From 178403e6d2b0e33a0a55c7000d1ea4c7a52cf4cb Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Mon, 29 Jul 2024 10:08:04 -0700 Subject: [PATCH 56/58] Fix chiles2010_fgmax-fgout scripts to make fgout animations Now need to call fgout_grid.read_fgout_grids_data() explicitly, but do not need to set format. --- .../tsunami/chile2010_fgmax-fgout/make_fgout_animation.py | 4 ++-- .../make_fgout_animation_with_transect.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py b/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py index 790e8b746..ab47c7588 100644 --- a/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py +++ b/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation.py @@ -34,7 +34,6 @@ fgno = 1 # which fgout grid outdir = '_output' -format = 'binary' # format of fgout grid output if 1: # use all fgout frames in outdir: @@ -48,7 +47,8 @@ fgframes = range(1,26) # frames of fgout solution to use in animation # Instantiate object for reading fgout frames: -fgout_grid = fgout_tools.FGoutGrid(fgno, outdir, format) +fgout_grid = fgout_tools.FGoutGrid(fgno, outdir) +fgout_grid.read_fgout_grids_data() # Plot one frame of fgout data and define the Artists that will need to diff --git a/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation_with_transect.py b/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation_with_transect.py index 4b32c40da..f2d19caad 100644 --- a/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation_with_transect.py +++ b/examples/tsunami/chile2010_fgmax-fgout/make_fgout_animation_with_transect.py @@ -38,7 +38,6 @@ fgno = 1 # which fgout grid outdir = '_output' -format = 'binary' # format of fgout grid output if 1: # use all fgout frames in outdir: @@ -52,7 +51,8 @@ fgframes = range(1,26) # frames of fgout solution to use in animation # Instantiate object for reading fgout frames: -fgout_grid = fgout_tools.FGoutGrid(fgno, outdir, format) +fgout_grid = fgout_tools.FGoutGrid(fgno, outdir) +fgout_grid.read_fgout_grids_data() # Plot one frame of fgout data and define the Artists that will need to From 1ec2512069f1b73a1ae7a42a1a5969e721040cb8 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Mon, 29 Jul 2024 10:09:55 -0700 Subject: [PATCH 57/58] Leave output_format as an argument to FGoutGrids.__init__ but not that this is ignored since it is read from fgout_grids.data. Also properly handle output_format == 2 for binary32. --- src/python/geoclaw/fgout_tools.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/python/geoclaw/fgout_tools.py b/src/python/geoclaw/fgout_tools.py index 71b30e4f1..4d2638259 100644 --- a/src/python/geoclaw/fgout_tools.py +++ b/src/python/geoclaw/fgout_tools.py @@ -321,9 +321,11 @@ def __init__(self,fgno=None,outdir='.',output_format=None, self.qmap = {'h':1, 'hu':2, 'hv':3, 'hm':4, 'pb':5, 'hchi':6, 'bdif':7, 'eta':8, 'B':9} else: - raise InputError('Invalid qmap: %s' % qmap) + raise ValueError('Invalid qmap: %s' % qmap) # GeoClaw input values: + # Many of these should be read from fgout_grids.data + # using read_fgout_grids_data before using read_frame self.id = '' # identifier, optional self.point_style = 2 # only option currently supported self.npts = None @@ -335,7 +337,10 @@ def __init__(self,fgno=None,outdir='.',output_format=None, self.nout = None self.fgno = fgno self.outdir = outdir + + # Note output_format will be reset by read_fgout_grids_data: self.output_format = output_format + self.q_out_vars = [1,2,3,4] # list of which components to print # default: h,hu,hv,eta (5=topo) self.nqout = None # number of vars to print out @@ -517,8 +522,13 @@ def read_fgout_grids_data(self, fgno=None, data_file='fgout_grids.data'): lineno += 1 if output_format == 1: self.output_format = 'ascii' + elif output_format == 2: + self.output_format = 'binary32' elif output_format == 3: self.output_format = 'binary' + else: + raise NotImplementedError("fgout not implemented for " \ + + "output_format %i" % output_format) print('Reading input for fgno=%i, point_style = %i ' \ % (self.fgno, self.point_style)) if point_style == 2: From 6e67973f95b6d15cdcd092bd4d40197bb28f4fd3 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Mon, 29 Jul 2024 11:03:37 -0700 Subject: [PATCH 58/58] fix qmap=='geoclaw-bouss' mapping --- src/python/geoclaw/fgout_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/geoclaw/fgout_tools.py b/src/python/geoclaw/fgout_tools.py index 4d2638259..7fdc47e19 100644 --- a/src/python/geoclaw/fgout_tools.py +++ b/src/python/geoclaw/fgout_tools.py @@ -316,7 +316,7 @@ def __init__(self,fgno=None,outdir='.',output_format=None, self.qmap = {'h':1, 'hu':2, 'hv':3, 'eta':4, 'B':5} elif qmap == 'geoclaw-bouss': self.qmap = {'h':1, 'hu':2, 'hv':3, 'huc':4, 'hvc':5, - 'eta':4, 'B':5} + 'eta':6, 'B':7} elif qmap == 'dclaw': self.qmap = {'h':1, 'hu':2, 'hv':3, 'hm':4, 'pb':5, 'hchi':6, 'bdif':7, 'eta':8, 'B':9}