diff --git a/client/initialize_adcirc.py b/client/initialize_adcirc.py index f5d7dcaa..72d92f61 100644 --- a/client/initialize_adcirc.py +++ b/client/initialize_adcirc.py @@ -255,7 +255,7 @@ def main(): fort13_path=mesh_directory / 'fort.13', fort14_path=mesh_directory / 'fort.14', modeled_start_time=modeled_start_time, - modeled_end_time=modeled_start_time + modeled_duration, + modeled_duration=modeled_duration, modeled_timestep=modeled_timestep, nems_interval=nems_interval, nems_connections=None, @@ -278,7 +278,7 @@ def main(): fort13_path=mesh_directory / 'fort.13', fort14_path=mesh_directory / 'fort.14', modeled_start_time=modeled_start_time, - modeled_end_time=modeled_start_time + modeled_duration, + modeled_duration=modeled_duration, modeled_timestep=modeled_timestep, tidal_spinup_duration=tidal_spinup_duration, platform=platform, diff --git a/coupledmodeldriver/configure/base.py b/coupledmodeldriver/configure/base.py index 46ca3a23..b9e381a7 100644 --- a/coupledmodeldriver/configure/base.py +++ b/coupledmodeldriver/configure/base.py @@ -10,6 +10,7 @@ import nemspy from nemspy import ModelingSystem from nemspy.model.base import ModelEntry +from pyschism.server import ServerConfig from coupledmodeldriver.platforms import Platform from coupledmodeldriver.script import SlurmEmailType @@ -212,6 +213,7 @@ class SlurmJSON(ConfigurationJSON): 'path_prefix': Path, 'extra_commands': [str], 'launcher': str, + 'executable': Path, 'nodes': int, } @@ -230,6 +232,7 @@ def __init__( path_prefix: Path = None, extra_commands: [str] = None, launcher: str = None, + executable: PathLike = None, nodes: int = None, **kwargs, ): @@ -256,6 +259,7 @@ def __init__( self['path_prefix'] = path_prefix self['extra_commands'] = extra_commands self['launcher'] = launcher + self['executable'] = executable self['nodes'] = nodes if self['email_type'] is None: @@ -302,6 +306,15 @@ def from_adcircpy(cls, slurm_config: SlurmConfig): instance['filename'] = slurm_config._filename + @property + def to_pyschism(self) -> ServerConfig: + return ServerConfig( + nproc=self.tasks, + symlink_outputs=None, + schism_binary=self['executable'], + mpi_launcher=self['launcher'], + ) + class ModelDriverJSON(ConfigurationJSON): name = 'ModelDriver' @@ -364,7 +377,7 @@ class NEMSJSON(ConfigurationJSON): field_types = { 'executable': Path, 'modeled_start_time': datetime, - 'modeled_end_time': datetime, + 'modeled_duration': timedelta, 'interval': timedelta, 'models': [ModelEntry], 'connections': [[str]], @@ -376,7 +389,7 @@ def __init__( self, executable: PathLike, modeled_start_time: datetime, - modeled_end_time: datetime, + modeled_duration: timedelta, interval: timedelta = None, models: [ModelEntry] = None, connections: [[str]] = None, @@ -392,7 +405,7 @@ def __init__( self['executable'] = executable self['modeled_start_time'] = modeled_start_time - self['modeled_end_time'] = modeled_end_time + self['modeled_duration'] = modeled_duration self['interval'] = interval self['models'] = models self['connections'] = connections @@ -403,7 +416,7 @@ def __init__( def nemspy_modeling_system(self) -> ModelingSystem: modeling_system = ModelingSystem( start_time=self['modeled_start_time'], - end_time=self['modeled_end_time'], + end_time=self['modeled_start_time'] + self['modeled_duration'], interval=self['interval'], **{model.model_type.value.lower(): model for model in self['models']}, ) diff --git a/coupledmodeldriver/configure/configure.py b/coupledmodeldriver/configure/configure.py index 2d58661f..b37c3963 100644 --- a/coupledmodeldriver/configure/configure.py +++ b/coupledmodeldriver/configure/configure.py @@ -8,7 +8,11 @@ from nemspy.model.base import ModelEntry from coupledmodeldriver.configure.base import ConfigurationJSON, ModelDriverJSON, NEMSCapJSON -from coupledmodeldriver.configure.forcings.base import ADCIRCPY_FORCING_CLASSES, ForcingJSON +from coupledmodeldriver.configure.forcings.base import ( + ADCIRCPY_FORCING_CLASSES, + ForcingJSON, + PYSCHISM_FORCING_CLASSES, +) class RunConfiguration(ABC): @@ -157,4 +161,6 @@ def from_user_input(value: Any) -> ConfigurationJSON: value = ConfigurationJSON.from_string(value) elif isinstance(value, ADCIRCPY_FORCING_CLASSES): value = ForcingJSON.from_adcircpy(value) + elif isinstance(value, PYSCHISM_FORCING_CLASSES): + value = ForcingJSON.from_pyschism(value) return value diff --git a/coupledmodeldriver/configure/forcings/base.py b/coupledmodeldriver/configure/forcings/base.py index 6d66610b..e8b40f21 100644 --- a/coupledmodeldriver/configure/forcings/base.py +++ b/coupledmodeldriver/configure/forcings/base.py @@ -3,9 +3,9 @@ from os import PathLike from pathlib import Path import sys -from typing import Any +from typing import Any, Union -from adcircpy import Tides +from adcircpy import Tides as adcircpy_Tides from adcircpy.forcing.base import Forcing from adcircpy.forcing.tides import HAMTIDE from adcircpy.forcing.tides.tides import TidalSource @@ -14,11 +14,18 @@ from adcircpy.forcing.winds.atmesh import AtmosphericMeshForcing from adcircpy.forcing.winds.owi import OwiForcing from nemspy.model import AtmosphericMeshEntry, WaveWatch3MeshEntry +from pyschism.enums import GFSProduct +from pyschism.forcing import GlobalForecastSystem, \ + Tides as pyschism_Tides +from pyschism.forcing.hydrology import NationalWaterModel from coupledmodeldriver.configure.base import AttributeJSON, \ ConfigurationJSON, NEMSCapJSON from coupledmodeldriver.utilities import LOGGER +ADCIRCPY_FORCING_CLASSES = (adcircpy_Tides, Forcing) +PYSCHISM_FORCING_CLASSES = (pyschism_Tides, NationalWaterModel, GlobalForecastSystem) + ADCIRCPY_FORCINGS = { 'Tides': 'TidalForcingJSON', 'AtmosphericMeshForcing': 'ATMESHForcingJSON', @@ -27,7 +34,11 @@ 'WaveWatch3DataForcing': 'WW3DATAForcingJSON', } -ADCIRCPY_FORCING_CLASSES = (Forcing, Tides) +PYSCHISM_FORCINGS = { + 'Tides': 'TidalForcingJSON', + 'GlobalForecastSystem': 'WindForcingJSON', + 'NationalWaterModel': 'NWMForcingJSON', +} class ForcingJSON(ConfigurationJSON, ABC): @@ -36,9 +47,17 @@ class ForcingJSON(ConfigurationJSON, ABC): def adcircpy_forcing(self) -> Forcing: raise NotImplementedError + @property + @abstractmethod + def pyschism_forcing(self): + raise NotImplementedError + def to_adcircpy(self) -> Forcing: return self.adcircpy_forcing + def to_pyschism(self) -> Union[PYSCHISM_FORCING_CLASSES]: + return self.pyschism_forcing + @classmethod @abstractmethod def from_adcircpy(cls, forcing: Forcing) -> 'ForcingJSON': @@ -51,6 +70,18 @@ def from_adcircpy(cls, forcing: Forcing) -> 'ForcingJSON': else: raise NotImplementedError() + @classmethod + @abstractmethod + def from_pyschism(cls, forcing: Union[PYSCHISM_FORCING_CLASSES]) -> 'ForcingJSON': + forcing_class_name = forcing.__class__.__name__ + if forcing_class_name in PYSCHISM_FORCING_CLASSES: + configuration_class = getattr( + sys.modules[__name__], ADCIRCPY_FORCINGS[forcing_class_name] + ) + return configuration_class.from_adcircpy(forcing) + else: + raise NotImplementedError() + class TimestepForcingJSON(ForcingJSON, ABC): default_modeled_timestep: timedelta @@ -113,7 +144,7 @@ def __init__( @property def adcircpy_forcing(self) -> Forcing: - tides = Tides(tidal_source=self['tidal_source'], resource=self['resource']) + tides = adcircpy_Tides(tidal_source=self['tidal_source'], resource=self['resource']) constituents = [constituent.capitalize() for constituent in self['constituents']] @@ -139,8 +170,12 @@ def adcircpy_forcing(self) -> Forcing: self['constituents'] = list(tides.active_constituents) return tides + @property + def pyschism_forcing(self): + raise NotImplementedError + @classmethod - def from_adcircpy(cls, forcing: Tides) -> 'TidalForcingJSON': + def from_adcircpy(cls, forcing: adcircpy_Tides) -> 'TidalForcingJSON': # TODO: workaround for this issue: https://github.com/JaimeCalzadaNOAA/adcircpy/pull/70#discussion_r607245713 resource = forcing.tidal_dataset.path if resource == HAMTIDE.OPENDAP_URL: @@ -152,6 +187,10 @@ def from_adcircpy(cls, forcing: Tides) -> 'TidalForcingJSON': constituents=forcing.active_constituents, ) + @classmethod + def from_pyschism(cls, forcing: Union[PYSCHISM_FORCING_CLASSES]) -> 'ForcingJSON': + raise NotImplementedError + class WindForcingJSON(ForcingJSON, ABC): default_nws: int @@ -245,6 +284,10 @@ def adcircpy_forcing(self) -> BestTrackForcing: return forcing + @property + def pyschism_forcing(self): + raise NotImplementedError + @classmethod def from_adcircpy(cls, forcing: BestTrackForcing) -> 'BestTrackForcingJSON': return cls( @@ -254,6 +297,10 @@ def from_adcircpy(cls, forcing: BestTrackForcing) -> 'BestTrackForcingJSON': end_date=forcing.end_date, ) + @classmethod + def from_pyschism(cls, forcing: Union[PYSCHISM_FORCING_CLASSES]) -> 'ForcingJSON': + raise NotImplementedError + @classmethod def from_fort22(cls, filename: PathLike, nws: int = None): return cls.from_adcircpy(BestTrackForcing.from_fort22(filename, nws)) @@ -273,10 +320,18 @@ def __init__(self, modeled_timestep: timedelta = None, **kwargs): def adcircpy_forcing(self) -> OwiForcing: return OwiForcing(interval_seconds=self['modeled_timestep'] / timedelta(seconds=1)) + @property + def pyschism_forcing(self): + raise NotImplementedError + @classmethod def from_adcircpy(cls, forcing: OwiForcing) -> 'OWIForcingJSON': return cls(modeled_timestep=timedelta(seconds=forcing.interval)) + @classmethod + def from_pyschism(cls, forcing: Union[PYSCHISM_FORCING_CLASSES]) -> 'ForcingJSON': + raise NotImplementedError + class ATMESHForcingJSON(WindForcingJSON, FileForcingJSON, TimestepForcingJSON, NEMSCapJSON): name = 'ATMESH' @@ -309,12 +364,20 @@ def adcircpy_forcing(self) -> Forcing: interval_seconds=self['modeled_timestep'] / timedelta(seconds=1), ) + @property + def pyschism_forcing(self): + raise NotImplementedError + @classmethod def from_adcircpy(cls, forcing: AtmosphericMeshForcing) -> 'ATMESHForcingJSON': return cls( resource=forcing.filename, nws=forcing.NWS, modeled_timestep=forcing.interval, ) + @classmethod + def from_pyschism(cls, forcing: Union[PYSCHISM_FORCING_CLASSES]) -> 'ForcingJSON': + raise NotImplementedError + @property def nemspy_entry(self) -> AtmosphericMeshEntry: return AtmosphericMeshEntry( @@ -322,6 +385,39 @@ def nemspy_entry(self) -> AtmosphericMeshEntry: ) +class GFSForcingJSON(FileForcingJSON): + field_types = {'product': GFSProduct} + + def __init__( + self, resource: PathLike, product: GFSProduct, **kwargs, + ): + if 'fields' not in kwargs: + kwargs['fields'] = {} + kwargs['fields'].update(GFSForcingJSON.field_types) + + FileForcingJSON.__init__( + self, resource=resource, **kwargs, + ) + + self['product'] = product + + @property + def adcircpy_forcing(self) -> Forcing: + raise NotImplementedError + + @property + def pyschism_forcing(self) -> GlobalForecastSystem: + return GlobalForecastSystem(self['product']) + + @classmethod + def from_adcircpy(cls, forcing: Forcing) -> 'GFSForcingJSON': + raise NotImplementedError + + @classmethod + def from_pyschism(cls, forcing: GlobalForecastSystem) -> 'GFSForcingJSON': + return cls(resource=forcing.resource, product=forcing.product) + + class WaveForcingJSON(ForcingJSON, ABC): default_nrs: int field_types = {'nrs': int} @@ -369,14 +465,62 @@ def adcircpy_forcing(self) -> Forcing: interval_seconds=self['modeled_timestep'], ) + @property + def pyschism_forcing(self): + raise NotImplementedError + @classmethod def from_adcircpy(cls, forcing: WaveWatch3DataForcing) -> 'WW3DATAForcingJSON': return cls( resource=forcing.filename, nrs=forcing.NRS, modeled_timestep=forcing.interval, ) + @classmethod + def from_pyschism(cls, forcing: Union[PYSCHISM_FORCING_CLASSES]) -> 'ForcingJSON': + raise NotImplementedError + @property def nemspy_entry(self) -> WaveWatch3MeshEntry: return WaveWatch3MeshEntry( filename=self['resource'], processors=self['processors'], **self['nems_parameters'] ) + + +class HydrologyForcingJSON(ForcingJSON, ABC): + field_types = {} + + def __init__(self, **kwargs): + if 'fields' not in kwargs: + kwargs['fields'] = {} + kwargs['fields'].update(HydrologyForcingJSON.field_types) + + ForcingJSON.__init__(self, **kwargs) + + +class NWMForcingJSON(HydrologyForcingJSON): + field_types = {'aggregation_radius': float} + + def __init__(self, aggregation_radius: float = None, **kwargs): + if 'fields' not in kwargs: + kwargs['fields'] = {} + kwargs['fields'].update(NWMForcingJSON.field_types) + + ForcingJSON.__init__(self, **kwargs) + + self['aggregation_radius'] = aggregation_radius + + @property + def adcircpy_forcing(self) -> Forcing: + raise NotImplementedError + + @property + def pyschism_forcing(self) -> NationalWaterModel: + return NationalWaterModel(self['aggregation_radius']) + + @classmethod + def from_adcircpy(cls, forcing: Forcing) -> 'ForcingJSON': + raise NotImplementedError + + @classmethod + def from_pyschism(cls, forcing: NationalWaterModel) -> 'ForcingJSON': + return cls(forcing.aggregation_radius) diff --git a/coupledmodeldriver/configure/models.py b/coupledmodeldriver/configure/models.py index 907e5d6b..675e4257 100644 --- a/coupledmodeldriver/configure/models.py +++ b/coupledmodeldriver/configure/models.py @@ -1,4 +1,5 @@ from abc import ABC +from datetime import datetime, timedelta from os import PathLike from pathlib import Path @@ -27,11 +28,25 @@ def __init__(self, executable: PathLike, **kwargs): class CirculationModelJSON(ModelJSON, ABC): field_types = { 'mesh_files': [Path], + 'modeled_start_time': datetime, + 'modeled_duration': timedelta, + 'modeled_timestep': timedelta, } - def __init__(self, mesh_files: [PathLike], executable: PathLike, **kwargs): + def __init__( + self, + mesh_files: [PathLike], + modeled_start_time: datetime, + modeled_duration: timedelta, + modeled_timestep: timedelta, + executable: PathLike, + **kwargs, + ): """ :param mesh_files: file path to mesh + :param modeled_start_time: start time in model run + :param modeled_duration: duration of model run + :param modeled_timestep: time interval between model steps :param executable: file path to model executable """ @@ -42,3 +57,10 @@ def __init__(self, mesh_files: [PathLike], executable: PathLike, **kwargs): ModelJSON.__init__(self, executable, **kwargs) self['mesh_files'] = mesh_files + self['modeled_start_time'] = modeled_start_time + self['modeled_duration'] = modeled_duration + self['modeled_timestep'] = modeled_timestep + + @property + def modeled_end_time(self) -> datetime: + return self['modeled_start_time'] + self['modeled_duration'] diff --git a/coupledmodeldriver/generate/adcirc/base.py b/coupledmodeldriver/generate/adcirc/base.py index e619e3e4..9a8baab6 100644 --- a/coupledmodeldriver/generate/adcirc/base.py +++ b/coupledmodeldriver/generate/adcirc/base.py @@ -129,7 +129,7 @@ class ADCIRCJSON(CirculationModelJSON, NEMSCapJSON, AttributeJSON): field_types = { 'adcprep_executable': Path, 'modeled_start_time': datetime, - 'modeled_end_time': datetime, + 'modeled_duration': timedelta, 'modeled_timestep': timedelta, 'tidal_spinup_duration': timedelta, 'tidal_spinup_timestep': timedelta, @@ -153,11 +153,11 @@ def __init__( executable: PathLike, adcprep_executable: PathLike, modeled_start_time: datetime, - modeled_end_time: datetime, + modeled_duration: timedelta, modeled_timestep: timedelta, tidal_spinup_duration: timedelta = None, tidal_spinup_timestep: timedelta = None, - forcings: [Forcing] = None, + forcings: [ForcingJSON] = None, source_filename: PathLike = None, slurm_configuration: SlurmJSON = None, use_original_mesh: bool = False, @@ -183,11 +183,11 @@ def __init__( :param executable: file path to `adcirc` or `NEMS.x` :param adcprep_executable: file path to `adcprep` :param modeled_start_time: start time in model run - :param modeled_end_time: edn time in model run + :param modeled_duration: duration of model run :param modeled_timestep: time interval between model steps :param tidal_spinup_duration: tidal spinup duration for ADCIRC coldstart :param tidal_spinup_timestep: tidal spinup modeled time interval for ADCIRC coldstart - :param forcings: list of Forcing objects to apply to the mesh + :param forcings: list of forcing configurations to apply to the mesh :param source_filename: path to modulefile to `source` :param slurm_configuration: Slurm configuration object :param use_original_mesh: whether to symlink / copy original mesh instead of rewriting with `adcircpy` @@ -214,7 +214,13 @@ def __init__( kwargs['fields'].update(ADCIRCJSON.field_types) CirculationModelJSON.__init__( - self, mesh_files=mesh_files, executable=executable, **kwargs, + self, + mesh_files=mesh_files, + modeled_start_time=modeled_start_time, + modeled_duration=modeled_duration, + modeled_timestep=modeled_timestep, + executable=executable, + **kwargs, ) NEMSCapJSON.__init__( self, processors=processors, nems_parameters=nems_parameters, **kwargs @@ -222,9 +228,6 @@ def __init__( AttributeJSON.__init__(self, attributes=attributes, **kwargs) self['adcprep_executable'] = adcprep_executable - self['modeled_start_time'] = modeled_start_time - self['modeled_end_time'] = modeled_end_time - self['modeled_timestep'] = modeled_timestep self['tidal_spinup_duration'] = tidal_spinup_duration self['tidal_spinup_timestep'] = tidal_spinup_timestep self['source_filename'] = source_filename @@ -323,7 +326,7 @@ def adcircpy_mesh(self) -> AdcircMesh: for adcircpy_forcing in self.adcircpy_forcings: if isinstance(adcircpy_forcing, (Tides, BestTrackForcing)): adcircpy_forcing.start_date = self['modeled_start_time'] - adcircpy_forcing.end_date = self['modeled_end_time'] + adcircpy_forcing.end_date = self.modeled_end_time if ( isinstance(adcircpy_forcing, Tides) @@ -361,7 +364,7 @@ def adcircpy_driver(self) -> AdcircRun: driver = AdcircRun( mesh=self.adcircpy_mesh, start_date=self['modeled_start_time'], - end_date=self['modeled_end_time'], + end_date=self.modeled_end_time, spinup_time=self['tidal_spinup_duration'], server_config=self.slurm_configuration.to_adcircpy() if self.slurm_configuration is not None @@ -398,7 +401,7 @@ def adcircpy_driver(self) -> AdcircRun: driver.set_elevation_surface_output( sampling_rate=self['surface_output_interval'], start=self['modeled_start_time'], - end=self['modeled_end_time'], + end=self.modeled_end_time, spinup=spinup_output_interval, spinup_start=spinup_start, spinup_end=spinup_end, @@ -407,7 +410,7 @@ def adcircpy_driver(self) -> AdcircRun: driver.set_elevation_stations_output( sampling_rate=self['stations_output_interval'], start=self['modeled_start_time'], - end=self['modeled_end_time'], + end=self.modeled_end_time, spinup=spinup_output_interval, spinup_start=spinup_start, spinup_end=spinup_end, @@ -418,7 +421,7 @@ def adcircpy_driver(self) -> AdcircRun: driver.set_velocity_surface_output( sampling_rate=self['surface_output_interval'], start=self['modeled_start_time'], - end=self['modeled_end_time'], + end=self.modeled_end_time, spinup=spinup_output_interval, spinup_start=spinup_start, spinup_end=spinup_end, @@ -427,7 +430,7 @@ def adcircpy_driver(self) -> AdcircRun: driver.set_velocity_stations_output( sampling_rate=self['stations_output_interval'], start=self['modeled_start_time'], - end=self['modeled_end_time'], + end=self.modeled_end_time, spinup=spinup_output_interval, spinup_start=spinup_start, spinup_end=spinup_end, @@ -438,7 +441,7 @@ def adcircpy_driver(self) -> AdcircRun: driver.set_concentration_surface_output( sampling_rate=self['surface_output_interval'], start=self['modeled_start_time'], - end=self['modeled_end_time'], + end=self.modeled_end_time, spinup=spinup_output_interval, spinup_start=spinup_start, spinup_end=spinup_end, @@ -447,7 +450,7 @@ def adcircpy_driver(self) -> AdcircRun: driver.set_concentration_stations_output( sampling_rate=self['stations_output_interval'], start=self['modeled_start_time'], - end=self['modeled_end_time'], + end=self.modeled_end_time, spinup=spinup_output_interval, spinup_start=spinup_start, spinup_end=spinup_end, @@ -458,7 +461,7 @@ def adcircpy_driver(self) -> AdcircRun: driver.set_meteorological_surface_output( sampling_rate=self['surface_output_interval'], start=self['modeled_start_time'], - end=self['modeled_end_time'], + end=self.modeled_end_time, spinup=spinup_output_interval, spinup_start=spinup_start, spinup_end=spinup_end, @@ -467,7 +470,7 @@ def adcircpy_driver(self) -> AdcircRun: driver.set_meteorological_stations_output( sampling_rate=self['stations_output_interval'], start=self['modeled_start_time'], - end=self['modeled_end_time'], + end=self.modeled_end_time, spinup=spinup_output_interval, spinup_start=spinup_start, spinup_end=spinup_end, diff --git a/coupledmodeldriver/generate/adcirc/configure.py b/coupledmodeldriver/generate/adcirc/configure.py index 1a8d5eb3..7c245a58 100644 --- a/coupledmodeldriver/generate/adcirc/configure.py +++ b/coupledmodeldriver/generate/adcirc/configure.py @@ -46,7 +46,7 @@ def __init__( fort13_path: PathLike, fort14_path: PathLike, modeled_start_time: datetime, - modeled_end_time: datetime, + modeled_duration: timedelta, modeled_timestep: timedelta, tidal_spinup_duration: timedelta = None, platform: Platform = None, @@ -66,13 +66,13 @@ def __init__( :param fort13_path: path to input mesh nodes `fort.13` :param fort14_path: path to input mesh attributes `fort.14` :param modeled_start_time: start time within the modeled system - :param modeled_end_time: end time within the modeled system + :param modeled_duration: duration within the modeled system :param modeled_timestep: time interval within the modeled system - :param adcirc_processors: numbers of processors to assign for ADCIRC - :param platform: HPC platform for which to configure :param tidal_spinup_duration: spinup time for ADCIRC tidal coldstart + :param platform: HPC platform for which to configure :param perturbations: dictionary of runs encompassing run names to parameter values :param forcings: list of forcing configurations to connect to ADCIRC + :param adcirc_processors: numbers of processors to assign for ADCIRC :param slurm_job_duration: wall clock time of job :param slurm_partition: Slurm partition :param slurm_email_address: email address to send Slurm notifications @@ -115,7 +115,7 @@ def __init__( executable=adcirc_executable, adcprep_executable=adcprep_executable, modeled_start_time=modeled_start_time, - modeled_end_time=modeled_end_time, + modeled_duration=modeled_duration, modeled_timestep=modeled_timestep, tidal_spinup_duration=tidal_spinup_duration, source_filename=source_filename, @@ -227,7 +227,7 @@ def __init__( fort13_path: PathLike, fort14_path: PathLike, modeled_start_time: datetime, - modeled_end_time: datetime, + modeled_duration: timedelta, modeled_timestep: timedelta, nems_interval: timedelta, nems_connections: [str], @@ -251,7 +251,7 @@ def __init__( fort13_path=fort13_path, fort14_path=fort14_path, modeled_start_time=modeled_start_time, - modeled_end_time=modeled_end_time, + modeled_duration=modeled_duration, modeled_timestep=modeled_timestep, tidal_spinup_duration=tidal_spinup_duration, platform=platform, @@ -268,7 +268,7 @@ def __init__( nems = NEMSJSON( executable=nems_executable, modeled_start_time=modeled_start_time, - modeled_end_time=modeled_end_time, + modeled_duration=modeled_duration, interval=nems_interval, models=self.nemspy_entries, connections=nems_connections, diff --git a/coupledmodeldriver/generate/schism/base.py b/coupledmodeldriver/generate/schism/base.py index f189b63d..e2fb31a3 100644 --- a/coupledmodeldriver/generate/schism/base.py +++ b/coupledmodeldriver/generate/schism/base.py @@ -1,97 +1,166 @@ from datetime import datetime, timedelta from os import PathLike from pathlib import Path -from typing import Any +from typing import Union from pyproj import CRS from pyschism import ModelDomain, ModelDriver, Stations -from pyschism.enums import Stratification +from pyschism.enums import IofHydroVariables, Stratification +from pyschism.forcing import Hydrology, Tides +from pyschism.forcing.atmosphere import NWS2 +from pyschism.forcing.atmosphere.nws.nws2.sflux import SfluxDataset from pyschism.mesh import Fgrid, Hgrid, Vgrid -from pyschism.server import ServerConfig from coupledmodeldriver.configure import CirculationModelJSON -from coupledmodeldriver.configure.base import AttributeJSON, \ - NEMSCapJSON +from coupledmodeldriver.configure.base import NEMSCapJSON, SlurmJSON +from coupledmodeldriver.configure.configure import from_user_input +from coupledmodeldriver.configure.forcings.base import ForcingJSON, PYSCHISM_FORCING_CLASSES -SCHISM_ATTRIBUTES = {} - -class SCHISMJSON(CirculationModelJSON, NEMSCapJSON, AttributeJSON): +class SCHISMJSON(CirculationModelJSON, NEMSCapJSON): name = 'SCHISM' default_filename = f'configure_schism.json' default_processors = 11 - default_attributes = SCHISM_ATTRIBUTES field_types = { + 'modeled_start_time': datetime, + 'modeled_duration': timedelta, 'modeled_timestep': timedelta, - 'rnday': timedelta, 'tidal_spinup_duration': timedelta, - 'start_date': datetime, - 'ibc': Stratification, - 'drampbc': timedelta, + 'tidal_bc_spinup_duration': timedelta, + 'tidal_bc_cutoff_depth': float, + 'stratification': Stratification, + 'hotstart_output_interval': timedelta, + 'hotstart_combination_executable': Path, + 'output_surface': bool, + 'surface_output_interval': timedelta, + 'surface_output_new_file_skips': int, + 'surface_output_variables': {str: bool}, + 'output_stations': bool, + 'stations_output_interval': timedelta, 'stations_file_path': Stations, - 'stations_frequency': timedelta, 'stations_crs': CRS, - 'nhot_write': timedelta, - 'server_config': ServerConfig, - 'combine_hotstart': Path, - 'cutoff_depth': float, - 'output_frequency': timedelta, - 'output_new_file_frequency': int, - 'surface_outputs': {str: Any}, } def __init__( self, mesh_files: [PathLike], executable: PathLike, + modeled_start_time: datetime, + modeled_duration: timedelta, modeled_timestep: timedelta, - rnday: timedelta, tidal_spinup_duration: timedelta, - start_date: datetime, - ibc: Stratification, - drampbc: timedelta, + tidal_bc_spinup_duration: timedelta, + tidal_bc_cutoff_depth: float = None, + forcings: [ForcingJSON] = None, + stratification: Stratification = None, + hotstart_output_interval: timedelta = None, + slurm_configuration: SlurmJSON = None, + hotstart_combination_executable: PathLike = None, + output_surface: bool = None, + surface_output_interval: timedelta = None, + surface_output_new_file_skips: int = None, + surface_output_variables: {IofHydroVariables: bool} = None, + output_stations: bool = None, + stations_output_interval: timedelta = None, stations_file_path: PathLike = None, - stations_frequency: timedelta = None, stations_crs: CRS = None, - nhot_write: timedelta = None, - server_config: ServerConfig = None, - combine_hotstart: PathLike = None, - cutoff_depth: float = None, - output_frequency: timedelta = None, - output_new_file_frequency: int = None, - surface_outputs: {str: Any} = None, **kwargs, ): - if surface_outputs is None: - surface_outputs = {} + """ + Instantiate a new SCHISMJSON configuration. + + :param mesh_files: file paths to grid files + :param executable: file path to `schism` or `NEMS.x` + :param modeled_start_time: start time in model run + :param modeled_duration: duration of model run + :param modeled_timestep: time interval between model steps + :param tidal_spinup_duration: tidal spinup duration for SCHISM coldstart + :param tidal_bc_spinup_duration: BC tidal spinup duration for SCHISM coldstart + :param tidal_bc_cutoff_depth: cutoff depth for `bctides.in` + :param forcings: list of forcing configurations to apply to the mesh + :param stratification: IBC parameter; one of [`BAROCLINIC`, `BAROTROPIC`] + :param hotstart_output_interval: hotstart output interval + :param server_config: `ServerConfig` object + :param hotstart_combination_executable: file path to hotstart combination executable + :param output_surface: write surface (entire mesh) + :param surface_output_interval: time interval at which to output surface + :param surface_output_new_file_skips: number of intervals to skip between output + :param surface_output_variables: variables to output to surface + :param output_stations: write stations + :param stations_output_interval: + :param stations_file_path: file path to stations file + :param stations_crs: coordinate reference system of stations + """ + + if stratification is None: + stratification = Stratification.BAROTROPIC + + if output_surface is None: + output_surface = False + + if output_stations is None: + output_stations = False CirculationModelJSON.__init__( - self, - mesh_files=mesh_files, - executable=executable, - **kwargs, + self, mesh_files=mesh_files, executable=executable, **kwargs, ) + self['modeled_start_time'] = modeled_start_time + self['modeled_duration'] = modeled_duration self['modeled_timestep'] = modeled_timestep - self['rnday'] = rnday + self['tidal_spinup_duration'] = tidal_spinup_duration - self['start_date'] = start_date - self['ibc'] = ibc - self['drampbc'] = drampbc + self['tidal_spinup_duration'] = tidal_bc_spinup_duration + self['stratification'] = stratification + + self['hotstart_output_interval'] = hotstart_output_interval + self['hotstart_combination_executable'] = hotstart_combination_executable + self['cutoff_depth'] = tidal_bc_cutoff_depth + + self['output_surface'] = output_surface + self['surface_output_interval'] = surface_output_interval + self['surface_output_new_file_skips'] = surface_output_new_file_skips + self['surface_output_variables'] = surface_output_variables + + self['output_stations'] = output_stations + self['stations_output_interval'] = stations_output_interval self['stations_file_path'] = stations_file_path - self['stations_frequency'] = stations_frequency self['stations_crs'] = stations_crs - self['nhot_write'] = nhot_write - self['server_config'] = server_config - self['combine_hotstart'] = combine_hotstart - self['cutoff_depth'] = cutoff_depth + self.slurm_configuration = slurm_configuration - self['output_frequency'] = output_frequency - self['output_new_file_frequency'] = output_new_file_frequency - self['surface_outputs'] = surface_outputs + self.__forcings = [] + self.forcings = forcings + + @property + def forcings(self) -> [ForcingJSON]: + return list(self.__forcings) + + @forcings.setter + def forcings(self, forcings: [ForcingJSON]): + if forcings is None: + forcings = [] + for forcing in forcings: + self.add_forcing(forcing) + + def add_forcing(self, forcing: ForcingJSON): + if not isinstance(forcing, ForcingJSON): + forcing = from_user_input(forcing) + pyschism_forcing = forcing.pyschism_forcing + + existing_forcings = [ + existing_forcing.__class__.__name__ for existing_forcing in self.pyschism_forcings + ] + if pyschism_forcing.__class__.__name__ in existing_forcings: + existing_index = existing_forcings.index(pyschism_forcing.__class__.__name__) + else: + existing_index = -1 + if existing_index > -1: + self.__forcings[existing_index] = forcing + else: + self.__forcings.append(forcing) @property def hgrid_path(self) -> Path: @@ -149,36 +218,85 @@ def fgrid(self) -> Fgrid: @property def pyschism_stations(self) -> Stations: - return Stations.from_file( - file=self['stations_file_path'], - nspool_sta=self['stations_frequency'], - crs=self['stations_crs'], - ) + if self['output_stations']: + stations_output_interval = self['stations_output_interval'] + else: + stations_output_interval = None + + if self['stations_file_path'] is not None: + stations = Stations.from_file( + file=self['stations_file_path'], + nspool_sta=stations_output_interval, + crs=self['stations_crs'], + ) + else: + # TODO implement the extra options here + stations = Stations( + nspool_sta=stations_output_interval, + crs=self['stations_crs'], + elev=True, + air_pressure=True, + windx=True, + windy=True, + T=True, + S=True, + u=True, + v=True, + w=True, + ) + return stations + + @property + def pyschism_forcings(self) -> Union[PYSCHISM_FORCING_CLASSES]: + return [forcing.pyschism_forcing for forcing in self.forcings] @property def pyschism_domain(self) -> ModelDomain: - return ModelDomain( - hgrid=self.hgrid, - vgrid=self.vgrid, - fgrid=self.fgrid, - ) + domain = ModelDomain(hgrid=self.hgrid, vgrid=self.vgrid, fgrid=self.fgrid) + sflux_forcings = [] + for pyschism_forcing in self.pyschism_forcings: + if isinstance(pyschism_forcing, Hydrology): + domain.add_hydrology(pyschism_forcing) + elif isinstance(pyschism_forcing, SfluxDataset): + sflux_forcings.append(pyschism_forcing) + elif isinstance(pyschism_forcing, Tides): + domain.add_boundary_condition(pyschism_forcing) + # TODO add more atmospheric forcings + if len(sflux_forcings) > 0: + if len(sflux_forcings) > 2: + raise NotImplementedError('more than 2 sflux forcings not implemented') + domain.set_atmospheric_forcing( + NWS2(sflux_1=sflux_forcings[0], sflux_2=sflux_forcings[1]) + ) + return domain @property def pyschism_driver(self) -> ModelDriver: - return ModelDriver( + if self.slurm_configuration is not None: + server_configuration = self.slurm_configuration.to_pyschism + else: + server_configuration = None + + surface_output_variables = { + key.value: value for key, value in self['surface_output_variables'].items() + } + + driver = ModelDriver( model_domain=self.pyschism_domain, dt=self['modeled_timestep'], - rnday=self['rnday'], - ihfskip=self['output_new_file_frequency'], + rnday=self['modeled_duration'], + ihfskip=self['surface_output_new_file_interval'], dramp=self['tidal_spinup_duration'], start_date=self['start_date'], - ibc=self['ibc'], - drampbc=self['drampbc'], + ibc=self['stratification'], + drampbc=self['tidal_bc_spinup_duration'], stations=self.pyschism_stations, - nspool=self['output_frequency'], - nhot_write=self['nhot_write'], - server_config=self['server_config'], - combine_hotstart=self['combine_hotstart'], - cutoff_depth=self['cutoff_depth'], - **self['surface_outputs'], + nspool=self['surface_output_interval'], + nhot_write=self['hotstart_output_interval'], + server_config=server_configuration, + combine_hotstart=self['hotstart_combination_executable'], + cutoff_depth=self['tidal_bc_cutoff_depth'], + **surface_output_variables, ) + + return driver diff --git a/coupledmodeldriver/generate/schism/configure.py b/coupledmodeldriver/generate/schism/configure.py index 341132ec..a67115f1 100644 --- a/coupledmodeldriver/generate/schism/configure.py +++ b/coupledmodeldriver/generate/schism/configure.py @@ -5,6 +5,7 @@ from nemspy import ModelingSystem from pyschism import ModelDomain, ModelDriver +from pyschism.enums import Stratification from coupledmodeldriver.configure import NEMSJSON from coupledmodeldriver.configure.base import ( @@ -43,12 +44,15 @@ class SCHISMRunConfiguration(RunConfiguration): def __init__( self, - fort13_path: PathLike, - fort14_path: PathLike, + hgrid_path: PathLike, + vgrid_path: PathLike, + fgrid_path: PathLike, modeled_start_time: datetime, - modeled_end_time: datetime, + modeled_duration: timedelta, modeled_timestep: timedelta, tidal_spinup_duration: timedelta = None, + tidal_spinup_duration_bc: timedelta = None, + stratification: Stratification = None, platform: Platform = None, perturbations: {str: {str: Any}} = None, forcings: [ForcingJSON] = None, @@ -57,35 +61,39 @@ def __init__( slurm_partition: str = None, slurm_email_address: str = None, schism_executable: PathLike = None, - schismprep_executable: PathLike = None, source_filename: PathLike = None, ): """ Generate required configuration files for an SCHISM run. - :param fort13_path: path to input mesh nodes `fort.13` - :param fort14_path: path to input mesh attributes `fort.14` + :param hgrid_path: path to input horizontal grid + :param vgrid_path: path to input vertical grid + :param fgrid_path: path to input friction grid :param modeled_start_time: start time within the modeled system - :param modeled_end_time: end time within the modeled system + :param modeled_duration: duration within the modeled system :param modeled_timestep: time interval within the modeled system - :param schism_processors: numbers of processors to assign for SCHISM + :param stratification: :param platform: HPC platform for which to configure :param tidal_spinup_duration: spinup time for SCHISM tidal coldstart + :param tidal_spinup_duration_bc: spinup time for SCHISM tidal coldstart :param perturbations: dictionary of runs encompassing run names to parameter values :param forcings: list of forcing configurations to connect to SCHISM + :param schism_processors: numbers of processors to assign for SCHISM :param slurm_job_duration: wall clock time of job :param slurm_partition: Slurm partition :param slurm_email_address: email address to send Slurm notifications :param schism_executable: filename of compiled `schism` - :param schismprep_executable: filename of compiled `adcprep` :param source_filename: path to module file to `source` """ - if not isinstance(fort13_path, Path): - fort13_path = Path(fort13_path) + if not isinstance(hgrid_path, Path): + hgrid_path = Path(hgrid_path) + + if not isinstance(vgrid_path, Path): + vgrid_path = Path(vgrid_path) - if not isinstance(fort14_path, Path): - fort14_path = Path(fort14_path) + if not isinstance(fgrid_path, Path): + fgrid_path = Path(fgrid_path) if platform is None: platform = Platform.LOCAL @@ -97,10 +105,7 @@ def __init__( schism_processors = 11 if schism_executable is None: - schism_executable = 'schism' - - if schismprep_executable is None: - schismprep_executable = 'adcprep' + schism_executable = 'pschism_TVD-VL' slurm = SlurmJSON( account=platform.value['slurm_account'], @@ -108,19 +113,28 @@ def __init__( partition=slurm_partition, job_duration=slurm_job_duration, email_address=slurm_email_address, + extra_commands=[f'source {source_filename}'], ) model = SCHISMJSON( - mesh_files=[fort13_path, fort14_path], + mesh_files=[hgrid_path, vgrid_path, fgrid_path], executable=schism_executable, - schismprep_executable=schismprep_executable, modeled_start_time=modeled_start_time, - modeled_end_time=modeled_end_time, + modeled_duration=modeled_duration, modeled_timestep=modeled_timestep, tidal_spinup_duration=tidal_spinup_duration, - source_filename=source_filename, + tidal_bc_spinup_duration=tidal_spinup_duration_bc, + tidal_bc_cutoff_depth=None, + stratification=stratification, + hotstart_output_interval=None, slurm_configuration=slurm, - processors=schism_processors, + hotstart_combination_executable=None, + surface_output_new_file_skips=None, + surface_output_variables=None, + stations_output_interval=None, + stations_file_path=None, + stations_crs=None, + output_frequency=None, ) driver = ModelDriverJSON(platform=platform, perturbations=perturbations) @@ -224,16 +238,19 @@ class NEMSSCHISMRunConfiguration(SCHISMRunConfiguration): def __init__( self, - fort13_path: PathLike, - fort14_path: PathLike, + hgrid_path: PathLike, + vgrid_path: PathLike, + fgrid_path: PathLike, modeled_start_time: datetime, - modeled_end_time: datetime, + modeled_duration: timedelta, modeled_timestep: timedelta, nems_interval: timedelta, nems_connections: [str], nems_mediations: [str], nems_sequence: [str], tidal_spinup_duration: timedelta = None, + tidal_spinup_duration_bc: timedelta = None, + stratification: Stratification = None, platform: Platform = None, perturbations: {str: {str: Any}} = None, forcings: [ForcingJSON] = None, @@ -242,18 +259,20 @@ def __init__( slurm_partition: str = None, slurm_email_address: str = None, nems_executable: PathLike = None, - schismprep_executable: PathLike = None, source_filename: PathLike = None, ): self.__nems = None super().__init__( - fort13_path=fort13_path, - fort14_path=fort14_path, + hgrid_path=hgrid_path, + vgrid_path=vgrid_path, + fgrid_path=fgrid_path, modeled_start_time=modeled_start_time, - modeled_end_time=modeled_end_time, + modeled_duration=modeled_duration, modeled_timestep=modeled_timestep, tidal_spinup_duration=tidal_spinup_duration, + tidal_spinup_duration_bc=tidal_spinup_duration_bc, + stratification=stratification, platform=platform, perturbations=perturbations, forcings=None, @@ -261,14 +280,14 @@ def __init__( slurm_job_duration=slurm_job_duration, slurm_partition=slurm_partition, slurm_email_address=slurm_email_address, - schismprep_executable=schismprep_executable, + schism_executable=None, source_filename=source_filename, ) nems = NEMSJSON( executable=nems_executable, modeled_start_time=modeled_start_time, - modeled_end_time=modeled_end_time, + modeled_duration=modeled_duration, interval=nems_interval, models=self.nemspy_entries, connections=nems_connections, @@ -281,7 +300,7 @@ def __init__( for forcing in forcings: self.add_forcing(forcing) - self['slurm']['tasks'] = self['nems'].nemspy_modeling_system.processors + self['slurm'].tasks = self['nems'].nemspy_modeling_system.processors @property def nemspy_modeling_system(self) -> ModelingSystem: diff --git a/coupledmodeldriver/generate/schism/generate.py b/coupledmodeldriver/generate/schism/generate.py index 89e42519..71e57804 100644 --- a/coupledmodeldriver/generate/schism/generate.py +++ b/coupledmodeldriver/generate/schism/generate.py @@ -3,7 +3,10 @@ from os import PathLike from pathlib import Path -from coupledmodeldriver.generate.schism.configure import NEMSSCHISMRunConfiguration, SCHISMRunConfiguration +from coupledmodeldriver.generate.schism.configure import ( + NEMSSCHISMRunConfiguration, + SCHISMRunConfiguration, +) from coupledmodeldriver.utilities import LOGGER, get_logger diff --git a/coupledmodeldriver/utilities.py b/coupledmodeldriver/utilities.py index 57c426bd..2f3d7a72 100644 --- a/coupledmodeldriver/utilities.py +++ b/coupledmodeldriver/utilities.py @@ -51,6 +51,7 @@ def get_logger( logger.setLevel(logging.DEBUG) if console_level != logging.NOTSET: if console_level <= logging.INFO: + class LoggingOutputFilter(logging.Filter): def filter(self, rec): return rec.levelno in (logging.DEBUG, logging.INFO) @@ -210,39 +211,43 @@ def convert_value(value: Any, to_type: type) -> Any: value = value.to_json_dict() elif issubclass(to_type, int): value = value.to_epsg() - if issubclass(to_type, bool): - value = eval(f'{value}') - elif issubclass(to_type, datetime): - value = parse_date(value) - elif issubclass(to_type, timedelta): - try: + if not isinstance(value, to_type): + if issubclass(to_type, bool): + value = eval(f'{value}') + elif issubclass(to_type, datetime): + value = parse_date(value) + elif issubclass(to_type, timedelta): try: - time = datetime.strptime(value, '%H:%M:%S') - value = timedelta( - hours=time.hour, minutes=time.minute, seconds=time.second - ) + try: + time = datetime.strptime(value, '%H:%M:%S') + value = timedelta( + hours=time.hour, minutes=time.minute, seconds=time.second + ) + except: + parts = [float(part) for part in value.split(':')] + if len(parts) > 3: + days = parts.pop(0) + else: + days = 0 + value = timedelta( + days=days, hours=parts[0], minutes=parts[1], seconds=parts[2] + ) except: - parts = [float(part) for part in value.split(':')] - if len(parts) > 3: - days = parts.pop(0) - else: - days = 0 - value = timedelta( - days=days, hours=parts[0], minutes=parts[1], seconds=parts[2] - ) - except: - value = timedelta(seconds=float(value)) - elif isinstance(value, str): - try: - if Path(value).exists(): - value = to_type.from_file(value) - except: + value = timedelta(seconds=float(value)) + elif isinstance(value, str): try: - value = to_type.from_string(value) + path_exists = Path(value).exists() except: - value = to_type(value) - else: - value = to_type(value) + path_exists = False + if path_exists: + value = to_type.from_file(value) + else: + try: + value = to_type.from_string(value) + except: + value = to_type(value) + else: + value = to_type(value) return value diff --git a/examples/adcirc_only/hera_hsofs120m_sandy_spinup.py b/examples/adcirc_only/hera_hsofs120m_sandy_spinup.py index 8fd1b855..21fded81 100644 --- a/examples/adcirc_only/hera_hsofs120m_sandy_spinup.py +++ b/examples/adcirc_only/hera_hsofs120m_sandy_spinup.py @@ -33,7 +33,7 @@ configuration = ADCIRCRunConfiguration( mesh_directory=MESH_DIRECTORY, modeled_start_time=MODELED_START_TIME, - modeled_end_time=MODELED_START_TIME + MODELED_DURATION, + modeled_duration=MODELED_START_TIME + MODELED_DURATION, modeled_timestep=MODELED_TIMESTEP, tidal_spinup_duration=TIDAL_SPINUP_DURATION, platform=PLATFORM, diff --git a/examples/adcirc_only/hera_hsofs120m_sandy_spinup_tidal.py b/examples/adcirc_only/hera_hsofs120m_sandy_spinup_tidal.py index 3cb1af63..d6266d63 100644 --- a/examples/adcirc_only/hera_hsofs120m_sandy_spinup_tidal.py +++ b/examples/adcirc_only/hera_hsofs120m_sandy_spinup_tidal.py @@ -44,7 +44,7 @@ configuration = ADCIRCRunConfiguration( mesh_directory=MESH_DIRECTORY, modeled_start_time=MODELED_START_TIME, - modeled_end_time=MODELED_START_TIME + MODELED_DURATION, + modeled_duration=MODELED_START_TIME + MODELED_DURATION, modeled_timestep=MODELED_TIMESTEP, tidal_spinup_duration=TIDAL_SPINUP_DURATION, platform=PLATFORM, diff --git a/examples/adcirc_only/hera_hsofs120m_sandy_spinup_tidal_atmesh_ww3data.py b/examples/adcirc_only/hera_hsofs120m_sandy_spinup_tidal_atmesh_ww3data.py index cc684df7..6f8d5300 100644 --- a/examples/adcirc_only/hera_hsofs120m_sandy_spinup_tidal_atmesh_ww3data.py +++ b/examples/adcirc_only/hera_hsofs120m_sandy_spinup_tidal_atmesh_ww3data.py @@ -56,7 +56,7 @@ configuration = ADCIRCRunConfiguration( mesh_directory=MESH_DIRECTORY, modeled_start_time=MODELED_START_TIME, - modeled_end_time=MODELED_START_TIME + MODELED_DURATION, + modeled_duration=MODELED_START_TIME + MODELED_DURATION, modeled_timestep=MODELED_TIMESTEP, tidal_spinup_duration=TIDAL_SPINUP_DURATION, platform=PLATFORM, diff --git a/examples/nems_adcirc/hera_hsofs120m_sandy_spinup_tidal_atmesh_ww3data.py b/examples/nems_adcirc/hera_hsofs120m_sandy_spinup_tidal_atmesh_ww3data.py index 8fde0bef..d9e3c12b 100644 --- a/examples/nems_adcirc/hera_hsofs120m_sandy_spinup_tidal_atmesh_ww3data.py +++ b/examples/nems_adcirc/hera_hsofs120m_sandy_spinup_tidal_atmesh_ww3data.py @@ -68,7 +68,7 @@ configuration = NEMSADCIRCRunConfiguration( mesh_directory=MESH_DIRECTORY, modeled_start_time=MODELED_START_TIME, - modeled_end_time=MODELED_START_TIME + MODELED_DURATION, + modeled_duration=MODELED_START_TIME + MODELED_DURATION, modeled_timestep=MODELED_TIMESTEP, nems_interval=NEMS_INTERVAL, nems_connections=NEMS_CONNECTIONS, diff --git a/examples/nems_adcirc/hera_shinnecock_ike_spinup_tidal_atmesh_ww3data_perturbed_mannings_n.py b/examples/nems_adcirc/hera_shinnecock_ike_spinup_tidal_atmesh_ww3data_perturbed_mannings_n.py index 33375f2a..8a091648 100644 --- a/examples/nems_adcirc/hera_shinnecock_ike_spinup_tidal_atmesh_ww3data_perturbed_mannings_n.py +++ b/examples/nems_adcirc/hera_shinnecock_ike_spinup_tidal_atmesh_ww3data_perturbed_mannings_n.py @@ -80,7 +80,7 @@ configuration = NEMSADCIRCRunConfiguration( mesh_directory=MESH_DIRECTORY, modeled_start_time=MODELED_START_TIME, - modeled_end_time=MODELED_START_TIME + MODELED_DURATION, + modeled_duration=MODELED_START_TIME + MODELED_DURATION, modeled_timestep=MODELED_TIMESTEP, nems_interval=NEMS_INTERVAL, nems_connections=NEMS_CONNECTIONS, diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 095aca27..c89502d7 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1,7 +1,9 @@ from datetime import datetime, timedelta from pathlib import Path -from nemspy.model import ADCIRCEntry, AtmosphericMeshEntry, WaveWatch3MeshEntry +from nemspy.model import ADCIRCEntry, AtmosphericMeshEntry, \ + WaveWatch3MeshEntry +from pyschism import ModelDomain, ModelDriver, Stations import pytest from coupledmodeldriver import Platform @@ -14,6 +16,7 @@ WW3DATAForcingJSON, ) from coupledmodeldriver.generate.adcirc.base import ADCIRCJSON +from coupledmodeldriver.generate.schism.base import SCHISMJSON def test_update(): @@ -73,7 +76,7 @@ def test_nems(): configuration = NEMSJSON( executable='NEMS.x', modeled_start_time=datetime(2012, 10, 22, 6), - modeled_end_time=datetime(2012, 10, 22, 6) + timedelta(days=14.5), + modeled_duration=datetime(2012, 10, 22, 6) + timedelta(days=14.5), interval=timedelta(hours=1), models=model_entries, connections=connections, @@ -122,6 +125,22 @@ def test_adcirc(): assert configuration.adcircpy_driver.IM == 511112 +def test_schism(): + configuration = SCHISMJSON( + mesh_files=[], + executable='schism', + modeled_start_time=datetime(2012, 10, 22, 6), + modeled_duration=timedelta(days=14.5), + modeled_timestep=timedelta(seconds=2), + tidal_spinup_duration=timedelta(days=12.5), + tidal_bc_spinup_duration=timedelta(days=12.5), + ) + + assert isinstance(configuration.pyschism_stations, Stations) + assert isinstance(configuration.pyschism_domain, ModelDomain) + assert isinstance(configuration.pyschism_driver, ModelDriver) + + def test_tidal(): configuration = TidalForcingJSON(tidal_source='HAMTIDE', constituents='all') diff --git a/tests/test_generation.py b/tests/test_generation.py index 6bdb818e..3c8826dd 100644 --- a/tests/test_generation.py +++ b/tests/test_generation.py @@ -87,7 +87,7 @@ def test_nems_adcirc_local_shinnecock_ike(): fort13_path=mesh_directory / 'fort.13', fort14_path=mesh_directory / 'fort.14', modeled_start_time=modeled_start_time, - modeled_end_time=modeled_start_time + modeled_duration, + modeled_duration=modeled_start_time + modeled_duration, modeled_timestep=modeled_timestep, nems_interval=nems_interval, nems_connections=nems_connections, @@ -169,7 +169,7 @@ def test_nems_adcirc_hera_shinnecock_ike(): fort13_path=mesh_directory / 'fort.13', fort14_path=mesh_directory / 'fort.14', modeled_start_time=modeled_start_time, - modeled_end_time=modeled_start_time + modeled_duration, + modeled_duration=modeled_start_time + modeled_duration, modeled_timestep=modeled_timestep, nems_interval=nems_interval, nems_connections=nems_connections, @@ -251,7 +251,7 @@ def test_nems_adcirc_stampede2_shinnecock_ike(): fort13_path=mesh_directory / 'fort.13', fort14_path=mesh_directory / 'fort.14', modeled_start_time=modeled_start_time, - modeled_end_time=modeled_start_time + modeled_duration, + modeled_duration=modeled_start_time + modeled_duration, modeled_timestep=modeled_timestep, nems_interval=nems_interval, nems_connections=nems_connections, @@ -311,7 +311,7 @@ def test_adcirc_local_shinnecock_ike(): fort13_path=mesh_directory / 'fort.13', fort14_path=mesh_directory / 'fort.14', modeled_start_time=modeled_start_time, - modeled_end_time=modeled_start_time + modeled_duration, + modeled_duration=modeled_start_time + modeled_duration, modeled_timestep=modeled_timestep, tidal_spinup_duration=tidal_spinup_duration, platform=platform, @@ -366,7 +366,7 @@ def test_adcirc_hera_shinnecock_ike(): fort13_path=mesh_directory / 'fort.13', fort14_path=mesh_directory / 'fort.14', modeled_start_time=modeled_start_time, - modeled_end_time=modeled_start_time + modeled_duration, + modeled_duration=modeled_start_time + modeled_duration, modeled_timestep=modeled_timestep, tidal_spinup_duration=tidal_spinup_duration, platform=platform, @@ -421,7 +421,7 @@ def test_adcirc_stampede2_shinnecock_ike(): fort13_path=mesh_directory / 'fort.13', fort14_path=mesh_directory / 'fort.14', modeled_start_time=modeled_start_time, - modeled_end_time=modeled_start_time + modeled_duration, + modeled_duration=modeled_start_time + modeled_duration, modeled_timestep=modeled_timestep, tidal_spinup_duration=tidal_spinup_duration, platform=platform, @@ -504,7 +504,7 @@ def test_nems_adcirc_hera_shinnecock_ike_nospinup(): fort13_path=mesh_directory / 'fort.13', fort14_path=mesh_directory / 'fort.14', modeled_start_time=modeled_start_time, - modeled_end_time=modeled_start_time + modeled_duration, + modeled_duration=modeled_start_time + modeled_duration, modeled_timestep=modeled_timestep, nems_interval=nems_interval, nems_connections=nems_connections, @@ -564,7 +564,7 @@ def test_adcirc_hera_shinnecock_ike_nospinup(): fort13_path=mesh_directory / 'fort.13', fort14_path=mesh_directory / 'fort.14', modeled_start_time=modeled_start_time, - modeled_end_time=modeled_start_time + modeled_duration, + modeled_duration=modeled_start_time + modeled_duration, modeled_timestep=modeled_timestep, tidal_spinup_duration=tidal_spinup_duration, platform=platform, diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 93ccb1ac..b05ada56 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -130,11 +130,11 @@ def test_convert_value(): assert ( crs_1 - == 'GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]]' + == 'GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["unknown"],AREA["World"],BBOX[-90,-180,90,180]],ID["EPSG",4326]]' ) assert crs_2 == 4326 assert crs_3 == { - '$schema': 'https://proj.org/schemas/v0.2/projjson.schema.json', + '$schema': 'https://proj.org/schemas/v0.1/projjson.schema.json', 'type': 'GeographicCRS', 'name': 'WGS 84', 'datum': { @@ -163,8 +163,7 @@ def test_convert_value(): }, ], }, - 'scope': 'Horizontal component of 3D system.', - 'area': 'World.', + 'area': 'World', 'bbox': { 'south_latitude': -90, 'west_longitude': -180,