Coverage for python / lsst / daf / butler / _topology.py: 77%

50 statements  

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

1# This file is part of daf_butler. 

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 

28from __future__ import annotations 

29 

30__all__ = ( 

31 "TopologicalFamily", 

32 "TopologicalRelationshipEndpoint", 

33 "TopologicalSpace", 

34) 

35 

36import enum 

37from abc import ABC, abstractmethod 

38from collections.abc import Mapping 

39from typing import TYPE_CHECKING, Any 

40 

41from lsst.utils.classes import immutable 

42 

43if TYPE_CHECKING: 

44 from .dimensions import DimensionGroup 

45 from .queries.tree import ColumnReference 

46 

47 

48@enum.unique 

49class TopologicalSpace(enum.Enum): 

50 """Enumeration of continuous-variable relationships for dimensions. 

51 

52 Most dimension relationships are discrete, in that they are regular foreign 

53 key relationships between tables. Those connected to a 

54 `TopologicalSpace` are not - a row in a table instead occupies some 

55 region in a continuous-variable space, and topological operators like 

56 "overlaps" between regions in that space define the relationships between 

57 rows. 

58 """ 

59 

60 SPATIAL = enum.auto() 

61 """The (spherical) sky, using `lsst.sphgeom.Region` objects to represent 

62 those regions in memory. 

63 """ 

64 

65 TEMPORAL = enum.auto() 

66 """Time, using `Timespan` instances (with TAI endpoints) to represent 

67 intervals in memory. 

68 """ 

69 

70 

71@immutable 

72class TopologicalFamily(ABC): 

73 """A grouping of `TopologicalRelationshipEndpoint` objects. 

74 

75 These regions form a hierarchy in which one endpoint's rows always contain 

76 another's in a predefined way. 

77 

78 This hierarchy means that endpoints in the same family do not generally 

79 have to be related using (e.g.) overlaps; instead, the regions 

80 from one "best" endpoint from each family are related to the best endpoint 

81 from each other family in a query. 

82 

83 Parameters 

84 ---------- 

85 name : `str` 

86 Unique string identifier for this family. 

87 space : `TopologicalSpace` 

88 Space in which the regions of this family live. 

89 """ 

90 

91 def __init__( 

92 self, 

93 name: str, 

94 space: TopologicalSpace, 

95 ): 

96 self.name = name 

97 self.space = space 

98 

99 def __eq__(self, other: Any) -> bool: 

100 if isinstance(other, TopologicalFamily): 

101 return self.space == other.space and self.name == other.name 

102 return False 

103 

104 def __hash__(self) -> int: 

105 return hash(self.name) 

106 

107 def __contains__(self, other: TopologicalRelationshipEndpoint) -> bool: 

108 return other.topology.get(self.space) == self 

109 

110 def __repr__(self) -> str: 

111 return self.name 

112 

113 @abstractmethod 

114 def choose(self, dimensions: DimensionGroup) -> TopologicalRelationshipEndpoint: 

115 """Select the best member of this family to use. 

116 

117 These are to be used in a query join or data ID when more than one 

118 is present. 

119 

120 Usually this should correspond to the most fine-grained region. 

121 

122 Parameters 

123 ---------- 

124 dimensions : `DimensionGroup` 

125 Dimensions to choose from, if this is a dimension-based topological 

126 family. 

127 

128 Returns 

129 ------- 

130 best : `TopologicalRelationshipEndpoint` 

131 The best endpoint from this family for these dimensions. 

132 """ 

133 raise NotImplementedError() 

134 

135 @abstractmethod 

136 def make_column_reference(self, endpoint: TopologicalRelationshipEndpoint) -> ColumnReference: 

137 """Create a column reference to the generalized region column for the 

138 given endpoint. 

139 

140 Parameters 

141 ---------- 

142 endpoint : `TopologicalRelationshipEndpoint` 

143 Endpoint to create a column reference to. 

144 

145 Returns 

146 ------- 

147 column : `.queries.tree.ColumnReference` 

148 Column reference. 

149 """ 

150 raise NotImplementedError() 

151 

152 name: str 

153 """Unique string identifier for this family (`str`). 

154 """ 

155 

156 space: TopologicalSpace 

157 """Space in which the regions of this family live (`TopologicalSpace`). 

158 """ 

159 

160 

161@immutable 

162class TopologicalRelationshipEndpoint(ABC): 

163 """Representation of a logical table that can participate in overlap joins. 

164 

165 An abstract base class whose instances represent a logical table that 

166 may participate in overlap joins defined by a `TopologicalSpace`. 

167 """ 

168 

169 @property 

170 @abstractmethod 

171 def name(self) -> str: 

172 """Return unique string identifier for this endpoint (`str`).""" 

173 raise NotImplementedError() 

174 

175 @property 

176 @abstractmethod 

177 def topology(self) -> Mapping[TopologicalSpace, TopologicalFamily]: 

178 """Return the relationship families to which this endpoint belongs. 

179 

180 It is keyed by the category for that family. 

181 """ 

182 raise NotImplementedError() 

183 

184 @property 

185 def spatial(self) -> TopologicalFamily | None: 

186 """Return this endpoint's `~TopologicalSpace.SPATIAL` family.""" 

187 return self.topology.get(TopologicalSpace.SPATIAL) 

188 

189 @property 

190 def temporal(self) -> TopologicalFamily | None: 

191 """Return this endpoint's `~TopologicalSpace.TEMPORAL` family.""" 

192 return self.topology.get(TopologicalSpace.TEMPORAL)