Last active
March 5, 2024 14:03
-
-
Save stla/c666eedba451c4b8423162e86a21ac9f to your computer and use it in GitHub Desktop.
Braid groups
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# hSepString :: HSep -> String | |
# hSepString hsep = case hsep of | |
# HSepEmpty -> "" | |
# HSepSpaces k -> replicate k ' ' | |
# HSepString s -> s | |
hSepString <- function(hsep) { | |
sep <- attr(hsep, "sep") | |
switch( | |
sep, | |
"empty" = "", | |
"spaces" = paste0(rep(" ", hsep), collapse = ""), | |
"string" = hsep | |
) | |
} | |
vSepString <- function(vsep) { | |
sep <- attr(vsep, "sep") | |
switch( | |
sep, | |
"empty" = character(0L), | |
"spaces" = paste0(rep(" ", vsep), collapse = ""), | |
"string" = vsep | |
) | |
} | |
vSepSpaces <- function(k) { | |
out <- k | |
attr(out, "sep") <- "spaces" | |
out | |
} | |
vSepSize <- function(vsep) { | |
nchar(vSepString(vsep)) | |
} | |
HSepEmpty <- function() { | |
out <- "." | |
attr(out, "sep") <- "empty" | |
out | |
} | |
ASCII <- function(x, y, lines) { | |
list("x" = x, "y" = y, "lines" = lines) | |
} | |
asciiLines <- function(ascii) { | |
ascii[["lines"]] | |
} | |
# -- | Extends an ASCII figure with spaces vertically to the given height. | |
# -- Note: the alignment is the alignment of the original picture in the new bigger picture! | |
# vExtendTo :: VAlign -> Int -> ASCII -> ASCII | |
# vExtendTo valign n0 rect@(ASCII (x,y) ls) = vExtendWith valign (max n0 y - y) rect | |
vExtendTo <- function(valign, n0, rect) { | |
y <- rect[["y"]] | |
vExtendWith(valign, max(n0, y) - y, rect) | |
} | |
# -- | Extend vertically with the given number of empty lines. | |
# vExtendWith :: VAlign -> Int -> ASCII -> ASCII | |
# vExtendWith valign d (ASCII (x,y) ls) = ASCII (x,y+d) (f ls) where | |
# f ls = case valign of | |
# VTop -> ls ++ replicate d emptyline | |
# VBottom -> replicate d emptyline ++ ls | |
# VCenter -> replicate a emptyline ++ ls ++ replicate (d-a) emptyline | |
# a = div d 2 | |
# emptyline = replicate x ' ' | |
vExtendWith <- function(valign, d, rect) { | |
x <- rect[["x"]] | |
y <- rect[["y"]] | |
lines <- rect[["lines"]] | |
emptyLine <- paste0(rep(" ", x), collapse = "") | |
f <- function(ls) { | |
a <- d %/% 2L | |
switch( | |
valign, | |
"Vtop" = c(ls, rep(emptyLine, d)), | |
"Vbottom" = c(rep(emptyLine, d), ls), | |
"Vcenter" = c(rep(emptyLine, a), ls, rep(emptyLine, d - a)) | |
) | |
} | |
ASCII(x, y + d, f(lines)) | |
} | |
# -- | Extends an ASCII figure with spaces horizontally to the given width. | |
# -- Note: the alignment is the alignment of the original picture in the new bigger picture! | |
# hExtendTo :: HAlign -> Int -> ASCII -> ASCII | |
# hExtendTo halign n0 rect@(ASCII (x,y) ls) = hExtendWith halign (max n0 x - x) rect | |
hExtendTo <- function(halign, n0, rect) { | |
x <- rect[["x"]] | |
hExtendWith(halign, max(n0, x) - x, rect) | |
} | |
# -- | Extend horizontally with the given number of spaces. | |
# hExtendWith :: HAlign -> Int -> ASCII -> ASCII | |
# hExtendWith alignment d (ASCII (x,y) ls) = ASCII (x+d,y) (map f ls) where | |
# f l = case alignment of | |
# HLeft -> l ++ replicate d ' ' | |
# HRight -> replicate d ' ' ++ l | |
# HCenter -> replicate a ' ' ++ l ++ replicate (d-a) ' ' | |
# a = div d 2 | |
hExtendWith <- function(halign, d, rect) { | |
x <- rect[["x"]] | |
y <- rect[["y"]] | |
lines <- rect[["lines"]] | |
f <- function(l) { | |
a <- d %/% 2L | |
switch( | |
halign, | |
"Hleft" = paste0(c(l, rep(" ", d)), collapse = ""), | |
"Hright" = paste0(c(rep(" ", d), l), collapse = ""), | |
"Hcenter" = paste0(c(rep(" ", a), l, rep(" ", d - a)), collapse = "") | |
) | |
} | |
ASCII(x + d, y, vapply(lines, f, character(1L))) | |
} | |
# asciiFromLines :: [String] -> ASCII | |
# asciiFromLines ls = ASCII (x,y) (map f ls) where | |
# y = length ls | |
# x = maximum (map length ls) | |
# f l = l ++ replicate (x - length l) ' ' | |
asciiFromLines <- function(ls) { | |
y <- length(ls) | |
x <- max(vapply(ls, nchar, integer(1L))) | |
f <- function(l) { | |
paste0(c(l, rep(" ", x - nchar(l))), collapse = "") | |
} | |
ASCII(x, y, vapply(ls, f, character(1L))) | |
} | |
# -- | Horizontal concatenation, top-aligned, no separation | |
# hCatTop :: [ASCII] -> ASCII | |
# hCatTop = hCatWith VTop HSepEmpty | |
# | |
hCatTop <- function(asciis) { | |
hCatWith("Vtop", HSepEmpty(), asciis) | |
} | |
# -- | General horizontal concatenation | |
# hCatWith :: VAlign -> HSep -> [ASCII] -> ASCII | |
# hCatWith valign hsep rects = ASCII (x',maxy) final where | |
# n = length rects | |
# maxy = maximum [ y | ASCII (_,y) _ <- rects ] | |
# xsz = [ x | ASCII (x,_) _ <- rects ] | |
# sep = hSepString hsep | |
# sepx = length sep | |
# rects1 = map (vExtendTo valign maxy) rects | |
# x' = sum' xsz + (n-1)*sepx | |
# final = map (intercalate sep) $ transpose (map asciiLines rects1) | |
hCatWith <- function(valign, hsep, rects) { | |
n <- length(rects) | |
maxy <- max(vapply(rects, `[[`, integer(1L), "y")) | |
xsz <- vapply(rects, `[[`, integer(1L), "x") | |
sep <- hSepString(hsep) | |
sepx <- length(sep) | |
rects1 <- lapply(rects, function(rect) { | |
vExtendTo(valign, maxy, rect) | |
}) | |
x2 <- sum(xsz) + (n - 1L) * sepx | |
M <- do.call(rbind, lapply(rects1, asciiLines)) | |
final <- apply(M, 2L, paste0, collapse = sep) | |
ASCII(x2, maxy, final) | |
} | |
# intercalate : List A → List (List A) → List A | |
# intercalate xs [] = [] | |
# intercalate xs (ys ∷ []) = ys | |
# intercalate xs (ys ∷ yss) = ys ++ xs ++ intercalate xs yss | |
intercalate <- function(sep, x) { | |
if(length(x) == 0L) { | |
list() | |
} else if(length(x) == 1L) { | |
x[1L] | |
} else { | |
unlist(c(x[1L], list(sep), intercalate(sep, x[-1L]))) | |
} | |
} | |
#intercalate(c("a", "b"), list(c("xxx", "yyy"), c("zzz", "ooo"), c("uuu", "vvv"))) | |
# -- | General vertical concatenation | |
# vCatWith :: HAlign -> VSep -> [ASCII] -> ASCII | |
# vCatWith halign vsep rects = ASCII (maxx,y') final where | |
# n = length rects | |
# maxx = maximum [ x | ASCII (x,_) _ <- rects ] | |
# ysz = [ y | ASCII (_,y) _ <- rects ] | |
# sepy = vSepSize vsep | |
# fullsep = transpose (replicate maxx $ vSepString vsep) :: [String] | |
# rects1 = map (hExtendTo halign maxx) rects | |
# y' = sum' ysz + (n-1)*sepy | |
# final = intercalate fullsep $ map asciiLines rects1 | |
vCatWith <- function(halign, vsep, rects) { | |
n <- length(rects) | |
maxx <- max(vapply(rects, `[[`, integer(1L), "x")) | |
ysz <- vapply(rects, `[[`, integer(1L), "y") | |
sepy <- vSepSize(vsep) | |
vsepstring <- vSepString(vsep) | |
fullsep <- apply( | |
rbind( | |
vapply(strsplit(vsepstring, "")[[1L]], rep, character(maxx), times = maxx) | |
), | |
2L, paste0, collapse = "" | |
) | |
rects1 <- lapply(rects, function(rect) { | |
hExtendTo(halign, maxx, rect) | |
}) | |
y2 <- sum(ysz) + (n - 1L) * sepy | |
final <- intercalate(fullsep, lapply(rects1, asciiLines)) | |
ASCII(maxx, y2, final) | |
} | |
# -- | A box simply filled with the given character | |
# filledBox :: Char -> (Int,Int) -> ASCII | |
# filledBox c (x0,y0) = asciiFromLines $ replicate y (replicate x c) where | |
# x = max 0 x0 | |
# y = max 0 y0 | |
# | |
# -- | A box of spaces | |
# transparentBox :: (Int,Int) -> ASCII | |
# transparentBox = filledBox ' ' | |
filledBox <- function(c, x0, y0) { | |
x <- max(0L, x0) | |
y <- max(0L, y0) | |
asciiFromLines(rep(paste0(rep(c, x), collapse = ""), y)) | |
} | |
transparentBox <- function(x0, y0) { | |
filledBox(" ", x0, y0) | |
} | |
asciiShow <- function(x) { | |
asciiFromLines(x) | |
} | |
# horizBraidASCII' :: KnownNat n => Bool -> Braid n -> ASCII | |
# horizBraidASCII' flipped braid@(Braid gens) = final where | |
# | |
# n = numberOfStrands braid | |
# | |
# final = vExtendWith VTop 1 $ hCatTop allBlocks | |
# allBlocks = prelude ++ middleBlocks ++ epilogue | |
# prelude = [ numberBlock , spaceBlock , beginEndBlock ] | |
# epilogue = [ beginEndBlock , spaceBlock , numberBlock' ] | |
# middleBlocks = map block gens | |
# | |
# block g = case g of | |
# Sigma i -> block' i $ if flipped then over else under | |
# SigmaInv i -> block' i $ if flipped then under else over | |
# | |
# block' i middle = asciiFromLines $ drop 2 $ concat | |
# $ replicate a horiz ++ [space3, middle] ++ replicate b horiz | |
# where | |
# (a,b) = if flipped then (n-i-1,i-1) else (i-1,n-i-1) | |
# | |
# spaceBlock = transparentBox (1,n*3-2) | |
# beginEndBlock = asciiFromLines $ drop 2 $ concat $ replicate n horiz | |
# numberBlock = mkNumbers [1..n] | |
# numberBlock' = mkNumbers $ P.fromPermutation $ braidPermutation braid | |
# | |
# mkNumbers :: [Int] -> ASCII | |
# mkNumbers list = vCatWith HRight (VSepSpaces 2) $ map asciiShow | |
# $ (if flipped then reverse else id) $ list | |
# | |
# under = [ "\\ /" , " / " , "/ \\" ] | |
# over = [ "\\ /" , " \\ " , "/ \\" ] | |
# horiz = [ " " , " " , "___" ] | |
# space3 = [ " " , " " , " " ] | |
horizBraidASCII <- function(flipped, braid) { | |
under = c("\\ /" , " / " , "/ \\") | |
over = c("\\ /" , " \\ " , "/ \\") | |
horiz = c(" " , " " , "___") | |
space3 = c(" " , " " , " ") | |
n <- numberOfStrands(braid) | |
block2 <- function(i, middle) { | |
if(flipped) { | |
a <- n - i - 1L | |
b <- i - 1L | |
} else { | |
a <- i - 1L | |
b <- n - i - 1L | |
} | |
x <- c(rep(horiz, a), c(space3, middle), rep(horiz, b)) | |
asciiFromLines(x[-c(1L, 2L)]) | |
} | |
block <- function(g) { | |
i <- g[1L] | |
if(g[2L] == 1L) { | |
block2(i, if(flipped) over else under) | |
} else { | |
block2(i, if(flipped) under else over) | |
} | |
} | |
mkNumbers <- function(x) { | |
if(flipped) x <- rev(x) | |
vCatWith( | |
"Hright", | |
vSepSpaces(2L), | |
lapply(x, asciiShow) | |
) | |
} | |
# spaceBlock = transparentBox (1,n*3-2) | |
# beginEndBlock = asciiFromLines $ drop 2 $ concat $ replicate n horiz | |
# numberBlock = mkNumbers [1..n] | |
# numberBlock' = mkNumbers $ P.fromPermutation $ braidPermutation braid | |
spaceBlock <- transparentBox(1L, 3L*n - 2L) | |
beginEndBlock <- asciiFromLines(rep(horiz, n)[-c(1L, 2L)]) | |
numberBlock <- mkNumbers(1L:n) | |
numberBlock2 <- mkNumbers(braidPermutation(braid)) | |
prelude <- list(numberBlock, spaceBlock, beginEndBlock) | |
epilogue <- list(beginEndBlock, spaceBlock, numberBlock2) | |
middleBlocks <- lapply(braid, block) | |
allBlocks <- c(prelude, middleBlocks, epilogue) | |
print(allBlocks) | |
final <- vExtendWith("Vtop", 1L, hCatTop(allBlocks)) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
library(maybe) | |
isPositiveInteger <- function(n) { | |
length(n) == 1L && is.numeric(n) && !is.na(n) && n != 0 && floor(n) == n | |
} | |
#' @title Artin generators | |
#' @description A standard Artin generator of a braid: \code{Sigma(i)} | |
#' represents twisting the neighbour strands \code{i} and \code{i+1}, | |
#' such that strand \code{i} goes \emph{under} strand \code{i+1}. | |
#' | |
#' @param i index of the strand, a positive integer | |
#' | |
#' @returns A vector of two integers. | |
#' @export | |
#' @name ArtinGenerators | |
#' @rdname ArtinGenerators | |
Sigma <- function(i) { | |
stopifnot(isPositiveInteger(i)) | |
c(as.integer(i), 1L) | |
} | |
#' @export | |
#' @rdname ArtinGenerators | |
SigmaInv <- function(i) { | |
stopifnot(isPositiveInteger(i)) | |
c(as.integer(i), -1L) | |
} | |
mkBraid <- function(n, brgens) { | |
stopifnot(isPositiveInteger(n)) | |
out <- brgens | |
idx <- vapply(brgens, `[[`, integer(1L), 1L) | |
if(any(idx) >= n) { | |
stop("Found a generator with a too large index.") | |
} | |
attr(out, "n") <- as.integer(n) | |
class(out) <- "braid" | |
out | |
} | |
#' @exportS3Method print braid | |
print.braid <- function(x, ...) { | |
idx <- vapply(x, `[[`, integer(1L), 1L) | |
signs <- vapply(x, `[[`, integer(1L), 2L) | |
print(paste0(vapply(seq_along(idx), function(i) { | |
if(signs[i] == 1L) { | |
sprintf("sigma_%d", idx[i]) | |
} else { | |
sprintf("sigmaInv_%d", idx[i]) | |
} | |
}, character(1L)), collapse = " ")) | |
invisible() | |
} | |
numberOfStrands <- function(braid) { | |
attr(braid, "n") | |
} | |
# freeReduceBraidWord :: Braid n -> Braid n | |
# freeReduceBraidWord (Braid orig) = Braid (loop orig) where | |
# | |
# loop w = case reduceStep w of | |
# Nothing -> w | |
# Just w' -> loop w' | |
# | |
# reduceStep :: [BrGen] -> Maybe [BrGen] | |
# reduceStep = go False where | |
# go !changed w = case w of | |
# (Sigma x : SigmaInv y : rest) | x==y -> go True rest | |
# (SigmaInv x : Sigma y : rest) | x==y -> go True rest | |
# (this : rest) -> liftM (this:) $ go changed rest | |
# _ -> if changed then Just w else Nothing | |
freeReduceBraidWord <- function(braid) { | |
reduceStep <- function(gens) { | |
go <- function(changed, w) { | |
if(length(w) >= 2L) { | |
w1 <- w[[1L]] | |
w2 <- w[[2L]] | |
x <- w1[1L] | |
y <- w2[1L] | |
s1 <- w1[2L] | |
s2 <- w2[2L] | |
if(x == y && ((s1 == 1L && s2 == -1L) || (s1 == -1L && s2 == 1L))) { | |
go(TRUE, w[-c(1L, 2L)]) | |
} else { | |
gg <- go(changed, w[-1L]) | |
if(is_nothing(gg)) { | |
nothing() | |
} else { | |
just(c(list(w1), from_just(gg))) | |
} | |
} | |
} else if(length(w) == 1L) { | |
if(changed) { | |
just(c(list(w[[1L]]), w)) | |
} else { | |
nothing() | |
} | |
} else { | |
if(changed) { | |
just(w) | |
} else { | |
nothing() | |
} | |
} | |
} | |
go(FALSE, gens) | |
} | |
loop <- function(w) { | |
x <- reduceStep(w) | |
if(is_nothing(x)) { | |
w | |
} else { | |
loop(from_just(x)) | |
} | |
} | |
mkBraid(numberOfStrands(braid), loop(braid)) | |
} | |
# -- | This is an untyped version of 'braidPermutation' | |
# _braidPermutation :: Int -> [Int] -> Permutation | |
# _braidPermutation n idxs = P.uarrayToPermutationUnsafe (runSTUArray action) where | |
# | |
# action :: forall s. ST s (STUArray s Int Int) | |
# action = do | |
# arr <- newArray_ (1,n) | |
# forM_ [1..n] $ \i -> writeArray arr i i | |
# worker arr idxs | |
# return arr | |
# | |
# worker arr = go where | |
# go [] = return arr | |
# go (i:is) = do | |
# a <- readArray arr i | |
# b <- readArray arr (i+1) | |
# writeArray arr i b | |
# writeArray arr (i+1) a | |
# go is | |
braidPermutation <- function(brain) { | |
n <- numberOfStrands(brain) | |
idxs <- vapply(brain, `[[`, integer(1L), 1L) | |
worker <- function(arr, idxs) { | |
if(length(idxs) == 0L) { | |
arr | |
} else { | |
i <- idxs[1L] | |
is <- idxs[-1L] | |
a <- arr[i] | |
b <- arr[i + 1L] | |
arr[i] <- b | |
arr[i + 1L] <- a | |
worker(arr, is) | |
} | |
} | |
worker(1L:n, idxs) | |
} | |
# doubleSigma :: KnownNat n => Int -> Int -> Braid (n :: Nat) | |
# doubleSigma s t = braid where | |
# n = numberOfStrands braid | |
# braid | |
# | s < 1 || s > n = error "doubleSigma: s index out of range" | |
# | t < 1 || t > n = error "doubleSigma: t index out of range" | |
# | s >= t = error "doubleSigma: s >= t" | |
# | otherwise = Braid $ | |
# [ Sigma i | i<-[t-1,t-2..s] ] ++ [ SigmaInv i | i<-[s+1..t-1] ] | |
doubleSigma <- function(n, s, t) { | |
stopifnot(isPositiveInteger(n), isPositiveInteger(s), isPositiveInteger(t)) | |
if(s > n) { | |
stop("The `s` index is out of range.") | |
} | |
if(t > n) { | |
stop("The `t` index is out of range.") | |
} | |
if(s >= t) { | |
stop("`s` must be strictly smaller than `t`.") | |
} | |
if(t - 1L <= s) { | |
gens <- lapply((t-1L):s, Sigma) | |
} else { | |
gens <- lapply((s+1L):(t-1L), SigmaInv) | |
} | |
mkBraid(n, gens) | |
} | |
# -- | The (positive) half-twist of all the braid strands, usually denoted by @Delta@. | |
# halfTwist :: KnownNat n => Braid n | |
# halfTwist = braid where | |
# braid = Braid $ map Sigma $ _halfTwist n | |
# n = numberOfStrands braid | |
# | |
# -- | The untyped version of 'halfTwist' | |
# _halfTwist :: Int -> [Int] | |
# _halfTwist n = gens where | |
# gens = concat [ sub k | k<-[1..n-1] ] | |
# sub k = [ j | j<-[n-1,n-2..k] ] | |
halfTwist <- function(n) { | |
stopifnot(isPositiveInteger(n)) | |
if(n == 1L) { | |
gens <- list() | |
} else { | |
subs <- lapply(1L:(n-1L), function(k) { | |
(n-1L):k | |
}) | |
gens <- lapply(do.call(c, subs), Sigma) | |
} | |
mkBraid(n, gens) | |
} | |
# tau :: KnownNat n => Braid n -> Braid n | |
# tau :: forall (n :: Nat). KnownNat n => Braid n -> Braid n | |
# tau braid@(Braid gens) = Braid (map f gens) where | |
# n = numberOfStrands braid | |
# f (Sigma i) = Sigma (n-i) | |
# f (SigmaInv i) = SigmaInv (n-i) | |
tau <- function(braid) { | |
n <- numberOfStrands(braid) | |
gens <- lapply(braid, function(gen) { | |
i <- gen[2L] | |
if(i == 1L) { | |
Sigma(n - i) | |
} else { | |
SigmaInv(n - i) | |
} | |
}) | |
mkBraid(n, gens) | |
} | |
# tauPerm :: Permutation -> Permutation | |
# tauPerm :: Permutation -> Permutation | |
# tauPerm perm = P.toPermutationUnsafeN n [ (n+1) - perm !!! (n-i) | i<-[0..n-1] ] where | |
# n = P.permutationSize perm | |
tauPerm <- function(perm) { | |
n <- length(perm) | |
# ?? | |
} | |
# -- | The inverse of a braid. Note: we do not perform reduction here, | |
# -- as a word is reduced if and only if its inverse is reduced. | |
# inverse :: Braid n -> Braid n | |
# inverse = Braid . reverse . map invBrGen . braidWord | |
inverseBraid <- function(braid) { | |
n <- numberOfStrands(braid) | |
invgens <- lapply(braid, function(gen) { | |
c(gen[1L], -gen[2L]) | |
}) | |
mkBraid(n, rev(invgens)) | |
} | |
# -- | Composes two braids, doing free reduction on the result | |
# -- (that is, removing @(sigma_k * sigma_k^-1)@ pairs@) | |
# compose :: Braid n -> Braid n -> Braid n | |
# compose (Braid gs) (Braid hs) = freeReduceBraidWord $ Braid (gs++hs) | |
composeTwoBraids <- function(braid1, braid2) { | |
n <- numberOfStrands(braid1) | |
if(n != numberOfStrands(braid2)) { | |
stop("Unequal numbers of strands.") | |
} | |
freeReduceBraidWord(mkBraid(n, c(braid1, braid2))) | |
} | |
composeManyBraids <- function(braids) { | |
ns <- vapply(braids, numberOfStrands, integer(1L)) | |
n <- ns[1L] | |
if(any(ns != n)) { | |
stop("Unequal numbers of strands.") | |
} | |
freeReduceBraidWord(mkBraid(n, do.call(c, braids))) | |
} | |
isPureBraid <- function(braid) { | |
identical(braidPermutation(braid), seq_len(numberOfStrands(braid))) | |
} | |
# -- | A positive braid word contains only positive (@Sigma@) generators. | |
# isPositiveBraidWord :: KnownNat n => Braid n -> Bool | |
# isPositiveBraidWord (Braid gs) = all (isPlus . brGenSign) gs | |
isPositiveBraidWord <- function(braid) { | |
signs <- vapply(braid, `[[`, integer(1L), 2L) | |
all(signs == 1L) | |
} | |
# -- | We compute the linking numbers between all pairs of strands: | |
# -- | |
# -- > linkingMatrix braid ! (i,j) == strandLinking braid i j | |
# -- | |
# linkingMatrix :: KnownNat n => Braid n -> UArray (Int,Int) Int | |
# linkingMatrix braid@(Braid gens) = _linkingMatrix (numberOfStrands braid) gens where | |
# | |
# -- | Untyped version of 'linkingMatrix' | |
# _linkingMatrix :: Int -> [BrGen] -> UArray (Int,Int) Int | |
# _linkingMatrix n gens = runSTUArray action where | |
# | |
# action :: forall s. ST s (STUArray s (Int,Int) Int) | |
# action = do | |
# perm <- newArray_ (1,n) :: ST s (STUArray s Int Int) | |
# forM_ [1..n] $ \i -> writeArray perm i i | |
# let doSwap :: Int -> ST s () | |
# doSwap i = do | |
# a <- readArray perm i | |
# b <- readArray perm (i+1) | |
# writeArray perm i b | |
# writeArray perm (i+1) a | |
# | |
# mat <- newArray ((1,1),(n,n)) 0 :: ST s (STUArray s (Int,Int) Int) | |
# let doAdd :: Int -> Int -> Int -> ST s () | |
# doAdd i j pm1 = do | |
# x <- readArray mat (i,j) | |
# writeArray mat (i,j) (x+pm1) | |
# writeArray mat (j,i) (x+pm1) | |
# | |
# forM_ gens $ \g -> do | |
# let (sgn,k) = brGenSignIdx g | |
# u <- readArray perm k | |
# v <- readArray perm (k+1) | |
# doAdd u v (signValue sgn) | |
# doSwap k | |
# | |
# return mat | |
linkingMatrix <- function(braid) { | |
n <- numberOfStrands(braid) | |
perm <- 1L:n | |
doSwap <- function(i) { | |
a <- perm[i] | |
b <- perm[i+1L] | |
perm[i] <<- b | |
perm[i+1L] <<- a | |
invisible() | |
} | |
mat <- matrix(0L, nrow = n, ncol = n) | |
doAdd <- function(i, j, pm1) { | |
x <- mat[i, j] | |
mat[i, j] <<- mat[j, i] <<- x + pm1 | |
invisible() | |
} | |
for(gen in braid) { | |
k <- gen[1L] | |
u <- perm[k] | |
v <- perm[k+1L] | |
doAdd(u, v, gen[2L]) | |
doSwap(k) | |
} | |
mat | |
} | |
braid <- mkBraid(4, list(Sigma(2), SigmaInv(3))) | |
linkingMatrix(braid) | |
# -- | A /permutation braid/ is a positive braid where any two strands cross | |
# -- at most one, and /positively/. | |
# -- | |
# isPermutationBraid :: KnownNat n => Braid n -> Bool | |
# isPermutationBraid braid = isPositiveBraidWord braid && crosses where | |
# crosses = and [ check i j | i<-[1..n-1], j<-[i+1..n] ] | |
# check i j = zeroOrOne (lkMatrix ! (i,j)) | |
# zeroOrOne a = (a==1 || a==0) | |
# lkMatrix = linkingMatrix braid | |
# n = numberOfStrands braid | |
isPermutationBraid <- function(braid) { | |
if(isPositiveBraidWord(braid)) { | |
lkMatrix <- linkingMatrix(braid) | |
all(lkMatrix[upper.tri(lkMatrix)] %in% c(0L, 1L)) | |
} else { | |
FALSE | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
library(maybe) | |
isPositiveInteger <- function(n) { | |
length(n) == 1L && is.numeric(n) && !is.na(n) && n != 0 && floor(n) == n | |
} | |
#' @title Artin generators | |
#' @description A standard Artin generator of a braid: \code{Sigma(i)} | |
#' represents twisting the neighbour strands \code{i} and \code{i+1}, | |
#' such that strand \code{i} goes \emph{under} strand \code{i+1}. | |
#' | |
#' @param i index of the strand, a positive integer | |
#' | |
#' @returns A vector of two integers. | |
#' @export | |
#' @name ArtinGenerators | |
#' @rdname ArtinGenerators | |
Sigma <- function(i) { | |
stopifnot(isPositiveInteger(i)) | |
c(as.integer(i), 1L) | |
} | |
#' @export | |
#' @rdname ArtinGenerators | |
SigmaInv <- function(i) { | |
stopifnot(isPositiveInteger(i)) | |
c(as.integer(i), -1L) | |
} | |
#' @title Make a braid | |
#' @description Make a braid. | |
#' | |
#' @param n number of strands, an integer, at least 2 | |
#' @param brgens list of generators obtained with \code{\link{Sigma}} or | |
#' \code{\link{SigmaInv}} | |
#' | |
#' @return A \code{braid} object. | |
#' @export | |
#' | |
#' @examples | |
#' mkBraid(4, list(Sigma(2), SigmaInv(3))) | |
mkBraid <- function(n, brgens) { | |
stopifnot(isPositiveInteger(n), n >= 2) | |
out <- brgens | |
idx <- vapply(brgens, `[[`, integer(1L), 1L) | |
if(any(idx) >= n) { | |
stop("Found a generator with a too large index.") | |
} | |
attr(out, "n") <- as.integer(n) | |
class(out) <- "braid" | |
out | |
} | |
#' @exportS3Method print braid | |
print.braid <- function(x, ...) { | |
idx <- vapply(x, `[[`, integer(1L), 1L) | |
signs <- vapply(x, `[[`, integer(1L), 2L) | |
print(paste0(vapply(seq_along(idx), function(i) { | |
if(signs[i] == 1L) { | |
sprintf("sigma_%d", idx[i]) | |
} else { | |
sprintf("sigmaInv_%d", idx[i]) | |
} | |
}, character(1L)), collapse = " ")) | |
invisible() | |
} | |
#' @title Number of strands | |
#' @description The number of strands of a braid. | |
#' | |
#' @param braid a \code{braid} object created with \code{\link{mkBraid}} | |
#' | |
#' @return An integer. | |
#' @export | |
#' | |
#' @examples | |
numberOfStrands <- function(braid) { | |
attr(braid, "n") | |
} | |
#' @title Free reduction of a braid | |
#' @description Applies free reduction to a braid, i.e. removes pairs of | |
#' consecutive generators inverse of each other. | |
#' | |
#' @param braid a \code{braid} object created with \code{\link{mkBraid}} | |
#' | |
#' @return A \code{braid} object. | |
#' @export | |
#' @importFrom maybe just nothing is_nothing from_just | |
#' | |
#' @examples | |
#' braid <- mkBraid(4, list(Sigma(2), SigmaInv(3), Sigma(3))) | |
#' freeReduceBraidWord(braid) | |
freeReduceBraidWord <- function(braid) { | |
reduceStep <- function(gens) { | |
go <- function(changed, w) { | |
if(length(w) >= 2L) { | |
w1 <- w[[1L]] | |
w2 <- w[[2L]] | |
x <- w1[1L] | |
y <- w2[1L] | |
s1 <- w1[2L] | |
s2 <- w2[2L] | |
if(x == y && ((s1 == 1L && s2 == -1L) || (s1 == -1L && s2 == 1L))) { | |
go(TRUE, w[-c(1L, 2L)]) | |
} else { | |
gg <- go(changed, w[-1L]) | |
if(is_nothing(gg)) { | |
nothing() | |
} else { | |
just(c(list(w1), from_just(gg))) | |
} | |
} | |
} else if(length(w) == 1L) { | |
if(changed) { | |
just(c(list(w[[1L]]), w)) | |
} else { | |
nothing() | |
} | |
} else { | |
if(changed) { | |
just(w) | |
} else { | |
nothing() | |
} | |
} | |
} | |
go(FALSE, gens) | |
} | |
loop <- function(w) { | |
x <- reduceStep(w) | |
if(is_nothing(x)) { | |
w | |
} else { | |
loop(from_just(x)) | |
} | |
} | |
mkBraid(numberOfStrands(braid), loop(braid)) | |
} | |
#' @title Braid permutation | |
#' @description Returns the left-to-right permutation associated to a braid. | |
#' | |
#' @param braid a \code{braid} object created with \code{\link{mkBraid}} | |
#' | |
#' @return A permutation. | |
#' @export | |
#' | |
#' @examples | |
#' braid <- mkBraid(4, list(Sigma(2), SigmaInv(3), Sigma(3))) | |
#' braidPermutation(braid) | |
braidPermutation <- function(braid) { | |
n <- numberOfStrands(braid) | |
idxs <- vapply(braid, `[[`, integer(1L), 1L) | |
worker <- function(arr, idxs) { | |
if(length(idxs) == 0L) { | |
arr | |
} else { | |
i <- idxs[1L] | |
is <- idxs[-1L] | |
a <- arr[i] | |
b <- arr[i + 1L] | |
arr[i] <- b | |
arr[i + 1L] <- a | |
worker(arr, is) | |
} | |
} | |
worker(1L:n, idxs) | |
} | |
#' @title Double generator | |
#' @description Generator \code{sigma_{s,t}} in the Birman-Ko-Lee new | |
#' presentation. It twistes the strands \code{s} and \code{t} whie going over | |
#' all other strands (for \code{t=s+1}, this is \code{sigma_s}). | |
#' | |
#' @param n number of strands, integer \code{>= 2} | |
#' @param s,t indices of two strands, \code{s < t} | |
#' | |
#' @return A \code{braid} object. | |
#' @export | |
#' | |
#' @examples | |
#' doubleSigma(5, 1, 3) | |
doubleSigma <- function(n, s, t) { | |
stopifnot(isPositiveInteger(n), isPositiveInteger(s), isPositiveInteger(t)) | |
stopifnot(n >= 2) | |
if(s > n) { | |
stop("The `s` index is out of range.") | |
} | |
if(t > n) { | |
stop("The `t` index is out of range.") | |
} | |
if(s >= t) { | |
stop("`s` must be strictly smaller than `t`.") | |
} | |
if(t - 1L <= s) { | |
gens <- lapply((t-1L):s, Sigma) | |
} else { | |
gens <- lapply((s+1L):(t-1L), SigmaInv) | |
} | |
mkBraid(n, gens) | |
} | |
#' @title Half-twist | |
#' @description The (positive) half-twist of all the braid strands, usually | |
#' denoted by \eqn{\Delta}. | |
#' | |
#' @param n number of strands, integer \code{>= 2} | |
#' | |
#' @return A \code{braid} object. | |
#' @export | |
#' | |
#' @examples | |
#' halfTwist(4) | |
halfTwist <- function(n) { | |
stopifnot(isPositiveInteger(n), n >= 2) | |
# if(n == 1L) { | |
# gens <- list() | |
# } else { | |
subs <- lapply(1L:(n-1L), function(k) { | |
(n-1L):k | |
}) | |
gens <- lapply(do.call(c, subs), Sigma) | |
# } | |
mkBraid(n, gens) | |
} | |
#' @title Inner automorphism | |
#' @description The inner automorphism defined by | |
#' \eqn{\tau X = \Delta^{-1} X \Delta}, where \eqn{\Delta} is the | |
#' positive half-twist; it send each generator \eqn{\sigma_j} to | |
#' \eqn{\sigma_{n-j}}. | |
#' | |
#' @param braid a \code{braid} object created with \code{\link{mkBraid}} | |
#' | |
#' @return A \code{braid} object. | |
#' @export | |
#' | |
#' @examples | |
#' braid <- mkBraid(4, list(Sigma(2), SigmaInv(3), Sigma(3))) | |
#' tau(braid) | |
tau <- function(braid) { | |
n <- numberOfStrands(braid) | |
gens <- lapply(braid, function(gen) { | |
i <- gen[2L] | |
if(i == 1L) { | |
Sigma(n - i) | |
} else { | |
SigmaInv(n - i) | |
} | |
}) | |
mkBraid(n, gens) | |
} | |
#' @title Inverse braid | |
#' @description The inverse of a braid (without performing reduction). | |
#' | |
#' @param braid a \code{braid} object created with \code{\link{mkBraid}} | |
#' | |
#' @return A \code{braid} object. | |
#' @export | |
#' | |
#' @examples | |
#' braid <- mkBraid(4, list(Sigma(2), SigmaInv(3), Sigma(3))) | |
#' ibraid <- inverseBraid(braid) | |
#' composeTwoBraids(braid, ibraid) | |
inverseBraid <- function(braid) { | |
n <- numberOfStrands(braid) | |
invgens <- lapply(braid, function(gen) { | |
c(gen[1L], -gen[2L]) | |
}) | |
mkBraid(n, rev(invgens)) | |
} | |
#' @title Composition of two braids | |
#' @description Composes two braids, doing free reduction on the result. | |
#' | |
#' @param braid1,braid2 \code{braid} objects with the same number of strands | |
#' | |
#' @return A \code{braid} object. | |
#' @export | |
#' | |
#' @examples | |
#' braid <- mkBraid(4, list(Sigma(2), SigmaInv(3), Sigma(3))) | |
#' composeTwoBraids(braid, braid) | |
composeTwoBraids <- function(braid1, braid2) { | |
n <- numberOfStrands(braid1) | |
if(n != numberOfStrands(braid2)) { | |
stop("Unequal numbers of strands.") | |
} | |
freeReduceBraidWord(mkBraid(n, c(braid1, braid2))) | |
} | |
#' @title Composition of many braids. | |
#' @description Composes many braids, doing free reduction on the result. | |
#' | |
#' @param braids list of \code{braid} objects with the same number of strands | |
#' | |
#' @return A \code{braid} object. | |
#' @export | |
#' | |
#' @examples | |
#' braid <- mkBraid(4, list(Sigma(2), SigmaInv(3), Sigma(3))) | |
#' composeManyBraids(list(braid, braid, braid)) | |
composeManyBraids <- function(braids) { | |
ns <- vapply(braids, numberOfStrands, integer(1L)) | |
n <- ns[1L] | |
if(any(ns != n)) { | |
stop("Unequal numbers of strands.") | |
} | |
freeReduceBraidWord(mkBraid(n, do.call(c, braids))) | |
} | |
#' @title Whether a braid is pure | |
#' @description Checks whether a braid is pure, i.e. its permutation is trivial. | |
#' | |
#' @param braid a \code{braid} object | |
#' | |
#' @return A Boolean value. | |
#' @export | |
#' | |
#' @examples | |
#' braid <- mkBraid(4, list(Sigma(2), SigmaInv(3), Sigma(3))) | |
#' isPureBraid(braid) | |
isPureBraid <- function(braid) { | |
identical(braidPermutation(braid), seq_len(numberOfStrands(braid))) | |
} | |
#' @title Whether a braid is positive | |
#' @description Checks whether a braid has only positive Artin generators. | |
#' | |
#' @param braid a \code{braid} object | |
#' | |
#' @return A Boolean value. | |
#' @export | |
#' | |
#' @examples | |
#' braid <- mkBraid(4, list(Sigma(2), SigmaInv(3), Sigma(3))) | |
#' isPositiveBraidWord(braid) | |
isPositiveBraidWord <- function(braid) { | |
signs <- vapply(braid, `[[`, integer(1L), 2L) | |
all(signs == 1L) | |
} | |
#' @title Linking matrix | |
#' @description Linking numbers between all pairs of strands of a braid. | |
#' | |
#' @param braid a \code{braid} object | |
#' | |
#' @return A matrix. | |
#' @export | |
#' | |
#' @examples | |
#' braid <- mkBraid(4, list(Sigma(2), SigmaInv(3), Sigma(3))) | |
#' linkingMatrix(braid) | |
linkingMatrix <- function(braid) { | |
n <- numberOfStrands(braid) | |
perm <- 1L:n | |
doSwap <- function(i) { | |
a <- perm[i] | |
b <- perm[i+1L] | |
perm[i] <<- b | |
perm[i+1L] <<- a | |
invisible() | |
} | |
mat <- matrix(0L, nrow = n, ncol = n) | |
doAdd <- function(i, j, pm1) { | |
x <- mat[i, j] | |
mat[i, j] <<- mat[j, i] <<- x + pm1 | |
invisible() | |
} | |
for(gen in braid) { | |
k <- gen[1L] | |
u <- perm[k] | |
v <- perm[k+1L] | |
doAdd(u, v, gen[2L]) | |
doSwap(k) | |
} | |
mat | |
} | |
#' @title Whether a braid is a permutation braid | |
#' @description Checks whether a braid is a permutation braid, that is, | |
#' a positive braid where any two strands cross at most one, and positively. | |
#' | |
#' @param braid a \code{braid} object | |
#' | |
#' @return A Boolean value. | |
#' @export | |
#' | |
#' @examples | |
#' braid <- mkBraid(4, list(Sigma(2), SigmaInv(3), Sigma(3))) | |
#' isPermutationBraid(braid) | |
isPermutationBraid <- function(braid) { | |
if(isPositiveBraidWord(braid)) { | |
lkMatrix <- linkingMatrix(braid) | |
all(lkMatrix[upper.tri(lkMatrix)] %in% c(0L, 1L)) | |
} else { | |
FALSE | |
} | |
} | |
isPermutation <- function(x) { | |
setequal(x, seq_along(x)) | |
} | |
.permutationBraid <- function(perm) { | |
n <- length(perm) | |
cfwd <- cinv <- 1L:n | |
doSwap <- function(i) { | |
a <- cinv[i] | |
b <- cinv[i+1L] | |
cinv[i] <<- b | |
cinv[i+1L] <<- a | |
u <- cfwd[a] | |
v <- cfwd[b] | |
cfwd[a] <<- v | |
cfwd[b] <<- u | |
invisible() | |
} | |
worker <- function(phase) { | |
if(phase >= n) { | |
list() | |
} else { | |
tgt <- perm[phase] | |
src <- cfwd[tgt] | |
this <- (src-1L):phase | |
lapply(this, doSwap) | |
rest <- worker(phase + 1L) | |
c(list(this), rest) | |
} | |
} | |
worker(1L) | |
} | |
#' @title Permutation braid | |
#' @description Makes a permutation braid from a permutation. | |
#' | |
#' @param perm a permutation | |
#' | |
#' @return A \code{braid} object. | |
#' @export | |
#' | |
#' @examples | |
#' perm <- c(3, 1, 4, 2) | |
#' braid <- permutationBraid(perm) | |
#' isPermutationBraid(braid) | |
#' braidPermutation(braid) | |
permutationBraid <- function(perm) { | |
stopifnot(isPermutation(perm), length(perm) >= 2) | |
gens <- lapply(do.call(c, .permutationBraid(perm)), Sigma) | |
mkBraid(length(perm), gens) | |
} | |
.allPositiveBraidWords <- function(n, l) { | |
go <- function(k) { | |
if(k == 0L) { | |
list(list()) | |
} else { | |
do.call(c, lapply(1L:(n-1L), function(i) { | |
lapply(go(k - 1L), function(rest) { | |
c(list(Sigma(i)), rest) | |
}) | |
})) | |
} | |
} | |
go(l) | |
} | |
#' @title Positive braid words of given length | |
#' @description All positive braid words of the given length. | |
#' | |
#' @param n number of strands, positive integer \code{>= 2} | |
#' @param l length of the words | |
#' | |
#' @return A list of \code{braid} objects. | |
#' @export | |
#' | |
#' @examples | |
#' allPositiveBraidWords(3, 4) | |
allPositiveBraidWords <- function(n, l) { | |
stopifnot(isPositiveInteger(n), n >= 2) | |
lapply(.allPositiveBraidWords(n, l), function(gens) { | |
mkBraid(n, gens) | |
}) | |
} | |
.allBraidWords <- function(n, l) { | |
go <- function(k) { | |
if(k == 0L) { | |
list(list()) | |
} else { | |
gens <- do.call(c, lapply(1L:(n-1L), function(i) { | |
c(list(Sigma(i)), list(SigmaInv(i))) | |
})) | |
do.call(c, lapply(go(k - 1L), function(rest) { | |
lapply(gens, function(gen) { | |
c(list(gen), rest) | |
}) | |
})) | |
} | |
} | |
go(l) | |
} | |
#' @title Braid words of given length | |
#' @description All braid words of the given length. | |
#' | |
#' @param n number of strands, positive integer \code{>= 2} | |
#' @param l length of the words | |
#' | |
#' @return A list of \code{braid} objects. | |
#' @export | |
#' | |
#' @examples | |
#' allPositiveBraidWords(3, 4) | |
allBraidWords <- function(n, l) { | |
stopifnot(isPositiveInteger(n), n >= 2) | |
lapply(.allBraidWords(n, l), function(gens) { | |
mkBraid(n, gens) | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment