---
title: "Permutation Groups"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Permutation Groups}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
library(cayleyR)
```

The package started with TopSpin, but nothing in its search code needs to
know which puzzle it is solving. All it needs is the **group**: how long
a state is, which moves there are, and what each move does. A
`perm_group` object holds exactly that. You pass it to a function through
`group =`, so the same code works on TopSpin, on a Rubik's cube, or on a
puzzle you describe yourself.

## Two built-in puzzles

```{r}
tg <- topspin_group(8, k = 4, moves = c("L", "R", "X"))
tg

cg <- cube_group(3)
cg
```

A TopSpin ring of 8 tiles has three moves. `X` reverses the first `k = 4`
tiles, and the group fixes `k` when you build it. A 3x3x3 cube has 54
stickers and 18 quarter turns, slices included.

TopSpin's moves are named `"1"`, `"2"`, `"3"` by default. Pass
`moves = c("L", "R", "X")` if you want to write words with letters, as
in this vignette.

## Applying moves

A state is an integer vector, and the solved state is `1:n`. A word can
be a vector of move names or one string with spaces:

```{r}
group_apply(tg, group_identity(tg), "L L X")

s <- group_apply(cg, group_identity(cg), "R U R' U'")
sum(s != group_identity(cg))   # stickers out of place
```

`group_inverse_seq()` writes the word that undoes a given one. You never
have to declare inverses: the group works them out from the
permutations.

```{r}
undo <- group_inverse_seq(cg, "R U R' U'")
undo
identical(group_apply(cg, s, undo), group_identity(cg))
```

## Order of a word

How many times must a word be repeated to return to the start? The
answer does not depend on the starting state, and `group_order()`
computes it:

```{r}
group_order(cg, "R")            # a quarter turn: 4
group_order(cg, "R U R' U'")    # the "sexy move": 6
group_order(cg, "R U")          # 105

group_order(tg, "L")            # going once round the ring
```

Cycles like these drive the iterative solver: one word repeated many
times sweeps through a long cycle of states.

## A puzzle of your own

`perm_group()` builds a group from a named list of permutations. Each
permutation is read as `new[i] <- state[perm[i]]`, i.e. position `i`
takes its value from position `perm[i]`. No C++ is needed.

Here is a toy puzzle on five points. It has a rotation `a`, its inverse
`a'`, and a swap of the first two points `b`:

```{r}
toy <- perm_group(list(
  a    = c(2L, 3L, 4L, 5L, 1L),
  "a'" = c(5L, 1L, 2L, 3L, 4L),
  b    = c(2L, 1L, 3L, 4L, 5L)
), name = "toy")
toy

group_apply(toy, 1:5, "a a b")
group_inverse_seq(toy, "a a b")
group_order(toy, "a b")
```

Everything above works on this group straight away, and so does every
function in the package that takes `group =`.

## Random states

`generate_state()` takes a group too. Given one, it only returns states
the puzzle can actually reach, rather than an arbitrary permutation of
54 numbers:

```{r}
scrambled <- generate_state(group = cg, n_moves = 20)
length(scrambled)
```

## Subgroups

You can build a cube group that uses only some of the moves. Here the
middle slice turns alone:

```{r}
m_only <- cube_group(3, moves = c("M", "M'"))
m_only
generate_state(group = m_only, n_moves = 5)
```

## Old calls still work

Without a group, `n`, `k` and `moves` still describe TopSpin, exactly as
before, so existing code does not need to change:

```{r}
generate_state(10, k = 4, n_moves = 100)
```
