solar 0.0.2 → 0.1.1

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.
@@ -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