• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.google.common.math;
16 
17 import static com.google.common.base.Preconditions.checkArgument;
18 import static com.google.common.math.DoubleUtils.isFinite;
19 import static java.lang.Double.NaN;
20 
21 import com.google.common.annotations.GwtIncompatible;
22 import com.google.common.annotations.J2ktIncompatible;
23 import com.google.errorprone.annotations.concurrent.LazyInit;
24 import javax.annotation.CheckForNull;
25 
26 /**
27  * The representation of a linear transformation between real numbers {@code x} and {@code y}.
28  * Graphically, this is the specification of a straight line on a plane. The transformation can be
29  * expressed as {@code y = m * x + c} for finite {@code m} and {@code c}, unless it is a vertical
30  * transformation in which case {@code x} has a constant value for all {@code y}. In the
31  * non-vertical case, {@code m} is the slope of the transformation (and a horizontal transformation
32  * has zero slope).
33  *
34  * @author Pete Gillin
35  * @since 20.0
36  */
37 @J2ktIncompatible
38 @GwtIncompatible
39 @ElementTypesAreNonnullByDefault
40 public abstract class LinearTransformation {
41 
42   /**
43    * Start building an instance which maps {@code x = x1} to {@code y = y1}. Both arguments must be
44    * finite. Call either {@link LinearTransformationBuilder#and} or {@link
45    * LinearTransformationBuilder#withSlope} on the returned object to finish building the instance.
46    */
mapping(double x1, double y1)47   public static LinearTransformationBuilder mapping(double x1, double y1) {
48     checkArgument(isFinite(x1) && isFinite(y1));
49     return new LinearTransformationBuilder(x1, y1);
50   }
51 
52   /**
53    * This is an intermediate stage in the construction process. It is returned by {@link
54    * LinearTransformation#mapping}. You almost certainly don't want to keep instances around, but
55    * instead use method chaining. This represents a single point mapping, i.e. a mapping between one
56    * {@code x} and {@code y} value pair.
57    *
58    * @since 20.0
59    */
60   public static final class LinearTransformationBuilder {
61 
62     private final double x1;
63     private final double y1;
64 
LinearTransformationBuilder(double x1, double y1)65     private LinearTransformationBuilder(double x1, double y1) {
66       this.x1 = x1;
67       this.y1 = y1;
68     }
69 
70     /**
71      * Finish building an instance which also maps {@code x = x2} to {@code y = y2}. These values
72      * must not both be identical to the values given in the first mapping. If only the {@code x}
73      * values are identical, the transformation is vertical. If only the {@code y} values are
74      * identical, the transformation is horizontal (i.e. the slope is zero).
75      */
and(double x2, double y2)76     public LinearTransformation and(double x2, double y2) {
77       checkArgument(isFinite(x2) && isFinite(y2));
78       if (x2 == x1) {
79         checkArgument(y2 != y1);
80         return new VerticalLinearTransformation(x1);
81       } else {
82         return withSlope((y2 - y1) / (x2 - x1));
83       }
84     }
85 
86     /**
87      * Finish building an instance with the given slope, i.e. the rate of change of {@code y} with
88      * respect to {@code x}. The slope must not be {@code NaN}. It may be infinite, in which case
89      * the transformation is vertical. (If it is zero, the transformation is horizontal.)
90      */
withSlope(double slope)91     public LinearTransformation withSlope(double slope) {
92       checkArgument(!Double.isNaN(slope));
93       if (isFinite(slope)) {
94         double yIntercept = y1 - x1 * slope;
95         return new RegularLinearTransformation(slope, yIntercept);
96       } else {
97         return new VerticalLinearTransformation(x1);
98       }
99     }
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    */
vertical(double x)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    */
horizontal(double y)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    */
forNaN()127   public static LinearTransformation forNaN() {
128     return NaNLinearTransformation.INSTANCE;
129   }
130 
131   /** Returns whether this is a vertical transformation. */
isVertical()132   public abstract boolean isVertical();
133 
134   /** Returns whether this is a horizontal transformation. */
isHorizontal()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    */
slope()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    */
transform(double x)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    */
inverse()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 
RegularLinearTransformation(double slope, double yIntercept)168     RegularLinearTransformation(double slope, double yIntercept) {
169       this.slope = slope;
170       this.yIntercept = yIntercept;
171       this.inverse = null; // to be lazily initialized
172     }
173 
RegularLinearTransformation(double slope, double yIntercept, LinearTransformation inverse)174     RegularLinearTransformation(double slope, double yIntercept, LinearTransformation inverse) {
175       this.slope = slope;
176       this.yIntercept = yIntercept;
177       this.inverse = inverse;
178     }
179 
180     @Override
isVertical()181     public boolean isVertical() {
182       return false;
183     }
184 
185     @Override
isHorizontal()186     public boolean isHorizontal() {
187       return (slope == 0.0);
188     }
189 
190     @Override
slope()191     public double slope() {
192       return slope;
193     }
194 
195     @Override
transform(double x)196     public double transform(double x) {
197       return x * slope + yIntercept;
198     }
199 
200     @Override
inverse()201     public LinearTransformation inverse() {
202       LinearTransformation result = inverse;
203       return (result == null) ? inverse = createInverse() : result;
204     }
205 
206     @Override
toString()207     public String toString() {
208       return String.format("y = %g * x + %g", slope, yIntercept);
209     }
210 
createInverse()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 
VerticalLinearTransformation(double x)226     VerticalLinearTransformation(double x) {
227       this.x = x;
228       this.inverse = null; // to be lazily initialized
229     }
230 
VerticalLinearTransformation(double x, LinearTransformation inverse)231     VerticalLinearTransformation(double x, LinearTransformation inverse) {
232       this.x = x;
233       this.inverse = inverse;
234     }
235 
236     @Override
isVertical()237     public boolean isVertical() {
238       return true;
239     }
240 
241     @Override
isHorizontal()242     public boolean isHorizontal() {
243       return false;
244     }
245 
246     @Override
slope()247     public double slope() {
248       throw new IllegalStateException();
249     }
250 
251     @Override
transform(double x)252     public double transform(double x) {
253       throw new IllegalStateException();
254     }
255 
256     @Override
inverse()257     public LinearTransformation inverse() {
258       LinearTransformation result = inverse;
259       return (result == null) ? inverse = createInverse() : result;
260     }
261 
262     @Override
toString()263     public String toString() {
264       return String.format("x = %g", x);
265     }
266 
createInverse()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
isVertical()277     public boolean isVertical() {
278       return false;
279     }
280 
281     @Override
isHorizontal()282     public boolean isHorizontal() {
283       return false;
284     }
285 
286     @Override
slope()287     public double slope() {
288       return NaN;
289     }
290 
291     @Override
transform(double x)292     public double transform(double x) {
293       return NaN;
294     }
295 
296     @Override
inverse()297     public LinearTransformation inverse() {
298       return this;
299     }
300 
301     @Override
toString()302     public String toString() {
303       return "NaN";
304     }
305   }
306 }
307