1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2003-2021 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // or <https://octave.org/copyright/>.
7 //
8 // Copyirght (C) 2009, 2010 VZLU Prague
9 //
10 // This file is part of Octave.
11 //
12 // Octave is free software: you can redistribute it and/or modify it
13 // under the terms of the GNU General Public License as published by
14 // the Free Software Foundation, either version 3 of the License, or
15 // (at your option) any later version.
16 //
17 // Octave is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with Octave; see the file COPYING.  If not, see
24 // <https://www.gnu.org/licenses/>.
25 //
26 ////////////////////////////////////////////////////////////////////////
27 
28 #if ! defined (octave_dim_vector_h)
29 #define octave_dim_vector_h 1
30 
31 #include "octave-config.h"
32 
33 #include <cassert>
34 
35 #include <algorithm>
36 #include <initializer_list>
37 #include <string>
38 
39 #include "oct-atomic.h"
40 #include "oct-refcount.h"
41 
42 template <typename T> class Array;
43 
44 //! Vector representing the dimensions (size) of an Array.
45 //!
46 //! A dim_vector is used to represent dimensions of an Array.  It is used
47 //! on its constructor to specify its size, or when reshaping it.
48 //!
49 //! @code{.cc}
50 //! // Matrix with 10 rows and 20 columns.
51 //! Matrix m Matrix (dim_vector (10, 20));
52 //!
53 //! // Change its size to 5 rows and 40 columns.
54 //! Matrix m2 = m.reshape (dim_vector (5, 40));
55 //!
56 //! // Five dimensional Array of length 10, 20, 3, 8, 7 on each dimension.
57 //! NDArray a (dim_vector (10, 20, 3, 8, 7));
58 //!
59 //! // Uninitialized array of same size as other.
60 //! NDArray b (a.dims ());
61 //! @endcode
62 //!
63 //! The main thing to understand about this class, is that methods such as
64 //! ndims() and numel(), return the value for an Array of these dimensions,
65 //! not the actual number of elements in the dim_vector.
66 //!
67 //! @code{.cc}
68 //! dim_vector d (10, 5, 3);
69 //! octave_idx_type n = d.numel (); // returns 150
70 //! octave_idx_type nd = d.ndims (); // returns 3
71 //! @endcode
72 //!
73 //! ## Implementation details ##
74 //!
75 //! This implementation is more tricky than Array, but the big plus is that
76 //! dim_vector requires only one allocation instead of two.  It is (slightly)
77 //! patterned after GCC's basic_string implementation.  rep is a pointer to an
78 //! array of memory, comprising count, length, and the data:
79 //!
80 //! @verbatim
81 //!        <count>
82 //!        <ndims>
83 //! rep --> <dims[0]>
84 //!        <dims[1]>
85 //!        ...
86 //! @endverbatim
87 //!
88 //! The inlines count(), ndims() recover this data from the rep.  Note
89 //! that rep points to the beginning of dims to grant faster access
90 //! (reinterpret_cast is assumed to be an inexpensive operation).
91 
92 class
93 OCTAVE_API
94 dim_vector
95 {
96 private:
97 
98   octave_idx_type *rep;
99 
count(void)100   octave_idx_type& count (void) const { return rep[-2]; }
101 
increment_count(void)102   octave_idx_type increment_count (void)
103   {
104     return octave_atomic_increment (&(count ()));
105   }
106 
decrement_count(void)107   octave_idx_type decrement_count (void)
108   {
109     return octave_atomic_decrement (&(count ()));
110   }
111 
112   //! Construct a new rep with count = 1 and ndims given.
113 
newrep(int ndims)114   static octave_idx_type * newrep (int ndims)
115   {
116     octave_idx_type *r = new octave_idx_type [ndims + 2];
117 
118     *r++ = 1;
119     *r++ = ndims;
120 
121     return r;
122   }
123 
124   //! Clone this->rep.
125 
clonerep(void)126   octave_idx_type * clonerep (void)
127   {
128     int nd = ndims ();
129 
130     octave_idx_type *r = newrep (nd);
131 
132     std::copy_n (rep, nd, r);
133 
134     return r;
135   }
136 
137   //! Clone and resize this->rep to length n, filling by given value.
138 
resizerep(int n,octave_idx_type fill_value)139   octave_idx_type * resizerep (int n, octave_idx_type fill_value)
140   {
141     int nd = ndims ();
142 
143     if (n < 2)
144       n = 2;
145 
146     octave_idx_type *r = newrep (n);
147 
148     if (nd > n)
149       nd = n;
150 
151     std::copy_n (rep, nd, r);
152     std::fill_n (r + nd, n - nd, fill_value);
153 
154     return r;
155   }
156 
157   //! Free the rep.
158 
freerep(void)159   void freerep (void)
160   {
161     assert (count () == 0);
162     delete [] (rep - 2);
163   }
164 
make_unique(void)165   void make_unique (void)
166   {
167     if (count () > 1)
168       {
169         octave_idx_type *new_rep = clonerep ();
170 
171         if (decrement_count () == 0)
172           freerep ();
173 
174         rep = new_rep;
175       }
176   }
177 
178 public:
179 
180   //! Construct dim_vector for a N dimensional array.
181   //!
182   //! Each argument to constructor defines the length of an additional
183   //! dimension.  A dim_vector always represents a minimum of 2 dimensions
184   //! (just like an Array has at least 2 dimensions) and there is no
185   //! upper limit on the number of dimensions.
186   //!
187   //! @code{.cc}
188   //! dim_vector dv (7, 5);
189   //! Matrix mat (dv);
190   //! @endcode
191   //!
192   //! The constructed dim_vector @c dv will have two elements, @f$[7, 5]@f$,
193   //! one for each dimension.  It can then be used to construct a Matrix
194   //! with such dimensions, i.e., 7 rows and 5 columns.
195   //!
196   //! @code{.cc}
197   //! NDArray x (dim_vector (7, 5, 10));
198   //! @endcode
199   //!
200   //! This will construct a 3 dimensional NDArray of lengths 7, 5, and 10,
201   //! on the first, second, and third dimension (rows, columns, and pages)
202   //! respectively.
203   //!
204   //! Note that that there is no constructor that accepts only one
205   //! dimension length to avoid confusion.  The source for such confusion
206   //! is that constructor could mean:
207   //!   - a column vector, i.e., assume @f$[N, 1]@f$;
208   //!   - a square matrix, i.e., as is common in Octave interpreter;
209   //!   - support for a 1 dimensional Array (does not exist);
210   //!
211   //! Using r, c, and lengths... as arguments, allow us to check at compile
212   //! time that there's at least 2 dimensions specified, while maintaining
213   //! type safety.
214 
215   template <typename... Ints>
dim_vector(const octave_idx_type r,const octave_idx_type c,Ints...lengths)216   dim_vector (const octave_idx_type r, const octave_idx_type c,
217               Ints... lengths) : rep (newrep (2 + sizeof... (Ints)))
218   {
219     std::initializer_list<octave_idx_type> all_lengths = {r, c, lengths...};
220     for (const octave_idx_type l: all_lengths)
221       *rep++ = l;
222     rep -= all_lengths.size ();
223   }
224 
225   // Fast access with absolutely no checking
226 
xelem(int i)227   octave_idx_type& xelem (int i) { return rep[i]; }
228 
xelem(int i)229   octave_idx_type xelem (int i) const { return rep[i]; }
230 
231   // Safe access to to elements
232 
elem(int i)233   octave_idx_type& elem (int i)
234   {
235     make_unique ();
236     return xelem (i);
237   }
238 
elem(int i)239   octave_idx_type elem (int i) const { return xelem (i); }
240 
chop_trailing_singletons(void)241   void chop_trailing_singletons (void)
242   {
243     int nd = ndims ();
244     if (nd > 2 && rep[nd-1] == 1)
245       {
246         make_unique ();
247         do
248           nd--;
249         while (nd > 2 && rep[nd-1] == 1);
250         rep[-1] = nd;
251       }
252   }
253 
254   void chop_all_singletons (void);
255 
256   // WARNING: Only call by jit
to_jit(void)257   octave_idx_type * to_jit (void) const
258   {
259     return rep;
260   }
261 
262 private:
263 
264   static octave_idx_type *nil_rep (void);
265 
266 public:
267 
268   static octave_idx_type dim_max (void);
269 
dim_vector(void)270   explicit dim_vector (void) : rep (nil_rep ())
271   { increment_count (); }
272 
dim_vector(const dim_vector & dv)273   dim_vector (const dim_vector& dv) : rep (dv.rep)
274   { increment_count (); }
275 
dim_vector(dim_vector && dv)276   dim_vector (dim_vector&& dv) : rep (dv.rep) { dv.rep = nullptr; }
277 
278 // FIXME: Should be private, but required by array constructor for jit
dim_vector(octave_idx_type * r)279   explicit dim_vector (octave_idx_type *r) : rep (r) { }
280 
alloc(int n)281   static dim_vector alloc (int n)
282   {
283     return dim_vector (newrep (n < 2 ? 2 : n));
284   }
285 
286   dim_vector& operator = (const dim_vector& dv)
287   {
288     if (&dv != this)
289       {
290         if (decrement_count () == 0)
291           freerep ();
292 
293         rep = dv.rep;
294         increment_count ();
295       }
296 
297     return *this;
298   }
299 
300   dim_vector& operator = (dim_vector&& dv)
301   {
302     if (&dv != this)
303       {
304         // Because we define a move constructor and a move assignment
305         // operator, rep may be a nullptr here.  We should only need to
306         // protect the destructor in a similar way.
307 
308         if (rep && decrement_count () == 0)
309           freerep ();
310 
311         rep = dv.rep;
312         dv.rep = nullptr;
313       }
314 
315     return *this;
316   }
317 
~dim_vector(void)318   ~dim_vector (void)
319   {
320     // Because we define a move constructor and a move assignment
321     // operator, rep may be a nullptr here.  We should only need to
322     // protect the move assignment operator in a similar way.
323 
324     if (rep && decrement_count () == 0)
325       freerep ();
326   }
327 
328   //! Number of dimensions.
329   //!
330   //! Returns the number of dimensions of the dim_vector.  This is number of
331   //! elements in the dim_vector including trailing singletons.  It is also
332   //! the number of dimensions an Array with this dim_vector would have.
333 
ndims(void)334   octave_idx_type ndims (void) const { return rep[-1]; }
335 
336   //! Number of dimensions.
337   //! Synonymous with ndims().
338   //!
339   //! While this method is not officially deprecated, consider using ndims()
340   //! instead to avoid confusion.  Array does not have length because of its
341   //! odd definition as length of the longest dimension.
342 
length(void)343   int length (void) const { return ndims (); }
344 
operator()345   octave_idx_type& operator () (int i) { return elem (i); }
346 
operator()347   octave_idx_type operator () (int i) const { return elem (i); }
348 
349   void resize (int n, int fill_value = 0)
350   {
351     int len = ndims ();
352 
353     if (n != len)
354       {
355         octave_idx_type *r = resizerep (n, fill_value);
356 
357         if (decrement_count () == 0)
358           freerep ();
359 
360         rep = r;
361       }
362   }
363 
364   std::string str (char sep = 'x') const;
365 
all_zero(void)366   bool all_zero (void) const
367   {
368     return std::all_of (rep, rep + ndims (),
369                         [] (octave_idx_type dim) { return dim == 0; });
370   }
371 
empty_2d(void)372   bool empty_2d (void) const
373   {
374     return ndims () == 2 && (xelem (0) == 0 || xelem (1) == 0);
375   }
376 
zero_by_zero(void)377   bool zero_by_zero (void) const
378   {
379     return ndims () == 2 && xelem (0) == 0 && xelem (1) == 0;
380   }
381 
any_zero(void)382   bool any_zero (void) const
383   {
384     return std::any_of (rep, rep + ndims (),
385                         [] (octave_idx_type dim) { return dim == 0; });
386   }
387 
388   int num_ones (void) const;
389 
all_ones(void)390   bool all_ones (void) const
391   {
392     return (num_ones () == ndims ());
393   }
394 
395   //! Number of elements that a matrix with this dimensions would have.
396   //!
397   //! Return the number of elements that a matrix with this dimension
398   //! vector would have, NOT the number of dimensions (elements in the
399   //! dimension vector).
400 
401   octave_idx_type numel (int n = 0) const
402   {
403     int n_dims = ndims ();
404 
405     octave_idx_type retval = 1;
406 
407     for (int i = n; i < n_dims; i++)
408       retval *= elem (i);
409 
410     return retval;
411   }
412 
413   //! The following function will throw a std::bad_alloc ()
414   //! exception if the requested size is larger than can be indexed by
415   //! octave_idx_type.  This may be smaller than the actual amount of
416   //! memory that can be safely allocated on a system.  However, if we
417   //! don't fail here, we can end up with a mysterious crash inside a
418   //! function that is iterating over an array using octave_idx_type
419   //! indices.
420 
421   octave_idx_type safe_numel (void) const;
422 
any_neg(void)423   bool any_neg (void) const
424   {
425     return std::any_of (rep, rep + ndims (),
426                         [] (octave_idx_type dim) { return dim < 0; });
427   }
428 
429   dim_vector squeeze (void) const;
430 
431   //! This corresponds to cat().
432   bool concat (const dim_vector& dvb, int dim);
433 
434   //! This corresponds to [,] (horzcat, dim = 0) and [;] (vertcat, dim = 1).
435   // The rules are more relaxed here.
436   bool hvcat (const dim_vector& dvb, int dim);
437 
438   //! Force certain dimensionality, preserving numel ().  Missing
439   //! dimensions are set to 1, redundant are folded into the trailing
440   //! one.  If n = 1, the result is 2d and the second dim is 1
441   //! (dim_vectors are always at least 2D).
442 
443   dim_vector redim (int n) const;
444 
as_column(void)445   dim_vector as_column (void) const
446   {
447     if (ndims () == 2 && xelem (1) == 1)
448       return *this;
449     else
450       return dim_vector (numel (), 1);
451   }
452 
as_row(void)453   dim_vector as_row (void) const
454   {
455     if (ndims () == 2 && xelem (0) == 1)
456       return *this;
457     else
458       return dim_vector (1, numel ());
459   }
460 
isvector(void)461   bool isvector (void) const
462   {
463     return (ndims () == 2 && (xelem (0) == 1 || xelem (1) == 1));
464   }
465 
is_nd_vector(void)466   bool is_nd_vector (void) const
467   {
468     int num_non_one = 0;
469 
470     for (int i = 0; i < ndims (); i++)
471       {
472         if (xelem (i) != 1)
473           {
474             num_non_one++;
475 
476             if (num_non_one > 1)
477               break;
478           }
479       }
480 
481     return num_non_one == 1;
482   }
483 
484   // Create a vector with length N.  If this object is a vector,
485   // preserve the orientation, otherwise, create a column vector.
486 
make_nd_vector(octave_idx_type n)487   dim_vector make_nd_vector (octave_idx_type n) const
488   {
489     dim_vector orig_dims;
490 
491     if (is_nd_vector ())
492       {
493         orig_dims = *this;
494 
495         for (int i = 0; i < orig_dims.ndims (); i++)
496           {
497             if (orig_dims(i) != 1)
498               {
499                 orig_dims(i) = n;
500                 break;
501               }
502           }
503       }
504     else
505       orig_dims = dim_vector (n, 1);
506 
507     return orig_dims;
508   }
509 
510   int first_non_singleton (int def = 0) const
511   {
512     for (int i = 0; i < ndims (); i++)
513       {
514         if (xelem (i) != 1)
515           return i;
516       }
517 
518     return def;
519   }
520 
521   //! Linear index from an index tuple.
compute_index(const octave_idx_type * idx)522   octave_idx_type compute_index (const octave_idx_type *idx) const
523   { return compute_index (idx, ndims ()); }
524 
525   //! Linear index from an incomplete index tuple (nidx < length ()).
compute_index(const octave_idx_type * idx,int nidx)526   octave_idx_type compute_index (const octave_idx_type *idx, int nidx) const
527   {
528     octave_idx_type k = 0;
529     for (int i = nidx - 1; i >= 0; i--)
530       k = rep[i] * k + idx[i];
531 
532     return k;
533   }
534 
535   //! Increment a multi-dimensional index tuple, optionally starting
536   //! from an offset position and return the index of the last index
537   //! position that was changed, or length () if just cycled over.
538 
539   int increment_index (octave_idx_type *idx, int start = 0) const
540   {
541     int i;
542     for (i = start; i < ndims (); i++)
543       {
544         if (++(*idx) == rep[i])
545           *idx++ = 0;
546         else
547           break;
548       }
549     return i;
550   }
551 
552   //! Return cumulative dimensions.
553 
cumulative(void)554   dim_vector cumulative (void) const
555   {
556     int nd = ndims ();
557     dim_vector retval = alloc (nd);
558 
559     octave_idx_type k = 1;
560     for (int i = 0; i < nd; i++)
561       retval.rep[i] = (k *= rep[i]);
562 
563     return retval;
564   }
565 
566   //! Compute a linear index from an index tuple.  Dimensions are
567   //! required to be cumulative.
568 
cum_compute_index(const octave_idx_type * idx)569   octave_idx_type cum_compute_index (const octave_idx_type *idx) const
570   {
571     octave_idx_type k = idx[0];
572 
573     for (int i = 1; i < ndims (); i++)
574       k += rep[i-1] * idx[i];
575 
576     return k;
577   }
578 
579   friend bool operator == (const dim_vector& a, const dim_vector& b);
580 
581   Array<octave_idx_type> as_array (void) const;
582 };
583 
584 inline bool
585 operator == (const dim_vector& a, const dim_vector& b)
586 {
587   // Fast case.
588   if (a.rep == b.rep)
589     return true;
590 
591   int a_len = a.ndims ();
592   int b_len = b.ndims ();
593 
594   if (a_len != b_len)
595     return false;
596 
597   return std::equal (a.rep, a.rep + a_len, b.rep);
598 }
599 
600 inline bool
601 operator != (const dim_vector& a, const dim_vector& b)
602 {
603   return ! operator == (a, b);
604 }
605 
606 #endif
607