GNU Octave  9.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
symrcm.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2007-2024 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 /*
27 An implementation of the Reverse Cuthill-McKee algorithm (symrcm)
28 
29 The implementation of this algorithm is based in the descriptions found in
30 
31 @INPROCEEDINGS{,
32  author = {E. Cuthill and J. McKee},
33  title = {Reducing the Bandwidth of Sparse Symmetric Matrices},
34  booktitle = {Proceedings of the 24th ACM National Conference},
35  publisher = {Brandon Press},
36  pages = {157 -- 172},
37  location = {New Jersey},
38  year = {1969}
39 }
40 
41 @BOOK{,
42  author = {Alan George and Joseph W. H. Liu},
43  title = {Computer Solution of Large Sparse Positive Definite Systems},
44  publisher = {Prentice Hall Series in Computational Mathematics},
45  ISBN = {0-13-165274-5},
46  year = {1981}
47 }
48 
49 The algorithm represents a heuristic approach to the NP-complete minimum
50 bandwidth problem.
51 
52 Written by Michael Weitzel <michael.weitzel@@uni-siegen.de>
53  <weitzel@@ldknet.org>
54 */
55 
56 #if defined (HAVE_CONFIG_H)
57 # include "config.h"
58 #endif
59 
60 #include <algorithm>
61 
62 #include "CSparse.h"
63 #include "boolNDArray.h"
64 #include "dNDArray.h"
65 #include "dSparse.h"
66 #include "oct-locbuf.h"
67 #include "oct-sparse.h"
68 #include "quit.h"
69 
70 #include "defun.h"
71 #include "errwarn.h"
72 #include "ov.h"
73 #include "ovl.h"
74 
76 
77 // A node struct for the Cuthill-McKee algorithm
78 struct CMK_Node
79 {
80  // the node's id (matrix row index)
81  octave_idx_type id;
82  // the node's degree
83  octave_idx_type deg;
84  // minimal distance to the root of the spanning tree
85  octave_idx_type dist;
86 };
87 
88 // A simple queue.
89 // Queues Q have a fixed maximum size N (rows,cols of the matrix) and are
90 // stored in an array. qh and qt point to queue head and tail.
91 
92 // Enqueue operation (adds a node "o" at the tail)
93 
94 inline static void
95 Q_enq (CMK_Node *Q, octave_idx_type N, octave_idx_type& qt, const CMK_Node& o)
96 {
97  Q[qt] = o;
98  qt = (qt + 1) % (N + 1);
99 }
100 
101 // Dequeue operation (removes a node from the head)
102 
103 inline static CMK_Node
104 Q_deq (CMK_Node *Q, octave_idx_type N, octave_idx_type& qh)
105 {
106  CMK_Node r = Q[qh];
107  qh = (qh + 1) % (N + 1);
108  return r;
109 }
110 
111 // Predicate (queue empty)
112 #define Q_empty(Q, N, qh, qt) ((qh) == (qt))
113 
114 // A simple, array-based binary heap (used as a priority queue for nodes)
115 
116 // the left descendant of entry i
117 #define LEFT(i) (((i) << 1) + 1) // = (2*(i)+1)
118  // the right descendant of entry i
119 #define RIGHT(i) (((i) << 1) + 2) // = (2*(i)+2)
120  // the parent of entry i
121 #define PARENT(i) (((i) - 1) >> 1) // = floor(((i)-1)/2)
122 
123 // Builds a min-heap (the root contains the smallest element). A is an array
124 // with the graph's nodes, i is a starting position, size is the length of A.
125 
126 static void
127 H_heapify_min (CMK_Node *A, octave_idx_type i, octave_idx_type size)
128 {
129  octave_idx_type j = i;
130  for (;;)
131  {
132  octave_idx_type l = LEFT(j);
133  octave_idx_type r = RIGHT(j);
134 
135  octave_idx_type smallest;
136  if (l < size && A[l].deg < A[j].deg)
137  smallest = l;
138  else
139  smallest = j;
140 
141  if (r < size && A[r].deg < A[smallest].deg)
142  smallest = r;
143 
144  if (smallest != j)
145  {
146  std::swap (A[j], A[smallest]);
147  j = smallest;
148  }
149  else
150  break;
151  }
152 }
153 
154 // Heap operation insert. Running time is O(log(n))
155 
156 static void
157 H_insert (CMK_Node *H, octave_idx_type& h, const CMK_Node& o)
158 {
159  octave_idx_type i = h++;
160 
161  H[i] = o;
162 
163  if (i == 0)
164  return;
165  do
166  {
167  octave_idx_type p = PARENT(i);
168  if (H[i].deg < H[p].deg)
169  {
170  std::swap (H[i], H[p]);
171 
172  i = p;
173  }
174  else
175  break;
176  }
177  while (i > 0);
178 }
179 
180 // Heap operation remove-min. Removes the smallest element in O(1) and
181 // reorganizes the heap optionally in O(log(n))
182 
183 inline static CMK_Node
184 H_remove_min (CMK_Node *H, octave_idx_type& h, int reorg/*=1*/)
185 {
186  CMK_Node r = H[0];
187  H[0] = H[--h];
188  if (reorg)
189  H_heapify_min (H, 0, h);
190  return r;
191 }
192 
193 // Predicate (heap empty)
194 #define H_empty(H, h) ((h) == 0)
195 
196 // Helper function for the Cuthill-McKee algorithm. Tries to determine a
197 // pseudo-peripheral node of the graph as starting node.
198 
199 static octave_idx_type
200 find_starting_node (octave_idx_type N, const octave_idx_type *ridx,
201  const octave_idx_type *cidx, const octave_idx_type *ridx2,
202  const octave_idx_type *cidx2, octave_idx_type *D,
203  octave_idx_type start)
204 {
205  CMK_Node w;
206 
207  OCTAVE_LOCAL_BUFFER (CMK_Node, Q, N+1);
208  boolNDArray btmp (dim_vector (1, N), false);
209  bool *visit = btmp.fortran_vec ();
210 
211  octave_idx_type qh = 0;
212  octave_idx_type qt = 0;
213  CMK_Node x;
214  x.id = start;
215  x.deg = D[start];
216  x.dist = 0;
217  Q_enq (Q, N, qt, x);
218  visit[start] = true;
219 
220  // distance level
221  octave_idx_type level = 0;
222  // current largest "eccentricity"
223  octave_idx_type max_dist = 0;
224 
225  for (;;)
226  {
227  while (! Q_empty (Q, N, qh, qt))
228  {
229  CMK_Node v = Q_deq (Q, N, qh);
230 
231  if (v.dist > x.dist || (v.id != x.id && v.deg > x.deg))
232  x = v;
233 
234  octave_idx_type i = v.id;
235 
236  // add all unvisited neighbors to the queue
237  octave_idx_type j1 = cidx[i];
238  octave_idx_type j2 = cidx2[i];
239  while (j1 < cidx[i+1] || j2 < cidx2[i+1])
240  {
241  if (j1 == cidx[i+1])
242  {
243  octave_idx_type r2 = ridx2[j2++];
244  if (! visit[r2])
245  {
246  // the distance of node j is dist(i)+1
247  w.id = r2;
248  w.deg = D[r2];
249  w.dist = v.dist+1;
250  Q_enq (Q, N, qt, w);
251  visit[r2] = true;
252 
253  if (w.dist > level)
254  level = w.dist;
255  }
256  }
257  else if (j2 == cidx2[i+1])
258  {
259  octave_idx_type r1 = ridx[j1++];
260  if (! visit[r1])
261  {
262  // the distance of node j is dist(i)+1
263  w.id = r1;
264  w.deg = D[r1];
265  w.dist = v.dist+1;
266  Q_enq (Q, N, qt, w);
267  visit[r1] = true;
268 
269  if (w.dist > level)
270  level = w.dist;
271  }
272  }
273  else
274  {
275  octave_idx_type r1 = ridx[j1];
276  octave_idx_type r2 = ridx2[j2];
277  if (r1 <= r2)
278  {
279  if (! visit[r1])
280  {
281  w.id = r1;
282  w.deg = D[r1];
283  w.dist = v.dist+1;
284  Q_enq (Q, N, qt, w);
285  visit[r1] = true;
286 
287  if (w.dist > level)
288  level = w.dist;
289  }
290  j1++;
291  if (r1 == r2)
292  j2++;
293  }
294  else
295  {
296  if (! visit[r2])
297  {
298  w.id = r2;
299  w.deg = D[r2];
300  w.dist = v.dist+1;
301  Q_enq (Q, N, qt, w);
302  visit[r2] = true;
303 
304  if (w.dist > level)
305  level = w.dist;
306  }
307  j2++;
308  }
309  }
310  }
311  } // finish of BFS
312 
313  if (max_dist < x.dist)
314  {
315  max_dist = x.dist;
316 
317  for (octave_idx_type i = 0; i < N; i++)
318  visit[i] = false;
319 
320  visit[x.id] = true;
321  x.dist = 0;
322  qt = qh = 0;
323  Q_enq (Q, N, qt, x);
324  }
325  else
326  break;
327  }
328  return x.id;
329 }
330 
331 // Calculates the node's degrees. This means counting the nonzero elements
332 // in the symmetric matrix' rows. This works for non-symmetric matrices
333 // as well.
334 
335 static octave_idx_type
336 calc_degrees (octave_idx_type N, octave_idx_type *cidx, octave_idx_type *ridx,
338 {
339  octave_idx_type max_deg = 0;
340  for (octave_idx_type i = 0; i < N; i++)
341  D[i] = 0;
342 
343  for (octave_idx_type j = 0; j < N; j++)
344  for (octave_idx_type i = cidx[j]; i < cidx[j+1]; i++)
345  D[ridx[i]]++;
346 
347  for (octave_idx_type j = 0; j < N; j++)
348  for (octave_idx_type i = cidx2[j]; i < cidx2[j+1]; i++)
349  D[ridx2[i]]++;
350 
351  for (octave_idx_type i = 0; i < N; i++)
352  if (D[i] > max_deg)
353  max_deg = D[i];
354 
355  return max_deg;
356 }
357 
358 // Transpose of the structure of a square sparse matrix
359 
360 static void
361 transpose (octave_idx_type N, const octave_idx_type *ridx,
362  const octave_idx_type *cidx, octave_idx_type *ridx2,
363  octave_idx_type *cidx2)
364 {
365  octave_idx_type nz = cidx[N];
366 
368  for (octave_idx_type i = 0; i < N; i++)
369  w[i] = 0;
370  for (octave_idx_type i = 0; i < nz; i++)
371  w[ridx[i]]++;
372  nz = 0;
373  for (octave_idx_type i = 0; i < N; i++)
374  {
375  cidx2[i] = nz;
376  nz += w[i];
377  w[i] = cidx2[i];
378  }
379  cidx2[N] = nz;
380  w[N] = nz;
381 
382  for (octave_idx_type j = 0; j < N; j++)
383  for (octave_idx_type k = cidx[j]; k < cidx[j + 1]; k++)
384  {
385  octave_idx_type q = w[ridx[k]]++;
386  ridx2[q] = j;
387  }
388 }
389 
390 // An implementation of the Cuthill-McKee algorithm.
391 DEFUN (symrcm, args, ,
392  doc: /* -*- texinfo -*-
393 @deftypefn {} {@var{p} =} symrcm (@var{S})
394 Return the symmetric reverse @nospell{Cuthill-McKee} permutation of @var{S}.
395 
396 @var{p} is a permutation vector such that
397 @code{@var{S}(@var{p}, @var{p})} tends to have its diagonal elements closer
398 to the diagonal than @var{S}. This is a good preordering for LU or
399 Cholesky@tie{}factorization of matrices that come from ``long, skinny''
400 problems. It works for both symmetric and asymmetric @var{S}.
401 
402 The algorithm represents a heuristic approach to the NP-complete bandwidth
403 minimization problem. The implementation is based in the descriptions found
404 in
405 
406 @nospell{E. Cuthill, J. McKee}.
407 @cite{Reducing the Bandwidth of Sparse Symmetric Matrices}.
408 Proceedings of the 24th @nospell{ACM} National Conference,
409 157--172 1969, Brandon Press, New Jersey.
410 
411 @nospell{A. George, J.W.H. Liu}. @cite{Computer Solution of Large Sparse
412 Positive Definite Systems}, Prentice Hall Series in Computational
413 Mathematics, ISBN 0-13-165274-5, 1981.
414 
415 @seealso{colperm, colamd, symamd}
416 @end deftypefn */)
417 {
418  if (args.length () != 1)
419  print_usage ();
420 
421  octave_value arg = args(0);
422 
423  octave_idx_type nr = arg.rows ();
424  octave_idx_type nc = arg.columns ();
425 
426  if (nr != nc)
427  err_square_matrix_required ("symrcm", "S");
428 
429  if (nr == 0 && nc == 0)
430  return ovl (NDArray (dim_vector (1, 0)));
431 
432  // dimension of the matrix
433  octave_idx_type N = nr;
434 
435  // the parameter of the matrix is converted into a sparse matrix
436  //(if necessary)
437  SparseMatrix Ar;
438 
439  octave_quit ();
440 
441  if (arg.isreal ())
442  {
443  Ar = arg.sparse_matrix_value ();
444  }
445  else
446  {
448  Ar = max (max (real (Ac), -real (Ac)), max (imag (Ac), -imag (Ac)));
449  }
450 
451  octave_quit ();
452 
453  // Note cidx/ridx are const, so use xridx and xcidx...
454  octave_idx_type *cidx = Ar.xcidx ();
455  octave_idx_type *ridx = Ar.xridx ();
456 
457  // transpose
458  OCTAVE_LOCAL_BUFFER (octave_idx_type, cidx2, N + 1);
459  OCTAVE_LOCAL_BUFFER (octave_idx_type, ridx2, cidx[N]);
460  transpose (N, ridx, cidx, ridx2, cidx2);
461 
462  octave_quit ();
463 
464  // vertex degrees
466  octave_idx_type max_deg = calc_degrees (N, cidx, ridx, cidx2, ridx2, D);
467 
468  octave_quit ();
469 
470  // the permutation vector
471  NDArray P (dim_vector (1, N));
472 
473  // if none of the nodes has a degree > 0 (a matrix of zeros)
474  // the return value corresponds to the identity permutation
475  if (max_deg == 0)
476  {
477  for (octave_idx_type i = 0; i < N; i++)
478  P(i) = i+1; // +1 to convert from base-0 to base-1
479 
480  return ovl (P);
481  }
482 
483  // At this point, all early returns have completed.
484  // Proceed to BFS.
485 
486  // sizes of the heaps
487  octave_idx_type s = 0;
488 
489  // head- and tail-indices for the queue
490  octave_idx_type qt = 0;
491  octave_idx_type qh = 0;
492  CMK_Node v, w;
493 
494  // a heap for the a node's neighbors. The number of neighbors is
495  // limited by the maximum degree max_deg:
496  OCTAVE_LOCAL_BUFFER (CMK_Node, S, max_deg);
497 
498  // a queue for the BFS. The array is always one element larger than
499  // the number of entries that are stored.
500  OCTAVE_LOCAL_BUFFER (CMK_Node, Q, N+1);
501 
502  // a counter (for building the permutation)
503  octave_idx_type c = -1;
504 
505  // upper bound for the bandwidth (=quality of solution)
506  // initialize the bandwidth of the graph with 0. B contains the
507  // the maximum of the theoretical lower limits of the subgraphs
508  // bandwidths.
509  octave_idx_type B = 0;
510 
511  // mark all nodes as unvisited; with the exception of the nodes
512  // that have degree==0 and build a CC of the graph.
513 
514  boolNDArray btmp (dim_vector (1, N), false);
515  bool *visit = btmp.fortran_vec ();
516 
517  octave_quit ();
518 
519  do
520  {
521  // locate an unvisited starting node of the graph
522  octave_idx_type i;
523  for (i = 0; i < N; i++)
524  if (! visit[i])
525  break;
526 
527  // locate a probably better starting node
528  v.id = find_starting_node (N, ridx, cidx, ridx2, cidx2, D, i);
529 
530  // mark the node as visited and enqueue it (a starting node
531  // for the BFS). Since the node will be a root of a spanning
532  // tree, its dist is 0.
533  v.deg = D[v.id];
534  v.dist = 0;
535  visit[v.id] = true;
536  Q_enq (Q, N, qt, v);
537 
538  // lower bound for the bandwidth of a subgraph
539  // keep a "level" in the spanning tree (= min. distance to the
540  // root) for determining the bandwidth of the computed
541  // permutation P
542  octave_idx_type Bsub = 0;
543  // min. dist. to the root is 0
544  octave_idx_type level = 0;
545  // the root is the first/only node on level 0
546  octave_idx_type level_N = 1;
547 
548  while (! Q_empty (Q, N, qh, qt))
549  {
550  v = Q_deq (Q, N, qh);
551  i = v.id;
552 
553  c++;
554 
555  // for computing the inverse permutation P where
556  // A(inv(P),inv(P)) or P'*A*P is banded
557  // P(i) = c;
558 
559  // for computing permutation P where
560  // A(P(i),P(j)) or P*A*P' is banded
561  P(c) = i;
562 
563  // put all unvisited neighbors j of node i on the heap
564  s = 0;
565  octave_idx_type j1 = cidx[i];
566  octave_idx_type j2 = cidx2[i];
567 
568  while (j1 < cidx[i+1] || j2 < cidx2[i+1])
569  {
570  if (j1 == cidx[i+1])
571  {
572  octave_idx_type r2 = ridx2[j2++];
573  if (! visit[r2])
574  {
575  // the distance of node j is dist(i)+1
576  w.id = r2;
577  w.deg = D[r2];
578  w.dist = v.dist+1;
579  H_insert (S, s, w);
580  visit[r2] = true;
581  }
582  }
583  else if (j2 == cidx2[i+1])
584  {
585  octave_idx_type r1 = ridx[j1++];
586  if (! visit[r1])
587  {
588  w.id = r1;
589  w.deg = D[r1];
590  w.dist = v.dist+1;
591  H_insert (S, s, w);
592  visit[r1] = true;
593  }
594  }
595  else
596  {
597  octave_idx_type r1 = ridx[j1];
598  octave_idx_type r2 = ridx2[j2];
599  if (r1 <= r2)
600  {
601  if (! visit[r1])
602  {
603  w.id = r1;
604  w.deg = D[r1];
605  w.dist = v.dist+1;
606  H_insert (S, s, w);
607  visit[r1] = true;
608  }
609  j1++;
610  if (r1 == r2)
611  j2++;
612  }
613  else
614  {
615  if (! visit[r2])
616  {
617  w.id = r2;
618  w.deg = D[r2];
619  w.dist = v.dist+1;
620  H_insert (S, s, w);
621  visit[r2] = true;
622  }
623  j2++;
624  }
625  }
626  }
627 
628  // add the neighbors to the queue (sorted by node degree)
629  while (! H_empty (S, s))
630  {
631  // locate a neighbor of i with minimal degree in O(log(N))
632  v = H_remove_min (S, s, 1);
633 
634  // entered the BFS a new level?
635  if (v.dist > level)
636  {
637  // adjustment of bandwidth:
638  // "[...] the minimum bandwidth that
639  // can be obtained [...] is the
640  // maximum number of nodes per level"
641  if (Bsub < level_N)
642  Bsub = level_N;
643 
644  level = v.dist;
645  // v is the first node on the new level
646  level_N = 1;
647  }
648  else
649  {
650  // there is no new level but another node on
651  // this level:
652  level_N++;
653  }
654 
655  // enqueue v in O(1)
656  Q_enq (Q, N, qt, v);
657  }
658 
659  // synchronize the bandwidth with level_N once again:
660  if (Bsub < level_N)
661  Bsub = level_N;
662  }
663  // finish of BFS. If there are still unvisited nodes in the graph
664  // then it is split into CCs. The computed bandwidth is the maximum
665  // of all subgraphs. Update:
666  if (Bsub > B)
667  B = Bsub;
668  }
669  // are there any nodes left?
670  while (c+1 < N);
671 
672  // compute the reverse-ordering
673  s = N / 2 - 1;
674  for (octave_idx_type i = 0, j = N - 1; i <= s; i++, j--)
675  std::swap (P.elem (i), P.elem (j));
676 
677  // increment all indices, since Octave is not C
678  return ovl (P+1);
679 }
680 
681 /*
682 
683  basic functionality test, with icosahedron:
684 %!test <*64718>
685 %! adj = [ 0 1 1 1 1 1 0 0 0 0 0 0;
686 %! 1 0 1 0 0 1 1 0 0 0 1 0;
687 %! 1 1 0 1 0 0 1 1 0 0 0 0;
688 %! 1 0 1 0 1 0 0 1 1 0 0 0;
689 %! 1 0 0 1 0 1 0 0 1 1 0 0;
690 %! 1 1 0 0 1 0 0 0 0 1 1 0;
691 %! 0 1 1 0 0 0 0 1 0 0 1 1;
692 %! 0 0 1 1 0 0 1 0 1 0 0 1;
693 %! 0 0 0 1 1 0 0 1 0 1 0 1;
694 %! 0 0 0 0 1 1 0 0 1 0 1 1;
695 %! 0 1 0 0 0 1 1 0 0 1 0 1;
696 %! 0 0 0 0 0 0 1 1 1 1 1 0 ];
697 %! p = symrcm (adj);
698 %! assert (p, [12 8 9 10 11 7 3 4 5 6 2 1]);
699 %! assert (bandwidth (adj), 9);
700 %! assert (bandwidth (adj(p, p)), 6);
701 
702  handle zero-matrix properly:
703 %!test <*64718>
704 %! adj = false (5);
705 %! p = symrcm (adj);
706 %! assert (p, 1:5);
707 
708 */
709 
710 OCTAVE_END_NAMESPACE(octave)
charNDArray max(char d, const charNDArray &m)
Definition: chNDArray.cc:230
T & elem(octave_idx_type n)
Size of the specified dimension.
Definition: Array.h:562
T * fortran_vec()
Size of the specified dimension.
Definition: Array-base.cc:1764
octave_idx_type * xcidx()
Definition: Sparse.h:602
octave_idx_type * xridx()
Definition: Sparse.h:589
Vector representing the dimensions (size) of an Array.
Definition: dim-vector.h:94
SparseMatrix sparse_matrix_value(bool frc_str_conv=false) const
Definition: ov.h:900
octave_idx_type rows() const
Definition: ov.h:545
bool isreal() const
Definition: ov.h:738
octave_idx_type columns() const
Definition: ov.h:547
SparseComplexMatrix sparse_complex_matrix_value(bool frc_str_conv=false) const
Definition: ov.h:904
ColumnVector real(const ComplexColumnVector &a)
Definition: dColVector.cc:137
ColumnVector imag(const ComplexColumnVector &a)
Definition: dColVector.cc:143
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
void print_usage(void)
Definition: defun-int.h:72
#define DEFUN(name, args_name, nargout_name, doc)
Macro to define a builtin function.
Definition: defun.h:56
void err_square_matrix_required(const char *fcn, const char *name)
Definition: errwarn.cc:122
F77_RET_T const F77_INT F77_CMPLX const F77_INT F77_CMPLX * B
F77_RET_T const F77_INT const F77_INT const F77_INT F77_DBLE const F77_INT F77_DBLE const F77_INT F77_DBLE * Q
F77_RET_T const F77_INT F77_CMPLX * A
F77_RET_T const F77_INT & N
F77_RET_T const F77_DBLE * x
T * r
Definition: mx-inlines.cc:781
std::complex< double > w(std::complex< double > z, double relerr=0)
#define OCTAVE_LOCAL_BUFFER(T, buf, size)
Definition: oct-locbuf.h:44
octave_value_list ovl(const OV_Args &... args)
Construct an octave_value_list with less typing.
Definition: ovl.h:219
#define RIGHT(i)
Definition: symrcm.cc:119
#define LEFT(i)
Definition: symrcm.cc:117
#define H_empty(H, h)
Definition: symrcm.cc:194
#define PARENT(i)
Definition: symrcm.cc:121
#define Q_empty(Q, N, qh, qt)
Definition: symrcm.cc:112