Jump to my Home Page Send me a message Check out stuff on GitHub Check out my photography on Instagram Check out my profile on LinkedIn Check out my profile on reddit Check me out on Facebook

Mitch Richling: Torus Knots

Author: Mitch Richling
Updated: 2021-09-22

torus_knot_7_2_small.gif torus_knot_5_7_small.gif

Table of Contents

1. What is this page all about?

Torus knots have many applications including some deep applications in physics. Unfortunately these knots are difficult to visualize for students, and difficult for educators to draw. The whole point of this page is to provide self contained, relatively simple, tools for physicists and educators who wish to render torus knots.

2. Usage Rights

The images of torus knots linked from this page may be freely used for educational and academic purposes. The code may be freely used to generate your own images. If you publish my images, then please provide a proper photo attribution or byline. If you use the code here to create images you publish, then please include this page in your bibliography.

3. Creating Torus Knot Images

3.1. Create the Geometry

The first task is to generate the geometric description of the objects our 3D rendering engine will draw. Our knots are curves, but curves are 1D objects – 3D rendering tools don't render 1D objects, they render 3D objects. So instead of generating a geometric description of a curve, we create a "tube" – a fat curve with some thickness. Many ways exist to do that, but the one I selected for this application is what I call the "ball and cylinder" technique. The idea is to sample the curve along a regularly spaced grid, draw a sphere at each sample point, and connect the spheres together with cylinders. I used this same technique is used to render the Lorenz Strange Attractor. As a side note, this is the kind of geometric structure my LISP calculator program produces as well.

While two scripts may be found in this section, only the first one was used for the images above. The scripts can do a bit more than is demonstrated here. They can dump CSV-style data that may be imported into graphing packages, draw sphere sweep-like geometries instead of ball-n-cylinder, and can add arrows to the curves.

mkTorusKnot.rb
Draw torus knots.
mkTorusHoops.rb
Draw hoops around a torus.

The mkTorusKnot.rb is used to generate a file, tordat.txt, that is used by the rendering tool in the next step. Here is a code snip showing how we do it:

./mkTorusKnot.rb 5 2 1 3 1000 pcyl > tordat.txt

For additional details regarding the script arguments and usage, consult the comments at the top of the scripts and the examples in the section Automating the whole thing

3.2. Render the 3D scene

The geometric object is rendered using POV-Ray. All of the above images are generated with a single POV-Ray source file that "includes" the output of the mkTorusKnot.rb script placed into a file named tordat.txt. The POV-Ray input file is torus_knot.pov and here is how we use it:

povray -D -P -W800 -H600 -Q11 +A0.4 -AM2 -R5 +Itorus_knot.pov +Otorus_knot.png
rm tordat.txt

After the above commands run, the image will be in a file called torus_knot.png

3.3. Annotate and create thumbnail

The tool here is ImageMagick. We use a larger font that one might normally expect because we immediately shrink the image down by 50%.

s0="gravity northwest fill black text 1,11 'Torus Knot ($p,$q)'"
convert torus_knot.png  -pointsize 40 -draw "$s0" -resize 50% torus_knot_small.gif

3.4. Automating the whole thing

It would be a real pain to do the above steps for each image manually, so we automate the process for a selection of low order knots with a little bash script.

#!/bin/bash
# -*- Mode:Shell-script; Coding:utf-8; fill-column:132 -*-

####################################################################################################################################
# @file      go.sh
# @author    Mitch Richling http://www.mitchr.me
# @Copyright Copyright 2013 by Mitch Richling.  All rights reserved.
# @brief     Automate the generation of images for my web page of all prime indexed torus knots from 2 up to 11. @EOL
# @Std       bash

CONVERT='TRUE'
DISPLAY='TRUE'

##----------------------------------------------------------------------------------------------------------------------------------
for p in 2 3 5 7 11; do
  for q in 2 3 5 7 11; do
# for p in  5; do
#   for q in 2; do
    if [ $p != $q ]; then
      echo Working on knot $p $q
      POVOPT=' -Q11 +A0.4 -AM2 -R5 '
      # Uncomment the next line for faster renders
      #POVOPT=' '
      SIZE='-W1920 -H1440'
      # Generate the knot geometry
      ruby ./mkTorusKnot.rb $p $q 1 3 1000 pcyl > tordat.txt
      # Render the image into a PNG file
      if [ -z "MSYSTEM" ]; then
        povray -D -P -W1920 -H1440 $POVOPT +Itorus_knot.pov +Otorus_knot_${p}_${q}.png
      else
        /c/Program\ Files/POV-Ray/v3.7/bin/pvengine.exe  -D -P $SIZE $POVOPT +Itorus_knot.pov +Otorus_knot_${p}_${q}.png //EXIT
      fi
      rm tordat.txt
      if [ "$CONVERT" = 'TRUE' ]; then
        # Create the thumbnail by annotating the image and reducing the size
        s0="gravity northwest fill black text 1,11 'Torus Knot ($p,$q)'"
        convert torus_knot_${p}_${q}.png -pointsize 130 -draw "$s0" -resize 20% torus_knot_${p}_${q}_small.gif
        # Create the big image by annotating the image
        s1="gravity northwest fill black text 1,11 'Torus Knot ($p,$q)'"
        s2="gravity southeast fill black text 1,11 'By Mitch Richling'"
        convert torus_knot_${p}_${q}.png -pointsize 130 -draw "$s2"   -draw "$s1" torus_knot_${p}_${q}_big.gif
      fi
    fi
  done
done

# Display the images
if [ "$DISPLAY" = 'TRUE' ]; then
  if [ -z "MSYSTEM" ]; then
    pqiv -f `ls torus_knot_*.png torus_knot_*_big.gif | sort` torus_knot_*_small.gif &
  else
    explorer .
  fi
fi

4. Standard Torus Knot Images

Here are the images generated by the go.sh script. Note the thumbnails link to larger images.

torus_knot_2_3_small.gif torus_knot_3_2_small.gif torus_knot_2_5_small.gif torus_knot_5_2_small.gif torus_knot_2_7_small.gif torus_knot_7_2_small.gif torus_knot_3_7_small.gif torus_knot_7_3_small.gif torus_knot_3_5_small.gif torus_knot_5_3_small.gif torus_knot_5_7_small.gif torus_knot_7_5_small.gif

5. Images with "hoops"

Here are some examples illustrating "hoops" that one can make with mkTorusHoops.rb curves offset from the torus, and arrows. The thumbnails below don't link to larger images.

x2.gif x4.gif
x1.gif x3.gif

6. Code

6.1. mkTorusKnot.rb

mkTorusKnot.rb

#!/home/richmit/bin/ruby
# -*- Mode:Ruby; Coding:utf-8; fill-column:132 -*-

####################################################################################################################################
# @file      mkTorusKnot.rb
# @author    Mitch Richling http://www.mitchr.me
# @Copyright Copyright 2013 by Mitch Richling.  All rights reserved.
# @brief     Dump out data that can be used to draw a torus knot.@EOL
# @Std       Ruby 1.9
#
#            Dump out data that can be used to draw a torus knot.  Focus is on PovRay and scientific visualization tools like
#            GNUplot.
#
#            USE: p q minorRadius MajorRadius numberOfObjectsInCurve what
#             what is one of:
#              * data  => csv file
#              * pcyl  => povray cylinder data
#              * psph  => povray sphere data
#              * pcyla => povray cylinder data with arrows!
#

##----------------------------------------------------------------------------------------------------------------------------------

require "matrix"

##----------------------------------------------------------------------------------------------------------------------------------
$p       = ARGV[0].to_f
$q       = ARGV[1].to_f
$sRad    = ARGV[2].to_f
$bRad    = ARGV[3].to_f
$numObj  = ARGV[4].to_f
$doWhat  = ARGV[5].to_sym

##----------------------------------------------------------------------------------------------------------------------------------
# Compute points on the torus knot.  The parametrization used is:
#     $$\begin{array}{rcl}
#         x(t) & = & \cos(qt) [R_M+R_m\cos(pt)] \\
#         y(t) & = & \sin(qt) [R_M+R_m\cos(pt)] \\
#         z(t) & = & R_m\sin(pt)              \\
#       \end{array}$$
# Here $t\in[0,2\pi)$, $p,q\in\mathbb{Z}$ are fixed, prime numbers such that $p\neq q$, $R_m\in\mathbb{R}$ is the
# minor radius, and $R_M\in\mathbb{R}$ is the major radius
def functionname(ti)
  t = ti*(2*Math::PI)/$numObj
  Vector[Math::cos($q*t)*($bRad+$sRad*Math::cos($p*t)), Math::sin($q*t)*($bRad+$sRad*Math::cos($p*t)), $sRad*Math::sin($p*t)]
end

##----------------------------------------------------------------------------------------------------------------------------------
case $doWhat
when :data
  # Dump out data for a plotting program.
  # GNUplot example: set datafile separator ","; splot "< ./mkTorusKnot.rb 5 2 1 3 1000 data" using 1:2:3 with lines
  for ti in 0..($numObj-1)
    puts(functionname(ti).to_a.join(','))
  end
  puts(functionname(0).to_a.join(','))
when :psph
  # Dump out data for a sphere sweep povray object
  sphRad = '0.1'
  for ti in 0..($numObj-1)
    puts("  sphere { <#{functionname(ti).to_a.join(',')}> , #{sphRad} }")
  end
when :pcyla, :pcyl
  # Dump out data for connected cylinder with arrows povray data
  sphRad = 0.051
  cylRad = 0.051
  conRad = 3*sphRad
  conTal = 2*conRad
  arrFrq = ($numObj/15).to_i
  zeroPoint = lastPoint = functionname(0)
  for ti in 1..($numObj-1)
    currentPoint = functionname(ti)
    puts("  cylinder { <#{lastPoint.to_a.join(',')}>, <#{currentPoint.to_a.join(',')}> , #{cylRad} }")
    puts("  sphere { <#{currentPoint.to_a.join(',')}> , #{sphRad} }")
    if (($doWhat == :pcyla) && ((ti % arrFrq) == 0)) then
      # Compute location of cone "tail"
      tmp = (lastPoint - currentPoint)
      tmp = conTal * tmp / tmp.magnitude + currentPoint
      puts("  cone { <#{lastPoint.to_a.join(',')}>, #{conRad} <#{tmp.to_a.join(',')}>, 0 }")
    end
    lastPoint = currentPoint
  end
  puts("  cylinder { <#{lastPoint.to_a.join(',')}>, <#{zeroPoint.to_a.join(',')}> , #{cylRad} }")
  puts("  sphere { <#{zeroPoint.to_a.join(',')}> , #{sphRad} }")
else
  puts "ERROR: Unknown 6th argument!"
end

6.2. mkTorusHoops.rb

mkTorusHoops.rb

#!/usr/bin/ruby1.9.1
# -*- Mode:Ruby; Coding:utf-8; fill-column:132 -*-

####################################################################################################################################
# @file      mkTorusHoops.rb
# @author    Mitch Richling http://www.mitchr.me
# @Copyright Copyright 2013 by Mitch Richling.  All rights reserved.
# @brief     Generate files describing hoops around a torus.@EOL
# @Std       Ruby 1.9
#
#            Dump out data that can be used to draw hoops around a torus.  Focus is on PovRay and scientific visualization tools
#            like GNUplot.
#
#            USE: p q minorRadius MajorRadius numberOfObjectsInCurve what
#             what is one of:
#              * data  => CSV data
#              * pcyl  => povray cylinder data
#              * pcyla => povray cylinder data with arrows!
#

##----------------------------------------------------------------------------------------------------------------------------------

require "matrix"

##----------------------------------------------------------------------------------------------------------------------------------
$p       = ARGV[0].to_f
$q       = ARGV[1].to_f
$sRad    = ARGV[2].to_f
$bRad    = ARGV[3].to_f
$numObj  = ARGV[4].to_f
$doWhat  = ARGV[5].to_sym

##----------------------------------------------------------------------------------------------------------------------------------
# Compute points on the torus.  The parametrization used is:
#     $$\begin{array}{rcl}
#         x(t) & = & \cos(u) [R_M+R_m\cos(v)] \\
#         y(t) & = & \sin(u) [R_M+R_m\cos(v)] \\
#         z(t) & = & R_m\sin(v)               \\
#       \end{array}$$
# Here $t\in[0,2\pi)$, $p,q\in\mathbb{Z}$ are fixed, prime numbers such that $p\neq q$, $R_m\in\mathbb{R}$ is the
# minor radius, and $R_M\in\mathbb{R}$ is the major radius
def functionname(u,v)
  Vector[Math::cos(u)*($bRad+$sRad*Math::cos(v)), Math::sin(u)*($bRad+$sRad*Math::cos(v)), $sRad*Math::sin(v)]
end

##----------------------------------------------------------------------------------------------------------------------------------
case $doWhat
when :data
  # Dump out data for a plotting program.
  # GNUplot example: set datafile separator ","; splot "< ./mkTorusHoops.rb 5 2 1 3 1000 data" using 1:2:3 with points
  0.upto($p) do |pi|
    u = pi*(2*Math::PI)/$p
    for vi in 0..$numObj
      v = vi*(2*Math::PI)/($numObj-1)
      puts(functionname(u,v).to_a.join(','))
    end
    puts(functionname(u,0).to_a.join(','))
  end
  0.upto($q) do |qi|
    v = qi*(2*Math::PI)/$q
    for ui in 0..$numObj
      u = ui*(2*Math::PI)/($numObj-1)
      puts(functionname(u,v).to_a.join(','))
    end
    puts(functionname(0,v).to_a.join(','))
  end
when :pcyla, :pcyl
  # Dump out data for connected cylinder with arrows povray data
  sphRad = 0.051
  cylRad = 0.051
  conRad = 3*sphRad
  conTal = 2*conRad
  arrFrq = ($numObj/10).to_i
  0.upto($p-1) do |pi|
    u = pi*(2*Math::PI)/$p
    zeroPoint = lastPoint = functionname(u,0)
    for vi in 1..($numObj-1)
      v = vi*(2*Math::PI)/$numObj
      currentPoint = functionname(u,v)
      puts("  cylinder { <#{lastPoint.to_a.join(',')}>, <#{currentPoint.to_a.join(',')}> , #{cylRad} }")
      puts("  sphere { <#{currentPoint.to_a.join(',')}> , #{sphRad} }")
      if (($doWhat == :pcyla) && ((vi % arrFrq) == 0)) then
        # Compute location of cone "tail"
        tmp = (lastPoint - currentPoint)
        tmp = conTal * tmp / tmp.magnitude + currentPoint
        puts("  cone { <#{lastPoint.to_a.join(',')}>, #{conRad} <#{tmp.to_a.join(',')}>, 0 }")
      end
      lastPoint = currentPoint
    end
    puts("  cylinder { <#{lastPoint.to_a.join(',')}>, <#{zeroPoint.to_a.join(',')}> , #{cylRad} }")
    puts("  sphere { <#{zeroPoint.to_a.join(',')}> , #{sphRad} }")
  end
  0.upto($q-1) do |qi|
    v = qi*(2*Math::PI)/$q
    zeroPoint = lastPoint = functionname(0,v)
    for ui in 1..($numObj-1)
      u = ui*(2*Math::PI)/$numObj
      currentPoint = functionname(u,v)
      puts("  cylinder { <#{lastPoint.to_a.join(',')}>, <#{currentPoint.to_a.join(',')}> , #{cylRad} }")
      puts("  sphere { <#{currentPoint.to_a.join(',')}> , #{sphRad} }")
      if (($doWhat == :pcyla) && ((ui % arrFrq) == 0)) then
        # Compute location of cone "tail"
        tmp = (lastPoint - currentPoint)
        tmp = conTal * tmp / tmp.magnitude + currentPoint
        puts("  cone { <#{lastPoint.to_a.join(',')}>, #{conRad} <#{tmp.to_a.join(',')}>, 0 }")
      end
      lastPoint = currentPoint
    end
    puts("  cylinder { <#{lastPoint.to_a.join(',')}>, <#{zeroPoint.to_a.join(',')}> , #{cylRad} }")
    puts("  sphere { <#{zeroPoint.to_a.join(',')}> , #{sphRad} }")
  end
else
  puts "ERROR: Unknown 6th argument!"
end

6.3. torus_knot.pov

torus_knot.pov

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// @file      torus_knot.pov
// @author    Mitch Richling http://www.mitchr.me
// @Copyright Copyright 2013 by Mitch Richling.  All rights reserved.
// @brief     Torus knot template.@EOL
// @Std       Povray_3.6
//
//            Draw a shiny plastic tube (from the include file tordat.txt) and a transparent torus.  Primary application is
//            torus knots, but anything with a curve and torus may be drawn -- OK, anything with a torus. :)
//            

//----------------------------------------------------------------------------------------------------------------------------------

#include "colors.inc"
#include "textures.inc"

union {
  union {
    #include "tordat.txt"
    texture {
      pigment { color Blue }
      finish  {
         ambient .50
         diffuse 0.05
         reflection 0.07
         specular 0.9
         roughness 0.03
         phong 1 
         phong_size 600
      }
    }
  }
  torus { 
    3, 
    1
    texture {
      pigment { rgbt <1,0,0,0.7> }
      finish {
        ambient 0.9
        diffuse 0.05
        specular 0.11
        roughness 0.1
        reflection 0.0
        phong 0.3
        phong_size 760
      }
    }
    interior {
      ior 1.0
      refraction 0.0
      fade_distance 15
      fade_power 1.5
      caustics 1
    }
    rotate <90,0,0> 
  }
  rotate <55,14,25> 
}

camera { 
    location <0,0,-10>
    look_at  <0,0,0>
}      

light_source { < 0,  0,-30> color White }
light_source { <30, 30,-30> color White }
light_source { <30,-30,-30> color White }

background { color White }

6.4. go.sh

Some effort has been made to make sure this script works on UNIX'ish systems (OS X & Linux) as well as on Windows under MSYS2. You may have to make adjustments for system differences – for example if you installed POV-Ray in a non-default different location.

go.sh

#!/bin/bash
# -*- Mode:Shell-script; Coding:utf-8; fill-column:132 -*-

####################################################################################################################################
# @file      go.sh
# @author    Mitch Richling http://www.mitchr.me
# @Copyright Copyright 2013 by Mitch Richling.  All rights reserved.
# @brief     Automate the generation of images for my web page of all prime indexed torus knots from 2 up to 11. @EOL
# @Std       bash

CONVERT='TRUE'
DISPLAY='TRUE'

##----------------------------------------------------------------------------------------------------------------------------------
for p in 2 3 5 7 11; do
  for q in 2 3 5 7 11; do
# for p in  5; do
#   for q in 2; do
    if [ $p != $q ]; then
      echo Working on knot $p $q
      POVOPT=' -Q11 +A0.4 -AM2 -R5 '
      # Uncomment the next line for faster renders
      #POVOPT=' '
      SIZE='-W1920 -H1440'
      # Generate the knot geometry
      ruby ./mkTorusKnot.rb $p $q 1 3 1000 pcyl > tordat.txt
      # Render the image into a PNG file
      if [ -z "MSYSTEM" ]; then
        povray -D -P -W1920 -H1440 $POVOPT +Itorus_knot.pov +Otorus_knot_${p}_${q}.png
      else
        /c/Program\ Files/POV-Ray/v3.7/bin/pvengine.exe  -D -P $SIZE $POVOPT +Itorus_knot.pov +Otorus_knot_${p}_${q}.png //EXIT
      fi
      rm tordat.txt
      if [ "$CONVERT" = 'TRUE' ]; then
        # Create the thumbnail by annotating the image and reducing the size
        s0="gravity northwest fill black text 1,11 'Torus Knot ($p,$q)'"
        convert torus_knot_${p}_${q}.png -pointsize 130 -draw "$s0" -resize 20% torus_knot_${p}_${q}_small.gif
        # Create the big image by annotating the image
        s1="gravity northwest fill black text 1,11 'Torus Knot ($p,$q)'"
        s2="gravity southeast fill black text 1,11 'By Mitch Richling'"
        convert torus_knot_${p}_${q}.png -pointsize 130 -draw "$s2"   -draw "$s1" torus_knot_${p}_${q}_big.gif
      fi
    fi
  done
done

# Display the images
if [ "$DISPLAY" = 'TRUE' ]; then
  if [ -z "MSYSTEM" ]; then
    pqiv -f `ls torus_knot_*.png torus_knot_*_big.gif | sort` torus_knot_*_small.gif &
  else
    explorer .
  fi
fi