Skip to content

Commit

Permalink
Almost: make adcircpy optional, update relevant tests
Browse files Browse the repository at this point in the history
  • Loading branch information
SorooshMani-NOAA committed Aug 5, 2024
1 parent 213740c commit 0dc3317
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 63 deletions.
2 changes: 1 addition & 1 deletion coupledmodeldriver/_depend.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
def optional_import(name):
if name in sys.modules:
_logger.warning(f"{name!r} already in sys.modules")
return sys.module[name]
return sys.modules[name]
elif (spec := importlib.util.find_spec(name)) is not None:
# If you chose to perform the actual import ...
module = importlib.util.module_from_spec(spec)
Expand Down
37 changes: 16 additions & 21 deletions coupledmodeldriver/client/check_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

from typepigeon import convert_value

from coupledmodeldriver.configure import ModelJSON
from coupledmodeldriver.generate.schism.base import SCHISMJSON
from coupledmodeldriver.configure import Model
from coupledmodeldriver.generate.schism.check import (
check_schism_completion,
CompletionStatus,
Expand All @@ -18,15 +17,11 @@
from coupledmodeldriver._depend import optional_import


if adcirc_gen := optional_import('coupledmodeldriver.generate.adcirc') is not None:
ADCIRCJSON = adcirc_gen.base.ADCIRCJSON
if (adcirc_check := optional_import('coupledmodeldriver.generate.adcirc.check')) is not None:
check_adcirc_completion = adcirc_check.check_adcirc_completion
CompletionStatus = adcirc_check.CompletionStatus
is_adcirc_run_directory = adcirc_check.is_adcirc_run_directory

check_adcirc_completion = adcirc_gen.check.check_adcirc_completion
CompletionStatus = adcirc_gen.check.CompletionStatus
is_adcirc_run_directory = adcirc_gen.check.is_adcirc_run_directory


MODELS = {model.name.lower(): model for model in ModelJSON.__subclasses__()}


def parse_check_completion_arguments():
Expand All @@ -46,7 +41,7 @@ def parse_check_completion_arguments():

model = arguments.model
if model is not None:
model = MODELS[model.lower()]
model = Model[model.upper()]

directory = convert_value(arguments.directory, [Path])
if len(directory) == 1:
Expand All @@ -59,18 +54,18 @@ def parse_check_completion_arguments():
}


def is_model_directory(directory: PathLike, model: ModelJSON = None) -> bool:
def is_model_directory(directory: PathLike, model: Model = None) -> bool:
if directory is None:
directory = Path.cwd()
elif not isinstance(directory, Path):
directory = Path(directory)

if model is None:
model = SCHISMJSON
model = Model.SCHISM

if model == ADCIRCJSON:
if model == Model.ADCIRC:
is_model_directory = is_adcirc_run_directory(directory)
elif model == SCHISMJSON:
elif model == Model.SCHISM:
is_model_directory = is_schism_run_directory(directory)
else:
raise NotImplementedError(f'model "{model}" not implemented')
Expand All @@ -79,26 +74,26 @@ def is_model_directory(directory: PathLike, model: ModelJSON = None) -> bool:


def check_model_directory(
directory: PathLike, verbose: bool = False, model: ModelJSON = None
directory: PathLike, verbose: bool = False, model: Model = None
) -> Dict[str, Any]:
if directory is None:
directory = Path.cwd()
elif not isinstance(directory, Path):
directory = Path(directory)

if model is None:
model = SCHISMJSON
model = Model.SCHISM

if model == ADCIRCJSON:
if model == Model.ADCIRC:
return check_adcirc_completion(directory, verbose=verbose)
elif model == SCHISMJSON:
elif model == Model.SCHISM:
return check_schism_completion(directory, verbose=verbose)
else:
raise NotImplementedError(f'model "{model}" not implemented')


def check_completion(
directory: PathLike = None, verbose: bool = False, model: ModelJSON = None
directory: PathLike = None, verbose: bool = False, model: Model = None
) -> Dict[str, Any]:
"""
check the completion status of a running model
Expand Down Expand Up @@ -127,7 +122,7 @@ def check_completion(
completion_status.update(subdirectory_completion_status)
elif isinstance(directory, Path):
if model is None:
for model_type in MODELS.values():
for model_type in Model.values():
if not is_model_directory(directory, model=model_type):
continue
model = model_type
Expand Down
2 changes: 1 addition & 1 deletion coupledmodeldriver/configure/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ def to_pyschism(self) -> PySCHISMSlurmConfig:
mail_user=self['email_address'],
log_filename=self['log_filename'],
modules=self['modules'],
path_prefix=self['path_prefix'],
modulepath=self['path_prefix'],
extra_commands=self['extra_commands'],
launcher=self['launcher'],
nodes=self['nodes'],
Expand Down
5 changes: 4 additions & 1 deletion coupledmodeldriver/configure/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path
from typing import Any, Collection, Dict, List, Mapping, Set, Union

import coupledmodeldriver.configure.forcings.base
from coupledmodeldriver.configure.base import ConfigurationJSON, ModelDriverJSON
from coupledmodeldriver.configure.forcings.base import (
PYSCHISM_FORCING_CLASSES,
Expand All @@ -12,7 +13,9 @@
from coupledmodeldriver.utilities import LOGGER
from coupledmodeldriver._depend import optional_import

ADCIRCPY_FORCING_CLASSES = optional_import('coupledmodeldriver.configure.forcings.base.ADCIRCPY_FORCING_CLASSES')
ADCIRCPY_FORCING_CLASSES = ()
if optional_import('adcircpy'):
ADCIRCPY_FORCING_CLASSES = coupledmodeldriver.configure.forcings.base.ADCIRCPY_FORCING_CLASSES

class RunConfiguration(ABC):
"""
Expand Down
8 changes: 6 additions & 2 deletions coupledmodeldriver/configure/forcings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,9 +446,13 @@ def __init__(
self.__dataframe = dataframe

if self.__dataframe is not None:
forcing = self.adcircpy_forcing
# TODO: Find a better solution; for now adcircpy is optional
# but pyschism is not!
forcing = self.pyschism_forcing
self['nhc_code'] = forcing.nhc_code
self['interval'] = forcing.interval
# for adcircpy
if hasattr(forcing, 'interval'):
self['interval'] = forcing.interval
self['start_date'] = forcing.start_date
self['end_date'] = forcing.end_date

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"nhc_code": "AL092008",
"interval": 3600.0,
"interval": null,
"start_date": "2008-09-01 06:00:00",
"end_date": "2008-09-15 12:00:00",
"fort22_filename": null,
Expand Down
57 changes: 36 additions & 21 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,47 @@
from pathlib import Path
from unittest.mock import patch

from coupledmodeldriver.client.initialize_adcirc import parse_initialize_adcirc_arguments
import pytest

from coupledmodeldriver.client.initialize_schism import parse_initialize_schism_arguments
from coupledmodeldriver.platforms import Platform
from coupledmodeldriver._depend import optional_import

# noinspection PyUnresolvedReferences
from tests import INPUT_DIRECTORY


ADCIRC_ARGUMENT_TYPES = {
'platform': Platform,
'mesh_directory': Path,
'modeled_start_time': datetime,
'modeled_duration': timedelta,
'modeled_timestep': timedelta,
'nems_interval': (timedelta, type(None)),
'tidal_spinup_duration': (timedelta, type(None)),
'modulefile': (Path, type(None)),
'forcings': list,
'adcirc_executable': Path,
'adcprep_executable': Path,
'aswip_executable': Path,
'adcirc_processors': int,
'job_duration': timedelta,
'output_directory': Path,
'absolute_paths': bool,
'overwrite': bool,
'verbose': bool,
}

test_adcirc = False
skip_adcircpy_msg = "AdcircPy is not available!"
if optional_import('adcircpy'):
test_adcirc = True
skip_adcircpy_msg = ""
init_adcirc = optional_import('coupledmodeldriver.client.initialize_adcirc')
parse_initialize_adcirc_arguments = init_adcirc.parse_initialize_adcirc_arguments


ADCIRC_ARGUMENT_TYPES = {
'platform': Platform,
'mesh_directory': Path,
'modeled_start_time': datetime,
'modeled_duration': timedelta,
'modeled_timestep': timedelta,
'nems_interval': (timedelta, type(None)),
'tidal_spinup_duration': (timedelta, type(None)),
'modulefile': (Path, type(None)),
'forcings': list,
'adcirc_executable': Path,
'adcprep_executable': Path,
'aswip_executable': Path,
'adcirc_processors': int,
'job_duration': timedelta,
'output_directory': Path,
'absolute_paths': bool,
'overwrite': bool,
'verbose': bool,
}

SCHISM_ARGUMENT_TYPES = {
'platform': Platform,
'mesh_directory': Path,
Expand Down Expand Up @@ -62,6 +75,7 @@ def cli_test_helper(parse_func, *test_args):
return parse_func()


@pytest.mark.skipif(not test_adcirc, reason=skip_adcircpy_msg)
def test_initialize_adcirc_noargs(capsys):
try:
cli_test_helper(parse_initialize_adcirc_arguments)
Expand All @@ -83,6 +97,7 @@ def test_initialize_adcirc_noargs(capsys):
)


@pytest.mark.skipif(not test_adcirc, reason=skip_adcircpy_msg)
def test_initialize_adcirc_requied_args():
results = cli_test_helper(
parse_initialize_adcirc_arguments,
Expand Down
27 changes: 18 additions & 9 deletions tests/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@
TidalForcingJSON,
WW3DATAForcingJSON,
)
from coupledmodeldriver.generate.adcirc.base import ADCIRCJSON
from coupledmodeldriver.generate.schism.base import SCHISMJSON
from coupledmodeldriver._depend import optional_import

# noinspection PyUnresolvedReferences
from tests import INPUT_DIRECTORY, tpxo_filename


test_adcirc = False
skip_adcircpy_msg = "AdcircPy is not available!"
if optional_import('adcircpy'):
test_adcirc = True
skip_adcircpy_msg = ""
ADCIRCJSON = optional_import('coupledmodeldriver.generate.adcirc.base').ADCIRCJSON

def test_update():
configuration = SlurmJSON(account='coastal', tasks=602, job_duration=timedelta(hours=6))

Expand Down Expand Up @@ -53,9 +60,9 @@ def test_slurm():
nodes=None,
)

slurm = configuration.to_adcircpy()
slurm = configuration.to_pyschism()

assert slurm.nprocs == 602
assert slurm.nproc == 602


def test_nems():
Expand Down Expand Up @@ -96,6 +103,7 @@ def test_nems():
]


@pytest.mark.skipif(not test_adcirc, reason=skip_adcircpy_msg)
def test_adcirc():
configuration = ADCIRCJSON(
adcirc_executable_path='adcirc',
Expand Down Expand Up @@ -146,7 +154,7 @@ def test_schism():
def test_tidal():
configuration = TidalForcingJSON(tidal_source='HAMTIDE', constituents='all')

assert list(configuration.adcircpy_forcing.active_constituents) == [
assert set(configuration.pyschism_forcing.active_constituents) == {
'Q1',
'O1',
'P1',
Expand All @@ -155,11 +163,11 @@ def test_tidal():
'M2',
'S2',
'K2',
]
}

configuration['constituents'] = 'major'

assert list(configuration.adcircpy_forcing.active_constituents) == [
assert set(configuration.pyschism_forcing.active_constituents) == {
'Q1',
'O1',
'P1',
Expand All @@ -168,13 +176,14 @@ def test_tidal():
'M2',
'S2',
'K2',
]
}

configuration['tidal_source'] = 'TPXO'
configuration['resource'] = 'nonexistent/path/to/h_tpxo9.nc'

with pytest.raises((FileNotFoundError, OSError)):
configuration.adcircpy_forcing
# pyschism tides don't fail if tpxo is linked locally
# with pytest.raises((FileNotFoundError, OSError)):
# configuration.pyschism_forcing

# TODO find a better way to host TPXO for testing
# configuration['resource'] = tpxo_filename()
Expand Down
Loading

0 comments on commit 0dc3317

Please sign in to comment.