def _generate_tas(config, channels, seed): """Generates a set amount of TAs in the range of data provided by parameters. TA IDs start at 1 Note that eligible frequencies is a list of Kbps values :param ConfigurationObject config: The Configuration for an instance of a challenge problem :param [<Channel>] channels: The list of already generated channels to be used when assigning eligible_frequencies :param int seed: The seed to use when evaluating PRFs :returns [<TA>] tas: A list of TAs generated from config """ tas = [] for x in range(config.num_tas): safety = PRF(config.safety) voice = PRF(config.voice) latency = PRF(config.latency) scaling_factor = PRF(config.scaling_factor) c = PRF(config.c) eligible_frequencies = PRF(config.eligible_frequencies) tas.append( TA(id_='TA{0}'.format(x + 1), minimum_voice_bandwidth=Kbps(voice.evaluate(seed)), minimum_safety_bandwidth=Kbps(safety.evaluate(seed)), latency=timedelta(microseconds=1000 * int(latency.evaluate(seed))), scaling_factor=scaling_factor.evaluate(seed), c=c.evaluate(seed), eligible_frequencies=list( map( lambda x: x.frequency, random.sample( channels, eligible_frequencies.evaluate(seed)))))) return tas
def construct_ta_from_node(self, orientdb_node): """Constructs a TA object from an OrientDB node. :param pyorient.otypes.OrientRecord orientdb_node: A pyorient node :returns TA ta: A TA object constructed from the pyorient node """ orient_record = self.get_connected_nodes(orientdb_node._rid, direction='in', filterdepth=1) eligible_frequencies=[] for record in orient_record: if record._class == 'Channel': eligible_frequencies.append(Frequency(int(record.frequency))) ta = TA( id_=orientdb_node.id, minimum_voice_bandwidth=Kbps( float(orientdb_node.minimum_voice_bandwidth)), minimum_safety_bandwidth=Kbps( float(orientdb_node.minimum_safety_bandwidth)), latency=timedelta(microseconds=1000*int(orientdb_node.latency)), scaling_factor=float(orientdb_node.scaling_factor), c=float(orientdb_node.c), eligible_frequencies=eligible_frequencies) return ta
def setUpClass(cls): MdlId.clear() cls.id_ = MdlId('TA1') cls.minimum_voice_bandwidth = Kbps(100) cls.minimum_safety_bandwidth = Kbps(75) cls.scaling_factor = 1 cls.c = 0.05
def test_generates_correct_amount_of_tas(self): generator = TAGenerator(lower_minimum_voice_bandwidth=Kbps(20), upper_minimum_voice_bandwidth=Kbps(100), lower_minimum_safety_bandwidth=Kbps(5), upper_minimum_safety_bandwidth=Kbps(50), lower_scaling_factor=1, upper_scaling_factor=5, lower_c=0.03, upper_c=0.08, lower_min_value=30, upper_min_value=70) num_tas = 100 ta_list = generator.generate(num_tas) self.assertEqual(len(ta_list), num_tas)
def test_bandwidth_rate_init(self): type_ = BandwidthTypes.VOICE rate = Kbps(100) bandwidth_rate = BandwidthRate(type_, rate) self.assertEqual(type_, bandwidth_rate.type_) self.assertEqual(rate, bandwidth_rate.rate)
def construct_channel_from_node(self, orientdb_node): """Constructs a Channel object from a pyorient node :param pyorient.otypes.OrientRecord orientdb_node: A pyorient channel node :returns Channel channel: A Channel object constructed from the pyorient node """ channel = Channel(frequency=Frequency(int(orientdb_node.frequency)), capacity=Kbps(int(orientdb_node.capacity))) return channel
def setUpClass(cls): cls.valid_channel_frequency = Frequency(4919500000) cls.valid_channel_length = time(microsecond=100000) cls.valid_channel_latency = time(microsecond=50000) cls.valid_channel_capacity = Kbps(100000) cls.valid_channel = Channel(cls.valid_channel_frequency, cls.valid_channel_length, cls.valid_channel_latency, cls.valid_channel_capacity)
def retrieve_constraints(self): """ Retrieves constraints from the provided database :returns ConstraintsObject constraints_object: A Constraints Object """ orientdb_constraints_objects = self._get_system_wide_constraints() constraints_objects = [] for orientdb_constraints_object in orientdb_constraints_objects: connected_nodes = self.get_connected_nodes(orientdb_constraints_object._rid, filterdepth=1) candidate_tas = [] channels = [] for node in connected_nodes: if node._class == 'TA': candidate_tas.append(self.construct_ta_from_node(node)) elif node._class == 'Channel': channels.append(self.construct_channel_from_node(node)) constraints_objects.append(ConstraintsObject( id_ = orientdb_constraints_object.id, candidate_tas=candidate_tas, channels=channels, goal_throughput_bulk=BandwidthRate( BandwidthTypes.BULK, Kbps(int(orientdb_constraints_object.goal_throughput_bulk)) ), goal_throughput_voice=BandwidthRate( BandwidthTypes.VOICE, Kbps(int(orientdb_constraints_object.goal_throughput_voice)) ), goal_throughput_safety=BandwidthRate( BandwidthTypes.SAFETY, Kbps(int(orientdb_constraints_object.goal_throughput_safety)) ), guard_band=timedelta(microseconds=1000 * int(orientdb_constraints_object.guard_band)), epoch=timedelta(microseconds=1000 * int(orientdb_constraints_object.epoch)), txop_timeout=TxOpTimeout(int(orientdb_constraints_object.txop_timeout)), seed=orientdb_constraints_object.seed)) return constraints_objects
def __init__(self, id_, minimum_voice_bandwidth, minimum_safety_bandwidth, latency, scaling_factor, c, eligible_frequencies, bandwidth=Kbps(0), channel=None, value=0): """Constructor :param str id_: The ID of the TA. :param Kbps minimum_voice_bandwidth: The minimum required voice bandwidth to get this TA in the air :param Kbps minimum_safety_bandwidth: The minimum required safety bandwidth to get this TA in the air :param timedelta latency: The maximum delay between radio transmissions :param int scaling_factor: The amount by which to scale the overall value by onc. :param int c: The coefficient of a sample value function. For now it's set to 1 because there is no real value function. :param List[Channel] eligible_frequencies: The list of channels communication is permissible over. :param Kbps bandwidth: The amount of bandwidth assigned to this TA :param Channel channel: The channel this TA has been assigned to communicate on :param timedelta channel: The channel this TA has been assigned to communicate on :param int value: The amount of value this TA provides at a some bandwidth """ self.id_ = id_ self.minimum_voice_bandwidth = minimum_voice_bandwidth self.minimum_safety_bandwidth = minimum_safety_bandwidth self.latency = latency self.scaling_factor = float(scaling_factor) self.c = float(c) self.eligible_frequencies = eligible_frequencies self.bandwidth = bandwidth self.channel = channel self.value = value self.total_minimum_bandwidth = Kbps(minimum_voice_bandwidth.value + minimum_safety_bandwidth.value) self.min_value = self.compute_value_at_bandwidth( self.total_minimum_bandwidth) self.max_value = self.compute_value_at_bandwidth(MAX_BANDWIDTH)
def __init__(self, id_, candidate_tas, channels, seed, goal_throughput_bulk=BandwidthRate(BandwidthTypes.BULK, Kbps(100)), goal_throughput_voice=BandwidthRate(BandwidthTypes.VOICE, Kbps(100)), goal_throughput_safety=BandwidthRate(BandwidthTypes.SAFETY, Kbps(100)), guard_band=timedelta(microseconds=1000), epoch=timedelta(microseconds=100000), txop_timeout=TxOpTimeout(255)): """ Constructor :param BandwidthRate goal_throughput_bulk: The new value for all Bulk ServiceLevelProfiles. :param BandwidthRate goal_throughput_voice: The new value for all Voice ServiceLevelProfiles. :param BandwidthRate goal_throughput_safety: The new value for all Safety ServiceLevelProfiles. :param timedelta guard_band: The unused part of the radio spectrum between radio bands to prevent interference. :param timedelta epoch: The epoch of the MDL file. :param TxOpTimeout txop_timeout: The timeout value for TxOp nodes :param List[TA] candidate_tas: List of potential TA's. :param List[Channel] channels: Channel specific data such as the amount of throughput available :param int seed: The number that was used to seed the random generator """ self.goal_throughput_bulk = goal_throughput_bulk self.goal_throughput_voice = goal_throughput_voice self.goal_throughput_safety = goal_throughput_safety self.guard_band = guard_band self.epoch = epoch self.txop_timeout = txop_timeout self.candidate_tas = candidate_tas self.channels = channels self.seed = seed self.id_ = id_ self.deadline_window = guard_band + MDL_MIN_INTERVAL
def compute_bw_from_comm_len(self, capacity, latency, communication_length, guard_band=timedelta(microseconds=0)): """Returns the amount of bandwidth required to allocate to this TA based on the amount of time this TA communicates for :param timedelta communication_length: The time this TA communicates for :returns Kbps bandwidth_required: The amount of bandwidth this TA requires to communicate """ bandwidth_required = (capacity.value / latency.get_microseconds()) * ( communication_length.get_microseconds() - 2 * guard_band.get_microseconds()) return Kbps(bandwidth_required)
def _discretize(self, constraints_object): discretized_tas = [] for ta in constraints_object.candidate_tas: for channel in constraints_object.channels: discretization_length = int( MAX_BANDWIDTH.value - ta.total_minimum_bandwidth.value) / self.disc_count for i in range(0, self.disc_count): disc_ta = copy.deepcopy(ta) disc_ta.channel = channel bandwidth = Kbps(disc_ta.total_minimum_bandwidth.value + (i * discretization_length)) disc_ta.value = ta.compute_value_at_bandwidth(bandwidth) disc_ta.bandwidth = bandwidth discretized_tas.append(disc_ta) return discretized_tas
def _generate_channels(config, seed): """Generates a set amount of Channels in the range of data provided by data_file. :param ConfigurationObject config: The Configuration for an instance of a challenge problem :param int seed: The seed to use when evaluating PRFs :returns [<Channel>] channels: A list of channels generated from config """ channels = [] for i in range(0, config.num_channels): base_frequency = config.frequency[0] frequency_incrementation = config.frequency[1] capacity = PRF(config.capacity) channels.append( Channel(frequency=Frequency(base_frequency + (i * frequency_incrementation)), capacity=Kbps(capacity.evaluate(seed)))) return channels
def generate(config): """Generates a ConstraintsObject within the parameters specified by a ConfigurationObject :param ConfigurationObject config: The Configuration for an instance of a challenge problem :returns ConstraintsObject: """ constraints_object_list = [] if config.testing == 1: if config.testing_seed != 'timestamp': seed = config.testing_seed numpy.random.seed(seed) random.seed(seed) channel_list = [] for i in range(config.testing_num_channels): channel = Channel(frequency=Frequency(4919 + i * 22), capacity=Kbps( config.testing_channel_capacity)) channel_list.append(channel) ta_list = [] for i in range(config.testing_num_tas): eligible_frequency_list = [] for j in range(config.testing_eligible_frequencies[i]): eligible_frequency_list.append(channel_list[j].frequency) ta = TA(id_='TA{0}'.format(i + 1), minimum_voice_bandwidth=Kbps( int(config.testing_total_min_bw[i]) / 2), minimum_safety_bandwidth=Kbps( int(config.testing_total_min_bw[i]) / 2), latency=timedelta(microseconds=1000 * int(config.testing_latency[i])), scaling_factor=config.testing_scaling_factor[i], c=config.testing_c[i], eligible_frequencies=eligible_frequency_list) ta_list.append(ta) testing_constraints_object = ConstraintsObject( id_='TestingConstraintsObject', candidate_tas=ta_list, channels=channel_list, seed='timestamp', goal_throughput_bulk=Kbps(config.goal_throughput_bulk), goal_throughput_voice=Kbps(config.goal_throughput_voice), goal_throughput_safety=Kbps(config.goal_throughput_safety), guard_band=timedelta(microseconds=1000 * int(config.guard_band)), epoch=timedelta(microseconds=1000 * int(config.epoch)), txop_timeout=TxOpTimeout(config.txop_timeout)) constraints_object_list.append(testing_constraints_object) else: if len(config.instances) == 2: seed = config.instances[1] numpy.random.seed(seed) random.seed(seed) else: seed = 'timestamp' for x in range(1, config.instances[0] + 1): channels = ConstraintsObjectGenerator._generate_channels( config, seed) candidate_tas = ConstraintsObjectGenerator._generate_tas( config, channels, seed) constraints_object = ConstraintsObject( id_=x, candidate_tas=candidate_tas, channels=channels, seed=seed, goal_throughput_bulk=Kbps(config.goal_throughput_bulk), goal_throughput_voice=Kbps(config.goal_throughput_voice), goal_throughput_safety=Kbps(config.goal_throughput_safety), guard_band=timedelta(microseconds=1000 * int(config.guard_band)), epoch=timedelta(microseconds=1000 * int(config.epoch)), txop_timeout=TxOpTimeout(config.txop_timeout)) constraints_object_list.append(constraints_object) if isinstance(seed, int): seed += 1 return constraints_object_list
def test_compute_value_caps_at_2000(self): ta = TA(self.id_, Kbps(2000), Kbps(2000), self.scaling_factor, self.c) ta2 = TA(self.id_, Kbps(2000), Kbps(0), self.scaling_factor, self.c) self.assertEqual(ta.min_value, ta2.min_value) self.assertEqual(100, ta.min_value)
def perturb_constraints_object(self, constraints_object, optimizer_result, lower_bound_optimizer_result): """Perturbs an optimization result based on the perturbations selected. There are three possible perturbations: 1. If ta_bandwidth is set to any value other than 0, 1 random TA will have it's bandwidth value perturbed by the specified amount. 2. If channel_dropoff is greater than 0, channel_dropoff number of TAs will be dropped from their channels until there are no TAs left to drop. 3. If channel_capacity is set to any value other than 0, one random channel will have it's capacity increased or decreased by the specified amount. :param OptimizerResult optimizer_result: An unperturbed optimization result :param OptimizerResult lower_bound_optimizer_result: The result of the lower bound optimizaztion to use when computing averages :returns OptimizerResult: A perturbed optimization result """ optimizer_result = deepcopy(optimizer_result) lower_bound_optimizer_result = deepcopy(lower_bound_optimizer_result) # Increases the total_minimum_bandwidth value of a TA by the selected # amount in the ConfigurationObject if self.ta_bandwidth != 0: for or_ in [lower_bound_optimizer_result, optimizer_result]: ta_to_perturb = self._select_random_scheduled_ta( or_.scheduled_tas, constraints_object.seed) if or_ == optimizer_result: logger.debug( 'PERTURBATION: Changing {0} minimum bandwidth by {1}'. format(ta_to_perturb.id_, self.ta_bandwidth)) if ta_to_perturb.total_minimum_bandwidth.value - self.ta_bandwidth < 0: self.ta_bandwidth = ta_to_perturb.total_minimum_bandwidth.value ta_to_perturb.total_minimum_bandwidth.value += self.ta_bandwidth ta_to_perturb.minimum_safety_bandwidth.value += ( self.ta_bandwidth / 2) ta_to_perturb.minimum_voice_bandwidth.value += ( self.ta_bandwidth / 2) ta_to_perturb.min_value = ta_to_perturb.compute_value_at_bandwidth( ta_to_perturb.total_minimum_bandwidth) # This is a workaround for the issue in floating point precision # in Python. Here I am setting the result to 0 if the two # floats are equal. Otherwise due to floating point precision # you get a value other than 0. if or_ == lower_bound_optimizer_result: if lower_bound_optimizer_result.value == ta_to_perturb.value: lower_bound_optimizer_result.value = 0 lower_bound_optimizer_result.scheduled_tas = [] else: lower_bound_optimizer_result.value -= ta_to_perturb.value lower_bound_optimizer_result.scheduled_tas.remove( ta_to_perturb) for channel, ta_list in lower_bound_optimizer_result.scheduled_tas_by_channel.items( ): for ta in ta_list: if ta == ta_to_perturb: ta_list.remove(ta) # Randomly selects one channel to drop from the original # ConstraintsObject if self.channel_dropoff > 0: for or_ in [lower_bound_optimizer_result, optimizer_result]: dropped_tas = [] for i in range(self.channel_dropoff): if len(dropped_tas) == len(or_.scheduled_tas): logger.debug( 'All scheduled TAs have been dropped from their channels. The number of TAs you have requested to drop ({0}) has exceeded the amount of TAs that have been scheduled ({1}).' .format(self.channel_dropoff, len(or_.scheduled_tas))) break else: ta_to_perturb = self._select_random_scheduled_ta([ ta for ta in or_.scheduled_tas if ta not in dropped_tas ], constraints_object.seed) dropped_tas.append(ta_to_perturb) if or_ == optimizer_result: logger.debug( 'PERTURBATION: Removing {0} from {1}'.format( ta_to_perturb.id_, ta_to_perturb.channel.frequency.value)) ta_to_perturb.eligible_frequencies.remove( ta_to_perturb.channel.frequency) if or_ == lower_bound_optimizer_result: previously_scheduled_tas = deepcopy(or_.scheduled_tas) or_.scheduled_tas = [ ta for ta in previously_scheduled_tas if ta not in dropped_tas ] for channel, ta_list in or_.scheduled_tas_by_channel.items( ): old_ta_list = deepcopy(ta_list) or_.scheduled_tas_by_channel[channel] = [ ta for ta in old_ta_list if ta not in dropped_tas ] or_.value = sum([ta.value for ta in or_.scheduled_tas]) # Increases or Decreases the capacity of one random channel if self.channel_capacity != 0: for or_ in [lower_bound_optimizer_result, optimizer_result]: ta_to_perturb = self._select_random_scheduled_ta( or_.scheduled_tas, constraints_object.seed) channel_to_perturb = next( channel for channel in constraints_object.channels if channel == ta_to_perturb.channel) if or_ == optimizer_result: logger.debug( 'PERTURBATION: Changing channel {0} capacity by {1}'. format(ta_to_perturb.channel.frequency.value, self.channel_capacity)) channel_to_perturb.capacity.value += self.channel_capacity # If the amount of bandwidth to reduce the channel by exceeds the # channel's capacity. Remove that channel, an the scheduled TAs on it. if channel_to_perturb.capacity.value <= 0: for ta in or_.scheduled_tas: tas_to_remove = [] if ta.channel.frequency.value == channel_to_perturb.frequency.value: tas_to_remove.append(ta) or_.value -= ta.value for channel, ta_list in or_.scheduled_tas_by_channel.items( ): if channel == channel_to_perturb: or_.scheduled_tas_by_channel[channel] = [] constraints_object.channels.remove(channel_to_perturb) previously_scheduled_tas = deepcopy(or_.scheduled_tas) or_.scheduled_tas = [ ta for ta in previously_scheduled_tas if ta not in tas_to_remove ] # Update the value if you're dealing with a greedy optimization if or_ == lower_bound_optimizer_result: or_.value = sum([ta.value for ta in or_.scheduled_tas]) if or_ == lower_bound_optimizer_result: if self.channel_capacity < 0: tas_to_remove = [] for ta in or_.scheduled_tas: if ta.channel.frequency.value == channel_to_perturb.frequency.value: tas_to_remove.append(ta) or_.value -= ta.value for channel, ta_list in or_.scheduled_tas_by_channel.items( ): if channel == channel_to_perturb: or_.scheduled_tas_by_channel[channel] = [] previously_scheduled_tas = deepcopy(or_.scheduled_tas) or_.scheduled_tas = [ ta for ta in previously_scheduled_tas if ta not in tas_to_remove ] # Randomly selects reconsider from the list of unscheduled TAs unscheduled_tas = [ ta for ta in constraints_object.candidate_tas if ta.id_ not in [x.id_ for x in optimizer_result.scheduled_tas] ] # The new candidate_tas for a ConstraintsObject is reconsider # unscheduled TAs and the scheduled TAs randomly_sampled_tas = [] try: randomly_sampled_tas = random.sample(unscheduled_tas, self.reconsider) except ValueError: if len(unscheduled_tas) == 0: logger.debug( """There are 0 unscheduled TAs to reconsider. The optimizer will attempt to resolve the perturbation using the currently scheduled TAs.""" ) else: logger.debug( f"""The amount of TAs to reconsider ({self.reconsider}) is greater than the number of unscheduled TAs ({len(unscheduled_tas)}). Only able to reconsider {len(unscheduled_tas)} TA{'s' if len(unscheduled_tas) > 1 else ''}.""" ) # Reset the bandwidth and value assignments of TAs between runs for ta in optimizer_result.scheduled_tas: ta.value = 0 ta.bandwidth = Kbps(0) scheduled_and_randomly_sampled_tas = optimizer_result.scheduled_tas + randomly_sampled_tas # Perturbed constraints objects are identical to original constraints, # just different candidate_tas and id constraints_object.candidate_tas = scheduled_and_randomly_sampled_tas # Just in case the perturbations have sent this value beneath 0 if lower_bound_optimizer_result.value < 0: lower_bound_optimizer_result.value = 0 if len(lower_bound_optimizer_result.scheduled_tas) < 0: lower_bound_optimizer_result.scheduled_tas = [] return (constraints_object, lower_bound_optimizer_result)
def test__eq__(self): kbps1 = Kbps(100) kbps2 = Kbps(100) self.assertEqual(kbps1, kbps2)
def test_to_bits_per_second(self): kbps = Kbps(100) self.assertEqual(100000, kbps.to_bits_per_second())
def test_compute_value_at_minimum(self): ta = TA(self.id_, Kbps(200), Kbps(0), self.scaling_factor, self.c) value = ta.compute_value(200) self.assertTrue(value >= 99.995 and value <= 99.996)
def test_compute_value_1750_03_001(self): ta = TA(self.id_, Kbps(1500), Kbps(250), 0.3, 0.001) self.assertTrue(ta.min_value >= 24.786 and ta.min_value <= 24.787)
def test_compute_value_1500_08_0001(self): ta = TA(self.id_, Kbps(1500), Kbps(0), 0.8, 0.001) self.assertTrue(ta.min_value >= 62.149 and ta.min_value <= 62.150)
def test_compute_value_1250_06_0009(self): ta = TA(self.id_, Kbps(500), Kbps(750), 0.6, 0.009) self.assertTrue(ta.min_value >= 59.999 and ta.min_value <= 60)
def test_compute_value_below_minimum_bandwidth(self): ta = TA(self.id_, Kbps(200), Kbps(0), self.scaling_factor, self.c) self.assertEqual(0, ta.compute_value(1))
def test_bandwidth_rate_invalid_type(self): self.assertRaises(BandwidthRateInitializationException, BandwidthRate, 'foo', Kbps(100))
def test_compute_value_at_500_05_0002(self): ta = TA(self.id_, Kbps(500), Kbps(0), 0.5, 0.002) self.assertTrue(ta.min_value >= 31.606 and ta.min_value <= 31.607)
def test_valid_init(self): value = 10000 kbps = Kbps(value) self.assertEqual(value, kbps.value)
def test_compute_value_at_750_01_0004(self): ta = TA(self.id_, Kbps(750), Kbps(0), 0.1, 0.004) self.assertTrue(ta.min_value >= 9.502 and ta.min_value <= 9.503)
def test_compute_value_negative(self): ta = TA(self.id_, Kbps(200), Kbps(0), self.scaling_factor, self.c) self.assertRaises(ComputeValueException, ta.compute_value, -1)
def test___eq__(self): b1 = BandwidthRate(BandwidthTypes.VOICE, Kbps(100)) b2 = BandwidthRate(BandwidthTypes.VOICE, Kbps(100)) self.assertEqual(b1, b2)
def test_compute_value_at_1000_025_0007(self): ta = TA(self.id_, Kbps(1000), Kbps(0), 0.25, 0.007) self.assertTrue(ta.min_value >= 24.977 and ta.min_value <= 24.978)