Home | History | Annotate | Download | only in fitting
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package org.apache.commons.math.optimization.fitting;
     19 
     20 import java.util.Arrays;
     21 import java.util.Comparator;
     22 
     23 import org.apache.commons.math.exception.util.LocalizedFormats;
     24 import org.apache.commons.math.exception.NumberIsTooSmallException;
     25 import org.apache.commons.math.exception.OutOfRangeException;
     26 import org.apache.commons.math.exception.ZeroException;
     27 import org.apache.commons.math.exception.NullArgumentException;
     28 
     29 /**
     30  * Guesses the parameters ({@code a}, {@code b}, {@code c}, and {@code d})
     31  * of a {@link ParametricGaussianFunction} based on the specified observed
     32  * points.
     33  *
     34  * @since 2.2
     35  * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 aot 2010) $
     36  */
     37 public class GaussianParametersGuesser {
     38 
     39     /** Observed points. */
     40     private final WeightedObservedPoint[] observations;
     41 
     42     /** Resulting guessed parameters. */
     43     private double[] parameters;
     44 
     45     /**
     46      * Constructs instance with the specified observed points.
     47      *
     48      * @param observations observed points upon which should base guess
     49      */
     50     public GaussianParametersGuesser(WeightedObservedPoint[] observations) {
     51         if (observations == null) {
     52             throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
     53         }
     54         if (observations.length < 3) {
     55             throw new NumberIsTooSmallException(observations.length, 3, true);
     56         }
     57         this.observations = observations.clone();
     58     }
     59 
     60     /**
     61      * Guesses the parameters based on the observed points.
     62      *
     63      * @return guessed parameters array <code>{a, b, c, d}</code>
     64      */
     65     public double[] guess() {
     66         if (parameters == null) {
     67             parameters = basicGuess(observations);
     68         }
     69         return parameters.clone();
     70     }
     71 
     72     /**
     73      * Guesses the parameters based on the specified observed points.
     74      *
     75      * @param points observed points upon which should base guess
     76      *
     77      * @return guessed parameters array <code>{a, b, c, d}</code>
     78      */
     79     private double[] basicGuess(WeightedObservedPoint[] points) {
     80         Arrays.sort(points, createWeightedObservedPointComparator());
     81         double[] params = new double[4];
     82 
     83         int minYIdx = findMinY(points);
     84         params[0] = points[minYIdx].getY();
     85 
     86         int maxYIdx = findMaxY(points);
     87         params[1] = points[maxYIdx].getY();
     88         params[2] = points[maxYIdx].getX();
     89 
     90         double fwhmApprox;
     91         try {
     92             double halfY = params[0] + ((params[1] - params[0]) / 2.0);
     93             double fwhmX1 = interpolateXAtY(points, maxYIdx, -1, halfY);
     94             double fwhmX2 = interpolateXAtY(points, maxYIdx, +1, halfY);
     95             fwhmApprox = fwhmX2 - fwhmX1;
     96         } catch (OutOfRangeException e) {
     97             fwhmApprox = points[points.length - 1].getX() - points[0].getX();
     98         }
     99         params[3] = fwhmApprox / (2.0 * Math.sqrt(2.0 * Math.log(2.0)));
    100 
    101         return params;
    102     }
    103 
    104     /**
    105      * Finds index of point in specified points with the smallest Y.
    106      *
    107      * @param points points to search
    108      *
    109      * @return index in specified points array
    110      */
    111     private int findMinY(WeightedObservedPoint[] points) {
    112         int minYIdx = 0;
    113         for (int i = 1; i < points.length; i++) {
    114             if (points[i].getY() < points[minYIdx].getY()) {
    115                 minYIdx = i;
    116             }
    117         }
    118         return minYIdx;
    119     }
    120 
    121     /**
    122      * Finds index of point in specified points with the largest Y.
    123      *
    124      * @param points points to search
    125      *
    126      * @return index in specified points array
    127      */
    128     private int findMaxY(WeightedObservedPoint[] points) {
    129         int maxYIdx = 0;
    130         for (int i = 1; i < points.length; i++) {
    131             if (points[i].getY() > points[maxYIdx].getY()) {
    132                 maxYIdx = i;
    133             }
    134         }
    135         return maxYIdx;
    136     }
    137 
    138     /**
    139      * Interpolates using the specified points to determine X at the specified
    140      * Y.
    141      *
    142      * @param points points to use for interpolation
    143      * @param startIdx index within points from which to start search for
    144      *        interpolation bounds points
    145      * @param idxStep index step for search for interpolation bounds points
    146      * @param y Y value for which X should be determined
    147      *
    148      * @return value of X at the specified Y
    149      *
    150      * @throws IllegalArgumentException if idxStep is 0
    151      * @throws OutOfRangeException if specified <code>y</code> is not within the
    152      *         range of the specified <code>points</code>
    153      */
    154     private double interpolateXAtY(WeightedObservedPoint[] points,
    155                                    int startIdx, int idxStep, double y) throws OutOfRangeException {
    156         if (idxStep == 0) {
    157             throw new ZeroException();
    158         }
    159         WeightedObservedPoint[] twoPoints = getInterpolationPointsForY(points, startIdx, idxStep, y);
    160         WeightedObservedPoint pointA = twoPoints[0];
    161         WeightedObservedPoint pointB = twoPoints[1];
    162         if (pointA.getY() == y) {
    163             return pointA.getX();
    164         }
    165         if (pointB.getY() == y) {
    166             return pointB.getX();
    167         }
    168         return pointA.getX() +
    169                (((y - pointA.getY()) * (pointB.getX() - pointA.getX())) / (pointB.getY() - pointA.getY()));
    170     }
    171 
    172     /**
    173      * Gets the two bounding interpolation points from the specified points
    174      * suitable for determining X at the specified Y.
    175      *
    176      * @param points points to use for interpolation
    177      * @param startIdx index within points from which to start search for
    178      *        interpolation bounds points
    179      * @param idxStep index step for search for interpolation bounds points
    180      * @param y Y value for which X should be determined
    181      *
    182      * @return array containing two points suitable for determining X at the
    183      *         specified Y
    184      *
    185      * @throws IllegalArgumentException if idxStep is 0
    186      * @throws OutOfRangeException if specified <code>y</code> is not within the
    187      *         range of the specified <code>points</code>
    188      */
    189     private WeightedObservedPoint[] getInterpolationPointsForY(WeightedObservedPoint[] points,
    190                                                                int startIdx, int idxStep, double y)
    191         throws OutOfRangeException {
    192         if (idxStep == 0) {
    193             throw new ZeroException();
    194         }
    195         for (int i = startIdx;
    196              (idxStep < 0) ? (i + idxStep >= 0) : (i + idxStep < points.length);
    197              i += idxStep) {
    198             if (isBetween(y, points[i].getY(), points[i + idxStep].getY())) {
    199                 return (idxStep < 0) ?
    200                        new WeightedObservedPoint[] { points[i + idxStep], points[i] } :
    201                        new WeightedObservedPoint[] { points[i], points[i + idxStep] };
    202             }
    203         }
    204 
    205         double minY = Double.POSITIVE_INFINITY;
    206         double maxY = Double.NEGATIVE_INFINITY;
    207         for (final WeightedObservedPoint point : points) {
    208             minY = Math.min(minY, point.getY());
    209             maxY = Math.max(maxY, point.getY());
    210         }
    211         throw new OutOfRangeException(y, minY, maxY);
    212 
    213     }
    214 
    215     /**
    216      * Determines whether a value is between two other values.
    217      *
    218      * @param value value to determine whether is between <code>boundary1</code>
    219      *        and <code>boundary2</code>
    220      * @param boundary1 one end of the range
    221      * @param boundary2 other end of the range
    222      *
    223      * @return true if <code>value</code> is between <code>boundary1</code> and
    224      *         <code>boundary2</code> (inclusive); false otherwise
    225      */
    226     private boolean isBetween(double value, double boundary1, double boundary2) {
    227         return (value >= boundary1 && value <= boundary2) ||
    228                (value >= boundary2 && value <= boundary1);
    229     }
    230 
    231     /**
    232      * Factory method creating <code>Comparator</code> for comparing
    233      * <code>WeightedObservedPoint</code> instances.
    234      *
    235      * @return new <code>Comparator</code> instance
    236      */
    237     private Comparator<WeightedObservedPoint> createWeightedObservedPointComparator() {
    238         return new Comparator<WeightedObservedPoint>() {
    239             public int compare(WeightedObservedPoint p1, WeightedObservedPoint p2) {
    240                 if (p1 == null && p2 == null) {
    241                     return 0;
    242                 }
    243                 if (p1 == null) {
    244                     return -1;
    245                 }
    246                 if (p2 == null) {
    247                     return 1;
    248                 }
    249                 if (p1.getX() < p2.getX()) {
    250                     return -1;
    251                 }
    252                 if (p1.getX() > p2.getX()) {
    253                     return 1;
    254                 }
    255                 if (p1.getY() < p2.getY()) {
    256                     return -1;
    257                 }
    258                 if (p1.getY() > p2.getY()) {
    259                     return 1;
    260                 }
    261                 if (p1.getWeight() < p2.getWeight()) {
    262                     return -1;
    263                 }
    264                 if (p1.getWeight() > p2.getWeight()) {
    265                     return 1;
    266                 }
    267                 return 0;
    268             }
    269         };
    270     }
    271 }
    272