Home Plotting Hasse Diagrams With Networkx (A First Approximation)
Post
Cancel

Plotting Hasse Diagrams With Networkx (A First Approximation)

Disapointed by ChatGPT in Asking ChatGPT to Code a Plotting Function for Covering Diagrams but inspired by some of the diagrams in The Joy of Abstraction: An Exploration of Math, Category Theory, and Life by Eugenia Cheng, I decided to take a quick attempt at plotting Hasse diagrams for the factorization of positive integers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
from collections import Counter
from functools import reduce
from typing import Dict, Set

import matplotlib.pyplot as plt
import networkx as nx
import numpy as np


def factors(n: int) -> Set[int]:
    """
    Compute the factors of a given number.

    Parameters:
        n (int): The number for which factors are to be computed.

    Returns:
        set[int]: A set containing the factors of the given number.
    """
    return set(
        reduce(
            list.__add__,
            ([i, n // i] for i in range(1, int(n**0.5) + 1) if n % i == 0),
        )
    )


def make_graph(x: int) -> nx.DiGraph:
    """
    Create a directed graph based on the factors of a given number.

    Parameters:
        x (int): The number for which the graph is to be created.

    Returns:
        nx.DiGraph: A directed graph representing the relationships between factors, after transitive reduction.
    """
    nodes = factors(x)
    g = nx.DiGraph()
    for ni in nodes:
        for nj in nodes:
            if ni != nj:
                if not ni % nj:
                    g.add_edge(nj, ni)
    g = nx.transitive_reduction(g)
    return g


def pos_hasse(g: nx.DiGraph) -> Dict[int, np.ndarray]:
    """
    Compute the positions of nodes in a Hasse diagram based on their grades.

    Parameters:
        g (nx.DiGraph): The directed graph representing the Hasse diagram.

    Returns:
        Dict[int, np.ndarray]: A dictionary mapping node IDs to their positions in the diagram.
    """
    grades = nx.shortest_path_length(g, source=1)
    grade_counts = Counter(grades.values())

    pos = {}

    for grade in set(grades.values()):
        gnodes = [node for node in grades if grades[node] == grade]
        gnodes = sorted(gnodes)
        center = len(gnodes) / 2
        for i, ni in enumerate(gnodes):
            pos[ni] = np.array([i - center, grades[ni]])

    return pos


if __name__ == "__main__":
    graph = make_graph(2018)
    pos = pos_hasse(graph)
    nx.draw(graph, pos=pos, with_labels=True)
    plt.savefig('2023-06-10-hasse-diagram.png', dpi=300, transparent=True)
    plt.close()

For the choice of integer 2018 given above we get the following diagram.

Hasse Diagram

This post is licensed under CC BY 4.0 by the author.

Jaime Krem's Tackling Friendship

Wilcoxon's Heuristic