/* * Copyright (c) 2011 LinkedIn, Inc * * Licensed 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 com.flaptor.indextank.index.scorer; import java.util.Random; import junit.framework.Assert; import org.junit.Test; public class ScoreMathTest { private static boolean PRINT = false; // old km distance formula private double km(double x1, double y1, double x2, double y2) { return 111.133 * Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)); } // Great Circle formula: http://en.wikipedia.org/wiki/Great-circle_distance private double kmgreatc(double x1, double y1, double x2, double y2) { x1 = Math.toRadians(x1); y1 = Math.toRadians(y1); x2 = Math.toRadians(x2); y2 = Math.toRadians(y2); return 6372.8 * Math.acos(Math.sin(x1)*Math.sin(x2)+Math.cos(x1)*Math.cos(x2)*Math.cos(Math.abs(y1 - y2))); } // Great Circle formula: http://en.wikipedia.org/wiki/Great-circle_distance private double milesgreatc(double x1, double y1, double x2, double y2) { x1 = Math.toRadians(x1); y1 = Math.toRadians(y1); x2 = Math.toRadians(x2); y2 = Math.toRadians(y2); return 3959.87433 * Math.acos(Math.sin(x1)*Math.sin(x2)+Math.cos(x1)*Math.cos(x2)*Math.cos(Math.abs(y1 - y2))); } // old miles distance formula private double miles(double x1, double y1, double x2, double y2) { return 69.055 * Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)); } // @Test public void testTimeKm() { Random r = new Random(); double x1 = r.nextInt(89) + r.nextDouble(); double y1 = r.nextInt(180) + r.nextDouble(); double x2 = r.nextInt(89) + r.nextDouble(); double y2 = r.nextInt(180) + r.nextDouble(); long oldTime = 0; long newTime = 0; //(just in case of compiler and caches optimizations) for(long i = 0; i < 100; i++) { km(x1, y1, x2, y2); ScoreMath.km(x1, y1, x2, y2); } for(int j = 0; j < 12; j++) { long times = 100000000L; // take care whether you measure new function and then old function or vice, because times changes /* TIME for new KM function*/ long t2 = System.currentTimeMillis(); for (long i = 0; i < times; i++) { ScoreMath.km(x1, y1, x2, y2); } long t3 = System.currentTimeMillis(); /* TIME for new KM function*/ /* TIME for old KM function*/ long t0 = System.currentTimeMillis(); for (long i = 0; i < times; i++) { km(x1, y1, x2, y2); } long t1 = System.currentTimeMillis(); /* TIME for old KM function*/ double expected = t1 - t0; oldTime += expected; double actual = t3 - t2; newTime += actual; System.out.printf("KM: Old formula %s, New formula %s\n", expected, actual); Assert.assertTrue("New km formula is too slow", (double)actual / (double)expected < 10d); } if (PRINT) System.out.printf("KM TIME: OldTime: %6d --- NewTime: %6d --- %3.2f times slower\n", oldTime, newTime, (double)newTime/(double)oldTime); } // @Test public void testTimeMiles() { Random r = new Random(); double x1 = r.nextInt(89) + r.nextDouble(); double y1 = r.nextInt(180) + r.nextDouble(); double x2 = r.nextInt(89) + r.nextDouble(); double y2 = r.nextInt(180) + r.nextDouble(); //(just in case of compiler and caches optimizations) for(long i = 0; i < 100; i++) { miles(x1, y1, x2, y2); ScoreMath.miles(x1, y1, x2, y2); } long oldTime = 0; long newTime = 0; for(int j = 0; j < 10; j++) { long times = 100000000L; // take care whether you measure new function and then old function or vice, because times changes /* TIME for new MILES function*/ long t2 = System.currentTimeMillis(); for (long i = 0; i < times; i++) { ScoreMath.miles(x1, y1, x2, y2); } long t3 = System.currentTimeMillis(); /* TIME for new MILES function*/ /* TIME for old MILES function*/ long t0 = System.currentTimeMillis(); for (long i = 0; i < times; i++) { miles(x1, y1, x2, y2); } long t1 = System.currentTimeMillis(); /* TIME for old MILES function*/ double expected = t1 - t0; oldTime += expected; double actual = t3 - t2; newTime += actual; System.out.printf("MILES: Old formula %s, New formula %s\n", expected, actual); Assert.assertTrue("New miles formula is too slow", (double)actual / (double)expected < 1000d); } if (PRINT) System.out.printf("MILES TIME: OldTime: %6d --- NewTime: %6d --- %3.2f times slower\n", oldTime, newTime, (double)newTime/(double)oldTime); } @Test public void testPrecisionKm() { long times = 100000L; int goodEnough= 0; int betterThanOld= 0; for(int j = 0; j < times; j++) { Random r = new Random(); double x1 = (r.nextBoolean()? r.nextInt(89) : -r.nextInt(90)) + r.nextDouble(); double y1 = r.nextInt(180) + r.nextDouble(); double x2 = (r.nextBoolean()? r.nextInt(89) : -r.nextInt(90)) + r.nextDouble(); double y2 = r.nextInt(180) + r.nextDouble(); double kmold = km(x1, y1, x2, y2); double kmnew = ScoreMath.km(x1, y1, x2, y2); double kmgreatc = kmgreatc(x1, y1, x2, y2); // System.out.printf("%10.2f %10.2f %10.2f\n", kmgreatc, kmnew, kmold); Assert.assertTrue("Distance cant be NaN", !Double.isNaN(kmnew)); if (Math.abs(kmgreatc - kmnew) <= Math.abs(kmgreatc - kmold)) { betterThanOld++; } if (Math.abs((kmgreatc - kmnew)/kmgreatc) < 0.15) { goodEnough++; } } if (PRINT) System.out.printf("KM RANDOM: BetterThanOld: %6.4f --- GoodEnough: %6.4f\n", (double)betterThanOld / (double)times, (double)goodEnough / (double)times); Assert.assertTrue((times - betterThanOld) + " times old formula was better", (double)betterThanOld/(double)times >= 0.9); Assert.assertTrue((double)goodEnough/(double)times >= 0.85); } @Test public void testPrecisionMiles() { long times = 100000L; int goodEnough= 0; int betterThanOld= 0; for(int j = 0; j < times; j++) { Random r = new Random(); double x1 = (r.nextBoolean()? r.nextInt(89) : -r.nextInt(90)) + r.nextDouble(); double y1 = r.nextInt(180) + r.nextDouble(); double x2 = (r.nextBoolean()? r.nextInt(89) : -r.nextInt(90)) + r.nextDouble(); double y2 = r.nextInt(180) + r.nextDouble(); double milesold = miles(x1, y1, x2, y2); double milesnew = ScoreMath.miles(x1, y1, x2, y2); double milesgreatc = milesgreatc(x1, y1, x2, y2); // System.out.printf("%10.2f %10.2f %10.2f\n", kmgreatc, kmnew, kmold); Assert.assertTrue("Distance cant be NaN", !Double.isNaN(milesnew)); if (Math.abs(milesgreatc - milesnew) <= Math.abs(milesgreatc - milesold)) { betterThanOld++; } if (Math.abs((milesgreatc - milesnew)/milesgreatc) < 0.15) { goodEnough++; } } if (PRINT) System.out.printf("MILES RANDOM: BetterThanOld: %6.4f --- GoodEnough: %6.4f\n", (double)betterThanOld / (double)times, (double)goodEnough / (double)times); Assert.assertTrue((times - betterThanOld) + " times old formula was better", (double)betterThanOld/(double)times >= 0.9); Assert.assertTrue("New formula is not good enough", (double)goodEnough/(double)times >= 0.85); } @Test public void testPrecisionKmAroundNewYork() { long times = 100000L; int goodEnough= 0; int betterThanOld= 0; for(int j = 0; j < times; j++) { Random r = new Random(); double x1 = 40.6d + r.nextDouble(); double y1 = -73.80 - r.nextDouble(); double x2 = 40.78d - r.nextDouble(); double y2 = -74.05 + r.nextDouble(); double kmold = km(x1, y1, x2, y2); double kmnew = ScoreMath.km(x1, y1, x2, y2); double kmgreatc = kmgreatc(x1, y1, x2, y2); // System.out.printf("%10.2f %10.2f %10.2f\n", kmgreatc, kmnew, kmold); Assert.assertTrue("Distance cant be NaN", !Double.isNaN(kmnew)); if (Math.abs(kmgreatc - kmnew) <= Math.abs(kmgreatc - kmold)) { betterThanOld++; } if (Math.abs((kmgreatc - kmnew)/kmgreatc) < 0.05) { goodEnough++; } } if (PRINT) System.out.printf("KM NEW_YORK: BetterThanOld: %6.4f --- GoodEnough: %6.4f\n", (double)betterThanOld / (double)times, (double)goodEnough / (double)times); Assert.assertTrue((times - betterThanOld) + " times old formula was better", (double)betterThanOld/(double)times >= 0.95); Assert.assertTrue((double)goodEnough/(double)times >= 0.95); } @Test public void testPrecisionMilesAroundNewYork() { long times = 100000L; int goodEnough= 0; int betterThanOld= 0; for(int j = 0; j < times; j++) { Random r = new Random(); double x1 = 40.6d + r.nextDouble(); double y1 = -73.80 - r.nextDouble(); double x2 = 40.78d - r.nextDouble(); double y2 = -74.05 + r.nextDouble(); double milesold = miles(x1, y1, x2, y2); double milesnew = ScoreMath.miles(x1, y1, x2, y2); double milesgreatc = milesgreatc(x1, y1, x2, y2); // System.out.printf("%10.2f %10.2f %10.2f\n", kmgreatc, kmnew, kmold); Assert.assertTrue("Distance cant be NaN", !Double.isNaN(milesnew)); if (Math.abs(milesgreatc - milesnew) <= Math.abs(milesgreatc - milesold)) { betterThanOld++; } if (Math.abs((milesgreatc - milesnew)/milesgreatc) < 0.05) { goodEnough++; } }if (PRINT) if (PRINT) System.out.printf("MILES NEW_YORK: BetterThanOld: %6.4f --- GoodEnough: %6.4f\n", (double)betterThanOld / (double)times, (double)goodEnough / (double)times); Assert.assertTrue((times - betterThanOld) + " times old formula was better", (double)betterThanOld/(double)times >= 0.95); Assert.assertTrue((double)goodEnough/(double)times >= 0.95); } @Test public void testPrecisionKmShortDistance() { long times = 100000L; int goodEnough= 0; int betterThanOld= 0; for(int j = 0; j < times; j++) { Random r = new Random(); int latitude = r.nextInt(90); int longitude = r.nextInt(180); double x1 = latitude + r.nextDouble(); double y1 = longitude - r.nextDouble(); double x2 = latitude - r.nextDouble(); double y2 = longitude + r.nextDouble(); double kmold = km(x1, y1, x2, y2); double kmnew = ScoreMath.km(x1, y1, x2, y2); double kmgreatc = kmgreatc(x1, y1, x2, y2); // System.out.printf("%10.2f %10.2f %10.2f\n", kmgreatc, kmnew, kmold); Assert.assertTrue("Distance cant be NaN", !Double.isNaN(kmnew)); if (Math.abs(kmgreatc - kmnew) <= Math.abs(kmgreatc - kmold)) { betterThanOld++; } if (Math.abs((kmgreatc - kmnew)/kmgreatc) < 0.05) { goodEnough++; } } if (PRINT) System.out.printf("KM NEAR: BetterThanOld: %6.4f --- GoodEnough: %6.4f\n", (double)betterThanOld / (double)times, (double)goodEnough / (double)times); Assert.assertTrue((times - betterThanOld) + " times old formula was better", (double)betterThanOld/(double)times >= 0.90); Assert.assertTrue((double)goodEnough/(double)times >= 0.95); } @Test public void testPrecisionMilesShortDistance() { long times = 100000L; int goodEnough= 0; int betterThanOld= 0; for(int j = 0; j < times; j++) { Random r = new Random(); int latitude = r.nextInt(90); int longitude = r.nextInt(180); double x1 = latitude + r.nextDouble(); double y1 = longitude - r.nextDouble(); double x2 = latitude - r.nextDouble(); double y2 = longitude + r.nextDouble(); double milesold = miles(x1, y1, x2, y2); double milesnew = ScoreMath.miles(x1, y1, x2, y2); double milesgreatc = milesgreatc(x1, y1, x2, y2); // System.out.printf("%10.2f %10.2f %10.2f\n", kmgreatc, kmnew, kmold); Assert.assertTrue("Distance cant be NaN", !Double.isNaN(milesnew)); if (Math.abs(milesgreatc - milesnew) <= Math.abs(milesgreatc - milesold)) { betterThanOld++; } if (Math.abs((milesgreatc - milesnew)/milesgreatc) < 0.05) { goodEnough++; } } if (PRINT) System.out.printf("MILES NEAR: BetterThanOld: %6.4f --- GoodEnough: %6.4f\n", (double)betterThanOld / (double)times, (double)goodEnough / (double)times); Assert.assertTrue((times - betterThanOld) + " times old formula was better", (double)betterThanOld/(double)times >= 0.90); Assert.assertTrue((double)goodEnough/(double)times >= 0.95); } @Test public void testBorderCases() { double x = ScoreMath.km(90, 180, -90, -180); Assert.assertTrue(x > 0 && x < Double.POSITIVE_INFINITY); double y = ScoreMath.km(90d, 0, 90d, 0); Assert.assertEquals(0d, y); } }