Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions adcircpy/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,9 @@ def write(
script = DriverFile(self, nproc)
script.write(output_directory / driver, overwrite)

if self.wave_forcing is not None:
self.wave_forcing.write(output_directory,overwrite)

def import_stations(
self,
fort15: os.PathLike,
Expand Down
2 changes: 2 additions & 0 deletions adcircpy/forcing/waves/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from adcircpy.forcing.waves.base import WaveForcing
from adcircpy.forcing.waves.ww3 import WaveWatch3DataForcing
from adcircpy.forcing.waves.swan import SWANForcing

__all__ = [
'WaveForcing',
'WaveWatch3DataForcing',
'SWANForcing',
]
21 changes: 21 additions & 0 deletions adcircpy/forcing/waves/swan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from os import PathLike
from pathlib import Path

from adcircpy.forcing.waves import WaveForcing

import warnings

from adcircpy.warnings import warn_adcirc, UnsupportedFeatureWarning

class SWANForcing(WaveForcing):
def __init__(self, nrs: int = 3, \
interval_seconds: int = 600):
super().__init__(nrs=nrs, interval_seconds=interval_seconds)

def write(self, directory: PathLike, overwrite: bool = False):
txt=f'ADCIRCPy does not currently support writing '\
+f'coupled ADCIRC+SWAN input files (fort.26 and swaninit). '\
+f'User will have to create these files themselves to run '\
+f'padcswan.'
warn_adcirc(txt, UnsupportedFeatureWarning)
pass
33 changes: 26 additions & 7 deletions adcircpy/forcing/winds/best_track.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os
from os import PathLike
import pathlib
from typing import Union
from typing import Union, Optional, List

from matplotlib import pyplot
from matplotlib.axis import Axis
Expand All @@ -29,12 +29,17 @@ def __init__(
interval_seconds: int = None,
start_date: datetime = None,
end_date: datetime = None,
file_deck: str = 'b',
advisories: Optional[List[str]] = None,
*args,
**kwargs,
):
if nws is None:
nws = 20

if advisories is None:
advisories=['BEST']

valid_nws_values = [8, 19, 20]
assert (
nws in valid_nws_values
Expand All @@ -48,8 +53,8 @@ def __init__(
storm=storm,
start_date=start_date,
end_date=end_date,
file_deck='b',
advisories=['BEST'],
file_deck=file_deck,
advisories=advisories,
)
WindForcing.__init__(self, nws=nws, interval_seconds=interval_seconds)

Expand All @@ -61,18 +66,32 @@ def from_fort22(
interval_seconds: int = None,
start_date: datetime = None,
end_date: datetime = None,
file_deck: Optional[str] = None,
advisories: Optional[List[str]] = None,
) -> 'WindForcing':
instance = cls.from_file(path=fort22, start_date=start_date, end_date=end_date)
WindForcing.__init__(instance, nws=nws, interval_seconds=interval_seconds)
if file_deck is None:
file_deck='b'
if advisories is None:
advisories=['BEST']

instance = cls.from_file(path=fort22, start_date=start_date, \
end_date=end_date, file_deck=file_deck,\
advisories=advisories)

WindForcing.__init__(instance, nws=nws, \
interval_seconds=interval_seconds)
return instance

def summary(
self, output: Union[str, os.PathLike] = None, overwrite: bool = False,
):
min_storm_speed = numpy.min(self.data['speed'])
max_storm_speed = numpy.max(self.data['speed'])
track_length = self.distance
duration = self.duration
track_length=0
for key in self.distances.keys():
for key2 in self.distances[key].keys():
track_length+=self.distances[key][key2]
duration = self.duration.total_seconds()/86400 # seconds to days
min_central_pressure = numpy.min(self.data['central_pressure'])
max_wind_speed = numpy.max(self.data['max_sustained_wind_speed'])
start_loc = (self.data['longitude'][0], self.data['latitude'][0])
Expand Down
106 changes: 83 additions & 23 deletions adcircpy/fort15.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from adcircpy.mesh.mesh import AdcircMesh

from adcircpy.warnings import warn_adcirc, ModelSetupWarning

class StationType(Enum):
ELEVATION = 'NSTAE'
Expand Down Expand Up @@ -154,6 +155,9 @@ def __init__(self, mesh: AdcircMesh = None):
self._mesh = mesh
self._runtype = None

# initialize user-defined nameilsts
self._custom_namelists: dict[str, dict[str,str]] = {}

@property
def mesh(self) -> AdcircMesh:
return self._mesh
Expand All @@ -167,10 +171,10 @@ def fort15(self, runtype: str) -> str:
f.extend(
[
fort15_line(
self.RUNDES, 'RUNDES', '32 CHARACTER ALPHANUMERIC RUN DESCRIPTION'
self.RUNDES
),
fort15_line(
self.RUNID, 'RUNID', '24 CHARACTER ALPANUMERIC RUN IDENTIFICATION'
self.RUNID
),
fort15_line(f'{self.NFOVER}', 'NFOVER', 'NONFATAL ERROR OVERRIDE OPTION'),
fort15_line(
Expand Down Expand Up @@ -540,26 +544,34 @@ def fort15(self, runtype: str) -> str:
raise NotImplementedError('3D runs not yet implemented')
f.extend(
[
fort15_line(self.NCPROJ, 'NCPROJ', 'PROJECT TITLE'),
fort15_line(self.NCINST, 'NCINST', 'PROJECT INSTITUTION'),
fort15_line(self.NCSOUR, 'NCSOUR', 'PROJECT SOURCE'),
fort15_line(self.NCHIST, 'NCHIST', 'PROJECT HISTORY'),
fort15_line(self.NCREF, 'NCREF', 'PROJECT REFERENCES'),
fort15_line(self.NCCOM, 'NCCOM', 'PROJECT COMMENTS'),
fort15_line(self.NCHOST, 'NCHOST', 'PROJECT HOST'),
fort15_line(self.NCCONV, 'NCONV', 'CONVENTIONS'),
fort15_line(self.NCCONT, 'NCCONT', 'CONTACT INFORMATION'),
fort15_line(self.NCDATE, 'NCDATE', 'forcing start date'),
fort15_line(self.NCPROJ),
fort15_line(self.NCINST),
fort15_line(self.NCSOUR),
fort15_line(self.NCHIST),
fort15_line(self.NCREF),
fort15_line(self.NCCOM),
fort15_line(self.NCHOST),
fort15_line(self.NCCONV),
fort15_line(self.NCCONT),
fort15_line(self.NCDATE),
]
)
del self._outputs

def _format_namelist_value(value):
if isinstance(value, bool):
return 'T' if value else 'F'
elif isinstance(value, str):
return f'"{value}"'
else:
return str(value)

for name, namelist in self.namelists.items():
f.append(
f'&{name} '
+ ', '.join([f'{key}={value}' for key, value in namelist.items()])
+ ' \\'
)
f.append(f'! -- Begin {name} Namelist --')
f.append(f'&{name}')
for key, value in namelist.items():
f.append(f' {key} = {_format_namelist_value(value)},')
f.append(f"/ ! End {name} Namelist")
f.append("")
return '\n'.join(f)

Expand Down Expand Up @@ -637,8 +649,45 @@ def namelists(self) -> {str: {str: str}}:
'outputWindDrag': 'F',
'invertedBarometerOnElevationBoundary': 'T',
}

# Handle user-defined namelist values/additions
for name, overrides in self._custom_namelists.items():
namelists.setdefault(name, {}).update(overrides)

return namelists

def add_namelist(
self,
name: str,
entries: dict[str,str] | None=None
) -> None:
'''
Adds or updates a namelist to the fort.15 structure
'''
if name not in self._custom_namelists:
self._custom_namelists[name] = {}
if entries:
self._custom_namelists[name].update(entries)

def set_namelist_values(
self,
block: str,
key: str,
value: str | float | int
) -> None:
'''
Sets or updates a single value within a custom namelist
'''
if block not in self._custom_namelists:
self._custom_namelists[block] = {}
self._custom_namelists[block][key] = value

def clear_namelists(self) -> None:
'''
Deletes all custom namelist entries
'''
self._custom_namelists.clear()

def set_time_weighting_factors_in_gwce(self, A00: float, B00: float, C00: float):
A00 = float(A00)
B00 = float(B00)
Expand Down Expand Up @@ -962,7 +1011,7 @@ def timestep(self, timestep: float):
@property
def RUNDES(self) -> str:
try:
self.__RUNDES
return self.__RUNDES
except AttributeError:
return datetime.now().strftime('created on %Y-%m-%d %H:%M')

Expand All @@ -973,7 +1022,7 @@ def RUNDES(self, RUNDES: str):
@property
def RUNID(self) -> str:
try:
self.__RUNID
return self.__RUNID
except AttributeError:
return self.mesh.description

Expand Down Expand Up @@ -1409,7 +1458,12 @@ def NTIP(self) -> int:
try:
self.fort24
except AttributeError:
raise Exception('Must generate fort.24 file.')
warn_adcirc(
"NTIP is presently set to 2 but no self attraction"
" and loading data is present and thus no fort.24 will be"
" generated.",
ModelSetupWarning
)
return NTIP
except AttributeError:
return 1
Expand Down Expand Up @@ -1551,7 +1605,13 @@ def REFTIM(self, REFTIM: float):

@property
def WTIMINC(self) -> Union[int, str]:
if self.NWS in [8, 19, 20]:
if self.NWS in [8, 19]:
return (
f'{self.forcing_start_date:%Y %m %d %H} '
f'{self.wind_forcing.data["storm_number"].iloc[0]} '
f'{self.wind_forcing.BLADj} '
)
elif self.NWS in [20]:
return (
f'{self.forcing_start_date:%Y %m %d %H} '
f'{self.wind_forcing.data["storm_number"].iloc[0]} '
Expand Down Expand Up @@ -1582,7 +1642,7 @@ def RNDAY(self) -> int:
RNDAY = self.end_date - self.start_date
else:
RNDAY = self.end_date - self.forcing_start_date
return RNDAY / timedelta(days=1)
return np.floor((RNDAY / timedelta(days=1))*10)/10

@property
def DRAMP(self) -> str:
Expand Down Expand Up @@ -2569,7 +2629,7 @@ def NCCONT(self, NCCONT: str):

@property
def NCDATE(self) -> str:
return f'{self.forcing_start_date:%Y-%m-%d %H:%M}'
return f'{self.forcing_start_date:%Y-%m-%d %H:%M:%S}'

@property
def FortranNamelists(self) -> str:
Expand Down
27 changes: 27 additions & 0 deletions adcircpy/mesh/fort14.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,33 @@ def open(cls, path, crs=None):
_grd['nodes'].iloc[:, 2:] *= -1
return cls(**_grd)

def update_bathymetry(self, new_values):
"""
Update the bathymetry (values column) of the Fort14 mesh.

Parameters
----------
new_values : array-like
Array of bathymetry values aligned to the node index order.
"""
if len(new_values) != len(self._values):
raise ValueError("new_values must be same length as number of nodes")

# update internal values DataFrame
self._values.iloc[:, 0] = new_values

# keep 'nodes' table synchronized
self.nodes.iloc[:, 2] = new_values

@property
def bathymetry(self):
'''Returns the primary bathymetry column as a numpy array'''
return self._values.iloc[:,0].to_numpy()

@bathymetry.setter
def bathymetry(self, new_values):
self.update_bathymetry(new_values)

def write(self, path, overwrite=False, format='fort.14'):
if format in ['fort.14']:
_grd = self.to_dict()
Expand Down
35 changes: 35 additions & 0 deletions adcircpy/warnings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import warnings

class ADCIRCPyWarning(UserWarning):
'''
base adcircpy warning class
'''
pass

class UnsupportedFeatureWarning(ADCIRCPyWarning):
'''
Feature exists in ADCIRC but is not yet supported by adcircpy
'''
pass

class ModelSetupWarning(ADCIRCPyWarning):
'''
Indicates a configuration or setup issue in adcircpy inputs
'''
pass

def warn_adcirc(message, category=ADCIRCPyWarning, stacklevel=2):
'''
Prints a standardized warning message

Parameters
----------
message: str
The warning message to display
category: Warning subclass, optional
The type of warning to issue (defaults to ADCIRCPyWarning)
stacklevel : int, optional
The stack level for the warning location
'''
clean_message = (message.strip()).rstrip(".") + "."
warnings.warn(clean_message, category, stacklevel=stacklevel)