Skip to content

Commit 048ee78

Browse files
committed
Edge refactored
1 parent 37be78d commit 048ee78

2 files changed

Lines changed: 63 additions & 11 deletions

File tree

src/ring_seq/methods.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,40 @@ class Vertex(AxisLocation):
3939
i: Index
4040

4141

42-
@dataclass(frozen=True)
4342
class Edge(AxisLocation):
44-
"""A symmetry axis location passing between two adjacent elements."""
45-
i: Index
46-
j: Index
43+
"""A symmetry axis location passing between two consecutive elements of a circular sequence.
44+
45+
The invariant `j == (i + 1) % n` is enforced at construction — direct
46+
construction of an edge with arbitrary `(i, j)` is not supported.
47+
Pattern matching with `match e: case Edge(i, j): ...` still works.
48+
"""
49+
50+
__match_args__ = ("i", "j")
51+
52+
def __init__(self, i: Index, n: int):
53+
"""Constructs the edge between consecutive elements of a circular sequence of size `n`,
54+
starting at circular index `i`. The endpoint `j = ((i mod n) + 1) mod n` is computed.
55+
56+
Args:
57+
i: circular index of the first endpoint (any integer, normalized to `[0, n)`)
58+
n: the ring size; must be positive
59+
60+
Raises:
61+
ValueError: if `n <= 0`
62+
"""
63+
if n <= 0:
64+
raise ValueError("ring size must be positive")
65+
self.i = i % n
66+
self.j = (self.i + 1) % n
67+
68+
def __eq__(self, other: object) -> bool:
69+
return isinstance(other, Edge) and self.i == other.i and self.j == other.j
70+
71+
def __hash__(self) -> int:
72+
return hash((Edge, self.i, self.j))
73+
74+
def __repr__(self) -> str:
75+
return f"Edge(i={self.i}, j={self.j})"
4776

4877

4978
def index_from(ring: Seq, i: IndexO) -> Index:
@@ -711,7 +740,7 @@ def reflectional_symmetry_axes(ring: Seq) -> list[tuple[AxisLocation, AxisLocati
711740
n: int = len(ring)
712741

713742
def edge_indices(i: Index) -> Edge:
714-
return Edge(i, (i + 1) % n)
743+
return Edge(i, n)
715744

716745
def opposite_edge_index(i: Index) -> Index:
717746
return (i + n // 2) % n

tests/SymmetryTest.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ def test_reflectional_symmetry_axes_triangle(self):
5454
self.assertEqual(
5555
reflectional_symmetry_axes((1, 1, 1)),
5656
[
57-
(Vertex(1), Edge(2, 0)),
58-
(Vertex(2), Edge(0, 1)),
59-
(Vertex(0), Edge(1, 2)),
57+
(Vertex(1), Edge(2, 3)),
58+
(Vertex(2), Edge(0, 3)),
59+
(Vertex(0), Edge(1, 3)),
6060
],
6161
)
6262

@@ -74,9 +74,9 @@ def test_reflectional_symmetry_axes_square(self):
7474
self.assertEqual(
7575
reflectional_symmetry_axes((1, 1, 1, 1)),
7676
[
77-
(Edge(1, 2), Edge(3, 0)),
77+
(Edge(1, 4), Edge(3, 4)),
7878
(Vertex(1), Vertex(3)),
79-
(Edge(0, 1), Edge(2, 3)),
79+
(Edge(0, 4), Edge(2, 4)),
8080
(Vertex(0), Vertex(2)),
8181
],
8282
)
@@ -95,9 +95,32 @@ def test_reflectional_symmetry_axes_doubled_square(self):
9595
def test_reflectional_symmetry_axes_specular_pentagon(self):
9696
self.assertEqual(
9797
reflectional_symmetry_axes((1, 1, 2, 3, 2)),
98-
[(Vertex(3), Edge(0, 1))],
98+
[(Vertex(3), Edge(0, 5))],
9999
)
100100

101+
def test_edge_computes_second_endpoint(self):
102+
e = Edge(2, 4)
103+
self.assertEqual(e.i, 2)
104+
self.assertEqual(e.j, 3)
105+
# wraps around the ring
106+
self.assertEqual(Edge(3, 4).j, 0)
107+
108+
def test_edge_normalizes_out_of_range_i(self):
109+
self.assertEqual(Edge(-1, 5), Edge(4, 5))
110+
self.assertEqual(Edge(7, 5), Edge(2, 5))
111+
112+
def test_edge_rejects_non_positive_ring_size(self):
113+
with self.assertRaises(ValueError):
114+
Edge(0, 0)
115+
with self.assertRaises(ValueError):
116+
Edge(0, -1)
117+
118+
def test_edge_supports_pattern_matching(self):
119+
match Edge(2, 4):
120+
case Edge(i, j):
121+
self.assertEqual(i, 2)
122+
self.assertEqual(j, 3)
123+
101124

102125
if __name__ == '__main__':
103126
unittest.main()

0 commit comments

Comments
 (0)