Called at each iteration.

        This function does time measurements, updates timing in the monitoring context and calls
        all monitoring tasks.

def _on_iteration(self, *args, **kwargs) -> None:
        """
        Called at each iteration.

        This function does time measurements, updates timing in the monitoring context and calls
        all monitoring tasks.
        """

        # Update timing and the iteration number in the monitoring context.
        current_timestamp = get_hr_time()
        self._context.optimisation_time += current_timestamp - self._last_timestamp
        self._context.total_time = current_timestamp - self._start_timestamp
        self._context.optimiser_updated = False
        if not self._context.optimisation_finished:
            self._context.iteration_no += 1

        # Call all monitoring functions
        for func in self._monitor_tasks:
            func(self._context, *args, **kwargs)

        # Remember the time when the control is returned back to the optimiser, so that the next
        # optimisation step can be accurately timed.
        self._last_timestamp = get_hr_time()