docs: 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