Skip to content

Commit c529913

Browse files
authored
Merge pull request #1 from UofU-Cryosphere/storm_day_comparison
Storm day fix
2 parents a6f1308 + a3af323 commit c529913

File tree

3 files changed

+255
-70
lines changed

3 files changed

+255
-70
lines changed

smrf/distribute/precipitation.py

+23-23
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
from datetime import timedelta
12

23
import netCDF4 as nc
34
import numpy as np
4-
5+
from dateutil.parser import parse
56
from smrf.distribute import image_data
67
from smrf.envphys import precip, Snow, storms
78
from smrf.utils import utils
@@ -126,35 +127,42 @@ def initialize(self, topo, data, date_time=None):
126127
self.snow_density = np.zeros((topo.ny, topo.nx))
127128
self.storm_days = np.zeros((topo.ny, topo.nx))
128129
self.storm_total = np.zeros((topo.ny, topo.nx))
129-
self.last_storm_day = np.zeros((topo.ny, topo.nx))
130130
self.dem = topo.dem
131131

132132
# Assign storm_days array if given
133133
if self.config["storm_days_restart"] is not None:
134134
self._logger.debug('Reading {} from {}'.format(
135-
'storm_days', self.config['storm_days_restart']))
136-
f = nc.Dataset(self.config['storm_days_restart'], 'r')
135+
'storm_days', self.config["storm_days_restart"])
136+
)
137+
f = nc.Dataset(self.config["storm_days_restart"])
137138
f.set_always_mask(False)
138139

139140
if 'storm_days' in f.variables:
140141
t = f.variables['time']
142+
t_max = t[:].max()
141143
time = nc.num2date(
142-
t[:],
144+
t_max,
143145
t.getncattr('units'),
144146
t.getncattr('calendar'),
145-
only_use_cftime_datetimes=False)
146-
time = np.array(
147-
[ti.replace(tzinfo=self.start_date.tzinfo) for ti in time])
148-
time_ind = np.where(time == self.start_date.to_pydatetime())[0]
147+
only_use_cftime_datetimes=False,
148+
only_use_python_datetimes=True
149+
)
150+
# Check whether the last storm day entry and the start
151+
# of this run is an hour apart (3600 seconds)
152+
max_time = time.replace(tzinfo=self.start_date.tzinfo)
153+
delta_seconds = (
154+
self.start_date.to_pydatetime() - parse(str(max_time))
155+
)
149156

150-
if time_ind.size == 0:
157+
# Python timedelta are handled in seconds and days
158+
if delta_seconds > timedelta(seconds=(self.time_step*60)):
151159
self._logger.warning(
152160
'Invalid storm_days input! Setting to 0.0')
153161
self.storm_days = np.zeros((topo.ny, topo.nx))
154162

155163
else:
156164
# start at index of storm_days - 1
157-
self.storm_days = f.variables['storm_days'][time_ind - 1, :, :][0] # noqa
165+
self.storm_days = f.variables['storm_days'][t_max]
158166
else:
159167
self._logger.error(
160168
'Variable storm_days not in {}'.format(
@@ -401,10 +409,6 @@ def distribute_for_marks2017(self, data, precip_temp, ta, time):
401409
self.percent_snow = perc_snow
402410
self.snow_density = snow_den
403411

404-
# day of last storm, this will be used in albedo
405-
self.last_storm_day = utils.water_day(data.name)[0] - \
406-
self.storm_days - 0.001
407-
408412
def distribute_for_susong1999(self, data, ppt_temp, time):
409413
"""Susong 1999 estimates percent snow and snow density based on
410414
Susong et al, (1999) :cite:`Susong&al:1999`.
@@ -430,11 +434,11 @@ def distribute_for_susong1999(self, data, ppt_temp, time):
430434
stormDays, stormPrecip = storms.time_since_storm(
431435
self.precip,
432436
perc_snow,
437+
storm_days=self.storm_days,
438+
storm_precip=self.storm_total,
433439
time_step=self.time_step/60/24,
434-
mass=self.ppt_threshold,
435-
time=self.time_to_end_storm,
436-
stormDays=self.storm_days,
437-
stormPrecip=self.storm_total)
440+
mass_threshold=self.ppt_threshold,
441+
)
438442

439443
# save the model state
440444
self.percent_snow = perc_snow
@@ -451,10 +455,6 @@ def distribute_for_susong1999(self, data, ppt_temp, time):
451455
self.percent_snow = np.zeros(self.storm_days.shape)
452456
self.snow_density = np.zeros(self.storm_days.shape)
453457

454-
# day of last storm, this will be used in albedo
455-
self.last_storm_day = utils.water_day(data.name)[0] - \
456-
self.storm_days - 0.001
457-
458458
def distribute_thread(self, smrf_queue, data_queue):
459459
"""
460460
Distribute the data using threading and queue. All data is provided and

smrf/envphys/storms.py

+46-47
Original file line numberDiff line numberDiff line change
@@ -2,69 +2,68 @@
22
import pandas as pd
33

44

5-
def time_since_storm(precipitation, perc_snow, time_step=1/24, mass=1.0,
6-
time=4, stormDays=None, stormPrecip=None, ps_thresh=0.5):
5+
def time_since_storm(
6+
precipitation,
7+
percent_snow_precipitation,
8+
storm_days,
9+
storm_precip,
10+
time_step,
11+
mass_threshold=1.0,
12+
percent_snow_threshold=0.5
13+
):
714
"""
8-
Calculate the decimal days since the last storm given a precip time series,
9-
percent snow, mass threshold, and time threshold
15+
Increase or reset the given storm days and storm precipitation based of
16+
given percent snow and mass threshold.
1017
11-
- Will look for pixels where perc_snow > 50% as storm locations
12-
- A new storm will start if the mass at the pixel has exceeded the mass
13-
limit, this ensures that the enough has accumulated
18+
Steps:
19+
- Look for pixels where percent snow precipitation is above the threshold.
20+
- Reset storm days if the precipitation at the pixel is above the mass
21+
threshold or increase the counter if it is below.
22+
- Add the precipitation amount to the storm precipitation.
23+
24+
*NOTE*: Storm day precipitation is initialized and set to 0 with each
25+
processed day. Hence, no cross-day storm tracking is possible if the
26+
period between the days falls below the threshold.
1427
1528
Args:
1629
precipitation: Precipitation values
17-
perc_snow: Percent of precipitation that was snow
30+
percent_snow_precipitation: Percent of precipitation that was snow
31+
storm_days: Storm days to keep track of
32+
storm_precip: Keeps track of the total storm precip
1833
time_step: Step in days of the model run
19-
mass: Threshold for the mass to start a new storm
20-
time: Threshold for the time to start a new storm
21-
stormDays: If specified, this is the output from a previous run of
22-
storms else it will be set to the date_time value
23-
stormPrecip: Keeps track of the total storm precip
34+
(Optional)
35+
mass_threshold: Minimum amount of precipitation required to be a storm
36+
(snow mass). Default: 0.5
37+
percent_snow_threshold: Minimum fraction for values in
38+
`percent_snow_precipitation` to be considered
39+
a snow event. Default: 0.5 (50%)
2440
2541
Returns:
2642
tuple:
27-
- **stormDays** - Array representing the days since the last storm at
28-
a pixel
29-
- **stormPrecip** - Array representing the precip accumulated during
30-
the most recent storm
43+
- **stormDays** - Updated storm days
44+
- **stormPrecip** - Updated storm precipitation
3145
32-
Created Janurary 5, 2016
46+
Created January 5, 2016
3347
@author: Scott Havens
34-
"""
35-
# either preallocate or use the input
36-
if stormDays is None:
37-
stormDays = np.zeros(precipitation.shape)
38-
39-
if stormPrecip is None:
40-
stormPrecip = np.zeros(precipitation.shape)
41-
42-
# if there is no snow, don't reset the counter
43-
# This ensures that the albedo won't be reset
44-
stormDays += 1
45-
if np.sum(perc_snow) == 0:
46-
# stormDays = np.add(stormDays, 1)
47-
stormPrecip = np.zeros(precipitation.shape)
48-
return stormDays, stormPrecip
4948
50-
# determine locations where it has snowed
51-
idx = perc_snow >= ps_thresh
52-
53-
# determine locations where the time threshold has passed
54-
# these areas, the stormPrecip will be set back to zero
55-
idx_time = stormDays >= time
56-
stormPrecip[idx_time] = 0
49+
Updated: February 07, 2022
50+
@author: Joachim Meyer, Dillon Ragar
51+
"""
5752

58-
# add the values to the stormPrecip
59-
stormPrecip[idx] = + precipitation[idx]
53+
# Step 1: Pixels above snow percent threshold
54+
location_index = (percent_snow_precipitation >= percent_snow_threshold)
55+
storm_precip[location_index] += precipitation[location_index]
6056

61-
# see if the mass threshold has been passed
62-
idx_mass = stormPrecip >= mass
57+
# Step 2: Reset locations above mass threshold or increase counter when
58+
# below
59+
location_index = (storm_precip >= mass_threshold)
60+
storm_days[location_index] = 0
61+
storm_days[~location_index] += time_step
6362

64-
# reset the stormDays to zero where the storm is present
65-
stormDays[idx_mass] = 0
63+
# Step 3: Increase the storm precipitation total for the day
64+
storm_precip[~location_index] = 0
6665

67-
return stormDays, stormPrecip
66+
return storm_days, storm_precip
6867

6968

7069
def time_since_storm_pixel(precipitation, dpt, perc_snow, storming,

0 commit comments

Comments
 (0)