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

46 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-01 11:00 +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 "TopologicalSpace", 

32 "TopologicalFamily", 

33 "TopologicalRelationshipEndpoint", 

34) 

35 

36import enum 

37from abc import ABC, abstractmethod 

38from collections.abc import Mapping, Set 

39from typing import TYPE_CHECKING, Any 

40 

41from lsst.utils.classes import immutable 

42 

43if TYPE_CHECKING: 

44 from .dimensions import DimensionUniverse 

45 

46 

47@enum.unique 

48class TopologicalSpace(enum.Enum): 

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

50 

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

52 key relationships between tables. Those connected to a 

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

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

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

56 rows. 

57 """ 

58 

59 SPATIAL = enum.auto() 

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

61 those regions in memory. 

62 """ 

63 

64 TEMPORAL = enum.auto() 

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

66 intervals in memory. 

67 """ 

68 

69 

70@immutable 

71class TopologicalFamily(ABC): 

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

73 

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

75 another's in a predefined way. 

76 

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

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

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

80 from each other family in a query. 

81 

82 Parameters 

83 ---------- 

84 name : `str` 

85 Unique string identifier for this family. 

86 category : `TopologicalSpace` 

87 Space in which the regions of this family live. 

88 """ 

89 

90 def __init__( 

91 self, 

92 name: str, 

93 space: TopologicalSpace, 

94 ): 

95 self.name = name 

96 self.space = space 

97 

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

99 if isinstance(other, TopologicalFamily): 

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

101 return False 

102 

103 def __hash__(self) -> int: 

104 return hash(self.name) 

105 

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

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

108 

109 @abstractmethod 

110 def choose(self, endpoints: Set[str], universe: DimensionUniverse) -> TopologicalRelationshipEndpoint: 

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

112 

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

114 is present. 

115 

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

117 

118 Parameters 

119 ---------- 

120 endpoints : `~collections.abc.Set` [`str`] 

121 Endpoints to choose from. May include endpoints that are not 

122 members of this family (which should be ignored). 

123 universe : `DimensionUniverse` 

124 Object that manages all known dimensions. 

125 

126 Returns 

127 ------- 

128 best : `TopologicalRelationshipEndpoint` 

129 The best endpoint that is both a member of ``self`` and in 

130 ``endpoints``. 

131 """ 

132 raise NotImplementedError() 

133 

134 name: str 

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

136 """ 

137 

138 space: TopologicalSpace 

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

140 """ 

141 

142 

143@immutable 

144class TopologicalRelationshipEndpoint(ABC): 

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

146 

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

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

149 """ 

150 

151 @property 

152 @abstractmethod 

153 def name(self) -> str: 

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

155 raise NotImplementedError() 

156 

157 @property 

158 @abstractmethod 

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

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

161 

162 It is keyed by the category for that family. 

163 """ 

164 raise NotImplementedError() 

165 

166 @property 

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

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

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

170 

171 @property 

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

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

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