├── .flake8 ├── .github ├── dependabot.yml └── workflows │ ├── build-and-deploy.yml │ └── test.yml ├── .gitignore ├── .isort.cfg ├── .pre-commit-config.yaml ├── .pylintrc ├── .zenodo.json ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.rst ├── mypy.ini ├── permuta ├── __init__.py ├── bisc │ ├── __init__.py │ ├── bisc.py │ ├── bisc_subfunctions.py │ └── perm_properties.py ├── cli.py ├── enumeration_strategies │ ├── __init__.py │ ├── abstract_strategy.py │ ├── core_strategies.py │ ├── finitely_many_simples.py │ └── insertion_encodable.py ├── misc │ ├── __init__.py │ ├── display.py │ ├── math.py │ └── union_find.py ├── patterns │ ├── __init__.py │ ├── bivincularpatt.py │ ├── meshpatt.py │ ├── patt.py │ └── perm.py ├── perm_sets │ ├── __init__.py │ ├── basis.py │ └── permset.py ├── permutils │ ├── __init__.py │ ├── bijections.py │ ├── finite.py │ ├── groups.py │ ├── insertion_encodable.py │ ├── pin_words.py │ ├── pinword_util.py │ ├── polynomial.py │ ├── statistics.py │ └── symmetry.py ├── py.typed └── resources │ └── bisc │ ├── Baxter_bad_len8.json │ ├── Baxter_good_len8.json │ ├── SimSun_bad_len8.json │ ├── SimSun_bad_len9.json │ ├── SimSun_good_len8.json │ ├── SimSun_good_len9.json │ ├── West_2_stack_sortable_bad_len8.json │ ├── West_2_stack_sortable_good_len8.json │ ├── av_231_and_mesh_bad_len8.json │ ├── av_231_and_mesh_bad_len9.json │ ├── av_231_and_mesh_good_len8.json │ ├── av_231_and_mesh_good_len9.json │ ├── dihedral_bad_len8.json │ ├── dihedral_good_len8.json │ ├── forest_like_bad_len8.json │ ├── forest_like_good_len8.json │ ├── in_alternating_group_bad_len8.json │ ├── in_alternating_group_good_len8.json │ ├── quick_sortable_bad_len8.json │ ├── quick_sortable_good_len8.json │ ├── smooth_bad_len8.json │ ├── smooth_good_len8.json │ ├── stack_sortable_bad_len8.json │ ├── stack_sortable_good_len8.json │ ├── yt_perm_avoids_22_bad_len8.json │ ├── yt_perm_avoids_22_good_len8.json │ ├── yt_perm_avoids_32_bad_len8.json │ └── yt_perm_avoids_32_good_len8.json ├── pyproject.toml ├── setup.py ├── tests ├── __init__.py ├── bisc │ ├── __init__.py │ ├── test_bisc.py │ ├── test_bisc_subfunctions.py │ └── test_perm_properties.py ├── enumeration_strategies │ ├── test_core_strategies.py │ └── test_enumeration_strategies.py ├── misc │ ├── __init__.py │ └── test_union_find.py ├── patterns │ ├── test_bivincular.py │ ├── test_meshpatt.py │ └── test_perm.py ├── perm_sets │ ├── __init__.py │ ├── test_av.py │ └── test_basis.py └── permutils │ ├── __init__.py │ ├── test_bijections.py │ ├── test_finite.py │ ├── test_function_imports.py │ ├── test_groups.py │ ├── test_insertion_encodable.py │ ├── test_pinwords.py │ ├── test_polynomial.py │ ├── test_stats.py │ └── test_symmetry.py └── tox.ini /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | extend-ignore = E203 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/build-and-deploy.yml: -------------------------------------------------------------------------------- 1 | name: build-and-deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build-and-deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-python@v2 14 | with: 15 | python-version: "3.13" 16 | - name: install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install setuptools wheel twine 20 | - name: build 21 | run: python setup.py sdist 22 | - name: deploy 23 | env: 24 | TWINE_USERNAME: __token__ 25 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 26 | run: twine upload dist/* 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | test: 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | include: 12 | - python: "3.13" 13 | toxenv: flake8 14 | os: ubuntu-latest 15 | - python: "3.13" 16 | toxenv: mypy 17 | os: ubuntu-latest 18 | - python: "3.13" 19 | toxenv: pylint 20 | os: ubuntu-latest 21 | - python: "3.13" 22 | toxenv: black 23 | os: ubuntu-latest 24 | 25 | - python: "3.10" 26 | toxenv: py310 27 | os: ubuntu-latest 28 | - python: "3.11" 29 | toxenv: py311 30 | os: ubuntu-latest 31 | - python: "3.12" 32 | toxenv: py312 33 | os: ubuntu-latest 34 | - python: "3.13" 35 | toxenv: py313 36 | os: ubuntu-latest 37 | 38 | - python: "pypy3.10" 39 | toxenv: pypy310 40 | os: ubuntu-latest 41 | - python: "pypy3.11" 42 | toxenv: pypy311 43 | os: ubuntu-latest 44 | 45 | - python: "3.13" 46 | toxenv: py313 47 | os: macos-latest 48 | - python: "3.11" 49 | toxenv: py313 50 | os: windows-latest 51 | 52 | runs-on: ${{ matrix.os }} 53 | steps: 54 | - uses: actions/checkout@v4 55 | - uses: actions/setup-python@v5 56 | with: 57 | python-version: ${{ matrix.python }} 58 | - name: install dependencies 59 | run: python -m pip install --upgrade pip tox setuptools wheel twine 60 | - name: run 61 | env: 62 | TOXENV: ${{ matrix.toxenv }} 63 | run: tox 64 | - name: setup 65 | run: python setup.py install 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dfa_db/* 2 | .mypy_cache 3 | # Mac stuff 4 | *.DS_Store 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | env/ 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *,cover 52 | .hypothesis/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # PyCharm stuff 59 | *.xml 60 | *.iml 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | docs/build/* 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # celery beat schedule file 87 | celerybeat-schedule 88 | 89 | # dotenv 90 | .env 91 | 92 | # virtualenv 93 | .venv/ 94 | venv/ 95 | ENV/ 96 | 97 | # Spyder project settings 98 | .spyderproject 99 | 100 | # Rope project settings 101 | .ropeproject 102 | 103 | # Pytest cache 104 | .pytest_cache/ 105 | 106 | # vscode settings 107 | .vscode/ -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | default_section = THIRDPARTY 3 | multi_line_output=3 4 | include_trailing_comma=True 5 | force_grid_wrap=0 6 | use_parentheses=True 7 | line_length=88 8 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 22.10.0 4 | hooks: 5 | - id: black 6 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | # This is a temporary .pylintrc file during refactoring. 2 | 3 | [MASTER] 4 | ignore-patterns=test_.*?py, 5 | # TODO: REMOVE 6 | bisc.py, 7 | bisc_subfunctions.py 8 | ignore= tests 9 | init-hook="import os, sys; sys.path.append(os.path.dirname(os.path.abspath('.')) + '/permuta')" 10 | disable=missing-module-docstring, 11 | fixme, 12 | unsubscriptable-object, 13 | unspecified-encoding 14 | good-names=i,j,n,k,x,y,_ 15 | 16 | [SIMILARITIES] 17 | ignore-imports=yes -------------------------------------------------------------------------------- /.zenodo.json: -------------------------------------------------------------------------------- 1 | { 2 | "upload_type": "software", 3 | "title": "Permuta: A Python Library for Permutations and Patterns", 4 | "creators": [ 5 | { 6 | "name": "Ragnar Pall Ardal" 7 | }, 8 | { 9 | "name": "Arnar Bjarni Arnarson" 10 | }, 11 | { 12 | "affiliation": "Reykjavik University", 13 | "name": "Christian Bean" 14 | }, 15 | { 16 | "name": "Alfur Birkir Bjarnason" 17 | }, 18 | { 19 | "affiliation": "Reykjavik University", 20 | "name": "Jon Steinn Eliasson" 21 | }, 22 | { 23 | "name": "Bjarki Agust Gudmundsson" 24 | }, 25 | { 26 | "affiliation": "Reykjavik University", 27 | "name": "Sigurjón Ingi Jónsson" 28 | } 29 | { 30 | "name": "Bjarni Jens Kristinsson" 31 | }, 32 | { 33 | "name": "Tomas Ken Magnusson" 34 | }, 35 | { 36 | "affiliation": "Reykjavik University", 37 | "name": "Émile Nadeau" 38 | }, 39 | { 40 | "affiliation": "Marquette University", 41 | "name": "Jay Pantone" 42 | }, 43 | { 44 | "name": "Murray Tannock" 45 | }, 46 | { 47 | "affiliation": "Reykjavik University", 48 | "name": "Henning Ulfarsson" 49 | }, 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 2.3.0 - 2024-04-03 10 | ### Changed 11 | - Updated dependency on automata-lib to version 7.0.1 12 | - Perm.avoids/contains will raise a TypeError if input is not an iterable of Patt 13 | 14 | ### Added 15 | - Now testing on Python 3.11 16 | - 'simple' to the permtool command for checking if finitely many simples in a class 17 | 18 | ### Fixed 19 | - bug in autobisc during setup 20 | 21 | ## 2.2.0 - 2021-10-21 22 | ### Added 23 | - Bijection class for known bijection. 24 | - An implementation of the Simion and Schmidt bijection. 25 | - Enumeration strategy to check whether a class has finitely many simple permutations 26 | 27 | ### Changed 28 | - Perm.to_standard now uses an lru cache 29 | 30 | ## 2.1.0 - 2021-06-14 31 | ### Added 32 | - Statistic: bounce of a permutation. 33 | - Statistic: maximum drop size. 34 | - Statistic: number of primes in the column sums. 35 | - Statistic: holeyness of a permutation. 36 | - Algorithm: `pop stack sort`. 37 | - Statistic: count stack sorts. 38 | - Statistic: count pop stack sorts. 39 | - Statistic: Pinnacle set and number of pinnacles. 40 | 41 | ### Changed 42 | - Functions for ascents and descents now take an optional argument to specify what step size to calculate. 43 | - Moved sorting functions from `permuta/bisc/perm_properties.py` to `permuta/patterns/perm.py`. 44 | - If you pass an iterable (that is not a perm) to a contains method, then it will now raise an error. 45 | These should be passed with the splat '*'. 46 | 47 | ## 2.0.3 - 2021-04-28 48 | ### Added 49 | - using Github Actions for testing and deployment 50 | - `containment_to_tikz` method in `Perm` that returns an iterator of tikz pictures 51 | displaying occurrences of classical patterns in self. 52 | - `permuta.permutils.PermutationStatistic` to check statistic distribution in 53 | classes and preservation and transformation in bijections. 54 | 55 | ### Deprecated 56 | - Python 3.6 is no longer supported 57 | 58 | ## 2.0.2 - 2020-08-06 59 | ### Fixed 60 | - Include the type hints in the pypi release. 61 | 62 | ## 2.0.1 - 2020-07-23 63 | ### Fixed 64 | - Typing for `apply` in `Perm` fixed. It is now of the same base type as argument. 65 | 66 | ## 2.0.0 - 2020-07-20 67 | ### Added 68 | - Two new tools added to permtools. A command to check if a class has a regular 69 | insertion encoding, and a command to compute the lexicographically minimal 70 | basis. 71 | - Typing 72 | - pylint 73 | - `clear_cache` method in `Perm` and `Av` 74 | - `up_to_length`, `of_length`, `first` iterators in Perm and Av 75 | - `to_svg` for all patterns 76 | - `show` method for all patterns (opens browser tab) 77 | - Functions returning list (or other data structures) made into generators when possible 78 | - `BivincularPatt`, `VincularPatt`, `CovincularPatt` patterns, 79 | - `dihedral_group` generator added to `permutils` 80 | - `from_string` method to `Basis` and `Av`. It accepts both 0 and 1 based perms 81 | seperated by anything 82 | - Check if polynomial added to `cli`, which can be used with the `poly` command 83 | 84 | ### Fixed 85 | - Bisc's resource files now included with pypi package 86 | 87 | ### Changed 88 | - Type and condition checking and Exception throwing changed to assertions 89 | - `Basis` moved to `permset` module 90 | - `gen_meshpatt` moved to meshpatt as `of_length` generator 91 | - Client now uses `Basis.from_string` to parse basis 92 | 93 | ### Removed 94 | - Permsets and their interfaces 95 | - Unused algorithms and utils 96 | - Symmetric interfaces 97 | - All rotate function other than `rotate` 98 | - `descriptors` module 99 | - sympy dependency 100 | 101 | ## 1.5.0 - 2020-06-23 102 | ## Added 103 | - A quick command line interface to compute the enumeration of a permutation class. 104 | - `Perm.skew_decomposition` and `Perm.sum_decomposition` methods. 105 | 106 | ## 1.4.2 - 2020-06-17 107 | ### Fixed 108 | - Make `permuta.bisc.permsets` a proper package. 109 | 110 | ## 1.4.1 - 2020-06-12 111 | ### Removed 112 | - The unused `permuta.misc.misc` module 113 | 114 | ### Fixed 115 | - Installation on windows 116 | 117 | ## 1.4.0 - 2020-05-11 118 | ### Added 119 | - The BiSC algorithm that can tell you what mesh patterns are avoided by a set 120 | of permutations. 121 | 122 | ### Changed 123 | - Updated `__str__` method of `Av` and `MeshPatt`. 124 | - Introduce a more efficient algorithm to build permutation in a permutation 125 | class. 126 | 127 | ### Fixed 128 | - `Av([])` returns `PermSetAll()` 129 | 130 | ### Removed 131 | - Support for Python 3.5 and earlier 132 | 133 | ## [1.3.0] - 2019-12-16 134 | ### Added 135 | - `MeshPatt`s are now comparable (i.e. a mesh patt is always less then, 136 | equal to or greater than another mesh patt) and therefore sortable 137 | - Added enumeration strategies that can point out to useful results to find the 138 | enumeration of a permutation class. 139 | 140 | ## [1.2.1] - 2019-09-10 141 | ### Fixed 142 | - Allow for a mix of permutation and mesh patterns in MeshBasis 143 | 144 | ## [1.2.0] - 2019-09-05 145 | ### Added 146 | - The `occurences_in` method of permutation can can handle coloured 147 | permutations. 148 | - Support for containment of mesh pattern in a mesh pattern. 149 | 150 | ## [1.1.0] - 2019-08-26 151 | ### Added 152 | - The ascii_plot, and to_tikz method in MeshPatt 153 | - is_subclass method in Av 154 | - Support for avoidance of mesh patterns with `Av` 155 | ### Removed 156 | - The broken latex method in MeshPatt 157 | ### Fixed 158 | - Wrong examples in the README. README.rst is now tested 159 | 160 | ## [1.0.0] - 2019-04-15 161 | ### Added 162 | - Made master branch deploy to PyPi. 163 | - Added testing for Python 3.7 and 3.8. 164 | - Added a from integer method, for creating a Perm from integer. 165 | - Added inversions and non-inversions function that yield pairs. 166 | ### Changed 167 | - Updated repr and str methods to Av, PermSetAll and PermSetStatic. 168 | - The string of a Perm is now one-line notation. 169 | - Can no longer initialise Perm with an integer. 170 | ### Removed 171 | - The demo. 172 | - Broken plot function 173 | - Support for Python 3.4 and earlier. 174 | ### Fixed 175 | - The ascii plot, and to_tikz method in Perm. 176 | - Bug in polynomial checker. 177 | 178 | ## [0.1.1] - 2017-03-05 179 | ### Added 180 | - Readme was rewritten in ReST. 181 | - Classifiers and python versions added to setup.py. 182 | 183 | ## [0.1.0] - 2017-03-05 184 | ### Added 185 | - This CHANGELOG file. 186 | - Package added to PYPI 187 | - Tests passing. 188 | - Conforming to PEP8. 189 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Henning Ulfarsson 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include permuta/py.typed 2 | include permuta/resources/bisc/*.json 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ####### 2 | permuta 3 | ####### 4 | 5 | .. image:: https://travis-ci.org/PermutaTriangle/Permuta.svg?branch=master 6 | :alt: Travis 7 | :target: https://travis-ci.org/PermutaTriangle/Permuta 8 | .. image:: https://img.shields.io/pypi/v/Permuta.svg 9 | :alt: PyPI 10 | :target: https://pypi.python.org/pypi/Permuta 11 | .. image:: https://img.shields.io/pypi/l/Permuta.svg 12 | :target: https://pypi.python.org/pypi/Permuta 13 | .. image:: https://img.shields.io/pypi/pyversions/Permuta.svg 14 | :target: https://pypi.python.org/pypi/Permuta 15 | .. image:: http://img.shields.io/badge/readme-tested-brightgreen.svg 16 | :alt: Travis 17 | :target: https://travis-ci.org/PermutaTriangle/Permuta 18 | .. image:: https://requires.io/github/PermutaTriangle/Permuta/requirements.svg?branch=master 19 | :target: https://requires.io/github/PermutaTriangle/Permuta/requirements/?branch=master 20 | :alt: Requirements Status 21 | .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.4725758.svg 22 | :target: https://doi.org/10.5281/zenodo.4725758 23 | 24 | Permuta is a Python library for working with perms (short for permutations), 25 | patterns, and mesh patterns. 26 | 27 | If this code is useful to you in your work, please consider citing it. To generate a 28 | BibTeX entry (or another format), click the "DOI" badge above and locate the "Cite As" 29 | section. 30 | 31 | If you need support, you can join us in our `Discord support server`_. 32 | 33 | .. _Discord support server: https://discord.gg/ngPZVT5 34 | 35 | Installing 36 | ========== 37 | 38 | To install Permuta on your system, run: 39 | 40 | .. code-block:: bash 41 | 42 | pip install permuta 43 | 44 | It is also possible to install Permuta in development mode to work on the 45 | source code, in which case you run the following after cloning the repository: 46 | 47 | .. code-block:: bash 48 | 49 | ./setup.py develop 50 | 51 | To run the unit tests: 52 | 53 | .. code-block:: bash 54 | 55 | pip install -r test_requirements.txt 56 | ./setup.py test 57 | 58 | Using Permuta 59 | ############# 60 | 61 | Once you've installed Permuta, it can be imported by a Python script or an 62 | interactive Python session, just like any other Python library: 63 | 64 | .. code-block:: python 65 | 66 | >>> from permuta import * 67 | 68 | Importing ``*`` supplies you with the 'Perm' and 'PermSet' 69 | classes along with the 'AvoidanceClass' class (with alias 'Av') for generating 70 | perms avoiding a set of patterns. It also gives you the 'MeshPatt' class 71 | and some other submodules which we will not discuss in this readme. 72 | 73 | Creating a single perm 74 | ###################### 75 | 76 | Permutations are zero-based in Permuta and can be created using any iterable. 77 | 78 | .. code-block:: python 79 | 80 | >>> Perm() # Empty perm 81 | Perm(()) 82 | >>> Perm([]) # Another empty perm 83 | Perm(()) 84 | >>> Perm((0, 1, 2, 3)) # The zero-based version of 1234 85 | Perm((0, 1, 2, 3)) 86 | >>> Perm((2, 1, 3)) # Warning: it will initialise with any iterable 87 | Perm((2, 1, 3)) 88 | 89 | Permutations can also be created using some specific class methods. 90 | 91 | .. code-block:: python 92 | 93 | >>> Perm.from_string("201") # strings 94 | Perm((2, 0, 1)) 95 | >>> Perm.one_based((1, 3, 2, 4)) # one-based iterable of integers 96 | Perm((0, 2, 1, 3)) 97 | >>> Perm.to_standard("a2gsv3") # standardising any iterable using '<' 98 | Perm((2, 0, 3, 4, 5, 1)) 99 | >>> Perm.from_integer(210) # an integer between 0 and 9876543210 100 | Perm((2, 1, 0)) 101 | >>> Perm.from_integer(321) # any integer given is standardised 102 | Perm((2, 1, 0)) 103 | >>> Perm.from_integer(201) 104 | Perm((2, 0, 1)) 105 | 106 | Printing perms gives zero-based strings. 107 | 108 | .. code-block:: python 109 | 110 | >>> print(Perm(())) 111 | ε 112 | >>> print(Perm((2, 1, 0))) 113 | 210 114 | >>> print(Perm((6, 2, 10, 9, 3, 8, 0, 1, 5, 11, 4, 7))) 115 | (6)(2)(10)(9)(3)(8)(0)(1)(5)(11)(4)(7) 116 | 117 | To get an iterator of all permutations of a certain length you can use 118 | 119 | .. code-block:: python 120 | 121 | >>> Perms4 = Perm.of_length(4) 122 | 123 | You can run a for-loop over this iterator if you need to do something with all 124 | the permutations of this size. If you just want a specific permutation of this 125 | size you might be better off using the unrank function. 126 | 127 | .. code-block:: python 128 | 129 | >>> Perm.unrank(23,4) 130 | Perm((3, 2, 1, 0)) 131 | 132 | The avoids, contains, and occurrence methods enable working with patterns: 133 | 134 | .. code-block:: python 135 | 136 | >>> p = Perm((0,2,1,3)) 137 | >>> p.contains(Perm((2, 1, 0))) 138 | False 139 | >>> p.avoids(Perm((0, 1))) 140 | False 141 | >>> list(p.occurrences_of(Perm((1, 0)))) 142 | [(1, 2)] 143 | >>> list(Perm((0, 1)).occurrences_in(p)) 144 | [(0, 1), (0, 2), (0, 3), (1, 3), (2, 3)] 145 | 146 | The basic symmetries are implemented: 147 | 148 | .. code-block:: python 149 | 150 | >>> [p.reverse(), p.complement(), p.inverse()] 151 | [Perm((3, 1, 2, 0)), Perm((3, 1, 2, 0)), Perm((0, 2, 1, 3))] 152 | 153 | To take direct sums and skew sums we use ``+`` and ``-``: 154 | 155 | .. code-block:: python 156 | 157 | >>> q = Perm((0, 1, 2, 3, 4)) 158 | >>> p + q 159 | Perm((0, 2, 1, 3, 4, 5, 6, 7, 8)) 160 | >>> p - q 161 | Perm((5, 7, 6, 8, 0, 1, 2, 3, 4)) 162 | 163 | There are numerous practical methods available: 164 | 165 | .. code-block:: python 166 | 167 | >>> list(p.fixed_points()) 168 | [0, 3] 169 | >>> list(p.ascents()) 170 | [0, 2] 171 | >>> list(p.descents()) 172 | [1] 173 | >>> list(p.inversions()) 174 | [(1, 2)] 175 | >>> p.major_index() 176 | 2 177 | 178 | Creating a perm class 179 | ##################### 180 | 181 | Perm classes are created by first specifying a basis and then calling the 'Av' class, to create the set of permutations avoiding the basis: 182 | 183 | .. code-block:: python 184 | 185 | >>> basis = Basis(Perm((1, 0, 2)), Perm((1, 2, 0))) 186 | >>> basis 187 | Basis((Perm((1, 0, 2)), Perm((1, 2, 0)))) 188 | >>> perm_class = Av(basis) 189 | >>> perm_class 190 | Av(Basis((Perm((1, 0, 2)), Perm((1, 2, 0))))) 191 | 192 | You can ask whether a perm belongs to the perm class: 193 | 194 | .. code-block:: python 195 | 196 | >>> Perm((3, 2, 1, 0)) in perm_class 197 | True 198 | >>> Perm((0, 2, 1, 3)) in perm_class 199 | False 200 | 201 | You can get its enumeration up to a fixed length. 202 | 203 | .. code-block:: python 204 | 205 | >>> perm_class.enumeration(10) 206 | [1, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512] 207 | >>> perm_class.count(11) 208 | 1024 209 | 210 | You can also look to see if some well know enumeration strategies apply to a 211 | given class. 212 | 213 | .. code-block:: python 214 | 215 | >>> from permuta.enumeration_strategies import find_strategies 216 | >>> basis = [Perm((3, 2, 0, 1)), Perm((1, 0, 2, 3))] 217 | >>> for strat in find_strategies(basis): 218 | ... print(strat.reference()) 219 | The insertion encoding of permutations: Corollary 10 220 | >>> basis = [Perm((1, 2, 0, 3)), Perm((2, 0, 1, 3)), Perm((0, 1, 2, 3))] 221 | >>> for strat in find_strategies(basis): 222 | ... print(strat.reference()) 223 | Enumeration of Permutation Classes and Weighted Labelled Independent Sets: Corollary 4.3 224 | >>> basis = [Perm((1, 3, 0, 2)), Perm((2, 0, 3, 1))] 225 | >>> for strat in find_strategies(basis): 226 | ... print(strat.reference()) 227 | Enumeration of Permutation Classes and Weighted Labelled Independent Sets: Corollary 4.6 228 | The class contains only finitely many simple permutations 229 | 230 | The output is the name of a paper, followed by the statement in the paper where the enumeration strategy is discussed or stated. 231 | 232 | Permutation statistics 233 | ###################### 234 | 235 | With the ``PermutationStatistic`` class we can look for distributions of statistics for 236 | classes and look for statistics preservations (or transformation) either for two classes 237 | or given a bijection. First we need to import it. 238 | 239 | .. code-block:: python 240 | 241 | >>> from permuta.permutils.statistics import PermutationStatistic 242 | 243 | To see a distribution for a given statistic we grab its instance and provide a length 244 | and a class (no class will use the set of all permutations). 245 | 246 | .. code-block:: python 247 | 248 | >>> PermutationStatistic.show_predefined_statistics() # Show all statistics with id 249 | [0] Number of inversions 250 | [1] Number of non-inversions 251 | [2] Major index 252 | [3] Number of descents 253 | [4] Number of ascents 254 | [5] Number of peaks 255 | [6] Number of valleys 256 | [7] Number of cycles 257 | [8] Number of left-to-right minimas 258 | [9] Number of left-to-right maximas 259 | [10] Number of right-to-left minimas 260 | [11] Number of right-to-left maximas 261 | [12] Number of fixed points 262 | [13] Order 263 | [14] Longest increasing subsequence 264 | [15] Longest decreasing subsequence 265 | [16] Depth 266 | [17] Number of bounces 267 | [18] Maximum drop size 268 | [19] Number of primes in the column sums 269 | [20] Holeyness of a permutation 270 | [21] Number of stack-sorts needed 271 | [22] Number of pop-stack-sorts needed 272 | [23] Number of pinnacles 273 | [24] Number of cyclic peaks 274 | [25] Number of cyclic valleys 275 | [26] Number of double excedance 276 | [27] Number of double drops 277 | [28] Number of foremaxima 278 | [29] Number of afterminima 279 | [30] Number of aftermaxima 280 | [31] Number of foreminima 281 | 282 | >>> depth = PermutationStatistic.get_by_index(16) 283 | >>> depth.distribution_for_length(5) 284 | [1, 4, 12, 24, 35, 24, 20] 285 | >>> depth.distribution_up_to(4, Av.from_string("123")) 286 | [[1], [1], [1, 1], [0, 2, 3], [0, 0, 3, 7, 4]] 287 | 288 | Given a bijection as a dictionary, we can check which statistics are preserved with 289 | ``check_all_preservations`` and which are transformed with ``check_all_transformed`` 290 | 291 | .. code-block:: python 292 | 293 | >>> bijection = {p: p.reverse() for p in Perm.up_to_length(5)} 294 | >>> for stat in PermutationStatistic.check_all_preservations(bijection): 295 | ... print(stat) 296 | Number of peaks 297 | Number of valleys 298 | Holeyness of a permutation 299 | Number of pinnacles 300 | 301 | We can find all (predefined) statistics equally distributed over two permutation 302 | classes with ``equally_distributed``. We also support checks for joint distribution 303 | of more than one statistics with ``jointly_equally_distributed`` and transformation 304 | of jointly distributed stats with ``jointly_transformed_equally_distributed``. 305 | 306 | .. code-block:: python 307 | 308 | >>> cls1 = Av.from_string("2143,415263") 309 | >>> cls2 = Av.from_string("3142") 310 | >>> for stat in PermutationStatistic.equally_distributed(cls1, cls2, 6): 311 | ... print(stat) 312 | Major index 313 | Number of descents 314 | Number of ascents 315 | Number of peaks 316 | Number of valleys 317 | Number of left-to-right minimas 318 | Number of right-to-left maximas 319 | Longest increasing subsequence 320 | Longest decreasing subsequence 321 | Number of pinnacles 322 | 323 | The BiSC algorithm 324 | ================== 325 | 326 | The BiSC algorithm can tell you what mesh patterns are avoided by a set of 327 | permutations. Although the output of the algorithm is only guaranteed to 328 | describe the finite inputted set of permutations, the user usually hopes that 329 | the patterns found by the algorithm describe an infinite set of permutatations. 330 | To use the algorithm we first need to import it. 331 | 332 | .. code-block:: python 333 | 334 | >>> from permuta.bisc import * 335 | 336 | A classic example of a set of permutations described by pattern avoidance are 337 | the permutations sortable in one pass through a stack. We use the function 338 | ``stack_sortable`` which returns ``True`` for permutations that satisfy this 339 | property. The user now has two choices: Run 340 | ``auto_bisc(Perm.stack_sortable)`` and let the algorithm run 341 | without any more user input. It will try to use sensible values, starting by 342 | learning small patterns from small permutations, and only considering longer 343 | patterns when that fails. If the user wants to have more control over what 344 | happens that is also possible and we now walk through that: We input the 345 | property into ``bisc`` and ask it to search for patterns of length 3. 346 | 347 | .. code-block:: python 348 | 349 | >>> bisc(Perm.stack_sortable, 3) 350 | I will use permutations up to length 7 351 | {3: {Perm((1, 2, 0)): [set()]}} 352 | 353 | When this command is run without specifying what length of permutations you 354 | want to consider, ``bisc`` will create permutations up to length 7 that satisfy 355 | the property of being stack-sortable. The output means: There is a single 356 | length 3 pattern found, and its underlying classical pattern is the permutation 357 | ``Perm((1, 2, 0))``. Ignore the ``[set()]`` in the output for now. We can use 358 | ``show_me`` to get a better visualization of the patterns found. In this call 359 | to the algorithm we also specify that only permutations up to length 5 should 360 | be considered. 361 | 362 | .. code-block:: python 363 | 364 | >>> SG = bisc(Perm.stack_sortable, 3, 5) 365 | >>> show_me(SG) 366 | There are 1 underlying classical patterns of length 3 367 | There are 1 different shadings on 120 368 | The number of sets to monitor at the start of the clean-up phase is 1 369 | 370 | Now displaying the patterns 371 | 372 | | | | 373 | -+-●-+- 374 | | | | 375 | -●-+-+- 376 | | | | 377 | -+-+-●- 378 | | | | 379 | 380 | 381 | We should ignore the ``The number of sets to monitor at the start of the clean-up phase 382 | is 1`` message for now. 383 | 384 | We do not really need this algorithm for sets of permutations described by the 385 | avoidance of classical patterns. Its main purpose is to describe sets with mesh 386 | patterns, such as the West-2-stack-sortable permutations 387 | 388 | .. code-block:: python 389 | 390 | >>> SG = bisc(Perm.west_2_stack_sortable, 5, 7) 391 | >>> show_me(SG) 392 | There are 2 underlying classical patterns of length 4 393 | There are 1 different shadings on 1230 394 | There are 1 different shadings on 2130 395 | The number of sets to monitor at the start of the clean-up phase is 1 396 | There are 1 underlying classical patterns of length 5 397 | There are 1 different shadings on 42130 398 | 399 | Now displaying the patterns 400 | 401 | | | | | 402 | -+-+-●-+- 403 | | | | | 404 | -+-●-+-+- 405 | | | | | 406 | -●-+-+-+- 407 | | | | | 408 | -+-+-+-●- 409 | | | | | 410 | 411 | |▒| | | 412 | -+-+-●-+- 413 | | | | | 414 | -●-+-+-+- 415 | | | | | 416 | -+-●-+-+- 417 | | | | | 418 | -+-+-+-●- 419 | | | | | 420 | 421 | |▒| | | | 422 | -●-+-+-+-+- 423 | | |▒| | | 424 | -+-+-+-●-+- 425 | | | | | | 426 | -+-●-+-+-+- 427 | | | | | | 428 | -+-+-●-+-+- 429 | | | | | | 430 | -+-+-+-+-●- 431 | | | | | | 432 | 433 | 434 | This is good news and bad news. Good because we quickly got a description of the 435 | set we were looking at, that would have taken a long time to find by hand. The bad news 436 | is that there is actually some redundancy in the output. To understand better what is 437 | going on we will start by putting the permutations under investigation in a dictionary, 438 | which keeps them separated by length. 439 | 440 | .. code-block:: python 441 | 442 | >>> A, B = create_bisc_input(7, Perm.west_2_stack_sortable) 443 | 444 | This creates two dictionaries with keys 1, 2, ..., 7 such that ``A[i]`` points 445 | to the list of permutations of length ``i`` that are West-2-stack-sortable, and 446 | ``B[i]`` points to the complement. We can pass the A dictionary directly into 447 | BiSC since only the permutations satisfying the property are used to find the 448 | patterns. We can use the second dictionary to check whether every permutation 449 | in the complement contains at least one of the patterns we found. 450 | 451 | .. code-block:: python 452 | 453 | >>> SG = bisc(A, 5, 7) 454 | >>> patterns_suffice_for_bad(SG, 7, B) 455 | Starting sanity check with bad perms 456 | Now checking permutations of length 0 457 | Now checking permutations of length 1 458 | Now checking permutations of length 2 459 | Now checking permutations of length 3 460 | Now checking permutations of length 4 461 | Now checking permutations of length 5 462 | Now checking permutations of length 6 463 | Now checking permutations of length 7 464 | Sanity check passes for the bad perms 465 | (True, []) 466 | 467 | In this case it is true that every permutation in B, up to length 7, contains 468 | at least one of the patterns found. Had that not been the case a list of 469 | permutations would have been outputted (instead of just the empty list). 470 | 471 | Now, we claim that there is actually redundancy in the patterns we found, and 472 | the length 4 mesh patterns should be enough to describe the set. This can occur 473 | and it can be tricky to theoretically prove that one mesh pattern is implied 474 | by another pattern (or a set of others, as is the case here). We use the dictionary 475 | ``B`` again and run 476 | 477 | .. code-block:: python 478 | 479 | >>> bases, dict_numbs_to_patts = run_clean_up(SG, B) 480 | 481 | The bases found have lengths 482 | [2] 483 | 484 | There is one basis of mesh patterns found, with 2 patterns 485 | 486 | .. code-block:: python 487 | 488 | >>> show_me_basis(bases[0], dict_numbs_to_patts) 489 | 490 | Displaying the patterns in the basis 491 | 492 | | | | | 493 | -+-+-●-+- 494 | | | | | 495 | -+-●-+-+- 496 | | | | | 497 | -●-+-+-+- 498 | | | | | 499 | -+-+-+-●- 500 | | | | | 501 | 502 | |▒| | | 503 | -+-+-●-+- 504 | | | | | 505 | -●-+-+-+- 506 | | | | | 507 | -+-●-+-+- 508 | | | | | 509 | -+-+-+-●- 510 | | | | | 511 | 512 | 513 | This is the output we were expecting. There are several other properties of 514 | permutations that can be imported from ``permuta.bisc.perm_properties``, such 515 | as ``smooth``, ``forest-like``, ``baxter``, ``simsun``, ``quick_sortable``, etc. 516 | 517 | Both ``bisc`` and ``auto_bisc`` can accept input in the form of a property, 518 | or a list of permutations (satisfying some property). 519 | 520 | License 521 | ####### 522 | 523 | BSD-3: see the `LICENSE `_ file. 524 | 525 | Citing 526 | ###### 527 | 528 | If you found this library helpful with your research and would like to cite us, 529 | you can use the following `BibTeX`_ or go to `Zenodo`_ for alternative formats. 530 | 531 | .. _BibTex: https://zenodo.org/record/4945792/export/hx#.YImTibX7SUk 532 | 533 | .. _Zenodo: https://doi.org/10.5281/zenodo.4725758 534 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | check_untyped_defs = True 3 | warn_return_any = True 4 | warn_unused_configs = True 5 | warn_no_return = False 6 | files = permuta/**/*.py 7 | 8 | [mypy-automata.*] 9 | ignore_missing_imports = True 10 | -------------------------------------------------------------------------------- /permuta/__init__.py: -------------------------------------------------------------------------------- 1 | from .patterns import BivincularPatt, CovincularPatt, MeshPatt, Perm, VincularPatt 2 | from .perm_sets.permset import Av, Basis, MeshBasis 3 | 4 | __version__ = "2.3.0" 5 | 6 | __all__ = [ 7 | "Perm", 8 | "Av", 9 | "Basis", 10 | "MeshBasis", 11 | "MeshPatt", 12 | "BivincularPatt", 13 | "CovincularPatt", 14 | "VincularPatt", 15 | ] 16 | -------------------------------------------------------------------------------- /permuta/bisc/__init__.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | 3 | from .bisc import auto_bisc, bisc, create_bisc_input, read_bisc_file, write_bisc_files 4 | from .bisc_subfunctions import ( 5 | patterns_suffice_for_bad, 6 | patterns_suffice_for_good, 7 | run_clean_up, 8 | show_me, 9 | show_me_basis, 10 | ) 11 | 12 | __all__ = [ 13 | "auto_bisc", 14 | "bisc", 15 | "create_bisc_input", 16 | "patterns_suffice_for_bad", 17 | "patterns_suffice_for_good", 18 | "read_bisc_file", 19 | "write_bisc_files", 20 | "run_clean_up", 21 | "show_me", 22 | "show_me_basis", 23 | ] 24 | -------------------------------------------------------------------------------- /permuta/bisc/bisc.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | 3 | import json 4 | import os 5 | import types 6 | from collections import defaultdict 7 | 8 | from permuta.bisc.bisc_subfunctions import ( 9 | forb, 10 | mine, 11 | patterns_suffice_for_bad, 12 | patterns_suffice_for_good, 13 | run_clean_up, 14 | show_me, 15 | show_me_basis, 16 | to_sg_format, 17 | ) 18 | from permuta.patterns.perm import Perm 19 | 20 | 21 | def bisc(A, m, n=None, report=False): 22 | if isinstance(A, list): 23 | D = defaultdict(list) 24 | for perm in A: 25 | D[len(perm)].append(perm) 26 | elif isinstance(A, types.FunctionType): 27 | if n is None: 28 | print("I will use permutations up to length 7") 29 | n = 7 30 | D = defaultdict(list) 31 | for i in range(n + 1): 32 | for perm in Perm.of_length(i): 33 | if A(perm): 34 | D[i].append(perm) 35 | 36 | elif isinstance(A, dict): 37 | D = A 38 | 39 | else: 40 | print( 41 | """BiSC can accept inputs: 42 | 1. A list of permutations 43 | 2. A property of permutations 44 | 3. A dictionary of lengths pointing to permutations""" 45 | ) 46 | assert False 47 | 48 | if n is None: 49 | n = max(D.keys()) 50 | 51 | ci, goodpatts = mine(D, m, n, report=report) 52 | SG = forb(ci, goodpatts, m, report=report) 53 | 54 | return SG 55 | 56 | 57 | def auto_bisc(prop): 58 | L = 8 # Want to sanity check on at least S8, and one above n 59 | n = 4 60 | m = 2 61 | 62 | if isinstance(prop, list): 63 | # If a list is passed in then we put it in a dictionary at the start 64 | A = defaultdict(list) 65 | # We will put the complement up to S8 into another dictionary 66 | B = defaultdict(list) 67 | for perm in prop: 68 | A[len(perm)].append(perm) 69 | if L not in A.keys(): 70 | print("You should have permutations up to length at least 8") 71 | return 72 | for i in range(max(L + 1, max(A.keys()) + 1)): 73 | if i > 7: 74 | print("Populating the dictionary of bad perms of length ", i) 75 | B[i] = [perm for perm in Perm.of_length(i) if perm not in A[i]] 76 | 77 | elif isinstance(prop, types.FunctionType): 78 | # If a property is passed in then we use it to populate both 79 | # both dictionaries up to S8 80 | A = defaultdict(list) 81 | B = defaultdict(list) 82 | for i in range(L + 1): 83 | A[i] = [] 84 | B[i] = [] 85 | for perm in Perm.of_length(i): 86 | if prop(perm): 87 | A[i].append(perm) 88 | else: 89 | B[i].append(perm) 90 | 91 | elif ( 92 | isinstance(prop, tuple) 93 | and isinstance(prop[0], dict) 94 | and isinstance(prop[1], dict) 95 | ): 96 | # If you already have the dictionaries you can pass them in as a tuple 97 | A = prop[0] 98 | B = prop[1] 99 | if L not in A.keys(): 100 | print("You should have permutations up to length at least 8") 101 | return 102 | 103 | elif isinstance(prop, str): 104 | # If you pass in a string we assume it is pointing to a file in permsets 105 | print("Attempting to read perms from permsets") 106 | good_entry = None 107 | bad_entry = None 108 | with os.scandir("../resources/bisc") as entries: 109 | for i, entry in enumerate(entries): 110 | en = entry.name[:-5] 111 | spl = en.split("_") 112 | if spl[0] == prop: 113 | if ( 114 | good_entry is None 115 | and spl[1] == "good" 116 | and int(spl[2].split("len")[1]) >= L 117 | ): 118 | good_entry = en 119 | if ( 120 | bad_entry is None 121 | and spl[1] == "bad" 122 | and int(spl[2].split("len")[1]) >= L 123 | ): 124 | bad_entry = en 125 | if good_entry is not None and bad_entry is not None: 126 | break 127 | if good_entry is not None and bad_entry is not None: 128 | A = read_bisc_file("../resources/bisc/" + good_entry) 129 | B = read_bisc_file("../resources/bisc/" + bad_entry) 130 | else: 131 | print("The required files do not exist") 132 | return 133 | 134 | else: 135 | print( 136 | """BiSC can accept inputs: 137 | 1. A list of permutations 138 | 2. A property of permutations 139 | 3. A dictionary of lengths pointing to permutations 140 | 4. A string """ 141 | ) 142 | assert False 143 | 144 | while True: 145 | print("Learning patterns of length {} using perms of length {}".format(m, n)) 146 | SG = bisc(A, m, n) 147 | show_me(SG, more=True) 148 | 149 | if SG != {}: 150 | print("Sanity checking the learned patterns up to length {}".format(L)) 151 | val, avoiding_perms = patterns_suffice_for_bad( 152 | SG, L, B, stop_on_failure=True 153 | ) 154 | else: 155 | val = False 156 | 157 | if val: 158 | print( 159 | "These patterns seem to suffice, I will now try to find a small basis" 160 | ) 161 | 162 | ib = len(SG[min(SG.keys())].keys()) 163 | while True: 164 | print("Trying to find a basis with at most {} patterns".format(ib)) 165 | print("using perms of length {}".format(n)) 166 | bases, dict_numbs_to_patts = run_clean_up(SG, B, n, limit_monitors=ib) 167 | 168 | if bases: 169 | basis = bases[0] 170 | show_me_basis(basis, dict_numbs_to_patts) 171 | 172 | sg = to_sg_format(basis, dict_numbs_to_patts) 173 | 174 | val, avoiding_perms = patterns_suffice_for_bad( 175 | sg, L, B, stop_on_failure=True 176 | ) 177 | 178 | if not val: 179 | print("A bad basis was chosen.") 180 | print("Increasing perm length to {}".format(n + 1)) 181 | n += 1 182 | continue 183 | 184 | val, containing_perms = patterns_suffice_for_good( 185 | sg, L, A, stop_on_failure=True 186 | ) 187 | if not val: 188 | print("This is a bad basis. Need to learn from longer perms") 189 | n += 1 190 | break # breaking out of the inner while-loop 191 | 192 | else: 193 | print("No bases found. Increasing number of patterns in basis") 194 | ib += 1 195 | val = False 196 | 197 | if val: 198 | print( 199 | "!!! Found a basis with {} patts of length at most {}".format( 200 | ib, m 201 | ) 202 | ) 203 | print("!!! for the input using perms of length {}".format(n)) 204 | print("Basis: ", sg) 205 | return sg 206 | 207 | else: 208 | print("Need to learn longer patterns") 209 | n += 1 210 | m += 1 211 | 212 | oldL = L 213 | if L < n + 1: 214 | L = n + 1 215 | if isinstance(prop, list) and L > max(A.keys()): 216 | print("You need to input a longer list of permutations") 217 | return 218 | 219 | elif isinstance(prop, types.FunctionType): 220 | # Adding perms to the dictionaries 221 | for i in range(oldL + 1, L + 1): 222 | print("Adding perms of length {}".format(i)) 223 | for perm in Perm.of_length(i): 224 | if prop(perm): 225 | A[i].append(perm) 226 | else: 227 | B[i].append(perm) 228 | 229 | elif isinstance(prop, tuple) and L > min(max(A.keys()), max(B.keys())): 230 | print("You need to add longer permutations to the dictionaries") 231 | return 232 | 233 | elif isinstance(prop, str) and L > min(max(A.keys()), max(B.keys())): 234 | # If you pass in a string we assume it is pointing to a file in permsets 235 | print("Attempting to read perms from permsets") 236 | good_entry = None 237 | bad_entry = None 238 | with os.scandir("../resources/bisc") as entries: 239 | for i, entry in enumerate(entries): 240 | en = entry.name[:-5] 241 | spl = en.split("_") 242 | if spl[0] == prop: 243 | if ( 244 | good_entry is None 245 | and spl[1] == "good" 246 | and int(spl[2].split("len")[1]) >= L 247 | ): 248 | good_entry = en 249 | if ( 250 | bad_entry is None 251 | and spl[1] == "bad" 252 | and int(spl[2].split("len")[1]) >= L 253 | ): 254 | bad_entry = en 255 | if good_entry is not None and bad_entry is not None: 256 | break 257 | if good_entry is not None and bad_entry is not None: 258 | A = read_bisc_file("../resources/bisc/" + good_entry) 259 | B = read_bisc_file("../resources/bisc/" + bad_entry) 260 | else: 261 | print("The required files do not exist") 262 | return 263 | 264 | 265 | def create_bisc_input(N, prop): 266 | """ 267 | Create a dictionary, D, containing keys 1, 2, 3, ..., N. Each key points to 268 | a list of permutations satisfying the property prop. The dictionary E has 269 | the same keys and they point to the complement. 270 | """ 271 | 272 | A, B = {}, {} 273 | 274 | for n in range(N + 1): 275 | An, Bn = [], [] 276 | 277 | for perm in Perm.of_length(n): 278 | if prop(perm): 279 | An.append(perm) 280 | else: 281 | Bn.append(perm) 282 | 283 | A[n], B[n] = An, Bn 284 | 285 | return A, B 286 | 287 | 288 | def write_bisc_files(n: int, prop, info: str) -> None: 289 | """Create a dictionary, D, containing keys 1, 2, 3, ..., n. Each key points to 290 | a list of permutations satisfying the property prop. The dictionary E has 291 | the same keys and they point to the complement. 292 | """ 293 | good, bad = create_bisc_input(n, prop) 294 | write_json_to_file(good, f"{info}_good_len{n}.json") 295 | write_json_to_file(bad, f"{info}_bad_len{n}.json") 296 | 297 | 298 | def write_json_to_file(json_obj, file_name): 299 | try: 300 | with open(file_name, "a+") as f: 301 | f.write(json.dumps(json_obj)) 302 | except OSError: 303 | print(f"Could not write to file: {file_name}") 304 | 305 | 306 | def from_json(json_string): 307 | json_obj = json.loads(json_string) 308 | return {int(key): list(map(Perm, values)) for key, values in json_obj.items()} 309 | 310 | 311 | def read_bisc_file(path): 312 | try: 313 | with open(f"{path}.json", "r") as f: 314 | return from_json(f.readline()) 315 | except (ValueError, TypeError, OSError): 316 | print(f"File is invalid: {path}") 317 | return {} 318 | -------------------------------------------------------------------------------- /permuta/bisc/perm_properties.py: -------------------------------------------------------------------------------- 1 | from itertools import islice 2 | from typing import List 3 | 4 | from permuta.patterns.meshpatt import MeshPatt 5 | from permuta.patterns.perm import Perm 6 | from permuta.permutils.groups import dihedral_group 7 | 8 | _SMOOTH_PATT = (Perm((0, 2, 1, 3)), Perm((1, 0, 3, 2))) 9 | 10 | 11 | def smooth(perm: Perm) -> bool: 12 | """Returns true if the perm is smooth, i.e. 0213- and 1032-avoiding.""" 13 | return perm.avoids(*_SMOOTH_PATT) 14 | 15 | 16 | _FOREST_LIKE_PATT = (Perm((0, 2, 1, 3)), MeshPatt(Perm((1, 0, 3, 2)), [(2, 2)])) 17 | 18 | 19 | def forest_like(perm: Perm) -> bool: 20 | """Returns true if the perm is forest like.""" 21 | return perm.avoids(*_FOREST_LIKE_PATT) 22 | 23 | 24 | _BAXTER_PATT = ( 25 | MeshPatt(Perm((1, 3, 0, 2)), [(2, 2)]), 26 | MeshPatt(Perm((2, 0, 3, 1)), [(2, 2)]), 27 | ) 28 | 29 | 30 | def baxter(perm: Perm) -> bool: 31 | """Returns true if the perm is a baxter permutation.""" 32 | return perm.avoids(*_BAXTER_PATT) 33 | 34 | 35 | _SIMSUN_PATT = MeshPatt(Perm((2, 1, 0)), [(1, 0), (1, 1), (2, 2)]) 36 | 37 | 38 | def simsun(perm: Perm) -> bool: 39 | """Returns true if the perm is a simsun permutation.""" 40 | return perm.avoids(_SIMSUN_PATT) 41 | 42 | 43 | def dihedral(perm: Perm) -> bool: 44 | """Does perm belong to a dihedral group? We use the convention that D1 and D2 are 45 | not subgrroups of S1 and S2, respectively.""" 46 | return any(perm == d_perm for d_perm in dihedral_group(len(perm))) 47 | 48 | 49 | def in_alternating_group(perm: Perm) -> bool: 50 | """Does perm belong to alternating group? We use the convention that D1 and D2 are 51 | not subgrroups of S1 and S2, respectively.""" 52 | n = len(perm) 53 | if n == 0: 54 | return True 55 | if n < 3: 56 | return n % 2 == 1 57 | return perm.count_inversions() % 2 == 0 58 | 59 | 60 | def _perm_to_yt(perm: Perm) -> List[List[int]]: 61 | # transform perm to standard young table 62 | 63 | def insert_in_row(i, k): 64 | cur_row = res[i] 65 | found = next(((ind, cur) for ind, cur in enumerate(cur_row) if cur > k), None) 66 | if not found: 67 | cur_row.append(k) 68 | else: 69 | ind, cur = found 70 | cur_row[ind] = k 71 | if len(res) <= i + 1: 72 | res.append([cur]) 73 | else: 74 | insert_in_row(i + 1, cur) 75 | 76 | res: List[List[int]] = [[perm[0]]] if perm else [] 77 | for val in islice(perm, 1, None): 78 | insert_in_row(0, val) 79 | return res 80 | 81 | 82 | def _tableau_contains_shape(tab: List[List[int]], shape: List[int]) -> bool: 83 | # Return True if the tableaux tab contains the shape 84 | return len(tab) >= len(shape) and all(s <= t for s, t in zip(shape, map(len, tab))) 85 | 86 | 87 | def yt_perm_avoids_22(perm: Perm) -> bool: 88 | """Returns true if perm's standard young table avoids shape [2,2].""" 89 | return not _tableau_contains_shape(_perm_to_yt(perm), [2, 2]) 90 | 91 | 92 | def yt_perm_avoids_32(perm: Perm) -> bool: 93 | """Returns true if perm's standard young table avoids shape [3,2].""" 94 | return not _tableau_contains_shape(_perm_to_yt(perm), [3, 2]) 95 | 96 | 97 | _AV_231_AND_MESH_PATT = ( 98 | Perm((1, 2, 0)), 99 | MeshPatt(Perm((0, 1, 5, 2, 3, 4)), [(1, 6), (4, 5), (4, 6)]), 100 | ) 101 | 102 | 103 | def av_231_and_mesh(perm: Perm) -> bool: 104 | """Check if perm avoids MeshPatt(Perm((0, 1, 5, 2, 3, 4)), [(1, 6), (4, 5), (4, 6)]) 105 | and the classial pattern 231. 106 | """ 107 | return perm.avoids(*_AV_231_AND_MESH_PATT) 108 | 109 | 110 | _HARD_MESH_PATT = ( 111 | MeshPatt(Perm((0, 1, 2)), [(0, 0), (1, 1), (2, 2), (3, 3)]), 112 | MeshPatt(Perm((0, 1, 2)), [(0, 3), (1, 2), (2, 1), (3, 0)]), 113 | ) 114 | 115 | 116 | def hard_mesh(perm: Perm) -> bool: 117 | """Check if perm avoids MeshPatt(Perm((0, 1, 2)), [(0, 0), (1, 1), (2, 2), (3, 3)]) 118 | and MeshPatt(Perm((0, 1, 2)), [(0, 3), (1, 2), (2, 1), (3, 0)]).""" 119 | return perm.avoids(*_HARD_MESH_PATT) 120 | -------------------------------------------------------------------------------- /permuta/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import signal 3 | import sys 4 | import types 5 | from typing import Any, Optional 6 | 7 | from permuta import Av, Basis 8 | from permuta.permutils import InsertionEncodablePerms, PolyPerms, lex_min 9 | 10 | 11 | def sigint_handler(sig: int, _frame: Optional[types.FrameType]) -> Any: 12 | """For terminating infinite task.""" 13 | if sig == signal.SIGINT: 14 | print("\nExiting.") 15 | sys.exit(0) 16 | 17 | 18 | def enumerate_class(args: argparse.Namespace) -> None: 19 | """Enumerate a perm class indefinitely, one length at a time.""" 20 | signal.signal(signal.SIGINT, sigint_handler) 21 | perm_class = Av.from_string(args.basis) 22 | print(f"Enumerating {perm_class}. Press Ctrl+C to exit.") 23 | n = 0 24 | while True: 25 | print(perm_class.count(n), end=", ", flush=True) 26 | n += 1 27 | 28 | 29 | def has_regular_insertion_encoding(args: argparse.Namespace) -> None: 30 | """Check if a perm class has a regular insertion encoding.""" 31 | basis = Basis.from_string(args.basis) 32 | perm_class = Av(basis) 33 | if InsertionEncodablePerms.is_insertion_encodable_maximum(basis): 34 | print(f"The class {perm_class} has a regular topmost insertion encoding") 35 | if InsertionEncodablePerms.is_insertion_encodable_rightmost(basis): 36 | print(f"The class {perm_class} has a regular rightmost insertion encoding") 37 | if not InsertionEncodablePerms.is_insertion_encodable(basis): 38 | print(f"{perm_class} does not have a regular insertion encoding") 39 | 40 | 41 | def get_lex_min(args: argparse.Namespace) -> None: 42 | """Prints the 0-based lexicographically minimal representation of the basis.""" 43 | basis = Basis.from_string(args.basis) 44 | print("_".join(str(perm) for perm in lex_min(basis))) 45 | 46 | 47 | def has_poly_growth(args: argparse.Namespace) -> None: 48 | """Prints whether perm class from basis has polynomial growth.""" 49 | basis = Basis.from_string(args.basis) 50 | poly = PolyPerms.is_polynomial(basis) 51 | print(f"Av({basis}) is {'' if poly else 'not '}polynomial") 52 | 53 | 54 | def has_finitely_many_simples(args: argparse.Namespace) -> None: 55 | """Check if a perm class has finitely many simples.""" 56 | basis = Basis.from_string(args.basis) 57 | perm_class = Av(basis) 58 | if perm_class.has_finitely_many_simples(): 59 | print(f"The class {perm_class} has finitely many simples.") 60 | else: 61 | print(f"The class {perm_class} has infinitely many simples") 62 | 63 | 64 | def get_parser() -> argparse.ArgumentParser: 65 | """Construct and return parser.""" 66 | basis_str: str = ( 67 | "The basis as a string where the permutations are separated by any token, " 68 | "(e.g. '231_4321', '0132:43210')" 69 | ) 70 | 71 | parser: argparse.ArgumentParser = argparse.ArgumentParser( 72 | description="A set of tools to work with permutations" 73 | ) 74 | subparsers = parser.add_subparsers(title="subcommands") 75 | 76 | # The count command 77 | count_parser: argparse.ArgumentParser = subparsers.add_parser( 78 | "count", 79 | description="A tool to quickly get the enumeration of permutation classes", 80 | ) 81 | count_parser.set_defaults(func=enumerate_class) 82 | count_parser.add_argument("basis", help=basis_str) 83 | 84 | # The insenc command 85 | insenc_parser: argparse.ArgumentParser = subparsers.add_parser( 86 | "insenc", 87 | description="A tool to check if a permutation class has a regular insertion" 88 | " encoding.", 89 | ) 90 | insenc_parser.set_defaults(func=has_regular_insertion_encoding) 91 | insenc_parser.add_argument("basis", help=basis_str) 92 | 93 | # The lexmin command 94 | lexmin_parser: argparse.ArgumentParser = subparsers.add_parser( 95 | "lexmin", 96 | description="A tool that returns the 0-based lexicographically minimal " 97 | "representation of the basis.", 98 | ) 99 | lexmin_parser.set_defaults(func=get_lex_min) 100 | lexmin_parser.add_argument("basis", help=basis_str) 101 | 102 | # The poly command 103 | poly_parser: argparse.ArgumentParser = subparsers.add_parser( 104 | "poly", 105 | description="A tool to check if permutation class has polynomial growth.", 106 | ) 107 | poly_parser.set_defaults(func=has_poly_growth) 108 | poly_parser.add_argument("basis", help=basis_str) 109 | 110 | # The simples command 111 | simple_parser: argparse.ArgumentParser = subparsers.add_parser( 112 | "simple", 113 | description="A tool to check if a permutation class has finitely many simples.", 114 | ) 115 | simple_parser.set_defaults(func=has_finitely_many_simples) 116 | simple_parser.add_argument("basis", help=basis_str) 117 | 118 | return parser 119 | 120 | 121 | def main() -> None: 122 | """Entry point.""" 123 | parser = get_parser() 124 | args = parser.parse_args() 125 | if not hasattr(args, "func"): 126 | parser.error("Invalid command") 127 | args.func(args) 128 | 129 | 130 | if __name__ == "__main__": 131 | main() 132 | -------------------------------------------------------------------------------- /permuta/enumeration_strategies/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, List, Type 2 | 3 | from permuta import Perm 4 | 5 | from .abstract_strategy import EnumerationStrategy 6 | from .core_strategies import core_strategies 7 | from .finitely_many_simples import FinitelyManySimplesStrategy 8 | from .insertion_encodable import InsertionEncodingStrategy 9 | 10 | fast_enumeration_strategies: List[Type[EnumerationStrategy]] = [ 11 | InsertionEncodingStrategy 12 | ] 13 | fast_enumeration_strategies.extend(core_strategies) 14 | 15 | long_enumeration_strategies: List[Type[EnumerationStrategy]] = [ 16 | FinitelyManySimplesStrategy 17 | ] 18 | 19 | all_enumeration_strategies: List[Type[EnumerationStrategy]] = ( 20 | fast_enumeration_strategies + long_enumeration_strategies 21 | ) 22 | 23 | 24 | def find_strategies( 25 | basis: Iterable[Perm], long_runnning: bool = True 26 | ) -> List[EnumerationStrategy]: 27 | """Test all enumeration strategies against the basis and return a list of 28 | potentially useful strategies. If `long_runnning` is False, test only the 29 | strategies that can be tested quickly. 30 | """ 31 | if long_runnning: 32 | strategies: List[Type[EnumerationStrategy]] = all_enumeration_strategies 33 | else: 34 | strategies = fast_enumeration_strategies 35 | working_strategies: List[EnumerationStrategy] = [] 36 | for strategy in strategies: 37 | strategy_object = strategy(basis) 38 | if strategy_object.applies(): 39 | working_strategies.append(strategy_object) 40 | return working_strategies 41 | -------------------------------------------------------------------------------- /permuta/enumeration_strategies/abstract_strategy.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import FrozenSet, Iterable, Iterator 3 | 4 | from permuta import Perm 5 | from permuta.permutils.symmetry import all_symmetry_sets 6 | 7 | 8 | class EnumerationStrategy(ABC): 9 | """Abstract class for a strategy to enumerate a permutation classes.""" 10 | 11 | def __init__(self, basis: Iterable[Perm]) -> None: 12 | self._basis = frozenset(basis) 13 | 14 | @property 15 | def basis(self) -> FrozenSet[Perm]: 16 | """Getter for basis.""" 17 | return self._basis 18 | 19 | @classmethod 20 | def reference(cls) -> str: 21 | """A reference for the strategy.""" 22 | raise NotImplementedError 23 | 24 | @abstractmethod 25 | def applies(self) -> bool: 26 | """Return True if the strategy can be used for the basis.""" 27 | 28 | 29 | class EnumerationStrategyWithSymmetry(EnumerationStrategy): 30 | """Abstract class for a strategy to enumerate a permutation classes. 31 | Each symmetry of the inputed basis is tested against the strategy. 32 | """ 33 | 34 | def applies(self) -> bool: 35 | """Check if the strategy applies to any symmetry.""" 36 | syms: Iterator[FrozenSet[Perm]] = map(frozenset, all_symmetry_sets(self._basis)) 37 | return next((True for b in syms if self._applies_to_symmetry(b)), False) 38 | 39 | @abstractmethod 40 | def _applies_to_symmetry(self, basis: FrozenSet[Perm]) -> bool: 41 | """Check if the strategy applies to this particular symmetry.""" 42 | -------------------------------------------------------------------------------- /permuta/enumeration_strategies/core_strategies.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | from typing import ClassVar, FrozenSet, List, Type 3 | 4 | from permuta import Av, MeshPatt, Perm 5 | from permuta.enumeration_strategies.abstract_strategy import ( 6 | EnumerationStrategyWithSymmetry, 7 | ) 8 | 9 | 10 | class CoreStrategy(EnumerationStrategyWithSymmetry): 11 | """Abstract class for a core related strategy.""" 12 | 13 | # https://arxiv.org/pdf/1912.07503.pdf 14 | # See this paper for corr_number 15 | 16 | @property 17 | @abstractmethod 18 | def patterns_needed(self) -> FrozenSet[Perm]: 19 | """Return the set of patterns that are needed for the strategy to be useful.""" 20 | 21 | @staticmethod 22 | @abstractmethod 23 | def is_valid_extension(patt: Perm) -> bool: 24 | """Determine if the pattern satisfies the condition for strategy to apply.""" 25 | 26 | def _applies_to_symmetry(self, basis: FrozenSet[Perm]): 27 | """Check if the core strategy applies to the basis or any of its symmetry.""" 28 | assert isinstance(basis, frozenset) 29 | perm_class: Av = Av.from_iterable(basis) 30 | patterns_are_contained = all(p not in perm_class for p in self.patterns_needed) 31 | extensions_are_valid = all( 32 | self.is_valid_extension(patt) 33 | for patt in basis.difference(self.patterns_needed) 34 | ) 35 | return patterns_are_contained and extensions_are_valid 36 | 37 | @classmethod 38 | def reference(cls) -> str: 39 | return ( 40 | "Enumeration of Permutation Classes and Weighted Labelled " 41 | f"Independent Sets: Corollary {cls.corr_number}" 42 | ) 43 | 44 | @property 45 | def corr_number(self) -> str: 46 | """The number of the corollary in the that gives this strategy.""" 47 | raise NotImplementedError 48 | 49 | 50 | def fstrip(perm: Perm) -> Perm: 51 | """Remove the leading 1 if the permutation is the sum of 1 + p.""" 52 | assert len(perm) > 0 53 | if perm[0] == 0: 54 | return Perm.one_based(perm[1:]) 55 | return perm 56 | 57 | 58 | def bstrip(perm: Perm) -> Perm: 59 | """Remove the trailing n if the permutation is the sum of p + 1.""" 60 | assert len(perm) > 0 61 | if perm[-1] == len(perm) - 1: 62 | return Perm(perm[:-1]) 63 | return perm 64 | 65 | 66 | def zero_plus_skewind(perm: Perm) -> bool: 67 | """Return True if the permutation is of the form 1 + p where p is a 68 | skew-indecomposable permutations 69 | """ 70 | assert len(perm) > 0 71 | return perm[0] == 0 and not fstrip(perm).skew_decomposable() 72 | 73 | 74 | def zero_plus_sumind(perm: Perm) -> bool: 75 | """Return True if the permutation is of the form 1 + p where p is a 76 | sum-indecomposable permutations 77 | """ 78 | assert len(perm) > 0 79 | return perm[0] == 0 and not fstrip(perm).sum_decomposable() 80 | 81 | 82 | def zero_plus_perm(perm: Perm) -> bool: 83 | """Return True if the permutation starts with a zero.""" 84 | assert len(perm) > 0 85 | return perm[0] == 0 86 | 87 | 88 | def last_sum_component(perm: Perm) -> Perm: 89 | """Return the last sum component of a permutation.""" 90 | assert len(perm) > 0 91 | n, i = len(perm), 1 92 | comp = {perm[-1]} 93 | while comp != set(range(n - i, n)): 94 | i += 1 95 | comp.add(perm[n - i]) 96 | return Perm.to_standard(perm[n - i : n]) 97 | 98 | 99 | def last_skew_component(perm: Perm) -> Perm: 100 | """Return the last skew component of a permutation.""" 101 | assert len(perm) > 0 102 | n, i = len(perm), 1 103 | i = 1 104 | comp = {perm[-1]} 105 | while comp != set(range(i)): 106 | i += 1 107 | comp.add(perm[n - i]) 108 | return Perm.to_standard(perm[n - i : n]) 109 | 110 | 111 | R_U: Perm = Perm((1, 2, 0, 3)) # 2314, row up 112 | C_U: Perm = Perm((2, 0, 1, 3)) # 3124, colmn up 113 | R_D: Perm = Perm((1, 3, 0, 2)) # 2413, row down 114 | C_D: Perm = Perm((2, 0, 3, 1)) # 3142, column down 115 | 116 | 117 | class RuCuCoreStrategy(CoreStrategy): 118 | """This strategies uses independent set of the up-core graph to enumerate a 119 | class as inflation of an independent set. 120 | """ 121 | 122 | patterns_needed: FrozenSet[Perm] = frozenset([R_U, C_U]) 123 | corr_number: ClassVar[str] = "4.3" 124 | 125 | @staticmethod 126 | def is_valid_extension(patt: Perm) -> bool: 127 | return zero_plus_skewind(patt) 128 | 129 | 130 | class RdCdCoreStrategy(CoreStrategy): 131 | """This strategies uses independent set of the down-core graph to enumerate a 132 | class as inflation of an independent set. 133 | """ 134 | 135 | patterns_needed = frozenset([R_D, C_D]) 136 | corr_number: ClassVar[str] = "4.6" 137 | 138 | @staticmethod 139 | def is_valid_extension(patt: Perm) -> bool: 140 | return zero_plus_sumind(patt) 141 | 142 | 143 | class RuCuRdCdCoreStrategy(CoreStrategy): 144 | """TODO""" 145 | 146 | patterns_needed = frozenset([R_D, C_D, R_U, C_U]) 147 | corr_number: ClassVar[str] = "5.4" 148 | 149 | @staticmethod 150 | def is_valid_extension(patt: Perm) -> bool: 151 | return zero_plus_perm(patt) 152 | 153 | 154 | class RuCuCdCoreStrategy(CoreStrategy): 155 | """TODO""" 156 | 157 | patterns_needed = frozenset([R_U, C_U, C_D]) 158 | corr_number: ClassVar[str] = "6.3" 159 | 160 | @staticmethod 161 | def is_valid_extension(patt: Perm) -> bool: 162 | return zero_plus_skewind(patt) 163 | 164 | 165 | class RdCdCuCoreStrategy(CoreStrategy): 166 | """TODO""" 167 | 168 | patterns_needed = frozenset([R_D, C_D, C_U]) 169 | corr_number: ClassVar[str] = "7.4" 170 | 171 | @staticmethod 172 | def is_valid_extension(patt): 173 | return zero_plus_sumind(bstrip(patt)) 174 | 175 | 176 | class RdCuCoreStrategy(CoreStrategy): 177 | """TODO""" 178 | 179 | patterns_needed = frozenset([R_D, C_U]) 180 | corr_number: ClassVar[str] = "8.3" 181 | 182 | @staticmethod 183 | def is_valid_extension(patt): 184 | return zero_plus_skewind(patt) and zero_plus_sumind(bstrip(patt)) 185 | 186 | 187 | class Rd2134CoreStrategy(CoreStrategy): 188 | """TODO""" 189 | 190 | _NON_INC: ClassVar[Av] = Av.from_iterable([Perm((0, 1))]) 191 | _M_PATT: ClassVar[MeshPatt] = MeshPatt( 192 | Perm((1, 0)), [(0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 1), (2, 2)] 193 | ) 194 | 195 | patterns_needed = frozenset([R_D, Perm((1, 0, 2, 3))]) 196 | corr_number: ClassVar[str] = "9.5" 197 | 198 | @staticmethod 199 | def is_valid_extension(patt: Perm) -> bool: 200 | last_comp = last_sum_component(fstrip(patt)) 201 | return ( 202 | patt[0] == 0 203 | and fstrip(patt).avoids(Rd2134CoreStrategy._M_PATT) 204 | and (last_comp not in Rd2134CoreStrategy._NON_INC or len(last_comp) == 1) 205 | ) 206 | 207 | 208 | class Ru2143CoreStrategy(CoreStrategy): 209 | """TODO""" 210 | 211 | _NON_DEC: ClassVar[Av] = Av.from_iterable([Perm((1, 0))]) 212 | _M_PATT: ClassVar[MeshPatt] = MeshPatt( 213 | Perm((0, 1)), [(0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 1), (2, 2)] 214 | ) 215 | 216 | patterns_needed = frozenset([R_U, Perm((1, 0, 3, 2))]) 217 | corr_number: ClassVar[str] = "10.5" 218 | 219 | @staticmethod 220 | def is_valid_extension(patt: Perm) -> bool: 221 | patt = fstrip(patt) 222 | return ( 223 | patt.avoids(Ru2143CoreStrategy._M_PATT) 224 | and last_skew_component(patt) not in Ru2143CoreStrategy._NON_DEC 225 | ) 226 | 227 | 228 | core_strategies: List[Type[CoreStrategy]] = [ 229 | RuCuCoreStrategy, 230 | RdCdCoreStrategy, 231 | RuCuRdCdCoreStrategy, 232 | RuCuCdCoreStrategy, 233 | RdCdCuCoreStrategy, 234 | RdCuCoreStrategy, 235 | Rd2134CoreStrategy, 236 | Ru2143CoreStrategy, 237 | ] 238 | -------------------------------------------------------------------------------- /permuta/enumeration_strategies/finitely_many_simples.py: -------------------------------------------------------------------------------- 1 | from permuta.enumeration_strategies.abstract_strategy import EnumerationStrategy 2 | from permuta.permutils.pin_words import PinWords 3 | 4 | 5 | class FinitelyManySimplesStrategy(EnumerationStrategy): 6 | """Enumeration strategies related to the class having finitely many simple 7 | permutations.""" 8 | 9 | def applies(self) -> bool: 10 | return PinWords.has_finite_simples(self.basis) 11 | 12 | @classmethod 13 | def reference(cls) -> str: 14 | return "The class contains only finitely many simple permutations" 15 | -------------------------------------------------------------------------------- /permuta/enumeration_strategies/insertion_encodable.py: -------------------------------------------------------------------------------- 1 | from permuta.enumeration_strategies.abstract_strategy import EnumerationStrategy 2 | from permuta.permutils.insertion_encodable import InsertionEncodablePerms 3 | from permuta.permutils.symmetry import rotate_90_clockwise_set 4 | 5 | 6 | class InsertionEncodingStrategy(EnumerationStrategy): 7 | """Enumeration strategies related to the insertion encoding.""" 8 | 9 | def applies(self) -> bool: 10 | return InsertionEncodablePerms.is_insertion_encodable( 11 | self.basis 12 | ) or InsertionEncodablePerms.is_insertion_encodable( 13 | rotate_90_clockwise_set(self.basis) 14 | ) 15 | 16 | @classmethod 17 | def reference(cls) -> str: 18 | return "The insertion encoding of permutations: Corollary 10" 19 | -------------------------------------------------------------------------------- /permuta/misc/__init__.py: -------------------------------------------------------------------------------- 1 | from .display import HTMLViewer 2 | from .union_find import UnionFind 3 | 4 | DIR_EAST = 0 5 | DIR_NORTH = 1 6 | DIR_WEST = 2 7 | DIR_SOUTH = 3 8 | DIR_NONE = -1 9 | DIRS = [DIR_EAST, DIR_NORTH, DIR_WEST, DIR_SOUTH] 10 | 11 | __all__ = [ 12 | "HTMLViewer", 13 | "UnionFind", 14 | "DIRS", 15 | "DIR_EAST", 16 | "DIR_NORTH", 17 | "DIR_WEST", 18 | "DIR_SOUTH", 19 | "DIR_NONE", 20 | ] 21 | -------------------------------------------------------------------------------- /permuta/misc/display.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | import threading 4 | import time 5 | import webbrowser 6 | from typing import ClassVar 7 | 8 | 9 | class HTMLViewer: 10 | """A class for opening html text in browser.""" 11 | 12 | _THREAD_WAIT_TIME: ClassVar[float] = 5 # seconds 13 | 14 | @staticmethod 15 | def _remove_file_thread(fname: str) -> None: 16 | time.sleep(HTMLViewer._THREAD_WAIT_TIME) 17 | if os.path.exists(fname): 18 | os.remove(fname) 19 | 20 | @staticmethod 21 | def _remove_file(fname: str) -> None: 22 | threading.Thread(target=HTMLViewer._remove_file_thread, args=(fname,)).start() 23 | 24 | @staticmethod 25 | def open_html(html: str) -> None: 26 | """Open and render html string in browser.""" 27 | with tempfile.NamedTemporaryFile( 28 | "r+", suffix=".html", delete=False 29 | ) as html_file: 30 | html_file.write(html) 31 | webbrowser.open_new_tab(f"file://{html_file.name}") 32 | HTMLViewer._remove_file(html_file.name) 33 | 34 | @staticmethod 35 | def open_svg(svg: str) -> None: 36 | """Open and render svg image string in browser.""" 37 | HTMLViewer.open_html(f"{svg}") 38 | -------------------------------------------------------------------------------- /permuta/misc/math.py: -------------------------------------------------------------------------------- 1 | def is_prime(n: int) -> bool: 2 | """Primality test using 6k+-1 optimization.""" 3 | if n <= 3: 4 | return n > 1 5 | if n % 2 == 0 or n % 3 == 0: 6 | return False 7 | i = 5 8 | while i**2 <= n: 9 | if n % i == 0 or n % (i + 2) == 0: 10 | return False 11 | i += 6 12 | return True 13 | -------------------------------------------------------------------------------- /permuta/misc/union_find.py: -------------------------------------------------------------------------------- 1 | class UnionFind: 2 | """A collection of distjoint sets.""" 3 | 4 | def __init__(self, size: int) -> None: 5 | """Creates a collection of size disjoint unit sets.""" 6 | self._parent = [-1] * size 7 | 8 | def find(self, idx: int) -> int: 9 | """Return the identifier of a representative element for the set 10 | containing the element with identifier idx.""" 11 | if self._parent[idx] < 0: 12 | return idx 13 | self._parent[idx] = self.find(self._parent[idx]) 14 | return self._parent[idx] 15 | 16 | def size(self, idx: int) -> int: 17 | """Return the number of elements in the set containing the element with 18 | identifier idx.""" 19 | return -self._parent[self.find(idx)] 20 | 21 | def unite(self, idx1: int, idx2: int) -> bool: 22 | """Unite the two sets containing the elements with identifiers idx1 and idx2, 23 | respectively.""" 24 | idx1, idx2 = self.find(idx1), self.find(idx2) 25 | if idx1 == idx2: 26 | return False 27 | if self.size(idx1) > self.size(idx2): 28 | idx1, idx2 = idx2, idx1 29 | self._parent[idx2] += self._parent[idx1] 30 | self._parent[idx1] = idx2 31 | return True 32 | -------------------------------------------------------------------------------- /permuta/patterns/__init__.py: -------------------------------------------------------------------------------- 1 | from .bivincularpatt import BivincularPatt, CovincularPatt, VincularPatt 2 | from .meshpatt import MeshPatt 3 | from .patt import Patt 4 | from .perm import Perm 5 | 6 | __all__ = [ 7 | "Patt", 8 | "Perm", 9 | "MeshPatt", 10 | "BivincularPatt", 11 | "VincularPatt", 12 | "CovincularPatt", 13 | ] 14 | -------------------------------------------------------------------------------- /permuta/patterns/bivincularpatt.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | from typing import Iterable, Iterator, List, Optional, Tuple 3 | 4 | from .meshpatt import MeshPatt 5 | from .patt import Patt 6 | from .perm import Perm 7 | 8 | 9 | class BivincularPatt(MeshPatt): 10 | """A bivincular pattern class.""" 11 | 12 | @staticmethod 13 | def _to_shading( 14 | n: int, adjacent_indices: Iterable[int], adjacent_values: Iterable[int] 15 | ) -> Iterator[Tuple[int, int]]: 16 | """Convert adjacent requirements into shading.""" 17 | for idx in adjacent_indices: 18 | assert 0 <= idx <= n 19 | yield from ((idx, val) for val in range(n + 1)) 20 | for val in adjacent_values: 21 | assert 0 <= val <= n 22 | yield from ((idx, val) for idx in range(n + 1)) 23 | 24 | def __init__( 25 | self, 26 | perm: Perm, 27 | adjacent_indices: Iterable[int], 28 | adjacent_values: Iterable[int], 29 | ) -> None: 30 | super().__init__( 31 | perm, 32 | BivincularPatt._to_shading(len(perm), adjacent_indices, adjacent_values), 33 | ) 34 | 35 | @classmethod 36 | def unrank(cls, pattern: Perm, number: int) -> MeshPatt: 37 | """Not implemented, inherited from MeshPatt.""" 38 | raise NotImplementedError 39 | 40 | @classmethod 41 | def of_length( 42 | cls, length: int, patt: Optional[Perm] = None 43 | ) -> Iterator["MeshPatt"]: 44 | """Not implemented, inherited from MeshPatt.""" 45 | raise NotImplementedError 46 | 47 | @classmethod 48 | def random(cls, length: int) -> "BivincularPatt": 49 | """Return a random Bivincular pattern of a given length.""" 50 | return cls( 51 | Perm.random(length), 52 | ( 53 | i 54 | for i, keep in enumerate(randint(0, 1) for _ in range(length + 1)) 55 | if keep 56 | ), 57 | ( 58 | i 59 | for i, keep in enumerate(randint(0, 1) for _ in range(length + 1)) 60 | if keep 61 | ), 62 | ) 63 | 64 | def get_adjacent_requirements(self) -> Tuple[List[int], List[int]]: 65 | """Convert shading into the bivincular requirements. Returned as a tuple of 66 | adjacent indices and adjacent values, both in order. 67 | 68 | Examples: 69 | >>> BivincularPatt(Perm((0, 3, 1, 2)), (0, 1, 2), 70 | ... (0, 2, 4)).get_adjacent_requirements() 71 | ([0, 1, 2], [0, 2, 4]) 72 | """ 73 | n, adj_idx, adj_val = len(self), set(), set() 74 | for x, y in self.shading: 75 | if all((x, i) in self.shading for i in range(n + 1)): 76 | adj_idx.add(x) 77 | if all((i, y) in self.shading for i in range(n + 1)): 78 | adj_val.add(y) 79 | return sorted(adj_idx), sorted(adj_val) 80 | 81 | def occurrences_in(self, patt: Patt, *args, **kwargs) -> Iterator[Tuple[int, ...]]: 82 | """Find all indices of self in patt. Each yielded element is a tuple of integer 83 | indices of the pattern such that 84 | 85 | Classical pattern: 86 | Occurrence of instance's perm in patt if no elements land 87 | in shaded region. 88 | 89 | Mesh pattern (including Bivincular): 90 | Occurrences of instances's perm in the pattern's perm is found, and if 91 | the sub mesh pattern formed by the occurrence indices is a superset of 92 | the instance shading, they are included. 93 | """ 94 | if isinstance(patt, Perm): 95 | # TODO: Optimize me for Bivincular patterns 96 | pass 97 | return super().occurrences_in(patt, args, kwargs) 98 | 99 | def __repr__(self) -> str: 100 | adj_idx, adj_val = self.get_adjacent_requirements() 101 | return f"BivincularPatt({repr(self.pattern)}, {adj_idx}, {adj_val})" 102 | 103 | def __eq__(self, other: object) -> bool: 104 | if isinstance(other, MeshPatt): 105 | return self.pattern == other.pattern and self.shading == other.shading 106 | return False 107 | 108 | def __hash__(self) -> int: 109 | return hash(super()) 110 | 111 | 112 | class VincularPatt(BivincularPatt): 113 | """A vincular pattern class.""" 114 | 115 | def __init__(self, perm: Perm, adjacent_indices: Iterable[int]) -> None: 116 | super().__init__(perm, adjacent_indices, ()) 117 | 118 | @classmethod 119 | def random(cls, length: int) -> "VincularPatt": 120 | """Return a random Vincular pattern of a given length.""" 121 | return cls( 122 | Perm.random(length), 123 | ( 124 | i 125 | for i, keep in enumerate(randint(0, 1) for _ in range(length + 1)) 126 | if keep 127 | ), 128 | ) 129 | 130 | def __repr__(self) -> str: 131 | adj_idx, _ = self.get_adjacent_requirements() 132 | return f"VincularPatt({repr(self.pattern)}, {adj_idx})" 133 | 134 | 135 | class CovincularPatt(BivincularPatt): 136 | """A covincular pattern class.""" 137 | 138 | def __init__(self, perm: Perm, adjacent_values: Iterable[int]) -> None: 139 | super().__init__(perm, (), adjacent_values) 140 | 141 | @classmethod 142 | def random(cls, length: int) -> "CovincularPatt": 143 | """Return a random Covincular pattern of a given length.""" 144 | return cls( 145 | Perm.random(length), 146 | ( 147 | i 148 | for i, keep in enumerate(randint(0, 1) for _ in range(length + 1)) 149 | if keep 150 | ), 151 | ) 152 | 153 | def __repr__(self) -> str: 154 | _, adj_val = self.get_adjacent_requirements() 155 | return f"CovincularPatt({repr(self.pattern)}, {adj_val})" 156 | -------------------------------------------------------------------------------- /permuta/patterns/patt.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import TYPE_CHECKING, Iterator, Tuple 3 | 4 | if TYPE_CHECKING: 5 | # pylint: disable=cyclic-import 6 | from .perm import Perm 7 | 8 | 9 | class Patt(abc.ABC): 10 | """A permutation pattern, e.g. classical, bivincular and mesh patterns.""" 11 | 12 | def avoided_by(self, *patts: "Patt") -> bool: 13 | """Check if self is avoided by all the provided patterns.""" 14 | return all(not patt.contains(self) for patt in patts) 15 | 16 | def contained_in(self, *patts: "Patt") -> bool: 17 | """Check if self is a pattern of all the provided patterns.""" 18 | return all(patt.contains(self) for patt in patts) 19 | 20 | def count_occurrences_in(self, patt: "Patt") -> int: 21 | """Count the number of occurrences of self in the pattern.""" 22 | return sum(1 for _ in self.occurrences_in(patt)) 23 | 24 | @abc.abstractmethod 25 | def occurrences_in( 26 | self, patt: "Patt", *args, **kwargs 27 | ) -> Iterator[Tuple[int, ...]]: 28 | """Find all indices of occurrences of self in pattern.""" 29 | 30 | @abc.abstractmethod 31 | def __len__(self) -> int: 32 | """The length of the pattern.""" 33 | 34 | @abc.abstractmethod 35 | def get_perm(self) -> "Perm": 36 | """Get the permutation part of the pattern""" 37 | 38 | @abc.abstractmethod 39 | def _contains(self, patt: "Patt") -> bool: 40 | """Does pattern contains another?""" 41 | 42 | contains = _contains 43 | 44 | def __contains__(self, patt: "Patt") -> bool: 45 | return self._contains(patt) 46 | -------------------------------------------------------------------------------- /permuta/perm_sets/__init__.py: -------------------------------------------------------------------------------- 1 | from .basis import Basis, MeshBasis 2 | from .permset import Av 3 | 4 | __all__ = ["Av", "Basis", "MeshBasis"] 5 | -------------------------------------------------------------------------------- /permuta/perm_sets/basis.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import Iterable, List, Union 3 | 4 | from ..patterns import MeshPatt, Patt, Perm 5 | 6 | 7 | class Basis(tuple): 8 | """A set of classical patterns such that none is 9 | contained in another within the basis. 10 | """ 11 | 12 | def __new__(cls, *patts: Perm) -> "Basis": 13 | if not patts: 14 | return tuple.__new__(cls, ()) 15 | return cls._pruner(sorted(patts)) 16 | 17 | @classmethod 18 | def from_string(cls, patts: str) -> "Basis": 19 | """Construct a Basis from a string. It can be either 0 or 1 based and 20 | seperated by anything.""" 21 | return cls(*map(Perm.to_standard, re.findall(r"\d+", patts))) 22 | 23 | @classmethod 24 | def from_iterable(cls, patts: Iterable[Perm]) -> "Basis": 25 | """Construct a Basis from an iterable.""" 26 | return cls(*patts) 27 | 28 | @classmethod 29 | def _pruner(cls, patts: List[Perm]) -> "Basis": 30 | if len(patts[0]) == 0: 31 | return tuple.__new__(cls, (patts[0],)) 32 | new_basis: List[Perm] = [] 33 | for patt in patts: 34 | if patt.avoids(*new_basis): 35 | new_basis.append(patt) 36 | return tuple.__new__(cls, new_basis) 37 | 38 | def __eq__(self, other: object) -> bool: 39 | return isinstance(other, self.__class__) and tuple.__eq__(self, other) 40 | 41 | def __hash__(self) -> int: 42 | return tuple.__hash__(self) 43 | 44 | def __repr__(self) -> str: 45 | return f"Basis({tuple.__repr__(self)})" 46 | 47 | def __str__(self) -> str: 48 | return f'{{{", ".join(str(p) for p in self)}}}' 49 | 50 | 51 | class MeshBasis(tuple): 52 | """A set of patterns such that none is 53 | contained in another within the basis. 54 | """ 55 | 56 | @staticmethod 57 | def is_mesh_basis(basis: Union[Patt, Iterable[Patt]]) -> bool: 58 | """Checks if a collection of patterns contains any non-classical ones.""" 59 | if isinstance(basis, Perm): 60 | return False 61 | if isinstance(basis, MeshPatt): 62 | return True 63 | if isinstance(basis, Patt): 64 | raise ValueError 65 | 66 | return any(isinstance(patt, MeshPatt) for patt in basis) 67 | 68 | def __new__(cls, *patts: Union[Perm, MeshPatt]) -> "MeshBasis": 69 | if not patts: 70 | return tuple.__new__(cls, ()) 71 | return cls._pruner( 72 | sorted( 73 | patt if isinstance(patt, MeshPatt) else MeshPatt(patt, []) 74 | for patt in patts 75 | ) 76 | ) 77 | 78 | @classmethod 79 | def from_iterable(cls, patts: Iterable[Union[Perm, MeshPatt]]) -> "MeshBasis": 80 | """Construct a MeshBasis from an iterable.""" 81 | return cls(*patts) 82 | 83 | @classmethod 84 | def _pruner(cls, patts: List[MeshPatt]) -> "MeshBasis": 85 | if len(patts[0]) == 0: 86 | return tuple.__new__(cls, (patts[0],)) 87 | new_basis: List[MeshPatt] = [] 88 | for patt in patts: 89 | if patt.avoids(*new_basis): 90 | new_basis.append(patt) 91 | return tuple.__new__(cls, new_basis) 92 | 93 | def __eq__(self, other: object) -> bool: 94 | return isinstance(other, self.__class__) and tuple.__eq__(self, other) 95 | 96 | def __hash__(self) -> int: 97 | return tuple.__hash__(self) 98 | 99 | def __repr__(self) -> str: 100 | return f"{MeshBasis}({tuple.__repr__(self)})" 101 | 102 | def __str__(self) -> str: 103 | return f'{{{", ".join(str(p) for p in self)}}}' 104 | -------------------------------------------------------------------------------- /permuta/perm_sets/permset.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | from itertools import islice 3 | from typing import ClassVar, Dict, Iterable, List, NamedTuple, Optional, Union 4 | 5 | from ..patterns import MeshPatt, Perm 6 | from ..permutils import is_finite, is_insertion_encodable, is_polynomial 7 | from ..permutils.pin_words import PinWords 8 | from .basis import Basis, MeshBasis 9 | 10 | 11 | class AvBase(NamedTuple): 12 | """A base class for Av to define instance variables without having to use 13 | __init__ in Av. 14 | """ 15 | 16 | basis: Union[Basis, MeshBasis] 17 | cache: List[Dict[Perm, Optional[List[int]]]] 18 | 19 | 20 | class Av(AvBase): 21 | """A permutation class defined by its minimal basis.""" 22 | 23 | _FORBIDDEN_BASIS = Basis(Perm()) 24 | _VALUE_ERROR_MSG = "Basis should be non-empty without the empty perm!" 25 | _BASIS_ONLY_MSG = "Only supported for Basis!" 26 | _CLASS_CACHE: ClassVar[Dict[Union[Basis, MeshBasis], "Av"]] = {} 27 | _CACHE_LOCK = multiprocessing.Lock() 28 | 29 | def __new__( 30 | cls, 31 | basis: Union[ 32 | Basis, 33 | MeshBasis, 34 | Iterable[Perm], 35 | Iterable[Union[Perm, MeshPatt]], 36 | ], 37 | ) -> "Av": 38 | if not isinstance(basis, (Basis, MeshBasis)): 39 | return Av.from_iterable(basis) 40 | if len(basis) == 0 or basis == Av._FORBIDDEN_BASIS: 41 | raise ValueError(Av._VALUE_ERROR_MSG) 42 | instance = Av._CLASS_CACHE.get(basis) 43 | if instance is None: 44 | new_instance: "Av" = AvBase.__new__(cls, basis, [{Perm(): [0]}]) 45 | Av._CLASS_CACHE[basis] = new_instance 46 | return new_instance 47 | return instance 48 | 49 | @classmethod 50 | def clear_cache(cls) -> None: 51 | """Clear the instance cache.""" 52 | cls._CLASS_CACHE = {} 53 | 54 | @classmethod 55 | def from_string(cls, basis) -> "Av": 56 | """Create a permutation class from a string. Basis can be either zero or one 57 | based and seperated by anything. MeshBasis is not supported. 58 | """ 59 | return cls(Basis.from_string(basis)) 60 | 61 | @classmethod 62 | def from_iterable( 63 | cls, basis: Union[Iterable[Perm], Iterable[Union[Perm, MeshPatt]]] 64 | ) -> "Av": 65 | """ 66 | Create a permutation class from a basis defined by an iterable of patterns. 67 | """ 68 | if MeshBasis.is_mesh_basis(basis): 69 | return cls(MeshBasis(*basis)) 70 | return cls(Basis(*basis)) 71 | 72 | def is_finite(self) -> bool: 73 | """Check if the perm class is finite.""" 74 | if isinstance(self.basis, MeshBasis): 75 | raise NotImplementedError(Av._BASIS_ONLY_MSG) 76 | return is_finite(self.basis) 77 | 78 | def is_polynomial(self) -> bool: 79 | """Check if the perm class has polynomial growth.""" 80 | if isinstance(self.basis, MeshBasis): 81 | raise NotImplementedError(Av._BASIS_ONLY_MSG) 82 | return is_polynomial(self.basis) 83 | 84 | def is_insertion_encodable(self) -> bool: 85 | """Check if the perm class is insertion encodable.""" 86 | if isinstance(self.basis, MeshBasis): 87 | raise NotImplementedError(Av._BASIS_ONLY_MSG) 88 | return is_insertion_encodable(self.basis) 89 | 90 | def has_finitely_many_simples(self) -> bool: 91 | """Check if the perm class has finitely many simples.""" 92 | if isinstance(self.basis, MeshBasis): 93 | raise NotImplementedError(Av._BASIS_ONLY_MSG) 94 | return ( 95 | self.is_finite() 96 | or self.is_polynomial() 97 | or PinWords.has_finite_simples(self.basis) 98 | ) 99 | 100 | def first(self, count: int) -> Iterable[Perm]: 101 | """Generate the first `count` permutation in this permutation class given 102 | that it has that many, if not all are generated. 103 | """ 104 | yield from islice(self._all(), count) 105 | 106 | def of_length(self, length: int) -> Iterable[Perm]: 107 | """ 108 | Generate all perms of a given length that belong to this permutation class. 109 | """ 110 | return iter(self._get_level(length)) 111 | 112 | def up_to_length(self, length: int) -> Iterable[Perm]: 113 | """Generate all perms up to and including a given length that 114 | belong to this permutation class. 115 | """ 116 | for n in range(length + 1): 117 | yield from self.of_length(n) 118 | 119 | def count(self, length: int) -> int: 120 | """Return the nubmber of permutations of a given length.""" 121 | return len(self._get_level(length)) 122 | 123 | def enumeration(self, length: int) -> List[int]: 124 | """Return the enumeration of this permutation class up and including a given 125 | length.""" 126 | return [self.count(i) for i in range(length + 1)] 127 | 128 | def __contains__(self, other: object): 129 | if isinstance(other, Perm): 130 | return other in self._get_level(len(other)) 131 | return False 132 | 133 | def is_subclass(self, other: "Av"): 134 | """Check if a sublcass of another permutation class.""" 135 | return all(p1 not in self for p1 in other.basis) 136 | 137 | def _ensure_level(self, level_number: int) -> None: 138 | start = max(0, len(self.cache) - 2) 139 | if isinstance(self.basis, Basis): 140 | self._ensure_level_classical_pattern_basis(level_number) 141 | else: 142 | self._ensure_level_mesh_pattern_basis(level_number) 143 | for i in range(start, level_number - 1): 144 | self.cache[i] = {perm: None for perm in self.cache[i]} 145 | 146 | def _ensure_level_classical_pattern_basis(self, level_number: int) -> None: 147 | # We build new elements from existing ones 148 | lengths = {len(b) for b in self.basis} 149 | max_size = max(lengths) 150 | for nplusone in range(len(self.cache), level_number + 1): 151 | n = nplusone - 1 152 | new_level: Dict[Perm, Optional[List[int]]] = {} 153 | last_level = self.cache[-1] 154 | check_length = nplusone in lengths 155 | smaller_elems = {b for b in self.basis if len(b) == nplusone} 156 | 157 | def valid_insertions(perm): 158 | # pylint: disable=cell-var-from-loop 159 | res = None 160 | for i in range(max(0, n - max_size), n): 161 | val = perm[i] 162 | subperm = perm.remove(i) 163 | spots = self.cache[n - 1][subperm] 164 | assert spots is not None 165 | acceptable = [k for k in spots if k <= val] 166 | acceptable.extend(k + 1 for k in spots if k >= val) 167 | if res is None: 168 | res = frozenset(acceptable) 169 | res = res.intersection(acceptable) 170 | if not res: 171 | break 172 | return res if res is not None else range(nplusone) 173 | 174 | for perm, lis in last_level.items(): 175 | for value in valid_insertions(perm): 176 | new_perm = perm.insert(index=nplusone, new_element=value) 177 | if not check_length or new_perm not in smaller_elems: 178 | new_level[new_perm] = [] 179 | assert lis is not None 180 | lis.append(value) 181 | self.cache.append(new_level) 182 | 183 | def _ensure_level_mesh_pattern_basis(self, level_number: int) -> None: 184 | self.cache.extend( 185 | {p: None for p in Perm.of_length(i) if p.avoids(*self.basis)} 186 | for i in range(len(self.cache), level_number + 1) 187 | ) 188 | 189 | def _get_level(self, level_number: int) -> Dict[Perm, Optional[List[int]]]: 190 | with Av._CACHE_LOCK: 191 | self._ensure_level(level_number) 192 | return self.cache[level_number] 193 | 194 | def _all(self) -> Iterable[Perm]: 195 | length = 0 196 | while True: 197 | gen = (p for p in self.of_length(length)) 198 | first: Optional[Perm] = next(gen, None) 199 | if first is None: 200 | break 201 | yield first 202 | yield from gen 203 | length += 1 204 | 205 | def __str__(self) -> str: 206 | return f"Av({','.join(str(p) for p in self.basis)})" 207 | 208 | def __repr__(self) -> str: 209 | return f"Av({repr(self.basis)})" 210 | -------------------------------------------------------------------------------- /permuta/permutils/__init__.py: -------------------------------------------------------------------------------- 1 | from .finite import is_finite 2 | from .groups import dihedral_group 3 | from .insertion_encodable import InsertionEncodablePerms 4 | from .polynomial import PolyPerms 5 | from .symmetry import ( 6 | all_symmetry_sets, 7 | antidiagonal_set, 8 | complement_set, 9 | inverse_set, 10 | lex_min, 11 | reverse_set, 12 | rotate_90_clockwise_set, 13 | rotate_180_clockwise_set, 14 | rotate_270_clockwise_set, 15 | ) 16 | 17 | is_insertion_encodable = InsertionEncodablePerms.is_insertion_encodable 18 | is_insertion_encodable_maximum = InsertionEncodablePerms.is_insertion_encodable_maximum 19 | is_insertion_encodable_rightmost = ( 20 | InsertionEncodablePerms.is_insertion_encodable_rightmost 21 | ) 22 | is_polynomial = PolyPerms.is_polynomial 23 | is_non_polynomial = PolyPerms.is_non_polynomial 24 | 25 | __all__ = [ 26 | "is_insertion_encodable", 27 | "is_insertion_encodable_maximum", 28 | "is_insertion_encodable_rightmost", 29 | "is_polynomial", 30 | "is_non_polynomial", 31 | "InsertionEncodablePerms", 32 | "PolyPerms", 33 | "is_finite", 34 | "dihedral_group", 35 | "all_symmetry_sets", 36 | "antidiagonal_set", 37 | "complement_set", 38 | "inverse_set", 39 | "lex_min", 40 | "reverse_set", 41 | "rotate_90_clockwise_set", 42 | "rotate_180_clockwise_set", 43 | "rotate_270_clockwise_set", 44 | ] 45 | -------------------------------------------------------------------------------- /permuta/permutils/bijections.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | from permuta import Perm 4 | 5 | 6 | class Bijections: 7 | """A collection of known bijections.""" 8 | 9 | # pylint: disable=too-few-public-methods 10 | 11 | @staticmethod 12 | def simion_and_schmidt(perm: Perm, inverse: bool = False) -> Perm: 13 | """The bijection from `Restricted permutations` by R. Simion and F. Schmidt 14 | between Av(123) and Av(132). 15 | """ 16 | n = len(perm) 17 | if n == 0: 18 | return Perm() 19 | if inverse: 20 | if perm.contains(Perm((0, 2, 1))): 21 | raise ValueError("Map only works for 132 avoiding permutations") 22 | return Bijections._simion_and_schmidt_inv(perm, n) 23 | if perm.contains(Perm((0, 1, 2))): 24 | raise ValueError("Map only works for 123 avoiding permutations") 25 | return Bijections._simion_and_schmidt(perm, n) 26 | 27 | @staticmethod 28 | def _simion_and_schmidt(perm: Perm, n: int) -> Perm: 29 | used, img, min_val = {perm[0]}, [perm[0]] * n, perm[0] 30 | for idx, val in itertools.islice(enumerate(perm), 1, None): 31 | if min_val > val: # val is L2R minima 32 | img[idx], min_val = val, val 33 | else: 34 | img[idx] = next(k for k in range(min_val + 1, n) if k not in used) 35 | used.add(img[idx]) 36 | return Perm(img) 37 | 38 | @staticmethod 39 | def _simion_and_schmidt_inv(perm: Perm, n: int) -> Perm: 40 | used, img, min_val = {perm[0]}, [perm[0]] * n, perm[0] 41 | for idx, val in itertools.islice(enumerate(perm), 1, None): 42 | if min_val > val: # val is L2R minima 43 | img[idx], min_val = val, val 44 | else: 45 | img[idx] = next(k for k in range(n - 1, -1, -1) if k not in used) 46 | used.add(img[idx]) 47 | return Perm(img) 48 | -------------------------------------------------------------------------------- /permuta/permutils/finite.py: -------------------------------------------------------------------------------- 1 | from itertools import tee 2 | from typing import Iterable 3 | 4 | from permuta.patterns.perm import Perm 5 | 6 | 7 | def is_finite(basis: Iterable[Perm]) -> bool: 8 | """Check if a basis is finite, i.e. it contains decreasing and increasing 9 | permutations. 10 | """ 11 | it1, it2 = tee(basis, 2) 12 | return any(perm.is_decreasing() for perm in it1) and any( 13 | perm.is_increasing() for perm in it2 14 | ) 15 | -------------------------------------------------------------------------------- /permuta/permutils/groups.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from typing import Iterable 3 | 4 | from ..patterns import Perm 5 | 6 | 7 | def dihedral_group(n: int) -> Iterable[Perm]: 8 | """Generate dihedral group of length n. 9 | 10 | Examples: 11 | >>> sorted(dihedral_group(4)) 12 | [Perm((0, 1, 2, 3)), Perm((0, 3, 2, 1)), Perm((1, 0, 3, 2)), Perm((1, 2, 3, 0))\ 13 | , Perm((2, 1, 0, 3)), Perm((2, 3, 0, 1)), Perm((3, 0, 1, 2)), Perm((3, 2, 1, 0))] 14 | """ 15 | if n <= 2: 16 | return 17 | increasing, decreasing = deque(range(n)), deque(range(n - 1, -1, -1)) 18 | yield Perm(increasing) 19 | yield Perm(decreasing) 20 | for _ in range(n - 1): 21 | increasing.appendleft(increasing.pop()) 22 | decreasing.appendleft(decreasing.pop()) 23 | yield Perm(increasing) 24 | yield Perm(decreasing) 25 | -------------------------------------------------------------------------------- /permuta/permutils/insertion_encodable.py: -------------------------------------------------------------------------------- 1 | from itertools import islice 2 | from typing import ClassVar, Dict, Iterable, Tuple 3 | 4 | from permuta.patterns.perm import Perm 5 | 6 | 7 | class InsertionEncodablePerms: 8 | """A static container of functions fortesting 9 | if a basis has a regular insertion encoding. 10 | """ 11 | 12 | _ALL_PROPERTIES: ClassVar[int] = 15 13 | _CACHE: ClassVar[Dict[Tuple, int]] = {} 14 | 15 | @staticmethod 16 | def _is_incr_next_incr(perm: Perm) -> bool: 17 | n = len(perm) 18 | return not any( 19 | curr < prev and any(perm[j + 1] < perm[j] for j in range(i + 1, n - 1)) 20 | for i, (prev, curr) in enumerate(zip(perm, islice(perm, 1, None))) 21 | ) 22 | 23 | @staticmethod 24 | def _is_incr_next_decr(perm: Perm) -> bool: 25 | n = len(perm) 26 | return not any( 27 | curr < prev and any(perm[j + 1] > perm[j] for j in range(i + 1, n - 1)) 28 | for i, (prev, curr) in enumerate(zip(perm, islice(perm, 1, None))) 29 | ) 30 | 31 | @staticmethod 32 | def _is_decr_next_incr(perm: Perm) -> bool: 33 | n = len(perm) 34 | return not any( 35 | curr > prev and any(perm[j + 1] < perm[j] for j in range(i + 1, n - 1)) 36 | for i, (prev, curr) in enumerate(zip(perm, islice(perm, 1, None))) 37 | ) 38 | 39 | @staticmethod 40 | def _is_decr_next_decr(perm: Perm) -> bool: 41 | n = len(perm) 42 | return not any( 43 | curr > prev and any(perm[j + 1] > perm[j] for j in range(i + 1, n - 1)) 44 | for i, (prev, curr) in enumerate(zip(perm, islice(perm, 1, None))) 45 | ) 46 | 47 | @staticmethod 48 | def _insertion_encodable_properties(perm: Perm) -> int: 49 | properties = InsertionEncodablePerms._CACHE.get(perm, -1) 50 | if properties < 0: 51 | properties = sum( 52 | val << shift 53 | for shift, val in enumerate( 54 | ( 55 | InsertionEncodablePerms._is_incr_next_decr(perm), 56 | InsertionEncodablePerms._is_incr_next_incr(perm), 57 | InsertionEncodablePerms._is_decr_next_decr(perm), 58 | InsertionEncodablePerms._is_decr_next_incr(perm), 59 | ) 60 | ) 61 | ) 62 | InsertionEncodablePerms._CACHE[perm] = properties 63 | return properties 64 | 65 | @staticmethod 66 | def is_insertion_encodable_rightmost(basis: Iterable[Perm]) -> bool: 67 | """Check if basis is insertion encodable by rightmost.""" 68 | curr = 0 69 | for perm in basis: 70 | curr = curr | InsertionEncodablePerms._insertion_encodable_properties(perm) 71 | if curr == InsertionEncodablePerms._ALL_PROPERTIES: 72 | return True 73 | return False 74 | 75 | @staticmethod 76 | def is_insertion_encodable_maximum(basis: Iterable[Perm]) -> bool: 77 | """Check if basis is insertion encodable by maximum.""" 78 | curr = 0 79 | for perm in basis: 80 | curr = curr | InsertionEncodablePerms._insertion_encodable_properties( 81 | perm.rotate() 82 | ) 83 | if curr == InsertionEncodablePerms._ALL_PROPERTIES: 84 | return True 85 | return False 86 | 87 | @staticmethod 88 | def is_insertion_encodable(basis: Iterable[Perm]) -> bool: 89 | """Check if basis is insertion encodable.""" 90 | return InsertionEncodablePerms.is_insertion_encodable_rightmost( 91 | basis 92 | ) or InsertionEncodablePerms.is_insertion_encodable_maximum(basis) 93 | -------------------------------------------------------------------------------- /permuta/permutils/pinword_util.py: -------------------------------------------------------------------------------- 1 | from fractions import Fraction 2 | from typing import Callable, Dict, List, Tuple 3 | 4 | 5 | class PinWordUtil: 6 | """Utility class for pinwords""" 7 | 8 | def __init__(self) -> None: 9 | self.caller: Dict[ 10 | str, Callable[[List[Tuple[Fraction, Fraction]]], Tuple[Fraction, Fraction]] 11 | ] = { 12 | "1": self.char_1, 13 | "2": self.char_2, 14 | "3": self.char_3, 15 | "4": self.char_4, 16 | "U": self.char_u, 17 | "L": self.char_l, 18 | "D": self.char_d, 19 | "R": self.char_r, 20 | } 21 | self.zero = Fraction(0, 1) 22 | self.one = Fraction(1, 1) 23 | self.half = Fraction(1, 2) 24 | 25 | @staticmethod 26 | def rzero() -> Fraction: 27 | """Returns the fraction zero""" 28 | return Fraction(0, 1) 29 | 30 | def call(self, char, pre_perm) -> Tuple[Fraction, Fraction]: 31 | """Main help function""" 32 | return self.caller[char](pre_perm) 33 | 34 | def char_1( 35 | self, pre_perm: List[Tuple[Fraction, Fraction]] 36 | ) -> Tuple[Fraction, Fraction]: 37 | """.""" 38 | next_x = PinWordUtil.max_x(pre_perm) + self.one 39 | next_y = PinWordUtil.max_y(pre_perm) + self.one 40 | return next_x, next_y 41 | 42 | def char_2( 43 | self, pre_perm: List[Tuple[Fraction, Fraction]] 44 | ) -> Tuple[Fraction, Fraction]: 45 | """.""" 46 | next_x = PinWordUtil.min_x(pre_perm) - self.one 47 | next_y = PinWordUtil.max_y(pre_perm) + self.one 48 | return next_x, next_y 49 | 50 | def char_3( 51 | self, pre_perm: List[Tuple[Fraction, Fraction]] 52 | ) -> Tuple[Fraction, Fraction]: 53 | """.""" 54 | next_x = PinWordUtil.min_x(pre_perm) - self.one 55 | next_y = PinWordUtil.min_y(pre_perm) - self.one 56 | return next_x, next_y 57 | 58 | def char_4( 59 | self, pre_perm: List[Tuple[Fraction, Fraction]] 60 | ) -> Tuple[Fraction, Fraction]: 61 | """.""" 62 | next_x = PinWordUtil.max_x(pre_perm) + self.one 63 | next_y = PinWordUtil.min_y(pre_perm) - self.one 64 | return next_x, next_y 65 | 66 | def char_u( 67 | self, pre_perm: List[Tuple[Fraction, Fraction]] 68 | ) -> Tuple[Fraction, Fraction]: 69 | """.""" 70 | (last_x, _) = pre_perm[-1] 71 | if last_x > PinWordUtil.max_x(pre_perm[:-1]): 72 | next_x = self.half * (last_x + PinWordUtil.max_x(pre_perm[:-1])) 73 | next_y = PinWordUtil.max_y(pre_perm) + self.one 74 | elif last_x < PinWordUtil.min_x(pre_perm[:-1]): 75 | next_x = self.half * (last_x + PinWordUtil.min_x(pre_perm[:-1])) 76 | next_y = PinWordUtil.max_y(pre_perm) + self.one 77 | else: 78 | assert False 79 | return next_x, next_y 80 | 81 | def char_l( 82 | self, pre_perm: List[Tuple[Fraction, Fraction]] 83 | ) -> Tuple[Fraction, Fraction]: 84 | """.""" 85 | _, last_y = pre_perm[-1] 86 | if last_y > PinWordUtil.max_y(pre_perm[:-1]): 87 | next_x = PinWordUtil.min_x(pre_perm) - self.one 88 | next_y = self.half * (last_y + PinWordUtil.max_y(pre_perm[:-1])) 89 | elif last_y < PinWordUtil.min_y(pre_perm[:-1]): 90 | next_x = PinWordUtil.min_x(pre_perm) - self.one 91 | next_y = self.half * (last_y + PinWordUtil.min_y(pre_perm[:-1])) 92 | else: 93 | assert False 94 | return next_x, next_y 95 | 96 | def char_d( 97 | self, pre_perm: List[Tuple[Fraction, Fraction]] 98 | ) -> Tuple[Fraction, Fraction]: 99 | """.""" 100 | (last_x, _) = pre_perm[-1] 101 | if last_x > PinWordUtil.max_x(pre_perm[:-1]): 102 | next_x = self.half * (last_x + PinWordUtil.max_x(pre_perm[:-1])) 103 | next_y = PinWordUtil.min_y(pre_perm) - self.one 104 | elif last_x < PinWordUtil.min_x(pre_perm[:-1]): 105 | next_x = self.half * (last_x + PinWordUtil.min_x(pre_perm[:-1])) 106 | next_y = PinWordUtil.min_y(pre_perm) - self.one 107 | else: 108 | assert False 109 | return next_x, next_y 110 | 111 | def char_r( 112 | self, pre_perm: List[Tuple[Fraction, Fraction]] 113 | ) -> Tuple[Fraction, Fraction]: 114 | """.""" 115 | _, last_y = pre_perm[-1] 116 | if last_y > PinWordUtil.max_y(pre_perm[:-1]): 117 | next_x = PinWordUtil.max_x(pre_perm) + self.one 118 | next_y = self.half * (last_y + PinWordUtil.max_y(pre_perm[:-1])) 119 | elif last_y < PinWordUtil.min_y(pre_perm[:-1]): 120 | next_x = PinWordUtil.max_x(pre_perm) + self.one 121 | next_y = self.half * (last_y + PinWordUtil.min_y(pre_perm[:-1])) 122 | else: 123 | assert False 124 | return next_x, next_y 125 | 126 | @staticmethod 127 | def min_x(pre_perm: List[Tuple[Fraction, Fraction]]) -> Fraction: 128 | """Utility function""" 129 | return min(pre_perm, key=lambda x: x[0])[0] 130 | 131 | @staticmethod 132 | def max_x(pre_perm: List[Tuple[Fraction, Fraction]]) -> Fraction: 133 | """Utility function""" 134 | return max(pre_perm, key=lambda x: x[0])[0] 135 | 136 | @staticmethod 137 | def min_y(pre_perm: List[Tuple[Fraction, Fraction]]) -> Fraction: 138 | """Utility function""" 139 | return min(pre_perm, key=lambda x: x[1])[1] 140 | 141 | @staticmethod 142 | def max_y(pre_perm: List[Tuple[Fraction, Fraction]]) -> Fraction: 143 | """Utility function""" 144 | return max(pre_perm, key=lambda x: x[1])[1] 145 | -------------------------------------------------------------------------------- /permuta/permutils/polynomial.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from enum import Enum 3 | from itertools import islice 4 | from typing import ClassVar, Deque, Dict, FrozenSet, Iterable, Iterator, Tuple 5 | 6 | from permuta.patterns.perm import Perm 7 | 8 | 9 | class PermType(Enum): 10 | """Collection of structural types for perms.""" 11 | 12 | WPP = 0 # W++ 13 | WPM = 1 # W+- 14 | WMP = 2 # W-+ 15 | WMM = 3 # W-- 16 | WIPP = 4 # Winv++ 17 | WIPM = 5 # Winv+- 18 | WIMP = 6 # Winv-+ 19 | WIMM = 7 # Winv-- 20 | L2 = 8 # L2 21 | L2I = 9 # L2inv 22 | 23 | 24 | class PolyPerms: 25 | """A static container of methods to check if a perm set has a polynomial growth.""" 26 | 27 | _CACHE: ClassVar[Dict[Perm, FrozenSet]] = {} 28 | 29 | @staticmethod 30 | def _types(perm: Perm) -> FrozenSet[int]: 31 | interset = PolyPerms._CACHE.get(perm) 32 | if interset is None: 33 | interset = frozenset(PolyPerms._find_type(perm)) 34 | PolyPerms._CACHE[perm] = interset 35 | return interset 36 | 37 | @staticmethod 38 | def _type_0_3(slice1: Deque[int], slice2: Deque[int]) -> Iterator[PermType]: 39 | if PolyPerms._is_incr(slice1): 40 | if PolyPerms._is_incr(slice2): 41 | yield PermType.WPP 42 | if PolyPerms._is_decr(slice2): 43 | yield PermType.WPM 44 | if PolyPerms._is_decr(slice1): 45 | if PolyPerms._is_incr(slice2): 46 | yield PermType.WMP 47 | if PolyPerms._is_decr(slice2): 48 | yield PermType.WMM 49 | 50 | @staticmethod 51 | def _type_4_7(slice1: Deque[int], slice2: Deque[int]) -> Iterator[PermType]: 52 | if PolyPerms._is_incr(slice1): 53 | if PolyPerms._is_incr(slice2): 54 | yield PermType.WIPP 55 | if PolyPerms._is_decr(slice2): 56 | yield PermType.WIPM 57 | if PolyPerms._is_decr(slice1): 58 | if PolyPerms._is_incr(slice2): 59 | yield PermType.WIMP 60 | if PolyPerms._is_decr(slice2): 61 | yield PermType.WIMM 62 | 63 | @staticmethod 64 | def _find_type(perm: Perm) -> Iterable[PermType]: 65 | p_deq1: Deque[int] = deque([]) 66 | p_deq2: Deque[int] = deque(perm) 67 | fp_deq1: Deque[int] = deque([]) 68 | fp_deq2: Deque[int] = deque(perm.inverse()) 69 | yield from PolyPerms._type_0_3(p_deq1, p_deq2) 70 | yield from PolyPerms._type_4_7(fp_deq1, fp_deq2) 71 | for _ in range(len(perm)): 72 | p_deq1.append(p_deq2.popleft()) 73 | fp_deq1.append(fp_deq2.popleft()) 74 | yield from PolyPerms._type_0_3(p_deq1, p_deq2) 75 | yield from PolyPerms._type_4_7(fp_deq1, fp_deq2) 76 | if PolyPerms._of_type_8(perm): 77 | yield PermType.L2 78 | if PolyPerms._of_type_8(perm.reverse()): 79 | yield PermType.L2I 80 | 81 | @staticmethod 82 | def _is_decr(perm_slice: Deque[int]) -> bool: 83 | return all( 84 | prev > curr for prev, curr in zip(perm_slice, islice(perm_slice, 1, None)) 85 | ) 86 | 87 | @staticmethod 88 | def _is_incr(perm_slice: Deque[int]) -> bool: 89 | return all( 90 | prev < curr for prev, curr in zip(perm_slice, islice(perm_slice, 1, None)) 91 | ) 92 | 93 | @staticmethod 94 | def _of_type_8(perm_slice: Tuple[int, ...]) -> bool: 95 | n = len(perm_slice) 96 | if n < 2: 97 | return True 98 | if perm_slice[-1] == n - 1: 99 | return PolyPerms._of_type_8(perm_slice[0 : n - 1]) 100 | if perm_slice[-1] == n - 2 and perm_slice[-2] == n - 1: 101 | return PolyPerms._of_type_8(perm_slice[0 : n - 2]) 102 | return False 103 | 104 | @staticmethod 105 | def is_polynomial(basis: Iterable[Perm]) -> bool: 106 | """True if the perm set generated by basis has polynomial growth.""" 107 | return ( 108 | len({pol_type for perm in basis for pol_type in PolyPerms._types(perm)}) 109 | == 10 110 | ) 111 | 112 | @staticmethod 113 | def is_non_polynomial(basis: Iterable[Perm]) -> bool: 114 | """False if the perm set generated by basis has polynomial growth.""" 115 | return not PolyPerms.is_polynomial(basis) 116 | -------------------------------------------------------------------------------- /permuta/permutils/statistics.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from itertools import combinations, permutations, product 3 | from typing import Callable, Counter, Dict, Iterator, List, Optional, Tuple 4 | 5 | from permuta import Av, Perm 6 | 7 | PermutationStatisticType = Callable[[Perm], int] 8 | BijectionType = Dict[Perm, Perm] 9 | 10 | 11 | def _count_descents(perm: Perm) -> int: 12 | return perm.count_descents() 13 | 14 | 15 | def _count_ascents(perm: Perm) -> int: 16 | return perm.count_ascents() 17 | 18 | 19 | class PermutationStatistic: 20 | """ 21 | A class for checking preservation of statistics 22 | in bijections and their distribution. 23 | """ 24 | 25 | # More statistics can be added here. 26 | _STATISTICS = ( 27 | ("Number of inversions", Perm.count_inversions), 28 | ("Number of non-inversions", Perm.count_non_inversions), 29 | ("Major index", Perm.major_index), 30 | ("Number of descents", _count_descents), 31 | ("Number of ascents", _count_ascents), 32 | ("Number of peaks", Perm.count_peaks), 33 | ("Number of valleys", Perm.count_valleys), 34 | ("Number of cycles", Perm.count_cycles), 35 | ("Number of left-to-right minimas", Perm.count_ltrmin), 36 | ("Number of left-to-right maximas", Perm.count_ltrmax), 37 | ("Number of right-to-left minimas", Perm.count_rtlmin), 38 | ("Number of right-to-left maximas", Perm.count_rtlmax), 39 | ("Number of fixed points", Perm.count_fixed_points), 40 | ("Order", Perm.order), 41 | ("Longest increasing subsequence", Perm.length_of_longestrun_ascending), 42 | ("Longest decreasing subsequence", Perm.length_of_longestrun_descending), 43 | ("Depth", Perm.depth), 44 | ("Number of bounces", Perm.count_bounces), 45 | ("Maximum drop size", Perm.max_drop_size), 46 | ("Number of primes in the column sums", Perm.count_column_sum_primes), 47 | ("Holeyness of a permutation", Perm.holeyness), 48 | ("Number of stack-sorts needed", Perm.count_stack_sorts), 49 | ("Number of pop-stack-sorts needed", Perm.count_pop_stack_sorts), 50 | ("Number of pinnacles", Perm.count_pinnacles), 51 | ("Number of cyclic peaks", Perm.count_cyclic_peaks), 52 | ("Number of cyclic valleys", Perm.count_cyclic_valleys), 53 | ("Number of double excedance", Perm.count_double_excedance), 54 | ("Number of double drops", Perm.count_double_drops), 55 | ("Number of foremaxima", Perm.count_foremaxima), 56 | ("Number of afterminima", Perm.count_afterminima), 57 | ("Number of aftermaxima", Perm.count_aftermaxima), 58 | ("Number of foreminima", Perm.count_foreminima), 59 | ) 60 | 61 | @staticmethod 62 | def show_predefined_statistics(idx: int = -1) -> None: 63 | """Show all or a specific predefined statistic.""" 64 | if idx < 0: 65 | print(PermutationStatistic._predefined_statistics()) 66 | else: 67 | print(PermutationStatistic._STATISTICS[idx][0]) 68 | 69 | @staticmethod 70 | def _predefined_statistics() -> str: 71 | """Name and index of each statistics defined.""" 72 | return "\n".join( 73 | f"[{i}] {name}" 74 | for i, (name, _) in enumerate(PermutationStatistic._STATISTICS) 75 | ) 76 | 77 | @classmethod 78 | def get_by_index(cls, idx: int) -> "PermutationStatistic": 79 | """Get a statistic by index.""" 80 | return cls(*PermutationStatistic._STATISTICS[idx]) 81 | 82 | def __init__(self, name: str, func: PermutationStatisticType) -> None: 83 | self.name: str = name 84 | self.func: PermutationStatisticType = func 85 | 86 | def preserved_in(self, bijection: BijectionType) -> bool: 87 | """Check if statistic (self) is preserved in a bijection.""" 88 | return all(self.func(k) == self.func(v) for k, v in bijection.items()) 89 | 90 | def distribution_for_length( 91 | self, n: int, perm_class: Optional[Av] = None 92 | ) -> List[int]: 93 | """Return a distribution of statistic for a fixed length of permutations. If a 94 | class is not provided, we use the set of all permutations. 95 | """ 96 | iterator = perm_class.of_length(n) if perm_class else Perm.of_length(n) 97 | cnt = Counter(self.func(p) for p in iterator) 98 | lis = [0] * (max(cnt.keys(), default=0) + 1) 99 | for key, val in cnt.items(): 100 | lis[key] = val 101 | return lis 102 | 103 | def distribution_up_to( 104 | self, n: int, perm_class: Optional[Av] = None 105 | ) -> List[List[int]]: 106 | """Return a table (i,k) for the distribution of a statistic. Here i=0..n is the 107 | length of the permutation and k is the statistic. If a class is not provided, 108 | we use the set of all permutations. 109 | """ 110 | return [self.distribution_for_length(i, perm_class) for i in range(n + 1)] 111 | 112 | @classmethod 113 | def equally_distributed(cls, class1: Av, class2: Av, n: int = 6) -> Iterator[str]: 114 | """Return all stats that are equally distributed for two classes up to a max 115 | length. 116 | """ 117 | return ( 118 | stat.name 119 | for stat in cls._get_all() 120 | if all( 121 | stat.distribution_for_length(i, class1) 122 | == stat.distribution_for_length(i, class2) 123 | for i in range(n + 1) 124 | ) 125 | ) 126 | 127 | @staticmethod 128 | def jointly_equally_distributed( 129 | class1: Av, class2: Av, n: int = 6, dim: int = 2 130 | ) -> Iterator[Tuple[str, ...]]: 131 | """Check if a combination of statistics is equally distributed between 132 | two classes up to a max length. 133 | """ 134 | return ( 135 | tuple(stat[0] for stat in stats) 136 | for stats in combinations(PermutationStatistic._STATISTICS, dim) 137 | if all( 138 | Counter( 139 | tuple(stat[1](p) for stat in stats) for p in class1.of_length(i) 140 | ) 141 | == Counter( 142 | tuple(stat[1](p) for stat in stats) for p in class2.of_length(i) 143 | ) 144 | for i in range(n + 1) 145 | ) 146 | ) 147 | 148 | @staticmethod 149 | def jointly_transformed_equally_distributed( 150 | class1: Av, class2: Av, n: int = 6, dim: int = 2 151 | ) -> Iterator[Tuple[Tuple[str, ...], Tuple[str, ...]]]: 152 | """Check if a combination of statistics in one class is equally distributed 153 | to any combination of statistics in the other class, up to a max length. 154 | """ 155 | return ( 156 | (tuple(stat[0] for stat in stats1), tuple(stat[0] for stat in stats2)) 157 | for stats1, stats2 in combinations( 158 | permutations(PermutationStatistic._STATISTICS, dim), 2 159 | ) 160 | if all( 161 | Counter( 162 | tuple(stat[1](p) for stat in stats1) for p in class1.of_length(i) 163 | ) 164 | == Counter( 165 | tuple(stat[1](p) for stat in stats2) for p in class2.of_length(i) 166 | ) 167 | for i in range(n + 1) 168 | ) 169 | ) 170 | 171 | def __str__(self) -> str: 172 | return self.name 173 | 174 | @classmethod 175 | def _get_all(cls) -> Iterator["PermutationStatistic"]: 176 | """Get all predefined statistics as an instance of PermutationStatistic.""" 177 | yield from (cls(name, func) for name, func in PermutationStatistic._STATISTICS) 178 | 179 | @classmethod 180 | def check_all_preservations(cls, bijection: BijectionType) -> Iterator[str]: 181 | """Given a bijection, check which statistics are preserved.""" 182 | return (stats.name for stats in cls._get_all() if stats.preserved_in(bijection)) 183 | 184 | @classmethod 185 | def check_all_transformed(cls, bijection: BijectionType) -> Dict[str, List[str]]: 186 | """Given a bijection, check what statistics transform into others.""" 187 | transf = defaultdict(list) 188 | all_stats = cls._get_all() 189 | for stat1, stat2 in product(all_stats, all_stats): 190 | if all(stat1.func(k) == stat2.func(v) for k, v in bijection.items()): 191 | transf[stat1.name].append(stat2.name) 192 | return dict(transf) 193 | 194 | @staticmethod 195 | def symmetry_duplication( 196 | bijection: BijectionType, 197 | ) -> Iterator[BijectionType]: 198 | """Yield all symmetric versions of a bijection.""" 199 | return ( 200 | bij 201 | for rotated in ( 202 | {k.rotate(angle): v.rotate(angle) for k, v in bijection.items()} 203 | for angle in range(4) 204 | ) 205 | for bij in (rotated, {k.inverse(): v.inverse() for k, v in rotated.items()}) 206 | ) 207 | 208 | # Some common ones for easy access 209 | 210 | @classmethod 211 | def inv(cls) -> "PermutationStatistic": 212 | """Number of inversions.""" 213 | return cls("Number of inversions", Perm.count_inversions) 214 | 215 | @classmethod 216 | def maj(cls) -> "PermutationStatistic": 217 | """Major index.""" 218 | return cls("Major index", Perm.major_index) 219 | 220 | @classmethod 221 | def des(cls) -> "PermutationStatistic": 222 | """Number of descents.""" 223 | return cls("Number of descents", Perm.count_descents) 224 | 225 | @classmethod 226 | def asc(cls) -> "PermutationStatistic": 227 | """Number of ascents.""" 228 | return cls("Number of ascents", Perm.count_ascents) 229 | -------------------------------------------------------------------------------- /permuta/permutils/symmetry.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable 2 | 3 | from permuta.patterns.perm import Iterator, Perm, Set, Tuple 4 | 5 | 6 | def rotate_90_clockwise_set(perms: Iterable[Perm]) -> Iterator[Perm]: 7 | """Rotate all perms by 90 degrees.""" 8 | return (perm.rotate() for perm in perms) 9 | 10 | 11 | def rotate_180_clockwise_set(perms: Iterable[Perm]) -> Iterator[Perm]: 12 | """Rotate all perms by 180 degrees.""" 13 | return (perm.rotate(2) for perm in perms) 14 | 15 | 16 | def rotate_270_clockwise_set(perms: Iterable[Perm]) -> Iterator[Perm]: 17 | """Rotate all perms by 270 degrees.""" 18 | return (perm.rotate(3) for perm in perms) 19 | 20 | 21 | def inverse_set(perms: Iterable[Perm]) -> Iterator[Perm]: 22 | """Return the inverse of each permutation a collection of perms.""" 23 | return (perm.inverse() for perm in perms) 24 | 25 | 26 | def reverse_set(perms: Iterable[Perm]) -> Iterator[Perm]: 27 | """Return the reverse of each permutation a collection of perms.""" 28 | return (perm.reverse() for perm in perms) 29 | 30 | 31 | def complement_set(perms: Iterable[Perm]) -> Iterator[Perm]: 32 | """Return the complement of each permutation in a collection of perms.""" 33 | return (perm.complement() for perm in perms) 34 | 35 | 36 | def antidiagonal_set(perms: Iterable[Perm]) -> Iterator[Perm]: 37 | """Return the antidiagonal of each permutation in a collection of perms.""" 38 | return (perm.flip_antidiagonal() for perm in perms) 39 | 40 | 41 | def all_symmetry_sets(perms: Iterable[Perm]) -> Set[Tuple[Perm, ...]]: 42 | """Given a collection of perms, return all possible collections formed by applying 43 | symmetric operations on all perms. Each group has their permutation sorted. 44 | """ 45 | perms = perms if isinstance(perms, list) else list(perms) 46 | answer = {tuple(sorted(perms)), tuple(sorted(inverse_set(perms)))} 47 | for _ in range(3): 48 | perms = list(rotate_90_clockwise_set(perms)) 49 | answer.add(tuple(sorted(perms))) 50 | answer.add(tuple(sorted(inverse_set(perms)))) 51 | return answer 52 | 53 | 54 | def lex_min(perms: Iterable[Perm]) -> Tuple[Perm, ...]: 55 | """Find the lexicographical minimum of the sets of all symmetries.""" 56 | return min(all_symmetry_sets(perms)) 57 | -------------------------------------------------------------------------------- /permuta/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PermutaTriangle/Permuta/ce704c07ce10ab131d1098c7a7eaef077ca19a82/permuta/py.typed -------------------------------------------------------------------------------- /permuta/resources/bisc/dihedral_good_len8.json: -------------------------------------------------------------------------------- 1 | {"0": [], "1": [], "2": [], "3": [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]], "4": [[0, 1, 2, 3], [0, 3, 2, 1], [1, 0, 3, 2], [1, 2, 3, 0], [2, 1, 0, 3], [2, 3, 0, 1], [3, 0, 1, 2], [3, 2, 1, 0]], "5": [[0, 1, 2, 3, 4], [0, 4, 3, 2, 1], [1, 0, 4, 3, 2], [1, 2, 3, 4, 0], [2, 1, 0, 4, 3], [2, 3, 4, 0, 1], [3, 2, 1, 0, 4], [3, 4, 0, 1, 2], [4, 0, 1, 2, 3], [4, 3, 2, 1, 0]], "6": [[0, 1, 2, 3, 4, 5], [0, 5, 4, 3, 2, 1], [1, 0, 5, 4, 3, 2], [1, 2, 3, 4, 5, 0], [2, 1, 0, 5, 4, 3], [2, 3, 4, 5, 0, 1], [3, 2, 1, 0, 5, 4], [3, 4, 5, 0, 1, 2], [4, 3, 2, 1, 0, 5], [4, 5, 0, 1, 2, 3], [5, 0, 1, 2, 3, 4], [5, 4, 3, 2, 1, 0]], "7": [[0, 1, 2, 3, 4, 5, 6], [0, 6, 5, 4, 3, 2, 1], [1, 0, 6, 5, 4, 3, 2], [1, 2, 3, 4, 5, 6, 0], [2, 1, 0, 6, 5, 4, 3], [2, 3, 4, 5, 6, 0, 1], [3, 2, 1, 0, 6, 5, 4], [3, 4, 5, 6, 0, 1, 2], [4, 3, 2, 1, 0, 6, 5], [4, 5, 6, 0, 1, 2, 3], [5, 4, 3, 2, 1, 0, 6], [5, 6, 0, 1, 2, 3, 4], [6, 0, 1, 2, 3, 4, 5], [6, 5, 4, 3, 2, 1, 0]], "8": [[0, 1, 2, 3, 4, 5, 6, 7], [0, 7, 6, 5, 4, 3, 2, 1], [1, 0, 7, 6, 5, 4, 3, 2], [1, 2, 3, 4, 5, 6, 7, 0], [2, 1, 0, 7, 6, 5, 4, 3], [2, 3, 4, 5, 6, 7, 0, 1], [3, 2, 1, 0, 7, 6, 5, 4], [3, 4, 5, 6, 7, 0, 1, 2], [4, 3, 2, 1, 0, 7, 6, 5], [4, 5, 6, 7, 0, 1, 2, 3], [5, 4, 3, 2, 1, 0, 7, 6], [5, 6, 7, 0, 1, 2, 3, 4], [6, 5, 4, 3, 2, 1, 0, 7], [6, 7, 0, 1, 2, 3, 4, 5], [7, 0, 1, 2, 3, 4, 5, 6], [7, 6, 5, 4, 3, 2, 1, 0]]} -------------------------------------------------------------------------------- /permuta/resources/bisc/quick_sortable_good_len8.json: -------------------------------------------------------------------------------- 1 | {"0": [[]], "1": [[0]], "2": [[0, 1], [1, 0]], "3": [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1]], "4": [[0, 1, 2, 3], [0, 1, 3, 2], [0, 2, 1, 3], [0, 2, 3, 1], [0, 3, 1, 2], [1, 0, 2, 3], [1, 2, 0, 3], [1, 2, 3, 0], [2, 0, 1, 3], [2, 0, 3, 1], [2, 3, 0, 1], [3, 0, 1, 2]], "5": [[0, 1, 2, 3, 4], [0, 1, 2, 4, 3], [0, 1, 3, 2, 4], [0, 1, 3, 4, 2], [0, 1, 4, 2, 3], [0, 2, 1, 3, 4], [0, 2, 3, 1, 4], [0, 2, 3, 4, 1], [0, 3, 1, 2, 4], [0, 3, 1, 4, 2], [0, 3, 4, 1, 2], [0, 4, 1, 2, 3], [1, 0, 2, 3, 4], [1, 0, 2, 4, 3], [1, 2, 0, 3, 4], [1, 2, 3, 0, 4], [1, 2, 3, 4, 0], [2, 0, 1, 3, 4], [2, 0, 3, 1, 4], [2, 0, 3, 4, 1], [2, 3, 0, 1, 4], [2, 3, 0, 4, 1], [2, 3, 4, 0, 1], [3, 0, 1, 2, 4], [3, 0, 1, 4, 2], [3, 0, 4, 1, 2], [3, 4, 0, 1, 2], [4, 0, 1, 2, 3]], "6": [[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 5, 4], [0, 1, 2, 4, 3, 5], [0, 1, 2, 4, 5, 3], [0, 1, 2, 5, 3, 4], [0, 1, 3, 2, 4, 5], [0, 1, 3, 4, 2, 5], [0, 1, 3, 4, 5, 2], [0, 1, 4, 2, 3, 5], [0, 1, 4, 2, 5, 3], [0, 1, 4, 5, 2, 3], [0, 1, 5, 2, 3, 4], [0, 2, 1, 3, 4, 5], [0, 2, 1, 3, 5, 4], [0, 2, 3, 1, 4, 5], [0, 2, 3, 4, 1, 5], [0, 2, 3, 4, 5, 1], [0, 3, 1, 2, 4, 5], [0, 3, 1, 4, 2, 5], [0, 3, 1, 4, 5, 2], [0, 3, 4, 1, 2, 5], [0, 3, 4, 1, 5, 2], [0, 3, 4, 5, 1, 2], [0, 4, 1, 2, 3, 5], [0, 4, 1, 2, 5, 3], [0, 4, 1, 5, 2, 3], [0, 4, 5, 1, 2, 3], [0, 5, 1, 2, 3, 4], [1, 0, 2, 3, 4, 5], [1, 0, 2, 3, 5, 4], [1, 0, 2, 4, 3, 5], [1, 0, 2, 4, 5, 3], [1, 0, 2, 5, 3, 4], [1, 2, 0, 3, 4, 5], [1, 2, 0, 3, 5, 4], [1, 2, 3, 0, 4, 5], [1, 2, 3, 4, 0, 5], [1, 2, 3, 4, 5, 0], [2, 0, 1, 3, 4, 5], [2, 0, 1, 3, 5, 4], [2, 0, 3, 1, 4, 5], [2, 0, 3, 4, 1, 5], [2, 0, 3, 4, 5, 1], [2, 3, 0, 1, 4, 5], [2, 3, 0, 4, 1, 5], [2, 3, 0, 4, 5, 1], [2, 3, 4, 0, 1, 5], [2, 3, 4, 0, 5, 1], [2, 3, 4, 5, 0, 1], [3, 0, 1, 2, 4, 5], [3, 0, 1, 4, 2, 5], [3, 0, 1, 4, 5, 2], [3, 0, 4, 1, 2, 5], [3, 0, 4, 1, 5, 2], [3, 0, 4, 5, 1, 2], [3, 4, 0, 1, 2, 5], [3, 4, 0, 1, 5, 2], [3, 4, 0, 5, 1, 2], [3, 4, 5, 0, 1, 2], [4, 0, 1, 2, 3, 5], [4, 0, 1, 2, 5, 3], [4, 0, 1, 5, 2, 3], [4, 0, 5, 1, 2, 3], [4, 5, 0, 1, 2, 3], [5, 0, 1, 2, 3, 4]], "7": [[0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 6, 5], [0, 1, 2, 3, 5, 4, 6], [0, 1, 2, 3, 5, 6, 4], [0, 1, 2, 3, 6, 4, 5], [0, 1, 2, 4, 3, 5, 6], [0, 1, 2, 4, 5, 3, 6], [0, 1, 2, 4, 5, 6, 3], [0, 1, 2, 5, 3, 4, 6], [0, 1, 2, 5, 3, 6, 4], [0, 1, 2, 5, 6, 3, 4], [0, 1, 2, 6, 3, 4, 5], [0, 1, 3, 2, 4, 5, 6], [0, 1, 3, 2, 4, 6, 5], [0, 1, 3, 4, 2, 5, 6], [0, 1, 3, 4, 5, 2, 6], [0, 1, 3, 4, 5, 6, 2], [0, 1, 4, 2, 3, 5, 6], [0, 1, 4, 2, 5, 3, 6], [0, 1, 4, 2, 5, 6, 3], [0, 1, 4, 5, 2, 3, 6], [0, 1, 4, 5, 2, 6, 3], [0, 1, 4, 5, 6, 2, 3], [0, 1, 5, 2, 3, 4, 6], [0, 1, 5, 2, 3, 6, 4], [0, 1, 5, 2, 6, 3, 4], [0, 1, 5, 6, 2, 3, 4], [0, 1, 6, 2, 3, 4, 5], [0, 2, 1, 3, 4, 5, 6], [0, 2, 1, 3, 4, 6, 5], [0, 2, 1, 3, 5, 4, 6], [0, 2, 1, 3, 5, 6, 4], [0, 2, 1, 3, 6, 4, 5], [0, 2, 3, 1, 4, 5, 6], [0, 2, 3, 1, 4, 6, 5], [0, 2, 3, 4, 1, 5, 6], [0, 2, 3, 4, 5, 1, 6], [0, 2, 3, 4, 5, 6, 1], [0, 3, 1, 2, 4, 5, 6], [0, 3, 1, 2, 4, 6, 5], [0, 3, 1, 4, 2, 5, 6], [0, 3, 1, 4, 5, 2, 6], [0, 3, 1, 4, 5, 6, 2], [0, 3, 4, 1, 2, 5, 6], [0, 3, 4, 1, 5, 2, 6], [0, 3, 4, 1, 5, 6, 2], [0, 3, 4, 5, 1, 2, 6], [0, 3, 4, 5, 1, 6, 2], [0, 3, 4, 5, 6, 1, 2], [0, 4, 1, 2, 3, 5, 6], [0, 4, 1, 2, 5, 3, 6], [0, 4, 1, 2, 5, 6, 3], [0, 4, 1, 5, 2, 3, 6], [0, 4, 1, 5, 2, 6, 3], [0, 4, 1, 5, 6, 2, 3], [0, 4, 5, 1, 2, 3, 6], [0, 4, 5, 1, 2, 6, 3], [0, 4, 5, 1, 6, 2, 3], [0, 4, 5, 6, 1, 2, 3], [0, 5, 1, 2, 3, 4, 6], [0, 5, 1, 2, 3, 6, 4], [0, 5, 1, 2, 6, 3, 4], [0, 5, 1, 6, 2, 3, 4], [0, 5, 6, 1, 2, 3, 4], [0, 6, 1, 2, 3, 4, 5], [1, 0, 2, 3, 4, 5, 6], [1, 0, 2, 3, 4, 6, 5], [1, 0, 2, 3, 5, 4, 6], [1, 0, 2, 3, 5, 6, 4], [1, 0, 2, 3, 6, 4, 5], [1, 0, 2, 4, 3, 5, 6], [1, 0, 2, 4, 5, 3, 6], [1, 0, 2, 4, 5, 6, 3], [1, 0, 2, 5, 3, 4, 6], [1, 0, 2, 5, 3, 6, 4], [1, 0, 2, 5, 6, 3, 4], [1, 0, 2, 6, 3, 4, 5], [1, 2, 0, 3, 4, 5, 6], [1, 2, 0, 3, 4, 6, 5], [1, 2, 0, 3, 5, 4, 6], [1, 2, 0, 3, 5, 6, 4], [1, 2, 0, 3, 6, 4, 5], [1, 2, 3, 0, 4, 5, 6], [1, 2, 3, 0, 4, 6, 5], [1, 2, 3, 4, 0, 5, 6], [1, 2, 3, 4, 5, 0, 6], [1, 2, 3, 4, 5, 6, 0], [2, 0, 1, 3, 4, 5, 6], [2, 0, 1, 3, 4, 6, 5], [2, 0, 1, 3, 5, 4, 6], [2, 0, 1, 3, 5, 6, 4], [2, 0, 1, 3, 6, 4, 5], [2, 0, 3, 1, 4, 5, 6], [2, 0, 3, 1, 4, 6, 5], [2, 0, 3, 4, 1, 5, 6], [2, 0, 3, 4, 5, 1, 6], [2, 0, 3, 4, 5, 6, 1], [2, 3, 0, 1, 4, 5, 6], [2, 3, 0, 1, 4, 6, 5], [2, 3, 0, 4, 1, 5, 6], [2, 3, 0, 4, 5, 1, 6], [2, 3, 0, 4, 5, 6, 1], [2, 3, 4, 0, 1, 5, 6], [2, 3, 4, 0, 5, 1, 6], [2, 3, 4, 0, 5, 6, 1], [2, 3, 4, 5, 0, 1, 6], [2, 3, 4, 5, 0, 6, 1], [2, 3, 4, 5, 6, 0, 1], [3, 0, 1, 2, 4, 5, 6], [3, 0, 1, 2, 4, 6, 5], [3, 0, 1, 4, 2, 5, 6], [3, 0, 1, 4, 5, 2, 6], [3, 0, 1, 4, 5, 6, 2], [3, 0, 4, 1, 2, 5, 6], [3, 0, 4, 1, 5, 2, 6], [3, 0, 4, 1, 5, 6, 2], [3, 0, 4, 5, 1, 2, 6], [3, 0, 4, 5, 1, 6, 2], [3, 0, 4, 5, 6, 1, 2], [3, 4, 0, 1, 2, 5, 6], [3, 4, 0, 1, 5, 2, 6], [3, 4, 0, 1, 5, 6, 2], [3, 4, 0, 5, 1, 2, 6], [3, 4, 0, 5, 1, 6, 2], [3, 4, 0, 5, 6, 1, 2], [3, 4, 5, 0, 1, 2, 6], [3, 4, 5, 0, 1, 6, 2], [3, 4, 5, 0, 6, 1, 2], [3, 4, 5, 6, 0, 1, 2], [4, 0, 1, 2, 3, 5, 6], [4, 0, 1, 2, 5, 3, 6], [4, 0, 1, 2, 5, 6, 3], [4, 0, 1, 5, 2, 3, 6], [4, 0, 1, 5, 2, 6, 3], [4, 0, 1, 5, 6, 2, 3], [4, 0, 5, 1, 2, 3, 6], [4, 0, 5, 1, 2, 6, 3], [4, 0, 5, 1, 6, 2, 3], [4, 0, 5, 6, 1, 2, 3], [4, 5, 0, 1, 2, 3, 6], [4, 5, 0, 1, 2, 6, 3], [4, 5, 0, 1, 6, 2, 3], [4, 5, 0, 6, 1, 2, 3], [4, 5, 6, 0, 1, 2, 3], [5, 0, 1, 2, 3, 4, 6], [5, 0, 1, 2, 3, 6, 4], [5, 0, 1, 2, 6, 3, 4], [5, 0, 1, 6, 2, 3, 4], [5, 0, 6, 1, 2, 3, 4], [5, 6, 0, 1, 2, 3, 4], [6, 0, 1, 2, 3, 4, 5]], "8": [[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 7, 6], [0, 1, 2, 3, 4, 6, 5, 7], [0, 1, 2, 3, 4, 6, 7, 5], [0, 1, 2, 3, 4, 7, 5, 6], [0, 1, 2, 3, 5, 4, 6, 7], [0, 1, 2, 3, 5, 6, 4, 7], [0, 1, 2, 3, 5, 6, 7, 4], [0, 1, 2, 3, 6, 4, 5, 7], [0, 1, 2, 3, 6, 4, 7, 5], [0, 1, 2, 3, 6, 7, 4, 5], [0, 1, 2, 3, 7, 4, 5, 6], [0, 1, 2, 4, 3, 5, 6, 7], [0, 1, 2, 4, 3, 5, 7, 6], [0, 1, 2, 4, 5, 3, 6, 7], [0, 1, 2, 4, 5, 6, 3, 7], [0, 1, 2, 4, 5, 6, 7, 3], [0, 1, 2, 5, 3, 4, 6, 7], [0, 1, 2, 5, 3, 6, 4, 7], [0, 1, 2, 5, 3, 6, 7, 4], [0, 1, 2, 5, 6, 3, 4, 7], [0, 1, 2, 5, 6, 3, 7, 4], [0, 1, 2, 5, 6, 7, 3, 4], [0, 1, 2, 6, 3, 4, 5, 7], [0, 1, 2, 6, 3, 4, 7, 5], [0, 1, 2, 6, 3, 7, 4, 5], [0, 1, 2, 6, 7, 3, 4, 5], [0, 1, 2, 7, 3, 4, 5, 6], [0, 1, 3, 2, 4, 5, 6, 7], [0, 1, 3, 2, 4, 5, 7, 6], [0, 1, 3, 2, 4, 6, 5, 7], [0, 1, 3, 2, 4, 6, 7, 5], [0, 1, 3, 2, 4, 7, 5, 6], [0, 1, 3, 4, 2, 5, 6, 7], [0, 1, 3, 4, 2, 5, 7, 6], [0, 1, 3, 4, 5, 2, 6, 7], [0, 1, 3, 4, 5, 6, 2, 7], [0, 1, 3, 4, 5, 6, 7, 2], [0, 1, 4, 2, 3, 5, 6, 7], [0, 1, 4, 2, 3, 5, 7, 6], [0, 1, 4, 2, 5, 3, 6, 7], [0, 1, 4, 2, 5, 6, 3, 7], [0, 1, 4, 2, 5, 6, 7, 3], [0, 1, 4, 5, 2, 3, 6, 7], [0, 1, 4, 5, 2, 6, 3, 7], [0, 1, 4, 5, 2, 6, 7, 3], [0, 1, 4, 5, 6, 2, 3, 7], [0, 1, 4, 5, 6, 2, 7, 3], [0, 1, 4, 5, 6, 7, 2, 3], [0, 1, 5, 2, 3, 4, 6, 7], [0, 1, 5, 2, 3, 6, 4, 7], [0, 1, 5, 2, 3, 6, 7, 4], [0, 1, 5, 2, 6, 3, 4, 7], [0, 1, 5, 2, 6, 3, 7, 4], [0, 1, 5, 2, 6, 7, 3, 4], [0, 1, 5, 6, 2, 3, 4, 7], [0, 1, 5, 6, 2, 3, 7, 4], [0, 1, 5, 6, 2, 7, 3, 4], [0, 1, 5, 6, 7, 2, 3, 4], [0, 1, 6, 2, 3, 4, 5, 7], [0, 1, 6, 2, 3, 4, 7, 5], [0, 1, 6, 2, 3, 7, 4, 5], [0, 1, 6, 2, 7, 3, 4, 5], [0, 1, 6, 7, 2, 3, 4, 5], [0, 1, 7, 2, 3, 4, 5, 6], [0, 2, 1, 3, 4, 5, 6, 7], [0, 2, 1, 3, 4, 5, 7, 6], [0, 2, 1, 3, 4, 6, 5, 7], [0, 2, 1, 3, 4, 6, 7, 5], [0, 2, 1, 3, 4, 7, 5, 6], [0, 2, 1, 3, 5, 4, 6, 7], [0, 2, 1, 3, 5, 6, 4, 7], [0, 2, 1, 3, 5, 6, 7, 4], [0, 2, 1, 3, 6, 4, 5, 7], [0, 2, 1, 3, 6, 4, 7, 5], [0, 2, 1, 3, 6, 7, 4, 5], [0, 2, 1, 3, 7, 4, 5, 6], [0, 2, 3, 1, 4, 5, 6, 7], [0, 2, 3, 1, 4, 5, 7, 6], [0, 2, 3, 1, 4, 6, 5, 7], [0, 2, 3, 1, 4, 6, 7, 5], [0, 2, 3, 1, 4, 7, 5, 6], [0, 2, 3, 4, 1, 5, 6, 7], [0, 2, 3, 4, 1, 5, 7, 6], [0, 2, 3, 4, 5, 1, 6, 7], [0, 2, 3, 4, 5, 6, 1, 7], [0, 2, 3, 4, 5, 6, 7, 1], [0, 3, 1, 2, 4, 5, 6, 7], [0, 3, 1, 2, 4, 5, 7, 6], [0, 3, 1, 2, 4, 6, 5, 7], [0, 3, 1, 2, 4, 6, 7, 5], [0, 3, 1, 2, 4, 7, 5, 6], [0, 3, 1, 4, 2, 5, 6, 7], [0, 3, 1, 4, 2, 5, 7, 6], [0, 3, 1, 4, 5, 2, 6, 7], [0, 3, 1, 4, 5, 6, 2, 7], [0, 3, 1, 4, 5, 6, 7, 2], [0, 3, 4, 1, 2, 5, 6, 7], [0, 3, 4, 1, 2, 5, 7, 6], [0, 3, 4, 1, 5, 2, 6, 7], [0, 3, 4, 1, 5, 6, 2, 7], [0, 3, 4, 1, 5, 6, 7, 2], [0, 3, 4, 5, 1, 2, 6, 7], [0, 3, 4, 5, 1, 6, 2, 7], [0, 3, 4, 5, 1, 6, 7, 2], [0, 3, 4, 5, 6, 1, 2, 7], [0, 3, 4, 5, 6, 1, 7, 2], [0, 3, 4, 5, 6, 7, 1, 2], [0, 4, 1, 2, 3, 5, 6, 7], [0, 4, 1, 2, 3, 5, 7, 6], [0, 4, 1, 2, 5, 3, 6, 7], [0, 4, 1, 2, 5, 6, 3, 7], [0, 4, 1, 2, 5, 6, 7, 3], [0, 4, 1, 5, 2, 3, 6, 7], [0, 4, 1, 5, 2, 6, 3, 7], [0, 4, 1, 5, 2, 6, 7, 3], [0, 4, 1, 5, 6, 2, 3, 7], [0, 4, 1, 5, 6, 2, 7, 3], [0, 4, 1, 5, 6, 7, 2, 3], [0, 4, 5, 1, 2, 3, 6, 7], [0, 4, 5, 1, 2, 6, 3, 7], [0, 4, 5, 1, 2, 6, 7, 3], [0, 4, 5, 1, 6, 2, 3, 7], [0, 4, 5, 1, 6, 2, 7, 3], [0, 4, 5, 1, 6, 7, 2, 3], [0, 4, 5, 6, 1, 2, 3, 7], [0, 4, 5, 6, 1, 2, 7, 3], [0, 4, 5, 6, 1, 7, 2, 3], [0, 4, 5, 6, 7, 1, 2, 3], [0, 5, 1, 2, 3, 4, 6, 7], [0, 5, 1, 2, 3, 6, 4, 7], [0, 5, 1, 2, 3, 6, 7, 4], [0, 5, 1, 2, 6, 3, 4, 7], [0, 5, 1, 2, 6, 3, 7, 4], [0, 5, 1, 2, 6, 7, 3, 4], [0, 5, 1, 6, 2, 3, 4, 7], [0, 5, 1, 6, 2, 3, 7, 4], [0, 5, 1, 6, 2, 7, 3, 4], [0, 5, 1, 6, 7, 2, 3, 4], [0, 5, 6, 1, 2, 3, 4, 7], [0, 5, 6, 1, 2, 3, 7, 4], [0, 5, 6, 1, 2, 7, 3, 4], [0, 5, 6, 1, 7, 2, 3, 4], [0, 5, 6, 7, 1, 2, 3, 4], [0, 6, 1, 2, 3, 4, 5, 7], [0, 6, 1, 2, 3, 4, 7, 5], [0, 6, 1, 2, 3, 7, 4, 5], [0, 6, 1, 2, 7, 3, 4, 5], [0, 6, 1, 7, 2, 3, 4, 5], [0, 6, 7, 1, 2, 3, 4, 5], [0, 7, 1, 2, 3, 4, 5, 6], [1, 0, 2, 3, 4, 5, 6, 7], [1, 0, 2, 3, 4, 5, 7, 6], [1, 0, 2, 3, 4, 6, 5, 7], [1, 0, 2, 3, 4, 6, 7, 5], [1, 0, 2, 3, 4, 7, 5, 6], [1, 0, 2, 3, 5, 4, 6, 7], [1, 0, 2, 3, 5, 6, 4, 7], [1, 0, 2, 3, 5, 6, 7, 4], [1, 0, 2, 3, 6, 4, 5, 7], [1, 0, 2, 3, 6, 4, 7, 5], [1, 0, 2, 3, 6, 7, 4, 5], [1, 0, 2, 3, 7, 4, 5, 6], [1, 0, 2, 4, 3, 5, 6, 7], [1, 0, 2, 4, 3, 5, 7, 6], [1, 0, 2, 4, 5, 3, 6, 7], [1, 0, 2, 4, 5, 6, 3, 7], [1, 0, 2, 4, 5, 6, 7, 3], [1, 0, 2, 5, 3, 4, 6, 7], [1, 0, 2, 5, 3, 6, 4, 7], [1, 0, 2, 5, 3, 6, 7, 4], [1, 0, 2, 5, 6, 3, 4, 7], [1, 0, 2, 5, 6, 3, 7, 4], [1, 0, 2, 5, 6, 7, 3, 4], [1, 0, 2, 6, 3, 4, 5, 7], [1, 0, 2, 6, 3, 4, 7, 5], [1, 0, 2, 6, 3, 7, 4, 5], [1, 0, 2, 6, 7, 3, 4, 5], [1, 0, 2, 7, 3, 4, 5, 6], [1, 2, 0, 3, 4, 5, 6, 7], [1, 2, 0, 3, 4, 5, 7, 6], [1, 2, 0, 3, 4, 6, 5, 7], [1, 2, 0, 3, 4, 6, 7, 5], [1, 2, 0, 3, 4, 7, 5, 6], [1, 2, 0, 3, 5, 4, 6, 7], [1, 2, 0, 3, 5, 6, 4, 7], [1, 2, 0, 3, 5, 6, 7, 4], [1, 2, 0, 3, 6, 4, 5, 7], [1, 2, 0, 3, 6, 4, 7, 5], [1, 2, 0, 3, 6, 7, 4, 5], [1, 2, 0, 3, 7, 4, 5, 6], [1, 2, 3, 0, 4, 5, 6, 7], [1, 2, 3, 0, 4, 5, 7, 6], [1, 2, 3, 0, 4, 6, 5, 7], [1, 2, 3, 0, 4, 6, 7, 5], [1, 2, 3, 0, 4, 7, 5, 6], [1, 2, 3, 4, 0, 5, 6, 7], [1, 2, 3, 4, 0, 5, 7, 6], [1, 2, 3, 4, 5, 0, 6, 7], [1, 2, 3, 4, 5, 6, 0, 7], [1, 2, 3, 4, 5, 6, 7, 0], [2, 0, 1, 3, 4, 5, 6, 7], [2, 0, 1, 3, 4, 5, 7, 6], [2, 0, 1, 3, 4, 6, 5, 7], [2, 0, 1, 3, 4, 6, 7, 5], [2, 0, 1, 3, 4, 7, 5, 6], [2, 0, 1, 3, 5, 4, 6, 7], [2, 0, 1, 3, 5, 6, 4, 7], [2, 0, 1, 3, 5, 6, 7, 4], [2, 0, 1, 3, 6, 4, 5, 7], [2, 0, 1, 3, 6, 4, 7, 5], [2, 0, 1, 3, 6, 7, 4, 5], [2, 0, 1, 3, 7, 4, 5, 6], [2, 0, 3, 1, 4, 5, 6, 7], [2, 0, 3, 1, 4, 5, 7, 6], [2, 0, 3, 1, 4, 6, 5, 7], [2, 0, 3, 1, 4, 6, 7, 5], [2, 0, 3, 1, 4, 7, 5, 6], [2, 0, 3, 4, 1, 5, 6, 7], [2, 0, 3, 4, 1, 5, 7, 6], [2, 0, 3, 4, 5, 1, 6, 7], [2, 0, 3, 4, 5, 6, 1, 7], [2, 0, 3, 4, 5, 6, 7, 1], [2, 3, 0, 1, 4, 5, 6, 7], [2, 3, 0, 1, 4, 5, 7, 6], [2, 3, 0, 1, 4, 6, 5, 7], [2, 3, 0, 1, 4, 6, 7, 5], [2, 3, 0, 1, 4, 7, 5, 6], [2, 3, 0, 4, 1, 5, 6, 7], [2, 3, 0, 4, 1, 5, 7, 6], [2, 3, 0, 4, 5, 1, 6, 7], [2, 3, 0, 4, 5, 6, 1, 7], [2, 3, 0, 4, 5, 6, 7, 1], [2, 3, 4, 0, 1, 5, 6, 7], [2, 3, 4, 0, 1, 5, 7, 6], [2, 3, 4, 0, 5, 1, 6, 7], [2, 3, 4, 0, 5, 6, 1, 7], [2, 3, 4, 0, 5, 6, 7, 1], [2, 3, 4, 5, 0, 1, 6, 7], [2, 3, 4, 5, 0, 6, 1, 7], [2, 3, 4, 5, 0, 6, 7, 1], [2, 3, 4, 5, 6, 0, 1, 7], [2, 3, 4, 5, 6, 0, 7, 1], [2, 3, 4, 5, 6, 7, 0, 1], [3, 0, 1, 2, 4, 5, 6, 7], [3, 0, 1, 2, 4, 5, 7, 6], [3, 0, 1, 2, 4, 6, 5, 7], [3, 0, 1, 2, 4, 6, 7, 5], [3, 0, 1, 2, 4, 7, 5, 6], [3, 0, 1, 4, 2, 5, 6, 7], [3, 0, 1, 4, 2, 5, 7, 6], [3, 0, 1, 4, 5, 2, 6, 7], [3, 0, 1, 4, 5, 6, 2, 7], [3, 0, 1, 4, 5, 6, 7, 2], [3, 0, 4, 1, 2, 5, 6, 7], [3, 0, 4, 1, 2, 5, 7, 6], [3, 0, 4, 1, 5, 2, 6, 7], [3, 0, 4, 1, 5, 6, 2, 7], [3, 0, 4, 1, 5, 6, 7, 2], [3, 0, 4, 5, 1, 2, 6, 7], [3, 0, 4, 5, 1, 6, 2, 7], [3, 0, 4, 5, 1, 6, 7, 2], [3, 0, 4, 5, 6, 1, 2, 7], [3, 0, 4, 5, 6, 1, 7, 2], [3, 0, 4, 5, 6, 7, 1, 2], [3, 4, 0, 1, 2, 5, 6, 7], [3, 4, 0, 1, 2, 5, 7, 6], [3, 4, 0, 1, 5, 2, 6, 7], [3, 4, 0, 1, 5, 6, 2, 7], [3, 4, 0, 1, 5, 6, 7, 2], [3, 4, 0, 5, 1, 2, 6, 7], [3, 4, 0, 5, 1, 6, 2, 7], [3, 4, 0, 5, 1, 6, 7, 2], [3, 4, 0, 5, 6, 1, 2, 7], [3, 4, 0, 5, 6, 1, 7, 2], [3, 4, 0, 5, 6, 7, 1, 2], [3, 4, 5, 0, 1, 2, 6, 7], [3, 4, 5, 0, 1, 6, 2, 7], [3, 4, 5, 0, 1, 6, 7, 2], [3, 4, 5, 0, 6, 1, 2, 7], [3, 4, 5, 0, 6, 1, 7, 2], [3, 4, 5, 0, 6, 7, 1, 2], [3, 4, 5, 6, 0, 1, 2, 7], [3, 4, 5, 6, 0, 1, 7, 2], [3, 4, 5, 6, 0, 7, 1, 2], [3, 4, 5, 6, 7, 0, 1, 2], [4, 0, 1, 2, 3, 5, 6, 7], [4, 0, 1, 2, 3, 5, 7, 6], [4, 0, 1, 2, 5, 3, 6, 7], [4, 0, 1, 2, 5, 6, 3, 7], [4, 0, 1, 2, 5, 6, 7, 3], [4, 0, 1, 5, 2, 3, 6, 7], [4, 0, 1, 5, 2, 6, 3, 7], [4, 0, 1, 5, 2, 6, 7, 3], [4, 0, 1, 5, 6, 2, 3, 7], [4, 0, 1, 5, 6, 2, 7, 3], [4, 0, 1, 5, 6, 7, 2, 3], [4, 0, 5, 1, 2, 3, 6, 7], [4, 0, 5, 1, 2, 6, 3, 7], [4, 0, 5, 1, 2, 6, 7, 3], [4, 0, 5, 1, 6, 2, 3, 7], [4, 0, 5, 1, 6, 2, 7, 3], [4, 0, 5, 1, 6, 7, 2, 3], [4, 0, 5, 6, 1, 2, 3, 7], [4, 0, 5, 6, 1, 2, 7, 3], [4, 0, 5, 6, 1, 7, 2, 3], [4, 0, 5, 6, 7, 1, 2, 3], [4, 5, 0, 1, 2, 3, 6, 7], [4, 5, 0, 1, 2, 6, 3, 7], [4, 5, 0, 1, 2, 6, 7, 3], [4, 5, 0, 1, 6, 2, 3, 7], [4, 5, 0, 1, 6, 2, 7, 3], [4, 5, 0, 1, 6, 7, 2, 3], [4, 5, 0, 6, 1, 2, 3, 7], [4, 5, 0, 6, 1, 2, 7, 3], [4, 5, 0, 6, 1, 7, 2, 3], [4, 5, 0, 6, 7, 1, 2, 3], [4, 5, 6, 0, 1, 2, 3, 7], [4, 5, 6, 0, 1, 2, 7, 3], [4, 5, 6, 0, 1, 7, 2, 3], [4, 5, 6, 0, 7, 1, 2, 3], [4, 5, 6, 7, 0, 1, 2, 3], [5, 0, 1, 2, 3, 4, 6, 7], [5, 0, 1, 2, 3, 6, 4, 7], [5, 0, 1, 2, 3, 6, 7, 4], [5, 0, 1, 2, 6, 3, 4, 7], [5, 0, 1, 2, 6, 3, 7, 4], [5, 0, 1, 2, 6, 7, 3, 4], [5, 0, 1, 6, 2, 3, 4, 7], [5, 0, 1, 6, 2, 3, 7, 4], [5, 0, 1, 6, 2, 7, 3, 4], [5, 0, 1, 6, 7, 2, 3, 4], [5, 0, 6, 1, 2, 3, 4, 7], [5, 0, 6, 1, 2, 3, 7, 4], [5, 0, 6, 1, 2, 7, 3, 4], [5, 0, 6, 1, 7, 2, 3, 4], [5, 0, 6, 7, 1, 2, 3, 4], [5, 6, 0, 1, 2, 3, 4, 7], [5, 6, 0, 1, 2, 3, 7, 4], [5, 6, 0, 1, 2, 7, 3, 4], [5, 6, 0, 1, 7, 2, 3, 4], [5, 6, 0, 7, 1, 2, 3, 4], [5, 6, 7, 0, 1, 2, 3, 4], [6, 0, 1, 2, 3, 4, 5, 7], [6, 0, 1, 2, 3, 4, 7, 5], [6, 0, 1, 2, 3, 7, 4, 5], [6, 0, 1, 2, 7, 3, 4, 5], [6, 0, 1, 7, 2, 3, 4, 5], [6, 0, 7, 1, 2, 3, 4, 5], [6, 7, 0, 1, 2, 3, 4, 5], [7, 0, 1, 2, 3, 4, 5, 6]]} -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | target-version = ['py310'] 3 | include = '\.pyi?$' 4 | exclude = ''' 5 | 6 | ( 7 | /( 8 | \.eggs # exclude a few common directories in the 9 | | \.git # root of the project 10 | | \.hg 11 | | \.mypy_cache 12 | | \.tox 13 | | \.venv 14 | | _build 15 | | buck-out 16 | | build 17 | | dist 18 | )/ 19 | | foo.py # also separately exclude a file named foo.py in 20 | # the root of the project 21 | ) 22 | ''' 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | from setuptools import find_packages, setup 6 | 7 | 8 | def read(fname): 9 | return open(os.path.join(os.path.dirname(__file__), fname), encoding="utf-8").read() 10 | 11 | 12 | def get_version(rel_path): 13 | for line in read(rel_path).splitlines(): 14 | if line.startswith("__version__"): 15 | delim = '"' if '"' in line else "'" 16 | return line.split(delim)[1] 17 | raise RuntimeError("Unable to find version string.") 18 | 19 | 20 | setup( 21 | name="permuta", 22 | version=get_version("permuta/__init__.py"), 23 | author="Permuta Triangle", 24 | author_email="permutatriangle@gmail.com", 25 | description="A comprehensive high performance permutation library.", 26 | license="BSD-3", 27 | keywords=( 28 | "permutation perm mesh pattern patt avoid contain occurrence" "statistic" 29 | ), 30 | url="https://github.com/PermutaTriangle/Permuta", 31 | project_urls={ 32 | "Source": "https://github.com/PermutaTriangle/Permuta", 33 | "Tracker": "https://github.com/PermutaTriangle/Permuta/issues", 34 | }, 35 | packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), 36 | package_data={"permuta": ["py.typed"]}, 37 | long_description=read("README.rst"), 38 | python_requires=">=3.7", 39 | include_package_data=True, 40 | zip_safe=False, 41 | classifiers=[ 42 | "Development Status :: 5 - Production/Stable", 43 | "Intended Audience :: Education", 44 | "Intended Audience :: Science/Research", 45 | "License :: OSI Approved :: BSD License", 46 | "Programming Language :: Python :: 3.7", 47 | "Programming Language :: Python :: 3.8", 48 | "Programming Language :: Python :: 3.9", 49 | "Programming Language :: Python :: Implementation :: CPython", 50 | "Programming Language :: Python :: Implementation :: PyPy", 51 | "Topic :: Education", 52 | "Topic :: Scientific/Engineering :: Mathematics", 53 | ], 54 | install_requires=["automata-lib==9.0.0"], 55 | entry_points={"console_scripts": ["permtools=permuta.cli:main"]}, 56 | ) 57 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PermutaTriangle/Permuta/ce704c07ce10ab131d1098c7a7eaef077ca19a82/tests/__init__.py -------------------------------------------------------------------------------- /tests/bisc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PermutaTriangle/Permuta/ce704c07ce10ab131d1098c7a7eaef077ca19a82/tests/bisc/__init__.py -------------------------------------------------------------------------------- /tests/bisc/test_bisc_subfunctions.py: -------------------------------------------------------------------------------- 1 | from permuta import Perm 2 | from permuta.bisc.bisc_subfunctions import maximal_mesh_pattern_of_occurrence 3 | 4 | 5 | def test_maximal_mesh_pattern_of_occurrence(): 6 | assert maximal_mesh_pattern_of_occurrence(Perm((0,)), (0,)) == set( 7 | [(0, 0), (0, 1), (1, 0), (1, 1)] 8 | ) 9 | assert maximal_mesh_pattern_of_occurrence(Perm((0, 1)), (0,)) == set( 10 | [(0, 0), (0, 1), (1, 0)] 11 | ) 12 | assert maximal_mesh_pattern_of_occurrence(Perm((0, 1)), (1,)) == set( 13 | [(0, 1), (1, 0), (1, 1)] 14 | ) 15 | assert maximal_mesh_pattern_of_occurrence(Perm((1, 0)), (0,)) == set( 16 | [(0, 0), (0, 1), (1, 1)] 17 | ) 18 | assert maximal_mesh_pattern_of_occurrence(Perm((1, 0)), (1,)) == set( 19 | [(0, 0), (1, 0), (1, 1)] 20 | ) 21 | -------------------------------------------------------------------------------- /tests/bisc/test_perm_properties.py: -------------------------------------------------------------------------------- 1 | from permuta import Perm 2 | from permuta.bisc.perm_properties import ( 3 | _perm_to_yt, 4 | av_231_and_mesh, 5 | baxter, 6 | dihedral, 7 | forest_like, 8 | hard_mesh, 9 | in_alternating_group, 10 | simsun, 11 | smooth, 12 | yt_perm_avoids_22, 13 | yt_perm_avoids_32, 14 | ) 15 | 16 | 17 | def test_smooth(): 18 | assert smooth(Perm(())) 19 | assert smooth(Perm((0,))) 20 | assert smooth(Perm((0, 1))) 21 | assert smooth(Perm((1, 0))) 22 | assert smooth(Perm((0, 1, 2))) 23 | assert smooth(Perm((0, 2, 1))) 24 | assert smooth(Perm((1, 0, 2))) 25 | assert smooth(Perm((1, 2, 0))) 26 | assert smooth(Perm((2, 0, 1))) 27 | assert smooth(Perm((2, 1, 0))) 28 | assert smooth(Perm((0, 1, 2, 3))) 29 | assert smooth(Perm((0, 1, 3, 2))) 30 | assert not smooth(Perm((0, 3, 5, 1, 2, 4))) 31 | assert not smooth(Perm((3, 4, 1, 0, 5, 2))) 32 | assert not smooth(Perm((0, 3, 2, 4, 5, 6, 1))) 33 | assert not smooth(Perm((5, 3, 1, 4, 2, 6, 0))) 34 | assert smooth(Perm((3, 7, 2, 0, 4, 5, 6, 1))) 35 | assert not smooth(Perm((4, 7, 1, 3, 8, 5, 0, 6, 2))) 36 | assert 1552 == sum(1 for p in Perm.of_length(7) if smooth(p)) 37 | 38 | 39 | def test_forest_like(): 40 | assert forest_like(Perm(())) 41 | assert forest_like(Perm((0,))) 42 | assert forest_like(Perm((0, 1))) 43 | assert forest_like(Perm((1, 0))) 44 | assert forest_like(Perm((0, 1, 2))) 45 | assert forest_like(Perm((0, 2, 1))) 46 | assert forest_like(Perm((1, 0, 2))) 47 | assert forest_like(Perm((1, 2, 0))) 48 | assert forest_like(Perm((2, 0, 1))) 49 | assert forest_like(Perm((2, 1, 0))) 50 | assert forest_like(Perm((0, 1, 2, 3))) 51 | assert forest_like(Perm((0, 1, 3, 2))) 52 | assert not forest_like(Perm((3, 4, 1, 0, 5, 2))) 53 | assert forest_like(Perm((5, 4, 0, 1, 3, 2))) 54 | assert forest_like(Perm((6, 0, 1, 5, 2, 3, 4))) 55 | assert forest_like(Perm((6, 1, 2, 5, 3, 4, 0))) 56 | assert forest_like(Perm((7, 2, 3, 4, 6, 5, 1, 0))) 57 | assert not forest_like(Perm((0, 1, 6, 7, 4, 2, 3, 8, 5))) 58 | assert 1661 == sum(1 for p in Perm.of_length(7) if forest_like(p)) 59 | 60 | 61 | def test_baxter(): 62 | assert baxter(Perm(())) 63 | assert baxter(Perm((0,))) 64 | assert baxter(Perm((0, 1))) 65 | assert baxter(Perm((1, 0))) 66 | assert baxter(Perm((0, 1, 2))) 67 | assert baxter(Perm((0, 2, 1))) 68 | assert baxter(Perm((1, 0, 2))) 69 | assert baxter(Perm((1, 2, 0))) 70 | assert baxter(Perm((2, 0, 1))) 71 | assert baxter(Perm((2, 1, 0))) 72 | assert baxter(Perm((0, 1, 2, 3))) 73 | assert baxter(Perm((0, 1, 3, 2))) 74 | assert not baxter(Perm((1, 3, 0, 5, 4, 2))) 75 | assert not baxter(Perm((5, 3, 1, 0, 4, 2))) 76 | assert baxter(Perm((5, 4, 3, 0, 1, 2, 6))) 77 | assert baxter(Perm((6, 0, 5, 3, 4, 1, 2))) 78 | assert baxter(Perm((5, 3, 4, 2, 0, 1, 6, 7))) 79 | assert baxter(Perm((1, 2, 4, 6, 5, 3, 0, 8, 7))) 80 | assert 2074 == sum(1 for p in Perm.of_length(7) if baxter(p)) 81 | 82 | 83 | def test_simsun(): 84 | assert simsun(Perm(())) 85 | assert simsun(Perm((0,))) 86 | assert simsun(Perm((0, 1))) 87 | assert simsun(Perm((1, 0))) 88 | assert simsun(Perm((0, 1, 2))) 89 | assert simsun(Perm((0, 2, 1))) 90 | assert simsun(Perm((1, 0, 2))) 91 | assert simsun(Perm((1, 2, 0))) 92 | assert simsun(Perm((2, 0, 1))) 93 | assert not simsun(Perm((2, 1, 0))) 94 | assert simsun(Perm((0, 1, 2, 3))) 95 | assert simsun(Perm((0, 1, 3, 2))) 96 | assert simsun(Perm((0, 5, 1, 4, 2, 3))) 97 | assert not simsun(Perm((1, 3, 5, 4, 2, 0))) 98 | assert simsun(Perm((1, 5, 0, 2, 3, 6, 4))) 99 | assert not simsun(Perm((3, 1, 2, 6, 5, 0, 4))) 100 | assert not simsun(Perm((4, 7, 3, 0, 6, 5, 2, 1))) 101 | assert not simsun(Perm((4, 6, 7, 0, 3, 5, 2, 8, 1))) 102 | assert 1385 == sum(1 for p in Perm.of_length(7) if simsun(p)) 103 | 104 | 105 | def test_dihedral(): 106 | assert not dihedral(Perm(())) 107 | assert not dihedral(Perm((0,))) 108 | assert not dihedral(Perm((0, 1))) 109 | assert not dihedral(Perm((1, 0))) 110 | assert dihedral(Perm((0, 1, 2))) 111 | assert dihedral(Perm((0, 2, 1))) 112 | assert dihedral(Perm((1, 0, 2))) 113 | assert dihedral(Perm((1, 2, 0))) 114 | assert dihedral(Perm((2, 0, 1))) 115 | assert dihedral(Perm((2, 1, 0))) 116 | assert dihedral(Perm((0, 1, 2, 3))) 117 | assert not dihedral(Perm((0, 1, 3, 2))) 118 | assert not dihedral(Perm((3, 4, 1, 0, 2, 5))) 119 | assert not dihedral(Perm((5, 4, 2, 1, 0, 3))) 120 | assert not dihedral(Perm((2, 3, 6, 1, 0, 5, 4))) 121 | assert not dihedral(Perm((6, 4, 2, 5, 1, 3, 0))) 122 | assert not dihedral(Perm((1, 6, 4, 2, 0, 5, 3, 7))) 123 | assert not dihedral(Perm((2, 0, 3, 8, 1, 5, 7, 4, 6))) 124 | assert not dihedral(Perm((1, 7, 4, 3, 8, 9, 6, 2, 5, 0))) 125 | assert not dihedral(Perm((5, 6, 2, 10, 1, 0, 9, 3, 7, 4, 8))) 126 | assert all( 127 | 2 * n == sum(1 for perm in Perm.of_length(n) if dihedral(perm)) 128 | for n in range(5, 8) 129 | ) 130 | 131 | 132 | def test_in_alternating_group(): 133 | assert in_alternating_group(Perm(())) 134 | assert in_alternating_group(Perm((0,))) 135 | assert not in_alternating_group(Perm((0, 1))) 136 | assert not in_alternating_group(Perm((1, 0))) 137 | assert in_alternating_group(Perm((0, 1, 2))) 138 | assert not in_alternating_group(Perm((0, 2, 1))) 139 | assert not in_alternating_group(Perm((1, 0, 2))) 140 | assert in_alternating_group(Perm((1, 2, 0))) 141 | assert in_alternating_group(Perm((2, 0, 1))) 142 | assert not in_alternating_group(Perm((2, 1, 0))) 143 | assert in_alternating_group(Perm((0, 1, 2, 3))) 144 | assert not in_alternating_group(Perm((0, 1, 3, 2))) 145 | assert not in_alternating_group(Perm((0, 4, 1, 5, 2, 3))) 146 | assert in_alternating_group(Perm((5, 4, 0, 2, 1, 3))) 147 | assert not in_alternating_group(Perm((0, 4, 3, 5, 2, 6, 1))) 148 | assert not in_alternating_group(Perm((1, 3, 0, 2, 5, 6, 4))) 149 | assert in_alternating_group(Perm((5, 7, 2, 1, 4, 0, 3, 6))) 150 | assert not in_alternating_group(Perm((3, 0, 6, 1, 2, 8, 5, 4, 7))) 151 | assert in_alternating_group(Perm((5, 1, 6, 4, 9, 8, 2, 0, 7, 3))) 152 | assert not in_alternating_group(Perm((7, 0, 2, 4, 8, 5, 1, 9, 6, 3, 10))) 153 | assert 2520 == sum(1 for p in Perm.of_length(7) if in_alternating_group(p)) 154 | 155 | 156 | def test__perm_to_yt(): 157 | z = {i: set() for i in range(8)} 158 | for i in range(8): 159 | for p in Perm.of_length(i): 160 | z[i].add(tuple(map(tuple, _perm_to_yt(p)))) 161 | expected = [1, 1, 2, 4, 10, 26, 76, 232] 162 | for i in range(8): 163 | assert len(z[i]) == expected[i] 164 | assert _perm_to_yt(Perm((1, 0))) == [[0], [1]] 165 | assert _perm_to_yt(Perm((1, 0, 3, 5, 2, 4))) == [[0, 2, 4], [1, 3, 5]] 166 | assert _perm_to_yt(Perm((1, 0, 2, 3))) == [[0, 2, 3], [1]] 167 | assert _perm_to_yt(Perm((3, 1, 4, 0, 2))) == [[0, 2], [1, 4], [3]] 168 | assert _perm_to_yt(Perm((5, 4, 1, 0, 3, 2))) == [[0, 2], [1, 3], [4], [5]] 169 | assert _perm_to_yt(Perm((0, 1))) == [[0, 1]] 170 | assert _perm_to_yt(Perm((0,))) == [[0]] 171 | assert _perm_to_yt(Perm((2, 3, 0, 1))) == [[0, 1], [2, 3]] 172 | assert _perm_to_yt(Perm((1, 2, 3, 0))) == [[0, 2, 3], [1]] 173 | assert _perm_to_yt(Perm((4, 0, 1, 2, 5, 3))) == [[0, 1, 2, 3], [4, 5]] 174 | 175 | 176 | def test_yt_perm_avoids_22(): 177 | assert yt_perm_avoids_22(Perm()) 178 | assert yt_perm_avoids_22(Perm((0,))) 179 | assert yt_perm_avoids_22(Perm((0, 1))) 180 | assert yt_perm_avoids_22(Perm((1, 0))) 181 | assert yt_perm_avoids_22(Perm((0, 1, 2))) 182 | assert yt_perm_avoids_22(Perm((0, 2, 1))) 183 | assert yt_perm_avoids_22(Perm((1, 0, 2))) 184 | assert yt_perm_avoids_22(Perm((1, 2, 0))) 185 | assert yt_perm_avoids_22(Perm((2, 0, 1))) 186 | assert yt_perm_avoids_22(Perm((2, 1, 0))) 187 | assert yt_perm_avoids_22(Perm((0, 1, 2, 3))) 188 | assert yt_perm_avoids_22(Perm((0, 1, 3, 2))) 189 | assert yt_perm_avoids_22(Perm((2, 5, 3, 1, 0, 4))) 190 | assert yt_perm_avoids_22(Perm((4, 3, 1, 2, 0, 5))) 191 | assert not yt_perm_avoids_22(Perm((3, 0, 4, 1, 5, 6, 2))) 192 | assert not yt_perm_avoids_22(Perm((5, 6, 0, 2, 4, 3, 1))) 193 | assert not yt_perm_avoids_22(Perm((7, 4, 3, 5, 0, 2, 6, 1))) 194 | assert yt_perm_avoids_22(Perm((3, 4, 5, 6, 2, 7, 1, 0, 8))) 195 | assert not yt_perm_avoids_22(Perm((7, 5, 2, 0, 4, 8, 6, 9, 1, 3))) 196 | assert not yt_perm_avoids_22(Perm((3, 5, 4, 6, 0, 7, 2, 10, 1, 8, 9))) 197 | assert not yt_perm_avoids_22(Perm((1, 2, 0, 4, 3, 5))) 198 | assert not yt_perm_avoids_22(Perm((5, 2, 0, 3, 1, 4))) 199 | assert not yt_perm_avoids_22(Perm((1, 0, 6, 4, 3, 5, 2))) 200 | assert not yt_perm_avoids_22(Perm((5, 0, 1, 3, 6, 2, 4))) 201 | assert not yt_perm_avoids_22(Perm((5, 1, 2, 7, 4, 3, 0, 6))) 202 | assert not yt_perm_avoids_22(Perm((5, 6, 0, 8, 3, 7, 4, 1, 2))) 203 | assert not yt_perm_avoids_22(Perm((2, 5, 7, 6, 1, 8, 9, 3, 0, 4))) 204 | assert not yt_perm_avoids_22(Perm((7, 8, 4, 0, 1, 5, 2, 10, 3, 9, 6))) 205 | assert 924 == sum(1 for p in Perm.of_length(7) if yt_perm_avoids_22(p)) 206 | 207 | 208 | def test_yt_perm_avoids_32(): 209 | assert yt_perm_avoids_32(Perm()) 210 | assert yt_perm_avoids_32(Perm((0,))) 211 | assert yt_perm_avoids_32(Perm((0, 1))) 212 | assert yt_perm_avoids_32(Perm((1, 0))) 213 | assert yt_perm_avoids_32(Perm((0, 1, 2))) 214 | assert yt_perm_avoids_32(Perm((0, 2, 1))) 215 | assert yt_perm_avoids_32(Perm((1, 0, 2))) 216 | assert yt_perm_avoids_32(Perm((1, 2, 0))) 217 | assert yt_perm_avoids_32(Perm((2, 0, 1))) 218 | assert yt_perm_avoids_32(Perm((2, 1, 0))) 219 | assert yt_perm_avoids_32(Perm((0, 1, 2, 3))) 220 | assert yt_perm_avoids_32(Perm((0, 1, 3, 2))) 221 | assert yt_perm_avoids_32(Perm((2, 3, 1, 4, 0, 5))) 222 | assert not yt_perm_avoids_32(Perm((2, 3, 1, 5, 4, 0))) 223 | assert not yt_perm_avoids_32(Perm((2, 0, 3, 1, 4, 5, 6))) 224 | assert not yt_perm_avoids_32(Perm((5, 1, 2, 0, 4, 3, 6))) 225 | assert not yt_perm_avoids_32(Perm((5, 0, 4, 2, 1, 6, 3, 7))) 226 | assert not yt_perm_avoids_32(Perm((0, 6, 3, 1, 8, 7, 4, 5, 2))) 227 | assert not yt_perm_avoids_32(Perm((6, 4, 9, 0, 3, 1, 8, 7, 5, 2))) 228 | assert not yt_perm_avoids_32(Perm((10, 8, 1, 4, 0, 5, 2, 7, 9, 3, 6))) 229 | assert yt_perm_avoids_32(Perm((7, 3, 2, 4, 5, 1, 0, 6))) 230 | assert yt_perm_avoids_32(Perm((5, 4, 6, 1, 3, 2, 0))) 231 | assert yt_perm_avoids_32(Perm((4, 6, 2, 0, 5, 3, 1))) 232 | assert 1316 == sum(1 for p in Perm.of_length(7) if yt_perm_avoids_32(p)) 233 | assert yt_perm_avoids_32(Perm((0, 14, 1, 13, 11, 8, 3, 5, 7, 9, 6, 10, 12, 4, 2))) 234 | assert yt_perm_avoids_32(Perm((7, 5, 2, 4, 6, 8, 9, 10, 3, 11, 12, 1, 0, 13, 14))) 235 | assert yt_perm_avoids_32(Perm((13, 9, 8, 0, 5, 1, 3, 4, 2, 6, 7, 10, 11, 12, 14))) 236 | assert yt_perm_avoids_32(Perm((14, 10, 13, 12, 8, 7, 11, 5, 9, 6, 3, 4, 1, 0, 2))) 237 | assert yt_perm_avoids_32(Perm((13, 0, 1, 10, 8, 7, 9, 11, 6, 5, 4, 12, 3, 14, 2))) 238 | assert yt_perm_avoids_32(Perm((0, 14, 1, 3, 12, 4, 5, 6, 10, 11, 9, 8, 7, 13, 2))) 239 | assert yt_perm_avoids_32(Perm((12, 9, 14, 13, 11, 6, 10, 8, 5, 2, 1, 7, 4, 0, 3))) 240 | assert yt_perm_avoids_32(Perm((7, 14, 13, 6, 4, 12, 11, 3, 10, 9, 8, 1, 0, 5, 2))) 241 | 242 | 243 | def test_av_231_and_mesh(): 244 | assert av_231_and_mesh(Perm(())) 245 | assert av_231_and_mesh(Perm((0,))) 246 | assert av_231_and_mesh(Perm((0, 1))) 247 | assert av_231_and_mesh(Perm((1, 0))) 248 | assert av_231_and_mesh(Perm((0, 1, 2))) 249 | assert av_231_and_mesh(Perm((0, 2, 1))) 250 | assert av_231_and_mesh(Perm((1, 0, 2))) 251 | assert not av_231_and_mesh(Perm((1, 2, 0))) 252 | assert av_231_and_mesh(Perm((2, 0, 1))) 253 | assert av_231_and_mesh(Perm((2, 1, 0))) 254 | assert av_231_and_mesh(Perm((0, 1, 2, 3))) 255 | assert av_231_and_mesh(Perm((0, 1, 3, 2))) 256 | assert av_231_and_mesh(Perm((0, 5, 4, 3, 2, 1))) 257 | assert av_231_and_mesh(Perm((5, 2, 1, 0, 4, 3))) 258 | assert not av_231_and_mesh(Perm((3, 4, 6, 2, 5, 0, 1))) 259 | assert not av_231_and_mesh(Perm((5, 0, 1, 6, 3, 4, 2))) 260 | assert not av_231_and_mesh(Perm((2, 5, 0, 7, 3, 4, 6, 1))) 261 | assert not av_231_and_mesh(Perm((5, 7, 4, 8, 1, 6, 0, 2, 3))) 262 | assert not av_231_and_mesh(Perm((2, 5, 9, 1, 7, 3, 8, 0, 4, 6))) 263 | assert not av_231_and_mesh(Perm((5, 6, 0, 3, 2, 1, 10, 8, 7, 4, 9))) 264 | assert av_231_and_mesh(Perm((4, 1, 0, 3, 2, 5, 6))) 265 | assert not av_231_and_mesh(Perm((2, 6, 4, 0, 1, 7, 3, 5))) 266 | assert av_231_and_mesh(Perm((9, 8, 6, 5, 1, 0, 4, 3, 2, 7))) 267 | assert av_231_and_mesh(Perm((0, 3, 2, 1, 9, 8, 7, 6, 5, 4))) 268 | assert av_231_and_mesh(Perm((9, 1, 0, 2, 4, 3, 7, 5, 6, 8))) 269 | assert av_231_and_mesh(Perm((8, 5, 1, 0, 2, 4, 3, 7, 6, 9))) 270 | assert av_231_and_mesh(Perm((9, 1, 0, 8, 2, 5, 4, 3, 6, 7))) 271 | assert av_231_and_mesh(Perm((9, 8, 7, 4, 0, 3, 1, 2, 5, 6))) 272 | assert av_231_and_mesh(Perm((6, 1, 0, 2, 5, 3, 4, 9, 7, 8))) 273 | assert av_231_and_mesh(Perm((8, 6, 0, 5, 3, 2, 1, 4, 7, 9))) 274 | assert av_231_and_mesh(Perm((0, 1, 3, 2, 4, 6, 5, 9, 8, 7))) 275 | assert 417 == sum(1 for p in Perm.of_length(7) if av_231_and_mesh(p)) 276 | 277 | 278 | def test_hard_mesh(): 279 | assert hard_mesh(Perm(())) 280 | assert hard_mesh(Perm((0,))) 281 | assert hard_mesh(Perm((0, 1))) 282 | assert hard_mesh(Perm((1, 0))) 283 | assert not hard_mesh(Perm((0, 1, 2))) 284 | assert hard_mesh(Perm((0, 2, 1))) 285 | assert hard_mesh(Perm((1, 0, 2))) 286 | assert hard_mesh(Perm((1, 2, 0))) 287 | assert hard_mesh(Perm((2, 0, 1))) 288 | assert hard_mesh(Perm((2, 1, 0))) 289 | assert not hard_mesh(Perm((0, 1, 2, 3))) 290 | assert not hard_mesh(Perm((0, 1, 3, 2))) 291 | assert not hard_mesh(Perm((1, 5, 0, 2, 3, 4))) 292 | assert not hard_mesh(Perm((2, 4, 1, 5, 0, 3))) 293 | assert hard_mesh(Perm((1, 3, 2, 5, 4, 6, 0))) 294 | assert not hard_mesh(Perm((4, 3, 1, 6, 2, 0, 5))) 295 | assert not hard_mesh(Perm((3, 4, 1, 5, 2, 6, 0, 7))) 296 | assert not hard_mesh(Perm((4, 3, 2, 0, 1, 5, 7, 6, 8))) 297 | assert not hard_mesh(Perm((7, 0, 5, 4, 6, 3, 2, 9, 1, 8))) 298 | assert not hard_mesh(Perm((2, 6, 9, 1, 8, 0, 10, 5, 7, 3, 4))) 299 | assert 692 == sum(1 for p in Perm.of_length(7) if hard_mesh(p)) 300 | -------------------------------------------------------------------------------- /tests/enumeration_strategies/test_core_strategies.py: -------------------------------------------------------------------------------- 1 | from permuta import Perm 2 | from permuta.enumeration_strategies.core_strategies import ( 3 | Rd2134CoreStrategy, 4 | RdCdCoreStrategy, 5 | RdCdCuCoreStrategy, 6 | RdCuCoreStrategy, 7 | Ru2143CoreStrategy, 8 | RuCuCdCoreStrategy, 9 | RuCuCoreStrategy, 10 | RuCuRdCdCoreStrategy, 11 | bstrip, 12 | fstrip, 13 | last_skew_component, 14 | last_sum_component, 15 | zero_plus_perm, 16 | zero_plus_skewind, 17 | zero_plus_sumind, 18 | ) 19 | 20 | ru = Perm((1, 2, 0, 3)) 21 | cu = Perm((2, 0, 1, 3)) 22 | rd = Perm((1, 3, 0, 2)) 23 | cd = Perm((2, 0, 3, 1)) 24 | p2134 = Perm((1, 0, 2, 3)) 25 | p2143 = Perm((1, 0, 3, 2)) 26 | 27 | 28 | class TestRuCu: 29 | def test_applies(self): 30 | assert RuCuCoreStrategy([ru, cu]) 31 | assert RuCuCoreStrategy([ru, cu, Perm((0, 1, 2, 3))]) 32 | assert not RuCuCoreStrategy([ru, Perm((0, 1, 2, 3))]).applies() 33 | assert not RuCuCoreStrategy([ru, cu, Perm((1, 4, 0, 2, 3))]).applies() 34 | assert RuCuCoreStrategy([ru, cu, Perm((0, 1, 2, 3, 4))]).applies() 35 | assert RuCuCoreStrategy( 36 | [ru, cu, Perm((0, 2, 4, 1, 3)), Perm((0, 1, 3, 2, 4))] 37 | ).applies() 38 | 39 | def test_applies_to_a_symmetry(self): 40 | assert RuCuCoreStrategy([Perm([0, 2, 3, 1]), Perm([0, 3, 1, 2])]).applies() 41 | assert RuCuCoreStrategy( 42 | [Perm([0, 2, 3, 1]), Perm([0, 3, 1, 2]), Perm([0, 1, 2, 3])] 43 | ).applies() 44 | 45 | def test_specific_symmetry(self): 46 | b1 = frozenset([ru, cu, Perm((3, 0, 1, 2))]) 47 | assert not RuCuCoreStrategy(b1)._applies_to_symmetry(b1) 48 | b2 = frozenset([ru, cu, Perm((0, 3, 2, 1))]) 49 | assert not RuCuCoreStrategy(b2)._applies_to_symmetry(b2) 50 | b3 = frozenset([ru, cu]) 51 | assert RuCuCoreStrategy(b3)._applies_to_symmetry(b3) 52 | 53 | def test_reference(self): 54 | print(RuCuCoreStrategy.reference()) 55 | assert RuCuCoreStrategy.reference() == ( 56 | "Enumeration of Permutation " 57 | "Classes and Weighted Labelled Independent Sets: " 58 | "Corollary 4.3" 59 | ) 60 | 61 | 62 | class TestRdCd: 63 | def test_applies(self): 64 | assert not RdCdCoreStrategy([rd, cd, Perm((0, 1, 2, 3, 4))]).applies() 65 | assert not RdCdCoreStrategy([rd, Perm((0, 3, 2, 1))]).applies() 66 | assert RdCdCoreStrategy([rd, cd]).applies() 67 | assert RdCdCoreStrategy([rd, cd, Perm((0, 4, 1, 2, 3))]).applies() 68 | 69 | def test_applies_to_a_symmetry(self): 70 | assert RdCdCoreStrategy([Perm((1, 3, 0, 2)), Perm((2, 0, 3, 1))]).applies() 71 | 72 | def test_specific_symmetry(self): 73 | b1 = frozenset([Perm((1, 3, 0, 2)), Perm((2, 0, 3, 1))]) 74 | assert RdCdCoreStrategy(b1)._applies_to_symmetry(b1) 75 | b2 = frozenset([rd, cd]) 76 | assert RdCdCoreStrategy(b2)._applies_to_symmetry(b2) 77 | 78 | 79 | class TestRuCuRdCd: 80 | def test_applies(self): 81 | assert not RuCuRdCdCoreStrategy([rd, cd]).applies() 82 | assert not RuCuRdCdCoreStrategy([rd, cd, ru, Perm((0, 3, 2, 1))]).applies() 83 | assert RuCuRdCdCoreStrategy([rd, cd, ru, cu]).applies() 84 | assert RuCuRdCdCoreStrategy([rd, cd, ru, cu, Perm((0, 1, 2, 3))]).applies() 85 | 86 | def test_applies_to_a_symmetry(self): 87 | assert RuCuRdCdCoreStrategy( 88 | [ 89 | Perm((0, 2, 3, 1)), 90 | Perm((0, 3, 1, 2)), 91 | Perm((1, 3, 0, 2)), 92 | Perm((2, 0, 3, 1)), 93 | ] 94 | ).applies() 95 | 96 | def test_specific_symmetry(self): 97 | b1 = frozenset( 98 | [ 99 | Perm((0, 2, 3, 1)), 100 | Perm((0, 3, 1, 2)), 101 | Perm((1, 3, 0, 2)), 102 | Perm((2, 0, 3, 1)), 103 | ] 104 | ) 105 | assert not RuCuRdCdCoreStrategy(b1)._applies_to_symmetry(b1) 106 | b2 = frozenset([rd, cd, cu, ru]) 107 | assert RuCuRdCdCoreStrategy(b2)._applies_to_symmetry(b2) 108 | 109 | 110 | class TestRuCuCd: 111 | def test_applies(self): 112 | assert RuCuCdCoreStrategy([cd, ru, cu]).applies() 113 | assert not RuCuCdCoreStrategy([rd, cd]).applies() 114 | assert not RuCuCdCoreStrategy([rd, cd, ru, Perm((0, 3, 2, 1))]).applies() 115 | assert RuCuCdCoreStrategy([ru, cu, cd, Perm((0, 1, 2, 3))]).applies() 116 | 117 | def test_applies_to_a_symmetry(self): 118 | assert RuCuCdCoreStrategy([ru, cu, rd]).applies() 119 | assert RuCuCdCoreStrategy([ru, cu, rd, Perm((0, 2, 1, 3))]).applies() 120 | assert RuCuCdCoreStrategy( 121 | [Perm((0, 2, 3, 1)), Perm((0, 3, 1, 2)), Perm((1, 3, 0, 2))] 122 | ).applies() 123 | assert RuCuCdCoreStrategy( 124 | [ 125 | Perm((0, 2, 3, 1)), 126 | Perm((0, 3, 1, 2)), 127 | Perm((1, 3, 0, 2)), 128 | Perm((0, 1, 2, 3)), 129 | ] 130 | ).applies() 131 | # 123 implies the avoidance of ru and cu 132 | assert RuCuCdCoreStrategy([rd, Perm((0, 1, 2))]).applies() 133 | 134 | 135 | class TestRdCdCu: 136 | def test_applies(self): 137 | assert RdCdCuCoreStrategy([rd, cd, cu]).applies() 138 | assert not RdCdCuCoreStrategy([rd, cd]).applies() 139 | assert not RdCdCuCoreStrategy([rd, cd, cu, Perm((0, 1, 2, 3))]).applies() 140 | assert RdCdCuCoreStrategy([rd, cd, cu, Perm((0, 2, 1, 3))]).applies() 141 | assert RdCdCuCoreStrategy([rd, cd, cu, Perm((0, 3, 2, 1))]).applies() 142 | 143 | def test_applies_to_a_symmetry(self): 144 | assert RdCdCuCoreStrategy([rd, cd, ru]).applies() 145 | # Symmetry and adding pattern 146 | assert RdCdCuCoreStrategy([Perm((1, 0, 2)), Perm((0, 3, 1, 2))]).applies() 147 | 148 | def test_is_valid_extension(self): 149 | assert RdCdCuCoreStrategy.is_valid_extension(Perm((0, 2, 1, 3))) 150 | assert not RdCdCuCoreStrategy.is_valid_extension(Perm((1, 2, 0, 3))) 151 | 152 | 153 | class TestRdCu: 154 | def test_applies(self): 155 | assert RdCuCoreStrategy([rd, cu]).applies() 156 | assert not RdCuCoreStrategy([rd, cd]).applies() 157 | assert not RdCuCoreStrategy([rd, cu, Perm((0, 1, 2, 3))]).applies() 158 | assert not RdCuCoreStrategy([rd, cu, Perm((0, 3, 2, 1))]).applies() 159 | assert RdCuCoreStrategy([rd, cu, Perm((0, 2, 1, 3))]).applies() 160 | assert RdCuCoreStrategy([rd, cu, Perm((0, 3, 1, 2, 4))]).applies() 161 | 162 | def test_applies_to_a_symmetry(self): 163 | assert RdCuCoreStrategy([ru, cd]).applies() 164 | 165 | def test_is_valid_extension(self): 166 | assert RdCuCoreStrategy.is_valid_extension(Perm((0, 2, 1, 3))) 167 | assert not RdCuCoreStrategy.is_valid_extension(Perm((1, 2, 0, 3))) 168 | 169 | 170 | class TestRd2134: 171 | def test_applies(self): 172 | assert Rd2134CoreStrategy([rd, p2134]).applies() 173 | assert not Rd2134CoreStrategy([rd, cu]).applies() 174 | assert not Rd2134CoreStrategy([rd, p2134, Perm((0, 4, 3, 1, 2))]).applies() 175 | assert not Rd2134CoreStrategy([rd, cu, Perm((0, 1, 2, 3))]).applies() 176 | assert Rd2134CoreStrategy([rd, p2134, Perm((0, 3, 4, 2, 1))]).applies() 177 | assert Rd2134CoreStrategy([rd, p2134, Perm((0, 3, 2, 4, 1))]).applies() 178 | assert Rd2134CoreStrategy([rd, p2134, Perm((0, 1, 2, 3))]).applies() 179 | assert Rd2134CoreStrategy( 180 | [rd, p2134, Perm((0, 2, 1, 3)), Perm((0, 1, 4, 2, 3))] 181 | ).applies() 182 | 183 | def test_applies_to_a_symmetry(self): 184 | assert Rd2134CoreStrategy([cd, p2134]).applies() 185 | assert Rd2134CoreStrategy( 186 | [ 187 | Perm.from_string("0132"), 188 | Perm.from_string("1302"), 189 | Perm.from_string("32014"), 190 | ] 191 | ).applies() 192 | 193 | def test_is_valid_extension(self): 194 | assert Rd2134CoreStrategy.is_valid_extension(Perm((0, 3, 4, 2, 1))) 195 | assert not Rd2134CoreStrategy.is_valid_extension(Perm((0, 4, 3, 1, 2))) 196 | assert Rd2134CoreStrategy.is_valid_extension(Perm((0, 1, 2, 3))) 197 | assert not Rd2134CoreStrategy.is_valid_extension(Perm((0, 1, 2, 4, 3))) 198 | 199 | 200 | class TestRu2143: 201 | def test_applies(self): 202 | assert Ru2143CoreStrategy([ru, p2143]).applies() 203 | assert not Ru2143CoreStrategy([ru]).applies() 204 | assert not Ru2143CoreStrategy([ru, p2143, Perm((0, 2, 1, 3, 4))]).applies() 205 | assert not Ru2143CoreStrategy([ru, p2143, Perm((0, 4, 3, 1, 2))]).applies() 206 | assert Ru2143CoreStrategy([ru, p2143, Perm((0, 2, 4, 1, 3))]).applies() 207 | assert Ru2143CoreStrategy([ru, p2143, Perm((0, 1, 2, 4, 3))]).applies() 208 | 209 | def test_applies_to_a_symmetry(self): 210 | assert Ru2143CoreStrategy([cu, p2143]).applies() 211 | assert Ru2143CoreStrategy( 212 | [ 213 | Perm.from_string("0231"), 214 | Perm.from_string("1032"), 215 | Perm.from_string("03124"), 216 | ] 217 | ).applies() 218 | 219 | def test_is_valid_extension(self): 220 | assert Ru2143CoreStrategy.is_valid_extension(Perm((0, 2, 4, 1, 3))) 221 | assert not Ru2143CoreStrategy.is_valid_extension(Perm((0, 2, 1, 3, 4))) 222 | assert not Ru2143CoreStrategy.is_valid_extension(Perm((0, 4, 3, 1, 2))) 223 | 224 | 225 | # Test for tools functions 226 | 227 | 228 | def test_fstrip(): 229 | assert fstrip(Perm((0, 1, 3, 2))) == Perm((0, 2, 1)) 230 | assert fstrip(Perm((4, 0, 1, 3, 2))) == Perm((4, 0, 1, 3, 2)) 231 | 232 | 233 | def test_bstrip(): 234 | assert bstrip(Perm((0, 1, 3, 2))) == Perm((0, 1, 3, 2)) 235 | assert bstrip(Perm((0, 1, 3, 2, 4))) == Perm((0, 1, 3, 2)) 236 | 237 | 238 | def test_zero_plus_perm(): 239 | assert zero_plus_perm(Perm((0, 1, 2))) 240 | assert not zero_plus_perm(Perm((3, 0, 1, 2))) 241 | 242 | 243 | def test_zero_plus_skewind(): 244 | assert zero_plus_skewind(Perm((0, 1, 3, 2))) 245 | assert not zero_plus_skewind(Perm((0, 3, 1, 2))) 246 | assert not zero_plus_skewind(Perm((1, 3, 0, 2))) 247 | 248 | 249 | def test_zero_plus_sumind(): 250 | assert zero_plus_sumind(Perm((0, 3, 1, 2))) 251 | assert not zero_plus_sumind(Perm((0, 1, 3, 2))) 252 | assert not zero_plus_sumind(Perm((1, 3, 0, 2))) 253 | 254 | 255 | def test_last_sum_component(): 256 | assert last_sum_component(Perm((0, 1, 2, 4, 3))) == Perm((1, 0)) 257 | assert last_sum_component(Perm((0, 1, 2, 3))) == Perm((0,)) 258 | assert last_sum_component(Perm((3, 2, 1, 0))) == Perm((3, 2, 1, 0)) 259 | 260 | 261 | def test_last_skew_component(): 262 | last_skew_component(Perm((2, 4, 3, 0, 1))) == Perm((0, 1)) 263 | last_skew_component(Perm((0, 1, 2, 3))) == Perm((0, 1, 2, 3)) 264 | last_skew_component(Perm((3, 2, 1, 0))) == Perm((0,)) 265 | -------------------------------------------------------------------------------- /tests/enumeration_strategies/test_enumeration_strategies.py: -------------------------------------------------------------------------------- 1 | from permuta import Perm 2 | from permuta.enumeration_strategies import all_enumeration_strategies, find_strategies 3 | from permuta.enumeration_strategies.core_strategies import ( 4 | RdCdCoreStrategy, 5 | RuCuCoreStrategy, 6 | ) 7 | from permuta.enumeration_strategies.finitely_many_simples import ( 8 | FinitelyManySimplesStrategy, 9 | ) 10 | from permuta.enumeration_strategies.insertion_encodable import InsertionEncodingStrategy 11 | from permuta.perm_sets.basis import Basis 12 | 13 | ru = Perm((1, 2, 0, 3)) 14 | cu = Perm((2, 0, 1, 3)) 15 | rd = Perm((1, 3, 0, 2)) 16 | cd = Perm((2, 0, 3, 1)) 17 | 18 | 19 | def test_init_strategy(): 20 | b1 = [Perm((0, 1, 2))] 21 | b2 = Basis(*[Perm((0, 1, 2, 3)), Perm((2, 0, 1))]) 22 | for Strat in all_enumeration_strategies: 23 | Strat(b1).applies() 24 | Strat(b2).applies() 25 | 26 | 27 | def test_insertion_encoding(): 28 | strat = InsertionEncodingStrategy([Perm((0, 1, 2)), Perm((2, 0, 1))]) 29 | assert strat.applies() 30 | strat = InsertionEncodingStrategy([Perm((0, 2, 1, 3))]) 31 | assert not strat.applies() 32 | 33 | 34 | def test_finite_simples(): 35 | strat = FinitelyManySimplesStrategy([Perm((0, 1, 2))]) 36 | assert not strat.applies() 37 | strat = FinitelyManySimplesStrategy([Perm((1, 3, 0, 2))]) 38 | assert not strat.applies() 39 | strat = FinitelyManySimplesStrategy([Perm((2, 0, 3, 1))]) 40 | assert not strat.applies() 41 | strat = FinitelyManySimplesStrategy([Perm((0, 2, 1, 3))]) 42 | assert not strat.applies() 43 | strat = FinitelyManySimplesStrategy([Perm((0, 2, 1))]) 44 | assert strat.applies() 45 | strat = FinitelyManySimplesStrategy([Perm((1, 3, 0, 2)), Perm((2, 0, 3, 1))]) 46 | assert strat.applies() 47 | 48 | 49 | def test_RuCu(): 50 | assert not RuCuCoreStrategy([ru, cu, Perm((1, 4, 0, 2, 3))]).applies() 51 | assert not RuCuCoreStrategy([ru, Perm((0, 1, 2, 3))]).applies() 52 | assert RuCuCoreStrategy([ru, cu]).applies() 53 | assert RuCuCoreStrategy([ru, cu, Perm((0, 1, 2, 3, 4))]).applies() 54 | 55 | 56 | def test_RdCd(): 57 | assert not RdCdCoreStrategy([rd, cd, Perm((0, 1, 2, 3, 4))]).applies() 58 | assert not RdCdCoreStrategy([rd, Perm((0, 3, 2, 1))]).applies() 59 | assert RdCdCoreStrategy([rd, cd]).applies() 60 | assert RdCdCoreStrategy([rd, cd, Perm((0, 4, 1, 2, 3))]).applies() 61 | 62 | 63 | def test_find_strategies(): 64 | b1 = [Perm((0, 1, 2, 3, 4))] 65 | b2 = Basis(*[Perm((0, 1, 2, 3)), Perm((2, 0, 1))]) 66 | b3 = [Perm((1, 3, 0, 2)), Perm((2, 0, 3, 1))] 67 | assert len(find_strategies(b1, long_runnning=True)) == 0 68 | assert len(find_strategies(b1, long_runnning=False)) == 0 69 | assert len(find_strategies(b2)) > 0 70 | assert len(find_strategies(b3, long_runnning=False)) == 1 71 | assert len(find_strategies(b3, long_runnning=True)) == 2 72 | assert any(isinstance(s, InsertionEncodingStrategy) for s in find_strategies(b2)) 73 | assert any(isinstance(s, RuCuCoreStrategy) for s in find_strategies([ru, cu])) 74 | -------------------------------------------------------------------------------- /tests/misc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PermutaTriangle/Permuta/ce704c07ce10ab131d1098c7a7eaef077ca19a82/tests/misc/__init__.py -------------------------------------------------------------------------------- /tests/misc/test_union_find.py: -------------------------------------------------------------------------------- 1 | import random 2 | from itertools import combinations 3 | 4 | from permuta.misc.union_find import UnionFind 5 | 6 | 7 | def test_union_find_init(): 8 | uf = UnionFind(10) 9 | assert sum(x == -1 for x in uf._parent) == 10 10 | 11 | 12 | def test_union_find_1(): 13 | uf = UnionFind(10) 14 | assert uf.find(1) != uf.find(3) 15 | assert uf.unite(1, 8) 16 | assert uf.unite(3, 8) 17 | assert uf.find(1) == uf.find(3) 18 | assert uf.size(1) == uf.size(3) == uf.size(8) == 3 19 | assert all(uf.size(i) == 1 for i in range(10) if i not in (1, 3, 8)) 20 | 21 | 22 | def test_union_find_2(): 23 | uf = UnionFind(100) 24 | for i in range(1, 100): 25 | assert uf.unite(0, i) 26 | for a, b in combinations(range(100), 2): 27 | assert uf.find(a) == uf.find(b) 28 | assert uf.size(a) == 100 29 | assert uf.size(b) == 100 30 | 31 | 32 | def test_union_find_3(): 33 | uf = UnionFind(4) 34 | assert uf.find(0) == uf.find(0) 35 | assert not uf.unite(0, 0) 36 | assert uf.unite(1, 0) 37 | assert not uf.unite(0, 1) 38 | assert uf.unite(1, 2) 39 | assert not uf.unite(1, 2) 40 | assert not uf.unite(0, 2) 41 | assert uf.find(0) != uf.find(3) 42 | 43 | 44 | def test_union_find_4(): 45 | n = 5000 46 | d = {i: {i} for i in range(n)} 47 | uf = UnionFind(n) 48 | 49 | for i in range(2 * n): 50 | a, b = random.randint(0, n - 1), random.randint(0, n - 1) 51 | if random.randint(0, 1): 52 | combined = d[a].union(d[b]) 53 | for x in combined: 54 | d[x] = combined 55 | uf.unite(a, b) 56 | assert uf.size(a) == len(combined) == uf.size(b) 57 | else: 58 | if a in d[b]: 59 | assert uf.find(a) == uf.find(b) 60 | else: 61 | assert uf.find(a) != uf.find(b) 62 | -------------------------------------------------------------------------------- /tests/perm_sets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PermutaTriangle/Permuta/ce704c07ce10ab131d1098c7a7eaef077ca19a82/tests/perm_sets/__init__.py -------------------------------------------------------------------------------- /tests/perm_sets/test_av.py: -------------------------------------------------------------------------------- 1 | from math import factorial 2 | 3 | import pytest 4 | 5 | from permuta import MeshPatt, Perm 6 | from permuta.perm_sets import Av 7 | from permuta.perm_sets.basis import Basis, MeshBasis 8 | 9 | 10 | # binom will be added to math in 3.8 so when pypy is compatible with 3.8, replace: 11 | def binom(n, k): 12 | return factorial(n) // (factorial(n - k) * factorial(k)) 13 | 14 | 15 | def catalan(n): 16 | return binom(2 * n, n) // (n + 1) 17 | 18 | 19 | test_classes = [ 20 | ([[0, 1]], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), 21 | ([[1, 0]], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), 22 | ([[0, 1, 2]], [catalan(i) for i in range(8)]), 23 | ([[0, 2, 1]], [catalan(i) for i in range(8)]), 24 | ([[1, 0, 2]], [catalan(i) for i in range(8)]), 25 | ([[1, 2, 0]], [catalan(i) for i in range(8)]), 26 | ([[2, 0, 1]], [catalan(i) for i in range(8)]), 27 | ([[2, 1, 0]], [catalan(i) for i in range(8)]), 28 | ([[0, 2, 1, 3]], [1, 1, 2, 6, 23, 103, 513, 2762]), 29 | ([[0, 2, 3, 1]], [1, 1, 2, 6, 23, 103, 512, 2740, 15485]), 30 | ([[0, 3, 2, 1]], [1, 1, 2, 6, 23, 103, 513, 2761, 15767]), 31 | ([[1, 0, 2], [2, 1, 0]], [1, 1, 2, 4, 7, 11, 16, 22]), 32 | ([[0, 2, 1], [3, 2, 1, 0]], [1, 1, 2, 5, 13, 31, 66, 127]), 33 | ([[2, 1, 0], [1, 2, 3, 0]], [1, 1, 2, 5, 13, 34, 89, 233]), 34 | ([[3, 2, 1, 0], [3, 2, 0, 1]], [1, 1, 2, 6, 22, 90, 394, 1806]), 35 | ([[2, 3, 0, 1], [1, 3, 0, 2]], [1, 1, 2, 6, 22, 90, 395, 1823]), 36 | ( 37 | [[3, 1, 2, 0], [2, 4, 0, 3, 1], [3, 1, 4, 0, 2], [2, 4, 0, 5, 1, 3]], 38 | [1, 1, 2, 6, 23, 101, 477, 2343, 11762], 39 | ), 40 | ([[0, 2, 1], [2, 1, 3, 4, 0]], [1, 1, 2, 5, 14, 41, 122, 365, 1094]), 41 | ] 42 | 43 | 44 | @pytest.mark.parametrize("patts,enum", test_classes) 45 | def test_avoiding_enumeration(patts, enum): 46 | patts = [Perm(patt) for patt in patts] 47 | basis = Basis(*patts) 48 | for n, cnt in enumerate(enum): 49 | # print(n, cnt) 50 | inst = Av(basis).of_length(n) 51 | gen = list(inst) 52 | # assert len(gen) == cnt 53 | assert len(gen) == len(set(gen)) 54 | for perm in gen: 55 | assert perm.avoids(*patts) 56 | 57 | mx = len(enum) - 1 58 | cnt = [0 for _ in range(mx + 1)] 59 | for perm in Av(basis).up_to_length(mx): 60 | assert perm.avoids(*patts) 61 | cnt[len(perm)] += 1 62 | 63 | assert enum == cnt 64 | 65 | 66 | def test_avoiding_generic_mesh_patterns(): 67 | p = Perm((2, 0, 1)) 68 | shading = ((2, 0), (2, 1), (2, 2), (2, 3)) 69 | mps = [MeshPatt(p, shading)] 70 | meshbasis = MeshBasis(*mps) 71 | avoiding_generic_basis = Av(meshbasis) 72 | enum = [1, 1, 2, 5, 15, 52, 203, 877] # Bell numbers 73 | 74 | for n, cnt in enumerate(enum): 75 | inst = avoiding_generic_basis.of_length(n) 76 | gen = list(inst) 77 | assert len(gen) == cnt 78 | assert len(gen) == len(set(gen)) 79 | for perm in gen: 80 | assert perm.avoids(*mps) 81 | assert perm in avoiding_generic_basis 82 | 83 | mx = len(enum) - 1 84 | cnt = [0 for _ in range(mx + 1)] 85 | for perm in Av(meshbasis).up_to_length(mx): 86 | assert perm.avoids(*mps) 87 | cnt[len(perm)] += 1 88 | 89 | assert enum == cnt 90 | 91 | 92 | def test_avoiding_generic_finite_class(): 93 | ts = [ 94 | ([[0]], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 95 | ([[0, 1], [3, 2, 1, 0]], [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]), 96 | ([[0, 1, 2], [3, 2, 1, 0]], [1, 1, 2, 5, 13, 25, 25, 0, 0, 0, 0, 0]), 97 | ] 98 | 99 | for patts, enum in ts: 100 | patts = [Perm(patt) for patt in patts] 101 | basis = Basis(*patts) 102 | for n, cnt in enumerate(enum): 103 | inst = Av(basis).of_length(n) 104 | gen = list(inst) 105 | assert len(gen) == cnt 106 | assert len(gen) == len(set(gen)) 107 | for perm in gen: 108 | assert perm.avoids(*patts) 109 | 110 | mx = len(enum) - 1 111 | cnt = [0 for _ in range(mx + 1)] 112 | for perm in Av(basis).up_to_length(mx): 113 | assert perm.avoids(*patts) 114 | cnt[len(perm)] += 1 115 | 116 | assert enum == cnt 117 | 118 | 119 | def test_is_subclass(): 120 | av1 = Av.from_iterable((Perm((0,)),)) 121 | av12_21 = Av.from_iterable((Perm((0, 1)), Perm((1, 0)))) 122 | av123 = Av.from_iterable((Perm((0, 1, 2)),)) 123 | av1234 = Av.from_iterable((Perm((0, 1, 2, 3)),)) 124 | assert av1.is_subclass(av123) 125 | assert not av123.is_subclass(av1) 126 | assert av123.is_subclass(av1234) 127 | assert not av1234.is_subclass(av12_21) 128 | assert av12_21.is_subclass(av1234) 129 | assert av123.is_subclass(av123) 130 | av1324_1423_12345 = Av.from_iterable( 131 | (Perm((0, 2, 1, 3)), Perm((0, 3, 1, 2)), Perm((0, 1, 2, 3, 4, 5))) 132 | ) 133 | av1324_1234 = Av.from_iterable((Perm((0, 2, 1, 3)), Perm((0, 1, 2, 3)))) 134 | av1234_132 = Av.from_iterable((Perm((0, 1, 2, 3)), Perm((0, 2, 1)))) 135 | assert av123.is_subclass(av1324_1423_12345) 136 | assert not av1324_1234.is_subclass(av1324_1423_12345) 137 | assert av1234_132.is_subclass(av1324_1423_12345) 138 | 139 | 140 | def test_av_of_length(): 141 | assert [ 142 | sum(1 for _ in Av(Basis(Perm((0, 2, 1)))).of_length(i)) for i in range(8) 143 | ] == [1, 1, 2, 5, 14, 42, 132, 429] 144 | 145 | 146 | def test_av_perm(): 147 | p = Perm((0, 1)) 148 | av = Av([p]) 149 | for length in range(10): 150 | assert len(set(av.of_length(length))) == 1 151 | 152 | 153 | def test_av_meshpatt(): 154 | p = Perm((2, 0, 1)) 155 | shading = ((2, 0), (2, 1), (2, 2), (2, 3)) 156 | mp = MeshPatt(p, shading) 157 | av = Av([mp]) 158 | enum = [1, 1, 2, 5, 15, 52, 203, 877] # Bell numbers 159 | 160 | for n, cnt in enumerate(enum): 161 | inst = av.of_length(n) 162 | gen = list(inst) 163 | assert len(gen) == cnt 164 | 165 | 166 | def test_enumeration(): 167 | assert ( 168 | Av.from_string("132").enumeration(8) 169 | == Av(Basis(Perm((0, 2, 1)))).enumeration(8) 170 | == [1, 1, 2, 5, 14, 42, 132, 429, 1430] 171 | ) 172 | assert ( 173 | Av.from_string("Av(123,231)").enumeration(8) 174 | == Av(Basis(Perm((0, 1, 2)), Perm((1, 2, 0)))).enumeration(8) 175 | == [1, 1, 2, 4, 7, 11, 16, 22, 29] 176 | ) 177 | assert Av( 178 | (Perm((0, 1, 2)), MeshPatt(Perm((2, 0, 1)), [(0, 1), (1, 1), (2, 1), (3, 1)])) 179 | ).enumeration(7) == [1, 1, 2, 4, 8, 16, 32, 64] 180 | assert ( 181 | Av.from_string("0123_2013_1023").enumeration(8) 182 | == Av( 183 | Basis(Perm((0, 1, 2, 3)), Perm((2, 0, 1, 3)), Perm((1, 0, 2, 3))) 184 | ).enumeration(8) 185 | == [1, 1, 2, 6, 21, 79, 309, 1237, 5026] 186 | ) 187 | assert ( 188 | Av.from_string("1243 1342 3241 3241").enumeration(8) 189 | == Av( 190 | Basis( 191 | Perm((0, 1, 3, 2)), 192 | Perm((0, 2, 3, 1)), 193 | Perm((2, 1, 3, 0)), 194 | Perm((2, 1, 3, 0)), 195 | ) 196 | ).enumeration(8) 197 | == [1, 1, 2, 6, 21, 75, 262, 891, 2964] 198 | ) 199 | assert ( 200 | Av.from_string("Av(1342, 3124, 1432, 4312)").enumeration(8) 201 | == Av( 202 | Basis( 203 | Perm((0, 2, 3, 1)), 204 | Perm((2, 0, 1, 3)), 205 | Perm((0, 3, 2, 1)), 206 | Perm((3, 2, 0, 1)), 207 | ) 208 | ).enumeration(8) 209 | == [1, 1, 2, 6, 20, 61, 169, 442, 1120] 210 | ) 211 | 212 | 213 | def test_generators(): 214 | assert list(Av(Basis(Perm((0, 1)), Perm((1, 0)))).first(500)) == [ 215 | Perm(), 216 | Perm((0,)), 217 | ] 218 | assert sorted(Av(Basis(Perm((0, 2, 1)), Perm((1, 2, 0)))).of_length(3)) == sorted( 219 | set(Perm.of_length(3)) - {Perm((0, 2, 1)), Perm((1, 2, 0))} 220 | ) 221 | assert sorted( 222 | Av(Basis(Perm((0, 2, 1)), Perm((1, 2, 0)))).up_to_length(3) 223 | ) == sorted(set(Perm.up_to_length(3)) - {Perm((0, 2, 1)), Perm((1, 2, 0))}) 224 | 225 | 226 | def test_instance_variable_cache(): 227 | Av.clear_cache() 228 | basis = Basis(Perm((0, 1))) 229 | av = Av(basis) 230 | assert basis in Av._CLASS_CACHE 231 | list(av.of_length(5)) 232 | assert len(av.cache) == 6 233 | assert len(Av(Basis(Perm((0, 1)))).cache) == 6 234 | av2 = Av(Basis(Perm((0, 1)))) 235 | assert len(av2.cache) == 6 236 | list(av2.of_length(10)) 237 | assert len(av.cache) == 11 238 | assert len(av2.cache) == 11 239 | assert len(Av.from_string("12").cache) == 11 240 | assert len(Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)))).cache) == 1 241 | list(Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)))).of_length(5)) 242 | assert len(Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)))).cache) == 6 243 | assert ( 244 | len(Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)), Perm((1, 2, 0, 3)))).cache) == 6 245 | ) 246 | assert len(Av(Basis(Perm((1, 2, 0)), Perm((2, 0, 1)))).cache) == 6 247 | for p in Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)), Perm((1, 2, 0, 3)))).of_length( 248 | 10 249 | ): 250 | pass 251 | assert len(Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)))).cache) == 11 252 | Av.clear_cache() 253 | 254 | 255 | def test_class_variable_cache(): 256 | Av.clear_cache() 257 | assert len(Av._CLASS_CACHE) == 0 258 | assert Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)))) is Av( 259 | Basis(Perm((2, 0, 1)), Perm((1, 2, 0))) 260 | ) 261 | av = Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)))) 262 | assert av is Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)))) 263 | assert av is Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)), Perm((1, 2, 0, 3)))) 264 | assert len(Av._CLASS_CACHE) == 1 265 | av2 = Av(Basis(Perm((0, 1, 3, 2)), Perm((0, 2, 1)))) 266 | assert len(Av._CLASS_CACHE) == 2 267 | assert av is not av2 268 | assert av2 is Av(Basis(Perm((0, 2, 1)))) 269 | assert Av.from_string("132") is av2 270 | assert Basis(Perm((0, 2, 1))) in Av._CLASS_CACHE 271 | assert ( 272 | Av._CLASS_CACHE[Basis(Perm((0, 2, 1)))] 273 | is Av._CLASS_CACHE[Basis(Perm((0, 1, 3, 2)), Perm((0, 2, 1)))] 274 | ) 275 | assert Av((Perm((2, 0, 1)),)) is Av(Basis(Perm((2, 0, 1)))) 276 | Av.clear_cache() 277 | assert len(Av._CLASS_CACHE) == 0 278 | 279 | 280 | def test_valid_error_in_construction(): 281 | with pytest.raises(ValueError): 282 | Av(Basis()) 283 | with pytest.raises(ValueError): 284 | Av(Basis(Perm())) 285 | 286 | 287 | def test_invalid_ops_with_mesh_patt(): 288 | with pytest.raises(NotImplementedError): 289 | Av(MeshBasis(Perm((0, 1)))).is_finite() 290 | with pytest.raises(NotImplementedError): 291 | Av(MeshBasis(Perm((0, 1)))).is_insertion_encodable() 292 | with pytest.raises(NotImplementedError): 293 | Av(MeshBasis(Perm((0, 1)))).is_polynomial() 294 | -------------------------------------------------------------------------------- /tests/perm_sets/test_basis.py: -------------------------------------------------------------------------------- 1 | from permuta import MeshPatt, Perm 2 | from permuta.perm_sets.basis import Basis, MeshBasis 3 | 4 | 5 | def test_is_mesh_basis(): 6 | assert not MeshBasis.is_mesh_basis(()) 7 | assert not MeshBasis.is_mesh_basis([]) 8 | p1 = Perm((0, 2, 1)) 9 | p2 = Perm((2, 0, 1)) 10 | perm_list = [p1, p2] 11 | assert not MeshBasis.is_mesh_basis(p1) 12 | assert not MeshBasis.is_mesh_basis(p2) 13 | assert not MeshBasis.is_mesh_basis(perm_list) 14 | p1 = Perm((0, 2, 1)) 15 | p2 = Perm((2, 0, 1)) 16 | shading = ((2, 0), (2, 1), (2, 2), (2, 3)) 17 | mp1 = MeshPatt(p1, shading) 18 | mp2 = MeshPatt(p2, shading) 19 | meshpatt_list = [mp1, mp2] 20 | mixed_patt_list = [p1, mp2] 21 | assert MeshBasis.is_mesh_basis(mp1) 22 | assert MeshBasis.is_mesh_basis(mp2) 23 | assert MeshBasis.is_mesh_basis(meshpatt_list) 24 | assert MeshBasis.is_mesh_basis(mixed_patt_list) 25 | 26 | 27 | def test_meshbasis_of_perms(): 28 | p1 = Perm((0, 2, 1)) 29 | p2 = Perm((2, 0, 1)) 30 | assert MeshBasis(p1, p2) == MeshBasis(MeshPatt(p1, []), MeshPatt(p2, [])) 31 | 32 | shading = ((2, 0), (2, 1), (2, 2), (2, 3)) 33 | mp1 = MeshPatt(p1, shading) 34 | mp2 = MeshPatt(p2, shading) 35 | assert MeshBasis(p1, p2) == MeshBasis(p1, p2, mp1, mp2) 36 | 37 | for elmnt in MeshBasis(p1, p2): 38 | assert isinstance(elmnt, MeshPatt) 39 | 40 | 41 | def test_meshbasis(): 42 | assert MeshBasis(*[MeshPatt(), MeshPatt(Perm((0,)), ())]) == MeshBasis(MeshPatt()) 43 | assert MeshBasis(*[MeshPatt(), Perm()]) == MeshBasis(MeshPatt()) 44 | assert MeshBasis(*(Perm((1, 2, 0)), MeshPatt(Perm((1, 2, 0)), []))) == MeshBasis( 45 | *(MeshPatt(Perm((1, 2, 0)), []),) 46 | ) 47 | assert MeshBasis( 48 | *( 49 | Perm((1, 2, 0)), 50 | MeshPatt(Perm((0, 2, 1)), [(0, 0), (0, 1), (1, 1), (2, 3), (3, 0), (3, 3)]), 51 | ) 52 | ) == MeshBasis( 53 | *( 54 | MeshPatt(Perm((0, 2, 1)), [(0, 0), (0, 1), (1, 1), (2, 3), (3, 0), (3, 3)]), 55 | MeshPatt(Perm((1, 2, 0)), []), 56 | ) 57 | ) 58 | assert MeshBasis( 59 | *( 60 | Perm((1, 2, 0)), 61 | MeshPatt(Perm((0, 2, 1)), [(0, 0), (0, 1), (1, 1), (2, 3), (3, 0), (3, 3)]), 62 | ) 63 | ) != MeshBasis( 64 | *(MeshPatt(Perm((0, 2, 1)), [(0, 0), (0, 1), (1, 1), (2, 3), (3, 0), (3, 3)]),) 65 | ) 66 | assert MeshBasis( 67 | *( 68 | MeshPatt(Perm((1, 2, 0)), [(0, 0), (0, 2), (1, 1), (1, 3), (2, 0), (2, 1)]), 69 | MeshPatt( 70 | Perm((3, 4, 2, 0, 1)), 71 | [ 72 | (0, 0), 73 | (0, 1), 74 | (0, 2), 75 | (0, 3), 76 | (0, 4), 77 | (1, 2), 78 | (1, 3), 79 | (1, 4), 80 | (1, 5), 81 | (2, 0), 82 | (2, 1), 83 | (2, 2), 84 | (2, 3), 85 | (3, 0), 86 | (3, 2), 87 | (3, 4), 88 | (3, 5), 89 | (4, 0), 90 | (4, 1), 91 | (4, 3), 92 | (4, 4), 93 | (5, 0), 94 | (5, 2), 95 | (5, 4), 96 | (5, 5), 97 | ], 98 | ), 99 | ) 100 | ) == MeshBasis( 101 | *(MeshPatt(Perm((1, 2, 0)), [(0, 0), (0, 2), (1, 1), (1, 3), (2, 0), (2, 1)]),) 102 | ) 103 | assert MeshBasis( 104 | *(Perm((2, 0, 1)), MeshPatt(Perm((2, 0, 1)), [(2, 2)])) 105 | ) == MeshBasis(*(Perm((2, 0, 1)),)) 106 | 107 | 108 | def test_basis(): 109 | assert Basis(*[Perm(), Perm((0,))]) == Basis(Perm()) 110 | assert Basis(*[Perm((0, 2, 1)), Perm((0, 3, 2, 1))]) == Basis(Perm((0, 2, 1))) 111 | assert Basis(Perm((2, 0, 1))) == Basis( 112 | *( 113 | Perm((4, 0, 1, 7, 2, 6, 3, 5)), 114 | Perm((4, 2, 3, 0, 7, 9, 6, 8, 1, 5)), 115 | Perm((0, 10, 3, 8, 7, 12, 2, 4, 6, 13, 5, 16, 14, 17, 1, 11, 15, 9)), 116 | Perm((8, 11, 7, 1, 4, 5, 10, 6, 0, 3, 2, 9)), 117 | Perm((2, 0, 1)), 118 | Perm((4, 1, 0, 5, 2, 3)), 119 | Perm((5, 3, 4, 1, 2, 0)), 120 | Perm((1, 0, 3, 6, 4, 5, 2)), 121 | Perm((1, 7, 10, 2, 3, 6, 0, 4, 9, 5, 8)), 122 | Perm((1, 2, 8, 9, 4, 6, 14, 10, 3, 12, 13, 5, 7, 11, 0)), 123 | Perm((10, 13, 6, 9, 8, 12, 2, 4, 11, 0, 3, 16, 14, 15, 7, 5, 1)), 124 | ) 125 | ) 126 | assert Basis(*(Perm((0, 2, 1)), Perm((0, 1, 2)))) == Basis( 127 | *( 128 | Perm((2, 3, 16, 14, 0, 6, 7, 13, 8, 11, 9, 15, 5, 1, 10, 12, 4, 17)), 129 | Perm( 130 | (4, 3, 9, 6, 1, 7, 5, 0, 12, 17, 16, 11, 18, 14, 10, 19, 13, 15, 2, 8) 131 | ), 132 | Perm((12, 5, 14, 0, 2, 8, 7, 6, 9, 13, 10, 11, 3, 4, 1)), 133 | Perm((0, 4, 3, 6, 2, 1, 5)), 134 | Perm((0, 2, 4, 3, 7, 5, 1, 6, 8)), 135 | Perm((0, 1, 2)), 136 | Perm((4, 3, 8, 9, 7, 5, 1, 6, 2, 0)), 137 | Perm((8, 13, 6, 4, 10, 7, 2, 12, 3, 0, 11, 1, 15, 5, 14, 16, 9)), 138 | Perm((2, 4, 0, 1, 6, 7, 3, 5)), 139 | Perm((5, 1, 4, 7, 3, 2, 0, 6, 8, 9)), 140 | Perm((0, 2, 1)), 141 | Perm((6, 0, 2, 1, 5, 3, 4)), 142 | ) 143 | ) 144 | assert Basis(*(Perm((0, 1, 2)), Perm((5, 2, 4, 3, 0, 1)))) == Basis( 145 | *( 146 | Perm( 147 | (16, 14, 5, 8, 19, 4, 12, 15, 9, 10, 7, 18, 0, 2, 13, 3, 6, 17, 11, 1) 148 | ), 149 | Perm((12, 16, 9, 4, 5, 0, 10, 15, 13, 1, 14, 8, 17, 11, 6, 18, 7, 2, 3)), 150 | Perm( 151 | (8, 14, 19, 0, 12, 11, 2, 4, 16, 7, 18, 13, 15, 17, 6, 5, 3, 9, 10, 1) 152 | ), 153 | Perm((5, 2, 4, 3, 0, 1)), 154 | Perm( 155 | (8, 15, 7, 4, 9, 14, 17, 3, 10, 6, 19, 18, 2, 16, 0, 1, 5, 11, 13, 12) 156 | ), 157 | Perm((3, 7, 4, 6, 1, 5, 8, 9, 0, 10, 2)), 158 | Perm((9, 3, 8, 6, 12, 7, 11, 5, 10, 4, 13, 0, 1, 2)), 159 | Perm((0, 1, 2)), 160 | Perm((8, 11, 12, 15, 5, 9, 16, 13, 0, 4, 10, 6, 17, 7, 14, 1, 2, 3)), 161 | Perm((16, 11, 4, 5, 2, 10, 0, 12, 15, 14, 8, 6, 17, 9, 13, 3, 1, 7)), 162 | Perm((2, 1, 17, 16, 13, 9, 3, 15, 18, 10, 8, 6, 12, 14, 4, 0, 7, 11, 5)), 163 | Perm((14, 8, 2, 11, 10, 13, 7, 16, 0, 15, 3, 1, 12, 9, 6, 4, 5)), 164 | ) 165 | ) 166 | assert Basis( 167 | *( 168 | Perm((2, 0, 1)), 169 | Perm((1, 2, 3, 5, 4, 0)), 170 | Perm((2, 0, 1, 3)), 171 | Perm((1, 2, 3, 5, 4, 0, 6)), 172 | ) 173 | ) == Basis( 174 | *( 175 | Perm( 176 | (7, 10, 18, 12, 13, 15, 11, 4, 17, 2, 9, 3, 1, 14, 8, 0, 5, 6, 16, 19) 177 | ), 178 | Perm((1, 2, 3, 5, 4, 0)), 179 | Perm((0, 14, 6, 13, 12, 2, 5, 3, 11, 16, 17, 4, 15, 8, 1, 10, 9, 7)), 180 | Perm((10, 3, 14, 18, 12, 8, 13, 6, 9, 16, 2, 17, 1, 7, 15, 11, 4, 5, 0)), 181 | Perm((2, 0, 1)), 182 | Perm((12, 14, 2, 18, 1, 11, 13, 17, 10, 7, 15, 4, 9, 8, 16, 5, 6, 0, 3)), 183 | Perm((5, 3, 7, 9, 10, 1, 4, 0, 2, 12, 11, 8, 6)), 184 | Perm( 185 | (17, 10, 8, 11, 6, 2, 12, 4, 1, 16, 0, 5, 3, 18, 14, 13, 15, 9, 7, 19) 186 | ), 187 | Perm((7, 11, 12, 5, 3, 14, 0, 6, 8, 4, 15, 1, 2, 9, 17, 16, 13, 10)), 188 | Perm((8, 1, 12, 14, 17, 2, 13, 10, 11, 5, 7, 15, 9, 0, 3, 6, 4, 16, 18)), 189 | Perm((7, 3, 1, 17, 0, 4, 18, 5, 16, 10, 15, 13, 11, 9, 12, 2, 14, 6, 8)), 190 | Perm( 191 | (3, 8, 19, 17, 18, 15, 7, 0, 6, 4, 1, 16, 13, 5, 14, 9, 2, 10, 11, 12) 192 | ), 193 | ) 194 | ) 195 | 196 | 197 | def test_alternative_construction_methods(): 198 | assert ( 199 | Basis.from_string("123_321") 200 | == Basis.from_iterable([Perm((0, 1, 2)), Perm((2, 1, 0))]) 201 | == Basis(Perm((0, 1, 2)), Perm((2, 1, 0))) 202 | == Basis.from_string("012:210") 203 | ) 204 | assert MeshBasis.from_iterable( 205 | [MeshPatt(Perm((1, 2, 0)), [(0, 0), (0, 2), (1, 1), (1, 3), (2, 0), (2, 1)])] 206 | ) == MeshBasis( 207 | MeshPatt(Perm((1, 2, 0)), [(0, 0), (0, 2), (1, 1), (1, 3), (2, 0), (2, 1)]) 208 | ) 209 | -------------------------------------------------------------------------------- /tests/permutils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PermutaTriangle/Permuta/ce704c07ce10ab131d1098c7a7eaef077ca19a82/tests/permutils/__init__.py -------------------------------------------------------------------------------- /tests/permutils/test_bijections.py: -------------------------------------------------------------------------------- 1 | from permuta import Perm 2 | from permuta.permutils.bijections import Bijections 3 | 4 | 5 | def test_simion_and_schmidt(): 6 | assert Bijections.simion_and_schmidt(Perm(())) == Perm(()) 7 | assert Bijections.simion_and_schmidt(Perm((0,))) == Perm((0,)) 8 | assert Bijections.simion_and_schmidt(Perm((0, 1))) == Perm((0, 1)) 9 | assert Bijections.simion_and_schmidt(Perm((1, 0))) == Perm((1, 0)) 10 | assert Bijections.simion_and_schmidt(Perm((0, 2, 1))) == Perm((0, 1, 2)) 11 | assert Bijections.simion_and_schmidt(Perm((1, 0, 2))) == Perm((1, 0, 2)) 12 | assert Bijections.simion_and_schmidt(Perm((1, 2, 0))) == Perm((1, 2, 0)) 13 | assert Bijections.simion_and_schmidt(Perm((2, 0, 1))) == Perm((2, 0, 1)) 14 | assert Bijections.simion_and_schmidt(Perm((2, 1, 0))) == Perm((2, 1, 0)) 15 | assert Bijections.simion_and_schmidt(Perm((0, 3, 2, 1))) == Perm((0, 1, 2, 3)) 16 | assert Bijections.simion_and_schmidt(Perm((1, 0, 3, 2))) == Perm((1, 0, 2, 3)) 17 | assert Bijections.simion_and_schmidt(Perm((1, 3, 0, 2))) == Perm((1, 2, 0, 3)) 18 | assert Bijections.simion_and_schmidt(Perm((1, 3, 2, 0))) == Perm((1, 2, 3, 0)) 19 | assert Bijections.simion_and_schmidt(Perm((2, 0, 3, 1))) == Perm((2, 0, 1, 3)) 20 | assert Bijections.simion_and_schmidt(Perm((2, 1, 0, 3))) == Perm((2, 1, 0, 3)) 21 | assert Bijections.simion_and_schmidt(Perm((2, 1, 3, 0))) == Perm((2, 1, 3, 0)) 22 | assert Bijections.simion_and_schmidt(Perm((2, 3, 0, 1))) == Perm((2, 3, 0, 1)) 23 | assert Bijections.simion_and_schmidt(Perm((2, 3, 1, 0))) == Perm((2, 3, 1, 0)) 24 | assert Bijections.simion_and_schmidt(Perm((3, 0, 2, 1))) == Perm((3, 0, 1, 2)) 25 | assert Bijections.simion_and_schmidt(Perm((3, 1, 0, 2))) == Perm((3, 1, 0, 2)) 26 | assert Bijections.simion_and_schmidt(Perm((3, 1, 2, 0))) == Perm((3, 1, 2, 0)) 27 | assert Bijections.simion_and_schmidt(Perm((3, 2, 0, 1))) == Perm((3, 2, 0, 1)) 28 | assert Bijections.simion_and_schmidt(Perm((3, 2, 1, 0))) == Perm((3, 2, 1, 0)) 29 | assert Bijections.simion_and_schmidt(Perm((0, 4, 3, 2, 1))) == Perm((0, 1, 2, 3, 4)) 30 | assert Bijections.simion_and_schmidt(Perm((1, 0, 4, 3, 2))) == Perm((1, 0, 2, 3, 4)) 31 | assert Bijections.simion_and_schmidt(Perm((1, 4, 0, 3, 2))) == Perm((1, 2, 0, 3, 4)) 32 | assert Bijections.simion_and_schmidt(Perm((1, 4, 3, 0, 2))) == Perm((1, 2, 3, 0, 4)) 33 | assert Bijections.simion_and_schmidt(Perm((1, 4, 3, 2, 0))) == Perm((1, 2, 3, 4, 0)) 34 | assert Bijections.simion_and_schmidt(Perm((2, 0, 4, 3, 1))) == Perm((2, 0, 1, 3, 4)) 35 | assert Bijections.simion_and_schmidt(Perm((2, 1, 0, 4, 3))) == Perm((2, 1, 0, 3, 4)) 36 | assert Bijections.simion_and_schmidt(Perm((2, 1, 4, 0, 3))) == Perm((2, 1, 3, 0, 4)) 37 | assert Bijections.simion_and_schmidt(Perm((2, 1, 4, 3, 0))) == Perm((2, 1, 3, 4, 0)) 38 | assert Bijections.simion_and_schmidt(Perm((2, 4, 0, 3, 1))) == Perm((2, 3, 0, 1, 4)) 39 | assert Bijections.simion_and_schmidt(Perm((2, 4, 1, 0, 3))) == Perm((2, 3, 1, 0, 4)) 40 | assert Bijections.simion_and_schmidt(Perm((2, 4, 1, 3, 0))) == Perm((2, 3, 1, 4, 0)) 41 | assert Bijections.simion_and_schmidt(Perm((2, 4, 3, 0, 1))) == Perm((2, 3, 4, 0, 1)) 42 | assert Bijections.simion_and_schmidt(Perm((2, 4, 3, 1, 0))) == Perm((2, 3, 4, 1, 0)) 43 | assert Bijections.simion_and_schmidt(Perm((3, 0, 4, 2, 1))) == Perm((3, 0, 1, 2, 4)) 44 | assert Bijections.simion_and_schmidt(Perm((3, 1, 0, 4, 2))) == Perm((3, 1, 0, 2, 4)) 45 | assert Bijections.simion_and_schmidt(Perm((3, 1, 4, 0, 2))) == Perm((3, 1, 2, 0, 4)) 46 | assert Bijections.simion_and_schmidt(Perm((3, 1, 4, 2, 0))) == Perm((3, 1, 2, 4, 0)) 47 | assert Bijections.simion_and_schmidt(Perm((3, 2, 0, 4, 1))) == Perm((3, 2, 0, 1, 4)) 48 | 49 | # Examples from article 50 | assert Bijections.simion_and_schmidt( 51 | Perm.to_standard((6, 8, 3, 2, 7, 1, 5, 4)) 52 | ) == Perm.to_standard((6, 7, 3, 2, 4, 1, 5, 8)) 53 | assert Bijections.simion_and_schmidt( 54 | Perm.to_standard((6, 5, 10, 9, 3, 1, 8, 7, 4, 2)) 55 | ) == Perm.to_standard((6, 5, 7, 8, 3, 1, 2, 4, 9, 10)) 56 | assert Bijections.simion_and_schmidt( 57 | Perm.to_standard((6, 7, 3, 2, 4, 1, 5, 8)), inverse=True 58 | ) == Perm.to_standard((6, 8, 3, 2, 7, 1, 5, 4)) 59 | assert Bijections.simion_and_schmidt( 60 | Perm.to_standard((6, 5, 7, 8, 3, 1, 2, 4, 9, 10)), inverse=True 61 | ) == Perm.to_standard((6, 5, 10, 9, 3, 1, 8, 7, 4, 2)) 62 | 63 | for p in Perm.first(500): 64 | try: 65 | assert ( 66 | Bijections.simion_and_schmidt(Bijections.simion_and_schmidt(p), True) 67 | == p 68 | ) 69 | except ValueError: 70 | pass 71 | -------------------------------------------------------------------------------- /tests/permutils/test_finite.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | from permuta import Perm 4 | from permuta.permutils.finite import is_finite 5 | 6 | 7 | def test_is_finite(): 8 | assert is_finite([Perm()]) 9 | assert is_finite([Perm((0,))]) 10 | for i in range(100): 11 | basis = [ 12 | Perm.monotone_decreasing(randint(0, 100)), 13 | Perm.monotone_increasing(randint(0, 100)), 14 | ] 15 | basis.extend([Perm.random(randint(0, 100)) for _ in range(randint(0, 10))]) 16 | assert is_finite(basis) 17 | assert not is_finite( 18 | ( 19 | p 20 | for p in (Perm.random(randint(0, 100)) for _ in range(10)) 21 | if not p.is_increasing() 22 | ) 23 | ) 24 | assert not is_finite( 25 | ( 26 | p 27 | for p in (Perm.random(randint(0, 100)) for _ in range(10)) 28 | if not p.is_decreasing() 29 | ) 30 | ) 31 | assert not is_finite([Perm((0, 1, 2))]) 32 | assert not is_finite([Perm((2, 1, 0))]) 33 | assert not is_finite((Perm.identity(i) for i in range(2, 10))) 34 | assert not is_finite((p for p in (Perm((1, 2, 0)), Perm((4, 1, 2, 0, 3))))) 35 | assert not is_finite((p for p in (Perm((1, 2)),))) 36 | # Old version failed on this 37 | assert is_finite((p for p in (Perm((0, 1)), Perm((1, 0))))) 38 | -------------------------------------------------------------------------------- /tests/permutils/test_function_imports.py: -------------------------------------------------------------------------------- 1 | from permuta import Perm 2 | from permuta.permutils import ( 3 | is_insertion_encodable, 4 | is_insertion_encodable_maximum, 5 | is_insertion_encodable_rightmost, 6 | is_non_polynomial, 7 | is_polynomial, 8 | ) 9 | 10 | 11 | def test_functions_polynomial(): 12 | assert is_polynomial( 13 | frozenset( 14 | { 15 | Perm((1, 0)), 16 | Perm((0, 2, 1, 3)), 17 | Perm((0, 1, 2)), 18 | Perm((2, 1, 0)), 19 | Perm((0, 2, 3, 1)), 20 | } 21 | ) 22 | ) 23 | assert is_non_polynomial( 24 | frozenset( 25 | { 26 | Perm((1, 0, 2, 3)), 27 | Perm((2, 0, 4, 1, 3)), 28 | Perm((2, 3, 4, 1, 0)), 29 | Perm((3, 4, 1, 0, 2)), 30 | } 31 | ) 32 | ) 33 | 34 | 35 | def test_functions_insertion_encodable(): 36 | assert is_insertion_encodable_rightmost( 37 | [ 38 | Perm(()), 39 | Perm((1, 0)), 40 | Perm((0, 2, 1)), 41 | Perm((1, 2, 0)), 42 | Perm((0, 1, 4, 2, 3)), 43 | Perm((2, 3, 0, 1, 4)), 44 | Perm((4, 7, 3, 0, 1, 5, 6, 2)), 45 | Perm((4, 7, 3, 2, 5, 6, 0, 1)), 46 | ] 47 | ) 48 | assert not is_insertion_encodable_rightmost( 49 | [ 50 | Perm((3, 2, 1, 4, 0)), 51 | Perm((4, 2, 3, 0, 1)), 52 | Perm((2, 3, 4, 0, 1, 5)), 53 | Perm((3, 2, 5, 4, 0, 1)), 54 | ] 55 | ) 56 | 57 | assert is_insertion_encodable_maximum( 58 | [ 59 | Perm((0, 3, 2, 1)), 60 | Perm((1, 2, 3, 0)), 61 | Perm((0, 2, 1, 4, 3)), 62 | Perm((6, 1, 0, 5, 3, 4, 2)), 63 | Perm((6, 0, 2, 5, 1, 4, 7, 3)), 64 | ] 65 | ) 66 | assert not is_insertion_encodable_maximum( 67 | [ 68 | Perm((1, 0, 2)), 69 | Perm((1, 3, 2, 0)), 70 | Perm((1, 5, 4, 2, 0, 3)), 71 | Perm((4, 2, 0, 3, 5, 1)), 72 | Perm((4, 3, 2, 0, 5, 1)), 73 | Perm((5, 1, 2, 4, 3, 0, 6)), 74 | ] 75 | ) 76 | 77 | assert is_insertion_encodable( 78 | [ 79 | Perm((0, 3, 2, 1)), 80 | Perm((0, 2, 3, 4, 1)), 81 | Perm((3, 0, 1, 2, 4)), 82 | Perm((4, 2, 1, 0, 3, 5)), 83 | Perm((0, 2, 1, 4, 6, 3, 5)), 84 | Perm((6, 2, 1, 0, 5, 4, 3)), 85 | ] 86 | ) 87 | assert not is_insertion_encodable( 88 | [ 89 | Perm((0, 2, 1, 3)), 90 | Perm((1, 2, 3, 0, 4)), 91 | Perm((3, 1, 0, 2, 4)), 92 | Perm((3, 1, 4, 2, 0)), 93 | ] 94 | ) 95 | -------------------------------------------------------------------------------- /tests/permutils/test_groups.py: -------------------------------------------------------------------------------- 1 | from permuta import Perm 2 | from permuta.permutils.groups import dihedral_group 3 | 4 | 5 | def test_dihedral_group(): 6 | first_11 = { 7 | 0: set(), 8 | 1: set(), 9 | 2: set(), 10 | 3: { 11 | Perm((1, 0, 2)), 12 | Perm((2, 0, 1)), 13 | Perm((0, 1, 2)), 14 | Perm((2, 1, 0)), 15 | Perm((1, 2, 0)), 16 | Perm((0, 2, 1)), 17 | }, 18 | 4: { 19 | Perm((3, 2, 1, 0)), 20 | Perm((0, 3, 2, 1)), 21 | Perm((3, 0, 1, 2)), 22 | Perm((2, 1, 0, 3)), 23 | Perm((2, 3, 0, 1)), 24 | Perm((1, 2, 3, 0)), 25 | Perm((1, 0, 3, 2)), 26 | Perm((0, 1, 2, 3)), 27 | }, 28 | 5: { 29 | Perm((0, 1, 2, 3, 4)), 30 | Perm((3, 2, 1, 0, 4)), 31 | Perm((4, 0, 1, 2, 3)), 32 | Perm((0, 4, 3, 2, 1)), 33 | Perm((4, 3, 2, 1, 0)), 34 | Perm((1, 0, 4, 3, 2)), 35 | Perm((3, 4, 0, 1, 2)), 36 | Perm((2, 3, 4, 0, 1)), 37 | Perm((1, 2, 3, 4, 0)), 38 | Perm((2, 1, 0, 4, 3)), 39 | }, 40 | 6: { 41 | Perm((3, 4, 5, 0, 1, 2)), 42 | Perm((4, 3, 2, 1, 0, 5)), 43 | Perm((2, 1, 0, 5, 4, 3)), 44 | Perm((0, 1, 2, 3, 4, 5)), 45 | Perm((2, 3, 4, 5, 0, 1)), 46 | Perm((3, 2, 1, 0, 5, 4)), 47 | Perm((1, 0, 5, 4, 3, 2)), 48 | Perm((4, 5, 0, 1, 2, 3)), 49 | Perm((5, 4, 3, 2, 1, 0)), 50 | Perm((5, 0, 1, 2, 3, 4)), 51 | Perm((0, 5, 4, 3, 2, 1)), 52 | Perm((1, 2, 3, 4, 5, 0)), 53 | }, 54 | 7: { 55 | Perm((2, 3, 4, 5, 6, 0, 1)), 56 | Perm((5, 6, 0, 1, 2, 3, 4)), 57 | Perm((0, 1, 2, 3, 4, 5, 6)), 58 | Perm((4, 3, 2, 1, 0, 6, 5)), 59 | Perm((1, 0, 6, 5, 4, 3, 2)), 60 | Perm((0, 6, 5, 4, 3, 2, 1)), 61 | Perm((3, 2, 1, 0, 6, 5, 4)), 62 | Perm((6, 5, 4, 3, 2, 1, 0)), 63 | Perm((4, 5, 6, 0, 1, 2, 3)), 64 | Perm((2, 1, 0, 6, 5, 4, 3)), 65 | Perm((1, 2, 3, 4, 5, 6, 0)), 66 | Perm((6, 0, 1, 2, 3, 4, 5)), 67 | Perm((3, 4, 5, 6, 0, 1, 2)), 68 | Perm((5, 4, 3, 2, 1, 0, 6)), 69 | }, 70 | 8: { 71 | Perm((1, 0, 7, 6, 5, 4, 3, 2)), 72 | Perm((7, 0, 1, 2, 3, 4, 5, 6)), 73 | Perm((4, 5, 6, 7, 0, 1, 2, 3)), 74 | Perm((7, 6, 5, 4, 3, 2, 1, 0)), 75 | Perm((2, 1, 0, 7, 6, 5, 4, 3)), 76 | Perm((6, 5, 4, 3, 2, 1, 0, 7)), 77 | Perm((1, 2, 3, 4, 5, 6, 7, 0)), 78 | Perm((0, 7, 6, 5, 4, 3, 2, 1)), 79 | Perm((2, 3, 4, 5, 6, 7, 0, 1)), 80 | Perm((0, 1, 2, 3, 4, 5, 6, 7)), 81 | Perm((3, 2, 1, 0, 7, 6, 5, 4)), 82 | Perm((6, 7, 0, 1, 2, 3, 4, 5)), 83 | Perm((5, 6, 7, 0, 1, 2, 3, 4)), 84 | Perm((5, 4, 3, 2, 1, 0, 7, 6)), 85 | Perm((3, 4, 5, 6, 7, 0, 1, 2)), 86 | Perm((4, 3, 2, 1, 0, 7, 6, 5)), 87 | }, 88 | 9: { 89 | Perm((3, 4, 5, 6, 7, 8, 0, 1, 2)), 90 | Perm((7, 8, 0, 1, 2, 3, 4, 5, 6)), 91 | Perm((4, 5, 6, 7, 8, 0, 1, 2, 3)), 92 | Perm((0, 1, 2, 3, 4, 5, 6, 7, 8)), 93 | Perm((5, 4, 3, 2, 1, 0, 8, 7, 6)), 94 | Perm((2, 3, 4, 5, 6, 7, 8, 0, 1)), 95 | Perm((8, 7, 6, 5, 4, 3, 2, 1, 0)), 96 | Perm((0, 8, 7, 6, 5, 4, 3, 2, 1)), 97 | Perm((2, 1, 0, 8, 7, 6, 5, 4, 3)), 98 | Perm((3, 2, 1, 0, 8, 7, 6, 5, 4)), 99 | Perm((4, 3, 2, 1, 0, 8, 7, 6, 5)), 100 | Perm((5, 6, 7, 8, 0, 1, 2, 3, 4)), 101 | Perm((6, 5, 4, 3, 2, 1, 0, 8, 7)), 102 | Perm((7, 6, 5, 4, 3, 2, 1, 0, 8)), 103 | Perm((6, 7, 8, 0, 1, 2, 3, 4, 5)), 104 | Perm((8, 0, 1, 2, 3, 4, 5, 6, 7)), 105 | Perm((1, 0, 8, 7, 6, 5, 4, 3, 2)), 106 | Perm((1, 2, 3, 4, 5, 6, 7, 8, 0)), 107 | }, 108 | 10: { 109 | Perm((3, 2, 1, 0, 9, 8, 7, 6, 5, 4)), 110 | Perm((6, 5, 4, 3, 2, 1, 0, 9, 8, 7)), 111 | Perm((4, 5, 6, 7, 8, 9, 0, 1, 2, 3)), 112 | Perm((6, 7, 8, 9, 0, 1, 2, 3, 4, 5)), 113 | Perm((0, 1, 2, 3, 4, 5, 6, 7, 8, 9)), 114 | Perm((5, 6, 7, 8, 9, 0, 1, 2, 3, 4)), 115 | Perm((3, 4, 5, 6, 7, 8, 9, 0, 1, 2)), 116 | Perm((5, 4, 3, 2, 1, 0, 9, 8, 7, 6)), 117 | Perm((7, 8, 9, 0, 1, 2, 3, 4, 5, 6)), 118 | Perm((9, 8, 7, 6, 5, 4, 3, 2, 1, 0)), 119 | Perm((9, 0, 1, 2, 3, 4, 5, 6, 7, 8)), 120 | Perm((2, 3, 4, 5, 6, 7, 8, 9, 0, 1)), 121 | Perm((0, 9, 8, 7, 6, 5, 4, 3, 2, 1)), 122 | Perm((2, 1, 0, 9, 8, 7, 6, 5, 4, 3)), 123 | Perm((8, 7, 6, 5, 4, 3, 2, 1, 0, 9)), 124 | Perm((1, 0, 9, 8, 7, 6, 5, 4, 3, 2)), 125 | Perm((1, 2, 3, 4, 5, 6, 7, 8, 9, 0)), 126 | Perm((8, 9, 0, 1, 2, 3, 4, 5, 6, 7)), 127 | Perm((4, 3, 2, 1, 0, 9, 8, 7, 6, 5)), 128 | Perm((7, 6, 5, 4, 3, 2, 1, 0, 9, 8)), 129 | }, 130 | } 131 | for i in range(11): 132 | assert set(dihedral_group(i)) == first_11[i] 133 | -------------------------------------------------------------------------------- /tests/permutils/test_stats.py: -------------------------------------------------------------------------------- 1 | from permuta import Av 2 | from permuta.permutils.statistics import PermutationStatistic 3 | 4 | 5 | def test_distribution_all_perms(): 6 | assert sum(PermutationStatistic.inv().distribution_up_to(7), []) == [ 7 | 1, 8 | 1, 9 | 1, 10 | 1, 11 | 1, 12 | 2, 13 | 2, 14 | 1, 15 | 1, 16 | 3, 17 | 5, 18 | 6, 19 | 5, 20 | 3, 21 | 1, 22 | 1, 23 | 4, 24 | 9, 25 | 15, 26 | 20, 27 | 22, 28 | 20, 29 | 15, 30 | 9, 31 | 4, 32 | 1, 33 | 1, 34 | 5, 35 | 14, 36 | 29, 37 | 49, 38 | 71, 39 | 90, 40 | 101, 41 | 101, 42 | 90, 43 | 71, 44 | 49, 45 | 29, 46 | 14, 47 | 5, 48 | 1, 49 | 1, 50 | 6, 51 | 20, 52 | 49, 53 | 98, 54 | 169, 55 | 259, 56 | 359, 57 | 455, 58 | 531, 59 | 573, 60 | 573, 61 | 531, 62 | 455, 63 | 359, 64 | 259, 65 | 169, 66 | 98, 67 | 49, 68 | 20, 69 | 6, 70 | 1, 71 | ] 72 | assert PermutationStatistic.maj().distribution_for_length(8) == [ 73 | 1, 74 | 7, 75 | 27, 76 | 76, 77 | 174, 78 | 343, 79 | 602, 80 | 961, 81 | 1415, 82 | 1940, 83 | 2493, 84 | 3017, 85 | 3450, 86 | 3736, 87 | 3836, 88 | 3736, 89 | 3450, 90 | 3017, 91 | 2493, 92 | 1940, 93 | 1415, 94 | 961, 95 | 602, 96 | 343, 97 | 174, 98 | 76, 99 | 27, 100 | 7, 101 | 1, 102 | ] 103 | 104 | 105 | def test_distribution_av(): 106 | assert sum( 107 | PermutationStatistic.des().distribution_up_to(11, Av.from_string("123")), [] 108 | ) == [ 109 | 1, 110 | 1, 111 | 1, 112 | 1, 113 | 0, 114 | 4, 115 | 1, 116 | 0, 117 | 2, 118 | 11, 119 | 1, 120 | 0, 121 | 0, 122 | 15, 123 | 26, 124 | 1, 125 | 0, 126 | 0, 127 | 5, 128 | 69, 129 | 57, 130 | 1, 131 | 0, 132 | 0, 133 | 0, 134 | 56, 135 | 252, 136 | 120, 137 | 1, 138 | 0, 139 | 0, 140 | 0, 141 | 14, 142 | 364, 143 | 804, 144 | 247, 145 | 1, 146 | 0, 147 | 0, 148 | 0, 149 | 0, 150 | 210, 151 | 1800, 152 | 2349, 153 | 502, 154 | 1, 155 | 0, 156 | 0, 157 | 0, 158 | 0, 159 | 42, 160 | 1770, 161 | 7515, 162 | 6455, 163 | 1013, 164 | 1, 165 | 0, 166 | 0, 167 | 0, 168 | 0, 169 | 0, 170 | 792, 171 | 11055, 172 | 27940, 173 | 16962, 174 | 2036, 175 | 1, 176 | ] 177 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # tox (https://tox.readthedocs.io/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = 8 | flake8, mypy, pylint, black 9 | py{310,311,312,313}, 10 | pypy{310,311} 11 | 12 | [default] 13 | basepython=python3.13 14 | 15 | [testenv] 16 | description = run test 17 | basepython = 18 | py310: python3.10 19 | py311: python3.11 20 | py312: python3.12 21 | py313: python3.13 22 | pypy310: pypy3.10 23 | pypy311: pypy3.11 24 | deps = 25 | pytest 26 | pytest-timeout 27 | commands = pytest 28 | 29 | [pytest] 30 | addopts = --doctest-modules --doctest-ignore-import-errors 31 | testpaths = tests permuta README.rst 32 | markers = slow: marks tests as slow (deselect with '-m "not slow"') 33 | 34 | [testenv:flake8] 35 | description = run flake8 (linter) 36 | basepython = {[default]basepython} 37 | skip_install = True 38 | deps = 39 | flake8 40 | flake8-isort 41 | commands = 42 | flake8 --isort-show-traceback permuta tests setup.py 43 | 44 | [testenv:pylint] 45 | description = run pylint (static code analysis) 46 | basepython = {[default]basepython} 47 | deps = 48 | pylint 49 | commands = pylint permuta 50 | 51 | [testenv:mypy] 52 | description = run mypy (static type checker) 53 | basepython = {[default]basepython} 54 | deps = 55 | mypy 56 | commands = mypy 57 | 58 | [testenv:black] 59 | description = check that comply with autoformating 60 | basepython = {[default]basepython} 61 | deps = 62 | black 63 | commands = black --check --diff . 64 | --------------------------------------------------------------------------------