passagemath-cmr 10.6.33__cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4526 @@
1
+ # sage_setup: distribution = sagemath-cmr
2
+ # sage.doctest: optional - sage.libs.cmr
3
+ r"""
4
+ Seymour's decomposition of totally unimodular matrices and regular matroids
5
+
6
+ This module is provided by the pip-installable package :ref:`passagemath-cmr <spkg_sagemath_cmr>`.
7
+ """
8
+
9
+ # ****************************************************************************
10
+ # Copyright (C) 2023 Javier Santillan
11
+ # 2023-2024 Matthias Koeppe
12
+ # 2023-2024 Luze Xu
13
+ #
14
+ # This program is free software: you can redistribute it and/or modify
15
+ # it under the terms of the GNU General Public License as published by
16
+ # the Free Software Foundation, either version 2 of the License, or
17
+ # (at your option) any later version.
18
+ # https://www.gnu.org/licenses/
19
+ # ****************************************************************************
20
+
21
+ from libc.stdint cimport SIZE_MAX
22
+
23
+ from cysignals.signals cimport sig_on, sig_off
24
+
25
+ from sage.libs.cmr.cmr cimport *
26
+ from sage.misc.cachefunc import cached_method
27
+ from sage.rings.integer cimport Integer
28
+ from sage.rings.integer_ring import ZZ
29
+ from sage.structure.sage_object cimport SageObject
30
+
31
+ from sage.matrix.constructor import Matrix
32
+ from sage.matrix.matrix_cmr_sparse cimport Matrix_cmr_chr_sparse, _sage_edges, _sage_graph, _set_cmr_seymour_parameters
33
+ from sage.matrix.matrix_cmr_sparse cimport _sage_arcs, _sage_digraph
34
+ from sage.matrix.matrix_space import MatrixSpace
35
+
36
+
37
+ cdef class DecompositionNode(SageObject):
38
+ r"""
39
+ Base class for nodes in Seymour's decomposition
40
+ """
41
+
42
+ def __cinit__(self, *args, **kwds):
43
+ r"""
44
+ Initialize the internal decomposition, a ``CMR_SEYMOUR_NODE``.
45
+ """
46
+ self._dec = NULL
47
+
48
+ def __init__(self, matrix=None, row_keys=None, column_keys=None, base_ring=None):
49
+ r"""
50
+ Create a node in Seymour's decomposition.
51
+
52
+ INPUT:
53
+
54
+ - ``matrix`` -- the internal matrix representing the node.
55
+ Convert to a :class:`Matrix_cmr_chr_sparse`.
56
+
57
+ - ``row_keys`` -- a finite or enumerated family of arbitrary objects
58
+ that index the rows of the matrix
59
+
60
+ - ``column_keys`` -- a finite or enumerated family of arbitrary objects
61
+ that index the columns of the matrix
62
+
63
+ - ``base_ring`` -- the base ring of ``matrix`` representing the node.
64
+ For Seymour decomposition node, the base ring is `\GF{2}` or `\GF{3}` or ``ZZ``.
65
+ If the base ring is `\GF{2}`, the node and the matrix deal with
66
+ the underlying binary linear matroid;
67
+ if the base ring is `\GF(3)` or ``ZZ``, the node deals with
68
+ the matrix decomposition.
69
+
70
+ A :class:`DecompositionNode` is usually created with an internal decomposition,
71
+ ``self._dec``, see :meth:create_DecompositionNode
72
+ Such decomposition comes from the certificate of the
73
+ totally unimodularity test, see
74
+ :meth:`matrix_cmr_sparse.Matrix_cmr_chr_sparse.is_totally_unimodular`
75
+
76
+ Another usage is to create a :class:`UnknownNode` from a matrix.
77
+ A root dummy decomposition is created before completing
78
+ the decomposition, see :meth:_set_root_dec, :meth:complete_decomposition
79
+
80
+ EXAMPLES::
81
+
82
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
83
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
84
+ ....: [[1, 0], [-1, 1], [0, 1]]); M
85
+ [ 1 0]
86
+ [-1 1]
87
+ [ 0 1]
88
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
89
+ sage: result, certificate
90
+ (True, GraphicNode (3×2))
91
+
92
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
93
+ sage: node = UnknownNode([[1, 1], [0, 1]]); node
94
+ UnknownNode (2×2)
95
+ """
96
+ if matrix is None:
97
+ self._matrix = None
98
+ elif isinstance(matrix, Matrix_cmr_chr_sparse):
99
+ self._matrix = matrix
100
+ else:
101
+ try:
102
+ self._matrix = matrix._matrix_cmr()
103
+ except (AttributeError, ImportError, TypeError):
104
+ if base_ring is not None:
105
+ matrix = Matrix(matrix, ring=base_ring)
106
+ else:
107
+ matrix = Matrix(matrix)
108
+ self._matrix = Matrix_cmr_chr_sparse(matrix.parent(), matrix)
109
+ else:
110
+ if row_keys is None:
111
+ try:
112
+ row_keys = matrix.codomain().basis().keys()
113
+ except:
114
+ row_keys = None
115
+ if column_keys is None:
116
+ try:
117
+ column_keys = matrix.domain().basis().keys()
118
+ except:
119
+ column_keys = None
120
+ if row_keys is not None:
121
+ self._set_row_keys(row_keys)
122
+ if column_keys is not None:
123
+ self._set_column_keys(column_keys)
124
+ if base_ring is None:
125
+ if self._matrix is not None:
126
+ base_ring = self._matrix.parent().base_ring()
127
+ self._base_ring = base_ring
128
+
129
+ cdef _set_dec(self, CMR_SEYMOUR_NODE *dec):
130
+ r"""
131
+ Set the decomposition ``self._dec`` to ``dec``.
132
+ If the value was previously set, then free it first.
133
+ """
134
+ if self._dec != NULL:
135
+ # We own it, so we have to free it.
136
+ CMR_CALL(CMRseymourRelease(cmr, &self._dec))
137
+ if dec != NULL:
138
+ CMR_CALL(CMRseymourCapture(cmr, dec))
139
+ self._dec = dec
140
+
141
+ cdef _set_root_dec(self):
142
+ r"""
143
+ Set the decomposition by creating a root ``CMR_SEYMOUR_NODE``
144
+ based on the internal matrix representation ``self._matrix``.
145
+ """
146
+ cdef CMR_SEYMOUR_NODE *root
147
+ cdef Matrix_cmr_chr_sparse matrix
148
+ try:
149
+ matrix = self.matrix()
150
+ except Exception:
151
+ raise ValueError('no Matrix_cmr_chr_sparse matrix')
152
+ base_ring = self.base_ring()
153
+ if base_ring.characteristic() not in [0, 2, 3] :
154
+ raise ValueError(f'only defined over binary or ternary, got {base_ring}')
155
+ isTernary = base_ring.characteristic() != 2
156
+ cdef CMR_CHRMAT *mat = matrix._mat
157
+
158
+ sig_on()
159
+ try:
160
+ CMR_CALL(CMRseymourCreate(cmr, &root, isTernary, mat, True))
161
+ finally:
162
+ sig_off()
163
+ self._set_dec(root)
164
+
165
+ cdef _set_row_keys(self, row_keys):
166
+ """
167
+ Set the row keys with consistency checking: if the
168
+ value was previously set, it must remain the same.
169
+ """
170
+ if row_keys is not None:
171
+ row_keys = tuple(row_keys)
172
+ if self._row_keys is not None and self._row_keys != row_keys:
173
+ raise ValueError(f"inconsistent row keys: should be {self._row_keys} "
174
+ f"but got {row_keys}")
175
+ if row_keys is not None and self._dec != NULL and self.nrows() != len(row_keys):
176
+ raise ValueError(f"inconsistent row keys: should be of cardinality {self.nrows()} "
177
+ f"but got {row_keys}")
178
+ self._row_keys = row_keys
179
+
180
+ cdef _set_column_keys(self, column_keys):
181
+ """
182
+ Set the column keys with consistency checking: if the
183
+ value was previously set, it must remain the same.
184
+ """
185
+ if column_keys is not None:
186
+ column_keys = tuple(column_keys)
187
+ if self._column_keys is not None and self._column_keys != column_keys:
188
+ raise ValueError(f"inconsistent column keys: should be {self._column_keys} "
189
+ f"but got {column_keys}")
190
+ if column_keys is not None and self._dec != NULL and self.ncols() != len(column_keys):
191
+ raise ValueError(f"inconsistent column keys: should be of cardinality {self.ncols()} "
192
+ f"but got {column_keys}")
193
+ self._column_keys = column_keys
194
+
195
+ def __dealloc__(self):
196
+ """
197
+ Frees all the memory allocated for this node.
198
+ """
199
+ self._set_dec(NULL)
200
+
201
+ def __hash__(self):
202
+ """
203
+ Return a hash of this node. It is the hash of the decomposition.
204
+ """
205
+ return <int>self._dec
206
+
207
+ def nrows(self):
208
+ r"""
209
+ Return the number of rows of the internal matrix representing this node.
210
+
211
+ EXAMPLES::
212
+
213
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
214
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
215
+ ....: [[1, 0], [-1, 1], [0, 1]]); M
216
+ [ 1 0]
217
+ [-1 1]
218
+ [ 0 1]
219
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
220
+ sage: result, certificate
221
+ (True, GraphicNode (3×2))
222
+ sage: certificate.nrows()
223
+ 3
224
+ sage: certificate.ncols()
225
+ 2
226
+
227
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
228
+ sage: node = UnknownNode([[1, 1], [0, 1]]); node
229
+ UnknownNode (2×2)
230
+ sage: node.nrows()
231
+ 2
232
+ sage: node.ncols()
233
+ 2
234
+ """
235
+ if self._row_keys is not None:
236
+ return len(self._row_keys)
237
+ if self._dec != NULL:
238
+ return CMRseymourNumRows(self._dec)
239
+ if self._matrix is not None:
240
+ return self._matrix.nrows()
241
+ raise RuntimeError('nrows undefined')
242
+
243
+ def ncols(self):
244
+ r"""
245
+ Return the number of columns of the internal matrix representing this node.
246
+
247
+ EXAMPLES::
248
+
249
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
250
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
251
+ ....: [[1, 0], [-1, 1], [0, 1]]); M
252
+ [ 1 0]
253
+ [-1 1]
254
+ [ 0 1]
255
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
256
+ sage: result, certificate
257
+ (True, GraphicNode (3×2))
258
+ sage: certificate.nrows()
259
+ 3
260
+ sage: certificate.ncols()
261
+ 2
262
+
263
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
264
+ sage: node = UnknownNode([[1, 1], [0, 1]]); node
265
+ UnknownNode (2×2)
266
+ sage: node.nrows()
267
+ 2
268
+ sage: node.ncols()
269
+ 2
270
+ """
271
+ if self._column_keys is not None:
272
+ return len(self._column_keys)
273
+ if self._dec != NULL:
274
+ return CMRseymourNumColumns(self._dec)
275
+ if self._matrix is not None:
276
+ return self._matrix.ncols()
277
+ raise RuntimeError('ncols undefined')
278
+
279
+ def dimensions(self):
280
+ r"""
281
+ Return the number of rows and columns of this node.
282
+
283
+ EXAMPLES::
284
+
285
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
286
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
287
+ ....: [[1, 0], [-1, 1], [0, 1]]); M
288
+ [ 1 0]
289
+ [-1 1]
290
+ [ 0 1]
291
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
292
+ sage: result, certificate
293
+ (True, GraphicNode (3×2))
294
+ sage: certificate.dimensions()
295
+ (3, 2)
296
+
297
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
298
+ sage: node = UnknownNode([[1, 1], [0, 1]]); node
299
+ UnknownNode (2×2)
300
+ sage: node.dimensions()
301
+ (2, 2)
302
+ """
303
+ return self.nrows(), self.ncols()
304
+
305
+ def base_ring(self):
306
+ r"""
307
+ Return the base ring of the matrix representing the node.
308
+ """
309
+ return self._base_ring
310
+
311
+ def matrix(self):
312
+ r"""
313
+ Return a :class:`Matrix`.
314
+
315
+ EXAMPLES::
316
+
317
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
318
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
319
+ ....: [[1, 0], [-1, 1], [0, 1]]); M
320
+ [ 1 0]
321
+ [-1 1]
322
+ [ 0 1]
323
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
324
+ sage: result, certificate
325
+ (True, GraphicNode (3×2))
326
+ sage: certificate.matrix()
327
+ [ 1 0]
328
+ [-1 1]
329
+ [ 0 1]
330
+
331
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
332
+ sage: node = UnknownNode([[1, 1], [0, 1]]); node
333
+ UnknownNode (2×2)
334
+ sage: node.matrix()
335
+ [1 1]
336
+ [0 1]
337
+ """
338
+ if self._matrix is not None:
339
+ return self._matrix
340
+ if self._dec is NULL:
341
+ if isinstance(self, SumNode):
342
+ return self.block_matrix_form()
343
+ raise ValueError('Matrix and decomposition are both missing')
344
+ cdef Matrix_cmr_chr_sparse result
345
+ cdef CMR_CHRMAT *mat = CMRseymourGetMatrix(self._dec)
346
+ if mat == NULL:
347
+ return None
348
+ ms = MatrixSpace(self.base_ring(), mat.numRows, mat.numColumns, sparse=True)
349
+ result = Matrix_cmr_chr_sparse.__new__(Matrix_cmr_chr_sparse, ms)
350
+ result._mat = mat
351
+ result._root = self # Matrix is owned by us
352
+ self._matrix = result
353
+ return result
354
+
355
+ def row_keys(self):
356
+ r"""
357
+ Return the row keys of this node.
358
+
359
+ OUTPUT: a tuple or ``None``
360
+
361
+ EXAMPLES::
362
+
363
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
364
+ sage: node = UnknownNode(matrix(ZZ, [[1, 0, 1], [0, 1, 1]]),
365
+ ....: row_keys='ab',
366
+ ....: column_keys=range(3)); node
367
+ UnknownNode (2×3)
368
+ sage: node.row_keys()
369
+ ('a', 'b')
370
+ """
371
+ return self._row_keys
372
+
373
+ def column_keys(self):
374
+ r"""
375
+ Return the column keys of this node.
376
+
377
+ OUTPUT: a tuple or ``None``
378
+
379
+ EXAMPLES::
380
+
381
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
382
+ sage: node = UnknownNode(matrix(ZZ, [[1, 0, 1], [0, 1, 1]]),
383
+ ....: row_keys='ab',
384
+ ....: column_keys=range(3)); node
385
+ UnknownNode (2×3)
386
+ sage: node.column_keys()
387
+ (0, 1, 2)
388
+ """
389
+ return self._column_keys
390
+
391
+ def set_default_keys(self):
392
+ r"""
393
+ Set default row and column keys.
394
+
395
+ .. SEEALSO:: :class:`ElementKey`
396
+
397
+ EXAMPLES::
398
+
399
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
400
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
401
+ ....: [[1, 0], [-1, 1], [0, 1]]); M
402
+ [ 1 0]
403
+ [-1 1]
404
+ [ 0 1]
405
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
406
+ sage: result, certificate
407
+ (True, GraphicNode (3×2))
408
+ sage: certificate.row_keys() is None
409
+ True
410
+ sage: certificate.set_default_keys()
411
+ sage: certificate.row_keys()
412
+ (r0, r1, r2)
413
+ """
414
+ row_keys = self.row_keys()
415
+ column_keys = self.column_keys()
416
+ if row_keys is None or column_keys is None:
417
+ row_keys = tuple(ElementKey(f"r{i}") for i in range(self.nrows()))
418
+ column_keys = tuple(ElementKey(f"c{i}") for i in range(self.ncols()))
419
+ elif not isinstance(row_keys[0], ElementKey):
420
+ row_keys = tuple(ElementKey(key) for key in row_keys)
421
+ column_keys = tuple(ElementKey(key) for key in column_keys)
422
+ self._row_keys = row_keys
423
+ self._column_keys = column_keys
424
+
425
+ @cached_method
426
+ def morphism(self):
427
+ r"""
428
+ Create the matrix in the distinguished bases of the domain and codomain
429
+ to build the module morphism.
430
+
431
+ See also: :class:`sage.modules.with_basis.morphism.ModuleMorphismFromMatrix`.
432
+
433
+ EXAMPLES::
434
+
435
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
436
+ sage: node = UnknownNode(matrix(ZZ, [[1, 0, 1], [0, 1, 1]]),
437
+ ....: row_keys='ab',
438
+ ....: column_keys=range(3)); node
439
+ UnknownNode (2×3)
440
+ sage: node.matrix()
441
+ [1 0 1]
442
+ [0 1 1]
443
+ sage: node.morphism()._unicode_art_matrix()
444
+ 0 1 2
445
+ a⎛1 0 1⎞
446
+ b⎝0 1 1⎠
447
+ """
448
+ return Matrix(self.matrix(),
449
+ row_keys=self.row_keys(),
450
+ column_keys=self.column_keys())
451
+
452
+ def as_ordered_tree(self):
453
+ r"""
454
+ Return the decomposition tree rooted at ``self``.
455
+
456
+ EXAMPLES::
457
+
458
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
459
+ sage: M = matrix([[1, 0], [-1, 1], [0, 1]], sparse=True)
460
+ sage: M2 = block_diagonal_matrix([M, M], sparse=True)
461
+ sage: M2cmr = Matrix_cmr_chr_sparse(M2.parent(), M2); M2cmr
462
+ [ 1 0 0 0]
463
+ [-1 1 0 0]
464
+ [ 0 1 0 0]
465
+ [ 0 0 1 0]
466
+ [ 0 0 -1 1]
467
+ [ 0 0 0 1]
468
+ sage: result, certificate = M2cmr.is_totally_unimodular(certificate=True)
469
+ sage: T = certificate.as_ordered_tree(); T
470
+ OneSumNode (6×4) with 2 children[GraphicNode (3×2)[], GraphicNode (3×2)[]]
471
+ sage: unicode_art(T)
472
+ ╭───────────OneSumNode (6×4) with 2 children
473
+ │ │
474
+ GraphicNode (3×2) GraphicNode (3×2)
475
+ """
476
+ from sage.combinat.ordered_tree import LabelledOrderedTree
477
+ return LabelledOrderedTree([child.as_ordered_tree() for child in self.child_nodes()],
478
+ label=self)
479
+
480
+ def plot(self, **kwds):
481
+ r"""
482
+ Plot the decomposition tree rooted at ``self``.
483
+
484
+ EXAMPLES::
485
+
486
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
487
+ sage: M = matrix([[1, 0], [-1, 1], [0, 1]], sparse=True)
488
+ sage: M2MT = block_diagonal_matrix([M, M, M.T], sparse=True)
489
+ sage: M2MTcmr = Matrix_cmr_chr_sparse(M2MT.parent(), M2MT)
490
+ sage: result, certificate = M2MTcmr.is_totally_unimodular(certificate=True)
491
+ sage: T = certificate.as_ordered_tree()
492
+ sage: T.plot() # needs sage.plot
493
+ Graphics object consisting of 8 graphics primitives
494
+ """
495
+ return self.as_ordered_tree().plot(**kwds)
496
+
497
+ def is_ternary(self):
498
+ r"""
499
+ Return whether the decomposition is over `\GF{3}`.
500
+
501
+ EXAMPLES::
502
+
503
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
504
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
505
+ ....: [[1, 0], [-1, 1], [0, 1]]); M
506
+ [ 1 0]
507
+ [-1 1]
508
+ [ 0 1]
509
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
510
+ sage: result, certificate
511
+ (True, GraphicNode (3×2))
512
+ sage: certificate.is_ternary()
513
+ True
514
+
515
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
516
+ ....: [[1, 0], [1, 1], [0, 1]]); M
517
+ [1 0]
518
+ [1 1]
519
+ [0 1]
520
+ sage: result, certificate = M._is_binary_linear_matroid_regular(certificate=True)
521
+ sage: result, certificate
522
+ (True, GraphicNode (3×2))
523
+ sage: certificate.is_ternary()
524
+ False
525
+ """
526
+ return <bint> CMRseymourIsTernary(self._dec)
527
+
528
+ def nchildren(self):
529
+ r"""
530
+ Return the number of children of the node.
531
+
532
+ EXAMPLES::
533
+
534
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
535
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
536
+ ....: [[1, 0], [-1, 1], [0, 1]]); M
537
+ [ 1 0]
538
+ [-1 1]
539
+ [ 0 1]
540
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
541
+ sage: result, certificate
542
+ (True, GraphicNode (3×2))
543
+ sage: certificate.nchildren()
544
+ 0
545
+ """
546
+ if self._child_nodes is not None:
547
+ return len(self._child_nodes)
548
+ if self._dec == NULL:
549
+ return 0
550
+ return CMRseymourNumChildren(self._dec)
551
+
552
+ cdef _CMRelement_to_key(self, CMR_ELEMENT element):
553
+ r"""
554
+ Transform a ``CMRelement`` (row or column index implemented in cmr)
555
+ to a row key or a column key.
556
+ """
557
+ if not CMRelementIsValid(element):
558
+ raise ValueError('CMRelement index not valid. Extra row or column is detected.')
559
+ if self.row_keys() is None or self.column_keys() is None:
560
+ raise ValueError('row_keys and column_keys are required')
561
+ if CMRelementIsRow(element):
562
+ return self.row_keys()[CMRelementToRowIndex(element)]
563
+ else:
564
+ return self.column_keys()[CMRelementToColumnIndex(element)]
565
+
566
+ def _create_child_node(self, index):
567
+ r"""
568
+ Return the child node of ``self`` corresponding to the ``index``,
569
+ and the corresponding row and column keys in the parent node.
570
+
571
+ OUTPUT: a tuple of (child node, child row keys, child column keys)
572
+ """
573
+ row_keys = self.row_keys()
574
+ column_keys = self.column_keys()
575
+ cdef CMR_SEYMOUR_NODE *child_dec = CMRseymourChild(self._dec, index)
576
+ cdef CMR_ELEMENT *parent_rows = CMRseymourChildRowsToParent(self._dec, index)
577
+ cdef CMR_ELEMENT *parent_columns = CMRseymourChildColumnsToParent(self._dec, index)
578
+ child_nrows = CMRseymourNumRows(child_dec)
579
+ child_ncols = CMRseymourNumColumns(child_dec)
580
+
581
+ if parent_rows == NULL or all(parent_rows[i] == 0 for i in range(child_nrows)):
582
+ raise ValueError(f"Child {index} does not have parents rows")
583
+ parent_rows_tuple = tuple(parent_rows[i] for i in range(child_nrows))
584
+
585
+ if parent_columns == NULL or all(parent_columns[i] == 0 for i in range(child_ncols)):
586
+ raise ValueError(f"Child {index} does not have parents columns")
587
+ parent_columns_tuple = tuple(parent_columns[i] for i in range(child_ncols))
588
+
589
+ if row_keys is not None and column_keys is not None:
590
+ child_row_keys = tuple(self._CMRelement_to_key(element)
591
+ for element in parent_rows_tuple)
592
+ child_column_keys = tuple(self._CMRelement_to_key(element)
593
+ for element in parent_columns_tuple)
594
+ child = create_DecompositionNode(child_dec, matrix=None,
595
+ row_keys=child_row_keys,
596
+ column_keys=child_column_keys,
597
+ base_ring=self.base_ring())
598
+ else:
599
+ child_row_keys = tuple(CMRelementToRowIndex(element)
600
+ for element in parent_rows_tuple)
601
+ child_column_keys = tuple(CMRelementToColumnIndex(element)
602
+ for element in parent_columns_tuple)
603
+ child = create_DecompositionNode(child_dec, matrix=None,
604
+ row_keys=child_row_keys,
605
+ column_keys=child_column_keys,
606
+ base_ring=self.base_ring())
607
+ return child, child_row_keys, child_column_keys
608
+
609
+ def _children(self):
610
+ r"""
611
+ Return a tuple of the tuples of children and their row and column keys.
612
+ The underlying implementation of :meth:`child_nodes`
613
+ and :meth:`child_keys`.
614
+ """
615
+ if self._child_nodes is not None:
616
+ return self._child_nodes
617
+ children_tuple = tuple(self._create_child_node(index)
618
+ for index in range(self.nchildren()))
619
+ self._child_nodes = children_tuple
620
+ return self._child_nodes
621
+
622
+ def child_nodes(self):
623
+ r"""
624
+ Return a tuple of the children.
625
+
626
+ The children are sorted by the ordering inherited from cmr, which
627
+ is their appearance in the parent.
628
+
629
+ In the case of :class:`SumNode`, this is the same as :meth:`~SumNode.summands`.
630
+
631
+ For graphic or leaf nodes, it returns the empty tuple.
632
+
633
+ EXAMPLES::
634
+
635
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
636
+ sage: M = Matrix_cmr_chr_sparse.one_sum([[1, 0], [-1, 1]],
637
+ ....: [[1, 1], [-1, 0]],
638
+ ....: [[1, 0], [0,1]]); M
639
+ [ 1 0| 0 0| 0 0]
640
+ [-1 1| 0 0| 0 0]
641
+ [-----+-----+-----]
642
+ [ 0 0| 1 1| 0 0]
643
+ [ 0 0|-1 0| 0 0]
644
+ [-----+-----+-----]
645
+ [ 0 0| 0 0| 1 0]
646
+ [ 0 0| 0 0| 0 1]
647
+ sage: result, certificate = M.is_totally_unimodular(certificate=True); certificate
648
+ OneSumNode (6×6) with 4 children
649
+ sage: certificate.child_nodes()
650
+ (GraphicNode (2×2), GraphicNode (2×2), GraphicNode (1×1), GraphicNode (1×1))
651
+
652
+ sage: M2 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 2, 2, sparse=True),
653
+ ....: [[1, 1], [-1, 0]]); M2
654
+ [ 1 1]
655
+ [-1 0]
656
+ sage: result, certificate = M2.is_totally_unimodular(certificate=True); certificate
657
+ GraphicNode (2×2)
658
+ sage: certificate.child_nodes()
659
+ ()
660
+ """
661
+ return tuple(child[0] for child in self._children())
662
+
663
+ def child_keys(self):
664
+ r"""
665
+ Return a tuple of the tuples of the row and column keys of children
666
+ in the parent node.
667
+
668
+ OUTPUT: a tuple of (row keys, column keys)
669
+
670
+ If the number of children is 1, then output (row keys, column keys).
671
+
672
+ EXAMPLES::
673
+
674
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
675
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
676
+ ....: [[1, 0], [-1, 1], [0, 1]]); M
677
+ [ 1 0]
678
+ [-1 1]
679
+ [ 0 1]
680
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
681
+ sage: certificate.child_keys()
682
+ ()
683
+
684
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
685
+ sage: M = matrix([[1, 0], [-1, 1], [0, 1]], sparse=True)
686
+ sage: M2 = block_diagonal_matrix([M, M], sparse=True)
687
+ sage: M2cmr = Matrix_cmr_chr_sparse(M2.parent(), M2); M2cmr
688
+ [ 1 0 0 0]
689
+ [-1 1 0 0]
690
+ [ 0 1 0 0]
691
+ [ 0 0 1 0]
692
+ [ 0 0 -1 1]
693
+ [ 0 0 0 1]
694
+ sage: result, certificate = M2cmr.is_totally_unimodular(certificate=True)
695
+ sage: result, certificate
696
+ (True, OneSumNode (6×4) with 2 children)
697
+ sage: C = certificate.summands(); C
698
+ (GraphicNode (3×2), GraphicNode (3×2))
699
+ sage: certificate.child_keys()[0]
700
+ ((0, 1, 2), (0, 1))
701
+ sage: certificate.child_keys()[1]
702
+ ((3, 4, 5), (2, 3))
703
+
704
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
705
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 9, 12, sparse=True),
706
+ ....: [[ 1, -1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
707
+ ....: [ 0, 0, 0, 1, -1, 0, 0, 0, 1, 1, 1, 1],
708
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
709
+ ....: [ 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0],
710
+ ....: [ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, -1, -1],
711
+ ....: [ 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0],
712
+ ....: [ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, -1, -1],
713
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0],
714
+ ....: [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1]])
715
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
716
+ ....: decompose_strategy="delta_pivot",
717
+ ....: row_keys=['r1', 'r2', 'r3', 'r4', 'r5',
718
+ ....: 'r6', 'r7', 'r8', 'r9'],
719
+ ....: column_keys=['a','b','c','d','e','f',
720
+ ....: 'g','h','i','j','k','l'])
721
+ sage: C = certificate.child_nodes()[0]; C
722
+ DeltaSumNode (9×12)
723
+ sage: C1, C2 = C.child_nodes()
724
+ sage: C1.matrix()
725
+ [ 0 0 1 1 1 1 1]
726
+ [ 1 1 0 0 0 -1 -1]
727
+ [ 1 0 -1 0 -1 -1 -1]
728
+ [ 0 1 1 0 1 0 0]
729
+ [ 0 0 0 -1 -1 0 -1]
730
+ sage: C2.matrix()
731
+ [-1 0 1 -1 0 0 0 0 -1]
732
+ [ 0 0 -1 1 0 1 -1 0 1]
733
+ [ 1 1 0 1 1 0 0 0 1]
734
+ [ 1 1 0 1 1 0 0 0 0]
735
+ [ 1 1 -1 1 0 1 0 1 1]
736
+ [ 1 1 0 0 0 0 1 1 0]
737
+ sage: certificate.child_keys()
738
+ ((i, r2, r3, r4, r5, r6, r7, r8, r9), (a, b, c, d, e, f, g, h, r1, j, k, l))
739
+ sage: C.matrix()
740
+ [ 1 -1 0 0 0 0 0 0 -1 1 1 1]
741
+ [-1 1 0 1 -1 0 0 0 1 0 0 0]
742
+ [-1 1 0 0 0 0 1 1 1 0 0 0]
743
+ [ 0 1 1 0 0 0 0 0 1 0 -1 -1]
744
+ [ 0 1 1 0 0 0 0 0 0 0 -1 -1]
745
+ [-1 1 0 1 0 1 0 0 1 0 -1 -1]
746
+ [ 0 0 0 0 1 1 0 0 0 0 -1 -1]
747
+ [-1 1 0 0 0 0 1 0 1 -1 0 -1]
748
+ [ 0 0 0 0 0 0 0 1 0 1 0 1]
749
+ sage: C.child_keys()
750
+ (((i, r3, r8, r9, r4), (g, h, j, k, l, a, +a-r4)),
751
+ ((i, r2, r4, r5, r6, r7), (-i+k, k, a, b, c, d, e, f, r1)))
752
+
753
+ sage: M2 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 2, 2, sparse=True),
754
+ ....: [[1, 1], [-1, 0]]); M2
755
+ [ 1 1]
756
+ [-1 0]
757
+ sage: result, certificate = M2.is_totally_unimodular(certificate=True); certificate
758
+ GraphicNode (2×2)
759
+ sage: certificate.child_keys()
760
+ ()
761
+ """
762
+ if self.nchildren() == 1:
763
+ child = self._children()[0]
764
+ return (child[1], child[2])
765
+ return tuple((child[1], child[2]) for child in self._children())
766
+
767
+ def _repr_(self):
768
+ r"""
769
+ Return a string representation of ``self``.
770
+
771
+ EXAMPLES::
772
+
773
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
774
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 6, sparse=True),
775
+ ....: [[1,0,1,1,0,0], [0,1,1,1,0,0], [1,0,1,0,1,1],
776
+ ....: [0,-1,0,-1,1,1], [1,0,1,0,1,0], [0,-1,0,-1,0,1]])
777
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
778
+ ....: decompose_strategy="delta_pivot",
779
+ ....: row_keys=range(6),
780
+ ....: column_keys='abcdef')
781
+ sage: print(certificate)
782
+ PivotsNode (6×6)
783
+ """
784
+ nrows, ncols = self.dimensions()
785
+ return f'{self.__class__.__name__} ({nrows}×{ncols})'
786
+
787
+ def _unicode_art_(self):
788
+ r"""
789
+ Return a unicode art representation of the decomposition tree rooted at ``self``.
790
+
791
+ EXAMPLES::
792
+
793
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
794
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 6, sparse=True),
795
+ ....: [[1,0,1,1,0,0], [0,1,1,1,0,0], [1,0,1,0,1,1],
796
+ ....: [0,-1,0,-1,1,1], [1,0,1,0,1,0], [0,-1,0,-1,0,1]])
797
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
798
+ ....: decompose_strategy="delta_pivot",
799
+ ....: row_keys=range(6),
800
+ ....: column_keys='abcdef')
801
+ sage: unicode_art(certificate)
802
+ PivotsNode (6×6)
803
+
804
+ ╭DeltaSumNode (6×6)─╮
805
+ │ │
806
+ CographicNode (4×5) GraphicNode (4×5)
807
+ """
808
+ return self.as_ordered_tree()._unicode_art_()
809
+
810
+ def _ascii_art_(self):
811
+ r"""
812
+ Return a ascii art representation of the decomposition tree rooted at ``self``.
813
+
814
+ EXAMPLES::
815
+
816
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
817
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 6, sparse=True),
818
+ ....: [[1,0,1,1,0,0], [0,1,1,1,0,0], [1,0,1,0,1,1],
819
+ ....: [0,-1,0,-1,1,1], [1,0,1,0,1,0], [0,-1,0,-1,0,1]])
820
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
821
+ ....: decompose_strategy="delta_pivot",
822
+ ....: row_keys=range(6),
823
+ ....: column_keys='abcdef')
824
+ sage: ascii_art(certificate)
825
+ PivotsNode (6×6)
826
+ |
827
+ _________DeltaSumNode (6×6)
828
+ / /
829
+ CographicNode (4×5) GraphicNode (4×5)
830
+ """
831
+ return self.as_ordered_tree()._ascii_art_()
832
+
833
+ def one_sum(*summands, **kwds):
834
+ r"""
835
+ Return a :class:`OneSumNode` constructed from the given nodes (summands).
836
+
837
+ INPUT:
838
+
839
+ - ``summands`` -- decomposition nodes :class:`DecompositionNode`
840
+
841
+ - ``summand_ids`` -- a tuple or list of ids for summands
842
+
843
+ - ``row_keys`` -- a finite or enumerated family of arbitrary objects
844
+ that index the rows of the result.
845
+ Must be consistent with the row keys of the summands
846
+
847
+ - ``column_keys`` -- a finite or enumerated family of arbitrary objects
848
+ that index the columns of the result.
849
+ Must be consistent with the column keys of the summands
850
+
851
+ Note that ``row_keys``, ``column_keys`` of ``summands`` are disjoint
852
+
853
+ OUTPUT: A :class:`OneSumNode`
854
+
855
+ The terminology "1-sum" is used in the context of Seymour's decomposition
856
+ of totally unimodular matrices and regular matroids, see [Sch1986]_.
857
+
858
+ .. SEEALSO:: :meth:`two_sum`
859
+ :meth:`delta_sum`, :meth:`three_sum`, :meth:`y_sum`
860
+
861
+ EXAMPLES::
862
+
863
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
864
+ sage: from sage.matrix.seymour_decomposition import DecompositionNode
865
+ sage: M2 = Matrix_cmr_chr_sparse.one_sum([[1, 0], [-1, 1]],
866
+ ....: [[1, 1], [-1, 0]])
867
+ sage: result, certificate = M2.is_totally_unimodular(certificate=True,
868
+ ....: row_keys=range(4),
869
+ ....: column_keys='abcd')
870
+ sage: certificate
871
+ OneSumNode (4×4) with 2 children
872
+ sage: certificate.summand_matrices()
873
+ (
874
+ [ 1 0] [ 1 1]
875
+ [-1 1], [-1 0]
876
+ )
877
+ sage: certificate.child_keys()
878
+ (((0, 1), ('a', 'b')), ((2, 3), ('c', 'd')))
879
+ sage: node = DecompositionNode.one_sum(*certificate.child_nodes())
880
+ sage: node.summand_matrices()
881
+ (
882
+ [ 1 0] [ 1 1]
883
+ [-1 1], [-1 0]
884
+ )
885
+ sage: node.child_keys()
886
+ (((0, 1), ('a', 'b')), ((2, 3), ('c', 'd')))
887
+
888
+ sage: M3 = Matrix_cmr_chr_sparse.one_sum([[1, 0], [-1, 1]],
889
+ ....: [[1]], [[-1]])
890
+ sage: result, certificate = M3.is_totally_unimodular(certificate=True,
891
+ ....: row_keys=range(4),
892
+ ....: column_keys='abcd')
893
+ sage: certificate
894
+ OneSumNode (4×4) with 3 children
895
+ sage: certificate.summand_matrices()
896
+ (
897
+ [ 1 0]
898
+ [-1 1], [1], [-1]
899
+ )
900
+ sage: certificate.child_keys()
901
+ (((0, 1), ('a', 'b')), ((2,), ('c',)), ((3,), ('d',)))
902
+ sage: node = DecompositionNode.one_sum(*certificate.child_nodes())
903
+ sage: node.summand_matrices()
904
+ (
905
+ [ 1 0]
906
+ [-1 1], [1], [-1]
907
+ )
908
+ sage: node.child_keys()
909
+ (((0, 1), ('a', 'b')), ((2,), ('c',)), ((3,), ('d',)))
910
+
911
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
912
+ sage: node = UnknownNode(matrix(ZZ, [[1, 0, 1], [0, 1, 1]]),
913
+ ....: row_keys='ab',
914
+ ....: column_keys=range(3)); node
915
+ UnknownNode (2×3)
916
+ sage: node = DecompositionNode.one_sum(certificate, node, summand_ids=range(2))
917
+ sage: node.summand_matrices()
918
+ (
919
+ [ 1 0| 0| 0]
920
+ [-1 1| 0| 0]
921
+ [-----+--+--]
922
+ [ 0 0| 1| 0]
923
+ [-----+--+--] [1 0 1]
924
+ [ 0 0| 0|-1], [0 1 1]
925
+ )
926
+ sage: node.child_keys()
927
+ ((((0, 0), (0, 1), (0, 2), (0, 3)), ((0, 'a'), (0, 'b'), (0, 'c'), (0, 'd'))),
928
+ (((1, 'a'), (1, 'b')), ((1, 0), (1, 1), (1, 2))))
929
+
930
+ ``row_keys``, ``column_keys`` of ``summands`` are disjoint::
931
+
932
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
933
+ sage: from sage.matrix.seymour_decomposition import DecompositionNode
934
+ sage: M2 = Matrix_cmr_chr_sparse.one_sum([[1, 0], [-1, 1]],
935
+ ....: [[1, 1], [-1, 0]])
936
+ sage: result, certificate = M2.is_totally_unimodular(certificate=True,
937
+ ....: row_keys='aefg',
938
+ ....: column_keys='abcd')
939
+ sage: node = DecompositionNode.one_sum(*certificate.child_nodes())
940
+ Traceback (most recent call last):
941
+ ...
942
+ ValueError: keys must be disjoint, got
943
+ summand_row_keys=('a', 'e'), summand_column_keys=('a', 'b')
944
+
945
+ sage: result, certificate = M2.is_totally_unimodular(certificate=True,
946
+ ....: row_keys=range(4),
947
+ ....: column_keys='abcd')
948
+ sage: node = DecompositionNode.one_sum(*certificate.child_nodes(),
949
+ ....: row_keys=range(4),
950
+ ....: column_keys='abce')
951
+ Traceback (most recent call last):
952
+ ...
953
+ ValueError: inconsistent column_keys, got column_keys=('a', 'b', 'c', 'e'),
954
+ should be a permutation of ['a', 'b', 'c', 'd']
955
+ """
956
+ summand_ids = kwds.pop('summand_ids', None)
957
+ row_keys = kwds.pop('row_keys', None)
958
+ column_keys = kwds.pop('column_keys', None)
959
+ if kwds:
960
+ raise ValueError(f'unknown keywords: {sorted(kwds)}')
961
+
962
+ result = OneSumNode()
963
+ summands = tuple(summands)
964
+ if summand_ids is not None:
965
+ summand_ids = tuple(summand_ids)
966
+ else:
967
+ summand_ids = tuple(None for summand in summands)
968
+ # TODO: Make summands DecompositionNodes if not already
969
+ # Check row_keys, column_keys of summands are disjoint. Otherwise error
970
+ summands_row_keys = []
971
+ summands_column_keys = []
972
+ row_key_list = []
973
+ column_key_list = []
974
+ key_set = set()
975
+ for summand, id in zip(summands, summand_ids):
976
+ summand_row_keys = summand.row_keys()
977
+ summand_column_keys = summand.column_keys()
978
+ if id is not None:
979
+ summand_row_keys = tuple((id, key) for key in summand_row_keys)
980
+ summand_column_keys = tuple((id, key) for key in summand_column_keys)
981
+
982
+ old_num_keys = len(key_set)
983
+ row_key_list.extend(summand_row_keys)
984
+ column_key_list.extend(summand_column_keys)
985
+ key_set.update(summand_row_keys)
986
+ key_set.update(summand_column_keys)
987
+ if old_num_keys + len(summand_row_keys) + len(summand_column_keys) != len(key_set):
988
+ raise ValueError(f'keys must be disjoint, '
989
+ f'got {summand_row_keys=}, {summand_column_keys=}')
990
+ summands_row_keys.append(summand_row_keys)
991
+ summands_column_keys.append(summand_column_keys)
992
+
993
+ if row_keys is not None:
994
+ row_keys = tuple(row_keys)
995
+ if set(row_keys) != set(row_key_list) or len(row_keys) != len(row_key_list):
996
+ raise ValueError(f'inconsistent row_keys, '
997
+ f'got {row_keys=}, should be a permutation of {row_key_list}')
998
+ else:
999
+ row_keys = tuple(row_key_list)
1000
+ if column_keys is not None:
1001
+ column_keys = tuple(column_keys)
1002
+ if set(column_keys) != set(column_key_list) or len(column_keys) != len(column_key_list):
1003
+ raise ValueError(f'inconsistent column_keys, '
1004
+ f'got {column_keys=}, should be a permutation of {column_key_list}')
1005
+ else:
1006
+ column_keys = tuple(column_key_list)
1007
+
1008
+ result._child_nodes = tuple(zip(summands, summands_row_keys, summands_column_keys))
1009
+ result._row_keys = row_keys
1010
+ result._column_keys = column_keys
1011
+ return result
1012
+
1013
+ def _regularity(self):
1014
+ r"""
1015
+ Return whether the decomposition node is regular (binary) or TU (ternary).
1016
+ If it is not determined, raise ValueError.
1017
+ """
1018
+ cdef int8_t regularity
1019
+ if self._dec != NULL:
1020
+ regularity = CMRseymourRegularity(self._dec)
1021
+ if regularity:
1022
+ return regularity > 0
1023
+ raise ValueError('It is not determined whether the decomposition node is regular/TU')
1024
+
1025
+ def _graphicness(self):
1026
+ r"""
1027
+ Return whether the decomposition node is graphic (binary) or network (ternary).
1028
+ If it is not determined, raise ValueError.
1029
+ """
1030
+ cdef int8_t graphicness
1031
+ if self._dec != NULL:
1032
+ graphicness = CMRseymourGraphicness(self._dec)
1033
+ if graphicness:
1034
+ return graphicness > 0
1035
+ raise ValueError('It is not determined whether the decomposition node is graphic/network')
1036
+
1037
+ def _cographicness(self):
1038
+ r"""
1039
+ Return whether the decomposition node is cographic (binary) or conetwork (ternary).
1040
+ If it is not determined, raise ValueError.
1041
+ """
1042
+ cdef int8_t cographicness
1043
+ if self._dec != NULL:
1044
+ cographicness = CMRseymourCographicness(self._dec)
1045
+ if cographicness:
1046
+ return cographicness > 0
1047
+ raise ValueError('It is not determined whether the decomposition node is cographic/conetwork')
1048
+
1049
+ def _is_binary_linear_matroid_graphic(self, *, decomposition=False, **kwds):
1050
+ r"""
1051
+ Return whether the linear matroid of ``self`` over `\GF{2}` is graphic.
1052
+ If there is some entry not in `\{0, 1\}`, return ``False``.
1053
+
1054
+ This method is based on Seymour's decomposition.
1055
+ The decomposition will stop once nongraphicness is detected.
1056
+ For direct graphicness check,
1057
+
1058
+ .. SEEALSO::
1059
+
1060
+ - :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse._is_binary_linear_matroid_graphic`
1061
+ - :meth:`UnknownNode._is_binary_linear_matroid_graphic`
1062
+ - :meth:`_binary_linear_matroid_complete_decomposition`
1063
+
1064
+ EXAMPLES::
1065
+
1066
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
1067
+ sage: from sage.matrix.seymour_decomposition import DecompositionNode
1068
+ sage: M2 = Matrix_cmr_chr_sparse.one_sum([[1, 0], [1, 1], [0, 1]],
1069
+ ....: [[1, 1, 0], [0, 1, 1]])
1070
+ sage: result, certificate = M2.is_totally_unimodular(certificate=True,
1071
+ ....: row_keys=range(5),
1072
+ ....: column_keys='abcde')
1073
+ sage: node = DecompositionNode.one_sum(*certificate.child_nodes())
1074
+ sage: node._graphicness()
1075
+ Traceback (most recent call last):
1076
+ ...
1077
+ ValueError: It is not determined whether the decomposition node is graphic/network
1078
+ sage: result, decomposition = node._is_binary_linear_matroid_graphic(decomposition=True)
1079
+ sage: result
1080
+ True
1081
+ sage: unicode_art(decomposition)
1082
+ ╭──────────OneSumNode (5×5) with 2 children
1083
+ │ │
1084
+ PlanarNode (3×2) PlanarNode (2×3)
1085
+ sage: decomposition._graphicness()
1086
+ True
1087
+ sage: node._is_binary_linear_matroid_graphic(certificate=True)
1088
+ Traceback (most recent call last):
1089
+ ...
1090
+ NotImplementedError
1091
+
1092
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(GF(2), 9, 9, sparse=True),
1093
+ ....: [[1, 1, 0, 0, 0, 0, 0, 0, 0],
1094
+ ....: [1, 1, 1, 0, 0, 0, 0, 0, 0],
1095
+ ....: [1, 0, 0, 1, 0, 0, 0, 0, 0],
1096
+ ....: [0, 1, 1, 1, 0, 0, 0, 0, 0],
1097
+ ....: [0, 0, 1, 1, 0, 0, 0, 0, 0],
1098
+ ....: [0, 0, 0, 0, 1, 1, 1, 0, 0],
1099
+ ....: [0, 0, 0, 0, 1, 1, 0, 1, 0],
1100
+ ....: [0, 0, 0, 0, 0, 1, 0, 1, 1],
1101
+ ....: [0, 0, 0, 0, 0, 0, 1, 1, 1]])
1102
+ sage: result, certificate = M._is_binary_linear_matroid_regular(certificate=True,
1103
+ ....: row_keys=[f'r{i}' for i in range(9)],
1104
+ ....: column_keys=[f'c{i}' for i in range(9)])
1105
+ sage: node = DecompositionNode.one_sum(*certificate.child_nodes())
1106
+ sage: result, decomposition = node._is_binary_linear_matroid_graphic(decomposition=True)
1107
+ sage: result
1108
+ False
1109
+ sage: unicode_art(decomposition)
1110
+ ╭───────────OneSumNode (9×9) with 2 children
1111
+ │ │
1112
+ GraphicNode (5×4) UnknownNode (4×5)
1113
+ sage: decomposition.child_nodes()[1]._graphicness()
1114
+ False
1115
+ """
1116
+ certificate = kwds.get('certificate', False)
1117
+ try:
1118
+ result = self._graphicness()
1119
+ if not decomposition and not certificate:
1120
+ return result
1121
+ result = [result]
1122
+ if decomposition:
1123
+ result.append(self)
1124
+ if certificate:
1125
+ raise NotImplementedError
1126
+ return result
1127
+ except ValueError:
1128
+ # compute it... wait for CMR functions
1129
+ full_dec = self._binary_linear_matroid_complete_decomposition(
1130
+ stop_when_nongraphic=True,
1131
+ check_graphic_minors_planar=True)
1132
+ return full_dec._is_binary_linear_matroid_graphic(
1133
+ decomposition=decomposition,
1134
+ certificate=certificate)
1135
+
1136
+ def _is_binary_linear_matroid_cographic(self, *, decomposition=False, **kwds):
1137
+ r"""
1138
+ Return whether the linear matroid of ``self`` over `\GF{2}` is cographic.
1139
+ If there is some entry not in `\{0, 1\}`, return ``False``.
1140
+
1141
+ This method is based on Seymour's decomposition.
1142
+ The decomposition will stop once noncographicness is detected.
1143
+ For direct cographicness check,
1144
+
1145
+ .. SEEALSO::
1146
+
1147
+ - :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse._is_binary_linear_matroid_cographic`
1148
+ - :meth:`UnknownNode._is_binary_linear_matroid_cographic`
1149
+ - :meth:`_binary_linear_matroid_complete_decomposition`
1150
+
1151
+ EXAMPLES::
1152
+
1153
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
1154
+ sage: from sage.matrix.seymour_decomposition import DecompositionNode
1155
+ sage: M2 = Matrix_cmr_chr_sparse.one_sum([[1, 0], [1, 1], [0, 1]],
1156
+ ....: [[1, 1, 0], [0, 1, 1]])
1157
+ sage: result, certificate = M2.is_totally_unimodular(certificate=True,
1158
+ ....: row_keys=range(5),
1159
+ ....: column_keys='abcde')
1160
+ sage: node = DecompositionNode.one_sum(*certificate.child_nodes())
1161
+ sage: node._cographicness()
1162
+ Traceback (most recent call last):
1163
+ ...
1164
+ ValueError: It is not determined whether the decomposition node is cographic/conetwork
1165
+ sage: result, decomposition = node._is_binary_linear_matroid_cographic(decomposition=True)
1166
+ sage: result
1167
+ True
1168
+ sage: unicode_art(decomposition)
1169
+ ╭──────────OneSumNode (5×5) with 2 children
1170
+ │ │
1171
+ PlanarNode (3×2) PlanarNode (2×3)
1172
+ sage: decomposition._cographicness()
1173
+ True
1174
+ sage: node._is_binary_linear_matroid_cographic(certificate=True)
1175
+ Traceback (most recent call last):
1176
+ ...
1177
+ NotImplementedError
1178
+
1179
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(GF(2), 9, 9, sparse=True),
1180
+ ....: [[1, 1, 0, 0, 0, 0, 0, 0, 0],
1181
+ ....: [1, 1, 1, 0, 0, 0, 0, 0, 0],
1182
+ ....: [1, 0, 0, 1, 0, 0, 0, 0, 0],
1183
+ ....: [0, 1, 1, 1, 0, 0, 0, 0, 0],
1184
+ ....: [0, 0, 1, 1, 0, 0, 0, 0, 0],
1185
+ ....: [0, 0, 0, 0, 1, 1, 1, 0, 0],
1186
+ ....: [0, 0, 0, 0, 1, 1, 0, 1, 0],
1187
+ ....: [0, 0, 0, 0, 0, 1, 0, 1, 1],
1188
+ ....: [0, 0, 0, 0, 0, 0, 1, 1, 1]])
1189
+ sage: result, certificate = M._is_binary_linear_matroid_regular(certificate=True,
1190
+ ....: row_keys=[f'r{i}' for i in range(9)],
1191
+ ....: column_keys=[f'c{i}' for i in range(9)])
1192
+ sage: node = DecompositionNode.one_sum(*certificate.child_nodes())
1193
+ sage: result, decomposition = node._is_binary_linear_matroid_cographic(decomposition=True)
1194
+ sage: result
1195
+ False
1196
+ sage: unicode_art(decomposition)
1197
+ ╭───────────OneSumNode (9×9) with 2 children
1198
+ │ │
1199
+ GraphicNode (5×4) UnknownNode (4×5)
1200
+ """
1201
+ certificate = kwds.get('certificate', False)
1202
+ try:
1203
+ result = self._cographicness()
1204
+ if not decomposition and not certificate:
1205
+ return result
1206
+ result = [result]
1207
+ if decomposition:
1208
+ result.append(self)
1209
+ if certificate:
1210
+ raise NotImplementedError
1211
+ return result
1212
+ except ValueError:
1213
+ # compute it... wait for CMR functions
1214
+ full_dec = self._binary_linear_matroid_complete_decomposition(
1215
+ stop_when_noncographic=True,
1216
+ check_graphic_minors_planar=True)
1217
+ return full_dec._is_binary_linear_matroid_cographic(
1218
+ decomposition=decomposition,
1219
+ certificate=certificate)
1220
+
1221
+ def _is_binary_linear_matroid_regular(self, *, decomposition=False, **kwds):
1222
+ r"""
1223
+ Return whether the linear matroid of ``self`` over `\GF{2}` is regular.
1224
+ If there is some entry not in `\{0, 1\}`, return ``False``.
1225
+
1226
+ .. SEEALSO::
1227
+
1228
+ - :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse._is_binary_linear_matroid_regular`
1229
+ - :meth:`_binary_linear_matroid_complete_decomposition`
1230
+
1231
+ EXAMPLES::
1232
+
1233
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
1234
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(GF(2), 9, 9, sparse=True),
1235
+ ....: [[1, 1, 0, 0, 0, 0, 0, 0, 0],
1236
+ ....: [1, 1, 1, 0, 0, 0, 0, 0, 0],
1237
+ ....: [1, 0, 0, 1, 0, 0, 0, 0, 0],
1238
+ ....: [0, 1, 1, 1, 0, 0, 0, 0, 0],
1239
+ ....: [0, 0, 1, 1, 0, 0, 0, 0, 0],
1240
+ ....: [0, 0, 0, 0, 1, 1, 1, 0, 0],
1241
+ ....: [0, 0, 0, 0, 1, 1, 0, 1, 0],
1242
+ ....: [0, 0, 0, 0, 0, 1, 0, 1, 1],
1243
+ ....: [0, 0, 0, 0, 0, 0, 1, 1, 1]])
1244
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
1245
+ sage: node = UnknownNode(M)
1246
+ sage: node._regularity()
1247
+ Traceback (most recent call last):
1248
+ ...
1249
+ ValueError: It is not determined whether the decomposition node is regular/TU
1250
+ sage: node._is_binary_linear_matroid_regular()
1251
+ True
1252
+ sage: C0 = node._binary_linear_matroid_complete_decomposition()
1253
+ sage: C0
1254
+ OneSumNode (9×9) with 2 children
1255
+ sage: result, decomposition = C0._is_binary_linear_matroid_graphic(decomposition=True)
1256
+ sage: result
1257
+ False
1258
+ sage: unicode_art(decomposition)
1259
+ ╭───────────OneSumNode (9×9) with 2 children
1260
+ │ │
1261
+ GraphicNode (5×4) CographicNode (4×5)
1262
+ sage: decomposition.child_nodes()[1]._graphicness()
1263
+ False
1264
+ sage: result, decomposition = C0._is_binary_linear_matroid_cographic(decomposition=True)
1265
+ sage: result
1266
+ False
1267
+ sage: unicode_art(decomposition)
1268
+ ╭───────────OneSumNode (9×9) with 2 children
1269
+ │ │
1270
+ GraphicNode (5×4) UnknownNode (4×5)
1271
+ sage: decomposition.child_nodes()[1]._graphicness()
1272
+ Traceback (most recent call last):
1273
+ ...
1274
+ ValueError: It is not determined whether the decomposition node is graphic/network
1275
+ sage: result, decomposition = C0._is_binary_linear_matroid_regular(decomposition=True)
1276
+ sage: result
1277
+ True
1278
+ sage: unicode_art(decomposition)
1279
+ ╭───────────OneSumNode (9×9) with 2 children
1280
+ │ │
1281
+ GraphicNode (5×4) CographicNode (4×5)
1282
+ """
1283
+ certificate = kwds.get('certificate', False)
1284
+ try:
1285
+ result = self._regularity()
1286
+ if not decomposition and not certificate:
1287
+ return result
1288
+ result = [result]
1289
+ if decomposition:
1290
+ result.append(self)
1291
+ if certificate:
1292
+ raise NotImplementedError
1293
+ return result
1294
+ except ValueError:
1295
+ # compute it... wait for CMR functions
1296
+ full_dec = self._binary_linear_matroid_complete_decomposition(
1297
+ stop_when_irregular=True,
1298
+ check_graphic_minors_planar=True)
1299
+ return full_dec._is_binary_linear_matroid_regular(
1300
+ decomposition=decomposition,
1301
+ certificate=certificate)
1302
+
1303
+ def _binary_linear_matroid_complete_decomposition(self, *,
1304
+ time_limit=60.0,
1305
+ use_direct_graphicness_test=True,
1306
+ prefer_graphicness=True,
1307
+ series_parallel_ok=True,
1308
+ check_graphic_minors_planar=False,
1309
+ stop_when_irregular=False,
1310
+ stop_when_nongraphic=False,
1311
+ stop_when_noncographic=False,
1312
+ stop_when_nongraphic_and_noncographic=False,
1313
+ decompose_strategy='delta_three',
1314
+ construct_leaf_graphs=False,
1315
+ construct_all_graphs=False):
1316
+ r"""
1317
+ Complete the Seymour's decomposition of ``self`` over `\GF{2}`.
1318
+
1319
+ INPUT:
1320
+
1321
+ - ``stop_when_irregular`` -- boolean;
1322
+ whether to stop decomposing once irregularity is determined.
1323
+
1324
+ - ``stop_when_nongraphic`` -- boolean;
1325
+ whether to stop decomposing once non-graphicness is determined.
1326
+
1327
+ - ``stop_when_noncographic`` -- boolean;
1328
+ whether to stop decomposing once non-cographicness is determined.
1329
+
1330
+ - ``stop_when_nongraphic_and_noncographic`` -- boolean;
1331
+ whether to stop decomposing once non-graphicness and non-cographicness
1332
+ is determined.
1333
+
1334
+ For a description of other parameters, see
1335
+ :meth:`sage.matrix.matrix_cmr_sparse._set_cmr_seymour_parameters`
1336
+
1337
+ EXAMPLES::
1338
+
1339
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
1340
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(GF(2), 9, 9, sparse=True),
1341
+ ....: [[1, 1, 0, 0, 0, 0, 0, 0, 0],
1342
+ ....: [1, 1, 1, 0, 0, 0, 0, 0, 0],
1343
+ ....: [1, 0, 0, 1, 0, 0, 0, 0, 0],
1344
+ ....: [0, 1, 1, 1, 0, 0, 0, 0, 0],
1345
+ ....: [0, 0, 1, 1, 0, 0, 0, 0, 0],
1346
+ ....: [0, 0, 0, 0, 1, 1, 1, 0, 0],
1347
+ ....: [0, 0, 0, 0, 1, 1, 0, 1, 0],
1348
+ ....: [0, 0, 0, 0, 0, 1, 0, 1, 1],
1349
+ ....: [0, 0, 0, 0, 0, 0, 1, 1, 1]])
1350
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
1351
+ sage: node = UnknownNode(M); node
1352
+ UnknownNode (9×9)
1353
+ sage: C0 = node._binary_linear_matroid_complete_decomposition()
1354
+ sage: C0
1355
+ OneSumNode (9×9) with 2 children
1356
+ sage: unicode_art(C0)
1357
+ ╭───────────OneSumNode (9×9) with 2 children
1358
+ │ │
1359
+ GraphicNode (5×4) CographicNode (4×5)
1360
+ sage: C1, C2 = C0.child_nodes()
1361
+ sage: C11 = C1._binary_linear_matroid_complete_decomposition(); C11
1362
+ GraphicNode (5×4)
1363
+ sage: unicode_art(C11)
1364
+ GraphicNode (5×4)
1365
+ sage: C1.matrix()
1366
+ [1 1 0 0]
1367
+ [1 1 1 0]
1368
+ [0 1 1 1]
1369
+ [1 0 0 1]
1370
+ [0 0 1 1]
1371
+ sage: C11.matrix()
1372
+ [1 1 0 0]
1373
+ [1 1 1 0]
1374
+ [0 1 1 1]
1375
+ [1 0 0 1]
1376
+ [0 0 1 1]
1377
+ sage: C22 = C2._binary_linear_matroid_complete_decomposition(); C22
1378
+ CographicNode (4×5)
1379
+ sage: unicode_art(C22)
1380
+ CographicNode (4×5)
1381
+ sage: C2.matrix()
1382
+ [1 1 1 0 0]
1383
+ [0 0 1 1 1]
1384
+ [0 1 0 1 1]
1385
+ [1 1 0 1 0]
1386
+ sage: C22.matrix()
1387
+ [1 1 1 0 0]
1388
+ [0 0 1 1 1]
1389
+ [0 1 0 1 1]
1390
+ [1 1 0 1 0]
1391
+
1392
+ This is test ``TreeFlagsStopNoncographic`` in CMR's ``test_regular.cpp``.
1393
+ The default settings will not do the planarity check::
1394
+
1395
+ sage: unicode_art(node)
1396
+ UnknownNode (9×9)
1397
+ sage: certificate1 = node._binary_linear_matroid_complete_decomposition(
1398
+ ....: stop_when_noncographic=True,
1399
+ ....: check_graphic_minors_planar=True)
1400
+ sage: unicode_art(certificate1)
1401
+ ╭───────────OneSumNode (9×9) with 2 children
1402
+ │ │
1403
+ GraphicNode (5×4) UnknownNode (4×5)
1404
+ sage: certificate2 = node._binary_linear_matroid_complete_decomposition(
1405
+ ....: stop_when_noncographic=True)
1406
+ sage: unicode_art(certificate2)
1407
+ ╭───────────OneSumNode (9×9) with 2 children
1408
+ │ │
1409
+ GraphicNode (5×4) CographicNode (4×5)
1410
+
1411
+ sage: certificate1 = node._binary_linear_matroid_complete_decomposition(
1412
+ ....: stop_when_nongraphic=True,
1413
+ ....: check_graphic_minors_planar=True)
1414
+ sage: unicode_art(certificate1)
1415
+ ╭───────────OneSumNode (9×9) with 2 children
1416
+ │ │
1417
+ GraphicNode (5×4) UnknownNode (4×5)
1418
+ sage: C1, C2 = certificate1.child_nodes()
1419
+ sage: C1._cographicness()
1420
+ False
1421
+ sage: C2._graphicness()
1422
+ False
1423
+ sage: certificate2 = node._binary_linear_matroid_complete_decomposition(
1424
+ ....: stop_when_nongraphic=True)
1425
+ sage: unicode_art(certificate2)
1426
+ ╭───────────OneSumNode (9×9) with 2 children
1427
+ │ │
1428
+ GraphicNode (5×4) UnknownNode (4×5)
1429
+ sage: C1, C2 = certificate2.child_nodes()
1430
+ sage: C1._cographicness()
1431
+ Traceback (most recent call last):
1432
+ ...
1433
+ ValueError: It is not determined whether the decomposition node is cographic/conetwork
1434
+ sage: C2._graphicness()
1435
+ False
1436
+ """
1437
+ cdef CMR_REGULAR_PARAMS params
1438
+ cdef CMR_REGULAR_STATS stats
1439
+ cdef CMR_SEYMOUR_NODE *clone = NULL
1440
+
1441
+ cdef CMR_SEYMOUR_NODE **pclone = &clone
1442
+
1443
+ if self._dec == NULL:
1444
+ base_ring = self.base_ring()
1445
+ if base_ring is None:
1446
+ from sage.rings.finite_rings.finite_field_constructor import GF
1447
+ self._base_ring = GF(2)
1448
+ elif base_ring.characteristic() != 2:
1449
+ raise ValueError(f'only defined over binary, got {base_ring}')
1450
+ self._set_root_dec()
1451
+
1452
+ cdef dict kwds = dict(use_direct_graphicness_test=use_direct_graphicness_test,
1453
+ prefer_graphicness=prefer_graphicness,
1454
+ series_parallel_ok=series_parallel_ok,
1455
+ check_graphic_minors_planar=check_graphic_minors_planar,
1456
+ stop_when_irregular=stop_when_irregular,
1457
+ stop_when_nongraphic=stop_when_nongraphic,
1458
+ stop_when_noncographic=stop_when_noncographic,
1459
+ stop_when_nongraphic_and_noncographic=stop_when_nongraphic_and_noncographic,
1460
+ decompose_strategy=decompose_strategy,
1461
+ construct_leaf_graphs=construct_leaf_graphs,
1462
+ construct_all_graphs=construct_all_graphs)
1463
+ _set_cmr_seymour_parameters(&params.seymour, kwds)
1464
+
1465
+ sig_on()
1466
+ try:
1467
+ CMR_CALL(CMRseymourCloneUnknown(cmr, self._dec, pclone))
1468
+ CMR_CALL(CMRregularCompleteDecomposition(cmr, clone, &params, &stats, time_limit))
1469
+ finally:
1470
+ sig_off()
1471
+ node = create_DecompositionNode(clone, matrix=self.matrix(),
1472
+ row_keys=self.row_keys(),
1473
+ column_keys=self.column_keys(),
1474
+ base_ring=self.base_ring())
1475
+ return node
1476
+
1477
+ def is_network_matrix(self, *, decomposition=False, **kwds):
1478
+ r"""
1479
+ Return whether the matrix ``self`` over `\GF{3}` or `\QQ` is a network matrix.
1480
+ If there is some entry not in `\{-1, 0, 1\}`, return ``False``.
1481
+
1482
+ This method is based on Seymour's decomposition.
1483
+ The decomposition will stop once being nonnetwork is detected.
1484
+ For direct network matrix check,
1485
+
1486
+ .. SEEALSO::
1487
+
1488
+ - :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.is_network_matrix`
1489
+ - :meth:`UnknownNode.is_network_matrix`
1490
+ - :meth:`complete_decomposition`
1491
+
1492
+ EXAMPLES::
1493
+
1494
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
1495
+ sage: from sage.matrix.seymour_decomposition import DecompositionNode
1496
+ sage: A = matrix(ZZ, [[-1, 0, 0, 0, 1,-1, 0],
1497
+ ....: [ 1, 0, 0, 1,-1, 1, 0],
1498
+ ....: [ 0,-1, 0,-1, 1,-1, 0],
1499
+ ....: [ 0, 1, 0, 0, 0, 0, 1],
1500
+ ....: [ 0, 0, 1,-1, 1, 0, 1],
1501
+ ....: [ 0, 0,-1, 1,-1, 0, 0]])
1502
+ sage: M1 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 7, sparse=True), A)
1503
+ sage: M2 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 7, 6, sparse=True), A.transpose())
1504
+ sage: M = Matrix_cmr_chr_sparse.one_sum(M1, M2)
1505
+ sage: result, certificate = M.is_totally_unimodular(certificate=True,
1506
+ ....: row_keys=[f'r{i}' for i in range(13)],
1507
+ ....: column_keys=[f'c{i}' for i in range(13)])
1508
+ sage: node = DecompositionNode.one_sum(*certificate.child_nodes())
1509
+
1510
+ sage: result, decomposition = node.is_network_matrix(decomposition=True)
1511
+ sage: unicode_art(decomposition)
1512
+ ╭─────────OneSumNode (13×13) with 2 children
1513
+ │ │
1514
+ PlanarNode (6×7) PlanarNode (7×6)
1515
+ sage: node._graphicness()
1516
+ Traceback (most recent call last):
1517
+ ...
1518
+ ValueError: It is not determined whether the decomposition node is graphic/network
1519
+
1520
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 9, 9, sparse=True),
1521
+ ....: [[1, 1, 0, 0, 0, 0, 0, 0, 0],
1522
+ ....: [-1,-1, 1, 0, 0, 0, 0, 0, 0],
1523
+ ....: [1, 0, 0, 1, 0, 0, 0, 0, 0],
1524
+ ....: [0, 1,-1,-1, 0, 0, 0, 0, 0],
1525
+ ....: [0, 0, 1, 1, 0, 0, 0, 0, 0],
1526
+ ....: [0, 0, 0, 0, 1,-1, 1, 0, 0],
1527
+ ....: [0, 0, 0, 0, 1,-1, 0, 1, 0],
1528
+ ....: [0, 0, 0, 0, 0, 1, 0,-1, 1],
1529
+ ....: [0, 0, 0, 0, 0, 0, 1,-1, 1]])
1530
+ sage: result, certificate = M.is_totally_unimodular(certificate=True,
1531
+ ....: row_keys=[f'r{i}' for i in range(9)],
1532
+ ....: column_keys=[f'c{i}' for i in range(9)])
1533
+ sage: node = DecompositionNode.one_sum(*certificate.child_nodes())
1534
+ sage: result, decomposition = node.is_network_matrix(decomposition=True)
1535
+ sage: result
1536
+ False
1537
+ sage: unicode_art(decomposition)
1538
+ ╭───────────OneSumNode (9×9) with 2 children
1539
+ │ │
1540
+ GraphicNode (5×4) UnknownNode (4×5)
1541
+ sage: decomposition.child_nodes()[1]._graphicness()
1542
+ False
1543
+ """
1544
+ certificate = kwds.get('certificate', False)
1545
+ try:
1546
+ result = self._graphicness()
1547
+ if not decomposition and not certificate:
1548
+ return result
1549
+ result = [result]
1550
+ if decomposition:
1551
+ result.append(self)
1552
+ if certificate:
1553
+ raise NotImplementedError
1554
+ return result
1555
+ except ValueError:
1556
+ # compute it... wait for CMR functions
1557
+ full_dec = self.complete_decomposition(
1558
+ stop_when_nonnetwork=True,
1559
+ check_graphic_minors_planar=True)
1560
+ return full_dec.is_network_matrix(
1561
+ decomposition=decomposition,
1562
+ certificate=certificate)
1563
+
1564
+ def is_conetwork_matrix(self, *, decomposition=False, **kwds):
1565
+ r"""
1566
+ Return whether the matrix ``self`` over `\GF{3}` or `\QQ` is a conetwork matrix.
1567
+ If there is some entry not in `\{-1, 0, 1\}`, return ``False``.
1568
+
1569
+ This method is based on Seymour's decomposition.
1570
+ The decomposition will stop once being nonconetwork is detected.
1571
+ For direct conetwork matrix check,
1572
+
1573
+ .. SEEALSO::
1574
+
1575
+ - :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.is_conetwork_matrix`
1576
+ - :meth:`UnknownNode.is_conetwork_matrix`
1577
+ - :meth:`complete_decomposition`
1578
+
1579
+ EXAMPLES::
1580
+
1581
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
1582
+ sage: from sage.matrix.seymour_decomposition import DecompositionNode
1583
+ sage: A = matrix(ZZ, [[-1, 0, 0, 0, 1,-1, 0],
1584
+ ....: [ 1, 0, 0, 1,-1, 1, 0],
1585
+ ....: [ 0,-1, 0,-1, 1,-1, 0],
1586
+ ....: [ 0, 1, 0, 0, 0, 0, 1],
1587
+ ....: [ 0, 0, 1,-1, 1, 0, 1],
1588
+ ....: [ 0, 0,-1, 1,-1, 0, 0]])
1589
+ sage: M1 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 7, sparse=True), A)
1590
+ sage: M2 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 7, 6, sparse=True), A.transpose())
1591
+ sage: M = Matrix_cmr_chr_sparse.one_sum(M1, M2)
1592
+ sage: result, certificate = M.is_totally_unimodular(certificate=True,
1593
+ ....: row_keys=[f'r{i}' for i in range(13)],
1594
+ ....: column_keys=[f'c{i}' for i in range(13)])
1595
+ sage: node = DecompositionNode.one_sum(*certificate.child_nodes())
1596
+
1597
+ sage: result, decomposition = node.is_conetwork_matrix(decomposition=True)
1598
+ sage: unicode_art(decomposition)
1599
+ ╭─────────OneSumNode (13×13) with 2 children
1600
+ │ │
1601
+ PlanarNode (6×7) PlanarNode (7×6)
1602
+ sage: node._cographicness()
1603
+ Traceback (most recent call last):
1604
+ ...
1605
+ ValueError: It is not determined whether the decomposition node is cographic/conetwork
1606
+
1607
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 9, 9, sparse=True),
1608
+ ....: [[1, 1, 0, 0, 0, 0, 0, 0, 0],
1609
+ ....: [-1,-1, 1, 0, 0, 0, 0, 0, 0],
1610
+ ....: [1, 0, 0, 1, 0, 0, 0, 0, 0],
1611
+ ....: [0, 1,-1,-1, 0, 0, 0, 0, 0],
1612
+ ....: [0, 0, 1, 1, 0, 0, 0, 0, 0],
1613
+ ....: [0, 0, 0, 0, 1,-1, 1, 0, 0],
1614
+ ....: [0, 0, 0, 0, 1,-1, 0, 1, 0],
1615
+ ....: [0, 0, 0, 0, 0, 1, 0,-1, 1],
1616
+ ....: [0, 0, 0, 0, 0, 0, 1,-1, 1]])
1617
+ sage: result, certificate = M.is_totally_unimodular(certificate=True,
1618
+ ....: row_keys=[f'r{i}' for i in range(9)],
1619
+ ....: column_keys=[f'c{i}' for i in range(9)])
1620
+ sage: node = DecompositionNode.one_sum(*certificate.child_nodes())
1621
+ sage: result, decomposition = node.is_conetwork_matrix(decomposition=True)
1622
+ sage: result
1623
+ False
1624
+ sage: unicode_art(decomposition)
1625
+ ╭───────────OneSumNode (9×9) with 2 children
1626
+ │ │
1627
+ GraphicNode (5×4) UnknownNode (4×5)
1628
+ sage: decomposition.child_nodes()[1]._graphicness()
1629
+ Traceback (most recent call last):
1630
+ ...
1631
+ ValueError: It is not determined whether the decomposition node is graphic/network
1632
+ """
1633
+ certificate = kwds.get('certificate', False)
1634
+ try:
1635
+ result = self._cographicness()
1636
+ if not decomposition and not certificate:
1637
+ return result
1638
+ result = [result]
1639
+ if decomposition:
1640
+ result.append(self)
1641
+ if certificate:
1642
+ raise NotImplementedError
1643
+ return result
1644
+ except ValueError:
1645
+ # compute it... wait for CMR functions
1646
+ full_dec = self.complete_decomposition(
1647
+ stop_when_nonconetwork=True,
1648
+ check_graphic_minors_planar=True)
1649
+ return full_dec.is_conetwork_matrix(
1650
+ decomposition=decomposition,
1651
+ certificate=certificate)
1652
+
1653
+ def is_totally_unimodular(self, *, decomposition=False, **kwds):
1654
+ r"""
1655
+ Return whether ``self`` is a totally unimodular matrix.
1656
+
1657
+ A matrix is totally unimodular if every subdeterminant is `0`, `1`, or `-1`.
1658
+
1659
+ .. SEEALSO::
1660
+
1661
+ - :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.is_totally_unimodular`
1662
+ - :meth:`complete_decomposition`
1663
+
1664
+ EXAMPLES::
1665
+
1666
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
1667
+ sage: from sage.matrix.seymour_decomposition import DecompositionNode
1668
+ sage: A = matrix(ZZ, [[-1, 0, 0, 0, 1,-1, 0],
1669
+ ....: [ 1, 0, 0, 1,-1, 1, 0],
1670
+ ....: [ 0,-1, 0,-1, 1,-1, 0],
1671
+ ....: [ 0, 1, 0, 0, 0, 0, 1],
1672
+ ....: [ 0, 0, 1,-1, 1, 0, 1],
1673
+ ....: [ 0, 0,-1, 1,-1, 0, 0]])
1674
+ sage: M1 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 7, sparse=True), A)
1675
+ sage: M2 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 7, 6, sparse=True), A.transpose())
1676
+ sage: M = Matrix_cmr_chr_sparse.one_sum(M1, M2)
1677
+ sage: result, certificate = M.is_totally_unimodular(certificate=True,
1678
+ ....: row_keys=[f'r{i}' for i in range(13)],
1679
+ ....: column_keys=[f'c{i}' for i in range(13)])
1680
+ sage: node = DecompositionNode.one_sum(*certificate.child_nodes())
1681
+
1682
+ sage: result, decomposition = node.is_totally_unimodular(decomposition=True)
1683
+ sage: unicode_art(decomposition)
1684
+ ╭─────────OneSumNode (13×13) with 2 children
1685
+ │ │
1686
+ PlanarNode (6×7) PlanarNode (7×6)
1687
+ sage: node._regularity()
1688
+ Traceback (most recent call last):
1689
+ ...
1690
+ ValueError: It is not determined whether the decomposition node is regular/TU
1691
+
1692
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 9, 9, sparse=True),
1693
+ ....: [[1, 1, 0, 0, 0, 0, 0, 0, 0],
1694
+ ....: [-1,-1, 1, 0, 0, 0, 0, 0, 0],
1695
+ ....: [1, 0, 0, 1, 0, 0, 0, 0, 0],
1696
+ ....: [0, 1,-1,-1, 0, 0, 0, 0, 0],
1697
+ ....: [0, 0, 1, 1, 0, 0, 0, 0, 0],
1698
+ ....: [0, 0, 0, 0, 1,-1, 1, 0, 0],
1699
+ ....: [0, 0, 0, 0, 1,-1, 0, 1, 0],
1700
+ ....: [0, 0, 0, 0, 0, 1, 0,-1, 1],
1701
+ ....: [0, 0, 0, 0, 0, 0, 1,-1, 1]])
1702
+ sage: result, certificate = M.is_totally_unimodular(certificate=True,
1703
+ ....: row_keys=[f'r{i}' for i in range(9)],
1704
+ ....: column_keys=[f'c{i}' for i in range(9)])
1705
+ sage: node = DecompositionNode.one_sum(*certificate.child_nodes())
1706
+ sage: result, decomposition = node.is_totally_unimodular(decomposition=True)
1707
+ sage: result
1708
+ True
1709
+ sage: unicode_art(decomposition)
1710
+ ╭───────────OneSumNode (9×9) with 2 children
1711
+ │ │
1712
+ GraphicNode (5×4) CographicNode (4×5)
1713
+ """
1714
+ certificate = kwds.get('certificate', False)
1715
+ try:
1716
+ result = self._regularity()
1717
+ if not decomposition and not certificate:
1718
+ return result
1719
+ result = [result]
1720
+ if decomposition:
1721
+ result.append(self)
1722
+ if certificate:
1723
+ raise NotImplementedError
1724
+ return result
1725
+ except ValueError:
1726
+ # compute it... wait for CMR functions
1727
+ full_dec = self.complete_decomposition(
1728
+ stop_when_nonTU=True,
1729
+ check_graphic_minors_planar=True)
1730
+ return full_dec.is_totally_unimodular(
1731
+ decomposition=decomposition,
1732
+ certificate=certificate)
1733
+
1734
+ def nminors(self):
1735
+ r"""
1736
+ Return the number of minors of the node.
1737
+ """
1738
+ if self._minors is not None:
1739
+ return len(self._minors)
1740
+ if self._dec == NULL:
1741
+ return 0
1742
+ return CMRseymourNumMinors(self._dec)
1743
+
1744
+ def _create_minor(self, index):
1745
+ r"""
1746
+ A minor of a represented matroid is another one obtained
1747
+ by deleting or contracting elements. In the reduced matrix representation,
1748
+ an element associated with a row (resp. column) can be contracted (resp. deleted)
1749
+ by removing the corresponding matrix row (resp. column).
1750
+ In order to delete a row element or contract a column element,
1751
+ one must first pivot such that the element type (row/column) is changed.
1752
+
1753
+ A minor of a matroid represented by matrix `M` is represented
1754
+ by means of a ``CMR_MINOR`` object.
1755
+ It consists of a (potentially empty) array of pivots and a ``CMR_SUBMAT`` object
1756
+ indicating a submatrix `M'` of the matrix obtained from `M` after applying the pivots.
1757
+ Moreover, the type field indicates a certain structure of `M'`:
1758
+
1759
+ - A determinant ``CMR_MINOR_TYPE_DETERMINANT``
1760
+ indicates that `M'` is a submatrix of `M` with `|\det(M')| \geq 2`.
1761
+ In particular, no pivots are applied.
1762
+ - A Fano ``CMR_MINOR_TYPE_FANO``
1763
+ indicates that `M'` represents the Fano matroid `F_7`.
1764
+ - A Fano-dual ``CMR_MINOR_TYPE_FANO_DUAL``
1765
+ indicates that `M'` represents the dual `F_7^\star` of the Fano matroid.
1766
+ - A K5 ``CMR_MINOR_TYPE_K5``
1767
+ indicates that `M'` represents the graphic matroid `M(K_5)` of the complete graph `K_5`.
1768
+ - A K5-dual ``CMR_MINOR_TYPE_K5_DUAL``
1769
+ indicates that `M'` represents the dual matroid `M(K_5)^\star` of the complete graph `K_5`.
1770
+ - A K33 ``CMR_MINOR_TYPE_K33``
1771
+ indicates that `M'` represents the graphic matroid `M(K_{3,3})`
1772
+ of the complete bipartite graph `K_{3,3}`.
1773
+ - A K33-dual ``CMR_MINOR_TYPE_K33_DUAL``
1774
+ indicates that `M'` represents the dual matroid `M(K_{3,3})^\star`
1775
+ of the complete bipartite graph `K_{3,3}`.
1776
+ """
1777
+ cdef CMR_MINOR * minor = CMRseymourMinor(self._dec, index)
1778
+ cdef CMR_MINOR_TYPE typ = CMRminorType(minor)
1779
+ cdef size_t npivots = CMRminorNumPivots(minor)
1780
+ cdef size_t *pivot_rows = CMRminorPivotRows(minor)
1781
+ cdef size_t *pivot_columns = CMRminorPivotColumns(minor)
1782
+ cdef CMR_SUBMAT *submat = CMRminorSubmatrix(minor)
1783
+ # TODO: consider the pivots information and the corresponding keys?
1784
+ pivots_tuple = tuple((pivot_rows[i], pivot_columns[i]) for i in range(npivots))
1785
+ submat_tuple = (tuple(submat.rows[i] for i in range(submat.numRows)),
1786
+ tuple(submat.columns[i] for i in range(submat.numColumns)))
1787
+ import sage.matroids.matroids_catalog as matroids
1788
+ from sage.graphs.graph_generators import graphs
1789
+ from sage.matroids.matroid import Matroid
1790
+
1791
+ if typ == CMR_MINOR_TYPE_FANO:
1792
+ return matroids.catalog.Fano()
1793
+ if typ == CMR_MINOR_TYPE_FANO_DUAL:
1794
+ return matroids.catalog.Fano().dual()
1795
+ if typ == CMR_MINOR_TYPE_K5:
1796
+ return matroids.CompleteGraphic(5)
1797
+ if typ == CMR_MINOR_TYPE_K5_DUAL:
1798
+ return matroids.CompleteGraphic(5).dual()
1799
+ if typ == CMR_MINOR_TYPE_K33:
1800
+ E = 'abcdefghi'
1801
+ G = graphs.CompleteBipartiteGraph(3, 3)
1802
+ return Matroid(groundset=E, graph=G, regular=True)
1803
+ if typ == CMR_MINOR_TYPE_K33_DUAL:
1804
+ return matroids.catalog.K33dual()
1805
+ if typ == CMR_MINOR_TYPE_DETERMINANT:
1806
+ return '|det| = 2 submatrix', submat_tuple
1807
+ if typ == CMR_MINOR_TYPE_ENTRY:
1808
+ return 'bad entry'
1809
+ if typ == CMR_MINOR_TYPE_CUSTOM:
1810
+ return 'custom'
1811
+
1812
+ def minors(self):
1813
+ r"""
1814
+ Return a tuple of minors of ``self``.
1815
+
1816
+ Currently, it only handles determinant 2 submatrix.
1817
+
1818
+ EXAMPLES::
1819
+
1820
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
1821
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 9, 9, sparse=True),
1822
+ ....: [[1, 1, 0, 0, 0, 0, 0, 0, 0],
1823
+ ....: [1, 1, 1, 0, 0, 0, 0, 0, 0],
1824
+ ....: [1, 0, 0, 1, 0, 0, 0, 0, 0],
1825
+ ....: [0, 1, 1, 1, 0, 0, 0, 0, 0],
1826
+ ....: [0, 0, 1, 1, 0, 0, 0, 0, 0],
1827
+ ....: [0, 0, 0, 0, 1, 1, 1, 0, 0],
1828
+ ....: [0, 0, 0, 0, 1, 1, 0, 1, 0],
1829
+ ....: [0, 0, 0, 0, 0, 1, 0, 1, 1],
1830
+ ....: [0, 0, 0, 0, 0, 0, 1, 1, 1]])
1831
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
1832
+ sage: C0 = UnknownNode(M).complete_decomposition()
1833
+ sage: C1, C2 = C0.child_nodes()
1834
+ sage: C1.minors()
1835
+ (('|det| = 2 submatrix', ((4, 3, 1), (2, 3, 0))),)
1836
+ sage: C2.minors()
1837
+ (('|det| = 2 submatrix', ((2, 1, 0), (4, 2, 1))),)
1838
+ """
1839
+ if self._minors is not None:
1840
+ return self._minors
1841
+ minors_tuple = tuple(self._create_minor(index)
1842
+ for index in range(self.nminors()))
1843
+ self._minors = minors_tuple
1844
+ return self._minors
1845
+
1846
+ def complete_decomposition(self, *, time_limit=60.0,
1847
+ use_direct_graphicness_test=True,
1848
+ prefer_graphicness=True,
1849
+ series_parallel_ok=True,
1850
+ check_graphic_minors_planar=False,
1851
+ stop_when_nonTU=False,
1852
+ stop_when_nonnetwork=False,
1853
+ stop_when_nonconetwork=False,
1854
+ stop_when_nonnetwork_and_nonconetwork=False,
1855
+ decompose_strategy='delta_three',
1856
+ construct_leaf_graphs=False,
1857
+ construct_all_graphs=False):
1858
+ r"""
1859
+ Complete the Seymour's decomposition of ``self`` over `\GF{3}` or `\ZZ`.
1860
+
1861
+ INPUT:
1862
+
1863
+ - ``stop_when_nonTU`` -- boolean;
1864
+ whether to stop decomposing once being non-TU is determined.
1865
+
1866
+ - ``stop_when_nonnetwork`` -- boolean;
1867
+ whether to stop decomposing once being non-network is determined.
1868
+
1869
+ - ``stop_when_nonconetwork`` -- boolean;
1870
+ whether to stop decomposing once being non-conetwork is determined.
1871
+
1872
+ - ``stop_when_nonnetwork_and_nonconetwork`` -- boolean;
1873
+ whether to stop decomposing once not being network
1874
+ and not being conetwork is determined.
1875
+
1876
+ For a description of other parameters, see
1877
+ :meth:`sage.matrix.matrix_cmr_sparse._set_cmr_seymour_parameters`.
1878
+
1879
+ EXAMPLES::
1880
+
1881
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
1882
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 9, 9, sparse=True),
1883
+ ....: [[1, 1, 0, 0, 0, 0, 0, 0, 0],
1884
+ ....: [1, 1, 1, 0, 0, 0, 0, 0, 0],
1885
+ ....: [1, 0, 0, 1, 0, 0, 0, 0, 0],
1886
+ ....: [0, 1, 1, 1, 0, 0, 0, 0, 0],
1887
+ ....: [0, 0, 1, 1, 0, 0, 0, 0, 0],
1888
+ ....: [0, 0, 0, 0, 1, 1, 1, 0, 0],
1889
+ ....: [0, 0, 0, 0, 1, 1, 0, 1, 0],
1890
+ ....: [0, 0, 0, 0, 0, 1, 0, 1, 1],
1891
+ ....: [0, 0, 0, 0, 0, 0, 1, 1, 1]])
1892
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
1893
+ sage: node = UnknownNode(M); node
1894
+ UnknownNode (9×9)
1895
+ sage: C0 = node.complete_decomposition()
1896
+ sage: unicode_art(C0)
1897
+ ╭OneSumNode (9×9) with 2 children─╮
1898
+ │ │
1899
+ ThreeConnectedIrregularNode (5×4) ThreeConnectedIrregularNode (4×5)
1900
+ sage: C1, C2 = C0.child_nodes()
1901
+ sage: C11 = C1.complete_decomposition(); C11
1902
+ ThreeConnectedIrregularNode (5×4)
1903
+ sage: unicode_art(C11)
1904
+ ThreeConnectedIrregularNode (5×4)
1905
+ sage: C1.matrix()
1906
+ [1 1 0 0]
1907
+ [1 1 1 0]
1908
+ [0 1 1 1]
1909
+ [1 0 0 1]
1910
+ [0 0 1 1]
1911
+ sage: C11.matrix()
1912
+ [1 1 0 0]
1913
+ [1 1 1 0]
1914
+ [0 1 1 1]
1915
+ [1 0 0 1]
1916
+ [0 0 1 1]
1917
+ sage: C22 = C2.complete_decomposition(); C22
1918
+ ThreeConnectedIrregularNode (4×5)
1919
+ sage: unicode_art(C22)
1920
+ ThreeConnectedIrregularNode (4×5)
1921
+ sage: C2.matrix()
1922
+ [1 1 1 0 0]
1923
+ [0 0 1 1 1]
1924
+ [0 1 0 1 1]
1925
+ [1 1 0 1 0]
1926
+ sage: C22.matrix()
1927
+ [1 1 1 0 0]
1928
+ [0 0 1 1 1]
1929
+ [0 1 0 1 1]
1930
+ [1 1 0 1 0]
1931
+ """
1932
+ cdef CMR_TU_PARAMS params
1933
+ cdef CMR_TU_STATS stats
1934
+ cdef CMR_SEYMOUR_NODE *clone = NULL
1935
+
1936
+ cdef CMR_SEYMOUR_NODE **pclone = &clone
1937
+
1938
+ if self._dec == NULL:
1939
+ if self.base_ring() is None:
1940
+ self._base_ring = ZZ
1941
+ self._set_root_dec()
1942
+
1943
+ cdef dict kwds = dict(use_direct_graphicness_test=use_direct_graphicness_test,
1944
+ prefer_graphicness=prefer_graphicness,
1945
+ series_parallel_ok=series_parallel_ok,
1946
+ check_graphic_minors_planar=check_graphic_minors_planar,
1947
+ stop_when_irregular=stop_when_nonTU,
1948
+ stop_when_nongraphic=stop_when_nonnetwork,
1949
+ stop_when_noncographic=stop_when_nonconetwork,
1950
+ stop_when_nongraphic_and_noncographic=stop_when_nonnetwork_and_nonconetwork,
1951
+ decompose_strategy=decompose_strategy,
1952
+ construct_leaf_graphs=construct_leaf_graphs,
1953
+ construct_all_graphs=construct_all_graphs)
1954
+ params.algorithm = CMR_TU_ALGORITHM_DECOMPOSITION
1955
+ params.ternary = True
1956
+ params.camionFirst = False
1957
+ _set_cmr_seymour_parameters(&params.seymour, kwds)
1958
+
1959
+ sig_on()
1960
+ try:
1961
+ CMR_CALL(CMRseymourCloneUnknown(cmr, self._dec, pclone))
1962
+ CMR_CALL(CMRtuCompleteDecomposition(cmr, clone, &params, &stats, time_limit))
1963
+ finally:
1964
+ sig_off()
1965
+ node = create_DecompositionNode(clone, matrix=self.matrix(),
1966
+ row_keys=self.row_keys(),
1967
+ column_keys=self.column_keys(),
1968
+ base_ring=self.base_ring())
1969
+ return node
1970
+
1971
+
1972
+ cdef class ThreeConnectedIrregularNode(DecompositionNode):
1973
+
1974
+ pass
1975
+
1976
+
1977
+ cdef class UnknownNode(DecompositionNode):
1978
+ r"""
1979
+ EXAMPLES::
1980
+
1981
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
1982
+ sage: node = UnknownNode([[1, 1], [0, 1]]); node
1983
+ UnknownNode (2×2)
1984
+ sage: node.matrix()
1985
+ [1 1]
1986
+ [0 1]
1987
+ sage: node = UnknownNode(matrix(ZZ, [[1, 0, 1], [0, 1, 1]])); node
1988
+ UnknownNode (2×3)
1989
+ sage: node.matrix()
1990
+ [1 0 1]
1991
+ [0 1 1]
1992
+ sage: node = UnknownNode(matrix(ZZ, [[1, 0, 1], [0, 1, 1]]),
1993
+ ....: row_keys='ab',
1994
+ ....: column_keys=range(3)); node
1995
+ UnknownNode (2×3)
1996
+ sage: node.matrix()
1997
+ [1 0 1]
1998
+ [0 1 1]
1999
+ sage: node.morphism()._unicode_art_matrix()
2000
+ 0 1 2
2001
+ a⎛1 0 1⎞
2002
+ b⎝0 1 1⎠
2003
+
2004
+ From a module morphism::
2005
+
2006
+ sage: phi = matrix(ZZ, [[1, 0, 1], [0, 1, 1]],
2007
+ ....: row_keys='ab', column_keys=range(3)); phi
2008
+ Generic morphism:
2009
+ From: Free module generated by {0, 1, 2} over Integer Ring
2010
+ To: Free module generated by {'a', 'b'} over Integer Ring
2011
+ sage: node = UnknownNode(phi); node
2012
+ UnknownNode (2×3)
2013
+ sage: node.matrix()
2014
+ [1 0 1]
2015
+ [0 1 1]
2016
+ """
2017
+
2018
+ def _is_binary_linear_matroid_graphic(self, *, decomposition=False, certificate=False, **kwds):
2019
+ r"""
2020
+ Return whether the linear matroid of ``self`` over `\GF{2}` is graphic.
2021
+ If there is some entry not in `\{0, 1\}`, return ``False``.
2022
+
2023
+ This is an internal method because it should really be exposed
2024
+ as a method of :class:`Matroid`.
2025
+
2026
+ .. SEEALSO::
2027
+
2028
+ :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse._is_binary_linear_matroid_graphic`
2029
+
2030
+ EXAMPLES::
2031
+
2032
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
2033
+ sage: node = UnknownNode([[1, 0], [1, 1], [0, 1]]); node
2034
+ UnknownNode (3×2)
2035
+ sage: node.matrix()
2036
+ [1 0]
2037
+ [1 1]
2038
+ [0 1]
2039
+ sage: node._is_binary_linear_matroid_graphic()
2040
+ True
2041
+ sage: result, certificate = node._is_binary_linear_matroid_graphic(certificate=True)
2042
+ sage: graph, forest_edges, coforest_edges = certificate
2043
+ sage: graph.vertices(sort=True) # the numbers have no meaning
2044
+ [1, 2, 7, 12]
2045
+ sage: graph.edges(sort=True, labels=False)
2046
+ [(1, 2), (1, 7), (1, 12), (2, 7), (7, 12)]
2047
+ sage: forest_edges # indexed by rows of M
2048
+ ((1, 2), (7, 1), (12, 7))
2049
+ sage: coforest_edges # indexed by cols of M
2050
+ ((2, 7), (1, 12))
2051
+ """
2052
+ matrix = self.matrix()
2053
+ if not decomposition and not certificate:
2054
+ return matrix._is_binary_linear_matroid_graphic(**kwds)
2055
+ result, cert = matrix._is_binary_linear_matroid_graphic(certificate=True,
2056
+ row_keys=self.row_keys(),
2057
+ column_keys=self.column_keys(), **kwds)
2058
+ result = [result]
2059
+ if decomposition:
2060
+ graph, forest_edges, coforest_edges = cert
2061
+ node = GraphicNode(matrix, graph, forest_edges, coforest_edges)
2062
+ result.append(node)
2063
+ if certificate:
2064
+ result.append(cert)
2065
+ return result
2066
+
2067
+ def _is_binary_linear_matroid_cographic(self, *, decomposition=False, certificate=False, **kwds):
2068
+ r"""
2069
+ Return whether the linear matroid of ``self`` over `\GF{2}` is cographic.
2070
+ If there is some entry not in `\{0, 1\}`, return ``False``.
2071
+
2072
+ This is an internal method because it should really be exposed
2073
+ as a method of :class:`Matroid`.
2074
+
2075
+ .. SEEALSO::
2076
+
2077
+ :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse._is_binary_linear_matroid_cographic`
2078
+
2079
+ EXAMPLES::
2080
+
2081
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
2082
+ sage: node = UnknownNode([[1, 1, 0], [0, 1, 1]]); node
2083
+ UnknownNode (2×3)
2084
+ sage: node.matrix()
2085
+ [1 1 0]
2086
+ [0 1 1]
2087
+ sage: node._is_binary_linear_matroid_cographic()
2088
+ True
2089
+ sage: result, certificate = node._is_binary_linear_matroid_cographic(certificate=True)
2090
+ sage: graph, forest_edges, coforest_edges = certificate
2091
+ sage: graph.vertices(sort=True) # the numbers have no meaning
2092
+ [1, 2, 7, 12]
2093
+ sage: graph.edges(sort=True, labels=False)
2094
+ [(1, 2), (1, 7), (1, 12), (2, 7), (7, 12)]
2095
+ sage: forest_edges # indexed by rows of M
2096
+ ((1, 2), (7, 1))
2097
+ sage: coforest_edges # indexed by cols of M
2098
+ ((2, 7), (1, 12), (1, 2))
2099
+ """
2100
+ matrix = self.matrix()
2101
+ if not decomposition and not certificate:
2102
+ return matrix._is_binary_linear_matroid_cographic(**kwds)
2103
+ result, cert = matrix._is_binary_linear_matroid_cographic(certificate=True,
2104
+ row_keys=self.row_keys(),
2105
+ column_keys=self.column_keys(), **kwds)
2106
+ result = [result]
2107
+ if decomposition:
2108
+ graph, forest_edges, coforest_edges = cert
2109
+ node = CographicNode(matrix, graph, forest_edges, coforest_edges)
2110
+ result.append(node)
2111
+ if certificate:
2112
+ result.append(cert)
2113
+ return result
2114
+
2115
+ def is_network_matrix(self, *, decomposition=False, certificate=False, **kwds):
2116
+ r"""
2117
+ Return whether the matrix ``self`` over `\GF{3}` or `\QQ` is a network matrix.
2118
+ If there is some entry not in `\{-1, 0, 1\}`, return ``False``.
2119
+
2120
+ .. SEEALSO::
2121
+
2122
+ :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.is_network_matrix`
2123
+
2124
+ EXAMPLES::
2125
+
2126
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
2127
+ sage: node = UnknownNode([[1, 0], [-1, 1], [0, -1]]); node
2128
+ UnknownNode (3×2)
2129
+ sage: node.matrix()
2130
+ [ 1 0]
2131
+ [-1 1]
2132
+ [ 0 -1]
2133
+ sage: node.is_network_matrix()
2134
+ True
2135
+ sage: result, certificate = node.is_network_matrix(certificate=True)
2136
+ sage: graph, forest_edges, coforest_edges = certificate
2137
+ sage: graph
2138
+ Digraph on 4 vertices
2139
+ sage: graph.vertices(sort=True) # the numbers have no meaning
2140
+ [1, 2, 7, 12]
2141
+ sage: graph.edges(sort=True, labels=False)
2142
+ [(2, 1), (2, 7), (7, 1), (7, 12), (12, 1)]
2143
+ sage: forest_edges # indexed by rows of M
2144
+ ((2, 1), (7, 1), (7, 12))
2145
+ sage: coforest_edges # indexed by cols of M
2146
+ ((2, 7), (12, 1))
2147
+ """
2148
+ matrix = self.matrix()
2149
+ if not decomposition and not certificate:
2150
+ return matrix.is_network_matrix(**kwds)
2151
+ result, cert = matrix.is_network_matrix(certificate=True,
2152
+ row_keys=self.row_keys(),
2153
+ column_keys=self.column_keys(), **kwds)
2154
+ result = [result]
2155
+ if decomposition:
2156
+ graph, forest_edges, coforest_edges = cert
2157
+ node = GraphicNode(matrix, graph, forest_edges, coforest_edges)
2158
+ result.append(node)
2159
+ if certificate:
2160
+ result.append(cert)
2161
+ return result
2162
+
2163
+ def is_conetwork_matrix(self, *, decomposition=False, certificate=False, **kwds):
2164
+ r"""
2165
+ Return whether the matrix ``self`` over `\GF{3}` or `\QQ` is a conetwork matrix.
2166
+ If there is some entry not in `\{-1, 0, 1\}`, return ``False``.
2167
+
2168
+ .. SEEALSO::
2169
+
2170
+ :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.is_conetwork_matrix`
2171
+
2172
+ EXAMPLES::
2173
+
2174
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
2175
+ sage: node = UnknownNode([[1, -1, 0], [0, 1, -1]]); node
2176
+ UnknownNode (2×3)
2177
+ sage: node.matrix()
2178
+ [ 1 -1 0]
2179
+ [ 0 1 -1]
2180
+ sage: node.is_conetwork_matrix()
2181
+ True
2182
+ sage: result, certificate = node.is_conetwork_matrix(certificate=True)
2183
+ sage: graph, forest_edges, coforest_edges = certificate
2184
+ sage: graph
2185
+ Digraph on 4 vertices
2186
+ sage: graph.vertices(sort=True) # the numbers have no meaning
2187
+ [1, 2, 7, 12]
2188
+ sage: graph.edges(sort=True, labels=False)
2189
+ [(2, 1), (2, 7), (7, 1), (7, 12), (12, 1)]
2190
+ sage: forest_edges # indexed by cols of M
2191
+ ((2, 1), (7, 1), (7, 12))
2192
+ sage: coforest_edges # indexed by rows of M
2193
+ ((2, 7), (12, 1))
2194
+ """
2195
+ matrix = self.matrix()
2196
+ if not decomposition and not certificate:
2197
+ return matrix.is_conetwork_matrix(**kwds)
2198
+ result, cert = matrix.is_conetwork_matrix(certificate=True,
2199
+ row_keys=self.row_keys(),
2200
+ column_keys=self.column_keys(), **kwds)
2201
+ result = [result]
2202
+ if decomposition:
2203
+ graph, forest_edges, coforest_edges = cert
2204
+ node = CographicNode(matrix, graph, forest_edges, coforest_edges)
2205
+ result.append(node)
2206
+ if certificate:
2207
+ result.append(cert)
2208
+ return result
2209
+
2210
+
2211
+ cdef class SumNode(DecompositionNode):
2212
+ r"""
2213
+ Base class for 1-sum, 2-sum, and 3-sums (`\Delta`-sum, 3-sum, Y-sum) nodes
2214
+ in Seymour's decomposition
2215
+ """
2216
+
2217
+ def _repr_(self):
2218
+ r"""
2219
+ Return a string representation of ``self``.
2220
+
2221
+ EXAMPLES::
2222
+
2223
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
2224
+ sage: M2 = Matrix_cmr_chr_sparse.one_sum([[1, 0], [-1, 1]],
2225
+ ....: [[1, 1], [-1, 0]])
2226
+ sage: result, certificate = M2.is_totally_unimodular(certificate=True,
2227
+ ....: row_keys=range(4),
2228
+ ....: column_keys='abcd')
2229
+ sage: print(certificate)
2230
+ OneSumNode (4×4) with 2 children
2231
+ """
2232
+ result = super()._repr_()
2233
+ if isinstance(self, OneSumNode):
2234
+ result += f' with {self.nchildren()} children'
2235
+ return result
2236
+
2237
+ def permuted_block_matrix(self):
2238
+ r"""
2239
+ Return the permutation matrices between the matrix
2240
+ and the block matrix form.
2241
+
2242
+ OUTPUT: a tuple ``(Prow, BlockMatrix, Pcolumn)``, where
2243
+ ``self.matrix() == Prow * BlockMatrix * Pcolumn``, and
2244
+ ``BlockMatrix == self.block_matrix_form()``.
2245
+
2246
+ EXAMPLES::
2247
+
2248
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
2249
+
2250
+ sage: M = Matrix_cmr_chr_sparse.one_sum([[1, 0], [-1, 1]],
2251
+ ....: [[1, 1], [-1, 0]],
2252
+ ....: [[1, 0], [0, 1]]); M
2253
+ [ 1 0| 0 0| 0 0]
2254
+ [-1 1| 0 0| 0 0]
2255
+ [-----+-----+-----]
2256
+ [ 0 0| 1 1| 0 0]
2257
+ [ 0 0|-1 0| 0 0]
2258
+ [-----+-----+-----]
2259
+ [ 0 0| 0 0| 1 0]
2260
+ [ 0 0| 0 0| 0 1]
2261
+ sage: M_perm = M.matrix_from_rows_and_columns([2, 4, 3, 0, 5, 1],
2262
+ ....: [0, 1, 3, 5, 4, 2]); M_perm
2263
+ [ 0 0 1 0 0 1]
2264
+ [ 0 0 0 0 1 0]
2265
+ [ 0 0 0 0 0 -1]
2266
+ [ 1 0 0 0 0 0]
2267
+ [ 0 0 0 1 0 0]
2268
+ [-1 1 0 0 0 0]
2269
+ sage: result, certificate = M_perm.is_totally_unimodular(certificate=True)
2270
+ sage: certificate
2271
+ OneSumNode (6×6) with 4 children
2272
+ sage: P_row, block_matrix, P_column = certificate.permuted_block_matrix()
2273
+ sage: P_row^(-1) * M_perm * P_column^(-1) == block_matrix
2274
+ True
2275
+ """
2276
+ from sage.combinat.permutation import Permutation
2277
+ cdef CMR_SEYMOUR_NODE *child_dec
2278
+ cdef CMR_ELEMENT *parent_rows
2279
+ cdef CMR_ELEMENT *parent_columns
2280
+ children_row = tuple()
2281
+ children_column = tuple()
2282
+ for index in range(self.nchildren()):
2283
+ child_dec = CMRseymourChild(self._dec, index)
2284
+ parent_rows = CMRseymourChildRowsToParent(self._dec, index)
2285
+ parent_columns = CMRseymourChildColumnsToParent(self._dec, index)
2286
+ child_nrows = CMRseymourNumRows(child_dec)
2287
+ child_ncols = CMRseymourNumColumns(child_dec)
2288
+
2289
+ if parent_rows == NULL or all(parent_rows[i] == 0 for i in range(child_nrows)):
2290
+ raise ValueError(f"Child {index} does not have parents rows")
2291
+ parent_rows_tuple = tuple(parent_rows[i] for i in range(child_nrows))
2292
+
2293
+ if parent_columns == NULL or all(parent_columns[i] == 0 for i in range(child_ncols)):
2294
+ raise ValueError(f"Child {index} does not have parents columns")
2295
+ parent_columns_tuple = tuple(parent_columns[i] for i in range(child_ncols))
2296
+ child_row = tuple(CMRelementToRowIndex(element) + 1
2297
+ for element in parent_rows_tuple)
2298
+ child_column = tuple(CMRelementToColumnIndex(element) + 1
2299
+ for element in parent_columns_tuple)
2300
+ children_row += child_row
2301
+ children_column += child_column
2302
+ P_row = Permutation(list(children_row)).to_matrix()
2303
+ P_column = Permutation(list(children_column)).to_matrix().transpose()
2304
+ return (P_row, self.block_matrix_form(), P_column)
2305
+
2306
+ summands = DecompositionNode.child_nodes
2307
+
2308
+ def summand_matrices(self):
2309
+ r"""
2310
+ Return a tuple of matrices representing the child nodes.
2311
+
2312
+ EXAMPLES::
2313
+
2314
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
2315
+ sage: M2 = Matrix_cmr_chr_sparse.one_sum([[1, 0], [-1, 1]],
2316
+ ....: [[1, 1], [-1, 0]])
2317
+ sage: result, certificate = M2.is_totally_unimodular(certificate=True,
2318
+ ....: row_keys=range(4),
2319
+ ....: column_keys='abcd')
2320
+ sage: certificate.summand_matrices()
2321
+ (
2322
+ [ 1 0] [ 1 1]
2323
+ [-1 1], [-1 0]
2324
+ )
2325
+ """
2326
+ return tuple(s.matrix() for s in self.summands())
2327
+
2328
+
2329
+ cdef class OneSumNode(SumNode):
2330
+
2331
+ def block_matrix_form(self):
2332
+ r"""
2333
+ Return the block matrix representing the one sum node.
2334
+
2335
+ EXAMPLES::
2336
+
2337
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
2338
+ sage: M = Matrix_cmr_chr_sparse.one_sum([[1, 0], [-1, 1]], [[1, 1], [-1, 0]])
2339
+ sage: result, certificate = M.is_totally_unimodular(certificate=True); certificate
2340
+ OneSumNode (4×4) with 2 children
2341
+ sage: certificate.summand_matrices()
2342
+ (
2343
+ [ 1 0] [ 1 1]
2344
+ [-1 1], [-1 0]
2345
+ )
2346
+ sage: certificate.block_matrix_form()
2347
+ [ 1 0| 0 0]
2348
+ [-1 1| 0 0]
2349
+ [-----+-----]
2350
+ [ 0 0| 1 1]
2351
+ [ 0 0|-1 0]
2352
+
2353
+ sage: M3 = Matrix_cmr_chr_sparse.one_sum([[1, 0], [-1, 1]],
2354
+ ....: [[1, 1], [-1, 0]],
2355
+ ....: [[1, 0], [0, 1]]); M3
2356
+ [ 1 0| 0 0| 0 0]
2357
+ [-1 1| 0 0| 0 0]
2358
+ [-----+-----+-----]
2359
+ [ 0 0| 1 1| 0 0]
2360
+ [ 0 0|-1 0| 0 0]
2361
+ [-----+-----+-----]
2362
+ [ 0 0| 0 0| 1 0]
2363
+ [ 0 0| 0 0| 0 1]
2364
+ sage: result, certificate = M3.is_totally_unimodular(certificate=True); certificate
2365
+ OneSumNode (6×6) with 4 children
2366
+ sage: certificate.summand_matrices()
2367
+ (
2368
+ [ 1 0] [ 1 1]
2369
+ [-1 1], [-1 0], [1], [1]
2370
+ )
2371
+ sage: certificate.block_matrix_form()
2372
+ [ 1 0| 0 0| 0| 0]
2373
+ [-1 1| 0 0| 0| 0]
2374
+ [-----+-----+--+--]
2375
+ [ 0 0| 1 1| 0| 0]
2376
+ [ 0 0|-1 0| 0| 0]
2377
+ [-----+-----+--+--]
2378
+ [ 0 0| 0 0| 1| 0]
2379
+ [-----+-----+--+--]
2380
+ [ 0 0| 0 0| 0| 1]
2381
+ """
2382
+ return Matrix_cmr_chr_sparse.one_sum(*self.summand_matrices())
2383
+
2384
+ @staticmethod
2385
+ def check(result_matrix, summand_matrices, summand_parent_rows_and_columns):
2386
+ r"""
2387
+ Check that ``result_matrix`` is a 1-sum of ``summand_matrices``.
2388
+
2389
+ EXAMPLES::
2390
+
2391
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
2392
+ sage: from sage.matrix.seymour_decomposition import OneSumNode
2393
+
2394
+ sage: M2 = Matrix_cmr_chr_sparse.one_sum([[1, 0], [-1, 1]],
2395
+ ....: [[1, 1], [-1, 0]])
2396
+ sage: result, certificate = M2.is_totally_unimodular(certificate=True); certificate
2397
+ OneSumNode (4×4) with 2 children
2398
+ sage: OneSumNode.check(M2,
2399
+ ....: certificate.summand_matrices(),
2400
+ ....: certificate.child_keys())
2401
+
2402
+ Symbolic identities::
2403
+
2404
+ sage: from sage.matrix.seymour_decomposition import OneSumNode
2405
+ sage: R.<x,y> = QQ[]
2406
+ sage: A = matrix([[x, 0], [-x, 1]])
2407
+ sage: B = matrix([[x, y], [-x, 0]])
2408
+ sage: A1B = block_diagonal_matrix([A, B])
2409
+ sage: OneSumNode.check(A1B, [A, B], [([0, 1], [0, 1]),
2410
+ ....: ([2, 3], [2, 3])])
2411
+
2412
+ Using program analysis::
2413
+
2414
+ sage: # optional - cutgeneratingfunctionology
2415
+ sage: R.<x,y,z> = ParametricRealField({x: 1}, {y: -1}, {z: 0}) # true example
2416
+ sage: A = matrix([[x, 0], [-x, 1]])
2417
+ sage: B = matrix([[x, y], [-x, 0]])
2418
+ sage: A1B = matrix([[z, 0, 0, 0], [-x, z, 0, 0], [], []])
2419
+ sage: OneSumNode.check(A1B, [A, B], [([0, 1], [0, 1]),
2420
+ ....: ([2, 3], [2, 3])])
2421
+ sage: # side-effect: R stores polynomial identities
2422
+ """
2423
+ # TODO: Check that summand_parent_rows_and_columns form partitions of rows and columns
2424
+ for matrix, rows_and_columns in zip(summand_matrices, summand_parent_rows_and_columns):
2425
+ assert result_matrix.matrix_from_rows_and_columns(*rows_and_columns) == matrix
2426
+ # TODO: Check zero blocks
2427
+
2428
+
2429
+ cdef class TwoSumNode(SumNode):
2430
+
2431
+ def block_matrix_form(self):
2432
+ r"""
2433
+ Return the block matrix representing the two sum node.
2434
+
2435
+ .. SEEALSO::
2436
+
2437
+ :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.two_sum`
2438
+
2439
+ EXAMPLES::
2440
+
2441
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
2442
+ sage: M2 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 5, 5, sparse=True),
2443
+ ....: [[1, 1, 1, 1, 1], [1, 1, 1, 0, 0],
2444
+ ....: [1, 0, 1, 1, 0], [1, 0, 0, 1, 1],
2445
+ ....: [1, 1, 0, 0, 1]]); M2
2446
+ [1 1 1 1 1]
2447
+ [1 1 1 0 0]
2448
+ [1 0 1 1 0]
2449
+ [1 0 0 1 1]
2450
+ [1 1 0 0 1]
2451
+ sage: M3 = Matrix_cmr_chr_sparse.two_sum(M2, M2, 0, 1); M3
2452
+ [1 1 1 1|1 1 1 0 0]
2453
+ [1 1 0 0|1 1 1 0 0]
2454
+ [0 1 1 0|1 1 1 0 0]
2455
+ [0 0 1 1|1 1 1 0 0]
2456
+ [1 0 0 1|1 1 1 0 0]
2457
+ [-------+---------]
2458
+ [0 0 0 0|1 1 1 1 1]
2459
+ [0 0 0 0|1 0 1 1 0]
2460
+ [0 0 0 0|1 0 0 1 1]
2461
+ [0 0 0 0|1 1 0 0 1]
2462
+ sage: result, certificate = M3.is_totally_unimodular(certificate=True); certificate
2463
+ TwoSumNode (9×9)
2464
+
2465
+ sage: K33 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 5, 4, sparse=True),
2466
+ ....: [[1, 1, 0, 0], [1, 1, 1, 0],
2467
+ ....: [1, 0, 0,-1], [0, 1, 1, 1],
2468
+ ....: [0, 0, 1, 1]]); K33
2469
+ [ 1 1 0 0]
2470
+ [ 1 1 1 0]
2471
+ [ 1 0 0 -1]
2472
+ [ 0 1 1 1]
2473
+ [ 0 0 1 1]
2474
+ sage: K33_dual = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 4, 5, sparse=True),
2475
+ ....: [[1, 1, 1, 0, 0], [1, 1, 0, 1, 0],
2476
+ ....: [0, 1, 0, 1, 1], [0, 0,-1, 1, 1]]); K33_dual
2477
+ [ 1 1 1 0 0]
2478
+ [ 1 1 0 1 0]
2479
+ [ 0 1 0 1 1]
2480
+ [ 0 0 -1 1 1]
2481
+ sage: M = Matrix_cmr_chr_sparse.two_sum(K33, K33_dual, 0, 0,
2482
+ ....: nonzero_block="bottom_left"); M
2483
+ [ 1 1 1 0| 0 0 0 0]
2484
+ [ 1 0 0 -1| 0 0 0 0]
2485
+ [ 0 1 1 1| 0 0 0 0]
2486
+ [ 0 0 1 1| 0 0 0 0]
2487
+ [-----------+-----------]
2488
+ [ 1 1 0 0| 1 1 0 0]
2489
+ [ 1 1 0 0| 1 0 1 0]
2490
+ [ 0 0 0 0| 1 0 1 1]
2491
+ [ 0 0 0 0| 0 -1 1 1]
2492
+ sage: result1, certificate1 = M.is_totally_unimodular(certificate=True); certificate1
2493
+ TwoSumNode (8×8)
2494
+ sage: certificate1.summand_matrices()
2495
+ (
2496
+ [ 1 1 1 0]
2497
+ [ 1 0 0 -1] [ 1 1 1 0 0]
2498
+ [ 0 1 1 1] [ 1 1 0 1 0]
2499
+ [ 0 0 1 1] [ 0 1 0 1 1]
2500
+ [ 1 1 0 0], [ 0 0 -1 1 1]
2501
+ )
2502
+ sage: certificate1.block_matrix_form()
2503
+ [ 1 1 1 0| 0 0 0 0]
2504
+ [ 1 0 0 -1| 0 0 0 0]
2505
+ [ 0 1 1 1| 0 0 0 0]
2506
+ [ 0 0 1 1| 0 0 0 0]
2507
+ [-----------+-----------]
2508
+ [ 1 1 0 0| 1 1 0 0]
2509
+ [ 1 1 0 0| 1 0 1 0]
2510
+ [ 0 0 0 0| 1 0 1 1]
2511
+ [ 0 0 0 0| 0 -1 1 1]
2512
+ sage: certificate1.child_keys()
2513
+ (((0, 1, 2, 3, 4), (0, 1, 2, 3)), ((4, 5, 6, 7), (0, 4, 5, 6, 7)))
2514
+ sage: M_perm = M.matrix_from_rows_and_columns([4, 6, 5, 7, 0, 1, 2, 3], range(M.ncols()))
2515
+ sage: M_perm
2516
+ [ 1 1 0 0 1 1 0 0]
2517
+ [ 0 0 0 0 1 0 1 1]
2518
+ [ 1 1 0 0 1 0 1 0]
2519
+ [ 0 0 0 0 0 -1 1 1]
2520
+ [ 1 1 1 0 0 0 0 0]
2521
+ [ 1 0 0 -1 0 0 0 0]
2522
+ [ 0 1 1 1 0 0 0 0]
2523
+ [ 0 0 1 1 0 0 0 0]
2524
+ sage: result2, certificate2 = M_perm.is_totally_unimodular(certificate=True)
2525
+ sage: certificate2.summand_matrices()
2526
+ (
2527
+ [ 1 1 1 0]
2528
+ [ 1 0 0 -1] [ 1 1 1 0 0]
2529
+ [ 0 1 1 1] [ 0 1 0 1 1]
2530
+ [ 0 0 1 1] [ 1 1 0 1 0]
2531
+ [ 1 1 0 0], [ 0 0 -1 1 1]
2532
+ )
2533
+ sage: certificate2.block_matrix_form()
2534
+ [ 1 1 1 0| 0 0 0 0]
2535
+ [ 1 0 0 -1| 0 0 0 0]
2536
+ [ 0 1 1 1| 0 0 0 0]
2537
+ [ 0 0 1 1| 0 0 0 0]
2538
+ [-----------+-----------]
2539
+ [ 1 1 0 0| 1 1 0 0]
2540
+ [ 0 0 0 0| 1 0 1 1]
2541
+ [ 1 1 0 0| 1 0 1 0]
2542
+ [ 0 0 0 0| 0 -1 1 1]
2543
+ sage: certificate2.child_keys()
2544
+ (((4, 5, 6, 7, 0), (0, 1, 2, 3)), ((0, 1, 2, 3), (0, 4, 5, 6, 7)))
2545
+ """
2546
+ M1, M2 = self.summand_matrices()
2547
+ return Matrix_cmr_chr_sparse.two_sum(M1, M2, M1.nrows() - 1, 0, "bottom_left")
2548
+
2549
+
2550
+ cdef class DeltaSumNode(SumNode):
2551
+
2552
+ def _children(self):
2553
+ r"""
2554
+ Return a tuple of the tuples of the two children
2555
+ and their row and column keys.
2556
+
2557
+ .. SEEALSO::
2558
+
2559
+ :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.delta_sum`
2560
+
2561
+ TESTS:
2562
+
2563
+ This is test ``DeltasumR12`` in CMR's ``test_tu.cpp``::
2564
+
2565
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
2566
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 6, sparse=True),
2567
+ ....: [[1,0,1,1,0,0],[0,1,1,1,0,0],[1,0,1,0,1,1],
2568
+ ....: [0,-1,0,-1,1,1],[1,0,1,0,1,0],[0,-1,0,-1,0,1]])
2569
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
2570
+ ....: decompose_strategy="delta_pivot",
2571
+ ....: row_keys=range(6),
2572
+ ....: column_keys='abcdef')
2573
+ sage: certificate.child_keys()
2574
+ ((0, 1, a, 3, 4, 5), (2, b, c, d, e, f))
2575
+ sage: C = certificate.child_nodes()[0]
2576
+ sage: C1, C2 = C.child_nodes()
2577
+ sage: C1.matrix()
2578
+ [ 0 0 1 1 1]
2579
+ [ 1 1 1 0 0]
2580
+ [ 0 1 0 -1 -1]
2581
+ [-1 0 -1 0 -1]
2582
+ sage: C2.matrix()
2583
+ [-1 0 1 -1 -1]
2584
+ [ 1 1 0 1 1]
2585
+ [ 0 0 1 0 -1]
2586
+ [ 1 1 0 0 1]
2587
+ sage: C.child_keys()
2588
+ (((0, 1, a, 3), (b, c, d, 2, +2-3)), ((0, 3, 4, 5), (-0+b, b, 2, e, f)))
2589
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
2590
+ sage: node = UnknownNode(R12,
2591
+ ....: row_keys=range(6),
2592
+ ....: column_keys='abcdef'); node
2593
+ UnknownNode (6×6)
2594
+ sage: C0 = node.complete_decomposition(
2595
+ ....: decompose_strategy="delta_pivot",
2596
+ ....: )
2597
+ sage: C0
2598
+ PivotsNode (6×6)
2599
+ sage: unicode_art(C0)
2600
+ PivotsNode (6×6)
2601
+
2602
+ ╭DeltaSumNode (6×6)─╮
2603
+ │ │
2604
+ CographicNode (4×5) GraphicNode (4×5)
2605
+ sage: unicode_art(node)
2606
+ UnknownNode (6×6)
2607
+
2608
+ sage: R12_pivot = R12.ternary_pivot(4, 0); R12_pivot
2609
+ [ 1 0 0 1 -1 0]
2610
+ [ 0 1 1 1 0 0]
2611
+ [ 1 0 0 0 0 1]
2612
+ [ 0 -1 0 -1 1 1]
2613
+ [-1 0 1 0 1 0]
2614
+ [ 0 -1 0 -1 0 1]
2615
+ sage: result, certificate = R12_pivot.is_totally_unimodular(certificate=True,
2616
+ ....: decompose_strategy="delta_pivot")
2617
+ sage: C1, C2 = certificate.child_nodes()
2618
+ sage: C1.matrix()
2619
+ [ 0 0 1 1 1]
2620
+ [ 1 1 1 0 0]
2621
+ [ 0 1 0 -1 -1]
2622
+ [-1 0 -1 0 -1]
2623
+ sage: C2.matrix()
2624
+ [-1 0 1 -1 0]
2625
+ [ 0 0 1 0 1]
2626
+ [ 1 1 0 1 1]
2627
+ [ 1 1 0 0 1]
2628
+
2629
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
2630
+ sage: R12_large = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 9, 12, sparse=True),
2631
+ ....: [[1, -1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
2632
+ ....: [0, 0, 0, 1, -1, 0, 0, 0, 1 , 1, 1, 1],
2633
+ ....: [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
2634
+ ....: [ 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0],
2635
+ ....: [ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, -1, -1],
2636
+ ....: [ 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0],
2637
+ ....: [ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, -1, -1],
2638
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0],
2639
+ ....: [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1]])
2640
+ sage: result, certificate = R12_large.is_totally_unimodular(certificate=True,
2641
+ ....: decompose_strategy="delta_pivot",
2642
+ ....: row_keys=range(9),
2643
+ ....: column_keys='abcdefghijkl')
2644
+ sage: C = certificate.child_nodes()[0]; C
2645
+ DeltaSumNode (9×12)
2646
+ sage: C1, C2 = C.child_nodes()
2647
+ sage: C1.matrix()
2648
+ [ 0 0 1 1 1 1 1]
2649
+ [ 1 1 0 0 0 -1 -1]
2650
+ [ 1 0 -1 0 -1 -1 -1]
2651
+ [ 0 1 1 0 1 0 0]
2652
+ [ 0 0 0 -1 -1 0 -1]
2653
+ sage: C2.matrix()
2654
+ [-1 0 1 -1 0 0 0 0 -1]
2655
+ [ 0 0 -1 1 0 1 -1 0 1]
2656
+ [ 1 1 0 1 1 0 0 0 1]
2657
+ [ 1 1 0 1 1 0 0 0 0]
2658
+ [ 1 1 -1 1 0 1 0 1 1]
2659
+ [ 1 1 0 0 0 0 1 1 0]
2660
+ sage: C.row_keys()
2661
+ (i, 1, 2, 3, 4, 5, 6, 7, 8)
2662
+ sage: C.column_keys()
2663
+ (a, b, c, d, e, f, g, h, 0, j, k, l)
2664
+ sage: C.child_keys()[0]
2665
+ ((i, 2, 7, 8, 3), (g, h, j, k, l, a, -3+a))
2666
+ sage: C.child_keys()[1]
2667
+ ((i, 1, 3, 4, 5, 6), (-i+k, k, a, b, c, d, e, f, 0))
2668
+ """
2669
+ if self._child_nodes is not None:
2670
+ return self._child_nodes
2671
+
2672
+ if self.nchildren() != 2:
2673
+ raise ValueError(f"DeltaSumNode has exactly two children not {self.nchildren()}!")
2674
+
2675
+ self.set_default_keys()
2676
+
2677
+ cdef CMR_SEYMOUR_NODE *child1_dec = CMRseymourChild(self._dec, 0)
2678
+ cdef CMR_ELEMENT *parent_rows1 = CMRseymourChildRowsToParent(self._dec, 0)
2679
+ cdef CMR_ELEMENT *parent_columns1 = CMRseymourChildColumnsToParent(self._dec, 0)
2680
+ cdef CMR_CHRMAT *mat1 = CMRseymourGetMatrix(child1_dec)
2681
+
2682
+ cdef CMR_SEYMOUR_NODE *child2_dec = CMRseymourChild(self._dec, 1)
2683
+ cdef CMR_ELEMENT *parent_rows2 = CMRseymourChildRowsToParent(self._dec, 1)
2684
+ cdef CMR_ELEMENT *parent_columns2 = CMRseymourChildColumnsToParent(self._dec, 1)
2685
+ cdef CMR_CHRMAT *mat2 = CMRseymourGetMatrix(child2_dec)
2686
+
2687
+ cdef size_t index1
2688
+
2689
+ child1_nrows = CMRseymourNumRows(child1_dec)
2690
+ child1_ncols = CMRseymourNumColumns(child1_dec)
2691
+
2692
+ child1_row_keys = tuple(self._CMRelement_to_key(parent_rows1[i])
2693
+ for i in range(child1_nrows))
2694
+ child1_column_keys = tuple(self._CMRelement_to_key(parent_columns1[i])
2695
+ for i in range(child1_ncols - 1))
2696
+
2697
+ row1_index = child1_nrows - 1
2698
+ column1_index = child1_ncols - 1
2699
+ CMR_CALL(CMRchrmatFindEntry(mat1, row1_index, column1_index, &index1))
2700
+ if index1 == SIZE_MAX:
2701
+ eps1 = Integer(0)
2702
+ else:
2703
+ eps1 = Integer(mat1.entryValues[index1])
2704
+ if eps1 != 1 and eps1 != -1:
2705
+ raise ValueError(f"First child in the Delta Sum "
2706
+ f"has 1 or -1 in the entry "
2707
+ f"row {row1_index} and column {column1_index} "
2708
+ f"but got {eps1}")
2709
+
2710
+ extra_key = ElementKey((1, child1_column_keys[column1_index - 1],
2711
+ eps1, child1_row_keys[row1_index]),
2712
+ composition=True)
2713
+ child1_column_keys += (extra_key,)
2714
+
2715
+ child1 = create_DecompositionNode(child1_dec, matrix=None,
2716
+ row_keys=child1_row_keys,
2717
+ column_keys=child1_column_keys,
2718
+ base_ring=self.base_ring())
2719
+
2720
+ child2_nrows = CMRseymourNumRows(child2_dec)
2721
+ child2_ncols = CMRseymourNumColumns(child2_dec)
2722
+
2723
+ child2_row_keys = tuple(self._CMRelement_to_key(parent_rows2[i])
2724
+ for i in range(child2_nrows))
2725
+
2726
+ row2_index = 0
2727
+ column2_index = 0
2728
+ CMR_CALL(CMRchrmatFindEntry(mat2, row2_index, column2_index, &index1))
2729
+ if index1 == SIZE_MAX:
2730
+ eps1 = Integer(0)
2731
+ else:
2732
+ eps1 = Integer(mat2.entryValues[index1])
2733
+
2734
+ if eps1 != 1 and eps1 != -1:
2735
+ raise ValueError(f"Second child in the Delta Sum "
2736
+ f"has 1 or -1 in the entry "
2737
+ f"row {row2_index} and column {column2_index} "
2738
+ f"but got {eps1}")
2739
+
2740
+ child2_column_keys = tuple(self._CMRelement_to_key(parent_columns2[i])
2741
+ for i in range(1, child2_ncols))
2742
+ extra_key = ElementKey((1, child2_column_keys[column2_index],
2743
+ eps1, child2_row_keys[row2_index]),
2744
+ composition=True)
2745
+ child2_column_keys = (extra_key,) + child2_column_keys
2746
+
2747
+ child2 = create_DecompositionNode(child2_dec, matrix=None,
2748
+ row_keys=child2_row_keys,
2749
+ column_keys=child2_column_keys,
2750
+ base_ring=self.base_ring())
2751
+
2752
+ self._child_nodes = ((child1, child1_row_keys, child1_column_keys),
2753
+ (child2, child2_row_keys, child2_column_keys))
2754
+ return self._child_nodes
2755
+
2756
+ def is_distributed_ranks(self):
2757
+ r"""
2758
+ Return ``True`` for the `\Delta`-sum node ``self``.
2759
+
2760
+ ``distributed_ranks`` is named after the two rank 1 off-diagonal blocks.
2761
+
2762
+ .. SEEALSO::
2763
+
2764
+ :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.delta_sum`
2765
+
2766
+ EXAMPLES::
2767
+
2768
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
2769
+ sage: R12_large = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 9, 12, sparse=True),
2770
+ ....: [[ 1, -1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
2771
+ ....: [ 0, 0, 0, 1, -1, 0, 0, 0, 1, 1, 1, 1],
2772
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
2773
+ ....: [ 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0],
2774
+ ....: [ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, -1, -1],
2775
+ ....: [ 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0],
2776
+ ....: [ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, -1, -1],
2777
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0],
2778
+ ....: [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1]])
2779
+ sage: result, certificate = R12_large.is_totally_unimodular(certificate=True,
2780
+ ....: decompose_strategy="delta_pivot")
2781
+ sage: C = certificate.child_nodes()[0]; C
2782
+ DeltaSumNode (9×12)
2783
+ sage: C.is_distributed_ranks()
2784
+ True
2785
+ sage: C.is_concentrated_rank()
2786
+ False
2787
+ """
2788
+ return True
2789
+
2790
+ def is_concentrated_rank(self):
2791
+ r"""
2792
+ Return ``False`` for the `\Delta`-sum node ``self``.
2793
+
2794
+ ``concentrated_rank`` is named after the rank 2 and rank 0 off-diagonal blocks.
2795
+
2796
+ .. SEEALSO::
2797
+
2798
+ :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.delta_sum`
2799
+ """
2800
+ return False
2801
+
2802
+ def block_matrix_form(self):
2803
+ r"""
2804
+ Return the block matrix constructed from the `\Delta`-sum of children.
2805
+
2806
+ EXAMPLES::
2807
+
2808
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
2809
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 6, sparse=True),
2810
+ ....: [[1,0,1,1,0,0],[0,1,1,1,0,0],[1,0,1,0,1,1],
2811
+ ....: [0,-1,0,-1,1,1],[1,0,1,0,1,0],[0,-1,0,-1,0,1]])
2812
+ sage: R12
2813
+ [ 1 0 1 1 0 0]
2814
+ [ 0 1 1 1 0 0]
2815
+ [ 1 0 1 0 1 1]
2816
+ [ 0 -1 0 -1 1 1]
2817
+ [ 1 0 1 0 1 0]
2818
+ [ 0 -1 0 -1 0 1]
2819
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
2820
+ ....: decompose_strategy="delta_pivot")
2821
+ sage: C = certificate.child_nodes()[0]; C
2822
+ DeltaSumNode (6×6)
2823
+ sage: C.matrix()
2824
+ [ 1 0 0 1 -1 -1]
2825
+ [ 0 1 1 1 0 0]
2826
+ [-1 0 1 0 1 1]
2827
+ [ 0 -1 0 -1 1 1]
2828
+ [ 1 0 0 0 0 -1]
2829
+ [ 0 -1 0 -1 0 1]
2830
+ sage: C.summand_matrices()
2831
+ (
2832
+ [ 0 0 1 1 1] [-1 0 1 -1 -1]
2833
+ [ 1 1 1 0 0] [ 1 1 0 1 1]
2834
+ [ 0 1 0 -1 -1] [ 0 0 1 0 -1]
2835
+ [-1 0 -1 0 -1], [ 1 1 0 0 1]
2836
+ )
2837
+ sage: C.row_keys()
2838
+ (r0, r1, c0, r3, r4, r5)
2839
+ sage: C.column_keys()
2840
+ (r2, c1, c2, c3, c4, c5)
2841
+ sage: C.child_keys()
2842
+ (((r0, r1, c0, r3), (c1, c2, c3, r2, +r2-r3)),
2843
+ ((r0, r3, r4, r5), (+c1-r0, c1, r2, c4, c5)))
2844
+ sage: C.block_matrix_form()
2845
+ [ 0 0 1 1 -1 -1]
2846
+ [ 1 1 1 0 0 0]
2847
+ [ 0 1 0 -1 1 1]
2848
+ [-1 0 -1 0 1 1]
2849
+ [ 0 0 0 1 0 -1]
2850
+ [-1 0 -1 0 0 1]
2851
+ """
2852
+ M1, M2 = self.summand_matrices()
2853
+ return Matrix_cmr_chr_sparse.delta_sum(M1, M2)
2854
+
2855
+ def permuted_block_matrix(self):
2856
+ r"""
2857
+ Return the permutation matrices between the matrix
2858
+ and the block matrix form.
2859
+
2860
+ OUTPUT: a tuple ``(Prow, BlockMatrix, Pcolumn)``, where
2861
+ ``self.matrix() == Prow * BlockMatrix * Pcolumn``, and
2862
+ ``BlockMatrix == self.block_matrix_form()``.
2863
+
2864
+ EXAMPLES::
2865
+
2866
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
2867
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 6, sparse=True),
2868
+ ....: [[1,0,1,1,0,0],[0,1,1,1,0,0],[1,0,1,0,1,1],
2869
+ ....: [0,-1,0,-1,1,1],[1,0,1,0,1,0],[0,-1,0,-1,0,1]])
2870
+ sage: R12
2871
+ [ 1 0 1 1 0 0]
2872
+ [ 0 1 1 1 0 0]
2873
+ [ 1 0 1 0 1 1]
2874
+ [ 0 -1 0 -1 1 1]
2875
+ [ 1 0 1 0 1 0]
2876
+ [ 0 -1 0 -1 0 1]
2877
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
2878
+ ....: decompose_strategy="delta_pivot")
2879
+ sage: C = certificate.child_nodes()[0]; C
2880
+ DeltaSumNode (6×6)
2881
+ sage: C.matrix()
2882
+ [ 1 0 0 1 -1 -1]
2883
+ [ 0 1 1 1 0 0]
2884
+ [-1 0 1 0 1 1]
2885
+ [ 0 -1 0 -1 1 1]
2886
+ [ 1 0 0 0 0 -1]
2887
+ [ 0 -1 0 -1 0 1]
2888
+ sage: C.block_matrix_form()
2889
+ [ 0 0 1 1 -1 -1]
2890
+ [ 1 1 1 0 0 0]
2891
+ [ 0 1 0 -1 1 1]
2892
+ [-1 0 -1 0 1 1]
2893
+ [ 0 0 0 1 0 -1]
2894
+ [-1 0 -1 0 0 1]
2895
+ sage: P_row, block_matrix, P_column = C.permuted_block_matrix()
2896
+ sage: P_row^(-1) * C.matrix() * P_column^(-1) == block_matrix
2897
+ True
2898
+ """
2899
+ from sage.combinat.permutation import Permutation
2900
+ cdef CMR_SEYMOUR_NODE *child_dec
2901
+ cdef CMR_ELEMENT *parent_rows
2902
+ cdef CMR_ELEMENT *parent_columns
2903
+ children_row = tuple()
2904
+ children_column = tuple()
2905
+ for index in range(self.nchildren()):
2906
+ child_dec = CMRseymourChild(self._dec, index)
2907
+ parent_rows = CMRseymourChildRowsToParent(self._dec, index)
2908
+ parent_columns = CMRseymourChildColumnsToParent(self._dec, index)
2909
+ child_nrows = CMRseymourNumRows(child_dec)
2910
+ child_ncols = CMRseymourNumColumns(child_dec)
2911
+
2912
+ if parent_rows == NULL or all(parent_rows[i] == 0 for i in range(child_nrows)):
2913
+ raise ValueError(f"Child {index} does not have parents rows")
2914
+ parent_rows_tuple = tuple(parent_rows[i] for i in range(child_nrows))
2915
+
2916
+ if parent_columns == NULL or all(parent_columns[i] == 0 for i in range(child_ncols)):
2917
+ raise ValueError(f"Child {index} does not have parents columns")
2918
+ parent_columns_tuple = tuple(parent_columns[i] for i in range(child_ncols))
2919
+ if index == 0:
2920
+ child_row = tuple(CMRelementToRowIndex(element) + 1
2921
+ for element in parent_rows_tuple[:-1])
2922
+ child_column = tuple(CMRelementToColumnIndex(element) + 1
2923
+ for element in parent_columns_tuple[:-2])
2924
+ else:
2925
+ child_row = tuple(CMRelementToRowIndex(element) + 1
2926
+ for element in parent_rows_tuple[1:])
2927
+ child_column = tuple(CMRelementToColumnIndex(element) + 1
2928
+ for element in parent_columns_tuple[2:])
2929
+ children_row += child_row
2930
+ children_column += child_column
2931
+ P_row = Permutation(list(children_row)).to_matrix()
2932
+ P_column = Permutation(list(children_column)).to_matrix().transpose()
2933
+ return (P_row, self.block_matrix_form(), P_column)
2934
+
2935
+
2936
+ cdef class ThreeSumNode(SumNode):
2937
+
2938
+ def _children(self):
2939
+ r"""
2940
+ Return a tuple of the tuples of the two children
2941
+ and their row and column keys.
2942
+
2943
+ .. SEEALSO::
2944
+
2945
+ :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.three_sum`
2946
+
2947
+ TESTS:
2948
+
2949
+ This is test ``ThreesumR12`` in CMR's ``test_tu.cpp``::
2950
+
2951
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
2952
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 6, sparse=True),
2953
+ ....: [[1,0,1,1,0,0],[0,1,1,1,0,0],[1,0,1,0,1,1],
2954
+ ....: [0,-1,0,-1,1,1],[1,0,1,0,1,0],[0,-1,0,-1,0,1]])
2955
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
2956
+ ....: decompose_strategy="three_pivot")
2957
+ sage: C1, C2 = certificate.child_nodes()
2958
+ sage: C1.matrix()
2959
+ [ 1 0 1 1 0]
2960
+ [ 0 1 1 1 0]
2961
+ [ 1 0 1 0 1]
2962
+ [ 0 -1 0 -1 1]
2963
+ sage: C2.matrix()
2964
+ [ 1 1 0 0]
2965
+ [ 1 0 1 1]
2966
+ [ 0 -1 1 1]
2967
+ [ 1 0 1 0]
2968
+ [ 0 -1 0 1]
2969
+ sage: certificate.child_keys()[0]
2970
+ ((r0, r1, r2, r3), (c0, c1, c2, c3, +r2+r3))
2971
+ sage: certificate.child_keys()[1]
2972
+ ((+c0+c3, r2, r3, r4, r5), (c0, c3, c4, c5))
2973
+
2974
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
2975
+ ....: decompose_strategy="three_pivot",
2976
+ ....: row_keys=range(6),
2977
+ ....: column_keys='abcdef')
2978
+ sage: C1, C2 = certificate.child_nodes()
2979
+ sage: C1.matrix()
2980
+ [ 1 0 1 1 0]
2981
+ [ 0 1 1 1 0]
2982
+ [ 1 0 1 0 1]
2983
+ [ 0 -1 0 -1 1]
2984
+ sage: C2.matrix()
2985
+ [ 1 1 0 0]
2986
+ [ 1 0 1 1]
2987
+ [ 0 -1 1 1]
2988
+ [ 1 0 1 0]
2989
+ [ 0 -1 0 1]
2990
+ sage: certificate.child_keys()[0]
2991
+ ((0, 1, 2, 3), (a, b, c, d, +2+3))
2992
+ sage: certificate.child_keys()[1]
2993
+ ((+a+d, 2, 3, 4, 5), (a, d, e, f))
2994
+ """
2995
+ if self._child_nodes is not None:
2996
+ return self._child_nodes
2997
+
2998
+ if self.nchildren() != 2:
2999
+ raise ValueError(f"ThreeSumNode has exactly two children not {self.nchildren()}!")
3000
+
3001
+ self.set_default_keys()
3002
+
3003
+ cdef CMR_SEYMOUR_NODE *child1_dec = CMRseymourChild(self._dec, 0)
3004
+ cdef CMR_ELEMENT *parent_rows1 = CMRseymourChildRowsToParent(self._dec, 0)
3005
+ cdef CMR_ELEMENT *parent_columns1 = CMRseymourChildColumnsToParent(self._dec, 0)
3006
+ cdef CMR_CHRMAT *mat1 = CMRseymourGetMatrix(child1_dec)
3007
+ cdef size_t *first_special_rows = CMRseymourChildSpecialRows(self._dec, 0)
3008
+ cdef size_t *first_special_columns = CMRseymourChildSpecialColumns(self._dec, 0)
3009
+
3010
+ cdef CMR_SEYMOUR_NODE *child2_dec = CMRseymourChild(self._dec, 1)
3011
+ cdef CMR_ELEMENT *parent_rows2 = CMRseymourChildRowsToParent(self._dec, 1)
3012
+ cdef CMR_ELEMENT *parent_columns2 = CMRseymourChildColumnsToParent(self._dec, 1)
3013
+ cdef CMR_CHRMAT *mat2 = CMRseymourGetMatrix(child2_dec)
3014
+ cdef size_t *second_special_rows = CMRseymourChildSpecialRows(self._dec, 1)
3015
+ cdef size_t *second_special_columns = CMRseymourChildSpecialColumns(self._dec, 1)
3016
+
3017
+ cdef size_t index1, index2
3018
+
3019
+ child1_nrows = CMRseymourNumRows(child1_dec)
3020
+ child1_ncols = CMRseymourNumColumns(child1_dec)
3021
+
3022
+ child1_row_keys = tuple(self._CMRelement_to_key(parent_rows1[i])
3023
+ for i in range(child1_nrows))
3024
+ child1_column_keys = tuple(self._CMRelement_to_key(parent_columns1[i])
3025
+ for i in range(child1_ncols - 1))
3026
+
3027
+ row1_index = first_special_rows[0]
3028
+ extra_column = first_special_columns[2]
3029
+ CMR_CALL(CMRchrmatFindEntry(mat1, row1_index, extra_column, &index1))
3030
+ if index1 == SIZE_MAX:
3031
+ eps1 = Integer(0)
3032
+ else:
3033
+ eps1 = Integer(mat1.entryValues[index1])
3034
+ if eps1 != 1:
3035
+ raise ValueError(f"First child in the Three Sum "
3036
+ f"has 1 in the entry "
3037
+ f"row {row1_index} and column {extra_column} "
3038
+ f"but got {eps1}")
3039
+
3040
+ row2_index = first_special_rows[1]
3041
+ CMR_CALL(CMRchrmatFindEntry(mat1, row2_index, extra_column, &index2))
3042
+ if index2 == SIZE_MAX:
3043
+ eps2 = Integer(0)
3044
+ else:
3045
+ eps2 = Integer(mat1.entryValues[index2])
3046
+ if eps2 != 1 and eps2 != -1:
3047
+ raise ValueError(f"First child in the Three Sum "
3048
+ f"has 1 or -1 in the entry "
3049
+ f"row {row2_index} and column {extra_column} "
3050
+ f"but got {eps2}")
3051
+
3052
+ extra_key = ElementKey((eps1, child1_row_keys[row1_index],
3053
+ eps2, child1_row_keys[row2_index]),
3054
+ composition=True)
3055
+ child1_column_keys += (extra_key,)
3056
+
3057
+ child1 = create_DecompositionNode(child1_dec, matrix=None,
3058
+ row_keys=child1_row_keys,
3059
+ column_keys=child1_column_keys,
3060
+ base_ring=self.base_ring())
3061
+
3062
+ child2_nrows = CMRseymourNumRows(child2_dec)
3063
+ child2_ncols = CMRseymourNumColumns(child2_dec)
3064
+
3065
+ child2_row_keys = tuple(self._CMRelement_to_key(parent_rows2[i])
3066
+ for i in range(1, child2_nrows))
3067
+ child2_column_keys = tuple(self._CMRelement_to_key(parent_columns2[i])
3068
+ for i in range(child2_ncols))
3069
+
3070
+ column1_index = second_special_columns[0]
3071
+ extra_row = second_special_rows[0]
3072
+ CMR_CALL(CMRchrmatFindEntry(mat2, extra_row, column1_index, &index1))
3073
+ if index1 == SIZE_MAX:
3074
+ eps1 = Integer(0)
3075
+ else:
3076
+ eps1 = Integer(mat1.entryValues[index1])
3077
+ if eps1 != 1 and eps1 != -1:
3078
+ raise ValueError(f"Second child in the Three Sum "
3079
+ f"has 1 or -1 in the entry "
3080
+ f"row {extra_row} and column {column1_index} "
3081
+ f"but got {eps1}")
3082
+ column2_index = second_special_columns[1]
3083
+ CMR_CALL(CMRchrmatFindEntry(mat2, extra_row, column2_index, &index2))
3084
+ if index2 == SIZE_MAX:
3085
+ eps2 = Integer(0)
3086
+ else:
3087
+ eps2 = Integer(mat2.entryValues[index2])
3088
+ if eps2 != 1:
3089
+ raise ValueError(f"Second child in the Three Sum "
3090
+ f"has 1 in the entry "
3091
+ f"row {extra_row} and column {column2_index} "
3092
+ f"but got {eps2}")
3093
+
3094
+ extra_key = ElementKey((eps1, child2_column_keys[column1_index],
3095
+ eps2, child2_column_keys[column2_index]),
3096
+ composition=True)
3097
+ child2_row_keys = (extra_key,) + child2_row_keys
3098
+
3099
+ child2 = create_DecompositionNode(child2_dec, matrix=None,
3100
+ row_keys=child2_row_keys,
3101
+ column_keys=child2_column_keys,
3102
+ base_ring=self.base_ring())
3103
+
3104
+ self._child_nodes = ((child1, child1_row_keys, child1_column_keys),
3105
+ (child2, child2_row_keys, child2_column_keys))
3106
+ return self._child_nodes
3107
+
3108
+ def is_distributed_ranks(self):
3109
+ r"""
3110
+ Return ``False`` for the 3-sum node ``self``.
3111
+
3112
+ ``distributed_ranks`` is named after the two rank 1 off-diagonal blocks.
3113
+
3114
+ .. SEEALSO::
3115
+
3116
+ :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.three_sum`
3117
+
3118
+ EXAMPLES::
3119
+
3120
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3121
+ sage: R12_large = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 9, 12, sparse=True),
3122
+ ....: [[ 1, -1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
3123
+ ....: [ 0, 0, 0, 1, -1, 0, 0, 0, 1, 1, 1, 1],
3124
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
3125
+ ....: [ 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0],
3126
+ ....: [ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, -1, -1],
3127
+ ....: [ 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0],
3128
+ ....: [ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, -1, -1],
3129
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0],
3130
+ ....: [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1]])
3131
+ sage: result, certificate = R12_large.is_totally_unimodular(certificate=True,
3132
+ ....: decompose_strategy="three_pivot")
3133
+ sage: C = certificate; C
3134
+ ThreeSumNode (9×12)
3135
+ sage: C.is_distributed_ranks()
3136
+ False
3137
+ sage: C.is_concentrated_rank()
3138
+ True
3139
+ """
3140
+ return False
3141
+
3142
+ def is_concentrated_rank(self):
3143
+ r"""
3144
+ Return ``True`` for the 3-sum node ``self``.
3145
+
3146
+ ``concentrated_rank`` is named after the rank 2 and rank 0 off-diagonal blocks.
3147
+
3148
+ .. SEEALSO::
3149
+
3150
+ :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.three_sum`
3151
+ """
3152
+ return True
3153
+
3154
+ def block_matrix_form(self):
3155
+ r"""
3156
+ Return the block matrix constructed from the 3-sum of children.
3157
+
3158
+ EXAMPLES::
3159
+
3160
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3161
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 6, sparse=True),
3162
+ ....: [[1,0,1,1,0,0],[0,1,1,1,0,0],[1,0,1,0,1,1],
3163
+ ....: [0,-1,0,-1,1,1],[1,0,1,0,1,0],[0,-1,0,-1,0,1]])
3164
+ sage: R12
3165
+ [ 1 0 1 1 0 0]
3166
+ [ 0 1 1 1 0 0]
3167
+ [ 1 0 1 0 1 1]
3168
+ [ 0 -1 0 -1 1 1]
3169
+ [ 1 0 1 0 1 0]
3170
+ [ 0 -1 0 -1 0 1]
3171
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
3172
+ ....: decompose_strategy="three_pivot")
3173
+ sage: C = certificate; C
3174
+ ThreeSumNode (6×6)
3175
+ sage: C.matrix()
3176
+ [ 1 0 1 1 0 0]
3177
+ [ 0 1 1 1 0 0]
3178
+ [ 1 0 1 0 1 1]
3179
+ [ 0 -1 0 -1 1 1]
3180
+ [ 1 0 1 0 1 0]
3181
+ [ 0 -1 0 -1 0 1]
3182
+ sage: C.summand_matrices()
3183
+ (
3184
+ [ 1 1 0 0]
3185
+ [ 1 0 1 1 0] [ 1 0 1 1]
3186
+ [ 0 1 1 1 0] [ 0 -1 1 1]
3187
+ [ 1 0 1 0 1] [ 1 0 1 0]
3188
+ [ 0 -1 0 -1 1], [ 0 -1 0 1]
3189
+ )
3190
+ sage: C.child_keys()
3191
+ (((r0, r1, r2, r3), (c0, c1, c2, c3, +r2+r3)),
3192
+ ((+c0+c3, r2, r3, r4, r5), (c0, c3, c4, c5)))
3193
+ sage: C.block_matrix_form()
3194
+ [ 1 0 1 1 0 0]
3195
+ [ 0 1 1 1 0 0]
3196
+ [ 1 0 1 0 1 1]
3197
+ [ 0 -1 0 -1 1 1]
3198
+ [ 1 0 1 0 1 0]
3199
+ [ 0 -1 0 -1 0 1]
3200
+ """
3201
+ M1, M2 = self.summand_matrices()
3202
+ return Matrix_cmr_chr_sparse.three_sum(M1, M2)
3203
+
3204
+ def permuted_block_matrix(self):
3205
+ r"""
3206
+ Return the permutation matrices between the matrix
3207
+ and the block matrix form.
3208
+
3209
+ OUTPUT: a tuple ``(Prow, BlockMatrix, Pcolumn)``, where
3210
+ ``self.matrix() == Prow * BlockMatrix * Pcolumn``, and
3211
+ ``BlockMatrix == self.block_matrix_form()``.
3212
+
3213
+ EXAMPLES::
3214
+
3215
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3216
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 6, sparse=True),
3217
+ ....: [[1,0,1,1,0,0],[0,1,1,1,0,0],[1,0,1,0,1,1],
3218
+ ....: [0,-1,0,-1,1,1],[1,0,1,0,1,0],[0,-1,0,-1,0,1]])
3219
+ sage: R12
3220
+ [ 1 0 1 1 0 0]
3221
+ [ 0 1 1 1 0 0]
3222
+ [ 1 0 1 0 1 1]
3223
+ [ 0 -1 0 -1 1 1]
3224
+ [ 1 0 1 0 1 0]
3225
+ [ 0 -1 0 -1 0 1]
3226
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
3227
+ ....: decompose_strategy="three_pivot")
3228
+ sage: C = certificate; C
3229
+ ThreeSumNode (6×6)
3230
+ sage: C.matrix()
3231
+ [ 1 0 1 1 0 0]
3232
+ [ 0 1 1 1 0 0]
3233
+ [ 1 0 1 0 1 1]
3234
+ [ 0 -1 0 -1 1 1]
3235
+ [ 1 0 1 0 1 0]
3236
+ [ 0 -1 0 -1 0 1]
3237
+ sage: C.block_matrix_form()
3238
+ [ 1 0 1 1 0 0]
3239
+ [ 0 1 1 1 0 0]
3240
+ [ 1 0 1 0 1 1]
3241
+ [ 0 -1 0 -1 1 1]
3242
+ [ 1 0 1 0 1 0]
3243
+ [ 0 -1 0 -1 0 1]
3244
+ sage: P_row, block_matrix, P_column = C.permuted_block_matrix()
3245
+ sage: P_row^(-1) * C.matrix() * P_column^(-1) == block_matrix
3246
+ True
3247
+ """
3248
+ from sage.combinat.permutation import Permutation
3249
+ cdef CMR_SEYMOUR_NODE *child_dec
3250
+ cdef CMR_ELEMENT *parent_rows
3251
+ cdef CMR_ELEMENT *parent_columns
3252
+ children_row = tuple()
3253
+ children_column = tuple()
3254
+ for index in range(self.nchildren()):
3255
+ child_dec = CMRseymourChild(self._dec, index)
3256
+ parent_rows = CMRseymourChildRowsToParent(self._dec, index)
3257
+ parent_columns = CMRseymourChildColumnsToParent(self._dec, index)
3258
+ child_nrows = CMRseymourNumRows(child_dec)
3259
+ child_ncols = CMRseymourNumColumns(child_dec)
3260
+
3261
+ if parent_rows == NULL or all(parent_rows[i] == 0 for i in range(child_nrows)):
3262
+ raise ValueError(f"Child {index} does not have parents rows")
3263
+ parent_rows_tuple = tuple(parent_rows[i] for i in range(child_nrows))
3264
+
3265
+ if parent_columns == NULL or all(parent_columns[i] == 0 for i in range(child_ncols)):
3266
+ raise ValueError(f"Child {index} does not have parents columns")
3267
+ parent_columns_tuple = tuple(parent_columns[i] for i in range(child_ncols))
3268
+ if index == 0:
3269
+ child_row = tuple(CMRelementToRowIndex(element) + 1
3270
+ for element in parent_rows_tuple[:-2])
3271
+ child_column = tuple(CMRelementToColumnIndex(element) + 1
3272
+ for element in parent_columns_tuple[:-1])
3273
+ else:
3274
+ child_row = tuple(CMRelementToRowIndex(element) + 1
3275
+ for element in parent_rows_tuple[1:])
3276
+ child_column = tuple(CMRelementToColumnIndex(element) + 1
3277
+ for element in parent_columns_tuple[2:])
3278
+ children_row += child_row
3279
+ children_column += child_column
3280
+ P_row = Permutation(list(children_row)).to_matrix()
3281
+ P_column = Permutation(list(children_column)).to_matrix().transpose()
3282
+ return (P_row, self.block_matrix_form(), P_column)
3283
+
3284
+
3285
+ cdef class YSumNode(SumNode):
3286
+
3287
+ def _children(self):
3288
+ r"""
3289
+ Return a tuple of the tuples of the two children
3290
+ and their row and column keys.
3291
+
3292
+ .. SEEALSO::
3293
+
3294
+ :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.y_sum`
3295
+
3296
+ TESTS:
3297
+
3298
+ This is test ``YsumR12`` in CMR's ``test_tu.cpp``::
3299
+
3300
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3301
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 6, sparse=True),
3302
+ ....: [[1,0,1,1,0,0],[0,1,1,1,0,0],[1,0,1,0,1,1],
3303
+ ....: [0,-1,0,-1,1,1],[1,0,1,0,1,0],[0,-1,0,-1,0,1]])
3304
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
3305
+ ....: decompose_strategy="y_pivot",
3306
+ ....: row_keys=range(6),
3307
+ ....: column_keys='abcdef')
3308
+ sage: certificate.child_keys()
3309
+ ((0, 1, a, 3, 4, 5), (2, b, c, d, e, f))
3310
+ sage: C = certificate.child_nodes()[0]
3311
+ sage: C1, C2 = C.child_nodes()
3312
+ sage: C1.matrix()
3313
+ [ 0 0 1 1]
3314
+ [ 1 1 1 0]
3315
+ [ 0 1 0 -1]
3316
+ [-1 0 -1 0]
3317
+ [-1 0 -1 -1]
3318
+ sage: C2.matrix()
3319
+ [-1 1 -1 -1]
3320
+ [ 0 1 -1 -1]
3321
+ [ 1 0 1 1]
3322
+ [ 0 1 0 -1]
3323
+ [ 1 0 0 1]
3324
+ sage: C.child_keys()
3325
+ (((0, 1, a, 3, -2+3), (b, c, d, 2)), ((+0-b, 0, 3, 4, 5), (b, 2, e, f)))
3326
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
3327
+ sage: node = UnknownNode(R12,
3328
+ ....: row_keys=range(6),
3329
+ ....: column_keys='abcdef'); node
3330
+ UnknownNode (6×6)
3331
+ sage: C0 = node.complete_decomposition(
3332
+ ....: decompose_strategy="y_pivot",
3333
+ ....: )
3334
+ sage: C0
3335
+ PivotsNode (6×6)
3336
+ sage: unicode_art(C0)
3337
+ PivotsNode (6×6)
3338
+
3339
+ ╭─YSumNode (6×6)──╮
3340
+ │ │
3341
+ GraphicNode (5×4) GraphicNode (5×4)
3342
+ sage: unicode_art(node)
3343
+ UnknownNode (6×6)
3344
+ """
3345
+ if self._child_nodes is not None:
3346
+ return self._child_nodes
3347
+
3348
+ if self.nchildren() != 2:
3349
+ raise ValueError(f"YSumNode has exactly two children not {self.nchildren()}!")
3350
+
3351
+ self.set_default_keys()
3352
+
3353
+ cdef CMR_SEYMOUR_NODE *child1_dec = CMRseymourChild(self._dec, 0)
3354
+ cdef CMR_ELEMENT *parent_rows1 = CMRseymourChildRowsToParent(self._dec, 0)
3355
+ cdef CMR_ELEMENT *parent_columns1 = CMRseymourChildColumnsToParent(self._dec, 0)
3356
+ cdef CMR_CHRMAT *mat1 = CMRseymourGetMatrix(child1_dec)
3357
+
3358
+ cdef CMR_SEYMOUR_NODE *child2_dec = CMRseymourChild(self._dec, 1)
3359
+ cdef CMR_ELEMENT *parent_rows2 = CMRseymourChildRowsToParent(self._dec, 1)
3360
+ cdef CMR_ELEMENT *parent_columns2 = CMRseymourChildColumnsToParent(self._dec, 1)
3361
+ cdef CMR_CHRMAT *mat2 = CMRseymourGetMatrix(child2_dec)
3362
+
3363
+ cdef size_t index1
3364
+
3365
+ child1_nrows = CMRseymourNumRows(child1_dec)
3366
+ child1_ncols = CMRseymourNumColumns(child1_dec)
3367
+
3368
+ child1_row_keys = tuple(self._CMRelement_to_key(parent_rows1[i])
3369
+ for i in range(child1_nrows - 1))
3370
+ child1_column_keys = tuple(self._CMRelement_to_key(parent_columns1[i])
3371
+ for i in range(child1_ncols))
3372
+
3373
+ row1_index = child1_nrows - 1
3374
+ column1_index = child1_ncols - 1
3375
+ CMR_CALL(CMRchrmatFindEntry(mat1, row1_index, column1_index, &index1))
3376
+ if index1 == SIZE_MAX:
3377
+ eps1 = Integer(0)
3378
+ else:
3379
+ eps1 = Integer(mat1.entryValues[index1])
3380
+ if eps1 != 1 and eps1 != -1:
3381
+ raise ValueError(f"First child in the Y Sum "
3382
+ f"has 1 or -1 in the entry "
3383
+ f"row {row1_index} and column {column1_index} "
3384
+ f"but got {eps1}")
3385
+
3386
+ extra_key = ElementKey((1, child1_row_keys[row1_index - 1],
3387
+ eps1, child1_column_keys[column1_index]),
3388
+ composition=True)
3389
+ child1_row_keys += (extra_key,)
3390
+
3391
+ child1 = create_DecompositionNode(child1_dec, matrix=None,
3392
+ row_keys=child1_row_keys,
3393
+ column_keys=child1_column_keys,
3394
+ base_ring=self.base_ring())
3395
+
3396
+ child2_nrows = CMRseymourNumRows(child2_dec)
3397
+ child2_ncols = CMRseymourNumColumns(child2_dec)
3398
+
3399
+ child2_row_keys = tuple(self._CMRelement_to_key(parent_rows2[i])
3400
+ for i in range(1, child2_nrows))
3401
+ child2_column_keys = tuple(self._CMRelement_to_key(parent_columns2[i])
3402
+ for i in range(child2_ncols))
3403
+
3404
+ row2_index = 0
3405
+ column2_index = 0
3406
+ CMR_CALL(CMRchrmatFindEntry(mat2, row2_index, column2_index, &index1))
3407
+ if index1 == SIZE_MAX:
3408
+ eps1 = Integer(0)
3409
+ else:
3410
+ eps1 = Integer(mat2.entryValues[index1])
3411
+
3412
+ if eps1 != 1 and eps1 != -1:
3413
+ raise ValueError(f"Second child in the Y Sum "
3414
+ f"has 1 or -1 in the entry "
3415
+ f"row {row2_index} and column {column2_index} "
3416
+ f"but got {eps1}")
3417
+
3418
+ extra_key = ElementKey((1, child2_row_keys[row2_index],
3419
+ eps1, child2_column_keys[column2_index]),
3420
+ composition=True)
3421
+ child2_row_keys = (extra_key,) + child2_row_keys
3422
+
3423
+ child2 = create_DecompositionNode(child2_dec, matrix=None,
3424
+ row_keys=child2_row_keys,
3425
+ column_keys=child2_column_keys,
3426
+ base_ring=self.base_ring())
3427
+
3428
+ self._child_nodes = ((child1, child1_row_keys, child1_column_keys),
3429
+ (child2, child2_row_keys, child2_column_keys))
3430
+ return self._child_nodes
3431
+
3432
+ def is_distributed_ranks(self):
3433
+ r"""
3434
+ Return ``True`` for the `\Delta`-sum node ``self``.
3435
+
3436
+ ``distributed_ranks`` is named after the two rank 1 off-diagonal blocks.
3437
+
3438
+ .. SEEALSO::
3439
+
3440
+ :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.y_sum`
3441
+
3442
+ EXAMPLES::
3443
+
3444
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3445
+ sage: R12_large = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 9, 12, sparse=True),
3446
+ ....: [[ 1, -1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
3447
+ ....: [ 0, 0, 0, 1, -1, 0, 0, 0, 1, 1, 1, 1],
3448
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
3449
+ ....: [ 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0],
3450
+ ....: [ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, -1, -1],
3451
+ ....: [ 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0],
3452
+ ....: [ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, -1, -1],
3453
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0],
3454
+ ....: [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1]])
3455
+ sage: result, certificate = R12_large.is_totally_unimodular(certificate=True,
3456
+ ....: decompose_strategy="y_pivot")
3457
+ sage: C = certificate.child_nodes()[0]; C
3458
+ YSumNode (9×12)
3459
+ sage: C.is_distributed_ranks()
3460
+ True
3461
+ sage: C.is_concentrated_rank()
3462
+ False
3463
+ """
3464
+ return True
3465
+
3466
+ def is_concentrated_rank(self):
3467
+ r"""
3468
+ Return ``False`` for the `\Delta`-sum node ``self``.
3469
+
3470
+ ``concentrated_rank`` is named after the rank 2 and rank 0 off-diagonal blocks.
3471
+
3472
+ .. SEEALSO::
3473
+
3474
+ :meth:`sage.matrix.matrix_cmr_sparse.Matrix_cmr_chr_sparse.y_sum`
3475
+ """
3476
+ return False
3477
+
3478
+ def block_matrix_form(self):
3479
+ r"""
3480
+ Return the block matrix constructed from the Y-sum of children.
3481
+
3482
+ EXAMPLES::
3483
+
3484
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3485
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 6, sparse=True),
3486
+ ....: [[1,0,1,1,0,0],[0,1,1,1,0,0],[1,0,1,0,1,1],
3487
+ ....: [0,-1,0,-1,1,1],[1,0,1,0,1,0],[0,-1,0,-1,0,1]])
3488
+ sage: R12
3489
+ [ 1 0 1 1 0 0]
3490
+ [ 0 1 1 1 0 0]
3491
+ [ 1 0 1 0 1 1]
3492
+ [ 0 -1 0 -1 1 1]
3493
+ [ 1 0 1 0 1 0]
3494
+ [ 0 -1 0 -1 0 1]
3495
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
3496
+ ....: decompose_strategy="y_pivot")
3497
+ sage: C = certificate.child_nodes()[0]; C
3498
+ YSumNode (6×6)
3499
+ sage: C.matrix()
3500
+ [ 1 0 0 1 -1 -1]
3501
+ [ 0 1 1 1 0 0]
3502
+ [-1 0 1 0 1 1]
3503
+ [ 0 -1 0 -1 1 1]
3504
+ [ 1 0 0 0 0 -1]
3505
+ [ 0 -1 0 -1 0 1]
3506
+ sage: C.summand_matrices()
3507
+ (
3508
+ [ 0 0 1 1] [-1 1 -1 -1]
3509
+ [ 1 1 1 0] [ 0 1 -1 -1]
3510
+ [ 0 1 0 -1] [ 1 0 1 1]
3511
+ [-1 0 -1 0] [ 0 1 0 -1]
3512
+ [-1 0 -1 -1], [ 1 0 0 1]
3513
+ )
3514
+ sage: C.row_keys()
3515
+ (r0, r1, c0, r3, r4, r5)
3516
+ sage: C.column_keys()
3517
+ (r2, c1, c2, c3, c4, c5)
3518
+ sage: C.child_keys()
3519
+ (((r0, r1, c0, r3, -r2+r3), (c1, c2, c3, r2)),
3520
+ ((-c1+r0, r0, r3, r4, r5), (c1, r2, c4, c5)))
3521
+ sage: C.block_matrix_form()
3522
+ [ 0 0 1 1 -1 -1]
3523
+ [ 1 1 1 0 0 0]
3524
+ [ 0 1 0 -1 1 1]
3525
+ [-1 0 -1 0 1 1]
3526
+ [ 0 0 0 1 0 -1]
3527
+ [-1 0 -1 0 0 1]
3528
+ """
3529
+ M1, M2 = self.summand_matrices()
3530
+ return Matrix_cmr_chr_sparse.y_sum(M1, M2)
3531
+
3532
+ def permuted_block_matrix(self):
3533
+ r"""
3534
+ Return the permutation matrices between the matrix
3535
+ and the block matrix form.
3536
+
3537
+ OUTPUT: a tuple ``(Prow, BlockMatrix, Pcolumn)``, where
3538
+ ``self.matrix() == Prow * BlockMatrix * Pcolumn``, and
3539
+ ``BlockMatrix == self.block_matrix_form()``.
3540
+
3541
+ EXAMPLES::
3542
+
3543
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3544
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 6, sparse=True),
3545
+ ....: [[1,0,1,1,0,0],[0,1,1,1,0,0],[1,0,1,0,1,1],
3546
+ ....: [0,-1,0,-1,1,1],[1,0,1,0,1,0],[0,-1,0,-1,0,1]])
3547
+ sage: R12
3548
+ [ 1 0 1 1 0 0]
3549
+ [ 0 1 1 1 0 0]
3550
+ [ 1 0 1 0 1 1]
3551
+ [ 0 -1 0 -1 1 1]
3552
+ [ 1 0 1 0 1 0]
3553
+ [ 0 -1 0 -1 0 1]
3554
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
3555
+ ....: decompose_strategy="y_pivot")
3556
+ sage: C = certificate.child_nodes()[0]; C
3557
+ YSumNode (6×6)
3558
+ sage: C.matrix()
3559
+ [ 1 0 0 1 -1 -1]
3560
+ [ 0 1 1 1 0 0]
3561
+ [-1 0 1 0 1 1]
3562
+ [ 0 -1 0 -1 1 1]
3563
+ [ 1 0 0 0 0 -1]
3564
+ [ 0 -1 0 -1 0 1]
3565
+ sage: C.block_matrix_form()
3566
+ [ 0 0 1 1 -1 -1]
3567
+ [ 1 1 1 0 0 0]
3568
+ [ 0 1 0 -1 1 1]
3569
+ [-1 0 -1 0 1 1]
3570
+ [ 0 0 0 1 0 -1]
3571
+ [-1 0 -1 0 0 1]
3572
+ sage: P_row, block_matrix, P_column = C.permuted_block_matrix()
3573
+ sage: P_row^(-1) * C.matrix() * P_column^(-1) == block_matrix
3574
+ True
3575
+ """
3576
+ from sage.combinat.permutation import Permutation
3577
+ cdef CMR_SEYMOUR_NODE *child_dec
3578
+ cdef CMR_ELEMENT *parent_rows
3579
+ cdef CMR_ELEMENT *parent_columns
3580
+ children_row = tuple()
3581
+ children_column = tuple()
3582
+ for index in range(self.nchildren()):
3583
+ child_dec = CMRseymourChild(self._dec, index)
3584
+ parent_rows = CMRseymourChildRowsToParent(self._dec, index)
3585
+ parent_columns = CMRseymourChildColumnsToParent(self._dec, index)
3586
+ child_nrows = CMRseymourNumRows(child_dec)
3587
+ child_ncols = CMRseymourNumColumns(child_dec)
3588
+
3589
+ if parent_rows == NULL or all(parent_rows[i] == 0 for i in range(child_nrows)):
3590
+ raise ValueError(f"Child {index} does not have parents rows")
3591
+ parent_rows_tuple = tuple(parent_rows[i] for i in range(child_nrows))
3592
+
3593
+ if parent_columns == NULL or all(parent_columns[i] == 0 for i in range(child_ncols)):
3594
+ raise ValueError(f"Child {index} does not have parents columns")
3595
+ parent_columns_tuple = tuple(parent_columns[i] for i in range(child_ncols))
3596
+ if index == 0:
3597
+ child_row = tuple(CMRelementToRowIndex(element) + 1
3598
+ for element in parent_rows_tuple[:-2])
3599
+ child_column = tuple(CMRelementToColumnIndex(element) + 1
3600
+ for element in parent_columns_tuple[:-1])
3601
+ else:
3602
+ child_row = tuple(CMRelementToRowIndex(element) + 1
3603
+ for element in parent_rows_tuple[2:])
3604
+ child_column = tuple(CMRelementToColumnIndex(element) + 1
3605
+ for element in parent_columns_tuple[1:])
3606
+ children_row += child_row
3607
+ children_column += child_column
3608
+ P_row = Permutation(list(children_row)).to_matrix()
3609
+ P_column = Permutation(list(children_column)).to_matrix().transpose()
3610
+ return (P_row, self.block_matrix_form(), P_column)
3611
+
3612
+
3613
+ cdef class BaseGraphicNode(DecompositionNode):
3614
+
3615
+ def __init__(self, matrix=None,
3616
+ graph=None, forest_edges=None, coforest_edges=None,
3617
+ row_keys=None, column_keys=None, base_ring=None):
3618
+ r"""
3619
+ Base class for :class:`GraphicNode`, :class:`CographicNode`, and :class:`PlanarNode`
3620
+
3621
+ If ``base_ring`` is `\GF{2}`, then it represents a graphic/cographic/planar matroid.
3622
+
3623
+ Suppose that ``self.matrix()`` is a graphic matrix of a graph `G`
3624
+ with respect to `T`, a spanning forest of the graph `G`.
3625
+
3626
+ - ``self._graph`` is the graph `G = (V,E)`.
3627
+
3628
+ - ``self._forest_edges`` is the edges of `T`.
3629
+
3630
+ - ``self._coforest_edges`` is the edges of `E \setminus T`.
3631
+
3632
+ If ``base_ring`` is `\GF{3}` or `\ZZ`, then it represents a network/conetwork
3633
+ or a both network and conetwork matrix.
3634
+
3635
+ Suppose that ``self.matrix()`` is a network matrix of a digraph `D`
3636
+ with respect to `T`, a directed spanning forest of the underlying undirected graph.
3637
+
3638
+ - ``self._graph`` is the digraph `D = (V,A)`.
3639
+
3640
+ - ``self._forest_edges`` is the arcs of `T`.
3641
+
3642
+ - ``self._coforest_edges`` is the arcs of `A \setminus T`.
3643
+ """
3644
+ super().__init__(matrix=matrix, row_keys=row_keys, column_keys=column_keys,
3645
+ base_ring=base_ring)
3646
+ self._graph = graph
3647
+ self._forest_edges = forest_edges
3648
+ self._coforest_edges = coforest_edges
3649
+
3650
+ def graph(self):
3651
+ r"""
3652
+ Return the graph representing ``self``.
3653
+
3654
+ If ``self.base_ring()`` is `\GF{2}`, then return an undirected graph.
3655
+ If ``self.base_ring()`` is `\GF{3}` or `\ZZ`, then return directed graph.
3656
+
3657
+ EXAMPLES:
3658
+
3659
+ Undirected graph::
3660
+
3661
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3662
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
3663
+ ....: [[1, 0], [1, 1], [0,1]]); M
3664
+ [1 0]
3665
+ [1 1]
3666
+ [0 1]
3667
+ sage: result, certificate = M._is_binary_linear_matroid_regular(certificate=True)
3668
+ sage: result, certificate
3669
+ (True, GraphicNode (3×2))
3670
+ sage: G = certificate.graph(); G
3671
+ Graph on 4 vertices
3672
+ sage: G.vertices(sort=True)
3673
+ [1, 2, 7, 12]
3674
+ sage: G.edges(sort=True)
3675
+ [(1, 2, None), (1, 7, None), (1, 12, None), (2, 7, None), (7, 12, None)]
3676
+
3677
+ Directed graph::
3678
+
3679
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3680
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
3681
+ ....: [[1, 0], [-1, 1], [0,-1]]); M
3682
+ [ 1 0]
3683
+ [-1 1]
3684
+ [ 0 -1]
3685
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
3686
+ sage: result, certificate
3687
+ (True, GraphicNode (3×2))
3688
+ sage: G = certificate.graph(); G
3689
+ Digraph on 4 vertices
3690
+ sage: G.vertices(sort=True)
3691
+ [1, 2, 7, 12]
3692
+ sage: G.edges(sort=True)
3693
+ [(2, 1, None), (2, 7, None), (7, 1, None), (7, 12, None), (12, 1, None)]
3694
+ """
3695
+ if self._graph is not None:
3696
+ return self._graph
3697
+ base_ring = self.base_ring()
3698
+ if base_ring.characteristic() == 2:
3699
+ self._graph = _sage_graph(CMRseymourGraph(self._dec))
3700
+ else:
3701
+ self._graph = _sage_digraph(CMRseymourGraph(self._dec),
3702
+ CMRseymourGraphArcsReversed(self._dec))
3703
+ return self._graph
3704
+
3705
+ def forest_edges(self):
3706
+ r"""
3707
+ Return the forest edges of ``self``.
3708
+
3709
+ If ``self.base_ring()`` is `\GF{2}`, then return edges.
3710
+ If ``self.base_ring()`` is `\GF{3}` or `\ZZ`, then arcs.
3711
+
3712
+ EXAMPLES:
3713
+
3714
+ Undirected graph::
3715
+
3716
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3717
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
3718
+ ....: [[1, 0], [1, 1], [0,1]]); M
3719
+ [1 0]
3720
+ [1 1]
3721
+ [0 1]
3722
+ sage: result, certificate = M._is_binary_linear_matroid_regular(certificate=True)
3723
+ sage: result, certificate
3724
+ (True, GraphicNode (3×2))
3725
+ sage: certificate.forest_edges()
3726
+ ((1, 2), (7, 1), (12, 7))
3727
+ sage: certificate.coforest_edges()
3728
+ ((2, 7), (1, 12))
3729
+
3730
+ Directed graph::
3731
+
3732
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3733
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
3734
+ ....: [[1, 0], [-1, 1], [0,-1]]); M
3735
+ [ 1 0]
3736
+ [-1 1]
3737
+ [ 0 -1]
3738
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
3739
+ sage: result, certificate
3740
+ (True, GraphicNode (3×2))
3741
+ sage: certificate.forest_edges()
3742
+ ((2, 1), (7, 1), (7, 12))
3743
+ sage: certificate.coforest_edges()
3744
+ ((2, 7), (12, 1))
3745
+
3746
+ Starting with a morphism::
3747
+
3748
+ sage: from sage.matrix.seymour_decomposition import UnknownNode
3749
+ sage: phi = matrix(ZZ, [[1, 0], [1, 1], [0, 1]],
3750
+ ....: row_keys=['a', 'b', 'c'], column_keys=['v', 'w'])
3751
+ sage: phi; phi._unicode_art_matrix()
3752
+ Generic morphism:
3753
+ From: Free module generated by {'v', 'w'} over Integer Ring
3754
+ To: Free module generated by {'a', 'b', 'c'} over Integer Ring
3755
+ v w
3756
+ a⎛1 0⎞
3757
+ b⎜1 1⎟
3758
+ c⎝0 1⎠
3759
+ sage: phi_node = UnknownNode(phi)
3760
+ sage: is_graphic, rephined_node = phi_node._is_binary_linear_matroid_graphic(decomposition=True)
3761
+ sage: is_graphic, rephined_node
3762
+ (True, GraphicNode (3×2))
3763
+ sage: rephined_node.forest_edges()
3764
+ {'a': (1, 2), 'b': (7, 1), 'c': (12, 7)}
3765
+ sage: phi_node # still in the dark about graphicness
3766
+ UnknownNode (3×2)
3767
+ """
3768
+ if self._forest_edges is not None:
3769
+ return self._forest_edges
3770
+ cdef CMR_GRAPH *graph = CMRseymourGraph(self._dec)
3771
+ cdef size_t num_edges = CMRseymourGraphSizeForest(self._dec)
3772
+ cdef CMR_GRAPH_EDGE *edges = CMRseymourGraphForest(self._dec)
3773
+ cdef bool *arcs_reversed
3774
+ base_ring = self.base_ring()
3775
+ if base_ring.characteristic() == 2:
3776
+ self._forest_edges = _sage_edges(graph, edges, num_edges, self.row_keys())
3777
+ else:
3778
+ arcs_reversed = CMRseymourGraphArcsReversed(self._dec)
3779
+ self._forest_edges = _sage_arcs(graph, edges, arcs_reversed, num_edges, self.row_keys())
3780
+ return self._forest_edges
3781
+
3782
+ def coforest_edges(self):
3783
+ r"""
3784
+ Return the forest edges of ``self``.
3785
+
3786
+ If ``self.base_ring()`` is `\GF{2}`, then return edges.
3787
+ If ``self.base_ring()`` is `\GF{3}` or `\ZZ`, then arcs.
3788
+
3789
+ EXAMPLES::
3790
+
3791
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3792
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 6, 7, sparse=True),
3793
+ ....: [[-1, 0, 0, 0, 1, -1, 0],
3794
+ ....: [ 1, 0, 0, 1, -1, 1, 0],
3795
+ ....: [ 0, -1, 0, -1, 1, -1, 0],
3796
+ ....: [ 0, 1, 0, 0, 0, 0, 1],
3797
+ ....: [ 0, 0, 1, -1, 1, 0, 1],
3798
+ ....: [ 0, 0, -1, 1, -1, 0, 0]])
3799
+ sage: M.is_network_matrix()
3800
+ True
3801
+ sage: result, certificate = M.is_network_matrix(certificate=True)
3802
+ sage: result, certificate
3803
+ (True,
3804
+ (Digraph on 7 vertices,
3805
+ ((9, 8), (3, 8), (3, 4), (5, 4), (4, 6), (0, 6)),
3806
+ ((3, 9), (5, 3), (4, 0), (0, 8), (9, 0), (4, 9), (5, 6))))
3807
+ sage: digraph, forest_arcs, coforest_arcs = certificate
3808
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
3809
+ sage: certificate.graph() == digraph
3810
+ True
3811
+ sage: certificate.forest_edges() == forest_arcs
3812
+ True
3813
+ sage: certificate.coforest_edges() == coforest_arcs
3814
+ True
3815
+ """
3816
+ if self._coforest_edges is not None:
3817
+ return self._coforest_edges
3818
+ cdef CMR_GRAPH *graph = CMRseymourGraph(self._dec)
3819
+ cdef size_t num_edges = CMRseymourGraphSizeCoforest(self._dec)
3820
+ cdef CMR_GRAPH_EDGE *edges = CMRseymourGraphCoforest(self._dec)
3821
+ cdef bool *arcs_reversed
3822
+ base_ring = self.base_ring()
3823
+ if base_ring.characteristic() == 2:
3824
+ self._coforest_edges = _sage_edges(graph, edges, num_edges, self.column_keys())
3825
+ else:
3826
+ arcs_reversed = CMRseymourGraphArcsReversed(self._dec)
3827
+ self._coforest_edges = _sage_arcs(graph, edges, arcs_reversed, num_edges, self.column_keys())
3828
+ return self._coforest_edges
3829
+
3830
+
3831
+ cdef class GraphicNode(BaseGraphicNode):
3832
+
3833
+ pass
3834
+
3835
+
3836
+ cdef class CographicNode(BaseGraphicNode):
3837
+ @cached_method
3838
+ def graph(self):
3839
+ r"""
3840
+ Actually the cograph of matrix, in the case where it is not graphic.
3841
+
3842
+ EXAMPLES:
3843
+
3844
+ Undirected graph::
3845
+
3846
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3847
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 4, 5, sparse=True),
3848
+ ....: [[1, 1, 1, 0, 0],
3849
+ ....: [0, 1, 1, 1, 0],
3850
+ ....: [0, 0, 1, 1, 1],
3851
+ ....: [1, 0, 0, 1, 1]]); M
3852
+ [1 1 1 0 0]
3853
+ [0 1 1 1 0]
3854
+ [0 0 1 1 1]
3855
+ [1 0 0 1 1]
3856
+ sage: result, certificate = M._is_binary_linear_matroid_regular(certificate=True)
3857
+ sage: certificate
3858
+ CographicNode (4×5)
3859
+ sage: certificate.base_ring()
3860
+ Finite Field of size 2
3861
+ sage: G = certificate.graph(); G
3862
+ Graph on 6 vertices
3863
+ sage: G.vertices(sort=True)
3864
+ [0, 1, 2, 5, 7, 8]
3865
+ sage: G.edges(sort=True)
3866
+ [(0, 2, None), (0, 5, None), (0, 7, None), (1, 2, None), (1, 5, None),
3867
+ (1, 7, None), (2, 8, None), (5, 8, None), (7, 8, None)]
3868
+
3869
+ Directed graph::
3870
+
3871
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3872
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 4, 5, sparse=True),
3873
+ ....: [[1, -1, 1, 0, 0],
3874
+ ....: [0, 1, -1, 1, 0],
3875
+ ....: [0, 0, 1, -1, 1],
3876
+ ....: [1, 0, 0, 1, -1]]); M
3877
+ [ 1 -1 1 0 0]
3878
+ [ 0 1 -1 1 0]
3879
+ [ 0 0 1 -1 1]
3880
+ [ 1 0 0 1 -1]
3881
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
3882
+ sage: certificate
3883
+ CographicNode (4×5)
3884
+ sage: G = certificate.graph(); G
3885
+ Digraph on 6 vertices
3886
+ sage: G.vertices(sort=True)
3887
+ [0, 1, 2, 5, 7, 8]
3888
+ sage: G.edges(sort=True)
3889
+ [(0, 2, None), (0, 5, None), (0, 7, None), (1, 2, None), (1, 5, None),
3890
+ (1, 7, None), (2, 8, None), (5, 8, None), (7, 8, None)]
3891
+ """
3892
+ if self._graph is not None:
3893
+ return self._graph
3894
+ base_ring = self.base_ring()
3895
+ if base_ring.characteristic() == 2:
3896
+ self._graph = _sage_graph(CMRseymourCograph(self._dec))
3897
+ else:
3898
+ self._graph = _sage_digraph(CMRseymourCograph(self._dec),
3899
+ CMRseymourCographArcsReversed(self._dec))
3900
+ return self._graph
3901
+
3902
+ @cached_method
3903
+ def forest_edges(self):
3904
+ r"""
3905
+ Return the forest edges of the cograph of ``self``.
3906
+
3907
+ EXAMPLES:
3908
+
3909
+ Undirected graph::
3910
+
3911
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3912
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 4, 5, sparse=True),
3913
+ ....: [[1, 1, 1, 0, 0],
3914
+ ....: [0, 1, 1, 1, 0],
3915
+ ....: [0, 0, 1, 1, 1],
3916
+ ....: [1, 0, 0, 1, 1]]); M
3917
+ [1 1 1 0 0]
3918
+ [0 1 1 1 0]
3919
+ [0 0 1 1 1]
3920
+ [1 0 0 1 1]
3921
+ sage: result, certificate = M._is_binary_linear_matroid_regular(certificate=True)
3922
+ sage: result, certificate
3923
+ (True, CographicNode (4×5))
3924
+ sage: certificate.forest_edges()
3925
+ ((7, 8), (5, 0), (0, 7), (1, 7), (2, 1))
3926
+ sage: certificate.coforest_edges()
3927
+ ((5, 8), (5, 1), (0, 2), (2, 8))
3928
+
3929
+ Directed graph::
3930
+
3931
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3932
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 4, 5, sparse=True),
3933
+ ....: [[1, -1, 1, 0, 0],
3934
+ ....: [0, 1, -1, 1, 0],
3935
+ ....: [0, 0, 1, -1, 1],
3936
+ ....: [1, 0, 0, 1, -1]]); M
3937
+ [ 1 -1 1 0 0]
3938
+ [ 0 1 -1 1 0]
3939
+ [ 0 0 1 -1 1]
3940
+ [ 1 0 0 1 -1]
3941
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
3942
+ sage: result, certificate
3943
+ (True, CographicNode (4×5))
3944
+ sage: certificate.forest_edges()
3945
+ ((7, 8), (0, 5), (0, 7), (1, 7), (1, 2))
3946
+ sage: certificate.coforest_edges()
3947
+ ((5, 8), (1, 5), (0, 2), (2, 8))
3948
+ """
3949
+ if self._forest_edges is not None:
3950
+ return self._forest_edges
3951
+ cdef CMR_GRAPH *graph = CMRseymourCograph(self._dec)
3952
+ cdef size_t num_edges = CMRseymourCographSizeForest(self._dec)
3953
+ cdef CMR_GRAPH_EDGE *edges = CMRseymourCographForest(self._dec)
3954
+ cdef bool *arcs_reversed
3955
+ base_ring = self.base_ring()
3956
+ if base_ring.characteristic() == 2:
3957
+ self._forest_edges = _sage_edges(graph, edges, num_edges, self.row_keys())
3958
+ else:
3959
+ arcs_reversed = CMRseymourCographArcsReversed(self._dec)
3960
+ self._forest_edges = _sage_arcs(graph, edges, arcs_reversed, num_edges, self.row_keys())
3961
+ return self._forest_edges
3962
+
3963
+ @cached_method
3964
+ def coforest_edges(self):
3965
+ r"""
3966
+ Return the forest edges of the cograph of ``self``.
3967
+ """
3968
+ if self._coforest_edges is not None:
3969
+ return self._coforest_edges
3970
+ cdef CMR_GRAPH *graph = CMRseymourCograph(self._dec)
3971
+ cdef size_t num_edges = CMRseymourCographSizeCoforest(self._dec)
3972
+ cdef CMR_GRAPH_EDGE *edges = CMRseymourCographCoforest(self._dec)
3973
+ cdef bool *arcs_reversed
3974
+ base_ring = self.base_ring()
3975
+ if base_ring.characteristic() == 2:
3976
+ self._coforest_edges = _sage_edges(graph, edges, num_edges, self.column_keys())
3977
+ else:
3978
+ arcs_reversed = CMRseymourCographArcsReversed(self._dec)
3979
+ self._coforest_edges = _sage_arcs(graph, edges, arcs_reversed, num_edges, self.column_keys())
3980
+ return self._coforest_edges
3981
+
3982
+
3983
+ cdef class PlanarNode(BaseGraphicNode):
3984
+ @cached_method
3985
+ def cograph(self):
3986
+ r"""
3987
+ Return the cograph of matrix.
3988
+
3989
+ EXAMPLES:
3990
+
3991
+ Undirected graph::
3992
+
3993
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
3994
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
3995
+ ....: [[1, 0], [1, 1], [0,1]]); M
3996
+ [1 0]
3997
+ [1 1]
3998
+ [0 1]
3999
+ sage: result, certificate = M._is_binary_linear_matroid_regular(certificate=True,
4000
+ ....: check_graphic_minors_planar=True)
4001
+ sage: result, certificate
4002
+ (True, PlanarNode (3×2))
4003
+ sage: G = certificate.cograph(); G
4004
+ Graph on 3 vertices
4005
+ sage: G.vertices(sort=True)
4006
+ [1, 2, 7]
4007
+ sage: G.edges(sort=True)
4008
+ [(1, 2, None), (1, 7, None), (2, 7, None)]
4009
+ sage: certificate.cograph_forest_edges()
4010
+ ((1, 2), (7, 1))
4011
+ sage: certificate.cograph_coforest_edges()
4012
+ ((1, 2), (2, 7), (7, 1))
4013
+
4014
+ Directed graph::
4015
+
4016
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
4017
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 3, 2, sparse=True),
4018
+ ....: [[1, 0], [-1, 1], [0,-1]]); M
4019
+ [ 1 0]
4020
+ [-1 1]
4021
+ [ 0 -1]
4022
+ sage: result, certificate = M.is_totally_unimodular(certificate=True,
4023
+ ....: check_graphic_minors_planar=True)
4024
+ sage: result, certificate
4025
+ (True, PlanarNode (3×2))
4026
+ sage: G = certificate.cograph(); G
4027
+ Digraph on 3 vertices
4028
+ sage: G.vertices(sort=True)
4029
+ [1, 2, 7]
4030
+ sage: G.edges(sort=True)
4031
+ [(1, 2, None), (1, 7, None), (2, 7, None), (7, 1, None)]
4032
+ sage: certificate.cograph_forest_edges()
4033
+ ((1, 2), (1, 7))
4034
+ sage: certificate.cograph_coforest_edges()
4035
+ ((1, 2), (2, 7), (7, 1))
4036
+ """
4037
+ if self._cograph is not None:
4038
+ return self._cograph
4039
+ base_ring = self.base_ring()
4040
+ if base_ring.characteristic() == 2:
4041
+ self._cograph = _sage_graph(CMRseymourCograph(self._dec))
4042
+ else:
4043
+ self._cograph = _sage_digraph(CMRseymourCograph(self._dec),
4044
+ CMRseymourCographArcsReversed(self._dec))
4045
+ return self._cograph
4046
+
4047
+ @cached_method
4048
+ def cograph_forest_edges(self):
4049
+ r"""
4050
+ Return the forest edges of the cograph of ``self``.
4051
+ """
4052
+ if self._cograph_forest_edges is not None:
4053
+ return self._cograph_forest_edges
4054
+ cdef CMR_GRAPH *cograph = CMRseymourCograph(self._dec)
4055
+ cdef size_t num_edges = CMRseymourCographSizeForest(self._dec)
4056
+ cdef CMR_GRAPH_EDGE *edges = CMRseymourCographForest(self._dec)
4057
+ cdef bool *arcs_reversed
4058
+ base_ring = self.base_ring()
4059
+ if base_ring.characteristic() == 2:
4060
+ self._cograph_forest_edges = _sage_edges(cograph, edges, num_edges, self.row_keys())
4061
+ else:
4062
+ arcs_reversed = CMRseymourCographArcsReversed(self._dec)
4063
+ self._cograph_forest_edges = _sage_arcs(cograph, edges, arcs_reversed, num_edges, self.row_keys())
4064
+ return self._cograph_forest_edges
4065
+
4066
+ @cached_method
4067
+ def cograph_coforest_edges(self):
4068
+ r"""
4069
+ Return the forest edges of the cograph of ``self``.
4070
+ """
4071
+ if self._cograph_coforest_edges is not None:
4072
+ return self._cograph_coforest_edges
4073
+ cdef CMR_GRAPH *cograph = CMRseymourCograph(self._dec)
4074
+ cdef size_t num_edges = CMRseymourCographSizeCoforest(self._dec)
4075
+ cdef CMR_GRAPH_EDGE *edges = CMRseymourCographCoforest(self._dec)
4076
+ cdef bool *arcs_reversed
4077
+ base_ring = self.base_ring()
4078
+ if base_ring.characteristic() == 2:
4079
+ self._cograph_coforest_edges = _sage_edges(cograph, edges, num_edges, self.column_keys())
4080
+ else:
4081
+ arcs_reversed = CMRseymourCographArcsReversed(self._dec)
4082
+ self._cograph_coforest_edges = _sage_arcs(cograph, edges, arcs_reversed, num_edges, self.column_keys())
4083
+ return self._cograph_coforest_edges
4084
+
4085
+
4086
+ cdef class SeriesParallelReductionNode(DecompositionNode):
4087
+
4088
+ def core(self):
4089
+ r"""
4090
+ Return the core of ``self``.
4091
+
4092
+ A :class:`SeriesParallelReductionNode` indicates that `M`
4093
+ arises from a smaller matrix `M'` (called the *core*)
4094
+ by successively adding zero rows/columns,
4095
+ unit rows/columns or duplicates of existing rows/columns
4096
+ (potentially scaled with `-1`).
4097
+
4098
+ Note that such series-parallel reductions preserve total unimodularity
4099
+ and binary regularity.
4100
+
4101
+ EXAMPLES::
4102
+
4103
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
4104
+ sage: M = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 5, 6, sparse=True),
4105
+ ....: [[1, 1, 1, 1, 1, 0], [1, 1, 1, 0, 0, 0],
4106
+ ....: [1, 0, 1, 1, 0, 1], [1, 0, 0, 1, 1, 0],
4107
+ ....: [1, 1, 0, 0, 1, 0]]); M
4108
+ [1 1 1 1 1 0]
4109
+ [1 1 1 0 0 0]
4110
+ [1 0 1 1 0 1]
4111
+ [1 0 0 1 1 0]
4112
+ [1 1 0 0 1 0]
4113
+ sage: result, certificate = M.is_totally_unimodular(certificate=True)
4114
+ sage: result, certificate
4115
+ (True, SeriesParallelReductionNode (5×6))
4116
+ sage: certificate.core()
4117
+ [1 1 1 1 1]
4118
+ [1 1 1 0 0]
4119
+ [1 0 1 1 0]
4120
+ [1 0 0 1 1]
4121
+ [1 1 0 0 1]
4122
+ """
4123
+ return self.child_nodes()[0].matrix()
4124
+
4125
+
4126
+ cdef class R10Node(DecompositionNode):
4127
+ r"""
4128
+ Special R10 Node.
4129
+ Only two possible 5 by 5 matrices up to row and column permutations and negations.
4130
+
4131
+ EXAMPLES::
4132
+
4133
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
4134
+ sage: R10 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 5, 5, sparse=True),
4135
+ ....: [[1, 0, 0, 1, 1],
4136
+ ....: [1, 1, 0, 0, 1],
4137
+ ....: [0, 1, 1, 0, 1],
4138
+ ....: [0, 0, 1, 1, 1],
4139
+ ....: [1, 1, 1, 1, 1]]); R10
4140
+ [1 0 0 1 1]
4141
+ [1 1 0 0 1]
4142
+ [0 1 1 0 1]
4143
+ [0 0 1 1 1]
4144
+ [1 1 1 1 1]
4145
+ sage: result, certificate = R10.is_totally_unimodular(certificate=True)
4146
+ sage: result
4147
+ True
4148
+ sage: R10.is_network_matrix()
4149
+ False
4150
+ sage: R10.is_conetwork_matrix()
4151
+ False
4152
+ sage: result, certificate = R10._is_binary_linear_matroid_regular(certificate=True)
4153
+ sage: result
4154
+ True
4155
+ sage: certificate._is_binary_linear_matroid_graphic()
4156
+ False
4157
+ sage: certificate._is_binary_linear_matroid_cographic()
4158
+ False
4159
+
4160
+ sage: R10 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 5, 5, sparse=True),
4161
+ ....: [[ 1,-1, 0, 0,-1],
4162
+ ....: [-1, 1,-1, 0, 0],
4163
+ ....: [ 0,-1, 1,-1, 0],
4164
+ ....: [ 0, 0,-1, 1,-1],
4165
+ ....: [-1, 0, 0,-1, 1]]); R10
4166
+ [ 1 -1 0 0 -1]
4167
+ [-1 1 -1 0 0]
4168
+ [ 0 -1 1 -1 0]
4169
+ [ 0 0 -1 1 -1]
4170
+ [-1 0 0 -1 1]
4171
+ sage: result, certificate = R10.is_totally_unimodular(certificate=True)
4172
+ sage: result
4173
+ True
4174
+ sage: R10.is_network_matrix()
4175
+ False
4176
+ sage: R10.is_conetwork_matrix()
4177
+ False
4178
+
4179
+ sage: R10 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 5, 5, sparse=True),
4180
+ ....: [[1, 1, 0, 0, 1],
4181
+ ....: [1, 1,-1, 0, 0],
4182
+ ....: [0, 1,-1,-1, 0],
4183
+ ....: [0, 0, 1, 1, 1],
4184
+ ....: [1, 0, 0, 1, 1]]); R10
4185
+ [ 1 1 0 0 1]
4186
+ [ 1 1 -1 0 0]
4187
+ [ 0 1 -1 -1 0]
4188
+ [ 0 0 1 1 1]
4189
+ [ 1 0 0 1 1]
4190
+ sage: result, certificate = R10.is_totally_unimodular(certificate=True)
4191
+ sage: result
4192
+ True
4193
+ sage: R10.is_network_matrix()
4194
+ False
4195
+ sage: R10.is_conetwork_matrix()
4196
+ False
4197
+
4198
+ sage: R10 = Matrix_cmr_chr_sparse(MatrixSpace(GF(2), 5, 5, sparse=True),
4199
+ ....: [[1, 1, 0, 0, 1],
4200
+ ....: [1, 1,-1, 0, 0],
4201
+ ....: [0, 1,-1,-1, 0],
4202
+ ....: [0, 0, 1, 1, 1],
4203
+ ....: [1, 0, 0, 1, 1]]); R10
4204
+ [1 1 0 0 1]
4205
+ [1 1 1 0 0]
4206
+ [0 1 1 1 0]
4207
+ [0 0 1 1 1]
4208
+ [1 0 0 1 1]
4209
+ sage: result, certificate = R10._is_binary_linear_matroid_regular(certificate=True)
4210
+ sage: result
4211
+ True
4212
+ sage: certificate
4213
+ R10Node (5×5) a reduced matrix representation of R10 matroid
4214
+ sage: certificate._is_binary_linear_matroid_graphic()
4215
+ False
4216
+ sage: certificate._is_binary_linear_matroid_cographic()
4217
+ False
4218
+ """
4219
+
4220
+ @cached_method
4221
+ def _matroid(self):
4222
+ r"""
4223
+ Return the R10 matroid represented by ``self``.
4224
+ ``self.matrix()`` is the reduced matrix representation of the matroid.
4225
+
4226
+ EXAMPLES::
4227
+
4228
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
4229
+ sage: R10 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 5, 5, sparse=True),
4230
+ ....: [[1, 1, 1, 1, 1],
4231
+ ....: [1, 1, 1, 0, 0],
4232
+ ....: [1, 0, 1, 1, 0],
4233
+ ....: [1, 0, 0, 1, 1],
4234
+ ....: [1, 1, 0, 0, 1]]); R10
4235
+ [1 1 1 1 1]
4236
+ [1 1 1 0 0]
4237
+ [1 0 1 1 0]
4238
+ [1 0 0 1 1]
4239
+ [1 1 0 0 1]
4240
+ sage: result, certificate = R10.is_totally_unimodular(certificate=True,
4241
+ ....: row_keys=range(5),
4242
+ ....: column_keys='abcde')
4243
+ sage: certificate._matroid()
4244
+ R10: Regular matroid of rank 5 on 10 elements with 162 bases
4245
+ sage: certificate._isomorphism()
4246
+ {'a': 0,
4247
+ 'b': 1,
4248
+ 'c': 2,
4249
+ 'd': 'a',
4250
+ 'e': 'b',
4251
+ 'f': 4,
4252
+ 'g': 'c',
4253
+ 'h': 'e',
4254
+ 'i': 3,
4255
+ 'j': 'd'}
4256
+ sage: result, certificate = R10.is_totally_unimodular(certificate=True)
4257
+ sage: certificate._matroid()
4258
+ R10: Regular matroid of rank 5 on 10 elements with 162 bases
4259
+ sage: certificate._isomorphism()
4260
+ {'a': 0,
4261
+ 'b': 1,
4262
+ 'c': 2,
4263
+ 'd': 5,
4264
+ 'e': 6,
4265
+ 'f': 4,
4266
+ 'g': 7,
4267
+ 'h': 9,
4268
+ 'i': 3,
4269
+ 'j': 8}
4270
+
4271
+ sage: R10 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 5, 5, sparse=True),
4272
+ ....: [[ 1,-1, 0, 0,-1],
4273
+ ....: [-1, 1,-1, 0, 0],
4274
+ ....: [ 0,-1, 1,-1, 0],
4275
+ ....: [ 0, 0,-1, 1,-1],
4276
+ ....: [-1, 0, 0,-1, 1]]); R10
4277
+ [ 1 -1 0 0 -1]
4278
+ [-1 1 -1 0 0]
4279
+ [ 0 -1 1 -1 0]
4280
+ [ 0 0 -1 1 -1]
4281
+ [-1 0 0 -1 1]
4282
+ sage: result, certificate = R10.is_totally_unimodular(certificate=True,
4283
+ ....: row_keys=range(5),
4284
+ ....: column_keys='abcde')
4285
+ sage: certificate._matroid()
4286
+ R10: Regular matroid of rank 5 on 10 elements with 162 bases
4287
+ """
4288
+ from sage.matroids.constructor import Matroid
4289
+ if self.row_keys() is None or self.column_keys() is None:
4290
+ M = Matroid(reduced_matrix=self.matrix(), regular=True)
4291
+ else:
4292
+ M = Matroid(reduced_morphism=self.morphism(), regular=True)
4293
+ M.rename("R10: " + repr(M))
4294
+ return M
4295
+
4296
+ def _isomorphism(self):
4297
+ r"""
4298
+ Return one isomorphism between the R10 matroid
4299
+ and ``self._matroid()``.
4300
+ """
4301
+ import sage.matroids.matroids_catalog as matroids
4302
+ M0 = matroids.catalog.R10()
4303
+ result, isomorphism = M0.is_isomorphic(self._matroid(), certificate=True)
4304
+ if result is False:
4305
+ raise ValueError("This is not a R10 node")
4306
+ else:
4307
+ return isomorphism
4308
+
4309
+ def _repr_(self):
4310
+ r"""
4311
+ Return a string representation of ``self``.
4312
+ """
4313
+ result = super()._repr_()
4314
+ result += f' a reduced matrix representation of R10 matroid'
4315
+ return result
4316
+
4317
+
4318
+ cdef class PivotsNode(DecompositionNode):
4319
+
4320
+ def npivots(self):
4321
+ r"""
4322
+ Return the number of pivots in ``self``.
4323
+
4324
+ EXAMPLES::
4325
+
4326
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
4327
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 9, 12, sparse=True),
4328
+ ....: [[ 1, -1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
4329
+ ....: [ 0, 0, 0, 1, -1, 0, 0, 0, 1, 1, 1, 1],
4330
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
4331
+ ....: [ 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0],
4332
+ ....: [ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, -1, -1],
4333
+ ....: [ 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0],
4334
+ ....: [ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, -1, -1],
4335
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0],
4336
+ ....: [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1]])
4337
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
4338
+ ....: decompose_strategy="delta_pivot")
4339
+ sage: certificate
4340
+ PivotsNode (9×12)
4341
+ sage: certificate.npivots()
4342
+ 1
4343
+ """
4344
+ return CMRseymourNumPivots(self._dec)
4345
+
4346
+ @cached_method
4347
+ def pivot_rows_and_columns(self):
4348
+ r"""
4349
+ Return a tuple of the row and column indices of all pivot entries.
4350
+
4351
+ EXAMPLES::
4352
+
4353
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
4354
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 9, 12, sparse=True),
4355
+ ....: [[ 1, -1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
4356
+ ....: [ 0, 0, 0, 1, -1, 0, 0, 0, 1, 1, 1, 1],
4357
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
4358
+ ....: [ 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0],
4359
+ ....: [ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, -1, -1],
4360
+ ....: [ 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0],
4361
+ ....: [ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, -1, -1],
4362
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0],
4363
+ ....: [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1]])
4364
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
4365
+ ....: decompose_strategy="delta_pivot")
4366
+ sage: certificate
4367
+ PivotsNode (9×12)
4368
+ sage: certificate.pivot_rows_and_columns()
4369
+ ((0, 8),)
4370
+ """
4371
+ cdef size_t *pivot_rows = CMRseymourPivotRows(self._dec)
4372
+ cdef size_t *pivot_columns = CMRseymourPivotColumns(self._dec)
4373
+
4374
+ return tuple((pivot_rows[i], pivot_columns[i]) for i in range(self.npivots()))
4375
+
4376
+ def _children(self):
4377
+ r"""
4378
+ Return a tuple of the tuples of children and their row and column keys.
4379
+ The underlying implementation of :meth:`child_nodes`
4380
+ and :meth:`child_keys`.
4381
+
4382
+ If row and column keys are not given, set the default keys.
4383
+ See alse :meth:set_default_keys
4384
+
4385
+ EXAMPLES::
4386
+
4387
+ sage: from sage.matrix.matrix_cmr_sparse import Matrix_cmr_chr_sparse
4388
+ sage: R12 = Matrix_cmr_chr_sparse(MatrixSpace(ZZ, 9, 12, sparse=True),
4389
+ ....: [[ 1, -1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
4390
+ ....: [ 0, 0, 0, 1, -1, 0, 0, 0, 1, 1, 1, 1],
4391
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
4392
+ ....: [ 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0],
4393
+ ....: [ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, -1, -1],
4394
+ ....: [ 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0],
4395
+ ....: [ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, -1, -1],
4396
+ ....: [ 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0],
4397
+ ....: [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1]])
4398
+ sage: result, certificate = R12.is_totally_unimodular(certificate=True,
4399
+ ....: decompose_strategy="delta_pivot")
4400
+ sage: certificate
4401
+ PivotsNode (9×12)
4402
+ sage: certificate.row_keys()
4403
+ sage: certificate.child_nodes()
4404
+ (DeltaSumNode (9×12),)
4405
+ sage: certificate.row_keys()
4406
+ (r0, r1, r2, r3, r4, r5, r6, r7, r8)
4407
+ """
4408
+ if self._child_nodes is not None:
4409
+ return self._child_nodes
4410
+ self.set_default_keys()
4411
+ children_tuple = tuple(self._create_child_node(index)
4412
+ for index in range(self.nchildren()))
4413
+ self._child_nodes = children_tuple
4414
+ return self._child_nodes
4415
+
4416
+
4417
+ cdef class ElementKey:
4418
+
4419
+ cdef frozenset _key
4420
+ cdef bint _composition
4421
+
4422
+ def __init__(self, keys, composition=False):
4423
+ r"""
4424
+ Create the element key for a row or column index
4425
+ of :class:`DecompositionNode`.
4426
+
4427
+ INPUT:
4428
+
4429
+ - ``keys`` -- a row/column key or a tuple
4430
+ (`\pm 1`, row/column key, `\pm 1`, row/column key).
4431
+
4432
+ - ``composition`` -- ``True`` or ``False`` (default).
4433
+ whether the key is a composition key or not.
4434
+ If ``False``, ``self._key`` is a frozenset with a row/column key.
4435
+ If ``True``, ``self._key`` is a frozenset with two tuples,
4436
+ where each tuple has a sign value and a row/column key.
4437
+ For example, ``frozenset((1,'a'), (-1,'7'))``.
4438
+ """
4439
+ if composition:
4440
+ sign1, key1, sign2, key2 = keys
4441
+ self._key = frozenset([(sign1, key1), (sign2, key2)])
4442
+ self._composition = True
4443
+ else:
4444
+ self._key = frozenset((keys,))
4445
+ self._composition = False
4446
+
4447
+ @property
4448
+ def key(self):
4449
+ return self._key
4450
+
4451
+ def __hash__(self):
4452
+ """
4453
+ Return a hash of this element key
4454
+ """
4455
+ return hash(self.key)
4456
+
4457
+ def __eq__(self, other):
4458
+ if isinstance(other, ElementKey):
4459
+ return self.key == other.key
4460
+ return False
4461
+
4462
+ def __repr__(self):
4463
+ """
4464
+ Return a string representation of ``self``.
4465
+ The composition key is sorted by the string of keys.
4466
+ """
4467
+ if self._composition:
4468
+ sorted_key = sorted(self.key, key=lambda x: (str(x[1]), x[0]))
4469
+ return "".join(['+'+str(a[1]) if a[0] == 1 else '-'+str(a[1]) for a in sorted_key])
4470
+ else:
4471
+ return "".join([str(a) for a in self.key])
4472
+
4473
+
4474
+ cdef _class(CMR_SEYMOUR_NODE *dec):
4475
+ r"""
4476
+ Return the class of the decomposition `CMR_SEYMOUR_NODE`.
4477
+
4478
+ INPUT:
4479
+
4480
+ - ``dec`` -- a ``CMR_SEYMOUR_NODE``
4481
+ """
4482
+ cdef CMR_SEYMOUR_NODE_TYPE typ = CMRseymourType(dec)
4483
+
4484
+ if typ == CMR_SEYMOUR_NODE_TYPE_ONESUM:
4485
+ return OneSumNode
4486
+ if typ == CMR_SEYMOUR_NODE_TYPE_TWOSUM:
4487
+ return TwoSumNode
4488
+ if typ == CMR_SEYMOUR_NODE_TYPE_DELTASUM:
4489
+ return DeltaSumNode
4490
+ if typ == CMR_SEYMOUR_NODE_TYPE_THREESUM:
4491
+ return ThreeSumNode
4492
+ if typ == CMR_SEYMOUR_NODE_TYPE_YSUM:
4493
+ return YSumNode
4494
+ if typ == CMR_SEYMOUR_NODE_TYPE_GRAPH:
4495
+ return GraphicNode
4496
+ if typ == CMR_SEYMOUR_NODE_TYPE_COGRAPH:
4497
+ return CographicNode
4498
+ if typ == CMR_SEYMOUR_NODE_TYPE_PLANAR:
4499
+ return PlanarNode
4500
+ if typ == CMR_SEYMOUR_NODE_TYPE_SERIES_PARALLEL:
4501
+ return SeriesParallelReductionNode
4502
+ if typ == CMR_SEYMOUR_NODE_TYPE_PIVOTS:
4503
+ return PivotsNode
4504
+ if typ == CMR_SEYMOUR_NODE_TYPE_IRREGULAR:
4505
+ return ThreeConnectedIrregularNode
4506
+ if typ == CMR_SEYMOUR_NODE_TYPE_UNKNOWN:
4507
+ return UnknownNode
4508
+ if typ == CMR_SEYMOUR_NODE_TYPE_R10:
4509
+ return R10Node
4510
+ raise NotImplementedError
4511
+
4512
+
4513
+ cdef create_DecompositionNode(CMR_SEYMOUR_NODE *dec, matrix=None, row_keys=None, column_keys=None, base_ring=None):
4514
+ r"""
4515
+ Create an instance of a subclass of :class:`DecompositionNode`.
4516
+
4517
+ INPUT:
4518
+
4519
+ - ``dec`` -- a ``CMR_SEYMOUR_NODE``
4520
+ """
4521
+ if dec == NULL:
4522
+ return None
4523
+ cdef DecompositionNode result = <DecompositionNode> _class(dec)(
4524
+ matrix, row_keys=row_keys, column_keys=column_keys, base_ring=base_ring)
4525
+ result._set_dec(dec)
4526
+ return result