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 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
Exemple #3
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)