Package: tilings
TilingsThetilingsPython library contains code for working with gridded
permutations and tilings, and in particular theTileScopealgorithm, which
can be used to enumerate permutation classes.If you are primarily interested in enumerating permutation classes, then you
may wish to skip ahead to theTileScopesection, but note the installation
will be the same as fortilings.If this code is useful to you in your work, please consider citing it. To generate a
BibTeX entry (or another format), click the “DOI” badge above and locate the “Cite As”
section.If you need support, have a suggestion, or just want to be up to date with the
latest developments please join us on ourDiscord serverwhere we’d be happy to hear
from you! To receive an email notification about major new releases,
send an email topermutatriangle@gmail.com(but please direct all requests for assistance to theDiscord server).InstallingTo installtilingson your system, run:pipinstalltilingsIf you would like to edit the source code, you should installtilingsin
development mode by cloning the repository, running./setup.pydevelopTo verify that your installation is correct, you can try to get a specification forAv(12)by running in your terminal:tilescopespec12point_placementsYou should then be all set up to usetilingsand theTileScopealgorithm! The
“Performance” section at the end of this document provides some more technical
information.What are gridded permutations and tilings?We will be brief in our definitions here, for more details seeChristian Bean’s PhD thesis.Agridded permutationis a pair(π, P)whereπis a
permutation andPis a tuple of cells, called the positions, that
denote the cells in which the points ofπare drawn on a grid. LetGdenote the set of all gridded permutations. Containment of gridded
permutations is defined the same as containment of permutations, except
including the preservation of the cells.For example,(284376915, ((0, 0), (0, 3), (1, 1), (1, 1), (2, 3), (2, 2), (3, 4), (3, 0), (4, 2))is drawn on the grid as follows.+--+--+--+--+-+||||●||+--+--+--+--+-+|●||||||||●|||+--+--+--+--+-+|||●||||||||●|+--+--+--+--+-+||●||||||●||||+--+--+--+--+-+|●|||||||||●||+--+--+--+--+-+Atilingis a tripleT = ((n, m), O, R), wherenandmare positive integers,Ois a set of gridded permutations calledobstructions, andRis a set of sets of gridded permutations
calledrequirements.We say a gridded permutations avoids a set of gridded permutations if it
avoids all of the permutations in the set, otherwise it contains the
set. To contain a set, therefore, means to contain at least one in the
set. The set of gridded permutations on a tilingGrid(T)is the set
of all gridded permutations in then x mgrid that avoidOand
contain each setrinR.Using tilingsOnce you’ve installedtilings, it can be imported by a Python script
or an interactive Python session, just like any other Python library:>>>fromtilingsimport*Importing*from it supplies you with theGriddedPermandTilingclasses.As above, a gridded permutation is a pair(π, P)whereπis a
permutation andPis a tuple of cells. The permutation is assumed to
be aPermfrom thepermutaPython library. Not every tuple of
cells is a valid position for a given permutation. This can be checked
using thecontradictorymethod.>>>frompermutaimportPerm>>>gp=GriddedPerm(Perm((0,2,1)),((0,0),(0,0),(1,0)))>>>gp.contradictory()False>>>gp=GriddedPerm(Perm((0,1,2)),((0,0),(0,1),(0,0)))>>>gp.contradictory()TrueATilingis created with an iterable of obstructions and an
iterable of requirements (and each requirement is an iterable of gridded permutations).
It is assumed that all cells not mentioned in some obstruction or
requirement are empty. You can print the tiling to get an overview of the
tiling created. In this example, we have a tiling that corresponds to
non-empty permutations avoiding123.>>>obstructions=[GriddedPerm.single_cell(Perm((0,1)),(1,1)),...GriddedPerm.single_cell(Perm((1,0)),(1,1)),...GriddedPerm.single_cell(Perm((0,1)),(0,0)),...GriddedPerm.single_cell(Perm((0,1,2)),(2,0)),...GriddedPerm(Perm((0,1,2)),((0,0),(2,0),(2,0)))]>>>requirements=[[GriddedPerm.single_cell(Perm((0,)),(1,1))]]>>>tiling=Tiling(obstructions,requirements)>>>print(tiling)+-+-+-+||●||+-+-+-+|\||1|+-+-+-+1:Av(012)\:Av(01)●:pointCrossingobstructions:012:(0,0),(2,0),(2,0)Requirement0:0:(1,1)There are several properties ofTilingthat you can use, e.g.,>>>tiling.dimensions(3,2)>>>sorted(tiling.active_cells)[(0,0),(1,1),(2,0)]>>>tiling.point_cellsfrozenset({(1,1)})>>>sorted(tiling.possibly_empty)[(0,0),(2,0)]>>>tiling.positive_cellsfrozenset({(1,1)})Those who have read ahead, or already started using tilings may have noticed
that aTilingcan also be defined with a third argument calledassumptions.
These can be used to keep track of occurrences of gridded permutations on
tilings. These are still in development but are essential for certain
parts of theTileScopealgorithm. For simplicity we will not discuss
these again until theFusionsection.There are a number of methods available on the tiling. You can generate
the gridded permutations satisfying the obstructions and requirements
using thegridded_perms_of_lengthmethod.>>>foriinrange(4):...forgpinsorted(tiling.gridded_perms_of_length(i)):...print(gp)0:(1,1)01:(0,0),(1,1)10:(1,1),(2,0)021:(0,0),(1,1),(2,0)102:(0,0),(0,0),(1,1)120:(0,0),(1,1),(2,0)201:(1,1),(2,0),(2,0)210:(1,1),(2,0),(2,0)There are numerous other methods and properties. Many of these are specific
to theTileScopealgorithm, discussed inChristian Bean’s PhD
thesis. For the remainder
of this readme we will focus on theTileScopealgorithm.The TileScope algorithmUsing TileScopeIf you’ve not installedtilingsyet then go ahead and do this first by
pip installingtilings:pipinstalltilingsOnce done you can use theTileScopealgorithm in two ways, either directly
by importing from thetilings.tilescopemodule which we will discuss in
greater detail shortly, or by using theTileScopecommand line tool.The command line toolFirst, check the help commands for more information about its usage.tilescope-htilescopespec-hTo search for a combinatorial specification use the subcommandtilescope spec, e.g.tilescopespec231point_placementsBy default this command will try to solve for the generating function,
although in some cases you will come across some not-yet-implemented features;
for more information please join us on ourDiscord server, where we’d be happy to talk
about it!Thepoint_placementsargument above is a strategy pack, which we explain in
more detail in theStrategyPackssection.The tilescope moduleTileScope can be imported in a interactive Python session fromtilings.tilescope.>>>fromtilings.tilescopeimport*Importing*fromtilings.tilescopesupplies you with theTileScopeandTileScopePackclasses. Running theTileScopeis as simple as
choosing a class and a strategy pack. We’ll go into more detail about the
different strategies
available shortly, but first let’s enumerate our first permutation class. The
example one always learns first in permutation patterns is enumerating
Av(231). There are many different packs that will succeed for this class,
but to get the most commonly described decomposition we can usepoint_placements. The basis can be given to TileScope in several
formats: an iterable of permuta.Perm, a string where the permutations
are separated by'_'(e.g.'231_4321'), or as aTiling.>>>pack=TileScopePack.point_placements()>>>tilescope=TileScope('231',pack)Once we have created ourTileScopewe can then use theauto_searchmethod which will search for a specification using the strategies given.
If successful it will return a CombinatorialSpecification.TileScopeuseslogzero.loggerto report information. If you wish to
suppress these prints, you can setlogzero.loglevel, which we have
done here for sake of brevity in this readme!>>>importlogzero;importlogging;logzero.loglevel(logging.CRITICAL)>>>spec=tilescope.auto_search()>>>print(spec)Acombinatorialspecificationwith4rules.-----------0->(1,2)insert0incell(0,0)+-++-++-+|1|=||+|1|+-++-++-+1:Av(120)1:Av+(120)Requirement0:0:(0,0)-------1->()isatom+-+||+-+<BLANKLINE>-----2=3placingthetopmostpointincell(0,0),thenrowandcolumnseparation+-++-+-+-++-+-+-+|1|=||●||=||●||+-++-+-+-++-+-+-+1:Av+(120)|1||1||||1|Requirement0:+-+-+-++-+-+-+0:(0,0)1:Av(120)|1|||●:point+-+-+-+Crossingobstructions:1:Av(120)10:(0,0),(2,0)●:pointRequirement0:Requirement0:0:(1,1)0:(1,2)---------3->(0,)tilingislocallyfactorable+-+-+-++-+||●||↝|1|+-+-+-++-+|||1|1:Av(120)+-+-+-+|1|||+-+-+-+1:Av(120)●:pointRequirement0:0:(1,2)The locally factorable tiling in the rule3 -> (0,)could be further expanded
down to atoms and the root tiling.
This can be done using theexpand_verifiedmethod.>>>spec=spec.expand_verified()>>>print(spec)Acombinatorialspecificationwith5rules.-----------0->(1,2)insert0incell(0,0)+-++-++-+|1|=||+|1|+-++-++-+1:Av(120)1:Av+(120)Requirement0:0:(0,0)-------1->()isatom+-+||+-+<BLANKLINE>-----2=3placingthetopmostpointincell(0,0),thenrowandcolumnseparation+-++-+-+-++-+-+-+|1|=||●||=||●||+-++-+-+-++-+-+-+1:Av+(120)|1||1||||1|Requirement0:+-+-+-++-+-+-+0:(0,0)1:Av(120)|1|||●:point+-+-+-+Crossingobstructions:1:Av(120)10:(0,0),(2,0)●:pointRequirement0:Requirement0:0:(1,1)0:(1,2)--------------3->(0,4,0)factorwithpartition{(0,0)}/{(1,2)}/{(2,1)}+-+-+-++-++-++-+||●||=|1|x|●|x|1|+-+-+-++-++-++-+|||1|1:Av(120)●:point1:Av(120)+-+-+-+Requirement0:|1|||0:(0,0)+-+-+-+1:Av(120)●:pointRequirement0:0:(1,2)-------4->()isatom+-+|●|+-+●:pointRequirement0:0:(0,0)Now that we have a specification we can do a number of things. For example,
counting how many permutations there are in the class. This can be done using
thecount_objects_of_sizemethod on the CombinatorialSpecification.>>>[spec.count_objects_of_size(i)foriinrange(10)][1,1,2,5,14,42,132,429,1430,4862]Of course we see the Catalan numbers! We can also sample uniformly using therandom_sample_object_of_sizemethod. This will return aGriddedPerm.
We have used theascii_plotmethod for us to visualise it.
If you want the underlyingPerm, this can be accessed with thepattattribute. We also highlighted here thepermuta.Perm.ascii_plotmethod for
an alternative visualisation.>>>gp=spec.random_sample_object_of_size(10)>>>print(gp)# doctest: +SKIP9543102768:(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0)>>>print(gp.ascii_plot())# doctest: +SKIP+----------+|●||●||●||●||●||●||●||●||●||●|+----------+>>>perm=gp.patt>>>print(perm)# doctest: +SKIP9543102768>>>print(perm.ascii_plot())# doctest: +SKIP||||||||||-●-+-+-+-+-+-+-+-+-+-||||||||||-+-+-+-+-+-+-+-+-+-●-||||||||||-+-+-+-+-+-+-+-●-+-+-||||||||||-+-+-+-+-+-+-+-+-●-+-||||||||||-+-●-+-+-+-+-+-+-+-+-||||||||||-+-+-●-+-+-+-+-+-+-+-||||||||||-+-+-+-●-+-+-+-+-+-+-||||||||||-+-+-+-+-+-+-●-+-+-+-||||||||||-+-+-+-+-●-+-+-+-+-+-||||||||||-+-+-+-+-+-●-+-+-+-+-||||||||||You can use theget_equationsmethod which returns an iterator for the
system of equations implied by the specification.>>>list(spec.get_equations())[Eq(F_0(x),F_1(x)+F_2(x)),Eq(F_1(x),1),Eq(F_2(x),F_3(x)),Eq(F_3(x),F_0(x)**2*F_4(x)),Eq(F_4(x),x)]You can also pass these directly to thesolvemethod insympyby using theget_genfmethod. It will then return the solution which matches the initial
conditions.>>>spec.get_genf()(1-sqrt(1-4*x))/(2*x)Thesympy.solvemethod can be very slow, particularly on big systems. If
you are having troubles, then other softwares such as Mathematica and Maple are
often better. You can also use the methodget_maple_equationswhich will
return a string containing Maple code for the equations.>>>print(spec.get_maple_equations())root_func:=F[0,x]:eqs:=[F[0,x]=(F[1,x]+F[2,x]),F[1,x]=(1),F[2,x]=F[3,x],F[3,x]=((F[0,x]**(2))*F[4,x]),F[4,x]=x]:count:=[1,1,2,5,14,42,132]:If you have a system of equations you are unable to solve, then please feel
free to send them to ourDiscord server.A specification can be saved and loaded later by converting it toJSON, a data storage format
that can be written to a file or copy-pasted elsewhere for safe keeping.
This functionality is built intoTileScope. We can retrieve the JSON
representation of a specification and load the specificiation from said
JSON string by doing the following:>>>importjson>>>fromcomb_spec_searcherimportCombinatorialSpecification>>>json_string=json.dumps(spec.to_jsonable())>>>reloaded_spec=CombinatorialSpecification.from_dict(json.loads(json_string))StrategyPacksWe have implemented a large number of structural decomposition strategies that
we will discuss a bit more in the strategies section that follows. One can use
any subset of these strategies to search for a combinatorial specification.
This can be done by creating aTileScopePack.We have prepared a number of curated packs of strategies that we find to be
rather effective. These can accessed as class methods onTileScopePack.
They are:point_placements: checks if cells are empty or not and places extreme
points in cellsrow_and_col_placements: places the left or rightmost points in columns,
or the bottom or topmost points in rowsregular_insertion_encoding: this pack includes the strategies required
for finding the specification corresponding to a regular insertion encodinginsertion_row_and_col_placements: this pack places rows and columns as
above, but first ensures every active cell contains a point (this is in the
same vein as the “slots” of the regular insertion encoding)insertion_point_placements: places extreme points in cells, but first
ensures every active cell contains a pointpattern_placements: inserts size one requirements into a tiling, and then
places points with respect to a pattern, e.g. if your permutation contains 123,
then place the leftmost point that acts as a 2 in an occurrence of 123requirement_placements: places points with respect to any requirement,
e.g. if your permutation contains {12, 21}, then place the rightmost point
that is either an occurrence of 1 in 12 or an occurrence of 2 in 21.only_root_placements: this is the same aspattern_placementsexcept
we only allow inserting into 1x1 tilings, therefore making it a finite packall_the_strategies: a pack containing (almost) all of the strategiesEach of these packs have different parameters that can be set. You can view
this by using the help command e.g.,help(TileScopePack.pattern_placements).
If you need help picking the right pack to enumerate your class join us on ourDiscord serverwhere we’d be happy to help.You can make any pack use the fusion strategy by using the methodmake_fusion; for example, here is how to create the packrow_placements_fusion.>>>pack=TileScopePack.row_and_col_placements(row_only=True).make_fusion()>>>print(pack)Lookingforrecursivecombinatorialspecificationwiththestrategies:Inferral:rowandcolumnseparation,obstructiontransitivityInitial:rearrangeassumptions,addassumptions,factor,trackedfusionVerification:verifyatoms,insertionencodingverified,onebyoneverification,locallyfactorableverificationSet1:rowplacementThis particular pack can be used to enumerateAv(123).>>>tilescope=TileScope('123',pack)>>>spec=tilescope.auto_search(smallest=True)>>>print(spec)# doctest: +SKIPAcombinatorialspecificationwith10rules.-----------0->(1,2)insert0incell(0,0)+-++-++-+|1|=||+|1|+-++-++-+1:Av(012)1:Av+(012)Requirement0:0:(0,0)-------1->()isatom+-+||+-+-----------3->(4,5)factorwithpartition{(0,0),(0,2)}/{(1,1)}+-+-++-++-+|1||=|1|x|●|+-+-++-++-+||●||\|●:point+-+-++-+Requirement0:|\||1:Av(012)0:(0,0)+-+-+\:Av(01)1:Av(012)Crossingobstructions:\:Av(01)012:(0,0),(0,1),(0,1)●:pointCrossingobstructions:012:(0,0),(0,2),(0,2)Requirement0:0:(1,1)---------3->(5,)addingtheassumption'can count points in cell (0, 0)'+-+-++-+-+|\|1|↣|\|1|+-+-++-+-+1:Av(012)1:Av(012)\:Av(01)\:Av(01)Crossingobstructions:Crossingobstructions:012:(0,0),(1,0),(1,0)012:(0,0),(1,0),(1,0)Assumption0:cancountpointsincell(0,0)--------------5->(1,6,7)placingthetopmostpointinrow0+-+-++-++-+-+-++-+-+-+-+|\|1|=||+|●|||+|||●||+-+-++-++-+-+-++-+-+-+-+1:Av(012)||\|1||\|\||1|\:Av(01)+-+-+-++-+-+-+-+Crossingobstructions:1:Av(012)1:Av(012)012:(0,0),(1,0),(1,0)\:Av(01)\:Av(01)Assumption0:●:point●:pointcancountpointsincell(0,0)Crossingobstructions:Crossingobstructions:012:(1,0),(2,0),(2,0)01:(0,0),(1,0)Requirement0:012:(0,0),(3,0),(3,0)0:(0,1)012:(1,0),(3,0),(3,0)Assumption0:Requirement0:cancountpointsincells(0,1),(1,0)0:(2,1)Assumption0:cancountpointsincell(0,0)-----------6->(8,5)factorwithpartition{(0,1)}/{(1,0),(2,0)}+-+-+-++-++-+-+|●|||=|●|x|\|1|+-+-+-++-++-+-+||\|1|●:point1:Av(012)+-+-+-+Requirement0:\:Av(01)1:Av(012)0:(0,0)Crossingobstructions:\:Av(01)Assumption0:012:(0,0),(1,0),(1,0)●:pointcancountpointsincell(0,0)Assumption0:Crossingobstructions:cancountpointsincell(0,0)012:(1,0),(2,0),(2,0)Requirement0:0:(0,1)Assumption0:cancountpointsincells(0,1),(1,0)-------8->()isatom+-+|●|+-+●:pointRequirement0:0:(0,0)Assumption0:cancountpointsincell(0,0)-----------7->(9,4)factorwithpartition{(0,0),(1,0),(3,0)}/{(2,1)}+-+-+-+-++-+-+-++-+|||●||=|\|\|1|x|●|+-+-+-+-++-+-+-++-+|\|\||1|1:Av(012)●:point+-+-+-+-+\:Av(01)Requirement0:1:Av(012)Crossingobstructions:0:(0,0)\:Av(01)01:(0,0),(1,0)●:point012:(0,0),(2,0),(2,0)Crossingobstructions:012:(1,0),(2,0),(2,0)01:(0,0),(1,0)Assumption0:012:(0,0),(3,0),(3,0)cancountpointsincell(0,0)012:(1,0),(3,0),(3,0)Requirement0:0:(2,1)Assumption0:cancountpointsincell(0,0)---------9->(5,)fusecolumns0and1+-+-+-++-+-+|\|\|1|↣|\|1|+-+-+-++-+-+1:Av(012)1:Av(012)\:Av(01)\:Av(01)Crossingobstructions:Crossingobstructions:01:(0,0),(1,0)012:(0,0),(1,0),(1,0)012:(0,0),(2,0),(2,0)Assumption0:012:(1,0),(2,0),(2,0)cancountpointsincell(0,0)Assumption0:cancountpointsincell(0,0)-------4->()isatom+-+|●|+-+●:pointRequirement0:0:(0,0)>>>[spec.count_objects_of_size(i)foriinrange(10)][1,1,2,5,14,42,132,429,1430,4862]It is possible to make your own pack as well, but for that you should first
learn more about what the individual strategies do.The strategiesTheTileScopealgorithm has in essence six different strategies that are
applied in many different ways, resulting in very different universes in which
to search for a combinatorial specification in. They are:requirement insertions: a disjoint union considering whether or not a tiling
contains a requirementpoint placements: places a uniquely defined point onto its own row and/or
columnfactor: when the obstructions and requirements become local to a set of
cells, we factor out the local subtilingrow and column separation: if all of the points in a cell in a row must
appear below all of the other points in the row, then separate this onto its own
row.obstruction inferral: add obstructions that the requirements and
obstructions of a tiling imply must be avoidedfusion: merge two adjacent rows or columns of a tiling, if it can be
viewed as a single row or column with a line drawn betweenRequirement insertionsThe simplest of all the arguments when enumerating permutation classes is to
say, either a tiling is empty or contains a point. This can be viewed in
tilings as either avoiding1: (0, 0)or containing1: (0, 0).>>>fromtilings.strategiesimportCellInsertionFactory>>>strategy_generator=CellInsertionFactory()>>>tiling=Tiling.from_string('231')>>>forstrategyinstrategy_generator(tiling):...print(strategy(tiling))insert0incell(0,0)+-++-++-+|1|=||+|1|+-++-++-+1:Av(120)1:Av+(120)Requirement0:0:(0,0)The same underlying principle corresponds to avoiding or containing any set of
gridded permutations. There are many different variations of this strategy
used throughout ourStrategyPacks.>>>importtilings>>>print(tilings.strategies.requirement_insertion.__all__)['CellInsertionFactory','RootInsertionFactory','RequirementExtensionFactory','RequirementInsertionFactory','FactorInsertionFactory','RequirementCorroborationFactory']Point placementsThe core idea of this strategy is to place a uniquely defined point onto
its own row and/or column. For example, here is a code snippet that
shows the rules coming from placing the extreme (rightmost, topmost, leftmost,
bottommost) points of a non-empty permutation avoiding231.>>>fromtilings.strategiesimportPatternPlacementFactory>>>strategy=PatternPlacementFactory()>>>tiling=Tiling.from_string('231').insert_cell((0,0))>>>forruleinstrategy(tiling):...print(rule)placingtherightmostpointincell(0,0)+-++-+-+|1|=|\||+-++-+-+1:Av+(120)||●|Requirement0:+-+-+0:(0,0)|1||+-+-+1:Av(120)\:Av(01)●:pointCrossingobstructions:120:(0,0),(0,2),(0,0)Requirement0:0:(1,1)placingthetopmostpointincell(0,0)+-++-+-+-+|1|=||●||+-++-+-+-+1:Av+(120)|1||1|Requirement0:+-+-+-+0:(0,0)1:Av(120)●:pointCrossingobstructions:10:(0,0),(2,0)Requirement0:0:(1,1)placingtheleftmostpointincell(0,0)+-++-+-+|1|=||1|+-++-+-+1:Av+(120)|●||Requirement0:+-+-+0:(0,0)||1|+-+-+1:Av(120)●:pointCrossingobstructions:10:(1,2),(1,0)Requirement0:0:(0,1)placingthebottommostpointincell(0,0)+-++-+-+-+|1|=|\||1|+-++-+-+-+1:Av+(120)||●||Requirement0:+-+-+-+0:(0,0)1:Av(120)\:Av(01)●:pointCrossingobstructions:120:(0,1),(2,1),(2,1)Requirement0:0:(1,0)Other algorithms used for automatically enumerating permutation classes have
used variations of point placements. For example, enumeration schemes and the
insertion encoding essentially consider placing the bottommost point into the
row of a tiling. Here is a code snippet for calling a strategy that places
points into a row of a tiling.>>>frompermuta.miscimportDIR_SOUTH>>>fromtilings.strategiesimportRowAndColumnPlacementFactory>>>strategy=RowAndColumnPlacementFactory(place_row=True,place_col=False)>>>placed_tiling=tiling.place_point_in_cell((0,0),DIR_SOUTH)>>>forruleinstrategy(placed_tiling):...print(rule)placingthetopmostpointinrow1+-+-+-++-++-+-+-+-++-+-+-+-+-+|\||1|=|●|+|●||||+||||●||+-+-+-++-++-+-+-+-++-+-+-+-+-+||●||●:point||\||1||\||1||1|+-+-+-+Requirement0:+-+-+-+-++-+-+-+-+-+1:Av(120)0:(0,0)|||●||||●||||\:Av(01)+-+-+-+-++-+-+-+-+-+●:point1:Av(120)1:Av(120)Crossingobstructions:\:Av(01)\:Av(01)120:(0,1),(2,1),(2,1)●:point●:pointRequirement0:Crossingobstructions:Crossingobstructions:0:(1,0)120:(1,1),(3,1),(3,1)10:(0,1),(4,1)Requirement0:10:(2,1),(4,1)0:(0,2)120:(0,1),(2,1),(2,1)Requirement1:Requirement0:0:(2,0)0:(1,0)Requirement1:0:(3,2)placingthebottommostpointinrow1+-+-+-++-++-+-+-+-++-+-+-+-+-+|\||1|=|●|+|\|||1|+|\||\||1|+-+-+-++-++-+-+-+-++-+-+-+-+-+||●||●:point||●|||||||●||+-+-+-+Requirement0:+-+-+-+-++-+-+-+-+-+1:Av(120)0:(0,0)|||●||||●||||\:Av(01)+-+-+-+-++-+-+-+-+-+●:point1:Av(120)1:Av(120)Crossingobstructions:\:Av(01)\:Av(01)120:(0,1),(2,1),(2,1)●:point●:pointRequirement0:Crossingobstructions:Crossingobstructions:0:(1,0)120:(0,2),(3,2),(3,2)01:(0,2),(2,2)Requirement0:120:(0,2),(4,2),(4,2)0:(1,1)120:(2,2),(4,2),(4,2)Requirement1:Requirement0:0:(2,0)0:(1,0)Requirement1:0:(3,1)Row and column separationEvery non-empty permutation inAv(231)can be written in the form αnβ whereα,βare permutation avoiding231, and all of the values inαare below all of the values inβ. The tiling representing placing the
topmost point inAv(231)contains a crossing size 2 obstruction10: (0, 0), (2, 0). This obstruction precisely says that the points in the
cell(0, 0)must appear below the points in the cell(2, 0). TheRowColumnSeparationStrategywill try to separate the rows and columns as
much as possible according to the size two crossing obstructions.>>>frompermuta.miscimportDIR_NORTH>>>fromtilings.strategiesimportRowColumnSeparationStrategy>>>strategy=RowColumnSeparationStrategy()>>>placed_tiling=tiling.place_point_in_cell((0,0),DIR_NORTH)>>>rule=strategy(placed_tiling)>>>print(rule)rowandcolumnseparation+-+-+-++-+-+-+||●||=||●||+-+-+-++-+-+-+|1||1||||1|+-+-+-++-+-+-+1:Av(120)|1|||●:point+-+-+-+Crossingobstructions:1:Av(120)10:(0,0),(2,0)●:pointRequirement0:Requirement0:0:(1,1)0:(1,2)FactorIf there are no crossing obstructions between two cellsaandbon a
tiling then the choice of points inaare independent from the choice
of points inb.>>>separated_tiling=rule.children[0]>>>fromtilings.strategiesimportFactorFactory>>>strategy_generator=FactorFactory()>>>forstrategyinstrategy_generator(separated_tiling):...print(strategy(separated_tiling))factorwithpartition{(0,0)}/{(1,2)}/{(2,1)}+-+-+-++-++-++-+||●||=|1|x|●|x|1|+-+-+-++-++-++-+|||1|1:Av(120)●:point1:Av(120)+-+-+-+Requirement0:|1|||0:(0,0)+-+-+-+1:Av(120)●:pointRequirement0:0:(1,2)Thexin the printed above rule is used to denote Cartesian product.
We do this to signify that there is a size-preserving bijection between the
gridded permutations on the left-hand side, to the set of 3-tuples coming from
the Cartesian product on the right-hand side, where the size of a tuple is the
sum of the sizes of the parts. In particular, it implies that the enumeration
of the gridded permutations on the left-hand side can be computed by applying the
Cauchy product to the enumerations of the three sets of gridded permutations on
the right-hand side.To guarantee that these rules are always counted using the Cauchy product
we must also ensure any two cells on the same row or column are also contained
in the same factor, otherwise when counting the left-hand side we have to
consider the possible interleavings going on.>>>tiling=Tiling.from_string('231_132').insert_cell((0,0))>>>placed_tiling=tiling.place_point_in_cell((0,0),DIR_SOUTH)>>>strategy_generator=FactorFactory()>>>forstrategyinstrategy_generator(placed_tiling):...print(strategy(placed_tiling))factorwithpartition{(0,1),(2,1)}/{(1,0)}+-+-+-++-+-++-+|\||/|=|\|/|x|●|+-+-+-++-+-++-+||●||/:Av(10)●:point+-+-+-+\:Av(01)Requirement0:/:Av(10)0:(0,0)\:Av(01)●:pointRequirement0:0:(1,0)Using the settingallinFactorFactorywill allow us to factor
according to only the obstructions and requirements.>>>strategy_generator=FactorFactory('all')>>>forstrategyinstrategy_generator(placed_tiling):...print(strategy(placed_tiling))interleavingfactorwithpartition{(0,1)}/{(1,0)}/{(2,1)}+-+-+-++-++-++-+|\||/|=|\|*|●|*|/|+-+-+-++-++-++-+||●||\:Av(01)●:point/:Av(10)+-+-+-+Requirement0:/:Av(10)0:(0,0)\:Av(01)●:pointRequirement0:0:(1,0)We instead use the symbol*to make us aware that this is not counted
by the Cauchy product, but we must also count the possible interleavings.Obstruction inferralThe presence of requirements alongside the obstructions on a tiling can
sometimes be used to imply that all of the gridded permutations on a tiling also avoid
some additional obstruction. The goal ofObstructionInferralis to add these to
a tiling.>>>frompermuta.miscimportDIR_NORTH>>>tiling=Tiling.from_string('1234_1243_1423_4123')>>>placed_tiling=tiling.partial_place_point_in_cell((0,0),DIR_NORTH)>>>fromtilings.strategiesimportObstructionInferralFactory>>>strategy_generator=ObstructionInferralFactory(3)>>>forstrategyinstrategy_generator(placed_tiling):...print(strategy(placed_tiling))addedtheobstructions{012:(0,0),(0,0),(0,0)}+-++-+|●|=|●|+-++-+|1||1|+-++-+1:Av(0123,0132,0312,3012)1:Av(012)●:point●:pointCrossingobstructions:Requirement0:0123:(0,0),(0,0),(0,0),(0,1)0:(0,1)0132:(0,0),(0,0),(0,1),(0,0)0312:(0,0),(0,1),(0,0),(0,0)3012:(0,1),(0,0),(0,0),(0,0)Requirement0:0:(0,1)In the above code snippet, we have added the obstructiongp = 012: (0, 0), (0, 0), (0, 0). In particular, the 4 crossing
obstructions, and the 4 localised obstructions, all contained a copy ofgp,
so we simplify the right-hand side by removing these from the tiling.
This simplification step happens automatically when creating aTiling.FusionConsider the gridded permutations on the following tiling.>>>tiling=Tiling([GriddedPerm(Perm((0,1)),((0,0),(0,0))),GriddedPerm(Perm((0,1)),((0,0),(1,0))),GriddedPerm(Perm((0,1)),((1,0),(1,0)))])>>>print(tiling)+-+-+|\|\|+-+-+\:Av(01)Crossingobstructions:01:(0,0),(1,0)>>>foriinrange(4):...forgpinsorted(tiling.gridded_perms_of_length(i)):...print(gp)ε:0:(0,0)0:(1,0)10:(0,0),(0,0)10:(0,0),(1,0)10:(1,0),(1,0)210:(0,0),(0,0),(0,0)210:(0,0),(0,0),(1,0)210:(0,0),(1,0),(1,0)210:(1,0),(1,0),(1,0)Due to the crossing01obstruction it is clear that all of the underlying
permutations will be decreasing. Moreover, the transition between the left cell
and the right cell can be between any of the points. In particular, this says
there aren + 1gridded permutations of sizenon this tiling. We
capture this idea by fusing the two columns into a single column.>>>fromtilings.strategiesimportFusionFactory>>>strategy_generator=FusionFactory()>>>forruleinstrategy_generator(tiling):...print(rule)fusecolumns0and1+-+-++-+|\|\|↣|\|+-+-++-+\:Av(01)\:Av(01)Crossingobstructions:Assumption0:01:(0,0),(1,0)cancountpointsincell(0,0)We use the symbol↣instead of=to remind us that the counts of the
two sides are definitely not the same.
Notice, the right-hand side tiling here also now requires that we can count the
number of points in cell(0, 0). If there arekpoints in cell(0, 0)in a gridded permutation then there will bek + 1gridded permutations that
fuse to this gridded permutation. Of course, here the number of points in cell``(0, 0)``
is going to be equal to the size of the gridded permutation, but in general,
the points that need to be counted might not cover the whole tiling. For
example, the following rule was used within specification to enumerateAv(123).>>>tiling=Tiling(...[...GriddedPerm(Perm((0,1)),((0,0),(0,0))),...GriddedPerm(Perm((0,1)),((0,0),(1,0))),...GriddedPerm(Perm((0,1)),((1,0),(1,0))),...GriddedPerm(Perm((0,1,2)),((0,0),(2,0),(2,0))),...GriddedPerm(Perm((0,1,2)),((1,0),(2,0),(2,0))),...GriddedPerm(Perm((0,1,2)),((2,0),(2,0),(2,0))),...]...)>>>forruleinstrategy_generator(tiling):...print(rule)fusecolumns0and1+-+-+-++-+-+|\|\|1|↣|\|1|+-+-+-++-+-+1:Av(012)1:Av(012)\:Av(01)\:Av(01)Crossingobstructions:Crossingobstructions:01:(0,0),(1,0)012:(0,0),(1,0),(1,0)012:(0,0),(2,0),(2,0)Assumption0:012:(1,0),(2,0),(2,0)cancountpointsincell(0,0)PerformanceTheTileScopealgorithm can be resource-intensive in both time and memory. This
codebase is fully compatible withPyPy, an alternative
Python interpreter that usually runsTileScope5x - 7x faster, at the cost of higher
memory usage (sometimes as high as 2x). This extra memory usage is largely caused by
PyPy’s approach to incremental garbage collection, and as a result can be partially
mitigated by setting the environmental variablesdescribed here.
For example, the configurationPYPY_GC_MAJOR_COLLECT=1.1
PYPY_GC_MAX_DELTA=200MB
PYPY_GC_INCREMENT_STEP=10GBtends to improve memory usage at the cost of 30% - 50% extra time.If memory usage, rather than time usage, is a bottleneck, then the default interpreterCPythonis preferred.Finally, we’d like to reiterate, if you need support, have a suggestion, or just
want to be up to date with the latest developments please join us on ourDiscord serverwhere we’d be happy to hear
from you!CitingIf you found this library helpful with your research and would like to cite us,
you can use the followingBibTeXor go toZenodofor alternative formats.