Re-scan until fit achieves desired confidence

Problem

Scan a peak and, in real time, fit Gaussian model to the data. Repeatedly re-scan the same region until the uncertainty in the Gaussian width parameter, sigma, is below some threshold.

Approach

Use callbacks.LiveFit() and callbacks.LiveFitPlot() to perform and visualize a non-linear least-squared fit.

Normally we would use plans.scan() to perform the 1D scan. In this case, we need something more sophisticated to incorporate adaptive logic that continues the scan until the fit attains sufficient confidence in sigma. We write our scan logic using the lower-level plans plans.abs_set() and plans.trigger_and_read().

Example Solution

import matplotlib.pyplot as plt
import numpy as np
import lmfit
from bluesky.plans import (subs_decorator, abs_set, trigger_and_read,
                           run_decorator, stage_decorator)
from bluesky.callbacks import LiveFit, LiveFitPlot, LivePlot
from bluesky.examples import motor, noisy_det


def errorbar(lmfit_result, param_name):
    # width of 95% conf interfal:
    ci = lmfit_result.conf_interval()
    return ci[param_name][-2][1] - ci[param_name][1][1]


def gaussian(x, A, sigma, x0):
    return A * np.exp(-(x - x0)**2 / (2 * sigma**2))


model = lmfit.Model(gaussian)
guess = {'A': 10,
         'x0': 1,
         'sigma': lmfit.Parameter('sigma', 3, min=0)}


def scan_gaussian(detectors, motor, start, stop, num):
    global lf
    main_detector = detectors[0]
    main_motor_field, *_ = motor.describe()
    lf = LiveFit(model, main_detector.name, {'x': main_motor_field}, guess)
    lfp = LiveFitPlot(lf, color='r', ax=plt.gca())
    lp = LivePlot(main_detector, main_motor_field,
                  linestyle='none', marker='o', ax=plt.gca())

    @subs_decorator([lfp, lp])
    @stage_decorator(list(detectors) + [motor])
    @run_decorator()
    def plan():
        while True:
            for step in np.linspace(start, stop, num):
                yield from abs_set(motor, step, wait=True)
                yield from trigger_and_read(list(detectors) + [motor])
            err = errorbar(lf.result, 'sigma')
            if err < .03:
                break

    yield from plan()


# Example usage:

RE(scan_gaussian([noisy_det], motor, -1, 1, 200))
../_images/scan_gaussian.png