diff --git a/.idea/dictionaries/kimchy.xml b/.idea/dictionaries/kimchy.xml
index 0ab91bc9b73..a000b6f177e 100644
--- a/.idea/dictionaries/kimchy.xml
+++ b/.idea/dictionaries/kimchy.xml
@@ -71,6 +71,7 @@
multi
multicast
multiline
+ mvel
nanos
newcount
ngram
@@ -109,6 +110,7 @@
traslog
trie
tuple
+ unboxed
unicast
unregister
uptime
diff --git a/.idea/modules/elasticsearch.iml b/.idea/modules/elasticsearch.iml
index f4420294886..28e6c3eecd5 100644
--- a/.idea/modules/elasticsearch.iml
+++ b/.idea/modules/elasticsearch.iml
@@ -40,6 +40,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index b478f6f055f..b05fed69071 100644
--- a/build.gradle
+++ b/build.gradle
@@ -37,6 +37,7 @@ allprojects {
repositories {
mavenCentral()
mavenRepo urls: 'https://repository.jboss.org/nexus/content/groups/public'
+ mavenRepo urls: 'http://repository.codehaus.org/'
mavenRepo urls: 'http://elasticsearch.googlecode.com/svn/maven'
}
}
@@ -82,7 +83,8 @@ task explodedDist(dependsOn: [configurations.distLib], description: 'Builds a mi
ant.delete { fileset(dir: explodedDistLibDir, includes: "joda-*.jar") } // no need joda, we jarjar it
ant.delete { fileset(dir: explodedDistLibDir, includes: "snakeyaml-*.jar") } // no need snakeyaml, we jarjar it
ant.delete { fileset(dir: explodedDistLibDir, includes: "sigar-*.jar") } // no need sigar directly under lib...
- ant.delete { fileset(dir: explodedDistLibDir, includes: "netty-*.jar") } // no need sigar directly under lib...
+ ant.delete { fileset(dir: explodedDistLibDir, includes: "netty-*.jar") } // no need netty directly under lib...
+ ant.delete { fileset(dir: explodedDistLibDir, includes: "mvel2-*.jar") } // no need mvel2 directly under lib...
ant.chmod(dir: "$explodedDistDir/bin", perm: "ugo+rx", includes: "**/*")
}
diff --git a/modules/elasticsearch/build.gradle b/modules/elasticsearch/build.gradle
index 73579e63be8..2a45480af47 100644
--- a/modules/elasticsearch/build.gradle
+++ b/modules/elasticsearch/build.gradle
@@ -31,6 +31,8 @@ dependencies {
compile 'joda-time:joda-time:1.6'
+ compile 'org.mvel:mvel2:2.0.17'
+
compile 'org.codehaus.jackson:jackson-core-asl:1.5.2'
compile 'org.yaml:snakeyaml:1.6'
@@ -70,12 +72,13 @@ jar << {
jarjar(jarfile: jarjarArchivePath) {
zipfileset(src: jar.archivePath)
configurations.compile.files.findAll {file ->
- ['jackson', 'joda', 'snakeyaml', 'netty'].any { file.name.contains(it) }
+ ['mvel', 'jackson', 'joda', 'snakeyaml', 'netty'].any { file.name.contains(it) }
}.each { jarjarFile ->
zipfileset(src: jarjarFile) {
exclude(name: "META-INF/**")
}
}
+ rule pattern: "org.mvel2.**", result: "org.elasticsearch.util.mvel2.@1"
rule pattern: "org.codehaus.jackson.**", result: "org.elasticsearch.util.jackson.@1"
rule pattern: "org.joda.**", result: "org.elasticsearch.util.joda.@1"
rule pattern: "org.yaml.**", result: "org.elasticsearch.util.yaml.@1"
@@ -88,6 +91,7 @@ jar << {
// seems like empty dirst still exists, unjar and clean them
unjar(src: jar.archivePath, dest: "build/tmp/extracted")
delete(dir: "build/tmp/extracted/org/codehaus")
+ delete(dir: "build/tmp/extracted/org/mvel2")
delete(dir: "build/tmp/extracted/org/joda")
delete(dir: "build/tmp/extracted/org/yaml")
delete(dir: "build/tmp/extracted/org/jboss")
@@ -162,6 +166,7 @@ uploadArchives {
pom.dependencies = pom.dependencies.findAll {dep -> !dep.artifactId.contains('joda') }
pom.dependencies = pom.dependencies.findAll {dep -> !dep.artifactId.contains('snakeyaml') }
pom.dependencies = pom.dependencies.findAll {dep -> !dep.artifactId.contains('netty') }
+ pom.dependencies = pom.dependencies.findAll {dep -> !dep.artifactId.contains('mvel') }
}
}
}
\ No newline at end of file
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/cache/NodeCache.java b/modules/elasticsearch/src/main/java/org/elasticsearch/cache/NodeCache.java
new file mode 100644
index 00000000000..c391487632f
--- /dev/null
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/cache/NodeCache.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to Elastic Search and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Elastic Search licenses this
+ * file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.cache;
+
+import org.elasticsearch.util.component.AbstractComponent;
+import org.elasticsearch.util.inject.Inject;
+import org.elasticsearch.util.settings.Settings;
+
+/**
+ * @author kimchy (shay.banon)
+ */
+public class NodeCache extends AbstractComponent {
+
+ @Inject public NodeCache(Settings settings) {
+ super(settings);
+ }
+}
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/lucene/search/function/FunctionProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/cache/NodeCacheModule.java
similarity index 78%
rename from modules/elasticsearch/src/main/java/org/elasticsearch/util/lucene/search/function/FunctionProvider.java
rename to modules/elasticsearch/src/main/java/org/elasticsearch/cache/NodeCacheModule.java
index c00eb31913f..1c14ec9e3c4 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/util/lucene/search/function/FunctionProvider.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/cache/NodeCacheModule.java
@@ -17,14 +17,16 @@
* under the License.
*/
-package org.elasticsearch.util.lucene.search.function;
+package org.elasticsearch.cache;
-import org.apache.lucene.index.IndexReader;
+import org.elasticsearch.util.inject.AbstractModule;
/**
* @author kimchy (shay.banon)
*/
-public interface FunctionProvider {
+public class NodeCacheModule extends AbstractModule {
- Function function(IndexReader reader);
+ @Override protected void configure() {
+ bind(NodeCache.class).asEagerSingleton();
+ }
}
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/DocFieldData.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/DocFieldData.java
index ab537ea4bf0..eb93e7ddc7f 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/DocFieldData.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/DocFieldData.java
@@ -48,6 +48,10 @@ public abstract class DocFieldData {
return fieldData.stringValue(docId);
}
+ public String getStringValue() {
+ return stringValue();
+ }
+
public FieldData.Type getType() {
return fieldData.type();
}
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/LongDocFieldData.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/LongDocFieldData.java
index aaccba3792f..97864cbb6ea 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/LongDocFieldData.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/LongDocFieldData.java
@@ -20,6 +20,7 @@
package org.elasticsearch.index.field.data.longs;
import org.elasticsearch.index.field.data.NumericDocFieldData;
+import org.joda.time.MutableDateTime;
/**
* @author kimchy (shay.banon)
@@ -37,4 +38,12 @@ public class LongDocFieldData extends NumericDocFieldData {
public long[] getValues() {
return fieldData.values(docId);
}
+
+ public MutableDateTime getDate() {
+ return fieldData.date(docId);
+ }
+
+ public MutableDateTime[] getDates() {
+ return fieldData.dates(docId);
+ }
}
\ No newline at end of file
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/LongFieldData.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/LongFieldData.java
index 6d4a7772746..f9a6c28e892 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/LongFieldData.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/LongFieldData.java
@@ -24,7 +24,9 @@ import org.apache.lucene.search.FieldCache;
import org.elasticsearch.index.field.data.FieldDataOptions;
import org.elasticsearch.index.field.data.NumericFieldData;
import org.elasticsearch.index.field.data.support.FieldDataLoader;
+import org.elasticsearch.util.ThreadLocals;
import org.elasticsearch.util.gnu.trove.TLongArrayList;
+import org.joda.time.MutableDateTime;
import java.io.IOException;
@@ -34,7 +36,13 @@ import java.io.IOException;
public abstract class LongFieldData extends NumericFieldData {
static final long[] EMPTY_LONG_ARRAY = new long[0];
+ static final MutableDateTime[] EMPTY_DATETIME_ARRAY = new MutableDateTime[0];
+ private ThreadLocal> dateTimeCache = new ThreadLocal>() {
+ @Override protected ThreadLocals.CleanableValue initialValue() {
+ return new ThreadLocals.CleanableValue(new MutableDateTime());
+ }
+ };
protected final long[] values;
protected final int[] freqs;
@@ -49,6 +57,14 @@ public abstract class LongFieldData extends NumericFieldData {
abstract public long[] values(int docId);
+ public MutableDateTime date(int docId) {
+ MutableDateTime dateTime = dateTimeCache.get().get();
+ dateTime.setMillis(value(docId));
+ return dateTime;
+ }
+
+ public abstract MutableDateTime[] dates(int docId);
+
@Override public LongDocFieldData docFieldData(int docId) {
return super.docFieldData(docId);
}
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/MultiValueLongFieldData.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/MultiValueLongFieldData.java
index 1257b5b9480..99ceed4f217 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/MultiValueLongFieldData.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/MultiValueLongFieldData.java
@@ -22,6 +22,7 @@ package org.elasticsearch.index.field.data.longs;
import org.elasticsearch.index.field.data.FieldDataOptions;
import org.elasticsearch.index.field.data.doubles.DoubleFieldData;
import org.elasticsearch.util.ThreadLocals;
+import org.joda.time.MutableDateTime;
/**
* @author kimchy (shay.banon)
@@ -40,6 +41,20 @@ public class MultiValueLongFieldData extends LongFieldData {
}
};
+ private ThreadLocal> dateTimesCache = new ThreadLocal>() {
+ @Override protected ThreadLocals.CleanableValue initialValue() {
+ MutableDateTime[][] value = new MutableDateTime[VALUE_CACHE_SIZE][];
+ for (int i = 0; i < value.length; i++) {
+ value[i] = new MutableDateTime[i];
+ for (int j = 0; j < i; j++) {
+ value[i][j] = new MutableDateTime();
+ }
+ }
+ return new ThreadLocals.CleanableValue(value);
+ }
+ };
+
+
private ThreadLocal> valuesCache = new ThreadLocal>() {
@Override protected ThreadLocals.CleanableValue initialValue() {
long[][] value = new long[VALUE_CACHE_SIZE][];
@@ -86,6 +101,26 @@ public class MultiValueLongFieldData extends LongFieldData {
}
}
+ @Override public MutableDateTime[] dates(int docId) {
+ int[] docOrders = order[docId];
+ if (docOrders == null) {
+ return EMPTY_DATETIME_ARRAY;
+ }
+ MutableDateTime[] dates;
+ if (docOrders.length < VALUE_CACHE_SIZE) {
+ dates = dateTimesCache.get().get()[docOrders.length];
+ } else {
+ dates = new MutableDateTime[docOrders.length];
+ for (int i = 0; i < dates.length; i++) {
+ dates[i] = new MutableDateTime();
+ }
+ }
+ for (int i = 0; i < docOrders.length; i++) {
+ dates[i].setMillis(values[docOrders[i]]);
+ }
+ return dates;
+ }
+
@Override public double[] doubleValues(int docId) {
int[] docOrders = order[docId];
if (docOrders == null) {
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/SingleValueLongFieldData.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/SingleValueLongFieldData.java
index 7b6fc225381..a862184fcb5 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/SingleValueLongFieldData.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/data/longs/SingleValueLongFieldData.java
@@ -21,6 +21,7 @@ package org.elasticsearch.index.field.data.longs;
import org.elasticsearch.index.field.data.FieldDataOptions;
import org.elasticsearch.index.field.data.doubles.DoubleFieldData;
+import org.joda.time.MutableDateTime;
/**
* @author kimchy (shay.banon)
@@ -33,6 +34,14 @@ public class SingleValueLongFieldData extends LongFieldData {
}
};
+ private ThreadLocal datesValuesCache = new ThreadLocal() {
+ @Override protected MutableDateTime[] initialValue() {
+ MutableDateTime[] date = new MutableDateTime[1];
+ date[0] = new MutableDateTime();
+ return date;
+ }
+ };
+
private ThreadLocal valuesCache = new ThreadLocal() {
@Override protected long[] initialValue() {
return new long[1];
@@ -71,6 +80,16 @@ public class SingleValueLongFieldData extends LongFieldData {
proc.onValue(docId, values[loc]);
}
+ @Override public MutableDateTime[] dates(int docId) {
+ int loc = order[docId];
+ if (loc == 0) {
+ return EMPTY_DATETIME_ARRAY;
+ }
+ MutableDateTime[] ret = datesValuesCache.get();
+ ret[0].setMillis(values[loc]);
+ return ret;
+ }
+
@Override public double[] doubleValues(int docId) {
int loc = order[docId];
if (loc == 0) {
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/function/FieldsFunction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/function/FieldsFunction.java
new file mode 100644
index 00000000000..1c04f2840f4
--- /dev/null
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/function/FieldsFunction.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to Elastic Search and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Elastic Search licenses this
+ * file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.index.field.function;
+
+import org.apache.lucene.index.IndexReader;
+
+import java.util.Map;
+
+/**
+ * @author kimchy (shay.banon)
+ */
+public interface FieldsFunction {
+
+ void setNextReader(IndexReader reader);
+
+ /**
+ * @param docId
+ * @param vars The vars providing additional parameters, should be reused and has values added to it in execute
+ * @return
+ */
+ Object execute(int docId, Map vars);
+}
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/function/script/ScriptFieldsFunction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/function/script/ScriptFieldsFunction.java
new file mode 100644
index 00000000000..be4a4f94568
--- /dev/null
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/field/function/script/ScriptFieldsFunction.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to Elastic Search and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Elastic Search licenses this
+ * file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.index.field.function.script;
+
+import org.apache.lucene.index.IndexReader;
+import org.elasticsearch.ElasticSearchException;
+import org.elasticsearch.ElasticSearchIllegalArgumentException;
+import org.elasticsearch.index.cache.field.data.FieldDataCache;
+import org.elasticsearch.index.field.data.FieldData;
+import org.elasticsearch.index.field.data.FieldDataOptions;
+import org.elasticsearch.index.field.function.FieldsFunction;
+import org.elasticsearch.index.mapper.FieldMapper;
+import org.elasticsearch.index.mapper.MapperService;
+import org.elasticsearch.script.ScriptService;
+import org.elasticsearch.util.ThreadLocals;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author kimchy (shay.banon)
+ */
+public class ScriptFieldsFunction implements FieldsFunction, Map {
+
+ private static ThreadLocal>> cachedFieldData = new ThreadLocal>>() {
+ @Override protected ThreadLocals.CleanableValue
+ *
+ * @param n the size of the set
+ * @param k the size of the subsets to be counted
+ * @return n choose k
+ * @throws IllegalArgumentException if preconditions are not met.
+ * @throws ArithmeticException if the result is too large to be represented
+ * by a long integer.
+ */
+ public static long binomialCoefficient(final int n, final int k) {
+ checkBinomial(n, k);
+ if ((n == k) || (k == 0)) {
+ return 1;
+ }
+ if ((k == 1) || (k == n - 1)) {
+ return n;
+ }
+ // Use symmetry for large k
+ if (k > n / 2)
+ return binomialCoefficient(n, n - k);
+
+ // We use the formula
+ // (n choose k) = n! / (n-k)! / k!
+ // (n choose k) == ((n-k+1)*...*n) / (1*...*k)
+ // which could be written
+ // (n choose k) == (n-1 choose k-1) * n / k
+ long result = 1;
+ if (n <= 61) {
+ // For n <= 61, the naive implementation cannot overflow.
+ int i = n - k + 1;
+ for (int j = 1; j <= k; j++) {
+ result = result * i / j;
+ i++;
+ }
+ } else if (n <= 66) {
+ // For n > 61 but n <= 66, the result cannot overflow,
+ // but we must take care not to overflow intermediate values.
+ int i = n - k + 1;
+ for (int j = 1; j <= k; j++) {
+ // We know that (result * i) is divisible by j,
+ // but (result * i) may overflow, so we split j:
+ // Filter out the gcd, d, so j/d and i/d are integer.
+ // result is divisible by (j/d) because (j/d)
+ // is relative prime to (i/d) and is a divisor of
+ // result * (i/d).
+ final long d = gcd(i, j);
+ result = (result / (j / d)) * (i / d);
+ i++;
+ }
+ } else {
+ // For n > 66, a result overflow might occur, so we check
+ // the multiplication, taking care to not overflow
+ // unnecessary.
+ int i = n - k + 1;
+ for (int j = 1; j <= k; j++) {
+ final long d = gcd(i, j);
+ result = mulAndCheck(result / (j / d), i / d);
+ i++;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a double
representation of the Binomial
+ * Coefficient, "n choose k
", the number of
+ * k
-element subsets that can be selected from an
+ * n
-element set.
+ *
+ * Preconditions:
+ *
+ * -
0 <= k <= n
(otherwise
+ * IllegalArgumentException
is thrown)
+ * - The result is small enough to fit into a
double
. The
+ * largest value of n
for which all coefficients are <
+ * Double.MAX_VALUE is 1029. If the computed value exceeds Double.MAX_VALUE,
+ * Double.POSITIVE_INFINITY is returned
+ *
+ *
+ * @param n the size of the set
+ * @param k the size of the subsets to be counted
+ * @return n choose k
+ * @throws IllegalArgumentException if preconditions are not met.
+ */
+ public static double binomialCoefficientDouble(final int n, final int k) {
+ checkBinomial(n, k);
+ if ((n == k) || (k == 0)) {
+ return 1d;
+ }
+ if ((k == 1) || (k == n - 1)) {
+ return n;
+ }
+ if (k > n / 2) {
+ return binomialCoefficientDouble(n, n - k);
+ }
+ if (n < 67) {
+ return binomialCoefficient(n, k);
+ }
+
+ double result = 1d;
+ for (int i = 1; i <= k; i++) {
+ result *= (double) (n - k + i) / (double) i;
+ }
+
+ return Math.floor(result + 0.5);
+ }
+
+ /**
+ * Returns the natural log
of the Binomial
+ * Coefficient, "n choose k
", the number of
+ * k
-element subsets that can be selected from an
+ * n
-element set.
+ *
+ * Preconditions:
+ *
+ * -
0 <= k <= n
(otherwise
+ * IllegalArgumentException
is thrown)
+ *
+ *
+ * @param n the size of the set
+ * @param k the size of the subsets to be counted
+ * @return n choose k
+ * @throws IllegalArgumentException if preconditions are not met.
+ */
+ public static double binomialCoefficientLog(final int n, final int k) {
+ checkBinomial(n, k);
+ if ((n == k) || (k == 0)) {
+ return 0;
+ }
+ if ((k == 1) || (k == n - 1)) {
+ return Math.log(n);
+ }
+
+ /*
+ * For values small enough to do exact integer computation,
+ * return the log of the exact value
+ */
+ if (n < 67) {
+ return Math.log(binomialCoefficient(n, k));
+ }
+
+ /*
+ * Return the log of binomialCoefficientDouble for values that will not
+ * overflow binomialCoefficientDouble
+ */
+ if (n < 1030) {
+ return Math.log(binomialCoefficientDouble(n, k));
+ }
+
+ if (k > n / 2) {
+ return binomialCoefficientLog(n, n - k);
+ }
+
+ /*
+ * Sum logs for values that could overflow
+ */
+ double logSum = 0;
+
+ // n!/(n-k)!
+ for (int i = n - k + 1; i <= n; i++) {
+ logSum += Math.log(i);
+ }
+
+ // divide by k!
+ for (int i = 2; i <= k; i++) {
+ logSum -= Math.log(i);
+ }
+
+ return logSum;
+ }
+
+ /**
+ * Check binomial preconditions.
+ *
+ * @param n the size of the set
+ * @param k the size of the subsets to be counted
+ * @throws IllegalArgumentException if preconditions are not met.
+ */
+ private static void checkBinomial(final int n, final int k)
+ throws IllegalArgumentException {
+ if (n < k) {
+ throw MathRuntimeException.createIllegalArgumentException(
+ "must have n >= k for binomial coefficient (n,k), got n = {0}, k = {1}",
+ n, k);
+ }
+ if (n < 0) {
+ throw MathRuntimeException.createIllegalArgumentException(
+ "must have n >= 0 for binomial coefficient (n,k), got n = {0}",
+ n);
+ }
+ }
+
+ /**
+ * Compares two numbers given some amount of allowed error.
+ *
+ * @param x the first number
+ * @param y the second number
+ * @param eps the amount of error to allow when checking for equality
+ * @return - 0 if {@link #equals(double, double, double) equals(x, y, eps)}
+ * - < 0 if !{@link #equals(double, double, double) equals(x, y, eps)} && x < y
+ * - > 0 if !{@link #equals(double, double, double) equals(x, y, eps)} && x > y
+ */
+ public static int compareTo(double x, double y, double eps) {
+ if (equals(x, y, eps)) {
+ return 0;
+ } else if (x < y) {
+ return -1;
+ }
+ return 1;
+ }
+
+ /**
+ * Returns the
+ * hyperbolic cosine of x.
+ *
+ * @param x double value for which to find the hyperbolic cosine
+ * @return hyperbolic cosine of x
+ */
+ public static double cosh(double x) {
+ return (Math.exp(x) + Math.exp(-x)) / 2.0;
+ }
+
+ /**
+ * Returns true iff both arguments are NaN or neither is NaN and they are
+ * equal
+ *
+ * @param x first value
+ * @param y second value
+ * @return true if the values are equal or both are NaN
+ */
+ public static boolean equals(double x, double y) {
+ return (Double.isNaN(x) && Double.isNaN(y)) || x == y;
+ }
+
+ /**
+ * Returns true iff both arguments are equal or within the range of allowed
+ * error (inclusive).
+ *
+ * Two NaNs are considered equals, as are two infinities with same sign.
+ *
+ *
+ * @param x first value
+ * @param y second value
+ * @param eps the amount of absolute error to allow
+ * @return true if the values are equal or within range of each other
+ */
+ public static boolean equals(double x, double y, double eps) {
+ return equals(x, y) || (Math.abs(y - x) <= eps);
+ }
+
+ /**
+ * Returns true iff both arguments are equal or within the range of allowed
+ * error (inclusive).
+ * Adapted from
+ * Bruce Dawson
+ *
+ * @param x first value
+ * @param y second value
+ * @param maxUlps {@code (maxUlps - 1)} is the number of floating point
+ * values between {@code x} and {@code y}.
+ * @return {@code true} if there are less than {@code maxUlps} floating
+ * point values between {@code x} and {@code y}
+ */
+ public static boolean equals(double x, double y, int maxUlps) {
+ // Check that "maxUlps" is non-negative and small enough so that the
+ // default NAN won't compare as equal to anything.
+ assert maxUlps > 0 && maxUlps < NAN_GAP;
+
+ long xInt = Double.doubleToLongBits(x);
+ long yInt = Double.doubleToLongBits(y);
+
+ // Make lexicographically ordered as a two's-complement integer.
+ if (xInt < 0) {
+ xInt = SGN_MASK - xInt;
+ }
+ if (yInt < 0) {
+ yInt = SGN_MASK - yInt;
+ }
+
+ return Math.abs(xInt - yInt) <= maxUlps;
+ }
+
+ /**
+ * Returns true iff both arguments are null or have same dimensions
+ * and all their elements are {@link #equals(double,double) equals}
+ *
+ * @param x first array
+ * @param y second array
+ * @return true if the values are both null or have same dimension
+ * and equal elements
+ * @since 1.2
+ */
+ public static boolean equals(double[] x, double[] y) {
+ if ((x == null) || (y == null)) {
+ return !((x == null) ^ (y == null));
+ }
+ if (x.length != y.length) {
+ return false;
+ }
+ for (int i = 0; i < x.length; ++i) {
+ if (!equals(x[i], y[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns n!. Shorthand for n
Factorial, the
+ * product of the numbers 1,...,n
.
+ *
+ * Preconditions:
+ *
+ * -
n >= 0
(otherwise
+ * IllegalArgumentException
is thrown)
+ * - The result is small enough to fit into a
long
. The
+ * largest value of n
for which n!
<
+ * Long.MAX_VALUE is 20. If the computed value exceeds Long.MAX_VALUE
+ * an ArithMeticException
is thrown.
+ *
+ *
+ *
+ * @param n argument
+ * @return n!
+ * @throws ArithmeticException if the result is too large to be represented
+ * by a long integer.
+ * @throws IllegalArgumentException if n < 0
+ */
+ public static long factorial(final int n) {
+ if (n < 0) {
+ throw MathRuntimeException.createIllegalArgumentException(
+ "must have n >= 0 for n!, got n = {0}",
+ n);
+ }
+ if (n > 20) {
+ throw new ArithmeticException(
+ "factorial value is too large to fit in a long");
+ }
+ return FACTORIALS[n];
+ }
+
+ /**
+ * Returns n!. Shorthand for n
Factorial, the
+ * product of the numbers 1,...,n
as a double
.
+ *
+ * Preconditions:
+ *
+ * -
n >= 0
(otherwise
+ * IllegalArgumentException
is thrown)
+ * - The result is small enough to fit into a
double
. The
+ * largest value of n
for which n!
<
+ * Double.MAX_VALUE is 170. If the computed value exceeds
+ * Double.MAX_VALUE, Double.POSITIVE_INFINITY is returned
+ *
+ *
+ *
+ * @param n argument
+ * @return n!
+ * @throws IllegalArgumentException if n < 0
+ */
+ public static double factorialDouble(final int n) {
+ if (n < 0) {
+ throw MathRuntimeException.createIllegalArgumentException(
+ "must have n >= 0 for n!, got n = {0}",
+ n);
+ }
+ if (n < 21) {
+ return factorial(n);
+ }
+ return Math.floor(Math.exp(factorialLog(n)) + 0.5);
+ }
+
+ /**
+ * Returns the natural logarithm of n!.
+ *
+ * Preconditions:
+ *
+ * -
n >= 0
(otherwise
+ * IllegalArgumentException
is thrown)
+ *
+ *
+ * @param n argument
+ * @return n!
+ * @throws IllegalArgumentException if preconditions are not met.
+ */
+ public static double factorialLog(final int n) {
+ if (n < 0) {
+ throw MathRuntimeException.createIllegalArgumentException(
+ "must have n >= 0 for n!, got n = {0}",
+ n);
+ }
+ if (n < 21) {
+ return Math.log(factorial(n));
+ }
+ double logSum = 0;
+ for (int i = 2; i <= n; i++) {
+ logSum += Math.log(i);
+ }
+ return logSum;
+ }
+
+ /**
+ *
+ * Gets the greatest common divisor of the absolute value of two numbers,
+ * using the "binary gcd" method which avoids division and modulo
+ * operations. See Knuth 4.5.2 algorithm B. This algorithm is due to Josef
+ * Stein (1961).
+ *
+ * Special cases:
+ *
+ * - The invocations
+ *
gcd(Integer.MIN_VALUE, Integer.MIN_VALUE)
,
+ * gcd(Integer.MIN_VALUE, 0)
and
+ * gcd(0, Integer.MIN_VALUE)
throw an
+ * ArithmeticException
, because the result would be 2^31, which
+ * is too large for an int value.
+ * - The result of
gcd(x, x)
, gcd(0, x)
and
+ * gcd(x, 0)
is the absolute value of x
, except
+ * for the special cases above.
+ * - The invocation
gcd(0, 0)
is the only one which returns
+ * 0
.
+ *
+ *
+ * @param p any number
+ * @param q any number
+ * @return the greatest common divisor, never negative
+ * @throws ArithmeticException if the result cannot be represented as a
+ * nonnegative int value
+ * @since 1.1
+ */
+ public static int gcd(final int p, final int q) {
+ int u = p;
+ int v = q;
+ if ((u == 0) || (v == 0)) {
+ if ((u == Integer.MIN_VALUE) || (v == Integer.MIN_VALUE)) {
+ throw MathRuntimeException.createArithmeticException(
+ "overflow: gcd({0}, {1}) is 2^31",
+ p, q);
+ }
+ return Math.abs(u) + Math.abs(v);
+ }
+ // keep u and v negative, as negative integers range down to
+ // -2^31, while positive numbers can only be as large as 2^31-1
+ // (i.e. we can't necessarily negate a negative number without
+ // overflow)
+ /* assert u!=0 && v!=0; */
+ if (u > 0) {
+ u = -u;
+ } // make u negative
+ if (v > 0) {
+ v = -v;
+ } // make v negative
+ // B1. [Find power of 2]
+ int k = 0;
+ while ((u & 1) == 0 && (v & 1) == 0 && k < 31) { // while u and v are
+ // both even...
+ u /= 2;
+ v /= 2;
+ k++; // cast out twos.
+ }
+ if (k == 31) {
+ throw MathRuntimeException.createArithmeticException(
+ "overflow: gcd({0}, {1}) is 2^31",
+ p, q);
+ }
+ // B2. Initialize: u and v have been divided by 2^k and at least
+ // one is odd.
+ int t = ((u & 1) == 1) ? v : -(u / 2)/* B3 */;
+ // t negative: u was odd, v may be even (t replaces v)
+ // t positive: u was even, v is odd (t replaces u)
+ do {
+ /* assert u<0 && v<0; */
+ // B4/B3: cast out twos from t.
+ while ((t & 1) == 0) { // while t is even..
+ t /= 2; // cast out twos
+ }
+ // B5 [reset max(u,v)]
+ if (t > 0) {
+ u = -t;
+ } else {
+ v = t;
+ }
+ // B6/B3. at this point both u and v should be odd.
+ t = (v - u) / 2;
+ // |u| larger: t positive (replace u)
+ // |v| larger: t negative (replace v)
+ } while (t != 0);
+ return -u * (1 << k); // gcd is u*2^k
+ }
+
+ /**
+ *
+ * Gets the greatest common divisor of the absolute value of two numbers,
+ * using the "binary gcd" method which avoids division and modulo
+ * operations. See Knuth 4.5.2 algorithm B. This algorithm is due to Josef
+ * Stein (1961).
+ *
+ * Special cases:
+ *
+ * - The invocations
+ *
gcd(Long.MIN_VALUE, Long.MIN_VALUE)
,
+ * gcd(Long.MIN_VALUE, 0L)
and
+ * gcd(0L, Long.MIN_VALUE)
throw an
+ * ArithmeticException
, because the result would be 2^63, which
+ * is too large for a long value.
+ * - The result of
gcd(x, x)
, gcd(0L, x)
and
+ * gcd(x, 0L)
is the absolute value of x
, except
+ * for the special cases above.
+ * - The invocation
gcd(0L, 0L)
is the only one which returns
+ * 0L
.
+ *
+ *
+ * @param p any number
+ * @param q any number
+ * @return the greatest common divisor, never negative
+ * @throws ArithmeticException if the result cannot be represented as a nonnegative long
+ * value
+ * @since 2.1
+ */
+ public static long gcd(final long p, final long q) {
+ long u = p;
+ long v = q;
+ if ((u == 0) || (v == 0)) {
+ if ((u == Long.MIN_VALUE) || (v == Long.MIN_VALUE)) {
+ throw MathRuntimeException.createArithmeticException(
+ "overflow: gcd({0}, {1}) is 2^63",
+ p, q);
+ }
+ return Math.abs(u) + Math.abs(v);
+ }
+ // keep u and v negative, as negative integers range down to
+ // -2^63, while positive numbers can only be as large as 2^63-1
+ // (i.e. we can't necessarily negate a negative number without
+ // overflow)
+ /* assert u!=0 && v!=0; */
+ if (u > 0) {
+ u = -u;
+ } // make u negative
+ if (v > 0) {
+ v = -v;
+ } // make v negative
+ // B1. [Find power of 2]
+ int k = 0;
+ while ((u & 1) == 0 && (v & 1) == 0 && k < 63) { // while u and v are
+ // both even...
+ u /= 2;
+ v /= 2;
+ k++; // cast out twos.
+ }
+ if (k == 63) {
+ throw MathRuntimeException.createArithmeticException(
+ "overflow: gcd({0}, {1}) is 2^63",
+ p, q);
+ }
+ // B2. Initialize: u and v have been divided by 2^k and at least
+ // one is odd.
+ long t = ((u & 1) == 1) ? v : -(u / 2)/* B3 */;
+ // t negative: u was odd, v may be even (t replaces v)
+ // t positive: u was even, v is odd (t replaces u)
+ do {
+ /* assert u<0 && v<0; */
+ // B4/B3: cast out twos from t.
+ while ((t & 1) == 0) { // while t is even..
+ t /= 2; // cast out twos
+ }
+ // B5 [reset max(u,v)]
+ if (t > 0) {
+ u = -t;
+ } else {
+ v = t;
+ }
+ // B6/B3. at this point both u and v should be odd.
+ t = (v - u) / 2;
+ // |u| larger: t positive (replace u)
+ // |v| larger: t negative (replace v)
+ } while (t != 0);
+ return -u * (1L << k); // gcd is u*2^k
+ }
+
+ /**
+ * Returns an integer hash code representing the given double value.
+ *
+ * @param value the value to be hashed
+ * @return the hash code
+ */
+ public static int hash(double value) {
+ return new Double(value).hashCode();
+ }
+
+ /**
+ * Returns an integer hash code representing the given double array.
+ *
+ * @param value the value to be hashed (may be null)
+ * @return the hash code
+ * @since 1.2
+ */
+ public static int hash(double[] value) {
+ return Arrays.hashCode(value);
+ }
+
+ /**
+ * For a byte value x, this method returns (byte)(+1) if x >= 0 and
+ * (byte)(-1) if x < 0.
+ *
+ * @param x the value, a byte
+ * @return (byte)(+1) or (byte)(-1), depending on the sign of x
+ */
+ public static byte indicator(final byte x) {
+ return (x >= ZB) ? PB : NB;
+ }
+
+ /**
+ * For a double precision value x, this method returns +1.0 if x >= 0 and
+ * -1.0 if x < 0. Returns NaN
if x
is
+ * NaN
.
+ *
+ * @param x the value, a double
+ * @return +1.0 or -1.0, depending on the sign of x
+ */
+ public static double indicator(final double x) {
+ if (Double.isNaN(x)) {
+ return Double.NaN;
+ }
+ return (x >= 0.0) ? 1.0 : -1.0;
+ }
+
+ /**
+ * For a float value x, this method returns +1.0F if x >= 0 and -1.0F if x <
+ * 0. Returns NaN
if x
is NaN
.
+ *
+ * @param x the value, a float
+ * @return +1.0F or -1.0F, depending on the sign of x
+ */
+ public static float indicator(final float x) {
+ if (Float.isNaN(x)) {
+ return Float.NaN;
+ }
+ return (x >= 0.0F) ? 1.0F : -1.0F;
+ }
+
+ /**
+ * For an int value x, this method returns +1 if x >= 0 and -1 if x < 0.
+ *
+ * @param x the value, an int
+ * @return +1 or -1, depending on the sign of x
+ */
+ public static int indicator(final int x) {
+ return (x >= 0) ? 1 : -1;
+ }
+
+ /**
+ * For a long value x, this method returns +1L if x >= 0 and -1L if x < 0.
+ *
+ * @param x the value, a long
+ * @return +1L or -1L, depending on the sign of x
+ */
+ public static long indicator(final long x) {
+ return (x >= 0L) ? 1L : -1L;
+ }
+
+ /**
+ * For a short value x, this method returns (short)(+1) if x >= 0 and
+ * (short)(-1) if x < 0.
+ *
+ * @param x the value, a short
+ * @return (short)(+1) or (short)(-1), depending on the sign of x
+ */
+ public static short indicator(final short x) {
+ return (x >= ZS) ? PS : NS;
+ }
+
+ /**
+ *
+ * Returns the least common multiple of the absolute value of two numbers,
+ * using the formula lcm(a,b) = (a / gcd(a,b)) * b
.
+ *
+ * Special cases:
+ *
+ * - The invocations
lcm(Integer.MIN_VALUE, n)
and
+ * lcm(n, Integer.MIN_VALUE)
, where abs(n)
is a
+ * power of 2, throw an ArithmeticException
, because the result
+ * would be 2^31, which is too large for an int value.
+ * - The result of
lcm(0, x)
and lcm(x, 0)
is
+ * 0
for any x
.
+ *
+ *
+ * @param a any number
+ * @param b any number
+ * @return the least common multiple, never negative
+ * @throws ArithmeticException if the result cannot be represented as a nonnegative int
+ * value
+ * @since 1.1
+ */
+ public static int lcm(int a, int b) {
+ if (a == 0 || b == 0) {
+ return 0;
+ }
+ int lcm = Math.abs(mulAndCheck(a / gcd(a, b), b));
+ if (lcm == Integer.MIN_VALUE) {
+ throw MathRuntimeException.createArithmeticException(
+ "overflow: lcm({0}, {1}) is 2^31",
+ a, b);
+ }
+ return lcm;
+ }
+
+ /**
+ *
+ * Returns the least common multiple of the absolute value of two numbers,
+ * using the formula lcm(a,b) = (a / gcd(a,b)) * b
.
+ *
+ * Special cases:
+ *
+ * - The invocations
lcm(Long.MIN_VALUE, n)
and
+ * lcm(n, Long.MIN_VALUE)
, where abs(n)
is a
+ * power of 2, throw an ArithmeticException
, because the result
+ * would be 2^63, which is too large for an int value.
+ * - The result of
lcm(0L, x)
and lcm(x, 0L)
is
+ * 0L
for any x
.
+ *
+ *
+ * @param a any number
+ * @param b any number
+ * @return the least common multiple, never negative
+ * @throws ArithmeticException if the result cannot be represented as a nonnegative long
+ * value
+ * @since 2.1
+ */
+ public static long lcm(long a, long b) {
+ if (a == 0 || b == 0) {
+ return 0;
+ }
+ long lcm = Math.abs(mulAndCheck(a / gcd(a, b), b));
+ if (lcm == Long.MIN_VALUE) {
+ throw MathRuntimeException.createArithmeticException(
+ "overflow: lcm({0}, {1}) is 2^63",
+ a, b);
+ }
+ return lcm;
+ }
+
+ /**
+ * Returns the
+ * logarithm
+ * for base b
of x
.
+ *
+ * Returns NaN if either argument is negative. If
+ * base
is 0 and x
is positive, 0 is returned.
+ * If base
is positive and x
is 0,
+ * Double.NEGATIVE_INFINITY
is returned. If both arguments
+ * are 0, the result is NaN
.
+ *
+ * @param base the base of the logarithm, must be greater than 0
+ * @param x argument, must be greater than 0
+ * @return the value of the logarithm - the number y such that base^y = x.
+ * @since 1.2
+ */
+ public static double log(double base, double x) {
+ return Math.log(x) / Math.log(base);
+ }
+
+ /**
+ * Multiply two integers, checking for overflow.
+ *
+ * @param x a factor
+ * @param y a factor
+ * @return the product x*y
+ * @throws ArithmeticException if the result can not be represented as an
+ * int
+ * @since 1.1
+ */
+ public static int mulAndCheck(int x, int y) {
+ long m = ((long) x) * ((long) y);
+ if (m < Integer.MIN_VALUE || m > Integer.MAX_VALUE) {
+ throw new ArithmeticException("overflow: mul");
+ }
+ return (int) m;
+ }
+
+ /**
+ * Multiply two long integers, checking for overflow.
+ *
+ * @param a first value
+ * @param b second value
+ * @return the product a * b
+ * @throws ArithmeticException if the result can not be represented as an
+ * long
+ * @since 1.2
+ */
+ public static long mulAndCheck(long a, long b) {
+ long ret;
+ String msg = "overflow: multiply";
+ if (a > b) {
+ // use symmetry to reduce boundary cases
+ ret = mulAndCheck(b, a);
+ } else {
+ if (a < 0) {
+ if (b < 0) {
+ // check for positive overflow with negative a, negative b
+ if (a >= Long.MAX_VALUE / b) {
+ ret = a * b;
+ } else {
+ throw new ArithmeticException(msg);
+ }
+ } else if (b > 0) {
+ // check for negative overflow with negative a, positive b
+ if (Long.MIN_VALUE / b <= a) {
+ ret = a * b;
+ } else {
+ throw new ArithmeticException(msg);
+
+ }
+ } else {
+ // assert b == 0
+ ret = 0;
+ }
+ } else if (a > 0) {
+ // assert a > 0
+ // assert b > 0
+
+ // check for positive overflow with positive a, positive b
+ if (a <= Long.MAX_VALUE / b) {
+ ret = a * b;
+ } else {
+ throw new ArithmeticException(msg);
+ }
+ } else {
+ // assert a == 0
+ ret = 0;
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Get the next machine representable number after a number, moving
+ * in the direction of another number.
+ *
+ * If direction
is greater than or equal tod
,
+ * the smallest machine representable number strictly greater than
+ * d
is returned; otherwise the largest representable number
+ * strictly less than d
is returned.
+ *
+ * If d
is NaN or Infinite, it is returned unchanged.
+ *
+ * @param d base number
+ * @param direction (the only important thing is whether
+ * direction is greater or smaller than d)
+ * @return the next machine representable number in the specified direction
+ * @since 1.2
+ */
+ public static double nextAfter(double d, double direction) {
+
+ // handling of some important special cases
+ if (Double.isNaN(d) || Double.isInfinite(d)) {
+ return d;
+ } else if (d == 0) {
+ return (direction < 0) ? -Double.MIN_VALUE : Double.MIN_VALUE;
+ }
+ // special cases MAX_VALUE to infinity and MIN_VALUE to 0
+ // are handled just as normal numbers
+
+ // split the double in raw components
+ long bits = Double.doubleToLongBits(d);
+ long sign = bits & 0x8000000000000000L;
+ long exponent = bits & 0x7ff0000000000000L;
+ long mantissa = bits & 0x000fffffffffffffL;
+
+ if (d * (direction - d) >= 0) {
+ // we should increase the mantissa
+ if (mantissa == 0x000fffffffffffffL) {
+ return Double.longBitsToDouble(sign |
+ (exponent + 0x0010000000000000L));
+ } else {
+ return Double.longBitsToDouble(sign |
+ exponent | (mantissa + 1));
+ }
+ } else {
+ // we should decrease the mantissa
+ if (mantissa == 0L) {
+ return Double.longBitsToDouble(sign |
+ (exponent - 0x0010000000000000L) |
+ 0x000fffffffffffffL);
+ } else {
+ return Double.longBitsToDouble(sign |
+ exponent | (mantissa - 1));
+ }
+ }
+
+ }
+
+ /**
+ * Scale a number by 2scaleFactor.
+ * If d
is 0 or NaN or Infinite, it is returned unchanged.
+ *
+ * @param d base number
+ * @param scaleFactor power of two by which d sould be multiplied
+ * @return d × 2scaleFactor
+ * @since 2.0
+ */
+ public static double scalb(final double d, final int scaleFactor) {
+
+ // handling of some important special cases
+ if ((d == 0) || Double.isNaN(d) || Double.isInfinite(d)) {
+ return d;
+ }
+
+ // split the double in raw components
+ final long bits = Double.doubleToLongBits(d);
+ final long exponent = bits & 0x7ff0000000000000L;
+ final long rest = bits & 0x800fffffffffffffL;
+
+ // shift the exponent
+ final long newBits = rest | (exponent + (((long) scaleFactor) << 52));
+ return Double.longBitsToDouble(newBits);
+
+ }
+
+ /**
+ * Normalize an angle in a 2&pi wide interval around a center value.
+ * This method has three main uses:
+ *
+ * - normalize an angle between 0 and 2π:
+ * a = MathUtils.normalizeAngle(a, Math.PI);
+ * - normalize an angle between -π and +π
+ * a = MathUtils.normalizeAngle(a, 0.0);
+ * - compute the angle between two defining angular positions:
+ * angle = MathUtils.normalizeAngle(end, start) - start;
+ *
+ * Note that due to numerical accuracy and since π cannot be represented
+ * exactly, the result interval is closed, it cannot be half-closed
+ * as would be more satisfactory in a purely mathematical view.
+ *
+ * @param a angle to normalize
+ * @param center center of the desired 2π interval for the result
+ * @return a-2kπ with integer k and center-π <= a-2kπ <= center+π
+ * @since 1.2
+ */
+ public static double normalizeAngle(double a, double center) {
+ return a - TWO_PI * Math.floor((a + Math.PI - center) / TWO_PI);
+ }
+
+ /**
+ * Normalizes an array to make it sum to a specified value.
+ * Returns the result of the transformation
+ * x |-> x * normalizedSum / sum
+ *
+ * applied to each non-NaN element x of the input array, where sum is the
+ * sum of the non-NaN entries in the input array.
+ *
+ * Throws IllegalArgumentException if normalizedSum
is infinite
+ * or NaN and ArithmeticException if the input array contains any infinite elements
+ * or sums to 0
+ *
+ * Ignores (i.e., copies unchanged to the output array) NaNs in the input array.
+ *
+ * @param values input array to be normalized
+ * @param normalizedSum target sum for the normalized array
+ * @return normalized array
+ * @throws ArithmeticException if the input array contains infinite elements or sums to zero
+ * @throws IllegalArgumentException if the target sum is infinite or NaN
+ * @since 2.1
+ */
+ public static double[] normalizeArray(double[] values, double normalizedSum)
+ throws ArithmeticException, IllegalArgumentException {
+ if (Double.isInfinite(normalizedSum)) {
+ throw MathRuntimeException.createIllegalArgumentException(
+ "Cannot normalize to an infinite value");
+ }
+ if (Double.isNaN(normalizedSum)) {
+ throw MathRuntimeException.createIllegalArgumentException(
+ "Cannot normalize to NaN");
+ }
+ double sum = 0d;
+ final int len = values.length;
+ double[] out = new double[len];
+ for (int i = 0; i < len; i++) {
+ if (Double.isInfinite(values[i])) {
+ throw MathRuntimeException.createArithmeticException(
+ "Array contains an infinite element, {0} at index {1}", values[i], i);
+ }
+ if (!Double.isNaN(values[i])) {
+ sum += values[i];
+ }
+ }
+ if (sum == 0) {
+ throw MathRuntimeException.createArithmeticException(
+ "Array sums to zero");
+ }
+ for (int i = 0; i < len; i++) {
+ if (Double.isNaN(values[i])) {
+ out[i] = Double.NaN;
+ } else {
+ out[i] = values[i] * normalizedSum / sum;
+ }
+ }
+ return out;
+ }
+
+ /**
+ * Round the given value to the specified number of decimal places. The
+ * value is rounded using the {@link BigDecimal#ROUND_HALF_UP} method.
+ *
+ * @param x the value to round.
+ * @param scale the number of digits to the right of the decimal point.
+ * @return the rounded value.
+ * @since 1.1
+ */
+ public static double round(double x, int scale) {
+ return round(x, scale, BigDecimal.ROUND_HALF_UP);
+ }
+
+ /**
+ * Round the given value to the specified number of decimal places. The
+ * value is rounded using the given method which is any method defined in
+ * {@link BigDecimal}.
+ *
+ * @param x the value to round.
+ * @param scale the number of digits to the right of the decimal point.
+ * @param roundingMethod the rounding method as defined in
+ * {@link BigDecimal}.
+ * @return the rounded value.
+ * @since 1.1
+ */
+ public static double round(double x, int scale, int roundingMethod) {
+ try {
+ return (new BigDecimal
+ (Double.toString(x))
+ .setScale(scale, roundingMethod))
+ .doubleValue();
+ } catch (NumberFormatException ex) {
+ if (Double.isInfinite(x)) {
+ return x;
+ } else {
+ return Double.NaN;
+ }
+ }
+ }
+
+ /**
+ * Round the given value to the specified number of decimal places. The
+ * value is rounding using the {@link BigDecimal#ROUND_HALF_UP} method.
+ *
+ * @param x the value to round.
+ * @param scale the number of digits to the right of the decimal point.
+ * @return the rounded value.
+ * @since 1.1
+ */
+ public static float round(float x, int scale) {
+ return round(x, scale, BigDecimal.ROUND_HALF_UP);
+ }
+
+ /**
+ * Round the given value to the specified number of decimal places. The
+ * value is rounded using the given method which is any method defined in
+ * {@link BigDecimal}.
+ *
+ * @param x the value to round.
+ * @param scale the number of digits to the right of the decimal point.
+ * @param roundingMethod the rounding method as defined in
+ * {@link BigDecimal}.
+ * @return the rounded value.
+ * @since 1.1
+ */
+ public static float round(float x, int scale, int roundingMethod) {
+ float sign = indicator(x);
+ float factor = (float) Math.pow(10.0f, scale) * sign;
+ return (float) roundUnscaled(x * factor, sign, roundingMethod) / factor;
+ }
+
+ /**
+ * Round the given non-negative, value to the "nearest" integer. Nearest is
+ * determined by the rounding method specified. Rounding methods are defined
+ * in {@link BigDecimal}.
+ *
+ * @param unscaled the value to round.
+ * @param sign the sign of the original, scaled value.
+ * @param roundingMethod the rounding method as defined in
+ * {@link BigDecimal}.
+ * @return the rounded value.
+ * @since 1.1
+ */
+ private static double roundUnscaled(double unscaled, double sign,
+ int roundingMethod) {
+ switch (roundingMethod) {
+ case BigDecimal.ROUND_CEILING:
+ if (sign == -1) {
+ unscaled = Math.floor(nextAfter(unscaled, Double.NEGATIVE_INFINITY));
+ } else {
+ unscaled = Math.ceil(nextAfter(unscaled, Double.POSITIVE_INFINITY));
+ }
+ break;
+ case BigDecimal.ROUND_DOWN:
+ unscaled = Math.floor(nextAfter(unscaled, Double.NEGATIVE_INFINITY));
+ break;
+ case BigDecimal.ROUND_FLOOR:
+ if (sign == -1) {
+ unscaled = Math.ceil(nextAfter(unscaled, Double.POSITIVE_INFINITY));
+ } else {
+ unscaled = Math.floor(nextAfter(unscaled, Double.NEGATIVE_INFINITY));
+ }
+ break;
+ case BigDecimal.ROUND_HALF_DOWN: {
+ unscaled = nextAfter(unscaled, Double.NEGATIVE_INFINITY);
+ double fraction = unscaled - Math.floor(unscaled);
+ if (fraction > 0.5) {
+ unscaled = Math.ceil(unscaled);
+ } else {
+ unscaled = Math.floor(unscaled);
+ }
+ break;
+ }
+ case BigDecimal.ROUND_HALF_EVEN: {
+ double fraction = unscaled - Math.floor(unscaled);
+ if (fraction > 0.5) {
+ unscaled = Math.ceil(unscaled);
+ } else if (fraction < 0.5) {
+ unscaled = Math.floor(unscaled);
+ } else {
+ // The following equality test is intentional and needed for rounding purposes
+ if (Math.floor(unscaled) / 2.0 == Math.floor(Math
+ .floor(unscaled) / 2.0)) { // even
+ unscaled = Math.floor(unscaled);
+ } else { // odd
+ unscaled = Math.ceil(unscaled);
+ }
+ }
+ break;
+ }
+ case BigDecimal.ROUND_HALF_UP: {
+ unscaled = nextAfter(unscaled, Double.POSITIVE_INFINITY);
+ double fraction = unscaled - Math.floor(unscaled);
+ if (fraction >= 0.5) {
+ unscaled = Math.ceil(unscaled);
+ } else {
+ unscaled = Math.floor(unscaled);
+ }
+ break;
+ }
+ case BigDecimal.ROUND_UNNECESSARY:
+ if (unscaled != Math.floor(unscaled)) {
+ throw new ArithmeticException("Inexact result from rounding");
+ }
+ break;
+ case BigDecimal.ROUND_UP:
+ unscaled = Math.ceil(nextAfter(unscaled, Double.POSITIVE_INFINITY));
+ break;
+ default:
+ throw MathRuntimeException.createIllegalArgumentException(
+ "invalid rounding method {0}, valid methods: {1} ({2}), {3} ({4})," +
+ " {5} ({6}), {7} ({8}), {9} ({10}), {11} ({12}), {13} ({14}), {15} ({16})",
+ roundingMethod,
+ "ROUND_CEILING", BigDecimal.ROUND_CEILING,
+ "ROUND_DOWN", BigDecimal.ROUND_DOWN,
+ "ROUND_FLOOR", BigDecimal.ROUND_FLOOR,
+ "ROUND_HALF_DOWN", BigDecimal.ROUND_HALF_DOWN,
+ "ROUND_HALF_EVEN", BigDecimal.ROUND_HALF_EVEN,
+ "ROUND_HALF_UP", BigDecimal.ROUND_HALF_UP,
+ "ROUND_UNNECESSARY", BigDecimal.ROUND_UNNECESSARY,
+ "ROUND_UP", BigDecimal.ROUND_UP);
+ }
+ return unscaled;
+ }
+
+ /**
+ * Returns the sign
+ * for byte value x
.
+ *
+ * For a byte value x, this method returns (byte)(+1) if x > 0, (byte)(0) if
+ * x = 0, and (byte)(-1) if x < 0.
+ *
+ * @param x the value, a byte
+ * @return (byte)(+1), (byte)(0), or (byte)(-1), depending on the sign of x
+ */
+ public static byte sign(final byte x) {
+ return (x == ZB) ? ZB : (x > ZB) ? PB : NB;
+ }
+
+ /**
+ * Returns the sign
+ * for double precision x
.
+ *
+ * For a double value x
, this method returns
+ * +1.0
if x > 0
, 0.0
if
+ * x = 0.0
, and -1.0
if x < 0
.
+ * Returns NaN
if x
is NaN
.
+ *
+ * @param x the value, a double
+ * @return +1.0, 0.0, or -1.0, depending on the sign of x
+ */
+ public static double sign(final double x) {
+ if (Double.isNaN(x)) {
+ return Double.NaN;
+ }
+ return (x == 0.0) ? 0.0 : (x > 0.0) ? 1.0 : -1.0;
+ }
+
+ /**
+ * Returns the sign
+ * for float value x
.
+ *
+ * For a float value x, this method returns +1.0F if x > 0, 0.0F if x =
+ * 0.0F, and -1.0F if x < 0. Returns NaN
if x
+ * is NaN
.
+ *
+ * @param x the value, a float
+ * @return +1.0F, 0.0F, or -1.0F, depending on the sign of x
+ */
+ public static float sign(final float x) {
+ if (Float.isNaN(x)) {
+ return Float.NaN;
+ }
+ return (x == 0.0F) ? 0.0F : (x > 0.0F) ? 1.0F : -1.0F;
+ }
+
+ /**
+ * Returns the sign
+ * for int value x
.
+ *
+ * For an int value x, this method returns +1 if x > 0, 0 if x = 0, and -1
+ * if x < 0.
+ *
+ * @param x the value, an int
+ * @return +1, 0, or -1, depending on the sign of x
+ */
+ public static int sign(final int x) {
+ return (x == 0) ? 0 : (x > 0) ? 1 : -1;
+ }
+
+ /**
+ * Returns the sign
+ * for long value x
.
+ *
+ * For a long value x, this method returns +1L if x > 0, 0L if x = 0, and
+ * -1L if x < 0.
+ *
+ * @param x the value, a long
+ * @return +1L, 0L, or -1L, depending on the sign of x
+ */
+ public static long sign(final long x) {
+ return (x == 0L) ? 0L : (x > 0L) ? 1L : -1L;
+ }
+
+ /**
+ * Returns the sign
+ * for short value x
.
+ *
+ * For a short value x, this method returns (short)(+1) if x > 0, (short)(0)
+ * if x = 0, and (short)(-1) if x < 0.
+ *
+ * @param x the value, a short
+ * @return (short)(+1), (short)(0), or (short)(-1), depending on the sign of
+ * x
+ */
+ public static short sign(final short x) {
+ return (x == ZS) ? ZS : (x > ZS) ? PS : NS;
+ }
+
+ /**
+ * Returns the
+ * hyperbolic sine of x.
+ *
+ * @param x double value for which to find the hyperbolic sine
+ * @return hyperbolic sine of x
+ */
+ public static double sinh(double x) {
+ return (Math.exp(x) - Math.exp(-x)) / 2.0;
+ }
+
+ /**
+ * Subtract two integers, checking for overflow.
+ *
+ * @param x the minuend
+ * @param y the subtrahend
+ * @return the difference x-y
+ * @throws ArithmeticException if the result can not be represented as an
+ * int
+ * @since 1.1
+ */
+ public static int subAndCheck(int x, int y) {
+ long s = (long) x - (long) y;
+ if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) {
+ throw new ArithmeticException("overflow: subtract");
+ }
+ return (int) s;
+ }
+
+ /**
+ * Subtract two long integers, checking for overflow.
+ *
+ * @param a first value
+ * @param b second value
+ * @return the difference a-b
+ * @throws ArithmeticException if the result can not be represented as an
+ * long
+ * @since 1.2
+ */
+ public static long subAndCheck(long a, long b) {
+ long ret;
+ String msg = "overflow: subtract";
+ if (b == Long.MIN_VALUE) {
+ if (a < 0) {
+ ret = a - b;
+ } else {
+ throw new ArithmeticException(msg);
+ }
+ } else {
+ // use additive inverse
+ ret = addAndCheck(a, -b, msg);
+ }
+ return ret;
+ }
+
+ /**
+ * Raise an int to an int power.
+ *
+ * @param k number to raise
+ * @param e exponent (must be positive or null)
+ * @return ke
+ * @throws IllegalArgumentException if e is negative
+ */
+ public static int pow(final int k, int e)
+ throws IllegalArgumentException {
+
+ if (e < 0) {
+ throw MathRuntimeException.createIllegalArgumentException(
+ "cannot raise an integral value to a negative power ({0}^{1})",
+ k, e);
+ }
+
+ int result = 1;
+ int k2p = k;
+ while (e != 0) {
+ if ((e & 0x1) != 0) {
+ result *= k2p;
+ }
+ k2p *= k2p;
+ e = e >> 1;
+ }
+
+ return result;
+
+ }
+
+ /**
+ * Raise an int to a long power.
+ *
+ * @param k number to raise
+ * @param e exponent (must be positive or null)
+ * @return ke
+ * @throws IllegalArgumentException if e is negative
+ */
+ public static int pow(final int k, long e)
+ throws IllegalArgumentException {
+
+ if (e < 0) {
+ throw MathRuntimeException.createIllegalArgumentException(
+ "cannot raise an integral value to a negative power ({0}^{1})",
+ k, e);
+ }
+
+ int result = 1;
+ int k2p = k;
+ while (e != 0) {
+ if ((e & 0x1) != 0) {
+ result *= k2p;
+ }
+ k2p *= k2p;
+ e = e >> 1;
+ }
+
+ return result;
+
+ }
+
+ /**
+ * Raise a long to an int power.
+ *
+ * @param k number to raise
+ * @param e exponent (must be positive or null)
+ * @return ke
+ * @throws IllegalArgumentException if e is negative
+ */
+ public static long pow(final long k, int e)
+ throws IllegalArgumentException {
+
+ if (e < 0) {
+ throw MathRuntimeException.createIllegalArgumentException(
+ "cannot raise an integral value to a negative power ({0}^{1})",
+ k, e);
+ }
+
+ long result = 1l;
+ long k2p = k;
+ while (e != 0) {
+ if ((e & 0x1) != 0) {
+ result *= k2p;
+ }
+ k2p *= k2p;
+ e = e >> 1;
+ }
+
+ return result;
+
+ }
+
+ /**
+ * Raise a long to a long power.
+ *
+ * @param k number to raise
+ * @param e exponent (must be positive or null)
+ * @return ke
+ * @throws IllegalArgumentException if e is negative
+ */
+ public static long pow(final long k, long e)
+ throws IllegalArgumentException {
+
+ if (e < 0) {
+ throw MathRuntimeException.createIllegalArgumentException(
+ "cannot raise an integral value to a negative power ({0}^{1})",
+ k, e);
+ }
+
+ long result = 1l;
+ long k2p = k;
+ while (e != 0) {
+ if ((e & 0x1) != 0) {
+ result *= k2p;
+ }
+ k2p *= k2p;
+ e = e >> 1;
+ }
+
+ return result;
+
+ }
+
+ /**
+ * Raise a BigInteger to an int power.
+ *
+ * @param k number to raise
+ * @param e exponent (must be positive or null)
+ * @return ke
+ * @throws IllegalArgumentException if e is negative
+ */
+ public static BigInteger pow(final BigInteger k, int e)
+ throws IllegalArgumentException {
+
+ if (e < 0) {
+ throw MathRuntimeException.createIllegalArgumentException(
+ "cannot raise an integral value to a negative power ({0}^{1})",
+ k, e);
+ }
+
+ return k.pow(e);
+
+ }
+
+ /**
+ * Raise a BigInteger to a long power.
+ *
+ * @param k number to raise
+ * @param e exponent (must be positive or null)
+ * @return ke
+ * @throws IllegalArgumentException if e is negative
+ */
+ public static BigInteger pow(final BigInteger k, long e)
+ throws IllegalArgumentException {
+
+ if (e < 0) {
+ throw MathRuntimeException.createIllegalArgumentException(
+ "cannot raise an integral value to a negative power ({0}^{1})",
+ k, e);
+ }
+
+ BigInteger result = BigInteger.ONE;
+ BigInteger k2p = k;
+ while (e != 0) {
+ if ((e & 0x1) != 0) {
+ result = result.multiply(k2p);
+ }
+ k2p = k2p.multiply(k2p);
+ e = e >> 1;
+ }
+
+ return result;
+
+ }
+
+ /**
+ * Raise a BigInteger to a BigInteger power.
+ *
+ * @param k number to raise
+ * @param e exponent (must be positive or null)
+ * @return ke
+ * @throws IllegalArgumentException if e is negative
+ */
+ public static BigInteger pow(final BigInteger k, BigInteger e)
+ throws IllegalArgumentException {
+
+ if (e.compareTo(BigInteger.ZERO) < 0) {
+ throw MathRuntimeException.createIllegalArgumentException(
+ "cannot raise an integral value to a negative power ({0}^{1})",
+ k, e);
+ }
+
+ BigInteger result = BigInteger.ONE;
+ BigInteger k2p = k;
+ while (!BigInteger.ZERO.equals(e)) {
+ if (e.testBit(0)) {
+ result = result.multiply(k2p);
+ }
+ k2p = k2p.multiply(k2p);
+ e = e.shiftRight(1);
+ }
+
+ return result;
+
+ }
+
+ /**
+ * Calculates the L1 (sum of abs) distance between two points.
+ *
+ * @param p1 the first point
+ * @param p2 the second point
+ * @return the L1 distance between the two points
+ */
+ public static double distance1(double[] p1, double[] p2) {
+ double sum = 0;
+ for (int i = 0; i < p1.length; i++) {
+ sum += Math.abs(p1[i] - p2[i]);
+ }
+ return sum;
+ }
+
+ /**
+ * Calculates the L1 (sum of abs) distance between two points.
+ *
+ * @param p1 the first point
+ * @param p2 the second point
+ * @return the L1 distance between the two points
+ */
+ public static int distance1(int[] p1, int[] p2) {
+ int sum = 0;
+ for (int i = 0; i < p1.length; i++) {
+ sum += Math.abs(p1[i] - p2[i]);
+ }
+ return sum;
+ }
+
+ /**
+ * Calculates the L2 (Euclidean) distance between two points.
+ *
+ * @param p1 the first point
+ * @param p2 the second point
+ * @return the L2 distance between the two points
+ */
+ public static double distance(double[] p1, double[] p2) {
+ double sum = 0;
+ for (int i = 0; i < p1.length; i++) {
+ final double dp = p1[i] - p2[i];
+ sum += dp * dp;
+ }
+ return Math.sqrt(sum);
+ }
+
+ /**
+ * Calculates the L2 (Euclidean) distance between two points.
+ *
+ * @param p1 the first point
+ * @param p2 the second point
+ * @return the L2 distance between the two points
+ */
+ public static double distance(int[] p1, int[] p2) {
+ double sum = 0;
+ for (int i = 0; i < p1.length; i++) {
+ final double dp = p1[i] - p2[i];
+ sum += dp * dp;
+ }
+ return Math.sqrt(sum);
+ }
+
+ /**
+ * Calculates the L∞ (max of abs) distance between two points.
+ *
+ * @param p1 the first point
+ * @param p2 the second point
+ * @return the L∞ distance between the two points
+ */
+ public static double distanceInf(double[] p1, double[] p2) {
+ double max = 0;
+ for (int i = 0; i < p1.length; i++) {
+ max = Math.max(max, Math.abs(p1[i] - p2[i]));
+ }
+ return max;
+ }
+
+ /**
+ * Calculates the L∞ (max of abs) distance between two points.
+ *
+ * @param p1 the first point
+ * @param p2 the second point
+ * @return the L∞ distance between the two points
+ */
+ public static int distanceInf(int[] p1, int[] p2) {
+ int max = 0;
+ for (int i = 0; i < p1.length; i++) {
+ max = Math.max(max, Math.abs(p1[i] - p2[i]));
+ }
+ return max;
+ }
+
+ /**
+ * Checks that the given array is sorted.
+ *
+ * @param val Values
+ * @param dir Order direction (-1 for decreasing, 1 for increasing)
+ * @param strict Whether the order should be strict
+ * @throws IllegalArgumentException if the array is not sorted.
+ */
+ public static void checkOrder(double[] val, int dir, boolean strict) {
+ double previous = val[0];
+
+ int max = val.length;
+ for (int i = 1; i < max; i++) {
+ if (dir > 0) {
+ if (strict) {
+ if (val[i] <= previous) {
+ throw MathRuntimeException.createIllegalArgumentException("points {0} and {1} are not strictly increasing ({2} >= {3})",
+ i - 1, i, previous, val[i]);
+ }
+ } else {
+ if (val[i] < previous) {
+ throw MathRuntimeException.createIllegalArgumentException("points {0} and {1} are not increasing ({2} > {3})",
+ i - 1, i, previous, val[i]);
+ }
+ }
+ } else {
+ if (strict) {
+ if (val[i] >= previous) {
+ throw MathRuntimeException.createIllegalArgumentException("points {0} and {1} are not strictly decreasing ({2} <= {3})",
+ i - 1, i, previous, val[i]);
+ }
+ } else {
+ if (val[i] > previous) {
+ throw MathRuntimeException.createIllegalArgumentException("points {0} and {1} are not decreasing ({2} < {3})",
+ i - 1, i, previous, val[i]);
+ }
+ }
+ }
+
+ previous = val[i];
+ }
+ }
+}
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/math/UnboxedMathUtils.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/math/UnboxedMathUtils.java
new file mode 100644
index 00000000000..cdeec5d5d70
--- /dev/null
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/math/UnboxedMathUtils.java
@@ -0,0 +1,244 @@
+/*
+ * Licensed to Elastic Search and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Elastic Search licenses this
+ * file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.util.math;
+
+/**
+ * @author kimchy (shay.banon)
+ */
+public class UnboxedMathUtils {
+
+ public static double sin(Double a) {
+ return Math.sin(a);
+ }
+
+ public static double cos(Double a) {
+ return Math.cos(a); // default impl. delegates to StrictMath
+ }
+
+ public static double tan(Double a) {
+ return Math.tan(a); // default impl. delegates to StrictMath
+ }
+
+ public static double asin(Double a) {
+ return Math.asin(a); // default impl. delegates to StrictMath
+ }
+
+ public static double acos(Double a) {
+ return Math.acos(a); // default impl. delegates to StrictMath
+ }
+
+ public static double atan(Double a) {
+ return Math.atan(a); // default impl. delegates to StrictMath
+ }
+
+ public static double toRadians(Double angdeg) {
+ return Math.toRadians(angdeg);
+ }
+
+ public static double toDegrees(Double angrad) {
+ return Math.toDegrees(angrad);
+ }
+
+ public static double exp(Double a) {
+ return Math.exp(a);
+ }
+
+ public static double log(Double a) {
+ return Math.log(a);
+ }
+
+ public static double log10(Double a) {
+ return Math.log10(a);
+ }
+
+ public static double sqrt(Double a) {
+ return Math.sqrt(a);
+ }
+
+
+ public static double cbrt(Double a) {
+ return Math.cbrt(a);
+ }
+
+ public static double IEEEremainder(Double f1, Double f2) {
+ return Math.IEEEremainder(f1, f2);
+ }
+
+ public static double ceil(Double a) {
+ return Math.ceil(a);
+ }
+
+ public static double floor(Double a) {
+ return Math.floor(a);
+ }
+
+ public static double rint(Double a) {
+ return Math.rint(a);
+ }
+
+ public static double atan2(Double y, Double x) {
+ return Math.atan2(y, x);
+ }
+
+ public static double pow(Double a, Double b) {
+ return Math.pow(a, b);
+ }
+
+ public static int round(Float a) {
+ return Math.round(a);
+ }
+
+ public static long round(Double a) {
+ return Math.round(a);
+ }
+
+ public static double random() {
+ return Math.random();
+ }
+
+ public static int abs(Integer a) {
+ return Math.abs(a);
+ }
+
+ public static long abs(Long a) {
+ return Math.abs(a);
+ }
+
+ public static float abs(Float a) {
+ return Math.abs(a);
+ }
+
+ public static double abs(Double a) {
+ return Math.abs(a);
+ }
+
+ public static int max(Integer a, Integer b) {
+ return Math.max(a, b);
+ }
+
+ public static long max(Long a, Long b) {
+ return Math.max(a, b);
+ }
+
+ public static float max(Float a, Float b) {
+ return Math.max(a, b);
+ }
+
+ public static double max(Double a, Double b) {
+ return Math.max(a, b);
+ }
+
+ public static int min(Integer a, Integer b) {
+ return Math.min(a, b);
+ }
+
+ public static long min(Long a, Long b) {
+ return Math.min(a, b);
+ }
+
+ public static float min(Float a, Float b) {
+ return Math.min(a, b);
+ }
+
+ public static double min(Double a, Double b) {
+ return Math.min(a, b);
+ }
+
+ public static double ulp(Double d) {
+ return Math.ulp(d);
+ }
+
+ public static float ulp(Float f) {
+ return Math.ulp(f);
+ }
+
+ public static double signum(Double d) {
+ return Math.signum(d);
+ }
+
+ public static float signum(Float f) {
+ return Math.signum(f);
+ }
+
+ public static double sinh(Double x) {
+ return Math.sinh(x);
+ }
+
+ public static double cosh(Double x) {
+ return Math.cosh(x);
+ }
+
+ public static double tanh(Double x) {
+ return Math.tanh(x);
+ }
+
+ public static double hypot(Double x, Double y) {
+ return Math.hypot(x, y);
+ }
+
+ public static double expm1(Double x) {
+ return Math.expm1(x);
+ }
+
+ public static double log1p(Double x) {
+ return Math.log1p(x);
+ }
+
+ public static double copySign(Double magnitude, Double sign) {
+ return Math.copySign(magnitude, sign);
+ }
+
+ public static float copySign(Float magnitude, Float sign) {
+ return Math.copySign(magnitude, sign);
+ }
+
+ public static int getExponent(Float f) {
+ return Math.getExponent(f);
+ }
+
+ public static int getExponent(Double d) {
+ return Math.getExponent(d);
+ }
+
+ public static double nextAfter(Double start, Double direction) {
+ return Math.nextAfter(start, direction);
+ }
+
+ public static float nextAfter(Float start, Double direction) {
+ return Math.nextAfter(start, direction);
+ }
+
+ public static double nextUp(Double d) {
+ return Math.nextUp(d);
+ }
+
+ public static float nextUp(Float f) {
+ return Math.nextUp(f);
+ }
+
+
+ public static double scalb(Double d, Integer scaleFactor) {
+ return Math.scalb(d, scaleFactor);
+ }
+
+ public static float scalb(Float f, Integer scaleFactor) {
+ return Math.scalb(f, scaleFactor);
+ }
+}
diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/field/data/longs/LongFieldDataTests.java b/modules/elasticsearch/src/test/java/org/elasticsearch/index/field/data/longs/LongFieldDataTests.java
index 9631dc8f924..d48e46f4b6b 100644
--- a/modules/elasticsearch/src/test/java/org/elasticsearch/index/field/data/longs/LongFieldDataTests.java
+++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/field/data/longs/LongFieldDataTests.java
@@ -91,10 +91,12 @@ public class LongFieldDataTests {
assertThat(sFieldData.hasValue(0), equalTo(true));
assertThat(sFieldData.docFieldData(0).isEmpty(), equalTo(false));
assertThat(sFieldData.value(0), equalTo(4l));
+ assertThat(sFieldData.date(0).getMillis(), equalTo(4l));
assertThat(sFieldData.docFieldData(0).getValue(), equalTo(4l));
assertThat(sFieldData.values(0).length, equalTo(1));
assertThat(sFieldData.docFieldData(0).getValues().length, equalTo(1));
assertThat(sFieldData.values(0)[0], equalTo(4l));
+ assertThat(sFieldData.dates(0)[0].getMillis(), equalTo(4l));
assertThat(sFieldData.docFieldData(0).getValues()[0], equalTo(4l));
assertThat(sFieldData.hasValue(1), equalTo(true));
@@ -141,9 +143,12 @@ public class LongFieldDataTests {
assertThat(mFieldData.hasValue(1), equalTo(true));
assertThat(mFieldData.value(1), equalTo(104l));
+ assertThat(mFieldData.date(1).getMillis(), equalTo(104l));
assertThat(mFieldData.values(1).length, equalTo(2));
assertThat(mFieldData.values(1)[0], equalTo(104l));
+ assertThat(mFieldData.dates(1)[0].getMillis(), equalTo(104l));
assertThat(mFieldData.values(1)[1], equalTo(105l));
+ assertThat(mFieldData.dates(1)[1].getMillis(), equalTo(105l));
assertThat(mFieldData.hasValue(2), equalTo(false));
diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/SimpleIndexQueryParserTests.java b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/SimpleIndexQueryParserTests.java
index 999aec18ec6..7c38d82f6c3 100644
--- a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/SimpleIndexQueryParserTests.java
+++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/SimpleIndexQueryParserTests.java
@@ -30,8 +30,9 @@ import org.elasticsearch.index.cache.IndexCache;
import org.elasticsearch.index.engine.robin.RobinIndexEngine;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.query.IndexQueryParser;
+import org.elasticsearch.script.ScriptService;
import org.elasticsearch.util.lucene.search.*;
-import org.elasticsearch.util.lucene.search.function.BoostFactorFunctionProvider;
+import org.elasticsearch.util.lucene.search.function.BoostScoreFunction;
import org.elasticsearch.util.lucene.search.function.FunctionScoreQuery;
import org.testng.annotations.Test;
@@ -712,13 +713,23 @@ public class SimpleIndexQueryParserTests {
assertThat(((TermFilter) constantScoreQuery.getFilter()).getTerm(), equalTo(new Term("name.last", "banon")));
}
+ @Test public void testCustomScoreQuery1() throws IOException {
+ IndexQueryParser queryParser = newQueryParser();
+ String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/custom_score1.json");
+ Query parsedQuery = queryParser.parse(query);
+ assertThat(parsedQuery, instanceOf(FunctionScoreQuery.class));
+ FunctionScoreQuery functionScoreQuery = (FunctionScoreQuery) parsedQuery;
+ assertThat(((TermQuery) functionScoreQuery.getSubQuery()).getTerm(), equalTo(new Term("name.last", "banon")));
+ assertThat(functionScoreQuery.getFunction(), instanceOf(CustomScoreQueryParser.ScriptScoreFunction.class));
+ }
+
@Test public void testCustomBoostFactorQueryBuilder() throws IOException {
IndexQueryParser queryParser = newQueryParser();
Query parsedQuery = queryParser.parse(customBoostFactorQuery(termQuery("name.last", "banon")).boostFactor(1.3f));
assertThat(parsedQuery, instanceOf(FunctionScoreQuery.class));
FunctionScoreQuery functionScoreQuery = (FunctionScoreQuery) parsedQuery;
assertThat(((TermQuery) functionScoreQuery.getSubQuery()).getTerm(), equalTo(new Term("name.last", "banon")));
- assertThat((double) ((BoostFactorFunctionProvider) functionScoreQuery.getFunctionProvider()).getBoost(), closeTo(1.3, 0.001));
+ assertThat((double) ((BoostScoreFunction) functionScoreQuery.getFunction()).getBoost(), closeTo(1.3, 0.001));
}
@@ -729,7 +740,7 @@ public class SimpleIndexQueryParserTests {
assertThat(parsedQuery, instanceOf(FunctionScoreQuery.class));
FunctionScoreQuery functionScoreQuery = (FunctionScoreQuery) parsedQuery;
assertThat(((TermQuery) functionScoreQuery.getSubQuery()).getTerm(), equalTo(new Term("name.last", "banon")));
- assertThat((double) ((BoostFactorFunctionProvider) functionScoreQuery.getFunctionProvider()).getBoost(), closeTo(1.3, 0.001));
+ assertThat((double) ((BoostScoreFunction) functionScoreQuery.getFunction()).getBoost(), closeTo(1.3, 0.001));
}
@Test public void testSpanTermQueryBuilder() throws IOException {
@@ -946,7 +957,7 @@ public class SimpleIndexQueryParserTests {
}
private XContentIndexQueryParser newQueryParser() throws IOException {
- return new XContentIndexQueryParser(new Index("test"), EMPTY_SETTINGS,
+ return new XContentIndexQueryParser(new Index("test"), EMPTY_SETTINGS, new ScriptService(EMPTY_SETTINGS),
newMapperService(), new IndexCache(index), new RobinIndexEngine(index), new AnalysisService(index), null, null, null, "test", null);
}
diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/custom_score1.json b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/custom_score1.json
new file mode 100644
index 00000000000..3d81d8210c0
--- /dev/null
+++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/custom_score1.json
@@ -0,0 +1,8 @@
+{
+ "custom_score" : {
+ "query" : {
+ "term" : { "name.last" : "banon"}
+ },
+ "script" : "score * doc['name.first']"
+ }
+}
\ No newline at end of file
diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/guice/IndexQueryParserModuleTests.java b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/guice/IndexQueryParserModuleTests.java
index 146b4c86768..5cebdd66323 100644
--- a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/guice/IndexQueryParserModuleTests.java
+++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/guice/IndexQueryParserModuleTests.java
@@ -30,9 +30,11 @@ import org.elasticsearch.index.query.xcontent.XContentIndexQueryParser;
import org.elasticsearch.index.query.xcontent.XContentQueryParserRegistry;
import org.elasticsearch.index.settings.IndexSettingsModule;
import org.elasticsearch.index.similarity.SimilarityModule;
+import org.elasticsearch.script.ScriptModule;
import org.elasticsearch.util.inject.Guice;
import org.elasticsearch.util.inject.Injector;
import org.elasticsearch.util.settings.Settings;
+import org.elasticsearch.util.settings.SettingsModule;
import org.testng.annotations.Test;
import static org.elasticsearch.util.settings.ImmutableSettings.*;
@@ -55,6 +57,8 @@ public class IndexQueryParserModuleTests {
Index index = new Index("test");
Injector injector = Guice.createInjector(
+ new SettingsModule(settings),
+ new ScriptModule(),
new IndexSettingsModule(settings),
new IndexCacheModule(settings),
new AnalysisModule(settings),
diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/plugin/IndexQueryParserPluginTests.java b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/plugin/IndexQueryParserPluginTests.java
index 8f3aa452aa5..ef647d605b3 100644
--- a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/plugin/IndexQueryParserPluginTests.java
+++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/xcontent/plugin/IndexQueryParserPluginTests.java
@@ -30,10 +30,12 @@ import org.elasticsearch.index.query.xcontent.XContentIndexQueryParser;
import org.elasticsearch.index.query.xcontent.XContentQueryParserRegistry;
import org.elasticsearch.index.settings.IndexSettingsModule;
import org.elasticsearch.index.similarity.SimilarityModule;
+import org.elasticsearch.script.ScriptModule;
import org.elasticsearch.util.inject.Guice;
import org.elasticsearch.util.inject.Injector;
import org.elasticsearch.util.settings.ImmutableSettings;
import org.elasticsearch.util.settings.Settings;
+import org.elasticsearch.util.settings.SettingsModule;
import org.testng.annotations.Test;
import static org.hamcrest.MatcherAssert.*;
@@ -60,6 +62,8 @@ public class IndexQueryParserPluginTests {
Index index = new Index("test");
Injector injector = Guice.createInjector(
+ new SettingsModule(settings),
+ new ScriptModule(),
new IndexSettingsModule(settings),
new IndexCacheModule(settings),
new AnalysisModule(settings),
diff --git a/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/customscore/CustomScoreSearchTests.java b/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/customscore/CustomScoreSearchTests.java
new file mode 100644
index 00000000000..cbde9c6cd6a
--- /dev/null
+++ b/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/customscore/CustomScoreSearchTests.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to Elastic Search and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Elastic Search licenses this
+ * file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.test.integration.search.customscore;
+
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.action.search.SearchType;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.test.integration.AbstractNodesTests;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.elasticsearch.client.Requests.*;
+import static org.elasticsearch.index.query.xcontent.QueryBuilders.*;
+import static org.elasticsearch.search.builder.SearchSourceBuilder.*;
+import static org.elasticsearch.util.xcontent.XContentFactory.*;
+import static org.hamcrest.MatcherAssert.*;
+import static org.hamcrest.Matchers.*;
+
+/**
+ * @author kimchy (shay.banon)
+ */
+@Test
+public class CustomScoreSearchTests extends AbstractNodesTests {
+
+ private Client client;
+
+ @BeforeMethod public void createNodes() throws Exception {
+ startNode("server1");
+ client = getClient();
+ }
+
+ @AfterMethod public void closeNodes() {
+ client.close();
+ closeAllNodes();
+ }
+
+ protected Client getClient() {
+ return client("server1");
+ }
+
+ @Test
+ public void testCustomScriptBoost() throws Exception {
+ // execute a search before we create an index
+ try {
+ client.prepareSearch().setQuery(termQuery("test", "value")).execute().actionGet();
+ assert false : "should fail";
+ } catch (Exception e) {
+ // ignore, no indices
+ }
+
+ try {
+ client.prepareSearch("test").setQuery(termQuery("test", "value")).execute().actionGet();
+ assert false : "should fail";
+ } catch (Exception e) {
+ // ignore, no indices
+ }
+
+ client.admin().indices().create(createIndexRequest("test")).actionGet();
+ client.index(indexRequest("test").type("type1").id("1")
+ .source(jsonBuilder().startObject().field("test", "value beck").field("num1", 1.0f).endObject())).actionGet();
+ client.index(indexRequest("test").type("type1").id("2")
+ .source(jsonBuilder().startObject().field("test", "value check").field("num1", 2.0f).endObject())).actionGet();
+ client.admin().indices().refresh(refreshRequest()).actionGet();
+
+ logger.info("--- QUERY_THEN_FETCH");
+
+ logger.info("running doc['num1'].value");
+ SearchResponse response = client.search(searchRequest()
+ .searchType(SearchType.QUERY_THEN_FETCH)
+ .source(searchSource().explain(true).query(customScoreQuery(termQuery("test", "value")).script("doc['num1'].value")))
+ ).actionGet();
+
+ assertThat(response.hits().totalHits(), equalTo(2l));
+ logger.info("Hit[0] {} Explanation {}", response.hits().getAt(0).id(), response.hits().getAt(0).explanation());
+ logger.info("Hit[1] {} Explanation {}", response.hits().getAt(1).id(), response.hits().getAt(1).explanation());
+ assertThat(response.hits().getAt(0).id(), equalTo("2"));
+ assertThat(response.hits().getAt(1).id(), equalTo("1"));
+
+ logger.info("running -doc['num1'].value");
+ response = client.search(searchRequest()
+ .searchType(SearchType.QUERY_THEN_FETCH)
+ .source(searchSource().explain(true).query(customScoreQuery(termQuery("test", "value")).script("-doc['num1'].value")))
+ ).actionGet();
+
+ assertThat(response.hits().totalHits(), equalTo(2l));
+ logger.info("Hit[0] {} Explanation {}", response.hits().getAt(0).id(), response.hits().getAt(0).explanation());
+ logger.info("Hit[1] {} Explanation {}", response.hits().getAt(1).id(), response.hits().getAt(1).explanation());
+ assertThat(response.hits().getAt(0).id(), equalTo("1"));
+ assertThat(response.hits().getAt(1).id(), equalTo("2"));
+
+
+ logger.info("running pow(doc['num1'].value, 2)");
+ response = client.search(searchRequest()
+ .searchType(SearchType.QUERY_THEN_FETCH)
+ .source(searchSource().explain(true).query(customScoreQuery(termQuery("test", "value")).script("pow(doc['num1'].value, 2)")))
+ ).actionGet();
+
+ assertThat(response.hits().totalHits(), equalTo(2l));
+ logger.info("Hit[0] {} Explanation {}", response.hits().getAt(0).id(), response.hits().getAt(0).explanation());
+ logger.info("Hit[1] {} Explanation {}", response.hits().getAt(1).id(), response.hits().getAt(1).explanation());
+ assertThat(response.hits().getAt(0).id(), equalTo("2"));
+ assertThat(response.hits().getAt(1).id(), equalTo("1"));
+
+ logger.info("running max(doc['num1'].value, 1)");
+ response = client.search(searchRequest()
+ .searchType(SearchType.QUERY_THEN_FETCH)
+ .source(searchSource().explain(true).query(customScoreQuery(termQuery("test", "value")).script("max(doc['num1'].value, 1d)")))
+ ).actionGet();
+
+ assertThat(response.hits().totalHits(), equalTo(2l));
+ logger.info("Hit[0] {} Explanation {}", response.hits().getAt(0).id(), response.hits().getAt(0).explanation());
+ logger.info("Hit[1] {} Explanation {}", response.hits().getAt(1).id(), response.hits().getAt(1).explanation());
+ assertThat(response.hits().getAt(0).id(), equalTo("2"));
+ assertThat(response.hits().getAt(1).id(), equalTo("1"));
+
+ logger.info("running doc['num1'].value * score");
+ response = client.search(searchRequest()
+ .searchType(SearchType.QUERY_THEN_FETCH)
+ .source(searchSource().explain(true).query(customScoreQuery(termQuery("test", "value")).script("doc['num1'].value * score")))
+ ).actionGet();
+
+ assertThat(response.hits().totalHits(), equalTo(2l));
+ logger.info("Hit[0] {} Explanation {}", response.hits().getAt(0).id(), response.hits().getAt(0).explanation());
+ logger.info("Hit[1] {} Explanation {}", response.hits().getAt(1).id(), response.hits().getAt(1).explanation());
+ assertThat(response.hits().getAt(0).id(), equalTo("2"));
+ assertThat(response.hits().getAt(1).id(), equalTo("1"));
+ }
+}
\ No newline at end of file
diff --git a/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/customscore/CustomScoreSearchTests.yml b/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/customscore/CustomScoreSearchTests.yml
new file mode 100644
index 00000000000..e700f1f35fa
--- /dev/null
+++ b/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/customscore/CustomScoreSearchTests.yml
@@ -0,0 +1,6 @@
+cluster:
+ routing:
+ schedule: 100ms
+index:
+ number_of_shards: 1
+ number_of_replicas: 0