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