Skip to content

Commit

Permalink
Simplify timeseries calculation API (#14)
Browse files Browse the repository at this point in the history
* Remove "save_segments" arguments as not needed anymore

* Compact formatting in timeseries calculation function

* Fix issue with test_serial_perez that wasn't caught in previous PR

* Improving test for perez luminance calculation

* Major update of timeseries calculation API

* all tests are now passing
* now passing individual arguments instead of dataframes with all arguments
* broke custom perez calculation from pvarray timeseries calculation:
this will allow the users to use one or the other separately
* next step will be to remove the "simple" calculation mode

* Add pytest-mock to circleci config for testing

* Removed the specific functions related to "isotropic" calcs

* now only using the perez functions, even when using isotropic approach
(just setting circumsolar and horizon to 0)

* Reorganizing package: seperate timeseries fns from plotting ones

* Update docstrings in timeseries and plot modules
  • Loading branch information
anomam authored Sep 12, 2018
1 parent 7f8e2b3 commit 33830cb
Show file tree
Hide file tree
Showing 17 changed files with 797 additions and 694 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- v1-dep-{{ .Branch }}-
- v1-dep-master-
- v1-dep-
- run: sudo pip install pytest==3.2.2 numpy==1.13.3 scipy==0.19.1 pandas==0.23.3 shapely==1.6.1 pvlib==0.5.0 future==0.16.0 six==1.11.0
- run: sudo pip install pytest==3.2.2 numpy==1.13.3 scipy==0.19.1 pandas==0.23.3 shapely==1.6.1 pvlib==0.5.0 future==0.16.0 six==1.11.0 pytest-mock==1.10.0
- save_cache:
key: v1-dep-{{ .Branch }}-{{ epoch }}
paths:
Expand Down
28 changes: 28 additions & 0 deletions pvfactors/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-

import logging
import sys
logging.basicConfig()

from pvfactors.version import __version__
Expand All @@ -16,3 +17,30 @@ class PVFactorsEdgePointDoesNotExist(Exception):

class PVFactorsArrayUpdateException(Exception):
pass


# Define function used for progress bar when running long simulations
# Borrowed from: https://gist.github.com/aubricus
def print_progress(iteration, total, prefix='', suffix='', decimals=1,
bar_length=100):
"""
Call in a loop to create terminal progress bar
@params:
iteration - Required : current iteration (Int)
total - Required : total iterations (Int)
prefix - Optional : prefix string (Str)
suffix - Optional : suffix string (Str)
decimals - Optional : positive number of decimals in percent
complete (Int)
bar_length - Optional : character length of bar (Int)
"""
format_str = "{0:." + str(decimals) + "f}"
percents = format_str.format(100 * (iteration / float(total)))
filled_length = int(round(bar_length * iteration / float(total)))
bar = '█' * filled_length + '-' * (bar_length - filled_length)
sys.stdout.write('\r%s |%s| %s%s %s' % (prefix, bar, percents, '%',
suffix)),
sys.stdout.flush()
if iteration == total:
sys.stdout.write('\n')
sys.stdout.flush()
117 changes: 117 additions & 0 deletions pvfactors/plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-

import numpy as np
from pvfactors import logging

LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.INFO)

# Define colors used for plotting the 2D arrays
COLOR_dic = {
'i': '#FFBB33',
's': '#A7A49D',
't': '#6699cc',
'pvrow_illum': '#6699cc',
'pvrow_shaded': '#ff0000',
'ground_shaded': '#A7A49D',
'ground_illum': '#FFBB33'
}


def plot_array_from_registry(ax, registry, line_types_selected=None,
fontsize=20):
"""
Plot a 2D PV array using the ``shapely`` geometry objects located in
a :class:`pvarray.Array` surface registry.
:param matplotlib.axes.Axes ax: axes to use for the plot
:param pd.DataFrame registry: registry containing geometries to plot
:param list line_types_selected: parameter used to select a subset of
'line_type' to plot; e.g. 'pvrow' or 'ground'
:return: None (``ax`` is updated)
"""

registry = registry.copy()
registry.loc[:, 'color'] = (
registry.line_type.values + '_'
+ np.where(registry.shaded.values, 'shaded', 'illum'))
# TODO: distance may not exist
if line_types_selected:
for line_type in line_types_selected:
surface_reg_selected = registry.loc[
registry.line_type == line_type, :]
for index, row in surface_reg_selected.iterrows():
LOGGER.debug("Plotting %s", row['line_type'])
plot_coords(ax, row['geometry'])
plot_bounds(ax, row['geometry'])
plot_line(ax, row['geometry'], row['style'],
row['shading_type'])
else:
for index, row in registry.iterrows():
LOGGER.debug("Plotting %s", row['line_type'])
plot_coords(ax, row['geometry'])
plot_bounds(ax, row['geometry'])
plot_line(ax, row['geometry'], row['style'], row['color'])

ax.axis('equal')
ax.set_xlabel("x [m]", fontsize=fontsize)
ax.set_ylabel("y [m]", fontsize=fontsize)


def plot_pvarray(ax, pvarray, line_types_selected=None, fontsize=20):
"""
Plot a 2D PV array from a :class:`pvarray.Array` using its
:attr:`pvarray.Array.surface_registry`.
:param ax: :class:`matplotlib.axes.Axes` object to use for the plot
:param pvarray: object containing the surface registry as attribute
:type pvarray: :class:`pvarray.Array`
:param list line_types_selected: parameter used to select a subset of
'line_type' to plot; e.g. 'pvrow' or 'ground'
:return: None (``ax`` is updated)
"""

# FIXME: repeating code from plot_line_registry
surface_registry = pvarray.surface_registry.copy()
plot_array_from_registry(ax, surface_registry,
line_types_selected=line_types_selected)

# Plot details
distance = pvarray.pvrow_distance
height = pvarray.pvrow_height
n_pvrows = pvarray.n_pvrows
ax.set_xlim(- 0.5 * distance, (n_pvrows - 0.5) * distance)
ax.set_ylim(-height, 2 * height)
ax.set_title("PV Array", fontsize=fontsize)


# Base functions used to plot the 2D array
def plot_coords(ax, ob):
try:
x, y = ob.xy
ax.plot(x, y, 'o', color='#999999', zorder=1)
except NotImplementedError:
for line in ob:
x, y = line.xy
ax.plot(x, y, 'o', color='#999999', zorder=1)


def plot_bounds(ax, ob):
# Check if shadow reduces to one point (for very specific sun alignment)
if len(ob.boundary) == 0:
x, y = ob.coords[0]
else:
x, y = zip(*list((p.x, p.y) for p in ob.boundary))
ax.plot(x, y, 'o', color='#000000', zorder=1)


def plot_line(ax, ob, line_style, line_color):
try:
x, y = ob.xy
ax.plot(x, y, color=COLOR_dic[line_color], ls=line_style, alpha=0.7,
linewidth=3, solid_capstyle='round', zorder=2)
except NotImplementedError:
for line in ob:
x, y = line.xy
ax.plot(x, y, color=COLOR_dic[line_color], ls=line_style,
alpha=0.7, linewidth=3, solid_capstyle='round', zorder=2)
33 changes: 0 additions & 33 deletions pvfactors/pvarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,39 +511,6 @@ def update_reflectivity_matrix(self):
self.inv_reflectivity_matrix = np.diag(
list(1. / self.surface_registry.reflectivity.values) + [1])

def calculate_radiosities_simple(self, solar_zenith, solar_azimuth,
array_tilt, array_azimuth, dni, dhi):
"""
Solve linear system of equations to calculate radiosity terms based on
the specified inputs and assuming that the whole sky dome is isotropic
(no diffuse sky dome decomposition)
:param float solar_zenith: zenith angle of the sun [degrees]
:param float solar_azimuth: azimuth angle of the sun [degrees]
:param float array_tilt: tilt angle of the whole array. All PV rows must
have the same tilt angle [degrees]
:param float array_azimuth: azimuth angle of the whole array. All PV
rows must have the same azimuth angle [degrees]
:param float dni: direct normal irradiance [W/m2]
:param float dhi: diffuse horizontal irradiance [W/m2]
:return: None; updating :attr:`surface_registry`
"""
# Update the array configuration
self.update_view_factors(solar_zenith, solar_azimuth, array_tilt,
array_azimuth)
self.update_irradiance_terms_simple(solar_zenith, solar_azimuth,
array_tilt, array_azimuth, dni, dhi)
self.update_reflectivity_matrix()

# Do calculation
a_mat = self.inv_reflectivity_matrix - self.vf_matrix
q0 = linalg.solve(a_mat, self.irradiance_terms)
qinc = np.dot(self.vf_matrix, q0) + self.irradiance_terms
# Assign to surfaces
self.surface_registry.loc[:, 'q0'] = q0[:-1]
self.surface_registry.loc[:, 'qinc'] = qinc[:-1]
self.calculate_sky_and_reflection_components()

def calculate_radiosities_perez(
self, solar_zenith, solar_azimuth, array_tilt, array_azimuth,
dni, luminance_isotropic, luminance_circumsolar, poa_horizon,
Expand Down
8 changes: 8 additions & 0 deletions pvfactors/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,11 @@ def df_segments():
df_segments = pd.read_csv(fp, header=[0, 1], index_col=0)
df_segments.index = pd.to_datetime(df_segments.index)
yield df_segments


@pytest.fixture(scope='function')
def df_perez_luminance():
""" Example of df_segments to be used for tests """
fp = os.path.join(DIR_TEST_DATA, 'file_test_df_perez_luminance.csv')
df_perez_luminance = pd.read_csv(fp, header=[0], index_col=0)
yield df_perez_luminance
2 changes: 1 addition & 1 deletion pvfactors/tests/test_array_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_plotting():
is_ci = os.environ.get('CI', False)
if not is_ci:
import matplotlib.pyplot as plt
from pvfactors.tools import plot_pvarray
from pvfactors.plot import plot_pvarray
# Create array where sun vector is in the direction of the modules
arguments = {
'n_pvrows': 3,
Expand Down
14 changes: 7 additions & 7 deletions pvfactors/tests/test_circumsolar_shading.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"""

from pvfactors.pvcore import calculate_circumsolar_shading
from pvfactors.pvarray import Array
from pvfactors.tools import calculate_radiosities_serially_perez
from pvfactors.timeseries import (calculate_radiosities_serially_perez,
breakup_df_inputs)
import pandas as pd
import os
import numpy as np
Expand Down Expand Up @@ -61,17 +61,17 @@ def test_serial_circumsolar_shading_calculation():
'calculate_front_circ_horizon_shading': True,
'circumsolar_model': 'gaussian'
}
save = (1, 'front')
# Load inputs for the serial calculation
test_file = os.path.join(
TEST_DATA, 'file_test_serial_circumsolar_shading_calculation.csv')
df_inputs = pd.read_csv(test_file, index_col=0)
df_inputs.index = pd.DatetimeIndex(df_inputs.index)

# Create shapely PV array
array = Array(**arguments)
(timestamps, array_tilt, array_azimuth,
solar_zenith, solar_azimuth, dni, dhi) = breakup_df_inputs(df_inputs)

# Run the calculation for functional testing
df_registries, df_inputs_perez = (
calculate_radiosities_serially_perez((arguments, df_inputs, save))
calculate_radiosities_serially_perez((arguments, timestamps, array_tilt,
array_azimuth, solar_zenith,
solar_azimuth, dni, dhi))
)
10 changes: 8 additions & 2 deletions pvfactors/tests/test_discretization.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,21 @@ def test_consistent_qinc():
# Run a calculation for the given configuration
dni = 1e3
dhi = 1e2
luminance_isotropic = dhi
luminance_circumsolar = 0.
poa_horizon = 0.
poa_circumsolar = 0.

solar_zenith = 20.
solar_azimuth = 180.

array_tilt = 20.
array_azimuth = 180.

array.calculate_radiosities_simple(solar_zenith, solar_azimuth, array_tilt,
array_azimuth, dni, dhi)
array.calculate_radiosities_perez(solar_zenith, solar_azimuth, array_tilt,
array_azimuth, dni, luminance_isotropic,
luminance_circumsolar, poa_horizon,
poa_circumsolar)

# Compare to expected values
expected_qinc = np.array([
Expand Down
5 changes: 5 additions & 0 deletions pvfactors/tests/test_files/file_test_df_perez_luminance.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
datetime,solar_zenith,solar_azimuth,array_tilt,array_azimuth,dni,dhi,poa_isotropic,poa_circumsolar,poa_horizon,vf_horizon,vf_circumsolar,vf_isotropic,luminance_horizon,luminance_circumsolar,luminance_isotropic,poa_total_diffuse
2015-10-02 12:40:00.001200,42.54210405,174.3715553,5.142972856,90,1000,100,53.9049589579,46.1721986001,1.8417661189,0.0896413203484,1.00404216032,0.99798705648,20.5459503691,45.986314544,54.013685456,101.918923677
2015-10-02 12:49:59.998800,42.41609807,178.0613184,1.77035709,90,1000,100,53.9393074992,46.0698063615,0.633882293345,0.0308936438071,1.00047755049,0.999761338734,20.5182107136,46.0478162043,53.9521837957,100.642996154
2015-10-02 13:00:00.000000,42.41574779,181.7602662,1.607556458,270,1000,100,53.9413957759,46.0661176346,0.575604882308,0.0280534721322,1.00039373104,0.99980321195,20.518133356,46.0479871126,53.9520128874,100.583118293
2015-10-02 13:10:00.001200,42.54106148,185.4501296,4.98118732,270,1000,100,53.9111795968,46.161162222,1.78395715039,0.0868286442933,1.00379105643,0.998111630694,20.545721575,45.9868235788,54.0131764212,101.856298969
2 changes: 1 addition & 1 deletion pvfactors/tests/test_files/file_test_serial_perez.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
timestamps,pvrow,side,term,value
timestamps,pvrow_index,surface_side,term,value
4/15/2017 10:26,0,back,circumsolar_term,0
4/15/2017 10:27,0,back,circumsolar_term,0
4/15/2017 10:26,0,front,circumsolar_term,51.02503788
Expand Down
12 changes: 8 additions & 4 deletions pvfactors/tests/test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
Testing the multiprocessing calculation from the ``tools`` module
"""

from pvfactors.tools import calculate_radiosities_parallel_perez
from pvfactors.timeseries import (calculate_radiosities_parallel_perez,
breakup_df_inputs)
import os
import pandas as pd

Expand Down Expand Up @@ -43,7 +44,10 @@ def test_calculate_radiosities_parallel_perez():
df_inputs_simulation = df_inputs_simulation.iloc[:subset_idx, :]
# Select number of processes
n_processes = None
# Break up inputs
(timestamps, array_tilt, array_azimuth,
solar_zenith, solar_azimuth, dni, dhi) = breakup_df_inputs(df_inputs_simulation)
# Run calculation
results = calculate_radiosities_parallel_perez(arguments,
df_inputs_simulation,
n_processes=n_processes)
results = calculate_radiosities_parallel_perez(
arguments, timestamps, array_tilt, array_azimuth,
solar_zenith, solar_azimuth, dni, dhi, n_processes=n_processes)
21 changes: 13 additions & 8 deletions pvfactors/tests/test_serial_perez_calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
consistent results
"""

from pvfactors.tools import (calculate_radiosities_serially_perez,
get_average_pvrow_outputs)
from pvfactors.timeseries import (calculate_radiosities_serially_perez,
get_average_pvrow_outputs,
breakup_df_inputs)
import pandas as pd
import numpy as np
import os
Expand Down Expand Up @@ -43,13 +44,18 @@ def test_serial_calculation():
idx_subset = 10
df_inputs_simulation = df_inputs_simulation.iloc[0:idx_subset, :]

# Break up inputs
(timestamps, array_tilt, array_azimuth,
solar_zenith, solar_azimuth, dni, dhi) = breakup_df_inputs(df_inputs_simulation)

# Run calculation in 1 process only
(df_registries, _) = (
calculate_radiosities_serially_perez((arguments, df_inputs_simulation))
)
df_registries, _ = calculate_radiosities_serially_perez(
(arguments, timestamps,
solar_zenith, solar_azimuth,
array_tilt, array_azimuth, dni, dhi))

# Format df_registries to get outputs
df_outputs = get_average_pvrow_outputs(df_registries)
df_outputs = get_average_pvrow_outputs(df_registries, include_shading=False)

# Did the outputs remain consistent?
test_results = values_are_consistent(df_outputs)
Expand All @@ -74,7 +80,6 @@ def values_are_consistent(df_outputs):
parse_dates=['timestamps'])
expected_df_outputs['origin'] = 'expected'
df_outputs = (df_outputs.assign(timestamps=lambda x: x.index)
.drop('shaded', axis=1)
.reset_index(drop=True)
.melt(id_vars=['timestamps']))
df_outputs['origin'] = 'calculated'
Expand All @@ -89,7 +94,7 @@ def values_are_consistent(df_outputs):
rtol = 1e-7
test_results = []
for name, group in grouped_comparison:
df_term = group.pivot_table(index=['timestamps', 'pvrow', 'side'],
df_term = group.pivot_table(index=['timestamps', 'pvrow_index', 'surface_side'],
columns=['origin'], values='value')
compare = (('calculated' in df_term.columns)
& ('expected' in df_term.columns))
Expand Down
Loading

0 comments on commit 33830cb

Please sign in to comment.