View Javadoc
1   package org.djunits.vecmat.dn;
2   
3   import java.lang.reflect.Array;
4   import java.util.Arrays;
5   import java.util.Iterator;
6   import java.util.List;
7   import java.util.Objects;
8   
9   import org.djunits.quantity.SIQuantity;
10  import org.djunits.quantity.def.Quantity;
11  import org.djunits.unit.Unit;
12  import org.djunits.unit.si.SIUnit;
13  import org.djunits.util.ArrayMath;
14  import org.djunits.vecmat.def.Vector;
15  import org.djunits.vecmat.storage.DataGridSi;
16  import org.djunits.vecmat.storage.DenseDoubleDataSi;
17  import org.djutils.exceptions.Throw;
18  
19  /**
20   * VectorN.java.
21   * <p>
22   * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
23   * for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
24   * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
25   * @author Alexander Verbraeck
26   * @param <Q> the quantity type
27   * @param <V> the vector type (Row or Col)
28   * @param <SI> the vector type with generics &lt;SIQuantity, SIUnit&lt;
29   * @param <H> the generic vector type with generics &lt;?, ?&lt; for Hadamard operations
30   * @param <VT> the type of the transposed version of the vector
31   */
32  public abstract class VectorN<Q extends Quantity<Q>, V extends VectorN<Q, V, SI, H, VT>,
33          SI extends VectorN<SIQuantity, SI, ?, ?, ?>, H extends VectorN<?, ?, ?, ?, ?>, VT extends VectorN<Q, VT, ?, ?, V>>
34          extends Vector<Q, V, SI, H, VT>
35  {
36      /** */
37      private static final long serialVersionUID = 600L;
38  
39      /** The data of the matrix, in SI unit. */
40      @SuppressWarnings("checkstyle:visibilitymodifier")
41      protected final DataGridSi<?> dataSi;
42  
43      /**
44       * Create a new VectorN with a unit, based on a DataGridSi storage object that contains SI data.
45       * @param dataSi the data of the vector, in SI unit.
46       * @param displayUnit the display unit to use
47       * @throws IllegalArgumentException when the number of rows or columns does not have a positive value
48       */
49      protected VectorN(final DataGridSi<?> dataSi, final Unit<?, Q> displayUnit)
50      {
51          super(displayUnit);
52          Throw.whenNull(dataSi, "dataSi");
53          this.dataSi = dataSi;
54      }
55  
56      @Override
57      public Iterator<Q> iterator()
58      {
59          final double[] si = this.dataSi.unsafeSiArray();
60          final Unit<?, Q> frozenDisplayUnit = getDisplayUnit(); // capture once
61          return Arrays.stream(si).mapToObj(v -> frozenDisplayUnit.ofSi(v).setDisplayUnit(frozenDisplayUnit)).iterator();
62      }
63  
64      @Override
65      public Q[] getScalarArray()
66      {
67          final double[] siArray = this.dataSi.unsafeSiArray();
68          final Unit<?, Q> frozenDisplayUnit = getDisplayUnit(); // capture once
69          final Q first = frozenDisplayUnit.ofSi(siArray[0]).setDisplayUnit(frozenDisplayUnit);
70          final Class<?> qClass = first.getClass();
71          @SuppressWarnings("unchecked")
72          final Q[] out = (Q[]) Array.newInstance(qClass, siArray.length);
73          out[0] = first;
74          for (int i = 1; i < siArray.length; i++)
75          {
76              out[i] = frozenDisplayUnit.ofSi(siArray[i]).setDisplayUnit(frozenDisplayUnit);
77          }
78          return out;
79      }
80  
81      @Override
82      public Q normL1()
83      {
84          double n = 0.0;
85          for (var d : unsafeSiArray())
86          {
87              n += Math.abs(d);
88          }
89          return getDisplayUnit().ofSi(n).setDisplayUnit(getDisplayUnit());
90      }
91  
92      @Override
93      public Q normL2()
94      {
95          double n = 0.0;
96          for (var d : unsafeSiArray())
97          {
98              n += d * d;
99          }
100         return getDisplayUnit().ofSi(Math.sqrt(n)).setDisplayUnit(getDisplayUnit());
101     }
102 
103     @Override
104     public Q normLp(final int p)
105     {
106         double n = 0.0;
107         for (var d : unsafeSiArray())
108         {
109             n += Math.pow(Math.abs(d), p);
110         }
111         return getDisplayUnit().ofSi(Math.pow(n, 1.0 / p)).setDisplayUnit(getDisplayUnit());
112     }
113 
114     @Override
115     public Q normLinf()
116     {
117         double max = Double.NEGATIVE_INFINITY;
118         for (var d : unsafeSiArray())
119         {
120             max = Math.max(Math.abs(d), max);
121         }
122         return getDisplayUnit().ofSi(max).setDisplayUnit(getDisplayUnit());
123     }
124 
125     @Override
126     public int rows()
127     {
128         return this.dataSi.rows();
129     }
130 
131     @Override
132     public int cols()
133     {
134         return this.dataSi.cols();
135     }
136 
137     @Override
138     public double[] getSiArray()
139     {
140         return this.dataSi.getSiArray();
141     }
142 
143     @Override
144     public double[] unsafeSiArray()
145     {
146         return this.dataSi.unsafeSiArray();
147     }
148 
149     @Override
150     public int nonZeroCount()
151     {
152         return this.dataSi.nonZeroCount();
153     }
154 
155     @Override
156     public int hashCode()
157     {
158         return Objects.hash(this.dataSi, rows(), cols());
159     }
160 
161     @SuppressWarnings("checkstyle:needbraces")
162     @Override
163     public boolean equals(final Object obj)
164     {
165         if (this == obj)
166             return true;
167         if (obj == null)
168             return false;
169         if (getClass() != obj.getClass())
170             return false;
171         VectorN<?, ?, ?, ?, ?> other = (VectorN<?, ?, ?, ?, ?>) obj;
172         return Objects.equals(this.dataSi, other.dataSi) && rows() == other.rows() && cols() == other.cols();
173     }
174 
175     /**
176      * VectorN.Col implements a column vector with real-valued entries. The vector is immutable, except for the display unit,
177      * which can be changed.
178      * <p>
179      * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
180      * See for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
181      * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
182      * @author Alexander Verbraeck
183      * @param <Q> the quantity type
184      */
185     public static class Col<Q extends Quantity<Q>> extends
186             VectorN<Q, Col<Q>, VectorN.Col<SIQuantity>, VectorN.Col<?>, VectorN.Row<Q>> implements Vector.Col<VectorN.Col<Q>, Q>
187     {
188         /** */
189         private static final long serialVersionUID = 600L;
190 
191         /**
192          * Create a new column VectorN with a unit, based on a DataGridSi storage object that contains SI data.
193          * @param dataSi the data of the vector, in SI unit.
194          * @param displayUnit the display unit to use
195          * @throws IllegalArgumentException when the number of rows or columns does not have a positive value or when the vector
196          *             is initialized with more than one row
197          */
198         public Col(final DataGridSi<?> dataSi, final Unit<?, Q> displayUnit)
199         {
200             super(dataSi, displayUnit);
201             Throw.when(dataSi.cols() != 1, IllegalArgumentException.class,
202                     "Column vector initialized with more than one column");
203         }
204 
205         @Override
206         public VectorN.Col<Q> instantiateSi(final double[] data)
207         {
208             return new VectorN.Col<Q>(this.dataSi.instantiateNew(data), getDisplayUnit().getBaseUnit())
209                     .setDisplayUnit(getDisplayUnit());
210         }
211 
212         @Override
213         public Col<SIQuantity> instantiateSi(final double[] siNew, final SIUnit siUnit)
214         {
215             return new VectorN.Col<SIQuantity>(this.dataSi.instantiateNew(siNew), siUnit);
216         }
217 
218         @Override
219         public boolean isColumnVector()
220         {
221             return true;
222         }
223 
224         @Override
225         public int size()
226         {
227             return this.dataSi.rows();
228         }
229 
230         @Override
231         public double si(final int index) throws IndexOutOfBoundsException
232         {
233             return this.dataSi.get(index, 0);
234         }
235 
236         @Override
237         public VectorN.Row<Q> transpose()
238         {
239             var newSi = this.dataSi.instantiateNew(unsafeSiArray(), cols(), rows());
240             return new VectorN.Row<Q>(newSi, getDisplayUnit().getBaseUnit()).setDisplayUnit(getDisplayUnit());
241         }
242 
243         @Override
244         public VectorN.Col<SIQuantity> invertEntries()
245         {
246             SIUnit siUnit = getDisplayUnit().siUnit().invert();
247             return new VectorN.Col<SIQuantity>(this.dataSi.instantiateNew(ArrayMath.reciprocal(unsafeSiArray())), siUnit);
248         }
249 
250         @Override
251         public VectorN.Col<SIQuantity> multiplyEntries(final VectorN.Col<?> other)
252         {
253             SIUnit siUnit = SIUnit.add(getDisplayUnit().siUnit(), other.getDisplayUnit().siUnit());
254             return new VectorN.Col<SIQuantity>(
255                     this.dataSi.instantiateNew(ArrayMath.multiply(unsafeSiArray(), other.unsafeSiArray())), siUnit);
256         }
257 
258         @Override
259         public VectorN.Col<SIQuantity> divideEntries(final VectorN.Col<?> other)
260         {
261             SIUnit siUnit = SIUnit.subtract(getDisplayUnit().siUnit(), other.getDisplayUnit().siUnit());
262             return new VectorN.Col<SIQuantity>(
263                     this.dataSi.instantiateNew(ArrayMath.divide(unsafeSiArray(), other.unsafeSiArray())), siUnit);
264         }
265 
266         @Override
267         public VectorN.Col<SIQuantity> multiplyEntries(final Quantity<?> quantity)
268         {
269             SIUnit siUnit = SIUnit.add(getDisplayUnit().siUnit(), quantity.getDisplayUnit().siUnit());
270             return new VectorN.Col<SIQuantity>(this.dataSi.instantiateNew(ArrayMath.scaleBy(unsafeSiArray(), quantity.si())),
271                     siUnit);
272         }
273 
274         // ------------------------------------------ OF METHODS ------------------------------------------
275 
276         /**
277          * Create a new column VectorN with a unit, based on a double[] array that contains data in the given unit.
278          * @param dataInUnit the data of the vector, in the given unit.
279          * @param unit the unit of the data
280          * @return a new column VectorN with a unit, based on a double[] array expressed in the given unit
281          * @param <Q> the quantity type
282          */
283         public static <Q extends Quantity<Q>> VectorN.Col<Q> of(final double[] dataInUnit, final Unit<?, Q> unit)
284         {
285             double[] dataSi = new double[dataInUnit.length];
286             for (int i = 0; i < dataInUnit.length; i++)
287             {
288                 dataSi[i] = unit.toBaseValue(dataInUnit[i]);
289             }
290             return ofSi(dataSi, unit);
291         }
292 
293         /**
294          * Create a new column VectorN with a unit, based on a double[] array that contains SI data.
295          * @param dataSi the data of the vector, in SI unit.
296          * @param displayUnit the display unit to use
297          * @return a new column VectorN with a unit, based on a double[] array that contains SI data
298          * @param <Q> the quantity type
299          */
300         public static <Q extends Quantity<Q>> VectorN.Col<Q> ofSi(final double[] dataSi, final Unit<?, Q> displayUnit)
301         {
302             return new VectorN.Col<Q>(new DenseDoubleDataSi(dataSi.clone(), dataSi.length, 1), displayUnit.getBaseUnit())
303                     .setDisplayUnit(displayUnit);
304         }
305 
306         /**
307          * Create a new column VectorN with a unit, based on a quantity array that contains data. The display unit will be taken
308          * from the first quantity in the array.
309          * @param data the data of the vector, in the given unit.
310          * @return a new column VectorN with a display unit, based on a quantity array
311          * @throws IllegalArgumentException when data array length is less than 1
312          * @param <Q> the quantity type
313          */
314         public static <Q extends Quantity<Q>> VectorN.Col<Q> of(final Q[] data)
315         {
316             Throw.when(data.length < 1, IllegalArgumentException.class, "data array length < 1");
317             double[] dataSi = new double[data.length];
318             for (int i = 0; i < data.length; i++)
319             {
320                 dataSi[i] = data[i].si();
321             }
322             return ofSi(dataSi, data[0].getDisplayUnit());
323         }
324 
325         /**
326          * Create a new column VectorN with a unit, based on a DataGridSi storage object that contains SI data.
327          * @param dataSi the data of the vector, in SI unit.
328          * @param displayUnit the display unit to use
329          * @throws IllegalArgumentException when the number of rows or columns does not have a positive value or when the vector
330          *             is initialized with more than one row
331          * @return a new column VectorN with a unit, based on a DataGridSi storage object that contains SI data
332          * @param <Q> the quantity type
333          */
334         public static <Q extends Quantity<Q>> VectorN.Col<Q> ofSi(final DataGridSi<?> dataSi, final Unit<?, Q> displayUnit)
335         {
336             return new VectorN.Col<Q>(dataSi, displayUnit.getBaseUnit()).setDisplayUnit(displayUnit);
337         }
338 
339         /**
340          * Create a new column VectorN with a unit, based on a quantity list that contains data. The display unit will be taken
341          * from the first quantity in the list.
342          * @param data the data of the vector, in the given unit.
343          * @return a new column VectorN with a display unit, based on a quantity list
344          * @throws IllegalArgumentException when data size is less than 1
345          * @param <Q> the quantity type
346          */
347         public static <Q extends Quantity<Q>> VectorN.Col<Q> of(final List<Q> data)
348         {
349             Throw.when(data.size() < 1, IllegalArgumentException.class, "data.size < 1");
350             double[] dataSi = new double[data.size()];
351             for (int i = 0; i < data.size(); i++)
352             {
353                 dataSi[i] = data.get(i).si();
354             }
355             return ofSi(dataSi, data.get(0).getDisplayUnit());
356         }
357 
358         // ------------------------------------------ AS METHODS ------------------------------------------
359 
360         /**
361          * Return the vector 'as' a vector with a known quantity, using a unit to express the result in. Throw a Runtime
362          * exception when the SI units of this vector and the target vector do not match. The dataSi object containing the
363          * vector values is NOT copied.
364          * @param targetUnit the unit to convert the vector to
365          * @return a quantity typed in the target vector class
366          * @throws IllegalArgumentException when the units do not match
367          * @param <TQ> target quantity type
368          */
369         public <TQ extends Quantity<TQ>> VectorN.Col<TQ> as(final Unit<?, TQ> targetUnit) throws IllegalArgumentException
370         {
371             Throw.when(!getDisplayUnit().siUnit().equals(targetUnit.siUnit()), IllegalArgumentException.class,
372                     "Quantity.as(%s) called, but units do not match: %s <> %s", targetUnit,
373                     getDisplayUnit().siUnit().getDisplayAbbreviation(), targetUnit.siUnit().getDisplayAbbreviation());
374             return new VectorN.Col<TQ>(this.dataSi, targetUnit);
375         }
376 
377     }
378 
379     /**
380      * VectorN.Row implements a row vector with real-valued entries. The vector is immutable, except for the display unit, which
381      * can be changed.
382      * <p>
383      * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
384      * See for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
385      * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
386      * @author Alexander Verbraeck
387      * @param <Q> the quantity type
388      */
389     public static class Row<Q extends Quantity<Q>> extends
390             VectorN<Q, Row<Q>, VectorN.Row<SIQuantity>, VectorN.Row<?>, VectorN.Col<Q>> implements Vector.Row<VectorN.Row<Q>, Q>
391     {
392         /** */
393         private static final long serialVersionUID = 600L;
394 
395         /**
396          * Create a new row VectorN with a unit, based on a DataGridSi storage object that contains SI data.
397          * @param dataSi the data of the vector, in SI unit.
398          * @param displayUnit the display unit to use
399          * @throws IllegalArgumentException when the number of rows or columns does not have a positive value or when the vector
400          *             is initialized with more than one row
401          */
402         public Row(final DataGridSi<?> dataSi, final Unit<?, Q> displayUnit)
403         {
404             super(dataSi, displayUnit);
405             Throw.when(dataSi.rows() != 1, IllegalArgumentException.class, "Row vector initialized with more than one row");
406         }
407 
408         @Override
409         public boolean isColumnVector()
410         {
411             return false;
412         }
413 
414         @Override
415         public VectorN.Row<Q> instantiateSi(final double[] data)
416         {
417             return new VectorN.Row<>(this.dataSi.instantiateNew(data), getDisplayUnit());
418         }
419 
420         @Override
421         public VectorN.Row<SIQuantity> instantiateSi(final double[] siNew, final SIUnit siUnit)
422         {
423             return new VectorN.Row<SIQuantity>(this.dataSi.instantiateNew(siNew), siUnit);
424         }
425 
426         @Override
427         public int size()
428         {
429             return this.dataSi.cols();
430         }
431 
432         @Override
433         public double si(final int index) throws IndexOutOfBoundsException
434         {
435             return this.dataSi.get(0, index);
436         }
437 
438         @Override
439         public VectorN.Col<Q> transpose()
440         {
441             var newSi = this.dataSi.instantiateNew(unsafeSiArray(), cols(), rows());
442             return new VectorN.Col<Q>(newSi, getDisplayUnit());
443         }
444 
445         @Override
446         public VectorN.Row<SIQuantity> invertEntries()
447         {
448             SIUnit siUnit = getDisplayUnit().siUnit().invert();
449             return new VectorN.Row<SIQuantity>(this.dataSi.instantiateNew(ArrayMath.reciprocal(unsafeSiArray())), siUnit);
450         }
451 
452         @Override
453         public VectorN.Row<SIQuantity> multiplyEntries(final VectorN.Row<?> other)
454         {
455             SIUnit siUnit = SIUnit.add(getDisplayUnit().siUnit(), other.getDisplayUnit().siUnit());
456             return new VectorN.Row<SIQuantity>(
457                     this.dataSi.instantiateNew(ArrayMath.multiply(unsafeSiArray(), other.unsafeSiArray())), siUnit);
458         }
459 
460         @Override
461         public VectorN.Row<SIQuantity> divideEntries(final VectorN.Row<?> other)
462         {
463             SIUnit siUnit = SIUnit.subtract(getDisplayUnit().siUnit(), other.getDisplayUnit().siUnit());
464             return new VectorN.Row<SIQuantity>(
465                     this.dataSi.instantiateNew(ArrayMath.divide(unsafeSiArray(), other.unsafeSiArray())), siUnit);
466         }
467 
468         @Override
469         public VectorN.Row<SIQuantity> multiplyEntries(final Quantity<?> quantity)
470         {
471             SIUnit siUnit = SIUnit.add(getDisplayUnit().siUnit(), quantity.getDisplayUnit().siUnit());
472             return new VectorN.Row<SIQuantity>(this.dataSi.instantiateNew(ArrayMath.scaleBy(unsafeSiArray(), quantity.si())),
473                     siUnit);
474         }
475 
476         // ------------------------------------------ OF METHODS ------------------------------------------
477 
478         /**
479          * Create a new row VectorN with a unit, based on a double[] array that contains data in the given unit.
480          * @param dataInUnit the data of the vector, in the given unit.
481          * @param unit the unit of the data
482          * @return a new row VectorN with a unit, based on a double[] array expressed in the given unit
483          * @param <Q> the quantity type
484          */
485         public static <Q extends Quantity<Q>> VectorN.Row<Q> of(final double[] dataInUnit, final Unit<?, Q> unit)
486         {
487             double[] dataSi = new double[dataInUnit.length];
488             for (int i = 0; i < dataInUnit.length; i++)
489             {
490                 dataSi[i] = unit.toBaseValue(dataInUnit[i]);
491             }
492             return ofSi(dataSi, unit);
493         }
494 
495         /**
496          * Create a new row VectorN with a unit, based on a double[] array that contains SI data.
497          * @param dataSi the data of the vector, in SI unit.
498          * @param displayUnit the display unit to use
499          * @return a new row VectorN with a unit, based on a double[] array that contains SI data
500          * @param <Q> the quantity type
501          */
502         public static <Q extends Quantity<Q>> VectorN.Row<Q> ofSi(final double[] dataSi, final Unit<?, Q> displayUnit)
503         {
504             return new VectorN.Row<Q>(new DenseDoubleDataSi(dataSi.clone(), 1, dataSi.length), displayUnit);
505         }
506 
507         /**
508          * Create a new row VectorN with a unit, based on a quantity array that contains data. The display unit will be taken
509          * from the first quantity in the array.
510          * @param data the data of the vector, in the given unit.
511          * @return a new row VectorN with a display unit, based on a quantity array
512          * @throws IllegalArgumentException when data array length is less than 1
513          * @param <Q> the quantity type
514          */
515         public static <Q extends Quantity<Q>> VectorN.Row<Q> of(final Q[] data)
516         {
517             Throw.when(data.length < 1, IllegalArgumentException.class, "data array length < 1");
518             double[] dataSi = new double[data.length];
519             for (int i = 0; i < data.length; i++)
520             {
521                 dataSi[i] = data[i].si();
522             }
523             return ofSi(dataSi, data[0].getDisplayUnit());
524         }
525 
526         /**
527          * Create a new row VectorN with a unit, based on a DataGridSi storage object that contains SI data.
528          * @param dataSi the data of the vector, in SI unit.
529          * @param displayUnit the display unit to use
530          * @throws IllegalArgumentException when the number of rows or rows does not have a positive value or when the vector is
531          *             initialized with more than one row
532          * @return a new row VectorN with a unit, based on a DataGridSi storage object that contains SI data
533          * @param <Q> the quantity type
534          */
535         public static <Q extends Quantity<Q>> VectorN.Row<Q> ofSi(final DataGridSi<?> dataSi, final Unit<?, Q> displayUnit)
536         {
537             return new VectorN.Row<Q>(dataSi, displayUnit);
538         }
539 
540         /**
541          * Create a new row VectorN with a unit, based on a quantity list that contains data. The display unit will be taken
542          * from the first quantity in the list.
543          * @param data the data of the vector, in the given unit.
544          * @return a new row VectorN with a display unit, based on a quantity list
545          * @throws IllegalArgumentException when data size is less than 1
546          * @param <Q> the quantity type
547          */
548         public static <Q extends Quantity<Q>> VectorN.Row<Q> of(final List<Q> data)
549         {
550             Throw.when(data.size() < 1, IllegalArgumentException.class, "data.size < 1");
551             double[] dataSi = new double[data.size()];
552             for (int i = 0; i < data.size(); i++)
553             {
554                 dataSi[i] = data.get(i).si();
555             }
556             return ofSi(dataSi, data.get(0).getDisplayUnit());
557         }
558 
559         // ------------------------------------------ AS METHODS ------------------------------------------
560 
561         /**
562          * Return the vector 'as' a vector with a known quantity, using a unit to express the result in. Throw a Runtime
563          * exception when the SI units of this vector and the target vector do not match. The dataSi object containing the
564          * vector values is NOT copied.
565          * @param targetUnit the unit to convert the vector to
566          * @return a quantity typed in the target vector class
567          * @throws IllegalArgumentException when the units do not match
568          * @param <TQ> target quantity type
569          */
570         public <TQ extends Quantity<TQ>> VectorN.Row<TQ> as(final Unit<?, TQ> targetUnit) throws IllegalArgumentException
571         {
572             Throw.when(!getDisplayUnit().siUnit().equals(targetUnit.siUnit()), IllegalArgumentException.class,
573                     "Quantity.as(%s) called, but units do not match: %s <> %s", targetUnit,
574                     getDisplayUnit().siUnit().getDisplayAbbreviation(), targetUnit.siUnit().getDisplayAbbreviation());
575             return new VectorN.Row<TQ>(this.dataSi, targetUnit);
576         }
577 
578     }
579 
580 }