'Run the calculator until it converges on the desired state.
:param desired_datasets: The dataset state to converge to.
:type desired_datasets: Mapping from ``UUID`` to ``DesiredDataset``.
:param int max_iterations: The maximum number of steps to iterate.
Defaults to 20 on the assumption that iterations are cheap, and
that there will always be fewer than 20 steps to transition from
one desired state to another if there are no bugs.'

def run_to_convergence(self, desired_datasets, max_iterations=20):

    for _ in range(max_iterations):
        try:
            self.run_convergence_step(dataset_map_from_iterable(desired_datasets))
            if compare_dataset_states(self.current_datasets(), dataset_map_from_iterable(desired_datasets)):
                break
        except Exception as e:
            note('Error    converging:    {e.__class__}:    {e}'.format(e=e))
    else:
        raise DidNotConverge(iteration_count=max_iterations)
