-
Notifications
You must be signed in to change notification settings - Fork 0
Planning graph guide
A graph with directed edges can be defined over the terrain grid, assigning a vertex/node to every free pixel/cell and connecting them with their (free) neighbourhood. The following code snippets are retrieved from the site graph demo notebook, which you can refer to for complete examples.
Two configuration dictionaries are required (here, we define them in separate yaml files):
- A "world" configuration, indicating the dataset with which the graph is associated and a few graph parameters.
velocity_model
describes what traverse velocity model to use to calculate traverse times along each edge. It is an optional collection - omitting it will not calculate edge traverse times during graph generaiton.power_model
describes the mobility power model used to calculate the mobility energy expenditure along every edge. It is also an optional parameter and it requires velocity_model to be defined. Refer to the edge weights calculation section below for more information.
# world.yaml
site_name: "JEZ5_sample" # Dataset name
graph:
connectivity: 8 # connected grid pattern (4 or 8)
velocity_model:
name: 'ConstantVelocity'
params: null
power_model:
name: 'ConstantPower'
params: null
- A "rover" configuration, describing the rover mobility model employed (effective velocity and drive power consumption):
# rover.yaml
motion:
# m/s
velocity: 0.045 # Average/effective traverse speed
# W
power: 137 # Effective mobility power consumption
solar_panel: # for the solar loader module
# m^2
area: 1.5
# 0...1 (0%-100%)
efficiency: 0.3
These configurations are loaded as dictionaries. The complete path to the dataset directory is added to the world configuration with the site_dirpath
key.
# Load world config, add full path to dataset to load
world_fpath = Path(GPLANETARY_CFG.params_dir, "world.yaml")
with open(mission_fpath) as f:
world_cfg = yaml.load(f, Loader=yaml.FullLoader)
world_cfg['site_dirpath'] = Path(os.environ.get('GNAV_DATASET_PATH'), world_cfg['site_name'])
# Load rover config
rover_fpath = Path(GPLANETARY_CFG.params_dir,"rover.yaml")
with open(rover_fpath) as f:
rover_cfg = yaml.load(f, Loader=yaml.FullLoader)
The graph is generated and/or loaded using the load
method. A DiGraph
instance from the networkx
python package is returned:
from gplanetary_nav.graph.grid_graph import GridGraph
gg = GridGraph.load(world_cfg, rover_cfg)
# gg.G is of type networkx.DiGraph
If no existing graph matching the world and rover configurations in the current dataset, this method generates a new one and saves it in the dataset directory under a (new)
graph/
subdirectory.
The data stored into every (directed) edge is the following:
for _, _, features_dict in G.edges(data=True):
log.info(f"Graph edge attributes: {features_dict.keys()}")
break
# Graph edge attributes: ['length', 'pitch_orig', 'roll_orig', 'pitch_dest', 'roll_dest', 'time_orig', 'time_dest', 'time_total', 'energy_orig', 'energy_dest', 'energy_total']
-
lenght: physical length of the edge in metres. This quantity is calculated using the Euclidean distance in three dimensions between the center of neighbouring grid cells according to the elevation map. As a first order approximation, we assume that half of this drive length takes place in the originating grid cell and the other half takes place in the destination grid cell.
-
pitch_{orig/dest}: the pitch of the rover in radians along the first/second half of the drive (i.e., in the "originating"/"destination" grid cell).
-
roll_{orig/dest}: the roll of the rover in radians along the first/second half of the drive.
If a velocity_model
was defined in the world configuration:
-
time_{orig/dest}: the time in seconds required to traverse the first/second half of the edge at the speed indicated in the rover mobility model.
-
time_total: total time in seconds to traverse the whole edge (
time_orig
+time_dest
).
If a power_model
was also defined in the world configuration:
-
energy_{orig/dest}: the energy in Watt-hours required to traverse the first/second half of the edge given the (constant) rover mobility power and the duration
time_{orig/dest}
. -
energy_total: total energy cost in Watt-hours of the whole edge (
energy_orig
+energy_dest
).
Optimal graph-based path planning can be carried out using the networkx package:
import networkx as nx
G = gg.G
start_cell, goal_cell = (152, 152), (40,60)
path = nx.shortest_path(G, start_cell, goal_cell, weight='length')
path_length = path_cost(G, path, 'length')
path_duration = path_cost(G, path, 'time_total')
The example above finds the physically shortest path between two grid cells and then computes the path length in addition to the time required to traverse it.
The site graph demo notebook also provides an example that uses the M2020 Terrain Traversability analysis Tools (MTTT) velocity model and computes the fastest path between the same start/goal cells:
Different velocity and power models can be implemented to calculate traverse times and drive energy, respectively. This section describes the process associated with the calculation of edge traverse times, but a similar reasoning applies to the calculation of energy expenditure.
The velocity model defined in the world configuration takes two parameters: name
and params
:
-
name: name of the class that implements the traverse time calculation. This class must inherit the
BaseCost
class -
params: a collection of parameters specific to this cost class.
As an example, you can refer to the ConstantVelocity
class. This class does not have any custom parameter - it is initialized using BaseCost.__init__()
. The MTTTVelocity
class, on the other hand, takes in 1 specific parameter (optimistic
) in addition to the generic arguments required by BaseCost
.
A world configuration using this model would look like this:
# Site name
site_name: "JEZ5_sample"
graph:
connectivity: 8 # connected grid pattern (4 or 8)
velocity_model:
name: 'MTTTVelocity'
params:
optimistic: False
Velocity models can be added to gplanetary_nav
by following the format of the existing models. The new model should be defined as a class inheriting BaseCost
and implemented in itw own module under (gplanetary_nav/cost/time). The class has to be imported in the subpackage's init file. The class name is the one that must be specified in the name
field in the world configuration file.
The new class needs its own init function if it requires custom parameters (i.e., parameters listed under params
in the world configuration file) and an eval()
method. This method must return the traverse time (in seconds) associated with the arguments provided to it. These arguments are documented in the BaseCost.eval
docstring.
The same reasoning as with the velocity model applies, but in the energy subpackage. The eval()
method of the new power model class must return the energy consumption (in Watt-hours) associated with the arguments provided to it.