Package: braidgenerator
Braid GeneratorBraid Generator is a project from the Department of Mathematics in the University of California, Davis. Braid Generator implements a Markov chain algorithm to generate an ensemble of braid representatives ("braidreps") from a given braid representative of a fixed knot or link type. The project was born when we attempted to use Machine Learning techniques to study braids but found the existing data sets of braid representatives to be too small. The hope is that this program will help others generate data to help better understand braids, knot invariants, and the topology of the space of knots. For more information on
mathematical braid groups, please refer to thisresource.Getting StartedThese instructions will get you a copy of the project up and running on your local machine for development and testing purposes.PrerequisitesDue to the formatting styles used in this package,braidgeneratoris compatible with Python 3.6 or 3.7.The only third-party dependency used in this package ispandas..
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── ThirdPartyLicenses
│   └── Pandas
│       └── LICENSE.md
├── braidgenerator
│   ├── __init__.py
│   ├── braidword.py
│   ├── decorators
│   │   ├── __init__.py
│   │   ├── braidword.py
│   │   └── markovchain.py
│   └── markovchain.py
├── docs
│   ├── braidword.html
│   ├── contact.html
│   ├── index.html
│   ├── markovchain.html
│   ├── style.css
│   └── van.js
├── setup.py
└── tests
    ├── test_braidword.py
    └── test_markovchain.pyThe tree represents the hierarchy of the github repository.InstallationInstall BraidGenerator from PyPITo install via pip, use the following$ pip install braidgeneratorPlease note the actual project has not been put up on PyPi yet. Please
follow installation by repository. Package will be uploaded to PyPi in the
coming few days.Install BraidGenerator from the GitHub sourceFirst, clone the Knots repository onto your local machine usinggit. For more information ongitcloning, visit thisresource.git clone https://github.com/Algebra8/KnotsThen,cdto the Knots folder and run the install command:cd Knots
python setup.py installSetup and ExamplesA quick example for the impatient userWithin Python try the following example snippet, which takes as its input a braid representative for the trefoil knot ([1, 1, 1]), and returns three randomized braid representatives.import pandas as pd

from braidgenerator import MarkovChain

# Create markov chain
mc = MarkovChain(braidword=[1, 1, 1], maxgen=9, maxlen=10)

# Model markov chain
mc.model(num_braidreps=3, msteps=500)

# Get braid representatives as words (i.e. as lists)
braidreps = mc.braidreps(as_word=True)Explanations and definitions are given in detail in the sections below.Setting up the Braid GeneratorTo create a Markov Chain, simply callMarkovChainwith at least thebraidwordargument given. TheMarkovChaininitializor has default values of
nine and ten for themaxgenandmaxlenparameters, respectively. The speficied argument ofbraidwordis the initial state of the Markov process; the state space consists of other braid words representating the same knot as the (closed) initial braid.from braidgenerator import MarkovChain
import pandas as pd

mc = MarkovChain(braidword=[1, 2, 3], maxgen=9, maxlen=10)Alternatively, the Markov Chain can be initialized with aBraidWordinstead of a list. The BraidWord is wrapped around a list (theword).from braidgenerator import MarkovChain
from braidgenerator import BraidWord
import pandas as pd

mc = MarkovChain(braidword=BraidWord([1, 2, 3]), maxgen=9, maxlen=10)Note that if the Markov Chain is initialized with a list instead of a BraidWord, it will be processed into a BraidWord behind the scenes.To create new braidreps it suffices to call themodelmethod. The methodmodeltakes two parameters,num_braidrepsandmsteps, wherenum_braidrepsis the number of braidreps that are to be generated andmstepsis the number of Markov steps taken in each random walk that produces such a braidrep. Default values are one and 100, respectively. Markov chain steps are comprised of braid relations and ``Markov moves" (in the sense of moves on closed braids).mc.model(num_braidreps=10, msteps=50)Once the modelling is complete, the braidreps and/or logs can be accessed via the following getter methods:MarkovChain.aggregateMarkovChain.logsMarkovChain.braidrepsMarkovChain.topandasMarkovChain.tocsvMarkovChain.totxtMarkovChain.aggregateTheaggregatemethod will return a dictionary with two keys:braidrepsandlogs, wherebybraidrepsis a list that contains the generated braidreps andlogsis a list that contains the relevant Markov steps per iteration and if they were successful or not.This aggregate dictionary is mainly a container for the relevant data and while this getter method is available, it is not recommended for retrieving the data. Better alternative methods exist for this.agg = mc.aggregate()

agg

> {'braidreps': [], 'logs': []}MarkovChain.logsThelogsmethod returns only the logs in the list format. Each element of the log is itself a dictionary that represents all the Markov steps for each braidrep created. Thus, the size ofbraidrepsandlogswill be the same, but each entry will contain a larger set of logs. The example below shows a MarkovChain's logs withnum_braidreps=1andmsteps=5logs = mc.logs()

logs[0]

> {0: 'MoveType: 5, Attempted stabilize: stabilize Succeeded.',
1: 'MoveType: 0, Attempted conjugate: conjugate Succeeded.',
2: 'MoveType: 6, Attempted destabilize: destabilize Failed.',
3: 'MoveType: 3, Attempted transpose: transpose Failed.',
4: 'MoveType: 4, Attempted flip: flip Failed.'}MarkovChain.braidrepsThebraidrepsmethod will return the list with the generated braidreps. This method takes an optional parameteras_wordwhich is set toFalseby default. If left asFalsethen it will return a list of BraidWords, otherwise it will return a list of words (i.e. of type list).# As BraidWords with as_word set to False (default)

mc = MarkovChain(braidword=BraidWord([1, 2, 3]), maxgen=9, maxlen=10)

mc.model(num_braidreps=3, msteps=5)

braidreps_asBraidWord = mc.braidreps()

braidreps_asBraidWord

> [<braidword.BraidWord at 0x114dbe7b8>,
 <braidword.BraidWord at 0x114dbe7f0>,
 <braidword.BraidWord at 0x114dbe588>]# As words with as_word set to True

mc = MarkovChain(braidword=BraidWord([1, 2, 3]), maxgen=9, maxlen=10)

mc.model(num_braidreps=3, msteps=5)

braidreps_asword = mc.braidreps(as_word=True)

braidreps_asword

> [[1, 2, 3], [2, 3, 1], [1, 2]]Note that the braid that was fed into the model is not included. The first word of the second example above is a coincidence resulting from one successful destabilize and one successful stabilize.MarkovChain.topandasThe logs and braidreps can be most easily accessed via thetopandasmethod. To use it, simply importpandasinto the script and call thetopandasmethod. The method takes one optional argument,only_braidreps, which is set toFalseby defualt. If set toTruethen the resulting dataframe will only include the braidreps in word form. Otherwise, each entry will comprise of both the braidrep and the logs associated with it.# only_braidreps set to False (default)
import pandas as pd

mc = MarkovChain(braidword=BraidWord([1, 2, 3]), maxgen=9, maxlen=10)

mc.model(num_braidreps=3, msteps=5)

df = mc.topandas()
df.columns

> Index(['braidreps', 'Logs'], dtype='object')# only_braidreps set to True
import pandas as pd

mc = MarkovChain(braidword=BraidWord([1, 2, 3]), maxgen=9, maxlen=10)

mc.model(num_braidreps=3, msteps=5)

df = mc.topandas(only_braidreps=True)
df.columns

> Index(['braidreps'], dtype='object')MarkovChain.tocsvThetocsvmethod exports the dataframe to a .csv file. The parameters includepath_or_filenameandonly_braidreps, respectively. Similar to thetopandasmethod,only_braidrepsis set toFalseby default. Thepath_or_filenameparameter is more interesting. If a path is not specified, the method will export the requested information to the current directory with either the namebraidreps.csvorbraidreps_and_Logs.csv, which it will implicitly interpret from the argument passed toonly_braidreps.mc = MarkovChain(braidword=BraidWord([1, 2, 3]), maxgen=9, maxlen=10)

mc.model(num_braidreps=3, msteps=5)

path = 'some/path/onyourpc.csv'
mc.tocsv(path_or_filename=path, only_braidreps=False)MarkovChain.totxtThetotxtmethod is similar to thetopandasandtocsvdifference being that it exports the data to a.txtfile in the following format:braidrep[1]
.
.
.
braidrep[n]

log[1]
.
.
.
log[n]As is with totocsv, thetotxthas the two parameterspath_or_filenameandonly_braidreps, respectively.Clearing the model and setting a new BraidWordIf it is desired to use the same model to create a new set of isomoprhs, theclear_modelmethod is required. This method clears thebraidaggcontainer.mc = MarkovChain(braidword=BraidWord([1, 2, 3]), maxgen=9, maxlen=10)

mc.model(num_braidreps=3, msteps=5)

mc.clear_model()

mc.aggregate()

> {'braidreps': [], 'logs': []}Note that if not changed, the subsequent BraidWord will have been mutated. This functionality is left in as it may seem desirable for some situations and is simple enough to undo (i.e. replace the BraidWord with thenew_braidwordmethod).In the event that it is desirable to set a newBraidWordinto an already existing method, thenew_braidwordmethod can be used. A list orBraidWordobject can be passed as an argument.mc.new_braidword([1, 1, 2])mc.new_braidword(BraidWord([1, 2, 3]))As is the case with the initializor, if a list is passed in as an argument it will be converted to a BraidWord behind the scenes.Bonus BraidWord functionalitycrossing_changeThecrossing_changemethod performs a crossing change on a BraidWord. That is, it returns a new BraidWord whereby
the generator at a specified index is inverted.The resulting index can be set randomly withrandom_indexset toTrue, or it can be set manually by settingrandom_index=Falseand assigningindexwith an integer value between zero and the length of the word of the BraidWord.from braidgenerator import BraidWord

braidword = BraidWord([1, 2, 3])

# Randomly set generator to be inverted
random_newbraidword = braidword.crossing_change(random_index=True)

# Manually set generator to be inverted
manual_newbraidword = braidword.crossing_change(random_index=False, index=1)

random_newbraidword.word
>>> [1, 2, -3]

manual_newbraidword.word
>>> [1, -2, 3]resolutionTheresolutionmethod performs a resolution on a BraidWord. That is, it returns a new BraidWord whereby
the generator at a specified index is removed and the new BraidWord's length is decreased by one.The resulting index can be set randomly withrandom_indexset toTrue, or it can be set manually by settingrandom_index=Falseand assigningindexwith an integer value between zero and the length of the word of the BraidWord.from braidgenerator import BraidWord

braidword = BraidWord([1, 2, 3])

# Randomly set generator to be removed
random_newbraidword = braidword.resolution(random_index=True)

# Manually set generator to be removed
manual_newbraidword = braidword.resolution(random_index=False, index=1)

random_newbraidword.word
>>> [2, 3]

manual_newbraidword.word
>>> [1, 3]Running the testsTests for these scripts are included in the github repository under the paths shown below..
|
└───tests
        test_braidword.py
        test_markovchain.pyBreakdown of testsThe given tests use Pythons UnitTest package for testing.test_braidword.pycontains over 350 lines of tests with 30+ test cases (at the time of writting this readme). The test cases contain suites that test for initialization, boolean helper functions (such ascanDestabilize,canFlip, etc...), and theBraidWordmethods (such asDestabilize,Flip, etc...). Examples oftest_braidword.pyinclude:def test_init_pathfail_0(self):
    '''
    Should raise TypeError if initword
    is not a list (args)
    '''
    with self.assertRaises(TypeError) as te:
        bw = BraidWord(1)def test_canCancel_pathfail(self):
    """Should return False when adjacent
    generators are not inverses of one other"""
    bw = BraidWord([1, 2, 3, -3])
    # Execution path False
    self.assertFalse(bw.canCancel(0))def test_cancel_pathsuccess(self):
    """Should successfully cancel gen @idx 3
    and should modify word"""
    bw = BraidWord([-1, 2, 3, 1])
    # Execution path True
    self.assertTrue(bw.cancel(3))
    # Word modification
    self.assertEqual(bw.word, [2, 3])test_markovchain.pycontains roughly 200 lines of tests with 18+ test cases (at the time of writing this readme). The test cases contain suites that test mainly for initialization and that containers, such aslogs,braidrepisms, andbraidagg, vis-a-vis theaggregate,logs, andbraidrepsmethods, as well as exporting methods, such astocsv,topandas, andtotxt, are valid (i.e. not empty). The lack of automated tests for said script is due to the probabilistic nature of the results of themodelmethod. Results for this method may be tested by hand or an auxiliary program, such as ``KnotPlot." Examples oftest_markovchain.pyinclude:def test_init_pathfail_0(self):
    """Should throw error if BraidWord not given"""
    with self.assertRaises(Exception) as te:
        MarkovChain()def test_aggregate(self):
    '''
    Should return non-empty self.braidagg
    '''
    mc = MarkovChain(BraidWord([1, 2, 3]))
    mc.model(num_braidreps=1, msteps=10)
    # Check if non-empty
    self.assertTrue(mc.aggregate())def test_logs(self):
    '''
    Should return non-empty self.braidagg.logs
    '''
    mc = MarkovChain(BraidWord([1, 2, 3]))
    mc.model(num_braidreps=1, msteps=10)
    # Check if non-empty
    self.assertTrue(mc.logs())BraidWord'scrossing_changeandresolutionmethods follow similar formats and can be viewed in thetests\test_braidwordmodule in the repository.DocumentationFor more information about methods and code fragments, please refer to thedocumentation.ContributingPlease readCONTRIBUTING.mdfor details on our code of conduct, and the process for submitting pull requests to us.VersioningWe useSemVerfor versioning. For the versions available, see thetags on this repository.AuthorsAllison Moore-Initial work-allisonhmooreMilad Michael Nasrollahi-Initial work-Algebra8Shawn Witte-Initial work-MinirogueLicenseThis project is licensed under the MIT License - see theLICENSE.mdfile for details.AcknowledgementsThis package makes use ofpandasas a requirement. Please refer to thepandaslicensefor more information on their license.The CONTRIBUTING.md content was adapted from PurpleBooth'sGood-CONTRIBUTING.md-template.md.A. Moore and S. Witte acknowledge the partial support of NSF grant DMS-1716987.