Source code for clifford.cga


'''
.. 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

[docs]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)
[docs] 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
[docs] 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
[docs]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
[docs] 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
[docs]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
[docs] 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()
[docs] 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
[docs]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 '''
[docs] 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'
[docs]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 '''
[docs] 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'
[docs]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 '''
[docs] 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'
[docs]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 '''
[docs] def __init__(self,cga, *args): self.mv = Translation(cga, *args).inverted()
def __repr__(self): return 'Transversion'
[docs]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) '''
[docs] 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
[docs] def base_vector(self): ''' random vector in the lower(original) space ''' return self.I_base.project(self.layout.randomV())
[docs] 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
[docs] def round(self, *args): ''' see :class:`Round` ''' return Round(self,*args)
[docs] def flat(self, *args): ''' see :class:`Flat` ''' return Flat(self,*args)
## Operators
[docs] def translation(self, *args): ''' see :class:`Translation` ''' return Translation(self, *args)
[docs] def transversion(self, *args): ''' see :class:`Transversion` ''' return Transversion(self, *args)
[docs] def dilation(self,*args): ''' see :class:`Dilation` ''' return Dilation(self,*args)
[docs] def rotation(self,*args): ''' see :class:`Rotation` ''' return Rotation(self,*args)
## methods
[docs] def straight_up(self, x): ''' place a vector from layout_orig into this CGA, without up() ''' return self.I_base.project(self.up(x))