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 }