Coverage Summary for Class: LinearTransformation (com.google.common.math)

Class Method, % Line, %
LinearTransformation 0% (0/5) 0% (0/9)
LinearTransformation$LinearTransformationBuilder 0% (0/4) 0% (0/14)
LinearTransformation$NaNLinearTransformation 0% (0/8) 0% (0/8)
LinearTransformation$RegularLinearTransformation 0% (0/9) 0% (0/18)
LinearTransformation$VerticalLinearTransformation 0% (0/9) 0% (0/14)
Total 0% (0/35) 0% (0/63)


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.Beta; 22 import com.google.common.annotations.GwtIncompatible; 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 @Beta 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  */ 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  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  */ 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  */ 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  */ 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 }