VectorMatrix.java
package org.djunits.vecmat.def;
import org.djunits.quantity.Dimensionless;
import org.djunits.quantity.SIQuantity;
import org.djunits.quantity.def.Quantity;
import org.djunits.unit.Unit;
import org.djunits.unit.si.SIUnit;
import org.djunits.util.ArrayMath;
import org.djunits.util.Math2;
import org.djunits.value.Additive;
import org.djunits.value.Scalable;
import org.djunits.value.Value;
import org.djunits.vecmat.d1.Matrix1x1;
import org.djunits.vecmat.d1.Vector1;
import org.djunits.vecmat.d2.Matrix2x2;
import org.djunits.vecmat.d2.Vector2;
import org.djunits.vecmat.d3.Matrix3x3;
import org.djunits.vecmat.d3.Vector3;
import org.djunits.vecmat.dn.MatrixNxN;
import org.djunits.vecmat.dn.VectorN;
import org.djunits.vecmat.dnxm.MatrixNxM;
import org.djunits.vecmat.operations.Hadamard;
import org.djunits.vecmat.storage.DenseDoubleDataSi;
import org.djunits.vecmat.table.QuantityTable;
import org.djutils.exceptions.Throw;
/**
* VectorMatrix contains a number of standard operations on vectors and matrices of relative 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 <Q> the quantity type
* @param <VM> the 'SELF' vector or matrix type
* @param <SI> the vector or matrix type with generics <SIQuantity, SIUnit<
* @param <H> the generic vector or matrix type with generics <?, ?< for Hadamard operations
* @param <VMT> the type of the transposed version of the vector or matrix
*/
public abstract class VectorMatrix<Q extends Quantity<Q>, VM extends VectorMatrix<Q, VM, SI, H, VMT>,
SI extends VectorMatrix<SIQuantity, SI, ?, ?, ?>, H extends VectorMatrix<?, ?, ?, ?, ?>,
VMT extends VectorMatrix<Q, VMT, ?, ?, VM>> implements Value<VM, Q>, Scalable<VM>, Additive<VM>, Hadamard<H, SI>
{
/** */
private static final long serialVersionUID = 600L;
/** The display unit. */
private Unit<?, Q> displayUnit;
/**
* Create a new vector or matrix with a unit.
* @param displayUnit the display unit to use
*/
public VectorMatrix(final Unit<?, Q> displayUnit)
{
Throw.whenNull(displayUnit, "displayUnit");
this.displayUnit = displayUnit;
}
@Override
public Unit<?, Q> getDisplayUnit()
{
return this.displayUnit;
}
@SuppressWarnings("unchecked")
@Override
public VM setDisplayUnit(final Unit<?, Q> newUnit)
{
Throw.whenNull(newUnit, "newUnit");
this.displayUnit = newUnit;
return (VM) this;
}
/**
* Return the number of rows.
* @return the number of rows
*/
public abstract int rows();
/**
* Return the number of columns.
* @return the number of columns
*/
public abstract int cols();
/**
* Return a new vector or matrix with the given SI or BASE values.
* @param siNew the values for the new vector or matrix in row-major format
* @return a new matrix with the provided SI or BASE values
*/
public abstract VM instantiateSi(double[] siNew);
/**
* Return a new vector or matrix in SI-units with the given SI or BASE values.
* @param siNew the values for the new vector or matrix in row-major format
* @param siUnit the new unit for the new vector or matrix
* @return a new matrix with the provided SI or BASE values
*/
public abstract SI instantiateSi(double[] siNew, SIUnit siUnit);
/**
* Return a row-major array of SI-values for this matrix or vector. This is guaranteed to be a safe copy.
* @return the row-major array of SI-values (safe copy)
*/
public abstract double[] getSiArray();
/**
* Return a row-major possibly UNSAFE array of SI-values for this matrix or vector. The method might give access to the
* underlying data structure, so treat the data carefully.
* @return the row-major array of SI-values (safe copy)
*/
public abstract double[] unsafeSiArray();
/**
* Return a transposed vector or matrix, where rows and columns have been swapped.
* @return a transposed vector or matrix, where rows and columns have been swapped
*/
public abstract VMT transpose();
@Override
public boolean isRelative()
{
return getDisplayUnit().ofSi(0.0).isRelative();
}
/**
* Return the mean value of the entries of the vector or matrix.
* @return the mean value of the entries of the vector or matrix
*/
public Q mean()
{
double[] siArray = unsafeSiArray();
return getDisplayUnit().ofSi(Math2.sum(siArray) / siArray.length).setDisplayUnit(getDisplayUnit());
}
/**
* 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 Q min()
{
return getDisplayUnit().ofSi(Math2.min(unsafeSiArray())).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 Q max()
{
return getDisplayUnit().ofSi(Math2.max(unsafeSiArray())).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 Q median()
{
return getDisplayUnit().ofSi(Math2.median(unsafeSiArray())).setDisplayUnit(getDisplayUnit());
}
/**
* Return the sum of the values of the entries of the vector or matrix.
* @return the sum of the values of the entries of the vector or matrix
*/
public Q sum()
{
return getDisplayUnit().ofSi(Math2.sum(unsafeSiArray())).setDisplayUnit(getDisplayUnit());
}
/**
* Return the 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 VM add(final Q increment)
{
return instantiateSi(ArrayMath.add(unsafeSiArray(), increment.si()));
}
/**
* Return the 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 VM subtract(final Q decrement)
{
return instantiateSi(ArrayMath.add(unsafeSiArray(), -decrement.si()));
}
@Override
public VM add(final VM other)
{
return instantiateSi(ArrayMath.add(unsafeSiArray(), other.unsafeSiArray()));
}
@Override
public VM subtract(final VM other)
{
return instantiateSi(ArrayMath.subtract(unsafeSiArray(), other.unsafeSiArray()));
}
@Override
public VM negate()
{
return scaleBy(-1.0);
}
@Override
public VM abs()
{
return instantiateSi(ArrayMath.abs(unsafeSiArray()));
}
@Override
public VM scaleBy(final double factor)
{
return instantiateSi(ArrayMath.scaleBy(unsafeSiArray(), factor));
}
/**
* Return the number of non-zero entries in the vector, matrix or table. Note that NaN and Infinity count as a non-zero
* element. The value -0.0 counts as 0.0.
* @return the number of non-zero entries in the vector, matrix or table
*/
public abstract int nonZeroCount();
/**
* Return the number of non-zero entries in the vector, matrix or table. Note that NaN and Infinity count as a non-zero
* element. The value -0.0 counts as 0.0. the acronym 'nnz' stands for 'number of non-zero entries'.
* @return the number of non-zero entries in the vector, matrix or table
*/
public int nnz()
{
return nonZeroCount();
}
/**
* Multiply the entries of this vector, matrix or table by the given quantity. This is actually a Hadamard operation, but
* since it is equivalent to a scaleBy operation, it is included in this interface.
* @param quantity the scalar quantity to multiply by
* @return a vector, matrix or table where the entries have been multiplied by the given quantity
*/
public VM multiplyElements(final Dimensionless quantity)
{
return scaleBy(quantity.si());
}
/**
* Multiply the entries of this vector, matrix or table by the given quantity. This is actually a Hadamard operation, but
* since it is equivalent to a scaleBy operation, it is included in this interface.
* @param quantity the scalar quantity to multiply by
* @return a vector, matrix or table where the entries have been multiplied by the given quantity
*/
public VM divideElements(final Dimensionless quantity)
{
return scaleBy(1.0 / quantity.si());
}
@Override
public SI invertEntries()
{
return (SI) instantiateSi(ArrayMath.reciprocal(unsafeSiArray()), getDisplayUnit().siUnit().invert());
}
@Override
public SI multiplyEntries(final H other)
{
return (SI) instantiateSi(ArrayMath.multiply(unsafeSiArray(), other.unsafeSiArray()),
getDisplayUnit().siUnit().plus(other.getDisplayUnit().siUnit()));
}
@Override
public SI divideEntries(final H other)
{
return (SI) instantiateSi(ArrayMath.divide(unsafeSiArray(), other.unsafeSiArray()),
getDisplayUnit().siUnit().minus(other.getDisplayUnit().siUnit()));
}
@Override
public SI multiplyEntries(final Quantity<?> quantity)
{
return (SI) instantiateSi(ArrayMath.scaleBy(unsafeSiArray(), quantity.si()),
getDisplayUnit().siUnit().plus(quantity.getDisplayUnit().siUnit()));
}
// ------------------------------------ AS() METHODS ------------------------------------
/**
* Convert this vector or matrix to a {@link MatrixNxM}. The underlying data MIGHT be shared between this object and the
* MatrixNxM.
* @return a {@code MatrixNxN} with identical SI data and display unit
*/
public MatrixNxM<Q> asMatrixNxM()
{
return new MatrixNxM<Q>(new DenseDoubleDataSi(unsafeSiArray(), rows(), cols()), getDisplayUnit());
}
/**
* Convert this vector or matrix to a {@link QuantityTable}. The underlying data MIGHT be shared between this object and the
* quantity table.
* @return a {@code QuantityTable} with identical SI data and display unit
*/
public QuantityTable<Q> asQuantityTable()
{
return new QuantityTable<Q>(new DenseDoubleDataSi(unsafeSiArray(), rows(), cols()), getDisplayUnit());
}
/**
* Return this vector, matrix or table as a 1-element column vector. Shape must be 1 x 1.
* @return a {@code Vector1} with identical SI data and display unit
* @throws IllegalStateException if shape is not 1 x 1
*/
public Vector1<Q> asVector1()
{
Throw.when(rows() != 1 || cols() != 1, IllegalStateException.class, "Matrix is not 1x1");
final double[] data = unsafeSiArray();
return new Vector1<Q>(data[0], getDisplayUnit());
}
/**
* Return this vector, matrix or table as a 2-element column vector. Shape must be 2 x 1.
* @return a {@code Vector2.Col} with identical SI data and display unit
* @throws IllegalStateException if shape is not 2 x 1
*/
public Vector2.Col<Q> asVector2Col()
{
Throw.when(rows() != 2 || cols() != 1, IllegalStateException.class, "Matrix is not 2x1");
final double[] data = unsafeSiArray();
return new Vector2.Col<Q>(data[0], data[1], getDisplayUnit());
}
/**
* Return this vector, matrix or table as a 3-element column vector. Shape must be 3 x 1.
* @return a {@code Vector3.Col} with identical SI data and display unit
* @throws IllegalStateException if shape is not 3 x 1
*/
public Vector3.Col<Q> asVector3Col()
{
Throw.when(rows() != 3 || cols() != 1, IllegalStateException.class, "Matrix is not 3x1");
final double[] data = unsafeSiArray();
return new Vector3.Col<Q>(data[0], data[1], data[2], getDisplayUnit());
}
/**
* Convert this 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 VectorN.Col.
* @return a {@code VectorN.Col} with identical SI data and display unit
* @throws IllegalStateException if {@code cols() != 1}
*/
public VectorN.Col<Q> asVectorNCol()
{
Throw.when(cols() != 1, IllegalStateException.class, "Matrix is not Nx1");
return VectorN.Col.ofSi(unsafeSiArray(), getDisplayUnit());
}
/**
* Return this vector, matrix or table as a 2-element row vector. Shape must be 1 x 2.
* @return a {@code Vector2.Row} with identical SI data and display unit
* @throws IllegalStateException if shape is not 1 x 2
*/
public Vector2.Row<Q> asVector2Row()
{
Throw.when(rows() != 1 || cols() != 2, IllegalStateException.class, "Matrix is not 1x2");
final double[] data = unsafeSiArray();
return new Vector2.Row<Q>(data[0], data[1], getDisplayUnit());
}
/**
* Return this vector, matrix or table as a 3-element row vector. Shape must be 1 x 3.
* @return a {@code Vector3.Row} with identical SI data and display unit
* @throws IllegalStateException if shape is not 1 x 3
*/
public Vector3.Row<Q> asVector3Row()
{
Throw.when(rows() != 1 || cols() != 3, IllegalStateException.class, "Matrix is not 1x3");
final double[] data = unsafeSiArray();
return new Vector3.Row<Q>(data[0], data[1], data[2], getDisplayUnit());
}
/**
* Convert this 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 VectorN.Row.
* @return a {@code VectorN.Row} with identical SI data and display unit
* @throws IllegalStateException if {@code rows() != 1}
*/
public VectorN.Row<Q> asVectorNRow()
{
Throw.when(rows() != 1, IllegalStateException.class, "Matrix is not 1xN");
return VectorN.Row.ofSi(unsafeSiArray(), getDisplayUnit());
}
/**
* Convert this vector, matrix or table to a {@link Matrix1x1}. The shape must be 1 x 1.
* @return a {@code Matrix1x1} with identical SI data and display unit
* @throws IllegalStateException if this matrix is not 1 x 1
*/
public Matrix1x1<Q> asMatrix1x1()
{
Throw.when(rows() != 1 || cols() != 1, IllegalStateException.class,
"asMatrix1x1() called, but matrix is no 1x1 but %dx%d", rows(), cols());
return Matrix1x1.ofSi(unsafeSiArray(), getDisplayUnit());
}
/**
* Convert this vector, matrix or table to a {@link Matrix2x2}. The shape must be 2 x 2.
* @return a {@code Matrix2x2} with identical SI data and display unit
* @throws IllegalStateException if this matrix is not 2 x 2
*/
public Matrix2x2<Q> asMatrix2x2()
{
Throw.when(rows() != 2 || cols() != 2, IllegalStateException.class,
"asMatrix2x2() called, but matrix is no 2x2 but %dx%d", rows(), cols());
return Matrix2x2.ofSi(unsafeSiArray(), getDisplayUnit());
}
/**
* Convert this vector, matrix or table to a {@link Matrix3x3}. The shape must be 3 x 3.
* @return a {@code Matrix3x3} with identical SI data and display unit
* @throws IllegalStateException if this matrix is not 3 x 3
*/
public Matrix3x3<Q> asMatrix3x3()
{
Throw.when(rows() != 3 || cols() != 3, IllegalStateException.class,
"asMatrix3x3() called, but matrix is no 3x3 but %dx%d", rows(), cols());
return Matrix3x3.ofSi(unsafeSiArray(), getDisplayUnit());
}
/**
* Convert this vector, matrix or table to a {@link MatrixNxN}. The shape must be square. The underlying data MIGHT be
* shared between this object and the MatrixNxN.
* @return a {@code MatrixNxN} with identical SI data and display unit
* @throws IllegalStateException if this matrix is not square
*/
public MatrixNxN<Q> asMatrixNxN()
{
Throw.when(rows() != cols(), IllegalStateException.class, "asMatrixNxN() called, but matrix is no square but %dx%d",
rows(), cols());
return new MatrixNxN<Q>(new DenseDoubleDataSi(unsafeSiArray(), rows(), cols()), getDisplayUnit());
}
// ------------------------------------ HELPER METHODS ------------------------------------
/**
* Check if the multiplication with the other matrix is valid. A valid matrix multiplication is (M x N) x (N x P).
* @param matrix the other matrix
* @throws IllegalArgumentException when this.cols() != other.rows()
*/
protected void checkMultiply(final Matrix<?, ?, ?, ?, ?> matrix)
{
Throw.whenNull(matrix, "matrix");
Throw.when(cols() != matrix.rows(), IllegalArgumentException.class,
"Matrix multiplication (M x N) x (N x P): this.cols (%d) != matrix.rows (%d)", cols(), matrix.rows());
}
/**
* Check if the multiplication with the other matrix is valid. A valid matrix multiplication is (M x N) x (N x P).
* @param vector the other matrix
* @throws IllegalArgumentException when this.cols() != other.rows()
*/
protected void checkMultiply(final Vector<?, ?, ?, ?, ?> vector)
{
Throw.whenNull(vector, "matrix");
Throw.when(cols() != vector.rows(), IllegalArgumentException.class,
"Matrix multiplication (M x N) x (N x P): this.cols (%d) != vector.rows (%d)", cols(), vector.rows());
}
// -------------------------------- TOSTRING / FORMAT METHODS -------------------------------
@Override
public String toString()
{
return format();
}
}