def test_geodesic_and_belongs(self): # TODO(nina): this tests fails when geodesic goes "too far" initial_point = tf.convert_to_tensor([4.0, 1., 3.0, math.sqrt(5)]) n_geodesic_points = 100 vector = tf.convert_to_tensor([1., 0., 0., 0.]) initial_tangent_vec = self.space.projection_to_tangent_space( vector=vector, base_point=initial_point) geodesic = self.metric.geodesic( initial_point=initial_point, initial_tangent_vec=initial_tangent_vec) t = gs.linspace(start=0., stop=1., num=n_geodesic_points) points = geodesic(t) bool_belongs = self.space.belongs(points) expected = tf.convert_to_tensor(n_geodesic_points * [[True]]) with self.test_session(): self.assertAllClose(gs.eval(expected), gs.eval(bool_belongs))
def test_geodesic_and_belongs(self): # TODO(nina): Fix this tests, as it fails when geodesic goes "too far" initial_point = gs.array([4.0, 1., 3.0, math.sqrt(5)]) n_geodesic_points = 100 vector = gs.array([1., 0., 0., 0.]) initial_tangent_vec = self.space.projection_to_tangent_space( vector=vector, base_point=initial_point) geodesic = self.metric.geodesic( initial_point=initial_point, initial_tangent_vec=initial_tangent_vec) t = gs.linspace(start=0., stop=1., num=n_geodesic_points) points = geodesic(t) result = self.space.belongs(points) expected = gs.array(n_geodesic_points * [[True]]) with self.session(): self.assertAllClose(expected, result)
def test_geodesic(self): """Test geodesic. Check that the norm of the velocity is constant. """ initial_point = self.dirichlet.random_point() end_point = self.dirichlet.random_point() n_steps = 10000 geod = self.metric.geodesic(initial_point=initial_point, end_point=end_point) t = gs.linspace(0., 1., n_steps) geod_at_t = geod(t) velocity = n_steps * (geod_at_t[1:, :] - geod_at_t[:-1, :]) velocity_norm = self.metric.norm(velocity, geod_at_t[:-1, :]) result = 1 / velocity_norm.min() * (velocity_norm.max() - velocity_norm.min()) expected = 0. self.assertAllClose(expected, result, rtol=1.)
def test_geodesic(self): """Test geodesic. Check that the norm of the velocity is constant. """ initial_point = self.categorical.random_point() end_point = self.categorical.random_point() n_steps = 100 geod = self.metric.geodesic(initial_point=initial_point, end_point=end_point) t = gs.linspace(0.0, 1.0, n_steps) geod_at_t = geod(t) velocity = n_steps * (geod_at_t[1:, :] - geod_at_t[:-1, :]) velocity_norm = self.metric.norm(velocity, geod_at_t[:-1, :]) result = (1 / gs.amin(velocity_norm) * (gs.amax(velocity_norm) - gs.amin(velocity_norm))) expected = 0.0 self.assertAllClose(expected, result, rtol=1.0)
def path(t): """Generate parameterized function for geodesic curve. Parameters ---------- t : array-like, shape=[n_times,] Times at which to compute points of the geodesics. Returns ------- geodesic : array-like, shape=[..., n_times, dim] Values of the geodesic at times t. """ t = gs.to_ndarray(t, to_ndim=1) n_times = len(t) geod = [] if n_times < n_steps: t_int = gs.linspace(0, 1, n_steps + 1) tangent_vecs = gs.einsum( 'i,...k->...ik', t, initial_tangent_vec) for point, vec in zip(initial_point, tangent_vecs): point = gs.tile(point, (n_times, 1)) exp = [] for pt, vc in zip(point, vec): initial_state = gs.hstack([pt, vc]) solution = odeint( ivp, initial_state, t_int, (), rtol=1e-6) exp.append(solution[-1, :self.dim]) exp = exp[0] if n_times == 1 else gs.stack(exp) geod.append(exp) else: t_int = t for point, vec in zip(initial_point, initial_tangent_vec): initial_state = gs.hstack([point, vec]) solution = odeint( ivp, initial_state, t_int, (), rtol=1e-6) geod.append(solution[:, :self.dim]) return geod[0] if len(initial_point) == 1 else \ gs.stack(geod) # , axis=1)
def test_quotient_dist( self, sampling_times, curve_fun_a, curve_a, n_sampling_points ): """Test quotient distance. Check that the quotient distance is the same as the distance between the end points of the horizontal geodesic. """ curve_a_resampled = curve_fun_a(sampling_times**2) curve_b = gs.transpose( gs.stack( ( gs.zeros(n_sampling_points), gs.zeros(n_sampling_points), gs.linspace(1.0, 0.5, n_sampling_points), ) ) ) quotient_srv_metric_r3 = DiscreteCurves(ambient_manifold=r3).quotient_srv_metric result = quotient_srv_metric_r3.dist(curve_a_resampled, curve_b) expected = quotient_srv_metric_r3.dist(curve_a, curve_b) self.assertAllClose(result, expected, atol=1e-3, rtol=1e-3)
def tests_geodesic_ivp_and_bvp(self): """Test geodesic intial and boundary value problems. Check the shape of the geodesic. """ n_steps = 50 t = gs.linspace(0.0, 1.0, n_steps) initial_points = self.dirichlet.random_point(self.n_points) initial_tangent_vecs = self.dirichlet.random_point(self.n_points) geodesic = self.metric._geodesic_ivp(initial_points, initial_tangent_vecs) geodesic_at_t = geodesic(t) result = geodesic_at_t.shape expected = (self.n_points, n_steps, self.dim) self.assertAllClose(result, expected) end_points = self.dirichlet.random_point(self.n_points) geodesic = self.metric._geodesic_bvp(initial_points, end_points) geodesic_at_t = geodesic(t) result = geodesic_at_t.shape self.assertAllClose(result, expected)
def test_geodesic_bvp_belongs( self, connection_args, space, n_points, initial_point, end_point, belongs_atol, ): """Check that connection geodesics belong to manifold. This is for geodesics defined by the boundary value problem (bvp). Parameters ---------- connection_args : tuple Arguments to pass to constructor of the connection. space : Manifold Manifold where connection is defined. n_points : int Number of points on the geodesics. initial_point : array-like Point on the manifold. end_point : array-like Point on the manifold. belongs_atol : float Absolute tolerance for the belongs function. """ connection = self.connection(*connection_args) geodesic = connection.geodesic(initial_point=initial_point, end_point=end_point) t = gs.linspace(start=0.0, stop=1.0, num=n_points) points = geodesic(t) result = space.belongs(points, belongs_atol) expected = gs.array(n_points * [True]) self.assertAllClose(result, expected)
def log(self, point, base_point, n_steps=N_STEPS, jacobian=False, init="linear"): """Compute the logarithm map. Compute logarithm map associated to the Fisher information metric by solving the boundary value problem associated to the geodesic ordinary differential equation (ODE) using the Christoffel symbols. Parameters ---------- point : array-like, shape=[..., dim] Point. base_point : array-like, shape=[..., dim] Base po int. n_steps : int Number of steps for integration. Optional, default: 100. jacobian : boolean. If True, the explicit value of the jacobian is used to solve the geodesic boundary value problem. Optional, default: False. init : str, {'linear', 'polynomial} Initialization used to solve the geodesic boundary value problem. If 'linear', use the Euclidean straight line as initial guess. If 'polynomial', use a curve with coordinates that are polynomial functions of time. Returns ------- tangent_vec : array-like, shape=[..., dim] Initial velocity of the geodesic starting at base_point and reaching point at time 1. """ t = gs.linspace(0.0, 1.0, n_steps) geodesic = self._geodesic_bvp( initial_point=base_point, end_point=point, jacobian=jacobian, init=init ) geodesic_at_t = geodesic(t) log = n_steps * (geodesic_at_t[..., 1, :] - geodesic_at_t[..., 0, :]) return gs.squeeze(gs.stack(log))
def log(self, point, base_point, n_steps=N_STEPS, jacobian=False, max_time=MAX_TIME): """Compute the logarithm map. Compute logarithm map associated to the Fisher information metric by solving the boundary value problem associated to the geodesic ordinary differential equation (ODE) using the Christoffel symbols. Parameters ---------- point : array-like, shape=[..., dim] Point. base_point : array-like, shape=[..., dim] Base po int. n_steps : int Number of steps for integration. Optional, default: 100. jacobian : boolean. If True, the explicit value of the jacobian is used to solve the geodesic boundary value problem. Optional, default: False. max_time : float. Maximum time in which the boundary value problem should be solved, in seconds. If it takes longer, the process is terminated. Optional, default: 300 seconds i.e. 5 minutes. Returns ------- tangent_vec : array-like, shape=[..., dim] Initial velocity of the geodesic starting at base_point and reaching point at time 1. """ t = gs.linspace(0., 1., n_steps) geodesic = self._geodesic_bvp( initial_point=base_point, end_point=point, jacobian=jacobian, max_time=max_time) geodesic_at_t = geodesic(t) log = n_steps * (geodesic_at_t[..., 1, :] - geodesic_at_t[..., 0, :]) return gs.squeeze(gs.stack(log))
def path(t): """Generate parameterized function for geodesic curve. Parameters ---------- t : array-like, shape=[n_times,] Times at which to compute points of the geodesics. Returns ------- geodesic : array-like, shape=[..., n_times, dim] Values of the geodesic at times t. """ t = gs.to_ndarray(t, to_ndim=1) geod = [] def initialize(point_0, point_1): """Initialize the solution of the boundary value problem.""" lin_init = gs.zeros([2 * self.dim, n_steps]) lin_init[:self.dim, :] = gs.transpose( gs.linspace(point_0, point_1, n_steps)) lin_init[self.dim:, :-1] = n_steps * ( lin_init[:self.dim, 1:] - lin_init[:self.dim, :-1]) lin_init[self.dim:, -1] = lin_init[self.dim:, -2] return lin_init t_int = gs.linspace(0., 1., n_steps) for ip, ep in zip(initial_point, end_point): geodesic_init = initialize(ip, ep) def bc(y0, y1, ip=ip, ep=ep): return boundary_cond(y0, y1, ip, ep) solution = solve_bvp(bvp, bc, t_int, geodesic_init) solution_at_t = solution.sol(t) geodesic = solution_at_t[:self.dim, :] geod.append(gs.squeeze(gs.transpose(geodesic))) return geod[0] if len(initial_point) == 1 else gs.stack(geod)
def exp(self, tangent_vec, base_point, n_steps=N_STEPS): """Exponential map associated to the Fisher information metric. Exponential map at base_point of tangent_vec computed by integration of the geodesic equation (initial value problem), using the christoffel symbols. Parameters ---------- tangent_vec : array-like, shape=[..., dim] Tangent vector at base point. base_point : array-like, shape=[..., dim] Base point. n_steps : int Number of steps for integration. Optional, default: 100. Returns ------- exp : array-like, shape=[..., dim] Riemannian exponential. """ base_point = gs.to_ndarray(base_point, to_ndim=2) tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=2) def ivp(state, _): """Reformat the initial value problem geodesic ODE.""" position, velocity = state[:2], state[2:] eq = self.geodesic_equation(velocity=velocity, position=position) return gs.hstack([velocity, eq]) times = gs.linspace(0, 1, n_steps + 1) exp = [] for point, vec in zip(base_point, tangent_vec): initial_state = gs.hstack([point, vec]) geodesic = odeint( ivp, initial_state, times, (), rtol=1e-6) exp.append(geodesic[-1, :2]) return exp[0] if len(base_point) == 1 else gs.stack(exp)
def cost_fun(param): """Compute the energy of the polynomial curve defined by param. Parameters ---------- param : array-like, shape=(degree - 1, dim) Parameters of the curve coordinates' polynomial functions of time. Returns ------- energy : float Energy of the polynomial approximation of the geodesic. length : float Length of the polynomial approximation of the geodesic. curve : array-like, shape=(n_times, dim) Polynomial approximation of the geodesic. velocity : array-like, shape=(n_times, dim) Velocity of the polynomial approximation of the geodesic. """ last_coef = end_point - initial_point - gs.sum(param, axis=0) coef = gs.vstack((initial_point, param, last_coef)) t = gs.linspace(0.0, 1.0, n_times) t_curve = [t**i for i in range(degree + 1)] t_curve = gs.stack(t_curve) curve = gs.einsum("ij,ik->kj", coef, t_curve) t_velocity = [i * t**(i - 1) for i in range(1, degree + 1)] t_velocity = gs.stack(t_velocity) velocity = gs.einsum("ij,ik->kj", coef[1:], t_velocity) if curve.min() < 0: return np.inf, np.inf, curve, np.nan velocity_sqnorm = self.squared_norm(vector=velocity, base_point=curve) length = gs.sum(velocity_sqnorm**(1 / 2)) / n_times energy = gs.sum(velocity_sqnorm) / n_times return energy, length, curve, velocity
def main(): """Plot a square on H2 with Poincare Disk visualization.""" top = SQUARE_SIZE / 2.0 bot = -SQUARE_SIZE / 2.0 left = -SQUARE_SIZE / 2.0 right = SQUARE_SIZE / 2.0 corners_int = gs.array([[bot, left], [bot, right], [top, right], [top, left]]) corners_ext = H2.from_coordinates(corners_int, "intrinsic") n_steps = 20 ax = plt.gca() for i, src in enumerate(corners_ext): dst_id = (i + 1) % len(corners_ext) dst = corners_ext[dst_id] tangent_vec = METRIC.log(point=dst, base_point=src) geodesic = METRIC.geodesic(initial_point=src, initial_tangent_vec=tangent_vec) t = gs.linspace(0.0, 1.0, n_steps) edge_points = geodesic(t) visualization.plot( edge_points, ax=ax, space="H2_poincare_disk", marker=".", color="black" ) plt.show()
def test_geodesic_and_coincides_exp(self, space, n_geodesic_points, vector): initial_point = space.random_uniform(2) initial_tangent_vec = space.to_tangent(vector=vector, base_point=initial_point) geodesic = space.metric.geodesic( initial_point=initial_point, initial_tangent_vec=initial_tangent_vec) t = gs.linspace(start=0.0, stop=1.0, num=n_geodesic_points) points = geodesic(t) result = points[:, -1] expected = space.metric.exp(initial_tangent_vec, initial_point) self.assertAllClose(result, expected) initial_point = initial_point[0] initial_tangent_vec = initial_tangent_vec[0] geodesic = space.metric.geodesic( initial_point=initial_point, initial_tangent_vec=initial_tangent_vec) points = geodesic(t) result = points[-1] expected = space.metric.exp(initial_tangent_vec, initial_point) self.assertAllClose(expected, result)
def compute_coordinates(point): """ Compute the ellipsis coordinates from 2D SPD matrix. :param point: array-like, shape = [2, 2]: SPD matrix. :return: X: array-like, shape = [n_steps,]; Y: array-like, shape = [n_steps,]; X and Y coordinates. """ w, vr = gs.linalg.eigh(point) w = w + EPS n_steps = 100 [e1, e2] = w x0, y0 = 0, 0 n = vr.shape[0] angle = SpecialOrthogonal(n).angle_of_rot2(vr) c, s = gs.cos(angle), gs.sin(angle) the = gs.linspace(0, 2 * gs.pi, n_steps) X = e1 * gs.cos(the) * c - s * e2 * gs.sin(the) + x0 y = e1 * gs.cos(the) * s + c * e2 * gs.sin(the) + y0 return X, y, X[n_steps // 4], y[n_steps // 4]
def test_geodesic_and_belongs(self): n_geodesic_points = 10 initial_point = self.space.random_uniform(2) vector = gs.array([[2.0, 0.0, -1.0, -2.0, 1.0]] * 2) initial_tangent_vec = self.space.to_tangent(vector=vector, base_point=initial_point) geodesic = self.metric.geodesic( initial_point=initial_point, initial_tangent_vec=initial_tangent_vec) t = gs.linspace(start=0.0, stop=1.0, num=n_geodesic_points) points = geodesic(t) result = gs.stack([self.space.belongs(pt) for pt in points]) self.assertTrue(gs.all(result)) initial_point = initial_point[0] initial_tangent_vec = initial_tangent_vec[0] geodesic = self.metric.geodesic( initial_point=initial_point, initial_tangent_vec=initial_tangent_vec) points = geodesic(t) result = self.space.belongs(points) expected = gs.array(n_geodesic_points * [True]) self.assertAllClose(expected, result)
def plot_gaussian_mixture_distribution( data, mixture_coefficients, means, variances, plot_precision=DEFAULT_PLOT_PRECISION, save_path="", metric=None, ): """Plot Gaussian Mixture Model.""" x_axis_samples = gs.linspace(-1, 1, plot_precision) y_axis_samples = gs.linspace(-1, 1, plot_precision) x_axis_samples, y_axis_samples = gs.meshgrid(x_axis_samples, y_axis_samples) z_axis_samples = gs.zeros((plot_precision, plot_precision)) for z_index, _ in enumerate(z_axis_samples): x_y_plane_mesh = gs.concatenate( ( gs.expand_dims(x_axis_samples[z_index], -1), gs.expand_dims(y_axis_samples[z_index], -1), ), axis=-1, ) mesh_probabilities = weighted_gmm_pdf( mixture_coefficients, x_y_plane_mesh, means, variances, metric ) z_axis_samples[z_index] = mesh_probabilities.sum(-1) fig = plt.figure( "Learned Gaussian Mixture Model " "via Expectation Maximization on Poincaré Disc" ) ax = fig.gca(projection="3d") ax.plot_surface( x_axis_samples, y_axis_samples, z_axis_samples, rstride=1, cstride=1, linewidth=1, antialiased=True, cmap=plt.get_cmap("viridis"), ) z_circle = -0.8 p = Circle((0, 0), 1, edgecolor="b", lw=1, facecolor="none") ax.add_patch(p) art3d.pathpatch_2d_to_3d(p, z=z_circle, zdir="z") for data_index, _ in enumerate(data): ax.scatter( data[data_index][0], data[data_index][1], z_circle, c="b", marker="." ) for means_index, _ in enumerate(means): ax.scatter( means[means_index][0], means[means_index][1], z_circle, c="r", marker="D" ) ax.set_xlim(-1.2, 1.2) ax.set_ylim(-1.2, 1.2) ax.set_zlim(-0.8, 0.4) ax.set_xlabel("X") ax.set_ylabel("Y") ax.set_zlabel("P") plt.savefig(save_path, format="pdf") return plt
def log(self, point, base_point, n_steps=N_STEPS): """Compute logarithm map associated to the Fisher information metric. Solve the boundary value problem associated to the geodesic ordinary differential equation (ODE) using the Christoffel symbols. Parameters ---------- point : array-like, shape=[n_samples, dim] base_point : array-like, shape=[n_samples, dim] n_steps : int Returns ------- tangent_vec : array-like, shape=[n_samples, dim] the initial velocity of the geodesic starting at base_point and reaching point at time 1 """ stop_time = 1. t = gs.linspace(0, stop_time, n_steps) point = gs.to_ndarray(point, to_ndim=2) base_point = gs.to_ndarray(base_point, to_ndim=2) def initialize(end_point, start_point): a0, b0 = start_point a1, b1 = end_point lin_init = gs.zeros([2 * self.dim, n_steps]) lin_init[0, :] = gs.linspace(a0, a1, n_steps) lin_init[1, :] = gs.linspace(b0, b1, n_steps) lin_init[2, :-1] = (lin_init[0, 1:] - lin_init[0, :-1]) * n_steps lin_init[3, :-1] = (lin_init[1, 1:] - lin_init[1, :-1]) * n_steps lin_init[2, -1] = lin_init[2, -2] lin_init[3, -1] = lin_init[3, -2] return lin_init def bvp(time, state): """Reformat the boundary value problem geodesic ODE. Parameters ---------- state : vector of the state variables: y = [a,b,u,v] time : time """ position, velocity = state[:2].T, state[2:].T eq = self.geodesic_equation( velocity=velocity, position=position) return gs.vstack((velocity.T, eq.T)) def boundary_cond( state_a, state_b, point_0_a, point_0_b, point_1_a, point_1_b): return gs.array( [state_a[0] - point_0_a, state_a[1] - point_0_b, state_b[0] - point_1_a, state_b[1] - point_1_b]) log = [] for bp, pt in zip(base_point, point): geodesic_init = initialize(pt, bp) base_point_a, base_point_b = bp point_a, point_b = pt def bc(y0, y1): return boundary_cond( y0, y1, base_point_a, base_point_b, point_a, point_b) solution = solve_bvp(bvp, bc, t, geodesic_init) geodesic = solution.sol(t) geodesic = geodesic[:2, :] log.append(n_steps * (geodesic[:, 1] - geodesic[:, 0])) return log[0] if len(base_point) == 1 else gs.stack(log)
ax = fig.add_subplot(111, projection='3d') plot_sphere = Sphere() n_points = 10000 f_points = plot_sphere.fibonnaci_points(n_points).swapaxes(0, 1) plot_sphere.plot_heatmap(ax=ax, n_points=n_points, scalar_function=loss_f) correct_points = s_points[labels == 0][:30, :] correct_labels = np.ones_like(correct_points) ax = visualization.plot(correct_points, ax=ax, space='S2', color='red', s=80) f_labels = np.array(f_labels)[:, 0] f_points = f_points[f_labels != 0] metric = HypersphereMetric(dim=2) for k in range(len(correct_points)): point_matrix = correct_points[k:k+1, :].repeat(len(f_points), axis=0) dist_array = metric.dist(point_matrix, f_points) idx_min = np.argmin(dist_array) geodesic = sphere.metric.geodesic( initial_point=correct_points[k], end_point=f_points[idx_min]) points_on_geodesic = geodesic(gs.linspace(0., 1., 10)) plot_sphere.add_points(points_on_geodesic) plot_sphere.draw_points(ax=ax, color='black', alpha=0.1) plt.show()
def log(self, point, base_point, n_steps=N_STEPS): """Compute the logarithm map. Compute logarithm map associated to the Fisher information metric by solving the boundary value problem associated to the geodesic ordinary differential equation (ODE) using the Christoffel symbols. Parameters ---------- point : array-like, shape=[..., dim] Point. base_point : array-like, shape=[..., dim] Base point. n_steps : int Number of steps for integration. Optional, default: 100. Returns ------- tangent_vec : array-like, shape=[..., dim] Initial velocity of the geodesic starting at base_point and reaching point at time 1. """ stop_time = 1. t = gs.linspace(0, stop_time, n_steps) point = gs.to_ndarray(point, to_ndim=2) base_point = gs.to_ndarray(base_point, to_ndim=2) n_points = point.shape[0] n_base_points = base_point.shape[0] if n_base_points > n_points: if n_points > 1: raise ValueError('For several base points, specify either ' 'one or the same number of points.') point = gs.tile(point, (n_base_points, 1)) elif n_points > n_base_points: if n_base_points > 1: raise ValueError('For several points, specify either ' 'one or the same number of base points.') base_point = gs.tile(base_point, (n_points, 1)) def initialize(end_point, start_point): lin_init = gs.zeros([2 * self.dim, n_steps]) lin_init[:self.dim, :] = gs.transpose( gs.linspace(start_point, end_point, n_steps)) lin_init[self.dim:, :-1] = (lin_init[:self.dim, 1:] - lin_init[:self.dim, :-1]) * n_steps lin_init[self.dim:, -1] = lin_init[self.dim:, -2] return lin_init def bvp(_, state): """Reformat the boundary value problem geodesic ODE. Parameters ---------- state : array-like, shape[4,] Vector of the state variables: y = [a,b,u,v] _ : unused Any (time). """ position, velocity = state[:self.dim].T, state[self.dim:].T eq = self.geodesic_equation(velocity=velocity, position=position) return gs.transpose(gs.hstack(eq)) def boundary_cond(state_0, state_1, point_0, point_1): return gs.hstack( (state_0[:self.dim] - point_0, state_1[:self.dim] - point_1)) log = [] for bp, pt in zip(base_point, point): geodesic_init = initialize(pt, bp) def bc(y0, y1, bp=bp, pt=pt): return boundary_cond(y0, y1, bp, pt) solution = solve_bvp(bvp, bc, t, geodesic_init) geodesic = solution.sol(t) geodesic = geodesic[:self.dim, :] log.append(n_steps * (geodesic[:, 1] - geodesic[:, 0])) return log[0] if len(base_point) == 1 else gs.stack(log)
r3 = s2.embedding_space initial_point = [0.0, 0.0, 1.0] initial_tangent_vec_a = [1.0, 0.0, 0.0] initial_tangent_vec_b = [0.0, 1.0, 0.0] initial_tangent_vec_c = [-1.0, 0.0, 0.0] curve_fun_a = s2.metric.geodesic(initial_point=initial_point, initial_tangent_vec=initial_tangent_vec_a) curve_fun_b = s2.metric.geodesic(initial_point=initial_point, initial_tangent_vec=initial_tangent_vec_b) curve_fun_c = s2.metric.geodesic(initial_point=initial_point, initial_tangent_vec=initial_tangent_vec_c) n_sampling_points = 10 sampling_times = gs.linspace(0.0, 1.0, n_sampling_points) curve_a = curve_fun_a(sampling_times) curve_b = curve_fun_b(sampling_times) curve_c = curve_fun_c(sampling_times) n_discretized_curves = 5 times = gs.linspace(0.0, 1.0, n_discretized_curves) class DiscreteCurvesTestData(_ManifoldTestData): space_args_list = [(r2, ), (r3, )] shape_list = [(10, 2), (10, 3)] n_samples_list = random.sample(range(2, 5), 2) n_points_list = random.sample(range(2, 5), 2) n_vecs_list = random.sample(range(2, 5), 2)
def multi_plot_modulation_factor(dim, n_expectation=1000, n_theta=20): """Plot modulation factor curves for large number of samples. Plot several curves of modulation factor on the convergence of the empirical Fréchet mean as a function of the radius of the bubble distribution and for 10 to 100 sample points on the hyperbolic space H_dim embedded in R^{1,dim}. Parameters ---------- dim: dimension of the hyperbolic space (embedded in R^{1,dim}) n_expectation: number of computations for approximating the expectation n_theta: number of sampled radii for the bubble distribution Returns ------- matplolib figure """ theta = gs.linspace(0.000001, 5, n_theta) small_var_modulation_factor = [] asymptotic_modulation_actor = [] print("Convergence rate modulation factor, " "hyperbolic space, dim={0}, n > 5".format(dim)) plt.figure() for theta_i in theta: small_var_modulation_factor.append(1.0 - 2.0 / 3.0 * theta_i**2 * (1.0 - 1.0 / dim) * 1.0) asymptotic_modulation_actor.append(asymptotic_modulation(dim, theta_i)) plt.plot(theta, small_var_modulation_factor, 'g', label='Small variance prediction') plt.plot(theta, asymptotic_modulation_actor, 'grey', label='Asymptotic prediction') color = {10: 'red', 20: 'orange', 50: 'olive', 100: 'blue'} for n_samples in [10, 20, 50, 100]: measured_modulation_factor = [] for theta_i in theta: (var, std_var) = modulation_factor(n_samples, theta_i, dim, n_expectation=n_expectation) measured_modulation_factor.append(var) print('{} {} {} {}\n'.format(n_samples, theta_i, var, std_var)) plt.plot(theta, measured_modulation_factor, color=color[n_samples], label="n={0}".format(n_samples)) plt.xlabel(r'Standard deviation $\theta$') plt.ylabel(r'Modulation factor $\alpha$') plt.legend(loc='best') plt.title("Convergence rate modulation factor, " "hyperbolic space, dim={0}, n > 5".format(dim)) plt.ylim([0, 1.3]) plt.draw() plt.pause(0.01) plt.savefig("Figures/HypVarModulation_n10p_d{0}_m{1}.png".format( dim, n_expectation)) plt.savefig("Figures/HypVarModulation_n10p_d{0}_m{1}.pdf".format( dim, n_expectation)) return plt
def plot_modulation_factor(n_samples, dim, n_expectation=1000, n_theta=20): """Plot the modulation factor curve w.r.t. the dispersion. Plot the curve of modulation factor on the convergence of the empirical Fréchet mean as a function of the radius of the bubble distribution and for n_samples points on the hyperbolic space H_dim embedded in R^{1,dim}. Parameters ---------- n_samples: number of samples to draw dim: dimension of the hyperbolic space (embedded in R^{1,dim}) n_expectation: number of computations for approximating the expectation n_theta: number of sampled radii for the bubble distribution Returns ------- matplolib figure """ theta = gs.linspace(0.000001, 5, n_theta) measured_modulation_factor = [] error = [] small_var_modulation_factor = [] asymptotic_modulation_factor = [] print("Convergence rate modulation factor, " "hyperbolic space dim={1}, n={0}".format(n_samples, dim)) for theta_i in theta: (var, std_var) = modulation_factor(n_samples, theta_i, dim, n_expectation=n_expectation) measured_modulation_factor.append(var) error.append(std_var) print('{} {} {} {}\n'.format(n_samples, theta_i, var, std_var)) small_var_modulation_factor.append(1.0 - 2.0 / 3.0 * theta_i**2 * (1.0 - 1.0 / dim) * (1.0 - 1.0 / n_samples)) asymptotic_modulation_factor.append(asymptotic_modulation( dim, theta_i)) plt.figure() plt.errorbar(theta, measured_modulation_factor, yerr=error, color='r', label='Measured') plt.plot(theta, small_var_modulation_factor, 'g', label='Small variance prediction') plt.plot(theta, asymptotic_modulation_factor, 'grey', label='Asymptotic prediction') plt.xlabel(r'Standard deviation $\theta$') plt.ylabel(r'Modulation factor $\alpha$') plt.title("Convergence rate modulation factor, " "hyperbolic space dim={1}, n={0}".format(n_samples, dim)) plt.legend(loc='best') plt.ylim([0, 1.3]) plt.draw() plt.pause(0.01) plt.savefig("Figures/HypVarModulation_n{0}_d{1}_m{2}.png".format( n_samples, dim, n_expectation)) plt.savefig("Figures/HypVarModulation_n{0}_d{1}_m{2}.pdf".format( n_samples, dim, n_expectation)) return plt
c1 = resample(c1, n) c2 = resample(c2, n) fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.plot(c1[:, 0], c1[:, 1], c1[:, 2]) ax.plot(c2[:, 0], c2[:, 1], c2[:, 2]) ax.scatter(c1[0, 0], c1[0, 1], c1[0, 2]) ax.scatter(c2[0, 0], c2[0, 1], c2[0, 2]) plt.show() srv_geod_fun = curves3D.square_root_velocity_metric.geodesic(initial_curve=c1, end_curve=c2) n_times = 50 t = gs.linspace(0., 1., n_times + 1) srv_geod = srv_geod_fun(t) fig = plt.figure(figsize=(12, 10)) ax = fig.add_subplot(111, projection='3d') for i in range(n_times): ax.plot(srv_geod[i, :, 0], srv_geod[i, :, 1], srv_geod[i, :, 2]) plt.title('SRV geodesic') plt.show() horizontal_geod = horizontal_geodesic(c1, c2, 50) fig = plt.figure(figsize=(10, 10)) ax = fig.add_subplot(111, projection='3d') for i in range(n_times + 1): ax.plot(horizontal_geod[i, :, 0], horizontal_geod[i, :, 1],
def main(): r"""Compute and visualize a geodesic regression on the sphere. The generative model of the data is: :math:`Z = Exp_{\beta_0}(\beta_1.X)` and :math:`Y = Exp_Z(\epsilon)` where: - :math:`Exp` denotes the Riemannian exponential, - :math:`\beta_0` is called the intercept, - :math:`\beta_1` is called the coefficient, - :math:`\epsilon \sim N(0, 1)` is a standard Gaussian noise, - :math:`X` is the input, :math:`Y` is the target. """ # Generate noise-free data n_samples = 50 X = gs.random.rand(n_samples) X -= gs.mean(X) intercept = SPACE.random_uniform() coef = SPACE.to_tangent(5.0 * gs.random.rand(EMBEDDING_DIM), base_point=intercept) y = METRIC.exp(X[:, None] * coef, base_point=intercept) # Generate normal noise normal_noise = gs.random.normal(size=(n_samples, EMBEDDING_DIM)) noise = SPACE.to_tangent(normal_noise, base_point=y) / gs.pi / 2 rss = gs.sum(METRIC.squared_norm(noise, base_point=y)) / n_samples # Add noise y = METRIC.exp(noise, y) # True noise level and R2 estimator = FrechetMean(METRIC) estimator.fit(y) variance_ = variance(y, estimator.estimate_, metric=METRIC) r2 = 1 - rss / variance_ # Fit geodesic regression gr = GeodesicRegression(SPACE, center_X=False, method="extrinsic", verbose=True) gr.fit(X, y, compute_training_score=True) intercept_hat, coef_hat = gr.intercept_, gr.coef_ # Measure Mean Squared Error mse_intercept = METRIC.squared_dist(intercept_hat, intercept) tangent_vec_to_transport = coef_hat tangent_vec_of_transport = METRIC.log(intercept, base_point=intercept_hat) transported_coef_hat = METRIC.parallel_transport( tangent_vec=tangent_vec_to_transport, base_point=intercept_hat, direction=tangent_vec_of_transport, ) mse_coef = METRIC.squared_norm(transported_coef_hat - coef, base_point=intercept) # Measure goodness of fit r2_hat = gr.training_score_ print(f"MSE on the intercept: {mse_intercept:.2e}") print(f"MSE on the coef, i.e. initial velocity: {mse_coef:.2e}") print(f"Determination coefficient: R^2={r2_hat:.2f}") print(f"True R^2: {r2:.2f}") # Plot fitted_data = gr.predict(X) fig = plt.figure(figsize=(8, 8)) ax = fig.add_subplot(111, projection="3d") sphere_visu = visualization.Sphere(n_meridians=30) ax = sphere_visu.set_ax(ax=ax) path = METRIC.geodesic(initial_point=intercept_hat, initial_tangent_vec=coef_hat) regressed_geodesic = path( gs.linspace(0.0, 1.0, 100) * gs.pi * 2 / METRIC.norm(coef)) regressed_geodesic = gs.to_numpy(gs.autodiff.detach(regressed_geodesic)) size = 10 marker = "o" sphere_visu.draw_points(ax, gs.array([intercept_hat]), marker=marker, c="r", s=size) sphere_visu.draw_points(ax, y, marker=marker, c="b", s=size) sphere_visu.draw_points(ax, fitted_data, marker=marker, c="g", s=size) ax.plot( regressed_geodesic[:, 0], regressed_geodesic[:, 1], regressed_geodesic[:, 2], c="gray", ) sphere_visu.draw(ax, linewidth=1) ax.grid(False) plt.axis("off") plt.show()
class TestDataL2Metric(_RiemannianMetricTestData): dim_list = random.sample(range(2, 4), 2) n_landmarks_list = random.sample(range(2, 5), 2) metric_args_list = [ (Hypersphere(dim), n_landmarks) for dim, n_landmarks in zip(dim_list, n_landmarks_list) ] + [(Euclidean(dim + 1), n_landmarks) for dim, n_landmarks in zip(dim_list, n_landmarks_list)] space_list = [Landmarks(*metric_arg) for metric_arg in metric_args_list] shape_list = [(n_landmark, dim + 1) for dim, n_landmark in zip(dim_list, n_landmarks_list)] * 2 n_points_list = random.sample(range(2, 5), 2) n_tangent_vecs_list = random.sample(range(2, 5), 2) n_points_a_list = random.sample(range(2, 5), 2) n_points_b_list = [1] alpha_list = [1] * 4 n_rungs_list = [1] * 4 scheme_list = ["pole"] * 4 s2 = Hypersphere(dim=2) r3 = s2.embedding_space initial_point = [0.0, 0.0, 1.0] initial_tangent_vec_a = [1.0, 0.0, 0.0] initial_tangent_vec_b = [0.0, 1.0, 0.0] initial_tangent_vec_c = [-1.0, 0.0, 0.0] landmarks_a = s2.metric.geodesic(initial_point=initial_point, initial_tangent_vec=initial_tangent_vec_a) landmarks_b = s2.metric.geodesic(initial_point=initial_point, initial_tangent_vec=initial_tangent_vec_b) landmarks_c = s2.metric.geodesic(initial_point=initial_point, initial_tangent_vec=initial_tangent_vec_c) n_sampling_points = 10 sampling_times = gs.linspace(0.0, 1.0, n_sampling_points) landmark_set_a = landmarks_a(sampling_times) landmark_set_b = landmarks_b(sampling_times) landmark_set_c = landmarks_c(sampling_times) n_landmark_sets = 5 times = gs.linspace(0.0, 1.0, n_landmark_sets) space_landmarks_in_sphere_2d = Landmarks(ambient_manifold=s2, k_landmarks=n_sampling_points) l2_metric_s2 = space_landmarks_in_sphere_2d.metric def exp_shape_test_data(self): return self._exp_shape_test_data(self.metric_args_list, self.space_list, self.shape_list) def log_shape_test_data(self): return self._log_shape_test_data(self.metric_args_list, self.space_list) def squared_dist_is_symmetric_test_data(self): return self._squared_dist_is_symmetric_test_data( self.metric_args_list, self.space_list, self.n_points_a_list, self.n_points_b_list, atol=gs.atol * 1000, ) def exp_belongs_test_data(self): return self._exp_belongs_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_tangent_vecs_list, belongs_atol=gs.atol * 10000, ) def log_is_tangent_test_data(self): return self._log_is_tangent_test_data( self.metric_args_list, self.space_list, self.n_points_list, is_tangent_atol=gs.atol * 1000, ) def geodesic_ivp_belongs_test_data(self): return self._geodesic_ivp_belongs_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_points_list, belongs_atol=gs.atol * 1000, ) def geodesic_bvp_belongs_test_data(self): return self._geodesic_bvp_belongs_test_data( self.metric_args_list, self.space_list, self.n_points_list, belongs_atol=gs.atol * 100, ) def exp_after_log_test_data(self): return self._exp_after_log_test_data( self.metric_args_list, self.space_list, self.n_tangent_vecs_list, rtol=gs.rtol * 1000, atol=gs.atol * 1000, ) def log_after_exp_test_data(self): return self._log_after_exp_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_points_list, amplitude=30, rtol=gs.rtol * 10000, atol=gs.atol * 100000, ) def exp_ladder_parallel_transport_test_data(self): return self._exp_ladder_parallel_transport_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_tangent_vecs_list, self.n_rungs_list, self.alpha_list, self.scheme_list, ) def exp_geodesic_ivp_test_data(self): return self._exp_geodesic_ivp_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_tangent_vecs_list, self.n_points_list, rtol=gs.rtol * 10000, atol=gs.atol * 10000, ) def parallel_transport_ivp_is_isometry_test_data(self): return self._parallel_transport_ivp_is_isometry_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_tangent_vecs_list, is_tangent_atol=gs.atol * 1000, atol=gs.atol * 100, ) def parallel_transport_bvp_is_isometry_test_data(self): return self._parallel_transport_bvp_is_isometry_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_tangent_vecs_list, is_tangent_atol=gs.atol * 100, atol=gs.atol * 100, ) def dist_is_symmetric_test_data(self): return self._dist_is_symmetric_test_data( self.metric_args_list, self.space_list, self.n_points_a_list, self.n_points_b_list, ) def dist_is_positive_test_data(self): return self._dist_is_positive_test_data( self.metric_args_list, self.space_list, self.n_points_a_list, self.n_points_b_list, ) def squared_dist_is_positive_test_data(self): return self._squared_dist_is_positive_test_data( self.metric_args_list, self.space_list, self.n_points_a_list, self.n_points_b_list, ) def dist_is_norm_of_log_test_data(self): return self._dist_is_norm_of_log_test_data( self.metric_args_list, self.space_list, self.n_points_a_list, self.n_points_b_list, ) def dist_point_to_itself_is_zero_test_data(self): return self._dist_point_to_itself_is_zero_test_data( self.metric_args_list, self.space_list, self.n_points_list) def inner_product_is_symmetric_test_data(self): return self._inner_product_is_symmetric_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_tangent_vecs_list, ) def triangle_inequality_of_dist_test_data(self): return self._triangle_inequality_of_dist_test_data( self.metric_args_list, self.space_list, self.n_points_list) def l2_metric_inner_product_vectorization_test_data(self): smoke_data = [ dict( l2_metric=self.l2_metric_s2, times=self.times, landmark_sets=self.n_landmark_sets, landmarks_a=self.landmark_set_a, landmarks_b=self.landmark_set_b, landmarks_c=self.landmark_set_c, ) ] return self.generate_tests(smoke_data) def l2_metric_exp_vectorization_test_data(self): smoke_data = [ dict( l2_metric=self.l2_metric_s2, times=self.times, landmarks_a=self.landmark_set_a, landmarks_b=self.landmark_set_b, landmarks_c=self.landmark_set_c, ) ] return self.generate_tests(smoke_data) def l2_metric_log_vectorization_test_data(self): smoke_data = [ dict( l2_metric=self.l2_metric_s2, times=self.times, landmarks_a=self.landmark_set_a, landmarks_b=self.landmark_set_b, landmarks_c=self.landmark_set_c, ) ] return self.generate_tests(smoke_data) def l2_metric_geodesic_test_data(self): smoke_data = [ dict( l2_metric=self.l2_metric_s2, times=self.times, n_sampling_points=self.n_sampling_points, landmarks_a=self.landmark_set_a, landmarks_b=self.landmark_set_b, ) ] return self.generate_tests(smoke_data)
def main(): r"""Compute and visualize a geodesic regression on the SE(2). The generative model of the data is: :math:`Z = Exp_{\beta_0}(\beta_1.X)` and :math:`Y = Exp_Z(\epsilon)` where: - :math:`Exp` denotes the Riemannian exponential, - :math:`\beta_0` is called the intercept, - :math:`\beta_1` is called the coefficient, - :math:`\epsilon \sim N(0, 1)` is a standard Gaussian noise, - :math:`X` is the input, :math:`Y` is the target. """ # Generate noise-free data n_samples = 20 X = gs.random.normal(size=(n_samples, )) X -= gs.mean(X) intercept = SPACE.random_point() coef = SPACE.to_tangent(5.0 * gs.random.rand(3, 3), intercept) y = METRIC.exp(X[:, None, None] * coef[None], intercept) # Generate normal noise in the Lie algebra normal_noise = gs.random.normal(size=(n_samples, 3)) normal_noise = SPACE.lie_algebra.matrix_representation(normal_noise) noise = SPACE.tangent_translation_map(y)(normal_noise) / gs.pi rss = gs.sum(METRIC.squared_norm(noise, y)) / n_samples # Add noise y = METRIC.exp(noise, y) # True noise level and R2 estimator = FrechetMean(METRIC) estimator.fit(y) variance_ = variance(y, estimator.estimate_, metric=METRIC) r2 = 1 - rss / variance_ # Fit geodesic regression gr = GeodesicRegression( SPACE, metric=METRIC, center_X=False, method="riemannian", max_iter=100, init_step_size=0.1, verbose=True, initialization="frechet", ) gr.fit(X, y, compute_training_score=True) intercept_hat, beta_hat = gr.intercept_, gr.coef_ # Measure Mean Squared Error mse_intercept = METRIC.squared_dist(intercept_hat, intercept) mse_beta = METRIC.squared_norm( METRIC.parallel_transport(beta_hat, intercept_hat, METRIC.log(intercept_hat, intercept)) - coef, intercept, ) # Measure goodness of fit r2_hat = gr.training_score_ print(f"MSE on the intercept: {mse_intercept:.2e}") print(f"MSE on the initial velocity beta: {mse_beta:.2e}") print(f"Determination coefficient: R^2={r2_hat:.2f}") print(f"True R^2: {r2:.2f}") # Plot fitted_data = gr.predict(X) fig = plt.figure(figsize=(8, 8)) ax = fig.add_subplot(111) sphere_visu = visualization.SpecialEuclidean2() ax = sphere_visu.set_ax(ax=ax) path = METRIC.geodesic(initial_point=intercept_hat, initial_tangent_vec=beta_hat) regressed_geodesic = path(gs.linspace(min(X), max(X), 100)) sphere_visu.draw_points(ax, y, marker="o", c="black") sphere_visu.draw_points(ax, fitted_data, marker="o", c="gray") sphere_visu.draw_points(ax, gs.array([intercept]), marker="x", c="r") sphere_visu.draw_points(ax, gs.array([intercept_hat]), marker="o", c="green") ax.plot(regressed_geodesic[:, 0, 2], regressed_geodesic[:, 1, 2], c="gray") plt.show()
def test_ball_geodesic(self): path_function =\ self.manifold.metric.geodesic(gs.array([0.1, 0.1]), gs.array([0.2, 0.2])) steps = gs.array(gs.linspace(-1000., 1000., 10000)) path_function(steps)
def plot_modulation_factor(n_samples, dim, n_expectation=1000, n_theta=20): """Plot the modulation factor curve w.r.t. the dispersion. Plot the curve of modulation factor on the convergence of the empirical Fréchet mean as a function of the radius of the bubble distribution and for n_samples points on the sphere S_dim embedded in R^{dim+1}. Parameters ---------- n_samples : int Number of samples to draw dim : int Dimension of the sphere (embedded in R^{dim+1}). n_expectation: int, optional (defaults to 1000) Number of computations for approximating the expectation. n_theta: int, optional (defaults to 20) Number of sampled radii for the bubble distribution. Returns ------- matplolib figure """ theta = gs.linspace(0.000001, gs.pi / 2.0 - 0.000001, n_theta) measured_modulation_factor = [] error = [] small_var_modulation_factor = [] asymptotic_modulation_factor = [] for theta_i in theta: (var, std_var) = modulation_factor(n_samples, theta_i, dim, n_expectation=n_expectation) measured_modulation_factor.append(var) error.append(std_var) logging.info('{} {} {} {}\n'.format(n_samples, theta_i, var, std_var)) small_var_modulation_factor.append(1.0 + 2.0 / 3.0 * theta_i**2 * (1.0 - 1.0 / dim) * (1.0 - 1.0 / n_samples)) asymptotic_modulation_factor.append(asymptotic_modulation( dim, theta_i)) plt.figure() plt.errorbar(theta, measured_modulation_factor, yerr=error, color='r', label='Measured') plt.plot(theta, small_var_modulation_factor, 'g', label='Small variance prediction') plt.plot(theta, asymptotic_modulation_factor, 'grey', label='Asymptotic prediction') plt.xlabel(r'Standard deviation $\theta$') plt.ylabel(r'Modulation factor $\alpha$') plt.title('Convergence rate modulation factor, ' 'sphere dim={1}, n={0}'.format(n_samples, dim)) plt.legend(loc='best') plt.draw() plt.pause(0.01) return plt