diff --git a/examples/power_example.ipynb b/examples/power_example.ipynb index c3bc1ceb1..8997df4ab 100644 --- a/examples/power_example.ipynb +++ b/examples/power_example.ipynb @@ -151,10 +151,12 @@ ], "source": [ "# Read in time-series data of voltage (V) and current (I)\n", - "power_data = pd.read_csv('data/power/2020224_181521_PowRaw.csv',skip_blank_lines=True,index_col='Time_UTC')\n", + "power_data = pd.read_csv(\n", + " \"data/power/2020224_181521_PowRaw.csv\", skip_blank_lines=True, index_col=\"Time_UTC\"\n", + ")\n", "\n", "# Convert the time index to type \"datetime\"\n", - "power_data.index=pd.to_datetime(power_data.index)\n", + "power_data.index = pd.to_datetime(power_data.index)\n", "\n", "# Display the data\n", "power_data.head()" @@ -498,8 +500,8 @@ } ], "source": [ - "# Finally we can compute the total harmonic current distortion as a percentage \n", - "THCD = power.quality.total_harmonic_current_distortion(h_s) \n", + "# Finally we can compute the total harmonic current distortion as a percentage\n", + "THCD = power.quality.total_harmonic_current_distortion(h_s)\n", "THCD" ] } diff --git a/mhkit/power/characteristics.py b/mhkit/power/characteristics.py index eb511df22..d9ca8ec39 100644 --- a/mhkit/power/characteristics.py +++ b/mhkit/power/characteristics.py @@ -3,9 +3,8 @@ import numpy as np from scipy.signal import hilbert -def instantaneous_frequency(um, time_dimension="", to_pandas=True): -def instantaneous_frequency(um): +def instantaneous_frequency(um, time_dimension="", to_pandas=True): """ Calculates instantaneous frequency of measured voltage @@ -15,7 +14,7 @@ def instantaneous_frequency(um): Measured voltage (V) indexed by time time_dimension: string (optional) - Name of the xarray dimension corresponding to time. If not supplied, + Name of the xarray dimension corresponding to time. If not supplied, defaults to the first dimension. Does not affect pandas input. to_pandas: bool (Optional) @@ -26,23 +25,27 @@ def instantaneous_frequency(um): frequency: pandas DataFrame or xarray Dataset Frequency of the measured voltage (Hz) indexed by time with signal name columns - """ + """ if not isinstance(um, (pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset)): - raise TypeError('um must be of type pd.Series, pd.DataFrame, ' + - f'xr.DataArray, or xr.Dataset. Got {type(um)}') - if not isinstance(to_pandas, bool): raise TypeError( - f'to_pandas must be of type bool. Got: {type(to_pandas)}') + "um must be of type pd.Series, pd.DataFrame, " + + f"xr.DataArray, or xr.Dataset. Got {type(um)}" + ) + if not isinstance(to_pandas, bool): + raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}") if not isinstance(time_dimension, str): raise TypeError( - f'time_dimension must be of type bool. Got: {type(time_dimension)}') + f"time_dimension must be of type bool. Got: {type(time_dimension)}" + ) # Convert input to xr.Dataset - um = _convert_to_dataset(um, 'data') - - if time_dimension != '' and time_dimension not in um.coords: - raise ValueError('time_dimension was supplied but is not a dimension ' - + f'of um. Got {time_dimension}') + um = _convert_to_dataset(um, "data") + + if time_dimension != "" and time_dimension not in um.coords: + raise ValueError( + "time_dimension was supplied but is not a dimension " + + f"of um. Got {time_dimension}" + ) # Get the dimension of interest if time_dimension == "": @@ -50,7 +53,9 @@ def instantaneous_frequency(um): # Calculate time step if isinstance(um.coords[time_dimension].values[0], np.datetime64): - t = (um[time_dimension] - np.datetime64('1970-01-01 00:00:00'))/np.timedelta64(1, 's') + t = ( + um[time_dimension] - np.datetime64("1970-01-01 00:00:00") + ) / np.timedelta64(1, "s") else: t = um[time_dimension] dt = np.diff(t) @@ -60,16 +65,21 @@ def instantaneous_frequency(um): for var in um.data_vars: f = hilbert(um[var]) instantaneous_phase = np.unwrap(np.angle(f)) - instantaneous_frequency = np.diff(instantaneous_phase)/(2.0*np.pi) * (1/dt) + instantaneous_frequency = ( + np.diff(instantaneous_phase) / (2.0 * np.pi) * (1 / dt) + ) frequency = frequency.assign({var: (time_dimension, instantaneous_frequency)}) - frequency = frequency.assign_coords({time_dimension: um.coords[time_dimension].values[0:-1]}) + frequency = frequency.assign_coords( + {time_dimension: um.coords[time_dimension].values[0:-1]} + ) if to_pandas: frequency = frequency.to_pandas() return frequency + def dc_power(voltage, current, to_pandas=True): """ Calculates DC power from voltage and current @@ -91,43 +101,54 @@ def dc_power(voltage, current, to_pandas=True): DC power [W] from each channel and gross power indexed by time """ if not isinstance(voltage, (pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset)): - raise TypeError('voltage must be of type pd.Series, pd.DataFrame, ' + - f'xr.DataArray, or xr.Dataset. Got {type(voltage)}') + raise TypeError( + "voltage must be of type pd.Series, pd.DataFrame, " + + f"xr.DataArray, or xr.Dataset. Got {type(voltage)}" + ) if not isinstance(current, (pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset)): - raise TypeError('current must be of type pd.Series, pd.DataFrame, ' + - f'xr.DataArray, or xr.Dataset. Got {type(current)}') - if not isinstance(to_pandas, bool): raise TypeError( - f'to_pandas must be of type bool. Got: {type(to_pandas)}') + "current must be of type pd.Series, pd.DataFrame, " + + f"xr.DataArray, or xr.Dataset. Got {type(current)}" + ) + if not isinstance(to_pandas, bool): + raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}") # Convert inputs to xr.Dataset - voltage = _convert_to_dataset(voltage, 'voltage') - current = _convert_to_dataset(current, 'current') + voltage = _convert_to_dataset(voltage, "voltage") + current = _convert_to_dataset(current, "current") # Check that sizes are the same - if not (voltage.sizes == current.sizes and len(voltage.data_vars) == len(current.data_vars)): - raise ValueError('current and voltage must have the same shape') + if not ( + voltage.sizes == current.sizes + and len(voltage.data_vars) == len(current.data_vars) + ): + raise ValueError("current and voltage must have the same shape") P = xr.Dataset() gross = None - + # Multiply current and voltage variables together, in order they're assigned - for i, (current_var, voltage_var) in enumerate(zip(current.data_vars,voltage.data_vars)): - temp = current[current_var]*voltage[voltage_var] - P = P.assign({f'{i}': temp}) + for i, (current_var, voltage_var) in enumerate( + zip(current.data_vars, voltage.data_vars) + ): + temp = current[current_var] * voltage[voltage_var] + P = P.assign({f"{i}": temp}) if gross is None: gross = temp else: gross = gross + temp - P = P.assign({'Gross': gross}) + P = P.assign({"Gross": gross}) if to_pandas: P = P.to_dataframe() return P -def ac_power_three_phase(voltage, current, power_factor, line_to_line=False, to_pandas=True): + +def ac_power_three_phase( + voltage, current, power_factor, line_to_line=False, to_pandas=True +): """ Calculates magnitude of active AC power from line to neutral voltage and current @@ -139,7 +160,7 @@ def ac_power_three_phase(voltage, current, power_factor, line_to_line=False, to_ current: pandas Series, pandas DataFrame, xarray DataArray, or xarray Dataset Measured three phase current [A] indexed by time - power_factor: float + power_factor: float Power factor for the efficiency of the system line_to_line: bool (Optional) @@ -151,36 +172,40 @@ def ac_power_three_phase(voltage, current, power_factor, line_to_line=False, to_ Returns -------- P: pandas DataFrame or xarray Dataset - Magnitude of active AC power [W] indexed by time with Power column + Magnitude of active AC power [W] indexed by time with Power column """ if not isinstance(voltage, (pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset)): - raise TypeError('voltage must be of type pd.Series, pd.DataFrame, ' + - f'xr.DataArray, or xr.Dataset. Got {type(voltage)}') + raise TypeError( + "voltage must be of type pd.Series, pd.DataFrame, " + + f"xr.DataArray, or xr.Dataset. Got {type(voltage)}" + ) if not isinstance(current, (pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset)): - raise TypeError('current must be of type pd.Series, pd.DataFrame, ' + - f'xr.DataArray, or xr.Dataset. Got {type(current)}') - if not isinstance(line_to_line, bool): raise TypeError( - f'line_to_line must be of type bool. Got: {type(line_to_line)}') + "current must be of type pd.Series, pd.DataFrame, " + + f"xr.DataArray, or xr.Dataset. Got {type(current)}" + ) + if not isinstance(line_to_line, bool): + raise TypeError(f"line_to_line must be of type bool. Got: {type(line_to_line)}") if not isinstance(to_pandas, bool): - raise TypeError( - f'to_pandas must be of type bool. Got: {type(to_pandas)}') + raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}") # Convert inputs to xr.Dataset - voltage = _convert_to_dataset(voltage, 'voltage') - current = _convert_to_dataset(current, 'current') + voltage = _convert_to_dataset(voltage, "voltage") + current = _convert_to_dataset(current, "current") # Check that sizes are the same if not len(voltage.data_vars) == 3: - raise ValueError('voltage must have three columns') + raise ValueError("voltage must have three columns") if not len(current.data_vars) == 3: - raise ValueError('current must have three columns') + raise ValueError("current must have three columns") if not current.sizes == voltage.sizes: - raise ValueError('current and voltage must be of the same size') + raise ValueError("current and voltage must be of the same size") - power = dc_power(voltage, current, to_pandas=False)['Gross'] - power.name = 'Power' - power = power.to_dataset() # force xr.DataArray to be consistently in xr.Dataset format + power = dc_power(voltage, current, to_pandas=False)["Gross"] + power.name = "Power" + power = ( + power.to_dataset() + ) # force xr.DataArray to be consistently in xr.Dataset format P = np.abs(power) * power_factor if line_to_line: @@ -191,60 +216,65 @@ def ac_power_three_phase(voltage, current, power_factor, line_to_line=False, to_ return P -def _convert_to_dataset(data, name='data'): + +def _convert_to_dataset(data, name="data"): """ Converts the given data to an xarray.Dataset. - + This function is designed to handle inputs that can be either a pandas DataFrame, a pandas Series, an xarray DataArray, or an xarray Dataset. It ensures that the output is consistently an xarray.Dataset. - + Parameters ---------- data: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset - The data to be converted. - + The data to be converted. + name: str (Optional) The name to assign to the data variable in case the input is an xarray DataArray without a name. Default value is 'data'. - + Returns ------- xarray.Dataset The input data converted to an xarray.Dataset. If the input is already an xarray.Dataset, it is returned as is. - + Examples -------- >>> df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]}) >>> ds = _convert_to_dataset(df) >>> type(ds) - + >>> series = pd.Series([1, 2, 3], name='C') >>> ds = _convert_to_dataset(series) >>> type(ds) - + >>> data_array = xr.DataArray([1, 2, 3]) >>> ds = _convert_to_dataset(data_array, name='D') >>> type(ds) """ if not isinstance(data, (pd.DataFrame, pd.Series, xr.DataArray, xr.Dataset)): - raise TypeError("Input data must be of type pandas.DataFrame, pandas.Series, " - "xarray.DataArray, or xarray.Dataset") + raise TypeError( + "Input data must be of type pandas.DataFrame, pandas.Series, " + "xarray.DataArray, or xarray.Dataset" + ) if not isinstance(name, str): - raise TypeError("The 'name' parameter must be a string") + raise TypeError("The 'name' parameter must be a string") - # Takes data that could be pd.DataFrame, pd.Series, xr.DataArray, or + # Takes data that could be pd.DataFrame, pd.Series, xr.DataArray, or # xr.Dataset and converts it to xr.Dataset if isinstance(data, (pd.DataFrame, pd.Series)): data = data.to_xarray() if isinstance(data, xr.DataArray): if data.name is None: - data.name = name # xr.DataArray.to_dataset() breaks if the data variable is unnamed + data.name = ( + name # xr.DataArray.to_dataset() breaks if the data variable is unnamed + ) data = data.to_dataset() return data diff --git a/mhkit/power/quality.py b/mhkit/power/quality.py index 87f4111b5..2421fac3a 100644 --- a/mhkit/power/quality.py +++ b/mhkit/power/quality.py @@ -4,6 +4,7 @@ import xarray as xr from .characteristics import _convert_to_dataset + # This group of functions are to be used for power quality assessments def harmonics(x, freq, grid_freq, to_pandas=True): """ @@ -26,49 +27,50 @@ def harmonics(x, freq, grid_freq, to_pandas=True): Returns -------- harmonics: pandas DataFrame or xarray Dataset - Amplitude of the time-series data harmonics indexed by the harmonic + Amplitude of the time-series data harmonics indexed by the harmonic frequency with signal name columns """ if not isinstance(x, (pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset)): - raise TypeError('x must be of type pd.Series, pd.DataFrame, ' + - f'xr.DataArray, or xr.Dataset. Got {type(x)}') + raise TypeError( + "x must be of type pd.Series, pd.DataFrame, " + + f"xr.DataArray, or xr.Dataset. Got {type(x)}" + ) if not isinstance(freq, (float, int)): - raise TypeError(f'freq must be of type float or integer. Got {type(freq)}') + raise TypeError(f"freq must be of type float or integer. Got {type(freq)}") if grid_freq not in [50, 60]: - raise ValueError(f'grid_freq must be either 50 or 60. Got {grid_freq}') + raise ValueError(f"grid_freq must be either 50 or 60. Got {grid_freq}") if not isinstance(to_pandas, bool): - raise TypeError( - f'to_pandas must be of type bool. Got {type(to_pandas)}') + raise TypeError(f"to_pandas must be of type bool. Got {type(to_pandas)}") # Convert input to xr.Dataset - x = _convert_to_dataset(x, 'data') + x = _convert_to_dataset(x, "data") + + sample_spacing = 1.0 / freq - sample_spacing = 1./freq - # Loop through all variables in x harmonics = xr.Dataset() for var in x.data_vars: dataarray = x[var] dataarray = dataarray.to_numpy() - + frequency_bin_centers = fftpack.fftfreq(len(dataarray), d=sample_spacing) harmonics_amplitude = np.abs(np.fft.fft(dataarray, axis=0)) - - harmonics = harmonics.assign({var: (['frequency'], harmonics_amplitude)}) - harmonics = harmonics.assign_coords({'frequency': frequency_bin_centers}) - harmonics = harmonics.sortby('frequency') + + harmonics = harmonics.assign({var: (["frequency"], harmonics_amplitude)}) + harmonics = harmonics.assign_coords({"frequency": frequency_bin_centers}) + harmonics = harmonics.sortby("frequency") if grid_freq == 60: hz = np.arange(0, 3060, 5) elif grid_freq == 50: hz = np.arange(0, 2570, 5) - harmonics = harmonics.reindex({'frequency': hz}, method='nearest') - harmonics = harmonics/len(x[var])*2 - + harmonics = harmonics.reindex({"frequency": hz}, method="nearest") + harmonics = harmonics / len(x[var]) * 2 + if to_pandas: harmonics = harmonics.to_pandas() @@ -82,13 +84,13 @@ def harmonic_subgroups(harmonics, grid_freq, frequency_dimension="", to_pandas=T Parameters ---------- harmonics: pandas Series, pandas DataFrame, xarray DataArray, or xarray Dataset - Harmonic amplitude indexed by the harmonic frequency + Harmonic amplitude indexed by the harmonic frequency grid_freq: int Value indicating if the power supply is 50 or 60 Hz. Options = 50 or 60 frequency_dimension: string (optional) - Name of the xarray dimension corresponding to frequency. If not supplied, + Name of the xarray dimension corresponding to frequency. If not supplied, defaults to the first dimension. Does not affect pandas input. to_pandas: bool (Optional) @@ -97,56 +99,60 @@ def harmonic_subgroups(harmonics, grid_freq, frequency_dimension="", to_pandas=T Returns -------- harmonic_subgroups: pandas DataFrame or xarray Dataset - Harmonic subgroups indexed by harmonic frequency + Harmonic subgroups indexed by harmonic frequency with signal name columns """ if not isinstance(harmonics, (pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset)): - raise TypeError('harmonics must be of type pd.Series, pd.DataFrame, ' + - f'xr.DataArray, or xr.Dataset. Got {type(harmonics)}') - + raise TypeError( + "harmonics must be of type pd.Series, pd.DataFrame, " + + f"xr.DataArray, or xr.Dataset. Got {type(harmonics)}" + ) + if grid_freq not in [50, 60]: - raise ValueError(f'grid_freq must be either 50 or 60. Got {grid_freq}') + raise ValueError(f"grid_freq must be either 50 or 60. Got {grid_freq}") if not isinstance(to_pandas, bool): - raise TypeError( - f'to_pandas must be of type bool. Got: {type(to_pandas)}') + raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}") if not isinstance(frequency_dimension, str): raise TypeError( - f'frequency_dimension must be of type bool. Got: {type(frequency_dimension)}') + f"frequency_dimension must be of type bool. Got: {type(frequency_dimension)}" + ) # Convert input to xr.Dataset - harmonics = _convert_to_dataset(harmonics, 'harmonics') - - if frequency_dimension != '' and frequency_dimension not in harmonics.coords: - raise ValueError('frequency_dimension was supplied but is not a dimension ' - + f'of harmonics. Got {frequency_dimension}') + harmonics = _convert_to_dataset(harmonics, "harmonics") + + if frequency_dimension != "" and frequency_dimension not in harmonics.coords: + raise ValueError( + "frequency_dimension was supplied but is not a dimension " + + f"of harmonics. Got {frequency_dimension}" + ) if grid_freq == 60: hz = np.arange(0, 3060, 60) else: hz = np.arange(0, 2550, 50) - + # Sort input data index if frequency_dimension == "": frequency_dimension = list(harmonics.dims)[0] harmonics = harmonics.sortby(frequency_dimension) - + # Loop through all variables in harmonics harmonic_subgroups = xr.Dataset() for var in harmonics.data_vars: dataarray = harmonics[var] subgroup = np.zeros(np.size(hz)) - - for ihz in np.arange(0,len(hz)): - n = hz[ihz] + + for ihz in np.arange(0, len(hz)): + n = hz[ihz] ind = dataarray.indexes[frequency_dimension].get_loc(n) - - data_subset = dataarray.isel({frequency_dimension:[ind-1, ind, ind+1]}) - subgroup[ihz] = (data_subset**2).sum()**0.5 - - harmonic_subgroups = harmonic_subgroups.assign({var: (['frequency'], subgroup)}) - harmonic_subgroups = harmonic_subgroups.assign_coords({'frequency': hz}) + + data_subset = dataarray.isel({frequency_dimension: [ind - 1, ind, ind + 1]}) + subgroup[ihz] = (data_subset**2).sum() ** 0.5 + + harmonic_subgroups = harmonic_subgroups.assign({var: (["frequency"], subgroup)}) + harmonic_subgroups = harmonic_subgroups.assign_coords({"frequency": hz}) if to_pandas: harmonic_subgroups = harmonic_subgroups.to_pandas() @@ -154,7 +160,9 @@ def harmonic_subgroups(harmonics, grid_freq, frequency_dimension="", to_pandas=T return harmonic_subgroups -def total_harmonic_current_distortion(harmonics_subgroup, frequency_dimension="", to_pandas=True): +def total_harmonic_current_distortion( + harmonics_subgroup, frequency_dimension="", to_pandas=True +): """ Calculates the total harmonic current distortion (THC) based on IEC/TS 62600-30 @@ -164,7 +172,7 @@ def total_harmonic_current_distortion(harmonics_subgroup, frequency_dimension="" Subgrouped current harmonics indexed by harmonic frequency frequency_dimension: string (optional) - Name of the xarray dimension corresponding to frequency. If not supplied, + Name of the xarray dimension corresponding to frequency. If not supplied, defaults to the first dimension. Does not affect pandas input. to_pandas: bool (Optional) @@ -173,37 +181,45 @@ def total_harmonic_current_distortion(harmonics_subgroup, frequency_dimension="" Returns -------- THCD: pd.DataFrame or xarray Dataset - Total harmonic current distortion indexed by signal name with THCD column + Total harmonic current distortion indexed by signal name with THCD column """ - if not isinstance(harmonics_subgroup, (pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset)): - raise TypeError('harmonics_subgroup must be of type pd.Series, pd.DataFrame, ' + - f'xr.DataArray, or xr.Dataset. Got {type(harmonics_subgroup)}') + if not isinstance( + harmonics_subgroup, (pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset) + ): + raise TypeError( + "harmonics_subgroup must be of type pd.Series, pd.DataFrame, " + + f"xr.DataArray, or xr.Dataset. Got {type(harmonics_subgroup)}" + ) if not isinstance(to_pandas, bool): - raise TypeError( - f'to_pandas must be of type bool. Got: {type(to_pandas)}') + raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}") if not isinstance(frequency_dimension, str): raise TypeError( - f'frequency_dimension must be of type bool. Got: {type(frequency_dimension)}') + f"frequency_dimension must be of type bool. Got: {type(frequency_dimension)}" + ) # Convert input to xr.Dataset - harmonics_subgroup = _convert_to_dataset(harmonics_subgroup, 'harmonics') + harmonics_subgroup = _convert_to_dataset(harmonics_subgroup, "harmonics") + + if frequency_dimension != "" and frequency_dimension not in harmonics.coords: + raise ValueError( + "frequency_dimension was supplied but is not a dimension " + + f"of harmonics. Got {frequency_dimension}" + ) - if frequency_dimension != '' and frequency_dimension not in harmonics.coords: - raise ValueError('frequency_dimension was supplied but is not a dimension ' - + f'of harmonics. Got {frequency_dimension}') - if frequency_dimension == "": frequency_dimension = list(harmonics_subgroup.dims)[0] - harmonics_sq = harmonics_subgroup.isel({frequency_dimension: slice(2,50)})**2 + harmonics_sq = harmonics_subgroup.isel({frequency_dimension: slice(2, 50)}) ** 2 harmonics_sum = harmonics_sq.sum() - THCD = (np.sqrt(harmonics_sum)/harmonics_subgroup.isel({frequency_dimension: 1}))*100 - + THCD = ( + np.sqrt(harmonics_sum) / harmonics_subgroup.isel({frequency_dimension: 1}) + ) * 100 + if isinstance(THCD, xr.DataArray): - THCD.name = ['THCD'] - + THCD.name = ["THCD"] + if to_pandas: THCD = THCD.to_pandas() @@ -217,13 +233,13 @@ def interharmonics(harmonics, grid_freq, frequency_dimension="", to_pandas=True) Parameters ----------- harmonics: pandas Series, pandas DataFrame, xarray DataArray, or xarray Dataset - Harmonic amplitude indexed by the harmonic frequency + Harmonic amplitude indexed by the harmonic frequency grid_freq: int Value indicating if the power supply is 50 or 60 Hz. Options = 50 or 60 frequency_dimension: string (optional) - Name of the xarray dimension corresponding to frequency. If not supplied, + Name of the xarray dimension corresponding to frequency. If not supplied, defaults to the first dimension. Does not affect pandas input. to_pandas: bool (Optional) @@ -235,22 +251,25 @@ def interharmonics(harmonics, grid_freq, frequency_dimension="", to_pandas=True) Interharmonics groups """ if not isinstance(harmonics, (pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset)): - raise TypeError('harmonics must be of type pd.Series, pd.DataFrame, ' + - f'xr.DataArray, or xr.Dataset. Got {type(harmonics)}') + raise TypeError( + "harmonics must be of type pd.Series, pd.DataFrame, " + + f"xr.DataArray, or xr.Dataset. Got {type(harmonics)}" + ) if grid_freq not in [50, 60]: - raise ValueError(f'grid_freq must be either 50 or 60. Got {grid_freq}') + raise ValueError(f"grid_freq must be either 50 or 60. Got {grid_freq}") if not isinstance(to_pandas, bool): - raise TypeError( - f'to_pandas must be of type bool. Got: {type(to_pandas)}') + raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}") # Convert input to xr.Dataset - harmonics = _convert_to_dataset(harmonics, 'harmonics') + harmonics = _convert_to_dataset(harmonics, "harmonics") - if frequency_dimension != '' and frequency_dimension not in harmonics.coords: - raise ValueError('frequency_dimension was supplied but is not a dimension ' - + f'of harmonics. Got {frequency_dimension}') + if frequency_dimension != "" and frequency_dimension not in harmonics.coords: + raise ValueError( + "frequency_dimension was supplied but is not a dimension " + + f"of harmonics. Got {frequency_dimension}" + ) if grid_freq == 60: hz = np.arange(0, 3060, 60) @@ -268,19 +287,19 @@ def interharmonics(harmonics, grid_freq, frequency_dimension="", to_pandas=True) dataarray = harmonics[var] subset = np.zeros(np.size(hz)) - for ihz in np.arange(0,len(hz)): + for ihz in np.arange(0, len(hz)): n = hz[ihz] ind = dataarray.indexes[frequency_dimension].get_loc(n) if grid_freq == 60: - data = dataarray.isel({frequency_dimension:slice(ind+1,ind+11)}) - subset[ihz] = (data**2).sum()**0.5 + data = dataarray.isel({frequency_dimension: slice(ind + 1, ind + 11)}) + subset[ihz] = (data**2).sum() ** 0.5 else: - data = dataarray.isel({frequency_dimension:slice(ind+1,ind+7)}) - subset[ihz] = (data**2).sum()**0.5 + data = dataarray.isel({frequency_dimension: slice(ind + 1, ind + 7)}) + subset[ihz] = (data**2).sum() ** 0.5 - interharmonics = interharmonics.assign({var: (['frequency'], subset)}) - interharmonics = interharmonics.assign_coords({'frequency': hz}) + interharmonics = interharmonics.assign({var: (["frequency"], subset)}) + interharmonics = interharmonics.assign_coords({"frequency": hz}) if to_pandas: interharmonics = interharmonics.to_pandas() diff --git a/mhkit/tests/power/test_power.py b/mhkit/tests/power/test_power.py index fce5a1aaf..e218d149f 100644 --- a/mhkit/tests/power/test_power.py +++ b/mhkit/tests/power/test_power.py @@ -1,4 +1,3 @@ - from os.path import abspath, dirname, join, normpath, relpath import mhkit.power as power import pandas as pd @@ -16,7 +15,7 @@ class TestDevice(unittest.TestCase): def setUpClass(self): self.t = 600 fs = 1000 - self.samples = np.linspace(0, self.t, int(fs*self.t), endpoint=False) + self.samples = np.linspace(0, self.t, int(fs * self.t), endpoint=False) self.frequency = 60 self.freq_array = np.ones(len(self.samples)) * 60 harmonics_int = np.arange(0, 60 * 60, 5) @@ -45,65 +44,66 @@ def tearDownClass(self): def test_harmonics_sine_wave_pandas(self): current = pd.Series(self.signal, index=self.samples) harmonics = power.quality.harmonics(current, 1000, self.frequency) - - for i, j in zip(harmonics['data'].values, self.harmonics_vals): + + for i, j in zip(harmonics["data"].values, self.harmonics_vals): self.assertAlmostEqual(i, j, 1) - + def test_harmonics_sine_wave_xarray(self): - current = xr.DataArray(data=self.signal, - dims='index', - coords={'index':self.samples}) + current = xr.DataArray( + data=self.signal, dims="index", coords={"index": self.samples} + ) harmonics = power.quality.harmonics(current, 1000, self.frequency) - - for i, j in zip(harmonics['data'].values, self.harmonics_vals): + + for i, j in zip(harmonics["data"].values, self.harmonics_vals): self.assertAlmostEqual(i, j, 1) def test_harmonic_subgroup_sine_wave_pandas(self): - harmonics = pd.DataFrame(self.harmonics_vals, - index=self.harmonics_int) + harmonics = pd.DataFrame(self.harmonics_vals, index=self.harmonics_int) hsg = power.quality.harmonic_subgroups(harmonics, self.frequency) - + for i, j in zip(hsg.values, self.harmonic_groups): self.assertAlmostEqual(i[0], j, 1) def test_harmonic_subgroup_sine_wave_xarray(self): - harmonics = xr.Dataset(data_vars={'harmonics':(['index'], self.harmonics_vals)}, - coords={'index':self.harmonics_int}) + harmonics = xr.Dataset( + data_vars={"harmonics": (["index"], self.harmonics_vals)}, + coords={"index": self.harmonics_int}, + ) hsg = power.quality.harmonic_subgroups(harmonics, self.frequency) - + for i, j in zip(hsg.values, self.harmonic_groups): self.assertAlmostEqual(i[0], j, 1) def test_TCHD_sine_wave_pandas(self): - harmonics = pd.DataFrame(self.harmonics_vals, - index=self.harmonics_int) + harmonics = pd.DataFrame(self.harmonics_vals, index=self.harmonics_int) hsg = power.quality.harmonic_subgroups(harmonics, self.frequency) TCHD = power.quality.total_harmonic_current_distortion(hsg) - + self.assertAlmostEqual(TCHD.values[0], self.thcd) def test_TCHD_sine_wave_xarray(self): - harmonics = xr.Dataset(data_vars={'harmonics':(['index'],self.harmonics_vals)}, - coords={'index':self.harmonics_int}) + harmonics = xr.Dataset( + data_vars={"harmonics": (["index"], self.harmonics_vals)}, + coords={"index": self.harmonics_int}, + ) hsg = power.quality.harmonic_subgroups(harmonics, self.frequency) TCHD = power.quality.total_harmonic_current_distortion(hsg) - + self.assertAlmostEqual(TCHD.values[0], self.thcd) def test_interharmonics_sine_wave_pandas(self): - harmonics = pd.DataFrame(self.harmonics_vals, - index=self.harmonics_int) - inter_harmonics = power.quality.interharmonics( - harmonics, self.frequency) + harmonics = pd.DataFrame(self.harmonics_vals, index=self.harmonics_int) + inter_harmonics = power.quality.interharmonics(harmonics, self.frequency) for i, j in zip(inter_harmonics.values, self.interharmonic): self.assertAlmostEqual(i[0], j, 1) - + def test_interharmonics_sine_wave_xarray(self): - harmonics = xr.Dataset(data_vars={'harmonics':(['index'],self.harmonics_vals)}, - coords={'index':self.harmonics_int}) - inter_harmonics = power.quality.interharmonics( - harmonics, self.frequency) + harmonics = xr.Dataset( + data_vars={"harmonics": (["index"], self.harmonics_vals)}, + coords={"index": self.harmonics_int}, + ) + inter_harmonics = power.quality.interharmonics(harmonics, self.frequency) for i, j in zip(inter_harmonics.values, self.interharmonic): self.assertAlmostEqual(i[0], j, 1) @@ -124,66 +124,62 @@ def test_instfreq_xarray(self): self.assertAlmostEqual(i[0], self.frequency, 1) def test_dc_power_pandas(self): - current = pd.DataFrame(self.current_data, columns=['A1','A2','A3']) - voltage = pd.DataFrame(self.voltage_data, columns=['V1','V2','V3']) - + current = pd.DataFrame(self.current_data, columns=["A1", "A2", "A3"]) + voltage = pd.DataFrame(self.voltage_data, columns=["V1", "V2", "V3"]) + P = power.characteristics.dc_power(voltage, current) - P_test = (self.current_data*self.voltage_data).sum() - self.assertEqual(P.sum()['Gross'], P_test) - - P = power.characteristics.dc_power(voltage['V1'], current['A1']) - P_test = (self.current_data[:,0]*self.voltage_data[:,0]).sum() - self.assertEqual(P.sum()['Gross'], P_test) + P_test = (self.current_data * self.voltage_data).sum() + self.assertEqual(P.sum()["Gross"], P_test) + + P = power.characteristics.dc_power(voltage["V1"], current["A1"]) + P_test = (self.current_data[:, 0] * self.voltage_data[:, 0]).sum() + self.assertEqual(P.sum()["Gross"], P_test) def test_dc_power_xarray(self): - current = pd.DataFrame(self.current_data, columns=['A1','A2','A3']) - voltage = pd.DataFrame(self.voltage_data, columns=['V1','V2','V3']) + current = pd.DataFrame(self.current_data, columns=["A1", "A2", "A3"]) + voltage = pd.DataFrame(self.voltage_data, columns=["V1", "V2", "V3"]) current = current.to_xarray() voltage = voltage.to_xarray() - + P = power.characteristics.dc_power(voltage, current) - P_test = (self.current_data*self.voltage_data).sum() - self.assertEqual(P.sum()['Gross'], P_test) - - P = power.characteristics.dc_power(voltage['V1'], current['A1']) - P_test = (self.current_data[:,0]*self.voltage_data[:,0]).sum() - self.assertEqual(P.sum()['Gross'], P_test) + P_test = (self.current_data * self.voltage_data).sum() + self.assertEqual(P.sum()["Gross"], P_test) + + P = power.characteristics.dc_power(voltage["V1"], current["A1"]) + P_test = (self.current_data[:, 0] * self.voltage_data[:, 0]).sum() + self.assertEqual(P.sum()["Gross"], P_test) def test_ac_power_three_phase_pandas(self): - current = pd.DataFrame(self.current_data, columns=['A1','A2','A3']) - voltage = pd.DataFrame(self.voltage_data, columns=['V1','V2','V3']) + current = pd.DataFrame(self.current_data, columns=["A1", "A2", "A3"]) + voltage = pd.DataFrame(self.voltage_data, columns=["V1", "V2", "V3"]) P1 = power.characteristics.ac_power_three_phase(voltage, current, 1, False) P1b = power.characteristics.ac_power_three_phase(voltage, current, 0.5, False) P2 = power.characteristics.ac_power_three_phase(voltage, current, 1, True) P2b = power.characteristics.ac_power_three_phase(voltage, current, 0.5, True) - P_test = (self.current_data*self.voltage_data).sum() + P_test = (self.current_data * self.voltage_data).sum() self.assertEqual(P1.sum().iloc[0], P_test) - self.assertEqual(P1b.sum().iloc[0], P_test/2) - self.assertAlmostEqual(P2.sum().iloc[0], P_test*np.sqrt(3), 2) - self.assertAlmostEqual(P2b.sum().iloc[0], P_test*np.sqrt(3)/2, 2) + self.assertEqual(P1b.sum().iloc[0], P_test / 2) + self.assertAlmostEqual(P2.sum().iloc[0], P_test * np.sqrt(3), 2) + self.assertAlmostEqual(P2b.sum().iloc[0], P_test * np.sqrt(3) / 2, 2) def test_ac_power_three_phase_xarray(self): - current = pd.DataFrame(self.current_data, columns=['A1','A2','A3']) - voltage = pd.DataFrame(self.voltage_data, columns=['V1','V2','V3']) + current = pd.DataFrame(self.current_data, columns=["A1", "A2", "A3"]) + voltage = pd.DataFrame(self.voltage_data, columns=["V1", "V2", "V3"]) current = current.to_xarray() voltage = voltage.to_xarray() - P1 = power.characteristics.ac_power_three_phase( - voltage, current, 1, False) - P1b = power.characteristics.ac_power_three_phase( - voltage, current, 0.5, False) - P2 = power.characteristics.ac_power_three_phase( - voltage, current, 1, True) - P2b = power.characteristics.ac_power_three_phase( - voltage, current, 0.5, True) + P1 = power.characteristics.ac_power_three_phase(voltage, current, 1, False) + P1b = power.characteristics.ac_power_three_phase(voltage, current, 0.5, False) + P2 = power.characteristics.ac_power_three_phase(voltage, current, 1, True) + P2b = power.characteristics.ac_power_three_phase(voltage, current, 0.5, True) - P_test = (self.current_data*self.voltage_data).sum() + P_test = (self.current_data * self.voltage_data).sum() self.assertEqual(P1.sum().iloc[0], P_test) - self.assertEqual(P1b.sum().iloc[0], P_test/2) - self.assertAlmostEqual(P2.sum().iloc[0], P_test*np.sqrt(3), 2) - self.assertAlmostEqual(P2b.sum().iloc[0], P_test*np.sqrt(3)/2, 2) + self.assertEqual(P1b.sum().iloc[0], P_test / 2) + self.assertAlmostEqual(P2.sum().iloc[0], P_test * np.sqrt(3), 2) + self.assertAlmostEqual(P2b.sum().iloc[0], P_test * np.sqrt(3) / 2, 2) if __name__ == "__main__":