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