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