PX4 Firmware
PX4 Autopilot Software http://px4.io
blocks.cpp
Go to the documentation of this file.
1 /****************************************************************************
2  *
3  * Copyright (C) 2012 PX4 Development Team. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  * notice, this list of conditions and the following disclaimer in
13  * the documentation and/or other materials provided with the
14  * distribution.
15  * 3. Neither the name PX4 nor the names of its contributors may be
16  * used to endorse or promote products derived from this software
17  * without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
26  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  *
32  ****************************************************************************/
33 
34 /**
35  * @file blocks.cpp
36  *
37  * Controller library code
38  */
39 
40 #include <math.h>
41 #include <stdio.h>
42 #include <float.h>
43 
44 #include <controllib/blocks.hpp>
45 
46 #define ASSERT_CL(T) if (!(T)) { printf("FAIL\n"); return -1; }
47 
48 namespace control
49 {
50 
51 int basicBlocksTest();
52 int blockLimitTest();
53 int blockLimitSymTest();
54 int blockLowPassTest();
55 int blockHighPassTest();
56 int blockLowPass2Test();
57 int blockIntegralTest();
60 int blockPTest();
61 int blockPITest();
62 int blockPDTest();
63 int blockPIDTest();
64 int blockOutputTest();
66 int blockRandGaussTest();
67 int blockStatsTest();
68 int blockDelayTest();
69 
71 {
80  blockPTest();
81  blockPITest();
82  blockPDTest();
83  blockPIDTest();
85  //blockRandUniformTest();
86  // known failures
87  // blockRandGaussTest();
90  return 0;
91 }
92 
94 {
95  printf("Test BlockLimit\t\t\t: ");
96  BlockLimit limit(NULL, "TEST");
97  // initial state
98  ASSERT_CL(equal(1.0f, limit.getMax()));
99  ASSERT_CL(equal(-1.0f, limit.getMin()));
100  ASSERT_CL(equal(0.0f, limit.getDt()));
101  // update
102  ASSERT_CL(equal(-1.0f, limit.update(-2.0f)));
103  ASSERT_CL(equal(1.0f, limit.update(2.0f)));
104  ASSERT_CL(equal(0.0f, limit.update(0.0f)));
105  printf("PASS\n");
106  return 0;
107 }
108 
110 {
111  printf("Test BlockLimitSym\t\t: ");
112  BlockLimitSym limit(NULL, "TEST");
113  // initial state
114  ASSERT_CL(equal(1.0f, limit.getMax()));
115  ASSERT_CL(equal(0.0f, limit.getDt()));
116  // update
117  ASSERT_CL(equal(-1.0f, limit.update(-2.0f)));
118  ASSERT_CL(equal(1.0f, limit.update(2.0f)));
119  ASSERT_CL(equal(0.0f, limit.update(0.0f)));
120  printf("PASS\n");
121  return 0;
122 }
123 
125 {
126  printf("Test BlockLowPass\t\t: ");
127  BlockLowPass lowPass(NULL, "TEST_LP");
128  // test initial state
129  ASSERT_CL(equal(10.0f, lowPass.getFCut()));
130  ASSERT_CL(equal(0.0f, lowPass.getState()));
131  ASSERT_CL(equal(0.0f, lowPass.getDt()));
132  // set dt
133  lowPass.setDt(0.1f);
134  ASSERT_CL(equal(0.1f, lowPass.getDt()));
135  // set state
136  lowPass.setState(1.0f);
137  ASSERT_CL(equal(1.0f, lowPass.getState()));
138  // test update
139  ASSERT_CL(equal(1.8626974f, lowPass.update(2.0f)));
140 
141  // test end condition
142  for (int i = 0; i < 100; i++) {
143  lowPass.update(2.0f);
144  }
145 
146  ASSERT_CL(equal(2.0f, lowPass.getState()));
147  ASSERT_CL(equal(2.0f, lowPass.update(2.0f)));
148  printf("PASS\n");
149  return 0;
150 };
151 
153 {
154  printf("Test BlockHighPass\t\t: ");
155  BlockHighPass highPass(NULL, "TEST_HP");
156  // test initial state
157  ASSERT_CL(equal(10.0f, highPass.getFCut()));
158  ASSERT_CL(equal(0.0f, highPass.getU()));
159  ASSERT_CL(equal(0.0f, highPass.getY()));
160  ASSERT_CL(equal(0.0f, highPass.getDt()));
161  // set dt
162  highPass.setDt(0.1f);
163  ASSERT_CL(equal(0.1f, highPass.getDt()));
164  // set state
165  highPass.setU(1.0f);
166  ASSERT_CL(equal(1.0f, highPass.getU()));
167  highPass.setY(1.0f);
168  ASSERT_CL(equal(1.0f, highPass.getY()));
169  // test update
170  ASSERT_CL(equal(0.2746051f, highPass.update(2.0f)));
171 
172  // test end condition
173  for (int i = 0; i < 100; i++) {
174  highPass.update(2.0f);
175  }
176 
177  ASSERT_CL(equal(0.0f, highPass.getY()));
178  ASSERT_CL(equal(0.0f, highPass.update(2.0f)));
179  printf("PASS\n");
180  return 0;
181 }
182 
184 {
185  printf("Test BlockLowPass2\t\t: ");
186  BlockLowPass2 lowPass(NULL, "TEST_LP", 100);
187  // test initial state
188  ASSERT_CL(equal(10.0f, lowPass.getFCutParam()));
189  ASSERT_CL(equal(0.0f, lowPass.getState()));
190  ASSERT_CL(equal(0.0f, lowPass.getDt()));
191  // set dt
192  lowPass.setDt(0.1f);
193  ASSERT_CL(equal(0.1f, lowPass.getDt()));
194  // set state
195  lowPass.setState(1.0f);
196  ASSERT_CL(equal(1.0f, lowPass.getState()));
197  // test update
198  ASSERT_CL(equal(1.06745527f, lowPass.update(2.0f)));
199 
200  // test end condition
201  for (int i = 0; i < 100; i++) {
202  lowPass.update(2.0f);
203  }
204 
205  ASSERT_CL(equal(2.0f, lowPass.getState()));
206  ASSERT_CL(equal(2.0f, lowPass.update(2.0f)));
207  printf("PASS\n");
208  return 0;
209 };
210 
212 {
213  printf("Test BlockIntegral\t\t: ");
214  BlockIntegral integral(NULL, "TEST_I");
215  // test initial state
216  ASSERT_CL(equal(1.0f, integral.getMax()));
217  ASSERT_CL(equal(0.0f, integral.getDt()));
218  // set dt
219  integral.setDt(0.1f);
220  ASSERT_CL(equal(0.1f, integral.getDt()));
221  // set Y
222  integral.setY(0.9f);
223  ASSERT_CL(equal(0.9f, integral.getY()));
224 
225  // test exceed max
226  for (int i = 0; i < 100; i++) {
227  integral.update(1.0f);
228  }
229 
230  ASSERT_CL(equal(1.0f, integral.update(1.0f)));
231  // test exceed min
232  integral.setY(-0.9f);
233  ASSERT_CL(equal(-0.9f, integral.getY()));
234 
235  for (int i = 0; i < 100; i++) {
236  integral.update(-1.0f);
237  }
238 
239  ASSERT_CL(equal(-1.0f, integral.update(-1.0f)));
240  // test update
241  integral.setY(0.1f);
242  ASSERT_CL(equal(0.2f, integral.update(1.0)));
243  ASSERT_CL(equal(0.2f, integral.getY()));
244  printf("PASS\n");
245  return 0;
246 }
247 
249 {
250  printf("Test BlockIntegralTrap\t\t: ");
251  BlockIntegralTrap integral(NULL, "TEST_I");
252  // test initial state
253  ASSERT_CL(equal(1.0f, integral.getMax()));
254  ASSERT_CL(equal(0.0f, integral.getDt()));
255  // set dt
256  integral.setDt(0.1f);
257  ASSERT_CL(equal(0.1f, integral.getDt()));
258  // set U
259  integral.setU(1.0f);
260  ASSERT_CL(equal(1.0f, integral.getU()));
261  // set Y
262  integral.setY(0.9f);
263  ASSERT_CL(equal(0.9f, integral.getY()));
264 
265  // test exceed max
266  for (int i = 0; i < 100; i++) {
267  integral.update(1.0f);
268  }
269 
270  ASSERT_CL(equal(1.0f, integral.update(1.0f)));
271  // test exceed min
272  integral.setU(-1.0f);
273  integral.setY(-0.9f);
274  ASSERT_CL(equal(-0.9f, integral.getY()));
275 
276  for (int i = 0; i < 100; i++) {
277  integral.update(-1.0f);
278  }
279 
280  ASSERT_CL(equal(-1.0f, integral.update(-1.0f)));
281  // test update
282  integral.setU(2.0f);
283  integral.setY(0.1f);
284  ASSERT_CL(equal(0.25f, integral.update(1.0)));
285  ASSERT_CL(equal(0.25f, integral.getY()));
286  ASSERT_CL(equal(1.0f, integral.getU()));
287  printf("PASS\n");
288  return 0;
289 }
290 
292 {
293  printf("Test BlockDerivative\t\t: ");
294  BlockDerivative derivative(NULL, "TEST_D");
295  // test initial state
296  ASSERT_CL(equal(0.0f, derivative.getU()));
297  ASSERT_CL(equal(10.0f, derivative.getLP()));
298  // set dt
299  derivative.setDt(0.1f);
300  ASSERT_CL(equal(0.1f, derivative.getDt()));
301  // set U
302  derivative.setU(1.0f);
303  ASSERT_CL(equal(1.0f, derivative.getU()));
304  // perform one update so initialized is set
305  derivative.update(1.0);
306  ASSERT_CL(equal(1.0f, derivative.getU()));
307  // test update
308  ASSERT_CL(equal(8.6269744f, derivative.update(2.0f)));
309  ASSERT_CL(equal(2.0f, derivative.getU()));
310  printf("PASS\n");
311  return 0;
312 }
313 
315 {
316  printf("Test BlockP\t\t\t: ");
317  BlockP blockP(NULL, "TEST_P");
318  // test initial state
319  ASSERT_CL(equal(0.2f, blockP.getKP()));
320  ASSERT_CL(equal(0.0f, blockP.getDt()));
321  // set dt
322  blockP.setDt(0.1f);
323  ASSERT_CL(equal(0.1f, blockP.getDt()));
324  // test update
325  ASSERT_CL(equal(0.4f, blockP.update(2.0f)));
326  printf("PASS\n");
327  return 0;
328 }
329 
331 {
332  printf("Test BlockPI\t\t\t: ");
333  BlockPI blockPI(NULL, "TEST");
334  // test initial state
335  ASSERT_CL(equal(0.2f, blockPI.getKP()));
336  ASSERT_CL(equal(0.1f, blockPI.getKI()));
337  ASSERT_CL(equal(0.0f, blockPI.getDt()));
338  ASSERT_CL(equal(1.0f, blockPI.getIntegral().getMax()));
339  // set dt
340  blockPI.setDt(0.1f);
341  ASSERT_CL(equal(0.1f, blockPI.getDt()));
342  // set integral state
343  blockPI.getIntegral().setY(0.1f);
344  ASSERT_CL(equal(0.1f, blockPI.getIntegral().getY()));
345  // test update
346  // 0.2*2 + 0.1*(2*0.1 + 0.1) = 0.43
347  ASSERT_CL(equal(0.43f, blockPI.update(2.0f)));
348  printf("PASS\n");
349  return 0;
350 }
351 
353 {
354  printf("Test BlockPD\t\t\t: ");
355  BlockPD blockPD(NULL, "TEST");
356  // test initial state
357  ASSERT_CL(equal(0.2f, blockPD.getKP()));
358  ASSERT_CL(equal(0.01f, blockPD.getKD()));
359  ASSERT_CL(equal(0.0f, blockPD.getDt()));
360  ASSERT_CL(equal(10.0f, blockPD.getDerivative().getLP()));
361  // set dt
362  blockPD.setDt(0.1f);
363  ASSERT_CL(equal(0.1f, blockPD.getDt()));
364  // set derivative state
365  blockPD.getDerivative().setU(1.0f);
366  ASSERT_CL(equal(1.0f, blockPD.getDerivative().getU()));
367  // perform one update so initialized is set
368  blockPD.getDerivative().update(1.0);
369  ASSERT_CL(equal(1.0f, blockPD.getDerivative().getU()));
370  // test update
371  // 0.2*2 + 0.1*(0.1*8.626...) = 0.486269744
372  ASSERT_CL(equal(0.486269744f, blockPD.update(2.0f)));
373  printf("PASS\n");
374  return 0;
375 }
376 
378 {
379  printf("Test BlockPID\t\t\t: ");
380  BlockPID blockPID(NULL, "TEST");
381  // test initial state
382  ASSERT_CL(equal(0.2f, blockPID.getKP()));
383  ASSERT_CL(equal(0.1f, blockPID.getKI()));
384  ASSERT_CL(equal(0.01f, blockPID.getKD()));
385  ASSERT_CL(equal(0.0f, blockPID.getDt()));
386  ASSERT_CL(equal(10.0f, blockPID.getDerivative().getLP()));
387  ASSERT_CL(equal(1.0f, blockPID.getIntegral().getMax()));
388  // set dt
389  blockPID.setDt(0.1f);
390  ASSERT_CL(equal(0.1f, blockPID.getDt()));
391  // set derivative state
392  blockPID.getDerivative().setU(1.0f);
393  ASSERT_CL(equal(1.0f, blockPID.getDerivative().getU()));
394  // perform one update so initialized is set
395  blockPID.getDerivative().update(1.0);
396  ASSERT_CL(equal(1.0f, blockPID.getDerivative().getU()));
397  // set integral state
398  blockPID.getIntegral().setY(0.1f);
399  ASSERT_CL(equal(0.1f, blockPID.getIntegral().getY()));
400  // test update
401  // 0.2*2 + 0.1*(2*0.1 + 0.1) + 0.1*(0.1*8.626...) = 0.5162697
402  ASSERT_CL(equal(0.5162697f, blockPID.update(2.0f)));
403  printf("PASS\n");
404  return 0;
405 }
406 
408 {
409  printf("Test BlockOutput\t\t: ");
410  BlockOutput blockOutput(NULL, "TEST");
411  // test initial state
412  ASSERT_CL(equal(0.0f, blockOutput.getDt()));
413  ASSERT_CL(equal(0.5f, blockOutput.get()));
414  ASSERT_CL(equal(-1.0f, blockOutput.getMin()));
415  ASSERT_CL(equal(1.0f, blockOutput.getMax()));
416  // test update below min
417  blockOutput.update(-2.0f);
418  ASSERT_CL(equal(-1.0f, blockOutput.get()));
419  // test update above max
420  blockOutput.update(2.0f);
421  ASSERT_CL(equal(1.0f, blockOutput.get()));
422  // test trim
423  blockOutput.update(0.0f);
424  ASSERT_CL(equal(0.5f, blockOutput.get()));
425  printf("PASS\n");
426  return 0;
427 }
428 
430 {
431  srand(1234);
432  printf("Test BlockRandUniform\t\t: ");
433  BlockRandUniform blockRandUniform(NULL, "TEST");
434  // test initial state
435  ASSERT_CL(equal(0.0f, blockRandUniform.getDt()));
436  ASSERT_CL(equal(-1.0f, blockRandUniform.getMin()));
437  ASSERT_CL(equal(1.0f, blockRandUniform.getMax()));
438  // test update
439  int n = 10000;
440  float mean = blockRandUniform.update();
441 
442  for (int i = 2; i < n + 1; i++) {
443  float val = blockRandUniform.update();
444  mean += (val - mean) / i;
445  ASSERT_CL(less_than_or_equal(val, blockRandUniform.getMax()));
446  ASSERT_CL(greater_than_or_equal(val, blockRandUniform.getMin()));
447  }
448 
449  ASSERT_CL(equal(mean, (blockRandUniform.getMin() +
450  blockRandUniform.getMax()) / 2, 1e-1));
451  printf("PASS\n");
452  return 0;
453 }
454 
456 {
457  srand(1234);
458  printf("Test BlockRandGauss\t\t: ");
459  BlockRandGauss blockRandGauss(NULL, "TEST");
460  // test initial state
461  ASSERT_CL(equal(0.0f, blockRandGauss.getDt()));
462  ASSERT_CL(equal(1.0f, blockRandGauss.getMean()));
463  ASSERT_CL(equal(2.0f, blockRandGauss.getStdDev()));
464  // test update
465  int n = 10000;
466  float mean = blockRandGauss.update();
467  float sum = 0;
468 
469  // recursive mean, stdev algorithm from Knuth
470  for (int i = 2; i < n + 1; i++) {
471  float val = blockRandGauss.update();
472  float newMean = mean + (val - mean) / i;
473  sum += (val - mean) * (val - newMean);
474  mean = newMean;
475  }
476 
477  float stdDev = sqrt(sum / (n - 1));
478  (void)(stdDev);
479  ASSERT_CL(equal(mean, blockRandGauss.getMean(), 1e-1));
480  ASSERT_CL(equal(stdDev, blockRandGauss.getStdDev(), 1e-1));
481  printf("PASS\n");
482  return 0;
483 }
484 
486 {
487  printf("Test BlockStats\t\t\t: ");
488  BlockStats<float, 1> stats(NULL, "TEST");
489  ASSERT_CL(equal(0.0f, stats.getMean()(0)));
490  ASSERT_CL(equal(0.0f, stats.getStdDev()(0)));
491  stats.update(matrix::Scalar<float>(1.0f));
492  stats.update(matrix::Scalar<float>(2));
493  ASSERT_CL(equal(1.5f, stats.getMean()(0)));
494  ASSERT_CL(equal(0.5f, stats.getStdDev()(0)));
495  stats.reset();
496  ASSERT_CL(equal(0.0f, stats.getMean()(0)));
497  ASSERT_CL(equal(0.0f, stats.getStdDev()(0)));
498  printf("PASS\n");
499  return 0;
500 }
501 
503 {
504  printf("Test BlockDelay\t\t\t: ");
505  using namespace matrix;
506  BlockDelay<float, 2, 1, 3> delay(NULL, "TEST");
507  Vector2f u1(1, 2);
508  Vector2f y1 = delay.update(u1);
509  ASSERT_CL(equal(y1(0), u1(0)));
510  ASSERT_CL(equal(y1(1), u1(1)));
511 
512  Vector2f u2(4, 5);
513  Vector2f y2 = delay.update(u2);
514  ASSERT_CL(equal(y2(0), u1(0)));
515  ASSERT_CL(equal(y2(1), u1(1)));
516 
517  Vector2f u3(7, 8);
518  Vector2f y3 = delay.update(u3);
519  ASSERT_CL(equal(y3(0), u1(0)));
520  ASSERT_CL(equal(y3(1), u1(1)));
521 
522  Vector2f u4(9, 10);
523  Vector2f y4 = delay.update(u4);
524  ASSERT_CL(equal(y4(0), u2(0)));
525  ASSERT_CL(equal(y4(1), u2(1)));
526  printf("PASS\n");
527  return 0;
528 }
529 
530 } // namespace control
float update(float input)
#define ASSERT_CL(T)
Definition: blocks.cpp:46
float update(float input)
Definition: BlockLimit.cpp:48
float update(float input)
matrix::Vector< Type, M > getMean()
Definition: BlockStats.hpp:85
int blockRandGaussTest()
Definition: blocks.cpp:455
BlockDerivative & getDerivative()
Definition: BlockPID.hpp:86
int blockIntegralTrapTest()
Definition: blocks.cpp:248
A limiter/ saturation.
Definition: BlockLimit.hpp:63
float getDt()
Definition: Block.hpp:78
int blockDelayTest()
Definition: blocks.cpp:502
A symmetric limiter/ saturation.
int basicBlocksTest()
Definition: blocks.cpp:70
float getKP()
Definition: BlockPI.hpp:79
int blockStatsTest()
Definition: blocks.cpp:485
int blockPDTest()
Definition: blocks.cpp:352
int blockHighPassTest()
Definition: blocks.cpp:152
A high pass filter as described here: http://en.wikipedia.org/wiki/High-pass_filter.
int blockPIDTest()
Definition: blocks.cpp:377
float update(float input)
Definition: BlockPID.hpp:75
void setState(float state)
virtual void setDt(float dt)
Definition: Block.hpp:77
float getKP()
Definition: BlockPD.hpp:79
float getKI()
Definition: BlockPI.hpp:80
void setState(float state)
float update(float input)
A low pass filter as described here: http://en.wikipedia.org/wiki/Low-pass_filter.
bool __EXPORT less_than_or_equal(float a, float b)
Definition: test.cpp:92
An output trim/ saturation block.
Definition: BlockOutput.hpp:60
bool __EXPORT equal(float a, float b, float epsilon)
Definition: test.cpp:48
A proportional-derivative controller.
Definition: BlockPD.hpp:62
matrix::Matrix< Type, M, N > update(const matrix::Matrix< Type, M, N > &u)
Definition: BlockDelay.hpp:70
float getKP()
Definition: BlockP.hpp:76
int blockDerivativeTest()
Definition: blocks.cpp:291
int blockLimitSymTest()
Definition: blocks.cpp:109
float update(float input)
A proportional-integral controller.
Definition: BlockPI.hpp:62
void setDt(float dt) override
Definition: Block.cpp:100
int blockLowPassTest()
Definition: blocks.cpp:124
Vector< float, 6 > f(float t, const Matrix< float, 6, 1 > &, const Matrix< float, 3, 1 > &)
Definition: integration.cpp:8
int blockPTest()
Definition: blocks.cpp:314
BlockIntegral & getIntegral()
Definition: BlockPI.hpp:81
bool __EXPORT greater_than_or_equal(float a, float b)
Definition: test.cpp:81
A rectangular integrator.
int blockLowPass2Test()
Definition: blocks.cpp:183
float update(float input)
int blockRandUniformTest()
Definition: blocks.cpp:429
A proportional-integral-derivative controller.
Definition: BlockPID.hpp:62
int blockLimitTest()
Definition: blocks.cpp:93
A trapezoidal integrator.
float update(float input)
Update the state and get current derivative.
matrix::Vector< Type, M > getStdDev()
Definition: BlockStats.hpp:90
int blockOutputTest()
Definition: blocks.cpp:407
Controller library code.
int blockPITest()
Definition: blocks.cpp:330
A proportional controller.
Definition: BlockP.hpp:62
A simple derivative approximation.
float update(float input)
Definition: BlockP.hpp:71
float update(float input)
Definition: BlockPD.hpp:73
A uniform random number generator.
A 2nd order low pass filter block which uses the default px4 2nd order low pass filter.
BlockDerivative & getDerivative()
Definition: BlockPD.hpp:81
BlockIntegral & getIntegral()
Definition: BlockPID.hpp:85
float update(float input)
Definition: BlockPI.hpp:73
float getKD()
Definition: BlockPD.hpp:80
void update(float input)
Definition: BlockOutput.hpp:74
int blockIntegralTest()
Definition: blocks.cpp:211
Dual< Scalar, N > sqrt(const Dual< Scalar, N > &a)
Definition: Dual.hpp:188
void update(const matrix::Vector< Type, M > &u)
Definition: BlockStats.hpp:71