Coverage for tests/test_polyMap.py: 10%
207 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-07-09 05:46 -0700
« prev ^ index » next coverage.py v6.4.1, created at 2022-07-09 05:46 -0700
2import unittest
4import numpy as np
5import numpy.testing as npt
7import astshim as ast
8from astshim.test import MappingTestCase
11class TestPolyMap(MappingTestCase):
13 def test_PolyMapIterativeInverse(self):
14 """Test a unidirectional polymap with its default iterative inverse
15 """
16 coeff_f = np.array([
17 [1.2, 1, 2, 0],
18 [-0.5, 1, 1, 1],
19 [1.0, 2, 0, 1],
20 ])
21 pm = ast.PolyMap(coeff_f, 2, "IterInverse=1")
22 self.assertIsInstance(pm, ast.Object)
23 self.assertIsInstance(pm, ast.Mapping)
24 self.assertIsInstance(pm, ast.PolyMap)
25 self.assertEqual(pm.nIn, 2)
26 self.assertEqual(pm.nOut, 2)
27 self.assertTrue(pm.iterInverse)
28 self.assertEqual(pm.nIterInverse, 4)
29 self.assertAlmostEqual(pm.tolInverse, 1.0E-6)
30 self.assertTrue(pm.hasForward)
31 self.assertTrue(pm.hasInverse)
33 self.checkBasicSimplify(pm)
34 self.checkCopy(pm)
36 indata = np.array([
37 [1.0, 2.0, 3.0],
38 [0.0, 1.0, 2.0],
39 ])
40 outdata = pm.applyForward(indata)
41 xin, yin = indata
42 pred_xout = (1.2 * xin * xin) - (0.5 * yin * xin)
43 pred_yout = yin
44 xout, yout = outdata
45 npt.assert_allclose(xout, pred_xout)
46 npt.assert_allclose(yout, pred_yout)
48 indata_roundtrip = pm.applyInverse(outdata)
49 npt.assert_allclose(indata, indata_roundtrip, atol=1.0e-4)
51 self.checkMappingPersistence(pm, indata)
53 def test_polyMapAttributes(self):
54 coeff_f = np.array([
55 [1.2, 1, 2, 0],
56 [-0.5, 1, 1, 1],
57 [1.0, 2, 0, 1],
58 ])
59 pm = ast.PolyMap(coeff_f, 2, "IterInverse=1, NIterInverse=6, TolInverse=1.2e-7")
60 self.assertIsInstance(pm, ast.Object)
61 self.assertIsInstance(pm, ast.Mapping)
62 self.assertIsInstance(pm, ast.PolyMap)
63 self.assertEqual(pm.nIn, 2)
64 self.assertEqual(pm.nOut, 2)
65 self.assertTrue(pm.iterInverse)
66 self.assertEqual(pm.nIterInverse, 6)
67 self.assertAlmostEqual(pm.tolInverse, 1.2E-7)
68 self.assertTrue(pm.hasForward)
69 self.assertTrue(pm.hasInverse)
71 indata = np.array([
72 [1.0, 2.0, 3.0],
73 [0.0, 1.0, 2.0],
74 ])
75 outdata = pm.applyForward(indata)
76 xin, yin = indata
77 pred_xout = (1.2 * xin * xin) - (0.5 * yin * xin)
78 pred_yout = yin
79 xout, yout = outdata
80 npt.assert_allclose(xout, pred_xout)
81 npt.assert_allclose(yout, pred_yout)
83 indata_roundtrip = pm.applyInverse(outdata)
84 npt.assert_allclose(indata, indata_roundtrip, atol=1.0e-6)
86 self.checkMappingPersistence(pm, indata)
88 def test_polyMapNoInverse(self):
89 """Test a unidirectional polymap with no numeric inverse
90 """
91 coeff_f = np.array([
92 [1.2, 1, 2, 0],
93 [-0.5, 1, 1, 1],
94 [1.0, 2, 0, 1],
95 ])
96 pm = ast.PolyMap(coeff_f, 2)
97 self.assertIsInstance(pm, ast.PolyMap)
98 self.assertEqual(pm.nIn, 2)
99 self.assertEqual(pm.nOut, 2)
100 self.assertTrue(pm.hasForward)
101 self.assertFalse(pm.hasInverse)
102 self.assertFalse(pm.iterInverse)
104 indata = np.array([
105 [1.0, 2.0, 3.0],
106 [0.0, 1.0, 2.0],
107 ])
108 outdata = pm.applyForward(indata)
109 with self.assertRaises(RuntimeError):
110 pm.applyInverse(indata)
112 pminv = pm.inverted()
113 self.assertFalse(pminv.hasForward)
114 self.assertTrue(pminv.hasInverse)
115 self.assertTrue(pminv.isInverted)
116 self.assertFalse(pm.iterInverse)
118 outdata2 = pminv.applyInverse(indata)
119 # outdata and outdata2 should be identical because inverting
120 # swaps the behavior of applyForward and applyInverse
121 npt.assert_equal(outdata, outdata2)
122 with self.assertRaises(RuntimeError):
123 pminv.applyForward(indata)
125 self.checkMappingPersistence(pm, indata)
127 def test_PolyMapBidirectional(self):
128 coeff_f = np.array([
129 [1., 1, 1, 0],
130 [1., 1, 0, 1],
131 [1., 2, 1, 0],
132 [-1., 2, 0, 1]
133 ])
134 coeff_i = np.array([
135 [0.5, 1, 1, 0],
136 [0.5, 1, 0, 1],
137 [0.5, 2, 1, 0],
138 [-0.5, 2, 0, 1],
139 ])
140 pm = ast.PolyMap(coeff_f, coeff_i)
141 self.assertEqual(pm.nIn, 2)
142 self.assertEqual(pm.nOut, 2)
144 self.checkBasicSimplify(pm)
145 self.checkCopy(pm)
147 indata = np.array([
148 [1.0, 2.0, 3.0],
149 [0.0, 1.0, 2.0],
150 ])
152 self.checkRoundTrip(pm, indata)
153 self.checkMappingPersistence(pm, indata)
155 def test_PolyMapEmptyForwardCoeffs(self):
156 """Test constructing a PolyMap with empty forward coefficients
157 """
158 coeff_f = np.array([], dtype=float)
159 coeff_f.shape = (0, 4)
160 coeff_i = np.array([
161 [0.5, 1, 1, 0],
162 [0.5, 1, 0, 1],
163 [0.5, 2, 1, 0],
164 [-0.5, 2, 0, 1],
165 ])
166 pm = ast.PolyMap(coeff_f, coeff_i)
167 self.assertEqual(pm.nIn, 2)
168 self.assertEqual(pm.nOut, 2)
170 self.checkBasicSimplify(pm)
171 self.checkCopy(pm)
173 self.assertFalse(pm.hasForward)
174 self.assertTrue(pm.hasInverse)
175 self.assertFalse(pm.iterInverse)
177 def test_PolyMapEmptyInverseCoeffs(self):
178 """Test constructing a PolyMap with empty inverse coefficients
179 """
180 coeff_f = np.array([
181 [1., 1, 1, 0],
182 [1., 1, 0, 1],
183 [1., 2, 1, 0],
184 [-1., 2, 0, 1]
185 ])
186 coeff_i = np.array([], dtype=float)
187 coeff_i.shape = (0, 4)
188 pm = ast.PolyMap(coeff_f, coeff_i)
189 self.assertEqual(pm.nIn, 2)
190 self.assertEqual(pm.nOut, 2)
192 self.checkBasicSimplify(pm)
193 self.checkCopy(pm)
195 self.assertTrue(pm.hasForward)
196 self.assertFalse(pm.hasInverse)
197 self.assertFalse(pm.iterInverse)
199 indata = np.array([
200 [1.0, 2.0, 3.0],
201 [0.0, 1.0, 2.0],
202 ])
203 self.checkMappingPersistence(pm, indata)
205 def test_PolyMapNoTransform(self):
206 """Test constructing a PolyMap with neither forward nor inverse
207 coefficients
208 """
209 coeff_f = np.array([], dtype=float)
210 coeff_f.shape = (0, 4)
211 coeff_i = np.array([], dtype=float)
212 coeff_i.shape = (0, 3)
214 with self.assertRaises(ValueError):
215 ast.PolyMap(coeff_f, coeff_i)
217 with self.assertRaises(ValueError):
218 ast.PolyMap(coeff_f, 3)
220 def test_PolyMapPolyTranTrivial(self):
221 coeff_f = np.array([
222 [1., 1, 1, 0],
223 [1., 1, 0, 1],
224 [1., 2, 1, 0],
225 [-1., 2, 0, 1]
226 ])
227 coeff_i = np.array([
228 [0.5, 1, 1, 0],
229 [0.5, 1, 0, 1],
230 [0.5, 2, 1, 0],
231 [-0.5, 2, 0, 1],
232 ])
233 pm = ast.PolyMap(coeff_f, coeff_i)
235 indata = np.array([
236 [1.0, 2.0, 3.0],
237 [0.0, 1.0, 2.0],
238 ])
240 outdata = pm.applyForward(indata)
242 # create a PolyMap with an identical forward transform and a fit
243 # inverse.
244 forward = False
245 pm2 = pm.polyTran(forward, 1.0E-10, 1.0E-10, 4, [-1.0, -1.0], [1.0, 1.0])
246 outdata2 = pm2.applyForward(indata)
247 npt.assert_equal(outdata, outdata2)
248 indata2 = pm2.applyInverse(outdata)
249 npt.assert_allclose(indata, indata2, atol=1.0e-10)
251 self.checkMappingPersistence(pm, indata)
252 self.checkMappingPersistence(pm2, indata)
254 def test_PolyMapPolyTranNontrivial(self):
255 """Test PolyMap.polyTran on a non-trivial case
256 """
257 # Approximate "field angle to focal plane" transformation coefficients
258 # for LSST thus the domain of the forward direction is
259 # 1.75 degrees = 0.0305 radians.
260 # The camera has 10 um pixels = 0.01 mm
261 # The desired accuracy of the inverse transformation is
262 # 0.001 pixels = 1e-5 mm = 9.69e-10 radians.
263 plateScaleRad = 9.69627362219072e-05 # radians per mm
264 radialCoeff = np.array([0.0, 1.0, 0.0, 0.925]) / plateScaleRad
265 polyCoeffs = []
266 for i, coeff in enumerate(radialCoeff):
267 polyCoeffs.append((coeff, 1, i))
268 polyCoeffs = np.array(polyCoeffs)
269 fieldAngleToFocalPlane = ast.PolyMap(polyCoeffs, 1)
271 atolRad = 1.0e-9
272 fieldAngleToFocalPlane2 = fieldAngleToFocalPlane.polyTran(forward=False, acc=atolRad, maxacc=atolRad,
273 maxorder=10, lbnd=[0], ubnd=[0.0305])
274 fieldAngle = np.linspace(0, 0.0305, 100)
275 focalPlane = fieldAngleToFocalPlane.applyForward(fieldAngle)
276 fieldAngleRoundTrip = fieldAngleToFocalPlane2.applyInverse(focalPlane)
277 npt.assert_allclose(fieldAngle, fieldAngleRoundTrip, atol=atolRad)
279 # Verify that polyTran cannot fit the inverse when maxorder is
280 # too small.
281 with self.assertRaises(RuntimeError):
282 fieldAngleToFocalPlane.polyTran(forward=False, acc=atolRad, maxacc=atolRad,
283 maxorder=3, lbnd=[0], ubnd=[0.0305])
285 def test_PolyMapIterInverseDominates(self):
286 """Test that IterInverse dominates inverse coefficients
287 for applyInverse.
288 """
289 coeff_f = np.array([
290 [1., 1, 1],
291 ])
292 # these coefficients don't match coeff_f, in that the inverse mapping
293 # does not undo the forward mapping (as proven below)
294 coeff_i = np.array([
295 [25., 1, 2],
296 ])
297 polyMap = ast.PolyMap(coeff_f, coeff_i, "IterInverse=1")
299 indata = np.array([-0.5, 0.5, 1.1, 1.8])
300 outdata = polyMap.applyForward(indata)
301 indata_roundtrip = polyMap.applyInverse(outdata)
302 npt.assert_allclose(indata, indata_roundtrip)
304 # Prove that without the iterative inverse the PolyMap does not invert
305 # correctly.
306 polyMap2 = ast.PolyMap(coeff_f, coeff_i)
307 indata_roundtrip2 = polyMap2.applyInverse(outdata)
308 self.assertFalse(np.allclose(indata, indata_roundtrip2))
310 def test_PolyMapPolyTranIterInverse(self):
311 """Test PolyTran on a PolyMap that has an iterative inverse
313 The result should use the fit inverse, not the iterative inverse
314 """
315 coeff_f = np.array([
316 [1., 1, 1],
317 ])
318 for polyMap in (
319 ast.PolyMap(coeff_f, 1, "IterInverse=1"),
320 ast.PolyMap(coeff_f, coeff_f, "IterInverse=1"),
321 ):
322 # make sure IterInverse is True and set
323 self.assertTrue(polyMap.iterInverse)
324 self.assertTrue(polyMap.test("IterInverse"))
326 # fit inverse; this should clear iterInverse
327 polyMapFitInv = polyMap.polyTran(False, 1.0E-10, 1.0E-10, 4, [-1.0], [1.0])
328 self.assertFalse(polyMapFitInv.iterInverse)
329 self.assertFalse(polyMapFitInv.test("IterInverse"))
331 # Fit forward direction of inverted mapping; this should also
332 # clear IterInverse.
333 polyMapInvFitFwd = polyMap.inverted().polyTran(True, 1.0E-10, 1.0E-10, 4, [-1.0], [1.0])
334 self.assertFalse(polyMapInvFitFwd.iterInverse)
335 self.assertFalse(polyMapFitInv.test("IterInverse"))
337 # cannot fit forward because inverse is iterative
338 with self.assertRaises(ValueError):
339 polyMap.polyTran(True, 1.0E-10, 1.0E-10, 4, [-1.0], [1.0])
341 # Cannot fit inverse of inverted mapping because forward is
342 # iterative.
343 with self.assertRaises(ValueError):
344 polyMap.inverted().polyTran(False, 1.0E-10, 1.0E-10, 4, [-1.0], [1.0])
346 def test_PolyMapPolyMapUnivertible(self):
347 """Test polyTran on a PolyMap without a single-valued inverse
349 The equation is y = x^2 - x^3, whose inverse has 3 values
350 between roughly -0.66 and 2.0
351 """
352 coeff_f = np.array([
353 [2.0, 1, 2],
354 [-1.0, 1, 3],
355 ])
356 pm = ast.PolyMap(coeff_f, 1, "IterInverse=1")
358 self.checkBasicSimplify(pm)
359 self.checkCopy(pm)
361 indata = np.array([-0.5, 0.5, 1.1, 1.8])
362 pred_outdata = (2.0*indata.T**2 - indata.T**3).T
363 outdata = pm.applyForward(indata)
364 npt.assert_allclose(outdata, pred_outdata)
366 # the iterative inverse should give valid values
367 indata_iterative = pm.applyInverse(outdata)
368 outdata_roundtrip = pm.applyForward(indata_iterative)
369 npt.assert_allclose(outdata, outdata_roundtrip)
371 self.checkMappingPersistence(pm, indata)
373 with self.assertRaises(RuntimeError):
374 # includes the range where the inverse has multiple values,
375 # so no inverse is possible
376 pm.polyTran(False, 1e-3, 1e-3, 10, [-1.0], [2.5])
378 def test_PolyMapDM10496(self):
379 """Test for a segfault when simplifying a SeriesMap
381 We saw an intermittent segfault when simplifying a SeriesMap
382 consisting of the inverse of PolyMap with 2 inputs and one output
383 followed by its inverse (which should simplify to a UnitMap
384 with one input and one output). David Berry fixed this bug in AST
385 2017-05-10.
387 I tried this test on an older version of astshim and found that it
388 triggering a segfault nearly every time.
389 """
390 coeff_f = np.array([
391 [-1.1, 1, 2, 0],
392 [1.3, 1, 3, 1],
393 ])
394 coeff_i = np.array([
395 [1.6, 1, 3],
396 [-3.6, 2, 1],
397 ])
399 # execute many times to increase the odds of a segfault
400 for i in range(1000):
401 amap = ast.PolyMap(coeff_f, coeff_i)
402 amapinv = amap.inverted()
403 cmp2 = amapinv.then(amap)
404 result = cmp2.simplified()
405 self.assertIsInstance(result, ast.UnitMap)
408if __name__ == "__main__": 408 ↛ 409line 408 didn't jump to line 409, because the condition on line 408 was never true
409 unittest.main()