Coverage for tests/test_contexts.py: 58%

92 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-09-21 09:25 +0000

1# This file is part of pipe_base. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://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 <http://www.gnu.org/licenses/>. 

21from __future__ import annotations 

22 

23import warnings 

24from typing import cast 

25from unittest import TestCase, main 

26 

27import lsst.utils.tests 

28import numpy as np 

29from lsst.analysis.tools import ( 

30 AnalysisAction, 

31 AnalysisTool, 

32 KeyedData, 

33 KeyedDataAction, 

34 KeyedDataSchema, 

35 Scalar, 

36 ScalarAction, 

37 Vector, 

38) 

39from lsst.analysis.tools.actions.scalar import MeanAction, MedianAction 

40from lsst.analysis.tools.contexts import Context 

41from lsst.pex.config import Field 

42from lsst.pipe.tasks.configurableActions import ConfigurableActionField, ConfigurableActionStructField 

43 

44 

45class MedianContext(Context): 

46 """Test Context for median""" 

47 

48 pass 

49 

50 

51class MeanContext(Context): 

52 """Test Context for mean""" 

53 

54 pass 

55 

56 

57class MultiplyContext(Context): 

58 """Test Context to multiply results""" 

59 

60 pass 

61 

62 

63class DivideContext(Context): 

64 """Test Context to divide result""" 

65 

66 pass 

67 

68 

69class TestAction1(KeyedDataAction): 

70 multiple = ConfigurableActionStructField[ScalarAction](doc="Multiple Actions") 

71 

72 def getInputSchema(self) -> KeyedDataSchema: 

73 return (("a", Vector),) 

74 

75 def medianContext(self) -> None: 

76 self.multiple.a = MedianAction(vectorKey="a") 

77 self.multiple.b = MedianAction(vectorKey="a") 

78 self.multiple.c = MedianAction(vectorKey="a") 

79 

80 def meanContext(self) -> None: 

81 self.multiple.a = MeanAction(vectorKey="a") 

82 self.multiple.b = MeanAction(vectorKey="a") 

83 

84 def __call__(self, data: KeyedData, **kwargs) -> KeyedData: 

85 result = np.array([action(data, **kwargs) for action in self.multiple]) 

86 return cast(KeyedData, {"b": result}) 

87 

88 

89class TestAction2(KeyedDataAction): 

90 single = ConfigurableActionField[ScalarAction](doc="Single Action") 

91 multiplier = ConfigurableActionField[AnalysisAction](doc="Multiplier") 

92 

93 def getInputSchema(self) -> KeyedDataSchema: 

94 return (("b", Vector),) 

95 

96 def setDefaults(self) -> None: 

97 super().setDefaults() 

98 # This action remains constant in every context, but it will be 

99 # recursively configured with any contexts 

100 self.multiplier = TestAction3() 

101 

102 def medianContext(self) -> None: 

103 self.single = MedianAction(vectorKey="b") 

104 

105 def meanContext(self) -> None: 

106 self.single = MeanAction(vectorKey="b") 

107 

108 def __call__(self, data: KeyedData, **kwargs) -> KeyedData: 

109 result = self.single(data, **kwargs) 

110 result *= cast(Scalar, self.multiplier(cast(KeyedData, {"c": result}), **kwargs)["d"]) 

111 return {"c": result} 

112 

113 

114class TestAction3(KeyedDataAction): 

115 multiplier = Field[float](doc="Number to multiply result by") 

116 

117 def getInputSchema(self) -> KeyedDataSchema: 

118 return (("c", Vector),) 

119 

120 def multiplyContext(self): 

121 self.multiplier = 2 

122 

123 def divideContext(self): 

124 self.multiplier = 0.5 

125 

126 def __call__(self, data: KeyedData, **kwargs) -> KeyedData: 

127 return {"d": cast(Scalar, data["c"]) * self.multiplier} 

128 

129 

130class TestAnalysisTool(AnalysisTool): 

131 def setDefaults(self) -> None: 

132 self.prep = TestAction1() 

133 self.process = TestAction2() 

134 self.produce = TestAction3() 

135 

136 

137class ContextTestCase(TestCase): 

138 def setUp(self) -> None: 

139 super().setUp() 

140 self.array = np.arange(20) 

141 self.input = cast(KeyedData, {"a": self.array}) 

142 

143 def testContext1(self): 

144 tester = TestAnalysisTool() 

145 # test applying contexts serially 

146 tester.applyContext(MultiplyContext()) 

147 

148 # verify assignment syntax works to support Yaml 

149 # normally this should be called as a function in python 

150 tester.applyContext = MedianContext 

151 # cast below, because we are abusing AnalysisTool a bit for testing 

152 # the final stage produces KeyedData instead of Measurement of Figure 

153 with warnings.catch_warnings(): 

154 warnings.simplefilter("ignore") 

155 result = cast(KeyedData, tester(self.input)) 

156 self.assertEqual(result["d"], 361) 

157 

158 def testContext2(self): 

159 tester2 = TestAnalysisTool() 

160 compound = MeanContext | DivideContext 

161 tester2.applyContext(compound) 

162 # cast below, because we are abusing AnalysisTool a bit for testing 

163 # the final stage produces KeyedData instead of Measurement of Figure 

164 with warnings.catch_warnings(): 

165 warnings.simplefilter("ignore") 

166 result = cast(KeyedData, tester2(self.input)) 

167 self.assertEqual(result["d"], 22.5625) 

168 

169 

170class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase): 

171 pass 

172 

173 

174def setup_module(module): 

175 lsst.utils.tests.init() 

176 

177 

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

179 lsst.utils.tests.init() 

180 main()