perm_groups.py 181 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472
  1. from math import factorial as _factorial, log, prod
  2. from itertools import chain, islice, product
  3. from sympy.combinatorics import Permutation
  4. from sympy.combinatorics.permutations import (_af_commutes_with, _af_invert,
  5. _af_rmul, _af_rmuln, _af_pow, Cycle)
  6. from sympy.combinatorics.util import (_check_cycles_alt_sym,
  7. _distribute_gens_by_base, _orbits_transversals_from_bsgs,
  8. _handle_precomputed_bsgs, _base_ordering, _strong_gens_from_distr,
  9. _strip, _strip_af)
  10. from sympy.core import Basic
  11. from sympy.core.random import _randrange, randrange, choice
  12. from sympy.core.symbol import Symbol
  13. from sympy.core.sympify import _sympify
  14. from sympy.functions.combinatorial.factorials import factorial
  15. from sympy.ntheory import primefactors, sieve
  16. from sympy.ntheory.factor_ import (factorint, multiplicity)
  17. from sympy.ntheory.primetest import isprime
  18. from sympy.utilities.iterables import has_variety, is_sequence, uniq
  19. rmul = Permutation.rmul_with_af
  20. _af_new = Permutation._af_new
  21. class PermutationGroup(Basic):
  22. r"""The class defining a Permutation group.
  23. Explanation
  24. ===========
  25. ``PermutationGroup([p1, p2, ..., pn])`` returns the permutation group
  26. generated by the list of permutations. This group can be supplied
  27. to Polyhedron if one desires to decorate the elements to which the
  28. indices of the permutation refer.
  29. Examples
  30. ========
  31. >>> from sympy.combinatorics import Permutation, PermutationGroup
  32. >>> from sympy.combinatorics import Polyhedron
  33. The permutations corresponding to motion of the front, right and
  34. bottom face of a $2 \times 2$ Rubik's cube are defined:
  35. >>> F = Permutation(2, 19, 21, 8)(3, 17, 20, 10)(4, 6, 7, 5)
  36. >>> R = Permutation(1, 5, 21, 14)(3, 7, 23, 12)(8, 10, 11, 9)
  37. >>> D = Permutation(6, 18, 14, 10)(7, 19, 15, 11)(20, 22, 23, 21)
  38. These are passed as permutations to PermutationGroup:
  39. >>> G = PermutationGroup(F, R, D)
  40. >>> G.order()
  41. 3674160
  42. The group can be supplied to a Polyhedron in order to track the
  43. objects being moved. An example involving the $2 \times 2$ Rubik's cube is
  44. given there, but here is a simple demonstration:
  45. >>> a = Permutation(2, 1)
  46. >>> b = Permutation(1, 0)
  47. >>> G = PermutationGroup(a, b)
  48. >>> P = Polyhedron(list('ABC'), pgroup=G)
  49. >>> P.corners
  50. (A, B, C)
  51. >>> P.rotate(0) # apply permutation 0
  52. >>> P.corners
  53. (A, C, B)
  54. >>> P.reset()
  55. >>> P.corners
  56. (A, B, C)
  57. Or one can make a permutation as a product of selected permutations
  58. and apply them to an iterable directly:
  59. >>> P10 = G.make_perm([0, 1])
  60. >>> P10('ABC')
  61. ['C', 'A', 'B']
  62. See Also
  63. ========
  64. sympy.combinatorics.polyhedron.Polyhedron,
  65. sympy.combinatorics.permutations.Permutation
  66. References
  67. ==========
  68. .. [1] Holt, D., Eick, B., O'Brien, E.
  69. "Handbook of Computational Group Theory"
  70. .. [2] Seress, A.
  71. "Permutation Group Algorithms"
  72. .. [3] https://en.wikipedia.org/wiki/Schreier_vector
  73. .. [4] https://en.wikipedia.org/wiki/Nielsen_transformation#Product_replacement_algorithm
  74. .. [5] Frank Celler, Charles R.Leedham-Green, Scott H.Murray,
  75. Alice C.Niemeyer, and E.A.O'Brien. "Generating Random
  76. Elements of a Finite Group"
  77. .. [6] https://en.wikipedia.org/wiki/Block_%28permutation_group_theory%29
  78. .. [7] https://algorithmist.com/wiki/Union_find
  79. .. [8] https://en.wikipedia.org/wiki/Multiply_transitive_group#Multiply_transitive_groups
  80. .. [9] https://en.wikipedia.org/wiki/Center_%28group_theory%29
  81. .. [10] https://en.wikipedia.org/wiki/Centralizer_and_normalizer
  82. .. [11] https://groupprops.subwiki.org/wiki/Derived_subgroup
  83. .. [12] https://en.wikipedia.org/wiki/Nilpotent_group
  84. .. [13] https://www.math.colostate.edu/~hulpke/CGT/cgtnotes.pdf
  85. .. [14] https://docs.gap-system.org/doc/ref/manual.pdf
  86. """
  87. is_group = True
  88. def __new__(cls, *args, dups=True, **kwargs):
  89. """The default constructor. Accepts Cycle and Permutation forms.
  90. Removes duplicates unless ``dups`` keyword is ``False``.
  91. """
  92. if not args:
  93. args = [Permutation()]
  94. else:
  95. args = list(args[0] if is_sequence(args[0]) else args)
  96. if not args:
  97. args = [Permutation()]
  98. if any(isinstance(a, Cycle) for a in args):
  99. args = [Permutation(a) for a in args]
  100. if has_variety(a.size for a in args):
  101. degree = kwargs.pop('degree', None)
  102. if degree is None:
  103. degree = max(a.size for a in args)
  104. for i in range(len(args)):
  105. if args[i].size != degree:
  106. args[i] = Permutation(args[i], size=degree)
  107. if dups:
  108. args = list(uniq([_af_new(list(a)) for a in args]))
  109. if len(args) > 1:
  110. args = [g for g in args if not g.is_identity]
  111. return Basic.__new__(cls, *args, **kwargs)
  112. def __init__(self, *args, **kwargs):
  113. self._generators = list(self.args)
  114. self._order = None
  115. self._center = []
  116. self._is_abelian = None
  117. self._is_transitive = None
  118. self._is_sym = None
  119. self._is_alt = None
  120. self._is_primitive = None
  121. self._is_nilpotent = None
  122. self._is_solvable = None
  123. self._is_trivial = None
  124. self._transitivity_degree = None
  125. self._max_div = None
  126. self._is_perfect = None
  127. self._is_cyclic = None
  128. self._is_dihedral = None
  129. self._r = len(self._generators)
  130. self._degree = self._generators[0].size
  131. # these attributes are assigned after running schreier_sims
  132. self._base = []
  133. self._strong_gens = []
  134. self._strong_gens_slp = []
  135. self._basic_orbits = []
  136. self._transversals = []
  137. self._transversal_slp = []
  138. # these attributes are assigned after running _random_pr_init
  139. self._random_gens = []
  140. # finite presentation of the group as an instance of `FpGroup`
  141. self._fp_presentation = None
  142. def __getitem__(self, i):
  143. return self._generators[i]
  144. def __contains__(self, i):
  145. """Return ``True`` if *i* is contained in PermutationGroup.
  146. Examples
  147. ========
  148. >>> from sympy.combinatorics import Permutation, PermutationGroup
  149. >>> p = Permutation(1, 2, 3)
  150. >>> Permutation(3) in PermutationGroup(p)
  151. True
  152. """
  153. if not isinstance(i, Permutation):
  154. raise TypeError("A PermutationGroup contains only Permutations as "
  155. "elements, not elements of type %s" % type(i))
  156. return self.contains(i)
  157. def __len__(self):
  158. return len(self._generators)
  159. def equals(self, other):
  160. """Return ``True`` if PermutationGroup generated by elements in the
  161. group are same i.e they represent the same PermutationGroup.
  162. Examples
  163. ========
  164. >>> from sympy.combinatorics import Permutation, PermutationGroup
  165. >>> p = Permutation(0, 1, 2, 3, 4, 5)
  166. >>> G = PermutationGroup([p, p**2])
  167. >>> H = PermutationGroup([p**2, p])
  168. >>> G.generators == H.generators
  169. False
  170. >>> G.equals(H)
  171. True
  172. """
  173. if not isinstance(other, PermutationGroup):
  174. return False
  175. set_self_gens = set(self.generators)
  176. set_other_gens = set(other.generators)
  177. # before reaching the general case there are also certain
  178. # optimisation and obvious cases requiring less or no actual
  179. # computation.
  180. if set_self_gens == set_other_gens:
  181. return True
  182. # in the most general case it will check that each generator of
  183. # one group belongs to the other PermutationGroup and vice-versa
  184. for gen1 in set_self_gens:
  185. if not other.contains(gen1):
  186. return False
  187. for gen2 in set_other_gens:
  188. if not self.contains(gen2):
  189. return False
  190. return True
  191. def __mul__(self, other):
  192. """
  193. Return the direct product of two permutation groups as a permutation
  194. group.
  195. Explanation
  196. ===========
  197. This implementation realizes the direct product by shifting the index
  198. set for the generators of the second group: so if we have ``G`` acting
  199. on ``n1`` points and ``H`` acting on ``n2`` points, ``G*H`` acts on
  200. ``n1 + n2`` points.
  201. Examples
  202. ========
  203. >>> from sympy.combinatorics.named_groups import CyclicGroup
  204. >>> G = CyclicGroup(5)
  205. >>> H = G*G
  206. >>> H
  207. PermutationGroup([
  208. (9)(0 1 2 3 4),
  209. (5 6 7 8 9)])
  210. >>> H.order()
  211. 25
  212. """
  213. if isinstance(other, Permutation):
  214. return Coset(other, self, dir='+')
  215. gens1 = [perm._array_form for perm in self.generators]
  216. gens2 = [perm._array_form for perm in other.generators]
  217. n1 = self._degree
  218. n2 = other._degree
  219. start = list(range(n1))
  220. end = list(range(n1, n1 + n2))
  221. for i in range(len(gens2)):
  222. gens2[i] = [x + n1 for x in gens2[i]]
  223. gens2 = [start + gen for gen in gens2]
  224. gens1 = [gen + end for gen in gens1]
  225. together = gens1 + gens2
  226. gens = [_af_new(x) for x in together]
  227. return PermutationGroup(gens)
  228. def _random_pr_init(self, r, n, _random_prec_n=None):
  229. r"""Initialize random generators for the product replacement algorithm.
  230. Explanation
  231. ===========
  232. The implementation uses a modification of the original product
  233. replacement algorithm due to Leedham-Green, as described in [1],
  234. pp. 69-71; also, see [2], pp. 27-29 for a detailed theoretical
  235. analysis of the original product replacement algorithm, and [4].
  236. The product replacement algorithm is used for producing random,
  237. uniformly distributed elements of a group `G` with a set of generators
  238. `S`. For the initialization ``_random_pr_init``, a list ``R`` of
  239. `\max\{r, |S|\}` group generators is created as the attribute
  240. ``G._random_gens``, repeating elements of `S` if necessary, and the
  241. identity element of `G` is appended to ``R`` - we shall refer to this
  242. last element as the accumulator. Then the function ``random_pr()``
  243. is called ``n`` times, randomizing the list ``R`` while preserving
  244. the generation of `G` by ``R``. The function ``random_pr()`` itself
  245. takes two random elements ``g, h`` among all elements of ``R`` but
  246. the accumulator and replaces ``g`` with a randomly chosen element
  247. from `\{gh, g(~h), hg, (~h)g\}`. Then the accumulator is multiplied
  248. by whatever ``g`` was replaced by. The new value of the accumulator is
  249. then returned by ``random_pr()``.
  250. The elements returned will eventually (for ``n`` large enough) become
  251. uniformly distributed across `G` ([5]). For practical purposes however,
  252. the values ``n = 50, r = 11`` are suggested in [1].
  253. Notes
  254. =====
  255. THIS FUNCTION HAS SIDE EFFECTS: it changes the attribute
  256. self._random_gens
  257. See Also
  258. ========
  259. random_pr
  260. """
  261. deg = self.degree
  262. random_gens = [x._array_form for x in self.generators]
  263. k = len(random_gens)
  264. if k < r:
  265. for i in range(k, r):
  266. random_gens.append(random_gens[i - k])
  267. acc = list(range(deg))
  268. random_gens.append(acc)
  269. self._random_gens = random_gens
  270. # handle randomized input for testing purposes
  271. if _random_prec_n is None:
  272. for i in range(n):
  273. self.random_pr()
  274. else:
  275. for i in range(n):
  276. self.random_pr(_random_prec=_random_prec_n[i])
  277. def _union_find_merge(self, first, second, ranks, parents, not_rep):
  278. """Merges two classes in a union-find data structure.
  279. Explanation
  280. ===========
  281. Used in the implementation of Atkinson's algorithm as suggested in [1],
  282. pp. 83-87. The class merging process uses union by rank as an
  283. optimization. ([7])
  284. Notes
  285. =====
  286. THIS FUNCTION HAS SIDE EFFECTS: the list of class representatives,
  287. ``parents``, the list of class sizes, ``ranks``, and the list of
  288. elements that are not representatives, ``not_rep``, are changed due to
  289. class merging.
  290. See Also
  291. ========
  292. minimal_block, _union_find_rep
  293. References
  294. ==========
  295. .. [1] Holt, D., Eick, B., O'Brien, E.
  296. "Handbook of computational group theory"
  297. .. [7] https://algorithmist.com/wiki/Union_find
  298. """
  299. rep_first = self._union_find_rep(first, parents)
  300. rep_second = self._union_find_rep(second, parents)
  301. if rep_first != rep_second:
  302. # union by rank
  303. if ranks[rep_first] >= ranks[rep_second]:
  304. new_1, new_2 = rep_first, rep_second
  305. else:
  306. new_1, new_2 = rep_second, rep_first
  307. total_rank = ranks[new_1] + ranks[new_2]
  308. if total_rank > self.max_div:
  309. return -1
  310. parents[new_2] = new_1
  311. ranks[new_1] = total_rank
  312. not_rep.append(new_2)
  313. return 1
  314. return 0
  315. def _union_find_rep(self, num, parents):
  316. """Find representative of a class in a union-find data structure.
  317. Explanation
  318. ===========
  319. Used in the implementation of Atkinson's algorithm as suggested in [1],
  320. pp. 83-87. After the representative of the class to which ``num``
  321. belongs is found, path compression is performed as an optimization
  322. ([7]).
  323. Notes
  324. =====
  325. THIS FUNCTION HAS SIDE EFFECTS: the list of class representatives,
  326. ``parents``, is altered due to path compression.
  327. See Also
  328. ========
  329. minimal_block, _union_find_merge
  330. References
  331. ==========
  332. .. [1] Holt, D., Eick, B., O'Brien, E.
  333. "Handbook of computational group theory"
  334. .. [7] https://algorithmist.com/wiki/Union_find
  335. """
  336. rep, parent = num, parents[num]
  337. while parent != rep:
  338. rep = parent
  339. parent = parents[rep]
  340. # path compression
  341. temp, parent = num, parents[num]
  342. while parent != rep:
  343. parents[temp] = rep
  344. temp = parent
  345. parent = parents[temp]
  346. return rep
  347. @property
  348. def base(self):
  349. r"""Return a base from the Schreier-Sims algorithm.
  350. Explanation
  351. ===========
  352. For a permutation group `G`, a base is a sequence of points
  353. `B = (b_1, b_2, \dots, b_k)` such that no element of `G` apart
  354. from the identity fixes all the points in `B`. The concepts of
  355. a base and strong generating set and their applications are
  356. discussed in depth in [1], pp. 87-89 and [2], pp. 55-57.
  357. An alternative way to think of `B` is that it gives the
  358. indices of the stabilizer cosets that contain more than the
  359. identity permutation.
  360. Examples
  361. ========
  362. >>> from sympy.combinatorics import Permutation, PermutationGroup
  363. >>> G = PermutationGroup([Permutation(0, 1, 3)(2, 4)])
  364. >>> G.base
  365. [0, 2]
  366. See Also
  367. ========
  368. strong_gens, basic_transversals, basic_orbits, basic_stabilizers
  369. """
  370. if self._base == []:
  371. self.schreier_sims()
  372. return self._base
  373. def baseswap(self, base, strong_gens, pos, randomized=False,
  374. transversals=None, basic_orbits=None, strong_gens_distr=None):
  375. r"""Swap two consecutive base points in base and strong generating set.
  376. Explanation
  377. ===========
  378. If a base for a group `G` is given by `(b_1, b_2, \dots, b_k)`, this
  379. function returns a base `(b_1, b_2, \dots, b_{i+1}, b_i, \dots, b_k)`,
  380. where `i` is given by ``pos``, and a strong generating set relative
  381. to that base. The original base and strong generating set are not
  382. modified.
  383. The randomized version (default) is of Las Vegas type.
  384. Parameters
  385. ==========
  386. base, strong_gens
  387. The base and strong generating set.
  388. pos
  389. The position at which swapping is performed.
  390. randomized
  391. A switch between randomized and deterministic version.
  392. transversals
  393. The transversals for the basic orbits, if known.
  394. basic_orbits
  395. The basic orbits, if known.
  396. strong_gens_distr
  397. The strong generators distributed by basic stabilizers, if known.
  398. Returns
  399. =======
  400. (base, strong_gens)
  401. ``base`` is the new base, and ``strong_gens`` is a generating set
  402. relative to it.
  403. Examples
  404. ========
  405. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  406. >>> from sympy.combinatorics.testutil import _verify_bsgs
  407. >>> from sympy.combinatorics.perm_groups import PermutationGroup
  408. >>> S = SymmetricGroup(4)
  409. >>> S.schreier_sims()
  410. >>> S.base
  411. [0, 1, 2]
  412. >>> base, gens = S.baseswap(S.base, S.strong_gens, 1, randomized=False)
  413. >>> base, gens
  414. ([0, 2, 1],
  415. [(0 1 2 3), (3)(0 1), (1 3 2),
  416. (2 3), (1 3)])
  417. check that base, gens is a BSGS
  418. >>> S1 = PermutationGroup(gens)
  419. >>> _verify_bsgs(S1, base, gens)
  420. True
  421. See Also
  422. ========
  423. schreier_sims
  424. Notes
  425. =====
  426. The deterministic version of the algorithm is discussed in
  427. [1], pp. 102-103; the randomized version is discussed in [1], p.103, and
  428. [2], p.98. It is of Las Vegas type.
  429. Notice that [1] contains a mistake in the pseudocode and
  430. discussion of BASESWAP: on line 3 of the pseudocode,
  431. `|\beta_{i+1}^{\left\langle T\right\rangle}|` should be replaced by
  432. `|\beta_{i}^{\left\langle T\right\rangle}|`, and the same for the
  433. discussion of the algorithm.
  434. """
  435. # construct the basic orbits, generators for the stabilizer chain
  436. # and transversal elements from whatever was provided
  437. transversals, basic_orbits, strong_gens_distr = \
  438. _handle_precomputed_bsgs(base, strong_gens, transversals,
  439. basic_orbits, strong_gens_distr)
  440. base_len = len(base)
  441. degree = self.degree
  442. # size of orbit of base[pos] under the stabilizer we seek to insert
  443. # in the stabilizer chain at position pos + 1
  444. size = len(basic_orbits[pos])*len(basic_orbits[pos + 1]) \
  445. //len(_orbit(degree, strong_gens_distr[pos], base[pos + 1]))
  446. # initialize the wanted stabilizer by a subgroup
  447. if pos + 2 > base_len - 1:
  448. T = []
  449. else:
  450. T = strong_gens_distr[pos + 2][:]
  451. # randomized version
  452. if randomized is True:
  453. stab_pos = PermutationGroup(strong_gens_distr[pos])
  454. schreier_vector = stab_pos.schreier_vector(base[pos + 1])
  455. # add random elements of the stabilizer until they generate it
  456. while len(_orbit(degree, T, base[pos])) != size:
  457. new = stab_pos.random_stab(base[pos + 1],
  458. schreier_vector=schreier_vector)
  459. T.append(new)
  460. # deterministic version
  461. else:
  462. Gamma = set(basic_orbits[pos])
  463. Gamma.remove(base[pos])
  464. if base[pos + 1] in Gamma:
  465. Gamma.remove(base[pos + 1])
  466. # add elements of the stabilizer until they generate it by
  467. # ruling out member of the basic orbit of base[pos] along the way
  468. while len(_orbit(degree, T, base[pos])) != size:
  469. gamma = next(iter(Gamma))
  470. x = transversals[pos][gamma]
  471. temp = x._array_form.index(base[pos + 1]) # (~x)(base[pos + 1])
  472. if temp not in basic_orbits[pos + 1]:
  473. Gamma = Gamma - _orbit(degree, T, gamma)
  474. else:
  475. y = transversals[pos + 1][temp]
  476. el = rmul(x, y)
  477. if el(base[pos]) not in _orbit(degree, T, base[pos]):
  478. T.append(el)
  479. Gamma = Gamma - _orbit(degree, T, base[pos])
  480. # build the new base and strong generating set
  481. strong_gens_new_distr = strong_gens_distr[:]
  482. strong_gens_new_distr[pos + 1] = T
  483. base_new = base[:]
  484. base_new[pos], base_new[pos + 1] = base_new[pos + 1], base_new[pos]
  485. strong_gens_new = _strong_gens_from_distr(strong_gens_new_distr)
  486. for gen in T:
  487. if gen not in strong_gens_new:
  488. strong_gens_new.append(gen)
  489. return base_new, strong_gens_new
  490. @property
  491. def basic_orbits(self):
  492. r"""
  493. Return the basic orbits relative to a base and strong generating set.
  494. Explanation
  495. ===========
  496. If `(b_1, b_2, \dots, b_k)` is a base for a group `G`, and
  497. `G^{(i)} = G_{b_1, b_2, \dots, b_{i-1}}` is the ``i``-th basic stabilizer
  498. (so that `G^{(1)} = G`), the ``i``-th basic orbit relative to this base
  499. is the orbit of `b_i` under `G^{(i)}`. See [1], pp. 87-89 for more
  500. information.
  501. Examples
  502. ========
  503. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  504. >>> S = SymmetricGroup(4)
  505. >>> S.basic_orbits
  506. [[0, 1, 2, 3], [1, 2, 3], [2, 3]]
  507. See Also
  508. ========
  509. base, strong_gens, basic_transversals, basic_stabilizers
  510. """
  511. if self._basic_orbits == []:
  512. self.schreier_sims()
  513. return self._basic_orbits
  514. @property
  515. def basic_stabilizers(self):
  516. r"""
  517. Return a chain of stabilizers relative to a base and strong generating
  518. set.
  519. Explanation
  520. ===========
  521. The ``i``-th basic stabilizer `G^{(i)}` relative to a base
  522. `(b_1, b_2, \dots, b_k)` is `G_{b_1, b_2, \dots, b_{i-1}}`. For more
  523. information, see [1], pp. 87-89.
  524. Examples
  525. ========
  526. >>> from sympy.combinatorics.named_groups import AlternatingGroup
  527. >>> A = AlternatingGroup(4)
  528. >>> A.schreier_sims()
  529. >>> A.base
  530. [0, 1]
  531. >>> for g in A.basic_stabilizers:
  532. ... print(g)
  533. ...
  534. PermutationGroup([
  535. (3)(0 1 2),
  536. (1 2 3)])
  537. PermutationGroup([
  538. (1 2 3)])
  539. See Also
  540. ========
  541. base, strong_gens, basic_orbits, basic_transversals
  542. """
  543. if self._transversals == []:
  544. self.schreier_sims()
  545. strong_gens = self._strong_gens
  546. base = self._base
  547. if not base: # e.g. if self is trivial
  548. return []
  549. strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
  550. basic_stabilizers = []
  551. for gens in strong_gens_distr:
  552. basic_stabilizers.append(PermutationGroup(gens))
  553. return basic_stabilizers
  554. @property
  555. def basic_transversals(self):
  556. """
  557. Return basic transversals relative to a base and strong generating set.
  558. Explanation
  559. ===========
  560. The basic transversals are transversals of the basic orbits. They
  561. are provided as a list of dictionaries, each dictionary having
  562. keys - the elements of one of the basic orbits, and values - the
  563. corresponding transversal elements. See [1], pp. 87-89 for more
  564. information.
  565. Examples
  566. ========
  567. >>> from sympy.combinatorics.named_groups import AlternatingGroup
  568. >>> A = AlternatingGroup(4)
  569. >>> A.basic_transversals
  570. [{0: (3), 1: (3)(0 1 2), 2: (3)(0 2 1), 3: (0 3 1)}, {1: (3), 2: (1 2 3), 3: (1 3 2)}]
  571. See Also
  572. ========
  573. strong_gens, base, basic_orbits, basic_stabilizers
  574. """
  575. if self._transversals == []:
  576. self.schreier_sims()
  577. return self._transversals
  578. def composition_series(self):
  579. r"""
  580. Return the composition series for a group as a list
  581. of permutation groups.
  582. Explanation
  583. ===========
  584. The composition series for a group `G` is defined as a
  585. subnormal series `G = H_0 > H_1 > H_2 \ldots` A composition
  586. series is a subnormal series such that each factor group
  587. `H(i+1) / H(i)` is simple.
  588. A subnormal series is a composition series only if it is of
  589. maximum length.
  590. The algorithm works as follows:
  591. Starting with the derived series the idea is to fill
  592. the gap between `G = der[i]` and `H = der[i+1]` for each
  593. `i` independently. Since, all subgroups of the abelian group
  594. `G/H` are normal so, first step is to take the generators
  595. `g` of `G` and add them to generators of `H` one by one.
  596. The factor groups formed are not simple in general. Each
  597. group is obtained from the previous one by adding one
  598. generator `g`, if the previous group is denoted by `H`
  599. then the next group `K` is generated by `g` and `H`.
  600. The factor group `K/H` is cyclic and it's order is
  601. `K.order()//G.order()`. The series is then extended between
  602. `K` and `H` by groups generated by powers of `g` and `H`.
  603. The series formed is then prepended to the already existing
  604. series.
  605. Examples
  606. ========
  607. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  608. >>> from sympy.combinatorics.named_groups import CyclicGroup
  609. >>> S = SymmetricGroup(12)
  610. >>> G = S.sylow_subgroup(2)
  611. >>> C = G.composition_series()
  612. >>> [H.order() for H in C]
  613. [1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1]
  614. >>> G = S.sylow_subgroup(3)
  615. >>> C = G.composition_series()
  616. >>> [H.order() for H in C]
  617. [243, 81, 27, 9, 3, 1]
  618. >>> G = CyclicGroup(12)
  619. >>> C = G.composition_series()
  620. >>> [H.order() for H in C]
  621. [12, 6, 3, 1]
  622. """
  623. der = self.derived_series()
  624. if not all(g.is_identity for g in der[-1].generators):
  625. raise NotImplementedError('Group should be solvable')
  626. series = []
  627. for i in range(len(der)-1):
  628. H = der[i+1]
  629. up_seg = []
  630. for g in der[i].generators:
  631. K = PermutationGroup([g] + H.generators)
  632. order = K.order() // H.order()
  633. down_seg = []
  634. for p, e in factorint(order).items():
  635. for _ in range(e):
  636. down_seg.append(PermutationGroup([g] + H.generators))
  637. g = g**p
  638. up_seg = down_seg + up_seg
  639. H = K
  640. up_seg[0] = der[i]
  641. series.extend(up_seg)
  642. series.append(der[-1])
  643. return series
  644. def coset_transversal(self, H):
  645. """Return a transversal of the right cosets of self by its subgroup H
  646. using the second method described in [1], Subsection 4.6.7
  647. """
  648. if not H.is_subgroup(self):
  649. raise ValueError("The argument must be a subgroup")
  650. if H.order() == 1:
  651. return self._elements
  652. self._schreier_sims(base=H.base) # make G.base an extension of H.base
  653. base = self.base
  654. base_ordering = _base_ordering(base, self.degree)
  655. identity = Permutation(self.degree - 1)
  656. transversals = self.basic_transversals[:]
  657. # transversals is a list of dictionaries. Get rid of the keys
  658. # so that it is a list of lists and sort each list in
  659. # the increasing order of base[l]^x
  660. for l, t in enumerate(transversals):
  661. transversals[l] = sorted(t.values(),
  662. key = lambda x: base_ordering[base[l]^x])
  663. orbits = H.basic_orbits
  664. h_stabs = H.basic_stabilizers
  665. g_stabs = self.basic_stabilizers
  666. indices = [x.order()//y.order() for x, y in zip(g_stabs, h_stabs)]
  667. # T^(l) should be a right transversal of H^(l) in G^(l) for
  668. # 1<=l<=len(base). While H^(l) is the trivial group, T^(l)
  669. # contains all the elements of G^(l) so we might just as well
  670. # start with l = len(h_stabs)-1
  671. if len(g_stabs) > len(h_stabs):
  672. T = g_stabs[len(h_stabs)]._elements
  673. else:
  674. T = [identity]
  675. l = len(h_stabs)-1
  676. t_len = len(T)
  677. while l > -1:
  678. T_next = []
  679. for u in transversals[l]:
  680. if u == identity:
  681. continue
  682. b = base_ordering[base[l]^u]
  683. for t in T:
  684. p = t*u
  685. if all(base_ordering[h^p] >= b for h in orbits[l]):
  686. T_next.append(p)
  687. if t_len + len(T_next) == indices[l]:
  688. break
  689. if t_len + len(T_next) == indices[l]:
  690. break
  691. T += T_next
  692. t_len += len(T_next)
  693. l -= 1
  694. T.remove(identity)
  695. T = [identity] + T
  696. return T
  697. def _coset_representative(self, g, H):
  698. """Return the representative of Hg from the transversal that
  699. would be computed by ``self.coset_transversal(H)``.
  700. """
  701. if H.order() == 1:
  702. return g
  703. # The base of self must be an extension of H.base.
  704. if not(self.base[:len(H.base)] == H.base):
  705. self._schreier_sims(base=H.base)
  706. orbits = H.basic_orbits[:]
  707. h_transversals = [list(_.values()) for _ in H.basic_transversals]
  708. transversals = [list(_.values()) for _ in self.basic_transversals]
  709. base = self.base
  710. base_ordering = _base_ordering(base, self.degree)
  711. def step(l, x):
  712. gamma = sorted(orbits[l], key = lambda y: base_ordering[y^x])[0]
  713. i = [base[l]^h for h in h_transversals[l]].index(gamma)
  714. x = h_transversals[l][i]*x
  715. if l < len(orbits)-1:
  716. for u in transversals[l]:
  717. if base[l]^u == base[l]^x:
  718. break
  719. x = step(l+1, x*u**-1)*u
  720. return x
  721. return step(0, g)
  722. def coset_table(self, H):
  723. """Return the standardised (right) coset table of self in H as
  724. a list of lists.
  725. """
  726. # Maybe this should be made to return an instance of CosetTable
  727. # from fp_groups.py but the class would need to be changed first
  728. # to be compatible with PermutationGroups
  729. if not H.is_subgroup(self):
  730. raise ValueError("The argument must be a subgroup")
  731. T = self.coset_transversal(H)
  732. n = len(T)
  733. A = list(chain.from_iterable((gen, gen**-1)
  734. for gen in self.generators))
  735. table = []
  736. for i in range(n):
  737. row = [self._coset_representative(T[i]*x, H) for x in A]
  738. row = [T.index(r) for r in row]
  739. table.append(row)
  740. # standardize (this is the same as the algorithm used in coset_table)
  741. # If CosetTable is made compatible with PermutationGroups, this
  742. # should be replaced by table.standardize()
  743. A = range(len(A))
  744. gamma = 1
  745. for alpha, a in product(range(n), A):
  746. beta = table[alpha][a]
  747. if beta >= gamma:
  748. if beta > gamma:
  749. for x in A:
  750. z = table[gamma][x]
  751. table[gamma][x] = table[beta][x]
  752. table[beta][x] = z
  753. for i in range(n):
  754. if table[i][x] == beta:
  755. table[i][x] = gamma
  756. elif table[i][x] == gamma:
  757. table[i][x] = beta
  758. gamma += 1
  759. if gamma >= n-1:
  760. return table
  761. def center(self):
  762. r"""
  763. Return the center of a permutation group.
  764. Explanation
  765. ===========
  766. The center for a group `G` is defined as
  767. `Z(G) = \{z\in G | \forall g\in G, zg = gz \}`,
  768. the set of elements of `G` that commute with all elements of `G`.
  769. It is equal to the centralizer of `G` inside `G`, and is naturally a
  770. subgroup of `G` ([9]).
  771. Examples
  772. ========
  773. >>> from sympy.combinatorics.named_groups import DihedralGroup
  774. >>> D = DihedralGroup(4)
  775. >>> G = D.center()
  776. >>> G.order()
  777. 2
  778. See Also
  779. ========
  780. centralizer
  781. Notes
  782. =====
  783. This is a naive implementation that is a straightforward application
  784. of ``.centralizer()``
  785. """
  786. return self.centralizer(self)
  787. def centralizer(self, other):
  788. r"""
  789. Return the centralizer of a group/set/element.
  790. Explanation
  791. ===========
  792. The centralizer of a set of permutations ``S`` inside
  793. a group ``G`` is the set of elements of ``G`` that commute with all
  794. elements of ``S``::
  795. `C_G(S) = \{ g \in G | gs = sg \forall s \in S\}` ([10])
  796. Usually, ``S`` is a subset of ``G``, but if ``G`` is a proper subgroup of
  797. the full symmetric group, we allow for ``S`` to have elements outside
  798. ``G``.
  799. It is naturally a subgroup of ``G``; the centralizer of a permutation
  800. group is equal to the centralizer of any set of generators for that
  801. group, since any element commuting with the generators commutes with
  802. any product of the generators.
  803. Parameters
  804. ==========
  805. other
  806. a permutation group/list of permutations/single permutation
  807. Examples
  808. ========
  809. >>> from sympy.combinatorics.named_groups import (SymmetricGroup,
  810. ... CyclicGroup)
  811. >>> S = SymmetricGroup(6)
  812. >>> C = CyclicGroup(6)
  813. >>> H = S.centralizer(C)
  814. >>> H.is_subgroup(C)
  815. True
  816. See Also
  817. ========
  818. subgroup_search
  819. Notes
  820. =====
  821. The implementation is an application of ``.subgroup_search()`` with
  822. tests using a specific base for the group ``G``.
  823. """
  824. if hasattr(other, 'generators'):
  825. if other.is_trivial or self.is_trivial:
  826. return self
  827. degree = self.degree
  828. identity = _af_new(list(range(degree)))
  829. orbits = other.orbits()
  830. num_orbits = len(orbits)
  831. orbits.sort(key=lambda x: -len(x))
  832. long_base = []
  833. orbit_reps = [None]*num_orbits
  834. orbit_reps_indices = [None]*num_orbits
  835. orbit_descr = [None]*degree
  836. for i in range(num_orbits):
  837. orbit = list(orbits[i])
  838. orbit_reps[i] = orbit[0]
  839. orbit_reps_indices[i] = len(long_base)
  840. for point in orbit:
  841. orbit_descr[point] = i
  842. long_base = long_base + orbit
  843. base, strong_gens = self.schreier_sims_incremental(base=long_base)
  844. strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
  845. i = 0
  846. for i in range(len(base)):
  847. if strong_gens_distr[i] == [identity]:
  848. break
  849. base = base[:i]
  850. base_len = i
  851. for j in range(num_orbits):
  852. if base[base_len - 1] in orbits[j]:
  853. break
  854. rel_orbits = orbits[: j + 1]
  855. num_rel_orbits = len(rel_orbits)
  856. transversals = [None]*num_rel_orbits
  857. for j in range(num_rel_orbits):
  858. rep = orbit_reps[j]
  859. transversals[j] = dict(
  860. other.orbit_transversal(rep, pairs=True))
  861. trivial_test = lambda x: True
  862. tests = [None]*base_len
  863. for l in range(base_len):
  864. if base[l] in orbit_reps:
  865. tests[l] = trivial_test
  866. else:
  867. def test(computed_words, l=l):
  868. g = computed_words[l]
  869. rep_orb_index = orbit_descr[base[l]]
  870. rep = orbit_reps[rep_orb_index]
  871. im = g._array_form[base[l]]
  872. im_rep = g._array_form[rep]
  873. tr_el = transversals[rep_orb_index][base[l]]
  874. # using the definition of transversal,
  875. # base[l]^g = rep^(tr_el*g);
  876. # if g belongs to the centralizer, then
  877. # base[l]^g = (rep^g)^tr_el
  878. return im == tr_el._array_form[im_rep]
  879. tests[l] = test
  880. def prop(g):
  881. return [rmul(g, gen) for gen in other.generators] == \
  882. [rmul(gen, g) for gen in other.generators]
  883. return self.subgroup_search(prop, base=base,
  884. strong_gens=strong_gens, tests=tests)
  885. elif hasattr(other, '__getitem__'):
  886. gens = list(other)
  887. return self.centralizer(PermutationGroup(gens))
  888. elif hasattr(other, 'array_form'):
  889. return self.centralizer(PermutationGroup([other]))
  890. def commutator(self, G, H):
  891. """
  892. Return the commutator of two subgroups.
  893. Explanation
  894. ===========
  895. For a permutation group ``K`` and subgroups ``G``, ``H``, the
  896. commutator of ``G`` and ``H`` is defined as the group generated
  897. by all the commutators `[g, h] = hgh^{-1}g^{-1}` for ``g`` in ``G`` and
  898. ``h`` in ``H``. It is naturally a subgroup of ``K`` ([1], p.27).
  899. Examples
  900. ========
  901. >>> from sympy.combinatorics.named_groups import (SymmetricGroup,
  902. ... AlternatingGroup)
  903. >>> S = SymmetricGroup(5)
  904. >>> A = AlternatingGroup(5)
  905. >>> G = S.commutator(S, A)
  906. >>> G.is_subgroup(A)
  907. True
  908. See Also
  909. ========
  910. derived_subgroup
  911. Notes
  912. =====
  913. The commutator of two subgroups `H, G` is equal to the normal closure
  914. of the commutators of all the generators, i.e. `hgh^{-1}g^{-1}` for `h`
  915. a generator of `H` and `g` a generator of `G` ([1], p.28)
  916. """
  917. ggens = G.generators
  918. hgens = H.generators
  919. commutators = []
  920. for ggen in ggens:
  921. for hgen in hgens:
  922. commutator = rmul(hgen, ggen, ~hgen, ~ggen)
  923. if commutator not in commutators:
  924. commutators.append(commutator)
  925. res = self.normal_closure(commutators)
  926. return res
  927. def coset_factor(self, g, factor_index=False):
  928. """Return ``G``'s (self's) coset factorization of ``g``
  929. Explanation
  930. ===========
  931. If ``g`` is an element of ``G`` then it can be written as the product
  932. of permutations drawn from the Schreier-Sims coset decomposition,
  933. The permutations returned in ``f`` are those for which
  934. the product gives ``g``: ``g = f[n]*...f[1]*f[0]`` where ``n = len(B)``
  935. and ``B = G.base``. f[i] is one of the permutations in
  936. ``self._basic_orbits[i]``.
  937. If factor_index==True,
  938. returns a tuple ``[b[0],..,b[n]]``, where ``b[i]``
  939. belongs to ``self._basic_orbits[i]``
  940. Examples
  941. ========
  942. >>> from sympy.combinatorics import Permutation, PermutationGroup
  943. >>> a = Permutation(0, 1, 3, 7, 6, 4)(2, 5)
  944. >>> b = Permutation(0, 1, 3, 2)(4, 5, 7, 6)
  945. >>> G = PermutationGroup([a, b])
  946. Define g:
  947. >>> g = Permutation(7)(1, 2, 4)(3, 6, 5)
  948. Confirm that it is an element of G:
  949. >>> G.contains(g)
  950. True
  951. Thus, it can be written as a product of factors (up to
  952. 3) drawn from u. See below that a factor from u1 and u2
  953. and the Identity permutation have been used:
  954. >>> f = G.coset_factor(g)
  955. >>> f[2]*f[1]*f[0] == g
  956. True
  957. >>> f1 = G.coset_factor(g, True); f1
  958. [0, 4, 4]
  959. >>> tr = G.basic_transversals
  960. >>> f[0] == tr[0][f1[0]]
  961. True
  962. If g is not an element of G then [] is returned:
  963. >>> c = Permutation(5, 6, 7)
  964. >>> G.coset_factor(c)
  965. []
  966. See Also
  967. ========
  968. sympy.combinatorics.util._strip
  969. """
  970. if isinstance(g, (Cycle, Permutation)):
  971. g = g.list()
  972. if len(g) != self._degree:
  973. # this could either adjust the size or return [] immediately
  974. # but we don't choose between the two and just signal a possible
  975. # error
  976. raise ValueError('g should be the same size as permutations of G')
  977. I = list(range(self._degree))
  978. basic_orbits = self.basic_orbits
  979. transversals = self._transversals
  980. factors = []
  981. base = self.base
  982. h = g
  983. for i in range(len(base)):
  984. beta = h[base[i]]
  985. if beta == base[i]:
  986. factors.append(beta)
  987. continue
  988. if beta not in basic_orbits[i]:
  989. return []
  990. u = transversals[i][beta]._array_form
  991. h = _af_rmul(_af_invert(u), h)
  992. factors.append(beta)
  993. if h != I:
  994. return []
  995. if factor_index:
  996. return factors
  997. tr = self.basic_transversals
  998. factors = [tr[i][factors[i]] for i in range(len(base))]
  999. return factors
  1000. def generator_product(self, g, original=False):
  1001. r'''
  1002. Return a list of strong generators `[s1, \dots, sn]`
  1003. s.t `g = sn \times \dots \times s1`. If ``original=True``, make the
  1004. list contain only the original group generators
  1005. '''
  1006. product = []
  1007. if g.is_identity:
  1008. return []
  1009. if g in self.strong_gens:
  1010. if not original or g in self.generators:
  1011. return [g]
  1012. else:
  1013. slp = self._strong_gens_slp[g]
  1014. for s in slp:
  1015. product.extend(self.generator_product(s, original=True))
  1016. return product
  1017. elif g**-1 in self.strong_gens:
  1018. g = g**-1
  1019. if not original or g in self.generators:
  1020. return [g**-1]
  1021. else:
  1022. slp = self._strong_gens_slp[g]
  1023. for s in slp:
  1024. product.extend(self.generator_product(s, original=True))
  1025. l = len(product)
  1026. product = [product[l-i-1]**-1 for i in range(l)]
  1027. return product
  1028. f = self.coset_factor(g, True)
  1029. for i, j in enumerate(f):
  1030. slp = self._transversal_slp[i][j]
  1031. for s in slp:
  1032. if not original:
  1033. product.append(self.strong_gens[s])
  1034. else:
  1035. s = self.strong_gens[s]
  1036. product.extend(self.generator_product(s, original=True))
  1037. return product
  1038. def coset_rank(self, g):
  1039. """rank using Schreier-Sims representation.
  1040. Explanation
  1041. ===========
  1042. The coset rank of ``g`` is the ordering number in which
  1043. it appears in the lexicographic listing according to the
  1044. coset decomposition
  1045. The ordering is the same as in G.generate(method='coset').
  1046. If ``g`` does not belong to the group it returns None.
  1047. Examples
  1048. ========
  1049. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1050. >>> a = Permutation(0, 1, 3, 7, 6, 4)(2, 5)
  1051. >>> b = Permutation(0, 1, 3, 2)(4, 5, 7, 6)
  1052. >>> G = PermutationGroup([a, b])
  1053. >>> c = Permutation(7)(2, 4)(3, 5)
  1054. >>> G.coset_rank(c)
  1055. 16
  1056. >>> G.coset_unrank(16)
  1057. (7)(2 4)(3 5)
  1058. See Also
  1059. ========
  1060. coset_factor
  1061. """
  1062. factors = self.coset_factor(g, True)
  1063. if not factors:
  1064. return None
  1065. rank = 0
  1066. b = 1
  1067. transversals = self._transversals
  1068. base = self._base
  1069. basic_orbits = self._basic_orbits
  1070. for i in range(len(base)):
  1071. k = factors[i]
  1072. j = basic_orbits[i].index(k)
  1073. rank += b*j
  1074. b = b*len(transversals[i])
  1075. return rank
  1076. def coset_unrank(self, rank, af=False):
  1077. """unrank using Schreier-Sims representation
  1078. coset_unrank is the inverse operation of coset_rank
  1079. if 0 <= rank < order; otherwise it returns None.
  1080. """
  1081. if rank < 0 or rank >= self.order():
  1082. return None
  1083. base = self.base
  1084. transversals = self.basic_transversals
  1085. basic_orbits = self.basic_orbits
  1086. m = len(base)
  1087. v = [0]*m
  1088. for i in range(m):
  1089. rank, c = divmod(rank, len(transversals[i]))
  1090. v[i] = basic_orbits[i][c]
  1091. a = [transversals[i][v[i]]._array_form for i in range(m)]
  1092. h = _af_rmuln(*a)
  1093. if af:
  1094. return h
  1095. else:
  1096. return _af_new(h)
  1097. @property
  1098. def degree(self):
  1099. """Returns the size of the permutations in the group.
  1100. Explanation
  1101. ===========
  1102. The number of permutations comprising the group is given by
  1103. ``len(group)``; the number of permutations that can be generated
  1104. by the group is given by ``group.order()``.
  1105. Examples
  1106. ========
  1107. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1108. >>> a = Permutation([1, 0, 2])
  1109. >>> G = PermutationGroup([a])
  1110. >>> G.degree
  1111. 3
  1112. >>> len(G)
  1113. 1
  1114. >>> G.order()
  1115. 2
  1116. >>> list(G.generate())
  1117. [(2), (2)(0 1)]
  1118. See Also
  1119. ========
  1120. order
  1121. """
  1122. return self._degree
  1123. @property
  1124. def identity(self):
  1125. '''
  1126. Return the identity element of the permutation group.
  1127. '''
  1128. return _af_new(list(range(self.degree)))
  1129. @property
  1130. def elements(self):
  1131. """Returns all the elements of the permutation group as a set
  1132. Examples
  1133. ========
  1134. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1135. >>> p = PermutationGroup(Permutation(1, 3), Permutation(1, 2))
  1136. >>> p.elements
  1137. {(1 2 3), (1 3 2), (1 3), (2 3), (3), (3)(1 2)}
  1138. """
  1139. return set(self._elements)
  1140. @property
  1141. def _elements(self):
  1142. """Returns all the elements of the permutation group as a list
  1143. Examples
  1144. ========
  1145. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1146. >>> p = PermutationGroup(Permutation(1, 3), Permutation(1, 2))
  1147. >>> p._elements
  1148. [(3), (3)(1 2), (1 3), (2 3), (1 2 3), (1 3 2)]
  1149. """
  1150. return list(islice(self.generate(), None))
  1151. def derived_series(self):
  1152. r"""Return the derived series for the group.
  1153. Explanation
  1154. ===========
  1155. The derived series for a group `G` is defined as
  1156. `G = G_0 > G_1 > G_2 > \ldots` where `G_i = [G_{i-1}, G_{i-1}]`,
  1157. i.e. `G_i` is the derived subgroup of `G_{i-1}`, for
  1158. `i\in\mathbb{N}`. When we have `G_k = G_{k-1}` for some
  1159. `k\in\mathbb{N}`, the series terminates.
  1160. Returns
  1161. =======
  1162. A list of permutation groups containing the members of the derived
  1163. series in the order `G = G_0, G_1, G_2, \ldots`.
  1164. Examples
  1165. ========
  1166. >>> from sympy.combinatorics.named_groups import (SymmetricGroup,
  1167. ... AlternatingGroup, DihedralGroup)
  1168. >>> A = AlternatingGroup(5)
  1169. >>> len(A.derived_series())
  1170. 1
  1171. >>> S = SymmetricGroup(4)
  1172. >>> len(S.derived_series())
  1173. 4
  1174. >>> S.derived_series()[1].is_subgroup(AlternatingGroup(4))
  1175. True
  1176. >>> S.derived_series()[2].is_subgroup(DihedralGroup(2))
  1177. True
  1178. See Also
  1179. ========
  1180. derived_subgroup
  1181. """
  1182. res = [self]
  1183. current = self
  1184. nxt = self.derived_subgroup()
  1185. while not current.is_subgroup(nxt):
  1186. res.append(nxt)
  1187. current = nxt
  1188. nxt = nxt.derived_subgroup()
  1189. return res
  1190. def derived_subgroup(self):
  1191. r"""Compute the derived subgroup.
  1192. Explanation
  1193. ===========
  1194. The derived subgroup, or commutator subgroup is the subgroup generated
  1195. by all commutators `[g, h] = hgh^{-1}g^{-1}` for `g, h\in G` ; it is
  1196. equal to the normal closure of the set of commutators of the generators
  1197. ([1], p.28, [11]).
  1198. Examples
  1199. ========
  1200. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1201. >>> a = Permutation([1, 0, 2, 4, 3])
  1202. >>> b = Permutation([0, 1, 3, 2, 4])
  1203. >>> G = PermutationGroup([a, b])
  1204. >>> C = G.derived_subgroup()
  1205. >>> list(C.generate(af=True))
  1206. [[0, 1, 2, 3, 4], [0, 1, 3, 4, 2], [0, 1, 4, 2, 3]]
  1207. See Also
  1208. ========
  1209. derived_series
  1210. """
  1211. r = self._r
  1212. gens = [p._array_form for p in self.generators]
  1213. set_commutators = set()
  1214. degree = self._degree
  1215. rng = list(range(degree))
  1216. for i in range(r):
  1217. for j in range(r):
  1218. p1 = gens[i]
  1219. p2 = gens[j]
  1220. c = list(range(degree))
  1221. for k in rng:
  1222. c[p2[p1[k]]] = p1[p2[k]]
  1223. ct = tuple(c)
  1224. if ct not in set_commutators:
  1225. set_commutators.add(ct)
  1226. cms = [_af_new(p) for p in set_commutators]
  1227. G2 = self.normal_closure(cms)
  1228. return G2
  1229. def generate(self, method="coset", af=False):
  1230. """Return iterator to generate the elements of the group.
  1231. Explanation
  1232. ===========
  1233. Iteration is done with one of these methods::
  1234. method='coset' using the Schreier-Sims coset representation
  1235. method='dimino' using the Dimino method
  1236. If ``af = True`` it yields the array form of the permutations
  1237. Examples
  1238. ========
  1239. >>> from sympy.combinatorics import PermutationGroup
  1240. >>> from sympy.combinatorics.polyhedron import tetrahedron
  1241. The permutation group given in the tetrahedron object is also
  1242. true groups:
  1243. >>> G = tetrahedron.pgroup
  1244. >>> G.is_group
  1245. True
  1246. Also the group generated by the permutations in the tetrahedron
  1247. pgroup -- even the first two -- is a proper group:
  1248. >>> H = PermutationGroup(G[0], G[1])
  1249. >>> J = PermutationGroup(list(H.generate())); J
  1250. PermutationGroup([
  1251. (0 1)(2 3),
  1252. (1 2 3),
  1253. (1 3 2),
  1254. (0 3 1),
  1255. (0 2 3),
  1256. (0 3)(1 2),
  1257. (0 1 3),
  1258. (3)(0 2 1),
  1259. (0 3 2),
  1260. (3)(0 1 2),
  1261. (0 2)(1 3)])
  1262. >>> _.is_group
  1263. True
  1264. """
  1265. if method == "coset":
  1266. return self.generate_schreier_sims(af)
  1267. elif method == "dimino":
  1268. return self.generate_dimino(af)
  1269. else:
  1270. raise NotImplementedError('No generation defined for %s' % method)
  1271. def generate_dimino(self, af=False):
  1272. """Yield group elements using Dimino's algorithm.
  1273. If ``af == True`` it yields the array form of the permutations.
  1274. Examples
  1275. ========
  1276. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1277. >>> a = Permutation([0, 2, 1, 3])
  1278. >>> b = Permutation([0, 2, 3, 1])
  1279. >>> g = PermutationGroup([a, b])
  1280. >>> list(g.generate_dimino(af=True))
  1281. [[0, 1, 2, 3], [0, 2, 1, 3], [0, 2, 3, 1],
  1282. [0, 1, 3, 2], [0, 3, 2, 1], [0, 3, 1, 2]]
  1283. References
  1284. ==========
  1285. .. [1] The Implementation of Various Algorithms for Permutation Groups in
  1286. the Computer Algebra System: AXIOM, N.J. Doye, M.Sc. Thesis
  1287. """
  1288. idn = list(range(self.degree))
  1289. order = 0
  1290. element_list = [idn]
  1291. set_element_list = {tuple(idn)}
  1292. if af:
  1293. yield idn
  1294. else:
  1295. yield _af_new(idn)
  1296. gens = [p._array_form for p in self.generators]
  1297. for i in range(len(gens)):
  1298. # D elements of the subgroup G_i generated by gens[:i]
  1299. D = element_list[:]
  1300. N = [idn]
  1301. while N:
  1302. A = N
  1303. N = []
  1304. for a in A:
  1305. for g in gens[:i + 1]:
  1306. ag = _af_rmul(a, g)
  1307. if tuple(ag) not in set_element_list:
  1308. # produce G_i*g
  1309. for d in D:
  1310. order += 1
  1311. ap = _af_rmul(d, ag)
  1312. if af:
  1313. yield ap
  1314. else:
  1315. p = _af_new(ap)
  1316. yield p
  1317. element_list.append(ap)
  1318. set_element_list.add(tuple(ap))
  1319. N.append(ap)
  1320. self._order = len(element_list)
  1321. def generate_schreier_sims(self, af=False):
  1322. """Yield group elements using the Schreier-Sims representation
  1323. in coset_rank order
  1324. If ``af = True`` it yields the array form of the permutations
  1325. Examples
  1326. ========
  1327. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1328. >>> a = Permutation([0, 2, 1, 3])
  1329. >>> b = Permutation([0, 2, 3, 1])
  1330. >>> g = PermutationGroup([a, b])
  1331. >>> list(g.generate_schreier_sims(af=True))
  1332. [[0, 1, 2, 3], [0, 2, 1, 3], [0, 3, 2, 1],
  1333. [0, 1, 3, 2], [0, 2, 3, 1], [0, 3, 1, 2]]
  1334. """
  1335. n = self._degree
  1336. u = self.basic_transversals
  1337. basic_orbits = self._basic_orbits
  1338. if len(u) == 0:
  1339. for x in self.generators:
  1340. if af:
  1341. yield x._array_form
  1342. else:
  1343. yield x
  1344. return
  1345. if len(u) == 1:
  1346. for i in basic_orbits[0]:
  1347. if af:
  1348. yield u[0][i]._array_form
  1349. else:
  1350. yield u[0][i]
  1351. return
  1352. u = list(reversed(u))
  1353. basic_orbits = basic_orbits[::-1]
  1354. # stg stack of group elements
  1355. stg = [list(range(n))]
  1356. posmax = [len(x) for x in u]
  1357. n1 = len(posmax) - 1
  1358. pos = [0]*n1
  1359. h = 0
  1360. while 1:
  1361. # backtrack when finished iterating over coset
  1362. if pos[h] >= posmax[h]:
  1363. if h == 0:
  1364. return
  1365. pos[h] = 0
  1366. h -= 1
  1367. stg.pop()
  1368. continue
  1369. p = _af_rmul(u[h][basic_orbits[h][pos[h]]]._array_form, stg[-1])
  1370. pos[h] += 1
  1371. stg.append(p)
  1372. h += 1
  1373. if h == n1:
  1374. if af:
  1375. for i in basic_orbits[-1]:
  1376. p = _af_rmul(u[-1][i]._array_form, stg[-1])
  1377. yield p
  1378. else:
  1379. for i in basic_orbits[-1]:
  1380. p = _af_rmul(u[-1][i]._array_form, stg[-1])
  1381. p1 = _af_new(p)
  1382. yield p1
  1383. stg.pop()
  1384. h -= 1
  1385. @property
  1386. def generators(self):
  1387. """Returns the generators of the group.
  1388. Examples
  1389. ========
  1390. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1391. >>> a = Permutation([0, 2, 1])
  1392. >>> b = Permutation([1, 0, 2])
  1393. >>> G = PermutationGroup([a, b])
  1394. >>> G.generators
  1395. [(1 2), (2)(0 1)]
  1396. """
  1397. return self._generators
  1398. def contains(self, g, strict=True):
  1399. """Test if permutation ``g`` belong to self, ``G``.
  1400. Explanation
  1401. ===========
  1402. If ``g`` is an element of ``G`` it can be written as a product
  1403. of factors drawn from the cosets of ``G``'s stabilizers. To see
  1404. if ``g`` is one of the actual generators defining the group use
  1405. ``G.has(g)``.
  1406. If ``strict`` is not ``True``, ``g`` will be resized, if necessary,
  1407. to match the size of permutations in ``self``.
  1408. Examples
  1409. ========
  1410. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1411. >>> a = Permutation(1, 2)
  1412. >>> b = Permutation(2, 3, 1)
  1413. >>> G = PermutationGroup(a, b, degree=5)
  1414. >>> G.contains(G[0]) # trivial check
  1415. True
  1416. >>> elem = Permutation([[2, 3]], size=5)
  1417. >>> G.contains(elem)
  1418. True
  1419. >>> G.contains(Permutation(4)(0, 1, 2, 3))
  1420. False
  1421. If strict is False, a permutation will be resized, if
  1422. necessary:
  1423. >>> H = PermutationGroup(Permutation(5))
  1424. >>> H.contains(Permutation(3))
  1425. False
  1426. >>> H.contains(Permutation(3), strict=False)
  1427. True
  1428. To test if a given permutation is present in the group:
  1429. >>> elem in G.generators
  1430. False
  1431. >>> G.has(elem)
  1432. False
  1433. See Also
  1434. ========
  1435. coset_factor, sympy.core.basic.Basic.has, __contains__
  1436. """
  1437. if not isinstance(g, Permutation):
  1438. return False
  1439. if g.size != self.degree:
  1440. if strict:
  1441. return False
  1442. g = Permutation(g, size=self.degree)
  1443. if g in self.generators:
  1444. return True
  1445. return bool(self.coset_factor(g.array_form, True))
  1446. @property
  1447. def is_perfect(self):
  1448. """Return ``True`` if the group is perfect.
  1449. A group is perfect if it equals to its derived subgroup.
  1450. Examples
  1451. ========
  1452. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1453. >>> a = Permutation(1,2,3)(4,5)
  1454. >>> b = Permutation(1,2,3,4,5)
  1455. >>> G = PermutationGroup([a, b])
  1456. >>> G.is_perfect
  1457. False
  1458. """
  1459. if self._is_perfect is None:
  1460. self._is_perfect = self.equals(self.derived_subgroup())
  1461. return self._is_perfect
  1462. @property
  1463. def is_abelian(self):
  1464. """Test if the group is Abelian.
  1465. Examples
  1466. ========
  1467. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1468. >>> a = Permutation([0, 2, 1])
  1469. >>> b = Permutation([1, 0, 2])
  1470. >>> G = PermutationGroup([a, b])
  1471. >>> G.is_abelian
  1472. False
  1473. >>> a = Permutation([0, 2, 1])
  1474. >>> G = PermutationGroup([a])
  1475. >>> G.is_abelian
  1476. True
  1477. """
  1478. if self._is_abelian is not None:
  1479. return self._is_abelian
  1480. self._is_abelian = True
  1481. gens = [p._array_form for p in self.generators]
  1482. for x in gens:
  1483. for y in gens:
  1484. if y <= x:
  1485. continue
  1486. if not _af_commutes_with(x, y):
  1487. self._is_abelian = False
  1488. return False
  1489. return True
  1490. def abelian_invariants(self):
  1491. """
  1492. Returns the abelian invariants for the given group.
  1493. Let ``G`` be a nontrivial finite abelian group. Then G is isomorphic to
  1494. the direct product of finitely many nontrivial cyclic groups of
  1495. prime-power order.
  1496. Explanation
  1497. ===========
  1498. The prime-powers that occur as the orders of the factors are uniquely
  1499. determined by G. More precisely, the primes that occur in the orders of the
  1500. factors in any such decomposition of ``G`` are exactly the primes that divide
  1501. ``|G|`` and for any such prime ``p``, if the orders of the factors that are
  1502. p-groups in one such decomposition of ``G`` are ``p^{t_1} >= p^{t_2} >= ... p^{t_r}``,
  1503. then the orders of the factors that are p-groups in any such decomposition of ``G``
  1504. are ``p^{t_1} >= p^{t_2} >= ... p^{t_r}``.
  1505. The uniquely determined integers ``p^{t_1} >= p^{t_2} >= ... p^{t_r}``, taken
  1506. for all primes that divide ``|G|`` are called the invariants of the nontrivial
  1507. group ``G`` as suggested in ([14], p. 542).
  1508. Notes
  1509. =====
  1510. We adopt the convention that the invariants of a trivial group are [].
  1511. Examples
  1512. ========
  1513. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1514. >>> a = Permutation([0, 2, 1])
  1515. >>> b = Permutation([1, 0, 2])
  1516. >>> G = PermutationGroup([a, b])
  1517. >>> G.abelian_invariants()
  1518. [2]
  1519. >>> from sympy.combinatorics import CyclicGroup
  1520. >>> G = CyclicGroup(7)
  1521. >>> G.abelian_invariants()
  1522. [7]
  1523. """
  1524. if self.is_trivial:
  1525. return []
  1526. gns = self.generators
  1527. inv = []
  1528. G = self
  1529. H = G.derived_subgroup()
  1530. Hgens = H.generators
  1531. for p in primefactors(G.order()):
  1532. ranks = []
  1533. while True:
  1534. pows = []
  1535. for g in gns:
  1536. elm = g**p
  1537. if not H.contains(elm):
  1538. pows.append(elm)
  1539. K = PermutationGroup(Hgens + pows) if pows else H
  1540. r = G.order()//K.order()
  1541. G = K
  1542. gns = pows
  1543. if r == 1:
  1544. break
  1545. ranks.append(multiplicity(p, r))
  1546. if ranks:
  1547. pows = [1]*ranks[0]
  1548. for i in ranks:
  1549. for j in range(i):
  1550. pows[j] = pows[j]*p
  1551. inv.extend(pows)
  1552. inv.sort()
  1553. return inv
  1554. def is_elementary(self, p):
  1555. """Return ``True`` if the group is elementary abelian. An elementary
  1556. abelian group is a finite abelian group, where every nontrivial
  1557. element has order `p`, where `p` is a prime.
  1558. Examples
  1559. ========
  1560. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1561. >>> a = Permutation([0, 2, 1])
  1562. >>> G = PermutationGroup([a])
  1563. >>> G.is_elementary(2)
  1564. True
  1565. >>> a = Permutation([0, 2, 1, 3])
  1566. >>> b = Permutation([3, 1, 2, 0])
  1567. >>> G = PermutationGroup([a, b])
  1568. >>> G.is_elementary(2)
  1569. True
  1570. >>> G.is_elementary(3)
  1571. False
  1572. """
  1573. return self.is_abelian and all(g.order() == p for g in self.generators)
  1574. def _eval_is_alt_sym_naive(self, only_sym=False, only_alt=False):
  1575. """A naive test using the group order."""
  1576. if only_sym and only_alt:
  1577. raise ValueError(
  1578. "Both {} and {} cannot be set to True"
  1579. .format(only_sym, only_alt))
  1580. n = self.degree
  1581. sym_order = _factorial(n)
  1582. order = self.order()
  1583. if order == sym_order:
  1584. self._is_sym = True
  1585. self._is_alt = False
  1586. if only_alt:
  1587. return False
  1588. return True
  1589. elif 2*order == sym_order:
  1590. self._is_sym = False
  1591. self._is_alt = True
  1592. if only_sym:
  1593. return False
  1594. return True
  1595. return False
  1596. def _eval_is_alt_sym_monte_carlo(self, eps=0.05, perms=None):
  1597. """A test using monte-carlo algorithm.
  1598. Parameters
  1599. ==========
  1600. eps : float, optional
  1601. The criterion for the incorrect ``False`` return.
  1602. perms : list[Permutation], optional
  1603. If explicitly given, it tests over the given candidates
  1604. for testing.
  1605. If ``None``, it randomly computes ``N_eps`` and chooses
  1606. ``N_eps`` sample of the permutation from the group.
  1607. See Also
  1608. ========
  1609. _check_cycles_alt_sym
  1610. """
  1611. if perms is None:
  1612. n = self.degree
  1613. if n < 17:
  1614. c_n = 0.34
  1615. else:
  1616. c_n = 0.57
  1617. d_n = (c_n*log(2))/log(n)
  1618. N_eps = int(-log(eps)/d_n)
  1619. perms = (self.random_pr() for i in range(N_eps))
  1620. return self._eval_is_alt_sym_monte_carlo(perms=perms)
  1621. for perm in perms:
  1622. if _check_cycles_alt_sym(perm):
  1623. return True
  1624. return False
  1625. def is_alt_sym(self, eps=0.05, _random_prec=None):
  1626. r"""Monte Carlo test for the symmetric/alternating group for degrees
  1627. >= 8.
  1628. Explanation
  1629. ===========
  1630. More specifically, it is one-sided Monte Carlo with the
  1631. answer True (i.e., G is symmetric/alternating) guaranteed to be
  1632. correct, and the answer False being incorrect with probability eps.
  1633. For degree < 8, the order of the group is checked so the test
  1634. is deterministic.
  1635. Notes
  1636. =====
  1637. The algorithm itself uses some nontrivial results from group theory and
  1638. number theory:
  1639. 1) If a transitive group ``G`` of degree ``n`` contains an element
  1640. with a cycle of length ``n/2 < p < n-2`` for ``p`` a prime, ``G`` is the
  1641. symmetric or alternating group ([1], pp. 81-82)
  1642. 2) The proportion of elements in the symmetric/alternating group having
  1643. the property described in 1) is approximately `\log(2)/\log(n)`
  1644. ([1], p.82; [2], pp. 226-227).
  1645. The helper function ``_check_cycles_alt_sym`` is used to
  1646. go over the cycles in a permutation and look for ones satisfying 1).
  1647. Examples
  1648. ========
  1649. >>> from sympy.combinatorics.named_groups import DihedralGroup
  1650. >>> D = DihedralGroup(10)
  1651. >>> D.is_alt_sym()
  1652. False
  1653. See Also
  1654. ========
  1655. _check_cycles_alt_sym
  1656. """
  1657. if _random_prec is not None:
  1658. N_eps = _random_prec['N_eps']
  1659. perms= (_random_prec[i] for i in range(N_eps))
  1660. return self._eval_is_alt_sym_monte_carlo(perms=perms)
  1661. if self._is_sym or self._is_alt:
  1662. return True
  1663. if self._is_sym is False and self._is_alt is False:
  1664. return False
  1665. n = self.degree
  1666. if n < 8:
  1667. return self._eval_is_alt_sym_naive()
  1668. elif self.is_transitive():
  1669. return self._eval_is_alt_sym_monte_carlo(eps=eps)
  1670. self._is_sym, self._is_alt = False, False
  1671. return False
  1672. @property
  1673. def is_nilpotent(self):
  1674. """Test if the group is nilpotent.
  1675. Explanation
  1676. ===========
  1677. A group `G` is nilpotent if it has a central series of finite length.
  1678. Alternatively, `G` is nilpotent if its lower central series terminates
  1679. with the trivial group. Every nilpotent group is also solvable
  1680. ([1], p.29, [12]).
  1681. Examples
  1682. ========
  1683. >>> from sympy.combinatorics.named_groups import (SymmetricGroup,
  1684. ... CyclicGroup)
  1685. >>> C = CyclicGroup(6)
  1686. >>> C.is_nilpotent
  1687. True
  1688. >>> S = SymmetricGroup(5)
  1689. >>> S.is_nilpotent
  1690. False
  1691. See Also
  1692. ========
  1693. lower_central_series, is_solvable
  1694. """
  1695. if self._is_nilpotent is None:
  1696. lcs = self.lower_central_series()
  1697. terminator = lcs[len(lcs) - 1]
  1698. gens = terminator.generators
  1699. degree = self.degree
  1700. identity = _af_new(list(range(degree)))
  1701. if all(g == identity for g in gens):
  1702. self._is_solvable = True
  1703. self._is_nilpotent = True
  1704. return True
  1705. else:
  1706. self._is_nilpotent = False
  1707. return False
  1708. else:
  1709. return self._is_nilpotent
  1710. def is_normal(self, gr, strict=True):
  1711. """Test if ``G=self`` is a normal subgroup of ``gr``.
  1712. Explanation
  1713. ===========
  1714. G is normal in gr if
  1715. for each g2 in G, g1 in gr, ``g = g1*g2*g1**-1`` belongs to G
  1716. It is sufficient to check this for each g1 in gr.generators and
  1717. g2 in G.generators.
  1718. Examples
  1719. ========
  1720. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1721. >>> a = Permutation([1, 2, 0])
  1722. >>> b = Permutation([1, 0, 2])
  1723. >>> G = PermutationGroup([a, b])
  1724. >>> G1 = PermutationGroup([a, Permutation([2, 0, 1])])
  1725. >>> G1.is_normal(G)
  1726. True
  1727. """
  1728. if not self.is_subgroup(gr, strict=strict):
  1729. return False
  1730. d_self = self.degree
  1731. d_gr = gr.degree
  1732. if self.is_trivial and (d_self == d_gr or not strict):
  1733. return True
  1734. if self._is_abelian:
  1735. return True
  1736. new_self = self.copy()
  1737. if not strict and d_self != d_gr:
  1738. if d_self < d_gr:
  1739. new_self = PermGroup(new_self.generators + [Permutation(d_gr - 1)])
  1740. else:
  1741. gr = PermGroup(gr.generators + [Permutation(d_self - 1)])
  1742. gens2 = [p._array_form for p in new_self.generators]
  1743. gens1 = [p._array_form for p in gr.generators]
  1744. for g1 in gens1:
  1745. for g2 in gens2:
  1746. p = _af_rmuln(g1, g2, _af_invert(g1))
  1747. if not new_self.coset_factor(p, True):
  1748. return False
  1749. return True
  1750. def is_primitive(self, randomized=True):
  1751. r"""Test if a group is primitive.
  1752. Explanation
  1753. ===========
  1754. A permutation group ``G`` acting on a set ``S`` is called primitive if
  1755. ``S`` contains no nontrivial block under the action of ``G``
  1756. (a block is nontrivial if its cardinality is more than ``1``).
  1757. Notes
  1758. =====
  1759. The algorithm is described in [1], p.83, and uses the function
  1760. minimal_block to search for blocks of the form `\{0, k\}` for ``k``
  1761. ranging over representatives for the orbits of `G_0`, the stabilizer of
  1762. ``0``. This algorithm has complexity `O(n^2)` where ``n`` is the degree
  1763. of the group, and will perform badly if `G_0` is small.
  1764. There are two implementations offered: one finds `G_0`
  1765. deterministically using the function ``stabilizer``, and the other
  1766. (default) produces random elements of `G_0` using ``random_stab``,
  1767. hoping that they generate a subgroup of `G_0` with not too many more
  1768. orbits than `G_0` (this is suggested in [1], p.83). Behavior is changed
  1769. by the ``randomized`` flag.
  1770. Examples
  1771. ========
  1772. >>> from sympy.combinatorics.named_groups import DihedralGroup
  1773. >>> D = DihedralGroup(10)
  1774. >>> D.is_primitive()
  1775. False
  1776. See Also
  1777. ========
  1778. minimal_block, random_stab
  1779. """
  1780. if self._is_primitive is not None:
  1781. return self._is_primitive
  1782. if self.is_transitive() is False:
  1783. return False
  1784. if randomized:
  1785. random_stab_gens = []
  1786. v = self.schreier_vector(0)
  1787. for _ in range(len(self)):
  1788. random_stab_gens.append(self.random_stab(0, v))
  1789. stab = PermutationGroup(random_stab_gens)
  1790. else:
  1791. stab = self.stabilizer(0)
  1792. orbits = stab.orbits()
  1793. for orb in orbits:
  1794. x = orb.pop()
  1795. if x != 0 and any(e != 0 for e in self.minimal_block([0, x])):
  1796. self._is_primitive = False
  1797. return False
  1798. self._is_primitive = True
  1799. return True
  1800. def minimal_blocks(self, randomized=True):
  1801. '''
  1802. For a transitive group, return the list of all minimal
  1803. block systems. If a group is intransitive, return `False`.
  1804. Examples
  1805. ========
  1806. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1807. >>> from sympy.combinatorics.named_groups import DihedralGroup
  1808. >>> DihedralGroup(6).minimal_blocks()
  1809. [[0, 1, 0, 1, 0, 1], [0, 1, 2, 0, 1, 2]]
  1810. >>> G = PermutationGroup(Permutation(1,2,5))
  1811. >>> G.minimal_blocks()
  1812. False
  1813. See Also
  1814. ========
  1815. minimal_block, is_transitive, is_primitive
  1816. '''
  1817. def _number_blocks(blocks):
  1818. # number the blocks of a block system
  1819. # in order and return the number of
  1820. # blocks and the tuple with the
  1821. # reordering
  1822. n = len(blocks)
  1823. appeared = {}
  1824. m = 0
  1825. b = [None]*n
  1826. for i in range(n):
  1827. if blocks[i] not in appeared:
  1828. appeared[blocks[i]] = m
  1829. b[i] = m
  1830. m += 1
  1831. else:
  1832. b[i] = appeared[blocks[i]]
  1833. return tuple(b), m
  1834. if not self.is_transitive():
  1835. return False
  1836. blocks = []
  1837. num_blocks = []
  1838. rep_blocks = []
  1839. if randomized:
  1840. random_stab_gens = []
  1841. v = self.schreier_vector(0)
  1842. for i in range(len(self)):
  1843. random_stab_gens.append(self.random_stab(0, v))
  1844. stab = PermutationGroup(random_stab_gens)
  1845. else:
  1846. stab = self.stabilizer(0)
  1847. orbits = stab.orbits()
  1848. for orb in orbits:
  1849. x = orb.pop()
  1850. if x != 0:
  1851. block = self.minimal_block([0, x])
  1852. num_block, _ = _number_blocks(block)
  1853. # a representative block (containing 0)
  1854. rep = {j for j in range(self.degree) if num_block[j] == 0}
  1855. # check if the system is minimal with
  1856. # respect to the already discovere ones
  1857. minimal = True
  1858. blocks_remove_mask = [False] * len(blocks)
  1859. for i, r in enumerate(rep_blocks):
  1860. if len(r) > len(rep) and rep.issubset(r):
  1861. # i-th block system is not minimal
  1862. blocks_remove_mask[i] = True
  1863. elif len(r) < len(rep) and r.issubset(rep):
  1864. # the system being checked is not minimal
  1865. minimal = False
  1866. break
  1867. # remove non-minimal representative blocks
  1868. blocks = [b for i, b in enumerate(blocks) if not blocks_remove_mask[i]]
  1869. num_blocks = [n for i, n in enumerate(num_blocks) if not blocks_remove_mask[i]]
  1870. rep_blocks = [r for i, r in enumerate(rep_blocks) if not blocks_remove_mask[i]]
  1871. if minimal and num_block not in num_blocks:
  1872. blocks.append(block)
  1873. num_blocks.append(num_block)
  1874. rep_blocks.append(rep)
  1875. return blocks
  1876. @property
  1877. def is_solvable(self):
  1878. """Test if the group is solvable.
  1879. ``G`` is solvable if its derived series terminates with the trivial
  1880. group ([1], p.29).
  1881. Examples
  1882. ========
  1883. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  1884. >>> S = SymmetricGroup(3)
  1885. >>> S.is_solvable
  1886. True
  1887. See Also
  1888. ========
  1889. is_nilpotent, derived_series
  1890. """
  1891. if self._is_solvable is None:
  1892. if self.order() % 2 != 0:
  1893. return True
  1894. ds = self.derived_series()
  1895. terminator = ds[len(ds) - 1]
  1896. gens = terminator.generators
  1897. degree = self.degree
  1898. identity = _af_new(list(range(degree)))
  1899. if all(g == identity for g in gens):
  1900. self._is_solvable = True
  1901. return True
  1902. else:
  1903. self._is_solvable = False
  1904. return False
  1905. else:
  1906. return self._is_solvable
  1907. def is_subgroup(self, G, strict=True):
  1908. """Return ``True`` if all elements of ``self`` belong to ``G``.
  1909. If ``strict`` is ``False`` then if ``self``'s degree is smaller
  1910. than ``G``'s, the elements will be resized to have the same degree.
  1911. Examples
  1912. ========
  1913. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1914. >>> from sympy.combinatorics import SymmetricGroup, CyclicGroup
  1915. Testing is strict by default: the degree of each group must be the
  1916. same:
  1917. >>> p = Permutation(0, 1, 2, 3, 4, 5)
  1918. >>> G1 = PermutationGroup([Permutation(0, 1, 2), Permutation(0, 1)])
  1919. >>> G2 = PermutationGroup([Permutation(0, 2), Permutation(0, 1, 2)])
  1920. >>> G3 = PermutationGroup([p, p**2])
  1921. >>> assert G1.order() == G2.order() == G3.order() == 6
  1922. >>> G1.is_subgroup(G2)
  1923. True
  1924. >>> G1.is_subgroup(G3)
  1925. False
  1926. >>> G3.is_subgroup(PermutationGroup(G3[1]))
  1927. False
  1928. >>> G3.is_subgroup(PermutationGroup(G3[0]))
  1929. True
  1930. To ignore the size, set ``strict`` to ``False``:
  1931. >>> S3 = SymmetricGroup(3)
  1932. >>> S5 = SymmetricGroup(5)
  1933. >>> S3.is_subgroup(S5, strict=False)
  1934. True
  1935. >>> C7 = CyclicGroup(7)
  1936. >>> G = S5*C7
  1937. >>> S5.is_subgroup(G, False)
  1938. True
  1939. >>> C7.is_subgroup(G, 0)
  1940. False
  1941. """
  1942. if isinstance(G, SymmetricPermutationGroup):
  1943. if self.degree != G.degree:
  1944. return False
  1945. return True
  1946. if not isinstance(G, PermutationGroup):
  1947. return False
  1948. if self == G or self.generators[0]==Permutation():
  1949. return True
  1950. if G.order() % self.order() != 0:
  1951. return False
  1952. if self.degree == G.degree or \
  1953. (self.degree < G.degree and not strict):
  1954. gens = self.generators
  1955. else:
  1956. return False
  1957. return all(G.contains(g, strict=strict) for g in gens)
  1958. @property
  1959. def is_polycyclic(self):
  1960. """Return ``True`` if a group is polycyclic. A group is polycyclic if
  1961. it has a subnormal series with cyclic factors. For finite groups,
  1962. this is the same as if the group is solvable.
  1963. Examples
  1964. ========
  1965. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1966. >>> a = Permutation([0, 2, 1, 3])
  1967. >>> b = Permutation([2, 0, 1, 3])
  1968. >>> G = PermutationGroup([a, b])
  1969. >>> G.is_polycyclic
  1970. True
  1971. """
  1972. return self.is_solvable
  1973. def is_transitive(self, strict=True):
  1974. """Test if the group is transitive.
  1975. Explanation
  1976. ===========
  1977. A group is transitive if it has a single orbit.
  1978. If ``strict`` is ``False`` the group is transitive if it has
  1979. a single orbit of length different from 1.
  1980. Examples
  1981. ========
  1982. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1983. >>> a = Permutation([0, 2, 1, 3])
  1984. >>> b = Permutation([2, 0, 1, 3])
  1985. >>> G1 = PermutationGroup([a, b])
  1986. >>> G1.is_transitive()
  1987. False
  1988. >>> G1.is_transitive(strict=False)
  1989. True
  1990. >>> c = Permutation([2, 3, 0, 1])
  1991. >>> G2 = PermutationGroup([a, c])
  1992. >>> G2.is_transitive()
  1993. True
  1994. >>> d = Permutation([1, 0, 2, 3])
  1995. >>> e = Permutation([0, 1, 3, 2])
  1996. >>> G3 = PermutationGroup([d, e])
  1997. >>> G3.is_transitive() or G3.is_transitive(strict=False)
  1998. False
  1999. """
  2000. if self._is_transitive: # strict or not, if True then True
  2001. return self._is_transitive
  2002. if strict:
  2003. if self._is_transitive is not None: # we only store strict=True
  2004. return self._is_transitive
  2005. ans = len(self.orbit(0)) == self.degree
  2006. self._is_transitive = ans
  2007. return ans
  2008. got_orb = False
  2009. for x in self.orbits():
  2010. if len(x) > 1:
  2011. if got_orb:
  2012. return False
  2013. got_orb = True
  2014. return got_orb
  2015. @property
  2016. def is_trivial(self):
  2017. """Test if the group is the trivial group.
  2018. This is true if the group contains only the identity permutation.
  2019. Examples
  2020. ========
  2021. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2022. >>> G = PermutationGroup([Permutation([0, 1, 2])])
  2023. >>> G.is_trivial
  2024. True
  2025. """
  2026. if self._is_trivial is None:
  2027. self._is_trivial = len(self) == 1 and self[0].is_Identity
  2028. return self._is_trivial
  2029. def lower_central_series(self):
  2030. r"""Return the lower central series for the group.
  2031. The lower central series for a group `G` is the series
  2032. `G = G_0 > G_1 > G_2 > \ldots` where
  2033. `G_k = [G, G_{k-1}]`, i.e. every term after the first is equal to the
  2034. commutator of `G` and the previous term in `G1` ([1], p.29).
  2035. Returns
  2036. =======
  2037. A list of permutation groups in the order `G = G_0, G_1, G_2, \ldots`
  2038. Examples
  2039. ========
  2040. >>> from sympy.combinatorics.named_groups import (AlternatingGroup,
  2041. ... DihedralGroup)
  2042. >>> A = AlternatingGroup(4)
  2043. >>> len(A.lower_central_series())
  2044. 2
  2045. >>> A.lower_central_series()[1].is_subgroup(DihedralGroup(2))
  2046. True
  2047. See Also
  2048. ========
  2049. commutator, derived_series
  2050. """
  2051. res = [self]
  2052. current = self
  2053. nxt = self.commutator(self, current)
  2054. while not current.is_subgroup(nxt):
  2055. res.append(nxt)
  2056. current = nxt
  2057. nxt = self.commutator(self, current)
  2058. return res
  2059. @property
  2060. def max_div(self):
  2061. """Maximum proper divisor of the degree of a permutation group.
  2062. Explanation
  2063. ===========
  2064. Obviously, this is the degree divided by its minimal proper divisor
  2065. (larger than ``1``, if one exists). As it is guaranteed to be prime,
  2066. the ``sieve`` from ``sympy.ntheory`` is used.
  2067. This function is also used as an optimization tool for the functions
  2068. ``minimal_block`` and ``_union_find_merge``.
  2069. Examples
  2070. ========
  2071. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2072. >>> G = PermutationGroup([Permutation([0, 2, 1, 3])])
  2073. >>> G.max_div
  2074. 2
  2075. See Also
  2076. ========
  2077. minimal_block, _union_find_merge
  2078. """
  2079. if self._max_div is not None:
  2080. return self._max_div
  2081. n = self.degree
  2082. if n == 1:
  2083. return 1
  2084. for x in sieve:
  2085. if n % x == 0:
  2086. d = n//x
  2087. self._max_div = d
  2088. return d
  2089. def minimal_block(self, points):
  2090. r"""For a transitive group, finds the block system generated by
  2091. ``points``.
  2092. Explanation
  2093. ===========
  2094. If a group ``G`` acts on a set ``S``, a nonempty subset ``B`` of ``S``
  2095. is called a block under the action of ``G`` if for all ``g`` in ``G``
  2096. we have ``gB = B`` (``g`` fixes ``B``) or ``gB`` and ``B`` have no
  2097. common points (``g`` moves ``B`` entirely). ([1], p.23; [6]).
  2098. The distinct translates ``gB`` of a block ``B`` for ``g`` in ``G``
  2099. partition the set ``S`` and this set of translates is known as a block
  2100. system. Moreover, we obviously have that all blocks in the partition
  2101. have the same size, hence the block size divides ``|S|`` ([1], p.23).
  2102. A ``G``-congruence is an equivalence relation ``~`` on the set ``S``
  2103. such that ``a ~ b`` implies ``g(a) ~ g(b)`` for all ``g`` in ``G``.
  2104. For a transitive group, the equivalence classes of a ``G``-congruence
  2105. and the blocks of a block system are the same thing ([1], p.23).
  2106. The algorithm below checks the group for transitivity, and then finds
  2107. the ``G``-congruence generated by the pairs ``(p_0, p_1), (p_0, p_2),
  2108. ..., (p_0,p_{k-1})`` which is the same as finding the maximal block
  2109. system (i.e., the one with minimum block size) such that
  2110. ``p_0, ..., p_{k-1}`` are in the same block ([1], p.83).
  2111. It is an implementation of Atkinson's algorithm, as suggested in [1],
  2112. and manipulates an equivalence relation on the set ``S`` using a
  2113. union-find data structure. The running time is just above
  2114. `O(|points||S|)`. ([1], pp. 83-87; [7]).
  2115. Examples
  2116. ========
  2117. >>> from sympy.combinatorics.named_groups import DihedralGroup
  2118. >>> D = DihedralGroup(10)
  2119. >>> D.minimal_block([0, 5])
  2120. [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
  2121. >>> D.minimal_block([0, 1])
  2122. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  2123. See Also
  2124. ========
  2125. _union_find_rep, _union_find_merge, is_transitive, is_primitive
  2126. """
  2127. if not self.is_transitive():
  2128. return False
  2129. n = self.degree
  2130. gens = self.generators
  2131. # initialize the list of equivalence class representatives
  2132. parents = list(range(n))
  2133. ranks = [1]*n
  2134. not_rep = []
  2135. k = len(points)
  2136. # the block size must divide the degree of the group
  2137. if k > self.max_div:
  2138. return [0]*n
  2139. for i in range(k - 1):
  2140. parents[points[i + 1]] = points[0]
  2141. not_rep.append(points[i + 1])
  2142. ranks[points[0]] = k
  2143. i = 0
  2144. len_not_rep = k - 1
  2145. while i < len_not_rep:
  2146. gamma = not_rep[i]
  2147. i += 1
  2148. for gen in gens:
  2149. # find has side effects: performs path compression on the list
  2150. # of representatives
  2151. delta = self._union_find_rep(gamma, parents)
  2152. # union has side effects: performs union by rank on the list
  2153. # of representatives
  2154. temp = self._union_find_merge(gen(gamma), gen(delta), ranks,
  2155. parents, not_rep)
  2156. if temp == -1:
  2157. return [0]*n
  2158. len_not_rep += temp
  2159. for i in range(n):
  2160. # force path compression to get the final state of the equivalence
  2161. # relation
  2162. self._union_find_rep(i, parents)
  2163. # rewrite result so that block representatives are minimal
  2164. new_reps = {}
  2165. return [new_reps.setdefault(r, i) for i, r in enumerate(parents)]
  2166. def conjugacy_class(self, x):
  2167. r"""Return the conjugacy class of an element in the group.
  2168. Explanation
  2169. ===========
  2170. The conjugacy class of an element ``g`` in a group ``G`` is the set of
  2171. elements ``x`` in ``G`` that are conjugate with ``g``, i.e. for which
  2172. ``g = xax^{-1}``
  2173. for some ``a`` in ``G``.
  2174. Note that conjugacy is an equivalence relation, and therefore that
  2175. conjugacy classes are partitions of ``G``. For a list of all the
  2176. conjugacy classes of the group, use the conjugacy_classes() method.
  2177. In a permutation group, each conjugacy class corresponds to a particular
  2178. `cycle structure': for example, in ``S_3``, the conjugacy classes are:
  2179. * the identity class, ``{()}``
  2180. * all transpositions, ``{(1 2), (1 3), (2 3)}``
  2181. * all 3-cycles, ``{(1 2 3), (1 3 2)}``
  2182. Examples
  2183. ========
  2184. >>> from sympy.combinatorics import Permutation, SymmetricGroup
  2185. >>> S3 = SymmetricGroup(3)
  2186. >>> S3.conjugacy_class(Permutation(0, 1, 2))
  2187. {(0 1 2), (0 2 1)}
  2188. Notes
  2189. =====
  2190. This procedure computes the conjugacy class directly by finding the
  2191. orbit of the element under conjugation in G. This algorithm is only
  2192. feasible for permutation groups of relatively small order, but is like
  2193. the orbit() function itself in that respect.
  2194. """
  2195. # Ref: "Computing the conjugacy classes of finite groups"; Butler, G.
  2196. # Groups '93 Galway/St Andrews; edited by Campbell, C. M.
  2197. new_class = {x}
  2198. last_iteration = new_class
  2199. while len(last_iteration) > 0:
  2200. this_iteration = set()
  2201. for y in last_iteration:
  2202. for s in self.generators:
  2203. conjugated = s * y * (~s)
  2204. if conjugated not in new_class:
  2205. this_iteration.add(conjugated)
  2206. new_class.update(last_iteration)
  2207. last_iteration = this_iteration
  2208. return new_class
  2209. def conjugacy_classes(self):
  2210. r"""Return the conjugacy classes of the group.
  2211. Explanation
  2212. ===========
  2213. As described in the documentation for the .conjugacy_class() function,
  2214. conjugacy is an equivalence relation on a group G which partitions the
  2215. set of elements. This method returns a list of all these conjugacy
  2216. classes of G.
  2217. Examples
  2218. ========
  2219. >>> from sympy.combinatorics import SymmetricGroup
  2220. >>> SymmetricGroup(3).conjugacy_classes()
  2221. [{(2)}, {(0 1 2), (0 2 1)}, {(0 2), (1 2), (2)(0 1)}]
  2222. """
  2223. identity = _af_new(list(range(self.degree)))
  2224. known_elements = {identity}
  2225. classes = [known_elements.copy()]
  2226. for x in self.generate():
  2227. if x not in known_elements:
  2228. new_class = self.conjugacy_class(x)
  2229. classes.append(new_class)
  2230. known_elements.update(new_class)
  2231. return classes
  2232. def normal_closure(self, other, k=10):
  2233. r"""Return the normal closure of a subgroup/set of permutations.
  2234. Explanation
  2235. ===========
  2236. If ``S`` is a subset of a group ``G``, the normal closure of ``A`` in ``G``
  2237. is defined as the intersection of all normal subgroups of ``G`` that
  2238. contain ``A`` ([1], p.14). Alternatively, it is the group generated by
  2239. the conjugates ``x^{-1}yx`` for ``x`` a generator of ``G`` and ``y`` a
  2240. generator of the subgroup ``\left\langle S\right\rangle`` generated by
  2241. ``S`` (for some chosen generating set for ``\left\langle S\right\rangle``)
  2242. ([1], p.73).
  2243. Parameters
  2244. ==========
  2245. other
  2246. a subgroup/list of permutations/single permutation
  2247. k
  2248. an implementation-specific parameter that determines the number
  2249. of conjugates that are adjoined to ``other`` at once
  2250. Examples
  2251. ========
  2252. >>> from sympy.combinatorics.named_groups import (SymmetricGroup,
  2253. ... CyclicGroup, AlternatingGroup)
  2254. >>> S = SymmetricGroup(5)
  2255. >>> C = CyclicGroup(5)
  2256. >>> G = S.normal_closure(C)
  2257. >>> G.order()
  2258. 60
  2259. >>> G.is_subgroup(AlternatingGroup(5))
  2260. True
  2261. See Also
  2262. ========
  2263. commutator, derived_subgroup, random_pr
  2264. Notes
  2265. =====
  2266. The algorithm is described in [1], pp. 73-74; it makes use of the
  2267. generation of random elements for permutation groups by the product
  2268. replacement algorithm.
  2269. """
  2270. if hasattr(other, 'generators'):
  2271. degree = self.degree
  2272. identity = _af_new(list(range(degree)))
  2273. if all(g == identity for g in other.generators):
  2274. return other
  2275. Z = PermutationGroup(other.generators[:])
  2276. base, strong_gens = Z.schreier_sims_incremental()
  2277. strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
  2278. basic_orbits, basic_transversals = \
  2279. _orbits_transversals_from_bsgs(base, strong_gens_distr)
  2280. self._random_pr_init(r=10, n=20)
  2281. _loop = True
  2282. while _loop:
  2283. Z._random_pr_init(r=10, n=10)
  2284. for _ in range(k):
  2285. g = self.random_pr()
  2286. h = Z.random_pr()
  2287. conj = h^g
  2288. res = _strip(conj, base, basic_orbits, basic_transversals)
  2289. if res[0] != identity or res[1] != len(base) + 1:
  2290. gens = Z.generators
  2291. gens.append(conj)
  2292. Z = PermutationGroup(gens)
  2293. strong_gens.append(conj)
  2294. temp_base, temp_strong_gens = \
  2295. Z.schreier_sims_incremental(base, strong_gens)
  2296. base, strong_gens = temp_base, temp_strong_gens
  2297. strong_gens_distr = \
  2298. _distribute_gens_by_base(base, strong_gens)
  2299. basic_orbits, basic_transversals = \
  2300. _orbits_transversals_from_bsgs(base,
  2301. strong_gens_distr)
  2302. _loop = False
  2303. for g in self.generators:
  2304. for h in Z.generators:
  2305. conj = h^g
  2306. res = _strip(conj, base, basic_orbits,
  2307. basic_transversals)
  2308. if res[0] != identity or res[1] != len(base) + 1:
  2309. _loop = True
  2310. break
  2311. if _loop:
  2312. break
  2313. return Z
  2314. elif hasattr(other, '__getitem__'):
  2315. return self.normal_closure(PermutationGroup(other))
  2316. elif hasattr(other, 'array_form'):
  2317. return self.normal_closure(PermutationGroup([other]))
  2318. def orbit(self, alpha, action='tuples'):
  2319. r"""Compute the orbit of alpha `\{g(\alpha) | g \in G\}` as a set.
  2320. Explanation
  2321. ===========
  2322. The time complexity of the algorithm used here is `O(|Orb|*r)` where
  2323. `|Orb|` is the size of the orbit and ``r`` is the number of generators of
  2324. the group. For a more detailed analysis, see [1], p.78, [2], pp. 19-21.
  2325. Here alpha can be a single point, or a list of points.
  2326. If alpha is a single point, the ordinary orbit is computed.
  2327. if alpha is a list of points, there are three available options:
  2328. 'union' - computes the union of the orbits of the points in the list
  2329. 'tuples' - computes the orbit of the list interpreted as an ordered
  2330. tuple under the group action ( i.e., g((1,2,3)) = (g(1), g(2), g(3)) )
  2331. 'sets' - computes the orbit of the list interpreted as a sets
  2332. Examples
  2333. ========
  2334. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2335. >>> a = Permutation([1, 2, 0, 4, 5, 6, 3])
  2336. >>> G = PermutationGroup([a])
  2337. >>> G.orbit(0)
  2338. {0, 1, 2}
  2339. >>> G.orbit([0, 4], 'union')
  2340. {0, 1, 2, 3, 4, 5, 6}
  2341. See Also
  2342. ========
  2343. orbit_transversal
  2344. """
  2345. return _orbit(self.degree, self.generators, alpha, action)
  2346. def orbit_rep(self, alpha, beta, schreier_vector=None):
  2347. """Return a group element which sends ``alpha`` to ``beta``.
  2348. Explanation
  2349. ===========
  2350. If ``beta`` is not in the orbit of ``alpha``, the function returns
  2351. ``False``. This implementation makes use of the schreier vector.
  2352. For a proof of correctness, see [1], p.80
  2353. Examples
  2354. ========
  2355. >>> from sympy.combinatorics.named_groups import AlternatingGroup
  2356. >>> G = AlternatingGroup(5)
  2357. >>> G.orbit_rep(0, 4)
  2358. (0 4 1 2 3)
  2359. See Also
  2360. ========
  2361. schreier_vector
  2362. """
  2363. if schreier_vector is None:
  2364. schreier_vector = self.schreier_vector(alpha)
  2365. if schreier_vector[beta] is None:
  2366. return False
  2367. k = schreier_vector[beta]
  2368. gens = [x._array_form for x in self.generators]
  2369. a = []
  2370. while k != -1:
  2371. a.append(gens[k])
  2372. beta = gens[k].index(beta) # beta = (~gens[k])(beta)
  2373. k = schreier_vector[beta]
  2374. if a:
  2375. return _af_new(_af_rmuln(*a))
  2376. else:
  2377. return _af_new(list(range(self._degree)))
  2378. def orbit_transversal(self, alpha, pairs=False):
  2379. r"""Computes a transversal for the orbit of ``alpha`` as a set.
  2380. Explanation
  2381. ===========
  2382. For a permutation group `G`, a transversal for the orbit
  2383. `Orb = \{g(\alpha) | g \in G\}` is a set
  2384. `\{g_\beta | g_\beta(\alpha) = \beta\}` for `\beta \in Orb`.
  2385. Note that there may be more than one possible transversal.
  2386. If ``pairs`` is set to ``True``, it returns the list of pairs
  2387. `(\beta, g_\beta)`. For a proof of correctness, see [1], p.79
  2388. Examples
  2389. ========
  2390. >>> from sympy.combinatorics.named_groups import DihedralGroup
  2391. >>> G = DihedralGroup(6)
  2392. >>> G.orbit_transversal(0)
  2393. [(5), (0 1 2 3 4 5), (0 5)(1 4)(2 3), (0 2 4)(1 3 5), (5)(0 4)(1 3), (0 3)(1 4)(2 5)]
  2394. See Also
  2395. ========
  2396. orbit
  2397. """
  2398. return _orbit_transversal(self._degree, self.generators, alpha, pairs)
  2399. def orbits(self, rep=False):
  2400. """Return the orbits of ``self``, ordered according to lowest element
  2401. in each orbit.
  2402. Examples
  2403. ========
  2404. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2405. >>> a = Permutation(1, 5)(2, 3)(4, 0, 6)
  2406. >>> b = Permutation(1, 5)(3, 4)(2, 6, 0)
  2407. >>> G = PermutationGroup([a, b])
  2408. >>> G.orbits()
  2409. [{0, 2, 3, 4, 6}, {1, 5}]
  2410. """
  2411. return _orbits(self._degree, self._generators)
  2412. def order(self):
  2413. """Return the order of the group: the number of permutations that
  2414. can be generated from elements of the group.
  2415. The number of permutations comprising the group is given by
  2416. ``len(group)``; the length of each permutation in the group is
  2417. given by ``group.size``.
  2418. Examples
  2419. ========
  2420. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2421. >>> a = Permutation([1, 0, 2])
  2422. >>> G = PermutationGroup([a])
  2423. >>> G.degree
  2424. 3
  2425. >>> len(G)
  2426. 1
  2427. >>> G.order()
  2428. 2
  2429. >>> list(G.generate())
  2430. [(2), (2)(0 1)]
  2431. >>> a = Permutation([0, 2, 1])
  2432. >>> b = Permutation([1, 0, 2])
  2433. >>> G = PermutationGroup([a, b])
  2434. >>> G.order()
  2435. 6
  2436. See Also
  2437. ========
  2438. degree
  2439. """
  2440. if self._order is not None:
  2441. return self._order
  2442. if self._is_sym:
  2443. n = self._degree
  2444. self._order = factorial(n)
  2445. return self._order
  2446. if self._is_alt:
  2447. n = self._degree
  2448. self._order = factorial(n)/2
  2449. return self._order
  2450. m = prod([len(x) for x in self.basic_transversals])
  2451. self._order = m
  2452. return m
  2453. def index(self, H):
  2454. """
  2455. Returns the index of a permutation group.
  2456. Examples
  2457. ========
  2458. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2459. >>> a = Permutation(1,2,3)
  2460. >>> b =Permutation(3)
  2461. >>> G = PermutationGroup([a])
  2462. >>> H = PermutationGroup([b])
  2463. >>> G.index(H)
  2464. 3
  2465. """
  2466. if H.is_subgroup(self):
  2467. return self.order()//H.order()
  2468. @property
  2469. def is_symmetric(self):
  2470. """Return ``True`` if the group is symmetric.
  2471. Examples
  2472. ========
  2473. >>> from sympy.combinatorics import SymmetricGroup
  2474. >>> g = SymmetricGroup(5)
  2475. >>> g.is_symmetric
  2476. True
  2477. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2478. >>> g = PermutationGroup(
  2479. ... Permutation(0, 1, 2, 3, 4),
  2480. ... Permutation(2, 3))
  2481. >>> g.is_symmetric
  2482. True
  2483. Notes
  2484. =====
  2485. This uses a naive test involving the computation of the full
  2486. group order.
  2487. If you need more quicker taxonomy for large groups, you can use
  2488. :meth:`PermutationGroup.is_alt_sym`.
  2489. However, :meth:`PermutationGroup.is_alt_sym` may not be accurate
  2490. and is not able to distinguish between an alternating group and
  2491. a symmetric group.
  2492. See Also
  2493. ========
  2494. is_alt_sym
  2495. """
  2496. _is_sym = self._is_sym
  2497. if _is_sym is not None:
  2498. return _is_sym
  2499. n = self.degree
  2500. if n >= 8:
  2501. if self.is_transitive():
  2502. _is_alt_sym = self._eval_is_alt_sym_monte_carlo()
  2503. if _is_alt_sym:
  2504. if any(g.is_odd for g in self.generators):
  2505. self._is_sym, self._is_alt = True, False
  2506. return True
  2507. self._is_sym, self._is_alt = False, True
  2508. return False
  2509. return self._eval_is_alt_sym_naive(only_sym=True)
  2510. self._is_sym, self._is_alt = False, False
  2511. return False
  2512. return self._eval_is_alt_sym_naive(only_sym=True)
  2513. @property
  2514. def is_alternating(self):
  2515. """Return ``True`` if the group is alternating.
  2516. Examples
  2517. ========
  2518. >>> from sympy.combinatorics import AlternatingGroup
  2519. >>> g = AlternatingGroup(5)
  2520. >>> g.is_alternating
  2521. True
  2522. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2523. >>> g = PermutationGroup(
  2524. ... Permutation(0, 1, 2, 3, 4),
  2525. ... Permutation(2, 3, 4))
  2526. >>> g.is_alternating
  2527. True
  2528. Notes
  2529. =====
  2530. This uses a naive test involving the computation of the full
  2531. group order.
  2532. If you need more quicker taxonomy for large groups, you can use
  2533. :meth:`PermutationGroup.is_alt_sym`.
  2534. However, :meth:`PermutationGroup.is_alt_sym` may not be accurate
  2535. and is not able to distinguish between an alternating group and
  2536. a symmetric group.
  2537. See Also
  2538. ========
  2539. is_alt_sym
  2540. """
  2541. _is_alt = self._is_alt
  2542. if _is_alt is not None:
  2543. return _is_alt
  2544. n = self.degree
  2545. if n >= 8:
  2546. if self.is_transitive():
  2547. _is_alt_sym = self._eval_is_alt_sym_monte_carlo()
  2548. if _is_alt_sym:
  2549. if all(g.is_even for g in self.generators):
  2550. self._is_sym, self._is_alt = False, True
  2551. return True
  2552. self._is_sym, self._is_alt = True, False
  2553. return False
  2554. return self._eval_is_alt_sym_naive(only_alt=True)
  2555. self._is_sym, self._is_alt = False, False
  2556. return False
  2557. return self._eval_is_alt_sym_naive(only_alt=True)
  2558. @classmethod
  2559. def _distinct_primes_lemma(cls, primes):
  2560. """Subroutine to test if there is only one cyclic group for the
  2561. order."""
  2562. primes = sorted(primes)
  2563. l = len(primes)
  2564. for i in range(l):
  2565. for j in range(i+1, l):
  2566. if primes[j] % primes[i] == 1:
  2567. return None
  2568. return True
  2569. @property
  2570. def is_cyclic(self):
  2571. r"""
  2572. Return ``True`` if the group is Cyclic.
  2573. Examples
  2574. ========
  2575. >>> from sympy.combinatorics.named_groups import AbelianGroup
  2576. >>> G = AbelianGroup(3, 4)
  2577. >>> G.is_cyclic
  2578. True
  2579. >>> G = AbelianGroup(4, 4)
  2580. >>> G.is_cyclic
  2581. False
  2582. Notes
  2583. =====
  2584. If the order of a group $n$ can be factored into the distinct
  2585. primes $p_1, p_2, \dots , p_s$ and if
  2586. .. math::
  2587. \forall i, j \in \{1, 2, \dots, s \}:
  2588. p_i \not \equiv 1 \pmod {p_j}
  2589. holds true, there is only one group of the order $n$ which
  2590. is a cyclic group [1]_. This is a generalization of the lemma
  2591. that the group of order $15, 35, \dots$ are cyclic.
  2592. And also, these additional lemmas can be used to test if a
  2593. group is cyclic if the order of the group is already found.
  2594. - If the group is abelian and the order of the group is
  2595. square-free, the group is cyclic.
  2596. - If the order of the group is less than $6$ and is not $4$, the
  2597. group is cyclic.
  2598. - If the order of the group is prime, the group is cyclic.
  2599. References
  2600. ==========
  2601. .. [1] 1978: John S. Rose: A Course on Group Theory,
  2602. Introduction to Finite Group Theory: 1.4
  2603. """
  2604. if self._is_cyclic is not None:
  2605. return self._is_cyclic
  2606. if len(self.generators) == 1:
  2607. self._is_cyclic = True
  2608. self._is_abelian = True
  2609. return True
  2610. if self._is_abelian is False:
  2611. self._is_cyclic = False
  2612. return False
  2613. order = self.order()
  2614. if order < 6:
  2615. self._is_abelian = True
  2616. if order != 4:
  2617. self._is_cyclic = True
  2618. return True
  2619. factors = factorint(order)
  2620. if all(v == 1 for v in factors.values()):
  2621. if self._is_abelian:
  2622. self._is_cyclic = True
  2623. return True
  2624. primes = list(factors.keys())
  2625. if PermutationGroup._distinct_primes_lemma(primes) is True:
  2626. self._is_cyclic = True
  2627. self._is_abelian = True
  2628. return True
  2629. if not self.is_abelian:
  2630. self._is_cyclic = False
  2631. return False
  2632. self._is_cyclic = all(
  2633. any(g**(order//p) != self.identity for g in self.generators)
  2634. for p, e in factors.items() if e > 1
  2635. )
  2636. return self._is_cyclic
  2637. @property
  2638. def is_dihedral(self):
  2639. r"""
  2640. Return ``True`` if the group is dihedral.
  2641. Examples
  2642. ========
  2643. >>> from sympy.combinatorics.perm_groups import PermutationGroup
  2644. >>> from sympy.combinatorics.permutations import Permutation
  2645. >>> from sympy.combinatorics.named_groups import SymmetricGroup, CyclicGroup
  2646. >>> G = PermutationGroup(Permutation(1, 6)(2, 5)(3, 4), Permutation(0, 1, 2, 3, 4, 5, 6))
  2647. >>> G.is_dihedral
  2648. True
  2649. >>> G = SymmetricGroup(3)
  2650. >>> G.is_dihedral
  2651. True
  2652. >>> G = CyclicGroup(6)
  2653. >>> G.is_dihedral
  2654. False
  2655. References
  2656. ==========
  2657. .. [Di1] https://math.stackexchange.com/a/827273
  2658. .. [Di2] https://kconrad.math.uconn.edu/blurbs/grouptheory/dihedral.pdf
  2659. .. [Di3] https://kconrad.math.uconn.edu/blurbs/grouptheory/dihedral2.pdf
  2660. .. [Di4] https://en.wikipedia.org/wiki/Dihedral_group
  2661. """
  2662. if self._is_dihedral is not None:
  2663. return self._is_dihedral
  2664. order = self.order()
  2665. if order % 2 == 1:
  2666. self._is_dihedral = False
  2667. return False
  2668. if order == 2:
  2669. self._is_dihedral = True
  2670. return True
  2671. if order == 4:
  2672. # The dihedral group of order 4 is the Klein 4-group.
  2673. self._is_dihedral = not self.is_cyclic
  2674. return self._is_dihedral
  2675. if self.is_abelian:
  2676. # The only abelian dihedral groups are the ones of orders 2 and 4.
  2677. self._is_dihedral = False
  2678. return False
  2679. # Now we know the group is of even order >= 6, and nonabelian.
  2680. n = order // 2
  2681. # Handle special cases where there are exactly two generators.
  2682. gens = self.generators
  2683. if len(gens) == 2:
  2684. x, y = gens
  2685. a, b = x.order(), y.order()
  2686. # Make a >= b
  2687. if a < b:
  2688. x, y, a, b = y, x, b, a
  2689. # Using Theorem 2.1 of [Di3]:
  2690. if a == 2 == b:
  2691. self._is_dihedral = True
  2692. return True
  2693. # Using Theorem 1.1 of [Di3]:
  2694. if a == n and b == 2 and y*x*y == ~x:
  2695. self._is_dihedral = True
  2696. return True
  2697. # Proceed with algorithm of [Di1]
  2698. # Find elements of orders 2 and n
  2699. order_2, order_n = [], []
  2700. for p in self.elements:
  2701. k = p.order()
  2702. if k == 2:
  2703. order_2.append(p)
  2704. elif k == n:
  2705. order_n.append(p)
  2706. if len(order_2) != n + 1 - (n % 2):
  2707. self._is_dihedral = False
  2708. return False
  2709. if not order_n:
  2710. self._is_dihedral = False
  2711. return False
  2712. x = order_n[0]
  2713. # Want an element y of order 2 that is not a power of x
  2714. # (i.e. that is not the 180-deg rotation, when n is even).
  2715. y = order_2[0]
  2716. if n % 2 == 0 and y == x**(n//2):
  2717. y = order_2[1]
  2718. self._is_dihedral = (y*x*y == ~x)
  2719. return self._is_dihedral
  2720. def pointwise_stabilizer(self, points, incremental=True):
  2721. r"""Return the pointwise stabilizer for a set of points.
  2722. Explanation
  2723. ===========
  2724. For a permutation group `G` and a set of points
  2725. `\{p_1, p_2,\ldots, p_k\}`, the pointwise stabilizer of
  2726. `p_1, p_2, \ldots, p_k` is defined as
  2727. `G_{p_1,\ldots, p_k} =
  2728. \{g\in G | g(p_i) = p_i \forall i\in\{1, 2,\ldots,k\}\}` ([1],p20).
  2729. It is a subgroup of `G`.
  2730. Examples
  2731. ========
  2732. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  2733. >>> S = SymmetricGroup(7)
  2734. >>> Stab = S.pointwise_stabilizer([2, 3, 5])
  2735. >>> Stab.is_subgroup(S.stabilizer(2).stabilizer(3).stabilizer(5))
  2736. True
  2737. See Also
  2738. ========
  2739. stabilizer, schreier_sims_incremental
  2740. Notes
  2741. =====
  2742. When incremental == True,
  2743. rather than the obvious implementation using successive calls to
  2744. ``.stabilizer()``, this uses the incremental Schreier-Sims algorithm
  2745. to obtain a base with starting segment - the given points.
  2746. """
  2747. if incremental:
  2748. base, strong_gens = self.schreier_sims_incremental(base=points)
  2749. stab_gens = []
  2750. degree = self.degree
  2751. for gen in strong_gens:
  2752. if [gen(point) for point in points] == points:
  2753. stab_gens.append(gen)
  2754. if not stab_gens:
  2755. stab_gens = _af_new(list(range(degree)))
  2756. return PermutationGroup(stab_gens)
  2757. else:
  2758. gens = self._generators
  2759. degree = self.degree
  2760. for x in points:
  2761. gens = _stabilizer(degree, gens, x)
  2762. return PermutationGroup(gens)
  2763. def make_perm(self, n, seed=None):
  2764. """
  2765. Multiply ``n`` randomly selected permutations from
  2766. pgroup together, starting with the identity
  2767. permutation. If ``n`` is a list of integers, those
  2768. integers will be used to select the permutations and they
  2769. will be applied in L to R order: make_perm((A, B, C)) will
  2770. give CBA(I) where I is the identity permutation.
  2771. ``seed`` is used to set the seed for the random selection
  2772. of permutations from pgroup. If this is a list of integers,
  2773. the corresponding permutations from pgroup will be selected
  2774. in the order give. This is mainly used for testing purposes.
  2775. Examples
  2776. ========
  2777. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2778. >>> a, b = [Permutation([1, 0, 3, 2]), Permutation([1, 3, 0, 2])]
  2779. >>> G = PermutationGroup([a, b])
  2780. >>> G.make_perm(1, [0])
  2781. (0 1)(2 3)
  2782. >>> G.make_perm(3, [0, 1, 0])
  2783. (0 2 3 1)
  2784. >>> G.make_perm([0, 1, 0])
  2785. (0 2 3 1)
  2786. See Also
  2787. ========
  2788. random
  2789. """
  2790. if is_sequence(n):
  2791. if seed is not None:
  2792. raise ValueError('If n is a sequence, seed should be None')
  2793. n, seed = len(n), n
  2794. else:
  2795. try:
  2796. n = int(n)
  2797. except TypeError:
  2798. raise ValueError('n must be an integer or a sequence.')
  2799. randomrange = _randrange(seed)
  2800. # start with the identity permutation
  2801. result = Permutation(list(range(self.degree)))
  2802. m = len(self)
  2803. for _ in range(n):
  2804. p = self[randomrange(m)]
  2805. result = rmul(result, p)
  2806. return result
  2807. def random(self, af=False):
  2808. """Return a random group element
  2809. """
  2810. rank = randrange(self.order())
  2811. return self.coset_unrank(rank, af)
  2812. def random_pr(self, gen_count=11, iterations=50, _random_prec=None):
  2813. """Return a random group element using product replacement.
  2814. Explanation
  2815. ===========
  2816. For the details of the product replacement algorithm, see
  2817. ``_random_pr_init`` In ``random_pr`` the actual 'product replacement'
  2818. is performed. Notice that if the attribute ``_random_gens``
  2819. is empty, it needs to be initialized by ``_random_pr_init``.
  2820. See Also
  2821. ========
  2822. _random_pr_init
  2823. """
  2824. if self._random_gens == []:
  2825. self._random_pr_init(gen_count, iterations)
  2826. random_gens = self._random_gens
  2827. r = len(random_gens) - 1
  2828. # handle randomized input for testing purposes
  2829. if _random_prec is None:
  2830. s = randrange(r)
  2831. t = randrange(r - 1)
  2832. if t == s:
  2833. t = r - 1
  2834. x = choice([1, 2])
  2835. e = choice([-1, 1])
  2836. else:
  2837. s = _random_prec['s']
  2838. t = _random_prec['t']
  2839. if t == s:
  2840. t = r - 1
  2841. x = _random_prec['x']
  2842. e = _random_prec['e']
  2843. if x == 1:
  2844. random_gens[s] = _af_rmul(random_gens[s], _af_pow(random_gens[t], e))
  2845. random_gens[r] = _af_rmul(random_gens[r], random_gens[s])
  2846. else:
  2847. random_gens[s] = _af_rmul(_af_pow(random_gens[t], e), random_gens[s])
  2848. random_gens[r] = _af_rmul(random_gens[s], random_gens[r])
  2849. return _af_new(random_gens[r])
  2850. def random_stab(self, alpha, schreier_vector=None, _random_prec=None):
  2851. """Random element from the stabilizer of ``alpha``.
  2852. The schreier vector for ``alpha`` is an optional argument used
  2853. for speeding up repeated calls. The algorithm is described in [1], p.81
  2854. See Also
  2855. ========
  2856. random_pr, orbit_rep
  2857. """
  2858. if schreier_vector is None:
  2859. schreier_vector = self.schreier_vector(alpha)
  2860. if _random_prec is None:
  2861. rand = self.random_pr()
  2862. else:
  2863. rand = _random_prec['rand']
  2864. beta = rand(alpha)
  2865. h = self.orbit_rep(alpha, beta, schreier_vector)
  2866. return rmul(~h, rand)
  2867. def schreier_sims(self):
  2868. """Schreier-Sims algorithm.
  2869. Explanation
  2870. ===========
  2871. It computes the generators of the chain of stabilizers
  2872. `G > G_{b_1} > .. > G_{b1,..,b_r} > 1`
  2873. in which `G_{b_1,..,b_i}` stabilizes `b_1,..,b_i`,
  2874. and the corresponding ``s`` cosets.
  2875. An element of the group can be written as the product
  2876. `h_1*..*h_s`.
  2877. We use the incremental Schreier-Sims algorithm.
  2878. Examples
  2879. ========
  2880. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2881. >>> a = Permutation([0, 2, 1])
  2882. >>> b = Permutation([1, 0, 2])
  2883. >>> G = PermutationGroup([a, b])
  2884. >>> G.schreier_sims()
  2885. >>> G.basic_transversals
  2886. [{0: (2)(0 1), 1: (2), 2: (1 2)},
  2887. {0: (2), 2: (0 2)}]
  2888. """
  2889. if self._transversals:
  2890. return
  2891. self._schreier_sims()
  2892. return
  2893. def _schreier_sims(self, base=None):
  2894. schreier = self.schreier_sims_incremental(base=base, slp_dict=True)
  2895. base, strong_gens = schreier[:2]
  2896. self._base = base
  2897. self._strong_gens = strong_gens
  2898. self._strong_gens_slp = schreier[2]
  2899. if not base:
  2900. self._transversals = []
  2901. self._basic_orbits = []
  2902. return
  2903. strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
  2904. basic_orbits, transversals, slps = _orbits_transversals_from_bsgs(base,\
  2905. strong_gens_distr, slp=True)
  2906. # rewrite the indices stored in slps in terms of strong_gens
  2907. for i, slp in enumerate(slps):
  2908. gens = strong_gens_distr[i]
  2909. for k in slp:
  2910. slp[k] = [strong_gens.index(gens[s]) for s in slp[k]]
  2911. self._transversals = transversals
  2912. self._basic_orbits = [sorted(x) for x in basic_orbits]
  2913. self._transversal_slp = slps
  2914. def schreier_sims_incremental(self, base=None, gens=None, slp_dict=False):
  2915. """Extend a sequence of points and generating set to a base and strong
  2916. generating set.
  2917. Parameters
  2918. ==========
  2919. base
  2920. The sequence of points to be extended to a base. Optional
  2921. parameter with default value ``[]``.
  2922. gens
  2923. The generating set to be extended to a strong generating set
  2924. relative to the base obtained. Optional parameter with default
  2925. value ``self.generators``.
  2926. slp_dict
  2927. If `True`, return a dictionary `{g: gens}` for each strong
  2928. generator `g` where `gens` is a list of strong generators
  2929. coming before `g` in `strong_gens`, such that the product
  2930. of the elements of `gens` is equal to `g`.
  2931. Returns
  2932. =======
  2933. (base, strong_gens)
  2934. ``base`` is the base obtained, and ``strong_gens`` is the strong
  2935. generating set relative to it. The original parameters ``base``,
  2936. ``gens`` remain unchanged.
  2937. Examples
  2938. ========
  2939. >>> from sympy.combinatorics.named_groups import AlternatingGroup
  2940. >>> from sympy.combinatorics.testutil import _verify_bsgs
  2941. >>> A = AlternatingGroup(7)
  2942. >>> base = [2, 3]
  2943. >>> seq = [2, 3]
  2944. >>> base, strong_gens = A.schreier_sims_incremental(base=seq)
  2945. >>> _verify_bsgs(A, base, strong_gens)
  2946. True
  2947. >>> base[:2]
  2948. [2, 3]
  2949. Notes
  2950. =====
  2951. This version of the Schreier-Sims algorithm runs in polynomial time.
  2952. There are certain assumptions in the implementation - if the trivial
  2953. group is provided, ``base`` and ``gens`` are returned immediately,
  2954. as any sequence of points is a base for the trivial group. If the
  2955. identity is present in the generators ``gens``, it is removed as
  2956. it is a redundant generator.
  2957. The implementation is described in [1], pp. 90-93.
  2958. See Also
  2959. ========
  2960. schreier_sims, schreier_sims_random
  2961. """
  2962. if base is None:
  2963. base = []
  2964. if gens is None:
  2965. gens = self.generators[:]
  2966. degree = self.degree
  2967. id_af = list(range(degree))
  2968. # handle the trivial group
  2969. if len(gens) == 1 and gens[0].is_Identity:
  2970. if slp_dict:
  2971. return base, gens, {gens[0]: [gens[0]]}
  2972. return base, gens
  2973. # prevent side effects
  2974. _base, _gens = base[:], gens[:]
  2975. # remove the identity as a generator
  2976. _gens = [x for x in _gens if not x.is_Identity]
  2977. # make sure no generator fixes all base points
  2978. for gen in _gens:
  2979. if all(x == gen._array_form[x] for x in _base):
  2980. for new in id_af:
  2981. if gen._array_form[new] != new:
  2982. break
  2983. else:
  2984. assert None # can this ever happen?
  2985. _base.append(new)
  2986. # distribute generators according to basic stabilizers
  2987. strong_gens_distr = _distribute_gens_by_base(_base, _gens)
  2988. strong_gens_slp = []
  2989. # initialize the basic stabilizers, basic orbits and basic transversals
  2990. orbs = {}
  2991. transversals = {}
  2992. slps = {}
  2993. base_len = len(_base)
  2994. for i in range(base_len):
  2995. transversals[i], slps[i] = _orbit_transversal(degree, strong_gens_distr[i],
  2996. _base[i], pairs=True, af=True, slp=True)
  2997. transversals[i] = dict(transversals[i])
  2998. orbs[i] = list(transversals[i].keys())
  2999. # main loop: amend the stabilizer chain until we have generators
  3000. # for all stabilizers
  3001. i = base_len - 1
  3002. while i >= 0:
  3003. # this flag is used to continue with the main loop from inside
  3004. # a nested loop
  3005. continue_i = False
  3006. # test the generators for being a strong generating set
  3007. db = {}
  3008. for beta, u_beta in list(transversals[i].items()):
  3009. for j, gen in enumerate(strong_gens_distr[i]):
  3010. gb = gen._array_form[beta]
  3011. u1 = transversals[i][gb]
  3012. g1 = _af_rmul(gen._array_form, u_beta)
  3013. slp = [(i, g) for g in slps[i][beta]]
  3014. slp = [(i, j)] + slp
  3015. if g1 != u1:
  3016. # test if the schreier generator is in the i+1-th
  3017. # would-be basic stabilizer
  3018. y = True
  3019. try:
  3020. u1_inv = db[gb]
  3021. except KeyError:
  3022. u1_inv = db[gb] = _af_invert(u1)
  3023. schreier_gen = _af_rmul(u1_inv, g1)
  3024. u1_inv_slp = slps[i][gb][:]
  3025. u1_inv_slp.reverse()
  3026. u1_inv_slp = [(i, (g,)) for g in u1_inv_slp]
  3027. slp = u1_inv_slp + slp
  3028. h, j, slp = _strip_af(schreier_gen, _base, orbs, transversals, i, slp=slp, slps=slps)
  3029. if j <= base_len:
  3030. # new strong generator h at level j
  3031. y = False
  3032. elif h:
  3033. # h fixes all base points
  3034. y = False
  3035. moved = 0
  3036. while h[moved] == moved:
  3037. moved += 1
  3038. _base.append(moved)
  3039. base_len += 1
  3040. strong_gens_distr.append([])
  3041. if y is False:
  3042. # if a new strong generator is found, update the
  3043. # data structures and start over
  3044. h = _af_new(h)
  3045. strong_gens_slp.append((h, slp))
  3046. for l in range(i + 1, j):
  3047. strong_gens_distr[l].append(h)
  3048. transversals[l], slps[l] =\
  3049. _orbit_transversal(degree, strong_gens_distr[l],
  3050. _base[l], pairs=True, af=True, slp=True)
  3051. transversals[l] = dict(transversals[l])
  3052. orbs[l] = list(transversals[l].keys())
  3053. i = j - 1
  3054. # continue main loop using the flag
  3055. continue_i = True
  3056. if continue_i is True:
  3057. break
  3058. if continue_i is True:
  3059. break
  3060. if continue_i is True:
  3061. continue
  3062. i -= 1
  3063. strong_gens = _gens[:]
  3064. if slp_dict:
  3065. # create the list of the strong generators strong_gens and
  3066. # rewrite the indices of strong_gens_slp in terms of the
  3067. # elements of strong_gens
  3068. for k, slp in strong_gens_slp:
  3069. strong_gens.append(k)
  3070. for i in range(len(slp)):
  3071. s = slp[i]
  3072. if isinstance(s[1], tuple):
  3073. slp[i] = strong_gens_distr[s[0]][s[1][0]]**-1
  3074. else:
  3075. slp[i] = strong_gens_distr[s[0]][s[1]]
  3076. strong_gens_slp = dict(strong_gens_slp)
  3077. # add the original generators
  3078. for g in _gens:
  3079. strong_gens_slp[g] = [g]
  3080. return (_base, strong_gens, strong_gens_slp)
  3081. strong_gens.extend([k for k, _ in strong_gens_slp])
  3082. return _base, strong_gens
  3083. def schreier_sims_random(self, base=None, gens=None, consec_succ=10,
  3084. _random_prec=None):
  3085. r"""Randomized Schreier-Sims algorithm.
  3086. Explanation
  3087. ===========
  3088. The randomized Schreier-Sims algorithm takes the sequence ``base``
  3089. and the generating set ``gens``, and extends ``base`` to a base, and
  3090. ``gens`` to a strong generating set relative to that base with
  3091. probability of a wrong answer at most `2^{-consec\_succ}`,
  3092. provided the random generators are sufficiently random.
  3093. Parameters
  3094. ==========
  3095. base
  3096. The sequence to be extended to a base.
  3097. gens
  3098. The generating set to be extended to a strong generating set.
  3099. consec_succ
  3100. The parameter defining the probability of a wrong answer.
  3101. _random_prec
  3102. An internal parameter used for testing purposes.
  3103. Returns
  3104. =======
  3105. (base, strong_gens)
  3106. ``base`` is the base and ``strong_gens`` is the strong generating
  3107. set relative to it.
  3108. Examples
  3109. ========
  3110. >>> from sympy.combinatorics.testutil import _verify_bsgs
  3111. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  3112. >>> S = SymmetricGroup(5)
  3113. >>> base, strong_gens = S.schreier_sims_random(consec_succ=5)
  3114. >>> _verify_bsgs(S, base, strong_gens) #doctest: +SKIP
  3115. True
  3116. Notes
  3117. =====
  3118. The algorithm is described in detail in [1], pp. 97-98. It extends
  3119. the orbits ``orbs`` and the permutation groups ``stabs`` to
  3120. basic orbits and basic stabilizers for the base and strong generating
  3121. set produced in the end.
  3122. The idea of the extension process
  3123. is to "sift" random group elements through the stabilizer chain
  3124. and amend the stabilizers/orbits along the way when a sift
  3125. is not successful.
  3126. The helper function ``_strip`` is used to attempt
  3127. to decompose a random group element according to the current
  3128. state of the stabilizer chain and report whether the element was
  3129. fully decomposed (successful sift) or not (unsuccessful sift). In
  3130. the latter case, the level at which the sift failed is reported and
  3131. used to amend ``stabs``, ``base``, ``gens`` and ``orbs`` accordingly.
  3132. The halting condition is for ``consec_succ`` consecutive successful
  3133. sifts to pass. This makes sure that the current ``base`` and ``gens``
  3134. form a BSGS with probability at least `1 - 1/\text{consec\_succ}`.
  3135. See Also
  3136. ========
  3137. schreier_sims
  3138. """
  3139. if base is None:
  3140. base = []
  3141. if gens is None:
  3142. gens = self.generators
  3143. base_len = len(base)
  3144. n = self.degree
  3145. # make sure no generator fixes all base points
  3146. for gen in gens:
  3147. if all(gen(x) == x for x in base):
  3148. new = 0
  3149. while gen._array_form[new] == new:
  3150. new += 1
  3151. base.append(new)
  3152. base_len += 1
  3153. # distribute generators according to basic stabilizers
  3154. strong_gens_distr = _distribute_gens_by_base(base, gens)
  3155. # initialize the basic stabilizers, basic transversals and basic orbits
  3156. transversals = {}
  3157. orbs = {}
  3158. for i in range(base_len):
  3159. transversals[i] = dict(_orbit_transversal(n, strong_gens_distr[i],
  3160. base[i], pairs=True))
  3161. orbs[i] = list(transversals[i].keys())
  3162. # initialize the number of consecutive elements sifted
  3163. c = 0
  3164. # start sifting random elements while the number of consecutive sifts
  3165. # is less than consec_succ
  3166. while c < consec_succ:
  3167. if _random_prec is None:
  3168. g = self.random_pr()
  3169. else:
  3170. g = _random_prec['g'].pop()
  3171. h, j = _strip(g, base, orbs, transversals)
  3172. y = True
  3173. # determine whether a new base point is needed
  3174. if j <= base_len:
  3175. y = False
  3176. elif not h.is_Identity:
  3177. y = False
  3178. moved = 0
  3179. while h(moved) == moved:
  3180. moved += 1
  3181. base.append(moved)
  3182. base_len += 1
  3183. strong_gens_distr.append([])
  3184. # if the element doesn't sift, amend the strong generators and
  3185. # associated stabilizers and orbits
  3186. if y is False:
  3187. for l in range(1, j):
  3188. strong_gens_distr[l].append(h)
  3189. transversals[l] = dict(_orbit_transversal(n,
  3190. strong_gens_distr[l], base[l], pairs=True))
  3191. orbs[l] = list(transversals[l].keys())
  3192. c = 0
  3193. else:
  3194. c += 1
  3195. # build the strong generating set
  3196. strong_gens = strong_gens_distr[0][:]
  3197. for gen in strong_gens_distr[1]:
  3198. if gen not in strong_gens:
  3199. strong_gens.append(gen)
  3200. return base, strong_gens
  3201. def schreier_vector(self, alpha):
  3202. """Computes the schreier vector for ``alpha``.
  3203. Explanation
  3204. ===========
  3205. The Schreier vector efficiently stores information
  3206. about the orbit of ``alpha``. It can later be used to quickly obtain
  3207. elements of the group that send ``alpha`` to a particular element
  3208. in the orbit. Notice that the Schreier vector depends on the order
  3209. in which the group generators are listed. For a definition, see [3].
  3210. Since list indices start from zero, we adopt the convention to use
  3211. "None" instead of 0 to signify that an element does not belong
  3212. to the orbit.
  3213. For the algorithm and its correctness, see [2], pp.78-80.
  3214. Examples
  3215. ========
  3216. >>> from sympy.combinatorics import Permutation, PermutationGroup
  3217. >>> a = Permutation([2, 4, 6, 3, 1, 5, 0])
  3218. >>> b = Permutation([0, 1, 3, 5, 4, 6, 2])
  3219. >>> G = PermutationGroup([a, b])
  3220. >>> G.schreier_vector(0)
  3221. [-1, None, 0, 1, None, 1, 0]
  3222. See Also
  3223. ========
  3224. orbit
  3225. """
  3226. n = self.degree
  3227. v = [None]*n
  3228. v[alpha] = -1
  3229. orb = [alpha]
  3230. used = [False]*n
  3231. used[alpha] = True
  3232. gens = self.generators
  3233. r = len(gens)
  3234. for b in orb:
  3235. for i in range(r):
  3236. temp = gens[i]._array_form[b]
  3237. if used[temp] is False:
  3238. orb.append(temp)
  3239. used[temp] = True
  3240. v[temp] = i
  3241. return v
  3242. def stabilizer(self, alpha):
  3243. r"""Return the stabilizer subgroup of ``alpha``.
  3244. Explanation
  3245. ===========
  3246. The stabilizer of `\alpha` is the group `G_\alpha =
  3247. \{g \in G | g(\alpha) = \alpha\}`.
  3248. For a proof of correctness, see [1], p.79.
  3249. Examples
  3250. ========
  3251. >>> from sympy.combinatorics.named_groups import DihedralGroup
  3252. >>> G = DihedralGroup(6)
  3253. >>> G.stabilizer(5)
  3254. PermutationGroup([
  3255. (5)(0 4)(1 3)])
  3256. See Also
  3257. ========
  3258. orbit
  3259. """
  3260. return PermGroup(_stabilizer(self._degree, self._generators, alpha))
  3261. @property
  3262. def strong_gens(self):
  3263. r"""Return a strong generating set from the Schreier-Sims algorithm.
  3264. Explanation
  3265. ===========
  3266. A generating set `S = \{g_1, g_2, \dots, g_t\}` for a permutation group
  3267. `G` is a strong generating set relative to the sequence of points
  3268. (referred to as a "base") `(b_1, b_2, \dots, b_k)` if, for
  3269. `1 \leq i \leq k` we have that the intersection of the pointwise
  3270. stabilizer `G^{(i+1)} := G_{b_1, b_2, \dots, b_i}` with `S` generates
  3271. the pointwise stabilizer `G^{(i+1)}`. The concepts of a base and
  3272. strong generating set and their applications are discussed in depth
  3273. in [1], pp. 87-89 and [2], pp. 55-57.
  3274. Examples
  3275. ========
  3276. >>> from sympy.combinatorics.named_groups import DihedralGroup
  3277. >>> D = DihedralGroup(4)
  3278. >>> D.strong_gens
  3279. [(0 1 2 3), (0 3)(1 2), (1 3)]
  3280. >>> D.base
  3281. [0, 1]
  3282. See Also
  3283. ========
  3284. base, basic_transversals, basic_orbits, basic_stabilizers
  3285. """
  3286. if self._strong_gens == []:
  3287. self.schreier_sims()
  3288. return self._strong_gens
  3289. def subgroup(self, gens):
  3290. """
  3291. Return the subgroup generated by `gens` which is a list of
  3292. elements of the group
  3293. """
  3294. if not all(g in self for g in gens):
  3295. raise ValueError("The group does not contain the supplied generators")
  3296. G = PermutationGroup(gens)
  3297. return G
  3298. def subgroup_search(self, prop, base=None, strong_gens=None, tests=None,
  3299. init_subgroup=None):
  3300. """Find the subgroup of all elements satisfying the property ``prop``.
  3301. Explanation
  3302. ===========
  3303. This is done by a depth-first search with respect to base images that
  3304. uses several tests to prune the search tree.
  3305. Parameters
  3306. ==========
  3307. prop
  3308. The property to be used. Has to be callable on group elements
  3309. and always return ``True`` or ``False``. It is assumed that
  3310. all group elements satisfying ``prop`` indeed form a subgroup.
  3311. base
  3312. A base for the supergroup.
  3313. strong_gens
  3314. A strong generating set for the supergroup.
  3315. tests
  3316. A list of callables of length equal to the length of ``base``.
  3317. These are used to rule out group elements by partial base images,
  3318. so that ``tests[l](g)`` returns False if the element ``g`` is known
  3319. not to satisfy prop base on where g sends the first ``l + 1`` base
  3320. points.
  3321. init_subgroup
  3322. if a subgroup of the sought group is
  3323. known in advance, it can be passed to the function as this
  3324. parameter.
  3325. Returns
  3326. =======
  3327. res
  3328. The subgroup of all elements satisfying ``prop``. The generating
  3329. set for this group is guaranteed to be a strong generating set
  3330. relative to the base ``base``.
  3331. Examples
  3332. ========
  3333. >>> from sympy.combinatorics.named_groups import (SymmetricGroup,
  3334. ... AlternatingGroup)
  3335. >>> from sympy.combinatorics.testutil import _verify_bsgs
  3336. >>> S = SymmetricGroup(7)
  3337. >>> prop_even = lambda x: x.is_even
  3338. >>> base, strong_gens = S.schreier_sims_incremental()
  3339. >>> G = S.subgroup_search(prop_even, base=base, strong_gens=strong_gens)
  3340. >>> G.is_subgroup(AlternatingGroup(7))
  3341. True
  3342. >>> _verify_bsgs(G, base, G.generators)
  3343. True
  3344. Notes
  3345. =====
  3346. This function is extremely lengthy and complicated and will require
  3347. some careful attention. The implementation is described in
  3348. [1], pp. 114-117, and the comments for the code here follow the lines
  3349. of the pseudocode in the book for clarity.
  3350. The complexity is exponential in general, since the search process by
  3351. itself visits all members of the supergroup. However, there are a lot
  3352. of tests which are used to prune the search tree, and users can define
  3353. their own tests via the ``tests`` parameter, so in practice, and for
  3354. some computations, it's not terrible.
  3355. A crucial part in the procedure is the frequent base change performed
  3356. (this is line 11 in the pseudocode) in order to obtain a new basic
  3357. stabilizer. The book mentiones that this can be done by using
  3358. ``.baseswap(...)``, however the current implementation uses a more
  3359. straightforward way to find the next basic stabilizer - calling the
  3360. function ``.stabilizer(...)`` on the previous basic stabilizer.
  3361. """
  3362. # initialize BSGS and basic group properties
  3363. def get_reps(orbits):
  3364. # get the minimal element in the base ordering
  3365. return [min(orbit, key = lambda x: base_ordering[x]) \
  3366. for orbit in orbits]
  3367. def update_nu(l):
  3368. temp_index = len(basic_orbits[l]) + 1 -\
  3369. len(res_basic_orbits_init_base[l])
  3370. # this corresponds to the element larger than all points
  3371. if temp_index >= len(sorted_orbits[l]):
  3372. nu[l] = base_ordering[degree]
  3373. else:
  3374. nu[l] = sorted_orbits[l][temp_index]
  3375. if base is None:
  3376. base, strong_gens = self.schreier_sims_incremental()
  3377. base_len = len(base)
  3378. degree = self.degree
  3379. identity = _af_new(list(range(degree)))
  3380. base_ordering = _base_ordering(base, degree)
  3381. # add an element larger than all points
  3382. base_ordering.append(degree)
  3383. # add an element smaller than all points
  3384. base_ordering.append(-1)
  3385. # compute BSGS-related structures
  3386. strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
  3387. basic_orbits, transversals = _orbits_transversals_from_bsgs(base,
  3388. strong_gens_distr)
  3389. # handle subgroup initialization and tests
  3390. if init_subgroup is None:
  3391. init_subgroup = PermutationGroup([identity])
  3392. if tests is None:
  3393. trivial_test = lambda x: True
  3394. tests = []
  3395. for i in range(base_len):
  3396. tests.append(trivial_test)
  3397. # line 1: more initializations.
  3398. res = init_subgroup
  3399. f = base_len - 1
  3400. l = base_len - 1
  3401. # line 2: set the base for K to the base for G
  3402. res_base = base[:]
  3403. # line 3: compute BSGS and related structures for K
  3404. res_base, res_strong_gens = res.schreier_sims_incremental(
  3405. base=res_base)
  3406. res_strong_gens_distr = _distribute_gens_by_base(res_base,
  3407. res_strong_gens)
  3408. res_generators = res.generators
  3409. res_basic_orbits_init_base = \
  3410. [_orbit(degree, res_strong_gens_distr[i], res_base[i])\
  3411. for i in range(base_len)]
  3412. # initialize orbit representatives
  3413. orbit_reps = [None]*base_len
  3414. # line 4: orbit representatives for f-th basic stabilizer of K
  3415. orbits = _orbits(degree, res_strong_gens_distr[f])
  3416. orbit_reps[f] = get_reps(orbits)
  3417. # line 5: remove the base point from the representatives to avoid
  3418. # getting the identity element as a generator for K
  3419. orbit_reps[f].remove(base[f])
  3420. # line 6: more initializations
  3421. c = [0]*base_len
  3422. u = [identity]*base_len
  3423. sorted_orbits = [None]*base_len
  3424. for i in range(base_len):
  3425. sorted_orbits[i] = basic_orbits[i][:]
  3426. sorted_orbits[i].sort(key=lambda point: base_ordering[point])
  3427. # line 7: initializations
  3428. mu = [None]*base_len
  3429. nu = [None]*base_len
  3430. # this corresponds to the element smaller than all points
  3431. mu[l] = degree + 1
  3432. update_nu(l)
  3433. # initialize computed words
  3434. computed_words = [identity]*base_len
  3435. # line 8: main loop
  3436. while True:
  3437. # apply all the tests
  3438. while l < base_len - 1 and \
  3439. computed_words[l](base[l]) in orbit_reps[l] and \
  3440. base_ordering[mu[l]] < \
  3441. base_ordering[computed_words[l](base[l])] < \
  3442. base_ordering[nu[l]] and \
  3443. tests[l](computed_words):
  3444. # line 11: change the (partial) base of K
  3445. new_point = computed_words[l](base[l])
  3446. res_base[l] = new_point
  3447. new_stab_gens = _stabilizer(degree, res_strong_gens_distr[l],
  3448. new_point)
  3449. res_strong_gens_distr[l + 1] = new_stab_gens
  3450. # line 12: calculate minimal orbit representatives for the
  3451. # l+1-th basic stabilizer
  3452. orbits = _orbits(degree, new_stab_gens)
  3453. orbit_reps[l + 1] = get_reps(orbits)
  3454. # line 13: amend sorted orbits
  3455. l += 1
  3456. temp_orbit = [computed_words[l - 1](point) for point
  3457. in basic_orbits[l]]
  3458. temp_orbit.sort(key=lambda point: base_ordering[point])
  3459. sorted_orbits[l] = temp_orbit
  3460. # lines 14 and 15: update variables used minimality tests
  3461. new_mu = degree + 1
  3462. for i in range(l):
  3463. if base[l] in res_basic_orbits_init_base[i]:
  3464. candidate = computed_words[i](base[i])
  3465. if base_ordering[candidate] > base_ordering[new_mu]:
  3466. new_mu = candidate
  3467. mu[l] = new_mu
  3468. update_nu(l)
  3469. # line 16: determine the new transversal element
  3470. c[l] = 0
  3471. temp_point = sorted_orbits[l][c[l]]
  3472. gamma = computed_words[l - 1]._array_form.index(temp_point)
  3473. u[l] = transversals[l][gamma]
  3474. # update computed words
  3475. computed_words[l] = rmul(computed_words[l - 1], u[l])
  3476. # lines 17 & 18: apply the tests to the group element found
  3477. g = computed_words[l]
  3478. temp_point = g(base[l])
  3479. if l == base_len - 1 and \
  3480. base_ordering[mu[l]] < \
  3481. base_ordering[temp_point] < base_ordering[nu[l]] and \
  3482. temp_point in orbit_reps[l] and \
  3483. tests[l](computed_words) and \
  3484. prop(g):
  3485. # line 19: reset the base of K
  3486. res_generators.append(g)
  3487. res_base = base[:]
  3488. # line 20: recalculate basic orbits (and transversals)
  3489. res_strong_gens.append(g)
  3490. res_strong_gens_distr = _distribute_gens_by_base(res_base,
  3491. res_strong_gens)
  3492. res_basic_orbits_init_base = \
  3493. [_orbit(degree, res_strong_gens_distr[i], res_base[i]) \
  3494. for i in range(base_len)]
  3495. # line 21: recalculate orbit representatives
  3496. # line 22: reset the search depth
  3497. orbit_reps[f] = get_reps(orbits)
  3498. l = f
  3499. # line 23: go up the tree until in the first branch not fully
  3500. # searched
  3501. while l >= 0 and c[l] == len(basic_orbits[l]) - 1:
  3502. l = l - 1
  3503. # line 24: if the entire tree is traversed, return K
  3504. if l == -1:
  3505. return PermutationGroup(res_generators)
  3506. # lines 25-27: update orbit representatives
  3507. if l < f:
  3508. # line 26
  3509. f = l
  3510. c[l] = 0
  3511. # line 27
  3512. temp_orbits = _orbits(degree, res_strong_gens_distr[f])
  3513. orbit_reps[f] = get_reps(temp_orbits)
  3514. # line 28: update variables used for minimality testing
  3515. mu[l] = degree + 1
  3516. temp_index = len(basic_orbits[l]) + 1 - \
  3517. len(res_basic_orbits_init_base[l])
  3518. if temp_index >= len(sorted_orbits[l]):
  3519. nu[l] = base_ordering[degree]
  3520. else:
  3521. nu[l] = sorted_orbits[l][temp_index]
  3522. # line 29: set the next element from the current branch and update
  3523. # accordingly
  3524. c[l] += 1
  3525. if l == 0:
  3526. gamma = sorted_orbits[l][c[l]]
  3527. else:
  3528. gamma = computed_words[l - 1]._array_form.index(sorted_orbits[l][c[l]])
  3529. u[l] = transversals[l][gamma]
  3530. if l == 0:
  3531. computed_words[l] = u[l]
  3532. else:
  3533. computed_words[l] = rmul(computed_words[l - 1], u[l])
  3534. @property
  3535. def transitivity_degree(self):
  3536. r"""Compute the degree of transitivity of the group.
  3537. Explanation
  3538. ===========
  3539. A permutation group `G` acting on `\Omega = \{0, 1, \dots, n-1\}` is
  3540. ``k``-fold transitive, if, for any `k` points
  3541. `(a_1, a_2, \dots, a_k) \in \Omega` and any `k` points
  3542. `(b_1, b_2, \dots, b_k) \in \Omega` there exists `g \in G` such that
  3543. `g(a_1) = b_1, g(a_2) = b_2, \dots, g(a_k) = b_k`
  3544. The degree of transitivity of `G` is the maximum ``k`` such that
  3545. `G` is ``k``-fold transitive. ([8])
  3546. Examples
  3547. ========
  3548. >>> from sympy.combinatorics import Permutation, PermutationGroup
  3549. >>> a = Permutation([1, 2, 0])
  3550. >>> b = Permutation([1, 0, 2])
  3551. >>> G = PermutationGroup([a, b])
  3552. >>> G.transitivity_degree
  3553. 3
  3554. See Also
  3555. ========
  3556. is_transitive, orbit
  3557. """
  3558. if self._transitivity_degree is None:
  3559. n = self.degree
  3560. G = self
  3561. # if G is k-transitive, a tuple (a_0,..,a_k)
  3562. # can be brought to (b_0,...,b_(k-1), b_k)
  3563. # where b_0,...,b_(k-1) are fixed points;
  3564. # consider the group G_k which stabilizes b_0,...,b_(k-1)
  3565. # if G_k is transitive on the subset excluding b_0,...,b_(k-1)
  3566. # then G is (k+1)-transitive
  3567. for i in range(n):
  3568. orb = G.orbit(i)
  3569. if len(orb) != n - i:
  3570. self._transitivity_degree = i
  3571. return i
  3572. G = G.stabilizer(i)
  3573. self._transitivity_degree = n
  3574. return n
  3575. else:
  3576. return self._transitivity_degree
  3577. def _p_elements_group(self, p):
  3578. '''
  3579. For an abelian p-group, return the subgroup consisting of
  3580. all elements of order p (and the identity)
  3581. '''
  3582. gens = self.generators[:]
  3583. gens = sorted(gens, key=lambda x: x.order(), reverse=True)
  3584. gens_p = [g**(g.order()/p) for g in gens]
  3585. gens_r = []
  3586. for i in range(len(gens)):
  3587. x = gens[i]
  3588. x_order = x.order()
  3589. # x_p has order p
  3590. x_p = x**(x_order/p)
  3591. if i > 0:
  3592. P = PermutationGroup(gens_p[:i])
  3593. else:
  3594. P = PermutationGroup(self.identity)
  3595. if x**(x_order/p) not in P:
  3596. gens_r.append(x**(x_order/p))
  3597. else:
  3598. # replace x by an element of order (x.order()/p)
  3599. # so that gens still generates G
  3600. g = P.generator_product(x_p, original=True)
  3601. for s in g:
  3602. x = x*s**-1
  3603. x_order = x_order/p
  3604. # insert x to gens so that the sorting is preserved
  3605. del gens[i]
  3606. del gens_p[i]
  3607. j = i - 1
  3608. while j < len(gens) and gens[j].order() >= x_order:
  3609. j += 1
  3610. gens = gens[:j] + [x] + gens[j:]
  3611. gens_p = gens_p[:j] + [x] + gens_p[j:]
  3612. return PermutationGroup(gens_r)
  3613. def _sylow_alt_sym(self, p):
  3614. '''
  3615. Return a p-Sylow subgroup of a symmetric or an
  3616. alternating group.
  3617. Explanation
  3618. ===========
  3619. The algorithm for this is hinted at in [1], Chapter 4,
  3620. Exercise 4.
  3621. For Sym(n) with n = p^i, the idea is as follows. Partition
  3622. the interval [0..n-1] into p equal parts, each of length p^(i-1):
  3623. [0..p^(i-1)-1], [p^(i-1)..2*p^(i-1)-1]...[(p-1)*p^(i-1)..p^i-1].
  3624. Find a p-Sylow subgroup of Sym(p^(i-1)) (treated as a subgroup
  3625. of ``self``) acting on each of the parts. Call the subgroups
  3626. P_1, P_2...P_p. The generators for the subgroups P_2...P_p
  3627. can be obtained from those of P_1 by applying a "shifting"
  3628. permutation to them, that is, a permutation mapping [0..p^(i-1)-1]
  3629. to the second part (the other parts are obtained by using the shift
  3630. multiple times). The union of this permutation and the generators
  3631. of P_1 is a p-Sylow subgroup of ``self``.
  3632. For n not equal to a power of p, partition
  3633. [0..n-1] in accordance with how n would be written in base p.
  3634. E.g. for p=2 and n=11, 11 = 2^3 + 2^2 + 1 so the partition
  3635. is [[0..7], [8..9], {10}]. To generate a p-Sylow subgroup,
  3636. take the union of the generators for each of the parts.
  3637. For the above example, {(0 1), (0 2)(1 3), (0 4), (1 5)(2 7)}
  3638. from the first part, {(8 9)} from the second part and
  3639. nothing from the third. This gives 4 generators in total, and
  3640. the subgroup they generate is p-Sylow.
  3641. Alternating groups are treated the same except when p=2. In this
  3642. case, (0 1)(s s+1) should be added for an appropriate s (the start
  3643. of a part) for each part in the partitions.
  3644. See Also
  3645. ========
  3646. sylow_subgroup, is_alt_sym
  3647. '''
  3648. n = self.degree
  3649. gens = []
  3650. identity = Permutation(n-1)
  3651. # the case of 2-sylow subgroups of alternating groups
  3652. # needs special treatment
  3653. alt = p == 2 and all(g.is_even for g in self.generators)
  3654. # find the presentation of n in base p
  3655. coeffs = []
  3656. m = n
  3657. while m > 0:
  3658. coeffs.append(m % p)
  3659. m = m // p
  3660. power = len(coeffs)-1
  3661. # for a symmetric group, gens[:i] is the generating
  3662. # set for a p-Sylow subgroup on [0..p**(i-1)-1]. For
  3663. # alternating groups, the same is given by gens[:2*(i-1)]
  3664. for i in range(1, power+1):
  3665. if i == 1 and alt:
  3666. # (0 1) shouldn't be added for alternating groups
  3667. continue
  3668. gen = Permutation([(j + p**(i-1)) % p**i for j in range(p**i)])
  3669. gens.append(identity*gen)
  3670. if alt:
  3671. gen = Permutation(0, 1)*gen*Permutation(0, 1)*gen
  3672. gens.append(gen)
  3673. # the first point in the current part (see the algorithm
  3674. # description in the docstring)
  3675. start = 0
  3676. while power > 0:
  3677. a = coeffs[power]
  3678. # make the permutation shifting the start of the first
  3679. # part ([0..p^i-1] for some i) to the current one
  3680. for _ in range(a):
  3681. shift = Permutation()
  3682. if start > 0:
  3683. for i in range(p**power):
  3684. shift = shift(i, start + i)
  3685. if alt:
  3686. gen = Permutation(0, 1)*shift*Permutation(0, 1)*shift
  3687. gens.append(gen)
  3688. j = 2*(power - 1)
  3689. else:
  3690. j = power
  3691. for i, gen in enumerate(gens[:j]):
  3692. if alt and i % 2 == 1:
  3693. continue
  3694. # shift the generator to the start of the
  3695. # partition part
  3696. gen = shift*gen*shift
  3697. gens.append(gen)
  3698. start += p**power
  3699. power = power-1
  3700. return gens
  3701. def sylow_subgroup(self, p):
  3702. '''
  3703. Return a p-Sylow subgroup of the group.
  3704. The algorithm is described in [1], Chapter 4, Section 7
  3705. Examples
  3706. ========
  3707. >>> from sympy.combinatorics.named_groups import DihedralGroup
  3708. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  3709. >>> from sympy.combinatorics.named_groups import AlternatingGroup
  3710. >>> D = DihedralGroup(6)
  3711. >>> S = D.sylow_subgroup(2)
  3712. >>> S.order()
  3713. 4
  3714. >>> G = SymmetricGroup(6)
  3715. >>> S = G.sylow_subgroup(5)
  3716. >>> S.order()
  3717. 5
  3718. >>> G1 = AlternatingGroup(3)
  3719. >>> G2 = AlternatingGroup(5)
  3720. >>> G3 = AlternatingGroup(9)
  3721. >>> S1 = G1.sylow_subgroup(3)
  3722. >>> S2 = G2.sylow_subgroup(3)
  3723. >>> S3 = G3.sylow_subgroup(3)
  3724. >>> len1 = len(S1.lower_central_series())
  3725. >>> len2 = len(S2.lower_central_series())
  3726. >>> len3 = len(S3.lower_central_series())
  3727. >>> len1 == len2
  3728. True
  3729. >>> len1 < len3
  3730. True
  3731. '''
  3732. from sympy.combinatorics.homomorphisms import (
  3733. orbit_homomorphism, block_homomorphism)
  3734. if not isprime(p):
  3735. raise ValueError("p must be a prime")
  3736. def is_p_group(G):
  3737. # check if the order of G is a power of p
  3738. # and return the power
  3739. m = G.order()
  3740. n = 0
  3741. while m % p == 0:
  3742. m = m/p
  3743. n += 1
  3744. if m == 1:
  3745. return True, n
  3746. return False, n
  3747. def _sylow_reduce(mu, nu):
  3748. # reduction based on two homomorphisms
  3749. # mu and nu with trivially intersecting
  3750. # kernels
  3751. Q = mu.image().sylow_subgroup(p)
  3752. Q = mu.invert_subgroup(Q)
  3753. nu = nu.restrict_to(Q)
  3754. R = nu.image().sylow_subgroup(p)
  3755. return nu.invert_subgroup(R)
  3756. order = self.order()
  3757. if order % p != 0:
  3758. return PermutationGroup([self.identity])
  3759. p_group, n = is_p_group(self)
  3760. if p_group:
  3761. return self
  3762. if self.is_alt_sym():
  3763. return PermutationGroup(self._sylow_alt_sym(p))
  3764. # if there is a non-trivial orbit with size not divisible
  3765. # by p, the sylow subgroup is contained in its stabilizer
  3766. # (by orbit-stabilizer theorem)
  3767. orbits = self.orbits()
  3768. non_p_orbits = [o for o in orbits if len(o) % p != 0 and len(o) != 1]
  3769. if non_p_orbits:
  3770. G = self.stabilizer(list(non_p_orbits[0]).pop())
  3771. return G.sylow_subgroup(p)
  3772. if not self.is_transitive():
  3773. # apply _sylow_reduce to orbit actions
  3774. orbits = sorted(orbits, key=len)
  3775. omega1 = orbits.pop()
  3776. omega2 = orbits[0].union(*orbits)
  3777. mu = orbit_homomorphism(self, omega1)
  3778. nu = orbit_homomorphism(self, omega2)
  3779. return _sylow_reduce(mu, nu)
  3780. blocks = self.minimal_blocks()
  3781. if len(blocks) > 1:
  3782. # apply _sylow_reduce to block system actions
  3783. mu = block_homomorphism(self, blocks[0])
  3784. nu = block_homomorphism(self, blocks[1])
  3785. return _sylow_reduce(mu, nu)
  3786. elif len(blocks) == 1:
  3787. block = list(blocks)[0]
  3788. if any(e != 0 for e in block):
  3789. # self is imprimitive
  3790. mu = block_homomorphism(self, block)
  3791. if not is_p_group(mu.image())[0]:
  3792. S = mu.image().sylow_subgroup(p)
  3793. return mu.invert_subgroup(S).sylow_subgroup(p)
  3794. # find an element of order p
  3795. g = self.random()
  3796. g_order = g.order()
  3797. while g_order % p != 0 or g_order == 0:
  3798. g = self.random()
  3799. g_order = g.order()
  3800. g = g**(g_order // p)
  3801. if order % p**2 != 0:
  3802. return PermutationGroup(g)
  3803. C = self.centralizer(g)
  3804. while C.order() % p**n != 0:
  3805. S = C.sylow_subgroup(p)
  3806. s_order = S.order()
  3807. Z = S.center()
  3808. P = Z._p_elements_group(p)
  3809. h = P.random()
  3810. C_h = self.centralizer(h)
  3811. while C_h.order() % p*s_order != 0:
  3812. h = P.random()
  3813. C_h = self.centralizer(h)
  3814. C = C_h
  3815. return C.sylow_subgroup(p)
  3816. def _block_verify(self, L, alpha):
  3817. delta = sorted(self.orbit(alpha))
  3818. # p[i] will be the number of the block
  3819. # delta[i] belongs to
  3820. p = [-1]*len(delta)
  3821. blocks = [-1]*len(delta)
  3822. B = [[]] # future list of blocks
  3823. u = [0]*len(delta) # u[i] in L s.t. alpha^u[i] = B[0][i]
  3824. t = L.orbit_transversal(alpha, pairs=True)
  3825. for a, beta in t:
  3826. B[0].append(a)
  3827. i_a = delta.index(a)
  3828. p[i_a] = 0
  3829. blocks[i_a] = alpha
  3830. u[i_a] = beta
  3831. rho = 0
  3832. m = 0 # number of blocks - 1
  3833. while rho <= m:
  3834. beta = B[rho][0]
  3835. for g in self.generators:
  3836. d = beta^g
  3837. i_d = delta.index(d)
  3838. sigma = p[i_d]
  3839. if sigma < 0:
  3840. # define a new block
  3841. m += 1
  3842. sigma = m
  3843. u[i_d] = u[delta.index(beta)]*g
  3844. p[i_d] = sigma
  3845. rep = d
  3846. blocks[i_d] = rep
  3847. newb = [rep]
  3848. for gamma in B[rho][1:]:
  3849. i_gamma = delta.index(gamma)
  3850. d = gamma^g
  3851. i_d = delta.index(d)
  3852. if p[i_d] < 0:
  3853. u[i_d] = u[i_gamma]*g
  3854. p[i_d] = sigma
  3855. blocks[i_d] = rep
  3856. newb.append(d)
  3857. else:
  3858. # B[rho] is not a block
  3859. s = u[i_gamma]*g*u[i_d]**(-1)
  3860. return False, s
  3861. B.append(newb)
  3862. else:
  3863. for h in B[rho][1:]:
  3864. if h^g not in B[sigma]:
  3865. # B[rho] is not a block
  3866. s = u[delta.index(beta)]*g*u[i_d]**(-1)
  3867. return False, s
  3868. rho += 1
  3869. return True, blocks
  3870. def _verify(H, K, phi, z, alpha):
  3871. '''
  3872. Return a list of relators ``rels`` in generators ``gens`_h` that
  3873. are mapped to ``H.generators`` by ``phi`` so that given a finite
  3874. presentation <gens_k | rels_k> of ``K`` on a subset of ``gens_h``
  3875. <gens_h | rels_k + rels> is a finite presentation of ``H``.
  3876. Explanation
  3877. ===========
  3878. ``H`` should be generated by the union of ``K.generators`` and ``z``
  3879. (a single generator), and ``H.stabilizer(alpha) == K``; ``phi`` is a
  3880. canonical injection from a free group into a permutation group
  3881. containing ``H``.
  3882. The algorithm is described in [1], Chapter 6.
  3883. Examples
  3884. ========
  3885. >>> from sympy.combinatorics import free_group, Permutation, PermutationGroup
  3886. >>> from sympy.combinatorics.homomorphisms import homomorphism
  3887. >>> from sympy.combinatorics.fp_groups import FpGroup
  3888. >>> H = PermutationGroup(Permutation(0, 2), Permutation (1, 5))
  3889. >>> K = PermutationGroup(Permutation(5)(0, 2))
  3890. >>> F = free_group("x_0 x_1")[0]
  3891. >>> gens = F.generators
  3892. >>> phi = homomorphism(F, H, F.generators, H.generators)
  3893. >>> rels_k = [gens[0]**2] # relators for presentation of K
  3894. >>> z= Permutation(1, 5)
  3895. >>> check, rels_h = H._verify(K, phi, z, 1)
  3896. >>> check
  3897. True
  3898. >>> rels = rels_k + rels_h
  3899. >>> G = FpGroup(F, rels) # presentation of H
  3900. >>> G.order() == H.order()
  3901. True
  3902. See also
  3903. ========
  3904. strong_presentation, presentation, stabilizer
  3905. '''
  3906. orbit = H.orbit(alpha)
  3907. beta = alpha^(z**-1)
  3908. K_beta = K.stabilizer(beta)
  3909. # orbit representatives of K_beta
  3910. gammas = [alpha, beta]
  3911. orbits = list({tuple(K_beta.orbit(o)) for o in orbit})
  3912. orbit_reps = [orb[0] for orb in orbits]
  3913. for rep in orbit_reps:
  3914. if rep not in gammas:
  3915. gammas.append(rep)
  3916. # orbit transversal of K
  3917. betas = [alpha, beta]
  3918. transversal = {alpha: phi.invert(H.identity), beta: phi.invert(z**-1)}
  3919. for s, g in K.orbit_transversal(beta, pairs=True):
  3920. if s not in transversal:
  3921. transversal[s] = transversal[beta]*phi.invert(g)
  3922. union = K.orbit(alpha).union(K.orbit(beta))
  3923. while (len(union) < len(orbit)):
  3924. for gamma in gammas:
  3925. if gamma in union:
  3926. r = gamma^z
  3927. if r not in union:
  3928. betas.append(r)
  3929. transversal[r] = transversal[gamma]*phi.invert(z)
  3930. for s, g in K.orbit_transversal(r, pairs=True):
  3931. if s not in transversal:
  3932. transversal[s] = transversal[r]*phi.invert(g)
  3933. union = union.union(K.orbit(r))
  3934. break
  3935. # compute relators
  3936. rels = []
  3937. for b in betas:
  3938. k_gens = K.stabilizer(b).generators
  3939. for y in k_gens:
  3940. new_rel = transversal[b]
  3941. gens = K.generator_product(y, original=True)
  3942. for g in gens[::-1]:
  3943. new_rel = new_rel*phi.invert(g)
  3944. new_rel = new_rel*transversal[b]**-1
  3945. perm = phi(new_rel)
  3946. try:
  3947. gens = K.generator_product(perm, original=True)
  3948. except ValueError:
  3949. return False, perm
  3950. for g in gens:
  3951. new_rel = new_rel*phi.invert(g)**-1
  3952. if new_rel not in rels:
  3953. rels.append(new_rel)
  3954. for gamma in gammas:
  3955. new_rel = transversal[gamma]*phi.invert(z)*transversal[gamma^z]**-1
  3956. perm = phi(new_rel)
  3957. try:
  3958. gens = K.generator_product(perm, original=True)
  3959. except ValueError:
  3960. return False, perm
  3961. for g in gens:
  3962. new_rel = new_rel*phi.invert(g)**-1
  3963. if new_rel not in rels:
  3964. rels.append(new_rel)
  3965. return True, rels
  3966. def strong_presentation(self):
  3967. '''
  3968. Return a strong finite presentation of group. The generators
  3969. of the returned group are in the same order as the strong
  3970. generators of group.
  3971. The algorithm is based on Sims' Verify algorithm described
  3972. in [1], Chapter 6.
  3973. Examples
  3974. ========
  3975. >>> from sympy.combinatorics.named_groups import DihedralGroup
  3976. >>> P = DihedralGroup(4)
  3977. >>> G = P.strong_presentation()
  3978. >>> P.order() == G.order()
  3979. True
  3980. See Also
  3981. ========
  3982. presentation, _verify
  3983. '''
  3984. from sympy.combinatorics.fp_groups import (FpGroup,
  3985. simplify_presentation)
  3986. from sympy.combinatorics.free_groups import free_group
  3987. from sympy.combinatorics.homomorphisms import (block_homomorphism,
  3988. homomorphism, GroupHomomorphism)
  3989. strong_gens = self.strong_gens[:]
  3990. stabs = self.basic_stabilizers[:]
  3991. base = self.base[:]
  3992. # injection from a free group on len(strong_gens)
  3993. # generators into G
  3994. gen_syms = [('x_%d'%i) for i in range(len(strong_gens))]
  3995. F = free_group(', '.join(gen_syms))[0]
  3996. phi = homomorphism(F, self, F.generators, strong_gens)
  3997. H = PermutationGroup(self.identity)
  3998. while stabs:
  3999. alpha = base.pop()
  4000. K = H
  4001. H = stabs.pop()
  4002. new_gens = [g for g in H.generators if g not in K]
  4003. if K.order() == 1:
  4004. z = new_gens.pop()
  4005. rels = [F.generators[-1]**z.order()]
  4006. intermediate_gens = [z]
  4007. K = PermutationGroup(intermediate_gens)
  4008. # add generators one at a time building up from K to H
  4009. while new_gens:
  4010. z = new_gens.pop()
  4011. intermediate_gens = [z] + intermediate_gens
  4012. K_s = PermutationGroup(intermediate_gens)
  4013. orbit = K_s.orbit(alpha)
  4014. orbit_k = K.orbit(alpha)
  4015. # split into cases based on the orbit of K_s
  4016. if orbit_k == orbit:
  4017. if z in K:
  4018. rel = phi.invert(z)
  4019. perm = z
  4020. else:
  4021. t = K.orbit_rep(alpha, alpha^z)
  4022. rel = phi.invert(z)*phi.invert(t)**-1
  4023. perm = z*t**-1
  4024. for g in K.generator_product(perm, original=True):
  4025. rel = rel*phi.invert(g)**-1
  4026. new_rels = [rel]
  4027. elif len(orbit_k) == 1:
  4028. # `success` is always true because `strong_gens`
  4029. # and `base` are already a verified BSGS. Later
  4030. # this could be changed to start with a randomly
  4031. # generated (potential) BSGS, and then new elements
  4032. # would have to be appended to it when `success`
  4033. # is false.
  4034. success, new_rels = K_s._verify(K, phi, z, alpha)
  4035. else:
  4036. # K.orbit(alpha) should be a block
  4037. # under the action of K_s on K_s.orbit(alpha)
  4038. check, block = K_s._block_verify(K, alpha)
  4039. if check:
  4040. # apply _verify to the action of K_s
  4041. # on the block system; for convenience,
  4042. # add the blocks as additional points
  4043. # that K_s should act on
  4044. t = block_homomorphism(K_s, block)
  4045. m = t.codomain.degree # number of blocks
  4046. d = K_s.degree
  4047. # conjugating with p will shift
  4048. # permutations in t.image() to
  4049. # higher numbers, e.g.
  4050. # p*(0 1)*p = (m m+1)
  4051. p = Permutation()
  4052. for i in range(m):
  4053. p *= Permutation(i, i+d)
  4054. t_img = t.images
  4055. # combine generators of K_s with their
  4056. # action on the block system
  4057. images = {g: g*p*t_img[g]*p for g in t_img}
  4058. for g in self.strong_gens[:-len(K_s.generators)]:
  4059. images[g] = g
  4060. K_s_act = PermutationGroup(list(images.values()))
  4061. f = GroupHomomorphism(self, K_s_act, images)
  4062. K_act = PermutationGroup([f(g) for g in K.generators])
  4063. success, new_rels = K_s_act._verify(K_act, f.compose(phi), f(z), d)
  4064. for n in new_rels:
  4065. if n not in rels:
  4066. rels.append(n)
  4067. K = K_s
  4068. group = FpGroup(F, rels)
  4069. return simplify_presentation(group)
  4070. def presentation(self, eliminate_gens=True):
  4071. '''
  4072. Return an `FpGroup` presentation of the group.
  4073. The algorithm is described in [1], Chapter 6.1.
  4074. '''
  4075. from sympy.combinatorics.fp_groups import (FpGroup,
  4076. simplify_presentation)
  4077. from sympy.combinatorics.coset_table import CosetTable
  4078. from sympy.combinatorics.free_groups import free_group
  4079. from sympy.combinatorics.homomorphisms import homomorphism
  4080. if self._fp_presentation:
  4081. return self._fp_presentation
  4082. def _factor_group_by_rels(G, rels):
  4083. if isinstance(G, FpGroup):
  4084. rels.extend(G.relators)
  4085. return FpGroup(G.free_group, list(set(rels)))
  4086. return FpGroup(G, rels)
  4087. gens = self.generators
  4088. len_g = len(gens)
  4089. if len_g == 1:
  4090. order = gens[0].order()
  4091. # handle the trivial group
  4092. if order == 1:
  4093. return free_group([])[0]
  4094. F, x = free_group('x')
  4095. return FpGroup(F, [x**order])
  4096. if self.order() > 20:
  4097. half_gens = self.generators[0:(len_g+1)//2]
  4098. else:
  4099. half_gens = []
  4100. H = PermutationGroup(half_gens)
  4101. H_p = H.presentation()
  4102. len_h = len(H_p.generators)
  4103. C = self.coset_table(H)
  4104. n = len(C) # subgroup index
  4105. gen_syms = [('x_%d'%i) for i in range(len(gens))]
  4106. F = free_group(', '.join(gen_syms))[0]
  4107. # mapping generators of H_p to those of F
  4108. images = [F.generators[i] for i in range(len_h)]
  4109. R = homomorphism(H_p, F, H_p.generators, images, check=False)
  4110. # rewrite relators
  4111. rels = R(H_p.relators)
  4112. G_p = FpGroup(F, rels)
  4113. # injective homomorphism from G_p into self
  4114. T = homomorphism(G_p, self, G_p.generators, gens)
  4115. C_p = CosetTable(G_p, [])
  4116. C_p.table = [[None]*(2*len_g) for i in range(n)]
  4117. # initiate the coset transversal
  4118. transversal = [None]*n
  4119. transversal[0] = G_p.identity
  4120. # fill in the coset table as much as possible
  4121. for i in range(2*len_h):
  4122. C_p.table[0][i] = 0
  4123. gamma = 1
  4124. for alpha, x in product(range(n), range(2*len_g)):
  4125. beta = C[alpha][x]
  4126. if beta == gamma:
  4127. gen = G_p.generators[x//2]**((-1)**(x % 2))
  4128. transversal[beta] = transversal[alpha]*gen
  4129. C_p.table[alpha][x] = beta
  4130. C_p.table[beta][x + (-1)**(x % 2)] = alpha
  4131. gamma += 1
  4132. if gamma == n:
  4133. break
  4134. C_p.p = list(range(n))
  4135. beta = x = 0
  4136. while not C_p.is_complete():
  4137. # find the first undefined entry
  4138. while C_p.table[beta][x] == C[beta][x]:
  4139. x = (x + 1) % (2*len_g)
  4140. if x == 0:
  4141. beta = (beta + 1) % n
  4142. # define a new relator
  4143. gen = G_p.generators[x//2]**((-1)**(x % 2))
  4144. new_rel = transversal[beta]*gen*transversal[C[beta][x]]**-1
  4145. perm = T(new_rel)
  4146. nxt = G_p.identity
  4147. for s in H.generator_product(perm, original=True):
  4148. nxt = nxt*T.invert(s)**-1
  4149. new_rel = new_rel*nxt
  4150. # continue coset enumeration
  4151. G_p = _factor_group_by_rels(G_p, [new_rel])
  4152. C_p.scan_and_fill(0, new_rel)
  4153. C_p = G_p.coset_enumeration([], strategy="coset_table",
  4154. draft=C_p, max_cosets=n, incomplete=True)
  4155. self._fp_presentation = simplify_presentation(G_p)
  4156. return self._fp_presentation
  4157. def polycyclic_group(self):
  4158. """
  4159. Return the PolycyclicGroup instance with below parameters:
  4160. Explanation
  4161. ===========
  4162. * pc_sequence : Polycyclic sequence is formed by collecting all
  4163. the missing generators between the adjacent groups in the
  4164. derived series of given permutation group.
  4165. * pc_series : Polycyclic series is formed by adding all the missing
  4166. generators of ``der[i+1]`` in ``der[i]``, where ``der`` represents
  4167. the derived series.
  4168. * relative_order : A list, computed by the ratio of adjacent groups in
  4169. pc_series.
  4170. """
  4171. from sympy.combinatorics.pc_groups import PolycyclicGroup
  4172. if not self.is_polycyclic:
  4173. raise ValueError("The group must be solvable")
  4174. der = self.derived_series()
  4175. pc_series = []
  4176. pc_sequence = []
  4177. relative_order = []
  4178. pc_series.append(der[-1])
  4179. der.reverse()
  4180. for i in range(len(der)-1):
  4181. H = der[i]
  4182. for g in der[i+1].generators:
  4183. if g not in H:
  4184. H = PermutationGroup([g] + H.generators)
  4185. pc_series.insert(0, H)
  4186. pc_sequence.insert(0, g)
  4187. G1 = pc_series[0].order()
  4188. G2 = pc_series[1].order()
  4189. relative_order.insert(0, G1 // G2)
  4190. return PolycyclicGroup(pc_sequence, pc_series, relative_order, collector=None)
  4191. def _orbit(degree, generators, alpha, action='tuples'):
  4192. r"""Compute the orbit of alpha `\{g(\alpha) | g \in G\}` as a set.
  4193. Explanation
  4194. ===========
  4195. The time complexity of the algorithm used here is `O(|Orb|*r)` where
  4196. `|Orb|` is the size of the orbit and ``r`` is the number of generators of
  4197. the group. For a more detailed analysis, see [1], p.78, [2], pp. 19-21.
  4198. Here alpha can be a single point, or a list of points.
  4199. If alpha is a single point, the ordinary orbit is computed.
  4200. if alpha is a list of points, there are three available options:
  4201. 'union' - computes the union of the orbits of the points in the list
  4202. 'tuples' - computes the orbit of the list interpreted as an ordered
  4203. tuple under the group action ( i.e., g((1, 2, 3)) = (g(1), g(2), g(3)) )
  4204. 'sets' - computes the orbit of the list interpreted as a sets
  4205. Examples
  4206. ========
  4207. >>> from sympy.combinatorics import Permutation, PermutationGroup
  4208. >>> from sympy.combinatorics.perm_groups import _orbit
  4209. >>> a = Permutation([1, 2, 0, 4, 5, 6, 3])
  4210. >>> G = PermutationGroup([a])
  4211. >>> _orbit(G.degree, G.generators, 0)
  4212. {0, 1, 2}
  4213. >>> _orbit(G.degree, G.generators, [0, 4], 'union')
  4214. {0, 1, 2, 3, 4, 5, 6}
  4215. See Also
  4216. ========
  4217. orbit, orbit_transversal
  4218. """
  4219. if not hasattr(alpha, '__getitem__'):
  4220. alpha = [alpha]
  4221. gens = [x._array_form for x in generators]
  4222. if len(alpha) == 1 or action == 'union':
  4223. orb = alpha
  4224. used = [False]*degree
  4225. for el in alpha:
  4226. used[el] = True
  4227. for b in orb:
  4228. for gen in gens:
  4229. temp = gen[b]
  4230. if used[temp] == False:
  4231. orb.append(temp)
  4232. used[temp] = True
  4233. return set(orb)
  4234. elif action == 'tuples':
  4235. alpha = tuple(alpha)
  4236. orb = [alpha]
  4237. used = {alpha}
  4238. for b in orb:
  4239. for gen in gens:
  4240. temp = tuple([gen[x] for x in b])
  4241. if temp not in used:
  4242. orb.append(temp)
  4243. used.add(temp)
  4244. return set(orb)
  4245. elif action == 'sets':
  4246. alpha = frozenset(alpha)
  4247. orb = [alpha]
  4248. used = {alpha}
  4249. for b in orb:
  4250. for gen in gens:
  4251. temp = frozenset([gen[x] for x in b])
  4252. if temp not in used:
  4253. orb.append(temp)
  4254. used.add(temp)
  4255. return {tuple(x) for x in orb}
  4256. def _orbits(degree, generators):
  4257. """Compute the orbits of G.
  4258. If ``rep=False`` it returns a list of sets else it returns a list of
  4259. representatives of the orbits
  4260. Examples
  4261. ========
  4262. >>> from sympy.combinatorics import Permutation
  4263. >>> from sympy.combinatorics.perm_groups import _orbits
  4264. >>> a = Permutation([0, 2, 1])
  4265. >>> b = Permutation([1, 0, 2])
  4266. >>> _orbits(a.size, [a, b])
  4267. [{0, 1, 2}]
  4268. """
  4269. orbs = []
  4270. sorted_I = list(range(degree))
  4271. I = set(sorted_I)
  4272. while I:
  4273. i = sorted_I[0]
  4274. orb = _orbit(degree, generators, i)
  4275. orbs.append(orb)
  4276. # remove all indices that are in this orbit
  4277. I -= orb
  4278. sorted_I = [i for i in sorted_I if i not in orb]
  4279. return orbs
  4280. def _orbit_transversal(degree, generators, alpha, pairs, af=False, slp=False):
  4281. r"""Computes a transversal for the orbit of ``alpha`` as a set.
  4282. Explanation
  4283. ===========
  4284. generators generators of the group ``G``
  4285. For a permutation group ``G``, a transversal for the orbit
  4286. `Orb = \{g(\alpha) | g \in G\}` is a set
  4287. `\{g_\beta | g_\beta(\alpha) = \beta\}` for `\beta \in Orb`.
  4288. Note that there may be more than one possible transversal.
  4289. If ``pairs`` is set to ``True``, it returns the list of pairs
  4290. `(\beta, g_\beta)`. For a proof of correctness, see [1], p.79
  4291. if ``af`` is ``True``, the transversal elements are given in
  4292. array form.
  4293. If `slp` is `True`, a dictionary `{beta: slp_beta}` is returned
  4294. for `\beta \in Orb` where `slp_beta` is a list of indices of the
  4295. generators in `generators` s.t. if `slp_beta = [i_1 \dots i_n]`
  4296. `g_\beta = generators[i_n] \times \dots \times generators[i_1]`.
  4297. Examples
  4298. ========
  4299. >>> from sympy.combinatorics.named_groups import DihedralGroup
  4300. >>> from sympy.combinatorics.perm_groups import _orbit_transversal
  4301. >>> G = DihedralGroup(6)
  4302. >>> _orbit_transversal(G.degree, G.generators, 0, False)
  4303. [(5), (0 1 2 3 4 5), (0 5)(1 4)(2 3), (0 2 4)(1 3 5), (5)(0 4)(1 3), (0 3)(1 4)(2 5)]
  4304. """
  4305. tr = [(alpha, list(range(degree)))]
  4306. slp_dict = {alpha: []}
  4307. used = [False]*degree
  4308. used[alpha] = True
  4309. gens = [x._array_form for x in generators]
  4310. for x, px in tr:
  4311. px_slp = slp_dict[x]
  4312. for gen in gens:
  4313. temp = gen[x]
  4314. if used[temp] == False:
  4315. slp_dict[temp] = [gens.index(gen)] + px_slp
  4316. tr.append((temp, _af_rmul(gen, px)))
  4317. used[temp] = True
  4318. if pairs:
  4319. if not af:
  4320. tr = [(x, _af_new(y)) for x, y in tr]
  4321. if not slp:
  4322. return tr
  4323. return tr, slp_dict
  4324. if af:
  4325. tr = [y for _, y in tr]
  4326. if not slp:
  4327. return tr
  4328. return tr, slp_dict
  4329. tr = [_af_new(y) for _, y in tr]
  4330. if not slp:
  4331. return tr
  4332. return tr, slp_dict
  4333. def _stabilizer(degree, generators, alpha):
  4334. r"""Return the stabilizer subgroup of ``alpha``.
  4335. Explanation
  4336. ===========
  4337. The stabilizer of `\alpha` is the group `G_\alpha =
  4338. \{g \in G | g(\alpha) = \alpha\}`.
  4339. For a proof of correctness, see [1], p.79.
  4340. degree : degree of G
  4341. generators : generators of G
  4342. Examples
  4343. ========
  4344. >>> from sympy.combinatorics.perm_groups import _stabilizer
  4345. >>> from sympy.combinatorics.named_groups import DihedralGroup
  4346. >>> G = DihedralGroup(6)
  4347. >>> _stabilizer(G.degree, G.generators, 5)
  4348. [(5)(0 4)(1 3), (5)]
  4349. See Also
  4350. ========
  4351. orbit
  4352. """
  4353. orb = [alpha]
  4354. table = {alpha: list(range(degree))}
  4355. table_inv = {alpha: list(range(degree))}
  4356. used = [False]*degree
  4357. used[alpha] = True
  4358. gens = [x._array_form for x in generators]
  4359. stab_gens = []
  4360. for b in orb:
  4361. for gen in gens:
  4362. temp = gen[b]
  4363. if used[temp] is False:
  4364. gen_temp = _af_rmul(gen, table[b])
  4365. orb.append(temp)
  4366. table[temp] = gen_temp
  4367. table_inv[temp] = _af_invert(gen_temp)
  4368. used[temp] = True
  4369. else:
  4370. schreier_gen = _af_rmuln(table_inv[temp], gen, table[b])
  4371. if schreier_gen not in stab_gens:
  4372. stab_gens.append(schreier_gen)
  4373. return [_af_new(x) for x in stab_gens]
  4374. PermGroup = PermutationGroup
  4375. class SymmetricPermutationGroup(Basic):
  4376. """
  4377. The class defining the lazy form of SymmetricGroup.
  4378. deg : int
  4379. """
  4380. def __new__(cls, deg):
  4381. deg = _sympify(deg)
  4382. obj = Basic.__new__(cls, deg)
  4383. return obj
  4384. def __init__(self, *args, **kwargs):
  4385. self._deg = self.args[0]
  4386. self._order = None
  4387. def __contains__(self, i):
  4388. """Return ``True`` if *i* is contained in SymmetricPermutationGroup.
  4389. Examples
  4390. ========
  4391. >>> from sympy.combinatorics import Permutation, SymmetricPermutationGroup
  4392. >>> G = SymmetricPermutationGroup(4)
  4393. >>> Permutation(1, 2, 3) in G
  4394. True
  4395. """
  4396. if not isinstance(i, Permutation):
  4397. raise TypeError("A SymmetricPermutationGroup contains only Permutations as "
  4398. "elements, not elements of type %s" % type(i))
  4399. return i.size == self.degree
  4400. def order(self):
  4401. """
  4402. Return the order of the SymmetricPermutationGroup.
  4403. Examples
  4404. ========
  4405. >>> from sympy.combinatorics import SymmetricPermutationGroup
  4406. >>> G = SymmetricPermutationGroup(4)
  4407. >>> G.order()
  4408. 24
  4409. """
  4410. if self._order is not None:
  4411. return self._order
  4412. n = self._deg
  4413. self._order = factorial(n)
  4414. return self._order
  4415. @property
  4416. def degree(self):
  4417. """
  4418. Return the degree of the SymmetricPermutationGroup.
  4419. Examples
  4420. ========
  4421. >>> from sympy.combinatorics import SymmetricPermutationGroup
  4422. >>> G = SymmetricPermutationGroup(4)
  4423. >>> G.degree
  4424. 4
  4425. """
  4426. return self._deg
  4427. @property
  4428. def identity(self):
  4429. '''
  4430. Return the identity element of the SymmetricPermutationGroup.
  4431. Examples
  4432. ========
  4433. >>> from sympy.combinatorics import SymmetricPermutationGroup
  4434. >>> G = SymmetricPermutationGroup(4)
  4435. >>> G.identity()
  4436. (3)
  4437. '''
  4438. return _af_new(list(range(self._deg)))
  4439. class Coset(Basic):
  4440. """A left coset of a permutation group with respect to an element.
  4441. Parameters
  4442. ==========
  4443. g : Permutation
  4444. H : PermutationGroup
  4445. dir : "+" or "-", If not specified by default it will be "+"
  4446. here ``dir`` specified the type of coset "+" represent the
  4447. right coset and "-" represent the left coset.
  4448. G : PermutationGroup, optional
  4449. The group which contains *H* as its subgroup and *g* as its
  4450. element.
  4451. If not specified, it would automatically become a symmetric
  4452. group ``SymmetricPermutationGroup(g.size)`` and
  4453. ``SymmetricPermutationGroup(H.degree)`` if ``g.size`` and ``H.degree``
  4454. are matching.``SymmetricPermutationGroup`` is a lazy form of SymmetricGroup
  4455. used for representation purpose.
  4456. """
  4457. def __new__(cls, g, H, G=None, dir="+"):
  4458. g = _sympify(g)
  4459. if not isinstance(g, Permutation):
  4460. raise NotImplementedError
  4461. H = _sympify(H)
  4462. if not isinstance(H, PermutationGroup):
  4463. raise NotImplementedError
  4464. if G is not None:
  4465. G = _sympify(G)
  4466. if not isinstance(G, (PermutationGroup, SymmetricPermutationGroup)):
  4467. raise NotImplementedError
  4468. if not H.is_subgroup(G):
  4469. raise ValueError("{} must be a subgroup of {}.".format(H, G))
  4470. if g not in G:
  4471. raise ValueError("{} must be an element of {}.".format(g, G))
  4472. else:
  4473. g_size = g.size
  4474. h_degree = H.degree
  4475. if g_size != h_degree:
  4476. raise ValueError(
  4477. "The size of the permutation {} and the degree of "
  4478. "the permutation group {} should be matching "
  4479. .format(g, H))
  4480. G = SymmetricPermutationGroup(g.size)
  4481. if isinstance(dir, str):
  4482. dir = Symbol(dir)
  4483. elif not isinstance(dir, Symbol):
  4484. raise TypeError("dir must be of type basestring or "
  4485. "Symbol, not %s" % type(dir))
  4486. if str(dir) not in ('+', '-'):
  4487. raise ValueError("dir must be one of '+' or '-' not %s" % dir)
  4488. obj = Basic.__new__(cls, g, H, G, dir)
  4489. return obj
  4490. def __init__(self, *args, **kwargs):
  4491. self._dir = self.args[3]
  4492. @property
  4493. def is_left_coset(self):
  4494. """
  4495. Check if the coset is left coset that is ``gH``.
  4496. Examples
  4497. ========
  4498. >>> from sympy.combinatorics import Permutation, PermutationGroup, Coset
  4499. >>> a = Permutation(1, 2)
  4500. >>> b = Permutation(0, 1)
  4501. >>> G = PermutationGroup([a, b])
  4502. >>> cst = Coset(a, G, dir="-")
  4503. >>> cst.is_left_coset
  4504. True
  4505. """
  4506. return str(self._dir) == '-'
  4507. @property
  4508. def is_right_coset(self):
  4509. """
  4510. Check if the coset is right coset that is ``Hg``.
  4511. Examples
  4512. ========
  4513. >>> from sympy.combinatorics import Permutation, PermutationGroup, Coset
  4514. >>> a = Permutation(1, 2)
  4515. >>> b = Permutation(0, 1)
  4516. >>> G = PermutationGroup([a, b])
  4517. >>> cst = Coset(a, G, dir="+")
  4518. >>> cst.is_right_coset
  4519. True
  4520. """
  4521. return str(self._dir) == '+'
  4522. def as_list(self):
  4523. """
  4524. Return all the elements of coset in the form of list.
  4525. """
  4526. g = self.args[0]
  4527. H = self.args[1]
  4528. cst = []
  4529. if str(self._dir) == '+':
  4530. for h in H.elements:
  4531. cst.append(h*g)
  4532. else:
  4533. for h in H.elements:
  4534. cst.append(g*h)
  4535. return cst