# HG changeset patch
# User Christian Nassau <nassau@nullhomotopie.de>
# Date 1243725587 -7200
# Node ID aec505ff4615a3b7ff0fe2c0f0482951407522b0
# Parent  268d1efbf60f6c24ad9c49cb9c91ff0437e42340
snapshot

diff -r 268d1efbf60f -r aec505ff4615 sage/algebras/all.py
--- a/sage/algebras/all.py	Tue May 05 23:19:43 2009 -0700
+++ b/sage/algebras/all.py	Sun May 31 01:19:47 2009 +0200
@@ -16,7 +16,6 @@
 #
 #                  http://www.gnu.org/licenses/
 #*****************************************************************************
-
 from quatalg.all import *
 
 # Algebra base classes
@@ -28,11 +27,16 @@
 
 from free_algebra import FreeAlgebra, is_FreeAlgebra
 from free_algebra_quotient import FreeAlgebraQuotient
+#from quaternion_algebra import QuaternionAlgebra
 
 from steenrod_algebra import SteenrodAlgebra
 from steenrod_algebra_element import Sq
 from steenrod_algebra_bases import steenrod_algebra_basis
 
+from stgfr import SteenrodAlgebraGroundFieldResolution
+from derive import SteenrodAlgebraSmashResolution, SteenrodAlgebraSmashableExampleMoore
+from chmap import SteenrodAlgebraChainMap
+
 from group_algebra import GroupAlgebra, GroupAlgebraElement
 
     
diff -r 268d1efbf60f -r aec505ff4615 sage/algebras/chmap.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sage/algebras/chmap.py	Sun May 31 01:19:47 2009 +0200
@@ -0,0 +1,270 @@
+r"""
+Chain maps between ground field resolutions
+
+AUTHORS: - Christian Nassau (2009-05-02: version 1.0)
+
+This page explains how to compute chain maps between ground field resolutions.
+Applications of this are:
+
+   - ring structure of `{\rm Ext_A({\mathbb F}_p,{\mathbb F}_p)}`
+   - change of algebra maps `{\rm Ext_A} \rightarrow {\rm Ext_B}` for `B\subset A`
+   - zero'th Steenrod operation `P^0:{\rm Ext}^{s,t}\rightarrow {\rm Ext}^{s,pt}`
+
+INTRODUCTION:
+
+Assume two Steenrod algebra ground field resolutions `C_\ast` and `D_\ast`.
+Given a cocycle `g:C_s\rightarrow{\mathbb F}_p` one can ask for a chain map
+`\phi:C_{s+k}\rightarrow D_k` covering `g`. In Sage such a map is represented
+by an object of class ``SteenrodAlgebraChainMap``::
+
+    sage: # define some convenient aliases
+    sage: SGFR = SteenrodAlgebraGroundFieldResolution
+    sage: ChainMap = SteenrodAlgebraChainMap
+    sage: # compute a resolution
+    sage: p=3; smax=20; nmax=10*p^2
+    sage: C = SGFR(SteenrodAlgebra(p),filename=":memory:")
+    sage: C.extend(s=smax,n=nmax,quiet=True)
+    sage: # pick b0 = <h0,h0,...,h0>
+    sage: b0 = C.g(2,2*p^2-2*p-2)
+    sage: # compute a chain map lifting b0
+    sage: phi = ChainMap(C,C,basecycle=b0,filename=":memory:")
+    sage: phi.extend(s=smax,n=nmax,quiet=True)
+   
+Using `\phi` we can pull back cycles `x:D_k\rightarrow {\mathbb F}_p` 
+to `x\circ\phi:C_{s+k}\rightarrow {\mathbb F}_p`::
+
+    sage: # pull back of the unit is the class that we started with:
+    sage: phi.pullback(C.g(0,0))
+    g(2,10)
+    sage: # compute some powers of b0
+    sage: b02 = phi.pullback(b0) ; b02     # doctest note: random sign
+    g(4,20)
+    sage: b03 = phi.pullback(b02) ; b03    # doctest note: random sign
+    g(6,30)
+    sage: # compute some other products
+    sage: h0 = C.g(1,2*p-3); h1 = C.g(1,2*p^2-2*p-1); h0h1 = C.g(2,2*p^2-2*p-1)
+    sage: (h0,h1,h0h1)
+    (g(1,3), g(1,11), g(2,11))
+    sage: b0h0 = phi.pullback(h0); b0h1 = phi.pullback(h1); b0h0h1 = phi.pullback(h0h1)
+    sage: (b0h0,b0h1)                      # doctest note: random sign
+    (2*g(3,13), 2*g(3,21))
+    sage: b0h0h1
+    0
+
+We can compute the induced map `{\rm Ext}_A\rightarrow {\rm Ext_B}` for `B\subset A`
+from a chain map that covers the unit cycle::
+
+    sage: # compute resolutions for Ext_A and Ext_A(2) at the prime 2
+    sage: Ares = SGFR(SteenrodAlgebra(2),filename=":memory:")
+    sage: A2res = SGFR(prime=2,profile="0 {3 2 1}",viewtype="even",filename=":memory:")
+    sage: smax = 20; nmax = 40
+    sage: Ares.extend(s=smax,n=nmax,quiet=True)
+    sage: A2res.extend(s=smax,n=nmax,quiet=True)
+    sage: # compute a comparison map A2res -> Ares. topologically, this
+    sage: # corresponds to the Hurewicz map \pi_*(S) -> tmf_*
+    sage: hmp = ChainMap(A2res,Ares,basecycle=A2res.g(0,0),filename=":memory:")
+    sage: hmp.extend(s=smax,n=nmax)
+    sage: # compute some pullbacks
+    sage: def print_pullbacks(genlist):
+    ...        for g in genlist:
+    ...            print "%s -> %s" % (g,hmp.pullback(g))
+    ...        
+    sage: print_pullbacks(Ares.generators(n=24))   # doctest note: random ordering 
+    g(5,24) -> 0
+    g(10,24) -> 0
+    g(11,24) -> g(11,24,1)
+    sage: # a little check to illustrate that the pullback is additive
+    sage: a = Ares.g(9,23); b = Ares.g(9,23,1)
+    sage: print_pullbacks([a,b,a-3*b])               # doctest note: random ordering
+    g(9,23) -> 0
+    g(9,23,1) -> g(9,23)
+    g(9,23) + g(9,23,1) -> g(9,23)
+
+A ``ChainMap`` can also be used to compute the Steenrod operation 
+`P^0:{\rm Ext}^{s,t} \rightarrow {\rm Ext}^{s,pt}`. This map is induced
+by the algebra map `A\rightarrow A` dual to the Frobenius `A_\ast\rightarrow A_\ast`::
+
+    sage: # compute a resolution for A(1)
+    sage: A1res = SGFR(prime=2,profile="0 {2 1}",viewtype="even",filename=":memory:")
+    sage: A1res.extend(s=smax,n=nmax,quiet=True)
+    sage: # compute the "doubling map" A2res -> A1res
+    sage: frob = ChainMap(A2res,A1res,algmap="frobenius",filename=":memory:")
+    sage: frob.extend(s=smax,n=nmax,quiet=True)
+    sage: # pull back A(1)'s periodicity generator
+    sage: frob.pullback(A1res.g(4,8))
+    g(4,20)
+
+Finally, you can open an SQL-console to directly investigate the underlying SQLite database::
+
+    sage: # open an SQL console
+    sage: frob.sqlcon()  # sage doctest note: not tested
+
+
+CLASS DOCUMENTATION:
+"""
+
+#*****************************************************************************
+#       Copyright (C) 2009 Christian Nassau <nassau@nullhomotopie.de>
+#  Distributed under the terms of the GNU General Public License (GPL)
+#*****************************************************************************
+
+from string import atoi
+from sage.structure.sage_object import SageObject
+from sage.sets.set import Set
+from sage.algebras.stgfr import SteenrodAlgebraGroundFieldResolution, Yacop
+from sage.rings.all import GF
+from sage.structure.formal_sum import FormalSum, FormalSums
+
+class SteenrodAlgebraChainMap(SageObject):
+    """
+    A chain map between ground field resolutions.
+    """
+
+    def __init__(self,src,dst,basecycle=None,algmap='inclusion',filename=":memory:"):
+
+        import sage
+
+        if not isinstance(src,SteenrodAlgebraGroundFieldResolution):
+            raise ValueError, "first argument is not a resolution"
+        if not isinstance(dst,SteenrodAlgebraGroundFieldResolution):
+            raise ValueError, "second argument is not a resolution"
+
+        if src._prime != dst._prime:
+            raise ValueError, "prime mismatch between source and destination"
+
+        if not algmap is None:
+            if algmap != 'inclusion' and algmap != 'frobenius':
+                raise ValueError, "algmap must be 'inclusion' or 'frobenius'"
+            if algmap == 'frobenius':
+                if not basecycle is None:
+                    raise ValueError, "basecycle not supported for Frobenius map"
+                basecycle = dst.g(0,0,0)
+                
+        if algmap is None and basecycle is None:
+            raise ValueError, "neither algmap nor basecycle given"
+
+        self._src = src
+        self._dst = dst
+        self._prime = dst._prime
+        self._parentGF = GF(self._prime)
+        self._fsums = FormalSums(self._parentGF)
+        self.tcl = Yacop.Slave()
+
+        if not basecycle is None:
+            bc = ""
+            for (cf,g) in basecycle:
+                bc = bc + " %d %d" % (cf,g["id"])
+            basecycle = bc
+
+        self._srcip = src.tcl.eval("namespace current")
+        self._dstip = dst.tcl.eval("namespace current")
+        self._filename = filename
+        self.tcl.eval("""
+           yacop::chainmap create chmap {%s::resolution} {%s::resolution} {%s} {%s} {%s}
+           """ % (self._srcip, self._dstip, algmap or 'default', basecycle or 'default', self._filename))
+        
+        
+
+    def _repr_(self):
+        return "ChainMap (%s) -> (%s), filename='%s'" % (self._src, self._dst, self._filename)
+    
+    def _latex_(self):
+        return "\\text{%s}" % self._repr_()
+
+
+    def extend(self,s,n,quiet=True):
+        """
+        Extend the chain map to the given bidegree `(s,n)`.
+        Here `s` is the homological degree and `n` the topological 
+        dimension.
+        """
+
+        if quiet:
+            self.tcl.eval("yacop::sectionizer quiet on")
+        else:
+            self.tcl.eval("yacop::sectionizer quiet off")
+
+        self.tcl.eval("chmap extend {smax %d nmax %d}" % (s,n))
+
+
+    def pullback(self,cycle):
+        """
+        Compute the pullback of a cycle from the target resolution.
+        """
+        import sage
+        bc = ""
+        for (cf,g) in cycle:
+            bc = bc + " %d %d" % (cf,g["id"])
+
+        res = self.tcl.eval("""
+            set res {}
+            foreach {cf id} [chmap pullback {%s}] {
+               lappend res ($cf,$id)
+            }
+            return "\[[join $res ,]\]"
+          """ % bc)
+        #print res
+        r=[(cf,self._src.g(id=gid)) for (gid,cf) in eval(res)]
+        return sage.algebras.stgfr.FormalSumOfDict(r,parent=self._fsums)
+            
+    def sqlcon(self):
+        """
+        Open a SQL console to inspect the underlying database.
+        """
+        self.tcl.loadTk()
+        self.tcl.eval("sqlconsole new [chmap db]")
+        
+
+
+if False:
+    Yacop.tcl.eval("package forget yacop")
+    execfile("local/lib/yacop/stgfr.py")
+    SGFR=SteenrodAlgebraGroundFieldResolution
+    ChainMap=SteenrodAlgebraChainMap
+    p=3; fname = None
+    C=SteenrodAlgebraGroundFieldResolution(SteenrodAlgebra(p),filename=fname)
+    smax=33
+    nmax=p*p*p*p*p
+    C.extend(s=smax,n=nmax,quiet=False)
+    x= C.g(2,10)
+    mp = ChainMap(C,C,basecycle=x,algmap='inclusion',filename=":memory:")
+    mp.extend(s=5,n=nmax,quiet=False)
+
+    for g in [C.g(0,0),x]:
+        print g, " -> ", mp.pullback(g)
+
+if False:
+
+    p=3
+
+    execfile("local/lib/yacop/stgfr.py")
+
+    fname = None
+    #fname = ":memory:"
+
+    #C=SGFR(SteenrodAlgebra(p),filename=fname)
+    #C=SGFR(prime=2,profile='0 {3 2 1}',filename=":memory:")
+
+    SteenrodAlgebraGroundFieldResolution=SGFR
+    C=SteenrodAlgebraGroundFieldResolution(SteenrodAlgebra(p),filename=fname)
+
+    smax=33
+    nmax=p*p*p*p*p
+    C.extend(s=smax,n=nmax,quiet=False)
+    print len(C.generators())
+
+    x= C.g(2,7)
+    mp = ChainMap(C,C,basecycle=x,algmap='inclusion',filename=":memory:")
+    print [(lambda z: (z,x[z]))(k) for k in ("s","i","e","id")]  
+    #mp.extend(s=smax,n=nmax,quiet=False)
+
+    if false:
+        for g in C.generators():
+            print g
+            m  = ChainMap(C,C,basecycle=g,algmap='inclusion',filename=":memory:")
+            m.extend(s=smax,n=nmax,quiet=False)
+
+    D=SteenrodAlgebraGroundFieldResolution(prime=p,profile='0 full',filename=fname)
+    D.extend(s=smax,n=nmax,quiet=False)
+    frob = ChainMap(C,C,algmap='frobenius',filename=":memory:")
+    frob.extend(s=smax,n=nmax,quiet=False)
diff -r 268d1efbf60f -r aec505ff4615 sage/algebras/derive.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sage/algebras/derive.py	Sun May 31 01:19:47 2009 +0200
@@ -0,0 +1,637 @@
+r"""
+Resolutions of small modules
+
+AUTHORS: - Christian Nassau (2009-04-06: version 1.0)
+
+This page explains how to use a ground field resolution 
+to compute Ext of other small modules.
+
+INTRODUCTION:
+
+Let `M` and `N` be left modules over Steenrod algebra.
+Given a resolution `C_\ast` of `{\mathbb F_p}` we can
+form the complex `M\land C_\ast` where `\land` denotes 
+the tensor product `\otimes_{\mathbb F_p}` with the
+diagonal action. This complex is a resolution of `M`
+and can be used to compute derived functors.
+In particular, we can think of `{\rm Ext}_A(M,N)`
+as the homology of
+
+.. math::
+     {\rm Hom}_A(M\land C_\ast,N) = {\rm Hom_A}(N^\ast\land M\land C_\ast,{\mathbb F_p})
+
+If `M` and `N` are small modules this can be used to
+compute `{\rm Ext}_A(M,N)` very quickly if `C_\ast` is 
+already available.
+
+Let `X=N^\ast\land M` and let `\otimes_{\mathbb F_p}^R` denote a tensor product with 
+action on the right factor only. The computation uses the isomorphism
+
+.. math::
+     X\otimes_{\mathbb F_p}^R C_\ast \owns x\otimes ag \mapsto \sum \pm (a'x)\land (a''g) \in X\land C_\ast 
+
+which exhibits the freeness of `X\land C_\ast`. Up to sign the inverse is given by
+
+.. math::
+     X\land C_\ast \owns x\land ag \mapsto \sum \pm (\chi(a')x)\otimes (a''g) \in X\otimes_{\mathbb F_p}^R C_\ast
+
+We can give `X` a right action of `A` via `xa = (-1)^{\vert x\vert \vert a\vert} \chi(a)x`
+to write this more succinctly as `x\land ag \mapsto \sum \pm (xa')\otimes (a''g)`. 
+This is the form that is internally used in Sage.
+
+THE MODULE:
+
+A smash product of the form `X\land C_\ast` is represented by
+an object of class ``SteenrodAlgebraSmashResolution``.
+The `X` can be any object that implements the ``SteenrodAlgebraSmashable`` interface.
+An example is given by the mod `p` Moore spectrum as follows::
+
+    sage: # set some parameters
+    sage: p=5; smax=10; nmax= 3*p*p*p
+    sage: # create X
+    sage: X = SteenrodAlgebraSmashableExampleMoore(p)
+    sage: # test the "SteenrodAlgebraSmashable" interface
+    sage: X.bbox()  # doctest note: random key order
+    {'emax': 1, 'emin': 0, 'imax': 1, 'imin': 0, 'smax': 0, 'smin': 0}
+    sage: basis = X.basis(); basis
+    {'b', 't'}
+    sage: # differentials are always zero:
+    sage: [X.diff(g) for g in basis]
+    [0, 0]
+    sage: # the action is as expected
+    sage: A = SteenrodAlgebra(p)
+    sage: ops = [A.P(0), A.Q(0)]
+    sage: [[(op,g,X.actR(g,op)) for g in basis] for op in ops]
+    [[(P(0), 'b', b), (P(0), 't', t)], [(Q_0, 'b', t), (Q_0, 't', 0)]]
+
+Elements of the module are represented as formal sums::
+
+    sage: type( X.actR(basis[0],A.Q(0)) )
+    <class 'sage.structure.formal_sum.FormalSum'>
+    sage: X.actR(basis[0],A.Q(0)).parent()
+    Finite Field of size 5
+    
+THE RESOLUTION:
+
+To compute `{\rm Ext}_A(M,{\mathbb F_p})` for the Moore space module we first need
+a ground field resolution::
+
+    sage: C = SteenrodAlgebraGroundFieldResolution(SteenrodAlgebra(p),filename=":memory:")
+    sage: C.extend(s=smax,n=nmax,quiet=True)
+
+We can then define and compute the smash product `X\land C_\ast`::
+
+    sage: prod = SteenrodAlgebraSmashResolution(C,X,filename=":memory:")
+    sage: prod.extend(s=smax,n=nmax,quiet=True)
+
+Let `R_\ast = X\land C_\ast`. The object ``prod`` now knows about both 
+`{\mathbb F}_p \otimes_A R_\ast` and its homology `{\rm Tor}^A(M,{\mathbb F}_p)`::
+
+    sage: # list some bases of the smash product and its homology
+    sage: for (s,n) in [(0,0),(0,1),(1,0),(1,1),(1,7),(1,8),(1,39),(2,39),(3,39),(4,39),(5,39)]:
+    ...       print "(%2d,%2d): %33s %15s" % (s,n,sorted(prod.smash_basis(s,n)),prod.homology(s,n))
+    ...    
+    ( 0, 0):                   [('b', g(0,0))]        {g(0,0)}
+    ( 0, 1):                   [('t', g(0,0))]              {}
+    ( 1, 0):                   [('b', g(1,0))]              {}
+    ( 1, 1):                   [('t', g(1,0))]              {}
+    ( 1, 7):                   [('b', g(1,7))]        {g(1,7)}
+    ( 1, 8):                   [('t', g(1,7))]        {g(1,8)}
+    ( 1,39):                  [('b', g(1,39))]       {g(1,39)}
+    ( 2,39):  [('b', g(2,39)), ('t', g(2,38))]              {}
+    ( 3,39):  [('b', g(3,39)), ('t', g(3,38))]              {}
+    ( 4,39):  [('b', g(4,39)), ('t', g(4,38))]              {}
+    ( 5,39):  [('b', g(5,39)), ('t', g(5,38))]       {g(5,39)}
+    sage: # find a representative of g(5,39): 
+    sage: prod.cycle(prod.g(5,39))
+    ('t', g(5,38))
+    sage: # look at some cycles and boundaries
+    sage: prod.cycles(s=4,n=39), prod.boundaries(s=4,n=39) # doctest note: random signs
+    ([('t', g(4,38))], [2*('t', g(4,38))])
+    sage: prod.cycles(s=2,n=39), prod.boundaries(s=2,n=39) # doctest note: random signs
+    ([('t', g(2,38))], [3*('t', g(2,38))])
+
+GRAPHICAL USER INTERFACE:
+
+You can inspect the smash product and its homology through a GUI window::
+
+    sage: prod.gui()  # doctest: not tested
+
+You can also just open an SQL console to work with the underlying database::
+
+    sage: prod.sqlcon()   # doctest: not tested 
+
+
+CLASS DOCUMENTATION:
+"""
+
+#*****************************************************************************
+#       Copyright (C) 2009 Christian Nassau <nassau@nullhomotopie.de>
+#  Distributed under the terms of the GNU General Public License (GPL)
+#*****************************************************************************
+
+from sage.structure.sage_object import SageObject
+from sage.sets.set import Set
+from sage.rings.all import GF
+from sage.rings.infinity import Infinity
+from sage.structure.formal_sum import FormalSum, FormalSums
+from sage.algebras.stgfr import Yacop, SteenrodAlgebraGroundFieldResolution
+
+class SteenrodAlgebraSmashable(SageObject):
+    """
+    An abstract base class for derivable functors.
+    """
+
+    def bbox(self):
+        """
+        Return a bounding box for `G(A)`.
+
+        The method should return a dictionary with the following keys::
+
+           :smin, smax:   bounds for homological degree *s*
+           :imin, imax:   bounds for internal degree *i*
+           :emin, emax:   bounds for exterior algebra degree *e*
+
+        """
+        raise NotImplementedError, "bbox method not implemented"
+
+    def basis(self,s,i,e):
+        """
+        Return a basis of the totalization `G(A)` in degree `(s,i,e)`.
+
+        INPUTS::
+
+            :self:          the derivable functor `G` 
+            :s:             homological degreee
+            :i:             internal degree (not to be confused with topological dimension)
+            :e:             exterior algebra degree
+
+        The basis should consist of hashable Python objects and must be iterable.
+        """
+        raise NotImplementedError, "basis method not implemented"
+
+    def diff(self,baselem): 
+        """
+        Return the internal differential of a basis element.
+
+        The differential should be a ``FormalSum`` with coefficient ring `GF(p)`.
+        If *basel* is from *self.basis(s,i,e)* then 
+        *self.diff(basel)* must be from the span of *self.basis(s-1,i,e)*.
+        """
+        raise NotImplementedError, "diff method not implemented"
+
+    def action(self,baselem,a):
+        """
+        Return the right action of a Steenrod operation on a basis element.
+
+        The return value should be a ``FormalSum`` with coefficient ring `GF(p)`.
+        If *basel* is from *self.basis(C,s,i,e)* then 
+        *self.action(basel,a)* must be from the span of *self.basis(C,s,i+p,e+q)*
+        where *p* (resp. *q*) is the internal (resp. exterior algebra) degree of *a*.
+
+        """
+        raise NotImplementedError, "action method not implemented"
+
+
+
+class SteenrodAlgebraSmashResolution(SageObject):
+    """
+    Object that represents `M\\otimes_A C_\\ast` for a 
+    minimal resolution `C_\\ast` and a differential right `A`-module `M`.
+    """
+
+    def __init__(self,resolution,module,filename=":memory:"):
+
+        if not isinstance(resolution,SteenrodAlgebraGroundFieldResolution):
+            raise ValueError, "first argument is not a resolution"
+
+        self._resolution = resolution
+        self._module = module
+        self._filename = filename
+        self._prime = self._resolution._prime
+        self._parentGF = GF(self._resolution._prime)
+        self._fsums = FormalSums(self._parentGF)
+        self.tcl = Yacop.Slave()
+
+        # link the resolution into our Tcl interpreter
+        self._resip = resolution.tcl.eval("namespace current")
+        self.tcl.eval("""
+           yacop::smashres create smashprod {%s::resolution} {%s} [namespace current]::sagemod {%s}
+           """ % (self._resip, self._module, self._filename))
+        
+    def _repr_(self):
+        return "SmashResolution (%s) * (%s), filename='%s'" % (self._module, self._resolution, self._filename)
+    
+    def _latex_(self):
+        return "\\text{%s}" % self._repr_()
+
+
+    def extend(self,s,n,quiet=True,smin=0,nmin=0):
+
+        if quiet:
+            self.tcl.eval("yacop::sectionizer quiet on")
+        else:
+            self.tcl.eval("yacop::sectionizer quiet off")
+
+        # dump module basis and actions into database
+        self._makeSageModule({"smax":s+1,"imax":2*n+s+1})
+
+        # compute the smash product
+        self.tcl.eval("smashprod compute {smin %d nmin %d smax %d nmax %d}" % (smin,nmin,s+1,n))
+
+    def gui(self):
+        """
+        Open a graphical user interface. This allows you to inspect the resolution, create
+        postscript charts and to run SQL commands.
+        """
+
+        self.tcl.loadTk()
+        self.tcl.eval("""
+              set chv [yacop::chartgui [smashprod db] {%s}]
+              trace add variable [$chv forever] write "[list $chv destroy];#"
+           """ % self._filename)
+
+    def sqlcon(self):
+        """
+        Open a SQL console to inspect the underlying database.
+        """
+        self.tcl.loadTk()
+        self.tcl.eval("sqlconsole new [smashprod db]")
+
+
+    def _search_condition(self,s=None,n=None,i=None,e=None,nov=None):
+        """
+        Create a where clause from the input parameters
+        """
+        
+        if self._resolution._viewtype == 'even':
+            nexp = "(s.ideg/2-s.sdeg)"
+        else:
+            nexp = "(s.ideg-s.sdeg)"
+
+        cond = ""
+        if not s is None: cond += " and s.sdeg = %d" % s
+        if not i is None: cond += " and s.ideg = %d" % i
+        if not n is None: cond += " and %s = %d" % (nexp,n)
+        if not e is None: cond += " and s.edeg = %d" % e
+        if not nov is None: cond += " and (s.sdeg-s.edeg) = %d" % nov
+        if cond != "": cond = "where " + cond[4:]
+        return (cond,nexp)
+
+    def homology(self,s=None,n=None,i=None,e=None,nov=None):
+        """
+        Return basis of the homology in a range of degrees
+        """
+
+        (cond,nexp) = self._search_condition(s=s,n=n,i=i,e=e,nov=nov)
+
+        res = "[" + self.tcl.eval( """
+               join [smashprod db eval {
+                   select pydict('id',rowid,'s',s.sdeg,'i',s.ideg,'e',s.edeg,'n',%s,'num',s.basid,'enum',s.ebasid) from homology_generators as s %s
+               }] ,
+            """ % (nexp,cond) ) + "]"
+        #print res
+        import sage
+        #return Set([sage.algebras.stgfr.FormalSumOfDict(self,x) for x in eval(res)])
+        return Set([sage.algebras.stgfr.FormalSumOfDict([(1,sage.algebras.stgfr.Generator(self,x))],parent=self._fsums) for x in eval(res)])
+
+    def smash_basis(self,s=None,n=None,i=None,e=None,nov=None):
+        """
+        Return A-basis of the smash product in a range of degrees
+        """
+
+        (cond,nexp) = self._search_condition(s=s,n=n,i=i,e=e,nov=nov)
+
+        res = "[" + self.tcl.eval( """
+               join [smashprod db eval {
+                   select pydict('modgen',''''||m.sagename||'''','resgen',s.resgen) 
+                   from smash_generators as s
+                   join module_generators as m on m.rowid = s.modgen %s
+               }] ,
+            """ % (cond) ) + "]"
+        return Set([(self._module._make_element_(x["modgen"]),self._resolution.g(id=x["resgen"])) for x in eval(res)])
+        
+    def _unpack_vector(self,row,basis):
+        """
+        Create a cycle from coefficient vector and basis
+        """
+
+        res = []
+        idx=-1
+        for coeff in row:
+            idx=idx+1
+            if coeff != 0:
+                x = basis[idx]
+                res.append((coeff,(self._module._make_element_(x["modgen"]),self._resolution.g(id=x["resgen"]))))
+                
+        return FormalSum(res,parent=self._fsums)
+
+    def _getmatrix(self,what,s=None,n=None,i=None,e=None,nov=None):
+        """
+        Return cycles in a range of degrees
+        """
+        
+        (cond,nexp) = self._search_condition(s=s,n=n,i=i,e=e,nov=nov)
+
+        res = "[" + self.tcl.eval( """
+               join [smashprod db eval {
+                   select '(' || pymatrix(%s) || ',' || 
+                      (select '[' 
+                           || group_concat(pydict('modgen',''''||m.sagename||'''',
+                                                  'resgen',subsel.resgen))
+                           || ']' from smash_generators as subsel 
+                           join module_generators as m on m.rowid = subsel.modgen 
+                       where s.sdeg = subsel.sdeg and s.ideg = subsel.ideg and s.edeg = subsel.edeg) 
+                   || ')'
+                   from smash_boxes as s
+                   %s
+               }] ,
+            """ % (what,cond) ) + "]"
+
+        #print "res=", eval(res)
+        mat = []
+        for (matrix,basis) in eval(res):
+            for row in matrix:
+                mat.append( self._unpack_vector(row,basis) )
+        return mat
+
+    def cycles(self,s=None,n=None,i=None,e=None,nov=None):
+        """
+        Return cycles in a range of degrees
+        """
+
+        return self._getmatrix('cycles',s=s,n=n,i=i,e=e,nov=nov)
+ 
+    def boundaries(self,s=None,n=None,i=None,e=None,nov=None):
+        """
+        Return boundaries in a range of degrees
+        """
+
+        return self._getmatrix('boundaries',s=s,n=n,i=i,e=e,nov=nov)
+ 
+    def cycle(self,generator):
+        """
+        Return a representing cycle for a homology generator.
+        """
+
+        mat = self._getmatrix('homology',s=generator["s"],i=generator["i"],e=generator["e"])
+        #print mat
+        return mat[generator["enum"]]
+
+    def g(self,s=None,n=None,num=None,id=None):
+        """
+        Construct a homology generator from id or sequence number
+        """
+
+        import sage
+        (cond,nexp) = self._search_condition()
+
+        if not id is None:
+            cond = "where rowid = %d" % id
+        else:
+            cond = "where sdeg = %d and ideg = n2i(%d,%d) and basid = %d" % (s,s,n,num or 0)
+
+        res = "[" + self.tcl.eval( """
+               join [smashprod db eval {
+                   select pydict('id',rowid,'s',s.sdeg,'i',s.ideg,'e',s.edeg,'n',%s,'num',s.basid,'enum',s.ebasid) from homology_generators as s %s
+               }] ,
+            """ % (nexp,cond) ) + "]"
+        #print res
+        return [sage.algebras.stgfr.FormalSumOfDict([(1,sage.algebras.stgfr.Generator(self,x))],parent=self._fsums) for x in eval(res)][0]
+        
+
+    def homology_class(self,cycle):
+        """
+        Express cycle in terms of homology generators
+        """
+
+        cmd = "unset -nocomplain hclvar; set hclvar {}"    
+        for (coeff,smashgen) in cycle:
+            (modgen,resgen) = smashgen
+            #print "cf=",coeff," mgen=",modgen," rgen=",resgen
+            cmd = cmd + ";lappend hclvar %d {%s} %d" % (coeff,modgen,resgen["id"]) 
+
+            
+        res = self.tcl.eval("""
+                     %s
+                     #puts hv=$hclvar
+                     smashprod sage_homology_class $hclvar
+                     """ % cmd)
+        #print eval(res)
+        return FormalSum([(cf,self.g(id=gid)) for (cf,gid) in eval(res)],parent=self._fsums)
+
+    def _makeSageModule(self,region):
+
+        from sage.algebras.steenrod_algebra import SteenrodAlgebra_generic, SteenrodAlgebra_mod_two
+
+        self.tcl.eval("""
+           if {[info commands sagemod] eq ""} {
+               set smashdb [smashprod db]
+               yacop::sagemodule create sagemod [%s::resolution config] $smashdb
+           }
+           """ % self._resip)
+
+        # find the module generators that are already in the database
+        modgens = list(self._module.basis(region))
+        script = "set mgens {}\n"
+        for g in modgens:
+            degs = self._module.degrees(g)
+            script += "lappend mgens {{%s} {s %d i %d e %d}}\n" % (g,degs["s"],degs["i"],degs["e"])
+        todo = self.tcl.eval("%s ; sagemod addModuleGens $mgens" % script)
+        if self._resolution._viewtype == "odd":
+            A = SteenrodAlgebra_generic(self._resolution._prime)
+        else:
+            A = SteenrodAlgebra_mod_two()
+        #print todo
+        todo = eval(todo)
+        #print todo
+        resp = "unset -nocomplain df;unset -nocomplain ar;array set df {};array set ar {};"
+        for g in todo["diff"]: 
+            mg = modgens[g]
+            resp += "set g {%s};set df($g) {};" %mg
+            dg = self._module.diff(mg)
+            for (cf,tg) in list(dg):
+                resp += "lappend df($g) %d {%s}\n" % (cf,tg) 
+        atodo = todo["actR"]
+        for g in atodo:
+            mg = modgens[g]
+            resp += "set g {%s};set ar($g) {};" % mg
+            for (yop,sop) in atodo[g]:
+                resp += "lappend ar($g) {%s} {" % (yop)
+                rs = self._module.actR(mg,sop)
+                for (cf,tg) in list(rs):
+                    resp += "%d {%s} " % (cf,tg)
+                resp += "}\n" 
+                    
+        #print resp
+        self.tcl.eval("""
+              %s
+              #parray df
+              #parray ar
+              sagemod addDiff [array get df]
+              sagemod addActionR [array get ar]
+              unset df
+              unset ar
+              #puts [sagemod db eval {select * from sagemod_ops}]
+              #puts [smashprod db eval {select * from smash_fragments}]
+           """ % resp)
+
+
+class SteenrodAlgebraSmashableExampleMoore(SteenrodAlgebraSmashable):
+    "Cohomology of the mod p moore space"
+
+    def __init__(self,prime):
+        if prime == 2:
+            raise NotImplementedError, "current implementation is restricted to odd primes"
+        self._prime = prime
+        self._ring = GF(prime)
+
+    def _repr_(self):
+        return "mod %d Moore spectrum" % self._prime
+
+    def _region_intersect(self,reg1,reg2):
+        res = {}
+        for l in ["s","i","e"]:
+            key = l + "max"
+            res[key] = min(reg1.get(key,+Infinity),reg2.get(key,+Infinity))
+            key = l + "min"
+            res[key] = max(reg1.get(key,-Infinity),reg2.get(key,-Infinity))
+        return res
+
+    def _region_contains(self,region,s,i,e):
+        if region["smax"] < s:
+            return False
+        if region["emax"] < e:
+            return False
+        if region["imax"] < i:
+            return False
+        if region["smin"] > s:
+            return False
+        if region["emin"] > e:
+            return False
+        if region["imin"] > i:
+            return False
+        return True
+
+    def basis(self,region=None):
+        res = Set([])
+        reg = {
+            "smin" : -Infinity,
+            "imin" : -Infinity,
+            "emin" : -Infinity,
+            "smax" : +Infinity,
+            "imax" : +Infinity,
+            "emax" : +Infinity,
+            }
+        if not region is None:
+            reg = self._region_intersect(reg,region)
+        if self._region_contains(reg,0,0,0):
+            res = res.union(Set(['b']))
+        if self._region_contains(reg,0,1,1):
+            res = res.union(Set(['t']))
+        return res
+
+    def actL(self,basel,a):
+        if a.is_unit():
+            deg = 0
+        else:
+            deg = a.degree()
+        if deg == 0:
+            # hack: this gets the constant term in a:
+            coeff = a._raw["milnor"][()]
+            return FormalSum([(coeff,basel)],parent=self._ring)
+        if deg != 1 or basel != 'b':
+            return FormalSum([],parent=self._ring)
+        try:
+            # hack: this gets the coefficient of Q_0 in a:
+            coeff = a._raw["milnor"][((0,), ())]
+        except KeyError:
+            return FormalSum([],parent=self._ring)
+        return FormalSum([(coeff,'t')],parent=self._ring)
+
+    def actR(self,basel,a):
+        return self.actL(basel,a)
+
+    def diff(self,basel):
+        return FormalSum([],parent=self._ring)
+
+    def bbox(self):
+        res = {
+            "smin" : 0,
+            "imin" : 0,
+            "emin" : 0,
+            "smax" : 0,
+            "imax" : 1,
+            "emax" : 1,
+            }
+        return res
+
+    def degrees(self,basel):
+        return {
+            'b': {
+                "s":0, "i":0, "e":0, 
+                },
+            't' : {
+                "s":0, "i":1, "e":1, 
+                },
+            }[basel]
+
+    def _make_element_(self,string):
+        return string
+
+
+if False:        
+
+    p=3
+
+    execfile("local/lib/yacop/stgfr.py")
+
+    fname = None
+    #fname = ":memory:"
+
+    #C=SteenrodAlgebraGroundFieldResolution(SteenrodAlgebra(p),filename=fname)
+    #C=SteenrodAlgebraGroundFieldResolution(prime=2,profile='0 {3 2 1}',filename=":memory:")
+
+    SteenrodAlgebraGroundFieldResolution=SteenrodAlgebraGroundFieldResolution
+    C=SteenrodAlgebraGroundFieldResolution(SteenrodAlgebra(p),filename=fname)
+
+    smax=33
+    nmax=p*p*p*p*p
+    C.extend(s=smax,n=nmax,quiet=False)
+    print len(C.generators())
+
+    if p != 2:
+        M=Moore(p)
+        print M.basis()
+        print M.basis(M.bbox())
+        print M.basis({"emax":0})
+        print [(lambda x: (x,M.degrees(x)))(g) for g in M.basis()]
+
+        X=SmashResolution(C,M,filename=":memory:")
+
+        print latex(X)
+        nmax=175
+        X.extend(s=smax-5,n=nmax,quiet=False)
+        X.extend(s=smax-2,n=nmax,quiet=False)
+    X.gui()
+    #C.gui()
+
+    #X.tcl.eval("smashprod db trace puts")
+
+    for nd in [10,11,12,27,82]:
+        gens = X.homology(n=nd)
+        print "dimension ", nd, ": gens ", gens
+        for g in gens:
+            s=g["s"]
+            i=g["i"]
+            sbas = X.smash_basis(s=s,i=i)
+            print "  smash basis s=", s, " i=",i, " : ", sbas
+            print "  cycles: ", X.cycles(s=s,i=i)
+            print "  boundaries: ", X.boundaries(s=s,i=i)
+            print "  representative of ", g, " = ", X.cycle(g)
+            print "  clone of g: ", X.g(id=g["id"]), " = ", X.g(s=s,i=i,num=g["num"])
+            for c in X.cycles(s=s,i=i):
+                print "  generator from cycle", c, "=", X.homology_class(c)
+
+
+
diff -r 268d1efbf60f -r aec505ff4615 sage/algebras/stgfr.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sage/algebras/stgfr.py	Sun May 31 01:19:47 2009 +0200
@@ -0,0 +1,685 @@
+r"""
+Steenrod algebra ground field resolutions
+
+AUTHORS: - Christian Nassau (2009-03-27: version 1.0)
+
+This package allows to compute a minimal resolution of a sub 
+Hopf-algebra of the Steenrod algebra. 
+
+INTRODUCTION:
+
+A resolution is represented by an object of class ``SteenrodAlgebraGroundFieldResolution``. 
+That name is clearly too long to be typed more than once, so we begin by defining a shorter alias name::
+
+    sage: # define alias
+    sage: SGFR = SteenrodAlgebraGroundFieldResolution
+    sage: # construct a resolution object
+    sage: C5 = SGFR(SteenrodAlgebra(5)) ; C5 # doctest note: random (data path may differ)
+    GroundFieldResolution (p=5, profile="-1 full", filename="gfr-steenrod-5-E-1Rfull.db", viewtype=odd)
+
+The resolution is stored in a single-file SQLite_ database. You can specify an arbitrary 
+file name in the constructor. The special name ":memory:" creates an in-memory resolution that
+is automatically forgotten when the sage session ends.
+
+The profile of the subalgebra has an exterior algebra part and a reduced part. It is represented 
+as "exterior-bitmask {list-of-integers}". For example, the algebra generated by `Q_0` and `Q_2` 
+has profile ``5 {}`` and `A(2)=span(Q_0,P^1,P^p)` has ``7 {2 1}`` (at odd primes) or "0 {3 2 1}" (for p=2)::
+
+    sage: C2 = SGFR(filename=":memory:", algebra = SteenrodAlgebra(2)) ; C2 # doctest note: random (might change)
+    GroundFieldResolution (p=2, profile="0 full", filename=":memory:", viewtype=even)
+    sage: tmf = SGFR(filename=":memory:", prime=2, profile="0 {3 2 1}") ; tmf # doctest note: random (might change)
+    GroundFieldResolution (p=2, profile="0 {3 2 1}", filename=":memory:", viewtype=even)
+
+The exterior part of the profile is also supported at the prime 2. 
+For example, the following represents the `E_2` of the algebraic Novikov 
+spectral sequence for `Ext(BP)` at the prime 2::
+
+    sage: algNSS = SGFR(filename=":memory:", prime=2, profile="-1 full") ; algNSS # doctest note: random (might change)
+    GroundFieldResolution (p=2, profile="-1 full", filename=":memory:", viewtype=odd)
+
+For p=2 one can think of the Steenrod algebra as the reduced part of the "odd Steenrod algebra
+at the prime 2", except for a rescaling of the degrees: 
+a generator of `Ext^{s,i}` in the first sense belongs to `Ext^{s,2i}`
+in the second. The *viewtype* parameter allows to switch between the two perspectives.
+Viewtype "even" only works if the exterior part is empty.
+
+COMPUTING A RESOLUTION:
+
+Before you can use a resolution you must extend it to 
+the degree that you need::
+
+    sage: C2 = SGFR(filename=":memory:", algebra = SteenrodAlgebra(2))
+    sage: C2.extend(s=20,n=40)
+    sage: # count generators in topological dimension 31
+    sage: len(C2.generators(n=31))
+    22
+    sage: # ask for generators outside of the computed range
+    sage: len(C2.generators(n=45))
+    0
+    sage: C2.is_complete(s=3,n=45)
+    False
+    sage: # extend the resolution and ask again
+    sage: C2.extend(s=20,n=50)
+    sage: C2.is_complete(s=20,n=45)
+    True
+    sage: len(C2.generators(n=45))
+    11
+
+You can get a running progress report for the computation if you call *extend*
+with the extra argument ``quiet=False``. You can always 
+interrupt a running computation with Ctrl-C without harm.
+
+Needless to say that you shouldn't run more than one computation on the 
+same file at the same time.
+But there's also no harm if you accidentally do: 
+in this case the earlier process will terminate with 
+the message "database has been hijacked" as soon as it tries to write
+to the database.
+
+USING THE RESOLUTION:
+
+After the resolution has been computed you can use the *generators(...)* function
+to find the generators in any given bidegree. Since the computed
+resolution is minimal these provide a dual basis to `Ext_A(F_p,F_p)`::
+
+    sage: # find a basis of Ext^{s,t}(F_2,F_2) for s=9, t-s=23
+    sage: sorted(C2.generators(s=9,n=23))
+    [g(9,23), g(9,23,1)]
+
+As you can see Ext is 2-dimensional in that bidegree. The generators are displayed in the 
+form *g(s,n,num)* where *s* is the homological degree, *n* the topological dimension 
+and *num* the number of the generator in that bidegree. If the number is 0 it is omitted.
+The `\text{\LaTeX}` representation is `g^{(s)}_{n}` or `g^{(s)}_{n,num}`.
+
+Every generator has a unique id in the resolution, namely the *rowid* of 
+the corresponding dataset. Although this *id* has no mathematical significance, 
+it is occasionally useful to have it, for example if you are using an external tool 
+to investigate the resolution datafile. You can get the *id* by looking 
+at the "id" entry in the dictionary under the generator::
+ 
+    sage: # find the id of the non-zero e_0 from Ext_A^{4,21}
+    sage: e0 = C2.g(4,17)
+    sage: e0["id"]                       # doctest note: random
+    47
+    sage: # create the generator from its id
+    sage: e0 == C2.g(id = e0["id"])
+    True
+
+Every generator contains a reference to the resolution it belongs to. FormalSumOfDicts from 
+different resolutions are never equal::
+
+    sage: # create another resolution for the same algebra
+    sage: C2b = SGFR(filename=":memory:", algebra = SteenrodAlgebra(2))
+    sage: C2b.extend(s=5,n=10)
+    sage: C2.g(2,3) == C2b.g(2,3)
+    False
+    sage: C2.g(id=1) == C2b.g(id=1)
+    False 
+    sage: # note that C2b sometimes uses different ids for corresponding generators: 
+    sage: C2.g(id=6), C2b.g(id=6)        # doctest note: random
+    (g(1,15), g(2,0))
+
+The *diff(...)* method gives you the differential of a generator::
+
+    sage: # differential of h_0h_2
+    sage: C2.diff(C2.g(2,3))
+    Sq(4)*g(1,0) + Sq(0,1)*g(1,1) + Sq(1)*g(1,3)
+
+In other words, one has `d(h_0h_2) = Sq^4 h_0 + Sq(0,1) h_1 + Sq^1 h_2`.
+
+The differentials are given as ``FormalSum`` objects::
+
+    sage: type(C2.diff(C2.g(2,3)))
+    <class 'sage.structure.formal_sum.FormalSum'>
+    sage: C2.diff(C2.g(2,3)).parent()
+    Abelian Group of all Formal Finite Sums over mod 2 Steenrod algebra
+    sage: sorted(list(C2.diff(C2.g(2,3))))
+    [(Sq(1), g(1,3)), (Sq(0,1), g(1,1)), (Sq(4), g(1,0))]
+
+If you play around with this you will find that the differentials soon become forbiddingly
+complicated. You can specify filter options in the *diff* routine to
+get only parts of the data::
+
+    sage: i = C2.g(7,23)
+    sage: # get the full differential
+    sage: C2.diff(i)
+    (Sq(3,7)+Sq(6,6))*g(6,0) + (Sq(4,1,1)+Sq(11,1))*g(6,10) + (Sq(0,2,1)+Sq(7,2))*g(6,11) + (Sq(1,3)+Sq(4,2))*g(6,14) + Sq(5,1)*g(6,16) + (Sq(0,0,1)+Sq(1,2)+Sq(4,1))*g(6,17) + Sq(1,1)*g(6,20)
+    sage: # extract only the P_t^s terms
+    sage: C2.diff(i,ptsonly=True)
+    Sq(0,0,1)*g(6,17)
+    sage: # only fetch the piece that is attached to h_0^2d_0
+    sage: C2.diff(i,target=C2.g(6,14))
+    (Sq(1,3)+Sq(4,2))*g(6,14)
+    sage: # filter by the degree of the Steenrod operations
+    sage: C2.diff(C2.g(6,17),opdeg=3)
+    Sq(0,1)*g(5,15,1)
+
+Divisibility by the `h_k` is indicated by the presence of `Sq^{2^k}`
+in the differential. You can search for these terms by combining *ptsonly* with *opdeg*::
+
+    sage: # find h1-divisibility in dimension 17: look for all Sq^2 that originate there
+    sage: sorted([(lambda x:(x,C2.diff(x,ptsonly=True,opdeg=2)))(g) for g in C2.generators(n=17)])
+    [(g(3,17), Sq(2)*g(2,16)),
+    (g(4,17), 0),
+    (g(5,17), 0),
+    (g(6,17), 0),
+    (g(7,17), Sq(2)*g(6,16)),
+    (g(8,17), Sq(2)*g(7,16)),
+    (g(9,17), 0)]
+
+
+For example, note that *g(7,17)* is divisible by `h_k` for all `k=0,1,2,3`::
+
+    sage: C2.diff(C2.g(7,17),ptsonly=True)
+    Sq(8)*g(6,10) + Sq(4)*g(6,14) + Sq(2)*g(6,16) + Sq(1)*g(6,17)
+
+GRAPHICAL USER INTERFACE:
+
+You can inspect the resolution with a graphical user interface using the *gui* method. 
+This will open up a new window with an Ext chart in the usual format. 
+You can also use this to create postscript charts or to access the underlying
+sqlite database from a SQL console::
+
+    sage: # start graphical user interface 
+    sage: C2.gui()  # sage doctest note: not tested
+
+You can also open the SQL console directly::
+
+    sage: # open an SQL console
+    sage: C2.sqlcon()  # sage doctest note: not tested
+
+REFERENCES:
+
+.. [Na] C. Nassau, "Ein neuer Algorithmus zur Untersuchung der Kohomologie der Steenrod-Algebra", 
+	Logos Verlag Berlin 2002, ISBN 3-89722-881-5
+.. [Null] The author's homepage `<http://www.nullhomotopie.de>`_. This should contain
+          the sources for the Steenrod and Yacop libraries.
+.. [SQLite] Project homepage `<http://www.sqlite.org>`_.
+
+
+CLASS DOCUMENTATION:
+"""
+
+#*****************************************************************************
+#       Copyright (C) 2009 Christian Nassau <nassau@nullhomotopie.de>
+#  Distributed under the terms of the GNU General Public License (GPL)
+#*****************************************************************************
+
+
+class Yacop:
+    """
+    Interface to yet another cohomology program (YaCoP).
+    """
+
+    tcl = None
+    "The main Tcl interpreter. Resolutions use a namespace of this as their interpreter."
+
+    tcllibrary = None
+    "Extra paths, added to auto_path when Tcl starts up"
+
+    def __init__(self):
+        pass
+
+    class Slave:
+        "A namespace in the main Yacop interpreter"
+
+        def __init__(self):
+            from Tkynter import Tcl, Tk
+            if Yacop.tcl is None:
+                Yacop.tcl = Tcl()
+                xdirs = Yacop.tcllibrary
+                if not xdirs is None:
+                    if not  isinstance(xdirs,list):
+                        xdirs = [ xdirs ]
+                    [Yacop.tcl.eval("lappend auto_path {%s}" % path) for path in xdirs]
+            Yacop.tcl.eval("""
+                lappend auto_path $::env(SAGE_LOCAL)/lib
+                package require yacop::sage 1.0
+              """)
+            self._slavename = Yacop.tcl.eval("yacop::sage::new-slave")
+     
+        def __del__(self):
+            if not self._slavename is None:
+                Yacop.tcl.eval("catch {namespace delete %s}" % self._slavename)
+ 
+        def eval(self,script):
+            "Execute a Tcl script in the slave's namespace"
+
+            return Yacop.tcl.eval("namespace eval {%s} {%s}" % (self._slavename,script)) 
+
+        def loadTk(self):
+            "Load the Tk widget library into the slave's interpreter"
+
+            self.eval("""
+               namespace eval :: {package require Tk}
+               if {![winfo exists .yacop]} {
+                   set x {}
+                   lappend x "I am the Yacop main window."
+                   lappend x "You should not see me."
+                   lappend x "If you close me, Yacop GUIs will no longer work."
+                   label .yacop -text [join $x \\n]
+                   pack .yacop -expand 1 -fill both
+                   wm title . "Yacop main window"
+
+                   set ::yacop::LastUpdate [clock milliseconds]
+                   proc ::yacop::progress {} {
+                        variable LastUpdate
+                        set now [clock milliseconds]
+                        if {$now-$LastUpdate > 300} {
+                           set LastUpdate $now
+                           update
+                        }  
+                   }
+               }
+               wm withdraw .
+             """ 
+                    )
+            
+from sage.structure.sage_object import SageObject
+from string import atoi
+from sage.rings.all import GF
+from sage.sets.set import Set
+from sage.structure.formal_sum import FormalSum, FormalSums
+
+class FormalSumOfDict(FormalSum):
+    """
+    A FormalSum variant with an extra __getitem__ method
+    """
+
+    def __getitem__(self, key):
+        res = None
+        for (cf,gen) in list(self):
+            if not res is None:
+                if gen[key] != res:
+                    raise KeyError, "inconsistent entries for key %s" % key
+            else:
+                res = gen[key]
+        if res is None:
+            raise KeyError, "cannot determine '%s' because sum is zero" % key
+        return res
+
+class Generator(SageObject,dict):
+    """
+     A generator is a dictionary with some of the following keys
+       :id:           unique identifier 
+       :s:            homological degree
+       :i:            internal degree
+       :n:            topological dimension (n=i-s)
+       :e:            exterior algebra degree
+       :num:          bidegree sequence number, identifies generators with given s and n
+       :enum:          bidegree sequence number, identifies generators with given s, n and e
+       :resolution:   weak reference to the parent resolution       
+    """
+
+    def __init__(self,owner,dictval):
+        from weakref import ref
+        dict.__init__(self)
+        self["resolution"] = ref(owner)
+        for i in dictval:
+            self[i] = dictval[i]
+
+    def _repr_(self):
+        num = self["num"]
+        if num == 0:
+            return "g(%d,%d)" % (self["s"], self["n"])
+        return "g(%d,%d,%d)" % (self["s"], self["n"], num)
+
+    def _latex_(self):
+        num = self["num"]
+        if num == 0:
+            return "g^{(%d)}_{%d}" % (self["s"], self["n"])
+        return "g^{(%d)}_{%d,%d}" % (self["s"], self["n"], num)
+
+    def __hash__(self):
+        return self["id"]
+
+class SteenrodAlgebraGroundFieldResolution(SageObject):
+    r"""
+    Minimal resolution of `F_p` over a sub Hopf algebra of the Steenrod algebra.
+
+    To construct a resolution you have to specify a meaningful subset of the arguments:
+       :prime:        (requires that you also give the profile)
+       :profile:      profile of the algebra to resolve, for example ``7 {2 1}`` for `A(2)`
+       :algebra:      the algebra to resolve. specify *either* this or prime/profile
+       :filename:     name of the database file to use. can be ":memory:" for an in-memory resolution
+       :viewtype:     even/odd, p=2 only. this switches between `i` and `i/2`
+       :quiet:        boolean to turn progress report on/off
+
+    IMPLEMENTATION NOTES:
+
+    Every resolution has its own Tcl interpreter with the Yacop package loaded into it.
+    Packages are searched in the "local/lib" directory, although extra paths
+    can be configured through the *Yacop.tcllibrary* variable.
+    The interpreter is a slave of the main interpreter *Yacop.tcl*, which is common
+    to all resolution related work.
+
+    The interpreter is accessible from the outside through the "tcl" attribute.
+    The resolution is represented in that interpreter by a "yacop::gfr" object
+    with name "resolution". Its associated sqlite database can be accessed 
+    through the "db" subcommand::
+
+        sage: C = SteenrodAlgebraGroundFieldResolution(SteenrodAlgebra(2),filename=":memory:")
+        sage: C.tcl.eval("resolution db eval {select value from yacop_cfg where key='prime'}")
+        '2'
+    """
+
+    def __init__(self, algebra=None, prime=None, profile=None, 
+                 filename=None, viewtype=None, quiet=True):
+        
+        self._filename = None
+        self._algebra  = None
+        self._prime    = None
+        self._profile  = None
+        self._profmode = 'auto'
+        self._viewtype = 'odd'
+
+        self.tcl = Yacop.Slave()
+
+        if ((prime is None) and (not profile is None)) or ((not prime is None) and (profile is None)):
+            raise ValueError, "prime and profile must be specified together"
+
+        if (not prime is None) and (not algebra is None):
+            raise ValueError, "cannot specify algebra and prime/profile at the same time"
+
+        if not algebra is None:
+            # translate algebra into prime/profile information
+            # Why does this not work???: if isinstance(algebra, SteenrodAlgebra_generic):
+            if hasattr(algebra, "prime"):
+                prime = algebra.prime
+                if 2 == prime:
+                    profile = "0 full"
+                    _viewtype = "even"
+                else:
+                    profile = "-1 full"
+                    _viewtype = "odd"
+            else:
+                raise ValueError, "algebra %s not understood" % algebra
+        
+        if prime is None and filename is None:
+            raise ValueError, "neither filename nor algebra given"
+
+        if not prime is None:
+            # test whether prime is supported
+            self.tcl.eval("steenrod::prime %s inverse 1" % prime)
+            self._prime = prime
+            # test profile 
+            self.tcl.eval("steenrod::algebra verify {%s}" % profile)
+            self._profile = profile
+            if not filename:
+                filename = self.tcl.eval("""
+                  set p %d
+                  set a {%s}
+                  set ae [lindex $a 0]
+                  set ar [lindex $a 1]
+                  set fnm "gfr-steenrod-$p-E${ae}R[join $ar -].db"
+                  return $fnm
+                 """ % (prime, profile))
+                 
+        if filename is None:
+            filename = ":memory:"
+        
+        self._filename = filename
+        if self._prime:
+            self.tcl.eval("set p %d;set alg {%s}" % (self._prime, self._profile))
+
+        self.tcl.eval("""
+                 yacop::gfr create resolution {%s}
+                 if {[info exists alg]} {
+                    resolution algebra $p $alg
+                 }
+                 array set resinfo [resolution algebra]
+                 if {$resinfo(prime) eq ""} {
+                    error "cannot deduce the prime/algebra of this resolution"
+                 }
+                 set resinfo(viewtype) odd
+                 if {$resinfo(prime) == 2 && 0 == [lindex $resinfo(algebra) 0]} {
+                    set resinfo(viewtype) even
+                 }
+                 resolution viewtype $resinfo(viewtype)
+            """ % self._filename)
+        
+        self._prime = atoi(self.tcl.eval("set resinfo(prime)"))
+        self._profile = self.tcl.eval("set resinfo(algebra)")
+        self._viewtype = self.tcl.eval("set resinfo(viewtype)")
+        self._fsumsGF = FormalSums(GF(self._prime))
+        if not viewtype is None:
+            self._viewtype = viewtype
+        self["viewtype"] = self._viewtype
+
+        self["quiet"] = quiet
+
+    def _repr_(self):
+        return "GroundFieldResolution (p=%d, profile='%s', filename='%s')" % (self._prime, 
+                                                                              self._profile, 
+                                                                              self._filename)
+
+    def _latex_(self):
+        return "{\rm %s}" % self._repr_()
+
+        
+    def __getitem__(self, key):
+        _getcode = {
+            'filename': lambda x: self._filename,
+            'algebra':  lambda x: self._algebra,
+            'prime':    lambda x: self._prime,
+            'profile':  lambda x: self._profile,
+            'profmode': lambda x: self._profmode,
+            'viewtype': lambda x: self._viewtype,
+            'quiet':    lambda x: self._quiet,
+            }
+        if _getcode.has_key(key):
+            return _getcode[key](x)
+        raise ValueError, "unknown key \"%s\"" % key
+    
+    def _setprofmode(self, value):
+        oklist = ["auto", "upper", "lower", "none"]
+        if not (value in oklist):
+            raise ValueError, "unknown profmode \"%s\", should be one of %s" % (value, oklist)
+        self._profmode = value
+ 
+    def _setviewtype(self, value):
+        oklist = ["odd", "even"]
+        if not (value in oklist):
+            raise ValueError, "unknown viewtype \"%s\", should be one of %s" % (value, oklist)
+        if value == "even" and self._prime != 2:
+            raise ValueError, "viewtype \"even\" can only be used at the prime 2"
+        self.tcl.eval("resolution viewtype %s" % value)
+        self._viewtype = value
+
+        # hmm, without the following line I get "NameError: global name 'sage' is not defined"
+        import sage
+
+        if self._viewtype == "even":
+            self._fsums = sage.structure.formal_sum.FormalSums_generic(sage.algebras.steenrod_algebra.SteenrodAlgebra_mod_two())
+        else:
+            self._fsums = sage.structure.formal_sum.FormalSums_generic(sage.algebras.steenrod_algebra.SteenrodAlgebra_generic(self._prime))
+
+    def _setquiet(self, value):
+        if not isinstance(value, bool):
+            raise ValueError, "quiet must be a bool"
+        if value:
+            self.tcl.eval("yacop::sectionizer quiet on")
+        else:
+            self.tcl.eval("yacop::sectionizer quiet off")
+        self._quiet = value
+
+    def __setitem__(self, key, value):
+        _setcode = {
+            'profmode': lambda x: self._setprofmode(value),
+            'viewtype': lambda x: self._setviewtype(value),
+            'quiet'   : lambda x: self._setquiet(value),
+            }
+        if _setcode.has_key(key):
+            return _setcode[key]('dummy')
+        raise ValueError, "key \"%s\" cannot be set" % key
+
+    def gui(self):
+        """
+        Open a graphical user interface. This allows you to inspect the resolution, create
+        postscript charts and to run SQL commands.
+        """
+
+        self.tcl.loadTk()
+        self.tcl.eval("""
+              set chv [yacop::chartgui [resolution db] {%s}]
+              trace add variable [$chv forever] write "[list $chv destroy];#"
+           """ % self._filename)
+                        
+    def sqlcon(self):
+        """
+        Open a SQL console to inspect the underlying database.
+        """
+        self.tcl.loadTk()
+        self.tcl.eval("sqlconsole new [resolution db]")
+        
+
+    def is_complete(self, s, i=None, n=None):
+        "Check whether the bidegree has been computed"
+        if (i is None) and (n is None):
+            raise ValueError, "either internal or topological degree must be given"
+        if (not i is None) and (not n is None):
+            raise ValueError, "both internal and topological degree given"
+        if n :
+            i = s+n
+        if self._viewtype == "even":
+            i = 2*i;
+        res = self.tcl.eval("resolution isComplete %d %d" % (s,i))
+        if eval(res):
+            return True
+        return False
+
+    def extend(self, s, i=None, n=None, quiet=None):
+        """
+        Extend the resolution to the indicated bidegree. 
+        """
+        if (i is None) and (n is None):
+            raise ValueError, "no bound for internal or topological degree given"
+        if (not i is None) and (not n is None):
+            raise ValueError, "cannot bound both internal and topological degree"
+        bounds = " sdeg %d" % s
+        if not i is None:
+            bounds += " ideg %d" % i
+        if not n is None:
+            if self._viewtype == "even":
+                bounds += " rtdeg %d" %n
+            else:
+                bounds += " tdeg %d" %n
+        if  not quiet is None:
+            self["quiet"] = quiet
+        self.tcl.eval("""
+              resolution profmode %s
+              resolution extend-to {%s}
+           """ % (self._profmode, bounds) )
+
+
+    def generators(self, s=None, i=None, n=None, e=None, nov=None):
+        """
+        Search for generators with the indicated attributes.
+        "nov" is just `s-e` (the name stands for *Novikov degree*).
+        """
+        import sage
+        cond = ""
+        if not s is None: cond += " and sdeg = %d" % s
+        if not i is None: cond += " and ideg = %d" % i
+        if not n is None: cond += " and ndeg = %d" % n
+        if not e is None: cond += " and edeg = %d" % e
+        if not nov is None: cond += " and (sdeg-edeg) = %d" % nov
+        if cond != "": cond = "where " + cond[4:]
+        res = "[" + self.tcl.eval( """
+               join [resolution db eval {
+                   select pydict('id',rowid,'s',sdeg,'i',ideg,'e',edeg,'n',ndeg,'num',basid) from chart_generators %s
+               }] ,
+            """ % cond ) + "]"
+        return Set([sage.algebras.stgfr.FormalSumOfDict([(1,sage.algebras.stgfr.Generator(self,x))],parent=self._fsumsGF) for x in eval(res)])
+        
+    def g(self, s=None, n=None, num=None, id=None):
+        """
+        Create a generator from either *id* or bidegree *(s,n)* and sequence number *num*.
+        """
+        import sage
+        if not id is None:
+            if (not s is None) or (not n is None) or (not num is None):
+                raise ValueError, "expected either id or s,n,num"
+            res = self.tcl.eval( """
+               resolution db onecolumn {
+                   select pydict('id',rowid,'s',sdeg,'i',ideg,'e',edeg,'n',ndeg,'num',basid) from chart_generators where rowid = %d
+               }
+            """ % id )
+        else:
+            if (s is None) or (n is None):
+                raise ValueError, "expected either id or s,n,num"
+            if num is None:
+                num = 0
+            res = self.tcl.eval( """
+               resolution db onecolumn {
+                   select pydict('id',rowid,'s',sdeg,'i',ideg,'e',edeg,'n',ndeg,'num',basid) from chart_generators 
+                   where sdeg = %d and ideg = %d and basid = %d
+               }
+            """ % (s,s+n,num) )
+        if res == "":
+            raise ValueError, "no such generator"
+        return sage.algebras.stgfr.FormalSumOfDict([(1,sage.algebras.stgfr.Generator(self,eval(res)))],parent=self._fsumsGF)
+    
+    def diff(self, src, target=None, opdeg=None, ptsonly=False):
+        """
+        Get the differential of the generator *src*. The result is returned 
+        as a list of pairs *(algebra element, generator)*.
+
+        The differential can be filtered through these optional parameters:
+           
+             :target:      filter by target generator
+             :opdeg:       filter by degree of the algebra element
+             :ptsonly:     return only `P_t^s` and Bocksteins
+
+        Note that you can search for a specific `P_t^s` or `Q_k` by combining *opdeg* and *ptsonly*.
+        """
+
+        from sage.algebras.steenrod_algebra import SteenrodAlgebra_mod_two, SteenrodAlgebra_generic
+
+        cond = "srcgen = %d" % src["id"]
+        if not (target is None):
+            cond += " and targen = %d" % target["id"]
+        if not (opdeg is None):
+            if self._viewtype == "even":
+                opdeg *= 2
+            cond += " and opideg = %d" % opdeg
+        funcname = "sagepoly_" + self._viewtype
+        if ptsonly:
+            funcname += "_PtsOnly"
+        res = self.tcl.eval( """
+               set result {}
+               resolution db eval {
+                   select %s(%d,group_concat(frag_decode(format,data),' '),targen) as spol from fragments 
+                   where %s group by targen, format
+               } {
+                  if {$spol ne ""} {
+                     lappend result $spol
+                  }
+               }
+               return "\[[join $result ,]\]"
+            """ % (funcname,self._prime,cond) )
+        if self._viewtype == "odd":
+            A = SteenrodAlgebra_generic(self._prime)
+        else:
+            A = SteenrodAlgebra_mod_two()
+        return FormalSum(eval(res),parent=self._fsums)
+        
+
+if __name__ == "huhu":
+
+    C2 = SteenrodAlgebraGroundFieldResolution(filename=":memory:", algebra = SteenrodAlgebra(2), quiet=True)
+    #C2["quiet"] = False
+    C2.extend(s=20,n=40)
+    print C2.generators(s=4)
+    print C2.diff(C2.g(4,23,0))
+    print C2.diff(C2.g(4,23,0),ptsonly=True)
+    print C2.diff(C2.g(7,17,0),ptsonly=True)
+
+
+if False:
+    SGFR = SteenrodAlgebraGroundFieldResolution
+    smax = 20; nmax = 40
+    Ares = SGFR(SteenrodAlgebra(2),filename=":memory:")
+    Ares.extend(s=smax,n=nmax,quiet=True)
+    a = Ares.g(9,23); b = Ares.g(9,23,1)
+
diff -r 268d1efbf60f -r aec505ff4615 sage/algebras/stmodvs.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sage/algebras/stmodvs.py	Sun May 31 01:19:47 2009 +0200
@@ -0,0 +1,435 @@
+
+from sage.matrix.constructor import matrix
+from sage.modules.module import Module
+from sage.modules.free_module import FreeModule_generic
+from sage.modules.matrix_morphism import MatrixMorphism
+from sage.categories.action import Action
+from sage.modules.free_module_element import FreeModuleElement_generic_sparse, vector
+import sage.categories.category_types
+from sage.categories.category_types import Modules
+from sage.categories.category_types import AlgebraModules
+#execfile("/home/cn/steenrod_module.py")
+from sage.algebras.steenrod_algebra_element import SteenrodAlgebraElement
+
+class SteenrodAction_vs(Action):
+    def __init__(self, M, is_left):
+        Action.__init__(self,SteenrodAlgebra(M._prime),M,is_left=is_left,op=operator.mul)
+        print "xxx", self.codomain(), self.domain(), self.operation(), self.is_left()
+
+    def _call_(self, a, b):
+        #print "xxx", self.codomain(), self.domain(), self.operation(), self.is_left()
+        #self.M.matrix(a) * b
+        #print "action %s / %s" % (a,b)
+        if self.is_left():
+            dict = self.codomain()._lactions
+            v = b._data
+            result = 0 * v
+            ad = a._basis_dictionary('milnor')
+            for mb in ad.keys():
+                if dict.has_key(mb):
+                    mat = dict[mb]
+                    result += ad[mb] * mat * v        
+            return SteenrodModuleElement_vector_space(self.codomain(),result) 
+        else:
+            dict = self.codomain()._ractions
+            v = a._data
+            result = 0 * v
+            bd = b._basis_dictionary('milnor')
+            for mb in bd.keys():
+                if dict.has_key(mb):
+                    mat = dict[mb]
+                    result += bd[mb] * v * mat
+            return SteenrodModuleElement_vector_space(self.codomain(),result) 
+
+def direct_sum(L):
+    pass
+
+#class SteenrodModule(Module, FreeModule_generic):
+class SteenrodModule(Module):
+    """
+    Might have a left action, might have a right action.
+    """
+    def __init__(self, prime=None, profile=None, algebra=None, name_prefix=None, name_override=None):
+        self._prime = prime
+        Parent.__init__(self,base=GF(prime),categories=[AlgebraModules(SteenrodAlgebra(prime))], names=name_prefix)
+        pass
+    
+    def gen(self, m, n=None):
+        """
+        Returns the m'th generator if n is None, or the nth generator of degree m.
+        """
+        pass
+    #def inject_variables(self, scope=None, verbose=True):
+    #    pass
+    def basis_in_range(self, islice=None, eslice=None):
+        """
+        islice -- internal degrees
+        eslice -- exterior degrees
+        """
+        # fallback implementation: get all generators and filter by degrees
+        res = []
+        for g in self._vector_space_basis():
+            d = g.degrees()
+            if (islice is None or d['i'] in islice) and (eslice is None or d['e'] in eslice):
+                res.append(g)
+        return res
+
+    def bbox(self):
+        """
+        Returns a dict with keys 'imin', 'imax', 'emin', 'emax'
+        """
+        pass
+    def check_adem_relations(self, on_left=True):
+        pass
+    def check_associativity(self, on_left=True):
+        pass
+    def action_is_on_left(self):
+        pass
+    def truncate(self, upper_bound=None, lower_bound=None):
+        pass
+    def direct_sum(self, M):
+        pass
+    def tensor_product(self, M):
+        pass
+    def _convert_to_vs(self):
+        pass
+    def _convert_to_fp(self):
+        pass
+    def _convert_to_tcl(self):
+        pass
+    def degree(self):
+        #total degree = dim as F_p vector space.
+        pass
+
+    def dualized(self):
+        return self._vs_dual()
+
+    def suspended(self,ishift,eshift=0):
+        return self._suspended(ishift,eshift);
+
+class SteenrodModule_vector_space(SteenrodModule): 
+
+    def _unfiddle_action(self,adict):
+        res={}
+        for k in adict:
+            if isinstance(k,SteenrodAlgebraElement):
+                x = k._basis_dictionary('milnor')
+                if len(x) != 1:
+                    raise ValueError, "dictionary key %s not a Milnor basis element" % k
+                mb=x.keys()[0]
+                if x[mb] != 1:
+                    raise ValueError, "dictionary key %s coefficient is not 1" % k
+                res[mb]=adict[k]
+            else:
+                res[k]=adict[k]
+        return res
+            
+    def _vs_dual(self):
+        """
+        create the vector space dual with the conjugated actions  
+        """
+
+        idegs = [-x for x in self._idegrees]
+        edegs = [-x for x in self._edegrees]
+        lact = {}
+        ract = {}
+        if not self._ractions is None:
+            for op in self._ractions.keys():
+                lact[op] = self._ractions[op].transpose()
+        if not self._lactions is None:
+            for op in self._lactions.keys():
+                ract[op] = self._lactions[op].transpose()
+        return SteenrodModule_vector_space(self._algebra,idegs,edegs,name_prefix=self._name_prefix,actions=lact,right_actions=ract,profile=self._profile)
+
+    def suspended(self,ishift,eshift=0):
+        """
+        create a suspension
+        """
+        idegs=[x+ishift for x in self._idegrees]
+        edegs=[x+eshift for x in self._edegrees]
+        return SteenrodModule_vector_space(self._algebra,idegs,edegs,name_prefix=self._name_prefix,actions=self._lactions,right_actions=self._ractions,profile=self._profile)
+
+    def __init__(self,algebra, idegrees, edegrees, name_prefix='m', actions=None, right_actions=None, profile=None):
+        """
+        action dictionaries: key=Milnor basis element, value=Matrix
+        """
+        if hasattr(algebra,"prime"):
+            self._prime = algebra.prime
+        else:
+            raise ValueError, "algebra not recognized"
+
+        self._algebra = algebra
+        self._idegrees=idegrees
+        self._edegrees=edegrees
+        self._name_prefix=name_prefix
+        self._lactions = self._unfiddle_action(actions)
+        self._ractions = self._unfiddle_action(right_actions)
+        self._dim = len(idegrees)
+        self._profile = profile
+
+        SteenrodModule.__init__(self,prime=self._prime,profile=profile,algebra=algebra,name_prefix=name_prefix)
+
+        #print self.parent()
+        print self.base()
+        print self.category()
+
+        #self._base = SteenrodAlgebra(self._prime)
+
+        raction = SteenrodAction_vs(self, is_left=0)
+        laction = SteenrodAction_vs(self, is_left=1)
+        print laction
+        print raction
+        #self._populate_coercion_lists_()
+        self._populate_coercion_lists_(action_list=[laction, raction])
+
+    def _element_constructor_(self,x):
+        print "eleco",x
+        if isinstance(x,vector):
+            return SteenrodModuleElement_vector_space(self,x)
+
+        raise ValueError, "cannot make %s a module element" % x
+
+    def _repr_(self):
+        return "SteenrodModule_vector_space(%s,%s,%s,actions=%s,right_actions=%s,profile=%s)" % (self._algebra,self._idegrees,self._edegrees,self._lactions,self._ractions,self._profile)
+
+    def ngens(self):
+        return len(self._idegrees)
+
+    def gen(self,num):
+        v = [0]*self.ngens()
+        v[num]=1
+        return SteenrodModuleElement_vector_space(self,vector(GF(self._prime),v))
+
+    def _vector_space_basis(self):
+        #print "x",self._idegrees, len(self._idegrees)
+        l=len(self._idegrees)
+        return [self.gen(i) for i in range(l)]
+
+    def bbox(self):
+        """
+        Returns a dict with keys 'imin', 'imax', 'emin', 'emax'
+        """
+        res={}
+        res['imin'] = min(self._idegrees)
+        res['imax'] = max(self._idegrees)
+        res['emin'] = min(self._edegrees)
+        res['emax'] = max(self._edegrees)
+        return res
+
+class SteenrodModuleElement(ModuleElement):
+    def __init__(self,parent):
+        ModuleElement.__init__(self,parent)
+
+    def degrees(self):
+        """
+        return dictionary with keys 'i' and 'e'
+        """
+        raise NotImplementedError, "degrees not implemented"
+
+class SteenrodModuleElement_vector_space(SteenrodModuleElement):
+    
+    def __init__(self,module,vector):
+        SteenrodModuleElement.__init__(self,module)
+        self._data = vector
+
+    def _repr_(self):
+        s = ""
+        p=self.parent()
+        N = p.variable_names()
+        for i in range(len(self._data)):
+            if self._data[i] == 1:
+                s += " + %s"%N[i]
+            elif self._data[i]:
+                s += " + %s*%s"%(self._data[i],N[i])
+        if len(s) > 0:
+            s = s[3:]
+        else:
+            s = "0"
+        return s
+        #return "x%s" % self._data
+    
+    def _add_(self,x):
+        return SteenrodModuleElement_vector_space(self.parent(),self._data + x._data)
+    def _sub_(self,x):
+        return SteenrodModuleElement_vector_space(self.parent(),self._data - x._data)
+         
+    def degrees(self):
+        """
+        return dictionary with keys 'i' and 'e'
+        """
+        s = ""
+        p=self.parent()
+        N = p.variable_names()
+        ideg = None
+        edeg = None
+        for i in range(len(self._data)):
+            if self._data[i] != 0:
+                if ideg is None:
+                    ideg = p._idegrees[i]
+                elif ideg != p._idegrees[i]:
+                    raise ValueError, "element %s is not homogeneous" % self
+                if edeg is None:
+                    edeg = p._edegrees[i]
+                elif edeg != p._edegrees[i]:
+                    raise ValueError, "element %s is not homogeneous" % self
+        if ideg is None:
+            raise ValueError, "degree not well defined for 0"
+        return {'i':ideg,'e':edeg}
+
+    def __getitem__(self,gen):
+        """
+        returns the inner product
+        """
+        s = ""
+        p=self.parent()
+        N = p.variable_names()
+        res = 0
+        for i in range(len(self._data)):
+            res += gen._data[i]*self._data[i]
+        return res
+
+def MooreSpace_generic(prime,name_prefix='x'):
+    unit = matrix(GF(prime),2,2,[1,0,0,1])
+    opq0 = matrix(GF(prime),2,2,[0,1,0,0])
+    actions = {}
+    A=SteenrodAlgebra(prime)
+    actions[A.P(0)] = unit
+    actions[A.Q(0)] = opq0
+    return SteenrodModule_vector_space(SteenrodAlgebra(prime),[0,1],[0,1],name_prefix=name_prefix,actions=actions,right_actions=actions)
+
+def MooreSpace(prime,name_prefix='x'):
+    if prime != 2:
+        return MooreSpace_generic(prime,name_prefix)
+    unit = matrix(GF(prime),2,2,[1,0,0,1])
+    opq0 = matrix(GF(prime),2,2,[0,1,0,0])
+    actions = {}
+    A2=SteenrodAlgebra(2)
+    actions[A2.Sq(0)] = unit
+    actions[A2.Sq(1)] = opq0
+    return SteenrodModule_vector_space(SteenrodAlgebra(prime),[0,1],[0,0],name_prefix=name_prefix,actions=actions,right_actions=actions)
+
+cm = sage.structure.element.get_coercion_model()
+
+M = MooreSpace(5,name_prefix='m')
+A = SteenrodAlgebra(5)
+b = M.gen(0)
+t = M.gen(1)
+
+print cm.explain(SteenrodAlgebra(5).P(0),M.gen(1),operator.mul)
+
+print M
+print M.gen(0)
+print M.gen(1)
+print M._vector_space_basis()
+
+if True:
+    for a in [A.P(0),A.Q(1),A.P(1)]:
+        for m in [M.gen(0),M.gen(1)]:
+            print "   %s * %s = %s" % (a,m,a*m)
+            print "   %s * %s = %s" % (m,a,m*a)
+
+print cm.explain(Integer(3),b,operator.mul)
+
+print b+t, " / ", b-t 
+print 3*b
+print 3*b-2*t
+b2=2*b
+s=b2+t
+print b2.degrees()
+#print s.degrees()
+               
+class SteenrodModuleYacopWrapper:
+    """
+    A wrapper around a SteenrodModule that provides the
+    SteenrodAlgebraSmashable interface required for the 
+    cohomology calculation
+    """
+
+    def __init__(self,module):
+        self.mod = module
+        self._fsums = FormalSums(GF(self.mod._prime))
+        self._prime = self.mod._prime
+        self._doublei = False
+        if self._prime == 2:
+            self._doublei = True
+
+    def bbox(self):
+        b = self.mod.bbox()
+        b['smax'] = b['smin'] = 0
+        if self._doublei:
+            b['imax'] = b['imax']*2
+            b['imin'] = b['imin']*2
+        return b
+
+    def basis(self,region):
+        im = region['imax']
+        if self._doublei:
+            im = im/2
+        bbx= self.mod.bbox()
+        return self.mod.basis_in_range(range(bbx['imin'],im+1))
+
+    def diff(self,baselem): 
+        return FormalSum([],parent=self._fsums) 
+    
+    def actR(self,baselem,a):
+        prod = baselem*a
+        lst = [(prod[g],g) for g in self.mod.basis_in_range()]
+        print "%s * %s = %s = %s" % (baselem,a,prod,lst) 
+        return FormalSum(lst,parent=self._fsums) 
+
+    def degrees(self,baselem):
+        r=baselem.degrees()
+        r['s']=0
+        if self._doublei:
+            r['i'] = r['i']*2
+        return r
+
+    def _make_element_(self,string):
+        print "mk",string
+        return self.mod(string)
+
+C2=SteenrodAlgebraGroundFieldResolution(SteenrodAlgebra(2))
+C=SteenrodAlgebraGroundFieldResolution(SteenrodAlgebra(5))
+
+if True:
+    Y=SteenrodModuleYacopWrapper(M)
+
+    D=SteenrodAlgebraSmashResolution(C,Y)
+    D.extend(s=20,n=500)
+    D.gui()
+
+if False:
+    # moore space, assumes 2-primary gfr C2
+    Y=SteenrodModuleYacopWrapper(MooreSpace(2,name_prefix='j'))
+
+    D=SteenrodAlgebraSmashResolution(C2,Y)
+    D.extend(s=30,n=60)
+    D.gui()
+
+if False:
+    # question mark cpx, assumes 2-primary gfr C2
+    prime=2
+    unit = matrix(GF(prime),3,3,[1,0,0,0,1,0,0,0,1])
+    opq0 = matrix(GF(prime),3,3,[0,1,0,0,0,0,0,0,0])
+    opp1 = matrix(GF(prime),3,3,[0,0,0,0,0,1,0,0,0])
+    ractions = {}
+    A2=SteenrodAlgebra(2)
+    ractions[A2.Sq(0)] = unit
+    ractions[A2.Sq(1)] = opq0
+    ractions[A2.Sq(2)] = opp1
+    ractions[A2.Sq(3)] = opq0*opp1
+    ractions[A2.Sq(0,1)] = ractions[A2.Sq(3)]
+    lactions=ractions
+    #lactions[A2.Sq(1,1)] = ractions[A2.Sq(3)]
+    Q=SteenrodModule_vector_space(SteenrodAlgebra(prime),[0,1,3],[0,0,0],name_prefix='q',actions=lactions,right_actions=ractions)
+
+    Z= Q.dualized().suspended(-5)
+    Y=SteenrodModuleYacopWrapper(Z)
+
+    D=SteenrodAlgebraSmashResolution(C2,Y)
+    D.extend(s=30,n=60)
+    D.gui()
+
+    for k in D.homology(n=4): 
+        print k, " <- " , D.cycle(k)
+        
diff -r 268d1efbf60f -r aec505ff4615 sage/structure/parent.pyx
--- a/sage/structure/parent.pyx	Tue May 05 23:19:43 2009 -0700
+++ b/sage/structure/parent.pyx	Sun May 31 01:19:47 2009 +0200
@@ -720,10 +720,10 @@
             if isinstance(action, Action):
                 if action.actor() is self:
                     self._action_list.append(action)
-                    self._action_list[action.domain(), action.operation(), action.is_left()] = action
+                    self._action_hash[action.domain(), action.operation(), action.is_left()] = action
                 elif action.domain() is self:
                     self._action_list.append(action)
-                    self._action_list[action.actor(), action.operation(), not action.is_left()] = action
+                    self._action_hash[action.actor(), action.operation(), not action.is_left()] = action
                 else:
                     raise ValueError, "Action must involve self"
             else:
# HG changeset patch
# User Christian Nassau <nassau@nullhomotopie.de>
# Date 1243774200 -7200
# Node ID 7267bd7be47328faef78d9bac8511f76786e4f72
# Parent  aec505ff4615a3b7ff0fe2c0f0482951407522b0
added forgotten doc index

diff -r aec505ff4615 -r 7267bd7be473 doc/en/reference/algebras.rst
--- a/doc/en/reference/algebras.rst	Sun May 31 01:19:47 2009 +0200
+++ b/doc/en/reference/algebras.rst	Sun May 31 14:50:00 2009 +0200
@@ -14,4 +14,7 @@
 
    sage/algebras/steenrod_algebra
    sage/algebras/steenrod_algebra_element
-   sage/algebras/steenrod_algebra_bases
\ No newline at end of file
+   sage/algebras/steenrod_algebra_bases
+   sage/algebras/stgfr
+   sage/algebras/derive
+   sage/algebras/chmap
