Source code for soft4pes.control.common.utils
"""
Utility functions and classes for the control module.
"""
import numpy as np
from soft4pes.utils import alpha_beta_2_abc
[docs]def wrap_theta(theta):
"""
Wrap the angle theta to the range [-pi, pi].
Parameters
----------
theta : float
The angle in radians.
Returns
-------
float
The wrapped angle in radians.
"""
return (theta + np.pi) % (2 * np.pi) - np.pi
[docs]def get_modulating_signal(v_ref, v_dc):
"""
Convert a voltage reference to a modulating signal.
Parameters
----------
v_ref : ndarray
The reference voltage in alpha-beta frame.
v_dc : float
The dc-link voltage.
Returns
-------
ndarray
The modulating signal in abc-frame.
"""
return np.clip(alpha_beta_2_abc(v_ref / (v_dc / 2)), -1, 1)
[docs]def magnitude_limiter(input_signal, limit):
"""
Limit the input in to maximum magnitude. The input can be in alpha-beta or dq-frame, and given
as a vector or complex number.
Parameters
----------
limit : float
Maximum magnitude [p.u.].
input_signal : 1 x 2 ndarray of floats or complex
Unlimited input [p.u.].
Returns
-------
1 x 2 ndarray of floats
Limited output [p.u.].
"""
if isinstance(input_signal, complex):
input_mag = np.abs(input_signal)
else:
input_mag = np.linalg.norm(input_signal)
if input_mag <= limit:
output = input_signal
else:
output = (input_signal / input_mag) * limit
return output
[docs]class FirstOrderFilter:
"""
General first-order filter.
Parameters
----------
w_bw : float
The bandwidth of the filter [p.u.].
size : int
The size of the signal to be filtered, i.e. the length of the input vector.
Attributes
----------
w_bw : float
The bandwidth of the filter [p.u.].
output : ndarray
The filtered signal.
"""
def __init__(self, w_bw, size):
[docs] self.output = np.zeros(size)
[docs] def update(self, value_in, Ts, base):
"""
Update the filter with a new input signal of the defined size.
Parameters
----------
value_in : ndarray
The input signal to be filtered.
Ts : float
The sampling interval [s].
base : object
The base values object containing the base angular frequency.
"""
Ts_pu = Ts * base.w
self.output += Ts_pu * self.w_bw * (value_in - self.output)
[docs]class DiscreteTransferFunction:
"""
Generic discrete-time transfer function implementation.
The transfer function is defined as
Y(z) / U(z) = (b0 + b1 z^-1 + ... + bm z^-m)
--------------------------------
(a0 + a1 z^-1 + ... + an z^-n)
Parameters
----------
numerator_coeffs : array-like
Numerator coefficients of the transfer function.
denominator_coeffs : array-like, optional
Denominator coefficients of the transfer function.
If not given, [1] is used.
"""
def __init__(self, numerator_coeffs, denominator_coeffs=None):
self.numerator_coeffs = np.array(numerator_coeffs)
self.denominator_coeffs = np.array(
[1] if denominator_coeffs is None else denominator_coeffs
)
if self.denominator_coeffs[0] != 1:
self.numerator_coeffs = self.numerator_coeffs / self.denominator_coeffs[0]
self.denominator_coeffs = self.denominator_coeffs / self.denominator_coeffs[0]
self.buffer_input = None
self.buffer_output = None
[docs] def apply(self, input_signal):
"""
Apply the discrete transfer function to the input signal.
Parameters
----------
input_signal : float or array-like
The current input signal.
Returns
-------
float or ndarray
The filtered output signal.
"""
input_signal = np.array(input_signal)
scalar_input = input_signal.ndim == 0
if scalar_input:
input_signal = np.array([input_signal])
if self.buffer_input is None:
self.buffer_input = np.zeros((len(self.numerator_coeffs),) + input_signal.shape)
self.buffer_output = np.zeros((len(self.denominator_coeffs) - 1,) + input_signal.shape)
self.buffer_input[1:] = self.buffer_input[:-1]
self.buffer_input[0] = input_signal
output = np.tensordot(self.numerator_coeffs, self.buffer_input, axes=(0, 0))
if len(self.denominator_coeffs) > 1:
output -= np.tensordot(self.denominator_coeffs[1:], self.buffer_output, axes=(0, 0))
self.buffer_output[1:] = self.buffer_output[:-1]
self.buffer_output[0] = output
if scalar_input:
return output[0]
return output