10
10
output = controller(10)
11
11
12
12
"""
13
- from typing import Optional , Tuple
13
+
14
+ from typing import List , Optional , Tuple
15
+
16
+
17
+ class Gain :
18
+ """
19
+ A simple class to store PID gains and the setpoint range for which they apply.
20
+ """
21
+ def __init__ (self , setpoint_scope : Tuple [float , float ], k_p : float , k_i : float , k_d : float ) -> None :
22
+ """
23
+ Initializes a Gain object with a setpoint range and PID gains.
24
+
25
+ Parameters:
26
+ setpoint_scope: The range of setpoints for which these gains apply.
27
+ The range is inclusive of the lower bound and exclusive of the upper bound.
28
+ k_p : The proportional gain.
29
+ k_i : The integral gain.
30
+ k_d : The derivative gain.
31
+ """
32
+ self .setpoint_scope = setpoint_scope
33
+ self .k_p = k_p
34
+ self .k_i = k_i
35
+ self .k_d = k_d
14
36
15
37
16
38
class PID :
@@ -25,9 +47,9 @@ class PID:
25
47
26
48
def __init__ (
27
49
self ,
28
- K_p : float = 1 ,
29
- K_i : float = 0.1 ,
30
- K_d : float = 0 ,
50
+ k_p : float = 1 ,
51
+ k_i : float = 0.1 ,
52
+ k_d : float = 0 ,
31
53
setpoint : float = 0 ,
32
54
dt : float = 1 ,
33
55
derivative_lowpass : float = 1 ,
@@ -38,9 +60,9 @@ def __init__(
38
60
Initialize PID controller
39
61
40
62
Parameters:
41
- K_p : Proportional gain
42
- K_i : Integral gain
43
- K_d : Derivative gain
63
+ k_p : Proportional gain
64
+ k_i : Integral gain
65
+ k_d : Derivative gain
44
66
dt : Time step
45
67
derivative_lowpass: lowpass constant (between 1 and 0, 1 meaning no lowpass)
46
68
upper_limit : Upper limit for the output
@@ -51,9 +73,9 @@ def __init__(
51
73
if not 0 <= derivative_lowpass <= 1 :
52
74
raise ValueError ("derivative_lowpass must be between 0 and 1" )
53
75
54
- self .K_p = K_p
55
- self .K_i = K_i
56
- self .K_d = K_d
76
+ self .k_p = k_p
77
+ self .k_i = k_i
78
+ self .k_d = k_d
57
79
self .P , self .I , self .D = None , None , None
58
80
self .dt = dt
59
81
self .alpha = derivative_lowpass
@@ -108,22 +130,25 @@ def limit(self, output: float) -> Tuple[bool, float]:
108
130
saturated = output != unlimited
109
131
110
132
return saturated , output
111
-
112
- def update_gains (self , K_p : float , K_i : float , K_d : float ) -> None :
133
+
134
+ def update_gains (self , k_p : float , k_i : float , k_d : float ) -> None :
113
135
"""
114
136
Update the PID gains.
115
137
116
138
Parameters:
117
- K_p : The new proportional gain
118
- K_i : The new integral gain
119
- K_d : The new derivative gain
139
+ k_p : The new proportional gain
140
+ k_i : The new integral gain
141
+ k_d : The new derivative gain
120
142
"""
121
- self .K_p = K_p
122
- self .K_i = K_i
123
- self .K_d = K_d
143
+ self .k_p = k_p
144
+ self .k_i = k_i
145
+ self .k_d = k_d
124
146
125
147
def __call__ (
126
- self , process_variable : float , manual_output : Optional [float ] = None , anti_windup : bool = True
148
+ self ,
149
+ process_variable : float ,
150
+ manual_output : Optional [float ] = None ,
151
+ anti_windup : bool = True ,
127
152
) -> float :
128
153
"""
129
154
Process the input signal and return the controller output.
@@ -137,9 +162,9 @@ def __call__(
137
162
self .integral += error * self .dt
138
163
derivative = (error - self ._previous_error ) / self .dt if self .dt != 0 else 0
139
164
140
- self .P = self .K_p * error
141
- self .I = self .K_i * self .integral
142
- self .D = self .K_d * (self .alpha * derivative + (1 - self .alpha ) * self ._previous_derivative )
165
+ self .P = self .k_p * error
166
+ self .I = self .k_i * self .integral
167
+ self .D = self .k_d * (self .alpha * derivative + (1 - self .alpha ) * self ._previous_derivative )
143
168
144
169
output = self .P + self .I + self .D
145
170
@@ -154,7 +179,7 @@ def __call__(
154
179
155
180
if manual_output :
156
181
# Use setpoint tracking by calculating integral so that the output matches the manual setpoint
157
- self .integral = - (self .P + self .D - manual_output ) / self .K_i if self .K_i != 0 else 0
182
+ self .integral = - (self .P + self .D - manual_output ) / self .k_i if self .k_i != 0 else 0
158
183
output = manual_output
159
184
160
185
return output
@@ -165,3 +190,64 @@ def __repr__(self):
165
190
f"P: { self .P } , I: { self .I } , D: { self .D } \n "
166
191
f"Limits: { self .lower_limit } < output < { self .upper_limit } "
167
192
)
193
+
194
+
195
+ class PIDGainScheduler (PID ):
196
+ """
197
+ An extended Proportional-Integral-Derivative controller that uses
198
+ gain scheduling to allow different gains, i.e., k_p, k_i, and k_d depending on
199
+ the setpoint.
200
+
201
+ """
202
+
203
+ def __init__ (
204
+ self ,
205
+ gains : List [Gain ],
206
+ setpoint : float = 0 ,
207
+ dt : float = 1 ,
208
+ derivative_lowpass : float = 1 ,
209
+ upper_limit : Optional [float ] = None ,
210
+ lower_limit : Optional [float ] = None ,
211
+ ) -> None :
212
+ """
213
+ Initialize PID controller
214
+
215
+ Parameters:
216
+ gains : List of Gain objects
217
+ setpoint : The setpoint
218
+ dt : Time step
219
+ derivative_lowpass: lowpass constant (between 1 and 0, 1 meaning no lowpass)
220
+ upper_limit : Upper limit for the output
221
+ lower_limit : Lower limit for the output
222
+ """
223
+
224
+ super ().__init__ (None , None , None , setpoint , dt , derivative_lowpass , upper_limit , lower_limit )
225
+
226
+ self .gains = gains
227
+ self .update_gain (setpoint )
228
+
229
+ def update_gain (self , setpoint : float ) -> Tuple [float , float , float ]:
230
+ """
231
+ Update the PID gains based on the current setpoint
232
+
233
+ Parameters:
234
+ output : The current output
235
+ """
236
+ for gain in self .gains :
237
+ lower , upper = gain .setpoint_scope
238
+ if lower <= setpoint < upper :
239
+ self .k_p , self .k_i , self .k_d = gain .k_p , gain .k_i , gain .k_d
240
+ break
241
+ else :
242
+ raise ValueError ("No gain found for the given setpoint." )
243
+
244
+ def __call__ (
245
+ self ,
246
+ process_variable : float ,
247
+ manual_output : Optional [float ] = None ,
248
+ anti_windup : bool = True ,
249
+ ) -> float :
250
+ self .update_gain (self .setpoint )
251
+
252
+ output = super ().__call__ (process_variable , manual_output , anti_windup )
253
+ return output
0 commit comments