def solver(constraints, coord_sys=None):
    """
    Solve constraints. Solutions pair :class:`Constraint <cqparts.constraint.Constraint>`
    instances with their suitable :class:`CoordSystem <cqparts.utils.geometry.CoordSystem>`
    world coordinates.

    :param constraints: constraints to solve
    :type constraints: iterable of :class:`Constraint <cqparts.constraint.Constraint>`
    :param coord_sys: coordinate system to offset solutions (default: no offset)
    :type coord_sys: :class:`CoordSystem <cqparts.utils.geometry.CoordSystem>`

    :return: generator of (:class:`Component <cqparts.Component>`,
             :class:`CoordSystem <cqparts.utils.geometry.CoordSystem>`) tuples.
    """

    if coord_sys is None:
        coord_sys = CoordSystem()  # default

    # Verify list contains constraints
    for constraint in constraints:
        if not isinstance(constraint, Constraint):
            raise ValueError("{!r} is not a constraint".format(constraint))

    solved_count = 0

    indexed = list(constraints)

    # Continue running solver until no solution is found
    while indexed:
        indexes_solved = []
        for (i, constraint) in enumerate(indexed):

            # Fixed
            if isinstance(constraint, Fixed):
                indexes_solved.append(i)
                yield (
                    constraint.mate.component,
                    coord_sys + constraint.world_coords + (CoordSystem() - constraint.mate.local_coords)
                )

            # Coincident
            elif isinstance(constraint, Coincident):
                try:
                    relative_to = constraint.to_mate.world_coords
                except ValueError:
                    relative_to = None

                if relative_to is not None:
                    indexes_solved.append(i)
                    # note: relative_to are world coordinates; adding coord_sys is not necessary
                    yield (
                        constraint.mate.component,
                        relative_to + (CoordSystem() - constraint.mate.local_coords)
                    )

        if not indexes_solved:  # no solutions found
            # At least 1 solution must be found each iteration.
            # if not, we'll just be looping forever.
            break
        else:
            # remove constraints from indexed list (so they're not solved twice)
            for j in reversed(indexes_solved):
                del indexed[j]

    if indexed:
        raise ValueError("not all constraints could be solved")