Source code for soft4pes.control.lin.state_space_curr_ctr
""" State-space current controller with anti-windup scheme for grid-connected converter with
RL load """
from types import SimpleNamespace
import numpy as np
from soft4pes.utils import alpha_beta_2_dq, dq_2_abc, dq_2_alpha_beta
[docs]class RLGridStateSpaceCurrCtr:
"""
State-space current controller with anti-windup scheme for grid-connected converter with
RL load.
Parameters
----------
sys : system object
System model.
base : base-value object
Base values.
Ts : float
Sampling interval [s].
ig_ref_seq_dq : Sequence object
Current reference sequence instance in dq-frame [p.u.].
Attributes
----------
Rf : float
Resistance [p.u.].
Xf : float
Reactance [p.u.].
Ts : float
Sampling interval [s].
Ts_pu : float
Sampling interval [p.u.].
ctr_pars : SimpleNamespace
A SimpleNamespace object containing controller parameters delta, K_i, k_ii and K_ti
uc_ii_dq : 1 x 2 ndarray of floats
Converter voltage reference after current controller integrator in dq frame [p.u.].
uc_km1_dq : 1 x 2 ndarray of floats
Previous converter voltage reference in dq frame [p.u.].
ig_ref_seq_dq : Sequence object
Current reference sequence instance in dq-frame [p.u.].
data : dict
Controller data.
"""
def __init__(self, sys, base, Ts, ig_ref_seq_dq):
[docs] self.Xf = sys.par.Xg # Assume the inductances are equal
[docs] self.Rf = sys.par.Rg # Assume the resitances are equal
[docs] self.Ts_pu = self.Ts * base.w
[docs] self.ctr_pars = self.get_state_space_ctr_pars()
self.u_ii_dq = np.zeros(2)
[docs] self.uc_km1_dq = np.zeros(2)
[docs] self.ig_ref_seq_dq = ig_ref_seq_dq
[docs] self.data = {
'ig_ref': [],
'u': [],
't': [],
}
[docs] def __call__(self, sys, kTs):
"""
Perform control.
Parameters
----------
sys : system object
System model.
kTs : float
Current discrete time instant [s].
Returns
-------
1 x 3 ndarray of floats
Modulating signal.
"""
vg = sys.get_grid_voltage(kTs)
# Get the reference for current step
ic_ref_dq = self.ig_ref_seq_dq(kTs)
# Calculate the transformation angle
theta = np.arctan2(vg[1], vg[0])
# Get dq frame current (converter current equals grid current due to lack of a filter)
ic_dq = alpha_beta_2_dq(sys.i_conv, theta)
# Maximum converter output voltage
u_max = sys.conv.v_dc / 2
# As the filter is not concidered, the filter capacitor voltage is assumed to be the same
# as the grid voltage after the grid indutance.
uf_dq = vg
# Compute the converter voltage reference using the state space controller with anti-windup
uc_dq = self.state_space_controller(ic_dq, ic_ref_dq, uf_dq, u_max)
# Transform the converter voltage reference back to abc frame
uc_abc = dq_2_abc(uc_dq, theta)
u_abc = uc_abc / (sys.conv.v_dc / 2)
# Save controller data
ig_ref = dq_2_alpha_beta(ic_ref_dq, theta)
self.save_data(ig_ref, u_abc, kTs)
return u_abc
[docs] def get_state_space_ctr_pars(self):
"""
Calculate state-space controller parameters.
Returns
-------
SimpleNamespace
Controller parameters.
"""
Ts_pu = self.Ts_pu
Rf = self.Rf
Xf = self.Xf
# Closed-loop controller bandwidth (10x crossover frequency)
alpha_c = 2 * np.pi / 10 / Ts_pu
# Consider delta equals to one due to not considering delay
delta = 1
# Coefficients
phi = np.exp((-Rf / Xf) * Ts_pu) * delta
landa = (delta - phi) / Rf
# The closed-loop pole locations
p_1 = 0
p_2 = np.exp(-alpha_c * Ts_pu)
p_3 = p_2
# Coefficients
k_2 = -p_1 - p_2 - p_3 + phi + 1
k_1 = (p_1 * p_2 + p_1 * p_3 + p_2 * p_3 + k_2 * phi + k_2 -
phi) / landa
# State feedback gain
K_i = np.array([k_1, 0, k_2 * 0])
# Integral gain
k_ii = (-p_1 * p_2 * p_3 + k_1 * landa - k_2 * phi) / landa
# Feedforward gain
k_ti = k_ii / (1 - p_3)
return SimpleNamespace(delta=delta, K_i=K_i, k_ii=k_ii, k_ti=k_ti)
[docs] def state_space_controller(self, ic_dq, ic_ref_dq, uf_dq, u_max):
"""
State-space controller in dq frame.
Parameters
----------
ic_dq : 1 x 2 ndarray of floats
Grid Current in dq frame [p.u.].
ic_ref_dq : 1 x 2 ndarray of floats
Reference current in dq frame [p.u.].
uf_dq : 1 x 2 ndarray of floats
Grid voltage in dq frame [p.u.] (In case: Without considering the filter).
u_max : float
Maximum converter output voltage [p.u.].
Returns
-------
1 x 2 ndarray of floats
Converter voltage reference in dq frame [p.u.].
"""
X_LC = np.array([ic_dq, uf_dq, self.uc_km1_dq])
# State space controller with anti-windup in dq frame
uc_ref_dq_unlim = (self.ctr_pars.k_ti * ic_ref_dq) - np.dot(
self.ctr_pars.K_i, X_LC) + self.u_ii_dq
# Limiting the converter voltage reference
uc_ref_dq = self.voltage_reference_limiter(u_max, uc_ref_dq_unlim)
self.u_ii_dq += self.ctr_pars.k_ii * ((
(uc_ref_dq - uc_ref_dq_unlim) / self.ctr_pars.k_ti) +
(ic_ref_dq - ic_dq))
self.uc_km1_dq = self.ctr_pars.delta * uc_ref_dq
return uc_ref_dq
[docs] def voltage_reference_limiter(self, u_max, uc_ref_dq_unlim):
"""
limit the converter voltage reference.
Parameters
----------
u_max : float
Maximum converter output voltage [p.u.].
uc_ref_dq_unlim : 1 x 2 ndarray of floats
Unlimited converter voltage reference [p.u.].
Returns
-------
1 x 2 ndarray of floats
Limited converter voltage reference [p.u.].
"""
uc_mag = np.linalg.norm(uc_ref_dq_unlim)
if uc_mag <= u_max:
uc_ref_dq = uc_ref_dq_unlim
else:
uc_ref_dq = (uc_ref_dq_unlim / uc_mag) * u_max
return uc_ref_dq
[docs] def save_data(self, ig_ref, u_abc, kTs):
"""
Save controller data.
Parameters
----------
ig_ref : 1 x 2 ndarray of floats
Current reference in alpha-beta frame.
u_abc : 1 x 3 ndarray of floats
Converter three-phase switch position or modulating signal.
kTs : float
Current discrete time instant [s].
"""
self.data['ig_ref'].append(ig_ref)
self.data['u'].append(u_abc)
self.data['t'].append(kTs)
[docs] def get_control_system_data(self):
"""
This is a empty method to make different controllers compatible when building the new
control system structure.
"""