Coverage for tests/test_angle.py: 14%
175 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-07-09 05:53 -0700
« prev ^ index » next coverage.py v6.4.1, created at 2022-07-09 05:53 -0700
1#
2# Developed for the LSST Data Management System.
3# This product includes software developed by the LSST Project
4# (https://www.lsst.org).
5# See the COPYRIGHT file at the top-level directory of this distribution
6# for details of code ownership.
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program. If not, see <https://www.gnu.org/licenses/>.
20#
22"""
23Tests for Angle
25Run with:
26 angle.py
27or
28 python
29 >>> import angle; angle.run()
30"""
31import itertools
32import math
33import unittest
35import numpy as np
37import lsst.utils.tests
38import lsst.geom
41class AngleTestCase(lsst.utils.tests.TestCase):
42 """A test case for Angle"""
44 def setUp(self):
45 self.pi = lsst.geom.Angle(math.pi, lsst.geom.radians)
46 self.d = 180*lsst.geom.degrees
48 def testCtor(self):
49 self.assertEqual(self.pi, math.pi)
50 self.assertEqual(self.pi, lsst.geom.Angle(math.pi))
51 self.assertEqual(self.pi, self.d)
53 dd = lsst.geom.Angle(180, lsst.geom.degrees)
54 self.assertEqual(self.d, dd)
55 dd = lsst.geom.Angle(60*180, lsst.geom.arcminutes)
56 self.assertEqual(self.d, dd)
57 dd = lsst.geom.Angle(60*60*180, lsst.geom.arcseconds)
58 self.assertEqual(self.d, dd)
59 dd = lsst.geom.Angle(60*60*180*1000, lsst.geom.milliarcseconds)
60 self.assertEqual(self.d, dd)
62 def testArithmetic(self):
63 self.assertTrue(lsst.geom.isAngle(self.pi))
64 self.assertFalse(lsst.geom.isAngle(self.pi.asRadians()))
65 self.assertFalse(lsst.geom.isAngle(math.pi))
67 with self.assertRaises(TypeError):
68 self.pi - math.pi # subtracting a float from an Angle
69 self.assertEqual(self.pi - math.pi*lsst.geom.radians, 0)
70 self.assertEqual(self.pi - self.d, 0) # can subtract Angles
72 with self.assertRaises(TypeError):
73 self.pi + math.pi # adding a float to an Angle
75 with self.assertRaises(NotImplementedError):
76 self.pi*lsst.geom.degrees # self.pi is already an Angle
78 self.assertEqual((self.pi + self.d).asAngularUnits(lsst.geom.degrees),
79 360)
80 self.assertEqual((self.pi).asRadians(), math.pi)
81 self.assertEqual((self.pi/2).asDegrees(), 90)
82 self.assertEqual((self.pi*2).asArcminutes(), 360*60)
83 self.assertEqual((self.pi*2).asArcseconds(), 360*60*60)
84 self.assertEqual((self.pi*2).asMilliarcseconds(), 360*60*60*1000)
85 self.assertEqual((-self.pi).asRadians(), -math.pi)
87 with self.assertRaises(TypeError):
88 2.0 / self.pi # dividing a float by an Angle
89 with self.assertRaises(TypeError):
90 self.pi / self.pi
92 # automatic conversion to double
93 self.assertEqual(math.sin(self.pi/2), 1.0)
95 def testAbs(self):
96 self.assertEqual(abs(0.0*lsst.geom.degrees - self.pi), self.pi)
98 def testPi(self):
99 self.assertEqual(lsst.geom.PI, math.pi)
101 def testComparison(self):
102 a2 = 2.0 * lsst.geom.arcseconds
103 a1 = 0.5 * lsst.geom.arcseconds
104 a3 = 0.5 * lsst.geom.arcseconds
105 self.assertEqual(a1, a3)
106 self.assertNotEqual(a1, a2)
107 self.assertLessEqual(a1, a2)
108 self.assertLess(a1, a2)
109 self.assertGreater(a2, a1)
110 self.assertGreaterEqual(a2, a1)
112 self.assertFalse(a1 != a3)
113 self.assertFalse(a1 == a2)
114 self.assertFalse(a1 >= a2)
115 self.assertFalse(a1 > a2)
116 self.assertFalse(a2 < a1)
117 self.assertFalse(a2 <= a1)
119 self.assertTrue(a1 == float(a1))
120 self.assertTrue(float(a1) == a1)
122 def testTrig(self):
123 self.assertEqual(math.cos(self.d), -1.0)
124 self.assertAlmostEqual(math.sin(self.d), 0.0, places=15)
125 thirty = 30.*lsst.geom.degrees
126 self.assertAlmostEqual(math.sin(thirty), 0.5, places=15)
128 def testSeparation(self):
129 """Tests whether angle differences are computed as expected.
131 Wrapping accuracy is assumed tested by testWrap.
132 """
133 angleBase = 0.0*lsst.geom.degrees
134 angleWrap = 360.0*lsst.geom.degrees
135 angleHalf = -180.0*lsst.geom.degrees
136 angleOdd = 32.0*lsst.geom.degrees
138 self.checkWrappedAngle(angleBase.separation(angleWrap),
139 0.0*lsst.geom.degrees)
140 self.checkWrappedAngle(angleWrap.separation(angleBase),
141 0.0*lsst.geom.degrees)
143 self.checkWrappedAngle(angleBase.separation(angleHalf), angleHalf)
144 self.checkWrappedAngle(angleHalf.separation(angleBase), angleHalf)
146 self.checkWrappedAngle(angleWrap.separation(angleHalf), angleHalf)
147 self.checkWrappedAngle(angleHalf.separation(angleWrap), angleHalf)
149 self.checkWrappedAngle(angleOdd.separation(angleBase), angleOdd)
150 self.checkWrappedAngle(angleBase.separation(angleOdd), -angleOdd)
152 self.checkWrappedAngle(angleOdd.separation(angleWrap), angleOdd)
153 self.checkWrappedAngle(angleWrap.separation(angleOdd), -angleOdd)
155 def checkWrappedAngle(self, observed, expected):
156 """Tests whether an angle wrapped to [-pi, pi) both matches its expected
157 value and strictly satisfies its range restriction.
158 """
159 obs = observed.asRadians()
160 exp = expected.asRadians()
161 self.assertAlmostEqual(obs, exp, delta=np.finfo(float).eps)
162 self.assertGreaterEqual(obs, -math.pi)
163 self.assertLess(obs, math.pi)
165 def testWrap(self):
166 eps = np.finfo(float).eps
167 self.assertNotEqual(1 + eps, eps)
168 for wrap, offset, epsMult in itertools.product(
169 (-1000, -10, -1, 0, 1, 10, 1000),
170 (-2*math.pi, -math.pi, -math.pi*0.5, 0.0, math.pi*0.5, math.pi*0.75, math.pi, math.pi*2.0),
171 (-3, -2, -1, 0, 1, 2, 3),
172 ):
173 angRad = (offset + (wrap*math.pi)) * (1 + (eps*epsMult))
174 ang = angRad * lsst.geom.radians
176 posAng = (angRad * lsst.geom.radians).wrap()
177 self.assertAnglesAlmostEqual(ang, posAng)
178 posAngRad = posAng.asRadians()
179 posAngDeg = posAng.asDegrees()
180 posAngArcmin = posAng.asArcminutes()
181 posAngArcsec = posAng.asArcseconds()
182 posAngMilliarcsec = posAng.asMilliarcseconds()
183 # the code promises 0 <= posAng for all units
184 self.assertGreaterEqual(posAngRad, 0)
185 self.assertGreaterEqual(posAngDeg, 0)
186 self.assertGreaterEqual(posAngArcmin, 0)
187 self.assertGreaterEqual(posAngArcsec, 0)
188 self.assertGreaterEqual(posAngMilliarcsec, 0)
189 # wrap promises posAng < 2*pi only for radians,
190 # but it seems to work for all units
191 self.assertLess(posAngRad, 2*math.pi)
192 self.assertLess(posAngDeg, 360)
193 self.assertLess(posAngArcmin, 360 * 60)
194 self.assertLess(posAngArcsec, 360 * 3600)
195 self.assertLess(posAngMilliarcsec, 360 * 3.6e6)
197 ctrAng = (angRad * lsst.geom.radians).wrapCtr()
198 self.assertAnglesAlmostEqual(ang, ctrAng)
199 ctrAngRad = ctrAng.asRadians()
200 ctrAngDeg = ctrAng.asDegrees()
201 ctrAngArcmin = ctrAng.asArcminutes()
202 ctrAngArcsec = ctrAng.asArcseconds()
203 ctrAngMilliarcsec = ctrAng.asMilliarcseconds()
204 # wrapCtr promises -pi <= ctrAngRad < pi only for radians,
205 # but it seems to work for all units
206 self.assertGreaterEqual(ctrAngRad, -math.pi)
207 self.assertGreaterEqual(ctrAngDeg, -180)
208 self.assertGreaterEqual(ctrAngArcmin, -180 * 60)
209 self.assertGreaterEqual(ctrAngArcsec, -180 * 3600)
210 self.assertGreaterEqual(ctrAngMilliarcsec, -180 * 3.6e6)
211 self.assertLess(ctrAngRad, math.pi)
212 self.assertLess(ctrAngDeg, 180)
213 self.assertLess(ctrAngArcmin, 180 * 60)
214 self.assertLess(ctrAngArcsec, 180 * 3600)
216 for refAngBase, refAngWrap, refEpsMult in itertools.product(
217 (-math.pi, 0.0, math.pi, math.pi*2.0),
218 range(-10, 11, 2),
219 (-3, -2, -1, 0, 1, 2, 3),
220 ):
221 refAngRad = refAngBase * (1 + (eps * refEpsMult)) + refAngWrap * math.pi * 2.0
222 refAng = refAngRad * lsst.geom.radians
223 refAngDeg = refAng.asDegrees()
224 refAngArcmin = refAng.asArcminutes()
225 refAngArcsec = refAng.asArcseconds()
226 refAngMilliarcsec = refAng.asMilliarcseconds()
227 nearAng = (angRad * lsst.geom.radians).wrapNear(refAng)
228 self.assertAnglesAlmostEqual(ang, nearAng)
229 nearAngRad = nearAng.asRadians()
230 nearAngDeg = nearAng.asDegrees()
231 nearAngArcmin = nearAng.asArcminutes()
232 nearAngArcsec = nearAng.asArcseconds()
233 nearAngMilliarcsec = nearAng.asMilliarcseconds()
235 fudgeFactor = 5 # 1 and 2 are too small for one case, 3 works; 5 has margin
236 relEps = max(1, nearAngRad / math.pi, refAngRad / math.pi) * eps * fudgeFactor
237 piWithSlop = math.pi * (1 + relEps)
238 oneEightyWithSlop = 180 * (1 + relEps)
240 # wrapNear promises nearAngRad - refAngRad >= -pi
241 # for radians but has known failures due to
242 # roundoff error for other units and for < pi
243 self.assertGreaterEqual(nearAngRad - refAngRad, -math.pi)
244 self.assertGreaterEqual(nearAngDeg - refAngDeg, -oneEightyWithSlop)
245 self.assertGreaterEqual(nearAngArcmin - refAngArcmin, -oneEightyWithSlop * 60)
246 self.assertGreaterEqual(nearAngArcsec - refAngArcsec, -oneEightyWithSlop * 3600)
247 self.assertGreaterEqual(nearAngMilliarcsec - refAngMilliarcsec, -oneEightyWithSlop * 3.6e6)
248 self.assertLessEqual(nearAngRad - refAngRad, piWithSlop)
249 self.assertLessEqual(nearAngDeg - refAngDeg, oneEightyWithSlop)
250 self.assertLessEqual(nearAngArcmin - refAngArcmin, oneEightyWithSlop * 60)
251 self.assertLessEqual(nearAngArcsec - refAngArcsec, oneEightyWithSlop * 3600)
252 self.assertLessEqual(nearAngMilliarcsec - refAngMilliarcsec, oneEightyWithSlop * 3.6e6)
254 def test_repr(self):
255 """Test that eval(repr(Angle)) round-trips for finite and non-finite
256 values.
257 """
258 from lsst.geom import Angle, degrees # need symbols in scope for eval
259 d = 4.0 / 7.0
260 self.assertEqual(eval(repr(Angle(d, degrees))), Angle(d, degrees))
261 self.assertEqual(eval(repr(Angle(float("inf"), degrees))), Angle(float("inf"), degrees))
262 self.assertTrue(np.isnan(eval(repr(Angle(float("NaN"), degrees))).asDegrees()))
265class MemoryTester(lsst.utils.tests.MemoryTestCase):
266 pass
269def setup_module(module):
270 lsst.utils.tests.init()
273if __name__ == "__main__": 273 ↛ 274line 273 didn't jump to line 274, because the condition on line 273 was never true
274 lsst.utils.tests.init()
275 unittest.main()