Skip to content

Commit

Permalink
Merge pull request #8 from schism-dev/feature/extend_atmos
Browse files Browse the repository at this point in the history
Completed 6-hourly forecasts with NWM+GFS
  • Loading branch information
jreniel authored Mar 10, 2021
2 parents cf70dc2 + f71867a commit 88d733b
Show file tree
Hide file tree
Showing 20 changed files with 1,151 additions and 868 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ jobs:
name: Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: true
fail-fast: false
matrix:
python-version: [ 3.6, 3.7, 3.8 ]
python-version: [ 3.7, 3.8 ]

steps:
- name: Checkout repository
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,10 @@ dist/
__pycache__
*_env
.ipynb_checkpoints
forecast
.vscode
*.swp
*.nc
hgrid.*
fgrid.*
vgrid.*
97 changes: 97 additions & 0 deletions examples/example_1/example_1.ipynb

Large diffs are not rendered by default.

15 changes: 10 additions & 5 deletions examples/example_1/example_1_tide-gfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@
from pyschism.forcing.atmosphere.nws.nws2 import NWS2
from pyschism.forcing.atmosphere.gfs import GlobalForecastSystem as GFS

#logging.basicConfig(filename='test.log',
# level=logging.INFO,
# format='%(asctime)s:%(levelnames'
# )

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

SIMPLE_SLURM_DRIVER = """#!/bin/bash --login
#SBATCH -D .
Expand Down Expand Up @@ -41,26 +47,25 @@

PARENT = pathlib.Path(__file__).parent

logging.basicConfig(level=logging.INFO)

_logger = logging.getLogger(__name__)

if __name__ == '__main__':
# open gr3 file
_logger.info('Reading hgrid file...')
logger.info('Reading hgrid file...')
_tic = time()
hgrid = Hgrid.open(PARENT / 'hgrid.gr3',crs='EPSG:4326')
_logger.info(f'Reading hgrind file took {time()-_tic}.')
logger.info(f'Reading hgrind file took {time()-_tic}.')

vgrid = Vgrid()
fgrid = Fgrid.open(PARENT / 'drag.gr3',crs='EPSG:4326')

# setup model domain
domain = ModelDomain(hgrid, vgrid, fgrid)
logger.info('Model domain setup finished')

# set tidal boundary conditions
elevbc = Tides()
elevbc.use_all() # activate all forcing constituents
logger.info('Tidal boundary setup finished')

# connect the boundary condition to the domain
domain.add_boundary_condition(elevbc)
Expand Down
20 changes: 20 additions & 0 deletions pyschism/__main__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
#! /usr/bin/env python
import argparse
from datetime import datetime
import logging

from pytz import timezone

from pyschism.cmd.forecast.forecast import ForecastCli, add_forecast
from pyschism.cmd.bctides import BctidesCli, add_bctides


# logging.getLogger().setLevel(logging.NOTSET)


def parse_args():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='mode')
Expand All @@ -20,6 +27,19 @@ def parse_args():
def main():
args = parse_args()

logging.basicConfig(
level={
'warning': logging.WARNING,
'info': logging.INFO,
'debug': logging.DEBUG,
}[args.log_level],
format='[%(asctime)s] %(name)s %(levelname)s: %(message)s',
force=True,
)

logging.Formatter.converter = lambda *args: datetime.now(
tz=timezone('UTC')).timetuple()

if args.mode == 'forecast':
ForecastCli(args)

Expand Down
8 changes: 5 additions & 3 deletions pyschism/cmd/forecast/forecast.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ def add_forecast_init(actions):
help="Number of days used for model initialization. "
"Defaults to 15 days spinup.",
type=float, default=15.)
init.add_argument("--forecast-interval", type=float, default=24)
init.add_argument("--timezone", default='UTC')
init.add_argument(
"--skip-run", action="store_true",
help="Skips running the model.")
Expand Down Expand Up @@ -102,7 +100,9 @@ def add_forecast(subparsers):
help="Allow overwrite of output directory.")
forecast.add_argument(
"--log-level",
choices=[name.lower() for name in logging._nameToLevel])
choices=['info', 'warning', 'debug'],
default='info'
)
actions = forecast.add_subparsers(dest="action")
add_forecast_init(actions)
add_forecast_update(actions)
Expand Down Expand Up @@ -173,6 +173,8 @@ def _add_tidal_constituents(parser):
tides = parser.add_argument_group('tides')
options = tides.add_mutually_exclusive_group()
options.required = True
options.add_argument("--tidal-database", choices=['tpxo', 'hamtide'],
default='hamtide')
options.add_argument("--all-constituents", action="store_true")
options.add_argument("--major-constituents", action="store_true")
options.add_argument(
Expand Down
80 changes: 30 additions & 50 deletions pyschism/cmd/forecast/init.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from argparse import Namespace
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
import json
# import logging
import logging
import os
import pathlib
import shutil
Expand Down Expand Up @@ -54,22 +54,6 @@ def __get__(self, obj, val):
return coldstart_directory


class TargetDatetime:

def __get__(self, obj, val):
target_datetime = obj.__dict__.get('target_datetime')
if target_datetime is None:
utcnow = pytz.timezone('UTC').localize(datetime.utcnow())
# localnow = utcnow.astimezone(pytz.timezone(obj.args.timezone))
nowcast_cycle = int(obj.forecast_interval * np.floor(
utcnow.hour/obj.forecast_interval)) # % 24
target_datetime = datetime(
utcnow.year, utcnow.month, utcnow.day, nowcast_cycle,
tzinfo=pytz.timezone(obj.args.timezone))
obj.__dict__['target_datetime'] = target_datetime
return target_datetime


class ColdstartDomain:

def __get__(self, obj, val):
Expand Down Expand Up @@ -111,18 +95,6 @@ def __get__(self, obj, val):
return coldstart


class ForecastInterval:

def __get__(self, obj, val):
forecast_interval = obj.__dict__.get('forecast_interval')
if forecast_interval is None:
if obj.args.forecast_interval != 24:
raise NotImplementedError(
'Forecast interval only at 24 hours for now.')
obj.__dict__['forecast_interval'] = obj.args.forecast_interval
return obj.__dict__['forecast_interval']


class HgridPath:

def __get__(self, obj, val):
Expand Down Expand Up @@ -197,7 +169,10 @@ def __get__(self, obj, val):
and not obj.args.constituents:
return
else:
tides = Tides(velocity=obj.args.bnd_vel)
tides = Tides(
database=obj.args.tidal_database,
velocity=obj.args.bnd_vel
)
if obj.args.all_constituents:
tides.use_all()
if obj.args.major_constituents:
Expand Down Expand Up @@ -245,10 +220,8 @@ class ForecastInit:
project_directory = ProjectDirectory()
config_file = ConfigFile()
coldstart_directory = ColdstartDirectory()
target_datetime = TargetDatetime()
coldstart_domain = ColdstartDomain()
coldstart_driver = ColdstartDriver()
forecast_interval = ForecastInterval()
static_files_directory = StaticFilesDirectory()
hgrid_path = HgridPath()
vgrid_path = VgridPath()
Expand All @@ -267,7 +240,7 @@ def __init__(self, args: Namespace):
self._write_config_file()
self._symlink_files(self.coldstart_directory,
self.coldstart_domain.ics)
# self.logger.info('Writting coldstart files to disk...')
self.logger.info('Writting coldstart files to disk...')
self.coldstart_driver.write(
self.coldstart_directory,
hgrid=False,
Expand All @@ -279,11 +252,11 @@ def __init__(self, args: Namespace):

if self.args.skip_run is False:
# release memory before launching SCHISM.
# self.logger.info('Releasing memory before calling SCHISM...')
self.logger.info('Releasing memory before calling SCHISM...')
for item in list(self.__dict__.keys()):
if not item.startswith('_'):
del self.__dict__[item]
# self.logger.info('Calling SCHISM using make.')
self.logger.info('Calling SCHISM using make.')
subprocess.check_call(
["make", "run"],
cwd=self.coldstart_directory
Expand All @@ -304,14 +277,14 @@ def _write_config_file(self):
'been initialized previously. Please use\npyschism '
'forecast --overwrite init [...]\nto allow overwrite of '
'previous initialization options.')
# self.logger.info(
# f"Writting configuration file to path {self.config_file}")
self.logger.info(
f"Writting configuration file to path {self.config_file}")
with open(self.config_file, 'w') as fp:
json.dump(self.args.__dict__, fp, indent=4)

def _symlink_files(self, target_directory, ics):
# self.logger.info(
# f"Establishing symlinks to target_directory: {target_directory}")
self.logger.info(
f"Establishing symlinks to target_directory: {target_directory}")
hgrid_lnk = target_directory / 'hgrid.gr3'
vgrid_lnk = target_directory / 'vgrid.in'
fgrid_lnk = target_directory / f'{self.fgrid_path.name}'
Expand Down Expand Up @@ -345,13 +318,20 @@ def _symlink_hgridll(self, target_directory):
os.symlink(os.path.relpath(
self.hgrid_path, target_directory), hgridll_lnk)

# @property
# def logger(self):
# try:
# return self._logger
# except AttributeError:
# self._logger = get_logger(
# console_level=logging._nameToLevel[self.args.log_level.upper()]
# )
# self._logger.propagate = 0
# return self._logger
@property
def target_datetime(self):
if not hasattr(self, '_target_datetime'):
now_utc = datetime.now(timezone.utc)
nearest_cycle = int(6 * np.floor(now_utc.hour/6))
self._target_datetime = datetime(
now_utc.year, now_utc.month, now_utc.day, nearest_cycle,
tzinfo=timezone.utc)
self.logger.info(
f'Target datetime is: {str(self._target_datetime)}.')
return self._target_datetime

@property
def logger(self):
if not hasattr(self, '_logger'):
self._logger = logging.getLogger(f"{self.__class__.__name__}")
return self._logger
33 changes: 33 additions & 0 deletions pyschism/dates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from datetime import datetime

import numpy as np
import pytz


def pivot_time(input_datetime=None):
"""
"pivot time" is defined as the nearest floor t00z for any given datetime.
If this function is called without arguments, it will return the pivot time
for the current datetime in UTC.
"""
input_datetime = nearest_cycle_date() if input_datetime is None else \
localize_datetime(input_datetime).astimezone(pytz.utc)
return localize_datetime(
datetime(input_datetime.year, input_datetime.month, input_datetime.day)
)


def nearest_cycle_date(input_datetime=None, period=6):
if input_datetime is None:
input_datetime = localize_datetime(datetime.utcnow())
current_cycle = int(period * np.floor(input_datetime.hour / period))
return pytz.timezone('UTC').localize(
datetime(input_datetime.year, input_datetime.month,
input_datetime.day, current_cycle))


def localize_datetime(d):
# datetime is naïve iff:
if d.tzinfo is None or d.tzinfo.utcoffset(d) is None:
return pytz.timezone('UTC').localize(d)
return d
Loading

0 comments on commit 88d733b

Please sign in to comment.