package me.moodcat.database.embeddables; import com.google.common.base.Preconditions; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Point; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import org.hibernate.annotations.Type; import javax.persistence.Embeddable; import java.util.List; import java.util.Random; /** * Valence/Arousal vector class. */ @Data @Embeddable @EqualsAndHashCode @NoArgsConstructor public class VAVector { /** * The zero vector. */ public static final VAVector ZERO; /** * The factory to create points. */ private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); /** * The point that represents this vector. */ @Type(type = "org.hibernate.spatial.GeometryType") private Point location; static { ZERO = new VAVector(0.0, 0.0); } /** * Constructor to create a vector. Asserts that the provided valence and arousal are in the * specified range. * * @param valence * The valence of this vector. * @param arousal * The arousal of this vector. */ public VAVector(final double valence, final double arousal) { this.setValence(valence); this.setArousal(arousal); } /** * Set the valence for the {@code VAVector}. * * @param valence * The valence value. */ public void setValence(final double valence) { if (this.getLocation() == null) { this.setLocation(GEOMETRY_FACTORY.createPoint(new Coordinate(valence, 0))); } else { this.setLocation(GEOMETRY_FACTORY.createPoint(new Coordinate(valence, this.location .getY()))); } } /** * Set the arousal for the {@code VAVector}. * * @param arousal * The arousal value. */ public void setArousal(final double arousal) { if (this.getLocation() == null) { this.setLocation(GEOMETRY_FACTORY.createPoint(new Coordinate(0, arousal))); } else { this.setLocation(GEOMETRY_FACTORY.createPoint(new Coordinate(this.location.getX(), arousal))); } } public double getValence() { return this.location.getX(); } public double getArousal() { return this.location.getY(); } /** * Add this VAVector to another VAVector. * * @param other * Another VAVector * @return a new VAVector */ public VAVector add(final VAVector other) { return new VAVector(this.getValence() + other.getValence(), this.getArousal() + other.getArousal()); } /** * Subtract another vector from this VAVector. * * @param other * Another VAVector * @return a new VAVector */ public VAVector subtract(final VAVector other) { return new VAVector(this.getValence() - other.getValence(), this.getArousal() - other.getArousal()); } /** * Multiply this VAVector with another VAVector. * * @param other * Another VAVector * @return a new VAVector */ public VAVector dotProduct(final VAVector other) { return new VAVector(this.getValence() * other.getValence(), this.getArousal() * other.getArousal()); } /** * Multiply this vector with a scalar. * * @param scalar * The multiplier. * @return VAVector */ public VAVector multiply(final double scalar) { return new VAVector(this.getValence() * scalar, this.getArousal() * scalar); } /** * Get the distance between two vectors. * * @param other * Another VAVector * @return the distance */ public double distance(final VAVector other) { final double a = other.getValence() - this.getValence(); final double b = other.getArousal() - this.getArousal(); return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); } /** * Get the distance between two vectors. * * @param one * first vector * @param other * second vector * @return the distance */ public static double distance(final VAVector one, final VAVector other) { return one.distance(other); } /** * Get the length of this vector. * * @return The length of this vector. */ public double length() { return distance(this, ZERO); } /** * Get the average vector of the list of vectors. * * @param vectors * The list of vectors. * @return The average. */ public static VAVector average(final List<VAVector> vectors) { final Counter counter = new Counter(); final VAVector average = vectors.stream() .reduce(ZERO, (one, other) -> { counter.increment(); return one.add(other); }); return counter.average(average); } /** * Create a {@link VAVector} with random valence and arousal values. * * @return the random vector. */ public static VAVector createRandomVector() { final Random random = new Random(); final double valence = 2 * random.nextDouble() - 1d; final double arousal = 2 * random.nextDouble() - 1d; return new VAVector(valence, arousal); } /** * Helper class in order to reduce more easily in {@link VAVector#average(List)}. */ private static final class Counter { /** * The actual counter. */ private int counter; public Counter() { counter = 0; } public void increment() { counter++; } public VAVector average(final VAVector vector) { if (counter > 0) { return vector.multiply(1.0 / counter); } return ZERO; } } }