#pragma once #include #include "p-spin.hpp" template Vector stereographicToEuclidean(const Vector& ζ) { unsigned N = ζ.size() + 1; Vector z(N); Scalar a = ζ.transpose() * ζ; Scalar b = 2 * sqrt(N) / (1.0 + a); for (unsigned i = 0; i < N - 1; i++) { z(i) = ζ(i); } z(N - 1) = (a - 1.0) / 2.0; return b * z; } template Vector euclideanToStereographic(const Vector& z) { unsigned N = z.size(); Vector ζ(N - 1); for (unsigned i = 0; i < N - 1; i++) { ζ(i) = z(i); } return ζ / (sqrt(N) - z(N - 1)); } template Matrix stereographicJacobian(const Vector& ζ) { unsigned N = ζ.size(); Matrix J(N, N + 1); Scalar b = 1.0 + (Scalar)(ζ.transpose() * ζ); for (unsigned i = 0; i < N; i++) { for (unsigned j = 0; j < N; j++) { J(i, j) = - ζ(i) * ζ(j); if (i == j) { J(i, j) += 0.5 * b; } } J(i, N) = ζ(i); } return 4 * sqrt(N + 1) * J / pow(b, 2); } template std::tuple, Matrix> stereographicHamGradHess(const Tensor& J, const Vector& ζ, const Vector& z) { auto [hamiltonian, gradZ, hessZ] = hamGradHess(J, z); Matrix jacobian = stereographicJacobian(ζ); Matrix metric = jacobian * jacobian.adjoint(); // The metric is Hermitian and positive definite, so a Cholesky decomposition can be used. Vector grad = metric.llt().solve(jacobian) * gradZ; Matrix hess = jacobian * hessZ * jacobian.transpose(); return {hamiltonian, grad, hess}; }