From a4a1f8c34ee4221f7e40d99d72dfecdbc62d27b1 Mon Sep 17 00:00:00 2001 From: Carlo Tibaldi <tlbl@dtu.dk> Date: Thu, 4 Aug 2016 11:36:44 +0200 Subject: [PATCH 1/5] adding folder for controller specific functions and pitch controller tuning --- wetb/control/__init__.py | 0 wetb/control/control.py | 87 ++++++++++++++++++++++++++++++ wetb/control/tests/test_control.py | 71 ++++++++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 wetb/control/__init__.py create mode 100644 wetb/control/control.py create mode 100644 wetb/control/tests/test_control.py diff --git a/wetb/control/__init__.py b/wetb/control/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wetb/control/control.py b/wetb/control/control.py new file mode 100644 index 00000000..5c5c20cc --- /dev/null +++ b/wetb/control/control.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu Aug 04 09:24:51 2016 + +@author: tlbl +""" + +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +from __future__ import absolute_import + +import numpy as np + + +class Control(object): + + def pitch_controller_tuning(self, pitch, I, dQdt, P, Omr, om, csi): + """ + + Function to compute the gains of the pitch controller of the Basic DTU + Wind Energy Controller. + + Parameters + ---------- + pitch: array + Pitch angle [deg] + I: array + Drivetrain inertia [kg*m**2] + + dQdt: array + Partial derivative of the aerodynamic torque with respect to the + pitch angle [kNm/deg] + P: float + Rated power [kW]. Set to zero in case of constant torque regulation + + Omr: float + Rated rotational speed [rpm] + + om: float + Freqeuncy of regulator mode [Hz] + + csi: float + Damping ratio of regulator mode + + Returns + ------- + + kp: float + Proportional gain [rad/(rad/s)] + ki: float + Intagral gain [rad/rad] + K1: float + Linear term of the gain scheduling [deg] + K2: float + Quadratic term of the gain shceduling [deg**2] + + + """ + pitch *= np.pi/180. + I *= 1e-3 + dQdt *= 180./np.pi + Omr *= np.pi/30. + om *= 2.*np.pi + + # Quadratic fitting of dQdt + A = np.ones([dQdt.shape[0], 3]) + A[:, 0] = pitch**2 + A[:, 1] = pitch + b = dQdt + ATA = np.dot(A.T, A) + iATA = np.linalg.inv(ATA) + iATAA = np.dot(iATA, A.T) + x = np.dot(iATAA, b) + + kp = -(2*csi*om*I[0] - P/(Omr**2))/x[2] + ki = -(om**2*I[0])/x[2] + + K1 = x[2]/x[0] * (180./np.pi) + K2 = x[2]/x[1] * (180./np.pi)**2 + + return kp, ki, K1, K2 + + +if __name__ == '__main__': + + pass diff --git a/wetb/control/tests/test_control.py b/wetb/control/tests/test_control.py new file mode 100644 index 00000000..2d4b68d8 --- /dev/null +++ b/wetb/control/tests/test_control.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu Aug 04 11:09:43 2016 + +@author: tlbl +""" + +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import +from future import standard_library +standard_library.install_aliases() +import unittest + +from wetb.control import control +import numpy as np + + +class TestControl(unittest.TestCase): + + def setUp(self): + dQdt = np.array([-0.126876918E+03, -0.160463547E+03, -0.211699586E+03, + -0.277209984E+03, -0.351476131E+03, -0.409411354E+03, + -0.427060299E+03, -0.608498644E+03, -0.719141594E+03, + -0.814129630E+03, -0.899248007E+03, -0.981457622E+03, + -0.106667910E+04, -0.114961807E+04, -0.123323210E+04, + -0.131169210E+04, -0.138758236E+04, -0.145930419E+04, + -0.153029102E+04, -0.159737975E+04, -0.166846850E+04]) + + pitch = np.array([0.2510780000E+00, 0.5350000000E-03, 0.535000000E-03, + 0.5350000000E-03, 0.5350000000E-03, 0.535000000E-03, + 0.5350000000E-03, 0.3751976000E+01, 0.625255700E+01, + 0.8195032000E+01, 0.9857780000E+01, 0.113476710E+02, + 0.1271615400E+02, 0.1399768300E+02, 0.152324310E+02, + 0.1642177100E+02, 0.1755302300E+02, 0.186442750E+02, + 0.1970333100E+02, 0.2073358600E+02, 0.217410280E+02]) + + I = np.array([0.4394996114E+08, 0.4395272885E+08, 0.4395488725E+08, + 0.4395301987E+08, 0.4394561932E+08, 0.4393327166E+08, + 0.4391779133E+08, 0.4394706335E+08, 0.4395826989E+08, + 0.4396263773E+08, 0.4396412693E+08, 0.4396397777E+08, + 0.4396275304E+08, 0.4396076315E+08, 0.4395824699E+08, + 0.4395531228E+08, 0.4395201145E+08, 0.4394837798E+08, + 0.4394456127E+08, 0.4394060604E+08, 0.4393647769E+08]) + + self.dQdt = dQdt + self.pitch = pitch + self.I = I + + def test_pitch_controller_tuning(self): + + crt = control.Control() + P = 0. + Omr = 12.1 + om = 0.10 + csi = 0.7 + i = 5 + kp, ki, K1, K2 = crt.pitch_controller_tuning(self.pitch[i:], + self.I[i:], + self.dQdt[i:], + P, Omr, om, csi) + + self.assertEqual(kp, 1.5960902436444313) + self.assertEqual(ki, 0.71632362627138402) + self.assertEqual(K1, 10.463887621856747) + self.assertEqual(K2, 573.59471652017498) + +if __name__ == "__main__": + + unittest.main() -- GitLab From 7762eede5f1878fa3af01296ad93d0b797c9b146 Mon Sep 17 00:00:00 2001 From: Carlo Tibaldi <tlbl@dtu.dk> Date: Thu, 4 Aug 2016 11:38:26 +0200 Subject: [PATCH 2/5] some comments --- wetb/control/control.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/wetb/control/control.py b/wetb/control/control.py index 5c5c20cc..921fbce2 100644 --- a/wetb/control/control.py +++ b/wetb/control/control.py @@ -19,7 +19,8 @@ class Control(object): """ Function to compute the gains of the pitch controller of the Basic DTU - Wind Energy Controller. + Wind Energy Controller with the pole placement technique implemented + in HAWCStab2. Parameters ---------- @@ -27,19 +28,15 @@ class Control(object): Pitch angle [deg] I: array Drivetrain inertia [kg*m**2] - dQdt: array Partial derivative of the aerodynamic torque with respect to the - pitch angle [kNm/deg] + pitch angle [kNm/deg]. Can be computed with HAWCStab2. P: float Rated power [kW]. Set to zero in case of constant torque regulation - Omr: float Rated rotational speed [rpm] - om: float Freqeuncy of regulator mode [Hz] - csi: float Damping ratio of regulator mode -- GitLab From b859b42788bff6b17305ba9e8fdebedbfb697c89 Mon Sep 17 00:00:00 2001 From: Carlo Tibaldi <tlbl@dtu.dk> Date: Thu, 4 Aug 2016 14:23:21 +0200 Subject: [PATCH 3/5] fixed one problem and added test --- wetb/control/control.py | 15 +++++++-------- wetb/control/tests/test_control.py | 8 ++++---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/wetb/control/control.py b/wetb/control/control.py index 921fbce2..1d378c53 100644 --- a/wetb/control/control.py +++ b/wetb/control/control.py @@ -42,7 +42,6 @@ class Control(object): Returns ------- - kp: float Proportional gain [rad/(rad/s)] ki: float @@ -54,11 +53,11 @@ class Control(object): """ - pitch *= np.pi/180. - I *= 1e-3 - dQdt *= 180./np.pi - Omr *= np.pi/30. - om *= 2.*np.pi + pitch = pitch * np.pi/180. + I = I * 1e-3 + dQdt = dQdt * 180./np.pi + Omr = Omr * np.pi/30. + om = om * 2.*np.pi # Quadratic fitting of dQdt A = np.ones([dQdt.shape[0], 3]) @@ -73,8 +72,8 @@ class Control(object): kp = -(2*csi*om*I[0] - P/(Omr**2))/x[2] ki = -(om**2*I[0])/x[2] - K1 = x[2]/x[0] * (180./np.pi) - K2 = x[2]/x[1] * (180./np.pi)**2 + K1 = x[2]/x[1]*(180./np.pi) + K2 = x[2]/x[0]*(180./np.pi)**2 return kp, ki, K1, K2 diff --git a/wetb/control/tests/test_control.py b/wetb/control/tests/test_control.py index 2d4b68d8..f16b6aa1 100644 --- a/wetb/control/tests/test_control.py +++ b/wetb/control/tests/test_control.py @@ -61,10 +61,10 @@ class TestControl(unittest.TestCase): self.dQdt[i:], P, Omr, om, csi) - self.assertEqual(kp, 1.5960902436444313) - self.assertEqual(ki, 0.71632362627138402) - self.assertEqual(K1, 10.463887621856747) - self.assertEqual(K2, 573.59471652017498) + self.assertEqual(kp, 1.596090243644432) + self.assertEqual(ki, 0.71632362627138424) + self.assertEqual(K1, 10.01111637532056) + self.assertEqual(K2, 599.53659803157643) if __name__ == "__main__": -- GitLab From 0e0e25d92d7242b3f9091f74994dc0bad342bc1f Mon Sep 17 00:00:00 2001 From: Carlo Tibaldi <tlbl@dtu.dk> Date: Fri, 5 Aug 2016 17:04:38 +0200 Subject: [PATCH 4/5] added line of comments --- wetb/control/control.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wetb/control/control.py b/wetb/control/control.py index 1d378c53..ed9c9c65 100644 --- a/wetb/control/control.py +++ b/wetb/control/control.py @@ -25,7 +25,8 @@ class Control(object): Parameters ---------- pitch: array - Pitch angle [deg] + Pitch angle [deg]. The values should only be those of the full load + region. I: array Drivetrain inertia [kg*m**2] dQdt: array -- GitLab From 3893e7aa4d11e814a067b419dc1483759432ccf1 Mon Sep 17 00:00:00 2001 From: Carlo Tibaldi <tlbl@dtu.dk> Date: Mon, 8 Aug 2016 12:57:52 +0200 Subject: [PATCH 5/5] controller:adding selection of regions and Komega2 --- wetb/control/control.py | 35 ++++++++++++++++ wetb/control/tests/test_control.py | 64 ++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/wetb/control/control.py b/wetb/control/control.py index ed9c9c65..771c97d8 100644 --- a/wetb/control/control.py +++ b/wetb/control/control.py @@ -78,6 +78,41 @@ class Control(object): return kp, ki, K1, K2 + def K_omega2(V, P, R, TSR): + + Va = np.array(V) + Pa = np.array(P) + Ra = np.array(R) + TSRa = np.array(TSR) + K = Ra**3 * np.mean(Pa/(TSRa*Va)**3) + + return K + + def select_regions(self, pitch, omega, power): + + i12 = 0 + + n = len(pitch) + + for i in range(n-1): + if (abs(power[i]/power[i+1] - 1.) > 0.01): + if (abs(omega[i] / omega[i+1] - 1.) > 0.01): + i12 = i + break + i23 = n-1 + for i in range(i12, n-1): + if (abs(omega[i] / omega[i+1] - 1.) < 0.01): + i23 = i + break + + i34 = i23 + for i in range(i23, n-1): + if (abs(power[i]/power[i+1] - 1.) > 0.01): + if (abs(omega[i] / omega[i+1] - 1.) < 0.01): + i34 = i+1 + + return i12, i23, i34 + if __name__ == '__main__': diff --git a/wetb/control/tests/test_control.py b/wetb/control/tests/test_control.py index f16b6aa1..721ffdc9 100644 --- a/wetb/control/tests/test_control.py +++ b/wetb/control/tests/test_control.py @@ -66,6 +66,70 @@ class TestControl(unittest.TestCase): self.assertEqual(K1, 10.01111637532056) self.assertEqual(K2, 599.53659803157643) + def test_regions(self): + + crt = control.Control() + + pitch = np.array([0.,-2.,-2.,-2.,-2.,-2.,-2.,-1., 0., ]) + omega = np.array([1., 1., 1., 2., 3., 3., 3., 3., 3., ]) + power = np.array([1., 2., 3., 4., 5., 6., 7., 7., 7., ]) + + istart, iend = 0, -1 + i1, i2, i3 = crt.select_regions(pitch[istart:iend], omega[istart:iend], + power[istart:iend]) + self.assertEqual(i1, 2) + self.assertEqual(i2, 4) + self.assertEqual(i3, 6) + + istart, iend = 3, -1 + i1, i2, i3 = crt.select_regions(pitch[istart:iend], omega[istart:iend], + power[istart:iend]) + self.assertEqual(i1, 0) + self.assertEqual(i2, 1) + self.assertEqual(i3, 3) + + istart, iend = 5, -1 + i1, i2, i3 = crt.select_regions(pitch[istart:iend], omega[istart:iend], + power[istart:iend]) + self.assertEqual(i1, 0) + self.assertEqual(i2, 0) + self.assertEqual(i3, 1) + + istart, iend = 6, -1 + i1, i2, i3 = crt.select_regions(pitch[istart:iend], omega[istart:iend], + power[istart:iend]) + self.assertEqual(i1, 0) + self.assertEqual(i2, 0) + self.assertEqual(i3, 0) + + istart, iend = 5, -2 + i1, i2, i3 = crt.select_regions(pitch[istart:iend], omega[istart:iend], + power[istart:iend]) + self.assertEqual(i1, 0) + self.assertEqual(i2, 0) + self.assertEqual(i3, 1) + + istart, iend = 3, -3 + i1, i2, i3 = crt.select_regions(pitch[istart:iend], omega[istart:iend], + power[istart:iend]) + self.assertEqual(i1, 0) + self.assertEqual(i2, 1) + self.assertEqual(i3, 2) + + istart, iend = 2, -4 + i1, i2, i3 = crt.select_regions(pitch[istart:iend], omega[istart:iend], + power[istart:iend]) + self.assertEqual(i1, 0) + self.assertEqual(i2, 2) + self.assertEqual(i3, 2) + + istart, iend = 0, 3 + i1, i2, i3 = crt.select_regions(pitch[istart:iend], omega[istart:iend], + power[istart:iend]) + self.assertEqual(i1, 0) + self.assertEqual(i2, 0) + self.assertEqual(i3, 2) + if __name__ == "__main__": unittest.main() -- GitLab