% -*- mode: noweb; noweb-default-code-mode: R-mode; -*- \documentclass[nojss]{jss} \usepackage{dsfont} \usepackage{bbm} \usepackage{amsfonts} \usepackage{wasysym} \usepackage{amssymb} %\title{Knot Theory in R} %\subtitle{The \pkg{knotR} package} %\author{Robin K. S. Hankin} %\maketitle \author{Robin K. S. Hankin\\The University of Stirling} \title{Visually pleasing knot projections in \proglang{R}} %\VignetteIndexEntry{A vignette for the knot package} %% for pretty printing and a nice hypersummary also set: \Plainauthor{Robin K. S. Hankin} \Plaintitle{Visually pleasing knot projections in R} \Shorttitle{Visually pleasing knot projections in \proglang{R}} %% an abstract and keywords \Abstract{ To cite this work in publications, use \cite{hankin2023}. In this short article I introduce the \pkg{knotR} package, which creates two dimensional knot diagrams optimized for visual appearance. The \pkg{knotR} package is a systematic \proglang{R}-centric suite of software for the creation of production-quality artwork of knot diagrams. } \Keywords{Knot theory, R} \Plainkeywords{Knot theory, R} %% publication information %% NOTE: This needs to filled out ONLY IF THE PAPER WAS ACCEPTED. %% If it was not (yet) accepted, leave them commented. %% \Volume{13} %% \Issue{9} %% \Month{September} %% \Year{2004} %% \Submitdate{2004-09-29} %% \Acceptdate{2004-09-29} %% \Repository{https://github.com/RobinHankin/knotR} %% this line for Tragula %% The address of (at least) one author should be given %% in the following format: \Address{ Robin K. S. Hankin\\%\orcid{https://orcid.org/0000-0001-5982-0415}\\ The University of Stirling\\ Scotland } %% It is also possible to add a telephone and fax number %% before the e-mail in the following format: %% Telephone: +43/1/31336-5053 %% Fax: +43/1/31336-734 %% for those who use Sweave please include the following line (with % symbols): %% need no \usepackage{Sweave.sty} %% end of declarations %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% <>= ignore <- require(knotR) @ \SweaveOpts{} \begin{document} \section{Introduction} A \emph{mathematical knot} is a smooth, unoriented embedding of a circle~$\mathbb{S}^1$ into~$\mathbb{R}^3$~\citep{manturov2004}. Two knots are said to be equivalent if one can be continuously deformed in to the other; if so, there is a homeomorphism~$h\colon\mathbb{R}^3\longrightarrow\mathbb{R}^3$ which takes one embedded circle to the other. It is common to present a knot using diagrams such as Figure~\ref{knot_table}, in which a two-dimensional projection of the knot is given with broken lines indicating where one strand passes over another. \begin{figure}[h] \centering \includegraphics[width=10cm]{1280px-Knot_table.png} \caption{A table of prime knots up \label{knot_table} to seven crossings, labels following~\citet{alexander1926}. Image taken from~\citet{wikipedia_knot_theory}.} \end{figure} Consider Figure~\ref{knot_table} from an \ae sthetic perspective; the diagrams are representative of those available under a free license. However, these diagrams are not suitable for high-quality artwork such as posters: they are not vectorized. Many of the underlying knots possess a line of symmetry (at least, the diagrams do if the breaks are ignored), which is not present in the visual representation. Also, several of the strands cross at acute angles. The diagram for knot~$7_3$, for example, contains ugly kinks and abrupt changes of curvature. Such considerations suggest that knot diagrams might be produced by minimization of some objective function that quantifies the visual inelegance of a knot diagram. The precise nature of such an objective function is, of course, a subjective choice but one might require the following desiderata: \begin{itemize} \item Curvature to be as uniform as possible \item Strands to cross at right angles \item Crossing points to be separate from one another \item Any symmetry present in the knot should be enforced exactly, and be visually apparent \end{itemize} Knot diagrams may be created using vectorized graphics software such as inkscape~\citep{kirsanov2009}: one specifies a sequence of control points, then interpolates between these points to create a knot diagram with the appropriate topology. One way of smoothly interpolating between specified points is Bezier curves~\citep{olsen2014}. A Bezier curve is a visually pleasing polynomial path that can be used to specify the path of a knot projection. The package presented here allows one to specify a knot in terms of its Bezier control points within inkscape, import the object into R, and then to use numerical optimization techniques to improve the visual appearance of the knot. \section{The package in use} In this section, I give workflows for creating two simple knots: firstly~$7_6$, followed by the figure-of-eight knot~$4_1$ which requires imposition of symmetry constraints. The first step is to create a closed curve in inkscape that shows the rough outline of the knot~(Figure~\ref{screenshot_inkscape_7_6} shows a screenshot of \code{7_6_first_draft.svg}, supplied with the package). Note that this file contains only the knot {\em path}; the over and under information is to be added later. Here, knot paths are required to have Bezier handles that are symmetrically placed with regard to nodes. Although radius of curvature is not necessarily matched at either side of a node, visual continuity of the strand is ensured. In the diagram, strand crossing points are far from nodes, as this ensures visual continuity of strands at crossing points, especially the understrand. \begin{figure}[h] \centering \includegraphics[width=10cm]{screenshot_inkscape_7_6.png} \caption{Screenshot of inkscape\label{screenshot_inkscape_7_6} setup for knot~$7_6$. Nodes are shown as squares, handles as small circles, symmetrically placed on either side of nodes} \end{figure} The knot shown in Figure~\ref{screenshot_inkscape_7_6} is clearly suboptimal: even though the nodes are connected by Bezier curves which are individually smooth, the path as a whole is ugly and unattractive as its path has unsightly regions where the radius of curvature changes abruptly. Although it is possible in principle to improve the visual appearance of the knot path by hand in inkscape, this is a surprisingly difficult and frustrating task. In order to remedy the flaws of the diagram using an automated system, we first read the \code{.svg} file into R using the \code{reader()} function: <>= k76 <- reader(system.file("7_6_first_draft.svg",package="knotR")) head(k76) @ Object~\code{k76} is stored as an object of class \code{inkscape}: a two-column matrix with rows corresponding to the node and handle positions of the inkscape path. This representation has a certain amount of redundancy as knot paths have handles which are symmetrically placed with respect to nodes; also, the first node is the same as the last for the loop is closed. The package can coerce inkscape objects to other forms, specifically \code{minobj} objects, which contain no redundancy (the position of each node, as well as one of the handles, is stored); or \code{controlpoints} objects, which allow for easy construction of Bezier interpolation between nodes. \begin{figure}[htbp] \begin{center} <<7_6_rough,fig=TRUE>>= par(oma=c(0, 0, 0, 0)) par(mar=c(0, 0, 0, 0)) par(plt=c(0, 1, 0, 1)) k76_rough <- reader(system.file("7_6_first_draft.svg",package="knotR")) knotplot2(k76_rough, seg=TRUE) @ \caption{The \emph{path} of \label{7_6_rough} knot~$7_6$, showing Bezier control points. Coloured circles have a radius proportional to the curvature (that is, the reciprocal of the radius of curvature) along the strand} \end{center} \end{figure} \begin{figure}[!tbp] \centering <>= par(oma=c(0, 0, 0, 0)) par(mar=c(0, 0, 0, 0)) par(plt=c(0, 1, 0, 1)) par(pty='m') knotplot2(k7_6,text=TRUE,lwd=1,circ=0,rainbow=TRUE) @ \caption{Knot~$7_6$ with strands numbered \label{k76_strands} to guide creation of over and under information} \end{figure} To beautify it we need to specify a function of the path that quantifies its displeasingness, and then minimize this objective function using numerical methods. Two examples of desiderata for such an objective function might be to keep the strands crossing at right angles, and the overall bending energy. These are evaluated in the package by functions \code{total_crossing_angle_badness()} and~\code{total_bending_energy()} respectively: <>= b <- as.controlpoints(k76_rough) total_crossing_angle_badness(b) total_bending_energy(b) @ The knots supplied in the package minimize a weighted sum of these and other badnesses\footnote{Function \code{badness()} includes various ``housekeeping'' badnesses which are used to make sure that the minimum found by \code{nlm()} is topologically identical to the starting configuration. Function \code{non\_crossing\_strand\_close\_approach\_badness()}, for example, makes non-crossing strands ``repel'' one another so as not to introduce spurious intersections.}, as evaluated by function \code{badness()}: <>= badness(k76_rough) @ This function may be minimized by numerical optimization: \begin{Schunk} \begin{Sinput} > o <- nlm(badness, as.knotvec(k76_rough)) > k7_6 <- as.minobj(o$estimate) > badness(k7_6) \end{Sinput} \begin{Soutput} [1] 3.550152 \end{Soutput} \end{Schunk} (the above takes about an hour of CPU time: it is optimizing function of~64 real variables, and the objective function takes a few seconds to evaluate). However, the result is much nicer (Figure~\ref{7_6}). \begin{figure}[!tbp] \centering <>= knotplot2(k7_6) @ \caption{Knot $7_6$, post-optimization\label{7_6}} \end{figure} To specify the senses of the knot's\ %---------------------------------' crossings, we create an overunder object which is a two-column matrix: <>= ou76 <- matrix(c( 12,01, 02,11, 07,03, 04,15, 16,06, 14,08, 10,13 ),byrow=TRUE,ncol=2) @ With reference to Figure~\ref{k76_strands}, each row of \code{ou76} corresponds to a crossing; the first element gives the overstrand and the second the understrand; thus strand~12 passes over strand~1, strand~2 passes over strand~11, and so on. The result is shown in figure~\ref{76_overunder}. \begin{figure}[!tbp] \centering <>= knotplot(k7_6,ou76) @ \caption{Knot $7_6$, post-optimization with breaks indicating \label{76_overunder} underpassing strands} \end{figure} \subsection{Symmetry} Many of the knots in Figure~\ref{knot_table} have an axis of symmetry, or possess rotational symmetry. The package has the ability to impose symmetry relations on knots, and to optimize the resulting symmetrical knot. Minimizing the badness is not entirely straightforward on account of the induced redundancy, which is characterized using a symmetry object specific to the knot under consideration. However, symmetry constraints do reduce the dimensionality of the optimization problem. I will consider the figure-of-eight knot~$4_1$ as an example. Using Figure~\ref{four_figure_8_knots}, top left, as reference, the appropriate symmetry object is defined as follows: <>= fig8 <- reader(system.file("4_1_first_draft.svg",package="knotR")) Mver8 <- matrix(c( 02,03, 09,07, 05,11, 10,06 ),ncol=2,byrow=TRUE) sym8 <- symmetry_object(fig8, Mver=Mver8, xver=8) @ (Matrix \code{Mver8} specifies that nodes~2 and~3 are symmetric, as are nodes~9 and~7, and so on; \code{xver=8} forces node~8 to be on the axis of symmetry). The results are shown in Figure~\ref{four_figure_8_knots}. \begin{figure}[!tbp] \centering <>= par(oma=c(0, 0, 0, 0)) par(mar=c(0, 0, 0, 0)) par(plt=c(0, 1, 0, 1)) par(pty='m') plot(NULL,xlim=c(-600,2200),ylim=c(-600,2200),asp=1,axes=FALSE) offx <- 1400 offy <- 1500 overunder_fig8 <- matrix(c( 05,10, 08,13, 01,06, 11,02 ),ncol=2,byrow=TRUE) jj <- sweep(as.inkscape(fig8),2,c(0,offy),"+") knotplot2(jj,add=TRUE,node=TRUE,lwd=3,circ=0) # top left fig8_symmetrized <- symmetrize(fig8,sym8) jj <- sweep(as.inkscape(fig8_symmetrized),2,c(offx,offy),"+") knotplot2(jj,add=TRUE,node=FALSE,width=TRUE) # top right jj <- sweep(as.inkscape(k4_1),2,c(0,0),"+") knotplot2(jj,add=TRUE,node=FALSE,width=TRUE) # lower left jj <- sweep(as.inkscape(k4_1),2,c(offx,0),"+") knotplot(jj,overunder_fig8,add=TRUE) # lower right @ \caption{Figure eight knots drawn using different plotting methods. top left, knot path with node numbers shown in order to facilitate \label{four_figure_8_knots} definition of the symmetry object; top right, result of symmetrizing the rough path; lower left, the optimized knot with imposed vertical symmetry, with curvature plotted; lower right, knot plotted with overstrand and understrand indicated using line breaks} \end{figure} \subsection{Rotational symmetry} Consider knot~$5_1$. This knot has fivefold rotational symmetry, in addition to a vertical line of symmetry. The package includes functionality to impose appropriate symmetry constraints. Using Figure~\ref{k5_1_twoknots} as reference, we have: \begin{figure}[htbp] \begin{center} <>= knotplot2(k5_1,node=TRUE,width=FALSE) @ \caption{Knot $5_1$ \label{k5_1_twoknots} shown with node numbers} \end{center} \end{figure} <>= sym51 <- symmetry_object(k5_1, Mver = cbind(11,13), xver = c(2,12), Mrot = rbind( c(12,04,16,08,20), c(13,05,17,09,01), c(11,03,15,07,19), c(02,14,06,18,10) )) @ Thus, using the same notation as before, nodes~11 and~13 are symmetrical about the vertical axis, nodes~2 and~12 are on the vertical axis. The \code{Mrot} argument specifies sets of nodes that map to themselves under rotation. The top line of \code{Mrot} indicates that nodes 12,4,16, 8, and~20 are concyclic. An example of a rotationally symmetric knot is given in Figure~\ref{orn20}. \section{Conclusions and further work} The \pkg{knot} package allows the user to create rough diagrams of knots using the inkscape suite of software, and subsequently polish up such diagrams in terms of a customizable objective function using numerical optimization techniques. Further work might include functionality to deal with links. \clearpage \section{Gallery} There now follows a selection of pleasing knot diagrams taken from datasets provided with the package. \begin{figure}[htbp] \begin{center} <>= par(oma=c(0, 0, 0, 0)) par(mar=c(0, 0, 0, 0)) par(plt=c(0, 1, 0, 1)) par(pty='m') plot(NULL,xlim=c(-700,2200),ylim=c(-1800,200),asp=1,box=FALSE) jjA <- as.inkscape(perko_A)*1.7 jjB <- as.inkscape(perko_B)*1.7 oA <- perko_A$overunder oB <- perko_B$overunder knotplot(jjA,ou=oA,add=TRUE,lwd=4) jjB <- sweep(as.inkscape(jjB),2,c(1500,-600),"+") knotplot(jjB,ou=oB,add=TRUE,lwd=4) @ \caption{Two representations of knot~$10_{125}$, known as the \label{perko_AB} Perko Pair} \end{center} \end{figure} \begin{figure}[htbp] \begin{center} <>= par(oma=c(0, 0, 0, 0)) par(mar=c(0, 0, 0, 0)) par(plt=c(0, 1, 0, 1)) par(pty='m') knotplot(ornamental20) @ \caption{An ornamental knot exhibiting fivefold rotational \label{orn20} symmetry; note the absence of mirror symmetry} \end{center} \end{figure} \begin{figure}[htbp] \begin{center} <<10123,fig=TRUE>>= par(oma=c(0, 0, 0, 0)) par(mar=c(0, 0, 0, 0)) par(plt=c(0, 1, 0, 1)) par(pty='m') knotplot(k10_123) @ \caption{Knot $10_{123}$, exhibiting fivefold symmetry} \end{center} \end{figure} \begin{figure}[htbp] \begin{center} <>= par(oma=c(0, 0, 0, 0)) par(mar=c(0, 0, 0, 0)) par(plt=c(0, 1, 0, 1)) par(pty='m') a <- list(k8_1, k8_2, k8_3, k8_4, k8_5, k8_6, k8_7, k8_8, k8_9, k8_10, k8_11, k8_12, k8_13, k8_14, k8_15, k8_16, k8_17, k8_18, k8_19, k8_20, k8_21) b <- lapply(a,overunder) plot(1:10,xlim=c(0,6000),ylim=c(0,12000),asp=1,type='n',axes=F,xlab='',ylab='') xs <- 1600 ys <- 1600 for(i in 1:3){ for(j in 1:7){ n <- (i-1)*7 + j xoff <- i*xs yoff <- j*ys k <- as.inkscape(a[[n]]) k[,2] <- k[,2] - mean(k[,2]) k <- sweep(k,2,c(xoff,yoff),"+") knotplot(k,b[[n]],add=TRUE,lwd=2) } } @ \caption{All prime eight-crossing knots, following \label{all8} Rolfsen} \end{center} \end{figure} \bibliography{knot} \end{document}