Coverage for tests/test_efdUtils.py: 37%
153 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-23 14:48 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-23 14:48 +0000
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/>.
22"""Test cases for utils."""
24import asyncio
25import datetime
26import unittest
28import astropy
29import pandas as pd
30from astropy.time import Time
31from utils import getVcr
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
48HAS_EFD_CLIENT = True
49try:
50 import lsst_efd_client
51except ImportError:
52 HAS_EFD_CLIENT = False
54vcr = getVcr()
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 )
85 @vcr.use_cassette()
86 def tearDown(self):
87 loop = asyncio.get_event_loop()
88 loop.run_until_complete(self.client.influx_client.close())
90 @vcr.use_cassette()
91 def test_makeEfdClient(self):
92 self.assertIsInstance(self.client, lsst_efd_client.efd_helper.EfdClient)
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)
112 dayEnd = getDayObsEndTime(dayObs)
113 self.assertIsInstance(dayStart, astropy.time.Time)
115 self.assertGreater(dayEnd, dayStart)
116 self.assertEqual(dayEnd.jd, dayStart.jd + 1)
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)
124 topics = getTopics(self.client, "*fake.topics.does.not.exist*")
125 self.assertIsInstance(topics, list)
126 self.assertEqual(len(topics), 0)
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)
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)
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)
145 # test the dayObs interface
146 dayObsData = getEfdData(self.client, self.axisTopic, dayObs=self.dayObs)
147 self.assertIsInstance(dayObsData, pd.DataFrame)
149 # test the starttime interface
150 dayStartData = getEfdData(self.client, self.axisTopic, begin=dayStart, timespan=oneDay)
151 self.assertIsInstance(dayStartData, pd.DataFrame)
153 # check they're equal
154 self.assertTrue(dayObsData.equals(dayStartData))
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))
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)
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 )
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
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)
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)
205 stateTime = efdTimestampToAstropy(rowData["private_efdStamp"])
206 self.assertLess(stateTime, time)
208 def test_efdTimestampToAstropy(self):
209 time = efdTimestampToAstropy(1687845854.736784)
210 self.assertIsInstance(time, astropy.time.Time)
211 return
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
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)
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)
231 self.assertIsInstance(clippedData, pd.DataFrame)
232 self.assertGreater(len(clippedData), 0)
233 self.assertLess(len(clippedData), len(dayObsData))
235 dataStart = efdTimestampToAstropy(clippedData.iloc[0]["private_efdStamp"])
236 dataEnd = efdTimestampToAstropy(clippedData.iloc[-1]["private_efdStamp"])
238 self.assertGreaterEqual(dataStart, self.event.begin)
239 self.assertLessEqual(dataEnd, self.event.end)
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))
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)
253 # Get the minimum and maximum timestamps before padding
254 startTimeUnpadded = clippedData["private_efdStamp"].min()
255 endTimeUnpadded = clippedData["private_efdStamp"].max()
257 # Get the minimum and maximum timestamps after padding
258 startTimePadded = clippedPaddedData["private_efdStamp"].min()
259 endTimePadded = clippedPaddedData["private_efdStamp"].max()
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
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)
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
281class TestMemory(lsst.utils.tests.MemoryTestCase):
282 pass
285def setup_module(module):
286 lsst.utils.tests.init()
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()