agwx_biophys 0.0.1 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/agwx_biophys.gemspec +1 -0
- data/calccumdd.c +944 -0
- data/getstndd.c +186 -0
- data/lib/agwx_biophys.rb +2 -2
- data/lib/agwx_biophys/degree_days.rb +347 -0
- data/lib/agwx_biophys/et.rb +5 -0
- data/lib/agwx_biophys/version.rb +1 -1
- data/test/grids/WIMNTAvg2012 +8057 -0
- data/test/grids/WIMNTMax2012 +8057 -0
- data/test/grids/WIMNTMin2012 +8057 -0
- data/test/rect_dd_seq_2012.rb +105 -0
- data/test/test_dds.rb +96 -0
- metadata +29 -4
- data/test/test_et.rb +0 -232
data/getstndd.c
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include <stdlib.h>
|
3
|
+
#include <string.h>
|
4
|
+
#include <math.h>
|
5
|
+
#include <unistd.h>
|
6
|
+
|
7
|
+
#include <awsdir.h>
|
8
|
+
#include <awstime.h>
|
9
|
+
#include <awserror.h>
|
10
|
+
#include <grid/grid.h>
|
11
|
+
|
12
|
+
#ifndef O_RDWR
|
13
|
+
# define O_RDWR 0000002 /* Open for reading or writing */
|
14
|
+
# define O_CREAT 0000400 /* Open with file create (uses third open arg) */
|
15
|
+
#endif
|
16
|
+
|
17
|
+
Grid * CalcCumDD(char * prefix, int StartDoy,
|
18
|
+
int EndDoy, int Year, int Method, float BaseTemp,
|
19
|
+
int Subset, float xLowSub, float xHighSub, float yLowSub,
|
20
|
+
float yHighSub);
|
21
|
+
|
22
|
+
/*========================================================================
|
23
|
+
* MAIN
|
24
|
+
*/
|
25
|
+
main( int argc, char ** argv)
|
26
|
+
{
|
27
|
+
|
28
|
+
char Sub[40];
|
29
|
+
|
30
|
+
char buf[255];
|
31
|
+
|
32
|
+
char TMinFileName[255];
|
33
|
+
char TMaxFileName[255];
|
34
|
+
char GradCtlFile[100];
|
35
|
+
char GradDatFile[100];
|
36
|
+
char Date[100];
|
37
|
+
char prefix[10];
|
38
|
+
int fd;
|
39
|
+
FILE * fhctl;
|
40
|
+
|
41
|
+
Grid * pTMinG =NULL;
|
42
|
+
Grid * pTMaxG =NULL;
|
43
|
+
Grid * pDDG =NULL;
|
44
|
+
GridCursor * pTMinGC =NULL;
|
45
|
+
GridCursor * pTMaxGC =NULL;
|
46
|
+
GLayer * pDDL =NULL;
|
47
|
+
GLView * pGLV =NULL;
|
48
|
+
GLayer * pGL =NULL;
|
49
|
+
float ** CumDD=NULL;
|
50
|
+
|
51
|
+
int Year;
|
52
|
+
int StartDoy;
|
53
|
+
int EndDoy;
|
54
|
+
int Method;
|
55
|
+
float BaseTemp;
|
56
|
+
|
57
|
+
float TMin;
|
58
|
+
float TMax;
|
59
|
+
int TMinErr;
|
60
|
+
int TMaxErr;
|
61
|
+
float DD;
|
62
|
+
|
63
|
+
int doy;
|
64
|
+
int earlydoy;
|
65
|
+
|
66
|
+
int srcdoy;
|
67
|
+
int TMinDoy,TMaxDoy;
|
68
|
+
float xPos,yPos;
|
69
|
+
int xIdx,yIdx;
|
70
|
+
int FoundMin;
|
71
|
+
int FoundMax;
|
72
|
+
|
73
|
+
int xNo,yNo;
|
74
|
+
float xLow, xHigh, yLow, yHigh;
|
75
|
+
float lon,lat;
|
76
|
+
|
77
|
+
float xLowSub, xHighSub, yLowSub, yHighSub;
|
78
|
+
float xIncr,yIncr;
|
79
|
+
int xStrt,xEnd,yStrt,yEnd;
|
80
|
+
int lStrt,lEnd;
|
81
|
+
float BadValue;
|
82
|
+
int yr, mnth ,day;
|
83
|
+
|
84
|
+
int Subset = FALSE;
|
85
|
+
|
86
|
+
int x,y,l;
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
strcpy(Sub,"GetStnDD:Main");
|
91
|
+
|
92
|
+
StartConPrint();
|
93
|
+
|
94
|
+
/*-------------------------------
|
95
|
+
* Check Parameters
|
96
|
+
*/
|
97
|
+
if ( !(argc==9) ) {
|
98
|
+
goto USAGE;
|
99
|
+
}
|
100
|
+
Year = atoi(argv[1]);
|
101
|
+
StartDoy = atoi(argv[2]);
|
102
|
+
EndDoy = atoi(argv[3]);
|
103
|
+
if ( !(strlen(argv[4])==4 &&
|
104
|
+
( argv[4][0]=='R' || argv[4][0]=='S' || argv[4][0]=='M'
|
105
|
+
|| argv[4][0]=='P' ) ) ) {
|
106
|
+
printf("Method Must be 'Rect' or 'Sine' or 'ModB' or 'PDay' \n");
|
107
|
+
goto USAGE;
|
108
|
+
}
|
109
|
+
switch (argv[4][0]) {
|
110
|
+
case 'R' :
|
111
|
+
Method = GRD_DD_RECTANGULAR;
|
112
|
+
break;
|
113
|
+
case 'S' :
|
114
|
+
Method = GRD_DD_SINE_WAVE;
|
115
|
+
break;
|
116
|
+
case 'M' :
|
117
|
+
Method = GRD_DD_MODIFIED_BASE;
|
118
|
+
break;
|
119
|
+
case 'P' :
|
120
|
+
Method = GRD_DD_PDAY;
|
121
|
+
break;
|
122
|
+
}
|
123
|
+
BaseTemp = atof(argv[5]);
|
124
|
+
sprintf(prefix,argv[6]);
|
125
|
+
lat = atof(argv[7]);
|
126
|
+
lon = atof(argv[8]);
|
127
|
+
|
128
|
+
Subset=TRUE;
|
129
|
+
xLowSub = lon-0.5;
|
130
|
+
xHighSub = lon+0.5;
|
131
|
+
yLowSub = lat-0.5;
|
132
|
+
yHighSub = lat+0.5;
|
133
|
+
|
134
|
+
if (StartDoy <1 || StartDoy > 366) {
|
135
|
+
RegErr(Sub,"","StartDoy Should be 1-366");
|
136
|
+
goto USAGE;
|
137
|
+
}
|
138
|
+
if (EndDoy <1 || EndDoy > 366) {
|
139
|
+
RegErr(Sub,"","EndDoy Should be 1-366");
|
140
|
+
goto USAGE;
|
141
|
+
}
|
142
|
+
if (BaseTemp < 30 || BaseTemp > 90 ) {
|
143
|
+
RegErr(Sub,"",
|
144
|
+
"Base Temp Should be in Deg F, <30 or >90 seems unreasonable");
|
145
|
+
goto USAGE;
|
146
|
+
}
|
147
|
+
|
148
|
+
/*----------------------------------
|
149
|
+
* Compute the Cumulative DD
|
150
|
+
*/
|
151
|
+
if ( (pDDG=CalcCumDD(prefix,StartDoy,EndDoy,Year,Method,
|
152
|
+
BaseTemp,Subset,xLowSub,xHighSub,yLowSub,
|
153
|
+
yHighSub)) == NULL ) {
|
154
|
+
RegErr(Sub,"","Error computing cumulative DD");
|
155
|
+
goto ERROR;
|
156
|
+
}
|
157
|
+
if ( (pDDL=GetGLayer(pDDG,1)) == NULL ) {
|
158
|
+
RegErr(Sub,"","Error retrieving layer from cumulative DD grid");
|
159
|
+
goto ERROR;
|
160
|
+
}
|
161
|
+
|
162
|
+
|
163
|
+
/*---------------------------------------
|
164
|
+
* Get the DD at lat,lon and print
|
165
|
+
*/
|
166
|
+
GetGLVal(pDDG,pDDL,lon,lat,&DD);
|
167
|
+
printf("%.0f",DD);
|
168
|
+
|
169
|
+
EndConPrint();
|
170
|
+
return AWS_OK;
|
171
|
+
|
172
|
+
ERROR:
|
173
|
+
|
174
|
+
EndConPrint();
|
175
|
+
return AWS_FAIL;
|
176
|
+
|
177
|
+
USAGE:
|
178
|
+
printf(
|
179
|
+
"Usage: %s Year StartDoy EndDoy {Rect|ModB|Sine|Pday} BaseTemp TempPrefix Lat Long\n"
|
180
|
+
, argv[0]);
|
181
|
+
printf(
|
182
|
+
"Example: %s 96 66 73 ModB 50 Wi 43 -90\n"
|
183
|
+
,argv[0]);
|
184
|
+
EndConPrint();
|
185
|
+
return AWS_FAIL;
|
186
|
+
}
|
data/lib/agwx_biophys.rb
CHANGED
@@ -0,0 +1,347 @@
|
|
1
|
+
module AgwxBiophys
|
2
|
+
module DegreeDays
|
3
|
+
|
4
|
+
# return an array so that we can access arr[0..yDim][0..xDim]
|
5
|
+
def cumulate_array(value=0.0)
|
6
|
+
arr = Array.new(Y_DIM)
|
7
|
+
for y in 0..MAX_Y_INDEX
|
8
|
+
arr[y] = Array.new(X_DIM,value)
|
9
|
+
end
|
10
|
+
arr
|
11
|
+
end
|
12
|
+
|
13
|
+
# Degree-day calcs taken from prg/src/tisdat/calccumdd.c
|
14
|
+
|
15
|
+
# Calculate rect DD from min and max for this day. Everything is in Fahrenheit.
|
16
|
+
def rect_DD(min,max,base=50)
|
17
|
+
rect = ((max + min) / 2.0) - base
|
18
|
+
# Never return a negative number
|
19
|
+
# puts "rect_dd for #{min},#{max},#{base},#{upper} returning #{rect}"
|
20
|
+
rect >= 0.0 ? rect : 0.0
|
21
|
+
end
|
22
|
+
|
23
|
+
def rect_DD_from_avg(avg,base=50)
|
24
|
+
rect = avg - base
|
25
|
+
rect >= 0.0 ? rect : 0.0
|
26
|
+
end
|
27
|
+
|
28
|
+
def modB_DD(min,max,base=50,upper=86)
|
29
|
+
min = [base,min].max
|
30
|
+
max = [base,max].max
|
31
|
+
|
32
|
+
min = [min,upper].min
|
33
|
+
max = [max,upper].min
|
34
|
+
rect_DD(min,max,base)
|
35
|
+
end
|
36
|
+
|
37
|
+
DD_MAX_TEMP = 86
|
38
|
+
M_1_PI = 0.31830988618379067154
|
39
|
+
|
40
|
+
def sine_DD(min,max,base=50,upper=DD_MAX_TEMP)
|
41
|
+
alpha = nil
|
42
|
+
o1 = o2 = nil
|
43
|
+
dd = nil
|
44
|
+
avg = (min + max) / 2.0
|
45
|
+
#
|
46
|
+
# Source Degree-Days: The Calculation and Use
|
47
|
+
# of Heat Units in Pest management
|
48
|
+
# Original returns an error if min > max
|
49
|
+
# if (TMin > TMax) {
|
50
|
+
# RegErr("CalcDayDD:SineDD:","","Tmin > Tmax");
|
51
|
+
# return -9999999;
|
52
|
+
return upper - base if (min >= upper)
|
53
|
+
return 0 if (max <= base)
|
54
|
+
return avg - base if (max <= upper && min >= base)
|
55
|
+
|
56
|
+
alpha = (max-min)/2;
|
57
|
+
if (max <= upper && min < base)
|
58
|
+
o1 = Math.asin( (base-avg)/alpha)
|
59
|
+
return M_1_PI*( (avg-base) * (Math::PI / 2-o1) + alpha*Math.cos(o1) )
|
60
|
+
end
|
61
|
+
|
62
|
+
if (max > upper && min >= base)
|
63
|
+
o2 = Math.asin( (upper-avg)/alpha);
|
64
|
+
return M_1_PI*( (avg-base) * (o2+M_1_PI) +
|
65
|
+
(upper-base) * (Math::PI / 2-o2) - alpha*Math.cos(o2) )
|
66
|
+
end
|
67
|
+
|
68
|
+
if (max > upper && min < base)
|
69
|
+
o1 = Math.asin( (base-avg)/alpha);
|
70
|
+
o2 = Math.asin( (upper-avg)/alpha);
|
71
|
+
return M_1_PI*( (avg-base)*(o2-o1) + alpha*(Math.cos(o1)-Math.cos(o2))
|
72
|
+
+ (upper-base)*(Math::PI / 2-o2) )
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def cumulate(min_temp_hash,max_temp_hash,start_doy,end_doy,base,upper=86)
|
78
|
+
prev = 0
|
79
|
+
days_found = 0
|
80
|
+
cumulation = (start_doy..end_doy).inject(0) do |sum,doy|
|
81
|
+
# Need a better interpolation algorithm here!
|
82
|
+
if min_temp_hash[doy] && max_temp_hash[doy]
|
83
|
+
days_found += 1
|
84
|
+
prev = yield(min_temp_hash[doy],max_temp_hash[doy],base,upper)
|
85
|
+
else
|
86
|
+
raise "doy #{doy} not found!"
|
87
|
+
end
|
88
|
+
sum + prev
|
89
|
+
end
|
90
|
+
[cumulation,days_found]
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
def to_fahrenheit(celsius)
|
95
|
+
(celsius * (9.0 / 5.0)) + 32.0
|
96
|
+
end
|
97
|
+
|
98
|
+
def frost?(grid,longi_index,lati_index,doy,frost_value)
|
99
|
+
val = grid.get_by_index(longi_index,lati_index,doy)
|
100
|
+
return false unless val
|
101
|
+
return false unless val != grid.mD.badVal
|
102
|
+
return (val <= frost_value)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Find the date of first frost -- defined as the first day after July 1 with a min temp below 0 C --
|
106
|
+
# for each grid point for each year. Return a hash keyed by year, of X by Y arrays of DOYs.
|
107
|
+
def frost_doys(min_grids,start_year=START_YEAR,end_year=END_YEAR,frost_value=0.0)
|
108
|
+
frost_doys = {}
|
109
|
+
for year in (start_year..end_year)
|
110
|
+
frost_doys[year] = cumulate_array(NO_DOY)
|
111
|
+
# We start with 0 grid cells at frost (it's July, after all). When the number reaches
|
112
|
+
# NUM_GRID_CELLS, we know we've seen frost everywhere on the grid and can safely
|
113
|
+
# terminate the loop.
|
114
|
+
num_frosty_grid_cells = 0
|
115
|
+
# Can't use START_DOY or END_DOY here since frost might extend outside the growing season
|
116
|
+
(START_OF_JULY..366).each do |doy|
|
117
|
+
if min_grids[year].get_by_index(0,0,doy)
|
118
|
+
for lati_index in (0..MAX_Y_INDEX)
|
119
|
+
for longi_index in (0..MAX_X_INDEX)
|
120
|
+
if frost_doys[year][lati_index][longi_index] == NO_DOY && frost?(min_grids[year],longi_index,lati_index,doy,frost_value)
|
121
|
+
frost_doys[year][lati_index][longi_index] = doy
|
122
|
+
num_frosty_grid_cells += 1
|
123
|
+
break if num_frosty_grid_cells >= NUM_GRID_CELLS
|
124
|
+
end
|
125
|
+
break if num_frosty_grid_cells >= NUM_GRID_CELLS
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
frost_doys
|
132
|
+
end
|
133
|
+
|
134
|
+
def cell_series(grid_hash,lati_index,longi_index)
|
135
|
+
series = []
|
136
|
+
grid_hash.keys.sort.each do |key|
|
137
|
+
series << grid_hash[key][lati_index][longi_index]
|
138
|
+
end
|
139
|
+
series
|
140
|
+
end
|
141
|
+
|
142
|
+
def median(array)
|
143
|
+
return nil unless array && array.size > 0 && (array = array.compact).size > 0
|
144
|
+
sorted = array.sort
|
145
|
+
len = sorted.length
|
146
|
+
return (sorted[(len - 1) / 2] + sorted[len / 2]) / 2.0
|
147
|
+
end
|
148
|
+
|
149
|
+
def build_averages(min_grids,max_grids,start_year=START_YEAR,end_year=END_YEAR)
|
150
|
+
averages = {}
|
151
|
+
layers_for_doy = {}
|
152
|
+
# print "building averages. "
|
153
|
+
# check how many years have data for each doy
|
154
|
+
(START_DOY..END_DOY).each do |doy|
|
155
|
+
for year in (start_year..end_year)
|
156
|
+
# Does this year have this DOY?
|
157
|
+
if min_grids[year].get_by_index(0,0,doy)
|
158
|
+
if layers_for_doy[doy]
|
159
|
+
layers_for_doy[doy] += 1
|
160
|
+
else
|
161
|
+
layers_for_doy[doy] = 1
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
(START_DOY..END_DOY).each do |doy|
|
167
|
+
# print "doy #{doy}.."; $stdout.flush
|
168
|
+
averages[doy] = cumulate_array
|
169
|
+
|
170
|
+
for lati_index in (0..MAX_Y_INDEX)
|
171
|
+
for longi_index in (0..MAX_X_INDEX)
|
172
|
+
for year in (start_year..end_year)
|
173
|
+
# Does this year have this DOY?
|
174
|
+
if min = min_grids[year].get_by_index(longi_index,lati_index,doy)
|
175
|
+
max = max_grids[year].get_by_index(longi_index,lati_index,doy)
|
176
|
+
begin
|
177
|
+
averages[doy][lati_index][longi_index] += (max + min) / 2.0
|
178
|
+
if lati_index == 2 && longi_index == 22
|
179
|
+
# puts "year #{year} doy #{doy}, min #{min} max #{max} avg #{averages[doy][lati_index][longi_index]}"
|
180
|
+
end
|
181
|
+
rescue Exception => e
|
182
|
+
puts "BORKED!\nlati #{lati_index}, longi #{longi_index}, avgs[doy] #{averages[doy].inspect}"
|
183
|
+
raise e
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
# print "\ndoing the averaging. "
|
191
|
+
# Now we have the sum of all the existing daily average temps. Divide by the number
|
192
|
+
# of layers that had that DOY.
|
193
|
+
(START_DOY..END_DOY).each do |doy|
|
194
|
+
"doy #{doy}.."; $stdout.flush
|
195
|
+
for lati_index in (0..MAX_Y_INDEX)
|
196
|
+
for longi_index in (0..MAX_X_INDEX)
|
197
|
+
begin
|
198
|
+
raise 'zero in lfd' if layers_for_doy[doy] == 0
|
199
|
+
averages[doy][lati_index][longi_index] /= layers_for_doy[doy]
|
200
|
+
rescue Exception => e
|
201
|
+
puts "BORKED on division! doy #{doy}, lati_index #{lati_index}, longi_index #{longi_index}, lfd[doy] #{layers_for_doy[doy]}"
|
202
|
+
puts averages[doy][lati_index].inspect
|
203
|
+
raise e
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
# puts ""
|
209
|
+
averages
|
210
|
+
end
|
211
|
+
|
212
|
+
# def cumulate(min_grids,max_grids,averages,start_year=START_YEAR,end_year=END_YEAR,base=10.0,upper=30.0,fahrenheit=false)
|
213
|
+
# # construct a set of DD arrays, one per year
|
214
|
+
# yearly_DD_accums = {}
|
215
|
+
# # print "cumulating. "
|
216
|
+
# for year in start_year..end_year
|
217
|
+
# yearly_DD_accums[year] = cumulate_array
|
218
|
+
# # print "#{year}.."; $stdout.flush
|
219
|
+
# for doy in (START_DOY..END_DOY)
|
220
|
+
# for lati_index in (0..MAX_Y_INDEX)
|
221
|
+
# for longi_index in (0..MAX_X_INDEX)
|
222
|
+
# begin
|
223
|
+
# min = min_grids[year].get_by_index(longi_index,lati_index,doy) || averages[doy][lati_index][longi_index]
|
224
|
+
# max = max_grids[year].get_by_index(longi_index,lati_index,doy) || averages[doy][lati_index][longi_index]
|
225
|
+
# if fahrenheit
|
226
|
+
# min = to_fahrenheit(min)
|
227
|
+
# max = to_fahrenheit(max)
|
228
|
+
# base = to_fahrenheit(base) if base == 10.0
|
229
|
+
# upper = to_fahrenheit(upper) if upper == 30.0
|
230
|
+
# end
|
231
|
+
# if block_given?
|
232
|
+
# dd = yield(min,max,base,upper)
|
233
|
+
# else
|
234
|
+
# dd = rect_DD(min,max,base,upper)
|
235
|
+
# end
|
236
|
+
# yearly_DD_accums[year][lati_index][longi_index] += dd
|
237
|
+
# rescue Exception => e
|
238
|
+
# puts "cumulate: problem at #{year}, #{doy}, #{lati_index}, #{longi_index}"
|
239
|
+
# raise e
|
240
|
+
# end
|
241
|
+
# end
|
242
|
+
# end
|
243
|
+
# end
|
244
|
+
# end
|
245
|
+
# # puts ""
|
246
|
+
# yearly_DD_accums
|
247
|
+
# end
|
248
|
+
|
249
|
+
def year_dd_grid(max_grids,min_grids,year)
|
250
|
+
return {1 => 4, 6 => 5}
|
251
|
+
end
|
252
|
+
|
253
|
+
# For a given year, return a hash of DOYs at a grid point with the daily DDs for each, using the passed-in block to calculate a day's DD
|
254
|
+
def dds_for_year_at_point(p)
|
255
|
+
calculate_fahrenheit = p[:calculate_fahrenheit] || true
|
256
|
+
base = p[:base] || 50.0
|
257
|
+
upper = p[:upper] || 86.0
|
258
|
+
start_doy = p[:start_doy] || START_DOY # May 1
|
259
|
+
[:max_grids,:min_grids,:year,:lati_index,:longi_index].each { |sym| raise "Required param #{sym.to_s} missing" unless p[sym] }
|
260
|
+
doy_dds = {} # Will be a Hash keyed by DOY
|
261
|
+
|
262
|
+
(start_doy..366).each do |doy|
|
263
|
+
min = p[:min_grids][p[:year]].get_by_index(p[:longi_index],p[:lati_index],doy)
|
264
|
+
max = p[:max_grids][p[:year]].get_by_index(p[:longi_index],p[:lati_index],doy)
|
265
|
+
next unless min && max
|
266
|
+
if block_given?
|
267
|
+
doy_dds[doy] = yield(to_fahrenheit(min),to_fahrenheit(max),base,upper)
|
268
|
+
else
|
269
|
+
doy_dds[doy] = modB_DD(to_fahrenheit(min),to_fahrenheit(max),base,upper)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
doy_dds
|
273
|
+
end
|
274
|
+
|
275
|
+
def frost_doy(grid,longi_index,lati_index,frost_value=0.0)
|
276
|
+
(START_OF_JULY..366).each do |doy|
|
277
|
+
min = grid.get_by_index(longi_index,lati_index,doy)
|
278
|
+
if min && min <= frost_value
|
279
|
+
return doy
|
280
|
+
end
|
281
|
+
end
|
282
|
+
nil
|
283
|
+
end
|
284
|
+
|
285
|
+
def remaining_dds_for_year_at_point(dds_at_point,frost_doy,start_doy=120)
|
286
|
+
remaining_dds = {}
|
287
|
+
sum = 0
|
288
|
+
frost_doy.to_i.downto(start_doy) do |doy|
|
289
|
+
next unless dds_at_point[doy]
|
290
|
+
sum += dds_at_point[doy]
|
291
|
+
remaining_dds[doy] = sum
|
292
|
+
end
|
293
|
+
remaining_dds
|
294
|
+
end
|
295
|
+
|
296
|
+
# Return a hash of remaining-DD values for a grid point up to the first frost for that year.
|
297
|
+
# Most of the params get passed unchanged to dds_for_year_at_point, which handles defaults and such.
|
298
|
+
# FIXME: Extend interface to include a passed-in block for DD calcs. For now,
|
299
|
+
# just uses dds_for_year_at_point's, which is ModB / 50.0 / 86.0
|
300
|
+
def pre_frost_remaining_dds_at_point(p,proc=nil)
|
301
|
+
|
302
|
+
frost_value = p[:frost_value] || 0.0
|
303
|
+
[:max_grids,:min_grids,:year,:lati_index,:longi_index].each { |sym| raise "Required param #{sym.to_s} missing" unless p[sym] }
|
304
|
+
fd = frost_doy(p[:min_grids][p[:year]],p[:longi_index],p[:lati_index],frost_value)
|
305
|
+
return nil unless fd
|
306
|
+
if block_given?
|
307
|
+
dds = dds_for_year_at_point(p,&proc)
|
308
|
+
else
|
309
|
+
dds = dds_for_year_at_point(p)
|
310
|
+
end
|
311
|
+
[remaining_dds_for_year_at_point(dds,fd),fd]
|
312
|
+
end
|
313
|
+
|
314
|
+
# Return a hash, by doy, of the median remaining-DD value for that doy over all years for a point
|
315
|
+
def median_pre_frost_remaining_dds(p)
|
316
|
+
# hash by year of per-day remaining DDs
|
317
|
+
remaining = {}
|
318
|
+
[:min_grids,:max_grids,:years,:longi_index,:lati_index].each { |sym| raise "Required param #{sym.to_s} missing" unless p[sym] }
|
319
|
+
# Assemble a dataset of remaining DDs for this point; it'll be a hash (by year) of hashes (by doy) of remaining DD
|
320
|
+
p[:years].each do |year|
|
321
|
+
p[:year] = year # Hack the param hash so it's good to go for pre_frost_remaining_dds_at_point
|
322
|
+
remaining[year],frost_doy = pre_frost_remaining_dds_at_point(p)
|
323
|
+
end
|
324
|
+
# Iterate over each possible DOY, find the median for that DOY. Will pointlessly access many nonexistent DOYs after
|
325
|
+
# first frost, but that means we don't terminate prematurely if a DOY happens to be missing
|
326
|
+
medians = {}
|
327
|
+
(START_DOY..366).each do |doy|
|
328
|
+
dds_for_doy = (p[:years].collect {|year| remaining[year][doy]}).compact
|
329
|
+
next unless dds_for_doy.size > 0
|
330
|
+
medians[doy] = median(dds_for_doy)
|
331
|
+
end
|
332
|
+
medians
|
333
|
+
end
|
334
|
+
|
335
|
+
def print_samples(yearly_DD_accums,start_year=START_YEAR,end_year=END_YEAR)
|
336
|
+
for year in start_year..end_year
|
337
|
+
print "\n#{year}:"
|
338
|
+
(0..30).step(5) do |longi_index|
|
339
|
+
(0..20).step(5) do |lati_index|
|
340
|
+
print "#{yearly_DD_accums[year][longi_index][lati_index]}, "
|
341
|
+
end
|
342
|
+
end
|
343
|
+
puts ""
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|