Coverage for tests/test_softenedLinearPrior.py: 18%

128 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-27 09:57 +0000

1# 

2# LSST Data Management System 

3# 

4# Copyright 2008-2016 AURA/LSST. 

5# 

6# This product includes software developed by the 

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

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the LSST License Statement and 

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

21# see <https://www.lsstcorp.org/LegalNotices/>. 

22# 

23import unittest 

24import numpy 

25 

26import lsst.utils.tests 

27import lsst.log 

28import lsst.utils.logging 

29import lsst.meas.modelfit 

30 

31try: 

32 import scipy.integrate 

33except ImportError: 

34 scipy = None 

35 

36lsst.utils.logging.trace_set_at("lsst.meas.modelfit.SoftenedLinearPrior", 5) 

37 

38 

39class SoftenedLinearPriorTestCase(lsst.utils.tests.TestCase): 

40 

41 NUM_DIFF_STEP = 1E-3 

42 

43 def setUp(self): 

44 # a prior with broad ramps and non-zero slope; broad ramps makes evaluating numerical 

45 # derivatives easier, and we want to do that to check the analytic ones 

46 numpy.random.seed(500) 

47 ctrl = lsst.meas.modelfit.SoftenedLinearPrior.Control() 

48 ctrl.logRadiusMinOuter = ctrl.logRadiusMinInner - 2.0 

49 ctrl.logRadiusMaxOuter = ctrl.logRadiusMaxInner + 2.0 

50 ctrl.ellipticityMaxOuter = ctrl.ellipticityMaxInner + 2.0 

51 ctrl.logRadiusMinMaxRatio = 2.0 

52 self.prior = lsst.meas.modelfit.SoftenedLinearPrior(ctrl) 

53 self.amplitudes = numpy.array([1.0], dtype=lsst.meas.modelfit.Scalar) 

54 

55 def tearDown(self): 

56 del self.prior 

57 del self.amplitudes 

58 

59 def evaluatePrior(self, e1, e2, r): 

60 b = numpy.broadcast(e1, e2, r) 

61 p = numpy.zeros(b.shape, dtype=lsst.meas.modelfit.Scalar) 

62 for i, (e1i, e2i, ri) in enumerate(b): 

63 p.flat[i] = self.prior.evaluate(numpy.array([e1i, e2i, ri]), self.amplitudes) 

64 return p 

65 

66 def checkDerivatives(self, e1, e2, r): 

67 nonlinear = numpy.array([e1, e2, r], dtype=lsst.meas.modelfit.Scalar) 

68 amplitudeGradient = numpy.zeros(1, dtype=lsst.meas.modelfit.Scalar) 

69 amplitudeHessian = numpy.zeros((1, 1), dtype=lsst.meas.modelfit.Scalar) 

70 crossHessian = numpy.zeros((3, 1), dtype=lsst.meas.modelfit.Scalar) 

71 nonlinearGradient = numpy.zeros(3, dtype=lsst.meas.modelfit.Scalar) 

72 nonlinearHessian = numpy.zeros((3, 3), dtype=lsst.meas.modelfit.Scalar) 

73 self.prior.evaluateDerivatives(nonlinear, self.amplitudes, 

74 nonlinearGradient, amplitudeGradient, 

75 nonlinearHessian, amplitudeHessian, 

76 crossHessian) 

77 p = self.prior.evaluate(nonlinear, self.amplitudes) 

78 for i in range(3): 

79 nonlinearA = nonlinear.copy() 

80 nonlinearB = nonlinear.copy() 

81 nonlinearA[i] -= self.NUM_DIFF_STEP 

82 nonlinearB[i] += self.NUM_DIFF_STEP 

83 pA = self.prior.evaluate(nonlinearA, self.amplitudes) 

84 pB = self.prior.evaluate(nonlinearB, self.amplitudes) 

85 dp = (pB - pA) / (2*self.NUM_DIFF_STEP) 

86 self.assertFloatsAlmostEqual(nonlinearGradient[i], dp, rtol=1E-3, atol=1E-8) 

87 d2p = (pA + pB - 2*p) / self.NUM_DIFF_STEP**2 

88 self.assertFloatsAlmostEqual(nonlinearHessian[i, i], d2p, rtol=1E-3, atol=1E-8) 

89 for j in range(i+1, 3): 

90 nonlinearAA = nonlinearA.copy() 

91 nonlinearAB = nonlinearA.copy() 

92 nonlinearBA = nonlinearB.copy() 

93 nonlinearBB = nonlinearB.copy() 

94 nonlinearAA[j] -= self.NUM_DIFF_STEP 

95 nonlinearAB[j] += self.NUM_DIFF_STEP 

96 nonlinearBA[j] -= self.NUM_DIFF_STEP 

97 nonlinearBB[j] += self.NUM_DIFF_STEP 

98 pAA = self.prior.evaluate(nonlinearAA, self.amplitudes) 

99 pAB = self.prior.evaluate(nonlinearAB, self.amplitudes) 

100 pBA = self.prior.evaluate(nonlinearBA, self.amplitudes) 

101 pBB = self.prior.evaluate(nonlinearBB, self.amplitudes) 

102 d2p = (pBB - pAB - pBA + pAA) / (4*self.NUM_DIFF_STEP**2) 

103 self.assertFloatsAlmostEqual(nonlinearHessian[i, j], d2p, rtol=1E-3, atol=1E-8) 

104 

105 def testDerivatives(self): 

106 """Test that evaluateDerivatives() returns results similar to finite-differences 

107 on evaluate(). 

108 """ 

109 ctrl = self.prior.getControl() 

110 # a single |e| value for each ellipticity zone 

111 ellipticityPoints = numpy.array([0.5*ctrl.ellipticityMaxInner, 

112 0.5*(ctrl.ellipticityMaxInner + ctrl.ellipticityMaxOuter)]) 

113 # a single ln(radius) value for each logRadius zone 

114 logRadiusPoints = numpy.array([0.5*(ctrl.logRadiusMinOuter + ctrl.logRadiusMinInner), 

115 0.5*(ctrl.logRadiusMinInner + ctrl.logRadiusMaxInner), 

116 0.5*(ctrl.logRadiusMaxInner + ctrl.logRadiusMaxOuter)]) 

117 # a range of position angles 

118 thetaPoints = numpy.linspace(0.0, numpy.pi, 5) 

119 for theta in thetaPoints: 

120 for e in ellipticityPoints: 

121 e1 = e*numpy.cos(2.0*theta) 

122 e2 = e*numpy.sin(2.0*theta) 

123 for r in logRadiusPoints: 

124 self.checkDerivatives(e1, e2, r) 

125 

126 @unittest.skipIf(scipy is None, "could not import scipy") 

127 def testIntegral(self): 

128 """Test that the prior is properly normalized. 

129 

130 Normally, this test has a very low bar, because it's expensive to compute a high-quality 

131 numerical integral to compare with. Even so, the scipy integrator does better than it 

132 thinks it does, and we use that smaller tolerance for the test. That means this test 

133 could fail if something about the scipy integrator changes, because we're not telling it 

134 that it has to get as close as it currently is (because doing so would take way too long). 

135 

136 If this class is ever changed, we should do at least one of this test with the tolerances 

137 tightened. 

138 """ 

139 ctrl = self.prior.getControl() 

140 integral, absErr = scipy.integrate.tplquad( 

141 self.evaluatePrior, 

142 ctrl.logRadiusMinOuter, ctrl.logRadiusMaxOuter, 

143 lambda logR: -ctrl.ellipticityMaxOuter, 

144 lambda logR: ctrl.ellipticityMaxOuter, 

145 lambda logR, e2: -(ctrl.ellipticityMaxOuter**2 - e2**2)**0.5, 

146 lambda logR, e2: (ctrl.ellipticityMaxOuter**2 - e2**2)**0.5, 

147 epsabs=1.0, 

148 epsrel=1.0, 

149 ) 

150 self.assertFloatsAlmostEqual(integral, 1.0, atol=0.01) 

151 

152 def testEllipticityDistribution(self): 

153 """Test that the ellipticity distribution is constant in the inner region, 

154 mononotically decreasing in the ramp, and zero in the outer region, according 

155 to evaluate(). 

156 """ 

157 ctrl = self.prior.getControl() 

158 # a range of |e| values in each ellipticity zone 

159 eInnerPoints = numpy.linspace(0.0, ctrl.ellipticityMaxInner, 5) 

160 eRampPoints = numpy.linspace(ctrl.ellipticityMaxInner, ctrl.ellipticityMaxOuter, 5) 

161 eOuterPoints = numpy.linspace(ctrl.ellipticityMaxOuter, ctrl.ellipticityMaxOuter + 5.0, 5) 

162 # a range of position angles 

163 thetaPoints = numpy.linspace(0.0, numpy.pi, 5) 

164 # a single ln(radius) value for each logRadius zone 

165 logRadiusPoints = numpy.array([0.5*(ctrl.logRadiusMinOuter + ctrl.logRadiusMinInner), 

166 0.5*(ctrl.logRadiusMinInner + ctrl.logRadiusMaxInner), 

167 0.5*(ctrl.logRadiusMaxInner + ctrl.logRadiusMaxOuter)]) 

168 for logRadius in logRadiusPoints: 

169 for theta in thetaPoints: 

170 # All inner points should have the same value 

171 pInner = self.evaluatePrior(eInnerPoints*numpy.cos(2*theta), 

172 eInnerPoints*numpy.sin(2*theta), 

173 logRadius) 

174 self.assertFloatsAlmostEqual(pInner.mean(), pInner) 

175 

176 # Each ramp point should be greater than the next one 

177 pRamp = self.evaluatePrior(eRampPoints*numpy.cos(2*theta), 

178 eRampPoints*numpy.sin(2*theta), 

179 logRadius) 

180 numpy.testing.assert_array_less(pRamp[1:], pRamp[:-1]) 

181 

182 # Each outer point should be zero 

183 pOuter = self.evaluatePrior(eOuterPoints*numpy.cos(2*theta), 

184 eOuterPoints*numpy.sin(2*theta), 

185 logRadius) 

186 self.assertFloatsAlmostEqual(pOuter, 0.0) 

187 

188 def testLogRadiusDistribution(self): 

189 """Test that the ln(radius) distribution is constant in the inner region, 

190 mononotically decreasing in the ramps, and zero in the outer regions, according 

191 to evaluate(). 

192 """ 

193 ctrl = self.prior.getControl() 

194 # a range of ln(radius) values in each logRadius zone 

195 rLowerOuterPoints = numpy.linspace(ctrl.logRadiusMinOuter - 2.0, ctrl.logRadiusMinOuter, 5) 

196 rLowerRampPoints = numpy.linspace(ctrl.logRadiusMinOuter, ctrl.logRadiusMinInner, 5) 

197 rInnerPoints = numpy.linspace(ctrl.logRadiusMinInner, ctrl.logRadiusMaxInner, 5) 

198 rUpperRampPoints = numpy.linspace(ctrl.logRadiusMaxInner, ctrl.logRadiusMaxOuter, 5) 

199 rUpperOuterPoints = numpy.linspace(ctrl.logRadiusMaxOuter, ctrl.logRadiusMaxOuter + 2.0, 5) 

200 

201 # a range of position angles 

202 thetaPoints = numpy.linspace(0.0, numpy.pi, 5) 

203 # a single |e| value for each ellipticity zone 

204 ellipticityPoints = numpy.array([0.5*ctrl.ellipticityMaxInner, 

205 0.5*(ctrl.ellipticityMaxInner + ctrl.ellipticityMaxOuter)]) 

206 for ellipticity in ellipticityPoints: 

207 for theta in thetaPoints: 

208 e1 = ellipticity*numpy.cos(2*theta) 

209 e2 = ellipticity*numpy.sin(2*theta) 

210 # Outer points should be zero 

211 pLowerOuter = self.evaluatePrior(e1, e2, rLowerOuterPoints) 

212 self.assertFloatsAlmostEqual(pLowerOuter, 0.0) 

213 # Each ramp point should be less than the next one 

214 pLowerRamp = self.evaluatePrior(e1, e2, rLowerRampPoints) 

215 numpy.testing.assert_array_less(pLowerRamp[:-1], pLowerRamp[1:]) 

216 # All adjacent inner points should have the same distance between them (constant slope) 

217 pInner = self.evaluatePrior(e1, e2, rInnerPoints) 

218 diffs = pInner[1:] - pInner[:-1] 

219 self.assertFloatsAlmostEqual(diffs.mean(), diffs) 

220 # Each ramp point should be greater than the next one 

221 pUpperRamp = self.evaluatePrior(e1, e2, rUpperRampPoints) 

222 numpy.testing.assert_array_less(pUpperRamp[1:], pUpperRamp[:-1]) 

223 # Outer points should be zero 

224 pUpperOuter = self.evaluatePrior(e1, e2, rUpperOuterPoints) 

225 self.assertFloatsAlmostEqual(pUpperOuter, 0.0) 

226 

227 

228class TestMemory(lsst.utils.tests.MemoryTestCase): 

229 pass 

230 

231 

232def setup_module(module): 

233 lsst.utils.tests.init() 

234 

235 

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

237 lsst.utils.tests.init() 

238 unittest.main()