Skip to content

Commit

Permalink
Merge pull request #67 from noaa-ocs-modeling/develop
Browse files Browse the repository at this point in the history
add entry points to initialize and generate configuration
  • Loading branch information
zacharyburnett authored Apr 7, 2021
2 parents 74124d0 + fc1fb34 commit f35a5f4
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 40 deletions.
39 changes: 39 additions & 0 deletions client/generate_adcirc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from argparse import ArgumentParser
from pathlib import Path

from coupledmodeldriver.generate import (
generate_adcirc_configuration,
generate_nems_adcirc_configuration,
)
from coupledmodeldriver.utilities import convert_value


def main():
argument_parser = ArgumentParser()

argument_parser.add_argument('--configuration-directory', default=Path().cwd(),
help='path containing JSON configuration files')
argument_parser.add_argument('--output-directory', default=None, help='path to store generated configuration files')
argument_parser.add_argument('--verbose', action='store_true', help='show more verbose log messages')

arguments = argument_parser.parse_args()

configuration_directory = convert_value(arguments.configuration_directory, Path)
output_directory = convert_value(arguments.output_directory, Path)

if output_directory is None:
output_directory = configuration_directory

if 'configure_nems.json' in [
filename.lower() for filename in configuration_directory.listdir()
]:
generate = generate_nems_adcirc_configuration
else:
generate = generate_adcirc_configuration

generate(
configuration_directory=configuration_directory,
output_directory=output_directory,
overwrite=True,
verbose=arguments.verbose,
)
138 changes: 138 additions & 0 deletions client/initialize_adcirc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
from argparse import ArgumentParser
from datetime import datetime, timedelta
from pathlib import Path

from adcircpy import Tides
from adcircpy.forcing.tides.tides import TidalSource

from coupledmodeldriver import Platform
from coupledmodeldriver.configure.forcings.base import FORCING_SOURCES
from coupledmodeldriver.generate import (
ADCIRCGenerationScript,
ADCIRCRunConfiguration,
NEMSADCIRCGenerationScript,
NEMSADCIRCRunConfiguration,
)
from coupledmodeldriver.utilities import convert_value


def main():
argument_parser = ArgumentParser()

argument_parser.add_argument('--platform', required=True, help='HPC platform for which to configure')
argument_parser.add_argument('--mesh-directory', required=True, help='path to input mesh (`fort.13`, `fort.14`)')
argument_parser.add_argument('--modeled-start-time', required=True, help='start time within the modeled system')
argument_parser.add_argument('--modeled-duration', required=True, help=' end time within the modeled system')
argument_parser.add_argument('--modeled-timestep', required=True, help='time interval within the modeled system')
argument_parser.add_argument('--nems-interval', default=None, help='main loop interval of NEMS run')
argument_parser.add_argument('--modulefile', required=True, help='path to module file to `source`')
argument_parser.add_argument('--tidal-spinup-duration', default=None, help='spinup time for ADCIRC tidal coldstart')
argument_parser.add_argument('--tidal-forcing-source', default='TPXO',
help=f'source of tidal forcing (can be one of `{list(TidalSource)}`)')
argument_parser.add_argument('--tidal-forcing-path', default=None, help='file path to tidal forcing resource')
argument_parser.add_argument('--additional-forcings', default=[],
help='comma-separated list of additional forcings to configure')
argument_parser.add_argument('--adcirc-executable', default='adcirc', help='filename of compiled `adcirc` or `NEMS.x`')
argument_parser.add_argument('--adcprep-executable', default='adcprep', help='filename of compiled `adcprep`')
argument_parser.add_argument('--adcirc-processors', default=11, help='numbers of processors to assign for ADCIRC')
argument_parser.add_argument('--job-duration', default='06:00:00', help='wall clock time for job')
argument_parser.add_argument('--configuration-directory', default=Path().cwd(),
help='directory to which to write configuration files')
argument_parser.add_argument('--generate-script', action='store_true', help='write a Python script to load configuration')

arguments = argument_parser.parse_args()

platform = convert_value(arguments.platform, Platform)
mesh_directory = convert_value(arguments.mesh_directory, Path)

modeled_start_time = convert_value(arguments.modeled_start_time, datetime)
modeled_duration = convert_value(arguments.modeled_duration, timedelta)
modeled_timestep = convert_value(arguments.modeled_timestep, timedelta)
nems_interval = convert_value(arguments.nems_interval, timedelta)

adcirc_processors = convert_value(arguments.adcirc_processors, int)

modulefile = convert_value(arguments.modulefile, Path)

tidal_spinup_duration = convert_value(arguments.tidal_spinup_duration, timedelta)
tidal_source = convert_value(arguments.tidal_forcing_source, TidalSource)
tidal_forcing_path = convert_value(arguments.tidal_forcing_path, Path)
additional_forcings = arguments.additional_forcings
if additional_forcings is not None:
additional_forcings = [forcing.strip() for forcing in additional_forcings.split(',')]
else:
additional_forcings = []

adcirc_executable = convert_value(arguments.adcirc_executable, Path)
adcprep_executable = convert_value(arguments.adcprep_executable, Path)

job_duration = convert_value(arguments.job_duration, timedelta)
configuration_directory = convert_value(arguments.configuration_directory, timedelta)

# initialize `adcircpy` forcing objects
forcings = []

tidal_forcing = Tides(tidal_source=tidal_source, resource=tidal_forcing_path)
tidal_forcing.use_all()
forcings.append(tidal_forcing)

for forcing in additional_forcings:
if forcing.upper() in FORCING_SOURCES:
forcing = FORCING_SOURCES[forcing.upper()](filename=None)
forcings.append(forcing)
else:
raise NotImplementedError(
f'unrecognized forcing "{forcing}"; must be one of {list(FORCING_SOURCES)}'
)

if nems_interval is not None:
configuration = NEMSADCIRCRunConfiguration(
fort13=mesh_directory / 'fort.13',
fort14=mesh_directory / 'fort.14',
modeled_start_time=modeled_start_time,
modeled_end_time=modeled_start_time + modeled_duration,
modeled_timestep=modeled_timestep,
nems_interval=nems_interval,
nems_connections=None,
nems_mediations=None,
nems_sequence=None,
tidal_spinup_duration=tidal_spinup_duration,
platform=platform,
runs=None,
forcings=forcings,
adcirc_processors=adcirc_processors,
slurm_partition=None,
slurm_job_duration=job_duration,
slurm_email_address=None,
nems_executable=adcirc_executable,
adcprep_executable=adcprep_executable,
source_filename=modulefile,
)
generation_script = NEMSADCIRCGenerationScript()
else:
configuration = ADCIRCRunConfiguration(
fort13=mesh_directory / 'fort.13',
fort14=mesh_directory / 'fort.14',
modeled_start_time=modeled_start_time,
modeled_end_time=modeled_start_time + modeled_duration,
modeled_timestep=modeled_timestep,
tidal_spinup_duration=tidal_spinup_duration,
platform=platform,
runs=None,
forcings=forcings,
adcirc_processors=adcirc_processors,
slurm_partition=None,
slurm_job_duration=job_duration,
slurm_email_address=None,
adcirc_executable=adcirc_executable,
adcprep_executable=adcprep_executable,
source_filename=modulefile,
)
generation_script = ADCIRCGenerationScript()

configuration.write_directory(directory=configuration_directory, overwrite=True)

if arguments.create_script:
generation_script.write(
filename=configuration_directory / 'generate_adcirc.py', overwrite=True,
)
9 changes: 9 additions & 0 deletions coupledmodeldriver/configure/forcings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,12 @@ def nemspy_entry(self) -> WaveMeshEntry:
return WaveMeshEntry(
filename=self['resource'], processors=self['processors'], **self['nems_parameters']
)


FORCING_SOURCES = {
'Tides': TidalForcingJSON,
'AtmosphericMeshForcing': ATMESHForcingJSON,
'BestTrackForcing': BestTrackForcingJSON,
'OwiForcing': OWIForcingJSON,
'WaveWatch3DataForcing': WW3DATAForcingJSON,
}
34 changes: 14 additions & 20 deletions coupledmodeldriver/generate/adcirc/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
from os import PathLike
from pathlib import Path

from adcircpy import AdcircMesh, AdcircRun, Tides
from adcircpy import AdcircMesh, AdcircRun
from adcircpy.forcing.base import Forcing
from adcircpy.forcing.waves.ww3 import WaveWatch3DataForcing
from adcircpy.forcing.winds.atmesh import AtmosphericMeshForcing
from nemspy import ModelingSystem

from ...configure.base import ModelDriverJSON, NEMSJSON, SlurmJSON
from ...configure.base import ModelDriverJSON, NEMSCapJSON, NEMSJSON, \
SlurmJSON
from ...configure.configure import RunConfiguration
from ...configure.forcings.base import (
ATMESHForcingJSON,
FORCING_SOURCES,
ForcingJSON,
TidalForcingJSON,
WW3DATAForcingJSON,
Expand Down Expand Up @@ -59,9 +59,9 @@ def __init__(
:param modeled_start_time: start time within the modeled system
:param modeled_end_time: end time within the modeled system
:param modeled_timestep: time interval within the modeled system
:param adcirc_processors: numbers of processors to use for Slurm job
: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 coldstart
:param tidal_spinup_duration: spinup time for ADCIRC tidal coldstart
:param runs: dictionary of run name to run value and mesh attribute name
:param slurm_job_duration: wall clock time of job
:param slurm_partition: Slurm partition
Expand Down Expand Up @@ -113,12 +113,9 @@ def __init__(

def add_forcing(self, forcing: ForcingJSON):
if not isinstance(forcing, ForcingJSON):
if isinstance(forcing, AtmosphericMeshForcing):
forcing = ATMESHForcingJSON.from_adcircpy(forcing)
elif isinstance(forcing, WaveWatch3DataForcing):
forcing = WW3DATAForcingJSON.from_adcircpy(forcing)
elif isinstance(forcing, Tides):
forcing = TidalForcingJSON.from_adcircpy(forcing)
class_name = forcing.__class__.__name__
if class_name in FORCING_SOURCES:
forcing = FORCING_SOURCES[class_name].from_adcircpy(forcing)
else:
raise NotImplementedError(f'unable to parse object of type {type(forcing)}')

Expand Down Expand Up @@ -259,16 +256,13 @@ def nemspy_modeling_system(self) -> ModelingSystem:

def add_forcing(self, forcing: Forcing):
if not isinstance(forcing, ForcingJSON):
if isinstance(forcing, AtmosphericMeshForcing):
forcing = ATMESHForcingJSON.from_adcircpy(forcing)
self['nems']['models'].append(forcing.nemspy_entry)
elif isinstance(forcing, WaveWatch3DataForcing):
forcing = WW3DATAForcingJSON.from_adcircpy(forcing)
self['nems']['models'].append(forcing.nemspy_entry)
elif isinstance(forcing, Tides):
forcing = TidalForcingJSON.from_adcircpy(forcing)
class_name = forcing.__class__.__name__
if class_name in FORCING_SOURCES:
forcing = FORCING_SOURCES[class_name].from_adcircpy(forcing)
else:
raise NotImplementedError(f'unable to parse object of type {type(forcing)}')
if isinstance(forcing, NEMSCapJSON):
self['nems']['models'].append(forcing.nemspy_entry)

if forcing not in self:
self[forcing.name] = forcing
Expand Down
36 changes: 18 additions & 18 deletions coupledmodeldriver/generate/adcirc/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,30 @@


def generate_adcirc_configuration(
output_directory: PathLike,
configuration_directory: PathLike = None,
configuration_directory: PathLike,
output_directory: PathLike = None,
overwrite: bool = False,
verbose: bool = False,
):
"""
Generate ADCIRC run configuration for given variable values.
:param output_directory: path to store generated configuration files
:param configuration_directory: path containing JSON configuration files
:param output_directory: path to store generated configuration files
:param overwrite: whether to overwrite existing files
:param verbose: whether to show more verbose log messages
"""

get_logger(LOGGER.name, console_level=logging.DEBUG if verbose else logging.INFO)

if not isinstance(output_directory, Path):
output_directory = Path(output_directory)

if configuration_directory is None:
configuration_directory = output_directory
elif not isinstance(configuration_directory, Path):
if not isinstance(configuration_directory, Path):
configuration_directory = Path(configuration_directory)

if output_directory is None:
output_directory = configuration_directory
elif not isinstance(output_directory, Path):
output_directory = Path(output_directory)

if not output_directory.exists():
os.makedirs(output_directory, exist_ok=True)

Expand Down Expand Up @@ -263,30 +263,30 @@ def generate_adcirc_configuration(


def generate_nems_adcirc_configuration(
output_directory: PathLike,
configuration_directory: PathLike = None,
configuration_directory: PathLike,
output_directory: PathLike = None,
overwrite: bool = False,
verbose: bool = False,
):
"""
Generate ADCIRC run configuration for given variable values.
:param output_directory: path to store generated configuration files
:param configuration_directory: path containing JSON configuration files
:param output_directory: path to store generated configuration files
:param overwrite: whether to overwrite existing files
:param verbose: whether to show more verbose log messages
"""

get_logger(LOGGER.name, console_level=logging.DEBUG if verbose else logging.INFO)

if not isinstance(output_directory, Path):
output_directory = Path(output_directory)

if configuration_directory is None:
configuration_directory = output_directory
elif not isinstance(configuration_directory, Path):
if not isinstance(configuration_directory, Path):
configuration_directory = Path(configuration_directory)

if output_directory is None:
output_directory = configuration_directory
elif not isinstance(output_directory, Path):
output_directory = Path(output_directory)

if not output_directory.exists():
os.makedirs(output_directory, exist_ok=True)

Expand Down
2 changes: 1 addition & 1 deletion coupledmodeldriver/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def __str__(self) -> str:
'# run every hotstart configuration',
bash_for_loop(
'for hotstart in ${DIRECTORY}/runs/*/',
['pushd ${hotstart} >/dev/null 2>&1', self.hotstart, 'popd >/dev/null 2>&1', ],
['pushd ${hotstart} >/dev/null 2>&1', self.hotstart, 'popd >/dev/null 2>&1'],
),
*(str(command) for command in self.commands),
]
Expand Down
8 changes: 7 additions & 1 deletion coupledmodeldriver/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,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)
Expand Down Expand Up @@ -180,7 +181,12 @@ def convert_value(value: Any, to_type: type) -> Any:
try:
value = to_type[value]
except (KeyError, ValueError):
value = to_type(value)
try:
value = to_type(value)
except (KeyError, ValueError):
raise ValueError(
f'unrecognized entry "{value}"; must be one of {list(to_type)}'
)
elif not isinstance(value, to_type) and value is not None:
if isinstance(value, timedelta):
if issubclass(to_type, str):
Expand Down
6 changes: 6 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,10 @@
'testing': ['flake8', 'pytest', 'pytest-cov', 'wget'],
'development': ['oitnb'],
},
entry_points={
'console_scripts': [
'initialize_adcirc=client.initialize_adcirc:main',
'generate_adcirc=client.generate_adcirc:main',
],
},
)

0 comments on commit f35a5f4

Please sign in to comment.