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