-
Notifications
You must be signed in to change notification settings - Fork 2
/
pdsi.m
276 lines (239 loc) · 9.34 KB
/
pdsi.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
function[X, Xm, Z, PE] = pdsi(T, P, years, lats, awcs, awcu, cafecYears, dim, showprogress)
%% Calculates PDSI and other fields
%
% [X, Xm] = pdsi(T, P, years, lats, awcs, awcu, cafecYears)
% Calculates the Palmer Drought Severity Index and modified Palmer Drought
% Severity Index given monthly temperature and precipitation data from a
% collection of sites.
%
% [X, Xm, Z, PE] = pdsi(...)
% Also return Z indices and potential evaportranspiration.
%
% [...] = pdsi(..., dim)
% Specify which dimension of the temperature and precipitation data is the
% monthly time step. By default, pdsi treats the first dimension as time.
%
% [...] = pdsi(..., dim, showprogress)
% Specify whether to show a progress bar. By default, does not display a
% progress bar.
%
% ----- Inputs -----
%
% T: Monthly temperature data (in Celsius) for a collection of sites. A
% numeric array that cannot contain NaN, Inf, or complex values. Assumed
% to progress from January to December in each included year.
%
% P: Monthly precipitation data (in mm/month) for a collection of sites. A
% numeric array that cannot contain NaN, Inf, or complex values. Must
% progress from January to December in each included year. Must have the
% same size as the temperature data.
%
% years: The first and last year of the T and P data. A two element vector.
%
% lats: The latitudes of the sites. A numeric array. Must have the same size
% as the temperature data, except for the monthly time dimension, which
% must have a length of 1.
%
% awcs: The available water capacity of the surface layer for each site
% (in mm). A common default value is 25.4 mm. A numeric array. Must have
% the same size as lats.
%
% awcu: The available water capacity of the underlying layer for each
% site (in mm). A common default value is 127 mm. A numeric array. Must
% have the same size as lats.
%
% cafecYears: The first and last year of the calibration period used to
% compute CAFEC normalization. A two element vector.
%
% showprogress: A scalar logical indicating whether to display a progress
% bar (true) or not (false).
%
% ----- Outputs -----
%
% X: Palmer Drought Severity Index for each site over time.
%
% Xm: Modified Palmer Drought Severity Index for each site over time.
%
% Z: The Z indices used to calculate PDSI for each site over time.
%
% PE: The computed potential evapotranspiration for each site
% over time in mm/month.
% ----- Written By -----
% Based on a Matlab function by Dave Meko
% Updated and vectorized by Jonathan King, 2020
%% Setup
% Error check and determine progress bar
if ~exist('showprogress','var') || isempty(showprogress)
showprogress = false;
end
assert(isscalar(showprogress) && islogical(showprogress), 'showprogress must be a scalar logical');
% Error check dim and determine time dimension
if ~exist('dim','var') || isempty(dim)
dim = 1;
end
assert(isscalar(dim), 'dim must be scalar');
assert(mod(dim,1)==0 & dim>0, 'dim must be a positive integer');
% Error check numeric data type
args = {T, P, years, lats, awcs, awcu, cafecYears, dim};
names = {'T','P','years','lats','awcs','awcu','calibrationYears','dim'};
for k = 1:numel(args)
assert(isnumeric(args{k}) & ~any(isnan(args{k}(:))) & ~any(isinf(args{k}(:))) & all(isreal(args{k}(:))), ...
sprintf('%s must be numeric and cannot contain NaN, Inf, or complex values', names{k}));
end
% Error check sizes for data and years
fullSize = size(T);
assert( mod(fullSize(dim),12)==0, sprintf(['The monthly time dimension (dimension %.f), ',...
'must have a length that is divisible by 12 (Each year must have 12 months)'], dim));
assert(isequal(fullSize, size(P)), 'T and P must have the same size');
assert(isvector(years) & numel(years)==2, 'years must be a vector with two elements');
assert(isvector(cafecYears) & numel(cafecYears)==2, ...
'calibrationYears must be a vector with two elements');
% Error check sizes for inputs that require a singleton time dimension
siz = fullSize;
siz(dim) = 1;
nDims = numel(siz);
for k = 4:6
argSize = size(args{k});
argSize(end+1:nDims) = 1;
assert(isequal(siz, argSize), sprintf(['The size of %s must match the size ',...
'of T, except for the monthly time dimension (dimension %.f), which ',...
'must have a length of 1.'], names{k}, dim));
end
% Error check bounded quantities
assert(all(P(:)>=0), 'P cannot have values less than 0');
assert(all(lats(:)>=-90 & lats<=90), 'Values in lats must be between -90 and 90 (inclusive).');
assert(all(awcs(:)>=0), 'awcs cannot have values less than 0');
assert(all(awcu(:)>=0), 'awcu cannot have values less than 0');
% Error check year args
assert(years(2)>=years(1), 'The second element of years cannot be smaller than the first element');
assert(cafecYears(2)>=cafecYears(1), 'The second element of calibrationYears cannot be smaller than the first element');
assert(all(mod(years,1)==0), 'The elements of years must be integers');
assert(all(mod(cafecYears,1)==0), 'The elements of calibrationYears must be integers');
% Get the years and error check size
years = (years(1):years(2))';
nYears = numel(years);
assert(fullSize(dim)/12==nYears, sprintf(['The number of listed years (%.f), ',...
'does not match the number of years in T (%.f)'], nYears, fullSize(dim)/12));
% Get the calibration period and error check
assert(all(ismember(cafecYears, years)), 'The calibration years must be within the years of the data');
calib = years>=cafecYears(1) & years<=cafecYears(2);
nCalib = sum(calib);
% Permute inputs so that time is along the first dimension
order = 1:nDims;
order(1) = dim;
order(dim) = 1;
T = permute(T, order);
P = permute(P, order);
awcs = permute(awcs, order);
awcu = permute(awcu, order);
lats = permute(lats, order);
% Reshape N-dimensional arrays to matrix form
fullSize = fullSize(order);
fullMatrix = [fullSize(1), prod(fullSize(2:end))];
T = reshape(T, fullMatrix);
P = reshape(P, fullMatrix);
siz = siz(order);
sizMatrix = [siz(1), prod(siz(2:end))];
lats = reshape(lats, sizMatrix);
awcs = reshape(awcs, sizMatrix);
awcu = reshape(awcu, sizMatrix);
%% Calculations
% Convert mm to inches (for CAFEC and soil moisture calculations)
P = P * (1/25.4);
awcs = awcs * (1/25.4);
awcu = awcu * (1/25.4);
% Separate months from years
[nTime, nSite] = size(T);
T = reshape(T, [12, nYears, nSite]);
P = reshape(P, [12, nYears, nSite]);
% Compute potential evapotranspiration via the Thornthwaite method
PE = pethorn(T, years, lats, calib);
% Get monthly means from the calibration period.
Pcalib = P(:,calib,:);
PEcalib = PE(:,calib,:);
Pmean = mean(Pcalib, 2);
PEmean = mean(PEcalib, 2);
% Initialize the soild moisture model by running it 10 years on the monthly
% means. Assume saturated starting conditions.
nInitial = 10;
Pi = repmat(squeeze(Pmean), [nInitial, 1]);
PEi = repmat(squeeze(PEmean), [nInitial, 1]);
s = soilMoisture(Pi, PEi, awcs, awcu, awcs, awcu, 1);
% Use the soil moisture in the last year of the initialization as the
% starting condition for additional runs of the soil moisture model
ssi = s.ssi(end-11,:);
sui = s.sui(end-11,:);
% Run the soil moisture model over the calibration period.
Pcalib = reshape(Pcalib, [12*nCalib, nSite]);
PEcalib = reshape(PEcalib, [12*nCalib, nSite]);
s = soilMoisture(Pcalib, PEcalib, awcs, awcu, ssi, sui, 2);
% Get the monthly means for recharge, runoff and loss from the soil
% moisture calibration period
et = mean(reshape(s.et, [12, nCalib, nSite]), 2);
r = mean(reshape(s.r, [12, nCalib, nSite]), 2);
pr = mean(reshape(s.pr, [12, nCalib, nSite]), 2);
ro = mean(reshape(s.ro, [12, nCalib, nSite]), 2);
pro = mean(reshape(s.pro, [12, nCalib, nSite]), 2);
loss = mean(reshape(s.loss, [12, nCalib, nSite]), 2);
ploss = mean(reshape(s.ploss, [12, nCalib, nSite]), 2);
% Alpha
alpha = et ./ PEmean;
alpha(PEmean==0 & et==0) = 1;
alpha(PEmean==0 & et~=0) = 0;
% Beta
beta = r ./ pr;
beta(pr==0 & r==0) = 1;
beta(pr==0 & r~=0) = 0;
% Gamma
gamma = ro ./ pro;
gamma(pro==0 & ro==0) = 1;
gamma(pro==0 & ro~=0) = 0;
% Delta
delta = loss ./ ploss;
delta(ploss==0) = 0;
% Run the soil moisture model over the entire time period
P = reshape(P, [nTime, nSite]);
PE = reshape(PE, [nTime, nSite]);
s = soilMoisture(P, PE, awcs, awcu, ssi, sui, 3, showprogress);
pr = reshape(s.pr, [12, nTime/12, nSite]);
pro = reshape(s.pro, [12, nTime/12, nSite]);
ploss = reshape(s.ploss, [12, nTime/12, nSite]);
% Reshape to monthly
P = reshape(P, [12, nYears, nSite]);
PE = reshape(PE, [12, nYears, nSite]);
% Calculate CAFEC precipitation, monthly departures, and monthly K' factors
Pcafec = (alpha.*PE) + (beta.*pr) + (gamma.*pro) - (delta.*ploss);
D = P - Pcafec;
Dmean = mean( abs(D(:,calib,:)), 2);
w = NaN(1,1,nSite);
for k = 1:nSite
num = PEmean(:,:,k) + r(:,:,k) + ro(:,:,k);
denom = Pmean(:,:,k) + loss(:,:,k);
w(:,:,k) = (num' / denom') + 2.8;
end
Kprime = 1.5 * log10(w ./ Dmean) + 0.5;
% Adjust the K' values and use to calculate Z indices
denom = sum(Dmean .* Kprime, 1);
K = (17.67/denom) .* Kprime;
Z = K .* D;
% Compute PDSI from Z indices
Z = reshape(Z, [nTime, nSite]);
[X, Xm] = zPDSI(Z, showprogress);
% Reshape to match size of input grids
X = reshape(X, fullSize);
X = permute(X, order);
if nargout > 1
Xm = reshape(Xm, fullSize);
Xm = permute(Xm, order);
end
if nargout > 2
Z = reshape(Z, fullSize);
Z = permute(Z, order);
end
if nargout > 3
PE = reshape(PE, fullSize);
PE = permute(PE, order);
% Convert PE from inches back to metric (mm/month)
PE = PE * 25.4;
end
end