From 1ac71a2aaffb4fecc60f16889f9cd8234b723228 Mon Sep 17 00:00:00 2001 From: Yonik Seeley Date: Fri, 20 Nov 2009 15:09:37 +0000 Subject: [PATCH] SOLR-1574: simplify specification of builtin functions, add many from java Math git-svn-id: https://svn.apache.org/repos/asf/lucene/solr/trunk@882596 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 + .../apache/solr/search/ValueSourceParser.java | 372 +++++++++++++++--- .../solr/search/function/DegreeFunction.java | 63 --- .../solr/search/function/RadianFunction.java | 79 ---- .../search/function/TestFunctionQuery.java | 50 ++- 5 files changed, 376 insertions(+), 190 deletions(-) delete mode 100644 src/java/org/apache/solr/search/function/DegreeFunction.java delete mode 100644 src/java/org/apache/solr/search/function/RadianFunction.java diff --git a/CHANGES.txt b/CHANGES.txt index 1fd37f0a36f..93aef610ceb 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -42,6 +42,8 @@ New Features fielded queries, improved proximity boosting, and improved stopword handling. (yonik) +* SOLR-1574: Add many new functions from java Math (e.g. sin, cos) (yonik) + Optimizations ---------------------- diff --git a/src/java/org/apache/solr/search/ValueSourceParser.java b/src/java/org/apache/solr/search/ValueSourceParser.java index 563ab214888..4f8f4e4c470 100755 --- a/src/java/org/apache/solr/search/ValueSourceParser.java +++ b/src/java/org/apache/solr/search/ValueSourceParser.java @@ -19,6 +19,7 @@ package org.apache.solr.search; import org.apache.lucene.index.IndexReader; import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.search.Query; +import org.apache.lucene.search.Searcher; import org.apache.solr.common.SolrException; import org.apache.solr.common.util.NamedList; import org.apache.solr.schema.DateField; @@ -47,13 +48,11 @@ import java.util.Map; * Intented usage is to create pluggable, named functions for use in function queries. */ public abstract class ValueSourceParser implements NamedListInitializedPlugin { - /** * Initialize the plugin. */ public void init(NamedList args) {} - /** * Parse the user input into a ValueSource. * @@ -72,6 +71,17 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin { return standardValueSourceParsers.put(name, p); } + /** Adds a new parser for the name and returns any existing one that was overriden. + * This is not thread safe. + */ + public static ValueSourceParser addParser(NamedParser p) { + return standardValueSourceParsers.put(p.name(), p); + } + + private static void alias(String source, String dest) { + standardValueSourceParsers.put(dest, standardValueSourceParsers.get(source)); + } + static { addParser("ord", new ValueSourceParser() { public ValueSource parse(FunctionQParser fp) throws ParseException { @@ -130,13 +140,6 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin { return new TopValueSource(new ScaleFloatFunction(source, min, max)); } }); - addParser("pow", new ValueSourceParser() { - public ValueSource parse(FunctionQParser fp) throws ParseException { - ValueSource a = fp.parseValueSource(); - ValueSource b = fp.parseValueSource(); - return new PowFloatFunction(a, b); - } - }); addParser("div", new ValueSourceParser() { public ValueSource parse(FunctionQParser fp) throws ParseException { ValueSource a = fp.parseValueSource(); @@ -154,34 +157,7 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin { return new RangeMapFloatFunction(source, min, max, target, def); } }); - addParser("sqrt", new ValueSourceParser() { - public ValueSource parse(FunctionQParser fp) throws ParseException { - ValueSource source = fp.parseValueSource(); - return new SimpleFloatFunction(source) { - protected String name() { - return "sqrt"; - } - protected float func(int doc, DocValues vals) { - return (float) Math.sqrt(vals.floatVal(doc)); - } - }; - } - }); - addParser("log", new ValueSourceParser() { - public ValueSource parse(FunctionQParser fp) throws ParseException { - ValueSource source = fp.parseValueSource(); - return new SimpleFloatFunction(source) { - protected String name() { - return "log"; - } - - protected float func(int doc, DocValues vals) { - return (float) Math.log10(vals.floatVal(doc)); - } - }; - } - }); addParser("abs", new ValueSourceParser() { public ValueSource parse(FunctionQParser fp) throws ParseException { ValueSource source = fp.parseValueSource(); @@ -191,7 +167,7 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin { } protected float func(int doc, DocValues vals) { - return (float) Math.abs(vals.floatVal(doc)); + return Math.abs(vals.floatVal(doc)); } }; } @@ -202,12 +178,16 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin { return new SumFloatFunction(sources.toArray(new ValueSource[sources.size()])); } }); + alias("sum","add"); + addParser("product", new ValueSourceParser() { public ValueSource parse(FunctionQParser fp) throws ParseException { List sources = fp.parseValueSourceList(); return new ProductFloatFunction(sources.toArray(new ValueSource[sources.size()])); } }); + alias("product","mul"); + addParser("sub", new ValueSourceParser() { public ValueSource parse(FunctionQParser fp) throws ParseException { ValueSource a = fp.parseValueSource(); @@ -276,16 +256,114 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin { } }); - - addParser("rad", new ValueSourceParser() { - public ValueSource parse(FunctionQParser fp) throws ParseException { - return new RadianFunction(fp.parseValueSource()); + addParser(new DoubleParser("rad") { + public double func(int doc, DocValues vals) { + return Math.toRadians(vals.doubleVal(doc)); } }); - - addParser("deg", new ValueSourceParser() { - public ValueSource parse(FunctionQParser fp) throws ParseException { - return new DegreeFunction(fp.parseValueSource()); + addParser(new DoubleParser("deg") { + public double func(int doc, DocValues vals) { + return Math.toDegrees(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("sqrt") { + public double func(int doc, DocValues vals) { + return Math.sqrt(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("cbrt") { + public double func(int doc, DocValues vals) { + return Math.cbrt(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("log") { + public double func(int doc, DocValues vals) { + return Math.log10(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("ln") { + public double func(int doc, DocValues vals) { + return Math.log(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("exp") { + public double func(int doc, DocValues vals) { + return Math.exp(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("sin") { + public double func(int doc, DocValues vals) { + return Math.sin(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("cos") { + public double func(int doc, DocValues vals) { + return Math.cos(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("tan") { + public double func(int doc, DocValues vals) { + return Math.tan(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("asin") { + public double func(int doc, DocValues vals) { + return Math.asin(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("acos") { + public double func(int doc, DocValues vals) { + return Math.acos(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("atan") { + public double func(int doc, DocValues vals) { + return Math.atan(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("sinh") { + public double func(int doc, DocValues vals) { + return Math.sinh(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("cosh") { + public double func(int doc, DocValues vals) { + return Math.cosh(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("tanh") { + public double func(int doc, DocValues vals) { + return Math.tanh(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("ceil") { + public double func(int doc, DocValues vals) { + return Math.ceil(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("floor") { + public double func(int doc, DocValues vals) { + return Math.floor(vals.doubleVal(doc)); + } + }); + addParser(new DoubleParser("rint") { + public double func(int doc, DocValues vals) { + return Math.rint(vals.doubleVal(doc)); + } + }); + addParser(new Double2Parser("pow") { + public double func(int doc, DocValues a, DocValues b) { + return Math.pow(a.doubleVal(doc), b.doubleVal(doc)); + } + }); + addParser(new Double2Parser("hypot") { + public double func(int doc, DocValues a, DocValues b) { + return Math.hypot(a.doubleVal(doc), b.doubleVal(doc)); + } + }); + addParser(new Double2Parser("atan2") { + public double func(int doc, DocValues a, DocValues b) { + return Math.atan2(a.doubleVal(doc), b.doubleVal(doc)); } }); @@ -319,9 +397,21 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin { } }); addParser("ms", new DateValueSourceParser()); + + + addParser("pi", new ValueSourceParser() { + public ValueSource parse(FunctionQParser fp) throws ParseException { + return new DoubleConstValueSource(Math.PI); + } + }); + addParser("e", new ValueSourceParser() { + public ValueSource parse(FunctionQParser fp) throws ParseException { + return new DoubleConstValueSource(Math.E); + } + }); } - protected void splitSources(int dim, List sources, List dest1, List dest2) { + private static void splitSources(int dim, List sources, List dest1, List dest2) { //Get dim value sources for the first vector for (int i = 0; i < dim; i++) { dest1.add(sources.get(i)); @@ -483,3 +573,193 @@ class LongConstValueSource extends ValueSource { return this.constant == other.constant; } } + +// Private for now - we need to revisit how to handle typing in function queries +class DoubleConstValueSource extends ValueSource { + final double constant; + + public DoubleConstValueSource(double constant) { + this.constant = constant; + } + + public String description() { + return "const(" + constant + ")"; + } + + public DocValues getValues(Map context, IndexReader reader) throws IOException { + return new DocValues() { + public float floatVal(int doc) { + return (float)constant; + } + + public int intVal(int doc) { + return (int) constant; + } + + public long longVal(int doc) { + return (long)constant; + } + + public double doubleVal(int doc) { + return constant; + } + + public String strVal(int doc) { + return Double.toString(constant); + } + + public String toString(int doc) { + return description(); + } + }; + } + + public int hashCode() { + long bits = Double.doubleToRawLongBits(constant); + return (int)(bits ^ (bits >>> 32)); + } + + public boolean equals(Object o) { + if (DoubleConstValueSource.class != o.getClass()) return false; + DoubleConstValueSource other = (DoubleConstValueSource) o; + return this.constant == other.constant; + } +} + + +abstract class NamedParser extends ValueSourceParser { + private final String name; + public NamedParser(String name) { + this.name = name; + } + public String name() { + return name; + } +} + + +abstract class DoubleParser extends NamedParser { + public DoubleParser(String name) { + super(name); + } + + public abstract double func(int doc, DocValues vals); + + public ValueSource parse(FunctionQParser fp) throws ParseException { + return new Function(fp.parseValueSource()); + } + + class Function extends SingleFunction { + public Function(ValueSource source) { + super(source); + } + + public String name() { + return DoubleParser.this.name(); + } + + @Override + public DocValues getValues(Map context, IndexReader reader) throws IOException { + final DocValues vals = source.getValues(context, reader); + return new DocValues() { + public float floatVal(int doc) { + return (float)doubleVal(doc); + } + public int intVal(int doc) { + return (int)doubleVal(doc); + } + public long longVal(int doc) { + return (long)doubleVal(doc); + } + public double doubleVal(int doc) { + return func(doc, vals); + } + public String strVal(int doc) { + return Double.toString(doubleVal(doc)); + } + public String toString(int doc) { + return name() + '(' + vals.toString(doc) + ')'; + } + }; + } + } +} + + +abstract class Double2Parser extends NamedParser { + public Double2Parser(String name) { + super(name); + } + + public abstract double func(int doc, DocValues a, DocValues b); + + public ValueSource parse(FunctionQParser fp) throws ParseException { + return new Function(fp.parseValueSource(), fp.parseValueSource()); + } + + class Function extends ValueSource { + private final ValueSource a; + private final ValueSource b; + + /** + * @param a the base. + * @param b the exponent. + */ + public Function(ValueSource a, ValueSource b) { + this.a = a; + this.b = b; + } + + public String description() { + return name() + "(" + a.description() + "," + b.description() + ")"; + } + + public DocValues getValues(Map context, IndexReader reader) throws IOException { + final DocValues aVals = a.getValues(context, reader); + final DocValues bVals = b.getValues(context, reader); + return new DocValues() { + public float floatVal(int doc) { + return (float)doubleVal(doc); + } + public int intVal(int doc) { + return (int)doubleVal(doc); + } + public long longVal(int doc) { + return (long)doubleVal(doc); + } + public double doubleVal(int doc) { + return func(doc, aVals, bVals); + } + public String strVal(int doc) { + return Double.toString(doubleVal(doc)); + } + public String toString(int doc) { + return name() + '(' + aVals.toString(doc) + ',' + bVals.toString(doc) + ')'; + } + }; + } + + @Override + public void createWeight(Map context, Searcher searcher) throws IOException { + a.createWeight(context,searcher); + b.createWeight(context,searcher); + } + + public int hashCode() { + int h = a.hashCode(); + h ^= (h << 13) | (h >>> 20); + h += b.hashCode(); + h ^= (h << 23) | (h >>> 10); + h += name().hashCode(); + return h; + } + + public boolean equals(Object o) { + if (this.getClass() != o.getClass()) return false; + Function other = (Function)o; + return this.a.equals(other.a) + && this.b.equals(other.b); + } + } + +} diff --git a/src/java/org/apache/solr/search/function/DegreeFunction.java b/src/java/org/apache/solr/search/function/DegreeFunction.java deleted file mode 100644 index 02c6e00120a..00000000000 --- a/src/java/org/apache/solr/search/function/DegreeFunction.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.apache.solr.search.function; - -import org.apache.lucene.index.IndexReader; - -import java.util.Map; -import java.io.IOException; - - -/** - * - * - **/ -public class DegreeFunction extends ValueSource{ - protected ValueSource valSource; - - public DegreeFunction(ValueSource valSource) { - this.valSource = valSource; - } - - public String description() { - return "deg(" + valSource.description() + ')'; - } - - public DocValues getValues(Map context, IndexReader reader) throws IOException { - final DocValues dv = valSource.getValues(context, reader); - return new DocValues() { - public float floatVal(int doc) { - return (float) doubleVal(doc); - } - - public int intVal(int doc) { - return (int) doubleVal(doc); - } - - public long longVal(int doc) { - return (long) doubleVal(doc); - } - - public double doubleVal(int doc) { - return Math.toDegrees(dv.doubleVal(doc)); - } - - public String strVal(int doc) { - return Double.toString(doubleVal(doc)); - } - - public String toString(int doc) { - return description() + '=' + floatVal(doc); - } - }; - } - - public boolean equals(Object o) { - if (o.getClass() != DegreeFunction.class) return false; - DegreeFunction other = (DegreeFunction) o; - return description().equals(other.description()) && valSource.equals(other.valSource); - } - - public int hashCode() { - return description().hashCode() + valSource.hashCode(); - }; - -} diff --git a/src/java/org/apache/solr/search/function/RadianFunction.java b/src/java/org/apache/solr/search/function/RadianFunction.java deleted file mode 100644 index e51cfe1a60b..00000000000 --- a/src/java/org/apache/solr/search/function/RadianFunction.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.apache.solr.search.function; -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF 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. - */ - -import org.apache.lucene.index.IndexReader; - -import java.io.IOException; -import java.util.Map; - - -/** - * Take a ValueSourc and produce convert the number to radians and - * return that value - */ -public class RadianFunction extends ValueSource { - protected ValueSource valSource; - - public RadianFunction(ValueSource valSource) { - this.valSource = valSource; - } - - public String description() { - return "rad(" + valSource.description() + ')'; - } - - public DocValues getValues(Map context, IndexReader reader) throws IOException { - final DocValues dv = valSource.getValues(context, reader); - return new DocValues() { - public float floatVal(int doc) { - return (float) doubleVal(doc); - } - - public int intVal(int doc) { - return (int) doubleVal(doc); - } - - public long longVal(int doc) { - return (long) doubleVal(doc); - } - - public double doubleVal(int doc) { - return Math.toRadians(dv.doubleVal(doc)); - } - - public String strVal(int doc) { - return Double.toString(doubleVal(doc)); - } - - public String toString(int doc) { - return description() + '=' + floatVal(doc); - } - }; - } - - public boolean equals(Object o) { - if (o.getClass() != RadianFunction.class) return false; - RadianFunction other = (RadianFunction) o; - return description().equals(other.description()) && valSource.equals(other.valSource); - } - - public int hashCode() { - return description().hashCode() + valSource.hashCode(); - }; - -} diff --git a/src/test/org/apache/solr/search/function/TestFunctionQuery.java b/src/test/org/apache/solr/search/function/TestFunctionQuery.java index 7e9cf92ffdd..f2f53720f8e 100755 --- a/src/test/org/apache/solr/search/function/TestFunctionQuery.java +++ b/src/test/org/apache/solr/search/function/TestFunctionQuery.java @@ -278,7 +278,7 @@ public class TestFunctionQuery extends AbstractSolrTestCase { } } - public void testGeneral() { + public void testGeneral() throws Exception { assertU(adoc("id","1", "a_tdt","2009-08-31T12:10:10.123Z", "b_tdt","2009-08-31T12:10:10.124Z")); assertU(adoc("id","2")); assertU(commit()); // create more than one segment @@ -326,9 +326,12 @@ public class TestFunctionQuery extends AbstractSolrTestCase { q ="{!func}sub(div(sum(0.0,product(1,query($qq))),1),0)"; assertQ(req("fl","*,score","q", q, "qq","text:batman", "fq",fq), "//float[@name='score']<'1.0'"); assertQ(req("fl","*,score","q", q, "qq","text:superman", "fq",fq), "//float[@name='score']>'1.0'"); + + doTestDegreeRads(); + doTestFuncs(); } - public void testDegreeRads() throws Exception { + public void doTestDegreeRads() throws Exception { assertU(adoc("id", "1", "x_td", "0", "y_td", "0")); assertU(adoc("id", "2", "x_td", "90", "y_td", String.valueOf(Math.PI / 2))); assertU(adoc("id", "3", "x_td", "45", "y_td", String.valueOf(Math.PI / 4))); @@ -343,4 +346,47 @@ public class TestFunctionQuery extends AbstractSolrTestCase { assertQ(req("fl", "*,score", "q", "{!func}deg(y_td)", "fq", "id:2"), "//float[@name='score']='90.0'"); assertQ(req("fl", "*,score", "q", "{!func}deg(y_td)", "fq", "id:3"), "//float[@name='score']='45.0'"); } + + public void dofunc(String func, double val) throws Exception { + // String sval = Double.toString(val); + String sval = Float.toString((float)val); + + assertQ(req("fl", "*,score", "defType","func", "fq","id:1", "q",func), + "//float[@name='score']='" + sval + "'"); + } + + public void doTestFuncs() throws Exception { + assertU(adoc("id", "1", "foo_d", "9")); + assertU(commit()); + + dofunc("1.0", 1.0); + dofunc("e()", Math.E); + dofunc("pi()", Math.PI); + dofunc("add(2,3)", 2+3); + dofunc("mul(2,3)", 2*3); + dofunc("rad(45)", Math.toRadians(45)); + dofunc("deg(.5)", Math.toDegrees(.5)); + dofunc("sqrt(9)", Math.sqrt(9)); + dofunc("cbrt(8)", Math.cbrt(8)); + dofunc("log(100)", Math.log10(100)); + dofunc("ln(3)", Math.log(3)); + dofunc("exp(1)", Math.exp(1)); + dofunc("sin(.5)", Math.sin(.5)); + dofunc("cos(.5)", Math.cos(.5)); + dofunc("tan(.5)", Math.tan(.5)); + dofunc("asin(.5)", Math.asin(.5)); + dofunc("acos(.5)", Math.acos(.5)); + dofunc("atan(.5)", Math.atan(.5)); + dofunc("sinh(.5)", Math.sinh(.5)); + dofunc("cosh(.5)", Math.cosh(.5)); + dofunc("tanh(.5)", Math.tanh(.5)); + dofunc("ceil(2.3)", Math.ceil(2.3)); + dofunc("floor(2.3)", Math.floor(2.3)); + dofunc("rint(2.3)", Math.rint(2.3)); + dofunc("pow(2,0.5)", Math.pow(2,0.5)); + dofunc("hypot(3,4)", Math.hypot(3,4)); + dofunc("atan2(.25,.5)", Math.atan2(.25,.5)); + } + + } \ No newline at end of file