{ "info": { "author": "Permuta Triangle", "author_email": "permutatriangle@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Education", "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Education", "Topic :: Scientific/Engineering :: Mathematics" ], "description": "Combinatorial Specification Searcher\n====================================\n.. image:: https://travis-ci.org/PermutaTriangle/comb_spec_searcher.svg?branch=master\n :alt: Travis\n :target: https://travis-ci.org/PermutaTriangle/comb_spec_searcher\n.. image:: https://img.shields.io/coveralls/github/PermutaTriangle/comb_spec_searcher.svg\n :alt: Coveralls\n :target: https://coveralls.io/github/PermutaTriangle/comb_spec_searcher\n.. image:: https://img.shields.io/pypi/v/comb_spec_searcher.svg\n :alt: PyPI\n :target: https://pypi.python.org/pypi/comb_spec_searcher\n.. image:: https://img.shields.io/pypi/l/comb_spec_searcher.svg\n :target: https://pypi.python.org/pypi/comb_spec_searcher\n.. image:: https://img.shields.io/pypi/pyversions/comb_spec_searcher.svg\n :target: https://pypi.python.org/pypi/comb_spec_searcher\n\n.. image:: http://img.shields.io/badge/readme-tested-brightgreen.svg\n :alt: Travis\n :target: https://travis-ci.org/PermutaTriangle/comb_spec_searcher\n\n.. image:: https://requires.io/github/PermutaTriangle/comb_spec_searcher/requirements.svg?branch=master\n :target: https://requires.io/github/PermutaTriangle/comb_spec_searcher/requirements/?branch=master\n :alt: Requirements Status\n\nThe ``comb_spec_searcher`` package contains code for combinatorial\nexploration.\n\nInstalling\n----------\n\nTo install ``comb_spec_searcher`` on your system, run:\n\n.. code:: bash\n\n pip install comb_spec_searcher\n\nIt is also possible to install comb_spec_searcher in development mode to\nwork on the source code, in which case you run the following after\ncloning the repository:\n\n.. code:: bash\n\n ./setup.py develop\n\nCombinatorial exploration\n-------------------------\n\nA (combinatorial) class is a set of objects with a notion of size such\nthat there are finitely many objects of each size. One of the primary\ngoals of enumerative combinatorics is to count how many objects of each\nsize there are in a class. One method for doing this is to find a\n(combinatorial) specification, which is a collection of (combinatorial)\nrules that describe how to build a class from other classes using\nwell-defined constructors. Such a specification can then be used to\ncount the number of objects of each size.\n\n*Combinatorial exploration* is a systematic application of strategies to\ncreate rules about a class of interest, until a specification can be\nfound. This package can be used to perform this process automatically.\nSee the `Combinatorial Exploration\nproject `__\nand `Christian Bean\u2019s PhD\nthesis `__ for more details.\n\nThe remainder of this README will be an example of how to use this\npackage for performing combinatorial exploration on a specific class,\nnamely words avoiding consecutive patterns.\n\nAvoiding consecutive patterns in words\n--------------------------------------\n\nA word ``w`` over an alphabet ``\u03a3`` is a string consisting of letters\nfrom ``\u03a3``. We say that ``w`` contains the word ``p`` if there is a\nconsecutive sequence of letters in ``w`` equal to ``p``. We say ``w``\navoids ``p`` if it does not contain ``p``. In this context, we call\n``p`` a pattern. In ``python``, this containment check can be checked\nusing ``in``.\n\n.. code:: python\n\n >>> w = \"acbabcabbb\"\n >>> p = \"abcab\"\n >>> p in w\n True\n\nFor an alphabet ``\u03a3`` and a set of patterns ``P`` we define ``\u03a3*(P)`` to\nbe the set of words over ``\u03a3`` that avoid every pattern in ``P``. These\nare the classes that we will count. Of course, these all form regular\nlanguages, but it will serve as a good example of how to use the\n``comb_spec_searcher`` package.\n\nThe first step is to create the classes that will be used for\ndiscovering the underlying structure of the class of interest. In this\ncase, considering the prefix of the words is what we need. We then\ncreate a new python ``class`` representing this that inherits from\n``CombinatorialClass`` which can be imported from\n``comb_spec_searcher``.\n\n.. code:: python\n\n >>> from comb_spec_searcher import CombinatorialClass\n\n\n >>> class AvoidingWithPrefix(CombinatorialClass):\n ... def __init__(self, prefix, patterns, alphabet, just_prefix=False):\n ... self.alphabet = frozenset(alphabet)\n ... self.prefix = prefix\n ... self.patterns = frozenset(patterns)\n ... self.just_prefix = just_prefix # this will be needed later\n\nInheriting from ``CombinatorialClass`` requires you to implement a few\nfunctions for combinatorial exploration: ``is_empty``, ``to_jsonable``,\n``__eq__``, ``__hash__``, ``__repr__``, and ``__str__``.\n\nWe will start by implementing the dunder methods (the ones with double\nunderscores) required. The ``__eq__`` method is particularly important\nas the ``CombinatorialSpecificationSearcher`` will use it to recognise\nif the same class appears multiple times.\n\n.. code:: python\n\n ... # The dunder methods required to perform combinatorial exploration\n ...\n ... def __eq__(self, other):\n ... return (self.alphabet == other.alphabet and\n ... self.prefix == other.prefix and\n ... self.patterns == other.patterns and\n ... self.just_prefix == other.just_prefix)\n ...\n ... def __hash__(self):\n ... return hash(hash(self.prefix) + hash(self.patterns) +\n ... hash(self.alphabet) + hash(self.just_prefix))\n ...\n ... def __str__(self):\n ... if self.just_prefix:\n ... return \"The word {}\".format(self.prefix)\n ... return (\"Words over {{{}}} avoiding {{{}}} with prefix {}\"\n ... \"\".format(\", \".join(l for l in self.alphabet),\n ... \", \".join(p for p in self.patterns),\n ... self.prefix if self.prefix else '\"\"'))\n ...\n ... def __repr__(self):\n ... return \"AvoidingWithPrefix({}, {}, {}\".format(repr(self.prefix),\n ... repr(self.patterns),\n ... repr(self.alphabet))\n\nPerhaps the most important function to be implemented is the\n``is_empty`` function. This should return ``True`` if there are no\nobjects of any length in the class, otherwise ``False``. If it is not\ncorrectly implemented it may lead to tautological specifications. For\nexample, in our case the class is empty if and only if the prefix\ncontains a pattern to be avoided.\n\n.. code:: python\n\n ... def is_empty(self):\n ... return any(p in self.prefix for p in self.patterns)\n\nThe final function required is ``to_jsonable``. This is primarily for\nthe output, and only necessary for saving the output. It should be in a\nformat that can be interpretated by ``json``. What is important is that\nthe ``from_dict`` function is written in such a way that for any class\n``c`` we have ``CombinatorialClass.from_dict(c.to_jsonable()) == c``.\n\n.. code:: python\n\n ... def to_jsonable(self):\n ... return {\"prefix\": self.prefix,\n ... \"patterns\": tuple(sorted(self.patterns)),\n ... \"alphabet\": tuple(sorted(self.alphabet)),\n ... \"just_prefix\": int(self.just_prefix)}\n ...\n ... @classmethod\n ... def from_dict(cls, data):\n ... return cls(data['prefix'],\n ... data['patterns'],\n ... data['alphabet'],\n ... bool(int(data['just_prefix'])))\n\nWe also add some methods that we will need to get the enumerations of the\nobjects later.\n\n.. code:: python\n\n ... def is_epsilon(self):\n ... \"\"\" Returns True if the generating function is 1\"\"\"\n ... return self.prefix == \"\" and self.just_prefix\n ...\n ... def is_atom(self):\n ... \"\"\" Returns True if the generating function is x\"\"\"\n ... return len(self.prefix) == 1 and self.just_prefix\n ...\n ... def is_positive(self):\n ... \"\"\" Returns True if the constant term of the generating function is 0\"\"\"\n ... return len(self.prefix) > 0\n\nOur ``CombinatorialClass`` is now ready. What is left to do is create\nthe strategies that the ``CombinatorialSpecificationSearcher`` will use\nfor performing combinatorial exploration. This is given in the form of a\n``StrategyPack`` which can be imported from ``comb_spec_searcher`` that\nwe will populate in the remainder of this example.\n\n.. code:: python\n\n >>> from comb_spec_searcher import StrategyPack\n >>> pack = StrategyPack(initial_strats=[],\n ... inferral_strats=[],\n ... expansion_strats=[],\n ... ver_strats=[],\n ... name=(\"Finding specification for words avoiding \"\n ... \"consecutive patterns.\"))\n\nStrategies are functions that take as input a class ``C`` and produce\nrules about ``C``. The types of strategies are as follows: -\n``initial_strats``: yields rules for classes - ``inferral_strats``:\nreturns a single equivalence rule - ``expansion_strats``: yields rules\nfor classes - ``ver_strats``: returns a rule when the count of a class\nis known\n\nFor example, every word over the alphabet ``\u03a3`` starting with prefix\n``p`` is either just ``p`` or has prefix ``pa`` for some ``a`` in ``\u03a3``.\nThis rule is splitting the original into disjoint subsets. We call a\nrule using disjoint union a ``BatchRule``. Although in this case there\nis a unique rule created by the strategy, strategies are assumed to\ncreate multiple rules, and as such should be implemented as generators.\n\n.. code:: python\n\n >>> from comb_spec_searcher import BatchRule\n\n\n >>> def expansion(avoiding_with_prefix, **kwargs):\n ... if avoiding_with_prefix.just_prefix:\n ... return\n ... alphabet, prefix, patterns = (avoiding_with_prefix.alphabet,\n ... avoiding_with_prefix.prefix,\n ... avoiding_with_prefix.patterns)\n ... # either just p\n ... comb_classes = [AvoidingWithPrefix(prefix, patterns, alphabet, True)]\n ... for a in alphabet:\n ... # or has prefix pa for some a in \u03a3.\n ... ends_with_a = AvoidingWithPrefix(prefix + a, patterns, alphabet)\n ... comb_classes.append(ends_with_a)\n ... yield BatchRule((\"The next letter in the prefix is one of {{{}}}\"\n ... \"\".format(\", \".join(l for l in alphabet))),\n ... comb_classes)\n\nThe classes that we will verify are those that consist of just the\nprefix. To verify these we create a new strategy that returns a\n``VerificationRule`` when this is the case.\n\n.. code:: python\n\n >>> from comb_spec_searcher import VerificationRule\n\n\n >>> def only_prefix(avoiding_with_prefix, **kwargs):\n ... if avoiding_with_prefix.just_prefix:\n ... return VerificationRule((\"The set contains only the word {}\"\n ... \"\".format(avoiding_with_prefix.prefix)))\n\nThe final strategy we will need is one that peels off much as possible\nfrom the front of the prefix ``p`` such that the avoidance conditions\nare unaffected. This should then give a rule that is a cartesian product\nof the part that is peeled off together with the words whose prefix is\nthat of the remainder of the original prefix. We call rules whose\nconstructor is cartesian product a ``DecompositionRule``.\n\n.. code:: python\n\n >>> from comb_spec_searcher import DecompositionRule\n\n\n >>> def remove_front_of_prefix(avoiding_with_prefix, **kwargs):\n ... \"\"\"If the k is the maximum length of a pattern to be avoided, then any\n ... occurrence using indices further to the right of the prefix can use at\n ... most the last k - 1 letters in the prefix.\"\"\"\n ... if avoiding_with_prefix.just_prefix:\n ... return\n ... prefix, patterns, alphabet = (avoiding_with_prefix.prefix,\n ... avoiding_with_prefix.patterns,\n ... avoiding_with_prefix.alphabet)\n ... # safe will be the index of the prefix in which we can remove upto without\n ... # affecting the avoidance conditions\n ... safe = max(0, len(prefix) - max(len(p) for p in patterns) + 1)\n ... for i in range(safe, len(prefix)):\n ... end = prefix[i:]\n ... if any(end == patt[:len(end)] for patt in patterns):\n ... break\n ... safe = i + 1\n ... if safe > 0:\n ... start_prefix = prefix[:safe]\n ... end_prefix = prefix[safe:]\n ... start = AvoidingWithPrefix(start_prefix, patterns, alphabet, True)\n ... end = AvoidingWithPrefix(end_prefix, patterns, alphabet)\n ... yield DecompositionRule(\"Remove up to index {} of prefix\".format(safe),\n ... [start, end])\n\nWith these three strategies we are now ready to perform combinatorial\nexploration using the following pack.\n\n.. code:: python\n\n >>> pack = StrategyPack(initial_strats=[remove_front_of_prefix],\n ... inferral_strats=[],\n ... expansion_strats=[[expansion]],\n ... ver_strats=[only_prefix],\n ... name=(\"Finding specification for words avoiding \"\n ... \"consecutive patterns.\"))\n\nFirst we need to create the combinatorial class we want to count. For\nexample, consider the words over the alphabet ``{a, b}`` that avoid\n``ababa`` and ``babb``. This class can be created using our initialise\nfunction.\n\n.. code:: python\n\n >>> prefix = ''\n >>> patterns = ['ababa', 'babb']\n >>> alphabet = ['a', 'b']\n >>> start_class = AvoidingWithPrefix(prefix, patterns, alphabet)\n\nWe can then initialise our ``CombinatorialSpecificationSearcher``, and\nuse the ``auto_search`` function which will return a ``ProofTree``\nobject that represents a specification assuming one is found (which in\nthis case always will).\n\n.. code:: python\n\n >>> from comb_spec_searcher import CombinatorialSpecificationSearcher\n\n\n >>> searcher = CombinatorialSpecificationSearcher(start_class, pack)\n >>> tree = searcher.auto_search()\n\nNow that we have a ``ProofTree`` i.e., a specification, the obvious\nthing we want to do is find the generating function for the class that\ncounts the number of objects of each size. This can be done by using the\n``get_genf`` or ``get_min_poly`` methods on ``ProofTree``. To use these\nmethods we will need to go back and implement a few functions in our\n``CombinatorialClass``.\n\nWhen you verify a class, this tells the ``ProofTree`` class that it can\nget the generating function by calling the ``get_genf`` (and/or the\n``get_min_poly``) function on ``CombinatorialClass``. In our case, we\nverified exactly when the class was only the prefix, say ``p``. The\ngenerating function of this is clearly ``x**len(p)``. We add these\nmethods to our class.\n\n.. code:: python\n\n >>> from sympy import abc, var\n\n >>> def get_genf(self, **kwargs):\n ... \"\"\"Return the generating function when only a prefix.\"\"\"\n ... if self.just_prefix:\n ... if self.is_empty():\n ... return 0\n ... else:\n ... return abc.x**len(self.prefix)\n >>> AvoidingWithPrefix.get_genf = get_genf\n >>> def get_min_poly(self, *args, **kwargs):\n ... \"\"\"Return the minimum polynomial satisfied by the generating function\n ... of the combinatorial class (in terms of F).\"\"\"\n ... if self.just_prefix:\n ... if self.is_empty():\n ... return 0\n ... else:\n ... return var('F') - abc.x**len(self.prefix)\n >>> AvoidingWithPrefix.get_min_poly = get_min_poly\n\nFinally, in order to get initial terms, you will also need to implement\nthe ``objects_of_length`` function which should yield all of the objects\nof a given length in the class.\n\n.. code:: python\n\n >>> from itertools import product\n\n >>> def objects_of_length(self, length):\n ... \"\"\"Yield the words of given length that start with prefix and avoid the\n ... patterns. If just_prefix, then only yield that word.\"\"\"\n ... def possible_words():\n ... \"\"\"Yield all words of given length over the alphabet with prefix\"\"\"\n ... for letters in product(self.alphabet,\n ... repeat=length - len(self.prefix)):\n ... yield self.prefix + \"\".join(a for a in letters)\n ...\n ... if self.just_prefix:\n ... if length == len(self.prefix) and not self.is_empty():\n ... yield self.prefix\n ... return\n ... for word in possible_words():\n ... if all(patt not in word for patt in self.patterns):\n ... yield word\n >>> AvoidingWithPrefix.objects_of_length = objects_of_length\n\nWith these in place if we then call the ``get_min_poly`` function with\nthe flag ``solve=True``\n\n.. code:: python\n\n >>> tree.get_min_poly()\n F*x**6 + F*x**3 - F*x**2 + 2*F*x - F + x**7 + x**5 + x**4 + x**3 + x**2 + 1\n >>> tree.get_genf()\n -(x + 1)*(x**2 - x + 1)**2*(x**2 + x + 1)/(x**6 + x**3 - x**2 + 2*x - 1)\n\nwe see that the minimum polynomial satisfied by the generating function\n``F`` is\n``F*(x**6 + x**3 - x**2 + 2*x - 1) + x**7 + x**5 + x**4 + x**3 + x**2 + 1``\nand moreover\n``F = -(x**7 + x**5 + x**4 + x**3 + x**2 + 1)/(x**6 + x**3 - x**2 + 2*x - 1)``.\n\nMoreover, we can get directly the number of objects by length with the method\n`count_objects_of_length`.\n\n.. code:: python\n\n >>> [tree.count_objects_of_length(i) for i in range(11)]\n [1, 2, 4, 8, 15, 27, 48, 87, 157, 283, 511]\n\nYou can now try this yourself using the file ``example.py``, which can\ncount any set of words avoiding consecutive patterns.", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/PermutaTriangle/comb_spec_searcher", "keywords": "enumerative combinatorics combinatorial specification counting", "license": "BSD-3", "maintainer": "", "maintainer_email": "", "name": "comb-spec-searcher", "package_url": "https://pypi.org/project/comb-spec-searcher/", "platform": "", "project_url": "https://pypi.org/project/comb-spec-searcher/", "project_urls": { "Homepage": "https://github.com/PermutaTriangle/comb_spec_searcher", "Source": "https://github.com/PermutaTriangle/comb_spec_searcher", "Tracker": "https://github.com/PermutaTriangle/comb_spec_searcher/issues" }, "release_url": "https://pypi.org/project/comb-spec-searcher/0.2.2/", "requires_dist": null, "requires_python": ">=3.5", "summary": "A library for performing combinatorial exploration.", "version": "0.2.2" }, "last_serial": 5792694, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "579c732385da80b3ecfa898972cdb6be", "sha256": "f8b8463d2155e013940185feb2ed0471a73b5ca623cef52de9e3cf2fc3a371c6" }, "downloads": -1, "filename": "comb_spec_searcher-0.1.0.tar.gz", "has_sig": false, "md5_digest": "579c732385da80b3ecfa898972cdb6be", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 37207, "upload_time": "2019-05-28T14:20:40", "url": "https://files.pythonhosted.org/packages/5a/b0/97e8ed3ac923abca077221f7b4ca8529de6ed2d304f0cef9238197f4de83/comb_spec_searcher-0.1.0.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "77a4d470cf0a42a04211b26124d188ae", "sha256": "eb1b63d2c813e090afaad13a96ebac86436c4e74df197e03e5c0d3e1e8dbf4fe" }, "downloads": -1, "filename": "comb_spec_searcher-0.2.0.tar.gz", "has_sig": false, "md5_digest": "77a4d470cf0a42a04211b26124d188ae", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 39398, "upload_time": "2019-08-26T15:08:48", "url": "https://files.pythonhosted.org/packages/69/09/8fbf254b3cd4fb2a4c1c031a3ed0193089f412c5045aa0ac0ccbfe235066/comb_spec_searcher-0.2.0.tar.gz" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "9d3655476280ce8df57f948679a4750c", "sha256": "59c1115b7a1e299df18859880acbe309837d31f2dfc79c2c9cde1e383d5cddb7" }, "downloads": -1, "filename": "comb_spec_searcher-0.2.1.tar.gz", "has_sig": false, "md5_digest": "9d3655476280ce8df57f948679a4750c", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 39396, "upload_time": "2019-08-26T17:01:43", "url": "https://files.pythonhosted.org/packages/79/d9/a52d9198702efde2ddd0217973485966602f6e582d4dc4f911a4d4f7cd5e/comb_spec_searcher-0.2.1.tar.gz" } ], "0.2.2": [ { "comment_text": "", "digests": { "md5": "5e8550277aec1c1d08e3dc38ce684210", "sha256": "62138211bcb17b760296b091c22de1e599abdbc1ee5dc516a4eba9636e4b89e0" }, "downloads": -1, "filename": "comb_spec_searcher-0.2.2.tar.gz", "has_sig": false, "md5_digest": "5e8550277aec1c1d08e3dc38ce684210", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 41268, "upload_time": "2019-09-06T15:02:27", "url": "https://files.pythonhosted.org/packages/7c/df/df26af38316cb5837a24d192575dc07b79c96a0999efb1e143f6a74ebe21/comb_spec_searcher-0.2.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "5e8550277aec1c1d08e3dc38ce684210", "sha256": "62138211bcb17b760296b091c22de1e599abdbc1ee5dc516a4eba9636e4b89e0" }, "downloads": -1, "filename": "comb_spec_searcher-0.2.2.tar.gz", "has_sig": false, "md5_digest": "5e8550277aec1c1d08e3dc38ce684210", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 41268, "upload_time": "2019-09-06T15:02:27", "url": "https://files.pythonhosted.org/packages/7c/df/df26af38316cb5837a24d192575dc07b79c96a0999efb1e143f6a74ebe21/comb_spec_searcher-0.2.2.tar.gz" } ] }