Coverage for python / lsst / pex / config / comparison.py: 8%

39 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-26 08:53 +0000

1# This file is part of pex_config. 

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 software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

15# This program is free software: you can redistribute it and/or modify 

16# it under the terms of the GNU General Public License as published by 

17# the Free Software Foundation, either version 3 of the License, or 

18# (at your option) any later version. 

19# 

20# This program is distributed in the hope that it will be useful, 

21# but WITHOUT ANY WARRANTY; without even the implied warranty of 

22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

23# GNU General Public License for more details. 

24# 

25# You should have received a copy of the GNU General Public License 

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

27 

28"""Helper functions for comparing `lsst.pex.config.Config` instancess. 

29 

30Theses function should be use for any comparison in a `lsst.pex.Config.compare` 

31or `lsst.pex.config.Field._compare` implementation, as they take care of 

32writing messages as well as floating-point comparisons and shortcuts. 

33""" 

34 

35__all__ = ("compareConfigs", "compareScalars", "getComparisonName") 

36 

37import numpy 

38 

39 

40def getComparisonName(name1, name2): 

41 """Create a comparison name that is used for printed output of comparisons. 

42 

43 Parameters 

44 ---------- 

45 name1 : `str` 

46 Name of the first configuration. 

47 name2 : `str` 

48 Name of the second configuration. 

49 

50 Returns 

51 ------- 

52 name : `str` 

53 When ``name1`` and ``name2`` are equal, the returned name is 

54 simply one of the names. When they are different the returned name is 

55 formatted as ``"{name1} / {name2}"``. 

56 """ 

57 if name1 != name2: 

58 return f"{name1} / {name2}" 

59 return name1 

60 

61 

62def compareScalars(name, v1, v2, output, rtol=1e-8, atol=1e-8, dtype=None): 

63 """Compare two scalar values for equality. 

64 

65 This function is a helper for `lsst.pex.config.Config.compare`. 

66 

67 Parameters 

68 ---------- 

69 name : `str` 

70 Name to use when reporting differences, typically created by 

71 `getComparisonName`. This will always appear as the beginning of any 

72 messages reported via ``output``. 

73 v1 : `object` 

74 Left-hand side value to compare. 

75 v2 : `object` 

76 Right-hand side value to compare. 

77 output : `collections.abc.Callable` or `None` 

78 A callable that takes a string, used (possibly repeatedly) to report 

79 inequalities (for example, `print`). Set to `None` to disable output. 

80 rtol : `float`, optional 

81 Relative tolerance for floating point comparisons. 

82 atol : `float`, optional 

83 Absolute tolerance for floating point comparisons. 

84 dtype : `type`, optional 

85 Data type of values for comparison. May be `None` if values are not 

86 floating-point. 

87 

88 Returns 

89 ------- 

90 areEqual : `bool` 

91 `True` if the values are equal, `False` if they are not. 

92 

93 See Also 

94 -------- 

95 lsst.pex.config.compareConfigs 

96 

97 Notes 

98 ----- 

99 Floating point comparisons are performed by `numpy.allclose`. 

100 """ 

101 if v1 is None or v2 is None: 

102 result = v1 == v2 

103 elif dtype in (float, complex): 

104 result = numpy.allclose(v1, v2, rtol=rtol, atol=atol) or (numpy.isnan(v1) and numpy.isnan(v2)) 

105 else: 

106 result = v1 == v2 

107 if not result and output is not None: 

108 output(f"{name}: {v1!r} != {v2!r}") 

109 return result 

110 

111 

112def compareConfigs(name, c1, c2, shortcut=True, rtol=1e-8, atol=1e-8, output=None): 

113 """Compare two `lsst.pex.config.Config` instances for equality. 

114 

115 This function is a helper for `lsst.pex.config.Config.compare`. 

116 

117 Parameters 

118 ---------- 

119 name : `str` 

120 Name to use when reporting differences, typically created by 

121 `getComparisonName`. This will always appear as the beginning of any 

122 messages reported via ``output``. 

123 c1 : `lsst.pex.config.Config` 

124 Left-hand side config to compare. 

125 c2 : `lsst.pex.config.Config` 

126 Right-hand side config to compare. 

127 shortcut : `bool`, optional 

128 If `True`, return as soon as an inequality is found. Default is `True`. 

129 rtol : `float`, optional 

130 Relative tolerance for floating point comparisons. 

131 atol : `float`, optional 

132 Absolute tolerance for floating point comparisons. 

133 output : `collections.abc.Callable`, optional 

134 A callable that takes a string, used (possibly repeatedly) to report 

135 inequalities. For example: `print`. 

136 

137 Returns 

138 ------- 

139 areEqual : `bool` 

140 `True` when the two `lsst.pex.config.Config` instances are equal. 

141 `False` if there is an inequality. 

142 

143 See Also 

144 -------- 

145 lsst.pex.config.compareScalars 

146 

147 Notes 

148 ----- 

149 Floating point comparisons are performed by `numpy.allclose`. 

150 

151 If ``c1`` or ``c2`` contain `~lsst.pex.config.RegistryField` or 

152 `~lsst.pex.config.ConfigChoiceField` instances, *unselected* 

153 `~lsst.pex.config.Config` instances will not be compared. 

154 """ 

155 from .config import _typeStr 

156 

157 assert name is not None 

158 if c1 is None: 

159 if c2 is None: 

160 return True 

161 else: 

162 if output is not None: 

163 output(f"{name}: None != {c2!r}.") 

164 return False 

165 else: 

166 if c2 is None: 

167 if output is not None: 

168 output(f"{name}: {c1!r} != None.") 

169 return False 

170 if type(c1) is not type(c2): 

171 if output is not None: 

172 output(f"{name}: config types do not match; {_typeStr(c1)} != {_typeStr(c2)}.") 

173 return False 

174 equal = True 

175 for field in c1._fields.values(): 

176 result = field._compare(c1, c2, shortcut=shortcut, rtol=rtol, atol=atol, output=output) 

177 if not result and shortcut: 

178 return False 

179 equal = equal and result 

180 return equal