From c46c2287c550aee2c018e491bb1b0b913b14429d Mon Sep 17 00:00:00 2001 From: "oceanpdoshi@g.ucla.edu" <oceanpdoshi@.ucla.edu> Date: Mon, 22 Apr 2019 21:24:49 -0700 Subject: [PATCH] Added my own test_simulation file for future reference Added custom noises to simulated_dataset_manager.py Thorough review of simulated_dataset_manager reveals that it works correctly! fixed delta_t with regards to alternation between measurement and odometry updates -> scheduling of this will change in next commit will add multiple robots --- .../simulated_dataset_manager.py | 89 ++++++++++--------- CoLo-AT/test_simulation_sagar.py | 68 ++++++++++++++ 2 files changed, 113 insertions(+), 44 deletions(-) create mode 100644 CoLo-AT/test_simulation_sagar.py diff --git a/CoLo-AT/dataset_manager/simulated_dataset_manager.py b/CoLo-AT/dataset_manager/simulated_dataset_manager.py index 8656977..2651dc5 100644 --- a/CoLo-AT/dataset_manager/simulated_dataset_manager.py +++ b/CoLo-AT/dataset_manager/simulated_dataset_manager.py @@ -17,31 +17,32 @@ from requests.request_response import Request_response # TODO - incorporate communcation (LQI, RSS) as a threshold/cutoff for communication class SimulatedDataSetManager: - def __init__(self, dataset_name, boundary, landmarks, duration, robot_labels, - odometry_noise, communication_noise, measurement_noise, robot_vision = pi/3): + def __init__(self, dataset_name, boundary, landmarks, duration, robot_labels, + velocity_noise = 0, angular_velocity_noise = 0, measurement_range_noise = 0, bearing_noise = 0, communication_noise = 0, robot_vision = pi/3): self.name = dataset_name - # Used for request scheduling + # Alternates between 'odometry' and 'measurement' self.next_request = 'odometry' self.duration = duration + self.boundary = boundary self.robot_labels = robot_labels self.num_robots = len(robot_labels) # Internal time "counter" self.time_idx = 0 - self.delta_t = 1 + self.delta_t = 1.0 # angular width of robots's vision with respect to the x_axis (radians) self.robot_vision = robot_vision - # User-specified noises - # TODO - may need to add more choice in the future (Ex: specify angular velocity noise) - self.odometry_noise = odometry_noise + # User-specified noises, if none given: defaulted to 0 + self.velocity_noise = velocity_noise + self.angular_velocity_noise = angular_velocity_noise + self.measurement_range_noise = measurement_range_noise + self.bearing_noise = bearing_noise self.communication_noise = communication_noise - self.measurement_noise = measurement_noise - self.boundary = boundary # All robots begin moving at t = 0s self.start_time = 20.0 @@ -53,16 +54,15 @@ class SimulatedDataSetManager: self.landmark_map = landmarks # starting_states = {robot label: [time, x_pos, y_pos, orientation], ... } - # TODO - Update this to be user-specified on how to initialize robot locations. - self.starting_states = {label : [self.start_time, i, i, 0] for i,label in enumerate(robot_labels)} + # TODO - Update this to be user-specified on how to initialize robot locations. (esecially in more advanced movement patterns) + self.starting_states = {label : [self.start_time, float(i), 1.0, 0.0] for i,label in enumerate(robot_labels)} # in the simulation all the times for all the measurements are the same. - self.time_arr = {'odometry': [[] for i in range(self.num_robots)], 'measurement': [[] for i in range(self.num_robots)], - 'groundtruth': [[] for i in range(self.num_robots)]} + self.time_arr = {'odometry': [[] for robot in robot_labels], 'measurement': [[] for robot in robot_labels], 'groundtruth': [[] for robot in robot_labels]} for data_type in self.time_arr: for i in range(self.num_robots): - self.time_arr[data_type][i] = np.arange(self.start_time, self.end_time+1) + self.time_arr[data_type][i] = np.arange(self.start_time, self.end_time) # Measurement Data Format @@ -134,11 +134,10 @@ class SimulatedDataSetManager: # NOTE - delta_t is currently hard coded to 1 loc1 = [self.groundtruth_data[i][j-1]['x_pos'], self.groundtruth_data[i][j-1]['y_pos']] loc2 = [self.groundtruth_data[i][j]['x_pos'], self.groundtruth_data[i][j]['y_pos']] - velocity = self.calc_distance(loc1, loc2)/self.delta_t + np.random.normal(loc=0.0,scale=self.odometry_noise) - + velocity = self.calc_distance(loc1, loc2)/self.delta_t + np.random.normal(loc=0.0,scale=self.velocity_noise) theta_1 = self.groundtruth_data[i][j-1]['orientation'] theta_2 = self.groundtruth_data[i][j]['orientation'] - angular_velocity = (theta_2-theta_1)/self.delta_t + np.random.normal(loc=0.0,scale=self.odometry_noise) + angular_velocity = (theta_2-theta_1)/self.delta_t + np.random.normal(loc=0.0,scale=self.angular_velocity_noise) self.odometry_data[i].append({'time': self.time_arr['odometry'][i][j], 'velocity' : velocity, 'angular velocity' : angular_velocity, 'orientation' : self.groundtruth_data[i][j]['orientation'], 'delta_t' : self.delta_t}) @@ -150,9 +149,9 @@ class SimulatedDataSetManager: robot_loc_x = self.groundtruth_data[i][j]['x_pos'] robot_loc_y = self.groundtruth_data[i][j]['y_pos'] robot_loc = [robot_loc_x, robot_loc_y] - if (self.within_vision(2*pi, robot_loc, landmark_loc)): - measurement_range = self.calc_distance(robot_loc, landmark_loc) + np.random.normal(loc=0.0,scale=self.measurement_noise) - bearing = atan2((landmark_loc[1]-robot_loc[1]), (landmark_loc[0]-robot_loc[0])) + np.random.normal(loc=0.0,scale=0.1) + if (True): + measurement_range = self.calc_distance(robot_loc, landmark_loc) + np.random.normal(loc=0.0,scale=self.measurement_range_noise) + bearing = atan2((landmark_loc[1]-robot_loc[1]), (landmark_loc[0]-robot_loc[0])) + np.random.normal(loc=0.0,scale=self.bearing_noise) self.measurement_data[i].append({'time': self.time_arr['measurement'][i][j], 'subject_ID': landmarkID, 'measurment_range':measurement_range, 'bearing':bearing}) self.dataset_data = {'odometry': self.odometry_data, 'measurement': self.measurement_data, 'groundtruth': self.groundtruth_data} @@ -183,9 +182,6 @@ class SimulatedDataSetManager: gt = self.groundtruth_data[robot_index][gt_time] return gt - def updateTime(self): - self.time_idx += 1 - def respond(self, req, current_time, need_specific_time = False): valid_respond, message, req_type, robot_idx, time_idx = self.get_dataline(req, current_time) @@ -198,9 +194,10 @@ class SimulatedDataSetManager: req.set_robot_idx(robot_idx) req.set_type(req_type) - print(req.get_message()) + if (req_type == 'measurement'): + current_time += self.delta_t - return valid_respond, current_time+self.delta_t, req + return valid_respond, current_time, req # Alternates odometry and measurement updates def get_dataline(self, req, current_time): @@ -224,14 +221,21 @@ class SimulatedDataSetManager: message['robot_index'] = robot_idx - time_idx = np.where(self.time_arr[req_type][robot_idx] == current_time)[0][0] - if self.dataset_data[req_type][robot_idx][time_idx]['time'] > self.end_time or time_idx == -1: + valid_dataline= True + + # TODO - handling of case where no data is available + if req_type == 'measurement' and (time_idx >= len (self.dataset_data[req_type][robot_idx])): valid_dataline = False - else: - valid_dataline= True - message['data'] = self.dataset_data[req_type][robot_idx][time_idx] - message['groundtruth'] = self.groundtruth_data[robot_idx][time_idx] + + if valid_dataline: + if req_type == 'odometry': + message['data'] = self.odometry_data[robot_idx][time_idx] + elif req_type == 'measurement': + message['data'] = self.find_measurement(current_time, robot_idx) # there may exist a built-in function to do a linear search through a list of dictionaries + + + message['groundtruth'] = self.groundtruth_data[robot_idx][time_idx] return valid_dataline, message, req_type, robot_idx, time_idx # Check if a given loc [x,y] is within map @@ -254,21 +258,18 @@ class SimulatedDataSetManager: distance = sqrt((x1-x2)**2 + (y1-y2)**2) return distance + # TODO - Check this function # robot_vision = angular width of robot's view in reference to x_axis # robot_loc & landmark_loc = [x,y] def within_vision(self, robot_vision, robot_loc, landmark_loc): - # arctan() returns between [-pi/2, pi/2] => Correct for our F.O.V application with respect to the x-axis as realistically F.O.V will not exceed pi/3 - bearing = 0 - # Same point - if (landmark_loc[1] == robot_loc[1] and landmark_loc[0] == robot_loc[0]): - bearing = 0 - # either + or - pi/2 (delta_x = 0) - elif (landmark_loc[0] == robot_loc[0]): - if (landmark_loc[1] < robot_loc[1]): - bearing = -1*pi/2 - else: - bearing = pi/2 - else: - bearing = np.arctan((landmark_loc[1]-robot_loc[1])/(landmark_loc[0]-robot_loc[0])) + np.random.normal(loc=0.0,scale=0.1) + bearing = atan2((landmark_loc[1]-robot_loc[1]),(landmark_loc[0]-robot_loc[0])) + np.random.normal(loc=0.0,scale=0.1) return abs(bearing) < robot_vision/2 + + def find_measurement(self, current_time, robot_idx): + for measurement in self.measurement_data[robot_idx]: + if (measurement['time'] == current_time): + return measurement + + # TODO - handling of case where no measurmeent data available (ex: no landmarks in vision at given time) + return None \ No newline at end of file diff --git a/CoLo-AT/test_simulation_sagar.py b/CoLo-AT/test_simulation_sagar.py new file mode 100644 index 0000000..d87d160 --- /dev/null +++ b/CoLo-AT/test_simulation_sagar.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Apr 17 2019 + +@author: Sagar +""" + +import os, sys +import getpass +import IPython +from matplotlib import pyplot as plt +from matplotlib import animation + +# sys.path.append(os.path.join(os.path.dirname(__file__), ".")) +from dataset_manager.realworld_dataset_manager import RW_Dataset_Manager +from dataset_manager.simulated_dataset_manager import SimulatedDataSetManager +from simulation_process.sim_manager import SimulationManager +from robots.robot_system import RobotSystem +from simulation_process.state_recorder import StatesRecorder +from data_analysis.data_analyzer import Analyzer +from data_analysis.realtime_plot import animate_plot + +from pprint import pprint + +# load algorithms +sys.path.append(os.path.join(os.path.dirname(__file__), "localization_algos")) +from centralized_ekf import Centralized_EKF # works +from simple_ekf import Simple_EKF +from ekf_ls_bda import EKF_LS_BDA +from ekf_ls_ci import EKF_LS_CI +from ekf_gs_bound import EKF_GS_BOUND +from ekf_gs_ci2 import EKF_GS_CI2 + +compname = getpass.getuser() +#dataset_path = "/home/"+ compname +"/CoLo/CoLo-D/UTIAS-Datasets/MRCLAM_Dataset3/" + +# landmark IDs must be bigger than 5 +landmarks = {10: [2,5]} + +robot_labels = [1] +boundary = 100 # size of circular map +duration = 120 # duration for the simulation in sec + +# NOTE - noise significantly affects the results +testing_dataset = SimulatedDataSetManager('testing', boundary, landmarks, duration, robot_labels, velocity_noise=0.01, angular_velocity_noise=0.01, +measurement_range_noise=0.01, bearing_noise=0.01 ) +start_time, starting_states, dataset_data, time_arr = testing_dataset.simulate_dataset() + + +# Real-World DataSet +# dataset_path = "D:/LEMUR/CoLo/CoLo-D/CoLo-Datasets/official_dataset1/" +# testing_dataset = RW_Dataset_Manager('testing') +# start_time, starting_states, dataset_data, time_arr = testing_dataset.load_datasets(dataset_path, robot_labels, duration) + + +analyzer = Analyzer('analyzer', robot_labels) + +loc_algo = Centralized_EKF('CentralizedEKF') +robot = RobotSystem('robot', robot_labels, loc_algo, distr_sys = False) + +sim = SimulationManager('sim') +state_recorder_bda = StatesRecorder('CentralizedEKF', robot_labels) + +sim.sim_process_native(robot_labels, testing_dataset, robot, state_recorder_bda, simple_plot = True, comm=False, simulated_comm = False) + +loc_err_per_run, trace_per_run, t_arr = analyzer.calculate_loc_err_and_trace_state_variance_per_run(state_recorder_bda, plot_graphs = True) + -- GitLab