AbsVectorMatrix.java

package org.djunits.vecmat.def;

import java.util.Objects;

import org.djunits.quantity.def.AbsQuantity;
import org.djunits.quantity.def.Quantity;
import org.djunits.quantity.def.Reference;
import org.djunits.unit.Unit;
import org.djunits.util.ArrayMath;
import org.djunits.value.Value;
import org.djunits.vecmat.d1.AbsMatrix1x1;
import org.djunits.vecmat.d1.AbsVector1;
import org.djunits.vecmat.d1.Matrix1x1;
import org.djunits.vecmat.d1.Vector1;
import org.djunits.vecmat.d2.AbsMatrix2x2;
import org.djunits.vecmat.d2.AbsVector2;
import org.djunits.vecmat.d2.Matrix2x2;
import org.djunits.vecmat.d2.Vector2;
import org.djunits.vecmat.d3.AbsMatrix3x3;
import org.djunits.vecmat.d3.AbsVector3;
import org.djunits.vecmat.d3.Matrix3x3;
import org.djunits.vecmat.d3.Vector3;
import org.djunits.vecmat.dn.AbsMatrixNxN;
import org.djunits.vecmat.dn.AbsVectorN;
import org.djunits.vecmat.dn.MatrixNxN;
import org.djunits.vecmat.dn.VectorN;
import org.djunits.vecmat.dnxm.AbsMatrixNxM;
import org.djunits.vecmat.dnxm.MatrixNxM;
import org.djunits.vecmat.table.AbsQuantityTable;
import org.djunits.vecmat.table.QuantityTable;
import org.djutils.exceptions.Throw;

/**
 * AbsVectorMatrix contains a number of standard operations on vectors and matrices of absolute quantities.
 * <p>
 * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
 * for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
 * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
 * @author Alexander Verbraeck
 * @param <A> the absolute quantity type
 * @param <Q> the quantity type
 * @param <VMA> the absolute vector or matrix type
 * @param <VMQ> the relative vector or matrix type
 * @param <VMAT> the type of the transposed version of the absolute vector or matrix
 */
public abstract class AbsVectorMatrix<A extends AbsQuantity<A, Q, ?>, Q extends Quantity<Q>,
        VMA extends AbsVectorMatrix<A, Q, VMA, VMQ, VMAT>, VMQ extends VectorMatrix<Q, VMQ, ?, ?, ?>,
        VMAT extends AbsVectorMatrix<A, Q, VMAT, ?, VMA>> implements Value<VMA, Q>
{
    /** */
    private static final long serialVersionUID = 600L;

    /** The underlying relative vector or matrix with SI values relative to the reference point. */
    private final VMQ relativeVecMat;

    /** The reference point for the absolute values. */
    private final Reference<?, A, Q> reference;

    /**
     * Create a new vector or matrix of absolute values with a reference point.
     * @param relativeVecMat the underlying relative vector or matrix with SI values relative to the reference point
     * @param reference the reference point for the absolute values
     */
    public AbsVectorMatrix(final VMQ relativeVecMat, final Reference<?, A, Q> reference)
    {
        Throw.whenNull(relativeVecMat, "relativeVecMat");
        Throw.whenNull(reference, "reference");
        this.relativeVecMat = relativeVecMat;
        this.reference = reference;
    }

    @Override
    public Unit<?, Q> getDisplayUnit()
    {
        return this.relativeVecMat.getDisplayUnit();
    }

    @SuppressWarnings("unchecked")
    @Override
    public VMA setDisplayUnit(final Unit<?, Q> newUnit)
    {
        this.relativeVecMat.setDisplayUnit(newUnit);
        return (VMA) this;
    }

    /**
     * Return the number of rows.
     * @return the number of rows
     */
    public int rows()
    {
        return this.relativeVecMat.rows();
    }

    /**
     * Return the number of columns.
     * @return the number of columns
     */
    public int cols()
    {
        return this.relativeVecMat.cols();
    }

    /**
     * Return a new vector or matrix with the given SI or BASE values for the relative vector or matrix.
     * @param siNew the values for the new vector or matrix in row-major format
     * @param newReference the reference point for the relative SI values
     * @return a new matrix with the provided SI or BASE values
     */
    public VMA instantiateSi(final double[] siNew, final Reference<?, A, Q> newReference)
    {
        VMQ rel = this.relativeVecMat.instantiateSi(siNew).setDisplayUnit(getDisplayUnit());
        return instantiate(rel, newReference);
    }

    /**
     * Return a new vector or matrix with the given SI or BASE values for the relative vector or matrix.
     * @param relVecMat the underlying relative vector or matrix with SI values relative to the reference point
     * @param newReference the reference point for the relative SI values
     * @return a new matrix with the provided SI or BASE values
     */
    public abstract VMA instantiate(VMQ relVecMat, Reference<?, A, Q> newReference);

    /**
     * Return the underlying relative vector or matrix with SI values relative to the reference point.
     * @return the underlying relative vector or matrix with SI values relative to the reference point
     */
    public VMQ getRelativeVecMat()
    {
        return this.relativeVecMat;
    }

    /**
     * Return the reference point for the absolute values.
     * @return the reference point for the absolute values
     */
    public Reference<?, A, Q> getReference()
    {
        return this.reference;
    }

    /**
     * Return a transposed absolute vector or matrix, where rows and columns have been swapped.
     * @return a transposed absolute vector or matrix, where rows and columns have been swapped
     */
    public abstract VMAT transpose();

    @Override
    public boolean isRelative()
    {
        return false;
    }

    /**
     * Check if the 0-based row is within bounds.
     * @param row the 0-based row to check
     * @throws IndexOutOfBoundsException when row is out of bounds
     */
    protected void checkRow(final int row)
    {
        Throw.when(row < 0 || row >= rows(), IndexOutOfBoundsException.class, "Row %d out of bounds [0..%d]", row, rows() - 1);
    }

    /**
     * Check if the 0-based column is within bounds.
     * @param col the 0-based column to check
     * @throws IndexOutOfBoundsException when column is out of bounds
     */
    protected void checkCol(final int col)
    {
        Throw.when(col < 0 || col >= cols(), IndexOutOfBoundsException.class, "Column %d out of bounds [0..%d]", col,
                cols() - 1);
    }

    /**
     * Check if the 1-based row is within bounds.
     * @param mRow the 1-based row to check
     * @throws IndexOutOfBoundsException when row is out of bounds
     */
    protected void mcheckRow(final int mRow)
    {
        Throw.when(mRow < 1 || mRow > rows(), IndexOutOfBoundsException.class, "Row %d out of bounds [1..%d]", mRow, rows());
    }

    /**
     * Check if the 1-based column is within bounds.
     * @param mCol the 1-based column to check
     * @throws IndexOutOfBoundsException when column is out of bounds
     */
    protected void mcheckCol(final int mCol)
    {
        Throw.when(mCol < 1 || mCol > cols(), IndexOutOfBoundsException.class, "Column %d out of bounds [1..%d]", mCol, cols());
    }

    /**
     * Return the minimum value of the entries of the vector or matrix.
     * @return the minimum value of the entries of the vector or matrix
     */
    public A min()
    {
        return getReference().instantiate(getDisplayUnit().ofSi(this.relativeVecMat.min().si()))
                .setDisplayUnit(getDisplayUnit());
    }

    /**
     * Return the maximum value of the entries of the vector or matrix.
     * @return the maximum value of the entries of the vector or matrix
     */
    public A max()
    {
        return getReference().instantiate(getDisplayUnit().ofSi(this.relativeVecMat.max().si()))
                .setDisplayUnit(getDisplayUnit());
    }

    /**
     * Return the median value of the entries of the vector or matrix.
     * @return the median value of the entries of the vector or matrix
     */
    public A median()
    {
        return getReference().instantiate(getDisplayUnit().ofSi(this.relativeVecMat.median().si()))
                .setDisplayUnit(getDisplayUnit());
    }

    /**
     * Return a vector or matrix with entries that contain the sum of the element and the increment.
     * @param increment the quantity by which to increase the values of the vector or matrix
     * @return a vector or matrix with entries that are incremented by the given quantity
     */
    public VMA add(final Q increment)
    {
        return instantiateSi(ArrayMath.add(this.relativeVecMat.unsafeSiArray(), increment.si()), getReference())
                .setDisplayUnit(getDisplayUnit());
    }

    /**
     * Return a vector or matrix with entries that contain the value minus the decrement.
     * @param decrement the quantity by which to decrease the values of the vector or matrix
     * @return a vector or matrix with entries that are decremented by the given quantity
     */
    public VMA subtract(final Q decrement)
    {
        return instantiateSi(ArrayMath.add(this.relativeVecMat.unsafeSiArray(), -decrement.si()), getReference())
                .setDisplayUnit(getDisplayUnit());
    }

    /**
     * Return a vector or matrix with entries that contain the sum of the element and the increment.
     * @param other the vector or matrix that contains the values by which to increase the values of the vector or matrix
     * @return a vector or matrix with entries that are decremented by the given quantity
     */
    public VMA add(final VMQ other)
    {
        return instantiateSi(ArrayMath.add(this.relativeVecMat.unsafeSiArray(), other.unsafeSiArray()), getReference())
                .setDisplayUnit(getDisplayUnit());
    }

    /**
     * Return a vector or matrix with entries that contain the value minus the decrement.
     * @param other the vector or matrix that contains the values by which to decrease the values of the vector or matrix
     * @return a vector or matrix with entries that are decremented by the given vector or matrix values
     */
    public VMA subtract(final VMQ other)
    {
        return instantiateSi(ArrayMath.subtract(this.relativeVecMat.unsafeSiArray(), other.unsafeSiArray()), getReference())
                .setDisplayUnit(getDisplayUnit());
    }

    /**
     * Return a relative vector or matrix with entries that contain the absolute value minus the absolute decrement.
     * @param other the vector or matrix that contains the values by which to decrease the values of the vector or matrix
     * @return a vector or matrix with entries that are decremented by the given vector or matrix values
     */
    public VMQ subtract(final VMA other)
    {
        return this.relativeVecMat
                .instantiateSi(
                        ArrayMath.subtract(this.relativeVecMat.unsafeSiArray(), other.getRelativeVecMat().unsafeSiArray()))
                .setDisplayUnit(getDisplayUnit());
    }

    /**
     * Return a relative vector or matrix with entries that contain the absolute value minus the absolute decrement.
     * @param decrement the absolute quantity by which to decrease the values of the vector or matrix
     * @return a vector or matrix with entries that are decremented by the given decrement
     */
    public VMQ subtract(final A decrement)
    {
        return this.relativeVecMat.instantiateSi(ArrayMath.add(this.relativeVecMat.unsafeSiArray(), -decrement.si()))
                .setDisplayUnit(getDisplayUnit());
    }

    // ------------------------------------ AS() METHODS ------------------------------------

    /**
     * Convert this absolute vector or matrix to a {@link AbsMatrixNxM}. The underlying data MIGHT be shared between this object
     * and the new AbsMatrixNxM.
     * @return a {@code AbsMatrixNxN} with identical SI data, display unit, and reference point
     */
    public AbsMatrixNxM<A, Q> asAbsMatrixNxM()
    {
        return new AbsMatrixNxM<A, Q>(MatrixNxM.ofSi(getRelativeVecMat().unsafeSiArray(), rows(), cols(), getDisplayUnit()),
                getReference());
    }

    /**
     * Convert this absolute vector or matrix to a {@link AbsQuantityTable}. The underlying data MIGHT be shared between this
     * object and the new AbsQuantityTable.
     * @return a {@code AbsQuantityTable} with identical SI data, display unit, and reference point
     */
    public AbsQuantityTable<A, Q> asAbsQuantityTable()
    {
        return new AbsQuantityTable<A, Q>(
                QuantityTable.ofSi(getRelativeVecMat().unsafeSiArray(), rows(), cols(), getDisplayUnit()), getReference());
    }

    /**
     * Return this absolute vector, matrix or table as a 1-element column vector. Shape must be 1 x 1.
     * @return a {@code AbsVector1} with identical SI data and display unit
     * @throws IllegalStateException if shape is not 1 x 1
     */
    public AbsVector1<A, Q> asAbsVector1()
    {
        Throw.when(rows() != 1 || cols() != 1, IllegalStateException.class, "Matrix is not 1x1");
        final double[] data = getRelativeVecMat().unsafeSiArray();
        return new AbsVector1<A, Q>(new Vector1<Q>(data[0], getDisplayUnit()), getReference());
    }

    /**
     * Return this absolute vector, matrix or table as a 2-element column vector. Shape must be 2 x 1.
     * @return a {@code AbsVector2.Col} with identical SI data and display unit
     * @throws IllegalStateException if shape is not 2 x 1
     */
    public AbsVector2.Col<A, Q> asAbsVector2Col()
    {
        Throw.when(rows() != 2 || cols() != 1, IllegalStateException.class, "Matrix is not 2x1");
        final double[] data = getRelativeVecMat().unsafeSiArray();
        return new AbsVector2.Col<A, Q>(new Vector2.Col<Q>(data[0], data[1], getDisplayUnit()), getReference());
    }

    /**
     * Return this absolute vector, matrix or table as a 3-element column vector. Shape must be 3 x 1.
     * @return a {@code AbsVector3.Col} with identical SI data and display unit
     * @throws IllegalStateException if shape is not 3 x 1
     */
    public AbsVector3.Col<A, Q> asAbsVector3Col()
    {
        Throw.when(rows() != 3 || cols() != 1, IllegalStateException.class, "Matrix is not 3x1");
        final double[] data = getRelativeVecMat().unsafeSiArray();
        return new AbsVector3.Col<A, Q>(new Vector3.Col<Q>(data[0], data[1], data[2], getDisplayUnit()), getReference());
    }

    /**
     * Convert this absolute vector, matrix or table to an N-element column vector. Shape must be N x 1. The underlying data
     * MIGHT be shared between this object and the AbsVectorN.Col.
     * @return a {@code AbsVectorN.Col} with identical SI data and display unit
     * @throws IllegalStateException if {@code cols() != 1}
     */
    public AbsVectorN.Col<A, Q> asAbsVectorNCol()
    {
        Throw.when(cols() != 1, IllegalStateException.class, "Matrix is not Nx1");
        return new AbsVectorN.Col<A, Q>(VectorN.Col.ofSi(getRelativeVecMat().unsafeSiArray(), getDisplayUnit()),
                getReference());
    }

    /**
     * Return this absolute vector, matrix or table as a 2-element row vector. Shape must be 1 x 2.
     * @return a {@code AbsVector2.Row} with identical SI data and display unit
     * @throws IllegalStateException if shape is not 1 x 2
     */
    public AbsVector2.Row<A, Q> asAbsVector2Row()
    {
        Throw.when(rows() != 1 || cols() != 2, IllegalStateException.class, "Matrix is not 1x2");
        final double[] data = getRelativeVecMat().unsafeSiArray();
        return new AbsVector2.Row<A, Q>(new Vector2.Row<Q>(data[0], data[1], getDisplayUnit()), getReference());
    }

    /**
     * Return this absolute vector, matrix or table as a 3-element row vector. Shape must be 1 x 3.
     * @return a {@code AbsVector3.Row} with identical SI data and display unit
     * @throws IllegalStateException if shape is not 1 x 3
     */
    public AbsVector3.Row<A, Q> asAbsVector3Row()
    {
        Throw.when(rows() != 1 || cols() != 3, IllegalStateException.class, "Matrix is not 1x3");
        final double[] data = getRelativeVecMat().unsafeSiArray();
        return new AbsVector3.Row<A, Q>(new Vector3.Row<Q>(data[0], data[1], data[2], getDisplayUnit()), getReference());
    }

    /**
     * Convert this absolute vector, matrix or table to an N-element row vector. Shape must be 1 x N. The underlying data MIGHT
     * be shared between this object and the AbsVectorN.Row.
     * @return a {@code AbsVectorN.Row} with identical SI data and display unit
     * @throws IllegalStateException if {@code rows() != 1}
     */
    public AbsVectorN.Row<A, Q> asAbsVectorNRow()
    {
        Throw.when(rows() != 1, IllegalStateException.class, "Matrix is not 1xN");
        return new AbsVectorN.Row<A, Q>(VectorN.Row.ofSi(getRelativeVecMat().unsafeSiArray(), getDisplayUnit()),
                getReference());
    }

    /**
     * Convert this absolute vector, matrix or table to a {@link AbsMatrix1x1}. The shape must be 1 x 1.
     * @return a {@code AbsMatrix1x1} with identical SI data and display unit
     * @throws IllegalStateException if this matrix is not 1 x 1
     */
    public AbsMatrix1x1<A, Q> asAbsMatrix1x1()
    {
        Throw.when(rows() != 1 || cols() != 1, IllegalStateException.class,
                "asAbsMatrix1x1() called, but matrix is no 1x1 but %dx%d", rows(), cols());
        return new AbsMatrix1x1<A, Q>(Matrix1x1.ofSi(getRelativeVecMat().unsafeSiArray(), getDisplayUnit()), getReference());
    }

    /**
     * Convert this absolute vector, matrix or table to a {@link AbsMatrix2x2}. The shape must be 2 x 2.
     * @return a {@code AbsMatrix2x2} with identical SI data and display unit
     * @throws IllegalStateException if this matrix is not 2 x 2
     */
    public AbsMatrix2x2<A, Q> asAbsMatrix2x2()
    {
        Throw.when(rows() != 2 || cols() != 2, IllegalStateException.class,
                "asAbsMatrix2x2() called, but matrix is no 2x2 but %dx%d", rows(), cols());
        return new AbsMatrix2x2<A, Q>(Matrix2x2.ofSi(getRelativeVecMat().unsafeSiArray(), getDisplayUnit()), getReference());
    }

    /**
     * Convert this absolute vector, matrix or table to a {@link AbsMatrix3x3}. The shape must be 3 x 3.
     * @return a {@code AbsMatrix3x3} with identical SI data and display unit
     * @throws IllegalStateException if this matrix is not 3 x 3
     */
    public AbsMatrix3x3<A, Q> asAbsMatrix3x3()
    {
        Throw.when(rows() != 3 || cols() != 3, IllegalStateException.class,
                "asAbsMatrix3x3() called, but matrix is no 3x3 but %dx%d", rows(), cols());
        return new AbsMatrix3x3<A, Q>(Matrix3x3.ofSi(getRelativeVecMat().unsafeSiArray(), getDisplayUnit()), getReference());
    }

    /**
     * Convert this absolute vector, matrix or table to a {@link AbsMatrixNxN}. The shape must be square. The underlying data
     * MIGHT be shared between this object and the AbsMatrixNxN.
     * @return a {@code AbsMatrixNxN} with identical SI data and display unit
     * @throws IllegalStateException if this matrix is not square
     */
    public AbsMatrixNxN<A, Q> asAbsMatrixNxN()
    {
        Throw.when(rows() != cols(), IllegalStateException.class, "asAbsMatrixNxN() called, but matrix is no square but %dx%d",
                rows(), cols());
        return new AbsMatrixNxN<A, Q>(MatrixNxN.ofSi(getRelativeVecMat().unsafeSiArray(), getDisplayUnit()), getReference());
    }

    // ======================================= hashCode() and equals() ===============================================

    @Override
    public int hashCode()
    {
        return Objects.hash(this.reference, this.relativeVecMat);
    }

    @Override
    @SuppressWarnings("checkstyle:needbraces")
    public boolean equals(final Object obj)
    {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        AbsVectorMatrix<?, ?, ?, ?, ?> other = (AbsVectorMatrix<?, ?, ?, ?, ?>) obj;
        return Objects.equals(this.reference, other.reference) && Objects.equals(this.relativeVecMat, other.relativeVecMat);
    }

    // -------------------------------- TOSTRING / FORMAT METHODS -------------------------------

    @Override
    public String toString()
    {
        return format();
    }
 
}