diff --git a/build/pkgs/database_knotinfo/SPKG.rst b/build/pkgs/database_knotinfo/SPKG.rst
new file mode 100644
index 00000000000..460416cc621
--- /dev/null
+++ b/build/pkgs/database_knotinfo/SPKG.rst
@@ -0,0 +1,35 @@
+database_knotinfo
+=================
+
+Description
+-----------
+
+Database for named knots and links provided at
+
+https://knotinfo.math.indiana.edu/
+
+and
+
+https://linkinfo.sitehost.iu.edu'
+
+Dependencies
+------------
+
+- Sage library
+
+Changelog
+---------
+
+- 20200713 (Sebastian Oehms, 13 Juli 2020, :trac:`30352`, initial version)
+
+ The tarball has been created from the both download files at the
+ given date:
+
+ ``knotinfo_data_complete.xls``
+ ``linkinfo_data_complete.xlsx``
+
+ exporting them to CSV via LibreOffice.
+
+ The second file has been changed manually deleting one character:
+ a trailing "}" occuring in the homfly_polynomial column of the last
+ link ``L11n459{1,1,1}``.
diff --git a/build/pkgs/database_knotinfo/checksums.ini b/build/pkgs/database_knotinfo/checksums.ini
new file mode 100644
index 00000000000..985cba4b86c
--- /dev/null
+++ b/build/pkgs/database_knotinfo/checksums.ini
@@ -0,0 +1,5 @@
+tarball=knotinfo-20200713.tar.bz2
+sha1=ec6c8436d5565fdd140cd3e4b301d215cd62b0d0
+md5=1d84f176290bdd3a752757242512fca2
+cksum=3419853512
+upstream_url=https://trac.sagemath.org/raw-attachment/ticket/30352/knotinfo-20200713.tar.bz2
diff --git a/build/pkgs/database_knotinfo/dependencies b/build/pkgs/database_knotinfo/dependencies
new file mode 100644
index 00000000000..c1b713883fe
--- /dev/null
+++ b/build/pkgs/database_knotinfo/dependencies
@@ -0,0 +1,5 @@
+| $(SAGERUNTIME)
+
+----------
+All lines of this file are ignored except the first.
+It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile.
diff --git a/build/pkgs/database_knotinfo/package-version.txt b/build/pkgs/database_knotinfo/package-version.txt
new file mode 100644
index 00000000000..f6f6326f811
--- /dev/null
+++ b/build/pkgs/database_knotinfo/package-version.txt
@@ -0,0 +1 @@
+20200713
diff --git a/build/pkgs/database_knotinfo/spkg-check.in b/build/pkgs/database_knotinfo/spkg-check.in
new file mode 100644
index 00000000000..94d3e947e00
--- /dev/null
+++ b/build/pkgs/database_knotinfo/spkg-check.in
@@ -0,0 +1,10 @@
+cd $SAGE_ROOT/src/sage/
+
+echo "Testing databases/knotinfo_db.py"
+sage -t --optional="sage,database_knotinfo" databases/knotinfo_db.py || sdh_die "Error testing KnotInfo databases"
+
+echo "Testing knots/knotinfo.py"
+sage -t --optional="sage,database_knotinfo" knots/knotinfo.py || sdh_die "Error testing KnotInfo funcionality"
+
+echo "Testing knots/link.py"
+sage -t --optional="sage,database_knotinfo" knots/link.py || sdh_die "Error testing KnotInfo funcionality"
diff --git a/build/pkgs/database_knotinfo/spkg-install.in b/build/pkgs/database_knotinfo/spkg-install.in
new file mode 100644
index 00000000000..e987af7791d
--- /dev/null
+++ b/build/pkgs/database_knotinfo/spkg-install.in
@@ -0,0 +1,21 @@
+INSTALL="yes"
+TARGET="${SAGE_SHARE}/knotinfo"
+VERSION=`cat package-version.txt`
+if [ -d $TARGET ]
+then
+ diff package-version.txt $TARGET > /dev/null 2>&1
+ if [ $? -eq 0 ]
+ then
+ INSTALL="no"
+ echo "Version $VERSION of knotinfo already installed"
+ else
+ OLD_VERSION=`cat $TARGET/package-version.txt`
+ echo "Removing former version $OLD_VERSION of knotinfo"
+ rm -rf $TARGET
+ fi
+fi
+
+if [ "$INSTALL" = "yes" ]
+then
+ exec sage-python23 spkg-install.py
+fi
diff --git a/build/pkgs/database_knotinfo/spkg-install.py b/build/pkgs/database_knotinfo/spkg-install.py
new file mode 100644
index 00000000000..7e9c3fad8f3
--- /dev/null
+++ b/build/pkgs/database_knotinfo/spkg-install.py
@@ -0,0 +1,15 @@
+import os
+from sage.all import save
+from sage.env import SAGE_SHARE
+from sage.misc.misc import sage_makedirs
+from sage.databases.knotinfo_db import KnotInfoDataBase
+
+install_root = os.path.join(SAGE_SHARE, 'knotinfo')
+
+if __name__ == '__main__':
+ sage_makedirs(install_root)
+ print("Creating the KnotInfo database.")
+ ki_db = KnotInfoDataBase()
+ ki_db.create_col_dict_sobj()
+ ki_db.create_data_sobj()
+ os.system('cp package-version.txt %s' %install_root)
diff --git a/build/pkgs/database_knotinfo/type b/build/pkgs/database_knotinfo/type
new file mode 100644
index 00000000000..134d9bc32d5
--- /dev/null
+++ b/build/pkgs/database_knotinfo/type
@@ -0,0 +1 @@
+optional
diff --git a/src/doc/en/reference/databases/index.rst b/src/doc/en/reference/databases/index.rst
index 5c198d9dcdf..34ae3ba3f1f 100644
--- a/src/doc/en/reference/databases/index.rst
+++ b/src/doc/en/reference/databases/index.rst
@@ -62,5 +62,6 @@ database engine.
sage/databases/cunningham_tables
sage/databases/db_class_polynomials
sage/databases/db_modular_polynomials
+ sage/databases/knotinfo_db
.. include:: ../footer.txt
diff --git a/src/doc/en/reference/knots/index.rst b/src/doc/en/reference/knots/index.rst
index ecaa3832288..8fc794b4705 100644
--- a/src/doc/en/reference/knots/index.rst
+++ b/src/doc/en/reference/knots/index.rst
@@ -6,5 +6,6 @@ Knot Theory
sage/knots/knot
sage/knots/link
+ sage/knots/knotinfo
.. include:: ../footer.txt
diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py
new file mode 100644
index 00000000000..25b472cfff6
--- /dev/null
+++ b/src/sage/databases/knotinfo_db.py
@@ -0,0 +1,1028 @@
+# -*- coding: utf-8 -*-
+r"""
+KontInfo Database
+
+This module contains the class :class:`KnotInfoDataBase` and auxilary classes for it
+which serves as an interface to the lists of named knots and links provided at the web-pages
+`KnotInfo `__ and `LinkInfo `__.
+
+
+AUTHORS:
+
+- Sebastian Oehms August 2020: initial version
+"""
+
+
+##############################################################################
+# Copyright (C) 2020 Sebastian Oehms
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+# http://www.gnu.org/licenses/
+##############################################################################
+
+
+import os
+import csv
+from enum import Enum
+
+from sage.structure.sage_object import SageObject
+from sage.misc.persist import save, load
+from sage.misc.verbose import verbose
+from sage.misc.cachefunc import cached_method
+from sage.env import SAGE_SHARE, SAGE_ROOT
+
+
+class KnotInfoColumnTypes(Enum):
+ r"""
+ Enum class to specify if a column from the table of knots and links provided at the web-pages
+ `KnotInfo `__ and `LinkInfo `__.
+ is used for knots only, links only or both.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoColumnTypes
+ sage: [col_type for col_type in KnotInfoColumnTypes]
+ [,
+ ,
+ ]
+ """
+
+ OnlyKnots = 'K' # column that is only used in the KnotInfo table
+ OnlyLinks = 'L' # column that is only used in the LinkInfo table
+ KnotsAndLinks = 'B' # column that is only used in both tables
+
+
+class KnotInfoColumns(Enum):
+ r"""
+ Enum class to select a column from the table of knots and links provided at the web-pages
+ `KnotInfo `__ and `LinkInfo `__.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns, KnotInfoColumnTypes
+ sage: ki_db = KnotInfoDataBase()
+ sage: KnotInfoColumns('Columns', ki_db.read_column_dict())
+
+ sage: [col.column_name() for col in _ if col.column_type() == col.types.OnlyLinks] # optional - database_knotinfo
+ ['Name - Unoriented',
+ 'Orientation',
+ 'Unoriented Rank',
+ 'PD Notation (vector)',
+ 'PD Notation (KnotTheory)',
+ 'Multivariable Alexander Polynomial',
+ 'HOMFLYPT Polynomial',
+ 'Unoriented',
+ 'Arc Notation',
+ 'Linking Matrix',
+ 'Rolfsen Name',
+ 'Components',
+ 'DT code',
+ 'Splitting Number',
+ 'Nullity',
+ 'Unlinking Number',
+ 'Weak Splitting Number']
+ """
+ @property
+ def types(self):
+ r"""
+ Return :class:`KnotInfoColumnTypes` to be used for checks.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns
+ sage: ki_db = KnotInfoDataBase()
+ sage: KIcols = KnotInfoColumns('Columns', ki_db.read_column_dict())
+ sage: KIcols.dt_code.column_type() == KIcols.dt_code.types.OnlyLinks
+ True
+ """
+ return KnotInfoColumnTypes
+
+ def column_name(self):
+ r"""
+ Return the name of ``self`` displayed on the KnotInfo web-page.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns
+ sage: ki_db = KnotInfoDataBase()
+ sage: KIcols = KnotInfoColumns('Columns', ki_db.read_column_dict())
+ sage: KIcols.dt_code.column_name()
+ 'DT code'
+ """
+ return self.value[0]
+
+ def column_type(self):
+ r"""
+ Return the type of ``self``. That is an instance of ``Enum``
+ :class:`KnotInfoColumnTypes`.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns, KnotInfoColumnTypes
+ sage: ki_db = KnotInfoDataBase()
+ sage: KIcols = KnotInfoColumns('Columns', ki_db.read_column_dict())
+ sage: KIcols.homfly_polynomial.column_type()
+
+ sage: KIcols.homflypt_polynomial.column_type()
+
+ sage: KIcols.name.column_type()
+
+ """
+ return self.value[1]
+
+ def description_webpage(self, new=0, autoraise=True):
+ r"""
+ Launch the description page of ``self`` in the standard web browser.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase, KnotInfoColumns
+ sage: ki_db = KnotInfoDataBase()
+ sage: KIcols = KnotInfoColumns('Columns', ki_db.read_column_dict())
+ sage: KIcols.pd_notation.description_webpage() # not tested
+ True
+ sage: KIcols.homflypt_polynomial.description_webpage() # not tested
+ True
+ """
+ import webbrowser
+ if self.column_type() == self.types.OnlyLinks:
+ url = KnotInfoFilename.links.description_url(self)
+ else:
+ url = KnotInfoFilename.knots.description_url(self)
+ return webbrowser.open(url, new=new, autoraise=autoraise)
+
+
+
+
+class KnotInfoFilename(Enum):
+ r"""
+ Enum for the different data files. The following choices are possible:
+
+ - ``knots`` -- contains the the data from KnotInfo
+ - ``links`` -- contains the the data for proper links from LinkInfo
+
+ Examples::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.filename
+
+ """
+
+ def url(self):
+ r"""
+ Return the URL to download the data from the web-page.
+
+ Examples::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.filename.knots.url()
+ 'https://knotinfo.math.indiana.edu/'
+ """
+ if self == KnotInfoFilename.knots:
+ return self.value[0]
+ else:
+ return self.value[0]
+
+ def excel(self):
+ r"""
+ Return the Excel-file name to download the data from the web-page.
+
+ Examples::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.filename.knots.excel()
+ 'knotinfo_data_complete.xls'
+ """
+ if self == KnotInfoFilename.knots:
+ return '%s.xls' %(self.value[1])
+ else:
+ return '%s.xlsx' %(self.value[1])
+
+ def csv(self):
+ r"""
+ Return the file name under which the data from the web-page
+ are stored as csv file.
+
+ Examples::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.filename.knots.csv()
+ 'knotinfo_data_complete.csv'
+ """
+ return '%s.csv' %(self.value[1])
+
+ def sobj_num_knots(self):
+ r"""
+ Return the file name under which the number of knots
+ is stored as in python int in a sobj-file.
+
+ Examples::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.filename.knots.sobj_num_knots()
+ 'num_knots.sobj'
+ """
+ return 'num_knots.sobj'
+
+ def sobj_row(self):
+ r"""
+ Return the file name under which the row-data of the csv-File
+ is stored as python dictionary in a sobj-file.
+
+ Examples::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.filename.knots.sobj_row()
+ 'row_dict.sobj'
+ """
+ return 'row_dict.sobj'
+
+ def sobj_column(self):
+ r"""
+ Return the file name under which the column-data of the csv-File
+ is stored as python dictionary in a sobj-file.
+
+ Examples::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.filename.knots.sobj_column()
+ 'column_dict.sobj'
+ """
+ return 'column_dict.sobj'
+
+
+ def sobj_data(self, column):
+ r"""
+ Return the file name under which the data of the given
+ column is stored as python list in a sobj-file.
+
+ Examples::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.filename.knots.sobj_data(ki_db.columns().braid_notation)
+ 'knotinfo_braid_notation'
+ """
+ if column.column_type() == column.types.OnlyLinks:
+ return 'linkinfo_%s' %(column.name)
+ else:
+ return 'knotinfo_%s' %(column.name)
+
+ def description_url(self, column):
+ r"""
+ Return the url of the description page of the given column.
+
+ Examples::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.filename.knots.description_url(ki_db.columns().braid_notation)
+ 'https://knotinfo.math.indiana.edu/descriptions/braid_notation.html'
+ """
+ return '%sdescriptions/%s.html' %(self.url(), column.name)
+
+ def diagram_url(self, fname, single=False):
+ r"""
+ Return the url of the diagram page of the given link.
+
+ Examples::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.filename.knots.diagram_url('3_1-50.png')
+ 'https://knotinfo.math.indiana.edu/diagram_display.php?3_1-50.png'
+ sage: ki_db.filename.knots.diagram_url('3_1', single=True)
+ 'https://knotinfo.math.indiana.edu/diagrams/3_1'
+ """
+ if single:
+ return '%sdiagrams/%s' %(self.url(), fname)
+ else:
+ return '%sdiagram_display.php?%s' %(self.url(), fname)
+
+
+ knots = ['https://knotinfo.math.indiana.edu/', 'knotinfo_data_complete']
+ links = ['https://linkinfo.sitehost.iu.edu/', 'linkinfo_data_complete']
+
+
+
+
+#----------------------------------------------------------------------------------------------------------------------------
+# Class to provide data for knots and links from the KnotInfo web-page
+#----------------------------------------------------------------------------------------------------------------------------
+class KnotInfoDataBase(SageObject):
+ r"""
+ Database interface to KnotInfo
+
+ The original data are obtained from KnotInfo web-page (URL see the example below). In order
+ to have these data installed during the build process as a sage-package they are converted
+ as csv files into a tarball. This tarball has been created using the method :meth:`create_spkg_tarball`.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.filename.knots
+
+ """
+
+ filename = KnotInfoFilename
+
+ def __init__(self):
+ r"""
+ Python constructor.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: from sage.env import SAGE_SHARE
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.filename.links
+
+ """
+ self._package = 'database_knotinfo'
+ version_file = os.path.join(SAGE_ROOT, 'build/pkgs/%s/package-version.txt' %self._package)
+ f = open(version_file)
+ self._version = f.read().splitlines()[0]
+ f.close()
+
+ # some constants
+ self._delimiter = '|'
+ self._names_column = 'name'
+ self._components_column = 'components'
+ self._knot_prefix = 'K'
+ self._import_path = os.path.join(SAGE_SHARE, 'knotinfo')
+
+ self._knot_list = None
+ self._link_list = None
+ self._available = None
+ self._num_knots = None
+
+
+ def is_available(self):
+ r"""
+ Return wether the KnotInfo databases are installed or not.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.is_available() # optional - database_knotinfo
+ True
+ """
+ if not self._available:
+ try:
+ lib_path = self._import_path
+ filename = self.filename.knots.sobj_num_knots()
+ self._num_knots = load('%s/%s' %(lib_path, filename))
+ self._available = True
+ except FileNotFoundError:
+ self._available = False
+ self._num_knots = len([v for v in row_demo_sample.values() if v[1]==1])
+ return self._available
+
+
+ def create_spkg_tarball(self, path_for_src=None):
+ r"""
+ Create a tarball for the sage-package ``database_knotinfo`` in the ``upstream`` directory. This
+ utility should only be used by users who know what they do in case of a switch to a new
+ version of the data files (that is if the original files on KnotInfo web-page have changed).
+ In that case an invocation of ``sage -package update database_knotinfo `` and
+ ``sage -package fix-checksum database_knotinfo`` will be necessary.
+
+ INPUT:
+
+ -- ``path_for_src`` - string of the path under which the source are stored in a
+ subdirectory called ``src``. In that directory there should be the data files in
+ csv format (for example ``KnotInfoDataBase.filename.knots.csv()``)
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.create_spkg_tarball() # not tested (because of internet access)
+ """
+ if not path_for_src:
+ path_for_src = os.environ['PWD']
+
+ os.system('cd %s; tar -cvjSf %s/upstream/%s-%s.tar.bz2 src' %(path_for_src, SAGE_ROOT, self._package, self._version) )
+
+
+ def version(self):
+ r"""
+ Return the current version.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.version()
+ '20200713'
+ """
+ return self._version
+
+ def knot_list(self):
+ r"""
+ Return the KnotInfo table rows as Python list.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: len(ki_db.knot_list()) # not tested because its only used on installation
+ """
+ if self._knot_list:
+ return self._knot_list
+
+ print('Importing KnotInfo database from SPKG!')
+ os.system('pwd')
+ knot_csv = open('src/%s' %self.filename.knots.csv())
+ knot_dict = csv.DictReader(knot_csv, delimiter=self._delimiter)
+ self._knot_list = list(knot_dict)
+ knot_csv.close()
+ return self._knot_list
+
+
+ def link_list(self):
+ r"""
+ Return the LinkInfo table rows as Python list.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: len(ki_db.link_list()) # not tested because its only used on installation
+ """
+ if self._link_list:
+ return self._link_list
+
+ print('Importing LinkInfo database from SPKG!')
+ link_csv = open('src/%s' %self.filename.links.csv())
+ link_dict = csv.DictReader(link_csv, delimiter=self._delimiter)
+ self._link_list = list(link_dict)
+ link_csv.close()
+ return self._link_list
+
+ def create_col_dict_sobj(self):
+ r"""
+ Create ``sobj`` files containing the number of knots and a dictionary
+ for the columns of the table.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.create_col_dict_sobj() # not tested because its only used on installation
+ """
+ knot_list = self.knot_list()
+ knot_column_names = knot_list[0]
+ len_knots = len(knot_list)
+
+ link_list = self.link_list()
+ link_column_names = link_list[0]
+
+ from sage.misc.misc import sage_makedirs
+ sage_makedirs(self._import_path)
+
+ num_knots = len_knots - 1
+ save(num_knots, '%s/%s' %(self._import_path, self.filename.knots.sobj_num_knots()))
+
+ column_dict = {}
+
+ # ----------------------------------------------------------------
+ # Columns that exist for knots and links
+ # ----------------------------------------------------------------
+ for col in knot_column_names:
+
+ name = knot_column_names[col]
+ if not name and col not in ['knot_atlas_anon', 'knotilus_page_anon']:
+ # not of interest
+ continue
+
+ col_type = KnotInfoColumnTypes.OnlyKnots
+ if col in link_column_names:
+ col_type = KnotInfoColumnTypes.KnotsAndLinks
+ column_dict[col] = [name, col_type]
+
+ # ----------------------------------------------------------------
+ # Columns that exist for links only
+ # ----------------------------------------------------------------
+ for col in link_column_names:
+
+ name = link_column_names[col]
+ if not name and col not in ['knot_atlas_anon', 'knotilus_page_anon']:
+ # not of interest
+ continue
+
+ if col in knot_column_names:
+ # already used
+ continue
+
+ col_type = KnotInfoColumnTypes.OnlyLinks
+ column_dict[col] = [name, col_type]
+
+ save(column_dict, '%s/%s' %(self._import_path, self.filename.knots.sobj_column()))
+
+
+
+ def create_data_sobj(self):
+ r"""
+ Create ``sobj`` files containing the contents of the whole table.
+ To each column there is created one file containing a list of
+ strings giving the entries of the database table.
+
+ The length of these lists depends on the type of the corresponding
+ column. If a column is used in both tables (``KnotInfoColumnTypes.KnotsAndLinks``)
+ the list of proper links is appended to the list of knots.
+ In both other cases the lenght of the list corresponds to
+ the number of listed knots and proper links respectively.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.create_data_sobj() # not tested because its only used on installation
+ """
+ knot_list = self.knot_list()
+ link_list = self.link_list()
+ len_knots = len(knot_list)
+ len_links = len(link_list)
+
+ row_dict = {}
+
+ # ----------------------------------------------------------------
+ # Columns that exist for knots and links
+ # ----------------------------------------------------------------
+ for col in self.columns():
+ val_list = []
+
+ if col.column_type() != col.types.OnlyLinks:
+ for i in range(1 , len_knots):
+ if col.name == self._names_column:
+ row_dict[self._knot_prefix + knot_list[i][col.name]] = [i - 1 , 1]
+
+ val_list.append(knot_list[i][col.name])
+
+ if col.column_type() != col.types.OnlyKnots:
+ for i in range(1 , len_links):
+ if col.name == self._names_column:
+ link_name = link_list[i][col.name]
+ link_name = link_name.replace('{', '_')
+ link_name = link_name.replace(',', '_')
+ link_name = link_name.replace('}', '')
+
+ num_comp = int(link_list[i][self._components_column])
+ row_dict[link_name] = [i + len_knots - 2 , num_comp]
+
+ val_list.append(link_list[i][col.name])
+
+ if val_list:
+ save(val_list, '%s/%s' %(self._import_path, self.filename.knots.sobj_data(col)))
+
+ save(row_dict, '%s/%s' %(self._import_path, self.filename.knots.sobj_row()))
+
+
+ @cached_method
+ def columns(self):
+ r"""
+ Return the columns ot the databese table as instances of enum class
+ :class:`KnotInfoColumns`.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: cols = ki_db.columns()
+ sage: [col.column_name() for col in cols if col.name.startswith('pd')] # optional - database_knotinfo
+ ['PD Notation', 'PD Notation (vector)', 'PD Notation (KnotTheory)']
+ """
+ column_dict = self.read_column_dict()
+ return KnotInfoColumns('Columns', column_dict)
+
+
+ # -------------------------------------------------------------------------------------------------------------
+ # read the dictionary for the column names from sobj-file
+ # -------------------------------------------------------------------------------------------------------------
+ @cached_method
+ def read_column_dict(self):
+ r"""
+ Read the dictionary for the column names from the according sobj-file
+
+ OUTPUT:
+
+ A python dictionary containing the column names and types
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: len(ki_db.read_column_dict()) # optional - database_knotinfo
+ 122
+ """
+ if not self.is_available():
+ return column_demo_sample
+ lib_path = self._import_path
+ filename = self.filename.knots.sobj_column()
+ return load('%s/%s' %(lib_path, filename))
+
+ # -------------------------------------------------------------------------------------------------------------
+ # read the dictionary for the row names that is the knot and link names from sobj-file
+ # -------------------------------------------------------------------------------------------------------------
+ @cached_method
+ def read_row_dict(self):
+ r"""
+ Read the dictionary for the row names that is the knot and link names
+ from the according sobj-file
+
+ OUTPUT:
+
+ A python dictionary containing the names of the knots and links
+ together with their table index and the corresponding number of
+ components
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.read_row_dict()['K7_1']
+ [8, 1]
+ """
+ if not self.is_available():
+ return row_demo_sample
+ lib_path = self._import_path
+ filename = self.filename.knots.sobj_row()
+ return load('%s/%s' %(lib_path, filename))
+
+ # -------------------------------------------------------------------------------------------------------------
+ # return a dictionary to obtain the original name to a row_dict key
+ # -------------------------------------------------------------------------------------------------------------
+ @cached_method
+ def row_names(self):
+ r"""
+ Return a dictionary to obtain the original name to a row_dict key
+
+ OUTPUT:
+
+ A python dictionary containing the names of the knots and links
+ together with their original names from the database,
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.row_names()['K7_1'] # optional - database_knotinfo
+ '7_1'
+ """
+ row_dict = self.read_row_dict()
+ names = self.read(self.columns().name)
+ return {k:names[v[0]] for k, v in row_dict.items()}
+
+
+ # -------------------------------------------------------------------------------------------------------------
+ # read the number of knots contained in the database (without proper links) from the according sobj-file.
+ # -------------------------------------------------------------------------------------------------------------
+ def read_num_knots(self):
+ r"""
+ Read the number of knots contained in the database (without
+ proper links) from the according sobj-file.
+
+ OUTPUT:
+
+ Integer
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ sage: ki_db.read_num_knots() # optional - database_knotinfo
+ 2978
+ """
+ if not self._num_knots:
+ self.is_available()
+ return self._num_knots
+
+
+ # -------------------------------------------------------------------------------------------------------------
+ # read an sobj-file obtained from KnotInfo database
+ # -------------------------------------------------------------------------------------------------------------
+ @cached_method
+ def read(self, column):
+ r"""
+ Access a column of KnotInfo / LinkInfo
+
+ INPUT:
+
+ ``column`` -- instance of enum :class:`KnotInfoColumns`
+ to select the data to be read in
+
+ OUTPUT:
+
+ A python list containing the data corresponding to the column.
+
+ EXAMPLES::
+
+ sage: from sage.databases.knotinfo_db import KnotInfoDataBase
+ sage: ki_db = KnotInfoDataBase()
+ """
+ if not isinstance(column, KnotInfoColumns):
+ raise TypeError('column must be an instance of enum %s' (KnotInfoColumns))
+
+ if not self.is_available():
+ return data_demo_sample[column]
+
+ lib_path = self._import_path
+ if column.column_type() == column.types.OnlyLinks:
+ filename = self.filename.links.sobj_data(column)
+ else:
+ filename = self.filename.knots.sobj_data(column)
+
+ verbose('loading data library %s ...' %(filename))
+ res = load('%s/%s' %(lib_path, filename))
+ verbose('... finished!')
+
+ return res
+
+
+
+column_demo_sample = {
+ 'name': ['Name', KnotInfoColumnTypes.KnotsAndLinks],
+ 'name_unoriented': ['Name - Unoriented', KnotInfoColumnTypes.OnlyLinks],
+ 'dt_notation': ['DT Notation', KnotInfoColumnTypes.OnlyKnots],
+ 'gauss_notation': ['Gauss Notation', KnotInfoColumnTypes.KnotsAndLinks],
+ 'pd_notation': ['PD Notation', KnotInfoColumnTypes.OnlyKnots],
+ 'pd_notation_vector': ['PD Notation (vector)', KnotInfoColumnTypes.OnlyLinks],
+ 'crossing_number': ['Crossing Number', KnotInfoColumnTypes.KnotsAndLinks],
+ 'braid_index': ['Braid Index', KnotInfoColumnTypes.OnlyKnots],
+ 'braid_length': ['Braid Length', KnotInfoColumnTypes.OnlyKnots],
+ 'braid_notation': ['Braid Notation', KnotInfoColumnTypes.KnotsAndLinks],
+ 'alternating': ['Alternating', KnotInfoColumnTypes.KnotsAndLinks],
+ 'alexander_polynomial': ['Alexander', KnotInfoColumnTypes.OnlyKnots],
+ 'jones_polynomial': ['Jones', KnotInfoColumnTypes.KnotsAndLinks],
+ 'conway_polynomial': ['Conway', KnotInfoColumnTypes.KnotsAndLinks],
+ 'homfly_polynomial': ['HOMFLY', KnotInfoColumnTypes.OnlyKnots],
+ 'homflypt_polynomial': ['HOMFLYPT Polynomial', KnotInfoColumnTypes.OnlyLinks],
+ 'kauffman_polynomial': ['Kauffman', KnotInfoColumnTypes.KnotsAndLinks],
+ 'determinant': ['Determinant', KnotInfoColumnTypes.KnotsAndLinks],
+ 'positive': ['Positive', KnotInfoColumnTypes.OnlyKnots],
+ 'fibered': ['Fibered', KnotInfoColumnTypes.OnlyKnots],
+ 'unoriented': ['Unoriented', KnotInfoColumnTypes.OnlyLinks],
+ 'symmetry_type': ['Symmetry Type', KnotInfoColumnTypes.OnlyKnots],
+ 'width': ['Width', KnotInfoColumnTypes.OnlyKnots],
+ 'arc_notation': ['Arc Notation', KnotInfoColumnTypes.OnlyLinks],
+ 'dt_code': ['DT code', KnotInfoColumnTypes.OnlyLinks]
+}
+
+
+row_demo_sample = {
+ 'K0_1': [0, 1],
+ 'K3_1': [1, 1],
+ 'K4_1': [2, 1],
+ 'K5_1': [3, 1],
+ 'K5_2': [4, 1],
+ 'K6_1': [5, 1],
+ 'K6_2': [6, 1],
+ 'K6_3': [7, 1],
+ 'K7_1': [8, 1],
+ 'K7_2': [9, 1],
+ 'L2a1_0': [10, 2],
+ 'L2a1_1': [11, 2],
+ 'L4a1_0': [12, 2],
+ 'L4a1_1': [13, 2],
+ 'L5a1_0': [14, 2],
+ 'L5a1_1': [15, 2],
+ 'L6a1_0': [16, 2],
+ 'L6a1_1': [17, 2],
+ 'L6a2_0': [18, 2],
+ 'L6a2_1': [19, 2]
+}
+
+db = KnotInfoDataBase()
+dc = db.columns()
+
+data_demo_sample = {
+ dc.name: ['0_1', '3_1', '4_1', '5_1', '5_2', '6_1', '6_2', '6_3', '7_1', '7_2',
+ 'L2a1{0}', 'L2a1{1}', 'L4a1{0}', 'L4a1{1}', 'L5a1{0}', 'L5a1{1}',
+ 'L6a1{0}', 'L6a1{1}', 'L6a2{0}', 'L6a2{1}', 'L6a3{0}'
+ ],
+ dc.name_unoriented: ['L2a1', 'L2a1', 'L4a1', 'L4a1', 'L5a1', 'L5a1', 'L6a1', 'L6a1', 'L6a2', 'L6a2', 'L6a3'],
+ dc.crossing_number: ['0', '3', '4', '5', '5', '6', '6', '6', '7', '7', '2', '2', '4', '4', '5', '5', '6', '6', '6', '6', '6'],
+ dc.braid_notation: [
+ '',
+ '{1,1,1}',
+ '{1,-2,1,-2}',
+ '{1,1,1,1,1}',
+ '{1,1,1,2,-1,2}',
+ '{1,1,2,-1,-3,2,-3}',
+ '{1,1,1,-2,1,-2}',
+ '{1,1,-2,1,-2,-2}',
+ '{1,1,1,1,1,1,1}',
+ '{1,1,1,2,-1,2,3,-2,3}',
+ '{2, {-1, -1}}',
+ '{2, {1, 1}}',
+ '{4, {1, -2, 3, -2, -1, -2, -3, -2}}',
+ '{2, {1, 1, 1, 1}}',
+ '{3, {-1, 2, -1, 2, -1}}',
+ '{3, {-1, 2, -1, 2, -1}}',
+ '{4, {1, -2, 3, -2, 1, -2, -3, -2}}',
+ '{4, {1, 2, 3, 2, 2, -1, 2, 2, -3, 2}}',
+ '{4, {1, -2, -2, -2, 3, -2, -1, -2, -3, -2}}',
+ '{4, {1, 2, -3, 2, -1, 2, 3, 2, 2, 2}}',
+ '{2, {-1, -1, -1, -1, -1, -1}}'
+ ],
+ dc.braid_index: ['1', '2', '3', '2', '3', '4', '3', '3', '2', '4'],
+ dc.braid_length: ['', '3', '4', '5', '6', '7', '6', '6', '7', '9'],
+ dc.determinant: ['0', '3', '5', '5', '7', '9', '11', '13', '7', '11', '2', '2', '4', '4', '8', '8', '12', '12', '10', '10', '6'],
+ dc.positive: ['', 'Y', 'N', 'Y', 'Y', 'N', 'N', 'N', 'Y', 'Y'],
+ dc.fibered: ['', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N'],
+ dc.unoriented: ['Y', 'N', 'Y', 'N', 'Y', 'N', 'Y', 'N', 'Y', 'N', 'Y'],
+ dc.pd_notation: [
+ '',
+ '[[1,5,2,4],[3,1,4,6],[5,3,6,2]]',
+ '[[4,2,5,1],[8,6,1,5],[6,3,7,4],[2,7,3,8]]',
+ '[[2,8,3,7],[4,10,5,9],[6,2,7,1],[8,4,9,3],[10,6,1,5]]',
+ '[[1,5,2,4],[3,9,4,8],[5,1,6,10],[7,3,8,2],[9,7,10,6]]',
+ '[[1,7,2,6],[3,10,4,11],[5,3,6,2],[7,1,8,12],[9,4,10,5],[11,9,12,8]]',
+ '[[1,8,2,9],[3,11,4,10],[5,1,6,12],[7,2,8,3],[9,7,10,6],[11,5,12,4]]',
+ '[[4,2,5,1],[8,4,9,3],[12,9,1,10],[10,5,11,6],[6,11,7,12],[2,8,3,7]]',
+ '[[1,9,2,8],[3,11,4,10],[5,13,6,12],[7,1,8,14],[9,3,10,2],[11,5,12,4],[13,7,14,6]]',
+ '[[2,10,3,9],[4,14,5,13],[6,12,7,11],[8,2,9,1],[10,8,11,7],[12,6,13,5],[14,4,1,3]]',
+ ],
+ dc.pd_notation_vector: [
+ '{{4, 1, 3, 2}, {2, 3, 1, 4}}',
+ '{{4, 2, 3, 1}, {2, 4, 1, 3}}',
+ '{{6, 1, 7, 2}, {8, 3, 5, 4}, {2, 5, 3, 6}, {4, 7, 1, 8}}',
+ '{{6, 2, 7, 1}, {8, 4, 5, 3}, {2, 8, 3, 7}, {4, 6, 1, 5}}',
+ '{{6, 1, 7, 2}, {10, 7, 5, 8}, {4, 5, 1, 6}, {2, 10, 3, 9}, {8, 4, 9, 3}}',
+ '{{8, 2, 9, 1}, {10, 7, 5, 8}, {4, 10, 1, 9}, {2, 5, 3, 6}, {6, 3, 7, 4}}',
+ '{{6, 1, 7, 2}, {10, 3, 11, 4}, {12, 8, 5, 7}, {8, 12, 9, 11}, {2, 5, 3, 6}, {4, 9, 1, 10}}',
+ '{{10, 2, 11, 1}, {6, 4, 7, 3}, {12, 10, 5, 9}, {8, 6, 9, 5}, {2, 12, 3, 11}, {4, 8, 1, 7}}',
+ '{{8, 1, 9, 2}, {12, 5, 7, 6}, {10, 3, 11, 4}, {4, 11, 5, 12}, {2, 7, 3, 8}, {6, 9, 1, 10}}',
+ '{{10, 2, 11, 1}, {12, 6, 7, 5}, {8, 4, 9, 3}, {4, 8, 5, 7}, {2, 12, 3, 11}, {6, 10, 1, 9}}',
+ '{{8, 1, 9, 2}, {2, 9, 3, 10}, {10, 3, 11, 4}, {12, 5, 7, 6}, {6, 7, 1, 8}, {4, 11, 5, 12}}'
+ ],
+ dc.dt_notation: [
+ '',
+ '[4, 6, 2]',
+ '[4, 6, 8, 2]',
+ '[6, 8, 10, 2, 4]',
+ '[4, 8, 10, 2, 6]',
+ '[4, 8, 12, 10, 2, 6]',
+ '[4, 8, 10, 12, 2, 6]',
+ '[4, 8, 10, 2, 12, 6]',
+ '[8, 10, 12, 14, 2, 4, 6]',
+ '[4, 10, 14, 12, 2, 8, 6]'
+ ],
+ dc.dt_code: [
+ '[{4}, {2}]',
+ '[{4}, {2}]',
+ '[{6, 8}, {2, 4}]',
+ '[{6, 8}, {4, 2}]',
+ '[{6, 8}, {4, 10, 2}]',
+ '[{8, 6}, {2, 10, 4}]',
+ '[{6, 10}, {2, 12, 4, 8}]',
+ '[{10, 6}, {8, 4, 12, 2}]',
+ '[{8, 10, 12}, {2, 6, 4}]',
+ '[{10, 8, 12}, {4, 6, 2}]',
+ '[{8, 10, 12}, {6, 2, 4}]'
+ ],
+ dc.gauss_notation: [
+ '',
+ '{1, -2, 3, -1, 2, -3}',
+ '{-1, 2, -3, 1, -4, 3, -2, 4}',
+ '{-1, 2, -3, 4, -5, 1, -2, 3, -4, 5}',
+ '{1, -2, 3, -1, 4, -5, 2, -3, 5, -4}',
+ '{1, -2, 3, -4, 2, -1, 5, -6, 4, -3, 6, -5}',
+ '{1, -2, 3, -4, 5, -6, 2, -1, 6, -3, 4, -5}',
+ '{-1, 2, -3, 1, -4, 5, -2, 3, -6, 4, -5, 6}',
+ '{1, -2, 3, -4, 5, -6, 7, -1, 2, -3, 4, -5, 6, -7}',
+ '{-1, 2, -3, 4, -5, 6, -7, 1, -2, 7, -6, 5, -4, 3}',
+ '{{1, -2}, {2, -1}}',
+ '{{1, -2}, {2, -1}}',
+ '{{1, -3, 2, -4}, {3, -1, 4, -2}}',
+ '{{1, -3, 2, -4}, {4, -1, 3, -2}}',
+ '{{1, -4, 5, -3}, {3, -1, 2, -5, 4, -2}}',
+ '{{1, -4, 5, -3}, {4, -5, 2, -1, 3, -2}}',
+ '{{1, -5, 2, -6}, {5, -1, 3, -4, 6, -2, 4, -3}}',
+ '{{1, -5, 2, -6}, {4, -2, 6, -4, 3, -1, 5, -3}}',
+ '{{1, -5, 3, -4, 2, -6}, {5, -1, 6, -3, 4, -2}}',
+ '{{1, -5, 3, -4, 2, -6}, {4, -3, 6, -1, 5, -2}}',
+ '{{1, -2, 3, -6, 4, -5}, {5, -1, 2, -3, 6, -4}}'
+ ],
+ dc.arc_notation: [
+ '{{4, 2}, {3, 1}, {4, 2}, {1, 3}}',
+ '{{2, 4}, {3, 1}, {2, 4}, {3, 1}}',
+ '{{6, 4}, {3, 5}, {4, 2}, {1, 3}, {2, 6}, {5, 1}}',
+ '{{3, 6}, {2, 5}, {6, 4}, {1, 3}, {5, 2}, {4, 1}}',
+ '{{6, 2}, {1, 4}, {3, 5}, {4, 7}, {2, 6}, {7, 3}, {5, 1}}',
+ '{{3, 5}, {6, 4}, {5, 2}, {7, 3}, {1, 6}, {2, 7}, {4, 1}}',
+ '{{8, 4}, {3, 5}, {4, 2}, {6, 3}, {5, 7}, {1, 6}, {2, 8}, {7, 1}}',
+ '{{2, 8}, {1, 7}, {8, 4}, {5, 3}, {4, 2}, {3, 6}, {7, 5}, {6, 1}}',
+ '{{8, 3}, {2, 7}, {3, 1}, {4, 8}, {5, 2}, {6, 4}, {7, 5}, {1, 6}}',
+ '{{3, 8}, {2, 7}, {8, 4}, {1, 3}, {5, 2}, {4, 6}, {7, 5}, {6, 1}}',
+ '{{8, 2}, {1, 3}, {2, 4}, {3, 5}, {4, 6}, {5, 7}, {6, 8}, {7, 1}}'
+ ],
+ dc.alternating: ['Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y'],
+ dc.symmetry_type: [
+ '',
+ 'reversible',
+ 'fully amphicheiral',
+ 'reversible',
+ 'reversible',
+ 'reversible',
+ 'reversible',
+ 'fully amphicheiral',
+ 'reversible',
+ 'reversible'
+ ],
+ dc.homfly_polynomial: [
+ '',
+ '(2*v^2-v^4)+ (v^2)*z^2',
+ '(v^(-2)-1+ v^2)+ (-1)*z^2',
+ '(3*v^4-2*v^6)+ (4*v^4-v^6)*z^2+ (v^4)*z^4',
+ '(v^2+ v^4-v^6)+ (v^2+ v^4)*z^2',
+ '(v^(-2)-v^2+ v^4)+ (-1-v^2)*z^2',
+ '(2-2*v^2+ v^4)+ (1-3*v^2+ v^4)*z^2+ (-v^2)*z^4',
+ '(-v^(-2)+ 3-v^2)+ (-v^(-2)+ 3-v^2)*z^2+ (1)*z^4',
+ '(4*v^6-3*v^8)+ (10*v^6-4*v^8)*z^2+ (6*v^6-v^8)*z^4+ (v^6)*z^6',
+ '(v^2+ v^6-v^8)+ (v^2+ v^4+ v^6)*z^2'
+ ],
+ dc.homflypt_polynomial: [
+ '1/(v^3*z)-1/(v*z)-z/v',
+ 'v/z-v^3/z + v*z',
+ '1/(v^5*z)-1/(v^3*z)-z/v^3-z/v',
+ 'v^3/z-v^5/z + 3*v^3*z-v^5*z + v^3*z^3',
+ '1/(v*z)-v/z-z/v^3 + (2*z)/v-v*z + z^3/v',
+ '1/(v*z)-v/z-z/v^3 + (2*z)/v-v*z + z^3/v',
+ '1/(v^5*z)-1/(v^3*z)-(2*z)/v^3 + z/v-v*z + z^3/v',
+ 'v^3/z-v^5/z + 2*v^3*z + v^5*z-v^7*z + v^3*z^3 + v^5*z^3',
+ '1/(v^7*z)-1/(v^5*z) + z/v^7-(2*z)/v^5-(2*z)/v^3-z^3/v^5-z^3/v^3',
+ 'v^5/z-v^7/z + 2*v^3*z + 2*v^5*z-v^7*z + v^3*z^3 + v^5*z^3',
+ '1/(v^7*z)-1/(v^5*z) + (3*z)/v^7-(6*z)/v^5 + z^3/v^7-(5*z^3)/v^5-z^5/v^5'
+ ],
+ dc.kauffman_polynomial: [
+ '',
+ '(-a^(-4)-2*a^(-2))*z^(0)+ (a^(-5)+ a^(-3))*z^(1)+ (a^(-4)+ a^(-2))*z^(2)',
+ '(-a^(-2)-1-a^2)*z^(0)+ (-a^(-1)-a)*z^(1)+ (a^(-2)+ 2+ a^2)*z^(2)+ (a^(-1)+ a)*z^(3)',
+ '(2*a^(-6)+ 3*a^(-4))*z^(0)+ (a^(-9)-a^(-7)-2*a^(-5))*z^(1)+ (a^(-8)-3*a^(-6)-4*a^(-4))*z^(2)+ (a^(-7)+ a^(-5))*z^(3)+ (a^(-6)+ a^(-4))*z^(4)',
+ '(a^(-6)+ a^(-4)-a^(-2))*z^(0)+ (-2*a^(-7)-2*a^(-5))*z^(1)+ (-2*a^(-6)-a^(-4)+ a^(-2))*z^(2)+ (a^(-7)+ 2*a^(-5)+ a^(-3))*z^(3)+ (a^(-6)+ a^(-4))*z^(4)',
+ '(a^(-4)+ a^(-2)-a^2)*z^(0)+ (2*a^(-3)+ 2*a^(-1))*z^(1)+ (-3*a^(-4)-4*a^(-2)+ a^2)*z^(2)+ (-3*a^(-3)-2*a^(-1)+ a)*z^(3)+ (a^(-4)+ 2*a^(-2)+ 1)*z^(4)+ (a^(-3)+ a^(-1))*z^(5)',
+ '(a^(-4)+ 2*a^(-2)+ 2)*z^(0)+ (-a^(-5)-a^(-3))*z^(1)+ (a^(-6)-2*a^(-4)-6*a^(-2)-3)*z^(2)+ (2*a^(-5)-2*a^(-1))*z^(3)+ (2*a^(-4)+ 3*a^(-2)+ 1)*z^(4)+ (a^(-3)+ a^(-1))*z^(5)',
+ '(a^(-2)+ 3+ a^2)*z^(0)+ (-a^(-3)-2*a^(-1)-2*a-a^3)*z^(1)+ (-3*a^(-2)-6-3*a^2)*z^(2)+ (a^(-3)+ a^(-1)+ a+ a^3)*z^(3)+ (2*a^(-2)+ 4+ 2*a^2)*z^(4)+ (a^(-1)+ a)*z^(5)',
+ '(-3*a^(-8)-4*a^(-6))*z^(0)+ (a^(-13)-a^(-11)+ a^(-9)+ 3*a^(-7))*z^(1)+ (a^(-12)-2*a^(-10)+ 7*a^(-8)+ 10*a^(-6))*z^(2)+ (a^(-11)-3*a^(-9)-4*a^(-7))*z^(3)+ (a^(-10)-5*a^(-8)-6*a^(-6))*z^(4)+ (a^(-9)+ a^(-7))*z^(5)+ (a^(-8)+ a^(-6))*z^(6)',
+ '(-a^(-8)-a^(-6)-a^(-2))*z^(0)+ (3*a^(-9)+ 3*a^(-7))*z^(1)+ (4*a^(-8)+ 3*a^(-6)+ a^(-2))*z^(2)+ (-4*a^(-9)-6*a^(-7)-a^(-5)+ a^(-3))*z^(3)+ (-4*a^(-8)-3*a^(-6)+ a^(-4))*z^(4)+ (a^(-9)+ 2*a^(-7)+ a^(-5))*z^(5)+ (a^(-8)+ a^(-6))*z^(6)',
+ 'a^2-a/z-a^3/z + a*z + a^3*z',
+ 'a^(-2)-1/(a^3*z)-1/(a*z) + z/a^3 + z/a',
+ '-a^4 + a^3/z + a^5/z + a*z-2*a^3*z-3*a^5*z + a^2*z^2 + a^4*z^2 + a^3*z^3 + a^5*z^3',
+ '-a^(-4) + 1/(a^5*z) + 1/(a^3*z) + z/a^7-(2*z)/a^5-(3*z)/a^3 + z^2/a^6 + z^2/a^4 + z^3/a^5 + z^3/a^3',
+ '-1 + 1/(a*z) + a/z-(2*z)/a-4*a*z-2*a^3*z-z^2 + a^4*z^2 + z^3/a + 3*a*z^3 + 2*a^3*z^3 + z^4 + a^2*z^4',
+ '-1 + 1/(a*z) + a/z-(2*z)/a-4*a*z-2*a^3*z-z^2 + a^4*z^2 + z^3/a + 3*a*z^3 + 2*a^3*z^3 + z^4 + a^2*z^4',
+ '-a^4 + a^3/z + a^5/z-z/a-a^3*z-2*a^5*z-3*z^2-3*a^2*z^2 + z^3/a + a^5*z^3 + 2*z^4 + 3*a^2*z^4 + a^4*z^4 + a*z^5 + a^3*z^5',
+ '-a^(-4) + 1/(a^5*z) + 1/(a^3*z)-z/a^9-z/a^5-(2*z)/a^3-(3*z^2)/a^8-(3*z^2)/a^6 + z^3/a^9 + z^3/a^3 + (2*z^4)/a^8 + (3*z^4)/a^6 + z^4/a^4 + z^5/a^7 + z^5/a^5',
+ 'a^6-a^5/z-a^7/z-2*a^3*z + 3*a^5*z + 3*a^7*z-2*a^9*z-a^4*z^2-2*a^6*z^2-a^8*z^2 + a^3*z^3-2*a^5*z^3-2*a^7*z^3 + a^9*z^3 + a^4*z^4 + 2*a^6*z^4 + a^8*z^4 + a^5*z^5 + a^7*z^5',
+ 'a^(-6)-1/(a^7*z)-1/(a^5*z)-(2*z)/a^9 + (3*z)/a^7 + (3*z)/a^5-(2*z)/a^3-z^2/a^8-(2*z^2)/a^6-z^2/a^4 + z^3/a^9-(2*z^3)/a^7-(2*z^3)/a^5 + z^3/a^3 + z^4/a^8 + (2*z^4)/a^6 + z^4/a^4 + z^5/a^7 + z^5/a^5',
+ 'a^6-a^5/z-a^7/z + 6*a^5*z + 4*a^7*z-a^9*z + a^11*z-3*a^6*z^2-2*a^8*z^2 + a^10*z^2-5*a^5*z^3-4*a^7*z^3 + a^9*z^3 + a^6*z^4 + a^8*z^4 + a^5*z^5 + a^7*z^5'
+ ],
+ dc.jones_polynomial: [
+ '1',
+ 't+ t^3-t^4',
+ 't^(-2)-t^(-1)+ 1-t+ t^2',
+ 't^2+ t^4-t^5+ t^6-t^7',
+ 't-t^2+ 2*t^3-t^4+ t^5-t^6',
+ 't^(-2)-t^(-1)+ 2-2*t+ t^2-t^3+ t^4',
+ 't^(-1)-1+ 2*t-2*t^2+ 2*t^3-2*t^4+ t^5',
+ '-t^(-3)+ 2*t^(-2)-2*t^(-1)+ 3-2*t+ 2*t^2-t^3',
+ 't^3+ t^5-t^6+ t^7-t^8+ t^9-t^10',
+ 't-t^2+ 2*t^3-2*t^4+ 2*t^5-t^6+ t^7-t^8',
+ '-x^(-5)-x^(-1)',
+ '-x-x^5',
+ '-x^(-9)-x^(-5) + x^(-3)-x^(-1)',
+ '-x^3-x^7 + x^9-x^11',
+ 'x^(-7)-2/x^5 + x^(-3)-2/x + x-x^3',
+ 'x^(-7)-2/x^5 + x^(-3)-2/x + x-x^3',
+ '-x^(-9) + x^(-7)-3/x^5 + 2/x^3-2/x + 2*x-x^3',
+ '-x^3 + x^5-3*x^7 + 2*x^9-2*x^11 + 2*x^13-x^15',
+ '-x^(-15) + x^(-13)-2/x^11 + 2/x^9-2/x^7 + x^(-5)-x^(-3)',
+ '-x^3 + x^5-2*x^7 + 2*x^9-2*x^11 + x^13-x^15',
+ '-x^(-17) + x^(-15)-x^(-13) + x^(-11)-x^(-9)-x^(-5)'
+ ],
+ dc.alexander_polynomial: [
+ '1',
+ '1-t+ t^2',
+ '1-3*t+ t^2',
+ '1-t+ t^2-t^3+ t^4',
+ '2-3*t+ 2*t^2',
+ '2-5*t+ 2*t^2',
+ '1-3*t+ 3*t^2-3*t^3+ t^4',
+ '1-3*t+ 5*t^2-3*t^3+ t^4',
+ '1-t+ t^2-t^3+ t^4-t^5+ t^6',
+ '3-5*t+ 3*t^2']
+}
diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py
new file mode 100644
index 00000000000..b91498cfa41
--- /dev/null
+++ b/src/sage/knots/knotinfo.py
@@ -0,0 +1,2070 @@
+# -*- coding: utf-8 -*-
+r"""
+Access to the KnotInfo database
+
+This module contains the class :class:`KnotInfoBase` which is derived from :class:`Enum`
+and provides knots and links listed in the databases at the web-pages `KnotInfo `__
+and `LinkInfo `__ as its items.
+
+This interface contains a set of about twenty knots and links statically as demonstration cases. The complete
+database can be installed as an optional Sage package using
+
+- ``sage -i database_knotinfo`` (does not install if the current version is already present)
+- ``sage -f database_knotinfo`` (installs even if the current version is already present)
+
+To perform all the doctests concerning the usage of the database on the installation add the option ``-c``.
+In this case (for instance ``sage -f -c database_knotinfo``) the installation breaks on failing tests.
+
+The installation of the complete database will be necessary in order to have access to all the properties
+recorded in the databases, as well.
+
+Be aware that there are a couple of conventions used differently on KnotInfo as in Sage, especially
+concerning the selection of the symmetry version of the link. In our transitions to Sage objects
+these are translated (by default) in order to avoid confusion about exchanged mirror versions.
+
+Briefly, these differences are:
+
+ - ``pd_notation`` -- KnotInfo: counter clockwise Sage: clockwise, see note in
+ :meth:`KnotInfoBase.link`
+
+ - ``homfly_polynomial`` -- KnotInfo: ``v`` Sage: `1/a`, see note in :meth:`KnotInfoBase.homfly_polynomial`.
+
+ - ``braid_notation`` -- This is used accordingly: The crossing of the braid generators are positive
+ in both systems. Here it is listed because there could arise confusion from the source where they are
+ taken from. There, the braid generators are assumed to have a negative crossing
+ (see definition 3 of `Gittings, T., "Minimum Braids: A Complete Invariant of Knots and Links `__).
+
+
+EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: L = KnotInfo.L4a1_0
+ sage: L.pd_notation()
+ [[6, 1, 7, 2], [8, 3, 5, 4], [2, 5, 3, 6], [4, 7, 1, 8]]
+ sage: L.pd_notation(original=True)
+ '{{6, 1, 7, 2}, {8, 3, 5, 4}, {2, 5, 3, 6}, {4, 7, 1, 8}}'
+ sage: L.is_knot()
+ False
+ sage: L.num_components()
+ 2
+
+Items for knots need a leading ``K`` for technical reason::
+
+ sage: K = KnotInfo.K4_1
+ sage: K.is_knot()
+ True
+
+Injecting the variable name into the namespace::
+
+ sage: KnotInfo.K5_1.inject()
+ Defining K5_1
+ sage: K5_1.dt_notation()
+ [6, 8, 10, 2, 4]
+
+Defining a link from the original name string::
+
+ sage: KnotInfo('L6a1{1}').inject()
+ Defining L6a1_1
+ sage: L6a1_1.is_alternating()
+ True
+
+Obtaining an instance of :class:`~sage.groups.braid.Braid`::
+
+ sage: L.braid()
+ s0*s1^-1*s2*s1^-1*s0^-1*s1^-1*s2^-1*s1^-1
+ sage: type(_)
+
+
+Obtaining an instance of :class:`Link`::
+
+ sage: l = L.link(); l
+ Link with 2 components represented by 4 crossings
+ sage: type(l)
+
+
+If you have `SnapPy `__ installed inside Sage
+you can obtain an instance of :class:`~spherogram.links.links_base.Link`, too::
+
+ sage: L6 = KnotInfo.L6a2_0
+ sage: l6s = L6.link(snappy=True); l6s # optional - snappy
+ Plink failed to import tkinter.
+
+ sage: type(l6s) # optional - snappy
+
+ sage: l6 = L6.link()
+ sage: l6 == l6s.sage_link() # optional - snappy
+ True
+ sage: l6sn = L6.link(use_item=L6.items.name, snappy=True); l6sn # optional - snappy
+
+ sage: l6s == l6sn # optional - snappy
+ False
+ sage: l6sn.sage_link().is_isotopic(l6) # optional - snappy
+ True
+
+But observe that the name conversion to SnapPy does not distingiush orientation types::
+
+ sage: L6b = KnotInfo.L6a2_1
+ sage: l6bsn = L6b.link(use_item=L6b.items.name, snappy=True) # optional - snappy
+ sage: l6bsn.PD_code() == l6sn.PD_code() # optional - snappy
+ True
+
+Obtaining the HOMFLY-PT polynomial::
+
+ sage: L.homfly_polynomial()
+ -v^-1*z - v^-3*z - v^-3*z^-1 + v^-5*z^-1
+ sage: L.homfly_polynomial(sage_convention=True)
+ L^5*M^-1 - L^3*M - L^3*M^-1 - L*M
+ sage: _ == l.homfly_polynomial(normalization='az')
+ True
+
+
+Obtaining the original string from the database for an arbitrary property::
+
+ sage: K[K.items.classical_conway_name] # optional - database_knotinfo
+ '4_1'
+
+Further methods::
+
+ sage: K.crossing_number()
+ 4
+ sage: K.gauss_notation()
+ [-1, 2, -3, 1, -4, 3, -2, 4]
+ sage: K.dt_notation()
+ [4, 6, 8, 2]
+ sage: K.determinant()
+ 5
+ sage: K.symmetry_type()
+ 'fully amphicheiral'
+ sage: _ == K[K.items.symmetry_type]
+ True
+ sage: K.is_reversible()
+ True
+ sage: K.is_amphicheiral()
+ True
+ sage: K.jones_polynomial()
+ t^2 - t - 1/t + 1/t^2 + 1
+ sage: K.kauffman_polynomial()
+ a^2*z^2 + a*z^3 - a^2 - a*z + 2*z^2 + a^-1*z^3 - 1 - a^-1*z + a^-2*z^2 - a^-2
+ sage: K.alexander_polynomial()
+ t^2 - 3*t + 1
+
+Using the ``column_type`` of a property::
+
+ sage: [i.column_name() for i in K.items if i.column_type() != i.types.OnlyLinks and K[i] == 'Y'] # optional - database_knotinfo
+ ['Alternating', 'Fibered', 'Quasialternating', 'Adequate']
+
+You can launch web-pages attached to the links::
+
+ sage: K.diagram() # not tested
+ True
+ sage: L.diagram(single=True) # not tested
+ True
+ sage: L.knot_atlas_webpage() # not tested
+ True
+ sage: K.knotilus_webpage() # not tested
+ True
+
+and the description web-pages of the properties::
+
+ sage: K.items.positive.description_webpage() # not tested
+ True
+
+To see all the properties available in this interface you can use "tab-completion".
+For example type ``K.items.`` and than hit the "tab-key". You can select the item
+you want from the list. If you know some first letters type them first to obtain a
+reduced selection list.
+
+In a similar way you may select the knots and links. Here you have to type ``KnotInfo.``
+or ``KnotInfo.L7`` before stroking the "tab-key". In the latter case the selection list
+will be reduced to proper links with 7 crossings.
+
+Finally there is a method :meth:`Link.identify_knotinfo` of class :class:`Link` to find an instance
+in the KnotInfo database::
+
+ sage: L = Link([[3,1,2,4], [8,9,1,7], [5,6,7,3], [4,18,6,5],
+ ....: [17,19,8,18], [9,10,11,14], [10,12,13,11],
+ ....: [12,19,15,13], [20,16,14,15], [16,20,17,2]])
+ sage: L.identify_knotinfo()
+ (, False)
+
+
+REFERENCES:
+
+- `KnotInfo `__
+- `LinkInfo `__
+
+
+
+AUTHORS:
+
+- Sebastian Oehms August 2020: initial version
+"""
+
+
+##############################################################################
+# Copyright (C) 2020 Sebastian Oehms
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+# http://www.gnu.org/licenses/
+##############################################################################
+
+
+
+from enum import Enum
+from sage.misc.cachefunc import cached_method, cached_function
+from sage.misc.sage_eval import sage_eval
+from sage.structure.sage_object import SageObject
+from sage.structure.unique_representation import UniqueRepresentation
+from sage.rings.integer_ring import ZZ
+from sage.groups.braid import BraidGroup
+from sage.knots.knot import Knots
+from sage.databases.knotinfo_db import KnotInfoColumnTypes, KnotInfoColumns, db
+
+
+
+
+def is_knotinfo_available(raise_error=False):
+ r"""
+ Return whether the KnotInfo databases are installed or not.
+
+ INPUT:
+
+ - ``raise_error`` -- boolean (default ``False``) if set to ``True``
+ an import error is raised in case KnotInfo is not installed
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import is_knotinfo_available
+ sage: is_knotinfo_available() # optional - database_knotinfo
+ True
+ """
+ res = db.is_available()
+ if not res and raise_error:
+ raise ImportError('This functionality needs KnotInfo to be installed! Type `sage -i database_knotinfo` to have this done')
+ return res
+
+@cached_function
+def knotinfo_matching_list(number_of_crossings, num_components, homfly_polynomial=None):
+ r"""
+ Return a list of links from the KontInfo and LinkInfo tables with given properties.
+
+ INPUT:
+
+ - ``number_of_crossings`` -- Python ``int`` giving the (not necessarily minimal)
+ number of crossings to be matched
+ - ``num_components`` -- Python ``int`` giving the number of components
+ to be matched
+ - ``homfly_polynomial`` -- instance of :class:`~sage.rings.polynomial.laurent_polynomial_ring.LaurentPolynomial_mpair`
+ giving the HOMFLY-PT polynomial to be matched
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo, knotinfo_matching_list
+ sage: knotinfo_matching_list(3,1)
+ [, ]
+ sage: [l.name for l in knotinfo_matching_list(4,2)]
+ ['L2a1_0', 'L2a1_1', 'L4a1_0', 'L4a1_1']
+ sage: L = KnotInfo.L6a3_0 # optional - database_knotinfo
+ sage: h = L.homfly_polynomial(sage_convention=True) # optional - database_knotinfo
+ sage: l = knotinfo_matching_list(L.crossing_number(), L.num_components(), h) # optional - database_knotinfo
+ sage: len(l) == 1 and l[0] == L # optional - database_knotinfo
+ True
+ """
+ res = []
+ if homfly_polynomial:
+ l = knotinfo_matching_list(number_of_crossings, num_components)
+ for L in l:
+ if homfly_polynomial:
+ if L.homfly_polynomial(sage_convention=True) != homfly_polynomial:
+ continue
+ res.append(L)
+ return res
+
+ for L in KnotInfo:
+ if L.crossing_number() > number_of_crossings:
+ continue
+ if L.num_components() != num_components:
+ continue
+ res.append(L)
+
+ return res
+
+
+def eval_knotinfo(string, locals={}, to_tuple=True):
+ r"""
+ Preparse a string from the KnotInfo database and evaluate it by ``sage_eval``.
+
+ INPUT:
+
+ - ``string`` -- string that gives a value of some database entry
+ - ``locals`` -- dictionary of locals passed to ``sage_eval``
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo, eval_knotinfo
+ sage: L = KnotInfo.L4a1_0
+ sage: L.braid_notation(original=True)
+ '{4, {1, -2, 3, -2, -1, -2, -3, -2}}'
+ sage: eval_knotinfo(_)
+ (4, (1, -2, 3, -2, -1, -2, -3, -2))
+ """
+ if to_tuple:
+ new_string = string.replace('{', '(')
+ new_string = new_string.replace('}', ')')
+ else:
+ new_string = string.replace('{', '[')
+ new_string = new_string.replace('}', ']')
+ new_string = new_string.replace(';', ',')
+ return sage_eval(new_string, locals=locals)
+
+def knotinfo_bool(string):
+ r"""
+ Preparse a string from the KnotInfo database representing a boolean.
+
+ INPUT:
+
+ - ``string`` -- string that gives a value of some database entry
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import knotinfo_bool
+ sage: knotinfo_bool('Y')
+ True
+ """
+ if string == 'Y':
+ return True
+ elif string == 'N':
+ return False
+ raise ValueError('%s is not a KnotInfo boolean')
+
+
+
+# ---------------------------------------------------------------------------------
+# KnotInfoBase
+# ---------------------------------------------------------------------------------
+class KnotInfoBase(Enum):
+ r"""
+ Enum class to select the knots and links listed in the databases at the web-pages
+ `KnotInfo `__ and `LinkInfo `__.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: [knot.name for knot in KnotInfo if knot.crossing_number() < 5]
+ ['K0_1', 'K3_1', 'K4_1', 'L2a1_0', 'L2a1_1', 'L4a1_0', 'L4a1_1']
+
+ More examples and information can be seen in the module header :mod:`~sage.knots.knotinfo` (by typing)::
+
+ sage: import sage.knots.knotinfo # not tested
+ sage: sage.knots.knotinfo? # not tested
+
+ TESTS:
+
+ sage: KnotInfo.K7_1.inject()
+ Defining K7_1
+ sage: TestSuite(K7_1).run()
+ """
+ @property
+ def items(self):
+ r"""
+ Return an Enum class to select a column item of the KnotInfo database.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: L = KnotInfo.L4a1_0
+ sage: it = L.items
+ sage: [i.name for i in it if i.name.endswith('notation')] # optional - database_knotinfo
+ ['dt_notation',
+ 'conway_notation',
+ 'two_bridge_notation',
+ 'gauss_notation',
+ 'enhanced_gauss_notation',
+ 'pd_notation',
+ 'braid_notation',
+ 'positive_braid_notation',
+ 'positive_pd_notation',
+ 'strongly_quasipositive_braid_notation',
+ 'quasipositive_braid_notation',
+ 'arc_notation']
+ sage: L.items.dt_notation.column_name()
+ 'DT Notation'
+
+ To check if the item is available for proper links or only knots type::
+
+ sage: it.gauss_notation.column_type()
+
+ sage: it.dt_notation.column_type()
+
+
+ To see the description of the item in your web browser type::
+
+ sage: it.gauss_notation.description_webpage() # not tested
+ True
+ """
+ return db.columns()
+
+ @cached_method
+ def __getitem__(self, item):
+ r"""
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: L = KnotInfo.L4a1_0
+ sage: L[L.items.alternating]
+ 'Y'
+ sage: L[L.items.arc_notation]
+ '{{6, 4}, {3, 5}, {4, 2}, {1, 3}, {2, 6}, {5, 1}}'
+ sage: L[L.items.braid_notation]
+ '{4, {1, -2, 3, -2, -1, -2, -3, -2}}'
+ sage: L[0]
+ Traceback (most recent call last):
+ ...
+ KeyError: "Item must be an instance of "
+ """
+ if not isinstance(item, KnotInfoColumns):
+ raise KeyError('Item must be an instance of %s' %(KnotInfoColumns))
+ if item.column_type() == item.types.OnlyLinks and self.is_knot():
+ raise KeyError('Item not available for knots' %(KnotInfoColumns))
+ if item.column_type() == item.types.OnlyKnots and not self.is_knot():
+ raise KeyError('Item not available for links' %(KnotInfoColumns))
+
+ l = db.read(item)
+ ind = db.read_row_dict()[self.name][0]
+ offset = 0
+ if item.column_type() == item.types.OnlyLinks:
+ offset = self._offset_knots()
+
+ return l[ind-offset]
+
+ def _offset_knots(self):
+ r"""
+ Return the list index of the first proper link in a combined
+ list containing knots and proper links together which is the
+ case for columns used for KnotInfo and LinkInfo in common.
+ This index is exactly the total number of knots recorded
+ in KnotInfo.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: L = KnotInfo.L4a1_0
+ sage: L._offset_knots() # optional - database_knotinfo
+ 2978
+ """
+ return db.read_num_knots()
+
+ @cached_method
+ def _braid_group(self):
+ r"""
+ Return the braid group corresponding to the braid index
+ of ``self``.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: L = KnotInfo.L4a1_0
+ sage: L._braid_group()
+ Braid group on 4 strands
+ """
+ n = self.braid_index()
+ if n == 1:
+ return BraidGroup(2)
+ else:
+ return BraidGroup(n)
+
+
+ @cached_method
+ def _homfly_pol_ring(self, var1, var2):
+ r"""
+ Return the parent Laurent polynomial ring for the HOMFLY-PT
+ polynomial according to Sage's internal one.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: L = KnotInfo.L4a1_1
+ sage: L._homfly_pol_ring('u', 'v')
+ Multivariate Laurent Polynomial Ring in u, v over Integer Ring
+ """
+ K3_1 = Knots().from_table(3,1)
+ return K3_1.homfly_polynomial(var1=var1, var2=var2).parent()
+
+ @cached_method
+ def pd_notation(self, original=False):
+ r"""
+ Return the value of column ``pd_notation`` for this
+ link as a Python list of Python lists. For more information
+ type ``KnotInfo.K0_1.items.pd_notation.description_webpage()``.
+
+ INPUT:
+
+ - ``original`` -- boolean (optional, default ``False``) if set to
+ ``True`` the original table entry is returned as a string
+
+ OUTPUT:
+
+ Python list of python lists each entry of the outer list
+ representing a crossing.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: L = KnotInfo.L4a1_0
+ sage: L.pd_notation()
+ [[6, 1, 7, 2], [8, 3, 5, 4], [2, 5, 3, 6], [4, 7, 1, 8]]
+ sage: L.pd_notation(original=True)
+ '{{6, 1, 7, 2}, {8, 3, 5, 4}, {2, 5, 3, 6}, {4, 7, 1, 8}}'
+ sage: K = KnotInfo.K4_1
+ sage: K.pd_notation()
+ [[4, 2, 5, 1], [8, 6, 1, 5], [6, 3, 7, 4], [2, 7, 3, 8]]
+ """
+ if self.is_knot():
+ pd_notation = self[self.items.pd_notation]
+ else:
+ pd_notation = self[self.items.pd_notation_vector]
+
+ if original:
+ return pd_notation
+
+ if not pd_notation:
+ # don't forget the unknot
+ return []
+
+ return eval_knotinfo(pd_notation, to_tuple=False)
+
+ @cached_method
+ def dt_notation(self, original=False):
+ r"""
+ Return the value of column ``dt_notation`` for this
+ link as a Python list of Python lists. For more information
+ type ``KnotInfo.K0_1.items.dt_notation.description_webpage()``.
+
+ INPUT:
+
+ - ``original`` -- boolean (optional, default ``False``) if set to
+ ``True`` the original table entry is returned as a string
+
+ OUTPUT:
+
+ Python list of python lists each entry of the outer list
+ representing a crossing.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: L = KnotInfo.L4a1_0
+ sage: L.dt_notation()
+ [[6, 8], [2, 4]]
+ sage: L.dt_notation(original=True)
+ '[{6, 8}, {2, 4}]'
+ sage: L = KnotInfo.L4a1_0
+ sage: K = KnotInfo.K4_1
+ sage: K.dt_notation()
+ [4, 6, 8, 2]
+ """
+ if self.is_knot():
+ dt_notation = self[self.items.dt_notation]
+ else:
+ dt_notation = self[self.items.dt_code]
+
+ if original:
+ return dt_notation
+
+ if not dt_notation:
+ # don't forget the unknot
+ return []
+
+ return eval_knotinfo(dt_notation, to_tuple=False)
+
+ @cached_method
+ def gauss_notation(self, original=False):
+ r"""
+ Return the value of column ``gauss_notation`` for this
+ link as a Python list of Python lists. For more information
+ type ``KnotInfo.K0_1.items.gauss_notation.description_webpage()``.
+
+ INPUT:
+
+ - ``original`` -- boolean (optional, default ``False``) if set to
+ ``True`` the original table entry is returned as a string
+
+ OUTPUT:
+
+ Python list of
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: L = KnotInfo.L4a1_0
+ sage: L.gauss_notation()
+ [[1, -3, 2, -4], [3, -1, 4, -2]]
+ sage: L.gauss_notation(original=True)
+ '{{1, -3, 2, -4}, {3, -1, 4, -2}}'
+ """
+ gauss_notation = self[self.items.gauss_notation]
+ if original:
+ return gauss_notation
+
+ if not gauss_notation:
+ # don't forget the unknot
+ return []
+
+ return eval_knotinfo(gauss_notation, to_tuple=False)
+
+ @cached_method
+ def braid_notation(self, original=False):
+ r"""
+ Return the value of column ``braid_notation`` for this
+ link as a Python tuple (Tietze form). For more information
+ type ``KnotInfo.K0_1.items.braid_notation.description_webpage()``.
+
+ INPUT:
+
+ - ``original`` -- boolean (optional, default ``False``) if set to
+ ``True`` the original table entry is returned as a string
+
+ OUTPUT:
+
+ Python tuple representing the braid whose closure is ``self``
+ in Tietze form.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: L = KnotInfo.L4a1_0
+ sage: L.braid_notation()
+ (1, -2, 3, -2, -1, -2, -3, -2)
+ sage: L.braid_notation(original=True)
+ '{4, {1, -2, 3, -2, -1, -2, -3, -2}}'
+ """
+ braid_notation = self[self.items.braid_notation]
+ if original:
+ return braid_notation
+
+ if not braid_notation:
+ # don't forget the unknot
+ return (1, -1)
+
+ braid_notation = eval_knotinfo(braid_notation)
+ if type(braid_notation) is list:
+ # in some cases there are a pair of braid representations
+ # in the database. If this is the case we select the
+ # corresponding to the braid index.
+ if type(braid_notation[0]) is tuple:
+ i = self.braid_index()
+ for b in braid_notation:
+ if -i < min(b) and max(b) < i:
+ braid_notation = b
+ break
+
+ if not self.is_knot():
+ # in linkinfo the braid_notation includes the braid_index as first item of a pair
+ braid_notation = braid_notation[1]
+ return braid_notation
+
+ @cached_method
+ def braid_index(self):
+ r"""
+ Return the value of column ``braid_index`` for this
+ link as a Python int.
+
+ OUTPUT:
+
+ Python int giving the minimum of strands needed to
+ represent ``self`` as closure of a braid.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: L = KnotInfo.L4a1_0
+ sage: L.braid_index()
+ 4
+ """
+ if self.is_knot():
+ return int(self[self.items.braid_index])
+ else:
+ braid_notation = self[self.items.braid_notation]
+ braid_notation = eval_knotinfo(braid_notation)
+ return int(braid_notation[0])
+
+ @cached_method
+ def braid_length(self):
+ r"""
+ Return the value of column ``braid_length`` for this
+ link as a Python int.
+
+ OUTPUT:
+
+ Python int giving the minimum length of a braid word
+ needed to represent ``self`` as closure of a braid.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: K = KnotInfo.K3_1
+ sage: K.braid_length()
+ 3
+ """
+ return int(self[self.items.braid_length])
+
+ @cached_method
+ def braid(self):
+ r"""
+ Return the braid notation of self as an instance of :class:`~sage.groups.braid.Braid`.
+
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: K = KnotInfo.K3_1
+ sage: K.braid()
+ s^3
+ sage: K.braid_notation()
+ (1, 1, 1)
+ """
+ return self._braid_group()(self.braid_notation())
+
+ @cached_method
+ def num_components(self):
+ r"""
+ Return the number of components of ``self``.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.L6a1_0.num_components()
+ 2
+ """
+ return db.read_row_dict()[self.name][1]
+
+ @cached_method
+ def crossing_number(self):
+ r"""
+ Return the minimal number of crossings of ``self``.
+
+ .. NOTE::
+
+ In contrast to the number of crossings displayed for instances
+ of :class:`Link` this number is the minimum over all possible
+ diagrams of the link. The number of crossings displayed in
+ the representation string of :class:`Link` refers to the
+ special diagram which could be larger.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.L4a1_0.crossing_number()
+ 4
+ sage: KnotInfo.K3_1.crossing_number()
+ 3
+ sage: Link(KnotInfo.L4a1_0.braid())
+ Link with 2 components represented by 8 crossings
+ """
+ return int(self[self.items.crossing_number])
+
+ @cached_method
+ def determinant(self):
+ r"""
+ Return the determinant of ``self``.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.L4a1_0.determinant()
+ 4
+ sage: KnotInfo.K3_1.determinant()
+ 3
+ """
+ return int(self[self.items.determinant])
+
+ @cached_method
+ def is_knot(self):
+ r"""
+ Return whether ``self`` is a knot or a proper link.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.L7a1_0.is_knot() # optional - database_knotinfo
+ False
+ sage: KnotInfo.K6_3.is_knot()
+ True
+ """
+ return self.num_components() == 1
+
+ @cached_method
+ def name_unoriented(self):
+ r"""
+ Return the the part of the name of ``self`` which is independent on the orientation.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.L10a122_1_0.name_unoriented() # optional - database_knotinfo
+ 'L10a122'
+ """
+ return self[self.items.name_unoriented]
+
+ @cached_method
+ def symmetry_type(self):
+ r"""
+ Return the symmetry type of ``self``.
+
+ From the KnotInfo description page:
+
+ If a knot is viewed as the oriented diffeomorphism
+ class of an oriented pair, `K = (S_3, S_1)`, with `S_i`
+ diffeomorphic to `S^i`, there are four oriented knots
+ associated to any particular knot `K`. In addition to
+ `K` itself, there is the reverse, `K^r = (S_3, -S_1)`,
+ the concordance inverse, `-K = (-S_3, -S_1)`, and the
+ mirror image, `K^m = (-S_3, S_1)`. A knot is called
+ reversible if `K = K^r`, negative amphicheiral if
+ `K = -K`, and positive amphicheiral if `K = K^m`.
+
+ A knot possessing any two of these types of symmetry
+ has all three. Thus, in the table, a knot is called
+ reversible if that is the only type of symmetry it has,
+ and likewise for negative amphicheiral. If it has none
+ of these types of symmetry it is called chiral, and if
+ it has all three it is called fully amphicheiral.
+
+ For prime knots with fewer than 12 crossings, all
+ amphicheiral knots are negative amphicheiral.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: [(L.name, L.symmetry_type()) for L in KnotInfo if L.is_knot() and L.crossing_number() < 6]
+ [('K0_1', 'fully amphicheiral'),
+ ('K3_1', 'reversible'),
+ ('K4_1', 'fully amphicheiral'),
+ ('K5_1', 'reversible'),
+ ('K5_2', 'reversible')]
+ """
+ if not self.is_knot():
+ raise NotImplementedError('This is only available for knots')
+ if not self[self.items.symmetry_type] and self.crossing_number() == 0:
+ return 'fully amphicheiral'
+ return self[self.items.symmetry_type]
+
+ @cached_method
+ def is_reversible(self):
+ r"""
+ Return whether ``self`` is reversible.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.K6_3.is_reversible()
+ True
+ """
+ if self.symmetry_type() == 'reversible':
+ return True
+ if self.symmetry_type() == 'fully amphicheiral':
+ return True
+ return False
+
+ @cached_method
+ def is_amphicheiral(self, positive=False):
+ r"""
+ Return whether ``self`` is amphicheiral.
+
+ INPUT:
+
+ - ``positive`` -- boolean (optional, default False) whether to check
+ if ``self`` is positive or negative amphicheiral (see documentation
+ of :meth:`symmetry_type`)
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: K = KnotInfo.K12a_427 # optional - database_knotinfo
+ sage: K.is_amphicheiral() # optional - database_knotinfo
+ False
+ sage: K.is_amphicheiral(positive=True) # optional - database_knotinfo
+ True
+ """
+ if positive:
+ if self.symmetry_type() == 'positive amphicheiral':
+ return True
+ else:
+ if self.symmetry_type() == 'negative amphicheiral':
+ return True
+ if self.symmetry_type() == 'fully amphicheiral':
+ return True
+ return False
+
+ @cached_method
+ def is_alternating(self):
+ r"""
+ Return whether ``self`` is alternating.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.K5_2.is_alternating()
+ True
+ """
+ return knotinfo_bool(self[self.items.alternating])
+
+ @cached_method
+ def is_almost_alternating(self):
+ r"""
+ Return whether ``self`` is almost alternating.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.K5_2.is_almost_alternating() # optional - database_knotinfo
+ False
+ """
+ is_knotinfo_available(raise_error=True) # column not available in demo-version
+ return knotinfo_bool(self[self.items.almost_alternating])
+
+ @cached_method
+ def is_quasi_alternating(self):
+ r"""
+ Return whether ``self`` is quasi alternating.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.K5_2.is_quasi_alternating() # optional - database_knotinfo
+ True
+ """
+ is_knotinfo_available(raise_error=True) # column not available in demo-version
+ return knotinfo_bool(self[self.items.quasi_alternating])
+
+ @cached_method
+ def is_adequate(self):
+ r"""
+ Return whether ``self`` is adequate.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.K5_2.is_adequate() # optional - database_knotinfo
+ True
+ """
+ is_knotinfo_available(raise_error=True) # column not available in demo-version
+ return knotinfo_bool(self[self.items.adequate])
+
+ @cached_method
+ def is_positive(self):
+ r"""
+ Return whether ``self`` is positive.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.K5_2.is_positive()
+ True
+ """
+ return knotinfo_bool(self[self.items.positive])
+
+ @cached_method
+ def is_quasipositive(self):
+ r"""
+ Return whether ``self`` is quasi-positive.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.K5_2.is_quasipositive() # optional - database_knotinfo
+ True
+ """
+ is_knotinfo_available(raise_error=True) # column not available in demo-version
+ return knotinfo_bool(self[self.items.quasipositive])
+
+ @cached_method
+ def is_strongly_quasipositive(self):
+ r"""
+ Return whether ``self`` is strongly quasi-positive.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.K5_2.is_strongly_quasipositive() # optional - database_knotinfo
+ True
+ """
+ is_knotinfo_available(raise_error=True) # column not available in demo-version
+ return knotinfo_bool(self[self.items.strongly_quasipositive])
+
+ @cached_method
+ def is_positive_braid(self):
+ r"""
+ Return whether ``self`` is a positive braid.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.K5_2.is_positive_braid() # optional - database_knotinfo
+ False
+ """
+ is_knotinfo_available(raise_error=True) # column not available in demo-version
+ return knotinfo_bool(self[self.items.positive_braid])
+
+ @cached_method
+ def is_fibered(self):
+ r"""
+ Return whether ``self`` is fibered.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.K6_3.is_fibered()
+ True
+ """
+ return knotinfo_bool(self[self.items.fibered])
+
+ @cached_method
+ def is_oriented(self):
+ r"""
+ Return whether ``self`` is oriented.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.L6a2_1.is_oriented()
+ True
+ """
+ return not knotinfo_bool(self[self.items.unoriented])
+
+
+ @cached_method
+ def homfly_polynomial(self, var1=None, var2=None, original=False, sage_convention=False):
+ r"""
+ Return the HOMFLY-PT polynomial according to the value of column
+ ``homfly_polynomial`` for this knot or link (in the latter case the
+ column ``homflypt_polynomial`` is used) as an instance of the
+ element class according to the output of :meth:`Link.homfly_polynomial`
+ of :class:`Link`.
+
+ The HOMFLY-PT polynomial `P(L)` of a link `L` satisfies the following skein relation
+ (see the corresponding `KnotInfo description page `__):
+
+ .. MATH::
+
+ P(O) = 1,\,\,\, v^{-1} P(L_+) - v P(L_-) = z P(L_0)
+
+ INPUT:
+
+ - ``var1`` -- string for the name of the first variable (default depending
+ on keyword ``sage_convention``: ``'v'`` or ``'L'`` if ``sage_convention == True``)
+ - ``var2`` -- string for the name of the second variable (default depending
+ on keyword ``sage_convention``: ``'z'`` or ``'M'`` if ``sage_convention == True``)
+ - ``original`` -- boolean (default ``False``) if set to
+ ``True`` the original table entry is returned as a string
+ - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` the conversion
+ to Sage's conventions (see the note below) is performed
+
+ OUTPUT:
+
+ A Laurent polynomial over the integers, more precisely an instance of
+ :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`.
+ If ``original`` is set to ``True`` then a string is returned.
+
+ .. NOTE::
+
+ The skein-relation for the HOMFLY-PT polynomial given on KnotInfo
+ differs from the ones used in Sage.
+
+ Using Sage's HOMFLY-PT polynomial with ``normalization='az'``
+ the corresponding skein-relation is (see :meth:`Link.homfly_polynomial`
+ of :class:`Link`):
+
+ .. MATH::
+
+ P(O) = 1,\,\,\, a P(L_+) - a^{-1} P(L_-) = z P(L_0)
+
+ Thus, the HOMFLY-PT polynomial of KnotInfo compares to the one of Sage
+ by replacing ``v`` by ``~a``. To keep them comparable this translation
+ can be performed by setting the keyword ``sage_convention`` to ``True``.
+
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: K3_1 = KnotInfo.K3_1
+ sage: PK3_1 = K3_1.homfly_polynomial(); PK3_1
+ -v^4 + v^2*z^2 + 2*v^2
+ sage: K3_1.homfly_polynomial(original=True)
+ '(2*v^2-v^4)+ (v^2)*z^2'
+ sage: PK3_1s = K3_1.homfly_polynomial(sage_convention=True); PK3_1s
+ L^-2*M^2 + 2*L^-2 - L^-4
+ sage: PK3_1s == K3_1.link().homfly_polynomial(normalization='az')
+ True
+
+ for proper links::
+
+ sage: L4a1_1 = KnotInfo.L4a1_1
+ sage: PL4a1_1 = L4a1_1.homfly_polynomial(var1='x', var2='y'); PL4a1_1
+ -x^5*y + x^3*y^3 - x^5*y^-1 + 3*x^3*y + x^3*y^-1
+ sage: PL4a1_1s = L4a1_1.homfly_polynomial(var1='x', var2='y', sage_convention=True); PL4a1_1s
+ x^-3*y^3 + 3*x^-3*y + x^-3*y^-1 - x^-5*y - x^-5*y^-1
+ sage: PL4a1_1s == L4a1_1.link().homfly_polynomial(var1='x', var2='y', normalization='az')
+ True
+
+ check the skein-relation from the KnotInfo description page (applied to one of
+ the positive crossings of the right-handed trefoil)::
+
+ sage: R = PK3_1.parent()
+ sage: PO = R.one()
+ sage: L2a1_1 = KnotInfo.L2a1_1
+ sage: PL2a1_1 = L2a1_1.homfly_polynomial()
+ sage: v, z = R.gens()
+ sage: ~v*PK3_1 -v*PO == z*PL2a1_1
+ True
+
+ check the skein-relation given in the doc string of :meth:`Link.homfly_polynomial` of
+ :class:`Link` (applied to one of the positive crossings of the right-handed trefoil)::
+
+ sage: Rs = PK3_1s.parent()
+ sage: POs = Rs.one()
+ sage: PL2a1_1s = L2a1_1.homfly_polynomial(sage_convention=True)
+ sage: a, z = Rs.gens()
+ sage: a*PK3_1s - ~a*POs == z*PL2a1_1s
+ True
+
+
+ TESTS::
+
+ all(L.homfly_polynomial() == L.link().homfly_polynomial(normalization='az') for L in KnotInfo if L.crossing_number() > 0 and L.crossing_number() < 7)
+ True
+
+ REFERENCES:
+
+ - :wikipedia:`HOMFLY_polynomial`
+ """
+ if self.is_knot():
+ homfly_polynomial = self[self.items.homfly_polynomial]
+ else:
+ homfly_polynomial = self[self.items.homflypt_polynomial]
+
+ if original:
+ return homfly_polynomial
+
+ if sage_convention:
+ if not var1:
+ var1='L'
+ if not var2:
+ var2='M'
+ else:
+ if not var1:
+ var1='v'
+ if not var2:
+ var2='z'
+
+ R = self._homfly_pol_ring(var1, var2)
+ if not homfly_polynomial and self.crossing_number() == 0:
+ return R.one()
+
+ L, M = R.gens()
+ if sage_convention:
+ lc = {'v': ~L, 'z':M} # see note above
+ else:
+ lc = {'v': L, 'z':M}
+ return eval_knotinfo(homfly_polynomial, locals=lc)
+
+ @cached_method
+ def kauffman_polynomial(self, var1='a', var2='z', original=False):
+ r"""
+ Return the Kauffman polynomial according to the value of column
+ ``kauffman_polynomial`` for this knot or link as an instance of
+ :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`.
+
+ The Kauffman polynomial `F(L)` respectivlely its corresponding invariant under
+ regular isotopy `\Delta (L) = a^{w(L)} F(L)` where `w(L)` is the writhe of
+ the link `L` satisfies the following skein relation
+ (see the corresponding `KnotInfo description page `__):
+
+ .. MATH::
+
+ \Delta(O) = 1,\,\,\, \Delta(L_+) - \Delta(L_-) = z (\Delta(L_0 + \Delta(L_{\infty}))
+
+ Furthermore, removing a curl of sign `\epsilon` leads to a multiplication of `\Delta(L)`
+ with `a^{\epsilon}`.
+
+ INPUT:
+
+ - ``var1`` -- (default: ``'a'``) the first variable
+ - ``var2`` -- (default: ``'z'``) the second variable
+ - ``original`` -- boolean (optional, default ``False``) if set to
+ ``True`` the original table entry is returned as a string
+
+ OUTPUT:
+
+ A Laurent polynomial over the integers, more precisely an instance of
+ :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`.
+ If ``original`` is set to ``False`` then a string is returned.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: L = KnotInfo.L2a1_1
+ sage: L.kauffman_polynomial()
+ a^-1*z - a^-1*z^-1 + a^-2 + a^-3*z - a^-3*z^-1
+ sage: K = KnotInfo.K4_1
+ sage: K.kauffman_polynomial()
+ a^2*z^2 + a*z^3 - a^2 - a*z + 2*z^2 + a^-1*z^3 - 1 - a^-1*z + a^-2*z^2 - a^-2
+
+ Comparison with Jones polynomial::
+
+ sage: k = _
+ sage: a, z = k.variables()
+ sage: j = K.jones_polynomial(skein_normalization=True)
+ sage: t, = j.variables()
+ sage: k.subs(a=-t^3, z=~t+t) == j.subs(t=t^4)
+ True
+
+ Check the skein relation::
+
+ sage: K3_1 = KnotInfo.K3_1
+ sage: FK3_1 = K3_1.kauffman_polynomial()
+ sage: FL2a1_1 = L.kauffman_polynomial()
+ sage: z, a = FK3_1.variables()
+ sage: ΔK3_1 = FK3_1 * a**K3_1.link().writhe()
+ sage: ΔL2a1_1 = FL2a1_1 * a**L.link().writhe()
+ sage: ΔO1p = a # unknot with one positive curl
+ sage: ΔO2n = a**-2 # unknot with two negative curls
+ sage: ΔK3_1 + ΔO1p == z*(ΔL2a1_1 + ΔO2n)
+ True
+
+ REFERENCES:
+
+ - :wikipedia:`Kauffman_polynomial`
+ """
+ kauffman_polynomial = self[self.items.kauffman_polynomial]
+
+ if original:
+ return kauffman_polynomial
+
+ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing
+ R = LaurentPolynomialRing(ZZ, (var1, var2))
+ if not kauffman_polynomial and self.crossing_number() == 0:
+ return R.one()
+
+ a, z = R.gens()
+ lc = {'a': a, 'z': z}
+ return R(eval_knotinfo(kauffman_polynomial, locals=lc))
+
+
+ @cached_method
+ def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False, original=False, sage_convention=False):
+ r"""
+ Return the Jones polynomial according to the value of column ``jones_polynomial``
+ for this knot or link as an element of the symbolic ring :class:`~sage.symbolic.ring.SR`
+ or an instance of :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial`
+ depending on the keyword ``skein_normalization``. Using the keyword ``puiseux`` instead
+ of an element of the symbolic ring an instance of :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries`
+ can be returned.
+
+ The Jones polynomial `V(L)` of a link `L` satisfies the following skein relation
+ (see the corresponding `KnotInfo description page `__):
+
+ .. MATH::
+
+ V(O) = 1,\,\,\, t^{-1} V(L_+) - t V(L_-) = (t^{\frac{1}{2}} - t^{-\frac{1}{2}}) V(L_0)
+
+ INPUT:
+
+ - ``variab`` -- variable (default: ``None``) used according to :meth:`Link.jones_polynomial`
+ - ``skein_normalization`` -- boolean (default: ``False``) used according to
+ :meth:`Link.jones_polynomial`
+ - ``puiseux`` -- boolean (default ``True``) only used in case ``skein_normalization=False``.
+ If set to ``True`` instead of an element of the symbolic ring an instance of
+ :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries` is returned
+ - ``original`` -- boolean (default ``False``) if set to
+ ``True`` the original table entry is returned as a string
+ - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` the conversion
+ to Sage's conventions (see the note below) is performed
+
+
+ OUTPUT:
+
+ Depends on the keywords (in excluding order):
+
+ - ``original=True`` a string according to the original value from the database
+ - ``skein_normalization=True`` a Laurent polynomial over the integers, more precisely
+ an instance of :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial`
+ - ``puiseux=True`` a puiseux series over the integers, more precisely an instance of
+ :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries`
+
+ In all other cases an element of the symbolic ring :class:`~sage.symbolic.ring.SR`.
+
+ .. NOTE::
+
+ The only difference of conventions concerning the Jones polynomial is its representation
+ in the case of proper links. KnotInfo does not display these polynomials in the indeterminate
+ `t` used in the skein relation. Instead a variable `x` is used defined by `x^2 = t`.
+ Sage uses `t` in both cases, knots and proper links. Thus, to obtain the Jones polynomial
+ for a proper link in `t` you have to set the keyword ``sage_convention`` to ``True``.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: K = KnotInfo.K4_1
+ sage: Kj = K.jones_polynomial(); Kj
+ t^2 - t - 1/t + 1/t^2 + 1
+ sage: Kjs = K.jones_polynomial(skein_normalization=True); Kjs
+ A^-8 - A^-4 + 1 - A^4 + A^8
+ sage: Kjp = K.jones_polynomial(puiseux=True); Kjp
+ t^-2 - t^-1 + 1 - t + t^2
+
+ for proper links::
+
+ sage: L = KnotInfo.L2a1_1
+ sage: Lj = L.jones_polynomial(); Lj
+ -x^5 - x
+ sage: Ljt = L.jones_polynomial(sage_convention=True); Ljt
+ -t^(5/2) - sqrt(t)
+ sage: Ljp = L.jones_polynomial(puiseux=True, sage_convention=True); Ljp
+ -t^(1/2) - t^(5/2)
+ sage: Ljs = L.jones_polynomial(skein_normalization=True); Ljs
+ -A^2 - A^10
+ sage: Lj.parent()
+ Symbolic Ring
+ sage: Ljt.parent()
+ Symbolic Ring
+ sage: Ljp.parent()
+ Puiseux Series Ring in t over Integer Ring
+ sage: Ljs.parent()
+ Univariate Laurent Polynomial Ring in A over Integer Ring
+
+ Comparison with Sage's results::
+
+ sage: k = K.link()
+ sage: kj = k.jones_polynomial()
+ sage: bool(Kj == kj)
+ True
+ sage: kjs = k.jones_polynomial(skein_normalization=True)
+ sage: Kjs == kjs
+ True
+ sage: l = L.link()
+ sage: lj = l.jones_polynomial()
+ sage: bool(Lj == lj)
+ False
+ sage: bool(Ljt == lj) # see note above
+ True
+ sage: ljs = l.jones_polynomial(skein_normalization=True)
+ sage: Ljs == ljs
+ True
+
+ Check the skein-relation from the KnotInfo description page (applied to one of
+ the positive crossings of the right-handed trefoil)::
+
+ sage: K3_1 = KnotInfo.K3_1
+ sage: K3_1j = K3_1.jones_polynomial()
+ sage: L2a1_1j = Ljt # see note above
+ sage: R = L2a1_1j.parent()
+ sage: Oj = R(1)
+ sage: t = R('t')
+ sage: lhs = expand(~t*K3_1j - t*Oj)
+ sage: rhs = expand((sqrt(t) - ~sqrt(t))*L2a1_1j)
+ sage: bool(lhs == rhs)
+ True
+
+ The same with the Puiseux series version::
+
+ sage: K3_1jp = K3_1.jones_polynomial(puiseux=True)
+ sage: L2a1_1jp = Ljp
+ sage: R = L2a1_1jp.parent()
+ sage: Ojp = R(1)
+ sage: t = R('t')
+ sage: ~t*K3_1jp - t*Ojp == (t^(1/2)-~t^(1/2))*L2a1_1jp
+ True
+
+ The same in the case of skein normalization (using `t = A^4`)::
+
+ sage: K3_1js = K3_1.jones_polynomial(skein_normalization=True)
+ sage: L2a1_1js = L.jones_polynomial(skein_normalization=True)
+ sage: Rs = K3_1js.parent()
+ sage: Ojs = Rs.one()
+ sage: A, = Rs.gens()
+ sage: ~A^4*K3_1js - A^4*Ojs == (A^2-~A^2)*L2a1_1js
+ True
+
+ REFERENCES:
+
+ - :wikipedia:`Jones_polynomial`
+ """
+ jones_polynomial = self[self.items.jones_polynomial]
+
+ if original:
+ return jones_polynomial
+
+ if skein_normalization:
+ if not variab:
+ variab='A'
+ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing
+ R = LaurentPolynomialRing(ZZ, variab)
+ else:
+ if not variab:
+ if sage_convention or self.is_knot():
+ variab='t'
+ else:
+ variab='x'
+ if puiseux:
+ from sage.rings.puiseux_series_ring import PuiseuxSeriesRing # since PuiseuxPolynomial is not available, so far
+ R = PuiseuxSeriesRing(ZZ, variab)
+ else:
+ from sage.symbolic.ring import SR
+ R = SR
+
+ if not jones_polynomial and self.crossing_number() == 0:
+ return R(1)
+
+ t = R(variab)
+ if skein_normalization:
+ if self.is_knot():
+ lc = {'t': t**4}
+ else:
+ lc = {'x': t**2}
+ else:
+ if self.is_knot():
+ lc = {'t': t}
+ elif puiseux:
+ lc = {'x': t**(1/2)}
+ elif sage_convention:
+ from sage.functions.other import sqrt
+ lc = {'x': sqrt(t)}
+ else:
+ lc = {'x': t}
+
+
+ return R(eval_knotinfo(jones_polynomial, locals=lc))
+
+
+ @cached_method
+ def alexander_polynomial(self, var='t', original=False, sage_convention=False):
+ r"""
+ Return the Alexander polynomial according to the value of column
+ ``alexander_polynomial`` for this knot as an instance of
+ :class:`~sage.rings.polynomial.polynomial_element.Polynomial`.
+
+ It is obtained from the Seifert matrix `V` of ``self`` by the following
+ formula (see the KnotInfo description web-page; to launch it see the
+ example below):
+
+ .. MATH::
+
+ A(L) = \det(V -t V^t)
+
+ Here `V^t` stands for the transpose of `V`.
+
+
+ INPUT:
+
+ - ``var`` -- (default: ``'t'``) the variable
+ - ``original`` -- boolean (optional, default ``False``) if set to
+ ``True`` the original table entry is returned as a string
+ - ``sage_convention`` -- boolean (default ``False``) if set to ``True`` the
+ conversion to Sage's conventions (see the note below) is performed
+
+ OUTPUT:
+
+ A polynomial over the integers, more precisely an instance of
+ :class:`~sage.rings.polynomial.polynomial_element.Polynomial`.
+ If ``sage_convention`` is set to ``True`` a Laurent polynomial
+ over the integers, more precisely an instance of
+ :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial`
+ is returned. If ``original`` is set to ``True`` then a string
+ is returned.
+
+ .. NOTE::
+
+ As an invariant the Alexander polynomial is only unique up to
+ a unit factor in the Laurent polynomial ring over the integers
+ in the indeterminate `t`. While the normalization of the exponents
+ in KnotInfo guarantees it to be a proper polynomial, this is
+ not the case for the implementation in Sage. The transition
+ can be made using the keyword ``sage_convention``. But, still
+ there may be a difference in sign (see the example below).
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: K = KnotInfo.K4_1
+ sage: Ka = K.alexander_polynomial(); Ka
+ t^2 - 3*t + 1
+
+ Comparison with Sage's results::
+
+ sage: k = K.link()
+ sage: ka = k.alexander_polynomial(); ka
+ -t^-1 + 3 - t
+ sage: K.alexander_polynomial(sage_convention=True)
+ t^-1 - 3 + t
+ sage: _ == -ka
+ True
+
+ Launch the KnotInfo description web-page::
+
+ sage: K.items.alexander_polynomial.description_webpage() # not tested
+ True
+ """
+ alexander_polynomial = self[self.items.alexander_polynomial]
+
+ if original:
+ return alexander_polynomial
+
+ if sage_convention:
+ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing
+ R = LaurentPolynomialRing(ZZ, var)
+ else:
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+ R = PolynomialRing(ZZ, var)
+
+ if not alexander_polynomial and self.crossing_number() == 0:
+ return R.one()
+
+ t, = R.gens()
+ lc = {'t': t}
+ ap = R(eval_knotinfo(alexander_polynomial, locals=lc))
+ if not sage_convention or ap.is_constant():
+ return ap
+
+ exp = ap.exponents()
+ return t ** ((-max(exp) - min(exp)) // 2) * ap
+
+
+ @cached_method
+ def link(self, use_item=db.columns().pd_notation, snappy=False):
+ r"""
+ Return ``self`` as an instance of :class:`Link` or optional
+ ``spherogram.links.invariants.Link`` (SnapPy).
+
+ INPUT:
+
+ - ``use_item`` -- (optional, default ``self.items.pd_notation``)
+ instance of :class:`KnotInfoColumns` to choose the column
+ that should be used to construct the link. Allowed values
+ are:
+
+ - ``self.items.pd_notation``
+ - ``self.items.braid_notation``
+ - ``self.items.name`` (only for ``snappy=True``)
+ - ``self.items.dt_notation`` (only for knots and ``snappy=False``)
+ - ``self.items.gauss_notation`` (only for knots and ``snappy=False``)
+
+ - ``snappy`` boolean (default ``False``) if set to ``True``
+ the target of the conversion is the ``pip`` installable
+ package `SnapPy `__
+ (explicitely, ``spherogram.links.invariants.Link``).
+ If SnapPy is not installed an ``ImportError`` is raised. To
+ install SnapPy use ``sage -pip install snappy``.
+
+ .. NOTE::
+
+ We use the PD-notation to construct ``self`` as
+ default. This ensures that the number of crossings
+ displayed in the representation string of the link
+ coincides with the crossing number as a topological
+ invariant.
+
+ But attention: The convention on how the edges are
+ listed are opposite to each other
+
+ - KnotInfo: counter clockwise
+ - Sage: clockwise
+
+ Therefore, we take the mirror version of the ``pd_notation``!
+
+ Furthermore, note that the mirror version may depend
+ on the used KnotInfo-notation. For instance, regarding to
+ the knot ``5_1`` the Gauss- and the DT-notation refer to
+ the mirror image (see example below).
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: K = KnotInfo.K3_1
+ sage: K.link()
+ Knot represented by 3 crossings
+ sage: _.braid()
+ s^3
+ sage: _ == K.braid()
+ True
+
+ using ``dt_notation``::
+
+ sage: K.link(use_item=K.items.dt_notation)
+ Knot represented by 3 crossings
+ sage: _.braid()
+ s^3
+
+ sage: L = KnotInfo.L4a1_0
+ sage: L.link()
+ Link with 2 components represented by 4 crossings
+
+ sage: L.link(use_item=L.items.dt_notation)
+ Traceback (most recent call last):
+ ...
+ ValueError: Link construction using Columns.dt_notation not possible
+
+ using ``snappy``::
+
+ sage: K7 = KnotInfo.K7_2
+ sage: k7s = K7.link(snappy=True); k7s # optional - snappy
+
+ sage: K7.link(use_item=K7.items.name, snappy=True) # optional - snappy
+
+ sage: k7sn = _ # optional - snappy
+ sage: k7s == k7sn # optional - snappy
+ False
+ sage: k7s.sage_link().is_isotopic(k7sn.sage_link()) # optional - snappy
+ True
+
+ but observe::
+
+ sage: L2 = KnotInfo.L2a1_1
+ sage: l2 = L2.link()
+ sage: l2s = L2.link(snappy=True).sage_link() # optional - snappy
+ sage: l2 == l2s # optional - snappy
+ False
+ sage: l2 == l2s.mirror_image() # optional - snappy
+ True
+
+ using ``braid_notation``::
+
+ sage: L2.link(use_item=L.items.braid_notation) == l2
+ True
+
+ observe::
+
+ sage: L.link(use_item=L.items.braid_notation)
+ Link with 2 components represented by 8 crossings
+
+ sage: K6_1 = KnotInfo.K6_1
+ sage: K6_1.link().braid() == K6_1.braid()
+ False
+
+ also observe::
+
+ sage: K4_1 = KnotInfo.K4_1
+ sage: K4_1.link().pd_code()
+ [[4, 1, 5, 2], [8, 5, 1, 6], [6, 4, 7, 3], [2, 8, 3, 7]]
+ sage: K4_1.pd_notation()
+ [[4, 2, 5, 1], [8, 6, 1, 5], [6, 3, 7, 4], [2, 7, 3, 8]]
+
+ sage: K5_1 = KnotInfo.K5_1
+ sage: K5_1.link().braid()
+ s^5
+ sage: K5_1.link(K5_1.items.dt_notation).braid()
+ s^-5
+ sage: K5_1.link(K5_1.items.gauss_notation).braid()
+ s^-5
+ """
+ if not isinstance(use_item, KnotInfoColumns):
+ raise TypeError('%s must be an instance of %s' %(use_item, KnotInfoColumns))
+
+ if snappy:
+ try:
+ from snappy import Link
+ except ImportError:
+ raise ImportError('This option demands snappy to be installed')
+ elif self.is_knot():
+ from sage.knots.knot import Knot as Link
+ else:
+ from sage.knots.link import Link
+
+ if use_item == self.items.pd_notation:
+ pd_code = [[a[0], a[3], a[2], a[1]] for a in self.pd_notation()] # take mirror version, see note above
+ return Link(pd_code)
+ elif use_item == self.items.braid_notation:
+ return Link(self.braid())
+ elif use_item == self.items.name and snappy:
+ if not self.is_knot():
+ use_item = self.items.name_unoriented
+ return Link(self[use_item])
+ elif self.is_knot() and not snappy:
+ # Construction via Gauss and DT-Code only possible for knots
+ from sage.knots.knot import Knots
+ if use_item == self.items.dt_notation:
+ return Knots().from_dowker_code(self.dt_notation())
+ elif use_item == self.items.gauss_notation:
+ return Knots().from_gauss_code(self.gauss_notation())
+
+ raise ValueError('Link construction using %s not possible' %use_item)
+
+
+ def inject(self, verbose=True):
+ """
+ Inject ``self`` with its name into the namespace of the
+ Python code from which this function is called.
+
+ INPUT:
+
+ - ``verbose`` -- boolean (optional, default ``True``) to suppress
+ the message printed on the invocation
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.K5_2.inject()
+ Defining K5_2
+ sage: K5_2.is_alternating()
+ True
+ """
+ name = self.name
+ if verbose:
+ print("Defining %s" % (name))
+ from sage.repl.user_globals import set_global
+ set_global(name, self)
+
+ def series(self, overview=True):
+ r"""
+ Return the series of links ``self`` belongs to.
+
+ INPUT:
+
+ - ``overview`` -- boolean (optional, default ``True``) if set to ``False``
+ the series will be reduced to the unoriented type of ``self``
+ in the case of proper links.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: K5 = KnotInfo.K5_2.series()
+ sage: K5(1)
+
+ sage: KnotInfo.L4a1_1.series().inject()
+ Defining L4a
+ sage: L4a(1)
+ Series of links L4a1
+ sage: KnotInfo.L4a1_1.series(overview=False).inject()
+ Defining L4a1
+ sage: L4a1(1)
+
+ """
+ if overview:
+ S = KnotInfoSeries(self.crossing_number(), self.is_knot(), self.is_alternating())
+ else:
+ S = KnotInfoSeries(self.crossing_number(), self.is_knot(), self.is_alternating(), self.name_unoriented())
+ return S
+
+ def diagram(self, single=False, new=0, autoraise=True):
+ r"""
+ Launch the diagram of ``self`` given on the KnotInfo web-page.
+
+ INPUT:
+
+ - ``single`` -- boolean (default ``False``) if set to ``True`` only one
+ diagram is shown.
+ - ``new`` -- ``int`` according to :func:`open` of :mod:`webbrowser`
+ (``0`` default, ``1`` new window, ``2`` new tab)
+ - ``autoraise`` -- boolean (default ``True``)
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: K = KnotInfo.K3_1
+ sage: K.diagram() # not tested
+ True
+ sage: K.diagram(single=True) # not tested
+ True
+ """
+ import webbrowser
+ if self.is_knot():
+ filename = db.filename.knots
+ else:
+ filename = db.filename.links
+
+ if single:
+ return webbrowser.open(filename.diagram_url(self[self.items.diagram], single=single), new=new, autoraise=autoraise)
+ else:
+ return webbrowser.open(filename.diagram_url(self[self.items.name]), new=new, autoraise=autoraise)
+
+
+ def knot_atlas_webpage(self, new=0, autoraise=True):
+ r"""
+ Launch the Knot Atlas web-page for ``self``.
+
+ INPUT:
+
+ - ``new`` -- ``int`` according to :func:`open` of :mod:`webbrowser`
+ (``0`` default, ``1`` new window, ``2`` new tab)
+ - ``autoraise`` -- boolean (default ``True``)
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: K = KnotInfo.K3_1
+ sage: K.knot_atlas_webpage() # not tested
+ True
+ """
+ import webbrowser
+ return webbrowser.open(self[self.items.knot_atlas_anon], new=new, autoraise=autoraise)
+
+ def knotilus_webpage(self, new=0, autoraise=True):
+ r"""
+ Launch the Knotilus web-page for ``self``.
+
+ INPUT:
+
+ - ``new`` -- ``int`` according to :func:`open` of :mod:`webbrowser`
+ (``0`` default, ``1`` new window, ``2`` new tab)
+ - ``autoraise`` -- boolean (default ``True``)
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: K = KnotInfo.K3_1
+ sage: K.knotilus_webpage(new=1) # not tested
+ True
+ """
+ import webbrowser
+ return webbrowser.open(self[self.items.knotilus_page_anon], new=new, autoraise=autoraise)
+
+
+
+# --------------------------------------------------------------------------------------------
+# KnotInfoSeries
+# --------------------------------------------------------------------------------------------
+class KnotInfoSeries(UniqueRepresentation):
+ r"""
+ This class can be used to access knots and links via their index
+ according to the series they belong to.
+
+ INPUT:
+
+ - ``crossing_number`` -- integer giving the crossing numer of this series of links
+ - ``is_knot`` -- boolean wether this series is a series of knots or proper links
+ - ``is_alternating`` -- boolean wether this series is restriced to alternating links or not.
+ This is not relevant for knots with less than 11 crossings
+ - ``name_unoriented`` -- string restricting the series to all links with that ``name_unoriented``
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfoSeries
+ sage: K6 = KnotInfoSeries(6, True, True); K6
+ Series of knots K6
+ sage: K6(3)
+
+ sage: list(K6)
+ [, , ]
+ sage: L6a = KnotInfoSeries(6, False, True); L6a
+ Series of links L6a
+ sage: L6a(2)
+ Series of links L6a2
+ sage: _.inject()
+ Defining L6a2
+ sage: list(L6a2)
+ [, ]
+ sage: L6a2(0).series() == L6a
+ True
+ sage: L6a2(0) == L6a2('0')
+ True
+ """
+
+
+ def __init__(self, crossing_number, is_knot, is_alternating, name_unoriented=None):
+ r"""
+ Python constructor.
+
+ EXAMPLES::
+ sage: from sage.knots.knotinfo import KnotInfoSeries
+ sage: L6a = KnotInfoSeries(6, False, True); L6a
+ Series of links L6a
+ sage: TestSuite(L6a).run()
+ """
+ self._crossing_number = crossing_number
+ self._is_knot = is_knot
+ self._is_alternating = is_alternating
+ self._name_unoriented = name_unoriented
+ self._list = None
+
+ def list(self):
+ r"""
+ Return this series as a Python list.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfoSeries
+ sage: K6 = KnotInfoSeries(6, True, True); K6
+ Series of knots K6
+ sage: K6(3)
+
+ """
+ if self._list:
+ return self._list
+
+ is_knot = self._is_knot
+ cross_nr = self._crossing_number
+ is_alt = self._is_alternating
+ n_unori = self._name_unoriented
+
+ self._list = []
+ curr_n_unori = None
+ for K in KnotInfo:
+ if K.is_knot() != is_knot:
+ continue
+ if K.crossing_number() != cross_nr:
+ continue
+ if not is_knot or cross_nr > 10:
+ if K.is_alternating() != is_alt:
+ continue
+ if is_knot:
+ self._list.append(K)
+ else:
+ this_n_unori = K.name_unoriented()
+ if n_unori:
+ if this_n_unori != n_unori:
+ continue
+ self._list.append(K)
+ elif this_n_unori != curr_n_unori:
+ if curr_n_unori:
+ self._list.append(KnotInfoSeries(cross_nr, is_knot, is_alt, curr_n_unori))
+ curr_n_unori = this_n_unori
+ else:
+ continue
+
+ if curr_n_unori:
+ self._list.append(KnotInfoSeries(cross_nr, is_knot, is_alt, curr_n_unori))
+ return self._list
+
+
+ def __repr__(self):
+ r"""
+ Return the representation string of ``self``.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfoSeries
+ sage: KnotInfoSeries(6, True, True)
+ Series of knots K6
+ sage: _.__repr__()
+ 'Series of knots K6'
+ """
+ if self._is_knot:
+ return 'Series of knots %s' %(self._name())
+ else:
+ return 'Series of links %s' %(self._name())
+
+
+ def __getitem__(self, item):
+ r"""
+ Return the given ``item`` from the list of ``self``
+ (making the Python build-in ``list`` work).
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfoSeries
+ sage: KnotInfoSeries(6, True, True).inject()
+ Defining K6
+ sage: list(K6) # indirect doctest
+ [, , ]
+ """
+ from sage.rings.integer import Integer
+ if not type(item) in (int, Integer):
+ raise ValueError('Item must be an integer')
+ l =self.list()
+ max_item = len(l)
+ if item < 0 or item > max_item:
+ raise ValueError('Item must be non negative and smaller than %s' %(max_item))
+
+ return l[item]
+
+ def __call__(self, item):
+ r"""
+ Return the given ``item`` from the list of ``self``
+ (making the function call for ``self`` work).
+ In contrast to ``__getitem__`` the first ``item``
+ has to be ``1`` (not ``0``).
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfoSeries
+ sage: KnotInfoSeries(6, True, True).inject()
+ Defining K6
+ sage: K6(2) # indirect doctest
+
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: KnotInfo.L8a21_0_1_0.inject() # optional - database_knotinfo
+ Defining L8a21_0_1_0
+ sage: L8a21_0_1_0.series()(1) # optional - database_knotinfo
+ Series of links L8a1
+ sage: L8a21_0_1_0.series()(21)(2) == L8a21_0_1_0 # optional - database_knotinfo
+ True
+ sage: L8a21_0_1_0.series()(21)('010') == L8a21_0_1_0 # optional database_knotinfo
+ True
+ """
+ if self._name_unoriented:
+ if type(item) == str:
+ # allow input as dual number according to naming
+ item = int(item, 2)
+ return self[item]
+
+ from sage.rings.integer import Integer
+ if not type(item) in (int, Integer):
+ raise ValueError('Item must be an integer')
+ l =self.list()
+ max_item = len(l)+1
+ if item < 1 or item > max_item:
+ raise ValueError('Item must be positive and smaller than %s' %(max_item))
+
+ return l[item-1]
+
+ def _name(self):
+ r"""
+ Return the name of the series.
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfoSeries
+ sage: KnotInfoSeries(6, True, True)._name()
+ 'K6'
+ """
+ is_knot = self._is_knot
+ cross_nr = self._crossing_number
+ is_alt = self._is_alternating
+ n_unori = self._name_unoriented
+
+ alt = 'a'
+ if not is_alt:
+ alt = 'n'
+
+ if is_knot:
+ if cross_nr > 10:
+ res = 'K%s%s' %(cross_nr, alt)
+ else:
+ res = 'K%s' %(cross_nr)
+ elif n_unori:
+ res = '%s' %(n_unori)
+ else:
+ res = 'L%s%s' %(cross_nr, alt)
+ return res
+
+
+ def inject(self, verbose=True):
+ """
+ Inject ``self`` with its name into the namespace of the
+ Python code from which this function is called.
+
+ INPUT:
+
+ - ``verbose`` -- boolean (optional, default ``True``) to suppress
+ the message printed on the invocation
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfoSeries
+ sage: KnotInfoSeries(6, True, True).inject()
+ Defining K6
+ sage: K6(2)
+
+ """
+ name = self._name()
+ if verbose:
+ print("Defining %s" % (name))
+ from sage.repl.user_globals import set_global
+ set_global(name, self)
+
+
+
+
+KnotInfo = KnotInfoBase('KnotInfo', db.row_names())
diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py
index 9ab236bd7c3..e72abe28166 100644
--- a/src/sage/knots/link.py
+++ b/src/sage/knots/link.py
@@ -26,14 +26,18 @@
.. SEEALSO::
- There are also tables of link and knot invariants at
- https://www.indiana.edu/~knotinfo/
- and https://www.indiana.edu/~linkinfo/.
+ There are also tables of link and knot invariants at web-pages
+ `KnotInfo `__ and
+ `LinkInfo `__. These can be
+ used inside Sage after installing the optional package
+ ``database_knotinfo`` (type ``sage -i database_knotinfo`` in a command shell,
+ see :mod:`~sage.knots.knotinfo`).
AUTHORS:
- Miguel Angel Marco Buzunariz
- Amit Jamadagni
+- Sebastian Oehms (October 2020, add :meth:`identify_knotinfo` and meth:`is_isotopic`)
"""
# ****************************************************************************
@@ -2515,7 +2519,7 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'):
.. NOTE::
Use the ``'az'`` normalization to agree with the data
- in [KnotAtlas]_ and http://www.indiana.edu/~knotinfo/.
+ in [KnotAtlas]_ and `KnotInfo `__.
EXAMPLES:
@@ -3245,3 +3249,242 @@ def delta(u, v):
image += l
ims += sum(line(a[0], **kwargs) for a in im)
return image
+
+
+ def identify_knotinfo(self, oriented=True, mirror_version=True, unique=True):
+ """
+ Identify this link as an item of the KontInfo database (if possible).
+
+ INPUT:
+
+ - ``oriented`` -- boolean (default is ``True``). If set to ``False`` the orientation
+ of the link will be ignored and instead of an instance of :class:`~sage.knots.knotinfo.KnotInfoBase`
+ a series of links (instance of :class:`~sage.knots.knotinfo.KnotInfoSeries`) will be
+ returned collecting all links having the same ``name_unoriented`` (if this is unique
+ for ``self``)
+
+ - ``mirror_version`` -- boolean (default is ``True``). If set to ``False`` the result
+ of the method will be just the instance of :class:`~sage.knots.knotinfo.KnotInfoBase` (by default the result
+ is a tuple of the instance and a boolean, see explanation of the output below)
+
+ - ``unique`` -- boolean (default is ``True``). This only affects the case where a unique
+ identification is not possible. If set to ``False`` you can obtain a matching list
+ (see explanation of the output below)
+
+ OUTPUT:
+
+ A tuple ``(K, m)`` where ``K`` is an instance of :class:`~sage.knots.knotinfo.KnotInfoBase` and ``m`` a
+ boolean telling if ``self`` corresponse to the mirrored version of ``K`` or not.
+
+ If ``oriented`` is set to ``False`` then the result is a series of links (instance of
+ :class:`~sage.knots.knotinfo.KnotInfoSeries`, see explanation above).
+
+ If ``mirror_version`` is set to ``False`` then the result is just ``K`` (that is: ``m``
+ is suppressed).
+
+ If it is not possible to determine a unique result a ``NotImplementedError`` will be
+ raised. To avoid this you can set ``unique`` to ``False``. You will get a list of matching
+ candidates instead.
+
+
+ EXAMPLES::
+
+ sage: from sage.knots.knotinfo import KnotInfo
+ sage: L = Link([[4,2,5,1], [10,3,11,4], [5,16,6,17], [7,12,8,13],
+ ....: [18,9,19,10], [2,11,3,12], [13,20,14,21], [15,6,16,7],
+ ....: [22,18,1,17], [8,19,9,20], [21,14,22,15]])
+ sage: L.identify_knotinfo() # optional - database_knotinfo
+ (, True)
+
+ sage: K = KnotInfo.K10_25 # optional - database_knotinfo
+ sage: l = K.link() # optional - database_knotinfo
+ sage: l.identify_knotinfo() # optional - database_knotinfo
+ (, False)
+
+ Lets identify the monster unknot (works without the database, as well)::
+
+ sage: L = Link([[3,1,2,4], [8,9,1,7], [5,6,7,3], [4,18,6,5],
+ ....: [17,19,8,18], [9,10,11,14], [10,12,13,11],
+ ....: [12,19,15,13], [20,16,14,15], [16,20,17,2]])
+ sage: L.identify_knotinfo()
+ (, False)
+
+ Usage of option ``mirror_version``::
+
+ sage: L.identify_knotinfo(mirror_version=False) == KnotInfo.K0_1
+ True
+
+ Usage of option ``oriented``::
+
+ sage: KnotInfo.L10a1_0.inject() # optional - database_knotinfo
+ Defining L10a1_0
+ sage: b = L10a1_0.link().braid() # optional - database_knotinfo
+ sage: l10 = Link(b) # optional - database_knotinfo
+ sage: l10.identify_knotinfo() # optional - database_knotinfo
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: Sorry, this link cannot be uniquely determined
+ sage: l10.identify_knotinfo(oriented=False) # optional - database_knotinfo
+ (Series of links L10a1, False)
+ sage: _[0].inject() # optional - database_knotinfo
+ Defining L10a1
+ sage: list(L10a1) # optional - database_knotinfo
+ [, ]
+
+ Usage of option ``unique``::
+
+ sage: l = K.link(use_item=K.items.gauss_notation) # optional - database_knotinfo
+ sage: l.identify_knotinfo() # optional - database_knotinfo
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: Sorry, this link cannot be uniquely determined
+
+ sage: l.identify_knotinfo(unique=False) # optional - database_knotinfo
+ [, ]
+
+ Clarifying the series around the Perko pair (:wikipedia:`Perko_pair`)::
+
+ sage: for i in range(160, 166): # optional - database_knotinfo
+ ....: K = Knots().from_table(10, i)
+ ....: print('%s_%s' %(10, i), '--->', K.identify_knotinfo())
+ 10_160 ---> (, False)
+ 10_161 ---> (, True)
+ 10_162 ---> (, False)
+ 10_163 ---> (, False)
+ 10_164 ---> (, False)
+ 10_165 ---> (, True)
+
+ Clarifying ther Perko series against `SnapPy `__::
+
+ sage: from sage.knots.knotinfo import KnotInfoSeries
+ sage: KnotInfoSeries(10, True, True).inject() # optional - database_knotinfo
+ Defining K10
+ sage: for i in range(160, 166): # optional - database_knotinfo snappy
+ ....: K = K10(i)
+ ....: k = K.link(use_item=K.items.name, snappy=True)
+ ....: print(k, '--->', k.sage_link().identify_knotinfo())
+ Plink failed to import tkinter.
+ ---> (, True)
+ ---> (, False)
+ ---> (, True)
+ ---> (, True)
+ ---> (, True)
+ ---> (, True)
+
+ sage: import snappy # optional - snappy
+ sage: k10_166 = snappy.Link('10_166') # optional - snappy
+ sage: k10_166.sage_link().identify_knotinfo() # optional - database_knotinfo snappy
+ (, False)
+ """
+ from sage.knots.knotinfo import knotinfo_matching_list, is_knotinfo_available, KnotInfoSeries
+ cr = len(self.pd_code())
+ co = self.number_of_components()
+ if co == 1 and cr > 12:
+ # we cannot not sure if this link is recorded in the KnotInfo database
+
+ raise NotImplementedError('Sorry, this link cannot be uniquely determined')
+ if co > 1 and cr > 11:
+ # we cannot not sure if this link is recorded in the KnotInfo database
+ raise NotImplementedError('Sorry, this link cannot be uniquely determined')
+
+ H = self.homfly_polynomial(normalization='az')
+
+ if len(H.factor()) > 1:
+ # we cannot be sure if this is a prime link
+ raise NotImplementedError('Sorry, this link cannot be uniquely determined')
+
+ Hm = None
+ l = knotinfo_matching_list(cr, co, homfly_polynomial=H)
+ if not l:
+ # try with the mirrored HOMFLY-PT polynomial
+ M, L = H.variables()
+ Hm = H.subs(L=~L, M=-M)
+ if H != Hm:
+ l = knotinfo_matching_list(cr, co, homfly_polynomial=Hm)
+
+ if not l:
+ is_knotinfo_available(raise_error=True)
+ return None
+
+ def answer(res, mirror):
+ if not oriented:
+ res = KnotInfoSeries(res.crossing_number(), res.is_knot(), res.is_alternating(), res.name_unoriented())
+
+ if mirror_version:
+ return res, mirror
+ else:
+ return res
+
+
+ if len(l) == 1:
+ return answer(l[0], Hm is not None)
+
+ self_m = self.mirror_image()
+
+ for L in l:
+ if L.braid() == self.braid():
+ return answer(L, False)
+ if L.braid() == self_m.braid():
+ return answer(L, True)
+
+ # note that KnotInfo pd_notation works counter clockwise, see docstring
+ # of :meth:`link` of :class:`~sage.knots.knotinfo.KnotInfoBase`.
+ if L.pd_notation() == self.pd_code():
+ return answer(L, True)
+ if L.pd_notation() == self_m.pd_code():
+ return answer(L, False)
+
+ if not oriented:
+ from sage.sets.set import Set
+ lu = list(Set([L.name_unoriented() for L in l]))
+ if len(lu) == 1:
+ return answer(l[0], Hm is not None)
+ elif unique:
+ raise NotImplementedError('Sorry, this link cannot be uniquely determined')
+ return lu
+
+ if unique:
+ raise NotImplementedError('Sorry, this link cannot be uniquely determined')
+ return l
+
+ def is_isotopic(self, other):
+ r"""
+ Check wether ``self`` is isotopic to ``other``.
+
+ INPUT:
+
+ - ``other`` -- another instance of :class:`Link`
+
+ EXAMPLES::
+
+ sage: l1 = Link([[2, 9, 3, 10], [4, 13, 5, 14], [6, 11, 7, 12],
+ ....: [8, 1, 9, 2], [10, 7, 11, 8], [12, 5, 13, 6],
+ ....: [14, 3, 1, 4]])
+ sage: l2 = Link([[1, 8, 2, 9], [9, 2, 10, 3], [3, 14, 4, 1],
+ ....: [13, 4, 14, 5], [5, 12, 6, 13], [11, 6, 12, 7],
+ ....: [7, 10, 8, 11]])
+ sage: l1.is_isotopic(l2)
+ True
+
+ sage: l3 = l2.mirror_image()
+ sage: l1.is_isotopic(l3)
+ False
+ """
+ if not isinstance(other, Link):
+ return False
+
+ if self == other:
+ # surely isotopic
+ return True
+
+ if self.homfly_polynomial() != other.homfly_polynomial():
+ # surely non isotopic
+ return False
+
+ ki = self.identify_knotinfo()
+ if ki and type(ki) == tuple:
+ kio = other.identify_knotinfo()
+ if kio and type(kio) == tuple:
+ return ki == kio
+
+ raise NotImplementedError('Comparison not possible!')