All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.statistics.inference.SquareMatrixSupport Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.statistics.inference;

/**
 * Provide support for square matrix basic algebraic operations.
 *
 * 

Matrix element indexing is 0-based e.g. {@code get(0, 0)} * returns the element in the first row, first column of the matrix. * *

This class supports computations in the {@link KolmogorovSmirnovTest}. * * @since 1.1 */ final class SquareMatrixSupport { /** * Define a real-valued square matrix. * *

This matrix supports a scale to protect against overflow. The true value * of any matrix value is multiplied by {@code 2^scale}. This is readily performed * using {@link Math#scalb(double, int)}. */ interface RealSquareMatrix { /** * Gets the dimension for the rows and columns. * * @return the dimension */ int dimension(); /** * Gets the scale of the matrix values. * The true value is the value returned from {@link #get(int, int)} multiplied by * {@code 2^scale}. * * @return the scale */ int scale(); /** * Gets the value. This is a scaled value. The true value is the value returned * multiplied by {@code 2^scale}. * * @param i Row * @param j Column * @return the value * @see #scale */ double get(int i, int j); /** * Returns the result of multiplying {@code this} with itself {@code n} times. * * @param n raise {@code this} to power {@code n} * @return {@code this^n} * @throws IllegalArgumentException if {@code n < 0} */ RealSquareMatrix power(int n); } /** * Implementation of {@link RealSquareMatrix} using a {@code double[]} array to * store entries. Values are addressed using {@code i*dim + j} where {@code dim} is * the square dimension. * *

Scaling is supported using the central element {@code [m][m]} where * {@code m = dimension/2}. Scaling is only implemented post-multiplication * to protect against overflow during repeat multiplication operations. * *

Note: The scaling is implemented to support computation of Kolmogorov's * distribution as described in: *

*/ private static class ArrayRealSquareMatrix implements RealSquareMatrix { /** The scaling threshold. Marsaglia used 1e140. This uses 2^400 ~ 2.58e120 */ private static final double SCALE_THRESHOLD = 0x1.0p400; /** Dimension. */ private final int dim; /** Entries of the matrix. */ private final double[] data; /** Matrix scale. */ private final int exp; /** * @param dimension Matrix dimension. * @param data Matrix data. * @param scale Matrix scale. */ ArrayRealSquareMatrix(int dimension, double[] data, int scale) { this.dim = dimension; this.data = data; this.exp = scale; } @Override public int dimension() { return dim; } @Override public int scale() { return exp; } @Override public double get(int i, int j) { return data[i * dim + j]; } @Override public RealSquareMatrix power(int n) { checkExponent(n); if (n == 0) { return identity(); } if (n == 1) { return this; } // Here at least 1 multiplication occurs. // Compute the power by repeat squaring and multiplication: // 13 = 1101 // x^13 = x^8 * x^4 * x^1 // = ((x^2 * x)^2)^2 * x // 21 = 10101 // x^21 = x^16 * x^4 * x^1 // = (((x^2)^2 * x)^2)^2 * x // 1. Find highest set bit in n // 2. Initialise result as x // 3. For remaining bits (0 or 1) below the highest set bit: // - square the current result // - if the current bit is 1 then multiply by x // In this scheme we require 2 matrix array allocations and a column array. // Working arrays final double[] col = new double[dim]; double[] b = new double[data.length]; double[] tmp; // Initialise result as A^1. final double[] a = data; final int ea = exp; double[] r = a.clone(); int er = ea; // Shift the highest set bit off the top. // Any remaining bits are detected in the sign bit. final int shift = Integer.numberOfLeadingZeros(n) + 1; int bits = n << shift; // Process remaining bits below highest set bit. for (int i = 32 - shift; i != 0; i--, bits <<= 1) { // Square the result er = multiply(r, er, r, er, col, b); // Recycle working array tmp = b; b = r; r = tmp; if (bits < 0) { // Multiply by A er = multiply(r, er, a, ea, col, b); // Recycle working array tmp = b; b = r; r = tmp; } } return new ArrayRealSquareMatrix(dim, r, er); } /** * Creates the identity matrix I with the same dimension as {@code this}. * * @return I */ private RealSquareMatrix identity() { final int n = dimension(); return new RealSquareMatrix() { @Override public int dimension() { return n; } @Override public int scale() { return 0; } @Override public double get(int i, int j) { return i == j ? 1 : 0; } @Override public RealSquareMatrix power(int p) { return this; } }; } /** * Returns the result of postmultiplying {@code a} by {@code b}. It is expected * the scale of the result will be the sum of the scale of the arguments; this * may be adjusted by the scale power if the result is scaled by a power of two * for overflow protection. * * @param a Matrix. * @param sa Scale of matrix a. * @param b Matrix to postmultiply by. * @param sb Scale of matrix b. * @param col Working array for a column of the matrix. * @param out Output {@code a * b} * @return Scale of {@code a * b} */ private static int multiply(double[] a, int sa, double[] b, int sb, double[] col, double[] out) { final int m = col.length; // Rows are contiguous; Columns are non-contiguous int k; for (int c = 0; c < m; c++) { // Extract column from b to contiguous memory k = c; for (int i = 0; i < m; i++, k += m) { col[i] = b[k]; } // row * col k = 0; for (int r = 0; r < m; r++) { double sum = 0; for (int i = 0; i < m; i++, k++) { sum += a[k] * col[i]; } out[r * m + c] = sum; } } int s = sa + sb; // Overflow protection. Ideally we would check all elements but for speed // we check the central one only. k = m >> 1; if (out[k * m + k] > SCALE_THRESHOLD) { // Downscale // We could downscale by the inverse of SCALE_THRESHOLD. // However this does not account for how far above the threshold // the central element is. Here we downscale so the central element // is roughly 1 allowing other elements to be larger and still protected // from overflow. final int exp = Math.getExponent(out[k * m + k]); final double downScale = Math.scalb(1.0, -exp); s += exp; for (int i = 0; i < out.length; i++) { out[i] *= downScale; } } return s; } /** * Check the exponent is not negative. * * @param p Exponent. * @throws IllegalArgumentException if the exponent is negative */ private static void checkExponent(int p) { if (p < 0) { throw new IllegalArgumentException("Not positive exponent: " + p); } } } /** No instances. */ private SquareMatrixSupport() {} /** * Creates a square matrix. Data may be used in-place. * *

Values are addressed using {@code a[i][j] = i*dimension + j}. * * @param dimension Matrix dimension. * @param data Matrix data. * @return the square matrix * @throws IllegalArgumentException if the matrix data is not square (length = dimension * dimension) */ static RealSquareMatrix create(int dimension, double[] data) { if (dimension * dimension != data.length) { // Note: %





© 2015 - 2024 Weber Informatics LLC | Privacy Policy