diff --git a/tools/RAiDER/cli/raider.py b/tools/RAiDER/cli/raider.py index 579e5fced..a1d171b33 100644 --- a/tools/RAiDER/cli/raider.py +++ b/tools/RAiDER/cli/raider.py @@ -20,12 +20,15 @@ from RAiDER.cli.parser import add_out, add_cpus, add_verbose from RAiDER.cli.validators import DateListAction, date_type from RAiDER.models.allowed import ALLOWED_MODELS +from RAiDER.models.customExceptions import * from RAiDER.utilFcns import get_dt from RAiDER.s1_azimuth_timing import get_s1_azimuth_time_grid, get_inverse_weights_for_dates, get_times_for_azimuth_interpolation import traceback +TIME_INTERPOLATION_METHODS = ['none', 'center_time', 'azimuth_time_grid'] + HELP_MESSAGE = """ Command line options for RAiDER processing. Default options can be found by running @@ -269,26 +272,31 @@ def calcDelays(iargs=None): wfiles = [] for tt in times: try: - wfile = RAiDER.processWM.prepareWeatherModel(model, - tt, - aoi.bounds(), - makePlots=params['verbose']) + wfile = RAiDER.processWM.prepareWeatherModel( + model, + tt, + aoi.bounds(), + makePlots=params['verbose'] + ) wfiles.append(wfile) - # catch when requested datetime fails - except RuntimeError: - continue + except TryToKeepGoingError: + if interp_method in ['azimuth_time_grid', 'none']: + raise DatetimeFailed(model.Model(), tt) + else: + continue - # catch when something else within weather model class fails + # log when something else happens and then re-raise the error except Exception as e: S, N, W, E = wm_bounds logger.info(f'Weather model point bounds are {S:.2f}/{N:.2f}/{W:.2f}/{E:.2f}') logger.info(f'Query datetime: {tt}') msg = f'Downloading and/or preparation of {model._Name} failed.' logger.error(e) + logger.error('Weather model files are: {}'.format(wfiles)) logger.error(msg) - if interp_method == 'azimuth_time_grid': - break + raise + # dont process the delays for download only if dl_only: continue @@ -478,7 +486,7 @@ def calcDelaysGUNW(iargs: list[str] = None) -> xr.Dataset: p.add_argument( '-interp', '--interpolate-time', default='azimuth_time_grid', type=str, - choices=['none', 'center_time', 'azimuth_time_grid'], + choices=TIME_INTERPOLATION_METHODS, help=('How to interpolate across model time steps. Possible options are: ' '[\'none\', \'center_time\', \'azimuth_time_grid\'] ' 'None: means nearest model time; center_time: linearly across center time; ' @@ -595,13 +603,13 @@ def getWeatherFile(wfiles, times, t, interp_method='none'): ''' # time interpolation method: number of expected files - ALLOWED_METHODS = {'none': 1, 'center_time': 2, 'azimuth_time_grid': 3} + EXPECTED_NUM_FILES = {'none': 1, 'center_time': 2, 'azimuth_time_grid': 3} Nfiles = len(wfiles) Ntimes = len(times) try: - Nfiles_expected = ALLOWED_METHODS[interp_method] + Nfiles_expected = EXPECTED_NUM_FILES[interp_method] except KeyError: raise ValueError('getWeatherFile: interp_method {} is not known'.format(interp_method)) diff --git a/tools/RAiDER/models/customExceptions.py b/tools/RAiDER/models/customExceptions.py new file mode 100644 index 000000000..3d8540399 --- /dev/null +++ b/tools/RAiDER/models/customExceptions.py @@ -0,0 +1,39 @@ +class DatetimeFailed(Exception): + def __init__(self, model, time): + msg = f"Weather model {model} failed to download for datetime {time}" + super().__init__(msg) + + +class DatetimeNotAvailable(Exception): + def __init__(self, model, time): + msg = f"Weather model {model} was not found for datetime {time}" + super().__init__(msg) + + +class DatetimeOutsideRange(Exception): + def __init__(self, model, time): + msg = f"Time {time} is outside the available date range for weather model {model}" + super().__init__(msg) + + +class ExistingWeatherModelTooSmall(Exception): + def __init__(self): + msg = 'The weather model passed does not cover all of the input ' \ + 'points; you may need to download a larger area.' + super().__init__(msg) + + +class TryToKeepGoingError(Exception): + def __init__(self, date=None): + if date is not None: + msg = 'The weather model does not exist for date {date}, so I will try to use the closest available date.' + else: + msg = 'I will try to keep going' + super().__init__(msg) + + +class CriticalError(Exception): + def __init__(self): + msg = 'I have experienced a critical error, please take a look at the log files' + super().__init__(msg) + diff --git a/tools/RAiDER/models/weatherModel.py b/tools/RAiDER/models/weatherModel.py index 70c3571a8..37165f07f 100755 --- a/tools/RAiDER/models/weatherModel.py +++ b/tools/RAiDER/models/weatherModel.py @@ -18,6 +18,7 @@ from RAiDER.interpolator import fillna3D from RAiDER.logger import logger from RAiDER.models import plotWeather as plots, weatherModel +from RAiDER.models.customExceptions import * from RAiDER.utilFcns import ( robmax, robmin, write2NETCDF4core, calcgeoh, transform_coords, clip_bbox ) @@ -31,7 +32,6 @@ 'HRRR-AK': 3, } - class WeatherModel(ABC): ''' Implement a generic weather model for getting estimated SAR delays @@ -160,12 +160,9 @@ def fetch(self, out, time): # write the error raised by the weather model API to the log try: self._fetch(out) - err = False - except Exception as E: - err = E - - return err + logger.exception(E) + raise @abstractmethod @@ -308,22 +305,17 @@ def checkTime(self, time): self.Model(), self._valid_range[0].date(), end_time ) - msg = f"Weather model {self.Model()} is not available at: {time}" - if time < self._valid_range[0]: - logger.error(msg) - raise RuntimeError(msg) + raise DatetimeOutsideRange(self.Model(), time) if self._valid_range[1] is not None: if self._valid_range[1] == 'Present': pass elif self._valid_range[1] < time: - logger.error(msg) - raise RuntimeError(msg) + raise DatetimeOutsideRange(self.Model(), time) if time > datetime.datetime.utcnow() - self._lag_time: - logger.error(msg) - raise RuntimeError(msg) + raise DatetimeOutsideRange(self.Model(), time) def setLevelType(self, levelType): diff --git a/tools/RAiDER/processWM.py b/tools/RAiDER/processWM.py index b1b034061..214bf210d 100755 --- a/tools/RAiDER/processWM.py +++ b/tools/RAiDER/processWM.py @@ -15,7 +15,7 @@ from RAiDER.logger import logger from RAiDER.utilFcns import getTimeFromFile from RAiDER.models.weatherModel import make_raw_weather_data_filename, checkContainment_raw - +from RAiDER.models.customExceptions import * def prepareWeatherModel( weather_model, @@ -67,10 +67,10 @@ def prepareWeatherModel( # if no weather model files supplied, check the standard location else: - E = weather_model.fetch(path_wm_raw, time) - if E: - logger.warning (E) - raise RuntimeError + try: + weather_model.fetch(path_wm_raw, time) + except DatetimeOutsideRange: + raise TryToKeepGoingError # If only downloading, exit now if download_only: @@ -89,11 +89,9 @@ def prepareWeatherModel( ) containment = weather_model.checkContainment(ll_bounds) - if not containment and weather_model.Model() in 'GMAO ERA5 ERA5T HRES'.split(): - msg = 'The weather model passed does not cover all of the input ' \ - 'points; you may need to download a larger area.' - logger.error(msg) - raise RuntimeError(msg) + if not containment and weather_model.Model() not in 'HRRR'.split(): + raise ExistingWeatherModelTooSmall + return f # Logging some basic info @@ -131,17 +129,14 @@ def prepareWeatherModel( except Exception as e: logger.exception("Unable to save weathermodel to file") logger.exception(e) - raise RuntimeError("Unable to save weathermodel to file") + raise CriticalError finally: wm = weather_model.Model() del weather_model - if not containment and wm in 'GMAO ERA5 ERA5T HRES'.split(): - msg = 'The weather model passed does not cover all of the input ' \ - 'points; you may need to download a larger area.' - logger.error(msg) - raise RuntimeError(msg) + if not containment and wm not in 'HRRR'.split(): + raise ExistingWeatherModelTooSmall else: return f