Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Implement Michalsky's solar position algorithm #2167

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions pvlib/solarposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,102 @@
return DFOut


def michalsky(time, lat, lon):
"""
Calculate solar position using Michalsky's algorithm.

Michalsky's algorithm [1]_ has a stated accuracy of 0.01 degrees
through the year 2050.

Parameters
----------
time : pandas.DatetimeIndex
Must be localized or UTC will be assumed.
latitude : float
Latitude in decimal degrees. Positive north of equator, negative
to south.
longitude : float
Longitude in decimal degrees. Positive east of prime meridian,
negative to west.

Returns
-------
DataFrame with the following columns (all values in degrees):

* apparent_elevation : sun elevation, accounting for
atmospheric refraction.
* elevation : actual sun elevation (not accounting for refraction).
* azimuth : sun azimuth, east of north.
* apparent_zenith : sun zenith, accounting for atmospheric
refraction.
* zenith : actual sun zenith (not accounting for refraction).

References
----------
.. [1] Michalsky, J., 1988. The Astronomical Almanac's algorithm for
approximate solar position (1950–2050). Solar Energy vol. 40 (3),
pp. 227-235. :doi:`10.1016/0038-092X(88)90045-X`
.. [2] Michalsky, J., 1988. Errata. Solar Energy vol. 41 (1), p. 113.
:doi:`10.1016/0038-092X(88)90122-3`
"""
lat = np.radians(lat)
lon = np.radians(lon)
unixtime = _datetime_to_unixtime(time)

Check warning on line 912 in pvlib/solarposition.py

View check run for this annotation

Codecov / codecov/patch

pvlib/solarposition.py#L910-L912

Added lines #L910 - L912 were not covered by tests
# unix time is the number of seconds since 1970-01-01, but Michalsky needs
# number of days since 2000-01-01 12:00. The difference is 10957.5 days.
n = unixtime / 86400 - 10957.5
hour = ((unixtime / 86400) % 1)*24

Check warning on line 916 in pvlib/solarposition.py

View check run for this annotation

Codecov / codecov/patch

pvlib/solarposition.py#L915-L916

Added lines #L915 - L916 were not covered by tests

# ecliptic coordinates
L = 280.460 + 0.9856474 * n
g = np.radians(357.528 + 0.9856003 * n)

Check warning on line 920 in pvlib/solarposition.py

View check run for this annotation

Codecov / codecov/patch

pvlib/solarposition.py#L919-L920

Added lines #L919 - L920 were not covered by tests
# Note: there is a typo in the reference math vs appendix (0.02 vs 0.2).
# 0.02 gives much better agreement with SPA, so use that one.
l = np.radians(L + 1.915 * np.sin(g) + 0.020 * np.sin(2*g))

Check failure on line 923 in pvlib/solarposition.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E741 ambiguous variable name 'l'
ep = np.radians(23.439 - 0.0000004 * n)

Check warning on line 924 in pvlib/solarposition.py

View check run for this annotation

Codecov / codecov/patch

pvlib/solarposition.py#L923-L924

Added lines #L923 - L924 were not covered by tests

# celestial coordinates
ra = np.arctan2(np.cos(ep) * np.sin(l), np.cos(l))
dec = np.arcsin(np.sin(ep) * np.sin(l))

Check warning on line 928 in pvlib/solarposition.py

View check run for this annotation

Codecov / codecov/patch

pvlib/solarposition.py#L927-L928

Added lines #L927 - L928 were not covered by tests

# local coordinates
gmst = np.radians(6.697375 + 0.0657098242 * n + hour)
lmst = gmst + lon / 15
ha = 15*lmst - ra # Eq 3
el = np.arcsin( # Eq 4

Check warning on line 934 in pvlib/solarposition.py

View check run for this annotation

Codecov / codecov/patch

pvlib/solarposition.py#L931-L934

Added lines #L931 - L934 were not covered by tests
np.sin(dec) * np.sin(lat) + np.cos(dec) * np.cos(lat) * np.cos(ha)
)
az = np.arcsin( # Eq 5

Check warning on line 937 in pvlib/solarposition.py

View check run for this annotation

Codecov / codecov/patch

pvlib/solarposition.py#L937

Added line #L937 was not covered by tests
-np.cos(dec) * np.sin(ha) / np.cos(el)
)
elc = np.arcsin(np.sin(dec) / np.sin(lat))

Check warning on line 940 in pvlib/solarposition.py

View check run for this annotation

Codecov / codecov/patch

pvlib/solarposition.py#L940

Added line #L940 was not covered by tests

el = np.degrees(el)
az = np.degrees(az)
elc = np.degrees(elc)

Check warning on line 944 in pvlib/solarposition.py

View check run for this annotation

Codecov / codecov/patch

pvlib/solarposition.py#L942-L944

Added lines #L942 - L944 were not covered by tests

az = np.where(el >= elc, 180 - az, az)
az = np.where((el <= elc) & (ha > 0), 360 + az, az) % 360

Check warning on line 947 in pvlib/solarposition.py

View check run for this annotation

Codecov / codecov/patch

pvlib/solarposition.py#L946-L947

Added lines #L946 - L947 were not covered by tests

# refraction correction
r = (

Check warning on line 950 in pvlib/solarposition.py

View check run for this annotation

Codecov / codecov/patch

pvlib/solarposition.py#L950

Added line #L950 was not covered by tests
3.51561
* (0.1594 + 0.0196 * el + 0.00002 * el**2)
/ (1 + 0.505 * el + 0.0845 * el**2)
)
apparent_elevation = el + r

Check warning on line 955 in pvlib/solarposition.py

View check run for this annotation

Codecov / codecov/patch

pvlib/solarposition.py#L955

Added line #L955 was not covered by tests

result = pd.DataFrame({

Check warning on line 957 in pvlib/solarposition.py

View check run for this annotation

Codecov / codecov/patch

pvlib/solarposition.py#L957

Added line #L957 was not covered by tests
'elevation': el,
'azimuth': az,
'zenith': 90 - el,
'apparent_elevation': apparent_elevation,
'apparent_zenith': 90 - apparent_elevation,
# TODO equation of time?
}, index=time)
return result

Check warning on line 965 in pvlib/solarposition.py

View check run for this annotation

Codecov / codecov/patch

pvlib/solarposition.py#L965

Added line #L965 was not covered by tests


def calc_time(lower_bound, upper_bound, latitude, longitude, attribute, value,
altitude=0, pressure=101325, temperature=12, horizon='+0:00',
xtol=1.0e-12):
Expand Down
Loading