Source code for pyknotid.catalogue.database

'''Database module
---------------

pyknotid looks up knot information via a prebuilt sqlite database,
accessed using the peewee ORM. Other ORMs are not currently supported.

The model class is :class:`pyknotid.catalogue.database.Knot`,
documented below. For generic documentation about using the database,
see :doc:`index`.

pyknotid also includes functions for creating a database from scratch
(using knot information from the Knot Atlas), and improving an
existing database by calculating new invariants or pulling information
from other sources such as the KnotInfo database. These functions can
be found in ``pyknotid.catalogue.build`` and
``pyknotid.catalogue.improve``, which are not included in the indexed
documentation here.

API documentation
~~~~~~~~~~~~~~~~~

'''

from peewee import *
import json
import os
from os.path import realpath, dirname, exists, join
from pyknotid.catalogue import converters

from pyknotid.catalogue.getdb import find_database

if not ('READTHEDOCS' in os.environ and os.environ['READTHEDOCS'] == 'True'):
    DB_LOCATION = find_database()
    db = SqliteDatabase(DB_LOCATION)
    db.connect()
else:
    DB_LOCATION = None
    db = None

class BaseModel(Model):
    class Meta(object):
        database = db


# If updating the db structure, don't forget to update
# pyknotid.catalogue.db_version!
[docs]class Knot(BaseModel): '''Peewee model for storing a knot in a database.''' name = CharField(null=True) '''The actual name (if any), e.g. trefoil''' identifier = CharField(null=True) '''The standard knot notation, e.g. 3_1 for trefoil''' min_crossings = IntegerField(null=True) '''Minimal crossing number for the knot, e.g. 3 for trefoil''' signature = IntegerField(null=True) '''The knot signature''' determinant = IntegerField(null=True) '''The knot determinant (Alexander polynomial at -1)''' alexander_imag_3 = IntegerField(null=True) '''The absolute value of the Alexander polynomial at exp(2 pi I / 3). This will always be an integer.''' alexander_imag_4 = IntegerField(null=True) '''The absolute value of the Alexander polynomial at exp(2 pi I / 4) == I. This will always be an integer.''' alexander = TextField(null=True) '''Alexander polynomial, stored as a json list of coefficients from lowest to highest index, including zeros if there are any jumps in index. ''' jones = TextField(null=True) '''Jones polynomial, stored as a json list of coefficients and indices for each monomial.''' homfly = TextField(null=True) '''HOMFLY-PT polynomial, stored as a json list.''' unknotting_number = IntegerField(null=True) '''Unknotting number, stored as an integer.''' hyperbolic_volume = CharField(null=True) '''Hyperbolic volume, stored as a string to avoid precision problems.''' conway_notation = CharField(null=True) '''Conway notation, as a string.''' gauss_code = CharField(null=True) '''Gauss code, as a string.''' planar_diagram = CharField(null=True) '''Planar diagram representation, as a string.''' dt_code = CharField(null=True) '''Dowker-Thistlethwaite code, as a string.''' two_bridge = CharField(null=True) '''Two-bridge notation, as a string.''' fibered = BooleanField(null=True) '''Whether the knot is fibered or not.''' composite = BooleanField(null=True) '''Whether the knot is composite or not.''' vassiliev_2 = IntegerField(null=True) '''The Vassiliev invariant of order 2.''' vassiliev_3 = IntegerField(null=True) '''The Vassiliev invariant of order 3.''' symmetry = CharField(null=True, max_length=25) '''The symmetry type of the knot; reversible, positive amphichiral, negative amphichiral fully amphichiral or chiral.''' planar_writhe = IntegerField(null=True) '''The writhe of the minimal diagram described by the DT_code. This is not necessarily unique (see Perko pair, I think?).''' def __str__(self): if self.name: return '<Knot {} ({})>'.format(self.identifier, self.name) return '<Knot {}>'.format(self.identifier) def __repr__(self): return str(self)
[docs] def pretty_print(self): '''Pretty print all information contained about self.''' if self.name: print('Name: {}'.format(self.name)) if self.identifier: print('Identifier: {}'.format(self.identifier)) if self.min_crossings: print('Min crossings: {}'.format(self.min_crossings)) if self.fibered is not None: print('Fibered: {}'.format(self.fibered)) if self.gauss_code: print('Gauss code: {}'.format(self.gauss_code)) if self.planar_diagram: print('Planar diagram: {}'.format(self.planar_diagram)) if self.dt_code: print('DT code: {}'.format(self.dt_code)) if self.determinant: print('Determinant: {}'.format(self.determinant)) if self.signature: print('Signature: {}'.format(self.signature)) if self.alexander: print('Alexander: {}'.format( converters.json_to_alexander(self.alexander))) if self.jones: print('Jones: {}'.format( converters.json_to_jones(self.jones))) if self.homfly: print('HOMFLY: {}'.format( converters.json_to_homfly(self.homfly))) if self.hyperbolic_volume: print('Hyperbolic volume: {}'.format(self.hyperbolic_volume)) if self.vassiliev_2 is not None: print('Vassiliev order 2: {}'.format(self.vassiliev_2)) if self.vassiliev_3 is not None: print('Vassiliev order 3: {}'.format(self.vassiliev_3)) if self.symmetry is not None: print('Symmetry: {}'.format(self.symmetry))
[docs] def space_curve(self, verbose=True, **kwargs): '''Returns a Knot object representing this knot.''' if self.dt_code is None: raise ValueError('No DT code is known, cannot create ' 'space curve.') from pyknotid.representations import DTNotation import numpy as np d = DTNotation(self.dt_code) k = d.representation(verbose=verbose, **kwargs).space_curve( verbose=verbose) k.points += 0.0001 * np.random.random(k.points.shape) k.close() return k
[docs] def url(self): '''The guessed url of this knot in the Knot Atlas. This page may not actually exist or be populated. ''' return 'http://katlas.org/wiki/{}'.format(self.identifier)
def open_url(self): import webbrowser webbrowser.open(self.url()) def retrieve_image_path(self, **kwargs): images_folder = join( dirname(realpath(__file__)), 'diagrams_with_mirrors') name = self.identifier filen = _name_to_filen(name) filen = join(images_folder, filen) if not exists(filen): return None return filen @property def components(self): '''A list tuples ``(identifier, index)``, where the knot with the given identifier occurs ``index`` times.''' name = self.identifier components = [] for component in name.split('#'): if '^' in component: component, index = component.split('^') index = int(index) else: index = 1 components.append((component, index)) return components
def _name_to_filen(name): if name.startswith('K'): name = name[1:] if int(name[:2]) == 11: digits = 3 else: digits = 4 filen = '{{}}_{{:0{}}}.png'.format(digits).format(name[:3], int(name[3:])) else: filen = name + '.png' return filen