perm_groups.py 180 KB

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