'''
.. 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)
>>> locals().update(blades)
>>> 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
-(1.0^e4) - (1.0^e5)
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 typing import overload
from . import conformalize, op, gp, MultiVector, Cl
from numpy import zeros, e, log
from numpy.random import rand
import math
try:
import pyganja as ganja
pyganja_available = True
except ImportError:
pyganja_available = False
[docs]class CGAThing(object):
'''
base class for cga objects and operators.
maps versor product to `__call__`.
'''
[docs] def __init__(self, cga: 'CGA') -> None:
self.cga = cga
self.layout = cga.layout
@overload
def __call__(self, other: MultiVector) -> MultiVector: pass # noqa: F811
@overload
def __call__(self, other: 'CGAThing') -> 'CGAThing': pass # noqa: F811
def __call__(self, other): # noqa: F811
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) -> MultiVector:
'''
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) -> MultiVector:
'''
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 = CGA(3)
>>> locals().update(cga.blades)
>>> F = cga.flat() # from None
>>> F = cga.flat(2) # from dim of space
>>> F = cga.flat(e1, e2) # from points
>>> F = cga.flat(cga.flat().mv) # from existing multivector
'''
# could inherent some generic CGAObject class
[docs] def __init__(self, cga, *args) -> None:
super().__init__(cga)
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) -> str:
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 = CGA(3)
>>> locals().update(cga.blades)
>>> cga.round() # from None
Sphere
>>> cga.round(2) # from dim of space
Sphere
>>> cga.round(e1, e2, -e1) # from points
Circle
>>> cga.round(cga.flat().mv) # from existing multivector
Sphere
'''
# could inherent some generic CGAObject class
[docs] def __init__(self, cga, *args) -> None:
super().__init__(cga)
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) -> str:
names = {4: 'Sphere', 3: 'Circle', 2: 'Point Pair', 1: 'Point'}
if self.dim <= 4:
return names[self.dim + 2]
else:
return '%i-Round' % (self.dim + 2)
@property
def dim(self):
'''
dimension of this round
'''
gr, = self.mv.grades()
return gr - 2
@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
----------
>>> cga = CGA(3)
>>> locals().update(cga.blades)
>>> 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) -> None:
super().__init__(cga)
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) -> str:
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
----------
>>> cga = CGA(3)
>>> D = cga.dilation() # from none
>>> D = cga.dilation(.4) # from number
'''
[docs] def __init__(self, cga, *args) -> None:
super().__init__(cga)
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) -> str:
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
----------
>>> cga = CGA(3)
>>> locals().update(cga.blades)
>>> 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) -> None:
super().__init__(cga)
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) -> str:
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
----------
>>> cga = CGA(3)
>>> locals().update(cga.blades)
>>> K = cga.transversion() # from None
>>> K = cga.transversion(e1+e2) # from base vector
>>> K = cga.transversion(cga.up(e1+e2)) # from null vector
>>> T = cga.translation()
>>> K = cga.transversion(T.mv) # from existing translation rotor
'''
[docs] def __init__(self, cga, *args) -> None:
CGAThing.__init__(self, cga)
self.mv = Translation(cga, *args).inverted()
def __repr__(self) -> str:
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) -> None:
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) -> MultiVector:
'''
random vector in the lower(original) space
'''
return self.I_base.project(self.layout.randomV())
[docs] def null_vector(self, x=None) -> MultiVector:
'''
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) -> Round:
'''
see :class:`Round`
'''
return Round(self, *args)
[docs] def flat(self, *args) -> Flat:
'''
see :class:`Flat`
'''
return Flat(self, *args)
# Operators
[docs] def translation(self, *args) -> Translation:
'''
see :class:`Translation`
'''
return Translation(self, *args)
[docs] def transversion(self, *args) -> Transversion:
'''
see :class:`Transversion`
'''
return Transversion(self, *args)
[docs] def dilation(self, *args) -> Dilation:
'''
see :class:`Dilation`
'''
return Dilation(self, *args)
[docs] def rotation(self, *args) -> Rotation:
'''
see :class:`Rotation`
'''
return Rotation(self, *args)
# methods
[docs] def straight_up(self, x) -> MultiVector:
'''
place a vector from layout_orig into this CGA, without up()
'''
return self.I_base.project(self.up(x))