geomotif.motifs.graphs
¶
Graph and number art: points on a circle, joined by an arithmetic rule.
Every motif here is the same motif with a different edge rule -- nodes spaced around a circle, and a statement about which pairs get a straight line between them. What makes the family worth a module is how little the rule has to change for the picture to change completely: multiplying by two gives a cardioid, by three a nephroid, and by 51 a figure with no name at all.
The workhorse is :class:ModularMultiplication, the times table drawn as
chords. It is also, viewed from the other side, circle string art -- so
:mod:geomotif.motifs.stringart re-exports it under that name rather than
implementing the same geometry twice.
All of these are :class:~geomotif.SegmentMotif subclasses, so they take
merge=True to chain segments that share an endpoint into longer strokes
(far fewer pen lifts on a plotter) and show_nodes=True to emit the nodes
themselves as loose points.
Classes:
| Name | Description |
|---|---|
CompleteGraph |
Every node joined to every other: K5, K12, and the ones in between. |
CyclicGraph |
The circulant: join every node to the ones a fixed number of steps away. |
BipartiteGraph |
Two rows of nodes, and every line from one row to the other. |
ChordDiagram |
Nodes on a circle and whichever chords you name between them. |
ModularMultiplication |
The times table drawn as chords, which turns out to be a cardioid. |
ModularAddition |
Join each number to the one a fixed distance further round. |
PrimeChords |
Join two numbers whenever they add up to a prime. |
CompleteGraph
dataclass
¶
CompleteGraph(order: int = 12, radius: float = 120.0, rotation: float = pi / 2.0, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
Every node joined to every other: K5, K12, and the ones in between.
The picture mathematicians draw when they say "complete graph", and a
surprisingly good ornament -- the chords cross in a moire that tightens
towards the middle. The edge count is order * (order - 1) / 2, so it
grows quadratically and gets solid black somewhere around forty nodes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
order
|
int
|
Number of nodes. |
12
|
radius
|
float
|
Radius of the circle they sit on. |
120.0
|
rotation
|
float
|
Angle of the first node, in radians. A quarter turn puts it at the top, which is how these are conventionally drawn. |
pi / 2.0
|
center
|
(float, float)
|
Middle of the circle. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
edge_count |
Return how many chords this graph draws. |
CyclicGraph
dataclass
¶
CyclicGraph(order: int = 16, steps: tuple[int, ...] = (1, 3, 5), radius: float = 120.0, rotation: float = pi / 2.0, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
The circulant: join every node to the ones a fixed number of steps away.
One step is the plain cycle, which is a regular polygon. Several steps at
once is where it gets interesting -- each one contributes its own star
polygon and they overlay into a rosette. steps=(1, 2, 3) on a dozen
nodes is a good place to start.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
order
|
int
|
Number of nodes. |
16
|
steps
|
tuple of int
|
How far around to reach. Each step |
(1, 3, 5)
|
radius
|
float
|
Radius of the circle the nodes sit on. |
120.0
|
rotation
|
float
|
Angle of the first node, in radians. |
pi / 2.0
|
center
|
(float, float)
|
Middle of the circle. |
(0.0, 0.0)
|
BipartiteGraph
dataclass
¶
BipartiteGraph(left: int = 4, right: int = 5, span: float = 200.0, height: float = 220.0, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
Two rows of nodes, and every line from one row to the other.
The complete bipartite graph, drawn the way it is drawn in textbooks: two
facing ranks with all left * right connections between them. Every
crossing is visible, which is what makes it useful for showing why K33
cannot be drawn without them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left
|
int
|
Nodes in each rank. |
4
|
right
|
int
|
Nodes in each rank. |
4
|
span
|
float
|
Horizontal distance between the two ranks. |
200.0
|
height
|
float
|
Vertical extent of each rank. |
220.0
|
center
|
(float, float)
|
Middle of the whole figure. |
(0.0, 0.0)
|
ChordDiagram
dataclass
¶
ChordDiagram(order: int = 16, chords: tuple[tuple[int, int], ...] = _EXAMPLE_CHORDS, radius: float = 120.0, rotation: float = pi / 2.0, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = True)
Bases: SegmentMotif
Nodes on a circle and whichever chords you name between them.
The escape hatch of the family, and the one to reach for when the connections come from data rather than from arithmetic -- a dependency graph, a migration table, who talks to whom. The other classes in this module are this one with the chord list computed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
order
|
int
|
Number of nodes around the circle. |
16
|
chords
|
tuple of (int, int)
|
Index pairs to join. Self-loops and repeats are dropped rather than rejected, since a real dataset routinely contains both. |
_EXAMPLE_CHORDS
|
radius
|
float
|
Radius of the circle. |
120.0
|
rotation
|
float
|
Angle of node zero, in radians. |
pi / 2.0
|
center
|
(float, float)
|
Middle of the circle. |
(0.0, 0.0)
|
show_nodes
|
bool
|
Emit the nodes as loose points. On by default here and nowhere else: an arithmetic rule fills the circle densely enough to imply where its nodes are, while a handful of chords from a dataset does not, and without the dots the figure reads as a pile of sticks. |
True
|
ModularMultiplication
dataclass
¶
ModularMultiplication(modulus: int = 200, factor: int = 2, radius: float = 120.0, rotation: float = pi, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
The times table drawn as chords, which turns out to be a cardioid.
Space the numbers 0 to modulus - 1 evenly around a circle and join
each i to factor * i. Doubling gives a cardioid, tripling a
nephroid, and every factor after that an epicycloid with one fewer cusp
than the factor -- none of which is put there deliberately. The cusps are
the envelope of the chords, and the whole family falls out of one line of
arithmetic.
Seen from the other direction this is circle string art, which is why
:mod:geomotif.motifs.stringart re-exports it as StringArtCircle
rather than drawing the same chords twice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
modulus
|
int
|
How many numbers go around the circle. |
200
|
factor
|
int
|
What each is multiplied by. Two for the cardioid, three for the nephroid, and large primes for the dense figures. |
2
|
radius
|
float
|
Radius of the circle. |
120.0
|
rotation
|
float
|
Angle of node zero, in radians. |
pi
|
center
|
(float, float)
|
Middle of the circle. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
cusp_count |
Return how many cusps the chord envelope has: one fewer than the factor. |
cusp_count
¶
ModularAddition
dataclass
¶
ModularAddition(modulus: int = 120, addend: int = 37, radius: float = 120.0, rotation: float = pi / 2.0, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
Join each number to the one a fixed distance further round.
The times table's sibling, and the plainer of the two: adding a constant
steps around the circle at a constant rate, so the result is the star
polygon {modulus/addend} -- one loop if the two are coprime, several
interleaved ones if they are not.
That makes it the same set of lines :class:~geomotif.motifs.primitives.StarPolygon
draws, and it is here because the family reads wrong without it: seeing
how little the addition version does is what makes the multiplication
version surprising. Reach for StarPolygon when you want the star
itself, and for this when you are exploring the arithmetic.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
modulus
|
int
|
How many numbers go around the circle. |
120
|
addend
|
int
|
How far each step reaches. |
37
|
radius
|
float
|
Radius of the circle. |
120.0
|
rotation
|
float
|
Angle of node zero, in radians. |
pi / 2.0
|
center
|
(float, float)
|
Middle of the circle. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
loop_count |
Return how many separate loops the walk falls into. |
PrimeChords
dataclass
¶
PrimeChords(limit: int = 60, radius: float = 120.0, rotation: float = pi / 2.0, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = False)
Bases: SegmentMotif
Join two numbers whenever they add up to a prime.
Space the whole numbers below limit around a circle and draw a chord
between every pair whose sum is prime. The result is a dense, oddly
orderly web, and every feature in it is a fact about primes rather than a
decision about drawing: no chord ever joins two even numbers or two odd
ones, because their sum would be even, so the figure is bipartite and the
ring of alternating nodes shows it. The one exception is the pair summing
to two, which is why zero-to-two is the only even-even chord in the
picture.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
limit
|
int
|
Numbers to place around the circle, from |
60
|
radius
|
float
|
Radius of the circle. |
120.0
|
rotation
|
float
|
Angle of node zero, in radians. |
pi / 2.0
|
center
|
(float, float)
|
Middle of the circle. |
(0.0, 0.0)
|