Skip to content

Commit 4c5a041

Browse files
committed
adding BeamNG example. Has been tested to work'' on local machine
1 parent 2c316e8 commit 4c5a041

File tree

9 files changed

+1389
-0
lines changed

9 files changed

+1389
-0
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
MPPI_config:
2+
ROLLOUTS: 1024 # 8192 if using 80 timesteps
3+
TIMESTEPS: &TIMESTEPS 30 # 80
4+
BINS: 1
5+
u_per_command: 1
6+
7+
Map_config:
8+
map_name: "small_island"
9+
map_size: &map_size 100
10+
map_res: &map_res 0.25
11+
map_res_hitl: 0.25
12+
elevation_range: 32.0
13+
layers:
14+
color: 3
15+
elevation: 1
16+
semantics: 3
17+
costmap: 1
18+
19+
Dynamics_config:
20+
wheelbase: &wheelbase 2.6
21+
throttle_to_wheelspeed: &throttle_to_wheelspeed 20.0
22+
steering_max: &steering_max 0.6
23+
dt: &delta_T 0.05
24+
D: 0.8
25+
B: 6.8
26+
C: 1.75
27+
lf: 1.3
28+
lr: 1.3
29+
Iz: 1.7
30+
LPF_tau: 0.5
31+
LPF_st: 0.5
32+
LPF_th: 0.5
33+
res_coeff: 0.00
34+
drag_coeff: 0.00
35+
car_length: *wheelbase
36+
car_width: &car_width 1.5
37+
cg_height: &cg_height 0.75
38+
patch_size: &patch_size 3.0
39+
type: "slip3d"
40+
41+
Sampling_config:
42+
control_dim: 2
43+
noise_0: 1.0 # steering noise
44+
noise_1: 1.0
45+
scaled_dt: 0.1 ## this is effectively the actuator bandwidth, larger values mean the actuator can move faster.
46+
temperature: 0.5
47+
max_thr: 0.8
48+
min_thr: 0.0
49+
50+
Cost_config:
51+
pos_w: 1.0 ## weight on the terminal goal cost
52+
speed_w: 0.1 ## weight with which target speed will be tracked
53+
heading_w: 0.1
54+
scaling_factor: 1.0
55+
critical_RI: &RI 0.8
56+
critical_vert_acc: &max_vert_acc 4.0
57+
roll_ditch_w: 50.0
58+
lethal_w: 50.0
59+
car_bb_length: 2.6
60+
car_bb_width: 1.6
61+
62+
RPS_config:
63+
cg_height: *cg_height
64+
wheelbase: *wheelbase
65+
track_width: *car_width
66+
max_speed: *throttle_to_wheelspeed
67+
steering_max: *steering_max
68+
accel_gain: 0.5
69+
roll_rate_gain: 0.5
70+
steer_slack: 1.0
71+
72+
Planner_config:
73+
unstuck_expansions: 100000 # number of expansions to get the first plan
74+
experiment_info_default:
75+
resolution: 4.0 # starting resolution used for discretization.
76+
epsilon: [5, 5, 3.14, 5.0] # ate, cte, heading, vel for goal region.
77+
tolerance: 0.5 # Your perception (map) resolution should be at least this.
78+
max_level: 5 # maximum level to which you expect the system to go; we use this to cache the hash values.
79+
division_factor: 2 # factor by which the resolution increases every level.
80+
max_expansions: 5000 # maximum number of expansions allowed.
81+
hysteresis: 1000 # hysteresis threshold for for IGHA*-H
82+
node_info:
83+
node_type: "kinodynamic"
84+
length: 2.6
85+
width: 1.6 # actual car width is 1.5
86+
map_res: *map_res
87+
dt: *delta_T
88+
timesteps: 10
89+
step_size: 1.0
90+
steering_list: [-15.0, -5.0, 0.0, 5.0, 15.0] # in degrees.
91+
throttle_list: [-5.0, 0, 5.0]
92+
del_theta: 90.0 # in degrees
93+
del_vel: 2.5
94+
RI: *RI
95+
max_vert_acc: *max_vert_acc
96+
min_vel: 0.0
97+
max_vel: 15.0
98+
max_theta: 25.0 # in degrees
99+
gear_switch_time: 1.0
100+
hash_theta: False
101+
102+
103+
hysteresis: [100, -1] # -1 means HA*M
104+
scenarios: ['0'] # , '1']
105+
time_limit: [120.0]
106+
lookahead: [50.0]
107+
wp_radius: [10.0]
108+
109+
start_pos: [-67, 336, 34.5]
110+
start_quat: [0, 0, 0.3826834, 0.9238795]
111+
112+
run_lockstep: True
113+
burn_time: 0.04
114+
smooth_map: False
115+
116+
vehicle:
117+
make: "sunburst"
118+
model: "offroad"
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# camera and lidar are very heavy from the rendering perspective especially if you have
2+
# a low end gaming laptop (such as the author, 4 GB 1050Ti GPU with an 8th gen i7 CPU)
3+
camera:
4+
enable: False
5+
width: 640 # Default width
6+
height: 480 # Default height
7+
fps: 30 # Default frames per second
8+
fov: 87.0 # default fov of d400 series
9+
pos: [1,0,0.7] ## use (0.15, 0.047, 0.02) for D455, (0.15, 0.025, 0.02) for d435
10+
dir: [0, -1, 0]
11+
up: [0, 0, 1]
12+
rot: [0, 0, 0, 1] ## technically, the up and forward dir should be derivable from this, however, I need to talk to the BnG devs to figure out the conversion from REP103 to BnG
13+
color_optical_frame: "camera_color_optical_frame"
14+
depth_optical_frame: "camera_depth_optical_frame"
15+
depth_frame: "camera_depth_frame"
16+
camera_color_topic: "/camera/color/image_raw"
17+
camera_depth_topic: "/camera/depth/image_rect_raw"
18+
camera_color_info_topic: "/camera/color/camera_info"
19+
camera_depth_info_topic: "/camera/depth/camera_info"
20+
monitor_topic: "/camera/depth/image_rect_raw" ## this is the topic HAL uses for monitoring purposes
21+
annotation: False
22+
lidar:
23+
enable: False
24+
rays_per_second_per_scan: 5000 # Default points
25+
channels: 3 # Default height. in execution we cut the first and last channel if there are only 3 channels to simulate a 2D scanner
26+
fps: 10 # Default frames per second
27+
vertical_angle: 26.9 # this is only for simulation purposes
28+
pos: [0.04, 0, 1.0] # default position of lidar wrt imu
29+
rot: [0, 0, 0, 1]
30+
dir: [0, -1, 0]
31+
up: [0, 0, 1]
32+
frame: "laser_frame"
33+
max_distance: 10.0
34+
scan_topic: "/scan"
35+
monitor_topic: "/scan"
36+
pc_topic: "converted_pc"
37+
vesc:
38+
fps: 50.0
39+
erpm_gain: 3166
40+
topic: "sensors/core"
41+
steering_degrees: 260
42+
mavros:
43+
pos: [0,0,0.8] # default position on the hound
44+
fps: 50
45+
monitor_topic: "/mavros/imu/data_raw"
46+
pose_topic: "/mavros/local_position/pose"
47+
odom_topic: "/mavros/local_position/odom"
48+
state_topic: "/mavros/state"
49+
gps_topic: "/mavros/gpsstatus/gps1/raw"
50+
notification_topic: "/mavros/play_tune"
51+
channel_topic: "mavros/rc/in"
52+
raw_input_topic: '/mavros/manual_control/send'
53+
frame: "base_link"
54+
failure_action: "rosrun mavros mavsys rate --all 50"

examples/BeamNG/IGHAStarMP.py

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import torch.multiprocessing as mp
2+
import numpy as np
3+
import time
4+
import traceback
5+
from queue import Empty
6+
7+
8+
class IGHAStarMP:
9+
"""
10+
Multiprocessing wrapper for the IGHA* planner. Runs the planner in a separate process and communicates via queues.
11+
"""
12+
13+
def __init__(self, configs):
14+
mp.set_start_method("spawn", force=True) # Safe for CUDA
15+
self.query_queue = mp.Queue(5)
16+
self.result_queue = mp.Queue(5)
17+
self.process = mp.Process(
18+
target=self._planner_process,
19+
args=(configs, self.query_queue, self.result_queue),
20+
)
21+
self.process.start()
22+
self.path = None
23+
self.success = False
24+
self.completed = True
25+
self.expansion_counter = 0
26+
27+
def _planner_process(self, configs, query_queue, result_queue):
28+
"""
29+
Planner process: loads the CUDA/C++ kernel and runs the IGHA* planner in response to queries.
30+
"""
31+
import torch
32+
import numpy as np
33+
import os
34+
import sys
35+
import pathlib
36+
37+
BASE_DIR = pathlib.Path(__file__).resolve().parent
38+
sys.path.append(str(BASE_DIR / "src"))
39+
from torch.utils.cpp_extension import load
40+
41+
env_name = configs["experiment_info_default"]["node_info"]["node_type"]
42+
env_macro = {
43+
"simple": "-DUSE_SIMPLE_ENV",
44+
"kinematic": "-DUSE_KINEMATIC_ENV",
45+
"kinodynamic": "-DUSE_KINODYNAMIC_ENV",
46+
}[env_name]
47+
folder_name = os.getcwd()
48+
for path in sys.path:
49+
folder_path = os.path.join(path, folder_name)
50+
if os.path.exists(folder_path):
51+
break
52+
cpp_path = f"{folder_path}/src/ighastar.cpp"
53+
cuda_path = f"{folder_path}/src/Environments/{env_name}.cu"
54+
header_path = f"{folder_path}/src/Environments"
55+
56+
kernel = load(
57+
name="ighastar",
58+
sources=[cpp_path, cuda_path],
59+
extra_include_paths=[header_path],
60+
extra_cflags=["-std=c++17", "-O3", env_macro],
61+
extra_cuda_cflags=["-O3"],
62+
verbose=True,
63+
)
64+
planner = kernel.IGHAStar(configs, False)
65+
print("[IGHAStarMP] Planner loaded.")
66+
67+
map_res = configs["experiment_info_default"]["node_info"]["map_res"]
68+
offset = None
69+
avg_dt = 0.1
70+
stream = torch.cuda.Stream()
71+
while True:
72+
task = query_queue.get()
73+
if task is None:
74+
time.sleep(0.1)
75+
continue
76+
(
77+
map_center,
78+
start_state,
79+
goal_,
80+
costmap,
81+
heightmap,
82+
hysteresis,
83+
expansion_limit,
84+
stop,
85+
) = task
86+
try:
87+
map_size = costmap.shape
88+
bitmap = torch.ones((map_size[0], map_size[1], 2), dtype=torch.float32)
89+
bitmap[..., 0] = torch.from_numpy(costmap)
90+
bitmap[..., 1] = torch.from_numpy(heightmap)
91+
if offset is None:
92+
offset = map_res * np.array(bitmap.shape[:2]) * 0.5
93+
start = torch.zeros(4, dtype=torch.float32)
94+
goal = torch.zeros(4, dtype=torch.float32)
95+
start[:2] = torch.from_numpy(start_state[:2] + offset - map_center)
96+
goal[:2] = torch.from_numpy(goal_[:2] + offset - map_center)
97+
start[2] = start_state[5]
98+
start[3] = float(np.linalg.norm(start_state[6:8]))
99+
if start[3] > 1.0 and start_state[6] > 0.5:
100+
V = start_state[6:8]
101+
theta = start_state[5]
102+
dx = V[0] * np.cos(theta) - V[1] * np.sin(theta)
103+
dy = V[0] * np.sin(theta) + V[1] * np.cos(theta)
104+
start[2] = np.arctan2(dy, dx)
105+
dx = goal[0] - start[0]
106+
dy = goal[1] - start[1]
107+
goal[2] = torch.atan2(dy, dx)
108+
goal[3] = 0.0 if stop else 10.0
109+
now = time.perf_counter()
110+
with torch.cuda.stream(stream):
111+
success = planner.search(
112+
start, goal, bitmap, expansion_limit, hysteresis, True
113+
) # ignore goal validity
114+
(
115+
avg_successor_time,
116+
avg_goal_check_time,
117+
avg_overhead_time,
118+
avg_g_update_time,
119+
switches,
120+
max_level_profile,
121+
Q_v_size,
122+
expansion_counter,
123+
expansion_list,
124+
) = planner.get_profiler_info()
125+
126+
end = time.perf_counter()
127+
avg_dt = 0.9 * avg_dt + 0.1 * (end - now)
128+
print(
129+
f"[IGHAStarMP] Avg_dt: {avg_dt:.3f}s, Expansions: {expansion_counter}, Success: {success}"
130+
)
131+
if success:
132+
path = planner.get_best_path().numpy()
133+
path = np.flip(path, axis=0)
134+
path[..., :2] -= offset
135+
path[..., :2] += map_center
136+
result_queue.put((True, True, path, expansion_counter))
137+
else:
138+
result_queue.put((False, True, None, expansion_counter))
139+
except Exception as e:
140+
print(f"[IGHAStarMP] Planner error: {e}")
141+
traceback.print_exc()
142+
result_queue.put((False, True, None, 3))
143+
144+
def set_query(
145+
self,
146+
map_center,
147+
start_state,
148+
goal_,
149+
costmap,
150+
heightmap,
151+
hysteresis,
152+
expansion_limit,
153+
stop=False,
154+
disable=False,
155+
):
156+
"""
157+
Submit a new planning query. Returns immediately; results are available via update().
158+
"""
159+
if disable:
160+
return
161+
if np.linalg.norm(start_state[:2] - goal_[:2]) < 5.0:
162+
print("[IGHAStarMP] Start and goal are too close, skipping query.")
163+
return
164+
if not self.completed:
165+
return
166+
self.query_queue.put(
167+
(
168+
map_center,
169+
start_state,
170+
goal_,
171+
costmap,
172+
heightmap,
173+
hysteresis,
174+
expansion_limit,
175+
stop,
176+
)
177+
)
178+
self.completed = False
179+
self.success = False
180+
181+
def reset(self):
182+
"""Reset planner state."""
183+
self.path = None
184+
self.success = False
185+
self.completed = True
186+
self.expansion_counter = 0
187+
188+
def update(self):
189+
"""
190+
Call this periodically in your main loop to check for planner results.
191+
Returns (success, path, expansion_counter).
192+
"""
193+
try:
194+
(
195+
self.success,
196+
self.completed,
197+
path,
198+
expansions,
199+
) = self.result_queue.get_nowait()
200+
self.expansion_counter = expansions
201+
if self.success:
202+
self.path = path
203+
return self.success, self.path, self.expansion_counter
204+
else:
205+
return False, self.path, self.expansion_counter
206+
except Empty:
207+
return False, self.path, self.expansion_counter
208+
209+
def shutdown(self):
210+
"""Shut down the planner process."""
211+
self.query_queue.put(None)
212+
self.process.terminate()
213+
self.process.join()

0 commit comments

Comments
 (0)