VectorN.java
package org.djunits.vecmat.dn;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
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.vecmat.def.Vector;
import org.djunits.vecmat.storage.DataGridSi;
import org.djunits.vecmat.storage.DenseDoubleDataSi;
import org.djutils.exceptions.Throw;
/**
* VectorN.java.
* <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 <V> the vector type (Row or Col)
* @param <SI> the vector type with generics <SIQuantity, SIUnit<
* @param <H> the generic vector type with generics <?, ?< for Hadamard operations
* @param <VT> the type of the transposed version of the vector
*/
public abstract class VectorN<Q extends Quantity<Q>, V extends VectorN<Q, V, SI, H, VT>,
SI extends VectorN<SIQuantity, SI, ?, ?, ?>, H extends VectorN<?, ?, ?, ?, ?>, VT extends VectorN<Q, VT, ?, ?, V>>
extends Vector<Q, V, SI, H, VT>
{
/** */
private static final long serialVersionUID = 600L;
/** The data of the matrix, in SI unit. */
@SuppressWarnings("checkstyle:visibilitymodifier")
protected final DataGridSi<?> dataSi;
/**
* Create a new VectorN with a unit, based on a DataGridSi storage object that contains SI data.
* @param dataSi the data of the vector, in SI unit.
* @param displayUnit the display unit to use
* @throws IllegalArgumentException when the number of rows or columns does not have a positive value
*/
protected VectorN(final DataGridSi<?> dataSi, final Unit<?, Q> displayUnit)
{
super(displayUnit);
Throw.whenNull(dataSi, "dataSi");
this.dataSi = dataSi;
}
@Override
public Iterator<Q> iterator()
{
final double[] si = this.dataSi.unsafeSiArray();
final Unit<?, Q> frozenDisplayUnit = getDisplayUnit(); // capture once
return Arrays.stream(si).mapToObj(v -> frozenDisplayUnit.ofSi(v).setDisplayUnit(frozenDisplayUnit)).iterator();
}
@Override
public Q[] getScalarArray()
{
final double[] siArray = this.dataSi.unsafeSiArray();
final Unit<?, Q> frozenDisplayUnit = getDisplayUnit(); // capture once
final Q first = frozenDisplayUnit.ofSi(siArray[0]).setDisplayUnit(frozenDisplayUnit);
final Class<?> qClass = first.getClass();
@SuppressWarnings("unchecked")
final Q[] out = (Q[]) Array.newInstance(qClass, siArray.length);
out[0] = first;
for (int i = 1; i < siArray.length; i++)
{
out[i] = frozenDisplayUnit.ofSi(siArray[i]).setDisplayUnit(frozenDisplayUnit);
}
return out;
}
@Override
public Q normL1()
{
double n = 0.0;
for (var d : unsafeSiArray())
{
n += Math.abs(d);
}
return getDisplayUnit().ofSi(n).setDisplayUnit(getDisplayUnit());
}
@Override
public Q normL2()
{
double n = 0.0;
for (var d : unsafeSiArray())
{
n += d * d;
}
return getDisplayUnit().ofSi(Math.sqrt(n)).setDisplayUnit(getDisplayUnit());
}
@Override
public Q normLp(final int p)
{
double n = 0.0;
for (var d : unsafeSiArray())
{
n += Math.pow(Math.abs(d), p);
}
return getDisplayUnit().ofSi(Math.pow(n, 1.0 / p)).setDisplayUnit(getDisplayUnit());
}
@Override
public Q normLinf()
{
double max = Double.NEGATIVE_INFINITY;
for (var d : unsafeSiArray())
{
max = Math.max(Math.abs(d), max);
}
return getDisplayUnit().ofSi(max).setDisplayUnit(getDisplayUnit());
}
@Override
public int rows()
{
return this.dataSi.rows();
}
@Override
public int cols()
{
return this.dataSi.cols();
}
@Override
public double[] getSiArray()
{
return this.dataSi.getSiArray();
}
@Override
public double[] unsafeSiArray()
{
return this.dataSi.unsafeSiArray();
}
@Override
public int nonZeroCount()
{
return this.dataSi.nonZeroCount();
}
@Override
public int hashCode()
{
return Objects.hash(this.dataSi, rows(), cols());
}
@SuppressWarnings("checkstyle:needbraces")
@Override
public boolean equals(final Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
VectorN<?, ?, ?, ?, ?> other = (VectorN<?, ?, ?, ?, ?>) obj;
return Objects.equals(this.dataSi, other.dataSi) && rows() == other.rows() && cols() == other.cols();
}
/**
* VectorN.Col implements a column vector with real-valued entries. The vector is immutable, except for the display unit,
* which can be changed.
* <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
*/
public static class Col<Q extends Quantity<Q>> extends
VectorN<Q, Col<Q>, VectorN.Col<SIQuantity>, VectorN.Col<?>, VectorN.Row<Q>> implements Vector.Col<VectorN.Col<Q>, Q>
{
/** */
private static final long serialVersionUID = 600L;
/**
* Create a new column VectorN with a unit, based on a DataGridSi storage object that contains SI data.
* @param dataSi the data of the vector, in SI unit.
* @param displayUnit the display unit to use
* @throws IllegalArgumentException when the number of rows or columns does not have a positive value or when the vector
* is initialized with more than one row
*/
public Col(final DataGridSi<?> dataSi, final Unit<?, Q> displayUnit)
{
super(dataSi, displayUnit);
Throw.when(dataSi.cols() != 1, IllegalArgumentException.class,
"Column vector initialized with more than one column");
}
@Override
public VectorN.Col<Q> instantiateSi(final double[] data)
{
return new VectorN.Col<Q>(this.dataSi.instantiateNew(data), getDisplayUnit().getBaseUnit())
.setDisplayUnit(getDisplayUnit());
}
@Override
public Col<SIQuantity> instantiateSi(final double[] siNew, final SIUnit siUnit)
{
return new VectorN.Col<SIQuantity>(this.dataSi.instantiateNew(siNew), siUnit);
}
@Override
public boolean isColumnVector()
{
return true;
}
@Override
public int size()
{
return this.dataSi.rows();
}
@Override
public double si(final int index) throws IndexOutOfBoundsException
{
return this.dataSi.get(index, 0);
}
@Override
public VectorN.Row<Q> transpose()
{
var newSi = this.dataSi.instantiateNew(unsafeSiArray(), cols(), rows());
return new VectorN.Row<Q>(newSi, getDisplayUnit().getBaseUnit()).setDisplayUnit(getDisplayUnit());
}
@Override
public VectorN.Col<SIQuantity> invertEntries()
{
SIUnit siUnit = getDisplayUnit().siUnit().invert();
return new VectorN.Col<SIQuantity>(this.dataSi.instantiateNew(ArrayMath.reciprocal(unsafeSiArray())), siUnit);
}
@Override
public VectorN.Col<SIQuantity> multiplyEntries(final VectorN.Col<?> other)
{
SIUnit siUnit = SIUnit.add(getDisplayUnit().siUnit(), other.getDisplayUnit().siUnit());
return new VectorN.Col<SIQuantity>(
this.dataSi.instantiateNew(ArrayMath.multiply(unsafeSiArray(), other.unsafeSiArray())), siUnit);
}
@Override
public VectorN.Col<SIQuantity> divideEntries(final VectorN.Col<?> other)
{
SIUnit siUnit = SIUnit.subtract(getDisplayUnit().siUnit(), other.getDisplayUnit().siUnit());
return new VectorN.Col<SIQuantity>(
this.dataSi.instantiateNew(ArrayMath.divide(unsafeSiArray(), other.unsafeSiArray())), siUnit);
}
@Override
public VectorN.Col<SIQuantity> multiplyEntries(final Quantity<?> quantity)
{
SIUnit siUnit = SIUnit.add(getDisplayUnit().siUnit(), quantity.getDisplayUnit().siUnit());
return new VectorN.Col<SIQuantity>(this.dataSi.instantiateNew(ArrayMath.scaleBy(unsafeSiArray(), quantity.si())),
siUnit);
}
// ------------------------------------------ OF METHODS ------------------------------------------
/**
* Create a new column VectorN with a unit, based on a double[] array that contains data in the given unit.
* @param dataInUnit the data of the vector, in the given unit.
* @param unit the unit of the data
* @return a new column VectorN with a unit, based on a double[] array expressed in the given unit
* @param <Q> the quantity type
*/
public static <Q extends Quantity<Q>> VectorN.Col<Q> of(final double[] dataInUnit, final Unit<?, Q> unit)
{
double[] dataSi = new double[dataInUnit.length];
for (int i = 0; i < dataInUnit.length; i++)
{
dataSi[i] = unit.toBaseValue(dataInUnit[i]);
}
return ofSi(dataSi, unit);
}
/**
* Create a new column VectorN with a unit, based on a double[] array that contains SI data.
* @param dataSi the data of the vector, in SI unit.
* @param displayUnit the display unit to use
* @return a new column VectorN with a unit, based on a double[] array that contains SI data
* @param <Q> the quantity type
*/
public static <Q extends Quantity<Q>> VectorN.Col<Q> ofSi(final double[] dataSi, final Unit<?, Q> displayUnit)
{
return new VectorN.Col<Q>(new DenseDoubleDataSi(dataSi.clone(), dataSi.length, 1), displayUnit.getBaseUnit())
.setDisplayUnit(displayUnit);
}
/**
* Create a new column VectorN with a unit, based on a quantity array that contains data. The display unit will be taken
* from the first quantity in the array.
* @param data the data of the vector, in the given unit.
* @return a new column VectorN with a display unit, based on a quantity array
* @throws IllegalArgumentException when data array length is less than 1
* @param <Q> the quantity type
*/
public static <Q extends Quantity<Q>> VectorN.Col<Q> of(final Q[] data)
{
Throw.when(data.length < 1, IllegalArgumentException.class, "data array length < 1");
double[] dataSi = new double[data.length];
for (int i = 0; i < data.length; i++)
{
dataSi[i] = data[i].si();
}
return ofSi(dataSi, data[0].getDisplayUnit());
}
/**
* Create a new column VectorN with a unit, based on a DataGridSi storage object that contains SI data.
* @param dataSi the data of the vector, in SI unit.
* @param displayUnit the display unit to use
* @throws IllegalArgumentException when the number of rows or columns does not have a positive value or when the vector
* is initialized with more than one row
* @return a new column VectorN with a unit, based on a DataGridSi storage object that contains SI data
* @param <Q> the quantity type
*/
public static <Q extends Quantity<Q>> VectorN.Col<Q> ofSi(final DataGridSi<?> dataSi, final Unit<?, Q> displayUnit)
{
return new VectorN.Col<Q>(dataSi, displayUnit.getBaseUnit()).setDisplayUnit(displayUnit);
}
/**
* Create a new column VectorN with a unit, based on a quantity list that contains data. The display unit will be taken
* from the first quantity in the list.
* @param data the data of the vector, in the given unit.
* @return a new column VectorN with a display unit, based on a quantity list
* @throws IllegalArgumentException when data size is less than 1
* @param <Q> the quantity type
*/
public static <Q extends Quantity<Q>> VectorN.Col<Q> of(final List<Q> data)
{
Throw.when(data.size() < 1, IllegalArgumentException.class, "data.size < 1");
double[] dataSi = new double[data.size()];
for (int i = 0; i < data.size(); i++)
{
dataSi[i] = data.get(i).si();
}
return ofSi(dataSi, data.get(0).getDisplayUnit());
}
// ------------------------------------------ AS METHODS ------------------------------------------
/**
* Return the vector 'as' a vector with a known quantity, using a unit to express the result in. Throw a Runtime
* exception when the SI units of this vector and the target vector do not match. The dataSi object containing the
* vector values is NOT copied.
* @param targetUnit the unit to convert the vector to
* @return a quantity typed in the target vector class
* @throws IllegalArgumentException when the units do not match
* @param <TQ> target quantity type
*/
public <TQ extends Quantity<TQ>> VectorN.Col<TQ> as(final Unit<?, TQ> targetUnit) throws IllegalArgumentException
{
Throw.when(!getDisplayUnit().siUnit().equals(targetUnit.siUnit()), IllegalArgumentException.class,
"Quantity.as(%s) called, but units do not match: %s <> %s", targetUnit,
getDisplayUnit().siUnit().getDisplayAbbreviation(), targetUnit.siUnit().getDisplayAbbreviation());
return new VectorN.Col<TQ>(this.dataSi, targetUnit);
}
}
/**
* VectorN.Row implements a row vector with real-valued entries. The vector is immutable, except for the display unit, which
* can be changed.
* <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
*/
public static class Row<Q extends Quantity<Q>> extends
VectorN<Q, Row<Q>, VectorN.Row<SIQuantity>, VectorN.Row<?>, VectorN.Col<Q>> implements Vector.Row<VectorN.Row<Q>, Q>
{
/** */
private static final long serialVersionUID = 600L;
/**
* Create a new row VectorN with a unit, based on a DataGridSi storage object that contains SI data.
* @param dataSi the data of the vector, in SI unit.
* @param displayUnit the display unit to use
* @throws IllegalArgumentException when the number of rows or columns does not have a positive value or when the vector
* is initialized with more than one row
*/
public Row(final DataGridSi<?> dataSi, final Unit<?, Q> displayUnit)
{
super(dataSi, displayUnit);
Throw.when(dataSi.rows() != 1, IllegalArgumentException.class, "Row vector initialized with more than one row");
}
@Override
public boolean isColumnVector()
{
return false;
}
@Override
public VectorN.Row<Q> instantiateSi(final double[] data)
{
return new VectorN.Row<>(this.dataSi.instantiateNew(data), getDisplayUnit());
}
@Override
public VectorN.Row<SIQuantity> instantiateSi(final double[] siNew, final SIUnit siUnit)
{
return new VectorN.Row<SIQuantity>(this.dataSi.instantiateNew(siNew), siUnit);
}
@Override
public int size()
{
return this.dataSi.cols();
}
@Override
public double si(final int index) throws IndexOutOfBoundsException
{
return this.dataSi.get(0, index);
}
@Override
public VectorN.Col<Q> transpose()
{
var newSi = this.dataSi.instantiateNew(unsafeSiArray(), cols(), rows());
return new VectorN.Col<Q>(newSi, getDisplayUnit());
}
@Override
public VectorN.Row<SIQuantity> invertEntries()
{
SIUnit siUnit = getDisplayUnit().siUnit().invert();
return new VectorN.Row<SIQuantity>(this.dataSi.instantiateNew(ArrayMath.reciprocal(unsafeSiArray())), siUnit);
}
@Override
public VectorN.Row<SIQuantity> multiplyEntries(final VectorN.Row<?> other)
{
SIUnit siUnit = SIUnit.add(getDisplayUnit().siUnit(), other.getDisplayUnit().siUnit());
return new VectorN.Row<SIQuantity>(
this.dataSi.instantiateNew(ArrayMath.multiply(unsafeSiArray(), other.unsafeSiArray())), siUnit);
}
@Override
public VectorN.Row<SIQuantity> divideEntries(final VectorN.Row<?> other)
{
SIUnit siUnit = SIUnit.subtract(getDisplayUnit().siUnit(), other.getDisplayUnit().siUnit());
return new VectorN.Row<SIQuantity>(
this.dataSi.instantiateNew(ArrayMath.divide(unsafeSiArray(), other.unsafeSiArray())), siUnit);
}
@Override
public VectorN.Row<SIQuantity> multiplyEntries(final Quantity<?> quantity)
{
SIUnit siUnit = SIUnit.add(getDisplayUnit().siUnit(), quantity.getDisplayUnit().siUnit());
return new VectorN.Row<SIQuantity>(this.dataSi.instantiateNew(ArrayMath.scaleBy(unsafeSiArray(), quantity.si())),
siUnit);
}
// ------------------------------------------ OF METHODS ------------------------------------------
/**
* Create a new row VectorN with a unit, based on a double[] array that contains data in the given unit.
* @param dataInUnit the data of the vector, in the given unit.
* @param unit the unit of the data
* @return a new row VectorN with a unit, based on a double[] array expressed in the given unit
* @param <Q> the quantity type
*/
public static <Q extends Quantity<Q>> VectorN.Row<Q> of(final double[] dataInUnit, final Unit<?, Q> unit)
{
double[] dataSi = new double[dataInUnit.length];
for (int i = 0; i < dataInUnit.length; i++)
{
dataSi[i] = unit.toBaseValue(dataInUnit[i]);
}
return ofSi(dataSi, unit);
}
/**
* Create a new row VectorN with a unit, based on a double[] array that contains SI data.
* @param dataSi the data of the vector, in SI unit.
* @param displayUnit the display unit to use
* @return a new row VectorN with a unit, based on a double[] array that contains SI data
* @param <Q> the quantity type
*/
public static <Q extends Quantity<Q>> VectorN.Row<Q> ofSi(final double[] dataSi, final Unit<?, Q> displayUnit)
{
return new VectorN.Row<Q>(new DenseDoubleDataSi(dataSi.clone(), 1, dataSi.length), displayUnit);
}
/**
* Create a new row VectorN with a unit, based on a quantity array that contains data. The display unit will be taken
* from the first quantity in the array.
* @param data the data of the vector, in the given unit.
* @return a new row VectorN with a display unit, based on a quantity array
* @throws IllegalArgumentException when data array length is less than 1
* @param <Q> the quantity type
*/
public static <Q extends Quantity<Q>> VectorN.Row<Q> of(final Q[] data)
{
Throw.when(data.length < 1, IllegalArgumentException.class, "data array length < 1");
double[] dataSi = new double[data.length];
for (int i = 0; i < data.length; i++)
{
dataSi[i] = data[i].si();
}
return ofSi(dataSi, data[0].getDisplayUnit());
}
/**
* Create a new row VectorN with a unit, based on a DataGridSi storage object that contains SI data.
* @param dataSi the data of the vector, in SI unit.
* @param displayUnit the display unit to use
* @throws IllegalArgumentException when the number of rows or rows does not have a positive value or when the vector is
* initialized with more than one row
* @return a new row VectorN with a unit, based on a DataGridSi storage object that contains SI data
* @param <Q> the quantity type
*/
public static <Q extends Quantity<Q>> VectorN.Row<Q> ofSi(final DataGridSi<?> dataSi, final Unit<?, Q> displayUnit)
{
return new VectorN.Row<Q>(dataSi, displayUnit);
}
/**
* Create a new row VectorN with a unit, based on a quantity list that contains data. The display unit will be taken
* from the first quantity in the list.
* @param data the data of the vector, in the given unit.
* @return a new row VectorN with a display unit, based on a quantity list
* @throws IllegalArgumentException when data size is less than 1
* @param <Q> the quantity type
*/
public static <Q extends Quantity<Q>> VectorN.Row<Q> of(final List<Q> data)
{
Throw.when(data.size() < 1, IllegalArgumentException.class, "data.size < 1");
double[] dataSi = new double[data.size()];
for (int i = 0; i < data.size(); i++)
{
dataSi[i] = data.get(i).si();
}
return ofSi(dataSi, data.get(0).getDisplayUnit());
}
// ------------------------------------------ AS METHODS ------------------------------------------
/**
* Return the vector 'as' a vector with a known quantity, using a unit to express the result in. Throw a Runtime
* exception when the SI units of this vector and the target vector do not match. The dataSi object containing the
* vector values is NOT copied.
* @param targetUnit the unit to convert the vector to
* @return a quantity typed in the target vector class
* @throws IllegalArgumentException when the units do not match
* @param <TQ> target quantity type
*/
public <TQ extends Quantity<TQ>> VectorN.Row<TQ> as(final Unit<?, TQ> targetUnit) throws IllegalArgumentException
{
Throw.when(!getDisplayUnit().siUnit().equals(targetUnit.siUnit()), IllegalArgumentException.class,
"Quantity.as(%s) called, but units do not match: %s <> %s", targetUnit,
getDisplayUnit().siUnit().getDisplayAbbreviation(), targetUnit.siUnit().getDisplayAbbreviation());
return new VectorN.Row<TQ>(this.dataSi, targetUnit);
}
}
}