1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * https://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* 19 * This is not the original file distributed by the Apache Software Foundation 20 * It has been modified by the Hipparchus project 21 */ 22 package org.hipparchus.analysis.interpolation; 23 24 import java.util.ArrayList; 25 import java.util.List; 26 27 import org.hipparchus.exception.LocalizedCoreFormats; 28 import org.hipparchus.exception.MathIllegalArgumentException; 29 import org.hipparchus.exception.MathIllegalStateException; 30 import org.hipparchus.random.UnitSphereRandomVectorGenerator; 31 import org.hipparchus.util.FastMath; 32 import org.hipparchus.util.MathArrays; 33 import org.hipparchus.util.MathUtils; 34 35 /** 36 * Utility class for the {@link MicrosphereProjectionInterpolator} algorithm. 37 * 38 */ 39 public class InterpolatingMicrosphere { 40 /** Microsphere. */ 41 private final List<Facet> microsphere; 42 /** Microsphere data. */ 43 private final List<FacetData> microsphereData; 44 /** Space dimension. */ 45 private final int dimension; 46 /** Number of surface elements. */ 47 private final int size; 48 /** Maximum fraction of the facets that can be dark. */ 49 private final double maxDarkFraction; 50 /** Lowest non-zero illumination. */ 51 private final double darkThreshold; 52 /** Background value. */ 53 private final double background; 54 55 /** 56 * Create an unitialiazed sphere. 57 * Sub-classes are responsible for calling the {@code add(double[]) add} 58 * method in order to initialize all the sphere's facets. 59 * 60 * @param dimension Dimension of the data space. 61 * @param size Number of surface elements of the sphere. 62 * @param maxDarkFraction Maximum fraction of the facets that can be dark. 63 * If the fraction of "non-illuminated" facets is larger, no estimation 64 * of the value will be performed, and the {@code background} value will 65 * be returned instead. 66 * @param darkThreshold Value of the illumination below which a facet is 67 * considered dark. 68 * @param background Value returned when the {@code maxDarkFraction} 69 * threshold is exceeded. 70 * @throws MathIllegalArgumentException if {@code dimension <= 0} 71 * or {@code size <= 0}. 72 * @throws MathIllegalArgumentException if {@code darkThreshold < 0}. 73 * @throws MathIllegalArgumentException if {@code maxDarkFraction} does not 74 * belong to the interval {@code [0, 1]}. 75 */ 76 protected InterpolatingMicrosphere(int dimension, 77 int size, 78 double maxDarkFraction, 79 double darkThreshold, 80 double background) { 81 if (dimension <= 0) { 82 throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL_BOUND_EXCLUDED, 83 dimension, 0); 84 } 85 if (size <= 0) { 86 throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL_BOUND_EXCLUDED, 87 size, 0); 88 } 89 MathUtils.checkRangeInclusive(maxDarkFraction, 0, 1); 90 if (darkThreshold < 0) { 91 throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL, darkThreshold, 0); 92 } 93 94 this.dimension = dimension; 95 this.size = size; 96 this.maxDarkFraction = maxDarkFraction; 97 this.darkThreshold = darkThreshold; 98 this.background = background; 99 microsphere = new ArrayList<>(size); 100 microsphereData = new ArrayList<>(size); 101 } 102 103 /** 104 * Create a sphere from randomly sampled vectors. 105 * 106 * @param dimension Dimension of the data space. 107 * @param size Number of surface elements of the sphere. 108 * @param rand Unit vector generator for creating the microsphere. 109 * @param maxDarkFraction Maximum fraction of the facets that can be dark. 110 * If the fraction of "non-illuminated" facets is larger, no estimation 111 * of the value will be performed, and the {@code background} value will 112 * be returned instead. 113 * @param darkThreshold Value of the illumination below which a facet 114 * is considered dark. 115 * @param background Value returned when the {@code maxDarkFraction} 116 * threshold is exceeded. 117 * @throws MathIllegalArgumentException if the size of the generated 118 * vectors does not match the dimension set in the constructor. 119 * @throws MathIllegalArgumentException if {@code dimension <= 0} 120 * or {@code size <= 0}. 121 * @throws MathIllegalArgumentException if {@code darkThreshold < 0}. 122 * @throws MathIllegalArgumentException if {@code maxDarkFraction} does not 123 * belong to the interval {@code [0, 1]}. 124 */ 125 public InterpolatingMicrosphere(int dimension, 126 int size, 127 double maxDarkFraction, 128 double darkThreshold, 129 double background, 130 UnitSphereRandomVectorGenerator rand) { 131 this(dimension, size, maxDarkFraction, darkThreshold, background); 132 133 // Generate the microsphere normals, assuming that a number of 134 // randomly generated normals will represent a sphere. 135 for (int i = 0; i < size; i++) { 136 add(rand.nextVector(), false); 137 } 138 } 139 140 /** 141 * Copy constructor. 142 * 143 * @param other Instance to copy. 144 */ 145 protected InterpolatingMicrosphere(InterpolatingMicrosphere other) { 146 dimension = other.dimension; 147 size = other.size; 148 maxDarkFraction = other.maxDarkFraction; 149 darkThreshold = other.darkThreshold; 150 background = other.background; 151 152 // Field can be shared. 153 microsphere = other.microsphere; 154 155 // Field must be copied. 156 microsphereData = new ArrayList<>(size); 157 for (FacetData fd : other.microsphereData) { 158 microsphereData.add(new FacetData(fd.illumination(), fd.sample())); 159 } 160 } 161 162 /** 163 * Perform a copy. 164 * 165 * @return a copy of this instance. 166 */ 167 public InterpolatingMicrosphere copy() { 168 return new InterpolatingMicrosphere(this); 169 } 170 171 /** 172 * Get the space dimensionality. 173 * 174 * @return the number of space dimensions. 175 */ 176 public int getDimension() { 177 return dimension; 178 } 179 180 /** 181 * Get the size of the sphere. 182 * 183 * @return the number of surface elements of the microspshere. 184 */ 185 public int getSize() { 186 return size; 187 } 188 189 /** 190 * Estimate the value at the requested location. 191 * This microsphere is placed at the given {@code point}, contribution 192 * of the given {@code samplePoints} to each sphere facet is computed 193 * (illumination) and the interpolation is performed (integration of 194 * the illumination). 195 * 196 * @param point Interpolation point. 197 * @param samplePoints Sampling data points. 198 * @param sampleValues Sampling data values at the corresponding 199 * {@code samplePoints}. 200 * @param exponent Exponent used in the power law that computes 201 * the weights (distance dimming factor) of the sample data. 202 * @param noInterpolationTolerance When the distance between the 203 * {@code point} and one of the {@code samplePoints} is less than 204 * this value, no interpolation will be performed, and the value 205 * of the sample will just be returned. 206 * @return the estimated value at the given {@code point}. 207 * @throws MathIllegalArgumentException if {@code exponent < 0}. 208 */ 209 public double value(double[] point, 210 double[][] samplePoints, 211 double[] sampleValues, 212 double exponent, 213 double noInterpolationTolerance) { 214 if (exponent < 0) { 215 throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL, exponent, 0); 216 } 217 218 clear(); 219 220 // Contribution of each sample point to the illumination of the 221 // microsphere's facets. 222 final int numSamples = samplePoints.length; 223 for (int i = 0; i < numSamples; i++) { 224 // Vector between interpolation point and current sample point. 225 final double[] diff = MathArrays.ebeSubtract(samplePoints[i], point); 226 final double diffNorm = MathArrays.safeNorm(diff); 227 228 if (FastMath.abs(diffNorm) < noInterpolationTolerance) { 229 // No need to interpolate, as the interpolation point is 230 // actually (very close to) one of the sampled points. 231 return sampleValues[i]; 232 } 233 234 final double weight = FastMath.pow(diffNorm, -exponent); 235 illuminate(diff, sampleValues[i], weight); 236 } 237 238 return interpolate(); 239 } 240 241 /** 242 * Replace {@code i}-th facet of the microsphere. 243 * Method for initializing the microsphere facets. 244 * 245 * @param normal Facet's normal vector. 246 * @param copy Whether to copy the given array. 247 * @throws MathIllegalArgumentException if the length of {@code n} 248 * does not match the space dimension. 249 * @throws MathIllegalStateException if the method has been called 250 * more times than the size of the sphere. 251 */ 252 protected void add(double[] normal, 253 boolean copy) { 254 if (microsphere.size() >= size) { 255 throw new MathIllegalStateException(LocalizedCoreFormats.MAX_COUNT_EXCEEDED, size); 256 } 257 if (normal.length > dimension) { 258 throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH, 259 normal.length, dimension); 260 } 261 262 microsphere.add(new Facet(copy ? normal.clone() : normal)); 263 microsphereData.add(new FacetData(0d, 0d)); 264 } 265 266 /** 267 * Interpolation. 268 * 269 * @return the value estimated from the current illumination of the 270 * microsphere. 271 */ 272 private double interpolate() { 273 // Number of non-illuminated facets. 274 int darkCount = 0; 275 276 double value = 0; 277 double totalWeight = 0; 278 for (FacetData fd : microsphereData) { 279 final double iV = fd.illumination(); 280 if (iV != 0d) { 281 value += iV * fd.sample(); 282 totalWeight += iV; 283 } else { 284 ++darkCount; 285 } 286 } 287 288 final double darkFraction = darkCount / (double) size; 289 290 return darkFraction <= maxDarkFraction ? 291 value / totalWeight : 292 background; 293 } 294 295 /** 296 * Illumination. 297 * 298 * @param sampleDirection Vector whose origin is at the interpolation 299 * point and tail is at the sample location. 300 * @param sampleValue Data value of the sample. 301 * @param weight Weight. 302 */ 303 private void illuminate(double[] sampleDirection, 304 double sampleValue, 305 double weight) { 306 for (int i = 0; i < size; i++) { 307 final double[] n = microsphere.get(i).getNormal(); 308 final double cos = MathArrays.cosAngle(n, sampleDirection); 309 310 if (cos > 0) { 311 final double illumination = cos * weight; 312 313 if (illumination > darkThreshold && 314 illumination > microsphereData.get(i).illumination()) { 315 microsphereData.set(i, new FacetData(illumination, sampleValue)); 316 } 317 } 318 } 319 } 320 321 /** 322 * Reset the all the {@link Facet facets} data to zero. 323 */ 324 private void clear() { 325 for (int i = 0; i < size; i++) { 326 microsphereData.set(i, new FacetData(0d, 0d)); 327 } 328 } 329 330 /** 331 * Microsphere "facet" (surface element). 332 */ 333 private static class Facet { 334 /** Normal vector characterizing a surface element. */ 335 private final double[] normal; 336 337 /** 338 * @param n Normal vector characterizing a surface element 339 * of the microsphere. No copy is made. 340 */ 341 Facet(double[] n) { 342 normal = n; // NOPMD - array cloning is taken care of at call site 343 } 344 345 /** 346 * Return a reference to the vector normal to this facet. 347 * 348 * @return the normal vector. 349 */ 350 public double[] getNormal() { 351 return normal; // NOPMD - returning an internal array is intentional and documented here 352 } 353 } 354 355 /** 356 * Data associated with each {@link Facet}. 357 */ 358 private static class FacetData { 359 /** Illumination received from the sample. */ 360 private final double illumination; 361 /** Data value of the sample. */ 362 private final double sample; 363 364 /** 365 * @param illumination Illumination. 366 * @param sample Data value. 367 */ 368 FacetData(double illumination, double sample) { 369 this.illumination = illumination; 370 this.sample = sample; 371 } 372 373 /** 374 * Get the illumination. 375 * @return the illumination. 376 */ 377 public double illumination() { 378 return illumination; 379 } 380 381 /** 382 * Get the data value. 383 * @return the data value. 384 */ 385 public double sample() { 386 return sample; 387 } 388 } 389 }