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
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)