Contact Geometries – Introducing Knoty

Written by Stanislaus Stein

The goal of this Python Module is to provide an interactive framework for the visualization of Legendre knots. It generates interactive 3D plots of Legendre knots and contact structures. The final result is an interactive plot such as

Further more the module aims to provide a convenient way of reconstructing a 3D visualization from a front projection. The module can load in Bezier Curves from SCG files and plot the corresponding 3D structure. Consider the front projection

It was drawn in Inkscape using the Bezier Paths tool. Knoty can import the SVG file resulting in


To install the knoty Module simply copy the “knoty” folder into the directory containing your Python file. The simplest way is to clone the GitHub repository using

git clone

The repository also includes a jupyter notebook illustrating the general workflow. Make sure, that all dependencies are installed.


  • K3D
  • Numpy
  • Sympy
  • SvgPathTools

A note on K3D: K3D is a module that generates interactive 3D plots just like Matplotlib. In contrast to Matplotlib however K3D can utilize GPU acceleration. The difference is staggering.

Basic Usage

First we load the dependencies and the module

import knoty
import matplotlib.pyplot as plt
import numpy as np
import sympy as sp

The basic step of any visualization is to create a K3D plot, initialize objects on the plot and then visualize it. The visualizations are generated by functions provided in the knoty module. They always take in the plot and some arguments and return the plot with added information that is then plotted by k3d.display(). Therefore a cell always looks as follows:

plot = k3d.plot() # Create the K3D plot
plot = knoty.some_visualisation(plot, args) # Initialize some visualizations
plot.display() # Display the plot

Visualise Contact Structures

We can specify a Contact Form in Cartesian coordinates using the notation

form = [0, 'x', 1]

where the first entry describes the coefficient of the \(dx\), the second entry the coefficient of \(dy\) and the third entry the coefficient of \(dz\). We can also specify the three coordinates as coefficients using upticks. Therefore the expression above defines the form

\(xdy +dz\)

i.e. the standard contact form. We can now plot the contact structure via the knoty.plot_contact_structure() function. The function needs the 3D plot as an input. An example is given by the following:

plot = k3d.plot()
form = [0, 'x', 1]
plot = knoty.plot_contact_structure(plot, form = form, alpha = 0.6)

The argument form specifies the form. If no form is given, then the function defaults to the standard contact form. The argument alpha sets the transparency of the contact planes and defaults to 0.5.

Visualise Legendre Knots

There are two ways to specify Legendre knots in knoty. Either trough an explicit equation or though a drawn front projection. For drawn front projections please see “Specifying knots through SVGs”.

To define a knot through an equation we first need to define a function for the knot as follows

def knot(t):
   return 3 * sp.sin(t) * sp.cos(t), sp.cos(t), sp.sin(t)**3

Make sure that the return contains three coordinates and that the components are defined in a Sympy compatible manner. Here for example we choose sp.sin instead of np.sin to get the sympy version of the sine function.

We can now plot the knot through the knoty.plot_knot() function. This function need the 3D plot and the function defining the knot. An example is given by the following:

plot = k3d.plot()
plot = knoty.plot_knot(plot, knot)

We can also plot contact planes along the knot through the knoty.plot_planes_along_knot() function. It again takes in the 3D plot and the knot equation. Additionally we can layer everything onto a single plot. For example, we could visualise the standard contact structure, plot the knot and indicate the contact planes in one 3D plot through the following code:

plot = k3d.plot()
plot = knoty.plot_contact_structure(plot)
plot = knoty.plot_knot(plot, knot)
plot = knoty.plot_planes_along_knot(plot, knot)

Specifying knots through SVGs

Knoty can import a front projection specified as an SVG using the knoty.import_from_svg() function. It returns a 3D Sympy equation that is compatible with the rest of the module. The path to the SVG needs to be specified as a string and given to the function as the first argument.

svg_file = './Examples/Blatt4A21.svg' # Replace with your SVG file path
svg_knot = knoty.import_from_svg(svg_file) # Generate knot equation
# Proceed with any knoty function using svg_knot as the knot equation...
Auto correction

To reduce the workload on the user, the import function attempts to correct the knot equation as to ensure continuity of the knot. The theory works as follows:

The variable of the Legendrian knot is specified by

\(x = -\frac{dz}{dy}\, .\)

Therefore for continuity, we need to ensure that all Bezier curves meet with the same derivative in the same (y, z) coordinate.

An SVG of Bezier Curves is organized in two layers. First we get a list of all continuous paths. Second each such list is a collection of linear or cubic Bezier curves specified by their control points. We assume that if a user draws a path in their vector graphics editor, that this path is already continuous.

Therefore we only worry about what happens, when the ends of two different paths come close to each other.

Here we assume, that the user is trying to draw a cusp. Therefore the function tries to auto correct the control points to achieve continuity. As discussed above, continuity is achieved if the curves meet tangentially. The simples way to achieve this is to first move the end points of the two different paths onto their average, ensuring (y, z) continuity and to then project their respective control points onto the horizontal going through that average point. Then, by the properties of Bezier curves, continuity is achieved.

During the import of the SVG the function will print out the corrections made

Correcting start point (0.15299824-7.6717687j) and start point (0.17485513-7.6936256j) to (0.16392668500000002-7.68269715j)
Correcting end point (6.600781-6.6663516000000005j) and start point (6.5789241-6.6007809j) to (6.58985255-6.63356625j)

To see if all necessary corrections were made, one might simply count the number of corrections and compare it to the number of cusps in the front.

The user can control this process using two arguments:

  • threshold (float, default 0.1) sets the radius in which an end point searches for other end points. Modify this if not all cusps were recognized or if the auto correction wrongly groups path ends together
  • correction (boolean, default True) sets whether the correction is performed. Useful if the front is crowded.

All in all this allows the user to draw a front without being too concerned about the exact Bezier curves. But be mindful of this feature, it may distort your knot. Notice how in the example the writhe of the knot changes.


  • Full in code documentation
  • Better scaling of SVG knots


  • Marc Kegel for holing an excellent lecture on Contact Geometry.
  • Lenny Ng for providing me with the equations for his beautiful Legendrian knots from his Legendrian knot gallery.
  • HEGL for technical support and all the 3D prints they have done.

Leave a Reply

Your email address will not be published. Required fields are marked *