Skip to content

Door Dataclass

Module defining door representation for the AMR Hub ABM simulation.

This module defines the Door and DetachedDoor classes, which represent doors in the AMR Hub ABM simulation. The Door class includes attributes for the rooms it connects and a unique ID, while the DetachedDoor class is a more flexible representation that can be used for doors that are not yet fully defined. Both classes include validation to ensure that door coordinates are consistent and that doors have a unique identity based on either their name or their coordinates.

DetachedDoor dataclass

Representation of a detached door in the AMR Hub ABM simulation.

Created to allow for more flexible door representations that can be used for doors that are newly read in from input files but whose connecting rooms have not yet been identified. Serves as a base class for the Door class, which includes additional attributes for connecting rooms and a unique ID.

Parameters:

Name Type Description Default
is_open bool

Whether the door is currently open.

required
access_control tuple[bool, bool]

A tuple indicating whether the door allows access from each side.

required
name str | None

The name of the door, if applicable.

None
start tuple[float, float] | None

The starting point of the door in the spatial representation.

None
end tuple[float, float] | None

The ending point of the door in the spatial representation.

None
Source code in src/amr_hub_abm/spatial/door.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
@dataclass(kw_only=True, frozen=True)
class DetachedDoor:
    """
    Representation of a detached door in the AMR Hub ABM simulation.

    Created to allow for more flexible door representations that can be used for doors
    that are newly read in from input files but whose connecting rooms have not yet been
    identified. Serves as a base class for the `Door` class, which includes additional
    attributes for connecting rooms and a unique ID.

    Parameters
    ----------
    is_open : bool
        Whether the door is currently open.
    access_control : tuple[bool, bool]
        A tuple indicating whether the door allows access from each side.
    name : str | None, optional
        The name of the door, if applicable.
    start : tuple[float, float] | None, optional
        The starting point of the door in the spatial representation.
    end : tuple[float, float] | None, optional
        The ending point of the door in the spatial representation.

    """

    is_open: bool
    access_control: tuple[bool, bool]
    name: str | None = field(default=None)
    start: tuple[float, float] | None = field(default=None)
    end: tuple[float, float] | None = field(default=None)

    # --8<--- [end:DetachedDoor]

    def _identity_key(self) -> tuple[object, ...]:
        """
        Key used for equality and hashing.

        If name is defined, use that as the identity key. Otherwise, use the
        coordinates.

        Returns
        -------
        tuple[object, ...]
            The identity key for the door, based on either its name or its coordinates.

        Raises
        ------
        InvalidDoorError
            If the door has neither a name nor defined coordinates.

        """
        if self.name is not None:
            return ("name", self.name)
        # at this point start/end are both not None due to validation

        if self.start is None or self.end is None:
            msg = "Cannot create identity key from door without name or coordinates."
            raise InvalidDoorError(msg)
        return ("coords", self.start, self.end)

    def __eq__(self, other: object) -> bool:
        """
        Define equality comparison for DetachedDoor instances.

        Two DetachedDoor instances are considered equal if they have the same name (if
        defined) or the same coordinates (if name is not defined).

        Parameters
        ----------
        other : object
            The other object to compare against.

        Returns
        -------
        bool
            True if the two DetachedDoor instances are considered equal,
            False otherwise.

        Raises
        ------
        NotImplementedError
            If the other object is not an instance of DetachedDoor.

        """
        if not isinstance(other, DetachedDoor):
            msg = f"Cannot compare DetachedDoor with object of type {type(other)}."
            raise NotImplementedError(msg)
        return self._identity_key() == other._identity_key()

    def __hash__(self) -> int:
        """Define hash for DetachedDoor instances."""
        return hash(self._identity_key())

    def check_for_start_end_consistency(self) -> None:
        """
        Check that start and end points are consistent.

        Raises
        ------
        InvalidDoorError
            If one of start or end is defined but not the other, or if start and end
            are the same point.

        """
        if (self.start is None) != (self.end is None):
            msg = "Both start and end points must be None or both must be defined."
            raise InvalidDoorError(msg)

        if (self.start is not None and self.end is not None) and self.start == self.end:
            msg = "Door start and end points cannot be the same."
            raise InvalidDoorError(msg)

    def __post_init__(self) -> None:
        """
        Post-initialization to validate door coordinates.

        Raises
        ------
        InvalidDoorError
            If the door has inconsistent start and end points, or if it has no name and
            no coordinates.

        """
        if self.start is None and self.end is None:
            # If both start and end are None, we are in topological mode
            # and don't need to check coordinates. We just need to ensure that the door
            # has a name for identity purposes.
            if self.name is None:
                msg = "Door must have a name if start and end points are not defined."
                raise InvalidDoorError(msg)
            return

        self.check_for_start_end_consistency()

        if self.start is None or self.end is None:
            # This should never happen due to the consistency check, but we check again
            # to address mypy's anger issues.
            msg = "Both start and end points must be defined when in spatial mode."
            raise InvalidDoorError(msg)

        if self.start > self.end:
            temp = self.start
            object.__setattr__(self, "start", self.end)
            object.__setattr__(self, "end", temp)

__eq__(other)

Define equality comparison for DetachedDoor instances.

Two DetachedDoor instances are considered equal if they have the same name (if defined) or the same coordinates (if name is not defined).

Parameters:

Name Type Description Default
other object

The other object to compare against.

required

Returns:

Type Description
bool

True if the two DetachedDoor instances are considered equal, False otherwise.

Raises:

Type Description
NotImplementedError

If the other object is not an instance of DetachedDoor.

Source code in src/amr_hub_abm/spatial/door.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
def __eq__(self, other: object) -> bool:
    """
    Define equality comparison for DetachedDoor instances.

    Two DetachedDoor instances are considered equal if they have the same name (if
    defined) or the same coordinates (if name is not defined).

    Parameters
    ----------
    other : object
        The other object to compare against.

    Returns
    -------
    bool
        True if the two DetachedDoor instances are considered equal,
        False otherwise.

    Raises
    ------
    NotImplementedError
        If the other object is not an instance of DetachedDoor.

    """
    if not isinstance(other, DetachedDoor):
        msg = f"Cannot compare DetachedDoor with object of type {type(other)}."
        raise NotImplementedError(msg)
    return self._identity_key() == other._identity_key()

__hash__()

Define hash for DetachedDoor instances.

Source code in src/amr_hub_abm/spatial/door.py
110
111
112
def __hash__(self) -> int:
    """Define hash for DetachedDoor instances."""
    return hash(self._identity_key())

__post_init__()

Post-initialization to validate door coordinates.

Raises:

Type Description
InvalidDoorError

If the door has inconsistent start and end points, or if it has no name and no coordinates.

Source code in src/amr_hub_abm/spatial/door.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
def __post_init__(self) -> None:
    """
    Post-initialization to validate door coordinates.

    Raises
    ------
    InvalidDoorError
        If the door has inconsistent start and end points, or if it has no name and
        no coordinates.

    """
    if self.start is None and self.end is None:
        # If both start and end are None, we are in topological mode
        # and don't need to check coordinates. We just need to ensure that the door
        # has a name for identity purposes.
        if self.name is None:
            msg = "Door must have a name if start and end points are not defined."
            raise InvalidDoorError(msg)
        return

    self.check_for_start_end_consistency()

    if self.start is None or self.end is None:
        # This should never happen due to the consistency check, but we check again
        # to address mypy's anger issues.
        msg = "Both start and end points must be defined when in spatial mode."
        raise InvalidDoorError(msg)

    if self.start > self.end:
        temp = self.start
        object.__setattr__(self, "start", self.end)
        object.__setattr__(self, "end", temp)

check_for_start_end_consistency()

Check that start and end points are consistent.

Raises:

Type Description
InvalidDoorError

If one of start or end is defined but not the other, or if start and end are the same point.

Source code in src/amr_hub_abm/spatial/door.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
def check_for_start_end_consistency(self) -> None:
    """
    Check that start and end points are consistent.

    Raises
    ------
    InvalidDoorError
        If one of start or end is defined but not the other, or if start and end
        are the same point.

    """
    if (self.start is None) != (self.end is None):
        msg = "Both start and end points must be None or both must be defined."
        raise InvalidDoorError(msg)

    if (self.start is not None and self.end is not None) and self.start == self.end:
        msg = "Door start and end points cannot be the same."
        raise InvalidDoorError(msg)

Door dataclass

Bases: DetachedDoor

Representation of a door in the AMR Hub ABM simulation.

Inherits from DetachedDoor and adds attributes for the rooms it connects and a unique ID. The Door class includes validation to ensure that door coordinates are consistent and that the door has a unique identity based on either its name or its coordinates.

Parameters:

Name Type Description Default
connecting_rooms tuple[int, int]

The IDs of the two rooms that the door connects.

required
door_id int

A unique identifier for the door.

required
Source code in src/amr_hub_abm/spatial/door.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
@dataclass(eq=False, kw_only=True, frozen=True)
class Door(DetachedDoor):
    """
    Representation of a door in the AMR Hub ABM simulation.

    Inherits from `DetachedDoor` and adds attributes for the rooms it connects and a
    unique ID. The `Door` class includes validation to ensure that door coordinates are
    consistent and that the door has a unique identity based on either its name or its
    coordinates.

    Parameters
    ----------
    connecting_rooms : tuple[int, int]
        The IDs of the two rooms that the door connects.
    door_id : int
        A unique identifier for the door.

    """

    connecting_rooms: tuple[int, int]
    door_id: int

    # --8<--- [end:Door]

    def __lt__(self, other: object) -> bool:
        """
        Define less-than comparison for Door instances.

        This allows for sorting of Door instances based on their identity key, which is
        determined by either their name or their coordinates.

        Parameters
        ----------
        other : object
            The other object to compare against.

        Returns
        -------
        bool
            True if this Door instance is considered less than the other, False
            otherwise.

        Raises
        ------
        NotImplementedError
            If the other object is not an instance of Door.

        """
        if not isinstance(other, Door):
            return NotImplemented
        return self._identity_key() < other._identity_key()

    def __post_init__(self) -> None:
        """Post-initialization to validate door coordinates and create hash."""
        super().__post_init__()

    @property
    def line(self) -> shapely.geometry.LineString:
        """
        Get the line representation of the door.

        Returns
        -------
        shapely.geometry.LineString
            The line representation of the door based on its start and end coordinates.

        Raises
        ------
        InvalidDoorError
            If the door does not have defined start and end coordinates.

        """
        if self.start is None or self.end is None:
            msg = "Door start and end must be defined when not in topological mode."
            raise InvalidDoorError(msg)
        return shapely.geometry.LineString([self.start, self.end])

line property

Get the line representation of the door.

Returns:

Type Description
LineString

The line representation of the door based on its start and end coordinates.

Raises:

Type Description
InvalidDoorError

If the door does not have defined start and end coordinates.

__lt__(other)

Define less-than comparison for Door instances.

This allows for sorting of Door instances based on their identity key, which is determined by either their name or their coordinates.

Parameters:

Name Type Description Default
other object

The other object to compare against.

required

Returns:

Type Description
bool

True if this Door instance is considered less than the other, False otherwise.

Raises:

Type Description
NotImplementedError

If the other object is not an instance of Door.

Source code in src/amr_hub_abm/spatial/door.py
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
def __lt__(self, other: object) -> bool:
    """
    Define less-than comparison for Door instances.

    This allows for sorting of Door instances based on their identity key, which is
    determined by either their name or their coordinates.

    Parameters
    ----------
    other : object
        The other object to compare against.

    Returns
    -------
    bool
        True if this Door instance is considered less than the other, False
        otherwise.

    Raises
    ------
    NotImplementedError
        If the other object is not an instance of Door.

    """
    if not isinstance(other, Door):
        return NotImplemented
    return self._identity_key() < other._identity_key()

__post_init__()

Post-initialization to validate door coordinates and create hash.

Source code in src/amr_hub_abm/spatial/door.py
222
223
224
def __post_init__(self) -> None:
    """Post-initialization to validate door coordinates and create hash."""
    super().__post_init__()