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