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

47 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-28 10:10 +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 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/>. 

21 

22from __future__ import annotations 

23 

24__all__ = ( 

25 "TopologicalSpace", 

26 "TopologicalFamily", 

27 "TopologicalRelationshipEndpoint", 

28) 

29 

30import enum 

31from abc import ABC, abstractmethod 

32from collections.abc import Mapping 

33from typing import Any 

34 

35from lsst.utils.classes import immutable 

36 

37from .named import NamedValueAbstractSet 

38 

39 

40@enum.unique 

41class TopologicalSpace(enum.Enum): 

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

43 

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

45 key relationships between tables. Those connected to a 

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

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

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

49 rows. 

50 """ 

51 

52 SPATIAL = enum.auto() 

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

54 those regions in memory. 

55 """ 

56 

57 TEMPORAL = enum.auto() 

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

59 intervals in memory. 

60 """ 

61 

62 

63@immutable 

64class TopologicalFamily(ABC): 

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

66 

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

68 another's in a predefined way. 

69 

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

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

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

73 from each other family in a query. 

74 

75 Parameters 

76 ---------- 

77 name : `str` 

78 Unique string identifier for this family. 

79 category : `TopologicalSpace` 

80 Space in which the regions of this family live. 

81 """ 

82 

83 def __init__( 

84 self, 

85 name: str, 

86 space: TopologicalSpace, 

87 ): 

88 self.name = name 

89 self.space = space 

90 

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

92 if isinstance(other, TopologicalFamily): 

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

94 return False 

95 

96 def __hash__(self) -> int: 

97 return hash(self.name) 

98 

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

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

101 

102 @abstractmethod 

103 def choose( 

104 self, endpoints: NamedValueAbstractSet[TopologicalRelationshipEndpoint] 

105 ) -> TopologicalRelationshipEndpoint: 

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

107 

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

109 is present. 

110 

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

112 

113 Parameters 

114 ---------- 

115 endpoints : `NamedValueAbstractSet` [`TopologicalRelationshipEndpoint`] 

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

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

118 

119 Returns 

120 ------- 

121 best : `TopologicalRelationshipEndpoint` 

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

123 ``endpoints``. 

124 """ 

125 raise NotImplementedError() 

126 

127 name: str 

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

129 """ 

130 

131 space: TopologicalSpace 

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

133 """ 

134 

135 

136@immutable 

137class TopologicalRelationshipEndpoint(ABC): 

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

139 

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

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

142 """ 

143 

144 @property 

145 @abstractmethod 

146 def name(self) -> str: 

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

148 raise NotImplementedError() 

149 

150 @property 

151 @abstractmethod 

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

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

154 

155 It is keyed by the category for that family. 

156 """ 

157 raise NotImplementedError() 

158 

159 @property 

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

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

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

163 

164 @property 

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

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

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