Skip to content

Add the rotational cut strategy, for classes closed under rotation - #530

Open
ulfarsson wants to merge 4 commits into
developfrom
rotational-cut
Open

ulfarsson wants to merge 4 commits into
developfrom
rotational-cut

Conversation

@ulfarsson

@ulfarsson ulfarsson commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Adds the rotational cut strategy (RotationalCutFactory, RotationalCutStrategy and RotationalCutConstructor, in tilings/strategies/rotation.py), the pack modifier TileScopePack.add_rotational_cuts with the tilescope spec flag -r/--rotational-cuts, and hints (tilings/hints.py). As discussed on Permuta#207, which was closed because its rotationally stable strategy belongs in Tilings as strategies, not in Permuta as an enumeration strategy, this moves it here.

The idea. The rotation of a permutation is the cyclic shift that moves its first entry to the end (Perm.shift_left). A class is closed under rotation when its basis is, that is, when the basis is rotationally stable. The n rotations of a permutation of length n are distinct, and exactly one of them starts with the minimum. So the class has n times as many permutations of length n as its min cut, {p : 1 + p in the class}, has of length n - 1. Exactly one rotation also ends with the maximum, so the max cut, {p : p + 1 in the class}, counts the class the same way, and it can be a different class: [1324] = {1324, 3241, 2413, 4132} has the cuts Av(213, 4132) and Av(132, 3241). A class closed under the cyclic shift of the values, whose inverse basis is rotationally stable, has the same two cuts and is counted by them in the same way, since (1 + p)^-1 = 1 + p^-1.

The strategy. RotationalCutFactory applies to a 1x1 tiling without assumptions whose class is closed under rotation of its positions or of its values, either the whole class or its nonempty permutations (a point requirement). It yields the rule to the min cut, and to the max cut when that is a different class. Added to a pack with add_rotational_cuts, it applies to every such tiling the search finds, so a cut closed under rotation again is cut too: the lifts of 12, {123, 231, 312} and its own lift, are counted by n and by n(n - 1). The constructor counts the parent as n times the child at n - 1, with the empty permutation at 0 for the whole class. Its equation is F = x (x C)', or 1 + x (x C)' for the whole class. It generates and samples objects: each child object p gives the n rotations of 1 + p, or of p + 1. So Rule.sanity_check checks counts, objects and random samples.

Hints. tilings.hints.hints(basis) returns hints: properties of a class that make it likely that a change to a pack helps the search, where Permuta's enumeration strategies settle a count outright. The first is a class closed under rotation: the hint names its cuts and suggests the rotational cuts. tilescope spec logs the hints that the pack it runs does not follow, so a user running a pack on such a class is told to add --rotational-cuts.

Tests (tests/strategies/test_rotation.py, tests/test_hints.py, and the encoding and pack tests):

  • the cuts count the classes, against brute force, to length 8;
  • every rule passes its sanity check to length 7, for the whole class and its nonempty part, including a class closed only under rotation of its values;
  • the forward and backward maps are inverse;
  • the equation;
  • where the strategy does not apply;
  • the hints, and the flag;
  • every pack in the pack tests with add_rotational_cuts, through JSON;
  • a search with point_placements().add_rotational_cuts() on [1234] = Av(1234, 2341, 3412, 4123), which finds a specification with a rotational cut in about a second, with counts 1, 1, 2, 6, 20, 65, 198, 560, 1480. Its generating function, derived from the cut's (Av(123, 3412)), is (1 - 8x + 27x^2 - 48x^3 + 49x^4 - 27x^5 + 6x^6 - 4x^7)/((1 - 2x)^2 (1 - x)^5).

CI's mypy, pylint and test_positive_reverse_fusion fail on develop too, at the same lines; a separate pull request fixes them. This pull request's own tests pass in every test job.

🤖 Generated with Claude Code

ulfarsson and others added 4 commits October 19, 2026 11:37
A 1x1 tiling whose class is closed under rotation (the cyclic shift that moves the
first entry to the end), which is when its basis is rotationally stable, has n times
as many permutations of length n as its min cut, {p : 1 + p in the class}, or its
max cut, {p : p + 1 in the class}, has of length n - 1: exactly one rotation of each
permutation starts with its minimum, and exactly one ends with its maximum. A class
closed under the cyclic shift of the values (its inverse basis is rotationally
stable) has the same two cuts. RotationalCutFactory adds the rule to each cut, the
max cut only when it is a different class, with RotationalCutConstructor:
F(x) = 1 + x (x C(x))' for the whole class, and x (x C(x))' for its nonempty part.
TileScopePack.make_rotational adds the factory to a pack.

It moves the rotationally stable strategy of Permuta's pull request #207, which was
closed in favor of a strategy in Tilings, into a strategy here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uts and hints

TileScopePack.add_rotational_cuts (was make_rotational) adds the rotational cut
strategies to any pack, whose name gets the suffix _rotational_cuts, and
tilescope spec's new flag -r/--rotational-cuts does the same on the command line.

tilings.hints gathers hints: properties of a class that make it likely that a
change to a pack helps the search, where Permuta's enumeration strategies settle
a count outright. The first is a class closed under rotation: the hint names its
cuts and suggests the rotational cuts. tilescope spec logs the hints that the
pack it runs does not follow. The packs' JSON tests cover the new modifier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Code that builds its own argparse.Namespace for build_pack, as some callers did
before the flag existed, broke on the new attribute (an AttributeError, found on a
test run on a cluster). build_pack now reads it with getattr, and a test calls it
with such a namespace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant