def test_ordered_pvarray_from_dict(params): """Test that can successfully create ordered pvarray from parameters dict, and that the axis azimuth convention works correctly (via normal vector) """ pvarray = OrderedPVArray.fit_from_dict_of_scalars(params) # Test that ground is created successfully assert isinstance(pvarray.ts_ground, TsGround) # TODO: check why this is not matching exactly: hint = look at length # of ground shaded surfaces, some small tolerance may be chipped away np.testing.assert_allclose(pvarray.ts_ground.length, MAX_X_GROUND - MIN_X_GROUND) # Test the front and back sides assert len(pvarray.ts_pvrows) == 3 np.testing.assert_array_equal(pvarray.ts_pvrows[0].front.n_vector, -pvarray.ts_pvrows[0].back.n_vector) assert pvarray.ts_pvrows[0].front.shaded_length == 0 assert pvarray.gcr == params['gcr'] assert np.abs(pvarray.rotation_vec) == params['surface_tilt'] assert pvarray.ts_pvrows[0].front.n_vector[0] > 0 distance_between_pvrows = \ pvarray.ts_pvrows[1].centroid.x - pvarray.ts_pvrows[0].centroid.x assert distance_between_pvrows == 5.0 assert pvarray.n_ts_surfaces == 40 # Orient the array the other way params.update({'surface_azimuth': 270.}) pvarray = OrderedPVArray.fit_from_dict_of_scalars(params) assert pvarray.ts_pvrows[0].front.n_vector[0] < 0 assert pvarray.n_ts_surfaces == 40
def test_pvengine_float_inputs_perez_transparency_spacing_fast(params): """Test that module transparency and spacing are having the expected effect to calculated PV back side irradiance""" # Irradiance inputs timestamps = dt.datetime(2019, 6, 11, 11) DNI = 1000. DHI = 100. # --- with 0 transparency and spacing # Create models irr_params = {'module_transparency': 0., 'module_spacing_ratio': 0.} irradiance_model = HybridPerezOrdered(**irr_params) pvarray = OrderedPVArray.init_from_dict(params) eng = PVEngine(pvarray, irradiance_model=irradiance_model) # Fit engine eng.fit(timestamps, DNI, DHI, params['solar_zenith'], params['solar_azimuth'], params['surface_tilt'], params['surface_azimuth'], params['rho_ground']) # Run timestep def fn_report(pvarray): return (pvarray.ts_pvrows[1] .back.get_param_weighted('qinc')) no_spacing_transparency_back_qinc = \ eng.run_fast_mode(fn_build_report=fn_report, pvrow_index=1) # --- with non-0 transparency and spacing # Create models irr_params = {'module_transparency': 0.1, 'module_spacing_ratio': 0.1} irradiance_model = HybridPerezOrdered(**irr_params) pvarray = OrderedPVArray.init_from_dict(params) eng = PVEngine(pvarray, irradiance_model=irradiance_model) # Fit engine eng.fit(timestamps, DNI, DHI, params['solar_zenith'], params['solar_azimuth'], params['surface_tilt'], params['surface_azimuth'], params['rho_ground']) # Run timestep w_spacing_transparency_back_qinc = \ eng.run_fast_mode(fn_build_report=fn_report, pvrow_index=1) # Checks expected_back_qinc = 134.7143531 # higher than when params are 0 np.testing.assert_almost_equal( w_spacing_transparency_back_qinc, expected_back_qinc) assert no_spacing_transparency_back_qinc < w_spacing_transparency_back_qinc
def test_pvengine_float_inputs_perez(params): """Test that PV engine works for float inputs""" irradiance_model = HybridPerezOrdered() pvarray = OrderedPVArray.init_from_dict(params) eng = PVEngine(pvarray, irradiance_model=irradiance_model) # Irradiance inputs timestamps = dt.datetime(2019, 6, 11, 11) DNI = 1000. DHI = 100. # Fit engine eng.fit(timestamps, DNI, DHI, params['solar_zenith'], params['solar_azimuth'], params['surface_tilt'], params['surface_azimuth'], params['rho_ground']) # Checks np.testing.assert_almost_equal(eng.irradiance.direct['front_illum_pvrow'], DNI) # Run timestep pvarray = eng.run_full_mode(fn_build_report=lambda pvarray: pvarray) # Checks assert isinstance(pvarray, OrderedPVArray) np.testing.assert_almost_equal( pvarray.ts_pvrows[0].front.get_param_weighted('qinc'), 1110.1164773159298) np.testing.assert_almost_equal( pvarray.ts_pvrows[1].front.get_param_weighted('qinc'), 1110.595903991) np.testing.assert_almost_equal( pvarray.ts_pvrows[2].front.get_param_weighted('qinc'), 1112.37717553) np.testing.assert_almost_equal( pvarray.ts_pvrows[1].back.get_param_weighted('qinc'), 116.49050349491208)
def test_run_fast_and_full_modes_sequentially(params, fn_report_example): """Make sure that can run fast and full modes one after the other without making the engine crash""" # Irradiance inputs timestamps = dt.datetime(2019, 6, 11, 11) DNI = 1000. DHI = 100. # Prepare some engine inputs pvarray = OrderedPVArray.init_from_dict(params) fast_mode_pvrow_index = 1 fast_mode_segment_index = 0 # Create engine object eng = PVEngine(pvarray, fast_mode_pvrow_index=fast_mode_pvrow_index, fast_mode_segment_index=fast_mode_segment_index) # Fit engine eng.fit(timestamps, DNI, DHI, params['solar_zenith'], params['solar_azimuth'], params['surface_tilt'], params['surface_azimuth'], params['rho_ground']) # Run fast mode def fn_report(pvarray): return (pvarray.ts_pvrows[1].back.get_param_weighted('qinc')) qinc_fast = eng.run_fast_mode(fn_build_report=fn_report) # Run full mode report = eng.run_full_mode(fn_build_report=fn_report_example) np.testing.assert_allclose(qinc_fast, 119.095285) np.testing.assert_allclose(report['qinc_back'], 116.49050349491)
def test_pvengine_float_inputs_iso(params): """Test that PV engine works for float inputs""" irradiance_model = IsotropicOrdered() pvarray = OrderedPVArray.init_from_dict(params) eng = PVEngine(pvarray, irradiance_model=irradiance_model) # Irradiance inputs timestamps = dt.datetime(2019, 6, 11, 11) DNI = 1000. DHI = 100. # Fit engine eng.fit(timestamps, DNI, DHI, params['solar_zenith'], params['solar_azimuth'], params['surface_tilt'], params['surface_azimuth'], params['rho_ground']) # Checks np.testing.assert_almost_equal(eng.irradiance.direct['front_illum_pvrow'], DNI) # Run timestep pvarray = eng.run_full_mode(fn_build_report=lambda pvarray: pvarray) # Checks assert isinstance(pvarray, OrderedPVArray) np.testing.assert_almost_equal( pvarray.ts_pvrows[0].front.get_param_weighted('qinc'), 1099.22245374) np.testing.assert_almost_equal( pvarray.ts_pvrows[1].front.get_param_weighted('qinc'), 1099.6948573) np.testing.assert_almost_equal( pvarray.ts_pvrows[2].front.get_param_weighted('qinc'), 1102.76149246) # Check absorbed np.testing.assert_almost_equal( pvarray.ts_pvrows[1].front.get_param_weighted('qabs'), 1099.6948573 * 0.99)
def test_ordered_pvarray_gnd_shadow_casting_tolerance(): """It seems that there are roundoff errors when running shadow casting on some computers, test that this case works.""" params = { 'axis_azimuth': 0, 'gcr': 0.3, 'n_pvrows': 3, 'pvrow_height': 1.8, 'pvrow_width': 1.98, 'solar_azimuth': 263.99310644558074, 'solar_zenith': 73.91658668648401, 'surface_azimuth': 270.0, 'surface_tilt': 51.98206680806641 } pvarray_w_direct_shading = OrderedPVArray.fit_from_dict_of_scalars(params) # Check that 3 shadows on ground assert len( pvarray_w_direct_shading.ts_ground.non_point_shaded_surfaces_at( 0)) == 5 # Check that there is no shading on the center pv row ts_pvrow = pvarray_w_direct_shading.ts_pvrows[1] assert ts_pvrow.front.list_segments[0].shaded.length[0] \ < DISTANCE_TOLERANCE
def test_orderedpvarray_almost_flat(): """Making sure that things are correct when the pvarray is almost flat and the sun is very low, which means that the shadows on the ground, and the edge points will be outside of ground range (since not infinite)""" params = { 'n_pvrows': 3, 'pvrow_height': 2.5, 'pvrow_width': 2., 'surface_azimuth': 90., # east oriented modules 'axis_azimuth': 0., # axis of rotation towards North 'surface_tilt': 0.01, # almost flat 'gcr': 0.4, 'solar_zenith': 89.9, # sun super low 'solar_azimuth': 90., # sun located in the east } pvarray = OrderedPVArray.fit_from_dict_of_scalars(params) ts_ground = pvarray.ts_ground # there should be 3 shadow elements on the ground assert len(ts_ground.shadow_elements) == 3 # But their lengths should be = to zero np.testing.assert_allclose(ts_ground.shaded_length, 0) # all of the edge points should be outside of range of ground geometry for coords in ts_ground.cut_point_coords: assert (coords.x < MIN_X_GROUND) | (coords.x > MAX_X_GROUND)
def test_param_names(params): """Test that parameter names are passed correctly""" param_names = ['qinc'] pvarray = OrderedPVArray.fit_from_dict_of_scalars(params, param_names=param_names) # Set all surfaces parameters to 1 pvarray.update_params({'qinc': 1}) # Check that all surfaces of the correct surface params all_ts_surfaces = pvarray.all_ts_surfaces for ts_surf in all_ts_surfaces: assert ts_surf.param_names == param_names assert ts_surf.get_param('qinc') == 1 # Check weighted values np.testing.assert_almost_equal( pvarray.ts_ground.get_param_weighted('qinc'), 1) np.testing.assert_almost_equal(pvarray.ts_ground.get_param_ww('qinc'), pvarray.ts_ground.length) for ts_pvrow in pvarray.ts_pvrows: # Front np.testing.assert_almost_equal( ts_pvrow.front.get_param_weighted('qinc'), 1) np.testing.assert_almost_equal(ts_pvrow.front.get_param_ww('qinc'), ts_pvrow.front.length) # Back np.testing.assert_almost_equal( ts_pvrow.back.get_param_weighted('qinc'), 1) np.testing.assert_almost_equal(ts_pvrow.back.get_param_ww('qinc'), ts_pvrow.back.length)
def test_pvengine_ts_inputs_perez(params_serial, df_inputs_serial_calculation, fn_report_example): """Test that PV engine works for timeseries inputs""" # Break up inputs (timestamps, surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, dni, dhi) = breakup_df_inputs(df_inputs_serial_calculation) albedo = params_serial['rho_ground'] # Create engine irradiance_model = HybridPerezOrdered() pvarray = OrderedPVArray.init_from_dict( params_serial, param_names=irradiance_model.params) eng = PVEngine(pvarray, irradiance_model=irradiance_model) # Fit engine eng.fit(timestamps, dni, dhi, solar_zenith, solar_azimuth, surface_tilt, surface_azimuth, albedo) # Run all timesteps report = eng.run_full_mode(fn_build_report=fn_report_example) # Check values np.testing.assert_array_almost_equal(report['qinc_front'], [1066.272392, 1065.979824]) np.testing.assert_array_almost_equal(report['qinc_back'], [135.897106, 136.01297]) np.testing.assert_array_almost_equal(report['iso_front'], [42.816637, 42.780206]) np.testing.assert_array_almost_equal(report['iso_back'], [1.727308, 1.726535]) np.testing.assert_array_almost_equal(report['qabs_back'], report['qinc_back'] * 0.97)
def test_fast_mode_8760(params, df_inputs_clearsky_8760): """Test fast mode with 1 PV row to make sure that is consistent after the vectorization update: should get exact same values """ # Get MET data df_inputs = df_inputs_clearsky_8760 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 # Run simulation for only 1 PV row params.update({'n_pvrows': 1}) # Run engine pvarray = OrderedPVArray.init_from_dict(params) eng = PVEngine(pvarray) eng.fit(timestamps, dni, dhi, solar_zenith, solar_azimuth, surface_tilt, surface_azimuth, params['rho_ground']) qinc = eng.run_fast_mode( fn_build_report=lambda pvarray: (pvarray.ts_pvrows[0].back.get_param_weighted('qinc')), pvrow_index=0) # Check than annual energy on back is consistent np.testing.assert_allclose(np.nansum(qinc) / 1e3, 342.848005)
def test_hybridperez_transform(df_inputs_clearsky_8760): n_points = 24 df_inputs = df_inputs_clearsky_8760.iloc[:n_points, :] # Base params params = { 'n_pvrows': 3, 'pvrow_height': 1, 'pvrow_width': 1, 'axis_azimuth': 0., 'gcr': 0.3 } albedo = 0.2 # Initialize and fit pv array pvarray = OrderedPVArray.init_from_dict(params) # Fit pv array to timeseries data pvarray.fit(df_inputs.solar_zenith, df_inputs.solar_azimuth, df_inputs.surface_tilt, df_inputs.surface_azimuth) # irradiance model model = HybridPerezOrdered(horizon_band_angle=15.) model.fit(df_inputs.index, df_inputs.dni.values, df_inputs.dhi.values, df_inputs.solar_zenith.values, df_inputs.solar_azimuth.values, df_inputs.surface_tilt.values, df_inputs.surface_azimuth.values, albedo) model.transform(pvarray) # Check timeseries parameters expected_middle_back_horizon = np.array([ 0., 0., 0., 0., 0., 0., 0., 0.8244883, 4.43051118, 6.12136418, 6.03641816, 2.75109931, 3.15586037, 6.14709947, 6.02242241, 4.25283177, 0.58518296, 0., 0., 0., 0., 0., 0., 0. ]) list_idx = np.where(expected_middle_back_horizon != 0) np.testing.assert_allclose( expected_middle_back_horizon[list_idx], pvarray.ts_pvrows[1].back.list_segments[0].illum.get_param_weighted( 'horizon')[list_idx]) expected_ground_circ = np.array([ 0., 0., 0., 0., 0., 0., 0., 2.19047189, 8.14152575, 13.9017384, 18.54394777, 21.11510529, 21.00554831, 18.24251837, 13.47583799, 7.66930532, 1.74693357, 0., 0., 0., 0., 0., 0., 0. ]) np.testing.assert_allclose(expected_ground_circ, pvarray.ts_ground.illum_params['circumsolar']) np.testing.assert_allclose(np.zeros(n_points), pvarray.ts_ground.shaded_params['circumsolar']) # Check at a given time idx pvrow = pvarray.ts_pvrows[1].at(7) np.testing.assert_allclose( pvrow.back.list_segments[0].illum_collection.get_param_weighted( 'horizon'), expected_middle_back_horizon[7]) pvground = pvarray.ts_ground.at(7) np.testing.assert_allclose( pvground.list_segments[0].illum_collection.get_param_weighted( 'circumsolar'), expected_ground_circ[7])
def test_ordered_pvarray_gnd_shadow_casting(params): """Test shadow casting on ground, no inter-row shading""" # Test front shading on right ordered_pvarray = OrderedPVArray.fit_from_dict_of_scalars(params) assert len(ordered_pvarray.ts_ground.non_point_shaded_surfaces_at(0)) == 3 assert len(ordered_pvarray.ts_ground.non_point_illum_surfaces_at(0)) == 7 assert ordered_pvarray.ts_ground.shaded_length == 6.385066634855473
def test_check_direct_shading_continuity(): """Make sure the calculation is correct when direct shading happens. - before v1.3.0, there's a discontinuity (big jump) in prediction when direct shading happens. The values are the same as >=v1.3.0 for no direct shading, but they are different with direct shading. - starting at v1.3.0, the values are more continuous (no big change) when going from no direct shading to direct shading, which means it's most certainly a better implementation. The issue before v1.3.0 could be due to the fact that shadows are merged, and there might be a piece of geometry lost there, but not entirely sure. Since it's still relatively small, will not dig further. Here, we're testing the outputs at 2 timestamps, right before and right after direct shading, when varying only solar zenith. """ # Prepare inputs n = 2 inputs = { 'solar_zenith': [81.275, 81.276], # right at the limit of direct shadg 'solar_azimuth': [295.9557133] * n, 'surface_tilt': [15.18714669] * n, 'surface_azimuth': [270.] * n, 'dni': [1000.] * n, 'dhi': [100.] * n, 'albedo': [0.2] * n, 'times': [dt.datetime(2014, 6, 25, 3)] * n} # Array parameters params = {'n_pvrows': 3, 'axis_azimuth': 0.0, 'pvrow_height': 1.5, 'pvrow_width': 2.5, 'gcr': 0.4} # Create engine and fit to inputs pvarray = OrderedPVArray.init_from_dict(params) eng = PVEngine(pvarray) eng.fit(np.array(inputs['times']), np.array(inputs['dni']), np.array(inputs['dhi']), np.array(inputs['solar_zenith']), np.array(inputs['solar_azimuth']), np.array(inputs['surface_tilt']), np.array(inputs['surface_azimuth']), np.array(inputs['albedo'])) # Check there we are indeed right at the limit of direct shading np.testing.assert_array_equal(pvarray.has_direct_shading, [False, True]) # Run simulation and get output pvarray = eng.run_full_mode(fn_build_report=lambda pvarray: pvarray) out = pvarray.ts_pvrows[1].back.get_param_weighted('qinc') # Check expected outputs: before v1.3.0, expected output is # [20.4971271991293, 21.389095477613356], which shows discontinuity expected_out = [20.497127, 20.50229] np.testing.assert_allclose(out, expected_out)
def test_plot_ordered_pvarray(): """Test that ordered pv array plotting works correctly""" is_ci = os.environ.get('CI', False) if not is_ci: import matplotlib.pyplot as plt # Create base params params = { 'n_pvrows': 3, 'pvrow_height': 2.5, 'pvrow_width': 2., 'surface_azimuth': 90., # east oriented modules / point right 'axis_azimuth': 0., # axis of rotation towards North 'surface_tilt': 20., 'gcr': 0.4, 'solar_zenith': 20., 'solar_azimuth': 90., # sun located in the east 'rho_ground': 0.2, 'rho_front_pvrow': 0.01, 'rho_back_pvrow': 0.03 } # Plot simple ordered pv array ordered_pvarray = OrderedPVArray.fit_from_dict_of_scalars(params) f, ax = plt.subplots() ordered_pvarray.plot_at_idx(0, ax) plt.show() # Plot discretized ordered pv array params.update({ 'cut': { 0: { 'front': 5 }, 1: { 'back': 3 } }, 'surface_azimuth': 270. }) # point left ordered_pvarray = OrderedPVArray.fit_from_dict_of_scalars(params) f, ax = plt.subplots() ordered_pvarray.plot_at_idx(0, ax) plt.show()
def test_coords_ground_shadows(): """Check coords of timeseries ground shadows""" # Create base params params = { 'axis_azimuth': 0, 'n_pvrows': 2, 'pvrow_height': 2.5, 'pvrow_width': 2., 'gcr': 0.4, 'cut': { 0: { 'front': 5 }, 1: { 'back': 3 } } } # Timeseries parameters for testing solar_zenith = np.array([20., 45.]) solar_azimuth = np.array([70., 200.]) surface_tilt = np.array([10., 70.]) surface_azimuth = np.array([90., 270.]) # Plot simple ordered pv array ordered_pvarray = OrderedPVArray(**params) ordered_pvarray.fit(solar_zenith, solar_azimuth, surface_tilt, surface_azimuth) expected_gnd_shadow_coords = [[([-1.89924929, 0.19163641], [0., 0.]), ([0.18914857, 1.51846431], [0., 0.])], [([3.10075071, 5.19163641], [0., 0.]), ([5.18914857, 6.51846431], [0., 0.])]] gnd_shadow_coords = [ shadow.coords.as_array for shadow in ordered_pvarray.ts_ground.shadow_elements ] np.testing.assert_almost_equal(expected_gnd_shadow_coords, gnd_shadow_coords)
def test_coords_cut_points(): """Test timeseries coords of cut points""" # Create base params params = { 'axis_azimuth': 0, 'n_pvrows': 2, 'pvrow_height': 2.5, 'pvrow_width': 2., 'gcr': 0.4, 'cut': { 0: { 'front': 5 }, 1: { 'back': 3 } } } # Timeseries parameters for testing solar_zenith = np.array([20., 45.]) solar_azimuth = np.array([70., 200.]) surface_tilt = np.array([10., 70.]) surface_azimuth = np.array([90., 270.]) # Plot simple ordered pv array ordered_pvarray = OrderedPVArray(**params) ordered_pvarray.fit(solar_zenith, solar_azimuth, surface_tilt, surface_azimuth) expected_cut_point_coords = [[[14.17820455, -0.90992559], [0., 0.]], [[19.17820455, 4.09007441], [0., 0.]]] cut_pt_coords = [ cut_point.as_array for cut_point in ordered_pvarray.ts_ground.cut_point_coords ] np.testing.assert_almost_equal(expected_cut_point_coords, cut_pt_coords)
def test_run_fast_mode_back_shading(params): """Test that PV engine works for timeseries fast mode and float inputs, and when there's large direct shading on the back surface. Value is very close to loop-style fast mode""" params.update({ 'gcr': 0.6, 'surface_azimuth': 270, 'surface_tilt': 120, 'solar_zenith': 70. }) # Prepare some engine inputs irradiance_model = HybridPerezOrdered() pvarray = OrderedPVArray.init_from_dict( params, param_names=irradiance_model.params) fast_mode_pvrow_index = 1 fast_mode_segment_index = 0 # Create engine object eng = PVEngine(pvarray, irradiance_model=irradiance_model, fast_mode_pvrow_index=fast_mode_pvrow_index, fast_mode_segment_index=fast_mode_segment_index) # Irradiance inputs timestamps = dt.datetime(2019, 6, 11, 11) DNI = 1000. DHI = 100. # Expected values expected_qinc = 683.537153 # Fit engine eng.fit(timestamps, DNI, DHI, params['solar_zenith'], params['solar_azimuth'], params['surface_tilt'], params['surface_azimuth'], params['rho_ground']) def fn_report(pvarray): return (pvarray.ts_pvrows[1].back.get_param_weighted('qinc')) # By providing segment index qinc = eng.run_fast_mode(fn_build_report=fn_report) # Check results np.testing.assert_allclose(qinc, expected_qinc) # Without providing segment index eng.fast_mode_segment_index = None qinc = eng.run_fast_mode(fn_build_report=fn_report) # Check results np.testing.assert_allclose(qinc, expected_qinc)
def test_engine_w_faoi_fn_in_irradiance_vfcalcs(params, pvmodule_canadian): """Run PV engine calcs with faoi functions for AOI losses""" # Irradiance inputs timestamps = dt.datetime(2019, 6, 11, 11) DNI = 1000. DHI = 100. pvarray = OrderedPVArray.init_from_dict(params) # create faoi function faoi_fn = faoi_fn_from_pvlib_sandia(pvmodule_canadian) # create vf_calculator with faoi function vfcalculator = VFCalculator(faoi_fn_front=faoi_fn, faoi_fn_back=faoi_fn) # create irradiance model with faoi function irradiance_model = HybridPerezOrdered(faoi_fn_front=faoi_fn, faoi_fn_back=faoi_fn) eng = PVEngine(pvarray, irradiance_model=irradiance_model, vf_calculator=vfcalculator) # Make sure aoi methods are available assert eng.vf_calculator.vf_aoi_methods is not None # Fit engine eng.fit(timestamps, DNI, DHI, params['solar_zenith'], params['solar_azimuth'], params['surface_tilt'], params['surface_azimuth'], params['rho_ground']) # Run timestep pvarray = eng.run_full_mode(fn_build_report=lambda pvarray: pvarray) # Checks np.testing.assert_almost_equal( pvarray.ts_pvrows[0].front.get_param_weighted('qinc'), 1110.1164773159298) np.testing.assert_almost_equal( pvarray.ts_pvrows[1].front.get_param_weighted('qinc'), 1110.595903991) np.testing.assert_almost_equal( pvarray.ts_pvrows[2].front.get_param_weighted('qinc'), 1112.37717553) np.testing.assert_almost_equal( pvarray.ts_pvrows[1].back.get_param_weighted('qinc'), 116.49050349491208) # Check absorbed irradiance: calculated using faoi functions np.testing.assert_almost_equal( pvarray.ts_pvrows[2].front.get_param_weighted('qabs'), [1109.1180884]) np.testing.assert_almost_equal( pvarray.ts_pvrows[1].back.get_param_weighted('qabs'), [114.2143503])
def test_create_engine_with_rho_init(params, pvmodule_canadian): """Check that can create PV engine with rho initialization from faoi functions""" # Create inputs pvarray = OrderedPVArray.init_from_dict(params) irradiance = HybridPerezOrdered(rho_front=None, rho_back=None) faoi_fn = faoi_fn_from_pvlib_sandia(pvmodule_canadian) vfcalculator = VFCalculator(faoi_fn_front=faoi_fn, faoi_fn_back=faoi_fn) # Create engine engine = PVEngine.with_rho_initialization(pvarray, vfcalculator, irradiance) # Check that rho values are the ones calculated np.testing.assert_allclose(engine.irradiance.rho_front, 0.02900688) np.testing.assert_allclose(engine.irradiance.rho_back, 0.02900688)
def test_run_fast_mode_segments(params): """Test that PV engine works for timeseries fast mode and float inputs. Value is very close to loop-like fast mode""" # Discretize middle PV row's back side params.update({'cut': {1: {'back': 5}}}) # Prepare some engine inputs irradiance_model = HybridPerezOrdered() pvarray = OrderedPVArray.init_from_dict( params, param_names=irradiance_model.params) fast_mode_pvrow_index = 1 fast_mode_segment_index = 2 # Create engine object eng = PVEngine(pvarray, irradiance_model=irradiance_model, fast_mode_pvrow_index=fast_mode_pvrow_index, fast_mode_segment_index=fast_mode_segment_index) # Irradiance inputs timestamps = dt.datetime(2019, 6, 11, 11) DNI = 1000. DHI = 100. # Fit engine eng.fit(timestamps, DNI, DHI, params['solar_zenith'], params['solar_azimuth'], params['surface_tilt'], params['surface_azimuth'], params['rho_ground']) # Checks np.testing.assert_almost_equal(eng.irradiance.direct['front_illum_pvrow'], DNI) # Define report function to grab irradiance from PV row segment def fn_report(pvarray): return (pvarray.ts_pvrows[1].back.list_segments[2].get_param_weighted( 'qinc')) # Expected value for middle segment qinc_expected = 116.572594 # Run fast mode for specific segment qinc_segment = eng.run_fast_mode(fn_build_report=fn_report) # Check results np.testing.assert_allclose(qinc_segment, qinc_expected) # Without providing segment index: the value should be the same as above eng.fast_mode_segment_index = None qinc_segment = eng.run_fast_mode(fn_build_report=fn_report) # Check results np.testing.assert_allclose(qinc_segment, qinc_expected)
def test_hybridperez_ordered_transparency_spacing_back(params_irr): """Check that module transparency and spacing params are applied correctly in HybridPerezOrdered""" params_irr.update({'surface_azimuth': 270, 'surface_tilt': 160}) # Apply irradiance model DNI = 1000. DHI = 100. ts = dt.datetime(2019, 6, 14, 11) irr_parameters = { 'horizon_band_angle': 6.5, 'module_transparency': 0.1, 'module_spacing_ratio': 0.1 } irr_model = HybridPerezOrdered(**irr_parameters) irr_model.fit(ts, DNI, DHI, params_irr['solar_zenith'], params_irr['solar_azimuth'], params_irr['surface_tilt'], params_irr['surface_azimuth'], params_irr['rho_ground']) # Create, fit, and transform pv array pvarray = OrderedPVArray.fit_from_dict_of_scalars( params_irr, param_names=IsotropicOrdered.params) irr_model.transform(pvarray) gnd_seg = pvarray.ts_ground pvrow_back = pvarray.ts_pvrows[1].back # check that back is shaded assert pvrow_back.shaded_length > 0 # Run some checks on gnd surfaces surf_gnd_shaded = gnd_seg.shaded.list_ts_surfaces[0] surf_gnd_illum = gnd_seg.illum.list_ts_surfaces[0] np.testing.assert_allclose( surf_gnd_illum.get_param('circumsolar') * 0.19, surf_gnd_shaded.get_param('circumsolar')) np.testing.assert_allclose( surf_gnd_illum.get_param('direct') * 0.19, surf_gnd_shaded.get_param('direct')) # Run check on pvrow surfaces surf_pvrow_shaded = ( pvrow_back.list_segments[0].shaded.list_ts_surfaces[0]) surf_pvrow_illum = (pvrow_back.list_segments[0].illum.list_ts_surfaces[0]) np.testing.assert_allclose( surf_pvrow_illum.get_param('direct') * 0.19, surf_pvrow_shaded.get_param('direct')) np.testing.assert_allclose( surf_pvrow_illum.get_param('circumsolar') * 0.19, surf_pvrow_shaded.get_param('circumsolar'))
def test_run_fast_mode_isotropic(params): """Test that PV engine works for timeseries fast mode and float inputs, and using the isotropic irradiance model""" # Prepare some engine inputs irradiance_model = IsotropicOrdered() pvarray = OrderedPVArray.init_from_dict( params, param_names=irradiance_model.params) fast_mode_pvrow_index = 1 fast_mode_segment_index = 0 # Create engine object eng = PVEngine(pvarray, irradiance_model=irradiance_model, fast_mode_pvrow_index=fast_mode_pvrow_index, fast_mode_segment_index=fast_mode_segment_index) # Irradiance inputs timestamps = dt.datetime(2019, 6, 11, 11) DNI = 1000. DHI = 100. # Fit engine eng.fit(timestamps, DNI, DHI, params['solar_zenith'], params['solar_azimuth'], params['surface_tilt'], params['surface_azimuth'], params['rho_ground']) # Checks np.testing.assert_almost_equal(eng.irradiance.direct['front_illum_pvrow'], DNI) # Expected value qinc_expected = 122.73453 # Run fast mode qinc = eng.run_fast_mode( fn_build_report=lambda pvarray: (pvarray.ts_pvrows[1] .back.get_param_weighted('qinc'))) # Check results np.testing.assert_allclose(qinc, qinc_expected) # Without providing segment index eng.fast_mode_segment_index = None qinc = eng.run_fast_mode( fn_build_report=lambda pvarray: (pvarray.ts_pvrows[1] .back.get_param_weighted('qinc'))) # Check results np.testing.assert_allclose(qinc, qinc_expected)
def test_ordered_pvarray_gnd_pvrow_shadow_casting_back_n_seg( params_direct_shading): """Back direct shading with discretized pv row sides""" params_direct_shading.update({ 'cut': { 1: { 'back': 7 } }, 'solar_azimuth': 270, 'surface_tilt': 120 }) # Test front shading on right ordered_pvarray = OrderedPVArray.fit_from_dict_of_scalars( params_direct_shading) _check_ground_surfaces(ordered_pvarray.ts_ground, 2, 4) # Shading length should be identical as in previous test for front surface, # but now with back surface np.testing.assert_almost_equal( ordered_pvarray.ts_pvrows[2].back.shaded_length, 0.33333333333333254) np.testing.assert_almost_equal( ordered_pvarray.ts_pvrows[1].back.shaded_length, 0.33333333333333254) np.testing.assert_almost_equal( ordered_pvarray.ts_pvrows[0].back.shaded_length, 0.) np.testing.assert_almost_equal( ordered_pvarray.ts_pvrows[2].front.shaded_length, 0.) np.testing.assert_almost_equal( ordered_pvarray.ts_pvrows[1].front.shaded_length, 0.) np.testing.assert_almost_equal( ordered_pvarray.ts_pvrows[0].front.shaded_length, 0.) # Test individual segments center_row = ordered_pvarray.ts_pvrows[1] list_pvsegments = center_row.back.list_segments fully_shaded_segment = list_pvsegments[-1] partial_shaded_segment = list_pvsegments[-2] np.testing.assert_allclose(fully_shaded_segment.illum.length, 0) np.testing.assert_almost_equal(fully_shaded_segment.shaded.length, list_pvsegments[0].length) assert partial_shaded_segment.shaded.length > 0 assert partial_shaded_segment.illum.length > 0 sum_lengths = (partial_shaded_segment.illum.length + partial_shaded_segment.shaded.length) np.testing.assert_almost_equal(sum_lengths, list_pvsegments[0].length)
def test_ts_surfaces_side_of_cut_point(params): """Check that can successfully call list ts surfaces on side of cut point""" pvarray = OrderedPVArray.fit_from_dict_of_scalars(params) # For first pvrow list_left = pvarray.ts_ground.ts_surfaces_side_of_cut_point('left', 0) list_right = pvarray.ts_ground.ts_surfaces_side_of_cut_point('right', 0) assert len(list_left) == 7 assert len(list_right) == 21 # For second pv row list_left = pvarray.ts_ground.ts_surfaces_side_of_cut_point('left', 1) list_right = pvarray.ts_ground.ts_surfaces_side_of_cut_point('right', 1) assert len(list_left) == 14 assert len(list_right) == 14 # For rightmost pv row list_left = pvarray.ts_ground.ts_surfaces_side_of_cut_point('left', 2) list_right = pvarray.ts_ground.ts_surfaces_side_of_cut_point('right', 2) assert len(list_left) == 21 assert len(list_right) == 7
def test_ordered_pvarray_gnd_pvrow_shadow_casting_right(params_direct_shading): """Front direct shading with the sun on the right side""" # Test front shading on right ordered_pvarray = OrderedPVArray.fit_from_dict_of_scalars( params_direct_shading) _check_ground_surfaces(ordered_pvarray.ts_ground, 2, 4) np.testing.assert_almost_equal( ordered_pvarray.ts_pvrows[0].front.shaded_length, 0.33333333333333254) np.testing.assert_almost_equal( ordered_pvarray.ts_pvrows[1].front.shaded_length, 0.33333333333333254) np.testing.assert_almost_equal( ordered_pvarray.ts_pvrows[2].front.shaded_length, 0.) np.testing.assert_almost_equal( ordered_pvarray.ts_pvrows[0].back.shaded_length, 0.) np.testing.assert_almost_equal( ordered_pvarray.ts_pvrows[1].back.shaded_length, 0.) np.testing.assert_almost_equal( ordered_pvarray.ts_pvrows[2].back.shaded_length, 0.)
def test_discretization_ordered_pvarray(discr_params): """Test that the number of segments and surfaces is correct when discretizing the PV rows""" pvarray = OrderedPVArray.fit_from_dict_of_scalars(discr_params) pvrows = pvarray.ts_pvrows # Check the transformed geometries assert len(pvrows[0].front.list_segments) == 5 assert len(pvrows[0].back.list_segments) == 1 assert len(pvrows[1].back.list_segments) == 3 assert len(pvrows[1].front.list_segments) == 2 # Check the timeseries geometries assert pvarray.ts_pvrows[0].n_ts_surfaces == 12 assert pvarray.ts_pvrows[1].n_ts_surfaces == 10 assert pvarray.ts_pvrows[2].n_ts_surfaces == 4 assert pvarray.ts_ground.n_ts_surfaces == 28 assert pvarray.n_ts_surfaces == 54 # Check that the list of ts surfaces match assert len(set(pvarray.all_ts_surfaces)) == 54
def test_ordered_pvarray_from_dict_w_direct_shading(): """Test that can successfully create ordered pvarray from parameters dict, and that the axis azimuth convention works correctly (via normal vector), and check that ground surfaces make sense. Came from direct shading case where ground shadows not correctly created """ # Specify array parameters params = { 'n_pvrows': 3, 'pvrow_height': 1, 'pvrow_width': 1, 'axis_azimuth': 0., 'gcr': 0.4, 'rho_front_pvrow': 0.01, 'rho_back_pvrow': 0.03, 'solar_zenith': 74, 'solar_azimuth': 229, 'surface_tilt': 50, 'surface_azimuth': 270 } pvarray = OrderedPVArray.fit_from_dict_of_scalars(params) # Test that ground is created successfully assert isinstance(pvarray.ts_ground, TsGround) np.testing.assert_equal(pvarray.ts_ground.length, MAX_X_GROUND - MIN_X_GROUND) np.testing.assert_equal(pvarray.ts_pvrows[0].length, 2) np.testing.assert_equal(pvarray.ts_pvrows[1].length, 2) np.testing.assert_equal(pvarray.ts_pvrows[2].length, 2) # Test the front and back sides assert len(pvarray.ts_pvrows) == 3 np.testing.assert_array_equal(pvarray.ts_pvrows[0].front.n_vector, -pvarray.ts_pvrows[0].back.n_vector) np.testing.assert_allclose(pvarray.ts_pvrows[1].front.shaded_length, 0.05979874) assert pvarray.gcr == params['gcr'] assert np.abs(pvarray.rotation_vec) == params['surface_tilt'] assert pvarray.ts_pvrows[0].front.n_vector[0] < 0 distance_between_pvrows = \ pvarray.ts_pvrows[1].centroid.x - pvarray.ts_pvrows[0].centroid.x assert distance_between_pvrows == 2.5
def test_check_tilt_zero_discontinuity(): """ Before version 1.5.2, surface_tilt=0 with certain combinations of surface_azimuth and axis_azimuth showed anomolous behavior where the irradiance at zero tilt was significantly different from the irradiance at very small but nonzero tilts. Additionally, the calculated VF matrix could have values outside [0, 1]. See GH #125 """ # expected value calculated for surface_tilt=0.001, so should # not be significantly different from result for surface_tilt=0 rear_qinc_expected = 76.10 timestamps = np.array([dt.datetime(2019, 6, 1, 10)]) solar_azimuth = np.array([135]) solar_zenith = np.array([45]) dni = np.array([200]) dhi = np.array([400]) albedo = np.array([0.2]) surface_tilt = np.array([0.0]) # the discontinuity did not occur for all combinations of # (surface_azimuth, axis_azimuth), so test all four "primary" pairs: for surface_azimuth in [90, 270]: surface_azimuth = np.array([surface_azimuth]) for axis_azimuth in [0, 180]: params = dict(n_pvrows=3, axis_azimuth=axis_azimuth, pvrow_height=2, pvrow_width=1, gcr=0.4) pvarray = OrderedPVArray.init_from_dict(params) eng = PVEngine(pvarray) eng.fit(timestamps, dni, dhi, solar_zenith, solar_azimuth, surface_tilt, surface_azimuth, albedo) # Run simulation and get output eng.run_full_mode() out = pvarray.ts_pvrows[1].back.get_param_weighted('qinc') assert np.all(pvarray.ts_vf_matrix >= 0) assert np.all(pvarray.ts_vf_matrix <= 1) assert rear_qinc_expected == pytest.approx(out[0], abs=1e-2)
def test_hybridperez_horizon_shading_ts(): # Base params params = { 'n_pvrows': 3, 'pvrow_height': 1, 'pvrow_width': 1, 'axis_azimuth': 0., 'gcr': 0.3 } # Timeseries inputs df_inputs = pd.DataFrame({ 'solar_zenith': [70., 80., 80., 70., 10.], 'solar_azimuth': [270., 90., 270., 90., 90.], 'surface_tilt': [20., 10., 20., 30., 0.], 'surface_azimuth': [270., 270., 90., 90., 90.] }) # Initialize and fit pv array pvarray = OrderedPVArray.init_from_dict(params) # Fit pv array to timeseries data pvarray.fit(df_inputs.solar_zenith, df_inputs.solar_azimuth, df_inputs.surface_tilt, df_inputs.surface_azimuth) # irradiance model model = HybridPerezOrdered(horizon_band_angle=15.) pvrow_idx = 1 centroid_coords = ( pvarray.ts_pvrows[pvrow_idx].back.list_segments[0].coords.centroid) tilted_to_left = pvarray.rotation_vec > 0 horizon_pct_shading = model._calculate_horizon_shading_pct_ts( pvarray.ts_pvrows, centroid_coords, pvrow_idx, tilted_to_left, is_back_side=True) # Check that values stay consistent expected_pct_shading = np.array( [17.163813, 8.667262, 17.163813, 25.317135, 0.]) np.testing.assert_allclose(expected_pct_shading, horizon_pct_shading)
def test_isotropic_ordered_transparency_spacing(params_irr): """Check that module transparency and spacing params are applied correctly in IsotropicOrdered""" # Apply irradiance model DNI = 1000. DHI = 100. ts = dt.datetime(2019, 6, 14, 11) irr_parameters = {'module_transparency': 0.1, 'module_spacing_ratio': 0.1} irr_model = IsotropicOrdered(**irr_parameters) irr_model.fit(ts, DNI, DHI, params_irr['solar_zenith'], params_irr['solar_azimuth'], params_irr['surface_tilt'], params_irr['surface_azimuth'], params_irr['rho_ground']) # Create, fit, and transform pv array pvarray = OrderedPVArray.fit_from_dict_of_scalars( params_irr, param_names=IsotropicOrdered.params) irr_model.transform(pvarray) gnd_seg = pvarray.ts_ground pvrow_front = pvarray.ts_pvrows[1].front # check that front is shaded assert pvrow_front.shaded_length > 0 # Run some checks surf_gnd_shaded = gnd_seg.shaded.list_ts_surfaces[0] surf_gnd_illum = gnd_seg.illum.list_ts_surfaces[0] np.testing.assert_allclose(surf_gnd_illum.get_param('direct') * 0.19, surf_gnd_shaded.get_param('direct')) # Run check on pvrow surfaces surf_pvrow_shaded = (pvrow_front.list_segments[0] .shaded.list_ts_surfaces[0]) surf_pvrow_illum = (pvrow_front.list_segments[0] .illum.list_ts_surfaces[0]) np.testing.assert_allclose(surf_pvrow_illum.get_param('direct') * 0.19, surf_pvrow_shaded.get_param('direct'))