def _build_single_scenario(clean, allow_offset_map, scenario): import importlib.resources as pkg_resources from smarts.sstudio.sumo2mesh import generate_glb_from_sumo_network click.echo(f"build-scenario {scenario}") if clean: _clean(scenario) scenario_root = Path(scenario) map_net = str(scenario_root / "map.net.xml") if not allow_offset_map: SumoRoadNetwork.from_file(map_net, shift_to_origin=True) elif os.path.isfile(SumoRoadNetwork.shifted_net_file_path(map_net)): click.echo( "WARNING: {} already exists. Remove it if you want to use unshifted/offset map.net.xml instead." .format(SumoRoadNetwork.shifted_net_file_name)) map_glb = scenario_root / "map.glb" generate_glb_from_sumo_network(map_net, str(map_glb)) requirements_txt = scenario_root / "requirements.txt" if requirements_txt.exists(): import zoo.policies with pkg_resources.path(zoo.policies, "") as path: # Serve policies through the static file server, then kill after # we've installed scenario requirements pip_index_proc = subprocess.Popen( ["twistd", "-n", "web", "--path", path], # Hide output to keep display simple stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, ) pip_install_cmd = [ sys.executable, "-m", "pip", "install", "-r", str(requirements_txt), ] click.echo( f"Installing scenario dependencies via '{' '.join(pip_install_cmd)}'" ) try: subprocess.check_call(pip_install_cmd, stdout=subprocess.DEVNULL) finally: pip_index_proc.terminate() pip_index_proc.wait() scenario_py = scenario_root / "scenario.py" if scenario_py.exists(): subprocess.check_call([sys.executable, scenario_py])
def __init__( self, scenario_root: str, route: str = None, missions: Dict[str, Mission] = None, social_agents: Dict[str, SocialAgent] = None, log_dir: str = None, surface_patches: list = None, traffic_history: str = None, ): self._logger = logging.getLogger(self.__class__.__name__) self._root = scenario_root self._route = route self._missions = missions or {} self._bubbles = Scenario._discover_bubbles(scenario_root) self._social_agents = social_agents or {} self._surface_patches = surface_patches self._log_dir = self._resolve_log_dir(log_dir) self._validate_assets_exist() if traffic_history: self._traffic_history = TrafficHistory(traffic_history) default_lane_width = self.traffic_history.lane_width else: self._traffic_history = None default_lane_width = None net_file = os.path.join(self._root, "map.net.xml") self._road_network = SumoRoadNetwork.from_file( net_file, default_lane_width=default_lane_width, lanepoint_spacing=1.0 ) self._net_file_hash = file_md5_hash(self._road_network.net_file) self._scenario_hash = path2hash(str(Path(self.root_filepath).resolve()))
def __init__( self, scenario_root: str, route: str = None, missions: Dict[str, Mission] = None, social_agents: Dict[str, SocialAgent] = None, log_dir: str = None, surface_patches: list = None, traffic_history: str = None, ): self._logger = logging.getLogger(self.__class__.__name__) self._root = scenario_root self._route = route self._missions = missions or {} self._bubbles = Scenario._discover_bubbles(scenario_root) self._social_agents = social_agents or {} self._surface_patches = surface_patches self._log_dir = self._resolve_log_dir(log_dir) self._validate_assets_exist() self._road_network = SumoRoadNetwork.from_file(self.net_filepath) self._net_file_hash = file_md5_hash(self.net_filepath) self._waypoints = Waypoints(self._road_network, spacing=1.0) self._scenario_hash = path2hash(str( Path(self.root_filepath).resolve())) self._traffic_history_service = Traffic_history_service( traffic_history)
def from_file(cls, net_file: str): """Constructs a route generator from the given file Args: net_file: The path to a '\\*.net.xml' file (generally 'map.net.xml') """ # XXX: Spacing is crudely "large enough" so we less likely overlap vehicles road_network = SumoRoadNetwork.from_file(net_file, lanepoint_spacing=2.0) return cls(road_network)
def map_bounding_box(self): # This function returns the following tuple: # (bbox length, bbox width, bbox center) net_file = os.path.join(self._root, "map.net.xml") road_network = SumoRoadNetwork.from_file(net_file) # 2D bbox in format (xmin, ymin, xmax, ymax) bounding_box = road_network.graph.getBoundary() bounding_box_length = bounding_box[2] - bounding_box[0] bounding_box_width = bounding_box[3] - bounding_box[1] bounding_box_center = [ (bounding_box[0] + bounding_box[2]) / 2, (bounding_box[1] + bounding_box[3]) / 2, 0, ] return (bounding_box_length, bounding_box_width, bounding_box_center)
def discover_agent_missions(scenario_root, agents_to_be_briefed): """Returns a sequence of {agent_id: mission} mappings. If no missions are discovered we generate random ones. If there is only one agent to be briefed we return a list of `{agent_id: mission}` cycling through each mission. If there are multiple agents to be briefed we assume that each one is intended to get its own mission and that `len(agents_to_be_briefed) == len(missions)`. In this case a list of one dictionary is returned. """ net_file = os.path.join(scenario_root, "map.net.xml") road_network = SumoRoadNetwork.from_file(net_file) missions = [] missions_file = os.path.join(scenario_root, "missions.pkl") if os.path.exists(missions_file): with open(missions_file, "rb") as f: missions = pickle.load(f) missions = [ Scenario._extract_mission(actor_and_mission.mission, road_network) for actor_and_mission in missions ] if not missions: missions = [None for _ in range(len(agents_to_be_briefed))] if len(agents_to_be_briefed) == 1: # single-agent, so we cycle through all missions individually. return missions elif len(agents_to_be_briefed) > 1: # multi-agent, so we assume missions "drive" the agents (i.e. one # mission per agent) and we will not be cycling through missions. assert not missions or len(missions) == len(agents_to_be_briefed), ( "You must either provide an equal number of missions ({}) to " "agents ({}) or provide no missions at all so they can be " "randomly generated.".format(len(missions), len(agents_to_be_briefed))) return missions
def is_valid_scenario(scenario_root): """Checks if the scenario_root directory matches our expected scenario structure >>> Scenario.is_valid_scenario("scenarios/loop") True >>> Scenario.is_valid_scenario("scenarios/non_existant") False """ paths = [ os.path.join(scenario_root, "map.net.xml"), ] for f in paths: if not os.path.exists(f): return False # make sure we can load the sumo network net_file = os.path.join(scenario_root, "map.net.xml") net = SumoRoadNetwork.from_file(net_file) if net is None: return False return True
def road_network(): return SumoRoadNetwork.from_file( "scenarios/intersections/4lane_t/map.net.xml")
def resolve_edge_length(self, edge_id, lane_id): if not self._road_network: self._road_network = SumoRoadNetwork.from_file( self._road_network_path) lane = self._road_network.edge_by_id(edge_id).getLanes()[lane_id] return lane.getLength()
def generate_glb_from_sumo_network(sumo_net_file, out_glb_file): road_network = SumoRoadNetwork.from_file(net_file=sumo_net_file) glb = road_network.build_glb(scale=1000) glb.write_glb(out_glb_file)
def _discover_social_agents_info( scenario, ) -> Sequence[Dict[str, SocialAgent]]: """Loops through the social agent mission pickles, instantiating corresponding implementations for the given types. The output is a list of {agent_id: (mission, locator)}, where each dictionary corresponds to the social agents to run for a given concrete Scenario (which translates to "per episode" when swapping). """ scenario_root = ( scenario.root_filepath if isinstance(scenario, Scenario) else scenario ) net_file = os.path.join(scenario_root, "map.net.xml") road_network = SumoRoadNetwork.from_file(net_file) social_agents_path = os.path.join(scenario_root, "social_agents") if not os.path.exists(social_agents_path): return [] # [ ( missions_file, agent_actor, Mission ) ] agent_bucketer = [] # like dict.setdefault def setdefault(l: Sequence[Any], index: int, default): while len(l) < index + 1: l.append([]) return l[index] file_match = os.path.join(social_agents_path, "*.pkl") for missions_file_path in glob.glob(file_match): with open(missions_file_path, "rb") as missions_file: count = 0 missions = pickle.load(missions_file) for mission_and_actor in missions: # Each pickle file will contain a list of actor/mission pairs. The pairs # will most likely be generated in an M:N fashion # (i.e. A1: M1, A1: M2, A2: M1, A2: M2). The desired behavior is to have # a single pair per concrete Scenario (which would translate to # "per episode" when swapping) assert isinstance( mission_and_actor.actor, sstudio_types.SocialAgentActor ) actor = mission_and_actor.actor extracted_mission = Scenario._extract_mission( mission_and_actor.mission, road_network ) namespace = os.path.basename(missions_file_path) namespace = os.path.splitext(namespace)[0] setdefault(agent_bucketer, count, []).append( ( SocialAgent( id=SocialAgentId.new(actor.name, group=namespace), name=actor.name, is_boid=False, is_boid_keep_alive=False, agent_locator=actor.agent_locator, policy_kwargs=actor.policy_kwargs, initial_speed=actor.initial_speed, ), extracted_mission, ) ) count += 1 social_agents_info = [] for l in agent_bucketer: social_agents_info.append( {agent.id: (agent, mission) for agent, mission in l} ) return social_agents_info
def _cache_road_network(self): if not self._road_network: self._road_network = SumoRoadNetwork.from_file(self._road_network_path)