View Javadoc
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.numbers.examples.jmh.core;
18  
19  import java.math.BigDecimal;
20  import java.math.MathContext;
21  
22  import org.apache.commons.rng.UniformRandomProvider;
23  
24  /**
25   * Utility class to create data for linear combinations.
26   */
27  final class LinearCombinationUtils {
28      /** ln(2). */
29      private static final double LN_2 = Math.log(2);
30  
31      /** No construction. */
32      private LinearCombinationUtils() {}
33      /**
34       * Generates ill conditioned dot products.
35       * See {@link #genDot(double, UniformRandomProvider, double[], double[], double[], MathContext)}.
36       *
37       * <p>The exact dot product is computed using {@link MathContext#UNLIMITED}. This
38       * will not scale to large lengths.
39       *
40       * @param c anticipated condition number of x’*y
41       * @param rng source of randomness
42       * @param x output array vector (length at least 6)
43       * @param y output array vector (length at least 6)
44       * @param computeC if not null compute the condition number and place at index 0
45       * @return the exact dot product
46       * @throws IllegalArgumentException If the vector length is below 6
47       */
48      static double genDot(double c, UniformRandomProvider rng,
49              double[] x, double[] y, double[] computeC) {
50          return genDot(c, rng, x, y, computeC, MathContext.UNLIMITED);
51      }
52  
53      /**
54       * Generates ill conditioned dot products. The length of the vectors should be
55       * {@code n>=6}.
56       *
57       * <p>The condition number is defined as the inverse of the cosine of the angle between
58       * the two vectors:
59       *
60       * <pre>
61       * C = 1 / cos angle(x, y)
62       *   = ||x'|| ||y|| / |x'*y|
63       * </pre>
64       * <p>where |x'*y| is the absolute of the dot product and ||.|| is the 2-norm (i.e. the
65       * Euclidean length of each vector).
66       *
67       * <p>A high condition number means that small perturbations in the values result in
68       * a large change in the final result. This occurs when the cosine of the angle approaches
69       * 0 and the vectors are close to orthogonal.
70       *
71       * <p>Computation of the actual dot product requires an exact method to avoid cancellation
72       * floating-point errors. BigDecimal is used to compute the exact result to the accuracy
73       * defined by the {@link MathContext}. The dot product result is created in the range [-1, 1].
74       * The actual condition number can be obtained by passing an array {@code computeC}. The
75       * routine has been tested with anticipated condition numbers up to 1e300 to generate data
76       * where the standard precision dot product will not overflow.
77       *
78       * <p>Uses the GenDot algorithm 6.1 from <a
79       * href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.2.1547">
80       * Accurate Sum and Dot Product</a> by Takeshi Ogita, Siegfried M. Rump, and
81       * Shin'ichi Oishi published in <em>SIAM J. Sci. Comput</em>.
82       *
83       * <p>Note: Ogita et al state "in general the actual condition number is a
84       * little larger than the anticipated one with quite some variation". This
85       * implementation is computing a condition number approximately equal to the
86       * requested for the parameters that have been tested.
87       *
88       * @param c anticipated condition number of x’*y
89       * @param rng source of randomness
90       * @param x output array vector (length at least 6)
91       * @param y output array vector (length at least 6)
92       * @param computeC if not null compute the condition number and place at index 0
93       * @param context the context to use for rounding the exact sum
94       * @return the exact dot product
95       * @throws IllegalArgumentException If the vector length is below 6
96       * @see <a href="https://en.wikipedia.org/wiki/Condition_number">Condition number</a>
97       * @see <a href="https://en.wikipedia.org/wiki/Dot_product">Dot product</a>
98       */
99      static double genDot(double c, UniformRandomProvider rng,
100             double[] x, double[] y, double[] computeC, MathContext context) {
101         // Initialisation
102         if (x.length < 6) {
103             throw new IllegalArgumentException("Incorrect usage: n < 6: " + x.length);
104         }
105         final int n = x.length;
106         final int n2 = n / 2;
107 
108         // log2(c)
109         final double b = Math.log(c) / LN_2;
110         final double b2 = b / 2;
111         // e vector of exponents between 0 and b/2
112         int[] e = new int[n2];
113         // make sure exponents b/2 and 0 actually occur in e
114         e[0] = (int) Math.round(b2) + 1;
115         for (int i = 1; i < n2 - 1; i++) {
116             e[i] = (int) Math.round(rng.nextDouble() * b2);
117         }
118         // e[end] = 0;
119 
120         // Generate first half vectors.
121         // Maintain the exact dot product for use later
122         BigDecimal exact = BigDecimal.ZERO;
123         for (int i = 0; i < n2; i++) {
124             x[i] = Math.scalb(m1p1(rng), e[i]);
125             y[i] = Math.scalb(m1p1(rng), e[i]);
126             exact = exact.add(new BigDecimal(x[i]).multiply(new BigDecimal(y[i])), context);
127         }
128 
129         // for i=n2+1:n and v=1:i,
130         // generate x(i), y(i) such that (*) x(v)’*y(v) ~ 2^e(i-n2)
131         // i.e. the dot product up to position i is a value that will increasingly approach 0
132         e = new int[n - n2];
133         // exponents for second half as a linear vector of exponents from b/2 to 0
134         // linspace(b/2, 0, n-n2)
135         for (int i = 0; i < e.length - 1; i++) {
136             e[i] = (int) Math.round(b2 * (e.length - i - 1) / (e.length - 1));
137         }
138 
139         for (int i = n2; i < x.length; i++) {
140             // x(i) random with generated exponent
141             x[i] = Math.scalb(m1p1(rng), e[i - n2]);
142             // y(i) according to (*).
143             // sum(i) = xi * yi + sum(i-1)
144             // yi = (sum(i) - sum(i-1)) / xi
145             // Here the new sum(i) is a random number with the exponent gradually
146             // reducing to e[end] = 0 so the final result is in [-1, 1].
147             y[i] = (Math.scalb(m1p1(rng), e[i - n2]) - exact.doubleValue()) / x[i];
148             // Maintain the exact dot product
149             exact = exact.add(new BigDecimal(x[i]).multiply(new BigDecimal(y[i])), context);
150         }
151 
152         // Shuffle x and y. Do a parallel Fisher-Yates shuffle.
153         for (int i = n; i > 1; i--) {
154             final int j = rng.nextInt(i);
155             swap(x, i - 1, j);
156             swap(y, i - 1, j);
157         }
158 
159         // Ogita el at.
160         // Compute condition number:
161         // d = DotExact(x’,y);             % the true dot product rounded to nearest
162         // C = 2*(abs(x’)*abs(y))/abs(d);  % the actual condition number
163 
164         // Compare to this:
165         // https://math.stackexchange.com/questions/3147927/condition-number-of-dot-product-of-vectors
166         // Compute the inverse of the cosine between the two vectors.
167         // ||y^t|| ||x|| / | y^t x |
168         // This value is similar to that computed by Ogita et al which effectively
169         // is using the magnitude of the largest component to approximate the vector
170         // length. This works as the vectors are created with matched magnitudes in their
171         // components. Here we compute the actual vector lengths ||y^t|| and ||x||.
172         final double d = exact.doubleValue();
173         if (computeC != null) {
174             // Sum should not overflow as elements are bounded to an exponent half the size
175             // of the input condition number.
176             double s1 = 0;
177             double s2 = 0;
178             for (int i = 0; i < n; i++) {
179                 s1 += x[i] * x[i];
180                 s2 += y[i] * y[i];
181             }
182             computeC[0] = Math.sqrt(s1) * Math.sqrt(s2) / Math.abs(d);
183         }
184         return d;
185     }
186 
187     /**
188      * Create a double in the range [-1, 1).
189      *
190      * @param rng source of randomness
191      * @return the double
192      */
193     private static double m1p1(UniformRandomProvider rng) {
194         // Create in the range [0, 1) then randomly subtract 1.
195         // This samples the 2^54 dyadic rationals in the range.
196         return rng.nextDouble() - rng.nextInt(1);
197     }
198 
199     /**
200      * Swaps the two specified elements in the array.
201      *
202      * @param array Array.
203      * @param i First index.
204      * @param j Second index.
205      */
206     private static void swap(double[] array, int i, int j) {
207         final double tmp = array[i];
208         array[i] = array[j];
209         array[j] = tmp;
210     }
211 }