Coverage for tests/test_polyMap.py: 10%

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

207 statements  

1 

2import unittest 

3 

4import numpy as np 

5import numpy.testing as npt 

6 

7import astshim as ast 

8from astshim.test import MappingTestCase 

9 

10 

11class TestPolyMap(MappingTestCase): 

12 

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) 

32 

33 self.checkBasicSimplify(pm) 

34 self.checkCopy(pm) 

35 

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) 

47 

48 indata_roundtrip = pm.applyInverse(outdata) 

49 npt.assert_allclose(indata, indata_roundtrip, atol=1.0e-4) 

50 

51 self.checkMappingPersistence(pm, indata) 

52 

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) 

70 

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) 

82 

83 indata_roundtrip = pm.applyInverse(outdata) 

84 npt.assert_allclose(indata, indata_roundtrip, atol=1.0e-6) 

85 

86 self.checkMappingPersistence(pm, indata) 

87 

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) 

103 

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) 

111 

112 pminv = pm.inverted() 

113 self.assertFalse(pminv.hasForward) 

114 self.assertTrue(pminv.hasInverse) 

115 self.assertTrue(pminv.isInverted) 

116 self.assertFalse(pm.iterInverse) 

117 

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) 

124 

125 self.checkMappingPersistence(pm, indata) 

126 

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) 

143 

144 self.checkBasicSimplify(pm) 

145 self.checkCopy(pm) 

146 

147 indata = np.array([ 

148 [1.0, 2.0, 3.0], 

149 [0.0, 1.0, 2.0], 

150 ]) 

151 

152 self.checkRoundTrip(pm, indata) 

153 self.checkMappingPersistence(pm, indata) 

154 

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) 

169 

170 self.checkBasicSimplify(pm) 

171 self.checkCopy(pm) 

172 

173 self.assertFalse(pm.hasForward) 

174 self.assertTrue(pm.hasInverse) 

175 self.assertFalse(pm.iterInverse) 

176 

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) 

191 

192 self.checkBasicSimplify(pm) 

193 self.checkCopy(pm) 

194 

195 self.assertTrue(pm.hasForward) 

196 self.assertFalse(pm.hasInverse) 

197 self.assertFalse(pm.iterInverse) 

198 

199 indata = np.array([ 

200 [1.0, 2.0, 3.0], 

201 [0.0, 1.0, 2.0], 

202 ]) 

203 self.checkMappingPersistence(pm, indata) 

204 

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) 

213 

214 with self.assertRaises(ValueError): 

215 ast.PolyMap(coeff_f, coeff_i) 

216 

217 with self.assertRaises(ValueError): 

218 ast.PolyMap(coeff_f, 3) 

219 

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) 

234 

235 indata = np.array([ 

236 [1.0, 2.0, 3.0], 

237 [0.0, 1.0, 2.0], 

238 ]) 

239 

240 outdata = pm.applyForward(indata) 

241 

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) 

250 

251 self.checkMappingPersistence(pm, indata) 

252 self.checkMappingPersistence(pm2, indata) 

253 

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) 

270 

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) 

278 

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]) 

284 

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") 

298 

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) 

303 

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)) 

309 

310 def test_PolyMapPolyTranIterInverse(self): 

311 """Test PolyTran on a PolyMap that has an iterative inverse 

312 

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")) 

325 

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")) 

330 

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")) 

336 

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]) 

340 

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]) 

345 

346 def test_PolyMapPolyMapUnivertible(self): 

347 """Test polyTran on a PolyMap without a single-valued inverse 

348 

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") 

357 

358 self.checkBasicSimplify(pm) 

359 self.checkCopy(pm) 

360 

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) 

365 

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) 

370 

371 self.checkMappingPersistence(pm, indata) 

372 

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]) 

377 

378 def test_PolyMapDM10496(self): 

379 """Test for a segfault when simplifying a SeriesMap 

380 

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. 

386 

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 ]) 

398 

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) 

406 

407 

408if __name__ == "__main__": 408 ↛ 409line 408 didn't jump to line 409, because the condition on line 408 was never true

409 unittest.main()