solarposition.py
tutorial¶This tutorial needs your help to make it better!
Table of contents:
This tutorial has been tested against the following package versions:
It should work with other Python and Pandas versions. It requires pvlib > 0.2.0 and IPython > 3.0.
Authors:
import datetime
# scientific python add-ons
import numpy as np
import pandas as pd
# plotting stuff
# first line makes the plots appear in the notebook
%matplotlib inline
import matplotlib.pyplot as plt
# seaborn makes your plots look better
try:
import seaborn as sns
sns.set(rc={"figure.figsize": (12, 6)})
except ImportError:
print('We suggest you install seaborn using conda or pip and rerun this cell')
# finally, we import the pvlib library
import pvlib
import pvlib
from pvlib.location import Location
tus = Location(32.2, -111, 'US/Arizona', 700, 'Tucson')
print(tus)
golden = Location(39.742476, -105.1786, 'America/Denver', 1830, 'Golden')
print(golden)
golden_mst = Location(39.742476, -105.1786, 'MST', 1830, 'Golden MST')
print(golden_mst)
berlin = Location(52.5167, 13.3833, 'Europe/Berlin', 34, 'Berlin')
print(berlin)
Tucson: latitude=32.2, longitude=-111, tz=US/Arizona, altitude=700 Golden: latitude=39.742476, longitude=-105.1786, tz=America/Denver, altitude=1830 Golden MST: latitude=39.742476, longitude=-105.1786, tz=MST, altitude=1830 Berlin: latitude=52.5167, longitude=13.3833, tz=Europe/Berlin, altitude=34
times = pd.date_range(start=datetime.datetime(2014,6,23), end=datetime.datetime(2014,6,24), freq='1Min')
times_loc = times.tz_localize(tus.pytz)
times
DatetimeIndex(['2014-06-23 00:00:00', '2014-06-23 00:01:00', '2014-06-23 00:02:00', '2014-06-23 00:03:00', '2014-06-23 00:04:00', '2014-06-23 00:05:00', '2014-06-23 00:06:00', '2014-06-23 00:07:00', '2014-06-23 00:08:00', '2014-06-23 00:09:00', ... '2014-06-23 23:51:00', '2014-06-23 23:52:00', '2014-06-23 23:53:00', '2014-06-23 23:54:00', '2014-06-23 23:55:00', '2014-06-23 23:56:00', '2014-06-23 23:57:00', '2014-06-23 23:58:00', '2014-06-23 23:59:00', '2014-06-24 00:00:00'], dtype='datetime64[ns]', length=1441, freq='T', tz=None)
pyephemout = pvlib.solarposition.pyephem(times, tus)
spaout = pvlib.solarposition.spa_python(times, tus)
reload(pvlib.solarposition)
pyephemout = pvlib.solarposition.pyephem(times_loc, tus)
spaout = pvlib.solarposition.spa_python(times_loc, tus)
pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
spaout['elevation'].plot(label='spa')
plt.legend(ncol=2)
plt.title('elevation')
print('pyephem')
print(pyephemout.head())
print('spa')
print(spaout.head())
pyephem apparent_elevation apparent_azimuth elevation \ 2014-06-23 00:00:00-07:00 -34.028890 352.757414 -34.028890 2014-06-23 00:01:00-07:00 -34.055060 353.032425 -34.055060 2014-06-23 00:02:00-07:00 -34.080223 353.307627 -34.080223 2014-06-23 00:03:00-07:00 -34.104374 353.583047 -34.104374 2014-06-23 00:04:00-07:00 -34.127518 353.858659 -34.127518 azimuth apparent_zenith zenith 2014-06-23 00:00:00-07:00 352.757414 124.028890 124.028890 2014-06-23 00:01:00-07:00 353.032425 124.055060 124.055060 2014-06-23 00:02:00-07:00 353.307627 124.080223 124.080223 2014-06-23 00:03:00-07:00 353.583047 124.104374 124.104374 2014-06-23 00:04:00-07:00 353.858659 124.127518 124.127518 spa apparent_elevation apparent_zenith azimuth \ 2014-06-23 00:00:00-07:00 -34.028842 124.028842 352.757345 2014-06-23 00:01:00-07:00 -34.055013 124.055013 353.032330 2014-06-23 00:02:00-07:00 -34.080176 124.080176 353.307536 2014-06-23 00:03:00-07:00 -34.104329 124.104329 353.582953 2014-06-23 00:04:00-07:00 -34.127472 124.127472 353.858574 elevation equation_of_time zenith 2014-06-23 00:00:00-07:00 -34.028842 -2.150130 124.028842 2014-06-23 00:01:00-07:00 -34.055013 -2.150281 124.055013 2014-06-23 00:02:00-07:00 -34.080176 -2.150431 124.080176 2014-06-23 00:03:00-07:00 -34.104329 -2.150582 124.104329 2014-06-23 00:04:00-07:00 -34.127472 -2.150733 124.127472
plt.figure()
pyephemout['elevation'].plot(label='pyephem')
spaout['elevation'].plot(label='spa')
(pyephemout['elevation'] - spaout['elevation']).plot(label='diff')
plt.legend(ncol=3)
plt.title('elevation')
plt.figure()
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
spaout['elevation'].plot(label='spa')
(pyephemout['apparent_elevation'] - spaout['elevation']).plot(label='diff')
plt.legend(ncol=3)
plt.title('elevation')
plt.figure()
pyephemout['apparent_zenith'].plot(label='pyephem apparent')
spaout['zenith'].plot(label='spa')
(pyephemout['apparent_zenith'] - spaout['zenith']).plot(label='diff')
plt.legend(ncol=3)
plt.title('zenith')
plt.figure()
pyephemout['apparent_azimuth'].plot(label='pyephem apparent')
spaout['azimuth'].plot(label='spa')
(pyephemout['apparent_azimuth'] - spaout['azimuth']).plot(label='diff')
plt.legend(ncol=3)
plt.title('azimuth')
<matplotlib.text.Text at 0x7f9ee8124c90>
reload(pvlib.solarposition)
pyephemout = pvlib.solarposition.pyephem(times, tus)
spaout = pvlib.solarposition.spa_python(times, tus)
pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
spaout['elevation'].plot(label='spa')
plt.legend(ncol=3)
plt.title('elevation')
print('pyephem')
print(pyephemout.head())
print('spa')
print(spaout.head())
pyephem apparent_elevation apparent_azimuth elevation \ 2014-06-23 00:00:00-07:00 -34.028890 352.757414 -34.028890 2014-06-23 00:01:00-07:00 -34.055060 353.032425 -34.055060 2014-06-23 00:02:00-07:00 -34.080223 353.307627 -34.080223 2014-06-23 00:03:00-07:00 -34.104374 353.583047 -34.104374 2014-06-23 00:04:00-07:00 -34.127518 353.858659 -34.127518 azimuth apparent_zenith zenith 2014-06-23 00:00:00-07:00 352.757414 124.028890 124.028890 2014-06-23 00:01:00-07:00 353.032425 124.055060 124.055060 2014-06-23 00:02:00-07:00 353.307627 124.080223 124.080223 2014-06-23 00:03:00-07:00 353.583047 124.104374 124.104374 2014-06-23 00:04:00-07:00 353.858659 124.127518 124.127518 spa apparent_elevation apparent_zenith azimuth \ 2014-06-23 00:00:00-07:00 -34.028842 124.028842 352.757345 2014-06-23 00:01:00-07:00 -34.055013 124.055013 353.032330 2014-06-23 00:02:00-07:00 -34.080176 124.080176 353.307536 2014-06-23 00:03:00-07:00 -34.104329 124.104329 353.582953 2014-06-23 00:04:00-07:00 -34.127472 124.127472 353.858574 elevation equation_of_time zenith 2014-06-23 00:00:00-07:00 -34.028842 -2.150130 124.028842 2014-06-23 00:01:00-07:00 -34.055013 -2.150281 124.055013 2014-06-23 00:02:00-07:00 -34.080176 -2.150431 124.080176 2014-06-23 00:03:00-07:00 -34.104329 -2.150582 124.104329 2014-06-23 00:04:00-07:00 -34.127472 -2.150733 124.127472
reload(pvlib.solarposition)
pyephemout = pvlib.solarposition.pyephem(times, golden)
spaout = pvlib.solarposition.spa_python(times, golden)
pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
spaout['elevation'].plot(label='spa')
plt.legend(ncol=2)
plt.title('elevation')
print('pyephem')
print(pyephemout.head())
print('spa')
print(spaout.head())
pyephem apparent_elevation apparent_azimuth elevation \ 2014-06-23 00:00:00-06:00 -25.154820 344.064195 -25.154820 2014-06-23 00:01:00-06:00 -25.207201 344.310956 -25.207201 2014-06-23 00:02:00-06:00 -25.258784 344.558018 -25.258784 2014-06-23 00:03:00-06:00 -25.309568 344.805380 -25.309568 2014-06-23 00:04:00-06:00 -25.359552 345.053043 -25.359552 azimuth apparent_zenith zenith 2014-06-23 00:00:00-06:00 344.064195 115.154820 115.154820 2014-06-23 00:01:00-06:00 344.310956 115.207201 115.207201 2014-06-23 00:02:00-06:00 344.558018 115.258784 115.258784 2014-06-23 00:03:00-06:00 344.805380 115.309568 115.309568 2014-06-23 00:04:00-06:00 345.053043 115.359552 115.359552 spa apparent_elevation apparent_zenith azimuth \ 2014-06-23 00:00:00-06:00 -25.154766 115.154766 344.064134 2014-06-23 00:01:00-06:00 -25.207147 115.207147 344.310880 2014-06-23 00:02:00-06:00 -25.258730 115.258730 344.557936 2014-06-23 00:03:00-06:00 -25.309515 115.309515 344.805299 2014-06-23 00:04:00-06:00 -25.359497 115.359497 345.052965 elevation equation_of_time zenith 2014-06-23 00:00:00-06:00 -25.154766 -2.141078 115.154766 2014-06-23 00:01:00-06:00 -25.207147 -2.141229 115.207147 2014-06-23 00:02:00-06:00 -25.258730 -2.141380 115.258730 2014-06-23 00:03:00-06:00 -25.309515 -2.141531 115.309515 2014-06-23 00:04:00-06:00 -25.359497 -2.141682 115.359497
pyephemout = pvlib.solarposition.pyephem(times, golden)
ephemout = pvlib.solarposition.ephemeris(times, golden)
pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
ephemout['elevation'].plot(label='ephem')
plt.legend(ncol=2)
plt.title('elevation')
print('pyephem')
print(pyephemout.head())
print('ephem')
print(ephemout.head())
pyephem apparent_elevation apparent_azimuth elevation \ 2014-06-23 00:00:00-06:00 -25.154820 344.064195 -25.154820 2014-06-23 00:01:00-06:00 -25.207201 344.310956 -25.207201 2014-06-23 00:02:00-06:00 -25.258784 344.558018 -25.258784 2014-06-23 00:03:00-06:00 -25.309568 344.805380 -25.309568 2014-06-23 00:04:00-06:00 -25.359552 345.053043 -25.359552 azimuth apparent_zenith zenith 2014-06-23 00:00:00-06:00 344.064195 115.154820 115.154820 2014-06-23 00:01:00-06:00 344.310956 115.207201 115.207201 2014-06-23 00:02:00-06:00 344.558018 115.258784 115.258784 2014-06-23 00:03:00-06:00 344.805380 115.309568 115.309568 2014-06-23 00:04:00-06:00 345.053043 115.359552 115.359552 ephem apparent_elevation elevation azimuth \ 2014-06-23 00:00:00-06:00 -25.149499 -25.149499 344.061394 2014-06-23 00:01:00-06:00 -25.201890 -25.201890 344.308126 2014-06-23 00:02:00-06:00 -25.253483 -25.253483 344.555170 2014-06-23 00:03:00-06:00 -25.304277 -25.304277 344.802520 2014-06-23 00:04:00-06:00 -25.354270 -25.354270 345.050172 apparent_zenith zenith solar_time 2014-06-23 00:00:00-06:00 115.149499 115.149499 22.952125 2014-06-23 00:01:00-06:00 115.201890 115.201890 22.968789 2014-06-23 00:02:00-06:00 115.253483 115.253483 22.985453 2014-06-23 00:03:00-06:00 115.304277 115.304277 23.002118 2014-06-23 00:04:00-06:00 115.354270 115.354270 23.018782
loc = berlin
pyephemout = pvlib.solarposition.pyephem(times, loc)
ephemout = pvlib.solarposition.ephemeris(times, loc)
pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
ephemout['elevation'].plot(label='ephem')
ephemout['apparent_elevation'].plot(label='ephem apparent')
plt.legend(ncol=2)
plt.title('elevation')
print('pyephem')
print(pyephemout.head())
print('ephem')
print(ephemout.head())
pyephem apparent_elevation apparent_azimuth elevation \ 2014-06-23 00:00:00+02:00 -12.598822 343.918876 -12.598822 2014-06-23 00:01:00+02:00 -12.640668 344.149982 -12.640668 2014-06-23 00:02:00+02:00 -12.681923 344.381225 -12.681923 2014-06-23 00:03:00+02:00 -12.722587 344.612605 -12.722587 2014-06-23 00:04:00+02:00 -12.762658 344.844121 -12.762658 azimuth apparent_zenith zenith 2014-06-23 00:00:00+02:00 343.918876 102.598822 102.598822 2014-06-23 00:01:00+02:00 344.149982 102.640668 102.640668 2014-06-23 00:02:00+02:00 344.381225 102.681923 102.681923 2014-06-23 00:03:00+02:00 344.612605 102.722587 102.722587 2014-06-23 00:04:00+02:00 344.844121 102.762658 102.762658 ephem apparent_elevation elevation azimuth \ 2014-06-23 00:00:00+02:00 -12.593452 -12.593452 343.916041 2014-06-23 00:01:00+02:00 -12.635306 -12.635306 344.147120 2014-06-23 00:02:00+02:00 -12.676569 -12.676569 344.378348 2014-06-23 00:03:00+02:00 -12.717240 -12.717240 344.609723 2014-06-23 00:04:00+02:00 -12.757320 -12.757320 344.841244 apparent_zenith zenith solar_time 2014-06-23 00:00:00+02:00 102.593452 102.593452 22.857453 2014-06-23 00:01:00+02:00 102.635306 102.635306 22.874117 2014-06-23 00:02:00+02:00 102.676569 102.676569 22.890781 2014-06-23 00:03:00+02:00 102.717240 102.717240 22.907446 2014-06-23 00:04:00+02:00 102.757320 102.757320 22.924110
pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
ephemout['elevation'].plot(label='ephem')
ephemout['apparent_elevation'].plot(label='ephem apparent')
plt.legend(ncol=2)
plt.title('elevation')
plt.xlim(pd.Timestamp('2015-06-28 03:00:00+02:00'), pd.Timestamp('2015-06-28 06:00:00+02:00'))
plt.ylim(-10,10)
(-10, 10)
loc = berlin
times = pd.DatetimeIndex(start=datetime.date(2015,3,28), end=datetime.date(2015,3,29), freq='5min')
pyephemout = pvlib.solarposition.pyephem(times, loc)
ephemout = pvlib.solarposition.ephemeris(times, loc)
pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
ephemout['elevation'].plot(label='ephem')
plt.legend(ncol=2)
plt.title('elevation')
plt.figure()
pyephemout['azimuth'].plot(label='pyephem')
ephemout['azimuth'].plot(label='ephem')
plt.legend(ncol=2)
plt.title('azimuth')
print('pyephem')
print(pyephemout.head())
print('ephem')
print(ephemout.head())
pyephem apparent_elevation apparent_azimuth elevation \ 2015-03-28 00:00:00+01:00 -34.669825 356.421155 -34.669825 2015-03-28 00:05:00+01:00 -34.705895 357.939260 -34.705895 2015-03-28 00:10:00+01:00 -34.721813 359.458321 -34.721813 2015-03-28 00:15:00+01:00 -34.717561 0.977630 -34.717561 2015-03-28 00:20:00+01:00 -34.693143 2.496380 -34.693143 azimuth apparent_zenith zenith 2015-03-28 00:00:00+01:00 356.421155 124.669825 124.669825 2015-03-28 00:05:00+01:00 357.939260 124.705895 124.705895 2015-03-28 00:10:00+01:00 359.458321 124.721813 124.721813 2015-03-28 00:15:00+01:00 0.977630 124.717561 124.717561 2015-03-28 00:20:00+01:00 2.496380 124.693143 124.693143 ephem apparent_elevation elevation azimuth \ 2015-03-28 00:00:00+01:00 -34.667077 -34.667077 356.419216 2015-03-28 00:05:00+01:00 -34.703175 -34.703175 357.937292 2015-03-28 00:10:00+01:00 -34.719120 -34.719120 359.456355 2015-03-28 00:15:00+01:00 -34.714893 -34.714893 0.975640 2015-03-28 00:20:00+01:00 -34.690500 -34.690500 2.494378 apparent_zenith zenith solar_time 2015-03-28 00:00:00+01:00 124.667077 124.667077 23.803474 2015-03-28 00:05:00+01:00 124.703175 124.703175 23.886825 2015-03-28 00:10:00+01:00 124.719120 124.719120 23.970175 2015-03-28 00:15:00+01:00 124.714893 124.714893 0.053526 2015-03-28 00:20:00+01:00 124.690500 124.690500 0.136877
loc = berlin
times = pd.DatetimeIndex(start=datetime.date(2015,3,30), end=datetime.date(2015,3,31), freq='5min')
pyephemout = pvlib.solarposition.pyephem(times, loc)
ephemout = pvlib.solarposition.ephemeris(times, loc)
pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
ephemout['elevation'].plot(label='ephem')
plt.legend(ncol=2)
plt.title('elevation')
plt.figure()
pyephemout['azimuth'].plot(label='pyephem')
ephemout['azimuth'].plot(label='ephem')
plt.legend(ncol=2)
plt.title('azimuth')
print('pyephem')
print(pyephemout.head())
print('ephem')
print(ephemout.head())
pyephem apparent_elevation apparent_azimuth elevation \ 2015-03-30 00:00:00+02:00 -31.976429 338.920871 -31.976429 2015-03-30 00:05:00+02:00 -32.239797 340.360894 -32.239797 2015-03-30 00:10:00+02:00 -32.485100 341.809413 -32.485100 2015-03-30 00:15:00+02:00 -32.712074 343.265937 -32.712074 2015-03-30 00:20:00+02:00 -32.920477 344.729920 -32.920477 azimuth apparent_zenith zenith 2015-03-30 00:00:00+02:00 338.920871 121.976429 121.976429 2015-03-30 00:05:00+02:00 340.360894 122.239797 122.239797 2015-03-30 00:10:00+02:00 341.809413 122.485100 122.485100 2015-03-30 00:15:00+02:00 343.265937 122.712074 122.712074 2015-03-30 00:20:00+02:00 344.729920 122.920477 122.920477 ephem apparent_elevation elevation azimuth \ 2015-03-30 00:00:00+02:00 -31.973191 -31.973191 338.919052 2015-03-30 00:05:00+02:00 -32.236587 -32.236587 340.359056 2015-03-30 00:10:00+02:00 -32.481918 -32.481918 341.807546 2015-03-30 00:15:00+02:00 -32.708921 -32.708921 343.264033 2015-03-30 00:20:00+02:00 -32.917353 -32.917353 344.727991 apparent_zenith zenith solar_time 2015-03-30 00:00:00+02:00 121.973191 121.973191 22.813319 2015-03-30 00:05:00+02:00 122.236587 122.236587 22.896670 2015-03-30 00:10:00+02:00 122.481918 122.481918 22.980021 2015-03-30 00:15:00+02:00 122.708921 122.708921 23.063372 2015-03-30 00:20:00+02:00 122.917353 122.917353 23.146722
loc = berlin
times = pd.DatetimeIndex(start=datetime.date(2015,6,28), end=datetime.date(2015,6,29), freq='5min')
pyephemout = pvlib.solarposition.pyephem(times, loc)
ephemout = pvlib.solarposition.ephemeris(times, loc)
pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
ephemout['elevation'].plot(label='ephem')
plt.legend(ncol=2)
plt.title('elevation')
plt.figure()
pyephemout['azimuth'].plot(label='pyephem')
ephemout['azimuth'].plot(label='ephem')
plt.legend(ncol=2)
plt.title('azimuth')
print('pyephem')
print(pyephemout.head())
print('ephem')
print(ephemout.head())
pyephem apparent_elevation apparent_azimuth elevation \ 2015-06-28 00:00:00+02:00 -12.679250 343.659411 -12.679250 2015-06-28 00:05:00+02:00 -12.885989 344.817210 -12.885989 2015-06-28 00:10:00+02:00 -13.077872 345.978615 -13.077872 2015-06-28 00:15:00+02:00 -13.254779 347.143381 -13.254779 2015-06-28 00:20:00+02:00 -13.416591 348.311289 -13.416591 azimuth apparent_zenith zenith 2015-06-28 00:00:00+02:00 343.659411 102.679250 102.679250 2015-06-28 00:05:00+02:00 344.817210 102.885989 102.885989 2015-06-28 00:10:00+02:00 345.978615 103.077872 103.077872 2015-06-28 00:15:00+02:00 347.143381 103.254779 103.254779 2015-06-28 00:20:00+02:00 348.311289 103.416591 103.416591 ephem apparent_elevation elevation azimuth \ 2015-06-28 00:00:00+02:00 -12.674183 -12.674183 343.658144 2015-06-28 00:05:00+02:00 -12.880940 -12.880940 344.815897 2015-06-28 00:10:00+02:00 -13.072843 -13.072843 345.977260 2015-06-28 00:15:00+02:00 -13.249769 -13.249769 347.141996 2015-06-28 00:20:00+02:00 -13.411601 -13.411601 348.309859 apparent_zenith zenith solar_time 2015-06-28 00:00:00+02:00 102.674183 102.674183 22.840578 2015-06-28 00:05:00+02:00 102.880940 102.880940 22.923900 2015-06-28 00:10:00+02:00 103.072843 103.072843 23.007221 2015-06-28 00:15:00+02:00 103.249769 103.249769 23.090542 2015-06-28 00:20:00+02:00 103.411601 103.411601 23.173864
%%timeit
pyephemout = pvlib.solarposition.pyephem(times, loc)
#ephemout = pvlib.solarposition.ephemeris(times, loc)
10 loops, best of 3: 36.3 ms per loop
%%timeit
#pyephemout = pvlib.solarposition.pyephem(times, loc)
ephemout = pvlib.solarposition.ephemeris(times, loc)
100 loops, best of 3: 8.42 ms per loop
%%timeit
#pyephemout = pvlib.solarposition.pyephem(times, loc)
ephemout = pvlib.solarposition.get_solarposition(times, loc, method='nrel_numpy')
100 loops, best of 3: 18.5 ms per loop
This numba test will only work properly if you have installed numba.
%%timeit
#pyephemout = pvlib.solarposition.pyephem(times, loc)
ephemout = pvlib.solarposition.get_solarposition(times, loc, method='nrel_numba')
The slowest run took 1251.83 times longer than the fastest. This could mean that an intermediate result is being cached 1 loops, best of 3: 4.2 ms per loop
The numba calculation takes a long time the first time that it's run because it uses LLVM to compile the Python code to machine code. After that it's about 4-10 times faster depending on your machine. You can pass a numthreads
argument to this function. The optimum numthreads
depends on your machine and is equal to 4 by default.
%%timeit
#pyephemout = pvlib.solarposition.pyephem(times, loc)
ephemout = pvlib.solarposition.get_solarposition(times, loc, method='nrel_numba', numthreads=16)
100 loops, best of 3: 4.02 ms per loop
%%timeit
ephemout = pvlib.solarposition.spa_python(times, loc, how='numba', numthreads=16)
100 loops, best of 3: 4 ms per loop