Coverage for tests/test_efdUtils.py: 37%

153 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-03 03:38 -0700

1# This file is part of summit_utils. 

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 

22"""Test cases for utils.""" 

23 

24import asyncio 

25import datetime 

26import unittest 

27 

28import astropy 

29import pandas as pd 

30from astropy.time import Time 

31from utils import getVcr 

32 

33import lsst.utils.tests 

34from lsst.summit.utils.efdUtils import ( 

35 astropyToEfdTimestamp, 

36 clipDataToEvent, 

37 efdTimestampToAstropy, 

38 getDayObsEndTime, 

39 getDayObsForTime, 

40 getDayObsStartTime, 

41 getEfdData, 

42 getMostRecentRowWithDataBefore, 

43 getTopics, 

44 makeEfdClient, 

45) 

46from lsst.summit.utils.tmaUtils import TMAEvent, TMAState 

47 

48HAS_EFD_CLIENT = True 

49try: 

50 import lsst_efd_client 

51except ImportError: 

52 HAS_EFD_CLIENT = False 

53 

54vcr = getVcr() 

55 

56 

57@unittest.skipIf(not HAS_EFD_CLIENT, "No EFD client available") 

58@vcr.use_cassette() 

59class EfdUtilsTestCase(lsst.utils.tests.TestCase): 

60 @classmethod 

61 @vcr.use_cassette() 

62 def setUpClass(cls): 

63 try: 

64 cls.client = makeEfdClient(testing=True) 

65 except RuntimeError: 

66 raise unittest.SkipTest("Could not instantiate an EFD client") 

67 cls.dayObs = 20230531 

68 # get a sample expRecord here to test expRecordToTimespan 

69 cls.axisTopic = "lsst.sal.MTMount.logevent_azimuthMotionState" 

70 cls.timeSeriesTopic = "lsst.sal.MTMount.azimuth" 

71 cls.event = TMAEvent( 

72 dayObs=20230531, 

73 seqNum=27, 

74 type=TMAState.TRACKING, 

75 endReason=TMAState.SLEWING, 

76 duration=0.47125244140625, 

77 begin=Time(1685578353.2265284, scale="utc", format="unix"), 

78 end=Time(1685578353.6977808, scale="utc", format="unix"), 

79 blockInfos=None, 

80 version=0, 

81 _startRow=254, 

82 _endRow=255, 

83 ) 

84 

85 @vcr.use_cassette() 

86 def tearDown(self): 

87 loop = asyncio.get_event_loop() 

88 loop.run_until_complete(self.client.influx_client.close()) 

89 

90 @vcr.use_cassette() 

91 def test_makeEfdClient(self): 

92 self.assertIsInstance(self.client, lsst_efd_client.efd_helper.EfdClient) 

93 

94 def test_getDayObsAsTimes(self): 

95 """This tests getDayObsStartTime and getDayObsEndTime explicitly, 

96 but the days we loop over are chosen to test calcNextDay() which is 

97 called by getDayObsEndTime(). 

98 """ 

99 for dayObs in ( 

100 self.dayObs, # the nominal value 

101 20200228, # day before end of Feb on a leap year 

102 20200229, # end of Feb on a leap year 

103 20210227, # day before end of Feb on a non-leap year 

104 20200228, # end of Feb on a non-leap year 

105 20200430, # end of a month with 30 days 

106 20200530, # end of a month with 31 days 

107 20201231, # year rollover 

108 ): 

109 dayStart = getDayObsStartTime(dayObs) 

110 self.assertIsInstance(dayStart, astropy.time.Time) 

111 

112 dayEnd = getDayObsEndTime(dayObs) 

113 self.assertIsInstance(dayStart, astropy.time.Time) 

114 

115 self.assertGreater(dayEnd, dayStart) 

116 self.assertEqual(dayEnd.jd, dayStart.jd + 1) 

117 

118 @vcr.use_cassette() 

119 def test_getTopics(self): 

120 topics = getTopics(self.client, "lsst.sal.MTMount*") 

121 self.assertIsInstance(topics, list) 

122 self.assertGreater(len(topics), 0) 

123 

124 topics = getTopics(self.client, "*fake.topics.does.not.exist*") 

125 self.assertIsInstance(topics, list) 

126 self.assertEqual(len(topics), 0) 

127 

128 # check we can find the mount with a preceding wildcard 

129 topics = getTopics(self.client, "*mTmoUnt*") 

130 self.assertIsInstance(topics, list) 

131 self.assertGreater(len(topics), 0) 

132 

133 # check it fails if we don't allow case insensitivity 

134 topics = getTopics(self.client, "*mTmoUnt*", caseSensitive=True) 

135 self.assertIsInstance(topics, list) 

136 self.assertEqual(len(topics), 0) 

137 

138 @vcr.use_cassette() 

139 def test_getEfdData(self): 

140 dayStart = getDayObsStartTime(self.dayObs) 

141 dayEnd = getDayObsEndTime(self.dayObs) 

142 oneDay = datetime.timedelta(hours=24) 

143 # twelveHours = datetime.timedelta(hours=12) 

144 

145 # test the dayObs interface 

146 dayObsData = getEfdData(self.client, self.axisTopic, dayObs=self.dayObs) 

147 self.assertIsInstance(dayObsData, pd.DataFrame) 

148 

149 # test the starttime interface 

150 dayStartData = getEfdData(self.client, self.axisTopic, begin=dayStart, timespan=oneDay) 

151 self.assertIsInstance(dayStartData, pd.DataFrame) 

152 

153 # check they're equal 

154 self.assertTrue(dayObsData.equals(dayStartData)) 

155 

156 # test the starttime interface with an endtime 

157 dayEnd = getDayObsEndTime(self.dayObs) 

158 dayStartEndData = getEfdData(self.client, self.axisTopic, begin=dayStart, end=dayEnd) 

159 self.assertTrue(dayObsData.equals(dayStartEndData)) 

160 

161 # test event 

162 # note that here we're going to clip to an event and pad things, so 

163 # we want to use the timeSeriesTopic not the states, so that there's 

164 # plenty of rows to test the padding is actually working 

165 eventData = getEfdData(self.client, self.timeSeriesTopic, event=self.event) 

166 self.assertIsInstance(dayObsData, pd.DataFrame) 

167 

168 # test padding options 

169 padded = getEfdData(self.client, self.timeSeriesTopic, event=self.event, prePadding=1, postPadding=2) 

170 self.assertGreater(len(padded), len(eventData)) 

171 startTimeDiff = efdTimestampToAstropy(eventData.iloc[0]["private_efdStamp"]) - efdTimestampToAstropy( 

172 padded.iloc[0]["private_efdStamp"] 

173 ) 

174 endTimeDiff = efdTimestampToAstropy(padded.iloc[-1]["private_efdStamp"]) - efdTimestampToAstropy( 

175 eventData.iloc[-1]["private_efdStamp"] 

176 ) 

177 

178 self.assertGreater(startTimeDiff.sec, 0) 

179 self.assertLess(startTimeDiff.sec, 1.1) # padding isn't super exact, so give a little wiggle room 

180 self.assertGreater(endTimeDiff.sec, 0) 

181 self.assertLess(endTimeDiff.sec, 2.1) # padding isn't super exact, so give a little wiggle room 

182 

183 with self.assertRaises(ValueError): 

184 # not enough info to constrain 

185 _ = getEfdData(self.client, self.axisTopic) 

186 # dayObs supplied and a start time is not allowed 

187 _ = getEfdData(self.client, self.axisTopic, dayObs=self.dayObs, begin=dayStart) 

188 # dayObs supplied and a stop time is not allowed 

189 _ = getEfdData(self.client, self.axisTopic, dayObs=self.dayObs, end=dayEnd) 

190 # dayObs supplied and timespan is not allowed 

191 _ = getEfdData(self.client, self.axisTopic, dayObs=self.dayObs, timespan=oneDay) 

192 # being alone is not allowed 

193 _ = getEfdData(self.client, self.axisTopic, begin=self.dayObs) 

194 # good query, except the topic doesn't exist 

195 _ = getEfdData(self.client, "badTopic", begin=dayStart, end=dayEnd) 

196 

197 @vcr.use_cassette() 

198 def test_getMostRecentRowWithDataBefore(self): 

199 time = Time(1687845854.736784, scale="utc", format="unix") 

200 rowData = getMostRecentRowWithDataBefore( 

201 self.client, "lsst.sal.MTM1M3.logevent_forceActuatorState", time 

202 ) 

203 self.assertIsInstance(rowData, pd.Series) 

204 

205 stateTime = efdTimestampToAstropy(rowData["private_efdStamp"]) 

206 self.assertLess(stateTime, time) 

207 

208 def test_efdTimestampToAstropy(self): 

209 time = efdTimestampToAstropy(1687845854.736784) 

210 self.assertIsInstance(time, astropy.time.Time) 

211 return 

212 

213 def test_astropyToEfdTimestamp(self): 

214 time = Time(1687845854.736784, scale="utc", format="unix") 

215 efdTimestamp = astropyToEfdTimestamp(time) 

216 self.assertIsInstance(efdTimestamp, float) 

217 return 

218 

219 @vcr.use_cassette() 

220 def test_clipDataToEvent(self): 

221 # get 10 mins of data either side of the event we'll clip to 

222 duration = datetime.timedelta(seconds=10 * 60) 

223 queryBegin = self.event.begin - duration 

224 queryEnd = self.event.end + duration 

225 dayObsData = getEfdData(self.client, "lsst.sal.MTMount.azimuth", begin=queryBegin, end=queryEnd) 

226 

227 # clip the data, and check it's shorter, non-zero, and falls in the 

228 # right time range 

229 clippedData = clipDataToEvent(dayObsData, self.event) 

230 

231 self.assertIsInstance(clippedData, pd.DataFrame) 

232 self.assertGreater(len(clippedData), 0) 

233 self.assertLess(len(clippedData), len(dayObsData)) 

234 

235 dataStart = efdTimestampToAstropy(clippedData.iloc[0]["private_efdStamp"]) 

236 dataEnd = efdTimestampToAstropy(clippedData.iloc[-1]["private_efdStamp"]) 

237 

238 self.assertGreaterEqual(dataStart, self.event.begin) 

239 self.assertLessEqual(dataEnd, self.event.end) 

240 

241 # test the pre/post padding options 

242 clippedPaddedData = clipDataToEvent(dayObsData, self.event, prePadding=1, postPadding=2) 

243 self.assertIsInstance(clippedPaddedData, pd.DataFrame) 

244 self.assertGreater(len(clippedPaddedData), 0) 

245 self.assertLess(len(clippedPaddedData), len(dayObsData)) 

246 self.assertGreater(len(clippedPaddedData), len(clippedData)) 

247 

248 paddedDataStart = efdTimestampToAstropy(clippedPaddedData.iloc[0]["private_efdStamp"]) 

249 paddedDataEnd = efdTimestampToAstropy(clippedPaddedData.iloc[-1]["private_efdStamp"]) 

250 self.assertLessEqual(paddedDataStart, dataStart) 

251 self.assertGreaterEqual(paddedDataEnd, dataEnd) 

252 

253 # Get the minimum and maximum timestamps before padding 

254 startTimeUnpadded = clippedData["private_efdStamp"].min() 

255 endTimeUnpadded = clippedData["private_efdStamp"].max() 

256 

257 # Get the minimum and maximum timestamps after padding 

258 startTimePadded = clippedPaddedData["private_efdStamp"].min() 

259 endTimePadded = clippedPaddedData["private_efdStamp"].max() 

260 

261 # Check that the difference between the min times and max times is 

262 # approximately equal to the padding. Not exact as data sampling is 

263 # not infinite. 

264 self.assertAlmostEqual(startTimeUnpadded - startTimePadded, 1, delta=0.1) 

265 self.assertAlmostEqual(endTimePadded - endTimeUnpadded, 2, delta=0.1) 

266 return 

267 

268 def test_getDayObsForTime(self): 

269 pydate = datetime.datetime(2023, 2, 5, 13, 30, 1) 

270 time = Time(pydate) 

271 dayObs = getDayObsForTime(time) 

272 self.assertEqual(dayObs, 20230205) 

273 

274 pydate = datetime.datetime(2023, 2, 5, 11, 30, 1) 

275 time = Time(pydate) 

276 dayObs = getDayObsForTime(time) 

277 self.assertEqual(dayObs, 20230204) 

278 return 

279 

280 

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

282 pass 

283 

284 

285def setup_module(module): 

286 lsst.utils.tests.init() 

287 

288 

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

290 lsst.utils.tests.init() 

291 unittest.main()