001    /**
002     * Copyright (c) 2010, SIB. All rights reserved.
003     * 
004     * SIB (Swiss Institute of Bioinformatics) - http://www.isb-sib.ch Host -
005     * https://sourceforge.net/projects/javaprotlib/
006     * 
007     * Redistribution and use in source and binary forms, with or without
008     * modification, are permitted provided that the following conditions are met:
009     * Redistributions of source code must retain the above copyright notice, this
010     * list of conditions and the following disclaimer. Redistributions in binary
011     * form must reproduce the above copyright notice, this list of conditions and
012     * the following disclaimer in the documentation and/or other materials provided
013     * with the distribution. Neither the name of the SIB/GENEBIO nor the names of
014     * its contributors may be used to endorse or promote products derived from this
015     * software without specific prior written permission.
016     * 
017     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
020     * ARE DISCLAIMED. IN NO EVENT SHALL SIB/GENEBIO BE LIABLE FOR ANY DIRECT,
021     * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023     * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
024     * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027     */
028    package org.expasy.jpl.commons.base.math;
029    
030    
031    import java.util.ArrayList;
032    
033    
034    /**
035     * Class to fit straight line to a set of 2D data points. The methods
036     * implemented in Press, Teukolsky, Vetterling, Flannery, Numerical Recipies,
037     * second edition are used.
038     * 
039     * @author mueller
040     * 
041     * @version 1.0
042     * 
043     */
044    public final class StraightLineFitter {
045            
046            /** x values of data points */
047            private ArrayList<Double> xValues;
048            /** y values of data points */
049            private ArrayList<Double> yValues;
050            /** Variance associated with y-values of data points */
051            private ArrayList<Double> yVariance;
052            /** Offset of fitted straight line */
053            private double offset;
054            /** Slope of fitted straight line */
055            private double slope;
056            /** Boolean specifying whether fit is up to date */
057            boolean fitIsUpToDate;
058            
059            /**
060             * Constructor : y-variance is set to 1 for all data points
061             * 
062             * @param xValues : x-values of the data points
063             * @param yValues : y-values of the data points
064             */
065            public StraightLineFitter(final ArrayList<Double> xValues,
066                final ArrayList<Double> yValues) throws IllegalArgumentException {
067                    setXYValues(xValues, yValues);
068            }
069            
070            /**
071             * Constructor
072             * 
073             * @param xValues : x-values of the data points
074             * @param yValues : y-values of the data points
075             * @param yVariance : variance associated with y-values of data points
076             */
077            public StraightLineFitter(final ArrayList<Double> xValues,
078                final ArrayList<Double> yValues, final ArrayList<Double> yVariance)
079                throws IllegalArgumentException {
080                    setXYValues(xValues, yValues, yVariance);
081            }
082            
083            /**
084             * Sets x-,y-values. y-variance is set to 1 for all data points
085             * 
086             * @param xValues : x-values of the data points
087             * @param yValues : y-values of the data points
088             */
089            public void setXYValues(final ArrayList<Double> xValues,
090                final ArrayList<Double> yValues) throws IllegalArgumentException {
091                    this.xValues = xValues;
092                    this.yValues = yValues;
093                    
094                    yVariance = new ArrayList<Double>();
095                    for (int i = 0; i < yValues.size(); i++) {
096                            yVariance.add(1.0);
097                    }
098                    
099                    fitIsUpToDate = false;
100                    
101                    if (xValues.size() != yValues.size()) {
102                            throw new IllegalArgumentException(
103                                "Incomaptible lengths of xValues and yValues");
104                    }
105            }
106            
107            /**
108             * Sets x-,y- and variance values
109             * 
110             * @param xValues : x-values of the data points
111             * @param yValues : y-values of the data points
112             * @param yVariance : variance associated with y-values of data points
113             */
114            public void setXYValues(final ArrayList<Double> xValues,
115                final ArrayList<Double> yValues, final ArrayList<Double> yVariance)
116                throws IllegalArgumentException {
117                    this.xValues = xValues;
118                    this.yValues = yValues;
119                    this.yVariance = yVariance;
120                    
121                    fitIsUpToDate = false;
122                    
123                    if ((xValues.size() != yValues.size())
124                        || (xValues.size() != yVariance.size())
125                        || (yValues.size() != yVariance.size())) {
126                            throw new IllegalArgumentException(
127                                "Incomaptible lengths of xValues, yValues or yVariance");
128                    }
129            }
130            
131            /**
132             * Get the x-values of the data points
133             * 
134             * @return x-values of the data points
135             */
136            public ArrayList<Double> getXValues() {
137                    return xValues;
138            }
139            
140            /**
141             * Get the y-values of the data points
142             * 
143             * @return y-values of the data points
144             */
145            public ArrayList<Double> getYValues() {
146                    return yValues;
147            }
148            
149            /**
150             * Get y-variance of data points
151             * 
152             * @return y-variance of data points
153             */
154            public ArrayList<Double> getYVariance() {
155                    return yVariance;
156            }
157            
158            /**
159             * Gets offset of fitted straight line
160             * 
161             * @return offset
162             */
163            public double getFitOffset() {
164                    if (!fitIsUpToDate) {
165                            calcFit();
166                    }
167                    return offset;
168            }
169            
170            /**
171             * Gets slope of fitted straight line
172             * 
173             * @return slope
174             */
175            public double getFitSlope() {
176                    if (!fitIsUpToDate) {
177                            calcFit();
178                    }
179                    return slope;
180            }
181            
182            /**
183             * Returns the values of the fitted straight line at xValues
184             * 
185             * @return values of the fitted straight line at xValues
186             */
187            public ArrayList<Double> getFittedValues() {
188                    if (!fitIsUpToDate) {
189                            calcFit();
190                    }
191                    
192                    final ArrayList<Double> fittedValues = new ArrayList<Double>();
193                    for (int i = 0; i < yValues.size(); i++) {
194                            fittedValues.add(i, slope * xValues.get(i) + offset);
195                    }
196                    
197                    return fittedValues;
198            }
199            
200            /**
201             * Calculates straight line fit
202             */
203            private void calcFit() {
204                    int i, nr;
205                    double s, sx, sy, stt, t, r, sig;
206                    
207                    s = sx = sy = stt = 0.0;
208                    nr = xValues.size();
209                    
210                    slope = 0.0;
211                    offset = 0.0;
212                    
213                    if (nr == 0) {
214                            return;
215                    }
216                    
217                    // only one point : do nothing
218                    if (nr == 1) {
219                            slope = 1.0;
220                            offset = yValues.get(0) - xValues.get(0);
221                            return;
222                    }
223                    
224                    // only two points : straight calculation of slope and offset
225                    if (nr == 2) {
226                            slope =
227                                (yValues.get(0) - yValues.get(1))
228                                    / (xValues.get(0) - xValues.get(1));
229                            offset = yValues.get(0) - slope * xValues.get(0);
230                            return;
231                    }
232                    
233                    // more than two points ..
234                    for (i = 0; i < nr; i++) {
235                            sig = 1.0 / yVariance.get(i);
236                            s += sig;
237                            sx += xValues.get(i) * sig;
238                            sy += yValues.get(i) * sig;
239                    }
240                    
241                    r = sx / s;
242                    for (i = 0; i < nr; i++) {
243                            sig = Math.sqrt(1.0 / yVariance.get(i));
244                            t = (xValues.get(i) - r) * sig;
245                            stt += t * t;
246                            slope += t * yValues.get(i) * sig;
247                    }
248                    
249                    slope /= stt;
250                    offset = (sy - sx * slope) / s;
251                    
252                    fitIsUpToDate = true;
253            }
254            
255    }