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))
Example #4
0
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}'.")
Example #5
0
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)
Example #6
0
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
Example #7
0
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
Example #8
0
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