Coverage for tests/test_function.py: 5%

470 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-24 02:29 -0700

1# 

2# LSST Data Management System 

3# Copyright 2008, 2009, 2010 LSST Corporation. 

4# 

5# This product includes software developed by the 

6# LSST Project (http://www.lsst.org/). 

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 LSST License Statement and 

19# the GNU General Public License along with this program. If not, 

20# see <http://www.lsstcorp.org/LegalNotices/>. 

21# 

22 

23import itertools 

24import math 

25import unittest 

26 

27import numpy as np 

28 

29import lsst.utils.tests 

30import lsst.geom 

31import lsst.afw.math as afwMath 

32import lsst.pex.exceptions as pexExceptions 

33 

34 

35def nrange(num, start, delta): 

36 """Return an array of num floats starting with start and incrementing by delta 

37 """ 

38 return np.arange(start, start + (delta * (num - 0.1)), delta) 

39 

40 

41def sinc(x): 

42 """Return the normalized sinc function: sinc(x) = sin(pi * x) / (pi * x) 

43 """ 

44 if abs(x) < 1.0e-15: 

45 return 1.0 

46 return math.sin(math.pi * x) / (math.pi * x) 

47 

48 

49def referenceChebyshev1(x, n): 

50 """Reference implementation of Chebyshev polynomials of the first kind 

51 

52 f(x) = T_n(x) 

53 """ 

54 # from Wikipedia 

55 if n == 0: 

56 return 1.0 

57 if n == 1: 

58 return x 

59 return (2.0 * x * referenceChebyshev1(x, n-1)) - referenceChebyshev1(x, n-2) 

60 

61 

62def referenceChebyshev1Polynomial1(x, params): 

63 """Reference implementation of a 1-D polynomial of Chebyshev polynomials of the first kind 

64 

65 f(x) = params[0] T_0(x) + params[1] T_1(x) + params[2] T_2(x) 

66 """ 

67 retVal = 0.0 

68 for ii in range(len(params)-1, -1, -1): 

69 retVal += params[ii] * referenceChebyshev1(x, ii) 

70 return retVal 

71 

72 

73def referenceChebyshev1Polynomial2(x, y, params): 

74 """Reference implementation of a 2-D polynomial of Chebyshev polynomials of the first kind 

75 

76 f(x) = params[0] T_0(x) T_0(y) # order 0 

77 + params[1] T_1(x) T_0(y) + params[2] T_0(x) T_1(y) # order 1 

78 + params[3] T_2(x) T_0(y) + params[4] T_1(x) T_1(y) + params[5] T_0(x) T_2(y) # order 2 

79 + ... 

80 

81 Raise RuntimeError if the number of parameters does not match an integer order. 

82 """ 

83 retVal = 0.0 

84 order = 0 

85 y_order = 0 

86 for ii in range(0, len(params)): 

87 x_order = order - y_order 

88 retVal += params[ii] * \ 

89 referenceChebyshev1(x, x_order) * referenceChebyshev1(y, y_order) 

90 if x_order > 0: 

91 y_order += 1 

92 else: 

93 order += 1 

94 y_order = 0 

95 if y_order != 0: 

96 raise RuntimeError(f"invalid # of parameters={len(params)}") 

97 return retVal 

98 

99 

100class FunctionTestCase(lsst.utils.tests.TestCase): 

101 def setUp(self): 

102 self.normErr = "Invalid {0} normalization: min={1}, max={2}, min/max norm=({3}, {4}) != (-1, 1)" 

103 # We need a slightly larger than the full floating point tolerance for 

104 # many of these tests. 

105 self.atol = 5e-14 

106 

107 def testChebyshev1Function1D(self): 

108 errMsg = "{}: {} != {} for x={}, xMin={}, xMax={}, xNorm={}, params={}; {}" 

109 maxOrder = 6 

110 deltaParam = 0.3 

111 ranges = ((-1, 1), (-1, 0), (0, 1), (-17, -2), (-65.3, 2.132)) 

112 rangeIter = itertools.cycle(ranges) 

113 nPoints = 9 

114 

115 for order in range(maxOrder + 1): 

116 xMin, xMax = next(rangeIter) 

117 xMean = (xMin + xMax) / 2.0 

118 xDelta = (xMax - xMin) / float(nPoints - 1) 

119 

120 f = afwMath.Chebyshev1Function1D(order, xMin, xMax) 

121 numParams = f.getNParameters() 

122 params = np.arange(deltaParam, deltaParam*numParams + (deltaParam / 2.0), deltaParam) 

123 f.setParameters(params) 

124 g = afwMath.Chebyshev1Function1D(params, xMin, xMax) 

125 h = f.clone() 

126 

127 self.assertEqual(f.getNParameters(), g.getNParameters()) 

128 

129 self.assertEqual(f.getMinX(), xMin) 

130 self.assertEqual(f.getMaxX(), xMax) 

131 self.assertEqual(f.getOrder(), order) 

132 

133 self.assertEqual(g.getMinX(), xMin) 

134 self.assertEqual(g.getMaxX(), xMax) 

135 self.assertEqual(g.getOrder(), order) 

136 

137 minXNorm = None 

138 maxXNorm = None 

139 for x in np.arange(xMin, xMax + xDelta/2.0, xDelta): 

140 xNorm = 2.0 * (x - xMean) / float(xMax - xMin) 

141 if minXNorm is None or xNorm < minXNorm: 

142 minXNorm = xNorm 

143 if maxXNorm is None or xNorm > maxXNorm: 

144 maxXNorm = xNorm 

145 

146 predVal = referenceChebyshev1Polynomial1(xNorm, params) 

147 msg = errMsg.format(type(f).__name__, f(x), predVal, x, xMin, xMax, xNorm, params, 

148 "order constructor") 

149 self.assertFloatsAlmostEqual( 

150 f(x), predVal, msg=msg, atol=self.atol) 

151 msg = errMsg.format(type(g).__name__, g(x), predVal, x, xMin, xMax, xNorm, params, 

152 "params constructor") 

153 self.assertFloatsAlmostEqual( 

154 g(x), predVal, msg=msg, atol=self.atol) 

155 msg = errMsg.format(type(h).__name__, h(x), 

156 predVal, x, xMin, xMax, xNorm, params, 

157 "clone") 

158 self.assertFloatsAlmostEqual( 

159 h(x), predVal, msg=msg, atol=self.atol) 

160 

161 msg = self.normErr.format("x", xMin, xMax, minXNorm, maxXNorm) 

162 self.assertFloatsAlmostEqual( 

163 minXNorm, -1., msg=msg, atol=self.atol) 

164 self.assertFloatsAlmostEqual(maxXNorm, 1., msg=msg, atol=self.atol) 

165 

166 def testChebyshev1Function2D(self): 

167 errMsg = ("{}: {} != {} for x={}, xMin={}, xMax={}, xNorm={}, " 

168 "yMin={}, yMax={}, yNorm={}, params={}; {}") 

169 maxOrder = 6 

170 deltaParam = 0.3 

171 ranges = ((-1, 1), (-1, 0), (0, 1), (-17, -2), (-65.3, 2.132)) 

172 xRangeIter = itertools.cycle(ranges) 

173 yRangeIter = itertools.cycle(ranges) 

174 next(yRangeIter) # make x and y ranges off from each other 

175 nPoints = 7 # number of points in x and y at which to test the functions 

176 

177 for order in range(maxOrder + 1): 

178 xMin, xMax = next(xRangeIter) 

179 xMean = (xMin + xMax) / 2.0 

180 xDelta = (xMax - xMin) / float(nPoints - 1) 

181 

182 yMin, yMax = next(yRangeIter) 

183 yMean = (yMin + yMax) / 2.0 

184 yDelta = (yMax - yMin) / float(nPoints - 1) 

185 

186 xyRange = lsst.geom.Box2D(lsst.geom.Point2D(xMin, yMin), 

187 lsst.geom.Point2D(xMax, yMax)) 

188 

189 f = afwMath.Chebyshev1Function2D(order, xyRange) 

190 numParams = f.getNParameters() 

191 params = nrange(numParams, deltaParam, deltaParam) 

192 f.setParameters(params) 

193 g = afwMath.Chebyshev1Function2D(params, xyRange) 

194 h = f.clone() 

195 

196 self.assertEqual(f.getNParameters(), g.getNParameters()) 

197 self.assertEqual(f.getNParameters(), h.getNParameters()) 

198 

199 self.assertEqual(f.getXYRange(), xyRange) 

200 self.assertEqual(f.getOrder(), order) 

201 

202 self.assertEqual(g.getXYRange(), xyRange) 

203 self.assertEqual(g.getOrder(), order) 

204 

205 # vary x in the inner loop to exercise the caching 

206 minYNorm = None 

207 maxYNorm = None 

208 for y in np.arange(yMin, yMax + yDelta/2.0, yDelta): 

209 yNorm = 2.0 * (y - yMean) / float(yMax - yMin) 

210 if minYNorm is None or yNorm < minYNorm: 

211 minYNorm = yNorm 

212 if maxYNorm is None or yNorm > maxYNorm: 

213 maxYNorm = yNorm 

214 

215 minXNorm = None 

216 maxXNorm = None 

217 for x in np.arange(xMin, xMax + xDelta/2.0, xDelta): 

218 xNorm = 2.0 * (x - xMean) / float(xMax - xMin) 

219 if minXNorm is None or xNorm < minXNorm: 

220 minXNorm = xNorm 

221 if maxXNorm is None or xNorm > maxXNorm: 

222 maxXNorm = xNorm 

223 

224 predVal = referenceChebyshev1Polynomial2( 

225 xNorm, yNorm, params) 

226 

227 msg = errMsg.format(type(f).__name__, f(x, y), predVal, x, xMin, xMax, xNorm, 

228 yMin, yMax, yNorm, params, "order constructor") 

229 self.assertFloatsAlmostEqual( 

230 f(x, y), predVal, msg=msg, atol=self.atol, rtol=None) 

231 msg = errMsg.format(type(g).__name__, g(x, y), predVal, x, xMin, xMax, xNorm, 

232 yMin, yMax, yNorm, params, "params constructor") 

233 self.assertFloatsAlmostEqual( 

234 g(x, y), predVal, msg=msg, atol=self.atol, rtol=None) 

235 msg = errMsg.format(type(h).__name__, h(x, y), predVal, x, xMin, xMax, xNorm, 

236 yMin, yMax, yNorm, params, "order") 

237 self.assertFloatsAlmostEqual( 

238 h(x, y), predVal, msg=msg, atol=self.atol, rtol=None) 

239 

240 msg = self.normErr.format("x", xMin, xMax, minXNorm, maxXNorm) 

241 self.assertFloatsAlmostEqual( 

242 minXNorm, -1., msg=msg, atol=self.atol) 

243 self.assertFloatsAlmostEqual( 

244 maxXNorm, 1., msg=msg, atol=self.atol) 

245 

246 msg = self.normErr.format("y", yMin, yMax, minYNorm, maxYNorm) 

247 self.assertFloatsAlmostEqual( 

248 minYNorm, -1., msg=msg, atol=self.atol) 

249 self.assertFloatsAlmostEqual(maxYNorm, 1., msg=msg, atol=self.atol) 

250 

251 # test that the number of parameters is correct for the given order 

252 def numParamsFromOrder(order): 

253 return (order + 1) * (order + 2) // 2 

254 MaxOrder = 13 

255 for order in range(MaxOrder+1): 

256 f = afwMath.Chebyshev1Function2D(order) 

257 predNParams = numParamsFromOrder(order) 

258 self.assertEqual(f.getNParameters(), predNParams) 

259 afwMath.Chebyshev1Function2D(np.zeros(predNParams, dtype=float)) 

260 

261 # test that the wrong number of parameters raises an exception 

262 validNumParams = set() 

263 for order in range(MaxOrder+1): 

264 validNumParams.add(numParamsFromOrder(order)) 

265 for numParams in range(numParamsFromOrder(MaxOrder)): 

266 if numParams in validNumParams: 

267 continue 

268 with self.assertRaises(pexExceptions.InvalidParameterError): 

269 afwMath.Chebyshev1Function2D(np.zeros(numParams, dtype=float)) 

270 

271 # test that changing parameters clears the cache 

272 # for simplicity use the xyRange that requires no normalization 

273 order = 3 

274 numParams = numParamsFromOrder(order) 

275 f = afwMath.Chebyshev1Function2D(order) 

276 xyRange = lsst.geom.Box2D( 

277 lsst.geom.Point2D(-1.0, -1.0), lsst.geom.Point2D(1.0, 1.0)) 

278 x = 0.5 

279 y = -0.24 

280 for addValue in (0.0, 0.2): 

281 params = nrange(numParams, deltaParam + addValue, deltaParam) 

282 f.setParameters(params) 

283 predVal = referenceChebyshev1Polynomial2(x, y, params) 

284 msg = f"{f(x, y)} != {predVal} for x={x}, y={y}, params={params}" 

285 self.assertFloatsAlmostEqual( 

286 f(x, y), predVal, msg=msg, atol=self.atol, rtol=None) 

287 

288 def testChebyshev1Function2DTruncate(self): 

289 errMsg = ("{} != {} = {} for x={}, xMin={}, xMax={}, xNorm={}," 

290 " yMin={}, yMax={}, yNorm={}, truncParams={}; order constructor") 

291 

292 maxOrder = 6 

293 deltaParam = 0.3 

294 ranges = ((-1, 1), (-17, -2), (-65.3, 2.132)) 

295 xRangeIter = itertools.cycle(ranges) 

296 yRangeIter = itertools.cycle(ranges) 

297 next(yRangeIter) # make x and y ranges off from each other 

298 nPoints = 7 # number of points in x and y at which to test the functions 

299 

300 for order in range(maxOrder + 1): 

301 xMin, xMax = next(xRangeIter) 

302 xMean = (xMin + xMax) / 2.0 

303 xDelta = (xMax - xMin) / float(nPoints - 1) 

304 

305 yMin, yMax = next(yRangeIter) 

306 yMean = (yMin + yMax) / 2.0 

307 yDelta = (yMax - yMin) / float(nPoints - 1) 

308 

309 xyRange = lsst.geom.Box2D(lsst.geom.Point2D(xMin, yMin), 

310 lsst.geom.Point2D(xMax, yMax)) 

311 

312 fullNParams = afwMath.Chebyshev1Function2D.nParametersFromOrder( 

313 order) 

314 fullParams = nrange(fullNParams, deltaParam, deltaParam) 

315 fullPoly = afwMath.Chebyshev1Function2D(fullParams, xyRange) 

316 

317 for tooBigTruncOrder in range(order + 1, order + 3): 

318 with self.assertRaises(pexExceptions.InvalidParameterError): 

319 fullPoly.truncate(tooBigTruncOrder) 

320 

321 for truncOrder in range(order + 1): 

322 truncNParams = fullPoly.nParametersFromOrder(truncOrder) 

323 truncParams = fullParams[0:truncNParams] 

324 

325 f = fullPoly.truncate(truncOrder) 

326 self.assertEqual(f.getNParameters(), truncNParams) 

327 

328 g = afwMath.Chebyshev1Function2D( 

329 fullParams[0:truncNParams], xyRange) 

330 

331 self.assertEqual(f.getNParameters(), g.getNParameters()) 

332 

333 self.assertEqual(f.getOrder(), truncOrder) 

334 self.assertEqual(f.getXYRange(), xyRange) 

335 

336 self.assertEqual(g.getOrder(), truncOrder) 

337 self.assertEqual(g.getXYRange(), xyRange) 

338 

339 minXNorm = None 

340 maxXNorm = None 

341 for x in np.arange(xMin, xMax + xDelta/2.0, xDelta): 

342 xNorm = 2.0 * (x - xMean) / float(xMax - xMin) 

343 if minXNorm is None or xNorm < minXNorm: 

344 minXNorm = xNorm 

345 if maxXNorm is None or xNorm > maxXNorm: 

346 maxXNorm = xNorm 

347 

348 minYNorm = None 

349 maxYNorm = None 

350 for y in np.arange(yMin, yMax + yDelta/2.0, yDelta): 

351 yNorm = 2.0 * (y - yMean) / float(yMax - yMin) 

352 if minYNorm is None or yNorm < minYNorm: 

353 minYNorm = yNorm 

354 if maxYNorm is None or yNorm > maxYNorm: 

355 maxYNorm = yNorm 

356 

357 msg = errMsg.format(type(f).__name__, f(x, y), g(x, y), type(g).__name__, 

358 x, xMin, xMax, xNorm, yMin, yMax, yNorm, truncParams) 

359 self.assertFloatsAlmostEqual( 

360 f(x, y), g(x, y), msg=msg) 

361 

362 msg = self.normErr.format( 

363 "y", yMin, yMax, minYNorm, maxYNorm) 

364 self.assertFloatsAlmostEqual( 

365 minYNorm, -1.0, msg=msg, atol=self.atol, rtol=None) 

366 self.assertFloatsAlmostEqual( 

367 maxYNorm, 1.0, msg=msg, atol=self.atol, rtol=None) 

368 

369 msg = self.normErr.format("x", xMin, xMax, minXNorm, maxXNorm) 

370 self.assertFloatsAlmostEqual( 

371 minXNorm, -1.0, msg=msg, atol=self.atol, rtol=None) 

372 self.assertFloatsAlmostEqual( 

373 maxXNorm, 1.0, msg=msg, atol=self.atol, rtol=None) 

374 

375 def testGaussianFunction1D(self): 

376 def basicGaussian(x, sigma): 

377 return (1.0 / (sigma * math.sqrt(2 * math.pi))) * math.exp(-x**2 / (2.0 * sigma**2)) 

378 

379 f = afwMath.GaussianFunction1D(1.0) 

380 for xsigma in (0.1, 1.0, 3.0): 

381 f.setParameters((xsigma,)) 

382 g = f.clone() 

383 xdelta = xsigma / 10.0 

384 fSum = 0.0 

385 for x in np.arange(-xsigma * 20, xsigma * 20.01, xdelta): 

386 predVal = basicGaussian(x, xsigma) 

387 fSum += predVal 

388 msg = f"{type(f).__name__} = {f(x)} != {predVal} for x={x}, xsigma={xsigma}" 

389 self.assertFloatsAlmostEqual( 

390 f(x), predVal, msg=msg, atol=self.atol, rtol=None) 

391 msg += "; clone" 

392 self.assertFloatsAlmostEqual( 

393 g(x), predVal, msg=msg, atol=self.atol, rtol=None) 

394 approxArea = fSum * xdelta 

395 msg = f"{type(f).__name__} area = {approxArea} != 1.0 for xsigma={xsigma}" 

396 self.assertFloatsAlmostEqual( 

397 approxArea, 1.0, msg=msg, atol=self.atol, rtol=None) 

398 

399 def testGaussianFunction2D(self): 

400 """Note: Assumes GaussianFunction1D is correct (tested elsewhere).""" 

401 errMsg = "{} = {} != {} for pos1={}, pos2={}, x={}, y={}, sigma1={}, sigma2={}, angle={}" 

402 areaMsg = "%s area = %s != 1.0 for sigma1=%s, sigma2=%s" 

403 f = afwMath.GaussianFunction2D(1.0, 1.0, 0.0) 

404 f1 = afwMath.GaussianFunction1D(1.0) 

405 f2 = afwMath.GaussianFunction1D(1.0) 

406 for sigma1 in (0.1, 1.0, 3.0): 

407 for sigma2 in (0.1, 1.0, 3.0): 

408 for angle in (0.0, 0.4, 1.1): 

409 sinNegAngle = math.sin(-angle) 

410 cosNegAngle = math.cos(-angle) 

411 f.setParameters((sigma1, sigma2, angle)) 

412 g = f.clone() 

413 f1.setParameters((sigma1,)) 

414 f2.setParameters((sigma2,)) 

415 fSum = 0.0 

416 delta1 = sigma1 / 5.0 

417 delta2 = sigma2 / 5.0 

418 for pos1 in np.arange(-sigma1 * 5, sigma1 * 5.01, delta1): 

419 for pos2 in np.arange(-sigma2 * 5.0, sigma2 * 5.01, delta2): 

420 x = (cosNegAngle * pos1) + (sinNegAngle * pos2) 

421 y = (-sinNegAngle * pos1) + (cosNegAngle * pos2) 

422 predVal = f1(pos1) * f2(pos2) 

423 fSum += predVal 

424 msg = errMsg.format(type(f).__name__, f(x, y), predVal, 

425 pos1, pos2, x, y, sigma1, sigma2, angle) 

426 self.assertFloatsAlmostEqual( 

427 f(x, y), predVal, msg=msg, atol=self.atol, rtol=None) 

428 msg = errMsg.format(type(g).__name__, g(x, y), predVal, 

429 pos1, pos2, x, y, sigma1, sigma2, angle) + "; clone" 

430 self.assertFloatsAlmostEqual( 

431 g(x, y), predVal, msg=msg, atol=self.atol, rtol=None) 

432 approxArea = fSum * delta1 * delta2 

433 msg = areaMsg % (type(f).__name__, 

434 approxArea, sigma1, sigma2) 

435 # approxArea is very approximate, so we need a high 

436 # tolerance threshold. 

437 self.assertFloatsAlmostEqual( 

438 approxArea, 1.0, msg=msg, atol=1e-6, rtol=None) 

439 

440 def testDoubleGaussianFunction2D(self): 

441 """Note: Assumes GaussianFunction2D is correct (tested elsewhere).""" 

442 errMsg = "{} = {} != {} for x={}, y={}, sigma1={}, sigma2={}, b={}" 

443 areaMsg = "{} area = {} != 1.0 for sigma1={}, sigma2={}" 

444 f = afwMath.DoubleGaussianFunction2D(1.0, 1.0) 

445 f1 = afwMath.GaussianFunction2D(1.0, 1.0, 0.0) 

446 f2 = afwMath.GaussianFunction2D(1.0, 1.0, 0.0) 

447 for sigma1 in (1.0,): 

448 for sigma2 in (0.5, 2.0): 

449 for b in (0.0, 0.2, 2.0): 

450 f.setParameters((sigma1, sigma2, b)) 

451 g = f.clone() 

452 f1.setParameters((sigma1, sigma1, 0.0)) 

453 f2.setParameters((sigma2, sigma2, 0.0)) 

454 sigma1Sq = sigma1**2 

455 sigma2Sq = sigma2**2 

456 f1Mult = b * sigma2Sq / sigma1Sq 

457 allMult = sigma1Sq / (sigma1Sq + (b * sigma2Sq)) 

458 fSum = 0.0 

459 maxsigma = max(sigma1, sigma2) 

460 minsigma = min(sigma1, sigma2) 

461 delta = minsigma / 5.0 

462 for y in np.arange(-maxsigma * 5, maxsigma * 5.01, delta): 

463 for x in np.arange(-maxsigma * 5.0, maxsigma * 5.01, delta): 

464 predVal = ( 

465 f1(x, y) + (f1Mult * f2(x, y))) * allMult 

466 fSum += predVal 

467 msg = errMsg.format( 

468 type(f).__name__, 

469 f(x, y), predVal, x, y, sigma1, sigma2, b) 

470 self.assertFloatsAlmostEqual( 

471 f(x, y), predVal, msg=msg, atol=self.atol, rtol=None) 

472 msg = errMsg.format(type(g).__name__, g(x, y), predVal, 

473 x, y, sigma1, sigma2, b) + "; clone" 

474 self.assertFloatsAlmostEqual( 

475 g(x, y), predVal, msg=msg, atol=self.atol, rtol=None) 

476 approxArea = fSum * delta**2 

477 msg = areaMsg.format( 

478 type(f).__name__, approxArea, sigma1, sigma2) 

479 # approxArea is very approximate, so we need a high 

480 # tolerance threshold. 

481 self.assertFloatsAlmostEqual( 

482 approxArea, 1.0, msg=msg, atol=1e-6, rtol=None) 

483 

484 def testIntegerDeltaFunction2D(self): 

485 def basicDelta(x, xo): 

486 return (x == xo) 

487 

488 errMsg = "{} = {} != {} for x={}, y={}, xo={}, yo={}" 

489 for xo in np.arange(-5.0, 5.0, 1.0): 

490 for yo in np.arange(-5.0, 5.0, 1.0): 

491 f = afwMath.IntegerDeltaFunction2D(xo, yo) 

492 g = f.clone() 

493 for x in np.arange(-5.0, 5.0, 1.0): 

494 for y in np.arange(-5.0, 5.0, 1.0): 

495 predVal = basicDelta(x, xo) * basicDelta(y, yo) 

496 msg = errMsg.format(type(f).__name__, 

497 f(x, y), predVal, x, y, xo, yo) 

498 self.assertFloatsAlmostEqual( 

499 f(x, y), predVal, msg=msg, atol=self.atol, rtol=None) 

500 msg = errMsg.format(type(g).__name__, 

501 g(x, y), predVal, x, y, xo, yo) + "; clone" 

502 self.assertFloatsAlmostEqual( 

503 g(x, y), predVal, msg=msg, atol=self.atol, rtol=None) 

504 

505 def testLanczosFunction1D(self): 

506 def basicLanczos1(x, n): 

507 return sinc(x) * sinc(x / float(n)) 

508 

509 errMsg = "{} = {} != {} for n={}, x={}, xOffset={}, xAdj={}" 

510 for n in range(1, 5): 

511 f = afwMath.LanczosFunction1D(n) 

512 self.assertEqual(f.getOrder(), n) 

513 

514 for xOffset in (-10.0, 0.0, 0.05): 

515 f.setParameters((xOffset,)) 

516 g = f.clone() 

517 for x in np.arange(-10.0, 10.1, 0.50): 

518 xAdj = x - xOffset 

519 predVal = basicLanczos1(xAdj, n) 

520 msg = errMsg.format(type(f).__name__, 

521 f(x), predVal, n, x, xOffset, xAdj) 

522 self.assertFloatsAlmostEqual( 

523 f(x), predVal, msg=msg, atol=self.atol, rtol=None) 

524 msg = errMsg.format(type(g).__name__, 

525 g(x), predVal, n, x, xOffset, xAdj) + "; clone" 

526 self.assertFloatsAlmostEqual( 

527 g(x), predVal, msg=msg, atol=self.atol, rtol=None) 

528 

529 def testLanczosFunction2D(self): 

530 def basicLanczos1(x, n): 

531 return sinc(x) * sinc(x / float(n)) 

532 

533 errMsg = "{} = {} != {} for n={}, x={}, xOffset={}, yOffset={}, xAdj={}, yAdj={}" 

534 for n in range(1, 5): 

535 f = afwMath.LanczosFunction2D(n) 

536 self.assertEqual(f.getOrder(), n) 

537 

538 for xOffset in (-10.0, 0.0, 0.05): 

539 for yOffset in (-0.01, 0.0, 7.5): 

540 f.setParameters((xOffset, yOffset)) 

541 g = f.clone() 

542 for x in np.arange(-10.0, 10.1, 2.0): 

543 for y in np.arange(-10.0, 10.1, 2.0): 

544 xAdj = x - xOffset 

545 yAdj = y - yOffset 

546 predVal = basicLanczos1(xAdj, n) * \ 

547 basicLanczos1(yAdj, n) 

548 msg = errMsg.format(type(f).__name__, f(x, y), predVal, n, 

549 x, xOffset, yOffset, xAdj, yAdj) 

550 self.assertFloatsAlmostEqual( 

551 f(x, y), predVal, msg=msg, atol=self.atol, rtol=None) 

552 msg = errMsg.format(type(g).__name__, g(x, y), predVal, n, 

553 x, xOffset, yOffset, xAdj, yAdj) 

554 self.assertFloatsAlmostEqual( 

555 g(x, y), predVal, msg=msg, atol=self.atol, rtol=None) 

556 

557 def testPolynomialFunction1D(self): 

558 def basic1DPoly(x, params): 

559 ii = len(params) - 1 

560 retVal = params[ii] 

561 while ii > 0: 

562 ii -= 1 

563 retVal = retVal * x + params[ii] 

564 return retVal 

565 

566 maxOrder = 4 

567 deltaParam = 0.3 

568 errMsg = "{} = {} != {} for x={}, params={}; {}" 

569 

570 # test value using order constructor 

571 for order in range(maxOrder): 

572 numParams = order + 1 

573 params = nrange(numParams, deltaParam, deltaParam) 

574 f = afwMath.PolynomialFunction1D(params) 

575 g = afwMath.PolynomialFunction1D(order) 

576 g.setParameters(params) 

577 h = f.clone() 

578 

579 self.assertEqual(f.getOrder(), order) 

580 self.assertEqual(g.getOrder(), order) 

581 

582 for x in np.arange(-10.0, 10.1, 1.0): 

583 predVal = basic1DPoly(x, params) 

584 msg = errMsg.format(type(f).__name__, 

585 f(x), predVal, x, params, 

586 "params constructor") 

587 self.assertFloatsAlmostEqual( 

588 f(x), predVal, msg=msg, atol=self.atol, rtol=1E-13) 

589 msg = errMsg.format(type(g).__name__, 

590 g(x), predVal, x, params, 

591 "order constructor") 

592 self.assertFloatsAlmostEqual( 

593 g(x), predVal, msg=msg, atol=self.atol, rtol=1E-13) 

594 msg = errMsg.format(type(h).__name__, 

595 h(x), predVal, x, params, "clone") 

596 self.assertFloatsAlmostEqual( 

597 h(x), predVal, msg=msg, atol=self.atol, rtol=1E-13) 

598 

599 def testPolynomialFunction2D(self): 

600 def basic2DPoly(x, y, params): 

601 retVal = 0 

602 numParams = len(params) 

603 order = 0 

604 ii = 0 

605 while True: 

606 for yOrder in range(order+1): 

607 xOrder = order - yOrder 

608 retVal += params[ii] * x**xOrder * y**yOrder 

609 ii += 1 

610 if ii >= numParams: 

611 if xOrder != 0: 

612 raise RuntimeError(f"invalid # params={numParams}") 

613 return retVal 

614 order += 1 

615 

616 numParamsList = (1, 3, 6, 10) 

617 deltaParam = 0.3 

618 errMsg = "{} = {} != {} for x={}, y={}, params={}; {}" 

619 

620 # test function values 

621 for order, numParams in enumerate(numParamsList): 

622 params = nrange(numParams, deltaParam, deltaParam) 

623 f = afwMath.PolynomialFunction2D(params) 

624 g = afwMath.PolynomialFunction2D(order) 

625 g.setParameters(params) 

626 h = f.clone() 

627 

628 self.assertEqual(f.getOrder(), order) 

629 self.assertEqual(g.getOrder(), order) 

630 

631 # vary x in the inner loop to exercise the caching 

632 for y in np.arange(-10.0, 10.1, 2.5): 

633 for x in np.arange(-10.0, 10.1, 2.5): 

634 predVal = basic2DPoly(x, y, params) 

635 msg = errMsg.format(type(f).__name__, f(x, y), predVal, 

636 x, y, params, "params constructor") 

637 self.assertFloatsAlmostEqual( 

638 f(x, y), predVal, msg=msg, atol=2e-12, rtol=None) 

639 msg = errMsg.format(type(g).__name__, 

640 g(x, y), predVal, x, y, params, 

641 "order constructor") 

642 self.assertFloatsAlmostEqual( 

643 g(x, y), predVal, msg=msg, atol=2e-12, rtol=None) 

644 msg = errMsg.format(type(h).__name__, 

645 h(x, y), predVal, x, y, params, "clone") 

646 self.assertFloatsAlmostEqual( 

647 h(x, y), predVal, msg=msg, atol=2e-12, rtol=None) 

648 

649 # test that the number of parameters is correct for the given order 

650 def numParamsFromOrder(order): 

651 return (order + 1) * (order + 2) // 2 

652 MaxOrder = 13 

653 for order in range(MaxOrder+1): 

654 f = afwMath.PolynomialFunction2D(order) 

655 predNParams = numParamsFromOrder(order) 

656 self.assertEqual(f.getNParameters(), predNParams) 

657 afwMath.PolynomialFunction2D(np.zeros(predNParams, dtype=float)) 

658 

659 # test that the wrong number of parameters raises an exception 

660 validNumParams = set() 

661 for order in range(MaxOrder+1): 

662 validNumParams.add(numParamsFromOrder(order)) 

663 for numParams in range(numParamsFromOrder(MaxOrder)): 

664 if numParams in validNumParams: 

665 continue 

666 with self.assertRaises(pexExceptions.InvalidParameterError): 

667 afwMath.PolynomialFunction2D(np.zeros(numParams, dtype=float)) 

668 

669 # test that changing parameters clears the cache 

670 order = 3 

671 numParams = numParamsFromOrder(order) 

672 f = afwMath.PolynomialFunction2D(order) 

673 x = 0.5 

674 y = -0.24 

675 for addValue in (0.0, 0.2): 

676 params = nrange(numParams, deltaParam + addValue, deltaParam) 

677 f.setParameters(params) 

678 predVal = basic2DPoly(x, y, params) 

679 msg = errMsg.format(type(f).__name__, f(x, y), 

680 predVal, x, y, params, "") 

681 self.assertFloatsAlmostEqual( 

682 f(x, y), predVal, msg=msg, atol=self.atol, rtol=None) 

683 

684 def testDFuncDParameters(self): 

685 """Test that we can differentiate the Function2 with respect to its parameters""" 

686 nOrder = 3 

687 params = [] 

688 for i in range((nOrder + 1)*(nOrder + 2)//2): 

689 # deterministic pretty-random numbers 

690 params.append(math.sin(1 + i)) 

691 

692 f = afwMath.PolynomialFunction2D(params) 

693 

694 for (x, y) in [(2, 1), (1, 2), (2, 2)]: 

695 dFdC = f.getDFuncDParameters(x, y) 

696 

697 self.assertAlmostEqual( 

698 f(x, y), 

699 sum([params[i]*dFdC[i] for i in range(len(params))])) 

700 

701 

702class MemoryTester(lsst.utils.tests.MemoryTestCase): 

703 pass 

704 

705 

706def setup_module(module): 

707 lsst.utils.tests.init() 

708 

709 

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

711 lsst.utils.tests.init() 

712 unittest.main()