Execute the factor over the given time interval. Note that this is normally done by the workflow,
        but can also be done on the factor directly

        :param time_interval: The time interval
        :return: self (for chaining)

def execute(self, time_interval):
        """
        Execute the factor over the given time interval. Note that this is normally done by the workflow,
        but can also be done on the factor directly

        :param time_interval: The time interval
        :return: self (for chaining)
        """
        logging.info('{} running from {} to {}'.format(
            self.tool.__class__.__name__, time_interval.start, time_interval.end))

        # Execute the tool to produce the output plate values
        output_plate_values = {}
        if self.input_plate:
            for ipv in self.input_plate.values:
                if ipv in self.source.streams:
                    source = self.source.streams[ipv]
                else:
                    logging.warn("{} with value {} not valid for source {}".format(
                        self.input_plate, ipv, self.source))
                    continue

                output_plate_values[ipv] = self.tool.execute(
                    source=source, interval=time_interval, input_plate_value=ipv)
        else:
            source = self.source.streams[None] if self.source else None
            if "parent_plate" in self.output_plate:
                # Get the parent plate values
                parent_plate = self._plate_manager.plates[self.output_plate["parent_plate"]]
                for ppv in parent_plate.values:
                    output_plate_values[ppv] = self.tool.execute(
                        source=source, interval=time_interval, input_plate_value=ppv)
            else:
                output_plate_values[None] = self.tool.execute(
                    source=source, interval=time_interval, input_plate_value=None)

        # Ensure that the output plate values exist
        for ipv, opv in output_plate_values.items():
            input_plate_value = ".".join("_".join(i) for i in ipv) if ipv else None
            for pv in opv:
                if ipv:
                    identifier = input_plate_value + "." + self.output_plate["meta_data_id"] + "_" + pv
                else:
                    identifier = self.output_plate["meta_data_id"] + "_" + pv
                if not self._meta_data_manager.contains(identifier):
                    self._meta_data_manager.insert(
                        tag=self.output_plate["meta_data_id"],
                        identifier=identifier,
                        parent=input_plate_value if ipv else "root",
                        data=pv
                    )

        if self.output_plate["use_provided_values"]:
            raise NotImplementedError("Currently only support using empty set and complement=True for the new plate")

        if self.output_plate["plate_id"] not in self._plate_manager.plates:
            # Create the output plate
            if self.input_plate:
                parent_plate = self.input_plate.plate_id
            else:
                if "parent_plate" in self.output_plate:
                    parent_plate = self.output_plate['parent_plate']
                else:
                    parent_plate = None
            self._plate_manager.create_plate(
                plate_id=self.output_plate["plate_id"],
                description=self.output_plate["description"],
                meta_data_id=self.output_plate["meta_data_id"],
                values=[],
                complement=True,
                parent_plate=parent_plate
            )

            logging.info("Plate with ID {} created".format(self.output_plate["plate_id"]))

        return self