Example #1
0
    def test_json_back_and_forth(self) -> None:
        """ Test converting back and forth from and to json """
        # GIVEN
        input_dict = TestInputValidation.get_default_inputs()

        # WHEN
        intersection = Intersection(**input_dict)

        # THEN converting back and forth should in the end give the same result
        intersection_dict = intersection.to_json()
        intersection_from_json = Intersection.from_json(intersection_dict=intersection_dict)
        self.assertDictEqual(intersection_dict, intersection_from_json.to_json())
Example #2
0
    def test_getting_non_existing_signal_group(self):
        """ Test retrieving signal group by id when this id does not exist """
        # GIVEN
        signalgroup1 = SignalGroup(id="sg1", traffic_lights=[TrafficLight(capacity=1800, lost_time=1)],
                                   min_greenyellow=10, max_greenyellow=80, min_red=10, max_red=80)
        signalgroup2 = SignalGroup(id="sg2", traffic_lights=[TrafficLight(capacity=1800, lost_time=1)],
                                   min_greenyellow=10, max_greenyellow=80, min_red=10, max_red=80)
        intersection = Intersection(signalgroups=[signalgroup1, signalgroup2], conflicts=[], sync_starts=[],
                                    offsets=[], greenyellow_leads=[])

        with self.assertRaises(ValueError):
            # WHEN
            intersection.get_signalgroup(signalgroup_id="sg3")
Example #3
0
    def test_getting_signal_group(self):
        """ Test retrieving signal group by id """
        # GIVEN
        signalgroup1 = SignalGroup(id="sg1", traffic_lights=[TrafficLight(capacity=1800, lost_time=1)],
                                   min_greenyellow=10, max_greenyellow=80, min_red=10, max_red=80)
        signalgroup2 = SignalGroup(id="sg2", traffic_lights=[TrafficLight(capacity=1800, lost_time=1)],
                                   min_greenyellow=10, max_greenyellow=80, min_red=10, max_red=80)

        intersection = Intersection(signalgroups=[signalgroup1, signalgroup2], conflicts=[], sync_starts=[],
                                    offsets=[], greenyellow_leads=[])

        # WHEN/THEN
        self.assertEqual(intersection.get_signalgroup(signalgroup_id="sg1"), signalgroup1)
        self.assertEqual(intersection.get_signalgroup(signalgroup_id="sg2"), signalgroup2)
    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
Example #5
0
    def test_multiple_relations(self) -> None:
        """ Test multiple relations being provided for the same pair of signal groups """
        # GIVEN
        input_dict = TestInputValidation.get_default_inputs()

        # WHEN two signal group relations exist for the same signal group pair
        id1 = "new_id1"
        id2 = "new_id2"
        for key1, key2 in product(["conflicts", "sync_starts", "offsets", "greenyellow_leads"],
                                  ["conflicts", "sync_starts", "offsets", "greenyellow_leads"]):
            if key1 == key2:
                continue

            if key1 == "conflicts":
                input_dict[key1][0].id1 = id1
                input_dict[key1][0].id2 = id2
            else:
                input_dict[key1][0].from_id = id2
                input_dict[key1][0].to_id = id1

            if key2 == "conflicts":
                input_dict[key2][0].id1 = id1
                input_dict[key2][0].id2 = id2
            else:
                input_dict[key2][0].from_id = id2
                input_dict[key2][0].to_id = id1

            with self.subTest(f"Two relations ('{key1}' and '{key2}') for same signalgroup pair"):
                with self.assertRaises(ValueError):
                    Intersection(**input_dict)
    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)
Example #7
0
    def test_successful_validation(self) -> None:
        """ Test initializing Intersection object with correct input """
        # GIVEN
        input_dict = TestInputValidation.get_default_inputs()

        # WHEN
        Intersection(**input_dict)
    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)
Example #9
0
    def test_ids_not_unique(self) -> None:
        """ Test for multiple signal groups having the same id """
        # GIVEN
        input_dict = TestInputValidation.get_default_inputs()

        # WHEN an id is used twice
        input_dict["signalgroups"][-1].id = "sg1"  # other object (of wrong type) to the list
        with self.assertRaises(ValueError):
            Intersection(**input_dict)
Example #10
0
def evaluate_fixed_time_schedule(print_fixed_time_schedule: bool = False):
    """
    In this example we show how to evaluate a fixed-time schedule.

    Use case: comparing two fixed-time schedules (perhaps not optimized via this api) on expected performance.

    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].

    In this example, we load an intersection from disk (export of Swift Mobility Desktop). You can download this
    file (example_smd_export.json) from
    https://github.com/stijnfleuren/SwiftCloudApi/tree/master/swift_cloud_py/examples
    """
    logging.info(f"Running example '{os.path.basename(__file__)}'")
    # absolute path to .json file that has been exported from swift mobility desktop
    smd_export = os.path.join(
        os.path.join(os.path.abspath(__file__), os.pardir),
        "example_smd_export.json")

    # retrieve the json structure from the file
    with open(smd_export, "r") as f:
        json_dict = json.load(f)

    logging.info(f"Loading intersection and traffic situation from disk")
    intersection = Intersection.from_json(
        intersection_dict=json_dict["intersection"])
    arrival_rates = ArrivalRates.from_json(
        arrival_rates_dict=json_dict["arrival_rates"])
    logging.info(f"Loaded intersection and traffic situation from disk")
    logging.info(f"Minimizing delay")
    fixed_time_schedule, phase_diagram, objective_value, _ = SwiftMobilityCloudApi.get_optimized_fts(
        intersection=intersection,
        arrival_rates=arrival_rates,
        min_period_duration=30,
        max_period_duration=180,
        objective=ObjectiveEnum.min_delay,
        horizon=2)

    logging.info(f"Average experienced delay: {objective_value:.2f} seconds")

    if print_fixed_time_schedule:
        logging.info(fixed_time_schedule)
        logging.info(phase_diagram)

    # this should return the same estimated delay. With this functionality we could evaluate the expected performance
    #  of any fixed-time schedule.
    logging.info(f"Evaluate this schedule")
    kpis = SwiftMobilityCloudApi.evaluate_fts(
        intersection=intersection,
        fixed_time_schedule=fixed_time_schedule,
        arrival_rates=arrival_rates,
        horizon=2)
    logging.info(f"Output: {kpis}")
Example #11
0
    def test_subsequent_non_conflicting_ids_in_periodic_order(self):
        # GIVEN
        input_dict = TestInputValidation.get_default_inputs()

        # WHEN
        input_dict["periodic_orders"] = [PeriodicOrder(["sg1", "sg2", "sg3"])]

        with self.assertRaises(ValueError):
            Intersection(**input_dict)
Example #12
0
    def test_unknown_ids_in_periodic_order(self):
        # GIVEN
        input_dict = TestInputValidation.get_default_inputs()

        # WHEN
        input_dict["periodic_orders"] = [PeriodicOrder(["sg1", "sg2", "unknown_id"])]

        with self.assertRaises(ValueError):
            Intersection(**input_dict)
Example #13
0
def optimize_multiple_schedules():
    """
    Example showing how to:
    - retrieve intersection information and arrival rates from a json file exported from Swift Mobility Desktop.
    - use this information to optimize fixed-time schedules

    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].

    In this example, we load an intersection from disk (export of Swift Mobility Desktop). This functionality is tested
    with Swift Mobility Desktop 0.7.0.alpha.
    """
    logging.info(f"Running example '{os.path.basename(__file__)}'")
    # absolute path to .json file that has been exported from swift mobility desktop
    smd_export = os.path.join(
        os.path.join(os.path.abspath(__file__), os.pardir),
        "example_smd_export.json")

    # retrieve the json structure from the file
    with open(smd_export, "r") as f:
        json_dict = json.load(f)

    logging.info(f"Loading intersection and traffic situation from disk")
    intersection = Intersection.from_json(
        intersection_dict=json_dict["intersection"])

    arrival_rates = ArrivalRates.from_json(
        arrival_rates_dict=json_dict["arrival_rates"])
    logging.info(f"Loaded intersection and traffic situation from disk")

    logging.info(f"Minimizing average experienced delay")
    best_fixed_time_schedule, best_phase_diagram, objective_value, warm_start_info = \
        SwiftMobilityCloudApi.get_optimized_fts(
            intersection=intersection, arrival_rates=arrival_rates, min_period_duration=30, max_period_duration=180,
            objective=ObjectiveEnum.min_delay)

    logging.info(f"Average experienced delay: {objective_value:.2f} seconds")
    logging.info(best_fixed_time_schedule)
    logging.info(best_phase_diagram)

    logging.info(f"Finding second best schedule")
    second_best_fixed_time_schedule, second_best_phase_diagram, objective_value, warm_start_info = \
        SwiftMobilityCloudApi.get_optimized_fts(
            intersection=intersection, arrival_rates=arrival_rates, min_period_duration=30, max_period_duration=180,
            objective=ObjectiveEnum.min_delay, fixed_time_schedules_to_exclude=[best_fixed_time_schedule],
            warm_start_info=warm_start_info)

    logging.info(
        f"Average experienced delay of second best schedule: {objective_value:.2f} seconds"
    )
    logging.info(second_best_fixed_time_schedule)
    logging.info(second_best_phase_diagram)
Example #14
0
    def test_wrong_type_in_list(self) -> None:
        """ Test providing the wrong type of elements inside the arguments (which are lists) """

        for key in TestInputValidation.get_default_inputs():
            with self.subTest(f"Wrong type in input '{key}'"):
                # GIVEN
                input_dict = TestInputValidation.get_default_inputs()
                input_dict[key].append(10)  # add other object (of wrong type) to the list
                with self.assertRaises(TypeError):
                    # WHEN initializing the intersection
                    Intersection(**input_dict)
Example #15
0
    def test_setup_to_small(self) -> None:
        """ Test for setup time being too small """
        # GIVEN
        input_dict = TestInputValidation.get_default_inputs()

        # WHEN setup12 plus min_greenyellow of signal group sg1 is not strictly positive
        for setup12 in [-10, -11]:  # equal to min_greenyellow or even smaller
            with self.subTest(f"setup too small: '{setup12}'"):
                input_dict["conflicts"][0].setup12 = setup12
                with self.assertRaises(ValueError):
                    Intersection(**input_dict)
Example #16
0
    def test_wrong_type(self) -> None:
        """ Test providing the wrong type of arguments (no list)"""

        # WHEN an input contains the wrong data type
        for key in TestInputValidation.get_default_inputs():
            with self.subTest(f"Wrong type in input '{key}'"):
                # GIVEN
                input_dict = TestInputValidation.get_default_inputs()
                input_dict[key] = 10  # wrong type (not a list)
                with self.assertRaises(TypeError):
                    # WHEN initializing the intersection
                    Intersection(**input_dict)
Example #17
0
    def evaluate_fts(
            cls,
            intersection: Intersection,
            arrival_rates: ArrivalRates,
            fixed_time_schedule: FixedTimeSchedule,
            horizon: float = 2.0,
            initial_queue_lengths: Optional[QueueLengths] = None) -> KPIs:
        """
        Evaluate a fixed-time schedule; returns KPIs (estimated delay experienced by road users and the capacity (
        see also KPIs and the SwiftMobilityCloudApi.get_optimized_fts() method for their definition.
        :param intersection: intersection for which to optimize the fts (contains signal groups, conflicts and more)
        :param arrival_rates: arrival rates; each arrival rate is specified in personal car equivalent per hour (PCE/h)
        cyclists per hour or pedestrians per hour
        :param fixed_time_schedule:
        :param initial_queue_lengths: initial amount of traffic waiting at each of the traffic lights; if None, then we
        assume no initial traffic.
        :param horizon: time period of interest in hours.
        :return KPIs, which are the estimated
        """
        assert horizon >= 1, HORIZON_LB_EXCEEDED_MSG
        if initial_queue_lengths is None:
            # assume no initial traffic
            initial_queue_lengths = QueueLengths({
                signalgroup.id: [0] * len(signalgroup.traffic_lights)
                for signalgroup in intersection.signalgroups
            })

        check_all_arrival_rates_and_queue_lengths_specified(
            intersection=intersection,
            arrival_rates=arrival_rates,
            initial_queue_lengths=initial_queue_lengths)

        endpoint = f"{CLOUD_API_URL}/fts-evaluation"
        headers = SwiftMobilityCloudApi.get_authentication_header()

        # rest-api call
        try:
            # assume that the traffic that is initially present arrives during the horizon.
            corrected_arrival_rates = arrival_rates + initial_queue_lengths / horizon
            json_dict = dict(intersection=intersection.to_json(),
                             arrival_rates=corrected_arrival_rates.to_json(),
                             fixed_time_schedule=fixed_time_schedule.to_json())
            logging.debug(f"calling endpoint {endpoint}")
            r = requests.post(endpoint, json=json_dict, headers=headers)
            logging.debug(f"finished calling endpoint {endpoint}")
        except requests.exceptions.ConnectionError:
            raise UnknownCloudException(CONNECTION_ERROR_MSG)

        # check for errors
        check_status_code(response=r)

        return KPIs.from_json(r.json())
Example #18
0
    def test_unknown_ids(self) -> None:
        """ Test unknown ids being used in relations between signal groups """
        # GIVEN
        input_dict = TestInputValidation.get_default_inputs()

        # WHEN an unknown id is used in a relations between signal groups
        for key in ["conflicts", "sync_starts", "offsets", "greenyellow_leads"]:
            if key == "conflicts":
                input_dict[key][0].id1 = "unknown"
            else:
                input_dict[key][0].from_id = "unknown"
            with self.subTest(f"Unknown id used in input '{key}'"):
                with self.assertRaises(ValueError):
                    Intersection(**input_dict)
    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)
Example #20
0
    def get_phase_diagram(
            cls, intersection: Intersection,
            fixed_time_schedule: FixedTimeSchedule) -> PhaseDiagram:
        """
        Get the phase diagram specifying the order in which the signal groups have their greenyellow intervals
        in the fixed-time schedule
        :param intersection: intersection for which to optimize the fts (contains signal groups, conflicts and more)
        :param fixed_time_schedule: fixed-time schedule for which we want to retrieve the phase diagram.
        :return: the associated phase diagram

        IMPORTANT: we try to start the greenyellow intervals of two signal groups that are subject to a synchronous
        start or a greenyellow-lead in the same phase; however, if this is not possible for all such pairs, then we try to
        satisfy it for as many such pairs as possible.

        For example consider the following theoretical problem where we have three signal groups:
        sg1, sg2, and sg3. sg1 conflicts with sg2 and sg3. sg2 has a greenyellow_lead(min=30, max=50) w.r.t. sg3.
        The following schedule is feasible
        greenyellow_intervals = {"sg1": [[0, 10], [40, 50]], "sg2": [[20, 30]], "sg3": [[60,70]]}
        period=80

        However, it is not possible to find a phase diagram where sg2 and sg3 start in the same phase; only the
        following phase diagram is possible:  [[["sg1", 0]], [["sg2", 0]], [["sg1", 1]], [["sg3", 0]]]
        """
        endpoint = f"{CLOUD_API_URL}/phase-diagram-computation"
        headers = SwiftMobilityCloudApi.get_authentication_header()

        # rest-api call
        try:
            json_dict = dict(
                intersection=intersection.to_json(),
                greenyellow_intervals=fixed_time_schedule.to_json()
                ["greenyellow_intervals"],
                period=fixed_time_schedule.to_json()["period"])
            logging.debug(f"calling endpoint {endpoint}")
            r = requests.post(endpoint, json=json_dict, headers=headers)
            logging.debug(f"finished calling endpoint {endpoint}")
        except requests.exceptions.ConnectionError:
            raise UnknownCloudException(CONNECTION_ERROR_MSG)

        # check for errors
        check_status_code(response=r)
        output = r.json()

        # parse output
        phase_diagram = PhaseDiagram.from_json(output["phase_diagram"])
        return phase_diagram
def minimizing_period_duration(print_fixed_time_schedule: bool = False):
    """
    In this example (given a traffic scenario) we search for the fixed-time schedule with the smallest period duration
    for which none of the traffic lights are oversatured.

    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].

    In this example, we load an intersection from disk (export of Swift Mobility Desktop). This functionality is tested
    with Swift Mobility Desktop 0.7.0.alpha.
    """
    logging.info(f"Running example '{os.path.basename(__file__)}'")
    # absolute path to .json file that has been exported from swift mobility desktop
    smd_export = os.path.join(
        os.path.join(os.path.abspath(__file__), os.pardir),
        "example_smd_export.json")

    # retrieve the json structure from the file
    with open(smd_export, "r") as f:
        json_dict = json.load(f)

    logging.info(f"Loading intersection and traffic situation from disk")
    intersection = Intersection.from_json(
        intersection_dict=json_dict["intersection"])
    arrival_rates = ArrivalRates.from_json(
        arrival_rates_dict=json_dict["arrival_rates"])
    logging.info(f"Loaded intersection and traffic situation from disk")

    logging.info(f"Minimizing period duration")
    fixed_time_schedule, phase_diagram, objective_value, _ = SwiftMobilityCloudApi.get_optimized_fts(
        intersection=intersection,
        arrival_rates=arrival_rates,
        min_period_duration=30,
        max_period_duration=180,
        objective=ObjectiveEnum.min_period)

    logging.info(f"Minimized period duration: {objective_value:.2f} seconds")

    if print_fixed_time_schedule:
        logging.info(fixed_time_schedule)
        logging.info(phase_diagram)
def load_from_smd_and_run():
    """
    Example showing how to:
    - retrieve intersection information and arrival rates from a json file exported from Swift Mobility Desktop.
    - use this information to optimize fixed-time schedules
    
    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].
    
    In this example, we load an intersection from disk (export of Swift Mobility Desktop). This functionality is tested
    with Swift Mobility Desktop 0.7.0.alpha.
    """
    logging.info(f"Running example '{os.path.basename(__file__)}'")
    # absolute path to .json file that has been exported from swift mobility desktop
    smd_export = os.path.join(os.path.join(os.path.abspath(__file__), os.pardir), "example_smd_export.json")

    # retrieve the json structure from the file
    with open(smd_export, "r") as f:
        json_dict = json.load(f)

    logging.info(f"Loading intersection and traffic situation from disk")
    intersection = Intersection.from_json(intersection_dict=json_dict["intersection"])

    arrival_rates = ArrivalRates.from_json(arrival_rates_dict=json_dict["arrival_rates"])
    logging.info(f"Loaded intersection and traffic situation from disk")

    logging.info(f"Minimizing average experienced delay")
    fixed_time_schedule, phase_diagram, objective_value, _ = SwiftMobilityCloudApi.get_optimized_fts(
        intersection=intersection, arrival_rates=arrival_rates, min_period_duration=30, max_period_duration=180,
        objective=ObjectiveEnum.min_delay)

    logging.info(f"Average experienced delay: {objective_value:.2f} 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)
Example #23
0
    def test_other_relations(self) -> None:
        """ Test if the attribute other_relation containers all other relations (sync starts,
         offsets and greenyellow-leads)"""
        # GIVEN
        input_dict = TestInputValidation.get_default_inputs()

        # WHEN an unknown id is used in a relations between signal groups
        intersection = Intersection(**input_dict)

        other_relations = intersection.other_relations
        # THEN other_relations is the list of all sync_starts, offsets and greenyellow-leads
        self.assertEqual(len(input_dict["sync_starts"]) + len(input_dict["offsets"]) +
                         len(input_dict["greenyellow_leads"]) + len(input_dict["greenyellow_trails"]),
                         len(other_relations))
        for key in ["sync_starts", "offsets", "greenyellow_leads"]:
            with self.subTest(f"'{key}' in other_relations"):
                for other_relation in input_dict[key]:
                    self.assertIn(other_relation, other_relations)
Example #24
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
    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
def minimizing_delay(print_fixed_time_schedule: bool = False):
    """
    In this example (given a traffic scenario) we search for the fixed-time schedule that minimizes the average
    delay that road uses (arriving during an horizon of 2 hours) are expected to experience at the intersection.

    Use case:
    This enables real-time optimization of traffic light control allowing truly dynamic and smart traffic
    light control that automatically adapts to the traffic situation. For example, by periodically computing the
    optimal fixed-time schedule, and automatically converting it to a vehicle-actuated controller (e.g., using
    the green times as maximum green times and allowing green times to be terminated prematurely).

    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].

    In this example, we load an intersection from disk (export of Swift Mobility Desktop). This functionality is tested
    with Swift Mobility Desktop 0.7.0.alpha.
    """
    logging.info(f"Running example '{os.path.basename(__file__)}'")
    # absolute path to .json file that has been exported from swift mobility desktop
    smd_export = os.path.join(
        os.path.join(os.path.abspath(__file__), os.pardir),
        "example_smd_export.json")

    # retrieve the json structure from the file
    with open(smd_export, "r") as f:
        json_dict = json.load(f)

    logging.info(f"Loading intersection and traffic situation from disk")
    intersection = Intersection.from_json(
        intersection_dict=json_dict["intersection"])
    arrival_rates = ArrivalRates.from_json(
        arrival_rates_dict=json_dict["arrival_rates"])
    logging.info(f"Loaded intersection and traffic situation from disk")

    logging.info(f"Minimizing delay")
    fixed_time_schedule, phase_diagram, objective_value, _ = SwiftMobilityCloudApi.get_optimized_fts(
        intersection=intersection,
        arrival_rates=arrival_rates,
        min_period_duration=30,
        max_period_duration=180,
        objective=ObjectiveEnum.min_delay,
        horizon=2)

    logging.info(f"Average experienced delay: {objective_value:.2f} seconds")

    if print_fixed_time_schedule:
        logging.info(fixed_time_schedule)
        logging.info(phase_diagram)

    # intersection becomes oversaturated
    scaling_factor = 1.3
    logging.info(
        f"Increasing original amount of traffic with {(scaling_factor - 1) * 100:.2f}%"
    )
    arrival_rates *= scaling_factor
    initial_queue = 25
    logging.info(
        f"Adding initial queue of {initial_queue: d} PCE/cyclists/pedestrians to each queue"
    )
    id_to_queue_lengths = dict()
    for signalgroup in intersection.signalgroups:
        id_to_queue_lengths[signalgroup.id] = [
            initial_queue for _ in signalgroup.traffic_lights
        ]
    initial_queue_lengths = QueueLengths(
        id_to_queue_lengths=id_to_queue_lengths)

    logging.info(f"Minimizing delay")
    fixed_time_schedule, phase_diagram, objective_value, _ = SwiftMobilityCloudApi.get_optimized_fts(
        intersection=intersection,
        arrival_rates=arrival_rates,
        min_period_duration=30,
        max_period_duration=180,
        objective=ObjectiveEnum.min_delay,
        horizon=2,
        initial_queue_lengths=initial_queue_lengths)

    logging.info(f"Average experienced delay: {objective_value:.2f} seconds")

    if print_fixed_time_schedule:
        logging.info(fixed_time_schedule)
        logging.info(phase_diagram)
def tune_fixed_time_schedule(print_fixed_time_schedule: bool = False):
    """
    In this example we show how to tune a fixed-time schedule.

    Use case: Traffic situations change throughout the day. This function allows you to quickly adapt the green times
    of an existing fixed-time schedule to a new traffic situations. This can be used, for example, to adapt the
    maximum greenyellow times of a smart traffic light controller to the current traffic situation in real-time.

    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].

    In this example, we load an intersection from disk (export of Swift Mobility Desktop). This functionality is tested
    with Swift Mobility Desktop 0.7.0.alpha.
    """
    logging.info(f"Running example '{os.path.basename(__file__)}'")
    # absolute path to .json file that has been exported from swift mobility desktop
    smd_export = os.path.join(
        os.path.join(os.path.abspath(__file__), os.pardir),
        "example_smd_export.json")

    # retrieve the json structure from the file
    with open(smd_export, "r") as f:
        json_dict = json.load(f)

    logging.info(f"Loading intersection and traffic situation from disk")
    intersection = Intersection.from_json(
        intersection_dict=json_dict["intersection"])
    arrival_rates = ArrivalRates.from_json(
        arrival_rates_dict=json_dict["arrival_rates"])
    logging.info(f"Loaded intersection and traffic situation from disk")

    logging.info(f"Minimizing delay")
    fixed_time_schedule, phase_diagram, objective_value, _ = SwiftMobilityCloudApi.get_optimized_fts(
        intersection=intersection,
        arrival_rates=arrival_rates,
        min_period_duration=30,
        max_period_duration=180,
        objective=ObjectiveEnum.min_delay,
        horizon=2)

    logging.info(f"Average experienced delay: {objective_value:.2f} seconds")

    if print_fixed_time_schedule:
        logging.info(fixed_time_schedule)
        logging.info(phase_diagram)

    # the more the traffic situation changes, the more effect tuning the fixed-time schedule has. In this example,
    # we only scale the amount of traffic.
    for scaling_factor in [0.95, 0.9, 0.7, 0.4]:
        logging.info(
            f"Evaluating schedule for situation with {(1-scaling_factor)*100 :.1f}% less traffic"
        )

        arrival_rates_scaled = arrival_rates * scaling_factor

        kpis = SwiftMobilityCloudApi.evaluate_fts(
            intersection=intersection,
            fixed_time_schedule=fixed_time_schedule,
            arrival_rates=arrival_rates_scaled,
            horizon=2)

        logging.info(
            f"Average experienced delay without tuning: {kpis.delay:.2f} seconds"
        )

        logging.info(
            f"Tuning schedule for situation with {(1-scaling_factor)*100 :.1f}% less traffic"
        )

        tuned_fixed_time_schedule, objective_value = SwiftMobilityCloudApi.get_tuned_fts(
            intersection=intersection,
            fixed_time_schedule=fixed_time_schedule,
            arrival_rates=arrival_rates_scaled,
            min_period_duration=30,
            max_period_duration=180,
            objective=ObjectiveEnum.min_delay,
            horizon=2)

        logging.info(
            f"Average experienced delay after tuning: {objective_value:.2f} seconds"
        )

        if print_fixed_time_schedule:
            logging.info(fixed_time_schedule)
            logging.info(phase_diagram)
Example #28
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)
Example #29
0
    def get_optimized_fts(
        cls,
        intersection: Intersection,
        arrival_rates: ArrivalRates,
        horizon: float = 2.0,
        min_period_duration: float = 0.0,
        max_period_duration: float = 180,
        objective: ObjectiveEnum = ObjectiveEnum.min_delay,
        initial_queue_lengths: Optional[QueueLengths] = None,
        fixed_time_schedules_to_exclude: Optional[
            List[FixedTimeSchedule]] = None,
        warm_start_info: Optional[Dict] = None,
    ) -> Tuple[FixedTimeSchedule, PhaseDiagram, float, dict]:
        """
        Optimize a fixed-time schedule
        :param intersection: intersection for which to optimize the fts (contains signal groups, conflicts and more)
        :param arrival_rates: arrival rates; each arrival rate is specified in personal car equivalent per hour (PCE/h)
        cyclists per hour or pedestrians per hour
        :param horizon: time period of interest in hours.
        :param min_period_duration: minimum period duration of the fixed-time schedule in seconds
        :param max_period_duration: minimum period duration of the fixed-time schedule in seconds
        :param objective: what kpi (key performance indicator) to optimize. The following options are available:
         - ObjectiveEnum.min_delay: minimize the delay experienced by road users arriving at the intersection during
         the next 'horizon' hours. The initially waiting traffic is modeled as implicitly by increasing the
         arrival rate by initial_queue_length / horizon PCE/h; this implies that we assume that this traffic is arriving
         (evenly spread) during the horizon.
         - ObjectiveEnum.min_period: search for the fixed-time schedule with the smallest period duration for which
         all traffic lights are 'stable', i.e., the greenyellow interval is large enough so that the amount of traffic
          that  can (on average) depart during the horizon exceeds the traffic that arrives during
          the horizon (+ initially waiting traffic).
         - ObjectiveEnum.max_capacity: search for the fixed-time schedule that can handle the largest (percentual)
         increase in traffic (including the initial amount of traffic), i.e., the largest percentual increase in traffic
          for which all traffic lights are 'stable' (see also ObjectiveEnum.min_period). This objective function
          disregards the maximum saturation of each traffic light (we assume the maximum saturation is 1 for each
          traffic light).
        :param initial_queue_lengths: initial amount of traffic waiting at each of the traffic lights; if None, then we
        assume no initial traffic. The unit of each queue-length should align with the unit used for the arrival rate;
        if the arrival rate is specified in PCE/h then the queue-length needs to be specified in PCE.
        :return: fixed-time schedule, associated phase diagram and the objective value
        (minimized delay, minimized period, or maximum percentual increase in traffic divided by 100, e.g. 1 means
        currently at the verge of stability)
        :param fixed_time_schedules_to_exclude: the fixed-time schedules that we want to exclude; this can be used to find
        the second best schedule by excluding the best one.
        :param warm_start_info: each optimization returns some information
        (usually in the format {"id": "some identification string"}); if you want to compute the second best schedule
        (by excluding the best schedule), then you can also provide the warm_start_info returned with the best schedule;
        this will significantly speedup computations when trying to find the second best one.
        """
        assert horizon >= 1, HORIZON_LB_EXCEEDED_MSG
        if initial_queue_lengths is None:
            # assume no initial traffic
            initial_queue_lengths = QueueLengths({
                signalgroup.id: [0] * len(signalgroup.traffic_lights)
                for signalgroup in intersection.signalgroups
            })

        check_all_arrival_rates_and_queue_lengths_specified(
            intersection=intersection,
            arrival_rates=arrival_rates,
            initial_queue_lengths=initial_queue_lengths)

        if fixed_time_schedules_to_exclude is not None:
            for fixed_time_schedule in fixed_time_schedules_to_exclude:
                try:
                    validate_safety_restrictions(
                        intersection=intersection,
                        fixed_time_schedule=fixed_time_schedule)
                except SafetyViolation as e:
                    logging.error(
                        f"One of the fixed-time schedules in fixed_time_schedules_to_exclude' does not"
                        f"satisfy all safety restrictions. The violation: {e}")
                    raise SafetyViolation(e)

        endpoint = f"{CLOUD_API_URL}/fts-optimization"
        headers = SwiftMobilityCloudApi.get_authentication_header()
        # rest-api call
        try:
            # assume that the traffic that is initially present arrives during the horizon.
            corrected_arrival_rates = arrival_rates + initial_queue_lengths / horizon
            json_dict = dict(
                intersection=intersection.to_json(),
                arrival_rates=corrected_arrival_rates.to_json(),
                min_period_duration=min_period_duration,
                max_period_duration=max_period_duration,
                objective=objective.value,
            )
            if fixed_time_schedules_to_exclude is not None:
                json_dict["fts_to_exclude"] = [
                    fts.to_json() for fts in fixed_time_schedules_to_exclude
                ]
            if warm_start_info is not None:
                json_dict["warm_start_info"] = warm_start_info
            logging.debug(f"calling endpoint {endpoint}")
            r = requests.post(endpoint, json=json_dict, headers=headers)
            logging.debug(f"finished calling endpoint {endpoint}")
        except requests.exceptions.ConnectionError:
            raise UnknownCloudException(CONNECTION_ERROR_MSG)

        # check for errors
        check_status_code(response=r)

        # parse output
        output = r.json()
        objective_value = output["obj_value"]
        fixed_time_schedule = FixedTimeSchedule.from_json(
            output["fixed_time_schedule"])
        # check if safety restrictions are satisfied; raises a SafetyViolation-exception if this is not the case.
        validate_safety_restrictions(intersection=intersection,
                                     fixed_time_schedule=fixed_time_schedule)
        phase_diagram = PhaseDiagram.from_json(output["phase_diagram"])

        warm_start_info = output.get("warm_start_info", dict())

        return fixed_time_schedule, phase_diagram, objective_value, warm_start_info
Example #30
0
    def get_tuned_fts(cls, intersection: Intersection, arrival_rates: ArrivalRates,
                      fixed_time_schedule: FixedTimeSchedule, horizon: float = 2.0,
                      min_period_duration: float = 0.0, max_period_duration: float = 180,
                      objective: ObjectiveEnum = ObjectiveEnum.min_delay,
                      initial_queue_lengths: Optional[QueueLengths] = None) \
            -> Tuple[FixedTimeSchedule, float]:
        """
        Tune a fixed-time schedule; tune the greenyellow times to a new situation but keep the 'structure' of this
         fixed-time schedule the same (the phase diagram remains the same).
        :param intersection: intersection for which to optimize the fts (contains signal groups, conflicts and more)
        :param arrival_rates: arrival rates; each arrival rate is specified in personal car equivalent per hour (PCE/h)
        cyclists per hour or pedestrians per hour
        :param fixed_time_schedule: fixed-time schedule to tune.
        :param horizon: time period of interest in hours.
        :param min_period_duration: minimum period duration of the fixed-time schedule in seconds
        :param max_period_duration: minimum period duration of the fixed-time schedule in seconds
        :param objective: what kpi (key performance indicator) to optimize. The following options are available:
         - ObjectiveEnum.min_delay: minimize the delay experienced by road users arriving at the intersection during
         the next 'horizon' hours. The initially waiting traffic is modeled as implicitly by increasing the
         arrival rate by initial_queue_length / horizon PCE/h; this implies that we assume that this traffic is arriving
         (evenly spread) during the horizon.
         - ObjectiveEnum.min_period: search for the fixed-time schedule with the smallest period duration for which
         all traffic lights are 'stable', i.e., the greenyellow interval is large enough so that the amount of traffic
          that  can (on average) depart during the horizon exceeds the traffic that arrives during
          the horizon (+ initially waiting traffic).
         - ObjectiveEnum.max_capacity: search for the fixed-time schedule that can handle the largest (percentual)
         increase in traffic (including the initial amount of traffic), i.e., the largest percentual increase in traffic
          for which all traffic lights are 'stable' (see also ObjectiveEnum.min_period).
        :param initial_queue_lengths: initial amount of traffic waiting at each of the traffic lights; if None, then we
        assume no initial traffic. The unit of each queue-length should align with the unit used for the arrival rate;
        if the arrival rate is specified in PCE/h then the queue-length needs to be specified in PCE.
        :return: fixed-time schedule, associated phase diagram and the objective value
        (minimized delay, minimized period, or maximum percentual increase in traffic divided by 100, e.g. 1 means
        currently at the verge of stability)
        """
        assert horizon >= 1, HORIZON_LB_EXCEEDED_MSG
        if initial_queue_lengths is None:
            # assume no initial traffic
            initial_queue_lengths = QueueLengths({
                signalgroup.id: [0] * len(signalgroup.traffic_lights)
                for signalgroup in intersection.signalgroups
            })

        check_all_arrival_rates_and_queue_lengths_specified(
            intersection=intersection,
            arrival_rates=arrival_rates,
            initial_queue_lengths=initial_queue_lengths)

        endpoint = f"{CLOUD_API_URL}/fts-tuning"
        headers = SwiftMobilityCloudApi.get_authentication_header()

        # rest-api call
        try:
            # assume that the traffic that is initially present arrives during the horizon.
            corrected_arrival_rates = arrival_rates + initial_queue_lengths / horizon
            json_dict = dict(intersection=intersection.to_json(),
                             fixed_time_schedule=fixed_time_schedule.to_json(),
                             arrival_rates=corrected_arrival_rates.to_json(),
                             min_period_duration=min_period_duration,
                             max_period_duration=max_period_duration,
                             objective=objective.value)
            logging.debug(f"calling endpoint {endpoint}")
            r = requests.post(endpoint, json=json_dict, headers=headers)
            logging.debug(f"finished calling endpoint {endpoint}")
        except requests.exceptions.ConnectionError:
            raise UnknownCloudException(CONNECTION_ERROR_MSG)

        # check for errors
        check_status_code(response=r)

        # parse output
        output = r.json()
        objective_value = output["obj_value"]
        fixed_time_schedule = FixedTimeSchedule.from_json(
            output["fixed_time_schedule"])
        # check if safety restrictions are satisfied; raises a SafetyViolation-exception if this is not the case.
        validate_safety_restrictions(intersection=intersection,
                                     fixed_time_schedule=fixed_time_schedule)

        return fixed_time_schedule, objective_value