Skip to content

Commit

Permalink
Enabled 6 hourly forecasts with GFS+NWM
Browse files Browse the repository at this point in the history
  • Loading branch information
jreniel committed Mar 8, 2021
1 parent 52abed1 commit b669c10
Show file tree
Hide file tree
Showing 15 changed files with 925 additions and 691 deletions.
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.*
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 b669c10

Please sign in to comment.