Creates a factor. Instantiates a single tool for all of the plates, and connects the source and sink nodes with
        that tool.

        Note that the tool parameters these are currently fixed over a plate. For parameters that vary over a plate,
        an extra input stream should be used

        :param alignment_node:
        :param tool: The tool to use. This is either an instantiated Tool object or a dict with "name" and "parameters"
        :param sources: The source nodes
        :param sink: The sink node
        :return: The factor object
        :type tool: Tool | dict
        :type sources: list[Node] | tuple[Node] | None
        :type sink: Node
        :type alignment_node: Node | None
        :rtype: Factor

def create_factor(self, tool, sources, sink, alignment_node=None):
        """
        Creates a factor. Instantiates a single tool for all of the plates, and connects the source and sink nodes with
        that tool.

        Note that the tool parameters these are currently fixed over a plate. For parameters that vary over a plate,
        an extra input stream should be used

        :param alignment_node:
        :param tool: The tool to use. This is either an instantiated Tool object or a dict with "name" and "parameters"
        :param sources: The source nodes
        :param sink: The sink node
        :return: The factor object
        :type tool: Tool | dict
        :type sources: list[Node] | tuple[Node] | None
        :type sink: Node
        :type alignment_node: Node | None
        :rtype: Factor
        """
        # if isinstance(tool, dict):
        #     tool = self.channels.get_tool(**tool)

        if not isinstance(tool, BaseTool):
            raise ValueError("Expected Tool, got {}".format(type(tool)))

        if sink.plates:
            if isinstance(tool, (AggregateTool, SelectorTool)):
                if not sources or len(sources) > 2:
                    raise FactorDefinitionError("{} requires one or two source nodes".format(type(tool)))

                if len(sources) == 2 and sources[0].plates:
                    raise FactorDefinitionError("{} requires the first source to have no plates".format(type(tool)))

                if not sources[-1].plates:
                    raise FactorDefinitionError("{} source must live on a plate".format(type(tool)))

                if len(sources[-1].plates) != 1:
                    # Make sure that there are exactly two plates that don't match: one from each side
                    diff, counts, is_sub_plate = sources[-1].difference(sink)
                    if counts == [1, 1]:
                        # TODO: This sub-plate selection is deprecated
                        if not is_sub_plate:
                            raise IncompatiblePlatesError("Sink plate is not a simplification of source plate")
                    else:
                        # If there are two plates, and one (or both) of them is a root plate, than assume that we are
                        # simplifying by removing that plate
                        if next(p.is_root for p in sources[-1].plates):
                            if len(sink.plates) != 1:
                                raise IncompatiblePlatesError(
                                    "Multiple sink plates defined. "
                                    "Did you intend a simplification of 2 source plates to a sink plate?")
                            if sink.plates[0] not in sources[-1].plates:
                                raise IncompatiblePlatesError(
                                    "Source and sink plates do not match. "
                                    "Did you intend a simplification of 2 source plates to a sink plate?")
                        else:
                            if len(sink.plates) > 1:
                                raise NotImplementedError
                            source_plates = sources[-1].plates
                            sink_plate = sink.plates[0]
                            if len(source_plates) != 2:
                                raise IncompatiblePlatesError(
                                    "Sink plate is not a simplification of source plate (source must be 2 plates)")
                            plate_diff = set(source_plates).difference({sink_plate, })
                            if len(plate_diff) != 1:
                                raise IncompatiblePlatesError(
                                    "Sink plate is not a simplification of source plate "
                                    "(the number of plates in the set difference of source and sink is not 1")
                            plate_diff = list(plate_diff)[0]
                            if plate_diff.parent != sink_plate.parent:
                                raise IncompatiblePlatesError(
                                    "Sink plate is not a simplification of source plate (parents do not match)")
                else:
                    # Check if the parent plate is valid instead
                    source_plate = sources[-1].plates[0]
                    sink_plate = sink.plates[0]

                    error = self.check_plate_compatibility(tool, source_plate, sink_plate)
                    if error is not None:
                        raise IncompatiblePlatesError(error)
            else:
                if sources:
                    # Check that the plates are compatible
                    source_plates = list(itertools.chain(*(source.plate_ids for source in sources)))
                    for p in sink.plate_ids:
                        if p not in set(source_plates):
                            raise IncompatiblePlatesError("{} not in source plates".format(p))
                    for p in source_plates:
                        if p not in set(sink.plate_ids):
                            raise IncompatiblePlatesError("{} not in sink plates".format(p))
            plates = sink.plates
        else:
            plates = None

        factor = Factor(tool=tool, source_nodes=sources,
                        sink_node=sink, alignment_node=alignment_node,
                        plates=plates)

        self._add_factor(factor)
        return factor