1 from . import DifferentiableManifold, np, contract, check
2 from .. import (any_orthogonal_direction, geodesic_distance_on_sphere,
3 random_direction, normalize_length, normalize_length_or_zero,
4 rotation_from_axis_angle, rot2d)
5 from numpy.core.numeric import outer
6
7
8 -class Sphere(DifferentiableManifold):
9 ''' These are hyperspheres of unit radius. '''
10
11 norm_rtol = 1e-5
12 atol_geodesic_distance = 1e-8
13
14 @contract(order='(1|2)')
18
20 return 'S%s' % (self.dimension)
21
22 @contract(a='belongs', b='belongs', returns='>=0')
25
26 @contract(base='belongs', target='belongs', returns='belongs_ts')
27 - def logmap(self, base, target):
42
43 @contract(bv='belongs_ts', returns='belongs')
45 base, vel = bv
46 angle = np.linalg.norm(vel)
47 if angle == 0:
48 return base
49
50 if self.N == 2:
51 direction = -np.sign(vel[0] * base[1] - base[0] * vel[1])
52 R = rot2d(angle * direction)
53 result = np.dot(R, base)
54 elif self.N == 3:
55 axis = -np.cross(normalize_length(vel), base)
56 axis = normalize_length(axis)
57 R = rotation_from_axis_angle(axis, angle)
58 result = np.dot(R, base)
59 else:
60 assert False
61
62 return result
63
64 @contract(bve='tuple(belongs, *)')
66 base, vel = bve
67 P = np.eye(self.N) - outer(base, base)
68 return base, np.dot(P, vel)
69
71 check('array[N],unit_length', x)
72
73 @contract(returns='belongs')
76
77 @contract(returns='list(belongs)')
79 if self.N == 2:
80 points = []
81 points.append(np.array([-1, 0]))
82 points.append(np.array([0, 1]))
83 points.append(np.array([0, -1]))
84 points.append(np.array([+1, 0]))
85 points.append(np.array([np.sqrt(2) / 2, np.sqrt(2) / 2]))
86 return points
87 elif self.N == 3:
88 points = []
89 points.append(np.array([-1, 0, 0]))
90 points.append(np.array([0, 1, 0]))
91 points.append(np.array([0, 0, 1]))
92 points.append(np.array([0, 0, -1]))
93 points.append(np.array([np.sqrt(2) / 2, np.sqrt(2) / 2, 0]))
94 return points
95 else:
96 assert False
97
99 if self.N == 2:
100 theta = np.arctan2(x[1], x[0])
101 return 'Dir(%6.1fdeg)' % np.degrees(theta)
102 elif self.N == 3:
103 theta = np.arctan2(x[1], x[0])
104 elevation = np.arcsin(x[2])
105 return 'Dir(%6.1fdeg,el:%5.1fdeg)' % (np.degrees(theta),
106 np.degrees(elevation))
107 else:
108 assert False
109
110
111 -class Sphere1(DifferentiableManifold):
112
113 norm_rtol = 1e-5
114 atol_geodesic_distance = 1e-8
115
118
121
122 @contract(a='S1', b='S1', returns='>=0')
125
126 @contract(base='S1', target='S1', returns='belongs_ts')
127 - def logmap(self, base, target):
139
140 @contract(bv='belongs_ts', returns='belongs')
142 base, vel = bv
143 angle = np.linalg.norm(vel)
144 if angle == 0:
145 return base
146 direction = -np.sign(vel[0] * base[1] - base[0] * vel[1])
147 R = rot2d(angle * direction)
148 result = np.dot(R, base)
149 return result
150
151 @contract(bx='tuple(belongs, *)')
153 base, x = bx
154 P = np.eye(2) - outer(base, base)
155 return base, np.dot(P, x)
156
157 @contract(x='S1')
160
163
165 points = []
166 points.append(np.array([-1, 0]))
167 points.append(np.array([0, 1]))
168 points.append(np.array([0, -1]))
169 points.append(np.array([+1, 0]))
170 points.append(np.array([np.sqrt(2) / 2, np.sqrt(2) / 2]))
171 return points
172
174 theta = np.arctan2(x[1], x[0])
175 return 'Dir(%6.1fdeg)' % np.degrees(theta)
176