# Tools for generic geometric transformations in arbitrary spaces. # Last edited on 2021-09-24 08:14:45 by stolfi import trafo_IMP; from trafo_IMP import Trafo_IMP # This module is about the representation of arbitrary transformations # in some unspecified geometric and topological /space/ {\RX}, such as # {n}-dimensional Cartesian or projective space, whose elements will be # called /points/. # # A /transformation/, or /trafo/ for short, is continuous bijection (a # homeomorphism) of the space to itself. Applying a trafo {T} to a set # of points {X} produces another set {Y = T(X) = {T(x) : x \in X}} Note # that if {X} is empty or the whole space, {T(X)} will be {X} for any # trafo {T}. plenum, {T(F)} is always {F}. Note also that geometric # transformations commute with set complement and distribute over set # union and intersection. # # For the procedures of this module, a trafo may be either a {Trafo} # object, or a tuple {(kinv, T1,T2,..Tn)} where {kinv} is {+1} or {-1} # and {T1,T2,...,Tn} are trafos. If {kinv} is {+1}, the tuple represents the # compostion of those trafos, applied in that order; if {kinv} is {-1}, it # represents the inverse of that, namely the inverses of {Tn,...,T2,T1} # applied in this order. # # A trafo may also be {None}, representing the identiy # function on {\RX}. That is equivalent to the tuples {(+1)} and {(-1)}. # # A /simple/ trafo is either {None} or a pair {(kinv, Tobj)} where {Tobj} is a # {Trafo} object. class Trafo(Trafo_IMP): # An object {T} of this class represents a transformation of the space # {\RX}. The nature of {\RX} and of the transformation is defined only # by subclasses. # # To create elements of this class, use "Trafo(nn)" where {nn} is {None} # or a string that is used in printouts and debigging as the trafo's name. # The only method is {T.get_name()} that returns that string. # # Any derived class {CL} should provide a method {Tobj.combine(T2)} that # tries to compute the composition of an object {Tobj} of class {CL} # with the simple trafo {T2}, in that order. The method should return # either a simple trafo {T3}, or the tuple {(+1 Tobj T2)}. pass def get_name(T): # Returns the name of the trafo {T}. # # If {T} is a singleton trafo, meaning either a {Trafo} object {Tobj} # or a list {(kinv Ts)} where {Ts} is a singleton trafo, then it # returns name that was given when the underlying {Trafo} object was # created, with a "'" suffix added or removed if {T} is equivalent to # the inverse of that object. # # If {T} is {None}, or the singleton tuples {(+1,)}, or {(-1,)} the retult is "None". # # Otherwise, if {T} is a tuple {(kinv T1 T2...Tn)}, the result is the # strinb "({N1} {N2} ... {Nn})", where {N1}, {N2}, ... {Nn} are the # names of those trafos; with a "'" suffix added if {kinv} is {-1}. return trafo_IMP.get_name(T) def set_name(T, name): # The parameter {T} must be a singleton trafo. The proceduer sets the # name field of of the underlying {Trafo} object to {name}, with a "'" # suffix added or removed if {T} is equovalent to the inverse of that # object. # # The {name} cannot be {None}. trafo_IMP.set_name(T, name) def unpack(T): # Given an oriented trafo {T}, returns two results: # the inversion bit ({+1} or {-1}), and the list of trafo factors {(T1, T2, ... Tn)} # # If {T} is itself a {Trafo} object, returns {+1} and the singleton # list {(T,)}. If {T} is {None}, returns {+1} and the empty tuple # {()}. Also does some type checking. return trafo_IMP.unpack(T) def identity(): # Returns a representation of the identity transformation,, namely {None}. return trafo_IMP.identity() def inv(T): # Returns a representation of the inverse of the trafo {T}. return trafo_IMP.inv(T) def compose(TL): # The argument {TL} must be a list or tuple of zero or more trafos. # Returns a representation of the composition # of those trafos, applied in that order. return trafo_IMP.compose(TL) def is_identity(T): # If the procedure can determine that {T} is the identity -- for example if # {T} is {None} or {[+1]} -- returns {True}. Otherwise (if {T} is NOT the identity, # or the procedure can't determine) it returns {False}. return trafo_IMP.is_identity(T) def simplify(T): # Applies some heuristic rules to reduce {T} to the # simplest possible form. # # If it can determine that {T} is the identity, returns {None}. If it # can reduce {T} to a single {Trafo} object, returns that object. # Otherwise returns a tuple {(+1,T1,T2,...,Tn)} where each {Ti} is either # {Oi} or a pair {(-1,Oi)} where {Oi} is a {Trafo} object. return trafo_IMP.simplify(T) # FORMULA MANIPULATION def flatten(T): # Converts a trafo {T} into an a flat list of trafos {TL} # that is equivalent to {T} if the elements are applied in left to right order. # # A list of trafos {TL} is /flat/ if every element is {Tobj} or # or a pair {(-1,Tobj)} where {Tobj} is a {Trafo} object (not {None}). return trafo_IMP.flatten(T) def reduce(TL): # Given a flat list of trafos {TL} that are to be composed in that order, # returns an equivalent reduced list. # # A flat list of trafos is /reduced/ if it has no # two conscutive elements are inverses of each other. return trafo_IMP.reduce(TL)