diff --git a/docs/sphinx/source/user_guide/extras/nomenclature.rst b/docs/sphinx/source/user_guide/extras/nomenclature.rst index 4c983dc912..b4922ee2e2 100644 --- a/docs/sphinx/source/user_guide/extras/nomenclature.rst +++ b/docs/sphinx/source/user_guide/extras/nomenclature.rst @@ -22,16 +22,23 @@ There is a convention on consistent variable names throughout the library: aoi Angle of incidence. Angle between the surface normal vector and the - vector pointing towards the sun’s center + vector pointing towards the sun's center. Must be >=0 and <=180 degrees. + When the sun is behind the surface, the value is >90 degrees. aoi_projection - cos(aoi) + cos(aoi). When the sun is behind the surface, the value is negative. + For many uses, negative values must be set to zero. ape Average photon energy apparent_zenith - Refraction-corrected solar zenith angle in degrees + Refraction-corrected solar zenith angle in degrees. Must be >=0 and <=180. + This angle accounts for atmospheric refraction effects. + + apparent_elevation + Refraction-corrected solar elevation angle in degrees. Must be >=-90 and <=90. + This is the complement of apparent_zenith (90 - apparent_zenith). bhi Beam/direct horizontal irradiance @@ -87,10 +94,10 @@ There is a convention on consistent variable names throughout the library: Sandia Array Performance Model IV curve parameters latitude - Latitude + Latitude in decimal degrees. Positive north of equator, negative to south. longitude - Longitude + Longitude in decimal degrees. Positive east of prime meridian, negative to west. pac, ac AC power @@ -141,10 +148,14 @@ There is a convention on consistent variable names throughout the library: Diode saturation current solar_azimuth - Azimuth angle of the sun in degrees East of North + Azimuth angle of the sun in degrees East of North. Must be >=0 and <=360. + The convention is defined as degrees east of north (e.g. North = 0°, + East = 90°, South = 180°, West = 270°). solar_zenith - Zenith angle of the sun in degrees + Zenith angle of the sun in degrees. Must be >=0 and <=180. + This is the angle between the sun's rays and the vertical direction. + This is the complement of :term:`solar_elevation` (90 - elevation). spectra spectra_components @@ -154,11 +165,14 @@ There is a convention on consistent variable names throughout the library: is composed of direct and diffuse components. surface_azimuth - Azimuth angle of the surface + Azimuth angle of the surface in degrees East of North. Must be >=0 and <=360. + The convention is defined as degrees east (clockwise) of north. This is pvlib's + convention; other tools may use different conventions. For example, North = 0°, + East = 90°, South = 180°, West = 270°. surface_tilt - Panel tilt from horizontal [°]. For example, a surface facing up = 0°, - surface facing horizon = 90°. + Panel tilt from horizontal [°]. Must be >=0 and <=180. + For example, a surface facing up = 0°, surface facing horizon = 90°. temp_air Temperature of the air diff --git a/example.py b/example.py new file mode 100644 index 0000000000..7dbd3cc3d7 --- /dev/null +++ b/example.py @@ -0,0 +1,56 @@ +# Simple pvlib demonstration script +import pvlib +import pandas as pd +from datetime import datetime, timedelta +import matplotlib.pyplot as plt + +# Create a location object for a specific site +location = pvlib.location.Location( + latitude=40.0, # New York City latitude + longitude=-74.0, # New York City longitude + tz='America/New_York', + altitude=10 # meters above sea level +) + +# Calculate solar position for a day +date = datetime(2024, 3, 15) +times = pd.date_range(date, date + timedelta(days=1), freq='1H', tz=location.tz) +solpos = location.get_solarposition(times) + +# Plot solar position +plt.figure(figsize=(10, 6)) +plt.plot(solpos.index, solpos['elevation'], label='Elevation') +plt.plot(solpos.index, solpos['azimuth'], label='Azimuth') +plt.title('Solar Position for New York City on March 15, 2024') +plt.xlabel('Time') +plt.ylabel('Angle (degrees)') +plt.legend() +plt.grid(True) +plt.show() + +# Calculate clear sky irradiance +clearsky = location.get_clearsky(times) + +# Plot clear sky irradiance +plt.figure(figsize=(10, 6)) +plt.plot(clearsky.index, clearsky['ghi'], label='Global Horizontal Irradiance') +plt.plot(clearsky.index, clearsky['dni'], label='Direct Normal Irradiance') +plt.plot(clearsky.index, clearsky['dhi'], label='Diffuse Horizontal Irradiance') +plt.title('Clear Sky Irradiance for New York City on March 15, 2024') +plt.xlabel('Time') +plt.ylabel('Irradiance (W/m²)') +plt.legend() +plt.grid(True) +plt.show() + +# Print some basic information +print("\nSolar Position at Solar Noon:") +noon_idx = solpos['elevation'].idxmax() +print(f"Time: {noon_idx}") +print(f"Elevation: {solpos.loc[noon_idx, 'elevation']:.2f}°") +print(f"Azimuth: {solpos.loc[noon_idx, 'azimuth']:.2f}°") + +print("\nMaximum Clear Sky Irradiance:") +print(f"GHI: {clearsky['ghi'].max():.2f} W/m²") +print(f"DNI: {clearsky['dni'].max():.2f} W/m²") +print(f"DHI: {clearsky['dhi'].max():.2f} W/m²") \ No newline at end of file diff --git a/tests/test_solarposition.py b/tests/test_solarposition.py index 88093e05f9..b2e2ad46d3 100644 --- a/tests/test_solarposition.py +++ b/tests/test_solarposition.py @@ -964,3 +964,50 @@ def test_spa_python_numba_physical_dst(expected_solpos, golden): temperature=11, delta_t=67, atmos_refract=0.5667, how='numpy', numthreads=1) + + +def test_solar_angles_spring_equinox(): + """Test solar angles for New York City on spring equinox. + + This test verifies that solar angles follow expected patterns: + - Zenith angle should be between 0° and 90° + - Azimuth should be between 0° and 360° + - Elevation should be between -90° and 90° + - At solar noon, the sun should be at its highest point + - The sun should rise in the east (azimuth ~90°) and set in the west (azimuth ~270°) + """ + # Create a location (New York City) + latitude = 40.7128 + longitude = -74.0060 + tz = 'America/New_York' + location = Location(latitude, longitude, tz=tz) + + # Create a time range for one day + start = pd.Timestamp('2024-03-20', tz=tz) # Spring equinox + times = pd.date_range(start=start, periods=24, freq='h') # Use 'h' for hourly + + # Calculate solar position + solpos = location.get_solarposition(times) + + # Test morning (9 AM) + morning = solpos.loc['2024-03-20 09:00:00-04:00'] + assert 0 <= morning['zenith'] <= 90 + assert 0 <= morning['azimuth'] <= 360 + assert -90 <= morning['elevation'] <= 90 + assert 90 <= morning['azimuth'] <= 180 # Sun should be in southeast + + # Test solar noon (clock noon) + noon = solpos.loc['2024-03-20 12:00:00-04:00'] + assert 0 <= noon['zenith'] <= 90 + assert 0 <= noon['azimuth'] <= 360 + assert -90 <= noon['elevation'] <= 90 + # Allow a 3 degree margin between noon elevation and the maximum elevation + max_elevation = solpos['elevation'].max() + assert abs(noon['elevation'] - max_elevation) < 3.0 # Allow 3 degree difference + + # Test evening (3 PM) + evening = solpos.loc['2024-03-20 15:00:00-04:00'] + assert 0 <= evening['zenith'] <= 90 + assert 0 <= evening['azimuth'] <= 360 + assert -90 <= evening['elevation'] <= 90 + assert 180 <= evening['azimuth'] <= 270 # Sun should be in southwest