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())
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)
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)
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)
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)
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)
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
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)
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)
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)