Coverage for tests/test_diaCalculationPlugins.py: 14%

404 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-10-14 01:52 -0700

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 

22from astropy.stats import median_absolute_deviation 

23import numpy as np 

24import pandas as pd 

25from scipy.stats import skew 

26import unittest 

27 

28from lsst.meas.base import ( 

29 MeanDiaPosition, MeanDiaPositionConfig, 

30 HTMIndexDiaPosition, HTMIndexDiaPositionConfig, 

31 NumDiaSourcesDiaPlugin, NumDiaSourcesDiaPluginConfig, 

32 SimpleSourceFlagDiaPlugin, SimpleSourceFlagDiaPluginConfig, 

33 WeightedMeanDiaPsfFlux, WeightedMeanDiaPsfFluxConfig, 

34 PercentileDiaPsfFlux, PercentileDiaPsfFluxConfig, 

35 SigmaDiaPsfFlux, SigmaDiaPsfFluxConfig, 

36 Chi2DiaPsfFlux, Chi2DiaPsfFluxConfig, 

37 MadDiaPsfFlux, MadDiaPsfFluxConfig, 

38 SkewDiaPsfFlux, SkewDiaPsfFluxConfig, 

39 MinMaxDiaPsfFlux, MinMaxDiaPsfFluxConfig, 

40 MaxSlopeDiaPsfFlux, MaxSlopeDiaPsfFluxConfig, 

41 ErrMeanDiaPsfFlux, ErrMeanDiaPsfFluxConfig, 

42 LinearFitDiaPsfFlux, LinearFitDiaPsfFluxConfig, 

43 StetsonJDiaPsfFlux, StetsonJDiaPsfFluxConfig, 

44 WeightedMeanDiaTotFlux, WeightedMeanDiaTotFluxConfig, 

45 SigmaDiaTotFlux, SigmaDiaTotFluxConfig, 

46 LombScarglePeriodogram, LombScarglePeriodogramConfig, 

47 LombScarglePeriodogramMulti, LombScarglePeriodogramMultiConfig) 

48import lsst.utils.tests 

49 

50 

51def run_single_plugin(diaObjectCat, 

52 diaObjectId, 

53 diaSourceCat, 

54 band, 

55 plugin): 

56 """Wrapper for running single plugins. 

57 

58 Reproduces some of the behavior of `lsst.ap.association.DiaCalcuation.run` 

59 

60 Parameters 

61 ---------- 

62 diaObjectCat : `pandas.DataFrame` 

63 Input object catalog to store data into and read from. 

64 diaSourcesCat : `pandas.DataFrame` 

65 DiaSource catalog to read data from and groupby on. 

66 fitlerName : `str` 

67 String name of the filter to process. 

68 plugin : `lsst.ap.association.DiaCalculationPlugin` 

69 Plugin to run. 

70 """ 

71 diaObjectCat.set_index("diaObjectId", inplace=True, drop=False) 

72 diaSourceCat.set_index( 

73 ["diaObjectId", "band", "diaSourceId"], 

74 inplace=True, 

75 drop=False) 

76 

77 objDiaSources = diaSourceCat.loc[diaObjectId] 

78 updatingFilterDiaSources = diaSourceCat.loc[ 

79 (diaObjectId, band), : 

80 ] 

81 

82 plugin.calculate(diaObjects=diaObjectCat, 

83 diaObjectId=diaObjectId, 

84 diaSources=objDiaSources, 

85 filterDiaSources=updatingFilterDiaSources, 

86 band=band) 

87 

88 

89def run_multi_plugin(diaObjectCat, diaSourceCat, band, plugin): 

90 """Wrapper for running multi plugins. 

91 

92 Reproduces some of the behavior of `lsst.ap.association.DiaCalcuation.run` 

93 

94 Parameters 

95 ---------- 

96 diaObjectCat : `pandas.DataFrame` 

97 Input object catalog to store data into and read from. 

98 diaSourcesCat : `pandas.DataFrame` 

99 DiaSource catalog to read data from and groupby on. 

100 filterName : `str` 

101 String name of the filter to process. 

102 plugin : `lsst.ap.association.DiaCalculationPlugin` 

103 Plugin to run. 

104 """ 

105 diaObjectCat.set_index("diaObjectId", inplace=True, drop=False) 

106 diaSourceCat.set_index( 

107 ["diaObjectId", "band", "diaSourceId"], 

108 inplace=True, 

109 drop=False) 

110 

111 updatingFilterDiaSources = diaSourceCat.loc[ 

112 (slice(None), band), : 

113 ] 

114 

115 diaSourcesGB = diaSourceCat.groupby(level=0) 

116 filterDiaSourcesGB = updatingFilterDiaSources.groupby(level=0) 

117 

118 plugin.calculate(diaObjects=diaObjectCat, 

119 diaSources=diaSourcesGB, 

120 filterDiaSources=filterDiaSourcesGB, 

121 band=band) 

122 

123 

124def run_multiband_plugin(diaObjectCat, diaSourceCat, plugin): 

125 """Wrapper for running multi plugins. 

126 

127 Reproduces some of the behavior of `lsst.ap.association.DiaCalcuation.run` 

128 

129 Parameters 

130 ---------- 

131 diaObjectCat : `pandas.DataFrame` 

132 Input object catalog to store data into and read from. 

133 diaSourcesCat : `pandas.DataFrame` 

134 DiaSource catalog to read data from and groupby on. 

135 plugin : `lsst.ap.association.DiaCalculationPlugin` 

136 Plugin to run. 

137 """ 

138 diaObjectCat.set_index("diaObjectId", inplace=True, drop=False) 

139 diaSourceCat.set_index( 

140 ["diaObjectId", "band", "diaSourceId"], 

141 inplace=True, 

142 drop=False) 

143 

144 diaSourcesGB = diaSourceCat.groupby(level=0) 

145 

146 plugin.calculate(diaObjects=diaObjectCat, 

147 diaSources=diaSourcesGB, 

148 ) 

149 

150 

151def make_diaObject_table(objId, plugin, default_value=None, band=None): 

152 """Create a minimal diaObject table with columns required for the plugin 

153 

154 Parameters 

155 ---------- 

156 objId : `int` 

157 The diaObjectId 

158 plugin : `lsst.ap.association.DiaCalculationPlugin` 

159 The plugin that will be run. 

160 default_value : None, optional 

161 Value to set new columns to. 

162 band : `str`, optional 

163 Band designation to append to the plugin columns. 

164 

165 Returns 

166 ------- 

167 diaObjects : `pandas.DataFrame` 

168 Output catalog with the required columns for the plugin. 

169 """ 

170 diaObjects = {"diaObjectId": [objId]} 

171 for col in plugin.outputCols: 

172 if band is not None: 

173 diaObjects[f"{band}_{col}"] = default_value 

174 else: 

175 diaObjects[col] = default_value 

176 return pd.DataFrame(diaObjects) 

177 

178 

179class TestMeanPosition(unittest.TestCase): 

180 

181 def testCalculate(self): 

182 """Test mean position calculation. 

183 """ 

184 n_sources = 10 

185 objId = 0 

186 

187 plug = MeanDiaPosition(MeanDiaPositionConfig(), 

188 "ap_meanPosition", 

189 None) 

190 

191 # Test expected means in RA. 

192 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

193 diaSources = pd.DataFrame(data={"ra": np.linspace(-1, 1, n_sources), 

194 "dec": np.zeros(n_sources), 

195 "midpointMjdTai": np.linspace(0, n_sources, n_sources), 

196 "diaObjectId": n_sources * [objId], 

197 "band": n_sources * ["g"], 

198 "diaSourceId": np.arange(n_sources, 

199 dtype=int)}) 

200 run_multi_plugin(diaObjects, diaSources, "g", plug) 

201 

202 self.assertAlmostEqual(diaObjects.loc[objId, "ra"], 0.0) 

203 self.assertAlmostEqual(diaObjects.loc[objId, "dec"], 0.0) 

204 

205 # Test expected means in DEC. 

206 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

207 diaSources = pd.DataFrame(data={"ra": np.zeros(n_sources), 

208 "dec": np.linspace(-1, 1, n_sources), 

209 "midpointMjdTai": np.linspace(0, n_sources, n_sources), 

210 "diaObjectId": n_sources * [objId], 

211 "band": n_sources * ["g"], 

212 "diaSourceId": np.arange(n_sources, 

213 dtype=int)}) 

214 run_multi_plugin(diaObjects, diaSources, "g", plug) 

215 

216 self.assertAlmostEqual(diaObjects.loc[objId, "ra"], 0.0) 

217 self.assertAlmostEqual(diaObjects.loc[objId, "dec"], 0.0) 

218 

219 # Test failure mode RA is nan. 

220 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

221 diaSources = pd.DataFrame(data={"ra": np.full(n_sources, np.nan), 

222 "dec": np.zeros(n_sources), 

223 "midpointMjdTai": np.linspace(0, n_sources, n_sources), 

224 "diaObjectId": n_sources * [objId], 

225 "band": n_sources * ["g"], 

226 "diaSourceId": np.arange(n_sources, 

227 dtype=int)}) 

228 run_multi_plugin(diaObjects, diaSources, "g", plug) 

229 

230 self.assertTrue(np.isnan(diaObjects.loc[objId, "ra"])) 

231 self.assertTrue(np.isnan(diaObjects.loc[objId, "dec"])) 

232 

233 # Test failure mode DEC is nan. 

234 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

235 diaSources = pd.DataFrame(data={"ra": np.zeros(n_sources), 

236 "dec": np.full(n_sources, np.nan), 

237 "midpointMjdTai": np.linspace(0, n_sources, n_sources), 

238 "diaObjectId": n_sources * [objId], 

239 "band": n_sources * ["g"], 

240 "diaSourceId": np.arange(n_sources, 

241 dtype=int)}) 

242 run_multi_plugin(diaObjects, diaSources, "g", plug) 

243 

244 self.assertTrue(np.isnan(diaObjects.loc[objId, "ra"])) 

245 self.assertTrue(np.isnan(diaObjects.loc[objId, "dec"])) 

246 

247 

248class TestHTMIndexPosition(unittest.TestCase): 

249 

250 def testCalculate(self): 

251 """Test HTMPixel assignment calculation. 

252 """ 

253 # Test expected pixelId at RA, DEC = 0 

254 objId = 0 

255 n_sources = 10 

256 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

257 diaObjects.loc[objId, "ra"] = 0. 

258 diaObjects.loc[objId, "dec"] = 0. 

259 diaSources = pd.DataFrame( 

260 data={"diaObjectId": n_sources * [objId], 

261 "band": n_sources * ["g"], 

262 "diaSourceId": np.arange(n_sources, dtype=int)}) 

263 plug = HTMIndexDiaPosition(HTMIndexDiaPositionConfig(), 

264 "ap_HTMIndex", 

265 None) 

266 

267 run_single_plugin(diaObjectCat=diaObjects, 

268 diaObjectId=objId, 

269 diaSourceCat=diaSources, 

270 band="g", 

271 plugin=plug) 

272 self.assertEqual(diaObjects.at[objId, "pixelId"], 

273 17042430230528) 

274 

275 # Test expected pixelId at some value of RA and DEC. 

276 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

277 diaObjects.loc[objId, "ra"] = 45.37 

278 diaObjects.loc[objId, "dec"] = 13.67 

279 diaSources = pd.DataFrame( 

280 data={"diaObjectId": n_sources * [objId], 

281 "band": n_sources * ["g"], 

282 "diaSourceId": np.arange(n_sources, dtype=int)}) 

283 run_single_plugin(diaObjectCat=diaObjects, 

284 diaObjectId=objId, 

285 diaSourceCat=diaSources, 

286 band="g", 

287 plugin=plug) 

288 self.assertEqual(diaObjects.at[objId, "pixelId"], 

289 17450571968473) 

290 

291 

292class TestNDiaSourcesDiaPlugin(unittest.TestCase): 

293 

294 def testCalculate(self): 

295 """Test that the number of DiaSources is correct. 

296 """ 

297 

298 for n_sources in [1, 8, 10]: 

299 # Test expected number of sources per object. 

300 objId = 0 

301 diaSources = pd.DataFrame( 

302 data={"diaObjectId": n_sources * [objId], 

303 "band": n_sources * ["g"], 

304 "diaSourceId": np.arange(n_sources, dtype=int)}) 

305 plug = NumDiaSourcesDiaPlugin(NumDiaSourcesDiaPluginConfig(), 

306 "ap_nDiaSources", 

307 None) 

308 diaObjects = make_diaObject_table(objId, plug, default_value=int(-1)) 

309 run_multi_plugin(diaObjects, diaSources, "g", plug) 

310 

311 self.assertEqual(n_sources, diaObjects.at[objId, "nDiaSources"]) 

312 

313 

314class TestSimpleSourceFlagDiaPlugin(unittest.TestCase): 

315 

316 def testCalculate(self): 

317 """Test that DiaObject flags are set. 

318 """ 

319 objId = 0 

320 n_sources = 10 

321 

322 # Test expected flags, no flags set. 

323 diaSources = pd.DataFrame( 

324 data={"diaObjectId": n_sources * [objId], 

325 "band": n_sources * ["g"], 

326 "diaSourceId": np.arange(n_sources, dtype=int), 

327 "flags": np.zeros(n_sources, dtype=np.uint64)}) 

328 plug = SimpleSourceFlagDiaPlugin(SimpleSourceFlagDiaPluginConfig(), 

329 "ap_diaObjectFlag", 

330 None) 

331 

332 diaObjects = make_diaObject_table(objId, plug, default_value=np.uint64) 

333 run_multi_plugin(diaObjects, diaSources, "g", plug) 

334 self.assertEqual(diaObjects.at[objId, "flags"], 0) 

335 

336 # Test expected flags, all flags set. 

337 diaSources = pd.DataFrame( 

338 data={"diaObjectId": n_sources * [objId], 

339 "band": n_sources * ["g"], 

340 "diaSourceId": np.arange(n_sources, dtype=int), 

341 "flags": np.ones(n_sources, dtype=np.uint64)}) 

342 diaObjects = make_diaObject_table(objId, plug, default_value=np.uint64) 

343 run_multi_plugin(diaObjects, diaSources, "g", plug) 

344 self.assertEqual(diaObjects.at[objId, "flags"], 1) 

345 

346 # Test expected flags, random flags. 

347 diaSources = pd.DataFrame( 

348 data={"diaObjectId": n_sources * [objId], 

349 "band": n_sources * ["g"], 

350 "diaSourceId": np.arange(n_sources, dtype=int), 

351 "flags": np.random.randint(0, 2 ** 16, size=n_sources)}) 

352 

353 diaObjects = make_diaObject_table(objId, plug, default_value=np.uint64) 

354 run_multi_plugin(diaObjects, diaSources, "g", plug) 

355 self.assertEqual(diaObjects.at[objId, "flags"], 1) 

356 

357 # Test expected flags, one flag set. 

358 flag_array = np.zeros(n_sources, dtype=np.uint64) 

359 flag_array[4] = 256 

360 diaSources = pd.DataFrame( 

361 data={"diaObjectId": n_sources * [objId], 

362 "band": n_sources * ["g"], 

363 "diaSourceId": np.arange(n_sources, dtype=int), 

364 "flags": flag_array}) 

365 diaObjects = make_diaObject_table(objId, plug, default_value=np.uint64) 

366 run_multi_plugin(diaObjects, diaSources, "g", plug) 

367 self.assertEqual(diaObjects.at[objId, "flags"], 1) 

368 

369 

370class TestWeightedMeanDiaPsfFlux(unittest.TestCase): 

371 

372 def testCalculate(self): 

373 """Test mean value calculation. 

374 """ 

375 n_sources = 10 

376 objId = 0 

377 

378 # Test expected mean. 

379 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

380 diaSources = pd.DataFrame( 

381 data={"diaObjectId": n_sources * [objId], 

382 "band": n_sources * ["u"], 

383 "diaSourceId": np.arange(n_sources, dtype=int), 

384 "psfFlux": np.linspace(-1, 1, n_sources), 

385 "psfFluxErr": np.ones(n_sources)}) 

386 

387 plug = WeightedMeanDiaPsfFlux(WeightedMeanDiaPsfFluxConfig(), 

388 "ap_meanFlux", 

389 None) 

390 run_multi_plugin(diaObjects, diaSources, "u", plug) 

391 

392 self.assertAlmostEqual(diaObjects.loc[objId, "u_psfFluxMean"], 0.0) 

393 self.assertAlmostEqual(diaObjects.loc[objId, "u_psfFluxMeanErr"], 

394 np.sqrt(1 / n_sources)) 

395 self.assertEqual(diaObjects.loc[objId, "u_psfFluxNdata"], n_sources) 

396 

397 # Test expected mean with a nan value. 

398 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

399 fluxes = np.linspace(-1, 1, n_sources) 

400 fluxes[4] = np.nan 

401 diaSources = pd.DataFrame( 

402 data={"diaObjectId": n_sources * [objId], 

403 "band": n_sources * ["r"], 

404 "diaSourceId": np.arange(n_sources, dtype=int), 

405 "psfFlux": fluxes, 

406 "psfFluxErr": np.ones(n_sources)}) 

407 run_multi_plugin(diaObjects, diaSources, "r", plug) 

408 

409 self.assertAlmostEqual(diaObjects.at[objId, "r_psfFluxMean"], 

410 np.nanmean(fluxes)) 

411 self.assertAlmostEqual(diaObjects.at[objId, "r_psfFluxMeanErr"], 

412 np.sqrt(1 / (n_sources - 1))) 

413 self.assertEqual(diaObjects.loc[objId, "r_psfFluxNdata"], n_sources - 1) 

414 

415 

416class TestPercentileDiaPsfFlux(unittest.TestCase): 

417 

418 def testCalculate(self): 

419 """Test flux percentile calculation. 

420 """ 

421 n_sources = 10 

422 objId = 0 

423 

424 # Test expected percentile values. 

425 fluxes = np.linspace(-1, 1, n_sources) 

426 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

427 diaSources = pd.DataFrame( 

428 data={"diaObjectId": n_sources * [objId], 

429 "band": n_sources * ["u"], 

430 "diaSourceId": np.arange(n_sources, dtype=int), 

431 "psfFlux": fluxes, 

432 "psfFluxErr": np.ones(n_sources)}) 

433 

434 plug = PercentileDiaPsfFlux(PercentileDiaPsfFluxConfig(), 

435 "ap_percentileFlux", 

436 None) 

437 run_multi_plugin(diaObjects, diaSources, "u", plug) 

438 for pTile, testVal in zip(plug.config.percentiles, 

439 np.nanpercentile( 

440 fluxes, 

441 plug.config.percentiles)): 

442 self.assertAlmostEqual( 

443 diaObjects.at[objId, "u_psfFluxPercentile{:02d}".format(pTile)], 

444 testVal) 

445 

446 # Test expected percentile values with a nan value. 

447 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

448 fluxes[4] = np.nan 

449 diaSources = pd.DataFrame( 

450 data={"diaObjectId": n_sources * [objId], 

451 "band": n_sources * ["r"], 

452 "diaSourceId": np.arange(n_sources, dtype=int), 

453 "psfFlux": fluxes, 

454 "psfFluxErr": np.ones(n_sources)}) 

455 run_multi_plugin(diaObjects, diaSources, "r", plug) 

456 for pTile, testVal in zip(plug.config.percentiles, 

457 np.nanpercentile( 

458 fluxes, 

459 plug.config.percentiles)): 

460 self.assertAlmostEqual( 

461 diaObjects.at[objId, "r_psfFluxPercentile{:02d}".format(pTile)], 

462 testVal) 

463 

464 

465class TestSigmaDiaPsfFlux(unittest.TestCase): 

466 

467 def testCalculate(self): 

468 """Test flux scatter calculation. 

469 """ 

470 n_sources = 10 

471 objId = 0 

472 

473 # Test expected sigma scatter of fluxes. 

474 fluxes = np.linspace(-1, 1, n_sources) 

475 diaSources = pd.DataFrame( 

476 data={"diaObjectId": n_sources * [objId], 

477 "band": n_sources * ["u"], 

478 "diaSourceId": np.arange(n_sources, dtype=int), 

479 "psfFlux": fluxes, 

480 "psfFluxErr": np.ones(n_sources)}) 

481 

482 plug = SigmaDiaPsfFlux(SigmaDiaPsfFluxConfig(), 

483 "ap_sigmaFlux", 

484 None) 

485 diaObjects = make_diaObject_table(objId, plug, band='u') 

486 run_multi_plugin(diaObjects, diaSources, "u", plug) 

487 self.assertAlmostEqual(diaObjects.at[objId, "u_psfFluxSigma"], 

488 np.nanstd(fluxes, ddof=1)) 

489 

490 # test one input, returns nan. 

491 diaSources = pd.DataFrame( 

492 data={"diaObjectId": 1 * [objId], 

493 "band": 1 * ["g"], 

494 "diaSourceId": [0], 

495 "psfFlux": [fluxes[0]], 

496 "psfFluxErr": [1.]}) 

497 

498 diaObjects = make_diaObject_table(objId, plug, band='g') 

499 run_multi_plugin(diaObjects, diaSources, "g", plug) 

500 self.assertTrue(np.isnan(diaObjects.at[objId, "g_psfFluxSigma"])) 

501 

502 # Test expected sigma scatter of fluxes with a nan value. 

503 fluxes[4] = np.nan 

504 diaSources = pd.DataFrame( 

505 data={"diaObjectId": n_sources * [objId], 

506 "band": n_sources * ["r"], 

507 "diaSourceId": np.arange(n_sources, dtype=int), 

508 "psfFlux": fluxes, 

509 "psfFluxErr": np.ones(n_sources)}) 

510 

511 diaObjects = make_diaObject_table(objId, plug, band='r') 

512 run_multi_plugin(diaObjects, diaSources, "r", plug) 

513 self.assertAlmostEqual(diaObjects.at[objId, "r_psfFluxSigma"], 

514 np.nanstd(fluxes, ddof=1)) 

515 

516 

517class TestChi2DiaPsfFlux(unittest.TestCase): 

518 

519 def testCalculate(self): 

520 """Test flux chi2 calculation. 

521 """ 

522 n_sources = 10 

523 objId = 0 

524 

525 # Test expected chi^2 value. 

526 fluxes = np.linspace(-1, 1, n_sources) 

527 diaObjects = pd.DataFrame({"diaObjectId": [objId], 

528 "u_psfFluxMean": [0.0]}) 

529 diaSources = pd.DataFrame( 

530 data={"diaObjectId": n_sources * [objId], 

531 "band": n_sources * ["u"], 

532 "diaSourceId": np.arange(n_sources, dtype=int), 

533 "psfFlux": fluxes, 

534 "psfFluxErr": np.ones(n_sources)}) 

535 

536 plug = Chi2DiaPsfFlux(Chi2DiaPsfFluxConfig(), 

537 "ap_chi2Flux", 

538 None) 

539 run_multi_plugin(diaObjects, diaSources, "u", plug) 

540 self.assertAlmostEqual( 

541 diaObjects.loc[objId, "u_psfFluxChi2"], 

542 np.nansum(((diaSources["psfFlux"] 

543 - np.nanmean(diaSources["psfFlux"])) 

544 / diaSources["psfFluxErr"]) ** 2)) 

545 

546 # Test expected chi^2 value with a nan value set. 

547 fluxes[4] = np.nan 

548 diaObjects = pd.DataFrame({"diaObjectId": [objId], 

549 "r_psfFluxMean": [np.nanmean(fluxes)]}) 

550 diaSources = pd.DataFrame( 

551 data={"diaObjectId": n_sources * [objId], 

552 "band": n_sources * ["r"], 

553 "diaSourceId": np.arange(n_sources, dtype=int), 

554 "psfFlux": fluxes, 

555 "psfFluxErr": np.ones(n_sources)}) 

556 run_multi_plugin(diaObjects, diaSources, "r", plug) 

557 self.assertAlmostEqual( 

558 diaObjects.loc[objId, "r_psfFluxChi2"], 

559 np.nansum(((diaSources["psfFlux"] 

560 - np.nanmean(diaSources["psfFlux"])) 

561 / diaSources["psfFluxErr"]) ** 2)) 

562 

563 

564class TestMadDiaPsfFlux(unittest.TestCase): 

565 

566 def testCalculate(self): 

567 """Test flux median absolute deviation calculation. 

568 """ 

569 n_sources = 10 

570 objId = 0 

571 

572 # Test expected MAD value. 

573 fluxes = np.linspace(-1, 1, n_sources) 

574 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

575 diaSources = pd.DataFrame( 

576 data={"diaObjectId": n_sources * [objId], 

577 "band": n_sources * ["u"], 

578 "diaSourceId": np.arange(n_sources, dtype=int), 

579 "psfFlux": fluxes, 

580 "psfFluxErr": np.ones(n_sources)}) 

581 

582 plug = MadDiaPsfFlux(MadDiaPsfFluxConfig(), 

583 "ap_madFlux", 

584 None) 

585 run_multi_plugin(diaObjects, diaSources, "u", plug) 

586 self.assertAlmostEqual(diaObjects.at[objId, "u_psfFluxMAD"], 

587 median_absolute_deviation(fluxes, 

588 ignore_nan=True)) 

589 

590 # Test expected MAD value with a nan set. 

591 fluxes[4] = np.nan 

592 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

593 diaSources = pd.DataFrame( 

594 data={"diaObjectId": n_sources * [objId], 

595 "band": n_sources * ["r"], 

596 "diaSourceId": np.arange(n_sources, dtype=int), 

597 "psfFlux": fluxes, 

598 "psfFluxErr": np.ones(n_sources)}) 

599 run_multi_plugin(diaObjects, diaSources, "r", plug) 

600 self.assertAlmostEqual(diaObjects.at[objId, "r_psfFluxMAD"], 

601 median_absolute_deviation(fluxes, 

602 ignore_nan=True)) 

603 

604 

605class TestSkewDiaPsfFlux(unittest.TestCase): 

606 

607 def testCalculate(self): 

608 """Test flux skew calculation. 

609 """ 

610 n_sources = 10 

611 objId = 0 

612 

613 # Test expected skew value. 

614 fluxes = np.linspace(-1, 1, n_sources) 

615 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

616 diaSources = pd.DataFrame( 

617 data={"diaObjectId": n_sources * [objId], 

618 "band": n_sources * ["u"], 

619 "diaSourceId": np.arange(n_sources, dtype=int), 

620 "psfFlux": fluxes, 

621 "psfFluxErr": np.ones(n_sources)}) 

622 

623 plug = SkewDiaPsfFlux(SkewDiaPsfFluxConfig(), 

624 "ap_skewFlux", 

625 None) 

626 run_multi_plugin(diaObjects, diaSources, "u", plug) 

627 self.assertAlmostEqual( 

628 diaObjects.loc[objId, "u_psfFluxSkew"], 

629 skew_wrapper(fluxes)) 

630 

631 # Test expected skew value with a nan set. 

632 fluxes[4] = np.nan 

633 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

634 diaSources = pd.DataFrame( 

635 data={"diaObjectId": n_sources * [objId], 

636 "band": n_sources * ["r"], 

637 "diaSourceId": np.arange(n_sources, dtype=int), 

638 "psfFlux": fluxes, 

639 "psfFluxErr": np.ones(n_sources)}) 

640 run_multi_plugin(diaObjects, diaSources, "r", plug) 

641 

642 self.assertAlmostEqual( 

643 diaObjects.at[objId, "r_psfFluxSkew"], 

644 skew_wrapper(fluxes)) 

645 

646 

647class TestMinMaxDiaPsfFlux(unittest.TestCase): 

648 

649 def testCalculate(self): 

650 """Test flux min/max calculation. 

651 """ 

652 n_sources = 10 

653 objId = 0 

654 

655 # Test expected MinMax fluxes. 

656 fluxes = np.linspace(-1, 1, n_sources) 

657 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

658 diaSources = pd.DataFrame( 

659 data={"diaObjectId": n_sources * [objId], 

660 "band": n_sources * ["u"], 

661 "diaSourceId": np.arange(n_sources, dtype=int), 

662 "psfFlux": fluxes, 

663 "psfFluxErr": np.ones(n_sources)}) 

664 

665 plug = MinMaxDiaPsfFlux(MinMaxDiaPsfFluxConfig(), 

666 "ap_minMaxFlux", 

667 None) 

668 run_multi_plugin(diaObjects, diaSources, "u", plug) 

669 self.assertEqual(diaObjects.loc[objId, "u_psfFluxMin"], -1) 

670 self.assertEqual(diaObjects.loc[objId, "u_psfFluxMax"], 1) 

671 

672 # Test expected MinMax fluxes with a nan set. 

673 fluxes[4] = np.nan 

674 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

675 diaSources = pd.DataFrame( 

676 data={"diaObjectId": n_sources * [objId], 

677 "band": n_sources * ["r"], 

678 "diaSourceId": np.arange(n_sources, dtype=int), 

679 "psfFlux": fluxes, 

680 "psfFluxErr": np.ones(n_sources)}) 

681 run_multi_plugin(diaObjects, diaSources, "r", plug) 

682 self.assertEqual(diaObjects.loc[objId, "r_psfFluxMin"], -1) 

683 self.assertEqual(diaObjects.loc[objId, "r_psfFluxMax"], 1) 

684 

685 

686class TestMaxSlopeDiaPsfFlux(unittest.TestCase): 

687 

688 def testCalculate(self): 

689 """Test flux maximum slope. 

690 """ 

691 n_sources = 10 

692 objId = 0 

693 

694 # Test max slope value. 

695 fluxes = np.linspace(-1, 1, n_sources) 

696 times = np.concatenate([np.linspace(0, 1, n_sources)[:-1], [1 - 1/90]]) 

697 diaSources = pd.DataFrame( 

698 data={"diaObjectId": n_sources * [objId], 

699 "band": n_sources * ["u"], 

700 "diaSourceId": np.arange(n_sources, dtype=int), 

701 "psfFlux": fluxes, 

702 "psfFluxErr": np.ones(n_sources), 

703 "midpointMjdTai": times}) 

704 

705 plug = MaxSlopeDiaPsfFlux(MaxSlopeDiaPsfFluxConfig(), 

706 "ap_maxSlopeFlux", 

707 None) 

708 diaObjects = make_diaObject_table(objId, plug, band='u') 

709 run_multi_plugin(diaObjects, diaSources, "u", plug) 

710 self.assertAlmostEqual(diaObjects.at[objId, "u_psfFluxMaxSlope"], 2 + 2/9) 

711 

712 # Test max slope value returns nan on 1 input. 

713 diaSources = pd.DataFrame( 

714 data={"diaObjectId": 1 * [objId], 

715 "band": 1 * ["g"], 

716 "diaSourceId": np.arange(1, dtype=int), 

717 "psfFlux": fluxes[0], 

718 "psfFluxErr": np.ones(1), 

719 "midpointMjdTai": times[0]}) 

720 diaObjects = make_diaObject_table(objId, plug, band='g') 

721 run_multi_plugin(diaObjects, diaSources, "g", plug) 

722 self.assertTrue(np.isnan(diaObjects.at[objId, "g_psfFluxMaxSlope"])) 

723 

724 # Test max slope value inputing nan values. 

725 fluxes[4] = np.nan 

726 times[7] = np.nan 

727 diaSources = pd.DataFrame( 

728 data={"diaObjectId": n_sources * [objId], 

729 "band": n_sources * ["r"], 

730 "diaSourceId": np.arange(n_sources, dtype=int), 

731 "psfFlux": fluxes, 

732 "psfFluxErr": np.ones(n_sources), 

733 "midpointMjdTai": times}) 

734 diaObjects = make_diaObject_table(objId, plug, band='r') 

735 run_multi_plugin(diaObjects, diaSources, "r", plug) 

736 self.assertAlmostEqual(diaObjects.at[objId, "r_psfFluxMaxSlope"], 2 + 2 / 9) 

737 

738 

739class TestErrMeanDiaPsfFlux(unittest.TestCase): 

740 

741 def testCalculate(self): 

742 """Test error mean calculation. 

743 """ 

744 n_sources = 10 

745 objId = 0 

746 

747 # Test mean of the errors. 

748 fluxes = np.linspace(-1, 1, n_sources) 

749 errors = np.linspace(1, 2, n_sources) 

750 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

751 diaSources = pd.DataFrame( 

752 data={"diaObjectId": n_sources * [objId], 

753 "band": n_sources * ["u"], 

754 "diaSourceId": np.arange(n_sources, dtype=int), 

755 "psfFlux": fluxes, 

756 "psfFluxErr": errors}) 

757 

758 plug = ErrMeanDiaPsfFlux(ErrMeanDiaPsfFluxConfig(), 

759 "ap_errMeanFlux", 

760 None) 

761 run_multi_plugin(diaObjects, diaSources, "u", plug) 

762 self.assertAlmostEqual(diaObjects.at[objId, "u_psfFluxErrMean"], 

763 np.nanmean(errors)) 

764 

765 # Test mean of the errors with input nan value. 

766 errors[4] = np.nan 

767 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

768 diaSources = pd.DataFrame( 

769 data={"diaObjectId": n_sources * [objId], 

770 "band": n_sources * ["r"], 

771 "diaSourceId": np.arange(n_sources, dtype=int), 

772 "psfFlux": fluxes, 

773 "psfFluxErr": errors}) 

774 run_multi_plugin(diaObjects, diaSources, "r", plug) 

775 self.assertAlmostEqual(diaObjects.at[objId, "r_psfFluxErrMean"], 

776 np.nanmean(errors)) 

777 

778 

779class TestLinearFitDiaPsfFlux(unittest.TestCase): 

780 

781 def testCalculate(self): 

782 """Test a linear fit to flux vs time. 

783 """ 

784 n_sources = 10 

785 objId = 0 

786 

787 # Test best fit linear model. 

788 fluxes = np.linspace(-1, 1, n_sources) 

789 errors = np.linspace(1, 2, n_sources) 

790 times = np.linspace(0, 1, n_sources) 

791 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

792 diaSources = pd.DataFrame( 

793 data={"diaObjectId": n_sources * [objId], 

794 "band": n_sources * ["u"], 

795 "diaSourceId": np.arange(n_sources, dtype=int), 

796 "psfFlux": fluxes, 

797 "psfFluxErr": errors, 

798 "midpointMjdTai": times}) 

799 

800 plug = LinearFitDiaPsfFlux(LinearFitDiaPsfFluxConfig(), 

801 "ap_LinearFit", 

802 None) 

803 run_multi_plugin(diaObjects, diaSources, "u", plug) 

804 self.assertAlmostEqual(diaObjects.loc[objId, "u_psfFluxLinearSlope"], 

805 2.) 

806 self.assertAlmostEqual(diaObjects.loc[objId, "u_psfFluxLinearIntercept"], 

807 -1.) 

808 

809 # Test best fit linear model with input nans. 

810 fluxes[7] = np.nan 

811 errors[4] = np.nan 

812 times[2] = np.nan 

813 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

814 diaSources = pd.DataFrame( 

815 data={"diaObjectId": n_sources * [objId], 

816 "band": n_sources * ["r"], 

817 "diaSourceId": np.arange(n_sources, dtype=int), 

818 "psfFlux": fluxes, 

819 "psfFluxErr": errors, 

820 "midpointMjdTai": times}) 

821 run_multi_plugin(diaObjects, diaSources, "r", plug) 

822 self.assertAlmostEqual(diaObjects.loc[objId, "r_psfFluxLinearSlope"], 2.) 

823 self.assertAlmostEqual(diaObjects.loc[objId, "r_psfFluxLinearIntercept"], 

824 -1.) 

825 

826 

827class TestStetsonJDiaPsfFlux(unittest.TestCase): 

828 

829 def testCalculate(self): 

830 """Test the stetsonJ statistic. 

831 """ 

832 n_sources = 10 

833 objId = 0 

834 

835 # Test stetsonJ calculation. 

836 fluxes = np.linspace(-1, 1, n_sources) 

837 errors = np.ones(n_sources) 

838 diaObjects = pd.DataFrame({"diaObjectId": [objId], 

839 "u_psfFluxMean": [np.nanmean(fluxes)]}) 

840 diaSources = pd.DataFrame( 

841 data={"diaObjectId": n_sources * [objId], 

842 "band": n_sources * ["u"], 

843 "diaSourceId": np.arange(n_sources, dtype=int), 

844 "psfFlux": fluxes, 

845 "psfFluxErr": errors}) 

846 

847 plug = StetsonJDiaPsfFlux(StetsonJDiaPsfFluxConfig(), 

848 "ap_StetsonJ", 

849 None) 

850 run_multi_plugin(diaObjects, diaSources, "u", plug) 

851 # Expected StetsonJ for the values created. Confirmed using Cesimum's 

852 # implementation. http://github.com/cesium-ml/cesium 

853 self.assertAlmostEqual(diaObjects.loc[objId, "u_psfFluxStetsonJ"], 

854 -0.5958393936080928) 

855 

856 # Test stetsonJ calculation returns nan on single input. 

857 diaObjects = pd.DataFrame({"diaObjectId": [objId], 

858 "g_psfFluxMean": [np.nanmean(fluxes)]}) 

859 diaSources = pd.DataFrame( 

860 data={"diaObjectId": 1 * [objId], 

861 "band": 1 * ["g"], 

862 "diaSourceId": np.arange(1, dtype=int), 

863 "psfFlux": fluxes[0], 

864 "psfFluxErr": errors[0]}) 

865 run_multi_plugin(diaObjects, diaSources, "g", plug) 

866 self.assertTrue(np.isnan(diaObjects.at[objId, "g_psfFluxStetsonJ"])) 

867 

868 # Test stetsonJ calculation returns when nans are input. 

869 fluxes[7] = np.nan 

870 errors[4] = np.nan 

871 nonNanMask = np.logical_and(~np.isnan(fluxes), 

872 ~np.isnan(errors)) 

873 diaObjects = pd.DataFrame( 

874 {"diaObjectId": [objId], 

875 "r_psfFluxMean": [np.average(fluxes[nonNanMask], 

876 weights=errors[nonNanMask])]}) 

877 diaSources = pd.DataFrame( 

878 data={"diaObjectId": n_sources * [objId], 

879 "band": n_sources * ["r"], 

880 "diaSourceId": np.arange(n_sources, dtype=int), 

881 "psfFlux": fluxes, 

882 "psfFluxErr": errors}) 

883 run_multi_plugin(diaObjects, diaSources, "r", plug) 

884 self.assertAlmostEqual(diaObjects.at[objId, "r_psfFluxStetsonJ"], 

885 -0.5412797916187173) 

886 

887 

888class TestWeightedMeanDiaTotFlux(unittest.TestCase): 

889 

890 def testCalculate(self): 

891 """Test mean value calculation. 

892 """ 

893 n_sources = 10 

894 objId = 0 

895 

896 # Test test mean on scienceFlux. 

897 fluxes = np.linspace(-1, 1, n_sources) 

898 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

899 diaSources = pd.DataFrame( 

900 data={"diaObjectId": n_sources * [objId], 

901 "band": n_sources * ["u"], 

902 "diaSourceId": np.arange(n_sources, dtype=int), 

903 "scienceFlux": fluxes, 

904 "scienceFluxErr": np.ones(n_sources)}) 

905 

906 plug = WeightedMeanDiaTotFlux(WeightedMeanDiaTotFluxConfig(), 

907 "ap_meanTotFlux", 

908 None) 

909 run_multi_plugin(diaObjects, diaSources, "u", plug) 

910 

911 self.assertAlmostEqual(diaObjects.at[objId, "u_scienceFluxMean"], 0.0) 

912 self.assertAlmostEqual(diaObjects.at[objId, "u_scienceFluxMeanErr"], 

913 np.sqrt(1 / n_sources)) 

914 

915 # Test test mean on scienceFlux with input nans 

916 fluxes[4] = np.nan 

917 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

918 diaSources = pd.DataFrame( 

919 data={"diaObjectId": n_sources * [objId], 

920 "band": n_sources * ["r"], 

921 "diaSourceId": np.arange(n_sources, dtype=int), 

922 "scienceFlux": fluxes, 

923 "scienceFluxErr": np.ones(n_sources)}) 

924 run_multi_plugin(diaObjects, diaSources, "r", plug) 

925 

926 self.assertAlmostEqual(diaObjects.at[objId, "r_scienceFluxMean"], 

927 np.nanmean(fluxes)) 

928 self.assertAlmostEqual(diaObjects.at[objId, "r_scienceFluxMeanErr"], 

929 np.sqrt(1 / (n_sources - 1))) 

930 

931 

932def generatePeriodicData(n=10, period=10): 

933 """Generate noisy, sinusoidally-varying periodic data for testing Lomb- 

934 Scargle Periodogram. 

935 

936 The returned fluxes will have, within the errors, the passed-in period and 

937 a power close to 1, because the fluxes are purely sinusoidal. 

938 

939 Parameters 

940 ---------- 

941 n : int 

942 Number of data points to generate. 

943 period : float 

944 Period of the periodic signal. 

945 

946 Returns 

947 ------- 

948 t : np.ndarray 

949 Time values. 

950 y_obs : np.ndarray 

951 Observed flux values. 

952 """ 

953 np.random.seed(42) 

954 

955 t = np.linspace(-2*np.pi, 2*np.pi, n) + 100*np.random.random(n) 

956 y = 10 + np.sin(2 * np.pi * t / period) 

957 y_obs = np.random.normal(y, 0.001) 

958 

959 return t, y_obs 

960 

961 

962class TestMultiLombScarglePeriodogram(lsst.utils.tests.TestCase): 

963 

964 def testCalculate(self): 

965 """Test Mulitband Lomb Scargle Periodogram.""" 

966 n_sources = 10 

967 objId = 0 

968 

969 # Create synthetic multi-band data 

970 times, fluxes = generatePeriodicData(n_sources, period=10) 

971 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

972 diaSources = pd.DataFrame( 

973 data={"diaObjectId": n_sources * [objId], 

974 "band": n_sources//2 * ["u"] + n_sources//2 * ["g"], 

975 "diaSourceId": np.arange(n_sources, dtype=int), 

976 "midpointMjdTai": times, 

977 "psfFlux": fluxes, 

978 "psfFluxErr": 1e-3+np.zeros(n_sources)}) 

979 

980 plugin = LombScarglePeriodogramMulti(LombScarglePeriodogramMultiConfig(), 

981 "ap_lombScarglePeriodogramMulti", 

982 None) 

983 

984 run_multiband_plugin(diaObjects, diaSources, plugin) 

985 self.assertAlmostEqual(diaObjects.at[objId, "multiPeriod"], 10, delta=0.04) 

986 self.assertAlmostEqual(diaObjects.at[objId, "multiPower"], 1, delta=1e-2) 

987 # This implementation of LS returns a normalized power < 1. 

988 self.assertLess(diaObjects.at[objId, "multiPower"], 1) 

989 self.assertAlmostEqual(diaObjects.at[objId, "multiFap"], 0, delta=0.04) 

990 # Note: The below values are empirical, but seem reasonable, and 

991 # test that we get values for each band. 

992 self.assertAlmostEqual(diaObjects.at[objId, "u_multiAmp"], 0.029, delta=0.01) 

993 self.assertAlmostEqual(diaObjects.at[objId, "g_multiAmp"], 0.029, delta=0.01) 

994 self.assertAlmostEqual(diaObjects.at[objId, "u_multiPhase"], -2.0, delta=0.2) 

995 self.assertAlmostEqual(diaObjects.at[objId, "g_multiPhase"], 1.0, delta=0.1) 

996 

997 def testCalculateTwoSources(self): 

998 """Test Mulitband Lomb Scargle Periodogram with 2 sources (minimum 

999 detections = 5), which will result in NaN output.""" 

1000 objId = 0 

1001 n_sources = 2 

1002 times, fluxes = generatePeriodicData(n_sources, period=10) 

1003 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

1004 diaSources = pd.DataFrame( 

1005 data={"diaObjectId": n_sources * [objId], 

1006 "band": n_sources * ["u"], 

1007 "diaSourceId": np.arange(n_sources, dtype=int), 

1008 "midpointMjdTai": times, 

1009 "psfFlux": fluxes, 

1010 "psfFluxErr": 1e-3+np.zeros(n_sources)}) 

1011 

1012 plugin = LombScarglePeriodogramMulti(LombScarglePeriodogramMultiConfig(), 

1013 "ap_lombScarglePeriodogramMulti", 

1014 None) 

1015 

1016 run_multi_plugin(diaObjects, diaSources, "u", plugin) 

1017 self.assertTrue(np.isnan(diaObjects.at[objId, "multiPeriod"])) 

1018 self.assertTrue(np.isnan(diaObjects.at[objId, "multiPower"])) 

1019 self.assertTrue(np.isnan(diaObjects.at[objId, "multiFap"])) 

1020 

1021 

1022class TestLombScarglePeriodogram(lsst.utils.tests.TestCase): 

1023 

1024 def testCalculate(self): 

1025 """Test Lomb Scargle Periodogram.""" 

1026 n_sources = 10 

1027 objId = 0 

1028 

1029 # Test period calculation. 

1030 times, fluxes = generatePeriodicData(n_sources, period=10) 

1031 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

1032 diaSources = pd.DataFrame( 

1033 data={"diaObjectId": n_sources * [objId], 

1034 "band": n_sources * ["u"], 

1035 "diaSourceId": np.arange(n_sources, dtype=int), 

1036 "midpointMjdTai": times, 

1037 "psfFlux": fluxes, 

1038 "psfFluxErr": 1e-3+np.zeros(n_sources)}) 

1039 

1040 plugin = LombScarglePeriodogram(LombScarglePeriodogramConfig(), 

1041 "ap_lombScarglePeriodogram", 

1042 None) 

1043 

1044 run_multi_plugin(diaObjects, diaSources, "u", plugin) 

1045 self.assertAlmostEqual(diaObjects.at[objId, "u_period"], 10, delta=0.04) 

1046 # This implementation of LS returns a normalized power < 1. 

1047 self.assertAlmostEqual(diaObjects.at[objId, "u_power"], 1, delta=1e-2) 

1048 self.assertLess(diaObjects.at[objId, "u_power"], 1) 

1049 

1050 # Test that we get the same result with a NaN flux. 

1051 diaSources.loc[4, "psfFlux"] = np.nan 

1052 diaObjects = pd.DataFrame({"diaObjectId": [objId]}) 

1053 diaSources = pd.DataFrame( 

1054 data={"diaObjectId": n_sources * [objId], 

1055 "band": n_sources * ["r"], 

1056 "diaSourceId": np.arange(n_sources, dtype=int), 

1057 "midpointMjdTai": times, 

1058 "psfFlux": fluxes, 

1059 "psfFluxErr": np.ones(n_sources)}) 

1060 run_multi_plugin(diaObjects, diaSources, "r", plugin) 

1061 self.assertAlmostEqual(diaObjects.at[objId, "r_period"], 10, delta=0.04) 

1062 self.assertAlmostEqual(diaObjects.at[objId, "r_power"], 1, delta=1e-2) 

1063 # This implementation of LS returns a normalized power < 1. 

1064 self.assertLess(diaObjects.at[objId, "r_power"], 1) 

1065 

1066 

1067class TestSigmaDiaTotFlux(unittest.TestCase): 

1068 

1069 def testCalculate(self): 

1070 """Test flux scatter calculation. 

1071 """ 

1072 n_sources = 10 

1073 objId = 0 

1074 

1075 # Test test scatter on scienceFlux. 

1076 fluxes = np.linspace(-1, 1, n_sources) 

1077 diaSources = pd.DataFrame( 

1078 data={"diaObjectId": n_sources * [objId], 

1079 "band": n_sources * ["u"], 

1080 "diaSourceId": np.arange(n_sources, dtype=int), 

1081 "scienceFlux": fluxes, 

1082 "scienceFluxErr": np.ones(n_sources)}) 

1083 

1084 plug = SigmaDiaTotFlux(SigmaDiaTotFluxConfig(), 

1085 "ap_sigmaTotFlux", 

1086 None) 

1087 diaObjects = make_diaObject_table(objId, plug, band='u') 

1088 run_multi_plugin(diaObjects, diaSources, "u", plug) 

1089 self.assertAlmostEqual(diaObjects.at[objId, "u_scienceFluxSigma"], 

1090 np.nanstd(fluxes, ddof=1)) 

1091 

1092 # Test test scatter on scienceFlux returns nan on 1 input. 

1093 diaSources = pd.DataFrame( 

1094 data={"diaObjectId": 1 * [objId], 

1095 "band": 1 * ["g"], 

1096 "diaSourceId": np.arange(1, dtype=int), 

1097 "scienceFlux": fluxes[0], 

1098 "scienceFluxErr": np.ones(1)}) 

1099 diaObjects = make_diaObject_table(objId, plug, band='g') 

1100 run_multi_plugin(diaObjects, diaSources, "g", plug) 

1101 self.assertTrue(np.isnan(diaObjects.at[objId, "g_scienceFluxSigma"])) 

1102 

1103 # Test test scatter on scienceFlux takes input nans. 

1104 fluxes[4] = np.nan 

1105 diaSources = pd.DataFrame( 

1106 data={"diaObjectId": n_sources * [objId], 

1107 "band": n_sources * ["r"], 

1108 "diaSourceId": np.arange(n_sources, dtype=int), 

1109 "scienceFlux": fluxes, 

1110 "scienceFluxErr": np.ones(n_sources)}) 

1111 diaObjects = make_diaObject_table(objId, plug, band='r') 

1112 run_multi_plugin(diaObjects, diaSources, "r", plug) 

1113 self.assertAlmostEqual(diaObjects.at[objId, "r_scienceFluxSigma"], 

1114 np.nanstd(fluxes, ddof=1)) 

1115 

1116 

1117def skew_wrapper(values): 

1118 """Compute scipy skew, omitting nans. 

1119 

1120 This version works with both scipy<1.9 (where it erroneously returns a 

1121 masked array) and scipy>=1.9 (where it correctly returns a float). 

1122 

1123 Parameters 

1124 ---------- 

1125 values : `np.ndarray` 

1126 

1127 Returns 

1128 ------- 

1129 skew_value : `float` 

1130 """ 

1131 value = skew(values, bias=False, nan_policy="omit") 

1132 if isinstance(value, np.ma.masked_array): 

1133 return value.data 

1134 else: 

1135 return value 

1136 

1137 

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

1139 pass 

1140 

1141 

1142def setup_module(module): 

1143 lsst.utils.tests.init() 

1144 

1145 

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

1147 lsst.utils.tests.init() 

1148 unittest.main()