Skip to content

Implementation of concepts related to fundamental theorem of semidistributive lattices - #42593

Open
Hey-Ya7 wants to merge 17 commits into
sagemath:developfrom
Hey-Ya7:develop
Open

Implementation of concepts related to fundamental theorem of semidistributive lattices#42593
Hey-Ya7 wants to merge 17 commits into
sagemath:developfrom
Hey-Ya7:develop

Conversation

@Hey-Ya7

@Hey-Ya7 Hey-Ya7 commented Jul 28, 2026

Copy link
Copy Markdown

This PR implements key concepts from Reading, Speyer, and Thomas' 2024 paper, "The fundamental theorem of finite semidistributive lattices" . Specifically, it adds the following methods to the file src/sage/graphs/digraph.py :

  • right_orthogonal() and left_orthogonal() : these compute the right and left orthogonal sets of some subset of vertices of a directed graph. These sets are defined on page 3 of the aforementioned paper, although they are not explicitly named.
  • maximal_orthogonal_pairs_lattice() : this computes the lattice of maximal orthogonal pairs, an object also defined on page 3 of the paper and that has been studied before in different contexts (i.e. Markowsky's representation theorem)
  • surjective_edges() and injective_edges() : these compute the "onto" and "into" relations as defined in the paper (also on page 3). The paper considers the factorization of an arbitrary reflexive binary relation; here, we consider the representation of the relation as a directed graph, which can also be seen as defining the relation as x -> y if x = y or xy is an edge.
  • is_two_acyclic_factorization_system() : checks whether a directed graph forms a two-acyclic factorization system. as before, this is defined and discussed in detail in the paper.

The main motivation behind all this is the Fundamental Theorem of Semidistributive Lattices, which says that any finite semidistributive lattice is isomorphic to some lattice of orthogonal pairs of a two-acyclic factorization system.

📝 Checklist

  • The title is concise and informative.
  • The description explains in detail what this PR is about.
  • I have linked a relevant issue or discussion.
  • I have created tests covering the changes.
  • I have updated the documentation and checked the documentation preview.

⌛ Dependencies

I am unaware of any PRs that this implementation depends on.

@Hey-Ya7
Hey-Ya7 marked this pull request as draft July 28, 2026 13:43
@mantepse mantepse self-assigned this Jul 29, 2026
@Hey-Ya7
Hey-Ya7 marked this pull request as ready for review July 30, 2026 06:21
@fchapoton

Copy link
Copy Markdown
Contributor

Le plugin "Lint" est pas content, merci de corriger tout ca.

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

Documentation preview for this PR (built with commit 76a6204; changes) is ready! 🎉
This preview will update shortly after each push to this PR.

Comment thread src/sage/graphs/digraph.py Outdated

@dcoudert dcoudert left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for contributing Sagemath.

However, I don't think these methods should be added to DiGraph. These methods are very specific to lattices and so should be somewhere in src/combinat/posets/....

Below are some commets on your code. Similar comments apply to method is_two_acyclic_factorization_system.

Comment thread src/sage/graphs/digraph.py Outdated

- ``X`` -- Set; a subset of vertices

OUTPUT: The right orthogonal of X as a set of vertices.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

X -> `X`

Comment thread src/sage/graphs/digraph.py Outdated
....: G = digraphs.RandomDirectedGNP(10, .3)
....: assert G.right_orthogonal(set(G)) == set()
"""
l = set(self.vertices())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use l = set(self) (several times in your code)

Comment thread src/sage/graphs/digraph.py Outdated

- ``X`` -- Set; a subset of vertices

OUTPUT: The left orthogonal of X as a set of vertices.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

Comment thread src/sage/graphs/digraph.py Outdated
G = DiGraph()
from sage.sets.set import Set
G.add_vertex(Set(self.vertices()))
pairs = {Set(self.vertices()): Set()} # dictionary of pairs, where

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

place the comments above the declaration of pairs. It makes it easier to respect the 80 columns mode.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, please avoid Set in library code.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will need to use some kind of set-like object that is immutable as the algorithm involves indexing a dictionary by some right orthogonal set, would frozenset() be preferred in this case, or is that also depreciated?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

frozenset is fine

Comment thread src/sage/graphs/digraph.py Outdated
pairs = {Set(self.vertices()): Set()} # dictionary of pairs, where
# the first component is indexed by the second
# for example, pairs[second_term] should give first_term
next_pairs = [Set(self.vertices())]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-        next_pairs = [Set(self.vertices())]
-        while next_pairs != []:
+        next_pairs = [Set(self)]
+        while next_pairs:
            new_pairs = []
            for rt in next_pairs:
                covering_pairs = []
-                for x in self.vertex_iterator():
+                for x in self:
                    if x in pairs[rt]:
                        continue
                    # calculate the new right orthogonal by removing vertices
                    new_rt = rt.difference(self.neighbors_out(x))
                    new_rt = new_rt.difference([x])
                    covering_pairs.append(new_rt)
                    if new_rt in pairs:
                        # merge all left components with the same right orthogonal
                        pairs[new_rt] = pairs[new_rt].union(pairs[rt])
                    else:
                        pairs[new_rt] = pairs[rt]
                        new_pairs.append(new_rt)
                    pairs[new_rt] = pairs[new_rt].union(Set({x}))
                # generate the upper covers
                for new_rt in covering_pairs:
                    if rt != new_rt:
-                       G.add_vertex(new_rt)
                        G.add_edge(rt, new_rt)
            next_pairs = new_pairs
        if labels == "left":
            G.relabel(lambda v: pairs[v])
        elif labels != "right":
            G.relabel(lambda v: (pairs[v], v))
        from sage.combinat.posets.lattices import LatticePoset
        return LatticePoset(G)

Comment thread src/sage/graphs/digraph.py Outdated
sage: G.surjective_edges(loops=True)
[]
"""
E = []

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

        E = []
        for x, y in self.edge_iterator(labels=False):
            if x != y:
                if all(self.has_edge(x, z) for z in self.neighbors_out(y)):
                    E.append((x, y))
            elif loops:
                E.append((x, y))
        return E

Comment thread src/sage/graphs/digraph.py Outdated
sage: G.injective_edges(loops=True)
[]
"""
E = []

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

        E = []
        for y, z in self.edge_iterator(labels=False):
            if y != z:
                if all(self.has_edge(x, z) for x in self.neighbors_in(y)):
                    E.append((y, z))
            elif loops:
                E.append((y, z))
        return E

@fchapoton

Copy link
Copy Markdown
Contributor

We now have a category of Semidistributive lattices in place, in src/sage/categories/lattice_posets.py. Maybe this would be the place to put some of these as methods of semidistributive lattices ?

@Hey-Ya7

Hey-Ya7 commented Jul 31, 2026

Copy link
Copy Markdown
Author

Thank you very much for your comments @dcoudert, I have modified my code following your suggestions; however I am unsure where to put these methods. I agree that these methods should probably be somewhere in the combinat/posets folder, but there doesn't really seem to be an appropriate file to add these to - I don't think these would fit well in neither posets.py nor lattices.py as they don't make sense as poset/lattice/semilattice methods.
@fchapoton For the same reason, I don't quite see which methods I could put as methods of semidistributive lattices, as of the 6 methods here, 5 do not operate on nor construct a lattice, and the only constructor isn't guaranteed to give a semidistributive lattice.

I am currently considering to perhaps create a new file under combinat/posets and transfer these methods there, and changing them from class methods to functions taking a DiGraph object as argument. Would this be an acceptable way to implement these changes?

@fchapoton

Copy link
Copy Markdown
Contributor

Oui, an independant file somewhere in combinat/posets seems to be a reasonable idea.

It could be named "semidistributivity.py" maybe.

@fchapoton

Copy link
Copy Markdown
Contributor

there is a wrong change in the file digraph.py

@fchapoton

Copy link
Copy Markdown
Contributor

you can try to sort the doctest to make it reproducible, or just check the length

@Hey-Ya7

Hey-Ya7 commented Aug 4, 2026

Copy link
Copy Markdown
Author

Sorting the list seems to have fixed it, although I'm not sure what's causing the [g-o] test error.

@fchapoton

Copy link
Copy Markdown
Contributor

the failure in libs/ecl is unrelated and happens currently in most pull requests

Comment thread src/sage/combinat/posets/semidistributivity.py Outdated
Comment thread src/sage/combinat/posets/semidistributivity.py Outdated
Comment thread src/sage/combinat/posets/semidistributivity.py Outdated
Comment thread src/sage/combinat/posets/semidistributivity.py
Comment thread src/sage/combinat/posets/semidistributivity.py Outdated
Comment thread src/sage/combinat/posets/semidistributivity.py Outdated
Comment thread src/sage/combinat/posets/semidistributivity.py

sage: from sage.combinat.posets.semidistributivity import is_two_acyclic_factorization_system
sage: G = DiGraph([(0, 1), (1, 0), (0, 0), (1, 1)], loops=True)
sage: is_two_acyclic_factorization_system(G, certificate=True) # (0, 1), (1, 0) are both surjective

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please do not use comments, but write them in documentation main text

Comment thread src/sage/combinat/posets/semidistributivity.py Outdated
@fchapoton

Copy link
Copy Markdown
Contributor

J'ai fait quelques suggestions. Il faut les ajouter au "batch" puis "commiter l'ensemble"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants