Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
14fed08
Power: Rewrite ac_power_three_phase in MATLAB
simmsa Mar 12, 2025
0fef802
Power: Rewrite dc_power in MATLAB
simmsa Mar 12, 2025
19111bb
Power: Rewrite instantaneous_frequency in MATLAB
simmsa Mar 12, 2025
9866bc2
Power: Rewrite ac_power_three_phase to use parse
simmsa Jun 10, 2025
73909a2
Power: Use `parse` to verify dc_power args
simmsa Jun 10, 2025
1079da8
Power: Rewrite harmonic_subgroups in MATLAB
simmsa Jun 10, 2025
954ff9a
Power: Rewrite instantaneous_frequency in MATLAB
simmsa Jun 10, 2025
3189f29
Power: Rewrite harmonics in MATLAB
simmsa Jun 10, 2025
6732319
Power: Rewrite interharmonics in MATLAB
simmsa Jun 10, 2025
5c7ad41
Power: Rewrite total_harmonic_current_distortion in MATLAB
simmsa Jun 10, 2025
59a1d39
Power: Add 'LineToLine' key for ac_power_three_phase tests
simmsa Jun 10, 2025
6b4095f
Merge remote-tracking branch 'upstream/develop' into feat_convert_pow…
simmsa Jul 1, 2025
2bb27b6
Power: Remove unnecessary time vector matching
simmsa Jul 17, 2025
efcbdcf
Power: Fix, properly set time_vector
simmsa Jul 17, 2025
3fc0f0a
Power: Warn user if delta t is outside of tolerance
simmsa Aug 7, 2025
1dd3211
Power: Use scipy to calculate hilbert if MATLAB hilbert is not found
simmsa Aug 7, 2025
b2c4efa
Power: Fix syntax in dt warning
simmsa Aug 7, 2025
8af597e
Merge remote-tracking branch 'upstream/develop' into feat_convert_pow…
simmsa Sep 28, 2026
1361756
Signal: Define scipy based hilbert function
simmsa Sep 30, 2026
eaf30e7
Power: Standardize docstring formatting and style
simmsa Sep 30, 2026
25c04df
Tests: Add power module and example to MATLAB only tests
simmsa Sep 30, 2026
8937377
Tests: Define SignalProcessing hilbert tests
simmsa Sep 30, 2026
138c528
Power: Use MHKiT-Python equation for ac_power_three_phase calculation
simmsa Sep 30, 2026
e12269c
Power: Refactor instantaneous_frequency to use mhkit_hilbert
simmsa Sep 30, 2026
9921f50
Power: Clean up gen_test_data docstring
simmsa Sep 30, 2026
5b0b205
Power: Format docstring in flicker_ufic_workflow
simmsa Sep 30, 2026
260b5bb
Power: Refactor harmonics using a more explainable implementation
simmsa Sep 30, 2026
43db065
Power: Define mhkit_validate_sample_rate_hz to check and validate sam…
simmsa Sep 30, 2026
deb0e61
Power: Use mhkit_validate_sample_rate_hz in instantaneous_frequency
simmsa Sep 30, 2026
c129c88
Tests: Define multi harmonic test
simmsa Sep 30, 2026
3ef29e1
Tests: Define tests for harmonics warnings and errors
simmsa Sep 30, 2026
1194b09
Power: Do not change ac_power_three_phase public api
simmsa Sep 30, 2026
b84fd08
Tests: Use all test cases in test_gen_test_data instead of one at random
simmsa Sep 30, 2026
f1aa5e2
Tests: Run every ZCD flicker scenario and phase angle test in test_fl…
simmsa Sep 30, 2026
890e6fe
Tests: Split STFT flicker scenario into separate test that skips with…
simmsa Sep 30, 2026
aff349e
Examples: Add 3 phase voltage and current plots to power example
simmsa Sep 30, 2026
c28705c
Examples: Clean up power example text and plot label formatting
simmsa Sep 30, 2026
4b5e628
Examples: Render power example
simmsa Sep 30, 2026
4dc2ad8
Power: Convent error messages to MHKiT convention
simmsa Sep 30, 2026
f5bacfd
Power: Validate function input and output using arguments
simmsa Sep 30, 2026
60e76fb
Power: Remove 50,60 check that is done by arguments
simmsa Sep 30, 2026
8daae2f
Utils: Add column vector helpers from wave refactor
simmsa Sep 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
294 changes: 180 additions & 114 deletions examples/power_example.html

Large diffs are not rendered by default.

253 changes: 160 additions & 93 deletions examples/power_example.m

Large diffs are not rendered by default.

Binary file modified examples/power_example.mlx
Binary file not shown.
178 changes: 91 additions & 87 deletions mhkit/power/characteristics/ac_power_three_phase.m
Original file line number Diff line number Diff line change
@@ -1,110 +1,114 @@
function P=ac_power_three_phase(voltage,current,power_factor,varargin)
function P = ac_power_three_phase(voltage, current, power_factor, line_to_line)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Calculates the real power from three phase ac voltage and current.
% Calculate magnitude of active AC power from line to neutral voltage and current
%
% Parameters
% ------------
% voltage: Time series of all three measured voltages [V]
% Pandas data frame
% To make a pandas data frame from user supplied frequency and spectra
% use py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas(time,voltage)
%
% OR
%
% structure of form:
% voltage.voltage : matrix of all three phases
%
% voltage.time : time vector
%
% current: Time series of all three measured current [A]
% Pandas data frame
% To make a pandas data frame from user supplied frequency and spectra
% use py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas(time,current)
%
% OR
%
% structure of form:
% current.current : matrix of all three phases
%
% current.time : time vector
%
% power_factor : float
% power factor for the system
%
% line_to_line: bool (Optional)
% set true if the given voltage measurement is line_to_line
% voltage: structure or matrix
% voltage.voltage : Three-phase voltage measurements [V] (n_time x 3 matrix)
% Each row represents one time step, columns are phases A, B, C
% voltage.time : Time vector (n_time x 1) (if time series data)
% current: structure or matrix
% current.current : Three-phase current measurements [A] (n_time x 3 matrix)
% Each row represents one time step, columns are phases A, B, C
% current.time : Time vector (n_time x 1) (if time series data)
% power_factor: numeric scalar
% Power factor for the efficiency of the system [dimensionless]
% line_to_line: logical (optional)
% Set to true if voltage measurements are line-to-line (default: false)
%
% Returns
% ---------
% P: Structure
%
% P: structure
% P.power : Magnitude of active AC power [W]
% P.time : Time vector
%
% P.power [W]
%
% P.time
%
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

py.importlib.import_module('mhkit');
arguments (Input)
voltage {mustBeA(voltage, ["struct", "numeric"])}
current {mustBeA(current, ["struct", "numeric"])}
power_factor (1,1) {mustBeNumeric}
line_to_line (1,1) {mustBeNumericOrLogical, mustBeMember(line_to_line, [0, 1])} = false
end

py.importlib.import_module('mhkit_python_utils');
arguments (Output)
P struct
end

if (isa(voltage,'py.pandas.core.frame.DataFrame')~=1)
if (isstruct(voltage)==1)
x=size(voltage.voltage);
li=py.list();
if x(2)==3
for i = 1:x(2)
app=py.list(double(voltage.voltage(:,i)));
li=py.mhkit_python_utils.pandas_dataframe.lis(li,app);
line_to_line = logical(line_to_line);

end
voltage=py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas(double(voltage.time(:,1)),li,int32(x(2)));
elseif x(2)==1
ME = MException('MATLAB:ac_power_three_phase','Three lines of voltage measurements are required');
throw(ME);
end
% Validate power factor is between 0 and 1
if power_factor < 0 || power_factor > 1
error('MHKiT:ac_power_three_phase:InvalidInput', 'power_factor must be between 0 and 1 (inclusive). Received: %.3f', power_factor);
end

% Extract data and time vectors
if isstruct(voltage)
% Validate input structures have required fields
if ~isfield(voltage, 'voltage')
error('MHKiT:ac_power_three_phase:InvalidInput', 'voltage structure must contain voltage field');
end
if ~isfield(voltage, 'time')
error('MHKiT:ac_power_three_phase:InvalidInput', 'voltage structure must contain time field');
end
voltage_data = voltage.voltage;
voltage_time = voltage.time;
else
ME = MException('MATLAB:ac_power_three_phase','voltage needs to be a structure or Pandas dataframe, use py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas to create one or a structure');
throw(ME);
voltage_data = voltage;
voltage_time = (1:size(voltage, 1))'; % Default time vector
end
end

if (isa(current,'py.pandas.core.frame.DataFrame')~=1)
if (isstruct(current)==1)
x=size(current.current);
li=py.list();
if x(2)==3
for i = 1:x(2)
app=py.list(double(current.current(:,i)));
li=py.mhkit_python_utils.pandas_dataframe.lis(li,app);

end
current=py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas(double(current.time(:,1)),li,int32(x(2)));
elseif x(2)==1
ME = MException('MATLAB:ac_power_three_phase','Three lines of voltage measurements are required');
throw(ME);

if isstruct(current)
% Validate input structures have required fields
if ~isfield(current, 'current')
error('MHKiT:ac_power_three_phase:InvalidInput', 'current structure must contain current field');
end

if ~isfield(current, 'time')
error('MHKiT:ac_power_three_phase:InvalidInput', 'current structure must contain time field');
end
current_data = current.current;
current_time = current.time;
else
ME = MException('MATLAB:ac_power_three_phase','voltage needs to be a structure or Pandas dataframe, use py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas to create one or a structure');
throw(ME);
current_data = current;
current_time = (1:size(current, 1))'; % Default time vector
end
end

if nargin == 4
p_pd=py.mhkit.power.characteristics.ac_power_three_phase(voltage,current,power_factor,pyargs('line_to_line',varargin{1}));
elseif nargin ==3
p_pd=py.mhkit.power.characteristics.ac_power_three_phase(voltage,current,power_factor);
else
ME = MException('MATLAB:ac_power_three_phase','incorrect number of input arguments');
throw(ME);
end
% Validate voltage has three columns
if size(voltage_data, 2) ~= 3
error('MHKiT:ac_power_three_phase:InvalidInput', 'voltage must have three columns for three-phase measurements');
end

% Validate current has three columns
if size(current_data, 2) ~= 3
error('MHKiT:ac_power_three_phase:InvalidInput', 'current must have three columns for three-phase measurements');
end

% Validate dimensions match
if ~isequal(size(voltage_data), size(current_data))
error('MHKiT:ac_power_three_phase:InvalidInput', 'voltage and current must have the same dimensions');
end

% Validate time vectors match
if ~isequal(voltage_time, current_time)
error('MHKiT:ac_power_three_phase:InvalidInput', 'voltage and current time vectors must match');
end

% After validation, use the time vector from voltage
time_vector = voltage_time;

P.power=double(py.array.array('d',py.numpy.nditer(p_pd.values)));
P.time=double(py.array.array('d',py.numpy.nditer(p_pd.index)));
% Instantaneous power summed across the three phases, as in MHKiT-Python
active_power = abs(sum(voltage_data .* current_data, 2)) * power_factor;

if line_to_line
active_power = active_power * sqrt(3);
end

% Create output structure
P = struct();
P.power = active_power;
P.time = time_vector;

end
162 changes: 66 additions & 96 deletions mhkit/power/characteristics/dc_power.m
Original file line number Diff line number Diff line change
@@ -1,110 +1,80 @@
function P=dc_power(voltage,current)
function power_dc = dc_power(voltage, current)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Calculates the real power from DC voltage and current.
%
% Calculate DC power from voltage and current
%
% Parameters
% ------------
% voltage: Time series of measured voltages [V]
% Pandas data frame
% To make a pandas data frame from user supplied frequency and spectra
% use py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas(time,voltage)
%
% OR
%
% structure of form:
% voltage.voltage : matrix or vector
%
% voltage.time : time vector
%
% current: Time series of current [A]
% Pandas data frame
% To make a pandas data frame from user supplied frequency and spectra
% use py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas(time,current)
%
% OR
%
% structure of form:
% current.current : matrix or vector
%
% current.time : time vector
% voltage: structure
% voltage.voltage : Matrix of voltage measurements [V]
% Each row is a time point, each column is a channel
% voltage.time : Time vector
% current: structure
% current.current : Matrix of current measurements [A]
% Each row is a time point, each column is a channel
% current.time : Time vector
%
% Returns
% ---------
% P: Structure
% power_dc: structure
% power_dc.power : Matrix of calculated power for each channel [W]
% Same dimensions as input voltage/current
% power_dc.gross : Vector of gross power (sum of all channels) [W]
% power_dc.time : Time vector
%
%
% P.power [W]
%
% P.gross: gross power from all lines [W]
%
% P.time
%
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

py.importlib.import_module('mhkit');

py.importlib.import_module('mhkit_python_utils');

if (isa(voltage,'py.pandas.core.frame.DataFrame')~=1)
if (isstruct(voltage)==1)
x=size(voltage.voltage);
li=py.list();
if x(2)> 1
for i = 1:x(2)
app=py.list(double(voltage.voltage(:,i)));
li=py.mhkit_python_utils.pandas_dataframe.lis(li,app);

end
voltage=py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas(double(voltage.time(:,1)),li,int32(x(2)));
elseif x(2)==1
voltage=py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas(double(voltage.time(:,1)),voltage.voltage,int32(x(2)));
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

else
ME = MException('MATLAB:dc_power','voltage needs to be a structure or Pandas dataframe, use py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas to create one or create a structure');
throw(ME);
arguments (Input)
voltage struct
current struct
end
end

if (isa(current,'py.pandas.core.frame.DataFrame')~=1)
if (isstruct(current)==1)
x=size(current.current);
li=py.list();
if x(2)==3
for i = 1:x(2)
app=py.list(double(current.current(:,i)));
li=py.mhkit_python_utils.pandas_dataframe.lis(li,app);

end
current=py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas(double(current.time(:,1)),li,int32(x(2)));
elseif x(2)==1
current=py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas(double(current.time(:,1)),current.current,int32(x(2)));
end

else
ME = MException('MATLAB:dc_power','voltage needs to be a structure or Pandas dataframe, use py.mhkit_python_utils.pandas_dataframe.spectra_to_pandas to create one or create a structure');
throw(ME);
arguments (Output)
power_dc struct
end
end

p_pd=py.mhkit.power.characteristics.dc_power(voltage,current);
vals=double(py.array.array('d',py.numpy.nditer(p_pd.values)));
sha=cell(p_pd.values.shape);
x=int64(sha{1,1});
y=int64(sha{1,2});
vals=reshape(vals,[x,y]);
si=size(vals);
% for i=1:si(2)
% if i == si(2)
% P.gross=vals(:,i);
% else
% P.power{i}=vals(:,i);
% %P.power{i}=[P.power{i}];
% end
% end
P.power=vals(:,1:end-1);
P.gross=vals(:,end);
P.time=double(py.array.array('d',py.numpy.nditer(p_pd.index))).';

% Validate input structures have required fields
if ~isfield(voltage, 'voltage')
error('MHKiT:dc_power:InvalidInput', 'voltage structure must contain voltage field');
end
if ~isfield(voltage, 'time')
error('MHKiT:dc_power:InvalidInput', 'voltage structure must contain time field');
end
if ~isfield(current, 'current')
error('MHKiT:dc_power:InvalidInput', 'current structure must contain current field');
end
if ~isfield(current, 'time')
error('MHKiT:dc_power:InvalidInput', 'current structure must contain time field');
end

% Extract data matrices
voltage_data = voltage.voltage;
current_data = current.current;
voltage_time = voltage.time;
current_time = current.time;

% Validate dimensions match
if ~isequal(size(voltage_data), size(current_data))
error('MHKiT:dc_power:InvalidInput', 'voltage and current must have the same dimensions');
end

% Validate time vectors match
if ~isequal(voltage_time, current_time)
error('MHKiT:dc_power:InvalidInput', 'Time vectors must match between voltage and current structures');
end

% Calculate power for each channel (element-wise multiplication)
power_matrix = voltage_data .* current_data;

% Calculate gross power (sum across channels - dimension 2)
% Handle NaN values by skipping them in the sum
gross_power_vector = sum(power_matrix, 2, 'omitnan');

% Create output structure
power_dc = struct();
power_dc.power = power_matrix;
power_dc.gross = gross_power_vector;
power_dc.time = voltage_time;

end
Loading
Loading