From 0de5a3e87a4a3b629a76785f7c9aafa3e4ab00ea Mon Sep 17 00:00:00 2001 From: Christian Bean Date: Mon, 20 Jul 2020 16:13:30 +0200 Subject: [PATCH] version 2.0.0 (#133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add insenc and lexmin options to permtools (#116) * Typing for permuta/perm.py (#118) * typing, refactoring and naming perm.py * minor updates * add mypy to test suite * rem typechecking, prune comments, refactor perm.py * mypy fix permutils * mypy fix enum-strat * mypy fix _perm_set * mypy sympy * permset mypy fix * interfaces mypy fix * flake8 fix in interfaces * potential 36 fix * 3.6 issues solved * resolve issues * fix issues 2.0 Co-authored-by: Émile Nadeau * Refactor (#120) * add sympy as dependency * more perm.py refactor + some tests * tests + refactor + reimplementing block method * more permpy tests and refactor, almost done * sympy top * . * removed Rotatable, Shiftable, Flippable abc * meshpatt refactor * remove comment * fix newl in docstr * doctest fixes * more fixes * fix isort issue * forgot -> type of inf iterator * fix issues * tmp solutions to sympy removal * Bivincular (#122) * rem cov badge * dihedral group * union find tests * misc cleared * tests updated * pre-move push * restructure * testing for bivpatt * added missing types * doctest fix * more_doctest_fixes * fix flake issues * Refactor permutils * permutils finite+sym refactor * poly refactor * poly deque for performance * utils refactoring done * issues and p_utils to putils * [requires.io] dependency update (#121) Co-authored-by: requires.io * SVG for patterns + HTML rendering (#125) * perm2svg * mesh svg * svg open in tab * openhtml cleanup * removal of file * Bisc perm properties refactor + tests (#127) * include bisc jsons in pypi package * .show in patts * bisc perm prop refactoring * forgot pylint in travis * missing comma in pylintrc * remove win from deployment * enumeration_strategies typing + removing permuta/descriptors (#129) * typing for enum_strat * removed readme.d + docs * forgot a few types in bisc-perm-prop * basis to permsets * basis * mp gen moved to mp * remove unused classvar * bivpatt random made uniform * doc fix * forgot len in rand * flake issue * and more fixes... * [requires.io] dependency update (#128) Co-authored-by: requires.io * permsets simplified (#130) * cache clear function in perm * minor cleanup in enumstrat * remove .vscode * .vscode to gitignore * basis refactor * perm_set simplified * comments + enum test * readme + comments + tests * cache fix + cache test * cache tests * fix assertion in Av * reduce memory of cache * reduce loop of auto cache clearing * issue fixing * identical types fix * cli typing (#132) * cli typing * typos + avoid iteration in cnt * poly to cli * , -> _ * version 2.0.0 * PEP 561 compatible Co-authored-by: Jón Steinn Elíasson Co-authored-by: Émile Nadeau Co-authored-by: requires.io --- .gitignore | 3 + .pylintrc | 16 + .travis.yml | 17 + CHANGELOG.md | 35 + MANIFEST.in | 1 + README.d/1324.png | Bin 1826 -> 0 bytes README.d/1324.svg | 234 -- README.d/av_213_231_of_length_14_heatmap.png | Bin 2451 -> 0 bytes README.rst | 96 +- docs/Makefile | 192 -- docs/make.bat | 263 -- docs/source/conf.py | 298 --- docs/source/index.rst | 28 - docs/source/install.rst | 21 - docs/source/license.rst | 7 - docs/source/modules.rst | 7 - docs/source/permuta.math.rst | 13 - docs/source/permuta.misc.rst | 61 - docs/source/permuta.rst | 42 - mypy.ini | 5 + permuta/__init__.py | 16 +- permuta/_perm_set/__init__.py | 4 - permuta/_perm_set/finite/__init__.py | 5 - permuta/_perm_set/finite/permset_finite.py | 13 - .../finite/permset_finite_specificlength.py | 7 - permuta/_perm_set/finite/permset_static.py | 49 - permuta/_perm_set/permset_base.py | 33 - permuta/_perm_set/unbounded/__init__.py | 4 - permuta/_perm_set/unbounded/all/__init__.py | 3 - .../_perm_set/unbounded/all/permset_all.py | 121 - .../_perm_set/unbounded/described/__init__.py | 4 - .../unbounded/described/avoiding/__init__.py | 9 - .../unbounded/described/avoiding/avoiding.py | 203 -- .../avoiding/avoiding_length_0/__init__.py | 3 - .../avoiding_length_0/avoiding_empty.py | 31 - .../unbounded/described/permset_described.py | 17 - .../_perm_set/unbounded/permset_unbounded.py | 7 - permuta/bisc/bisc.py | 85 +- permuta/bisc/bisc_subfunctions.py | 9 +- permuta/bisc/perm_properties.py | 254 ++ permuta/bisc/permsets/__init__.py | 35 - permuta/bisc/permsets/perm_properties.py | 298 --- permuta/cli.py | 125 +- permuta/descriptors/__init__.py | 5 - permuta/descriptors/basis.py | 141 - permuta/descriptors/descriptor.py | 14 - permuta/descriptors/predicate.py | 28 - permuta/enumeration_strategies/__init__.py | 31 +- .../abstract_strategy.py | 56 +- .../enumeration_strategies/core_strategies.py | 234 +- .../insertion_encodable.py | 21 +- permuta/interfaces/__init__.py | 6 - permuta/interfaces/flippable.py | 25 - permuta/interfaces/patt.py | 52 - permuta/interfaces/rotatable.py | 41 - permuta/interfaces/shiftable.py | 37 - permuta/meshpatt.py | 1104 -------- permuta/meshpattset.py | 40 - permuta/misc/__init__.py | 35 +- permuta/misc/algorithm_x.py | 147 -- permuta/misc/checking.py | 46 - permuta/misc/counting.py | 20 - permuta/misc/dancing_links.py | 72 - permuta/misc/display.py | 37 + permuta/misc/exact_cover.py | 75 - permuta/misc/iterable_floor_and_ceiling.py | 66 - permuta/misc/ordered_set_partitions.py | 107 - permuta/misc/progressbar.py | 76 - permuta/misc/ranges.py | 11 - permuta/misc/triemap.py | 52 - permuta/misc/union_find.py | 52 +- permuta/patterns/__init__.py | 13 + permuta/patterns/bivincularpatt.py | 155 ++ permuta/patterns/meshpatt.py | 862 ++++++ permuta/patterns/patt.py | 40 + permuta/patterns/perm.py | 2160 +++++++++++++++ permuta/perm.py | 2333 ----------------- permuta/perm_sets/__init__.py | 4 + permuta/perm_sets/basis.py | 98 + permuta/perm_sets/permset.py | 192 ++ permuta/permset.py | 103 - permuta/permutils/__init__.py | 41 +- permuta/permutils/finite.py | 16 +- permuta/permutils/groups.py | 24 + permuta/permutils/insertion_encodable.py | 184 +- permuta/permutils/polynomial.py | 206 +- permuta/permutils/symmetry.py | 155 +- .../_perm_set/__init__.py => permuta/py.typed | 0 .../bisc/Baxter_bad_len8.json} | 0 .../bisc/Baxter_good_len8.json} | 0 .../bisc/SimSun_bad_len8.json} | 0 .../bisc/SimSun_bad_len9.json} | 0 .../bisc/SimSun_good_len8.json} | 0 .../bisc/SimSun_good_len9.json} | 0 .../bisc/West_2_stack_sortable_bad_len8.json} | 0 .../West_2_stack_sortable_good_len8.json} | 0 .../bisc/av_231_and_mesh_bad_len8.json} | 0 .../bisc/av_231_and_mesh_bad_len9.json} | 0 .../bisc/av_231_and_mesh_good_len8.json} | 0 .../bisc/av_231_and_mesh_good_len9.json} | 0 .../bisc/dihedral_bad_len8.json} | 0 .../bisc/dihedral_good_len8.json} | 0 .../bisc/forest_like_bad_len8.json} | 0 .../bisc/forest_like_good_len8.json} | 0 .../bisc/in_alternating_group_bad_len8.json} | 0 .../bisc/in_alternating_group_good_len8.json} | 0 .../bisc/quick_sortable_bad_len8.json} | 0 .../bisc/quick_sortable_good_len8.json} | 0 .../bisc/smooth_bad_len8.json} | 0 .../bisc/smooth_good_len8.json} | 0 .../bisc/stack_sortable_bad_len8.json} | 0 .../bisc/stack_sortable_good_len8.json} | 0 .../bisc/yt_perm_avoids_22_bad_len8.json} | 0 .../bisc/yt_perm_avoids_22_good_len8.json} | 0 .../bisc/yt_perm_avoids_32_bad_len8.json} | 0 .../bisc/yt_perm_avoids_32_good_len8.json} | 0 setup.py | 4 +- .../unbounded/all/test_perm_set_all.py | 7 - .../avoiding/test_avoiding_generic.py | 148 -- tests/bisc/test_bisc.py | 4 +- tests/bisc/test_perm_properties.py | 625 +++++ tests/descriptors/test_basis.py | 80 - .../test_enumeration_strategies.py | 6 +- tests/misc/test_catalan.py | 40 - tests/misc/test_counting.py | 20 - tests/misc/test_iterable_floor_and_ceiling.py | 56 - tests/misc/test_ordered_set_partitions.py | 76 - tests/misc/test_ranges.py | 19 - tests/misc/test_union_find.py | 61 + tests/patterns/test_bivincular.py | 1821 +++++++++++++ tests/patterns/test_meshpatt.py | 1589 +++++++++++ tests/{ => patterns}/test_perm.py | 1124 ++++++-- tests/{descriptors => perm_sets}/__init__.py | 0 tests/perm_sets/test_av.py | 292 +++ tests/perm_sets/test_basis.py | 208 ++ tests/permutils/test_finite.py | 37 + tests/permutils/test_function_imports.py | 94 + tests/permutils/test_groups.py | 132 + tests/permutils/test_insertion_encodable.py | 1033 ++++++++ tests/permutils/test_polynomial.py | 1343 ++++++++++ tests/permutils/test_symmetry.py | 147 +- tests/test_av.py | 27 - tests/test_meshpatt.py | 754 ------ tox.ini | 18 +- 144 files changed, 12769 insertions(+), 8887 deletions(-) create mode 100644 .pylintrc create mode 100644 MANIFEST.in delete mode 100644 README.d/1324.png delete mode 100644 README.d/1324.svg delete mode 100644 README.d/av_213_231_of_length_14_heatmap.png delete mode 100644 docs/Makefile delete mode 100644 docs/make.bat delete mode 100644 docs/source/conf.py delete mode 100644 docs/source/index.rst delete mode 100644 docs/source/install.rst delete mode 100644 docs/source/license.rst delete mode 100644 docs/source/modules.rst delete mode 100644 docs/source/permuta.math.rst delete mode 100644 docs/source/permuta.misc.rst delete mode 100644 docs/source/permuta.rst create mode 100644 mypy.ini delete mode 100644 permuta/_perm_set/__init__.py delete mode 100644 permuta/_perm_set/finite/__init__.py delete mode 100644 permuta/_perm_set/finite/permset_finite.py delete mode 100644 permuta/_perm_set/finite/permset_finite_specificlength.py delete mode 100644 permuta/_perm_set/finite/permset_static.py delete mode 100644 permuta/_perm_set/permset_base.py delete mode 100644 permuta/_perm_set/unbounded/__init__.py delete mode 100644 permuta/_perm_set/unbounded/all/__init__.py delete mode 100644 permuta/_perm_set/unbounded/all/permset_all.py delete mode 100644 permuta/_perm_set/unbounded/described/__init__.py delete mode 100644 permuta/_perm_set/unbounded/described/avoiding/__init__.py delete mode 100644 permuta/_perm_set/unbounded/described/avoiding/avoiding.py delete mode 100644 permuta/_perm_set/unbounded/described/avoiding/avoiding_length_0/__init__.py delete mode 100644 permuta/_perm_set/unbounded/described/avoiding/avoiding_length_0/avoiding_empty.py delete mode 100644 permuta/_perm_set/unbounded/described/permset_described.py delete mode 100644 permuta/_perm_set/unbounded/permset_unbounded.py create mode 100644 permuta/bisc/perm_properties.py delete mode 100644 permuta/bisc/permsets/__init__.py delete mode 100644 permuta/bisc/permsets/perm_properties.py delete mode 100644 permuta/descriptors/__init__.py delete mode 100644 permuta/descriptors/basis.py delete mode 100644 permuta/descriptors/descriptor.py delete mode 100644 permuta/descriptors/predicate.py delete mode 100644 permuta/interfaces/__init__.py delete mode 100644 permuta/interfaces/flippable.py delete mode 100644 permuta/interfaces/patt.py delete mode 100644 permuta/interfaces/rotatable.py delete mode 100644 permuta/interfaces/shiftable.py delete mode 100644 permuta/meshpatt.py delete mode 100644 permuta/meshpattset.py delete mode 100644 permuta/misc/algorithm_x.py delete mode 100644 permuta/misc/checking.py delete mode 100644 permuta/misc/counting.py delete mode 100644 permuta/misc/dancing_links.py create mode 100644 permuta/misc/display.py delete mode 100644 permuta/misc/exact_cover.py delete mode 100644 permuta/misc/iterable_floor_and_ceiling.py delete mode 100644 permuta/misc/ordered_set_partitions.py delete mode 100644 permuta/misc/progressbar.py delete mode 100644 permuta/misc/ranges.py delete mode 100644 permuta/misc/triemap.py create mode 100644 permuta/patterns/__init__.py create mode 100644 permuta/patterns/bivincularpatt.py create mode 100644 permuta/patterns/meshpatt.py create mode 100644 permuta/patterns/patt.py create mode 100644 permuta/patterns/perm.py delete mode 100644 permuta/perm.py create mode 100644 permuta/perm_sets/__init__.py create mode 100644 permuta/perm_sets/basis.py create mode 100644 permuta/perm_sets/permset.py delete mode 100644 permuta/permset.py create mode 100644 permuta/permutils/groups.py rename tests/_perm_set/__init__.py => permuta/py.typed (100%) rename permuta/{bisc/permsets/Baxter_bad_len8 => resources/bisc/Baxter_bad_len8.json} (100%) rename permuta/{bisc/permsets/Baxter_good_len8 => resources/bisc/Baxter_good_len8.json} (100%) rename permuta/{bisc/permsets/SimSun_bad_len8 => resources/bisc/SimSun_bad_len8.json} (100%) rename permuta/{bisc/permsets/SimSun_bad_len9 => resources/bisc/SimSun_bad_len9.json} (100%) rename permuta/{bisc/permsets/SimSun_good_len8 => resources/bisc/SimSun_good_len8.json} (100%) rename permuta/{bisc/permsets/SimSun_good_len9 => resources/bisc/SimSun_good_len9.json} (100%) rename permuta/{bisc/permsets/West_2_stack_sortable_bad_len8 => resources/bisc/West_2_stack_sortable_bad_len8.json} (100%) rename permuta/{bisc/permsets/West_2_stack_sortable_good_len8 => resources/bisc/West_2_stack_sortable_good_len8.json} (100%) rename permuta/{bisc/permsets/av_231_and_mesh_bad_len8 => resources/bisc/av_231_and_mesh_bad_len8.json} (100%) rename permuta/{bisc/permsets/av_231_and_mesh_bad_len9 => resources/bisc/av_231_and_mesh_bad_len9.json} (100%) rename permuta/{bisc/permsets/av_231_and_mesh_good_len8 => resources/bisc/av_231_and_mesh_good_len8.json} (100%) rename permuta/{bisc/permsets/av_231_and_mesh_good_len9 => resources/bisc/av_231_and_mesh_good_len9.json} (100%) rename permuta/{bisc/permsets/dihedral_bad_len8 => resources/bisc/dihedral_bad_len8.json} (100%) rename permuta/{bisc/permsets/dihedral_good_len8 => resources/bisc/dihedral_good_len8.json} (100%) rename permuta/{bisc/permsets/forest_like_bad_len8 => resources/bisc/forest_like_bad_len8.json} (100%) rename permuta/{bisc/permsets/forest_like_good_len8 => resources/bisc/forest_like_good_len8.json} (100%) rename permuta/{bisc/permsets/in_alternating_group_bad_len8 => resources/bisc/in_alternating_group_bad_len8.json} (100%) rename permuta/{bisc/permsets/in_alternating_group_good_len8 => resources/bisc/in_alternating_group_good_len8.json} (100%) rename permuta/{bisc/permsets/quick_sortable_bad_len8 => resources/bisc/quick_sortable_bad_len8.json} (100%) rename permuta/{bisc/permsets/quick_sortable_good_len8 => resources/bisc/quick_sortable_good_len8.json} (100%) rename permuta/{bisc/permsets/smooth_bad_len8 => resources/bisc/smooth_bad_len8.json} (100%) rename permuta/{bisc/permsets/smooth_good_len8 => resources/bisc/smooth_good_len8.json} (100%) rename permuta/{bisc/permsets/stack_sortable_bad_len8 => resources/bisc/stack_sortable_bad_len8.json} (100%) rename permuta/{bisc/permsets/stack_sortable_good_len8 => resources/bisc/stack_sortable_good_len8.json} (100%) rename permuta/{bisc/permsets/yt_perm_avoids_22_bad_len8 => resources/bisc/yt_perm_avoids_22_bad_len8.json} (100%) rename permuta/{bisc/permsets/yt_perm_avoids_22_good_len8 => resources/bisc/yt_perm_avoids_22_good_len8.json} (100%) rename permuta/{bisc/permsets/yt_perm_avoids_32_bad_len8 => resources/bisc/yt_perm_avoids_32_bad_len8.json} (100%) rename permuta/{bisc/permsets/yt_perm_avoids_32_good_len8 => resources/bisc/yt_perm_avoids_32_good_len8.json} (100%) delete mode 100644 tests/_perm_set/unbounded/all/test_perm_set_all.py delete mode 100644 tests/_perm_set/unbounded/described/avoiding/test_avoiding_generic.py create mode 100644 tests/bisc/test_perm_properties.py delete mode 100644 tests/descriptors/test_basis.py rename tests/{ => enumeration_strategies}/test_enumeration_strategies.py (91%) delete mode 100644 tests/misc/test_catalan.py delete mode 100644 tests/misc/test_counting.py delete mode 100644 tests/misc/test_iterable_floor_and_ceiling.py delete mode 100644 tests/misc/test_ordered_set_partitions.py delete mode 100644 tests/misc/test_ranges.py create mode 100644 tests/misc/test_union_find.py create mode 100644 tests/patterns/test_bivincular.py create mode 100644 tests/patterns/test_meshpatt.py rename tests/{ => patterns}/test_perm.py (56%) rename tests/{descriptors => perm_sets}/__init__.py (100%) create mode 100644 tests/perm_sets/test_av.py create mode 100644 tests/perm_sets/test_basis.py create mode 100644 tests/permutils/test_finite.py create mode 100644 tests/permutils/test_function_imports.py create mode 100644 tests/permutils/test_groups.py create mode 100644 tests/permutils/test_insertion_encodable.py create mode 100644 tests/permutils/test_polynomial.py delete mode 100644 tests/test_av.py delete mode 100644 tests/test_meshpatt.py diff --git a/.gitignore b/.gitignore index 472c4c47..7c26472a 100644 --- a/.gitignore +++ b/.gitignore @@ -101,3 +101,6 @@ ENV/ # Pytest cache .pytest_cache/ + +# vscode settings +.vscode/ \ No newline at end of file diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..e75aca78 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,16 @@ +# This is a temporary .pylintrc file during refactoring. + +[MASTER] +ignore-patterns=test_.*?py, + # TODO: REMOVE + bisc.py, + bisc_subfunctions.py +ignore= tests +init-hook="from pylint.config import find_pylintrc; import os, sys; sys.path.append(os.path.dirname(find_pylintrc())+'/permuta')" +disable=bad-continuation, + missing-module-docstring, + fixme +good-names=i,j,n,k,x,y,_ + +[SIMILARITIES] +ignore-imports=yes diff --git a/.travis.yml b/.travis.yml index 7379f162..e312f3e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,12 @@ matrix: include: - python: 3.8 env: TOXENV=flake8 + - python: 3.8 + env: TOXENV=mypy - python: 3.8 env: TOXENV=black + - python: 3.8 + env: TOXENV=pylint - python: 3.6 env: TOXENV=py36 @@ -18,6 +22,18 @@ matrix: - python: pypy3 env: TOXENV=pypy36 + - name: "py38-win10" + os: windows + language: shell + before_install: + - choco install python --version 3.8.0 + - python -m pip install --upgrade pip + env: PATH=/c/Python38:/c/Python38/Scripts:$PATH + install: + - pip install pytest + script: + - python -m pytest tests + - python setup.py install install: - pip install tox @@ -33,3 +49,4 @@ deploy: skip_existing: true on: branch: master + condition: $TRAVIS_OS_NAME = linux diff --git a/CHANGELOG.md b/CHANGELOG.md index 21c1a595..a07b6006 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,41 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased +## 2.0.0 - 2020-07-20 +### Added + - Two new tools added to permtools. A command to check if a class has a regular + insertion encoding, and a command to compute the lexicographically minimal + basis. + - Typing + - pylint + - `clear_cache` method in `Perm` and `Av` + - `up_to_length`, `of_length`, `first` iterators in Perm and Av + - `to_svg` for all patterns + - `show` method for all patterns (opens browser tab) + - Functions returning list (or other data structures) made into generators when possible + - `BivincularPatt`, `VincularPatt`, `CovincularPatt` patterns, + - `dihedral_group` generator added to `permutils` + - `from_string` method to `Basis` and `Av`. It accepts both 0 and 1 based perms + seperated by anything + - Check if polynomial added to `cli`, which can be used with the `poly` command + +### Fixed + - Bisc's resource files now included with pypi package + +### Changed + - Type and condition checking and Exception throwing changed to assertions + - `Basis` moved to `permset` module + - `gen_meshpatt` moved to meshpatt as `of_length` generator + - Client now uses `Basis.from_string` to parse basis + +### Removed + - Permsets and their interfaces + - Unused algorithms and utils + - Symmetric interfaces + - All rotate function other than `rotate` + - `descriptors` module + - sympy dependency + ## 1.5.0 - 2020-06-23 ## Added - A quick command line interface to compute the enumeration of a permutation class. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..9b96f4bc --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include permuta/resources/bisc/*.json \ No newline at end of file diff --git a/README.d/1324.png b/README.d/1324.png deleted file mode 100644 index cc8338cf2bd60cf72ba26279f076a001eef7e0fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1826 zcmeAS@N?(olHy`uVBq!ia0y~yVB7)19Be=l^HZNT11XkbC(jTLAgJL;=>YOM3p^r= z85sBzL6~uc{qjr(2DT%fE{-7;bKYKW%yc#sU_DrW@bC7Tbqb9fJs*sM4>BuGn|=2# zdqe!$GjBO%e(bbmV6bz1z~CWd!7z!1uR*1eS;2FZI|xGKWob1-dfKyZEPQX?0b}Yz z-u;uT><*I-Fiub@U{K+dVen)c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/README.d/av_213_231_of_length_14_heatmap.png b/README.d/av_213_231_of_length_14_heatmap.png deleted file mode 100644 index ecdcaa688fbf9399442bb0f0f732e76bcf5a2587..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2451 zcmdT`?@Lor7`~R62`cri($)v-TlHa?Kd8-0(IIKXveG#VN-Lc+H&HjYYZhfi76y$) z?$VbQq^^?PIj3GhY3LSKms)hat(dl!+>M-i(|PYYR}lOMy3m2cIq&=BJm)>n^StNw ziK6_(gzX6s1SLuea!*1~Og}f{<3J{3Vy+J?v8tmIX*?J$@fX@byt%I6tO|mX&vP^8 zx!8OeAeO^b(Z#^zOUN3F-6QXu-A{bHgQr%0l6r zpCj2@?(~OExI3k9+GW^ZhKHBJek)}(CJRK}3QW&v6xzxknxS3=GP#<^1L}vw%k#81 z35piSCbgzr+qg^e4Jz2033dMSFrZFxzDj2oW<5QD21KMQCD=c|eP0&cZl4bY%jEDUf8tzYf*e9lp<^v71Woa8D49 zvbqKskI*3xDi8=fXjSFcA*~J7!7zsE8XIlsVWG+{`-D#VN$prbT}pbV`3B5YrmMr3 zi6&pYt7lj2DC2Y9=a^tNn?EwgX_~Gkz-7t_eN)pJ!end?|9Jalk*l@In1v|mohk>C z(t3b*I_TnAn_Mn0p%Db}poq!z39VYhB6}_Jp;8iaI2_~P4z8Kk1K4;X&vfNeI|c!2Os2Ir-Ba+5opRZ z+YqX16orQI@_hy?^)|#{QMQbGWW8Qauzmzy1R@xh{^XP=@s1@xo7z`g7zaRKB&?*4Q zg!HZ#r!6IPC>X4*2mW1e?+z;K`Nqe!gkEA(|0yxd$_Vzi&&kyc$MIk6vq&UjrEE%X zuR?t2P&bgoU~{-q5z&x3w`tO*y>BOrlX4#OYS^S`=a+!dhJ_KBvjShgO}zmR%=8sI|zDR z`1|NBE9J3NaAqj8c&t>oneGazv&rd$HFbRRbXoi$>&d+JN|Xe@DoB!7l-qmsT-#sI Cg?6F< diff --git a/README.rst b/README.rst index a34f2986..ae7d4422 100644 --- a/README.rst +++ b/README.rst @@ -5,9 +5,6 @@ permuta .. image:: https://travis-ci.org/PermutaTriangle/Permuta.svg?branch=master :alt: Travis :target: https://travis-ci.org/PermutaTriangle/Permuta -.. image:: https://coveralls.io/repos/github/PermutaTriangle/Permuta/badge.svg?branch=master - :alt: Coveralls - :target: https://coveralls.io/github/PermutaTriangle/Permuta?branch=master .. image:: https://img.shields.io/pypi/v/Permuta.svg :alt: PyPI :target: https://pypi.python.org/pypi/Permuta @@ -82,18 +79,6 @@ Permutations are zero-based in Permuta and can be created using any iterable. Perm((0, 1, 2, 3)) >>> Perm((2, 1, 3)) # Warning: it will initialise with any iterable Perm((2, 1, 3)) - >>> Perm((2, 1, 3), check=True) # If you are unsure, you can check - Traceback (most recent call last): - ... - ValueError: Element out of range: 3 - >>> Perm((4, 2, 3, 0, 0), check=True) - Traceback (most recent call last): - ... - ValueError: Duplicate element: 0 - >>> Perm("123", check=True) - Traceback (most recent call last): - ... - TypeError: ''1'' object is not an integer Permutations can also be created using some specific class methods. @@ -172,32 +157,16 @@ There are numerous practical methods available: Creating a perm class ##################### -You might want the set of all perms: +Perm classes are specified with a basis: .. code-block:: python - >>> all_perms = PermSet() - >>> print(all_perms) - - -Perm classes can be specified with a basis: - -.. code-block:: python - - >>> basis = [Perm((1, 0, 2)), Perm((1, 2, 0))] + >>> basis = Basis(Perm((1, 0, 2)), Perm((1, 2, 0))) >>> basis - [Perm((1, 0, 2)), Perm((1, 2, 0))] + Basis((Perm((1, 0, 2)), Perm((1, 2, 0)))) >>> perm_class = Av(basis) >>> perm_class - Av((Perm((1, 0, 2)), Perm((1, 2, 0)))) - -When a basis consists of a single element you can pass it directly to `Av`: - -.. code-block:: python - - >>> q = Perm((1,0)) - >>> len(Av(q).of_length(100)) - 1 + Av(Basis((Perm((1, 0, 2)), Perm((1, 2, 0))))) You can ask whether a perm belongs to the perm class: @@ -208,49 +177,14 @@ You can ask whether a perm belongs to the perm class: >>> Perm((0, 2, 1, 3)) in perm_class False -You can get the n-th perm of the class or iterate: - -.. code-block:: python - - >>> sorted([perm_class[n] for n in range(8)]) - [Perm(()), Perm((0,)), Perm((0, 1)), Perm((1, 0)), Perm((0, 1, 2)), Perm((0, 2, 1)), Perm((2, 0, 1)), Perm((2, 1, 0))] - >>> perm_class_iter = iter(perm_class) - >>> sorted([next(perm_class_iter) for _ in range(8)]) - [Perm(()), Perm((0,)), Perm((0, 1)), Perm((1, 0)), Perm((0, 1, 2)), Perm((0, 2, 1)), Perm((2, 0, 1)), Perm((2, 1, 0))] - -(BEWARE: Lexicographic order is not guaranteed at the moment!) - -The subset of a perm class where the perms are a specific length -################################################################ - -You can define a subset of perms of a specific length in the perm class: +You can get its enumeration up to a fixed length. .. code-block:: python - >>> perm_class_14 = perm_class.of_length(14) - >>> perm_class_14 - Av((Perm((1, 0, 2)), Perm((1, 2, 0)))).of_length(14) - -You can ask for the size of the subset because it is guaranteed to be finite: - -.. code-block:: python - - >>> len(perm_class_14) - 8192 - -The iterating and containment functionality is the same as with `perm_class`, -but indexing has yet to be implemented: - -.. code-block:: python - - >>> Perm((2, 1, 0)) in perm_class_14 - False - >>> Perm((0, 13, 1, 12, 2, 3, 4, 11, 5, 10, 6, 7, 8, 9)) in perm_class_14 - True - >>> Perm(range(10)) - Perm(range(4)) in perm_class_14 - False - >>> next(iter(perm_class_14)) in perm_class_14 - True + >>> perm_class.enumeration(10) + [1, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512] + >>> perm_class.count(11) + 1024 The BiSC algorithm ================== @@ -278,7 +212,7 @@ search for patterns of length 3. .. code-block:: python - >>> from permuta.bisc.permsets.perm_properties import stack_sortable + >>> from permuta.bisc.perm_properties import stack_sortable >>> bisc(stack_sortable, 3) I will use permutations up to length 7 {3: {Perm((1, 2, 0)): [set()]}} @@ -320,8 +254,8 @@ patterns, such as the West-2-stack-sortable permutations .. code-block:: python - >>> from permuta.bisc.permsets.perm_properties import West_2_stack_sortable - >>> SG = bisc(West_2_stack_sortable, 5, 7) + >>> from permuta.bisc.perm_properties import west_2_stack_sortable + >>> SG = bisc(west_2_stack_sortable, 5, 7) >>> show_me(SG) There are 2 underlying classical patterns of length 4 There are 1 different shadings on 1230 @@ -373,7 +307,7 @@ which keeps them separated by length. .. code-block:: python - >>> A, B = create_bisc_input(7, West_2_stack_sortable) + >>> A, B = create_bisc_input(7, west_2_stack_sortable) This creates two dictionaries with keys 1, 2, ..., 7 such that ``A[i]`` points to the list of permutations of length ``i`` that are West-2-stack-sortable, and @@ -445,8 +379,8 @@ There is one basis of mesh patterns found, with 2 patterns This is the output we were expecting. There are several other properties of -permutations that can be imported from ``permuta.bisc.permsets.perm_properties``, such -as ``smooth``, ``forest-like``, ``Baxter``, ``Simsun``, ``quick_sortable``, etc. +permutations that can be imported from ``permuta.bisc.perm_properties``, such +as ``smooth``, ``forest-like``, ``baxter``, ``simsun``, ``quick_sortable``, etc. Both ``bisc`` and ``auto_bisc`` can accept input in the form of a property, or a list of permutations (satisfying some property). diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 1ee3dfc3..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,192 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = build - -# User-friendly check for sphinx-build2 -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Permuta.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Permuta.qhc" - -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/Permuta" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Permuta" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index aa882712..00000000 --- a/docs/make.bat +++ /dev/null @@ -1,263 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build2 -) -set BUILDDIR=build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source -set I18NSPHINXOPTS=%SPHINXOPTS% source -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build2 is available and fallback to Python version if any -%SPHINXBUILD% 1>NUL 2>NUL -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build2' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build2' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Permuta.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Permuta.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index 80149883..00000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,298 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Permuta documentation build configuration file, created by -# sphinx-quickstart2 on Thu Dec 17 11:42:53 2015. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import os -import shlex -import sys - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) - -sys.path.insert(0, os.path.abspath("../..")) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.mathjax", - "sphinx.ext.coverage", - "sphinx.ext.napoleon", -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# source_suffix = ['.rst', '.md'] -source_suffix = ".rst" - -# The encoding of source files. -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = "index" - -# General information about the project. -project = "Permuta" -copyright = "2015, Úlfarsson" -author = "Úlfarsson" - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = "0.1" -# The full version, including alpha/beta/rc tags. -release = "0.1" - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = [] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - -# -- Autodoc options ----------------------------------------------------- - -autodoc_default_flags = ["members", "inherited-members"] - -# -- Options for HTML output ---------------------------------------------- - -# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org -on_rtd = os.environ.get("READTHEDOCS", None) == "True" - -if not on_rtd: # only import and set the theme if we're building docs locally - import sphinx_rtd_theme - - html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -# html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -# html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -# html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = "Permutadoc" - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - "papersize": "a4paper", - # The font size ('10pt', '11pt' or '12pt'). - "pointsize": "11pt", - # Additional stuff for the LaTeX preamble. - #'preamble': '', - # Latex figure (float) alignment - "figure_align": "h", -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, "Permuta.tex", "Permuta Documentation", "Úlfarsson", "manual"), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [(master_doc, "permuta", "Permuta Documentation", [author], 1)] - -# If true, show URL addresses after external links. -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ( - master_doc, - "Permuta", - "Permuta Documentation", - author, - "Permuta", - "One line description of project.", - "Miscellaneous", - ), -] - -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] - -# If false, no module index is generated. -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# texinfo_no_detailmenu = False diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index 953c31d8..00000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. Permuta documentation master file, created by - sphinx-quickstart2 on Thu Dec 17 11:42:53 2015. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Permuta's documentation! -=================================== -Permuta is a Python library for working with permutations and mesh patterns. - -.. image:: https://travis-ci.org/PermutaTriangle/Permuta.svg?branch=master - :target: https://travis-ci.org/PermutaTriangle/Permuta - -Table of contents: - -.. toctree:: - :maxdepth: 2 - - install - license - permuta - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/docs/source/install.rst b/docs/source/install.rst deleted file mode 100644 index d5c7045f..00000000 --- a/docs/source/install.rst +++ /dev/null @@ -1,21 +0,0 @@ -Installing -########## - -To install Permuta on your system, simply run the following command as -a superuser:: - - ./setup.py install - -It is also possible to install Permuta in development mode, in which case you -run the following instead:: - - ./setup.py develop - -To run the unit tests, you can run the following command:: - - ./setup.py test - - -Once you've installed Permuta, it can be imported into a Python script just like any other Python library:: - - from permuta import Permutation, MeshPattern diff --git a/docs/source/license.rst b/docs/source/license.rst deleted file mode 100644 index e2422b0a..00000000 --- a/docs/source/license.rst +++ /dev/null @@ -1,7 +0,0 @@ -License -======= - -BSD-3 - -.. include:: ../../LICENSE - diff --git a/docs/source/modules.rst b/docs/source/modules.rst deleted file mode 100644 index 0181f0aa..00000000 --- a/docs/source/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -permuta -======= - -.. toctree:: - :maxdepth: 4 - - permuta diff --git a/docs/source/permuta.math.rst b/docs/source/permuta.math.rst deleted file mode 100644 index 83bd78ad..00000000 --- a/docs/source/permuta.math.rst +++ /dev/null @@ -1,13 +0,0 @@ -permuta.math package -==================== - -Submodules ----------- - -permuta.math.counting module ----------------------------- - -.. automodule:: permuta.math.counting - :members: - :show-inheritance: - diff --git a/docs/source/permuta.misc.rst b/docs/source/permuta.misc.rst deleted file mode 100644 index c1111d24..00000000 --- a/docs/source/permuta.misc.rst +++ /dev/null @@ -1,61 +0,0 @@ -permuta.misc package -==================== - -Submodules ----------- - -permuta.misc.algorithm_x module -------------------------------- - -.. automodule:: permuta.misc.algorithm_x - :members: - :show-inheritance: - -permuta.misc.dancing_links module ---------------------------------- - -.. automodule:: permuta.misc.dancing_links - :members: - :show-inheritance: - -permuta.misc.exact_cover module -------------------------------- - -.. automodule:: permuta.misc.exact_cover - :members: - :show-inheritance: - -permuta.misc.misc module ------------------------- - -.. automodule:: permuta.misc.misc - :members: - :show-inheritance: - -permuta.misc.ordered_set_partitions module ------------------------------------------- - -.. automodule:: permuta.misc.ordered_set_partitions - :members: - :show-inheritance: - -permuta.misc.progressbar module -------------------------------- - -.. automodule:: permuta.misc.progressbar - :members: - :show-inheritance: - -permuta.misc.triemap module ---------------------------- - -.. automodule:: permuta.misc.triemap - :members: - :show-inheritance: - -permuta.misc.union_find module ------------------------------- - -.. automodule:: permuta.misc.union_find - :members: - :show-inheritance: diff --git a/docs/source/permuta.rst b/docs/source/permuta.rst deleted file mode 100644 index 869690a0..00000000 --- a/docs/source/permuta.rst +++ /dev/null @@ -1,42 +0,0 @@ -permuta package -=============== - -Subpackages ------------ - -.. toctree:: - - permuta.math - permuta.misc - -Submodules ----------- - -permuta.mesh_pattern module ---------------------------- - -.. automodule:: permuta.mesh_pattern - :members: - :show-inheritance: - -permuta.mesh_patterns module ----------------------------- - -.. automodule:: permuta.mesh_patterns - :members: - :show-inheritance: - -permuta.permutation module --------------------------- - -.. automodule:: permuta.permutation - :members: - :show-inheritance: - -permuta.permutations module ---------------------------- - -.. automodule:: permuta.permutations - :members: - :show-inheritance: - diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..80812c87 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,5 @@ +[mypy] +warn_return_any = True +warn_unused_configs = True +warn_no_return = False +files = permuta/**/*.py \ No newline at end of file diff --git a/permuta/__init__.py b/permuta/__init__.py index 2be9d1f3..fb4b593a 100644 --- a/permuta/__init__.py +++ b/permuta/__init__.py @@ -1,5 +1,13 @@ -from .meshpatt import MeshPatt -from .perm import Perm -from .permset import Av, PermSet +from .patterns import BivincularPatt, CovincularPatt, MeshPatt, Perm, VincularPatt +from .perm_sets.permset import Av, Basis, MeshBasis -__all__ = ["Perm", "PermSet", "Av", "MeshPatt"] +__all__ = [ + "Perm", + "Av", + "Basis", + "MeshBasis", + "MeshPatt", + "BivincularPatt", + "CovincularPatt", + "VincularPatt", +] diff --git a/permuta/_perm_set/__init__.py b/permuta/_perm_set/__init__.py deleted file mode 100644 index 05900e6d..00000000 --- a/permuta/_perm_set/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from . import finite, unbounded -from .permset_base import PermSetBase - -__all__ = ["finite", "unbounded", "PermSetBase"] diff --git a/permuta/_perm_set/finite/__init__.py b/permuta/_perm_set/finite/__init__.py deleted file mode 100644 index bc455718..00000000 --- a/permuta/_perm_set/finite/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .permset_finite import PermSetFinite -from .permset_finite_specificlength import PermSetFiniteSpecificLength -from .permset_static import PermSetStatic - -__all__ = ["PermSetFinite", "PermSetFiniteSpecificLength", "PermSetStatic"] diff --git a/permuta/_perm_set/finite/permset_finite.py b/permuta/_perm_set/finite/permset_finite.py deleted file mode 100644 index f0579498..00000000 --- a/permuta/_perm_set/finite/permset_finite.py +++ /dev/null @@ -1,13 +0,0 @@ -import abc - -from ..permset_base import PermSetBase - - -class PermSetFinite(PermSetBase): - """Base class for all finite perm sets.""" - - @abc.abstractmethod - def random(self): - # Return a random element from the range - # Only possible due to the perm set being finite - pass diff --git a/permuta/_perm_set/finite/permset_finite_specificlength.py b/permuta/_perm_set/finite/permset_finite_specificlength.py deleted file mode 100644 index 9e6688b6..00000000 --- a/permuta/_perm_set/finite/permset_finite_specificlength.py +++ /dev/null @@ -1,7 +0,0 @@ -from .permset_finite import PermSetFinite - - -class PermSetFiniteSpecificLength(PermSetFinite): - """Base class for all finite perm sets of perms of a specific length.""" - - pass diff --git a/permuta/_perm_set/finite/permset_static.py b/permuta/_perm_set/finite/permset_static.py deleted file mode 100644 index e4c67028..00000000 --- a/permuta/_perm_set/finite/permset_static.py +++ /dev/null @@ -1,49 +0,0 @@ -import random - -from ...perm import Perm -from .permset_finite import PermSetFinite - - -class PermSetStatic(PermSetFinite): - """A static perm set.""" - - __slots__ = ("_set", "_generating_function", "_iter") - - def __init__(self, iterable=()): - self._set = set(iterable) - self._tuple = tuple(self._set) - self._generating_function = "X" - self._iter = None - - @property - def generating_function(self): - # TODO Replace with symbolic variables and stuff - return self._generating_function - - def of_length(self, length): - return PermSetStatic(perm for perm in self if len(perm) == length) - - def random(self): - return random.choice(self._tuple) - - def __contains__(self, item): - return isinstance(item, Perm) and item in self._set - - def __getitem__(self, key): - return self._tuple[key] - - def __iter__(self): - self._iter = iter(self._tuple) - return self - - def __len__(self): - return len(self._tuple) - - def __next__(self): - return next(self._iter) - - def __repr__(self): - return "PermSet({})".format(repr(self._tuple)) - - def __str__(self): - return "{{{}}}".format(", ".join(str(p) for p in sorted(self))) diff --git a/permuta/_perm_set/permset_base.py b/permuta/_perm_set/permset_base.py deleted file mode 100644 index 724548af..00000000 --- a/permuta/_perm_set/permset_base.py +++ /dev/null @@ -1,33 +0,0 @@ -import abc - - -class PermSetBase(metaclass=abc.ABCMeta): - """Base class for all perm sets.""" - - def contains(self, perm): - # This comes for free when __contains__ is implemented - return perm in self - - @abc.abstractmethod - def of_length(self, perm): - pass - - @abc.abstractmethod - def __contains__(self, perm): - pass - - @abc.abstractmethod - def __getitem__(self, key): - pass - - @abc.abstractmethod - def __len__(self, key): - pass - - def __str__(self): - # Base __str__ which subclasses should override - return "a perm set" - - def __repr__(self): - # Base __repr__ which subclasses should override - return "" diff --git a/permuta/_perm_set/unbounded/__init__.py b/permuta/_perm_set/unbounded/__init__.py deleted file mode 100644 index 5ea7afee..00000000 --- a/permuta/_perm_set/unbounded/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from . import all, described -from .permset_unbounded import PermSetUnbounded - -__all__ = ["all", "described", "PermSetUnbounded"] diff --git a/permuta/_perm_set/unbounded/all/__init__.py b/permuta/_perm_set/unbounded/all/__init__.py deleted file mode 100644 index 89b29944..00000000 --- a/permuta/_perm_set/unbounded/all/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .permset_all import PermSetAll - -__all__ = ["PermSetAll"] diff --git a/permuta/_perm_set/unbounded/all/permset_all.py b/permuta/_perm_set/unbounded/all/permset_all.py deleted file mode 100644 index 5df5bbd1..00000000 --- a/permuta/_perm_set/unbounded/all/permset_all.py +++ /dev/null @@ -1,121 +0,0 @@ -# TODO: Module docstring - -import itertools -import random -from math import factorial - -from ....perm import Perm -from ...finite.permset_finite import PermSetFinite -from ...finite.permset_finite_specificlength import PermSetFiniteSpecificLength -from ...finite.permset_static import PermSetStatic -from ..permset_unbounded import PermSetUnbounded - - -class PermSetAll(PermSetUnbounded): - __iter = None - __iter_number = None - - def up_to(self, perm): - # Should return a PermSetAllRange - raise NotImplementedError - - def of_length(self, length): - return PermSetAllSpecificLength(length) - - def range(self, stop): - raise NotImplementedError - - def __len__(self): - raise NotImplementedError - - def __getitem__(self, key): - return Perm.unrank(key) - - def __next__(self): - if self.__iter is None: - self.__iter = iter(self[self.__iter_number]) - try: - return next(self.__iter) - except StopIteration: - self.__iter = None - self.__iter_number += 1 - return self.__next__() - - def __iter__(self): - self.__iter = None - self.__iter_number = 0 - return self - - def __contains__(self, perm): - return isinstance(perm, Perm) # Why would you even ask? - - def __repr__(self): - return "PermSet()" - - def __str__(self): - return "" - - -class PermSetAllSpecificLength(PermSetFiniteSpecificLength): - """Class for iterating through all perms of a specific length.""" - - __slots__ = "_length" - - def __init__(self, length): - self._length = length - - @property - def length(self): - return self._length - - @property - def domain(self): - return list(range(self.length)) # tuple instead? - - def of_length(self, length): - if length != self._length: - return PermSetStatic([]) - else: - return self - - def random(self): - """Return a random perm of the length.""" - all_elements = self.domain - random.shuffle(all_elements) - return Perm(all_elements) - - def __contains__(self, other): - """Check if other is a permutation in the set.""" - return isinstance(other, Perm) and len(other) == self.length - - def __getitem__(self, key): - return Perm.unrank(key, self.length) - - def __iter__(self): - # Need to return new instance because permutations of itertools - # depletes self - # This probably needs looking into because the iter isn't a subclass of - # PermSet - return PermSetAllSpecificLengthIterator(self.domain) - - def __len__(self): - return factorial(self.length) - - def __repr__(self): - return "PermSet({})".format(self.length) - - def __str__(self): - return "".format(self.length) - - -class PermSetAllSpecificLengthIterator(itertools.permutations): - def __next__(self): - return Perm(super(PermSetAllSpecificLengthIterator, self).__next__()) - - -class PermSetAllFiniteLengthSubset(PermSetFinite): - pass - - -class PermSetAllUnboundedLengthSubset(PermSetUnbounded): - pass diff --git a/permuta/_perm_set/unbounded/described/__init__.py b/permuta/_perm_set/unbounded/described/__init__.py deleted file mode 100644 index 4f13992c..00000000 --- a/permuta/_perm_set/unbounded/described/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from . import avoiding -from .permset_described import PermSetDescribed - -__all__ = ["avoiding", "PermSetDescribed"] diff --git a/permuta/_perm_set/unbounded/described/avoiding/__init__.py b/permuta/_perm_set/unbounded/described/avoiding/__init__.py deleted file mode 100644 index acff2ef6..00000000 --- a/permuta/_perm_set/unbounded/described/avoiding/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from . import avoiding_length_0 -from .avoiding import Avoiding, AvoidingGeneric, AvoidingGenericSpecificLength - -__all__ = [ - "avoiding_length_0", - "Avoiding", - "AvoidingGeneric", - "AvoidingGenericSpecificLength", -] diff --git a/permuta/_perm_set/unbounded/described/avoiding/avoiding.py b/permuta/_perm_set/unbounded/described/avoiding/avoiding.py deleted file mode 100644 index 984e7489..00000000 --- a/permuta/_perm_set/unbounded/described/avoiding/avoiding.py +++ /dev/null @@ -1,203 +0,0 @@ -import functools -import multiprocessing -import random - -from .....descriptors.basis import AbstractBasis, Basis -from .....perm import Perm -from ....finite.permset_finite_specificlength import PermSetFiniteSpecificLength -from ....finite.permset_static import PermSetStatic -from ....unbounded.all.permset_all import PermSetAll -from ..permset_described import PermSetDescribed - - -class Avoiding(PermSetDescribed): - """The base class for all avoidance classes.""" - - # NOTE: Monkey patching of default subclass happens at end of file - DESCRIPTOR_CLASS = AbstractBasis - - @property - def basis(self): - return self._descriptor - - def __hash__(self): - return id(self) # Requires the singleton property - - def __len__(self): - raise NotImplementedError # This is a hard task! - - def __repr__(self): - return "Av({})".format(tuple(self.basis)) - - def __str__(self): - return "Av({})".format(", ".join(map(str, self.basis))) - - -class AvoidingGeneric(Avoiding): - # Empty basis is dispatched to correct/another class (AvoidingEmpty) - __CLASS_CACHE = {} - _CACHE_LOCK = multiprocessing.Lock() - - def __new__(cls, basis): - if basis in AvoidingGeneric.__CLASS_CACHE: - return AvoidingGeneric.__CLASS_CACHE[basis] - else: - instance = super(AvoidingGeneric, cls).__new__(cls) - # Generic case includes empty permutation - instance.cache = [{Perm(): [0]}] - AvoidingGeneric.__CLASS_CACHE[basis] = instance - return instance - - def _ensure_level(self, level_number): - # Ensure level is available - while len(self.cache) <= level_number: - nplusone = len(self.cache) # really: len(perm) + 1 - if isinstance(self.basis, Basis): - # Smart way when basis consists only of Perms - # We will build the length n + 1 perms, from the length n perms - - n = nplusone - 1 - new_level = dict() - max_size = max(len(p) for p in self.basis) - last_level = self.cache[-1] - - # If we are currently building a length for which there is a - # basis element of that length, we set this to True - check_length = nplusone in [len(b) for b in self.basis] - # and get the basis element of this nplusone - smaller_elems = [b for b in self.basis if len(b) == nplusone] - - def valid_insertions(perm): - res = None - for i in range(max(0, n - max_size), n): - val = perm[i] - subperm = perm.remove(i) - spots = self.cache[n - 1][subperm] - acceptable = [k for k in spots if k <= val] + [ - k + 1 for k in spots if k >= val - ] - if res is None: - res = frozenset(acceptable) - res = res.intersection(acceptable) - if not res: - break - return res if res is not None else frozenset(range(nplusone)) - - for perm in last_level.keys(): - for value in valid_insertions(perm): - new_perm = perm.insert(index=nplusone, new_element=value) - if not check_length or new_perm not in smaller_elems: - new_level[new_perm] = [] - last_level[perm].append(value) - else: - # Necessary non-smart way for e.g. MeshBasis - new_level = set() - for new_perm in PermSetAll().of_length(nplusone): - if new_perm.avoids(*self.basis): - new_level.add(new_perm) - self.cache.append(new_level) - - def _get_level(self, level_number): - with AvoidingGeneric._CACHE_LOCK: - self._ensure_level(level_number) - res = self.cache[level_number] - if isinstance(res, dict): - return self.cache[level_number].keys() - return res - - def of_length(self, length): - # TODO: Cache of instances? - getter = functools.partial(self._get_level, length) - return AvoidingGenericSpecificLength(length, self.basis, getter) - - def __getitem__(self, key): - level_number = 0 - while True: - level = self._get_level(level_number) - if len(level) <= key: - key -= len(level) - else: # TODO: So dumb - return list(level)[key] - level_number += 1 - - def __next__(self): - if self._iter is None: - cached_perms = self._get_level(self._iter_number) - if len(cached_perms) == 0: - raise StopIteration - self._iter = iter(cached_perms) - try: - return next(self._iter) - except StopIteration: - self._iter = None - self._iter_number += 1 - return self.__next__() - - def __iter__(self): - self._iter = None - self._iter_number = 0 - return self - - def __contains__(self, perm): - # TODO: Think about heuristics for switching to avoiding the patterns - # in the basis instead - if isinstance(perm, Perm): - length = len(perm) - return perm in self._get_level(length) - else: - raise TypeError - - def is_subclass(self, other): - """ Check if the `self` is a subclass of `other`. """ - return all(p1 not in self for p1 in other.basis) - - -class AvoidingGenericSpecificLength(PermSetFiniteSpecificLength): - """Class for iterating through all perms of a specific length avoiding a - basis.""" - - # __slots__ = ("_length", "_basis", "_get_perms", "_iter") - - def __init__(self, length, basis, get_perms): - self._length = length - self._basis = basis - self._get_perms = get_perms - self._iter = None - - def of_length(self, length): - if length != self._length: - return PermSetStatic() - else: - return self - - def random(self): - return random.choice(self._get_perms()) - - def __contains__(self, other): - """Check if other is a permutation in the set.""" - return isinstance(other, Perm) and other in self._get_perms() - - def __getitem__(self, key): - raise NotImplementedError - - def __iter__(self): - self._iter = iter(self._get_perms()) - return self - - def __len__(self): - return len(self._get_perms()) - - def __next__(self): - return next(self._iter) - - def __str__(self): - return "" "".format( - self._length, self._basis - ) - - def __repr__(self): - return "Av({}).of_length({})" "".format(repr(tuple(self._basis)), self._length) - - -# Set default Avoiding class to be dispatched -Avoiding.DEFAULT_CLASS = AvoidingGeneric diff --git a/permuta/_perm_set/unbounded/described/avoiding/avoiding_length_0/__init__.py b/permuta/_perm_set/unbounded/described/avoiding/avoiding_length_0/__init__.py deleted file mode 100644 index bf89cd1b..00000000 --- a/permuta/_perm_set/unbounded/described/avoiding/avoiding_length_0/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .avoiding_empty import AvoidingEmpty - -__all__ = ["AvoidingEmpty"] diff --git a/permuta/_perm_set/unbounded/described/avoiding/avoiding_length_0/avoiding_empty.py b/permuta/_perm_set/unbounded/described/avoiding/avoiding_length_0/avoiding_empty.py deleted file mode 100644 index 47d908c0..00000000 --- a/permuta/_perm_set/unbounded/described/avoiding/avoiding_length_0/avoiding_empty.py +++ /dev/null @@ -1,31 +0,0 @@ -from ......descriptors.basis import Basis -from ......perm import Perm -from .....finite.permset_static import PermSetStatic -from ..avoiding import Avoiding - - -class AvoidingEmpty(Avoiding): - """The empty perm set class.""" - - DESCRIPTOR = Basis(Perm()) - __CLASS_CACHE = None - - def __new__(cls, basis): - if AvoidingEmpty.__CLASS_CACHE is None: - instance = super(AvoidingEmpty, cls).__new__(cls) - AvoidingEmpty.__CLASS_CACHE = instance - return instance - else: - return AvoidingEmpty.__CLASS_CACHE - - def of_length(self, _length): - return PermSetStatic() - - def __contains__(self, _object): - return False - - def __getitem__(self, _key): - raise IndexError - - def __len__(self): - return 0 diff --git a/permuta/_perm_set/unbounded/described/permset_described.py b/permuta/_perm_set/unbounded/described/permset_described.py deleted file mode 100644 index 36c6aee4..00000000 --- a/permuta/_perm_set/unbounded/described/permset_described.py +++ /dev/null @@ -1,17 +0,0 @@ -from ..permset_unbounded import PermSetUnbounded - - -class PermSetDescribed(PermSetUnbounded): - """A base class for unbounded perm sets.""" - - # TODO: Make things abstract properties as a solution? - - # TODO: These two attribute needs to be defined in all immediate subclasses - DEFAULT_CLASS = None - DESCRIPTOR_CLASS = None - - # TODO: This needs to be defined in classes for specific descriptors - DESCRIPTOR = None - - def __init__(self, descriptor): - self._descriptor = descriptor diff --git a/permuta/_perm_set/unbounded/permset_unbounded.py b/permuta/_perm_set/unbounded/permset_unbounded.py deleted file mode 100644 index 985beef9..00000000 --- a/permuta/_perm_set/unbounded/permset_unbounded.py +++ /dev/null @@ -1,7 +0,0 @@ -from ..permset_base import PermSetBase - - -class PermSetUnbounded(PermSetBase): - """Base class for all unbounded perm sets.""" - - pass diff --git a/permuta/bisc/bisc.py b/permuta/bisc/bisc.py index 58cbd10e..afbcf42f 100644 --- a/permuta/bisc/bisc.py +++ b/permuta/bisc/bisc.py @@ -13,8 +13,7 @@ show_me_basis, to_sg_format, ) -from permuta.perm import Perm -from permuta.permset import PermSet +from permuta.patterns.perm import Perm def bisc(A, m, n=None, report=False): @@ -29,7 +28,7 @@ def bisc(A, m, n=None, report=False): n = 7 D = defaultdict(list) for i in range(n + 1): - for perm in PermSet(i): + for perm in Perm.of_length(i): if A(perm): D[i].append(perm) @@ -71,7 +70,7 @@ def auto_bisc(prop): print("You should have permutations up to length at least 8") return for i in range(L + 1): - for perm in PermSet(i): + for perm in Perm.of_length(i): if perm not in A[i]: B[i].append(perm) @@ -83,7 +82,7 @@ def auto_bisc(prop): for i in range(L + 1): A[i] = [] B[i] = [] - for perm in PermSet(i): + for perm in Perm.of_length(i): if prop(perm): A[i].append(perm) else: @@ -106,9 +105,9 @@ def auto_bisc(prop): print("Attempting to read perms from permsets") good_entry = None bad_entry = None - with os.scandir("permsets") as entries: + with os.scandir("../resources/bisc") as entries: for i, entry in enumerate(entries): - en = entry.name + en = entry.name[:-5] spl = en.split("_") if spl[0] == prop: if ( @@ -126,8 +125,8 @@ def auto_bisc(prop): if good_entry is not None and bad_entry is not None: break if good_entry is not None and bad_entry is not None: - A = read_bisc_file("permsets/" + good_entry) - B = read_bisc_file("permsets/" + bad_entry) + A = read_bisc_file("../resources/bisc/" + good_entry) + B = read_bisc_file("../resources/bisc/" + bad_entry) else: print("The required files do not exist") return @@ -221,7 +220,7 @@ def auto_bisc(prop): # Adding perms to the dictionaries for i in range(oldL + 1, L + 1): print("Adding perms of length {}".format(i)) - for perm in PermSet(i): + for perm in Perm.of_length(i): if prop(perm): A[i].append(perm) else: @@ -236,9 +235,9 @@ def auto_bisc(prop): print("Attempting to read perms from permsets") good_entry = None bad_entry = None - with os.scandir("permsets") as entries: + with os.scandir("../resources/bisc") as entries: for i, entry in enumerate(entries): - en = entry.name + en = entry.name[:-5] spl = en.split("_") if spl[0] == prop: if ( @@ -256,8 +255,8 @@ def auto_bisc(prop): if good_entry is not None and bad_entry is not None: break if good_entry is not None and bad_entry is not None: - A = read_bisc_file("permsets/" + good_entry) - B = read_bisc_file("permsets/" + bad_entry) + A = read_bisc_file("../resources/bisc/" + good_entry) + B = read_bisc_file("../resources/bisc/" + bad_entry) else: print("The required files do not exist") return @@ -276,7 +275,7 @@ def create_bisc_input(N, prop): An, Bn = [], [] - for perm in PermSet(n): + for perm in Perm.of_length(n): if prop(perm): An.append(perm) else: @@ -287,47 +286,33 @@ def create_bisc_input(N, prop): return A, B -def write_bisc_files(N, prop, info): - """ - Create a dictionary, D, containing keys 1, 2, 3, ..., N. Each key points to +def write_bisc_files(n: int, prop, info: str) -> None: + """Create a dictionary, D, containing keys 1, 2, 3, ..., n. Each key points to a list of permutations satisfying the property prop. The dictionary E has the same keys and they point to the complement. """ + good, bad = create_bisc_input(n, prop) + write_json_to_file(good, f"{info}_good_len{n}.json") + write_json_to_file(bad, f"{info}_bad_len{n}.json") - A, B = {}, {} - - for n in range(N + 1): - - An, Bn = [], [] - - for perm in PermSet(n): - if prop(perm): - An.append(perm) - else: - Bn.append(perm) - - A[n], B[n] = An, Bn - f = open("{}_good_len{}".format(info, N), "a+") - f.write(json.dumps(A)) - f.close() - - f = open("{}_bad_len{}".format(info, N), "a+") - f.write(json.dumps(B)) - f.close() - - -def from_json(s): - d = json.loads(s) - return {int(key): list(map(Perm, values)) for key, values in d.items()} +def write_json_to_file(json_obj, file_name): + try: + with open(file_name, "a+") as f: + f.write(json.dumps(json_obj)) + except OSError: + print(f"Could not write to file: {file_name}") -def read_bisc_file(p): - A = dict() +def from_json(json_string): + json_obj = json.loads(json_string) + return {int(key): list(map(Perm, values)) for key, values in json_obj.items()} - f = open(p, "r") - for line in f: - A = from_json(line) - f.close() - return A +def read_bisc_file(path): + try: + with open(f"{path}.json", "r") as f: + return from_json(f.readline()) + except (ValueError, TypeError, OSError): + print(f"File is invalid: {path}") + return {} diff --git a/permuta/bisc/bisc_subfunctions.py b/permuta/bisc/bisc_subfunctions.py index 7abb0274..f5c11edf 100644 --- a/permuta/bisc/bisc_subfunctions.py +++ b/permuta/bisc/bisc_subfunctions.py @@ -1,9 +1,8 @@ from itertools import chain +from math import factorial -from permuta.meshpatt import MeshPatt -from permuta.misc import factorial -from permuta.perm import Perm -from permuta.permset import PermSet +from permuta.patterns.meshpatt import MeshPatt +from permuta.patterns.perm import Perm def mine(goodperms, M, N=None, report=False): @@ -293,7 +292,7 @@ def find_badpatts(perm): print("Starting search for forbidden patterns of length {}".format(j)) badpatts[j] = {} - for perm in PermSet(j): + for perm in Perm.of_length(j): badpatts[j][perm] = find_badpatts(perm) if report: diff --git a/permuta/bisc/perm_properties.py b/permuta/bisc/perm_properties.py new file mode 100644 index 00000000..65045194 --- /dev/null +++ b/permuta/bisc/perm_properties.py @@ -0,0 +1,254 @@ +from collections import deque +from itertools import islice +from typing import Deque, List, Tuple + +from permuta.patterns.meshpatt import MeshPatt +from permuta.patterns.patt import Patt +from permuta.patterns.perm import Perm +from permuta.permutils.groups import dihedral_group + + +def _is_sorted(lis: List[int]) -> bool: + # Return true if w is increasing, i.e., sorted. + return all(elem == i for elem, i in zip(lis, range(len(lis)))) + + +def _stack_sort(perm_slice: List[int]) -> List[int]: + n = len(perm_slice) + if n in (0, 1): + return perm_slice + max_i, max_v = max(enumerate(perm_slice), key=lambda pos_elem: pos_elem[1]) + # Recursively solve without largest + if max_i == 0: + n_lis = _stack_sort(perm_slice[1:n]) + elif max_i == n - 1: + n_lis = _stack_sort(perm_slice[0 : n - 1]) + else: + n_lis = _stack_sort(perm_slice[0:max_i]) + n_lis.extend(_stack_sort(perm_slice[max_i + 1 : n])) + n_lis.append(max_v) + return n_lis + + +def stack_sortable(perm: Perm) -> bool: + """Returns true if perm is stack sortable.""" + return _is_sorted(_stack_sort(list(perm))) + + +def _bubble_sort(perm_slice: List[int]) -> List[int]: + n = len(perm_slice) + if n in (0, 1): + return perm_slice + max_i, max_v = max(enumerate(perm_slice), key=lambda pos_elem: pos_elem[1]) + # Recursively solve without largest + if max_i == 0: + n_lis = perm_slice[1:n] + elif max_i == n - 1: + n_lis = _bubble_sort(perm_slice[0 : n - 1]) + else: + n_lis = _bubble_sort(perm_slice[0:max_i]) + n_lis.extend(perm_slice[max_i + 1 : n]) + n_lis.append(max_v) + return n_lis + + +def bubble_sortable(perm: Perm) -> bool: + """Returns true if perm is stack sortable.""" + return _is_sorted(_bubble_sort(list(perm))) + + +def _quick_sort(perm_slice: List[int]) -> List[int]: + assert not perm_slice or set(perm_slice) == set( + range(min(perm_slice), max(perm_slice) + 1) + ) + n = len(perm_slice) + if n == 0: + return perm_slice + maxind = -1 + # Note that perm does not need standardizing as sfp uses left to right maxima. + for maxind in Perm(perm_slice).strong_fixed_points(): + pass + if maxind != -1: + lis: List[int] = ( + _quick_sort(perm_slice[:maxind]) + + [perm_slice[maxind]] + + _quick_sort(perm_slice[maxind + 1 :]) + ) + else: + firstval = perm_slice[0] + lis = ( + list(filter(lambda x: x < firstval, perm_slice)) + + [perm_slice[0]] + + list(filter(lambda x: x > firstval, perm_slice)) + ) + return lis + + +def quick_sortable(perm: Perm) -> bool: + """Returns true if perm is quicksort sortable.""" + return _is_sorted(_quick_sort(list(perm))) + + +_BKV_PATT = Perm((1, 0)) + + +def bkv_sortable(perm: Perm, patterns: Tuple[Patt, ...] = ()) -> bool: + """Check if a permutation is BKV sortable. + See: + https://arxiv.org/pdf/1907.08142.pdf + https://arxiv.org/pdf/2004.01812.pdf + """ + # See + n = len(perm) + inp = deque(perm) + # the right stack read from top to bottom + # the left stack read from top to bottom + right_stack: Deque[int] = deque([]) + left_stack: Deque[int] = deque([]) + expected = 0 + while expected < n: + if inp: + right_stack.appendleft(inp[0]) + if Perm.to_standard(right_stack).avoids(*patterns): + inp.popleft() + continue + right_stack.popleft() + + if right_stack: + left_stack.appendleft(right_stack[0]) + if Perm.to_standard(left_stack).avoids(_BKV_PATT): + right_stack.popleft() + continue + left_stack.popleft() + + assert left_stack + # Normally, we would gather elements from left stack but since we only care + # about wether it sorts the permutation, we just compare it against expected. + if expected != left_stack.popleft(): + return False + expected += 1 + return True + + +def west_2_stack_sortable(perm: Perm) -> bool: + """Returns true if perm can be sorted by two passes through a stack""" + return _is_sorted(_stack_sort(_stack_sort(list(perm)))) + + +def west_3_stack_sortable(perm: Perm) -> bool: + """Returns true if perm can be sorted by three passes through a stack""" + return _is_sorted(_stack_sort(_stack_sort(_stack_sort(list(perm))))) + + +_SMOOTH_PATT = (Perm((0, 2, 1, 3)), Perm((1, 0, 3, 2))) + + +def smooth(perm: Perm) -> bool: + """Returns true if the perm is smooth, i.e. 0213- and 1032-avoiding.""" + return perm.avoids(*_SMOOTH_PATT) + + +_FOREST_LIKE_PATT = (Perm((0, 2, 1, 3)), MeshPatt(Perm((1, 0, 3, 2)), [(2, 2)])) + + +def forest_like(perm: Perm) -> bool: + """Returns true if the perm is forest like.""" + return perm.avoids(*_FOREST_LIKE_PATT) + + +_BAXTER_PATT = ( + MeshPatt(Perm((1, 3, 0, 2)), [(2, 2)]), + MeshPatt(Perm((2, 0, 3, 1)), [(2, 2)]), +) + + +def baxter(perm: Perm) -> bool: + """Returns true if the perm is a baxter permutation.""" + return perm.avoids(*_BAXTER_PATT) + + +_SIMSUN_PATT = MeshPatt(Perm((2, 1, 0)), [(1, 0), (1, 1), (2, 2)]) + + +def simsun(perm: Perm) -> bool: + """Returns true if the perm is a simsun permutation.""" + return perm.avoids(*_SIMSUN_PATT) + + +def dihedral(perm: Perm) -> bool: + """Does perm belong to a dihedral group? We use the convention that D1 and D2 are + not subgrroups of S1 and S2, respectively.""" + return any(perm == d_perm for d_perm in dihedral_group(len(perm))) + + +def in_alternating_group(perm: Perm) -> bool: + """Does perm belong to alternating group? We use the convention that D1 and D2 are + not subgrroups of S1 and S2, respectively.""" + n = len(perm) + if n == 0: + return True + if n < 3: + return n % 2 == 1 + return perm.count_inversions() % 2 == 0 + + +def _perm_to_yt(perm: Perm) -> List[List[int]]: + # transform perm to standard young table + + def insert_in_row(i, k): + cur_row = res[i] + found = next(((ind, cur) for ind, cur in enumerate(cur_row) if cur > k), None) + if not found: + cur_row.append(k) + else: + ind, cur = found + cur_row[ind] = k + if len(res) <= i + 1: + res.append([cur]) + else: + insert_in_row(i + 1, cur) + + res: List[List[int]] = [[perm[0]]] if perm else [] + for val in islice(perm, 1, None): + insert_in_row(0, val) + return res + + +def _tableau_contains_shape(tab: List[List[int]], shape: List[int]) -> bool: + # Return True if the tableaux tab contains the shape + return len(tab) >= len(shape) and all(s <= t for s, t in zip(shape, map(len, tab))) + + +def yt_perm_avoids_22(perm: Perm) -> bool: + """Returns true if perm's standard young table avoids shape [2,2].""" + return not _tableau_contains_shape(_perm_to_yt(perm), [2, 2]) + + +def yt_perm_avoids_32(perm: Perm) -> bool: + """Returns true if perm's standard young table avoids shape [3,2].""" + return not _tableau_contains_shape(_perm_to_yt(perm), [3, 2]) + + +_AV_231_AND_MESH_PATT = ( + Perm((1, 2, 0)), + MeshPatt(Perm((0, 1, 5, 2, 3, 4)), [(1, 6), (4, 5), (4, 6)]), +) + + +def av_231_and_mesh(perm: Perm) -> bool: + """Check if perm avoids MeshPatt(Perm((0, 1, 5, 2, 3, 4)), [(1, 6), (4, 5), (4, 6)]) + and the classial pattern 231. + """ + return perm.avoids(*_AV_231_AND_MESH_PATT) + + +_HARD_MESH_PATT = ( + MeshPatt(Perm((0, 1, 2)), [(0, 0), (1, 1), (2, 2), (3, 3)]), + MeshPatt(Perm((0, 1, 2)), [(0, 3), (1, 2), (2, 1), (3, 0)]), +) + + +def hard_mesh(perm: Perm) -> bool: + """Check if perm avoids MeshPatt(Perm((0, 1, 2)), [(0, 0), (1, 1), (2, 2), (3, 3)]) + and MeshPatt(Perm((0, 1, 2)), [(0, 3), (1, 2), (2, 1), (3, 0)]).""" + return perm.avoids(*_HARD_MESH_PATT) diff --git a/permuta/bisc/permsets/__init__.py b/permuta/bisc/permsets/__init__.py deleted file mode 100644 index e5dcb3c0..00000000 --- a/permuta/bisc/permsets/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -from .perm_properties import ( - Baxter, - BKV_sortable, - SimSun, - West_2_stack_sortable, - West_3_stack_sortable, - av_231_and_mesh, - dihedral, - forest_like, - hard_mesh, - in_alternating_group, - quick_sortable, - smooth, - stack_sortable, - yt_perm_avoids_22, - yt_perm_avoids_32, -) - -__all__ = [ - "Baxter", - "BKV_sortable", - "SimSun", - "West_2_stack_sortable", - "West_3_stack_sortable", - "av_231_and_mesh", - "dihedral", - "forest_like", - "hard_mesh", - "in_alternating_group", - "quick_sortable", - "smooth", - "stack_sortable", - "yt_perm_avoids_22", - "yt_perm_avoids_32", -] diff --git a/permuta/bisc/permsets/perm_properties.py b/permuta/bisc/permsets/perm_properties.py deleted file mode 100644 index 9490286d..00000000 --- a/permuta/bisc/permsets/perm_properties.py +++ /dev/null @@ -1,298 +0,0 @@ -from permuta.meshpatt import MeshPatt -from permuta.perm import Perm - - -def is_sorted(w): - """ - Return true if w is increasing, i.e., sorted - - EXAMPLES - sage: is_sorted([1,3,2]) - False - """ - - n = len(w) - - if n <= 1: - return True - - for i in range(n - 1): - if w[i] > w[i + 1]: - return False - - return True - - -# -# helper function for stack-sort and bubble-sort. -# finds the location of the maximal element -# - - -def loc_max(w): - - L = max(w) - - for i in range(L + 1): - if w[i] == L: - return i - - -# -# function takes a permutation w and does one pass of stack-sort on it -# - - -def stack_sort(w): - - i = len(w) - - if i == 0: - return [] - - if i == 1: - return w - else: - [j, J] = [loc_max(w), max(w)] - - if j == 0: - W2 = list(stack_sort(w[1:i])) - W2.append(J) - - return W2 - - if j == i - 1: - W1 = list(stack_sort(w[0 : i - 1])) - W1.append(J) - - return W1 - - else: - W1 = list(stack_sort(w[0:j])) - W2 = list(stack_sort(w[j + 1 : i])) - - W1.extend(W2) - W1.extend([J]) - - return W1 - - -# -# function takes a permutation w and does one pass of bubble-sort on it -# - - -def bubble_sort(w): - - i = len(w) - - if i == 0: - return [] - - if i == 1: - return w - else: - [j, J] = [loc_max(w), max(w)] - - if j == 0: - W2 = w[1:i] - W2.append(J) - - return W2 - - if j == i - 1: - W1 = list(bubble_sort(w[0 : i - 1])) - W1.append(J) - - return W1 - - else: - W1 = list(bubble_sort(w[0:j])) - W2 = w[j + 1 : i] - - W1.extend(W2) - W1.extend([J]) - - return W1 - - -def quick_sort_sub(perm): - - L = len(perm) - - if L == 0: - return [] - - sfps = list(Perm(perm).strong_fixed_points()) - if sfps: - maxind = sfps[-1] - return ( - quick_sort_sub([perm[i] for i in range(maxind)]) - + [perm[maxind]] - + quick_sort_sub([perm[i] for i in range(maxind + 1, L)]) - ) - else: - firstval = perm[0] - smaller = filter(lambda x: x < firstval, perm) - bigger = filter(lambda x: x > firstval, perm) - return list(smaller) + [firstval] + list(bigger) - - -def quick_sort(perm): - - L = len(perm) - - if L == 0: - return perm - - return Perm(quick_sort_sub(perm)) - - -def BKV_sort(perm, B=[]): - inp = list(perm) - stackR = [] # the right stack read from top to bottom - stackL = [] # the left stack read from top to bottom - out = [] - while len(out) < len(perm): - if inp and Perm.to_standard(stackR + [inp[0]]).reverse().avoids(*B): - stackR.append(inp.pop(0)) - elif stackR and ( - Perm.to_standard(stackL + [stackR[-1]]).reverse().avoids(Perm((1, 0))) - ): - stackL.append(stackR.pop()) - elif stackL: - out.append(stackL.pop()) - else: - print("wtf") - print(out, stackL, stackR, inp) - assert False - return Perm(out) - - -def stack_sortable(perm): - return is_sorted(stack_sort(perm)) - - -def quick_sortable(perm): - return is_sorted(quick_sort(perm)) - - -def BKV_sortable(perm, B=[]): - return is_sorted(BKV_sort(perm, B)) - - -def West_2_stack_sortable(perm): - return is_sorted(stack_sort(stack_sort(perm))) - - -def West_3_stack_sortable(perm): - return is_sorted(stack_sort(stack_sort(stack_sort(perm)))) - - -def smooth(perm): - return perm.avoids(Perm((0, 2, 1, 3)), Perm((1, 0, 3, 2))) - - -def forest_like(perm): - return perm.avoids(Perm((0, 2, 1, 3)), MeshPatt(Perm((1, 0, 3, 2)), [(2, 2)])) - - -def Baxter(perm): - return perm.avoids( - MeshPatt(Perm((1, 3, 0, 2)), [(2, 2)]), MeshPatt(Perm((2, 0, 3, 1)), [(2, 2)]) - ) - - -def SimSun(perm): - return perm.avoids(MeshPatt(Perm((2, 1, 0)), [(1, 0), (1, 1), (2, 2)])) - - -def dihedral(perm): - """ - We use the convention that D1 and D2 are not subgrroups of S1 and S2, - respectively.""" - - from sympy.combinatorics import Permutation - from sympy.combinatorics.named_groups import DihedralGroup - - return Permutation(list(perm)) in DihedralGroup(len(perm)) - - -def in_alternating_group(perm): - """ - We use the convention that D1 and D2 are not subgrroups of S1 and S2, - respectively.""" - - from sympy.combinatorics import Permutation - from sympy.combinatorics.named_groups import AlternatingGroup - - if perm == Perm(()): - return True - - return Permutation(list(perm)) in AlternatingGroup(len(perm)) - - -def perm_to_yt(perm): - - res = [] - - def insert_in_row(i, k): - if len(res) <= i: - return res.append([k]) - - cur_row = res[i] - cur = cur_row[0] - for ind, cur in enumerate(cur_row): - if cur > k: - cur_row[ind] = k - return insert_in_row(i + 1, cur) - else: - cur_row.append(k) - - for k in perm: - insert_in_row(0, k) - - return res - - -def tab_shape(tab): - return list(map(len, tab)) - - -def tableau_contains_shape(tab, sh): - """ - Return True if the tableaux tab contains the shape sh - """ - tab_sh = tab_shape(tab) - - lsh = len(sh) - - if len(tab_sh) < lsh: - return False - - for i in range(lsh): - if sh[i] > tab_sh[i]: - return False - - return True - - -def yt_perm_avoids_22(perm): - return not tableau_contains_shape(perm_to_yt(perm), [2, 2]) - - -def yt_perm_avoids_32(perm): - return not tableau_contains_shape(perm_to_yt(perm), [3, 2]) - - -def av_231_and_mesh(perm): - return perm.avoids( - Perm((1, 2, 0)), MeshPatt(Perm((0, 1, 5, 2, 3, 4)), [(1, 6), (4, 5), (4, 6)]) - ) - - -def hard_mesh(perm): - return perm.avoids( - MeshPatt(Perm((0, 1, 2)), [(0, 0), (1, 1), (2, 2), (3, 3)]), - MeshPatt(Perm((0, 1, 2)), [(0, 3), (1, 2), (2, 1), (3, 0)]), - ) diff --git a/permuta/cli.py b/permuta/cli.py index c3ddd09c..5966d22a 100644 --- a/permuta/cli.py +++ b/permuta/cli.py @@ -1,56 +1,117 @@ import argparse import signal import sys +import types -from permuta import Av, Perm +from permuta import Av, Basis +from permuta.permutils import InsertionEncodablePerms, PolyPerms, lex_min -def signal_handler(sig, frame): - print() - print("Exiting.") - sys.exit(0) +def sigint_handler(sig: int, _frame: types.FrameType) -> None: + """For terminating infinite task.""" + if sig == signal.SIGINT: + print("\nExiting.") + sys.exit(0) -def enumerate_class(args): - basis = [] - for perm_str in args.basis.split("_"): - perm_tuple = tuple(map(int, perm_str)) - if 0 not in perm_tuple: - perm_tuple = tuple(i - 1 for i in perm_tuple) - perm = Perm(perm_tuple, check=True) - basis.append(perm) - perm_class = Av(basis) - n = 0 +def enumerate_class(args: argparse.Namespace) -> None: + """Enumerate a perm class indefinitely, one length at a time.""" + signal.signal(signal.SIGINT, sigint_handler) + perm_class = Av.from_string(args.basis) print(f"Enumerating {perm_class}. Press Ctrl+C to exit.") - signal.signal(signal.SIGINT, signal_handler) + n = 0 while True: - num = len(list(perm_class.of_length(n))) - print(f"{num}, ", end="", flush=True) + print(perm_class.count(n), end=", ", flush=True) n += 1 -parser = argparse.ArgumentParser(description="A set of tools to work with permutations") -subparsers = parser.add_subparsers(title="subcommands") +def has_regular_insertion_encoding(args: argparse.Namespace) -> None: + """Check if a perm class has a regular insertion encoding.""" + basis = Basis.from_string(args.basis) + perm_class = Av(basis) + if InsertionEncodablePerms.is_insertion_encodable_maximum(basis): + print(f"The class {perm_class} has a regular topmost insertion encoding") + if InsertionEncodablePerms.is_insertion_encodable_rightmost(basis): + print(f"The class {perm_class} has a regular rightmost insertion encoding") + if not InsertionEncodablePerms.is_insertion_encodable(basis): + print(f"{perm_class} does not have a regular insertion encoding") + + +def get_lex_min(args: argparse.Namespace) -> None: + """Prints the 0-based lexicographically minimal representation of the basis.""" + basis = Basis.from_string(args.basis) + print("_".join(str(perm) for perm in lex_min(basis))) + + +def has_poly_growth(args: argparse.Namespace) -> None: + """Prints whether perm class from basis has polynomial growth.""" + basis = Basis.from_string(args.basis) + poly = PolyPerms.is_polynomial(basis) + print(f"Av({basis}) is {'' if poly else 'not '}polynomial") + + +def get_parser() -> argparse.ArgumentParser: + """Construct and return parser.""" + basis_str: str = ( + "The basis as a string where the permutations are separated any token, " + "(e.g. '231_4321', '0132:43210')" + ) + + parser: argparse.ArgumentParser = argparse.ArgumentParser( + description="A set of tools to work with permutations" + ) + subparsers = parser.add_subparsers(title="subcommands") + + # The count command + count_parser: argparse.ArgumentParser = subparsers.add_parser( + "count", + description="A tool to quickly get the enumeration of permutation classes", + ) + count_parser.set_defaults(func=enumerate_class) + count_parser.add_argument( + "basis", help=basis_str, + ) + + # The insenc command + insenc_parser: argparse.ArgumentParser = subparsers.add_parser( + "insenc", + description="A tool to check if a permutation class has a regular insertion" + " encoding.", + ) + insenc_parser.set_defaults(func=has_regular_insertion_encoding) + insenc_parser.add_argument( + "basis", help=basis_str, + ) + + # The lexmin command + lexmin_parser: argparse.ArgumentParser = subparsers.add_parser( + "lexmin", + description="A tool that returns the 0-based lexicographically minimal " + "representation of the basis.", + ) + lexmin_parser.set_defaults(func=get_lex_min) + lexmin_parser.add_argument( + "basis", help=basis_str, + ) + # The poly command + poly_parser: argparse.ArgumentParser = subparsers.add_parser( + "poly", + description="A tool to check if permutation class has polynomial growth.", + ) + poly_parser.set_defaults(func=has_poly_growth) + poly_parser.add_argument("basis", help=basis_str) -# The count command -count_parser = subparsers.add_parser( - "count", description="A tool to quickly get the enumeration of permutation classes" -) -count_parser.set_defaults(func=enumerate_class) -count_parser.add_argument( - "basis", - help="The basis as a string where the permutations are separated by '_' " - "(e.g. '231_4321')", -) + return parser -def main(): +def main() -> None: + """Entry point.""" + parser = get_parser() args = parser.parse_args() if not hasattr(args, "func"): parser.error("Invalid command") args.func(args) - sys.exit(0) if __name__ == "__main__": diff --git a/permuta/descriptors/__init__.py b/permuta/descriptors/__init__.py deleted file mode 100644 index dcc4b7d6..00000000 --- a/permuta/descriptors/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .basis import Basis, MeshBasis, detect_basis_cls -from .descriptor import Descriptor -from .predicate import Predicate - -__all__ = ["basis", "Basis", "MeshBasis", "detect_basis_cls", "Descriptor", "Predicate"] diff --git a/permuta/descriptors/basis.py b/permuta/descriptors/basis.py deleted file mode 100644 index 3976ea55..00000000 --- a/permuta/descriptors/basis.py +++ /dev/null @@ -1,141 +0,0 @@ -# TODO: Module docstring - -import abc -from collections.abc import Iterable - -from ..meshpatt import MeshPatt -from ..perm import Perm -from .descriptor import Descriptor - - -class AbstractBasis(Descriptor, tuple, abc.ABC): - @property - @abc.abstractmethod - def ALLOWED_BASIS_ELEMENT_TYPES(self): - raise NotImplementedError - - def __new__(cls, patts): - return tuple.__new__(cls).union(patts, cls.ALLOWED_BASIS_ELEMENT_TYPES) - - def union(self, patts, patt_types): - if not isinstance(patts, Iterable): - raise TypeError("Non-iterable argument cannot be unified with basis") - - # Input cleaning - patts = set([patts] if isinstance(patts, patt_types) else patts) - - if not patts: - # Empty set of patts added to basis - return self - - # Make sure the elements are patterns - for patt in patts: - if not isinstance(patt, patt_types): - raise TypeError( - "Elements of a basis should all be of type(s) {}".format(patt_types) - ) - - # Add basis patts and sort - patts.update(self) - patts = sorted(patts) # Necessarily non-empty - - # The new basis - new_basis = [] - - # The list of basis patts used for the new basis - basis_patts_used = [] - - # The list of new patts used for the new basis - new_patts_used = [] - - if patts[0] in [c() for c in patt_types]: - new_basis.append(patts[0]) - new_patts_used.append(patts[0]) - else: - patts_iter = iter(patts) - basis_iter = iter(self) - for basis_perm in basis_iter: - # Add perms up to and including this basis perm to the basis - while True: - patt = next(patts_iter) - if patt == basis_perm: - # Add if it avoids the new perms used - if patt.avoids(*new_patts_used): - new_basis.append(patt) - basis_patts_used.append(patt) - break - elif patt.avoids(*new_basis): - # Add it if it avoids the new basis perms - new_basis.append(patt) - new_patts_used.append(patt) - for patt in patts_iter: - # All perms left over weren't in the basis before - if patt.avoids(*new_basis): - new_basis.append(patt) - new_patts_used.append(patt) - - # Return either the unmodified basis or a new basis - if new_patts_used: - return tuple.__new__(self.__class__, new_basis) - else: - return self - - def is_polynomial(self): - return True # TODO - - def __eq__(self, other): - return isinstance(other, self.__class__) and tuple.__eq__(self, other) - - def __hash__(self): - return tuple.__hash__(self) - - def __repr__(self): - return "{}({})".format(self.__class__.__qualname__, tuple.__repr__(self)) - - def __str__(self): - return "{{{}}}".format(", ".join(str(p) for p in self)) - - -class Basis(AbstractBasis): - """A basis class. - - A PermSet can be built with a Basis instance by using the basis provided - to it to see if a perm should be in the PermSet or not. Additionally, - various fast methods exist to build a PermSet defined by a basis. - """ - - ALLOWED_BASIS_ELEMENT_TYPES = (Perm,) - - -class MeshBasis(AbstractBasis): - ALLOWED_BASIS_ELEMENT_TYPES = (MeshPatt,) - - def __new__(cls, patts): - return super().__new__( - cls, - { - patt if isinstance(patt, MeshPatt) else MeshPatt(patt, []) - for patt in patts - }, - ) - - -def detect_basis_cls(basis): - # Argument can be the actual basis class - if basis in AbstractBasis.__subclasses__(): - return basis - - # Argument can be an instance of the basis classes - for BasisCls in AbstractBasis.__subclasses__(): - if isinstance(basis, BasisCls): - return BasisCls - - # Argument can be an object or an iterable of objects that makes up a basis - if isinstance(basis, Perm) or all(isinstance(patt, Perm) for patt in basis): - return Basis - elif isinstance(basis, MeshPatt) or all( - isinstance(patt, (Perm, MeshPatt)) for patt in basis - ): - return MeshBasis - else: - raise ValueError("A basis can only contain Perms and MeshPatts.") diff --git a/permuta/descriptors/descriptor.py b/permuta/descriptors/descriptor.py deleted file mode 100644 index 85187eb8..00000000 --- a/permuta/descriptors/descriptor.py +++ /dev/null @@ -1,14 +0,0 @@ -# TODO: Module docstring - -import abc - - -class Descriptor(metaclass=abc.ABCMeta): - # TODO: Docstring - @abc.abstractmethod - def __eq__(self, other): - pass - - @abc.abstractmethod - def __hash__(self): - pass diff --git a/permuta/descriptors/predicate.py b/permuta/descriptors/predicate.py deleted file mode 100644 index 270304ed..00000000 --- a/permuta/descriptors/predicate.py +++ /dev/null @@ -1,28 +0,0 @@ -# TODO: Module docstring - -from collections.abc import Callable -from dis import dis - -from .descriptor import Descriptor - - -class Predicate(Descriptor): - """A predicate class. - - A PermSet can be built with a Predicate instance by using the predicate - provided to it to see if a perm should be in the PermSet or not. - """ - - def __init__(self, predicate): - if isinstance(predicate, Callable): - self.predicate = predicate - else: - message = "{} object is not callable".format(repr(predicate)) - raise TypeError(message) - - def __eq__(self, other): - if isinstance(other, self.__class__): - # Disassemble predicates and see if code is the same - return dis(self.predicate) == dis(other.predicate) - else: - return False diff --git a/permuta/enumeration_strategies/__init__.py b/permuta/enumeration_strategies/__init__.py index 7d747d8c..cbb50e70 100644 --- a/permuta/enumeration_strategies/__init__.py +++ b/permuta/enumeration_strategies/__init__.py @@ -1,26 +1,37 @@ +from typing import Iterable, List, Type + +from permuta import Perm + +from .abstract_strategy import EnumerationStrategy from .core_strategies import core_strategies from .insertion_encodable import InsertionEncodingStrategy -fast_enumeration_strategies = [InsertionEncodingStrategy] + core_strategies +fast_enumeration_strategies: List[Type[EnumerationStrategy]] = [ + InsertionEncodingStrategy +] +fast_enumeration_strategies.extend(core_strategies) -long_enumeration_strategies = [] +long_enumeration_strategies: List[Type[EnumerationStrategy]] = [] -all_enumeration_strategies = fast_enumeration_strategies + long_enumeration_strategies +all_enumeration_strategies: List[Type[EnumerationStrategy]] = ( + fast_enumeration_strategies + long_enumeration_strategies +) -def find_strategies(basis, long_runnning=True): - """ - Test all enumeration strategies against the basis and return a list of +def find_strategies( + basis: Iterable[Perm], long_runnning: bool = True +) -> List[EnumerationStrategy]: + """Test all enumeration strategies against the basis and return a list of potentially useful strategies. If `long_runnning` is False, test only the strategies that can be tested quickly. """ if long_runnning: - strategies = all_enumeration_strategies + strategies: List[Type[EnumerationStrategy]] = all_enumeration_strategies else: strategies = fast_enumeration_strategies - working_strategies = [] - for Strategy in strategies: - strategy_object = Strategy(basis) + working_strategies: List[EnumerationStrategy] = [] + for strategy in strategies: + strategy_object = strategy(basis) if strategy_object.applies(): working_strategies.append(strategy_object) return working_strategies diff --git a/permuta/enumeration_strategies/abstract_strategy.py b/permuta/enumeration_strategies/abstract_strategy.py index bc10eaf1..eaf524ca 100644 --- a/permuta/enumeration_strategies/abstract_strategy.py +++ b/permuta/enumeration_strategies/abstract_strategy.py @@ -1,65 +1,41 @@ from abc import ABC, abstractmethod +from typing import FrozenSet, Iterable, Iterator +from permuta import Perm from permuta.permutils.symmetry import all_symmetry_sets class EnumerationStrategy(ABC): - """Abstract class for a strategy to enumerate a permutation classes""" + """Abstract class for a strategy to enumerate a permutation classes.""" - def __init__(self, basis): - ABC.__init__(self) + def __init__(self, basis: Iterable[Perm]) -> None: self._basis = frozenset(basis) @property - def basis(self): + def basis(self) -> FrozenSet[Perm]: + """Getter for basis.""" return self._basis @classmethod - def reference(cls): + def reference(cls) -> str: """A reference for the strategy.""" raise NotImplementedError @abstractmethod - def applies(self): - """ - Return True if the strategy can be used for the basis - """ - pass + def applies(self) -> bool: + """Return True if the strategy can be used for the basis.""" class EnumerationStrategyWithSymmetry(EnumerationStrategy): - """ - Abstract class for a strategy to enumerate a permutation classes. - + """Abstract class for a strategy to enumerate a permutation classes. Each symmetry of the inputed basis is tested against the strategy. """ - def __init__(self, basis): - super().__init__(basis) - self._apply_basis = None - - @property - def basis(self): - """ - The symmetry of the inputed basis to which the strategy applies to. - """ - if self._basis is None: - self.applies() - return self._basis - - def applies(self): - """ - Check if the strategy applies to any symmetry. - """ - for b in map(frozenset, all_symmetry_sets(self._basis)): - if self._applies_to_symmetry(b): - self._apply_basis = b - return True - return False + def applies(self) -> bool: + """Check if the strategy applies to any symmetry.""" + syms: Iterator[FrozenSet[Perm]] = map(frozenset, all_symmetry_sets(self._basis)) + return next((True for b in syms if self._applies_to_symmetry(b)), False) @abstractmethod - def _applies_to_symmetry(self, b): - """ - Check if the strategy applies to this particular symmetry. - """ - pass + def _applies_to_symmetry(self, basis: FrozenSet[Perm]) -> bool: + """Check if the strategy applies to this particular symmetry.""" diff --git a/permuta/enumeration_strategies/core_strategies.py b/permuta/enumeration_strategies/core_strategies.py index 1573779f..b3a46bc3 100644 --- a/permuta/enumeration_strategies/core_strategies.py +++ b/permuta/enumeration_strategies/core_strategies.py @@ -1,181 +1,172 @@ -from abc import abstractproperty, abstractstaticmethod +from abc import abstractmethod, abstractproperty +from typing import ClassVar, FrozenSet, List, Type from permuta import Av, MeshPatt, Perm from permuta.enumeration_strategies.abstract_strategy import ( EnumerationStrategyWithSymmetry, ) -R_U = Perm((1, 2, 0, 3)) -C_U = Perm((2, 0, 1, 3)) -R_D = Perm((1, 3, 0, 2)) -C_D = Perm((2, 0, 3, 1)) - - -# Abstract Core Strategy - class CoreStrategy(EnumerationStrategyWithSymmetry): - """ - Abstract class for a core related strategy. - """ + """Abstract class for a core related strategy.""" - @abstractproperty - def patterns_needed(): - """ - Return the set of patterns that are needed for the strategy to be - useful. - """ - pass - - @abstractstaticmethod - def is_valid_extension(patt): - """ - Determine if the pattern satisfies the condition for strategy to apply. - """ - pass + # https://arxiv.org/pdf/1912.07503.pdf + # See this paper for corr_number - def _applies_to_symmetry(self, b): - """ - Check if the core strategy applies to the basis or any of its symmetry. - - INPUT: + @abstractproperty + def patterns_needed(self) -> FrozenSet[Perm]: + """Return the set of patterns that are needed for the strategy to be useful.""" - - `b`: a set of permutations. - """ - assert isinstance(b, frozenset) - perm_class = Av(b) + @staticmethod + @abstractmethod + def is_valid_extension(patt: Perm) -> bool: + """Determine if the pattern satisfies the condition for strategy to apply.""" + + def _applies_to_symmetry(self, basis: FrozenSet[Perm]): + """Check if the core strategy applies to the basis or any of its symmetry.""" + assert isinstance(basis, frozenset) + perm_class: Av = Av.from_iterable(basis) patterns_are_contained = all(p not in perm_class for p in self.patterns_needed) extensions_are_valid = all( - self.is_valid_extension(patt) for patt in b.difference(self.patterns_needed) + self.is_valid_extension(patt) + for patt in basis.difference(self.patterns_needed) ) return patterns_are_contained and extensions_are_valid @classmethod - def reference(cls): + def reference(cls) -> str: return ( "Enumeration of Permutation Classes and Weighted Labelled " - "Independent Sets: Corollary {}" - ).format(cls.corr_number) + f"Independent Sets: Corollary {cls.corr_number}" + ) @property @staticmethod - def corr_number(): + def corr_number() -> str: """The number of the corollary in the that gives this strategy.""" raise NotImplementedError -# Tool functions - - -def fstrip(perm): - """ - Remove the leading 1 if the permutation is the sum of 1 + p. - """ +def fstrip(perm: Perm) -> Perm: + """Remove the leading 1 if the permutation is the sum of 1 + p.""" + assert len(perm) > 0 if perm[0] == 0: - return Perm.from_iterable(perm[1:]) - else: - return perm + return Perm.one_based(perm[1:]) + return perm -def bstrip(perm): - """ - Remove the trailing n if the permutation is the sum of p + 1. - """ +def bstrip(perm: Perm) -> Perm: + """Remove the trailing n if the permutation is the sum of p + 1.""" + assert len(perm) > 0 if perm[-1] == len(perm) - 1: - return Perm.from_iterable(perm[:-1]) - else: - return perm + return Perm(perm[:-1]) + return perm -def zero_plus_skewind(perm): - """ - Return True if the permutation is of the form 1 + p where p is a +def zero_plus_skewind(perm: Perm) -> bool: + """Return True if the permutation is of the form 1 + p where p is a skew-indecomposable permutations """ + assert len(perm) > 0 return perm[0] == 0 and not fstrip(perm).skew_decomposable() -def zero_plus_sumind(perm): - """ - Return True if the permutation is of the form 1 + p where p is a +def zero_plus_sumind(perm: Perm) -> bool: + """Return True if the permutation is of the form 1 + p where p is a sum-indecomposable permutations """ + assert len(perm) > 0 return perm[0] == 0 and not fstrip(perm).sum_decomposable() -def zero_plus_perm(perm): - """ - Return True if the permutation starts with a zero. - """ +def zero_plus_perm(perm: Perm) -> bool: + """Return True if the permutation starts with a zero.""" + assert len(perm) > 0 return perm[0] == 0 -def last_sum_component(p): - """ - Return the last sum component of a permutation. - """ - n = len(p) - i = 1 - comp = set([p[n - i]]) +def last_sum_component(perm: Perm) -> Perm: + """Return the last sum component of a permutation.""" + assert len(perm) > 0 + n, i = len(perm), 1 + comp = {perm[-1]} while comp != set(range(n - i, n)): i += 1 - comp.add(p[n - i]) - return Perm.to_standard(p[n - i : n]) + comp.add(perm[n - i]) + return Perm.to_standard(perm[n - i : n]) -def last_skew_component(p): - """ - Return the last skew component of a permutation. - """ - n = len(p) +def last_skew_component(perm: Perm) -> Perm: + """Return the last skew component of a permutation.""" + assert len(perm) > 0 + n, i = len(perm), 1 i = 1 - comp = set([p[n - i]]) - while comp != set(range(0, i)): + comp = {perm[-1]} + while comp != set(range(i)): i += 1 - comp.add(p[n - i]) - return Perm.to_standard(p[n - i : n]) + comp.add(perm[n - i]) + return Perm.to_standard(perm[n - i : n]) -# Core Strategies +R_U: Perm = Perm((1, 2, 0, 3)) # 2314, row up +C_U: Perm = Perm((2, 0, 1, 3)) # 3124, colmn up +R_D: Perm = Perm((1, 3, 0, 2)) # 2413, row down +C_D: Perm = Perm((2, 0, 3, 1)) # 3142, column down class RuCuCoreStrategy(CoreStrategy): - """ - This strategies uses independent set of the up-core graph to enumerate a + """This strategies uses independent set of the up-core graph to enumerate a class as inflation of an independent set. """ - patterns_needed = frozenset([R_U, C_U]) - is_valid_extension = staticmethod(zero_plus_skewind) - corr_number = "4.3" + patterns_needed: FrozenSet[Perm] = frozenset([R_U, C_U]) + corr_number: ClassVar[str] = "4.3" + + @staticmethod + def is_valid_extension(patt: Perm) -> bool: + return zero_plus_skewind(patt) class RdCdCoreStrategy(CoreStrategy): - """ - This strategies uses independent set of the down-core graph to enumerate a + """This strategies uses independent set of the down-core graph to enumerate a class as inflation of an independent set. """ patterns_needed = frozenset([R_D, C_D]) - is_valid_extension = staticmethod(zero_plus_sumind) - corr_number = "4.6" + corr_number: ClassVar[str] = "4.6" + + @staticmethod + def is_valid_extension(patt: Perm) -> bool: + return zero_plus_sumind(patt) class RuCuRdCdCoreStrategy(CoreStrategy): + """TODO""" + patterns_needed = frozenset([R_D, C_D, R_U, C_U]) - is_valid_extension = staticmethod(zero_plus_perm) - corr_number = "5.4" + corr_number: ClassVar[str] = "5.4" + + @staticmethod + def is_valid_extension(patt: Perm) -> bool: + return zero_plus_perm(patt) class RuCuCdCoreStrategy(CoreStrategy): + """TODO""" + patterns_needed = frozenset([R_U, C_U, C_D]) - is_valid_extension = staticmethod(zero_plus_skewind) - corr_number = "6.3" + corr_number: ClassVar[str] = "6.3" + + @staticmethod + def is_valid_extension(patt: Perm) -> bool: + return zero_plus_skewind(patt) class RdCdCuCoreStrategy(CoreStrategy): + """TODO""" + patterns_needed = frozenset([R_D, C_D, C_U]) - corr_number = "7.4" + corr_number: ClassVar[str] = "7.4" @staticmethod def is_valid_extension(patt): @@ -183,8 +174,10 @@ def is_valid_extension(patt): class RdCuCoreStrategy(CoreStrategy): + """TODO""" + patterns_needed = frozenset([R_D, C_U]) - corr_number = "8.3" + corr_number: ClassVar[str] = "8.3" @staticmethod def is_valid_extension(patt): @@ -192,36 +185,47 @@ def is_valid_extension(patt): class Rd2134CoreStrategy(CoreStrategy): + """TODO""" + + _NON_INC: ClassVar[Av] = Av.from_iterable([Perm((0, 1))]) + _M_PATT: ClassVar[MeshPatt] = MeshPatt( + Perm((1, 0)), [(0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 1), (2, 2)] + ) + patterns_needed = frozenset([R_D, Perm((1, 0, 2, 3))]) - corr_number = "9.5" + corr_number: ClassVar[str] = "9.5" @staticmethod - def is_valid_extension(patt): - mp = MeshPatt( - Perm((1, 0)), [(0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 1), (2, 2)] - ) + def is_valid_extension(patt: Perm) -> bool: last_comp = last_sum_component(fstrip(patt)) return ( patt[0] == 0 - and fstrip(patt).avoids(mp) - and (last_comp not in Av([Perm((0, 1))]) or len(last_comp) == 1) + and fstrip(patt).avoids(Rd2134CoreStrategy._M_PATT) + and (last_comp not in Rd2134CoreStrategy._NON_INC or len(last_comp) == 1) ) class Ru2143CoreStrategy(CoreStrategy): + """TODO""" + + _NON_DEC: ClassVar[Av] = Av.from_iterable([Perm((1, 0))]) + _M_PATT: ClassVar[MeshPatt] = MeshPatt( + Perm((0, 1)), [(0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 1), (2, 2)] + ) + patterns_needed = frozenset([R_U, Perm((1, 0, 3, 2))]) - corr_number = "10.5" + corr_number: ClassVar[str] = "10.5" @staticmethod - def is_valid_extension(patt): - mp = MeshPatt( - Perm((0, 1)), [(0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 1), (2, 2)] - ) + def is_valid_extension(patt: Perm) -> bool: patt = fstrip(patt) - return patt.avoids(mp) and last_skew_component(patt) not in Av([Perm((1, 0))]) + return ( + patt.avoids(Ru2143CoreStrategy._M_PATT) + and last_skew_component(patt) not in Ru2143CoreStrategy._NON_DEC + ) -core_strategies = [ +core_strategies: List[Type[CoreStrategy]] = [ RuCuCoreStrategy, RdCdCoreStrategy, RuCuRdCdCoreStrategy, diff --git a/permuta/enumeration_strategies/insertion_encodable.py b/permuta/enumeration_strategies/insertion_encodable.py index d6c2845a..f742323e 100644 --- a/permuta/enumeration_strategies/insertion_encodable.py +++ b/permuta/enumeration_strategies/insertion_encodable.py @@ -1,13 +1,14 @@ -""" -Enumeration strategies related to the insertion encoding. -""" +from permuta.enumeration_strategies.abstract_strategy import EnumerationStrategy +from permuta.permutils.insertion_encodable import InsertionEncodablePerms +from permuta.permutils.symmetry import rotate_90_clockwise_set -from permuta.enumeration_strategies.abstract_strategy import ( - EnumerationStrategyWithSymmetry, -) -from permuta.permutils.insertion_encodable import is_insertion_encodable_maximum +class InsertionEncodingStrategy(EnumerationStrategy): + """Enumeration strategies related to the insertion encoding.""" -class InsertionEncodingStrategy(EnumerationStrategyWithSymmetry): - def _applies_to_symmetry(self, b): - return is_insertion_encodable_maximum(b) + def applies(self) -> bool: + return InsertionEncodablePerms.is_insertion_encodable( + self.basis + ) or InsertionEncodablePerms.is_insertion_encodable( + rotate_90_clockwise_set(self.basis) + ) diff --git a/permuta/interfaces/__init__.py b/permuta/interfaces/__init__.py deleted file mode 100644 index eeec90ab..00000000 --- a/permuta/interfaces/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .flippable import Flippable -from .patt import Patt -from .rotatable import Rotatable -from .shiftable import Shiftable - -__all__ = ["Flippable", "Patt", "Rotatable", "Shiftable"] diff --git a/permuta/interfaces/flippable.py b/permuta/interfaces/flippable.py deleted file mode 100644 index 275e90cf..00000000 --- a/permuta/interfaces/flippable.py +++ /dev/null @@ -1,25 +0,0 @@ -import abc - -ABC = abc.ABCMeta("ABC", (object,), {}) - - -class Flippable(ABC): - @abc.abstractmethod - def flip_horizontal(self): - """Return self flipped horizontally.""" - pass - - @abc.abstractmethod - def flip_vertical(self): - """Return self flipped vertically.""" - pass - - @abc.abstractmethod - def flip_diagonal(self): - """Return self flipped along the diagonal.""" - pass - - @abc.abstractmethod - def flip_antidiagonal(self): - """Return self flipped along the antidiagonal.""" - pass diff --git a/permuta/interfaces/patt.py b/permuta/interfaces/patt.py deleted file mode 100644 index e72d26d7..00000000 --- a/permuta/interfaces/patt.py +++ /dev/null @@ -1,52 +0,0 @@ -import abc - -ABC = abc.ABCMeta("ABC", (object,), {}) - - -class Patt(ABC): - def avoided_by(self, *perms): - """Check if self is avoided by perms. - - Args: - self: - A classical/mesh pattern. - perms: [permuta.Permutation] - A list of permutations. - - Returns: bool - True if and only if every permutation in perms avoids self. - """ - return all(self not in perm for perm in perms) - - def contained_in(self, *perms): - """Check if self is a pattern of perms. - - Args: - self: - A classical/mesh pattern. - perms: [permuta.Permutation] - A list of permutations. - - Returns: bool - True if and only if self is a pattern of all permutations in perms. - """ - return all(self in perm for perm in perms) - - def count_occurrences_in(self, perm): - """Count the number of occurrences of self in perm. - - Args: - self: - A classical/mesh pattern. - perm: permuta.Permutation - A permutation. - - Returns: int - The number of times self occurs in perm. - """ - return sum(1 for _ in self.occurrences_in(perm)) - - @abc.abstractmethod - def occurrences_in(self, perm): - """Find all indices of occurrences of self in perm.""" - pass diff --git a/permuta/interfaces/rotatable.py b/permuta/interfaces/rotatable.py deleted file mode 100644 index dc4ccf44..00000000 --- a/permuta/interfaces/rotatable.py +++ /dev/null @@ -1,41 +0,0 @@ -import abc - -ABC = abc.ABCMeta("ABC", (object,), {}) - - -class Rotatable(ABC): - def rotate(self, times=1): - """Return self rotated 90 degrees to the right.""" - return self._rotate(times) - - rotate_right = rotate - - def rotate_left(self, times=1): - """Return self rotated 90 degrees to the left.""" - return self._rotate(-times) - - def _rotate(self, times=1): - """Return self rotated 90 times times degrees to the right.""" - times = times % 4 - if times == 0: - return self - elif times == 1: - return self._rotate_right() - elif times == 2: - return self._rotate_180() - else: - return self._rotate_left() - - def _rotate_180(self): - """Return self rotated 180 degrees.""" - return self._rotate_right()._rotate_right() - - def _rotate_left(self): - """Return self rotated 90 degrees left.""" - return self._rotate_180()._rotate_right() - pass - - @abc.abstractmethod - def _rotate_right(self): - """Return self rotated 90 degrees right.""" - pass diff --git a/permuta/interfaces/shiftable.py b/permuta/interfaces/shiftable.py deleted file mode 100644 index e052f8c1..00000000 --- a/permuta/interfaces/shiftable.py +++ /dev/null @@ -1,37 +0,0 @@ -import abc - -ABC = abc.ABCMeta("ABC", (object,), {}) - - -class Shiftable(ABC): - @abc.abstractmethod - def shift_right(self, times=1): - """Return self shifted times steps to the right. - - If shift is negative, shifted to the left. - """ - pass - - @abc.abstractmethod - def shift_left(self, times=1): - """Return self shifted times steps to the left. - - If times is negative, shifted to the right. - """ - pass - - @abc.abstractmethod - def shift_up(self, times=1): - """Return self shifted times steps up. - - If times is negative, shifted down. - """ - pass - - @abc.abstractmethod - def shift_down(self, times=1): - """Return self shifted times steps down. - - If times is negative, shifted up. - """ - pass diff --git a/permuta/meshpatt.py b/permuta/meshpatt.py deleted file mode 100644 index 7d57a176..00000000 --- a/permuta/meshpatt.py +++ /dev/null @@ -1,1104 +0,0 @@ -import collections -import numbers -import random -from itertools import cycle, islice - -from .interfaces.flippable import Flippable -from .interfaces.patt import Patt -from .interfaces.rotatable import Rotatable -from .interfaces.shiftable import Shiftable -from .misc import DIR_EAST, DIR_NONE, DIR_NORTH, DIR_SOUTH, DIR_WEST -from .perm import Perm - -MeshPatternBase = collections.namedtuple("MeshPatternBase", ["pattern", "shading"]) - - -class MeshPatt(MeshPatternBase, Patt, Rotatable, Shiftable, Flippable): - """A mesh pattern class. - - Attributes: - pattern: - The underlying classical pattern. - shading: frozenset - The shading as a immutable set of coordinates, with lower-left as - origin. - """ - - def __new__(cls, pattern=Perm(), shading=frozenset()): - """Return a MeshPatt instance. - - Args: - cls: - The class of which an instance is requested. - pattern: or - An perm or an iterable corresponding to a legal perm. - shading: - An iterable of 2-tuples. - Raises: - TypeError: - Bad argument type. - ValueError: - Bad argument, but correct type. - """ - if not isinstance(pattern, Perm): - pattern = Perm(pattern) - if not isinstance(shading, frozenset): - shading = frozenset(shading) - return super(MeshPatt, cls).__new__(cls, pattern, shading) - - def __init__(self, pattern=Perm(), shading=frozenset()): - for coordinate in self.shading: - if not isinstance(coordinate, tuple): - message = "'{}' object is not a tuple".format(repr(coordinate)) - raise TypeError(message) - if len(coordinate) != 2: - message = "Element is not a shading coordinate: '{}'".format( - repr(coordinate) - ) - raise ValueError(message) - x, y = coordinate - if not isinstance(x, numbers.Integral): - message = "'{}' object is not an integer".format(repr(x)) - raise TypeError(message) - if not isinstance(y, numbers.Integral): - message = "'{}' object is not an integer".format(repr(y)) - raise TypeError(message) - if (not 0 <= x <= len(self.pattern)) or (not 0 <= y <= len(self.pattern)): - message = "Element out of range: '{}'".format(coordinate) - raise ValueError(message) - - # - # Methods returning new permutations - # - - def complement(self): - """Returns the complement of the mesh pattern, which has the complement - of the underlying pattern and every shading flipped across the - horizontal axis. - - Returns: - The complement of the meshpatt. - - Examples: - >>> MeshPatt(Perm((0,)), frozenset({(0, 1)})).complement() - MeshPatt(Perm((0,)), [(0, 0)]) - >>> MeshPatt(Perm((0, 2, 1)), - ... frozenset({(0, 1), (0, 2), (0, 3)})).complement() - MeshPatt(Perm((2, 0, 1)), [(0, 0), (0, 1), (0, 2)]) - """ - return MeshPatt( - self.pattern.complement(), [(x, len(self) - y) for (x, y) in self.shading] - ) - - def reverse(self): - """Returns the reversed mesh patterns, which has the underlying pattern - reversed and every shading flipped across the vertical axis. - - Returns: - The meshpatt reversed. - - Examples: - >>> MeshPatt(Perm((0,)), frozenset({(0, 1)})).reverse() - MeshPatt(Perm((0,)), [(1, 1)]) - >>> MeshPatt(Perm((2, 1, 0)), - ... frozenset({(3, 2), (2, 2), (1, 1)})).reverse() - MeshPatt(Perm((0, 1, 2)), [(0, 2), (1, 2), (2, 1)]) - """ - return MeshPatt( - self.pattern.reverse(), [(len(self) - x, y) for (x, y) in self.shading] - ) - - def inverse(self): - """Returns the inverse of the meshpatt, that is the meshpatt with the - underlying classical pattern as the inverse and the shadings hold the - same relation between the points. This is equivalent to flipping the - pattern over the diagonal. - - Returns: - The 'inverse' of the meshpatt. - - Examples: - >>> MeshPatt(Perm((0,)), frozenset({(0, 1)})).inverse() - MeshPatt(Perm((0,)), [(1, 0)]) - """ - return MeshPatt(self.pattern.inverse(), [(y, x) for (x, y) in self.shading]) - - def sub_mesh_pattern(self, indices): - """Return the mesh pattern induced by indices. - - Args: - indices: of - A list of unique indices of elements in self. - - Returns: - A mesh pattern where the pattern is the permutation induced by the - indices and a region is shaded if and only if the corresponding - region of self is fully shaded. - - Exampes: - >>> shading = frozenset({(3, 2), (1, 3), (4, 2), (0, 3), (1, 2), - ... (4, 3)}) - >>> MeshPatt(Perm((3, 2, 1, 0)), - ... shading).sub_mesh_pattern((0, 1, 3)) - MeshPatt(Perm((2, 1, 0)), [(0, 2), (1, 2), (3, 2)]) - >>> MeshPatt(Perm((2, 3, 1, 0)), - ... shading).sub_mesh_pattern((1, 2, 3)) - MeshPatt(Perm((2, 1, 0)), [(3, 2)]) - """ - indices = sorted(indices) - if not indices: - return MeshPatt() - pattern = Perm.to_standard(self.pattern[index] for index in indices) - vertical = [0] - vertical.extend(index + 1 for index in indices) - vertical.append(len(self) + 1) - horizontal = [0] - horizontal.extend(sorted(self.pattern[index] + 1 for index in indices)) - horizontal.append(len(self) + 1) - shading = frozenset( - (x, y) - for x in range(len(pattern) + 1) - for y in range(len(pattern) + 1) - if ( - self.is_shaded( - (vertical[x], horizontal[y]), - (vertical[x + 1] - 1, horizontal[y + 1] - 1), - ) - and self.is_pointfree( - (vertical[x], horizontal[y]), - (vertical[x + 1] - 1, horizontal[y + 1] - 1), - ) - ) - ) - return MeshPatt(pattern, shading) - - def flip_horizontal(self): - """Return self flipped horizontally which is equivalent to the - complement. - - Returns: - The complement of the meshpatt. - - Examples: - >>> MeshPatt(Perm((0,)), frozenset({(0, 1)})).flip_horizontal() - MeshPatt(Perm((0,)), [(0, 0)]) - >>> MeshPatt(Perm((0, 2, 1)), - ... frozenset({(0, 1), (0, 2), (0, 3)})).flip_horizontal() - MeshPatt(Perm((2, 0, 1)), [(0, 0), (0, 1), (0, 2)]) - """ - return self.complement() - - def flip_vertical(self): - """Return self flipped vertically which is equivalent to the - meshpatt reversed. - - Returns: - The meshpatt reversed. - - Examples: - >>> MeshPatt(Perm((0,)), frozenset({(0, 1)})).flip_vertical() - MeshPatt(Perm((0,)), [(1, 1)]) - >>> MeshPatt(Perm((2, 1, 0)), - ... frozenset({(3, 2), (3, 3), (0, 2)})).flip_vertical() - MeshPatt(Perm((0, 1, 2)), [(0, 2), (0, 3), (3, 2)]) - """ - return self.reverse() - - def flip_diagonal(self): - """Return self flipped along the diagonal which is equivalent to the - 'inverse' of the pattern. - - Returns: - The 'inverse' of the meshpatt. - - Examples: - >>> MeshPatt(Perm((0,)), frozenset({(0, 1)})).inverse() - MeshPatt(Perm((0,)), [(1, 0)]) - """ - return self.inverse() - - def _rotate_right(self): - """Return the pattern rotated 90 degrees to the right. - - Returns: - The meshpatt rotated 90 degrees to the right. - - Examples: - >>> MeshPatt(Perm((0,)), - ... frozenset({(0, 1), (1, 1)}))._rotate_right() - MeshPatt(Perm((0,)), [(1, 0), (1, 1)]) - """ - return MeshPatt( - self.pattern.rotate(), - set( - [ - _rotate_right(len(self.pattern), coordinate) - for coordinate in self.shading - ] - ), - ) - - def _rotate_left(self): - """Return the pattern rotated 90 degrees to the left. - - Returns: - The meshpatt rotated 90 degrees to the left. - - Examples: - >>> MeshPatt(Perm((0,)), - ... frozenset({(0, 1), (1, 1)}))._rotate_left() - MeshPatt(Perm((0,)), [(0, 0), (0, 1)]) - """ - return MeshPatt( - self.pattern.rotate(3), - set( - [ - _rotate_left(len(self.pattern), coordinate) - for coordinate in self.shading - ] - ), - ) - - def _rotate_180(self): - """Return the pattern rotated 180 degrees. - - Returns: - The meshpatt rotated 180 degrees. - - Examples: - >>> MeshPatt(Perm((0,)), frozenset({(0, 1), (1, 1)}))._rotate_180() - MeshPatt(Perm((0,)), [(0, 0), (1, 0)]) - """ - return MeshPatt( - self.pattern.rotate(2), - set( - [ - _rotate_180(len(self.pattern), coordinate) - for coordinate in self.shading - ] - ), - ) - - def all_symmetries(self): - """Return the set of all symmetries of the mesh pattern. - - Returns: - All the symmetries of a mesh pattern. - """ - symmetries = set() - current = self - symmetries.add(current) - symmetries.add(current.inverse()) - for i in range(3): - current = current._rotate_left() - symmetries.add(current) - symmetries.add(current.inverse()) - return symmetries - - def shade(self, positions): - """Returns the mesh pattern with the added shadings given by positions. - - Args: - positions: tuple or - The shading given as a single coordinate or as an iterable of - coordinates. - - Raises: - ValueError: - Bad argument, but correct type. - - Returns: - The new meshpatt with the added shadings. - """ - if isinstance(positions, tuple): - if len(positions) == 0: - raise ValueError( - "Element is not a valid shading coordinate: '{}'".format(positions) - ) - if isinstance(positions[0], numbers.Integral): - positions = set([positions]) - positions = set(positions) - - return MeshPatt(self.pattern, self.shading | positions) - - def add_point(self, pos, shade_dir=DIR_NONE, safe=True): - """Returns a mesh pattern with a point added in the box at position - pos. If shade_dir is specified adds shading in that direction. - - Args: - pos: tuple - Coordinates corresponding to a box in the pattern. - shade_dir: int - The direction which to shade within the box. - safe: bool, optional - True to check if box must not be shaded. - - Raises: - ValueError: - Bad box argument, if it is already shaded. - TypeError: - Bad argument type. - - Returns: - The mesh pattern with a point added in pos. - """ - x, y = pos - if not isinstance(x, numbers.Integral) or not isinstance(y, numbers.Integral): - message = "Element is not a tuple of integers: '{}'".format(pos) - raise TypeError(message) - if safe and (x, y) in self.shading: - message = "Can not add point to shaded pos: '{}'" - raise ValueError(message) - - perm = [v if v < y else (v + 1) for v in self.pattern] - nperm = perm[:x] + [y] + perm[x:] - nshading = set() - for (a, b) in self.shading: - if a < x: - nx = [a] - elif a == x: - nx = [a, a + 1] - else: - nx = [a + 1] - - if b < y: - ny = [b] - elif b == y: - ny = [b, b + 1] - else: - ny = [b + 1] - - for na in nx: - for nb in ny: - nshading.add((na, nb)) - - if shade_dir == DIR_EAST: - nshading.add((x + 1, y)) - nshading.add((x + 1, y + 1)) - elif shade_dir == DIR_NORTH: - nshading.add((x, y + 1)) - nshading.add((x + 1, y + 1)) - elif shade_dir == DIR_WEST: - nshading.add((x, y)) - nshading.add((x, y + 1)) - elif shade_dir == DIR_SOUTH: - nshading.add((x, y)) - nshading.add((x + 1, y)) - - return MeshPatt(Perm(nperm), nshading) - - def add_increase(self, pos): - """Adds an increasing pattern (0, 1) into the given coordinate. - - Args: - pos: tuple - The coordinate of the position to insert (0, 1) into. - - Returns: - The new pattern with the increased pattern inserted into pos. - - Examples: - >>> MeshPatt((0,)).add_increase((0, 0)) - MeshPatt(Perm((0, 1, 2)), []) - """ - x, y = pos - return self.add_point((x, y)).add_point((x + 1, y + 1)) - - def add_decrease(self, pos): - """Adds an decreasing pattern (1, 0) into the given coordinate. - - Args: - pos: tuple - The coordinate of the position to insert (1, 0) into. - - Returns: - The new pattern with the decreasing pattern inserted into pos. - - Examples: - >>> MeshPatt((0,)).add_decrease((0, 0)) - MeshPatt(Perm((1, 0, 2)), []) - """ - x, y = pos - return self.add_point((x, y)).add_point((x + 1, y)) - - # - # Occurrence/Avoidance/Containment methods - # - - def contains(self, *patts): - """Check if self contains patts. - - Args: - self: - A MeshPatt. - patts: argument list - Classical/mesh patterns. - - Returns: - True if and only if all patterns in patts are contained in self. - """ - return all(patt in self for patt in patts) - - def avoids(self, *patts): - """Check if self avoids patts. - - Args: - self: - A MeshPatt. - patts: argument list - Classical/mesh patterns. - - Returns: - True if and only if self avoids all patterns in patts. - """ - return all(patt not in self for patt in patts) - - def occurrences_in(self, patt): - """ - Find all indices of self in patt. - - Args: - self: - The mesh pattern whose occurrences are to be found. - patt: or - The patt to search for occurrences in. - - Yields: numbers.Integral - The indices of the occurrences of self in perm. Each yielded - element l is a list of integer indices of the pattern `patt` such - that: - - If `patt` is a classical pattern: - self.pattern == permuta.Perm.to_standard([patt[i] for i in l]) - and that no element not in the occurrence falls within - self.shading. - - If `patt` is a mesh pattern: - The list of indices describe a classical occurrence of - `patt.patt` as described above and the shading of the submesh - pattern induced by this occurrence is a superset of shading of - `self.shading`. - """ - if isinstance(patt, Perm): - for candidate_indices in self.pattern.occurrences_in(patt): - candidate = [patt[index] for index in candidate_indices] - x = 0 - for element in patt: - if element in candidate: - x += 1 - continue - y = sum( - 1 - for candidate_element in candidate - if candidate_element < element - ) - if (x, y) in self.shading: - break - else: - # No unused point fell within shading - yield list(candidate_indices) - elif isinstance(patt, MeshPatt): - for occurrence in self.occurrences_in(patt.pattern): - candidate_sub_mesh_patt = patt.sub_mesh_pattern(occurrence) - if set(self.shading) <= set(candidate_sub_mesh_patt.shading): - yield list(occurrence) - else: - raise ValueError("Variable 'patt' needs to be either a Perm or " "MeshPatt") - - # - # Other methods - # - - def is_shaded(self, lower_left, upper_right=None): - """Check if a region of the grid is shaded. - - Args: - self: - A mesh pattern. - lower_left: (int, int) - A shading coordinate of self. - upper_right: (int, int) - A shading coordinate of self. - - Raises: - ValueError: - Bad argument, but correct type. - - Returns: bool - If upper_right is None, then True if and only if lower_left is - shaded; otherwise, True if and only if all regions (x, y) for x in - the range lower_left[0] to upper_right[0] (inclusive) and for y in - the range lower_left[1] to upper_right[1] (inclusive) are shaded. - """ - if (lower_left[0] < 0 or lower_left[1] < 0) or ( - lower_left[0] > len(self) or lower_left[1] > len(self) - ): - message = "Element out of range: '{}'".format(lower_left) - raise ValueError(message) - elif upper_right is None: - return lower_left in self.shading - elif (upper_right[0] < 0 or upper_right[1] < 0) or ( - upper_right[0] > len(self) or upper_right[1] > len(self) - ): - message = "Element out of range: '{}'".format(upper_right) - raise ValueError(message) - elif lower_left[0] > upper_right[0] or lower_left[1] > upper_right[1]: - message = ( - "Elements do not correspond to lower left and upper" - " right of a non-empty rectangle: '{}' '{}'" - ).format(lower_left, upper_right) - raise ValueError(message) - else: - left, lower = lower_left - right, upper = upper_right - for x in range(left, right + 1): - for y in range(lower, upper + 1): - if (x, y) not in self.shading: - return False - return True - - def is_pointfree(self, lower_left, upper_right): - """Check if a region in the grid has no points. - - Args: - self: - A mesh pattern. - lower_left: (int, int) - A point coordinate of self. - upper_right: (int, int) - A point coordinate of self. - - Raises: - ValueError: - Bad argument, but correct type. - - Returns: bool - True if and only if all points self[x] for x in the range - lower_left[0] + 1 to upper_right[0] - 1 (inclusive) have values - less than lower_left[1] or greater than or equal to upper_right[1]. - """ - if (lower_left[0] < 0 or lower_left[1] < 0) or ( - lower_left[0] > len(self) or lower_left[1] > len(self) - ): - message = "Element out of range: '{}'".format(lower_left) - raise ValueError(message) - elif (upper_right[0] < 0 or upper_right[1] < 0) or ( - upper_right[0] > len(self) or upper_right[1] > len(self) - ): - message = "Element out of range: '{}'".format(upper_right) - raise ValueError(message) - elif lower_left[0] > upper_right[0] or lower_left[1] > upper_right[1]: - message = ( - "Elements do not correspond to lower left and upper " - "right of a non-empty rectangle: '{}' '{}'" - ).format(lower_left, upper_right) - raise ValueError(message) - else: - left, lower = lower_left - right, upper = upper_right - for x in range(left, right): - if lower <= self.pattern[x] < upper: - return False - return True - - def _can_shade(self, pos): - """Checks if the box at pos can be shaded according to the Shading - Lemma(northeast). - - Args: - pos: tuple - The coordinates of the box to check. - - Returns: tuple - The point that is 'moved' to shade the box at pos. - """ - i, j = pos - # if pos is shaded - if (i, j) in self.shading: - return False - # if pos does not have point in lower-left - if i - 1 < 0 or self.pattern[i - 1] != j - 1: - return False - # if box in south-west direction is shaded - if (i - 1, j - 1) in self.shading: - return False - c = 0 - # only one of the boxes to the left and down can be shaded - if (i, j - 1) in self.shading: - c += 1 - if (i - 1, j) in self.shading: - c += 1 - if c == 2: - return False - - # if the box on the lower side of the horizontal line is shaded then - # the upper one must be shaded - for k in range(len(self.pattern) + 1): - if k == i - 1 or k == i: - continue - if (k, j - 1) in self.shading and (k, j) not in self.shading: - return False - # if the box on the left side of the vertical line is shaded then the - # right one must be shaded - for k in range(len(self.pattern) + 1): - if k == j - 1 or k == j: - continue - if (i - 1, k) in self.shading and (i, k) not in self.shading: - return False - return (i - 1, j - 1) - - def can_shade(self, pos): - """Returns whether it is possible to shade the box at position pos - according to the Shading Lemma. Every direction is checked and the list - of the values of the adjacent points to the box that can be used is - returned. - - Args: - pos: tuple - The position of the box to check. - - Returns: list - The values of the points in the permutation adjacent to the box at - pos that can be used to shade the box. - - Examples: - >>> MeshPatt((0,),[(0, 0)]).can_shade((1, 1)) - [] - >>> MeshPatt((1, 2, 0), - ... [(2, 2),(3, 0),(3, 2),(3, 3)]).can_shade((1, 2)) - [1, 2] - """ - mp = self - poss = [] - for i in range(4): - ans = mp._can_shade(pos) - if ans: - for j in range((-i) % 4): - ans = _rotate_right(len(self.pattern) - 1, ans) - poss.append(ans[1]) - mp = mp.rotate_right() - pos = _rotate_right(len(self.pattern), pos) - return poss - - def _can_simul_shade(self, pos1, pos2): - if pos1[1] < pos2[1]: - pos1, pos2 = pos2, pos1 - # There must be a point at (pos1[0]-1, pos1[1]-1) - if pos1[0] == 0 or self.pattern[pos1[0] - 1] != pos1[1] - 1: - return False - if pos1[0] != pos2[0] or pos1[1] - 1 != pos2[1]: - # the pos1 must be directly above pos2 - return False - - if pos1 in self.shading: - return False - if pos2 in self.shading: - return False - # The boxes surrounding the point (pos1[0]-1, pos1[1] - 1) must be - # empty - if (pos1[0] - 1, pos1[1]) in self.shading: - return False - if (pos2[0] - 1, pos2[1]) in self.shading: - return False - - # Check the boxes on each side of the vertical line of pos1[0], if the - # box on the left side is shaded then the box on the right side must be - # shaded. - for y in range(len(self.pattern) + 1): - if y == pos1[1] or y == pos1[1] - 1: - continue - if (pos1[0] - 1, y) in self.shading and (pos1[0], y) not in self.shading: - return False - - # Check the boxes on each side of the horizontal line of pos1[1]-1, - # they must match. - for x in range(len(self.pattern) + 1): - if x == pos1[0] or x == pos1[0] - 1: - continue - if ((x, pos1[1]) in self.shading) != ((x, pos2[1]) in self.shading): - return False - return (pos1[0] - 1, pos1[1] - 1) - - def can_simul_shade(self, pos1, pos2): - """Returns whether it is possible to shade the boxes at positions pos1, - pos2 according to the Shading Lemma. Every direction is checked and the - list of the values of the adjacent points to the box that can be used - is returned. - - Args: - pos1: tuple - The position of the first box to check. - pos2: tuple - The position of the second box to check. - - Returns: list - The values of the points in the permutation adjacent to the boxes - at pos1 and pos2 that can be used to shade the box. - - Examples: - - """ - mp = self - poss = [] - for i in range(4): - ans = mp._can_simul_shade(pos1, pos2) - if ans: - for j in range((-i) % 4): - ans = _rotate_right(len(self.pattern) - 1, ans) - poss.append(ans[1]) - mp = mp.rotate_right() - pos1 = _rotate_right(len(self.pattern), pos1) - pos2 = _rotate_right(len(self.pattern), pos2) - return poss - - can_shade2 = can_simul_shade - - def shadable_boxes(self): - """Returns a dictionary of all tuples of shadable boxes with the - shading lemma, with the keys as the points used to shade the tuples of - boxes. - - Returns: dict - Dictionary with keys as points and values as tuples of boxes. - - Examples: - >>> sh = {(3, 2), (3, 0), (4, 2), (1, 0), (0, 3), (1, 2), (0, 4), - ... (0, 2)} - >>> m = MeshPatt(Perm((0, 3, 1, 2)), sh) - >>> dict(m.shadable_boxes()) - {0: [((0, 0),)], 3: [((1, 3),), ((1, 3), (1, 4)), ((1, 4),)]} - """ - shadable = collections.defaultdict(list) - for i in range(len(self) + 1): - for j in range(len(self) + 1): - points = self.can_shade((i, j)) - for p in points: - shadable[p].append(((i, j),)) - if i < len(self): - points = self.can_simul_shade((i, j), (i + 1, j)) - for p in points: - shadable[p].append(((i, j), (i + 1, j))) - if j < len(self): - points = self.can_simul_shade((i, j), (i, j + 1)) - for p in points: - shadable[p].append(((i, j), (i, j + 1))) - return shadable - - def non_pointless_boxes(self): - """ Returns the coordinates of the boxes that have a point on their - boundaries in one of their four corners. - - Returns: set - The set of boxes with points in one of the corners. - - Examples: - >>> m = MeshPatt(Perm((0, 1)), frozenset({(0, 1), (2, 0), (0, 2)})) - >>> sorted(m.non_pointless_boxes()) - [(0, 0), (0, 1), (1, 0), (1, 1), (1, 2), (2, 1), (2, 2)] - """ - res = [] - for i, v in enumerate(self.pattern): - res.extend([(i + 1, v + 1), (i, v + 1), (i, v), (i + 1, v)]) - return set(res) - - def has_anchored_point(self): - """Checks if the mesh pattern has any point anchored to the boundary. - Returns a tuple (right, top, left, bottom) where each value represent - whether the point is anchored to the corresponding direction. - - Returns: tuple - Each boolean in the tuple tells whether the mesh pattern is - anchored to the corresponding direction. - - Examples: - >>> m = MeshPatt(Perm((0, 1)), {(0, 0), (1, 0), (2, 0), (1, 1)}) - >>> m.has_anchored_point() - (False, False, False, True) - """ - right = all((len(self), i) in self.shading for i in range(len(self) + 1)) - top = all((i, len(self)) in self.shading for i in range(len(self) + 1)) - left = all((0, i) in self.shading for i in range(len(self) + 1)) - bottom = all((i, 0) in self.shading for i in range(len(self) + 1)) - return (right, top, left, bottom) - - def rank(self): - """Computes the rank of the mesh pattern, the bit string of the - shadings interpreted as an integer. - - Returns: numbers.Integral - The rank of the mesh pattern. - - Examples: - >>> sh = {(0, 0), (3, 0), (0, 2), (2, 1), (2, 3), (1, 2), (3, 3), - ... (3, 1), (1, 1)} - >>> m = MeshPatt(Perm((1, 0, 2)), sh) - >>> rank = m.rank() - >>> rank - 47717 - >>> bin(rank) - '0b1011101001100101' - """ - res = 0 - for (x, y) in self.shading: - res |= 1 << (x * (len(self.pattern) + 1) + y) - return res - - def ascii_plot(self, cell_size=1): - """Return an ascii plot of the given Permutation. - - Args: - self: - A perm. - cell_size: - The size of the cell of the grid - - Returns: - The ascii art string of the permutation - - Examples: - >>> print(MeshPatt((0,1,2), [(2,1), (3,0), (3,1), (3,2), (3,3)] - ... ).ascii_plot()) - | | |▒ - -+-+-●- - | | |▒ - -+-●-+- - | |▒|▒ - -●-+-+- - | | |▒ - """ - - def roundrobin(*iterables): - "roundrobin('ABC', 'D', 'EF') --> A D E B F C" - # Recipe credited to George Sakkis - num_active = len(iterables) - nexts = cycle(iter(it).__next__ for it in iterables) - while num_active: - try: - for next in nexts: - yield next() - except StopIteration: - # Remove the iterator we just exhausted from the cycle. - num_active -= 1 - nexts = cycle(islice(nexts, num_active)) - - def fill_char(c): - shading_char = "\u2592" - if c in self.shading: - return shading_char - elif c[0] == len(self): - return "" - else: - return " " - - if cell_size < 1: - raise ValueError("`cell_size` must be positive") - empty_char = "+" - point_char = "\u25cf" - n = self.pattern.__len__() - array = [[empty_char for i in range(n)] for j in range(n)] - for i in range(n): - array[self.pattern[i]][i] = point_char - array.reverse() - lines = [("-" * cell_size).join([""] + line + [""]) + "\n" for line in array] - vlines = [ - ("|".join(fill_char((j, i)) * cell_size for j in range(n + 1)) + "\n") - * cell_size - for i in range(n + 1) - ] - vlines.reverse() - s = "".join(roundrobin(vlines, lines)) - return s[:-1] - - def to_tikz(self): - """ - Return the tikz code to plot the mesh pattern. The tikz code requires - the TikZ library patter. - - Returns: str - The LaTeX code for the TikZ figure of the pattern. - """ - s = r"\begin{tikzpicture}" - s += r"[scale=.3,baseline=(current bounding box.center)]" - s += "\n\t" - s += r"\foreach \x in {1,...," + str(len(self)) + "} {" - s += "\n\t\t" - s += r"\draw[ultra thin] (\x,0)--(\x," + str(len(self) + 1) + "); %vline" - s += "\n\t\t" - s += r"\draw[ultra thin] (0,\x)--(" + str(len(self) + 1) + r",\x); %hline" - s += 2 * "\n\t" - s += r"}" - print(self.shading) - for cell in sorted(self.shading): - s += "\n\t" - s += r"\fill[pattern color = black!75, pattern=north east lines] " - s += str(cell) + r" rectangle +(1,1);" - print(cell) - for (i, e) in enumerate(self.pattern): - s += "\n\t" - s += ( - r"\draw[fill=black] (" - + str(i + 1) - + "," - + str(e + 1) - + ") circle (5pt);" - ) - s += "\n" - s += r"\end{tikzpicture}" - return s - - # - # Static methods - # - - @staticmethod - def unrank(pattern, number): - """Return the number-th shading of pattern. - - Args: - pattern: or - An perm or an iterable corresponding to a legal perm. - number: - An integer of which binary representation corresponds to a - legal shading. - Raises: - TypeError: - Bad argument type. - ValueError: - Bad argument, but correct type. - Examples: - >>> bin(22563) - '0b101100000100011' - >>> MeshPatt.unrank((0, 1, 2), 386) - MeshPatt(Perm((0, 1, 2)), [(0, 1), (1, 3), (2, 0)]) - """ - if not isinstance(number, numbers.Integral): - message = "'{}' object is not an integer".format(repr(number)) - raise TypeError(message) - if not (0 <= number < 2 ** ((len(pattern) + 1) ** 2)): - message = "Element out of range: '{}'".format(number) - raise ValueError(message) - bound = len(pattern) + 1 - shading = set( - (index // bound, index % bound) - for index, bit in enumerate(reversed(bin(number)[2:])) - if bit == "1" - ) - return MeshPatt(pattern, shading) - - @staticmethod - def random(length): - """Return a random mesh pattern of the specified length. - - Args: - length: - The length of the random pattern. - - Examples: - >>> mp = set(MeshPatt.unrank(Perm((0, )), i) for i in range(0, 16)) - >>> MeshPatt.random(1) in mp - True - >>> len(MeshPatt.random(4)) - 4 - """ - return MeshPatt.unrank( - Perm.random(length), random.randint(0, 2 ** ((length + 1) ** 2) - 1) - ) - - # - # Dunder methods - # - - def __repr__(self): # pragma: no cover - return "MeshPatt({}, {})".format(repr(self.pattern), sorted(self.shading)) - - def __str__(self): - return "({}, {})".format(self.pattern, sorted(self.shading)) - - def __len__(self): - return len(self.pattern) - - def __bool__(self): - return bool(self.pattern) or bool(self.shading) - - def __lt__(self, other): - return (self.pattern, sorted(self.shading)) < ( - other.pattern, - sorted(other.shading), - ) - - def __le__(self, other): - return (self.pattern, sorted(self.shading)) <= ( - other.pattern, - sorted(other.shading), - ) - - def __gt__(self, other): - return other.__lt__(self) - - def __ge__(self, other): - return other.__le__(self) - - def __contains__(self, patt): - """Check if self contains patt. - - - Args: - self: - A perm. - patt: - A classical/mesh pattern. - - Returns: - True if and only if the pattern patt is contained in self. - """ - return any(True for _ in patt.occurrences_in(self)) - - -def _rotate_right(length, element): - """Rotate an element of the Cartesian product of {0,...,length} clockwise. - - Args: - length: - The size of the area(of mesh). - element: tuple - A cartesiean coordinate within [0,...,length]x[0,...,length] - - Returns: tuple - The input point rotated within the box of size length x length. - """ - x, y = element - return (y, length - x) - - -def _rotate_left(length, element): - """Rotate an element of the Cartesian product of {0,...,length} - counterclockwise. - - Args: - length: - The size of the area(of mesh). - element: tuple - A cartesiean coordinate within [0,...,length]x[0,...,length] - - Returns: tuple - The input point rotated within the box of size length x length. - """ - x, y = element - return (length - y, x) - - -def _rotate_180(length, element): - """Rotate an element of the Cartesian product of {0,...,length} - 180-degrees. - - Args: - length: - The size of the area(of mesh). - element: tuple - A cartesiean coordinate within [0,...,length]x[0,...,length] - - Returns: tuple - The input point rotated within the box of size length x length. - """ - x, y = element - return (length - x, length - y) diff --git a/permuta/meshpattset.py b/permuta/meshpattset.py deleted file mode 100644 index f4d195c4..00000000 --- a/permuta/meshpattset.py +++ /dev/null @@ -1,40 +0,0 @@ -from .meshpatt import MeshPatt -from .perm import Perm -from .permset import PermSet - - -def gen_meshpatts(length, patt=None): - """Generates all mesh patterns of length n. If the classical pattern is - specified then only the mesh patterns with the classical pattern as the - underlying pattern are generated. - - Args: - length: - The length(size) of the mesh pattern. - patt: , or - - - Yields: - Every permutation of the specified length with the specified classical - pattern or each of them if the pattern is not specified. - - Examples: - >>> mps = list(gen_meshpatts(0)) - >>> len(mps) - 2 - >>> mps[0] - MeshPatt(Perm(()), []) - >>> mps[1] - MeshPatt(Perm(()), [(0, 0)]) - >>> len(list(gen_meshpatts(2, (1, 2)))) - 512 - """ - if patt is None: - for p in PermSet(length): - for i in range(2 ** ((length + 1) ** 2)): - yield MeshPatt.unrank(p, i) - else: - if not isinstance(patt, Perm): - patt = Perm(patt) - for i in range(2 ** ((length + 1) ** 2)): - yield MeshPatt.unrank(patt, i) diff --git a/permuta/misc/__init__.py b/permuta/misc/__init__.py index 2c2b89e7..39811c91 100644 --- a/permuta/misc/__init__.py +++ b/permuta/misc/__init__.py @@ -1,16 +1,4 @@ -from . import checking -from .algorithm_x import AlgorithmX -from .counting import binomial, catalan, factorial -from .dancing_links import DancingLinks -from .exact_cover import exact_cover, exact_cover_smallest -from .iterable_floor_and_ceiling import left_floor_and_ceiling, right_floor_and_ceiling -from .ordered_set_partitions import ( - ordered_set_partitions, - ordered_set_partitions_no_cache, -) -from .progressbar import ProgressBar -from .ranges import cyclic_range, modulo_range -from .triemap import TrieMap +from .display import HTMLViewer from .union_find import UnionFind DIR_EAST = 0 @@ -21,22 +9,7 @@ DIRS = [DIR_EAST, DIR_NORTH, DIR_WEST, DIR_SOUTH] __all__ = [ - "checking", - "AlgorithmX", - "binomial", - "catalan", - "factorial", - "DancingLinks", - "exact_cover", - "exact_cover_smallest", - "left_floor_and_ceiling", - "right_floor_and_ceiling", - "ordered_set_partitions", - "ordered_set_partitions_no_cache", - "ProgressBar", - "cyclic_range", - "modulo_range", - "TrieMap", + "HTMLViewer", "UnionFind", "DIRS", "DIR_EAST", @@ -45,7 +18,3 @@ "DIR_SOUTH", "DIR_NONE", ] - - -def signum(n): - return 1 if n > 0 else -1 if n < 0 else 0 diff --git a/permuta/misc/algorithm_x.py b/permuta/misc/algorithm_x.py deleted file mode 100644 index 5091e041..00000000 --- a/permuta/misc/algorithm_x.py +++ /dev/null @@ -1,147 +0,0 @@ -class Node: - def __init__(self, row, col): - self.left = None - self.right = None - self.u = None - self.d = None - self.p = None - self.row = row - self.col = col - self.size = 0 - - -def cover(c): - c.right.left = c.left - c.left.right = c.right - i = c.d - while i != c: - j = i.right - while j != i: - j.d.u = j.u - j.u.d = j.d - j.p.size -= 1 - j = j.right - i = i.d - - -def uncover(c): - i = c.u - while i != c: - j = i.left - while j != i: - j.p.size += 1 - j.u.d = j - j.d.u = j.u.d - j = j.left - i = i.u - c.left.right = c - c.right.left = c.left.right - - -class AlgorithmX: - def __init__(self, rows, cols, solution_callback): - assert rows > 0 and cols > 0 - self.rows = rows - self.cols = cols - self.head = None - self.arr = [[False for c in range(self.cols)] for r in range(self.rows)] - self.sol = [0 for i in range(self.rows)] - self.solution_callback = solution_callback - self.can_continue = False - - def set_value(self, row, col, val=True): - self.arr[row][col] = val - - def setup(self): - ptr = [ - [ - Node(i, j) if i == self.rows or self.arr[i][j] else None - for j in range(self.cols) - ] - for i in range(self.rows + 1) - ] - for i in range(self.rows + 1): - for j in range(self.cols): - if ptr[i][j] is None: - continue - - ni = i + 1 - nj = j + 1 - - while True: - if ni == self.rows + 1: - ni = 0 - if ni == self.rows or self.arr[ni][j]: - break - ni += 1 - - ptr[i][j].d = ptr[ni][j] - ptr[ni][j].u = ptr[i][j] - - while True: - if nj == self.cols: - nj = 0 - if i == self.rows or self.arr[i][nj]: - break - nj += 1 - - ptr[i][j].right = ptr[i][nj] - ptr[i][nj].left = ptr[i][j] - - self.head = Node(self.rows, -1) - self.head.right = ptr[self.rows][0] - ptr[self.rows][0].left = self.head - self.head.left = ptr[self.rows][self.cols - 1] - ptr[self.rows][self.cols - 1].right = self.head - - for j in range(self.cols): - cnt = -1 - for i in range(self.rows + 1): - if ptr[i][j] is not None: - cnt += 1 - ptr[i][j].p = ptr[self.rows][j] - ptr[self.rows][j].size = cnt - - def search(self, k=0, at_most=None): - if self.head == self.head.right: - res = [self.sol[i] for i in range(k)] - res = sorted(res) - return self.solution_callback(res) - - if at_most is not None and k >= at_most: - self.can_continue = True - return - - c = self.head.right - tmp = self.head.right - while tmp != self.head: - if tmp.size < c.size: - c = tmp - tmp = tmp.right - - if c == c.d: - return False - - cover(c) - - found = False - r = c.d - while not found and r != c: - self.sol[k] = r.row - - j = r.right - while j != r: - cover(j.p) - j = j.right - - found = self.search(k + 1, at_most) - - j = r.left - while j != r: - uncover(j.p) - j = j.left - - r = r.d - - uncover(c) - return found diff --git a/permuta/misc/checking.py b/permuta/misc/checking.py deleted file mode 100644 index c857f8bf..00000000 --- a/permuta/misc/checking.py +++ /dev/null @@ -1,46 +0,0 @@ -import numbers - -INDEX_TYPE_ERROR_MESSAGE = "'{}' object is not a valid index" -INDEX_VALUE_ERROR_MESSAGE = "{} is not a valid index" - - -def index(obj, maximum=None): - """Check if an object is an index and raise exceptions if it isn't. - - Args: - obj: - The object to be checked. - maximum: - The number the object is not to exceed if it is a number. - - Raises: - TypeError: - Object is not an integral number. - ValueError: - Object is an integral number, but not a valid index. - - Examples: - >>> index(4) - >>> index(0) - >>> index(-3) - Traceback (most recent call last): - ... - ValueError: -3 is not a valid index - >>> index(21, 30) - >>> index(0.3) - Traceback (most recent call last): - ... - TypeError: '0.3' object is not a valid index - >>> index(333, 30) - Traceback (most recent call last): - ... - ValueError: 333 is not a valid index - - """ - if isinstance(obj, numbers.Integral): - if obj >= 0 and (maximum is None or obj <= maximum): - return # Index is good - else: - raise ValueError(INDEX_VALUE_ERROR_MESSAGE.format(obj)) - else: - raise TypeError(INDEX_TYPE_ERROR_MESSAGE.format(repr(obj))) diff --git a/permuta/misc/counting.py b/permuta/misc/counting.py deleted file mode 100644 index 5167b54b..00000000 --- a/permuta/misc/counting.py +++ /dev/null @@ -1,20 +0,0 @@ -def factorial(n): - res = 1 - for i in range(2, n + 1): - res *= i - return res - - -def binomial(n, k): - if k > n: - return 0 - if n - k < k: - k = n - k - res = 1 - for i in range(1, k + 1): - res = res * (n - (k - i)) // i - return res - - -def catalan(n): - return binomial(2 * n, n) // (n + 1) diff --git a/permuta/misc/dancing_links.py b/permuta/misc/dancing_links.py deleted file mode 100644 index b5eb0d37..00000000 --- a/permuta/misc/dancing_links.py +++ /dev/null @@ -1,72 +0,0 @@ -class Node(object): - def __init__(self, value, prev=None, next=None): - self.value = value - self.prev = prev - self.next = next - - if self.prev is not None: - self.prev.next = self - - if self.next is not None: - self.next.prev = self - - def __repr__(self): - return "Node(%s)" % repr(self.value) - - -class DancingLinks(object): - def __init__(self, lst=None): - self.front = None - self.back = None - self.count = 0 - - if lst is not None: - for value in lst: - self.append(value) - - def append(self, value): - self.count += 1 - self.back = Node(value, self.back) - if self.front is None: - self.front = self.back - - def erase(self, node): - self.count -= 1 - - if node.prev is not None: - node.prev.next = node.next - - if node.next is not None: - node.next.prev = node.prev - - if node == self.front: - self.front = node.next - - if node == self.back: - self.back = node.prev - - def restore(self, node): - self.count += 1 - - if node.prev is not None: - node.prev.next = node - - if node.next is not None: - node.next.prev = node - - if node.next == self.front: - self.front = node - - if node.prev == self.back: - self.back = node - - def __len__(self): - return self.count - - def __repr__(self): - cur = self.front - lst = [] - while cur is not None: - lst.append(cur) - cur = cur.next - return "DancingLinks([%s])" % ", ".join(map(repr, lst)) diff --git a/permuta/misc/display.py b/permuta/misc/display.py new file mode 100644 index 00000000..45538946 --- /dev/null +++ b/permuta/misc/display.py @@ -0,0 +1,37 @@ +import os +import tempfile +import threading +import time +import webbrowser +from typing import ClassVar + + +class HTMLViewer: + """A class for opening html text in browser.""" + + _THREAD_WAIT_TIME: ClassVar[float] = 5 # seconds + + @staticmethod + def _remove_file_thread(fname: str) -> None: + time.sleep(HTMLViewer._THREAD_WAIT_TIME) + if os.path.exists(fname): + os.remove(fname) + + @staticmethod + def _remove_file(fname: str) -> None: + threading.Thread(target=HTMLViewer._remove_file_thread, args=(fname,)).start() + + @staticmethod + def open_html(html: str) -> None: + """Open and render html string in browser.""" + with tempfile.NamedTemporaryFile( + "r+", suffix=".html", delete=False + ) as html_file: + html_file.write(html) + webbrowser.open_new_tab(f"file://{html_file.name}") + HTMLViewer._remove_file(html_file.name) + + @staticmethod + def open_svg(svg: str) -> None: + """Open and render svg image string in browser.""" + HTMLViewer.open_html(f"{svg}") diff --git a/permuta/misc/exact_cover.py b/permuta/misc/exact_cover.py deleted file mode 100644 index 19e50706..00000000 --- a/permuta/misc/exact_cover.py +++ /dev/null @@ -1,75 +0,0 @@ -from .algorithm_x import AlgorithmX - - -def exact_cover(bss, validcnt, max_cnt, ignore_first, allow_overlap_in_first): - - # curcover = [] - # ball = (1 << validcnt) - 1 - # care = ball & ~((1 << ignore_first) - 1) - # def bt(at, left, done): - # if (done & care) == (ball & care): - # yield list(curcover) - # elif not (left == 0 or at == len(bss)): - # if (bss[at] & done & - # (care if allow_overlap_in_first else ball)) == 0: - # curcover.append(at) - # for res in bt(at + 1, left - 1, done | bss[at]): - # yield res - - # curcover.pop() - - # for res in bt(at + 1, left, done): - # yield res - - # sols1 = [] - sols2 = [] - - # for res in bt(0, max_cnt, 0): - # sols1.append(res) - - def handle_solution(sol): - sols2.append(sol) - return False - - ec = AlgorithmX(len(bss), validcnt - ignore_first, handle_solution) - - for i in range(len(bss)): - bs = bss[i] >> ignore_first - for j in range(validcnt - ignore_first): - if (bs & (1 << j)) != 0: - ec.set_value(i, j, True) - - ec.setup() - ec.search(at_most=max_cnt) - - # print(sols1) - # print(sols2) - # assert sols1 == sols2 - - return sols2 - - -def exact_cover_smallest(bss, validcnt, max_cnt, ignore_first, allow_overlap_in_first): - sols = [] - - def handle_solution(sol): - sols.append(sol) - return False - - ec = AlgorithmX(len(bss), validcnt - ignore_first, handle_solution) - for i in range(len(bss)): - bs = bss[i] >> ignore_first - for j in range(validcnt - ignore_first): - if (bs & (1 << j)) != 0: - ec.set_value(i, j, True) - - ec.setup() - d = 1 - while max_cnt is None or d <= max_cnt: - ec.can_continue = False - ec.search(at_most=d) - if sols or not ec.can_continue: - break - d += 1 - - return sols diff --git a/permuta/misc/iterable_floor_and_ceiling.py b/permuta/misc/iterable_floor_and_ceiling.py deleted file mode 100644 index 8b7e5e42..00000000 --- a/permuta/misc/iterable_floor_and_ceiling.py +++ /dev/null @@ -1,66 +0,0 @@ -import collections - -FloorAndCeiling = collections.namedtuple("FloorAndCeiling", ["floor", "ceiling"]) - - -def left_floor_and_ceiling(iterable, default_floor=None, default_ceiling=None): - """Find the left floor and ceiling indices of iterable. - - Define left_floor of an element in a sequence to be the index of the - greatest smaller element to the left of said element, or default_floor - if there is none. Similarly define default_ceiling. This function yields - a tuple (left_floor, left_ceiling) for each element of iterable. - - Args: - iterable: - An iterable of totally ordered unique elements. - default_floor: - default_ceiling: - - Yields: (int, int) - The i-th yielded tuple is the left floor and ceiling indices of - the i-th element of the iterable. The tuples are named tuples - with floor and ceiling attributes. - """ - # TODO: Define behaviour for duplicate elements - dq = collections.deque() - smallest = None - biggest = None - index = 0 - for element in iterable: - if index == 0: - dq.append((element, index)) - smallest = element - biggest = element - yield FloorAndCeiling(default_floor, default_ceiling) - else: - if element <= smallest: - # Rotate until smallest element is at front - while dq[0][0] != smallest: - dq.rotate(-1) - yield FloorAndCeiling(default_floor, dq[0][1]) - dq.appendleft((element, index)) - smallest = element - elif element >= biggest: - # Rotate until biggest element is at end - while dq[-1][0] != biggest: - dq.rotate(-1) - yield FloorAndCeiling(dq[-1][1], default_ceiling) - dq.append((element, index)) - biggest = element - else: - while not dq[-1][0] <= element <= dq[0][0]: - dq.rotate() - yield FloorAndCeiling(dq[-1][1], dq[0][1]) - dq.appendleft((element, index)) - index += 1 - - -def right_floor_and_ceiling(iterable, default_floor=None, default_ceiling=None): - """The right counterpart of left_floor_and_ceiling.""" - # TODO: Implement nicely - result = left_floor_and_ceiling( - reversed(list(iterable)), default_floor, default_ceiling - ) - for fac in reversed(list(result)): - yield fac diff --git a/permuta/misc/ordered_set_partitions.py b/permuta/misc/ordered_set_partitions.py deleted file mode 100644 index d989f3bf..00000000 --- a/permuta/misc/ordered_set_partitions.py +++ /dev/null @@ -1,107 +0,0 @@ -from itertools import combinations - - -def ordered_set_partitions(lst, parts, CACHE={}): - key = tuple(parts) - if key not in CACHE: - CACHE[key] = list(helper(lst, parts)) - for partition in CACHE[key]: - yield partition - - -def ordered_set_partitions_no_cache(lst, parts): - for partition in list(helper(lst, parts)): - yield partition - - -def helper(lst, parts): - if not parts: - yield [] - else: - for comb in combinations(lst, parts[0]): - new_lst = list(i for i in lst if i not in comb) - for f in helper(new_lst, parts[1:]): - yield [list(comb)] + f - - -def ordered_set_partitions_list(lst): - for parts in partitions(len(lst)): - if len(parts) == len(lst) or len(parts) == 1: - continue - for partition in ordered_set_partitions_no_cache(lst, parts): - yield partition - - -def partitions(n, CACHE={}): - if n not in CACHE: - if n == 0: - CACHE[n] = [[]] - elif n == 1: - CACHE[n] = [[1]] - else: - - CACHE[n] = [[n]] - for i in range(1, n): - for part in partitions(n - i): - if part: - CACHE[n].append([i] + part) - return CACHE[n] - - -def partitions_of_n_of_size_k(n, k): - """possibly empty parts""" - if n == 0 and k == 0: - return [[]] - elif k < 0: - return [] - else: - parts = [] - for i in range(n + 1): - for part in partitions_of_n_of_size_k(n - i, k - 1): - parts.append([i] + part) - return parts - - -# internet versions - -# def ordered_set_partitions(lst, parts): -# return partitions(*parts) -# -# def partitions(*args): -# def p(s, *args): -# if not args: return [[]] -# res = [] -# for c in combinations(s, args[0]): -# s0 = [x for x in s if x not in c] -# for r in p(s0, *args[1:]): -# res.append([c] + r) -# return res -# s = range(sum(args)) -# return p(s, *args) -# -# def partitions(*args): -# def minus(s1, s2): return [x for x in s1 if x not in s2] -# def p(s, *args): -# if not args: return [[]] -# return [[c] + r for c in combinations(s, args[0]) -# for r in p(minus(s, c), *args[1:])] -# return p(range(1, sum(args) + 1), *args) - - -# Raggis index - -# def ordered_set_partitions(lst, parts): -# if -# result = [() for _ in range(len(parts))] -# for partition in _helper(lst, parts, index): -# yield partition -# -# -# def _helper(lst, parts, index): -# if index >= len(parts): -# yield -# -# -# def splits(lst, k): -# for comb in combinations(lst, k): -# yield comb, tuple(i for i in lst if i not in comb) diff --git a/permuta/misc/progressbar.py b/permuta/misc/progressbar.py deleted file mode 100644 index 34a6abc4..00000000 --- a/permuta/misc/progressbar.py +++ /dev/null @@ -1,76 +0,0 @@ -import math -import sys -import time - - -class ProgressBar(object): - @staticmethod - def create(mx, mn=0): - ProgressBar.mn = mn - ProgressBar.mx = mx - ProgressBar.at = mn - ProgressBar.start = time.time() - ProgressBar.last = 0 - ProgressBar.curw = 0 - # sys.stderr.write('\n') - ProgressBar.progress(mn) - - @staticmethod - def clear(): - sys.stderr.write("\r") - sys.stderr.write(" " * ProgressBar.curw) - sys.stderr.write("\r") - - @staticmethod - def draw(fin=False): - # sys.stderr.write('\033[1F') - sys.stderr.write("\r") - width = 50 - prog = ( - 1 - if ProgressBar.mn == ProgressBar.mx - else ( - float(ProgressBar.at - ProgressBar.mn) - / (ProgressBar.mx - ProgressBar.mn) - ) - ) - bars = int(round(prog * width)) - bars = max(0, min(width, bars)) - here = "%3d%% [%s%s] " % (round(prog * 100), "#" * bars, "-" * (width - bars)) - ProgressBar.curw = len(here) - sys.stderr.write(here) - elapsed = ProgressBar.last - ProgressBar.start - # if elapsed >= 4 and prog > 0: - show_time = None - if fin: - show_time = elapsed - elif elapsed >= 2 and prog > 0: - show_time = max(0, elapsed / prog - elapsed) - if show_time is not None: - h = math.floor(show_time / 60 / 60) - show_time -= h * 60 * 60 - m = math.floor(show_time / 60) - show_time -= m * 60 - s = math.floor(show_time) - here = " %02d:%02d:%02d" % (h, m, s) - sys.stderr.write(here) - ProgressBar.curw += len(here) - # sys.stderr.write('\n') - - @staticmethod - def progress(prg=None, fin=False): - if prg is not None: - ProgressBar.at = prg - else: - ProgressBar.at = ProgressBar.at + 1 - prg = ProgressBar.at - curt = time.time() - if curt - ProgressBar.last < 0.5 and not fin: - return - ProgressBar.last = curt - ProgressBar.draw(fin) - - @staticmethod - def finish(): - ProgressBar.progress(ProgressBar.mx, fin=True) - sys.stderr.write("\n") diff --git a/permuta/misc/ranges.py b/permuta/misc/ranges.py deleted file mode 100644 index 64fff2ca..00000000 --- a/permuta/misc/ranges.py +++ /dev/null @@ -1,11 +0,0 @@ -import itertools - - -def cyclic_range(start, end, restart): - """Yields start,...,end-1,restart,...,start-1.""" - return itertools.chain(range(start, end), range(restart, start)) - - -def modulo_range(start, modulo): - """Yields start,...,modulo-1,0,...,start-1.""" - return cyclic_range(start, modulo, 0) diff --git a/permuta/misc/triemap.py b/permuta/misc/triemap.py deleted file mode 100644 index 783a7322..00000000 --- a/permuta/misc/triemap.py +++ /dev/null @@ -1,52 +0,0 @@ -class TrieNode(object): - def __init__(self): - self.down = {} - self.value = None - self.end = False - - def child(self, k): - if k not in self.down: - self.down[k] = TrieNode() - return self.down[k] - - def height(self): - return 1 + max([n.height() for n in self.down.values()] + [0]) - - -class TrieMap(object): - def __init__(self): - self.root = TrieNode() - self.cnt = 0 - - def __contains__(self, key): - cur = self.root - for k in key: - cur = cur.down.get(k, None) - if cur is None: - return False - return cur.end - - def __setitem__(self, key, value): - cur = self.root - for k in key: - cur = cur.child(k) - cur.value = value - if not cur.end: - cur.end = True - self.cnt += 1 - - def __getitem__(self, key): - cur = self.root - for k in key: - cur = cur.down.get(k, None) - if cur is None: - raise KeyError() - if not cur.end: - raise KeyError() - return cur.value - - def height(self): - return self.root.height() - - def __len__(self): - return self.cnt diff --git a/permuta/misc/union_find.py b/permuta/misc/union_find.py index 68b7cd34..f7f89d3d 100644 --- a/permuta/misc/union_find.py +++ b/permuta/misc/union_find.py @@ -1,41 +1,31 @@ -class UnionFind(object): +class UnionFind: """A collection of distjoint sets.""" - def __init__(self, n=0): - """Creates a collection of n disjoint unit sets.""" - self.p = [-1] * n - self.leaders = set(i for i in range(n)) + def __init__(self, size: int) -> None: + """Creates a collection of size disjoint unit sets.""" + self._parent = [-1] * size - def find(self, x): + def find(self, idx: int) -> int: """Return the identifier of a representative element for the set - containing the element with identifier x.""" - if self.p[x] < 0: - return x - self.p[x] = self.find(self.p[x]) - return self.p[x] + containing the element with identifier idx.""" + if self._parent[idx] < 0: + return idx + self._parent[idx] = self.find(self._parent[idx]) + return self._parent[idx] - def size(self, x): + def size(self, idx: int) -> int: """Return the number of elements in the set containing the element with - identifier x.""" - return -self.p[self.find(x)] + identifier idx.""" + return -self._parent[self.find(idx)] - def unite(self, x, y): - """Unite the two sets containing the elements with identifiers x and y, + def unite(self, idx1: int, idx2: int) -> bool: + """Unite the two sets containing the elements with identifiers idx1 and idx2, respectively.""" - x = self.find(x) - y = self.find(y) - if x == y: + idx1, idx2 = self.find(idx1), self.find(idx2) + if idx1 == idx2: return False - if self.size(x) > self.size(y): - x, y = y, x - self.p[y] += self.p[x] - self.p[x] = y - self.leaders.remove(x) + if self.size(idx1) > self.size(idx2): + idx1, idx2 = idx2, idx1 + self._parent[idx2] += self._parent[idx1] + self._parent[idx1] = idx2 return True - - def add(self): - """Add a unit set containing a new element to the collection, and - return the identifier of the new element.""" - nid = len(self.p) - self.p.append(nid) - return nid diff --git a/permuta/patterns/__init__.py b/permuta/patterns/__init__.py new file mode 100644 index 00000000..bd04bbfa --- /dev/null +++ b/permuta/patterns/__init__.py @@ -0,0 +1,13 @@ +from .bivincularpatt import BivincularPatt, CovincularPatt, VincularPatt +from .meshpatt import MeshPatt +from .patt import Patt +from .perm import Perm + +__all__ = [ + "Patt", + "Perm", + "MeshPatt", + "BivincularPatt", + "VincularPatt", + "CovincularPatt", +] diff --git a/permuta/patterns/bivincularpatt.py b/permuta/patterns/bivincularpatt.py new file mode 100644 index 00000000..fea387bc --- /dev/null +++ b/permuta/patterns/bivincularpatt.py @@ -0,0 +1,155 @@ +from random import randint +from typing import Iterable, Iterator, List, Optional, Tuple + +from .meshpatt import MeshPatt +from .patt import Patt +from .perm import Perm + + +class BivincularPatt(MeshPatt): + """A bivincular pattern class.""" + + @staticmethod + def _to_shading( + n: int, adjacent_indices: Iterable[int], adjacent_values: Iterable[int] + ) -> Iterator[Tuple[int, int]]: + """Convert adjacent requirements into shading.""" + for idx in adjacent_indices: + assert 0 <= idx <= n + yield from ((idx, val) for val in range(n + 1)) + for val in adjacent_values: + assert 0 <= val <= n + yield from ((idx, val) for idx in range(n + 1)) + + def __init__( + self, + perm: Perm, + adjacent_indices: Iterable[int], + adjacent_values: Iterable[int], + ) -> None: + super().__init__( + perm, + BivincularPatt._to_shading(len(perm), adjacent_indices, adjacent_values), + ) + + @classmethod + def unrank(cls, pattern: Perm, number: int) -> MeshPatt: + """Not implemented, inherited from MeshPatt.""" + raise NotImplementedError + + @classmethod + def of_length( + cls, length: int, patt: Optional[Perm] = None + ) -> Iterator["MeshPatt"]: + """Not implemented, inherited from MeshPatt.""" + raise NotImplementedError + + @classmethod + def random(cls, length: int) -> "BivincularPatt": + """Return a random Bivincular pattern of a given length.""" + return cls( + Perm.random(length), + ( + i + for i, keep in enumerate(randint(0, 1) for _ in range(length + 1)) + if keep + ), + ( + i + for i, keep in enumerate(randint(0, 1) for _ in range(length + 1)) + if keep + ), + ) + + def get_adjacent_requirements(self) -> Tuple[List[int], List[int]]: + """Convert shading into the bivincular requirements. Returned as a tuple of + adjacent indices and adjacent values, both in order. + + Examples: + >>> BivincularPatt(Perm((0, 3, 1, 2)), (0, 1, 2), + ... (0, 2, 4)).get_adjacent_requirements() + ([0, 1, 2], [0, 2, 4]) + """ + n, adj_idx, adj_val = len(self), set(), set() + for x, y in self.shading: + if all((x, i) in self.shading for i in range(n + 1)): + adj_idx.add(x) + if all((i, y) in self.shading for i in range(n + 1)): + adj_val.add(y) + return sorted(adj_idx), sorted(adj_val) + + def occurrences_in(self, patt: Patt, *args, **kwargs) -> Iterator[Tuple[int, ...]]: + """Find all indices of self in patt. Each yielded element is a tuple of integer + indices of the pattern such that + + Classical pattern: + Occurrence of instance's perm in patt if no elements land + in shaded region. + + Mesh pattern (including Bivincular): + Occurrences of instances's perm in the pattern's perm is found, and if + the sub mesh pattern formed by the occurrence indices is a superset of + the instance shading, they are included. + """ + if isinstance(patt, Perm): + # TODO: Optimize me for Bivincular patterns + pass + return super().occurrences_in(patt, args, kwargs) + + def __repr__(self) -> str: + adj_idx, adj_val = self.get_adjacent_requirements() + return f"BivincularPatt({repr(self.pattern)}, {adj_idx}, {adj_val})" + + def __eq__(self, other: object) -> bool: + if isinstance(other, MeshPatt): + return self.pattern == other.pattern and self.shading == other.shading + return False + + def __hash__(self) -> int: + return hash(super()) + + +class VincularPatt(BivincularPatt): + """A vincular pattern class.""" + + def __init__(self, perm: Perm, adjacent_indices: Iterable[int]) -> None: + super().__init__(perm, adjacent_indices, ()) + + @classmethod + def random(cls, length: int) -> "VincularPatt": + """Return a random Vincular pattern of a given length.""" + return cls( + Perm.random(length), + ( + i + for i, keep in enumerate(randint(0, 1) for _ in range(length + 1)) + if keep + ), + ) + + def __repr__(self) -> str: + adj_idx, _ = self.get_adjacent_requirements() + return f"VincularPatt({repr(self.pattern)}, {adj_idx})" + + +class CovincularPatt(BivincularPatt): + """A covincular pattern class.""" + + def __init__(self, perm: Perm, adjacent_values: Iterable[int]) -> None: + super().__init__(perm, (), adjacent_values) + + @classmethod + def random(cls, length: int) -> "CovincularPatt": + """Return a random Covincular pattern of a given length.""" + return cls( + Perm.random(length), + ( + i + for i, keep in enumerate(randint(0, 1) for _ in range(length + 1)) + if keep + ), + ) + + def __repr__(self) -> str: + _, adj_val = self.get_adjacent_requirements() + return f"CovincularPatt({repr(self.pattern)}, {adj_val})" diff --git a/permuta/patterns/meshpatt.py b/permuta/patterns/meshpatt.py new file mode 100644 index 00000000..201348d3 --- /dev/null +++ b/permuta/patterns/meshpatt.py @@ -0,0 +1,862 @@ +# pylint: disable=too-many-public-methods + +import collections +import random +from itertools import chain, cycle, islice +from typing import ( + Dict, + FrozenSet, + Iterable, + Iterator, + List, + Optional, + Set, + Tuple, + Union, +) + +from ..misc import DIR_EAST, DIR_NONE, DIR_NORTH, DIR_SOUTH, DIR_WEST, HTMLViewer +from .patt import Patt +from .perm import Perm + + +class MeshPatt(Patt): + """A mesh pattern class.""" + + def __init__( + self, pattern: Perm = Perm(), shading: Iterable[Tuple[int, int]] = frozenset(), + ): + self.pattern = pattern + self.shading = shading if isinstance(shading, frozenset) else frozenset(shading) + + assert all( + isinstance(coordinate, tuple) + and len(coordinate) == 2 + and isinstance(coordinate[0], int) + and isinstance(coordinate[1], int) + and 0 <= coordinate[0] <= len(self.pattern) + and 0 <= coordinate[1] <= len(self.pattern) + for coordinate in self.shading + ) + + @classmethod + def unrank(cls, pattern: Perm, number: int) -> "MeshPatt": + """Return the number-th shading of pattern. + + Examples: + >>> bin(22563) + '0b101100000100011' + >>> MeshPatt.unrank(Perm((0, 1, 2)), 386) + MeshPatt(Perm((0, 1, 2)), [(0, 1), (1, 3), (2, 0)]) + """ + assert 0 <= number < 2 ** ((len(pattern) + 1) ** 2) + bound = len(pattern) + 1 + shading = ( + divmod(index, bound) + for index, bit in enumerate(reversed(bin(number)[2:])) + if bit == "1" + ) + return cls(pattern, shading) + + @classmethod + def random(cls, length: int) -> "MeshPatt": + """Return a random mesh pattern of the specified length. + + Examples: + >>> mp = set(MeshPatt.unrank(Perm((0, )), i) for i in range(0, 16)) + >>> MeshPatt.random(1) in mp + True + >>> len(MeshPatt.random(4)) + 4 + """ + return cls.unrank( + Perm.random(length), random.randint(0, 2 ** ((length + 1) ** 2) - 1) + ) + + @classmethod + def of_length( + cls, length: int, patt: Optional[Perm] = None + ) -> Iterator["MeshPatt"]: + """Generates all mesh patterns of length n. If the classical pattern is + specified then only the mesh patterns with the classical pattern as the + underlying pattern are generated. + + Examples: + >>> mps = list(MeshPatt.of_length(0)) + >>> len(mps) + 2 + >>> mps[0] + MeshPatt(Perm(()), []) + >>> mps[1] + MeshPatt(Perm(()), [(0, 0)]) + >>> len(list(MeshPatt.of_length(2, Perm((1, 2))))) + 512 + """ + if patt is None: + for perm in Perm.of_length(length): + for i in range(2 ** ((length + 1) ** 2)): + yield MeshPatt.unrank(perm, i) + else: + assert isinstance(patt, Perm) + for i in range(2 ** ((length + 1) ** 2)): + yield MeshPatt.unrank(patt, i) + + def get_perm(self) -> "Perm": + """Returns the permutation part of the pattern. + + Examples: + >>> MeshPatt(Perm((2, 0, 1)), [(0, 0), (0, 1), (0, 2)]).get_perm() + Perm((2, 0, 1)) + """ + return self.pattern + + def complement(self) -> "MeshPatt": + """Returns the complement of the mesh pattern, which has the complement + of the underlying pattern and every shading flipped across the + horizontal axis. + + Examples: + >>> MeshPatt(Perm((0,)), frozenset({(0, 1)})).complement() + MeshPatt(Perm((0,)), [(0, 0)]) + >>> MeshPatt(Perm((0, 2, 1)), + ... frozenset({(0, 1), (0, 2), (0, 3)})).complement() + MeshPatt(Perm((2, 0, 1)), [(0, 0), (0, 1), (0, 2)]) + """ + n = len(self) + return MeshPatt( + self.pattern.complement(), ((x, n - y) for (x, y) in self.shading) + ) + + def reverse(self) -> "MeshPatt": + """Returns the reversed mesh patterns, which has the underlying pattern + reversed and every shading flipped across the vertical axis. + + Examples: + >>> MeshPatt(Perm((0,)), frozenset({(0, 1)})).reverse() + MeshPatt(Perm((0,)), [(1, 1)]) + >>> MeshPatt(Perm((2, 1, 0)), + ... frozenset({(3, 2), (2, 2), (1, 1)})).reverse() + MeshPatt(Perm((0, 1, 2)), [(0, 2), (1, 2), (2, 1)]) + """ + n = len(self) + return MeshPatt(self.pattern.reverse(), ((n - x, y) for (x, y) in self.shading)) + + def inverse(self) -> "MeshPatt": + """Returns the inverse of the meshpatt, that is the meshpatt with the + underlying classical pattern as the inverse and the shadings hold the + same relation between the points. This is equivalent to flipping the + pattern over the diagonal. + + Examples: + >>> MeshPatt(Perm((0,)), frozenset({(0, 1)})).inverse() + MeshPatt(Perm((0,)), [(1, 0)]) + """ + return MeshPatt(self.pattern.inverse(), ((y, x) for (x, y) in self.shading)) + + def sub_mesh_pattern(self, indices: Iterable[int]) -> "MeshPatt": + """Return the mesh pattern induced by unique indices where the pattern is + the permutation induced by the indices and a region is shaded if and only + if the corresponding region of self is fully shaded. + + Exampes: + >>> shading = frozenset({(3, 2), (1, 3), (4, 2), (0, 3), (1, 2), + ... (4, 3)}) + >>> MeshPatt(Perm((3, 2, 1, 0)), + ... shading).sub_mesh_pattern((0, 1, 3)) + MeshPatt(Perm((2, 1, 0)), [(0, 2), (1, 2), (3, 2)]) + >>> MeshPatt(Perm((2, 3, 1, 0)), + ... shading).sub_mesh_pattern((1, 2, 3)) + MeshPatt(Perm((2, 1, 0)), [(3, 2)]) + """ + indices = sorted(indices) + if not indices: + return MeshPatt() + n = len(self) + pattern = Perm.to_standard(self.pattern[index] for index in indices) + vertical = [0] + vertical.extend(index + 1 for index in indices) + vertical.append(n + 1) + horizontal = [0] + horizontal.extend(sorted(self.pattern[index] + 1 for index in indices)) + horizontal.append(n + 1) + shading = frozenset( + (x, y) + for x in range(len(pattern) + 1) + for y in range(len(pattern) + 1) + if ( + self.is_shaded( + (vertical[x], horizontal[y]), + (vertical[x + 1] - 1, horizontal[y + 1] - 1), + ) + and self.is_pointfree( + (vertical[x], horizontal[y]), + (vertical[x + 1] - 1, horizontal[y + 1] - 1), + ) + ) + ) + return MeshPatt(pattern, shading) + + flip_horizontal = complement + flip_vertical = reverse + flip_diagonal = inverse + + def rotate(self, times: int = 1) -> "MeshPatt": + """Rotate the mesh pattern. The parameter determines how often it is rotated. + A negative value rotates it to the left. + + Exampes: + >>> MeshPatt(Perm((0, 2, 1)), [(2, 3), (3, 0), (3, 3)]).rotate(-3) + MeshPatt(Perm((2, 0, 1)), [(0, 0), (3, 0), (3, 1)]) + >>> MeshPatt(Perm((0, 2, 1)), [(2, 3), (3, 0), (3, 3)]).rotate(-2) + MeshPatt(Perm((1, 0, 2)), [(0, 0), (0, 3), (1, 0)]) + >>> MeshPatt(Perm((0, 2, 1)), [(2, 3), (3, 0), (3, 3)]).rotate(-1) + MeshPatt(Perm((1, 2, 0)), [(0, 2), (0, 3), (3, 3)]) + >>> MeshPatt(Perm((0, 2, 1)), [(2, 3), (3, 0), (3, 3)]).rotate(0) + MeshPatt(Perm((0, 2, 1)), [(2, 3), (3, 0), (3, 3)]) + >>> MeshPatt(Perm((0, 2, 1)), [(2, 3), (3, 0), (3, 3)]).rotate(1) + MeshPatt(Perm((2, 0, 1)), [(0, 0), (3, 0), (3, 1)]) + >>> MeshPatt(Perm((0, 2, 1)), [(2, 3), (3, 0), (3, 3)]).rotate(2) + MeshPatt(Perm((1, 0, 2)), [(0, 0), (0, 3), (1, 0)]) + >>> MeshPatt(Perm((0, 2, 1)), [(2, 3), (3, 0), (3, 3)]).rotate(3) + MeshPatt(Perm((1, 2, 0)), [(0, 2), (0, 3), (3, 3)]) + """ + times = times % 4 + if times == 0: + return self + n, perm = len(self), self.pattern.rotate(times) + if times == 1: + return MeshPatt(perm, ((y, n - x) for x, y in self.shading)) + if times == 2: + return MeshPatt(perm, ((n - x, n - y) for x, y in self.shading)) + return MeshPatt(perm, ((n - y, x) for x, y in self.shading),) + + def all_syms(self) -> Tuple["MeshPatt", ...]: + """Return the set of all symmetries of the mesh pattern. + + Exampes: + >>> p = MeshPatt(Perm((0, 1)), [(0, 1), (1, 1)]) + >>> print("\\n".join(repr(s) for s in sorted(p.all_syms()))) + MeshPatt(Perm((0, 1)), [(0, 1), (1, 1)]) + MeshPatt(Perm((0, 1)), [(1, 0), (1, 1)]) + MeshPatt(Perm((0, 1)), [(1, 1), (1, 2)]) + MeshPatt(Perm((0, 1)), [(1, 1), (2, 1)]) + MeshPatt(Perm((1, 0)), [(0, 1), (1, 1)]) + MeshPatt(Perm((1, 0)), [(1, 0), (1, 1)]) + MeshPatt(Perm((1, 0)), [(1, 1), (1, 2)]) + MeshPatt(Perm((1, 0)), [(1, 1), (2, 1)]) + """ + current, symmetries = self, {self, self.inverse()} + for _ in range(3): + current = current.rotate() + symmetries.update((current, current.inverse())) + return tuple(symmetries) + + def shade(self, *positions: Tuple[int, int]) -> "MeshPatt": + """Returns the mesh pattern with the added shadings given by positions. + + Exampes: + >>> MeshPatt(Perm((0, 1)), [(0, 1)]).shade((1, 1)) + MeshPatt(Perm((0, 1)), [(0, 1), (1, 1)]) + >>> MeshPatt(Perm((0, 1)), [(0, 1)]).shade((1, 1), (1, 0)) + MeshPatt(Perm((0, 1)), [(0, 1), (1, 0), (1, 1)]) + """ + return MeshPatt(self.pattern, self.shading | set(positions)) + + def add_point(self, pos: Tuple[int, int], shade_dir: int = DIR_NONE) -> "MeshPatt": + """Returns a mesh pattern with a point added in the box at position + pos. If shade_dir is specified adds shading in that direction. + + Exampes: + >>> MeshPatt().add_point((0, 0)) + MeshPatt(Perm((0,)), []) + >>> p = MeshPatt(Perm((0, 1, 2)), [(1, 0), (2, 1), (3, 2)]) + >>> p.add_point((2, 0), shade_dir=DIR_SOUTH) + MeshPatt(Perm((1, 2, 0, 3)), [(1, 0), (1, 1), (2, 0), (2, 2), (3, 0), (3, 2), \ +(4, 3)]) + """ + assert pos not in self.shading + x, y = pos + new_shading = self._add_point_base_shading(x, y) + if shade_dir == DIR_EAST: + new_shading.update(((x + 1, y), (x + 1, y + 1))) + elif shade_dir == DIR_NORTH: + new_shading.update(((x, y + 1), (x + 1, y + 1))) + elif shade_dir == DIR_WEST: + new_shading.update(((x, y), (x, y + 1))) + elif shade_dir == DIR_SOUTH: + new_shading.update(((x, y), (x + 1, y))) + return MeshPatt(self._add_point_new_perm(x, y), new_shading) + + def _add_point_base_shading(self, x: int, y: int) -> Set[Tuple[int, int]]: + new_shading = set() + for s_x, s_y in self.shading: + new_xs, new_ys = [], [] + if s_x <= x: + new_xs.append(s_x) + if s_x >= x: + new_xs.append(s_x + 1) + if s_y <= y: + new_ys.append(s_y) + if s_y >= y: + new_ys.append(s_y + 1) + for new_x in new_xs: + for new_y in new_ys: + new_shading.add((new_x, new_y)) + return new_shading + + def _add_point_new_perm(self, x: int, y: int) -> Perm: + iterator = iter(self.pattern) + return Perm( + chain( + (val if val < y else (val + 1) for val in islice(iterator, x)), + (y,), + (val if val < y else (val + 1) for val in iterator), + ) + ) + + def add_increase(self, pos: Tuple[int, int]) -> "MeshPatt": + """Adds an increasing pattern (0, 1) into the given coordinate. + + Returns: + The new pattern with the increased pattern inserted into pos. + + Examples: + >>> MeshPatt(Perm((0,))).add_increase((0, 0)) + MeshPatt(Perm((0, 1, 2)), []) + """ + x, y = pos + return self.add_point((x, y)).add_point((x + 1, y + 1)) + + def add_decrease(self, pos: Tuple[int, int]) -> "MeshPatt": + """Adds an decreasing pattern (1, 0) into the given coordinate. + + Examples: + >>> MeshPatt(Perm((0,))).add_decrease((0, 0)) + MeshPatt(Perm((1, 0, 2)), []) + """ + x, y = pos + return self.add_point((x, y)).add_point((x + 1, y)) + + def contains(self, *patts: Patt) -> bool: + """Check if self contains all provided patterns. + + Examples: + >>> MeshPatt(Perm((0,)), [(0, 1)]).contains(MeshPatt(Perm((0,)), [(0, 0)])) + False + >>> MeshPatt(Perm((0,)), [(0, 1)]).contains(MeshPatt(Perm((0,)), [])) + True + """ + return all(patt in self for patt in patts) + + def avoids(self, *patts: Patt) -> bool: + """Check if self avoids all provided patterns. + + Examples: + >>> MeshPatt(Perm((0,)), [(0, 0)]).avoids(MeshPatt(Perm((0,)), [(0, 1)])) + True + >>> MeshPatt(Perm((0,)), []).avoids(MeshPatt(Perm((0,)), [(0, 1)])) + True + >>> MeshPatt(Perm((0,)), [(0, 1)]).avoids(MeshPatt(Perm((0,)), [(0, 0)])) + True + >>> MeshPatt(Perm((0,)), [(0, 1)]).avoids(MeshPatt(Perm((0,)), [])) + False + """ + return all(patt not in self for patt in patts) + + def occurrences_in(self, patt: Patt, *args, **kwargs) -> Iterator[Tuple[int, ...]]: + """ + Find all indices of self in patt. Each yielded element is a tuple of integer + indices of the pattern such that + + Classical pattern: + Occurrence of instance's perm in patt if no elements land + in shaded region. + + Mesh pattern: + Occurrences of instances's perm in the pattern's perm is found, and if + the sub mesh pattern formed by the occurrence indices is a superset of + the instance shading, they are included. + + Example: + >>> mp = MeshPatt(Perm((1, 0, 2)), [(1, 2), (2, 2), (2, 3)]) + >>> p = Perm((3, 1, 0, 2, 4)) + >>> sorted(mp.occurrences_in(p)) + [(0, 1, 4), (0, 2, 4), (0, 3, 4), (1, 2, 3)] + >>> mp2 = MeshPatt(p, [(0,0), (0,1), (0,2), (1,4), (2,4), (3,3), (3,4), + ... (3,5), (4,0), (4,3), (4,4), (4,5), (5,0)]) + >>> sorted(mp.occurrences_in(mp2)) + [(0, 2, 4), (0, 3, 4)] + """ + assert isinstance(patt, (Perm, MeshPatt)) + if isinstance(patt, Perm): + yield from self._occurrences_in_perm(patt) + else: + yield from self._occurrences_in_mesh(patt) + + def _occurrences_in_mesh(self, patt) -> Iterator[Tuple[int, ...]]: + return ( + occurrence + for occurrence in self.occurrences_in(patt.pattern) + if self.shading <= patt.sub_mesh_pattern(occurrence).shading + ) + + def _occurrences_in_perm(self, patt: Perm) -> Iterator[Tuple[int, ...]]: + for candidate_indices in self.pattern.occurrences_in(patt): + candidate = [patt[index] for index in candidate_indices] + x = 0 + for element in patt: + if element in candidate: + x += 1 + continue + y = sum( + 1 for candidate_element in candidate if candidate_element < element + ) + if (x, y) in self.shading: + break + else: + yield tuple(candidate_indices) + + def is_shaded( + self, lower_left: Tuple[int, int], upper_right: Optional[Tuple[int, int]] = None + ) -> bool: + """Check if a region of the grid is shaded. If a single point is provided, we + only check that point. Otherwise we check the rectangle formed by the points. + + Example: + >>> MeshPatt(Perm((3, 2, 1, 0)), [(0, 0), (0, 1), (0, 2)]).is_shaded((0, 1)) + True + >>> MeshPatt(Perm((3, 2, 1, 0)), [(0, 0), (0, 1), (0, 2)]).is_shaded((1, 0)) + False + >>> MeshPatt(Perm((3, 2, 1, 0)), [(0, 0), (0, 1), (1, 1)]).is_shaded( + ... (0, 0), (1, 1)) + False + >>> MeshPatt(Perm((3, 2, 1, 0)), [(0, 0), (0, 1), (1, 1), (1, 2), (2, 1), + ... (2, 2)]).is_shaded((1, 1), (2, 2)) + True + """ + assert all(0 <= pos <= len(self) for pos in lower_left) + if upper_right is None: + return lower_left in self.shading + assert ( + all(0 <= pos <= len(self) for pos in upper_right) + and lower_left[0] <= upper_right[0] + and lower_left[1] <= upper_right[1] + ) + (left, lower), (right, upper) = lower_left, upper_right + return all( + (x, y) in self.shading + for y in range(lower, upper + 1) + for x in range(left, right + 1) + ) + + def is_pointfree( + self, lower_left: Tuple[int, int], upper_right: Tuple[int, int] + ) -> bool: + """Check if a the rectangular region defined by the points has any points. + + Examples: + >>> MeshPatt.random(10).is_pointfree((0, 0), (10, 10)) + False + >>> MeshPatt(Perm((4, 0, 1, 2, 3))).is_pointfree((0, 2), (2, 3)) + True + """ + assert ( + all(0 <= pos <= len(self) for pos in (*lower_left, *upper_right)) + and lower_left[0] <= upper_right[0] + and lower_left[1] <= upper_right[1] + ) + (left, lower), (right, upper) = lower_left, upper_right + return not any(lower <= self.pattern[idx] < upper for idx in range(left, right)) + + def can_shade(self, pos: Tuple[int, int]) -> List[int]: + """If it is possible to shade box at provided position according to the shading + lemma, we return a list of the values of the adjacent points to the box that + can be used. If not, an empty list is returned. + + Examples: + >>> MeshPatt(Perm((0,)),[(0, 0)]).can_shade((1, 1)) + [] + >>> MeshPatt(Perm((1, 2, 0)), + ... [(2, 2),(3, 0),(3, 2),(3, 3)]).can_shade((1, 2)) + [1, 2] + """ + n, m_patt, positions = len(self), self, [] + # Rotate everything and check NE conditions + for rot in range(4): + if m_patt.north_east_shading_lemma_conditions(pos): + ans = pos[0] - 1, pos[1] - 1 + for _ in range((-rot) % 4): + ans = ans[1], n - 1 - ans[0] + positions.append(ans[1]) + m_patt = m_patt.rotate() + pos = pos[1], n - pos[0] + return positions + + def north_east_shading_lemma_conditions(self, pos: Tuple[int, int]) -> bool: + """Checks if a box at provided position to the northeast of a point satisfies + the shading lemma's conditions. + + Examples: + >>> MeshPatt(Perm((0,))).north_east_shading_lemma_conditions((0, 0)) + False + >>> MeshPatt(Perm((0,))).north_east_shading_lemma_conditions((1, 1)) + True + """ + x, y = pos + return not any( + ( + # if pos is shaded + (x, y) in self.shading, + # if pos does not have point in lower-left + x - 1 < 0 or self.pattern[x - 1] != y - 1, + # if box in south-west direction is shaded + (x - 1, y - 1) in self.shading, + # only one of the boxes to the left and down can be shaded + all(((x, y - 1) in self.shading, (x - 1, y) in self.shading)), + # if the box on the lower side of the horizontal line + # is shaded then the upper one must be shaded + any( + (n_x, y - 1) in self.shading and (n_x, y) not in self.shading + for n_x in range(len(self.pattern) + 1) + if n_x not in (x - 1, x) + ), + # if the box on the left side of the vertical line + # is shaded then the right one must be shaded + any( + (x - 1, n_y) in self.shading and (x, n_y) not in self.shading + for n_y in range(len(self.pattern) + 1) + if n_y not in (y - 1, y) + ), + ) + ) + + def can_simul_shade( + self, pos1: Tuple[int, int], pos2: Tuple[int, int] + ) -> List[int]: + """Returns whether it is possible to shade the boxes at positions pos1, + pos2 according to the Shading Lemma. Every direction is checked and the + list of the values of the adjacent points to the box that can be used + is returned. + + Examples: + >>> MeshPatt(Perm((0, 2, 1))).can_simul_shade((1, 1), (1, 0)) + [0] + >>> MeshPatt(Perm((0, 2, 1))).can_simul_shade((3, 2), (3, 1)) + [1] + >>> MeshPatt(Perm((0, 2, 1))).can_simul_shade((2, 3), (2, 2)) + [2] + >>> MeshPatt(Perm((0, 2, 1))).can_simul_shade((1, 1), (2, 1)) + [] + """ + n, m_patt, positions = len(self), self, [] + for i in range(4): + if pos1[1] < pos2[1]: + pos1, pos2 = pos2, pos1 + if m_patt.north_east_simul_shading_lemma_conditions(pos1, pos2): + ans = (pos1[0] - 1, pos1[1] - 1) + for _ in range((-i) % 4): + ans = ans[1], n - 1 - ans[0] + positions.append(ans[1]) + m_patt = m_patt.rotate() + pos1, pos2 = (pos1[1], n - pos1[0]), (pos2[1], n - pos2[0]) + return positions + + can_shade2 = can_simul_shade + + def north_east_simul_shading_lemma_conditions( + self, pos1: Tuple[int, int], pos2: Tuple[int, int] + ) -> bool: + """Checks if two boxes at provided positions, where pos1 is assumed to be above + pos2 and to the northeast of a point, satisfy the shading lemma's conditions. + + Examples: + >>> MeshPatt((0,2,1)).north_east_simul_shading_lemma_conditions((1,1),(1,0)) + True + >>> MeshPatt((0,2,1)).north_east_simul_shading_lemma_conditions((2,3),(2,2)) + True + >>> MeshPatt((0,2,1)).north_east_simul_shading_lemma_conditions((3,2),(3,1)) + True + >>> MeshPatt((0,2,1)).north_east_simul_shading_lemma_conditions((3,3),(3,2)) + False + >>> MeshPatt((0,2,1)).north_east_simul_shading_lemma_conditions((3,1),(3,0)) + False + """ + assert pos1[1] >= pos2[1] + return not any( + ( + # There must be a point at (pos1[0]-1, pos1[1]-1) + pos1[0] == 0 or self.pattern[pos1[0] - 1] != pos1[1] - 1, + # The pos1 must be directly above pos2 + pos1[0] != pos2[0] or pos1[1] - 1 != pos2[1], + # Either is shaded + pos1 in self.shading or pos2 in self.shading, + # The boxes surrounding the point (pos1[0]-1, pos1[1] - 1) must be empty + ( + (pos1[0] - 1, pos1[1]) in self.shading + or (pos2[0] - 1, pos2[1]) in self.shading + ), + # Check the boxes on each side of the vertical line of pos1[0], if the + # box on the left side is shaded then the box on the right side must be + # shaded. + any( + ( + (pos1[0] - 1, y) in self.shading + and (pos1[0], y) not in self.shading + ) + for y in range(len(self.pattern) + 1) + if y not in (pos1[1], pos1[1] - 1) + ), + # Check the boxes on each side of the horizontal line of pos1[1]-1, + # they must match. + any( + ((x, pos1[1]) in self.shading) != ((x, pos2[1]) in self.shading) + for x in range(len(self.pattern) + 1) + if x not in (pos1[0], pos1[0] - 1) + ), + ) + ) + + def shadable_boxes( + self, + ) -> Dict[ + int, + List[Union[Tuple[Tuple[int, int]], Tuple[Tuple[int, int], Tuple[int, int]]]], + ]: + """Returns a dictionary of all tuples of shadable boxes with the + shading lemma, with the keys as the points used to shade the tuples of + boxes. + + Examples: + >>> sh = {(3, 2), (3, 0), (4, 2), (1, 0), (0, 3), (1, 2), (0, 4), + ... (0, 2)} + >>> m = MeshPatt(Perm((0, 3, 1, 2)), sh) + >>> dict(m.shadable_boxes()) + {0: [((0, 0),)], 3: [((1, 3),), ((1, 3), (1, 4)), ((1, 4),)]} + """ + shadable: Dict[ + int, + List[ + Union[Tuple[Tuple[int, int]], Tuple[Tuple[int, int], Tuple[int, int]]] + ], + ] = collections.defaultdict(list) + n = len(self) + for x in range(n + 1): + for y in range(n + 1): + for pnt in self.can_shade((x, y)): + shadable[pnt].append(((x, y),)) + if x < n: + for pnt in self.can_simul_shade((x, y), (x + 1, y)): + shadable[pnt].append(((x, y), (x + 1, y))) + if y < n: + for pnt in self.can_simul_shade((x, y), (x, y + 1)): + shadable[pnt].append(((x, y), (x, y + 1))) + return shadable + + def non_pointless_boxes(self) -> Set[Tuple[int, int]]: + """ Returns the coordinates of the boxes that have a point on their + boundaries in one of their four corners. + + Examples: + >>> m = MeshPatt(Perm((0, 1)), frozenset({(0, 1), (2, 0), (0, 2)})) + >>> sorted(m.non_pointless_boxes()) + [(0, 0), (0, 1), (1, 0), (1, 1), (1, 2), (2, 1), (2, 2)] + """ + return { + pos + for idx, val in enumerate(self.pattern) + for pos in ((idx + 1, val + 1), (idx, val + 1), (idx, val), (idx + 1, val)) + } + + def has_anchored_point(self) -> Tuple[bool, bool, bool, bool]: + """Checks if the mesh pattern has any point anchored to the boundary. + Returns a tuple (right, top, left, bottom) where each value represent + whether the point is anchored to the corresponding direction. + + Examples: + >>> m = MeshPatt(Perm((0, 1)), {(0, 0), (1, 0), (2, 0), (1, 1)}) + >>> m.has_anchored_point() + (False, False, False, True) + """ + n = len(self) + right = all((n, i) in self.shading for i in range(n + 1)) + top = all((i, n) in self.shading for i in range(n + 1)) + left = all((0, i) in self.shading for i in range(n + 1)) + bottom = all((i, 0) in self.shading for i in range(n + 1)) + return (right, top, left, bottom) + + def rank(self) -> int: + """Computes the rank of the mesh pattern, the bit string of the + shadings interpreted as an integer. + + Examples: + >>> sh = {(0, 0), (3, 0), (0, 2), (2, 1), (2, 3), (1, 2), (3, 3), + ... (3, 1), (1, 1)} + >>> m = MeshPatt(Perm((1, 0, 2)), sh) + >>> rank = m.rank() + >>> rank + 47717 + >>> bin(rank) + '0b1011101001100101' + """ + n, res = len(self), 0 + for (x, y) in self.shading: + res |= 1 << (x * (n + 1) + y) + return res + + def ascii_plot(self, cell_size: int = 1) -> str: + """Return an ascii plot of the given Mesh pattern. + + Examples: + >>> print(MeshPatt(Perm((0,1,2)), [(2,1), (3,0), (3,1), (3,2), (3,3)] + ... ).ascii_plot()) + | | |▒ + -+-+-●- + | | |▒ + -+-●-+- + | |▒|▒ + -●-+-+- + | | |▒ + """ + + def roundrobin(*iterables: Iterable[str]) -> Iterable[str]: + # Recipe credited to George Sakkis + nexts = cycle(iter(it).__next__ for it in iterables) + for _ in range(2): + try: + for nxt in nexts: + yield nxt() + except StopIteration: + nexts = cycle(islice(nexts, 1)) + + def fill_char(char: Tuple[int, int]) -> str: + if char in self.shading: + return "\u2592" + if char[0] == n: + return "" + return " " + + assert cell_size >= 1 + n = len(self) + array = [["+" for i in range(n)] for j in range(n)] + for idx, val in enumerate(self.pattern): + array[val][idx] = "\u25cf" + lines = ( + ("-" * cell_size).join([""] + line + [""]) + "\n" + for line in reversed(array) + ) + vlines = ( + ("|".join(fill_char((j, i)) * cell_size for j in range(n + 1)) + "\n") + * cell_size + for i in range(n, -1, -1) + ) + return "".join(roundrobin(vlines, lines))[:-1] + + def to_svg(self, image_scale: float = 1.0) -> str: + """Return the svg code to plot the mesh pattern. The image size defaults to + 100x100 pixels and the parameter scales that.""" + patt_svg = self.pattern.to_svg(image_scale) + n = len(self) + p_scale = 100 / (n + 1) + line_split = patt_svg.find(">") + return "".join( + [ + patt_svg[: line_split + 2], + "\n".join( + ( + f"' + ) + for x, y in self.shading + ), + patt_svg[line_split + 1 :], + ] + ) + + def to_tikz(self) -> str: + """ + Return the tikz code to plot the mesh pattern. The tikz code requires + the TikZ library patter. + """ + n, tab = len(self), " " * 4 + lis = [ + "\\begin{tikzpicture}", + f"[scale=.3,baseline=(current bounding box.center)]\n{tab}", + f"\\foreach \\x in {{1,...,{n}}} {{\n{tab*2}", + f"\\draw[ultra thin] (\\x,0)--(\\x,{n+1}); %vline\n{tab*2}", + f"\\draw[ultra thin] (0,\\x)--({n+1},\\x); %hline\n{tab}}}", + "".join( + ( + f"\n{tab}\\fill[pattern color = black!75, pattern=" + f"north east lines] {cell} rectangle +(1,1);" + ) + for cell in sorted(self.shading) + ), + f"\n{tab}", + f"\n{tab}".join( + f"\\draw[fill=black] ({idx+1},{val+1}) circle (5pt);" + for idx, val in enumerate(self.pattern) + ), + "\n\\end{tikzpicture}", + ] + return "".join(lis) + + def show(self, scale: float = 1.0) -> None: + """Open a browser tab and display pattern graphically. Image can be + enlarged with scale parameter""" + HTMLViewer.open_svg(self.to_svg(image_scale=scale)) + + def __eq__(self, other: object) -> bool: + if isinstance(other, self.__class__): + return self.shading == other.shading and self.pattern == other.pattern + return False + + def __hash__(self) -> int: + return hash((self.pattern, self.shading)) + + def __iter__(self) -> Iterator[Union[Perm, FrozenSet]]: + return iter((self.pattern, self.shading)) + + def __repr__(self) -> str: + return f"MeshPatt({repr(self.pattern)}, {sorted(self.shading)})" + + def __str__(self) -> str: + return f"({self.pattern}, {sorted(self.shading)})" + + def __len__(self) -> int: + return len(self.pattern) + + def __bool__(self) -> bool: + return bool(self.pattern) or bool(self.shading) + + def __lt__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return (self.pattern, sorted(self.shading)) < ( + other.pattern, + sorted(other.shading), + ) + + def __le__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return (self.pattern, sorted(self.shading)) <= ( + other.pattern, + sorted(other.shading), + ) + + def __gt__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return other.__lt__(self) + + def __ge__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return other.__le__(self) + + def __contains__(self, patt: object) -> bool: + if isinstance(patt, Patt): + return any(True for _ in patt.occurrences_in(self)) + return False diff --git a/permuta/patterns/patt.py b/permuta/patterns/patt.py new file mode 100644 index 00000000..77962776 --- /dev/null +++ b/permuta/patterns/patt.py @@ -0,0 +1,40 @@ +import abc +from typing import TYPE_CHECKING, Iterator, Tuple + +if TYPE_CHECKING: + # pylint: disable=cyclic-import + from .perm import Perm + + +class Patt(abc.ABC): + """A permutation pattern, e.g. classical, bivincular and mesh patterns.""" + + def avoided_by(self, *patts: "Patt") -> bool: + """Check if self is avoided by all the provided patterns.""" + return all(self not in patt for patt in patts) + + def contained_in(self, *patts: "Patt") -> bool: + """Check if self is a pattern of all the provided patterns.""" + return all(self in patt for patt in patts) + + def count_occurrences_in(self, patt: "Patt") -> int: + """Count the number of occurrences of self in the pattern.""" + return sum(1 for _ in self.occurrences_in(patt)) + + @abc.abstractmethod + def occurrences_in( + self, patt: "Patt", *args, **kwargs + ) -> Iterator[Tuple[int, ...]]: + """Find all indices of occurrences of self in pattern.""" + + @abc.abstractmethod + def __len__(self) -> int: + """The length of the pattern.""" + + @abc.abstractmethod + def get_perm(self) -> "Perm": + """Get the permutation part of the pattern""" + + @abc.abstractmethod + def __contains__(self, patt: object) -> bool: + """Does pattern contains another?""" diff --git a/permuta/patterns/perm.py b/permuta/patterns/perm.py new file mode 100644 index 00000000..9cea9856 --- /dev/null +++ b/permuta/patterns/perm.py @@ -0,0 +1,2160 @@ +# pylint: disable=super-init-not-called +# pylint: disable=too-many-lines +# pylint: disable=too-many-public-methods + +import bisect +import collections +import itertools +import math +import numbers +import operator +import random +from typing import ( + TYPE_CHECKING, + Callable, + ClassVar, + Deque, + Dict, + Iterable, + Iterator, + List, + Optional, + Set, + Tuple, + Union, +) + +from permuta.misc import HTMLViewer + +from .patt import Patt + +__all__ = ("Perm",) + +# Remove when pypy is 3.7 compatible andn replace TupleType with Tuple[int] +if TYPE_CHECKING: + TupleType = Tuple[int] +else: + TupleType = tuple + + +class Perm(TupleType, Patt): + """A perm class.""" + + _TO_STANDARD_CACHE: ClassVar[Dict[Tuple, "Perm"]] = {} + + def __new__(cls, iterable: Iterable[int] = ()) -> "Perm": + """Return a Perm instance. + + Examples: + >>> Perm((0, 3, 1, 2)) + Perm((0, 3, 1, 2)) + >>> Perm(range(5, -1, -1)) + Perm((5, 4, 3, 2, 1, 0)) + """ + return tuple.__new__(cls, iterable) + + def __init__(self, _iterable: Iterable[int] = ()) -> None: + # Cache for data used when finding occurrences of self in a perm + self._cached_pattern_details: Optional[List[Tuple[int, int, int, int]]] = None + + @classmethod + def clear_cache(cls): + """Clears to_standardize cache.""" + cls._TO_STANDARD_CACHE = {} + + @classmethod + def to_standard(cls, iterable: Iterable) -> "Perm": + """Return the perm corresponding to iterable. Duplicate elements + are allowed and become consecutive elements (see example). + + Examples: + >>> Perm.to_standard("a2gsv3") + Perm((2, 0, 3, 4, 5, 1)) + >>> Perm.to_standard("caaba") + Perm((4, 0, 1, 3, 2)) + """ + iterable = tuple(iterable) + if iterable not in cls._TO_STANDARD_CACHE: + cls._TO_STANDARD_CACHE[iterable] = cls( + idx + for (idx, _) in sorted(enumerate(iterable), key=operator.itemgetter(1)) + ).inverse() + return cls._TO_STANDARD_CACHE[iterable] + + standardize = to_standard + from_iterable = to_standard + + @classmethod + def from_integer(cls, integer: int) -> "Perm": + """Return the perm corresponding to the integer given. The permutation + can be given one-based or zero-base but it will be returned in 0-based. + + Examples: + >>> Perm.from_integer(123) + Perm((0, 1, 2)) + >>> Perm.from_integer(321) + Perm((2, 1, 0)) + >>> Perm.from_integer(201) + Perm((2, 0, 1)) + """ + assert 0 <= integer <= 9876543210 + if integer == 0: + return Perm((0,)) + digit_list: List[int] = [] + while integer != 0: + digit_list.append(integer % 10) + integer //= 10 + return cls.to_standard(reversed(digit_list)) + + @classmethod + def from_string(cls, string: str) -> "Perm": + """Return the perm corresponding to the string given. + + Examples: + >>> Perm.from_string("203451") + Perm((2, 0, 3, 4, 5, 1)) + >>> Perm.from_string("40132") + Perm((4, 0, 1, 3, 2)) + """ + if string == "ε": + return cls(()) + return cls(map(int, string)) + + @classmethod + def one_based(cls, iterable: Iterable[int]) -> "Perm": + """A way to enter a perm in the traditional permuta way. + + Examples: + >>> Perm.one_based((4, 1, 3, 2)) + Perm((3, 0, 2, 1)) + """ + return cls(val - 1 for val in iterable) + + one = one_based + proper = one_based + scientific = one_based + + @classmethod + def from_iterable_validated(cls, iterable: Union[str, Iterable[int]]) -> "Perm": + """Creates a permutation from either a string or an iterable of integers and + validates that it is a bijection on {0,1,...,n-1}. + + Examples: + >>> Perm.from_iterable_validated((0, 4, 1, 3, 2)) + Perm((0, 4, 1, 3, 2)) + >>> Perm.from_iterable_validated("04132") + Perm((0, 4, 1, 3, 2)) + >>> Perm.from_iterable_validated((2, 3, 1)) + Traceback (most recent call last): + ... + ValueError: Element out of range: 3 + >>> Perm.from_iterable_validated((2, 1, 1)) + Traceback (most recent call last): + ... + ValueError: Duplicate element: 1 + >>> Perm.from_iterable_validated((2, None, 1)) + Traceback (most recent call last): + ... + TypeError: 'None' object is not an integer + + Raises: + TypeError: If the perm has non integer values. + ValueError: If not a bijection. + """ + if isinstance(iterable, str): + iterable = map(int, iterable) + perm = cls(iterable) + used = [False] * len(perm) + for val in perm: + if not isinstance(val, numbers.Integral): + raise TypeError(f"'{repr(val)}' object is not an integer") + if not 0 <= val < len(perm): + raise ValueError(f"Element out of range: {val}") + if used[val]: + raise ValueError(f"Duplicate element: {val}") + used[val] = True + return perm + + @classmethod + def random(cls, length: int) -> "Perm": + """Return a random perm of the specified length. + + Examples: + >>> perm = Perm.random(8) + >>> len(perm) == 8 + True + """ + result = list(range(length)) + random.shuffle(result) + return cls(result) + + @classmethod + def of_length(cls, length: int) -> Iterator["Perm"]: + """Generate all permutations of a given length in lexicographical order. + + Examples: + >>> list(Perm.of_length(2)) + [Perm((0, 1)), Perm((1, 0))] + """ + yield from (cls(perm) for perm in itertools.permutations(range(length))) + + @classmethod + def up_to_length(cls, length: int) -> Iterator["Perm"]: + """Generate all permutations up to a and including a given + length in lexicographical order. + + Examples: + >>> list(Perm.up_to_length(2)) + [Perm(()), Perm((0,)), Perm((0, 1)), Perm((1, 0))] + """ + for n in range(length + 1): + yield from (cls(perm) for perm in itertools.permutations(range(n))) + + @classmethod + def first(cls, count: int) -> Iterator["Perm"]: + """Generate all permutations in lexicographical order up to a count. + + Examples: + >>> list(Perm.first(5)) + [Perm(()), Perm((0,)), Perm((0, 1)), Perm((1, 0)), Perm((0, 1, 2))] + """ + yield from itertools.islice(cls._all(), count) + + @classmethod + def _all(cls) -> Iterator["Perm"]: + length = 0 + while True: + yield from cls.of_length(length) + length += 1 + + @classmethod + def identity(cls, length: int) -> "Perm": + """Return the identity perm of the specified length. + + Examples: + >>> Perm.identity(0) + Perm(()) + >>> Perm.identity(4) + Perm((0, 1, 2, 3)) + """ + return cls(range(length)) + + monotone_increasing = identity + + @classmethod + def monotone_decreasing(cls, length: int) -> "Perm": + """Return a monotone decreasing perm of the specified length. + + Examples: + >>> Perm.monotone_decreasing(0) + Perm(()) + >>> Perm.monotone_decreasing(4) + Perm((3, 2, 1, 0)) + """ + return cls(range(length - 1, -1, -1)) + + @classmethod + def unrank(cls, number: int, length: Optional[int] = None) -> "Perm": + """ + Get permutation by lexicographical order. + + Examples: + >>> Perm.unrank(0) + Perm(()) + >>> Perm.unrank(1) + Perm((0,)) + >>> Perm.unrank(2) + Perm((0, 1)) + >>> Perm.unrank(3) + Perm((1, 0)) + >>> Perm.unrank(4) + Perm((0, 1, 2)) + >>> Perm.unrank(5) + Perm((0, 2, 1)) + >>> Perm.unrank(1, 3) + Perm((0, 2, 1)) + """ + if number == 0: + return cls.identity(0 if length is None else length) + factorial = [1, 1] + if length is None: + while number > factorial[-1]: + number -= factorial[-1] + factorial.append(factorial[-1] * len(factorial)) + return cls(cls._unrank(number - 1, len(factorial) - 1, factorial)) + for i in range(len(factorial), length): + factorial.append(i * factorial[-1]) + return cls(cls._unrank(number, length, factorial)) + + @staticmethod + def _unrank(number: int, length: int, factorial: List[int]) -> Iterator[int]: + assert length >= 0 + assert 0 <= number < factorial[length - 1] * length + candidates = list(range(length)) + for val in range(1, length + 1): + division, number = divmod(number, factorial[length - val]) + yield candidates.pop(division) + + ind2perm = unrank + + def get_perm(self) -> "Perm": + """Returns the permutation part of the pattern. + + Examples: + >>> Perm((3,2,1,0)).get_perm() + Perm((3, 2, 1, 0)) + """ + return self + + def direct_sum(self, *others: "Perm") -> "Perm": + """Return the direct sum of two or more perms. + + Examples: + >>> Perm((0,)).direct_sum(Perm((1, 0))) + Perm((0, 2, 1)) + >>> Perm((0,)).direct_sum(Perm((1, 0)), Perm((2, 1, 0))) + Perm((0, 2, 1, 5, 4, 3)) + """ + result = list(self) + shift = len(self) + for other in others: + result.extend(val + shift for val in other) + shift += len(other) + return Perm(result) + + def skew_sum(self, *others: "Perm") -> "Perm": + """Return the skew sum of two or more perms. + + Examples: + >>> Perm((0,)).skew_sum(Perm((0, 1))) + Perm((2, 0, 1)) + >>> Perm((0,)).skew_sum(Perm((0, 1)), Perm((2, 1, 0))) + Perm((5, 3, 4, 2, 1, 0)) + """ + shift = sum(len(other) for other in others) + result = [val + shift for val in self] + for other in others: + shift -= len(other) + result.extend(val + shift for val in other) + return Perm(result) + + def compose(self, *others: "Perm") -> "Perm": + """Return the composition of two or more perms. + + Examples: + >>> Perm((0, 3, 1, 2)).compose(Perm((2, 1, 0, 3))) + Perm((1, 3, 0, 2)) + >>> Perm((1, 0, 2)).compose(Perm((0, 1, 2)), Perm((2, 1, 0))) + Perm((2, 0, 1)) + """ + assert all( + isinstance(other, Perm) and len(other) == len(self) for other in others + ) + return Perm(self._composed_value(idx, *others) for idx in range(len(self))) + + def _composed_value(self, idx: int, *others: "Perm") -> int: + for other in reversed(others): + idx = other[idx] + return self[idx] + + multiply = compose + + def insert( + self, index: Optional[int] = None, new_element: Optional[int] = None + ) -> "Perm": + """Return the perm acquired by adding a new element at index. The index defaults + to the right end and value defaults to len(self). + + Examples: + >>> Perm((0, 1)).insert() + Perm((0, 1, 2)) + >>> Perm((0, 1)).insert(0) + Perm((2, 0, 1)) + >>> Perm((2, 0, 1)).insert(2, 1) + Perm((3, 0, 1, 2)) + """ + n = len(self) + if index is None: + index = n + 1 + if new_element is None: + new_element = n + assert 0 <= index <= n + 1 + assert 0 <= new_element <= n + slice_1 = ( + val if val < new_element else val + 1 + for val in itertools.islice(self, index) + ) + slice_2 = ( + val if val < new_element else val + 1 + for val in itertools.islice(self, index, n) + ) + return Perm(itertools.chain(slice_1, (new_element,), slice_2)) + + def remove(self, index: Optional[int] = None) -> "Perm": + """Return the perm acquired by removing an element at a specified index. It + defaults to the greatest element. + + Examples: + >>> Perm((2, 0, 1)).remove() + Perm((0, 1)) + >>> Perm((3, 0, 1, 2)).remove(0) + Perm((0, 1, 2)) + >>> Perm((2, 0, 1)).remove(2) + Perm((1, 0)) + >>> Perm((0,)).remove(0) + Perm(()) + """ + if index is None: + return self.remove_element() + selected = self[index] + return Perm( + val if val < selected else val - 1 for val in self if val != selected + ) + + def remove_element(self, selected: Optional[int] = None) -> "Perm": + """Return the perm acquired by removing a specific element from self. It + defaults to the largest element. + + Examples: + >>> Perm((3, 0, 1, 2)).remove_element() + Perm((0, 1, 2)) + >>> Perm((3, 0, 2, 1)).remove_element(0) + Perm((2, 1, 0)) + """ + if selected is None: + if len(self) == 0: + return self + selected = len(self) - 1 + assert 0 <= selected < len(self) + return Perm( + val if val < selected else val - 1 for val in self if val != selected + ) + + def inflate(self, components: Iterable["Perm"]) -> "Perm": + """Inflate elements of the permutation to create a new one. + + Examples: + >>> Perm((0, 1)).inflate([Perm((1, 0)), Perm((2, 1, 0))]) + Perm((1, 0, 4, 3, 2)) + >>> Perm((1, 0, 2)).inflate([None, Perm((0, 1)), Perm((0, 1))]) + Perm((2, 0, 1, 3, 4)) + >>> Perm((0, 1)).inflate([Perm(), Perm()]) + Perm(()) + """ + components = tuple(components) + assert len(components) == len(self) + shift = 0 + shifts = [0] * len(self) + for index in self.inverse(): + shifts[index] = shift + component = components[index] + shift += 1 if component is None else len(component) + perm_elements: List[int] = [] + for index, component in enumerate(components): + if component is None: + perm_elements.append(shifts[index]) + else: + shift = shifts[index] + perm_elements.extend(element + shift for element in component) + return Perm(perm_elements) + + def inverse(self) -> "Perm": + """Return the inverse of the perm self. + + Examples: + >>> Perm((1, 2, 5, 0, 3, 4)).inverse() + Perm((3, 0, 1, 4, 5, 2)) + >>> Perm((2, 0, 1)).inverse().inverse() == Perm((2, 0, 1)) + True + >>> Perm((0, 1)).inverse() + Perm((0, 1)) + """ + result = [0] * len(self) + for idx, val in enumerate(self): + result[val] = idx + return Perm(result) + + def reverse(self) -> "Perm": + """Return the reverse of the perm self. + + Examples: + >>> Perm((1, 2, 5, 0, 3, 4)).reverse() + Perm((4, 3, 0, 5, 2, 1)) + >>> Perm((0, 1)).reverse() + Perm((1, 0)) + """ + return Perm(reversed(self)) + + def complement(self) -> "Perm": + """Return the complement of the perm self. + + Examples: + >>> Perm((1, 2, 3, 0, 4)).complement() + Perm((3, 2, 1, 4, 0)) + >>> Perm((2, 0, 1)).complement() + Perm((0, 2, 1)) + """ + base = len(self) - 1 + return Perm(base - element for element in self) + + def reverse_complement(self) -> "Perm": + """Return the reverse complement of self. Equivalent to two left or right + rotations. + + Examples: + >>> Perm((1, 2, 3, 0, 4)).reverse_complement() + Perm((0, 4, 1, 2, 3)) + >>> Perm((2, 0, 1)).reverse_complement() + Perm((1, 2, 0)) + """ + base = len(self) - 1 + return Perm(base - element for element in reversed(self)) + + def shift_right(self, times: int = 1) -> "Perm": + """Return self shifted times steps to the right. If shift is negative, shifted + to the left. + + Examples: + >>> Perm((0, 1, 2)).shift_right() + Perm((2, 0, 1)) + >>> Perm((0, 1, 2)).shift_right(-4) + Perm((1, 2, 0)) + """ + if len(self) == 0: + return self + times = times % len(self) + if times == 0: + return self + index = len(self) - times + slice_1 = itertools.islice(self, index) + slice_2 = itertools.islice(self, index, len(self)) + return Perm(itertools.chain(slice_2, slice_1)) + + def shift_left(self, times: int = 1) -> "Perm": + """Return self shifted times steps to the left. If shift is negative, shifted + to the right. + + Examples: + >>> Perm((0, 1, 2)).shift_left() + Perm((1, 2, 0)) + >>> Perm((0, 1, 2)).shift_left(-4) + Perm((2, 0, 1)) + """ + return self.shift_right(-times) + + shift = shift_right + cyclic_shift = shift_right + cyclic_shift_right = shift_right + cyclic_shift_left = shift_left + + def shift_up(self, times: int = 1) -> "Perm": + """Return self shifted times steps up. If times is negative, shifted down. + + Examples: + >>> Perm((0, 1, 2, 3)).shift_up(1) + Perm((1, 2, 3, 0)) + >>> Perm((0, 1, 2, 3)).shift_up(-7) + Perm((1, 2, 3, 0)) + >>> Perm((0,)).shift_up(1234) + Perm((0,)) + """ + if len(self) == 0: + return self + times = times % len(self) + if times == 0: + return self + bound = len(self) + return Perm((val + times) % bound for val in self) + + def shift_down(self, times: int = 1) -> "Perm": + """Return self shifted times steps down. If times is negative, shifted up. + + Examples: + >>> Perm((0, 1, 2, 3)).shift_down(1) + Perm((3, 0, 1, 2)) + >>> Perm((0, 1, 2, 3)).shift_down(-7) + Perm((3, 0, 1, 2)) + >>> Perm((0,)).shift_down(1234) + Perm((0,)) + """ + return self.shift_up(-times) + + flip_horizontal = complement + flip_vertical = reverse + flip_diagonal = inverse + + def flip_antidiagonal(self) -> "Perm": + """Return self flipped along the antidiagonal. + + Examples: + >>> Perm((3, 2, 0, 1)).flip_antidiagonal() + Perm((3, 2, 0, 1)) + >>> Perm((1, 2, 3, 0, 4)).flip_antidiagonal() + Perm((0, 2, 3, 4, 1)) + >>> Perm((1, 2, 0, 3)).flip_antidiagonal() + Perm((0, 2, 3, 1)) + """ + n = len(self) + result = [0] * n + for idx, val in ((n - val - 1, n - idx - 1) for idx, val in enumerate(self)): + result[idx] = val + return Perm(result) + + def rotate(self, times: int = 1) -> "Perm": + """Rotate the permutation. The parameter determines how often it is rotated. + A negative value rotates it to the left. + + Examples: + >>> Perm((0, 4, 1, 3, 2)).rotate(-3) + Perm((4, 2, 0, 1, 3)) + >>> Perm((0, 4, 1, 3, 2)).rotate(-2) + Perm((2, 1, 3, 0, 4)) + >>> Perm((0, 4, 1, 3, 2)).rotate(-1) + Perm((1, 3, 4, 2, 0)) + >>> Perm((0, 4, 1, 3, 2)).rotate(0) + Perm((0, 4, 1, 3, 2)) + >>> Perm((0, 4, 1, 3, 2)).rotate(1) + Perm((4, 2, 0, 1, 3)) + >>> Perm((0, 4, 1, 3, 2)).rotate() + Perm((4, 2, 0, 1, 3)) + >>> Perm((0, 4, 1, 3, 2)).rotate(2) + Perm((2, 1, 3, 0, 4)) + >>> Perm((0, 4, 1, 3, 2)).rotate(3) + Perm((1, 3, 4, 2, 0)) + """ + times = times % 4 + if times == 0: + return self + if times == 2: + return self.reverse_complement() + n = len(self) + result = [0] * n + if times == 1: + for idx, val in enumerate(self): + result[val] = n - idx - 1 + else: + for idx, val in enumerate(self): + result[n - val - 1] = idx + return Perm(result) + + def all_syms(self) -> Tuple["Perm", ...]: + """Returns all symmetries of the permutation in a PermSet, all possible + combinations of reverse, complement and inverse. + + Examples: + >>> sorted(Perm((0, 2, 1)).all_syms()) + [Perm((0, 2, 1)), Perm((1, 0, 2)), Perm((1, 2, 0)), Perm((2, 0, 1))] + """ + syms = {self, self.inverse()} + curr: "Perm" = self + for _ in range(3): + curr = curr.rotate() + syms.update((curr, curr.inverse())) + return tuple(syms) + + def is_increasing(self) -> bool: + """Return True if the perm is increasing, and False otherwise. + + Examples: + >>> Perm((0, 2, 1, 3)).is_increasing() + False + >>> Perm((0, 1)).is_increasing() + True + """ + return all(idx == val for idx, val in enumerate(self)) + + def is_decreasing(self) -> bool: + """Return True if the perm is decreasing, and False otherwise. + + Examples: + >>> Perm((3, 2, 0, 1)).is_decreasing() + False + >>> Perm((3, 2, 1, 0)).is_decreasing() + True + """ + n = len(self) + return all(val == n - idx - 1 for idx, val in enumerate(self)) + + def count_fixed_points(self) -> int: + """Return the number of fixed points in self. + + Examples: + >>> Perm((0, 1, 4, 3, 2)).count_fixed_points() + 3 + >>> Perm((0, 1, 2, 3, 4)).count_fixed_points() + 5 + >>> Perm((3, 2, 1, 0)).count_fixed_points() + 0 + """ + return sum(1 for _ in self.fixed_points()) + + def fixed_points(self) -> Iterator[int]: + """Yield the index of the fixed points in self. + + Examples: + >>> tuple(Perm((0, 2, 1, 3)).fixed_points()) + (0, 3) + >>> tuple(Perm((0, 1, 4, 3, 2)).fixed_points()) + (0, 1, 3) + """ + return (idx for idx, val in enumerate(self) if idx == val) + + def strong_fixed_points(self) -> Iterator[int]: + """Yield the index of the strong fixed points in self. + + Examples: + >>> tuple(Perm((0, 2, 1, 3)).strong_fixed_points()) + (0, 3) + >>> tuple(Perm((0, 1, 4, 3, 2)).strong_fixed_points()) + (0, 1) + """ + return (idx for idx in self.ltrmax() if idx == self[idx]) + + def is_skew_decomposable(self) -> bool: + """Determines whether the permutation is expressible as the skew sum of + two permutations. + + >>> p = Perm.random(8).direct_sum(Perm.random(12)) + >>> p.skew_decomposable() + False + >>> p.complement().skew_decomposable() + True + """ + n = len(self) + return any( + set(range(n - i, n)) == set(itertools.islice(self, i)) for i in range(1, n) + ) + + skew_decomposable = is_skew_decomposable + + def is_sum_decomposable(self) -> bool: + """Determines whether the permutation is expressible as the direct sum of + two permutations. + + >>> p = Perm.random(4).direct_sum(Perm.random(15)) + >>> p.sum_decomposable() + True + >>> p.reverse().sum_decomposable() + False + """ + return any( + set(range(i)) == set(itertools.islice(self, i)) for i in range(1, len(self)) + ) + + sum_decomposable = is_sum_decomposable + + def descents(self) -> Iterator[int]: + """Yield the 0-based descents of self. + + Examples: + >>> tuple(Perm((0, 1, 3, 2, 4)).descents()) + (2,) + >>> tuple(Perm((3, 2, 1, 0)).descents()) + (0, 1, 2) + >>> tuple(Perm((0, 1, 2)).descents()) + () + """ + return ( + idx + for idx, (prev, curr) in enumerate( + zip(self, itertools.islice(self, 1, None)) + ) + if prev > curr + ) + + def descent_set(self) -> List[int]: + """Return the list of descents of self. + + Examples: + >>> Perm((0, 1, 3, 2, 4)).descent_set() + [2] + >>> Perm((3, 2, 1, 0)).descent_set() + [0, 1, 2] + >>> Perm((0, 1, 2)).descent_set() + [] + """ + return list(self.descents()) + + def count_descents(self) -> int: + """Count the number of descents of self. + Examples: + >>> Perm((0, 1, 3, 2, 4)).count_descents() + 1 + >>> Perm((3, 2, 1, 0)).count_descents() + 3 + >>> Perm((0, 1, 2)).count_descents() + 0 + """ + return sum(1 for _ in self.descents()) + + num_descents = count_descents + + def ascents(self) -> Iterator[int]: + """Yield the 0-based ascent of self. + + Examples: + >>> tuple(Perm((0, 1, 3, 2, 4)).ascents()) + (0, 1, 3) + >>> tuple(Perm((0, 4, 3, 2, 1)).ascents()) + (0,) + >>> tuple(Perm((3, 2, 1, 0)).ascents()) + () + """ + return ( + idx + for idx, (prev, curr) in enumerate( + zip(self, itertools.islice(self, 1, None)) + ) + if prev < curr + ) + + def ascent_set(self) -> List[int]: + """Return the list of ascents of self. + + Examples: + >>> Perm((0, 1, 3, 2, 4)).ascent_set() + [0, 1, 3] + >>> Perm((0, 4, 3, 2, 1)).ascent_set() + [0] + >>> Perm((3, 2, 1, 0)).ascent_set() + [] + """ + return list(self.ascents()) + + def count_ascents(self) -> int: + """Count the number of ascents in self. + + Examples: + >>> Perm((0, 1, 3, 2, 4)).count_ascents() + 3 + >>> Perm((0, 4, 3, 2, 1)).count_ascents() + 1 + >>> Perm((3, 2, 1, 0)).count_ascents() + 0 + """ + return sum(1 for _ in self.ascents()) + + num_ascents = count_ascents + + def peaks(self) -> Iterator[int]: + """Yield the indices of the peaks of self. The i-th element of a perm is a peak + if self[i-1] < self[i] > self[i+1]. + + Examples: + >>> tuple(Perm((5, 3, 4, 0, 2, 1)).peaks()) + (2, 4) + >>> tuple(Perm((1, 2, 0)).peaks()) + (1,) + >>> tuple(Perm((2, 1, 0)).peaks()) + () + """ + return ( + idx + 1 + for idx, (prev, curr, nxt) in enumerate( + zip( + itertools.islice(self, 0, None), + itertools.islice(self, 1, None), + itertools.islice(self, 2, None), + ) + ) + if prev < curr > nxt + ) + + def peak_list(self) -> List[int]: + """Return the list of peaks of self. + + Examples: + >>> Perm((5, 3, 4, 0, 2, 1)).peak_list() + [2, 4] + >>> Perm((1, 2, 0)).peak_list() + [1] + >>> Perm((2, 1, 0)).peak_list() + [] + """ + return list(self.peaks()) + + def count_peaks(self) -> int: + """Count the number of peaks of self. + + Examples: + >>> Perm((5, 3, 4, 0, 2, 1)).count_peaks() + 2 + >>> Perm((1, 2, 0)).count_peaks() + 1 + >>> Perm((2, 1, 0)).count_peaks() + 0 + """ + return sum(1 for _ in self.peaks()) + + num_peaks = count_peaks + + def valleys(self) -> Iterator[int]: + """Yield the indices of the valleys of self. The i-th element of a perm is a + valley if self[i-1] > self[i] < self[i+1]. + + Examples: + >>> tuple(Perm((5, 3, 4, 0, 2, 1)).valleys()) + (1, 3) + >>> tuple(Perm((2, 0, 1)).valleys()) + (1,) + >>> tuple(Perm((1, 2, 0)).valleys()) + () + """ + return ( + idx + 1 + for idx, (prev, curr, nxt) in enumerate( + zip( + itertools.islice(self, 0, None), + itertools.islice(self, 1, None), + itertools.islice(self, 2, None), + ) + ) + if prev > curr < nxt + ) + + def valley_list(self) -> List[int]: + """Return the list of valleys of self. + + Examples: + >>> Perm((5, 3, 4, 0, 2, 1)).valley_list() + [1, 3] + >>> Perm((2, 0, 1)).valley_list() + [1] + >>> Perm((1, 2, 0)).valley_list() + [] + """ + return list(self.valleys()) + + def count_valleys(self) -> int: + """Count the number of valleys of self. + + Examples: + >>> Perm((5, 3, 4, 0, 2, 1)).count_valleys() + 2 + >>> Perm((2, 0, 1)).count_valleys() + 1 + >>> Perm((1, 2, 0)).count_valleys() + 0 + """ + return sum(1 for _ in self.valleys()) + + num_valleys = count_valleys + + def bends(self) -> Iterator[int]: + """Yield the indices at which the permutation changes direction. That + is, the number of non-monotone consecutive triples of the permutation. + A permutation p can be expressed as the concatenation of len(p.bends()) + + 1 monotone segments. + + Examples: + >>> list(Perm((5, 3, 4, 0, 2, 1)).bends()) + [1, 2, 3, 4] + >>> list(Perm((2, 0, 1)).bends()) + [1] + """ + return ( + idx + 1 + for idx, (prev, curr, nxt) in enumerate( + zip( + itertools.islice(self, 0, None), + itertools.islice(self, 1, None), + itertools.islice(self, 2, None), + ) + ) + if (prev < curr > nxt) or (prev > curr < nxt) + ) + + def bend_list(self) -> List[int]: + """Returns the list of indices at which the permutation changes + direction. That is, the number of non-monotone consecutive triples of + the permutation. A permutation p can be expressed as the concatenation + of len(p.bend_list()) + 1 monotone segments. + + Examples: + >>> Perm((5, 3, 4, 0, 2, 1)).bend_list() + [1, 2, 3, 4] + >>> Perm((2, 0, 1)).bend_list() + [1] + """ + return list(self.bends()) + + def order(self) -> int: + """Returns the order of the permutation. + + Examples: + >>> Perm((4, 3, 5, 0, 2, 1)).order() + 6 + >>> Perm((0, 1, 2)).order() + 1 + """ + acc = 1 + for cycle in map(len, self.cycle_decomp()): + acc = (acc * cycle) // math.gcd(acc, cycle) + return acc + + def ltrmin(self) -> Iterator[int]: + """Returns the positions of the left-to-right minima. + + Examples: + >>> list(Perm((2, 4, 3, 0, 1)).ltrmin()) + [0, 3] + """ + min_val = len(self) + for idx, val in enumerate(self): + if val < min_val: + min_val = val + yield idx + + def rtlmin(self) -> Iterator[int]: + """Returns the positions of the right-to-left minima. + + Examples: + >>> list(Perm((2, 0, 4, 1, 5, 3)).rtlmin()) + [1, 3, 5] + """ + yield from reversed(self._rtlmin_reverse_list()) + + def _rtlmin_reverse_list(self) -> List[int]: + lis, (n, min_val) = [], (len(self),) * 2 + for idx, val in enumerate(reversed(self)): + if val < min_val: + min_val = val + lis.append(n - idx - 1) + return lis + + def ltrmax(self) -> Iterator[int]: + """Returns the positions of the left-to-right maxima. + + Examples: + >>> list(Perm((2, 0, 4, 1, 5, 3)).ltrmax()) + [0, 2, 4] + """ + max_val = -1 + for idx, val in enumerate(self): + if val > max_val: + max_val = val + yield idx + + def rtlmax(self) -> Iterator[int]: + """Returns the positions of the right-to-left maxima. + + Examples: + >>> list(Perm((2, 4, 3, 0, 1)).rtlmax()) + [1, 2, 4] + """ + yield from reversed(self._rtlmax_reverse_list()) + + def _rtlmax_reverse_list(self) -> List[int]: + lis, n, max_val = [], len(self), -1 + for idx, val in enumerate(reversed(self)): + if val > max_val: + max_val = val + lis.append(n - idx - 1) + return lis + + def count_ltrmin(self) -> int: + """Counts the number of left-to-right minimas. + + Example: + >>> Perm((2, 4, 3, 0, 1)).count_ltrmin() + 2 + """ + return sum(1 for _ in self.ltrmin()) + + def count_ltrmax(self) -> int: + """Counts the number of left-to-right minimas. + + Example: + >>> Perm((2, 4, 3, 0, 1)).count_ltrmin() + 2 + """ + return sum(1 for _ in self.ltrmax()) + + def count_rtlmin(self) -> int: + """Counts the number of left-to-right minimas. + + Example: + >>> Perm((2, 4, 3, 0, 1)).count_ltrmin() + 2 + """ + return len(self._rtlmin_reverse_list()) + + def count_rtlmax(self) -> int: + """Counts the number of left-to-right minimas. + + Example: + >>> Perm((2, 4, 3, 0, 1)).count_ltrmin() + 2 + """ + return len(self._rtlmax_reverse_list()) + + num_ltrmin = count_ltrmin + + def count_inversions(self) -> int: + """Returns the number of inversions of the permutation, i.e., the + number of pairs i,j such that i < j and self(i) > self(j). + + Example: + >>> Perm((3, 0, 2, 1)).count_inversions() + 4 + >>> Perm.monotone_decreasing(6).count_inversions() == 5*6 / 2 + True + >>> Perm.monotone_increasing(7).count_inversions() + 0 + """ + bit_len = len(self) + 1 + bit = [0] * bit_len + for element in reversed(self): + bit_index = element + 1 + # Count lesser elements to the right + while element: + bit[0] += bit[element] + # Flip the right most set bit + element &= element - 1 + # Increment frequency for current element + while bit_index < bit_len: + bit[bit_index] += 1 + # Increase index by the largest power of two that divides it + bit_index += bit_index & -bit_index + return bit[0] + + def inversions(self) -> Iterator[Tuple[int, int]]: + """Yield the inversions of the permutation, i.e., the pairs i,j + such that i < j and self(i) > self(j). + + Example: + >>> tuple(Perm((3, 0, 2, 1)).inversions()) + ((0, 1), (0, 2), (0, 3), (2, 3)) + """ + n = len(self) + for i, prev in enumerate(self): + for j in range(i + 1, n): + if prev > self[j]: + yield i, j + + def count_non_inversions(self) -> int: + """Returns the number of non_inversions of the permutation, i.e., the + number of pairs i,j such that i < j and self[i] < self[j]. + + Examples: + >>> Perm((3, 0, 2, 1, 4)).count_non_inversions() + 6 + >>> Perm.monotone_increasing(7).count_non_inversions() == (6 * 7)/2 + True + """ + n = len(self) + return n * (n - 1) // 2 - self.count_inversions() + + def non_inversions(self) -> Iterator[Tuple[int, int]]: + """Yields the non_inversions of the permutation, i.e., the pairs i,j + such that i < j and self[i] < self[j]. + + Examples: + >>> tuple(Perm((3, 0, 2, 1, 4)).non_inversions()) + ((0, 4), (1, 2), (1, 3), (1, 4), (2, 4), (3, 4)) + """ + n = len(self) + for i, prev in enumerate(self): + for j in range(i + 1, n): + if prev < self[j]: + yield i, j + + def min_gapsize(self) -> int: + """Returns the minimum gap between any two entries in the permutation + (computed with the taxicab metric). + + Examples: + >>> Perm((2, 0, 3, 1)).min_gapsize() + 3 + """ + return min( + abs(i - j) + abs(self[i] - self[j]) + for i, j in itertools.combinations(range(len(self)), 2) + ) + + def all_bonds(self) -> Iterator[int]: + """Generate all bonds, that is the adjacent locations with adjacent values. + + Examples: + >>> list(Perm((0, 1, 2)).all_bonds()) + [0, 1] + >>> list(Perm((2, 1, 0)).all_bonds()) + [0, 1] + >>> list(Perm((4, 0, 3, 2, 1, 5)).all_bonds()) + [2, 3] + """ + return ( + idx + for idx, (prev, curr) in enumerate( + zip(self, itertools.islice(self, 1, None)) + ) + if curr == prev + 1 or prev == curr + 1 + ) + + def count_bonds(self) -> int: + """Counts the number of bonds, that is the number of adjacent locations + with adjacent values. + + Examples: + >>> Perm((0, 1, 2)).count_bonds() + 2 + >>> Perm((2, 1, 0)).count_bonds() + 2 + >>> Perm((4, 0, 3, 2, 1, 5)).count_bonds() + 2 + """ + return sum(1 for _ in self.all_bonds()) + + num_bonds = count_bonds + bonds = count_bonds + + def inc_bonds(self) -> Iterator[int]: + """Yields the indices of the increasing bonds, that is the indices of + the ascents with adjacent values. + + Examples: + >>> list(Perm((2, 3, 4, 5, 0, 1)).inc_bonds()) + [0, 1, 2, 4] + """ + return ( + idx + for idx, (prev, curr) in enumerate( + zip(self, itertools.islice(self, 1, None)) + ) + if curr == prev + 1 + ) + + def count_inc_bonds(self) -> int: + """Counts the number of increasing bonds. + + Examples: + >>> Perm((0, 2, 3, 1)).count_inc_bonds() + 1 + >>> Perm((2, 3, 4, 5, 0, 1)).count_inc_bonds() + 4 + """ + return sum(1 for _ in self.inc_bonds()) + + num_inc_bonds = count_inc_bonds + + def dec_bonds(self) -> Iterator[int]: + """Yields the indices of the decreasing bonds, that is the indices of + the descents with adjacent values. + + Examples: + >>> list(Perm((1, 0, 3, 2, 5, 4)).dec_bonds()) + [0, 2, 4] + """ + return ( + idx + for idx, (prev, curr) in enumerate( + zip(self, itertools.islice(self, 1, None)) + ) + if prev == curr + 1 + ) + + def count_dec_bonds(self) -> int: + """Counts the number of decreasing bonds. + + Examples: + >>> Perm((2, 1, 0, 3)).count_dec_bonds() + 2 + >>> Perm((1, 0, 3, 2, 5, 4)).count_dec_bonds() + 3 + """ + return sum(1 for _ in self.dec_bonds()) + + num_dec_bonds = count_dec_bonds + + def major_index(self) -> int: + """Returns the major index of the permutation, that is the sum of the + positions of the descents of the permutation. + + Examples: + >>> Perm((3, 1, 2, 4, 0)).major_index() + 5 + >>> Perm((0, 2, 1)).major_index() + 2 + """ + return sum(1 + desc for desc in self.descents()) + + def maximal_decreasing_run(self) -> int: + """Returns the longest decreasing run of consecutive elements starting + from the leargest. + + Examples: + >>> Perm((3, 1, 2, 4, 0)).maximal_decreasing_run() + 1 + >>> Perm((0, 2, 1)).maximal_decreasing_run() + 2 + >>> Perm((5, 0, 4, 1, 2, 3)).maximal_decreasing_run() + 3 + """ + n = len(self) + next_val, max_not_included = n - 1, -1 + for val in self: + if val == next_val: + next_val -= 1 + elif val > max_not_included: + max_not_included = val + if next_val < max_not_included: + break + return n - next_val - 1 + + def longestruns_ascending(self) -> Tuple[int, List[int]]: + """Returns the longest ascending runs in the permutation as a pair of + the length and a list of the starting indices. + + Examples: + >>> Perm((0, 2, 1, 4, 3, 5)).longestruns_ascending() + (2, [0, 2, 4]) + >>> Perm((4, 3, 0, 1, 2)).longestruns_ascending() + (3, [2]) + >>> Perm((2, 1, 0)).longestruns_ascending() + (1, [0, 1, 2]) + """ + n = len(self) + if n == 0: + return (0, []) + maxi, cur = 1, 0 + res: List[int] = [] + for idx, (prev, curr) in enumerate(zip(self, itertools.islice(self, 1, None))): + if prev < curr: + if idx - cur + 2 > maxi: + res.clear() + maxi = idx - cur + 2 + else: + if idx - cur + 1 == maxi: + res.append(cur) + cur = idx + 1 + if n - cur == maxi: + res.append(cur) + return (maxi, res) + + def longestruns_descending(self) -> Tuple[int, List[int]]: + """Returns the longest descending runs in the permutation as a pair of + the length and a list of the starting indices. + + Examples: + >>> Perm((0, 1, 2, 3)).longestruns_descending() + (1, [0, 1, 2, 3]) + >>> Perm((1, 2, 0)).longestruns_descending() + (2, [1]) + >>> Perm((2, 1, 3, 0)).longestruns_descending() + (2, [0, 2]) + """ + return self.complement().longestruns_ascending() + + def length_of_longestrun_ascending(self) -> int: + """Returns the length of the longest ascending run in the permutation. + + Examples: + >>> Perm((0, 1, 2, 3)).length_of_longestrun_ascending() + 4 + >>> Perm((1, 2, 0)).length_of_longestrun_ascending() + 2 + """ + return self.longestruns_ascending()[0] + + def length_of_longestrun_descending(self) -> int: + """Returns the length of the longest descending run in the permutation. + + Examples: + >>> Perm((0, 1, 2, 3)).length_of_longestrun_descending() + 1 + >>> Perm((1, 2, 0)).length_of_longestrun_descending() + 2 + """ + return self.complement().length_of_longestrun_ascending() + + def cycle_decomp(self) -> Deque[List[int]]: + """Calculates the cycle decomposition of the permutation. Returns a list + of cycles, each of which is represented as a list. + + Examples: + >>> Perm((4, 2, 7, 0, 3, 1, 6, 5)).cycle_decomp() + deque([[4, 3, 0], [6], [7, 5, 1, 2]]) + """ + n = len(self) + remaining_elements = set(range(n)) + cyclelist: Deque[List[int]] = collections.deque() + while remaining_elements: + max_not_seen = max(remaining_elements) + cycle, val = [max_not_seen], self[max_not_seen] + remaining_elements.remove(val) + while val != max_not_seen: + cycle.append(val) + val = self(val) + remaining_elements.remove(val) + cyclelist.appendleft(cycle) + return cyclelist + + def count_cycles(self) -> int: + """Returns the number of cycles in the permutation. + + >>> Perm((5, 3, 8, 1, 0, 4, 2, 7, 6)).count_cycles() + 4 + """ + return len(self.cycle_decomp()) + + num_cycles = count_cycles + + def is_involution(self) -> bool: + """Checks if the permutation is an involution, i.e., is equal to it's + own inverse. + + Examples: + >>> Perm((2, 1, 0)).is_involution() + True + >>> Perm((3, 0, 2, 4, 1, 5)).is_involution() + False + """ + return self == self.inverse() + + is_identity = is_increasing + + def rank(self) -> int: + """Computes the rank of a permutation. + Examples: + >>> Perm((0, 1)).rank() + 2 + >>> Perm((0, 2, 1, 3)).rank() + 12 + """ + n, res = len(self), 0 + fact = [1] + for i in range(n): + fact.append(fact[i] * (i + 1)) + vals: List[int] = list() + for idx, val in enumerate(self): + ordered_pos = bisect.bisect_left(vals, val) + res += (val - ordered_pos) * fact[n - idx - 1] + fact[n - idx - 1] + vals.insert(ordered_pos, val) + return res + + perm2ind = rank + + def threepats(self) -> Dict["Perm", int]: + """Returns a dictionary of the number of occurrences of each + permutation pattern of length 3. + + Examples: + >>> res = Perm((2, 1, 0, 3)).threepats() + >>> res[Perm((1, 0, 2))] + 3 + >>> res[Perm((1, 2, 0))] + 0 + """ + return collections.Counter( + Perm.to_standard((left, mid, right)) + for left, mid, right in itertools.combinations(self, 3) + ) + + def fourpats(self) -> Dict["Perm", int]: + """Returns a dictionary of the number of occurrences of each + permutation pattern of length 4. + + Examples: + >>> res = Perm((1, 0, 3, 5, 2, 4)).fourpats() + >>> res[Perm((0, 2, 3, 1))] + 2 + >>> res[Perm((3, 1, 2, 0))] + 0 + """ + return collections.Counter( + Perm.to_standard((left, mid_left, mid_right, right)) + for left, mid_left, mid_right, right in itertools.combinations(self, 4) + ) + + def rank_encoding(self) -> List[int]: + """Returns the rank_encoding of each index in the permutation, the + number of inversions 'caused' by the values at each index. + + Examples: + >>> Perm((3, 0, 2, 1)).rank_encoding() + [3, 0, 1, 0] + >>> Perm((0, 2, 4, 3, 1)).rank_encoding() + [0, 1, 2, 1, 0] + """ + rank_encoding = [0] * len(self) + for left, _ in self.inversions(): + rank_encoding[left] += 1 + return rank_encoding + + def sum_decomposition(self) -> List["Perm"]: + """Return the sum decomposition of the permutation. + + Examples: + >>> Perm((0, 1, 2)).sum_decomposition() + [Perm((0,)), Perm((0,)), Perm((0,))] + >>> Perm((1, 2, 0, 4, 3)).sum_decomposition() + [Perm((1, 2, 0)), Perm((1, 0))] + """ + res: List[Perm] = [] + max_val = -1 + curr_block_start_idx = 0 + for idx, val in enumerate(self): + max_val = max(max_val, val) + if idx == max_val: + res.append(Perm.to_standard(self[curr_block_start_idx : idx + 1])) + curr_block_start_idx = idx + 1 + return res + + def skew_decomposition(self) -> List["Perm"]: + """Return the skew decomposition of the permutation. + + Examples: + >>> Perm((5, 3, 4, 1, 0, 2)).skew_decomposition() + [Perm((0,)), Perm((0, 1)), Perm((1, 0, 2))] + >>> Perm((5, 1, 2, 0, 3, 4)).skew_decomposition() + [Perm((0,)), Perm((1, 2, 0, 3, 4))] + """ + n = len(self) + res: List[Perm] = [] + min_val = n + 1 + curr_block_start_idx = 0 + for idx, val in enumerate(self): + min_val = min(min_val, val) + if n - idx - 1 == min_val: + res.append(Perm.to_standard(self[curr_block_start_idx : idx + 1])) + curr_block_start_idx = idx + 1 + return res + + def block_decomposition_as_pattern(self) -> List["Perm"]: + """Return block decomposition as a list of perm. + + Examples: + >>> sorted(Perm((4, 1, 0, 5, 2, 3)).block_decomposition_as_pattern()) + [Perm((0, 1)), Perm((1, 0))] + """ + patterns: Set["Perm"] = set() + for length, block in enumerate(self.block_decomposition()): + for start in block: + patterns.add(Perm.to_standard(self[start : start + length])) + return list(patterns) + + def block_decomposition(self) -> List[List[int]]: + """Returns the list of all blocks (intervals) in the permutation that + are of length at least 2. The returned list of lists contains the + indices of blocks of length i in index i. + + Examples: + >>> Perm((5, 3, 0, 1, 2, 4, 7, 6)).block_decomposition() + [[], [], [2, 3, 6], [2], [1], [1], [0], []] + """ + n = len(self) + blocks: List[List[int]] = [[] for i in range(n)] + for idx, val in enumerate(self): + min_val, max_val = val, val + for length in range(2, n - idx + 1): + if length == n: + continue + end = idx + length - 1 + min_val, max_val = min(min_val, self[end]), max(max_val, self[end]) + if max_val - min_val == length - 1: + blocks[length].append(idx) + return blocks + + all_intervals = block_decomposition + decomposition = block_decomposition + + def monotone_block_decomposition( + self, with_ones: bool = False + ) -> Iterator[Tuple[int, int]]: + """Returns the iterator of all monotone blocks(intervals) in the + permutation. Depending on the with_ones parameter it will return the + length 1 blocks. The blocks are pairs of indices, the start and end + index. + + Examples: + >>> list(Perm((2, 6, 3, 7, 4, 5, 1, 0)).monotone_block_decomposition()) + [(4, 5), (6, 7)] + >>> list(Perm((2, 6, 3, 4, 5, 1, 0)).monotone_block_decomposition(True)) + [(0, 0), (1, 1), (2, 4), (5, 6)] + >>> list(Perm((0, 1, 2, 3, 4, 5)).monotone_block_decomposition()) + [(0, 5)] + """ + yield from self._block_decomposition_generator( + lambda curr, prev: abs(curr - prev) == 1, with_ones + ) + + def monotone_block_decomposition_ascending( + self, with_ones: bool = False + ) -> Iterable[Tuple[int, int]]: + """Returns the iterator of all ascending blocks(intervals) in the + permutation. Depending on the with_ones parameter it will return the + length 1 blocks. The blocks are pairs of indices, the start and end + index. + + Examples: + >>> list(Perm((1, 0, 2, 3)).monotone_block_decomposition_ascending(True)) + [(0, 0), (1, 1), (2, 3)] + >>> list(Perm((0, 2, 1)).monotone_block_decomposition_ascending(False)) + [] + """ + yield from self._block_decomposition_generator( + lambda prev, curr: curr - prev == 1, with_ones + ) + + def monotone_block_decomposition_descending( + self, with_ones: bool = False + ) -> Iterator[Tuple[int, int]]: + """Returns the iterator of all descending blocks(intervals) in the + permutation. Depending on the with_ones parameter it will return the + length 1 blocks. The blocks are pairs of indices, the start and end + index. + + Examples: + >>> list(Perm((2,1,0,5,4,3)).monotone_block_decomposition_descending(True)) + [(0, 2), (3, 5)] + >>> list(Perm((0, 2, 1)).monotone_block_decomposition_descending(False)) + [(1, 2)] + """ + yield from self._block_decomposition_generator( + lambda prev, curr: prev - curr == 1, with_ones + ) + + all_monotone_intervals = monotone_block_decomposition + + def _block_decomposition_generator( + self, comparator: Callable[[int, int], bool], with_ones: bool = False + ) -> Iterator[Tuple[int, int]]: + diff, start, length = (0,) * 3 + for idx, (prev, curr) in enumerate(zip(self, itertools.islice(self, 1, None))): + if comparator(prev, curr) and (length == 0 or curr - prev == diff): + length += 1 + diff = curr - prev + else: + if length > 0 or with_ones: + yield start, start + length + diff, start, length = 0, idx + 1, 0 + if len(self) != 0 and (length > 0 or with_ones): + yield start, start + length + + def contract_inc_bonds(self) -> "Perm": + """Turn entries, consecutive in position and increasing in value, then + standardize the resulting permutation. + + Examples: + >>> Perm((1, 0, 5, 3, 4, 2)).contract_inc_bonds() + Perm((1, 0, 4, 3, 2)) + """ + monblocks = self.monotone_block_decomposition_ascending(with_ones=True) + return Perm.to_standard(self[start] for start, _ in monblocks) + + def contract_dec_bonds(self) -> "Perm": + """Turn entries, consecutive in position and decreasing in value, then + standardize the resulting permutation. + + Examples: + >>> Perm((1, 0, 5, 3, 4, 2)).contract_dec_bonds() + Perm((0, 4, 2, 3, 1)) + """ + monblocks = self.monotone_block_decomposition_descending(with_ones=True) + return Perm.to_standard(self[start] for start, _ in monblocks) + + def contract_bonds(self) -> "Perm": + """Turn entries, consecutive in position and decreasing or increasing in value, + into a single element, then standardize the resulting permutation. + + Examples: + >>> Perm((1, 0, 5, 3, 4, 2)).contract_bonds() + Perm((0, 3, 2, 1)) + """ + monblocks = self.monotone_block_decomposition(with_ones=True) + return Perm.to_standard(self[start] for start, _ in monblocks) + + def monotone_quotient(self) -> "Perm": + """Return the permutation pattern consisting of the starting values of + the monotone blocks in the permutation. Simply contracts the monotone + blocks. + + Examples: + >>> list(Perm((0, 2, 1, 5, 6, 4, 3)).monotone_block_decomposition(True)) + [(0, 0), (1, 2), (3, 4), (5, 6)] + >>> Perm((0, 2, 1, 5, 6, 4, 3)).monotone_quotient() + Perm((0, 1, 3, 2)) + """ + return Perm.to_standard( + ( + self[start] + for start, _ in self.monotone_block_decomposition(with_ones=True) + ) + ) + + def maximum_block(self) -> Tuple[int, int]: + """Finds the biggest interval, and returns (i,j) if one is found, + where i is the size of the interval, and j is the index of the first + entry in the interval. Returns (0,0) if no interval is found, i.e., + if the permutation is simple. + + Example: + >>> Perm((0, 2, 1, 5, 6, 7, 4, 3)).maximum_block() + (7, 1) + """ + blocks = self.block_decomposition() + for length, indexlist in reversed(list(enumerate(blocks))): + if len(indexlist) != 0: + return (length, indexlist[0]) + return (0, 0) + + maximal_interval = maximum_block + simple_location = maximum_block + + def is_simple(self) -> bool: + """Checks if the permutation is simple. + + Example: + >>> Perm((2, 0, 3, 1)).is_simple() + True + >>> Perm((2, 0, 1)).is_simple() + False + """ + return self.simple_location()[0] == 0 + + def is_strongly_simple(self) -> bool: + """Checks if the permutation is strongly simple, that is if the + permutation is simple and any of permutation of one less length in the + downset is simple. + + Example: + >>> Perm((4, 1, 6, 3, 0, 7, 2, 5)).is_strongly_simple() + True + """ + return self.is_simple() and all([patt.is_simple() for patt in self.children()]) + + def children(self) -> List["Perm"]: + """Returns all patterns of length one less than the permutation. One + layer of the downset, also called the shadow. + + Example: + >>> sorted(Perm((2, 0, 1)).children()) + [Perm((0, 1)), Perm((1, 0))] + >>> sorted(Perm((4, 1, 6, 3, 0, 7, 2, 5)).children())[:2] + [Perm((1, 5, 3, 0, 6, 2, 4)), Perm((3, 0, 5, 2, 6, 1, 4))] + """ + return list(set(self.remove(i) for i in range(len(self)))) + + shrink_by_one = children + + def coveredby(self) -> List["Perm"]: + """Returns one layer of the upset of the permutation. + + Examples: + >>> sorted(Perm((0, 1)).coveredby())[:3] + [Perm((0, 1, 2)), Perm((0, 2, 1)), Perm((1, 0, 2))] + """ + n = len(self) + return list(set(self.insert(i, j) for i in range(n + 1) for j in range(n + 1))) + + def count_rtlmax_ltrmin_layers(self) -> int: + """Counts the layers in the right-to-left maxima, left-to-right minima + decomposition. + + Examples: + >>> Perm((2, 7, 3, 1, 4, 8, 6, 0, 5)).count_rtlmax_ltrmin_layers() + 3 + >>> Perm((5, 4, 3, 0, 2, 1)).count_rtlmax_ltrmin_layers() + 1 + """ + return sum(1 for _ in self.rtlmax_ltrmin_decomposition()) + + num_rtlmax_ltrmin_layers = count_rtlmax_ltrmin_layers + + def rtlmax_ltrmin_decomposition(self) -> Iterator[List[int]]: + """Returns the right-to-left maxima, left-to-right minima + decomposition. The decomposition consists of layers, starting with the + first layer which is union of the right-to-left maximas and the + left-to-right minimas and the next layer is defined similarly for the + permutation with the first layer removed and so on. + + Examples: + >>> list(Perm((2, 7, 3, 1, 4, 8, 6, 0, 5)).rtlmax_ltrmin_decomposition()) + [[0, 3, 5, 6, 7, 8], [0, 2], [0]] + >>> list(Perm((5, 4, 3, 0, 2, 1)).rtlmax_ltrmin_decomposition()) + [[0, 1, 2, 3, 4, 5]] + """ + perm = self + while len(perm) > 0: + pos_set = set(itertools.chain(perm.rtlmax(), perm.ltrmin())) + yield sorted(pos_set) + perm = Perm(perm[i] for i in range(len(perm)) if i not in pos_set) + + def contains(self, *patts: "Patt") -> bool: + """Check if self contains patts. + + Examples: + >>> Perm.monotone_decreasing(7).avoids(Perm((0, 1))) + True + >>> Perm((4, 2, 3, 1, 0)).contains(Perm((1, 2, 0))) + True + >>> Perm((0, 1, 2)).contains(Perm((1,0))) + False + >>> pattern1 = Perm((0, 1)) + >>> pattern2 = Perm((2, 0, 1)) + >>> pattern3 = Perm((0, 3, 1, 2)) + >>> Perm((5, 3, 0, 4, 2, 1)).contains(pattern1, pattern2) + True + >>> Perm((5, 3, 0, 4, 2, 1)).contains(pattern2, pattern3) + False + """ + return all(patt in self for patt in patts) + + def avoids(self, *patts: "Patt") -> bool: + """Check if self avoids patts. + + Examples: + >>> Perm.monotone_increasing(8).avoids(Perm((1, 0))) + True + >>> Perm((4, 2, 3, 1, 0)).avoids(Perm((1, 2, 0))) + False + >>> Perm((0, 1, 2)).avoids(Perm((1,0))) + True + >>> pattern1 = Perm((0, 1)) + >>> pattern2 = Perm((2, 0, 1)) + >>> pattern3 = Perm((0, 3, 1, 2)) + >>> pattern4 = Perm((0, 1, 2)) + >>> Perm((5, 3, 0, 4, 2, 1)).avoids(pattern1, pattern2) + False + >>> Perm((5, 3, 0, 4, 2, 1)).avoids(pattern2, pattern3) + False + >>> Perm((5, 3, 0, 4, 2, 1)).avoids(pattern3, pattern4) + True + """ + return all(patt not in self for patt in patts) + + def avoids_set(self, patts: Iterable["Patt"]) -> bool: + """Check if self avoids patts for an iterable of patterns. + + Examples: + >>> (Perm([4, 0, 1, 2, 3]).avoids_set([Perm([2, 1, 0]), Perm([1, 0])])) + False + >>> Perm([4, 0, 1, 2, 3]).avoids_set([Perm([2, 1, 0]), Perm([1, 2, 0])]) + True + """ + return self.avoids(*tuple(patts)) + + def count_occurrences_of(self, patt: "Patt") -> int: + """Count the number of occurrences of patt in self. + + Examples: + >>> Perm((0, 1, 2)).count_occurrences_of(Perm((0, 1))) + 3 + >>> Perm((5, 3, 0, 4, 2, 1)).count_occurrences_of(Perm((2, 0, 1))) + 6 + """ + return patt.count_occurrences_in(self) + + occurrences = count_occurrences_of + + def occurrences_in( + self, patt: "Patt", *args, **kwargs + ) -> Iterator[Tuple[int, ...]]: + """Find all indices of occurrences of self in patt. If the optional colours + are provided, in an occurrences the colours of the patterns have to match the + colours of the permutation. + + Examples: + >>> list(Perm((2, 0, 1)).occurrences_in(Perm((5, 3, 0, 4, 2, 1)))) + [(0, 1, 3), (0, 2, 3), (0, 2, 4), (0, 2, 5), (1, 2, 4), (1, 2, 5)] + >>> list(Perm((1, 0)).occurrences_in(Perm((1, 2, 3, 0)))) + [(0, 3), (1, 3), (2, 3)] + >>> list(Perm((0,)).occurrences_in(Perm((1, 2, 3, 0)))) + [(0,), (1,), (2,), (3,)] + >>> list(Perm().occurrences_in(Perm((1, 2, 3, 0)))) + [()] + """ + self_colours, patt_colours = (None, None) if len(args) < 2 else args + n, patt = len(self), patt.get_perm() + + if n == 0: + yield () + return + if n > len(patt): + return + + # The indices of the occurrence in perm + occurrence_indices = [0] * n + pattern_details = self._pattern_details() + + # Define function that works with the above defined variables + # i is the index of the element in perm that is to be considered + # k is how many elements of the perm have already been added to + # occurrence + def occurrences(i, k): + elements_remaining = len(patt) - i + elements_needed = n - k + + # lfi = left floor index + # lci = left ceiling index + # lbp = lower bound pre-computation + # ubp = upper bound pre-computation + lfi, lci, lbp, ubp = pattern_details[k] + + # Set the bounds for the new element + if lfi == -1: + # The new element of the occurrence must be at least self[k]; + # i.e., the k-th element of the pattern + # In this case, lbp = self[k] + lower_bound = lbp + else: + # The new element of the occurrence must be at least as far + # from its left floor as self[k] is from its left floor + # In this case, lbp = self[k] - self[lfi] + occurrence_left_floor = patt[occurrence_indices[lfi]] + lower_bound = occurrence_left_floor + lbp + if lci == -1: + # The new element of the occurrence must be at least as less + # than its maximum possible element---i.e., len(perm)---as + # self[k] is to its maximum possible element---i.e., len(self) + # In this case, ubp = len(self) - self[k] + upper_bound = len(patt) - ubp + else: + # The new element of the occurrence must be at least as less + # than its left ceiling as self[k] is to its left ceiling + # In this case, ubp = self[lci] - self[k] + upper_bound = patt[occurrence_indices[lci]] - ubp + + # Loop over remaining elements of perm (actually i, the index) + while True: + if elements_remaining < elements_needed: + # Can't form an occurrence with remaining elements + return + element = patt[i] + compare_colours = ( + self_colours is None or patt_colours[i] == self_colours[k] + ) + if compare_colours and lower_bound <= element <= upper_bound: + occurrence_indices[k] = i + if elements_needed == 1: + yield tuple(occurrence_indices) + else: + yield from occurrences(i + 1, k + 1) + i, elements_remaining = i + 1, elements_remaining - 1 + + yield from occurrences(0, 0) + + def occurrences_of(self, patt: "Patt") -> Union[Iterator[Tuple[int, ...]]]: + """Find all indices of occurrences of patt in self. This method is complementary + to permuta.Perm.occurrences_in. It just calls patt.occurrences_in(self) + internally. See permuta.Perm.occurrences_in for documentation. + + Examples: + >>> list(Perm((5, 3, 0, 4, 2, 1)).occurrences_of(Perm((2, 0, 1)))) + [(0, 1, 3), (0, 2, 3), (0, 2, 4), (0, 2, 5), (1, 2, 4), (1, 2, 5)] + >>> list(Perm((1, 2, 3, 0)).occurrences_of(Perm((1, 0)))) + [(0, 3), (1, 3), (2, 3)] + >>> list(Perm((1, 2, 3, 0)).occurrences_of(Perm((0,)))) + [(0,), (1,), (2,), (3,)] + >>> list(Perm((1, 2, 3, 0)).occurrences_of(Perm())) + [()] + """ + return patt.occurrences_in(self) + + def left_floor_and_ceiling(self) -> Iterator[Tuple[int, int]]: + """For each element, return the pair of indices of (largest less, smalllest + greater) to the left, if they exist. If not, -1 is used instead. + + Examples: + >>> list(Perm((2, 5, 0, 3, 6, 4, 7, 1)).left_floor_and_ceiling()) + [(-1, -1), (0, -1), (-1, 0), (0, 1), (1, -1), (3, 1), (4, -1), (2, 0)] + """ + deq: Deque[Tuple[int, int]] = collections.deque() + smallest, biggest = -1, -1 + for idx, val in enumerate(self): + if idx == 0: + deq.append((val, idx)) + smallest, biggest = val, val + yield (-1, -1) + elif val < smallest: + # Rotate until smallest val is at front + while deq[0][0] != smallest: + deq.rotate(-1) + yield (-1, deq[0][1]) + deq.appendleft((val, idx)) + smallest = val + elif val > biggest: + # Rotate until biggest val is at end + while deq[-1][0] != biggest: + deq.rotate(-1) + yield (deq[-1][1], -1) + deq.append((val, idx)) + biggest = val + else: + while not deq[-1][0] <= val <= deq[0][0]: + deq.rotate(1) + yield (deq[-1][1], deq[0][1]) + deq.appendleft((val, idx)) + + def _pattern_details(self) -> List[Tuple[int, int, int, int]]: + if self._cached_pattern_details is None: + self._cached_pattern_details = [ + ( + floor, + ceiling, + val if floor == -1 else val - self[floor], + len(self) - val if ceiling == -1 else self[ceiling] - val, + ) + for val, (floor, ceiling) in zip(self, self.left_floor_and_ceiling()) + ] + return self._cached_pattern_details + + def apply(self, iterable: Iterable[int]) -> Tuple[int, ...]: + """Permute an iterable using the perm. + + Examples: + >>> Perm((4, 1, 2, 0, 3)).apply((1, 2, 3, 4, 5)) + (5, 2, 3, 1, 4) + >>> Perm((4, 1, 2, 0, 3)).apply("abcde") + ('e', 'b', 'c', 'a', 'd') + """ + iterable = tuple(iterable) + assert len(iterable) == len(self) + return tuple(iterable[index] for index in self) + + permute = apply + + def ascii_plot(self, cell_size: int = 1) -> str: + """Return an ascii plot of the given Permutation. + + Examples: + >>> print(Perm((0,1,2)).ascii_plot()) + | | | + -+-+-●- + | | | + -+-●-+- + | | | + -●-+-+- + | | | + """ + assert cell_size >= 0 + empty_char, point_char, n = "+" if cell_size > 0 else " ", "\u25cf", len(self) + array = [[empty_char for i in range(n)] for j in range(n)] + for i in range(n): + array[self[i]][i] = point_char + array.reverse() + lines = [("-" * cell_size).join([""] + line + [""]) + "\n" for line in array] + vline = (" " * cell_size + "|") * n + "\n" + return str((vline * cell_size).join([""] + lines + [""])[:-1]) + + def cycle_notation(self) -> str: + """Returns the cycle notation representation of the permutation. + + Examples: + >>> Perm((5, 3, 0, 1, 2, 4)).cycle_notation() + '( 3 1 ) ( 5 4 2 0 )' + """ + if len(self) == 0: + return "( )" + return " ".join( + f'( {" ".join(str(x) for x in cyc)} )' for cyc in self.cycle_decomp() + ) + + cycles = cycle_notation + + def to_svg(self, image_scale: float = 1.0) -> str: + """Return the svg code to plot the permutation. The image size defaults to + 100x100 pixels and the parameter scales that. + """ + n = len(self) + p_scale = 100 / (n + 1) + i_scale = int(image_scale * 100) + return "".join( + [ + '\n', + "\n".join( + ( + f'\n' + ) + for i in range(1, n + 1) + ), + "\n", + "\n".join( + ( + f'' + ) + for (idx, val) in enumerate(self) + ), + "\n", + ] + ) + + def to_tikz(self) -> str: + """ + Return the tikz code to plot the permutation. + """ + n, tab = len(self), " " + return "".join( + [ + "\\begin{tikzpicture}[scale=.3,baseline=(current bounding box.center)]", + f"\n{tab}\\foreach \\x in {{1,...,{n}}} {{\n{tab*2}", + f"\\draw[ultra thin] (\\x,0)--(\\x,{n+1}); %vline\n{tab*2}", + f"\\draw[ultra thin] (0,\\x)--({n+1},\\x); %hline\n{tab}}}\n{tab}", + f"\n{tab}".join( + f"\\draw[fill=black] ({idx + 1},{val + 1}) circle (5pt);" + for (idx, val) in enumerate(self) + ), + "\n\\end{tikzpicture}", + ] + ) + + def show(self, scale: float = 1.0) -> None: + """Open a browser tab and display permutation graphically. Image can be + enlarged with scale parameter""" + HTMLViewer.open_svg(self.to_svg(image_scale=scale)) + + def __call__(self, value: int) -> int: + assert 0 <= value < len(self) + return self[value] + + def __add__(self, other: object) -> "Perm": + if not isinstance(other, self.__class__): + return NotImplemented + return self.direct_sum(other) + + def __sub__(self, other: object) -> "Perm": + if not isinstance(other, self.__class__): + return NotImplemented + return self.skew_sum(other) + + def __mul__(self, other: object) -> "Perm": + if not isinstance(other, self.__class__): + return NotImplemented + return self.compose(other) + + def __repr__(self) -> "str": + return f"Perm({super(Perm, self).__repr__()})" + + def __str__(self) -> "str": + if not self: + return "\u03B5" + if len(self) <= 10: + return "".join(str(i) for i in self) + return "".join(f"({i})" for i in self) + + def __lt__(self, other: tuple) -> bool: + return (len(self), tuple(self)) < (len(other), tuple(other)) + + def __le__(self, other: tuple) -> bool: + return (len(self), tuple(self)) <= (len(other), tuple(other)) + + def __gt__(self, other: tuple) -> bool: + return other.__lt__(self) + + def __ge__(self, other: tuple) -> bool: + return other.__le__(self) + + def __len__(self) -> int: + return tuple.__len__(self) + + def __contains__(self, patt: object) -> bool: + if isinstance(patt, Patt): + return any(True for _ in patt.occurrences_in(self)) + return False diff --git a/permuta/perm.py b/permuta/perm.py deleted file mode 100644 index b49fecb6..00000000 --- a/permuta/perm.py +++ /dev/null @@ -1,2333 +0,0 @@ -import bisect -import collections -import itertools -import math -import numbers -import operator -import random -from typing import List - -from .interfaces.flippable import Flippable -from .interfaces.patt import Patt -from .interfaces.rotatable import Rotatable -from .interfaces.shiftable import Shiftable -from .misc.iterable_floor_and_ceiling import left_floor_and_ceiling - -__all__ = ("Perm",) - - -class Perm(tuple, Patt, Rotatable, Shiftable, Flippable): - """A perm class.""" - - _TYPE_ERROR = "'{}' object is not a perm" - - # - # Methods returning a single Perm instance - # - - def __new__(cls, iterable=(), check=False): - """Return a Perm instance. - - Args: - cls: - The class of which an instance is requested. - iterable: or - An iterable corresponding to a legal perm. - Also supports passing just a number with unique digits. - - Raises: - TypeError: - Bad argument type. - ValueError: - Bad argument, but correct type. - - Examples: - >>> Perm((0, 3, 1, 2)) - Perm((0, 3, 1, 2)) - >>> Perm(range(5, -1, -1)) - Perm((5, 4, 3, 2, 1, 0)) - >>> Perm("abc", check=True) # Not good - Traceback (most recent call last): - ... - TypeError: ''a'' object is not an integer - """ - return tuple.__new__(cls, iterable) - - def __init__(self, iterable=(), check=False): - # Cache for data used when finding occurrences of self in a perm - self._cached_pattern_details = None - if check: - self._init_checked() - - def _init_checked(self): - """Checks if a suitable iterable given when initialised.""" - used = [False] * len(self) - for value in self: - if not isinstance(value, numbers.Integral): - message = "'{}' object is not an integer".format(repr(value)) - raise TypeError(message) - if not 0 <= value < len(self): - raise ValueError("Element out of range: {}".format(value)) - if used[value]: - raise ValueError("Duplicate element: {}".format(value)) - used[value] = True - - _to_standard_cache = {} - - @classmethod - def to_standard(cls, iterable): - """Return the perm corresponding to iterable. - - Duplicate elements are allowed and become consecutive elements (see - example). - - The standardize alias is supplied for backwards compatibility with - permpy. However, the permpy version did not allow for duplicate - elements. - - Examples: - >>> Perm.to_standard("a2gsv3") - Perm((2, 0, 3, 4, 5, 1)) - >>> Perm.to_standard("caaba") - Perm((4, 0, 1, 3, 2)) - """ - # TODO: Do performance testing - iterable = tuple(iterable) - if iterable not in Perm._to_standard_cache: - result = [None] * len(iterable) - value = 0 - for (index, _) in sorted(enumerate(iterable), key=operator.itemgetter(1)): - result[index] = value - value += 1 - Perm._to_standard_cache[iterable] = cls(result) - return Perm._to_standard_cache[iterable] - - standardize = to_standard # permpy backwards compatibility - from_iterable = to_standard - - @classmethod - def from_integer(cls, integer): - """Return the perm corresponding to the integer given. The permutation - can be given one-based or zero-base but it will be returned in 0-based. - - Examples: - >>> Perm.from_integer(123) - Perm((0, 1, 2)) - >>> Perm.from_integer(321) - Perm((2, 1, 0)) - >>> Perm.from_integer(201) - Perm((2, 0, 1)) - """ - if isinstance(integer, numbers.Integral): - if not 0 <= integer <= 9876543210: - raise ValueError("Illegal perm: {}".format(integer)) - digit_list = [] - if integer == 0: - digit_list.append(integer) - else: - while integer != 0: - digit_list.append(integer % 10) - integer //= 10 - digit_list.reverse() - return Perm.to_standard(digit_list) - else: - raise TypeError("{} is not an integer".format(repr(integer))) - - @classmethod - def from_string(cls, string, check=False): - """Return the perm corresponding to the string given. - - Examples: - >>> Perm.from_string("203451") - Perm((2, 0, 3, 4, 5, 1)) - >>> Perm.from_string("40132") - Perm((4, 0, 1, 3, 2)) - """ - if isinstance(string, str): - if string == "ε": - return cls([], check=check) - else: - return cls(map(int, string), check=check) - # TODO: throw exception when not a string - - @classmethod - def one_based(cls, iterable): - """A way to enter a perm in the traditional permuta way. - - Examples: - >>> Perm.one_based((4, 1, 3, 2)) - Perm((3, 0, 2, 1)) - """ - return cls((element - 1 for element in iterable)) - - one = one_based - proper = one_based - scientific = one_based - - @classmethod - def identity(cls, length): - """Return the identity perm of the specified length. - - Examples: - >>> Perm.identity(0) - Perm(()) - >>> Perm.identity(4) - Perm((0, 1, 2, 3)) - """ - return cls(range(length)) - - @classmethod - def random(cls, length): - """Return a random perm of the specified length. - - Examples: - >>> perm = Perm.random(8) - >>> len(perm) == 8 - True - >>> # TODO: test perm in PermSet(8) - """ - result = list(range(length)) - random.shuffle(result) - return cls(result) - - @classmethod - def monotone_increasing(cls, length): - """Return a monotone increasing perm of the specified length. - - Examples: - >>> Perm.monotone_increasing(0) - Perm(()) - >>> Perm.monotone_increasing(4) - Perm((0, 1, 2, 3)) - """ - return cls(range(length)) - - @classmethod - def monotone_decreasing(cls, length): - """Return a monotone decreasing perm of the specified length. - - Examples: - >>> Perm.monotone_decreasing(0) - Perm(()) - >>> Perm.monotone_decreasing(4) - Perm((3, 2, 1, 0)) - """ - return cls(range(length - 1, -1, -1)) - - @classmethod - def unrank(cls, number, length=None): - """ - - Examples: - >>> Perm.unrank(0) - Perm(()) - >>> Perm.unrank(1) - Perm((0,)) - >>> Perm.unrank(2) - Perm((0, 1)) - >>> Perm.unrank(3) - Perm((1, 0)) - >>> Perm.unrank(4) - Perm((0, 1, 2)) - >>> Perm.unrank(5) - Perm((0, 2, 1)) - >>> Perm.unrank(1, 3) - Perm((0, 2, 1)) - """ - # TODO: Docstring, and do better? Assertions and messages - # Implement readably, nicely, and efficiently - if length is None: - # Work out the length and number from the number given - assert isinstance(number, numbers.Integral) - assert number >= 0 - if number == 0: - return cls() - length = 1 - amount = 1 # Amount of perms of length - while number > amount: - number -= amount - length += 1 - amount *= length - number -= 1 - else: - assert isinstance(length, numbers.Integral) - assert length >= 0 - assert 0 <= number < math.factorial(length) - return cls(Perm.__unrank(number, length)) - - @staticmethod - def __unrank(number, length): - candidates = list(range(length)) - for value in range(1, length + 1): - factorial = math.factorial(length - value) - division = number // factorial - yield candidates.pop(division) - number %= factorial - - ind2perm = unrank # permpy backwards compatibility - - # - # Methods modifying/combining Perm instances - # - - def direct_sum(self, *others): - """Return the direct sum of two or more perms. - - Args: - self: - A perm. - others: argument list - Perms. - - Returns: - The direct sum of all the perms. - - Examples: - >>> Perm((0,)).direct_sum(Perm((1, 0))) - Perm((0, 2, 1)) - >>> Perm((0,)).direct_sum(Perm((1, 0)), Perm((2, 1, 0))) - Perm((0, 2, 1, 5, 4, 3)) - """ - result = list(self) - shift = len(self) - for other in others: - if not isinstance(other, Perm): - raise TypeError(Perm._TYPE_ERROR.format(repr(other))) - result.extend(element + shift for element in other) - shift += len(other) - return Perm(result) - - def skew_sum(self, *others): - """Return the skew sum of two or more perms. - - Args: - self: - A perm. - others: argument list - Perms. - - Returns: - The skew sum of all the perms. - - Examples: - >>> Perm((0,)).skew_sum(Perm((0, 1))) - Perm((2, 0, 1)) - >>> Perm((0,)).skew_sum(Perm((0, 1)), Perm((2, 1, 0))) - Perm((5, 3, 4, 2, 1, 0)) - """ - shift = sum(len(other) for other in others) - result = [element + shift for element in self] - for index in range(len(others)): - other = others[index] - if not isinstance(other, Perm): - raise TypeError(Perm._TYPE_ERROR.format(repr(other))) - shift -= len(other) - result.extend(element + shift for element in other) - return Perm(result) - - def compose(self, *others): - """Return the composition of two or more perms. - - Args: - self: - A perm. - others: argument list - Perms. - - Returns: - The consecutive pointwise application of all the above perms - in reverse order. - - Raises: - TypeError: - An object in the argument list is not a perm. - ValueError: - A perm in the argument list is of the wrong length. - - Examples: - >>> Perm((0, 3, 1, 2)).compose(Perm((2, 1, 0, 3))) - Perm((1, 3, 0, 2)) - >>> Perm((1, 0, 2)).compose(Perm((0, 1, 2)), Perm((2, 1, 0))) - Perm((2, 0, 1)) - """ - for other in others: - if not isinstance(other, Perm): - raise TypeError(Perm._TYPE_ERROR.format(repr(other))) - if len(other) != len(self): - raise ValueError("Perm length mismatch") - result = [None] * len(self) - for value in range(len(self)): - composed_value = value - for other in reversed(others): - composed_value = other[composed_value] - composed_value = self[composed_value] - result[value] = composed_value - return Perm(result) - - multiply = compose - - def insert(self, index=None, new_element=None): - """Return the perm acquired by adding a new element. - - Args: - index: - Where in the perm the value is to occur. - If None, the value defaults to len(self)+1. - new_element: - An integer in the range of 0 to len(self) inclusive. - If None, the element defaults to len(self). - - Returns: - The perm with the added element (and other elements adjusted - as needed). - - Raises: - IndexError: - Index is not valid. - ValueError: - Element passed cannot legally be added to perm. - TypeError: - Element passed is not an integer. - - Examples: - >>> Perm((0, 1)).insert() - Perm((0, 1, 2)) - >>> Perm((0, 1)).insert(0) - Perm((2, 0, 1)) - >>> Perm((2, 0, 1)).insert(2, 1) - Perm((3, 0, 1, 2)) - """ - if index is None: - index = len(self) + 1 - if new_element is None: - new_element = len(self) - else: - if not isinstance(new_element, numbers.Integral): - raise TypeError( - "'{}' object is not an integer".format(repr(new_element)) - ) - if not 0 <= new_element <= len(self): - raise ValueError("Element out of range: {}".format(new_element)) - slice_1 = ( - element if element < new_element else element + 1 - for element in itertools.islice(self, index) - ) - slice_2 = ( - element if element < new_element else element + 1 - for element in itertools.islice(self, index, len(self)) - ) - return Perm(itertools.chain(slice_1, (new_element,), slice_2)) - - def remove(self, index=None): - """Return the perm acquired by removing an element at a specified index. - - Args: - index: - The index of the element to be removed. - If None, the greatest element of the perm is removed. - - Returns: - The perm without the element (and other elements adjusted). - - Raises: - IndexError: - Index is not valid. - - Examples: - >>> Perm((2, 0, 1)).remove() - Perm((0, 1)) - >>> Perm((3, 0, 1, 2)).remove(0) - Perm((0, 1, 2)) - >>> Perm((2, 0, 1)).remove(2) - Perm((1, 0)) - >>> Perm((0,)).remove(0) - Perm(()) - """ - if index is None: - return self.remove_element() - selected = self[index] - return Perm( - element if element < selected else element - 1 - for element in self - if element != selected - ) - - def remove_element(self, selected=None): - """Return the perm acquired by removing a specific element from self. - - Args: - selected: - The element selected to be removed. It is an integer in the - range of 0 to len(self) inclusive. If None, it defaults to - len(self) - 1. - - Returns: - The perm with the selected element removed (and other - elements adjusted as needed). - - Raises: - ValueError: - Selected element does not belong to perm. - TypeError: - Element passed is not an integer. - - Examples: - >>> Perm((3, 0, 1, 2)).remove_element() - Perm((0, 1, 2)) - >>> Perm((3, 0, 2, 1)).remove_element(0) - Perm((2, 1, 0)) - """ - if selected is None: - selected = len(self) - 1 - else: - if not isinstance(selected, numbers.Integral): - raise TypeError("'{}' object is not an integer".format(repr(selected))) - if not 0 <= selected < len(self): - raise ValueError("Element out of range: {}".format(selected)) - return Perm( - element if element < selected else element - 1 - for element in self - if element != selected - ) - - def inflate(self, components): - """Inflate elements of the permutation to create a new one. - - Args: - component: of - This can also be a dict with keys and perms... - - Returns: - - Examples: - >>> Perm((0, 1)).inflate([Perm((1, 0)), Perm((2, 1, 0))]) - Perm((1, 0, 4, 3, 2)) - >>> Perm((1, 0, 2)).inflate([None, Perm((0, 1)), Perm((0, 1))]) - Perm((2, 0, 1, 3, 4)) - >>> Perm((0, 2, 1)).inflate({2: Perm((0, 1, 2))}) - Traceback (most recent call last): - ... - NotImplementedError - >>> # Can also deflate points - >>> Perm((0, 1)).inflate([Perm(), Perm()]) - Perm(()) - """ - # TODO: Spitshine method and docstring - if isinstance(components, collections.abc.Mapping): - raise NotImplementedError - elif isinstance(components, collections.abc.Iterable): - components = tuple(components) - assert len(components) == len(self) - shift = 0 - shifts = [0] * len(self) - for index in self.inverse(): - shifts[index] = shift - component = components[index] - shift += 1 if component is None else len(component) - perm_elements = [] - for index, component in enumerate(components): - if component is None: - perm_elements.append(shifts[index]) - else: - shift = shifts[index] - perm_elements.extend(element + shift for element in component) - return Perm(perm_elements) - else: - raise TypeError - - def contract_inc_bonds(self): - # TODO: test - monblocks = self.monotone_block_decompositon_ascending(with_ones=True) - return Perm.to_standard([start for (start, end) in monblocks]) - - def contract_dec_bonds(self): - # TODO: test - monblocks = self.monotone_block_decompositon_descending(with_ones=True) - return Perm.to_standard([start for (start, end) in monblocks]) - - def contract_bonds(self): - # TODO: reimplement by calling contract_{inc,dec}_bonds or remove - pass - - # - # Methods for basic Perm transforming - # - - def inverse(self): - """Return the inverse of the perm self. - - Examples: - >>> Perm((1, 2, 5, 0, 3, 4)).inverse() - Perm((3, 0, 1, 4, 5, 2)) - >>> Perm((2, 0, 1)).inverse().inverse() == Perm((2, 0, 1)) - True - >>> Perm((0, 1)).inverse() - Perm((0, 1)) - """ - len_perm = len(self) - result = [None] * len_perm - for index in range(len_perm): - result[self[index]] = index - return Perm(result) - - def reverse(self): - """Return the reverse of the perm self. - - Examples: - >>> Perm((1, 2, 5, 0, 3, 4)).reverse() - Perm((4, 3, 0, 5, 2, 1)) - >>> Perm((0, 1)).reverse() - Perm((1, 0)) - """ - return Perm(self[::-1]) - - def complement(self): - """Return the complement of the perm self. - - Examples: - >>> Perm((1, 2, 3, 0, 4)).complement() - Perm((3, 2, 1, 4, 0)) - >>> Perm((2, 0, 1)).complement() - Perm((0, 2, 1)) - """ - base = len(self) - 1 - return Perm(base - element for element in self) - - def reverse_complement(self): - """Return the reverse complement of self. - - Equivalent to two left or right rotations. - - Examples: - >>> Perm((1, 2, 3, 0, 4)).reverse_complement() - Perm((0, 4, 1, 2, 3)) - >>> Perm((2, 0, 1)).reverse_complement() - Perm((1, 2, 0)) - """ - base = len(self) - 1 - return Perm(base - element for element in reversed(self)) - - def shift_right(self, times=1): - """Return self shifted times steps to the right. - - If shift is negative, shifted to the left. - - Examples: - >>> Perm((0, 1, 2)).shift_right() - Perm((2, 0, 1)) - >>> Perm((0, 1, 2)).shift_right(-4) - Perm((1, 2, 0)) - """ - if len(self) == 0: - return self - times = times % len(self) - if times == 0: - return self - index = len(self) - times - slice_1 = itertools.islice(self, index) - slice_2 = itertools.islice(self, index, len(self)) - return Perm(itertools.chain(slice_2, slice_1)) - - def shift_left(self, times=1): - """Return self shifted times steps to the left. - - If shift is negative, shifted to the right. - - Examples: - >>> Perm((0, 1, 2)).shift_left() - Perm((1, 2, 0)) - >>> Perm((0, 1, 2)).shift_left(-4) - Perm((2, 0, 1)) - """ - return self.shift_right(-times) - - shift = shift_right - cyclic_shift = shift_right - cyclic_shift_right = shift_right - cyclic_shift_left = shift_left - - def shift_up(self, times=1): - """Return self shifted times steps up. - - If times is negative, shifted down. - - Examples: - >>> Perm((0, 1, 2, 3)).shift_up(1) - Perm((1, 2, 3, 0)) - >>> Perm((0, 1, 2, 3)).shift_up(-7) - Perm((1, 2, 3, 0)) - >>> Perm((0,)).shift_up(1234) - Perm((0,)) - """ - if len(self) == 0: - return self - times = times % len(self) - if times == 0: - return self - bound = len(self) - return Perm((element + times) % bound for element in self) - - def shift_down(self, times=1): - """Return self shifted times steps down. - - If times is negative, shifted up. - - Examples: - >>> Perm((0, 1, 2, 3)).shift_down(1) - Perm((3, 0, 1, 2)) - >>> Perm((0, 1, 2, 3)).shift_down(-7) - Perm((3, 0, 1, 2)) - >>> Perm((0,)).shift_down(1234) - Perm((0,)) - """ - return self.shift_up(-times) - - def flip_horizontal(self): - """Return self flipped horizontally. - - Examples: - >>> Perm((1, 2, 3, 0, 4)).flip_horizontal() - Perm((3, 2, 1, 4, 0)) - >>> Perm((2, 0, 1)).flip_horizontal() - Perm((0, 2, 1)) - """ - return self.complement() - - def flip_vertical(self): - """Return self flipped vertically. - - Examples: - >>> Perm((1, 2, 5, 0, 3, 4)).flip_vertical() - Perm((4, 3, 0, 5, 2, 1)) - >>> Perm((0, 1)).flip_vertical() - Perm((1, 0)) - """ - return self.reverse() - - def flip_diagonal(self): - """Return self flipped along the diagonal. - - Examples: - >>> Perm((1, 2, 5, 0, 3, 4)).flip_diagonal() - Perm((3, 0, 1, 4, 5, 2)) - >>> Perm((0, 1)).flip_diagonal() - Perm((0, 1)) - """ - return self.inverse() - - def flip_antidiagonal(self): - """Return self flipped along the antidiagonal.. - - Examples: - >>> Perm((3, 2, 0, 1)).flip_antidiagonal() - Perm((3, 2, 0, 1)) - >>> Perm((1, 2, 3, 0, 4)).flip_antidiagonal() - Perm((0, 2, 3, 4, 1)) - >>> Perm((1, 2, 0, 3)).flip_antidiagonal() - Perm((0, 2, 3, 1)) - """ - len_perm = len(self) - result = [None] * len_perm - - flipped_pairs = ( - (len_perm - element - 1, len_perm - index - 1) - for index, element in enumerate(self) - ) - - for index, element in flipped_pairs: - result[index] = element - return Perm(result) - - def _rotate_right(self): - """Return self rotated 90 degrees to the right.""" - len_perm = len(self) - result = [None] * len_perm - for index, value in enumerate(self): - result[value] = len_perm - index - 1 - return Perm(result) - - def _rotate_left(self): - """Return self rotated 90 degrees to the left.""" - len_perm = len(self) - result = [None] * len_perm - for index, value in enumerate(self): - result[len_perm - value - 1] = index - return Perm(result) - - def _rotate_180(self): - """Return self rotated 180 degrees.""" - return self.reverse_complement() - - def all_syms(self): - """Returns all symmetries of the permutation in a PermSet, all possible - combinations of revers, complement and inverse. - """ - syms = set([self, self.inverse()]) - curr = self - for _ in range(3): - curr = curr.rotate() - syms.add(curr) - syms.add(curr.inverse()) - return tuple(syms) - - def is_representative(self): - """Checks if the permutation is representative, that is, all the - symmetries of the permutation are the same. - """ - # return self == sorted(self.all_syms())[0] - pass - - # - # Statistical methods - # - - def is_increasing(self): - """Return True if the perm is increasing, and False otherwise.""" - for index in range(len(self)): - if self[index] != index: - return False - return True - - def is_decreasing(self): - """Return True if the perm is decreasing, and False otherwise.""" - len_perm = len(self) - for index in range(len_perm): - if self[index] != len_perm - index - 1: - return False - return True - - def count_fixed_points(self): - """Return the number of fixed points in self. - - Examples: - >>> Perm((0, 1, 4, 3, 2)).count_fixed_points() - 3 - >>> Perm((0, 1, 2, 3, 4)).count_fixed_points() - 5 - >>> Perm((3, 2, 1, 0)).count_fixed_points() - 0 - """ - return sum(1 for _ in self.fixed_points()) - - def fixed_points(self): - """Yield the index of the fixed points in self. - - Examples: - >>> tuple(Perm((0, 2, 1, 3)).fixed_points()) - (0, 3) - >>> tuple(Perm((0, 1, 4, 3, 2)).fixed_points()) - (0, 1, 3) - """ - for idx, val in enumerate(self): - if idx == val: - yield idx - - def strong_fixed_points(self): - """Yield the index of the strong fixed points in self. - - Examples: - >>> tuple(Perm((0, 2, 1, 3)).strong_fixed_points()) - (0, 3) - >>> tuple(Perm((0, 1, 4, 3, 2)).strong_fixed_points()) - (0, 1) - """ - if self != Perm(()): - L = len(self) - curmax = self[0] - for idx, val in enumerate(self): - if idx == val: - if val >= curmax: - if idx == L - 1 or val < min( - self[i] for i in range(idx + 1, L) - ): - yield idx - - def is_skew_decomposable(self): - """Determines whether the permutation is expressible as the skew sum of - two permutations. - - >>> p = Perm.random(8).direct_sum(Perm.random(12)) - >>> p.skew_decomposable() - False - >>> p.complement().skew_decomposable() - True - """ - - p = list(self) - n = self.__len__() - for i in range(1, n): - if set(range(n - i, n)) == set(p[0:i]): - return True - return False - - skew_decomposable = is_skew_decomposable # permpy backwards compatibility - - def is_sum_decomposable(self): - """Determines whether the permutation is expressible as the direct sum of - two permutations. - - >>> p = Perm.random(4).direct_sum(Perm.random(15)) - >>> p.sum_decomposable() - True - >>> p.reverse().sum_decomposable() - False - """ - - p = list(self) - n = self.__len__() - for i in range(1, n): - if set(range(0, i)) == set(p[0:i]): - return True - return False - - sum_decomposable = is_sum_decomposable # permpy backwards compatibility - - def descents(self): - """Yield the 0-based descents of self. - - Examples: - >>> tuple(Perm((0, 1, 3, 2, 4)).descents()) - (2,) - >>> tuple(Perm((3, 2, 1, 0)).descents()) - (0, 1, 2) - >>> tuple(Perm((0, 1, 2)).descents()) - () - """ - for index in range(len(self) - 1): - if self[index] > self[index + 1]: - yield index - - def descent_set(self): - """Return the list of descents of self. - - This method is for backwards compatibility with permpy. - """ - return list(self.descents()) - - def count_descents(self): - """Count the number of descents of self. - Examples: - >>> Perm((0, 1, 3, 2, 4)).count_descents() - 1 - >>> Perm((3, 2, 1, 0)).count_descents() - 3 - >>> Perm((0, 1, 2)).count_descents() - 0 - """ - return sum(1 for _ in self.descents()) - - num_descents = count_descents # permpy backwards compatibility - - def ascents(self): - """Yield the 0-based ascent of self. - - Examples: - >>> tuple(Perm((0, 1, 3, 2, 4)).ascents()) - (0, 1, 3) - >>> tuple(Perm((0, 4, 3, 2, 1)).ascents()) - (0,) - >>> tuple(Perm((3, 2, 1, 0)).ascents()) - () - """ - for index in range(len(self) - 1): - if self[index] < self[index + 1]: - yield index - - def ascent_set(self): - """Return the list of ascents of self. - - This method is for backwards compatibility with permpy. - """ - return list(self.ascents()) - - def count_ascents(self): - """Count the number of ascents in self. - - Examples: - >>> Perm((0, 1, 3, 2, 4)).count_ascents() - 3 - >>> Perm((0, 4, 3, 2, 1)).count_ascents() - 1 - >>> Perm((3, 2, 1, 0)).count_ascents() - 0 - """ - return sum(1 for _ in self.ascents()) - - num_ascents = count_ascents # permpy backwards compatibility - - def peaks(self): - """Yield the indices of the peaks of self. - - The i-th element of a perm is a peak if - self[i-1] < self[i] > self[i+1]. - - Examples: - >>> tuple(Perm((5, 3, 4, 0, 2, 1)).peaks()) - (2, 4) - >>> tuple(Perm((1, 2, 0)).peaks()) - (1,) - >>> tuple(Perm((2, 1, 0)).peaks()) - () - """ - if len(self) <= 2: - return - ascent = False - for index in range(1, len(self) - 1): - if self[index - 1] < self[index]: - # Perm ascended - ascent = True - else: - # Perm descended - if ascent: - yield index - 1 - ascent = False - # Check if penultimate element is a peak - if ascent and self[-2] > self[-1]: - yield len(self) - 2 - - def peak_list(self): - """Return the list of peaks of self. - - This method is for backwards compatibility with permpy. - """ - return list(self.peaks()) - - def count_peaks(self): - """Count the number of peaks of self. - - Examples: - >>> Perm((5, 3, 4, 0, 2, 1)).count_peaks() - 2 - >>> Perm((1, 2, 0)).count_peaks() - 1 - >>> Perm((2, 1, 0)).count_peaks() - 0 - """ - return sum(1 for _ in self.peaks()) - - num_peaks = count_peaks # permpy backwards compatibility - - def valleys(self): - """Yield the indices of the valleys of self. - - The i-th element of a perm is a valley if - self[i-1] > self[i] < self[i+1]. - - Examples: - >>> tuple(Perm((5, 3, 4, 0, 2, 1)).valleys()) - (1, 3) - >>> tuple(Perm((2, 0, 1)).valleys()) - (1,) - >>> tuple(Perm((1, 2, 0)).valleys()) - () - """ - if len(self) <= 2: - return - ascent = True - for index in range(1, len(self) - 1): - if self[index - 1] < self[index]: - # Perm ascended - if not ascent: - yield index - 1 - ascent = True - else: - # Perm descended - ascent = False - # Check if penultimate element is a valley - if not ascent and self[-2] < self[-1]: - yield len(self) - 2 - - def valley_list(self): - """Return the list of valleys of self. - - This method is for backwards compatibility with permpy. - """ - return list(self.valleys()) - - def count_valleys(self): - """Count the number of valleys of self. - - Examples: - >>> Perm((5, 3, 4, 0, 2, 1)).count_valleys() - 2 - >>> Perm((2, 0, 1)).count_valleys() - 1 - >>> Perm((1, 2, 0)).count_valleys() - 0 - """ - return sum(1 for _ in self.valleys()) - - num_valleys = count_valleys # permpy backwards compatibility - - def bends(self): - """Yield the indices at which the permutation changes direction. That - is, the number of non-monotone consecutive triples of the permutation. - A permutation p can be expressed as the concatenation of len(p.bends()) - + 1 monotone segments. - - Examples: - >>> list(Perm((5, 3, 4, 0, 2, 1)).bends()) - [1, 2, 3, 4] - >>> list(Perm((2, 0, 1)).bends()) - [1] - """ - if len(self) <= 2: - return - ascent = self[0] < self[1] - for index in range(1, len(self) - 1): - if self[index] > self[index + 1] and ascent: - yield index - elif self[index] < self[index + 1] and not ascent: - yield index - ascent = self[index] < self[index + 1] - - def bend_list(self): - """Returns the list of indices at which the permutation changes - direction. That is, the number of non-monotone consecutive triples of - the permutation. A permutation p can be expressed as the concatenation - of len(p.bend_list()) + 1 monotone segments. - - Examples: - >>> Perm((5, 3, 4, 0, 2, 1)).bend_list() - [1, 2, 3, 4] - >>> Perm((2, 0, 1)).bend_list() - [1] - """ - return list(self.bends()) - - def order(self): - """Returns the order of the permutation. - - Examples: - >>> Perm((4, 3, 5, 0, 2, 1)).order() - 6 - >>> Perm((0, 1, 2)).order() - 1 - """ - acc = 1 - for cycle in map(len, self.cycle_decomp()): - acc = (acc * cycle) // math.gcd(acc, cycle) - return acc - - # TODO: reimplement the following four functions to return generators - def ltrmin(self): - """Returns the positions of the left-to-right minima. - - Examples: - >>> Perm((2, 4, 3, 0, 1)).ltrmin() - [0, 3] - """ - L = [] - minval = len(self) + 1 - for idx, val in enumerate(self): - if val < minval: - L.append(idx) - minval = val - return L - - def rtlmin(self): - """Returns the positions of the right-to-left minima. - - Examples: - >>> Perm((2, 0, 4, 1, 5, 3)).rtlmin() - [1, 3, 5] - """ - rev_perm = self.reverse() - return [len(self) - val - 1 for val in rev_perm.ltrmin()][::-1] - - def ltrmax(self): - """Returns the positions of the left-to-right maxima. - - Examples: - >>> Perm((2, 0, 4, 1, 5, 3)).ltrmax() - [0, 2, 4] - """ - return [len(self) - i - 1 for i in Perm(self[::-1]).rtlmax()][::-1] - - def rtlmax(self): - """Returns the positions of the right-to-left maxima. - - Examples: - >>> Perm((2, 4, 3, 0, 1)).rtlmax() - [1, 2, 4] - """ - return [len(self) - i - 1 for i in self.complement().reverse().ltrmin()][::-1] - - def count_ltrmin(self): - """Counts the number of left-to-right minimas. - - Example: - >>> Perm((2, 4, 3, 0, 1)).count_ltrmin() - 2 - """ - return len(self.ltrmin()) - - def count_ltrmax(self): - """Counts the number of left-to-right minimas. - - Example: - >>> Perm((2, 4, 3, 0, 1)).count_ltrmin() - 2 - """ - return len(self.ltrmax()) - - def count_rtlmin(self): - """Counts the number of left-to-right minimas. - - Example: - >>> Perm((2, 4, 3, 0, 1)).count_ltrmin() - 2 - """ - return len(self.rtlmin()) - - def count_rtlmax(self): - """Counts the number of left-to-right minimas. - - Example: - >>> Perm((2, 4, 3, 0, 1)).count_ltrmin() - 2 - """ - return len(self.rtlmax()) - - num_ltrmin = count_ltrmin - - def count_inversions(self): - """Returns the number of inversions of the permutation, i.e., the - number of pairs i,j such that i < j and self(i) > self(j). - - Example: - >>> Perm((3, 0, 2, 1)).count_inversions() - 4 - >>> Perm.monotone_decreasing(6).count_inversions() == 5*6 / 2 - True - >>> Perm.monotone_increasing(7).count_inversions() - 0 - """ - bit_len = len(self) + 1 - bit = [0] * bit_len - for element in reversed(self): - bit_index = element + 1 - # Count lesser elements to the right - while element: - bit[0] += bit[element] - # Flip the right most set bit - element &= element - 1 - # Increment frequency for current element - while bit_index < bit_len: - bit[bit_index] += 1 - # Increase index by the largest power of two that divides it - bit_index += bit_index & -bit_index - return bit[0] - - def inversions(self): - """Yield the inversions of the permutation, i.e., the pairs i,j - such that i < j and self(i) > self(j). - - TODO: Reimplement in NlogN time. - Example: - >>> tuple(Perm((3, 0, 2, 1)).inversions()) - ((0, 1), (0, 2), (0, 3), (2, 3)) - """ - n = len(self) - for i in range(n): - for j in range(i + 1, n): - if self[i] > self[j]: - yield (i, j) - - def count_non_inversions(self): - """Returns the number of non_inversions of the permutation, i.e., the - number of pairs i,j such that i < j and self[i] < self[j]. - - Examples: - >>> Perm((3, 0, 2, 1, 4)).count_non_inversions() - 6 - >>> Perm.monotone_increasing(7).count_non_inversions() == (6 * 7)/2 - True - """ - n = len(self) - return n * (n - 1) // 2 - self.count_inversions() - - def non_inversions(self): - """Yields the non_inversions of the permutation, i.e., the pairs i,j - such that i < j and self[i] < self[j]. - - Examples: - >>> tuple(Perm((3, 0, 2, 1, 4)).non_inversions()) - ((0, 4), (1, 2), (1, 3), (1, 4), (2, 4), (3, 4)) - """ - for i in range(len(self)): - for j in range(i + 1, len(self)): - if self[i] < self[j]: - yield (i, j) - - def min_gapsize(self): - """Returns the minimum gap between any two entries in the permutation - (computed with the taxicab metric). - - TODO: currently uses the naive algorithm --- can be improved - - Examples: - >>> Perm((2, 0, 3, 1)).min_gapsize() - 3 - """ - min_dist = len(self) - for i, j in itertools.combinations(range(len(self)), 2): - h_dist = abs(i - j) - v_dist = abs(self[i] - self[j]) - dist = h_dist + v_dist - if dist < min_dist: - min_dist = dist - return min_dist - - def count_bonds(self): - """Counts the number of bonds, that is the number of adjacent locations - with adjacent values. - - Examples: - >>> Perm((0, 1, 2)).count_bonds() - 2 - >>> Perm((2, 1, 0)).count_bonds() - 2 - >>> Perm((4, 0, 3, 2, 1, 5)).count_bonds() - 2 - """ - return self.count_dec_bonds() + self.count_inc_bonds() - - num_bonds = count_bonds - bonds = count_bonds # permpy backwards compatibility - - def inc_bonds(self): - """Yields the indices of the increasing bonds, that is the indices of - the ascents with adjacent values. - - Examples: - >>> list(Perm((2, 3, 4, 5, 0, 1)).inc_bonds()) - [0, 1, 2, 4] - """ - for i in range(len(self) - 1): - if self[i + 1] == self[i] + 1: - yield i - - def count_inc_bonds(self): - """Counts the number of increasing bonds. - - Examples: - >>> Perm((0, 2, 3, 1)).count_inc_bonds() - 1 - >>> Perm((2, 3, 4, 5, 0, 1)).count_inc_bonds() - 4 - """ - return len(list(self.inc_bonds())) - - num_inc_bonds = count_inc_bonds - - def dec_bonds(self): - """Yields the indices of the decreasing bonds, that is the indices of - the descents with adjacent values. - - Examples: - >>> list(Perm((1, 0, 3, 2, 5, 4)).dec_bonds()) - [0, 2, 4] - """ - for i in range(len(self) - 1): - if self[i] == self[i + 1] + 1: - yield i - - def count_dec_bonds(self): - """Counts the number of decreasing bonds. - - Examples: - >>> Perm((2, 1, 0, 3)).count_dec_bonds() - 2 - >>> Perm((1, 0, 3, 2, 5, 4)).count_dec_bonds() - 3 - """ - return len(list(self.dec_bonds())) - - num_dec_bonds = count_dec_bonds - - def major_index(self): - """Returns the major index of the permutation, that is the sum of the - positions of the descents of the permutation. - - Examples: - >>> Perm((3, 1, 2, 4, 0)).major_index() - 5 - >>> Perm((0, 2, 1)).major_index() - 2 - """ - desc = list(self.descents()) - return sum(desc) + len(desc) - - def longestruns_ascending(self): - """Returns the longest ascending runs in the permutation as a pair of - the length and a list of the starting indices. - """ - n = self.__len__() - if n == 0: - return (0, []) - p = list(self) - maxi = 1 - res = [] - cur = 0 - for i in range(1, n): - if p[i - 1] < p[i]: - if (i - cur + 1) > maxi: - del res - res = [] - maxi = i - cur + 1 - else: - if (i - cur) == maxi: - res.append(cur) - cur = i - if n - cur == maxi: - res.append(cur) - return (maxi, res) - - def longestruns_descending(self): - """Returns the longest descending runs in the permutation as a pair of - the length and a list of the starting indices. - """ - return self.complement().longestruns_ascending() - - def longestruns(self): - """Returns the longest ascending runs in the permutation as a pair of - the length and a list of the starting indices. - """ - return self.longestruns_ascending() - - def length_of_longestrun_ascending(self): - """Returns the length of the longest ascending run in the permutation. - """ - return self.longestruns_ascending()[0] - - def length_of_longestrun_descending(self): - """Returns the length of the longest descending run in the permutation. - """ - return self.complement().length_of_longestrun_ascending() - - def length_of_longestrun(self): - """Returns the length of the longest ascending run in the permutation. - """ - return self.length_of_longestrun_ascending() - - def cycle_decomp(self): - """Calculates the cycle decomposition of the permutation. Returns a list - of cycles, each of which is represented as a list. - - >>> Perm((4, 2, 7, 0, 3, 1, 6, 5)).cycle_decomp() - [[4, 3, 0], [6], [7, 5, 1, 2]] - """ - n = self.__len__() - seen = set() - cyclelist = [] - while len(seen) < n: - a = max(set(range(n)) - seen) - cyc = [a] - b = self(a) - seen.add(b) - while b != a: - cyc.append(b) - b = self(b) - seen.add(b) - cyclelist.append(cyc) - cyclelist.reverse() - return cyclelist - - def count_cycles(self): - """Returns the number of cycles in the permutation. - - >>> Perm((5, 3, 8, 1, 0, 4, 2, 7, 6)).count_cycles() - 4 - """ - return len(self.cycle_decomp()) - - num_cycles = count_cycles # permpy backwards compatibility - - def is_involution(self): - """Checks if the permutation is an involution, i.e., is equal to it's - own inverse. - - Examples: - >>> Perm((2, 1, 0)).is_involution() - True - >>> Perm((3, 0, 2, 4, 1, 5)).is_involution() - False - """ - - return self == self.inverse() - - def is_identity(self): - """Checks if the permutation is the identity. - - >>> p = Perm.random(10) - >>> (p * p.inverse()).is_identity() - True - """ - - return self == Perm.identity(len(self)) - - def rank(self): - """Computes the rank of a permutation. - Examples: - >>> Perm((0, 1)).rank() - 2 - >>> Perm((0, 2, 1, 3)).rank() - 12 - """ - if len(self) == 0: - return 0 - fact = [1] - for i in range(len(self)): - fact.append(fact[i] * (i + 1)) - res = 0 - vals = list() - for i in range(len(self)): - r = bisect.bisect_left(vals, self[i]) - res += (self[i] - r) * fact[len(self) - i - 1] + fact[len(self) - i - 1] - vals.insert(r, self[i]) - return res - - perm2ind = rank # permpy backwards compatibility - - def threepats(self): - """Returns a dictionary of the number of occurrences of each - permutation pattern of length 3. - - Examples: - >>> res = Perm((2, 1, 0, 3)).threepats() - >>> res[Perm((1, 0, 2))] - 3 - >>> res[Perm((1, 2, 0))] - 0 - """ - patnums = { - Perm((0, 1, 2)): 0, - Perm((0, 2, 1)): 0, - Perm((1, 0, 2)): 0, - Perm((1, 2, 0)): 0, - Perm((2, 0, 1)): 0, - Perm((2, 1, 0)): 0, - } - for i, j, k in itertools.combinations(range(len(self)), 3): - patnums[Perm.to_standard((self[i], self[j], self[k]))] += 1 - return patnums - - def fourpats(self): - """Returns a dictionary of the number of occurrences of each - permutation pattern of length 4. - - Examples: - >>> res = Perm((1, 0, 3, 5, 2, 4)).fourpats() - >>> res[Perm((0, 2, 3, 1))] - 2 - >>> res[Perm((3, 1, 2, 0))] - 0 - """ - patnums = { - Perm((0, 1, 2, 3)): 0, - Perm((0, 1, 3, 2)): 0, - Perm((0, 2, 1, 3)): 0, - Perm((0, 2, 3, 1)): 0, - Perm((0, 3, 1, 2)): 0, - Perm((0, 3, 2, 1)): 0, - Perm((1, 0, 2, 3)): 0, - Perm((1, 0, 3, 2)): 0, - Perm((1, 2, 0, 3)): 0, - Perm((1, 2, 3, 0)): 0, - Perm((1, 3, 0, 2)): 0, - Perm((1, 3, 2, 0)): 0, - Perm((2, 0, 1, 3)): 0, - Perm((2, 0, 3, 1)): 0, - Perm((2, 1, 0, 3)): 0, - Perm((2, 1, 3, 0)): 0, - Perm((2, 3, 0, 1)): 0, - Perm((2, 3, 1, 0)): 0, - Perm((3, 0, 1, 2)): 0, - Perm((3, 0, 2, 1)): 0, - Perm((3, 1, 0, 2)): 0, - Perm((3, 1, 2, 0)): 0, - Perm((3, 2, 0, 1)): 0, - Perm((3, 2, 1, 0)): 0, - } - - for i, j, k, l in itertools.combinations(range(len(self)), 4): - patnums[Perm.to_standard((self[i], self[j], self[k], self[l]))] += 1 - return patnums - - def rank_val(self, i): - """Returns the 'rank value'(?) of index i, the number of inversions - with the value at i being the greater element. - - - Examples: - >>> Perm((3, 0, 2, 1)).rank_val(0) - 3 - >>> Perm((0, 2, 4, 3, 1)).rank_val(1) - 1 - """ - return len([j for j in range(i + 1, len(self)) if self[j] < self[i]]) - - def rank_encoding(self): - """Returns the 'rank value'(?) of each index in the permutation, the - number of inversions 'caused' by the values at each index. - - Examples: - >>> Perm((3, 0, 2, 1)).rank_encoding() - [3, 0, 1, 0] - >>> Perm((0, 2, 4, 3, 1)).rank_encoding() - [0, 1, 2, 1, 0] - """ - return [self.rank_val(i) for i in range(len(self))] - - # - # Decomposition and generation from self methods - # - def sum_decomposition(self) -> List["Perm"]: - """ - Return the sum decomposition of the permutation. - """ - res: List[Perm] = [] - max_val = -1 - curr_block_start_idx = 0 - for idx, val in enumerate(self): - max_val = max(max_val, val) - if idx == max_val: - res.append(Perm.to_standard(self[curr_block_start_idx : idx + 1])) - curr_block_start_idx = idx + 1 - return res - - def skew_decomposition(self) -> List["Perm"]: - """ - Return the skew decomposition of the permutation. - """ - res: List[Perm] = [] - min_val = len(self) + 1 - curr_block_start_idx = 0 - for idx, val in enumerate(self): - min_val = min(min_val, val) - if len(self) - idx - 1 == min_val: - res.append(Perm.to_standard(self[curr_block_start_idx : idx + 1])) - curr_block_start_idx = idx + 1 - return res - - def block_decomposition(self, return_patterns=False): - """Returns the list of all blocks(intervals) in the permutation that - are of length at least 2. The returned list of lists contains the - indices of blocks of length i in index i. - - When return_patterns is set to True, a list of patterns is returned - instead of list of list of indices. - - Examples: - >>> Perm((5, 3, 0, 1, 2, 4, 7, 6)).block_decomposition() - [[], [], [2, 3, 6], [2], [1], [1], [0], []] - >>> sorted(Perm((4, 1, 0, 5, 2, 3)).block_decomposition(True)) - [Perm((0, 1)), Perm((1, 0))] - """ - blocks = [[] for i in range(len(self))] - for start in range(0, len(self)): - mn, mx = self[start], self[start] - for length in range(2, len(self) - start + 1): - if length == len(self): - continue - end = start + length - 1 - mn, mx = min(mn, self[end]), max(mx, self[end]) - if mx - mn == length - 1: - blocks[length].append(start) - - if return_patterns: - patterns = set() - for length in range(0, len(blocks)): - for start in blocks[length]: - patterns.add(Perm.to_standard(self[start : start + length])) - return list(patterns) - else: - return blocks - - all_intervals = block_decomposition # permpy backwards compatibility - decomposition = block_decomposition - - def monotone_block_decomposition(self, with_ones=False): - """Returns the list of all monotone blocks(intervals) in the - permutation. Depending on the with_ones parameter it will return the - length 1 blocks. The blocks are pairs of indices, the start and end - index. - - Examples: - >>> Perm((2, 6, 3, 7, 4, 5, 1, 0)).monotone_block_decomposition() - [(4, 5), (6, 7)] - >>> Perm((2, 6, 3, 4, 5, 1, 0)).monotone_block_decomposition(True) - [(0, 0), (1, 1), (2, 4), (5, 6)] - >>> Perm((0, 1, 2, 3, 4, 5)).monotone_block_decomposition() - [(0, 5)] - """ - blocks = [] - diff = 0 - start = 0 - length = 0 - for i in range(1, len(self)): - if math.fabs(self[i] - self[i - 1]) == 1 and ( - length == 0 or self[i] - self[i - 1] == diff - ): - length += 1 - diff = self[i] - self[i - 1] - else: - blocks.append((start, start + length)) - start = i - length = 0 - diff = 0 - if len(self): - blocks.append((start, start + length)) - - if with_ones: - return blocks - return [block for block in blocks if block[1] - block[0] > 0] - - def monotone_block_decompositon_ascending(self, with_ones=False): - # TODO: test, untested - # TODO: rename to refer to runs, which this function basically - # computes, brakes the permutation up into its runs. - blocks = [] - start = 0 - length = 0 - for i in range(1, len(self)): - if self[i] + 1 == self[i - 1]: - length += 1 - else: - blocks.append((start, start + length)) - start = i - length = 0 - if len(self): - blocks.append((start, start + length)) - - if with_ones: - return blocks - return [block for block in blocks if block[1] - block[0] > 0] - - def monotone_block_decompositon_descending(self, with_ones=False): - # TODO: test, untested - return self.complement().monotone_block_decomposition_ascending(with_ones) - - # permpy backwards compatibility - all_monotone_intervals = monotone_block_decomposition - - def monotone_quotient(self): - """Return the permutation pattern consisting of the starting values of - the monotone blocks in the permutation. Simply contracts the monotone - blocks. - - Examples: - >>> Perm((0, 2, 1, 5, 6, 4, 3)).monotone_block_decomposition(True) - [(0, 0), (1, 2), (3, 4), (5, 6)] - >>> Perm((0, 2, 1, 5, 6, 4, 3)).monotone_quotient() - Perm((0, 1, 3, 2)) - """ - return Perm.to_standard( - [ - self[start] - for (start, end) in self.monotone_block_decomposition(with_ones=True) - ] - ) - - def maximum_block(self): - """Finds the biggest interval, and returns (i,j) is one is found, - where i is the size of the interval, and j is the index of the first - entry in the interval. - - Returns (0,0) if no interval is found, i.e., if the permutation is - simple. - - Example: - >>> Perm((0, 2, 1, 5, 6, 7, 4, 3)).maximum_block() - (7, 1) - """ - blocks = self.block_decomposition() - for length, indexlist in reversed(list(enumerate(blocks))): - if len(indexlist): - return (length, indexlist[0]) - return (0, 0) - - maximal_interval = maximum_block # permpy backwards compatibility - - def simple_location(self): - """Searches for an interval, and returns (i,j) if one is found, where i - is the size of the interval, and j is the first index of the interval. - - Returns (0,0) if no interval is found, i.e., if the permutation is - simple. - - Simply calls the Perm.maximum_block(), the maximum block is any block. - """ - return self.maximum_block() - - def is_simple(self): - """Checks if the permutation is simple. - - Example: - >>> Perm((2, 0, 3, 1)).is_simple() - True - >>> Perm((2, 0, 1)).is_simple() - False - """ - (i, j) = self.simple_location() - return i == 0 - - def is_strongly_simple(self): - """Checks if the permutation is strongly simple, that is if the - permutation is simple and any of permutation of one less length in the - downset is simple. - - Example: - >>> Perm((4, 1, 6, 3, 0, 7, 2, 5)).is_strongly_simple() - True - """ - return self.is_simple() and all([p.is_simple() for p in self.children()]) - - def children(self): - """Returns all patterns of length one less than the permutation. One - layer of the downset, also called the shadow. - - Example: - >>> sorted(Perm((2, 0, 1)).children()) - [Perm((0, 1)), Perm((1, 0))] - >>> sorted(Perm((4, 1, 6, 3, 0, 7, 2, 5)).children())[:2] - [Perm((1, 5, 3, 0, 6, 2, 4)), Perm((3, 0, 5, 2, 6, 1, 4))] - """ - return list(set(self.remove(i) for i in range(len(self)))) - - shrink_by_one = children - - # TODO: discuss return value conventions, should this return PermSet - # instead of set of Perm? maybe list of Perm? - def coveredby(self): - """Returns one layer of the upset of the permutation. - - Examples: - >>> sorted(Perm((0, 1)).coveredby())[:3] - [Perm((0, 1, 2)), Perm((0, 2, 1)), Perm((1, 0, 2))] - """ - S = set() - n = len(self) - for i in range(n + 1): - for j in range(n + 1): - S.add(self.insert(i, j)) - return list(S) - - # TODO: discuss return value conventions, should this return PermSet - # instead of set of Perm? maybe list of Perm? - def buildupset(self, height): - """Returns height-th layer of the upset of the permutation - """ - n = len(self) - L = [set() for i in range(n)] - L.append(set([self])) - for i in range(n + 1, height): - oldS = list(L[i - 1]) - newS = set() - for perm in oldS: - newS = newS.union(perm.coveredby()) - L.append(newS) - return L - - def count_rtlmax_ltrmin_layers(self): - """Counts the layers in the right-to-left maxima, left-to-right minima - decomposition. - """ - return len(self.rtlmax_ltrmin_decomposition()) - - num_rtlmax_ltrmin_layers = count_rtlmax_ltrmin_layers - - def rtlmax_ltrmin_decomposition(self): - """Returns the right-to-left maxima, left-to-right minima - decomposition. The decomposition consists of layers, starting with the - first layer which is union of the right-to-left maximas and the - left-to-right minimas and the next layer is defined similarly for the - permutation with the first layer removed and so on. - - TODO: If this function is to be kept, then it probably should return - the layers as indices in the original permutation. - """ - P = Perm(self) - num_layers = 0 - layers = [] - while len(P) > 0: - num_layers += 1 - positions = sorted(list(set(P.rtlmax() + P.ltrmin()))) - layers.append(positions) - P = Perm([P[i] for i in range(len(P)) if i not in positions]) - return layers - - # - # Pattern matching methods - # - - def contains(self, *patts): - """Check if self contains patts. - - Args: - self: - A perm. - patts: argument list - Classical/mesh patterns. - - Returns: - True if and only if all patterns in patts are contained in self. - - Examples: - >>> Perm.monotone_decreasing(7).avoids(Perm((0, 1))) - True - >>> Perm((4, 2, 3, 1, 0)).contains(Perm((1, 2, 0))) - True - >>> Perm((0, 1, 2)).contains(Perm((1,0))) - False - >>> pattern1 = Perm((0, 1)) - >>> pattern2 = Perm((2, 0, 1)) - >>> pattern3 = Perm((0, 3, 1, 2)) - >>> Perm((5, 3, 0, 4, 2, 1)).contains(pattern1, pattern2) - True - >>> Perm((5, 3, 0, 4, 2, 1)).contains(pattern2, pattern3) - False - """ - return all(patt in self for patt in patts) - - def avoids(self, *patts): - """Check if self avoids patts. - - Args: - self: - A perm. - patts: argument list - Classical/mesh patterns. - - Returns: - True if and only if self avoids all patterns in patts. - - Examples: - >>> Perm.monotone_increasing(8).avoids(Perm((1, 0))) - True - >>> Perm((4, 2, 3, 1, 0)).avoids(Perm((1, 2, 0))) - False - >>> Perm((0, 1, 2)).avoids(Perm((1,0))) - True - >>> pattern1 = Perm((0, 1)) - >>> pattern2 = Perm((2, 0, 1)) - >>> pattern3 = Perm((0, 3, 1, 2)) - >>> pattern4 = Perm((0, 1, 2)) - >>> Perm((5, 3, 0, 4, 2, 1)).avoids(pattern1, pattern2) - False - >>> Perm((5, 3, 0, 4, 2, 1)).avoids(pattern2, pattern3) - False - >>> Perm((5, 3, 0, 4, 2, 1)).avoids(pattern3, pattern4) - True - """ - return all(patt not in self for patt in patts) - - def avoids_set(self, patts): - """Check if self avoids patts. - - This method is for backwards compatibility with permpy. - """ - return self.avoids(*tuple(patts)) - - def count_occurrences_of(self, patt): - """Count the number of occurrences of patt in self. - - Args: - self: - A perm. - patt: - A classical/mesh pattern. - - Returns: - The number of times patt occurs in self. - - Examples: - >>> Perm((0, 1, 2)).count_occurrences_of(Perm((0, 1))) - 3 - >>> Perm((5, 3, 0, 4, 2, 1)).count_occurrences_of(Perm((2, 0, 1))) - 6 - """ - return patt.count_occurrences_in(self) - - occurrences = count_occurrences_of # permpy backwards compatibility - - def occurrences_in(self, patt, self_colours=None, patt_colours=None): - """Find all indices of occurrences of self in patt. - - If the optional colours are provided, in an occurrences the colours of - the patterns have to match the colours of the permutation. - - Args: - self: - The classical pattern whose occurrences are to be found. - patt: - The patt to search for occurrences in. - self_colours: - Optional colours on each entry of the permutation. - patt_colours: - Optional colours on each entry of the pattern. - - - Yields: of - The indices of the occurrences of self in patt. - Each yielded element l is a tuple of integer indices of the - pattern patt such that: - self == permuta.Perm.to_standard([patt[i] for i in l]) - - Examples: - >>> list(Perm((2, 0, 1)).occurrences_in(Perm((5, 3, 0, 4, 2, 1)))) - [(0, 1, 3), (0, 2, 3), (0, 2, 4), (0, 2, 5), (1, 2, 4), (1, 2, 5)] - >>> list(Perm((1, 0)).occurrences_in(Perm((1, 2, 3, 0)))) - [(0, 3), (1, 3), (2, 3)] - >>> list(Perm((0,)).occurrences_in(Perm((1, 2, 3, 0)))) - [(0,), (1,), (2,), (3,)] - >>> list(Perm().occurrences_in(Perm((1, 2, 3, 0)))) - [()] - """ - if not isinstance(patt, Perm): - patt = patt.pattern - - # Special cases - if len(self) == 0: - # Pattern is empty, occurs in all perms - # This is needed for the occurrences function to work correctly - yield () - return - if len(self) > len(patt): - # Pattern is too long to occur in perm - return - - # The indices of the occurrence in perm - occurrence_indices = [None] * len(self) - - # Get left to right scan details - pattern_details = self.__pattern_details() - - # Define function that works with the above defined variables - # i is the index of the element in perm that is to be considered - # k is how many elements of the perm have already been added to - # occurrence - def occurrences(i, k): - elements_remaining = len(patt) - i - elements_needed = len(self) - k - - # Get the following variables: - # - lfi: Left Floor Index - # - lci: Left Ceiling Index - # - lbp: Lower Bound Pre-computation - # - ubp: Upper Bound Pre-computation - lfi, lci, lbp, ubp = pattern_details[k] - - # Set the bounds for the new element - if lfi is None: - # The new element of the occurrence must be at least self[k]; - # i.e., the k-th element of the pattern - # In this case, lbp = self[k] - lower_bound = lbp - else: - # The new element of the occurrence must be at least as far - # from its left floor as self[k] is from its left floor - # In this case, lbp = self[k] - self[lfi] - occurrence_left_floor = patt[occurrence_indices[lfi]] - lower_bound = occurrence_left_floor + lbp - if lci is None: - # The new element of the occurrence must be at least as less - # than its maximum possible element---i.e., len(perm)---as - # self[k] is to its maximum possible element---i.e., len(self) - # In this case, ubp = len(self) - self[k] - upper_bound = len(patt) - ubp - else: - # The new element of the occurrence must be at least as less - # than its left ceiling as self[k] is to its left ceiling - # In this case, ubp = self[lci] - self[k] - upper_bound = patt[occurrence_indices[lci]] - ubp - - # Loop over remaining elements of perm (actually i, the index) - while True: - if elements_remaining < elements_needed: - # Can't form an occurrence with remaining elements - return - element = patt[i] - compare_colours = ( - self_colours is None or patt_colours[i] == self_colours[k] - ) - if compare_colours and lower_bound <= element <= upper_bound: - occurrence_indices[k] = i - if elements_needed == 1: - # Yield occurrence - yield tuple(occurrence_indices) - else: - # Yield occurrences where the i-th element is chosen - for occurrence in occurrences(i + 1, k + 1): - yield occurrence - # Increment i, that also means elements_remaining should - # decrement - i += 1 - elements_remaining -= 1 - - for occurrence in occurrences(0, 0): - yield occurrence - - def occurrences_of(self, patt): - """Find all indices of occurrences of patt in self. - - This method is complementary to permuta.Perm.occurrences_in. - It just calls patt.occurrences_in(self) internally. - See permuta.Perm.occurrences_in for documentation. - - Args: - self: - A perm. - patt: - A classical/mesh pattern. - - Yields: of - The indices of the occurrences of self in perm. - - Examples: - >>> list(Perm((5, 3, 0, 4, 2, 1)).occurrences_of(Perm((2, 0, 1)))) - [(0, 1, 3), (0, 2, 3), (0, 2, 4), (0, 2, 5), (1, 2, 4), (1, 2, 5)] - >>> list(Perm((1, 2, 3, 0)).occurrences_of(Perm((1, 0)))) - [(0, 3), (1, 3), (2, 3)] - >>> list(Perm((1, 2, 3, 0)).occurrences_of(Perm((0,)))) - [(0,), (1,), (2,), (3,)] - >>> list(Perm((1, 2, 3, 0)).occurrences_of(Perm())) - [()] - """ - return patt.occurrences_in(self) - - def __pattern_details(self): - """Subroutine of occurrences_in method.""" - # If details have been calculated before, return cached result - if self._cached_pattern_details is not None: - return self._cached_pattern_details - result = [] - index = 0 - for fac_indices in left_floor_and_ceiling(self): - base_element = self[index] - compiled = ( - fac_indices.floor, - fac_indices.ceiling, - self[index] - if fac_indices.floor is None - else base_element - self[fac_indices.floor], - len(self) - self[index] - if fac_indices.ceiling is None - else self[fac_indices.ceiling] - base_element, - ) - result.append(compiled) - index += 1 - self._cached_pattern_details = result - return result - - # - # General methods - # - - def apply(self, iterable): - """Permute an iterable using the perm. - - Args: - self: - A perm. - iterable: - An iterable of len(self) elements. - - Returns: - The elements of iterable in the permuted order. - - Raises: - TypeError: - Bad argument type. - - Examples: - >>> Perm((4, 1, 2, 0, 3)).apply((1, 2, 3, 4, 5)) - (5, 2, 3, 1, 4) - >>> Perm((4, 1, 2, 0, 3)).apply("abcde") - ('e', 'b', 'c', 'a', 'd') - >>> Perm((1, 2, 0, 3)).apply("abcde") - Traceback (most recent call last): - ... - ValueError: Length mismatch - """ - iterable = tuple(iterable) - if len(iterable) != len(self): - raise ValueError("Length mismatch") - return tuple(iterable[index] for index in self) - - permute = apply # Alias of Perm.apply - - # - # Visualization methods - # - def ascii_plot(self, cell_size=1): - """Return an ascii plot of the given Permutation. - - Args: - self: - A perm. - cell_size: - The size of the cell of the grid - - Returns: - The ascii art string of the permutation - - Examples: - >>> print(Perm((0,1,2)).ascii_plot()) - | | | - -+-+-●- - | | | - -+-●-+- - | | | - -●-+-+- - | | | - """ - if cell_size > 0: - empty_char = "+" - elif cell_size == 0: - empty_char = " " - else: - raise ValueError("`cell_size` must be positive") - point_char = "\u25cf" - n = self.__len__() - array = [[empty_char for i in range(n)] for j in range(n)] - for i in range(n): - array[self[i]][i] = point_char - array.reverse() - lines = [("-" * cell_size).join([""] + line + [""]) + "\n" for line in array] - vline = (" " * cell_size + "|") * n + "\n" - s = (vline * cell_size).join([""] + lines + [""]) - return s[:-1] - - def cycle_notation(self): - """Returns the cycle notation representation of the permutation. - - Examples: - >>> Perm((5, 3, 0, 1, 2, 4)).cycle_notation() - '( 3 1 ) ( 5 4 2 0 )' - """ - if len(self) == 0: - return "( )" - base = 0 - stringlist = [ - "( " + " ".join([str(x + base) for x in cyc]) + " )" - for cyc in self.cycle_decomp() - ] - return " ".join(stringlist) - - cycles = cycle_notation # permpy backwards compatibility - - def plot(self, **kwargs): - """ - Draws a plot of the permutation. - - Todo: - * Implement this function using matplotlib or some other tools - """ - raise NotImplementedError("Use `ascii_plot` or `to_tikz` method") - - def to_tikz(self): - """ - Return the tikz code to plot the permutation. - """ - s = r"\begin{tikzpicture}" - s += r"[scale=.3,baseline=(current bounding box.center)]" - s += "\n\t" - s += r"\foreach \x in {1,...," + str(len(self)) + "} {" - s += "\n\t\t" - s += r"\draw[ultra thin] (\x,0)--(\x," + str(len(self) + 1) + "); %vline" - s += "\n\t\t" - s += r"\draw[ultra thin] (0,\x)--(" + str(len(self) + 1) + r",\x); %hline" - s += "\n\t" - s += r"}" - for (i, e) in enumerate(self): - s += "\n\t" - s += ( - r"\draw[fill=black] (" - + str(i + 1) - + "," - + str(e + 1) - + ") circle (5pt);" - ) - s += "\n" - s += r"\end{tikzpicture}" - return s - - # - # Magic/dunder methods - # - - def __call__(self, value): - """Map value to its image defined by the perm. - - Examples: - >>> Perm((3, 1, 2, 0))(0) - 3 - >>> Perm((3, 1, 2, 0))(1) - 1 - >>> Perm((3, 1, 2, 0))(2) - 2 - >>> Perm((3, 1, 2, 0))(3) - 0 - """ - if not isinstance(value, numbers.Integral): - raise TypeError("'{}' object is not an integer".format(repr(value))) - if not 0 <= value < len(self): - raise ValueError("Element out of range: {}".format(value)) - return self[value] - - def __add__(self, other): - """Return the direct sum of the perms self and other.""" - return self.direct_sum(other) - - def __sub__(self, other): - """Return the skew sum of the perms self and other.""" - return self.skew_sum(other) - - def __mul__(self, other): - """Return the composition of two perms.""" - return self.compose(other) - - def __repr__(self): - return "Perm({})".format(super(Perm, self).__repr__()) - - def __str__(self): - if not self: - return "\u03B5" - if len(self) <= 10: - return "".join(str(i) for i in self) - else: - return "".join("({})".format(i) for i in self) - - def __lt__(self, other): - """ - >>> p1 = Perm((0,1,2,3)) - >>> p2 = Perm((0,1,2)) - >>> p2 <= p1 - True - """ - return (len(self), tuple(self)) < (len(other), tuple(other)) - - def __le__(self, other): - return (len(self), tuple(self)) <= (len(other), tuple(other)) - - def __gt__(self, other): - return other.__lt__(self) - - def __ge__(self, other): - return other.__le__(self) - - def __contains__(self, patt): - """Check if self contains patt. - - Args: - self: - A perm. - patt: - A classical/mesh pattern. - - Returns: - True if and only if the pattern patt is contained in self. - """ - return any(True for _ in patt.occurrences_in(self)) diff --git a/permuta/perm_sets/__init__.py b/permuta/perm_sets/__init__.py new file mode 100644 index 00000000..e211a946 --- /dev/null +++ b/permuta/perm_sets/__init__.py @@ -0,0 +1,4 @@ +from .basis import Basis, MeshBasis +from .permset import Av + +__all__ = ["Av", "Basis", "MeshBasis"] diff --git a/permuta/perm_sets/basis.py b/permuta/perm_sets/basis.py new file mode 100644 index 00000000..9bba10f4 --- /dev/null +++ b/permuta/perm_sets/basis.py @@ -0,0 +1,98 @@ +import re +from typing import Iterable, List, Union + +from ..patterns import MeshPatt, Patt, Perm + + +class Basis(tuple): + """A set of classical patterns such that none is + contained in another within the basis. + """ + + def __new__(cls, *patts: Perm) -> "Basis": + if not patts: + return tuple.__new__(cls, ()) + return cls._pruner(sorted(patts)) + + @classmethod + def from_string(cls, patts: str) -> "Basis": + """Construct a Basis from a string. It can be either 0 or 1 based and + seperated by anything.""" + return cls(*map(Perm.to_standard, re.findall(r"\d+", patts))) + + @classmethod + def from_iterable(cls, patts: Iterable[Perm]) -> "Basis": + """Construct a Basis from an iterable.""" + return cls(*patts) + + @classmethod + def _pruner(cls, patts: List[Perm]) -> "Basis": + if len(patts[0]) == 0: + return tuple.__new__(cls, (patts[0],)) + new_basis: List[Perm] = [] + for patt in patts: + if patt.avoids(*new_basis): + new_basis.append(patt) + return tuple.__new__(cls, new_basis) + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) and tuple.__eq__(self, other) + + def __hash__(self) -> int: + return tuple.__hash__(self) + + def __repr__(self) -> str: + return f"Basis({tuple.__repr__(self)})" + + def __str__(self) -> str: + return f'{{{", ".join(str(p) for p in self)}}}' + + +class MeshBasis(tuple): + """A set of patterns such that none is + contained in another within the basis. + """ + + @staticmethod + def is_mesh_basis(basis: Iterable[Patt]) -> bool: + """Checks if a collection of patterns contains any non-classical ones.""" + return not ( + isinstance(basis, Perm) or all(isinstance(patt, Perm) for patt in basis) + ) + + def __new__(cls, *patts: Union[Perm, MeshPatt]) -> "MeshBasis": + if not patts: + return tuple.__new__(cls, ()) + return cls._pruner( + sorted( + patt if isinstance(patt, MeshPatt) else MeshPatt(patt, []) + for patt in patts + ) + ) + + @classmethod + def from_iterable(cls, patts: Iterable[Union[Perm, MeshPatt]]) -> "MeshBasis": + """Construct a MeshBasis from an iterable.""" + return cls(*patts) + + @classmethod + def _pruner(cls, patts: List[MeshPatt]) -> "MeshBasis": + if len(patts[0]) == 0: + return tuple.__new__(cls, (patts[0],)) + new_basis: List[MeshPatt] = [] + for patt in patts: + if patt.avoids(*new_basis): + new_basis.append(patt) + return tuple.__new__(cls, new_basis) + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) and tuple.__eq__(self, other) + + def __hash__(self) -> int: + return tuple.__hash__(self) + + def __repr__(self) -> str: + return f"{MeshBasis}({tuple.__repr__(self)})" + + def __str__(self) -> str: + return f'{{{", ".join(str(p) for p in self)}}}' diff --git a/permuta/perm_sets/permset.py b/permuta/perm_sets/permset.py new file mode 100644 index 00000000..8e56f95f --- /dev/null +++ b/permuta/perm_sets/permset.py @@ -0,0 +1,192 @@ +import multiprocessing +from itertools import islice +from typing import ClassVar, Dict, Iterable, List, NamedTuple, Optional, Union + +from ..patterns import MeshPatt, Perm +from ..permutils import is_finite, is_insertion_encodable, is_polynomial +from .basis import Basis, MeshBasis + + +class AvBase(NamedTuple): + """A base class for Av to define instance variables without having to use + __init__ in Av. + """ + + basis: Union[Basis, MeshBasis] + cache: List[Dict[Perm, Optional[List[int]]]] + + +class Av(AvBase): + """A permutation class defined by its minimal basis.""" + + _FORBIDDEN_BASIS = Basis(Perm()) + _VALUE_ERROR_MSG = "Basis should be non-empty without the empty perm!" + _BASIS_ONLY_MSG = "Only supported for Basis!" + _CLASS_CACHE: ClassVar[Dict[Union[Basis, MeshBasis], "Av"]] = {} + _CACHE_LOCK = multiprocessing.Lock() + + def __new__( + cls, + basis: Union[ + Basis, MeshBasis, Iterable[Perm], Iterable[Union[Perm, MeshPatt]], + ], + ) -> "Av": + if not isinstance(basis, (Basis, MeshBasis)): + return Av.from_iterable(basis) + if len(basis) == 0 or basis == Av._FORBIDDEN_BASIS: + raise ValueError(Av._VALUE_ERROR_MSG) + instance = Av._CLASS_CACHE.get(basis) + if instance is None: + new_instance: "Av" = AvBase.__new__(cls, basis, [{Perm(): [0]}]) + Av._CLASS_CACHE[basis] = new_instance + return new_instance + return instance + + @classmethod + def clear_cache(cls) -> None: + """Clear the instance cache.""" + cls._CLASS_CACHE = {} + + @classmethod + def from_string(cls, basis) -> "Av": + """Create a permutation class from a string. Basis can be either zero or one + based and seperated by anything. MeshBasis is not supported. + """ + return cls(Basis.from_string(basis)) + + @classmethod + def from_iterable( + cls, basis: Union[Iterable[Perm], Iterable[Union[Perm, MeshPatt]]] + ) -> "Av": + """Create a permutation class from a basis defined by an iterable of patterns. + """ + if MeshBasis.is_mesh_basis(basis): + return cls(MeshBasis(*basis)) + return cls(Basis(*basis)) + + def is_finite(self) -> bool: + """Check if the perm class is finite.""" + if isinstance(self.basis, MeshBasis): + raise NotImplementedError(Av._BASIS_ONLY_MSG) + return is_finite(self.basis) + + def is_polynomial(self) -> bool: + """Check if the perm class has polynomial growth.""" + if isinstance(self.basis, MeshBasis): + raise NotImplementedError(Av._BASIS_ONLY_MSG) + return is_polynomial(self.basis) + + def is_insertion_encodable(self) -> bool: + """Check if the perm class is insertion encodable.""" + if isinstance(self.basis, MeshBasis): + raise NotImplementedError(Av._BASIS_ONLY_MSG) + return is_insertion_encodable(self.basis) + + def first(self, count: int) -> Iterable[Perm]: + """Generate the first `count` permutation in this permutation class given + that it has that many, if not all are generated. + """ + yield from islice(self._all(), count) + + def of_length(self, length: int) -> Iterable[Perm]: + """Generate all perms of a given length that belong to this permutation class. + """ + return iter(self._get_level(length)) + + def up_to_length(self, length: int) -> Iterable[Perm]: + """Generate all perms up to and including a given length that + belong to this permutation class. + """ + for n in range(length + 1): + yield from self.of_length(n) + + def count(self, length: int) -> int: + """Return the nubmber of permutations of a given length.""" + return len(self._get_level(length)) + + def enumeration(self, length: int) -> List[int]: + """Return the enumeration of this permutation class up and including a given + length.""" + return [self.count(i) for i in range(length + 1)] + + def __contains__(self, other: object): + if isinstance(other, Perm): + return other in self._get_level(len(other)) + return False + + def is_subclass(self, other: "Av"): + """Check if a sublcass of another permutation class.""" + return all(p1 not in self for p1 in other.basis) + + def _ensure_level(self, level_number: int) -> None: + start = max(0, len(self.cache) - 2) + if isinstance(self.basis, Basis): + self._ensure_level_classical_pattern_basis(level_number) + else: + self._ensure_level_mesh_pattern_basis(level_number) + for i in range(start, level_number - 1): + self.cache[i] = {perm: None for perm in self.cache[i]} + + def _ensure_level_classical_pattern_basis(self, level_number: int) -> None: + # We build new elements from existing ones + lengths = {len(b) for b in self.basis} + max_size = max(lengths) + for nplusone in range(len(self.cache), level_number + 1): + n = nplusone - 1 + new_level: Dict[Perm, Optional[List[int]]] = dict() + last_level = self.cache[-1] + check_length = nplusone in lengths + smaller_elems = {b for b in self.basis if len(b) == nplusone} + + def valid_insertions(perm): + # pylint: disable=cell-var-from-loop + res = None + for i in range(max(0, n - max_size), n): + val = perm[i] + subperm = perm.remove(i) + spots = self.cache[n - 1][subperm] + acceptable = [k for k in spots if k <= val] + acceptable.extend(k + 1 for k in spots if k >= val) + if res is None: + res = frozenset(acceptable) + res = res.intersection(acceptable) + if not res: + break + return res if res is not None else range(nplusone) + + for perm, lis in last_level.items(): + for value in valid_insertions(perm): + new_perm = perm.insert(index=nplusone, new_element=value) + if not check_length or new_perm not in smaller_elems: + new_level[new_perm] = [] + assert lis is not None + lis.append(value) + self.cache.append(new_level) + + def _ensure_level_mesh_pattern_basis(self, level_number: int) -> None: + self.cache.extend( + {p: None for p in Perm.of_length(i) if p.avoids(*self.basis)} + for i in range(len(self.cache), level_number + 1) + ) + + def _get_level(self, level_number: int) -> Dict[Perm, Optional[List[int]]]: + with Av._CACHE_LOCK: + self._ensure_level(level_number) + return self.cache[level_number] + + def _all(self) -> Iterable[Perm]: + length = 0 + while True: + gen = (p for p in self.of_length(length)) + first: Optional[Perm] = next(gen, None) + if first is None: + break + yield first + yield from gen + length += 1 + + def __str__(self) -> str: + return f"Av({','.join(str(p) for p in self.basis)})" + + def __repr__(self) -> str: + return f"Av({repr(self.basis)})" diff --git a/permuta/permset.py b/permuta/permset.py deleted file mode 100644 index d2cd46a3..00000000 --- a/permuta/permset.py +++ /dev/null @@ -1,103 +0,0 @@ -import numbers - -from ._perm_set.finite.permset_static import PermSetStatic -from ._perm_set.permset_base import PermSetBase -from ._perm_set.unbounded.all.permset_all import PermSetAll -from ._perm_set.unbounded.described.avoiding.avoiding import Avoiding -from ._perm_set.unbounded.described.permset_described import PermSetDescribed -from .descriptors.basis import detect_basis_cls -from .descriptors.descriptor import Descriptor -from .descriptors.predicate import Predicate - -__all__ = [ - "PermSet", - "Av", - "AvoidanceClass", -] - - -class PermSetMetaclass(type): - def __instancecheck__(self, instance): - return isinstance(instance, PermSetBase) - - def __subclasscheck__(self, subclass): - return issubclass(subclass, PermSetBase) - - -class PermSet(object, metaclass=PermSetMetaclass): - def __new__(cls, descriptor=None): - if descriptor is None: - return PermSetAll() - elif isinstance(descriptor, numbers.Integral): - # Descriptor is actually just a number - return PermSetAll().of_length(descriptor) - elif isinstance(descriptor, Descriptor): - return cls._dispatch_described(descriptor) - else: - # Descriptor might just be a set of perms - return PermSetStatic(descriptor) - - @classmethod - def _dispatch_described(cls, descriptor): - # Loop through all the described superclasses; e.g. Avoiding - for described_superclass in PermSetDescribed.__subclasses__(): - # Check if descriptor's class is of that of the superclasses' - # descriptor - if isinstance(descriptor, described_superclass.DESCRIPTOR_CLASS): - # The correct superclass has been found! - # Try to find subclass specifically for this descriptor - described_class = cls._find_described_class( - descriptor, PermSetDescribed - ) - if described_class is None: - return described_superclass.DEFAULT_CLASS(descriptor) - else: - return described_class(descriptor) - # TODO: Something else? - raise RuntimeError( - "PermSet for descriptor {} not found".format(repr(descriptor)) - ) - - @classmethod - def _find_described_class(cls, descriptor, current_class): - # TODO: Use metaclasses to track subclasses, rather than this mess - if descriptor == current_class.DESCRIPTOR: - return current_class - else: - for subclass in current_class.__subclasses__(): - described_class = cls._find_described_class(descriptor, subclass) - if described_class is not None: - return described_class - return None - - @classmethod - def avoiding(cls, basis): - BasisCls = detect_basis_cls(basis) - return cls(BasisCls(basis)) - - @classmethod - def filtering(cls, predicate): - return cls(Predicate(predicate)) - - -# -# Syntactic sugar -# - - -class AvoidanceClassMetaclass(type): - def __instancecheck__(self, instance): - return isinstance(instance, Avoiding) - - def __subclasscheck__(self, subclass): - return issubclass(subclass, Avoiding) - - -class Av(object, metaclass=AvoidanceClassMetaclass): - def __new__(cls, basis=None): - if basis is None or len(basis) == 0: - return PermSetAll() - return PermSet.avoiding(basis) - - -AvoidanceClass = Av diff --git a/permuta/permutils/__init__.py b/permuta/permutils/__init__.py index f53b808b..e0e4b41c 100644 --- a/permuta/permutils/__init__.py +++ b/permuta/permutils/__init__.py @@ -1,13 +1,44 @@ -from .insertion_encodable import ( - is_insertion_encodable, - is_insertion_encodable_maximum, - is_insertion_encodable_rightmost, +from .finite import is_finite +from .groups import dihedral_group +from .insertion_encodable import InsertionEncodablePerms +from .polynomial import PolyPerms +from .symmetry import ( + all_symmetry_sets, + antidiagonal_set, + complement_set, + inverse_set, + lex_min, + reverse_set, + rotate_90_clockwise_set, + rotate_180_clockwise_set, + rotate_270_clockwise_set, ) -from .symmetry import lex_min + +is_insertion_encodable = InsertionEncodablePerms.is_insertion_encodable +is_insertion_encodable_maximum = InsertionEncodablePerms.is_insertion_encodable_maximum +is_insertion_encodable_rightmost = ( + InsertionEncodablePerms.is_insertion_encodable_rightmost +) +is_polynomial = PolyPerms.is_polynomial +is_non_polynomial = PolyPerms.is_non_polynomial __all__ = [ "is_insertion_encodable", "is_insertion_encodable_maximum", "is_insertion_encodable_rightmost", + "is_polynomial", + "is_non_polynomial", + "InsertionEncodablePerms", + "PolyPerms", + "is_finite", + "dihedral_group", + "all_symmetry_sets", + "antidiagonal_set", + "complement_set", + "inverse_set", "lex_min", + "reverse_set", + "rotate_90_clockwise_set", + "rotate_180_clockwise_set", + "rotate_270_clockwise_set", ] diff --git a/permuta/permutils/finite.py b/permuta/permutils/finite.py index b92f4826..ef1c1ee7 100644 --- a/permuta/permutils/finite.py +++ b/permuta/permutils/finite.py @@ -1,4 +1,14 @@ -def is_finite(basis): - return any(perm.is_decreasing() for perm in basis) and any( - perm.is_increasing() for perm in basis +from itertools import tee +from typing import Iterable + +from permuta.patterns.perm import Perm + + +def is_finite(basis: Iterable[Perm]) -> bool: + """Check if a basis is finite, i.e. it contains decreasing and increasing + permutations. + """ + it1, it2 = tee(basis, 2) + return any(perm.is_decreasing() for perm in it1) and any( + perm.is_increasing() for perm in it2 ) diff --git a/permuta/permutils/groups.py b/permuta/permutils/groups.py new file mode 100644 index 00000000..37085c92 --- /dev/null +++ b/permuta/permutils/groups.py @@ -0,0 +1,24 @@ +from collections import deque +from typing import Iterable + +from ..patterns import Perm + + +def dihedral_group(n: int) -> Iterable[Perm]: + """Generate dihedral group of length n. + + Examples: + >>> sorted(dihedral_group(4)) + [Perm((0, 1, 2, 3)), Perm((0, 3, 2, 1)), Perm((1, 0, 3, 2)), Perm((1, 2, 3, 0))\ +, Perm((2, 1, 0, 3)), Perm((2, 3, 0, 1)), Perm((3, 0, 1, 2)), Perm((3, 2, 1, 0))] + """ + if n <= 2: + return + increasing, decreasing = deque(range(n)), deque(range(n - 1, -1, -1)) + yield Perm(increasing) + yield Perm(decreasing) + for _ in range(n - 1): + increasing.appendleft(increasing.pop()) + decreasing.appendleft(decreasing.pop()) + yield Perm(increasing) + yield Perm(decreasing) diff --git a/permuta/permutils/insertion_encodable.py b/permuta/permutils/insertion_encodable.py index 75dca081..3c7a6b7e 100644 --- a/permuta/permutils/insertion_encodable.py +++ b/permuta/permutils/insertion_encodable.py @@ -1,92 +1,92 @@ -"""Function for testing if a basis has a regular insertion encoding.""" - - -def is_incr_next_incr(perm): - for i in range(len(perm) - 1): - if perm[i + 1] < perm[i]: - for j in range(i + 1, len(perm) - 1): - if perm[j + 1] < perm[j]: - return False - break - return True - - -def is_incr_next_decr(perm): - for i in range(len(perm) - 1): - if perm[i + 1] < perm[i]: - for j in range(i + 1, len(perm) - 1): - if perm[j + 1] > perm[j]: - return False - break - return True - - -def is_decr_next_incr(perm): - for i in range(len(perm) - 1): - if perm[i + 1] > perm[i]: - for j in range(i + 1, len(perm) - 1): - if perm[j + 1] < perm[j]: - return False - break - return True - - -def is_decr_next_decr(perm): - for i in range(len(perm) - 1): - if perm[i + 1] > perm[i]: - for j in range(i + 1, len(perm) - 1): - if perm[j + 1] > perm[j]: - return False - break - return True - - -mem = dict() - - -def insertion_encodable_properties(perm): - if perm in mem: - return mem[tuple(perm)] - - properties = [] - if is_incr_next_decr(perm): - properties.append(0) - if is_incr_next_incr(perm): - properties.append(1) - if is_decr_next_decr(perm): - properties.append(2) - if is_decr_next_incr(perm): - properties.append(3) - - res = sum(1 << x for x in properties) - mem[perm] = res - - return res - - -def is_insertion_encodable_rightmost(basis): - goal = (1 << 4) - 1 - curr = 0 - # Check for insertion_encodable by rightmost - for perm in basis: - curr = curr | insertion_encodable_properties(perm) - if curr == goal: - return True - return False - - -def is_insertion_encodable_maximum(basis): - goal = (1 << 4) - 1 - curr = 0 - # Check for insertion_encodable by maximum - for perm in basis: - curr = curr | insertion_encodable_properties(perm.rotate()) - if curr == goal: - return True - return False - - -def is_insertion_encodable(basis): - return is_insertion_encodable_rightmost(basis) or is_insertion_encodable_maximum( - basis - ) +from itertools import islice +from typing import ClassVar, Dict, Iterable, Tuple + +from permuta.patterns.perm import Perm + + +class InsertionEncodablePerms: + """A static container of functions fortesting + if a basis has a regular insertion encoding. + """ + + _ALL_PROPERTIES: ClassVar[int] = 15 + _CACHE: ClassVar[Dict[Tuple, int]] = dict() + + @staticmethod + def _is_incr_next_incr(perm: Perm) -> bool: + n = len(perm) + return not any( + curr < prev and any(perm[j + 1] < perm[j] for j in range(i + 1, n - 1)) + for i, (prev, curr) in enumerate(zip(perm, islice(perm, 1, None))) + ) + + @staticmethod + def _is_incr_next_decr(perm: Perm) -> bool: + n = len(perm) + return not any( + curr < prev and any(perm[j + 1] > perm[j] for j in range(i + 1, n - 1)) + for i, (prev, curr) in enumerate(zip(perm, islice(perm, 1, None))) + ) + + @staticmethod + def _is_decr_next_incr(perm: Perm) -> bool: + n = len(perm) + return not any( + curr > prev and any(perm[j + 1] < perm[j] for j in range(i + 1, n - 1)) + for i, (prev, curr) in enumerate(zip(perm, islice(perm, 1, None))) + ) + + @staticmethod + def _is_decr_next_decr(perm: Perm) -> bool: + n = len(perm) + return not any( + curr > prev and any(perm[j + 1] > perm[j] for j in range(i + 1, n - 1)) + for i, (prev, curr) in enumerate(zip(perm, islice(perm, 1, None))) + ) + + @staticmethod + def _insertion_encodable_properties(perm: Perm) -> int: + properties = InsertionEncodablePerms._CACHE.get(perm, -1) + if properties < 0: + properties = sum( + val << shift + for shift, val in enumerate( + ( + InsertionEncodablePerms._is_incr_next_decr(perm), + InsertionEncodablePerms._is_incr_next_incr(perm), + InsertionEncodablePerms._is_decr_next_decr(perm), + InsertionEncodablePerms._is_decr_next_incr(perm), + ) + ) + ) + InsertionEncodablePerms._CACHE[perm] = properties + return properties + + @staticmethod + def is_insertion_encodable_rightmost(basis: Iterable[Perm]) -> bool: + """Check if basis is insertion encodable by rightmost.""" + curr = 0 + for perm in basis: + curr = curr | InsertionEncodablePerms._insertion_encodable_properties(perm) + if curr == InsertionEncodablePerms._ALL_PROPERTIES: + return True + return False + + @staticmethod + def is_insertion_encodable_maximum(basis: Iterable[Perm]) -> bool: + """Check if basis is insertion encodable by maximum.""" + curr = 0 + for perm in basis: + curr = curr | InsertionEncodablePerms._insertion_encodable_properties( + perm.rotate() + ) + if curr == InsertionEncodablePerms._ALL_PROPERTIES: + return True + return False + + @staticmethod + def is_insertion_encodable(basis: Iterable[Perm]) -> bool: + """Check if basis is insertion encodable.""" + return InsertionEncodablePerms.is_insertion_encodable_rightmost( + basis + ) or InsertionEncodablePerms.is_insertion_encodable_maximum(basis) diff --git a/permuta/permutils/polynomial.py b/permuta/permutils/polynomial.py index cda19373..25501a11 100644 --- a/permuta/permutils/polynomial.py +++ b/permuta/permutils/polynomial.py @@ -1,95 +1,115 @@ -# Will return the set of polynomial types it intersects with -# (W_++, W+-, W^-1 ++, L_2, L_2^R etc) -# 1: W++ -# 2: W+- -# 3: W-+ -# 4: W-- -# 5: Winv++ -# 6: Winv+- -# 7: Winv-+ -# 8: Winv-- -# 9: L2 -# 10: L2inv -mem = {} - - -def types(perm): - interset = mem.get(perm) - if interset is None: - interset = set([]) - for i in range(len(perm) + 1): - part1 = perm[0:i] - part2 = perm[i:] - - if is_incr(part1): - if is_incr(part2): - interset.add(1) - if is_decr(part2): - interset.add(2) - - if is_decr(part1): - if is_incr(part2): - interset.add(3) - if is_decr(part2): - interset.add(4) - - flipperm = perm.inverse() - for i in range(len(perm) + 1): - part1 = flipperm[0:i] - part2 = flipperm[i:] - - if is_incr(part1): - if is_incr(part2): - interset.add(5) - if is_decr(part2): - interset.add(6) - - if is_decr(part1): - if is_incr(part2): - interset.add(7) - if is_decr(part2): - interset.add(8) - if in_L2(perm): - interset.add(9) - if in_L2(perm.reverse()): - interset.add(10) - return interset - - -def is_decr(L): - for i in range(len(L) - 1): - if L[i] < L[i + 1]: - return False - return True - - -def is_incr(L): - for i in range(len(L) - 1): - if L[i] > L[i + 1]: - return False - return True - - -def in_L2(L): - n = len(L) - if n == 0 or n == 1: - return True - if L[-1] == n - 1: - return in_L2(L[0 : n - 1]) - elif L[-1] == n - 2 and L[-2] == n - 1: - return in_L2(L[0 : n - 2]) - else: - return False - - -def is_polynomial(basis): - overallinterset = set([]) - for perm in basis: - overallinterset = overallinterset.union(types(perm)) - if len(overallinterset) == 10: +from collections import deque +from enum import Enum +from itertools import islice +from typing import ClassVar, Deque, Dict, FrozenSet, Iterable, Iterator, Tuple + +from permuta.patterns.perm import Perm + + +class PermType(Enum): + """Collection of structural types for perms.""" + + WPP = 0 # W++ + WPM = 1 # W+- + WMP = 2 # W-+ + WMM = 3 # W-- + WIPP = 4 # Winv++ + WIPM = 5 # Winv+- + WIMP = 6 # Winv-+ + WIMM = 7 # Winv-- + L2 = 8 # L2 + L2I = 9 # L2inv + + +class PolyPerms: + """A static container of methods to check if a perm set has a polynomial growth.""" + + _CACHE: ClassVar[Dict[Perm, FrozenSet]] = {} + + @staticmethod + def _types(perm: Perm) -> FrozenSet[int]: + interset = PolyPerms._CACHE.get(perm) + if interset is None: + interset = frozenset(PolyPerms._find_type(perm)) + PolyPerms._CACHE[perm] = interset + return interset + + @staticmethod + def _type_0_3(slice1: Deque[int], slice2: Deque[int]) -> Iterator[PermType]: + if PolyPerms._is_incr(slice1): + if PolyPerms._is_incr(slice2): + yield PermType.WPP + if PolyPerms._is_decr(slice2): + yield PermType.WPM + if PolyPerms._is_decr(slice1): + if PolyPerms._is_incr(slice2): + yield PermType.WMP + if PolyPerms._is_decr(slice2): + yield PermType.WMM + + @staticmethod + def _type_4_7(slice1: Deque[int], slice2: Deque[int]) -> Iterator[PermType]: + if PolyPerms._is_incr(slice1): + if PolyPerms._is_incr(slice2): + yield PermType.WIPP + if PolyPerms._is_decr(slice2): + yield PermType.WIPM + if PolyPerms._is_decr(slice1): + if PolyPerms._is_incr(slice2): + yield PermType.WIMP + if PolyPerms._is_decr(slice2): + yield PermType.WIMM + + @staticmethod + def _find_type(perm: Perm) -> Iterable[PermType]: + p_deq1: Deque[int] = deque([]) + p_deq2: Deque[int] = deque(perm) + fp_deq1: Deque[int] = deque([]) + fp_deq2: Deque[int] = deque(perm.inverse()) + yield from PolyPerms._type_0_3(p_deq1, p_deq2) + yield from PolyPerms._type_4_7(fp_deq1, fp_deq2) + for _ in range(len(perm)): + p_deq1.append(p_deq2.popleft()) + fp_deq1.append(fp_deq2.popleft()) + yield from PolyPerms._type_0_3(p_deq1, p_deq2) + yield from PolyPerms._type_4_7(fp_deq1, fp_deq2) + if PolyPerms._of_type_8(perm): + yield PermType.L2 + if PolyPerms._of_type_8(perm.reverse()): + yield PermType.L2I + + @staticmethod + def _is_decr(perm_slice: Deque[int]) -> bool: + return all( + prev > curr for prev, curr in zip(perm_slice, islice(perm_slice, 1, None)) + ) + + @staticmethod + def _is_incr(perm_slice: Deque[int]) -> bool: + return all( + prev < curr for prev, curr in zip(perm_slice, islice(perm_slice, 1, None)) + ) + + @staticmethod + def _of_type_8(perm_slice: Tuple[int, ...]) -> bool: + n = len(perm_slice) + if n < 2: return True - return False - + if perm_slice[-1] == n - 1: + return PolyPerms._of_type_8(perm_slice[0 : n - 1]) + if perm_slice[-1] == n - 2 and perm_slice[-2] == n - 1: + return PolyPerms._of_type_8(perm_slice[0 : n - 2]) + return False -def is_non_polynomial(basis): - return not is_polynomial(basis) + @staticmethod + def is_polynomial(basis: Iterable[Perm]) -> bool: + """True if the perm set generated by basis has polynomial growth.""" + return ( + len({pol_type for perm in basis for pol_type in PolyPerms._types(perm)}) + == 10 + ) + + @staticmethod + def is_non_polynomial(basis: Iterable[Perm]) -> bool: + """False if the perm set generated by basis has polynomial growth.""" + return not PolyPerms.is_polynomial(basis) diff --git a/permuta/permutils/symmetry.py b/permuta/permutils/symmetry.py index ba98473b..b779f73a 100644 --- a/permuta/permutils/symmetry.py +++ b/permuta/permutils/symmetry.py @@ -1,99 +1,56 @@ -"""Return symmetries of sets.""" - - -def rotate_90_clockwise_set(perms): - try: - return perms.__class__([p._rotate_right() for p in perms]) - except (AttributeError, TypeError): - raise TypeError( - ("perms parameter must be of type list, set, tuple of" " permuta.Perms") - ) - - -def rotate_180_clockwise_set(perms): - try: - return perms.__class__([p._rotate_180() for p in perms]) - except (AttributeError, TypeError): - raise TypeError( - ("perms parameter must be of type list, set, tuple of" " permuta.Perms") - ) - - -def rotate_270_clockwise_set(perms): - try: - return perms.__class__([p._rotate_left() for p in perms]) - except (AttributeError, TypeError): - raise TypeError( - ("perms parameter must be of type list, set, tuple of" " permuta.Perms") - ) - - -def rotate_set(perms): - try: - return perms.__class__([p.rotate() for p in perms]) - except (AttributeError, TypeError): - raise TypeError( - ("perms parameter must be of type list, set, tuple of" " permuta.Perms") - ) - - -def inverse_set(perms): - try: - return perms.__class__([p.inverse() for p in perms]) - except (AttributeError, TypeError): - raise TypeError( - ("perms parameter must be of type list, set, tuple of" " permuta.Perms") - ) - - -def reverse_set(perms): - try: - return perms.__class__([p.reverse() for p in perms]) - except (AttributeError, TypeError): - raise TypeError( - ("perms parameter must be of type list, set, tuple of" " permuta.Perms") - ) - - -def complement_set(perms): - try: - return perms.__class__([p.complement() for p in perms]) - except (AttributeError, TypeError): - raise TypeError( - ("perms parameter must be of type list, set, tuple of" " permuta.Perms") - ) - - -def antidiagonal_set(perms): - try: - return perms.__class__([p.flip_antidiagonal() for p in perms]) - except (AttributeError, TypeError): - raise TypeError( - ("perms parameter must be of type list, set, tuple of" " permuta.Perms") - ) - - -def all_symmetry_sets(input_perms): - try: - perms = sorted(tuple(input_perms)) - answer = set() - for i in range(4): - answer.add(tuple(sorted(perms))) - answer.add(tuple(sorted(inverse_set(perms)))) - if i == 3: - break - perms = rotate_set(perms) - return answer - except (AttributeError, TypeError): - raise TypeError( - ("perms parameter must be of type list, set, tuple of" " permuta.Perms") - ) - - -def lex_min(perms): - try: - return perms.__class__(sorted(all_symmetry_sets(perms))[0]) - except (AttributeError, TypeError): - raise TypeError( - ("perms parameter must be of type list, set, tuple of" " permuta.Perms") - ) +from typing import Iterable + +from permuta.patterns.perm import Iterator, Perm, Set, Tuple + + +def rotate_90_clockwise_set(perms: Iterable[Perm]) -> Iterator[Perm]: + """Rotate all perms by 90 degrees.""" + return (perm.rotate() for perm in perms) + + +def rotate_180_clockwise_set(perms: Iterable[Perm]) -> Iterator[Perm]: + """Rotate all perms by 180 degrees.""" + return (perm.rotate(2) for perm in perms) + + +def rotate_270_clockwise_set(perms: Iterable[Perm]) -> Iterator[Perm]: + """Rotate all perms by 270 degrees.""" + return (perm.rotate(3) for perm in perms) + + +def inverse_set(perms: Iterable[Perm]) -> Iterator[Perm]: + """Return the inverse of each permutation a collection of perms.""" + return (perm.inverse() for perm in perms) + + +def reverse_set(perms: Iterable[Perm]) -> Iterator[Perm]: + """Return the reverse of each permutation a collection of perms.""" + return (perm.reverse() for perm in perms) + + +def complement_set(perms: Iterable[Perm]) -> Iterator[Perm]: + """Return the complement of each permutation in a collection of perms.""" + return (perm.complement() for perm in perms) + + +def antidiagonal_set(perms: Iterable[Perm]) -> Iterator[Perm]: + """Return the antidiagonal of each permutation in a collection of perms.""" + return (perm.flip_antidiagonal() for perm in perms) + + +def all_symmetry_sets(perms: Iterable[Perm]) -> Set[Tuple[Perm, ...]]: + """Given a collection of perms, return all possible collections formed by applying + symmetric operations on all perms. Each group has their permutation sorted. + """ + perms = perms if isinstance(perms, list) else list(perms) + answer = {tuple(sorted(perms)), tuple(sorted(inverse_set(perms)))} + for _ in range(3): + perms = list(rotate_90_clockwise_set(perms)) + answer.add(tuple(sorted(perms))) + answer.add(tuple(sorted(inverse_set(perms)))) + return answer + + +def lex_min(perms: Iterable[Perm]) -> Tuple[Perm, ...]: + """Find the lexicographical minimum of the sets of all symmetries.""" + return min(all_symmetry_sets(perms)) diff --git a/tests/_perm_set/__init__.py b/permuta/py.typed similarity index 100% rename from tests/_perm_set/__init__.py rename to permuta/py.typed diff --git a/permuta/bisc/permsets/Baxter_bad_len8 b/permuta/resources/bisc/Baxter_bad_len8.json similarity index 100% rename from permuta/bisc/permsets/Baxter_bad_len8 rename to permuta/resources/bisc/Baxter_bad_len8.json diff --git a/permuta/bisc/permsets/Baxter_good_len8 b/permuta/resources/bisc/Baxter_good_len8.json similarity index 100% rename from permuta/bisc/permsets/Baxter_good_len8 rename to permuta/resources/bisc/Baxter_good_len8.json diff --git a/permuta/bisc/permsets/SimSun_bad_len8 b/permuta/resources/bisc/SimSun_bad_len8.json similarity index 100% rename from permuta/bisc/permsets/SimSun_bad_len8 rename to permuta/resources/bisc/SimSun_bad_len8.json diff --git a/permuta/bisc/permsets/SimSun_bad_len9 b/permuta/resources/bisc/SimSun_bad_len9.json similarity index 100% rename from permuta/bisc/permsets/SimSun_bad_len9 rename to permuta/resources/bisc/SimSun_bad_len9.json diff --git a/permuta/bisc/permsets/SimSun_good_len8 b/permuta/resources/bisc/SimSun_good_len8.json similarity index 100% rename from permuta/bisc/permsets/SimSun_good_len8 rename to permuta/resources/bisc/SimSun_good_len8.json diff --git a/permuta/bisc/permsets/SimSun_good_len9 b/permuta/resources/bisc/SimSun_good_len9.json similarity index 100% rename from permuta/bisc/permsets/SimSun_good_len9 rename to permuta/resources/bisc/SimSun_good_len9.json diff --git a/permuta/bisc/permsets/West_2_stack_sortable_bad_len8 b/permuta/resources/bisc/West_2_stack_sortable_bad_len8.json similarity index 100% rename from permuta/bisc/permsets/West_2_stack_sortable_bad_len8 rename to permuta/resources/bisc/West_2_stack_sortable_bad_len8.json diff --git a/permuta/bisc/permsets/West_2_stack_sortable_good_len8 b/permuta/resources/bisc/West_2_stack_sortable_good_len8.json similarity index 100% rename from permuta/bisc/permsets/West_2_stack_sortable_good_len8 rename to permuta/resources/bisc/West_2_stack_sortable_good_len8.json diff --git a/permuta/bisc/permsets/av_231_and_mesh_bad_len8 b/permuta/resources/bisc/av_231_and_mesh_bad_len8.json similarity index 100% rename from permuta/bisc/permsets/av_231_and_mesh_bad_len8 rename to permuta/resources/bisc/av_231_and_mesh_bad_len8.json diff --git a/permuta/bisc/permsets/av_231_and_mesh_bad_len9 b/permuta/resources/bisc/av_231_and_mesh_bad_len9.json similarity index 100% rename from permuta/bisc/permsets/av_231_and_mesh_bad_len9 rename to permuta/resources/bisc/av_231_and_mesh_bad_len9.json diff --git a/permuta/bisc/permsets/av_231_and_mesh_good_len8 b/permuta/resources/bisc/av_231_and_mesh_good_len8.json similarity index 100% rename from permuta/bisc/permsets/av_231_and_mesh_good_len8 rename to permuta/resources/bisc/av_231_and_mesh_good_len8.json diff --git a/permuta/bisc/permsets/av_231_and_mesh_good_len9 b/permuta/resources/bisc/av_231_and_mesh_good_len9.json similarity index 100% rename from permuta/bisc/permsets/av_231_and_mesh_good_len9 rename to permuta/resources/bisc/av_231_and_mesh_good_len9.json diff --git a/permuta/bisc/permsets/dihedral_bad_len8 b/permuta/resources/bisc/dihedral_bad_len8.json similarity index 100% rename from permuta/bisc/permsets/dihedral_bad_len8 rename to permuta/resources/bisc/dihedral_bad_len8.json diff --git a/permuta/bisc/permsets/dihedral_good_len8 b/permuta/resources/bisc/dihedral_good_len8.json similarity index 100% rename from permuta/bisc/permsets/dihedral_good_len8 rename to permuta/resources/bisc/dihedral_good_len8.json diff --git a/permuta/bisc/permsets/forest_like_bad_len8 b/permuta/resources/bisc/forest_like_bad_len8.json similarity index 100% rename from permuta/bisc/permsets/forest_like_bad_len8 rename to permuta/resources/bisc/forest_like_bad_len8.json diff --git a/permuta/bisc/permsets/forest_like_good_len8 b/permuta/resources/bisc/forest_like_good_len8.json similarity index 100% rename from permuta/bisc/permsets/forest_like_good_len8 rename to permuta/resources/bisc/forest_like_good_len8.json diff --git a/permuta/bisc/permsets/in_alternating_group_bad_len8 b/permuta/resources/bisc/in_alternating_group_bad_len8.json similarity index 100% rename from permuta/bisc/permsets/in_alternating_group_bad_len8 rename to permuta/resources/bisc/in_alternating_group_bad_len8.json diff --git a/permuta/bisc/permsets/in_alternating_group_good_len8 b/permuta/resources/bisc/in_alternating_group_good_len8.json similarity index 100% rename from permuta/bisc/permsets/in_alternating_group_good_len8 rename to permuta/resources/bisc/in_alternating_group_good_len8.json diff --git a/permuta/bisc/permsets/quick_sortable_bad_len8 b/permuta/resources/bisc/quick_sortable_bad_len8.json similarity index 100% rename from permuta/bisc/permsets/quick_sortable_bad_len8 rename to permuta/resources/bisc/quick_sortable_bad_len8.json diff --git a/permuta/bisc/permsets/quick_sortable_good_len8 b/permuta/resources/bisc/quick_sortable_good_len8.json similarity index 100% rename from permuta/bisc/permsets/quick_sortable_good_len8 rename to permuta/resources/bisc/quick_sortable_good_len8.json diff --git a/permuta/bisc/permsets/smooth_bad_len8 b/permuta/resources/bisc/smooth_bad_len8.json similarity index 100% rename from permuta/bisc/permsets/smooth_bad_len8 rename to permuta/resources/bisc/smooth_bad_len8.json diff --git a/permuta/bisc/permsets/smooth_good_len8 b/permuta/resources/bisc/smooth_good_len8.json similarity index 100% rename from permuta/bisc/permsets/smooth_good_len8 rename to permuta/resources/bisc/smooth_good_len8.json diff --git a/permuta/bisc/permsets/stack_sortable_bad_len8 b/permuta/resources/bisc/stack_sortable_bad_len8.json similarity index 100% rename from permuta/bisc/permsets/stack_sortable_bad_len8 rename to permuta/resources/bisc/stack_sortable_bad_len8.json diff --git a/permuta/bisc/permsets/stack_sortable_good_len8 b/permuta/resources/bisc/stack_sortable_good_len8.json similarity index 100% rename from permuta/bisc/permsets/stack_sortable_good_len8 rename to permuta/resources/bisc/stack_sortable_good_len8.json diff --git a/permuta/bisc/permsets/yt_perm_avoids_22_bad_len8 b/permuta/resources/bisc/yt_perm_avoids_22_bad_len8.json similarity index 100% rename from permuta/bisc/permsets/yt_perm_avoids_22_bad_len8 rename to permuta/resources/bisc/yt_perm_avoids_22_bad_len8.json diff --git a/permuta/bisc/permsets/yt_perm_avoids_22_good_len8 b/permuta/resources/bisc/yt_perm_avoids_22_good_len8.json similarity index 100% rename from permuta/bisc/permsets/yt_perm_avoids_22_good_len8 rename to permuta/resources/bisc/yt_perm_avoids_22_good_len8.json diff --git a/permuta/bisc/permsets/yt_perm_avoids_32_bad_len8 b/permuta/resources/bisc/yt_perm_avoids_32_bad_len8.json similarity index 100% rename from permuta/bisc/permsets/yt_perm_avoids_32_bad_len8 rename to permuta/resources/bisc/yt_perm_avoids_32_bad_len8.json diff --git a/permuta/bisc/permsets/yt_perm_avoids_32_good_len8 b/permuta/resources/bisc/yt_perm_avoids_32_good_len8.json similarity index 100% rename from permuta/bisc/permsets/yt_perm_avoids_32_good_len8 rename to permuta/resources/bisc/yt_perm_avoids_32_good_len8.json diff --git a/setup.py b/setup.py index 3fead36c..077931b0 100755 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ def read(fname): setup( name="permuta", - version="1.5.0", + version="2.0.0", author="Permuta Triangle", author_email="permutatriangle@gmail.com", description="A comprehensive high performance permutation library.", @@ -25,8 +25,10 @@ def read(fname): "Tracker": "https://github.com/PermutaTriangle/Permuta/issues", }, packages=find_packages(), + package_data={"permuta": ["py.typed"]}, long_description=read("README.rst"), python_requires=">=3.6", + include_package_data=True, classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Education", diff --git a/tests/_perm_set/unbounded/all/test_perm_set_all.py b/tests/_perm_set/unbounded/all/test_perm_set_all.py deleted file mode 100644 index cfe435cc..00000000 --- a/tests/_perm_set/unbounded/all/test_perm_set_all.py +++ /dev/null @@ -1,7 +0,0 @@ -from permuta import PermSet - - -def test_random(): - for length in range(0, 10): - for _ in range(100): - assert list(range(length)) == sorted(PermSet(length).random()) diff --git a/tests/_perm_set/unbounded/described/avoiding/test_avoiding_generic.py b/tests/_perm_set/unbounded/described/avoiding/test_avoiding_generic.py deleted file mode 100644 index 6cd31ce7..00000000 --- a/tests/_perm_set/unbounded/described/avoiding/test_avoiding_generic.py +++ /dev/null @@ -1,148 +0,0 @@ -import pytest -from permuta import MeshPatt, Perm, PermSet -from permuta._perm_set.unbounded.described.avoiding import AvoidingGeneric -from permuta.descriptors import Basis, MeshBasis -from permuta.misc import catalan - - -def test_iter_getitem_same_principal_classes(): - maximum = 100 - for length in range(3, 5): - for patt in PermSet(length): - basis = Basis(patt) - avoiders = AvoidingGeneric(basis) - for index, perm in enumerate(avoiders): - assert perm == avoiders[index] - if index > maximum: - break - - -test_classes = [ - ([[0, 1]], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), - ([[1, 0]], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), - ([[0, 1, 2]], [catalan(i) for i in range(8)]), - ([[0, 2, 1]], [catalan(i) for i in range(8)]), - ([[1, 0, 2]], [catalan(i) for i in range(8)]), - ([[1, 2, 0]], [catalan(i) for i in range(8)]), - ([[2, 0, 1]], [catalan(i) for i in range(8)]), - ([[2, 1, 0]], [catalan(i) for i in range(8)]), - ([[0, 2, 1, 3]], [1, 1, 2, 6, 23, 103, 513, 2762]), - ([[0, 2, 3, 1]], [1, 1, 2, 6, 23, 103, 512, 2740, 15485]), - ([[0, 3, 2, 1]], [1, 1, 2, 6, 23, 103, 513, 2761, 15767]), - ([[1, 0, 2], [2, 1, 0]], [1, 1, 2, 4, 7, 11, 16, 22]), - ([[0, 2, 1], [3, 2, 1, 0]], [1, 1, 2, 5, 13, 31, 66, 127]), - ([[2, 1, 0], [1, 2, 3, 0]], [1, 1, 2, 5, 13, 34, 89, 233]), - ([[3, 2, 1, 0], [3, 2, 0, 1]], [1, 1, 2, 6, 22, 90, 394, 1806]), - ([[2, 3, 0, 1], [1, 3, 0, 2]], [1, 1, 2, 6, 22, 90, 395, 1823]), - ( - [[3, 1, 2, 0], [2, 4, 0, 3, 1], [3, 1, 4, 0, 2], [2, 4, 0, 5, 1, 3]], - [1, 1, 2, 6, 23, 101, 477, 2343, 11762], - ), - ([[0, 2, 1], [2, 1, 3, 4, 0]], [1, 1, 2, 5, 14, 41, 122, 365, 1094]), -] - - -@pytest.mark.parametrize("patts,enum", test_classes) -def test_avoiding_enumeration(patts, enum): - patts = [Perm(patt) for patt in patts] - basis = Basis(patts) - for (n, cnt) in enumerate(enum): - print(n, cnt) - inst = AvoidingGeneric(basis).of_length(n) - gen = list(inst) - assert len(gen) == cnt - assert len(gen) == len(set(gen)) - for perm in gen: - assert perm.avoids(*patts) - - mx = len(enum) - 1 - cnt = [0 for _ in range(mx + 1)] - for perm in AvoidingGeneric(basis): - if len(perm) > mx: - break - assert perm.avoids(*patts) - cnt[len(perm)] += 1 - - assert enum == cnt - - -def test_avoiding_generic_mesh_patterns(): - p = Perm((2, 0, 1)) - shading = ((2, 0), (2, 1), (2, 2), (2, 3)) - mps = [MeshPatt(p, shading)] - meshbasis = MeshBasis(mps) - avoiding_generic_basis = AvoidingGeneric(meshbasis) - enum = [1, 1, 2, 5, 15, 52, 203, 877] # Bell numbers - - for (n, cnt) in enumerate(enum): - inst = avoiding_generic_basis.of_length(n) - gen = list(inst) - assert len(gen) == cnt - assert len(gen) == len(set(gen)) - for perm in gen: - assert perm.avoids(*mps) - assert perm in avoiding_generic_basis - - for mp in mps: - with pytest.raises(TypeError): - mp in avoiding_generic_basis - - mx = len(enum) - 1 - cnt = [0 for _ in range(mx + 1)] - for perm in AvoidingGeneric(meshbasis): - if len(perm) > mx: - break - assert perm.avoids(*mps) - cnt[len(perm)] += 1 - - assert enum == cnt - - -def test_avoiding_generic_finite_class(): - ts = [ - ([[0]], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - ([[0, 1], [3, 2, 1, 0]], [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]), - ([[0, 1, 2], [3, 2, 1, 0]], [1, 1, 2, 5, 13, 25, 25, 0, 0, 0, 0, 0]), - ] - - for (patts, enum) in ts: - patts = [Perm(patt) for patt in patts] - basis = Basis(patts) - for (n, cnt) in enumerate(enum): - inst = AvoidingGeneric(basis).of_length(n) - gen = list(inst) - assert len(gen) == cnt - assert len(gen) == len(set(gen)) - for perm in gen: - assert perm.avoids(*patts) - - mx = len(enum) - 1 - cnt = [0 for _ in range(mx + 1)] - for perm in AvoidingGeneric(basis): - if len(perm) > mx: - break - assert perm.avoids(*patts) - cnt[len(perm)] += 1 - - assert enum == cnt - - -def test_is_subclass(): - av1 = AvoidingGeneric((Perm((0,)),)) - av12_21 = AvoidingGeneric((Perm((0, 1)), Perm((1, 0)))) - av123 = AvoidingGeneric((Perm((0, 1, 2)),)) - av1234 = AvoidingGeneric((Perm((0, 1, 2, 3)),)) - assert av1.is_subclass(av123) - assert not av123.is_subclass(av1) - assert av123.is_subclass(av1234) - assert not av1234.is_subclass(av12_21) - assert av12_21.is_subclass(av1234) - assert av123.is_subclass(av123) - av1324_1423_12345 = AvoidingGeneric( - (Perm((0, 2, 1, 3)), Perm((0, 3, 1, 2)), Perm((0, 1, 2, 3, 4, 5))) - ) - av1324_1234 = AvoidingGeneric((Perm((0, 2, 1, 3)), Perm((0, 1, 2, 3)))) - av1234_132 = AvoidingGeneric((Perm((0, 1, 2, 3)), Perm((0, 2, 1)))) - assert av123.is_subclass(av1324_1423_12345) - assert not av1324_1234.is_subclass(av1324_1423_12345) - assert av1234_132.is_subclass(av1324_1423_12345) diff --git a/tests/bisc/test_bisc.py b/tests/bisc/test_bisc.py index d0dff655..07237593 100644 --- a/tests/bisc/test_bisc.py +++ b/tests/bisc/test_bisc.py @@ -1,9 +1,9 @@ from permuta.bisc.bisc import bisc, read_bisc_file from permuta.bisc.bisc_subfunctions import patterns_suffice_for_bad, run_clean_up -from permuta.perm import Perm +from permuta.patterns.perm import Perm # Path to permutation files -ppf = "permuta/bisc/permsets/" +ppf = "permuta/resources/bisc/" def test_stack_sortable(): diff --git a/tests/bisc/test_perm_properties.py b/tests/bisc/test_perm_properties.py new file mode 100644 index 00000000..414522a1 --- /dev/null +++ b/tests/bisc/test_perm_properties.py @@ -0,0 +1,625 @@ +from math import factorial +from random import randint + +from permuta import MeshPatt, Perm +from permuta.bisc.perm_properties import ( + _bubble_sort, + _is_sorted, + _perm_to_yt, + _quick_sort, + _stack_sort, + av_231_and_mesh, + baxter, + bkv_sortable, + bubble_sortable, + dihedral, + forest_like, + hard_mesh, + in_alternating_group, + quick_sortable, + simsun, + smooth, + stack_sortable, + west_2_stack_sortable, + west_3_stack_sortable, + yt_perm_avoids_22, + yt_perm_avoids_32, +) + + +def test__is_sorted(): + assert _is_sorted([]) + assert _is_sorted([0]) + assert _is_sorted([0, 1]) + assert not _is_sorted([1, 0]) + assert _is_sorted([0, 1, 2]) + assert not _is_sorted([0, 2, 1]) + assert not _is_sorted([1, 0, 2]) + assert not _is_sorted([1, 2, 0]) + assert not _is_sorted([2, 0, 1]) + assert not _is_sorted([2, 1, 0]) + + +def test__stack_sort(): + assert _stack_sort([]) == [] + assert _stack_sort([0]) == [0] + assert _stack_sort([0, 1]) == [0, 1] + assert _stack_sort([1, 0]) == [0, 1] + assert _stack_sort([0, 1, 2]) == [0, 1, 2] + assert _stack_sort([0, 2, 1]) == [0, 1, 2] + assert _stack_sort([1, 0, 2]) == [0, 1, 2] + assert _stack_sort([1, 2, 0]) == [1, 0, 2] + assert _stack_sort([2, 0, 1]) == [0, 1, 2] + assert _stack_sort([2, 1, 0]) == [0, 1, 2] + assert _stack_sort([0, 1, 2, 3]) == [0, 1, 2, 3] + assert _stack_sort([0, 1, 3, 2]) == [0, 1, 2, 3] + assert _stack_sort([0, 2, 1, 3]) == [0, 1, 2, 3] + assert _stack_sort([0, 2, 3, 1]) == [0, 2, 1, 3] + assert _stack_sort([0, 3, 1, 2]) == [0, 1, 2, 3] + assert _stack_sort([0, 3, 2, 1]) == [0, 1, 2, 3] + assert _stack_sort([1, 0, 2, 3]) == [0, 1, 2, 3] + assert _stack_sort([1, 0, 3, 2]) == [0, 1, 2, 3] + assert _stack_sort([1, 2, 0, 3]) == [1, 0, 2, 3] + assert _stack_sort([1, 2, 3, 0]) == [1, 2, 0, 3] + assert _stack_sort([1, 3, 0, 2]) == [1, 0, 2, 3] + assert _stack_sort([1, 3, 2, 0]) == [1, 0, 2, 3] + assert _stack_sort([2, 0, 1, 3]) == [0, 1, 2, 3] + assert _stack_sort([2, 0, 3, 1]) == [0, 2, 1, 3] + assert _stack_sort([2, 1, 0, 3]) == [0, 1, 2, 3] + assert _stack_sort([2, 1, 3, 0]) == [1, 2, 0, 3] + assert _stack_sort([2, 3, 0, 1]) == [2, 0, 1, 3] + assert _stack_sort([2, 3, 1, 0]) == [2, 0, 1, 3] + assert _stack_sort([3, 0, 1, 2]) == [0, 1, 2, 3] + assert _stack_sort([3, 0, 2, 1]) == [0, 1, 2, 3] + assert _stack_sort([3, 1, 0, 2]) == [0, 1, 2, 3] + assert _stack_sort([3, 1, 2, 0]) == [1, 0, 2, 3] + assert _stack_sort([3, 2, 0, 1]) == [0, 1, 2, 3] + assert _stack_sort([3, 2, 1, 0]) == [0, 1, 2, 3] + patt231 = Perm((1, 2, 0)) + for _ in range(100): + p = Perm.random(randint(5, 20)) + if p.avoids(patt231): + assert list(_stack_sort(list(p))) == sorted(range(len(p))) + else: + assert not list(_stack_sort(list(p))) == sorted(range(len(p))) + + +def test_stack_sortable(): + assert stack_sortable(Perm(())) + assert stack_sortable(Perm((0,))) + assert stack_sortable(Perm((0, 1))) + assert stack_sortable(Perm((1, 0))) + assert stack_sortable(Perm((0, 1, 2))) + assert stack_sortable(Perm((0, 2, 1))) + assert stack_sortable(Perm((1, 0, 2))) + assert not stack_sortable(Perm((1, 2, 0))) + assert stack_sortable(Perm((2, 0, 1))) + assert stack_sortable(Perm((2, 1, 0))) + assert stack_sortable(Perm((2, 1, 0, 4, 3))) + assert not stack_sortable(Perm((1, 4, 3, 0, 8, 5, 2, 9, 7, 6))) + assert not stack_sortable(Perm((1, 3, 0, 2))) + assert stack_sortable(Perm((3, 0, 1, 2))) + assert not stack_sortable(Perm((6, 2, 4, 3, 7, 1, 5, 0))) + assert not stack_sortable(Perm((4, 8, 5, 0, 6, 1, 7, 3, 2))) + assert stack_sortable(Perm((0, 1, 3, 2))) + assert stack_sortable(Perm((3, 0, 2, 1))) + assert not stack_sortable(Perm((1, 0, 7, 2, 5, 4, 3, 8, 9, 6))) + assert not stack_sortable(Perm((4, 1, 5, 2, 6, 0, 3, 8, 7, 9))) + assert stack_sortable(Perm((0, 12, 11, 10, 9, 1, 5, 4, 2, 3, 6, 7, 8, 14, 13))) + + +def test__bubble_sort(): + assert _bubble_sort([]) == [] + assert _bubble_sort([0]) == [0] + assert _bubble_sort([0, 1]) == [0, 1] + assert _bubble_sort([1, 0]) == [0, 1] + assert _bubble_sort([0, 1, 2]) == [0, 1, 2] + assert _bubble_sort([0, 2, 1]) == [0, 1, 2] + assert _bubble_sort([1, 0, 2]) == [0, 1, 2] + assert _bubble_sort([1, 2, 0]) == [1, 0, 2] + assert _bubble_sort([2, 0, 1]) == [0, 1, 2] + assert _bubble_sort([2, 1, 0]) == [1, 0, 2] + assert _bubble_sort([0, 3, 2, 4, 1]) == [0, 2, 3, 1, 4] + assert _bubble_sort([1, 4, 2, 5, 0, 3]) == [1, 2, 4, 0, 3, 5] + assert _bubble_sort([3, 6, 7, 2, 5, 4, 1, 0]) == [3, 6, 2, 5, 4, 1, 0, 7] + assert _bubble_sort([2, 6, 7, 8, 5, 1, 9, 4, 3, 0]) == [ + 2, + 6, + 7, + 5, + 1, + 8, + 4, + 3, + 0, + 9, + ] + assert _bubble_sort([5, 4, 7, 9, 1, 6, 3, 2, 8, 0]) == [ + 4, + 5, + 7, + 1, + 6, + 3, + 2, + 8, + 0, + 9, + ] + + +def test_bubble_sortable(): + enumeration = [1] + enumeration.extend([2 ** i for i in range(10)]) + assert bubble_sortable(Perm(())) + assert bubble_sortable(Perm((0,))) + assert bubble_sortable(Perm((0, 1))) + assert bubble_sortable(Perm((1, 0))) + assert bubble_sortable(Perm((0, 1, 2))) + assert bubble_sortable(Perm((0, 2, 1))) + assert bubble_sortable(Perm((1, 0, 2))) + assert not bubble_sortable(Perm((1, 2, 0))) + assert bubble_sortable(Perm((2, 0, 1))) + assert not bubble_sortable(Perm((2, 1, 0))) + assert bubble_sortable(Perm((2, 0, 1, 3))) + assert not bubble_sortable(Perm((1, 4, 3, 2, 0))) + assert not bubble_sortable(Perm((0, 1, 5, 4, 2, 3))) + assert not bubble_sortable(Perm((1, 3, 4, 0, 6, 2, 5, 7))) + assert not bubble_sortable(Perm((1, 5, 6, 7, 0, 3, 2, 4))) + assert not bubble_sortable(Perm((2, 3, 1, 6, 4, 7, 5, 0))) + assert not bubble_sortable(Perm((2, 5, 1, 4, 7, 0, 6, 3))) + assert not bubble_sortable(Perm((3, 0, 4, 5, 7, 6, 1, 2))) + assert not bubble_sortable(Perm((4, 0, 7, 5, 3, 6, 2, 1))) + assert not bubble_sortable(Perm((6, 5, 2, 0, 3, 7, 1, 8, 4))) + for i in range(4, 7): + assert sum(1 for p in Perm.of_length(i) if bubble_sortable(p)) == enumeration[i] + patts = (Perm((1, 2, 0)), Perm((2, 1, 0))) + for i in range(20): + p = Perm.random(randint(5, 10)) + if p.avoids(*patts): + assert bubble_sortable(p) + else: + assert not bubble_sortable(p) + + +def test__quick_sort(): + assert _quick_sort([]) == [] + assert _quick_sort([0]) == [0] + assert _quick_sort([0, 1]) == [0, 1] + assert _quick_sort([1, 0]) == [0, 1] + assert _quick_sort([0, 1, 2]) == [0, 1, 2] + assert _quick_sort([0, 2, 1]) == [0, 1, 2] + assert _quick_sort([1, 0, 2]) == [0, 1, 2] + assert _quick_sort([1, 2, 0]) == [0, 1, 2] + assert _quick_sort([2, 0, 1]) == [0, 1, 2] + assert _quick_sort([2, 1, 0]) == [1, 0, 2] + assert _quick_sort([0, 1, 2, 3]) == [0, 1, 2, 3] + assert _quick_sort([0, 1, 2, 4, 3]) == [0, 1, 2, 3, 4] + assert _quick_sort([2, 3, 5, 4, 0, 1]) == [0, 1, 2, 3, 5, 4] + assert _quick_sort([3, 4, 1, 2, 0, 5]) == [1, 2, 0, 3, 4, 5] + assert _quick_sort([3, 1, 2, 0, 6, 4, 5]) == [1, 2, 0, 3, 6, 4, 5] + assert _quick_sort([5, 6, 1, 2, 0, 3, 4]) == [1, 2, 0, 3, 4, 5, 6] + assert _quick_sort([6, 5, 3, 2, 1, 0, 4]) == [5, 3, 2, 1, 0, 4, 6] + assert _quick_sort([0, 3, 8, 5, 2, 1, 7, 6, 4]) == [0, 2, 1, 3, 8, 5, 7, 6, 4] + assert _quick_sort([4, 6, 0, 5, 7, 1, 3, 2, 8]) == [0, 1, 3, 2, 4, 6, 5, 7, 8] + assert _quick_sort([4, 0, 3, 7, 1, 8, 2, 9, 5, 6]) == [0, 3, 1, 2, 4, 7, 8, 9, 5, 6] + + +def test_quick_sortable(): + enumeration = [1, 1, 2, 5, 12, 28, 65, 151, 351, 816] + for i in range(4, 7): + assert sum(1 for p in Perm.of_length(i) if quick_sortable(p)) == enumeration[i] + assert quick_sortable(Perm(())) + assert quick_sortable(Perm((0,))) + assert quick_sortable(Perm((0, 1))) + assert quick_sortable(Perm((1, 0))) + assert quick_sortable(Perm((0, 1, 2))) + assert quick_sortable(Perm((0, 2, 1))) + assert quick_sortable(Perm((1, 0, 2))) + assert quick_sortable(Perm((1, 2, 0))) + assert quick_sortable(Perm((2, 0, 1))) + assert not quick_sortable(Perm((2, 1, 0))) + assert not quick_sortable(Perm((2, 1, 0, 3))) + assert not quick_sortable(Perm((2, 3, 1, 0))) + assert not quick_sortable(Perm((3, 4, 2, 1, 0))) + assert not quick_sortable(Perm((4, 3, 2, 0, 1))) + assert quick_sortable(Perm((0, 2, 1, 3, 4, 5))) + assert quick_sortable(Perm((0, 2, 3, 4, 5, 1))) + assert not quick_sortable(Perm((5, 0, 1, 4, 3, 2, 6))) + assert not quick_sortable(Perm((3, 6, 5, 4, 7, 2, 0, 1))) + assert not quick_sortable(Perm((6, 9, 7, 2, 3, 0, 5, 4, 1, 8))) + assert not quick_sortable(Perm((7, 5, 0, 4, 3, 1, 9, 2, 6, 8))) + + +def test_bkv_sortable(): + patts_to_enumeration = { + (Perm((0, 1, 2)), Perm((0, 2, 1))): [1, 1, 2, 5, 14, 42, 132, 429], + (Perm((1, 2, 0)), Perm((0, 2, 1))): [1, 1, 2, 6, 22, 90, 394, 1806], + (): [1, 1, 2, 5, 14, 42, 132, 429], + (Perm((2, 1, 0)),): [1, 1, 2, 4, 8, 16, 32, 64], + (Perm((2, 1, 0, 3)),): [1, 1, 2, 5, 13, 34, 89, 233], + (Perm((3, 1, 0, 2)),): [1, 1, 2, 5, 13, 34, 89, 233], + (Perm((3, 2, 0, 1)),): [1, 1, 2, 5, 13, 34, 89, 233], + (Perm((3, 2, 1, 0)),): [1, 1, 2, 5, 13, 34, 89, 233], + (Perm((2, 1, 0, 3, 4)),): [1, 1, 2, 5, 14, 41, 121, 355], + (Perm((4, 1, 0, 2, 3)),): [1, 1, 2, 5, 14, 41, 121, 355], + (Perm((4, 3, 0, 1, 2)),): [1, 1, 2, 5, 14, 41, 121, 356], + (Perm((2, 1, 0, 4, 3)),): [1, 1, 2, 5, 14, 41, 122, 365], + (Perm((3, 1, 0, 2, 4)),): [1, 1, 2, 5, 14, 41, 122, 365], + (Perm((3, 2, 0, 1, 4)),): [1, 1, 2, 5, 14, 41, 122, 365], + (Perm((3, 2, 1, 0, 4)),): [1, 1, 2, 5, 14, 41, 122, 365], + (Perm((4, 1, 0, 3, 2)),): [1, 1, 2, 5, 14, 41, 122, 365], + (Perm((4, 2, 0, 1, 3)),): [1, 1, 2, 5, 14, 41, 122, 365], + (Perm((4, 2, 1, 0, 3)),): [1, 1, 2, 5, 14, 41, 122, 365], + (Perm((4, 3, 0, 2, 1)),): [1, 1, 2, 5, 14, 41, 122, 365], + (Perm((4, 3, 1, 0, 2)),): [1, 1, 2, 5, 14, 41, 122, 365], + (Perm((4, 3, 2, 0, 1)),): [1, 1, 2, 5, 14, 41, 122, 365], + (Perm((4, 3, 2, 1, 0)),): [1, 1, 2, 5, 14, 41, 122, 365], + } + + all_patts = patts_to_enumeration.keys() + compares = 7 # can be increased to 8 at most but slow + res = {k: [0] * compares for k in all_patts} + for i in range(compares): + for perm in Perm.of_length(i): + for k in all_patts: + if bkv_sortable(perm, k): + res[k][i] += 1 + slicer = compares - 8 + if slicer == 0: + slicer = 8 + assert all(res[pat] == patts_to_enumeration[pat][:slicer] for pat in all_patts) + assert bkv_sortable(Perm((0, 1)), ()) + assert not bkv_sortable(Perm((7, 6, 9, 2, 0, 3, 8, 1, 5, 4)), (Perm((0, 2, 1)),)) + assert not bkv_sortable(Perm((4, 5, 2, 6, 7, 8, 1, 0, 3)), (Perm((0, 1)),)) + assert bkv_sortable(Perm((2, 0, 1, 3, 4, 5, 6)), (Perm((2, 0, 1, 3)),)) + assert not bkv_sortable( + Perm((5, 2, 1, 0, 3, 4)), (Perm((2, 1, 0)), Perm((0, 2, 1))) + ) + assert bkv_sortable(Perm((3, 2, 1, 0, 4)), ()) + assert not bkv_sortable(Perm((4, 1, 2, 3, 0, 5)), (Perm((0, 2, 1)),)) + assert bkv_sortable(Perm((2, 0, 1)), (Perm((0, 1)),)) + assert bkv_sortable(Perm((2, 1, 0)), (Perm((2, 0, 1, 3)),)) + assert bkv_sortable(Perm((5, 3, 1, 0, 4, 2)), (Perm((2, 1, 0)), Perm((0, 2, 1)))) + assert bkv_sortable(Perm((0,)), ()) + assert bkv_sortable(Perm((2, 1, 3, 0)), (Perm((0, 2, 1)),)) + assert bkv_sortable(Perm((0,)), (Perm((0, 1)),)) + assert not bkv_sortable(Perm((6, 4, 0, 5, 8, 3, 2, 7, 1)), (Perm((2, 0, 1, 3)),)) + assert bkv_sortable(Perm(()), (Perm((2, 1, 0)), Perm((0, 2, 1)))) + assert not bkv_sortable(Perm((4, 1, 3, 2, 0)), ()) + assert not bkv_sortable(Perm((1, 2, 0, 3)), (Perm((0, 2, 1)),)) + assert bkv_sortable(Perm(()), (Perm((0, 1)),)) + assert not bkv_sortable(Perm((1, 5, 4, 2, 0, 3)), (Perm((2, 0, 1, 3)),)) + assert bkv_sortable(Perm(()), (Perm((2, 1, 0)), Perm((0, 2, 1)))) + + +def test_west_2_stack_sortable(): + eq_patt_avoidance = (Perm((1, 2, 3, 0)), MeshPatt(Perm((2, 1, 3, 0)), {(1, 4)})) + for _ in range(100): + if randint(0, 10) < 1: + p = Perm.random(randint(0, 3)) + else: + p = Perm.random(randint(4, 10)) + if p.avoids(*eq_patt_avoidance): + assert west_2_stack_sortable(p) + else: + assert not west_2_stack_sortable(p) + for n in (5, 6, 7): + assert sum( + 1 for p in Perm.of_length(n) if west_2_stack_sortable(p) + ) == 2 * factorial(3 * n) / (factorial(n + 1) * factorial(2 * n + 1)) + + +def test_west_3_stack_sortable(): + assert west_3_stack_sortable(Perm(())) + assert west_3_stack_sortable(Perm((0,))) + assert west_3_stack_sortable(Perm((0, 1))) + assert west_3_stack_sortable(Perm((1, 0))) + assert west_3_stack_sortable(Perm((0, 1, 2))) + assert west_3_stack_sortable(Perm((0, 2, 1))) + assert west_3_stack_sortable(Perm((1, 0, 2))) + assert west_3_stack_sortable(Perm((1, 2, 0))) + assert west_3_stack_sortable(Perm((2, 0, 1))) + assert west_3_stack_sortable(Perm((2, 1, 0))) + assert west_3_stack_sortable(Perm((0, 1, 2, 3))) + assert west_3_stack_sortable(Perm((0, 1, 3, 2))) + assert west_3_stack_sortable(Perm((2, 0, 5, 4, 1, 3))) + assert not west_3_stack_sortable(Perm((3, 2, 0, 4, 5, 1))) + assert west_3_stack_sortable(Perm((4, 6, 3, 2, 1, 0, 5))) + assert west_3_stack_sortable(Perm((6, 2, 3, 0, 5, 1, 4))) + assert west_3_stack_sortable(Perm((0, 3, 6, 2, 5, 1, 7, 4))) + assert west_3_stack_sortable(Perm((0, 3, 7, 8, 1, 6, 4, 2, 5))) + assert not west_3_stack_sortable(Perm((0, 2, 3, 4, 6, 5, 1))) + assert not west_3_stack_sortable(Perm((2, 3, 0, 4, 6, 1, 5))) + assert not west_3_stack_sortable(Perm((5, 4, 8, 0, 3, 6, 7, 2, 1))) + assert not west_3_stack_sortable(Perm((0, 3, 7, 5, 1, 4, 8, 6, 2, 9))) + assert not west_3_stack_sortable(Perm((5, 1, 11, 10, 9, 8, 6, 0, 4, 3, 7, 2))) + assert not west_3_stack_sortable( + Perm((4, 6, 9, 10, 13, 2, 0, 5, 8, 12, 3, 1, 11, 7)) + ) + assert 3494 == sum(1 for p in Perm.of_length(7) if west_3_stack_sortable(p)) + + +def test_smooth(): + assert smooth(Perm(())) + assert smooth(Perm((0,))) + assert smooth(Perm((0, 1))) + assert smooth(Perm((1, 0))) + assert smooth(Perm((0, 1, 2))) + assert smooth(Perm((0, 2, 1))) + assert smooth(Perm((1, 0, 2))) + assert smooth(Perm((1, 2, 0))) + assert smooth(Perm((2, 0, 1))) + assert smooth(Perm((2, 1, 0))) + assert smooth(Perm((0, 1, 2, 3))) + assert smooth(Perm((0, 1, 3, 2))) + assert not smooth(Perm((0, 3, 5, 1, 2, 4))) + assert not smooth(Perm((3, 4, 1, 0, 5, 2))) + assert not smooth(Perm((0, 3, 2, 4, 5, 6, 1))) + assert not smooth(Perm((5, 3, 1, 4, 2, 6, 0))) + assert smooth(Perm((3, 7, 2, 0, 4, 5, 6, 1))) + assert not smooth(Perm((4, 7, 1, 3, 8, 5, 0, 6, 2))) + assert 1552 == sum(1 for p in Perm.of_length(7) if smooth(p)) + + +def test_forest_like(): + assert forest_like(Perm(())) + assert forest_like(Perm((0,))) + assert forest_like(Perm((0, 1))) + assert forest_like(Perm((1, 0))) + assert forest_like(Perm((0, 1, 2))) + assert forest_like(Perm((0, 2, 1))) + assert forest_like(Perm((1, 0, 2))) + assert forest_like(Perm((1, 2, 0))) + assert forest_like(Perm((2, 0, 1))) + assert forest_like(Perm((2, 1, 0))) + assert forest_like(Perm((0, 1, 2, 3))) + assert forest_like(Perm((0, 1, 3, 2))) + assert not forest_like(Perm((3, 4, 1, 0, 5, 2))) + assert forest_like(Perm((5, 4, 0, 1, 3, 2))) + assert forest_like(Perm((6, 0, 1, 5, 2, 3, 4))) + assert forest_like(Perm((6, 1, 2, 5, 3, 4, 0))) + assert forest_like(Perm((7, 2, 3, 4, 6, 5, 1, 0))) + assert not forest_like(Perm((0, 1, 6, 7, 4, 2, 3, 8, 5))) + assert 1661 == sum(1 for p in Perm.of_length(7) if forest_like(p)) + + +def test_baxter(): + assert baxter(Perm(())) + assert baxter(Perm((0,))) + assert baxter(Perm((0, 1))) + assert baxter(Perm((1, 0))) + assert baxter(Perm((0, 1, 2))) + assert baxter(Perm((0, 2, 1))) + assert baxter(Perm((1, 0, 2))) + assert baxter(Perm((1, 2, 0))) + assert baxter(Perm((2, 0, 1))) + assert baxter(Perm((2, 1, 0))) + assert baxter(Perm((0, 1, 2, 3))) + assert baxter(Perm((0, 1, 3, 2))) + assert not baxter(Perm((1, 3, 0, 5, 4, 2))) + assert not baxter(Perm((5, 3, 1, 0, 4, 2))) + assert baxter(Perm((5, 4, 3, 0, 1, 2, 6))) + assert baxter(Perm((6, 0, 5, 3, 4, 1, 2))) + assert baxter(Perm((5, 3, 4, 2, 0, 1, 6, 7))) + assert baxter(Perm((1, 2, 4, 6, 5, 3, 0, 8, 7))) + assert 2074 == sum(1 for p in Perm.of_length(7) if baxter(p)) + + +def test_simsun(): + assert simsun(Perm(())) + assert simsun(Perm((0,))) + assert simsun(Perm((0, 1))) + assert simsun(Perm((1, 0))) + assert simsun(Perm((0, 1, 2))) + assert simsun(Perm((0, 2, 1))) + assert simsun(Perm((1, 0, 2))) + assert simsun(Perm((1, 2, 0))) + assert simsun(Perm((2, 0, 1))) + assert not simsun(Perm((2, 1, 0))) + assert simsun(Perm((0, 1, 2, 3))) + assert simsun(Perm((0, 1, 3, 2))) + assert not simsun(Perm((0, 5, 1, 4, 2, 3))) + assert not simsun(Perm((1, 3, 5, 4, 2, 0))) + assert simsun(Perm((1, 5, 0, 2, 3, 6, 4))) + assert not simsun(Perm((3, 1, 2, 6, 5, 0, 4))) + assert not simsun(Perm((4, 7, 3, 0, 6, 5, 2, 1))) + assert not simsun(Perm((4, 6, 7, 0, 3, 5, 2, 8, 1))) + assert 429 == sum(1 for p in Perm.of_length(7) if simsun(p)) + + +def test_dihedral(): + assert not dihedral(Perm(())) + assert not dihedral(Perm((0,))) + assert not dihedral(Perm((0, 1))) + assert not dihedral(Perm((1, 0))) + assert dihedral(Perm((0, 1, 2))) + assert dihedral(Perm((0, 2, 1))) + assert dihedral(Perm((1, 0, 2))) + assert dihedral(Perm((1, 2, 0))) + assert dihedral(Perm((2, 0, 1))) + assert dihedral(Perm((2, 1, 0))) + assert dihedral(Perm((0, 1, 2, 3))) + assert not dihedral(Perm((0, 1, 3, 2))) + assert not dihedral(Perm((3, 4, 1, 0, 2, 5))) + assert not dihedral(Perm((5, 4, 2, 1, 0, 3))) + assert not dihedral(Perm((2, 3, 6, 1, 0, 5, 4))) + assert not dihedral(Perm((6, 4, 2, 5, 1, 3, 0))) + assert not dihedral(Perm((1, 6, 4, 2, 0, 5, 3, 7))) + assert not dihedral(Perm((2, 0, 3, 8, 1, 5, 7, 4, 6))) + assert not dihedral(Perm((1, 7, 4, 3, 8, 9, 6, 2, 5, 0))) + assert not dihedral(Perm((5, 6, 2, 10, 1, 0, 9, 3, 7, 4, 8))) + assert all( + 2 * n == sum(1 for perm in Perm.of_length(n) if dihedral(perm)) + for n in range(5, 8) + ) + + +def test_in_alternating_group(): + assert in_alternating_group(Perm(())) + assert in_alternating_group(Perm((0,))) + assert not in_alternating_group(Perm((0, 1))) + assert not in_alternating_group(Perm((1, 0))) + assert in_alternating_group(Perm((0, 1, 2))) + assert not in_alternating_group(Perm((0, 2, 1))) + assert not in_alternating_group(Perm((1, 0, 2))) + assert in_alternating_group(Perm((1, 2, 0))) + assert in_alternating_group(Perm((2, 0, 1))) + assert not in_alternating_group(Perm((2, 1, 0))) + assert in_alternating_group(Perm((0, 1, 2, 3))) + assert not in_alternating_group(Perm((0, 1, 3, 2))) + assert not in_alternating_group(Perm((0, 4, 1, 5, 2, 3))) + assert in_alternating_group(Perm((5, 4, 0, 2, 1, 3))) + assert not in_alternating_group(Perm((0, 4, 3, 5, 2, 6, 1))) + assert not in_alternating_group(Perm((1, 3, 0, 2, 5, 6, 4))) + assert in_alternating_group(Perm((5, 7, 2, 1, 4, 0, 3, 6))) + assert not in_alternating_group(Perm((3, 0, 6, 1, 2, 8, 5, 4, 7))) + assert in_alternating_group(Perm((5, 1, 6, 4, 9, 8, 2, 0, 7, 3))) + assert not in_alternating_group(Perm((7, 0, 2, 4, 8, 5, 1, 9, 6, 3, 10))) + assert 2520 == sum(1 for p in Perm.of_length(7) if in_alternating_group(p)) + + +def test__perm_to_yt(): + z = {i: set() for i in range(8)} + for i in range(8): + for p in Perm.of_length(i): + z[i].add(tuple(map(tuple, _perm_to_yt(p)))) + expected = [1, 1, 2, 4, 10, 26, 76, 232] + for i in range(8): + assert len(z[i]) == expected[i] + assert _perm_to_yt(Perm((1, 0))) == [[0], [1]] + assert _perm_to_yt(Perm((1, 0, 3, 5, 2, 4))) == [[0, 2, 4], [1, 3, 5]] + assert _perm_to_yt(Perm((1, 0, 2, 3))) == [[0, 2, 3], [1]] + assert _perm_to_yt(Perm((3, 1, 4, 0, 2))) == [[0, 2], [1, 4], [3]] + assert _perm_to_yt(Perm((5, 4, 1, 0, 3, 2))) == [[0, 2], [1, 3], [4], [5]] + assert _perm_to_yt(Perm((0, 1))) == [[0, 1]] + assert _perm_to_yt(Perm((0,))) == [[0]] + assert _perm_to_yt(Perm((2, 3, 0, 1))) == [[0, 1], [2, 3]] + assert _perm_to_yt(Perm((1, 2, 3, 0))) == [[0, 2, 3], [1]] + assert _perm_to_yt(Perm((4, 0, 1, 2, 5, 3))) == [[0, 1, 2, 3], [4, 5]] + + +def test_yt_perm_avoids_22(): + assert yt_perm_avoids_22(Perm()) + assert yt_perm_avoids_22(Perm((0,))) + assert yt_perm_avoids_22(Perm((0, 1))) + assert yt_perm_avoids_22(Perm((1, 0))) + assert yt_perm_avoids_22(Perm((0, 1, 2))) + assert yt_perm_avoids_22(Perm((0, 2, 1))) + assert yt_perm_avoids_22(Perm((1, 0, 2))) + assert yt_perm_avoids_22(Perm((1, 2, 0))) + assert yt_perm_avoids_22(Perm((2, 0, 1))) + assert yt_perm_avoids_22(Perm((2, 1, 0))) + assert yt_perm_avoids_22(Perm((0, 1, 2, 3))) + assert yt_perm_avoids_22(Perm((0, 1, 3, 2))) + assert yt_perm_avoids_22(Perm((2, 5, 3, 1, 0, 4))) + assert yt_perm_avoids_22(Perm((4, 3, 1, 2, 0, 5))) + assert not yt_perm_avoids_22(Perm((3, 0, 4, 1, 5, 6, 2))) + assert not yt_perm_avoids_22(Perm((5, 6, 0, 2, 4, 3, 1))) + assert not yt_perm_avoids_22(Perm((7, 4, 3, 5, 0, 2, 6, 1))) + assert yt_perm_avoids_22(Perm((3, 4, 5, 6, 2, 7, 1, 0, 8))) + assert not yt_perm_avoids_22(Perm((7, 5, 2, 0, 4, 8, 6, 9, 1, 3))) + assert not yt_perm_avoids_22(Perm((3, 5, 4, 6, 0, 7, 2, 10, 1, 8, 9))) + assert not yt_perm_avoids_22(Perm((1, 2, 0, 4, 3, 5))) + assert not yt_perm_avoids_22(Perm((5, 2, 0, 3, 1, 4))) + assert not yt_perm_avoids_22(Perm((1, 0, 6, 4, 3, 5, 2))) + assert not yt_perm_avoids_22(Perm((5, 0, 1, 3, 6, 2, 4))) + assert not yt_perm_avoids_22(Perm((5, 1, 2, 7, 4, 3, 0, 6))) + assert not yt_perm_avoids_22(Perm((5, 6, 0, 8, 3, 7, 4, 1, 2))) + assert not yt_perm_avoids_22(Perm((2, 5, 7, 6, 1, 8, 9, 3, 0, 4))) + assert not yt_perm_avoids_22(Perm((7, 8, 4, 0, 1, 5, 2, 10, 3, 9, 6))) + assert 924 == sum(1 for p in Perm.of_length(7) if yt_perm_avoids_22(p)) + + +def test_yt_perm_avoids_32(): + assert yt_perm_avoids_32(Perm()) + assert yt_perm_avoids_32(Perm((0,))) + assert yt_perm_avoids_32(Perm((0, 1))) + assert yt_perm_avoids_32(Perm((1, 0))) + assert yt_perm_avoids_32(Perm((0, 1, 2))) + assert yt_perm_avoids_32(Perm((0, 2, 1))) + assert yt_perm_avoids_32(Perm((1, 0, 2))) + assert yt_perm_avoids_32(Perm((1, 2, 0))) + assert yt_perm_avoids_32(Perm((2, 0, 1))) + assert yt_perm_avoids_32(Perm((2, 1, 0))) + assert yt_perm_avoids_32(Perm((0, 1, 2, 3))) + assert yt_perm_avoids_32(Perm((0, 1, 3, 2))) + assert yt_perm_avoids_32(Perm((2, 3, 1, 4, 0, 5))) + assert not yt_perm_avoids_32(Perm((2, 3, 1, 5, 4, 0))) + assert not yt_perm_avoids_32(Perm((2, 0, 3, 1, 4, 5, 6))) + assert not yt_perm_avoids_32(Perm((5, 1, 2, 0, 4, 3, 6))) + assert not yt_perm_avoids_32(Perm((5, 0, 4, 2, 1, 6, 3, 7))) + assert not yt_perm_avoids_32(Perm((0, 6, 3, 1, 8, 7, 4, 5, 2))) + assert not yt_perm_avoids_32(Perm((6, 4, 9, 0, 3, 1, 8, 7, 5, 2))) + assert not yt_perm_avoids_32(Perm((10, 8, 1, 4, 0, 5, 2, 7, 9, 3, 6))) + assert yt_perm_avoids_32(Perm((7, 3, 2, 4, 5, 1, 0, 6))) + assert yt_perm_avoids_32(Perm((5, 4, 6, 1, 3, 2, 0))) + assert yt_perm_avoids_32(Perm((4, 6, 2, 0, 5, 3, 1))) + assert 1316 == sum(1 for p in Perm.of_length(7) if yt_perm_avoids_32(p)) + assert yt_perm_avoids_32(Perm((0, 14, 1, 13, 11, 8, 3, 5, 7, 9, 6, 10, 12, 4, 2))) + assert yt_perm_avoids_32(Perm((7, 5, 2, 4, 6, 8, 9, 10, 3, 11, 12, 1, 0, 13, 14))) + assert yt_perm_avoids_32(Perm((13, 9, 8, 0, 5, 1, 3, 4, 2, 6, 7, 10, 11, 12, 14))) + assert yt_perm_avoids_32(Perm((14, 10, 13, 12, 8, 7, 11, 5, 9, 6, 3, 4, 1, 0, 2))) + assert yt_perm_avoids_32(Perm((13, 0, 1, 10, 8, 7, 9, 11, 6, 5, 4, 12, 3, 14, 2))) + assert yt_perm_avoids_32(Perm((0, 14, 1, 3, 12, 4, 5, 6, 10, 11, 9, 8, 7, 13, 2))) + assert yt_perm_avoids_32(Perm((12, 9, 14, 13, 11, 6, 10, 8, 5, 2, 1, 7, 4, 0, 3))) + assert yt_perm_avoids_32(Perm((7, 14, 13, 6, 4, 12, 11, 3, 10, 9, 8, 1, 0, 5, 2))) + + +def test_av_231_and_mesh(): + assert av_231_and_mesh(Perm(())) + assert av_231_and_mesh(Perm((0,))) + assert av_231_and_mesh(Perm((0, 1))) + assert av_231_and_mesh(Perm((1, 0))) + assert av_231_and_mesh(Perm((0, 1, 2))) + assert av_231_and_mesh(Perm((0, 2, 1))) + assert av_231_and_mesh(Perm((1, 0, 2))) + assert not av_231_and_mesh(Perm((1, 2, 0))) + assert av_231_and_mesh(Perm((2, 0, 1))) + assert av_231_and_mesh(Perm((2, 1, 0))) + assert av_231_and_mesh(Perm((0, 1, 2, 3))) + assert av_231_and_mesh(Perm((0, 1, 3, 2))) + assert av_231_and_mesh(Perm((0, 5, 4, 3, 2, 1))) + assert av_231_and_mesh(Perm((5, 2, 1, 0, 4, 3))) + assert not av_231_and_mesh(Perm((3, 4, 6, 2, 5, 0, 1))) + assert not av_231_and_mesh(Perm((5, 0, 1, 6, 3, 4, 2))) + assert not av_231_and_mesh(Perm((2, 5, 0, 7, 3, 4, 6, 1))) + assert not av_231_and_mesh(Perm((5, 7, 4, 8, 1, 6, 0, 2, 3))) + assert not av_231_and_mesh(Perm((2, 5, 9, 1, 7, 3, 8, 0, 4, 6))) + assert not av_231_and_mesh(Perm((5, 6, 0, 3, 2, 1, 10, 8, 7, 4, 9))) + assert av_231_and_mesh(Perm((4, 1, 0, 3, 2, 5, 6))) + assert not av_231_and_mesh(Perm((2, 6, 4, 0, 1, 7, 3, 5))) + assert av_231_and_mesh(Perm((9, 8, 6, 5, 1, 0, 4, 3, 2, 7))) + assert av_231_and_mesh(Perm((0, 3, 2, 1, 9, 8, 7, 6, 5, 4))) + assert av_231_and_mesh(Perm((9, 1, 0, 2, 4, 3, 7, 5, 6, 8))) + assert av_231_and_mesh(Perm((8, 5, 1, 0, 2, 4, 3, 7, 6, 9))) + assert av_231_and_mesh(Perm((9, 1, 0, 8, 2, 5, 4, 3, 6, 7))) + assert av_231_and_mesh(Perm((9, 8, 7, 4, 0, 3, 1, 2, 5, 6))) + assert av_231_and_mesh(Perm((6, 1, 0, 2, 5, 3, 4, 9, 7, 8))) + assert av_231_and_mesh(Perm((8, 6, 0, 5, 3, 2, 1, 4, 7, 9))) + assert av_231_and_mesh(Perm((0, 1, 3, 2, 4, 6, 5, 9, 8, 7))) + assert 417 == sum(1 for p in Perm.of_length(7) if av_231_and_mesh(p)) + + +def test_hard_mesh(): + assert hard_mesh(Perm(())) + assert hard_mesh(Perm((0,))) + assert hard_mesh(Perm((0, 1))) + assert hard_mesh(Perm((1, 0))) + assert not hard_mesh(Perm((0, 1, 2))) + assert hard_mesh(Perm((0, 2, 1))) + assert hard_mesh(Perm((1, 0, 2))) + assert hard_mesh(Perm((1, 2, 0))) + assert hard_mesh(Perm((2, 0, 1))) + assert hard_mesh(Perm((2, 1, 0))) + assert not hard_mesh(Perm((0, 1, 2, 3))) + assert not hard_mesh(Perm((0, 1, 3, 2))) + assert not hard_mesh(Perm((1, 5, 0, 2, 3, 4))) + assert not hard_mesh(Perm((2, 4, 1, 5, 0, 3))) + assert hard_mesh(Perm((1, 3, 2, 5, 4, 6, 0))) + assert not hard_mesh(Perm((4, 3, 1, 6, 2, 0, 5))) + assert not hard_mesh(Perm((3, 4, 1, 5, 2, 6, 0, 7))) + assert not hard_mesh(Perm((4, 3, 2, 0, 1, 5, 7, 6, 8))) + assert not hard_mesh(Perm((7, 0, 5, 4, 6, 3, 2, 9, 1, 8))) + assert not hard_mesh(Perm((2, 6, 9, 1, 8, 0, 10, 5, 7, 3, 4))) + assert 692 == sum(1 for p in Perm.of_length(7) if hard_mesh(p)) diff --git a/tests/descriptors/test_basis.py b/tests/descriptors/test_basis.py deleted file mode 100644 index 43a3367e..00000000 --- a/tests/descriptors/test_basis.py +++ /dev/null @@ -1,80 +0,0 @@ -import pytest -from permuta import MeshPatt, Perm -from permuta.descriptors.basis import Basis, MeshBasis, detect_basis_cls - - -def test_detect_empty_basis_cls(): - assert detect_basis_cls(()) == Basis - assert detect_basis_cls([]) == Basis - - -def test_detect_basis_cls_with_None_elmnt(): - # TypeError: 'NoneType' object is not iterable - with pytest.raises(TypeError): - detect_basis_cls(None) - - # ValueError: A basis can only contain Perms and MeshPatts. - with pytest.raises(ValueError): - detect_basis_cls([None]) - - -def test_detect_basis_cls(): - p1 = Perm((0, 2, 1)) - p2 = Perm((2, 0, 1)) - perm_list = [p1, p2] - - assert detect_basis_cls(p1) == Basis - assert detect_basis_cls(p2) == Basis - assert detect_basis_cls(perm_list) == Basis - assert detect_basis_cls(Basis) == Basis - assert detect_basis_cls(Basis(perm_list)) == Basis - - -def test_detect_meshbasis_cls(): - p1 = Perm((0, 2, 1)) - p2 = Perm((2, 0, 1)) - shading = ((2, 0), (2, 1), (2, 2), (2, 3)) - mp1 = MeshPatt(p1, shading) - mp2 = MeshPatt(p2, shading) - meshpatt_list = [mp1, mp2] - mixed_patt_list = [p1, mp2] - - assert detect_basis_cls(mp1) == MeshBasis - assert detect_basis_cls(mp2) == MeshBasis - assert detect_basis_cls(meshpatt_list) == MeshBasis - assert detect_basis_cls(mixed_patt_list) == MeshBasis - assert detect_basis_cls(MeshBasis) == MeshBasis - assert detect_basis_cls(MeshBasis(meshpatt_list)) == MeshBasis - - -def test_meshbasis_of_perms(): - p1 = Perm((0, 2, 1)) - p2 = Perm((2, 0, 1)) - assert MeshBasis([p1, p2]) == MeshBasis([MeshPatt(p1, []), MeshPatt(p2, [])]) - - shading = ((2, 0), (2, 1), (2, 2), (2, 3)) - mp1 = MeshPatt(p1, shading) - mp2 = MeshPatt(p2, shading) - assert MeshBasis([p1, p2]) == MeshBasis([p1, p2, mp1, mp2]) - - # TypeError: Elements of a basis should all be of type(s) Perm - with pytest.raises(TypeError): - Basis([mp1, mp2]) - - for elmnt in MeshBasis([p1, p2]): - assert isinstance(elmnt, MeshPatt) - - -def test_basis_with_empty_perm(): - basis = Basis([Perm(), Perm((0,))]) - assert basis == Basis(Perm()) - - -def test_meshbasis_with_empty_meshpatt(): - meshbasis = MeshBasis([MeshPatt(), MeshPatt(Perm((0,)), ())]) - assert meshbasis == MeshBasis(MeshPatt()) - - -def test_meshbasis_with_empty_perm(): - meshbasis = MeshBasis([MeshPatt(), Perm()]) - assert meshbasis == MeshBasis(MeshPatt()) diff --git a/tests/test_enumeration_strategies.py b/tests/enumeration_strategies/test_enumeration_strategies.py similarity index 91% rename from tests/test_enumeration_strategies.py rename to tests/enumeration_strategies/test_enumeration_strategies.py index 7b3c969f..04150c10 100644 --- a/tests/test_enumeration_strategies.py +++ b/tests/enumeration_strategies/test_enumeration_strategies.py @@ -1,11 +1,11 @@ from permuta import Perm -from permuta.descriptors import Basis from permuta.enumeration_strategies import all_enumeration_strategies, find_strategies from permuta.enumeration_strategies.core_strategies import ( RdCdCoreStrategy, RuCuCoreStrategy, ) from permuta.enumeration_strategies.insertion_encodable import InsertionEncodingStrategy +from permuta.perm_sets.basis import Basis ru = Perm((1, 2, 0, 3)) cu = Perm((2, 0, 1, 3)) @@ -15,7 +15,7 @@ def test_init_strategy(): b1 = [Perm((0, 1, 2))] - b2 = Basis([Perm((0, 1, 2, 3)), Perm((2, 0, 1))]) + b2 = Basis(*[Perm((0, 1, 2, 3)), Perm((2, 0, 1))]) for Strat in all_enumeration_strategies: Strat(b1).applies() Strat(b2).applies() @@ -44,7 +44,7 @@ def test_RdCd(): def test_find_strategies(): b1 = [Perm((0, 1, 2, 3, 4))] - b2 = Basis([Perm((0, 1, 2, 3)), Perm((2, 0, 1))]) + b2 = Basis(*[Perm((0, 1, 2, 3)), Perm((2, 0, 1))]) assert len(find_strategies(b1, long_runnning=True)) == 0 assert len(find_strategies(b1, long_runnning=False)) == 0 assert len(find_strategies(b2)) > 0 diff --git a/tests/misc/test_catalan.py b/tests/misc/test_catalan.py deleted file mode 100644 index f382effc..00000000 --- a/tests/misc/test_catalan.py +++ /dev/null @@ -1,40 +0,0 @@ -from permuta.misc import catalan - -CATALAN_NUMBERS = [ - 1, - 1, - 2, - 5, - 14, - 42, - 132, - 429, - 1430, - 4862, - 16796, - 58786, - 208012, - 742900, - 2674440, - 9694845, - 35357670, - 129644790, - 477638700, - 1767263190, - 6564120420, - 24466267020, - 91482563640, - 343059613650, - 1289904147324, - 4861946401452, - 18367353072152, - 69533550916004, - 263747951750360, - 1002242216651368, - 3814986502092304, -] # Such funny indentation - - -def test_first_few_catalan(): - for index, number in enumerate(CATALAN_NUMBERS): - assert catalan(index) == number diff --git a/tests/misc/test_counting.py b/tests/misc/test_counting.py deleted file mode 100644 index 940f4d6a..00000000 --- a/tests/misc/test_counting.py +++ /dev/null @@ -1,20 +0,0 @@ -from permuta.misc import binomial, factorial - - -def test_factorial(): - assert factorial(0) == 1 - assert factorial(1) == 1 - assert factorial(2) == 2 - assert factorial(3) == 6 - assert factorial(9) == 362880 - assert factorial(10) == 3628800 - - -def test_binomial(): - assert binomial(0, 0) == 1 - assert binomial(0, 1) == 0 - assert binomial(120, 1231) == 0 - assert binomial(10, 3) == 120 - assert binomial(10, 1) == 10 - assert binomial(1243, 10) == 2339835883576802918726133 - assert binomial(1243, 20) == 27325212682042866855639367404897873973276230 diff --git a/tests/misc/test_iterable_floor_and_ceiling.py b/tests/misc/test_iterable_floor_and_ceiling.py deleted file mode 100644 index 85bc1d73..00000000 --- a/tests/misc/test_iterable_floor_and_ceiling.py +++ /dev/null @@ -1,56 +0,0 @@ -from permuta.misc import left_floor_and_ceiling, right_floor_and_ceiling - - -def test_left_floor_and_ceiling(): - iterable = [4, 5, 1, 2, 3, 6] - expected = [ - (None, None), # 4 - (0, None), # 5 - (None, 0), # 1 - (2, 0), # 2 - (3, 0), # 3 - (1, None), # 6 - ] - index = 0 - for fac in left_floor_and_ceiling(iterable): - assert fac == expected[index] - index += 1 - - iterable = [4, 1, 2, 5, 3] - expected = [(-1, 5), (-1, 0), (1, 0), (0, 5), (2, 0)] # 4 # 1 # 2 # 5 # 3 - index = 0 - for fac in left_floor_and_ceiling(iterable, default_floor=-1, default_ceiling=5): - assert fac == expected[index] - index += 1 - - iterable = [1, 2, 3] - expected = [(None, None), (0, None), (1, None)] # 1 # 2 # 3 - index = 0 - for fac in left_floor_and_ceiling(iterable): - assert fac == expected[index] - index += 1 - - iterable = [3, 2, 1] - expected = [(None, None), (None, 0), (None, 1)] # 3 # 2 # 1 - index = 0 - for fac in left_floor_and_ceiling(iterable): - assert fac == expected[index] - index += 1 - - -def test_right_floor_and_ceiling(): - iterable = [4, 5, 1, 2, 3, 6] - expected = [ - (None, "on"), # 4 - (0, "on"), # 5 - (None, 0), # 1 - (2, 0), # 2 - (3, 0), # 3 - (1, "on"), # 6 - ] - iterable = reversed(iterable) - expected = list(reversed(expected)) - index = 0 - for fac in right_floor_and_ceiling(iterable, default_ceiling="on"): - assert fac == expected[index] - index += 1 diff --git a/tests/misc/test_ordered_set_partitions.py b/tests/misc/test_ordered_set_partitions.py deleted file mode 100644 index 18624554..00000000 --- a/tests/misc/test_ordered_set_partitions.py +++ /dev/null @@ -1,76 +0,0 @@ -import pytest -from permuta.misc import ordered_set_partitions - - -def test_ordered_set_partitions(): - it = ordered_set_partitions([1, 2, 3], [2, 1]) - assert [[1, 2], [3]] == next(it) - assert [[1, 3], [2]] == next(it) - assert [[2, 3], [1]] == next(it) - with pytest.raises(StopIteration): - next(it) - - it = ordered_set_partitions([1, 2, 3], [2, 1]) - assert [[1, 2], [3]] == next(it) - assert [[1, 3], [2]] == next(it) - assert [[2, 3], [1]] == next(it) - with pytest.raises(StopIteration): - next(it) - - lst = list(ordered_set_partitions([2, 3, 4, 5, 6], [2, 1, 2])) - assert [ - [[2, 3], [4], [5, 6]], - [[2, 3], [5], [4, 6]], - [[2, 3], [6], [4, 5]], - [[2, 4], [3], [5, 6]], - [[2, 4], [5], [3, 6]], - [[2, 4], [6], [3, 5]], - [[2, 5], [3], [4, 6]], - [[2, 5], [4], [3, 6]], - [[2, 5], [6], [3, 4]], - [[2, 6], [3], [4, 5]], - [[2, 6], [4], [3, 5]], - [[2, 6], [5], [3, 4]], - [[3, 4], [2], [5, 6]], - [[3, 4], [5], [2, 6]], - [[3, 4], [6], [2, 5]], - [[3, 5], [2], [4, 6]], - [[3, 5], [4], [2, 6]], - [[3, 5], [6], [2, 4]], - [[3, 6], [2], [4, 5]], - [[3, 6], [4], [2, 5]], - [[3, 6], [5], [2, 4]], - [[4, 5], [2], [3, 6]], - [[4, 5], [3], [2, 6]], - [[4, 5], [6], [2, 3]], - [[4, 6], [2], [3, 5]], - [[4, 6], [3], [2, 5]], - [[4, 6], [5], [2, 3]], - [[5, 6], [2], [3, 4]], - [[5, 6], [3], [2, 4]], - [[5, 6], [4], [2, 3]], - ] == sorted(lst) - - lst = list(ordered_set_partitions([2, 3, 4, 5, 6], [3, 1, 1])) - assert [ - [[2, 3, 4], [5], [6]], - [[2, 3, 4], [6], [5]], - [[2, 3, 5], [4], [6]], - [[2, 3, 5], [6], [4]], - [[2, 3, 6], [4], [5]], - [[2, 3, 6], [5], [4]], - [[2, 4, 5], [3], [6]], - [[2, 4, 5], [6], [3]], - [[2, 4, 6], [3], [5]], - [[2, 4, 6], [5], [3]], - [[2, 5, 6], [3], [4]], - [[2, 5, 6], [4], [3]], - [[3, 4, 5], [2], [6]], - [[3, 4, 5], [6], [2]], - [[3, 4, 6], [2], [5]], - [[3, 4, 6], [5], [2]], - [[3, 5, 6], [2], [4]], - [[3, 5, 6], [4], [2]], - [[4, 5, 6], [2], [3]], - [[4, 5, 6], [3], [2]], - ] == sorted(lst) diff --git a/tests/misc/test_ranges.py b/tests/misc/test_ranges.py deleted file mode 100644 index 38e2b866..00000000 --- a/tests/misc/test_ranges.py +++ /dev/null @@ -1,19 +0,0 @@ -from permuta.misc import cyclic_range, modulo_range - - -def test_cyclic_range(): - result = list(cyclic_range(5, 30, -20)) - expected = list(range(5, 30)) - expected.extend(range(-20, 5)) - assert result == expected - - -def test_modulo_range(): - result = list(modulo_range(7, 30)) - expected = list(range(7, 30)) - expected.extend(range(7)) - assert result == expected - - result = list(modulo_range(0, 30)) - expected = list(range(0, 30)) - assert result == expected diff --git a/tests/misc/test_union_find.py b/tests/misc/test_union_find.py new file mode 100644 index 00000000..85724308 --- /dev/null +++ b/tests/misc/test_union_find.py @@ -0,0 +1,61 @@ +import random +from itertools import combinations + +from permuta.misc.union_find import UnionFind + + +def test_union_find_init(): + uf = UnionFind(10) + assert sum(x == -1 for x in uf._parent) == 10 + + +def test_union_find_1(): + uf = UnionFind(10) + assert uf.find(1) != uf.find(3) + assert uf.unite(1, 8) + assert uf.unite(3, 8) + assert uf.find(1) == uf.find(3) + assert uf.size(1) == uf.size(3) == uf.size(8) == 3 + assert all(uf.size(i) == 1 for i in range(10) if i not in (1, 3, 8)) + + +def test_union_find_2(): + uf = UnionFind(100) + for i in range(1, 100): + assert uf.unite(0, i) + for (a, b) in combinations(range(100), 2): + assert uf.find(a) == uf.find(b) + assert uf.size(a) == 100 + assert uf.size(b) == 100 + + +def test_union_find_3(): + uf = UnionFind(4) + assert uf.find(0) == uf.find(0) + assert not uf.unite(0, 0) + assert uf.unite(1, 0) + assert not uf.unite(0, 1) + assert uf.unite(1, 2) + assert not uf.unite(1, 2) + assert not uf.unite(0, 2) + assert uf.find(0) != uf.find(3) + + +def test_union_find_4(): + n = 5000 + d = {i: {i} for i in range(n)} + uf = UnionFind(n) + + for i in range(2 * n): + a, b = random.randint(0, n - 1), random.randint(0, n - 1) + if random.randint(0, 1): + combined = d[a].union(d[b]) + for x in combined: + d[x] = combined + uf.unite(a, b) + assert uf.size(a) == len(combined) == uf.size(b) + else: + if a in d[b]: + assert uf.find(a) == uf.find(b) + else: + assert uf.find(a) != uf.find(b) diff --git a/tests/patterns/test_bivincular.py b/tests/patterns/test_bivincular.py new file mode 100644 index 00000000..f7c5757b --- /dev/null +++ b/tests/patterns/test_bivincular.py @@ -0,0 +1,1821 @@ +from permuta import BivincularPatt, CovincularPatt, MeshPatt, Perm, VincularPatt + + +def test_init_bivpatt(): + p = BivincularPatt(Perm((0, 1, 2)), (1, 2), (0, 1)) + assert p.shading == { + (1, 0), + (1, 1), + (1, 2), + (1, 3), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (0, 0), + (3, 0), + (0, 1), + (3, 1), + } + + +def test_init_covpatt(): + p = CovincularPatt(Perm((0, 1, 2)), (0, 1)) + assert p.shading == { + (1, 0), + (1, 1), + (2, 0), + (2, 1), + (0, 0), + (3, 0), + (0, 1), + (3, 1), + } + + +def test_init_vinpatt(): + p = VincularPatt(Perm((0, 1, 2)), (1, 2)) + assert p.shading == { + (1, 0), + (1, 1), + (1, 2), + (1, 3), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + } + + +def test_perm_avoids_bv1(): + bp1 = BivincularPatt(Perm((0, 1, 2)), (2,), ()) + vp = VincularPatt(Perm((0, 1, 2)), (2,)) + bp2 = BivincularPatt(Perm((2, 0, 1)), (), (2,)) + cp = CovincularPatt(Perm((2, 0, 1)), (2,)) + + assert Perm().avoids(bp1, bp2) + assert Perm().avoids(vp, cp) + for i in range(1, 7): + assert 2 ** (i - 1) == sum(1 for p in Perm.of_length(i) if p.avoids(bp1, bp2)) + assert 2 ** (i - 1) == sum(1 for p in Perm.of_length(i) if p.avoids(vp, cp)) + + +def test_perm_avoids_bv2(): + class_pat = Perm.identity(3) + bp = BivincularPatt(Perm((2, 0, 1)), (), (1,)) + cp = CovincularPatt(Perm((2, 0, 1)), (1,)) + assert Perm().avoids(class_pat, bp) + assert Perm().avoids(class_pat, cp) + for i in range(1, 7): + assert 2 ** (i - 1) == sum( + 1 for p in Perm.of_length(i) if p.avoids(class_pat, bp) + ) + assert 2 ** (i - 1) == sum( + 1 for p in Perm.of_length(i) if p.avoids(class_pat, cp) + ) + + +def test_perm_avoids_bv3(): + motzkin = [1, 1, 2, 4, 9, 21, 51, 127, 323, 835, 2188, 5798, 15511, 41835] + class_pat = Perm((0, 2, 1)) + bp = BivincularPatt(Perm.identity(3), (), (2,)) + cp = CovincularPatt(Perm.identity(3), (2,)) + for plen in range(8): + assert ( + sum(1 for perm in Perm.of_length(plen) if perm.avoids(class_pat, bp)) + == motzkin[plen] + ) + assert ( + sum(1 for perm in Perm.of_length(plen) if perm.avoids(class_pat, cp)) + == motzkin[plen] + ) + + +def test_avoids_bv(): + assert BivincularPatt( + Perm((0, 1, 12, 14, 9, 10, 13, 5, 2, 7, 8, 4, 3, 6, 11)), + [9, 13, 14], + [1, 2, 3, 5, 7, 9, 14, 15], + ).avoids(BivincularPatt(Perm((8, 9, 1, 3, 7, 6, 0, 2, 5, 4)), [4, 8], [9])) + assert BivincularPatt( + Perm((0, 1, 12, 14, 9, 10, 13, 5, 2, 7, 8, 4, 3, 6, 11)), + [9, 13, 14], + [1, 2, 3, 5, 7, 9, 14, 15], + ).avoids( + CovincularPatt( + Perm((6, 10, 14, 0, 11, 7, 1, 4, 2, 3, 13, 8, 12, 5, 9)), + [0, 1, 3, 6, 8, 9, 11, 12, 14, 15], + ) + ) + assert BivincularPatt( + Perm((0, 1, 12, 14, 9, 10, 13, 5, 2, 7, 8, 4, 3, 6, 11)), + [9, 13, 14], + [1, 2, 3, 5, 7, 9, 14, 15], + ).avoids(VincularPatt(Perm((4, 1, 3, 2, 0, 6, 5, 7)), [0, 3, 6])) + assert BivincularPatt( + Perm((0, 1, 12, 14, 9, 10, 13, 5, 2, 7, 8, 4, 3, 6, 11)), + [9, 13, 14], + [1, 2, 3, 5, 7, 9, 14, 15], + ).avoids(CovincularPatt(Perm((2, 3, 5, 9, 8, 7, 1, 6, 4, 0)), [2, 5, 7, 8])) + assert BivincularPatt( + Perm((0, 1, 12, 14, 9, 10, 13, 5, 2, 7, 8, 4, 3, 6, 11)), + [9, 13, 14], + [1, 2, 3, 5, 7, 9, 14, 15], + ).avoids( + VincularPatt( + Perm((13, 0, 10, 2, 11, 7, 14, 3, 1, 6, 8, 12, 4, 5, 9)), + [0, 5, 6, 8, 9, 10, 12, 15], + ) + ) + assert CovincularPatt( + Perm((6, 10, 14, 0, 11, 7, 1, 4, 2, 3, 13, 8, 12, 5, 9)), + [0, 1, 3, 6, 8, 9, 11, 12, 14, 15], + ).avoids( + BivincularPatt( + Perm((0, 1, 12, 14, 9, 10, 13, 5, 2, 7, 8, 4, 3, 6, 11)), + [9, 13, 14], + [1, 2, 3, 5, 7, 9, 14, 15], + ) + ) + assert CovincularPatt( + Perm((6, 2, 10, 1, 4, 7, 11, 9, 5, 8, 3, 0)), [0, 1, 3, 5, 6, 8, 10, 11, 12] + ).avoids( + BivincularPatt( + Perm((9, 1, 0, 12, 4, 8, 2, 6, 11, 7, 5, 3, 10)), + [0, 2, 6, 7, 8, 11, 13], + [0, 2, 3, 4, 5, 6, 7, 9, 10, 13], + ) + ) + + +def test_get_perm(): + p = Perm((1, 0)) + assert BivincularPatt(p, (), ()).get_perm() == p + + +def test_complement(): + assert BivincularPatt( + Perm((1, 2, 0, 3)), [0, 1, 3, 4], [4] + ).complement() == MeshPatt( + Perm((2, 1, 3, 0)), + [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 0), + (1, 1), + (1, 2), + (1, 3), + (1, 4), + (2, 0), + (3, 0), + (3, 1), + (3, 2), + (3, 3), + (3, 4), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + ], + ) + assert CovincularPatt(Perm((0, 1, 2)), [1, 2]).complement() == MeshPatt( + Perm((2, 1, 0)), + [(0, 1), (0, 2), (1, 1), (1, 2), (2, 1), (2, 2), (3, 1), (3, 2)], + ) + assert VincularPatt(Perm((0, 2, 1)), [0]).complement() == MeshPatt( + Perm((2, 0, 1)), [(0, 0), (0, 1), (0, 2), (0, 3)] + ) + + +def test_reverse(): + assert BivincularPatt(Perm((0, 1, 2)), [2], []).reverse() == MeshPatt( + Perm((2, 1, 0)), [(1, 0), (1, 1), (1, 2), (1, 3)] + ) + assert CovincularPatt(Perm((4, 2, 0, 1, 3)), [0, 1, 2, 4, 5]).reverse() == MeshPatt( + Perm((3, 1, 0, 2, 4)), + [ + (0, 0), + (0, 1), + (0, 2), + (0, 4), + (0, 5), + (1, 0), + (1, 1), + (1, 2), + (1, 4), + (1, 5), + (2, 0), + (2, 1), + (2, 2), + (2, 4), + (2, 5), + (3, 0), + (3, 1), + (3, 2), + (3, 4), + (3, 5), + (4, 0), + (4, 1), + (4, 2), + (4, 4), + (4, 5), + (5, 0), + (5, 1), + (5, 2), + (5, 4), + (5, 5), + ], + ) + assert VincularPatt(Perm((0, 3, 4, 1, 2)), [0, 1]).reverse() == MeshPatt( + Perm((2, 1, 4, 3, 0)), + [ + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + (4, 5), + (5, 0), + (5, 1), + (5, 2), + (5, 3), + (5, 4), + (5, 5), + ], + ) + + +def test_inverse(): + assert BivincularPatt(Perm((1, 0, 2)), [3], [1, 3]).inverse() == MeshPatt( + Perm((1, 0, 2)), + [ + (0, 3), + (1, 0), + (1, 1), + (1, 2), + (1, 3), + (2, 3), + (3, 0), + (3, 1), + (3, 2), + (3, 3), + ], + ) + assert CovincularPatt(Perm((3, 1, 0, 2)), [1, 2, 3]).inverse() == MeshPatt( + Perm((2, 1, 3, 0)), + [ + (1, 0), + (1, 1), + (1, 2), + (1, 3), + (1, 4), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (2, 4), + (3, 0), + (3, 1), + (3, 2), + (3, 3), + (3, 4), + ], + ) + assert VincularPatt(Perm((0, 4, 3, 2, 1)), [0, 1, 4, 5]).inverse() == MeshPatt( + Perm((0, 4, 3, 2, 1)), + [ + (0, 0), + (0, 1), + (0, 4), + (0, 5), + (1, 0), + (1, 1), + (1, 4), + (1, 5), + (2, 0), + (2, 1), + (2, 4), + (2, 5), + (3, 0), + (3, 1), + (3, 4), + (3, 5), + (4, 0), + (4, 1), + (4, 4), + (4, 5), + (5, 0), + (5, 1), + (5, 4), + (5, 5), + ], + ) + + +def test_rotate(): + assert VincularPatt(Perm((0, 1, 2)), (0,)).rotate(2) == VincularPatt( + Perm((0, 1, 2)).rotate(2), (3,) + ) + assert VincularPatt(Perm((0, 1, 2)), (0,)).rotate(1) == CovincularPatt( + Perm((0, 1, 2)).rotate(1), (3,) + ) + assert VincularPatt(Perm((0, 1, 2)), (0,)).rotate(-1) == CovincularPatt( + Perm((0, 1, 2)).rotate(1), (0,) + ) + assert BivincularPatt(Perm((3, 2, 1, 0)), [0, 1, 3, 4], [0, 2, 3, 4]).rotate( + 0 + ) == BivincularPatt(Perm((3, 2, 1, 0)), [0, 1, 3, 4], [0, 2, 3, 4]) + assert BivincularPatt(Perm((3, 2, 1, 0)), [0, 1, 3, 4], [0, 2, 3, 4]).rotate( + 1 + ) == MeshPatt( + Perm((0, 1, 2, 3)), + [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 0), + (1, 1), + (1, 3), + (1, 4), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (2, 4), + (3, 0), + (3, 1), + (3, 2), + (3, 3), + (3, 4), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + ], + ) + assert BivincularPatt(Perm((3, 2, 1, 0)), [0, 1, 3, 4], [0, 2, 3, 4]).rotate( + 2 + ) == MeshPatt( + Perm((3, 2, 1, 0)), + [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 0), + (1, 1), + (1, 2), + (1, 3), + (1, 4), + (2, 0), + (2, 1), + (2, 2), + (2, 4), + (3, 0), + (3, 1), + (3, 2), + (3, 3), + (3, 4), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + ], + ) + assert BivincularPatt(Perm((3, 2, 1, 0)), [0, 1, 3, 4], [0, 2, 3, 4]).rotate( + 3 + ) == MeshPatt( + Perm((0, 1, 2, 3)), + [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 0), + (1, 1), + (1, 2), + (1, 3), + (1, 4), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (2, 4), + (3, 0), + (3, 1), + (3, 3), + (3, 4), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + ], + ) + assert CovincularPatt(Perm((2, 1, 3, 0)), [0, 1]).rotate(0) == CovincularPatt( + Perm((2, 1, 3, 0)), [0, 1] + ) + assert CovincularPatt(Perm((2, 1, 3, 0)), [0, 1]).rotate(1) == MeshPatt( + Perm((0, 2, 3, 1)), + [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 0), + (1, 1), + (1, 2), + (1, 3), + (1, 4), + ], + ) + assert CovincularPatt(Perm((2, 1, 3, 0)), [0, 1]).rotate(2) == MeshPatt( + Perm((3, 0, 2, 1)), + [ + (0, 3), + (0, 4), + (1, 3), + (1, 4), + (2, 3), + (2, 4), + (3, 3), + (3, 4), + (4, 3), + (4, 4), + ], + ) + assert CovincularPatt(Perm((2, 1, 3, 0)), [0, 1]).rotate(3) == MeshPatt( + Perm((2, 0, 1, 3)), + [ + (3, 0), + (3, 1), + (3, 2), + (3, 3), + (3, 4), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + ], + ) + assert VincularPatt(Perm((0, 1, 2)), [0, 1, 3]).rotate(0) == VincularPatt( + Perm((0, 1, 2)), [0, 1, 3] + ) + assert VincularPatt(Perm((0, 1, 2)), [0, 1, 3]).rotate(1) == MeshPatt( + Perm((2, 1, 0)), + [ + (0, 0), + (0, 2), + (0, 3), + (1, 0), + (1, 2), + (1, 3), + (2, 0), + (2, 2), + (2, 3), + (3, 0), + (3, 2), + (3, 3), + ], + ) + assert VincularPatt(Perm((0, 1, 2)), [0, 1, 3]).rotate(2) == MeshPatt( + Perm((0, 1, 2)), + [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (3, 0), + (3, 1), + (3, 2), + (3, 3), + ], + ) + assert VincularPatt(Perm((0, 1, 2)), [0, 1, 3]).rotate(3) == MeshPatt( + Perm((2, 1, 0)), + [ + (0, 0), + (0, 1), + (0, 3), + (1, 0), + (1, 1), + (1, 3), + (2, 0), + (2, 1), + (2, 3), + (3, 0), + (3, 1), + (3, 3), + ], + ) + + +def test_contains(): + assert VincularPatt(Perm((5, 2, 0, 6, 1, 3, 4)), [1, 3, 4, 5, 7]).contains( + CovincularPatt(Perm((4, 0, 1, 2, 3)), []), + VincularPatt(Perm((5, 2, 0, 6, 1, 3, 4)), [1, 3, 4, 5, 7]), + VincularPatt(Perm((2, 1, 0, 3)), []), + ) + assert BivincularPatt( + Perm((4, 5, 1, 3, 2, 0)), [0, 2, 4, 5], [0, 1, 2, 4, 5, 6] + ).contains( + CovincularPatt(Perm((2, 1, 0)), [2]), + VincularPatt(Perm((2, 3, 0, 1)), []), + BivincularPatt(Perm((4, 5, 1, 3, 2, 0)), [0, 2, 4, 5], [0, 1, 2, 4, 5, 6]), + ) + assert BivincularPatt(Perm((1, 3, 0, 4, 5, 6, 2)), [3, 4, 6], [1, 4, 5]).contains( + BivincularPatt(Perm((0, 2, 1)), [2], []), + BivincularPatt(Perm((1, 3, 0, 4, 5, 6, 2)), [3, 4, 6], [1, 4, 5]), + CovincularPatt(Perm((1, 2, 0, 3)), [1]), + ) + assert VincularPatt(Perm((3, 5, 1, 6, 2, 4, 0)), [0, 1, 2, 4, 5, 7]).contains( + BivincularPatt(Perm((0, 2, 1)), [2], []), + VincularPatt(Perm((3, 5, 1, 6, 2, 4, 0)), [0, 1, 2, 4, 5, 7]), + VincularPatt(Perm((3, 1, 2, 0)), []), + ) + assert BivincularPatt(Perm((3, 2, 0, 1)), [0], []).contains( + BivincularPatt(Perm((3, 2, 0, 1)), [0], []), + VincularPatt(Perm((3, 2, 0, 1)), []), + VincularPatt(Perm((2, 1, 0)), []), + ) + assert VincularPatt(Perm((4, 0, 2, 6, 3, 1, 5)), [0, 1, 2, 3, 5, 6, 7]).contains( + BivincularPatt(Perm((1, 0, 2)), [0, 1, 3], []), + VincularPatt(Perm((4, 0, 2, 6, 3, 1, 5)), [0, 1, 2, 3, 5, 6, 7]), + VincularPatt(Perm((1, 0, 2)), [3]), + ) + assert CovincularPatt(Perm((0,)), []).contains( + BivincularPatt(Perm(()), [], []), + VincularPatt(Perm(()), []), + CovincularPatt(Perm((0,)), []), + ) + assert BivincularPatt( + Perm((6, 8, 1, 4, 2, 0, 5, 7, 3)), + [2, 3, 4, 6, 7, 8], + [1, 2, 3, 4, 5, 6, 7, 8, 9], + ).contains( + BivincularPatt(Perm(()), [], []), + BivincularPatt( + Perm((6, 8, 1, 4, 2, 0, 5, 7, 3)), + [2, 3, 4, 6, 7, 8], + [1, 2, 3, 4, 5, 6, 7, 8, 9], + ), + CovincularPatt(Perm((0,)), []), + ) + assert BivincularPatt( + Perm((6, 8, 1, 4, 2, 0, 5, 7, 3)), + [2, 3, 4, 6, 7, 8], + [1, 2, 3, 4, 5, 6, 7, 8, 9], + ).contains( + BivincularPatt(Perm(()), [], []), + BivincularPatt( + Perm((6, 8, 1, 4, 2, 0, 5, 7, 3)), + [2, 3, 4, 6, 7, 8], + [1, 2, 3, 4, 5, 6, 7, 8, 9], + ), + VincularPatt(Perm(()), []), + ) + assert BivincularPatt( + Perm((6, 8, 1, 4, 2, 0, 5, 7, 3)), + [2, 3, 4, 6, 7, 8], + [1, 2, 3, 4, 5, 6, 7, 8, 9], + ).contains( + BivincularPatt(Perm(()), [], []), + VincularPatt(Perm(()), []), + CovincularPatt(Perm((0,)), []), + ) + assert BivincularPatt( + Perm((6, 8, 1, 4, 2, 0, 5, 7, 3)), + [2, 3, 4, 6, 7, 8], + [1, 2, 3, 4, 5, 6, 7, 8, 9], + ).contains( + CovincularPatt(Perm((0,)), []), + BivincularPatt( + Perm((6, 8, 1, 4, 2, 0, 5, 7, 3)), + [2, 3, 4, 6, 7, 8], + [1, 2, 3, 4, 5, 6, 7, 8, 9], + ), + VincularPatt(Perm(()), []), + ) + assert VincularPatt( + Perm((9, 0, 2, 14, 5, 4, 10, 12, 3, 13, 8, 11, 7, 1, 6)), + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15], + ).contains(VincularPatt(Perm((3, 0, 7, 4, 5, 6, 1, 2)), [8])) + + +def test_occuerrences_in_mesh(): + assert list( + BivincularPatt(Perm((0, 1, 2)), (2,), ()).occurrences_in(Perm((0, 4, 3, 1, 2))) + ) == [(0, 3, 4)] + assert list( + VincularPatt(Perm((0, 1, 2)), (2,)).occurrences_in(Perm((0, 4, 3, 1, 2))) + ) == [(0, 3, 4)] + assert list( + BivincularPatt(Perm((2, 0, 1)), (), (2,)).occurrences_in(Perm((1, 2, 4, 0, 3))) + ) == [(2, 3, 4)] + assert list( + CovincularPatt(Perm((2, 0, 1)), (2,)).occurrences_in(Perm((1, 2, 4, 0, 3))) + ) == [(2, 3, 4)] + assert ( + list( + BivincularPatt( + Perm((6, 11, 5, 0, 12, 7, 2, 1, 4, 3, 9, 8, 10)), + [1, 7, 8, 12, 13], + [1, 3, 4, 5, 6, 7, 8, 11, 12, 13], + ).occurrences_in( + BivincularPatt( + Perm((1, 3, 7, 6, 5, 0, 8, 2, 4, 9)), + [0, 3, 5, 6, 7, 9, 10], + [2, 3, 6, 7], + ) + ) + ) + == [] + ) + assert ( + list( + BivincularPatt( + Perm((6, 11, 5, 0, 12, 7, 2, 1, 4, 3, 9, 8, 10)), + [1, 7, 8, 12, 13], + [1, 3, 4, 5, 6, 7, 8, 11, 12, 13], + ).occurrences_in( + CovincularPatt( + Perm((1, 6, 10, 3, 11, 0, 2, 7, 9, 8, 4, 5)), [7, 8, 11, 12] + ) + ) + ) + == [] + ) + assert ( + list( + BivincularPatt( + Perm((6, 11, 5, 0, 12, 7, 2, 1, 4, 3, 9, 8, 10)), + [1, 7, 8, 12, 13], + [1, 3, 4, 5, 6, 7, 8, 11, 12, 13], + ).occurrences_in( + VincularPatt(Perm((8, 4, 2, 5, 0, 6, 3, 9, 1, 7)), [1, 2, 3, 8]) + ) + ) + == [] + ) + assert ( + list( + BivincularPatt( + Perm((6, 11, 5, 0, 12, 7, 2, 1, 4, 3, 9, 8, 10)), + [1, 7, 8, 12, 13], + [1, 3, 4, 5, 6, 7, 8, 11, 12, 13], + ).occurrences_in( + VincularPatt( + Perm((7, 11, 4, 2, 0, 9, 10, 3, 14, 6, 1, 5, 12, 13, 8)), + [0, 1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], + ) + ) + ) + == [] + ) + assert ( + list( + BivincularPatt( + Perm((6, 11, 5, 0, 12, 7, 2, 1, 4, 3, 9, 8, 10)), + [1, 7, 8, 12, 13], + [1, 3, 4, 5, 6, 7, 8, 11, 12, 13], + ).occurrences_in( + CovincularPatt( + Perm((2, 0, 3, 5, 1, 6, 7, 8, 4)), [1, 2, 3, 4, 5, 7, 8, 9] + ) + ) + ) + == [] + ) + assert ( + list( + VincularPatt( + Perm((7, 11, 4, 2, 0, 9, 10, 3, 14, 6, 1, 5, 12, 13, 8)), + [0, 1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], + ).occurrences_in( + BivincularPatt( + Perm((1, 3, 7, 6, 5, 0, 8, 2, 4, 9)), + [0, 3, 5, 6, 7, 9, 10], + [2, 3, 6, 7], + ) + ) + ) + == [] + ) + assert ( + list( + VincularPatt( + Perm((7, 11, 4, 2, 0, 9, 10, 3, 14, 6, 1, 5, 12, 13, 8)), + [0, 1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], + ).occurrences_in( + CovincularPatt( + Perm((1, 6, 10, 3, 11, 0, 2, 7, 9, 8, 4, 5)), [7, 8, 11, 12] + ) + ) + ) + == [] + ) + assert ( + list( + VincularPatt( + Perm((7, 11, 4, 2, 0, 9, 10, 3, 14, 6, 1, 5, 12, 13, 8)), + [0, 1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], + ).occurrences_in( + VincularPatt(Perm((8, 4, 2, 5, 0, 6, 3, 9, 1, 7)), [1, 2, 3, 8]) + ) + ) + == [] + ) + assert ( + list( + VincularPatt( + Perm((7, 11, 4, 2, 0, 9, 10, 3, 14, 6, 1, 5, 12, 13, 8)), + [0, 1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], + ).occurrences_in( + BivincularPatt( + Perm((6, 11, 5, 0, 12, 7, 2, 1, 4, 3, 9, 8, 10)), + [1, 7, 8, 12, 13], + [1, 3, 4, 5, 6, 7, 8, 11, 12, 13], + ) + ) + ) + == [] + ) + assert ( + list( + VincularPatt( + Perm((7, 11, 4, 2, 0, 9, 10, 3, 14, 6, 1, 5, 12, 13, 8)), + [0, 1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], + ).occurrences_in( + CovincularPatt( + Perm((2, 0, 3, 5, 1, 6, 7, 8, 4)), [1, 2, 3, 4, 5, 7, 8, 9] + ) + ) + ) + == [] + ) + assert ( + list( + BivincularPatt( + Perm((1, 3, 7, 6, 5, 0, 8, 2, 4, 9)), + [0, 3, 5, 6, 7, 9, 10], + [2, 3, 6, 7], + ).occurrences_in( + VincularPatt( + Perm((7, 11, 4, 2, 0, 9, 10, 3, 14, 6, 1, 5, 12, 13, 8)), + [0, 1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], + ) + ) + ) + == [] + ) + assert ( + list( + BivincularPatt( + Perm((1, 3, 7, 6, 5, 0, 8, 2, 4, 9)), + [0, 3, 5, 6, 7, 9, 10], + [2, 3, 6, 7], + ).occurrences_in( + CovincularPatt( + Perm((1, 6, 10, 3, 11, 0, 2, 7, 9, 8, 4, 5)), [7, 8, 11, 12] + ) + ) + ) + == [] + ) + assert ( + list( + BivincularPatt( + Perm((1, 3, 7, 6, 5, 0, 8, 2, 4, 9)), + [0, 3, 5, 6, 7, 9, 10], + [2, 3, 6, 7], + ).occurrences_in( + VincularPatt(Perm((8, 4, 2, 5, 0, 6, 3, 9, 1, 7)), [1, 2, 3, 8]) + ) + ) + == [] + ) + assert ( + list( + BivincularPatt( + Perm((1, 3, 7, 6, 5, 0, 8, 2, 4, 9)), + [0, 3, 5, 6, 7, 9, 10], + [2, 3, 6, 7], + ).occurrences_in( + BivincularPatt( + Perm((6, 11, 5, 0, 12, 7, 2, 1, 4, 3, 9, 8, 10)), + [1, 7, 8, 12, 13], + [1, 3, 4, 5, 6, 7, 8, 11, 12, 13], + ) + ) + ) + == [] + ) + assert ( + list( + BivincularPatt( + Perm((1, 3, 7, 6, 5, 0, 8, 2, 4, 9)), + [0, 3, 5, 6, 7, 9, 10], + [2, 3, 6, 7], + ).occurrences_in( + CovincularPatt( + Perm((2, 0, 3, 5, 1, 6, 7, 8, 4)), [1, 2, 3, 4, 5, 7, 8, 9] + ) + ) + ) + == [] + ) + assert ( + list( + CovincularPatt( + Perm((2, 0, 3, 5, 1, 6, 7, 8, 4)), [1, 2, 3, 4, 5, 7, 8, 9] + ).occurrences_in( + VincularPatt( + Perm((7, 11, 4, 2, 0, 9, 10, 3, 14, 6, 1, 5, 12, 13, 8)), + [0, 1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], + ) + ) + ) + == [] + ) + assert ( + list( + CovincularPatt( + Perm((2, 0, 3, 5, 1, 6, 7, 8, 4)), [1, 2, 3, 4, 5, 7, 8, 9] + ).occurrences_in( + CovincularPatt( + Perm((1, 6, 10, 3, 11, 0, 2, 7, 9, 8, 4, 5)), [7, 8, 11, 12] + ) + ) + ) + == [] + ) + assert ( + list( + CovincularPatt( + Perm((2, 0, 3, 5, 1, 6, 7, 8, 4)), [1, 2, 3, 4, 5, 7, 8, 9] + ).occurrences_in( + VincularPatt(Perm((8, 4, 2, 5, 0, 6, 3, 9, 1, 7)), [1, 2, 3, 8]) + ) + ) + == [] + ) + assert ( + list( + CovincularPatt( + Perm((2, 0, 3, 5, 1, 6, 7, 8, 4)), [1, 2, 3, 4, 5, 7, 8, 9] + ).occurrences_in( + BivincularPatt( + Perm((6, 11, 5, 0, 12, 7, 2, 1, 4, 3, 9, 8, 10)), + [1, 7, 8, 12, 13], + [1, 3, 4, 5, 6, 7, 8, 11, 12, 13], + ) + ) + ) + == [] + ) + assert ( + list( + CovincularPatt( + Perm((2, 0, 3, 5, 1, 6, 7, 8, 4)), [1, 2, 3, 4, 5, 7, 8, 9] + ).occurrences_in( + BivincularPatt( + Perm((1, 3, 7, 6, 5, 0, 8, 2, 4, 9)), + [0, 3, 5, 6, 7, 9, 10], + [2, 3, 6, 7], + ) + ) + ) + == [] + ) + assert ( + list( + CovincularPatt( + Perm((1, 6, 10, 3, 11, 0, 2, 7, 9, 8, 4, 5)), [7, 8, 11, 12] + ).occurrences_in( + BivincularPatt( + Perm((1, 3, 7, 6, 5, 0, 8, 2, 4, 9)), + [0, 3, 5, 6, 7, 9, 10], + [2, 3, 6, 7], + ) + ) + ) + == [] + ) + assert ( + list( + CovincularPatt( + Perm((1, 6, 10, 3, 11, 0, 2, 7, 9, 8, 4, 5)), [7, 8, 11, 12] + ).occurrences_in( + BivincularPatt( + Perm((6, 11, 5, 0, 12, 7, 2, 1, 4, 3, 9, 8, 10)), + [1, 7, 8, 12, 13], + [1, 3, 4, 5, 6, 7, 8, 11, 12, 13], + ) + ) + ) + == [] + ) + assert ( + list( + CovincularPatt( + Perm((1, 6, 10, 3, 11, 0, 2, 7, 9, 8, 4, 5)), [7, 8, 11, 12] + ).occurrences_in( + VincularPatt(Perm((8, 4, 2, 5, 0, 6, 3, 9, 1, 7)), [1, 2, 3, 8]) + ) + ) + == [] + ) + assert ( + list( + CovincularPatt( + Perm((1, 6, 10, 3, 11, 0, 2, 7, 9, 8, 4, 5)), [7, 8, 11, 12] + ).occurrences_in( + VincularPatt( + Perm((7, 11, 4, 2, 0, 9, 10, 3, 14, 6, 1, 5, 12, 13, 8)), + [0, 1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], + ) + ) + ) + == [] + ) + assert ( + list( + CovincularPatt( + Perm((1, 6, 10, 3, 11, 0, 2, 7, 9, 8, 4, 5)), [7, 8, 11, 12] + ).occurrences_in( + CovincularPatt( + Perm((2, 0, 3, 5, 1, 6, 7, 8, 4)), [1, 2, 3, 4, 5, 7, 8, 9] + ) + ) + ) + == [] + ) + assert ( + list( + VincularPatt( + Perm((8, 4, 2, 5, 0, 6, 3, 9, 1, 7)), [1, 2, 3, 8] + ).occurrences_in( + VincularPatt( + Perm((7, 11, 4, 2, 0, 9, 10, 3, 14, 6, 1, 5, 12, 13, 8)), + [0, 1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], + ) + ) + ) + == [] + ) + assert ( + list( + VincularPatt( + Perm((8, 4, 2, 5, 0, 6, 3, 9, 1, 7)), [1, 2, 3, 8] + ).occurrences_in( + CovincularPatt( + Perm((1, 6, 10, 3, 11, 0, 2, 7, 9, 8, 4, 5)), [7, 8, 11, 12] + ) + ) + ) + == [] + ) + assert ( + list( + VincularPatt( + Perm((8, 4, 2, 5, 0, 6, 3, 9, 1, 7)), [1, 2, 3, 8] + ).occurrences_in( + CovincularPatt( + Perm((2, 0, 3, 5, 1, 6, 7, 8, 4)), [1, 2, 3, 4, 5, 7, 8, 9] + ) + ) + ) + == [] + ) + assert ( + list( + VincularPatt( + Perm((8, 4, 2, 5, 0, 6, 3, 9, 1, 7)), [1, 2, 3, 8] + ).occurrences_in( + BivincularPatt( + Perm((6, 11, 5, 0, 12, 7, 2, 1, 4, 3, 9, 8, 10)), + [1, 7, 8, 12, 13], + [1, 3, 4, 5, 6, 7, 8, 11, 12, 13], + ) + ) + ) + == [] + ) + assert ( + list( + VincularPatt( + Perm((8, 4, 2, 5, 0, 6, 3, 9, 1, 7)), [1, 2, 3, 8] + ).occurrences_in( + BivincularPatt( + Perm((1, 3, 7, 6, 5, 0, 8, 2, 4, 9)), + [0, 3, 5, 6, 7, 9, 10], + [2, 3, 6, 7], + ) + ) + ) + == [] + ) + assert sorted( + VincularPatt(Perm((1, 7, 0, 2, 4, 5, 3, 6)), [2, 8]).occurrences_in( + BivincularPatt( + Perm((10, 3, 0, 2, 14, 13, 1, 4, 12, 11, 7, 8, 6, 5, 9)), + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15], + [4, 5, 6, 11], + ) + ) + ) == [ + (1, 5, 6, 7, 10, 11, 12, 14), + (1, 5, 6, 7, 10, 11, 13, 14), + (3, 5, 6, 7, 10, 11, 12, 14), + (3, 5, 6, 7, 10, 11, 13, 14), + ] + assert sorted( + BivincularPatt(Perm((2, 4, 5, 7, 3, 6, 1, 0)), [5], [0]).occurrences_in( + BivincularPatt(Perm((2, 4, 5, 7, 3, 6, 1, 0)), [5], [0]) + ) + ) == [(0, 1, 2, 3, 4, 5, 6, 7)] + assert sorted( + CovincularPatt(Perm((1, 3, 2, 6, 0, 4, 5, 7)), [3]).occurrences_in( + CovincularPatt( + Perm((1, 4, 3, 9, 0, 6, 2, 5, 8, 10, 7)), [0, 1, 2, 4, 6, 7, 8, 10, 11] + ) + ) + ) == [(0, 1, 2, 3, 4, 5, 8, 9), (0, 1, 2, 3, 4, 7, 8, 9)] + assert sorted( + CovincularPatt(Perm((1, 2, 3, 0, 6, 5, 4, 7)), []).occurrences_in( + BivincularPatt( + Perm((2, 10, 8, 3, 12, 5, 1, 13, 9, 7, 0, 6, 4, 11)), + [0, 9, 12], + [2, 3, 9, 11, 12, 13], + ) + ) + ) == [(0, 3, 5, 6, 8, 9, 11, 13)] + assert sorted( + VincularPatt(Perm((7, 1, 5, 2, 0, 4, 6, 3)), []).occurrences_in( + CovincularPatt( + Perm((13, 12, 5, 1, 8, 4, 2, 9, 6, 0, 7, 10, 3, 14, 11)), [6, 10] + ) + ) + ) == [(0, 3, 4, 6, 9, 10, 11, 12), (1, 3, 4, 6, 9, 10, 11, 12)] + assert sorted( + MeshPatt( + Perm((2, 3, 0, 1, 4)), + [ + (0, 3), + (0, 4), + (0, 5), + (1, 4), + (3, 2), + (3, 4), + (3, 5), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 5), + (5, 1), + (5, 2), + (5, 3), + (5, 4), + ], + ).occurrences_in( + VincularPatt(Perm((2, 7, 0, 6, 4, 3, 5, 1, 8)), [0, 1, 3, 4, 5, 6, 7, 8, 9]) + ) + ) == [(0, 1, 2, 7, 8)] + assert sorted( + BivincularPatt(Perm((0, 1)), [2], [1]).occurrences_in( + MeshPatt( + Perm((2, 3, 4, 0, 1)), + [ + (0, 0), + (0, 1), + (0, 2), + (0, 5), + (1, 0), + (1, 1), + (1, 2), + (1, 3), + (1, 5), + (2, 0), + (2, 1), + (3, 0), + (3, 1), + (3, 3), + (3, 5), + (4, 1), + (4, 2), + (4, 3), + (5, 0), + (5, 1), + (5, 2), + (5, 3), + (5, 4), + (5, 5), + ], + ) + ) + ) == [(3, 4)] + + +def test_occuerrences_in_perm(): + assert ( + sorted( + CovincularPatt(Perm((2, 0, 1)), [0, 1, 2]).occurrences_in( + Perm((0, 2, 1, 3)) + ) + ) + == [] + ) + assert ( + sorted( + CovincularPatt(Perm((2, 0, 1)), [0, 1, 2]).occurrences_in(Perm((1, 0, 2))) + ) + == [] + ) + assert ( + sorted( + BivincularPatt(Perm((2, 4, 3, 1, 0)), [0, 1], [3, 4]).occurrences_in( + Perm((6, 2, 5, 9, 4, 3, 1, 8, 7, 0)) + ) + ) + == [] + ) + assert ( + sorted( + BivincularPatt(Perm((2, 4, 3, 1, 0)), [0, 1], [3, 4]).occurrences_in( + Perm((5, 1, 6, 7, 4, 2, 3, 0)) + ) + ) + == [] + ) + assert sorted(VincularPatt(Perm((0, 1)), [2]).occurrences_in(Perm((2, 1, 0)))) == [] + assert ( + sorted( + BivincularPatt(Perm((1, 0)), [], [0, 2]).occurrences_in( + Perm((0, 1, 2, 4, 3)) + ) + ) + == [] + ) + assert ( + sorted( + BivincularPatt(Perm((1, 0)), [], [0, 2]).occurrences_in( + Perm((2, 1, 3, 0, 4)) + ) + ) + == [] + ) + assert ( + sorted(CovincularPatt(Perm((2, 0, 1)), [1]).occurrences_in(Perm((0, 2, 1)))) + == [] + ) + assert ( + sorted(VincularPatt(Perm((0, 1, 2)), [0, 1, 3]).occurrences_in(Perm((1, 0)))) + == [] + ) + assert ( + sorted(BivincularPatt(Perm((1, 0)), [0, 2], []).occurrences_in(Perm(()))) == [] + ) + assert sorted(CovincularPatt(Perm((0, 1)), []).occurrences_in(Perm(()))) == [] + assert ( + sorted( + CovincularPatt(Perm((1, 3, 0, 2)), [1, 3]).occurrences_in(Perm((0, 2, 1))) + ) + == [] + ) + assert ( + sorted( + BivincularPatt(Perm((0, 2, 1, 3, 5, 4)), [], [1]).occurrences_in( + Perm((1, 0)) + ) + ) + == [] + ) + assert ( + sorted(VincularPatt(Perm((3, 2, 4, 0, 1)), []).occurrences_in(Perm(()))) == [] + ) + assert sorted( + VincularPatt(Perm((1, 0)), []).occurrences_in(Perm((1, 0, 6, 4, 5, 2, 3))) + ) == [(0, 1), (2, 3), (2, 4), (2, 5), (2, 6), (3, 5), (3, 6), (4, 5), (4, 6)] + assert sorted( + CovincularPatt(Perm((0, 1)), []).occurrences_in(Perm((2, 1, 4, 0, 3))) + ) == [(0, 2), (0, 4), (1, 2), (1, 4), (3, 4)] + assert sorted( + CovincularPatt(Perm((5, 0, 2, 1, 3, 4)), [6]).occurrences_in( + Perm((6, 0, 3, 2, 1, 4, 5)) + ) + ) == [(0, 1, 2, 3, 5, 6), (0, 1, 2, 4, 5, 6), (0, 1, 3, 4, 5, 6)] + assert sorted(VincularPatt(Perm((1, 0)), []).occurrences_in(Perm((1, 2, 0)))) == [ + (0, 2), + (1, 2), + ] + assert sorted( + CovincularPatt(Perm((0, 1)), []).occurrences_in(Perm((2, 5, 0, 3, 4, 1))) + ) == [(0, 1), (0, 3), (0, 4), (2, 3), (2, 4), (2, 5), (3, 4)] + assert sorted( + BivincularPatt(Perm((0, 1, 3, 2)), [0, 1], [0, 4]).occurrences_in( + Perm((0, 3, 6, 1, 4, 5, 2)) + ) + ) == [(0, 1, 2, 4), (0, 1, 2, 5)] + assert sorted(VincularPatt(Perm((1, 0)), []).occurrences_in(Perm((1, 0, 2)))) == [ + (0, 1) + ] + assert sorted( + CovincularPatt(Perm((0, 1)), []).occurrences_in(Perm((1, 5, 3, 2, 0, 4))) + ) == [(0, 1), (0, 2), (0, 3), (0, 5), (2, 5), (3, 5), (4, 5)] + assert sorted( + VincularPatt(Perm((4, 1, 0, 3, 2)), [0, 1, 2, 3, 5]).occurrences_in( + Perm((7, 3, 2, 6, 1, 0, 5, 4)) + ) + ) == [(0, 1, 2, 3, 7)] + assert sorted( + CovincularPatt(Perm((5, 0, 2, 1, 3, 4)), [6]).occurrences_in( + Perm((7, 0, 6, 2, 1, 4, 5, 3)) + ) + ) == [(0, 1, 3, 4, 5, 6)] + assert sorted( + VincularPatt(Perm((1, 0)), []).occurrences_in(Perm((4, 0, 3, 2, 1, 6, 5))) + ) == [(0, 1), (0, 2), (0, 3), (0, 4), (2, 3), (2, 4), (3, 4), (5, 6)] + assert sorted( + CovincularPatt(Perm((0, 1)), []).occurrences_in(Perm((1, 5, 4, 0, 2, 3, 6))) + ) == [ + (0, 1), + (0, 2), + (0, 4), + (0, 5), + (0, 6), + (1, 6), + (2, 6), + (3, 4), + (3, 5), + (3, 6), + (4, 5), + (4, 6), + (5, 6), + ] + assert sorted( + VincularPatt(Perm((1, 0)), []).occurrences_in(Perm((3, 4, 0, 1, 2))) + ) == [(0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4)] + assert sorted( + CovincularPatt(Perm((0, 1)), []).occurrences_in(Perm((3, 0, 1, 2))) + ) == [(1, 2), (1, 3), (2, 3)] + assert sorted( + CovincularPatt(Perm((5, 0, 2, 1, 3, 4)), [6]).occurrences_in( + Perm((1, 4, 8, 2, 5, 3, 6, 7, 0)) + ) + ) == [(2, 3, 4, 5, 6, 7)] + assert sorted( + BivincularPatt(Perm((0, 1, 3, 2)), [0, 1], [0, 4]).occurrences_in( + Perm((0, 2, 4, 1, 3)) + ) + ) == [(0, 1, 2, 4)] + assert sorted( + VincularPatt(Perm((1, 0)), []).occurrences_in(Perm((0, 6, 3, 5, 1, 2, 4))) + ) == [ + (1, 2), + (1, 3), + (1, 4), + (1, 5), + (1, 6), + (2, 4), + (2, 5), + (3, 4), + (3, 5), + (3, 6), + ] + assert sorted( + CovincularPatt(Perm((0, 1)), []).occurrences_in(Perm((3, 5, 4, 0, 2, 1))) + ) == [(0, 1), (0, 2), (3, 4), (3, 5)] + assert sorted( + CovincularPatt(Perm((5, 0, 2, 1, 3, 4)), [6]).occurrences_in( + Perm((9, 4, 1, 5, 2, 0, 6, 3, 8, 7)) + ) + ) == [(0, 2, 3, 4, 6, 8), (0, 2, 3, 4, 6, 9)] + assert sorted( + BivincularPatt(Perm((0, 1, 3, 2)), [0, 1], [0, 4]).occurrences_in( + Perm((0, 3, 5, 6, 2, 7, 4, 1)) + ) + ) == [(0, 1, 5, 6)] + assert sorted( + VincularPatt(Perm((1, 0)), []).occurrences_in(Perm((1, 5, 0, 4, 2, 6, 3))) + ) == [(0, 2), (1, 2), (1, 3), (1, 4), (1, 6), (3, 4), (3, 6), (5, 6)] + assert sorted( + CovincularPatt(Perm((0, 1)), []).occurrences_in(Perm((1, 0, 2, 3))) + ) == [(0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] + assert sorted( + BivincularPatt(Perm((0, 1, 3, 2)), [0, 1], [0, 4]).occurrences_in( + Perm((0, 4, 7, 8, 6, 2, 5, 1, 3)) + ) + ) == [(0, 1, 3, 4), (0, 1, 3, 6)] + assert sorted( + VincularPatt(Perm((1, 0)), []).occurrences_in(Perm((4, 1, 2, 3, 0))) + ) == [(0, 1), (0, 2), (0, 3), (0, 4), (1, 4), (2, 4), (3, 4)] + assert sorted( + CovincularPatt(Perm((0, 1)), []).occurrences_in(Perm((0, 2, 3, 1))) + ) == [(0, 1), (0, 2), (0, 3), (1, 2)] + assert sorted( + CovincularPatt(Perm((5, 0, 2, 1, 3, 4)), [6]).occurrences_in( + Perm((8, 0, 2, 5, 7, 1, 4, 3, 6)) + ) + ) == [(0, 1, 2, 5, 6, 8), (0, 1, 2, 5, 7, 8)] + assert sorted( + BivincularPatt(Perm((0, 1, 3, 2)), [0, 1], [0, 4]).occurrences_in( + Perm((0, 1, 3, 2)) + ) + ) == [(0, 1, 2, 3)] + assert sorted( + VincularPatt(Perm((1, 0)), []).occurrences_in(Perm((2, 0, 3, 4, 1))) + ) == [(0, 1), (0, 4), (2, 4), (3, 4)] + assert sorted( + CovincularPatt(Perm((0, 1)), []).occurrences_in(Perm((5, 0, 4, 6, 1, 3, 2))) + ) == [(0, 3), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (2, 3), (4, 5), (4, 6)] + assert sorted( + VincularPatt(Perm((4, 1, 0, 3, 2)), [0, 1, 2, 3, 5]).occurrences_in( + Perm((4, 1, 0, 3, 2)) + ) + ) == [(0, 1, 2, 3, 4)] + assert sorted( + VincularPatt(Perm((1, 0)), []).occurrences_in(Perm((0, 1, 3, 2))) + ) == [(2, 3)] + assert sorted(CovincularPatt(Perm((0, 1)), []).occurrences_in(Perm((0, 1, 2)))) == [ + (0, 1), + (0, 2), + (1, 2), + ] + assert sorted( + BivincularPatt(Perm((1, 3, 0, 2, 4)), [], [0, 1, 3]).occurrences_in( + Perm((7, 1, 4, 0, 3, 6, 5, 8, 9, 2)) + ) + ) == [(1, 2, 3, 4, 5), (1, 2, 3, 4, 6), (1, 2, 3, 4, 7), (1, 2, 3, 4, 8)] + assert sorted( + VincularPatt(Perm((0, 2, 1, 3)), [0, 1]).occurrences_in( + Perm((10, 16, 7, 1, 13, 15, 14, 0, 5, 12, 3, 4, 2, 11, 8, 17, 6, 9)) + ) + ) == [(0, 1, 4, 15), (0, 1, 5, 15), (0, 1, 6, 15), (0, 1, 9, 15), (0, 1, 13, 15)] + assert sorted( + CovincularPatt(Perm((1, 2, 0, 3)), []).occurrences_in( + Perm((1, 6, 2, 0, 5, 4, 3)) + ) + ) == [(0, 2, 3, 4), (0, 2, 3, 5), (0, 2, 3, 6)] + assert sorted( + CovincularPatt(Perm((0, 1, 2)), [1, 2, 3]).occurrences_in(Perm((2, 0, 3, 1, 4))) + ) == [(0, 2, 4)] + assert sorted( + VincularPatt(Perm((2, 3, 0, 1)), [1, 3, 4]).occurrences_in( + Perm((5, 0, 6, 8, 9, 3, 1, 2, 4, 7)) + ) + ) == [(3, 4, 8, 9)] + assert sorted( + BivincularPatt(Perm((2, 4, 3, 1, 0, 5)), [], [2]).occurrences_in( + Perm((15, 16, 8, 17, 7, 18, 11, 12, 9, 6, 4, 5, 2, 13, 0, 3, 1, 14, 19, 10)) + ) + ) == [ + (4, 5, 6, 9, 10, 18), + (4, 5, 6, 9, 11, 18), + (4, 5, 6, 9, 12, 18), + (4, 5, 6, 9, 14, 18), + (4, 5, 6, 9, 15, 18), + (4, 5, 6, 9, 16, 18), + (4, 5, 7, 9, 10, 18), + (4, 5, 7, 9, 11, 18), + (4, 5, 7, 9, 12, 18), + (4, 5, 7, 9, 14, 18), + (4, 5, 7, 9, 15, 18), + (4, 5, 7, 9, 16, 18), + (4, 5, 8, 9, 10, 18), + (4, 5, 8, 9, 11, 18), + (4, 5, 8, 9, 12, 18), + (4, 5, 8, 9, 14, 18), + (4, 5, 8, 9, 15, 18), + (4, 5, 8, 9, 16, 18), + (4, 6, 8, 9, 10, 13), + (4, 6, 8, 9, 10, 17), + (4, 6, 8, 9, 10, 18), + (4, 6, 8, 9, 11, 13), + (4, 6, 8, 9, 11, 17), + (4, 6, 8, 9, 11, 18), + (4, 6, 8, 9, 12, 13), + (4, 6, 8, 9, 12, 17), + (4, 6, 8, 9, 12, 18), + (4, 6, 8, 9, 14, 17), + (4, 6, 8, 9, 14, 18), + (4, 6, 8, 9, 15, 17), + (4, 6, 8, 9, 15, 18), + (4, 6, 8, 9, 16, 17), + (4, 6, 8, 9, 16, 18), + (4, 7, 8, 9, 10, 13), + (4, 7, 8, 9, 10, 17), + (4, 7, 8, 9, 10, 18), + (4, 7, 8, 9, 11, 13), + (4, 7, 8, 9, 11, 17), + (4, 7, 8, 9, 11, 18), + (4, 7, 8, 9, 12, 13), + (4, 7, 8, 9, 12, 17), + (4, 7, 8, 9, 12, 18), + (4, 7, 8, 9, 14, 17), + (4, 7, 8, 9, 14, 18), + (4, 7, 8, 9, 15, 17), + (4, 7, 8, 9, 15, 18), + (4, 7, 8, 9, 16, 17), + (4, 7, 8, 9, 16, 18), + ] + assert sorted( + CovincularPatt(Perm((1, 3, 0, 2)), [2, 4]).occurrences_in( + Perm((3, 8, 6, 1, 4, 12, 15, 9, 0, 18, 10, 11, 7, 2, 14, 5, 13, 17, 16)) + ) + ) == [ + (4, 9, 13, 15), + (5, 9, 10, 16), + (5, 9, 11, 16), + (5, 9, 12, 16), + (5, 9, 13, 16), + (5, 9, 15, 16), + (6, 9, 10, 18), + (6, 9, 11, 18), + (6, 9, 12, 18), + (6, 9, 13, 18), + (6, 9, 14, 18), + (6, 9, 15, 18), + (6, 9, 16, 18), + ] + assert sorted( + CovincularPatt(Perm((2, 5, 0, 3, 4, 1)), []).occurrences_in( + Perm((1, 16, 0, 6, 2, 3, 13, 9, 14, 15, 5, 11, 10, 12, 7, 4, 8)) + ) + ) == [ + (7, 8, 10, 11, 13, 14), + (7, 8, 10, 11, 13, 16), + (7, 8, 10, 12, 13, 14), + (7, 8, 10, 12, 13, 16), + (7, 9, 10, 11, 13, 14), + (7, 9, 10, 11, 13, 16), + (7, 9, 10, 12, 13, 14), + (7, 9, 10, 12, 13, 16), + ] + assert sorted( + VincularPatt(Perm((1, 0, 2)), [2]).occurrences_in( + Perm((6, 4, 8, 0, 2, 3, 1, 9, 7, 5)) + ) + ) == [(0, 1, 2), (0, 6, 7), (1, 6, 7), (2, 6, 7), (4, 6, 7), (5, 6, 7)] + assert sorted( + VincularPatt(Perm((2, 0, 1)), [0, 1, 2]).occurrences_in( + Perm((13, 2, 12, 4, 10, 3, 1, 9, 6, 15, 5, 7, 0, 16, 8, 11, 14)) + ) + ) == [(0, 1, 2)] + + +def test_contain_dunder_method(): + assert BivincularPatt(Perm((0, 1, 2)), (2,), ()) in Perm((0, 4, 3, 1, 2)) + assert VincularPatt(Perm((0, 1, 2)), (2,)) in Perm((0, 4, 3, 1, 2)) + assert BivincularPatt(Perm((2, 0, 1)), (), (2,)) in Perm((1, 2, 4, 0, 3)) + assert CovincularPatt(Perm((2, 0, 1)), (2,)) in Perm((1, 2, 4, 0, 3)) + + +def test_avoided_by(): + assert CovincularPatt(Perm((2, 3, 0, 1, 4)), [1, 2, 5]).avoided_by( + Perm((7, 1, 2, 8, 6, 0, 3, 4, 5)), + Perm((2, 5, 4, 3, 1, 0)), + Perm((0, 2, 3, 4, 1, 5)), + ) + assert BivincularPatt(Perm((0, 2, 1)), [0, 2], [0, 1, 2]).avoided_by( + Perm((5, 1, 4, 0, 2, 6, 3)), Perm((3, 2, 0, 1, 5, 4)), Perm((0, 1)) + ) + assert BivincularPatt(Perm((1, 0)), [0, 2], [0, 1]).avoided_by( + Perm((2, 3, 4, 5, 0, 1)), Perm((2, 0, 3, 1)), Perm((0, 1, 2, 3)) + ) + assert not VincularPatt(Perm((3, 1, 0, 2)), [1, 3]).avoided_by( + Perm((1, 4, 8, 2, 3, 7, 6, 0, 5)), + Perm((2, 1, 3, 4, 0)), + Perm((6, 5, 1, 4, 0, 3, 7, 2)), + ) + assert not CovincularPatt(Perm((2, 1, 3, 0)), [2]).avoided_by( + Perm((8, 6, 4, 0, 7, 2, 3, 1, 5)), + Perm((7, 3, 0, 1, 2, 6, 4, 5)), + Perm((7, 2, 0, 4, 3, 8, 5, 6, 1)), + ) + assert VincularPatt(Perm((0, 1, 3, 2)), [0]).avoided_by( + Perm((4, 0, 3, 1, 2)), Perm((1, 0)), Perm((3, 1, 4, 0, 5, 2)) + ) + assert BivincularPatt(Perm((0, 2, 1)), [0, 1], [0, 1, 3]).avoided_by( + Perm((3, 2, 1, 0)), Perm((5, 2, 4, 0, 3, 1)), Perm((3, 2, 1, 0, 4, 5)) + ) + assert not BivincularPatt(Perm((3, 2, 1, 0)), [], []).avoided_by( + Perm((4, 3, 2, 0, 1)), Perm((1, 3, 4, 5, 6, 0, 2)), Perm((1, 3, 2, 0)) + ) + assert not CovincularPatt(Perm((0, 2, 1)), [0, 2]).avoided_by( + Perm((6, 1, 2, 3, 0, 5, 4)), Perm((4, 2, 1, 0, 3)), Perm((0, 1, 2, 3)) + ) + assert not VincularPatt(Perm((1, 0)), []).avoided_by( + Perm((2, 0, 6, 3, 5, 4, 1)), Perm((1, 3, 0, 2, 4)), Perm((0, 4, 1, 3, 2)) + ) + assert not CovincularPatt(Perm((1, 0, 2, 3)), [4]).avoided_by( + Perm((3, 2, 0, 4, 1, 5)), + Perm((3, 2, 4, 0, 1)), + Perm((0, 3, 4, 8, 6, 1, 7, 5, 2)), + ) + assert VincularPatt(Perm((5, 2, 1, 0, 3, 4)), [1, 4, 5, 6]).avoided_by( + Perm((3, 4, 1, 0, 6, 5, 7, 2)), + Perm((0, 5, 3, 1, 7, 4, 2, 6)), + Perm((7, 0, 6, 3, 9, 1, 8, 2, 5, 4)), + ) + assert CovincularPatt(Perm((0, 2, 1)), [0, 1, 3]).avoided_by( + Perm((0, 6, 1, 8, 3, 2, 9, 7, 4, 5)), + Perm((4, 2, 3, 6, 5, 8, 1, 0, 7)), + Perm((0, 2, 9, 6, 5, 8, 1, 4, 7, 11, 10, 3)), + ) + assert BivincularPatt(Perm((3, 1, 0, 2)), [0, 1, 2, 3], [2]).avoided_by( + Perm((4, 3, 0, 7, 8, 2, 9, 6, 5, 1)), + Perm((2, 9, 4, 0, 7, 1, 3, 8, 6, 5)), + Perm((0, 1, 5, 4, 7, 6, 8, 3, 2)), + ) + assert BivincularPatt(Perm((0, 2, 4, 5, 1, 3)), [], [1, 2, 3, 4, 5]).avoided_by( + Perm((8, 4, 12, 11, 10, 6, 2, 1, 9, 7, 3, 5, 0)), + Perm((11, 12, 4, 9, 8, 3, 10, 2, 6, 7, 5, 0, 1)), + Perm((10, 14, 4, 3, 7, 15, 1, 11, 5, 6, 12, 8, 9, 2, 13, 0)), + ) + + +def test_count_occurrences_in(): + assert ( + BivincularPatt( + Perm((4, 0, 1, 2, 3, 5)), [1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5] + ).count_occurrences_in(Perm((7, 2, 6, 4, 8, 5, 3, 0, 1))) + == 0 + ) + assert ( + BivincularPatt( + Perm((4, 0, 1, 2, 3, 5)), [1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5] + ).count_occurrences_in( + Perm((10, 9, 3, 5, 12, 4, 6, 7, 1, 8, 14, 15, 0, 11, 13, 2)) + ) + == 0 + ) + assert ( + BivincularPatt( + Perm((4, 0, 1, 2, 3, 5)), [1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5] + ).count_occurrences_in(Perm((1, 3, 7, 6, 5, 0, 8, 2, 4))) + == 0 + ) + assert ( + VincularPatt(Perm((0, 1, 2)), [0, 1, 3]).count_occurrences_in( + Perm((1, 4, 2, 6, 8, 5, 0, 3, 7)) + ) + == 1 + ) + assert ( + VincularPatt(Perm((0, 1, 2)), [0, 1, 3]).count_occurrences_in( + Perm((8, 10, 3, 4, 6, 1, 11, 7, 0, 9, 2, 5)) + ) + == 0 + ) + assert ( + VincularPatt(Perm((0, 1, 2)), [0, 1, 3]).count_occurrences_in( + Perm((0, 5, 2, 1, 4, 3)) + ) + == 0 + ) + assert ( + CovincularPatt(Perm((4, 2, 0, 1, 3)), [3]).count_occurrences_in( + Perm((0, 7, 10, 2, 9, 12, 3, 1, 4, 5, 8, 11, 6, 13)) + ) + == 0 + ) + assert ( + CovincularPatt(Perm((4, 2, 0, 1, 3)), [3]).count_occurrences_in( + Perm((3, 0, 7, 1, 4, 2, 5, 6)) + ) + == 0 + ) + assert ( + CovincularPatt(Perm((4, 2, 0, 1, 3)), [3]).count_occurrences_in( + Perm((6, 11, 7, 5, 8, 1, 0, 10, 4, 3, 9, 2)) + ) + == 4 + ) + assert ( + CovincularPatt(Perm((0, 1)), [0, 1]).count_occurrences_in( + Perm((6, 4, 2, 0, 5, 7, 1, 3)) + ) + == 1 + ) + assert ( + CovincularPatt(Perm((0, 1)), [0, 1]).count_occurrences_in( + Perm((1, 4, 6, 3, 0, 9, 8, 2, 5, 7)) + ) + == 0 + ) + assert ( + CovincularPatt(Perm((0, 1)), [0, 1]).count_occurrences_in( + Perm((0, 6, 2, 4, 3, 7, 5, 1)) + ) + == 1 + ) + assert ( + BivincularPatt(Perm((1, 0, 3, 2)), [1], [0, 1, 3, 4]).count_occurrences_in( + Perm((6, 0, 5, 4, 7, 3, 2, 1)) + ) + == 0 + ) + assert ( + BivincularPatt(Perm((1, 0, 3, 2)), [1], [0, 1, 3, 4]).count_occurrences_in( + Perm((1, 10, 6, 11, 13, 2, 3, 12, 9, 5, 0, 7, 8, 4)) + ) + == 0 + ) + assert ( + BivincularPatt(Perm((1, 0, 3, 2)), [1], [0, 1, 3, 4]).count_occurrences_in( + Perm((11, 7, 2, 0, 4, 10, 1, 3, 6, 8, 12, 9, 5)) + ) + == 0 + ) + assert ( + VincularPatt(Perm((2, 4, 0, 3, 1)), [0, 3]).count_occurrences_in( + Perm((10, 4, 9, 13, 6, 2, 0, 5, 1, 12, 11, 8, 7, 3)) + ) + == 3 + ) + assert ( + VincularPatt(Perm((2, 4, 0, 3, 1)), [0, 3]).count_occurrences_in( + Perm((13, 12, 2, 0, 5, 11, 10, 3, 9, 4, 1, 6, 8, 7)) + ) + == 0 + ) + assert ( + VincularPatt(Perm((2, 4, 0, 3, 1)), [0, 3]).count_occurrences_in( + Perm((4, 7, 11, 2, 0, 9, 5, 8, 14, 3, 1, 6, 13, 12, 10)) + ) + == 2 + ) + assert ( + VincularPatt(Perm((3, 0, 1, 2, 4)), [0, 2, 4, 5]).count_occurrences_in( + BivincularPatt(Perm((1, 2, 0)), [0, 1], [1, 2, 3]) + ) + == 0 + ) + assert ( + CovincularPatt(Perm((1, 0, 2)), [2]).count_occurrences_in( + BivincularPatt(Perm((2, 0, 1, 3, 4)), [], [0, 1, 2, 3]) + ) + == 2 + ) + assert ( + CovincularPatt(Perm((0, 2, 1)), [1, 3]).count_occurrences_in( + BivincularPatt(Perm((0, 1, 3, 4, 2)), [0, 1, 2, 4], [2, 3, 4, 5]) + ) + == 1 + ) + + +def test_contained_in(): + assert not BivincularPatt(Perm((2, 1, 0)), [0, 2, 3], [0, 1, 2]).contained_in( + Perm((8, 4, 6, 3, 1, 7, 2, 0, 5)) + ) + assert not BivincularPatt(Perm((2, 1, 0)), [0, 2, 3], [0, 1, 2]).contained_in( + Perm((5, 2, 3, 4, 1, 6, 0)) + ) + assert not BivincularPatt(Perm((2, 1, 0)), [0, 2, 3], [0, 1, 2]).contained_in( + Perm((1, 0)) + ) + assert not CovincularPatt(Perm((3, 0, 2, 1)), []).contained_in( + Perm((2, 0, 1, 5, 3, 6, 4)) + ) + assert not CovincularPatt(Perm((3, 0, 2, 1)), []).contained_in(Perm((0, 3, 1, 2))) + assert CovincularPatt(Perm((3, 0, 2, 1)), []).contained_in( + Perm((5, 3, 6, 7, 8, 0, 2, 1, 4, 9)) + ) + assert not BivincularPatt(Perm((1, 3, 0, 2)), [0], []).contained_in(Perm((1, 0))) + assert not BivincularPatt(Perm((1, 3, 0, 2)), [0], []).contained_in( + Perm((6, 0, 4, 1, 3, 5, 2, 7)) + ) + assert not BivincularPatt(Perm((1, 3, 0, 2)), [0], []).contained_in( + Perm((9, 0, 1, 3, 4, 5, 7, 8, 2, 6)) + ) + assert not CovincularPatt(Perm((4, 3, 0, 2, 1)), []).contained_in( + Perm((1, 2, 0, 6, 10, 3, 4, 7, 5, 9, 11, 8)) + ) + assert not CovincularPatt(Perm((4, 3, 0, 2, 1)), []).contained_in( + Perm((3, 2, 4, 0, 1)) + ) + assert CovincularPatt(Perm((4, 3, 0, 2, 1)), []).contained_in( + Perm((6, 0, 5, 1, 9, 2, 8, 11, 7, 4, 12, 3, 10)) + ) + assert VincularPatt(Perm((0, 1)), [0, 2]).contained_in(Perm((0, 3, 1, 2))) + assert not VincularPatt(Perm((0, 1)), [0, 2]).contained_in(Perm(())) + assert VincularPatt(Perm((0, 1)), [0, 2]).contained_in(Perm((3, 2, 0, 1, 4))) + assert VincularPatt(Perm((1, 2, 0)), []).contained_in( + Perm((2, 4, 10, 7, 5, 0, 6, 8, 3, 9, 11, 1)) + ) + assert VincularPatt(Perm((1, 2, 0)), []).contained_in(Perm((4, 1, 2, 5, 3, 0))) + assert not VincularPatt(Perm((1, 2, 0)), []).contained_in(Perm((0,))) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in( + Perm((11, 1, 4, 10, 12, 0, 9, 2, 6, 5, 7, 3, 8)) + ) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in( + Perm((0, 4, 2, 6, 3, 1, 5)) + ) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in( + Perm((2, 8, 6, 4, 3, 0, 9, 7, 5, 10, 1)) + ) + assert VincularPatt(Perm((0, 1)), [0]).contained_in(Perm((0, 1, 2))) + assert VincularPatt(Perm((0, 1)), [0]).contained_in(Perm((0, 2, 1, 6, 3, 4, 5))) + assert BivincularPatt(Perm((0, 2, 1)), [0, 3], [2]).contained_in( + Perm((8, 3, 9, 1, 6, 10, 12, 0, 2, 7, 5, 4, 11)) + ) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in(Perm((1, 3, 4, 2, 0))) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in( + Perm((9, 0, 3, 6, 7, 2, 1, 8, 5, 4, 11, 10)) + ) + assert VincularPatt(Perm((0, 1)), [0]).contained_in( + Perm((6, 1, 0, 2, 10, 7, 5, 9, 3, 4, 8)) + ) + assert VincularPatt(Perm((0, 1)), [0]).contained_in(Perm((3, 0, 4, 5, 2, 1, 6))) + assert VincularPatt(Perm((0, 1)), [0]).contained_in(Perm((1, 2, 0))) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in( + Perm((7, 5, 8, 4, 1, 9, 3, 6, 2, 0)) + ) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in( + Perm((1, 3, 0, 4, 5, 6, 2)) + ) + assert VincularPatt(Perm((0, 1)), [0]).contained_in(Perm((2, 7, 5, 0, 3, 6, 1, 4))) + assert VincularPatt(Perm((0, 1)), [0]).contained_in( + Perm((6, 7, 0, 2, 8, 4, 1, 9, 3, 10, 5)) + ) + assert BivincularPatt(Perm((0, 2, 1)), [0, 3], [2]).contained_in(Perm((0, 2, 1))) + assert BivincularPatt(Perm((0, 2, 1)), [0, 3], [2]).contained_in(Perm((1, 3, 0, 2))) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in( + Perm((4, 5, 3, 0, 1, 8, 6, 2, 10, 11, 9, 7, 12)) + ) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in( + Perm((6, 2, 7, 5, 1, 4, 3, 0)) + ) + assert VincularPatt(Perm((0, 1)), [0]).contained_in(Perm((1, 2, 0))) + assert VincularPatt(Perm((0, 1)), [0]).contained_in(Perm((1, 2, 3, 4, 0, 5))) + assert VincularPatt(Perm((0, 1)), [0]).contained_in(Perm((2, 3, 0, 1))) + assert VincularPatt(Perm((0, 1)), [0]).contained_in(Perm((0, 1, 2))) + assert VincularPatt(Perm((0, 1)), [0]).contained_in(Perm((0, 1))) + assert BivincularPatt(Perm((0, 2, 1)), [0, 3], [2]).contained_in( + Perm((5, 3, 4, 0, 6, 7, 1, 2, 9, 8)) + ) + assert BivincularPatt(Perm((0, 2, 1)), [0, 3], [2]).contained_in(Perm((1, 0, 3, 2))) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in( + Perm((1, 3, 5, 10, 7, 4, 8, 0, 2, 9, 6)) + ) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in(Perm((2, 3, 1, 0))) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in( + Perm((7, 5, 3, 1, 8, 2, 0, 4, 11, 9, 10, 6)) + ) + assert VincularPatt(Perm((0, 1)), [0]).contained_in(Perm((2, 3, 0, 1))) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in( + Perm((5, 8, 9, 0, 3, 2, 7, 6, 1, 4)) + ) + assert VincularPatt(Perm((0, 1)), [0]).contained_in( + Perm((3, 6, 10, 7, 11, 1, 4, 2, 5, 8, 0, 9)) + ) + assert VincularPatt(Perm((0, 1)), [0]).contained_in( + Perm((9, 7, 4, 5, 0, 1, 10, 11, 6, 2, 3, 8)) + ) + assert VincularPatt(Perm((0, 1)), [0]).contained_in( + Perm((4, 9, 8, 6, 0, 5, 2, 3, 7, 1)) + ) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in(Perm((1, 2, 0))) + assert VincularPatt(Perm((0, 1)), [0]).contained_in( + Perm((4, 10, 1, 5, 11, 9, 2, 3, 8, 7, 6, 0)) + ) + assert VincularPatt(Perm((0, 1)), [0]).contained_in( + Perm((2, 5, 9, 3, 0, 1, 8, 4, 10, 6, 7)) + ) + assert BivincularPatt(Perm((0, 2, 1)), [0, 3], [2]).contained_in( + Perm((5, 3, 2, 8, 4, 0, 1, 6, 7)) + ) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in( + Perm((2, 4, 5, 7, 0, 3, 6, 1)) + ) + assert VincularPatt(Perm((0, 1)), [0]).contained_in(Perm((1, 0, 2))) + assert BivincularPatt(Perm((0, 2, 1)), [0, 3], [2]).contained_in( + Perm((0, 4, 2, 6, 1, 5, 3)) + ) + assert BivincularPatt(Perm((0, 2, 1)), [0, 3], [2]).contained_in(Perm((0, 2, 1))) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in( + Perm((2, 12, 9, 1, 6, 10, 5, 8, 11, 3, 4, 0, 7)) + ) + assert CovincularPatt(Perm((1, 2, 0)), [1, 2]).contained_in( + Perm((4, 5, 7, 9, 2, 1, 8, 0, 6, 3)) + ) + assert VincularPatt(Perm((0, 1)), [0]).contained_in(Perm((5, 0, 2, 3, 4, 6, 1))) + assert VincularPatt(Perm((0, 1)), [0]).contained_in(Perm((0, 1))) diff --git a/tests/patterns/test_meshpatt.py b/tests/patterns/test_meshpatt.py new file mode 100644 index 00000000..8d1d3b68 --- /dev/null +++ b/tests/patterns/test_meshpatt.py @@ -0,0 +1,1589 @@ +import itertools +import random +from math import factorial + +import pytest +from permuta import MeshPatt, Perm +from permuta.misc import DIR_EAST, DIR_NORTH, DIR_SOUTH, DIR_WEST + +mesh_pattern = MeshPatt( + Perm([1, 3, 2, 0]), + set([(0, 0), (4, 0), (2, 1), (4, 1), (2, 2), (4, 2), (3, 3), (0, 4)]), +) +perm1 = Perm([5, 2, 8, 6, 7, 9, 4, 3, 1, 0]) # Occurrence: E.g., [1, 3, 6, 9] +perm2 = Perm([1, 2, 8, 6, 5, 9, 4, 3, 7, 0]) # Occurrence: E.g., [1, 6, 7, 9] +perm3 = Perm([9, 8, 7, 6, 2, 5, 3, 4, 0, 1]) # Occurrence: None (avoids) +perm4 = Perm([0, 1, 2, 3, 4]) # Avoids as well +perm5 = Perm([1, 2, 4, 3, 0]) # Two occurrences +patt1 = perm4 +patt2 = perm5 +patt3 = Perm((2, 3, 0, 1)) +shad1 = frozenset( + [(0, 0), (1, 0), (2, 0), (2, 1), (3, 2), (3, 3), (5, 0), (5, 1), (5, 2)] +) +shad2 = frozenset([(3, 3), (2, 2), (1, 1), (0, 2)]) +shad3 = frozenset([(1, 3), (4, 4), (2, 1), (2, 2), (0, 4), (4, 0)]) +mesh1 = MeshPatt(patt1, shad1) +mesh2 = MeshPatt(patt2, shad2) +mesh3 = MeshPatt(patt3, shad3) + + +def test_init(): + with pytest.raises(AssertionError): + MeshPatt(Perm(()), (0, 1)) + with pytest.raises(AssertionError): + MeshPatt(Perm((0, 1, 2)), [(1, "a")]) + with pytest.raises(AssertionError): + MeshPatt(Perm((0, 1, 2),), [("a", 1)]) + with pytest.raises(AssertionError): + MeshPatt(Perm.random(5), [(0,), (1, 1)]) + with pytest.raises(AssertionError): + MeshPatt(Perm.random(5), [(0, 0, 0), (1, 1, 1)]) + with pytest.raises(AssertionError): + MeshPatt(Perm(()), [(0, 1), (1, 0)]) + with pytest.raises(AssertionError): + MeshPatt(Perm.random(3), [(0, -1), (0, 0)]) + with pytest.raises(AssertionError): + MeshPatt(Perm.random(10), [(0, 0), (12, 7)]) + MeshPatt(Perm(), ()) + MeshPatt(Perm([]), ()) + MeshPatt(Perm((0,)), ()) + MeshPatt(Perm([0]), ()) + MeshPatt(Perm([3, 0, 2, 1]), ()) + MeshPatt( + Perm([3, 0, 2, 1]), + [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 0), + (1, 1), + (1, 2), + (1, 3), + (1, 4), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (2, 4), + (3, 0), + (3, 1), + (3, 2), + (3, 3), + (3, 4), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + ], + ) + MeshPatt(Perm([3, 0, 2, 1]), [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4)]) + MeshPatt([3, 0, 2, 1], [(0, 2), (0, 3), (0, 4)]) + MeshPatt(set([3, 0, 2, 1]), [(0, 2), (0, 3), (0, 4)]) + + +def test_complement(): + assert MeshPatt(Perm(), []).complement() == MeshPatt(Perm(), []) + assert MeshPatt(Perm((0,)), []).complement() == MeshPatt(Perm((0,)), []) + assert MeshPatt(Perm((0,)), [(0, 0)]).complement() == MeshPatt(Perm((0,)), [(0, 1)]) + assert MeshPatt(Perm((0,)), [(0, 1)]).complement() == MeshPatt(Perm((0,)), [(0, 0)]) + assert MeshPatt(Perm((0,)), [(1, 0)]).complement() == MeshPatt(Perm((0,)), [(1, 1)]) + assert MeshPatt(Perm((0,)), [(1, 1)]).complement() == MeshPatt(Perm((0,)), [(1, 0)]) + for _ in range(20): + mpatt = MeshPatt.random(10) + comp = mpatt.complement() + assert len(mpatt.pattern) == len(comp.pattern) + assert comp.complement() == mpatt + + +def test_flip_horizontal(): + assert MeshPatt(Perm(), []).flip_vertical() == MeshPatt(Perm(), []) + assert MeshPatt(Perm((0,)), [(0, 1)]).flip_horizontal() == MeshPatt( + Perm((0,)), [(0, 0)] + ) + + +def test_reverse(): + assert MeshPatt(Perm(), []).reverse() == MeshPatt(Perm(), []) + assert MeshPatt(Perm((0,)), []).reverse() == MeshPatt(Perm((0,)), []) + assert MeshPatt(Perm((0,)), [(0, 0)]).reverse() == MeshPatt(Perm((0,)), [(1, 0)]) + assert MeshPatt(Perm((0,)), [(0, 1)]).reverse() == MeshPatt(Perm((0,)), [(1, 1)]) + assert MeshPatt(Perm((0,)), [(1, 0)]).reverse() == MeshPatt(Perm((0,)), [(0, 0)]) + assert MeshPatt(Perm((0,)), [(1, 1)]).reverse() == MeshPatt(Perm((0,)), [(0, 1)]) + for _ in range(20): + mpatt = MeshPatt.random(10) + comp = mpatt.reverse() + assert len(mpatt.pattern) == len(comp.pattern) + assert comp.reverse() == mpatt + + +def test_flip_vertical(): + assert MeshPatt(Perm(), []).flip_vertical() == MeshPatt(Perm(), []) + assert MeshPatt(Perm((0,)), [(0, 0)]).flip_vertical() == MeshPatt( + Perm((0,)), [(1, 0)] + ) + + +def test_inverse(): + assert MeshPatt(Perm(), []).inverse() == MeshPatt(Perm(), []) + assert MeshPatt(Perm((0,)), []).inverse() == MeshPatt(Perm((0,)), []) + assert MeshPatt(Perm((0,)), [(0, 0)]).inverse() == MeshPatt(Perm((0,)), [(0, 0)]) + assert MeshPatt(Perm((0,)), [(0, 1)]).inverse() == MeshPatt(Perm((0,)), [(1, 0)]) + assert MeshPatt(Perm((0,)), [(1, 0)]).inverse() == MeshPatt(Perm((0,)), [(0, 1)]) + assert MeshPatt(Perm((0,)), [(1, 1)]).inverse() == MeshPatt(Perm((0,)), [(1, 1)]) + for _ in range(20): + mpatt = MeshPatt.random(10) + comp = mpatt.inverse() + assert len(mpatt.pattern) == len(comp.pattern) + assert comp.inverse() == mpatt + + +def test_sub_mesh_pattern(): + # Empty pattern + assert mesh1.sub_mesh_pattern(()) == MeshPatt((), ()) + # Sub mesh pattern from indices 1, 2, and 3 of mesh1 + pattern = (0, 1, 2) + shading = set([(1, 0), (2, 1), (2, 2)]) + mesh_pattern = MeshPatt(pattern, shading) + sub_mesh_pattern = mesh1.sub_mesh_pattern((1, 2, 3)) + assert sub_mesh_pattern == mesh_pattern + # Sub mesh pattern from indices 0, 1, and 4 of mesh1 + pattern = (0, 1, 2) + shading = set([(0, 0), (1, 0), (3, 0), (3, 1)]) + mesh_pattern = MeshPatt(pattern, shading) + sub_mesh_pattern = mesh1.sub_mesh_pattern((0, 1, 4)) + assert sub_mesh_pattern == mesh_pattern + # Sub mesh pattern from indices 3 and 4 of mesh1 + assert mesh1.sub_mesh_pattern((3, 4)) == MeshPatt((0, 1)) + # Sub mesh pattern from indices 2 and 3 of mesh1 + assert mesh1.sub_mesh_pattern((2, 3)) == MeshPatt((0, 1), set([(1, 1)])) + # Sub mesh pattern from index 0 of mesh3 + assert mesh3.sub_mesh_pattern((0,)) == MeshPatt((0,)) + # Sub mesh pattern from indices 0, 1, and 3 of mesh3 + assert mesh3.sub_mesh_pattern((0, 1, 3)) == MeshPatt( + (1, 2, 0), set([(0, 3), (1, 2), (3, 3)]) + ) + # Some complete sub meshes + assert mesh1.sub_mesh_pattern(range(len(mesh1))) == mesh1 + assert mesh2.sub_mesh_pattern(range(len(mesh2))) == mesh2 + assert mesh3.sub_mesh_pattern(range(len(mesh3))) == mesh3 + + mpatt = MeshPatt((0, 1), [(0, 0), (0, 1), (1, 0), (1, 1)]) + assert mpatt.sub_mesh_pattern([0]) == MeshPatt((0,), [(0, 0)]) + assert mpatt.sub_mesh_pattern([1]) == MeshPatt((0,)) + + mpatt = MeshPatt((0, 1, 2), [(0, 1), (1, 1), (1, 2), (2, 1), (2, 2), (3, 1)]) + mpatt.sub_mesh_pattern([0, 1]) == MeshPatt((0, 1), [(0, 1), (1, 1), (2, 1)]) + mpatt.sub_mesh_pattern([0, 2]) == MeshPatt((0, 1), []) + + +def test_flip_diagonal(): + assert MeshPatt(Perm(), []).flip_diagonal() == MeshPatt(Perm(), []) + assert MeshPatt(Perm((0,)), [(0, 1)]).flip_diagonal() == MeshPatt( + Perm((0,)), [(1, 0)] + ) + + +def test_rotate(): + assert MeshPatt(Perm(), []).rotate() == MeshPatt(Perm(), []) + assert MeshPatt(Perm(), []).rotate(-1) == MeshPatt(Perm(), []) + assert MeshPatt(Perm(), []).rotate(2) == MeshPatt(Perm(), []) + + assert MeshPatt(Perm((0,)), []).rotate() == MeshPatt(Perm((0,)), []) + assert MeshPatt(Perm((0,)), []).rotate(-1) == MeshPatt(Perm((0,)), []) + assert MeshPatt(Perm((0,)), []).rotate(2) == MeshPatt(Perm((0,)), []) + + for _ in range(50): + mpatt = MeshPatt.random(6) + assert mpatt.rotate().rotate() == mpatt.rotate(2) + assert mpatt.rotate(-1).rotate(-1) == mpatt.rotate(2) + assert mpatt.rotate().rotate(-1) == mpatt + assert mpatt.rotate(-1).rotate() == mpatt + assert mpatt.rotate(2).rotate(2) == mpatt + + +def test_shade(): + assert MeshPatt().shade((0, 0)).is_shaded((0, 0)) + + newshad = [(1, 2), (3, 3), (4, 4)] + mesh1shaded = mesh1.shade(*newshad) + for shading in shad1: + assert mesh1shaded.is_shaded(shading) + for shading in newshad: + assert mesh1shaded.is_shaded((shading)) + + newshad = [(2, 2), (1, 1), (0, 2), (1, 2), (3, 3), (4, 4)] + mesh2shaded = mesh2.shade(*newshad) + for shading in shad2: + assert mesh2shaded.is_shaded(shading) + for shading in newshad: + assert mesh2shaded.is_shaded(shading) + + +def test_occurrences_in_perm(): + assert sorted( + MeshPatt(Perm((1, 0, 2)), [(1, 2), (2, 2), (2, 3)]).occurrences_in( + Perm((3, 1, 0, 2, 4)) + ) + ) == [(0, 1, 4), (0, 2, 4), (0, 3, 4), (1, 2, 3)] + # 102 is not as there is a larger point between 02 + assert sorted( + MeshPatt(Perm((1, 0, 2)), [(1, 2), (2, 2), (2, 3)]).occurrences_in( + Perm((1, 0, 3, 2)) + ) + ) == [(0, 1, 2)] + assert list( + MeshPatt( + Perm((1, 0, 2)), [(0, 1), (0, 2), (1, 0), (1, 1), (2, 1), (2, 2)] + ).occurrences_in(Perm((2, 3, 1, 0, 4))) + )[0] == (0, 2, 4) + assert sorted( + MeshPatt(Perm((1, 3, 0, 2)), [(0, 1), (0, 3)]).occurrences_in( + Perm((0, 2, 5, 4, 1, 3)) + ) + ) == [(1, 2, 4, 5), (1, 3, 4, 5)] + assert sorted( + MeshPatt(Perm((2, 1, 0, 3)), [(0, 3)]).occurrences_in(Perm((2, 5, 1, 3, 0, 4))) + ) == [(0, 2, 4, 5)] + assert sorted( + MeshPatt(Perm((3, 2, 1, 0)), [(0, 0), (0, 1), (0, 2)]).occurrences_in( + Perm((4, 3, 0, 2, 1, 5)) + ) + ) == [(0, 1, 3, 4)] + assert sorted( + MeshPatt(Perm((1, 3, 2, 0)), [(0, 0), (0, 2)]).occurrences_in( + Perm((1, 2, 5, 3, 0, 4)) + ) + ) == [(0, 2, 3, 4), (1, 2, 3, 4)] + assert sorted( + MeshPatt(Perm((2, 3, 0, 1)), [(0, 0), (0, 2)]).occurrences_in( + Perm((3, 4, 5, 1, 0, 2)) + ) + ) == [(0, 1, 3, 5), (0, 1, 4, 5), (0, 2, 3, 5), (0, 2, 4, 5)] + assert sorted( + MeshPatt(Perm((1, 3, 0, 2)), [(0, 3)]).occurrences_in(Perm((2, 3, 5, 0, 4, 1))) + ) == [(0, 2, 3, 4), (1, 2, 3, 4)] + assert sorted( + MeshPatt(Perm((2, 0, 3, 1)), [(0, 0), (0, 3)]).occurrences_in( + Perm((4, 1, 2, 5, 3, 0)) + ) + ) == [(0, 1, 3, 4), (0, 2, 3, 4)] + assert sorted( + MeshPatt(Perm((2, 3, 0, 1)), [(0, 0), (0, 2)]).occurrences_in( + Perm((3, 5, 1, 2, 0, 4)) + ) + ) == [(0, 1, 2, 3)] + assert sorted( + MeshPatt(Perm((2, 0, 1, 3)), [(0, 0), (0, 3)]).occurrences_in( + Perm((2, 0, 4, 1, 5, 3)) + ) + ) == [(0, 1, 3, 4), (0, 1, 3, 5)] + assert sorted( + MeshPatt(Perm((2, 0, 3, 1)), [(0, 0), (0, 1), (0, 2)]).occurrences_in( + Perm((5, 2, 0, 3, 4, 1)) + ) + ) == [(1, 2, 3, 5), (1, 2, 4, 5)] + assert sorted( + MeshPatt(Perm((2, 1, 0, 3)), [(0, 3)]).occurrences_in(Perm((3, 2, 5, 0, 1, 4))) + ) == [(0, 1, 3, 5), (0, 1, 4, 5)] + + +def test_occurrences_in_mesh(): + assert sorted( + MeshPatt(Perm((1, 0, 2)), [(1, 2), (2, 2), (2, 3)]).occurrences_in( + MeshPatt( + Perm((3, 1, 0, 2, 4)), + [ + (0, 0), + (0, 1), + (0, 2), + (1, 4), + (2, 4), + (3, 3), + (3, 4), + (3, 5), + (4, 0), + (4, 3), + (4, 4), + (4, 5), + (5, 0), + ], + ) + ) + ) == [(0, 2, 4), (0, 3, 4)] + assert list( + MeshPatt(Perm((1, 0, 2)), [(0, 1), (0, 2), (1, 0), (2, 2)]).occurrences_in( + MeshPatt( + Perm((3, 1, 2, 0, 4)), + [ + (1, 0), + (0, 1), + (1, 1), + (2, 1), + (0, 2), + (1, 2), + (2, 2), + (3, 2), + (4, 2), + (0, 3), + (1, 3), + (2, 3), + (4, 3), + (0, 4), + (1, 4), + (2, 4), + (3, 4), + (4, 4), + ], + ) + ) + )[0] == (0, 1, 4) + assert sorted( + MeshPatt.unrank(Perm((0, 3, 1, 2)), 5).occurrences_in( + MeshPatt( + Perm((0, 3, 5, 4, 1, 2)), + [(0, 0), (0, 1), (0, 2), (0, 4), (0, 6), (1, 0), (1, 1), (1, 3)], + ) + ) + ) == [(0, 1, 4, 5), (0, 2, 4, 5), (0, 3, 4, 5)] + assert list( + MeshPatt(Perm((1, 3, 0, 2)), [(0, 1), (0, 3)]).occurrences_in( + MeshPatt( + Perm((0, 2, 5, 4, 1, 3)), + [ + (0, 2), + (0, 3), + (0, 4), + (0, 5), + (0, 6), + (1, 1), + (1, 2), + (1, 3), + (1, 4), + ], + ) + ) + )[0] == (1, 3, 4, 5) + assert sorted( + MeshPatt(Perm((2, 1, 0, 3)), [(0, 3)]).occurrences_in( + MeshPatt( + Perm((2, 5, 1, 3, 0, 4)), + [ + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (0, 5), + (1, 1), + (1, 2), + (1, 4), + (1, 5), + ], + ) + ) + ) == [(0, 2, 4, 5)] + assert sorted( + MeshPatt(Perm((3, 2, 1, 0)), [(0, 0), (0, 1), (0, 2)]).occurrences_in( + MeshPatt( + Perm((4, 3, 0, 2, 1, 5)), + [(0, 0), (0, 1), (0, 2), (0, 3), (0, 6), (1, 1), (1, 2), (1, 6)], + ) + ) + ) == [(0, 1, 3, 4)] + assert sorted( + MeshPatt(Perm((1, 3, 2, 0)), [(0, 0), (0, 2)]).occurrences_in( + MeshPatt( + Perm((1, 2, 5, 3, 0, 4)), + [(0, 0), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1), (1, 3)], + ) + ) + ) == [(0, 2, 3, 4), (1, 2, 3, 4)] + assert sorted( + MeshPatt(Perm((2, 3, 0, 1)), [(0, 0), (0, 2)]).occurrences_in( + MeshPatt( + Perm((3, 4, 5, 1, 0, 2)), + [ + (0, 0), + (0, 3), + (0, 4), + (0, 5), + (0, 6), + (1, 0), + (1, 1), + (1, 3), + (1, 6), + ], + ) + ) + ) == [(0, 1, 4, 5), (0, 2, 4, 5)] + assert sorted( + MeshPatt(Perm((1, 3, 0, 2)), [(0, 3)]).occurrences_in( + MeshPatt( + Perm((2, 3, 5, 0, 4, 1)), + [(0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (1, 0), (1, 1), (1, 5)], + ) + ) + ) == [(0, 2, 3, 4), (1, 2, 3, 4)] + assert sorted( + MeshPatt(Perm((2, 0, 3, 1)), [(0, 0), (0, 3)]).occurrences_in( + MeshPatt( + Perm((4, 1, 2, 5, 3, 0)), + [ + (0, 0), + (0, 1), + (0, 2), + (0, 4), + (0, 5), + (0, 6), + (1, 2), + (1, 3), + (1, 5), + ], + ) + ) + ) == [(0, 1, 3, 4), (0, 2, 3, 4)] + assert sorted( + MeshPatt(Perm((2, 3, 0, 1)), [(0, 0), (0, 2)]).occurrences_in( + MeshPatt( + Perm((3, 5, 1, 2, 0, 4)), + [(0, 0), (0, 1), (0, 3), (0, 4), (0, 5), (0, 6), (1, 0), (1, 5)], + ) + ) + ) == [(0, 1, 2, 3)] + assert sorted( + MeshPatt(Perm((2, 0, 1, 3)), [(0, 0), (0, 3)]).occurrences_in( + MeshPatt( + Perm((2, 0, 4, 1, 5, 3)), + [(0, 0), (0, 3), (0, 4), (0, 6), (1, 1), (1, 5)], + ) + ) + ) == [(0, 1, 3, 5)] + assert sorted( + MeshPatt(Perm((2, 0, 3, 1)), [(0, 0), (0, 1), (0, 2)]).occurrences_in( + MeshPatt( + Perm((5, 2, 0, 3, 4, 1)), + [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 5), + (0, 6), + (1, 0), + (1, 1), + (1, 2), + (1, 3), + (1, 5), + ], + ) + ) + ) == [(1, 2, 3, 5), (1, 2, 4, 5)] + assert sorted( + MeshPatt(Perm((2, 1, 0, 3)), [(0, 3)]).occurrences_in( + MeshPatt( + Perm((3, 2, 5, 0, 1, 4)), + [ + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (0, 5), + (0, 6), + (1, 0), + (1, 1), + (1, 2), + (1, 5), + ], + ) + ) + ) == [(0, 1, 3, 5), (0, 1, 4, 5)] + + +def test_all_syms(): + assert sorted(MeshPatt(Perm((2, 0, 1)), [(0, 3), (1, 1), (2, 0)]).all_syms()) == [ + MeshPatt(Perm((0, 2, 1)), [(0, 0), (1, 2), (2, 3)]), + MeshPatt(Perm((0, 2, 1)), [(0, 0), (2, 1), (3, 2)]), + MeshPatt(Perm((1, 0, 2)), [(0, 1), (1, 2), (3, 3)]), + MeshPatt(Perm((1, 0, 2)), [(1, 0), (2, 1), (3, 3)]), + MeshPatt(Perm((1, 2, 0)), [(0, 2), (1, 1), (3, 0)]), + MeshPatt(Perm((1, 2, 0)), [(1, 3), (2, 2), (3, 0)]), + MeshPatt(Perm((2, 0, 1)), [(0, 3), (1, 1), (2, 0)]), + MeshPatt(Perm((2, 0, 1)), [(0, 3), (2, 2), (3, 1)]), + ] + assert sorted( + MeshPatt( + Perm((1, 4, 3, 0, 2)), [(0, 0), (1, 4), (2, 0), (2, 1), (3, 0), (5, 2)] + ).all_syms() + ) == [ + MeshPatt( + Perm((1, 2, 4, 0, 3)), [(1, 1), (3, 5), (4, 2), (5, 0), (5, 2), (5, 3)] + ), + MeshPatt( + Perm((1, 4, 0, 2, 3)), [(0, 2), (0, 3), (0, 5), (1, 3), (2, 0), (4, 4)] + ), + MeshPatt( + Perm((1, 4, 3, 0, 2)), [(0, 0), (1, 4), (2, 0), (2, 1), (3, 0), (5, 2)] + ), + MeshPatt( + Perm((2, 0, 3, 4, 1)), [(0, 2), (2, 0), (3, 0), (3, 1), (4, 4), (5, 0)] + ), + MeshPatt( + Perm((2, 4, 1, 0, 3)), [(0, 3), (2, 5), (3, 4), (3, 5), (4, 1), (5, 5)] + ), + MeshPatt( + Perm((3, 0, 1, 4, 2)), [(0, 5), (1, 1), (2, 4), (2, 5), (3, 5), (5, 3)] + ), + MeshPatt( + Perm((3, 0, 4, 2, 1)), [(0, 0), (0, 2), (0, 3), (1, 2), (2, 5), (4, 1)] + ), + MeshPatt( + Perm((3, 2, 0, 4, 1)), [(1, 4), (3, 0), (4, 3), (5, 2), (5, 3), (5, 5)] + ), + ] + + +def test_add_point(): + mpatt = MeshPatt() + assert mpatt.add_point((0, 0)) == MeshPatt((0,)) + assert mpatt.add_point((0, 0), shade_dir=DIR_EAST) == MeshPatt( + (0,), [(1, 0), (1, 1)] + ) + assert mpatt.add_point((0, 0), shade_dir=DIR_NORTH) == MeshPatt( + (0,), [(0, 1), (1, 1)] + ) + assert mpatt.add_point((0, 0), shade_dir=DIR_WEST) == MeshPatt( + (0,), [(0, 0), (0, 1)] + ) + assert mpatt.add_point((0, 0), shade_dir=DIR_SOUTH) == MeshPatt( + (0,), [(0, 0), (1, 0)] + ) + + mpatt = MeshPatt((0, 1, 2), [(1, 0), (2, 1), (3, 2)]) + assert mpatt.add_point((2, 0), shade_dir=DIR_SOUTH) == MeshPatt( + (1, 2, 0, 3), [(1, 0), (1, 1), (2, 0), (2, 2), (3, 0), (3, 2), (4, 3)] + ) + assert mpatt.add_point((1, 2), shade_dir=DIR_WEST) == MeshPatt( + (0, 2, 1, 3), [(1, 0), (1, 2), (1, 3), (2, 0), (3, 1), (4, 2), (4, 3)] + ) + assert mesh1.add_point((2, 3), shade_dir=DIR_NORTH) == MeshPatt( + (0, 1, 3, 2, 4, 5), + [ + (0, 0), + (1, 0), + (2, 0), + (2, 1), + (2, 4), + (3, 0), + (3, 1), + (3, 4), + (4, 2), + (4, 3), + (4, 4), + (6, 0), + (6, 1), + (6, 2), + ], + ) + + with pytest.raises(TypeError): + mpatt.add_point(("a", (0,))) + with pytest.raises(AssertionError): + mpatt.add_point((2, 1)) + + +def test_add_increase(): + mpatt = MeshPatt() + assert mpatt.add_increase((0, 0)) == MeshPatt((0, 1)) + mpatt = MeshPatt((0, 1, 2), [(1, 0), (2, 1), (3, 2)]) + assert mpatt.add_increase((2, 0)) == MeshPatt( + Perm((2, 3, 0, 1, 4)), [(1, 2), (5, 4), (3, 3), (2, 3), (4, 3), (1, 0), (1, 1)] + ) + assert mpatt.add_increase((1, 2)) == MeshPatt( + (0, 2, 3, 1, 4), [(1, 0), (2, 0), (3, 0), (4, 1), (5, 2), (5, 3), (5, 4)] + ) + + +def test_add_decrease(): + mpatt = MeshPatt() + assert mpatt.add_decrease((0, 0)) == MeshPatt((1, 0)) + mpatt = MeshPatt((0, 1, 2), [(1, 0), (2, 1), (3, 2)]) + assert mpatt.add_decrease((2, 0)) == MeshPatt( + Perm((2, 3, 1, 0, 4)), [(1, 2), (5, 4), (3, 3), (2, 3), (4, 3), (1, 0), (1, 1)] + ) + assert mpatt.add_decrease((1, 2)) == MeshPatt( + (0, 3, 2, 1, 4), [(1, 0), (2, 0), (3, 0), (4, 1), (5, 2), (5, 3), (5, 4)] + ) + + +def test_contained_in(): + assert Perm([0, 1, 2]).contains( + MeshPatt(Perm([0, 1]), set([(1, 0), (1, 1), (1, 2)])) + ) + assert mesh_pattern.contained_in(perm1) + assert mesh_pattern.contained_in(perm2) + assert not (mesh_pattern.contained_in(perm3)) + assert mesh_pattern.contained_in(perm1, perm2) + assert not (mesh_pattern.contained_in(perm1, perm3)) + + +def test_meshpatt_contains_meshpatt(): + p_12 = Perm([0, 1]) + p_123 = Perm([0, 1, 2]) + small_mesh_patt = MeshPatt(p_12, [(1, 0), (1, 1), (1, 2)]) + big_mesh_patt_1st_col = MeshPatt(p_123, [(1, 0), (1, 1), (1, 2), (1, 3)]) + big_mesh_patt_2nd_col = MeshPatt(p_123, [(2, 0), (2, 1), (2, 2), (2, 3)]) + assert small_mesh_patt in big_mesh_patt_1st_col + assert small_mesh_patt in big_mesh_patt_2nd_col + assert big_mesh_patt_1st_col not in small_mesh_patt + assert big_mesh_patt_2nd_col not in small_mesh_patt + assert big_mesh_patt_1st_col not in big_mesh_patt_2nd_col + assert big_mesh_patt_2nd_col not in big_mesh_patt_1st_col + + +def test_contains(): + assert MeshPatt( + Perm((0, 2, 1, 3)), + [ + (0, 2), + (0, 4), + (1, 0), + (1, 4), + (2, 0), + (2, 1), + (2, 2), + (3, 0), + (3, 1), + (3, 3), + (3, 4), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + ], + ).contains(MeshPatt(Perm((0, 1)), [(1, 0), (2, 1)])) + assert MeshPatt( + Perm((0, 1)), [(0, 1), (1, 0), (1, 1), (1, 2), (2, 0), (2, 2)] + ).contains(MeshPatt(Perm((0, 1)), [(1, 2), (2, 0), (2, 2)])) + assert MeshPatt( + Perm((2, 1, 0, 3, 4, 5)), + [ + (0, 0), + (0, 2), + (1, 1), + (1, 2), + (1, 4), + (1, 5), + (2, 2), + (2, 3), + (2, 4), + (2, 5), + (2, 6), + (3, 1), + (3, 2), + (3, 5), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + (4, 5), + (4, 6), + (5, 0), + (5, 1), + (5, 2), + (5, 4), + (5, 6), + (6, 0), + (6, 1), + (6, 4), + (6, 6), + ], + ).contains(MeshPatt(Perm((0, 1)), [(2, 0), (2, 2)])) + assert MeshPatt( + Perm((2, 4, 1, 5, 3, 0)), + [ + (0, 4), + (0, 5), + (0, 6), + (1, 2), + (1, 3), + (1, 4), + (1, 5), + (1, 6), + (2, 0), + (2, 1), + (2, 4), + (2, 5), + (2, 6), + (3, 3), + (3, 6), + (4, 0), + (4, 2), + (4, 3), + (4, 4), + (4, 5), + (4, 6), + (5, 1), + (5, 2), + (5, 4), + (5, 5), + (6, 1), + (6, 3), + (6, 6), + ], + ).contains(MeshPatt(Perm((1, 0)), [(0, 1)])) + assert MeshPatt( + Perm((1, 0, 2, 3, 4)), + [ + (0, 0), + (0, 3), + (0, 4), + (0, 5), + (1, 0), + (1, 4), + (2, 0), + (2, 1), + (2, 2), + (2, 4), + (2, 5), + (3, 1), + (3, 2), + (3, 3), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + (5, 2), + (5, 3), + (5, 4), + (5, 5), + ], + ).contains(MeshPatt(Perm((0, 1)), [(0, 0)])) + assert MeshPatt( + Perm((0, 3, 1, 2)), + [(0, 1), (0, 4), (1, 0), (1, 1), (2, 1), (3, 2), (4, 2), (4, 4)], + ).contains(MeshPatt(Perm((0, 1)), [(1, 1), (2, 1)])) + assert MeshPatt( + Perm((1, 0, 2, 3)), + [ + (0, 1), + (0, 4), + (1, 1), + (1, 2), + (1, 3), + (1, 4), + (2, 0), + (2, 2), + (3, 0), + (3, 1), + (3, 3), + (3, 4), + (4, 1), + (4, 2), + ], + ).contains(MeshPatt(Perm((1, 0)), [(0, 1), (1, 1)])) + assert MeshPatt( + Perm((0, 1, 3, 2)), + [ + (0, 3), + (1, 1), + (2, 0), + (2, 1), + (2, 4), + (3, 0), + (3, 4), + (4, 2), + (4, 3), + (4, 4), + ], + ).contains(MeshPatt(Perm((1, 0)), [(2, 2)])) + assert MeshPatt( + Perm((1, 0)), [(0, 0), (0, 1), (0, 2), (1, 1), (2, 0), (2, 1), (2, 2)] + ).contains(MeshPatt(Perm((1, 0)), [(1, 1), (2, 0), (2, 1)])) + assert MeshPatt( + Perm((0, 1)), [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 2)] + ).contains(MeshPatt(Perm((0, 1)), [(0, 0), (0, 1), (1, 0), (2, 2)])) + + +def test_avoids(): + assert MeshPatt( + Perm((1, 3, 2, 0)), + [ + (0, 1), + (0, 2), + (1, 1), + (1, 3), + (1, 4), + (2, 0), + (2, 1), + (2, 3), + (2, 4), + (3, 0), + (3, 2), + (3, 3), + (4, 0), + ], + ).avoids(MeshPatt(Perm((2, 1, 0)), [(0, 1), (0, 2), (1, 3), (2, 3), (3, 1)])) + assert MeshPatt( + Perm((2, 1, 0, 3)), + [ + (0, 0), + (0, 1), + (0, 2), + (1, 0), + (1, 1), + (2, 0), + (2, 1), + (2, 3), + (3, 1), + (3, 2), + (3, 3), + (4, 1), + ], + ).avoids(MeshPatt(Perm((1, 0)), [(0, 0), (0, 1), (0, 2), (1, 0), (2, 1), (2, 2)])) + assert MeshPatt( + Perm((0, 1)), [(0, 1), (1, 0), (1, 1), (1, 2), (2, 1), (2, 2)] + ).avoids( + MeshPatt( + Perm((2, 0, 1)), + [(0, 1), (0, 3), (1, 1), (2, 1), (2, 2), (2, 3), (3, 0), (3, 1)], + ) + ) + assert MeshPatt( + Perm((0, 1, 3, 2)), + [ + (0, 2), + (1, 1), + (1, 3), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (2, 4), + (3, 0), + (3, 1), + (3, 2), + (4, 0), + (4, 4), + ], + ).avoids(MeshPatt(Perm((1, 0)), [(1, 2), (2, 1), (2, 2)])) + assert MeshPatt(Perm((0, 1)), [(0, 1), (0, 2), (1, 1), (2, 1)]).avoids( + MeshPatt( + Perm((2, 3, 1, 0)), + [ + (0, 3), + (0, 4), + (1, 0), + (1, 1), + (1, 4), + (2, 0), + (2, 4), + (3, 1), + (3, 2), + (3, 3), + (3, 4), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + ], + ) + ) + assert MeshPatt( + Perm((3, 2, 0, 1)), + [(0, 0), (1, 4), (3, 0), (3, 3), (4, 0), (4, 2), (4, 3), (4, 4)], + ).avoids( + MeshPatt( + Perm((1, 2, 3, 0)), + [ + (0, 0), + (0, 4), + (1, 1), + (2, 1), + (2, 3), + (2, 4), + (3, 0), + (3, 1), + (3, 2), + (3, 4), + (4, 1), + (4, 2), + (4, 3), + ], + ) + ) + assert MeshPatt(Perm((0, 1)), [(0, 1), (1, 0), (1, 1), (2, 1)]).avoids( + MeshPatt(Perm((0, 1, 2)), [(0, 0), (1, 1), (3, 0), (3, 1)]) + ) + assert MeshPatt(Perm((0, 2, 1)), [(0, 1), (3, 0), (3, 1), (3, 2)]).avoids( + MeshPatt( + Perm((0, 1, 2)), + [ + (0, 0), + (0, 1), + (0, 3), + (1, 0), + (1, 1), + (1, 2), + (1, 3), + (2, 1), + (2, 3), + (3, 1), + (3, 3), + ], + ) + ) + assert MeshPatt( + Perm((3, 0, 2, 1)), + [ + (0, 0), + (0, 1), + (0, 4), + (1, 0), + (1, 1), + (1, 2), + (1, 3), + (1, 4), + (2, 1), + (3, 0), + (3, 2), + (3, 3), + (3, 4), + (4, 0), + (4, 3), + (4, 4), + ], + ).avoids( + MeshPatt(Perm((1, 0, 2)), [(0, 1), (0, 3), (1, 1), (1, 3), (3, 2), (3, 3)]) + ) + assert MeshPatt( + Perm((0, 2, 1)), [(0, 0), (0, 1), (0, 2), (1, 0), (2, 0), (3, 1), (3, 2)] + ).avoids( + MeshPatt( + Perm((2, 1, 0)), + [(0, 2), (0, 3), (1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (3, 0), (3, 3)], + ) + ) + assert MeshPatt(Perm((1, 0)), [(0, 1), (0, 2), (1, 0)]).avoids( + MeshPatt(Perm((1, 0)), [(0, 0), (0, 1), (0, 2), (1, 0), (2, 0), (2, 1)]), + MeshPatt(Perm((0, 1)), [(1, 0), (1, 2), (2, 1), (2, 2)]), + MeshPatt(Perm((0, 1)), [(0, 2), (1, 2), (2, 1)]), + MeshPatt( + Perm((0, 1)), [(0, 0), (0, 1), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1)] + ), + MeshPatt( + Perm((1, 2, 3, 0)), + [ + (0, 0), + (0, 4), + (1, 0), + (1, 3), + (2, 0), + (2, 1), + (2, 3), + (2, 4), + (3, 3), + (3, 4), + (4, 1), + (4, 4), + ], + ), + MeshPatt( + Perm((2, 0, 1, 3)), + [ + (0, 0), + (1, 1), + (1, 4), + (2, 2), + (3, 0), + (3, 1), + (3, 3), + (4, 0), + (4, 1), + (4, 2), + (4, 4), + ], + ), + MeshPatt( + Perm((0, 1)), [(0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] + ), + MeshPatt(Perm((1, 2, 0)), [(0, 2), (1, 2), (2, 2), (2, 3)]), + MeshPatt( + Perm((2, 0, 1)), [(0, 0), (0, 1), (1, 0), (1, 3), (2, 0), (2, 3), (3, 1)] + ), + MeshPatt(Perm((1, 0, 2)), [(1, 1), (1, 2), (1, 3), (2, 1), (2, 2)]), + ) + + +def test_avoided_by(): + assert not (mesh_pattern.avoided_by(perm1)) + assert not (mesh_pattern.avoided_by(perm2)) + assert mesh_pattern.avoided_by(perm3) + assert mesh_pattern.avoided_by(perm4) + assert mesh_pattern.avoided_by(perm3, perm4) + assert not (mesh_pattern.avoided_by(perm4, perm1, perm3)) + + +def test_count_occurrences_in(): + assert mesh_pattern.count_occurrences_in(perm1) == 8 + assert mesh_pattern.count_occurrences_in(perm2) == 12 + assert mesh_pattern.count_occurrences_in(perm3) == 0 + assert mesh_pattern.count_occurrences_in(perm4) == 0 + assert mesh_pattern.count_occurrences_in(perm5) == 2 + + +def test_is_shaded(): + mpatt = MeshPatt(Perm(), ((0, 0),)) + assert mpatt.is_shaded((0, 0)) + mpatt = MeshPatt( + (0, 2, 1), [(0, 0), (0, 2), (1, 1), (1, 3), (2, 0), (2, 2), (3, 1), (3, 3)] + ) + for (x0, y0) in itertools.combinations(range(len(mpatt) + 1), 2): + for (x1, y1) in itertools.combinations(range(len(mpatt) + 1), 2): + if x0 > x1 or y0 > y1: + with pytest.raises(AssertionError): + mpatt.is_shaded((x0, y0), (x1, y1)) + elif x0 == x1 and y0 == y1: + if (x0 + y0) % 2 == 0: + assert mpatt.is_shaded((x0, y0)) + assert mpatt.is_shaded((x0, y0), (x1, y1)) + else: + assert not mpatt.is_shaded((x0, y0), (x1, y1)) + with pytest.raises(AssertionError): + mpatt.is_shaded((4, 0)) + with pytest.raises(AssertionError): + mpatt.is_shaded((0, 4)) + with pytest.raises(AssertionError): + mpatt.is_shaded((-1, 2)) + with pytest.raises(AssertionError): + mpatt.is_shaded((0, 0), (0, 4)) + + +def test_is_pointfree(): + mpatt = MeshPatt((0, 1), [(0, 0), (0, 1), (1, 0), (1, 1)]) + assert not mpatt.is_pointfree((0, 0), (1, 1)) + assert not mpatt.is_pointfree((1, 1), (2, 2)) + assert not mpatt.is_pointfree((0, 1), (2, 2)) + assert mpatt.is_pointfree((0, 1), (2, 1)) + assert not mpatt.is_pointfree((0, 1), (2, 2)) + mpatt = MeshPatt((0, 1, 2), [(0, 1), (1, 1), (1, 2), (2, 1), (2, 2), (3, 1)]) + assert not mpatt.is_pointfree((1, 1), (2, 2)) + with pytest.raises(AssertionError): + mpatt.is_shaded((-1, 0), (0, 4)) + with pytest.raises(AssertionError): + mpatt.is_shaded((0, -1), (0, 4)) + with pytest.raises(AssertionError): + mpatt.is_shaded((0, 0), (-1, 4)) + with pytest.raises(AssertionError): + mpatt.is_shaded((0, 0), (0, -1)) + with pytest.raises(AssertionError): + mpatt.is_shaded((100, 0), (0, 4)) + with pytest.raises(AssertionError): + mpatt.is_shaded((0, 100), (0, 4)) + with pytest.raises(AssertionError): + mpatt.is_shaded((0, 0), (100, 4)) + with pytest.raises(AssertionError): + mpatt.is_shaded((0, 0), (0, 100)) + with pytest.raises(AssertionError): + mpatt.is_shaded((1, 0), (0, 4)) + with pytest.raises(AssertionError): + mpatt.is_shaded((0, 3), (0, 2)) + + +def test_can_shade(): + assert not (MeshPatt().can_shade((0, 0))) + assert MeshPatt(Perm((0,))).can_shade((0, 0)) == [0] + assert MeshPatt(Perm((0,))).can_shade((1, 0)) == [0] + assert not (MeshPatt(Perm((0,)), [(0, 0)]).can_shade((0, 0))) + assert not (MeshPatt(Perm((0,)), [(0, 0)]).can_shade((1, 1))) + assert not mesh_pattern.can_shade((1, 1)) + assert not mesh_pattern.can_shade((3, 2)) + assert not mesh_pattern.can_shade((1, 2)) + assert mesh_pattern.can_shade((0, 1)) == [1] + mpatt = MeshPatt(Perm((1, 2, 0)), [(2, 2), (3, 0), (3, 2), (3, 3)]) + assert list(sorted(mpatt.can_shade((1, 2)))) == [1, 2] + assert mpatt.can_shade((3, 1)) == [0] + + +def test_can_simul_shade(): + assert not (MeshPatt().can_simul_shade((0, 0), (0, 0))) + assert MeshPatt(Perm((0,))).can_simul_shade((0, 0), (0, 1)) == [0] + assert not (MeshPatt(Perm((0, 1, 2))).can_simul_shade((2, 0), (2, 2))) + assert not mesh1.can_simul_shade((2, 2), (3, 2)) + assert not mesh1.can_simul_shade((1, 2), (2, 2)) + assert not mesh2.can_simul_shade((3, 3), (4, 3)) + assert not mesh2.can_simul_shade((3, 4), (4, 4)) + assert not mesh1.can_simul_shade((4, 4), (4, 5)) + assert not mesh1.can_simul_shade((4, 5), (5, 5)) + assert mesh1.can_simul_shade((5, 4), (5, 5)) == [4] + + mpatt = MeshPatt( + Perm((2, 3, 0, 1)), [(0, 4), (1, 3), (2, 1), (2, 2), (3, 4), (4, 0), (4, 4)] + ) + assert mpatt.can_simul_shade((4, 1), (4, 2)) == [1] + mpatt = MeshPatt( + Perm((1, 2, 0)), [(0, 2), (0, 3), (1, 1), (2, 0), (2, 1), (3, 2), (3, 3)] + ) + assert mpatt.can_simul_shade((2, 2), (2, 3)) == [2] + assert not mpatt.can_simul_shade((1, 2), (2, 2)) + assert not mpatt.can_simul_shade((1, 2), (1, 3)) + + +def test_shadable_boxes(): + assert not (MeshPatt().shadable_boxes()) + assert len(MeshPatt(Perm((0,))).shadable_boxes()[0]) == 8 + assert len(MeshPatt(Perm((0, 1))).shadable_boxes()[0]) == 8 + assert len(MeshPatt(Perm((0, 1))).shadable_boxes()[1]) == 8 + + +def test_get_perm(): + assert MeshPatt(Perm((3, 0, 2, 1)), ((1, 0),)).get_perm() == Perm((3, 0, 2, 1)) + + +def test_non_pointless_boxes(): + assert MeshPatt(Perm()).non_pointless_boxes() == set() + assert MeshPatt((0,)).non_pointless_boxes() == set([(0, 0), (0, 1), (1, 0), (1, 1)]) + assert MeshPatt( + (0, 1, 2), [(0, 0), (1, 2,), (0, 2), (2, 0), (2, 1)] + ).non_pointless_boxes() == set( + [(0, 0), (0, 1), (1, 0), (1, 1), (1, 2), (2, 1), (2, 2), (2, 3), (3, 2), (3, 3)] + ) + print(mesh2) + assert mesh2.non_pointless_boxes() == set( + [ + (0, 1), + (0, 2), + (1, 1), + (1, 2), + (1, 3), + (2, 2), + (2, 3), + (2, 4), + (2, 5), + (3, 3), + (3, 4), + (3, 5), + (4, 0), + (4, 1), + (4, 3), + (4, 4), + (5, 0), + (5, 1), + ] + ) + + +def test_has_anchered_point(): + assert not any(MeshPatt(Perm()).has_anchored_point()) + assert all(MeshPatt(Perm(), [(0, 0)]).has_anchored_point()) + assert not any(MeshPatt(Perm.random(5)).has_anchored_point()) + right, top, left, bottom = MeshPatt( + (0, 1, 2), [(0, i) for i in range(4)] + ).has_anchored_point() + assert left and not (right or top or bottom) + right, top, left, bottom = MeshPatt( + (0, 1, 2), [(3, i) for i in range(4)] + ).has_anchored_point() + assert right and not (left or top or bottom) + right, top, left, bottom = MeshPatt( + (0, 1, 2), [(i, 0) for i in range(4)] + ).has_anchored_point() + assert bottom and not (left or top or right) + right, top, left, bottom = MeshPatt( + (0, 1, 2), [(i, 3) for i in range(4)] + ).has_anchored_point() + assert top and not (left or bottom or right) + + mpatt = MeshPatt.random(5) + leftshad = [(0, i) for i in range(len(mpatt) + 1)] + rightshad = [(len(mpatt), i) for i in range(len(mpatt) + 1)] + upshad = [(i, len(mpatt)) for i in range(len(mpatt) + 1)] + bottomshad = [(i, 0) for i in range(len(mpatt) + 1)] + right, top, left, bottom = mpatt.shade(*leftshad).has_anchored_point() + assert left + right, top, left, bottom = mpatt.shade(*rightshad).has_anchored_point() + assert right + right, top, left, bottom = mpatt.shade(*upshad).has_anchored_point() + assert top + right, top, left, bottom = mpatt.shade(*bottomshad).has_anchored_point() + assert bottom + + +def test_rank(): + assert ( + MeshPatt(Perm((0, 1)), [(0, 1), (1, 2), (2, 1), (2, 0), (2, 2), (1, 1)]).rank() + == 498 + ) + assert ( + MeshPatt( + Perm((1, 0, 2)), + [(3, 2), (0, 0), (2, 3), (1, 0), (0, 1), (1, 2), (3, 3), (3, 1), (2, 0)], + ).rank() + == 59731 + ) + assert ( + MeshPatt( + Perm((0, 3, 2, 1)), [(0, 1), (4, 4), (1, 4), (2, 3), (4, 2), (4, 1), (0, 2)] + ).rank() + == 23077382 + ) + + pattern = Perm([1, 2]) + mesh = MeshPatt(pattern, set([(0, 1), (0, 2), (1, 2), (2, 0), (2, 1)])) + assert mesh.rank() == 230 + + pattern = Perm([3, 1, 2]) + mesh = MeshPatt( + pattern, + set( + [ + (0, 0), + (0, 1), + (0, 2), + (1, 0), + (1, 2), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (3, 0), + ] + ), + ) + assert mesh.rank() == 8023 + + assert mesh1.rank() == 0b111000000001100000011000001000001 + + for _ in range(50): + mesh = MeshPatt.random(random.randint(0, 20)) + assert MeshPatt.unrank(mesh.pattern, mesh.rank()) == mesh + + +def test_unrank(): + assert MeshPatt.unrank(Perm((0, 1)), 498) == MeshPatt( + Perm((0, 1)), frozenset({(0, 1), (1, 2), (2, 1), (2, 0), (2, 2), (1, 1)}) + ) + assert MeshPatt.unrank(Perm((1, 0, 2)), 59731) == MeshPatt( + Perm((1, 0, 2)), + frozenset( + {(3, 2), (0, 0), (2, 3), (1, 0), (0, 1), (1, 2), (3, 3), (3, 1), (2, 0)} + ), + ) + assert MeshPatt.unrank(Perm((0, 3, 2, 1)), 23077382) == MeshPatt( + Perm((0, 3, 2, 1)), + frozenset({(0, 1), (4, 4), (1, 4), (2, 3), (4, 2), (4, 1), (0, 2)}), + ) + + pattern = Perm([1, 2]) + mesh = MeshPatt(pattern, set([(0, 1), (0, 2), (1, 2), (2, 0), (2, 1)])) + assert MeshPatt.unrank(pattern, 230) == mesh + + pattern = Perm([3, 1, 2]) + mesh = MeshPatt( + pattern, + set( + [ + (0, 0), + (0, 1), + (0, 2), + (1, 0), + (1, 2), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (3, 0), + ] + ), + ) + assert MeshPatt.unrank(pattern, 8023) == mesh + + assert MeshPatt.unrank(patt1, 0b111000000001100000011000001000001) == mesh1 + + for length in range(20): + m = MeshPatt.unrank(Perm.random(length), 0) + assert not m.shading + for length in range(20): + m = MeshPatt.unrank(Perm.random(length), 2 ** ((length + 1) ** 2) - 1) + assert len(m.shading) == (length + 1) ** 2 + + with pytest.raises(AssertionError): + MeshPatt.unrank([1, 2, 3], -1) + with pytest.raises(AssertionError): + MeshPatt.unrank(Perm([1]), 16) + with pytest.raises(AssertionError): + MeshPatt.unrank(Perm([1]), -1) + with pytest.raises(AssertionError): + MeshPatt.unrank(Perm.random(10), 2 ** ((10 + 1) ** 2) + 1) + + +def test_random(): + assert MeshPatt.random(0) in set( + [MeshPatt(Perm(()), []), MeshPatt(Perm(()), [(0, 0)])] + ) + assert MeshPatt.random(1) in set( + MeshPatt.unrank(Perm((0,)), i) for i in range(0, 16) + ) + assert MeshPatt.random(2) in ( + set(MeshPatt.unrank(Perm((0, 1)), i) for i in range(0, 512)) + | set(MeshPatt.unrank(Perm((1, 0)), i) for i in range(0, 512)) + ) + for length in range(3, 20): + assert len(MeshPatt.random(length)) == length + + +def test_len(): + assert len(mesh1) == 5 + assert len(mesh2) == 5 + assert len(mesh3) == 4 + assert len(MeshPatt()) == 0 + assert len(MeshPatt((1,))) == 1 + for _ in range(20): + length = random.randint(0, 20) + m = MeshPatt( + Perm.random(length), + random.sample( + [(i, j) for i in range(length + 1) for j in range(length + 1)], k=length + ), + ) + assert len(m) == length + + +def test_bool(): + assert mesh1 + assert mesh2 + assert mesh3 + assert MeshPatt(Perm((1,))) + assert MeshPatt((), set([(0, 0)])) + assert MeshPatt(Perm([0, 1, 2, 3]), ()) + assert MeshPatt(Perm([0, 1, 2, 3]), ((0, 0),)) + assert MeshPatt(Perm([0]), ()) + assert not (MeshPatt(Perm([]), ())) + assert not (MeshPatt(Perm(), ())) + + +def test_eq(): + assert not (mesh1 == mesh2) + assert not (mesh1 == mesh3) + assert not (mesh2 == mesh1) + assert not (mesh2 == mesh3) + assert not (mesh3 == mesh1) + assert not (mesh3 == mesh2) + mesh1copy = MeshPatt(patt1, shad1) + mesh2copy = MeshPatt(patt2, shad2) + mesh3copy = MeshPatt(patt3, shad3) + assert mesh1 == mesh1copy + assert mesh2 == mesh2copy + assert mesh3 == mesh3copy + assert mesh1copy == mesh1copy + assert mesh2copy == mesh2copy + assert mesh3copy == mesh3copy + + +def test_ordering(): + # mesh1, mesh2 and mesh3 have different underlying patterns + mp1 = MeshPatt(perm1, shad1) + mp2 = MeshPatt(perm1, shad2) + mp3 = MeshPatt(perm1, shad3) + + # Ensure some ordering of mesh patts (all should be comparable) + assert mesh1 > mesh2 or mesh1 <= mesh2 + assert mp1 < mp2 or mp1 >= mp2 + + # Describing lexicographical ordering of mesh patts: + # 1) order first by underlying patts + # 2) order second by shading [Python list ordering by smallest indices] + assert mesh3 < mesh1 < mesh2 # case 1) + assert mp1 < mp2 < mp3 # case 2) + + +def test_to_tikz(): + assert ( + mesh_pattern.to_tikz() + == "\\begin{tikzpicture}[scale=.3,baseline=(current bounding box.center)]\n" + " \\foreach \\x in {1,...,4} {\n" + " \\draw[ultra thin] (\\x,0)--(\\x,5); %vline\n" + " \\draw[ultra thin] (0,\\x)--(5,\\x); %hline\n" + " }\n" + " \\fill[pattern color = black!75, pattern=north east lines] (0, 0) rectangle +(1,1);\n" # noqa: E501 + " \\fill[pattern color = black!75, pattern=north east lines] (0, 4) rectangle +(1,1);\n" # noqa: E501 + " \\fill[pattern color = black!75, pattern=north east lines] (2, 1) rectangle +(1,1);\n" # noqa: E501 + " \\fill[pattern color = black!75, pattern=north east lines] (2, 2) rectangle +(1,1);\n" # noqa: E501 + " \\fill[pattern color = black!75, pattern=north east lines] (3, 3) rectangle +(1,1);\n" # noqa: E501 + " \\fill[pattern color = black!75, pattern=north east lines] (4, 0) rectangle +(1,1);\n" # noqa: E501 + " \\fill[pattern color = black!75, pattern=north east lines] (4, 1) rectangle +(1,1);\n" # noqa: E501 + " \\fill[pattern color = black!75, pattern=north east lines] (4, 2) rectangle +(1,1);\n" # noqa: E501 + " \\draw[fill=black] (1,2) circle (5pt);\n" + " \\draw[fill=black] (2,4) circle (5pt);\n" + " \\draw[fill=black] (3,3) circle (5pt);\n" + " \\draw[fill=black] (4,1) circle (5pt);\n" + "\\end{tikzpicture}" + ) + + +def test_ascii_plot(): + assert ( + mesh_pattern.ascii_plot() + == "▒| | | |\n-+-●-+-+-\n | | |▒|\n-+-+-●-+-\n | |▒| |▒\n-●-+-+-+-\n" + " | |▒| |▒\n-+-+-+-●-\n▒| | | |▒" + ) + assert ( + MeshPatt( + Perm((0, 2, 1)), + [(0, 0), (0, 3), (1, 1), (2, 0), (2, 1), (2, 2), (2, 3), (3, 1), (3, 3)], + ).ascii_plot() + == "▒| |▒|▒\n-+-●-+-\n | |▒|\n-+-+-●-\n |▒|▒|▒\n-●-+-+-\n▒| |▒|" + ) + assert ( + MeshPatt( + Perm((3, 2, 1, 0)), + [ + (1, 0), + (1, 2), + (1, 3), + (2, 2), + (2, 3), + (2, 4), + (3, 0), + (3, 2), + (3, 4), + (4, 0), + (4, 1), + (4, 2), + (4, 4), + ], + ).ascii_plot() + == " | |▒|▒|▒\n-●-+-+-+-\n |▒|▒| |\n-+-●-+-+-\n |▒|▒|▒|▒\n-+-+-●-+-\n " + "| | | |▒\n-+-+-+-●-\n |▒| |▒|▒" + ) + assert ( + MeshPatt( + Perm((2, 5, 3, 4, 0, 1)), + [ + (0, 1), + (0, 2), + (0, 3), + (0, 5), + (2, 0), + (2, 2), + (2, 4), + (2, 5), + (3, 0), + (3, 2), + (3, 3), + (3, 5), + (3, 6), + (4, 1), + (4, 4), + (4, 6), + (5, 1), + (5, 6), + (6, 0), + (6, 2), + (6, 4), + ], + ).ascii_plot() + == " | | |▒|▒|▒|\n-+-●-+-+-+-+-\n▒| |▒|▒| | |\n-+-+-+-●-+-+-\n | |▒|" + " |▒| |▒\n-+-+-●-+-+-+-\n▒| | |▒| | |\n-●-+-+-+-+-+-\n▒| |▒|▒| | " + "|▒\n-+-+-+-+-+-●-\n▒| | | |▒|▒|\n-+-+-+-+-●-+-\n | |▒|▒| | |▒" + ) + assert ( + MeshPatt( + Perm((1, 4, 5, 2, 0, 3)), + [ + (0, 0), + (0, 2), + (0, 4), + (1, 2), + (1, 4), + (1, 6), + (2, 1), + (2, 2), + (2, 5), + (2, 6), + (3, 2), + (3, 3), + (3, 4), + (3, 6), + (4, 1), + (4, 4), + (5, 3), + (5, 4), + (5, 5), + (5, 6), + (6, 4), + (6, 6), + ], + ).ascii_plot() + == " |▒|▒|▒| |▒|▒\n-+-+-●-+-+-+-\n | |▒| | |▒|\n-+-●-+-+-+-+-\n▒|▒| " + "|▒|▒|▒|▒\n-+-+-+-+-+-●-\n | | |▒| |▒|\n-+-+-+-●-+-+-\n▒|▒|▒|▒| |" + " |\n-●-+-+-+-+-+-\n | |▒| |▒| |\n-+-+-+-+-●-+-\n▒| | | | | |" + ) + assert ( + MeshPatt( + Perm((3, 2, 0, 1)), + [ + (0, 2), + (1, 0), + (1, 2), + (2, 1), + (2, 2), + (2, 3), + (3, 0), + (3, 3), + (3, 4), + (4, 1), + (4, 4), + ], + ).ascii_plot() + == " | | |▒|▒\n-●-+-+-+-\n | |▒|▒|\n-+-●-+-+-\n▒|▒|▒| |\n-+-+-+-●-\n |" + " |▒| |▒\n-+-+-●-+-\n |▒| |▒|" + ) + assert ( + MeshPatt( + Perm((1, 2, 0, 3)), + [ + (0, 0), + (0, 2), + (0, 3), + (0, 4), + (1, 0), + (1, 1), + (1, 3), + (1, 4), + (2, 0), + (2, 1), + (2, 3), + (3, 0), + (3, 1), + (3, 2), + (3, 3), + (3, 4), + (4, 0), + ], + ).ascii_plot() + == "▒|▒| |▒|\n-+-+-+-●-\n▒|▒|▒|▒|\n-+-●-+-+-\n▒| | |▒|\n-●-+-+-+-\n |▒" + "|▒|▒|\n-+-+-●-+-\n▒|▒|▒|▒|▒" + ) + + +def test_gen_meshpatts(): + assert list(MeshPatt.of_length(0)) == [MeshPatt(), MeshPatt((), [(0, 0)])] + assert len(list(MeshPatt.of_length(1))) == 2 ** 4 + for i in range(2, 4): + patt = Perm.random(i) + len_i = list(MeshPatt.of_length(i, patt)) + assert (len(set(len_i))) == 2 ** ((i + 1) ** 2) + for mpatt in len_i: + assert mpatt.pattern == patt + len_i = list(MeshPatt.of_length(2)) + assert len(set(len_i)) == (2 ** ((2 + 1) ** 2) * factorial(2)) + patt = Perm.random(3) + len_i = list(MeshPatt.of_length(3, patt)) + assert (len(set(len_i))) == 2 ** ((3 + 1) ** 2) diff --git a/tests/test_perm.py b/tests/patterns/test_perm.py similarity index 56% rename from tests/test_perm.py rename to tests/patterns/test_perm.py index 110edc29..d9994729 100644 --- a/tests/test_perm.py +++ b/tests/patterns/test_perm.py @@ -1,34 +1,45 @@ +import itertools import random +from collections import deque import pytest -from permuta import Perm, PermSet +from permuta import Perm -def test_init(): +def test_from_iterable_validated(): with pytest.raises(ValueError): - Perm([0, 1, 1], check=True) + Perm.from_iterable_validated([0, 1, 1]) with pytest.raises(ValueError): - Perm([1, 0, 1], check=True) + Perm.from_iterable_validated([1, 0, 1]) with pytest.raises(ValueError): - Perm([0, 0], check=True) + Perm.from_iterable_validated([0, 0]) with pytest.raises(ValueError): - Perm([1], check=True) + Perm.from_iterable_validated([1]) with pytest.raises(ValueError): - Perm((1,), check=True) + Perm.from_iterable_validated((1,)) with pytest.raises(TypeError): - Perm(101, check=True) + Perm.from_iterable_validated(101) with pytest.raises(TypeError): - Perm(-234, check=True) + Perm.from_iterable_validated(-234) with pytest.raises(TypeError): - Perm(None, check=True) + Perm.from_iterable_validated(None) with pytest.raises(TypeError): - Perm([0.1, 0.2, 0.3], check=True) - Perm(check=True) - Perm([], check=True) - Perm((0,), check=True) - Perm([0], check=True) - Perm([3, 0, 2, 1], check=True) - Perm(set([0, 1, 2]), check=True) + Perm.from_iterable_validated([0.1, 0.2, 0.3]) + with pytest.raises(ValueError): + Perm.from_iterable_validated("132") + with pytest.raises(ValueError): + Perm.from_iterable_validated("134") + with pytest.raises(ValueError): + Perm.from_iterable_validated("112") + with pytest.raises(ValueError): + Perm.from_iterable_validated("5") + try: + Perm.from_iterable_validated(()) + Perm.from_iterable_validated((1, 0)) + Perm.from_iterable_validated([5, 1, 0, 2, 4, 3]) + Perm.from_iterable_validated("1302") + except (ValueError, TypeError): + pytest.fail("Exception raised when it shouldn't") def test_to_standard(): @@ -48,6 +59,19 @@ def gen(perm): assert perm == Perm.to_standard(perm) assert perm == Perm.to_standard(gen(perm)) + assert Perm.to_standard("aaa") == Perm((0, 1, 2)) + assert Perm.to_standard("cba") == Perm((2, 1, 0)) + + +def test_from_integer(): + assert Perm.from_integer(123) == Perm((0, 1, 2)) + assert Perm.from_integer(321) == Perm((2, 1, 0)) + assert Perm.from_integer(201) == Perm((2, 0, 1)) + assert Perm.from_integer(0) == Perm((0,)) + assert Perm.from_integer(1) == Perm((0,)) + assert Perm.from_integer(123456789) == Perm((0, 1, 2, 3, 4, 5, 6, 7, 8)) + assert Perm.from_integer(9876543210) == Perm((9, 8, 7, 6, 5, 4, 3, 2, 1, 0)) + def test_from_string(): assert Perm.from_string("203451") == Perm((2, 0, 3, 4, 5, 1)) @@ -65,6 +89,39 @@ def test_str_representation(): str(Perm((0, 11, 1, 10, 2, 9, 3, 8, 4, 7, 5, 6))) == "(0)(11)(1)(10)(2)(9)(3)(8)(4)(7)(5)(6)" ) + assert str(Perm(())) == "\u03B5" + + +def test_of_length(): + assert list(Perm.of_length(3)) == [ + Perm((0, 1, 2)), + Perm((0, 2, 1)), + Perm((1, 0, 2)), + Perm((1, 2, 0)), + Perm((2, 0, 1)), + Perm((2, 1, 0)), + ] + assert list(Perm.of_length(0)) == [Perm(())] + assert list(Perm.of_length(1)) == [Perm((0,))] + + +def test_first(): + assert list(Perm.first(3)) == [Perm(()), Perm((0,)), Perm((0, 1))] + assert list(Perm.first(0)) == [] + assert list(Perm.first(1)) == [Perm(())] + assert list(Perm.first(11)) == [ + Perm(()), + Perm((0,)), + Perm((0, 1)), + Perm((1, 0)), + Perm((0, 1, 2)), + Perm((0, 2, 1)), + Perm((1, 0, 2)), + Perm((1, 2, 0)), + Perm((2, 0, 1)), + Perm((2, 1, 0)), + Perm((0, 1, 2, 3)), + ] def test_one_based(): @@ -88,12 +145,7 @@ def test_random(): for _ in range(10): perm = Perm.random(length) assert len(perm) == length - Perm(perm) - - -def test_monotone_increasing(): - for length in range(11): - assert Perm.monotone_increasing(length) == Perm(range(length)) + assert set(perm) == set(range(length)) def test_monotone_decreasing(): @@ -102,30 +154,17 @@ def test_monotone_decreasing(): def test_unrank(): - assert Perm.unrank(0) == Perm() - assert Perm.unrank(1) == Perm((0,)) - assert Perm.unrank(2) == Perm((0, 1)) - assert Perm.unrank(3) == Perm((1, 0)) - assert Perm.unrank(4) == Perm((0, 1, 2)) - assert Perm.unrank(5) == Perm((0, 2, 1)) - assert Perm.unrank(6) == Perm((1, 0, 2)) - assert Perm.unrank(10) == Perm((0, 1, 2, 3)) - amount = 1 + 1 + 2 + 6 + 24 - assert Perm.unrank(amount) == Perm((0, 1, 2, 3, 4)) - amount = (1 + 1 + 2 + 6 + 24 + 120) - 1 - assert Perm.unrank(amount) == Perm((4, 3, 2, 1, 0)) + counter = 0 + for n in range(8): + for i, perm in enumerate(itertools.permutations(range(n))): + assert Perm(perm) == Perm.unrank(i, n) == Perm.unrank(counter) + counter += 1 with pytest.raises(AssertionError): Perm.unrank(-1) with pytest.raises(AssertionError): Perm.unrank(6, 3) -def test_unrank_2(): - length = 7 - for number, perm in enumerate(PermSet(length)): - assert Perm.unrank(number, length) == perm - - def test_contained_in(): def generate_contained(n, perm): for i in range(len(perm), n): @@ -150,7 +189,7 @@ def generate_contained(n, perm): for i in range(100): n = random.randint(0, 4) - patt = PermSet(n).random() + patt = Perm.random(n) perm = generate_contained(random.randint(n, 8), list(patt)) assert patt.contained_in(perm) assert perm.contains(patt) @@ -168,6 +207,348 @@ def generate_contained(n, perm): assert not Perm([0, 1, 2, 3]).contained_in(Perm([4, 7, 5, 1, 6, 2, 3, 0])) +def test_left_floor_and_ceiling(): + iterable = Perm([4, 5, 1, 2, 3, 6]) + expected = [ + (-1, -1), # 4 + (0, -1), # 5 + (-1, 0), # 1 + (2, 0), # 2 + (3, 0), # 3 + (1, -1), # 6 + ] + index = 0 + for fac in iterable.left_floor_and_ceiling(): + assert fac == expected[index] + index += 1 + + iterable = Perm([4, 1, 2, 5, 3]) + expected = [(-1, -1), (-1, 0), (1, 0), (0, -1), (2, 0)] # 4 # 1 # 2 # 5 # 3 + index = 0 + for fac in iterable.left_floor_and_ceiling(): + assert fac == expected[index] + index += 1 + + iterable = Perm([1, 2, 3]) + expected = [(-1, -1), (0, -1), (1, -1)] # 1 # 2 # 3 + index = 0 + for fac in iterable.left_floor_and_ceiling(): + assert fac == expected[index] + index += 1 + + iterable = Perm([3, 2, 1]) + expected = [(-1, -1), (-1, 0), (-1, 1)] # 3 # 2 # 1 + index = 0 + for fac in iterable.left_floor_and_ceiling(): + assert fac == expected[index] + index += 1 + assert list(Perm(()).left_floor_and_ceiling()) == [] + assert list(Perm((0,)).left_floor_and_ceiling()) == [(-1, -1)] + assert list(Perm((0, 1)).left_floor_and_ceiling()) == [(-1, -1), (0, -1)] + assert list(Perm((1, 0)).left_floor_and_ceiling()) == [(-1, -1), (-1, 0)] + assert list(Perm((2, 1, 0, 3)).left_floor_and_ceiling()) == [ + (-1, -1), + (-1, 0), + (-1, 1), + (0, -1), + ] + assert list(Perm((3, 2, 0, 1)).left_floor_and_ceiling()) == [ + (-1, -1), + (-1, 0), + (-1, 1), + (2, 1), + ] + assert list(Perm((0, 4, 1, 2, 3)).left_floor_and_ceiling()) == [ + (-1, -1), + (0, -1), + (0, 1), + (2, 1), + (3, 1), + ] + assert list(Perm((1, 2, 4, 3, 0)).left_floor_and_ceiling()) == [ + (-1, -1), + (0, -1), + (1, -1), + (1, 2), + (-1, 0), + ] + assert list(Perm((3, 0, 2, 5, 1, 4)).left_floor_and_ceiling()) == [ + (-1, -1), + (-1, 0), + (1, 0), + (0, -1), + (1, 2), + (0, 3), + ] + assert list(Perm((2, 5, 0, 3, 4, 1)).left_floor_and_ceiling()) == [ + (-1, -1), + (0, -1), + (-1, 0), + (0, 1), + (3, 1), + (2, 0), + ] + assert list(Perm((6, 5, 0, 2, 1, 3, 4)).left_floor_and_ceiling()) == [ + (-1, -1), + (-1, 0), + (-1, 1), + (2, 1), + (2, 3), + (3, 1), + (5, 1), + ] + assert list(Perm((0, 2, 6, 5, 4, 1, 3)).left_floor_and_ceiling()) == [ + (-1, -1), + (0, -1), + (1, -1), + (1, 2), + (1, 3), + (0, 1), + (1, 4), + ] + assert list(Perm((3, 6, 2, 7, 5, 0, 1, 4)).left_floor_and_ceiling()) == [ + (-1, -1), + (0, -1), + (-1, 0), + (1, -1), + (0, 1), + (-1, 2), + (5, 2), + (0, 4), + ] + assert list(Perm((2, 0, 5, 6, 1, 7, 4, 3)).left_floor_and_ceiling()) == [ + (-1, -1), + (-1, 0), + (0, -1), + (2, -1), + (1, 0), + (3, -1), + (0, 2), + (0, 6), + ] + assert list(Perm((8, 5, 0, 7, 1, 2, 4, 6, 3)).left_floor_and_ceiling()) == [ + (-1, -1), + (-1, 0), + (-1, 1), + (1, 0), + (2, 1), + (4, 1), + (5, 1), + (1, 3), + (5, 6), + ] + assert list(Perm((0, 3, 7, 8, 2, 6, 4, 5, 1)).left_floor_and_ceiling()) == [ + (-1, -1), + (0, -1), + (1, -1), + (2, -1), + (0, 1), + (1, 2), + (1, 5), + (6, 5), + (0, 4), + ] + assert list(Perm((5, 3, 4, 1, 7, 9, 0, 6, 8, 2)).left_floor_and_ceiling()) == [ + (-1, -1), + (-1, 0), + (1, 0), + (-1, 1), + (0, -1), + (4, -1), + (-1, 3), + (0, 4), + (4, 5), + (3, 1), + ] + assert list(Perm((9, 2, 0, 5, 3, 6, 1, 8, 7, 4)).left_floor_and_ceiling()) == [ + (-1, -1), + (-1, 0), + (-1, 1), + (1, 0), + (1, 3), + (3, 0), + (2, 1), + (5, 0), + (5, 7), + (4, 3), + ] + assert list(Perm((9, 0, 6, 2, 3, 1, 4, 5, 7, 8, 10)).left_floor_and_ceiling()) == [ + (-1, -1), + (-1, 0), + (1, 0), + (1, 2), + (3, 2), + (1, 3), + (4, 2), + (6, 2), + (2, 0), + (8, 0), + (0, -1), + ] + assert list(Perm((10, 0, 1, 2, 3, 5, 7, 6, 4, 8, 9)).left_floor_and_ceiling()) == [ + (-1, -1), + (-1, 0), + (1, 0), + (2, 0), + (3, 0), + (4, 0), + (5, 0), + (5, 6), + (4, 5), + (6, 0), + (9, 0), + ] + assert list( + Perm((4, 10, 11, 3, 9, 7, 2, 8, 6, 1, 5, 0)).left_floor_and_ceiling() + ) == [ + (-1, -1), + (0, -1), + (1, -1), + (-1, 0), + (0, 1), + (0, 4), + (-1, 3), + (5, 4), + (0, 5), + (-1, 6), + (0, 8), + (-1, 9), + ] + assert list( + Perm((3, 4, 11, 8, 9, 10, 0, 2, 5, 1, 7, 6)).left_floor_and_ceiling() + ) == [ + (-1, -1), + (0, -1), + (1, -1), + (1, 2), + (3, 2), + (4, 2), + (-1, 0), + (6, 0), + (1, 3), + (6, 7), + (8, 3), + (8, 10), + ] + assert list( + Perm((2, 9, 8, 4, 1, 5, 10, 12, 7, 3, 11, 0, 6)).left_floor_and_ceiling() + ) == [ + (-1, -1), + (0, -1), + (0, 1), + (0, 2), + (-1, 0), + (3, 2), + (1, -1), + (6, -1), + (5, 2), + (0, 3), + (6, 7), + (-1, 4), + (5, 8), + ] + assert list( + Perm((7, 9, 6, 3, 0, 2, 4, 12, 1, 5, 11, 10, 8)).left_floor_and_ceiling() + ) == [ + (-1, -1), + (0, -1), + (-1, 0), + (-1, 2), + (-1, 3), + (4, 3), + (3, 2), + (1, -1), + (4, 5), + (6, 2), + (1, 7), + (1, 10), + (0, 1), + ] + assert list( + Perm((8, 3, 4, 12, 5, 13, 2, 6, 1, 7, 9, 10, 11, 0)).left_floor_and_ceiling() + ) == [ + (-1, -1), + (-1, 0), + (1, 0), + (0, -1), + (2, 0), + (3, -1), + (-1, 1), + (4, 0), + (-1, 6), + (7, 0), + (0, 3), + (10, 3), + (11, 3), + (-1, 8), + ] + assert list( + Perm((9, 12, 8, 11, 6, 13, 1, 5, 4, 3, 10, 2, 0, 7)).left_floor_and_ceiling() + ) == [ + (-1, -1), + (0, -1), + (-1, 0), + (0, 1), + (-1, 2), + (1, -1), + (-1, 4), + (6, 4), + (6, 7), + (6, 8), + (0, 3), + (6, 9), + (-1, 6), + (4, 2), + ] + assert list( + Perm( + (11, 4, 12, 1, 8, 9, 10, 14, 3, 2, 13, 0, 5, 6, 7) + ).left_floor_and_ceiling() + ) == [ + (-1, -1), + (-1, 0), + (0, -1), + (-1, 1), + (1, 0), + (4, 0), + (5, 0), + (2, -1), + (3, 1), + (3, 8), + (2, 7), + (-1, 3), + (1, 4), + (12, 4), + (13, 4), + ] + assert list( + Perm( + (8, 3, 16, 0, 17, 5, 9, 10, 6, 19, 18, 2, 11, 14, 12, 13, 15, 4, 7, 1) + ).left_floor_and_ceiling() + ) == [ + (-1, -1), + (-1, 0), + (0, -1), + (-1, 1), + (2, -1), + (1, 0), + (0, 2), + (6, 2), + (5, 0), + (4, -1), + (4, 9), + (3, 1), + (7, 2), + (12, 2), + (12, 13), + (14, 13), + (13, 2), + (1, 5), + (8, 0), + (3, 11), + ] + + def test_count_occurrences_in(): assert Perm([]).count_occurrences_in(Perm([4, 1, 2, 3, 0])) == 1 assert Perm([0]).count_occurrences_in(Perm([4, 1, 2, 3, 0])) == 5 @@ -224,9 +605,9 @@ def test_occurrences_in(): def test_apply(): - with pytest.raises(ValueError): + with pytest.raises(AssertionError): Perm((1, 2, 4, 0, 3, 5)).apply(Perm((0, 2, 1, 3))) - with pytest.raises(ValueError): + with pytest.raises(AssertionError): Perm().apply(Perm((0,))) for i in range(100): @@ -292,6 +673,32 @@ def test_skew_sum(): p5 - "hahaha" +def test_maximal_decreasing_run(): + assert Perm((1, 3, 2, 5, 0, 4)).maximal_decreasing_run() == 2 + assert Perm((0, 1)).maximal_decreasing_run() == 1 + assert Perm((3, 1, 0, 4, 2, 5)).maximal_decreasing_run() == 1 + assert Perm((0,)).maximal_decreasing_run() == 1 + assert Perm((1, 2, 4, 0, 3, 5)).maximal_decreasing_run() == 1 + assert Perm((2, 1, 0)).maximal_decreasing_run() == 3 + assert Perm((5, 2, 4, 1, 0, 3)).maximal_decreasing_run() == 3 + assert Perm((5, 3, 2, 4, 0, 1)).maximal_decreasing_run() == 2 + assert Perm((5, 0, 4, 1, 3, 2)).maximal_decreasing_run() == 4 + assert Perm((2, 0, 5, 4, 1, 3)).maximal_decreasing_run() == 3 + assert Perm((1, 2, 0)).maximal_decreasing_run() == 1 + assert Perm((1, 0)).maximal_decreasing_run() == 2 + assert Perm((2, 0, 1)).maximal_decreasing_run() == 2 + assert Perm((0, 3, 4, 1, 2)).maximal_decreasing_run() == 1 + assert Perm(()).maximal_decreasing_run() == 0 + assert Perm((0, 3, 5, 4, 1, 2)).maximal_decreasing_run() == 2 + assert Perm((0, 1, 2)).maximal_decreasing_run() == 1 + assert Perm((4, 3, 5, 2, 1, 0)).maximal_decreasing_run() == 1 + assert Perm((1, 0, 2)).maximal_decreasing_run() == 1 + assert Perm((0, 2, 1)).maximal_decreasing_run() == 2 + for i in range(1, 100): + assert Perm.monotone_increasing(i).maximal_decreasing_run() == 1 + assert Perm.monotone_decreasing(i).maximal_decreasing_run() == i + + def test_compose(): p0 = Perm() @@ -315,30 +722,32 @@ def test_compose(): assert p4 * p5 * p6 == p7 assert p5 * p6 * p7 * p4 == p7 - with pytest.raises(TypeError): + with pytest.raises(AssertionError): p1.compose(None) with pytest.raises(TypeError): p1 * None - with pytest.raises(TypeError): + with pytest.raises(AssertionError): p2.compose(p2, None) - with pytest.raises(TypeError): + with pytest.raises(AssertionError): p3.compose(1237) with pytest.raises(TypeError): p5 * ("hahaha") with pytest.raises(TypeError): p5 * (p1,) - with pytest.raises(ValueError): + with pytest.raises(AssertionError): p1.compose(p0) - with pytest.raises(ValueError): + with pytest.raises(AssertionError): p0.compose(p5) - with pytest.raises(ValueError): + with pytest.raises(AssertionError): p2.compose(p3, p0) - with pytest.raises(ValueError): + with pytest.raises(AssertionError): p4.compose(p5, p6, p1) def test_insert(): + assert Perm(()).insert() == Perm((0,)) + assert Perm(()).insert(0) == Perm((0,)) assert Perm((0, 1)).insert() == Perm((0, 1, 2)) assert Perm((0, 1)).insert(0) == Perm((2, 0, 1)) assert Perm((0, 1)).insert(1) == Perm((0, 2, 1)) @@ -349,16 +758,11 @@ def test_insert(): assert Perm((0, 3, 1, 2)).insert(3, 3) == Perm((0, 4, 1, 3, 2)) assert Perm((0, 3, 1, 2)).insert(1, 0) == Perm((1, 0, 4, 2, 3)) - with pytest.raises(TypeError): - Perm((2, 1, 0, 3)).insert(new_element="hehe") - with pytest.raises(TypeError): - Perm((2, 1, 0, 3)).insert(3, "hehe") - - with pytest.raises(ValueError): + with pytest.raises(AssertionError): Perm((2, 1, 0, 3)).insert(2, 100) - with pytest.raises(ValueError): + with pytest.raises(AssertionError): Perm((2, 1, 0, 3)).insert(0, 5) - with pytest.raises(ValueError): + with pytest.raises(AssertionError): Perm((2, 1, 0, 3)).insert(3, -3) @@ -368,13 +772,14 @@ def test_remove(): assert Perm((3, 0, 1, 2)).remove(0) == Perm((0, 1, 2)) assert Perm((2, 0, 1)).remove(2) == Perm((1, 0)) assert Perm((0,)).remove(0) == Perm() - assert Perm((3, 0, 1, 2)).remove(3) == Perm((2, 0, 1)) assert Perm((0, 3, 1, 4, 2)).remove(3) == Perm((0, 3, 1, 2)) assert Perm((0, 4, 1, 3, 2)).remove(4) == Perm((0, 3, 1, 2)) assert Perm((1, 0, 4, 2, 3)).remove(0) == Perm((0, 3, 1, 2)) assert Perm((1, 0, 4, 2, 3)).remove() == Perm((1, 0, 2, 3)) assert Perm((0, 4, 1, 3, 2)).remove() == Perm((0, 1, 3, 2)) + with pytest.raises(IndexError): + assert Perm((0, 5, 4, 3, 2, 1)).remove(100) def test_remove_element(): @@ -388,21 +793,15 @@ def test_remove_element(): assert Perm((1, 0, 4, 2, 3)).remove_element() == Perm((1, 0, 2, 3)) assert Perm((0, 4, 1, 3, 2)).remove_element() == Perm((0, 1, 3, 2)) - with pytest.raises(ValueError): + with pytest.raises(AssertionError): Perm((1, 0, 4, 2, 3)).remove_element(5) - with pytest.raises(ValueError): + with pytest.raises(AssertionError): Perm((1, 0, 4, 2, 3)).remove_element(51) - with pytest.raises(ValueError): + with pytest.raises(AssertionError): Perm((1, 0, 4, 2, 3)).remove_element(-5) - with pytest.raises(TypeError): - Perm((2, 1, 0, 3)).remove_element("ble") - with pytest.raises(TypeError): - Perm((2, 1, 0, 3)).remove_element([0]) - def test_inflate(): - # TODO: make proper tests when the Perm.inflate has been implemented. assert Perm((0, 1)).inflate([Perm((1, 0)), Perm((2, 1, 0))]) == Perm( (1, 0, 4, 3, 2) ) @@ -411,55 +810,43 @@ def test_inflate(): ) assert Perm((0, 1)).inflate([Perm(), Perm()]) == Perm() - with pytest.raises(TypeError): - Perm((0, 1, 2, 3)).inflate(237) - with pytest.raises(TypeError): - Perm((0, 1, 2, 3)).inflate("hehe") - -# TODO: The following three functions have yet to be implemented -@pytest.mark.xfail def test_contract_inc_bonds(): assert Perm().contract_inc_bonds() == Perm() assert Perm((0,)).contract_inc_bonds() == Perm((0,)) - assert Perm((1, 2)).contract_inc_bonds() == Perm((0,)) - assert Perm((1, 2, 3)).contract_inc_bonds() == Perm((0,)) - assert Perm((3, 2, 1)).contract_inc_bonds() == Perm((3, 2, 1)) - + assert Perm((0, 1)).contract_inc_bonds() == Perm((0,)) + assert Perm((0, 1, 2)).contract_inc_bonds() == Perm((0,)) + assert Perm((2, 1, 0)).contract_inc_bonds() == Perm((2, 1, 0)) assert Perm((0, 3, 1, 4, 2)).contract_inc_bonds() == Perm((0, 3, 1, 4, 2)) assert Perm((1, 0, 4, 2, 3)).contract_inc_bonds() == Perm((1, 0, 3, 2)) assert Perm((1, 0, 2, 3, 4)).contract_inc_bonds() == Perm((1, 0, 2)) - assert Perm((0, 4, 1, 2, 3)).contract_inc_bonds() == Perm((0, 2, 2)) + assert Perm((0, 4, 1, 2, 3)).contract_inc_bonds() == Perm((0, 2, 1)) -@pytest.mark.xfail def test_contract_dec_bonds(): assert Perm().contract_dec_bonds() == Perm() assert Perm((0,)).contract_dec_bonds() == Perm((0,)) assert Perm((2, 1)).contract_dec_bonds() == Perm((0,)) - assert Perm((3, 2, 1)).contract_dec_bonds() == Perm((0,)) - assert Perm((1, 2, 3)).contract_inc_bonds() == Perm((1, 2, 3)) - + assert Perm((2, 1, 0)).contract_dec_bonds() == Perm((0,)) + assert Perm((0, 1, 2)).contract_dec_bonds() == Perm((0, 1, 2)) assert Perm((0, 3, 1, 4, 2)).contract_dec_bonds() == Perm((0, 3, 1, 4, 2)) assert Perm((0, 4, 3, 2, 1)).contract_dec_bonds() == Perm((0, 1)) assert Perm((1, 0, 4, 2, 3)).contract_dec_bonds() == Perm((0, 3, 1, 2)) assert Perm((0, 4, 1, 3, 2)).contract_dec_bonds() == Perm((0, 3, 1, 2)) -@pytest.mark.xfail def test_contract_bonds(): assert Perm().contract_bonds() == Perm() assert Perm((0,)).contract_bonds() == Perm((0,)) - assert Perm((2, 1)).contract_bonds() == Perm((0,)) - assert Perm((1, 2)).contract_bonds() == Perm((0,)) - assert Perm((3, 2, 1)).contract_bonds() == Perm((0,)) - assert Perm((1, 2, 3)).contract_bonds() == Perm((0,)) - + assert Perm((1, 0)).contract_bonds() == Perm((0,)) + assert Perm((0, 1)).contract_bonds() == Perm((0,)) + assert Perm((2, 1, 0)).contract_bonds() == Perm((0,)) + assert Perm((0, 1, 2)).contract_bonds() == Perm((0,)) assert Perm((0, 3, 1, 4, 2)).contract_bonds() == Perm((0, 3, 1, 4, 2)) assert Perm((0, 4, 3, 2, 1)).contract_bonds() == Perm((0, 1)) assert Perm((1, 0, 4, 2, 3)).contract_bonds() == Perm((0, 2, 1)) assert Perm((0, 4, 1, 3, 2)).contract_bonds() == Perm((0, 3, 1, 2)) - assert Perm((1, 0, 2, 3, 4)).contract_bonds() == Perm((0, 1, 2)) + assert Perm((1, 0, 2, 3, 4)).contract_bonds() == Perm((0, 1)) def test_inverse(): @@ -494,28 +881,23 @@ def test_reverse_complement(): def test_rotate_right(): for i in range(10): - assert Perm(range(i - 1, -1, -1)) == Perm(range(i)).rotate_right() - assert Perm([2, 1, 3, 4, 0, 5, 6]) == Perm([6, 5, 3, 2, 0, 1, 4]).rotate_right() - assert ( - Perm([4, 5, 3, 1, 7, 0, 2, 6]) == Perm([4, 7, 1, 0, 2, 6, 3, 5]).rotate_right() - ) - assert Perm([0, 1, 2]) == Perm([2, 1, 0]).rotate_right(5) - assert Perm([4, 5, 3, 1, 0, 2]) == Perm([4, 5, 3, 1, 0, 2]).rotate_right(4) + assert Perm(range(i - 1, -1, -1)) == Perm(range(i)).rotate() + assert Perm([2, 1, 3, 4, 0, 5, 6]) == Perm([6, 5, 3, 2, 0, 1, 4]).rotate() + assert Perm([4, 5, 3, 1, 7, 0, 2, 6]) == Perm([4, 7, 1, 0, 2, 6, 3, 5]).rotate() + assert Perm([0, 1, 2]) == Perm([2, 1, 0]).rotate(5) + assert Perm([4, 5, 3, 1, 0, 2]) == Perm([4, 5, 3, 1, 0, 2]).rotate(4) def test_rotate_left(): for i in range(10): - assert Perm(list(range(i - 1, -1, -1))) == Perm(range(i)).rotate_right() - assert Perm([6, 5, 3, 2, 0, 1, 4]).rotate_left() == Perm( - [6, 5, 3, 2, 0, 1, 4] - ).rotate_right(3) - assert Perm([6, 5, 3, 2, 0, 1, 4]).rotate_left() == Perm( - [6, 5, 3, 2, 0, 1, 4] - ).rotate_right(-1) - assert Perm([4, 7, 1, 0, 2, 6, 3, 5]).rotate_left() == Perm( + assert Perm(list(range(i - 1, -1, -1))) == Perm(range(i)).rotate() + assert Perm([6, 5, 3, 2, 0, 1, 4]).rotate(-1) == Perm([6, 5, 3, 2, 0, 1, 4]).rotate( + 3 + ) + assert Perm([4, 7, 1, 0, 2, 6, 3, 5]).rotate(-1) == Perm( [4, 7, 1, 0, 2, 6, 3, 5] - ).rotate_right(7) - assert Perm([]).rotate_left() == Perm([]).rotate_left(123) + ).rotate(7) + assert Perm([]).rotate(-1) == Perm([]).rotate(-123) def test_shift_left(): @@ -587,10 +969,10 @@ def test_flip_antidiagonal(): assert perm.reverse().complement().inverse() == perm.flip_antidiagonal() -def test__rotate_180(): +def test_rotate_180(): for _ in range(100): perm = Perm.random(random.randint(0, 20)) - assert perm._rotate_180() == perm.rotate().rotate() + assert perm.rotate(times=2) == perm.rotate().rotate() def test_all_syms(): @@ -601,13 +983,57 @@ def test_all_syms(): Perm((1, 2, 0)), Perm((2, 0, 1)), ] - - -@pytest.mark.xfail -def test_is_representative(): - # TODO: write proper tests when the function is working - assert Perm().is_representative() - assert Perm((0,)).is_representative() + assert set(Perm((2, 1, 5, 3, 4, 0)).all_syms()) == { + Perm((3, 4, 0, 2, 1, 5)), + Perm((0, 4, 5, 2, 1, 3)), + Perm((2, 4, 3, 0, 1, 5)), + Perm((5, 1, 0, 3, 4, 2)), + Perm((0, 4, 3, 5, 1, 2)), + Perm((3, 1, 2, 5, 4, 0)), + Perm((2, 1, 5, 3, 4, 0)), + Perm((5, 1, 2, 0, 4, 3)), + } + assert set(Perm((1, 3, 4, 0, 2)).all_syms()) == { + Perm((3, 1, 0, 4, 2)), + Perm((1, 4, 0, 3, 2)), + Perm((1, 3, 4, 0, 2)), + Perm((2, 3, 0, 4, 1)), + Perm((2, 0, 4, 3, 1)), + Perm((2, 1, 4, 0, 3)), + Perm((2, 4, 0, 1, 3)), + Perm((3, 0, 4, 1, 2)), + } + assert set(Perm((2, 1, 0)).all_syms()) == {Perm((2, 1, 0)), Perm((0, 1, 2))} + assert set(Perm((0, 2, 1, 3, 4)).all_syms()) == { + Perm((4, 2, 3, 1, 0)), + Perm((0, 1, 3, 2, 4)), + Perm((0, 2, 1, 3, 4)), + Perm((4, 3, 1, 2, 0)), + } + assert set(Perm((2, 1, 0)).all_syms()) == {Perm((2, 1, 0)), Perm((0, 1, 2))} + assert set(Perm(()).all_syms()) == {Perm(())} + assert set(Perm((0,)).all_syms()) == {Perm((0,))} + assert set(Perm((3, 1, 6, 8, 5, 7, 4, 0, 2)).all_syms()) == { + Perm((5, 7, 2, 0, 3, 1, 4, 8, 6)), + Perm((2, 0, 4, 7, 5, 8, 6, 1, 3)), + Perm((3, 5, 2, 4, 6, 0, 8, 1, 7)), + Perm((6, 8, 4, 1, 3, 0, 2, 7, 5)), + Perm((3, 1, 6, 8, 5, 7, 4, 0, 2)), + Perm((5, 3, 6, 4, 2, 8, 0, 7, 1)), + Perm((7, 1, 8, 0, 6, 4, 2, 5, 3)), + Perm((1, 7, 0, 8, 2, 4, 6, 3, 5)), + } + assert set(Perm((6, 1, 3, 2, 5, 0, 4)).all_syms()) == { + Perm((4, 0, 5, 2, 3, 1, 6)), + Perm((6, 2, 0, 4, 3, 5, 1)), + Perm((2, 6, 1, 4, 3, 5, 0)), + Perm((0, 4, 6, 2, 3, 1, 5)), + Perm((6, 1, 3, 2, 5, 0, 4)), + Perm((1, 5, 3, 4, 0, 2, 6)), + Perm((0, 5, 3, 4, 1, 6, 2)), + Perm((5, 1, 3, 2, 6, 4, 0)), + } + assert set(Perm((1, 0)).all_syms()) == {Perm((1, 0)), Perm((0, 1))} def test_fixed_points(): @@ -618,6 +1044,36 @@ def test_fixed_points(): assert list(Perm((0, 1, 2, 3, 4, 5)).fixed_points()) == [0, 1, 2, 3, 4, 5] +def test_strong_fixed_points(): + assert list(Perm().strong_fixed_points()) == [] + assert list(Perm((5, 4, 3, 2, 1, 0)).strong_fixed_points()) == [] + assert list(Perm((4, 1, 0, 3, 2, 5)).strong_fixed_points()) == [5] + assert list(Perm((0, 1, 2, 3, 4, 5)).strong_fixed_points()) == [0, 1, 2, 3, 4, 5] + assert list(Perm((0, 1)).strong_fixed_points()) == [0, 1] + assert list(Perm((0,)).strong_fixed_points()) == [0] + assert list(Perm((2, 1, 0)).strong_fixed_points()) == [] + assert list(Perm((2, 0, 7, 4, 1, 5, 3, 6)).strong_fixed_points()) == [] + assert list(Perm((6, 4, 5, 3, 2, 0, 1)).strong_fixed_points()) == [] + assert list(Perm((4, 7, 2, 6, 0, 1, 5, 3)).strong_fixed_points()) == [] + assert list(Perm((0, 1, 2)).strong_fixed_points()) == [0, 1, 2] + assert list(Perm((1, 6, 4, 3, 2, 0, 7, 5)).strong_fixed_points()) == [] + assert list(Perm((4, 1, 2, 6, 3, 5, 0)).strong_fixed_points()) == [] + assert list(Perm((5, 1, 0, 7, 4, 2, 3, 6)).strong_fixed_points()) == [] + assert list(Perm((0, 6, 5, 4, 3, 2, 1, 7)).strong_fixed_points()) == [0, 7] + assert list(Perm((1, 4, 6, 2, 3, 5, 7, 0)).strong_fixed_points()) == [] + assert list(Perm((1, 3, 2, 0, 4, 6, 7, 5)).strong_fixed_points()) == [4] + assert list(Perm((0, 1, 5, 4, 6, 3, 2, 7)).strong_fixed_points()) == [0, 1, 7] + assert list(Perm((5, 6, 0, 2, 4, 3, 7, 1)).strong_fixed_points()) == [] + assert list(Perm((3, 6, 2, 5, 1, 4, 7, 0)).strong_fixed_points()) == [] + assert list(Perm((7, 6, 2, 3, 0, 1, 4, 5)).strong_fixed_points()) == [] + assert list(Perm((0, 2, 1)).strong_fixed_points()) == [0] + assert list(Perm((7, 3, 2, 4, 5, 1, 6, 0)).strong_fixed_points()) == [] + assert list(Perm((1, 0, 2)).strong_fixed_points()) == [2] + assert list(Perm((5, 3, 0, 6, 1, 4, 2, 7)).strong_fixed_points()) == [7] + assert list(Perm((7, 1, 2, 3, 4, 5, 6, 0)).strong_fixed_points()) == [] + assert list(Perm((1, 0, 2, 4, 3, 5, 7, 6)).strong_fixed_points()) == [2, 5] + + def test_count_fixed_points(): assert Perm().count_fixed_points() == 0 assert Perm((0, 2, 1)).count_fixed_points() == 1 @@ -722,15 +1178,6 @@ def test_count_descents(): assert Perm((3, 1, 4, 5, 0, 7, 6, 2)).count_descents() == 4 -def test_ascents(): - assert list(Perm().ascents()) == [] - assert list(Perm((0, 1, 2, 3)).ascents()) == [0, 1, 2] - assert list(Perm((3, 2, 1, 0)).ascents()) == [] - assert list(Perm((2, 1, 0, 4, 3, 5)).ascents()) == [2, 4] - assert list(Perm((1, 2, 3, 0, 6, 5, 4)).ascents()) == [0, 1, 3] - assert list(Perm((3, 1, 4, 5, 0, 7, 6, 2)).ascents()) == [1, 2, 4] - - def test_ascent_set(): assert Perm().ascent_set() == [] assert Perm((0, 1, 2, 3)).ascent_set() == [0, 1, 2] @@ -837,12 +1284,12 @@ def test_order(): def test_ltrmin(): - assert Perm().ltrmin() == [] - assert Perm((0,)).ltrmin() == [0] - assert Perm((2, 4, 3, 0, 1)).ltrmin() == [0, 3] + assert list(Perm().ltrmin()) == [] + assert list(Perm((0,)).ltrmin()) == [0] + assert list(Perm((2, 4, 3, 0, 1)).ltrmin()) == [0, 3] for _ in range(100): perm = Perm.random(random.randint(2, 20)) - ltrmin_list = perm.ltrmin() + ltrmin_list = list(perm.ltrmin()) assert ltrmin_list[0] == 0 for i in range(len(ltrmin_list)): for j in range(ltrmin_list[i] + 1, len(perm)): @@ -852,12 +1299,12 @@ def test_ltrmin(): def test_rtlmin(): - assert Perm().rtlmin() == [] - assert Perm((0,)).rtlmin() == [0] - assert Perm((2, 4, 3, 0, 1)).rtlmin() == [3, 4] + assert list(Perm().rtlmin()) == [] + assert list(Perm((0,)).rtlmin()) == [0] + assert list(Perm((2, 4, 3, 0, 1)).rtlmin()) == [3, 4] for _ in range(100): perm = Perm.random(random.randint(2, 20)) - rtlmin_list = perm.rtlmin() + rtlmin_list = list(perm.rtlmin()) rtlmin_list.reverse() assert rtlmin_list[0] == len(perm) - 1 for i in range(len(rtlmin_list)): @@ -868,12 +1315,12 @@ def test_rtlmin(): def test_ltrmax(): - assert Perm().ltrmax() == [] - assert Perm((0,)).ltrmax() == [0] - assert Perm((2, 0, 4, 1, 5, 3)).ltrmax() == [0, 2, 4] + assert list(Perm().ltrmax()) == [] + assert list(Perm((0,)).ltrmax()) == [0] + assert list(Perm((2, 0, 4, 1, 5, 3)).ltrmax()) == [0, 2, 4] for _ in range(100): perm = Perm.random(random.randint(2, 20)) - ltrmax_list = perm.ltrmax() + ltrmax_list = list(perm.ltrmax()) assert ltrmax_list[0] == 0 for i in range(len(ltrmax_list)): for j in range(ltrmax_list[i] + 1, len(perm)): @@ -883,12 +1330,12 @@ def test_ltrmax(): def test_rtlmax(): - assert Perm().rtlmax() == [] - assert Perm((0,)).rtlmax() == [0] - assert Perm((2, 4, 3, 0, 1)).rtlmax() == [1, 2, 4] + assert list(Perm().rtlmax()) == [] + assert list(Perm((0,)).rtlmax()) == [0] + assert list(Perm((2, 4, 3, 0, 1)).rtlmax()) == [1, 2, 4] for _ in range(100): perm = Perm.random(random.randint(2, 20)) - rtlmax_list = perm.rtlmax() + rtlmax_list = list(perm.rtlmax()) rtlmax_list.reverse() assert rtlmax_list[0] == len(perm) - 1 for i in range(len(rtlmax_list)): @@ -960,6 +1407,14 @@ def test_count_non_inversions(): assert perm.count_non_inversions() == invs +def test_all_bonds(): + assert list(Perm().all_bonds()) == [] + assert list(Perm((0, 1, 4, 2, 3)).all_bonds()) == [0, 3] + assert list(Perm((0,)).all_bonds()) == [] + assert list(Perm.identity(10).all_bonds()) == list(range(9)) + assert list(Perm.monotone_decreasing(10).all_bonds()) == list(range(9)) + + def test_count_bonds(): assert Perm().count_bonds() == 0 assert Perm((0,)).count_bonds() == 0 @@ -972,6 +1427,32 @@ def test_count_bonds(): assert bons == perm.count_bonds() +def test_inc_bonds(): + assert list(Perm().inc_bonds()) == [] + assert list(Perm((1, 0, 4, 2, 3)).inc_bonds()) == [3] + assert list(Perm((0,)).inc_bonds()) == [] + assert list(Perm.identity(10).inc_bonds()) == list(range(9)) + assert list(Perm.monotone_decreasing(10).inc_bonds()) == [] + + +def test_count_inc_bonds(): + assert Perm((0, 2, 3, 1)).count_inc_bonds() == 1 + assert Perm((2, 3, 4, 5, 0, 1)).count_inc_bonds() == 4 + + +def test_dec_bonds(): + assert list(Perm().dec_bonds()) == [] + assert list(Perm((1, 0, 4, 2, 3)).dec_bonds()) == [0] + assert list(Perm((0,)).dec_bonds()) == [] + assert list(Perm.identity(10).dec_bonds()) == [] + assert list(Perm.monotone_decreasing(10).dec_bonds()) == list(range(9)) + + +def test_count_dec_bonds(): + assert Perm((2, 1, 0, 3)).count_dec_bonds() == 2 + assert Perm((1, 0, 3, 2, 5, 4)).count_dec_bonds() == 3 + + def test_major_index(): assert Perm().major_index() == 0 assert Perm((0,)).major_index() == 0 @@ -1000,12 +1481,15 @@ def test_longestruns_descending(): ) -def test_longestruns(): - assert Perm().longestruns() == (0, []) - assert Perm((0, 1, 2, 3)).longestruns() == (4, [0]) - assert Perm((4, 3, 2, 1, 0)).longestruns() == (1, [0, 1, 2, 3, 4]) - assert Perm((8, 1, 7, 5, 6, 2, 9, 3, 0, 4)).longestruns() == (2, [1, 3, 5, 8]) - assert Perm((1, 2, 3, 4, 6, 0, 9, 7, 8, 5)).longestruns() == (5, [0]) +def test_longestruns_ascending(): + assert Perm().longestruns_ascending() == (0, []) + assert Perm((0, 1, 2, 3)).longestruns_ascending() == (4, [0]) + assert Perm((4, 3, 2, 1, 0)).longestruns_ascending() == (1, [0, 1, 2, 3, 4]) + assert Perm((8, 1, 7, 5, 6, 2, 9, 3, 0, 4)).longestruns_ascending() == ( + 2, + [1, 3, 5, 8], + ) + assert Perm((1, 2, 3, 4, 6, 0, 9, 7, 8, 5)).longestruns_ascending() == (5, [0]) def test_length_of_longestrun_ascending(): @@ -1014,6 +1498,16 @@ def test_length_of_longestrun_ascending(): assert Perm((0, 1, 2)).length_of_longestrun_ascending() == 3 assert Perm((0, 1, 2, 3, 4, 5, 6, 7, 8, 9)).length_of_longestrun_ascending() == 10 assert Perm((6, 0, 9, 1, 4, 7, 3, 8, 5, 2)).length_of_longestrun_ascending() == 3 + assert Perm((6, 1, 3, 7, 4, 5, 9, 8, 0, 2)).length_of_longestrun_ascending() == 3 + assert Perm((4, 0, 9, 5, 3, 7, 1, 6, 8, 2)).length_of_longestrun_ascending() == 3 + assert Perm((1, 9, 4, 6, 0, 8, 2, 7, 5, 3)).length_of_longestrun_ascending() == 2 + assert Perm((2, 5, 8, 6, 0, 1, 3, 7, 9, 4)).length_of_longestrun_ascending() == 5 + + +def test_len(): + assert len(Perm()) == 0 + assert len(Perm((0,))) == 1 + assert len(Perm((1, 2, 0, 4, 3))) == 5 def test_length_of_longestrun_descending(): @@ -1024,13 +1518,6 @@ def test_length_of_longestrun_descending(): assert Perm((5, 4, 8, 9, 7, 3, 2, 1, 0, 6)).length_of_longestrun_descending() == 6 -def test_length_of_longestrun(): - assert Perm((6, 1, 3, 7, 4, 5, 9, 8, 0, 2)).length_of_longestrun() == 3 - assert Perm((4, 0, 9, 5, 3, 7, 1, 6, 8, 2)).length_of_longestrun() == 3 - assert Perm((1, 9, 4, 6, 0, 8, 2, 7, 5, 3)).length_of_longestrun() == 2 - assert Perm((2, 5, 8, 6, 0, 1, 3, 7, 9, 4)).length_of_longestrun() == 5 - - def test_count_cycles(): for i in range(10): assert Perm.identity(i).count_cycles() == i @@ -1048,22 +1535,70 @@ def test_is_involution(): assert perm.is_involution() == all(map(lambda x: len(x) <= 2, cyclelist)) +def test_cycle_decomp(): + assert Perm(()).cycle_decomp() == deque([]) + assert Perm((0,)).cycle_decomp() == deque([[0]]) + assert Perm((0, 1)).cycle_decomp() == deque([[0], [1]]) + assert Perm((0, 2, 1)).cycle_decomp() == deque([[0], [2, 1]]) + assert Perm((2, 3, 1, 0)).cycle_decomp() == deque([[3, 0, 2, 1]]) + assert Perm((0, 2, 4, 3, 1)).cycle_decomp() == deque([[0], [3], [4, 1, 2]]) + assert Perm((0, 1, 4, 3, 2, 5)).cycle_decomp() == deque( + [[0], [1], [3], [4, 2], [5]] + ) + assert Perm((0, 5, 4, 2, 1, 3, 6)).cycle_decomp() == deque( + [[0], [5, 3, 2, 4, 1], [6]] + ) + assert Perm((1, 7, 3, 5, 0, 2, 4, 6)).cycle_decomp() == deque( + [[5, 2, 3], [7, 6, 4, 0, 1]] + ) + assert Perm((0, 5, 1, 3, 2, 4, 6, 7, 8)).cycle_decomp() == deque( + [[0], [3], [5, 4, 2, 1], [6], [7], [8]] + ) + 25 + assert Perm( + (10, 0, 1, 3, 7, 11, 5, 2, 9, 13, 4, 8, 14, 6, 12) + ).cycle_decomp() == deque( + [[3], [10, 4, 7, 2, 1, 0], [13, 6, 5, 11, 8, 9], [14, 12]] + ) + assert Perm( + (13, 10, 4, 1, 6, 9, 2, 3, 8, 7, 5, 14, 12, 11, 0) + ).cycle_decomp() == deque( + [[6, 2, 4], [8], [10, 5, 9, 7, 3, 1], [12], [14, 0, 13, 11]] + ) + assert Perm( + (4, 1, 12, 14, 0, 5, 2, 7, 13, 3, 9, 6, 10, 8, 11) + ).cycle_decomp() == deque( + [[1], [4, 0], [5], [7], [13, 8], [14, 11, 6, 2, 12, 10, 9, 3]] + ) + assert Perm( + (6, 8, 5, 7, 0, 14, 2, 1, 3, 13, 12, 11, 4, 10, 9) + ).cycle_decomp() == deque([[8, 3, 7, 1], [11], [14, 9, 13, 10, 12, 4, 0, 6, 2, 5]]) + assert Perm( + (12, 6, 4, 0, 3, 2, 14, 13, 8, 1, 9, 7, 10, 11, 5) + ).cycle_decomp() == deque([[8], [13, 11, 7], [14, 5, 2, 4, 3, 0, 12, 10, 9, 1, 6]]) + + def test_rank(): - assert list(map(lambda x: x.rank(), PermSet(5))) == list(range(34, 154)) + for i, perm in enumerate(Perm.first(1000)): + assert perm.rank() == i def test_threepats(): assert all(v == 0 for v in Perm().threepats().values()) assert all(v == 0 for v in Perm((0,)).threepats().values()) assert all(v == 0 for v in Perm((0, 1)).threepats().values()) - assert Perm((2, 1, 0, 3)).threepats() == { - Perm((0, 1, 2)): 0, - Perm((0, 2, 1)): 0, - Perm((1, 0, 2)): 3, - Perm((1, 2, 0)): 0, - Perm((2, 0, 1)): 0, - Perm((2, 1, 0)): 1, - } + assert ( + lambda counter: all( + ( + counter[Perm((0, 1, 2))] == 0, + counter[Perm((0, 2, 1))] == 0, + counter[Perm((1, 0, 2))] == 3, + counter[Perm((1, 2, 0))] == 0, + counter[Perm((2, 0, 1))] == 0, + counter[Perm((2, 1, 0))] == 1, + ) + ) + )(Perm((2, 1, 0, 3)).threepats()) for _ in range(20): perm = Perm.random(random.randint(0, 20)) threepatdict = perm.threepats() @@ -1075,32 +1610,36 @@ def test_fourpats(): assert all(v == 0 for v in Perm().fourpats().values()) assert all(v == 0 for v in Perm((0,)).fourpats().values()) assert all(v == 0 for v in Perm((0, 1)).fourpats().values()) - assert Perm((1, 0, 3, 5, 2, 4)).fourpats() == { - Perm((0, 1, 2, 3)): 0, - Perm((0, 1, 3, 2)): 2, - Perm((0, 2, 1, 3)): 2, - Perm((0, 2, 3, 1)): 2, - Perm((0, 3, 1, 2)): 2, - Perm((0, 3, 2, 1)): 0, - Perm((1, 0, 2, 3)): 3, - Perm((1, 0, 3, 2)): 3, - Perm((1, 2, 0, 3)): 0, - Perm((1, 2, 3, 0)): 0, - Perm((1, 3, 0, 2)): 1, - Perm((1, 3, 2, 0)): 0, - Perm((2, 0, 1, 3)): 0, - Perm((2, 0, 3, 1)): 0, - Perm((2, 1, 0, 3)): 0, - Perm((2, 1, 3, 0)): 0, - Perm((2, 3, 0, 1)): 0, - Perm((2, 3, 1, 0)): 0, - Perm((3, 0, 1, 2)): 0, - Perm((3, 0, 2, 1)): 0, - Perm((3, 1, 0, 2)): 0, - Perm((3, 1, 2, 0)): 0, - Perm((3, 2, 0, 1)): 0, - Perm((3, 2, 1, 0)): 0, - } + assert ( + lambda counter: all( + ( + counter[Perm((0, 1, 2, 3))] == 0, + counter[Perm((0, 1, 3, 2))] == 2, + counter[Perm((0, 2, 1, 3))] == 2, + counter[Perm((0, 2, 3, 1))] == 2, + counter[Perm((0, 3, 1, 2))] == 2, + counter[Perm((0, 3, 2, 1))] == 0, + counter[Perm((1, 0, 2, 3))] == 3, + counter[Perm((1, 0, 3, 2))] == 3, + counter[Perm((1, 2, 0, 3))] == 0, + counter[Perm((1, 2, 3, 0))] == 0, + counter[Perm((1, 3, 0, 2))] == 1, + counter[Perm((1, 3, 2, 0))] == 0, + counter[Perm((2, 0, 1, 3))] == 0, + counter[Perm((2, 0, 3, 1))] == 0, + counter[Perm((2, 1, 0, 3))] == 0, + counter[Perm((2, 1, 3, 0))] == 0, + counter[Perm((2, 3, 0, 1))] == 0, + counter[Perm((2, 3, 1, 0))] == 0, + counter[Perm((3, 0, 1, 2))] == 0, + counter[Perm((3, 0, 2, 1))] == 0, + counter[Perm((3, 1, 0, 2))] == 0, + counter[Perm((3, 1, 2, 0))] == 0, + counter[Perm((3, 2, 0, 1))] == 0, + counter[Perm((3, 2, 1, 0))] == 0, + ) + ) + )(Perm((1, 0, 3, 5, 2, 4)).fourpats()) for _ in range(20): perm = Perm.random(random.randint(0, 20)) fourpatdict = perm.fourpats() @@ -1134,13 +1673,13 @@ def test_block_decomposition(): [0], [], ] - assert set(Perm((4, 1, 0, 5, 2, 3)).block_decomposition(True)) == set( + assert set(Perm((4, 1, 0, 5, 2, 3)).block_decomposition_as_pattern()) == set( [Perm((0, 1)), Perm((1, 0))] ) for _ in range(20): perm = Perm.random(random.randint(0, 20)) blocks = perm.block_decomposition() - patts = set(perm.block_decomposition(True)) + patts = set(perm.block_decomposition_as_pattern()) for length in range(len(blocks)): for start in blocks[length]: assert ( @@ -1151,15 +1690,69 @@ def test_block_decomposition(): assert Perm.to_standard(perm[start : start + length]) in patts +def test_count_rtlmax_ltrmin_layers(): + assert Perm((2, 1, 3, 0)).count_rtlmax_ltrmin_layers() == 1 + assert Perm((2, 0, 1, 3)).count_rtlmax_ltrmin_layers() == 2 + assert Perm((1, 3, 2, 7, 6, 5, 8, 0, 4)).count_rtlmax_ltrmin_layers() == 2 + assert Perm((0, 8, 2, 4, 5, 6, 3, 7, 1)).count_rtlmax_ltrmin_layers() == 4 + assert Perm((1, 0, 2)).count_rtlmax_ltrmin_layers() == 1 + assert Perm((1, 2, 0, 3)).count_rtlmax_ltrmin_layers() == 2 + assert Perm((3, 0, 2, 1)).count_rtlmax_ltrmin_layers() == 1 + assert Perm((2, 0, 1)).count_rtlmax_ltrmin_layers() == 1 + assert Perm((1, 3, 2, 0)).count_rtlmax_ltrmin_layers() == 1 + assert Perm(()).count_rtlmax_ltrmin_layers() == 0 + assert Perm((5, 3, 7, 4, 2, 0, 6, 1)).count_rtlmax_ltrmin_layers() == 2 + assert Perm((0, 2, 1, 6, 4, 3, 5, 7, 8)).count_rtlmax_ltrmin_layers() == 4 + assert Perm((2, 1, 0)).count_rtlmax_ltrmin_layers() == 1 + assert Perm((0, 2, 1)).count_rtlmax_ltrmin_layers() == 1 + assert Perm((0, 8, 3, 5, 7, 4, 2, 6, 1)).count_rtlmax_ltrmin_layers() == 2 + + +def test_rtlmax_ltrmin_decomposition(): + assert list(Perm(()).rtlmax_ltrmin_decomposition()) == [] + assert list(Perm((0,)).rtlmax_ltrmin_decomposition()) == [[0]] + assert list(Perm((2, 0, 3, 1)).rtlmax_ltrmin_decomposition()) == [[0, 1, 2, 3]] + assert list(Perm((0, 1, 2, 3)).rtlmax_ltrmin_decomposition()) == [[0, 3], [0, 1]] + assert list(Perm((1, 0, 2)).rtlmax_ltrmin_decomposition()) == [[0, 1, 2]] + assert list(Perm((2, 1, 0)).rtlmax_ltrmin_decomposition()) == [[0, 1, 2]] + assert list(Perm((0, 1)).rtlmax_ltrmin_decomposition()) == [[0, 1]] + assert list(Perm((2, 1, 3, 0)).rtlmax_ltrmin_decomposition()) == [[0, 1, 2, 3]] + assert list(Perm((0, 2, 1)).rtlmax_ltrmin_decomposition()) == [[0, 1, 2]] + assert list(Perm((2, 3, 0, 1)).rtlmax_ltrmin_decomposition()) == [[0, 1, 2, 3]] + assert list(Perm((1, 3, 0, 2)).rtlmax_ltrmin_decomposition()) == [[0, 1, 2, 3]] + assert list(Perm((1, 2, 3, 0)).rtlmax_ltrmin_decomposition()) == [[0, 2, 3], [0]] + assert list(Perm((0, 1, 6, 5, 4, 3, 2)).rtlmax_ltrmin_decomposition()) == [ + [0, 2, 3, 4, 5, 6], + [0], + ] + assert list(Perm((0, 1, 4, 3, 2, 5)).rtlmax_ltrmin_decomposition()) == [ + [0, 5], + [0, 1, 2, 3], + ] + assert list(Perm((2, 3, 0, 4, 1, 5)).rtlmax_ltrmin_decomposition()) == [ + [0, 2, 5], + [1, 2], + [0], + ] + assert list(Perm((4, 0, 2, 5, 3, 1)).rtlmax_ltrmin_decomposition()) == [ + [0, 1, 3, 4, 5], + [0], + ] + assert list(Perm((1, 5, 0, 3, 2, 4)).rtlmax_ltrmin_decomposition()) == [ + [0, 1, 2, 5], + [0, 1], + ] + + def test_monotone_block_decomposition(): - assert Perm().monotone_block_decomposition(True) == [] - assert Perm((0,)).monotone_block_decomposition() == [] - assert Perm((0,)).monotone_block_decomposition(True) == [(0, 0)] - assert Perm((6, 7, 5, 3, 0, 1, 2, 4)).monotone_block_decomposition() == [ + assert list(Perm().monotone_block_decomposition(True)) == [] + assert list(Perm((0,)).monotone_block_decomposition()) == [] + assert list(Perm((0,)).monotone_block_decomposition(True)) == [(0, 0)] + assert list(Perm((6, 7, 5, 3, 0, 1, 2, 4)).monotone_block_decomposition()) == [ (0, 1), (4, 6), ] - assert Perm((0, 2, 1, 5, 6, 7, 4, 3)).monotone_block_decomposition() == [ + assert list(Perm((0, 2, 1, 5, 6, 7, 4, 3)).monotone_block_decomposition()) == [ (1, 2), (3, 5), (6, 7), @@ -1177,6 +1770,87 @@ def test_monotone_block_decomposition(): ) +def test_monotone_block_decomposition_ascending(): + assert list(Perm().monotone_block_decomposition_ascending()) == [] + for i in range(1, 10): + assert list(Perm.identity(i).monotone_block_decomposition_ascending(True)) == [ + (0, i - 1) + ] + assert list( + Perm((0, 1, 3, 6, 5, 4, 2)).monotone_block_decomposition_ascending(True) + ) == [(0, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)] + assert list( + Perm((0, 1, 5, 6, 3, 2, 4)).monotone_block_decomposition_ascending(False) + ) == [(0, 1), (2, 3)] + assert list( + Perm((2, 3, 1, 4, 5, 0)).monotone_block_decomposition_ascending(False) + ) == [(0, 1), (3, 4)] + assert ( + list(Perm((3, 5, 1, 4, 0, 2)).monotone_block_decomposition_ascending(False)) + == [] + ) + assert list( + Perm((5, 2, 4, 0, 3, 1)).monotone_block_decomposition_ascending(True) + ) == [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5)] + assert list( + Perm((3, 1, 2, 5, 4, 0)).monotone_block_decomposition_ascending(False) + ) == [(1, 2)] + assert ( + list(Perm((4, 0, 3, 2, 5, 1)).monotone_block_decomposition_ascending(False)) + == [] + ) + assert list( + Perm((3, 2, 5, 4, 0, 1)).monotone_block_decomposition_ascending(True) + ) == [(0, 0), (1, 1), (2, 2), (3, 3), (4, 5)] + assert list( + Perm((0, 1, 2, 4, 3, 5)).monotone_block_decomposition_ascending(False) + ) == [(0, 2)] + assert ( + list(Perm((3, 5, 1, 0, 4, 2)).monotone_block_decomposition_ascending(False)) + == [] + ) + + +def test_test_monotone_block_decomposition_descending(): + assert list(Perm().monotone_block_decomposition_descending()) == [] + for i in range(1, 10): + assert list( + Perm.monotone_decreasing(i).monotone_block_decomposition_descending(True) + ) == [(0, i - 1)] + assert ( + list(Perm((5, 0, 3, 1, 2, 4)).monotone_block_decomposition_descending(False)) + == [] + ) + assert list( + Perm((0, 5, 2, 3, 4, 1)).monotone_block_decomposition_descending(True) + ) == [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5)] + assert list( + Perm((5, 4, 2, 1, 0, 3)).monotone_block_decomposition_descending(False) + ) == [(0, 1), (2, 4)] + assert list( + Perm((1, 3, 2, 0, 4)).monotone_block_decomposition_descending(True) + ) == [(0, 0), (1, 2), (3, 3), (4, 4)] + assert list( + Perm((4, 5, 2, 3, 1, 0)).monotone_block_decomposition_descending(True) + ) == [(0, 0), (1, 1), (2, 2), (3, 3), (4, 5)] + assert list( + Perm((2, 1, 3, 4, 0)).monotone_block_decomposition_descending(True) + ) == [(0, 1), (2, 2), (3, 3), (4, 4)] + assert list( + Perm((0, 5, 1, 3, 2, 4)).monotone_block_decomposition_descending(True) + ) == [(0, 0), (1, 1), (2, 2), (3, 4), (5, 5)] + assert ( + list(Perm((2, 4, 0, 1, 5, 3)).monotone_block_decomposition_descending(False)) + == [] + ) + assert list( + Perm((5, 4, 0, 1, 3, 2)).monotone_block_decomposition_descending(False) + ) == [(0, 1), (4, 5)] + assert list( + Perm((5, 2, 1, 4, 3, 0)).monotone_block_decomposition_descending(True) + ) == [(0, 0), (1, 2), (3, 4), (5, 5)] + + def test_monotone_quotient(): assert Perm().monotone_quotient() == Perm() assert Perm((0,)).monotone_quotient() == Perm((0,)) @@ -1189,6 +1863,17 @@ def test_monotone_quotient(): assert monblocks in list(perm.occurrences_of(perm.monotone_quotient())) +def test_maximum_block(): + assert Perm((0, 1, 2)).maximum_block() == (2, 0) + assert Perm((0, 2, 1)).maximum_block() == (2, 1) + assert Perm((1, 0, 2)).maximum_block() == (2, 0) + assert Perm((1, 2, 0)).maximum_block() == (2, 0) + assert Perm((2, 0, 1)).maximum_block() == (2, 1) + assert Perm((2, 1, 0)).maximum_block() == (2, 0) + assert Perm((4, 0, 6, 3, 5, 1, 2)).maximum_block() == (2, 5) + assert Perm((1, 2, 7, 8, 6, 0, 3, 5, 4)).maximum_block() == (3, 2) + + def test_simple_location(): assert Perm().simple_location() == (0, 0) assert Perm((0,)).simple_location() == (0, 0) @@ -1255,16 +1940,32 @@ def test_coveredby(): assert perm in p.children() +def test_children(): + assert Perm().children() == [] + assert Perm((0,)).children() == [Perm()] + assert sorted(Perm((0, 4, 3, 1, 2)).children()) == [ + Perm((0, 3, 1, 2)), + Perm((0, 3, 2, 1)), + Perm((3, 2, 0, 1)), + ] + assert sorted(Perm((5, 0, 2, 4, 3, 1, 6)).children()) == [ + Perm((0, 2, 4, 3, 1, 5)), + Perm((4, 0, 1, 3, 2, 5)), + Perm((4, 0, 2, 3, 1, 5)), + Perm((4, 0, 3, 2, 1, 5)), + Perm((4, 1, 3, 2, 0, 5)), + Perm((5, 0, 2, 4, 3, 1)), + ] + + def test_call_1(): p = Perm((0, 1, 2, 3)) for i in range(len(p)): assert p(i) == i - with pytest.raises(ValueError): + with pytest.raises(AssertionError): p(-1) - with pytest.raises(ValueError): + with pytest.raises(AssertionError): p(4) - with pytest.raises(TypeError): - p("abc") def test_call_2(): @@ -1274,12 +1975,10 @@ def test_call_2(): assert p(2) == 0 assert p(3) == 2 assert p(4) == 1 - with pytest.raises(ValueError): + with pytest.raises(AssertionError): p(-1) - with pytest.raises(ValueError): + with pytest.raises(AssertionError): p(5) - with pytest.raises(TypeError): - p([1, 2, 3]) def test_eq(): @@ -1319,7 +2018,7 @@ def do_test(patts, expected): for i in range(min(len(expected), bound)): length = i + 1 cnt = 0 - for p in PermSet(length): + for p in Perm.of_length(length): ok = True for patt in patts: if not p.avoids(Perm(patt)): @@ -1437,6 +2136,17 @@ def test_ascii_plot(): assert plot[len(perm) - perm[i] - 1][2 * i] == "\u25cf" +def test_to_tikz(): + assert Perm((1, 0, 4, 2, 3)).to_tikz() == ( + "\\begin{tikzpicture}[scale=.3,baseline=(current bounding box.center)]\n\t\\" + "foreach \\x in {1,...,5} {\n\t\t\\draw[ultra thin] (\\x,0)--(\\x,6); %vline\n" + "\t\t\\draw[ultra thin] (0,\\x)--(6,\\x); %hline\n\t}\n\t\\draw[fill=black] (1," + "2) circle (5pt);\n\t\\draw[fill=black] (2,1) circle (5pt);\n\t\\draw[fill=blac" + "k] (3,5) circle (5pt);\n\t\\draw[fill=black] (4,3) circle (5pt);\n\t\\draw[fil" + "l=black] (5,4) circle (5pt);\n\\end{tikzpicture}".replace("\t", " " * 4) + ) + + def test_cycle_notation(): assert Perm().cycle_notation() == "( )" assert Perm((0,)).cycle_notation() == "( 0 )" diff --git a/tests/descriptors/__init__.py b/tests/perm_sets/__init__.py similarity index 100% rename from tests/descriptors/__init__.py rename to tests/perm_sets/__init__.py diff --git a/tests/perm_sets/test_av.py b/tests/perm_sets/test_av.py new file mode 100644 index 00000000..7f19ea86 --- /dev/null +++ b/tests/perm_sets/test_av.py @@ -0,0 +1,292 @@ +from math import factorial + +import pytest +from permuta import MeshPatt, Perm +from permuta.perm_sets import Av +from permuta.perm_sets.basis import Basis, MeshBasis + + +# binom will be added to math in 3.8 so when pypy is compatible with 3.8, replace: +def binom(n, k): + return factorial(n) // (factorial(n - k) * factorial(k)) + + +def catalan(n): + return binom(2 * n, n) // (n + 1) + + +test_classes = [ + ([[0, 1]], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), + ([[1, 0]], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), + ([[0, 1, 2]], [catalan(i) for i in range(8)]), + ([[0, 2, 1]], [catalan(i) for i in range(8)]), + ([[1, 0, 2]], [catalan(i) for i in range(8)]), + ([[1, 2, 0]], [catalan(i) for i in range(8)]), + ([[2, 0, 1]], [catalan(i) for i in range(8)]), + ([[2, 1, 0]], [catalan(i) for i in range(8)]), + ([[0, 2, 1, 3]], [1, 1, 2, 6, 23, 103, 513, 2762]), + ([[0, 2, 3, 1]], [1, 1, 2, 6, 23, 103, 512, 2740, 15485]), + ([[0, 3, 2, 1]], [1, 1, 2, 6, 23, 103, 513, 2761, 15767]), + ([[1, 0, 2], [2, 1, 0]], [1, 1, 2, 4, 7, 11, 16, 22]), + ([[0, 2, 1], [3, 2, 1, 0]], [1, 1, 2, 5, 13, 31, 66, 127]), + ([[2, 1, 0], [1, 2, 3, 0]], [1, 1, 2, 5, 13, 34, 89, 233]), + ([[3, 2, 1, 0], [3, 2, 0, 1]], [1, 1, 2, 6, 22, 90, 394, 1806]), + ([[2, 3, 0, 1], [1, 3, 0, 2]], [1, 1, 2, 6, 22, 90, 395, 1823]), + ( + [[3, 1, 2, 0], [2, 4, 0, 3, 1], [3, 1, 4, 0, 2], [2, 4, 0, 5, 1, 3]], + [1, 1, 2, 6, 23, 101, 477, 2343, 11762], + ), + ([[0, 2, 1], [2, 1, 3, 4, 0]], [1, 1, 2, 5, 14, 41, 122, 365, 1094]), +] + + +@pytest.mark.parametrize("patts,enum", test_classes) +def test_avoiding_enumeration(patts, enum): + patts = [Perm(patt) for patt in patts] + basis = Basis(*patts) + for (n, cnt) in enumerate(enum): + # print(n, cnt) + inst = Av(basis).of_length(n) + gen = list(inst) + # assert len(gen) == cnt + assert len(gen) == len(set(gen)) + for perm in gen: + assert perm.avoids(*patts) + + mx = len(enum) - 1 + cnt = [0 for _ in range(mx + 1)] + for perm in Av(basis).up_to_length(mx): + assert perm.avoids(*patts) + cnt[len(perm)] += 1 + + assert enum == cnt + + +def test_avoiding_generic_mesh_patterns(): + p = Perm((2, 0, 1)) + shading = ((2, 0), (2, 1), (2, 2), (2, 3)) + mps = [MeshPatt(p, shading)] + meshbasis = MeshBasis(*mps) + avoiding_generic_basis = Av(meshbasis) + enum = [1, 1, 2, 5, 15, 52, 203, 877] # Bell numbers + + for (n, cnt) in enumerate(enum): + inst = avoiding_generic_basis.of_length(n) + gen = list(inst) + assert len(gen) == cnt + assert len(gen) == len(set(gen)) + for perm in gen: + assert perm.avoids(*mps) + assert perm in avoiding_generic_basis + + mx = len(enum) - 1 + cnt = [0 for _ in range(mx + 1)] + for perm in Av(meshbasis).up_to_length(mx): + assert perm.avoids(*mps) + cnt[len(perm)] += 1 + + assert enum == cnt + + +def test_avoiding_generic_finite_class(): + ts = [ + ([[0]], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + ([[0, 1], [3, 2, 1, 0]], [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]), + ([[0, 1, 2], [3, 2, 1, 0]], [1, 1, 2, 5, 13, 25, 25, 0, 0, 0, 0, 0]), + ] + + for (patts, enum) in ts: + patts = [Perm(patt) for patt in patts] + basis = Basis(*patts) + for (n, cnt) in enumerate(enum): + inst = Av(basis).of_length(n) + gen = list(inst) + assert len(gen) == cnt + assert len(gen) == len(set(gen)) + for perm in gen: + assert perm.avoids(*patts) + + mx = len(enum) - 1 + cnt = [0 for _ in range(mx + 1)] + for perm in Av(basis).up_to_length(mx): + assert perm.avoids(*patts) + cnt[len(perm)] += 1 + + assert enum == cnt + + +def test_is_subclass(): + av1 = Av.from_iterable((Perm((0,)),)) + av12_21 = Av.from_iterable((Perm((0, 1)), Perm((1, 0)))) + av123 = Av.from_iterable((Perm((0, 1, 2)),)) + av1234 = Av.from_iterable((Perm((0, 1, 2, 3)),)) + assert av1.is_subclass(av123) + assert not av123.is_subclass(av1) + assert av123.is_subclass(av1234) + assert not av1234.is_subclass(av12_21) + assert av12_21.is_subclass(av1234) + assert av123.is_subclass(av123) + av1324_1423_12345 = Av.from_iterable( + (Perm((0, 2, 1, 3)), Perm((0, 3, 1, 2)), Perm((0, 1, 2, 3, 4, 5))) + ) + av1324_1234 = Av.from_iterable((Perm((0, 2, 1, 3)), Perm((0, 1, 2, 3)))) + av1234_132 = Av.from_iterable((Perm((0, 1, 2, 3)), Perm((0, 2, 1)))) + assert av123.is_subclass(av1324_1423_12345) + assert not av1324_1234.is_subclass(av1324_1423_12345) + assert av1234_132.is_subclass(av1324_1423_12345) + + +def test_av_of_length(): + assert [ + sum(1 for _ in Av(Basis(Perm((0, 2, 1)))).of_length(i)) for i in range(8) + ] == [1, 1, 2, 5, 14, 42, 132, 429] + + +def test_av_perm(): + p = Perm((0, 1)) + av = Av([p]) + for length in range(10): + assert len(set(av.of_length(length))) == 1 + + +def test_av_meshpatt(): + p = Perm((2, 0, 1)) + shading = ((2, 0), (2, 1), (2, 2), (2, 3)) + mp = MeshPatt(p, shading) + av = Av([mp]) + enum = [1, 1, 2, 5, 15, 52, 203, 877] # Bell numbers + + for (n, cnt) in enumerate(enum): + inst = av.of_length(n) + gen = list(inst) + assert len(gen) == cnt + + +def test_enumeration(): + assert ( + Av.from_string("132").enumeration(8) + == Av(Basis(Perm((0, 2, 1)))).enumeration(8) + == [1, 1, 2, 5, 14, 42, 132, 429, 1430] + ) + assert ( + Av.from_string("Av(123,231)").enumeration(8) + == Av(Basis(Perm((0, 1, 2)), Perm((1, 2, 0)))).enumeration(8) + == [1, 1, 2, 4, 7, 11, 16, 22, 29] + ) + assert Av( + (Perm((0, 1, 2)), MeshPatt(Perm((2, 0, 1)), [(0, 1), (1, 1), (2, 1), (3, 1)])) + ).enumeration(7) == [1, 1, 2, 4, 8, 16, 32, 64] + assert ( + Av.from_string("0123_2013_1023").enumeration(8) + == Av( + Basis(Perm((0, 1, 2, 3)), Perm((2, 0, 1, 3)), Perm((1, 0, 2, 3))) + ).enumeration(8) + == [1, 1, 2, 6, 21, 79, 309, 1237, 5026] + ) + assert ( + Av.from_string("1243 1342 3241 3241").enumeration(8) + == Av( + Basis( + Perm((0, 1, 3, 2)), + Perm((0, 2, 3, 1)), + Perm((2, 1, 3, 0)), + Perm((2, 1, 3, 0)), + ) + ).enumeration(8) + == [1, 1, 2, 6, 21, 75, 262, 891, 2964] + ) + assert ( + Av.from_string("Av(1342, 3124, 1432, 4312)").enumeration(8) + == Av( + Basis( + Perm((0, 2, 3, 1)), + Perm((2, 0, 1, 3)), + Perm((0, 3, 2, 1)), + Perm((3, 2, 0, 1)), + ) + ).enumeration(8) + == [1, 1, 2, 6, 20, 61, 169, 442, 1120] + ) + + +def test_generators(): + assert list(Av(Basis(Perm((0, 1)), Perm((1, 0)))).first(500)) == [ + Perm(), + Perm((0,)), + ] + assert sorted(Av(Basis(Perm((0, 2, 1)), Perm((1, 2, 0)))).of_length(3)) == sorted( + set(Perm.of_length(3)) - {Perm((0, 2, 1)), Perm((1, 2, 0))} + ) + assert sorted( + Av(Basis(Perm((0, 2, 1)), Perm((1, 2, 0)))).up_to_length(3) + ) == sorted(set(Perm.up_to_length(3)) - {Perm((0, 2, 1)), Perm((1, 2, 0))}) + + +def test_instance_variable_cache(): + Av.clear_cache() + basis = Basis(Perm((0, 1))) + av = Av(basis) + assert basis in Av._CLASS_CACHE + list(av.of_length(5)) + assert len(av.cache) == 6 + assert len(Av(Basis(Perm((0, 1)))).cache) == 6 + av2 = Av(Basis(Perm((0, 1)))) + assert len(av2.cache) == 6 + list(av2.of_length(10)) + assert len(av.cache) == 11 + assert len(av2.cache) == 11 + assert len(Av.from_string("12").cache) == 11 + assert len(Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)))).cache) == 1 + list(Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)))).of_length(5)) + assert len(Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)))).cache) == 6 + assert ( + len(Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)), Perm((1, 2, 0, 3)))).cache) == 6 + ) + assert len(Av(Basis(Perm((1, 2, 0)), Perm((2, 0, 1)))).cache) == 6 + for p in Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)), Perm((1, 2, 0, 3)))).of_length( + 10 + ): + pass + assert len(Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)))).cache) == 11 + Av.clear_cache() + + +def test_class_variable_cache(): + Av.clear_cache() + assert len(Av._CLASS_CACHE) == 0 + assert Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)))) is Av( + Basis(Perm((2, 0, 1)), Perm((1, 2, 0))) + ) + av = Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)))) + assert av is Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)))) + assert av is Av(Basis(Perm((2, 0, 1)), Perm((1, 2, 0)), Perm((1, 2, 0, 3)))) + assert len(Av._CLASS_CACHE) == 1 + av2 = Av(Basis(Perm((0, 1, 3, 2)), Perm((0, 2, 1)))) + assert len(Av._CLASS_CACHE) == 2 + assert av is not av2 + assert av2 is Av(Basis(Perm((0, 2, 1)))) + assert Av.from_string("132") is av2 + assert Basis(Perm((0, 2, 1))) in Av._CLASS_CACHE + assert ( + Av._CLASS_CACHE[Basis(Perm((0, 2, 1)))] + is Av._CLASS_CACHE[Basis(Perm((0, 1, 3, 2)), Perm((0, 2, 1)))] + ) + assert Av((Perm((2, 0, 1)),)) is Av(Basis(Perm((2, 0, 1)))) + Av.clear_cache() + assert len(Av._CLASS_CACHE) == 0 + + +def test_valid_error_in_construction(): + with pytest.raises(ValueError): + Av(Basis()) + with pytest.raises(ValueError): + Av(Basis(Perm())) + + +def test_invalid_ops_with_mesh_patt(): + with pytest.raises(NotImplementedError): + Av(MeshBasis(Perm((0, 1)))).is_finite() + with pytest.raises(NotImplementedError): + Av(MeshBasis(Perm((0, 1)))).is_insertion_encodable() + with pytest.raises(NotImplementedError): + Av(MeshBasis(Perm((0, 1)))).is_polynomial() diff --git a/tests/perm_sets/test_basis.py b/tests/perm_sets/test_basis.py new file mode 100644 index 00000000..1a88781d --- /dev/null +++ b/tests/perm_sets/test_basis.py @@ -0,0 +1,208 @@ +from permuta import MeshPatt, Perm +from permuta.perm_sets.basis import Basis, MeshBasis + + +def test_is_mesh_basis(): + assert not MeshBasis.is_mesh_basis(()) + assert not MeshBasis.is_mesh_basis([]) + p1 = Perm((0, 2, 1)) + p2 = Perm((2, 0, 1)) + perm_list = [p1, p2] + assert not MeshBasis.is_mesh_basis(p1) + assert not MeshBasis.is_mesh_basis(p2) + assert not MeshBasis.is_mesh_basis(perm_list) + p1 = Perm((0, 2, 1)) + p2 = Perm((2, 0, 1)) + shading = ((2, 0), (2, 1), (2, 2), (2, 3)) + mp1 = MeshPatt(p1, shading) + mp2 = MeshPatt(p2, shading) + meshpatt_list = [mp1, mp2] + mixed_patt_list = [p1, mp2] + assert MeshBasis.is_mesh_basis(mp1) + assert MeshBasis.is_mesh_basis(mp2) + assert MeshBasis.is_mesh_basis(meshpatt_list) + assert MeshBasis.is_mesh_basis(mixed_patt_list) + + +def test_meshbasis_of_perms(): + p1 = Perm((0, 2, 1)) + p2 = Perm((2, 0, 1)) + assert MeshBasis(p1, p2) == MeshBasis(MeshPatt(p1, []), MeshPatt(p2, [])) + + shading = ((2, 0), (2, 1), (2, 2), (2, 3)) + mp1 = MeshPatt(p1, shading) + mp2 = MeshPatt(p2, shading) + assert MeshBasis(p1, p2) == MeshBasis(p1, p2, mp1, mp2) + + for elmnt in MeshBasis(p1, p2): + assert isinstance(elmnt, MeshPatt) + + +def test_meshbasis(): + assert MeshBasis(*[MeshPatt(), MeshPatt(Perm((0,)), ())]) == MeshBasis(MeshPatt()) + assert MeshBasis(*[MeshPatt(), Perm()]) == MeshBasis(MeshPatt()) + assert MeshBasis(*(Perm((1, 2, 0)), MeshPatt(Perm((1, 2, 0)), []))) == MeshBasis( + *(MeshPatt(Perm((1, 2, 0)), []),) + ) + assert MeshBasis( + *( + Perm((1, 2, 0)), + MeshPatt(Perm((0, 2, 1)), [(0, 0), (0, 1), (1, 1), (2, 3), (3, 0), (3, 3)]), + ) + ) == MeshBasis( + *( + MeshPatt(Perm((0, 2, 1)), [(0, 0), (0, 1), (1, 1), (2, 3), (3, 0), (3, 3)]), + MeshPatt(Perm((1, 2, 0)), []), + ) + ) + assert MeshBasis( + *( + Perm((1, 2, 0)), + MeshPatt(Perm((0, 2, 1)), [(0, 0), (0, 1), (1, 1), (2, 3), (3, 0), (3, 3)]), + ) + ) != MeshBasis( + *(MeshPatt(Perm((0, 2, 1)), [(0, 0), (0, 1), (1, 1), (2, 3), (3, 0), (3, 3)]),) + ) + assert MeshBasis( + *( + MeshPatt(Perm((1, 2, 0)), [(0, 0), (0, 2), (1, 1), (1, 3), (2, 0), (2, 1)]), + MeshPatt( + Perm((3, 4, 2, 0, 1)), + [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 2), + (1, 3), + (1, 4), + (1, 5), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (3, 0), + (3, 2), + (3, 4), + (3, 5), + (4, 0), + (4, 1), + (4, 3), + (4, 4), + (5, 0), + (5, 2), + (5, 4), + (5, 5), + ], + ), + ) + ) == MeshBasis( + *(MeshPatt(Perm((1, 2, 0)), [(0, 0), (0, 2), (1, 1), (1, 3), (2, 0), (2, 1)]),) + ) + assert MeshBasis( + *(Perm((2, 0, 1)), MeshPatt(Perm((2, 0, 1)), [(2, 2)])) + ) == MeshBasis(*(Perm((2, 0, 1)),)) + + +def test_basis(): + assert Basis(*[Perm(), Perm((0,))]) == Basis(Perm()) + assert Basis(*[Perm((0, 2, 1)), Perm((0, 3, 2, 1))]) == Basis(Perm((0, 2, 1))) + assert Basis(Perm((2, 0, 1))) == Basis( + *( + Perm((4, 0, 1, 7, 2, 6, 3, 5)), + Perm((4, 2, 3, 0, 7, 9, 6, 8, 1, 5)), + Perm((0, 10, 3, 8, 7, 12, 2, 4, 6, 13, 5, 16, 14, 17, 1, 11, 15, 9)), + Perm((8, 11, 7, 1, 4, 5, 10, 6, 0, 3, 2, 9)), + Perm((2, 0, 1)), + Perm((4, 1, 0, 5, 2, 3)), + Perm((5, 3, 4, 1, 2, 0)), + Perm((1, 0, 3, 6, 4, 5, 2)), + Perm((1, 7, 10, 2, 3, 6, 0, 4, 9, 5, 8)), + Perm((1, 2, 8, 9, 4, 6, 14, 10, 3, 12, 13, 5, 7, 11, 0)), + Perm((10, 13, 6, 9, 8, 12, 2, 4, 11, 0, 3, 16, 14, 15, 7, 5, 1)), + ) + ) + assert Basis(*(Perm((0, 2, 1)), Perm((0, 1, 2)))) == Basis( + *( + Perm((2, 3, 16, 14, 0, 6, 7, 13, 8, 11, 9, 15, 5, 1, 10, 12, 4, 17)), + Perm( + (4, 3, 9, 6, 1, 7, 5, 0, 12, 17, 16, 11, 18, 14, 10, 19, 13, 15, 2, 8) + ), + Perm((12, 5, 14, 0, 2, 8, 7, 6, 9, 13, 10, 11, 3, 4, 1)), + Perm((0, 4, 3, 6, 2, 1, 5)), + Perm((0, 2, 4, 3, 7, 5, 1, 6, 8)), + Perm((0, 1, 2)), + Perm((4, 3, 8, 9, 7, 5, 1, 6, 2, 0)), + Perm((8, 13, 6, 4, 10, 7, 2, 12, 3, 0, 11, 1, 15, 5, 14, 16, 9)), + Perm((2, 4, 0, 1, 6, 7, 3, 5)), + Perm((5, 1, 4, 7, 3, 2, 0, 6, 8, 9)), + Perm((0, 2, 1)), + Perm((6, 0, 2, 1, 5, 3, 4)), + ) + ) + assert Basis(*(Perm((0, 1, 2)), Perm((5, 2, 4, 3, 0, 1)))) == Basis( + *( + Perm( + (16, 14, 5, 8, 19, 4, 12, 15, 9, 10, 7, 18, 0, 2, 13, 3, 6, 17, 11, 1) + ), + Perm((12, 16, 9, 4, 5, 0, 10, 15, 13, 1, 14, 8, 17, 11, 6, 18, 7, 2, 3)), + Perm( + (8, 14, 19, 0, 12, 11, 2, 4, 16, 7, 18, 13, 15, 17, 6, 5, 3, 9, 10, 1) + ), + Perm((5, 2, 4, 3, 0, 1)), + Perm( + (8, 15, 7, 4, 9, 14, 17, 3, 10, 6, 19, 18, 2, 16, 0, 1, 5, 11, 13, 12) + ), + Perm((3, 7, 4, 6, 1, 5, 8, 9, 0, 10, 2)), + Perm((9, 3, 8, 6, 12, 7, 11, 5, 10, 4, 13, 0, 1, 2)), + Perm((0, 1, 2)), + Perm((8, 11, 12, 15, 5, 9, 16, 13, 0, 4, 10, 6, 17, 7, 14, 1, 2, 3)), + Perm((16, 11, 4, 5, 2, 10, 0, 12, 15, 14, 8, 6, 17, 9, 13, 3, 1, 7)), + Perm((2, 1, 17, 16, 13, 9, 3, 15, 18, 10, 8, 6, 12, 14, 4, 0, 7, 11, 5)), + Perm((14, 8, 2, 11, 10, 13, 7, 16, 0, 15, 3, 1, 12, 9, 6, 4, 5)), + ) + ) + assert Basis( + *( + Perm((2, 0, 1)), + Perm((1, 2, 3, 5, 4, 0)), + Perm((2, 0, 1, 3)), + Perm((1, 2, 3, 5, 4, 0, 6)), + ) + ) == Basis( + *( + Perm( + (7, 10, 18, 12, 13, 15, 11, 4, 17, 2, 9, 3, 1, 14, 8, 0, 5, 6, 16, 19) + ), + Perm((1, 2, 3, 5, 4, 0)), + Perm((0, 14, 6, 13, 12, 2, 5, 3, 11, 16, 17, 4, 15, 8, 1, 10, 9, 7)), + Perm((10, 3, 14, 18, 12, 8, 13, 6, 9, 16, 2, 17, 1, 7, 15, 11, 4, 5, 0)), + Perm((2, 0, 1)), + Perm((12, 14, 2, 18, 1, 11, 13, 17, 10, 7, 15, 4, 9, 8, 16, 5, 6, 0, 3)), + Perm((5, 3, 7, 9, 10, 1, 4, 0, 2, 12, 11, 8, 6)), + Perm( + (17, 10, 8, 11, 6, 2, 12, 4, 1, 16, 0, 5, 3, 18, 14, 13, 15, 9, 7, 19) + ), + Perm((7, 11, 12, 5, 3, 14, 0, 6, 8, 4, 15, 1, 2, 9, 17, 16, 13, 10)), + Perm((8, 1, 12, 14, 17, 2, 13, 10, 11, 5, 7, 15, 9, 0, 3, 6, 4, 16, 18)), + Perm((7, 3, 1, 17, 0, 4, 18, 5, 16, 10, 15, 13, 11, 9, 12, 2, 14, 6, 8)), + Perm( + (3, 8, 19, 17, 18, 15, 7, 0, 6, 4, 1, 16, 13, 5, 14, 9, 2, 10, 11, 12) + ), + ) + ) + + +def test_alternative_construction_methods(): + assert ( + Basis.from_string("123_321") + == Basis.from_iterable([Perm((0, 1, 2)), Perm((2, 1, 0))]) + == Basis(Perm((0, 1, 2)), Perm((2, 1, 0))) + == Basis.from_string("012:210") + ) + assert MeshBasis.from_iterable( + [MeshPatt(Perm((1, 2, 0)), [(0, 0), (0, 2), (1, 1), (1, 3), (2, 0), (2, 1)])] + ) == MeshBasis( + MeshPatt(Perm((1, 2, 0)), [(0, 0), (0, 2), (1, 1), (1, 3), (2, 0), (2, 1)]) + ) diff --git a/tests/permutils/test_finite.py b/tests/permutils/test_finite.py new file mode 100644 index 00000000..5595302b --- /dev/null +++ b/tests/permutils/test_finite.py @@ -0,0 +1,37 @@ +from random import randint + +from permuta import Perm +from permuta.permutils.finite import is_finite + + +def test_is_finite(): + assert is_finite([Perm()]) + assert is_finite([Perm((0,))]) + for i in range(100): + basis = [ + Perm.monotone_decreasing(randint(0, 100)), + Perm.monotone_increasing(randint(0, 100)), + ] + basis.extend([Perm.random(randint(0, 100)) for _ in range(randint(0, 10))]) + assert is_finite(basis) + assert not is_finite( + ( + p + for p in (Perm.random(randint(0, 100)) for _ in range(10)) + if not p.is_increasing() + ) + ) + assert not is_finite( + ( + p + for p in (Perm.random(randint(0, 100)) for _ in range(10)) + if not p.is_decreasing() + ) + ) + assert not is_finite([Perm((0, 1, 2))]) + assert not is_finite([Perm((2, 1, 0))]) + assert not is_finite((Perm.identity(i) for i in range(2, 10))) + assert not is_finite((p for p in (Perm((1, 2, 0)), Perm((4, 1, 2, 0, 3))))) + assert not is_finite((p for p in (Perm((1, 2)),))) + # Old version failed on this + assert is_finite((p for p in (Perm((0, 1)), Perm((1, 0))))) diff --git a/tests/permutils/test_function_imports.py b/tests/permutils/test_function_imports.py new file mode 100644 index 00000000..01d1f6a2 --- /dev/null +++ b/tests/permutils/test_function_imports.py @@ -0,0 +1,94 @@ +from permuta import Perm +from permuta.permutils import ( + is_insertion_encodable, + is_insertion_encodable_maximum, + is_insertion_encodable_rightmost, + is_non_polynomial, + is_polynomial, +) + + +def test_functions_polynomial(): + assert is_polynomial( + frozenset( + { + Perm((1, 0)), + Perm((0, 2, 1, 3)), + Perm((0, 1, 2)), + Perm((2, 1, 0)), + Perm((0, 2, 3, 1)), + } + ) + ) + assert is_non_polynomial( + frozenset( + { + Perm((1, 0, 2, 3)), + Perm((2, 0, 4, 1, 3)), + Perm((2, 3, 4, 1, 0)), + Perm((3, 4, 1, 0, 2)), + } + ) + ) + + +def test_functions_insertion_encodable(): + assert is_insertion_encodable_rightmost( + [ + Perm(()), + Perm((1, 0)), + Perm((0, 2, 1)), + Perm((1, 2, 0)), + Perm((0, 1, 4, 2, 3)), + Perm((2, 3, 0, 1, 4)), + Perm((4, 7, 3, 0, 1, 5, 6, 2)), + Perm((4, 7, 3, 2, 5, 6, 0, 1)), + ] + ) + assert not is_insertion_encodable_rightmost( + [ + Perm((3, 2, 1, 4, 0)), + Perm((4, 2, 3, 0, 1)), + Perm((2, 3, 4, 0, 1, 5)), + Perm((3, 2, 5, 4, 0, 1)), + ] + ) + + assert is_insertion_encodable_maximum( + [ + Perm((0, 3, 2, 1)), + Perm((1, 2, 3, 0)), + Perm((0, 2, 1, 4, 3)), + Perm((6, 1, 0, 5, 3, 4, 2)), + Perm((6, 0, 2, 5, 1, 4, 7, 3)), + ] + ) + assert not is_insertion_encodable_maximum( + [ + Perm((1, 0, 2)), + Perm((1, 3, 2, 0)), + Perm((1, 5, 4, 2, 0, 3)), + Perm((4, 2, 0, 3, 5, 1)), + Perm((4, 3, 2, 0, 5, 1)), + Perm((5, 1, 2, 4, 3, 0, 6)), + ] + ) + + assert is_insertion_encodable( + [ + Perm((0, 3, 2, 1)), + Perm((0, 2, 3, 4, 1)), + Perm((3, 0, 1, 2, 4)), + Perm((4, 2, 1, 0, 3, 5)), + Perm((0, 2, 1, 4, 6, 3, 5)), + Perm((6, 2, 1, 0, 5, 4, 3)), + ] + ) + assert not is_insertion_encodable( + [ + Perm((0, 2, 1, 3)), + Perm((1, 2, 3, 0, 4)), + Perm((3, 1, 0, 2, 4)), + Perm((3, 1, 4, 2, 0)), + ] + ) diff --git a/tests/permutils/test_groups.py b/tests/permutils/test_groups.py new file mode 100644 index 00000000..948430a2 --- /dev/null +++ b/tests/permutils/test_groups.py @@ -0,0 +1,132 @@ +from permuta import Perm +from permuta.permutils.groups import dihedral_group + + +def test_dihedral_group(): + first_11 = { + 0: set(), + 1: set(), + 2: set(), + 3: { + Perm((1, 0, 2)), + Perm((2, 0, 1)), + Perm((0, 1, 2)), + Perm((2, 1, 0)), + Perm((1, 2, 0)), + Perm((0, 2, 1)), + }, + 4: { + Perm((3, 2, 1, 0)), + Perm((0, 3, 2, 1)), + Perm((3, 0, 1, 2)), + Perm((2, 1, 0, 3)), + Perm((2, 3, 0, 1)), + Perm((1, 2, 3, 0)), + Perm((1, 0, 3, 2)), + Perm((0, 1, 2, 3)), + }, + 5: { + Perm((0, 1, 2, 3, 4)), + Perm((3, 2, 1, 0, 4)), + Perm((4, 0, 1, 2, 3)), + Perm((0, 4, 3, 2, 1)), + Perm((4, 3, 2, 1, 0)), + Perm((1, 0, 4, 3, 2)), + Perm((3, 4, 0, 1, 2)), + Perm((2, 3, 4, 0, 1)), + Perm((1, 2, 3, 4, 0)), + Perm((2, 1, 0, 4, 3)), + }, + 6: { + Perm((3, 4, 5, 0, 1, 2)), + Perm((4, 3, 2, 1, 0, 5)), + Perm((2, 1, 0, 5, 4, 3)), + Perm((0, 1, 2, 3, 4, 5)), + Perm((2, 3, 4, 5, 0, 1)), + Perm((3, 2, 1, 0, 5, 4)), + Perm((1, 0, 5, 4, 3, 2)), + Perm((4, 5, 0, 1, 2, 3)), + Perm((5, 4, 3, 2, 1, 0)), + Perm((5, 0, 1, 2, 3, 4)), + Perm((0, 5, 4, 3, 2, 1)), + Perm((1, 2, 3, 4, 5, 0)), + }, + 7: { + Perm((2, 3, 4, 5, 6, 0, 1)), + Perm((5, 6, 0, 1, 2, 3, 4)), + Perm((0, 1, 2, 3, 4, 5, 6)), + Perm((4, 3, 2, 1, 0, 6, 5)), + Perm((1, 0, 6, 5, 4, 3, 2)), + Perm((0, 6, 5, 4, 3, 2, 1)), + Perm((3, 2, 1, 0, 6, 5, 4)), + Perm((6, 5, 4, 3, 2, 1, 0)), + Perm((4, 5, 6, 0, 1, 2, 3)), + Perm((2, 1, 0, 6, 5, 4, 3)), + Perm((1, 2, 3, 4, 5, 6, 0)), + Perm((6, 0, 1, 2, 3, 4, 5)), + Perm((3, 4, 5, 6, 0, 1, 2)), + Perm((5, 4, 3, 2, 1, 0, 6)), + }, + 8: { + Perm((1, 0, 7, 6, 5, 4, 3, 2)), + Perm((7, 0, 1, 2, 3, 4, 5, 6)), + Perm((4, 5, 6, 7, 0, 1, 2, 3)), + Perm((7, 6, 5, 4, 3, 2, 1, 0)), + Perm((2, 1, 0, 7, 6, 5, 4, 3)), + Perm((6, 5, 4, 3, 2, 1, 0, 7)), + Perm((1, 2, 3, 4, 5, 6, 7, 0)), + Perm((0, 7, 6, 5, 4, 3, 2, 1)), + Perm((2, 3, 4, 5, 6, 7, 0, 1)), + Perm((0, 1, 2, 3, 4, 5, 6, 7)), + Perm((3, 2, 1, 0, 7, 6, 5, 4)), + Perm((6, 7, 0, 1, 2, 3, 4, 5)), + Perm((5, 6, 7, 0, 1, 2, 3, 4)), + Perm((5, 4, 3, 2, 1, 0, 7, 6)), + Perm((3, 4, 5, 6, 7, 0, 1, 2)), + Perm((4, 3, 2, 1, 0, 7, 6, 5)), + }, + 9: { + Perm((3, 4, 5, 6, 7, 8, 0, 1, 2)), + Perm((7, 8, 0, 1, 2, 3, 4, 5, 6)), + Perm((4, 5, 6, 7, 8, 0, 1, 2, 3)), + Perm((0, 1, 2, 3, 4, 5, 6, 7, 8)), + Perm((5, 4, 3, 2, 1, 0, 8, 7, 6)), + Perm((2, 3, 4, 5, 6, 7, 8, 0, 1)), + Perm((8, 7, 6, 5, 4, 3, 2, 1, 0)), + Perm((0, 8, 7, 6, 5, 4, 3, 2, 1)), + Perm((2, 1, 0, 8, 7, 6, 5, 4, 3)), + Perm((3, 2, 1, 0, 8, 7, 6, 5, 4)), + Perm((4, 3, 2, 1, 0, 8, 7, 6, 5)), + Perm((5, 6, 7, 8, 0, 1, 2, 3, 4)), + Perm((6, 5, 4, 3, 2, 1, 0, 8, 7)), + Perm((7, 6, 5, 4, 3, 2, 1, 0, 8)), + Perm((6, 7, 8, 0, 1, 2, 3, 4, 5)), + Perm((8, 0, 1, 2, 3, 4, 5, 6, 7)), + Perm((1, 0, 8, 7, 6, 5, 4, 3, 2)), + Perm((1, 2, 3, 4, 5, 6, 7, 8, 0)), + }, + 10: { + Perm((3, 2, 1, 0, 9, 8, 7, 6, 5, 4)), + Perm((6, 5, 4, 3, 2, 1, 0, 9, 8, 7)), + Perm((4, 5, 6, 7, 8, 9, 0, 1, 2, 3)), + Perm((6, 7, 8, 9, 0, 1, 2, 3, 4, 5)), + Perm((0, 1, 2, 3, 4, 5, 6, 7, 8, 9)), + Perm((5, 6, 7, 8, 9, 0, 1, 2, 3, 4)), + Perm((3, 4, 5, 6, 7, 8, 9, 0, 1, 2)), + Perm((5, 4, 3, 2, 1, 0, 9, 8, 7, 6)), + Perm((7, 8, 9, 0, 1, 2, 3, 4, 5, 6)), + Perm((9, 8, 7, 6, 5, 4, 3, 2, 1, 0)), + Perm((9, 0, 1, 2, 3, 4, 5, 6, 7, 8)), + Perm((2, 3, 4, 5, 6, 7, 8, 9, 0, 1)), + Perm((0, 9, 8, 7, 6, 5, 4, 3, 2, 1)), + Perm((2, 1, 0, 9, 8, 7, 6, 5, 4, 3)), + Perm((8, 7, 6, 5, 4, 3, 2, 1, 0, 9)), + Perm((1, 0, 9, 8, 7, 6, 5, 4, 3, 2)), + Perm((1, 2, 3, 4, 5, 6, 7, 8, 9, 0)), + Perm((8, 9, 0, 1, 2, 3, 4, 5, 6, 7)), + Perm((4, 3, 2, 1, 0, 9, 8, 7, 6, 5)), + Perm((7, 6, 5, 4, 3, 2, 1, 0, 9, 8)), + }, + } + for i in range(11): + assert set(dihedral_group(i)) == first_11[i] diff --git a/tests/permutils/test_insertion_encodable.py b/tests/permutils/test_insertion_encodable.py new file mode 100644 index 00000000..ed42f385 --- /dev/null +++ b/tests/permutils/test_insertion_encodable.py @@ -0,0 +1,1033 @@ +from permuta import Perm +from permuta.permutils import InsertionEncodablePerms + + +def test_is_insertion_encodable_rightmost(): + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((0, 1)), + Perm((0, 1, 2)), + Perm((4, 0, 2, 3, 5, 1)), + Perm((0, 6, 3, 4, 2, 5, 1)), + Perm((6, 0, 3, 7, 5, 1, 2, 4)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm(()), + Perm((0, 1)), + Perm((1, 0)), + Perm((3, 0, 1, 2)), + Perm((2, 1, 3, 0, 4)), + Perm((4, 1, 3, 2, 5, 0)), + Perm((5, 1, 2, 3, 4, 0)), + Perm((2, 0, 4, 1, 5, 6, 3)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((0,)), + Perm((1, 0, 2, 3)), + Perm((0, 4, 1, 2, 3)), + Perm((6, 0, 1, 7, 4, 5, 2, 3)), + Perm((7, 3, 6, 4, 2, 5, 1, 0)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm(()), + Perm((1, 0)), + Perm((2, 1, 0)), + Perm((0, 3, 1, 2)), + Perm((3, 2, 0, 5, 7, 4, 6, 1)), + Perm((4, 1, 5, 7, 0, 3, 2, 6)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm(()), + Perm((1, 0)), + Perm((0, 2, 1)), + Perm((1, 2, 0)), + Perm((0, 1, 4, 2, 3)), + Perm((2, 3, 0, 1, 4)), + Perm((4, 7, 3, 0, 1, 5, 6, 2)), + Perm((4, 7, 3, 2, 5, 6, 0, 1)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((3, 2, 1, 4, 0)), + Perm((4, 2, 3, 0, 1)), + Perm((2, 3, 4, 0, 1, 5)), + Perm((3, 2, 5, 4, 0, 1)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [Perm((4, 2, 5, 0, 3, 1)), Perm((6, 5, 3, 4, 1, 0, 2))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [Perm((2, 1, 0)), Perm((3, 1, 2, 0))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [Perm((6, 1, 3, 0, 2, 4, 5)), Perm((4, 3, 5, 6, 0, 2, 1, 7))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [Perm((1, 0, 2)), Perm((0, 2, 3, 1, 4))] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((0, 1, 2)), + Perm((0, 2, 1)), + Perm((1, 0, 2, 3)), + Perm((3, 1, 0, 2)), + Perm((3, 4, 1, 2, 0)), + Perm((1, 5, 2, 3, 0, 4)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((2, 0, 1)), + Perm((0, 2, 3, 1)), + Perm((3, 1, 0, 2)), + Perm((3, 2, 1, 4, 0)), + Perm((4, 0, 2, 3, 1)), + Perm((4, 2, 0, 5, 1, 3)), + Perm((6, 5, 0, 2, 7, 4, 3, 1)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((1, 0, 2)), + Perm((1, 2, 0)), + Perm((2, 1, 0)), + Perm((3, 6, 1, 5, 0, 2, 4)), + Perm((6, 1, 2, 5, 4, 3, 0)), + Perm((5, 4, 6, 0, 3, 2, 7, 1)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [Perm((1, 0, 2)), Perm((1, 3, 2, 0))] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((1, 2, 0)), + Perm((2, 3, 0, 1)), + Perm((3, 2, 1, 0)), + Perm((1, 3, 2, 0, 4)), + Perm((2, 1, 6, 0, 5, 4, 3)), + Perm((4, 5, 0, 2, 3, 6, 7, 1)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((2, 1, 0)), + Perm((1, 2, 0, 3)), + Perm((2, 0, 1, 3, 4)), + Perm((4, 0, 1, 2, 3)), + Perm((1, 3, 0, 5, 2, 4)), + Perm((1, 5, 3, 0, 2, 4)), + Perm((4, 3, 5, 0, 2, 1, 6)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((1, 0, 2)), + Perm((2, 3, 1, 0)), + Perm((0, 4, 1, 3, 2)), + Perm((6, 3, 5, 1, 2, 4, 0)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [Perm((0, 1, 2)), Perm((4, 2, 5, 3, 1, 0)), Perm((1, 2, 3, 0, 5, 6, 4))] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [Perm((1, 2, 0)), Perm((2, 4, 0, 1, 3)), Perm((7, 5, 4, 3, 1, 0, 2, 6))] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((0, 1, 2)), + Perm((1, 2, 0)), + Perm((1, 0, 3, 2)), + Perm((2, 0, 3, 1)), + Perm((5, 3, 2, 4, 0, 1)), + Perm((2, 0, 3, 4, 6, 5, 1)), + Perm((5, 1, 6, 2, 7, 0, 4, 3)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((0, 2, 1)), + Perm((2, 0, 1)), + Perm((2, 0, 3, 1, 4)), + Perm((4, 0, 3, 1, 2)), + Perm((4, 3, 1, 2, 0)), + Perm((4, 2, 5, 1, 0, 3)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((0, 2, 1)), + Perm((1, 2, 0)), + Perm((0, 3, 1, 2)), + Perm((1, 0, 3, 2)), + Perm((2, 4, 1, 0, 3)), + Perm((4, 3, 0, 1, 2)), + Perm((2, 5, 0, 3, 6, 4, 1)), + Perm((5, 7, 1, 6, 3, 4, 0, 2)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((0, 2, 1)), + Perm((2, 0, 1, 3)), + Perm((1, 3, 0, 2, 4)), + Perm((5, 0, 4, 3, 2, 7, 6, 1)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((0, 2, 1)), + Perm((2, 1, 0)), + Perm((3, 0, 2, 1)), + Perm((0, 1, 2, 3, 4)), + Perm((0, 1, 3, 2, 4)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((0, 1, 2)), + Perm((2, 0, 1, 3)), + Perm((2, 1, 3, 0)), + Perm((1, 0, 4, 2, 3)), + Perm((1, 6, 2, 3, 4, 0, 5)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((2, 0, 1)), + Perm((3, 1, 0, 4, 2)), + Perm((1, 6, 4, 2, 0, 3, 7, 5)), + Perm((2, 6, 1, 5, 4, 7, 0, 3)), + Perm((3, 0, 7, 2, 1, 4, 5, 6)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [Perm((1, 0, 2))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((2, 0, 1)), + Perm((3, 1, 0, 2)), + Perm((1, 0, 5, 2, 4, 3)), + Perm((4, 3, 0, 5, 6, 1, 2)), + Perm((1, 2, 5, 6, 7, 3, 0, 4)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((3, 4, 2, 1, 0)), + Perm((3, 6, 2, 0, 5, 4, 1)), + Perm((6, 3, 0, 2, 4, 5, 1)), + Perm((5, 7, 0, 4, 2, 1, 3, 6)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((3, 1, 0, 2)), + Perm((4, 0, 2, 3, 1)), + Perm((2, 5, 4, 1, 0, 3)), + Perm((0, 6, 1, 2, 4, 5, 3)), + Perm((6, 2, 4, 1, 5, 7, 3, 0)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((1, 2, 0)), + Perm((1, 2, 0, 3)), + Perm((1, 5, 7, 2, 3, 4, 6, 0)), + Perm((3, 6, 7, 4, 5, 2, 0, 1)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [Perm((2, 5, 1, 3, 0, 4))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [Perm((4, 0, 3, 1, 2)), Perm((1, 3, 5, 2, 0, 4)), Perm((1, 5, 4, 0, 3, 2))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [Perm((1, 2, 0)), Perm((4, 1, 2, 5, 0, 3)), Perm((1, 4, 0, 6, 2, 3, 7, 5))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((1, 2, 0)), + Perm((3, 4, 1, 5, 2, 0)), + Perm((1, 4, 3, 5, 6, 2, 0)), + Perm((3, 2, 4, 1, 6, 5, 0)), + Perm((3, 0, 4, 2, 6, 7, 5, 1)), + Perm((4, 1, 3, 6, 5, 2, 7, 0)), + Perm((4, 2, 5, 0, 6, 1, 3, 7)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((2, 1, 3, 0)), + Perm((2, 1, 3, 0, 4)), + Perm((4, 0, 3, 1, 2)), + Perm((1, 3, 4, 2, 5, 0)), + Perm((2, 0, 6, 4, 1, 3, 5)), + Perm((3, 4, 1, 6, 2, 5, 7, 0)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [Perm((1, 3, 2, 0)), Perm((0, 4, 2, 1, 3)), Perm((4, 0, 3, 2, 1))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [ + Perm((2, 1, 3, 0)), + Perm((3, 1, 2, 0)), + Perm((1, 2, 0, 4, 3)), + Perm((4, 2, 1, 0, 3)), + Perm((0, 1, 2, 5, 3, 4)), + Perm((3, 2, 5, 4, 0, 1)), + Perm((2, 5, 7, 3, 4, 0, 6, 1)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [Perm((3, 5, 0, 1, 4, 2))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_rightmost( + [Perm((0, 1, 2)), Perm((6, 1, 3, 0, 4, 2, 5, 7))] + ) + + +def test_is_insertion_encodable_maximum(): + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((0,)), Perm((0, 1)), Perm((2, 0, 1, 3)), Perm((2, 1, 3, 0))] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((0, 1, 2)), + Perm((1, 2, 0)), + Perm((0, 1, 2, 4, 3)), + Perm((2, 0, 4, 1, 3)), + Perm((3, 0, 6, 1, 4, 2, 5)), + Perm((3, 5, 1, 4, 6, 0, 2)), + Perm((6, 1, 2, 3, 0, 5, 7, 4)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((0, 2, 1)), Perm((2, 1, 0)), Perm((3, 1, 2, 0, 4, 5))] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((1, 2, 0)), + Perm((2, 0, 1)), + Perm((2, 3, 1, 0)), + Perm((3, 1, 4, 0, 2)), + Perm((0, 5, 4, 1, 3, 2)), + Perm((3, 2, 5, 4, 0, 1)), + Perm((0, 5, 3, 6, 4, 1, 7, 2)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((1, 2, 0)), + Perm((2, 1, 0)), + Perm((1, 2, 0, 3)), + Perm((2, 3, 1, 0, 4)), + Perm((1, 2, 5, 6, 3, 4, 0)), + Perm((5, 4, 6, 1, 0, 3, 2)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((2, 1, 0, 3)), Perm((3, 1, 0, 2)), Perm((0, 1, 4, 2, 3))] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((0, 1, 2)), + Perm((0, 2, 1)), + Perm((2, 0, 1)), + Perm((2, 1, 0)), + Perm((1, 3, 2, 0)), + Perm((1, 4, 3, 2, 0)), + Perm((1, 2, 4, 0, 3, 6, 5)), + Perm((1, 7, 0, 3, 4, 5, 6, 2)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((2, 0, 1)), + Perm((1, 2, 3, 0)), + Perm((3, 0, 2, 1, 4)), + Perm((4, 1, 6, 3, 0, 5, 2)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((1, 0)), Perm((0, 2, 1)), Perm((1, 2, 0)), Perm((2, 1, 0))] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm(()), Perm((0, 1, 3, 2))] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((0, 2, 1)), + Perm((0, 1, 2, 3)), + Perm((0, 3, 5, 1, 4, 2)), + Perm((3, 5, 2, 6, 0, 1, 4, 7)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((2, 0, 1, 3)), + Perm((3, 1, 2, 0)), + Perm((3, 2, 1, 0)), + Perm((2, 3, 4, 1, 0)), + Perm((4, 2, 0, 3, 5, 1)), + Perm((6, 5, 4, 3, 7, 1, 2, 0)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((0, 1, 2, 3)), Perm((3, 2, 5, 1, 4, 0))] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((0, 1)), Perm((0, 1, 2)), Perm((2, 1, 4, 0, 3)), Perm((3, 2, 0, 1, 4))] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((0, 1, 2)), + Perm((0, 2, 1)), + Perm((0, 2, 1, 4, 3)), + Perm((2, 1, 4, 3, 0)), + Perm((2, 4, 3, 5, 1, 0)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((0, 1, 2)), + Perm((2, 0, 1)), + Perm((0, 4, 2, 1, 3)), + Perm((1, 0, 3, 4, 2)), + Perm((3, 2, 4, 0, 1)), + Perm((1, 4, 5, 3, 0, 2)), + Perm((0, 4, 2, 5, 1, 3, 6)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((0, 2, 1)), + Perm((0, 3, 2, 1)), + Perm((1, 2, 3, 0)), + Perm((1, 2, 3, 4, 0)), + Perm((1, 4, 0, 3, 2)), + Perm((4, 0, 1, 5, 2, 3)), + Perm((3, 6, 1, 2, 0, 5, 4)), + Perm((4, 0, 5, 1, 6, 2, 3)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((0, 2, 1)), + Perm((1, 2, 0, 3)), + Perm((1, 3, 4, 0, 2)), + Perm((1, 2, 5, 4, 3, 0)), + Perm((5, 0, 2, 1, 4, 3)), + Perm((4, 5, 2, 6, 3, 1, 0)), + Perm((5, 0, 1, 4, 6, 3, 2)), + Perm((5, 0, 3, 1, 6, 2, 4)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((0, 2, 1)), + Perm((1, 2, 0)), + Perm((3, 1, 2, 0)), + Perm((3, 2, 4, 0, 1)), + Perm((2, 0, 5, 1, 3, 4)), + Perm((3, 5, 0, 1, 4, 2, 7, 6)), + Perm((4, 5, 2, 0, 3, 6, 1, 7)), + Perm((6, 3, 2, 0, 1, 7, 4, 5)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((0, 3, 2, 1)), + Perm((1, 2, 3, 0)), + Perm((0, 2, 1, 4, 3)), + Perm((6, 1, 0, 5, 3, 4, 2)), + Perm((6, 0, 2, 5, 1, 4, 7, 3)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((3, 0, 5, 2, 4, 1))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((1, 0, 2)), + Perm((1, 3, 2, 0)), + Perm((1, 5, 4, 2, 0, 3)), + Perm((4, 2, 0, 3, 5, 1)), + Perm((4, 3, 2, 0, 5, 1)), + Perm((5, 1, 2, 4, 3, 0, 6)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((2, 0, 1)), + Perm((1, 2, 0, 4, 3)), + Perm((3, 0, 2, 1, 4, 5)), + Perm((5, 3, 2, 0, 1, 6, 4)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((0, 4, 3, 1, 2))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((0, 2, 1)), Perm((0, 4, 2, 3, 1))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((2, 3, 1, 5, 0, 4)), Perm((6, 2, 4, 1, 3, 7, 5, 0))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((0, 4, 2, 1, 3)), + Perm((1, 4, 0, 2, 3)), + Perm((3, 1, 2, 5, 6, 0, 4)), + Perm((0, 4, 6, 5, 7, 1, 2, 3)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((1, 0, 3, 4, 2))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((2, 0, 1, 3))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((1, 3, 2, 4, 0))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((0, 2, 1, 3)), + Perm((0, 2, 3, 1)), + Perm((0, 5, 3, 1, 2, 6, 4)), + Perm((2, 4, 3, 0, 1, 6, 7, 5)), + Perm((2, 7, 1, 4, 5, 6, 0, 3)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((3, 0, 2, 1)), + Perm((3, 1, 2, 0)), + Perm((3, 1, 0, 4, 2)), + Perm((4, 0, 2, 3, 1)), + Perm((4, 0, 3, 1, 2)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((1, 5, 6, 4, 2, 0, 3))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((3, 2, 1, 0, 4)), + Perm((4, 3, 2, 0, 5, 1)), + Perm((5, 4, 2, 3, 0, 1)), + Perm((0, 3, 2, 1, 4, 6, 5)), + Perm((2, 3, 0, 5, 1, 6, 4)), + Perm((3, 6, 2, 4, 1, 0, 5)), + Perm((0, 5, 3, 4, 2, 6, 7, 1)), + Perm((0, 6, 4, 3, 7, 1, 5, 2)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((1, 2, 0)), Perm((4, 2, 5, 0, 1, 3)), Perm((6, 3, 5, 1, 7, 4, 0, 2))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((3, 0, 2, 1)), Perm((4, 2, 1, 3, 0))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((3, 2, 0, 1)), Perm((6, 1, 3, 2, 4, 0, 5))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((0, 2, 3, 1)), + Perm((0, 3, 2, 1)), + Perm((3, 0, 1, 2)), + Perm((4, 3, 2, 0, 5, 1)), + Perm((1, 0, 6, 4, 3, 5, 2)), + Perm((3, 1, 6, 2, 5, 0, 4)), + Perm((5, 2, 6, 7, 0, 3, 1, 4)), + Perm((7, 1, 6, 2, 5, 0, 4, 3)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [Perm((0, 2, 1)), Perm((3, 1, 2, 0))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable_maximum( + [ + Perm((0, 2, 1)), + Perm((4, 0, 2, 1, 3)), + Perm((2, 5, 4, 1, 3, 0)), + Perm((3, 5, 4, 0, 2, 1)), + Perm((1, 0, 4, 2, 3, 6, 5)), + Perm((6, 3, 2, 0, 5, 1, 4)), + Perm((4, 0, 1, 2, 3, 5, 6, 7)), + ] + ) + + +def test_is_insertion_encodable(): + assert InsertionEncodablePerms.is_insertion_encodable( + [Perm(()), Perm((0, 1)), Perm((0, 1, 2))] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 3, 2, 1)), + Perm((0, 2, 3, 4, 1)), + Perm((3, 0, 1, 2, 4)), + Perm((4, 2, 1, 0, 3, 5)), + Perm((0, 2, 1, 4, 6, 3, 5)), + Perm((6, 2, 1, 0, 5, 4, 3)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [Perm((0, 1)), Perm((1, 0)), Perm((1, 0, 3, 2))] + ) + assert InsertionEncodablePerms.is_insertion_encodable([Perm(()), Perm((1, 0))]) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 1, 2)), + Perm((2, 0, 1)), + Perm((0, 2, 4, 3, 1)), + Perm((1, 2, 4, 0, 3)), + Perm((3, 2, 4, 1, 5, 0)), + Perm((2, 0, 3, 6, 4, 1, 5)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [Perm((1, 0)), Perm((0, 1, 2)), Perm((0, 3, 1, 2)), Perm((1, 3, 2, 0))] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((2, 1, 0)), + Perm((0, 3, 1, 2)), + Perm((0, 3, 2, 1)), + Perm((3, 2, 1, 0)), + Perm((2, 1, 3, 5, 0, 4)), + Perm((3, 1, 5, 2, 4, 0)), + Perm((5, 1, 4, 3, 0, 2, 6)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [Perm(()), Perm((1, 0)), Perm((2, 0, 1)), Perm((0, 1, 2, 4, 3))] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [Perm(()), Perm((0,)), Perm((0, 1)), Perm((1, 0)), Perm((0, 3, 1, 2, 4))] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 2, 1)), + Perm((3, 2, 0, 1)), + Perm((2, 1, 0, 3, 4)), + Perm((3, 4, 2, 0, 5, 1)), + Perm((4, 2, 7, 5, 0, 6, 3, 1)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable([Perm(()), Perm((2, 1, 0))]) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((2, 0, 1)), + Perm((3, 0, 1, 2)), + Perm((1, 0, 2, 3, 4)), + Perm((2, 3, 0, 1, 4)), + Perm((3, 4, 1, 0, 2)), + Perm((4, 3, 1, 6, 0, 7, 2, 5)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 1, 2)), + Perm((1, 2, 0)), + Perm((0, 2, 1, 3)), + Perm((0, 3, 2, 1)), + Perm((3, 0, 1, 2)), + Perm((1, 0, 2, 4, 5, 6, 3)), + Perm((1, 3, 6, 7, 2, 5, 4, 0)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [Perm(()), Perm((0,)), Perm((1, 0)), Perm((4, 0, 1, 2, 3))] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 1, 2)), + Perm((2, 1, 0)), + Perm((1, 2, 0, 3, 4)), + Perm((3, 1, 0, 4, 2)), + Perm((0, 5, 1, 4, 3, 2, 6)), + Perm((2, 5, 4, 3, 6, 0, 1)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((1, 0, 2)), + Perm((2, 3, 1, 0)), + Perm((0, 1, 4, 3, 2)), + Perm((0, 4, 3, 1, 2)), + Perm((4, 0, 1, 3, 2)), + Perm((0, 3, 2, 1, 5, 4)), + Perm((2, 4, 0, 3, 5, 1)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((2, 0, 1)), + Perm((3, 0, 1, 2)), + Perm((0, 1, 4, 3, 2)), + Perm((0, 5, 4, 7, 6, 3, 1, 2)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [Perm(()), Perm((1, 0, 2)), Perm((2, 0, 1, 3)), Perm((0, 3, 1, 2, 4))] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((1, 2, 0)), + Perm((2, 0, 1)), + Perm((2, 1, 0)), + Perm((3, 0, 2, 1)), + Perm((1, 4, 2, 3, 5, 0)), + Perm((1, 5, 0, 2, 3, 7, 6, 4)), + Perm((4, 6, 5, 0, 2, 1, 3, 7)), + Perm((7, 0, 1, 6, 3, 5, 4, 2)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 2, 1)), + Perm((2, 0, 1, 3)), + Perm((0, 3, 2, 4, 1)), + Perm((3, 4, 2, 0, 1)), + Perm((1, 4, 5, 2, 0, 3)), + Perm((4, 5, 3, 2, 0, 1)), + Perm((6, 3, 0, 1, 2, 5, 4)), + Perm((4, 3, 7, 5, 2, 1, 6, 0)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable([Perm((0,))]) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 1, 2)), + Perm((0, 2, 1)), + Perm((2, 0, 1)), + Perm((2, 3, 1, 0, 4)), + Perm((4, 1, 3, 0, 2)), + Perm((1, 3, 2, 0, 6, 4, 5)), + Perm((1, 4, 5, 3, 2, 6, 0)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((3, 2, 1, 0)), + Perm((1, 0, 2, 3, 4)), + Perm((3, 1, 4, 0, 2, 7, 5, 6)), + Perm((4, 5, 1, 7, 0, 6, 3, 2)), + Perm((6, 5, 2, 4, 3, 0, 1, 7)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [Perm((0,)), Perm((0, 1)), Perm((1, 2, 0)), Perm((2, 1, 0))] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [Perm((2, 0, 1)), Perm((2, 1, 3, 0, 4))] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((1, 0, 2)), + Perm((0, 2, 3, 1)), + Perm((2, 1, 3, 0)), + Perm((3, 0, 4, 1, 2)), + Perm((6, 5, 4, 2, 0, 3, 1)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 2, 1)), + Perm((2, 0, 1)), + Perm((2, 5, 4, 1, 0, 3)), + Perm((3, 2, 1, 5, 0, 4)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((1, 0, 2, 3)), + Perm((1, 3, 2, 0)), + Perm((2, 1, 3, 0, 4)), + Perm((5, 2, 1, 4, 3, 0)), + Perm((0, 5, 1, 3, 6, 4, 2)), + Perm((1, 4, 0, 2, 3, 5, 6)), + Perm((6, 1, 2, 0, 3, 5, 4)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [Perm(()), Perm((1, 0)), Perm((1, 2, 0, 3)), Perm((3, 1, 0, 2))] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 2, 1)), + Perm((1, 0, 2)), + Perm((1, 2, 0)), + Perm((3, 1, 2, 5, 4, 0)), + Perm((0, 2, 3, 5, 6, 4, 1)), + Perm((4, 5, 6, 3, 1, 0, 2)), + Perm((0, 4, 6, 7, 2, 3, 5, 1)), + Perm((7, 5, 3, 6, 2, 4, 0, 1)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [Perm((0, 1)), Perm((0, 1, 2)), Perm((1, 3, 0, 2)), Perm((0, 3, 4, 2, 1))] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((1, 0, 2)), + Perm((2, 0, 1)), + Perm((1, 3, 4, 0, 2)), + Perm((2, 6, 4, 5, 0, 1, 3)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((1, 2, 0)), + Perm((2, 1, 0)), + Perm((0, 2, 3, 1)), + Perm((0, 2, 3, 1, 4)), + Perm((0, 5, 3, 2, 4, 1)), + Perm((0, 4, 6, 1, 5, 3, 2)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((1, 2, 0)), + Perm((0, 2, 1, 3)), + Perm((0, 3, 2, 1)), + Perm((1, 0, 2, 3)), + Perm((3, 0, 1, 2, 4)), + Perm((4, 0, 1, 3, 2)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [Perm(()), Perm((3, 1, 2, 0)), Perm((1, 0, 3, 2, 4)), Perm((4, 2, 1, 0, 3))] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [Perm((1, 2, 3, 0)), Perm((2, 1, 0, 3)), Perm((2, 1, 3, 0))] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((1, 2, 0, 3)), + Perm((1, 2, 3, 0)), + Perm((4, 3, 0, 2, 1)), + Perm((2, 1, 4, 7, 6, 3, 0, 5)), + Perm((7, 4, 1, 3, 6, 5, 2, 0)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 2, 1)), + Perm((2, 3, 1, 0)), + Perm((3, 1, 0, 2)), + Perm((3, 1, 2, 0, 4)), + Perm((2, 3, 1, 0, 4, 5)), + Perm((1, 4, 7, 2, 6, 0, 5, 3)), + Perm((5, 0, 3, 1, 7, 2, 4, 6)), + ] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [Perm(()), Perm((0, 1, 3, 2)), Perm((2, 1, 4, 3, 0))] + ) + assert InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 1, 2)), + Perm((1, 0, 3, 2)), + Perm((0, 1, 4, 3, 2)), + Perm((1, 4, 3, 0, 2)), + Perm((4, 2, 1, 3, 0, 5)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((3, 1, 6, 2, 7, 0, 4, 5))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 3, 1, 2)), + Perm((1, 0, 3, 2)), + Perm((1, 3, 2, 0, 4)), + Perm((3, 2, 4, 0, 1)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((1, 2, 0)), + Perm((0, 2, 3, 1)), + Perm((2, 3, 1, 4, 0)), + Perm((2, 3, 4, 0, 1)), + Perm((4, 3, 1, 5, 0, 2)), + Perm((0, 3, 2, 6, 4, 1, 5)), + Perm((1, 5, 3, 2, 7, 6, 0, 4)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((2, 0, 1, 3)), + Perm((2, 1, 0, 3)), + Perm((3, 1, 0, 2)), + Perm((3, 1, 2, 0)), + Perm((6, 5, 1, 4, 0, 3, 2)), + Perm((4, 2, 1, 0, 6, 3, 7, 5)), + Perm((6, 2, 5, 3, 4, 1, 0, 7)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable([Perm((0, 3, 1, 2))]) + assert not InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 2, 1, 3)), + Perm((1, 2, 3, 0, 4)), + Perm((3, 1, 0, 2, 4)), + Perm((3, 1, 4, 2, 0)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((1, 0, 2)), Perm((3, 1, 2, 6, 4, 0, 5)), Perm((3, 4, 2, 1, 0, 5, 6))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 2, 1, 3)), + Perm((2, 0, 3, 4, 1)), + Perm((3, 4, 0, 1, 2)), + Perm((4, 0, 3, 1, 2)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 4, 3, 2, 1)), + Perm((1, 0, 2, 4, 3)), + Perm((2, 3, 0, 1, 4)), + Perm((3, 1, 4, 2, 0)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((4, 5, 0, 1, 2, 6, 3))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable([Perm((0, 3, 4, 2, 1))]) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((3, 5, 6, 0, 7, 4, 2, 1))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable([Perm((3, 1, 2, 4, 0))]) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((1, 0, 3, 2)), Perm((2, 3, 0, 1, 4, 6, 5)), Perm((5, 4, 2, 1, 3, 0, 6))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((0, 2, 1)), Perm((2, 7, 6, 5, 0, 4, 3, 1))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((2, 0, 1, 3)), + Perm((3, 0, 2, 1)), + Perm((1, 2, 4, 0, 3)), + Perm((2, 3, 4, 0, 1)), + Perm((3, 0, 2, 4, 1)), + Perm((4, 2, 1, 0, 3)), + Perm((4, 3, 7, 2, 0, 6, 1, 5)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((2, 1, 0)), Perm((1, 3, 4, 2, 0)), Perm((0, 2, 6, 5, 1, 4, 3))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 4, 5, 1, 3, 2)), + Perm((5, 0, 2, 4, 1, 3)), + Perm((0, 2, 4, 3, 6, 5, 1)), + Perm((3, 0, 4, 1, 2, 5, 7, 6)), + Perm((7, 5, 1, 6, 3, 2, 4, 0)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable([Perm((0, 2, 1, 3))]) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((0, 1, 2)), Perm((0, 4, 5, 1, 3, 2)), Perm((0, 6, 3, 5, 4, 1, 2))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((0, 2, 1)), Perm((0, 2, 1, 3)), Perm((0, 2, 3, 1))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((1, 2, 0)), Perm((2, 0, 3, 1)), Perm((2, 1, 3, 0))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable([Perm((3, 2, 1, 0))]) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((0, 1, 2)), Perm((2, 3, 0, 4, 1))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable([Perm((0, 1, 2, 3))]) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((0, 3, 1, 2)), Perm((3, 1, 0, 2, 4))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((2, 1, 0)), Perm((0, 4, 3, 2, 1)), Perm((4, 3, 0, 2, 1))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable([Perm((2, 1, 3, 0))]) + assert not InsertionEncodablePerms.is_insertion_encodable([Perm((1, 0, 2))]) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((1, 3, 4, 0, 2, 6, 5)), Perm((4, 0, 3, 1, 6, 5, 2))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((1, 2, 0)), Perm((3, 1, 2, 0))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((1, 2, 0, 3)), + Perm((4, 2, 0, 5, 1, 3)), + Perm((5, 4, 3, 1, 0, 2)), + Perm((6, 1, 5, 4, 0, 2, 3)), + Perm((7, 5, 4, 2, 0, 1, 6, 3)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((1, 3, 0, 2)), Perm((4, 0, 3, 2, 1))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((2, 3, 4, 5, 1, 0)), + Perm((1, 0, 5, 3, 4, 2, 6)), + Perm((0, 3, 6, 4, 1, 7, 2, 5)), + Perm((0, 6, 5, 3, 2, 1, 4, 7)), + Perm((4, 5, 1, 6, 0, 7, 3, 2)), + Perm((6, 3, 2, 5, 1, 4, 0, 7)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable([Perm((0, 1, 2))]) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((0, 1, 2)), Perm((0, 1, 2, 3, 4)), Perm((2, 5, 1, 0, 4, 6, 3))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [Perm((0, 3, 2, 1, 4)), Perm((4, 3, 1, 0, 2))] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((2, 0, 1)), + Perm((4, 2, 0, 1, 5, 6, 3)), + Perm((6, 7, 2, 4, 5, 1, 3, 0)), + Perm((7, 4, 2, 1, 3, 0, 5, 6)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((0, 1, 2)), + Perm((1, 3, 0, 2, 4)), + Perm((0, 1, 5, 3, 4, 6, 2)), + Perm((0, 2, 6, 1, 3, 4, 5)), + ] + ) + assert not InsertionEncodablePerms.is_insertion_encodable( + [ + Perm((4, 2, 3, 1, 0)), + Perm((0, 4, 6, 3, 5, 2, 1)), + Perm((2, 3, 0, 6, 1, 5, 4)), + ] + ) diff --git a/tests/permutils/test_polynomial.py b/tests/permutils/test_polynomial.py new file mode 100644 index 00000000..97333ef7 --- /dev/null +++ b/tests/permutils/test_polynomial.py @@ -0,0 +1,1343 @@ +from permuta import Perm +from permuta.permutils.polynomial import PolyPerms + +expected = { + frozenset( + { + Perm((1, 0, 2, 3)), + Perm((1, 2, 0, 3)), + Perm((0, 3, 1, 2)), + Perm((2, 4, 0, 3, 1)), + Perm((3, 4, 2, 1, 0)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((2, 0, 3, 1)), + Perm((1, 0)), + Perm((3, 2, 0, 1)), + Perm((0, 1, 2)), + Perm((0, 3, 1, 2, 4)), + Perm((1, 2, 3, 0)), + Perm((3, 0, 4, 1, 2)), + } + ): True, + frozenset( + { + Perm((1, 0)), + Perm((1, 0, 2)), + Perm((1, 0, 2, 3)), + Perm((0, 2, 1, 3, 4)), + Perm((2, 1, 3, 4, 0)), + Perm((0, 2, 1)), + } + ): True, + frozenset( + { + Perm((0, 2, 1, 3)), + Perm((1, 0, 2, 3)), + Perm((1, 2, 0, 3)), + Perm((1, 0, 2)), + Perm((2, 1, 0)), + } + ): True, + frozenset( + { + Perm((1, 0)), + Perm((3, 0, 1, 2)), + Perm((2, 0, 1)), + Perm((0, 1, 3, 2)), + Perm((0, 3, 1, 2, 4)), + Perm((2, 4, 3, 1, 0)), + Perm((1, 2, 0)), + Perm((0, 2, 1)), + } + ): True, + frozenset( + { + Perm((3, 0, 2, 1, 4)), + Perm((1, 0)), + Perm((3, 1, 0, 2)), + Perm((1, 3, 0, 4, 2)), + Perm((3, 1, 2, 0)), + Perm((0, 1)), + } + ): True, + frozenset({Perm((3, 1, 0, 2, 4)), Perm((0, 1))}): True, + frozenset({Perm((1, 2, 0, 3)), Perm((0, 1))}): True, + frozenset( + { + Perm((3, 0, 2, 1, 4)), + Perm((1, 0)), + Perm((4, 1, 0, 3, 2)), + Perm((3, 0, 1, 2)), + Perm((2, 3, 0, 1)), + Perm((2, 1, 0)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((1, 0)), + Perm((4, 1, 0, 3, 2)), + Perm((3, 0, 1, 2)), + Perm((2, 1, 0, 3)), + Perm((0, 2, 3, 1)), + Perm((1, 2, 0, 3, 4)), + Perm((3, 4, 2, 1, 0)), + Perm((0, 1)), + } + ): True, + frozenset({Perm((2, 1, 0, 4, 3)), Perm((1, 4, 3, 0, 2)), Perm((0, 1))}): True, + frozenset( + { + Perm((4, 3, 2, 0, 1)), + Perm((1, 0)), + Perm((4, 1, 3, 2, 0)), + Perm((3, 0, 1, 2)), + Perm((3, 0, 2, 1)), + Perm((0, 1, 3, 2, 4)), + Perm((0, 2, 3, 1)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((1, 0, 2, 3)), + Perm((3, 0, 2, 1)), + Perm((3, 2, 0, 1)), + Perm((0, 1)), + Perm((0, 1, 2, 3)), + } + ): True, + frozenset( + { + Perm((1, 2, 0, 4, 3)), + Perm((3, 2, 1, 0)), + Perm((1, 0)), + Perm((2, 3, 0, 4, 1)), + Perm((1, 3, 0, 2)), + Perm((0, 2, 1)), + } + ): True, + frozenset({Perm((0, 1)), Perm((0, 1, 2)), Perm((1, 0))}): True, + frozenset( + {Perm((0, 3, 1, 2)), Perm((4, 0, 2, 3, 1)), Perm((2, 1, 0, 3)), Perm((0, 1))} + ): True, + frozenset( + {Perm((2, 1, 0)), Perm((0, 1, 3, 4, 2)), Perm((0, 1)), Perm((1, 2, 4, 0, 3))} + ): True, + frozenset( + { + Perm((1, 0, 3, 2, 4)), + Perm((3, 2, 1, 0)), + Perm((4, 2, 0, 1, 3)), + Perm((1, 0, 2)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((2, 0, 1, 3)), + Perm((2, 3, 1, 4, 0)), + Perm((1, 0)), + Perm((1, 0, 2)), + Perm((2, 0, 1)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((4, 2, 1, 0, 3)), + Perm((1, 0)), + Perm((1, 0, 2)), + Perm((2, 3, 0, 1)), + Perm((0, 2, 3, 1)), + Perm((0, 2, 1)), + } + ): True, + frozenset( + { + Perm((1, 0)), + Perm((4, 1, 0, 3, 2)), + Perm((2, 1, 0, 3)), + Perm((2, 0, 1)), + Perm((0, 1, 2)), + Perm((0, 1)), + } + ): True, + frozenset({Perm((0, 1)), Perm((3, 1, 0, 2))}): True, + frozenset( + { + Perm((2, 0, 3, 1)), + Perm((1, 0)), + Perm((1, 0, 2)), + Perm((2, 0, 1, 3, 4)), + Perm((2, 1, 0)), + Perm((0, 1)), + Perm((0, 2, 1)), + } + ): True, + frozenset( + { + Perm((1, 0)), + Perm((4, 1, 0, 3, 2)), + Perm((2, 3, 1, 0)), + Perm((0, 1, 2)), + Perm((0, 2, 1)), + Perm((0, 1)), + } + ): True, + frozenset({Perm((0, 1)), Perm((3, 2, 1, 0, 4)), Perm((1, 0))}): True, + frozenset( + { + Perm((1, 0, 2)), + Perm((2, 1, 0)), + Perm((0, 1)), + Perm((0, 2, 3, 1, 4)), + Perm((0, 2, 1)), + } + ): True, + frozenset({Perm((0, 1)), Perm((2, 0, 1)), Perm((0, 2, 1))}): True, + frozenset( + {Perm((3, 0, 1, 2)), Perm((1, 0, 2, 4, 3)), Perm((0, 2, 3, 1)), Perm((0, 1))} + ): True, + frozenset( + { + Perm((2, 0, 1, 3)), + Perm((4, 2, 1, 0, 3)), + Perm((2, 0, 3, 1)), + Perm((1, 0)), + Perm((1, 0, 2, 3)), + Perm((4, 0, 2, 1, 3)), + Perm((0, 2, 3, 1, 4)), + Perm((0, 1)), + } + ): True, + frozenset({Perm((0, 1)), Perm((1, 0))}): True, + frozenset( + { + Perm((2, 1, 3, 0)), + Perm((2, 0, 1, 3)), + Perm((4, 1, 3, 0, 2)), + Perm((1, 4, 0, 3, 2)), + Perm((1, 3, 0, 4, 2)), + Perm((4, 1, 2, 3, 0)), + Perm((2, 0, 1)), + Perm((0, 2, 3, 1)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((1, 0)), + Perm((3, 1, 0, 2)), + Perm((2, 0, 1)), + Perm((0, 1, 2)), + Perm((0, 2, 1)), + Perm((0, 1)), + } + ): True, + frozenset({Perm((1, 0, 2)), Perm((1, 2, 0)), Perm((0, 2, 1))}): True, + frozenset( + { + Perm((1, 0, 4, 2, 3)), + Perm((1, 0)), + Perm((3, 1, 0, 2)), + Perm((1, 0, 2)), + Perm((3, 0, 2, 1)), + Perm((1, 2, 4, 0, 3)), + Perm((1, 0, 3, 2)), + } + ): True, + frozenset({Perm((0, 1, 2, 3)), Perm((0, 1))}): True, + frozenset({Perm((0, 1)), Perm((0, 2, 1))}): True, + frozenset( + { + Perm((2, 0, 1, 3)), + Perm((1, 0)), + Perm((0, 2, 1, 3)), + Perm((1, 0, 2)), + Perm((3, 1, 2, 0)), + Perm((0, 2, 1)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((1, 0)), + Perm((4, 0, 1, 3, 2)), + Perm((2, 1, 0, 3, 4)), + Perm((0, 1, 2, 3)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((0, 1, 2, 4, 3)), + Perm((3, 2, 4, 0, 1)), + Perm((1, 2, 0)), + Perm((3, 2, 1, 0)), + } + ): True, + frozenset( + { + Perm((2, 0, 3, 1)), + Perm((2, 1, 3, 0, 4)), + Perm((3, 0, 1, 2, 4)), + Perm((3, 1, 2, 0)), + Perm((0, 1, 2)), + } + ): True, + frozenset( + {Perm((2, 3, 0, 1)), Perm((3, 0, 4, 1, 2)), Perm((0, 1)), Perm((0, 1, 3, 2))} + ): True, + frozenset( + { + Perm((1, 0)), + Perm((1, 0, 2, 3)), + Perm((1, 3, 0, 2, 4)), + Perm((4, 2, 1, 3, 0)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((2, 1, 3, 0)), + Perm((1, 0)), + Perm((3, 1, 0, 2)), + Perm((0, 3, 2, 1)), + Perm((0, 1, 2, 3)), + Perm((0, 3, 1, 2)), + Perm((0, 1)), + } + ): True, + frozenset({Perm((1, 0)), Perm((3, 1, 0, 4, 2))}): True, + frozenset({Perm((1, 3, 4, 0, 2)), Perm((1, 0, 4, 3, 2)), Perm((0, 1))}): True, + frozenset( + { + Perm((1, 0, 4, 2, 3)), + Perm((2, 4, 0, 3, 1)), + Perm((0, 2, 3, 1)), + Perm((1, 2, 0)), + Perm((0, 1)), + } + ): True, + frozenset({Perm((2, 1, 0, 3, 4)), Perm((2, 0, 1, 3)), Perm((0, 1))}): True, + frozenset( + { + Perm((0, 3, 1, 4, 2)), + Perm((1, 0, 2)), + Perm((2, 0, 1, 3, 4)), + Perm((0, 1, 2)), + Perm((1, 3, 0, 2)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((4, 1, 3, 0, 2)), + Perm((2, 0, 3, 1)), + Perm((1, 0)), + Perm((0, 4, 2, 3, 1)), + Perm((3, 2, 4, 1, 0)), + Perm((0, 2, 3, 1)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((0, 4, 2, 1, 3)), + Perm((2, 3, 1, 4, 0)), + Perm((0, 3, 2, 1)), + Perm((2, 0, 1)), + Perm((0, 1, 2)), + } + ): True, + frozenset({Perm((1, 0, 2)), Perm((1, 0))}): True, + frozenset( + { + Perm((1, 0)), + Perm((3, 1, 2, 0)), + Perm((2, 0, 1)), + Perm((0, 1, 3, 2)), + Perm((0, 1, 2)), + Perm((1, 2, 3, 0)), + Perm((1, 4, 2, 3, 0)), + Perm((0, 3, 2, 1, 4)), + Perm((0, 1)), + } + ): True, + frozenset({Perm((1, 0))}): True, + frozenset( + { + Perm((3, 2, 1, 4, 0)), + Perm((4, 3, 2, 1, 0)), + Perm((1, 0, 2)), + Perm((4, 1, 2, 3, 0)), + Perm((0, 1, 2)), + Perm((0, 2, 1)), + } + ): True, + frozenset( + {Perm((3, 2, 4, 1, 0)), Perm((1, 0)), Perm((2, 0, 1)), Perm((0, 4, 3, 1, 2))} + ): True, + frozenset( + { + Perm((1, 0)), + Perm((0, 2, 3, 4, 1)), + Perm((1, 0, 3, 4, 2)), + Perm((1, 2, 0)), + Perm((0, 1, 2, 3)), + } + ): True, + frozenset( + { + Perm((3, 1, 2, 0)), + Perm((3, 0, 2, 1)), + Perm((1, 4, 2, 0, 3)), + Perm((0, 1)), + Perm((0, 1, 2, 3)), + } + ): True, + frozenset( + { + Perm((2, 1, 3, 0)), + Perm((1, 0)), + Perm((2, 0, 1)), + Perm((0, 2, 4, 1, 3)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((2, 3, 1, 0)), + Perm((0, 1, 2)), + Perm((1, 4, 3, 0, 2)), + Perm((2, 1, 0)), + Perm((1, 3, 4, 2, 0)), + Perm((0, 2, 1)), + } + ): True, + frozenset({Perm((0, 2, 3, 1)), Perm((1, 0))}): True, + frozenset({Perm((2, 1, 3, 0)), Perm((1, 0))}): True, + frozenset( + { + Perm((2, 0, 3, 1)), + Perm((3, 1, 4, 0, 2)), + Perm((0, 3, 2, 4, 1)), + Perm((2, 0, 1)), + Perm((2, 1, 0, 3, 4)), + Perm((0, 2, 1)), + } + ): True, + frozenset( + { + Perm((1, 0)), + Perm((3, 2, 0, 1)), + Perm((0, 1, 2)), + Perm((2, 1, 0)), + Perm((0, 2, 1)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((0, 2, 3, 4, 1)), + Perm((3, 0, 2, 1)), + Perm((3, 2, 0, 1)), + Perm((2, 3, 1, 0)), + Perm((0, 3, 1, 2)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((4, 2, 1, 0, 3)), + Perm((2, 3, 1, 4, 0)), + Perm((4, 3, 2, 1, 0)), + Perm((1, 0, 2)), + Perm((3, 0, 1, 4, 2)), + Perm((1, 2, 0)), + } + ): True, + frozenset( + { + Perm((1, 0)), + Perm((3, 1, 0, 4, 2)), + Perm((1, 0, 2, 4, 3)), + Perm((0, 1, 2)), + Perm((2, 1, 0)), + } + ): True, + frozenset( + { + Perm((0, 1, 2, 3, 4)), + Perm((1, 0)), + Perm((1, 0, 2)), + Perm((3, 1, 2, 0)), + Perm((0, 1)), + Perm((1, 0, 3, 2)), + Perm((0, 2, 1)), + Perm((0, 1, 4, 3, 2)), + } + ): True, + frozenset( + { + Perm((2, 1, 3, 0)), + Perm((0, 4, 2, 1, 3)), + Perm((1, 0)), + Perm((2, 0, 1)), + Perm((2, 1, 0)), + Perm((3, 4, 0, 2, 1)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((0, 3, 2, 1)), + Perm((2, 0, 4, 3, 1)), + Perm((1, 2, 0, 3)), + Perm((0, 3, 1, 2)), + Perm((1, 3, 0, 2)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((2, 1, 3, 0)), + Perm((2, 4, 3, 0, 1)), + Perm((4, 1, 0, 2, 3)), + Perm((2, 1, 0, 3, 4)), + Perm((0, 3, 1, 2)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((1, 0)), + Perm((2, 1, 0, 3)), + Perm((1, 2, 0, 3)), + Perm((2, 3, 1, 0)), + Perm((2, 1, 0)), + Perm((0, 3, 4, 2, 1)), + Perm((1, 2, 0)), + Perm((0, 1)), + } + ): True, + frozenset( + {Perm((3, 2, 1, 4, 0)), Perm((0, 2, 1)), Perm((1, 2, 0, 3)), Perm((0, 1))} + ): True, + frozenset( + { + Perm((2, 0, 1, 3)), + Perm((4, 2, 0, 1, 3)), + Perm((0, 1, 2)), + Perm((2, 1, 0)), + Perm((0, 2, 3, 1)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((3, 0, 1, 2)), + Perm((2, 1, 0, 3)), + Perm((2, 0, 1)), + Perm((0, 1, 2)), + Perm((1, 4, 3, 2, 0)), + Perm((4, 0, 3, 1, 2)), + } + ): True, + frozenset( + { + Perm((1, 0)), + Perm((4, 3, 2, 1, 0)), + Perm((2, 0, 1)), + Perm((1, 0, 2, 4, 3)), + Perm((3, 2, 4, 0, 1)), + Perm((2, 1, 0)), + Perm((0, 2, 3, 1)), + Perm((0, 2, 1)), + Perm((0, 1)), + } + ): True, + frozenset({Perm((3, 1, 2, 4, 0)), Perm((0, 1, 2)), Perm((0, 1))}): True, + frozenset({Perm((2, 1, 0)), Perm((1, 0))}): True, + frozenset({Perm((0, 3, 1, 2)), Perm((1, 2, 0)), Perm((1, 0))}): True, + frozenset( + { + Perm((2, 1, 4, 3, 0)), + Perm((0, 1, 2)), + Perm((1, 2, 3, 0)), + Perm((0, 2, 1)), + Perm((1, 2, 0)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((1, 0)), + Perm((1, 0, 2)), + Perm((1, 2, 0, 3)), + Perm((3, 0, 2, 1)), + Perm((2, 3, 1, 0)), + Perm((0, 1, 2)), + Perm((2, 1, 0)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((2, 0, 3, 1)), + Perm((4, 1, 2, 3, 0)), + Perm((2, 0, 1, 4, 3)), + Perm((2, 3, 1, 0)), + Perm((1, 3, 0, 2)), + Perm((0, 2, 4, 1, 3)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((3, 1, 0, 4, 2)), + Perm((1, 2, 3, 0)), + Perm((2, 1, 0)), + Perm((1, 2, 0)), + Perm((0, 1)), + } + ): True, + frozenset({Perm((0, 1)), Perm((0, 3, 2, 1)), Perm((0, 1, 2)), Perm((1, 0))}): True, + frozenset({Perm((3, 4, 0, 1, 2)), Perm((1, 0))}): True, + frozenset( + { + Perm((1, 0)), + Perm((3, 1, 0, 4, 2)), + Perm((2, 3, 1, 0)), + Perm((1, 2, 0)), + Perm((1, 2, 3, 0)), + Perm((0, 3, 4, 2, 1)), + Perm((2, 1, 3, 4, 0)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((2, 0, 1, 3)), + Perm((3, 2, 0, 1)), + Perm((0, 1, 2)), + Perm((0, 2, 1)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((2, 0, 3, 1, 4)), + Perm((1, 0)), + Perm((3, 2, 1, 0)), + Perm((0, 2, 1, 3)), + Perm((2, 1, 0, 3)), + Perm((3, 0, 4, 2, 1)), + Perm((2, 1, 0)), + Perm((0, 1)), + Perm((0, 2, 3, 1)), + Perm((0, 2, 1)), + } + ): True, + frozenset({Perm((2, 1, 3, 0)), Perm((2, 0, 1, 3)), Perm((0, 1))}): True, + frozenset( + { + Perm((3, 1, 0, 2)), + Perm((1, 2, 0, 3)), + Perm((2, 0, 1)), + Perm((2, 1, 0)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((1, 2, 0, 4, 3)), + Perm((3, 2, 1, 0)), + Perm((1, 0)), + Perm((2, 1, 0)), + Perm((0, 1)), + } + ): True, + frozenset({Perm((1, 0, 2)), Perm((0, 1, 2)), Perm((0, 2, 1)), Perm((1, 0))}): True, + frozenset( + { + Perm((1, 0, 3, 2, 4)), + Perm((3, 2, 1, 0)), + Perm((1, 0)), + Perm((1, 0, 2)), + Perm((0, 1, 2)), + Perm((1, 2, 0, 3, 4)), + Perm((3, 0, 4, 1, 2)), + Perm((0, 2, 3, 1)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((4, 0, 3, 2, 1)), + Perm((1, 0)), + Perm((2, 0, 1)), + Perm((0, 1, 2)), + Perm((1, 3, 0, 2, 4)), + Perm((1, 2, 0)), + } + ): True, + frozenset( + { + Perm((2, 1, 3, 0)), + Perm((2, 0, 1, 3)), + Perm((2, 0, 1)), + Perm((1, 2, 0)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((0, 3, 2, 4, 1)), + Perm((1, 0, 2, 3)), + Perm((2, 1, 0, 3)), + Perm((2, 1, 0)), + Perm((0, 3, 4, 1, 2)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((1, 0)), + Perm((1, 0, 2)), + Perm((2, 3, 1, 0)), + Perm((0, 1, 2)), + Perm((4, 2, 3, 0, 1)), + Perm((0, 3, 4, 1, 2)), + Perm((1, 2, 0)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((3, 2, 1, 0, 4)), + Perm((1, 0)), + Perm((1, 0, 2)), + Perm((1, 2, 0, 3)), + Perm((3, 2, 0, 1, 4)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((0, 1)), + Perm((0, 1, 3, 2, 4)), + Perm((3, 0, 4, 1, 2)), + Perm((1, 2, 0)), + Perm((0, 2, 1)), + } + ): True, + frozenset( + { + Perm((3, 2, 1, 0)), + Perm((1, 0)), + Perm((0, 1, 2)), + Perm((2, 3, 4, 1, 0)), + Perm((3, 0, 4, 1, 2)), + Perm((0, 1)), + } + ): True, + frozenset( + { + Perm((1, 0)), + Perm((0, 2, 1, 3)), + Perm((0, 1, 2)), + Perm((2, 1, 0)), + Perm((0, 2, 3, 1)), + } + ): True, + frozenset({Perm((0, 2, 1, 3))}): False, + frozenset({Perm((2, 3, 0, 1, 4))}): False, + frozenset( + { + Perm((1, 0, 2, 3)), + Perm((2, 0, 4, 1, 3)), + Perm((2, 3, 4, 1, 0)), + Perm((3, 4, 1, 0, 2)), + } + ): False, + frozenset( + {Perm((1, 0, 3, 4, 2)), Perm((3, 4, 1, 2, 0)), Perm((0, 1, 3, 2))} + ): False, + frozenset({Perm((2, 1, 0)), Perm((3, 0, 2, 4, 1))}): False, + frozenset( + { + Perm((0, 4, 1, 2, 3)), + Perm((3, 4, 2, 1, 0)), + Perm((1, 3, 0, 2)), + Perm((2, 0, 1)), + } + ): False, + frozenset({Perm((1, 0, 2)), Perm((1, 2, 0)), Perm((0, 4, 2, 3, 1))}): False, + frozenset({Perm((2, 0, 1, 3)), Perm((1, 3, 0, 2)), Perm((2, 1, 3, 0, 4))}): False, + frozenset({Perm((2, 1, 0)), Perm((4, 1, 2, 3, 0)), Perm((3, 1, 0, 2))}): False, + frozenset({Perm((1, 0, 2)), Perm((0, 2, 1))}): False, + frozenset({Perm((1, 4, 2, 3, 0)), Perm((0, 2, 1))}): False, + frozenset({Perm((3, 1, 2, 0)), Perm((2, 0, 1))}): False, + frozenset( + {Perm((2, 1, 0)), Perm((2, 0, 1, 3)), Perm((1, 3, 4, 0, 2)), Perm((1, 3, 2, 0))} + ): False, + frozenset({Perm((0, 1, 2))}): False, + frozenset({Perm((3, 1, 2, 0)), Perm((1, 2, 0))}): False, + frozenset({Perm((0, 3, 1, 2)), Perm((1, 3, 0, 2)), Perm((0, 2, 3, 1))}): False, + frozenset({Perm((0, 4, 2, 3, 1))}): False, + frozenset({Perm((0, 3, 4, 1, 2)), Perm((3, 2, 1, 0))}): False, + frozenset({Perm((1, 3, 2, 0))}): False, + frozenset({Perm((0, 2, 1)), Perm((0, 1, 2, 3))}): False, + frozenset({Perm((2, 3, 1, 4, 0)), Perm((2, 0, 1))}): False, + frozenset({Perm((1, 0, 2)), Perm((2, 3, 0, 1, 4))}): False, + frozenset({Perm((2, 3, 0, 1)), Perm((3, 1, 2, 0))}): False, + frozenset( + {Perm((1, 0, 2, 3)), Perm((2, 4, 0, 3, 1)), Perm((2, 4, 3, 0, 1))} + ): False, + frozenset({Perm((3, 0, 1, 2))}): False, + frozenset({Perm((2, 1, 0))}): False, + frozenset({Perm((1, 2, 0))}): False, + frozenset({Perm((2, 0, 1, 3)), Perm((0, 3, 2, 1)), Perm((4, 3, 0, 1, 2))}): False, + frozenset({Perm((3, 2, 1, 0, 4))}): False, + frozenset( + {Perm((0, 3, 2, 1)), Perm((3, 1, 2, 4, 0)), Perm((0, 2, 3, 1)), Perm((0, 2, 1))} + ): False, + frozenset( + { + Perm((2, 1, 0)), + Perm((2, 4, 0, 3, 1)), + Perm((3, 0, 2, 1)), + Perm((2, 0, 1, 4, 3)), + } + ): False, + frozenset({Perm((2, 0, 3, 1, 4))}): False, + frozenset({Perm((0, 1, 3, 4, 2)), Perm((2, 4, 0, 1, 3))}): False, + frozenset({Perm((0, 2, 1)), Perm((2, 4, 3, 0, 1)), Perm((4, 0, 2, 1, 3))}): False, + frozenset({Perm((1, 0, 2))}): False, + frozenset({Perm((0, 1, 2)), Perm((0, 2, 1))}): False, + frozenset({Perm((0, 1, 2)), Perm((2, 0, 3, 1))}): False, + frozenset( + {Perm((1, 0, 2)), Perm((1, 2, 0)), Perm((1, 0, 2, 4, 3)), Perm((2, 4, 0, 1, 3))} + ): False, + frozenset( + { + Perm((0, 1, 2, 4, 3)), + Perm((3, 0, 2, 1)), + Perm((1, 2, 0, 3)), + Perm((0, 1, 2)), + Perm((1, 3, 0, 2)), + } + ): False, + frozenset({Perm((1, 0, 2)), Perm((0, 1, 2))}): False, + frozenset( + {Perm((1, 2, 3, 4, 0)), Perm((1, 3, 0, 2)), Perm((1, 2, 0, 3)), Perm((0, 1, 2))} + ): False, + frozenset({Perm((3, 0, 2, 1)), Perm((1, 2, 4, 0, 3))}): False, + frozenset({Perm((2, 1, 0)), Perm((2, 4, 0, 3, 1)), Perm((1, 2, 0))}): False, + frozenset( + { + Perm((4, 1, 3, 0, 2)), + Perm((1, 4, 0, 3, 2)), + Perm((1, 0, 2, 3)), + Perm((3, 1, 2, 0)), + Perm((2, 0, 1, 4, 3)), + Perm((3, 2, 0, 1, 4)), + Perm((0, 3, 1, 2)), + Perm((0, 4, 1, 2, 3)), + } + ): False, + frozenset({Perm((1, 2, 0, 3, 4)), Perm((1, 2, 0)), Perm((1, 3, 2, 0))}): False, + frozenset( + { + Perm((2, 4, 0, 1, 3)), + Perm((1, 2, 0, 3)), + Perm((1, 0, 2, 4, 3)), + Perm((2, 3, 1, 0)), + Perm((1, 2, 3, 0)), + Perm((0, 2, 1)), + } + ): False, + frozenset({Perm((1, 0, 2, 3))}): False, + frozenset({Perm((1, 3, 4, 0, 2)), Perm((3, 2, 1, 4, 0))}): False, + frozenset( + { + Perm((2, 1, 3, 0)), + Perm((4, 0, 1, 3, 2)), + Perm((3, 0, 2, 1)), + Perm((3, 4, 2, 0, 1)), + Perm((2, 0, 1)), + Perm((4, 1, 0, 2, 3)), + Perm((1, 4, 2, 0, 3)), + Perm((2, 3, 1, 0)), + } + ): False, + frozenset( + { + Perm((2, 0, 1)), + Perm((1, 4, 2, 0, 3)), + Perm((2, 3, 4, 1, 0)), + Perm((3, 0, 4, 2, 1)), + Perm((1, 2, 0)), + } + ): False, + frozenset({Perm((0, 2, 1, 3)), Perm((3, 1, 2, 0)), Perm((3, 2, 0, 1))}): False, + frozenset({Perm((0, 2, 1))}): False, + frozenset({Perm((4, 2, 3, 1, 0)), Perm((2, 0, 1)), Perm((2, 0, 3, 1))}): False, + frozenset({Perm((0, 3, 4, 1, 2))}): False, + frozenset( + { + Perm((4, 3, 2, 0, 1)), + Perm((3, 0, 1, 2, 4)), + Perm((2, 0, 1)), + Perm((0, 4, 1, 2, 3)), + Perm((1, 2, 0)), + } + ): False, + frozenset({Perm((0, 3, 2, 1))}): False, + frozenset({Perm((1, 4, 3, 2, 0)), Perm((0, 2, 1))}): False, + frozenset({Perm((1, 3, 0, 4, 2)), Perm((3, 0, 4, 1, 2))}): False, + frozenset({Perm((3, 2, 0, 1, 4)), Perm((1, 3, 2, 0))}): False, + frozenset({Perm((2, 4, 0, 1, 3))}): False, + frozenset({Perm((0, 3, 1, 2)), Perm((0, 4, 1, 2, 3)), Perm((0, 1, 2))}): False, + frozenset({Perm((1, 3, 0, 2)), Perm((4, 1, 2, 3, 0)), Perm((2, 0, 1))}): False, + frozenset({Perm((0, 3, 2, 1)), Perm((0, 2, 3, 1))}): False, + frozenset( + { + Perm((2, 1, 3, 0)), + Perm((0, 3, 2, 1)), + Perm((1, 0, 2)), + Perm((0, 1, 4, 2, 3)), + Perm((0, 2, 1)), + } + ): False, + frozenset( + { + Perm((1, 4, 0, 3, 2)), + Perm((3, 0, 1, 2)), + Perm((1, 0, 2)), + Perm((4, 1, 0, 2, 3)), + Perm((0, 1, 3, 2)), + Perm((0, 1, 2)), + } + ): False, + frozenset({Perm((0, 1, 2)), Perm((1, 3, 2, 4, 0))}): False, + frozenset({Perm((1, 3, 0, 2))}): False, + frozenset({Perm((2, 3, 0, 1))}): False, + frozenset( + { + Perm((1, 2, 3, 0, 4)), + Perm((1, 3, 0, 2)), + Perm((0, 2, 3, 1)), + Perm((2, 0, 1, 4, 3)), + } + ): False, + frozenset( + { + Perm((0, 3, 2, 1)), + Perm((2, 0, 1)), + Perm((3, 2, 4, 1, 0)), + Perm((2, 0, 1, 4, 3)), + } + ): False, + frozenset({Perm((1, 0, 2)), Perm((2, 1, 0, 3)), Perm((1, 2, 0))}): False, + frozenset({Perm((2, 1, 0, 3))}): False, + frozenset({Perm((3, 1, 0, 2))}): False, + frozenset( + {Perm((1, 2, 3, 4, 0)), Perm((0, 2, 3, 1, 4)), Perm((0, 1, 3, 2))} + ): False, + frozenset( + { + Perm((3, 0, 1, 2)), + Perm((2, 3, 1, 4, 0)), + Perm((4, 3, 1, 2, 0)), + Perm((4, 3, 2, 1, 0)), + } + ): False, + frozenset( + { + Perm((0, 2, 1, 3)), + Perm((0, 4, 1, 2, 3)), + Perm((0, 2, 4, 3, 1)), + Perm((0, 1, 3, 2)), + } + ): False, + frozenset({Perm((3, 0, 1, 2)), Perm((4, 0, 1, 3, 2))}): False, + frozenset( + { + Perm((2, 0, 1, 3)), + Perm((2, 1, 0, 3)), + Perm((0, 1, 2)), + Perm((0, 4, 2, 3, 1)), + Perm((0, 3, 4, 2, 1)), + } + ): False, + frozenset({Perm((0, 2, 1)), Perm((1, 4, 2, 0, 3))}): False, + frozenset( + {Perm((1, 0, 2)), Perm((2, 3, 0, 1, 4)), Perm((0, 2, 1)), Perm((3, 4, 1, 0, 2))} + ): False, + frozenset({Perm((0, 3, 2, 1)), Perm((1, 0, 2)), Perm((0, 4, 2, 1, 3))}): False, + frozenset( + { + Perm((4, 2, 3, 1, 0)), + Perm((1, 2, 4, 3, 0)), + Perm((3, 4, 2, 0, 1)), + Perm((2, 0, 1)), + Perm((1, 2, 3, 0)), + Perm((2, 1, 0)), + } + ): False, + frozenset({Perm((2, 3, 0, 1)), Perm((0, 1, 2)), Perm((4, 1, 0, 2, 3))}): False, + frozenset({Perm((3, 4, 0, 1, 2)), Perm((0, 2, 1, 4, 3))}): False, + frozenset({Perm((0, 3, 1, 2)), Perm((0, 1, 2))}): False, + frozenset({Perm((4, 0, 3, 2, 1))}): False, + frozenset({Perm((1, 2, 3, 4, 0))}): False, + frozenset( + { + Perm((3, 1, 2, 4, 0)), + Perm((2, 0, 3, 1)), + Perm((2, 1, 0)), + Perm((2, 3, 1, 0)), + Perm((1, 2, 3, 0)), + Perm((2, 1, 0, 4, 3)), + Perm((3, 0, 1, 4, 2)), + } + ): False, + frozenset( + {Perm((4, 2, 3, 0, 1)), Perm((2, 1, 0, 3)), Perm((3, 2, 4, 1, 0))} + ): False, + frozenset({Perm((2, 1, 3, 4, 0)), Perm((0, 4, 3, 1, 2))}): False, + frozenset( + { + Perm((4, 1, 3, 0, 2)), + Perm((2, 0, 3, 1)), + Perm((3, 1, 0, 2)), + Perm((0, 2, 1, 3)), + Perm((3, 2, 0, 1)), + } + ): False, + frozenset({Perm((2, 0, 1))}): False, + frozenset({Perm((2, 3, 0, 1)), Perm((0, 1, 2))}): False, + frozenset( + {Perm((4, 3, 1, 0, 2)), Perm((1, 2, 3, 0, 4)), Perm((1, 3, 2, 0))} + ): False, + frozenset( + { + Perm((2, 1, 3, 0)), + Perm((2, 0, 1)), + Perm((2, 3, 0, 1)), + Perm((2, 1, 0)), + Perm((1, 2, 0, 3, 4)), + } + ): False, + frozenset( + { + Perm((2, 3, 0, 1)), + Perm((2, 3, 0, 1, 4)), + Perm((3, 4, 1, 2, 0)), + Perm((1, 0, 4, 2, 3)), + } + ): False, + frozenset({Perm((4, 2, 3, 1, 0)), Perm((4, 3, 2, 0, 1))}): False, + frozenset({Perm((1, 0, 3, 2, 4))}): False, + frozenset({Perm((2, 1, 0)), Perm((2, 0, 4, 3, 1)), Perm((0, 4, 2, 3, 1))}): False, + frozenset({Perm((3, 2, 0, 1, 4)), Perm((2, 4, 3, 0, 1)), Perm((1, 2, 0))}): False, + frozenset( + { + Perm((0, 1, 3, 4, 8, 6, 2, 7, 5)), + Perm((4, 3, 2, 1, 0)), + Perm((1, 3, 2, 0, 4)), + Perm((2, 1, 0, 3, 4)), + Perm((0, 1, 3, 2, 4)), + Perm((2, 1, 0, 5, 3, 4)), + } + ): True, + frozenset( + { + Perm((2, 8, 5, 7, 4, 3, 1, 6, 0, 9)), + Perm((4, 3, 2, 0, 1)), + Perm((0, 1, 2, 3, 4)), + Perm((3, 1, 4, 0, 2)), + Perm((0, 2, 1, 4, 5, 3)), + Perm((2, 0, 7, 3, 1, 8, 4, 6, 5)), + Perm((2, 3, 5, 7, 0, 8, 1, 4, 6)), + Perm((3, 2, 4, 0, 1)), + Perm((3, 4, 0, 2, 1)), + } + ): True, + frozenset( + { + Perm((0, 1, 2, 4, 3)), + Perm((4, 3, 2, 1, 0)), + Perm((1, 3, 0, 5, 6, 4, 2)), + Perm((3, 4, 2, 0, 1)), + Perm((2, 3, 4, 5, 0, 1)), + Perm((5, 3, 8, 1, 0, 7, 6, 2, 4)), + Perm((4, 0, 2, 1, 3)), + Perm((6, 2, 0, 8, 1, 3, 7, 5, 4)), + Perm((9, 3, 7, 0, 4, 5, 8, 1, 2, 6)), + } + ): True, + frozenset( + { + Perm((4, 3, 2, 0, 1)), + Perm((0, 4, 3, 2, 1)), + Perm((9, 1, 3, 7, 8, 0, 2, 5, 6, 4)), + Perm((2, 3, 1, 0, 4)), + Perm((7, 5, 8, 1, 0, 6, 4, 9, 2, 3)), + Perm((7, 5, 4, 3, 0, 1, 6, 2)), + Perm((0, 3, 1, 2, 4)), + Perm((5, 0, 2, 4, 3, 1)), + Perm((0, 1, 3, 2, 4)), + Perm((6, 9, 0, 5, 3, 8, 7, 2, 1, 4)), + } + ): True, + frozenset( + { + Perm((2, 3, 4, 0, 5, 8, 1, 7, 6)), + Perm((4, 3, 2, 1, 0)), + Perm((2, 3, 1, 0, 4)), + Perm((2, 0, 1, 3, 4)), + Perm((7, 1, 6, 5, 4, 2, 3, 0)), + Perm((0, 2, 1, 3, 4, 6, 5)), + Perm((2, 3, 1, 4, 5, 0)), + Perm((3, 2, 1, 4, 6, 5, 0)), + Perm((5, 7, 2, 4, 0, 6, 1, 3)), + Perm((8, 0, 3, 5, 1, 7, 9, 2, 6, 4)), + } + ): True, + frozenset( + { + Perm((4, 3, 2, 0, 1)), + Perm((0, 1, 2, 3, 4)), + Perm((5, 0, 4, 2, 1, 3)), + Perm((4, 1, 2, 0, 3)), + Perm((2, 1, 4, 5, 0, 3)), + Perm((0, 5, 6, 2, 4, 7, 8, 3, 1)), + Perm((6, 2, 0, 3, 1, 4, 5)), + } + ): True, + frozenset( + { + Perm((1, 0, 4, 2, 5, 3)), + Perm((0, 1, 2, 3, 4)), + Perm((6, 2, 1, 3, 5, 4, 7, 0)), + Perm((1, 3, 4, 0, 2)), + Perm((5, 4, 3, 2, 0, 1)), + Perm((1, 2, 0, 6, 4, 5, 3)), + Perm((2, 1, 3, 5, 0, 4)), + Perm((2, 3, 4, 0, 1)), + Perm((1, 5, 4, 6, 3, 0, 2)), + Perm((5, 0, 4, 2, 3, 6, 7, 1)), + } + ): True, + frozenset( + { + Perm((4, 3, 2, 0, 1)), + Perm((0, 1, 4, 5, 6, 3, 2)), + Perm((0, 4, 3, 1, 5, 2)), + Perm((4, 1, 3, 2, 0)), + Perm((7, 0, 6, 8, 5, 1, 2, 4, 3)), + Perm((3, 1, 2, 0, 4)), + Perm((1, 0, 2, 3, 4)), + Perm((2, 4, 0, 3, 5, 1)), + } + ): True, + frozenset( + { + Perm((0, 1, 2, 3, 5, 4)), + Perm((6, 8, 0, 3, 5, 7, 2, 1, 9, 4)), + Perm((4, 3, 2, 1, 0)), + Perm((1, 0, 5, 7, 3, 6, 2, 8, 9, 4)), + Perm((2, 0, 1, 3, 4)), + Perm((1, 8, 3, 7, 4, 5, 6, 2, 0, 9)), + Perm((4, 0, 3, 1, 2, 5, 6)), + Perm((2, 0, 1, 8, 7, 4, 6, 3, 5)), + Perm((6, 4, 7, 5, 0, 2, 1, 3)), + } + ): True, + frozenset( + { + Perm((0, 2, 4, 3, 1)), + Perm((4, 3, 2, 1, 0)), + Perm((2, 4, 5, 3, 0, 1)), + Perm((3, 1, 2, 4, 0, 5)), + Perm((2, 3, 4, 1, 0)), + Perm((5, 4, 0, 6, 3, 7, 2, 1)), + Perm((0, 1, 3, 2, 4)), + Perm((4, 1, 3, 0, 5, 2)), + } + ): True, + frozenset( + { + Perm((5, 2, 0, 4, 3, 7, 1, 6)), + Perm((2, 8, 0, 7, 3, 6, 5, 1, 4)), + Perm((0, 2, 3, 4, 1)), + Perm((7, 4, 8, 3, 2, 5, 6, 1, 0)), + Perm((3, 4, 0, 1, 2)), + Perm((4, 3, 2, 5, 0, 1)), + Perm((1, 0, 2, 5, 4, 3)), + Perm((7, 6, 0, 8, 1, 4, 2, 3, 9, 5)), + } + ): False, + frozenset( + { + Perm((2, 7, 3, 1, 0, 4, 5, 8, 9, 6)), + Perm((3, 6, 2, 7, 0, 1, 4, 5)), + Perm((2, 0, 3, 1, 4)), + Perm((6, 2, 4, 5, 3, 1, 0)), + Perm((4, 3, 0, 2, 5, 1)), + Perm((5, 4, 0, 3, 1, 2)), + Perm((2, 0, 4, 1, 3, 5)), + Perm((9, 2, 0, 5, 7, 1, 6, 8, 3, 4)), + Perm((5, 0, 3, 4, 2, 1)), + } + ): False, + frozenset( + { + Perm((4, 1, 3, 5, 2, 6, 0)), + Perm((4, 6, 5, 1, 0, 2, 3)), + Perm((0, 1, 6, 2, 3, 8, 4, 5, 7)), + Perm((1, 0, 3, 4, 6, 5, 2)), + Perm((5, 1, 3, 4, 0, 2)), + Perm((3, 4, 5, 7, 0, 8, 2, 1, 6)), + Perm((0, 3, 6, 4, 1, 5, 2)), + Perm((1, 2, 5, 4, 3, 0)), + Perm((3, 5, 0, 7, 4, 6, 1, 2)), + Perm((7, 2, 5, 3, 0, 6, 4, 8, 1)), + } + ): False, + frozenset( + { + Perm((2, 0, 1, 6, 5, 3, 7, 4)), + Perm((4, 3, 1, 2, 0)), + Perm((0, 1, 7, 2, 8, 5, 3, 6, 4)), + Perm((8, 0, 5, 6, 4, 1, 2, 7, 3)), + Perm((9, 6, 0, 4, 2, 8, 7, 5, 3, 1)), + Perm((2, 1, 0, 3, 4)), + Perm((1, 5, 0, 3, 2, 6, 4)), + Perm((4, 0, 6, 2, 5, 3, 1)), + } + ): False, + frozenset( + { + Perm((5, 3, 4, 0, 1, 7, 6, 8, 9, 2)), + Perm((0, 4, 2, 1, 3)), + Perm((5, 7, 3, 4, 2, 6, 1, 0)), + Perm((1, 4, 5, 2, 3, 0)), + Perm((5, 4, 0, 1, 3, 2)), + Perm((4, 1, 2, 3, 0, 6, 5, 7)), + } + ): False, + frozenset( + { + Perm((1, 0, 4, 2, 3)), + Perm((7, 9, 2, 1, 5, 8, 0, 3, 4, 6)), + Perm((3, 2, 5, 1, 0, 4)), + Perm((1, 0, 2, 6, 7, 4, 3, 5)), + Perm((9, 2, 3, 6, 1, 7, 8, 4, 5, 0)), + Perm((3, 2, 0, 6, 5, 1, 4)), + Perm((2, 4, 1, 0, 3, 5)), + Perm((3, 7, 4, 6, 8, 0, 2, 5, 1)), + Perm((7, 1, 5, 6, 3, 0, 2, 4, 8)), + } + ): False, + frozenset( + { + Perm((0, 4, 1, 3, 2)), + Perm((5, 2, 3, 4, 6, 7, 0, 1)), + Perm((3, 1, 2, 0, 4)), + Perm((2, 0, 3, 4, 5, 1)), + Perm((3, 0, 2, 1, 5, 4, 6)), + Perm((0, 6, 3, 8, 1, 4, 2, 5, 7)), + } + ): False, + frozenset( + { + Perm((1, 5, 4, 7, 0, 6, 2, 3)), + Perm((1, 2, 0, 4, 3)), + Perm((1, 3, 5, 4, 6, 0, 2)), + Perm((6, 5, 7, 4, 0, 1, 2, 3, 9, 8)), + Perm((6, 7, 5, 3, 1, 0, 4, 2)), + Perm((3, 0, 2, 4, 1)), + Perm((6, 1, 4, 7, 2, 3, 0, 5)), + Perm((5, 3, 1, 2, 4, 0, 9, 7, 6, 8)), + Perm((0, 1, 4, 3, 2)), + Perm((1, 3, 0, 5, 4, 2)), + } + ): False, + frozenset( + { + Perm((4, 0, 1, 2, 3)), + Perm((4, 0, 1, 2, 5, 3)), + Perm((5, 4, 1, 0, 2, 3)), + Perm((5, 2, 0, 4, 3, 1)), + Perm((5, 6, 3, 0, 7, 1, 2, 4)), + Perm((6, 2, 4, 3, 0, 5, 7, 1, 8)), + Perm((1, 3, 2, 0, 8, 5, 6, 7, 4)), + } + ): False, + frozenset( + { + Perm((0, 1, 5, 6, 3, 2, 7, 4)), + Perm((3, 7, 4, 8, 9, 5, 0, 2, 6, 1)), + Perm((3, 5, 0, 7, 1, 4, 2, 6)), + Perm((0, 1, 2, 4, 9, 6, 7, 5, 3, 8)), + Perm((8, 4, 3, 6, 0, 2, 7, 1, 9, 5)), + Perm((2, 6, 8, 1, 4, 3, 5, 0, 7)), + } + ): False, + frozenset( + { + Perm((0, 1, 2, 3, 4)), + Perm((4, 3, 1, 2, 0)), + Perm((4, 1, 2, 0, 5, 3)), + Perm((0, 4, 3, 5, 6, 1, 2, 7)), + Perm((0, 1, 2, 4, 3)), + Perm((6, 1, 2, 5, 4, 3, 0)), + Perm((2, 8, 7, 4, 6, 0, 1, 3, 5)), + Perm((3, 0, 2, 4, 5, 1)), + Perm((0, 6, 3, 4, 2, 1, 5, 7)), + Perm((1, 0, 2, 3, 4, 5)), + Perm((3, 4, 2, 6, 1, 0, 8, 9, 7, 5)), + Perm((8, 4, 2, 6, 3, 7, 5, 0, 1)), + Perm((7, 3, 4, 1, 2, 5, 0, 6)), + Perm((0, 8, 3, 7, 2, 1, 4, 5, 6)), + Perm((1, 7, 2, 6, 3, 4, 0, 5)), + Perm((6, 2, 0, 1, 3, 4, 5)), + Perm((8, 3, 0, 5, 2, 1, 7, 4, 6)), + } + ): True, + frozenset( + { + Perm((2, 4, 1, 3, 5, 0)), + Perm((0, 4, 6, 8, 5, 1, 3, 7, 2)), + Perm((6, 4, 2, 0, 5, 3, 1)), + Perm((6, 0, 5, 2, 4, 1, 3)), + Perm((0, 4, 2, 1, 3)), + Perm((5, 0, 3, 1, 2, 4, 6)), + Perm((3, 1, 4, 0, 2)), + Perm((0, 4, 3, 1, 6, 7, 2, 8, 5)), + Perm((4, 2, 0, 3, 1)), + Perm((7, 6, 3, 0, 4, 1, 2, 5)), + Perm((6, 3, 7, 4, 0, 8, 5, 9, 2, 1)), + Perm((6, 0, 4, 2, 1, 3, 5)), + Perm((7, 6, 0, 1, 4, 2, 5, 3)), + Perm((7, 6, 8, 2, 4, 1, 3, 9, 5, 0)), + Perm((2, 3, 5, 0, 4, 1)), + Perm((5, 6, 0, 7, 4, 3, 1, 2, 8)), + Perm((4, 3, 2, 5, 1, 0, 6, 7)), + } + ): False, +} + + +def test_is_polynomial(): + for k, v in expected.items(): + assert PolyPerms.is_polynomial(k) == v + assert PolyPerms.is_non_polynomial(k) != v diff --git a/tests/permutils/test_symmetry.py b/tests/permutils/test_symmetry.py index c2521cf3..010291b4 100644 --- a/tests/permutils/test_symmetry.py +++ b/tests/permutils/test_symmetry.py @@ -1,15 +1,16 @@ import random -import pytest -from permuta import Perm, PermSet +from permuta import Perm from permuta.permutils.symmetry import ( all_symmetry_sets, inverse_set, lex_min, reverse_set, - rotate_set, + rotate_90_clockwise_set, ) +rotate_set = rotate_90_clockwise_set + def get_inp(): return [ @@ -789,132 +790,65 @@ def get_inp(): def test_rotate_set(): inp = get_inp() for x in inp: - assert rotate_set(x["input"]) == x["rotate"] + assert set(rotate_set(x["input"])) == set(x["rotate"]) def test_inverse_set(): inp = get_inp() for x in inp: - assert inverse_set(x["input"]) == x["inverse"] + assert set(inverse_set(x["input"])) == set(x["inverse"]) def test_reverse_set(): inp = get_inp() for x in inp: - assert reverse_set(x["input"]) == x["reverse"] + assert set(reverse_set(x["input"])) == set(x["reverse"]) def test_roundtrip_rotate(): for i in range(100): n = random.randint(0, 100) - perm_set = PermSet(n) - input_set = set([perm_set.random() for i in range(random.randint(1, 100))]) + input_set = set([Perm.random(n) for i in range(random.randint(1, 100))]) output_set = input_set for i in range(4): output_set = rotate_set(output_set) - assert input_set == output_set + assert set(input_set) == set(output_set) def test_roundtrip_inverse(): for i in range(100): n = random.randint(0, 100) - perm_set = PermSet(n) - input_set = set([perm_set.random() for i in range(random.randint(1, 100))]) + input_set = set([Perm.random(n) for i in range(random.randint(1, 100))]) output_set = input_set for i in range(2): output_set = inverse_set(output_set) - assert input_set == output_set + assert set(input_set) == set(output_set) def test_roundtrip_reverse(): for i in range(100): n = random.randint(0, 100) - perm_set = PermSet(n) - input_set = set([perm_set.random() for i in range(random.randint(1, 100))]) + input_set = set([Perm.random(n) for i in range(random.randint(1, 100))]) output_set = input_set for i in range(4): output_set = reverse_set(output_set) - assert input_set == output_set - - -def test_rotate_type(): - p = PermSet(10).random() - assert type(rotate_set([p])) == list - assert type(rotate_set((p,))) == tuple - assert type(rotate_set({p})) == set + assert set(input_set) == set(output_set) def test_rotate_set_length(): - perm_list = [PermSet(6).random() for i in range(10)] - perm_tup = tuple(perm_list) + perm_list = [Perm.random(6) for i in range(10)] perm_set = set(perm_list) - - assert len(rotate_set(perm_list)) == len(perm_list) - assert len(rotate_set(perm_tup)) == len(perm_tup) - assert len(rotate_set(perm_set)) == len(perm_set) - - -def test_inverse_type(): - p = PermSet(10).random() - assert type(inverse_set([p])) == list - assert type(inverse_set((p,))) == tuple - assert type(inverse_set({p})) == set + assert len(list(rotate_set(perm_set))) == len(perm_set) def test_inverse_set_length(): - perm_list = [PermSet(6).random() for i in range(10)] - perm_tup = tuple(perm_list) - perm_set = set(perm_list) - - assert len(inverse_set(perm_list)) == len(perm_list) - assert len(inverse_set(perm_tup)) == len(perm_tup) - assert len(inverse_set(perm_set)) == len(perm_set) - - -def test_reverse_type(): - p = PermSet(10).random() - assert type(reverse_set([p])) == list - assert type(reverse_set((p,))) == tuple - assert type(reverse_set({p})) == set - - -def test_reverse_set_length(): - perm_list = [PermSet(6).random() for i in range(10)] - perm_tup = tuple(perm_list) + perm_list = [Perm.random(6) for i in range(10)] perm_set = set(perm_list) - assert len(reverse_set(perm_list)) == len(perm_list) - assert len(reverse_set(perm_tup)) == len(perm_tup) - assert len(reverse_set(perm_set)) == len(perm_set) - - -def test_raise_TypeError(): - inp = ( - 2, - 2.04, - 1.0, - 0, - True, - None, - "Hello World", - [""], - [Perm((1, 0, 3, 2)), 0], - [Perm((1, 0, 3, 2)), "Hello"], - [Perm((1, 0, 3, 2)), 0.4], - [Perm((1, 0, 3, 2)), False], - {Perm((1, 0, 3, 2)), 0}, - (Perm((1, 0, 3, 2)), 0), - ) - for i in inp: - with pytest.raises(TypeError): - rotate_set(i) - with pytest.raises(TypeError): - inverse_set(i) - with pytest.raises(TypeError): - reverse_set(i) + assert len(list(inverse_set(perm_set))) == len(perm_set) def test_input_all_symmetries(): @@ -968,34 +902,11 @@ def test_input_all_symmetries(): def test_length_of_output_should_be_1_2_4_or_8(): for i in range(100): n = random.randint(0, 100) - perm_set = PermSet(n) - input_set = set([perm_set.random() for i in range(random.randint(1, 100))]) + input_set = set([Perm.random(n) for i in range(random.randint(1, 100))]) assert len(all_symmetry_sets(input_set)) in [1, 2, 4, 8] -def test_all_symmetries_raise_TypeError(): - inp = ( - 2, - 2.04, - 1.0, - 0, - True, - None, - "Hello World", - [""], - [Perm((1, 0, 3, 2)), 0], - [Perm((1, 0, 3, 2)), "Hello"], - [Perm((1, 0, 3, 2)), 0.4], - [Perm((1, 0, 3, 2)), False], - {Perm((1, 0, 3, 2)), 0}, - (Perm((1, 0, 3, 2)), 0), - ) - for i in inp: - with pytest.raises(TypeError): - all_symmetry_sets(i) - - def test_input_lex_min(): inp = [ {"input": {(Perm(()),)}, "output": (Perm(()),)}, @@ -1065,25 +976,3 @@ def test_input_lex_min(): for x in inp: for i in x["input"]: assert lex_min(i) == x["output"] - - -def test_lex_min_raise_TypeError(): - inp = ( - 2, - 2.04, - 1.0, - 0, - True, - None, - "Hello World", - [""], - [Perm((1, 0, 3, 2)), 0], - [Perm((1, 0, 3, 2)), "Hello"], - [Perm((1, 0, 3, 2)), 0.4], - [Perm((1, 0, 3, 2)), False], - {Perm((1, 0, 3, 2)), 0}, - (Perm((1, 0, 3, 2)), 0), - ) - for i in inp: - with pytest.raises(TypeError): - lex_min(i) diff --git a/tests/test_av.py b/tests/test_av.py deleted file mode 100644 index 0ea626ce..00000000 --- a/tests/test_av.py +++ /dev/null @@ -1,27 +0,0 @@ -from permuta import Av, MeshPatt, Perm -from permuta.permset import PermSetAll - - -def test_av_perm(): - p = Perm((0, 1)) - av = Av([p]) - for length in range(10): - assert len(set(av.of_length(length))) == 1 - - -def test_av_meshpatt(): - p = Perm((2, 0, 1)) - shading = ((2, 0), (2, 1), (2, 2), (2, 3)) - mp = MeshPatt(p, shading) - av = Av([mp]) - enum = [1, 1, 2, 5, 15, 52, 203, 877] # Bell numbers - - for (n, cnt) in enumerate(enum): - inst = av.of_length(n) - gen = list(inst) - assert len(gen) == cnt - - -def test_av_empty(): - bases = [[], tuple(), set(), frozenset(), None] - assert all(isinstance(Av(basis), PermSetAll) for basis in bases) diff --git a/tests/test_meshpatt.py b/tests/test_meshpatt.py deleted file mode 100644 index c17ac161..00000000 --- a/tests/test_meshpatt.py +++ /dev/null @@ -1,754 +0,0 @@ -import itertools -import random - -import pytest -from permuta import MeshPatt, Perm -from permuta.meshpattset import gen_meshpatts -from permuta.misc import DIR_EAST, DIR_NORTH, DIR_SOUTH, DIR_WEST, factorial - -mesh_pattern = MeshPatt( - [1, 3, 2, 0], set([(0, 0), (4, 0), (2, 1), (4, 1), (2, 2), (4, 2), (3, 3), (0, 4)]) -) -perm1 = Perm([5, 2, 8, 6, 7, 9, 4, 3, 1, 0]) # Occurrence: E.g., [1, 3, 6, 9] -perm2 = Perm([1, 2, 8, 6, 5, 9, 4, 3, 7, 0]) # Occurrence: E.g., [1, 6, 7, 9] -perm3 = Perm([9, 8, 7, 6, 2, 5, 3, 4, 0, 1]) # Occurrence: None (avoids) -perm4 = Perm([0, 1, 2, 3, 4]) # Avoids as well -perm5 = Perm([1, 2, 4, 3, 0]) # Two occurrences -patt1 = perm4 -patt2 = perm5 -patt3 = Perm((2, 3, 0, 1)) -shad1 = frozenset( - [(0, 0), (1, 0), (2, 0), (2, 1), (3, 2), (3, 3), (5, 0), (5, 1), (5, 2)] -) -shad2 = frozenset([(3, 3), (2, 2), (1, 1), (0, 2)]) -shad3 = frozenset([(1, 3), (4, 4), (2, 1), (2, 2), (0, 4), (4, 0)]) -mesh1 = MeshPatt(patt1, shad1) -mesh2 = MeshPatt(patt2, shad2) -mesh3 = MeshPatt(patt3, shad3) - - -def test_init(): - with pytest.raises(ValueError): - MeshPatt(Perm([0, 1, 1], check=True), ()) - with pytest.raises(ValueError): - MeshPatt(Perm([1, 0, 1], check=True), ()) - with pytest.raises(ValueError): - MeshPatt(Perm([0, 0], check=True), ()) - with pytest.raises(ValueError): - MeshPatt(Perm([1], check=True), ()) - with pytest.raises(ValueError): - MeshPatt(Perm((1,), check=True), ()) - with pytest.raises(TypeError): - MeshPatt(Perm(101, check=True), ()) - with pytest.raises(TypeError): - MeshPatt(Perm(-234, check=True), ()) - with pytest.raises(TypeError): - MeshPatt(Perm(None, check=True), ()) - with pytest.raises(TypeError): - MeshPatt(Perm([0.1, 0.2, 0.3], check=True), ()) - with pytest.raises(TypeError): - MeshPatt(Perm((), check=True), (0, 1)) - with pytest.raises(TypeError): - MeshPatt(Perm((0, 1, 2), check=True), [(1, "a")]) - with pytest.raises(TypeError): - MeshPatt(Perm((0, 1, 2), check=True), [("a", 1)]) - with pytest.raises(ValueError): - MeshPatt(Perm.random(5), [(0,), (1, 1)]) - with pytest.raises(ValueError): - MeshPatt(Perm.random(5), [(0, 0, 0), (1, 1, 1)]) - with pytest.raises(ValueError): - MeshPatt(Perm((), check=True), [(0, 1), (1, 0)]) - with pytest.raises(ValueError): - MeshPatt(Perm.random(3), [(0, -1), (0, 0)]) - with pytest.raises(ValueError): - MeshPatt(Perm.random(10), [(0, 0), (12, 7)]) - MeshPatt(Perm(check=True), ()) - MeshPatt(Perm([], check=True), ()) - MeshPatt(Perm((0,), check=True), ()) - MeshPatt(Perm([0], check=True), ()) - MeshPatt(Perm([3, 0, 2, 1], check=True), ()) - MeshPatt( - Perm([3, 0, 2, 1], check=True), - [ - (0, 0), - (0, 1), - (0, 2), - (0, 3), - (0, 4), - (1, 0), - (1, 1), - (1, 2), - (1, 3), - (1, 4), - (2, 0), - (2, 1), - (2, 2), - (2, 3), - (2, 4), - (3, 0), - (3, 1), - (3, 2), - (3, 3), - (3, 4), - (4, 0), - (4, 1), - (4, 2), - (4, 3), - (4, 4), - ], - ) - MeshPatt(Perm([3, 0, 2, 1], check=True), [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4)]) - MeshPatt([3, 0, 2, 1], [(0, 2), (0, 3), (0, 4)]) - MeshPatt(set([3, 0, 2, 1]), [(0, 2), (0, 3), (0, 4)]) - - -def test_complement(): - assert MeshPatt(Perm(), []).complement() == MeshPatt(Perm(), []) - assert MeshPatt(Perm((0,)), []).complement() == MeshPatt(Perm((0,)), []) - assert MeshPatt(Perm((0,)), [(0, 0)]).complement() == MeshPatt(Perm((0,)), [(0, 1)]) - assert MeshPatt(Perm((0,)), [(0, 1)]).complement() == MeshPatt(Perm((0,)), [(0, 0)]) - assert MeshPatt(Perm((0,)), [(1, 0)]).complement() == MeshPatt(Perm((0,)), [(1, 1)]) - assert MeshPatt(Perm((0,)), [(1, 1)]).complement() == MeshPatt(Perm((0,)), [(1, 0)]) - for _ in range(20): - mpatt = MeshPatt.random(10) - comp = mpatt.complement() - assert len(mpatt.pattern) == len(comp.pattern) - assert comp.complement() == mpatt - - -def test_flip_horizontal(): - assert MeshPatt(Perm(), []).flip_vertical() == MeshPatt(Perm(), []) - assert MeshPatt(Perm((0,)), [(0, 1)]).flip_horizontal() == MeshPatt( - Perm((0,)), [(0, 0)] - ) - - -def test_reverse(): - assert MeshPatt(Perm(), []).reverse() == MeshPatt(Perm(), []) - assert MeshPatt(Perm((0,)), []).reverse() == MeshPatt(Perm((0,)), []) - assert MeshPatt(Perm((0,)), [(0, 0)]).reverse() == MeshPatt(Perm((0,)), [(1, 0)]) - assert MeshPatt(Perm((0,)), [(0, 1)]).reverse() == MeshPatt(Perm((0,)), [(1, 1)]) - assert MeshPatt(Perm((0,)), [(1, 0)]).reverse() == MeshPatt(Perm((0,)), [(0, 0)]) - assert MeshPatt(Perm((0,)), [(1, 1)]).reverse() == MeshPatt(Perm((0,)), [(0, 1)]) - for _ in range(20): - mpatt = MeshPatt.random(10) - comp = mpatt.reverse() - assert len(mpatt.pattern) == len(comp.pattern) - assert comp.reverse() == mpatt - - -def test_flip_vertical(): - assert MeshPatt(Perm(), []).flip_vertical() == MeshPatt(Perm(), []) - assert MeshPatt(Perm((0,)), [(0, 0)]).flip_vertical() == MeshPatt( - Perm((0,)), [(1, 0)] - ) - - -def test_inverse(): - assert MeshPatt(Perm(), []).inverse() == MeshPatt(Perm(), []) - assert MeshPatt(Perm((0,)), []).inverse() == MeshPatt(Perm((0,)), []) - assert MeshPatt(Perm((0,)), [(0, 0)]).inverse() == MeshPatt(Perm((0,)), [(0, 0)]) - assert MeshPatt(Perm((0,)), [(0, 1)]).inverse() == MeshPatt(Perm((0,)), [(1, 0)]) - assert MeshPatt(Perm((0,)), [(1, 0)]).inverse() == MeshPatt(Perm((0,)), [(0, 1)]) - assert MeshPatt(Perm((0,)), [(1, 1)]).inverse() == MeshPatt(Perm((0,)), [(1, 1)]) - for _ in range(20): - mpatt = MeshPatt.random(10) - comp = mpatt.inverse() - assert len(mpatt.pattern) == len(comp.pattern) - assert comp.inverse() == mpatt - - -def test_sub_mesh_pattern(): - # Empty pattern - assert mesh1.sub_mesh_pattern(()) == MeshPatt((), ()) - # Sub mesh pattern from indices 1, 2, and 3 of mesh1 - pattern = (0, 1, 2) - shading = set([(1, 0), (2, 1), (2, 2)]) - mesh_pattern = MeshPatt(pattern, shading) - sub_mesh_pattern = mesh1.sub_mesh_pattern((1, 2, 3)) - assert sub_mesh_pattern == mesh_pattern - # Sub mesh pattern from indices 0, 1, and 4 of mesh1 - pattern = (0, 1, 2) - shading = set([(0, 0), (1, 0), (3, 0), (3, 1)]) - mesh_pattern = MeshPatt(pattern, shading) - sub_mesh_pattern = mesh1.sub_mesh_pattern((0, 1, 4)) - assert sub_mesh_pattern == mesh_pattern - # Sub mesh pattern from indices 3 and 4 of mesh1 - assert mesh1.sub_mesh_pattern((3, 4)) == MeshPatt((0, 1)) - # Sub mesh pattern from indices 2 and 3 of mesh1 - assert mesh1.sub_mesh_pattern((2, 3)) == MeshPatt((0, 1), set([(1, 1)])) - # Sub mesh pattern from index 0 of mesh3 - assert mesh3.sub_mesh_pattern((0,)) == MeshPatt((0,)) - # Sub mesh pattern from indices 0, 1, and 3 of mesh3 - assert mesh3.sub_mesh_pattern((0, 1, 3)) == MeshPatt( - (1, 2, 0), set([(0, 3), (1, 2), (3, 3)]) - ) - # Some complete sub meshes - assert mesh1.sub_mesh_pattern(range(len(mesh1))) == mesh1 - assert mesh2.sub_mesh_pattern(range(len(mesh2))) == mesh2 - assert mesh3.sub_mesh_pattern(range(len(mesh3))) == mesh3 - - mpatt = MeshPatt((0, 1), [(0, 0), (0, 1), (1, 0), (1, 1)]) - assert mpatt.sub_mesh_pattern([0]) == MeshPatt((0,), [(0, 0)]) - assert mpatt.sub_mesh_pattern([1]) == MeshPatt((0,)) - - mpatt = MeshPatt((0, 1, 2), [(0, 1), (1, 1), (1, 2), (2, 1), (2, 2), (3, 1)]) - mpatt.sub_mesh_pattern([0, 1]) == MeshPatt((0, 1), [(0, 1), (1, 1), (2, 1)]) - mpatt.sub_mesh_pattern([0, 2]) == MeshPatt((0, 1), []) - - -def test_flip_diagonal(): - assert MeshPatt(Perm(), []).flip_diagonal() == MeshPatt(Perm(), []) - assert MeshPatt(Perm((0,)), [(0, 1)]).flip_diagonal() == MeshPatt( - Perm((0,)), [(1, 0)] - ) - - -def test_rotate(): - assert MeshPatt(Perm(), [])._rotate_right() == MeshPatt(Perm(), []) - assert MeshPatt(Perm(), [])._rotate_left() == MeshPatt(Perm(), []) - assert MeshPatt(Perm(), [])._rotate_180() == MeshPatt(Perm(), []) - - assert MeshPatt(Perm((0,)), [])._rotate_right() == MeshPatt(Perm((0,)), []) - assert MeshPatt(Perm((0,)), [])._rotate_left() == MeshPatt(Perm((0,)), []) - assert MeshPatt(Perm((0,)), [])._rotate_180() == MeshPatt(Perm((0,)), []) - - for _ in range(50): - mpatt = MeshPatt.random(6) - assert mpatt._rotate_right()._rotate_right() == mpatt._rotate_180() - assert mpatt._rotate_left()._rotate_left() == mpatt._rotate_180() - assert mpatt._rotate_right()._rotate_left() == mpatt - assert mpatt._rotate_left()._rotate_right() == mpatt - assert mpatt._rotate_180()._rotate_180() == mpatt - - -def test_shade(): - assert MeshPatt().shade((0, 0)).is_shaded((0, 0)) - assert MeshPatt().shade([(0, 0)]).is_shaded((0, 0)) - - newshad = [(1, 2), (3, 3), (4, 4)] - mesh1shaded = mesh1.shade(newshad) - for shading in shad1: - assert mesh1shaded.is_shaded(shading) - for shading in newshad: - assert mesh1shaded.is_shaded((shading)) - - newshad = [(2, 2), (1, 1), (0, 2), (1, 2), (3, 3), (4, 4)] - mesh2shaded = mesh2.shade(newshad) - for shading in shad2: - assert mesh2shaded.is_shaded(shading) - for shading in newshad: - assert mesh2shaded.is_shaded(shading) - - with pytest.raises(ValueError): - mesh2.shade(()) - with pytest.raises(ValueError): - mesh1.shade(()) - - -def test_add_point(): - mpatt = MeshPatt() - assert mpatt.add_point((0, 0)) == MeshPatt((0,)) - assert mpatt.add_point((0, 0), shade_dir=DIR_EAST) == MeshPatt( - (0,), [(1, 0), (1, 1)] - ) - assert mpatt.add_point((0, 0), shade_dir=DIR_NORTH) == MeshPatt( - (0,), [(0, 1), (1, 1)] - ) - assert mpatt.add_point((0, 0), shade_dir=DIR_WEST) == MeshPatt( - (0,), [(0, 0), (0, 1)] - ) - assert mpatt.add_point((0, 0), shade_dir=DIR_SOUTH) == MeshPatt( - (0,), [(0, 0), (1, 0)] - ) - - mpatt = MeshPatt((0, 1, 2), [(1, 0), (2, 1), (3, 2)]) - assert mpatt.add_point((2, 0), shade_dir=DIR_SOUTH) == MeshPatt( - (1, 2, 0, 3), [(1, 0), (1, 1), (2, 0), (2, 2), (3, 0), (3, 2), (4, 3)] - ) - assert mpatt.add_point((1, 2), shade_dir=DIR_WEST) == MeshPatt( - (0, 2, 1, 3), [(1, 0), (1, 2), (1, 3), (2, 0), (3, 1), (4, 2), (4, 3)] - ) - assert mesh1.add_point((2, 3), shade_dir=DIR_NORTH) == MeshPatt( - (0, 1, 3, 2, 4, 5), - [ - (0, 0), - (1, 0), - (2, 0), - (2, 1), - (2, 4), - (3, 0), - (3, 1), - (3, 4), - (4, 2), - (4, 3), - (4, 4), - (6, 0), - (6, 1), - (6, 2), - ], - ) - - with pytest.raises(TypeError): - mpatt.add_point(("a", (0,))) - with pytest.raises(ValueError): - mpatt.add_point((2, 1)) - - -def test_add_increase(): - mpatt = MeshPatt() - assert mpatt.add_increase((0, 0)) == MeshPatt((0, 1)) - mpatt = MeshPatt((0, 1, 2), [(1, 0), (2, 1), (3, 2)]) - assert mpatt.add_increase((2, 0)) == MeshPatt( - Perm((2, 3, 0, 1, 4)), [(1, 2), (5, 4), (3, 3), (2, 3), (4, 3), (1, 0), (1, 1)] - ) - assert mpatt.add_increase((1, 2)) == MeshPatt( - (0, 2, 3, 1, 4), [(1, 0), (2, 0), (3, 0), (4, 1), (5, 2), (5, 3), (5, 4)] - ) - - -def test_add_decrease(): - mpatt = MeshPatt() - assert mpatt.add_decrease((0, 0)) == MeshPatt((1, 0)) - mpatt = MeshPatt((0, 1, 2), [(1, 0), (2, 1), (3, 2)]) - assert mpatt.add_decrease((2, 0)) == MeshPatt( - Perm((2, 3, 1, 0, 4)), [(1, 2), (5, 4), (3, 3), (2, 3), (4, 3), (1, 0), (1, 1)] - ) - assert mpatt.add_decrease((1, 2)) == MeshPatt( - (0, 3, 2, 1, 4), [(1, 0), (2, 0), (3, 0), (4, 1), (5, 2), (5, 3), (5, 4)] - ) - - -def test_contained_in(): - assert Perm([0, 1, 2]).contains(MeshPatt([0, 1], set([(1, 0), (1, 1), (1, 2)]))) - assert mesh_pattern.contained_in(perm1) - assert mesh_pattern.contained_in(perm2) - assert not (mesh_pattern.contained_in(perm3)) - assert mesh_pattern.contained_in(perm1, perm2) - assert not (mesh_pattern.contained_in(perm1, perm3)) - - -def test_meshpatt_contains_meshpatt(): - p_12 = Perm([0, 1]) - p_123 = Perm([0, 1, 2]) - small_mesh_patt = MeshPatt(p_12, [(1, 0), (1, 1), (1, 2)]) - big_mesh_patt_1st_col = MeshPatt(p_123, [(1, 0), (1, 1), (1, 2), (1, 3)]) - big_mesh_patt_2nd_col = MeshPatt(p_123, [(2, 0), (2, 1), (2, 2), (2, 3)]) - assert small_mesh_patt in big_mesh_patt_1st_col - assert small_mesh_patt in big_mesh_patt_2nd_col - assert big_mesh_patt_1st_col not in small_mesh_patt - assert big_mesh_patt_2nd_col not in small_mesh_patt - assert big_mesh_patt_1st_col not in big_mesh_patt_2nd_col - assert big_mesh_patt_2nd_col not in big_mesh_patt_1st_col - - -def test_avoided_by(): - assert not (mesh_pattern.avoided_by(perm1)) - assert not (mesh_pattern.avoided_by(perm2)) - assert mesh_pattern.avoided_by(perm3) - assert mesh_pattern.avoided_by(perm4) - assert mesh_pattern.avoided_by(perm3, perm4) - assert not (mesh_pattern.avoided_by(perm4, perm1, perm3)) - - -def test_count_occurrences_in(): - assert mesh_pattern.count_occurrences_in(perm1) == 8 - assert mesh_pattern.count_occurrences_in(perm2) == 12 - assert mesh_pattern.count_occurrences_in(perm3) == 0 - assert mesh_pattern.count_occurrences_in(perm4) == 0 - assert mesh_pattern.count_occurrences_in(perm5) == 2 - - -def test_is_shaded(): - mpatt = MeshPatt(Perm(), ((0, 0),)) - assert mpatt.is_shaded((0, 0)) - mpatt = MeshPatt( - (0, 2, 1), [(0, 0), (0, 2), (1, 1), (1, 3), (2, 0), (2, 2), (3, 1), (3, 3)] - ) - for (x0, y0) in itertools.combinations(range(len(mpatt) + 1), 2): - for (x1, y1) in itertools.combinations(range(len(mpatt) + 1), 2): - if x0 > x1 or y0 > y1: - with pytest.raises(ValueError): - mpatt.is_shaded((x0, y0), (x1, y1)) - elif x0 == x1 and y0 == y1: - if (x0 + y0) % 2 == 0: - assert mpatt.is_shaded((x0, y0)) - assert mpatt.is_shaded((x0, y0), (x1, y1)) - else: - assert not mpatt.is_shaded((x0, y0), (x1, y1)) - with pytest.raises(ValueError): - mpatt.is_shaded((4, 0)) - with pytest.raises(ValueError): - mpatt.is_shaded((0, 4)) - with pytest.raises(ValueError): - mpatt.is_shaded((-1, 2)) - with pytest.raises(ValueError): - mpatt.is_shaded((0, 0), (0, 4)) - - -def test_is_pointfree(): - mpatt = MeshPatt((0, 1), [(0, 0), (0, 1), (1, 0), (1, 1)]) - assert not mpatt.is_pointfree((0, 0), (1, 1)) - assert not mpatt.is_pointfree((1, 1), (2, 2)) - assert not mpatt.is_pointfree((0, 1), (2, 2)) - assert mpatt.is_pointfree((0, 1), (2, 1)) - assert not mpatt.is_pointfree((0, 1), (2, 2)) - mpatt = MeshPatt((0, 1, 2), [(0, 1), (1, 1), (1, 2), (2, 1), (2, 2), (3, 1)]) - assert not mpatt.is_pointfree((1, 1), (2, 2)) - - -def test_can_shade(): - assert not (MeshPatt().can_shade((0, 0))) - assert MeshPatt((0,)).can_shade((0, 0)) == [0] - assert MeshPatt((0,)).can_shade((1, 0)) == [0] - assert not (MeshPatt((0,), [(0, 0)]).can_shade((0, 0))) - assert not (MeshPatt((0,), [(0, 0)]).can_shade((1, 1))) - assert not mesh_pattern.can_shade((1, 1)) - assert not mesh_pattern.can_shade((3, 2)) - assert not mesh_pattern.can_shade((1, 2)) - assert mesh_pattern.can_shade((0, 1)) == [1] - mpatt = MeshPatt((1, 2, 0), [(2, 2), (3, 0), (3, 2), (3, 3)]) - assert list(sorted(mpatt.can_shade((1, 2)))) == [1, 2] - assert mpatt.can_shade((3, 1)) == [0] - - -def test_can_simul_shade(): - assert not (MeshPatt().can_simul_shade((0, 0), (0, 0))) - assert MeshPatt((0,)).can_simul_shade((0, 0), (0, 1)) == [0] - assert not (MeshPatt((0, 1, 2)).can_simul_shade((2, 0), (2, 2))) - assert not mesh1.can_simul_shade((2, 2), (3, 2)) - assert not mesh1.can_simul_shade((1, 2), (2, 2)) - assert not mesh2.can_simul_shade((3, 3), (4, 3)) - assert not mesh2.can_simul_shade((3, 4), (4, 4)) - assert not mesh1.can_simul_shade((4, 4), (4, 5)) - assert not mesh1.can_simul_shade((4, 5), (5, 5)) - assert mesh1.can_simul_shade((5, 4), (5, 5)) == [4] - - mpatt = MeshPatt( - (2, 3, 0, 1), [(0, 4), (1, 3), (2, 1), (2, 2), (3, 4), (4, 0), (4, 4)] - ) - assert mpatt.can_simul_shade((4, 1), (4, 2)) == [1] - mpatt = MeshPatt( - (1, 2, 0), [(0, 2), (0, 3), (1, 1), (2, 0), (2, 1), (3, 2), (3, 3)] - ) - assert mpatt.can_simul_shade((2, 2), (2, 3)) == [2] - assert not mpatt.can_simul_shade((1, 2), (2, 2)) - assert not mpatt.can_simul_shade((1, 2), (1, 3)) - - -def test_shadable_boxes(): - assert not (MeshPatt().shadable_boxes()) - assert len(MeshPatt((0,)).shadable_boxes()[0]) == 8 - assert len(MeshPatt((0, 1)).shadable_boxes()[0]) == 8 - assert len(MeshPatt((0, 1)).shadable_boxes()[1]) == 8 - - -def test_non_pointless_boxes(): - assert MeshPatt(Perm()).non_pointless_boxes() == set() - assert MeshPatt((0,)).non_pointless_boxes() == set([(0, 0), (0, 1), (1, 0), (1, 1)]) - assert MeshPatt( - (0, 1, 2), [(0, 0), (1, 2,), (0, 2), (2, 0), (2, 1)] - ).non_pointless_boxes() == set( - [(0, 0), (0, 1), (1, 0), (1, 1), (1, 2), (2, 1), (2, 2), (2, 3), (3, 2), (3, 3)] - ) - print(mesh2) - assert mesh2.non_pointless_boxes() == set( - [ - (0, 1), - (0, 2), - (1, 1), - (1, 2), - (1, 3), - (2, 2), - (2, 3), - (2, 4), - (2, 5), - (3, 3), - (3, 4), - (3, 5), - (4, 0), - (4, 1), - (4, 3), - (4, 4), - (5, 0), - (5, 1), - ] - ) - - -def test_has_anchered_point(): - assert not any(MeshPatt(Perm()).has_anchored_point()) - assert all(MeshPatt(Perm(), [(0, 0)]).has_anchored_point()) - assert not any(MeshPatt(Perm.random(5)).has_anchored_point()) - assert all(MeshPatt.unrank((1, 2, 0), 65535)) - right, top, left, bottom = MeshPatt( - (0, 1, 2), [(0, i) for i in range(4)] - ).has_anchored_point() - assert left and not (right or top or bottom) - right, top, left, bottom = MeshPatt( - (0, 1, 2), [(3, i) for i in range(4)] - ).has_anchored_point() - assert right and not (left or top or bottom) - right, top, left, bottom = MeshPatt( - (0, 1, 2), [(i, 0) for i in range(4)] - ).has_anchored_point() - assert bottom and not (left or top or right) - right, top, left, bottom = MeshPatt( - (0, 1, 2), [(i, 3) for i in range(4)] - ).has_anchored_point() - assert top and not (left or bottom or right) - - mpatt = MeshPatt.random(5) - leftshad = [(0, i) for i in range(len(mpatt) + 1)] - rightshad = [(len(mpatt), i) for i in range(len(mpatt) + 1)] - upshad = [(i, len(mpatt)) for i in range(len(mpatt) + 1)] - bottomshad = [(i, 0) for i in range(len(mpatt) + 1)] - right, top, left, bottom = mpatt.shade(leftshad).has_anchored_point() - assert left - right, top, left, bottom = mpatt.shade(rightshad).has_anchored_point() - assert right - right, top, left, bottom = mpatt.shade(upshad).has_anchored_point() - assert top - right, top, left, bottom = mpatt.shade(bottomshad).has_anchored_point() - assert bottom - - -def test_rank(): - assert ( - MeshPatt(Perm((0, 1)), [(0, 1), (1, 2), (2, 1), (2, 0), (2, 2), (1, 1)]).rank() - == 498 - ) - assert ( - MeshPatt( - Perm((1, 0, 2)), - [(3, 2), (0, 0), (2, 3), (1, 0), (0, 1), (1, 2), (3, 3), (3, 1), (2, 0)], - ).rank() - == 59731 - ) - assert ( - MeshPatt( - Perm((0, 3, 2, 1)), [(0, 1), (4, 4), (1, 4), (2, 3), (4, 2), (4, 1), (0, 2)] - ).rank() - == 23077382 - ) - - pattern = Perm([1, 2]) - mesh = MeshPatt(pattern, set([(0, 1), (0, 2), (1, 2), (2, 0), (2, 1)])) - assert mesh.rank() == 230 - - pattern = Perm([3, 1, 2]) - mesh = MeshPatt( - pattern, - set( - [ - (0, 0), - (0, 1), - (0, 2), - (1, 0), - (1, 2), - (2, 0), - (2, 1), - (2, 2), - (2, 3), - (3, 0), - ] - ), - ) - assert mesh.rank() == 8023 - - assert mesh1.rank() == 0b111000000001100000011000001000001 - - for _ in range(50): - mesh = MeshPatt.random(random.randint(0, 20)) - assert MeshPatt.unrank(mesh.pattern, mesh.rank()) == mesh - - -def test_unrank(): - assert MeshPatt.unrank(Perm((0, 1)), 498) == MeshPatt( - Perm((0, 1)), frozenset({(0, 1), (1, 2), (2, 1), (2, 0), (2, 2), (1, 1)}) - ) - assert MeshPatt.unrank(Perm((1, 0, 2)), 59731) == MeshPatt( - Perm((1, 0, 2)), - frozenset( - {(3, 2), (0, 0), (2, 3), (1, 0), (0, 1), (1, 2), (3, 3), (3, 1), (2, 0)} - ), - ) - assert MeshPatt.unrank(Perm((0, 3, 2, 1)), 23077382) == MeshPatt( - Perm((0, 3, 2, 1)), - frozenset({(0, 1), (4, 4), (1, 4), (2, 3), (4, 2), (4, 1), (0, 2)}), - ) - - pattern = Perm([1, 2]) - mesh = MeshPatt(pattern, set([(0, 1), (0, 2), (1, 2), (2, 0), (2, 1)])) - assert MeshPatt.unrank(pattern, 230) == mesh - - pattern = Perm([3, 1, 2]) - mesh = MeshPatt( - pattern, - set( - [ - (0, 0), - (0, 1), - (0, 2), - (1, 0), - (1, 2), - (2, 0), - (2, 1), - (2, 2), - (2, 3), - (3, 0), - ] - ), - ) - assert MeshPatt.unrank(pattern, 8023) == mesh - - assert MeshPatt.unrank(patt1, 0b111000000001100000011000001000001) == mesh1 - - for length in range(20): - m = MeshPatt.unrank(Perm.random(length), 0) - assert not m.shading - for length in range(20): - m = MeshPatt.unrank(Perm.random(length), 2 ** ((length + 1) ** 2) - 1) - assert len(m.shading) == (length + 1) ** 2 - - with pytest.raises(ValueError): - MeshPatt.unrank([1, 2, 3], -1) - with pytest.raises(ValueError): - MeshPatt.unrank(Perm([1]), 16) - with pytest.raises(ValueError): - MeshPatt.unrank(Perm([1]), -1) - with pytest.raises(TypeError): - MeshPatt.unrank(Perm.random(length), "haha") - with pytest.raises(TypeError): - MeshPatt.unrank(Perm([1]), "1") - with pytest.raises(ValueError): - MeshPatt.unrank(Perm.random(10), 2 ** ((10 + 1) ** 2) + 1) - - -def test_random(): - assert MeshPatt.random(0) in set( - [MeshPatt(Perm(()), []), MeshPatt(Perm(()), [(0, 0)])] - ) - assert MeshPatt.random(1) in set( - MeshPatt.unrank(Perm((0,)), i) for i in range(0, 16) - ) - assert MeshPatt.random(2) in ( - set(MeshPatt.unrank(Perm((0, 1)), i) for i in range(0, 512)) - | set(MeshPatt.unrank(Perm((1, 0)), i) for i in range(0, 512)) - ) - for length in range(3, 20): - assert len(MeshPatt.random(length)) == length - - -def test_len(): - assert len(mesh1) == 5 - assert len(mesh2) == 5 - assert len(mesh3) == 4 - assert len(MeshPatt()) == 0 - assert len(MeshPatt((1,))) == 1 - for _ in range(20): - length = random.randint(0, 20) - m = MeshPatt( - Perm.random(length), - random.sample( - [(i, j) for i in range(length + 1) for j in range(length + 1)], k=length - ), - ) - assert len(m) == length - - -def test_bool(): - assert mesh1 - assert mesh2 - assert mesh3 - assert MeshPatt(Perm((1,))) - assert MeshPatt((), set([(0, 0)])) - assert MeshPatt(Perm([0, 1, 2, 3]), ()) - assert MeshPatt(Perm([0, 1, 2, 3]), ((0, 0),)) - assert MeshPatt(Perm([0]), ()) - assert not (MeshPatt(Perm([]), ())) - assert not (MeshPatt(Perm(), ())) - - -def test_eq(): - assert not (mesh1 == mesh2) - assert not (mesh1 == mesh3) - assert not (mesh2 == mesh1) - assert not (mesh2 == mesh3) - assert not (mesh3 == mesh1) - assert not (mesh3 == mesh2) - mesh1copy = MeshPatt(patt1, shad1) - mesh2copy = MeshPatt(patt2, shad2) - mesh3copy = MeshPatt(patt3, shad3) - assert mesh1 == mesh1copy - assert mesh2 == mesh2copy - assert mesh3 == mesh3copy - assert mesh1copy == mesh1copy - assert mesh2copy == mesh2copy - assert mesh3copy == mesh3copy - - -def test_ordering(): - # mesh1, mesh2 and mesh3 have different underlying patterns - mp1 = MeshPatt(perm1, shad1) - mp2 = MeshPatt(perm1, shad2) - mp3 = MeshPatt(perm1, shad3) - - # Ensure some ordering of mesh patts (all should be comparable) - assert mesh1 > mesh2 or mesh1 <= mesh2 - assert mp1 < mp2 or mp1 >= mp2 - - # Describing lexicographical ordering of mesh patts: - # 1) order first by underlying patts - # 2) order second by shading [Python list ordering by smallest indices] - assert mesh3 < mesh1 < mesh2 # case 1) - assert mp1 < mp2 < mp3 # case 2) - - -def test_to_tikz(): - assert ( - mesh_pattern.to_tikz() - == "\\begin{tikzpicture}[scale=.3,baseline=(current bounding box.center)]\n" - "\t\\foreach \\x in {1,...,4} {\n" - "\t\t\\draw[ultra thin] (\\x,0)--(\\x,5); %vline\n" - "\t\t\\draw[ultra thin] (0,\\x)--(5,\\x); %hline\n" - "\t\n" - "\t}\n" - "\t\\fill[pattern color = black!75, pattern=north east lines] (0, 0) rectangle +(1,1);\n" # noqa: E501 - "\t\\fill[pattern color = black!75, pattern=north east lines] (0, 4) rectangle +(1,1);\n" # noqa: E501 - "\t\\fill[pattern color = black!75, pattern=north east lines] (2, 1) rectangle +(1,1);\n" # noqa: E501 - "\t\\fill[pattern color = black!75, pattern=north east lines] (2, 2) rectangle +(1,1);\n" # noqa: E501 - "\t\\fill[pattern color = black!75, pattern=north east lines] (3, 3) rectangle +(1,1);\n" # noqa: E501 - "\t\\fill[pattern color = black!75, pattern=north east lines] (4, 0) rectangle +(1,1);\n" # noqa: E501 - "\t\\fill[pattern color = black!75, pattern=north east lines] (4, 1) rectangle +(1,1);\n" # noqa: E501 - "\t\\fill[pattern color = black!75, pattern=north east lines] (4, 2) rectangle +(1,1);\n" # noqa: E501 - "\t\\draw[fill=black] (1,2) circle (5pt);\n" - "\t\\draw[fill=black] (2,4) circle (5pt);\n" - "\t\\draw[fill=black] (3,3) circle (5pt);\n" - "\t\\draw[fill=black] (4,1) circle (5pt);\n" - "\\end{tikzpicture}" - ) - - -def test_ascii_plot(): - assert ( - mesh_pattern.ascii_plot() - == "▒| | | |\n-+-●-+-+-\n | | |▒|\n-+-+-●-+-\n | |▒| |▒\n-●-+-+-+-\n" - " | |▒| |▒\n-+-+-+-●-\n▒| | | |▒" - ) - - -def test_gen_meshpatts(): - assert list(gen_meshpatts(0)) == [MeshPatt(), MeshPatt((), [(0, 0)])] - assert len(list(gen_meshpatts(1))) == 2 ** 4 - for i in range(2, 4): - patt = Perm.random(i) - len_i = list(gen_meshpatts(i, patt)) - assert (len(set(len_i))) == 2 ** ((i + 1) ** 2) - for mpatt in len_i: - assert mpatt.pattern == patt - len_i = list(gen_meshpatts(2)) - assert len(set(len_i)) == (2 ** ((2 + 1) ** 2) * factorial(2)) - patt = tuple(Perm.random(3)) - len_i = list(gen_meshpatts(3, patt)) - assert (len(set(len_i))) == 2 ** ((3 + 1) ** 2) diff --git a/tox.ini b/tox.ini index 4a890d43..12924e6c 100644 --- a/tox.ini +++ b/tox.ini @@ -22,7 +22,7 @@ basepython = pypy36: pypy3 deps = pytest==5.4.3 - pytest-timeout==1.4.1 + pytest-timeout==1.4.2 commands = pytest [pytest] @@ -36,10 +36,24 @@ basepython = {[default]basepython} skip_install = True deps = flake8==3.8.3 - flake8-isort==3.0.0 + flake8-isort==3.0.1 commands = flake8 --isort-show-traceback permuta tests setup.py +[testenv:pylint] +description = run pylint (static code analysis) +basepython = {[default]basepython} +deps = + pylint==2.5.3 +commands = pylint permuta + +[testenv:mypy] +description = run mypy (static type checker) +basepython = {[default]basepython} +deps = + mypy==0.782 +commands = mypy + [testenv:black] description = check that comply with autoformating basepython = {[default]basepython}