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!')