Skip to content

geomotif.motifs.symmetry

Point sets defined by a symmetry group and a rule about their distances.

Every other motif in this catalog says where its points are. This one says what has to be true of them -- that they fall into the orbits of a cyclic or dihedral group, and that neighbouring points sit the same distance apart -- and then solves for an arrangement that satisfies it.

The question it exists to answer is the one that sounds trivial and is not: fifteen points, five-fold symmetry, every neighbour the same distance from the last. Fifteen is not a multiple of ten, so the dihedral group D5 cannot build it out of generic ten-point orbits alone; it needs one of those and one five-point orbit sitting on the mirror lines. Which orbits are available, and which counts they can add up to, is arithmetic the motif does for you -- :class:SymmetricPointSet refuses a count it cannot arrange and names the nearest two it can.

Symmetry is preserved by construction rather than by the solver: relaxation moves one representative point per orbit, and the group carries the rest along. No amount of iteration can drift the figure off its own symmetry, and the result is exactly reproducible without a random seed anywhere.

.. warning::

This module is **experimental** -- the one place in the library where a
motif is solved for rather than evaluated. Its parameters and its output
may change in a minor release; see the API policy.

Classes:

Name Description
SymmetricPointSet

Points arranged by a symmetry group, spaced by iterative relaxation.

SymmetricPointSet dataclass

SymmetricPointSet(count: int = 15, group: str = 'D5', radius: float = 120.0, relax: int = 200, connect: Connection = 'equal-distance', neighbors: int = 3, tolerance: float = 0.08, center: Point = (0.0, 0.0), *, merge: bool = False, show_nodes: bool = True)

Bases: SegmentMotif

Points arranged by a symmetry group, spaced by iterative relaxation.

The points are laid out in orbits -- sets the group carries onto themselves. Under Cn an orbit holds n points; under Dn a general orbit holds 2n and one sitting on the mirror lines holds n. A single point at the center is fixed by either group, so a count that is one more than a multiple of n gets one.

That is the whole constraint on count: it must be a multiple of the group's order, or one more than one. Anything else cannot be arranged symmetrically at all, and is refused with the nearest two counts that can.

Relaxation then equalizes the distances. Each orbit contributes one representative point, which is pushed away from neighbours closer than the mean nearest-neighbour distance and pulled toward those further away; the group replicates whatever it does. Because only representatives move, the figure cannot drift off its symmetry, and no random numbers are involved anywhere -- the same parameters always give the same points.

The relaxation is local, so it finds an even arrangement rather than proving one. Most counts come out exactly equal; a few settle for a figure that is well spread but not uniform, usually where two orbits of the same size want the same radius. Raising relax does not rescue those -- they are settled, not unfinished. This is the part of the motif that is still experimental.

Parameters:

Name Type Description Default
count int

How many points to place.

15
group str

"C5" for five-fold rotation, "D5" for five-fold rotation plus mirrors. Case-insensitive.

'D5'
radius float

Radius of the outermost point. The relaxed figure is scaled to this, so it is a size rather than a constraint on the solution.

120.0
relax int

Relaxation iterations. Zero returns the seeded rings untouched, which is worth looking at to see what the relaxation is doing.

200
connect ('none', 'nearest', 'equal-distance', 'all-pairs')

What to draw between the points:

  • "none" -- nothing; the points are the design
  • "nearest" -- each point to its neighbors closest
  • "equal-distance" -- every pair the shortest distance apart, within tolerance. This is the one the constraint is for: it draws exactly the edges the relaxation was equalizing
  • "all-pairs" -- the complete graph, which gets solid quickly
"none"
neighbors int

How many neighbours each point is relaxed against, and how many connect="nearest" joins it to.

3
tolerance float

How far above the shortest distance a pair may be and still count as equal, as a fraction of it. Relaxation converges rather than lands, so this is never zero.

0.08
center (float, float)

Middle of the figure.

(0.0, 0.0)

Examples:

>>> design = SymmetricPointSet(count=15, group="D5").build()
>>> len(design.points)
15

Methods:

Name Description
orbit_sizes

Return how many points each orbit holds, innermost first.

orbit_sizes

orbit_sizes() -> tuple[int, ...]

Return how many points each orbit holds, innermost first.

Source code in src/geomotif/motifs/symmetry.py
def orbit_sizes(self) -> tuple[int, ...]:
    """Return how many points each orbit holds, innermost first."""
    order, dihedral = _parse_group(self.group, owner=type(self).__name__)
    plan = _plan(self.count, order, dihedral=dihedral)
    return tuple(_size(kind, order, dihedral=dihedral) for kind in plan)