예제 #1
0
    def test_json_back_and_forth(self) -> None:
        """ test converting back and forth from and to json """
        # GIVEN
        input_dict = TestConflictInputValidation.get_default_inputs()

        # WHEN
        conflict = Conflict(**input_dict)

        # THEN converting back and forth should in the end give the same result
        conflict_dict = conflict.to_json()
        conflict_from_json = Conflict.from_json(conflict_dict=conflict_dict)
        self.assertDictEqual(conflict_dict, conflict_from_json.to_json())
예제 #2
0
 def get_default_inputs() -> Dict:
     """ Function to get default (valid) inputs for Intersection() """
     signalgroups = [SignalGroup(id=f"sg{i+1}", traffic_lights=[TrafficLight(capacity=1800, lost_time=1)],
                                 min_greenyellow=10, max_greenyellow=80, min_red=10, max_red=80) for i in range(6)]
     conflicts = [Conflict(id1="sg1", id2="sg2", setup12=1, setup21=2),
                  Conflict(id1="sg1", id2="sg6", setup12=1, setup21=2),
                  Conflict(id1="sg2", id2="sg6", setup12=1, setup21=2)]
     sync_starts = [SyncStart(from_id="sg1", to_id="sg3")]
     offsets = [Offset(from_id="sg1", to_id="sg4", seconds=10)]
     periodic_orders = [PeriodicOrder(order=["sg1", "sg2", "sg6"])]
     greenyellow_leads = [GreenyellowLead(from_id="sg1", to_id="sg5", min_seconds=1, max_seconds=10)]
     greenyellow_trails = [GreenyellowTrail(from_id="sg5", to_id="sg1", min_seconds=2, max_seconds=8)]
     return dict(signalgroups=signalgroups, conflicts=conflicts, sync_starts=sync_starts,
                 offsets=offsets, greenyellow_leads=greenyellow_leads, greenyellow_trails=greenyellow_trails,
                 periodic_orders=periodic_orders)
    def _expect_valid_fixed_order(self,
                                  expect_valid: bool = True,
                                  shift: float = 0) -> None:
        # assume all signalgroups are conflicting for this test
        conflicts = []
        for signalgroup1, signalgroup2 in combinations(self._signal_groups, 2):
            if signalgroup1 == signalgroup2:
                continue
            conflicts.append(
                Conflict(id1=signalgroup1.id,
                         id2=signalgroup2.id,
                         setup12=1,
                         setup21=1))
        intersection = Intersection(signalgroups=self._signal_groups,
                                    conflicts=conflicts,
                                    periodic_orders=self._fixed_orders)

        fts_dict = {
            "period": self._period,
            "greenyellow_intervals": {
                name: [[(t + shift) % self._period for t in interval]
                       for interval in intervals]
                for name, intervals in self._greenyellow_intervals.items()
            }
        }

        fts = FixedTimeSchedule.from_json(fts_dict=fts_dict)
        valid = True
        try:
            validate_fixed_orders(intersection=intersection, fts=fts)
        except SafetyViolation:
            valid = False

        self.assertEqual(valid, expect_valid)
예제 #4
0
    def test_successful_validation(self) -> None:
        """ Test initializing Conflict object with correct input """
        # GIVEN
        input_dict = TestConflictInputValidation.get_default_inputs()

        # WHEN
        Conflict(**input_dict)
    def get_default_intersection(
        additional_signalgroups: Optional[List[SignalGroup]] = None,
        additional_conflicts: Optional[List[Conflict]] = None,
    ) -> Intersection:
        """
        Get a default intersection object with 2 conflicting signal groups "sg1" and "sg2"
        :param additional_signalgroups: signal groups to add to the intersection (besides signal group 'sg1' and 'sg2')
        :param additional_conflicts: additional conflicts to add
         (besides the conflict between signal group 'sg1' and 'sg2')
        :return: the intersection object
        """
        if additional_signalgroups is None:
            additional_signalgroups = []
        if additional_conflicts is None:
            additional_conflicts = []

        signalgroup1 = TestFTSValidationOfBounds.get_default_signalgroup(
            name="sg1")
        signalgroup2 = TestFTSValidationOfBounds.get_default_signalgroup(
            name="sg2")

        conflict = Conflict(id1="sg1", id2="sg2", setup12=2, setup21=3)

        intersection = Intersection(signalgroups=[signalgroup1, signalgroup2] +
                                    additional_signalgroups,
                                    conflicts=[conflict] +
                                    additional_conflicts)

        return intersection
    def test_conflict_satisfied(self) -> None:
        """
        test that validations pass if constraints are satisfied
        :return:
        """
        # GIVEN
        signalgroup1 = TestFTSConflictValidation.get_default_signalgroup(
            name="sg1")
        signalgroup2 = TestFTSConflictValidation.get_default_signalgroup(
            name="sg2")

        conflict = Conflict(id1="sg1", id2="sg2", setup12=2, setup21=3)

        intersection = Intersection(signalgroups=[signalgroup1, signalgroup2],
                                    conflicts=[conflict])

        fts = FixedTimeSchedule(greenyellow_intervals=dict(
            sg1=[
                GreenYellowInterval(start_greenyellow=90, end_greenyellow=10),
                GreenYellowInterval(start_greenyellow=33, end_greenyellow=60)
            ],
            sg2=[
                GreenYellowInterval(start_greenyellow=12, end_greenyellow=30),
                GreenYellowInterval(start_greenyellow=62, end_greenyellow=87)
            ]),
                                period=100)

        for interval_shift in range(2):
            with self.subTest(f"interval_shift={interval_shift}"):
                fts._greenyellow_intervals["sg2"] = \
                    fts._greenyellow_intervals["sg2"][:interval_shift] + \
                    fts._greenyellow_intervals["sg2"][interval_shift:]
                # WHEN validating
                validate_conflicts(intersection=intersection, fts=fts)
예제 #7
0
 def test_setup_sum_negative(self) -> None:
     """ Test sum of setups being negative """
     # GIVEN
     input_dict = TestConflictInputValidation.get_default_inputs()
     input_dict["setup12"] = 0
     input_dict["setup21"] = -1
     with self.assertRaises(ValueError):
         Conflict(**input_dict)
예제 #8
0
 def test_non_unique_ids(self) -> None:
     """ Test giving two identical ids to initialize a Conflict """
     # GIVEN
     input_dict = TestConflictInputValidation.get_default_inputs()
     input_dict["id1"] = "1"
     input_dict["id2"] = "1"
     with self.assertRaises(ValueError):
         Conflict(**input_dict)
예제 #9
0
 def test_wrong_datatype_for_numbers(self) -> None:
     """ Test giving wrong datatype to Conflict for numbers """
     for key in ["setup12", "setup21"]:
         with self.subTest(f"Wrong type in input '{key}'"):
             # GIVEN
             input_dict = TestConflictInputValidation.get_default_inputs()
             input_dict[key] = 'string'  # all arguments are numbers
             with self.assertRaises(ValueError):
                 Conflict(**input_dict)
예제 #10
0
    def test_wrong_datatype_for_ids(self) -> None:
        """ Test giving wrong datatype to Conflict for ids """
        # GIVEN
        input_dict = TestConflictInputValidation.get_default_inputs()
        input_dict["id1"] = 1
        input_dict["id2"] = 2
        # WHEN initializing the conflict
        conflict = Conflict(**input_dict)

        # Should not give an error (datatype is converted to string)
        self.assertEqual(conflict.id1, "1")
        self.assertEqual(conflict.id2, "2")
    def test_violating_conflict(self) -> None:
        """
        test that validations fails if minimum clearance times are violated.
        """
        # GIVEN
        signalgroup1 = TestFTSConflictValidation.get_default_signalgroup(
            name="sg1")
        signalgroup2 = TestFTSConflictValidation.get_default_signalgroup(
            name="sg2")

        conflict = Conflict(id1="sg1", id2="sg2", setup12=2, setup21=3)

        intersection = Intersection(signalgroups=[signalgroup1, signalgroup2],
                                    conflicts=[conflict])

        fts = FixedTimeSchedule(greenyellow_intervals=dict(
            sg1=[
                GreenYellowInterval(start_greenyellow=90, end_greenyellow=10),
                GreenYellowInterval(start_greenyellow=33, end_greenyellow=60)
            ],
            sg2=[
                GreenYellowInterval(start_greenyellow=12, end_greenyellow=30),
                GreenYellowInterval(start_greenyellow=62, end_greenyellow=87)
            ]),
                                period=100)

        for signalgroup_id, interval_index, interval_shift in product(
            ["sg1", "sg2"], [0, 1], [0]):
            with self.subTest(
                    f"signalgroup_id={signalgroup_id}, interval_index={interval_index}, "
                    f"interval_shift={interval_shift}"):
                # adjusting schedule such that the start of greenyellow interval 'interval_index' of signalgroup_id
                # violates the minimum clearance time
                fts_copy = deepcopy(fts)
                if signalgroup_id == "sg1":
                    fts_copy._greenyellow_intervals["sg1"][interval_index].start_greenyellow = \
                        (fts_copy._greenyellow_intervals["sg2"][(interval_index + 1) % 2].end_greenyellow +
                         conflict.setup21 - 1) % fts_copy.period
                if signalgroup_id == "sg2":
                    fts_copy._greenyellow_intervals["sg2"][interval_index].start_greenyellow = \
                        (fts_copy._greenyellow_intervals["sg1"][interval_index].end_greenyellow +
                         conflict.setup12 - 1) % fts_copy.period

                fts_copy._greenyellow_intervals["sg2"] = fts_copy._greenyellow_intervals["sg2"][:interval_shift] + \
                    fts_copy._greenyellow_intervals["sg2"][interval_shift:]

                with self.assertRaises(SafetyViolation):
                    # WHEN validating
                    validate_conflicts(intersection=intersection, fts=fts_copy)
    def get_default_intersection(additional_signalgroups: Optional[List[SignalGroup]] = None,
                                 sync_starts: List[SyncStart] = None,
                                 offsets: List[Offset] = None,
                                 greenyellow_leads: List[GreenyellowLead] = None,
                                 greenyellow_trails: List[GreenyellowTrail] = None,
                                 ) -> Intersection:
        """
        Get a default intersection object with 2 conflicting signal groups "sg1" and "sg2"
        :param additional_signalgroups: signal groups to add to the intersection (besides signal group 'sg1' and 'sg2')
        :param sync_starts: SyncStarts that must be satisfied
        :param offsets: Coordinations that must be satisfied
        :param greenyellow_leads: GreenyellowLeads that must be satisfied
        :param greenyellow_trails: GreenyellowTrails that must be satisfied
        :return: the intersection object
        """
        if additional_signalgroups is None:
            additional_signalgroups = []

        if sync_starts is None:
            sync_starts = []
        if offsets is None:
            offsets = []
        if greenyellow_leads is None:
            greenyellow_leads = []
        if greenyellow_trails is None:
            greenyellow_trails = []

        signalgroup1 = TestFTSOtherSGRelationValidation.get_default_signalgroup(name="sg1")
        signalgroup2 = TestFTSOtherSGRelationValidation.get_default_signalgroup(name="sg2")

        conflict = Conflict(id1="sg1", id2="sg2", setup12=2, setup21=3)

        intersection = Intersection(signalgroups=[signalgroup1, signalgroup2] + additional_signalgroups,
                                    conflicts=[conflict], sync_starts=sync_starts, offsets=offsets,
                                    greenyellow_leads=greenyellow_leads, greenyellow_trails=greenyellow_trails)

        return intersection
예제 #13
0
def create_intersection_and_optimize():
    """
    Example showing how to:
    - create traffic lights, signal groups and intersections, ...
    - optimize a fixed-time schedule for this intersection

    NOTE:
    To run the example below you need credentials to invoke the swift mobility cloud api.
    To this end, you need to specify the following environment variables:
    - smc_api_key: the access key of your swift mobility cloud api account
    - smc_api_secret: the secret access key of your swift mobility cloud api account
    If you do not have such an account yet, please contact [email protected].
    """
    logging.info(f"Running example '{os.path.basename(__file__)}'")
    # signal group consisting of two traffic light allowing 1 or 2 greenyellow intervals per repeating period.
    traffic_light1 = TrafficLight(capacity=1800, lost_time=2.2)
    traffic_light2 = TrafficLight(capacity=1810, lost_time=2.1)
    signalgroup1 = SignalGroup(id="2",
                               traffic_lights=[traffic_light1, traffic_light2],
                               min_greenyellow=10,
                               max_greenyellow=100,
                               min_red=10,
                               max_red=100,
                               min_nr=1,
                               max_nr=2)

    # signal group consisting of one traffic light allowing 1 greenyellow interval (default) per repeating period.
    traffic_light3 = TrafficLight(capacity=1650, lost_time=3.0)
    signalgroup2 = SignalGroup(id="5",
                               traffic_lights=[traffic_light3],
                               min_greenyellow=10,
                               max_greenyellow=100,
                               min_red=10,
                               max_red=100)

    # signal group consisting of one traffic light allowing 1 greenyellow interval (default) per repeating period.
    traffic_light4 = TrafficLight(capacity=1800, lost_time=2.1)
    signalgroup3 = SignalGroup(id="8",
                               traffic_lights=[traffic_light4],
                               min_greenyellow=10,
                               max_greenyellow=100,
                               min_red=10,
                               max_red=100)

    # conflicts & clearance times
    conflict12 = Conflict(id1=signalgroup1.id,
                          id2=signalgroup2.id,
                          setup12=1,
                          setup21=2)
    conflict13 = Conflict(id1=signalgroup1.id,
                          id2=signalgroup3.id,
                          setup12=1,
                          setup21=2)
    conflict23 = Conflict(id1=signalgroup2.id,
                          id2=signalgroup3.id,
                          setup12=2,
                          setup21=3)

    # initialize intersection object
    intersection = Intersection(
        signalgroups=[signalgroup1, signalgroup2, signalgroup3],
        conflicts=[conflict12, conflict13, conflict23])

    # set associated arrival rates
    arrival_rates = ArrivalRates(id_to_arrival_rates={
        "2": [800, 700],
        "5": [150],
        "8": [180]
    })

    logging.info(f"Minimizing average experienced delay")
    # optimize fixed-time schedule
    fixed_time_schedule, phase_diagram, objective_value, _ = SwiftMobilityCloudApi.get_optimized_fts(
        intersection=intersection,
        arrival_rates=arrival_rates,
        objective=ObjectiveEnum.min_delay)

    logging.info(f"Average experienced delay {objective_value: .3f} seconds")
    logging.info(fixed_time_schedule)
    logging.info(phase_diagram)

    # the following code indicates how to compute a phase diagram from a fixed-time schedule (note that now it makes
    #  no sense to do so as it was already computed above)
    logging.info(
        "Computing phase diagram from fixed-time schedule. Should be the same as before"
    )
    phase_diagram = SwiftMobilityCloudApi.get_phase_diagram(
        intersection=intersection, fixed_time_schedule=fixed_time_schedule)
    logging.info(phase_diagram)
예제 #14
0
    def from_json(intersection_dict: Dict) -> Intersection:
        """
        Loading intersection from json (expected same json structure as generated with to_json)
        :param intersection_dict:
        :return: intersection object
        """
        # load signal groups
        signalgroups = [
            SignalGroup.from_json(signalgroup_dict=signalgroup_dict)
            for signalgroup_dict in intersection_dict["signalgroups"]
        ]

        if "periodic_orders" in intersection_dict:
            periodic_orders = [
                PeriodicOrder.from_json(order_dict=order_dict)
                for order_dict in intersection_dict["periodic_orders"]
            ]
        else:
            periodic_orders = []

        # load conflicts
        conflicts = [
            Conflict.from_json(conflict_dict=conflict_dict)
            for conflict_dict in intersection_dict["conflicts"]
        ]

        # load other relations (synchronous starts, offsets and greenyellow_lead)
        sync_starts = []
        offsets = []
        greenyellow_leads = []
        greenyellow_trails = []
        for other_relation_dict in intersection_dict["other_relations"]:
            assert other_relation_dict["from_start_gy"] == other_relation_dict["to_start_gy"], \
                "besides conflicts, at the moment the cloud api can only handle synchronous starts, offsets, " \
                "greenyellow-leads and greenyellow-trails."
            if other_relation_dict[
                    "from_start_gy"] is True and other_relation_dict[
                        "to_start_gy"] is True:
                if other_relation_dict["min_time"] == other_relation_dict[
                        "max_time"]:
                    if other_relation_dict["min_time"] == 0:  # sync start
                        sync_starts.append(
                            SyncStart.from_json(
                                sync_start_dict=other_relation_dict))
                    else:  # offset
                        offsets.append(
                            Offset.from_json(offset_dict=other_relation_dict))
                else:  # greenyellow-leads
                    greenyellow_leads.append(
                        GreenyellowLead.from_json(
                            json_dict=other_relation_dict))
            elif other_relation_dict[
                    "from_start_gy"] is False and other_relation_dict[
                        "to_start_gy"] is False:
                greenyellow_trails.append(
                    GreenyellowTrail.from_json(json_dict=other_relation_dict))

        return Intersection(signalgroups=signalgroups,
                            conflicts=conflicts,
                            sync_starts=sync_starts,
                            offsets=offsets,
                            greenyellow_leads=greenyellow_leads,
                            greenyellow_trails=greenyellow_trails,
                            periodic_orders=periodic_orders)
예제 #15
0
def fix_order_and_optimize():
    """
    This example shows how to ask for a fixed-time schedule that adheres to a specified fix order in which
    the signalgroups should receive their greenyellow interval.
    """
    logging.info(f"Running example '{os.path.basename(__file__)}'")
    # signal group consisting of two traffic light allowing 1 or 2 greenyellow intervals per repeating period.
    traffic_light1 = TrafficLight(capacity=1800, lost_time=2.2)
    traffic_light2 = TrafficLight(capacity=1810, lost_time=2.1)
    signalgroup1 = SignalGroup(id="2",
                               traffic_lights=[traffic_light1, traffic_light2],
                               min_greenyellow=10,
                               max_greenyellow=100,
                               min_red=10,
                               max_red=100,
                               min_nr=1,
                               max_nr=2)

    # signal group consisting of one traffic light allowing 1 greenyellow interval (default) per repeating period.
    traffic_light3 = TrafficLight(capacity=1650, lost_time=3.0)
    signalgroup2 = SignalGroup(id="5",
                               traffic_lights=[traffic_light3],
                               min_greenyellow=10,
                               max_greenyellow=100,
                               min_red=10,
                               max_red=100)

    # signal group consisting of one traffic light allowing 1 greenyellow interval (default) per repeating period.
    traffic_light4 = TrafficLight(capacity=1800, lost_time=2.1)
    signalgroup3 = SignalGroup(id="8",
                               traffic_lights=[traffic_light4],
                               min_greenyellow=10,
                               max_greenyellow=100,
                               min_red=10,
                               max_red=100)

    # conflicts & clearance times
    conflict12 = Conflict(id1=signalgroup1.id,
                          id2=signalgroup2.id,
                          setup12=1,
                          setup21=2)
    conflict13 = Conflict(id1=signalgroup1.id,
                          id2=signalgroup3.id,
                          setup12=2,
                          setup21=1)
    conflict23 = Conflict(id1=signalgroup2.id,
                          id2=signalgroup3.id,
                          setup12=2,
                          setup21=3)

    # initialize intersection object
    intersection = Intersection(
        signalgroups=[signalgroup1, signalgroup2, signalgroup3],
        conflicts=[conflict12, conflict13, conflict23])

    # set associated arrival rates
    arrival_rates = ArrivalRates(id_to_arrival_rates={
        "2": [800, 700],
        "5": [150],
        "8": [180]
    })
    logging.info(
        f"Not yet requesting any fixed order of greenyellow intervals")
    logging.info(f"Minimizing average experienced delay")
    # optimize fixed-time schedule
    fixed_time_schedule, phase_diagram, objective_value, _ = SwiftMobilityCloudApi.get_optimized_fts(
        intersection=intersection,
        arrival_rates=arrival_rates,
        objective=ObjectiveEnum.min_delay)

    logging.info(f"Average experienced delay {objective_value: .3f} seconds")
    logging.info(fixed_time_schedule)
    logging.info(phase_diagram)

    logging.info(f"Requesting order: 2 -> 8 -> 5 -> ")
    # initialize intersection object
    intersection = Intersection(
        signalgroups=[signalgroup1, signalgroup2, signalgroup3],
        conflicts=[conflict12, conflict13, conflict23],
        periodic_orders=[PeriodicOrder(order=["2", "8", "5"])])
    logging.info(f"Minimizing average experienced delay")
    # optimize fixed-time schedule
    fixed_time_schedule, phase_diagram, objective_value, _ = SwiftMobilityCloudApi.get_optimized_fts(
        intersection=intersection,
        arrival_rates=arrival_rates,
        objective=ObjectiveEnum.min_delay)

    logging.info(f"Average experienced delay {objective_value: .3f} seconds")
    logging.info(fixed_time_schedule)
    logging.info(phase_diagram)