Execute the factor over the given time interval

        :param time_interval:
        :return:

def execute(self, time_interval):
        """
        Execute the factor over the given time interval

        :param time_interval:
        :return:
        """
        logging.info('{} with sink node {} running from {} to {}'.format(
            self.tool.__class__.__name__, self.sink.node_id, time_interval.start, time_interval.end))

        if self.plates:
            if isinstance(self.tool, AggregateTool):
                if len(self.sources) != 1:
                    raise ValueError("Currently only a single source node is valid for an Aggregate Tool")
                if self.alignment_node:
                    raise ValueError("Currently an alignment node cannot be used with an Aggregate Tool")
                
                all_sources = self.sources[0]
                
                # Here we should loop through the plate values of the sink, and get the sources that are appropriate for
                # that given plate value, and pass only those sources to the tool. This is cleaner than making the tool
                # deal with all of the sources
                for pv in self.sink.plate_values:
                    sources = [all_sources.streams[s] for s in all_sources.streams if all([v in s for v in pv])]
                    sink = self.sink.streams[pv]
                    self.tool.execute(sources=sources, sink=sink, interval=time_interval, alignment_stream=None)
            elif isinstance(self.tool, SelectorTool):
                if len(self.sources) == 1:
                    sources = self.sources[0].streams.values()
                elif len(self.sources) == 2:
                    selector_node = self.sources[0]
                    if len(selector_node.streams) != 1:
                        raise ValueError("Selector node should only have one stream")
                    sources = [self.sources[0].streams[None], self.sources[1].streams.values()]
                else:
                    raise ValueError("Currently only one or twos source nodes are valid for a Selector Tool")
                if self.alignment_node:
                    raise ValueError("Currently an alignment node cannot be used with a Selector Tool")
                
                diff, counts, is_sub_plate = self.sources[-1].difference(self.sink)
                # TODO: This sub-plate selection is deprecated

                if (counts == [1, 1] and is_sub_plate) or \
                    (len(self.sink.plates)==1 and counts == [1, 0] and is_sub_plate) or \
                    (next(p.is_root for p in self.sources[-1].plates)
                     and len(self.sink.plates) == 1
                     and self.sink.plates[0] in self.sources[-1].plates):
                        # Special case of tools that are performing sub-selection
                        self.tool.execute(sources=sources,
                                          sinks=self.sink.streams.values(),
                                          interval=time_interval)
                else:
                    raise ValueError("Source and sink plates do not match within a Selector Tool")
            else:
                # TODO: This loop over plates is probably not correct:
                # What we probably want is to take the cartesian product of plate values
                if len(self.plates) == 1:
                    plate = self.plates[0]
                    for pv in plate.values:
                        sources = self.get_sources(plate, pv)
                        sink = self.sink.streams[pv]
                        self.tool.execute(sources=sources, sink=sink, interval=time_interval,
                                          alignment_stream=self.get_alignment_stream(None, None))
                else:
                    if len(self.sources) != 1 and not all(s.plates == self.plates for s in self.sources):
                        source_plates = sorted(p.plate_id for s in self.sources for p in s.plates)
                        self_plates = sorted(p.plate_id for p in self.plates)
                        if source_plates == self_plates:
                            # This is the case where the sources are all on separate plates and the sink is the
                            # combination
                            search = [[x[0] for x in p.values] for p in self.plates]
                            _pv = sorted(itertools.product(*search))
                            for pv in _pv:
                                # Here we're selecting the streams that have the partial match of the plate value
                                sources = [source.streams[s] for source in self.sources
                                           for s in source.streams if (s[0] in pv)]
                                try:
                                    sink = self.sink.streams[pv]
                                    self.tool.execute(sources=sources, sink=sink, interval=time_interval,
                                                  alignment_stream=self.get_alignment_stream(None, None))
                                except KeyError as e:
                                    continue

                        else:
                            raise NotImplementedError
                    for pv in Plate.get_overlapping_values(self.plates):
                        sources = [source.streams[s] for source in self.sources for s in source.streams if pv == s]
                        sink = self.sink.streams[pv]
                        self.tool.execute(sources=sources, sink=sink, interval=time_interval,
                                          alignment_stream=self.get_alignment_stream(None, None))
        else:
            if isinstance(self.tool, AggregateTool):
                # raise ValueError("Cannot execute an AggregateTool if no plates are defined for the factor")
                # Here we're trying to aggregate off a plate. This is only allowed for a single non-overlapping plate.
                if len(self.sources) != 1:
                    raise ValueError("Currently only a single source node is valid for an Aggregate Tool")
                if self.alignment_node:
                    raise ValueError("Currently an alignment node cannot be used with an Aggregate Tool")

                sources = self.sources[0].streams.values()
                sink = self.sink.streams[None]
                self.tool.execute(sources=sources, sink=sink, interval=time_interval, alignment_stream=None)

            else:
                # sources = [source.streams[None] for source in self.sources] if self.sources else None
                sources = self.get_global_sources()
                sink = self.sink.streams[None]
                self.tool.execute(sources=sources, sink=sink, interval=time_interval,
                                  alignment_stream=self.get_alignment_stream(None, None))
        return self