-- Copyright (c) 2011, David Amos. All rights reserved.{-# LANGUAGE NoMonomorphismRestriction, DeriveFunctor #-}-- |A module providing functions to construct and investigate (small, finite) matroids.moduleMath.Combinatorics.Matroidwhere-- Source: Oxley, Matroid Theory (second edition)importMath.Core.UtilsimportMath.Core.Fieldhiding(f7)importMath.Common.ListSetasLS-- set operations on strictly ascending lists-- import Math.Algebra.Field.Base hiding (Q, F2, F3, F5, F7, F11, f2, f3, f5, f7, f11)importMath.Algebra.LinearAlgebrahiding(rank,(*>))importqualifiedMath.Combinatorics.GraphasG-- hiding (combinationsOf, restriction, component, isConnected)importMath.Combinatorics.FiniteGeometryimportMath.Combinatorics.GraphAutsimportMath.Algebra.Group.PermutationGrouphiding(closure)importMath.Algebras.VectorSpacehiding(dual)importMath.Algebras.StructuresimportMath.Algebras.CommutativeimportData.ListasLimportqualifiedData.MapasMimportqualifiedData.SetasSimpliespq=q||notpexists=not.nullunique[x]=xshortlexxsys=casecompare(lengthxs)(lengthys)ofLT->LTEQ->comparexsysGT->GTisShortlexxs=foldcmpl(\x1x2->shortlexx1x2/=GT)xstoShortlexxs=mapsnd$L.sort[(lengthx,x)|x<-xs]isClutterss=and[(s1`LS.isSubset`s2)`implies`(s1==s2)|s1<-ss,s2<-ss]-- note that this definition would allow duplicates-- single-element deletions of xs-- if xs is sorted, then so is reverse (deletions xs)deletionsxs=zipWith(++)(initsxs)(tail$tailsxs)closedUnderSubsetsxss=and[xs'`S.member`xss'|xs<-xss,xs'<-deletionsxs]wherexss'=S.fromListxss-- |The data structure that we use to store the bases of the matroiddataTrieSeta=TS[(a,TrieSeta)]deriving(Eq,Ord,Functor)-- Note that in a trie we would normally have a Bool at each node saying whether or not the node is terminal-- (ie corresponds to a member and not just a prefix of a member).-- However, since we intend to use the trie to store the bases, and they are all the same length,-- we can use lack of children to detect that a node is terminal.-- We could try storing the children in a map rather than a list-- for debuggingtsshow(TSxts)="TS ["++concatMap(\(x,t)->"("++showx++","++tsshowt++")")xts++"]"instanceShowa=>Show(TrieSeta)whereshow=show.tstolisttsempty=TS[]tsinsert(x:xs)(TSts)=caseL.lookupxtsofNothing->lett=tsinsertxs(TS[])inTS$L.insert(x,t)tsJustt->lett'=tsinsertxstinTS$L.insert(x,t')$L.delete(x,t)tstsinsert[]t=ttsmember(x:xs)(TSts)=caselookupxtsofNothing->FalseJustt->tsmemberxsttsmember[](TS[])=True-- the node has no children, and hence is terminaltsmember[]_=False-- xs is a subset of a member of ttssubmember(x:xs)(TSts)=or[casecomparexyofLT->FalseEQ->tssubmemberxstGT->tssubmember(x:xs)t|(y,t)<-ts]tssubmember[]_=Truetstolist(TS[])=[[]]tstolist(TSxts)=concatMap(\(x,t)->map(x:)(tstolistt))xtstsfromlist=foldl'(fliptsinsert)tsempty-- |A datatype to represent a matroid. @M es bs@ is the matroid whose elements are @es@, and whose bases are @bs@.-- The normal form is for the @es@ to be in order, for each of the @bs@ individually to be in order.-- (So the TrieSet should have the property that any path from the root to a leaf is strictly increasing).dataMatroida=M[a](TrieSeta)deriving(Eq,Show,Functor)-- |Return the elements over which the matroid is defined.elements::Matroidt->[t]elements(Mesbs)=es-- |Return all the independent sets of a matroid, in shortlex order.indeps::(Orda)=>Matroida->[[a]]indepsm=bfs[([],es)]wherees=elementsmbfs((ls,rs):nodes)=letls'=reverselsinifisIndependentmls'thenls':bfs(nodes++successors(ls,rs))elsebfsnodesbfs[]=[]successors(ls,rs)=[(r:ls,rs')|r:rs'<-L.tailsrs]isIndependent::(Orda)=>Matroida->[a]->BoolisIndependent(Mesbs)xs=xs`tssubmember`bsisDependent::(Orda)=>Matroida->[a]->BoolisDependentm=not.isIndependentm-- |Are these the independent sets of a matroid? (The sets must individually be ordered.)isMatroidIndeps::(Orda)=>[[a]]->BoolisMatroidIndepsis=[]`elem`is&&closedUnderSubsetsis&&and[(l1<l2)`implies`exists[e|e<-i2LS.\\i1,L.insertei1`elem`is]|i1<-is,letl1=lengthi1,i2<-is,letl2=lengthi2]-- |Construct a matroid from its elements and its independent sets.fromIndeps::(Orda)=>[a]->[[a]]->MatroidafromIndepsesis=fromBasesesbswherebs=dfs[][([],es)]dfsbs(node@(ls,rs):nodes)=letsuccs=successorsnodeinifnullsuccsthendfs(ls:bs)nodeselsedfsbs(succs++nodes)dfsls[]=letr=length$lastls-- we know that the first one we found is a true baseinmapreverse$filter(\b->lengthb==r)ls-- we might have had null succs simply because we ran out of vectors to addsuccessors(ls,rs)=[(r:ls,rs')|r:rs'<-L.tailsrs,(r:ls)`S.member`is']is'=S.fromList$mapreverseis-- this seems to be just slightly slowerfromIndeps1esis=fromBasesesbswhereb=greedy[]es-- first find any basisgreedyls(r:rs)=if(r:ls)`S.member`ris'thengreedy(r:ls)rselsegreedylsrsgreedyls[]=reverselsris'=S.fromList$mapreverseisbs=closureS.empty(S.singletonb)-- now find all other bases by passing to "neighbouring" basesclosureinteriorboundary=ifS.nullboundarythenS.toListinteriorelseletinterior'=interior`S.union`boundaryboundary'=S.fromList[b'|b<-S.toListboundary,x<-b,y<-esLS.\\b,letb'=L.inserty(L.deletexb),b'`S.notMember`interior',b'`S.member`is']inclosureinterior'boundary'is'=S.fromListis-- The basis properties imply that the set of all bases is connected under the "neighbour" relation-- |Given a matrix, represented as a list of rows, number the columns [1..],-- and construct the matroid whose independent sets correspond to those sets of columns which are linearly independent-- (or in case there are repetitions, those multisets of columns which are sets, and which are linearly independent).vectorMatroid::(Eqk,Fractionalk)=>[[k]]->MatroidIntvectorMatroid=vectorMatroid'.L.transpose-- |Given a list of vectors (or rows of a matrix), number the vectors (rows) [1..], and construct the matroid whose independent sets-- correspond to those sets of vectors (rows) which are linearly independent-- (or in case there are repetitions, those multisets which are sets, and which are linearly independent).vectorMatroid'::(Eqk,Fractionalk)=>[[k]]->MatroidIntvectorMatroid'vs=fromBases(mapfstvs')bswherevs'=zip[1..]vsbs=dfs[][([],[],vs')]dfsls(r@(i,ref,es):rs)=letsuccs=successorsrinifnullsuccsthendfs(i:ls)(succs++rs)elsedfsls(succs++rs)dfsls[]=letr=length$lastls-- we know that the first one we found is a true baseinmapreverse$filter(\b->lengthb==r)ls-- we might have had null succs simply because we ran out of vectors to addsuccessors(i,ref,es)=[(i',ref',es')|(j,e):es'<-L.tailses,not(inSpanRErefe),letref'=rowEchelonForm(ref++[e]),-- is this really better than e:ref?leti'=j:i]-- |Given the edges of an undirected graph, number the edges [1..], and construct the matroid whose independent sets-- correspond to those sets of edges which contain no cycle. The bases therefore correspond to maximal forests within the graph.-- The edge set is allowed to contain loops or parallel edges.cycleMatroid::(Orda)=>[[a]]->MatroidIntcycleMatroides=fromBases(mapfstes')bswherees'=zip[1..]esbs=dfs[][([],M.empty,es')]dfsls(r@(i,ref,es):rs)=letsuccs=successorsrinifnullsuccsthendfs(i:ls)(succs++rs)elsedfsls(succs++rs)dfsls[]=letr=length$lastls-- we know that the first one we found is a true baseinmapreverse$filter(\b->lengthb==r)ls-- we might have had null succs simply because we ran out of edges to addsuccessors(i,reps,(j,[u,v]):es')=ifu==vthensuccessors(i,reps,es')elsecase(M.lookupureps,M.lookupvreps)of(Nothing,Nothing)->(j:i,M.insertuu$M.insertvureps,es'):successors(i,reps,es')-- neither of these vertices has been seen before, so add this edge as a new tree in the forest(Justu',Nothing)->(j:i,M.insertvu'reps,es'):successors(i,reps,es')-- we have seen u before but not v, so add v to the u-tree(Nothing,Justv')->(j:i,M.insertuv'reps,es'):successors(i,reps,es')-- we have seen v before but not u, so add u to the v-tree(Justu',Justv')->ifu'==v'thensuccessors(i,reps,es')-- u and v are already in the same tree, so adding this edge would make a cycleelse(j:i,M.map(\w->ifw==v'thenu'elsew)reps,es'):successors(i,reps,es')-- u and v are in different trees, so join the trees togethersuccessors(_,_,[])=[]-- A version of cycle matroid that retains the original edges rather than relabeling with integers.-- Not really valid if there are parallel edges (because they can't be distinguished in the output).-- Required for "markedfcim" below.cycleMatroid'es=fmaplookupEdge$cycleMatroideswheretable=M.fromList$zip[1..]eslookupEdge=(M.!)table-- |Given a matroid over an arbitrary type, relabel to obtain a matroid over the integers.to1n::(Orda)=>Matroida->MatroidIntto1nm=fmapto1n'mwherees=elementsmtable=M.fromList$zipes[1..]to1n'=(M.!)table-- ISOMORPHISMS AND AUTOMORPHISMSincidenceGraphBm=G.Gvs'es'wherees=elementsmbs=basesmvs'=mapLeftes++mapRightbses'=L.sort[[Lefte,Rightb]|b<-bs,e<-b]-- incidence graph for the matroid considered as an incidence structure between elements and basesincidenceGraphCm=G.Gvs'es'wherees=elementsmcs=L.sort$circuitsmvs'=mapLeftes++mapRightcses'=L.sort[[Lefte,Rightc]|c<-cs,e<-c]-- incidence graph for the matroid considered as an incidence structure between elements and circuitsincidenceGraphHm=G.Gvs'es'wherees=elementsmhs=L.sort$hyperplanesmvs'=mapLeftes++mapRighthses'=L.sort[[Lefte,Righth]|h<-hs,e<-h]-- incidence graph for the matroid considered as an incidence structure between elements and hyperplanes-- for "sparse" matroids, there are likely to be fewer hyperplanes than bases (why?)-- incidenceGraphH may not be connected - eg u 2 4-- (So a rank 3 or higher matroid, provided it has more than one hyperplane, certainly has a connected incidenceGraphH)matroidIsosm1m2=incidenceIsos(incidenceGraphHm1)(incidenceGraphHm2)-- |Are the two matroids isomorphic?isMatroidIso::(Orda,Ordb)=>Matroida->Matroidb->BoolisMatroidIsom1m2=isIncidenceIso(incidenceGraphHm1)(incidenceGraphHm2)-- |Return the automorphisms of the matroid.matroidAuts::(Orda)=>Matroida->[Permutationa]matroidAutsm=incidenceAuts$incidenceGraphHm-- Note that the results aren't always what one intuitively expects from the geometric representation.-- This is because geometric representations suggest additional structure beyond matroid structure.-- For example, for the Vamos matroid v8,-- it returns auts which are not "geometric" auts of the geometric representation.-- Matroids are really combinatorial objects, not geometric.-- In the case of v8, the key is that the only thing the auts have to preserve are the planes, not the lines-- CIRCUITS-- |A circuit in a matroid is a minimal dependent set.isCircuit::(Orda)=>Matroida->[a]->BoolisCircuitmc=isDependentmc&&all(isIndependentm)(deletionsc)-- |Return all circuits for the given matroid, in shortlex order.circuits::(Orda)=>Matroida->[[a]]circuitsm=toShortlex$dfsS.empty[L.inserteb|b<-bs,e<-esLS.\\b]wherees=elementsmbs=basesmdfsvs(c:cs)|c`S.member`vs=dfsvscs|otherwise=letcs'=successorscvs'=S.insertcvsinifnullcs'thenc:dfsvs'cselsedfsvs'(cs'++cs)dfs_[]=[]successorsc=[c'|c'<-deletionsc,isDependentmc']-- Oxley p10-- |Are the given sets the circuits of some matroid?isMatroidCircuits::(Orda)=>[[a]]->BoolisMatroidCircuitscs=[]`notElem`cs&&and[(c1`LS.isSubset`c2)`implies`(c1==c2)|c1<-cs,c2<-cs]&&and[exists[c3|c3<-cs,c3`LS.isSubset`c12']|c1<-cs,c2<-cs,c1/=c2,e<-c1`LS.intersect`c2,letc12'=L.deletee(c1`LS.union`c2)]-- |Reconstruct a matroid from its elements and circuits.fromCircuits::(Orda)=>[a]->[[a]]->MatroidafromCircuitsescs=fromBasesesbswhereb=greedy[]es-- first find any basisgreedyls(r:rs)=letls'=ls++[r]inifisIndepls'thengreedyls'rselsegreedylsrsgreedyls[]=lsbs=closureS.empty(S.singletonb)-- now find all other bases by passing to "neighbouring" basesclosureinteriorboundary=ifS.nullboundarythenS.toListinteriorelseletinterior'=interior`S.union`boundaryboundary'=S.fromList[b'|b<-S.toListboundary,x<-b,y<-esLS.\\b,letb'=L.inserty(L.deletexb),b'`S.notMember`interior',isIndepb']inclosureinterior'boundary'isIndepxs=not(any(`LS.isSubset`xs)cs)-- |An element e in a matroid M is a loop if {e} is a circuit of M.isLoop::(Orda)=>Matroida->a->BoolisLoopme=isCircuitm[e]-- isLoop (M es is) e = [e] `notElem` is-- |Elements f and g in a matroid M are parallel if {f,g} is a circuit of M.isParallel::(Orda)=>Matroida->a->a->BoolisParallelmfg=isCircuitm[f,g]-- |A matroid is simple if it has no loops or parallel elementsisSimple::(Orda)=>Matroida->BoolisSimplem=all((>2).length)(circuitsm)-- BASES-- |A base or basis in a matroid is a maximal independent set.isBase::(Orda)=>Matroida->[a]->BoolisBase(Mesbs)b=b`tsmember`bs-- |Return all bases for the given matroidbases::(Orda)=>Matroida->[[a]]bases(Mesbs)=tstolistbs-- |Are the given sets the bases of some matroid?isMatroidBases::(Orda)=>[[a]]->BoolisMatroidBasesbs=(not.null)bs&&and[exists[y|y<-b2LS.\\b1,L.inserty(L.deletexb1)`elem`bs]|b1<-bs,b2<-bs,x<-b1LS.\\b2]-- |Reconstruct a matroid from its elements and bases.fromBases::(Orda)=>[a]->[[a]]->MatroidafromBasesesbs=Mes(tsfromlistbs)-- The elements are required because a loop does not appear in any basis.-- Oxley p17-- |Given a matroid m, a basis b, and an element e, @fundamentalCircuit m b e@ returns the unique circuit contained in b union {e},-- which is called the fundamental circuit of e with respect to b.fundamentalCircuit::(Orda)=>Matroida->[a]->a->[a]fundamentalCircuitmbe=unique[c|c<-circuitsm,c`LS.isSubset`be]wherebe=L.insertebuniformMatroidmn|m<=n=fromBasesesbswherees=[1..n]bs=combinationsOfmes-- |The uniform matroid U m n is the matroid whose independent sets are all subsets of [1..n] with m or fewer elements.u::Int->Int->MatroidIntu=uniformMatroid-- RANK FUNCTION-- Oxley p103, 3.1.14restriction1mxs=fromBasesxsbs'wherebs=basesmis'=toShortlex[b`LS.intersect`xs|b<-bs]r=length$lastis'bs'=dropWhile((<r).length)is'-- |The restriction of a matroid to a subset of its elementsrestriction::(Orda)=>Matroida->[a]->Matroidarestrictionm@(Mesbs)xs=Mxsbs'where(_,bs')=balance$prunebsprune(TSyts)=let(ins,outs)=L.partition(\(y,t)->y`elem`xs)ytsins'=[(y,prunet)|(y,t)<-ins]outs'=concat[zts|(y,t)<-outs,letTSzts=prunet]inTS$ins'++outs'balance(TSyts)=letdyt's=[(d',(y,t'))|(y,t)<-yts,let(d',t')=balancet]d=maximum$0:mapfstdyt'sin(d+1,TS$toSet[(y,t')|(d',(y,t'))<-dyt's,d'==d])-- we have to make the toSet call *after* we balance, otherwise two trees may appear unequal because of undergrowth that is going to be removed-- !! Need thorough testing to prove that restriction == restriction1-- |Given a matroid m, @rankfun m@ is the rank function on subsets of its element setrankfun::(Orda)=>Matroida->[a]->Intrankfunmxs=(length.head.bases)(restrictionmxs)-- no danger of head [], because bases must be non-null-- |The rank of a matroid is the cardinality of a basisrank::(Orda)=>Matroida->Intrankm=length$head$basesm-- rank m@(M es bs) = rankfun m es-- Oxley p23-- |Reconstruct a matroid from its elements and rank functionfromRankfun::(Orda)=>[a]->([a]->Int)->MatroidafromRankfunesrkf=fromBasesesbswhereb=greedy0[]es-- first find any basisgreedyrkls(r:rs)=letls'=ls++[r]inifrkfls'==rk+1thengreedy(rk+1)ls'rselsegreedyrklsrsgreedy_ls[]=lsrk=rkfbisBasisb'=rkfb'==rkbs=closureS.empty(S.singletonb)S.empty-- now find all other bases by passing to "neighbouring" basesclosureinteriorboundaryexterior=ifS.nullboundarythenS.toListinteriorelseletinterior'=interior`S.union`boundarycandidates=S.fromList[b'|b<-S.toListboundary,x<-b,y<-esLS.\\b,letb'=L.inserty(L.deletexb),b'`S.notMember`interior',b'`S.notMember`exterior](boundary',exterior')=S.partitionisBasiscandidatesinclosureinterior'boundary'(S.unionexteriorexterior')-- The purpose of keeping track of exterior points we have encountered-- is to avoid making repeat calls to the rank function rkf-- CLOSURE OPERATOR AND FLATS-- |Given a matroid m, @closure m@ is the closure operator on subsets of its element setclosure::(Orda)=>Matroida->[a]->[a]closuremxs=[x|x<-es,x`elem`xs||rankfunm(L.insertxxs)==rankxs]wherees=elementsmrankxs=rankfunmxs-- The intuition is that closure xs is all elements within the span of xs-- |Reconstruct a matroid from its elements and closure operatorfromClosure::(Orda)=>[a]->([a]->[a])->MatroidafromClosureescl=fromBasesesbswhereb=greedy(cl[])[]es-- first find any basisgreedyspanls(r:rs)=letls'=ls++[r]inifr`notElem`span-- r is independent relative to lsthengreedy(clls')ls'rselsegreedyspanlsrsgreedy_ls[]=lsrk=lengthbisBasisb'=clb'==esbs=closureS.empty(S.singletonb)S.empty-- now find all other bases by passing to "neighbouring" basesclosureinteriorboundaryexterior=ifS.nullboundarythenS.toListinteriorelseletinterior'=interior`S.union`boundarycandidates=S.fromList[b'|b<-S.toListboundary,x<-b,y<-esLS.\\b,letb'=L.inserty(L.deletexb),b'`S.notMember`interior',b'`S.notMember`exterior](boundary',exterior')=S.partitionisBasiscandidatesinclosureinterior'boundary'(S.unionexteriorexterior')-- The purpose of keeping track of exterior points we have encountered-- is to avoid making repeat calls to the closure operator cl{-
-- Not quite sure why the following is so much slower than the above
-- May just be because Data.Set is compiled, and this would be comparable if also compiled
fromClosure2 es cl = M es bs
where b = greedy (cl []) [] es -- first find any basis
greedy span ls (r:rs) = let ls' = ls ++ [r] in
if r `notElem` span -- r is independent relative to ls
then greedy (cl ls') ls' rs
else greedy span ls rs
greedy _ ls [] = ls
rk = length b
isBasis b' = cl b' == es
bs = closure tsempty [b] tsempty -- now find all other bases by passing to "neighbouring" bases
closure interior boundary exterior =
if null boundary
then interior
else let interior' = foldl' (flip tsinsert) interior boundary
candidates = [ b' | b <- boundary,
x <- b, y <- es LS.\\ b,
let b' = L.insert y (L.delete x b),
not (b' `tsmember` interior'),
not (b' `tsmember` exterior) ]
(boundary', exterior') = L.partition isBasis candidates
in closure interior' boundary' (foldl' (flip tsinsert) exterior exterior')
-}-- |A flat in a matroid is a closed set, that is a set which is equal to its own closureisFlat::(Orda)=>Matroida->[a]->BoolisFlatmxs=closuremxs==xsflats1m=[xs|xs<-powersetbfses,isFlatmxs]wherees=elementsm-- first, inefficient, implementation-- given xs, a flat in m, return the flats which cover xs-- these have the property that they partition es \\ xscoveringFlatsmxs=coveringFlats'(esLS.\\xs)wherees=elementsmcoveringFlats'(y:ys)=letzs=closurem(L.insertyxs)inzs:coveringFlats'(ysLS.\\zs)coveringFlats'[]=[]-- since we are dealing with finite matroids, the lattice of flats is finite, so it has a minimal elementminimalFlatm=head$filter(isFlatm)$powersetbfs$elementsm-- |The flats of a matroid are its closed sets. They form a lattice under inclusion.flats::(Orda)=>Matroida->[[a]]flatsm=flats'S.empty[minimalFlatm]whereflats'ls(r:rs)=ifr`S.member`lsthenflats'lsrselseflats'(S.insertrls)(rs++coveringFlatsmr)flats'ls[]=toShortlex$S.toListls-- this is just breadth-first search-- isMatroidFlats-- Oxley p31-32{-
isMatroidFlats es fs =
es `elem` fs &&
[ (f1 `LS.intersect` f2) `elem` fs | f1 <- fs, f2 <- fs ] &&
-- for all flats f, the minimal flats containing f partition es-f
-}-- |Reconstruct a matroid from its flats. (The flats must be given in shortlex order.)fromFlats::(Orda)=>[[a]]->MatroidafromFlatsfs|isShortlexfs=fromFlats'fs|otherwise=error"fromFlats: flats must be in shortlex order"fromFlats'fs=fromClosureesclwherees=lastfs-- es is a flat, and last in shortlex orderclxs=head[f|f<-fs,xs`LS.isSubset`f]-- the first flat is minimal, because of shortlex order-- !! we can probably do better (efficiency-wise)-- eg by constructing the lattice as a DAG, and climbing up it-- |A subset of the elements in a matroid is spanning if its closure is all the elementsisSpanning::(Orda)=>Matroida->[a]->BoolisSpanningmxs=closuremxs==eswherees=elementsm-- |A hyperplane is a flat whose rank is one less than that of the matroidisHyperplane::(Orda)=>Matroida->[a]->BoolisHyperplanemxs=isFlatmxs&&rankfunmxs==rankm-1hyperplanes1m=[h|h<-flatsm,rankfunmh==rk-1]whererk=rankm-- Oxley p65: h is a hyperplane iff its complement is a cocircuithyperplanes::(Orda)=>Matroida->[[a]]hyperplanesm=toShortlex$mapcomplement$cocircuitsmwherees=elementsmcomplementcc=esLS.\\cc-- This appears to be faster than hyperplanes1-- Oxley p70isMatroidHyperplanes::(Orda)=>[a]->[[a]]->BoolisMatroidHyperplaneseshs=es`notElem`hs&&isClutterhs&&and[exists[h3|h3<-hs,h12e`LS.isSubset`h3]|(h1,h2)<-pairshs,e<-esLS.\\(LS.unionh1h2),leth12e=L.inserte(LS.intersecth1h2)]-- Note that contrary to what one might initially think-- it does *not* follow from the third condition that every element is in some hyperplane-- since there might be only one hyperplane, eg fromBases [1,2] [[2]] has [1] as it's only hyperplane-- Haven't actually proven that the following is always correct-- but it seems to workfromHyperplanes1eshs=fromFlats$closureS.empty(S.fromLisths)whereclosureinteriorboundary=ifS.nullboundarythen(toShortlex$S.toListinterior)++[es]elseletinterior'=S.unioninteriorboundaryboundary'=S.fromList[f1`LS.intersect`f2|(f1,f2)<-pairs(S.toListboundary)]S.\\interior'inclosureinterior'boundary'-- |Reconstruct a matroid from its elements and hyperplanesfromHyperplanes::(Orda)=>[a]->[[a]]->MatroidafromHyperplaneseshs=fromCocircuitses$mapcomplementhswherefromCocircuitses=dual.fromCircuitsescomplementxs=esLS.\\xs-- GEOMETRIC REPRESENTATION-- |Given a list of points in k^n, number the points [1..], and construct the matroid whose independent sets-- correspond to those sets of points which are affinely independent.---- A multiset of points in k^n is said to be affinely dependent if it contains two identical points,-- or three collinear points, or four coplanar points, or ... - and affinely independent otherwise.affineMatroid::(Eqk,Fractionalk)=>[[k]]->MatroidIntaffineMatroidvs=vectorMatroid'$map(1:)vs-- |fromGeoRep returns a matroid from a geometric representation consisting of dependent flats of various ranks.-- Given lists of dependent rank 0 flats (loops), rank 1 flats (points), rank 2 flats (lines) and rank 3 flats (planes),-- @fromGeoRep loops points lines planes@ returns the matroid having these as dependent flats.-- Note that if all the elements lie in the same plane, then this should still be listed as an argument.fromGeoRep::(Orda)=>[[a]]->[[a]]->[[a]]->[[a]]->MatroidafromGeoReploopspointslinesplanes=fromCircuitses$minimal$loops++concatMap(combinationsOf2)points++concatMap(combinationsOf3)lines++concatMap(combinationsOf4)planes++combinationsOf5eswherees=toSet$concatloops++concatpoints++concatlines++concatplanes-- Note that we don't check that the inputs are valid-- xss assumed to be in shortlex orderminimalxss=minimal'[]xsswhereminimal'ls(r:rs)=ifany(`LS.isSubset`r)lsthenminimal'lsrselseminimal'(r:ls)rsminimal'ls[]=reversels-- |A simple matroid has no loops or parallel elements, hence its geometric representation has no loops or dependent points.-- @simpleFromGeoRep lines planes@ returns the simple matroid having these dependent flats.simpleFromGeoRep::(Orda)=>[[a]]->[[a]]->MatroidasimpleFromGeoReplinesplanes=fromGeoRep[][]linesplanes-- Oxley p37-8isSimpleGeoReplinesplanes=all((<=1).length)[l1`LS.intersect`l2|(l1,l2)<-pairslines]&&all(\i->lengthi<=2||i`elem`lines)[p1`LS.intersect`p2|(p1,p2)<-pairsplanes]&&and[any(u`LS.isSubset`)planes|(l1,l2)<-pairslines,length(l1`LS.intersect`l2)==1,letu=l1`LS.union`l2]&&and[lengthi==1||i==l|l<-lines,p<-planes,leti=l`LS.intersect`p]isCircuitHyperplanemxs=isCircuitmxs&&isHyperplanemxs-- |List the circuit-hyperplanes of a matroid.circuitHyperplanes::(Orda)=>Matroida->[[a]]circuitHyperplanesm=[h|h<-hyperplanesm,isCircuitmh]-- Oxley p39-- |Given a matroid m, and a set of elements b which is both a circuit and a hyperplane in m,-- then @relaxation m b@ is the matroid which is obtained by adding b as a new basis.-- This corresponds to removing b from the geometric representation of m.relaxation::(Orda)=>Matroida->[a]->Matroidarelaxationmb|isCircuitHyperplanemb=fromBasesesbs|otherwise=error"relaxation: not a circuit-hyperplane"wherees=elementsmbs=b:basesm-- TRANSVERSAL MATROIDSex161=[[1,2,6],[3,4,5,6],[2,3],[2,4,6]]-- the edges of the bipartite graphtransversalGraphas=[(Leftx,Righti)|(a,i)<-zipas[1..],x<-a]partialMatchingses=dfs[(S.empty,[],es)]wheredfs(node@(vs,ls,rs):nodes)=ls:dfs(successorsnode++nodes)dfs[]=[]successors(vs,ls,rs)=[(S.insertu$S.insertvvs,L.insertrls,rs')|r@(u,v):rs'<-L.tailsrs,u`S.notMember`vs,v`S.notMember`vs]-- |Given a set of elements es, and a sequence as = [a1,...,am] of subsets of es,-- return the matroid whose independent sets are the partial transversals of the as.transversalMatroid::(Orda)=>[a]->[[a]]->MatroidatransversalMatroidesas=fromBasesesbswhereis@(i:_)=reverse$toShortlex$toSet$(map.map)(unLeft.fst)$partialMatchings(transversalGraphas)unLeft(Leftx)=xl=lengthibs=reverse$takeWhile((==l).length)is-- In this case, as is called a presentation of the matroid-- Note that there may be partial transversals even if there are no transversals-- Not obvious how to efficiently find the bases without finding the independent sets,-- since neighbouring partial transversals might arise in different ways-- (Although Oxley p93 seems to be saying that they don't){-
transversalMatroid2 es as = fromBases es bs
where es' = transversalGraph as
b = greedy [] es' -- first find any basis
greedy ls (r@(u,v):rs) = if u `notElem` map fst ls && v `notElem` map snd ls
then greedy (r:ls) rs
else greedy ls rs
greedy ls [] = ls
-- now choose the subset of the as indicated by the Right part of b
-- and seek all full transversals
bs = closure S.empty (S.singleton b) -- now find all other bases by passing to "neighbouring" bases
closure interior boundary =
if S.null boundary
then S.toList interior
else let interior' = interior `S.union` boundary
boundary' = S.fromList [ b' | b <- S.toList boundary,
x <- b, y <- es LS.\\ b,
let b' = L.insert y (L.delete x b),
b' `S.notMember` interior',
cl b' == es ] -- b' is spanning, and same size as b, hence must be basis
-- S.\\ interior'
in closure interior' boundary'
-}-- DUALITY-- |The dual matroiddual::(Orda)=>Matroida->Matroidadualm=fromBasesesbs'wherees=elementsmbs=basesmbs'=L.sort$map(esLS.\\)bsisCoindependentmxs=isIndependent(dualm)xsisCobasemxs=isBase(dualm)xs-- quicker but less clear to calculate this directlyisCocircuitmxs=isCircuit(dualm)xscocircuits::(Orda)=>Matroida->[[a]]cocircuitsm=circuits(dualm)isColoopme=isLoop(dualm)eisCoparallelmfg=isParallel(dualm)fg-- MINORSdeletion::(Orda)=>Matroida->[a]->Matroidadeletionmxs=restrictionm(esLS.\\xs)wherees=elementsm(\\\)=deletioncontraction::(Orda)=>Matroida->[a]->Matroidacontractionmxs=dual(deletion(dualm)xs)(///)=contraction-- CONNECTIVITY-- Oxley p120-- |A matroid is (2-)connected if, for every pair of distinct elements, there is a circuit containing bothisConnected::(Orda)=>Matroida->BoolisConnectedm=and[any(pair`LS.isSubset`)cs|pair<-combinationsOf2es]wherees=elementsmcs=circuitsmcomponentmx=closureS.empty(S.singletonx)wherecs=circuitsmclosureinteriorboundary=ifS.nullboundarythenS.toListinteriorelseletinterior'=S.unioninteriorboundaryboundary'=S.fromList(concat[c|c<-cs,(not.null)(LS.intersectc(S.toListboundary))])S.\\interior'inclosureinterior'boundary'-- |The direct sum of two matroidsdsum::(Orda,Ordb)=>Matroida->Matroidb->Matroid(Eitherab)dsumm1m2=fromBasesesbswherees=mapLeft(elementsm1)++mapRight(elementsm2)bs=[mapLeftb1++mapRightb2|b1<-basesm1,b2<-basesm2]-- REPRESENTABILITY-- |@matroidPG n fq@ returns the projective geometry PG(n,Fq), where fq is a list of the elements of FqmatroidPG::(Eqa,Fractionala)=>Int->[a]->MatroidIntmatroidPGnfq=vectorMatroid'$ptsPGnfq-- |@matroidAG n fq@ returns the affine geometry AG(n,Fq), where fq is a list of the elements of FqmatroidAG::(Eqa,Fractionala)=>Int->[a]->MatroidIntmatroidAGnfq=vectorMatroid'$ptsAGnfq-- Oxley p182-- |Given a matroid m, the fundamental-circuit incidence matrix relative to a base b-- has rows indexed by the elements of b, and columns indexed by the elements not in b.-- The bi, ej entry is 1 if bi is in the fundamental circuit of ej relative to b, and 0 otherwise.fundamentalCircuitIncidenceMatrix::(Orda,Numk)=>Matroida->[a]->[[k]]fundamentalCircuitIncidenceMatrixmb=L.transpose$fundamentalCircuitIncidenceMatrix'mbfundamentalCircuitIncidenceMatrix'mb=[[ife`elem`fundamentalCircuitmbe'then1else0|e<-b]|e'<-elementsmLS.\\b]fcim=fundamentalCircuitIncidenceMatrixfcim'=fundamentalCircuitIncidenceMatrix'-- Then fcim w4 [1..4] == L.transpose (fcim (dual w4) [5..8])-- Given a matrix of 0s and 1s, return a matrix of 0s, 1s and *s-- where 0 -> 0, and 1 -> 1 if it is the first 1 in either a row or column, 1 -> * otherwisemarkNonInitialRCsmx=mark(replicatewFalse)mxwherew=length$headmx-- the widthmarkcms(r:rs)=let(cms',r')=mark'False[]cms[]rinr':markcms'rsmark_[]=[]mark'rmcms'(cm:cms)ys(x:xs)|x==0=mark'rm(cm:cms')cms(Zero:ys)xs|x==1=ifrm&&cmthenmark'True(True:cms')cms(Star:ys)xselsemark'True(True:cms')cms(One:ys)xsmark'_cms'[]ys[]=(reversecms',reverseys)-- Given a matrix of 0s, 1s and *s, return all distinct matrices that can be obtained-- by substituting non-zero elements of Fq for the *ssubstStarsmxfq=substStars'mxwherefq'=tailfq-- non-zero elts of fqsubstStars'(r:rs)=[r':rs'|r'<-substStars''r,rs'<-substStars'rs]substStars'[]=[[]]substStars''(Zero:xs)=map(0:)$substStars''xssubstStars''(One:xs)=map(1:)$substStars''xssubstStars''(Star:xs)=[x':xs'|x'<-fq',xs'<-substStars''xs]substStars''[]=[[]]starSubstitutionsVfq'(Zero:xs)=map(0:)$starSubstitutionsVfq'xsstarSubstitutionsVfq'(One:xs)=map(1:)$starSubstitutionsVfq'xsstarSubstitutionsVfq'(Star:xs)=[x':xs'|x'<-fq',xs'<-starSubstitutionsVfq'xs]starSubstitutionsV_[]=[[]]-- Oxley p184-5-- Note that the particular representations you get depend on which basis is used-- (Perhaps we should let you pass in the basis to use)representations1fqm=[L.transposed|d<-substStarsdhashfq,letmx=ir++d,to1nm==(vectorMatroid'$mapsnd$L.sort$zip(b++b')mx)]whereb=head$basesmb'=elementsmLS.\\br=lengthb-- rank of the matroidir=idMxr-- identity matrixdhash=markNonInitialRCs$fcim'mb-- edges of the fundamental circuit incidence graphfcigmb=[[e,e']|e<-b,e'<-elementsmLS.\\b,e`elem`fundamentalCircuitmbe']-- the fcim of m relative to b, with 1s and *s markedmarkedfcimmb=markbb'(fcimmb)whereb'=elementsmLS.\\b-- the elements of b are the row labels, those of b' are the column labelsentries=fcigmb-- the [row,column] coordinates of the non-zero entries in the fcimones=head$bases$cycleMatroid'entries-- a set of entries which we can take to be 1stars=entriesLS.\\ones-- the set of entries which we are then still free to assignmark(i:is)js(r:rs)=(mark'ijsr):markisjsrsmark[]_[]=[]mark'i(j:js)(x:xs)|x==0=Zero:mark'ijsxs|x==1=(if[i,j]`elem`starsthenStarelseOne):mark'ijsxsmark'_[][]=[]-- The markedfcim will sometimes do better than markNonInitialRCs, and is best possible-- Oxley p184-5representations2fqm=[L.transposemx|d<-substStarsdhash'fq,letmx=ir++d,m'==(vectorMatroid'$mapsnd$L.sort$zip(b++b')mx)]wherem'=to1nmes=elementsmb=head$basesmb'=esLS.\\br=lengthb-- rank of the matroidir=idMxr-- identity matrixdhash'=L.transpose$markedfcimmb-- In the following, we check the representations of the restrictions as we add a column at a time-- This enables us to early out if a potential representation doesn't work on a restriction-- |Find representations of the matroid m over fq. Specifically, this function will find one representative-- of each projective equivalence class of representation.representations::(Eqfq,Fractionalfq,Orda)=>[fq]->Matroida->[[[fq]]]representationsfqm=mapL.transpose$representations'(reverse$zipbir)(zipb'dhash')wherefq'=tailfq-- fq \ {0}b=head$basesmb'=elementsmLS.\\br=lengthb-- rank of the matroidir=idMxr-- identity matrixdhash'=L.transpose$markedfcimmbrepresentations'ls((i,r):rs)=concat[representations'((i,r'):ls)rs|r'<-starSubstitutionsVfq'r,let(is,vs)=unzip$L.sortBycmpfst((i,r'):ls),to1n(restrictionmis)==(vectorMatroid'vs)]representations'ls[]=[mapsnd$reversels]-- |Is the matroid representable over Fq? For example, to find out whether a matroid m is binary, evaluate @isRepresentable f2 m@.isRepresentable::(Eqfq,Fractionalfq,Orda)=>[fq]->Matroida->BoolisRepresentablefqm=(not.null)(representationsfqm)-- |A binary matroid is a matroid which is representable over F2isBinary::(Orda)=>Matroida->BoolisBinary=isRepresentablef2-- |A ternary matroid is a matroid which is representable over F3isTernary::(Orda)=>Matroida->BoolisTernary=isRepresentablef3-- CONSTRUCTIONS-- The next three functions not very thoroughly testeddataLMRab=La|Mid|Rbderiving(Eq,Ord,Show)-- Oxley p252seriesConnection(m1,p1)(m2,p2)|not(isLoopm1p1)&&not(isColoopm1p1)&&not(isLoopm2p2)&&not(isColoopm2p2)=fromCircuitsescs|otherwise=error"not yet implemented"wherees=mapL(elementsm1LS.\\[p1])++[Mid]++mapR(elementsm2LS.\\[p2])cs=(map.map)L(circuits$m1\\\[p1])++(map.map)R(circuits$m2\\\[p2])++[mapL(L.deletep1c1)++[Mid]++mapR(L.deletep2c2)|c1<-circuitsm1,p1`elem`c1,c2<-circuitsm2,p2`elem`c2]parallelConnection(m1,p1)(m2,p2)|not(isLoopm1p1)&&not(isColoopm1p1)&&not(isLoopm2p2)&&not(isColoopm2p2)=fromCircuitsescs|otherwise=error"not yet implemented"wherees=mapL(elementsm1LS.\\[p1])++[Mid]++mapR(elementsm2LS.\\[p2])cs=(map.map)L(circuits$m1\\\[p1])++[mapL(L.deletep1c1)++[Mid]|c1<-circuitsm1,p1`elem`c1]++(map.map)R(circuits$m2\\\[p2])++[[Mid]++mapR(L.deletep2c2)|c2<-circuitsm2,p2`elem`c2]++[mapL(L.deletep1c1)++mapR(L.deletep2c2)|c1<-circuitsm1,p1`elem`c1,c2<-circuitsm2,p2`elem`c2]twoSum(m1,p1)(m2,p2)|not(isLoopm1p1)&&not(isColoopm1p1)&&not(isLoopm2p2)&&not(isColoopm2p2)=fromCircuitsescs|otherwise=error"not yet implemented"wherees=mapL(elementsm1LS.\\[p1])++[Mid]++mapR(elementsm2LS.\\[p2])cs=(map.map)L(circuits$m1\\\[p1])++(map.map)R(circuits$m2\\\[p2])++[mapL(L.deletep1c1)++mapR(L.deletep2c2)|c1<-circuitsm1,p1`elem`c1,c2<-circuitsm2,p2`elem`c2]-- Note: The only different from seriesConnection is that we don't have Mid in the last set-- Oxley p427matroidUnionm1m2=fromBasesesbswherees=LS.union(elementsm1)(elementsm2)is=toShortlex$toSet[LS.unionb1b2|b1<-basesm1,b2<-basesm2]r=length$lastisbs=dropWhile((<r).length)is-- SOME INTERESTING MATROIDS-- |The Fano plane F7 = PG(2,F2)f7::MatroidIntf7=fromGeoRep[][][[1,2,3],[1,4,7],[1,5,6],[2,4,6],[2,5,7],[3,4,5],[3,6,7]][[1..7]]-- Oxley p36, fig 1.12:-- cycleMatroid [[1,2],[1,3],[2,3],[3,4],[2,4],[1,4]] == restriction fanoPlane [1..6]-- |F7-, the relaxation of the Fano plane by removal of a linef7m::MatroidIntf7m=relaxationf7[2,4,6]-- Oxley p39-- |The Pappus configuration from projective geometrypappus::MatroidIntpappus=fromGeoRep[][][[1,2,3],[1,5,7],[1,6,8],[2,4,7],[2,6,9],[3,4,8],[3,5,9],[4,5,6],[7,8,9]][[1..9]]-- more logical relabeling-- pappus' = fromGeoRep [] [] [[1,2,3],[1,5,9],[1,6,8],[2,4,9],[2,6,7],[3,4,8],[3,5,7],[4,5,6],[7,8,9]] [[1..9]]-- |Relaxation of the Pappus configuration by removal of a linenonPappus::MatroidIntnonPappus=relaxationpappus[7,8,9]-- fromGeoRep [] [] [[1,2,3],[1,5,7],[1,6,8],[2,4,7],[2,6,9],[3,4,8],[3,5,9],[4,5,6]] [[1..9]]-- |The Desargues configurationdesargues::MatroidIntdesargues=fromGeoRep[][][[1,2,5],[1,3,6],[1,4,7],[2,3,8],[2,4,9],[3,4,10],[5,6,8],[5,7,9],[6,7,10],[8,9,10]][[1,2,3,5,6,8],[1,2,4,5,7,9],[1,3,4,6,7,10],[2,3,4,8,9,10],[5,6,7,8,9,10]]-- desargues == cycleMatroid (combinationsOf 2 [1..5])-- (ie Desargues = M(K5) )-- interestingly, although these are all the dependent flats that are evident from the diagram,-- there are also some rank 3 flats consisting of a line and an "antipodal" point-- eg, in terms of K5, [1,8,9,10] corresponds to [[1,2],[3,4],[3,5],[4,5]]-- Oxley p71vamosMatroid1=fromHyperplanes[1..8](hs4++hs3)wherehs4=[[1,2,3,4],[1,4,5,6],[2,3,5,6],[1,4,7,8],[2,3,7,8]]hs3=[h3|h3<-combinationsOf3[1..8],null[h4|h4<-hs4,h3`LS.isSubset`h4]]vamosMatroid=fromGeoRep[][][][[1,2,3,4],[1,4,5,6],[2,3,5,6],[1,4,7,8],[2,3,7,8]]-- |The Vamos matroid V8. It is not representable over any field.v8::MatroidIntv8=vamosMatroid-- v8 is self-dual (isomorphic to its own dual)-- Oxley p188-- |P8 is a minor-minimal matroid that is not representable over F4, F8, F16, ... .-- It is Fq-representable if and only if q is not a power of 2.p8::MatroidIntp8=vectorMatroid$([[1,0,0,0,0,1,1,-1],[0,1,0,0,1,0,1,1],[0,0,1,0,1,1,0,1],[0,0,0,1,-1,1,1,0]]::[[F3]])p8'=fromGeoRep[][][][[1,2,3,8],[1,2,4,7],[1,3,4,6],[1,4,5,8],[1,5,6,7],[2,3,4,5],[2,3,6,7],[2,5,6,8],[3,5,7,8],[4,6,7,8]]-- |P8- is a relaxation of P8. It is Fq-representable if and only if q >= 4.p8m::MatroidIntp8m=relaxationp8[2,3,6,7]-- |P8-- is a relaxation of P8-. It is a minor-minimal matroid that is not representable over F4.-- It is Fq-representable if and only if q >= 5.p8mm::MatroidIntp8mm=relaxationp8m[1,4,5,8]-- Oxley p317-- r-spoked wheel graphwheelGraphr=G.Gvseswherevs=[0..r]es=[[0,i]|i<-[1..r]]++[[i,i+1]|i<-[1..r-1]]++[[1,r]]-- for normal form, should L.sort esmw4=cycleMatroid$G.edges$wheelGraph4-- has [5,6,7,8] as unique circuit-hyperplanew4'=relaxationmw4$unique$circuitHyperplanesmw4-- [5,6,7,8]-- Oxley p183w4=fromGeoRep[][][[1,2,5],[1,4,8],[2,3,6],[3,4,7]][[1,2,3,5,6],[1,2,4,5,8],[1,3,4,7,8],[2,3,4,6,7]]-- BINARY MATROIDS-- Oxley p344isBinary2m=all(even.length)[c`LS.intersect`cc|c<-circuitsm,cc<-cocircuitsm]-- RANK POLYNOMIAL-- Godsil & Royle p356[x,y]=mapglexVar["x","y"]::[GlexPolyIntegerString]-- first naive versionrankPoly1m=sum[x^(rm-ra)*y^(rm'-r'a')|a<-powersetdfses,leta'=esLS.\\a]wherees=elementsmrm=rankmr=rankfunmm'=dualmrm'=rankm'r'=rankfunm'-- |Given a matroid m over elements es, the rank polynomial is a polynomial r(x,y),-- which is essentially a generating function for the subsets of es, enumerated by size and rank.-- It is efficiently calculated using deletion and contraction.---- It has the property that r(0,0) is the number of bases in m, r(1,0) is the number of independent sets,-- r(0,1) is the number of spanning sets. It can also be used to derive the chromatic polynomial of a graph,-- the weight enumerator of a linear code, and more.rankPoly::(Orda)=>Matroida->GlexPolyIntegerStringrankPolym|nulles=1|isLoopme=(1+y)*rankPoly(m\\\[e])-- deletion|isColoopme=(1+x)*rankPoly(m///[e])-- contraction|otherwise=rankPoly(m\\\[e])+rankPoly(m///[e])wherees=elementsme=headesnumBasesm=unwrap$rankPolym`bind`(\v->casevof"x"->0;"y"->0)numIndepsm=unwrap$rankPolym`bind`(\v->casevof"x"->1;"y"->0)numSpanningm=unwrap$rankPolym`bind`(\v->casevof"x"->0;"y"->1)-- It is then possible to derive the chromatic poly of a graph from the rankPoly of its cycle matroid, etc-- Oxley p586-- How many independent sets are there of each sizeindepCountsm=maplength$L.groupByeqfst$[(lengthi,i)|i<-indepsm]-- relying on shortlex order{-
-- The following tries to calculate the indep counts
-- as the Hilbert series of the Stanley-Reisner ring.
-- However, it's not giving the right answer.
indepCounts2 m = take r $ indepCounts' (circuits m)
where n = length (elements m) -- the number of "variables"
r = rank m
n' = toInteger n
indepCounts' [] = map (\k -> (k+n'-1) `choose` (n'-1)) [0..]
-- the number of ways of choosing n-1 separators, so as to leave a product of k powers
indepCounts' (c:cs) =
let d = length c -- the "degree" of c
cs' = reduce [] $ toShortlex $ toSet $ map (LS.\\ c) cs -- the quotient of cs by c
in indepCounts' cs <-> (replicate d 0 ++ indepCounts' cs')
reduce ls (r:rs) = if any (`LS.isSubset` r) ls then reduce ls rs else reduce (r:ls) rs
reduce ls [] = reverse ls
(x:xs) <+> (y:ys) = (x+y) : (xs <+> ys)
xs <+> ys = xs ++ ys -- one of them is null
xs <-> ys = xs <+> map negate ys
-}-- Whitney numbers of the second kind-- The number of flats of each rankwhitney2ndm=maplength$L.groupByeqfst$L.sort[(rankfunmf,f)|f<-flatsm]-- Whitney numbers of the first kind-- The number of subsets (of the element set) of each rankwhitney1stm=alternatingSign$maplength$L.groupByeqfst$L.sort[(rankfunmx,x)|x<-powersetdfs(elementsm)]wherealternatingSign(x:xs)=x:alternatingSign(mapnegatexs)alternatingSign[]=[]-- TODO-- 1. Sort out the isomorphism code in the case where the incidence graph isn't connected-- 2. We could generate the geometric representation from a matroid (provided its rank <= 4)-- geoRep m = filter (isDependent m) (flats m)-- 3. isMatroidFlats