001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.math3.analysis.interpolation;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.commons.math3.analysis.MultivariateFunction;
025import org.apache.commons.math3.exception.DimensionMismatchException;
026import org.apache.commons.math3.exception.NoDataException;
027import org.apache.commons.math3.exception.NullArgumentException;
028import org.apache.commons.math3.linear.ArrayRealVector;
029import org.apache.commons.math3.linear.RealVector;
030import org.apache.commons.math3.random.UnitSphereRandomVectorGenerator;
031import org.apache.commons.math3.util.FastMath;
032
033/**
034 * Interpolating function that implements the
035 * <a href="http://www.dudziak.com/microsphere.php">Microsphere Projection</a>.
036 *
037 * @deprecated Code will be removed in 4.0.  Use {@link InterpolatingMicrosphere}
038 * and {@link MicrosphereProjectionInterpolator} instead.
039 */
040@Deprecated
041public class MicrosphereInterpolatingFunction
042    implements MultivariateFunction {
043    /**
044     * Space dimension.
045     */
046    private final int dimension;
047    /**
048     * Internal accounting data for the interpolation algorithm.
049     * Each element of the list corresponds to one surface element of
050     * the microsphere.
051     */
052    private final List<MicrosphereSurfaceElement> microsphere;
053    /**
054     * Exponent used in the power law that computes the weights of the
055     * sample data.
056     */
057    private final double brightnessExponent;
058    /**
059     * Sample data.
060     */
061    private final Map<RealVector, Double> samples;
062
063    /**
064     * Class for storing the accounting data needed to perform the
065     * microsphere projection.
066     */
067    private static class MicrosphereSurfaceElement {
068        /** Normal vector characterizing a surface element. */
069        private final RealVector normal;
070        /** Illumination received from the brightest sample. */
071        private double brightestIllumination;
072        /** Brightest sample. */
073        private Map.Entry<RealVector, Double> brightestSample;
074
075        /**
076         * @param n Normal vector characterizing a surface element
077         * of the microsphere.
078         */
079        MicrosphereSurfaceElement(double[] n) {
080            normal = new ArrayRealVector(n);
081        }
082
083        /**
084         * Return the normal vector.
085         * @return the normal vector
086         */
087        RealVector normal() {
088            return normal;
089        }
090
091        /**
092         * Reset "illumination" and "sampleIndex".
093         */
094        void reset() {
095            brightestIllumination = 0;
096            brightestSample = null;
097        }
098
099        /**
100         * Store the illumination and index of the brightest sample.
101         * @param illuminationFromSample illumination received from sample
102         * @param sample current sample illuminating the element
103         */
104        void store(final double illuminationFromSample,
105                   final Map.Entry<RealVector, Double> sample) {
106            if (illuminationFromSample > this.brightestIllumination) {
107                this.brightestIllumination = illuminationFromSample;
108                this.brightestSample = sample;
109            }
110        }
111
112        /**
113         * Get the illumination of the element.
114         * @return the illumination.
115         */
116        double illumination() {
117            return brightestIllumination;
118        }
119
120        /**
121         * Get the sample illuminating the element the most.
122         * @return the sample.
123         */
124        Map.Entry<RealVector, Double> sample() {
125            return brightestSample;
126        }
127    }
128
129    /**
130     * @param xval Arguments for the interpolation points.
131     * {@code xval[i][0]} is the first component of interpolation point
132     * {@code i}, {@code xval[i][1]} is the second component, and so on
133     * until {@code xval[i][d-1]}, the last component of that interpolation
134     * point (where {@code dimension} is thus the dimension of the sampled
135     * space).
136     * @param yval Values for the interpolation points.
137     * @param brightnessExponent Brightness dimming factor.
138     * @param microsphereElements Number of surface elements of the
139     * microsphere.
140     * @param rand Unit vector generator for creating the microsphere.
141     * @throws DimensionMismatchException if the lengths of {@code yval} and
142     * {@code xval} (equal to {@code n}, the number of interpolation points)
143     * do not match, or the the arrays {@code xval[0]} ... {@code xval[n]},
144     * have lengths different from {@code dimension}.
145     * @throws NoDataException if there an array has zero-length.
146     * @throws NullArgumentException if an argument is {@code null}.
147     */
148    public MicrosphereInterpolatingFunction(double[][] xval,
149                                            double[] yval,
150                                            int brightnessExponent,
151                                            int microsphereElements,
152                                            UnitSphereRandomVectorGenerator rand)
153        throws DimensionMismatchException,
154               NoDataException,
155               NullArgumentException {
156        if (xval == null ||
157            yval == null) {
158            throw new NullArgumentException();
159        }
160        if (xval.length == 0) {
161            throw new NoDataException();
162        }
163        if (xval.length != yval.length) {
164            throw new DimensionMismatchException(xval.length, yval.length);
165        }
166        if (xval[0] == null) {
167            throw new NullArgumentException();
168        }
169
170        dimension = xval[0].length;
171        this.brightnessExponent = brightnessExponent;
172
173        // Copy data samples.
174        samples = new HashMap<RealVector, Double>(yval.length);
175        for (int i = 0; i < xval.length; ++i) {
176            final double[] xvalI = xval[i];
177            if (xvalI == null) {
178                throw new NullArgumentException();
179            }
180            if (xvalI.length != dimension) {
181                throw new DimensionMismatchException(xvalI.length, dimension);
182            }
183
184            samples.put(new ArrayRealVector(xvalI), yval[i]);
185        }
186
187        microsphere = new ArrayList<MicrosphereSurfaceElement>(microsphereElements);
188        // Generate the microsphere, assuming that a fairly large number of
189        // randomly generated normals will represent a sphere.
190        for (int i = 0; i < microsphereElements; i++) {
191            microsphere.add(new MicrosphereSurfaceElement(rand.nextVector()));
192        }
193    }
194
195    /**
196     * @param point Interpolation point.
197     * @return the interpolated value.
198     * @throws DimensionMismatchException if point dimension does not math sample
199     */
200    public double value(double[] point) throws DimensionMismatchException {
201        final RealVector p = new ArrayRealVector(point);
202
203        // Reset.
204        for (MicrosphereSurfaceElement md : microsphere) {
205            md.reset();
206        }
207
208        // Compute contribution of each sample points to the microsphere elements illumination
209        for (Map.Entry<RealVector, Double> sd : samples.entrySet()) {
210
211            // Vector between interpolation point and current sample point.
212            final RealVector diff = sd.getKey().subtract(p);
213            final double diffNorm = diff.getNorm();
214
215            if (FastMath.abs(diffNorm) < FastMath.ulp(1d)) {
216                // No need to interpolate, as the interpolation point is
217                // actually (very close to) one of the sampled points.
218                return sd.getValue();
219            }
220
221            for (MicrosphereSurfaceElement md : microsphere) {
222                final double w = FastMath.pow(diffNorm, -brightnessExponent);
223                md.store(cosAngle(diff, md.normal()) * w, sd);
224            }
225
226        }
227
228        // Interpolation calculation.
229        double value = 0;
230        double totalWeight = 0;
231        for (MicrosphereSurfaceElement md : microsphere) {
232            final double iV = md.illumination();
233            final Map.Entry<RealVector, Double> sd = md.sample();
234            if (sd != null) {
235                value += iV * sd.getValue();
236                totalWeight += iV;
237            }
238        }
239
240        return value / totalWeight;
241    }
242
243    /**
244     * Compute the cosine of the angle between 2 vectors.
245     *
246     * @param v Vector.
247     * @param w Vector.
248     * @return the cosine of the angle between {@code v} and {@code w}.
249     */
250    private double cosAngle(final RealVector v, final RealVector w) {
251        return v.dotProduct(w) / (v.getNorm() * w.getNorm());
252    }
253}