Skip to content
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

Add test for NaN when comparing angles #15

Merged
merged 3 commits into from
Oct 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion python/lsst/geom/_SpherePoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ def __iter__(self):
yield self[i]

def __repr__(self):
argList = ["%r*afwGeom.degrees" % (pos.asDegrees(),) for pos in self]
argList = ["%r*geom.degrees" % (pos.asDegrees(),) for pos in self]
return "SpherePoint(%s)" % (", ".join(argList))
77 changes: 54 additions & 23 deletions python/lsst/geom/testUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#

"""Utilities that should be imported into the lsst.geom namespace when lsst.geom is used
"""Utilities that should be imported into the lsst.geom namespace when
lsst.geom is used

In the case of the assert functions, importing them makes them available in lsst.utils.tests.TestCase
"""
Expand All @@ -44,22 +45,29 @@ def extraMsg(msg):
@lsst.utils.tests.inTestCase
def assertAnglesAlmostEqual(testCase, ang0, ang1, maxDiff=0.001*arcseconds,
ignoreWrap=True, msg="Angles differ"):
r"""Assert that two `~lsst.afw.geom.Angle`\ s are almost equal, ignoring wrap differences by default
r"""Assert that two `~lsst.geom.Angle`\ s are almost equal, ignoring
wrap differences by default.

If both arguments are NaN the assert will pass. If one of the arguments
is NaN but the other is not the assert will fail.

Parameters
----------
testCase : `unittest.TestCase`
test case the test is part of; an object supporting one method: fail(self, msgStr)
ang0 : `lsst.afw.geom.Angle`
test case the test is part of; an object supporting one method:
fail(self, msgStr)
ang0 : `lsst.geom.Angle`
angle 0
ang1 : `an lsst.afw.geom.Angle`
ang1 : `lsst.geom.Angle`
angle 1
maxDiff : `an lsst.afw.geom.Angle`
maxDiff : `lsst.geom.Angle`
maximum difference between the two angles
ignoreWrap : `bool`
ignore wrap when comparing the angles?
- if True then wrap is ignored, e.g. 0 and 360 degrees are considered equal
- if False then wrap matters, e.g. 0 and 360 degrees are considered different
- if True then wrap is ignored, e.g. 0 and 360 degrees are considered
equal
- if False then wrap matters, e.g. 0 and 360 degrees are considered
different
msg : `str`
exception message prefix; details of the error are appended after ": "

Expand All @@ -68,6 +76,14 @@ def assertAnglesAlmostEqual(testCase, ang0, ang1, maxDiff=0.001*arcseconds,
AssertionError
Raised if the difference is greater than ``maxDiff``
"""
isNan0 = math.isnan(ang0.asRadians())
isNan1 = math.isnan(ang1.asRadians())
if isNan0 and isNan1:
return
if isNan0:
testCase.fail("ang0 is NaN")
if isNan1:
testCase.fail("ang1 is NaN")
measDiff = ang1 - ang0
if ignoreWrap:
measDiff = measDiff.wrapCtr()
Expand Down Expand Up @@ -168,20 +184,26 @@ def assertPairListsAlmostEqual(testCase, list0, list1, maxDiff=1e-7, msg=None):

@lsst.utils.tests.inTestCase
def assertSpherePointsAlmostEqual(testCase, sp0, sp1, maxSep=0.001*arcseconds, msg=""):
r"""Assert that two `~lsst.afw.geom.SpherePoint`\ s are almost equal
r"""Assert that two `~lsst.geom.SpherePoint`\ s are almost equal

Parameters
----------
testCase : `unittest.TestCase`
test case the test is part of; an object supporting one method: fail(self, msgStr)
sp0 : `lsst.afw.geom.SpherePoint`
test case the test is part of; an object supporting one method:
fail(self, msgStr)
sp0 : `lsst.geom.SpherePoint`
SpherePoint 0
sp1 : `lsst.afw.geom.SpherePoint`
sp1 : `lsst.geom.SpherePoint`
SpherePoint 1
maxSep : `lsst.afw.geom.Angle`
maxSep : `lsst.geom.Angle`
maximum separation
msg : `str`
extra information to be printed with any error message

Raises
------
AssertionError
The SpherePoints are not equal.
"""
if sp0.separation(sp1) > maxSep:
testCase.fail("Angular separation between %s and %s = %s\" > maxSep = %s\"%s" %
Expand All @@ -190,20 +212,26 @@ def assertSpherePointsAlmostEqual(testCase, sp0, sp1, maxSep=0.001*arcseconds, m

@lsst.utils.tests.inTestCase
def assertSpherePointListsAlmostEqual(testCase, splist0, splist1, maxSep=0.001*arcseconds, msg=None):
r"""Assert that two lists of `~lsst.afw.geom.SpherePoint`\ s are almost equal
r"""Assert that two lists of `~lsst.geom.SpherePoint`\ s are almost equal

Parameters
----------
testCase : `unittest.TestCase`
test case the test is part of; an object supporting one method: fail(self, msgStr)
splist0 : `list` of `lsst.afw.geom.SpherePoint`
test case the test is part of; an object supporting one method:
fail(self, msgStr)
splist0 : `list` of `lsst.geom.SpherePoint`
list of SpherePoints 0
splist1 : `list` of `lsst.afw.geom.SpherePoint`
splist1 : `list` of `lsst.geom.SpherePoint`
list of SpherePoints 1
maxSep : `lsst.afw.geom.Angle`
maxSep : `lsst.geom.Angle`
maximum separation
msg : `str`
exception message prefix; details of the error are appended after ": "

Raises
------
AssertionError
The SpherePoint lists are not equal.
"""
testCase.assertEqual(len(splist0), len(splist1), msg=msg)
sepArr = np.array([sp0.separation(sp1)
Expand All @@ -218,15 +246,17 @@ def assertSpherePointListsAlmostEqual(testCase, splist0, splist1, maxSep=0.001*a

@lsst.utils.tests.inTestCase
def assertBoxesAlmostEqual(testCase, box0, box1, maxDiff=1e-7, msg="Boxes differ"):
"""Assert that two boxes (`~lsst.afw.geom.Box2D` or `~lsst.afw.geom.Box2I`) are almost equal
"""Assert that two boxes (`~lsst.geom.Box2D` or `~lsst.geom.Box2I`) are
almost equal

Parameters
----------
testCase : `unittest.TestCase`
test case the test is part of; an object supporting one method: fail(self, msgStr)
box0 : `lsst.afw.geom.Box2D` or `lsst.afw.geom.Box2I`
test case the test is part of; an object supporting one method:
fail(self, msgStr)
box0 : `lsst.geom.Box2D` or `lsst.geom.Box2I`
box 0
box1 : `lsst.afw.geom.Box2D` or `lsst.afw.geom.Box2I`
box1 : `lsst.geom.Box2D` or `lsst.geom.Box2I`
box 1
maxDiff : `float`
maximum radial separation between the min points and max points
Expand All @@ -236,7 +266,8 @@ def assertBoxesAlmostEqual(testCase, box0, box1, maxDiff=1e-7, msg="Boxes differ
Raises
------
AssertionError
Raised if the radial difference of the min points or max points is greater than maxDiff
Raised if the radial difference of the min points or max points is
greater than maxDiff

Notes
-----
Expand Down
64 changes: 32 additions & 32 deletions tests/test_spherePoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

import lsst.utils.tests
import lsst.sphgeom
import lsst.geom as afwGeom
import lsst.geom as geom
import lsst.pex.exceptions as pexEx

from lsst.geom import degrees, radians, SpherePoint
Expand All @@ -53,11 +53,11 @@ class SpherePointTestSuite(lsst.utils.tests.TestCase):
def setUp(self):
self._dataset = SpherePointTestSuite.positions()
self._poleLatitudes = [
afwGeom.HALFPI*afwGeom.radians,
6.0*afwGeom.hours,
90.0*afwGeom.degrees,
5400.0*afwGeom.arcminutes,
324000.0*afwGeom.arcseconds,
geom.HALFPI*geom.radians,
6.0*geom.hours,
90.0*geom.degrees,
5400.0*geom.arcminutes,
324000.0*geom.arcseconds,
]

@property
Expand Down Expand Up @@ -89,12 +89,12 @@ def positions():
# Ensure corner cases are tested.
points += [
(0.0*degrees, 0.0*degrees),
(afwGeom.PI*radians, -6.0*degrees),
(geom.PI*radians, -6.0*degrees),
(42.0*degrees, -90.0*degrees),
(172.0*degrees, afwGeom.HALFPI*radians),
(172.0*degrees, geom.HALFPI*radians),
(360.0*degrees, 45.0*degrees),
(-278.0*degrees, -42.0*degrees),
(765.0*degrees, 0.25*afwGeom.PI*radians),
(765.0*degrees, 0.25*geom.PI*radians),
(180.0*degrees, nan*radians),
(inf*degrees, 45.0*degrees),
(nan*degrees, -8.3*degrees),
Expand Down Expand Up @@ -196,7 +196,7 @@ def testGetLongitudeValue(self):
SpherePoint(lon.asDegrees(), lat.asDegrees(), degrees),
SpherePoint(lon.asRadians(), lat.asRadians(), radians),
):
self.assertIsInstance(point.getLongitude(), afwGeom.Angle)
self.assertIsInstance(point.getLongitude(), geom.Angle)
# Behavior for non-finite points is undefined; depends on internal
# data representation
if point.isFinite():
Expand All @@ -216,7 +216,7 @@ def testGetLongitudeValue(self):

def testGetPosition(self):
for sp in self.pointSet:
for units in (degrees, afwGeom.hours, radians):
for units in (degrees, geom.hours, radians):
point = sp.getPosition(units)
expectedPoint = [val.asAngularUnits(units) for val in sp]
assert_allclose(point, expectedPoint, atol=1e-15)
Expand Down Expand Up @@ -244,7 +244,7 @@ def testGetLatitudeValue(self):
SpherePoint(lon.asDegrees(), lat.asDegrees(), degrees),
SpherePoint(lon.asRadians(), lat.asRadians(), radians),
):
self.assertIsInstance(point.getLatitude(), afwGeom.Angle)
self.assertIsInstance(point.getLatitude(), geom.Angle)
# Behavior for non-finite points is undefined; depends on internal
# data representation
if point.isFinite():
Expand Down Expand Up @@ -400,10 +400,10 @@ def testGetItemValue(self):
"""Test if indexing returns the expected value.
"""
for point in self.pointSet:
self.assertIsInstance(point[-2], afwGeom.Angle)
self.assertIsInstance(point[-1], afwGeom.Angle)
self.assertIsInstance(point[0], afwGeom.Angle)
self.assertIsInstance(point[1], afwGeom.Angle)
self.assertIsInstance(point[-2], geom.Angle)
self.assertIsInstance(point[-1], geom.Angle)
self.assertIsInstance(point[0], geom.Angle)
self.assertIsInstance(point[1], geom.Angle)

if not math.isnan(point.getLongitude().asRadians()):
self.assertEqual(point.getLongitude(), point[-2])
Expand Down Expand Up @@ -527,7 +527,7 @@ def testBearingToValueOnEquator(self):
end = SpherePoint(trial['lonEnd']*degrees, trial['latEnd']*degrees)
bearing = origin.bearingTo(end)

self.assertIsInstance(bearing, afwGeom.Angle)
self.assertIsInstance(bearing, geom.Angle)
if origin.isFinite() and end.isFinite():
self.assertGreaterEqual(bearing.asDegrees(), 0.0)
self.assertLess(bearing.asDegrees(), 360.0)
Expand Down Expand Up @@ -587,9 +587,9 @@ def testBearingToValueSingular(self):
180.0*degrees, self.nextDown(northPoleSame.getLatitude()))

self.assertAnglesAlmostEqual(southPole.bearingTo(northPoleSame),
afwGeom.HALFPI*afwGeom.radians)
geom.HALFPI*geom.radians)
self.assertAnglesAlmostEqual(southPole.bearingTo(northPoleOpposite),
(afwGeom.PI + afwGeom.HALFPI)*afwGeom.radians)
(geom.PI + geom.HALFPI)*geom.radians)

def testSeparationValueGeneric(self):
"""Test if separation() returns the correct value.
Expand All @@ -610,7 +610,7 @@ def testSeparationValueGeneric(self):
expected = 0.0

sep = point1.separation(point2)
self.assertIsInstance(sep, afwGeom.Angle)
self.assertIsInstance(sep, geom.Angle)
if point1.isFinite() and point2.isFinite():
self.assertGreaterEqual(sep.asDegrees(), 0.0)
self.assertLessEqual(sep.asDegrees(), 180.0)
Expand All @@ -635,7 +635,7 @@ def testSeparationValueAbsolute(self):
32.7930, spica.separation(arcturus).asDegrees(), 4)

# Verify small angles: along a constant ra, add an arcsec to spica dec.
epsilon = 1.0*afwGeom.arcseconds
epsilon = 1.0*geom.arcseconds
spicaPlus = SpherePoint(spica.getLongitude(),
spica.getLatitude() + epsilon)

Expand Down Expand Up @@ -732,7 +732,7 @@ def testRotatedValue(self):
for lon, lat in self._dataset:
point = SpherePoint(lon, lat)
dist = point.separation(pole)
newPoint = point.rotated(pole, -32.4*afwGeom.radians)
newPoint = point.rotated(pole, -32.4*geom.radians)

self.assertNotAlmostEqual(point.getLongitude().asDegrees(),
newPoint.getLongitude().asDegrees())
Expand Down Expand Up @@ -860,20 +860,20 @@ def testOffsetValue(self):
def testOffsetTangentPlane(self):
"""Test offsets on a tangent plane (good for small angles)"""

c0 = SpherePoint(0.0, 0.0, afwGeom.degrees)
c0 = SpherePoint(0.0, 0.0, geom.degrees)

for dRaDeg in (0.0123, 0.0, -0.0321):
dRa = dRaDeg*afwGeom.degrees
dRa = dRaDeg*geom.degrees
for dDecDeg in (0.0543, 0.0, -0.0987):
dDec = dDecDeg*afwGeom.degrees
dDec = dDecDeg*geom.degrees
c1 = SpherePoint(dRa, dDec)

offset = c0.getTangentPlaneOffset(c1)

# This more-or-less works for small angles because c0 is 0,0
expectedOffset = [
math.tan(dRa.asRadians())*afwGeom.radians,
math.tan(dDec.asRadians())*afwGeom.radians,
math.tan(dRa.asRadians())*geom.radians,
math.tan(dDec.asRadians())*geom.radians,
]

for i in range(2):
Expand Down Expand Up @@ -942,25 +942,25 @@ def testReprValue(self):
def testAverageSpherePoint(self):
"""Test the averageSpherePoint function"""

def checkCircle(center, start, numPts, maxSep=1.0e-9*afwGeom.arcseconds):
def checkCircle(center, start, numPts, maxSep=1.0e-9*geom.arcseconds):
"""Generate points in a circle; test that average is in the center
"""
coords = []
deltaAngle = 360*degrees / numPts
for ii in range(numPts):
new = start.rotated(center, ii*deltaAngle)
coords.append(new)
result = afwGeom.averageSpherePoint(coords)
result = geom.averageSpherePoint(coords)
self.assertSpherePointsAlmostEqual(center, result, maxSep=maxSep)

for numPts in (2, 3, 120):
for center, start in (
# RA=0=360 border
(SpherePoint(0, 0, afwGeom.degrees), SpherePoint(5, 0, afwGeom.degrees)),
(SpherePoint(0, 0, geom.degrees), SpherePoint(5, 0, geom.degrees)),
# North pole
(SpherePoint(0, 90, afwGeom.degrees), SpherePoint(0, 85, afwGeom.degrees)),
(SpherePoint(0, 90, geom.degrees), SpherePoint(0, 85, geom.degrees)),
# South pole
(SpherePoint(0, -90, afwGeom.degrees), SpherePoint(0, -85, afwGeom.degrees)),
(SpherePoint(0, -90, geom.degrees), SpherePoint(0, -85, geom.degrees)),
):
checkCircle(center=center, start=start, numPts=numPts)

Expand Down
8 changes: 8 additions & 0 deletions tests/test_testUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ def testAssertAnglesAlmostEqual(self):
maxDiff=0.009999*lsst.geom.arcseconds,
)

# Compare with NaN
ang0 = float("NaN")*lsst.geom.degrees
ang1 = 1.*lsst.geom.degrees
with self.assertRaises(AssertionError):
self.assertAnglesAlmostEqual(ang0, ang1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also test with the arguments swapped

with self.assertRaises(AssertionError):
self.assertAnglesAlmostEqual(ang1, ang0)

def testAssertBoxesAlmostEqual(self):
"""Test assertBoxesAlmostEqual"""
for min0 in ((0, 0), (-1000.5, 5000.1)):
Expand Down