def test__calc_loss_system_startend(side, energy_data_outage_single): # data starts or ends in an outage (meter_power, meter_energy, inverter_power, expected_power, _, _) = energy_data_outage_single if side == 'start': # an outage all day on the 1st, so technically the outage extends to # sunrise on the 2nd, but it doesn't wrap around to the previous dusk date = '2019-01-01' expected_start = '2019-01-01 00:00' expected_end = '2019-01-02 07:45' idx = 0 else: # last day doesn't have a "sunrise on the next day", so it doesn't # wrap around date = '2019-01-15' expected_start = '2019-01-14 17:15' expected_end = '2019-01-15 23:45' idx = -1 meter_power.loc[date] = 0 meter_energy.loc[date] = 0 inverter_power.loc[date] = 0 aa = AvailabilityAnalysis(meter_power, inverter_power, meter_energy, expected_power) aa.run() outage_info = aa.outage_info actual_start = outage_info['start'].iloc[idx].strftime('%Y-%m-%d %H:%M') actual_end = outage_info['end'].iloc[idx].strftime('%Y-%m-%d %H:%M') assert actual_start == expected_start assert actual_end == expected_end
def availability_analysis_object(energy_data_outage_single): (meter_power, meter_energy, inverter_power, expected_power, _, _) = energy_data_outage_single aa = AvailabilityAnalysis(meter_power, inverter_power, meter_energy, expected_power) aa.run() return aa
def test_calc_loss_subsystem_threshold(dummy_power_data): # test low_threshold parameter. # negative threshold means the inverter is never classified as offline inverter_power, meter_power, dummy = dummy_power_data aa = AvailabilityAnalysis(meter_power, inverter_power, energy_cumulative=dummy, power_expected=dummy) aa._calc_loss_subsystem(low_threshold=-1, relative_sizes=None, power_system_limit=None) actual_loss = aa.loss_subsystem assert actual_loss.sum() == 0
def test__calc_loss_system_multiple(energy_data): # test multiple outages (meter_power, meter_energy, inverter_power, expected_power, _, _) = energy_data date = '2019-01-08' meter_power.loc[date] = 0 meter_energy.loc[date] = 0 inverter_power.loc[date] = 0 aa = AvailabilityAnalysis(meter_power, inverter_power, meter_energy, expected_power) aa.run() outage_info = aa.outage_info assert len(outage_info) == 2
def test_calc_loss_subsystem_limit(dummy_power_data): # test system_power_limit parameter. # set it unrealistically low to verify it constrains the loss. # real max power is 2, real max loss is 1, so setting limit=1.5 sets max # loss to 0.5 inverter_power, meter_power, dummy = dummy_power_data aa = AvailabilityAnalysis(meter_power, inverter_power, energy_cumulative=dummy, power_expected=dummy) aa._calc_loss_subsystem(low_threshold=None, relative_sizes=None, power_system_limit=1.5) actual_loss = aa.loss_subsystem assert actual_loss.max() == pytest.approx(0.5, abs=0.01)
def test__calc_loss_subsystem(power_data): # implicitly sweeps across the parameter space because power_data is # parametrized inverter_power, meter_power, expected_loss = power_data # these values aren't relevant to this test, but the timeseries are # checked for timestamp consistency so just pass in dummy data: energy_cumulative = pd.Series(np.nan, meter_power.index) power_expected = pd.Series(np.nan, meter_power.index) aa = AvailabilityAnalysis(meter_power, inverter_power, energy_cumulative=energy_cumulative, power_expected=power_expected) aa._calc_loss_subsystem(low_threshold=None, relative_sizes=None, power_system_limit=None) actual_loss = aa.loss_subsystem # pandas <1.1.0 as no atol/rtol parameters, so just use np.round instead: assert_series_equal(np.round(expected_loss, 1), np.round(actual_loss, 1))
def test__calc_loss_system(energy_data): # test single outage (meter_power, meter_energy, inverter_power, expected_power, expected_loss, expected_type) = energy_data aa = AvailabilityAnalysis(meter_power, inverter_power, meter_energy, expected_power) aa.run() outage_info = aa.outage_info # only one outage assert len(outage_info) == 1 outage_info = outage_info.iloc[0, :] # outage was correctly classified: assert outage_info['type'] == expected_type # outage loss is accurate to 5% of the true value: assert outage_info['loss'] == pytest.approx(expected_loss, rel=0.05)
def test__calc_loss_system_quantiles(energy_data_comms_single): # exercise the quantiles parameter (meter_power, meter_energy, inverter_power, expected_power, _, _) = energy_data_comms_single # first make sure it gets picked up as a comms outage with normal quantiles aa = AvailabilityAnalysis(meter_power, inverter_power, meter_energy, expected_power) aa.run(quantiles=(0.01, 0.99)) outage_info = aa.outage_info assert outage_info['type'].values[0] == 'comms' # set the lower quantile very high so that the comms outage gets # classified as a real outage aa = AvailabilityAnalysis(meter_power, inverter_power, meter_energy, expected_power) aa.run(quantiles=(0.999, 0.9999)) outage_info = aa.outage_info assert outage_info['type'].values[0] == 'real'
def test_availability_analysis_index_mismatch(energy_data_outage_single): # exercise the timeseries index check (meter_power, meter_energy, inverter_power, expected_power, _, _) = energy_data_outage_single base_kwargs = { 'power_system': meter_power, 'power_subsystem': inverter_power, 'energy_cumulative': meter_energy, 'power_expected': expected_power, } # verify that the check works for any of the timeseries inputs for key in base_kwargs.keys(): kwargs = base_kwargs.copy() value = kwargs.pop(key) value_shortened = value.iloc[1:] kwargs[key] = value_shortened with pytest.raises(ValueError, match='timeseries indexes must match'): aa = AvailabilityAnalysis(**kwargs)
def test_calc_loss_subsystem_relative_sizes(difficult_data): # test that manually passing in relative_sizes improves the results # for pathological datasets with tons of downtime invs, meter, expected, relative_sizes = difficult_data aa = AvailabilityAnalysis(meter, invs, energy_cumulative=meter.cumsum() / 4, power_expected=expected) # verify that results are bad by default -- without the correction, the # two inverters are weighted equally, so availability will be 50% when # only one is online aa.run(rollup_period='d') ava = aa.results['availability'] assert np.allclose(ava.iloc[0:4], 0.5) assert np.allclose(ava.iloc[4], 1.0) # now use the correct relative_sizes aa.run(rollup_period='d', relative_sizes=relative_sizes) ava = aa.results['availability'] assert np.allclose(ava.iloc[0:2], 0.75) assert np.allclose(ava.iloc[2:4], 0.25) assert np.allclose(ava.iloc[4], 1.0)
def test_plot_norun(dummy_power_data): _, _, dummy = dummy_power_data aa = AvailabilityAnalysis(dummy, dummy, dummy, dummy) # don't call run, just go straight to plot with pytest.raises(TypeError, match="No results to plot"): aa.plot()