def test_run_timeseries_engine(fn_report_example, params_serial, df_inputs_clearsky_8760): df_inputs = df_inputs_clearsky_8760.iloc[:24, :] n = df_inputs.shape[0] # Get MET data timestamps = df_inputs.index dni = df_inputs.dni.values dhi = df_inputs.dhi.values solar_zenith = df_inputs.solar_zenith.values solar_azimuth = df_inputs.solar_azimuth.values surface_tilt = df_inputs.surface_tilt.values surface_azimuth = df_inputs.surface_azimuth.values report = run_timeseries_engine(fn_report_example, params_serial, timestamps, dni, dhi, solar_zenith, solar_azimuth, surface_tilt, surface_azimuth, params_serial['rho_ground']) assert len(report['qinc_front']) == n # Test value consistency np.testing.assert_almost_equal(np.nansum(report['qinc_back']), 541.7115807694377) np.testing.assert_almost_equal(np.nansum(report['iso_back']), 18.050083142438311) # Check a couple values np.testing.assert_almost_equal(report['qinc_back'][7], 11.160301350847325) np.testing.assert_almost_equal(report['qinc_back'][-8], 8.642850754173368)
def test_params_irradiance_model(): """Test that irradiance params are passed correctly in run_timeseries_engine""" mock_irradiance_model = mock.MagicMock() mock_engine = mock.MagicMock() mock_pvarray = mock.MagicMock() irradiance_params = {'horizon_band_angle': 15.} _ = run_timeseries_engine(None, None, None, None, None, None, None, None, None, None, cls_engine=mock_engine, cls_pvarray=mock_pvarray, cls_irradiance=mock_irradiance_model, irradiance_model_params=irradiance_params) mock_irradiance_model.assert_called_once_with( horizon_band_angle=irradiance_params['horizon_band_angle'])
def test_params_ghi_passed(): """Test that GHI is passed correctly to run functions""" mock_irradiance_model = mock.MagicMock() mock_engine = mock.MagicMock() mock_pvarray = mock.MagicMock() ghi = [1000.] _ = run_timeseries_engine(None, None, None, None, None, None, None, None, None, None, cls_engine=mock_engine, cls_pvarray=mock_pvarray, cls_irradiance=mock_irradiance_model, ghi=ghi) mock_engine.return_value.fit.assert_called_with(None, None, None, None, None, None, None, None, ghi=ghi)
def test_run_timeseries_engine_fast_mode(fn_report_example, params_serial, df_inputs_clearsky_8760): """Test that running timeseries engine with fast mode works consistently. Values are supposed to be a little higher than with full mode""" df_inputs = df_inputs_clearsky_8760.iloc[:24, :] n = df_inputs.shape[0] # Get MET data timestamps = df_inputs.index dni = df_inputs.dni.values dhi = df_inputs.dhi.values solar_zenith = df_inputs.solar_zenith.values solar_azimuth = df_inputs.solar_azimuth.values surface_tilt = df_inputs.surface_tilt.values surface_azimuth = df_inputs.surface_azimuth.values fast_mode_pvrow_index = 1 def fn_report(pvarray): return { 'qinc_back': pvarray.ts_pvrows[1].back.get_param_weighted('qinc'), 'iso_back': pvarray.ts_pvrows[1].back.get_param_weighted('isotropic') } report = run_timeseries_engine(fn_report, params_serial, timestamps, dni, dhi, solar_zenith, solar_azimuth, surface_tilt, surface_azimuth, params_serial['rho_ground'], fast_mode_pvrow_index=fast_mode_pvrow_index) assert len(report['qinc_back']) == n # Test value consistency np.testing.assert_almost_equal(np.nansum(report['qinc_back']), 548.0011865481954) np.testing.assert_almost_equal(np.nansum(report['iso_back']), 18.03732189070727) # Check a couple values np.testing.assert_almost_equal(report['qinc_back'][7], 11.304105184587364) np.testing.assert_almost_equal(report['qinc_back'][-8], 8.743201975668212)
def pvfactors_timeseries(solar_azimuth, solar_zenith, surface_azimuth, surface_tilt, axis_azimuth, 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.): """ 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. pvfactors implements the model described in [1]_. 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) axis_azimuth: float Azimuth angle of the rotation axis of the PV modules, using pvlib's convention (deg). This is supposed to be fixed for all timestamps. 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) Returns ------- poa_front: numeric Calculated incident irradiance on the front surface of the PV modules (W/m2) poa_back: numeric Calculated incident irradiance on the back surface of the PV modules (W/m2) poa_front_absorbed: numeric Calculated absorbed irradiance on the front surface of the PV modules (W/m2), after AOI losses poa_back_absorbed: numeric Calculated absorbed irradiance on the back surface of the PV modules (W/m2), after AOI losses 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 (and some lists) to numpy arrays if isinstance(solar_azimuth, pd.Series): solar_azimuth = solar_azimuth.values elif isinstance(solar_azimuth, list): solar_azimuth = np.array(solar_azimuth) if isinstance(solar_zenith, pd.Series): solar_zenith = solar_zenith.values elif isinstance(solar_zenith, list): solar_zenith = np.array(solar_zenith) if isinstance(surface_azimuth, pd.Series): surface_azimuth = surface_azimuth.values elif isinstance(surface_azimuth, list): surface_azimuth = np.array(surface_azimuth) if isinstance(surface_tilt, pd.Series): surface_tilt = surface_tilt.values elif isinstance(surface_tilt, list): surface_tilt = np.array(surface_tilt) if isinstance(dni, pd.Series): dni = dni.values elif isinstance(dni, list): dni = np.array(dni) if isinstance(dhi, pd.Series): dhi = dhi.values elif isinstance(dhi, list): dhi = np.array(dhi) # Import pvfactors functions for timeseries calculations. from pvfactors.run import run_timeseries_engine # Build up pv array configuration parameters pvarray_parameters = { 'n_pvrows': n_pvrows, 'axis_azimuth': axis_azimuth, 'pvrow_height': pvrow_height, 'pvrow_width': pvrow_width, 'gcr': gcr } irradiance_model_params = { 'rho_front': rho_front_pvrow, 'rho_back': rho_back_pvrow, 'horizon_band_angle': horizon_band_angle } # Create report function def fn_build_report(pvarray): return { 'total_inc_back': pvarray.ts_pvrows[index_observed_pvrow].back.get_param_weighted( 'qinc'), 'total_inc_front': pvarray.ts_pvrows[index_observed_pvrow].front.get_param_weighted( 'qinc'), 'total_abs_back': pvarray.ts_pvrows[index_observed_pvrow].back.get_param_weighted( 'qabs'), 'total_abs_front': pvarray.ts_pvrows[index_observed_pvrow].front.get_param_weighted( 'qabs') } # Run pvfactors calculations report = run_timeseries_engine( fn_build_report, pvarray_parameters, timestamps, dni, dhi, solar_zenith, solar_azimuth, surface_tilt, surface_azimuth, albedo, irradiance_model_params=irradiance_model_params) # Turn report into dataframe df_report = pd.DataFrame(report, index=timestamps) return (df_report.total_inc_front, df_report.total_inc_back, df_report.total_abs_front, df_report.total_abs_back)
def get_effective_irradiance(self, weather, utc_offset): ''' Transform GHI/DNI/DHI from weather dataframe into POA. This is a custom method added to the modelchain class. Modelchain instance must already be assigned bifacial_losses attribute. Parameters: self: pvlib.modelchain a ModelChain instance weather: pandas.DataFrame time series weather data with columns ['ghi', 'dni', 'dhi', 'temp_air', 'wind_speed', 'surface_albedo'] utc_offset: int. hour offset of local timezone (in weather DataFrame) from UTC Returns: pvlib.modelchain ''' # Prepare weather DataFrame ---- # Must be datetimeindex aware tz = 'Etc/GMT+' + str(-utc_offset) # If pd.datetimeindex is naive, localize, otherwise convert if (pd.to_datetime(weather.index).tz is None): weather.index = pd.to_datetime(weather.index).tz_localize(tz) else: weather.index = pd.to_datetime(weather.index).tz_convert(tz) # Check if I should override weather's albedo values # (only canopy or rooftop systems will have self.system.albedo values) try: weather.albedo = self.system.albedo except: pass # Run PVLib models ---- self.prepare_inputs(weather) self.aoi_model() self.spectral_model() self.effective_irradiance_model() self.effective_irradiance = self.effective_irradiance.fillna(0) self.temperature_model() # Use PVFactors viewshed model ---- # Surface Inputs calculated in prepare_inputs if tracking, if isinstance(self.system, SingleAxisTracker): surface_tilt = self.tracking.surface_tilt surface_azimuth = self.tracking.surface_azimuth # but must be manually calculated if fixed-tilt else: surface_tilt = np.repeat(self.system.surface_tilt, len(self.weather.index)) surface_azimuth = np.repeat(self.system.surface_azimuth, len(self.weather.index)) # Need a custom function to build the report of the PVFactors simulation def pvfactor_build_report(pvarray): return { 'total_inc_back': pvarray.ts_pvrows[1].back.get_param_weighted('qinc').tolist(), 'total_inc_front': pvarray.ts_pvrows[1].front.get_param_weighted('qinc').tolist() } pvarray_parameters = { 'n_pvrows': 3, 'axis_azimuth': self.system.axis_azimuth, 'pvrow_height': self.system.axis_height, 'pvrow_width': self.system.collector_width, 'gcr': self.system.gcr, # front and back reflectivity 'rho_front_pvrow': 0.01, 'rho_back_pvrow': .03, # sky dome's diffuse horizon band angle 'horizon_band_angle': 15 } pvfactor_report = run_timeseries_engine( pvfactor_build_report, pvarray_parameters, weather.index, weather.dni, weather.dhi, self.solar_position.zenith, self.solar_position.azimuth, surface_tilt, surface_azimuth, weather.surface_albedo) # Save the PVFactor results pvfactor_df = pd.DataFrame(pvfactor_report, index=weather.index) setattr(self, 'back_irradiance', pvfactor_df.total_inc_back.fillna(0)) setattr(self, 'front_irradiance', pvfactor_df.total_inc_front.fillna(0)) # Calculate plane of array irradiance losses ---- # PVFactors doesn't account for backtracking, use frontside irradiance from # PVLib instead. (self.effective_irradiance for backtracking systems instead # of self.front_irradiance) # Effective Irradiance = Frontside POA + Backside POA * # (Bifaciality Reduced For Backside Losses) if self.system.backtrack: setattr( self, 'effective_irradiance_bifacial', self.effective_irradiance + self.back_irradiance * (self.system.module_parameters['bifaciality'] * (1 - self.bifacial_losses))) else: setattr( self, 'effective_irradiance_bifacial', self.front_irradiance + self.back_irradiance * (self.system.module_parameters['bifaciality'] * (1 - self.bifacial_losses))) # Apply same soiling loss to front-and-backside POA (conservative approach) setattr(self, 'soiling', weather.soiling) setattr(self, 'effective_irradiance_soiled', self.effective_irradiance_bifacial * (1 - self.soiling)) return self
def pvfactors_timeseries(solar_azimuth, solar_zenith, surface_azimuth, surface_tilt, axis_azimuth, 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=2): """ 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. pvfactors implements the model described in [1]_. 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) axis_azimuth: float Azimuth angle of the rotation axis of the PV modules, using pvlib's convention (deg). This is supposed to be fixed for all timestamps. 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 2 Number of workers to use in the case of parallel calculations. The '-1' value 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 (and some lists) to numpy arrays if isinstance(solar_azimuth, pd.Series): solar_azimuth = solar_azimuth.values elif isinstance(solar_azimuth, list): solar_azimuth = np.array(solar_azimuth) if isinstance(solar_zenith, pd.Series): solar_zenith = solar_zenith.values if isinstance(surface_azimuth, pd.Series): surface_azimuth = surface_azimuth.values elif isinstance(surface_azimuth, list): surface_azimuth = np.array(surface_azimuth) 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 if isinstance(solar_azimuth, list): solar_azimuth = np.array(solar_azimuth) # Import pvfactors functions for timeseries calculations. from pvfactors.run import (run_timeseries_engine, run_parallel_engine) # Build up pv array configuration parameters pvarray_parameters = { 'n_pvrows': n_pvrows, 'axis_azimuth': axis_azimuth, 'pvrow_height': pvrow_height, 'pvrow_width': pvrow_width, 'gcr': gcr, '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: report = run_parallel_engine(PVFactorsReportBuilder, pvarray_parameters, timestamps, dni, dhi, solar_zenith, solar_azimuth, surface_tilt, surface_azimuth, albedo, n_processes=n_workers_for_parallel_calcs) else: report = run_timeseries_engine(PVFactorsReportBuilder.build, pvarray_parameters, timestamps, dni, dhi, solar_zenith, solar_azimuth, surface_tilt, surface_azimuth, albedo) # Turn report into dataframe df_report = pd.DataFrame(report, index=timestamps) return df_report.total_inc_front, df_report.total_inc_back
def test_run_timeseries_faoi_fn(params_serial, pvmodule_canadian, df_inputs_clearsky_8760): """Test that in run_timeseries function, faoi functions are used correctly""" # Prepare timeseries inputs df_inputs = df_inputs_clearsky_8760.iloc[:24, :] timestamps = df_inputs.index dni = df_inputs.dni.values dhi = df_inputs.dhi.values solar_zenith = df_inputs.solar_zenith.values solar_azimuth = df_inputs.solar_azimuth.values surface_tilt = df_inputs.surface_tilt.values surface_azimuth = df_inputs.surface_azimuth.values expected_qinc_back = 542.018551 expected_qinc_front = 5452.858863 # --- Test without passing vf parameters # report function with test in it def report_fn_with_tests_no_faoi(pvarray): vf_aoi_matrix = pvarray.ts_vf_aoi_matrix pvrow = pvarray.ts_pvrows[0] list_back_pvrow_idx = [ ts_surf.index for ts_surf in pvarray.ts_pvrows[0].all_ts_surfaces ] # Check that sum of vf_aoi is equal to reflectivity values # since no faoi_fn used np.testing.assert_allclose( vf_aoi_matrix[list_back_pvrow_idx, :, 12].sum(axis=1), [0.99, 0., 0.97, 0.]) return { 'qinc_front': pvrow.front.get_param_weighted('qinc'), 'qabs_front': pvrow.front.get_param_weighted('qabs'), 'qinc_back': pvrow.back.get_param_weighted('qinc'), 'qabs_back': pvrow.back.get_param_weighted('qabs') } # create calculator report = run_timeseries_engine(report_fn_with_tests_no_faoi, params_serial, timestamps, dni, dhi, solar_zenith, solar_azimuth, surface_tilt, surface_azimuth, params_serial['rho_ground'], vf_calculator_params=None, irradiance_model_params=None) np.testing.assert_allclose(np.nansum(report['qinc_back']), expected_qinc_back) np.testing.assert_allclose(np.nansum(report['qabs_back']), 525.757995) np.testing.assert_allclose(np.nansum(report['qinc_front']), expected_qinc_front) np.testing.assert_allclose(np.nansum(report['qabs_front']), 5398.330275) # --- Test when passing vf parameters # Prepare vf calc params faoi_fn = faoi_fn_from_pvlib_sandia(pvmodule_canadian) # the following is a very high number to get agreement in # integral sums between back and front surfaces n_sections = 10000 vf_calc_params = { 'faoi_fn_front': faoi_fn, 'faoi_fn_back': faoi_fn, 'n_aoi_integral_sections': n_sections } irr_params = {'faoi_fn_front': faoi_fn, 'faoi_fn_back': faoi_fn} def report_fn_with_tests_w_faoi(pvarray): vf_aoi_matrix = pvarray.ts_vf_aoi_matrix pvrow = pvarray.ts_pvrows[0] list_back_pvrow_idx = [ ts_surf.index for ts_surf in pvrow.all_ts_surfaces ] # Check that sum of vf_aoi is consistent np.testing.assert_allclose(vf_aoi_matrix[list_back_pvrow_idx, :, 12].sum(axis=1), [0.97102, 0., 0.971548, 0.], atol=0, rtol=1e-6) return { 'qinc_front': pvrow.front.get_param_weighted('qinc'), 'qabs_front': pvrow.front.get_param_weighted('qabs'), 'qinc_back': pvrow.back.get_param_weighted('qinc'), 'qabs_back': pvrow.back.get_param_weighted('qabs') } # create calculator report = run_timeseries_engine(report_fn_with_tests_w_faoi, params_serial, timestamps, dni, dhi, solar_zenith, solar_azimuth, surface_tilt, surface_azimuth, params_serial['rho_ground'], vf_calculator_params=vf_calc_params, irradiance_model_params=irr_params) np.testing.assert_allclose(np.nansum(report['qinc_back']), expected_qinc_back) np.testing.assert_allclose(np.nansum(report['qabs_back']), 520.892016) np.testing.assert_allclose(np.nansum(report['qinc_front']), expected_qinc_front) np.testing.assert_allclose(np.nansum(report['qabs_front']), 5347.050682)