focus() / blur() should be on HTMLElement / SVGElement, not Element
[WebKit-https.git] / PerformanceTests / Animometer / tests / resources / math.js
1 SimpleKalmanEstimator = Utilities.createSubclass(Experiment,
2     function(processError, measurementError) {
3         Experiment.call(this, false);
4         var error = .5 * (Math.sqrt(processError * processError + 4 * processError * measurementError) - processError);
5         this._gain = error / (error + measurementError);
6     }, {
7
8     sample: function(newMeasurement)
9     {
10         if (!this._initialized) {
11             this._initialized = true;
12             this.estimate = newMeasurement;
13             return;
14         }
15
16         this.estimate = this.estimate + this._gain * (newMeasurement - this.estimate);
17     },
18
19     reset: function()
20     {
21         Experiment.prototype.reset.call(this);
22         this._initialized = false;
23         this.estimate = 0;
24     }
25 });
26
27 PIDController = Utilities.createClass(
28     function(ysp)
29     {
30         this._ysp = ysp;
31         this._out = 0;
32
33         this._Kp = 0;
34         this._stage = PIDController.stages.WARMING;
35
36         this._eold = 0;
37         this._I = 0;
38     }, {
39
40     // Determines whether the current y is
41     //  before ysp => (below ysp if ysp > y0) || (above ysp if ysp < y0)
42     //  after ysp => (above ysp if ysp > y0) || (below ysp if ysp < y0)
43     _yPosition: function(y)
44     {
45         return (y < this._ysp) == (this._y0 < this._ysp)
46             ? PIDController.yPositions.BEFORE_SETPOINT
47             : PIDController.yPositions.AFTER_SETPOINT;
48     },
49
50     // Calculate the ultimate distance from y0 after time t. We want to move very
51     // slowly at the beginning to see how adding few items to the test can affect
52     // its output. The complexity of a single item might be big enough to keep the
53     // proportional gain very small but achieves the desired progress. But if y does
54     // not change significantly after adding few items, that means we need a much
55     // bigger gain. So we need to move over a cubic curve which increases very
56     // slowly with small t values but moves very fast with larger t values.
57     // The basic formula is: y = t^3
58     // Change the formula to reach y=1 after 1000 ms: y = (t/1000)^3
59     // Change the formula to reach y=(ysp - y0) after 1000 ms: y = (ysp - y0) * (t/1000)^3
60     _distanceUltimate: function(t)
61     {
62         return (this._ysp - this._y0) * Math.pow(t / 1000, 3);
63     },
64
65     // Calculates the distance of y relative to y0. It also ensures we do not return
66     // zero by returning a epsilon value in the same direction as ultimate distance.
67     _distance: function(y, du)
68     {
69         const epsilon = 0.0001;
70         var d  = y - this._y0;
71         return du < 0 ? Math.min(d, -epsilon) : Math.max(d, epsilon);
72     },
73
74     // Decides how much the proportional gain should be increased during the manual
75     // gain stage. We choose to use the ratio of the ultimate distance to the current
76     // distance as an indication of how much the system is responsive. We want
77     // to keep the increment under control so it does not cause the system instability
78     // So we choose to take the natural logarithm of this ratio.
79     _gainIncrement: function(t, y, e)
80     {
81         var du = this._distanceUltimate(t);
82         var d = this._distance(y, du);
83         return Math.log(du / d) * 0.1;
84     },
85
86     // Update the stage of the controller based on its current stage and the system output
87     _updateStage: function(y)
88     {
89         var yPosition = this._yPosition(y);
90
91         switch (this._stage) {
92         case PIDController.stages.WARMING:
93             if (yPosition == PIDController.yPositions.AFTER_SETPOINT)
94                 this._stage = PIDController.stages.OVERSHOOT;
95             break;
96
97         case PIDController.stages.OVERSHOOT:
98             if (yPosition == PIDController.yPositions.BEFORE_SETPOINT)
99                 this._stage = PIDController.stages.UNDERSHOOT;
100             break;
101
102         case PIDController.stages.UNDERSHOOT:
103             if (yPosition == PIDController.yPositions.AFTER_SETPOINT)
104                 this._stage = PIDController.stages.SATURATE;
105             break;
106         }
107     },
108
109     // Manual tuning is used before calculating the PID controller gains.
110     _tuneP: function(e)
111     {
112         // The output is the proportional term only.
113         return this._Kp * e;
114     },
115
116     // PID tuning function. Kp, Ti and Td were already calculated
117     _tunePID: function(h, y, e)
118     {
119         // Proportional term.
120         var P = this._Kp * e;
121
122         // Integral term is the area under the curve starting from the beginning
123         // till the current time.
124         this._I += (this._Kp / this._Ti) * ((e + this._eold) / 2) * h;
125
126         // Derivative term is the slope of the curve at the current time.
127         var D = (this._Kp * this._Td) * (e - this._eold) / h;
128
129         // The ouput is a PID function.
130        return P + this._I + D;
131     },
132
133     // Apply different strategies for the tuning based on the stage of the controller.
134     _tune: function(t, h, y, e)
135     {
136         switch (this._stage) {
137         case PIDController.stages.WARMING:
138             // This is the first stage of the Zieglerâ\80Nichols method. It increments
139             // the proportional gain till the system output passes the set-point value.
140             if (typeof this._y0 == "undefined") {
141                 // This is the first time a tuning value is required. We want the test
142                 // to add only one item. So we need to return -1 which forces us to
143                 // choose the initial value of Kp to be = -1 / e
144                 this._y0 = y;
145                 this._Kp = -1 / e;
146             } else {
147                 // Keep incrementing the Kp as long as we have not reached the
148                 // set-point yet
149                 this._Kp += this._gainIncrement(t, y, e);
150             }
151
152             return this._tuneP(e);
153
154         case PIDController.stages.OVERSHOOT:
155             // This is the second stage of the Zieglerâ\80Nichols method. It measures the
156             // oscillation period.
157             if (typeof this._t0 == "undefined") {
158                 // t is the time of the begining of the first overshot
159                 this._t0 = t;
160                 this._Kp /= 2;
161             }
162
163             return this._tuneP(e);
164
165         case PIDController.stages.UNDERSHOOT:
166             // This is the end of the Zieglerâ\80Nichols method. We need to calculate the
167             // integral and derivative periods.
168             if (typeof this._Ti == "undefined") {
169                 // t is the time of the end of the first overshot
170                 var Tu = t - this._t0;
171
172                 // Calculate the system parameters from Kp and Tu assuming
173                 // a "some overshoot" control type. See:
174                 // https://en.wikipedia.org/wiki/Ziegler%E2%80%93Nichols_method
175                 this._Ti = Tu / 2;
176                 this._Td = Tu / 3;
177                 this._Kp = 0.33 * this._Kp;
178
179                 // Calculate the tracking time.
180                 this._Tt = Math.sqrt(this._Ti * this._Td);
181             }
182
183             return this._tunePID(h, y, e);
184
185         case PIDController.stages.SATURATE:
186             return this._tunePID(h, y, e);
187         }
188
189         return 0;
190     },
191
192     // Ensures the system does not fluctuates.
193     _saturate: function(v, e)
194     {
195         var u = v;
196
197         switch (this._stage) {
198         case PIDController.stages.OVERSHOOT:
199         case PIDController.stages.UNDERSHOOT:
200             // Calculate the min-max values of the saturation actuator.
201             if (typeof this._min == "undefined")
202                 this._min = this._max = this._out;
203             else {
204                 this._min = Math.min(this._min, this._out);
205                 this._max = Math.max(this._max, this._out);
206             }
207             break;
208
209         case PIDController.stages.SATURATE:
210             const limitPercentage = 0.90;
211             var min = this._min > 0 ? Math.min(this._min, this._max * limitPercentage) : this._min;
212             var max = this._max < 0 ? Math.max(this._max, this._min * limitPercentage) : this._max;
213             var out = this._out + u;
214
215             // Clip the controller output to the min-max values
216             out = Math.max(Math.min(max, out), min);
217             u = out - this._out;
218
219             // Apply the back-calculation and tracking
220             if (u != v)
221                 u += (this._Kp * this._Tt / this._Ti) * e;
222             break;
223         }
224
225         this._out += u;
226         return u;
227     },
228
229     // Called from the benchmark to tune its test. It uses Ziegler-Nichols method
230     // to calculate the controller parameters. It then returns a PID tuning value.
231     tune: function(t, h, y)
232     {
233         this._updateStage(y);
234
235         // Current error.
236         var e = this._ysp - y;
237         var v = this._tune(t, h, y, e);
238
239         // Save e for the next call.
240         this._eold = e;
241
242         // Apply back-calculation and tracking to avoid integrator windup
243         return this._saturate(v, e);
244     }
245 });
246
247 Utilities.extendObject(PIDController, {
248     // This enum will be used to tell whether the system output (or the controller input)
249     // is moving towards the set-point or away from it.
250     yPositions: {
251         BEFORE_SETPOINT: 0,
252         AFTER_SETPOINT: 1
253     },
254
255     // The Ziegler-Nichols method for is used tuning the PID controller. The workflow of
256     // the tuning is split into four stages. The first two stages determine the values
257     // of the PID controller gains. During these two stages we return the proportional
258     // term only. The third stage is used to determine the min-max values of the
259     // saturation actuator. In the last stage back-calculation and tracking are applied
260     // to avoid integrator windup. During the last two stages, we return a PID control
261     // value.
262     stages: {
263         WARMING: 0,         // Increase the value of the Kp until the system output reaches ysp.
264         OVERSHOOT: 1,       // Measure the oscillation period and the overshoot value
265         UNDERSHOOT: 2,      // Return PID value and measure the undershoot value
266         SATURATE: 3         // Return PID value and apply back-calculation and tracking.
267     }
268 });