solar 0.0.2 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,65 @@
1
+ module Solar
2
+ class <<self
3
+ # Day-night (or twilight) status at a given position and time.
4
+ # Returns +:night+, +:day+ or +:twilight+
5
+ #
6
+ # Options:
7
+ #
8
+ # * +:twilight_zenith+ is the zenith for the sun at dawn
9
+ # (at the beginning of twilight)
10
+ # and at dusk (end of twilight). Default value is +:civil+
11
+ # * +:day_zenith+ is the zenith for the san at sunrise and sun set.
12
+ # Default value is +:official+ (sun aparently under the horizon,
13
+ # tangent to it)
14
+ #
15
+ # These parameters can be assigned zenith values in degrees or these
16
+ # symbols: +:official+, +:civil+, +:nautical+ or +:astronomical+.
17
+ #
18
+ # A simple day/night result (returning either +:day+ or +:night+)
19
+ # can be requested by setting the +:simple+ option to true
20
+ # (which usses the official day definition),
21
+ # or by setting a +:zenith+ parameter to define the kind of day-night
22
+ # distinction.
23
+ #
24
+ # By setting the +:detailed+ option to true, the result will be one of:
25
+ # +:night+, +:astronomical_twilight+, +:nautical_twilight+,
26
+ # +:civil_twilight+, +:day+
27
+ #
28
+ def day_or_night(t, longitude, latitude, options = {})
29
+ h, _ = position(t, longitude, latitude)
30
+ options = { zenith: :official } if options[:simple]
31
+ if options[:detailed]
32
+ if h < ALTITUDES[:astronomical]
33
+ :night
34
+ elsif h < ALTITUDES[:nautical]
35
+ :astronomical_twilight
36
+ elsif h < ALTITUDES[:civil]
37
+ :nautical_twilight
38
+ elsif h < ALTITUDES[:official]
39
+ :civil_twilight
40
+ else
41
+ :day
42
+ end
43
+ else
44
+ # Determined :night / :twilight / :day state;
45
+ # twilight/day definition can be changed with options :zenith or :twilight_zenith, :day_zenith
46
+ if options[:zenith]
47
+ # only :day / :night distinction as defined by :zenith
48
+ twilight_altitude = day_altitude = altitude_from_options(options)
49
+ else
50
+ twilight_altitude = altitude_from_options(
51
+ zenith: options[:twilight_zenith] || :civil
52
+ )
53
+ day_altitude = altitude_from_options(
54
+ zenith: options[:day_zenith] || :official
55
+ )
56
+ end
57
+ if h > day_altitude
58
+ :day
59
+ else
60
+ h <= twilight_altitude ? :night : :twilight
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,68 @@
1
+ module Solar
2
+ class <<self
3
+
4
+ # slope, sun_azimuth: degrees (0: horizontal; 90: vertical)
5
+ # aspect: degrees from North towards East
6
+ # sun_elevation: degrees (0 on horizon, 90 on zenith)
7
+ def illumination_factor(sun_azimuth, sun_elevation, slope, aspect)
8
+ s = sun_vector(sun_azimuth, sun_elevation)
9
+ nh = vertical_vector
10
+ n = normal_from_slope_aspect(slope, aspect)
11
+ # Problem: when sun near horizon...
12
+ f = dot(s, n) / dot(s, nh)
13
+ f < 0 ? 0.0 : f
14
+ end
15
+
16
+ def illumination_factor_at(t, longitude, latitude, slope, aspect)
17
+ sun_elevation, sun_azimuth = Solar.position(t, longitude, latitude)
18
+ return 0.0 if sun_elevation < 0 # should return 1.0, really
19
+ illumination_factor(sun_azimuth, sun_elevation, slope, aspect)
20
+ end
21
+
22
+ def mean_illumination_factor_on(date, latitude, slope, aspect, options = {})
23
+ t1, t2, t3 = Solar.passages(date, 0, latitude, altitude: 24.0)
24
+ if options[:noon]
25
+ return factor_at(t2, 0.0, latitude, slope, aspect)
26
+ end
27
+ if n = options[:n]
28
+ dt = (t3 - t1).to_f*24*3600/n
29
+ else
30
+ dt = (options[:dt] || 60*30.0).to_f
31
+ end
32
+ f = 0.0
33
+ n = 0
34
+ # max_f = 0.0
35
+ (t1..t3).step(dt).each do |t|
36
+ f += illumination_factor_at(Time.at(t), 0.0, latitude, slope, aspect)
37
+ n += 1
38
+ end
39
+ f / n
40
+ end
41
+
42
+ private
43
+
44
+ def dot(u, v)
45
+ u[0]*v[0] + u[1]*v[1] + u[2]*v[2]
46
+ end
47
+
48
+ # Vertical unitary vector (normal to the horizontal plane)
49
+ def vertical_vector
50
+ [0, 0, 1]
51
+ end
52
+
53
+ # normal unitary vector in X (horizontal plane to N),
54
+ # Y (horizontal plane to E), Z (vertical upward) system
55
+ def normal_from_slope_aspect(slope, aspect)
56
+ a = to_rad(aspect)
57
+ b = to_rad(slope)
58
+ [Math.sin(b)*Math.sin(a), Math.sin(b)*Math.cos(a), Math.cos(b)]
59
+ end
60
+
61
+ # sun vector in X Y Z system
62
+ def sun_vector(sun_azimuth, sun_elevation)
63
+ a = to_rad(sun_azimuth)
64
+ b = to_rad(sun_elevation)
65
+ [Math.cos(b)*Math.sin(a), Math.cos(b)*Math.cos(a), Math.sin(b)]
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,174 @@
1
+ module Solar
2
+ class <<self
3
+ # Sun rise time for a given date (UTC) and position.
4
+ #
5
+ # The +:zenith+ or +:altitude+ of the sun can be passed as an argument,
6
+ # which can be numeric (in degrees) or symbolic:
7
+ # +:official+, +:civil+, +:nautical+ or +:astronomical+.
8
+ #
9
+ # +nil+ is returned if the sun doesn't rise at the date and position.
10
+ #
11
+ def rise(date, longitude, latitude, options = {})
12
+ rising, _, setting = passages(date, longitude, latitude, options)
13
+ if rising == setting || (setting - rising) == 1
14
+ nil # rising==setting => no rise; setting-rising == 1 => no set
15
+ else
16
+ rising
17
+ end
18
+ end
19
+
20
+ # Sun set time for a given date (UTC) and position.
21
+ #
22
+ # The +:zenith+ or +:altitude+ of the sun can be passed as an argument,
23
+ # which can be numeric (in degrees) or symbolic:
24
+ # +:official+, +:civil+, +:nautical+ or +:astronomical+.
25
+ #
26
+ # +nil+ is returned if the sun doesn't set at the date and position.
27
+ #
28
+ def set(date, longitude, latitude, options = {})
29
+ rising, _, setting = passages(date, longitude, latitude, options)
30
+ if rising==setting || (setting-rising)==1
31
+ nil # rising==setting => no rise; setting-rising == 1 => no set
32
+ else
33
+ setting
34
+ end
35
+ end
36
+
37
+ # Rise and set times as given by rise() and set()
38
+ def rise_and_set(date, longitude, latitude, options = {})
39
+ rising, _, setting = passages(date, longitude, latitude, options)
40
+ if rising==setting || (setting-rising)==1
41
+ nil # rising==setting => no rise; setting-rising == 1 => no set
42
+ else
43
+ [rising, setting]
44
+ end
45
+ end
46
+
47
+ # Solar passages [rising, transit, setting] for a given date (UTC) and position.
48
+ #
49
+ # The +:zenith+ or +:altitude+ of the sun can be passed as an argument,
50
+ # which can be numeric (in degrees) or symbolic:
51
+ # +:official+, +:civil+, +:nautical+ or +:astronomical+.
52
+ #
53
+ # In circumpolar case:
54
+ # * If Sun never rises, returns 00:00:00 on Date for all passages.
55
+ # * If Sun never sets, returns 00:00:00 (rising), 12:00:00 (transit),
56
+ # 24:00:00 (setting) on Date for all passages.
57
+ #
58
+ def passages(date, longitude, latitude, options = {})
59
+ ho = altitude_from_options(options)
60
+ t = to_jc(jd_r(date.to_datetime))
61
+ theta0 = (100.46061837 + (36000.770053608 + (0.000387933 - t/38710000)*t)*t) % 360
62
+
63
+ # Calculate apparent right ascention and declination for 0 hr Dynamical time for three days (degrees)
64
+ ra = []
65
+ decl = []
66
+ -1.upto(1) do |i|
67
+ declination, right_ascention = equatorial_position_rad((date+i).to_datetime)
68
+ ra << to_deg(right_ascention)
69
+ decl << to_deg(declination)
70
+ end
71
+ # tweak right ascention around 180 degrees (autumnal equinox)
72
+ if ra[0] > ra[1]
73
+ ra[0] -= 360
74
+ end
75
+ if ra[2] < ra[1]
76
+ ra[2] += 360
77
+ end
78
+
79
+ ho_rad, latitude_rad = [ho, latitude].map{|x| to_rad(x)}
80
+ decl_rad = decl.map { |x| to_rad(x) }
81
+
82
+ # approximate Hour Angle (degrees)
83
+ ha = Math.sin(ho_rad) /
84
+ Math.cos(latitude_rad) * Math.cos(decl_rad[1]) -
85
+ Math.tan(latitude_rad) * Math.tan(decl_rad[1])
86
+ # handle circumpolar. see note 2 at end of chapter
87
+ if ha.abs <= 1
88
+ ha = to_deg(Math.acos(ha))
89
+ elsif ha > 1 # circumpolar - sun never rises
90
+ # format sunrise, sunset & solar noon as DateTime
91
+ sunrise = date.to_datetime
92
+ transit = date.to_datetime
93
+ sunset = date.to_datetime
94
+ return [sunrise, transit, sunset]
95
+ else # cirumpolar - sun never sets
96
+ # format sunrise, sunset & solar noon as DateTime
97
+ sunrise = date.to_datetime
98
+ transit = date.to_datetime + 0.5
99
+ sunset = date.to_datetime + 1
100
+ return [sunrise, transit, sunset]
101
+ end
102
+ # approximate m (fraction of 1 day)
103
+ # store days added or subtracted to add in later
104
+ m = []
105
+ days = [0]*3
106
+ (0..2).each do |i|
107
+ case i
108
+ when 0
109
+ m[i] = (ra[1] - longitude - theta0) / 360 # transit
110
+ day_offset = +1
111
+ when 1
112
+ m[i] = m[0] - ha / 360 # rising
113
+ day_offset = -1
114
+ when 2
115
+ m[i] = m[0] + ha / 360 # setting
116
+ day_offset = -1
117
+ end
118
+
119
+ until m[i] >= 0
120
+ m[i] += 1
121
+ days[i] += day_offset
122
+ end
123
+ until m[i] <= 1
124
+ m[i] -= 1
125
+ days[i] -= day_offset
126
+ end
127
+ end
128
+ theta = [] # apparent sidereal time (degrees)
129
+ ra2 = [] # apparent right ascension (degrees)
130
+ decl2 = [] # apparent declination (degrees)
131
+ h = [] # local hour angle (degrees)
132
+ alt = [] # altitude (degrees)
133
+ delta_m = [1]*3
134
+ while delta_m[0] >= 0.01 || delta_m[1] >= 0.01 || delta_m[2] >= 0.01
135
+ 0.upto(2) do |i|
136
+ theta[i] = theta0 + 360.985647 * m[i]
137
+ n = m[i] + delta_t(date.to_datetime).to_r / 86_400
138
+ a = ra[1] - ra[0]
139
+ b = ra[2] - ra[1]
140
+ c = b - a
141
+ ra2[i] = ra[1] + n / 2 * (a + b + n * c)
142
+
143
+ n = m[i] + delta_t(date.to_datetime).to_r / 86_400
144
+ a = decl[1] - decl[0]
145
+ b = decl[2] - decl[1]
146
+ c = b - a
147
+ decl2[i] = decl[1] + n / 2 * (a + b + n * c)
148
+
149
+ h[i] = theta[i] + longitude - ra2[i]
150
+
151
+ alt[i] = to_deg Math.asin(
152
+ Math.sin(latitude_rad) * Math.sin(to_rad(decl2[i])) +
153
+ Math.cos(latitude_rad) * Math.cos(to_rad(decl2[i])) *
154
+ Math.cos(to_rad(h[i]))
155
+ )
156
+ end
157
+ # adjust m
158
+ delta_m[0] = -h[0] / 360
159
+ 1.upto(2) do |i|
160
+ delta_m[i] = (alt[i] - ho) /
161
+ 360 * Math.cos(to_rad(decl2[i])) * Math.cos(latitude_rad) * Math.sin(to_rad(h[i]))
162
+ end
163
+ 0.upto(2) do |i|
164
+ m[i] += delta_m[i]
165
+ end
166
+ end
167
+ # format sunrise, sunset & solar noon as DateTime
168
+ sunrise = date.to_datetime + m[1] + days[1]
169
+ transit = date.to_datetime + m[0] + days[0]
170
+ sunset = date.to_datetime + m[2] + days[2]
171
+ [sunrise, transit, sunset]
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,51 @@
1
+ module Solar
2
+ class <<self
3
+ # Sun horizontal coordinates (relative position) in degrees:
4
+ #
5
+ # * +elevation+ (altitude over horizon) in degrees; positive upwards
6
+ # * +azimuth+ in degrees measured clockwise (towards East) from North direction
7
+ #
8
+ def position(t, longitude, latitude)
9
+
10
+ delta_rad, alpha_rad = equatorial_position_rad(t)
11
+ alpha_deg = to_deg(alpha_rad)
12
+ # alpha_h += 360 if alpha_h < 0
13
+
14
+ # t as Julian centuries of 36525 ephemeris days form the epoch J2000.0
15
+ if false
16
+ # Float
17
+ jd = jd_f(t)
18
+ else
19
+ # Rational
20
+ jd = jd_r(t)
21
+ end
22
+ t = to_jc(jd)
23
+
24
+ # Sidereal time at Greenwich
25
+ theta = 280.46061837 +
26
+ 360.98564736629*(jd - 2_451_545) +
27
+ (0.000387933 - t/38_710_000)*t*t
28
+
29
+ # Reduce magnitude to minimize errors
30
+ theta %= 360
31
+
32
+ # Local hour angle
33
+ h = theta + longitude - alpha_deg
34
+ h %= 360
35
+
36
+ latitude_rad = to_rad(latitude)
37
+ h_rad = to_rad(h)
38
+
39
+ # Local horizontal coordinates : Meeus pg 89
40
+ altitude_rad = Math.asin(Math.sin(latitude_rad)*Math.sin(delta_rad) +
41
+ Math.cos(latitude_rad)*Math.cos(delta_rad)*Math.cos(h_rad))
42
+ azimuth_rad = Math.atan2(
43
+ Math.sin(h_rad),
44
+ Math.cos(h_rad) * Math.sin(latitude_rad) -
45
+ Math.tan(delta_rad) * Math.cos(latitude_rad)
46
+ )
47
+
48
+ [to_deg(altitude_rad), (180 + to_deg(azimuth_rad)) % 360]
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,258 @@
1
+ module Solar
2
+ DEBUG_RADIATION = ENV['DEBUG_SOLAR_RADIATION']
3
+ class <<self
4
+ # Radiation in W/m^2 at time t at a position given by longitude, latitud on
5
+ # terrain of given slope and aspect, given the global radiation on a horizontal plane
6
+ # and optionally the diffuse radiation on a horizontal plane.
7
+ # Computes mean radiation in a short period of length givien by :t_step (10 minute by default) centered at t.
8
+ # Basically the method used is the oned presented in:
9
+ #
10
+ # * Solar Engineering of Thermal Processes
11
+ # 1991 Duffie, Beckman
12
+ #
13
+ # Options:
14
+ #
15
+ # * +:slope+ slope angle in degrees (0-horizontal to 90-vertical)
16
+ # * +:aspect+ clockwise from North in degrees
17
+ # * +:t_step+ time step in hours; if radiation values are given they
18
+ # are considered as the mean values for the time step.
19
+ # * +:global_radiation+ Global radation measured on a horizontal plane
20
+ # in W/m^2 (mean value over the time step)
21
+ # * +:diffuse_radiation+ Diffuse radation measured on a horizontal plane
22
+ # in W/m^2 (mean value over the time step)
23
+ # * +:albedo+ of the terrain, used to compute reflected radiation.
24
+ #
25
+ # This can be used with a measured :global_radiation and optional
26
+ # :diffuse_radiation, both measured on horizontal to compute the
27
+ # estimated global radiation on a sloped plane.
28
+ #
29
+ # It can be also used by giving the :clearness_index to compute
30
+ # estimated radiation.
31
+ #
32
+ def radiation(t, longitude, latitude, options = {})
33
+ # TODO: parameterize on the .... algorithms
34
+ # consider method of [Neteler-2008] Appendix A pg. 377-381
35
+
36
+ slope = options[:slope] || 0.0
37
+ aspect = options[:aspect] || 0.0
38
+
39
+ # time step in hours
40
+ t_step = options[:t_step] || 1/6.0
41
+ # global measured radiation in W/m^2 (mean over time step) (horizontal)
42
+ g = options[:global_radiation]
43
+ # optional: diffuse radiation (horizontal)
44
+ g_d = options[:diffuse_radiation]
45
+ k_t = options[:clearness_index]
46
+
47
+ # ground reflectance (albedo) as %
48
+ rg = options[:albedo] || 0.2
49
+
50
+ # t is assumed at half the time step
51
+
52
+ t_utc = t.utc
53
+ n = t_utc.yday # day of the year (a number between 1 and 365)
54
+ lambda = to_rad(longitude)
55
+ phi = to_rad(latitude)
56
+
57
+ d = solar_declination(n)
58
+
59
+ # utc time in hours
60
+ tu = t_utc.hour + t_utc.min/60.0 + t_utc.sec/3600.0
61
+ b = to_rad(360.0*(n-1)/365.0)
62
+ # equation of time
63
+ e = 3.82*(0.000075+0.001868*Math.cos(b) - 0.032077*Math.sin(b) - 0.014615*Math.cos(2*b) - 0.04089*Math.sin(2*b))
64
+ # solar time in hours
65
+ ts = tu + longitude/15.0 + e
66
+
67
+ # hour angle (omega) in radians
68
+ w = to_rad((ts - 12.0)*15.0)
69
+ cos_w = Math.cos(w)
70
+ sin_w = Math.sin(w)
71
+
72
+ # extraterrestrial_normal_radiation in W/m^2
73
+ g_on = ext_radiation(t)
74
+
75
+ cos_phi = Math.cos(phi)
76
+ sin_phi = Math.sin(phi)
77
+ cos_d = Math.cos(d)
78
+ sin_d = Math.sin(d)
79
+
80
+ # zenith angle in radians
81
+ # eq. 1.6.5 (pg 15) [1991-Duffie]
82
+ cos_phi_z = cos_phi*cos_d*cos_w + sin_phi*sin_d
83
+
84
+ # extraterrestrial horizontal radiation in W/m^2
85
+ g_o = g_on*cos_phi_z
86
+
87
+ # hour_angle at beginning of time step
88
+ w1 = to_rad((ts - t_step/2 - 12.0)*15.0)
89
+ # hour_angle at end of time step
90
+ w2 = to_rad((ts + t_step/2 - 12.0)*15.0)
91
+
92
+ # extraterrestrial horizontal radiation in W/m^2 averaged over the time step
93
+ # [1991-Duffie pg 37] eq. 1.10.1, 1.10.3
94
+ # TODO: for long time steps we should average as:
95
+ # g_o_a = 12/Math::PI * g_on * ( cos_phi*cos_d*(Math.sin(w2)-Math.sin(w1)) + (w2-w1)*sin_phi*sin_d )
96
+ g_o_a = g_o
97
+ g_o_a = 0 if g_o_a < 0
98
+
99
+ # clearness index
100
+ k_t ||= g/g_o_a
101
+
102
+ # either k_t or g must be defined by the user
103
+ g ||= (g_o_a == 0 ? 0.0 : k_t*g_o_a)
104
+
105
+ # diffuse fraction
106
+ if k_t.infinite?
107
+ df = 1.0 # actual df may be around 0.5; for the purpose of computing g_d it is 1
108
+ else
109
+ solar_elevation = 90 - to_deg(Math.acos(cos_phi_z))
110
+ df = diffuse_fraction(k_t, solar_elevation)
111
+ end
112
+
113
+ # diffuse radiation W/m^2
114
+ g_d ||= df*g
115
+
116
+ # beam radiation
117
+ g_b = g - g_d
118
+
119
+ # slope
120
+ beta = to_rad(slope)
121
+ # azimuth
122
+ gamma = to_rad(aspect-180)
123
+
124
+ cos_beta = Math.cos(beta)
125
+ sin_beta = Math.sin(beta)
126
+ cos_gamma = Math.cos(gamma)
127
+ sin_gamma = Math.sin(gamma)
128
+
129
+ # angle of incidence
130
+ # eq (1.6.2) pg 14 [1991-Duffie]
131
+ # eq (3) "Analytical integrated functions for daily solar radiation on slopes" - Allen, Trezza, Tasumi
132
+ cos_phi = sin_d*sin_phi*cos_beta \
133
+ - sin_d*cos_phi*sin_beta*cos_gamma \
134
+ + cos_d*cos_phi*cos_beta*cos_w \
135
+ + cos_d*sin_phi*sin_beta*cos_gamma*cos_w \
136
+ + cos_d*sin_beta*sin_gamma*sin_w
137
+
138
+ # ratio of beam radiation on tilted surface to beam radiation on horizontal
139
+ # [1991-Duffie pg 23-24] eq. 1.8.1
140
+ # rb = illumination_factor_at(t, longitude, latitude, slope, aspect)
141
+ rb = cos_phi / cos_phi_z
142
+ rb = 0.0 if rb < 0.0
143
+
144
+ # anisotropy index
145
+ if k_t.infinite? || g_o_a == 0
146
+ ai = 0.0
147
+ else
148
+ ai = g_b / g_o_a
149
+ end
150
+
151
+ # horizontal brightening factor
152
+ if g != 0
153
+ f = Math.sqrt(g_b / g)
154
+ else
155
+ f = 1.0
156
+ end
157
+
158
+ if DEBUG_RADIATION
159
+ sun_elevation, sun_azimuth = Solar.position(t_utc, longitude, latitude)
160
+ rb2 = illumination_factor_at(t_utc, longitude, latitude, slope, aspect)
161
+ puts ""
162
+ puts " @#{t_utc} #{sun_elevation} [#{90-sun_elevation}] az: #{sun_azimuth} <#{Math.acos(cos_phi_z)*180/Math::PI}>"
163
+ puts " kt:#{k_t} df:#{df} g: #{g} gon: #{g_on}"
164
+ puts " rb: #{rb} (#{rb2}) g_b:#{g_b} g_o_a:#{g_o_a} g_d:#{g_d}"
165
+ puts " -> #{(g_b + g_d*ai)*rb}+#{g_d*(1-ai)*((1 + cos_beta)/2)*(1 + f*Math.sin(beta/2)**3)}+#{g*rg*(1 - cos_beta)/2}"
166
+ end
167
+
168
+ # global radiation on slope according to HDKR model
169
+ # eq. 2.16.7 (pg.92) [1991-Duffie]
170
+ # three terms:
171
+ # * direct and circumsolar: (g_b + g_d*ai)*rb
172
+ # * diffuse: g_d*(1-ai)*((1 + cos_beta)/2)*(1 + f*Math.sin(beta/2)**3)
173
+ # * reflected: g*rg*(1 - cos_beta)/2
174
+ (g_b + g_d*ai)*rb + g_d*(1-ai)*((1 + cos_beta)/2)*(1 + f*Math.sin(beta/2)**3) + g*rg*(1 - cos_beta)/2
175
+ end
176
+
177
+ private
178
+
179
+ G_SC = 1367.0 # solar constant W/m^2
180
+
181
+ # Extraterrestrial normal radiation
182
+ # Solar Engineering of Thermal Processes [1991, Duffie, Beckman]
183
+ # pg.5-6, 8-9; pg 37.
184
+ def ext_radiation(t)
185
+ n = t.utc.yday
186
+ G_SC*(1.0 + 0.033*Math.cos(to_rad(360.0*n/365.0)))
187
+
188
+ # Alternative based on [Neteler-2008 pg378] (A.65)
189
+ # j = 2*Math::PI*n/365.25
190
+ # G_SC*(1.0 + 0.03344*Math.cos(j - 0.048869))
191
+ end
192
+
193
+ # Diffuse fraction as a function of the clearness index
194
+ # Erbs model:
195
+ #
196
+ # * Estimation of the diffuse radiation fraction for hourly, daily and monthly-average global radiation
197
+ # 1982 Erbs, Klein, Duffie
198
+ # SOLAR ENERGY - 1982 Vol.28 Issue 4
199
+ #
200
+ # Other sources of information for this model:
201
+ #
202
+ # * Different methods for separating diffuse and direct components of solar radiation and their application in crop growth models
203
+ # 1992 Bindi, Miglietta, Zipoli
204
+ # CLIMATE RESEARCH - July 1992
205
+ # http://www.int-res.com/articles/cr/2/c002p047.pdf (pg 53 Method ER)
206
+ # * DIVISION OF GLOBAL RADIATION INTO DIRECT RADIATION AND DIFFUSE RADIATION
207
+ # 2010 Fabienne Lanini
208
+ # * Solar radiation model
209
+ # 2001 Wong, Chow
210
+ # APPLIED ENERGY - August 2001 [pg. 210]
211
+ # * COMPARISON OF MODELS FOR THE DERIVATION OF DIFFUSE FRACTION OF GLOBAL IRRADIANCE DATA FOR VIENNA, AUSTRIA
212
+ # 2011 Dervishi, Mahdavi - pg 766
213
+ #
214
+ # TODO: use model that uses solar elevation (Skartveit Olseth):
215
+ #
216
+ # * AN HOURLY DIFFUSE FRACTION MODEL WITH CORRECTION FOR VARIABILITY AND SURFACE ALBEDO
217
+ # 1998 Skartveit, Olseth, Tuft
218
+ # SOLAR ENERY - September 1998
219
+ # http://www.sciencedirect.com/science/article/pii/S0038092X9800067X
220
+ #
221
+ # Precedent model:
222
+ #
223
+ # * A model for the diffuse fraction of hourly global radiation
224
+ # 1986 Skartveit, Olseth
225
+ # SOLAR ENERGY - JANUARY 1987
226
+ # http://www.researchgate.net/publication/222958126
227
+ #
228
+ # Additional information:
229
+ #
230
+ # * Solar radiation model
231
+ # 2001 Wong, Chow
232
+ # APPLIED ENERGY - August 2001 [pg. 212]
233
+ #
234
+ # * Solar Engineering of Thermal Processes
235
+ # 1991 Duffie, Beckman [pg.77]
236
+ #
237
+ def diffuse_fraction(k_t, solar_elevation)
238
+ if k_t <= 0.22
239
+ df = 1.0 - 0.09*k_t
240
+ elsif k_t <= 0.80
241
+ df = 0.9511 - 0.1604*k_t + 4.388*k_t**2 - 16.638*k_t**3 + 12.336*k_t**4
242
+ else
243
+ df = 0.165
244
+ end
245
+ end
246
+
247
+ def solar_declination(day)
248
+ # solar declination [Duffie-1991 pg 13]
249
+ ang = to_rad(360*(284.0 + day)/365.0)
250
+ to_rad(23.45)*Math.sin(ang)
251
+
252
+ # Alternative based on [Neteler-2008 pg337] (A.59)
253
+ # j = 2*Math::PI*day/365.25
254
+ # Math.asin(0.3978*Math.sin(j - 1.4 + 0.0355*Math.sin(j - 0.0489)))
255
+ end
256
+
257
+ end
258
+ end