Example #1
0
def test_calculate_back_horizon_shading():

    arguments = {
        'surface_azimuth': 0.0,
        'surface_tilt': 30.0,
        'gcr': 0.5,
        'n_pvrows': 2,
        'pvrow_height': 1.5,
        'pvrow_width': 1.,
        'rho_ground': 0.2,
        'rho_pvrow_back': 0.03,
        'rho_pvrow_front': 0.01,
        'solar_azimuth': 90.0,
        'solar_zenith': 30.0,
        'circumsolar_angle': 50.,
        'horizon_band_angle': 6.5,
        'calculate_front_circ_horizon_shading': False,
        'circumsolar_model': 'gaussian'
    }

    # Create shapely PV array
    array = Array(**arguments)

    # Test the horizon band shading part
    solar_zenith = 45.
    solar_azimuth = 90.
    surface_tilt = 30.
    surface_azimuth = 0.
    dni = 0.
    luminance_isotropic = 0.
    luminance_circumsolar = 0.
    poa_horizon = 1.
    poa_circumsolar = 0.

    array.update_irradiance_terms_perez(solar_zenith, solar_azimuth,
                                        surface_tilt, surface_azimuth, dni,
                                        luminance_isotropic,
                                        luminance_circumsolar, poa_horizon,
                                        poa_circumsolar)

    expected_horizon_shading = np.array([90.25751984, 0.0974248])
    calculated_horizon_shading = array.surface_registry.query(
        'pvrow_index==0 and surface_side=="back"')[[
            'horizon_band_shading_pct', 'horizon_term'
        ]].values[0]
    calculated_no_horizon_shading = array.surface_registry.query(
        'pvrow_index==1 and surface_side=="back"')[[
            'horizon_band_shading_pct', 'horizon_term'
        ]].values[0]

    TOL = 1e-7
    np.testing.assert_allclose(expected_horizon_shading,
                               calculated_horizon_shading,
                               atol=0,
                               rtol=TOL)
    np.testing.assert_allclose(np.array([0., 1.]),
                               calculated_no_horizon_shading,
                               atol=0,
                               rtol=TOL)
def test_plotting():
    """
    Check that the plotting functions are functional (only on local machine)
    """

    is_ci = os.environ.get('CI', False)
    if not is_ci:
        import matplotlib.pyplot as plt
        from pvfactors.plot import plot_pvarray
        # Create array where sun vector is in the direction of the modules
        arguments = {
            'n_pvrows': 3,
            'pvrow_height': 1.5,
            'solar_zenith': 30,
            'solar_azimuth': 0.,
            'array_azimuth': 180.,
            'pvrow_width': 1.,
            'gcr': 0.3,
            'array_tilt': 0.
        }
        array = Array(**arguments)
        f, ax = plt.subplots(figsize=(10, 5))
        _ = plot_pvarray(ax, array)

        # Test with interrow forward shading
        arguments = {
            'n_pvrows': 5,
            'pvrow_height': 3.0,
            'solar_zenith': 30,
            'solar_azimuth': 180.,
            'array_azimuth': 180.,
            'pvrow_width': 3.0,
            'gcr': 0.9,
            'array_tilt': 20.
        }
        array = Array(**arguments)
        f, ax = plt.subplots()
        _ = plot_pvarray(ax, array)

        # Test with interrow backward shading
        arguments = {
            'n_pvrows': 5,
            'pvrow_height': 3.0,
            'solar_zenith': 60,
            'solar_azimuth': 0.,
            'array_azimuth': 180.,
            'pvrow_width': 3.0,
            'gcr': 0.9,
            'array_tilt': -20.
        }
        array = Array(**arguments)
        f, ax = plt.subplots()
        _ = plot_pvarray(ax, array)

    else:
        print("Not running 'test_plotting' in CI")
def test_consistent_qinc():
    """
    Test that the values of the calculated incident irradiance on all the
    surfaces (discretized or not) stays consistent
    """
    arguments = {
        'n_pvrows': 5,
        'pvrow_height': 1.,
        'solar_zenith': 70,
        'solar_azimuth': 180.,
        'surface_azimuth': 180.,
        'pvrow_width': 1.5,
        'gcr': 0.6,
        'surface_tilt': 30.,
        'cut': [(0, 5, 'front'), (4, 2, 'front')]
    }
    array = Array(**arguments)

    # 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.

    tracker_theta = 20.
    surface_azimuth = 180.

    array.calculate_radiosities_perez(solar_zenith, solar_azimuth,
                                      tracker_theta, surface_azimuth, dni,
                                      luminance_isotropic,
                                      luminance_circumsolar, poa_horizon,
                                      poa_circumsolar)

    # Compare to expected values
    expected_qinc = np.array([
        1103.10852238, 1097.25580244, 1096.27281294, 1095.61848916,
        1093.44645666, 47.62848148, 37.3519236, 36.41100695, 36.49146269,
        45.94191771, 993.12051239, 991.41490791, 991.3692459, 991.83044071,
        1039.63791536, 1039.23551228, 1026.62401361, 49.74148561, 44.25032849,
        43.80024727, 44.00294823, 76.10124014, 69.32324555, 69.60603804,
        71.31511657, 93.12702949, 1103.09038367, 1103.07239864, 1103.05456561,
        1103.03688292, 1097.08812485
    ])

    tol = 1e-8
    np.testing.assert_allclose(array.surface_registry.qinc,
                               expected_qinc,
                               atol=0,
                               rtol=tol,
                               equal_nan=True)
def test_create_array():
    """
    Check that the pvrows know what's the index of their neighbors.
    """
    # PV array parameters
    arguments = {
        'n_pvrows': 3,
        'pvrow_height': 1.5,
        'pvrow_width': 1.,
        'gcr': 0.3,
        'array_tilt': 20.
    }
    # Create vf array
    array = Array(**arguments)

    # Run some sanity checks on the creation of the vf array
    assert len(array.pvrows) == 3
    assert isinstance(array.pvrows[0], PVRowLine)
    assert isinstance(array.pvrows[0].lines[0], LinePVArray)
    assert array.line_registry.shape[0] == 13

    # Check that the expected neighbors are correct
    tol = 1e-8
    expected_pvrow_neighbors = np.array([
        np.nan, 0., 1., np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan,
        np.nan, np.nan, np.nan, 1., 2., np.nan
    ])
    calculated_pvrow_neighbors = (
        array.surface_registry.index_pvrow_neighbor.values)
    assert np.allclose(calculated_pvrow_neighbors,
                       expected_pvrow_neighbors,
                       atol=tol,
                       rtol=0,
                       equal_nan=True)
def test_discretized_surfaces():
    """
    Functional test to check that the discretization is working
    """
    # Create vf pv array with discretization request
    arguments = {
        'n_pvrows': 3,
        'pvrow_height': 1.5,
        'pvrow_width': 1.,
        'gcr': 0.4,
        'array_tilt': 20.,
        'cut': [(0, 5, 'front'), (1, 3, 'front')]
    }
    array = Array(**arguments)

    # Check that the number of discretized surfaces is correct
    front_pvrow_0_has_5_surfaces = (
        array.surface_registry.loc[
            (array.surface_registry.pvrow_index == 0)
            & (array.surface_registry.surface_side == 'front'),
            :].shape[0] == 5
    )
    front_pvrow_1_has_3_surfaces = (
        array.surface_registry.loc[
            (array.surface_registry.pvrow_index == 1)
            & (array.surface_registry.surface_side == 'front'),
            :].shape[0] == 3
    )
    assert front_pvrow_0_has_5_surfaces & front_pvrow_1_has_3_surfaces
def test_interrow_shading():
    """
    Testing the ability of the model to find direct shading between pvrows
    """
    # Forward direct shading of the pvrows
    arguments = {
        'n_pvrows': 5,
        'pvrow_height': 3.,
        'solar_zenith': 30,
        'solar_azimuth': 180.,
        'array_azimuth': 180.,
        'pvrow_width': 3.,
        'gcr': 0.9,
        'array_tilt': 20.
    }
    array = Array(**arguments)
    # There should be 4 pvrows with direct shading
    assert (array.line_registry.loc[
        (array.line_registry.line_type == 'pvrow')
        & array.line_registry.shaded]
        .shape[0] == 4)

    # Backward direct shading of the pvrows (sun in the back of the modules)
    arguments = {
        'n_pvrows': 5,
        'pvrow_height': 3.0,
        'solar_zenith': 60,
        'solar_azimuth': 0.,
        'array_azimuth': 180.,
        'pvrow_width': 3.0,
        'gcr': 0.9,
        'array_tilt': -20.
    }
    array = Array(**arguments)
    # There should still be 4 pvrows with direct shading
    assert (array.line_registry.loc[
        (array.line_registry.line_type == 'pvrow')
        & array.line_registry.shaded]
        .shape[0] == 4)

    print("Done.")
def test_view_factor_matrix():
    """
    Check that the calculation of view factors remains consistent
    """
    # PV array parameters
    arguments = {
        'n_pvrows': 2,
        'pvrow_height': 1.5,
        'solar_zenith': 30,
        'solar_azimuth': 180.,
        'surface_azimuth': 180.,
        'pvrow_width': 1.0,
        'gcr': 0.4,
        'surface_tilt': 30.
    }
    array = Array(**arguments)

    # Expected values
    expected_vf_matrix = np.array([
        [0., 0., 0., 0., 0., 0.06328772, 0., 0., 0., 0., 0., 0.93671228],
        [0., 0., 0., 0., 0., 0.0342757, 0., 0.01414893, 0., 0.05586108,
            0., 0.89571429],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.23662653,
            0.04159081, 0.72178267],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.04671626,
            0.23662653, 0.71665722],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.10675226,
            0.21878649, 0.67446125],
        [0.00064976, 0.0003519, 0., 0., 0., 0., 0., 0., 0., 0.,
            0., 0.99899834],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.00084338,
            0.0032498, 0.99590682],
        [0., 0.00565957, 0., 0., 0., 0., 0., 0., 0., 0.09305653,
            0., 0.9012839],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.28720098,
            0.00351237, 0.70928665],
        [0., 0.05586108, 0.27323278, 0.05394329, 0.14361376,
            0., 0.08101242, 0.23264133, 0.11107537, 0.,
            0., 0.04861999],
        [0., 0., 0.04802493, 0.27323278, 0.29433335,
            0., 0.31216481, 0., 0.00135841, 0.,
            0., 0.07088572],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])

    # Perform comparison using very small absolute tolerance
    tol = 1e-8
    assert np.allclose(array.vf_matrix, expected_vf_matrix, atol=tol, rtol=0)
Example #8
0
def test_calculate_radiosities_serially_simple():
    """
    Check that the results of the radiosity calculation using the isotropic
    diffuse sky stay consistent
    """
    # Simple sky and array configuration
    df_inputs = pd.DataFrame(
        np.array(
            [[80., 0., 70., 180., 1e3, 1e2],
             [20., 180., 40., 180., 1e3, 1e2],
             [70.4407256, 248.08690811, 42.4337927, 270., 1000., 100.]]),
        columns=['solar_zenith', 'solar_azimuth', 'array_tilt', 'array_azimuth',
                 'dni', 'dhi'],
        index=[0, 1, 2]
    )
    arguments = {
        'n_pvrows': 3,
        'pvrow_height': 1.5,
        'pvrow_width': 1.,
        'gcr': 0.3,
    }
    array = Array(**arguments)

    # Calculate irradiance terms
    df_outputs, df_bifacial_gains = (
        calculate_radiosities_serially_simple(array, df_inputs))

    # Check that the outputs are as expected
    expected_outputs_array = np.array([
        [31.601748050014145, 6.289069752504206, 3.5833558115691035],
        [632.0349610002829, 125.78139505008411, 71.66711623138208],
        [2.2784386524603493, 31.554019855401, 28.05923970649779],
        [75.94795508201167, 1051.8006618467002, 935.3079902165931],
        [31.87339865348199, 6.377687102750911, 1.814318872353118],
        [637.4679730696398, 127.55374205501823, 36.286377447062364],
        [2.2047681015326277, 31.218033061227334, 27.857908527655677],
        [73.4922700510876, 1040.6011020409114, 928.596950921856],
        [46.79602079759552, 7.215187943800262, 2.1664217462458804],
        [935.9204159519105, 144.30375887600525, 43.328434924917616],
        [2.2998617834782267, 31.167227926438414, 27.776289194444438],
        [76.66205944927422, 1038.9075975479473, 925.8763064814813],
        [True, False, False]], dtype=object)
    tol = 1e-8
    assert np.allclose(expected_outputs_array[:-1, :].astype(float),
                       df_outputs.values[:-1, :].astype(float),
                       atol=tol, rtol=0, equal_nan=True)
Example #9
0
def test_serial_circumsolar_shading_calculation():
    """
    Calculate and save results from front surface circumsolar shading on
    pvrows. Test that it functions with the given data.
    """

    # Choose a PV array configuration and pass the arguments necessary for
    # the calculation to be triggered:
    # eg 'calculate_front_circ_horizon_shading'
    arguments = {
        'array_azimuth': 90.0,
        'array_tilt': 20.0,
        'cut': [(1, 5, 'front')],
        'gcr': 0.3,
        'n_pvrows': 2,
        'pvrow_height': 1.5,
        'pvrow_width': 1.,
        'rho_ground': 0.2,
        'rho_pvrow_back': 0.03,
        'rho_pvrow_front': 0.01,
        'solar_azimuth': 90.0,
        'solar_zenith': 30.0,
        'circumsolar_angle': 50.,
        'horizon_band_angle': 6.5,
        '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)

    # Run the calculation for functional testing
    df_outputs, df_bifacial, df_inputs_perez, df_outputs_segments = (
        calculate_radiosities_serially_perez((arguments, df_inputs, save))
    )
def test_view_matrix():
    """
    Test that the view matrix provides the expected views between surfaces
    """
    # PV array parameters
    arguments = {
        'n_pvrows': 2,
        'pvrow_height': 1.5,
        'solar_zenith': 30,
        'solar_azimuth': 180.,
        'surface_azimuth': 180.,
        'pvrow_width': 1.,
        'gcr': 0.3,
        'surface_tilt': 20.
    }
    array = Array(**arguments)

    # Expected values of the view matrix
    expected_view_matrix = np.array([
        [0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 4],
        [0, 0, 0, 0, 0, 8, 0, 8, 0, 10, 0, 4],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 1],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 1],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 1],
        [9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 1],
        [0, 9, 0, 0, 0, 0, 0, 0, 0, 7, 0, 1],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 1],
        [0, 10, 6, 6, 6, 0, 6, 6, 6, 0, 0, 5],
        [0, 0, 6, 6, 6, 0, 6, 0, 6, 0, 0, 5],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

    # Compare with expectations: make sure to remove the sky from the views
    n_shape = expected_view_matrix.shape[0]
    np.testing.assert_array_equal(array.view_matrix, expected_view_matrix)
    finite_surfaces_view_matrix = array.view_matrix[n_shape - 1:, n_shape - 1]
    # Make sure that the matrix is symmetric:
    # "if I can see you, you can see me"
    assert is_symmetric(finite_surfaces_view_matrix)
def test_merge_shadows():
    """
    When direct shading happens between pvrows, the shadow of the rows on the
    ground is supposed to form a continuous shadow (because of the overlap).
    Test that this functionally works
    """
    # Use specific vf array configuration leading to direct shading
    arguments = {
        'n_pvrows': 5,
        'pvrow_height': 2.,
        'solar_zenith': 30,
        'solar_azimuth': 0.,
        'array_azimuth': 180.,
        'pvrow_width': 3,
        'gcr': 0.9,
        'array_tilt': -20.
    }
    array = Array(**arguments)
    # There should be 1 continuous shadow on the groud, but 4 distinct ground
    #  shaded areas, delimited by what the front and the back of the pvrows
    # can see
    assert (
        array.line_registry.loc[(array.line_registry.line_type == 'ground')
                                & array.line_registry.shaded].shape[0] == 4)
def test_irradiance_terms_perez_but_isotropic():
    """
    Check that the irradiance terms calculated for this configuration and
    using the isotropic diffuse sky dome is working as expected
    There is some direct shading on the back surface in this configuration
    """
    # Simple sky and array configuration
    dni = 1e3
    dhi = 1e2
    solar_zenith = 80.
    solar_azimuth = 0.
    surface_tilt = 70.
    surface_azimuth = 180.
    arguments = {
        'n_pvrows': 3,
        'pvrow_height': 1.5,
        'pvrow_width': 1.,
        'gcr': 0.4,
    }
    # Create vf array
    array = Array(**arguments)
    # Calculate irradiance terms
    array.update_view_factors(solar_zenith, solar_azimuth, surface_tilt,
                              surface_azimuth)
    array.update_irradiance_terms_perez(solar_zenith, solar_azimuth,
                                        surface_tilt, surface_azimuth, dni,
                                        dhi, 0., 0., 0.)

    # Check that the values are as expected
    expected_irradiance_terms = np.array([
        0., 0., 0., 0., 173.64817767, 173.64817767, 173.64817767, 173.64817767,
        173.64817767, 0., 0., 866.02540378, 866.02540378, 866.02540378, 100.
    ])
    tol = 1e-8
    np.testing.assert_allclose(array.irradiance_terms,
                               expected_irradiance_terms,
                               atol=tol,
                               rtol=0)
Example #13
0
def array_timeseries_calculate(pvarray_parameters, timestamps, solar_zenith,
                               solar_azimuth, array_tilt, array_azimuth, dni,
                               luminance_isotropic, luminance_circumsolar,
                               poa_horizon, poa_circumsolar):
    """
    Calculate the view factor radiosity and irradiance terms for multiple
    times.
    The function inputs assume a diffuse sky dome as represented in the Perez
    diffuse sky transposition model (from ``pvlib-python`` implementation).

    :param dict pvarray_parameters: parameters used to instantiate
        ``pvarray.Array`` class
    :param array-like timestamps: simulation timestamps
    :param array-like solar_zenith: solar zenith angles
    :param array-like solar_azimuth: solar azimuth angles
    :param array-like array_tilt: pv module tilt angles
    :param array-like array_azimuth: pv array azimuth angles
    :param array-like dni: values for direct normal irradiance
    :param array-like luminance_isotropic: luminance of the isotropic sky dome
    :param array-like luminance_circumsolar: luminance of circumsolar area
    :param array-like poa_horizon: POA irradiance horizon component
    :param array-like poa_circumsolar: POA irradiance circumsolar component

    :return: ``df_registries``.
        Concatenated form of the ``pvarray.Array`` surface registries.
    :rtype: :class:`pandas.DataFrame`
    """
    # Instantiate array
    array = Array(**pvarray_parameters)
    # We want to save the whole registry for each timestamp
    list_registries = []
    # We want to record the skipped_ts
    skipped_ts = []
    # Use for printing progress
    n = len(timestamps)
    i = 1
    for idx, ts in enumerate(timestamps):
        try:
            if ((isinstance(solar_zenith[idx], float))
                    & (solar_zenith[idx] <= 90.)):
                # Run calculation only if daytime
                array.calculate_radiosities_perez(
                    solar_zenith[idx], solar_azimuth[idx], array_tilt[idx],
                    array_azimuth[idx], dni[idx], luminance_isotropic[idx],
                    luminance_circumsolar[idx], poa_horizon[idx],
                    poa_circumsolar[idx])

                # Save the whole registry
                registry = deepcopy(array.surface_registry)
                registry['timestamps'] = ts
                list_registries.append(registry)

            else:
                skipped_ts.append(ts)

        except Exception as err:
            LOGGER.debug("Unexpected error: {0}".format(err))
            skipped_ts.append(ts)

        # Printing progress
        print_progress(i,
                       n,
                       prefix='Progress:',
                       suffix='Complete',
                       bar_length=50)
        i += 1

    # Concatenate all surface registries into one dataframe
    if list_registries:
        if skipped_ts:
            df_skipped = pd.DataFrame(
                np.nan,
                columns=Array.registry_cols,
                index=range(len(skipped_ts))).assign(timestamps=skipped_ts)
            list_registries.append(df_skipped)
        df_registries = (pd.concat(
            list_registries, axis=0, join='outer',
            sort=False).sort_values(by=['timestamps']).reset_index(drop=True))
    else:
        df_registries = pd.DataFrame(
            np.nan, columns=Array.registry_cols,
            index=range(len(timestamps))).assign(timestamps=timestamps)

    return df_registries
Example #14
0
def test_array_calculate_timeseries():
    """
    Check that the timeseries results of the radiosity calculation using the
    isotropic diffuse sky approach stay consistent
    """
    # Simple sky and array configuration
    df_inputs = pd.DataFrame(
        np.array([[80., 0., 70., 180., 1e3, 1e2],
                  [20., 180., 40., 180., 1e3, 1e2],
                  [70.4407256, 248.08690811, 42.4337927, 270., 1000., 100.]]),
        columns=[
            'solar_zenith', 'solar_azimuth', 'array_tilt', 'array_azimuth',
            'dni', 'dhi'
        ],
        index=[0, 1, 2])
    arguments = {
        'n_pvrows': 3,
        'pvrow_height': 1.5,
        'pvrow_width': 1.,
        'gcr': 0.3,
    }

    array = Array(**arguments)

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

    # Fill in the missing pieces
    luminance_isotropic = dhi
    luminance_circumsolar = np.zeros(len(timestamps))
    poa_horizon = np.zeros(len(timestamps))
    poa_circumsolar = np.zeros(len(timestamps))

    # Run timeseries calculation
    df_registries = array_timeseries_calculate(arguments, timestamps,
                                               solar_zenith, solar_azimuth,
                                               array_tilt, array_azimuth, dni,
                                               luminance_isotropic,
                                               luminance_circumsolar,
                                               poa_horizon, poa_circumsolar)

    # Calculate surface averages for pvrows
    df_outputs = get_average_pvrow_outputs(df_registries,
                                           values=['q0', 'qinc'],
                                           include_shading=False)

    # Check that the outputs are as expected
    expected_outputs_array = np.array(
        [[31.60177482, 6.28906975, 3.58335581],
         [632.03549634, 125.78139505, 71.66711623],
         [2.27843869, 31.55401986, 28.05923971],
         [75.94795617, 1051.80066185, 935.30799022],
         [31.87339866, 6.3776871, 1.81431887],
         [637.46797317, 127.55374206, 36.28637745],
         [2.20476856, 31.21803306, 27.85790853],
         [73.49228524, 1040.60110204, 928.59695092],
         [46.7960208, 7.21518794, 2.16642175],
         [935.92041595, 144.30375888, 43.32843492],
         [2.29986192, 31.16722793, 27.77628919],
         [76.66206408, 1038.90759755, 925.87630648], [True, False, False]],
        dtype=object)
    tol = 1e-8
    assert np.allclose(expected_outputs_array[:-1, :].astype(float),
                       df_outputs.values.T,
                       atol=tol,
                       rtol=0,
                       equal_nan=True)
Example #15
0
def calculate_radiosities_serially_perez(args):
    """
    Calculate the view factor radiosity and irradiance terms for multiple times.
    The calculations will be sequential, and they will assume a diffuse sky
    dome as calculated in the Perez diffuse sky transposition model (from
    ``pvlib-python`` implementation).

    :param args: tuple of at least two arguments: ``(arguments, df_inputs)``,
        where ``arguments`` is a ``dict`` that contains the array parameters
        used to instantiate a :class:`pvarray.Array` object, and ``df_inputs``
        is a :class:`pandas.DataFrame` with the following columns:
        ['solar_zenith', 'solar_azimuth', 'array_tilt', 'array_azimuth', 'dhi',
        'dni'], and with the following units: ['deg', 'deg', 'deg', 'deg',
        'W/m2', 'W/m2']. A possible 3rd argument for the tuple is
        ``save_segments``, which is a ``tuple`` of two elements used to save
        all the irradiance terms calculated for one side of a PV row; e.g.
        ``(1, 'front')`` the first element is an ``int`` for the PV row index,
        and the second element a ``str`` to specify the side of the PV row,
        'front' or 'back'
    :return: ``df_outputs, df_bifacial_gain, df_inputs_perez, ipoa_dict``;
        :class:`pandas.DataFrame` objects and ``dict`` where ``df_outputs``
        contains *averaged* irradiance terms for all PV row sides and at each
        time stamp; ``df_bifacial_gain`` contains the calculation of
        back-surface over front-surface irradiance for all PV rows and at each
        time stamp; ``df_inputs_perez`` contains the intermediate input and
        output values from the Perez model calculation in ``pvlib-python``;
        ``ipoa_dict`` is not ``None`` only when the ``save_segments`` input is
        specified, and it is otherwise a ``dict`` where the keys are all the
        calculated irradiance terms' names, and the values are
        :class:`pandas.DataFrame` objects containing the calculated values for
        all the segments of the PV string side (it is a way of getting detailed
        values instead of averages)
    """

    if len(args) == 3:
        arguments, df_inputs, save_segments = args
    else:
        arguments, df_inputs = args
        save_segments = None

    array = Array(**arguments)
    # Pre-process df_inputs to use the expected format of pvlib's perez model:
    # only positive tilt angles, and switching azimuth angles
    df_inputs_before_perez = df_inputs.copy()
    df_inputs_before_perez.loc[df_inputs.array_tilt < 0, 'array_azimuth'] = (
        np.remainder(
            df_inputs_before_perez.loc[df_inputs.array_tilt < 0,
                                       'array_azimuth'] + 180.,
            360.)
    )
    df_inputs_before_perez.array_tilt = np.abs(df_inputs_before_perez
                                               .array_tilt)

    # Calculate the perez inputs
    df_inputs_perez = perez_diffuse_luminance(df_inputs_before_perez)

    # Post process: in vf model tilt angles can be negative and azimuth is
    # generally fixed
    df_inputs_perez.loc[:, ['array_azimuth', 'array_tilt']] = (
        df_inputs.loc[:, ['array_azimuth', 'array_tilt']]
    )

    # Create index df_outputs
    cols = ['q0', 'qinc', 'circumsolar_term', 'horizon_term',
            'direct_term', 'irradiance_term', 'isotropic_term',
            'reflection_term']
    iterables = [
        range(array.n_pvrows),
        ['front', 'back'],
        cols
    ]
    multiindex = pd.MultiIndex.from_product(iterables,
                                            names=['pvrow', 'side', 'term'])

    # Initialize df_outputs
    df_outputs = pd.DataFrame(np.nan, columns=df_inputs_perez.index,
                              index=multiindex)
    df_outputs.sort_index(inplace=True)
    df_outputs.loc['array_is_shaded', :] = np.nan

    # Initialize df_outputs_segments
    if save_segments is not None:
        n_cols = len(array.pvrows[save_segments[0]].cut_points)
        cols_segments = range(n_cols + 1)
        irradiance_terms_segments = [
            'qinc', 'direct_term', 'circumsolar_term', 'horizon_term',
            'isotropic_term', 'reflection_term', 'circumsolar_shading_pct',
            'horizon_band_shading_pct'
        ]
        iterables_segments = [
            irradiance_terms_segments,
            cols_segments
        ]
        multiindex_segments = pd.MultiIndex.from_product(
            iterables_segments, names=['irradiance_term', 'segment_index'])
        df_outputs_segments = pd.DataFrame(np.nan,
                                           columns=df_inputs_perez.index,
                                           index=multiindex_segments)
        df_outputs_segments.sort_index(inplace=True)
        df_outputs_segments = df_outputs_segments.transpose()
    else:
        df_outputs_segments = None

    idx_slice = pd.IndexSlice

    n = df_inputs_perez.shape[0]
    i = 1

    for idx, row in df_inputs_perez.iterrows():
        try:
            if ((isinstance(row['solar_zenith'], float))
                    & (row['solar_zenith'] <= 90.)):
                array.calculate_radiosities_perez(row['solar_zenith'],
                                                  row['solar_azimuth'],
                                                  row['array_tilt'],
                                                  row['array_azimuth'],
                                                  row['dni'],
                                                  row['luminance_isotropic'],
                                                  row['luminance_circumsolar'],
                                                  row['poa_horizon'],
                                                  row['poa_circumsolar'])

                # TODO: this will only work if there is no shading on the
                # surfaces
                # Format data to save all the surfaces for a pvrow
                if save_segments is not None:
                    # Select the surface of the pv row with the segments and the
                    # right columns
                    df_pvrow = array.surface_registry.loc[
                        (array.surface_registry.pvrow_index == save_segments[0])
                        & (array.surface_registry.surface_side
                           == save_segments[1]),
                        irradiance_terms_segments
                        + ['shaded']
                    ]
                    # Check that no segment has direct shading before saving
                    # results
                    if df_pvrow.shaded.sum() == 0:
                        # Add the data to the output variable by looping over
                        # irradiance terms
                        for irradiance_term in irradiance_terms_segments:
                            df_outputs_segments.loc[
                                idx, idx_slice[irradiance_term, :]] = (
                                df_pvrow[irradiance_term].values
                            )

                # Format data to save averages for all pvrows and sides
                array.surface_registry.loc[:, cols] = (
                    array.surface_registry.loc[:, cols].apply(
                        lambda x: x * array.surface_registry['area'], axis=0)
                )
                df_summed = array.surface_registry.groupby(
                    ['pvrow_index', 'surface_side']).sum()
                df_avg_irradiance = (
                    df_summed.div(df_summed['area'], axis=0).loc[
                        idx_slice[:, :], cols].sort_index().stack())

                # # Assign values to df_outputs
                df_outputs.loc[idx_slice[:, :, cols], idx] = (
                    df_avg_irradiance.loc[idx_slice[:, :, cols]]
                )

                df_outputs.loc['array_is_shaded', idx] = (
                    array.has_direct_shading
                )

        except Exception as err:
            LOGGER.debug("Unexpected error: {0}".format(err))

        print_progress(i, n, prefix='Progress:', suffix='Complete',
                       bar_length=50)
        i += 1

    try:
        bifacial_gains = (df_outputs.loc[
                          idx_slice[:, 'back', 'qinc'], :].values
                          / df_outputs.loc[
            idx_slice[:, 'front', 'qinc'], :].values)
        df_bifacial_gain = pd.DataFrame(bifacial_gains.T,
                                        index=df_inputs_perez.index,
                                        columns=range(array.n_pvrows))
    except Exception as err:
        LOGGER.warning("Error in calculation of bifacial gain %s" % err)
        df_bifacial_gain = pd.DataFrame(
            np.nan * np.ones((len(df_inputs.index), array.n_pvrows)),
            index=df_inputs.index,
            columns=range(array.n_pvrows))

    return (df_outputs.transpose(), df_bifacial_gain, df_inputs_perez,
            df_outputs_segments)