Hi Markus,

Thanks for your patch. Would you mind sending us a pull request
against the official repository?

https://github.com/sympy/sympy

Then we can easily review it and push it in.
Thanks,
Ondrej

On Sat, Sep 17, 2011 at 7:41 AM, Markus Müller
<markus.mueller....@googlemail.com> wrote:
> Hallo
> Since I needed not only the jordan normal form but also the
> transformation I had a look at the code.
> (How nice to have open source CAS, Thanks for that)
> I tried to combine the computation of P and J and therefore had to
> look very closely.
> So I realized the bug
> It can be expressed very easy:
> In sympy 0.71 only the geometric and the algebraic multiplicity
> are used to determine the blocksizes.
>
> This is expressed by the interface of function:
>  self._jordan_split(multiplicity, geometrical)
> in the matrix class.
> However this is not possible in general since there exist different
> Jordan matrices
> (with different blocksizes ) that have the same eigenvectors with the
> same geometrical multiplicity as an counter example shows which I
> added in the comment for the patch.
>
> I hope that helps (took me a week nearly inspite of my plan to
> implement it in 4h...)
>
> Best regards
> Markus
>
> So here comes the patch with a lot of comments how it works and some
> testing code around it
> I inherited from matrix and overloaded the jordan_cells method:
>
>
> #!/usr/bin/python
> from sympy.interactive.printing import init_printing
> init_printing(use_unicode=True, wrap_line=False, no_global=True)
> from sympy.matrices import *
> def jordan_cell(eigenval, n):
>    n = int(n)
>    out = zeros(n)
>    for i in range(n-1):
>        out[i, i] = eigenval
>        out[i, i+1] = 1
>    out[n-1, n-1] = eigenval
>    return out
>
> class Matrix2(Matrix):
>   def jordan_cells(self, calc_transformation = True):
>      n=self.rows
>      Jcells = []
>      Pcols_new=[]
>      # to every eingenvalue may belong i blocks with size s(i)
>      # and a chain of generalized eigenvectors
>      # which will be determined by the following computations
>      # for every eigenvalue we will add a dictionary
>      # containing for all blocks the blockssizes and the attached
> chain vectors
>      # that will eventually be used to form the transformation P
>      JordanBlockStructures={}
>      _eigenvects=self.eigenvects()
>      for eigenval, multiplicity, vects in _eigenvects:
>         geometrical = len(vects)
>         if geometrical == multiplicity:
>             Jcell = diag( *([eigenval] * multiplicity))
>             Jcells.append(Jcell)
>             Pcols+=vects
>
>         elif geometrical==0:
>             raise MatrixError("Matrix has the eigen vector with
> geometrical multiplicity equal zero.")
>         else:
>
>            # Up to now we know nothing about the sizes of the blocks of our
> Jordan matrix
>            # Note that knowledge of algebraic and geometrical multiplicity
>            # will >> n o t << be sufficient to determin this structure
>            # The blocksize s could be defined as the minimal k where
>            # (self-lI)^k=0 (Since  (self-lI) is a nilpotent matrix such k is
> bound to exist.
>            # The worst case would be that k= (multiplicity-geometrical+1)
> but the blocks could be smaller.
>            # Consider for instance the following matrix
>            # [2 1 0 0]
>            # [0 2 1 0]
>            # [0 0 2 0]
>            # [0 0 0 2]
>            # which coincides with it own Jordan canonical form.
>            # It has only one eigenvalue l=2 of (algebraic) multiplicity=4.
>            # It has two eigenvectors. one belonging to the last row
> (blocksize 1)
>            # and one being the last part of a jordan chain of length 3
> (blocksize of the first block)
>            # Note again that it is not not possible to obtain this from the
> algebraic and geometrical
>            # multiplicity alone. This only gives us an upper limit for the
> dimension of one of
>            # the subspaces (blocksize of according jordan block) given by
>            # max=(multiplicity-geometrical+1) which is reached for our
> matrix
>            # but not for
>            # [2 1 0 0]
>            # [0 2 0 0]
>            # [0 0 2 1]
>            # [0 0 0 2]
>            # although multiplicity =4 and geometrical=2 are the same  for
> this matrix.
>            # Actually we have to increase k until (A-lI)^k=0
>            Z=zeros(self.rows)
>            I=eye(self.rows)
>            l=eigenval
>            M=(self-l*I)
>            # We will store the matrices (self-l*I)^k  for further
> computations
>            # for convinience only we store Ms[0]=(sefl-lI)^0=I
>            # so the index is the same as the power for all further Ms
> entries
>            # We also store the vectors that span these kernels (Ns[0] =[] )
>            # and also their dimensions a_s
>            # this is mainly done for debugging since the number of blocks of
> a given size
>            # can be computed from the a_s, in order to check our result
> which is obtained simpler
>            # by counting the number of jordanchains for a given s
>            # a_0 is dim(Kernel(Ms[0]) =dim (Kernel(I))=0 since I is regular
>            smax=0
>            Ms=[I]
>            Ns=[[]]
>            a=[0]
>            l_JordanChains={}
>            l_JordanBlockNumbers={}
>            ChainVectors=[]
>            while Ms[-1] != Z:
>               M_new=Ms[-1]*M
>               Ns_new=M_new.nullspace()
>               a_new=len(Ns_new)
>               Ms.append(M_new)
>               Ns.append(Ns_new)
>               a.append(a_new)
>               smax+=1
>            # We now have Ms[-1]=((self-l*I)**s)=Z=0
>            # We now know the size of the biggest jordan block
>            # associatet with l to be s
>            # now let us proceed with the computation of the associate part
> of the transformation matrix P
>            # We already know the kernel (=nullspace)  K_l of (self-lI) which
> consists of the
>            # eigenvectors belonging to eigenvalue l
>            # The dimension of this space is the geometric multiplicity of
> eigenvalue l.
>            # For every eigenvector ev out of K_l, there exists a subspace
> that is
>            # spanned by the jordan chain of ev. The dimension of this
> subspace is
>            # represented by the length s of the jordan block.
>            # The chain itself is given by {e_0,..,e_s-1} where:
>            # e_k+1 =(A-lI)e_k (*)
>            # and e_s-1=ev
>            # So it would be possible to start with the already known ev and
> work backwards until one
>            # reaches e_0. Unfortunately this can not be done by simply
> solving system (*) since its matrix
>            # is singular (by definition of the eigenspaces)
>            # This approach would force us a choose in every step the degree
> of freedom undetermined
>            # by (*). This is difficult to implement with computer algebra
> systems and also quite unefficient
>            # We therefore reformulate the problem in terms of nullspaces.
>            # To do so we start from the other end and choose e0's out of
>            # E=Kernel(self-lI)^s / Kernel(self-lI)^(s-1)
>            # Note that Kernel(self-lI)^s =Kernel(Z)=V (the whole vector
> space)
>            # So in the firs step s=smax this restriction turns out to
> actually restrict nothing at all
>            # and the only remaining condition is to chose vectors in
> Kernel(self-lI)^(s-1)
>            # Subsequently we compute e_1=(self-lI)e_0, e_2=(self-lI)*e_1 and
> so on.
>            # The subspace E can have a dimension larger than one
>            # That means that we have more than one Jordanblocks of size s
> for the eigenvalue l
>            # and as many jordanchains (This is the case in the second
> example)
>            # In this case we start as many jordan chains and have as many
> blocks of size s in the jcf
>            # We now have all the jordanblocks of size s but there might be
> others attached to the same
>            # eigenvalue that are smaller.
>            # So we will do the same procedure also for s-1 and so on until 1
> the lowest possible order
>            # where the jordanchain is of lenght 1 and just represented by
> the eigenvector
>            for s in reversed(xrange(1,smax+1)):
>               #we want the vectors in Kernel((self-lI)^s) (**)
>               S=Ms[s]
>               # but without those in Kernel(self-lI)^s-1 so we will add
> these as additional equations
>               # to the sytem formed by S (S will no longer be quadratic but
> this does not harm
>               # since S is rank deficiant)
>               excludeVectors=Ns[s-1]
>               for k in range(0,a[s-1]):
>                  S=S.col_join((excludeVectors[k]).transpose())
>               # we also want to exclude the vectors in the chains for the
> bigger blogs
>               # that we have already computed (if there are any)
>               # (That is why we start wiht the biggest s)
>               ######## implementation remark:#############
>               # Doing so for >> a l l << already computed chain vectors
>               # we actually exclude some vectors twice because they are
> already excluded
>               # by the condition (**)
>               # This happens if there are more than one blocks attached to
> the same eigenvalue >>and<<
>               # the current blocksize is smaller than the block whose chain
> vectors we exclude
>               # If the current block has size s_i and the next bigger block
> has size s_i-1 then
>               # the first s_i-s_i-1 chainvectors of the bigger block are
> allready excluded by (**)
>               # The unnecassary adding of these equations could be avoided
> if the algorithm would
>               # take into account the lengths of the already computed chains
> which are already stored
>               # and add only the last s items.
>               # However the following loop would be a good deal more nested
> to do so.
>               #
>               # A much more elegant alternative approach might be to drop
> condition (**) altogether
>               # because it is added implicitly by excluding the chainvectors
>               #
>               # Since adding a linear dependent equation does not change the
> result
>               # it can harm only in terms of efficiency.
>               # So to be sure i let it there for the moment
>               #
>               l=len(ChainVectors)
>               if l>0:
>                  for k in range(0,l):
>                     old=ChainVectors[k].transpose()
>                     S=S.col_join(old)
>
>               e0s=S.nullspace()
>               #determine the number of chain leaders which equals the number
> of blocks with that size
>               n_e0=len(e0s)
>               s_chains=[]
>               s_cells=[]
>               for i in range(0,n_e0):
>                  chain=[e0s[i]]
>                  for k in range(1,s):
>                     v=M*chain[k-1]
>                     chain.append(v)
>
>                  chain.reverse() #we want the chain leader appear as the last 
> of
> the block
>                  ChainVectors+=chain
>                  s_chains.append(chain)
>               l_JordanChains[s]=s_chains
>         JordanBlockStructures[eigenval]=l_JordanChains
>      P=zeros(n)
>      for l in reversed(sorted(JordanBlockStructures.keys())): #start
> with the biggest eigenvalue
>         l_JordanChains=JordanBlockStructures[l]
>         for s in reversed(sorted((l_JordanChains).keys())):  #start with the
> biggest block
>            s_chains=l_JordanChains[s]
>            block= jordan_cell(eigenval,s)
>            number_of_s_chains=len(s_chains)
>            for i in range(0,number_of_s_chains):
>               Jcells.append(block)
>               chainVectors=s_chains[i]
>               lc=len(chainVectors)
>               assert(lc==s)
>               for j in range(0,lc):
>                  generalizedEigenVector=chainVectors[j]
>                  Pcols_new.append(generalizedEigenVector)
>
>      J = diag(*Jcells)
>      P=zeros(n)
>      for j in range(0,n):
>         P[:,j]=Pcols_new[j]
>      assert(J==P.inv()*self*P)
>      return (P,Jcells)
>
> #A=Matrix2(5,5,
> #   [5,1,0,0,0,
> #    0,5,1,0,0,
> #    0,0,5,0,0,
> #    0,0,0,5,1,
> #    0,0,0,0,5]
> #   )
> A=Matrix2(7,7,
>   [5,1,0,0,0,0,0,
>    0,5,1,0,0,0,0,
>    0,0,5,1,0,0,0,
>    0,0,0,5,0,0,0,
>    0,0,0,0,5,0,0,
>    0,0,0,0,0,5,1,
>    0,0,0,0,0,0,5]
>   )
> #A=Matrix2(5,5,
> #   [25, -16,  30, -44, -12,
> #    13,  -7,  18, -26,  -6,
> #   -18,  12, -21,  36,  12,
> #    -9,   6, -12,  21,   6,
> #    11,  -8,  15, -22,  -3]
> #)
>
>
> print(A)
> (P,cells)=A.jordan_cells()
> print "P=",P
> print "P^-1 A P="
> print P.inv()*A*P
>
>
> --
> You received this message because you are subscribed to the Google Groups 
> "sympy-patches" group.
> To post to this group, send email to sympy-patches@googlegroups.com.
> To unsubscribe from this group, send email to 
> sympy-patches+unsubscr...@googlegroups.com.
> For more options, visit this group at 
> http://groups.google.com/group/sympy-patches?hl=en.
>
>

-- 
You received this message because you are subscribed to the Google Groups 
"sympy-patches" group.
To post to this group, send email to sympy-patches@googlegroups.com.
To unsubscribe from this group, send email to 
sympy-patches+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sympy-patches?hl=en.

Reply via email to