def test_retrieving_for_unkown_signalgroup(self): """ test retrieving greenyellow intervals of an unkown signal group """ # GIVEN input_dict = TestFTSMethods.get_default_fts_inputs() # unkown signal group sg3 = SignalGroup( id="sg3", traffic_lights=[TrafficLight(capacity=800, lost_time=1)], min_greenyellow=10, max_greenyellow=80, min_red=10, max_red=80) fts = FixedTimeSchedule(**input_dict) with self.assertRaises(ValueError): # WHEN trying to access greenyellow intervals of an unkown signal group fts.get_greenyellow_intervals(sg3) # THEN an error should be raised # same but using id with self.assertRaises(ValueError): # WHEN trying to access greenyellow intervals of an unkown signal group fts.get_greenyellow_intervals(sg3.id)
def test_retrieving_greenyellow_intervals(self): """ test retrieving all greenyellow intervals of a signal group """ # GIVEN input_dict = TestFTSMethods.get_default_fts_inputs() sg1 = SignalGroup( id="sg1", traffic_lights=[TrafficLight(capacity=800, lost_time=1)], min_greenyellow=10, max_greenyellow=80, min_red=10, max_red=80) sg2 = SignalGroup( id="sg2", traffic_lights=[TrafficLight(capacity=800, lost_time=1)], min_greenyellow=10, max_greenyellow=80, min_red=10, max_red=80) # WHEN fts = FixedTimeSchedule(**input_dict) # THEN self.assertEqual(fts.get_greenyellow_intervals(signalgroup=sg1), input_dict["greenyellow_intervals"]["sg1"]) self.assertEqual(fts.get_greenyellow_intervals(signalgroup=sg2), input_dict["greenyellow_intervals"]["sg2"])
def test_retrieving_greenyellow_intervals_by_id(self): # GIVEN input_dict = TestFTSMethods.get_default_fts_inputs() sg1 = SignalGroup( id="sg1", traffic_lights=[TrafficLight(capacity=800, lost_time=1)], min_greenyellow=10, max_greenyellow=80, min_red=10, max_red=80) sg2 = SignalGroup( id="sg2", traffic_lights=[TrafficLight(capacity=800, lost_time=1)], min_greenyellow=10, max_greenyellow=80, min_red=10, max_red=80) # WHEN fts = FixedTimeSchedule(**input_dict) # using id should give the same result self.assertEqual(fts.get_greenyellow_intervals(signalgroup=sg1), fts.get_greenyellow_intervals(signalgroup=sg1.id)) self.assertEqual(fts.get_greenyellow_intervals(signalgroup=sg2), fts.get_greenyellow_intervals(signalgroup=sg2.id))
def validate_conflicts(intersection: Intersection, fts: FixedTimeSchedule, tolerance: float = 10**(-2)): """ Ensure all conflicts are satisfied. :param intersection: intersection object (this object contains all conflicts and associated minimum clearance times that should be satisfied) :param fts: fixed-time schedule to check :param tolerance: tolerance in seconds for violating safety restrictions :raises SafetyViolation if validations fail """ for conflict in intersection.conflicts: intervals1 = fts.get_greenyellow_intervals(signalgroup=conflict.id1) intervals2 = fts.get_greenyellow_intervals(signalgroup=conflict.id2) for index1, interval1 in enumerate(intervals1): for index2, interval2 in enumerate(intervals2): if not conflict_satisfied(interval1=interval1, interval2=interval2, period=fts.period, conflict=conflict, tolerance=tolerance): raise SafetyViolation( f"Conflict not satified for interval {index1:d} of '{conflict.id1:s}' " f"and interval {index2:d} of '{conflict.id2:s}'.")
def get_other_sg_relation_shift( other_relation: Union[Offset, GreenyellowLead, SyncStart], fts: FixedTimeSchedule, tolerance: float = 10**(-2)) -> Optional[int]: """ Find a shift 'shift' of the greenyellow intervals such that the specified inter signal group relation is satisfied for each pair {(id_from, index), (id_to, index + shift)} of greenyellow intervals of signal groups id_from and id_to, where (id, index) refers to the greenyellow interval with index 'index' of signal group with id 'id'. :param other_relation: the inter signal group relation for which we want to find the shift. :param fts: fixed-time schedule. :param tolerance: tolerance in seconds for violating safety restrictions :return: the shift (None if no such shift can be found). """ # Get the greenyellow intervals of the associated signal groups intervals_from = fts.get_greenyellow_intervals( signalgroup=other_relation.from_id) intervals_to = fts.get_greenyellow_intervals( signalgroup=other_relation.to_id) if len(intervals_from) != len(intervals_to): raise SafetyViolation( f"Signal groups {other_relation.__class__} should have the same number of GreenYellowPhases;" f"this is not satisfied for signalgroups {other_relation.from_id} and {other_relation.to_id}" ) # Matrix of size len(intervals_to) x len(intervals_from) matches = [[False] * len(intervals_to)] * len(intervals_from) # for each greenyellow interval of signal group with id 'other_relation.from_id' we try to find which of the # greenyellow intervals of the signal group with id 'other_relation.to_id' satisfy the specified inter signal group # relation w.r.t. this greenyellow interval for index_from, interval_from in enumerate(intervals_from): matches[index_from] = find_other_sg_relation_matches( other_relation=other_relation, fts=fts, index_from=index_from, tolerance=tolerance) # does an unambiguous shift (reindexing) of the greenyellow intervals of signal group with id 'other_relation.to_id' # exist return get_shift_of_one_to_one_match(matches=matches)
def validate_bounds(intersection: Intersection, fts: FixedTimeSchedule, tolerance: float = 10**(-2)): """ Ensure that all bounds on greenyellow and red times are satiesfied for the specified fixed-time schedule. :param intersection: intersection object (this object also contains safety restrictions that a fixed-time schedule should satisfy) :param fts: FixedTimeSchedule object for which we want to check the safety restrictions :param tolerance: tolerance in seconds for violating safety restrictions :raises SafetyViolation if validations fail """ # check the duration of greenyellow times and red times for signalgroup in intersection.signalgroups: greenyellow_intervals = fts.get_greenyellow_intervals( signalgroup=signalgroup) # end of the last greenyellow interval prev_red_switch = greenyellow_intervals[-1].end_greenyellow # loop over the greenyellow intervals for _, interval in enumerate(greenyellow_intervals): # the duration of the red interval preceeding this greenyellow interval red_time = (interval.start_greenyellow - prev_red_switch + tolerance) % fts.period - tolerance # the duration of the greenyellow interval greenyellow_time = (interval.end_greenyellow - interval.start_greenyellow + tolerance) % fts.period - \ tolerance # check these durations for violations of the minimum and maximum durations if red_time < signalgroup.min_red - tolerance: raise SafetyViolation( f"Red time of sg '{signalgroup.id}' too short ({red_time:3.1f} seconds while " f"min={signalgroup.min_red:3.1f})") if red_time > signalgroup.max_red + tolerance: raise SafetyViolation( f"Red time of sg '{signalgroup.id}' too long ({red_time:3.1f} seconds " f"while max={signalgroup.max_red:3.1f})") if greenyellow_time < signalgroup.min_greenyellow - tolerance: raise SafetyViolation( f"Greenyellow time of sg '{signalgroup.id}' too short ({greenyellow_time:3.1f} seconds while " f"min={signalgroup.min_greenyellow:3.1f})") if greenyellow_time > signalgroup.max_greenyellow + tolerance: raise SafetyViolation( f"Greenyellow time of sg '{signalgroup.id}' too large ({greenyellow_time:3.1f} seconds while " f"max={signalgroup.max_greenyellow:3.1f})") prev_red_switch = interval.end_greenyellow
def validate_fixed_order(intersection: Intersection, fts: FixedTimeSchedule, periodic_order: PeriodicOrder) -> None: """ Validate that the the signalgroups indeed receive their greenyellow intervals in the requested periodic order (for only the periodic order that is given as argument). :return: - :raises SafetyException: if the requested order is not satisfied""" first_signalgroup = intersection.get_signalgroup( signalgroup_id=periodic_order.order[0]) first_interval_start = fts.get_greenyellow_interval(first_signalgroup, k=0).start_greenyellow prev_switch = 0 for signalgroup in periodic_order.order: for interval in fts.get_greenyellow_intervals(signalgroup): # shift schedule such that first greenyellow interval of the first signalgroup in the order starts at time=0 switch = (interval.start_greenyellow - first_interval_start + EPSILON) % fts.period - EPSILON if switch < prev_switch: raise SafetyViolation( f"Periodic order {periodic_order.to_json()} is violated") prev_switch = switch
def find_other_sg_relation_matches( other_relation: Union[SyncStart, Offset, GreenyellowLead, GreenyellowTrail], fts: FixedTimeSchedule, index_from: int, tolerance: float = 10**(-2)) -> List[bool]: """ Find the greenyellow intervals of the signal group with id 'other_relation.to_id' that satisfies the specified inter signalgroup relation w.r.t. the greenyellow interval of signal group other_relation.from_id at index 'index_from' :param other_relation: the other relation (sync start, offset or greenyellow-lead) :param fts: fixed-time schedule :param index_from: see above :param tolerance: tolerance in seconds for violating safety restrictions :return: boolean list indicating the matches. """ # Get the greenyellow intervals of the associated signal groups interval_from = fts.get_greenyellow_interval( signalgroup=other_relation.from_id, k=index_from) intervals_to = fts.get_greenyellow_intervals( signalgroup=other_relation.to_id) matches = [False] * len(intervals_to) if isinstance(other_relation, (SyncStart, Offset, GreenyellowLead)): time_from = interval_from.start_greenyellow elif isinstance(other_relation, GreenyellowTrail): time_from = interval_from.end_greenyellow else: raise ValueError(UNKNOWN_TYPE_OTHER_RELATION) for index_to in range(len(intervals_to)): if isinstance(other_relation, (SyncStart, Offset, GreenyellowLead)): time_to = intervals_to[index_to].start_greenyellow elif isinstance(other_relation, GreenyellowTrail): time_to = intervals_to[index_to].end_greenyellow else: raise ValueError(UNKNOWN_TYPE_OTHER_RELATION) # determine the desired range of the time between time_from and time_to. if isinstance(other_relation, SyncStart): min_time = 0 max_time = 0 elif isinstance(other_relation, Offset): min_time = other_relation.seconds max_time = other_relation.seconds elif isinstance(other_relation, (GreenyellowLead, GreenyellowTrail)): min_time = other_relation.min_seconds max_time = other_relation.max_seconds else: raise ValueError(UNKNOWN_TYPE_OTHER_RELATION) # Determine the actual time between time_from and time_to. We correct for min_time potentially being negative. time_between = (time_to - time_from - (min_time - tolerance)) % fts.period + (min_time - tolerance) # Note that result is time_between in [other_relation.min_time, other_relation.min_time + period] if min_time - tolerance < time_between < max_time + tolerance: matches[index_to] = True return matches