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

org.ojalgo.matrix.BasicMatrix Maven / Gradle / Ivy

Go to download

oj! Algorithms - ojAlgo - is Open Source Java code that has to do with mathematics, linear algebra and optimisation.

There is a newer version: 55.0.1
Show newest version
/*
 * Copyright 1997-2020 Optimatika
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.ojalgo.matrix;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Supplier;

import org.ojalgo.ProgrammingError;
import org.ojalgo.RecoverableCondition;
import org.ojalgo.algebra.NormedVectorSpace;
import org.ojalgo.algebra.Operation;
import org.ojalgo.algebra.ScalarOperation;
import org.ojalgo.function.aggregator.Aggregator;
import org.ojalgo.function.aggregator.AggregatorFunction;
import org.ojalgo.function.constant.PrimitiveMath;
import org.ojalgo.matrix.decomposition.Cholesky;
import org.ojalgo.matrix.decomposition.Eigenvalue;
import org.ojalgo.matrix.decomposition.Eigenvalue.Eigenpair;
import org.ojalgo.matrix.decomposition.LDL;
import org.ojalgo.matrix.decomposition.LDU;
import org.ojalgo.matrix.decomposition.LU;
import org.ojalgo.matrix.decomposition.MatrixDecomposition;
import org.ojalgo.matrix.decomposition.QR;
import org.ojalgo.matrix.decomposition.SingularValue;
import org.ojalgo.matrix.store.ElementsSupplier;
import org.ojalgo.matrix.store.MatrixStore;
import org.ojalgo.matrix.store.PhysicalStore;
import org.ojalgo.matrix.store.PhysicalStore.Factory;
import org.ojalgo.matrix.task.DeterminantTask;
import org.ojalgo.matrix.task.InverterTask;
import org.ojalgo.matrix.task.SolverTask;
import org.ojalgo.scalar.Scalar;
import org.ojalgo.structure.Access1D;
import org.ojalgo.structure.Access2D;
import org.ojalgo.structure.Mutate2D;
import org.ojalgo.structure.Structure2D;
import org.ojalgo.type.NumberDefinition;
import org.ojalgo.type.context.NumberContext;

/**
 * A base class for, easy to use, immutable (thread safe) matrices with a rich feature set. This class handles
 * a lot of complexity, and makes choices, for you. If you want more control, and to be exposed to all the
 * implementation details, then look at the various interfaces/classes in the
 * {@linkplain org.ojalgo.matrix.store} and {@linkplain org.ojalgo.matrix.decomposition} packages.
 *
 * @author apete
 */
public abstract class BasicMatrix, M extends BasicMatrix> implements NormedVectorSpace, Operation.Subtraction,
        Operation.Multiplication, ScalarOperation.Addition, ScalarOperation.Division, ScalarOperation.Subtraction, Access2D,
        Access2D.Elements, Access2D.Aggregatable, Structure2D.ReducibleTo1D, NumberContext.Enforceable, Access2D.Collectable> {

    public interface LogicalBuilder, M extends BasicMatrix>
            extends Structure2D.Logical>, Access2D.Collectable> {

        default M build() {
            return this.get();
        }

    }

    public interface PhysicalReceiver, M extends BasicMatrix>
            extends Mutate2D.ModifiableReceiver, Supplier, Access2D.Collectable> {

        default M build() {
            return this.get();
        }

    }

    private static final NumberContext EQUALS = NumberContext.getGeneral(8, 12);

    /**
     * The Frobenius norm is the square root of the sum of the squares of each element, or the square root of
     * the sum of the square of the singular values.
     *
     * @return The matrix' Frobenius norm
     */
    public static > double calculateFrobeniusNorm(final M matrix) {
        return matrix.norm();
    }

    /**
     * @return The inf-norm or maximum row sum
     */
    public static > double calculateInfinityNorm(final M matrix) {

        double retVal = PrimitiveMath.ZERO;

        final long tmpLimit = matrix.countRows();
        for (long i = 0L; i < tmpLimit; i++) {
            retVal = PrimitiveMath.MAX.invoke(retVal, NumberDefinition.doubleValue(matrix.aggregateRow(i, Aggregator.NORM1)));
        }

        return retVal;
    }

    /**
     * @return The 1-norm or maximum column sum
     */
    public static > double calculateOneNorm(final M matrix) {

        double retVal = PrimitiveMath.ZERO;

        final long tmpLimit = matrix.countColumns();
        for (long j = 0L; j < tmpLimit; j++) {
            retVal = PrimitiveMath.MAX.invoke(retVal, NumberDefinition.doubleValue(matrix.aggregateColumn(j, Aggregator.NORM1)));
        }

        return retVal;
    }

    private transient MatrixDecomposition myDecomposition = null;
    private transient int myHashCode = 0;
    private transient Boolean myHermitian = null;
    private transient Boolean mySPD = null;
    private final MatrixStore myStore;
    private transient Boolean mySymmetric = null;

    @SuppressWarnings("unused")
    private BasicMatrix() {

        this(null);

        ProgrammingError.throwForIllegalInvocation();
    }

    BasicMatrix(final MatrixStore store) {

        super();

        myStore = store;
    }

    public M add(final double scalarAddend) {

        Factory physical = myStore.physical();

        PhysicalStore retVal = physical.copy(myStore);

        N right = physical.scalar().cast(scalarAddend);

        retVal.modifyAll(physical.function().add().second(right));

        return this.getFactory().instantiate(retVal);
    }

    public M add(final M addend) {

        ProgrammingError.throwIfNotEqualDimensions(myStore, addend);

        final PhysicalStore retVal = myStore.physical().copy(addend);

        retVal.modifyMatching(myStore, myStore.physical().function().add());

        return this.getFactory().instantiate(retVal);
    }

    public M add(final N scalarAddend) {

        PhysicalStore.Factory physical = myStore.physical();

        PhysicalStore retVal = physical.copy(myStore);

        retVal.modifyAll(physical.function().add().second(scalarAddend));

        return this.getFactory().instantiate(retVal);
    }

    public N aggregateColumn(final long row, final long col, final Aggregator aggregator) {
        return myStore.aggregateColumn(row, col, aggregator);
    }

    public N aggregateDiagonal(final long row, final long col, final Aggregator aggregator) {
        return myStore.aggregateDiagonal(row, col, aggregator);
    }

    public N aggregateRange(final long first, final long limit, final Aggregator aggregator) {
        return myStore.aggregateRange(first, limit, aggregator);
    }

    public N aggregateRow(final long row, final long col, final Aggregator aggregator) {
        return myStore.aggregateRow(row, col, aggregator);
    }

    public M conjugate() {
        return this.getFactory().instantiate(myStore.conjugate());
    }

    /**
     * @return A fully mutable matrix builder with the elements initially set to a copy of this matrix.
     */
    public abstract BasicMatrix.PhysicalReceiver copy();

    public long count() {
        return myStore.count();
    }

    public long countColumns() {
        return myStore.countColumns();
    }

    public long countRows() {
        return myStore.countRows();
    }

    public M divide(final double scalarDivisor) {

        Factory physical = myStore.physical();

        PhysicalStore retVal = physical.copy(myStore);

        N right = physical.scalar().cast(scalarDivisor);

        retVal.modifyAll(physical.function().divide().second(right));

        return this.getFactory().instantiate(retVal);
    }

    public M divide(final N scalarDivisor) {

        Factory physical = myStore.physical();

        PhysicalStore retVal = physical.copy(myStore);

        retVal.modifyAll(physical.function().divide().second(scalarDivisor));

        return this.getFactory().instantiate(retVal);
    }

    public double doubleValue(final long index) {
        return myStore.doubleValue(index);
    }

    public double doubleValue(final long i, final long j) {
        return myStore.doubleValue(i, j);
    }

    public M enforce(final NumberContext context) {

        final PhysicalStore tmpCopy = myStore.copy();

        tmpCopy.modifyAll(myStore.physical().function().enforce(context));

        return this.getFactory().instantiate(tmpCopy);
    }

    /**
     * @return true if the frobenius norm of the difference between [this] and [another] is zero within the
     *         limits of [precision].
     */
    public boolean equals(final Access2D another, final NumberContext precision) {
        return Access2D.equals(myStore, another, precision);
    }

    @Override
    public boolean equals(final Object other) {
        if (other instanceof Access2D) {
            return Access2D.equals(myStore, (Access2D) other, EQUALS);
        } else {
            return super.equals(other);
        }
    }

    /**
     * BasicMatrix instances are intended to be immutable. If they are it is possible to cache (partial)
     * calculation results. Calling this method should flush any cached calculation results.
     */
    public void flushCache() {

        myHashCode = 0;

        if (myDecomposition != null) {
            myDecomposition.reset();
            myDecomposition = null;
        }

        myHermitian = null;
        mySymmetric = null;

    }

    public N get(final long index) {
        return myStore.get(index);
    }

    public N get(final long aRow, final long aColumn) {
        return myStore.get(aRow, aColumn);
    }

    /**
     * Matrix condition (2-norm)
     *
     * @return ratio of largest to smallest singular value.
     */
    public Scalar getCondition() {
        return myStore.physical().scalar().convert(this.getComputedSingularValue().getCondition());
    }

    /**
     * @return The matrix' determinant.
     */
    public Scalar getDeterminant() {

        N tmpDeterminant = null;

        if ((myDecomposition != null) && (myDecomposition instanceof MatrixDecomposition.Determinant)
                && ((MatrixDecomposition.Determinant) myDecomposition).isComputed()) {

            tmpDeterminant = ((MatrixDecomposition.Determinant) myDecomposition).getDeterminant();

        } else {

            final DeterminantTask tmpTask = this.getTaskDeterminant(myStore);

            if (tmpTask instanceof MatrixDecomposition.Determinant) {
                myDecomposition = (MatrixDecomposition.Determinant) tmpTask;
            }

            tmpDeterminant = tmpTask.calculateDeterminant(myStore);
        }

        return myStore.physical().scalar().convert(tmpDeterminant);
    }

    public List getEigenpairs() {

        if (!this.isSquare()) {
            throw new ProgrammingError("Only defined for square matrices!");
        }

        Eigenvalue evd = this.getComputedEigenvalue();

        List retVal = new ArrayList<>();

        for (int i = 0, limit = evd.getEigenvalues().size(); i < limit; i++) {
            retVal.add(evd.getEigenpair(i));
        }

        retVal.sort(Comparator.reverseOrder());

        return retVal;
    }

    /**
     * The rank of a matrix is the (maximum) number of linearly independent rows or columns it contains. It is
     * also equal to the number of nonzero singular values of the matrix.
     *
     * @return The matrix' rank.
     * @see org.ojalgo.matrix.decomposition.MatrixDecomposition.RankRevealing
     */
    public int getRank() {
        return this.getRankRevealing(myStore).getRank();
    }

    /**
     * The sum of the diagonal elements.
     *
     * @return The matrix' trace.
     */
    public Scalar getTrace() {

        final AggregatorFunction tmpAggr = myStore.physical().aggregator().sum();

        myStore.visitDiagonal(tmpAggr);

        return myStore.physical().scalar().convert(tmpAggr.get());
    }

    @Override
    public int hashCode() {
        if (myHashCode == 0) {
            myHashCode = MatrixUtils.hashCode(myStore);
        }
        return myHashCode;
    }

    /**
     * 

* About inverting matrices: *

*
    *
  • "right inverse": [this][right inverse]=[I]. You may calculate it using * {@linkplain #solve(Access2D)}.
  • *
  • "left inverse": [left inverse][this]=[I]. You may calculate it using {@linkplain #solve(Access2D)} * and transposing.
  • *
  • "generalised inverse": [this][generalised inverse][this]=[this]. Note that if [this] is singular or * non-square, then [generalised inverse] is not unique.
  • *
  • "pseudoinverse": The generalised inverse (there are typically/possibly many) with the smallest * frobenius norm is called the pseudoinverse. You may calculate it using the {@linkplain QR} or * {@linkplain SingularValue} decompositions.
  • *
  • "inverse": *
      *
    • If [left inverse]=[right inverse] then it is also [inverse].
    • *
    • If [this] is square and has full rank then the [generalised inverse] is unique, with the * [pseudoinverse] given, and equal to [inverse].
    • *
    *
  • *
* * @return The "best possible" inverse.... */ public M invert() { MatrixStore tmpInverse = null; if ((myDecomposition != null) && (myDecomposition instanceof MatrixDecomposition.Solver) && ((MatrixDecomposition.Solver) myDecomposition).isSolvable()) { tmpInverse = ((MatrixDecomposition.Solver) myDecomposition).getInverse(); } else { final InverterTask tmpTask = this.getTaskInverter(myStore); if (tmpTask instanceof MatrixDecomposition.Solver) { final MatrixDecomposition.Solver tmpSolver = (MatrixDecomposition.Solver) tmpTask; myDecomposition = tmpSolver; if (tmpSolver.compute(myStore)) { tmpInverse = tmpSolver.getInverse(); } else { tmpInverse = null; } } else { try { tmpInverse = tmpTask.invert(myStore); } catch (final RecoverableCondition xcptn) { xcptn.printStackTrace(); tmpInverse = null; } } } if (tmpInverse == null) { SingularValue computedSVD = this.getComputedSingularValue(); myDecomposition = computedSVD; tmpInverse = computedSVD.getInverse(); } return this.getFactory().instantiate(tmpInverse); } public boolean isAbsolute(final long row, final long col) { return myStore.isAbsolute(row, col); } /** * @return true if {@linkplain #getRank()} == min({@linkplain #countRows()}, {@linkplain #countColumns()}) * @see org.ojalgo.matrix.decomposition.MatrixDecomposition.RankRevealing */ public boolean isFullRank() { return this.getRankRevealing(myStore).isFullRank(); } public boolean isHermitian() { if (myHermitian == null) { myHermitian = this.isSquare() && myStore.equals(myStore.conjugate(), EQUALS); } return myHermitian.booleanValue(); } public boolean isSmall(final double comparedTo) { return myStore.isSmall(comparedTo); } public boolean isSmall(final long row, final long col, final double comparedTo) { return myStore.isSmall(row, col, comparedTo); } public boolean isSymmetric() { if (mySymmetric == null) { mySymmetric = this.isSquare() && myStore.equals(myStore.transpose(), EQUALS); } return mySymmetric.booleanValue(); } public abstract BasicMatrix.LogicalBuilder logical(); public M multiply(final double scalarMultiplicand) { Factory physical = myStore.physical(); PhysicalStore retVal = physical.copy(myStore); N right = physical.scalar().cast(scalarMultiplicand); retVal.modifyAll(physical.function().multiply().second(right)); return this.getFactory().instantiate(retVal); } public M multiply(final M multiplicand) { ProgrammingError.throwIfMultiplicationNotPossible(myStore, multiplicand); return this.getFactory().instantiate(myStore.multiply(this.cast(multiplicand).get())); } public M multiply(final N scalarMultiplicand) { Factory physical = myStore.physical(); PhysicalStore retVal = physical.copy(myStore); retVal.modifyAll(physical.function().multiply().second(scalarMultiplicand)); return this.getFactory().instantiate(retVal); } public M negate() { final PhysicalStore retVal = myStore.copy(); retVal.modifyAll(myStore.physical().function().negate()); return this.getFactory().instantiate(retVal); } /** * The Frobenius norm is the square root of the sum of the squares of each element, or the square root of * the sum of the square of the singular values. This definition fits the requirements of * {@linkplain NormedVectorSpace#norm()}. * * @return The matrix' Frobenius norm */ public double norm() { return myStore.norm(); } public M power(final int power) { return this.getFactory().instantiate(myStore.power(power)); } public M reduceColumns(final Aggregator aggregator) { return this.getFactory().instantiate(myStore.reduceColumns(aggregator).get()); } public M reduceRows(final Aggregator aggregator) { return this.getFactory().instantiate(myStore.reduceRows(aggregator).get()); } public M signum() { return this.getFactory().instantiate(myStore.signum()); } /** *

* This method solves a system of linear equations: [this][X]=[rhs]. A combination of columns in [this] * should produce a column(s) in [rhs]. It is ok for [rhs] to have more than 1 column. *

*
    *
  • If the problem is over-qualified an approximate solution is returned.
  • *
  • If the problem is under-qualified one possible solution is returned.
  • *
*

* Remember that: [X][this]=[rhs] is equivalent to [this]T[X]T=[rhs]T *

* * @param rhs The right hand side of the equation. * @return The solution, [X]. */ public M solve(final Access2D rhs) { MatrixStore tmpSolution = null; if ((myDecomposition != null) && (myDecomposition instanceof MatrixDecomposition.Solver) && ((MatrixDecomposition.Solver) myDecomposition).isSolvable()) { tmpSolution = ((MatrixDecomposition.Solver) myDecomposition).getSolution(this.cast(rhs)); } else { final SolverTask tmpTask = this.getTaskSolver(myStore, rhs); if (tmpTask instanceof MatrixDecomposition.Solver) { final MatrixDecomposition.Solver tmpSolver = (MatrixDecomposition.Solver) tmpTask; myDecomposition = tmpSolver; if (tmpSolver.compute(myStore)) { tmpSolution = tmpSolver.getSolution(this.cast(rhs)); } else { tmpSolution = null; } } else { try { tmpSolution = tmpTask.solve(myStore, rhs); } catch (final RecoverableCondition xcptn) { xcptn.printStackTrace(); tmpSolution = null; } } } return this.getFactory().instantiate(tmpSolution); } public M subtract(final double scalarSubtrahend) { Factory physical = myStore.physical(); PhysicalStore retVal = physical.copy(myStore); N right = physical.scalar().cast(scalarSubtrahend); retVal.modifyAll(physical.function().subtract().second(right)); return this.getFactory().instantiate(retVal); } public M subtract(final M subtrahend) { ProgrammingError.throwIfNotEqualDimensions(myStore, subtrahend); final PhysicalStore retVal = myStore.physical().copy(subtrahend); retVal.modifyMatching(myStore, myStore.physical().function().subtract()); return this.getFactory().instantiate(retVal); } public M subtract(final N scalarSubtrahend) { Factory physical = myStore.physical(); PhysicalStore retVal = physical.copy(myStore); retVal.modifyAll(physical.function().subtract().second(scalarSubtrahend)); return this.getFactory().instantiate(retVal); } public final void supplyTo(final PhysicalStore receiver) { myStore.supplyTo(receiver); } /** * Extracts one element of this matrix as a Scalar. * * @param row A row index. * @param col A column index. * @return One matrix element */ public Scalar toScalar(final long row, final long col) { return myStore.toScalar(row, col); } @Override public final String toString() { return Access2D.toString(this); } /** * Transposes this matrix. For complex matrices conjugate() and transpose() are NOT EQUAL. * * @return A matrix that is the transpose of this matrix. * @see org.ojalgo.matrix.BasicMatrix#conjugate() */ public M transpose() { return this.getFactory().instantiate(myStore.transpose()); } private final Eigenvalue getComputedEigenvalue() { if (!this.isComputedEigenvalue()) { myDecomposition = Eigenvalue.make(myStore); myDecomposition.decompose(myStore); } return (Eigenvalue) myDecomposition; } private final SingularValue getComputedSingularValue() { if (!this.isComputedSingularValue()) { myDecomposition = SingularValue.make(myStore); myDecomposition.decompose(myStore); } return (SingularValue) myDecomposition; } private MatrixDecomposition.RankRevealing getRankRevealing(final MatrixStore store) { if ((myDecomposition != null) && (myDecomposition instanceof MatrixDecomposition.RankRevealing) && ((MatrixDecomposition.RankRevealing) myDecomposition).isComputed()) { } else { if (store.isTall()) { myDecomposition = this.getDecompositionQR(store); } else if (store.isFat()) { myDecomposition = this.getDecompositionSingularValue(store); } else { myDecomposition = this.getDecompositionLU(store); } myDecomposition.decompose(store); } return (MatrixDecomposition.RankRevealing) myDecomposition; } private boolean isComputedEigenvalue() { return (myDecomposition != null) && (myDecomposition instanceof Eigenvalue) && myDecomposition.isComputed(); } private boolean isComputedSingularValue() { return (myDecomposition != null) && (myDecomposition instanceof SingularValue) && myDecomposition.isComputed(); } abstract ElementsSupplier cast(Access1D matrix); abstract Cholesky getDecompositionCholesky(Structure2D typical); abstract Eigenvalue getDecompositionEigenvalue(Structure2D typical); abstract LDL getDecompositionLDL(Structure2D typical); LDU getDecompositionLDU(final Structure2D typical) { if ((myDecomposition != null) && (myDecomposition instanceof LDU)) { return (LDU) myDecomposition; } if ((myHermitian != null) && myHermitian.booleanValue()) { if ((mySPD != null) && mySPD.booleanValue()) { return (LDU) (myDecomposition = this.getDecompositionCholesky(typical)); } else { return (LDU) (myDecomposition = this.getDecompositionLDL(typical)); } } else { return (LDU) (myDecomposition = this.getDecompositionLDU(typical)); } } abstract LU getDecompositionLU(Structure2D typical); abstract QR getDecompositionQR(Structure2D typical); abstract SingularValue getDecompositionSingularValue(Structure2D typical); abstract MatrixFactory, ? extends PhysicalReceiver, ? extends PhysicalReceiver> getFactory(); final MatrixStore getStore() { return myStore; } abstract DeterminantTask getTaskDeterminant(final MatrixStore template); abstract InverterTask getTaskInverter(final MatrixStore template); abstract SolverTask getTaskSolver(MatrixStore templateBody, Access2D templateRHS); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy