Coverage for tests/test_metrics.py: 27%

242 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-09 12:10 +0000

1# This file is part of ap_association. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

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 GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22import unittest 

23import unittest.mock 

24 

25import astropy.units as u 

26 

27import lsst.utils.tests 

28from lsst.pex.config import Config 

29from lsst.daf.base import PropertySet 

30from lsst.dax.apdb import Apdb 

31from lsst.pipe.base import Task, Struct 

32import lsst.pipe.base.testUtils 

33from lsst.verify import Name 

34from lsst.verify.tasks import MetricComputationError 

35from lsst.verify.tasks.testUtils import MetricTaskTestCase, MetadataMetricTestCase, ApdbMetricTestCase 

36 

37from lsst.ap.association.metrics import \ 

38 NumberNewDiaObjectsMetricTask, \ 

39 NumberUnassociatedDiaObjectsMetricTask, \ 

40 FractionUpdatedDiaObjectsMetricTask, \ 

41 NumberSolarSystemObjectsMetricTask, \ 

42 NumberAssociatedSolarSystemObjectsMetricTask, \ 

43 TotalUnassociatedDiaObjectsMetricTask 

44 

45 

46def _makeAssociationMetadata(numUpdated=27, numNew=4, numUnassociated=15, numSso=5, numAssocSso=1): 

47 metadata = PropertySet() 

48 metadata.add("association.numUpdatedDiaObjects", numUpdated) 

49 metadata.add("association.numNewDiaObjects", numNew) 

50 metadata.add("association.numTotalSolarSystemObjects", numSso) 

51 metadata.add("association.numAssociatedSsObjects", numAssocSso) 

52 metadata.add("association.numUnassociatedDiaObjects", numUnassociated) 

53 return metadata 

54 

55 

56class TestNewDiaObjects(MetadataMetricTestCase): 

57 

58 @classmethod 

59 def makeTask(cls): 

60 return NumberNewDiaObjectsMetricTask() 

61 

62 def testValid(self): 

63 metadata = _makeAssociationMetadata() 

64 result = self.task.run(metadata) 

65 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

66 meas = result.measurement 

67 

68 self.assertEqual(meas.metric_name, Name(metric="ap_association.numNewDiaObjects")) 

69 self.assertEqual(meas.quantity, metadata.getAsDouble("association.numNewDiaObjects") * u.count) 

70 

71 def testNoNew(self): 

72 metadata = _makeAssociationMetadata(numNew=0) 

73 result = self.task.run(metadata) 

74 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

75 meas = result.measurement 

76 

77 self.assertEqual(meas.metric_name, Name(metric="ap_association.numNewDiaObjects")) 

78 self.assertEqual(meas.quantity, 0.0 * u.count) 

79 

80 def testAssociationFailed(self): 

81 try: 

82 result = self.task.run(PropertySet()) 

83 except lsst.pipe.base.NoWorkFound: 

84 # Correct behavior 

85 pass 

86 else: 

87 # Alternative correct behavior 

88 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

89 meas = result.measurement 

90 self.assertIsNone(meas) 

91 

92 def testBadlyTypedKeys(self): 

93 metadata = _makeAssociationMetadata() 

94 metadata.set("association.numNewDiaObjects", "Ultimate Answer") 

95 

96 with self.assertRaises(MetricComputationError): 

97 self.task.run(metadata) 

98 

99 

100class TestUnassociatedDiaObjects(MetadataMetricTestCase): 

101 

102 @classmethod 

103 def makeTask(cls): 

104 return NumberUnassociatedDiaObjectsMetricTask() 

105 

106 def testValid(self): 

107 metadata = _makeAssociationMetadata() 

108 result = self.task.run(metadata) 

109 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

110 meas = result.measurement 

111 

112 self.assertEqual(meas.metric_name, Name(metric="ap_association.numUnassociatedDiaObjects")) 

113 self.assertEqual(meas.quantity, 

114 metadata.getAsDouble("association.numUnassociatedDiaObjects") * u.count) 

115 

116 def testAllUpdated(self): 

117 metadata = _makeAssociationMetadata(numUnassociated=0) 

118 result = self.task.run(metadata) 

119 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

120 meas = result.measurement 

121 

122 self.assertEqual(meas.metric_name, Name(metric="ap_association.numUnassociatedDiaObjects")) 

123 self.assertEqual(meas.quantity, 0.0 * u.count) 

124 

125 def testAssociationFailed(self): 

126 try: 

127 result = self.task.run(PropertySet()) 

128 except lsst.pipe.base.NoWorkFound: 

129 # Correct behavior 

130 pass 

131 else: 

132 # Alternative correct behavior 

133 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

134 meas = result.measurement 

135 self.assertIsNone(meas) 

136 

137 def testBadlyTypedKeys(self): 

138 metadata = _makeAssociationMetadata() 

139 metadata.set("association.numUnassociatedDiaObjects", "Ultimate Answer") 

140 

141 with self.assertRaises(MetricComputationError): 

142 self.task.run(metadata) 

143 

144 

145class TestFracUpdatedDiaObjects(MetadataMetricTestCase): 

146 

147 @classmethod 

148 def makeTask(cls): 

149 return FractionUpdatedDiaObjectsMetricTask() 

150 

151 def testValid(self): 

152 metadata = _makeAssociationMetadata() 

153 result = self.task.run(metadata) 

154 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

155 meas = result.measurement 

156 

157 self.assertEqual(meas.metric_name, Name(metric="ap_association.fracUpdatedDiaObjects")) 

158 nUpdated = metadata.getAsDouble("association.numUpdatedDiaObjects") 

159 nTotal = metadata.getAsDouble("association.numUnassociatedDiaObjects") + nUpdated 

160 self.assertEqual(meas.quantity, nUpdated / nTotal * u.dimensionless_unscaled) 

161 

162 def testNoUpdated(self): 

163 metadata = _makeAssociationMetadata(numUpdated=0) 

164 result = self.task.run(metadata) 

165 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

166 meas = result.measurement 

167 

168 self.assertEqual(meas.metric_name, Name(metric="ap_association.fracUpdatedDiaObjects")) 

169 self.assertEqual(meas.quantity, 0.0 * u.dimensionless_unscaled) 

170 

171 def testAllUpdated(self): 

172 metadata = _makeAssociationMetadata(numUnassociated=0) 

173 result = self.task.run(metadata) 

174 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

175 meas = result.measurement 

176 

177 self.assertEqual(meas.metric_name, Name(metric="ap_association.fracUpdatedDiaObjects")) 

178 self.assertEqual(meas.quantity, 1.0 * u.dimensionless_unscaled) 

179 

180 def testNoObjects(self): 

181 metadata = _makeAssociationMetadata(numUpdated=0, numUnassociated=0) 

182 try: 

183 result = self.task.run(metadata) 

184 except lsst.pipe.base.NoWorkFound: 

185 # Correct behavior 

186 pass 

187 else: 

188 # Alternative correct behavior 

189 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

190 meas = result.measurement 

191 self.assertIsNone(meas) 

192 

193 def testAssociationFailed(self): 

194 try: 

195 result = self.task.run(PropertySet()) 

196 except lsst.pipe.base.NoWorkFound: 

197 # Correct behavior 

198 pass 

199 else: 

200 # Alternative correct behavior 

201 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

202 meas = result.measurement 

203 self.assertIsNone(meas) 

204 

205 def testBadlyTypedKeys(self): 

206 metadata = _makeAssociationMetadata() 

207 metadata.set("association.numUnassociatedDiaObjects", "Ultimate Answer") 

208 

209 with self.assertRaises(MetricComputationError): 

210 self.task.run(metadata) 

211 

212 

213class TestNumberSolarSystemObjects(MetadataMetricTestCase): 

214 

215 @classmethod 

216 def makeTask(cls): 

217 return NumberSolarSystemObjectsMetricTask() 

218 

219 def testValid(self): 

220 metadata = _makeAssociationMetadata() 

221 result = self.task.run(metadata) 

222 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

223 meas = result.measurement 

224 

225 self.assertEqual(meas.metric_name, Name(metric="ap_association.numTotalSolarSystemObjects")) 

226 self.assertEqual(meas.quantity, 

227 metadata.getAsDouble("association.numTotalSolarSystemObjects") * u.count) 

228 

229 def testAllUpdated(self): 

230 metadata = _makeAssociationMetadata(numSso=0) 

231 result = self.task.run(metadata) 

232 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

233 meas = result.measurement 

234 

235 self.assertEqual(meas.metric_name, Name(metric="ap_association.numTotalSolarSystemObjects")) 

236 self.assertEqual(meas.quantity, 0.0 * u.count) 

237 

238 def testAssociationFailed(self): 

239 try: 

240 result = self.task.run(PropertySet()) 

241 except lsst.pipe.base.NoWorkFound: 

242 # Correct behavior 

243 pass 

244 else: 

245 # Alternative correct behavior 

246 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

247 meas = result.measurement 

248 self.assertIsNone(meas) 

249 

250 def testBadlyTypedKeys(self): 

251 metadata = _makeAssociationMetadata() 

252 metadata.set("association.numTotalSolarSystemObjects", "Ultimate Answer") 

253 

254 with self.assertRaises(MetricComputationError): 

255 self.task.run(metadata) 

256 

257 

258class TestNumberAssociatedSolarSystemObjects(MetadataMetricTestCase): 

259 

260 @classmethod 

261 def makeTask(cls): 

262 return NumberAssociatedSolarSystemObjectsMetricTask() 

263 

264 def testValid(self): 

265 metadata = _makeAssociationMetadata() 

266 result = self.task.run(metadata) 

267 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

268 meas = result.measurement 

269 

270 self.assertEqual(meas.metric_name, Name(metric="ap_association.numAssociatedSsObjects")) 

271 self.assertEqual(meas.quantity, 

272 metadata.getAsDouble("association.numAssociatedSsObjects") * u.count) 

273 

274 def testAllUpdated(self): 

275 metadata = _makeAssociationMetadata(numAssocSso=0) 

276 result = self.task.run(metadata) 

277 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

278 meas = result.measurement 

279 

280 self.assertEqual(meas.metric_name, Name(metric="ap_association.numAssociatedSsObjects")) 

281 self.assertEqual(meas.quantity, 0.0 * u.count) 

282 

283 def testAssociationFailed(self): 

284 try: 

285 result = self.task.run(PropertySet()) 

286 except lsst.pipe.base.NoWorkFound: 

287 # Correct behavior 

288 pass 

289 else: 

290 # Alternative correct behavior 

291 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

292 meas = result.measurement 

293 self.assertIsNone(meas) 

294 

295 def testBadlyTypedKeys(self): 

296 metadata = _makeAssociationMetadata() 

297 metadata.set("association.numAssociatedSsObjects", "Ultimate Answer") 

298 

299 with self.assertRaises(MetricComputationError): 

300 self.task.run(metadata) 

301 

302 

303class TestTotalUnassociatedObjects(ApdbMetricTestCase): 

304 

305 @staticmethod 

306 def _makeApdb(dummy_dbInfo): 

307 """Create a dummy apdb. 

308 

309 We don't have access to the apdb in the task directly so mocking 

310 return values is difficult. We thus make use of the dummy dbInfo 

311 that is passed to the init task to pass values to the apdb object 

312 instantiated. 

313 """ 

314 apdb = unittest.mock.Mock(Apdb) 

315 test_value = dummy_dbInfo["test_value"] 

316 apdb.countUnassociatedObjects = unittest.mock.MagicMock( 

317 return_value=test_value) 

318 return apdb 

319 

320 @classmethod 

321 def makeTask(cls): 

322 class SimpleDbLoader(Task): 

323 ConfigClass = Config 

324 

325 def run(self, dummy): 

326 if dummy is not None: 

327 return Struct(apdb=cls._makeApdb(dummy)) 

328 else: 

329 return Struct(apdb=None) 

330 

331 config = TotalUnassociatedDiaObjectsMetricTask.ConfigClass() 

332 config.dbLoader.retarget(SimpleDbLoader) 

333 return TotalUnassociatedDiaObjectsMetricTask(config=config) 

334 

335 @classmethod 

336 def makeDbInfo(cls): 

337 return {"test_value": "whatever"} 

338 

339 def setUp(self): 

340 super().setUp() 

341 

342 def testValid(self): 

343 result = self.task.run([{"test_value": 42}]) 

344 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

345 meas = result.measurement 

346 

347 self.assertEqual(meas.metric_name, Name(metric="ap_association.totalUnassociatedDiaObjects")) 

348 self.assertEqual(meas.quantity, 42 * u.count) 

349 

350 def testAllAssociated(self): 

351 result = self.task.run([{"test_value": 0}]) 

352 lsst.pipe.base.testUtils.assertValidOutput(self.task, result) 

353 meas = result.measurement 

354 

355 self.assertEqual(meas.metric_name, Name(metric="ap_association.totalUnassociatedDiaObjects")) 

356 self.assertEqual(meas.quantity, 0.0 * u.count) 

357 

358 def testFineGrainedMetric(self): 

359 with self.assertRaises(ValueError): 

360 self.task.run([self.makeDbInfo()], outputDataId={"visit": 42}) 

361 

362 

363# Hack around unittest's hacky test setup system 

364del MetricTaskTestCase 

365del MetadataMetricTestCase 

366del ApdbMetricTestCase 

367 

368 

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

370 pass 

371 

372 

373def setup_module(module): 

374 lsst.utils.tests.init() 

375 

376 

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

378 lsst.utils.tests.init() 

379 unittest.main()