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