# 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
>>> 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):
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)

else:
if len(args[0]) == 2:
center = self.cga.null_vector(center)
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()

'''
'''
center = self.cga.null_vector(center)
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
'''

@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
'''
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):
# we have vector
mv = 1 - self.cga.straight_up(arg)*self.cga.einf/2.
# we have ro tor
## TODO ensure its a translation
mv = args[0]
else:

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):
# we have a rotor
mv = arg
arg = float(arg)

if arg<0:
raise(ValueError('dilation should be positive'))

mv = e**((-log(arg)/2.)*(self.cga.E0))

else:

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):
# we have a rotor
self.mv = arg

# 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:
else:
# arg isnt a multivector

else:
# more than 1 arg

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
>>> g3c = CGA(g3)
>>> g3c = CGA(3)

'''
[docs]    def __init__(self, layout_orig):
if isinstance( layout_orig,int):
self.layout_orig = 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))