daredare.extern.toolkithelpers

Package daredare :: Package extern :: Module toolkithelpers |
[hide private]
[frames] | no frames]

# Source Code for Module daredare.extern.toolkithelpers

      1  # toolkithelpers:
      2  # 1) qzswitch port
      3  # 2) qzdiv port
      4  # 3) the numpy-octave bridge class
      5
      6  from numpy import mat, c_, r_, multiply, where, sqrt, newaxis
      7  from numpy.linalg import solve
      8  from numpy.matlib import diag
      9
     10 -def qzswitch(i, A2, B2, Q, Z):
     11      #print i, A2, B2, Q, Z
     12      Aout = A2.copy(); Bout = B2.copy(); Qout = Q.copy(); Zout = Z.copy()
     13      ix = i-1    # from 1-based to 0-based indexing...
     14      # use all 1x1-matrices for convenient conjugate-transpose even if real:
     15      a = mat(A2[ix, ix]); d = mat(B2[ix, ix]); b = mat(A2[ix, ix+1]);
     16      e = mat(B2[ix, ix+1]); c = mat(A2[ix+1, ix+1]); f = mat(B2[ix+1, ix+1])
     17      wz = c_[c*e - f*b, (c*d - f*a).H]
     18      xy = c_[(b*d - e*a).H, (c*d - f*a).H]
     19      n = sqrt(wz*wz.H)
     20      m = sqrt(xy*xy.H)
     21      if n[0,0] == 0: return (Aout, Bout, Qout, Zout)
     22      wz = solve(n, wz)
     23      xy = solve(m, xy)
     24      wz = r_[ wz, \
     25              c_[-wz[:,1].H, wz[:,0].H]]
     26      xy = r_[ xy, \
     27           c_[-xy[:,1].H, xy[:,0].H]]
     28      Aout[ix:ix+2, :] = xy * Aout[ix:ix+2, :]
     29      Bout[ix:ix+2, :] = xy * Bout[ix:ix+2, :]
     30      Aout[:, ix:ix+2] = Aout[:, ix:ix+2] * wz
     31      Bout[:, ix:ix+2] = Bout[:, ix:ix+2] * wz
     32      Zout[:, ix:ix+2] = Zout[:, ix:ix+2] * wz
     33      Qout[ix:ix+2, :] = xy * Qout[ix:ix+2, :]
     34      return (Aout, Bout, Qout, Zout)
     35
     36 -def qzdiv(stake, A2, B2, Q, Z):
     37      Aout = A2.copy(); Bout = B2.copy(); Qout = Q.copy(); Zout = Z.copy()
     38      n, jnk = A2.shape
     39      # remember diag returns 1d
     40      root = mat(abs(c_[diag(A2)[:,newaxis], diag(B2)[:,newaxis]]))
     41      root[:,1] /= where(root[:,0]<1e-13, -root[:,1], root[:,0])
     42      for i in range(1,n+1)[::-1]:        # always first i rows, decreasing
     43          m = None
     44          for j in range(1,i+1)[::-1]:    # search backwards in the first i rows
     45              #print root.shape
     46              #print n, i, j
     47              #print 'i,j in qzdiv', i,j
     48              if root[j-1,1] > stake or root[j-1,1] < -0.1:
     49                  m = j                   # get last relevant row
     50                  break
     51          if m == None: return (Aout, Bout, Qout, Zout)
     52          for k in range(m,i):            # from relev. row to end of first part
     53              (Aout, Bout, Qout, Zout) = qzswitch(k, Aout, Bout, Qout, Zout)
     54              root[k-1:k+1, 1] = root[k-1:k+1, 1][::-1]
     55      return (Aout, Bout, Qout, Zout)
     56
     57  import numpy as n
     58  from subprocess import Popen, PIPE
     59 -class octave4numpy:
     60      '''
     61      Lets octave functions calculate things, collects results as numpy-matrices.
     62
     63      Example usage qz decomposition with octave syntax
     64       [AA,BB,Q,Z,V,W,lambda] = qz(A,B):
     65
     66      1) "session approach"
     67      myoct = octave4numpy()
     68      myoct.definemats(['A', 'B'], [a, b])    # a and b: numpy arrays/matrices
     69      (AA,BB,Q,Z,V,W,lda) = myoct.execfunc('qz', ['A', 'B'], 7)
     70      # (7 is the number of returned objects, must be specified here in advance!)
     71      # (possibly execute other funcs after this)
     72      myoct.close()
     73
     74      2) or the "shortcut approach", quick way for one-time use:
     75      myoct = octave4numpy('qz', (a, b), 7)
     76      (AA,BB,Q,Z,V,W,lda) = myoct.results
     77      # (connection to octave is automatically closed in this variant)
     78
     79      If something else than 'octave' is needed to invoke your octave version,
     80        specify the command string in optional keyword arg cmd, like so:
     81      myoct = octave4numpy(cmd = 'octave2.0')
     82
     83      The user should not pass 1d-numpy-arrays; be explicit about row or col!
     84
     85      to do:
     86      - test on windows
     87      - allow to set precision
     88      - test if it also works with complex numbers (it should)
     89      ...
     90      '''
     91 -    def __init__(self, func = None, args = None, numout = 1, cmd = 'octave'):
     92          '''
     93          opens the octave connection via pipes (is this really cross-platform?)
     94
     95          optionally already executes a function for quick use, func is the name
     96           of an octave function; then assignments must match the number of return
     97           objects from that specific octave function
     98          '''
     99          self.o = Popen([cmd, '-q'], stdin = PIPE, stdout = PIPE)  # -q: quiet
    100          if type(func) == type('f'):         # a function name was specified
    101              assert args != None
    102              if type(args) == type([]): args = tuple(args) # forgive user error
    103              elif type(args) != type((1,)):  # allow single matrix here
    104                  args = (args,)              # convert to tuple
    105              # use generic names 'a0', 'a1', ...
    106              argnamelist = ['a' + str(ix) for ix in range(len(args))]
    107              self.definemats(argnamelist, args)
    108              self.results = self.execfunc(func, argnamelist, numout)
    109              self.octreturncode = self.close()
    110
    111 -    def definemats(self, namelist, mlist):
    112          '''
    113          transfers matrices (values, can be 1x1) to octave for further use
    114
    115          names must be valid denominator strings
    116
    117          examples:
    118          myoct.definemats('a', m)
    119          myoct.definemats(['a', 'b'], [m1, m2])
    120
    121          (since octave also accepts j for imaginary parts, should also work for
    122           complex numbers)
    123          '''
    124          if type(namelist) == type('n'):     # just one string instead of list
    125              namelist = [namelist]
    126              mlist = [mlist]
    127          assert len(namelist) == len(mlist)
    128          for name in namelist:
    129              m = mlist[namelist.index(name)]
    130              out =  ';'.join( \
    131                  [','.join(map(str, row)) for row in n.mat(m).tolist()] )
    132              out =  '[' + out + ']'
    133              self.o.stdin.write(name + '=' + out + '\n')
    134              # just to clear stdout from noise:
    135              self.getreaction()
    136
    137 -    def getreaction(self, items = 1):
    138          '''
    139          parse (and thereby chop off) octave's stdout
    140
    141          Converting the string to a numpy matrix is left to another method.
    142          '''
    143          ## octave's pattern seems to be:
    144          ##   1) two '\n' after '=' (and thus before first matrix row)
    145          ##   2) one '\n' after each matrix row
    146          ##   3) two '\n' after last matrix row (and thus after each item)
    147          ##
    148          ## After that it's an open-ended file and reading stdout stalls,
    149          ##  so we must avoid that!
    150          #
    151          # this is all very unpythonic, but if it works...
    152          output = ''
    153          itemcount = 0
    154          while itemcount < items:
    155              line = self.o.stdout.readline()
    156              output = output + line
    157              if output.endswith('\n\n') and output[-3] != '=': itemcount += 1
    158          #print output
    159          return output
    160
    161 -    def octstr2nmat(self, octstring):
    162          '''
    163          creates numpy matrix from octave's matrix printout
    164
    165          only one matrix per call should be passed
    166          '''
    167          # use only the second part, after the leading '...=' and w/o whitespace:
    168          temp = octstring.split('=')[1].strip()
    169          # convert row sep
    170          temp = temp.replace('\n', ';')
    171          ## deal with complex numbers;
    172          # octave uses 'i', python 'j'
    173          temp = temp.replace('i', 'j')
    174          # and octave's formatting 3 + 5i with blanks surrounding the plus sign
    175          #  produces numpy error, so strip blanks
    176          temp = temp.replace(' + ', '+')
    177          # (arbitrary number of blanks as col sep should be ok for n.mat)
    178          return n.mat(temp)
    179
    180 -    def execfunc(self, funcname, argnames, numout = 1):
    181          '''
    182          executes an octave function call and returns matrix results
    183
    184          For example, the octave function [AA,BB,Q,Z,V,W] = qz(A,B) would be
    185           called by
    186           (a, b, q, z, v, w) = myoctconn.exefunc('qz', ['myA', 'myB'], 6)
    187
    188          The arg names must be given as strings, and must have been defined in
    189           octave before with definemats.
    190
    191          If there's only one arg, it is admissible to provide just one arg string
    192           instead of an 1-element list
    193          '''
    194          if type(argnames) == type('a'): argnames = [argnames]
    195          # construct lhs, using generic names 'r0', 'r1', etc.
    196          lhs = '['
    197          for returnix in range(numout):
    198              lhs = lhs + 'r' + str(returnix) + ','
    199          lhs = lhs[:-1] + ']'        # stripping trailing comma
    200          # construct rhs
    201          rhs = funcname + '('
    202          for argname in argnames: rhs = rhs + argname + ','
    203          rhs = rhs[:-1] + ')'
    204          # execute
    205          self.o.stdin.write(lhs + '=' + rhs + '\n')
    206          outlist = []
    207          for item in range(numout):
    208              outlist.append(self.octstr2nmat(self.getreaction()))
    209          return outlist
    210
    211 -    def close(self):
    212          self.o.communicate('quit')
    213          return self.o.returncode
    214
    215  #########
    216  if __name__ == '__main__':
    217      print 'running testcode'
    218      # initial and debugging bits and pieces
    219      myo = octave4numpy()
    220      a = n.hstack( [n.ones((4,2)), n.random.rand(4,2)] )
    221      b = n.hstack( [n.zeros((4,2)), n.random.rand(4,2)] )
    222      myo.definemats('a', a)
    223      myo.definemats('b', b)
    224      # cleaning up
    225      print myo.close()
    226      del myo
    227
    228      # some serious test cases
    229      print 'shortcut approach:'
    230      c = a
    231      d = b
    232      myo2 = octave4numpy('qz', (c, d), 7)
    233      # and then the result matrices are available as:
    234      for result in myo2.results: print result
    235      print 'octreturncode: ' + str(myo2.octreturncode)
    236      del myo2
    237
    238      print 'session approach:'
    239      e = a
    240      f = b
    241      myoct = octave4numpy()
    242      myoct.definemats(['e', 'f'], [e, f])
    243      (AA,BB,Q,Z,V,W, lam) = myoct.execfunc('qz', ['e', 'f'], 7)
    244      myoct.close()
    245      print V
    246      print W
    247