Skip to content

Commit 2a935d6

Browse files
authored
Refactor reachable set computation into Agent (#107)
* Refactored reachable set calculation into agent * Resolved bug in home state verification * resolved spelling error
1 parent 88dd79b commit 2a935d6

File tree

11 files changed

+376
-252
lines changed

11 files changed

+376
-252
lines changed

.devcontainer/devcontainer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@
8585
"**/build": true,
8686
"**/install": true,
8787
"**/log": true
88-
}
88+
},
89+
"autoDocstring.docstringFormat": "sphinx"
8990
},
9091
"extensions": [
9192
"ms-azuretools.vscode-docker",

.github/CONTRIBUTING.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,15 @@ rather than
110110
```python
111111
from typing import Optional
112112

113-
agent_location: Optional[Location] = None
113+
agent_location: Optional[Position] = None
114114
```
115115

116116
You should use
117117

118118
```python
119119
from __future__ import annotations
120120

121-
agent_location: Location | None = None
121+
agent_location: Position | None = None
122122
```
123123

124124
Commonly used types will appear in `pymavswarm.types`. These should be used
@@ -139,16 +139,18 @@ The following example demonstrates the Sphinx markdown conventions used by
139139
`pymavswarm`:
140140

141141
```python
142-
def compute_location(current_location: Location | None = None) -> Location | None:
142+
def compute_location(
143+
current_location: Position | None = None
144+
) -> Position | None:
143145
"""
144146
Demonstrate how to write a docstring.
145147
146148
Docstrings are a great way to add developer documentation.
147149
148150
:param current_location: current location of an agent, defaults to None
149-
:type current_location: Location | None, optional
151+
:type current_location: Position | None, optional
150152
:return: computed agent location
151-
:rtype: Location | None
153+
:rtype: Position | None
152154
"""
153155
return current_location
154156
```

pymavswarm/agent.py

Lines changed: 144 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525

2626
import pymavswarm.state as swarm_state
2727
from pymavswarm.mission import SwarmMission
28+
from pymavswarm.safety import HyperRectangle, Interval, SafetyChecker
2829
from pymavswarm.state.generic import Generic
30+
from pymavswarm.utils import latitude_conversion, longitude_conversion
2931

3032

3133
class Agent:
@@ -79,7 +81,7 @@ def __init__(
7981
self.__gps_info = swarm_state.GPSInfo(
8082
0.0, 0.0, 0, 0, optional_context_props=context_props
8183
)
82-
self.__location = swarm_state.Location(
84+
self.__position = swarm_state.Position(
8385
0.0, 0.0, 0.0, optional_context_props=context_props
8486
)
8587
self.__ekf = swarm_state.EKFStatus(
@@ -127,7 +129,7 @@ def __init__(
127129
self.__last_params_read = swarm_state.ParameterList(
128130
max_length=max_params_stored, optional_context_props=context_props
129131
)
130-
self.__home_position = swarm_state.Location(
132+
self.__home_position = swarm_state.Position(
131133
0.0, 0.0, 0.0, optional_context_props=context_props
132134
)
133135
self.__hrl_state = swarm_state.Generic(
@@ -136,7 +138,7 @@ def __init__(
136138
self.__ping = swarm_state.Generic(
137139
"ping", 0, optional_context_props=context_props
138140
)
139-
self.__last_gps_message_timestamp = swarm_state.Generic(
141+
self.__last_position_message_timestamp = swarm_state.Generic(
140142
"time_boot_ms", 0, optional_context_props=context_props
141143
)
142144
self.__clock_offset: deque[int] = deque(maxlen=5)
@@ -208,14 +210,14 @@ def gps_info(self) -> swarm_state.GPSInfo:
208210
return self.__gps_info
209211

210212
@property
211-
def location(self) -> swarm_state.Location:
213+
def position(self) -> swarm_state.Position:
212214
"""
213-
Location of an agent.
215+
Position of an agent.
214216
215-
:return: agent location
216-
:rtype: Location
217+
:return: agent position
218+
:rtype: Position
217219
"""
218-
return self.__location
220+
return self.__position
219221

220222
@property
221223
def ekf(self) -> swarm_state.EKFStatus:
@@ -368,12 +370,12 @@ def last_params_read(self) -> swarm_state.ParameterList:
368370
return self.__last_params_read
369371

370372
@property
371-
def home_position(self) -> swarm_state.Location:
373+
def home_position(self) -> swarm_state.Position:
372374
"""
373375
Home position of the agent.
374376
375377
:return: agent's home position
376-
:rtype: Location
378+
:rtype: Position
377379
"""
378380
return self.__home_position
379381

@@ -398,14 +400,14 @@ def ping(self) -> Generic:
398400
return self.__ping
399401

400402
@property
401-
def last_gps_message_timestamp(self) -> Generic:
403+
def last_position_message_timestamp(self) -> Generic:
402404
"""
403-
Most recent time that the agent sent the GPS message in the global clock.
405+
Most recent time that the agent sent the position message in the global clock.
404406
405407
:return: time since boot [ms]
406408
:rtype: Generic
407409
"""
408-
return self.__last_gps_message_timestamp
410+
return self.__last_position_message_timestamp
409411

410412
def update_clock_offset(self, offset: int) -> None:
411413
"""
@@ -429,6 +431,135 @@ def clock_offset(self) -> int:
429431
"""
430432
return int(fmean(self.__clock_offset))
431433

434+
def compute_reachable_set(
435+
self,
436+
position_error: float,
437+
velocity_error: float,
438+
reach_time: float,
439+
initial_step_size: float = 0.5,
440+
reach_timeout: float = 0.001,
441+
uses_lat_lon: bool = True,
442+
) -> tuple[HyperRectangle, float]:
443+
"""
444+
Compute the current reachable set of the agent.
445+
446+
:param position_error: 3D position error to account for in the measurement
447+
:type position_error: float
448+
:param velocity_error: 3D velocity error to account for in the measurement
449+
:type velocity_error: float
450+
:param reach_time: time that the reachable set should reach forward to
451+
:type reach_time: float
452+
:param initial_step_size: initial step to step forward when performing face
453+
lifting (lower means higher accuracy but slower; higher means lower
454+
accuracy but faster), defaults to 0.5
455+
:type initial_step_size: float, optional
456+
:param reach_timeout: maximum amount of time to spend computing the reachable
457+
set [s], defaults to 0.001
458+
:type reach_timeout: float, optional
459+
:return: reachable set for the agent, time that the reachable set reaches to
460+
from the start time
461+
:rtype: tuple[HyperRectangle, float]
462+
"""
463+
if uses_lat_lon:
464+
rect = HyperRectangle(
465+
[
466+
# When computing the reachable state for the GPS position, we pass
467+
# the origin as the position. After computing what the reachable
468+
# change, we correct the original lat/lon position. This
469+
# helps us eliminate *some* error that occurs during
470+
# conversions.
471+
Interval(-position_error, position_error),
472+
Interval(-position_error, position_error),
473+
Interval(
474+
self.position.z - position_error,
475+
self.position.z + position_error,
476+
),
477+
Interval(
478+
self.velocity.x - velocity_error,
479+
self.velocity.x + velocity_error,
480+
),
481+
Interval(
482+
self.velocity.y - velocity_error,
483+
self.velocity.y + velocity_error,
484+
),
485+
Interval(
486+
self.velocity.z - velocity_error,
487+
self.velocity.z + velocity_error,
488+
),
489+
]
490+
)
491+
else:
492+
rect = HyperRectangle(
493+
[
494+
Interval(
495+
self.position.x - position_error,
496+
self.position.x + position_error,
497+
),
498+
Interval(
499+
self.position.y - position_error,
500+
self.position.y + position_error,
501+
),
502+
Interval(
503+
self.position.z - position_error,
504+
self.position.z + position_error,
505+
),
506+
Interval(
507+
self.velocity.x - velocity_error,
508+
self.velocity.x + velocity_error,
509+
),
510+
Interval(
511+
self.velocity.y - velocity_error,
512+
self.velocity.y + velocity_error,
513+
),
514+
Interval(
515+
self.velocity.z - velocity_error,
516+
self.velocity.z + velocity_error,
517+
),
518+
]
519+
)
520+
521+
# Compute the sender's reachable state
522+
(
523+
reachable_state,
524+
reach_time_elapsed,
525+
) = SafetyChecker.face_lifting_iterative_improvement(
526+
rect,
527+
self.last_position_message_timestamp.value,
528+
(
529+
self.acceleration.x,
530+
self.acceleration.y,
531+
self.acceleration.z,
532+
),
533+
reach_time,
534+
initial_step_size=initial_step_size,
535+
timeout=reach_timeout,
536+
)
537+
538+
if uses_lat_lon:
539+
# Correct the latitude using the reachable positions
540+
reachable_state.intervals[0].interval_min = latitude_conversion(
541+
self.position.x,
542+
reachable_state.intervals[0].interval_min,
543+
)
544+
reachable_state.intervals[0].interval_max = latitude_conversion(
545+
self.position.x,
546+
reachable_state.intervals[0].interval_max,
547+
)
548+
549+
# Correct the longitude using the reachable positions
550+
reachable_state.intervals[1].interval_min = longitude_conversion(
551+
self.position.x,
552+
self.position.y,
553+
reachable_state.intervals[1].interval_min,
554+
)
555+
reachable_state.intervals[1].interval_max = longitude_conversion(
556+
self.position.x,
557+
self.position.y,
558+
reachable_state.intervals[1].interval_max,
559+
)
560+
561+
return reachable_state, reach_time_elapsed
562+
432563
def __str__(self) -> str:
433564
"""
434565
Print agent information in a human-readable format.

pymavswarm/handlers/message_receivers.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,12 @@ def listener(message: Any, agents: dict[AgentID, Agent]) -> None:
162162
)
163163

164164
# Update the agent location
165-
agents[agent_id].location.latitude = message.lat / 1.0e7
166-
agents[agent_id].location.longitude = message.lon / 1.0e7
167-
agents[agent_id].location.altitude = message.alt / 1000
165+
agents[agent_id].position.x = message.lat / 1.0e7
166+
agents[agent_id].position.y = message.lon / 1.0e7
167+
agents[agent_id].position.z = message.alt / 1000
168168

169169
# Update the last GPS timestamp using the current clock offset
170-
agents[agent_id].last_gps_message_timestamp.value = (
170+
agents[agent_id].last_position_message_timestamp.value = (
171171
message.time_boot_ms - agents[agent_id].clock_offset
172172
)
173173

@@ -322,9 +322,9 @@ def listener(message: Any, agents: dict[AgentID, Agent]) -> None:
322322
return agents
323323

324324
# Update the agent home location
325-
agents[agent_id].home_position.latitude = message.latitude / 1.0e7
326-
agents[agent_id].home_position.longitude = message.longitude / 1.0e7
327-
agents[agent_id].home_position.altitude = message.altitude / 1000
325+
agents[agent_id].home_position.x = message.latitude / 1.0e7
326+
agents[agent_id].home_position.y = message.longitude / 1.0e7
327+
agents[agent_id].home_position.z = message.altitude / 1000
328328

329329
return
330330

0 commit comments

Comments
 (0)