Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

matroids: Add certificate argument to is_valid #38711

Merged
merged 6 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/sage/matroids/basis_exchange_matroid.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,6 @@ cdef class BasisExchangeMatroid(Matroid):
cpdef _is_isomorphism(self, other, morphism)
cdef bint __is_isomorphism(self, BasisExchangeMatroid other, morphism) noexcept

cpdef bint is_valid(self) noexcept
cpdef is_valid(self, certificate=*)

cdef bint nxksrd(bitset_s *b, long n, long k, bint succ) noexcept
18 changes: 11 additions & 7 deletions src/sage/matroids/basis_exchange_matroid.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2231,7 +2231,7 @@ cdef class BasisExchangeMatroid(Matroid):

return self._characteristic_setsystem()._isomorphism(other._characteristic_setsystem(), PS, PO) is not None

cpdef bint is_valid(self) noexcept:
cpdef is_valid(self, certificate=False):
r"""
Test if the data obey the matroid axioms.

Expand All @@ -2242,7 +2242,11 @@ cdef class BasisExchangeMatroid(Matroid):
* if `X` and `Y` are in `B`, and `x` is in `X - Y`, then there is a
`y` in `Y - X` such that `(X - x) + y` is again a member of `B`.

OUTPUT: boolean
INPUT:

- ``certificate`` -- boolean (default: ``False``)

OUTPUT: boolean, or (boolean, dictionary)

EXAMPLES::

Expand All @@ -2251,8 +2255,8 @@ cdef class BasisExchangeMatroid(Matroid):
sage: M.is_valid()
True
sage: M = Matroid(groundset='abcd', bases=['ab', 'cd'])
sage: M.is_valid()
False
sage: M.is_valid(certificate=True)
(False, {'error': 'exchange axiom failed'})

TESTS:

Expand All @@ -2278,7 +2282,7 @@ cdef class BasisExchangeMatroid(Matroid):
if not bitset_eq(self._current_basis, BB._subsets[pointerY]):
# We failed to set the current basis to Y through basis exchanges.
# Therefore, the exchange axioms are violated!
return False
return False if not certificate else (False, {"error": "exchange axiom failed"})
bitset_difference(self._input, BB._subsets[pointerX], BB._subsets[pointerY])
bitset_difference(self._input2, BB._subsets[pointerY], BB._subsets[pointerX])
x = bitset_first(self._input)
Expand All @@ -2292,11 +2296,11 @@ cdef class BasisExchangeMatroid(Matroid):
else:
y = bitset_next(self._input2, y + 1)
if not foundpair:
return False
return False if not certificate else (False, {"error": "exchange axiom failed"})
x = bitset_next(self._input, x + 1)
pointerY += 1
pointerX += 1
return True
return True if not certificate else (True, {})

cdef bint nxksrd(bitset_s* b, long n, long k, bint succ) noexcept:
"""
Expand Down
2 changes: 1 addition & 1 deletion src/sage/matroids/circuits_matroid.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ cdef class CircuitsMatroid(Matroid):
cpdef relabel(self, mapping)

# verification
cpdef bint is_valid(self) noexcept
cpdef is_valid(self, certificate=*)
24 changes: 16 additions & 8 deletions src/sage/matroids/circuits_matroid.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -869,13 +869,17 @@ cdef class CircuitsMatroid(Matroid):

# verification

cpdef bint is_valid(self) noexcept:
cpdef is_valid(self, certificate=False):
r"""
Test if ``self`` obeys the matroid axioms.

For a matroid defined by its circuits, we check the circuit axioms.

OUTPUT: boolean
INPUT:

- ``certificate`` -- boolean (default: ``False``)

OUTPUT: boolean, or (boolean, dictionary)

EXAMPLES::

Expand All @@ -901,8 +905,12 @@ cdef class CircuitsMatroid(Matroid):
False
sage: C = [[1, 2, 3], [3, 4, 5]]
sage: M = Matroid(circuits=C)
sage: M.is_valid()
False
sage: M.is_valid(certificate=True)
(False,
{'circuit 1': frozenset({...}),
'circuit 2': frozenset({...}),
'element': 3,
'error': 'elimination axiom failed'})
"""
from itertools import combinations_with_replacement
cdef int i, j
Expand All @@ -911,7 +919,7 @@ cdef class CircuitsMatroid(Matroid):
# loop through all circuit length pairs (i, j) with i <= j
for C1 in self._k_C[i]:
if not C1: # the empty set can't be a circuit
return False
return False if not certificate else (False, {"error": "the empty set can't be a circuit"})
for C2 in self._k_C[j]:
I12 = C1 & C2
if not I12: # C1 and C2 are disjoint; nothing to test
Expand All @@ -920,10 +928,10 @@ cdef class CircuitsMatroid(Matroid):
if len(C1) == len(C2): # they are the same circuit
break
# C1 < C2; a circuit can't be a subset of another circuit
return False
return False if not certificate else (False, {"error": "a circuit can't be a subset of another circuit", "circuit 1": C1, "circuit 2": C2})
# check circuit elimination axiom
U12 = C1 | C2
for e in I12:
if self._is_independent(U12 - {e}):
return False
return True
return False if not certificate else (False, {"error": "elimination axiom failed", "circuit 1": C1, "circuit 2": C2, "element": e})
return True if not certificate else (True, {})
15 changes: 13 additions & 2 deletions src/sage/matroids/dual_matroid.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,13 +545,17 @@
M = self._matroid.relabel(mapping).dual()
return M

def is_valid(self):
def is_valid(self, certificate=False):
"""
Test if ``self`` obeys the matroid axioms.

For a :class:`DualMatroid`, we check its dual.

OUTPUT: boolean
INPUT:

- ``certificate`` -- boolean (default: ``False``)

OUTPUT: boolean, or (boolean, dictionary)

EXAMPLES::

Expand All @@ -564,4 +568,11 @@
sage: M.dual().is_valid()
False
"""
if certificate:
v, c = self._matroid.is_valid(certificate)
if v:
return True, {}

Check warning on line 574 in src/sage/matroids/dual_matroid.py

View check run for this annotation

Codecov / codecov/patch

src/sage/matroids/dual_matroid.py#L572-L574

Added lines #L572 - L574 were not covered by tests
else:
c["error"] = "the dual matroid is not valid: " + c["error"]
return v, c

Check warning on line 577 in src/sage/matroids/dual_matroid.py

View check run for this annotation

Codecov / codecov/patch

src/sage/matroids/dual_matroid.py#L576-L577

Added lines #L576 - L577 were not covered by tests
return self._matroid.is_valid()
2 changes: 1 addition & 1 deletion src/sage/matroids/flats_matroid.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ cdef class FlatsMatroid(Matroid):
cpdef relabel(self, mapping)

# verification
cpdef bint is_valid(self) noexcept
cpdef is_valid(self, certificate=*)
69 changes: 43 additions & 26 deletions src/sage/matroids/flats_matroid.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ cdef class FlatsMatroid(Matroid):

# verification

cpdef bint is_valid(self) noexcept:
cpdef is_valid(self, certificate=False):
r"""
Test if ``self`` obeys the matroid axioms.

Expand All @@ -548,7 +548,11 @@ cdef class FlatsMatroid(Matroid):
If the lattice of flats has already been computed, we instead perform
the equivalent check of whether it forms a geometric lattice.

OUTPUT: boolean
INPUT:

- ``certificate`` -- boolean (default: ``False``)

OUTPUT: boolean, or (boolean, dictionary)

EXAMPLES::

Expand Down Expand Up @@ -586,8 +590,8 @@ cdef class FlatsMatroid(Matroid):
....: '06a','16b','268','369','07b','178','279','37a',
....: '0123c','89abc',
....: '0123456789abc'])
sage: M.is_valid()
False
sage: M.is_valid(certificate=True)
(False, {'error': 'the lattice of flats is not geometric'})
sage: Matroid(matroids.catalog.Fano().lattice_of_flats()).is_valid()
True

Expand All @@ -609,41 +613,54 @@ cdef class FlatsMatroid(Matroid):

TESTS::

sage: Matroid(flats={0: [], 1: [[0], [1]], 2: [[0, 1]]}).is_valid() # missing an intersection
False
sage: Matroid(flats={0: [[]], 2: [[0], [1]], 3: [[0, 1]]}).is_valid() # invalid ranks
False
sage: Matroid(flats={0: [[]], 1: [[0], [1]], 2: [[0], [0, 1]]}).is_valid() # duplicates
False
sage: Matroid(flats={0: [[]], 1: [[0], [1], [0, 1]]}).is_valid()
False
sage: Matroid(flats={0: [[]], 1: [[0, 1], [2]], 2: [[0], [1], [0, 1, 2]]}).is_valid()
False
sage: Matroid(flats={0: [], 1: [[0], [1]], 2: [[0, 1]]}).is_valid(certificate=True) # missing an intersection
(False, {'error': 'flats dictionary has invalid ranks'})
sage: Matroid(flats={0: [[]], 2: [[0], [1]], 3: [[0, 1]]}).is_valid(certificate=True) # invalid ranks
(False, {'error': 'flats dictionary has invalid ranks'})
sage: Matroid(flats={0: [[]], 1: [[0], [1]], 2: [[0], [0, 1]]}).is_valid(certificate=True) # duplicates
(False, {'error': 'flats dictionary has repeated flats'})
sage: Matroid(flats={0: [[]], 1: [[0], [1], [0, 1]]}).is_valid(certificate=True)
(False,
{'error': 'a single element extension of a flat must be a subset of exactly one flat',
'flat': frozenset()})
sage: Matroid(flats={0: [[]], 1: [[0, 1], [2]], 2: [[0], [1], [0, 1, 2]]}).is_valid(certificate=True)
(False,
{'error': 'the intersection of two flats must be a flat',
'flat 1': frozenset({0, 1}),
'flat 2': frozenset({1})})
sage: M = Matroid(flats={0: [''], # missing an extension of flat ['5'] by '6'
....: 1: ['0','1','2','3','4','5','6','7','8','9','a','b','c'],
....: 2: ['45','46','47','4c','57','5c','67','6c','7c',
....: '048','149','24a','34b','059','15a','25b','358',
....: '06a','16b','268','369','07b','178','279','37a',
....: '0123c','89abc'],
....: 3: ['0123456789abc']})
sage: M.is_valid()
False
sage: M.is_valid(certificate=True)
(False,
{'error': 'a single element extension of a flat must be a subset of exactly one flat',
'flat': frozenset({'...'})})
sage: M = Matroid(flats=[[], [0], [1], [0], [0, 1]]) # duplicates are ignored
sage: M.lattice_of_flats()
Finite lattice containing 4 elements
sage: M.is_valid()
True
sage: M.is_valid(certificate=True)
(True, {})
sage: M = Matroid(flats=['',
....: '0','1','2','3','4','5','6','7','8','9','a','b','c',
....: '45','46','47','4c','56','57','5c','67','6c','7c',
....: '048','149','24a','34b','059','15a','25b','358',
....: '06a','16b','268','369','07b','178','279','37a',
....: '0123c','89abc',
....: '0123456789abc'])
sage: M.is_valid()
True
sage: M.is_valid(certificate=True)
(True, {})
"""
if self._L is not None: # if the lattice of flats is available
if certificate:
if not self._is_closed(self._groundset):
return False, {"error": "the groundset must be a flat"}
if not self._L.is_geometric():
return False, {"error": "the lattice of flats is not geometric"}
return True, {}
return self._is_closed(self._groundset) and self._L.is_geometric()

cdef int i, j, k
Expand All @@ -654,14 +671,14 @@ cdef class FlatsMatroid(Matroid):
# check flats dictionary for invalid ranks and repeated flats
ranks = list(self._F)
if ranks != list(range(len(ranks))):
return False
return False if not certificate else (False, {"error": "flats dictionary has invalid ranks"})
flats_lst = [F for i in self._F for F in self._F[i]]
if len(flats_lst) != len(set(flats_lst)):
return False
return False if not certificate else (False, {"error": "flats dictionary has repeated flats"})

# the groundset must be a flat
if not self._is_closed(self._groundset):
return False
return False if not certificate else (False, {"error": "the groundset must be a flat"})

# a single element extension of a flat must be a subset of exactly one flat
for i in ranks[:-1]:
Expand All @@ -671,7 +688,7 @@ cdef class FlatsMatroid(Matroid):
if F2 >= F1:
cover.extend(F1 ^ F2)
if len(cover) != len(F1 ^ self._groundset) or set(cover) != F1 ^ self._groundset:
return False
return False if not certificate else (False, {"error": "a single element extension of a flat must be a subset of exactly one flat", "flat": F1})

# the intersection of two flats must be a flat
for i in ranks:
Expand All @@ -688,6 +705,6 @@ cdef class FlatsMatroid(Matroid):
if flag:
break
if not flag:
return False
return False if not certificate else (False, {"error": "the intersection of two flats must be a flat", "flat 1": F1, "flat 2": F2})

return True
return True if not certificate else (True, {})
2 changes: 1 addition & 1 deletion src/sage/matroids/graphic_matroid.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ cdef class GraphicMatroid(Matroid):
cpdef bint _is_closed(self, frozenset X) noexcept
cpdef _is_isomorphic(self, other, certificate=*)
cpdef _isomorphism(self, other)
cpdef bint is_valid(self) noexcept
cpdef is_valid(self, certificate=*)
cpdef bint is_graphic(self) noexcept
cpdef bint is_regular(self) noexcept
cpdef graph(self)
Expand Down
10 changes: 7 additions & 3 deletions src/sage/matroids/graphic_matroid.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1093,13 +1093,17 @@ cdef class GraphicMatroid(Matroid):
"""
return self.is_isomorphic(other, certificate=True)[1]

cpdef bint is_valid(self) noexcept:
cpdef is_valid(self, certificate=False):
"""
Test if the data obey the matroid axioms.

Since a graph is used for the data, this is always the case.

OUTPUT: ``True``
INPUT:

- ``certificate`` -- boolean (default: ``False``)

OUTPUT: ``True``, or ``(True, {})``

EXAMPLES::

Expand All @@ -1108,7 +1112,7 @@ cdef class GraphicMatroid(Matroid):
sage: M.is_valid()
True
"""
return True
return True if not certificate else (True, {})

cpdef bint is_graphic(self) noexcept:
r"""
Expand Down
10 changes: 5 additions & 5 deletions src/sage/matroids/linear_matroid.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ cdef class LinearMatroid(BasisExchangeMatroid):
cpdef _is_3connected_shifting(self, certificate=*)
cpdef _is_4connected_shifting(self, certificate=*)

cpdef bint is_valid(self) noexcept
cpdef is_valid(self, certificate=*)

cdef class BinaryMatroid(LinearMatroid):
cdef tuple _b_invariant, _b_partition
Expand Down Expand Up @@ -92,7 +92,7 @@ cdef class BinaryMatroid(LinearMatroid):
cpdef relabel(self, mapping)

cpdef bint is_graphic(self) noexcept
cpdef bint is_valid(self) noexcept
cpdef is_valid(self, certificate=*)


cdef class TernaryMatroid(LinearMatroid):
Expand Down Expand Up @@ -122,7 +122,7 @@ cdef class TernaryMatroid(LinearMatroid):
cpdef _fast_isom_test(self, other)
cpdef relabel(self, mapping)

cpdef bint is_valid(self) noexcept
cpdef is_valid(self, certificate=*)

cdef class QuaternaryMatroid(LinearMatroid):
cdef object _x_zero, _x_one
Expand All @@ -149,7 +149,7 @@ cdef class QuaternaryMatroid(LinearMatroid):
cpdef _fast_isom_test(self, other)
cpdef relabel(self, mapping)

cpdef bint is_valid(self) noexcept
cpdef is_valid(self, certificate=*)

cdef class RegularMatroid(LinearMatroid):
cdef _bases_count, _r_invariant
Expand All @@ -174,4 +174,4 @@ cdef class RegularMatroid(LinearMatroid):

cpdef bint is_regular(self) noexcept
cpdef bint is_graphic(self) noexcept
cpdef bint is_valid(self) noexcept
cpdef is_valid(self, certificate=*)
Loading
Loading