Esempio n. 1
0
    def compute_bw_efficiency(self):
        """Computes the total bandwidth efficiency in this schedule."""
        comm_len = timedelta(microseconds=0)
        for txop in self.txops:
            comm_len += txop.stop_usec - txop.start_usec

        bw_eff = comm_len / timedelta(microseconds=100000)
        return bw_eff
Esempio n. 2
0
    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
Esempio n. 3
0
    def compute_value_ta(self, constraints_object):
        """Computes the total value for each indidividual TA

        :param ConstraintsObject constraints_object: The constraints this schedule was created under
        :returns Dict[TA: int]: A dictionary of TA to it's value provided
        """
        values_by_ta = {}

        # Group TxOps by TA
        txops_by_ta = defaultdict(list)
        for txop in self.txops:
            txops_by_ta[txop.ta].append(txop)

        # Sort by start time
        for ta, txops in txops_by_ta.items():
            txops.sort(key=lambda x: x.start_usec, reverse=True)

        for ta, txops in txops_by_ta.items():
            comm_len = timedelta(microseconds=0)
            for txop in txops:
                comm_len += txop.stop_usec - txop.start_usec

            bw = ta.compute_bw_from_comm_len(ta.channel.capacity, ta.latency,
                                             comm_len)
            values_by_ta[ta] = ta.compute_value_at_bandwidth(bw)

        return values_by_ta
Esempio n. 4
0
    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
Esempio n. 5
0
    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
Esempio n. 6
0
    def __init__(self, frequency, capacity):
        """
        Constructor

        :param Frequency frequency: The frequency this channel sends data over.
        :param Kbps capacity: Throughput capacity. Range is from 1Mbps to 10Mbps inclusive.
        """
        self.frequency = frequency
        self.capacity = capacity

        self.start_time = timedelta(microseconds=0)
        self.value = 0
Esempio n. 7
0
    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
Esempio n. 8
0
    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)
Esempio n. 9
0
    def compute_communication_length(self,
                                     channel_capacity,
                                     latency,
                                     guard_band=timedelta(microseconds=0)):
        """Returns the amount of bandwidth required to allocate to this TA within the min_interval.
        The output will be a multiple of min_interval + 2 * guard_band.

        :param Kbps channel_capacity: The capacity of the channel
        :param timedelta guard_band: The guard_band requirement
        :returns timedelta communication_length: The amount of time this TA must communicate for to meet it's bandwidth requirement
        """
        two_way_min_interval = 2 * MDL_MIN_INTERVAL
        communication_length = (
            (self.bandwidth.value / channel_capacity.value) *
            latency.get_microseconds()) + (2 * guard_band.get_microseconds())

        # Round value to return within the specified MIN_INTERVAL
        dist_from_interval = (communication_length -
                              2 * guard_band.get_microseconds()) % (
                                  two_way_min_interval.get_microseconds())
        if dist_from_interval != 0:
            communication_length += two_way_min_interval.get_microseconds(
            ) - dist_from_interval
        return timedelta(microseconds=communication_length)
    def _extend_final_os_schedule_block(self, schedule):
        """Extends the stop_usec of the final TxOps scheduled to communicate in the
           conservative schedule blocks.

        :param Schedule schedule: The schedule to modify
        """
        num_txops = len(schedule.txops)
        schedule.txops.sort(key=lambda x: x.start_usec)

        for i in range(1, num_txops):
            prev_txop = schedule.txops[i - 1]
            curr_txop = schedule.txops[i]
            communication_gap = curr_txop.start_usec - prev_txop.stop_usec

            if communication_gap > self.constraints_object.guard_band:
                prev_txop.stop_usec += communication_gap - self.constraints_object.guard_band

            elif i == num_txops - 1:
                curr_txop.stop_usec = timedelta(microseconds=99000)
    def _wraparound_blocks(self, channel, ta_list, min_latency, start_times,
                           schedule):
        """Places two conservative blocks at the beginnings and ends of the channel

        :param Channel channel: The channel to schedule on
        :param [<TA>] ta_list: The list of TAs scheduled to communicate on this channel
        :param timedelta min_latency: The minimum latency TA scheduled on this channel
        :param {str: timedelta} start_times: The list of times that TAs start communicating.
                                             This is also formatted by their direction. i.e.
                                             {TA7up: timedelta(microseconds=500)}
                                             This means TA7 communicates in the up
                                             direction at 500 microseconds
        :param Schedule schedule: The schedule to modify

        :returns [<TxOp>] txops: The list of created TxOp nodes
        """
        first_wraparound_block = timedelta(microseconds=0)
        last_wraparound_block = self.constraints_object.epoch - min_latency
        block_starts = [first_wraparound_block, last_wraparound_block]

        self.create_blocks(channel, ta_list, min_latency, start_times,
                           block_starts, schedule)
Esempio n. 12
0
    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
Esempio n. 13
0
    def create_blocks(self, channel, ta_list, min_latency, start_times,
                      block_starts, schedule):
        """ Creates a set of communication blocks for one channel, each of length min_latency amongst the scheduled TAs

        :param Channel channel: The channel to create this schedule on
        :param [<TA>] ta_list: The list of TAs scheduled on this channel
        :param OptimizerResult optimizer_result: The algorithm result to create a schedule from
        :param {str, int} start_times: The dictionary of start times formatted as: ID + Direction: milliseconds_time
        :param [<int>] block_starts: The list of times each block starts at. i.e. If [0, 80] is entered, you will get two conservative blocks, one that begins at 0, and one that begins at 80
        :param Schedule schedule: The schedule
        :returns [<TxOp>] txops: A list of TxOps to be scheduled on this channel
        """
        channel_start_time = timedelta(microseconds=0)

        for x in range(len(ta_list)):
            ta = ta_list[x]
            # The time at which a TA can begin communicating is equal to the start
            # time of the channel
            up_start = channel_start_time

            # The amount of time a TA is allowed to communicate is equal to
            # the amount of time it requires for two way communication / 2,
            # without a guard band added in. Guard bands will be added later.
            one_way_transmission_length = ta.compute_communication_length(
                channel.capacity, min_latency, timedelta(microseconds=0)) / 2

            # Stretch out last TA scheduled to communicate on this channel
            if x == len(
                    ta_list) - 1 and one_way_transmission_length > timedelta(
                        microseconds=1000):
                one_way_transmission_length = (
                    (min_latency - up_start) /
                    2) - (2 * self.constraints_object.guard_band)
            # Each direction of transmission requires
            up_stop = one_way_transmission_length + channel_start_time
            down_start = up_stop + self.constraints_object.guard_band
            down_stop = down_start + one_way_transmission_length

            # Update the start time of this channel to be the time at which the
            # last TA communicated, plus on guard_band
            channel_start_time = down_stop + self.constraints_object.guard_band

            # We need to record what times a TA last communicated UP and DOWN
            # in order to make sure we are satisfying latency requirements
            # This is only used by the ConservativeScheduler
            start_times[ta.id_ + 'up'] = up_stop
            start_times[ta.id_ + 'down'] = down_stop

            # Create TxOps for each block in blocks
            txops = []
            for x in block_starts:

                txop_up = TxOp(
                    ta=ta,
                    radio_link_id=id_to_mac(ta.id_, 'up'),
                    start_usec=up_start + x,
                    stop_usec=up_stop + x,
                    center_frequency_hz=channel.frequency,
                    txop_timeout=self.constraints_object.txop_timeout)
                txop_down = TxOp(
                    ta=ta,
                    radio_link_id=id_to_mac(ta.id_, 'down'),
                    start_usec=down_start + x,
                    stop_usec=down_stop + x,
                    center_frequency_hz=channel.frequency,
                    txop_timeout=self.constraints_object.txop_timeout)

                schedule.txops.extend([txop_up, txop_down])
Esempio n. 14
0
'''constants.py

A list of upper bounds and other constant values.
Paths are relative to start.py.
'''

from cp1.data_objects.mdl.kbps import Kbps
from cp1.utils.decorators.timedelta import timedelta
from ortools.linear_solver import pywraplp

# MDL Constraints
MAX_BANDWIDTH = Kbps(8000)  # Maximum allowable bandwidth for any TA
MDL_MIN_INTERVAL = timedelta(
    microseconds=500
)  # The minimum time interval allowed to be scheduled in an MDL file

# Optimization Parameters
DYNAMIC_PROGRAM_TABLE_QUANTIZATION = 2  # The number of minimum schedulable time units per millisecond
INTEGER_PROGRAM_TIME_LIMIT = 15  # OR Tools Solver engine time limit in seconds
INTEGER_PROGRAM_ENGINE = pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING

# OrientDB
ORIENTDB_CONFIG_FILE = '../../../conf/orientdb_config.json'
CONSTRAINTS_DB = 'cra_constraints'
MDL_DB = 'cra_mdl'

# Outputs and file generation relative to start.py
CONFIG_FILE = '../../../conf/config.json'
BASE_MDL_SHELL_FILE = '../../../external/TxOpScheduleViewer/brass_mdl_tools/base.xml'
MDL_SHELL_FILE = '../../../output/mdl/mdl_shell.xml'
MDL_DIR = '/data/mdl'
Esempio n. 15
0
    def update_schedule(self, schedules):
        """
        Stores the list of TxOp nodes found in schedule in the MDL file.
        Tries to create TransmissionSchedule and TxOp classes if they don't exist.
        Creates one TransmissionSchedule node for each TA we are interested in scheduling.


        :param [<Schedule>] schedules: A list of schedule objects, one for each channel.
        """
        # The original MDL file we import does not contain any TransmissionSchedule or TxOp
        # elements, so we have to create this class before indexing TransmissionSchedules
        try:
            self.create_node_class('TransmissionSchedule')
        except:
            pass

        try:
            self.create_node_class('TxOp')
        except:
            pass

        try:
            self.delete_nodes_by_class('TransmissionSchedule')
        except:
            pass
        try:
            self.delete_nodes_by_class('TxOp')
        except:
            pass

        radio_links = self.get_nodes_by_type('RadioLink')

        # Construct one list of TxOps from the list of Schedules
        txop_list = []
        for schedule in schedules:
            for txop in schedule.txops:
                txop.start_usec = timedelta(days=txop.start_usec.days, seconds=txop.start_usec.seconds, microseconds=txop.start_usec.microseconds)
                txop.stop_usec = timedelta(days=txop.stop_usec.days, seconds=txop.stop_usec.seconds, microseconds=txop.stop_usec.microseconds)
                txop_list.append(txop)

        # Generate a Dictionary of RadioLinks
        radio_link_rids = defaultdict(list)
        for txop in txop_list:
            if txop.radio_link_id not in radio_link_rids:
                radio_link_rids[txop.radio_link_id].append(txop.center_frequency_hz.value)

        # Match RadioLinks to their corresponding RIDs
        transmission_schedule_rids = {}
        for rl in radio_link_rids:
            i = 0
            found = False
            for orient_record in radio_links:
                if rl == orient_record.ID:
                    radio_link_rids[rl].append(orient_record._rid)
                    transmission_schedule_rid = self.create_node('TransmissionSchedule', {'uid': 'TransmissionSchedule-{0}'.format(i)})
                    transmission_schedule_rids[rl] = transmission_schedule_rid
                    self.set_containment_relationship(parent_rid=orient_record._rid, child_rid=transmission_schedule_rid)
                    found = True
                    i += 1
                    break
            if not found:
                raise RadioLinkNotFoundException('Unable to find RadioLink ({0}) in OrientDB database'.format(rl), 'OrientDBSession.update_schedule')

        # Create TxOp objects in OrientDB and connect them to RadioLinks
        i = 0
        for txop in txop_list:
            txop_properties = {
                'StartUSec': txop.start_usec.get_microseconds(),
                'StopUSec': txop.stop_usec.get_microseconds(),
                'TxOpTimeout': txop.txop_timeout.value,
                'CenterFrequencyHz': txop.center_frequency_hz.value,
                'uid': 'TxOp-{0}'.format(i)
                }

            txop_rid = self.create_node('TxOp', txop_properties)
            self.set_containment_relationship(parent_rid=transmission_schedule_rids[txop.radio_link_id], child_rid=txop_rid)
            i += 1

        self._update_ran(radio_link_rids)
Esempio n. 16
0
    def _optimize(self,
                  constraints_object,
                  discretized_tas,
                  deadline_window=None):
        """Greedily selects TAs for scheduling.
        Sorts the candidate TAs based on the ratio between utility threshold and minimum bandwidth.
        Then iterates through the sorted list to schedule TAs on the first available channel.
        A TA requires the total_minimum_bandwidth and twice the guard band
        since TAs require two-way communication; Ground to TA and TA to Ground.

        i.e.
        <ID, Bandwidth, Value>
        [<TA1, 200, 50>, <TA2, 150, 100>, <TA3, 100, 100>, <TA4, 150, 50>, <TA5, 180, 60>]
        Gets sorted to:
        [<TA3, ...>, <TA2, ...>, <TA5, ...>]

        :returns dict{int:[TA]}: A channel frequency and a list of TAs to be scheduled at that frequency.
        """
        logger.debug('Beginning Greedy Optimization...')
        start_time = time.perf_counter()
        scheduled_tas = []

        # Set the assigned bandwidth of each TA to be it's minimum as the
        # Greedy Optimizer only considers TAs at their minimum bandwidths
        for ta in constraints_object.candidate_tas:
            ta.bandwidth = ta.total_minimum_bandwidth

        # Order TAs by the ratio of their value / bandwidth requirement at minimum bandwidth
        # TAs will then be selected based on this ordering, hence the name
        # Greedy Optimizer
        constraints_object.candidate_tas.sort(
            key=lambda ta: (ta.min_value / ta.total_minimum_bandwidth.value),
            reverse=True)

        for channel in constraints_object.channels:
            channel_start_time = timedelta(microseconds=0)

            # The length of time of each communication block on a channel in a Greedy Optimizer
            # is identical. It is the minimum latency of all TAs scheduled to communicate on
            # this channel. By using the minimum latency, though it is leff efficient, we
            # can gurantee that every scheduled TA using this algorithm can fit within
            # this block
            min_latency = self.retrieve_min_latency(
                constraints_object.candidate_tas)

            for ta in constraints_object.candidate_tas:

                # Only consider this TA if it has not already been assigned to another
                # channel, and it is eligible to communicate on this channel
                if ta not in scheduled_tas and ta.channel_is_eligible(channel):

                    # The minimum communication requirement is the amount of time
                    # this TA requires when only assigned it's minimum bandwidth
                    min_communication_requirement = ta.compute_communication_length(
                        channel.capacity, min_latency,
                        constraints_object.guard_band)

                    # The TA fits on this channel if the amount of time it requires
                    # does not exceed the length of one block on this channel, while
                    # also taking into consideration that other TAs may have already
                    # been scheduled on this channel already (hence channel_start_time)
                    ta_fits_on_channel = min_communication_requirement + channel_start_time <= min_latency
                    if ta_fits_on_channel:
                        bandwidth = ta.compute_bw_from_comm_len(
                            channel.capacity, min_latency,
                            min_communication_requirement,
                            constraints_object.guard_band)
                        ta.bandwidth = bandwidth
                        ta.value = ta.compute_value_at_bandwidth(bandwidth)
                        ta.channel = channel
                        channel_start_time += min_communication_requirement
                        scheduled_tas.append(ta)

        value = self.compute_solution_value(scheduled_tas)
        run_time = time.perf_counter() - start_time
        logger.debug(
            'Greedy Optimization complete in {0} seconds'.format(run_time))

        return OptimizerResult(scheduled_tas=scheduled_tas,
                               run_time=run_time,
                               solve_time=run_time,
                               value=value)