├── mamp ├── agents │ ├── __init__.py │ └── obstacle.py ├── configs │ ├── __init__.py │ └── config.py ├── envs │ ├── rosport │ │ ├── __init__.py │ │ ├── car.py │ │ └── bot.py │ ├── __init__.py │ ├── gazeboEnv.py │ ├── carenv.py │ └── macaenv.py ├── policies │ ├── pcaPolicy │ │ ├── __init__.py │ │ ├── dubinsmaneuver2d.py │ │ ├── rvoDubinsPolicy.py │ │ └── pcaPolicy.py │ ├── __init__.py │ ├── policy.py │ ├── rvoPolicy.py │ ├── drvoPolicy.py │ └── orcaPolicy.py ├── dynamics │ ├── __init__.py │ ├── Dynamics.py │ ├── FullDynamics.py │ └── UnicycleDynamics.py ├── sensors │ ├── __init__.py │ ├── Sensor.py │ ├── OtherAgentsObsSensor.py │ └── OtherAgentsStatesSensor.py ├── __init__.py └── util.py ├── draw ├── figs │ ├── s1.png │ ├── cc1.jpg │ ├── cc2.jpg │ ├── cr1.jpg │ ├── cr2.jpg │ ├── large1.jpg │ └── large2.jpg ├── animate.py ├── vis_util.py └── plt2d.py ├── .gitignore ├── LICENSE ├── README.md └── runpy ├── run_drvo.py ├── run_orca.py ├── run_rvo.py ├── run_rvodubins.py └── run_pca.py /mamp/agents/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mamp/configs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mamp/envs/rosport/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /mamp/policies/pcaPolicy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /draw/figs/s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuuya1/PCA/HEAD/draw/figs/s1.png -------------------------------------------------------------------------------- /draw/figs/cc1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuuya1/PCA/HEAD/draw/figs/cc1.jpg -------------------------------------------------------------------------------- /draw/figs/cc2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuuya1/PCA/HEAD/draw/figs/cc2.jpg -------------------------------------------------------------------------------- /draw/figs/cr1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuuya1/PCA/HEAD/draw/figs/cr1.jpg -------------------------------------------------------------------------------- /draw/figs/cr2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuuya1/PCA/HEAD/draw/figs/cr2.jpg -------------------------------------------------------------------------------- /draw/figs/large1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuuya1/PCA/HEAD/draw/figs/large1.jpg -------------------------------------------------------------------------------- /draw/figs/large2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuuya1/PCA/HEAD/draw/figs/large2.jpg -------------------------------------------------------------------------------- /mamp/dynamics/__init__.py: -------------------------------------------------------------------------------- 1 | from .Dynamics import Dynamics 2 | from .FullDynamics import FullDynamics 3 | from .UnicycleDynamics import UnicycleDynamics -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .idea 3 | data_analysis 4 | draw/drvo 5 | draw/orca 6 | draw/pca 7 | draw/rvo 8 | draw/rvo_dubins 9 | data_analysis 10 | 11 | -------------------------------------------------------------------------------- /mamp/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | from .OtherAgentsObsSensor import OtherAgentsObsSensor 2 | from .OtherAgentsStatesSensor import OtherAgentsStatesSensor 3 | from .Sensor import Sensor 4 | 5 | 6 | 7 | sensor_dict = { 8 | 'empty_obs': Sensor, 9 | 'other_agents_obs': OtherAgentsObsSensor, 10 | 'other_agents_states': OtherAgentsStatesSensor, 11 | } 12 | -------------------------------------------------------------------------------- /mamp/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from gym.envs.registration import register 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | register( 7 | id='LineFollower-v0', 8 | entry_point='mamp.envs.carenv:CAREnv', 9 | ) 10 | 11 | register( 12 | id='MAGazebo-v0', 13 | entry_point='mamp.envs.gazeboEnv:GazeboEnv', 14 | ) 15 | 16 | register( 17 | id='MultiAgentCollisionAvoidance-v0', 18 | entry_point='mamp.envs.macaenv:MACAEnv', 19 | ) 20 | 21 | __all__=['agent'] 22 | -------------------------------------------------------------------------------- /mamp/policies/__init__.py: -------------------------------------------------------------------------------- 1 | from .policy import Policy 2 | from .rvoPolicy import RVOPolicy 3 | from .rvoPolicy import RVOPolicy 4 | from .pcaPolicy.pcaPolicy import PCAPolicy 5 | from .drvoPolicy import DRVOPolicy 6 | from .orcaPolicy import ORCAPolicy 7 | from .pcaPolicy.rvoDubinsPolicy import RVODubinsPolicy 8 | 9 | policy_dict = { 10 | # rule 11 | 'rvo': RVOPolicy, 12 | 'pca': PCAPolicy, 13 | 'drvo': DRVOPolicy, 14 | 'orca': ORCAPolicy, 15 | 'rvo_dubins': RVODubinsPolicy, 16 | } 17 | -------------------------------------------------------------------------------- /mamp/envs/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | gym_config_path = os.environ.get('GYM_CONFIG_PATH', os.path.join(os.path.dirname(os.path.realpath(__file__)), 'config.py')) 3 | gym_config_class = os.environ.get('GYM_CONFIG_CLASS', 'Config') 4 | 5 | import importlib.util 6 | spec = importlib.util.spec_from_file_location(gym_config_class, gym_config_path) 7 | foo = importlib.util.module_from_spec(spec) 8 | spec.loader.exec_module(foo) 9 | 10 | config_class = getattr(foo, gym_config_class, None) 11 | assert(callable(config_class)) 12 | Config = config_class() -------------------------------------------------------------------------------- /mamp/agents/obstacle.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import math 3 | 4 | 5 | class Obstacle(object): 6 | def __init__(self, pos, shape_dict, id): 7 | self.shape = shape = shape_dict['shape'] 8 | self.feature = feature = shape_dict['feature'] 9 | if shape == 'rect': 10 | self.width, self.heigh = shape_dict['feature'] 11 | self.radius = math.sqrt(self.width ** 2 + self.heigh ** 2) / 2 12 | elif shape == 'circle': 13 | self.radius = shape_dict['feature'] 14 | else: 15 | raise NotImplementedError 16 | 17 | self.pos_global_frame = np.array(pos, dtype='float64') 18 | self.vel_global_frame = np.array([0.0, 0.0, 0.0]) 19 | self.pos = pos 20 | self.is_obstacle = True 21 | self.id = id 22 | self.t = 0.0 23 | self.step_num = 0 24 | self.is_at_goal = True 25 | self.was_in_collision_already = False 26 | self.in_collision = False 27 | 28 | self.x = pos[0] 29 | self.y = pos[1] 30 | self.r = self.radius 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Gang Xu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /mamp/sensors/Sensor.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from mamp.envs import Config 3 | 4 | class Sensor(object): 5 | def __init__(self): 6 | self.name = 'empty_obs' 7 | 8 | def sense(self, agents, agent_index): 9 | """ Dummy method to be re-implemented by each Sensor subclass 10 | """ 11 | host_agent = agents[agent_index] 12 | other_agents_states = np.zeros((Config.MAX_NUM_OTHER_AGENTS_OBSERVED, Config.OTHER_AGENT_OBSERVATION_LENGTH)) 13 | 14 | other_agent_count = 0 15 | for other_agent in agents: 16 | if other_agent.id == host_agent.id: continue 17 | # rel_pos_to_other_global_frame = other_agent.pos_global_frame - host_agent.pos_global_frame 18 | # dist_2_other = np.linalg.norm(rel_pos_to_other_global_frame) - host_agent.radius - other_agent.radius 19 | # if dist_2_other > host_agent.view_radius: continue 20 | 21 | other_agent_count += 1 22 | 23 | host_agent.num_other_agents_observed = other_agent_count 24 | return other_agents_states 25 | 26 | def set_args(self, args): 27 | """ Update several class attributes (in dict format) of the Sensor object 28 | 29 | Args: 30 | args (dict): {'arg_name1': new_value1, ...} sets :code:`self.arg_name1 = new_value1`, etc. 31 | 32 | """ 33 | # Supply a dict of arg key value pairs 34 | for arg, value in args.items(): 35 | # print("Setting self.{} to {}".format(arg, value)) 36 | setattr(self, arg, value) 37 | -------------------------------------------------------------------------------- /mamp/policies/policy.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from mamp.util import wrap 3 | 4 | 5 | class Policy(object): 6 | """ Each :class:`~gym_collision_avoidance.envs.agent.Agent` has one of these, which nominally converts an observation to an action 7 | 8 | :param is_still_learning: (bool) whether this policy is still being learned (i.e., weights are changing during execution) 9 | :param is_external: (bool) whether the Policy computes its own actions or relies on an external process to provide an action. 10 | 11 | """ 12 | 13 | def __init__(self, str="NoPolicy"): 14 | self.str = str 15 | self.is_still_learning = False 16 | self.is_external = False 17 | 18 | def near_goal_smoother(self, dist_to_goal, pref_speed, heading, raw_action): 19 | """ Linearly ramp down speed/turning if agent is near goal, stop if close enough. 20 | 21 | I think this is just here for convenience, but nobody uses it? We used it on the jackal for sure. 22 | """ 23 | 24 | kp_v = 0.5 25 | kp_r = 1 26 | 27 | if dist_to_goal < 2.0: 28 | near_goal_action = np.empty((2, 1)) 29 | pref_speed = max(min(kp_v * (dist_to_goal - 0.1), pref_speed), 0.0) 30 | near_goal_action[0] = min(raw_action[0], pref_speed) 31 | turn_amount = max(min(kp_r * (dist_to_goal - 0.1), 1.0), 0.0) * raw_action[1] 32 | near_goal_action[1] = wrap(turn_amount + heading) 33 | if dist_to_goal < 0.3: 34 | near_goal_action = np.array([0., 0.]) 35 | else: 36 | near_goal_action = raw_action 37 | 38 | return near_goal_action 39 | -------------------------------------------------------------------------------- /mamp/dynamics/Dynamics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import math 3 | from mamp.util import wrap 4 | 5 | 6 | class Dynamics(object): 7 | """ Class to convert an agent's action to change in state 8 | 9 | There is a fundamental issue in passing the agent to the Dynamics. There should be a State object or something like that. 10 | 11 | :param agent: (:class:`~gym_collision_avoidance.envs.agent.Agent`) the Agent whose state should be updated 12 | 13 | """ 14 | 15 | def __init__(self, agent): 16 | self.agent = agent 17 | pass 18 | 19 | def step(self, action, dt): 20 | """ Dummy method to be implemented by each Dynamics subclass 21 | """ 22 | raise NotImplementedError 23 | 24 | def update_ego_frame(self): 25 | """ Update agent's heading and velocity by converting those values from the global to ego frame. 26 | 27 | This should be run every time :code:`step` is called (add to :code:`step`?) 28 | 29 | """ 30 | # Compute heading w.r.t. ref_prll, ref_orthog coordinate axes 31 | self.agent.ref_prll, self.agent.ref_orth = self.agent.get_ref() 32 | ref_prll_angle_global_frame = np.arctan2(self.agent.ref_prll[1], self.agent.ref_prll[0]) 33 | if self.agent.heading_global_frame is not None: 34 | self.agent.heading_ego_frame = wrap(self.agent.heading_global_frame - ref_prll_angle_global_frame) 35 | 36 | # Compute velocity w.r.t. ref_prll, ref_orthog coordinate axes 37 | cur_speed = math.sqrt(self.agent.vel_global_frame[0]**2 + self.agent.vel_global_frame[1]**2) # much faster than np.linalg.norm 38 | v_prll = cur_speed * np.cos(self.agent.heading_ego_frame) 39 | v_orthog = cur_speed * np.sin(self.agent.heading_ego_frame) 40 | self.agent.vel_ego_frame = np.array([v_prll, v_orthog]) 41 | -------------------------------------------------------------------------------- /mamp/dynamics/FullDynamics.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | from mamp.util import wrap 4 | from mamp.dynamics.Dynamics import Dynamics 5 | 6 | 7 | class FullDynamics(Dynamics): 8 | """ Convert a speed & heading to a new state according to Unicycle Kinematics model. 9 | 10 | """ 11 | 12 | def __init__(self, agent): 13 | Dynamics.__init__(self, agent) 14 | self.action_type = "XY" 15 | 16 | def step(self, action, dt): 17 | """ 18 | 19 | In the global frame, assume the agent instantaneously turns by :code:`heading` 20 | and moves forward at :code:`speed` for :code:`dt` seconds. 21 | Add that offset to the current position. Update the velocity in the 22 | same way. Also update the agent's turning direction (only used by CADRL). 23 | 24 | Args: 25 | action (list): [vx, vy] command for this agent 26 | dt (float): time in seconds to execute :code:`action` 27 | 28 | """ 29 | dx = action[0] * dt 30 | dy = action[1] * dt 31 | self.agent.pos_global_frame += np.array([dx, dy]) 32 | 33 | selected_speed = math.sqrt(dx ** 2 + dy ** 2) 34 | selected_heading = np.arctan2(dy, dx) 35 | 36 | self.agent.vel_global_frame[0] = action[0] 37 | self.agent.vel_global_frame[1] = action[1] 38 | 39 | self.agent.speed_global_frame = selected_speed 40 | self.agent.delta_heading_global_frame = wrap(selected_heading - self.agent.heading_global_frame) 41 | self.agent.heading_global_frame = selected_heading 42 | 43 | # turning dir: needed for cadrl value fn 44 | if abs(self.agent.turning_dir) < 1e-5: 45 | self.agent.turning_dir = 0.11 * np.sign(selected_heading) 46 | elif self.agent.turning_dir * selected_heading < 0: 47 | self.agent.turning_dir = max(-np.pi, min(np.pi, -self.agent.turning_dir + selected_heading)) 48 | else: 49 | self.agent.turning_dir = np.sign(self.agent.turning_dir) * max(0.0, abs(self.agent.turning_dir) - 0.1) 50 | -------------------------------------------------------------------------------- /mamp/envs/gazeboEnv.py: -------------------------------------------------------------------------------- 1 | import rospy 2 | import time 3 | import threading 4 | import numpy as np 5 | 6 | from mamp.policies.kdTree import KDTree 7 | 8 | from std_srvs.srv import Empty 9 | from gazebo_msgs.msg import ModelState 10 | from gazebo_msgs.srv import SetModelState 11 | 12 | from .macaenv import MACAEnv 13 | from .rosport.bot import AgentPort 14 | 15 | 16 | def thread_job(): 17 | rospy.spin() 18 | 19 | 20 | class GazeboEnv(MACAEnv): 21 | def __init__(self): 22 | self.env_name = 'gazebo' 23 | super(GazeboEnv, self).__init__() 24 | self.node = rospy.init_node('macaenv_node', anonymous=True) 25 | self.reset_world = rospy.ServiceProxy('/gazebo/reset_world', Empty) 26 | self.set_state = rospy.ServiceProxy('/gazebo/set_model_state', SetModelState) 27 | self.rate = rospy.Rate(1 / self.dt_nominal) 28 | add_thread = threading.Thread(target=thread_job) 29 | add_thread.start() 30 | 31 | def set_start(self, model_name, pos, heading): 32 | x, y = pos[0], pos[1] 33 | state = ModelState() 34 | state.model_name = model_name 35 | state.reference_frame = 'world' # ''ground_plane' 36 | # pose 37 | state.pose.position.x = x 38 | state.pose.position.y = y 39 | state.pose.position.z = 0 40 | # quaternion = tf.transformations.quaternion_from_euler(0, 0, 0) 41 | state.pose.orientation.x = 0 42 | state.pose.orientation.y = 0 43 | state.pose.orientation.z = np.sin(heading/2) 44 | state.pose.orientation.w = np.cos(heading/2) 45 | # twist 46 | state.twist.linear.x = 0 47 | state.twist.linear.y = 0 48 | state.twist.linear.z = 0 49 | state.twist.angular.x = 0 50 | state.twist.angular.y = 0 51 | state.twist.angular.z = 0 52 | 53 | rospy.wait_for_service('/gazebo/set_model_state') 54 | try: 55 | set_state = self.set_state 56 | result = set_state(state) 57 | assert result.success is True 58 | except rospy.ServiceException: 59 | print("/gazebo/get_model_state service call failed") 60 | 61 | def reset(self): 62 | super().reset() 63 | self.reset_world() 64 | # 将Agent和Obj进行绑定 65 | for agent in self.agents: 66 | agent.obj = AgentPort(agent.id, agent.name) 67 | self.set_start('Agent'+str(agent.id+1), agent.pos_global_frame, agent.heading_global_frame) 68 | agent.obj.stop_moving() 69 | time.sleep(0.5) 70 | for agent in self.agents: 71 | agent.update_from_obj() # update agent's dynamic state 72 | 73 | def _take_action(self, actions): 74 | super()._take_action(actions) 75 | self.rate.sleep() 76 | 77 | def step(self, actions): 78 | next_observations, rewards, game_over, info = super().step(actions) 79 | if game_over: self._stop_robots() 80 | return next_observations, rewards, game_over, info 81 | 82 | def _stop_robots(self): 83 | for agent in self.agents: 84 | agent.obj.stop_moving() 85 | -------------------------------------------------------------------------------- /draw/animate.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import glob 4 | import imageio 5 | import numpy as np 6 | import pandas as pd 7 | 8 | from plt2d import plot_episode 9 | 10 | case_name = 'pca' # circle_case rand_case 11 | # safety_weight = 0 12 | # forname = 2 * safety_weight 13 | 14 | abs_path = os.path.abspath('.') 15 | plot_save_dir = abs_path + '/' + case_name + '/' 16 | log_save_dir = abs_path + '/' + case_name + '/' 17 | 18 | 19 | def get_agent_traj(info_file, traj_file): 20 | with open(info_file, "r") as f: 21 | json_info = json.load(f) 22 | 23 | traj_list = [] 24 | step_num_list = [] 25 | agents_info = json_info['all_agent_info'] 26 | obstacles_info = json_info['all_obstacle'] 27 | for agent_info in agents_info: 28 | agent_id = agent_info['id'] 29 | agent_gp = agent_info['gp'] 30 | agent_rd = agent_info['radius'] 31 | 32 | df = pd.read_excel(traj_file, index_col=0, sheet_name='agent' + str(agent_id)) 33 | step_num_list.append(df.shape[0]) 34 | traj_list.append(df.to_dict('list')) 35 | # for indexs in df.index: 36 | # traj_info.append(df.loc[indexs].values[:].tolist()) 37 | # traj_info = np.array(traj_info) 38 | 39 | return agents_info, traj_list, step_num_list, obstacles_info 40 | 41 | 42 | def png_to_gif(general_fig_name, last_fig_name, animation_filename, video_filename): 43 | all_filenames = plot_save_dir + general_fig_name 44 | last_filename = plot_save_dir + last_fig_name 45 | print(all_filenames) 46 | 47 | # Dump all those images into a gif (sorted by timestep) 48 | filenames = glob.glob(all_filenames) 49 | filenames.sort() 50 | images = [] 51 | for filename in filenames: 52 | images.append(imageio.imread(filename)) 53 | os.remove(filename) 54 | for i in range(10): 55 | images.append(imageio.imread(last_filename)) 56 | 57 | # Save the gif in a new animations sub-folder 58 | animation_save_dir = plot_save_dir + "animations/" 59 | os.makedirs(animation_save_dir, exist_ok=True) 60 | video_filename = animation_save_dir + video_filename 61 | animation_filename = animation_save_dir + animation_filename 62 | imageio.mimsave(animation_filename, images, ) 63 | 64 | # convert .gif to .mp4 65 | try: 66 | import moviepy.editor as mp 67 | except imageio.core.fetching.NeedDownloadError: 68 | imageio.plugins.ffmpeg.download() 69 | import moviepy.editor as mp 70 | clip = mp.VideoFileClip(animation_filename) 71 | clip.write_videofile(video_filename) 72 | 73 | 74 | info_file = log_save_dir + 'log/env_cfg.json' 75 | traj_file = log_save_dir + 'log/trajs.xlsx' 76 | 77 | agents_info, traj_list, step_num_list, obstacles_info = get_agent_traj(info_file, traj_file) 78 | 79 | agent_num = len(step_num_list) 80 | base_fig_name_style = "{test_case}_{policy}_{num_agents}agents" 81 | base_fig_name = base_fig_name_style.format(policy=case_name, num_agents=agent_num, test_case=str(0).zfill(3)) 82 | general_fig_name = base_fig_name + '_*.png' 83 | last_fig_name = base_fig_name + '.png' 84 | animation_filename = base_fig_name + '.gif' 85 | video_filename = base_fig_name + '.mp4' 86 | 87 | plot_episode(obstacles_info, agents_info, traj_list, step_num_list, plot_save_dir=plot_save_dir, 88 | base_fig_name=base_fig_name, last_fig_name=last_fig_name, show=False) 89 | 90 | png_to_gif(general_fig_name, last_fig_name, animation_filename, video_filename) 91 | -------------------------------------------------------------------------------- /mamp/dynamics/UnicycleDynamics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from mamp.util import wrap 3 | from mamp.dynamics.Dynamics import Dynamics 4 | 5 | 6 | class UnicycleDynamics(Dynamics): 7 | """ Convert a speed & heading to a new state according to Unicycle Kinematics model. 8 | 9 | """ 10 | 11 | def __init__(self, agent): 12 | Dynamics.__init__(self, agent) 13 | self.action_type = "R_THETA" 14 | 15 | def step(self, action, dt): 16 | """ 17 | 18 | In the global frame, assume the agent instantaneously turns by :code:`heading` 19 | and moves forward at :code:`speed` for :code:`dt` seconds. 20 | Add that offset to the current position. Update the velocity in the 21 | same way. Also update the agent's turning direction (only used by CADRL). 22 | 23 | Args: 24 | action (list): [delta heading angle, speed] command for this agent 25 | dt (float): time in seconds to execute :code:`action` 26 | 27 | """ 28 | selected_speed = action[0] 29 | selected_heading = wrap(action[1] + self.agent.heading_global_frame) 30 | 31 | dx = selected_speed * np.cos(selected_heading) * dt 32 | dy = selected_speed * np.sin(selected_heading) * dt 33 | self.agent.pos_global_frame += np.array([dx, dy]) 34 | self.agent.total_dist += np.sqrt(dx**2+dy**2) 35 | 36 | # 以下注释代码只是对环境范围做一下约束,当智能体在约束环境之外时,自动调为约束环境范围内 37 | # cmb_array = np.concatenate( 38 | # [np.array([[self.agent.max_x, self.agent.max_y]]), self.agent.pos_global_frame[np.newaxis, :]]) 39 | # self.agent.pos_global_frame = np.min(cmb_array, axis=0) 40 | # cmb_array = np.concatenate( 41 | # [np.array([[self.agent.min_x, self.agent.min_y]]), self.agent.pos_global_frame[np.newaxis, :]]) 42 | # self.agent.pos_global_frame = np.max(cmb_array, axis=0) 43 | 44 | self.agent.vel_global_frame[0] = round(selected_speed * np.cos(selected_heading), 5) 45 | self.agent.vel_global_frame[1] = round(selected_speed * np.sin(selected_heading), 5) 46 | self.agent.speed_global_frame = selected_speed 47 | self.agent.delta_heading_global_frame = wrap(selected_heading - self.agent.heading_global_frame) 48 | self.agent.heading_global_frame = selected_heading 49 | 50 | # turning dir: needed for cadrl value fn 51 | if abs(self.agent.turning_dir) < 1e-5: 52 | self.agent.turning_dir = 0.11 * np.sign(selected_heading) 53 | elif self.agent.turning_dir * selected_heading < 0: 54 | self.agent.turning_dir = max(-np.pi, min(np.pi, -self.agent.turning_dir + selected_heading)) 55 | else: 56 | self.agent.turning_dir = np.sign(self.agent.turning_dir) * max(0.0, abs(self.agent.turning_dir) - 0.1) 57 | 58 | def update_no_step(self, action): 59 | if action is None: return 60 | selected_speed = action[0] 61 | # selected_heading = wrap(action[1] + self.agent.heading_global_frame) 62 | selected_heading = self.agent.heading_global_frame 63 | 64 | self.agent.vel_global_frame[0] = selected_speed * np.cos(selected_heading) 65 | self.agent.vel_global_frame[1] = selected_speed * np.sin(selected_heading) 66 | self.agent.speed_global_frame = selected_speed 67 | # self.agent.delta_heading_global_frame = wrap(selected_heading - self.agent.heading_global_frame) 68 | # self.agent.heading_global_frame = selected_heading 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Multi-Vehicle Motion Planning with Posture Constraints in Real World 2 | Python Implementation of multi-vehicle motion planning, including our proposed method, namely postured collision avoidance (PCA), the reciprocal volocity obstacles (RVO), and the optimal reciprocal collision avoidance (ORCA, also namely RVO2) . 3 | 4 | ----- 5 | 6 | Description 7 | ----- 8 | 9 | This paper investigates more practical applications in the real world for non-holonomic unmanned ground vehicles. In this case, a strategy of diversion is designed to optimize the smoothness of motion. Considering the problem of the posture constraints, a postured collision avoidance (PCA) algorithm is proposed for the motion planning of the multiple non-holonomic unmanned ground vehicles. In addition, we provide the optimal reciprocal collision avoidance (ORCA) method and reciprocal velocity obstacles (RVO) method for comparison. 10 | 11 | About 12 | ----- 13 | 14 | **Paper**: Multi-Vehicle Motion Planning with Posture Constraints in Real World, Gang Xu, Yansong Chen, Junjie Cao\*, Deye Zhu, Weiwei Liu, and Yong Liu\*, Accepted as TMECH/AIM Focused Section Paper in IEEE-ASME Transactions on Mechatronics (**TMECH**). 15 | 16 | 17 | ----- 18 | 19 | Requirement 20 | ----- 21 | 22 | ```python 23 | pip install numpy 24 | pip install pandas 25 | pip install matplotlib 26 | ``` 27 | 28 | ----- 29 | 30 | Applications 31 | ----- 32 | 33 | ```python 34 | cd run_py 35 | python run_pca.py 36 | For the test, you can select the scenarios, including circle, random. 37 | ``` 38 | 39 | #### Simulation 1: 40 | 41 |

42 | 43 |

44 | 45 | 46 | 47 | 48 | #### Experimental Results (Ours: DRVO, PCA): 49 | 50 |

51 | 52 |

53 |

54 | 55 |

56 |

57 | 58 |

59 |

60 | 61 |

62 | 63 | #### Large Scale Results (Ours: DRVO, PCA): 64 | 65 |

66 | 67 |

68 |

69 | 70 |

71 | 72 | 73 | ---- 74 | 75 | References 76 | ---- 77 | 78 | * Papers on [RVO](https://www.cs.unc.edu/~geom/RVO/icra2008.pdf), [ORCA](http://gamma-web.iacs.umd.edu/ORCA/publications/ORCA.pdf). 79 | 80 | 81 | ---- 82 | 83 | Discussion 84 | ---- 85 | 86 | In the first simulation, the preferred speed is 0.22 m/s. In the compared experiments, the preferred speed is 1.0 m/s. All agents' radius is 0.2 m. The radius of circle benchmark is set as 15 m for DRVO, ORCA and RVO. The square of random benchmark is set as 30 m * 30 m for DRVO, ORCA and RVO. The radius of circle benchmark is set as 40 m for PCA and RVO + Dubins. The square of random benchmark is set as 80 m * 80 m for PCA and RVO + Dubins. 87 | 88 | In the large scale, the radius of circle benchmark is set as 20 m for DRVO, ORCA and RVO, the square of random benchmark is set as 60 m * 60 m for DRVO, ORCA and RVO, the radius of circle benchmark is set as 50 m for PCA and RVO + Dubins, and the square of random benchmark is set as 100 m * 100 m for PCA and RVO + Dubins. 89 | -------------------------------------------------------------------------------- /mamp/envs/rosport/car.py: -------------------------------------------------------------------------------- 1 | #!/usr/env/bin python 2 | import numpy as np 3 | import math 4 | import rospy 5 | from nav_msgs.msg import Odometry 6 | from mrobot.msg import EnvInfo 7 | from geometry_msgs.msg import Twist 8 | 9 | 10 | class RosCarPort(object): 11 | 12 | """ 13 | 循迹机器人ros接口类 14 | """ 15 | 16 | def __init__(self, agent): 17 | self.id = agent.id 18 | self.name = agent.name 19 | self.group = agent.group # 0 表示 leader 1 表示 follower 20 | self.radianR = None 21 | self.radianP = None 22 | self.radianY = None 23 | self.angleR = None 24 | self.angleP = None 25 | self.angleY = None 26 | 27 | self.str_pub_twist = '/' + self.name + '/cmd_vel' 28 | self.pub_twist = rospy.Publisher(self.str_pub_twist,Twist,queue_size=1) 29 | self.str_odom = '/' + self.name + '/odom' 30 | self.sub_odom = rospy.Subscriber(self.str_odom, Odometry, self.pos_callback) 31 | self.agent = agent 32 | if self.group == 0: 33 | self.detect_sub = rospy.Subscriber("env_feedback", EnvInfo, \ 34 | self.error_callback) 35 | 36 | def pos_callback(self, data): 37 | """require pos info""" 38 | 39 | x = data.pose.pose.position.x 40 | y = data.pose.pose.position.y 41 | dist = (x-self.agent.pose_global_frame[0])**2 + \ 42 | (y-self.agent.pose_global_frame[1])**2 43 | self.agent.distance += math.sqrt(dist) 44 | 45 | self.agent.pose_list.append([x, y]) 46 | 47 | self.agent.v_x = data.twist.twist.linear.x 48 | self.agent.w_z = data.twist.twist.angular.z 49 | self.agent.pose_global_frame = np.array([x,y]) 50 | 51 | """ 52 | require angle info 53 | """ 54 | 55 | qx = data.pose.pose.orientation.x 56 | qy = data.pose.pose.orientation.y 57 | qz = data.pose.pose.orientation.z 58 | qw = data.pose.pose.orientation.w 59 | 60 | self.radianR = math.atan2(2 * (qw * qx + qy * qz), 1 - 2 * (qx * qx + qy * qy)) 61 | self.radianP = math.asin(2 * (qw * qy - qz * qx)) 62 | self.radianY = math.atan2(2 * (qw * qz + qx * qy), 1 - 2 * (qz * qz + qy * qy)) 63 | 64 | self.angleR = self.radianR * 180 / math.pi # 横滚角 65 | self.angleP = self.radianP * 180 / math.pi # 俯仰角 66 | self.angleY = self.radianY * 180 / math.pi # 偏航角 67 | 68 | self.agent.heading_global_frame = self.radianY 69 | 70 | self.agent.vel_global_frame = [math.cos(self.radianY)*self.agent.v_x, 71 | math.sin(self.radianY)* self.agent.v_x] 72 | 73 | 74 | def error_callback(self, data): 75 | """require error info""" 76 | 77 | e_x = data.error_x 78 | e_k = data.error_k 79 | self.agent.cx = data.cx 80 | self.agent.cy = data.cy 81 | self.agent.failed_flag = data.failed_flag 82 | self.agent.error.pop() 83 | self.agent.error.insert(0, e_x) 84 | self.agent.error_k.pop() 85 | self.agent.error_k.insert(0, e_k) 86 | self.agent.errorx_list.append(self.agent.error[0]) 87 | self.agent.last_error = self.agent.error[0] 88 | 89 | def pubTwist(self, action, dt): 90 | twist = Twist() 91 | if self.group == 0 : 92 | twist.linear.x = action[0] 93 | twist.angular.z = action[1] 94 | else: 95 | twist.linear.x = action[0] 96 | twist.angular.z = action[1] / dt 97 | twist.angular.z = np.clip(twist.angular.z, -np.pi/2, np.pi/2) 98 | 99 | self.pub_twist.publish(twist) 100 | 101 | def stop_moving(self): 102 | twist = Twist() 103 | self.pub_twist.publish(twist) 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /draw/vis_util.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from math import sin, cos, atan2, sqrt, pow 4 | 5 | plt_colors = [[0.8500, 0.3250, 0.0980], [0.0, 0.4470, 0.7410], [0.4660, 0.8740, 0.1880], 6 | [0.4940, 0.1840, 0.5560], 7 | [0.9290, 0.6940, 0.1250], [0.3010, 0.7450, 0.9330], [0.6350, 0.0780, 0.1840]] 8 | 9 | 10 | # plt_colors = ['red', 'green', 'blue', 'orange', 'yellow', 'purple'] 11 | 12 | 13 | def get_2d_car_model(size): 14 | verts = [ 15 | [-1. * size, 1. * size], # 矩形左下角的坐标(left,bottom) 16 | [-1. * size, -1. * size], # 矩形左上角的坐标(left,top) 17 | [1. * size, -1. * size], # 矩形右上角的坐标(right,top) 18 | [1. * size, 1. * size], # 矩形右下角的坐标(right, bottom) 19 | [-1. * size, 1. * size], # 封闭到起点 20 | ] 21 | return verts 22 | 23 | 24 | def get_2d_uav_model0(size): 25 | # size /= 2 26 | verts = [ 27 | [0., 1. * size], # 矩形左下角的坐标(left, bottom) 28 | [-0.5 * size, -0.8 * size], # 矩形左上角的坐标(left, top) 29 | [0, -0.2 * size], # 矩形右上角的坐标(right,top) 30 | [0.5 * size, -0.8 * size], # 矩形右下角的坐标(right, bottom) 31 | [0., 1. * size], # 封闭到起点 32 | ] 33 | return verts 34 | 35 | 36 | def get_2d_uav_model(size): 37 | # size /= 2 38 | verts = [ 39 | [0., 1. * size], # 矩形左下角的坐标(left, bottom) 40 | [-1. * size, -1. * size], # 矩形左上角的坐标(left, top) 41 | [0., -0.5 * size], # 矩形右上角的坐标(right,top) 42 | [1. * size, -1. * size], # 矩形右下角的坐标(right, bottom) 43 | [0., 1. * size], # 封闭到起点 44 | ] 45 | return verts 46 | 47 | 48 | def rgba2rgb(rgba): 49 | # rgba is a list of 4 color elements btwn [0.0, 1.0] 50 | # or a 2d np array (num_colors, 4) 51 | # returns a list of rgb values between [0.0, 1.0] accounting for alpha and background color [1, 1, 1] == WHITE 52 | if isinstance(rgba, list): 53 | alpha = rgba[3] 54 | r = max(min((1 - alpha) * 1.0 + alpha * rgba[0], 1.0), 0.0) 55 | g = max(min((1 - alpha) * 1.0 + alpha * rgba[1], 1.0), 0.0) 56 | b = max(min((1 - alpha) * 1.0 + alpha * rgba[2], 1.0), 0.0) 57 | return [r, g, b] 58 | elif rgba.ndim == 2: 59 | alphas = rgba[:, 3] 60 | r = np.clip((1 - alphas) * 1.0 + alphas * rgba[:, 0], 0, 1) 61 | g = np.clip((1 - alphas) * 1.0 + alphas * rgba[:, 1], 0, 1) 62 | b = np.clip((1 - alphas) * 1.0 + alphas * rgba[:, 2], 0, 1) 63 | return np.vstack([r, g, b]).T 64 | 65 | 66 | def draw_env(ax, buildings_obj_list, keypoints_obj_list, connection_matrix): 67 | pass 68 | 69 | 70 | def draw(origin_pos, objects_list, buildings_obj_list, keypoints_obj_list, connection_matrix): 71 | fig = plt.figure() 72 | plt.xlabel('x (m)') 73 | plt.ylabel('y (m)') 74 | plt.xlim([-500.0, 3500.0]) 75 | plt.ylim([-1000.0, 1000.0]) 76 | ax = fig.add_subplot(1, 1, 1) 77 | 78 | ax.set_aspect('equal') 79 | 80 | draw_objects(ax, objects_list) 81 | draw_buildings(ax, origin_pos, buildings_obj_list) 82 | draw_roads(ax, keypoints_obj_list, connection_matrix) 83 | plt.show() 84 | 85 | 86 | def draw_buildings(ax, origin_pos, buildings_obj): 87 | for building_obj in buildings_obj: 88 | id = building_obj.id 89 | pos = building_obj.pos 90 | draw_rectangle(ax, origin_pos, pos) 91 | 92 | 93 | def draw_roads(ax, keypoints_obj_list, connection_matrix): 94 | for index_i, info in enumerate(connection_matrix): 95 | self_pos = keypoints_obj_list[index_i].pos 96 | for index_j, distance in enumerate(info): 97 | if distance > 0: 98 | target_pos = keypoints_obj_list[index_j].pos 99 | x = [self_pos[0], target_pos[0]] 100 | y = [self_pos[1], target_pos[1]] 101 | plt.plot(x, y, color='r') 102 | plt.scatter(x, y, color='b') 103 | 104 | 105 | def draw_objects(ax, objects_obj): 106 | for object_obj in objects_obj: 107 | pass 108 | 109 | 110 | def draw_rectangle(ax, origin_pos, pos): 111 | pos_x = pos[0] 112 | pos_y = pos[1] 113 | ax.add_patch( 114 | plt.Rectangle( 115 | (pos_x - 5, pos_y - 5), # (x,y)矩形左下角 116 | 10, # width长 117 | 10, # height宽 118 | color='maroon', 119 | alpha=0.5 120 | )) 121 | 122 | 123 | def convert_to_actual_model_3d(agent_model, pos_global_frame, heading_global_frame): 124 | alpha = heading_global_frame[0] 125 | beta = heading_global_frame[1] 126 | gamma = heading_global_frame[2] 127 | for point in agent_model: 128 | x = point[0] 129 | y = point[1] 130 | z = point[2] 131 | # 进行俯仰计算 132 | r = sqrt(pow(y, 2) + pow(z, 2)) 133 | beta_model = atan2(z, y) 134 | beta_ = beta + beta_model 135 | y = r * cos(beta_) 136 | z = r * sin(beta_) 137 | # 进行翻滚计算 138 | h = sqrt(pow(x, 2) + pow(z, 2)) 139 | gama_model = atan2(z, x) 140 | gamma_ = gamma + gama_model 141 | x = h * cos(gamma_) 142 | z = h * sin(gamma_) 143 | # 进行航向计算 144 | l = sqrt(pow(x, 2) + pow(y, 2)) 145 | alpha_model = atan2(y, x) 146 | alpha_ = alpha + alpha_model - np.pi / 2 # 改加 - np.pi / 2 因为画模型的时候UAV朝向就是正北方向,所以要减去90° 147 | point[0] = l * cos(alpha_) + pos_global_frame[0] 148 | point[1] = l * sin(alpha_) + pos_global_frame[1] 149 | point[2] = z + pos_global_frame[2] 150 | -------------------------------------------------------------------------------- /mamp/policies/rvoPolicy.py: -------------------------------------------------------------------------------- 1 | import time 2 | import numpy as np 3 | from math import sqrt, sin, cos, atan2, asin, pi, floor, acos, asin 4 | from itertools import product 5 | 6 | from mamp.envs import Config 7 | from mamp.util import sqr, l2norm, norm, normalize, absSq, mod2pi, pi_2_pi, eps, angle_2_vectors, is_intersect 8 | from mamp.policies.policy import Policy 9 | 10 | 11 | class RVOPolicy(Policy): 12 | def __init__(self): 13 | Policy.__init__(self, str="RVOPolicy") 14 | self.type = "internal" 15 | self.now_goal = None 16 | 17 | def find_next_action(self, obs, dict_comm, agent, kdTree): 18 | ts = time.time() 19 | self.now_goal = agent.goal_global_frame 20 | v_pref = compute_v_pref(agent) 21 | vA = agent.vel_global_frame 22 | pA = agent.pos_global_frame 23 | agent_rad = agent.radius + 0.05 24 | computeNeighbors(agent, kdTree) 25 | RVO_BA_all = [] 26 | for obj in agent.neighbors: 27 | obj = obj[0] 28 | pB = obj.pos_global_frame 29 | if obj.is_at_goal: 30 | transl_vB_vA = pA 31 | else: 32 | vB = obj.vel_global_frame 33 | transl_vB_vA = pA + 0.5 * (vB + vA) # Use RVO. 34 | obj_rad = obj.radius + 0.05 35 | 36 | RVO_BA = [transl_vB_vA, pA, pB, obj_rad + agent_rad] 37 | RVO_BA_all.append(RVO_BA) 38 | vA_post = intersect(v_pref, RVO_BA_all, agent) 39 | te = time.time() 40 | cost_step = te - ts 41 | agent.total_time += cost_step 42 | action = to_unicycle(vA_post, agent) 43 | theta = angle_2_vectors(vA, vA_post) 44 | agent.vel_global_unicycle[0] = int(1.0 * action[0]*eps)/eps 45 | agent.vel_global_unicycle[1] = int((0.22 * 0.5 * action[1] / Config.DT)*eps)/eps 46 | dist = l2norm(agent.pos_global_frame, agent.goal_global_frame) 47 | if theta > agent.max_heading_change: 48 | print('agent' + str(agent.id), len(agent.neighbors), action, '终点距离:', dist, 'theta:', theta) 49 | else: 50 | print('agent' + str(agent.id), len(agent.neighbors), action, '终点距离:', dist) 51 | 52 | return action 53 | 54 | 55 | def compute_v_pref(agent): 56 | goal = agent.goal_global_frame 57 | diff = goal - agent.pos_global_frame 58 | v_pref = agent.pref_speed * normalize(diff) 59 | if l2norm(goal, agent.pos_global_frame) < Config.NEAR_GOAL_THRESHOLD: 60 | v_pref = np.zeros_like(v_pref) 61 | return v_pref 62 | 63 | 64 | def intersect(v_pref, RVO_BA_all, agent): 65 | norm_v = np.linalg.norm(v_pref) 66 | suitable_V = [] 67 | unsuitable_V = [] 68 | 69 | Theta = np.arange(0, 2 * pi, step=pi / 32) 70 | RAD = np.linspace(0.02, 1.0, num=5) 71 | v_list = [v_pref[:]] 72 | for theta, rad in product(Theta, RAD): 73 | new_v = np.array([cos(theta), sin(theta)]) * rad * norm_v 74 | new_v = np.array([new_v[0], new_v[1]]) 75 | v_list.append(new_v) 76 | 77 | for new_v in v_list: 78 | suit = True 79 | for RVO_BA in RVO_BA_all: 80 | p_0 = RVO_BA[0] 81 | pA = RVO_BA[1] 82 | pB = RVO_BA[2] 83 | combined_radius = RVO_BA[3] 84 | vAB = new_v + pA - p_0 85 | if is_intersect(pA, pB, combined_radius, vAB): 86 | suit = False 87 | break 88 | if suit: 89 | suitable_V.append(new_v) 90 | else: 91 | unsuitable_V.append(new_v) 92 | 93 | # ---------------------- 94 | if suitable_V: 95 | suitable_V.sort(key=lambda v: l2norm(v, v_pref)) # sort begin at minimum and end at maximum 96 | vA_post = suitable_V[0] 97 | else: 98 | tc_V = dict() 99 | for unsuit_v in unsuitable_V: 100 | tc_V[tuple(unsuit_v)] = 0 101 | tc = [] # 时间 102 | for RVO_BA in RVO_BA_all: 103 | p_0 = RVO_BA[0] 104 | pA = RVO_BA[1] 105 | pB = RVO_BA[2] 106 | combined_radius = RVO_BA[3] 107 | vAB = unsuit_v + pA - p_0 108 | pApB = pB - pA 109 | if is_intersect(pA, pB, combined_radius, vAB): 110 | discr = sqr(np.dot(vAB, pApB)) - absSq(vAB) * (absSq(pApB) - sqr(combined_radius)) 111 | if discr < 0.0: 112 | print(discr) 113 | continue 114 | tc_v = (np.dot(vAB, pApB) - sqrt(discr)) / absSq(vAB) 115 | if tc_v < 0: 116 | tc_v = 0.0 117 | tc.append(tc_v) 118 | if len(tc) == 0: 119 | tc = [0.0] 120 | tc_V[tuple(unsuit_v)] = min(tc) + 1e-5 121 | WT = agent.safetyFactor 122 | vA_post = min(unsuitable_V, key=lambda v: ((WT / tc_V[tuple(v)]) + l2norm(v, v_pref))) 123 | return vA_post 124 | 125 | 126 | def computeNeighbors(agent, kdTree): 127 | agent.neighbors.clear() 128 | 129 | # Check obstacle neighbors. 130 | rangeSq = agent.neighborDist ** 2 131 | if len(agent.neighbors) != agent.maxNeighbors: 132 | rangeSq = 1.0 * agent.neighborDist ** 2 133 | kdTree.computeObstacleNeighbors(agent, rangeSq) 134 | 135 | if agent.in_collision: 136 | return 137 | 138 | # Check other agents. 139 | if len(agent.neighbors) != agent.maxNeighbors: 140 | rangeSq = agent.neighborDist ** 2 141 | kdTree.computeAgentNeighbors(agent, rangeSq) 142 | 143 | 144 | def to_unicycle(vA_post, agent): 145 | vA_post = np.array(vA_post) 146 | norm_vA = norm(vA_post) 147 | yaw_next = mod2pi(atan2(vA_post[1], vA_post[0])) 148 | yaw_current = mod2pi(agent.heading_global_frame) 149 | delta_theta = yaw_next - yaw_current 150 | delta_theta = pi_2_pi(delta_theta) 151 | if delta_theta < -pi: 152 | delta_theta = delta_theta + 2 * pi 153 | if delta_theta > pi: 154 | delta_theta = delta_theta - 2 * pi 155 | if delta_theta >= 1.0: 156 | delta_theta = 1.0 157 | if delta_theta <= -1: 158 | delta_theta = -1.0 159 | action = np.array([norm_vA, delta_theta]) 160 | return action 161 | -------------------------------------------------------------------------------- /mamp/envs/rosport/bot.py: -------------------------------------------------------------------------------- 1 | #!/usr/env/bin python 2 | 3 | import math 4 | import rospy 5 | import numpy as np 6 | from nav_msgs.msg import Odometry, Path 7 | from geometry_msgs.msg import PoseStamped, Twist, Vector3, Point, PoseWithCovarianceStamped, Pose 8 | 9 | 10 | # from tf.transformations import euler_from_quaternion 11 | 12 | class AgentPort(object): 13 | def __init__(self, id, name): 14 | self.id = id 15 | self.name = name 16 | self.taken_action = None 17 | self.pos_global_frame = None 18 | self.radianR = None 19 | self.radianP = None 20 | self.radianY = None 21 | self.angleR = None 22 | self.angleP = None 23 | self.angleY = None 24 | self.goal_global_frame = None 25 | self.new_goal_received = False 26 | 27 | self.str_pub_twist = '/' + self.name + '/cmd_vel' 28 | self.pub_twist = rospy.Publisher(self.str_pub_twist, Twist, queue_size=1) 29 | 30 | self.str_pub_twist_real = '/' + 'robot0' + '/cmd_vel' 31 | self.pub_twist_real = rospy.Publisher(self.str_pub_twist_real, Twist, queue_size=1) 32 | 33 | self.str_pub_path = '/' + self.name + '/trajectory' 34 | self.pub_path = rospy.Publisher(self.str_pub_path, Path, queue_size=1) 35 | 36 | self.str_pose = '/' + self.name + '/pose' 37 | self.str_odom = '/' + self.name + '/odom' 38 | self.str_goal = '/' + self.name + '/move_base_simple/goal' 39 | self.str_robot_pose = '/' + self.name + '/' + self.name + '/robot_pose' 40 | self.sub_odom = rospy.Subscriber(self.str_odom, Odometry, self.cbOdom) 41 | # self.sub_robot_pose = rospy.Subscriber(self.str_robot_pose, Pose, self.cbPose) 42 | 43 | def cbOdom(self, msg): 44 | self.odom = msg 45 | self.pos_global_frame = np.array([msg.pose.pose.position.x, msg.pose.pose.position.y]) 46 | 47 | qx = msg.pose.pose.orientation.x 48 | qy = msg.pose.pose.orientation.y 49 | qz = msg.pose.pose.orientation.z 50 | qw = msg.pose.pose.orientation.w 51 | # (roll, pitch, yaw) = euler_from_quaternion([qx, qy, qz, qw]) # 将四元数转化为roll, pitch, yaw 52 | self.radianR = math.atan2(2 * (qw * qx + qy * qz), 1 - 2 * (qx * qx + qy * qy)) 53 | self.radianP = math.asin(2 * (qw * qy - qz * qx)) 54 | self.radianY = math.atan2(2 * (qw * qz + qx * qy), 1 - 2 * (qz * qz + qy * qy)) 55 | 56 | self.angleR = self.radianR * 180 / math.pi # 横滚角 57 | self.angleP = self.radianP * 180 / math.pi # 俯仰角 58 | self.angleY = self.radianY * 180 / math.pi # 偏航角 59 | 60 | self.v_x = msg.twist.twist.linear.x 61 | self.w_z = msg.twist.twist.angular.z 62 | 63 | self.vel_global_frame = np.array([math.cos(self.angleY), math.sin(self.angleY)]) * self.v_x 64 | 65 | def cbPose(self, msg): 66 | self.pos_global_frame = np.array([msg.position.x, msg.position.y]) 67 | 68 | def stop_moving(self): 69 | twist = Twist() 70 | self.pub_twist.publish(twist) 71 | 72 | def pubTwist(self, action, dt, agent_id): 73 | self.taken_action = action 74 | twist = Twist() 75 | pose = Path() 76 | twist.linear.x = round(1.0 * action[0], 5) 77 | twist.angular.z = round(0.22 * 0.5 * action[1] / dt, 5) 78 | 79 | # test 80 | # twist.linear.x = round(0.2, 5) 81 | # twist.angular.z = 0.0 82 | # twist.angular.z = round(0.22 * 0.5 * action[1] / dt, 5) 83 | self.pub_twist.publish(twist) 84 | 85 | self.pub_path.publish(pose) 86 | 87 | # self.pub_twist_real.publish(twist) 88 | 89 | # RVO Para 90 | # if agent_id == 0: # Agent1 91 | # twist.linear.x = round(1.1 * action[0], 5) 92 | # twist.angular.z = round(0.22 * 0.5 * action[1] / dt, 5) 93 | # self.pub_twist_real.publish(twist) 94 | # if action[1] > 0: 95 | # twist.angular.z = round(1.5*0.18 * 0.5 * action[1] / dt, 5) 96 | # else: 97 | # twist.angular.z = round(1.5*0.11 * 0.5 * action[1] / dt, 5) 98 | # elif agent_id == 1: # Agent2 99 | # if action[1] > 0: 100 | # twist.angular.z = round(1.5*0.13 * 0.5 * action[1] / dt, 5) 101 | # else: 102 | # twist.angular.z = round(1.5*0.13 * 0.5 * action[1] / dt, 5) 103 | # elif agent_id == 2: # Agent3 104 | # if action[1] > 0: 105 | # twist.angular.z = round(1.6*0.10 * 0.5 * action[1] / dt, 5) 106 | # else: 107 | # twist.angular.z = round(1.6*0.15 * 0.5 * action[1] / dt, 5) 108 | # elif agent_id == 3: # Agent4 109 | # if action[1] > 0: 110 | # twist.angular.z = round(1.5*0.16 * 0.5 * action[1] / dt, 5) 111 | # else: 112 | # twist.angular.z = round(1.5*0.12 * 0.5 * action[1] / dt, 5) 113 | 114 | # SCA or Dubins Para 115 | # if agent_id == 0: # Agent1 116 | # if action[1] > 0: 117 | # twist.angular.z = round(1.5*0.12 * 0.5 * action[1] / dt, 5) 118 | # else: 119 | # twist.angular.z = round(1.5*0.12 * 0.5 * action[1] / dt, 5) 120 | # elif agent_id == 1: # Agent2 121 | # if action[1] > 0: 122 | # twist.angular.z = round(1.6*0.13 * 0.5 * action[1] / dt, 5) 123 | # else: 124 | # twist.angular.z = round(1.6*0.13 * 0.5 * action[1] / dt, 5) 125 | # elif agent_id == 2: # Agent3 126 | # if action[1] > 0: 127 | # twist.angular.z = round(1.5*0.10 * 0.5 * action[1] / dt, 5) 128 | # else: 129 | # twist.angular.z = round(1.5*0.15 * 0.5 * action[1] / dt, 5) 130 | # elif agent_id == 3: # Agent4 131 | # if action[1] > 0: 132 | # twist.angular.z = round(1.5*0.16 * 0.5 * action[1] / dt, 5) 133 | # else: 134 | # twist.angular.z = round(1.5*0.12 * 0.5 * action[1] / dt, 5) 135 | # self.pub_twist_real.publish(twist) 136 | 137 | # def cbRobotPose1(self, msg): 138 | # self.obstacle_list[0] = Obstacle(pos = np.array([msg.pose.position.x, msg.pose.position.y]), shape_dict = { 'shape' : "circle", 'feature' : 0.2}) 139 | 140 | # def cbRobotPose2(self, msg): 141 | # self.obstacle_list[1] = Obstacle(pos = np.array([msg.pose.position.x, msg.pose.position.y]), shape_dict = { 'shape' : "circle", 'feature' : 0.2}) 142 | 143 | def getRobotPose(self): 144 | return self.pos_global_frame 145 | 146 | def getEulerRadian(self): 147 | return self.radianR, self.radianP, self.radianY 148 | 149 | def getGoalPose(self): 150 | return self.goal_global_frame 151 | -------------------------------------------------------------------------------- /runpy/run_drvo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import json 5 | import numpy as np 6 | import pandas as pd 7 | 8 | sys.path.append('/home/wuuya/PycharmProjects/maros') 9 | sys.path.append('/opt/ros/kinetic/lib/python2.7/dist-packages') 10 | import os 11 | import random 12 | 13 | os.environ['GYM_CONFIG_CLASS'] = 'Example' 14 | os.environ['GYM_CONFIG_PATH'] = '../mamp/configs/config.py' 15 | import gym 16 | 17 | gym.logger.set_level(40) 18 | 19 | from mamp.agents.agent import Agent 20 | # Policies 21 | from mamp.policies import policy_dict 22 | # Dynamics 23 | from mamp.dynamics.UnicycleDynamics import UnicycleDynamics 24 | # Sensors 25 | from mamp.sensors import Sensor 26 | from mamp.envs import Config 27 | from mamp.util import mod2pi 28 | 29 | 30 | def simulation_experiment2_in_circle(num_agents, rad): 31 | center = [0.0, 0.0] 32 | test_qi, test_qf = [], [] 33 | k = 0 # np.random.uniform(0, 2) 34 | for n in range(num_agents): 35 | test_qi.append([center[0] + round(rad * np.cos(2 * n * np.pi / num_agents + k * np.pi / 4), 2), 36 | center[1] + round(rad * np.sin(2 * n * np.pi / num_agents + k * np.pi / 4), 2), 37 | mod2pi(2 * n * np.pi / num_agents + k * np.pi / 4 + np.pi)]) 38 | for n in range(num_agents): 39 | test_qf.append([center[0] + round(rad * np.cos(2 * n * np.pi / num_agents + np.pi + k * np.pi / 4), 2), 40 | center[1] + round(rad * np.sin(2 * n * np.pi / num_agents + np.pi + k * np.pi / 4), 2), 41 | mod2pi(2 * n * np.pi / num_agents + k * np.pi / 4 + np.pi)]) 42 | return test_qi, test_qf 43 | 44 | 45 | def set_random_pos(agent_num, r=15): 46 | """ 47 | The square is 30 * 30 48 | The maximum number of agents is 961. 49 | """ 50 | r = int(r) 51 | poses = [] 52 | for i in range(-r, r+1): 53 | for j in range(-r, r+1): 54 | pos = np.array([i, j, 0.0]) 55 | poses.append(pos) 56 | agent_pos = random.sample(poses, agent_num) 57 | agent_goal = random.sample(poses, agent_num) 58 | 59 | return agent_pos, agent_goal 60 | 61 | 62 | def build_agents(): 63 | """ 64 | simulation 1: agents' num is 10-150, circle's radius is 15, random's radius is 15 65 | large simulation: agents' num is 200, circle's radius is 20, random's radius is 30 66 | """ 67 | num_agents = 100 68 | test_qi, test_qf = simulation_experiment2_in_circle(num_agents, rad=15.0) 69 | # test_qi, test_qf = set_random_pos(num_agents) 70 | radius = 0.2 71 | pref_speed = 1.0 # turtleBot3's max speed is 0.22 or 0.26 m/s, and 2.84 or 1.82 rad/s 72 | agents = [] 73 | for j in range(len(test_qi)): 74 | agents.append(Agent(start_pos=test_qi[j][:2], 75 | goal_pos=test_qf[j][:2], 76 | name='Agent' + str(j+1), 77 | radius=radius, pref_speed=pref_speed, 78 | initial_heading=test_qi[j][2], 79 | goal_heading=test_qf[j][2], 80 | policy=policy_dict['drvo'], 81 | dynamics_model=UnicycleDynamics, 82 | sensors=[Sensor], 83 | id=j)) 84 | return agents 85 | 86 | 87 | def build_obstacles(): 88 | obstacles = [] 89 | return obstacles 90 | 91 | 92 | if __name__ == '__main__': 93 | # Set agent configuration (start/goal pos, radius, size, policy) 94 | agents = build_agents() 95 | obstacles = build_obstacles() 96 | [agent.policy.initialize_network() for agent in agents if hasattr(agent.policy, 'initialize_network')] 97 | 98 | agents_num = len(agents) 99 | 100 | # env = gym.make("MAGazebo-v0") 101 | env = gym.make("MultiAgentCollisionAvoidance-v0") 102 | env.set_agents(agents, obstacles=obstacles) 103 | 104 | # is_back2start is True in experiment1 and is False in experiment2 105 | for agent in agents: 106 | agent.is_back2start = False 107 | 108 | epi_maximum = 1 109 | for epi in range(epi_maximum): 110 | env.reset() 111 | print("episode:", epi) 112 | game_over = False 113 | while not game_over: 114 | print("step is ", env.episode_step_number) 115 | actions = {} 116 | obs, rewards, game_over, which_agents_done = env.step(actions) 117 | print("All agents finished!", env.episode_step_number) 118 | print("Experiment over.") 119 | 120 | log_save_dir = os.path.dirname(os.path.realpath(__file__)) + '/../draw/drvo/log/' 121 | os.makedirs(log_save_dir, exist_ok=True) 122 | 123 | # trajectory 124 | writer = pd.ExcelWriter(log_save_dir + '/trajs_'+str(agents_num)+'.xlsx') 125 | for agent in agents: 126 | agent.history_info.to_excel(writer, sheet_name='agent' + str(agent.id)) 127 | writer.save() 128 | 129 | # scenario information 130 | info_dict_to_visualize = { 131 | 'all_agent_info': [], 132 | 'all_obstacle': [], 133 | 'all_compute_time': 0.0, 134 | 'all_straight_distance': 0.0, 135 | 'all_distance': 0.0, 136 | 'successful_num': 0, 137 | 'all_desire_step_num': 0, 138 | 'all_step_num': 0, 139 | 'SuccessRate': 0.0, 140 | 'ExtraTime': 0.0, 141 | 'ExtraDistance': 0.0, 142 | 'AverageSpeed': 0.0, 143 | 'AverageCost': 0.0 144 | } 145 | all_straight_dist = 0.0 146 | all_agent_total_time = 0.0 147 | all_agent_total_dist = 0.0 148 | num_of_success = 0 149 | all_desire_step_num = 0 150 | all_step_num = 0 151 | 152 | SuccessRate = 0.0 153 | ExtraTime = 0.0 154 | ExtraDistance = 0.0 155 | AverageSpeed = 0.0 156 | AverageCost = 0.0 157 | for agent in agents: 158 | agent_info_dict = {'id': agent.id, 'gp': agent.group, 'radius': agent.radius, 159 | 'goal_pos': agent.goal_global_frame.tolist()} 160 | info_dict_to_visualize['all_agent_info'].append(agent_info_dict) 161 | if not agent.in_collision and not agent.ran_out_of_time: 162 | num_of_success += 1 163 | all_agent_total_time += agent.total_time 164 | all_straight_dist += agent.straight_path_length 165 | all_agent_total_dist += agent.total_dist 166 | all_desire_step_num += agent.desire_steps 167 | all_step_num += agent.step_num 168 | info_dict_to_visualize['all_compute_time'] = all_agent_total_time 169 | info_dict_to_visualize['all_straight_distance'] = all_straight_dist 170 | info_dict_to_visualize['all_distance'] = all_agent_total_dist 171 | info_dict_to_visualize['successful_num'] = num_of_success 172 | info_dict_to_visualize['all_desire_step_num'] = all_desire_step_num 173 | info_dict_to_visualize['all_step_num'] = all_step_num 174 | 175 | info_dict_to_visualize['SuccessRate'] = num_of_success / agents_num 176 | info_dict_to_visualize['ExtraTime'] = ((all_step_num - all_desire_step_num) * Config.DT) / num_of_success 177 | info_dict_to_visualize['ExtraDistance'] = (all_agent_total_dist - all_straight_dist) / num_of_success 178 | info_dict_to_visualize['AverageSpeed'] = all_agent_total_dist / all_step_num / Config.DT 179 | info_dict_to_visualize['AverageCost'] = 1000 * all_agent_total_time / all_step_num 180 | 181 | for obstacle in env.obstacles: 182 | obstacle_info_dict = {'position': obstacle.pos, 'shape': obstacle.shape, 'feature': obstacle.feature} 183 | info_dict_to_visualize['all_obstacle'].append(obstacle_info_dict) 184 | 185 | info_str = json.dumps(info_dict_to_visualize, indent=4) 186 | with open(log_save_dir + '/env_cfg_'+str(agents_num)+'.json', 'w') as json_file: 187 | json_file.write(info_str) 188 | json_file.close() 189 | -------------------------------------------------------------------------------- /runpy/run_orca.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import json 5 | import random 6 | import numpy as np 7 | import pandas as pd 8 | 9 | # set path 10 | sys.path.append('/home/wuuya/PycharmProjects/maros') 11 | sys.path.append('/opt/ros/kinetic/lib/python2.7/dist-packages') 12 | import os 13 | 14 | os.environ['GYM_CONFIG_CLASS'] = 'Example' 15 | os.environ['GYM_CONFIG_PATH'] = '../mamp/configs/config.py' 16 | import gym 17 | 18 | gym.logger.set_level(40) 19 | 20 | from mamp.agents.agent import Agent, Obstacle 21 | # Policies 22 | from mamp.policies import policy_dict 23 | # Dynamics 24 | from mamp.dynamics.UnicycleDynamics import UnicycleDynamics 25 | # Sensors 26 | from mamp.sensors import Sensor 27 | from mamp.envs import Config 28 | from mamp.util import mod2pi 29 | 30 | 31 | def simulation_experiment2_in_circle(num_agents, rad): 32 | center = [0.0, 0.0] 33 | test_qi, test_qf = [], [] 34 | k = 0 # np.random.uniform(0, 2) 35 | for n in range(num_agents): 36 | test_qi.append([center[0] + round(rad * np.cos(2 * n * np.pi / num_agents + k * np.pi / 4), 2), 37 | center[1] + round(rad * np.sin(2 * n * np.pi / num_agents + k * np.pi / 4), 2), 38 | mod2pi(2 * n * np.pi / num_agents + k * np.pi / 4 + np.pi)]) 39 | for n in range(num_agents): 40 | test_qf.append([center[0] + round(rad * np.cos(2 * n * np.pi / num_agents + np.pi + k * np.pi / 4), 2), 41 | center[1] + round(rad * np.sin(2 * n * np.pi / num_agents + np.pi + k * np.pi / 4), 2), 42 | mod2pi(2 * n * np.pi / num_agents + k * np.pi / 4 + np.pi)]) 43 | return test_qi, test_qf 44 | 45 | 46 | def set_random_pos(agent_num, r=15): 47 | """ 48 | The square is 30 * 30 49 | The maximum number of agents is 961. 50 | """ 51 | r = int(r) 52 | poses = [] 53 | for i in range(-r, r+1): 54 | for j in range(-r, r+1): 55 | pos = np.array([i, j, 0.0]) 56 | poses.append(pos) 57 | agent_pos = random.sample(poses, agent_num) 58 | agent_goal = random.sample(poses, agent_num) 59 | 60 | return agent_pos, agent_goal 61 | 62 | 63 | def build_agents(): 64 | """ 65 | simulation 1: agents' num is 10-150, circle's radius is 15, random's radius is 15 66 | large simulation: agents' num is 200, circle's radius is 20, random's radius is 30 67 | """ 68 | num_agents = 100 69 | test_qi, test_qf = simulation_experiment2_in_circle(num_agents, rad=15.0) 70 | # test_qi, test_qf = set_random_pos(num_agents, r=30) 71 | radius = 0.2 72 | pref_speed = 1.0 # turtleBot3's max speed is 0.22 or 0.26 m/s, and 2.84 or 1.82 rad/s 73 | agents = [] 74 | for j in range(len(test_qi)): 75 | agents.append(Agent(start_pos=test_qi[j][:2], 76 | goal_pos=test_qf[j][:2], 77 | name='Agent' + str(j + 1), 78 | radius=radius, pref_speed=pref_speed, 79 | initial_heading=test_qi[j][2], 80 | goal_heading=test_qf[j][2], 81 | policy=policy_dict['orca'], 82 | dynamics_model=UnicycleDynamics, 83 | sensors=[Sensor], 84 | id=j)) 85 | return agents 86 | 87 | 88 | def build_obstacles(): 89 | """ 90 | exp_2: r = 16.0, agents' num is 100, radius of circle is 40 91 | """ 92 | obstacles = [] 93 | r = 16.0 94 | # obstacles.append(Obstacle(pos=[0.0, 0.0], shape_dict={'shape': "rect", 'feature': (r, r)}, id=0)) 95 | return obstacles 96 | 97 | 98 | if __name__ == '__main__': 99 | # Set agent configuration (start/goal pos, radius, size, policy) 100 | agents = build_agents() 101 | obstacles = build_obstacles() 102 | [agent.policy.initialize_network() for agent in agents if hasattr(agent.policy, 'initialize_network')] 103 | 104 | agents_num = len(agents) 105 | 106 | # env = gym.make("MAGazebo-v0") 107 | env = gym.make("MultiAgentCollisionAvoidance-v0") 108 | env.set_agents(agents, obstacles=obstacles) 109 | 110 | epi_maximum = 1 111 | for epi in range(epi_maximum): 112 | env.reset() 113 | print("episode:", epi) 114 | game_over = False 115 | while not game_over: 116 | print("step is ", env.episode_step_number) 117 | actions = {} 118 | obs, rewards, game_over, which_agents_done = env.step(actions) 119 | print("All agents finished!", env.episode_step_number) 120 | print("Experiment over.") 121 | 122 | log_save_dir = os.path.dirname(os.path.realpath(__file__)) + '/../draw/orca/log/' 123 | os.makedirs(log_save_dir, exist_ok=True) 124 | 125 | # trajectory 126 | writer = pd.ExcelWriter(log_save_dir + '/trajs_'+str(agents_num)+'.xlsx') 127 | for agent in agents: 128 | agent.history_info.to_excel(writer, sheet_name='agent' + str(agent.id)) 129 | writer.save() 130 | 131 | # scenario information 132 | info_dict_to_visualize = { 133 | 'all_agent_info': [], 134 | 'all_obstacle': [], 135 | 'all_compute_time': 0.0, 136 | 'all_straight_distance': 0.0, 137 | 'all_distance': 0.0, 138 | 'successful_num': 0, 139 | 'all_desire_step_num': 0, 140 | 'all_step_num': 0, 141 | 'SuccessRate': 0.0, 142 | 'ExtraTime': 0.0, 143 | 'ExtraDistance': 0.0, 144 | 'AverageSpeed': 0.0, 145 | 'AverageCost': 0.0 146 | } 147 | all_straight_dist = 0.0 148 | all_agent_total_time = 0.0 149 | all_agent_total_dist = 0.0 150 | num_of_success = 0 151 | all_desire_step_num = 0 152 | all_step_num = 0 153 | 154 | SuccessRate = 0.0 155 | ExtraTime = 0.0 156 | ExtraDistance = 0.0 157 | AverageSpeed = 0.0 158 | AverageCost = 0.0 159 | for agent in agents: 160 | agent_info_dict = {'id': agent.id, 'gp': agent.group, 'radius': agent.radius, 161 | 'goal_pos': agent.goal_global_frame.tolist()} 162 | info_dict_to_visualize['all_agent_info'].append(agent_info_dict) 163 | if not agent.in_collision and not agent.ran_out_of_time: 164 | num_of_success += 1 165 | all_agent_total_time += agent.total_time 166 | all_straight_dist += agent.straight_path_length 167 | all_agent_total_dist += agent.total_dist 168 | all_desire_step_num += agent.desire_steps 169 | all_step_num += agent.step_num 170 | info_dict_to_visualize['all_compute_time'] = all_agent_total_time 171 | info_dict_to_visualize['all_straight_distance'] = all_straight_dist 172 | info_dict_to_visualize['all_distance'] = all_agent_total_dist 173 | info_dict_to_visualize['successful_num'] = num_of_success 174 | info_dict_to_visualize['all_desire_step_num'] = all_desire_step_num 175 | info_dict_to_visualize['all_step_num'] = all_step_num 176 | 177 | info_dict_to_visualize['SuccessRate'] = num_of_success / agents_num 178 | info_dict_to_visualize['ExtraTime'] = ((all_step_num - all_desire_step_num) * Config.DT) / num_of_success 179 | info_dict_to_visualize['ExtraDistance'] = (all_agent_total_dist - all_straight_dist) / num_of_success 180 | info_dict_to_visualize['AverageSpeed'] = all_agent_total_dist / all_step_num / Config.DT 181 | info_dict_to_visualize['AverageCost'] = 1000 * all_agent_total_time / all_step_num 182 | 183 | for obstacle in env.obstacles: 184 | obstacle_info_dict = {'position': obstacle.pos, 'shape': obstacle.shape, 'feature': obstacle.feature} 185 | info_dict_to_visualize['all_obstacle'].append(obstacle_info_dict) 186 | 187 | info_str = json.dumps(info_dict_to_visualize, indent=4) 188 | with open(log_save_dir + '/env_cfg_'+str(agents_num)+'.json', 'w') as json_file: 189 | json_file.write(info_str) 190 | json_file.close() 191 | -------------------------------------------------------------------------------- /runpy/run_rvo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import json 5 | import numpy as np 6 | import pandas as pd 7 | 8 | # set path 9 | sys.path.append('/home/wuuya/PycharmProjects/maros') 10 | sys.path.append('/opt/ros/kinetic/lib/python2.7/dist-packages') 11 | import os 12 | import random 13 | 14 | os.environ['GYM_CONFIG_CLASS'] = 'Example' 15 | os.environ['GYM_CONFIG_PATH'] = '../mamp/configs/config.py' 16 | import gym 17 | 18 | gym.logger.set_level(40) 19 | 20 | from mamp.agents.agent import Agent, Obstacle 21 | # Policies 22 | from mamp.policies import policy_dict 23 | # Dynamics 24 | from mamp.dynamics.UnicycleDynamics import UnicycleDynamics 25 | # Sensors 26 | from mamp.sensors import Sensor 27 | from mamp.envs import Config 28 | from mamp.util import mod2pi 29 | 30 | 31 | def simulation_experiment2_in_circle(num_agents, rad): 32 | center = [0.0, 0.0] 33 | test_qi, test_qf = [], [] 34 | k = 0 # np.random.uniform(0, 2) 35 | for n in range(num_agents): 36 | test_qi.append([center[0] + round(rad * np.cos(2 * n * np.pi / num_agents + k * np.pi / 4), 2), 37 | center[1] + round(rad * np.sin(2 * n * np.pi / num_agents + k * np.pi / 4), 2), 38 | mod2pi(2 * n * np.pi / num_agents + k * np.pi / 4 + np.pi)]) 39 | for n in range(num_agents): 40 | test_qf.append([center[0] + round(rad * np.cos(2 * n * np.pi / num_agents + np.pi + k * np.pi / 4), 2), 41 | center[1] + round(rad * np.sin(2 * n * np.pi / num_agents + np.pi + k * np.pi / 4), 2), 42 | mod2pi(2 * n * np.pi / num_agents + k * np.pi / 4 + np.pi)]) 43 | return test_qi, test_qf 44 | 45 | 46 | def set_random_pos(agent_num, r=15): 47 | """ 48 | The square is 30 * 30 49 | The maximum number of agents is 961. 50 | """ 51 | r = int(r) 52 | poses = [] 53 | for i in range(-r, r+1): 54 | for j in range(-r, r+1): 55 | pos = np.array([i, j, 0.0]) 56 | poses.append(pos) 57 | agent_pos = random.sample(poses, agent_num) 58 | agent_goal = random.sample(poses, agent_num) 59 | 60 | return agent_pos, agent_goal 61 | 62 | 63 | def build_agents(): 64 | """ 65 | simulation 1: agents' num is 10-150, circle's radius is 15, random's radius is 15 66 | large simulation: agents' num is 200, circle's radius is 20, random's radius is 30 67 | """ 68 | num_agents = 100 69 | test_qi, test_qf = simulation_experiment2_in_circle(num_agents, rad=15.0) 70 | # test_qi, test_qf = set_random_pos(num_agents, r=15) 71 | radius = 0.2 72 | pref_speed = 1.0 # turtleBot3's max speed is 0.22 or 0.26 m/s, and 2.84 or 1.82 rad/s 73 | agents = [] 74 | for j in range(len(test_qi)): 75 | agents.append(Agent(start_pos=test_qi[j][:2], 76 | goal_pos=test_qf[j][:2], 77 | name='Agent' + str(j+1), 78 | radius=radius, pref_speed=pref_speed, 79 | initial_heading=test_qi[j][2], 80 | goal_heading=test_qf[j][2], 81 | policy=policy_dict['rvo'], 82 | dynamics_model=UnicycleDynamics, 83 | sensors=[Sensor], 84 | id=j)) 85 | return agents 86 | 87 | 88 | def build_obstacles(): 89 | """ 90 | exp_2: r = 16.0, agents' num is 100, radius of circle is 40 91 | """ 92 | obstacles = [] 93 | r = 16.0 94 | # obstacles.append(Obstacle(pos=[0.0, 0.0], shape_dict={'shape': "rect", 'feature': (r, r)}, id=0)) 95 | return obstacles 96 | 97 | 98 | if __name__ == '__main__': 99 | # Set agent configuration (start/goal pos, radius, size, policy) 100 | agents = build_agents() 101 | obstacles = build_obstacles() 102 | [agent.policy.initialize_network() for agent in agents if hasattr(agent.policy, 'initialize_network')] 103 | 104 | agents_num = len(agents) 105 | 106 | # env = gym.make("MAGazebo-v0") 107 | env = gym.make("MultiAgentCollisionAvoidance-v0") 108 | env.set_agents(agents, obstacles=obstacles) 109 | 110 | epi_maximum = 1 111 | for epi in range(epi_maximum): 112 | env.reset() 113 | print("episode:", epi) 114 | game_over = False 115 | while not game_over: 116 | print("step is ", env.episode_step_number) 117 | actions = {} 118 | obs, rewards, game_over, which_agents_done = env.step(actions) 119 | print("All agents finished!", env.episode_step_number) 120 | print("Experiment over.") 121 | 122 | log_save_dir = os.path.dirname(os.path.realpath(__file__)) + '/../draw/rvo/log/' 123 | os.makedirs(log_save_dir, exist_ok=True) 124 | 125 | # trajectory 126 | writer = pd.ExcelWriter(log_save_dir + '/trajs_'+str(agents_num)+'.xlsx') 127 | for agent in agents: 128 | agent.history_info.to_excel(writer, sheet_name='agent' + str(agent.id)) 129 | writer.save() 130 | 131 | # scenario information 132 | info_dict_to_visualize = { 133 | 'all_agent_info': [], 134 | 'all_obstacle': [], 135 | 'all_compute_time': 0.0, 136 | 'all_straight_distance': 0.0, 137 | 'all_distance': 0.0, 138 | 'successful_num': 0, 139 | 'all_desire_step_num': 0, 140 | 'all_step_num': 0, 141 | 'SuccessRate': 0.0, 142 | 'ExtraTime': 0.0, 143 | 'ExtraDistance': 0.0, 144 | 'AverageSpeed': 0.0, 145 | 'AverageCost': 0.0 146 | } 147 | all_straight_dist = 0.0 148 | all_agent_total_time = 0.0 149 | all_agent_total_dist = 0.0 150 | num_of_success = 0 151 | all_desire_step_num = 0 152 | all_step_num = 0 153 | 154 | SuccessRate = 0.0 155 | ExtraTime = 0.0 156 | ExtraDistance = 0.0 157 | AverageSpeed = 0.0 158 | AverageCost = 0.0 159 | for agent in agents: 160 | agent_info_dict = {'id': agent.id, 'gp': agent.group, 'radius': agent.radius, 161 | 'goal_pos': agent.goal_global_frame.tolist()} 162 | info_dict_to_visualize['all_agent_info'].append(agent_info_dict) 163 | if not agent.in_collision and not agent.ran_out_of_time: 164 | num_of_success += 1 165 | all_agent_total_time += agent.total_time 166 | all_straight_dist += agent.straight_path_length 167 | all_agent_total_dist += agent.total_dist 168 | all_desire_step_num += agent.desire_steps 169 | all_step_num += agent.step_num 170 | if num_of_success == 0: 171 | num_of_success = 1e-5 172 | if all_step_num == 0: 173 | all_step_num = 1e-5 174 | info_dict_to_visualize['all_compute_time'] = all_agent_total_time 175 | info_dict_to_visualize['all_straight_distance'] = all_straight_dist 176 | info_dict_to_visualize['all_distance'] = all_agent_total_dist 177 | info_dict_to_visualize['successful_num'] = num_of_success 178 | info_dict_to_visualize['all_desire_step_num'] = all_desire_step_num 179 | info_dict_to_visualize['all_step_num'] = all_step_num 180 | 181 | info_dict_to_visualize['SuccessRate'] = num_of_success / agents_num 182 | info_dict_to_visualize['ExtraTime'] = ((all_step_num - all_desire_step_num) * Config.DT) / num_of_success 183 | info_dict_to_visualize['ExtraDistance'] = (all_agent_total_dist - all_straight_dist) / num_of_success 184 | info_dict_to_visualize['AverageSpeed'] = all_agent_total_dist / all_step_num / Config.DT 185 | info_dict_to_visualize['AverageCost'] = 1000 * all_agent_total_time / all_step_num 186 | 187 | for obstacle in env.obstacles: 188 | obstacle_info_dict = {'position': obstacle.pos, 'shape': obstacle.shape, 'feature': obstacle.feature} 189 | info_dict_to_visualize['all_obstacle'].append(obstacle_info_dict) 190 | 191 | info_str = json.dumps(info_dict_to_visualize, indent=4) 192 | with open(log_save_dir + '/env_cfg_'+str(agents_num)+'.json', 'w') as json_file: 193 | json_file.write(info_str) 194 | json_file.close() 195 | -------------------------------------------------------------------------------- /mamp/policies/drvoPolicy.py: -------------------------------------------------------------------------------- 1 | import time 2 | import numpy as np 3 | from math import sqrt, sin, cos, atan2, asin, pi, floor, acos, asin 4 | from itertools import combinations, product 5 | 6 | from mamp.envs import Config 7 | from mamp.util import sqr, l2norm, norm, normalize, absSq, mod2pi, pi_2_pi, get_phi, angle_2_vectors, is_intersect, eps 8 | from mamp.policies.policy import Policy 9 | 10 | 11 | class DRVOPolicy(Policy): 12 | def __init__(self): 13 | Policy.__init__(self, str="DRVOPolicy") 14 | self.type = "internal" 15 | self.now_goal = None 16 | 17 | def find_next_action(self, obs, dict_comm, agent, kdTree): 18 | ts = time.time() 19 | self.now_goal = agent.goal_global_frame 20 | v_pref = compute_v_pref(agent) 21 | vA = agent.vel_global_frame 22 | pA = agent.pos_global_frame 23 | agent_rad = agent.radius + 0.05 24 | computeNeighbors(agent, kdTree) 25 | RVO_BA_all = [] 26 | for obj in agent.neighbors: 27 | obj = obj[0] 28 | pB = obj.pos_global_frame 29 | if obj.is_at_goal: 30 | transl_vB_vA = pA 31 | else: 32 | vB = obj.vel_global_frame 33 | transl_vB_vA = pA + 0.5 * (vB + vA) # Use RVO. 34 | obj_rad = obj.radius + 0.05 35 | 36 | RVO_BA = [transl_vB_vA, pA, pB, obj_rad + agent_rad] 37 | RVO_BA_all.append(RVO_BA) 38 | vA_post = intersect(v_pref, RVO_BA_all, agent) 39 | te = time.time() 40 | cost_step = te - ts 41 | agent.total_time += cost_step 42 | action = to_unicycle(vA_post, agent) 43 | theta = angle_2_vectors(vA, vA_post) 44 | agent.vel_global_unicycle[0] = int(1.0 * action[0]*eps)/eps 45 | agent.vel_global_unicycle[1] = int((0.22 * 0.5 * action[1] / Config.DT)*eps)/eps 46 | dist = l2norm(agent.pos_global_frame, agent.goal_global_frame) 47 | if theta > agent.max_heading_change: 48 | print('agent' + str(agent.id), len(agent.neighbors), action, '终点距离:', dist, 'theta:', theta) 49 | else: 50 | print('agent' + str(agent.id), len(agent.neighbors), action, '终点距离:', dist) 51 | 52 | return action 53 | 54 | 55 | def compute_v_pref(agent): 56 | goal = agent.goal_global_frame 57 | diff = goal - agent.pos_global_frame 58 | v_pref = agent.pref_speed * normalize(diff) 59 | if l2norm(goal, agent.pos_global_frame) < Config.NEAR_GOAL_THRESHOLD: 60 | v_pref[0] = 0.0 61 | v_pref[1] = 0.0 62 | return np.array(v_pref) 63 | 64 | 65 | def intersect(v_pref, RVO_BA_all, agent): 66 | norm_v = np.linalg.norm(v_pref) 67 | suitable_V = [] 68 | unsuitable_V = [] 69 | 70 | Theta = np.arange(0, 2 * pi, step=pi / 32) 71 | RAD = np.linspace(0.02, 1.0, num=5) 72 | v_list = [v_pref[:]] 73 | for theta, rad in product(Theta, RAD): 74 | new_v = np.array([cos(theta), sin(theta)]) * rad * norm_v 75 | new_v = np.array([new_v[0], new_v[1]]) 76 | v_list.append(new_v) 77 | 78 | for new_v in v_list: 79 | suit = True 80 | for RVO_BA in RVO_BA_all: 81 | p_0 = RVO_BA[0] 82 | pA = RVO_BA[1] 83 | pB = RVO_BA[2] 84 | combined_radius = RVO_BA[3] 85 | v_dif = np.array(new_v + pA - p_0) # new_v-0.5*(vA+vB) or new_v 86 | if is_intersect(pA, pB, combined_radius, v_dif): 87 | suit = False 88 | break 89 | if suit: 90 | suitable_V.append(new_v) 91 | else: 92 | unsuitable_V.append(new_v) 93 | 94 | # ---------------------- 95 | if suitable_V: 96 | suitable_V.sort(key=lambda v: l2norm(v, v_pref)) # Sort begin at minimum and end at maximum. 97 | if len(suitable_V) > 1: 98 | vA_post = select_right_side(suitable_V, agent.vel_global_frame, epsilon=1e-1, opt_num=6) 99 | else: 100 | vA_post = suitable_V[0] 101 | else: 102 | print('not find feasible velocity--------------------------------------', len(unsuitable_V), len(suitable_V)) 103 | tc_V = dict() 104 | for unsuit_v in unsuitable_V: 105 | tc_V[tuple(unsuit_v)] = 0 106 | tc = [] # Collision time. 107 | for RVO_BA in RVO_BA_all: 108 | p_0 = RVO_BA[0] 109 | pA = RVO_BA[1] 110 | pB = RVO_BA[2] 111 | combined_radius = RVO_BA[3] 112 | v_dif = np.array(unsuit_v + pA - p_0) 113 | pApB = pB - pA 114 | if is_intersect(pA, pB, combined_radius, v_dif): 115 | discr = sqr(np.dot(v_dif, pApB)) - absSq(v_dif) * (absSq(pApB) - sqr(combined_radius)) 116 | if discr < 0.0: 117 | print(discr) 118 | continue 119 | tc_v = max((np.dot(v_dif, pApB) - sqrt(discr)) / absSq(v_dif), 0.0) 120 | tc.append(tc_v) 121 | if len(tc) == 0: 122 | tc = [0.0] 123 | tc_V[tuple(unsuit_v)] = min(tc) + 1e-5 124 | WT = agent.safetyFactor 125 | unsuitable_V.sort(key=lambda v: ((WT / tc_V[tuple(v)]) + l2norm(v, v_pref))) 126 | if len(unsuitable_V) > 1: 127 | vA_post = select_right_side(unsuitable_V, agent.vel_global_frame, epsilon=1e-1, opt_num=6) 128 | else: 129 | vA_post = unsuitable_V[0] 130 | 131 | return vA_post 132 | 133 | 134 | def satisfied_constraint(agent, vCand): 135 | vA = agent.vel_global_frame 136 | costheta = np.dot(vA, vCand) / (np.linalg.norm(vA) * np.linalg.norm(vCand)) 137 | if costheta > 1.0: 138 | costheta = 1.0 139 | elif costheta < -1.0: 140 | costheta = -1.0 141 | theta = acos(costheta) # Rotational constraints. 142 | if theta <= agent.max_heading_change: 143 | return True 144 | else: 145 | return False 146 | 147 | 148 | def select_right_side(v_list, vA, epsilon=1e-5, opt_num=4): 149 | opt_v = [v_list[0]] 150 | i = 1 151 | while True: 152 | if abs(l2norm(v_list[0], vA) - l2norm(v_list[i], vA)) < epsilon: 153 | opt_v.append(v_list[i]) 154 | i += 1 155 | if i == len(v_list) or i == opt_num: 156 | break 157 | else: 158 | break 159 | vA_phi_min = min(opt_v, key=lambda v: get_phi(v)) 160 | vA_phi_max = max(opt_v, key=lambda v: get_phi(v)) 161 | phi_min = get_phi(vA_phi_min) 162 | phi_max = get_phi(vA_phi_max) 163 | if abs(phi_max - phi_min) <= pi: 164 | vA_opti = vA_phi_min 165 | else: 166 | vA_opti = vA_phi_max 167 | return vA_opti 168 | 169 | 170 | def computeNeighbors(agent, kdTree): 171 | agent.neighbors.clear() 172 | 173 | # Check obstacle neighbors. 174 | rangeSq = agent.neighborDist ** 2 175 | if len(agent.neighbors) != agent.maxNeighbors: 176 | rangeSq = 1.0 * agent.neighborDist ** 2 177 | kdTree.computeObstacleNeighbors(agent, rangeSq) 178 | 179 | if agent.in_collision: 180 | return 181 | 182 | # Check other agents. 183 | if len(agent.neighbors) != agent.maxNeighbors: 184 | rangeSq = agent.neighborDist ** 2 185 | kdTree.computeAgentNeighbors(agent, rangeSq) 186 | 187 | 188 | def to_unicycle(vA_post, agent): 189 | vA_post = np.array(vA_post) 190 | norm_vA = norm(vA_post) 191 | yaw_next = mod2pi(atan2(vA_post[1], vA_post[0])) 192 | yaw_current = mod2pi(agent.heading_global_frame) 193 | delta_theta = yaw_next - yaw_current 194 | delta_theta = pi_2_pi(delta_theta) 195 | if delta_theta < -pi: 196 | delta_theta = delta_theta + 2 * pi 197 | if delta_theta > pi: 198 | delta_theta = delta_theta - 2 * pi 199 | if delta_theta >= 1.0: 200 | delta_theta = 1.0 201 | if delta_theta <= -1: 202 | delta_theta = -1.0 203 | action = np.array([norm_vA, delta_theta]) 204 | return action 205 | -------------------------------------------------------------------------------- /runpy/run_rvodubins.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import json 5 | import random 6 | import numpy as np 7 | import pandas as pd 8 | 9 | sys.path.append('/home/wuuya/PycharmProjects/maros') 10 | sys.path.append('/opt/ros/kinetic/lib/python2.7/dist-packages') 11 | 12 | os.environ['GYM_CONFIG_CLASS'] = 'Example' 13 | os.environ['GYM_CONFIG_PATH'] = '../mamp/configs/config.py' 14 | import gym 15 | 16 | gym.logger.set_level(40) 17 | 18 | from mamp.agents.agent import Agent 19 | from mamp.agents.obstacle import Obstacle 20 | # Policies 21 | from mamp.policies import policy_dict 22 | # Dynamics 23 | from mamp.dynamics.UnicycleDynamics import UnicycleDynamics 24 | # Sensors 25 | from mamp.sensors import Sensor 26 | 27 | from mamp.util import mod2pi 28 | from mamp.envs import Config 29 | 30 | 31 | def simulation1_in_circle(num_agents, rad=40.0): 32 | center = [0.0, 0.0] 33 | test_qi, test_qf = [], [] 34 | k = 0 # rotation parameter 35 | for n in range(num_agents): 36 | test_qi.append([center[0] + round(rad * np.cos(2 * n * np.pi / num_agents + k * np.pi / 4), 2), 37 | center[1] + round(rad * np.sin(2 * n * np.pi / num_agents + k * np.pi / 4), 2), 38 | mod2pi(2 * n * np.pi / num_agents + k * np.pi / 4 + np.pi)]) 39 | for n in range(num_agents): 40 | test_qf.append([center[0] + round(rad * np.cos(2 * n * np.pi / num_agents + np.pi + k * np.pi / 4), 2), 41 | center[1] + round(rad * np.sin(2 * n * np.pi / num_agents + np.pi + k * np.pi / 4), 2), 42 | mod2pi(2 * n * np.pi / num_agents + k * np.pi / 4)]) 43 | return test_qi, test_qf 44 | 45 | 46 | def set_random_pos(agent_num, r=40): 47 | """ 48 | The square is 80 * 80 49 | The maximum number of agents is 81*81. 50 | """ 51 | r = int(r) 52 | poses = [] 53 | for i in range(-r, r+1): 54 | for j in range(-r, r+1): 55 | pos = np.array([i, j, random.uniform(0.0, 2*np.pi)]) 56 | poses.append(pos) 57 | agent_pos = random.sample(poses, agent_num) 58 | agent_goal = random.sample(poses, agent_num) 59 | 60 | return agent_pos, agent_goal 61 | 62 | 63 | def build_agents(): 64 | """ 65 | simulation1: agents' num is 10-150, circle's radius is 40, random's radius is 40 66 | large simulation : agents' num is 200, circle's radius is 50, random's radius is 50 67 | """ 68 | num_agents = 100 69 | test_qi, test_qf = simulation1_in_circle(num_agents, rad=40.0) 70 | # test_qi, test_qf = set_random_pos(num_agents, r=50) 71 | radius = 0.2 72 | pref_speed = 1.0 # turtleBot3's max speed is 0.22 or 0.26 m/s, and 2.84 or 1.82 rad/s 73 | agents = [] 74 | for j in range(len(test_qi)): 75 | agents.append(Agent(start_pos=test_qi[j][:2], 76 | goal_pos=test_qf[j][:2], 77 | name='Agent' + str(j+1), 78 | radius=radius, pref_speed=pref_speed, 79 | initial_heading=test_qi[j][2], 80 | goal_heading=test_qf[j][2], 81 | policy=policy_dict['rvo_dubins'], 82 | dynamics_model=UnicycleDynamics, 83 | sensors=[Sensor], 84 | id=j)) 85 | return agents 86 | 87 | 88 | def build_obstacles(): 89 | """ 90 | simulation1: r = 16.0, agents' num is 100, radius of circle is 40 91 | simulation2: r = 16.0, agents' num is 100, radius of circle is 40 92 | exp_real-world: obstacles is None 93 | """ 94 | obstacles = [] 95 | r = 16.0 96 | # obstacles.append(Obstacle(pos=[0.0, 0.0], shape_dict={'shape': "rect", 'feature': (r, r)}, id=0)) 97 | return obstacles 98 | 99 | 100 | if __name__ == '__main__': 101 | # Set agent configuration (start/goal pos, radius, size, policy) 102 | agents = build_agents() 103 | obstacles = build_obstacles() 104 | [agent.policy.initialize_network() for agent in agents if hasattr(agent.policy, 'initialize_network')] 105 | 106 | agents_num = len(agents) 107 | 108 | # env = gym.make("MAGazebo-v0") 109 | env = gym.make("MultiAgentCollisionAvoidance-v0") 110 | env.set_agents(agents, obstacles=obstacles) 111 | 112 | # is_back2start is True in experiment1 and is False in experiment2 113 | for agent in agents: 114 | agent.is_back2start = False 115 | 116 | epi_maximum = 1 117 | for epi in range(epi_maximum): 118 | env.reset() 119 | # print("episode:", epi) 120 | game_over = False 121 | while not game_over: 122 | print("step is ", env.episode_step_number) 123 | actions = {} 124 | obs, rewards, game_over, which_agents_done = env.step(actions) 125 | print("All agents finished!", env.episode_step_number) 126 | print("Experiment over.") 127 | 128 | log_save_dir = os.path.dirname(os.path.realpath(__file__)) + '/../draw/rvo_dubins/log/' 129 | os.makedirs(log_save_dir, exist_ok=True) 130 | 131 | # trajectory 132 | writer = pd.ExcelWriter(log_save_dir + '/trajs_'+str(agents_num)+'.xlsx') 133 | for agent in agents: 134 | agent.history_info.to_excel(writer, sheet_name='agent' + str(agent.id)) 135 | writer.save() 136 | 137 | # scenario information 138 | info_dict_to_visualize = { 139 | 'all_agent_info': [], 140 | 'all_obstacle': [], 141 | 'all_compute_time': 0.0, 142 | 'all_straight_distance': 0.0, 143 | 'all_distance': 0.0, 144 | 'successful_num': 0, 145 | 'all_desire_step_num': 0, 146 | 'all_step_num': 0, 147 | 'SuccessRate': 0.0, 148 | 'ExtraTime': 0.0, 149 | 'ExtraDistance': 0.0, 150 | 'AverageSpeed': 0.0, 151 | 'AverageCost': 0.0 152 | } 153 | all_straight_dist = 0.0 154 | all_agent_total_time = 0.0 155 | all_agent_total_dist = 0.0 156 | num_of_success = 0 157 | all_desire_step_num = 0 158 | all_step_num = 0 159 | 160 | SuccessRate = 0.0 161 | ExtraTime = 0.0 162 | ExtraDistance = 0.0 163 | AverageSpeed = 0.0 164 | AverageCost = 0.0 165 | for agent in agents: 166 | agent_info_dict = {'id': agent.id, 'gp': agent.group, 'radius': agent.radius, 167 | 'goal_pos': agent.goal_global_frame.tolist()} 168 | info_dict_to_visualize['all_agent_info'].append(agent_info_dict) 169 | if not agent.in_collision and not agent.ran_out_of_time: 170 | num_of_success += 1 171 | all_agent_total_time += agent.total_time 172 | all_straight_dist += agent.straight_path_length 173 | all_agent_total_dist += agent.total_dist 174 | all_desire_step_num += agent.desire_steps 175 | all_step_num += agent.step_num 176 | info_dict_to_visualize['all_compute_time'] = all_agent_total_time 177 | info_dict_to_visualize['all_straight_distance'] = all_straight_dist 178 | info_dict_to_visualize['all_distance'] = all_agent_total_dist 179 | info_dict_to_visualize['successful_num'] = num_of_success 180 | info_dict_to_visualize['all_desire_step_num'] = all_desire_step_num 181 | info_dict_to_visualize['all_step_num'] = all_step_num 182 | 183 | info_dict_to_visualize['SuccessRate'] = num_of_success / agents_num 184 | info_dict_to_visualize['ExtraTime'] = ((all_step_num - all_desire_step_num) * Config.DT) / num_of_success 185 | info_dict_to_visualize['ExtraDistance'] = (all_agent_total_dist - all_straight_dist) / num_of_success 186 | info_dict_to_visualize['AverageSpeed'] = all_agent_total_dist / all_step_num / Config.DT 187 | info_dict_to_visualize['AverageCost'] = 1000 * all_agent_total_time / all_step_num 188 | 189 | for obstacle in env.obstacles: 190 | obstacle_info_dict = {'position': obstacle.pos, 'shape': obstacle.shape, 'feature': obstacle.feature} 191 | info_dict_to_visualize['all_obstacle'].append(obstacle_info_dict) 192 | 193 | info_str = json.dumps(info_dict_to_visualize, indent=4) 194 | with open(log_save_dir + '/env_cfg_'+str(agents_num)+'.json', 'w') as json_file: 195 | json_file.write(info_str) 196 | json_file.close() 197 | -------------------------------------------------------------------------------- /mamp/sensors/OtherAgentsObsSensor.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from mamp.sensors.Sensor import Sensor 3 | from mamp.envs import Config 4 | from mamp.util import compute_time_to_impact, vec2_l2_norm 5 | import operator 6 | 7 | class OtherAgentsObsSensor(Sensor): 8 | """ A dense matrix of relative states of other agents (e.g., their positions, vel, radii) 9 | 10 | :param max_num_other_agents_observed: (int) only can observe up to this many agents (the closest ones) 11 | :param agent_sorting_method: (str) definition of closeness in words (one of ['closest_last', 'closest_first', 'time_to_impact']) 12 | 13 | """ 14 | def __init__(self, max_num_other_agents_observed=Config.MAX_NUM_OTHER_AGENTS_OBSERVED, agent_sorting_method=Config.AGENT_SORTING_METHOD): 15 | Sensor.__init__(self) 16 | self.name = 'other_agents_obs' 17 | self.max_num_other_agents_observed = max_num_other_agents_observed 18 | self.agent_sorting_method = agent_sorting_method 19 | 20 | def get_clipped_sorted_inds(self, sorting_criteria): 21 | """ Determine the closest N agents using the desired sorting criteria 22 | 23 | Args: 24 | sorting_criteria (str): how to sort the list of agents (one of ['closest_last', 'closest_first', 'time_to_impact']). 25 | See journal paper. 26 | 27 | Returns: 28 | clipped_sorted_inds (list): indices of the "closest" max_num_other_agents_observed 29 | agents sorted by "closeness" ("close" defined by sorting criteria), 30 | 31 | """ 32 | 33 | # Grab first N agents (where N=Config.MAX_NUM_OTHER_AGENTS_OBSERVED) 34 | if self.agent_sorting_method in ['closest_last', 'closest_first']: 35 | # where "first" == closest 36 | sorted_sorting_criteria = sorted(sorting_criteria, key = lambda x: (x[1], x[2])) 37 | elif self.agent_sorting_method in ['time_to_impact']: 38 | # where "first" == lowest time-to-impact 39 | sorted_sorting_criteria = sorted(sorting_criteria, key = lambda x: (-x[3], -x[1], x[2])) 40 | clipped_sorting_criteria = sorted_sorting_criteria[:self.max_num_other_agents_observed] 41 | 42 | # Then sort those N agents by the preferred ordering scheme 43 | if self.agent_sorting_method == "closest_last": 44 | # sort by inverse distance away, then by lateral position 45 | sorted_dists = sorted(clipped_sorting_criteria, key = lambda x: (-x[1], x[2])) 46 | elif self.agent_sorting_method == "closest_first": 47 | # sort by distance away, then by lateral position 48 | sorted_dists = sorted(clipped_sorting_criteria, key = lambda x: (x[1], x[2])) 49 | elif self.agent_sorting_method == "time_to_impact": 50 | # sort by time_to_impact, break ties by distance away, then by lateral position (e.g. in case inf TTC) 51 | sorted_dists = sorted(clipped_sorting_criteria, key = lambda x: (-x[3], -x[1], x[2])) 52 | else: 53 | raise ValueError("Did not supply proper self.agent_sorting_method in Agent.py.") 54 | 55 | clipped_sorted_inds = [x[0] for x in sorted_dists] 56 | return clipped_sorted_inds 57 | 58 | 59 | def sense(self, agents, agent_index): 60 | """ Go through each agent in the environment, and compute its relative position, vel, etc. and put into an array 61 | 62 | This is a denser measurement of other agents' states vs. a LaserScan or OccupancyGrid 63 | 64 | Args: 65 | agents (list): all :class:`~gym_collision_avoidance.envs.agent.Agent` in the environment 66 | agent_index (int): index of this agent (the one with this sensor) in :code:`agents` 67 | top_down_map (2D np array): binary image with 0 if that pixel is free space, 1 if occupied (not used!) 68 | 69 | Returns: 70 | other_agents_states (np array): (max_num_other_agents_observed x 7) the 7 states about each other agent, :code:`[p_parallel_ego_frame, p_orthog_ego_frame, v_parallel_ego_frame, v_orthog_ego_frame, other_agent.radius, combined_radius, dist_2_other]` 71 | 72 | """ 73 | host_agent = agents[agent_index] 74 | other_agent_dists = {} 75 | sorted_pairs = sorted(other_agent_dists.items(), key=operator.itemgetter(1)) 76 | 77 | sorting_criteria = [] 78 | for i, other_agent in enumerate(agents): 79 | if host_agent.pos_global_frame is not None and other_agent.pos_global_frame is not None: 80 | if other_agent.id == host_agent.id: 81 | continue 82 | # project other elements onto the new reference frame 83 | rel_pos_to_other_global_frame = other_agent.pos_global_frame - host_agent.pos_global_frame 84 | p_parallel_ego_frame = np.dot(rel_pos_to_other_global_frame, host_agent.ref_prll) 85 | p_orthog_ego_frame = np.dot(rel_pos_to_other_global_frame, host_agent.ref_orth) 86 | dist_between_agent_centers = vec2_l2_norm(rel_pos_to_other_global_frame) 87 | dist_2_other = dist_between_agent_centers - host_agent.radius - other_agent.radius 88 | combined_radius = host_agent.radius + other_agent.radius 89 | 90 | if dist_between_agent_centers > Config.SENSING_HORIZON: 91 | # print("Agent too far away") 92 | continue 93 | 94 | if self.agent_sorting_method != "time_to_impact": 95 | time_to_impact = None 96 | else: 97 | time_to_impact = compute_time_to_impact(host_agent.pos_global_frame, 98 | other_agent.pos_global_frame, 99 | host_agent.vel_global_frame, 100 | other_agent.vel_global_frame, 101 | combined_radius) 102 | 103 | sorting_criteria.append([i, round(dist_2_other,2), p_orthog_ego_frame, time_to_impact]) 104 | 105 | clipped_sorted_inds = self.get_clipped_sorted_inds(sorting_criteria) 106 | clipped_sorted_agents = [agents[i] for i in clipped_sorted_inds] 107 | 108 | other_agents_states = np.zeros((Config.MAX_NUM_OTHER_AGENTS_OBSERVED, Config.OTHER_AGENT_OBSERVATION_LENGTH)) 109 | other_agent_count = 0 110 | for other_agent in clipped_sorted_agents: 111 | if other_agent.id == host_agent.id: 112 | continue 113 | 114 | # project other elements onto the new reference frame 115 | rel_pos_to_other_global_frame = other_agent.pos_global_frame - host_agent.pos_global_frame 116 | dist_2_other = np.linalg.norm(rel_pos_to_other_global_frame) - host_agent.radius - other_agent.radius 117 | 118 | p_parallel_ego_frame = np.dot(rel_pos_to_other_global_frame, host_agent.ref_prll) 119 | p_orthog_ego_frame = np.dot(rel_pos_to_other_global_frame, host_agent.ref_orth) 120 | v_parallel_ego_frame = np.dot(other_agent.vel_global_frame, host_agent.ref_prll) 121 | v_orthog_ego_frame = np.dot(other_agent.vel_global_frame, host_agent.ref_orth) 122 | combined_radius = host_agent.radius + other_agent.radius 123 | 124 | other_obs = np.array([p_parallel_ego_frame, 125 | p_orthog_ego_frame, 126 | v_parallel_ego_frame, 127 | v_orthog_ego_frame, 128 | other_agent.radius, 129 | combined_radius, 130 | dist_2_other]) 131 | 132 | if other_agent_count == 0: 133 | host_agent.other_agent_obs[:] = other_obs 134 | 135 | other_agents_states[other_agent_count,:] = other_obs 136 | other_agent_count += 1 137 | 138 | host_agent.num_other_agents_observed = other_agent_count 139 | return other_agents_states 140 | 141 | -------------------------------------------------------------------------------- /draw/plt2d.py: -------------------------------------------------------------------------------- 1 | import time 2 | import numpy as np 3 | import pandas as pd 4 | import matplotlib.cm as cmx 5 | import matplotlib.colors as colors 6 | import matplotlib.pyplot as plt 7 | from matplotlib.path import Path 8 | import matplotlib.patches as patches 9 | from math import sin, cos, atan2, sqrt, pow 10 | 11 | from vis_util import get_2d_car_model, get_2d_uav_model 12 | from vis_util import rgba2rgb 13 | 14 | simple_plot = True 15 | 16 | 17 | # img = plt.imread('beijing.jpg', 100) 18 | 19 | 20 | def convert_to_actual_model_2d(agent_model, pos_global_frame, heading_global_frame): 21 | alpha = heading_global_frame 22 | for point in agent_model: 23 | x = point[0] 24 | y = point[1] 25 | # 进行航向计算 26 | ll = sqrt(pow(x, 2) + pow(y, 2)) 27 | alpha_model = atan2(y, x) 28 | alpha_ = alpha + alpha_model - np.pi / 2 # 改加 - np.pi / 2 因为画模型的时候UAV朝向就是正北方向,所以要减去90° 29 | point[0] = ll * cos(alpha_) + pos_global_frame[0] 30 | point[1] = ll * sin(alpha_) + pos_global_frame[1] 31 | 32 | 33 | def draw_agent_2d(ax, pos_global_frame, heading_global_frame, my_agent_model, color='blue'): 34 | agent_model = my_agent_model 35 | convert_to_actual_model_2d(agent_model, pos_global_frame, heading_global_frame) 36 | 37 | codes = [Path.MOVETO, 38 | Path.LINETO, 39 | Path.LINETO, 40 | Path.LINETO, 41 | Path.CLOSEPOLY, 42 | ] 43 | 44 | path = Path(agent_model, codes) 45 | # 第二步:创建一个patch,路径依然也是通过patch实现的,只不过叫做pathpatch 46 | col = [0.8, 0.8, 0.8] 47 | patch = patches.PathPatch(path, fc=col, ec=col, lw=1.5) 48 | 49 | ax.add_patch(patch) 50 | 51 | 52 | def draw_traj_2d(ax, obstacles_info, agents_info, agents_traj_list, step_num_list, current_step): 53 | cmap = get_cmap(64) 54 | plt_colors = get_colors() 55 | for idx, agent_traj in enumerate(agents_traj_list): 56 | # ax.imshow(img, extent=[-1, 4, -1, 4]) # 添加仿真背景 57 | agent_id = agents_info[idx]['id'] 58 | agent_gp = agents_info[idx]['gp'] 59 | agent_rd = agents_info[idx]['radius'] 60 | agent_goal = agents_info[idx]['goal_pos'] 61 | info = agents_info[idx] 62 | group = info['gp'] 63 | radius = info['radius'] / 1 64 | color_ind = idx % len(plt_colors) 65 | # p_color = p_colors[color_ind] 66 | plt_color = plt_colors[color_ind] 67 | 68 | ag_step_num = step_num_list[idx] 69 | if current_step > ag_step_num - 1: 70 | plot_step = ag_step_num - 1 71 | else: 72 | plot_step = current_step 73 | 74 | pos_x = agent_traj['pos_x'] 75 | pos_y = agent_traj['pos_y'] 76 | alpha = agent_traj['alpha'] 77 | 78 | # # 绘制目标点 79 | plt.plot(agent_goal[0], agent_goal[1], color=[0.933, 0.933, 0.0], marker='*', markersize=4) 80 | 81 | # 绘制探测区域 82 | # rect = patches.Rectangle((agent_goal[0] - 0.5, agent_goal[1] - 0.5), 1.0, 1.0, linewidth=1, 83 | # edgecolor=plt_color, facecolor='none', alpha=0.5) 84 | # ax.add_patch(rect) 85 | # plt.text(agent_goal[0]-0.85, agent_goal[1]-0.65, 'UGV ' + str(agent_id+1) + 86 | # ' Exploration Region', fontsize=12, color=p_color) 87 | 88 | # 绘制实线 89 | # plt.plot(pos_x[:plot_step], pos_y[:plot_step], color=plt_color) 90 | 91 | # 绘制箭头 92 | plt.arrow(pos_x[plot_step], pos_y[plot_step], 0.6 * cos(alpha[plot_step]), 0.6 * sin(alpha[plot_step]), 93 | fc=plt_color, ec=plt_color, head_width=0.3, head_length=0.4) 94 | 95 | # 绘制渐变线 96 | # pcolors = np.zeros((plot_step, 4)) 97 | # pcolors[:, :3] = plt_color[:3] 98 | # pcolors[:, 3] = np.linspace(0.2, 1., plot_step) 99 | # pcolors = rgba2rgb(pcolors) 100 | endure_time = 320 101 | # if plot_step <= endure_time: 102 | ax.scatter(pos_x[:plot_step], pos_y[:plot_step], marker='.', color=plt_color, s=0.2, alpha=0.5) 103 | # else: 104 | # nt = plot_step - endure_time 105 | # ax.scatter(pos_x[nt:plot_step], pos_y[nt:plot_step], color=pcolors[nt:plot_step], s=0.1, alpha=0.5) 106 | 107 | if simple_plot: 108 | ax.add_patch(plt.Circle((pos_x[plot_step], pos_y[plot_step]), radius=agent_rd, fc=plt_color, ec=plt_color)) 109 | text_offset = agent_rd 110 | ax.text(pos_x[plot_step] + text_offset, pos_y[plot_step] + text_offset, str(agent_id + 1), color=plt_color) 111 | else: 112 | if group == 0: 113 | my_model = get_2d_car_model(size=agent_rd) 114 | else: 115 | my_model = get_2d_uav_model(size=agent_rd) 116 | pos = [pos_x[plot_step], pos_y[plot_step]] 117 | heading = alpha[plot_step] 118 | draw_agent_2d(ax, pos, heading, my_model) 119 | for i in range(len(obstacles_info)): 120 | pos = obstacles_info[i]['position'] 121 | heading = 0.0 122 | rd = obstacles_info[i]['feature'] 123 | agent_rd = rd[0] / 2 124 | my_model = get_2d_car_model(size=agent_rd) 125 | draw_agent_2d(ax, pos, heading, my_model) 126 | 127 | 128 | def plot_save_one_pic(obstacles_info, agents_info, agents_traj_list, step_num_list, filename, current_step): 129 | fig = plt.figure(0) 130 | fig_size = (10, 8) 131 | fig.set_size_inches(fig_size[0], fig_size[1]) 132 | ax = fig.add_subplot(1, 1, 1) 133 | ax.set(xlabel='X', 134 | ylabel='Y', 135 | ) 136 | ax.axis('equal') 137 | # plt.grid(alpha=0.2) 138 | draw_traj_2d(ax, obstacles_info, agents_info, agents_traj_list, step_num_list, current_step) 139 | fig.savefig(filename, bbox_inches="tight") 140 | if current_step == 0: plt.show() 141 | # fig.savefig(filename) 142 | plt.close() 143 | 144 | 145 | def plot_episode(obstacles_info, agents_info, traj_list, step_num_list, plot_save_dir, base_fig_name, last_fig_name, 146 | show=False): 147 | current_step = 0 148 | num_agents = len(step_num_list) 149 | total_step = max(step_num_list) 150 | print('num_agents:', num_agents, 'total_step:', total_step) 151 | while current_step < total_step: 152 | fig_name = base_fig_name + "_{:05}".format(current_step) + '.png' 153 | filename = plot_save_dir + fig_name 154 | plot_save_one_pic(obstacles_info, agents_info, traj_list, step_num_list, filename, current_step) 155 | print(filename) 156 | current_step += 3 157 | filename = plot_save_dir + last_fig_name 158 | plot_save_one_pic(obstacles_info, agents_info, traj_list, step_num_list, filename, total_step) 159 | 160 | 161 | def get_cmap(N): 162 | """Returns a function that maps each index in 0, 1, ... N-1 to a distinct RGB color.""" 163 | color_norm = colors.Normalize(vmin=0, vmax=N - 1) 164 | scalar_map = cmx.ScalarMappable(norm=color_norm, cmap='hsv') 165 | 166 | def map_index_to_rgb_color(index): 167 | return scalar_map.to_rgba(index) 168 | 169 | return map_index_to_rgb_color 170 | 171 | 172 | def get_colors(): 173 | py_colors = np.array( 174 | [ 175 | [255, 0, 0], [255, 69, 0], [255, 165, 0], [0, 255, 0], [152, 251, 152], [0, 0, 255], [160, 32, 240], 176 | [255, 99, 71], [132, 112, 255], [0, 255, 255], [255, 69, 0], [148, 0, 211], [255, 192, 203], 177 | [255, 127, 0], [0, 191, 255], [255, 0, 255], 178 | ] 179 | ) 180 | # py_colors = np.array( 181 | # [ 182 | # [255, 99, 71], [255, 69, 0], [255, 0, 0], [255, 105, 180], [255, 192, 203], [238, 130, 238], 183 | # [78, 238, 148], [67, 205, 128], [46, 139, 87], [154, 255, 154], [144, 238, 144], [0, 255, 127], 184 | # [0, 238, 118], [0, 205, 102], [0, 139, 69], [0, 255, 0], [0, 139, 0], [127, 255, 0], [102, 205, 0], 185 | # [192, 255, 62], [202, 255, 112], [255, 255, 0], [255, 215, 0], [255, 193, 37], [255, 193, 193], 186 | # [250, 128, 114], [255, 160, 122], [255, 165, 0], [255, 140, 0], [255, 127, 80], [240, 128, 128], 187 | # [221, 160, 221], [218, 112, 214], [186, 85, 211], [148, 0, 211], [138, 43, 226], [160, 32, 240], 188 | # [106, 90, 205], [123, 104, 238], [0, 0, 205], [0, 0, 255], [30, 144, 255], [0, 191, 255], [152, 251, 152], 189 | # [0, 255, 127], [124, 252, 0], [255, 250, 240], [253, 245, 230], [250, 235, 215], 190 | # [255, 235, 205], [255, 218, 185], [255, 228, 181], [255, 248, 220], [255, 255, 240], [240, 255, 240], 191 | # [230, 230, 250], [0, 245, 255], [187, 255, 255], [0, 255, 255], [127, 255, 212], [118, 238, 198], 192 | # [102, 205, 170], [193, 255, 193], [84, 255, 159], 193 | # ] 194 | # ) 195 | return py_colors / 255 196 | -------------------------------------------------------------------------------- /runpy/run_pca.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import json 5 | import random 6 | import numpy as np 7 | import pandas as pd 8 | 9 | sys.path.append('/home/wuuya/PycharmProjects/maros') 10 | sys.path.append('/opt/ros/kinetic/lib/python2.7/dist-packages') 11 | import os 12 | 13 | os.environ['GYM_CONFIG_CLASS'] = 'Example' 14 | os.environ['GYM_CONFIG_PATH'] = '../mamp/configs/config.py' 15 | import gym 16 | 17 | gym.logger.set_level(40) 18 | 19 | from mamp.agents.agent import Agent 20 | from mamp.agents.obstacle import Obstacle 21 | # Policies 22 | from mamp.policies import policy_dict 23 | # Dynamics 24 | from mamp.dynamics.UnicycleDynamics import UnicycleDynamics 25 | # Sensors 26 | from mamp.sensors import Sensor 27 | 28 | from mamp.util import mod2pi 29 | from mamp.envs import Config 30 | 31 | 32 | def simulation1_in_circle(num_agents, rad=10.0): 33 | center = [0.0, 0.0] 34 | test_qi, test_qf = [], [] 35 | k = 0 # rotation parameter 36 | for n in range(num_agents): 37 | test_qi.append([center[0] + round(rad * np.cos(2 * n * np.pi / num_agents + k * np.pi / 4), 2), 38 | center[1] + round(rad * np.sin(2 * n * np.pi / num_agents + k * np.pi / 4), 2), 39 | mod2pi(2 * n * np.pi / num_agents + k * np.pi / 4 + np.pi)]) 40 | for n in range(num_agents): 41 | test_qf.append([center[0] + round(rad * np.cos(2 * n * np.pi / num_agents + np.pi + k * np.pi / 4), 2), 42 | center[1] + round(rad * np.sin(2 * n * np.pi / num_agents + np.pi + k * np.pi / 4), 2), 43 | mod2pi(2 * n * np.pi / num_agents + k * np.pi / 4)]) 44 | return test_qi, test_qf 45 | 46 | 47 | def real_world_experiment_in_circle(num_agents): 48 | center = [1.5, 1.5] 49 | rad = 1.5 50 | test_qi, test_qf = [], [] 51 | k = 0 # np.random.uniform(0, 2) 52 | for n in range(num_agents): 53 | test_qi.append([center[0] + round(rad * np.cos(2 * n * np.pi / num_agents + k * np.pi / 4), 2), 54 | center[1] + round(rad * np.sin(2 * n * np.pi / num_agents + k * np.pi / 4), 2), 55 | mod2pi(2 * n * np.pi / num_agents + k * np.pi / 4 + np.pi)]) 56 | for n in range(num_agents): 57 | test_qf.append([center[0] + round(rad * np.cos(2 * n * np.pi / num_agents + np.pi + k * np.pi / 4), 2), 58 | center[1] + round(rad * np.sin(2 * n * np.pi / num_agents + np.pi + k * np.pi / 4), 2), 59 | mod2pi(2 * n * np.pi / num_agents + k * np.pi / 4)]) 60 | return test_qi, test_qf 61 | 62 | 63 | def set_random_pos(agent_num, r=40): 64 | """ 65 | The square is 80 * 80 66 | The maximum number of agents is 81*81. 67 | """ 68 | r = int(r) 69 | poses = [] 70 | for i in range(-r, r+1): 71 | for j in range(-r, r+1): 72 | pos = np.array([i, j, random.uniform(0.0, 2*np.pi)]) 73 | poses.append(pos) 74 | agent_pos = random.sample(poses, agent_num) 75 | agent_goal = random.sample(poses, agent_num) 76 | 77 | return agent_pos, agent_goal 78 | 79 | 80 | def build_agents(): 81 | """ 82 | simulation 1: agents' num is 10-150, circle's radius is 40, random's radius is 40 83 | large simulation: agents' num is 200, circle's radius is 50, random's radius is 50 84 | real-world: agents' num is 4, circle's is radius 1.5 85 | """ 86 | num_agents = 100 87 | test_qi, test_qf = simulation1_in_circle(num_agents, rad=40.0) 88 | # test_qi, test_qf = set_random_pos(num_agents, r=50) 89 | # test_qi, test_qf = exp_realworld_in_circle() 90 | radius = 0.2 91 | pref_speed = 0.22 # turtleBot3's max speed is 0.22 or 0.26 m/s, and 2.84 or 1.82 rad/s 92 | agents = [] 93 | for j in range(len(test_qi)): 94 | agents.append(Agent(start_pos=test_qi[j][:2], 95 | goal_pos=test_qf[j][:2], 96 | name='Agent' + str(j+1), 97 | radius=radius, pref_speed=pref_speed, 98 | initial_heading=test_qi[j][2], 99 | goal_heading=test_qf[j][2], 100 | policy=policy_dict['pca'], 101 | dynamics_model=UnicycleDynamics, 102 | sensors=[Sensor], 103 | id=j)) 104 | return agents 105 | 106 | 107 | def build_obstacles(): 108 | """ 109 | simulation1: r = 16.0, agents' num is 100, radius of circle is 40 110 | simulation2: r = 16.0, agents' num is 100, radius of circle is 40 111 | exp_real-world: obstacles is None 112 | """ 113 | obstacles = [] 114 | r = 16.0 115 | obstacles.append(Obstacle(pos=[0.0, 0.0], shape_dict={'shape': "rect", 'feature': (r, r)}, id=0)) 116 | return obstacles 117 | 118 | 119 | if __name__ == '__main__': 120 | # Set agent configuration (start/goal pos, radius, size, policy) 121 | agents = build_agents() 122 | obstacles = build_obstacles() 123 | [agent.policy.initialize_network() for agent in agents if hasattr(agent.policy, 'initialize_network')] 124 | 125 | agents_num = len(agents) 126 | 127 | # env = gym.make("MAGazebo-v0") 128 | env = gym.make("MultiAgentCollisionAvoidance-v0") 129 | env.set_agents(agents, obstacles=obstacles) 130 | 131 | # is_back2start is True in experiment1 and is False in experiment2 132 | for agent in agents: 133 | agent.is_back2start = False 134 | 135 | epi_maximum = 1 136 | for epi in range(epi_maximum): 137 | env.reset() 138 | # print("episode:", epi) 139 | game_over = False 140 | while not game_over: 141 | print("step is ", env.episode_step_number) 142 | actions = {} 143 | obs, rewards, game_over, which_agents_done = env.step(actions) 144 | print("All agents finished!", env.episode_step_number) 145 | print("Experiment over.") 146 | 147 | log_save_dir = os.path.dirname(os.path.realpath(__file__)) + '/../draw/pca/log/' 148 | os.makedirs(log_save_dir, exist_ok=True) 149 | 150 | # trajectory 151 | writer = pd.ExcelWriter(log_save_dir + '/trajs_'+str(agents_num)+'.xlsx') 152 | for agent in agents: 153 | agent.history_info.to_excel(writer, sheet_name='agent' + str(agent.id)) 154 | writer.save() 155 | 156 | # scenario information 157 | info_dict_to_visualize = { 158 | 'all_agent_info': [], 159 | 'all_obstacle': [], 160 | 'all_compute_time': 0.0, 161 | 'all_straight_distance': 0.0, 162 | 'all_distance': 0.0, 163 | 'successful_num': 0, 164 | 'all_desire_step_num': 0, 165 | 'all_step_num': 0, 166 | 'SuccessRate': 0.0, 167 | 'ExtraTime': 0.0, 168 | 'ExtraDistance': 0.0, 169 | 'AverageSpeed': 0.0, 170 | 'AverageCost': 0.0 171 | } 172 | all_straight_dist = 0.0 173 | all_agent_total_time = 0.0 174 | all_agent_total_dist = 0.0 175 | num_of_success = 0 176 | all_desire_step_num = 0 177 | all_step_num = 0 178 | 179 | SuccessRate = 0.0 180 | ExtraTime = 0.0 181 | ExtraDistance = 0.0 182 | AverageSpeed = 0.0 183 | AverageCost = 0.0 184 | for agent in agents: 185 | agent_info_dict = {'id': agent.id, 'gp': agent.group, 'radius': agent.radius, 186 | 'goal_pos': agent.goal_global_frame.tolist()} 187 | info_dict_to_visualize['all_agent_info'].append(agent_info_dict) 188 | if not agent.in_collision and not agent.ran_out_of_time: 189 | num_of_success += 1 190 | all_agent_total_time += agent.total_time 191 | all_straight_dist += agent.straight_path_length 192 | all_agent_total_dist += agent.total_dist 193 | all_desire_step_num += agent.desire_steps 194 | all_step_num += agent.step_num 195 | info_dict_to_visualize['all_compute_time'] = all_agent_total_time 196 | info_dict_to_visualize['all_straight_distance'] = all_straight_dist 197 | info_dict_to_visualize['all_distance'] = all_agent_total_dist 198 | info_dict_to_visualize['successful_num'] = num_of_success 199 | info_dict_to_visualize['all_desire_step_num'] = all_desire_step_num 200 | info_dict_to_visualize['all_step_num'] = all_step_num 201 | 202 | info_dict_to_visualize['SuccessRate'] = num_of_success / agents_num 203 | info_dict_to_visualize['ExtraTime'] = ((all_step_num - all_desire_step_num) * Config.DT) / num_of_success 204 | info_dict_to_visualize['ExtraDistance'] = (all_agent_total_dist - all_straight_dist) / num_of_success 205 | info_dict_to_visualize['AverageSpeed'] = all_agent_total_dist / all_step_num / Config.DT 206 | info_dict_to_visualize['AverageCost'] = 1000 * all_agent_total_time / all_step_num 207 | 208 | for obstacle in env.obstacles: 209 | obstacle_info_dict = {'position': obstacle.pos, 'shape': obstacle.shape, 'feature': obstacle.feature} 210 | info_dict_to_visualize['all_obstacle'].append(obstacle_info_dict) 211 | 212 | info_str = json.dumps(info_dict_to_visualize, indent=4) 213 | with open(log_save_dir + '/env_cfg_'+str(agents_num)+'.json', 'w') as json_file: 214 | json_file.write(info_str) 215 | json_file.close() 216 | -------------------------------------------------------------------------------- /mamp/envs/carenv.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | import gym 3 | import rospy 4 | import numpy as np 5 | import time 6 | from mamp.envs.rosport.car import RosCarPort 7 | from gazebo_msgs.msg import ModelState 8 | from gazebo_msgs.srv import SetModelState 9 | from std_srvs.srv import Empty 10 | from math import sqrt, sin, cos, atan2 11 | from copy import deepcopy 12 | 13 | ERROR_MAX = 1 14 | 15 | 16 | class CAREnv(gym.Env): 17 | """ 18 | line follower robot environment 19 | 多机器人编队环境: 20 | 1位领导者:通过强化学习算法来学习PID系数完成循迹任务 21 | n位跟随者:跟随领导者前进,并且保持编队秩序 22 | """ 23 | 24 | def __init__(self): 25 | super().__init__() 26 | # information for rl 27 | self.reward = 0 28 | self.state = np.zeros(13) 29 | self.action_space = np.zeros(6) 30 | self.state_space = np.zeros(13) 31 | # information recorded 32 | self.success_record = [] 33 | self.game_over = False 34 | self.node = rospy.init_node('line_following_node', anonymous=False) 35 | self.reset_world = rospy.ServiceProxy('/gazebo/reset_world', Empty) 36 | self.set_state = rospy.ServiceProxy('/gazebo/set_model_state', SetModelState) 37 | self.rate = rospy.Rate(10) 38 | self.count = 0 39 | # agent 40 | self.agents = None 41 | 42 | def reset(self): 43 | # print('mean_v:', self.agents[0].distance/(time.time()-self.time)) 44 | self.reset_world() 45 | for ag in self.agents: 46 | ag.stop() 47 | time.sleep(1) 48 | self.game_over = False 49 | # if self.agents[0].failed_flag: 50 | # self.success_record.append(0) 51 | # print("fail!") 52 | if self.agents[0].success_flag: 53 | self.success_record.append(1) 54 | print("success!") 55 | else: 56 | self.success_record.append(0) 57 | self.time = time.time() 58 | for ag in self.agents: 59 | ag.reset() 60 | self.agents[0].set_pos([0, 0]) 61 | self.agents[1].set_pos([-1, 1]) 62 | self.agents[2].set_pos([-1, -1]) 63 | self.agents[3].set_pos([-2, 0]) 64 | obs = self._get_obs() 65 | return obs 66 | 67 | def set_start(self, model_name, pos, heading): 68 | """ 69 | 设置gazebo中机器人的位置和姿态 70 | """ 71 | x, y = pos[0], pos[1] 72 | state = ModelState() 73 | state.model_name = model_name 74 | state.reference_frame = 'world' # ''ground_plane' 75 | # pose 76 | state.pose.position.x = x 77 | state.pose.position.y = y 78 | state.pose.position.z = 0 79 | # quaternion = tf.transformations.quaternion_from_euler(0, 0, 0) 80 | state.pose.orientation.x = 0 81 | state.pose.orientation.y = 0 82 | state.pose.orientation.z = np.sin(heading / 2) 83 | state.pose.orientation.w = np.cos(heading / 2) 84 | # twist 85 | state.twist.linear.x = 0 86 | state.twist.linear.y = 0 87 | state.twist.linear.z = 0 88 | state.twist.angular.x = 0 89 | state.twist.angular.y = 0 90 | state.twist.angular.z = 0 91 | 92 | rospy.wait_for_service('/gazebo/set_model_state') 93 | try: 94 | set_state = self.set_state 95 | result = set_state(state) 96 | assert result.success is True 97 | except rospy.ServiceException: 98 | print("/gazebo/get_model_state service call failed") 99 | 100 | def set_agents(self, agents, neighbor_info): 101 | ''' 102 | neighbor_info = [{1: [-1, 0], 3: [0, 1]}, 103 | {0: [1, 0], 2: [0, 1]}, 104 | {1: [0, -1], 3: [1, 0]}, 105 | {0: [0, -1], 2: [-1, 0]},] 106 | ''' 107 | self.agents = agents 108 | self.neighbor_info = deepcopy(neighbor_info) # complete formation matrix 109 | # 设置临接矩阵 110 | num_agent = len(agents) 111 | adj_matrix = np.zeros((num_agent, num_agent)) 112 | for i, info in enumerate(neighbor_info): 113 | neighbor = info.keys() 114 | for j in neighbor: 115 | adj_matrix[i, j] = 1 116 | self.agents[i].neighbor.append(j) 117 | print(self.agents[i].name, self.agents[i].neighbor) 118 | self.adjMatrix = adj_matrix 119 | # 绑定智能体 120 | for ag in self.agents: 121 | ag.neighbor_info = neighbor_info[ag.id] 122 | ag.reset() 123 | ag.rosport = RosCarPort(ag) 124 | 125 | def _check_if_done(self): 126 | """ 127 | 检查是否回合结束 128 | """ 129 | dones = [] 130 | # check which agent is already done 131 | for ag in self.agents: 132 | if ag.done == True: 133 | ag.quit = True 134 | if abs(self.agents[0].pose_global_frame[0]) < 0.5 and \ 135 | abs(self.agents[0].pose_global_frame[1]) < 0.5 and (time.time() - self.time) > 20: 136 | for ag in self.agents: 137 | ag.success_flag = True 138 | 139 | for ag in self.agents: 140 | if ag.group == 1: 141 | if abs(ag.error[0][0]) > ERROR_MAX or abs(ag.error[0][1]) > ERROR_MAX: 142 | ag.failed_flag = True 143 | if ag.success_flag == True or ag.failed_flag == True: 144 | ag.done = True 145 | dones.append(ag.done) 146 | if self.agents[0].done == True: 147 | self.game_over = True 148 | if any([ag.done for ag in self.agents if ag.group == 1]): 149 | self.game_over = True 150 | return dones 151 | 152 | def _compute_rewards(self): 153 | rewards = [] 154 | for ag in self.agents: 155 | if ag.quit: 156 | rewards.append(0) 157 | else: 158 | var_e = np.var(ag.error) 159 | dist = ag.distance - ag.last_distance 160 | reward = 2 * dist - var_e 161 | if ag.group == 0: 162 | reward = 2 * dist - 10 * var_e 163 | reward -= 0.1 164 | if ag.done: 165 | if ag.success_flag: 166 | reward = 5 167 | else: 168 | reward = -5 169 | rewards.append(reward) 170 | ag.last_distance = ag.distance 171 | return rewards 172 | 173 | def _get_obs(self): 174 | """ 175 | 获取机器人完整的状态观测值 176 | """ 177 | ob = [] 178 | self.agents[0].state = np.append(self.agents[0].cx, self.agents[0].cy) 179 | state2 = [self.agents[0].v_x, self.agents[0].w_z, self.agents[0].error_k[0], 180 | self.agents[0].distance / 45.0] 181 | self.agents[0].state = np.append(self.agents[0].state, state2) 182 | ob.append(self.agents[0].state) 183 | for ag in self.agents: 184 | if ag.group == 1: 185 | ag.state = np.array([ag.error[0][0], ag.error[0][1], ag.v_x, ag.w_z, 186 | self.agents[ag.neighbor[0]].v_x, self.agents[ag.neighbor[0]].w_z, 187 | self.agents[ag.neighbor[1]].v_x, self.agents[ag.neighbor[1]].w_z, 188 | ag.distance / 45.0]) # 或许可以多加几个偏差 189 | 190 | ob.append(ag.state) 191 | return ob 192 | 193 | def _get_dict_comm(self): 194 | dict_comm = [] 195 | for ag in self.agents: 196 | info = {'pose_global_frame': ag.pose_global_frame, 197 | 'vel_global_frame': ag.vel_global_frame} 198 | dict_comm.append(info) 199 | return dict_comm 200 | 201 | def step(self, actions, dt=None): 202 | """ 203 | 执行单次动作(ros控制周期循环3次) 204 | 返回新的状态,奖励,回合结束标志位 205 | """ 206 | time_step = 0 207 | all_command = [] 208 | while time_step < 1: 209 | dones = self._check_if_done() 210 | dict_comm = self._get_dict_comm() 211 | if self.game_over: 212 | break 213 | for idx, ag in enumerate(self.agents): 214 | if ag.quit: 215 | all_command.append([0, 0]) 216 | else: 217 | if ag.group == 1: 218 | self._dynamic_update() 219 | command = ag.generate_next_command(actions[idx], dict_comm) 220 | all_command.append(command) 221 | self._control(all_command) 222 | time_step += 1 223 | obs = self._get_obs() 224 | rewards = self._compute_rewards() 225 | info = {} 226 | return obs, rewards, dones, info 227 | 228 | def _control(self, commands): 229 | """ 230 | 发送ROS控制指令 231 | """ 232 | for idx, ag in enumerate(self.agents): 233 | ag.rosport.pubTwist(commands[idx], dt=1 / 10) 234 | self.rate.sleep() 235 | 236 | def stop(self): 237 | """ 238 | 停止机器人运行 239 | """ 240 | for ag in self.agents: 241 | ag.stop() 242 | 243 | def _dynamic_update(self): 244 | """ 245 | 编队机器人动态矩阵信息的更新 246 | """ 247 | # update formation matrix 248 | for ag in self.agents: 249 | if ag.group == 1: 250 | for key, value in ag.neighbor_info.items(): 251 | yaw = self.agents[key].heading_global_frame 252 | Rz = np.array([[cos(yaw), -sin(yaw)], [sin(yaw), cos(yaw)]], dtype=float) 253 | value = Rz @ np.array(self.neighbor_info[ag.id][key]) 254 | ag.neighbor_info[key] = value 255 | -------------------------------------------------------------------------------- /mamp/configs/config.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | ''' 3 | formation movement: from pos to goal_pos with other agent observation 4 | ''' 5 | class Config(object): 6 | def __init__(self): 7 | ######################################################################### 8 | # GENERAL PARAMETERS 9 | self.COLLISION_AVOIDANCE = True 10 | self.continuous, self.discrete = range(2) # Initialize game types as enum 11 | self.ACTION_SPACE_TYPE = self.continuous 12 | 13 | ### DISPLAY 14 | self.ANIMATE_EPISODES = False 15 | self.SHOW_EPISODE_PLOTS = False 16 | self.SAVE_EPISODE_PLOTS = False 17 | if not hasattr(self, "PLOT_CIRCLES_ALONG_TRAJ"): 18 | self.PLOT_CIRCLES_ALONG_TRAJ = True 19 | self.ANIMATION_PERIOD_STEPS = 5 # plot every n-th DT step (if animate mode on) 20 | self.PLT_LIMITS = None 21 | self.PLT_FIG_SIZE = (10, 8) 22 | 23 | if not hasattr(self, "USE_STATIC_MAP"): 24 | self.USE_STATIC_MAP = False 25 | 26 | ### TRAIN / PLAY / EVALUATE 27 | self.TRAIN_MODE = True # Enable to see the trained agent in action (for testing) 28 | self.PLAY_MODE = False # Enable to see the trained agent in action (for testing) 29 | self.EVALUATE_MODE = False # Enable to see the trained agent in action (for testing) 30 | 31 | ### REWARDS 32 | self.REWARD_MODE = "no formation" # formation 33 | self.REWARD_AT_GOAL = 1.0 # reward given when agent reaches goal position 34 | self.REWARD_AT_FORMAT = 0.1 # 35 | self.REWARD_TO_GOAL_RATE = 0.0 # 36 | self.REWARD_COLLISION_WITH_AGENT = -0.25 # reward given when agent collides with another agent 37 | self.REWARD_COLLISION_WITH_WALL = -0.25 # reward given when agent collides with wall 38 | self.REWARD_GETTING_CLOSE = -0.1 # reward when agent gets close to another agent (unused?) 39 | self.REWARD_ENTERED_NORM_ZONE = -0.05 # reward when agent enters another agent's social zone 40 | self.REWARD_TIME_STEP = 0.0 # default reward given if none of the others apply (encourage speed) 41 | self.REWARD_WIGGLY_BEHAVIOR = 0.0 42 | self.WIGGLY_BEHAVIOR_THRESHOLD = np.inf 43 | self.COLLISION_DIST = 0.0 # meters between agents' boundaries for collision 44 | self.GETTING_CLOSE_RANGE = 0.2 # meters between agents' boundaries for collision 45 | # self.SOCIAL_NORMS = "right" 46 | # self.SOCIAL_NORMS = "left" 47 | self.SOCIAL_NORMS = "none" 48 | 49 | ### SIMULATION 50 | self.DT = 0.2 # seconds between simulation time steps 51 | self.NEAR_GOAL_THRESHOLD = 0.2 52 | self.NEAR_FORMAT_THRESHOLD = 0.2 53 | self.MAX_TIME_RATIO = 10. # agent has this number times the straight-line-time to reach its goal before "timing out" 54 | 55 | ### PARAMETERS THAT OVERWRITE/IMPACT THE ENV'S PARAMETERS 56 | if not hasattr(self, "MAX_NUM_OBS_IN_ENVIRONMENT"): 57 | self.MAX_NUM_OBS_IN_ENVIRONMENT = 0 58 | if not hasattr(self, "MAX_NUM_AGENTS_IN_ENVIRONMENT"): 59 | self.MAX_NUM_AGENTS_IN_ENVIRONMENT = 4 60 | if not hasattr(self, "MAX_NUM_AGENTS_TO_SIM"): 61 | self.MAX_NUM_AGENTS_TO_SIM = 4 62 | self.MAX_NUM_OTHER_AGENTS_IN_ENVIRONMENT = self.MAX_NUM_AGENTS_IN_ENVIRONMENT - 1 63 | if not hasattr(self, "MAX_NUM_OTHER_AGENTS_OBSERVED"): 64 | self.MAX_NUM_OTHER_AGENTS_OBSERVED = self.MAX_NUM_AGENTS_IN_ENVIRONMENT - 1 65 | 66 | ### EXPERIMENTS 67 | self.PLOT_EVERY_N_EPISODES = 100 # for tensorboard visualization 68 | 69 | ### SENSORS 70 | self.SENSING_HORIZON = np.inf 71 | # self.SENSING_HORIZON = 3.0 72 | self.LASERSCAN_LENGTH = 512 # num range readings in one scan 73 | self.LASERSCAN_NUM_PAST = 3 # num range readings in one scan 74 | self.NUM_STEPS_IN_OBS_HISTORY = 1 # number of time steps to store in observation vector 75 | self.NUM_PAST_ACTIONS_IN_STATE = 0 76 | self.WITH_COMM = False 77 | 78 | ### RVO AGENTS 79 | self.RVO_TIME_HORIZON = 5.0 80 | self.RVO_COLLAB_COEFF = 0.5 81 | self.RVO_ANTI_COLLAB_T = 1.0 82 | 83 | ### OBSERVATION VECTOR 84 | self.TRAIN_SINGLE_AGENT = False 85 | self.STATE_INFO_DICT = { 86 | 'dist_to_goal': { 87 | 'dtype': np.float32, 88 | 'size': 1, 89 | 'bounds': [-np.inf, np.inf], 90 | 'attr': 'get_agent_data("dist_to_goal")', 91 | 'std': np.array([5.], dtype=np.float32), 92 | 'mean': np.array([0.], dtype=np.float32) 93 | }, 94 | 'radius': { 95 | 'dtype': np.float32, 96 | 'size': 1, 97 | 'bounds': [0, np.inf], 98 | 'attr': 'get_agent_data("radius")', 99 | 'std': np.array([1.0], dtype=np.float32), 100 | 'mean': np.array([0.5], dtype=np.float32) 101 | }, 102 | 'heading_ego_frame': { 103 | 'dtype': np.float32, 104 | 'size': 1, 105 | 'bounds': [-np.pi, np.pi], 106 | 'attr': 'get_agent_data("heading_ego_frame")', 107 | 'std': np.array([3.14], dtype=np.float32), 108 | 'mean': np.array([0.], dtype=np.float32) 109 | }, 110 | 'pref_speed': { 111 | 'dtype': np.float32, 112 | 'size': 1, 113 | 'bounds': [0, np.inf], 114 | 'attr': 'get_agent_data("pref_speed")', 115 | 'std': np.array([1.0], dtype=np.float32), 116 | 'mean': np.array([1.0], dtype=np.float32) 117 | }, 118 | 'num_other_agents': { 119 | 'dtype': np.float32, 120 | 'size': 1, 121 | 'bounds': [0, np.inf], 122 | 'attr': 'get_agent_data("num_other_agents_observed")', 123 | 'std': np.array([1.0], dtype=np.float32), 124 | 'mean': np.array([1.0], dtype=np.float32) 125 | }, 126 | 'other_agent_states': { 127 | 'dtype': np.float32, 128 | 'size': 7, 129 | 'bounds': [-np.inf, np.inf], 130 | 'attr': 'get_agent_data("other_agent_states")', 131 | 'std': np.array([5.0, 5.0, 1.0, 1.0, 1.0, 5.0, 1.0], dtype=np.float32), 132 | 'mean': np.array([0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 1.0], dtype=np.float32) 133 | }, 134 | 'other_agents_states': { 135 | 'dtype': np.float32, 136 | 'size': (self.MAX_NUM_OTHER_AGENTS_OBSERVED,7), 137 | 'bounds': [-np.inf, np.inf], 138 | 'attr': 'get_sensor_data("other_agents_states")', 139 | 'std': np.tile(np.array([5.0, 5.0, 1.0, 1.0, 1.0, 5.0, 1.0], dtype=np.float32), (self.MAX_NUM_OTHER_AGENTS_OBSERVED, 1)), 140 | 'mean': np.tile(np.array([0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 1.0], dtype=np.float32), (self.MAX_NUM_OTHER_AGENTS_OBSERVED, 1)), 141 | }, 142 | 'laserscan': { 143 | 'dtype': np.float32, 144 | 'size': (self.LASERSCAN_NUM_PAST, self.LASERSCAN_LENGTH), 145 | 'bounds': [0., 6.], 146 | 'attr': 'get_sensor_data("laserscan")', 147 | 'std': 5.*np.ones((self.LASERSCAN_NUM_PAST, self.LASERSCAN_LENGTH), dtype=np.float32), 148 | 'mean': 5.*np.ones((self.LASERSCAN_NUM_PAST, self.LASERSCAN_LENGTH), dtype=np.float32) 149 | }, 150 | 'is_learning': { 151 | 'dtype': np.float32, 152 | 'size': 1, 153 | 'bounds': [0., 1.], 154 | 'attr': 'get_agent_data_equiv("policy.str", "learning")' 155 | }, 156 | } 157 | 158 | self.ANIMATION_COLUMNS = ['pos_x', 'pos_y', 'alpha', 'vel_x', 'vel_y', 'vel_linear', 'vel_angular', 'total_time'] 159 | if not hasattr(self, "STATES_IN_OBS_MULTI"): 160 | self.STATES_IN_OBS_MULTI = [ 161 | ['is_learning', 'num_other_agents', 'dist_to_goal', 'heading_ego_frame', 'pref_speed', 'radius'], 162 | ] 163 | 164 | if not hasattr(self, "STATES_NOT_USED_IN_POLICY"): 165 | self.STATES_NOT_USED_IN_POLICY = ['is_learning'] 166 | 167 | self.HOST_AGENT_OBSERVATION_LENGTH = 4 # dist to goal, heading to goal, pref speed, radius 168 | self.OTHER_AGENT_STATE_LENGTH = 7 # other px, other py, other vx, other vy, other radius, combined radius, distance between 169 | self.OTHER_AGENT_OBSERVATION_LENGTH = 7 # other px, other py, other vx, other vy, other radius, combined radius, distance between 170 | self.OTHER_AGENT_FULL_OBSERVATION_LENGTH = self.OTHER_AGENT_OBSERVATION_LENGTH 171 | self.HOST_AGENT_STATE_SIZE = self.HOST_AGENT_OBSERVATION_LENGTH 172 | 173 | # self.AGENT_SORTING_METHOD = "closest_last" 174 | self.AGENT_SORTING_METHOD = "closest_first" 175 | # self.AGENT_SORTING_METHOD = "time_to_impact" 176 | 177 | class Example(Config): 178 | def __init__(self): 179 | self.MAX_NUM_AGENTS_IN_ENVIRONMENT = 1024 180 | Config.__init__(self) 181 | self.EVALUATE_MODE = True 182 | self.TRAIN_MODE = False 183 | self.DT = 0.1 184 | self.SAVE_EPISODE_PLOTS = True 185 | self.PLOT_CIRCLES_ALONG_TRAJ = True 186 | self.ANIMATE_EPISODES = True 187 | # self.SENSING_HORIZON = 4 188 | # self.PLT_LIMITS = [[-20, 20], [-20, 20]] 189 | # self.PLT_FIG_SIZE = (10,10) 190 | 191 | class AStar(Config): 192 | def __init__(self): 193 | self.MAX_NUM_AGENTS_IN_ENVIRONMENT = 512 194 | Config.__init__(self) 195 | self.EVALUATE_MODE = True 196 | self.TRAIN_MODE = False 197 | self.DT = 0.1 198 | self.SAVE_EPISODE_PLOTS = True 199 | self.PLOT_CIRCLES_ALONG_TRAJ = True 200 | self.ANIMATE_EPISODES = True 201 | # self.SENSING_HORIZON = 4 202 | # self.PLT_LIMITS = [[-20, 20], [-20, 20]] 203 | # self.PLT_FIG_SIZE = (10,10) 204 | -------------------------------------------------------------------------------- /mamp/envs/macaenv.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ''' 3 | import os 4 | import gym 5 | import copy 6 | import itertools 7 | import gym.spaces 8 | import numpy as np 9 | 10 | from mamp.envs import Config 11 | from mamp.util import l2norm 12 | from mamp.policies.kdTree import KDTree 13 | 14 | 15 | class MACAEnv(gym.Env): 16 | def __init__(self): 17 | self.id = 0 18 | self.episode_number = 0 19 | self.episode_step_number = None 20 | 21 | # Initialize Rewards 22 | # self._initialize_rewards() 23 | 24 | # Simulation Parameters 25 | self.num_agents = Config.MAX_NUM_AGENTS_IN_ENVIRONMENT 26 | self.dt_nominal = Config.DT 27 | 28 | # Collision Parameters 29 | self.collision_dist = Config.COLLISION_DIST 30 | self.getting_close_range = Config.GETTING_CLOSE_RANGE 31 | self.evaluate = Config.EVALUATE_MODE 32 | 33 | ### The gym.spaces library doesn't support Python2.7 (syntax of Super().__init__()) 34 | self.action_space_type = Config.ACTION_SPACE_TYPE 35 | # Upper/Lower bounds on Actions 36 | self.max_heading_change = np.pi / 4 37 | self.min_heading_change = -self.max_heading_change 38 | self.min_speed = 0.0 39 | self.max_speed = 1.0 40 | if self.action_space_type == Config.discrete: 41 | self.action_space = gym.spaces.Discrete(self.actions.num_actions, dtype=np.float32) 42 | elif self.action_space_type == Config.continuous: 43 | self.low_action = np.array([self.min_speed, self.min_heading_change]) 44 | self.high_action = np.array([self.max_speed, self.max_heading_change]) 45 | self.action_space = gym.spaces.Box(self.low_action, self.high_action, dtype=np.float32) 46 | 47 | # single agent dict obs 48 | self.observation = {} 49 | for agent in range(Config.MAX_NUM_AGENTS_IN_ENVIRONMENT): 50 | self.observation[agent] = {} 51 | 52 | self.agents = None 53 | self.centralized_planner = None 54 | self.obstacles = [] 55 | self.kdTree = None 56 | 57 | def set_agents(self, agents, obstacles=None): 58 | self.agents = agents 59 | self.obstacles = obstacles 60 | self.kdTree = KDTree(self.agents, self.obstacles) 61 | self.kdTree.buildObstacleTree() 62 | 63 | def reset(self): 64 | for ag in self.agents: ag.reset() 65 | if self.episode_step_number is not None and self.episode_step_number > 0: 66 | self.episode_number += 1 67 | self.begin_episode = True 68 | self.episode_step_number = 0 69 | 70 | def step(self, actions): 71 | self.episode_step_number += 1 72 | 73 | # Take action 74 | self._take_action(actions) 75 | 76 | self._update_after_action() 77 | 78 | collision_with_obstacle, collision_with_agent = self._check_for_collisions() 79 | 80 | # Take observation 81 | next_observations = self._get_obs() 82 | 83 | # Check which agents' games are finished (at goal/collided/out of time) 84 | which_agents_done, game_over = self._check_which_agents_done() 85 | 86 | which_agents_done_dict = {} 87 | which_agents_learning_dict = {} 88 | for i, agent in enumerate(self.agents): 89 | which_agents_done_dict[agent.id] = which_agents_done[i] 90 | which_agents_learning_dict[agent.id] = agent.policy.is_still_learning 91 | info = { 92 | 'which_agents_done': which_agents_done_dict, 93 | 'which_agents_learning': which_agents_learning_dict, 94 | } 95 | return next_observations, 0, game_over, info 96 | 97 | def _take_action(self, actions): 98 | self.kdTree.buildAgentTree() 99 | 100 | num_actions_per_agent = 2 # speed, delta heading angle 101 | all_actions = np.zeros((len(self.agents), num_actions_per_agent), dtype=np.float32) 102 | 103 | # Agents set their action (either from external or w/ find_next_action) 104 | for agent_index, agent in enumerate(self.agents): 105 | if agent.is_done: 106 | continue 107 | dict_obs = self.observation[agent_index] 108 | other_agents = copy.copy(self.agents) 109 | other_agents.remove(agent) 110 | dict_comm = {'other_agents': other_agents, 'obstacles': self.obstacles} 111 | all_actions[agent_index, :] = agent.find_next_action(dict_obs, dict_comm, actions, self.kdTree) 112 | 113 | # After all agents have selected actions, run one dynamics update 114 | for i, agent in enumerate(self.agents): 115 | agent.take_action(all_actions[i, :]) 116 | 117 | def set_cbs_planner(self, planner): 118 | self.centralized_planner = planner 119 | solutions = self.centralized_planner.search() 120 | 121 | if not solutions: 122 | print("agent" + str(self.id) + "'s Solution not found", '\n') 123 | return 124 | for aid, solution in solutions.items(): 125 | if len(solution) <= 1: continue 126 | for time_sol in solution[1:][::-1]: 127 | self.agents[aid].path.append(time_sol) 128 | print("agent" + str(aid) + "'s trajectory is:", solution) 129 | 130 | def _check_for_collisions(self): 131 | """ Check whether each agent has collided with another agent or a static obstacle in the map 132 | This method doesn't compute social zones currently!!!!! 133 | Returns: 134 | - collision_with_agent (list): for each agent, bool True if that agent is in collision with another agent 135 | - collision_with_wall (list): for each agent, bool True if that agent is in collision with object in map 136 | - entered_norm_zone (list): for each agent, bool True if that agent entered another agent's social zone 137 | - dist_btwn_nearest_agent (list): for each agent, float closest distance to another agent 138 | """ 139 | collision_with_agent = [False for _ in self.agents] 140 | collision_with_wall = [False for _ in self.agents] 141 | entered_norm_zone = [False for _ in self.agents] 142 | dist_btwn_nearest_agent = [np.inf for _ in self.agents] 143 | agent_shapes = [] 144 | agent_front_zones = [] 145 | agent_inds = list(range(len(self.agents))) 146 | agent_pairs = list(itertools.combinations(agent_inds, 2)) 147 | for i, j in agent_pairs: 148 | dist_btwn = l2norm(self.agents[i].pos_global_frame, self.agents[j].pos_global_frame) 149 | combined_radius = self.agents[i].radius + self.agents[j].radius 150 | dist_btwn_nearest_agent[i] = min(dist_btwn_nearest_agent[i], dist_btwn - combined_radius) 151 | if dist_btwn <= combined_radius: 152 | # Collision with another agent! 153 | collision_with_agent[i] = True 154 | collision_with_agent[j] = True 155 | self.agents[i].in_collision = True 156 | self.agents[j].in_collision = True 157 | print('collision:', 'agent' + str(i), 'and agent' + str(j)) 158 | 159 | agent_inds = list(range(len(self.agents))) 160 | collision_with_obstacle = [False for _ in self.agents] 161 | dist_to_nearest_obstacle = [np.inf for _ in self.agents] 162 | for obstacle in self.obstacles: 163 | for j in agent_inds: 164 | dist_btwn = l2norm(obstacle.pos_global_frame, self.agents[j].pos_global_frame) 165 | combined_radius = obstacle.radius + self.agents[j].radius 166 | dist_to_nearest_obstacle[j] = min(dist_to_nearest_obstacle[j], dist_btwn - combined_radius) 167 | # diff_verct = self.agents[j].pos_global_frame - obstacle.pos_global_frame 168 | # norm = np.sqrt(diff_verct[0]**2 + diff_verct[1]**2) 169 | if dist_btwn <= combined_radius: 170 | collision_with_obstacle[j] = True 171 | self.agents[j].in_collision = True 172 | print('collision:', 'agent' + str(j), 'and obstacle' + str(obstacle.id)) 173 | 174 | return collision_with_obstacle, collision_with_agent 175 | 176 | def _check_which_agents_done(self): 177 | at_goal_condition = np.array([a.is_at_goal for a in self.agents]) 178 | ran_out_of_time_condition = np.array([a.ran_out_of_time for a in self.agents]) 179 | in_collision_condition = np.array([a.in_collision for a in self.agents]) 180 | which_agents_done = np.logical_or.reduce((at_goal_condition, ran_out_of_time_condition, in_collision_condition)) 181 | for agent_index, agent in enumerate(self.agents): 182 | agent.is_done = which_agents_done[agent_index] 183 | 184 | if Config.EVALUATE_MODE: 185 | # Episode ends when every agent is done 186 | game_over = np.all(which_agents_done) 187 | elif Config.TRAIN_SINGLE_AGENT: 188 | # Episode ends when ego agent is done 189 | game_over = which_agents_done[0] 190 | else: 191 | # Episode is done when all *learning* agents are done 192 | learning_agent_inds = [i for i in range(len(self.agents)) if self.agents[i].policy.is_still_learning] 193 | game_over = np.all(which_agents_done[learning_agent_inds]) 194 | 195 | return which_agents_done, game_over 196 | 197 | def _update_after_action(self): 198 | for i, agent in enumerate(self.agents): 199 | if agent.is_at_goal: continue 200 | agent.update_after_action() 201 | 202 | def _get_obs(self): 203 | """ Update the map now that agents have moved, have each agent sense the world, and fill in their observations 204 | Returns: 205 | observation (list): for each agent, a dictionary observation. 206 | """ 207 | # Agents collect a reading from their map-based sensors 208 | for i, agent in enumerate(self.agents): 209 | agent.sense(self.agents, i) 210 | # Agents fill in their element of the multiagent observation vector 211 | for i, agent in enumerate(self.agents): 212 | self.observation[i] = agent.get_observation_dict() 213 | 214 | return self.observation 215 | -------------------------------------------------------------------------------- /mamp/sensors/OtherAgentsStatesSensor.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from mamp.sensors.Sensor import Sensor 3 | from mamp.envs import Config 4 | from mamp.util import compute_time_to_impact, vec2_l2_norm 5 | import operator 6 | 7 | class OtherAgentsStatesSensor(Sensor): 8 | """ A dense matrix of relative states of other agents (e.g., their positions, vel, radii) 9 | 10 | :param max_num_other_agents_observed: (int) only can observe up to this many agents (the closest ones) 11 | :param agent_sorting_method: (str) definition of closeness in words (one of ['closest_last', 'closest_first', 'time_to_impact']) 12 | 13 | """ 14 | def __init__(self, max_num_other_agents_observed=Config.MAX_NUM_OTHER_AGENTS_OBSERVED, agent_sorting_method=Config.AGENT_SORTING_METHOD): 15 | Sensor.__init__(self) 16 | self.name = 'other_agents_states' 17 | self.max_num_other_agents_observed = max_num_other_agents_observed 18 | self.agent_sorting_method = agent_sorting_method 19 | 20 | def get_clipped_sorted_inds(self, sorting_criteria): 21 | """ Determine the closest N agents using the desired sorting criteria 22 | 23 | Args: 24 | sorting_criteria (str): how to sort the list of agents (one of ['closest_last', 'closest_first', 'time_to_impact']). 25 | See journal paper. 26 | 27 | Returns: 28 | clipped_sorted_inds (list): indices of the "closest" max_num_other_agents_observed 29 | agents sorted by "closeness" ("close" defined by sorting criteria), 30 | 31 | """ 32 | 33 | # Grab first N agents (where N=Config.MAX_NUM_OTHER_AGENTS_OBSERVED) 34 | if self.agent_sorting_method in ['closest_last', 'closest_first']: 35 | # where "first" == closest 36 | sorted_sorting_criteria = sorted(sorting_criteria, key = lambda x: (x[1], x[2])) 37 | elif self.agent_sorting_method in ['time_to_impact']: 38 | # where "first" == lowest time-to-impact 39 | sorted_sorting_criteria = sorted(sorting_criteria, key = lambda x: (-x[3], -x[1], x[2])) 40 | clipped_sorting_criteria = sorted_sorting_criteria[:self.max_num_other_agents_observed] 41 | 42 | # Then sort those N agents by the preferred ordering scheme 43 | if self.agent_sorting_method == "closest_last": 44 | # sort by inverse distance away, then by lateral position 45 | sorted_dists = sorted(clipped_sorting_criteria, key = lambda x: (-x[1], x[2])) 46 | elif self.agent_sorting_method == "closest_first": 47 | # sort by distance away, then by lateral position 48 | sorted_dists = sorted(clipped_sorting_criteria, key = lambda x: (x[1], x[2])) 49 | elif self.agent_sorting_method == "time_to_impact": 50 | # sort by time_to_impact, break ties by distance away, then by lateral position (e.g. in case inf TTC) 51 | sorted_dists = sorted(clipped_sorting_criteria, key = lambda x: (-x[3], -x[1], x[2])) 52 | else: 53 | raise ValueError("Did not supply proper self.agent_sorting_method in Agent.py.") 54 | 55 | clipped_sorted_inds = [x[0] for x in sorted_dists] 56 | return clipped_sorted_inds 57 | 58 | 59 | def sense(self, agents, agent_index): 60 | """ Go through each agent in the environment, and compute its relative position, vel, etc. and put into an array 61 | 62 | This is a denser measurement of other agents' states vs. a LaserScan or OccupancyGrid 63 | 64 | Args: 65 | agents (list): all :class:`~gym_collision_avoidance.envs.agent.Agent` in the environment 66 | agent_index (int): index of this agent (the one with this sensor) in :code:`agents` 67 | top_down_map (2D np array): binary image with 0 if that pixel is free space, 1 if occupied (not used!) 68 | 69 | Returns: 70 | other_agents_states (np array): (max_num_other_agents_observed x 7) the 7 states about each other agent, :code:`[p_parallel_ego_frame, p_orthog_ego_frame, v_parallel_ego_frame, v_orthog_ego_frame, other_agent.radius, combined_radius, dist_2_other]` 71 | 72 | """ 73 | host_agent = agents[agent_index] 74 | other_agents_states = [] 75 | other_agent_count = 0 76 | for other_agent in agents: 77 | if other_agent.id == host_agent.id: 78 | continue 79 | # project other elements onto the new reference frame 80 | rel_pos_to_other_global_frame = other_agent.pos_global_frame - host_agent.pos_global_frame 81 | dist_2_other = np.linalg.norm(rel_pos_to_other_global_frame) - host_agent.radius - other_agent.radius 82 | 83 | if dist_2_other > host_agent.view_radius: continue 84 | 85 | other_obs = np.array([other_agent.group, 86 | other_agent.pos_global_frame[0], 87 | other_agent.pos_global_frame[1], 88 | dist_2_other]) 89 | 90 | if other_agent_count == 0: 91 | host_agent.other_agent_states[:] = other_obs 92 | 93 | other_agents_states.append(other_obs) 94 | other_agent_count += 1 95 | 96 | host_agent.num_other_agents_observed = other_agent_count 97 | other_agents_states_array = np.reshape(other_agents_states, (-1, 4)) 98 | return other_agents_states_array 99 | 100 | def sense1(self, agents, agent_index, top_down_map=None): 101 | """ Go through each agent in the environment, and compute its relative position, vel, etc. and put into an array 102 | 103 | This is a denser measurement of other agents' states vs. a LaserScan or OccupancyGrid 104 | 105 | Args: 106 | agents (list): all :class:`~gym_collision_avoidance.envs.agent.Agent` in the environment 107 | agent_index (int): index of this agent (the one with this sensor) in :code:`agents` 108 | top_down_map (2D np array): binary image with 0 if that pixel is free space, 1 if occupied (not used!) 109 | 110 | Returns: 111 | other_agents_states (np array): (max_num_other_agents_observed x 7) the 7 states about each other agent, :code:`[p_parallel_ego_frame, p_orthog_ego_frame, v_parallel_ego_frame, v_orthog_ego_frame, other_agent.radius, combined_radius, dist_2_other]` 112 | 113 | """ 114 | host_agent = agents[agent_index] 115 | other_agent_dists = {} 116 | sorted_pairs = sorted(other_agent_dists.items(), key=operator.itemgetter(1)) 117 | 118 | sorting_criteria = [] 119 | for i, other_agent in enumerate(agents): 120 | if other_agent.id == host_agent.id: 121 | continue 122 | # project other elements onto the new reference frame 123 | rel_pos_to_other_global_frame = other_agent.pos_global_frame - host_agent.pos_global_frame 124 | p_parallel_ego_frame = np.dot(rel_pos_to_other_global_frame, host_agent.ref_prll) 125 | p_orthog_ego_frame = np.dot(rel_pos_to_other_global_frame, host_agent.ref_orth) 126 | dist_between_agent_centers = vec2_l2_norm(rel_pos_to_other_global_frame) 127 | dist_2_other = dist_between_agent_centers - host_agent.radius - other_agent.radius 128 | combined_radius = host_agent.radius + other_agent.radius 129 | 130 | if dist_between_agent_centers > Config.SENSING_HORIZON: 131 | # print("Agent too far away") 132 | continue 133 | 134 | if self.agent_sorting_method != "time_to_impact": 135 | time_to_impact = None 136 | else: 137 | time_to_impact = compute_time_to_impact(host_agent.pos_global_frame, 138 | other_agent.pos_global_frame, 139 | host_agent.vel_global_frame, 140 | other_agent.vel_global_frame, 141 | combined_radius) 142 | 143 | sorting_criteria.append([i, round(dist_2_other,2), p_orthog_ego_frame, time_to_impact]) 144 | 145 | clipped_sorted_inds = self.get_clipped_sorted_inds(sorting_criteria) 146 | clipped_sorted_agents = [agents[i] for i in clipped_sorted_inds] 147 | 148 | other_agents_states = np.zeros((Config.MAX_NUM_OTHER_AGENTS_OBSERVED, Config.OTHER_AGENT_OBSERVATION_LENGTH)) 149 | other_agent_count = 0 150 | for other_agent in clipped_sorted_agents: 151 | if other_agent.id == host_agent.id: 152 | continue 153 | 154 | rel_pos_to_other_global_frame_exp = other_agent.goal_global_frame - host_agent.goal_global_frame 155 | # project other elements onto the new reference frame 156 | rel_pos_to_other_global_frame = other_agent.pos_global_frame - host_agent.pos_global_frame 157 | dist_2_other = np.linalg.norm(rel_pos_to_other_global_frame) - host_agent.radius - other_agent.radius 158 | rel_pos_diff = rel_pos_to_other_global_frame - rel_pos_to_other_global_frame_exp 159 | 160 | p_parallel_ego_frame = np.dot(rel_pos_to_other_global_frame, host_agent.ref_prll) 161 | p_orthog_ego_frame = np.dot(rel_pos_to_other_global_frame, host_agent.ref_orth) 162 | v_parallel_ego_frame = np.dot(other_agent.vel_global_frame, host_agent.ref_prll) 163 | v_orthog_ego_frame = np.dot(other_agent.vel_global_frame, host_agent.ref_orth) 164 | combined_radius = host_agent.radius + other_agent.radius 165 | 166 | other_obs = np.array([rel_pos_diff[0], 167 | rel_pos_diff[1], 168 | p_parallel_ego_frame, 169 | p_orthog_ego_frame, 170 | v_parallel_ego_frame, 171 | v_orthog_ego_frame, 172 | other_agent.radius, 173 | combined_radius, 174 | dist_2_other]) 175 | 176 | if other_agent_count == 0: 177 | host_agent.other_agent_obs[:] = other_obs 178 | 179 | other_agents_states[other_agent_count,:] = other_obs 180 | other_agent_count += 1 181 | 182 | host_agent.num_other_agents_observed = other_agent_count 183 | return other_agents_states 184 | -------------------------------------------------------------------------------- /mamp/policies/pcaPolicy/dubinsmaneuver2d.py: -------------------------------------------------------------------------------- 1 | """ 2 | @ Revise: Gang Xu 3 | @ Date: 2021.8.13 4 | @ Details: dubins path planner 5 | @ reference: https://github.com/phanikiran1169/RRTstar_3D-Dubins.git 6 | """ 7 | 8 | 9 | import math 10 | import matplotlib.pyplot as plt 11 | import numpy as np 12 | 13 | 14 | class DubinsManeuver(object): 15 | def __init__(self, qi, qf, r_min): 16 | self.qi = qi 17 | self.qf = qf 18 | self.r_min = r_min 19 | self.t = -1.0 20 | self.p = -1.0 21 | self.q = -1.0 22 | self.px = [] 23 | self.py = [] 24 | self.pyaw = [] 25 | self.path = [] 26 | self.mode = None 27 | self.length = -1.0 28 | self.sampling_size = np.deg2rad(6.0) 29 | 30 | 31 | def mod2pi(theta): # to 0-2*pi 32 | return theta - 2.0 * math.pi * math.floor(theta / 2.0 / math.pi) 33 | 34 | 35 | def pi_2_pi(angle): # to -pi-pi 36 | return (angle + math.pi) % (2 * math.pi) - math.pi 37 | 38 | 39 | def LSL(alpha, beta, d): 40 | sa = math.sin(alpha) 41 | sb = math.sin(beta) 42 | ca = math.cos(alpha) 43 | cb = math.cos(beta) 44 | c_ab = math.cos(alpha - beta) 45 | 46 | tmp0 = d + sa - sb 47 | 48 | mode = ["L", "S", "L"] 49 | p_squared = 2 + (d * d) - (2 * c_ab) + (2 * d * (sa - sb)) 50 | if p_squared < 0: 51 | return None, None, None, mode 52 | tmp1 = math.atan2((cb - ca), tmp0) 53 | t = mod2pi(-alpha + tmp1) 54 | p = math.sqrt(p_squared) 55 | q = mod2pi(beta - tmp1) 56 | 57 | return t, p, q, mode 58 | 59 | 60 | def RSR(alpha, beta, d): 61 | sa = math.sin(alpha) 62 | sb = math.sin(beta) 63 | ca = math.cos(alpha) 64 | cb = math.cos(beta) 65 | c_ab = math.cos(alpha - beta) 66 | 67 | tmp0 = d - sa + sb 68 | mode = ["R", "S", "R"] 69 | p_squared = 2 + (d * d) - (2 * c_ab) + (2 * d * (sb - sa)) 70 | if p_squared < 0: 71 | return None, None, None, mode 72 | tmp1 = math.atan2((ca - cb), tmp0) 73 | t = mod2pi(alpha - tmp1) 74 | p = math.sqrt(p_squared) 75 | q = mod2pi(-beta + tmp1) 76 | 77 | return t, p, q, mode 78 | 79 | 80 | def LSR(alpha, beta, d): 81 | sa = math.sin(alpha) 82 | sb = math.sin(beta) 83 | ca = math.cos(alpha) 84 | cb = math.cos(beta) 85 | c_ab = math.cos(alpha - beta) 86 | 87 | p_squared = -2 + (d * d) + (2 * c_ab) + (2 * d * (sa + sb)) 88 | mode = ["L", "S", "R"] 89 | if p_squared < 0: 90 | return None, None, None, mode 91 | p = math.sqrt(p_squared) 92 | tmp2 = math.atan2((-ca - cb), (d + sa + sb)) - math.atan2(-2.0, p) 93 | t = mod2pi(-alpha + tmp2) 94 | q = mod2pi(-mod2pi(beta) + tmp2) 95 | 96 | return t, p, q, mode 97 | 98 | 99 | def RSL(alpha, beta, d): 100 | sa = math.sin(alpha) 101 | sb = math.sin(beta) 102 | ca = math.cos(alpha) 103 | cb = math.cos(beta) 104 | c_ab = math.cos(alpha - beta) 105 | 106 | p_squared = (d * d) - 2 + (2 * c_ab) - (2 * d * (sa + sb)) 107 | mode = ["R", "S", "L"] 108 | if p_squared < 0: 109 | return None, None, None, mode 110 | p = math.sqrt(p_squared) 111 | tmp2 = math.atan2((ca + cb), (d - sa - sb)) - math.atan2(2.0, p) 112 | t = mod2pi(alpha - tmp2) 113 | q = mod2pi(beta - tmp2) 114 | 115 | return t, p, q, mode 116 | 117 | 118 | def RLR(alpha, beta, d): 119 | sa = math.sin(alpha) 120 | sb = math.sin(beta) 121 | ca = math.cos(alpha) 122 | cb = math.cos(beta) 123 | c_ab = math.cos(alpha - beta) 124 | 125 | mode = ["R", "L", "R"] 126 | tmp_rlr = (6.0 - d * d + 2.0 * c_ab + 2.0 * d * (sa - sb)) / 8.0 127 | if abs(tmp_rlr) > 1.0: 128 | return None, None, None, mode 129 | 130 | p = mod2pi(2 * math.pi - math.acos(tmp_rlr)) 131 | t = mod2pi(alpha - math.atan2(ca - cb, d - sa + sb) + mod2pi(p / 2.0)) 132 | q = mod2pi(alpha - beta - t + mod2pi(p)) 133 | return t, p, q, mode 134 | 135 | 136 | def LRL(alpha, beta, d): 137 | sa = math.sin(alpha) 138 | sb = math.sin(beta) 139 | ca = math.cos(alpha) 140 | cb = math.cos(beta) 141 | c_ab = math.cos(alpha - beta) 142 | 143 | mode = ["L", "R", "L"] 144 | tmp_lrl = (6. - d * d + 2 * c_ab + 2 * d * (- sa + sb)) / 8. 145 | if abs(tmp_lrl) > 1: 146 | return None, None, None, mode 147 | p = mod2pi(2 * math.pi - math.acos(tmp_lrl)) 148 | t = mod2pi(-alpha - math.atan2(ca - cb, d + sa - sb) + p / 2.) 149 | q = mod2pi(mod2pi(beta) - alpha - t + mod2pi(p)) 150 | 151 | return t, p, q, mode 152 | 153 | 154 | def dubins_path_planning_from_origin(ex, ey, syaw, eyaw, c): 155 | # nomalize 156 | dx = ex 157 | dy = ey 158 | D = math.sqrt(dx ** 2.0 + dy ** 2.0) 159 | d = D / c 160 | 161 | theta = mod2pi(math.atan2(dy, dx)) 162 | alpha = mod2pi(syaw - theta) 163 | beta = mod2pi(eyaw - theta) 164 | 165 | planners = [LSL, RSR, LSR, RSL, RLR, LRL] 166 | 167 | bcost = float("inf") 168 | bt, bp, bq, bmode = None, None, None, None 169 | 170 | for planner in planners: 171 | t, p, q, mode = planner(alpha, beta, d) 172 | if t is None: 173 | continue 174 | 175 | cost = c*(abs(t) + abs(p) + abs(q)) 176 | if bcost > cost: 177 | bt, bp, bq, bmode = t, p, q, mode 178 | bcost = cost 179 | 180 | px, py, pyaw = generate_course([bt, bp, bq], bmode, c) 181 | 182 | return px, py, pyaw, bmode, bcost, bt, bp, bq 183 | 184 | 185 | def dubins_path_planning(start_pos, end_pos, c): 186 | """ 187 | Dubins path plannner 188 | 189 | input:start_pos, end_pos, c 190 | start_pos[0] x position of start point [m] 191 | start_pos[1] y position of start point [m] 192 | start_pos[2] yaw angle of start point [rad] 193 | end_pos[0] x position of end point [m] 194 | end_pos[1] y position of end point [m] 195 | end_pos[2] yaw angle of end point [rad] 196 | c radius [m] 197 | 198 | output: maneuver 199 | maneuver.t the first segment curve of dubins 200 | maneuver.p the second segment line of dubins 201 | maneuver.q the third segment curve of dubins 202 | maneuver.px x position sets [m] 203 | maneuver.py y position sets [m] 204 | maneuver.pyaw heading angle sets [rad] 205 | maneuver.length length of dubins 206 | maneuver.mode mode of dubins 207 | """ 208 | maneuver = DubinsManeuver(start_pos, end_pos, c) 209 | sx, sy = start_pos[0], start_pos[1] 210 | ex, ey = end_pos[0], end_pos[1] 211 | syaw, eyaw = start_pos[2], end_pos[2] 212 | 213 | ex = ex - sx 214 | ey = ey - sy 215 | 216 | lpx, lpy, lpyaw, mode, clen, t, p, q = dubins_path_planning_from_origin(ex, ey, syaw, eyaw, c) 217 | 218 | px = [math.cos(-syaw) * x + math.sin(-syaw) * y + sx for x, y in zip(lpx, lpy)] 219 | py = [-math.sin(-syaw) * x + math.cos(-syaw) * y + sy for x, y in zip(lpx, lpy)] 220 | pyaw = [pi_2_pi(iyaw + syaw) for iyaw in lpyaw] 221 | maneuver.t, maneuver.p, maneuver.q, maneuver.mode = t, p, q, mode 222 | maneuver.px, maneuver.py, maneuver.pyaw, maneuver.length = px, py, pyaw, clen 223 | for index in range(len(maneuver.px)): 224 | maneuver.path.append([maneuver.px[index], maneuver.py[index], maneuver.pyaw[index]]) 225 | 226 | return maneuver 227 | 228 | 229 | def generate_course(length, mode, c): 230 | px = [0.0] 231 | py = [0.0] 232 | pyaw = [0.0] 233 | 234 | for m, l in zip(mode, length): 235 | pd = 0.0 236 | if m == "S": 237 | d = 1.0 / c 238 | else: # turning course 239 | d = np.deg2rad(6.0) 240 | 241 | while pd < abs(l - d): 242 | px.append(px[-1] + d * c * math.cos(pyaw[-1])) 243 | py.append(py[-1] + d * c * math.sin(pyaw[-1])) 244 | 245 | if m == "L": # left turn 246 | pyaw.append(pyaw[-1] + d) 247 | elif m == "S": # Straight 248 | pyaw.append(pyaw[-1]) 249 | elif m == "R": # right turn 250 | pyaw.append(pyaw[-1] - d) 251 | pd += d 252 | 253 | d = l - pd 254 | px.append(px[-1] + d * c * math.cos(pyaw[-1])) 255 | py.append(py[-1] + d * c * math.sin(pyaw[-1])) 256 | 257 | if m == "L": # left turn 258 | pyaw.append(pyaw[-1] + d) 259 | elif m == "S": # Straight 260 | pyaw.append(pyaw[-1]) 261 | elif m == "R": # right turn 262 | pyaw.append(pyaw[-1] - d) 263 | pd += d 264 | 265 | return px, py, pyaw 266 | 267 | 268 | def get_coordinates(maneuver, offset): 269 | noffset = offset / maneuver.r_min 270 | qi = [0., 0., maneuver.qi[2]] 271 | 272 | l1 = maneuver.t 273 | l2 = maneuver.p 274 | q1 = get_position_in_segment(l1, qi, maneuver.mode[0]) # Final do segmento 1 275 | q2 = get_position_in_segment(l2, q1, maneuver.mode[1]) # Final do segmento 2 276 | 277 | if noffset < l1: 278 | q = get_position_in_segment(noffset, qi, maneuver.mode[0]) 279 | elif noffset < (l1 + l2): 280 | q = get_position_in_segment(noffset - l1, q1, maneuver.mode[1]) 281 | else: 282 | q = get_position_in_segment(noffset - l1 - l2, q2, maneuver.mode[2]) 283 | 284 | q[0] = q[0] * maneuver.r_min + qi[0] 285 | q[1] = q[1] * maneuver.r_min + qi[1] 286 | q[2] = mod2pi(q[2]) 287 | 288 | return q 289 | 290 | 291 | def get_position_in_segment(offset, qi, mode): 292 | q = [0.0, 0.0, 0.0] 293 | if mode == 'L': 294 | q[0] = qi[0] + math.sin(qi[2] + offset) - math.sin(qi[2]) 295 | q[1] = qi[1] - math.cos(qi[2] + offset) + math.cos(qi[2]) 296 | q[2] = qi[2] + offset 297 | elif mode == 'R': 298 | q[0] = qi[0] - math.sin(qi[2] - offset) + math.sin(qi[2]) 299 | q[1] = qi[1] + math.cos(qi[2] - offset) - math.cos(qi[2]) 300 | q[2] = qi[2] - offset 301 | elif mode == 'S': 302 | q[0] = qi[0] + math.cos(qi[2]) * offset 303 | q[1] = qi[1] + math.sin(qi[2]) * offset 304 | q[2] = qi[2] 305 | return q 306 | 307 | 308 | def get_sampling_points(maneuver, sampling_size=0.1): 309 | points = [] 310 | for offset in np.arange(0.0, maneuver.length+sampling_size, sampling_size): 311 | points.append(get_coordinates(maneuver, offset)) 312 | return points 313 | 314 | 315 | def plot_arrow(x, y, yaw, length=1.0, width=0.5, fc="r", ec="k"): # pragma: no cover 316 | """ 317 | Plot arrow 318 | """ 319 | 320 | if not isinstance(x, float): 321 | for (ix, iy, iyaw) in zip(x, y, yaw): 322 | plot_arrow(ix, iy, iyaw) 323 | else: 324 | plt.arrow(x, y, length * math.cos(yaw), length * math.sin(yaw), 325 | fc=fc, ec=ec, head_width=width, head_length=width) 326 | plt.plot(x, y) 327 | 328 | 329 | if __name__ == '__main__': 330 | print("Dubins Path Planner Start!!") 331 | 332 | test_qi = [[0.0, 5.0, np.deg2rad(180.0)], [10.0, 5.0, np.deg2rad(0.0)]] # start_x, start_y, start_yaw 333 | test_qf = [[10.0, 5.0, np.deg2rad(180.0)], [0.0, 5.0, np.deg2rad(0.0)]] # end_x, end_y, end_yaw 334 | 335 | rmin = 0.5 336 | 337 | test_maneuver = dubins_path_planning(test_qi[0], test_qf[0], rmin) 338 | test_maneuver1 = dubins_path_planning(test_qi[1], test_qf[1], rmin) 339 | 340 | path = [] 341 | for i in range(len(test_maneuver.px)): 342 | path.append([test_maneuver.px[i], test_maneuver.py[i], test_maneuver.pyaw[i]]) 343 | print(len(path), test_maneuver.length, test_maneuver.path) 344 | print(len(path), test_maneuver.length, test_maneuver1.path) 345 | 346 | plt.plot(test_maneuver.px, test_maneuver.py, label="(0->10),(180°->180°) " + "".join(test_maneuver.mode)) 347 | plt.plot(test_maneuver1.px, test_maneuver1.py, label="(10->0),(0°->0°) " + "".join(test_maneuver1.mode)) 348 | 349 | 350 | # plotting 351 | plot_arrow(test_qi[0][0], test_qi[0][1], test_qi[0][2]) 352 | plot_arrow(test_qf[0][0], test_qf[0][1], test_qf[0][2]) 353 | plot_arrow(test_qi[1][0], test_qi[1][1], test_qi[1][2]) 354 | plot_arrow(test_qf[1][0], test_qf[1][1], test_qf[1][2]) 355 | 356 | plt.legend() 357 | plt.grid(True) 358 | plt.axis("equal") 359 | plt.show() 360 | -------------------------------------------------------------------------------- /mamp/policies/pcaPolicy/rvoDubinsPolicy.py: -------------------------------------------------------------------------------- 1 | import time 2 | import numpy as np 3 | from math import sqrt, sin, cos, atan2, pi, acos 4 | from itertools import product 5 | 6 | from mamp.envs import Config 7 | from mamp.policies.policy import Policy 8 | from mamp.policies.pcaPolicy import dubinsmaneuver2d 9 | from mamp.util import sqr, l2norm, norm, is_parallel, absSq, mod2pi, pi_2_pi, get_phi, angle_2_vectors, is_intersect 10 | 11 | 12 | class RVODubinsPolicy(Policy): 13 | def __init__(self): 14 | Policy.__init__(self, str="RVODubinsPolicy") 15 | self.type = "internal" 16 | self.now_goal = None 17 | 18 | def find_next_action(self, obs, dict_comm, agent, kdTree): 19 | ts = time.time() 20 | self.now_goal = agent.goal_global_frame 21 | v_pref = compute_v_pref(agent) 22 | vA = agent.vel_global_frame 23 | pA = agent.pos_global_frame 24 | if l2norm(vA, [0, 0, 0]) <= 1e-5: 25 | vA_post = 0.2 * v_pref 26 | te = time.time() 27 | cost_step = te - ts 28 | agent.total_time += cost_step 29 | action = to_unicycle(vA_post, agent) 30 | theta = 0.0 31 | else: 32 | agent_rad = agent.radius + 0.05 33 | computeNeighbors(agent, kdTree) 34 | RVO_BA_all = [] 35 | for obj in agent.neighbors: 36 | obj = obj[0] 37 | pB = obj.pos_global_frame 38 | if obj.is_at_goal: 39 | transl_vB_vA = pA 40 | else: 41 | vB = obj.vel_global_frame 42 | transl_vB_vA = pA + 0.5 * (vB + vA) # Use RVO. 43 | obj_rad = obj.radius + 0.05 44 | 45 | RVO_BA = [transl_vB_vA, pA, pB, obj_rad + agent_rad] 46 | RVO_BA_all.append(RVO_BA) 47 | vA_post = intersect(v_pref, RVO_BA_all, agent) 48 | te = time.time() 49 | cost_step = te - ts 50 | agent.total_time += cost_step 51 | action = to_unicycle(vA_post, agent) 52 | theta = angle_2_vectors(vA, vA_post) 53 | agent.vel_global_unicycle[0] = round(1.0 * action[0], 5) 54 | agent.vel_global_unicycle[1] = round(0.22 * 0.5 * action[1] / Config.DT, 5) 55 | dist = l2norm(agent.pos_global_frame, agent.goal_global_frame) 56 | if theta > agent.max_heading_change: 57 | print('agent' + str(agent.id), len(agent.neighbors), action, '终点距离:', dist, 'theta:', theta) 58 | else: 59 | print('agent' + str(agent.id), len(agent.neighbors), action, '终点距离:', dist) 60 | 61 | return action 62 | 63 | 64 | def update_dubins(agent): 65 | dis = l2norm(agent.pos_global_frame, agent.dubins_now_goal) 66 | sampling_size = agent.dubins_sampling_size 67 | if dis <= sampling_size: 68 | if agent.dubins_path: 69 | agent.dubins_now_goal = np.array(agent.dubins_path.pop()[:2], dtype='float64') 70 | else: 71 | agent.dubins_now_goal = agent.goal_global_frame 72 | 73 | 74 | def compute_dubins(agent): 75 | qi = np.hstack((agent.pos_global_frame, agent.heading_global_frame)) 76 | qf = np.hstack((agent.goal_global_frame, agent.goal_heading_frame)) 77 | rmin, pitchlims = agent.turning_radius, agent.pitchlims 78 | maneuver = dubinsmaneuver2d.dubins_path_planning(qi, qf, rmin) 79 | dubins_path = maneuver.path.copy() 80 | agent.dubins_sampling_size = maneuver.sampling_size 81 | desire_length = maneuver.length 82 | points_num = len(dubins_path) 83 | path = [] 84 | for i in range(points_num): 85 | path.append((dubins_path.pop())) 86 | return path, desire_length, points_num 87 | 88 | 89 | def is_parallel_neighbor(agent): 90 | range_para = 0.15 91 | max_parallel_steps = int((agent.radius + agent.neighbors[0][0].radius + 0.2) / (agent.pref_speed * agent.timeStep)) 92 | if len(agent.is_parallel_neighbor) < max_parallel_steps and len(agent.neighbors) > 0: 93 | agent.is_parallel_neighbor.append((agent.neighbors[0][0].id, agent.neighbors[0][1])) 94 | return False 95 | else: 96 | agent.is_parallel_neighbor.pop(0) 97 | agent.is_parallel_neighbor.append((agent.neighbors[0][0].id, agent.neighbors[0][1])) 98 | nei = agent.is_parallel_neighbor 99 | is_same_id = True 100 | is_in_range = True 101 | for i in range(len(nei) - 1): 102 | if nei[0][0] != nei[i + 1][0]: 103 | is_same_id = False 104 | dis = abs(sqrt(nei[0][1]) - sqrt(nei[i + 1][1])) 105 | if dis > range_para: 106 | is_in_range = False 107 | if is_same_id and is_in_range: 108 | return True 109 | else: 110 | return False 111 | 112 | 113 | def dubins_path_node_pop(agent): 114 | agent.dubins_last_goal = np.array(agent.dubins_path.pop()[:3], dtype='float64') 115 | agent.dubins_last_goal = np.array(agent.dubins_path.pop()[:3], dtype='float64') 116 | agent.dubins_last_goal = np.array(agent.dubins_path.pop()[:3], dtype='float64') 117 | agent.dubins_last_goal = np.array(agent.dubins_path.pop()[:3], dtype='float64') 118 | 119 | 120 | def compute_v_pref(agent): 121 | """ 122 | is_parallel(vA, v_pref): —— Whether to leave Dubins trajectory as collision avoidance. 123 | dis_goal <= k: —— Regard as obstacles-free when the distance is less than k. 124 | dis < 6 * sampling_size: —— Follow the current Dubins path when not moving away from the current Dubins path. 125 | theta >= np.deg2rad(100): —— Avoid the agent moving away from the goal position after update Dubins path. 126 | """ 127 | pA = agent.pos_global_frame 128 | pG = agent.goal_global_frame 129 | dist_goal = l2norm(pA, pG) 130 | k = 3.0 * agent.turning_radius 131 | if not agent.is_use_dubins: # first 132 | agent.is_use_dubins = True 133 | dubins_path, desire_length, points_num = compute_dubins(agent) 134 | agent.dubins_path = dubins_path 135 | dubins_path_node_pop(agent) 136 | agent.dubins_now_goal = np.array(agent.dubins_path.pop()[:2], dtype='float64') 137 | dif_x = agent.dubins_now_goal - pA 138 | else: 139 | update_dubins(agent) 140 | vA = np.array(agent.vel_global_frame) 141 | v_pref = agent.v_pref 142 | dis = l2norm(pA, agent.dubins_now_goal) 143 | max_size = round(6 * agent.dubins_sampling_size, 5) 144 | pApG = pG - pA 145 | theta = angle_2_vectors(vA, pApG) 146 | deg135 = np.deg2rad(135) 147 | if ((is_parallel(vA, v_pref) or dist_goal <= k) and dis < max_size) or (theta >= deg135): 148 | update_dubins(agent) 149 | if agent.dubins_path: 150 | dif_x = agent.dubins_now_goal - pA 151 | else: 152 | dif_x = pG - pA 153 | else: 154 | dubins_path, length, points_num = compute_dubins(agent) 155 | agent.dubins_path = dubins_path 156 | dubins_path_node_pop(agent) 157 | agent.dubins_now_goal = np.array(agent.dubins_path.pop()[:2], dtype='float64') 158 | dif_x = agent.dubins_now_goal - pA 159 | 160 | norm_dif_x = dif_x * agent.pref_speed / l2norm(dif_x, [0, 0]) 161 | v_pref = np.array(norm_dif_x) 162 | if dist_goal < Config.NEAR_GOAL_THRESHOLD: 163 | v_pref[0] = 0.0 164 | v_pref[1] = 0.0 165 | agent.v_pref = v_pref 166 | v_pref = np.array([round(v_pref[0], 5), round(v_pref[1], 5)]) 167 | return v_pref 168 | 169 | 170 | def intersect(v_pref, RVO_BA_all, agent): 171 | norm_v = np.linalg.norm(v_pref) 172 | suitable_V = [] 173 | unsuitable_V = [] 174 | 175 | Theta = np.arange(0, 2 * pi, step=pi / 32) 176 | RAD = np.linspace(0.02, 1.0, num=5) 177 | v_list = [v_pref[:]] 178 | for theta, rad in product(Theta, RAD): 179 | new_v = np.array([cos(theta), sin(theta)]) * rad * norm_v 180 | new_v = np.array([new_v[0], new_v[1]]) 181 | v_list.append(new_v) 182 | 183 | for new_v in v_list: 184 | suit = True 185 | for RVO_BA in RVO_BA_all: 186 | p_0 = RVO_BA[0] 187 | pA = RVO_BA[1] 188 | pB = RVO_BA[2] 189 | combined_radius = RVO_BA[3] 190 | v_dif = np.array(new_v + pA - p_0) # new_v-0.5*(vA+vB) or new_v-vB 191 | if is_intersect(pA, pB, combined_radius, v_dif): 192 | suit = False 193 | break 194 | if suit: 195 | suitable_V.append(new_v) 196 | else: 197 | unsuitable_V.append(new_v) 198 | 199 | # ---------------------- 200 | if suitable_V: 201 | suitable_V.sort(key=lambda v: l2norm(v, v_pref)) # Sort begin at minimum and end at maximum. 202 | vA_post = suitable_V[0] 203 | else: 204 | tc_V = dict() 205 | for unsuit_v in unsuitable_V: 206 | tc_V[tuple(unsuit_v)] = 0 207 | tc = [] # 时间 208 | for RVO_BA in RVO_BA_all: 209 | p_0 = RVO_BA[0] 210 | pA = RVO_BA[1] 211 | pB = RVO_BA[2] 212 | combined_radius = RVO_BA[3] 213 | v_dif = np.array(unsuit_v + pA - p_0) 214 | pApB = pB - pA 215 | if is_intersect(pA, pB, combined_radius, v_dif): 216 | discr = sqr(np.dot(v_dif, pApB)) - absSq(v_dif) * (absSq(pApB) - sqr(combined_radius)) 217 | if discr < 0.0: 218 | print(discr) 219 | continue 220 | tc_v = max((np.dot(v_dif, pApB) - sqrt(discr)) / absSq(v_dif), 0.0) 221 | tc.append(tc_v) 222 | if len(tc) == 0: 223 | tc = [0.0] 224 | tc_V[tuple(unsuit_v)] = min(tc) + 1e-5 225 | WT = agent.safetyFactor 226 | vA_post = min(unsuitable_V, key=lambda v: ((WT / tc_V[tuple(v)]) + l2norm(v, v_pref))) 227 | 228 | return vA_post 229 | 230 | 231 | def satisfied_constraint(agent, vCand): 232 | vA = agent.vel_global_frame 233 | costheta = np.dot(vA, vCand) / (np.linalg.norm(vA) * np.linalg.norm(vCand)) 234 | if costheta > 1.0: 235 | costheta = 1.0 236 | elif costheta < -1.0: 237 | costheta = -1.0 238 | theta = acos(costheta) # Rotational constraints. 239 | if theta <= agent.max_heading_change: 240 | return True 241 | else: 242 | return False 243 | 244 | 245 | def select_right_side(v_list, vA): 246 | opt_v = [v_list[0]] 247 | i = 1 248 | while True: 249 | if abs(l2norm(v_list[0], vA) - l2norm(v_list[i], vA)) < 1e-1: 250 | opt_v.append(v_list[i]) 251 | i += 1 252 | if i == len(v_list): 253 | break 254 | else: 255 | break 256 | vA_phi_min = min(opt_v, key=lambda v: get_phi(v)) 257 | vA_phi_max = max(opt_v, key=lambda v: get_phi(v)) 258 | phi_min = get_phi(vA_phi_min) 259 | phi_max = get_phi(vA_phi_max) 260 | if abs(phi_max - phi_min) <= pi: 261 | vA_opti = vA_phi_min 262 | else: 263 | vA_opti = vA_phi_max 264 | return vA_opti 265 | 266 | 267 | def computeNeighbors(agent, kdTree): 268 | agent.neighbors.clear() 269 | 270 | # Check obstacle neighbors. 271 | rangeSq = agent.neighborDist ** 2 272 | if len(agent.neighbors) != agent.maxNeighbors: 273 | rangeSq = 1.0 * agent.neighborDist ** 2 274 | kdTree.computeObstacleNeighbors(agent, rangeSq) 275 | 276 | if agent.in_collision: 277 | return 278 | 279 | # Check other agents. 280 | if len(agent.neighbors) != agent.maxNeighbors: 281 | rangeSq = agent.neighborDist ** 2 282 | kdTree.computeAgentNeighbors(agent, rangeSq) 283 | 284 | 285 | def to_unicycle(vA_post, agent): 286 | vA_post = np.array(vA_post) 287 | norm_vA = norm(vA_post) 288 | yaw_next = mod2pi(atan2(vA_post[1], vA_post[0])) 289 | yaw_current = mod2pi(agent.heading_global_frame) 290 | delta_theta = yaw_next - yaw_current 291 | delta_theta = pi_2_pi(delta_theta) 292 | if delta_theta < -pi: 293 | delta_theta = delta_theta + 2 * pi 294 | if delta_theta > pi: 295 | delta_theta = delta_theta - 2 * pi 296 | if delta_theta >= 1.0: 297 | delta_theta = 1.0 298 | if delta_theta <= -1: 299 | delta_theta = -1.0 300 | action = np.array([norm_vA, delta_theta]) 301 | return action 302 | -------------------------------------------------------------------------------- /mamp/policies/pcaPolicy/pcaPolicy.py: -------------------------------------------------------------------------------- 1 | import time 2 | import numpy as np 3 | from math import sqrt, sin, cos, atan2, pi, acos 4 | from itertools import product 5 | 6 | from mamp.envs import Config 7 | from mamp.policies.policy import Policy 8 | from mamp.policies.pcaPolicy import dubinsmaneuver2d 9 | from mamp.util import sqr, l2norm, norm, is_parallel, absSq, mod2pi, pi_2_pi, get_phi, angle_2_vectors, is_intersect 10 | 11 | 12 | class PCAPolicy(Policy): 13 | def __init__(self): 14 | Policy.__init__(self, str="PCAPolicy") 15 | self.type = "internal" 16 | self.now_goal = None 17 | 18 | def find_next_action(self, obs, dict_comm, agent, kdTree): 19 | ts = time.time() 20 | self.now_goal = agent.goal_global_frame 21 | v_pref = compute_v_pref(agent) 22 | vA = agent.vel_global_frame 23 | pA = agent.pos_global_frame 24 | if l2norm(vA, [0, 0, 0]) <= 1e-5: 25 | vA_post = 0.2 * v_pref 26 | te = time.time() 27 | cost_step = te - ts 28 | agent.total_time += cost_step 29 | action = to_unicycle(vA_post, agent) 30 | theta = 0.0 31 | else: 32 | agent_rad = agent.radius + 0.05 33 | computeNeighbors(agent, kdTree) 34 | RVO_BA_all = [] 35 | for obj in agent.neighbors: 36 | obj = obj[0] 37 | pB = obj.pos_global_frame 38 | if obj.is_at_goal: 39 | transl_vB_vA = pA 40 | else: 41 | vB = obj.vel_global_frame 42 | transl_vB_vA = pA + 0.5 * (vB + vA) # Use RVO. 43 | obj_rad = obj.radius + 0.05 44 | 45 | RVO_BA = [transl_vB_vA, pA, pB, obj_rad + agent_rad] 46 | RVO_BA_all.append(RVO_BA) 47 | vA_post = intersect(v_pref, RVO_BA_all, agent) 48 | te = time.time() 49 | cost_step = te - ts 50 | agent.total_time += cost_step 51 | action = to_unicycle(vA_post, agent) 52 | theta = angle_2_vectors(vA, vA_post) 53 | agent.vel_global_unicycle[0] = round(1.0 * action[0], 5) 54 | agent.vel_global_unicycle[1] = round(0.22 * 0.5 * action[1] / Config.DT, 5) 55 | dist = l2norm(agent.pos_global_frame, agent.goal_global_frame) 56 | if theta > agent.max_heading_change: 57 | print('agent' + str(agent.id), len(agent.neighbors), action, '终点距离:', dist, 'theta:', theta) 58 | else: 59 | print('agent' + str(agent.id), len(agent.neighbors), action, '终点距离:', dist) 60 | 61 | return action 62 | 63 | 64 | def update_dubins(agent): 65 | dis = l2norm(agent.pos_global_frame, agent.dubins_now_goal) 66 | sampling_size = agent.dubins_sampling_size 67 | if dis <= sampling_size: 68 | if agent.dubins_path: 69 | agent.dubins_now_goal = np.array(agent.dubins_path.pop()[:2], dtype='float64') 70 | else: 71 | agent.dubins_now_goal = agent.goal_global_frame 72 | 73 | 74 | def compute_dubins(agent): 75 | qi = np.hstack((agent.pos_global_frame, agent.heading_global_frame)) 76 | qf = np.hstack((agent.goal_global_frame, agent.goal_heading_frame)) 77 | rmin, pitchlims = agent.turning_radius, agent.pitchlims 78 | maneuver = dubinsmaneuver2d.dubins_path_planning(qi, qf, rmin) 79 | dubins_path = maneuver.path.copy() 80 | agent.dubins_sampling_size = maneuver.sampling_size 81 | desire_length = maneuver.length 82 | points_num = len(dubins_path) 83 | path = [] 84 | for i in range(points_num): 85 | path.append((dubins_path.pop())) 86 | return path, desire_length, points_num 87 | 88 | 89 | def is_parallel_neighbor(agent): 90 | range_para = 0.15 91 | max_parallel_steps = int((agent.radius + agent.neighbors[0][0].radius + 0.2) / (agent.pref_speed * agent.timeStep)) 92 | if len(agent.is_parallel_neighbor) < max_parallel_steps and len(agent.neighbors) > 0: 93 | agent.is_parallel_neighbor.append((agent.neighbors[0][0].id, agent.neighbors[0][1])) 94 | return False 95 | else: 96 | agent.is_parallel_neighbor.pop(0) 97 | agent.is_parallel_neighbor.append((agent.neighbors[0][0].id, agent.neighbors[0][1])) 98 | nei = agent.is_parallel_neighbor 99 | is_same_id = True 100 | is_in_range = True 101 | for i in range(len(nei) - 1): 102 | if nei[0][0] != nei[i + 1][0]: 103 | is_same_id = False 104 | dis = abs(sqrt(nei[0][1]) - sqrt(nei[i + 1][1])) 105 | if dis > range_para: 106 | is_in_range = False 107 | if is_same_id and is_in_range: 108 | return True 109 | else: 110 | return False 111 | 112 | 113 | def dubins_path_node_pop(agent): 114 | agent.dubins_last_goal = np.array(agent.dubins_path.pop()[:3], dtype='float64') 115 | agent.dubins_last_goal = np.array(agent.dubins_path.pop()[:3], dtype='float64') 116 | agent.dubins_last_goal = np.array(agent.dubins_path.pop()[:3], dtype='float64') 117 | agent.dubins_last_goal = np.array(agent.dubins_path.pop()[:3], dtype='float64') 118 | 119 | 120 | def compute_v_pref(agent): 121 | """ 122 | is_parallel(vA, v_pref): —— Whether to leave Dubins trajectory as collision avoidance. 123 | dis_goal <= k: —— Regard as obstacles-free when the distance is less than k. 124 | dis < 6 * sampling_size: —— Follow the current Dubins path when not moving away from the current Dubins path. 125 | theta >= np.deg2rad(100): —— Avoid the agent moving away from the goal position after update Dubins path. 126 | """ 127 | pA = agent.pos_global_frame 128 | pG = agent.goal_global_frame 129 | dist_goal = l2norm(pA, pG) 130 | k = 3.0 * agent.turning_radius 131 | if not agent.is_use_dubins: # first 132 | agent.is_use_dubins = True 133 | dubins_path, desire_length, points_num = compute_dubins(agent) 134 | agent.dubins_path = dubins_path 135 | dubins_path_node_pop(agent) 136 | agent.dubins_now_goal = np.array(agent.dubins_path.pop()[:2], dtype='float64') 137 | dif_x = agent.dubins_now_goal - pA 138 | else: 139 | update_dubins(agent) 140 | vA = np.array(agent.vel_global_frame) 141 | v_pref = agent.v_pref 142 | dis = l2norm(pA, agent.dubins_now_goal) 143 | max_size = round(6 * agent.dubins_sampling_size, 5) 144 | pApG = pG - pA 145 | theta = angle_2_vectors(vA, pApG) 146 | deg135 = np.deg2rad(135) 147 | if ((is_parallel(vA, v_pref) or dist_goal <= k) and dis < max_size) or (theta >= deg135): 148 | update_dubins(agent) 149 | if agent.dubins_path: 150 | dif_x = agent.dubins_now_goal - pA 151 | else: 152 | dif_x = pG - pA 153 | else: 154 | dubins_path, length, points_num = compute_dubins(agent) 155 | agent.dubins_path = dubins_path 156 | dubins_path_node_pop(agent) 157 | agent.dubins_now_goal = np.array(agent.dubins_path.pop()[:2], dtype='float64') 158 | dif_x = agent.dubins_now_goal - pA 159 | 160 | norm_dif_x = dif_x * agent.pref_speed / l2norm(dif_x, [0, 0]) 161 | v_pref = np.array(norm_dif_x) 162 | if dist_goal < Config.NEAR_GOAL_THRESHOLD: 163 | v_pref[0] = 0.0 164 | v_pref[1] = 0.0 165 | agent.v_pref = v_pref 166 | v_pref = np.array([v_pref[0], v_pref[1]]) 167 | return v_pref 168 | 169 | 170 | def intersect(v_pref, RVO_BA_all, agent): 171 | norm_v = agent.pref_speed 172 | suitable_V = [] 173 | unsuitable_V = [] 174 | 175 | Theta = np.arange(0, 2 * pi, step=pi / 32) 176 | RAD = np.linspace(0.02, norm_v, num=5) 177 | v_list = [v_pref[:]] 178 | for theta, rad in product(Theta, RAD): 179 | new_v = np.array([cos(theta), sin(theta)]) * rad 180 | new_v = np.array([new_v[0], new_v[1]]) 181 | v_list.append(new_v) 182 | 183 | for new_v in v_list: 184 | suit = True 185 | if len(RVO_BA_all) == 0: 186 | if not satisfied_constraint(agent, new_v): 187 | suit = False 188 | for RVO_BA in RVO_BA_all: 189 | p_0 = RVO_BA[0] 190 | pA = RVO_BA[1] 191 | pB = RVO_BA[2] 192 | combined_radius = RVO_BA[3] 193 | v_dif = np.array(new_v + pA - p_0) # new_v-0.5*(vA+vB) or new_v-vB 194 | if is_intersect(pA, pB, combined_radius, v_dif) or not satisfied_constraint(agent, new_v): 195 | suit = False 196 | break 197 | if suit: 198 | suitable_V.append(new_v) 199 | else: 200 | unsuitable_V.append(new_v) 201 | 202 | # ---------------------- 203 | if agent.pref_speed >= 0.44: # Adjust this parameter according to agents' preferred speed. 204 | delta_vopt = 1e-1 205 | else: 206 | delta_vopt = 1e-2 207 | 208 | if suitable_V: 209 | suitable_V.sort(key=lambda v: l2norm(v, v_pref)) # Sort begin at minimum and end at maximum. 210 | if len(suitable_V) > 1: 211 | vA_post = select_right_side(suitable_V, agent.vel_global_frame, epsilon=delta_vopt, opt_num=6) 212 | else: 213 | vA_post = suitable_V[0] 214 | else: 215 | tc_V = dict() 216 | for unsuit_v in unsuitable_V: 217 | tc_V[tuple(unsuit_v)] = 0 218 | tc = [] # 时间 219 | for RVO_BA in RVO_BA_all: 220 | p_0 = RVO_BA[0] 221 | pA = RVO_BA[1] 222 | pB = RVO_BA[2] 223 | combined_radius = RVO_BA[3] 224 | v_dif = np.array(unsuit_v + pA - p_0) 225 | pApB = pB - pA 226 | if is_intersect(pA, pB, combined_radius, v_dif) and satisfied_constraint(agent, unsuit_v): 227 | discr = sqr(np.dot(v_dif, pApB)) - absSq(v_dif) * (absSq(pApB) - sqr(combined_radius)) 228 | if discr < 0.0: 229 | print(discr) 230 | continue 231 | tc_v = max((np.dot(v_dif, pApB) - sqrt(discr)) / absSq(v_dif), 0.0) 232 | tc.append(tc_v) 233 | if len(tc) == 0: 234 | tc = [0.0] 235 | tc_V[tuple(unsuit_v)] = min(tc) + 1e-5 236 | WT = agent.safetyFactor 237 | unsuitable_V.sort(key=lambda v: ((WT / tc_V[tuple(v)]) + l2norm(v, v_pref))) 238 | if len(unsuitable_V) > 1: 239 | vA_post = select_right_side(unsuitable_V, agent.vel_global_frame, epsilon=delta_vopt, opt_num=6) 240 | else: 241 | vA_post = unsuitable_V[0] 242 | 243 | return vA_post 244 | 245 | 246 | def satisfied_constraint(agent, vCand): 247 | vA = agent.vel_global_frame 248 | costheta = np.dot(vA, vCand) / (np.linalg.norm(vA) * np.linalg.norm(vCand)) 249 | if costheta > 1.0: 250 | costheta = 1.0 251 | elif costheta < -1.0: 252 | costheta = -1.0 253 | theta = acos(costheta) # Rotational constraints. 254 | if theta <= agent.max_heading_change: 255 | return True 256 | else: 257 | return False 258 | 259 | 260 | def select_right_side(v_list, vA, epsilon=1e-5, opt_num=4): 261 | opt_v = [v_list[0]] 262 | i = 1 263 | while True: 264 | if abs(l2norm(v_list[0], vA) - l2norm(v_list[i], vA)) < epsilon: 265 | opt_v.append(v_list[i]) 266 | i += 1 267 | if i == len(v_list) or i == opt_num: 268 | break 269 | else: 270 | break 271 | vA_phi_min = min(opt_v, key=lambda v: get_phi(v)) 272 | vA_phi_max = max(opt_v, key=lambda v: get_phi(v)) 273 | phi_min = get_phi(vA_phi_min) 274 | phi_max = get_phi(vA_phi_max) 275 | if abs(phi_max - phi_min) <= pi: 276 | vA_opti = vA_phi_min 277 | else: 278 | vA_opti = vA_phi_max 279 | return vA_opti 280 | 281 | 282 | def computeNeighbors(agent, kdTree): 283 | agent.neighbors.clear() 284 | 285 | # Check obstacle neighbors. 286 | rangeSq = agent.neighborDist ** 2 287 | if len(agent.neighbors) != agent.maxNeighbors: 288 | rangeSq = 1.0 * agent.neighborDist ** 2 289 | kdTree.computeObstacleNeighbors(agent, rangeSq) 290 | 291 | if agent.in_collision: 292 | return 293 | 294 | # Check other agents. 295 | if len(agent.neighbors) != agent.maxNeighbors: 296 | rangeSq = agent.neighborDist ** 2 297 | kdTree.computeAgentNeighbors(agent, rangeSq) 298 | 299 | 300 | def to_unicycle(vA_post, agent): 301 | vA_post = np.array(vA_post) 302 | norm_vA = norm(vA_post) 303 | yaw_next = mod2pi(atan2(vA_post[1], vA_post[0])) 304 | yaw_current = mod2pi(agent.heading_global_frame) 305 | delta_theta = yaw_next - yaw_current 306 | delta_theta = pi_2_pi(delta_theta) 307 | if delta_theta < -pi: 308 | delta_theta = delta_theta + 2 * pi 309 | if delta_theta > pi: 310 | delta_theta = delta_theta - 2 * pi 311 | if delta_theta >= 1.0: 312 | delta_theta = 1.0 313 | if delta_theta <= -1: 314 | delta_theta = -1.0 315 | action = np.array([norm_vA, delta_theta]) 316 | return action 317 | -------------------------------------------------------------------------------- /mamp/util.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from math import cos, sin, sqrt, atan2, acos, asin 4 | eps = 1e5 5 | 6 | 7 | def is_intersect(pA, pB, combined_radius, vAB): 8 | pApB = pB - pA 9 | dist_pAB = l2norm(pA, pB) 10 | if dist_pAB <= combined_radius: 11 | dist_pAB = combined_radius 12 | theta_pABBound = asin(combined_radius / dist_pAB) 13 | if (dist_pAB * np.linalg.norm(vAB)) == 0.0: # The v_dif is zero vector 14 | return False 15 | 16 | theta_pABvAB = angle_2_vectors(pApB, vAB) 17 | if theta_pABBound < theta_pABvAB: # No intersecting or tangent. 18 | return False 19 | else: 20 | return True 21 | 22 | 23 | def angle_2_vectors(v1, v2): 24 | v1v2_norm = (np.linalg.norm(v1) * np.linalg.norm(v2)) 25 | if v1v2_norm == 0.0: 26 | v1v2_norm = 1e-5 27 | cosdv = np.dot(v1, v2) / v1v2_norm 28 | if cosdv > 1.0: 29 | cosdv = 1.0 30 | elif cosdv < -1.0: 31 | cosdv = -1.0 32 | else: 33 | cosdv = cosdv 34 | angle = acos(cosdv) 35 | return angle 36 | 37 | 38 | def reached(p1, p2, bound=0.5): 39 | if l2norm(p1, p2) < bound: 40 | return True 41 | else: 42 | return False 43 | 44 | 45 | def is_in_between(theta_right, vec_AB, theta_v, theta_left): 46 | """ 47 | 判断一条射线(速度)在不在一个角度范围内 48 | """ 49 | b_left = [cos(theta_left), sin(theta_left)] 50 | b_right = [cos(theta_right), sin(theta_right)] 51 | v = [cos(theta_v), sin(theta_v)] 52 | norm_b1 = norm(b_left + vec_AB) 53 | norm_b2 = norm(b_right + vec_AB) 54 | norm_bV = norm(v + vec_AB) 55 | return norm_bV >= norm_b1 or norm_bV >= norm_b2 56 | 57 | 58 | def absSq(vec): 59 | return np.dot(vec, vec) 60 | 61 | 62 | def left_of_line(p, p1, p2): 63 | tmpx = (p1[0] - p2[0]) / (p1[1] - p2[1]) * (p[1] - p2[1]) + p2[0] 64 | if tmpx > p[0]: 65 | return True 66 | else: 67 | return False 68 | 69 | 70 | def cross(p1, p2, p3): # 跨立实验 71 | x1 = p2[0] - p1[0] 72 | y1 = p2[1] - p1[1] 73 | x2 = p3[0] - p1[0] 74 | y2 = p3[1] - p1[1] 75 | return x1 * y2 - x2 * y1 76 | 77 | 78 | def IsIntersec(p1, p2, p3, p4): # 判断两线段是否相交 79 | # 快速排斥,以l1、l2为对角线的矩形必相交,否则两线段不相交 80 | if (max(p1[0], p2[0]) >= min(p3[0], p4[0]) # 矩形1最右端大于矩形2最左端 81 | and max(p3[0], p4[0]) >= min(p1[0], p2[0]) # 矩形2最右端大于矩形最左端 82 | and max(p1[1], p2[1]) >= min(p3[1], p4[1]) # 矩形1最高端大于矩形最低端 83 | and max(p3[1], p4[1]) >= min(p1[1], p2[1])): # 矩形2最高端大于矩形最低端 84 | 85 | # 若通过快速排斥则进行跨立实验 86 | if (cross(p1, p2, p3) * cross(p1, p2, p4) <= 0 87 | and cross(p3, p4, p1) * cross(p3, p4, p2) <= 0): 88 | D = True 89 | else: 90 | D = False 91 | else: 92 | D = False 93 | return D 94 | 95 | 96 | """ 97 | 用于列表排序,指定每个数组第二个元素 98 | Example: 99 | a=[(2, 2), (3, 4), (4, 1), (1, 3)] 100 | a.sort(key=takeSecond) 101 | """ 102 | 103 | 104 | def takeSecond(elem): 105 | return elem[1] 106 | 107 | 108 | def sqr(a): 109 | return a ** 2 110 | 111 | 112 | def leftOf(a, b, c): 113 | return det(a - c, b - a) # 114 | 115 | 116 | def det(p, q): 117 | return p[0] * q[1] - p[1] * q[0] 118 | 119 | 120 | def is_parallel(vec1, vec2): 121 | """ 判断二个向量是否平行 """ 122 | assert vec1.shape == vec2.shape, r'输入的参数 shape 必须相同' 123 | norm_vec1 = np.linalg.norm(vec1) 124 | norm_vec2 = np.linalg.norm(vec2) 125 | vec1_normalized = vec1 / norm_vec1 126 | vec2_normalized = vec2 / norm_vec2 127 | if norm_vec1 <= 1e-3 or norm_vec2 <= 1e-3: 128 | return True 129 | elif 1.0 - abs(np.dot(vec1_normalized, vec2_normalized)) < 1e-3: 130 | return True 131 | else: 132 | return False 133 | 134 | 135 | def get_phi(vec): # 计算两个向量投影后的夹角, 返回值是0-2*pi 136 | phi = mod2pi(atan2(vec[1], vec[0])) 137 | return int(phi*eps)/eps 138 | 139 | 140 | def pi_2_pi(angle): # to -pi-pi 141 | return int(((angle + np.pi) % (2 * np.pi) - np.pi)*eps)/eps 142 | 143 | 144 | def mod2pi(theta): # to 0-2*pi 145 | return int((theta - 2.0 * np.pi * np.floor(theta / 2.0 / np.pi))*eps)/eps 146 | 147 | 148 | def norm(vec): 149 | return int(np.linalg.norm(vec)*eps)/eps 150 | 151 | 152 | def normalize(vec): 153 | return vec / np.linalg.norm(vec) 154 | 155 | 156 | def l2norm(x, y): 157 | return int(sqrt(l2normsq(x, y))*eps)/eps 158 | 159 | 160 | def l2normsq(x, y): 161 | return (x[0] - y[0]) ** 2 + (x[1] - y[1]) ** 2 162 | 163 | 164 | def plot_learning_curve(x, scores, figure_file, time, title='average reward'): 165 | running_avg = np.zeros(len(scores)) 166 | for i in range(len(running_avg)): 167 | running_avg[i] = np.mean(scores[max(0, i - time):(i + 1)]) 168 | plt.plot(x, running_avg) 169 | plt.title(title) 170 | plt.savefig(figure_file) 171 | 172 | 173 | ################## 174 | # Utils 175 | ################ 176 | 177 | def compute_time_to_impact(host_pos, other_pos, host_vel, other_vel, combined_radius): 178 | # http://www.ambrsoft.com/TrigoCalc/Circles2/CirclePoint/CirclePointDistance.htm 179 | v_rel = host_vel - other_vel 180 | coll_cone_vec1, coll_cone_vec2 = tangent_vecs_from_external_pt(host_pos[0], 181 | host_pos[1], 182 | other_pos[0], 183 | other_pos[1], 184 | combined_radius) 185 | 186 | if coll_cone_vec1 is None: 187 | # collision already occurred ==> collision cone isn't meaningful anymore 188 | return 0.0 189 | else: 190 | # check if v_rel btwn coll_cone_vecs 191 | # (B btwn A, C): https://stackoverflow.com/questions/13640931/how-to-determine-if-a-vector-is-between-two-other-vectors) 192 | 193 | if (np.cross(coll_cone_vec1, v_rel) * np.cross(coll_cone_vec1, coll_cone_vec2) >= 0 and 194 | np.cross(coll_cone_vec2, v_rel) * np.cross(coll_cone_vec2, coll_cone_vec1) >= 0): 195 | # quadratic eqn for soln to line from host agent pos along v_rel vector to collision circle 196 | # circle: (x-a)**2 + (y-b)**2 = r**2 197 | # line: y = v1/v0 *(x-px) + py 198 | # solve for x: (x-a)**2 + ((v1/v0)*(x-px)+py-a)**2 = r**2 199 | v0, v1 = v_rel 200 | if abs(v0) < 1e-5 and abs(v1) < 1e-5: 201 | # agents aren't moving toward each other ==> inf TTC 202 | return np.inf 203 | 204 | px, py = host_pos 205 | a, b = other_pos 206 | r = combined_radius 207 | if abs(v0) < 1e-5: # vertical v_rel (solve for y, x known) 208 | print("[warning] v0=0, and not yet handled") 209 | x1 = x2 = px 210 | A = 1 211 | B = -2 * b 212 | C = b ** 2 + (px - a) ** 2 - r ** 2 213 | y1 = (-B + np.sqrt(B ** 2 - 4 * A * C)) / (2 * A) 214 | y2 = (-B - np.sqrt(B ** 2 - 4 * A * C)) / (2 * A) 215 | else: # non-vertical v_rel (solve for x) 216 | A = 1 + (v1 / v0) ** 2 217 | B = -2 * a + 2 * (v1 / v0) * (py - b - (v1 / v0) * px) 218 | C = a ** 2 - r ** 2 + ((v1 / v0) * px - (py - b)) ** 2 219 | 220 | det = B ** 2 - 4 * A * C 221 | if det == 0: 222 | print("[warning] det == 0, so only one tangent pt") 223 | elif det < 0: 224 | print("[warning] det < 0, so no tangent pts...") 225 | 226 | x1 = (-B + np.sqrt(B ** 2 - 4 * A * C)) / (2 * A) 227 | x2 = (-B - np.sqrt(B ** 2 - 4 * A * C)) / (2 * A) 228 | y1 = (v1 / v0) * (x1 - px) + py 229 | y2 = (v1 / v0) * (x2 - px) + py 230 | 231 | d1 = np.linalg.norm([x1 - px, y1 - py]) 232 | d2 = np.linalg.norm([x2 - px, y2 - py]) 233 | d = min(d1, d2) 234 | spd = np.linalg.norm(v_rel) 235 | return d / spd 236 | else: 237 | return np.inf 238 | 239 | 240 | def tangent_vecs_from_external_pt(xp, yp, a, b, r): 241 | # http://www.ambrsoft.com/TrigoCalc/Circles2/CirclePoint/CirclePointDistance.htm 242 | # (xp, yp) is coords of pt outside of circle 243 | # (x-a)**2 + (y-b)**2 = r**2 is defn of circle 244 | 245 | sq_dist_to_perimeter = (xp - a) ** 2 + (yp - b) ** 2 - r ** 2 246 | if sq_dist_to_perimeter < 0: 247 | # print("sq_dist_to_perimeter < 0 ==> agent center is already within coll zone??") 248 | return None, None 249 | 250 | sqrt_term = np.sqrt((xp - a) ** 2 + (yp - b) ** 2 - r ** 2) 251 | xnum1 = r ** 2 * (xp - a) 252 | xnum2 = r * (yp - b) * sqrt_term 253 | 254 | ynum1 = r ** 2 * (yp - b) 255 | ynum2 = r * (xp - a) * sqrt_term 256 | 257 | den = (xp - a) ** 2 + (yp - b) ** 2 258 | 259 | # pt1, pt2 are the tangent pts on the circle perimeter 260 | pt1 = np.array([(xnum1 + xnum2) / den + a, (ynum1 - ynum2) / den + b]) 261 | pt2 = np.array([(xnum1 - xnum2) / den + a, (ynum1 + ynum2) / den + b]) 262 | 263 | # vec1, vec2 are the vecs from (xp,yp) to the tangent pts on the circle perimeter 264 | vec1 = pt1 - np.array([xp, yp]) 265 | vec2 = pt2 - np.array([xp, yp]) 266 | 267 | return vec1, vec2 268 | 269 | 270 | def vec2_l2_norm(vec): 271 | # return np.linalg.norm(vec) 272 | return sqrt(vec2_l2_norm_squared(vec)) 273 | 274 | 275 | def vec2_l2_norm_squared(vec): 276 | return vec[0] ** 2 + vec[1] ** 2 277 | 278 | 279 | # speed filter 280 | # input: past velocity in (x,y) at time_past 281 | # output: filtered velocity in (speed, theta) 282 | def filter_vel(dt_vec, agent_past_vel_xy): 283 | average_x = np.sum(dt_vec * agent_past_vel_xy[:, 0]) / np.sum(dt_vec) 284 | average_y = np.sum(dt_vec * agent_past_vel_xy[:, 1]) / np.sum(dt_vec) 285 | speeds = np.linalg.norm(agent_past_vel_xy, axis=1) 286 | speed = np.linalg.norm(np.array([average_x, average_y])) 287 | angle = np.arctan2(average_y, average_x) 288 | 289 | return np.array([speed, angle]) 290 | 291 | 292 | # angle_1 - angle_2 293 | # contains direction in range [-3.14, 3.14] 294 | def find_angle_diff(angle_1, angle_2): 295 | angle_diff_raw = angle_1 - angle_2 296 | angle_diff = (angle_diff_raw + np.pi) % (2 * np.pi) - np.pi 297 | return angle_diff 298 | 299 | 300 | # keep angle between [-pi, pi] 301 | def wrap(angle): 302 | while angle >= np.pi: 303 | angle -= 2 * np.pi 304 | while angle < -np.pi: 305 | angle += 2 * np.pi 306 | return angle 307 | 308 | 309 | def find_nearest(array, value): 310 | # array is a 1D np array 311 | # value is an scalar or 1D np array 312 | tiled_value = np.tile(np.expand_dims(value, axis=0).transpose(), (1, np.shape(array)[0])) 313 | idx = (np.abs(array - tiled_value)).argmin(axis=1) 314 | return array[idx], idx 315 | 316 | 317 | def rad2deg(rad): 318 | return rad * 180 / np.pi 319 | 320 | 321 | def rgba2rgb(rgba): 322 | # rgba is a list of 4 color elements btwn [0.0, 1.0] 323 | # or a 2d np array (num_colors, 4) 324 | # returns a list of rgb values between [0.0, 1.0] accounting for alpha and background color [1, 1, 1] == WHITE 325 | if isinstance(rgba, list): 326 | alpha = rgba[3] 327 | r = max(min((1 - alpha) * 1.0 + alpha * rgba[0], 1.0), 0.0) 328 | g = max(min((1 - alpha) * 1.0 + alpha * rgba[1], 1.0), 0.0) 329 | b = max(min((1 - alpha) * 1.0 + alpha * rgba[2], 1.0), 0.0) 330 | return [r, g, b] 331 | elif rgba.ndim == 2: 332 | alphas = rgba[:, 3] 333 | r = np.clip((1 - alphas) * 1.0 + alphas * rgba[:, 0], 0, 1) 334 | g = np.clip((1 - alphas) * 1.0 + alphas * rgba[:, 1], 0, 1) 335 | b = np.clip((1 - alphas) * 1.0 + alphas * rgba[:, 2], 0, 1) 336 | return np.vstack([r, g, b]).T 337 | 338 | 339 | def yaw_to_quaternion(yaw): 340 | pitch = 0; 341 | roll = 0 342 | cy = np.cos(yaw * 0.5) 343 | sy = np.sin(yaw * 0.5) 344 | cp = np.cos(pitch * 0.5) 345 | sp = np.sin(pitch * 0.5) 346 | cr = np.cos(roll * 0.5) 347 | sr = np.sin(roll * 0.5) 348 | 349 | qw = cy * cp * cr + sy * sp * sr 350 | qx = cy * cp * sr - sy * sp * cr 351 | qy = sy * cp * sr + cy * sp * cr 352 | qz = sy * cp * cr - cy * sp * sr 353 | return qx, qy, qz, qw 354 | 355 | 356 | def run_episode(env, one_env): 357 | total_reward = 0 358 | step = 0 359 | done = False 360 | while not done: 361 | obs, rew, done, info = env.step([None]) 362 | total_reward += rew[0] 363 | step += 1 364 | 365 | # After end of episode, store some statistics about the environment 366 | # Some stats apply to every gym env... 367 | generic_episode_stats = {'total_reward': total_reward, 'steps': step, } 368 | 369 | agents = one_env.agents 370 | # agents = one_env.prev_episode_agents 371 | time_to_goal = np.array([a.t for a in agents]) 372 | extra_time_to_goal = np.array([a.t - a.straight_line_time_to_reach_goal for a in agents]) 373 | collision = np.array(np.any([a.in_collision for a in agents])).tolist() 374 | all_at_goal = np.array(np.all([a.is_at_goal for a in agents])).tolist() 375 | any_stuck = np.array(np.any([not a.in_collision and not a.is_at_goal for a in agents])).tolist() 376 | outcome = "collision" if collision else "all_at_goal" if all_at_goal else "stuck" 377 | specific_episode_stats = {'num_agents': len(agents), 'time_to_goal': time_to_goal, 378 | 'total_time_to_goal': np.sum(time_to_goal), 379 | 'extra_time_to_goal': extra_time_to_goal, 'collision': collision, 380 | 'all_at_goal': all_at_goal, 'any_stuck': any_stuck, 'outcome': outcome, 381 | 'policies': [agent.policy.str for agent in agents], } 382 | 383 | # Merge all stats into a single dict 384 | episode_stats = {**generic_episode_stats, **specific_episode_stats} 385 | 386 | env.reset() 387 | 388 | return episode_stats, agents 389 | 390 | 391 | def store_stats(df, hyperparameters, episode_stats): 392 | # Add a new row to the pandas DataFrame (a table of results, where each row is an episode) 393 | # that contains the hyperparams and stats from that episode, for logging purposes 394 | df_columns = {**hyperparameters, **episode_stats} 395 | df = df.append(df_columns, ignore_index=True) 396 | return df 397 | -------------------------------------------------------------------------------- /mamp/policies/orcaPolicy.py: -------------------------------------------------------------------------------- 1 | import time 2 | import numpy as np 3 | from math import sqrt, sin, cos, atan2, asin, pi, floor, acos, asin 4 | from itertools import combinations, product 5 | 6 | from mamp.envs import Config 7 | from mamp.util import absSq, l2norm, norm, sqr, det, mod2pi, pi_2_pi, normalize, angle_2_vectors 8 | from mamp.policies.policy import Policy 9 | 10 | 11 | class Line(object): 12 | def __init__(self): 13 | self.direction = np.array([0.0, 1.0]) # The direction of the directed line. 14 | self.point = np.array([0.0, 0.0]) # A point on the directed line. 15 | 16 | 17 | class ORCAPolicy(Policy): 18 | def __init__(self): 19 | Policy.__init__(self, str="ORCAPolicy") 20 | self.type = "internal" 21 | self.now_goal = None 22 | self.epsilon = 1e-5 23 | self.orcaLines = [] 24 | self.newVelocity = np.array([0.0, 0.0]) 25 | 26 | def find_next_action(self, obs, dict_comm, agent, kdTree): 27 | ts = time.time() 28 | self.now_goal = agent.goal_global_frame 29 | self.orcaLines.clear() 30 | self.newVelocity = np.array([0.0, 0.0]) 31 | invTimeHorizonObst = 1.0 / agent.timeHorizonObst 32 | invTimeHorizon = 1.0 / agent.timeHorizon 33 | v_pref = compute_v_pref(agent) 34 | computeNeighbors(agent, kdTree) 35 | 36 | for obj in agent.neighbors: 37 | obj = obj[0] 38 | relativePosition = obj.pos_global_frame - agent.pos_global_frame 39 | relativeVelocity = agent.vel_global_frame - obj.vel_global_frame 40 | distSq = absSq(relativePosition) 41 | agent_rad = agent.radius + 0.05 42 | obj_rad = obj.radius + 0.05 43 | combinedRadius = agent_rad + obj_rad 44 | combinedRadiusSq = sqr(combinedRadius) 45 | 46 | line = Line() 47 | 48 | if distSq > combinedRadiusSq: # No collision. 49 | w = relativeVelocity - invTimeHorizon * relativePosition 50 | # Vector from cutoff center to relative velocity. 51 | wLengthSq = absSq(w) 52 | 53 | dotProduct1 = np.dot(w, relativePosition) 54 | 55 | if dotProduct1 < 0.0 and sqr(dotProduct1) > combinedRadiusSq * wLengthSq: 56 | # Project on cut-off circle. 57 | wLength = sqrt(wLengthSq) 58 | unitW = w / wLength 59 | 60 | line.direction = np.array([unitW[1], -unitW[0]]) 61 | u = (combinedRadius * invTimeHorizon - wLength) * unitW 62 | else: 63 | # Project on legs. 64 | leg = sqrt(distSq - combinedRadiusSq) 65 | 66 | if det(relativePosition, w) > 0.0 > 0.0: 67 | # Project on left leg. 68 | x = relativePosition[0] * leg - relativePosition[1] * combinedRadius 69 | y = relativePosition[0] * combinedRadius + relativePosition[1] * leg 70 | line.direction = np.array([x, y]) / distSq 71 | else: 72 | # Project on right leg. 73 | x = relativePosition[0] * leg + relativePosition[1] * combinedRadius 74 | y = -relativePosition[0] * combinedRadius + relativePosition[1] * leg 75 | line.direction = -np.array([x, y]) / distSq 76 | 77 | dotProduct2 = np.dot(relativeVelocity, line.direction) 78 | 79 | u = dotProduct2 * line.direction - relativeVelocity 80 | else: 81 | # Collision.Project on cut - off circle of time timeStep. 82 | invTimeStep = 1.0 / agent.timeStep 83 | 84 | # Vector from cutoff center to relative velocity. 85 | w = relativeVelocity - invTimeStep * relativePosition 86 | wLength = norm(w) 87 | unitW = w / wLength 88 | 89 | line.direction = np.array([unitW[1], -unitW[0]]) 90 | u = (combinedRadius * invTimeStep - wLength) * unitW 91 | 92 | line.point = agent.vel_global_frame + 0.5 * u 93 | self.orcaLines.append(line) 94 | 95 | lineFail = self.linearProgram2(self.orcaLines, agent.maxSpeed, v_pref, False) 96 | 97 | if lineFail < len(self.orcaLines): 98 | self.linearProgram3(self.orcaLines, 0, lineFail, agent.maxSpeed) 99 | # print(222, self.newVelocity) 100 | 101 | vA_post = np.array([self.newVelocity[0], self.newVelocity[1]]) 102 | vA = agent.vel_global_frame 103 | te = time.time() 104 | cost_step = te - ts 105 | agent.total_time += cost_step 106 | action = to_unicycle(vA_post, agent) 107 | agent.vel_global_unicycle[0] = round(1.0 * action[0], 5) 108 | agent.vel_global_unicycle[1] = round(0.22 * 0.5 * action[1] / Config.DT, 5) 109 | theta = angle_2_vectors(vA, vA_post) 110 | dist = l2norm(agent.pos_global_frame, agent.goal_global_frame) 111 | if theta > agent.max_heading_change: 112 | print('agent' + str(agent.id), len(agent.neighbors), action, '终点距离:', dist, 'theta:', theta) 113 | else: 114 | print('agent' + str(agent.id), len(agent.neighbors), action, '终点距离:', dist) 115 | 116 | return action 117 | 118 | def linearProgram1(self, lines, lineNo, radius, optVelocity, directionOpt): 119 | """ 120 | Solves a one-dimensional linear program on a specified line subject to linear 121 | constraints defined by lines and a circular constraint. 122 | Args: 123 | lines (list): Lines defining the linear constraints. 124 | lineNo (int): The specified line constraint. 125 | radius (float): The radius of the circular constraint. 126 | optVelocity (Vector2): The optimization velocity. 127 | directionOpt (bool): True if the direction should be optimized. 128 | Returns: 129 | bool: True if successful. 130 | Vector2: A reference to the result of the linear program. 131 | """ 132 | dotProduct = np.dot(lines[lineNo].point, lines[lineNo].direction) 133 | discriminant = sqr(dotProduct) + sqr(radius) - absSq(lines[lineNo].point) 134 | 135 | if discriminant < 0.0: 136 | # Max speed circle fully invalidates line lineNo. 137 | return False 138 | 139 | sqrtDiscriminant = sqrt(discriminant) 140 | tLeft = -dotProduct - sqrtDiscriminant 141 | tRight = -dotProduct + sqrtDiscriminant 142 | 143 | for i in range(lineNo): 144 | denominator = det(lines[lineNo].direction, lines[i].direction) 145 | numerator = det(lines[i].direction, lines[lineNo].point - lines[i].point) 146 | 147 | if abs(denominator) <= self.epsilon: 148 | # Lines lineNo and i are (almost) parallel. 149 | if numerator < 0.0: 150 | return False 151 | else: 152 | continue 153 | 154 | t = numerator / denominator 155 | 156 | if denominator >= 0.0: 157 | # Line i bounds line lineNo on the right. 158 | tRight = min(tRight, t) 159 | else: 160 | # * Line i bounds line lineNo on the left. 161 | tLeft = max(tLeft, t) 162 | 163 | if tLeft > tRight: 164 | return False 165 | 166 | if directionOpt: 167 | # Optimize direction. 168 | if np.dot(optVelocity, lines[lineNo].direction) > 0.0: 169 | # Take right extreme. 170 | self.newVelocity = lines[lineNo].point + tRight * lines[lineNo].direction 171 | else: 172 | # Take left extreme. 173 | self.newVelocity = lines[lineNo].point + tLeft * lines[lineNo].direction 174 | else: 175 | # Optimize closest point. 176 | t = np.dot(lines[lineNo].direction, optVelocity - lines[lineNo].point) 177 | 178 | if t < tLeft: 179 | self.newVelocity = lines[lineNo].point + tLeft * lines[lineNo].direction 180 | elif t > tRight: 181 | self.newVelocity = lines[lineNo].point + tRight * lines[lineNo].direction 182 | else: 183 | self.newVelocity = lines[lineNo].point + t * lines[lineNo].direction 184 | 185 | return True 186 | 187 | def linearProgram2(self, lines, radius, optVelocity, directionOpt): 188 | """ 189 | Solves a two-dimensional linear program subject to linear constraints defined by 190 | lines and a circular constraint. 191 | Args: 192 | lines (list): Lines defining the linear constraints. 193 | radius (float): The radius of the circular constraint. 194 | optVelocity (Vector2): The optimization velocity. 195 | directionOpt (bool): True if the direction should be optimized. 196 | Returns: 197 | int: The number of the line it fails on, and the number of lines if successful. 198 | Vector2: A reference to the result of the linear program. 199 | """ 200 | if directionOpt: 201 | # Optimize direction. Note that the optimization velocity is of unit length in this case. 202 | self.newVelocity = optVelocity * radius 203 | elif absSq(optVelocity) > sqr(radius): 204 | # Optimize closest point and outside circle. 205 | self.newVelocity = normalize(optVelocity) * radius 206 | else: 207 | # Optimize closest point and inside circle. 208 | self.newVelocity = optVelocity 209 | for i in range(len(lines)): 210 | if det(lines[i].direction, lines[i].point - self.newVelocity) > 0.0: 211 | # Result does not satisfy constraint i.Compute new optimal result. 212 | tempResult = self.newVelocity 213 | 214 | if not self.linearProgram1(lines, i, radius, optVelocity, directionOpt): 215 | self.newVelocity = tempResult 216 | return i 217 | 218 | return len(lines) 219 | 220 | def linearProgram3(self, lines, numObstLines, beginLine, radius): 221 | """ 222 | Solves a two-dimensional linear program subject to linear constraints defined by lines and a circular constraint. 223 | Args: 224 | lines (list): Lines defining the linear constraints. 225 | numObstLines (int): Count of obstacle lines. 226 | beginLine (int): The line on which the 2-d linear program failed. 227 | radius (float): The radius of the circular constraint. 228 | """ 229 | distance = 0.0 230 | 231 | for i in range(beginLine, len(lines)): 232 | if det(lines[i].direction, lines[i].point - self.newVelocity) > distance: 233 | # Result does not satisfy constraint of line i. 234 | projLines = [] 235 | for j in range(numObstLines): 236 | projLines.append(lines[j]) 237 | 238 | for j in range(numObstLines, i): 239 | line = Line() 240 | determinant = det(lines[i].direction, lines[j].direction) 241 | 242 | if abs(determinant) <= self.epsilon: 243 | # Line i and line j are parallel. 244 | if np.dot(lines[i].direction, lines[j].direction) > 0.0: 245 | # Line i and line j point in the same direction. 246 | continue 247 | else: 248 | # Line i and line j point in opposite direction. 249 | line.point = 0.5 * (lines[i].point + lines[j].point) 250 | else: 251 | p1 = (det(lines[j].direction, lines[i].point - lines[j].point) / determinant) 252 | line.point = lines[i].point + p1 * lines[i].direction 253 | 254 | line.direction = normalize(lines[j].direction - lines[i].direction) 255 | projLines.append(line) 256 | 257 | tempResult = self.newVelocity 258 | optVelocity = np.array([-lines[i].direction[1], lines[i].direction[0]]) 259 | if self.linearProgram2(projLines, radius, optVelocity, True) < len(projLines): 260 | """ 261 | This should in principle not happen. The result is by definition already 262 | in the feasible region of this linear program. If it fails, it is due to 263 | small floating point error, and the current result is kept. 264 | """ 265 | self.newVelocity = tempResult 266 | 267 | distance = det(lines[i].direction, lines[i].point - self.newVelocity) 268 | 269 | 270 | def compute_v_pref(agent): 271 | goal = agent.goal_global_frame 272 | diff = goal - agent.pos_global_frame 273 | v_pref = agent.pref_speed * normalize(diff) 274 | if l2norm(goal, agent.pos_global_frame) < Config.NEAR_GOAL_THRESHOLD: 275 | v_pref = np.zeros_like(v_pref) 276 | return v_pref 277 | 278 | 279 | def computeNeighbors(agent, kdTree): 280 | agent.neighbors.clear() 281 | 282 | # check obstacle neighbors 283 | rangeSq = agent.neighborDist ** 2 284 | if len(agent.neighbors) != agent.maxNeighbors: 285 | rangeSq = 1.0 * agent.neighborDist ** 2 286 | kdTree.computeObstacleNeighbors(agent, rangeSq) 287 | 288 | if agent.in_collision: 289 | return 290 | 291 | # check other agents 292 | if len(agent.neighbors) != agent.maxNeighbors: 293 | rangeSq = agent.neighborDist ** 2 294 | kdTree.computeAgentNeighbors(agent, rangeSq) 295 | 296 | 297 | def to_unicycle(vA_post, agent): 298 | vA_post = np.array(vA_post) 299 | norm_vA = norm(vA_post) 300 | yaw_next = mod2pi(atan2(vA_post[1], vA_post[0])) 301 | yaw_current = mod2pi(agent.heading_global_frame) 302 | delta_theta = yaw_next - yaw_current 303 | delta_theta = pi_2_pi(delta_theta) 304 | if delta_theta < -pi: 305 | delta_theta = delta_theta + 2 * pi 306 | if delta_theta > pi: 307 | delta_theta = delta_theta - 2 * pi 308 | if delta_theta >= 1.0: 309 | delta_theta = 1.0 310 | if delta_theta <= -1: 311 | delta_theta = -1.0 312 | action = np.array([norm_vA, delta_theta]) 313 | return action 314 | 315 | --------------------------------------------------------------------------------