-
Couldn't load subscription status.
- Fork 771
SOLR-16292 : NVector for alternative great-circle distance calculations #940
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks interesting, and I don't quite understand the math behind all of it, but I left some comments about the code itself.
| } | ||
| out[i] = externalVal.substring(start, end); | ||
| start = idx + 1; | ||
| end = externalVal.indexOf(',', start); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Numbers will always be stored as decimals with . and separator as ,? Need to make sure this doesn't mess up in other locales that use comma as the decimal separator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a separator init param and parse doubles using NumberFormat with default locale
solr/core/src/java/org/apache/solr/search/function/distance/NVector.java
Outdated
Show resolved
Hide resolved
solr/core/src/java/org/apache/solr/search/function/distance/NVector.java
Outdated
Show resolved
Hide resolved
solr/core/src/java/org/apache/solr/search/function/distance/NVector.java
Outdated
Show resolved
Hide resolved
|
|
||
| package org.apache.solr.util; | ||
|
|
||
| public class FastInvTrig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you write this code? Is it borrowed from a library? Is it an implementation of something found in a text book?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I wrote this, original in my github repo. It's a Maclaurin series expansion of acos. TABLE stores the coefficients that can be re-used for subsequent calculations, it also reuses x^2 for x^3,x^5 etc. Also initially my implementation required a lot of terms for convergence, until I found this https://stackoverflow.com/questions/20196000/own-asin-function-with-taylor-series-not-accurate which allows for faster convergence near -1,1. I have a benchmark in my repo to show it more performant than Math.acos or FastMath.acos, accuracy appears OK in my tests. NVector with acos faster than Haversine.
|
please also run |
…Field and NVectorFunction to avoid confusion, use better asserts for tests
|
I found your source My results are directionally the same as yours - One concern for comparison that I have would be that the current Lucene implementation (which is only 2x slower than yours) has an upper error bound of 40cm. I think we can get there with your series expansions by adding additional terms, but I don't remember my calculus well enough to know how many we need. If we do that, how much does performance suffer? Are we still competitive? You're currently at 10m, which I suspect might be too large of a delta to be useful for the applications that I am familiar with. |
|
Thanks for looking into this more @madrob . I wasn't aware of SloppyMath. If we assume Math.acos correct, then to get the same accuracy with FastInvTrig to within 40cm, I've tested it takes 17 terms, which after running a benchmark means that: So then: SloppyMath.HaversinSortKey is 1.9x slower than FastInvTrig with 10 terms (as you found) However I also noticed that SloppyMath has an asin implementation, and with pi/2-asin(x) = acos(x) after adding this into the benchmark, using nvector and this identity ^ for acos then: FastInvTrig with 17 terms is 1.3x slower than SloppyMath.asin ! The caveat is that SloppyMath.asin uses more memory for caching values I think. So I'm wondering now whether to abandon the FastInvTrig series expansion, and use SloppyMath.asin for NVector? What do you think ? |
|
I'm a little confused as to what the sloppyAsin results are... From your tests, we should still prefer NVector with MacLaurian expansion at 17 terms over using Sloppy Haversine, right? Is Sloppy Asin using NVector with no series expansion for the trig and instead the lookup tables from Sloppy Math? Which gives us the 40cm accuracy at almost the original 10 term expansion performance level? |
|
Can we do a similar trick to split the calculation to get an n-vector sort key and an n-vector meters and get even more speedup for the cases where we don't care about absolute distances? |
We calculate the Great circle distance as We also know So I compared FastInvTrig.acos (with 17 terms) with SloppyMath.asin, and found that FastInvTrig.acos with 17 terms is 1.3x slower than SloppyMath.asin. This is what I meant with FastInvTrigBenchmark.sloppyAsin so SloppyMath.asin, at the required precision, is faster than FastInvTrig.acos, So I was thinking of abandoning FastInvTrig.acos in favour of SloppyMath.asin, what do you think?
Yes! From looking at the acos plot ( https://www.wolframalpha.com/input?i2d=true&i=acos%5C%2840%29x%5C%2841%29 ) it's a 1 to 1 function, so well suited for comparison. I did a quick test which confirmed that the dot product is enough for comparison between values (which is all we need to do, as we cache the nvectors in the NVectorField). So we then have the following benchmark (with NVectorSortKey as this comparison and the fastest) : So in Solr perhaps we can use NVectorSortKey for sort comparisons then. |
|
Ok, I get it now. Yes, let's do the SloppyMath.asin approach, splitting out the sort key (I would call it dot product and add comments about why we can use it as sort key instead of calling the method |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the core of the idea is good here. Big thing missing is an update to the ref guide, probably function-queries.adoc or spatial-search.adoc?
Another thought I had was how exactly do we expect users to use this. If they're still going to be providing indexable data in lat/long and also expecting lat/long for output information, then will this really be faster than using haversine? Or does it move the computation to the indexing side when we only have to do it once, so over multiple queries the total time taken gets reduced...
| import java.util.Locale; | ||
| import java.util.Map; | ||
|
|
||
| public class NVectorField extends CoordinateFieldType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this extend from PointType instead? is there more that we get from it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NVectorField is different I think to PointType. NVector replaces lat/lon and overrides most of the important methods in PointType anyway. NVectorField would need a specialized getSpecializedRangeQuery too (not sure how to implement this for nvector yet)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, fair enough about not extending PointType. As for range queries... does NVector efficiently support shape intersections? I'm under the impression that it doesn't - that's part of the way that we get such a speed up on distance...
| * @return An array of the values that make up the point (aka vector) | ||
| * @throws SolrException if the dimension specified does not match the number found | ||
| */ | ||
| public static String[] parseCommaSeparatedList(String externalVal, int dimension, String separator) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have some unit tests for this? Parsing is one of those thing that seems easy but there are always edge cases and it's easy to introduce regressions.
use Math for radian calc
When i get a moment I'll ad something to spatial-search.adoc perhaps?
This moves most of the calculation to the indexing side. We then only need to calculate an n-vector for the input lat/lon. The sorting can then be done on the dot-product alone. Users can optionally index spatial data in multiple formats (e.g. LanLonSpatialField and NVectorField) should they find that a performance boost. N-Vector provides faster comparison (and great circle distance calculation) than haversine, additionally without caveats, or accuracy degradation, for calculations at poles/equator etc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few more comments, mainly about documentation of the feature.
| import java.util.Locale; | ||
| import java.util.Map; | ||
|
|
||
| public class NVectorField extends CoordinateFieldType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, fair enough about not extending PointType. As for range queries... does NVector efficiently support shape intersections? I'm under the impression that it doesn't - that's part of the way that we get such a speed up on distance...
| }; | ||
| } | ||
|
|
||
| public static double[] NVectorToLatLong(double[] n) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: method names should start with lowercase (here and others)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated
|
|
||
| import static org.locationtech.spatial4j.distance.DistanceUtils.EARTH_MEAN_RADIUS_KM; | ||
|
|
||
| public class NVectorUtil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add javadoc for the class and methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
|
|
||
| public static double[] NVectorToLatLong(double[] n) { | ||
| return new double[] { | ||
| Math.asin(n[2]) * (180 / Math.PI), Math.atan(n[1] / n[0]) * (180 / Math.PI) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Math.toDegrees
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed
| req( | ||
| "defType", "lucene", | ||
| "q", "*:*", | ||
| "nvd", "nvdist(52.01966071979866, -0.4983083573742952,nvector)", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed where 'nvdist' is defined as a function. Do we need to add to ValueSourceParser static init block?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added to ValueSourceParser
- lc method names
|
This PR had no visible activity in the past 60 days, labeling it as stale. Any new activity will remove the stale label. To attract more reviewers, please tag someone or notify the dev@solr.apache.org mailing list. Thank you for your contribution! |
|
This PR is now closed due to 60 days of inactivity after being marked as stale. Re-opening this PR is still possible, in which case it will be marked as active again. |
https://issues.apache.org/jira/browse/SOLR-16292
Description
N-Vector is a three-parameter representation that can be used to calculate the great-circle distance (assuming a spherical Earth).
It uses FastInvTrig, which for small distances it compares well with Math.acos. NVector is a faster way of calculating the great-circle distance than Haversine.
Solution
Store N-Vectors in solr index via CoordinateFieldType with 3 values for the nvector into single value double subfields, use java Math class for indexing these
Use an maclaurin approximation for acos for calculating great-circle distance at query-time via a function query
Tests
Tests for the FastInvTrigTest impl to compare it's acos with Math.acos.
Tests for NVectorUtil, NVectorDist , latLongToNVector and NVectorToLatLong
Tests for indexing N-Vectors and calculating the great-circle distance via the function query.
Checklist
Please review the following and check all that apply:
mainbranch../gradlew check.