MicrosphereInterpolatingFunction.java

  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.  *      http://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. package org.apache.commons.math3.analysis.interpolation;

  18. import java.util.ArrayList;
  19. import java.util.HashMap;
  20. import java.util.List;
  21. import java.util.Map;

  22. import org.apache.commons.math3.analysis.MultivariateFunction;
  23. import org.apache.commons.math3.exception.DimensionMismatchException;
  24. import org.apache.commons.math3.exception.NoDataException;
  25. import org.apache.commons.math3.exception.NullArgumentException;
  26. import org.apache.commons.math3.linear.ArrayRealVector;
  27. import org.apache.commons.math3.linear.RealVector;
  28. import org.apache.commons.math3.random.UnitSphereRandomVectorGenerator;
  29. import org.apache.commons.math3.util.FastMath;

  30. /**
  31.  * Interpolating function that implements the
  32.  * <a href="http://www.dudziak.com/microsphere.php">Microsphere Projection</a>.
  33.  *
  34.  */
  35. public class MicrosphereInterpolatingFunction
  36.     implements MultivariateFunction {
  37.     /**
  38.      * Space dimension.
  39.      */
  40.     private final int dimension;
  41.     /**
  42.      * Internal accounting data for the interpolation algorithm.
  43.      * Each element of the list corresponds to one surface element of
  44.      * the microsphere.
  45.      */
  46.     private final List<MicrosphereSurfaceElement> microsphere;
  47.     /**
  48.      * Exponent used in the power law that computes the weights of the
  49.      * sample data.
  50.      */
  51.     private final double brightnessExponent;
  52.     /**
  53.      * Sample data.
  54.      */
  55.     private final Map<RealVector, Double> samples;

  56.     /**
  57.      * Class for storing the accounting data needed to perform the
  58.      * microsphere projection.
  59.      */
  60.     private static class MicrosphereSurfaceElement {
  61.         /** Normal vector characterizing a surface element. */
  62.         private final RealVector normal;
  63.         /** Illumination received from the brightest sample. */
  64.         private double brightestIllumination;
  65.         /** Brightest sample. */
  66.         private Map.Entry<RealVector, Double> brightestSample;

  67.         /**
  68.          * @param n Normal vector characterizing a surface element
  69.          * of the microsphere.
  70.          */
  71.         MicrosphereSurfaceElement(double[] n) {
  72.             normal = new ArrayRealVector(n);
  73.         }

  74.         /**
  75.          * Return the normal vector.
  76.          * @return the normal vector
  77.          */
  78.         RealVector normal() {
  79.             return normal;
  80.         }

  81.         /**
  82.          * Reset "illumination" and "sampleIndex".
  83.          */
  84.         void reset() {
  85.             brightestIllumination = 0;
  86.             brightestSample = null;
  87.         }

  88.         /**
  89.          * Store the illumination and index of the brightest sample.
  90.          * @param illuminationFromSample illumination received from sample
  91.          * @param sample current sample illuminating the element
  92.          */
  93.         void store(final double illuminationFromSample,
  94.                    final Map.Entry<RealVector, Double> sample) {
  95.             if (illuminationFromSample > this.brightestIllumination) {
  96.                 this.brightestIllumination = illuminationFromSample;
  97.                 this.brightestSample = sample;
  98.             }
  99.         }

  100.         /**
  101.          * Get the illumination of the element.
  102.          * @return the illumination.
  103.          */
  104.         double illumination() {
  105.             return brightestIllumination;
  106.         }

  107.         /**
  108.          * Get the sample illuminating the element the most.
  109.          * @return the sample.
  110.          */
  111.         Map.Entry<RealVector, Double> sample() {
  112.             return brightestSample;
  113.         }
  114.     }

  115.     /**
  116.      * @param xval Arguments for the interpolation points.
  117.      * {@code xval[i][0]} is the first component of interpolation point
  118.      * {@code i}, {@code xval[i][1]} is the second component, and so on
  119.      * until {@code xval[i][d-1]}, the last component of that interpolation
  120.      * point (where {@code dimension} is thus the dimension of the sampled
  121.      * space).
  122.      * @param yval Values for the interpolation points.
  123.      * @param brightnessExponent Brightness dimming factor.
  124.      * @param microsphereElements Number of surface elements of the
  125.      * microsphere.
  126.      * @param rand Unit vector generator for creating the microsphere.
  127.      * @throws DimensionMismatchException if the lengths of {@code yval} and
  128.      * {@code xval} (equal to {@code n}, the number of interpolation points)
  129.      * do not match, or the the arrays {@code xval[0]} ... {@code xval[n]},
  130.      * have lengths different from {@code dimension}.
  131.      * @throws NoDataException if there an array has zero-length.
  132.      * @throws NullArgumentException if an argument is {@code null}.
  133.      */
  134.     public MicrosphereInterpolatingFunction(double[][] xval,
  135.                                             double[] yval,
  136.                                             int brightnessExponent,
  137.                                             int microsphereElements,
  138.                                             UnitSphereRandomVectorGenerator rand)
  139.         throws DimensionMismatchException,
  140.                NoDataException,
  141.                NullArgumentException {
  142.         if (xval == null ||
  143.             yval == null) {
  144.             throw new NullArgumentException();
  145.         }
  146.         if (xval.length == 0) {
  147.             throw new NoDataException();
  148.         }
  149.         if (xval.length != yval.length) {
  150.             throw new DimensionMismatchException(xval.length, yval.length);
  151.         }
  152.         if (xval[0] == null) {
  153.             throw new NullArgumentException();
  154.         }

  155.         dimension = xval[0].length;
  156.         this.brightnessExponent = brightnessExponent;

  157.         // Copy data samples.
  158.         samples = new HashMap<RealVector, Double>(yval.length);
  159.         for (int i = 0; i < xval.length; ++i) {
  160.             final double[] xvalI = xval[i];
  161.             if (xvalI == null) {
  162.                 throw new NullArgumentException();
  163.             }
  164.             if (xvalI.length != dimension) {
  165.                 throw new DimensionMismatchException(xvalI.length, dimension);
  166.             }

  167.             samples.put(new ArrayRealVector(xvalI), yval[i]);
  168.         }

  169.         microsphere = new ArrayList<MicrosphereSurfaceElement>(microsphereElements);
  170.         // Generate the microsphere, assuming that a fairly large number of
  171.         // randomly generated normals will represent a sphere.
  172.         for (int i = 0; i < microsphereElements; i++) {
  173.             microsphere.add(new MicrosphereSurfaceElement(rand.nextVector()));
  174.         }
  175.     }

  176.     /**
  177.      * @param point Interpolation point.
  178.      * @return the interpolated value.
  179.      * @throws DimensionMismatchException if point dimension does not math sample
  180.      */
  181.     public double value(double[] point) throws DimensionMismatchException {
  182.         final RealVector p = new ArrayRealVector(point);

  183.         // Reset.
  184.         for (MicrosphereSurfaceElement md : microsphere) {
  185.             md.reset();
  186.         }

  187.         // Compute contribution of each sample points to the microsphere elements illumination
  188.         for (Map.Entry<RealVector, Double> sd : samples.entrySet()) {

  189.             // Vector between interpolation point and current sample point.
  190.             final RealVector diff = sd.getKey().subtract(p);
  191.             final double diffNorm = diff.getNorm();

  192.             if (FastMath.abs(diffNorm) < FastMath.ulp(1d)) {
  193.                 // No need to interpolate, as the interpolation point is
  194.                 // actually (very close to) one of the sampled points.
  195.                 return sd.getValue();
  196.             }

  197.             for (MicrosphereSurfaceElement md : microsphere) {
  198.                 final double w = FastMath.pow(diffNorm, -brightnessExponent);
  199.                 md.store(cosAngle(diff, md.normal()) * w, sd);
  200.             }

  201.         }

  202.         // Interpolation calculation.
  203.         double value = 0;
  204.         double totalWeight = 0;
  205.         for (MicrosphereSurfaceElement md : microsphere) {
  206.             final double iV = md.illumination();
  207.             final Map.Entry<RealVector, Double> sd = md.sample();
  208.             if (sd != null) {
  209.                 value += iV * sd.getValue();
  210.                 totalWeight += iV;
  211.             }
  212.         }

  213.         return value / totalWeight;
  214.     }

  215.     /**
  216.      * Compute the cosine of the angle between 2 vectors.
  217.      *
  218.      * @param v Vector.
  219.      * @param w Vector.
  220.      * @return the cosine of the angle between {@code v} and {@code w}.
  221.      */
  222.     private double cosAngle(final RealVector v, final RealVector w) {
  223.         return v.dotProduct(w) / (v.getNorm() * w.getNorm());
  224.     }
  225. }