001/*
002 * Copyright (C) 2012 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 */
014
015package com.google.common.math;
016
017import static com.google.common.base.Preconditions.checkArgument;
018import static com.google.common.math.DoubleUtils.isFinite;
019import static java.lang.Double.NaN;
020
021import com.google.common.annotations.Beta;
022import com.google.common.annotations.GwtIncompatible;
023import com.google.errorprone.annotations.concurrent.LazyInit;
024
025/**
026 * The representation of a linear transformation between real numbers {@code x} and {@code y}.
027 * Graphically, this is the specification of a straight line on a plane. The transformation can be
028 * expressed as {@code y = m * x + c} for finite {@code m} and {@code c}, unless it is a vertical
029 * transformation in which case {@code x} has a constant value for all {@code y}. In the
030 * non-vertical case, {@code m} is the slope of the transformation (and a horizontal transformation
031 * has zero slope).
032 *
033 * @author Pete Gillin
034 * @since 20.0
035 */
036@Beta
037@GwtIncompatible
038public abstract class LinearTransformation {
039
040  /**
041   * Start building an instance which maps {@code x = x1} to {@code y = y1}. Both arguments must be
042   * finite. Call either {@link LinearTransformationBuilder#and} or
043   * {@link LinearTransformationBuilder#withSlope} on the returned object to finish building the
044   * instance.
045   */
046  public static LinearTransformationBuilder mapping(double x1, double y1) {
047    checkArgument(isFinite(x1) && isFinite(y1));
048    return new LinearTransformationBuilder(x1, y1);
049  }
050
051  /**
052   * This is an intermediate stage in the construction process. It is returned by
053   * {@link LinearTransformation#mapping}. You almost certainly don't want to keep instances around,
054   * but instead use method chaining. This represents a single point mapping, i.e. a mapping between
055   * one {@code x} and {@code y} value pair.
056   */
057  public static final class LinearTransformationBuilder {
058
059    private final double x1;
060    private final double y1;
061
062    private LinearTransformationBuilder(double x1, double y1) {
063      this.x1 = x1;
064      this.y1 = y1;
065    }
066
067    /**
068     * Finish building an instance which also maps {@code x = x2} to {@code y = y2}. These values
069     * must not both be identical to the values given in the first mapping. If only the {@code x}
070     * values are identical, the transformation is vertical. If only the {@code y} values are
071     * identical, the transformation is horizontal (i.e. the slope is zero).
072     */
073    public LinearTransformation and(double x2, double y2) {
074      checkArgument(isFinite(x2) && isFinite(y2));
075      if (x2 == x1) {
076        checkArgument(y2 != y1);
077        return new VerticalLinearTransformation(x1);
078      } else {
079        return withSlope((y2 - y1) / (x2 - x1));
080      }
081    }
082
083    /**
084     * Finish building an instance with the given slope, i.e. the rate of change of {@code y} with
085     * respect to {@code x}. The slope must not be {@code NaN}. It may be infinite, in which case
086     * the transformation is vertical. (If it is zero, the transformation is horizontal.)
087     */
088    public LinearTransformation withSlope(double slope) {
089      checkArgument(!Double.isNaN(slope));
090      if (isFinite(slope)) {
091        double yIntercept = y1 - x1 * slope;
092        return new RegularLinearTransformation(slope, yIntercept);
093      } else {
094        return new VerticalLinearTransformation(x1);
095      }
096    }
097  }
098
099  /**
100   * Builds an instance representing a vertical transformation with a constant value of {@code x}.
101   * (The inverse of this will be a horizontal transformation.)
102   */
103  public static LinearTransformation vertical(double x) {
104    checkArgument(isFinite(x));
105    return new VerticalLinearTransformation(x);
106  }
107
108  /**
109   * Builds an instance representing a horizontal transformation with a constant value of {@code y}.
110   * (The inverse of this will be a vertical transformation.)
111   */
112  public static LinearTransformation horizontal(double y) {
113    checkArgument(isFinite(y));
114    double slope = 0.0;
115    return new RegularLinearTransformation(slope, y);
116  }
117
118  /**
119   * Builds an instance for datasets which contains {@link Double#NaN}. The {@link #isHorizontal}
120   * and {@link #isVertical} methods return {@code false} and the {@link #slope}, and
121   * {@link #transform} methods all return {@link Double#NaN}. The {@link #inverse} method returns
122   * the same instance.
123   */
124  public static LinearTransformation forNaN() {
125    return NaNLinearTransformation.INSTANCE;
126  }
127
128  /**
129   * Returns whether this is a vertical transformation.
130   */
131  public abstract boolean isVertical();
132
133  /**
134   * Returns whether this is a horizontal transformation.
135   */
136  public abstract boolean isHorizontal();
137
138  /**
139   * Returns the slope of the transformation, i.e. the rate of change of {@code y} with respect to
140   * {@code x}. This must not be called on a vertical transformation (i.e. when
141   * {@link #isVertical()} is true).
142   */
143  public abstract double slope();
144
145  /**
146   * Returns the {@code y} corresponding to the given {@code x}. This must not be called on a
147   * vertical transformation (i.e. when {@link #isVertical()} is true).
148   */
149  public abstract double transform(double x);
150
151  /**
152   * Returns the inverse linear transformation. The inverse of a horizontal transformation is a
153   * vertical transformation, and vice versa. The inverse of the {@link #forNaN} transformation is
154   * itself. In all other cases, the inverse is a transformation such that applying both the
155   * original transformation and its inverse to a value gives you the original value give-or-take
156   * numerical errors. Calling this method multiple times on the same instance will always return
157   * the same instance. Calling this method on the result of calling this method on an instance will
158   * always return that original instance.
159   */
160  public abstract LinearTransformation inverse();
161
162  private static final class RegularLinearTransformation extends LinearTransformation {
163
164    final double slope;
165    final double yIntercept;
166
167    @LazyInit
168    LinearTransformation inverse;
169
170    RegularLinearTransformation(double slope, double yIntercept) {
171      this.slope = slope;
172      this.yIntercept = yIntercept;
173      this.inverse = null; // to be lazily initialized
174    }
175
176    RegularLinearTransformation(double slope, double yIntercept, LinearTransformation inverse) {
177      this.slope = slope;
178      this.yIntercept = yIntercept;
179      this.inverse = inverse;
180    }
181
182    @Override
183    public boolean isVertical() {
184      return false;
185    }
186
187    @Override
188    public boolean isHorizontal() {
189      return (slope == 0.0);
190    }
191
192    @Override
193    public double slope() {
194      return slope;
195    }
196
197    @Override
198    public double transform(double x) {
199      return x * slope + yIntercept;
200    }
201
202    @Override
203    public LinearTransformation inverse() {
204      LinearTransformation result = inverse;
205      return (result == null) ? inverse = createInverse() : result;
206    }
207
208    @Override
209    public String toString() {
210      return String.format("y = %g * x + %g", slope, yIntercept);
211    }
212
213    private LinearTransformation createInverse() {
214      if (slope != 0.0) {
215        return new RegularLinearTransformation(1.0 / slope, -1.0 * yIntercept / slope, this);
216      } else {
217        return new VerticalLinearTransformation(yIntercept, this);
218      }
219    }
220  }
221
222  private static final class VerticalLinearTransformation extends LinearTransformation {
223
224    final double x;
225
226    @LazyInit
227    LinearTransformation inverse;
228
229    VerticalLinearTransformation(double x) {
230      this.x = x;
231      this.inverse = null; // to be lazily initialized
232    }
233
234    VerticalLinearTransformation(double x, LinearTransformation inverse) {
235      this.x = x;
236      this.inverse = inverse;
237    }
238
239    @Override
240    public boolean isVertical() {
241      return true;
242    }
243
244    @Override
245    public boolean isHorizontal() {
246      return false;
247    }
248
249    @Override
250    public double slope() {
251      throw new IllegalStateException();
252    }
253
254    @Override
255    public double transform(double x) {
256      throw new IllegalStateException();
257    }
258
259    @Override
260    public LinearTransformation inverse() {
261      LinearTransformation result = inverse;
262      return (result == null) ? inverse = createInverse() : result;
263    }
264
265    @Override
266    public String toString() {
267      return String.format("x = %g", x);
268    }
269
270    private LinearTransformation createInverse() {
271      return new RegularLinearTransformation(0.0, x, this);
272    }
273  }
274
275  private static final class NaNLinearTransformation extends LinearTransformation {
276
277    static final NaNLinearTransformation INSTANCE = new NaNLinearTransformation();
278
279    @Override
280    public boolean isVertical() {
281      return false;
282    }
283
284    @Override
285    public boolean isHorizontal() {
286      return false;
287    }
288
289    @Override
290    public double slope() {
291      return NaN;
292    }
293
294    @Override
295    public double transform(double x) {
296      return NaN;
297    }
298
299    @Override
300    public LinearTransformation inverse() {
301      return this;
302    }
303
304    @Override
305    public String toString() {
306      return "NaN";
307    }
308  }
309}