text: 
  '''
  .. currentmodule:: clifford.cga
  ========================================
  cga (:mod:`clifford.cga`)
  ========================================
  
  Object Oriented Conformal Geometric Algebra. 
  
  Examples
  -----------
  >>> from clifford import Cl
  >>> from clifford.cga import CGA
  >>> g3,blades  = Cl(3)
  >>> g3c = CGA(g3)
  >>> C = g3c.round(3)             # create random sphere 
  >>> T = g3c.translation(e1+e2)   # create translation
  >>> C_ = T(C)                    # translate the sphere 
  >>> C_.center                    # compute center of sphere 
  
  The CGA 
  ========
  
  .. autosummary::
      :toctree: generated/
  
      CGA
      
  
  Objects
  ================
  
  .. autosummary::
      :toctree: generated/
  
      Flat
      Round
  
  Operators
  ================
  
  .. autosummary::
      :toctree: generated/
  
      Rotation
      Dilation
      Translation
      Transversion
  
  Meta-Class
  ===========
  
  .. autosummary::
      :toctree: generated/
  
      CGAThing
  
  '''
  
  from functools import reduce
  from . import conformalize, op,gp, MultiVector,Cl
  from numpy import zeros,e,log
  from numpy.random import rand
  import math
  
  global pyganja_available
  try:
      import pyganja as ganja
      pyganja_available =True
  except:
      pyganja_available = False
  
  class CGAThing(object):
      '''
      base class for cga objects and operators. 
      
      maps versor product to `__call__`. 
      '''
      def __call__(self, other):
          if isinstance(other, MultiVector):
              if other.grades() ==[1]:
                  null= self.cga.null_vector(other)
                  return self.mv*null*~self.mv
              return self.mv*other*~self.mv
          else:
              klass = other.__class__
              return klass(self.cga, self.mv*other.mv*~self.mv)
       
              
      def inverted(self):
          '''
          inverted version of this thing. 
          
          self -> ep*self*ep
          
          where ep is the positive added basis vector 
          '''
          return self.cga.ep*self.mv* self.cga.ep
      
      def involuted(self):
          '''
          inverted version of this thing. 
          
          self -> E0*self*E0
          
          where E0 is the added minkowski bivector 
          '''
          return self.cga.E0*self.mv* self.cga.E0
          
  ## Objects  
  class Flat(CGAThing):
      '''
          A line, plane, or hyperplane. 
          
          Typically constructed as method of existing cga, like `cga.flat()`
          
          multivector is accessable by `mv` property
          
          Parameters 
          -----------
          cga : `CGA`
              the cga object
          
          args : [int, Multivector,Multivectors]    
              * if nothing supplied, generate a flat of highest dimension 
              * int: dimension of flat  (2=line, 3=plane, etc)
              * Multivector : can be 
                * existing Multivector representing the Flat
                * vectors on the flat
                
          Examples
          ----------  
          
          >>> cga.flat()               # from None 
          >>> cga.flat(2)              # from dim of space
          >>> cga.flat(e1,e2)          # from points
          >>> cga.flat(cga.flat().mv)  # from existing multivector   
          '''
      # could inherent some generic CGAObject class
      def __init__(self,cga, *args):
          
          self.cga = cga
          self.layout = cga.layout # note: self.layout is the cga layout
          self.einf = self.cga.einf # we use this alot
          
          if len(args) == 0:    
              # generate random highest dimension flat
              nulls = [self.cga.null_vector() for k in range(self.layout.dims-2)]
              self.mv = reduce(op,nulls + [self.einf]) 
          
          
          elif len(args) == 1:
              # from existing multivector
              if isinstance(args[0], MultiVector):
                  self.mv = args[0]
              
              # generate random flat  for given  dimension
              elif isinstance(args[0], int):
                  dim = args[0]
                  points = [self.cga.base_vector() for k in range(dim+1)]
                  points = list(map(self.cga.up, points))
                  self.mv = reduce(op,points + [self.einf])
              
              
          # from vectors on flat         
          else:
              nulls = map(self.cga.null_vector,args)
              if self.einf not in nulls:
                  nulls = list(nulls)+[self.einf]
                  
              self.mv = reduce(op,nulls) 
          
          self.mv = self.mv.normal()
      
      def __repr__(self):
          return '%i-Flat'%self.dim
                      
  class Round(CGAThing):
      '''
      A point pair, circle, sphere or hyper-sphere.
      
      Typically constructed as method of existing cga, like `cga.round()`
      
      multivector is accessable by `mv` property
      
      Parameters 
      -----------
      cga : `CGA`
          the cga object
      
      args : [int, Multivector,Multivectors]    
          * if nothing supplied, generate a round of highest dimension 
          * int: dimension of flat  (2=point pair, 3=circle, etc)
          * Multivector : can be 
            * existing Multivector representing the round
            * vectors on the round
            
      Examples
      ----------  
      
      >>> cga.round()               # from None 
      >>> cga.round(2)              # from dim of space
      >>> cga.round(e1,e2,-e1)      # from points
      >>> cga.round(cga.flat().mv)  # from existing multivector   
      '''
      # could inherent some generic CGAObject class
      def __init__(self,cga, *args):
          # me
          self.cga = cga
          self.layout = cga.layout # note: self.layout is the cga layout
          
          
          if len(args) == 0:    
              # generate random highest dimension round
              nulls = [self.cga.null_vector() for k in range(self.layout.dims-1)]
              self.mv = reduce(op,nulls) 
          
          
          elif len(args) == 1:
              # from existing multivector
              if isinstance(args[0], MultiVector):
                  self.mv = args[0]
              
              # generate random round  for given  dimension
              elif isinstance(args[0], int):
                  dim = args[0]
                  points = [self.cga.base_vector() for k in range(dim+2)]
                  points = map(self.cga.up, points)
                  self.mv = reduce(op,points)
          
              # from center, radius tuple
              else:
                  if len(args[0]) == 2:
                      center,radius = args[0]
                      center = self.cga.null_vector(center)
                      dual_round = (center - .5*radius**2*self.cga.einf)
                      self.mv = dual_round.normal().dual()
                         
          # from vectors on round         
          else:
              nulls = map(self.cga.null_vector,args)
              self.mv = reduce(op,nulls) 
          
          self.mv = self.mv.normal()
          
      def from_center_radius(self,center, radius):
          '''
          construct a round from center/radius
          '''
          center = self.cga.null_vector(center)
          self.mv = (center - .5*radius**2*self.cga.einf).normal().dual()
          return self
      
      def __repr__(self):
          names = {4:'Sphere',3:'Circle',2:'Point Pair',1:'Point'}
          if self.dim <=4:
              return names[self.dim]
          else:
              return '%i-Round'%self.dim
      
      @property
      def dim(self):
          '''
          dimension of this round
          '''
          return self.mv.grades()[0]
      
      @property
      def center(self):
          '''
          center of this round, as a null vector 
          '''
          return self.mv * self.cga.einf * self.mv
      
      @property
      def center_down(self):
          '''
          center of this round, as a down-projected  vector (in I_base)
          
          (but still in cga's layout)
          '''
          return self.cga.down(self.center)
      
      @property
      def radius(self):
          '''
          radius of the round (a float)
          '''
          dual_sphere = self.dual 
          dual_sphere / (-dual_sphere | self.cga.einf)
          return math.sqrt(abs(dual_sphere * dual_sphere))
  
      @property 
      def dual(self):
          '''
          self.mv* self.layout.I
          '''
          return self.mv* self.layout.I
              
  ## Operators
  class Translation(CGAThing):
      '''
      A Translation
      
      Can be constructed from a vector in base space or a null
      vector, or nothing. 
      
      Parameters 
      -------------
      args : [none, `clifford.Multivector`]
          if none, a random translation will be generated
          several types of Multivectors can be used:
              * base vector - vector in base space 
              * null vector 
              * existing translation rotor  
          
      Examples
      ----------
      >>> T = cga.translation()       # from None 
      >>> T = cga.translation(e1+e2)  # from base vector  
      >>> T = cga.translation(cga.up(e1+e2)) # from null vector 
      >>> T = cga.translation(T.mv)  # from existing translation rotor 
      '''
      def __init__(self,cga, *args):
          self.cga = cga
          self.layout = cga.layout
          
          if len(args) ==0:    
              # generate generator!
              mv = 1 - self.cga.base_vector()*self.cga.einf/2.
          
          elif len(args)==1:
              arg = args[0]
              if isinstance(arg, MultiVector):
                  if arg.grades()==[1]:
                      # we have vector 
                      mv = 1 - self.cga.straight_up(arg)*self.cga.einf/2.     
                  if arg.grades()==[0,2]:
                      # we have ro tor  
                      ## TODO ensure its a translation
                      mv = args[0]
          else:
              raise ValueError('bad input')
              
          self.mv = mv
      
      def __repr__(self):
          return 'Translation'
    
  class Dilation(CGAThing):
      '''
      A global dilation
      
      Parameters 
      -------------
      args : [none, number]
          if none, a random dilation will be generated
          if a number, dilation of given amount
          
      Examples
      ----------
      >>> D = cga.dilation()          # from  none 
      >>> D = cga.dilation(.4)        # from number
      '''
      def __init__(self,cga, *args):
          self.cga = cga
          self.layout = cga.layout
          
          if len(args) ==0:    
              # generate a dilation 
              args= [rand()]
          
          if len(args)==1:
              arg = args[0]
              if isinstance(arg, MultiVector):
                  if arg.grades()==[0,2]:
                      # we have a rotor 
                      mv = arg
                  if arg.grades == [0]:
                      arg = float(arg)
              
              if arg<0:
                  raise(ValueError('dilation should be positive'))
              
              mv = e**((-log(arg)/2.)*(self.cga.E0)) 
              
                  
          else:
              raise ValueError('bad input')
              
          self.mv = mv
      
      def __repr__(self):
          return 'Dilation'
  
  class Rotation(CGAThing):
      '''
      A Rotation
      
      Can be constructed from a generator, rotor, or none
      
      Parameters 
      -------------
      args : [none, `clifford.Multivector`]
          if none, a random translation will be generated
          several types of Multivectors can be used:
              * bivector   - interpreted as the generator
              * existing translation rotor  
          
      Examples
      ----------
      >>> R = cga.rotation()          # from None
      >>> R = cga.rotation(e12+e23)   # from bivector 
      >>> R = cga.rotation(R.mv)   # from bivector 
      '''
      def __init__(self,cga, *args):
          self.cga = cga
          self.layout = cga.layout
          
          if len(args) ==0:    
              # generate a rotation
              
              U = self.layout.randomMV()(2)
              U = self.cga.I_base.project(U)
              self.mv = e**(U)
          
          
          elif len(args)==1:
              arg = args[0]
              if isinstance(arg, MultiVector):
                  if arg.grades()==[0,2]:
                      # we have a rotor 
                      self.mv = arg
                  
                  elif arg.grades() == [2]:
                      # we have a bivector, make sure its in base space
                      if arg^self.cga.I_base != 0:
                          arg = self.cga.I_base.project(arg)
                      self.mv = e**(arg)
                  else:
                      # multivector has improper grade
                      raise ValueError('bad input')
              else:
                  # arg isnt a multivector
                  raise ValueError('bad input')            
                  
          else:
              # more than 1 arg
              raise ValueError('bad input')
                  
      def __repr__(self):
          return 'Rotation'    
  
  class Transversion(Translation):
      '''
      A Transversion
      
      A transversion is a combination of an inversion-translation-inversion,
      or in other words an inverted translation operator. This inherits
      from `Translation`
      
      Can be constructed from a vector in base space or a null
      vector, or nothing.  
      
      Parameters 
      -------------
      args : [none, `clifford.Multivector`]
          if none, a random transversion will be generated
          several types of Multivectors can be used:
              * base vector - vector in base space 
              * null vector 
              * existing transversion rotor  
          
      Examples
      ----------
      >>> K = cga.transversion()       # from None 
      >>> K = cga.transversion(e1+e2)  # from base vector  
      >>> K = cga.transversion(cga.up(e1+e2)) # from null vector 
      >>> K = cga.transversion(T.mv)  # from existing translation rotor 
      '''
      def __init__(self,cga, *args):
          self.mv = Translation(cga, *args).inverted()
          
      def __repr__(self):
          return 'Transversion'    
         
           
  class CGA(object):
      '''
      Conformal Geometric Algebra 
      
      conformalizes the layout_orig,  and provides several
      methods and for objects/operators
      
      Parameters 
      -----------
      layout_orig: [`clifford.Layout`, int]
          a layout for the *base* geometric algebra which is conformalized
          if given as an int, then generates a euclidean space of given 
          dimension
          
      Examples 
      ----------
      >>> from clifford import Cl
      >>> from clifford.cga import CGA
      >>> g3,blades  = Cl(3)
      >>> g3c = CGA(g3)
      >>> g3c = CGA(3)
      
      '''
      def __init__(self, layout_orig):
          if isinstance( layout_orig,int):
              layout_orig,blades = Cl(layout_orig)
          self.layout_orig = layout_orig
          self.layout, self.blades, stuff = conformalize(layout_orig)
          self.__dict__.update(stuff)
      
      ## Objects
      def base_vector(self):
          '''
          random vector in the lower(original) space
          '''
          return self.I_base.project(self.layout.randomV())
      
      def null_vector(self,x=None):
          '''
          generates random null vector if x is None, or
          returns a null vector from base vector x, if  x^self.I_base ==0 
          returns x, 
          
          a null vector will lay on the horisphere
          '''
          if x is None:
              return self.up(self.base_vector())
          else:
              if x^self.I_base ==0:
                  return self.up(x)
              return x
      
      def round(self, *args):
          '''
          see :class:`Round`
          '''
          return Round(self,*args) 
      
      def flat(self, *args):
          '''
          see :class:`Flat`
          '''
          return Flat(self,*args) 
      ##  Operators
      def translation(self, *args):
          '''
          see :class:`Translation`
          '''
          return Translation(self, *args)
          
      def transversion(self, *args):
          '''
          see :class:`Transversion`
          '''
          return Transversion(self, *args)
  
      def dilation(self,*args):
          '''
          see :class:`Dilation`
          '''
          return Dilation(self,*args)
      
      def rotation(self,*args):
          '''
          see :class:`Rotation`
          '''
          return Rotation(self,*args)
      
      ##  methods
      def straight_up(self, x):
          '''
          place a vector from layout_orig into this CGA, without up()
          '''
          return self.I_base.project(self.up(x))
          
  