1 #ifndef NETGEN_CORE_TABLE_HPP 2 #define NETGEN_CORE_TABLE_HPP 3 4 /**************************************************************************/ 5 /* File: table.hpp */ 6 /* Author: Joachim Schoeberl */ 7 /* Date: 25. Mar. 2000 */ 8 /**************************************************************************/ 9 10 #include <atomic> 11 #include <iostream> 12 #include <optional> 13 14 #include "array.hpp" 15 #include "bitarray.hpp" 16 #include "memtracer.hpp" 17 #include "ngcore_api.hpp" 18 #include "profiler.hpp" 19 #include "taskmanager.hpp" 20 21 namespace ngcore 22 { 23 24 25 template <class T, class IndexType = size_t> 26 class FlatTable 27 { 28 protected: 29 static constexpr IndexType BASE = IndexBASE<IndexType>(); 30 /// number of rows 31 size_t size; 32 /// pointer to first in row 33 size_t * index; 34 /// array of data 35 T * data; 36 37 public: 38 FlatTable() = delete; 39 FlatTable(size_t as,size_t * aindex,T * adata)40 NETGEN_INLINE FlatTable(size_t as, size_t * aindex, T * adata) 41 : size(as), index(aindex), data(adata) { ; } 42 43 /// Size of table Size() const44 NETGEN_INLINE size_t Size() const { return size; } 45 46 /// Access entry operator [](IndexType i) const47 NETGEN_INLINE const FlatArray<T> operator[] (IndexType i) const 48 { 49 i = i-BASE; 50 return FlatArray<T> (index[i+1]-index[i], data+index[i]); 51 } 52 Data() const53 NETGEN_INLINE T * Data() const { return data; } 54 AsArray() const55 NETGEN_INLINE FlatArray<T> AsArray() const 56 { 57 return FlatArray<T> (index[size]-index[0], data+index[0]); 58 } 59 IndexArray() const60 NETGEN_INLINE FlatArray<size_t> IndexArray() const 61 { 62 return FlatArray<size_t, IndexType> (size+1, index); 63 } 64 65 /// takes range starting from position start of end-start elements Range(size_t start,size_t end) const66 NETGEN_INLINE FlatTable<T> Range (size_t start, size_t end) const 67 { 68 return FlatTable<T> (end-start, index+start-BASE, data); 69 } 70 71 /// takes range starting from position start of end-start elements Range(T_Range<size_t> range) const72 NETGEN_INLINE FlatTable<T> Range (T_Range<size_t> range) const 73 { 74 return FlatTable<T> (range.Size(), index+range.First()-BASE, data); 75 } 76 Range() const77 NETGEN_INLINE T_Range<IndexType> Range () const 78 { 79 return T_Range<IndexType> (BASE, size+BASE); 80 } 81 82 class Iterator 83 { 84 const FlatTable & tab; 85 size_t row; 86 public: Iterator(const FlatTable & _tab,size_t _row)87 Iterator (const FlatTable & _tab, size_t _row) : tab(_tab), row(_row) { ; } operator ++()88 Iterator & operator++ () { ++row; return *this; } operator *() const89 FlatArray<T> operator* () const { return tab[row]; } operator !=(const Iterator & it2)90 bool operator!= (const Iterator & it2) { return row != it2.row; } 91 }; 92 begin() const93 Iterator begin() const { return Iterator(*this, BASE); } end() const94 Iterator end() const { return Iterator(*this, BASE+size); } 95 }; 96 97 NGCORE_API extern size_t * TablePrefixSum32 (FlatArray<unsigned int> entrysize); 98 NGCORE_API extern size_t * TablePrefixSum64 (FlatArray<size_t> entrysize); 99 100 TablePrefixSum(FlatArray<unsigned int> entrysize)101 NETGEN_INLINE size_t * TablePrefixSum (FlatArray<unsigned int> entrysize) 102 { return TablePrefixSum32 (entrysize); } TablePrefixSum(FlatArray<int> entrysize)103 NETGEN_INLINE size_t * TablePrefixSum (FlatArray<int> entrysize) 104 { return TablePrefixSum32 (FlatArray<unsigned> (entrysize.Size(), (unsigned int*)(int*)(entrysize.Addr(0)))); } TablePrefixSum(FlatArray<std::atomic<int>> entrysize)105 NETGEN_INLINE size_t * TablePrefixSum (FlatArray<std::atomic<int>> entrysize) 106 { return TablePrefixSum32 (FlatArray<unsigned> (entrysize.Size(), (unsigned int*)(std::atomic<int>*)entrysize.Addr(0))); } TablePrefixSum(FlatArray<size_t> entrysize)107 NETGEN_INLINE size_t * TablePrefixSum (FlatArray<size_t> entrysize) 108 { return TablePrefixSum64 (entrysize); } 109 110 111 /** 112 A compact Table container. 113 A table contains size entries of variable size. 114 The entry sizes must be known at construction. 115 */ 116 template <class T, class IndexType = size_t> 117 class Table : public FlatTable<T, IndexType> 118 { 119 protected: 120 121 using FlatTable<T,IndexType>::size; 122 using FlatTable<T,IndexType>::index; 123 using FlatTable<T,IndexType>::data; 124 125 public: 126 /// Table()127 NETGEN_INLINE Table () : FlatTable<T,IndexType> (0,nullptr,nullptr) { ; } 128 /// Construct table of uniform entrysize Table(size_t asize,size_t entrysize)129 NETGEN_INLINE Table (size_t asize, size_t entrysize) 130 : FlatTable<T,IndexType>( asize, new size_t[asize+1], new T[asize*entrysize] ) 131 { 132 for (size_t i : IntRange(size+1)) 133 index[i] = i*entrysize; 134 } 135 136 /// Construct table of variable entrysize 137 template <typename TI> Table(FlatArray<TI,IndexType> entrysize)138 NETGEN_INLINE Table (FlatArray<TI,IndexType> entrysize) 139 : FlatTable<T,IndexType> (0, nullptr, nullptr) 140 { 141 size = entrysize.Size(); 142 index = TablePrefixSum (FlatArray<TI> (entrysize.Size(), entrysize.Data())); 143 size_t cnt = index[size]; 144 data = new T[cnt]; 145 } 146 Table(const Table & tab2)147 explicit NETGEN_INLINE Table (const Table & tab2) 148 : FlatTable<T,IndexType>(0, nullptr, nullptr) 149 { 150 size = tab2.Size(); 151 152 index = new size_t[size+1]; 153 for (size_t i = 0; i <= size; i++) 154 index[i] = tab2.index[i]; 155 156 size_t cnt = index[size]; 157 data = new T[cnt]; 158 for (size_t i = 0; i < cnt; i++) 159 data[i] = tab2.data[i]; 160 } 161 Table(Table && tab2)162 NETGEN_INLINE Table (Table && tab2) 163 : FlatTable<T,IndexType>(0, nullptr, nullptr) 164 { 165 tab2.mt.Free(tab2.GetMemUsage()); 166 Swap (size, tab2.size); 167 Swap (index, tab2.index); 168 Swap (data, tab2.data); 169 } 170 DoArchive(Archive & ar)171 void DoArchive(Archive& ar) 172 { 173 ar & size; 174 if(size == 0) 175 return; 176 if(ar.Input()) 177 { 178 index = new IndexType[size+1]; 179 mt.Alloc(sizeof(IndexType) * (size+1)); 180 } 181 ar.Do(index, size+1); 182 if(ar.Input()) 183 { 184 data = new T[index[size]]; 185 mt.Alloc(sizeof(T) * index[size]); 186 } 187 ar.Do(data, index[size]); 188 } 189 operator =(Table && tab2)190 NETGEN_INLINE Table & operator= (Table && tab2) 191 { 192 mt.Swap(GetMemUsage(), tab2.mt, tab2.GetMemUsage()); 193 Swap (size, tab2.size); 194 Swap (index, tab2.index); 195 Swap (data, tab2.data); 196 return *this; 197 } 198 199 200 201 /// Delete data ~Table()202 NETGEN_INLINE ~Table () 203 { 204 mt.Free(GetMemUsage()); 205 delete [] data; 206 delete [] index; 207 } 208 209 /// Size of table 210 using FlatTable<T,IndexType>::Size; 211 212 /// number of elements in all rows NElements() const213 NETGEN_INLINE size_t NElements() const { return index[size]; } 214 215 using FlatTable<T,IndexType>::operator[]; 216 StartMemoryTracing(int)217 NETGEN_INLINE void StartMemoryTracing (int /* mem_id */) 218 { 219 mt.Alloc(GetMemUsage()); 220 } GetMemoryTracer() const221 const MemoryTracer& GetMemoryTracer() const { return mt; } 222 223 private: GetMemUsage() const224 size_t GetMemUsage() const { return size == 0 ? 0 : sizeof(T)*index[size] + sizeof(IndexType) * size+1; } 225 MemoryTracer mt; 226 }; 227 228 229 /// Print table 230 template <class T, typename IndexType> operator <<(ostream & s,const Table<T,IndexType> & table)231 inline ostream & operator<< (ostream & s, const Table<T,IndexType> & table) 232 { 233 for (auto i : table.Range()) 234 { 235 s << i << ":"; 236 for (auto el : table[i]) 237 s << " " << el; 238 s << "\n"; 239 } 240 s << std::flush; 241 return s; 242 } 243 244 245 246 247 template <class T, typename IndexType=size_t> 248 class TableCreator 249 { 250 protected: 251 int mode; // 1 .. cnt, 2 .. cnt entries, 3 .. fill table 252 std::atomic<size_t> nd; 253 Array<std::atomic<int>,IndexType> cnt; 254 Table<T,IndexType> table; 255 public: TableCreator()256 TableCreator() 257 { nd = 0; mode = 1; } TableCreator(size_t acnt)258 TableCreator (size_t acnt) 259 { nd = acnt; SetMode(2); } 260 MoveTable()261 Table<T,IndexType> MoveTable() 262 { 263 return std::move(table); 264 } 265 Done()266 bool Done () { return mode > 3; } operator ++(int)267 void operator++(int) { SetMode (mode+1); } 268 GetMode() const269 int GetMode () const { return mode; } SetMode(int amode)270 void SetMode (int amode) 271 { 272 mode = amode; 273 if (mode == 2) 274 { 275 // cnt.SetSize(nd); // atomic has no copy 276 cnt = Array<std::atomic<int>,IndexType> (nd); 277 for (auto & ci : cnt) ci.store (0, std::memory_order_relaxed); 278 } 279 if (mode == 3) 280 { 281 table = Table<T,IndexType> (cnt); 282 // for (auto & ci : cnt) ci = 0; 283 for (auto & ci : cnt) ci.store (0, std::memory_order_relaxed); 284 // cnt = 0; 285 } 286 } 287 SetSize(size_t _nd)288 void SetSize (size_t _nd) 289 { 290 if (mode == 1) 291 nd = _nd; 292 else 293 { 294 if (nd != _nd) 295 throw Exception ("cannot change size of table-creator"); 296 } 297 } 298 Add(IndexType blocknr,const T & data)299 void Add (IndexType blocknr, const T & data) 300 { 301 switch (mode) 302 { 303 case 1: 304 { 305 size_t oldval = nd; 306 while (blocknr+1>nd) { 307 nd.compare_exchange_weak (oldval, blocknr+1); 308 oldval = nd; 309 } 310 break; 311 } 312 case 2: 313 cnt[blocknr]++; 314 break; 315 case 3: 316 int ci = cnt[blocknr]++; 317 table[blocknr][ci] = data; 318 break; 319 } 320 } 321 322 Add(IndexType blocknr,IntRange range)323 void Add (IndexType blocknr, IntRange range) 324 { 325 switch (mode) 326 { 327 case 1: 328 { 329 size_t oldval = nd; 330 while (blocknr+1>nd) { 331 nd.compare_exchange_weak (oldval, blocknr+1); 332 oldval = nd; 333 } 334 break; 335 } 336 case 2: 337 cnt[blocknr] += range.Size(); 338 break; 339 case 3: 340 size_t ci = ( cnt[blocknr] += range.Size() ) - range.Size(); 341 for (size_t j = 0; j < range.Size(); j++) 342 table[blocknr][ci+j] = range.First()+j; 343 break; 344 } 345 } 346 Add(IndexType blocknr,const FlatArray<int> & dofs)347 void Add (IndexType blocknr, const FlatArray<int> & dofs) 348 { 349 switch (mode) 350 { 351 case 1: 352 { 353 size_t oldval = nd; 354 while (blocknr+1>nd) { 355 nd.compare_exchange_weak (oldval, blocknr+1); 356 oldval = nd; 357 } 358 break; 359 } 360 case 2: 361 cnt[blocknr] += dofs.Size(); 362 break; 363 case 3: 364 size_t ci = ( cnt[blocknr] += dofs.Size() ) - dofs.Size(); 365 for (size_t j = 0; j < dofs.Size(); j++) 366 table[blocknr][ci+j] = dofs[j]; 367 break; 368 } 369 } 370 }; 371 372 template <typename TEntry, typename TIndex, typename TRange, typename TFunc> CreateTable(const TRange & range,const TFunc & func,std::optional<size_t> cnt)373 Table<TEntry, TIndex> CreateTable( const TRange & range, const TFunc & func, std::optional< size_t > cnt ) 374 { 375 static Timer timer("CreateTable"); 376 RegionTimer rt(timer); 377 std::unique_ptr<TableCreator<TEntry, TIndex>> pcreator; 378 379 if(cnt) 380 pcreator = std::make_unique<TableCreator<TEntry, TIndex>>(*cnt); 381 else 382 pcreator = std::make_unique<TableCreator<TEntry, TIndex>>(); 383 384 auto & creator = *pcreator; 385 386 for ( ; !creator.Done(); creator++) 387 ParallelForRange 388 (range, [&] (auto myrange) 389 { 390 for (auto i : myrange) 391 func(creator, i); 392 }, TasksPerThread(4) 393 ); 394 395 return creator.MoveTable(); 396 } 397 398 template <typename TEntry, typename TIndex, typename TRange, typename TFunc> CreateSortedTable(const TRange & range,const TFunc & func,std::optional<size_t> cnt)399 Table<TEntry, TIndex> CreateSortedTable( const TRange & range, const TFunc & func, std::optional< size_t > cnt ) 400 { 401 static Timer timer("CreateSortedTable"); 402 RegionTimer rt(timer); 403 Table<TEntry, TIndex> table = CreateTable<TEntry, TIndex>(range, func, cnt); 404 ParallelForRange 405 (table.Range(), [&] (auto myrange) 406 { 407 for (auto i : myrange) 408 QuickSort(table[i]); 409 }, TasksPerThread(4) 410 ); 411 412 return table; 413 } 414 415 class NGCORE_API FilteredTableCreator : public TableCreator<int> 416 { 417 protected: 418 const BitArray* takedofs; 419 public: FilteredTableCreator(const BitArray * atakedofs)420 FilteredTableCreator(const BitArray* atakedofs) 421 : TableCreator<int>(), takedofs(atakedofs) { }; FilteredTableCreator(int acnt,const BitArray * atakedofs)422 FilteredTableCreator(int acnt, const BitArray* atakedofs) 423 : TableCreator<int>(acnt),takedofs(atakedofs) { }; 424 void Add (size_t blocknr, int data); 425 void Add (size_t blocknr, IntRange range); 426 void Add (size_t blocknr, FlatArray<int> dofs); 427 }; 428 429 430 431 /** 432 A dynamic table class. 433 434 A DynamicTable contains entries of variable size. Entry sizes can 435 be increased dynamically. 436 */ 437 template <class T, class IndexType = size_t> 438 class DynamicTable 439 { 440 protected: 441 static constexpr IndexType BASE = IndexBASE<IndexType>(); 442 443 struct linestruct 444 { 445 int size; 446 int maxsize; 447 T * col; 448 }; 449 450 Array<linestruct, IndexType> data; 451 T * oneblock = nullptr; 452 453 public: 454 /// Creates table of size size DynamicTable(int size=0)455 DynamicTable (int size = 0) 456 : data(size) 457 { 458 for (auto & d : data) 459 { 460 d.maxsize = 0; 461 d.size = 0; 462 d.col = nullptr; 463 } 464 oneblock = nullptr; 465 } 466 467 /// Creates table with a priori fixed entry sizes. DynamicTable(const Array<int,IndexType> & entrysizes)468 DynamicTable (const Array<int, IndexType> & entrysizes) 469 : data(entrysizes.Size()) 470 { 471 size_t cnt = 0; 472 // size_t n = entrysizes.Size(); 473 474 for (auto es : entrysizes) 475 cnt += es; 476 oneblock = new T[cnt]; 477 478 cnt = 0; 479 for (auto i : data.Range()) 480 { 481 data[i].maxsize = entrysizes[i]; 482 data[i].size = 0; 483 data[i].col = &oneblock[cnt]; 484 cnt += entrysizes[i]; 485 } 486 } 487 DynamicTable(DynamicTable && tab2)488 DynamicTable (DynamicTable && tab2) 489 { 490 Swap (data, tab2.data); 491 Swap (oneblock, tab2.oneblock); 492 } 493 ~DynamicTable()494 ~DynamicTable () 495 { 496 if (oneblock) 497 delete [] oneblock; 498 else 499 for (auto & d : data) 500 delete [] d.col; 501 } 502 operator =(DynamicTable && tab2)503 DynamicTable & operator= (DynamicTable && tab2) 504 { 505 Swap (data, tab2.data); 506 Swap (oneblock, tab2.oneblock); 507 return *this; 508 } 509 510 /// Changes Size of table to size, deletes data SetSize(int size)511 void SetSize (int size) 512 { 513 for (auto & d : data) 514 delete [] d.col; 515 516 data.SetSize(size); 517 for (auto & d : data) 518 { 519 d.maxsize = 0; 520 d.size = 0; 521 d.col = nullptr; 522 } 523 } 524 ChangeSize(size_t size)525 void ChangeSize (size_t size) 526 { 527 if (oneblock) 528 throw Exception ("cannot change size of oneblock dynamic table"); 529 530 size_t oldsize = data.Size(); 531 if (size == oldsize) 532 return; 533 534 if (size < oldsize) 535 for (int i = size; i < oldsize; i++) 536 delete [] data[i+BASE].col; 537 538 data.SetSize(size); 539 540 for (int i = oldsize; i < size; i++) 541 { 542 data[i+BASE].maxsize = 0; 543 data[i+BASE].size = 0; 544 data[i+BASE].col = nullptr; 545 } 546 } 547 548 549 550 /// IncSize(IndexType i)551 void IncSize (IndexType i) 552 { 553 NETGEN_CHECK_RANGE(i,BASE,data.Size()+BASE); 554 555 linestruct & line = data[i]; 556 557 if (line.size == line.maxsize) 558 { 559 T * p; 560 if constexpr (std::is_default_constructible<T>::value) 561 p = new T[(2*line.maxsize+5)]; 562 else 563 p = reinterpret_cast<T*>(new char[(2*line.maxsize+5)*sizeof(T)]); 564 for (size_t i = 0; i < line.maxsize; i++) 565 p[i] = std::move(line.col[i]); 566 // memcpy (p, line.col, line.maxsize * sizeof(T)); 567 delete [] line.col; 568 line.col = p; 569 line.maxsize = 2*line.maxsize+5; 570 } 571 572 line.size++; 573 } 574 DecSize(IndexType i)575 void DecSize (IndexType i) 576 { 577 NETGEN_CHECK_RANGE(i,BASE,data.Size()+BASE); 578 linestruct & line = data[i]; 579 580 #ifdef NETGEN_ENABLE_CHECK_RANGE 581 if (line.size == 0) 582 throw Exception ("BaseDynamicTable::Dec: EntrySize < 0"); 583 #endif 584 585 line.size--; 586 } 587 588 589 /// Inserts element acont into row i. Does not test if already used. Add(IndexType i,const T & acont)590 void Add (IndexType i, const T & acont) 591 { 592 if (data[i].size == data[i].maxsize) 593 this->IncSize (i); 594 else 595 data[i].size++; 596 data[i].col[data[i].size-1] = acont; 597 } 598 599 /// Inserts element acont into row i, iff not yet exists. AddUnique(IndexType i,const T & cont)600 void AddUnique (IndexType i, const T & cont) 601 { 602 int es = EntrySize (i); 603 T * line = data[i].col; 604 for (int j = 0; j < es; j++) 605 if (line[j] == cont) 606 return; 607 Add (i, cont); 608 } 609 610 611 /// Inserts element acont into row i. Does not test if already used. AddEmpty(IndexType i)612 void AddEmpty (IndexType i) 613 { 614 IncSize (i); 615 } 616 617 /** Set the nr-th element in the i-th row to acont. 618 Does not check for overflow. */ Set(IndexType i,int nr,const T & acont)619 void Set (IndexType i, int nr, const T & acont) 620 { 621 data[i].col[nr] = acont; 622 } 623 624 625 /** Returns the nr-th element in the i-th row. 626 Does not check for overflow. */ Get(IndexType i,int nr) const627 const T & Get (IndexType i, int nr) const 628 { 629 return data[i].col[nr]; 630 } 631 632 633 /** Returns pointer to the first element in row i. */ GetLine(IndexType i) const634 const T * GetLine (IndexType i) const 635 { 636 return data[i].col; 637 } 638 639 /// Returns size of the table. Size() const640 size_t Size () const 641 { 642 return data.Size(); 643 } 644 Range() const645 auto Range () const 646 { 647 return data.Range(); 648 } 649 650 /// Returns size of the i-th row. EntrySize(IndexType i) const651 int EntrySize (IndexType i) const 652 { 653 return data[i].size; 654 } 655 656 /// DecEntrySize(IndexType i)657 void DecEntrySize (IndexType i) 658 { 659 DecSize(i); 660 } 661 662 /// Access entry i operator [](IndexType i)663 FlatArray<T> operator[] (IndexType i) 664 { 665 return FlatArray<T> (data[i].size, data[i].col); 666 } 667 668 /* 669 typedef const FlatArray<T> ConstFlatArray; 670 /// Access entry i 671 ConstFlatArray operator[] (int i) const 672 { return FlatArray<T> (data[i].size, static_cast<T*> (data[i].col)); } 673 */ operator [](IndexType i) const674 FlatArray<T> operator[] (IndexType i) const 675 { 676 return FlatArray<T> (data[i].size, data[i].col); 677 } 678 }; 679 680 681 /// Print table 682 template <class T> operator <<(ostream & s,const DynamicTable<T> & table)683 inline ostream & operator<< (ostream & s, const DynamicTable<T> & table) 684 { 685 for (auto i : Range(table)) 686 { 687 s << i << ":"; 688 for (int j = 0; j < table[i].Size(); j++) 689 s << " " << table[i][j]; 690 s << "\n"; 691 } 692 s << std::flush; 693 return s; 694 } 695 696 697 // Helper function to calculate coloring of a set of indices for parallel processing of independent elements/points/etc. 698 // Assigns a color to each of colors.Size() elements, such that two elements with the same color don't share a common 'dof', 699 // the mapping from element to dofs is provided by the function getDofs(int) -> iterable<int> 700 // 701 // Returns the number of used colors 702 template <typename Tmask> ComputeColoring(FlatArray<int> colors,size_t ndofs,Tmask const & getDofs)703 int ComputeColoring( FlatArray<int> colors, size_t ndofs, Tmask const & getDofs) 704 { 705 static Timer timer("ComputeColoring - "+Demangle(typeid(Tmask).name())); RegionTimer rt(timer); 706 static_assert(sizeof(unsigned int)==4, "Adapt type of mask array"); 707 size_t n = colors.Size(); 708 709 Array<unsigned int> mask(ndofs); 710 711 size_t colored_blocks = 0; 712 713 // We are coloring with 32 colors at once and use each bit to mask conflicts 714 unsigned int check = 0; 715 unsigned int checkbit = 0; 716 717 int current_color = 0; 718 colors = -1; 719 int maxcolor = 0; 720 721 while(colored_blocks<n) 722 { 723 mask = 0; 724 for (auto i : Range(n) ) 725 { 726 if(colors[i]>-1) continue; 727 check = 0; 728 const auto & dofs = getDofs(i); 729 730 // Check if adjacent dofs are already marked by current color 731 for (auto dof : dofs) 732 check|=mask[dof]; 733 734 // Did we find a free color? 735 if(check != 0xFFFFFFFF) 736 { 737 checkbit = 1; 738 int color = current_color; 739 // find the actual color, which is free (out of 32) 740 while (check & checkbit) 741 { 742 color++; 743 checkbit *= 2; 744 } 745 colors[i] = color; 746 maxcolor = color > maxcolor ? color : maxcolor; 747 colored_blocks++; 748 // mask all adjacent dofs with the found color 749 for (auto dof : dofs) 750 mask[dof] |= checkbit; 751 } 752 } 753 current_color+=32; 754 } 755 return maxcolor+1; 756 } 757 758 759 typedef DynamicTable<int> IntTable; 760 761 } // namespace ngcore 762 763 #endif // NETGEN_CORE_TABLE_HPP 764