Skip to content
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
7916426
Create twoaxistrackerfield.py
AdamRJensen Feb 22, 2022
396e297
Add TwoAxisTrackerField to __init__.py and documentation.rst
AdamRJensen Feb 22, 2022
97bf622
Add _calculate_l_min to layout
AdamRJensen Feb 22, 2022
1561434
Fix linter error
AdamRJensen Feb 22, 2022
5e4cbf6
Update twoaxistrackerfield.py
AdamRJensen Feb 22, 2022
b63e676
Reorder layout ValueError warnings
AdamRJensen Feb 23, 2022
9ee9bd9
Fix typo
AdamRJensen Feb 27, 2022
de7b7d7
Remove layout_type from generate_field_layout function
AdamRJensen Feb 27, 2022
31f45a2
Add layout_type code to TwoAxisTrackerField
AdamRJensen Feb 27, 2022
156d98f
L_min -> min_tracker_spacing
AdamRJensen Feb 27, 2022
90893d1
Add whatsnew entry
AdamRJensen Feb 27, 2022
f5db9d1
Fix linting errors
AdamRJensen Feb 27, 2022
6fcbc4c
Update twoaxistrackerfield.py
AdamRJensen Feb 27, 2022
167078f
Correct horizon shading
AdamRJensen Feb 28, 2022
dd40bcd
Update twoaxistracking/twoaxistrackerfield.py
AdamRJensen Feb 28, 2022
d898205
Update twoaxistracking/twoaxistrackerfield.py
AdamRJensen Feb 28, 2022
bc93293
Update twoaxistracking/twoaxistrackerfield.py
AdamRJensen Feb 28, 2022
6fd5717
Return scalar when scalar is passed
AdamRJensen Feb 28, 2022
5177ff9
Correct use of np.isscalar
AdamRJensen Feb 28, 2022
4e36d1d
Update documentation
AdamRJensen Feb 28, 2022
77a5abc
Update whatsnew
AdamRJensen Feb 28, 2022
9210ac3
Rework intro_tutorial.ipynb
AdamRJensen Feb 28, 2022
82116a7
Update README.md
AdamRJensen Feb 27, 2022
86cf947
Create test_layout.py
AdamRJensen Feb 27, 2022
8f5098e
Update test_layout.py
AdamRJensen Feb 27, 2022
2f0ef34
Use test folder in package folder
AdamRJensen Feb 27, 2022
49e3ac7
Delete test_placeholder.py
AdamRJensen Feb 27, 2022
18c6549
Add test files
AdamRJensen Feb 27, 2022
057e3aa
Fix linting errors
AdamRJensen Feb 28, 2022
901111a
Update scalar test
AdamRJensen Feb 28, 2022
3e49947
Add pandas to test in setup.cfg
AdamRJensen Feb 28, 2022
bcf5908
Revert "package metadata and doc build updates (#17)"
AdamRJensen Feb 28, 2022
147ba2a
Undo commits
AdamRJensen Feb 28, 2022
a87ee3e
Merge branch 'tests' of https://github.com/pvlib/twoaxistracking into…
AdamRJensen Feb 28, 2022
e9cc008
Revert "Undo commits"
AdamRJensen Feb 28, 2022
e4d2d82
Add test files
AdamRJensen Feb 28, 2022
0cdfffc
Switch to using np.testing.assert_allclose
AdamRJensen Feb 28, 2022
13c7bd5
Add test for sloped field layout
AdamRJensen Feb 28, 2022
4a75240
Move test_twoaxistracker to twoaxistracker PR
AdamRJensen Mar 1, 2022
a9f368a
Add conftest.py to avoid duplicate fixtures
AdamRJensen Mar 1, 2022
2a44791
Merge branch 'main' into tests
AdamRJensen Mar 1, 2022
c517f4e
Remove imports of fixtures from conftest
AdamRJensen Mar 1, 2022
37ee22f
Remove unnecessary imports
AdamRJensen Mar 1, 2022
eda911a
Increase test tolerance for test_field_slope
AdamRJensen Mar 1, 2022
6d6a391
Add multi-polygon active area test
AdamRJensen Mar 1, 2022
e4ea335
Add empty __init__.py
AdamRJensen Mar 1, 2022
4c84e5d
Use "from .conftest" as Kevin said..
AdamRJensen Mar 1, 2022
b3d2496
Base square_field_layout_slope fixture off square_field_layout
AdamRJensen Mar 2, 2022
7352085
Fix linter error
AdamRJensen Mar 2, 2022
39c94e6
Remove backslash in conftest.py
AdamRJensen Mar 2, 2022
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
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ install_requires =
test =
pytest
pytest-cov
pandas
doc =
sphinx==4.4.0
myst-nb==0.13.2
Expand Down
181 changes: 181 additions & 0 deletions twoaxistracking/tests/test_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
from twoaxistracking import layout
from shapely import geometry
import numpy as np
import pytest


@pytest.fixture
def rectangular_geometry():
collector_geometry = geometry.box(-2, -1, 2, 1)
total_collector_area = collector_geometry.area
min_tracker_spacing = layout._calculate_min_tracker_spacing(collector_geometry)
return collector_geometry, total_collector_area, min_tracker_spacing


@pytest.fixture
def square_field_layout():
# Corresponds to GCR 0.125 with the rectangular_geometry
X = np.array([-8, 0, 8, -8, 8, -8, 0, 8])
Y = np.array([-8, -8, -8, 0, 0, 8, 8, 8])
tracker_distance = (X**2 + Y**2)**0.5
relative_azimuth = np.array([225, 180, 135, 270, 90, 315, 0, 45])
Z = np.zeros(8)
relative_slope = np.zeros(8)
return X, Y, Z, tracker_distance, relative_azimuth, relative_slope


@pytest.fixture
def square_field_layout_sloped():
# Corresponds to GCR 0.125 with the rectangular_geometry
X = np.array([-8, 0, 8, -8, 8, -8, 0, 8])
Y = np.array([-8, -8, -8, 0, 0, 8, 8, 8])
tracker_distance = (X**2 + Y**2)**0.5
relative_azimuth = np.array([225, 180, 135, 270, 90, 315, 0, 45])
Z = np.array([0.12372765, 0.06186383, 0, 0.06186383,
-0.06186383, 0, -0.06186383, -0.12372765])
relative_slope = np.array([5, 3.53553391, 0, 3.53553391,
-3.53553391, 0, -3.53553391, -5])
return X, Y, Z, tracker_distance, relative_azimuth, relative_slope


def test_min_tracker_spacing_rectangle(rectangular_geometry):
# Test calculation of min_tracker_spacing for a rectangular collector
min_tracker_spacing = layout._calculate_min_tracker_spacing(rectangular_geometry[0])
np.testing.assert_allclose(min_tracker_spacing, np.sqrt(4**2+2**2))


def test_min_tracker_spacing_circle():
# Test calculation of min_tracker_spacing for a circular collector with radius 1
collector_geometry = geometry.Point(0, 0).buffer(1)
min_tracker_spacing = layout._calculate_min_tracker_spacing(collector_geometry)
assert min_tracker_spacing == 2


def test_min_tracker_spacing_circle_offcenter():
# Test calculation of min_tracker_spacing for a circular collector with radius 1 rotating
# off-center around the point (0, 1)
collector_geometry = geometry.Point(0, 1).buffer(1)
min_tracker_spacing = layout._calculate_min_tracker_spacing(collector_geometry)
assert min_tracker_spacing == 4


def test_min_tracker_spacingpolygon():
# Test calculation of min_tracker_spacing for a polygon
collector_geometry = geometry.Polygon([(-1, -1), (3, 2), (4, 4), (1, 2), (-1, -1)])
min_tracker_spacing = layout._calculate_min_tracker_spacing(collector_geometry)
np.testing.assert_allclose(min_tracker_spacing, 2 * np.sqrt(4**2 + 4**2))


def test_square_layout_generation(rectangular_geometry, square_field_layout):
# Test that a square field layout is returned correctly
collector_geometry, total_collector_area, min_tracker_spacing = rectangular_geometry
X_exp, Y_exp, Z_exp, tracker_distance_exp, relative_azimuth_exp, relative_slope_exp = \
square_field_layout

X, Y, Z, tracker_distance, relative_azimuth, relative_slope = \
layout.generate_field_layout(
gcr=0.125,
total_collector_area=total_collector_area,
min_tracker_spacing=min_tracker_spacing,
neighbor_order=1,
aspect_ratio=1,
offset=0,
rotation=0)
np.testing.assert_allclose(X, X_exp)
np.testing.assert_allclose(Y, Y_exp)
np.testing.assert_allclose(Z, Z_exp)
np.testing.assert_allclose(tracker_distance_exp, tracker_distance_exp)
np.testing.assert_allclose(relative_azimuth, relative_azimuth_exp)
np.testing.assert_allclose(relative_slope, relative_slope_exp)


def test_field_slope(rectangular_geometry, square_field_layout_sloped):
# Test that a square field layout on tilted surface is returned correctly
collector_geometry, total_collector_area, min_tracker_spacing = rectangular_geometry
X_exp, Y_exp, Z_exp, tracker_distance_exp, relative_azimuth_exp, relative_slope_exp = \
square_field_layout_sloped
X, Y, Z, tracker_distance, relative_azimuth, relative_slope = \
layout.generate_field_layout(
gcr=0.125,
total_collector_area=total_collector_area,
min_tracker_spacing=min_tracker_spacing,
neighbor_order=1,
aspect_ratio=1,
offset=0,
rotation=0,
slope_azimuth=45,
slope_tilt=5)
np.testing.assert_allclose(X, X_exp)
np.testing.assert_allclose(Y, Y_exp)
np.testing.assert_allclose(Z, Z_exp)
np.testing.assert_allclose(tracker_distance_exp, tracker_distance_exp)
np.testing.assert_allclose(relative_azimuth, relative_azimuth_exp)
np.testing.assert_allclose(relative_slope, relative_slope_exp, atol=10**-9)


def test_layout_generation_value_error(rectangular_geometry):
# Test if value errors are correctly raised
collector_geometry, total_collector_area, min_tracker_spacing = rectangular_geometry

# Test if ValueError is raised if offset is out of range
with pytest.raises(ValueError, match="offset is outside the valid range"):
_ = layout.generate_field_layout(
gcr=0.25, total_collector_area=total_collector_area,
min_tracker_spacing=min_tracker_spacing, neighbor_order=1,
aspect_ratio=1, offset=1.1, rotation=0)

# Test if ValueError is raised if aspect ratio is too low
with pytest.raises(ValueError, match="Aspect ratio is too low"):
_ = layout.generate_field_layout(
gcr=0.25, total_collector_area=total_collector_area,
min_tracker_spacing=min_tracker_spacing, neighbor_order=1,
aspect_ratio=0.6, offset=0, rotation=0)

# Test if ValueError is raised if aspect ratio is too high
with pytest.raises(ValueError, match="Aspect ratio is too high"):
_ = layout.generate_field_layout(
gcr=0.25, total_collector_area=total_collector_area,
min_tracker_spacing=min_tracker_spacing, neighbor_order=1,
aspect_ratio=5, offset=0, rotation=0)

# Test if ValueError is raised if rotation is greater than 180 degrees
with pytest.raises(ValueError, match="rotation is outside the valid range"):
_ = layout.generate_field_layout(
gcr=0.25, total_collector_area=total_collector_area,
min_tracker_spacing=min_tracker_spacing, neighbor_order=1,
aspect_ratio=1.2, offset=0, rotation=190)

# Test if ValueError is raised if rotation is less than 0
with pytest.raises(ValueError, match="rotation is outside the valid range"):
_ = layout.generate_field_layout(
gcr=0.5, total_collector_area=total_collector_area,
min_tracker_spacing=min_tracker_spacing, neighbor_order=1,
aspect_ratio=1, offset=0, rotation=-1)

# Test if ValueError is raised if min_tracker_spacing is outside valid range
with pytest.raises(ValueError, match="Lmin is not physically possible"):
_ = layout.generate_field_layout(
gcr=0.25, total_collector_area=total_collector_area,
min_tracker_spacing=1, neighbor_order=1, aspect_ratio=1.2,
offset=0, rotation=90)

# Test if ValueError is raised if maximum ground cover ratio is exceeded
with pytest.raises(ValueError, match="Maximum ground cover ratio exceded"):
_ = layout.generate_field_layout(
gcr=0.5, total_collector_area=total_collector_area,
min_tracker_spacing=min_tracker_spacing, neighbor_order=1,
aspect_ratio=1, offset=0, rotation=0)


def test_neighbor_order(rectangular_geometry):
collector_geometry, total_collector_area, min_tracker_spacing = rectangular_geometry
X, Y, Z, tracker_distance, relative_azimuth, relative_slope = \
layout.generate_field_layout(
gcr=0.125,
total_collector_area=total_collector_area,
min_tracker_spacing=min_tracker_spacing,
neighbor_order=3,
aspect_ratio=1,
offset=0,
rotation=0)
assert len(X) == (7*7-1)
6 changes: 0 additions & 6 deletions twoaxistracking/tests/test_placeholder.py

This file was deleted.

52 changes: 52 additions & 0 deletions twoaxistracking/tests/test_plotting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import matplotlib.pyplot as plt
from twoaxistracking import plotting, layout, twoaxistrackerfield
from shapely import geometry
import numpy as np
import pytest


def assert_isinstance(obj, klass):
assert isinstance(obj, klass), f'got {type(obj)}, expected {klass}'


def test_field_layout_plot():
X = np.array([-8, 0, 8, -8, 8, -8, 0, 8])
Y = np.array([-8, -8, -8, 0, 0, 8, 8, 8])
Z = np.array([0.1237, 0.0619, 0, 0.0619, -0.0619, 0, -0.0619, -0.1237])
L_min = 4.4721
result = plotting._plot_field_layout(X, Y, Z, L_min)
assert_isinstance(result, plt.Figure)
plt.close('all')


@pytest.fixture
def rectangular_geometry():
collector_geometry = geometry.box(-2, -1, 2, 1)
total_collector_area = collector_geometry.area
min_tracker_spacing = layout._calculate_min_tracker_spacing(collector_geometry)
return collector_geometry, total_collector_area, min_tracker_spacing


def test_shading_plot(rectangular_geometry):
collector_geometry, total_collector_area, min_tracker_spacing = rectangular_geometry
result = plotting._plot_shading(collector_geometry, collector_geometry,
collector_geometry, min_tracker_spacing)
assert_isinstance(result, plt.Figure)
plt.close('all')


def test_plotting_of_field_layout(rectangular_geometry):
# Test if plot_field_layout returns a figure object
collector_geometry, total_collector_area, min_tracker_spacing = rectangular_geometry
field = twoaxistrackerfield.TwoAxisTrackerField(
total_collector_geometry=collector_geometry,
active_collector_geometry=collector_geometry,
neighbor_order=1,
gcr=0.25,
aspect_ratio=1,
offset=0.45,
rotation=30,
)
result = field.plot_field_layout()
assert_isinstance(result, plt.Figure)
plt.close('all')
125 changes: 125 additions & 0 deletions twoaxistracking/tests/test_shading.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
from twoaxistracking import shading, layout
from shapely import geometry
import numpy as np
import pytest


@pytest.fixture
def rectangular_geometry():
collector_geometry = geometry.box(-2, -1, 2, 1)
total_collector_area = collector_geometry.area
min_tracker_spacing = layout._calculate_min_tracker_spacing(collector_geometry)
return collector_geometry, total_collector_area, min_tracker_spacing


@pytest.fixture
def square_field_layout():
# Corresponds to GCR 0.125 with the rectangular_geometry
X = np.array([-8, 0, 8, -8, 8, -8, 0, 8])
Y = np.array([-8, -8, -8, 0, 0, 8, 8, 8])
tracker_distance = (X**2 + Y**2)**0.5
relative_azimuth = np.array([225, 180, 135, 270, 90, 315, 0, 45])
Z = np.zeros(8)
relative_slope = np.zeros(8)
return X, Y, Z, tracker_distance, relative_azimuth, relative_slope


def test_shading(rectangular_geometry, square_field_layout):
# Test shading calculation
# Also plots the geometry (ensures no errors are raised)
collector_geometry, total_collector_area, min_tracker_spacing = rectangular_geometry
X, Y, Z, tracker_distance, relative_azimuth, relative_slope = \
square_field_layout
shaded_fraction = shading.shaded_fraction(
solar_elevation=3,
solar_azimuth=120,
total_collector_geometry=collector_geometry,
active_collector_geometry=collector_geometry,
min_tracker_spacing=min_tracker_spacing,
tracker_distance=tracker_distance,
relative_azimuth=relative_azimuth,
relative_slope=relative_slope,
slope_azimuth=0,
slope_tilt=0,
plot=True)
np.testing.assert_allclose(shaded_fraction, 0.191324034)


def test_shading_zero_solar_elevation(rectangular_geometry, square_field_layout):
# Test shading when geometries completely overlap
collector_geometry, total_collector_area, min_tracker_spacing = rectangular_geometry
X, Y, Z, tracker_distance, relative_azimuth, relative_slope = \
square_field_layout
shaded_fraction = shading.shaded_fraction(
solar_elevation=0,
solar_azimuth=180,
total_collector_geometry=collector_geometry,
active_collector_geometry=collector_geometry,
min_tracker_spacing=min_tracker_spacing,
tracker_distance=tracker_distance,
relative_azimuth=relative_azimuth,
relative_slope=relative_slope,
slope_azimuth=0,
slope_tilt=0,
plot=False)
assert shaded_fraction == 1


def test_no_shading(rectangular_geometry, square_field_layout):
# Test shading calculation when there is no shading (high solar elevation)
collector_geometry, total_collector_area, min_tracker_spacing = rectangular_geometry
X, Y, Z, tracker_distance, relative_azimuth, relative_slope = \
square_field_layout
shaded_fraction = shading.shaded_fraction(
solar_elevation=45,
solar_azimuth=180,
total_collector_geometry=collector_geometry,
active_collector_geometry=collector_geometry,
min_tracker_spacing=min_tracker_spacing,
tracker_distance=tracker_distance,
relative_azimuth=relative_azimuth,
relative_slope=relative_slope,
slope_azimuth=0,
slope_tilt=0,
plot=False)
assert shaded_fraction == 0


def test_shading_below_horizon(rectangular_geometry, square_field_layout):
# Test shading calculation when sun is below the horizon (elevation<0)
collector_geometry, total_collector_area, min_tracker_spacing = rectangular_geometry
X, Y, Z, tracker_distance, relative_azimuth, relative_slope = \
square_field_layout
shaded_fraction = shading.shaded_fraction(
solar_elevation=-5.1,
solar_azimuth=180,
total_collector_geometry=collector_geometry,
active_collector_geometry=collector_geometry,
min_tracker_spacing=min_tracker_spacing,
tracker_distance=tracker_distance,
relative_azimuth=relative_azimuth,
relative_slope=relative_slope,
slope_azimuth=0,
slope_tilt=0,
plot=False)
assert np.isnan(shaded_fraction)


def test_shading_below_hill_horizon(rectangular_geometry, square_field_layout):
# Test shading when sun is below horizon line caused by sloped surface
collector_geometry, total_collector_area, min_tracker_spacing = rectangular_geometry
X, Y, Z, tracker_distance, relative_azimuth, relative_slope = \
square_field_layout
shaded_fraction = shading.shaded_fraction(
solar_elevation=9,
solar_azimuth=180,
total_collector_geometry=collector_geometry,
active_collector_geometry=collector_geometry,
min_tracker_spacing=min_tracker_spacing,
tracker_distance=tracker_distance,
relative_azimuth=relative_azimuth,
relative_slope=relative_slope,
slope_azimuth=0,
slope_tilt=10,
plot=False)
assert shaded_fraction == 1