/*
* HyperSphereDistribution.java
*
* Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
*
* This file is part of BEAST.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* BEAST is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
package dr.geo.distributions;
import dr.math.distributions.MultivariateDistribution;
import dr.geo.math.SphericalPolarCoordinates;
import dr.geo.math.Space;
/**
* @author Marc Suchard
*
* Abstract base class for probability distributions on a hyper-sphere S^{d-1} in R^{d}.
* Usual cases are: circle (d=2) and sphere (d=3)
*/
public abstract class HyperSphereDistribution implements MultivariateDistribution {
protected static double LOG_2_PI = Math.log(2 * Math.PI);
public HyperSphereDistribution(int dim, Space space, double[] mean, double kappa) {
this.dim = dim;
this.mean = mean;
this.kappa = kappa;
this.space = space;
checkImplementation();
}
public abstract double logPdf(double[] x);
public double[][] getScaleMatrix() {
return new double[0][];
}
public double[] getMean() {
return new double[0];
}
public String getType() {
return "Hyperspherical";
}
protected int getAllowableDim() {
return dim;
}
protected void checkImplementation() {
final int allowableDim = getAllowableDim();
if (allowableDim != dim) {
throw new RuntimeException(getType() + " distribution is not implemented in R^"+dim);
}
}
public static double innerProduct(double[] x, double[] y, Space space) {
switch (space) {
case LAT_LONG : return latLongToCartesianInnerProduct(x, y, 1.0);
case CARTESIAN : return cartestianInnerProduct(x, y);
case RADIANS : return radiansInnerProduct(x, y);
default : throw new RuntimeException("Inner product not yet implemented");
}
}
private static double radiansInnerProduct(double[] x, double[] y) {
// x[] and y[] should be in the form (radians)
if (x.length != 1 && y.length != 1) {
throw new RuntimeException("Wrong dimensions");
}
return Math.cos(x[0] - y[0]);
}
private static double cartestianInnerProduct(double[] x, double[] y) {
// x[] and y[] must be unit vectors
if (!isUnitVector(x) || !isUnitVector(y)) {
throw new RuntimeException("Inner produce must be on unit vectors");
}
final int len = x.length;
double innerProduct = 0;
for(int i = 0; i < len; i++) {
innerProduct += x[i] * y[i];
}
return innerProduct;
}
private static double tolerance = 1E-10;
public static boolean isUnitVector(double[] test) {
double norm = 0;
for(double d : test) {
norm += d * d;
}
return (Math.abs(norm - 1) < tolerance);
}
private static double latLongToCartesianInnerProduct(double[] x, double[] y, double radius) {
// x[] and y[] should be in the form (lat, long)
if (x.length != 2 || y.length != 2) {
throw new RuntimeException("Wrong dimensions");
}
final SphericalPolarCoordinates coordX = new SphericalPolarCoordinates(x[0], x[1], radius);
final SphericalPolarCoordinates coordY = new SphericalPolarCoordinates(y[0], y[1], radius);
return coordX.getCartesianCoordinates().dot(coordY.getCartesianCoordinates());
}
protected int dim;
protected Space space;
protected double[] mean;
protected double kappa;
}