def test_luminance_in_timeseries_calc(df_perez_luminance,
                                      mock_array_timeseries_calculate):
    """
    Test that the calculation of luminance -- first step in using the vf model
    with Perez -- is functional
    """
    df_inputs_clearday = pd.read_csv(FILE_PATH)
    df_inputs_clearday = df_inputs_clearday.set_index('datetime', drop=True)
    df_inputs_clearday.index = (pd.DatetimeIndex(
        df_inputs_clearday.index).tz_localize('UTC').tz_convert(
            'Etc/GMT+7').tz_localize(None))

    # Break up inputs
    (timestamps, surface_tilt, surface_azimuth, solar_zenith, solar_azimuth,
     dni, dhi) = breakup_df_inputs(df_inputs_clearday)
    _, df_outputs = calculate_radiosities_serially_perez(
        (None, timestamps, solar_zenith, solar_azimuth, surface_tilt,
         surface_azimuth, dni, dhi))

    col_order = df_outputs.columns
    tol = 1e-8
    np.testing.assert_allclose(df_outputs.values,
                               df_perez_luminance[col_order].values,
                               atol=0,
                               rtol=tol)
def test_serial_calculation(pvarray_parameters_serial_calc,
                            df_inputs_serial_calculation):
    """
    Make sure that the calculations using the Perez model stay consistent for
    all the modeled surfaces. Also testing that there is no unexpected NaN.
    """

    # Break up inputs
    (timestamps, surface_tilt, surface_azimuth, solar_zenith, solar_azimuth,
     dni, dhi) = breakup_df_inputs(df_inputs_serial_calculation)

    # Run calculation in 1 process only
    df_registries, _ = calculate_radiosities_serially_perez(
        (pvarray_parameters_serial_calc, timestamps, solar_zenith,
         solar_azimuth, surface_tilt, surface_azimuth, dni, dhi))

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

    # Did the outputs remain consistent?
    test_results = values_are_consistent(df_outputs)
    for result in test_results:
        assert result['passed'], ("test failed for %s" %
                                  result['irradiance_term'])
def test_back_surface_luminance():
    """
    The model didn't calculate cases when the sun would hit the back surface
    because the perez model would return 0 circumsolar (not calculated for
    back surface). Fix was implemented, and this should check for it.
    """
    pvarray_parameters = {
        'surface_azimuth': 90,
        'surface_tilt': 0.0,
        'gcr': 0.3,
        'n_pvrows': 3,
        'Pvrow_height': 1.5,
        'pvrow_width': 1.0,
        'rho_back_pvrow': 0.03,
        'rho_front_pvrow': 0.01,
        'rho_ground': 0.2,
        'solar_azimuth': 90.0,
        'solar_zenith': 20.0
    }

    input_filename = 'file_test_back_surface_luminance.csv'

    df_inputs = pd.read_csv(os.path.join(TEST_DATA, input_filename),
                            index_col=0)
    df_inputs.index = pd.DatetimeIndex(df_inputs.index).tz_localize(
        'UTC').tz_convert('US/Arizona')

    # Break up inputs
    (timestamps, tracker_theta, surface_azimuth,
     solar_zenith, solar_azimuth, dni, dhi) = breakup_df_inputs(df_inputs)

    args = (pvarray_parameters, timestamps, solar_zenith, solar_azimuth,
            tracker_theta, surface_azimuth, dni, dhi)
    df_registries, _ = calculate_radiosities_serially_perez(args)

    df_outputs = get_average_pvrow_outputs(df_registries)

    vf_ipoa_front = df_outputs.loc[:, IDX_SLICE[1, 'front', 'qinc']]
    vf_ipoa_back = df_outputs.loc[:, IDX_SLICE[1, 'back', 'qinc']]

    assert isinstance(vf_ipoa_front[0], float)
    assert isinstance(vf_ipoa_back[0], float)
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'
    }
    # 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)
    (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, timestamps, array_tilt,
                                              array_azimuth, solar_zenith,
                                              solar_azimuth, dni, dhi))
    )
def test_negativevf_and_flatcasenoon():

    pvarray_parameters = {
        'surface_azimuth': 90,
        'tracker_theta': 0.0,
        'gcr': 0.3,
        'n_pvrows': 3,
        'pvrow_height': 1.5,
        'pvrow_width': 1.0,
        'rho_back_pvrow': 0.03,
        'rho_front_pvrow': 0.01,
        'rho_ground': 0.2,
        'solar_azimuth': 90.0,
        'solar_zenith': 20.0
    }

    input_filename = 'file_test_negativevf_and_flatcasenoon.csv'
    df_inputs = pd.read_csv(os.path.join(TEST_DATA, input_filename),
                            index_col=0)
    df_inputs.index = pd.DatetimeIndex(df_inputs.index).tz_localize(
        'UTC').tz_convert('US/Arizona')

    # Break up inputs
    (timestamps, tracker_theta, surface_azimuth,
     solar_zenith, solar_azimuth, dni, dhi) = breakup_df_inputs(df_inputs)

    args = (pvarray_parameters, timestamps, solar_zenith, solar_azimuth,
            tracker_theta, surface_azimuth, dni, dhi)
    df_registries, _ = calculate_radiosities_serially_perez(args)
    df_outputs = get_average_pvrow_outputs(df_registries)

    vf_ipoa_front = df_outputs.loc[:, IDX_SLICE[1, 'front', 'qinc']]
    vf_ipoa_back = df_outputs.loc[:, IDX_SLICE[1, 'back', 'qinc']]

    # The model should calculate for all daytime points now since we fixed
    # the solar noon case (almost flat but not really), and we allowed
    # negative vf values early and late in the day
    expected_n_calculated_values = 13

    assert np.sum(vf_ipoa_front.notnull()) == expected_n_calculated_values
    assert np.sum(vf_ipoa_back.notnull()) == expected_n_calculated_values
def test_serial_calculation_with_skips(
        pvarray_parameters_serial_calc,
        df_inputs_serial_calculation_with_skips):
    """
    Make sure that the calculations using the Perez model stay consistent for
    all the modeled surfaces. Also testing that there is no unexpected NaN.
    """

    # Break up inputs
    (timestamps, surface_tilt, surface_azimuth, solar_zenith, solar_azimuth,
     dni, dhi) = breakup_df_inputs(df_inputs_serial_calculation_with_skips)

    # Run calculation in 1 process only
    df_registries, _ = calculate_radiosities_serially_perez(
        (pvarray_parameters_serial_calc, timestamps, solar_zenith,
         solar_azimuth, surface_tilt, surface_azimuth, dni, dhi))

    list_nan_idx = df_registries.index[df_registries.set_index(
        'timestamps').count(axis=1) == 0]
    # There should be one line with only nan values
    assert len(list_nan_idx) == 1
def test_save_all_outputs_calculate_perez():
    """
    Make sure that the serial and parallel calculations are able to save all
    the requested data on discretized segments (instead of averaging them by
    default). Check the consistency of the results.
    """
    # Load timeseries input data
    df_inputs_clearday = pd.read_csv(FILE_PATH)
    df_inputs_clearday = df_inputs_clearday.set_index('datetime', drop=True)
    df_inputs_clearday.index = (pd.DatetimeIndex(
        df_inputs_clearday.index).tz_localize('UTC').tz_convert(
            'Etc/GMT+7').tz_localize(None))
    idx_subset = 10

    # PV array parameters for test
    arguments = {
        'n_pvrows': 3,
        'pvrow_height': 1.5,
        'pvrow_width': 1.,
        'gcr': 0.4,
        'rho_ground': 0.8,
        'rho_back_pvrow': 0.03,
        'rho_front_pvrow': 0.01,
        'cut': [(1, 3, 'front')]
    }

    # Break up inputs
    (timestamps, surface_tilt, surface_azimuth, solar_zenith, solar_azimuth,
     dni, dhi) = breakup_df_inputs(df_inputs_clearday.iloc[:idx_subset])

    args = (arguments, timestamps, solar_zenith, solar_azimuth, surface_tilt,
            surface_azimuth, dni, dhi)

    # Run the serial calculation
    df_registries_serial, _ = (calculate_radiosities_serially_perez(args))

    df_registries_parallel, _ = (calculate_radiosities_parallel_perez(*args))

    # Format the outputs
    df_outputs_segments_serial = get_pvrow_segment_outputs(
        df_registries_serial, values=['qinc'], include_shading=False)
    df_outputs_segments_parallel = get_pvrow_segment_outputs(
        df_registries_parallel, values=['qinc'], include_shading=False)

    # Load files with expected outputs
    expected_ipoa_dict_qinc = np.array(
        [[842.54617681, 842.5566707, 842.43690951],
         [839.30179691, 839.30652961, 839.30906023],
         [839.17118956, 839.17513098, 839.17725568],
         [842.24679271, 842.26194393, 842.15463231]])

    # Perform the comparisons
    rtol = 1e-6
    atol = 0
    np.testing.assert_allclose(expected_ipoa_dict_qinc,
                               df_outputs_segments_serial.values,
                               atol=atol,
                               rtol=rtol)
    np.testing.assert_allclose(expected_ipoa_dict_qinc,
                               df_outputs_segments_parallel.values,
                               atol=atol,
                               rtol=rtol)
Exemple #8
0
def pvfactors_timeseries(solar_azimuth,
                         solar_zenith,
                         surface_azimuth,
                         surface_tilt,
                         timestamps,
                         dni,
                         dhi,
                         gcr,
                         pvrow_height,
                         pvrow_width,
                         albedo,
                         n_pvrows=3,
                         index_observed_pvrow=1,
                         rho_front_pvrow=0.03,
                         rho_back_pvrow=0.05,
                         horizon_band_angle=15.,
                         run_parallel_calculations=True,
                         n_workers_for_parallel_calcs=None):
    """
    Calculate front and back surface plane-of-array irradiance on
    a fixed tilt or single-axis tracker PV array configuration, and using
    the open-source "pvfactors" package.
    Please refer to pvfactors online documentation for more details:
    https://sunpower.github.io/pvfactors/

    Parameters
    ----------
    solar_azimuth: numeric
        Sun's azimuth angles using pvlib's azimuth convention (deg)
    solar_zenith: numeric
        Sun's zenith angles (deg)
    surface_azimuth: numeric
        Azimuth angle of the front surface of the PV modules, using pvlib's
        convention (deg)
    surface_tilt: numeric
        Tilt angle of the PV modules, going from 0 to 180 (deg)
    timestamps: datetime or DatetimeIndex
        List of simulation timestamps
    dni: numeric
        Direct normal irradiance (W/m2)
    dhi: numeric
        Diffuse horizontal irradiance (W/m2)
    gcr: float
        Ground coverage ratio of the pv array
    pvrow_height: float
        Height of the pv rows, measured at their center (m)
    pvrow_width: float
        Width of the pv rows in the considered 2D plane (m)
    albedo: float
        Ground albedo
    n_pvrows: int, default 3
        Number of PV rows to consider in the PV array
    index_observed_pvrow: int, default 1
        Index of the PV row whose incident irradiance will be returned. Indices
        of PV rows go from 0 to n_pvrows-1.
    rho_front_pvrow: float, default 0.03
        Front surface reflectivity of PV rows
    rho_back_pvrow: float, default 0.05
        Back surface reflectivity of PV rows
    horizon_band_angle: float, default 15
        Elevation angle of the sky dome's diffuse horizon band (deg)
    run_parallel_calculations: bool, default True
        pvfactors is capable of using multiprocessing. Use this flag to decide
        to run calculations in parallel (recommended) or not.
    n_workers_for_parallel_calcs: int, default None
        Number of workers to use in the case of parallel calculations. The
        default value of 'None' will lead to using a value equal to the number
        of CPU's on the machine running the model.

    Returns
    -------
    front_poa_irradiance: numeric
        Calculated incident irradiance on the front surface of the PV modules
        (W/m2)
    back_poa_irradiance: numeric
        Calculated incident irradiance on the back surface of the PV modules
        (W/m2)
    df_registries: pandas DataFrame
        DataFrame containing detailed outputs of the simulation; for
        instance the shapely geometries, the irradiance components incident on
        all surfaces of the PV array (for all timestamps), etc.
        In the pvfactors documentation, this is refered to as the "surface
        registry".

    References
    ----------
    .. [1] Anoma, Marc Abou, et al. "View Factor Model and Validation for
        Bifacial PV and Diffuse Shade on Single-Axis Trackers." 44th IEEE
        Photovoltaic Specialist Conference. 2017.
    """

    # Convert pandas Series inputs to numpy arrays
    if isinstance(solar_azimuth, pd.Series):
        solar_azimuth = solar_azimuth.values
    if isinstance(solar_zenith, pd.Series):
        solar_zenith = solar_zenith.values
    if isinstance(surface_azimuth, pd.Series):
        surface_azimuth = surface_azimuth.values
    if isinstance(surface_tilt, pd.Series):
        surface_tilt = surface_tilt.values
    if isinstance(dni, pd.Series):
        dni = dni.values
    if isinstance(dhi, pd.Series):
        dhi = dhi.values

    # Import pvfactors functions for timeseries calculations.
    from pvfactors.timeseries import (calculate_radiosities_parallel_perez,
                                      calculate_radiosities_serially_perez,
                                      get_average_pvrow_outputs)
    idx_slice = pd.IndexSlice

    # Build up pv array configuration parameters
    pvarray_parameters = {
        'n_pvrows': n_pvrows,
        'pvrow_height': pvrow_height,
        'pvrow_width': pvrow_width,
        'gcr': gcr,
        'rho_ground': albedo,
        'rho_front_pvrow': rho_front_pvrow,
        'rho_back_pvrow': rho_back_pvrow,
        'horizon_band_angle': horizon_band_angle
    }

    # Run pvfactors calculations: either in parallel or serially
    if run_parallel_calculations:
        df_registries, df_custom_perez = calculate_radiosities_parallel_perez(
            pvarray_parameters,
            timestamps,
            solar_zenith,
            solar_azimuth,
            surface_tilt,
            surface_azimuth,
            dni,
            dhi,
            n_processes=n_workers_for_parallel_calcs)
    else:
        inputs = (pvarray_parameters, timestamps, solar_zenith, solar_azimuth,
                  surface_tilt, surface_azimuth, dni, dhi)
        df_registries, df_custom_perez = calculate_radiosities_serially_perez(
            inputs)

    # Get the average surface outputs
    df_outputs = get_average_pvrow_outputs(df_registries,
                                           values=['qinc'],
                                           include_shading=True)

    # Select the calculated outputs from the pvrow to observe
    ipoa_front = df_outputs.loc[:, idx_slice[index_observed_pvrow, 'front',
                                             'qinc']]

    ipoa_back = df_outputs.loc[:, idx_slice[index_observed_pvrow, 'back',
                                            'qinc']]

    # Set timestamps as index of df_registries for consistency of outputs
    df_registries = df_registries.set_index('timestamps')

    return ipoa_front, ipoa_back, df_registries
Exemple #9
0
def pvfactors_timeseries(
        solar_azimuth, solar_zenith, surface_azimuth, surface_tilt,
        timestamps, dni, dhi, gcr, pvrow_height, pvrow_width, albedo,
        n_pvrows=3, index_observed_pvrow=1,
        rho_front_pvrow=0.03, rho_back_pvrow=0.05,
        horizon_band_angle=15.,
        run_parallel_calculations=True, n_workers_for_parallel_calcs=None):
    """
    Calculate front and back surface plane-of-array irradiance on
    a fixed tilt or single-axis tracker PV array configuration, and using
    the open-source "pvfactors" package.
    Please refer to pvfactors online documentation for more details:
    https://sunpower.github.io/pvfactors/

    Inputs
    ------
    solar_azimuth: numeric
        Sun's azimuth angles using pvlib's azimuth convention (deg)
    solar_zenith: numeric
        Sun's zenith angles (deg)
    surface_azimuth: numeric
        Azimuth angle of the front surface of the PV modules, using pvlib's
        convention (deg)
    surface_tilt: numeric
        Tilt angle of the PV modules, going from 0 to 180 (deg)
    timestamps: datetime or DatetimeIndex
        List of simulation timestamps
    dni: numeric
        Direct normal irradiance (W/m2)
    dhi: numeric
        Diffuse horizontal irradiance (W/m2)
    gcr: float
        Ground coverage ratio of the pv array
    pvrow_height: float
        Height of the pv rows, measured at their center (m)
    pvrow_width: float
        Width of the pv rows in the considered 2D plane (m)
    albedo: float
        Ground albedo
    n_pvrows: int, default 3
        Number of PV rows to consider in the PV array
    index_observed_pvrow: int, default 1
        Index of the PV row whose incident irradiance will be returned. Indices
        of PV rows go from 0 to n_pvrows-1.
    rho_front_pvrow: float, default 0.03
        Front surface reflectivity of PV rows
    rho_back_pvrow: float, default 0.05
        Back surface reflectivity of PV rows
    horizon_band_angle: float, default 15
        Elevation angle of the sky dome's diffuse horizon band (deg)
    run_parallel_calculations: bool, default True
        pvfactors is capable of using multiprocessing. Use this flag to decide
        to run calculations in parallel (recommended) or not.
    n_workers_for_parallel_calcs: int, default None
        Number of workers to use in the case of parallel calculations. The
        default value of 'None' will lead to using a value equal to the number
        of CPU's on the machine running the model.

    Returns
    -------
    front_poa_irradiance: numeric
        Calculated incident irradiance on the front surface of the PV modules
        (W/m2)
    back_poa_irradiance: numeric
        Calculated incident irradiance on the back surface of the PV modules
        (W/m2)
    df_registries: pandas DataFrame
        DataFrame containing detailed outputs of the simulation; for
        instance the shapely geometries, the irradiance components incident on
        all surfaces of the PV array (for all timestamps), etc.
        In the pvfactors documentation, this is refered to as the "surface
        registry".

    References
    ----------
    .. [1] Anoma, Marc Abou, et al. "View Factor Model and Validation for
        Bifacial PV and Diffuse Shade on Single-Axis Trackers." 44th IEEE
        Photovoltaic Specialist Conference. 2017.
    """

    # Convert pandas Series inputs to numpy arrays
    if isinstance(solar_azimuth, pd.Series):
        solar_azimuth = solar_azimuth.values
    if isinstance(solar_zenith, pd.Series):
        solar_zenith = solar_zenith.values
    if isinstance(surface_azimuth, pd.Series):
        surface_azimuth = surface_azimuth.values
    if isinstance(surface_tilt, pd.Series):
        surface_tilt = surface_tilt.values
    if isinstance(dni, pd.Series):
        dni = dni.values
    if isinstance(dhi, pd.Series):
        dhi = dhi.values

    # Import pvfactors functions for timeseries calculations.
    from pvfactors.timeseries import (calculate_radiosities_parallel_perez,
                                      calculate_radiosities_serially_perez,
                                      get_average_pvrow_outputs)
    idx_slice = pd.IndexSlice

    # Build up pv array configuration parameters
    pvarray_parameters = {
        'n_pvrows': n_pvrows,
        'pvrow_height': pvrow_height,
        'pvrow_width': pvrow_width,
        'gcr': gcr,
        'rho_ground': albedo,
        'rho_front_pvrow': rho_front_pvrow,
        'rho_back_pvrow': rho_back_pvrow,
        'horizon_band_angle': horizon_band_angle
    }

    # Run pvfactors calculations: either in parallel or serially
    if run_parallel_calculations:
        df_registries, df_custom_perez = calculate_radiosities_parallel_perez(
            pvarray_parameters, timestamps, solar_zenith, solar_azimuth,
            surface_tilt, surface_azimuth, dni, dhi,
            n_processes=n_workers_for_parallel_calcs)
    else:
        inputs = (pvarray_parameters, timestamps, solar_zenith, solar_azimuth,
                  surface_tilt, surface_azimuth, dni, dhi)
        df_registries, df_custom_perez = calculate_radiosities_serially_perez(
            inputs)

    # Get the average surface outputs
    df_outputs = get_average_pvrow_outputs(df_registries,
                                           values=['qinc'],
                                           include_shading=True)

    # Select the calculated outputs from the pvrow to observe
    ipoa_front = df_outputs.loc[:, idx_slice[index_observed_pvrow,
                                             'front', 'qinc']]

    ipoa_back = df_outputs.loc[:, idx_slice[index_observed_pvrow,
                                            'back', 'qinc']]

    # Set timestamps as index of df_registries for consistency of outputs
    df_registries = df_registries.set_index('timestamps')

    return ipoa_front, ipoa_back, df_registries
def test_save_all_outputs_calculate_perez():
    """
    Make sure that the serial and parallel calculations are able to save all
    the requested data on discretized segments (instead of averaging them by
    default). Check the consistency of the results.
    """
    # Load timeseries input data
    df_inputs_clearday = pd.read_csv(FILE_PATH)
    df_inputs_clearday = df_inputs_clearday.set_index('datetime', drop=True)
    df_inputs_clearday.index = (pd.DatetimeIndex(
        df_inputs_clearday.index).tz_localize('UTC').tz_convert(
            'Etc/GMT+7').tz_localize(None))
    idx_subset = 10

    # Adjustment in angles needed: need to keep azimuth constant and change
    # tilt angle only
    df_inputs_clearday.loc[(df_inputs_clearday.solar_azimuth <= 180.),
                           'array_azimuth'] = (
                               df_inputs_clearday.loc[:, 'array_azimuth'][-1])
    df_inputs_clearday.loc[(df_inputs_clearday.solar_azimuth <= 180.),
                           'array_tilt'] *= (-1)

    # PV array parameters for test
    arguments = {
        'n_pvrows': 3,
        'pvrow_height': 1.5,
        'pvrow_width': 1.,
        'gcr': 0.4,
        'rho_ground': 0.8,
        'rho_back_pvrow': 0.03,
        'rho_front_pvrow': 0.01,
        'cut': [(1, 3, 'front')]
    }

    # Break up inputs
    (timestamps, array_tilt, array_azimuth, solar_zenith, solar_azimuth, dni,
     dhi) = breakup_df_inputs(df_inputs_clearday.iloc[:idx_subset])

    args = (arguments, timestamps, solar_zenith, solar_azimuth, array_tilt,
            array_azimuth, dni, dhi)

    # Run the serial calculation
    df_registries_serial, _ = (calculate_radiosities_serially_perez(args))

    df_registries_parallel, _ = (calculate_radiosities_parallel_perez(*args))

    # Format the outputs
    df_outputs_segments_serial = get_pvrow_segment_outputs(
        df_registries_serial, values=['qinc'], include_shading=False)
    df_outputs_segments_parallel = get_pvrow_segment_outputs(
        df_registries_parallel, values=['qinc'], include_shading=False)

    # Load files with expected outputs
    expected_ipoa_dict_qinc = np.array(
        [[842.43691838, 842.54795737, 842.52912932],
         [839.30539601, 839.30285394, 839.29810984],
         [839.17118976, 839.17513111, 839.17725576],
         [842.24681064, 842.26195526, 842.15463995]])

    # Perform the comparisons
    rtol = 1e-7
    atol = 0
    assert np.allclose(expected_ipoa_dict_qinc,
                       df_outputs_segments_serial.values,
                       atol=atol,
                       rtol=rtol)
    assert np.allclose(expected_ipoa_dict_qinc,
                       df_outputs_segments_parallel.values,
                       atol=atol,
                       rtol=rtol)