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 /** 043 * Start building an instance which maps {@code x = x1} to {@code y = y1}. Both arguments must be 044 * finite. Call either {@link LinearTransformationBuilder#and} or {@link 045 * LinearTransformationBuilder#withSlope} on the returned object to finish building the instance. 046 */ 047 public static LinearTransformationBuilder mapping(double x1, double y1) { 048 checkArgument(isFinite(x1) && isFinite(y1)); 049 return new LinearTransformationBuilder(x1, y1); 050 } 051 052 /** 053 * This is an intermediate stage in the construction process. It is returned by {@link 054 * LinearTransformation#mapping}. You almost certainly don't want to keep instances around, but 055 * instead use method chaining. This represents a single point mapping, i.e. a mapping between one 056 * {@code x} and {@code y} value pair. 057 * 058 * @since 20.0 059 */ 060 public static final class LinearTransformationBuilder { 061 062 private final double x1; 063 private final double y1; 064 065 private LinearTransformationBuilder(double x1, double y1) { 066 this.x1 = x1; 067 this.y1 = y1; 068 } 069 070 /** 071 * Finish building an instance which also maps {@code x = x2} to {@code y = y2}. These values 072 * must not both be identical to the values given in the first mapping. If only the {@code x} 073 * values are identical, the transformation is vertical. If only the {@code y} values are 074 * identical, the transformation is horizontal (i.e. the slope is zero). 075 */ 076 public LinearTransformation and(double x2, double y2) { 077 checkArgument(isFinite(x2) && isFinite(y2)); 078 if (x2 == x1) { 079 checkArgument(y2 != y1); 080 return new VerticalLinearTransformation(x1); 081 } else { 082 return withSlope((y2 - y1) / (x2 - x1)); 083 } 084 } 085 086 /** 087 * Finish building an instance with the given slope, i.e. the rate of change of {@code y} with 088 * respect to {@code x}. The slope must not be {@code NaN}. It may be infinite, in which case 089 * the transformation is vertical. (If it is zero, the transformation is horizontal.) 090 */ 091 public LinearTransformation withSlope(double slope) { 092 checkArgument(!Double.isNaN(slope)); 093 if (isFinite(slope)) { 094 double yIntercept = y1 - x1 * slope; 095 return new RegularLinearTransformation(slope, yIntercept); 096 } else { 097 return new VerticalLinearTransformation(x1); 098 } 099 } 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}