1 /* -------------------------------------------------------------------------- *
2 * OpenSim: PiecewiseLinearFunction.cpp *
3 * -------------------------------------------------------------------------- *
4 * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. *
5 * See http://opensim.stanford.edu and the NOTICE file for more information. *
6 * OpenSim is developed at Stanford University and supported by the US *
7 * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA *
8 * through the Warrior Web program. *
9 * *
10 * Copyright (c) 2005-2017 Stanford University and the Authors *
11 * Author(s): Peter Loan *
12 * *
13 * Licensed under the Apache License, Version 2.0 (the "License"); you may *
14 * not use this file except in compliance with the License. You may obtain a *
15 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. *
16 * *
17 * Unless required by applicable law or agreed to in writing, software *
18 * distributed under the License is distributed on an "AS IS" BASIS, *
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
20 * See the License for the specific language governing permissions and *
21 * limitations under the License. *
22 * -------------------------------------------------------------------------- */
23
24
25 // C++ INCLUDES
26 #include "PiecewiseLinearFunction.h"
27 #include "Constant.h"
28 #include "FunctionAdapter.h"
29 #include "SimmMacros.h"
30 #include "XYFunctionInterface.h"
31
32
33 using namespace OpenSim;
34 using namespace std;
35 using SimTK::Vector;
36
37
38 //=============================================================================
39 // STATICS
40 //=============================================================================
41
42
43 //=============================================================================
44 // DESTRUCTOR AND CONSTRUCTORS
45 //=============================================================================
46 //_____________________________________________________________________________
47 /**
48 * Destructor.
49 */
~PiecewiseLinearFunction()50 PiecewiseLinearFunction::~PiecewiseLinearFunction()
51 {
52 }
53 //_____________________________________________________________________________
54 /**
55 * Default constructor.
56 */
PiecewiseLinearFunction()57 PiecewiseLinearFunction::PiecewiseLinearFunction() :
58 _x(_propX.getValueDblArray()),
59 _y(_propY.getValueDblArray())
60 {
61 setNull();
62 }
63 //_____________________________________________________________________________
64 /**
65 */
PiecewiseLinearFunction(int aN,const double * aX,const double * aY,const string & aName)66 PiecewiseLinearFunction::PiecewiseLinearFunction(int aN,const double *aX,const double *aY,
67 const string &aName) :
68 _x(_propX.getValueDblArray()),
69 _y(_propY.getValueDblArray()),
70 _b(0.0)
71 {
72 setNull();
73
74 // OBJECT TYPE AND NAME
75 setName(aName);
76
77 // NUMBER OF DATA POINTS
78 if(aN < 2)
79 {
80 printf("PiecewiseLinearFunction: ERROR- there must be 2 or more data points.\n");
81 return;
82 }
83
84 // CHECK DATA
85 if((aX==NULL)||(aY==NULL))
86 {
87 printf("PiecewiseLinearFunction: ERROR- NULL arrays for data points encountered.\n");
88 return;
89 }
90
91 // INDEPENDENT VALUES (KNOT SEQUENCE)
92 _x.setSize(0);
93 _x.append(aN,aX);
94
95 _y.setSize(0);
96 _y.append(aN,aY);
97
98 // Calculate the slope coefficients
99 calcCoefficients();
100 }
101 //_____________________________________________________________________________
102 /**
103 * Copy constructor.
104 * All data members of the specified function are copied.
105 *
106 * @param aFunction PiecewiseLinearFunction object to be copied.
107 */
PiecewiseLinearFunction(const PiecewiseLinearFunction & aFunction)108 PiecewiseLinearFunction::PiecewiseLinearFunction(const PiecewiseLinearFunction &aFunction) :
109 Function(aFunction),
110 _x(_propX.getValueDblArray()),
111 _y(_propY.getValueDblArray()),
112 _b(0.0)
113 {
114 setEqual(aFunction);
115 }
116
117
118 //=============================================================================
119 // CONSTRUCTION
120 //=============================================================================
121 //_____________________________________________________________________________
122 /**
123 * Set all member variables to their NULL or default values.
124 */
setNull()125 void PiecewiseLinearFunction::setNull()
126 {
127 setupProperties();
128 }
129 //_____________________________________________________________________________
130 /**
131 * Set up the serialized member variables. This involves both generating
132 * the properties and connecting them to the local pointers used to access
133 * the serialized member variables.
134 */
setupProperties()135 void PiecewiseLinearFunction::setupProperties()
136 {
137 // X- INDEPENDENT VARIABLES
138 _propX.setName("x");
139 Array<double> x(0.0);
140 _propX.setValue(x);
141 _propertySet.append( &_propX );
142
143 // Y- DEPENDENT VARIABLES
144 _propY.setName("y");
145 Array<double> y(0.0);
146 _propY.setValue(y);
147 _propertySet.append( &_propY );
148 }
149 //_____________________________________________________________________________
150 /**
151 * Set all member variables equal to the members of another object.
152 * Note that this method is private. It is only meant for copying the data
153 * members defined in this class. It does not, for example, make any changes
154 * to data members of base classes.
155 */
setEqual(const PiecewiseLinearFunction & aFunction)156 void PiecewiseLinearFunction::setEqual(const PiecewiseLinearFunction &aFunction)
157 {
158 setNull();
159
160 // CHECK ARRAY SIZES
161 if(aFunction.getSize()<=0) return;
162
163 // ALLOCATE ARRAYS
164 _x = aFunction._x;
165 _y = aFunction._y;
166 _b = aFunction._b;
167 }
168 //_____________________________________________________________________________
169 /**
170 * Initialize the function with X and Y values.
171 *
172 * @param aN the number of X and Y values
173 * @param aXValues the X values
174 * @param aYValues the Y values
175 */
init(Function * aFunction)176 void PiecewiseLinearFunction::init(Function* aFunction)
177 {
178 if (aFunction == NULL)
179 return;
180
181 PiecewiseLinearFunction* lf = dynamic_cast<PiecewiseLinearFunction*>(aFunction);
182 if (lf != NULL) {
183 setEqual(*lf);
184 } else {
185 XYFunctionInterface xyFunc(aFunction);
186 if (xyFunc.getNumberOfPoints() == 0) {
187 // A PiecewiseLinearFunction must have at least 2 data points.
188 // If aFunction is a Constant, use its Y value for both data points.
189 // If it is not, make up two data points.
190 double x[2] = {0.0, 1.0}, y[2];
191 Constant* cons = dynamic_cast<Constant*>(aFunction);
192 if (cons != NULL) {
193 y[0] = y[1] = cons->calcValue(SimTK::Vector(0));
194 } else {
195 y[0] = y[1] = 1.0;
196 }
197 *this = PiecewiseLinearFunction(2, x, y);
198 } else if (xyFunc.getNumberOfPoints() == 1) {
199 double x[2], y[2];
200 x[0] = xyFunc.getXValues()[0];
201 x[1] = x[0] + 1.0;
202 y[0] = y[1] = xyFunc.getYValues()[0];
203 *this = PiecewiseLinearFunction(2, x, y);
204 } else {
205 *this = PiecewiseLinearFunction(xyFunc.getNumberOfPoints(),
206 xyFunc.getXValues(), xyFunc.getYValues());
207 }
208 }
209 }
210
211 //=============================================================================
212 // OPERATORS
213 //=============================================================================
214 //_____________________________________________________________________________
215 /**
216 * Assignment operator.
217 * Note that data members of the base class are also assigned.
218 *
219 * @return Reference to this object.
220 */
operator =(const PiecewiseLinearFunction & aFunction)221 PiecewiseLinearFunction& PiecewiseLinearFunction::operator=(const PiecewiseLinearFunction &aFunction)
222 {
223 // BASE CLASS
224 Function::operator=(aFunction);
225
226 // DATA
227 setEqual(aFunction);
228
229 return(*this);
230 }
231
232
233 //=============================================================================
234 // SET AND GET
235 //=============================================================================
236 //-----------------------------------------------------------------------------
237 // NUMBER OF DATA POINTS (N)
238 //-----------------------------------------------------------------------------
239 //_____________________________________________________________________________
240 /**
241 * Get size or number of independent data points (or number of coefficients)
242 * used to construct the function.
243 *
244 * @return Number of data points (or number of coefficients).
245 */
getSize() const246 int PiecewiseLinearFunction::getSize() const
247 {
248 return(_x.getSize());
249 }
250
251 //-----------------------------------------------------------------------------
252 // X AND COEFFICIENTS
253 //-----------------------------------------------------------------------------
254 //_____________________________________________________________________________
255 /**
256 * Get the array of independent variables used to construct the function.
257 * For the number of independent variable data points use getN().
258 *
259 * @return Pointer to the independent variable data points.
260 * @see getN();
261 */
getX() const262 const Array<double>& PiecewiseLinearFunction::getX() const
263 {
264 return(_x);
265 }
266 //_____________________________________________________________________________
267 /**
268 * Get the array of Y values for the function.
269 * For the number of Y values use getNX().
270 *
271 * @return Pointer to the coefficients.
272 * @see getCoefficients();
273 */
getY() const274 const Array<double>& PiecewiseLinearFunction::getY() const
275 {
276 return(_y);
277 }
278 //_____________________________________________________________________________
279 /**
280 * Get the array of independent variables used to construct the function.
281 * For the number of independent variable data points use getN().
282 *
283 * @return Pointer to the independent variable data points.
284 * @see getN();
285 */
getXValues() const286 const double* PiecewiseLinearFunction::getXValues() const
287 {
288 return(&_x[0]);
289 }
290 //_____________________________________________________________________________
291 /**
292 * Get the array of dependent variables used to construct the function.
293 *
294 * @return Pointer to the dependent variable data points.
295 */
getYValues() const296 const double* PiecewiseLinearFunction::getYValues() const
297 {
298 return(&_y[0]);
299 }
300
301
302 //-----------------------------------------------------------------------------
303 // UPDATE FROM XML NODE
304 //-----------------------------------------------------------------------------
305 //_____________________________________________________________________________
306 /**
307 * Update this object based on its XML node.
308 */
updateFromXMLNode(SimTK::Xml::Element & aNode,int versionNumber)309 void PiecewiseLinearFunction::updateFromXMLNode(SimTK::Xml::Element& aNode, int versionNumber)
310 {
311 Function::updateFromXMLNode(aNode, versionNumber);
312 calcCoefficients();
313 }
314
getX(int aIndex) const315 double PiecewiseLinearFunction::getX(int aIndex) const
316 {
317 if (aIndex >= 0 && aIndex < _x.getSize())
318 return _x.get(aIndex);
319 else {
320 throw Exception("PiecewiseLinearFunction::getX(): index out of bounds.");
321 return 0.0;
322 }
323 }
324
getY(int aIndex) const325 double PiecewiseLinearFunction::getY(int aIndex) const
326 {
327 if (aIndex >= 0 && aIndex < _y.getSize())
328 return _y.get(aIndex);
329 else {
330 throw Exception("PiecewiseLinearFunction::getY(): index out of bounds.");
331 return 0.0;
332 }
333 }
334
setX(int aIndex,double aValue)335 void PiecewiseLinearFunction::setX(int aIndex, double aValue)
336 {
337 if (aIndex >= 0 && aIndex < _x.getSize()) {
338 _x[aIndex] = aValue;
339 calcCoefficients();
340 } else {
341 throw Exception("PiecewiseLinearFunction::setX(): index out of bounds.");
342 }
343 }
344
setY(int aIndex,double aValue)345 void PiecewiseLinearFunction::setY(int aIndex, double aValue)
346 {
347 if (aIndex >= 0 && aIndex < _y.getSize()) {
348 _y[aIndex] = aValue;
349 calcCoefficients();
350 } else {
351 throw Exception("PiecewiseLinearFunction::setY(): index out of bounds.");
352 }
353 }
354
deletePoint(int aIndex)355 bool PiecewiseLinearFunction::deletePoint(int aIndex)
356 {
357 if (_x.getSize() > 2 && _y.getSize() > 2 &&
358 aIndex < _x.getSize() && aIndex < _y.getSize()) {
359 _x.remove(aIndex);
360 _y.remove(aIndex);
361
362 // Recalculate the slopes
363 calcCoefficients();
364 return true;
365 }
366
367 return false;
368 }
369
deletePoints(const Array<int> & indices)370 bool PiecewiseLinearFunction::deletePoints(const Array<int>& indices)
371 {
372 bool pointsDeleted = false;
373 int numPointsLeft = _x.getSize() - indices.getSize();
374
375 if (numPointsLeft >= 2) {
376 // Assume the indices are sorted highest to lowest
377 for (int i=0; i<indices.getSize(); i++) {
378 int index = indices.get(i);
379 if (index >= 0 && index < _x.getSize()) {
380 _x.remove(index);
381 _y.remove(index);
382 pointsDeleted = true;
383 }
384 }
385 if (pointsDeleted)
386 calcCoefficients();
387 }
388
389 return pointsDeleted;
390 }
391
addPoint(double aX,double aY)392 int PiecewiseLinearFunction::addPoint(double aX, double aY)
393 {
394 int i=0;
395 for (i=0; i<_x.getSize(); i++)
396 if (_x[i] > aX)
397 break;
398
399 _x.insert(i, aX);
400 _y.insert(i, aY);
401
402 // Recalculate the slopes
403 calcCoefficients();
404
405 return i;
406 }
407
408 //=============================================================================
409 // EVALUATION
410 //=============================================================================
calcCoefficients()411 void PiecewiseLinearFunction::calcCoefficients()
412 {
413 int n = _x.getSize();
414
415 if (n < 2)
416 return;
417
418 _b.setSize(n);
419
420 for (int i=0; i<n-1; i++) {
421 double range = MAX(TINY_NUMBER, _x[i+1] - _x[i]);
422 _b[i] = (_y[i+1] - _y[i]) / range;
423 }
424 _b[n-1] = _b[n-2];
425 }
426
calcValue(const Vector & x) const427 double PiecewiseLinearFunction::calcValue(const Vector& x) const
428 {
429 int n = _x.getSize();
430 double aX = x[0];
431
432 if (aX < _x[0])
433 return _y[0] + (aX - _x[0]) * _b[0];
434 else if (aX > _x[n-1])
435 return _y[n-1] + (aX - _x[n-1]) * _b[n-1];
436
437 /* Check to see if the abscissa is close to one of the end points
438 * (the binary search method doesn't work well if you are at one of the
439 * end points.
440 */
441 if (EQUAL_WITHIN_ERROR(aX, _x[0]))
442 return _y[0];
443 else if (EQUAL_WITHIN_ERROR(aX,_x[n-1]))
444 return _y[n-1];
445
446 // Do a binary search to find which two points the abscissa is between.
447 int k, i = 0;
448 int j = n;
449 while (1)
450 {
451 k = (i+j)/2;
452 if (aX < _x[k])
453 j = k;
454 else if (aX > _x[k+1])
455 i = k;
456 else
457 break;
458 }
459
460 return _y[k] + (aX - _x[k]) * _b[k];
461 }
462
calcDerivative(const std::vector<int> & derivComponents,const Vector & x) const463 double PiecewiseLinearFunction::calcDerivative(const std::vector<int>& derivComponents, const Vector& x) const
464 {
465 if (derivComponents.size() == 0)
466 return SimTK::NaN;
467 if (derivComponents.size() > 1)
468 return 0.0;
469
470 int n = _x.getSize();
471 double aX = x[0];
472
473 if (aX < _x[0]) {
474 return _b[0];
475 } else if (aX > _x[n-1]) {
476 return _b[n-1];
477 }
478
479 /* Check to see if the abscissa is close to one of the end points
480 * (the binary search method doesn't work well if you are at one of the
481 * end points.
482 */
483 if (EQUAL_WITHIN_ERROR(aX, _x[0])) {
484 return _b[0];
485 } else if (EQUAL_WITHIN_ERROR(aX,_x[n-1])) {
486 return _b[n-1];
487 }
488
489 // Do a binary search to find which two points the abscissa is between.
490 int k, i = 0;
491 int j = n;
492 while (1)
493 {
494 k = (i+j)/2;
495 if (aX < _x[k])
496 j = k;
497 else if (aX > _x[k+1])
498 i = k;
499 else
500 break;
501 }
502
503 return _b[k];
504 }
505
getArgumentSize() const506 int PiecewiseLinearFunction::getArgumentSize() const
507 {
508 return 1;
509 }
510
getMaxDerivativeOrder() const511 int PiecewiseLinearFunction::getMaxDerivativeOrder() const
512 {
513 return std::numeric_limits<int>::max();
514 }
515
createSimTKFunction() const516 SimTK::Function* PiecewiseLinearFunction::createSimTKFunction() const {
517 return new FunctionAdapter(*this);
518 }
519