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