1 from .. import assert_allclose, formatm, printm, contract, new_contract
2 from abc import ABCMeta, abstractmethod
3 from collections import namedtuple
7 ''' This is the base class for differentiable manifolds. '''
8 __metaclass__ = ABCMeta
9
11 self._isomorphisms = {}
12 self._embedding = {}
13 self._projection = {}
14 self.dimension = dimension
15
16 self.atol_distance = 1e-8
17
18
19 self._tangent_bundle = None
20
21 @abstractmethod
22 @new_contract
24 '''
25 Raises an Exception if the point does not belong to this manifold.
26
27 This function wraps some checks around :py:func:`belongs_`,
28 which is implemented by the subclasses.
29 '''
30
31 @new_contract
32 @contract(bv='tuple(belongs, *)')
34 '''
35 Checks that a vector *vx* belongs to the tangent space
36 at the given point *base*.
37
38 '''
39 bvp = self.project_ts(bv)
40 assert_allclose(bv[1], bvp[1], atol=self.atol_distance)
41
42 @abstractmethod
43 @contract(bv='tuple(belongs, *)')
45 '''
46 Projects a vector *v_ambient* in the ambient space
47 to the tangent space at point *base*.
48 '''
49
50 @abstractmethod
51 @contract(a='belongs', b='belongs', returns='>=0')
53 '''
54 Computes the geodesic distance between two points.
55 '''
56
57
59 ''' Returns the manifold corresponding to the tangent bundle.
60 The default gives a generic implementation.
61 MatrixLieGroup have a different one.
62 '''
63 if self._tangent_bundle is None:
64 from . import TangentBundle
65 self._tangent_bundle = TangentBundle(self)
66 return self._tangent_bundle
67
68 @abstractmethod
69 @contract(base='belongs', p='belongs', returns='belongs_ts')
71 '''
72 Computes the logarithmic map from base point *base* to target *b*.
73 # XXX: what should we do in the case there is more than one logmap?
74 '''
75
76 @abstractmethod
77 @contract(bv='belongs_ts', returns='belongs')
79 '''
80 Computes the exponential map from *base* for the velocity
81 vector *v*.
82
83 This function wraps some checks around :py:func:`expmap_`,
84 which is implemented by the subclasses.
85
86 '''
87
88 @contract(returns='list(belongs)')
90 '''
91 Returns a list of "interesting points" on this manifold that
92 should be used for testing various properties.
93 '''
94 return []
95
96 @contract(a='belongs', b='belongs', t='>=0,<=1', returns='belongs')
98 ''' Returns the point interpolated along the geodesic. '''
99 bv = self.logmap(a, b)
100 return self.expmap((bv[0], bv[1] * t))
101
102 @contract(a='belongs')
104 '''
105 Returns a friendly description string for a point on the manifold.
106 '''
107 return a.__str__()
108
109 @contract(a='belongs', b='belongs')
111 '''
112 Asserts that two points on the manifold are close to the given
113 tolerance.
114 '''
115 distance = self.distance(a, b)
116 if msg is None:
117 msg = ""
118 if distance > atol:
119 msg += "\nThe two points should be the same:\n"
120 msg += "- a: %s\n" % self.friendly(a)
121 msg += "- b: %s\n" % self.friendly(b)
122 msg += formatm('a', a, 'b', b)
123 assert_allclose(distance, 0, atol=atol, err_msg=msg)
124 return distance
125
126 Isomorphism = namedtuple('Isomorphism',
127 'A B A_to_B B_to_A steps type desc')
128 Embedding = namedtuple('Embedding',
129 'A B A_to_B B_to_A steps type desc')
130
131 @staticmethod
132 - def isomorphism(A, B, A_to_B, B_to_A, itype='user', steps=None, desc=None):
133 if A.dimension != B.dimension:
134 msg = ('You are trying to define an isomorphism'
135 ' between manifolds of different dimension:\n'
136 '- %s has dimension %d;\n'
137 '- %s has dimension %d.\n' % (A, A.dimension,
138 B, B.dimension))
139 raise ValueError(msg)
140
141 Iso = DifferentiableManifold.Isomorphism
142 if steps is None:
143 steps = [(A, '~', B)]
144 A._isomorphisms[B] = Iso(A, B, A_to_B, B_to_A, steps, itype, desc)
145 B._isomorphisms[A] = Iso(B, A, B_to_A, A_to_B, steps, itype, desc)
146
147 @staticmethod
148 - def embedding(A, B, A_to_B, B_to_A, itype='user', steps=None, desc=None):
149 if A.dimension > B.dimension:
150 msg = ('You are trying to define an embedding'
151 ' from a large to a smaller manifold:\n'
152 '- %s has dimension %d;\n'
153 '- %s has dimension %d.\n' % (A, A.dimension,
154 B, B.dimension))
155 raise ValueError(msg)
156
157 Embed = DifferentiableManifold.Embedding
158 if steps is None:
159 steps = [(A, '=', B)]
160 A._embedding[B] = Embed(A, B, A_to_B, B_to_A, steps, itype, desc)
161 B._projection[A] = Embed(B, A, B_to_A, A_to_B, steps, itype, desc)
162
163
164 if False:
165
166 try:
167 for a in A.interesting_points():
168 A.belongs(a)
169 b = A_to_B(a)
170 B.belongs(b)
171 except:
172 print('Invalid embedding:\n %s -> %s using %s' % (A, B,
173 A_to_B))
174 printm('a', a)
175 raise
176
177 try:
178 for b in B.interesting_points():
179 B.belongs(b)
180 a = B_to_A(b)
181 A.belongs(a)
182 except:
183 printm('b', b)
184 print('Invalid embedding:\n %s <- %s using %s' %
185 (A, B, B_to_A))
186 raise
187
189 s = ('[= %s >= %s <= %s]' %
190 (" ".join([str(a) for a in self._isomorphisms]),
191 " ".join([str(a) for a in self._projection]),
192 " ".join([str(a) for a in self._embedding]))
193 )
194 return s
195
196 @contract(my_point='belongs')
198 ''' Embeds a point on this manifold to the target manifold M. '''
199
200 if not self.embeddable_in(M):
201 msg = ('%s is not embeddable in %s; %s' %
202 (self, M, self.relations_descriptions()))
203 raise ValueError(msg)
204 x = self._embedding[M].A_to_B(my_point)
205
206
207 return x
208
209 @contract(returns='belongs')
211 ''' Projects a point on a bigger manifold to this manifold. '''
212 if not self.embeddable_in(M):
213 msg = ('Cannot project from %s to %s; %s' %
214 (self, M, self.relations_descriptions()))
215 raise ValueError(msg)
216 x = self._embedding[M].B_to_A(his_point)
217 return x
218
219 @contract(my_point='belongs')
221 if not self.can_represent(m):
222 msg = ('%s does not contain %s; %s' %
223 (self, m, self.relations_descriptions()))
224 raise ValueError(msg)
225 x = self._projection[m].A_to_B(my_point)
226 return x
227
228 @contract(my_point='belongs')
230 if not self.can_convert_to(m):
231 msg = ('%s cannot be converted to %s; %s' %
232 (self, m, self.relations_descriptions()))
233 raise ValueError(msg)
234 x = self._isomorphisms[m].A_to_B(my_point)
235 return x
236
238 return manifold in self._isomorphisms
239
241 return manifold in self._projection
242
244 return manifold in self._embedding
245
247 ''' Returns the intrinsic dimension of this manifold. '''
248 return self.dimension
249
250 @contract(returns='belongs', yaml_structure='list|dict')
257
258 @contract(x='belongs')
263
266 ''' This is the base class for manifolds that have the ability
267 to sample random points. '''
268
269 @abstractmethod
275
276 @abstractmethod
277 @contract(a='belongs')
279 ''' Samples a random velocity with length 1 at the base point a'''
280