def _get_good_fits(loop, target, fit_type, fix=()):
    # find good fits from other loop
    good_guesses, powers, fields, temperatures = [], [], [], []
    try:
        loops = loop.resonator.loops
    except AttributeError:  # no resonator defined
        return good_guesses, powers, fields, temperatures
    for potential_loop in loops:
        if potential_loop is loop:  # don't use fits from this loop
            continue
        if np.isnan(getattr(potential_loop,
                            target)):  # no nans in the target loop attribute
            continue
        different = [
            not np.isclose(getattr(potential_loop, fixed),
                           getattr(loop, fixed),
                           equal_nan=True) for fixed in fix
        ]
        if any(different):  # don't use fits with a different fixed attribute
            continue
        # only use fits that have redchi < MAX_REDCHI
        results_dict = getattr(potential_loop, fit_type + "_results")
        red_chi = _red_chi(
            fit_type,
            results_dict['best']) if "best" in results_dict.keys() else np.inf
        if red_chi < MAX_REDCHI:
            if fit_type == "lmfit":
                good_guesses.append(
                    results_dict['best']['result'].params.copy())
            elif fit_type == "loopfit":
                good_guesses.append(results_dict['best'].copy())
            else:
                raise ValueError(
                    "'fit_type' must be either 'loopfit' or 'lmfit'")
            powers.append(potential_loop.power)
            temperatures.append(potential_loop.field)
            fields.append(potential_loop.temperature)
    return good_guesses, powers, fields, temperatures
def nonlinear_fit(data,
                  fit_type="lmfit",
                  label="nonlinear_fit",
                  sigma=True,
                  parameter=None,
                  value=None,
                  vary=True,
                  parallel=False,
                  return_dict=False,
                  callback=None,
                  **fit_kwargs):
    """
    Fit the loop using a previous good fit, but with the nonlinearity.
    Args:
        data: Loop, Resonator, Sweep, or collection of those objects
            The loop or loops to fit. If Resonator or Sweep objects are given
            all of the contained loops are fit.
        fit_type: string
            The type of fit to use. Allowed options are "lmfit" and
            "loopfit". The default is "lmfit".
        label: string (optional)
            The label to store the fit results under. The default is
            "nonlinear_fit".
        sigma: boolean, complex number, or numpy.ndarray
            If True, the standard deviation of the S21 data is computed from
            the loop. It can also be specified with a complex number or an
            array of complex numbers. The value is then passed to the model
            residual with the sigma keyword argument. If the model does not
            have this key word, this will not work and sigma should be set to
            False.
        parameter: string (optional)
            The nonlinear parameter name and value to use. If None, the default
            for the fit_type is used.
        value: float (optional)
            The nonlinear parameter name and value to use. If None, the default
            for the fit_type is used.
        vary: boolean (optional)
            Determines if the nonlinearity is varied in the fit. The default is
            True.
        parallel: boolean or integer (optional)
            Compute the fit for each loop in parallel. The default is False,
            and the computation is done in serial. If True, a Pool object is
            created with multiprocessing.cpu_count() // 2 CPUs. If an integer,
            that many CPUs will be used. This method will only work on systems
            that can use os.fork().
        return_dict: boolean (optional)
            Return only the fit results dictionary if True. The default is
            False.
        callback: function (optional)
            A function to be called after every loop fit. The default is None
            and no function call is done.
        fit_kwargs: optional keyword arguments
            Additional keyword arguments to pass to the fitting function. E.g.
            loop.lmfit() or loop.loopfit().
    Returns:
        loops: a list of mkidcalculator.Loop objects or a list of dictionaries
            The loop objects that were fit. If return_dict is True, the
            loop.lmfit_results or loop.loopfit_results dictionaries are
            returned instead.
    """
    loops = _get_loops(data)
    if parallel:
        loops = _parallel(nonlinear_fit,
                          loops,
                          parallel,
                          fit_type,
                          label=label,
                          sigma=sigma,
                          parameter=parameter,
                          value=value,
                          vary=vary,
                          **fit_kwargs)
        return _prepare_output(loops, fit_type, return_dict=return_dict)
    for loop in loops:
        # make guess
        results_dict = getattr(loop, fit_type + "_results")
        if "best" in results_dict.keys():
            # only fit if previous fit has been done
            if fit_type == "lmfit":
                guess = loop.lmfit_results["best"]["result"].params.copy()
                if parameter is None:
                    parameter = "a_sqrt"
                if value is None:
                    value = 0.05
            elif fit_type == "loopfit":
                guess = loop.loopfit_results["best"].copy()
                if parameter is None:
                    parameter = "a"
                if value is None:
                    value = 0.0025
            else:
                raise ValueError(
                    "'fit_type' must be either 'loopfit' or 'lmfit'")
            _set(fit_type, parameter, guess, value, fit_kwargs, vary=vary)
            # do fit
            result = _do_fit(loop, guess, fit_type, fit_kwargs, sigma, label)
            log.info(
                FIT_MESSAGE.format(loop.name, label,
                                   _red_chi(fit_type, result)))
        else:
            raise AttributeError(
                "loop does not have a previous fit on which to base the nonlinear fit."
            )
        if callback is not None:
            callback()
    return _prepare_output(loops, fit_type, return_dict=return_dict)
def power_fit(data,
              fit_type="lmfit",
              label="power_fit",
              sigma=True,
              baseline=None,
              parallel=False,
              return_dict=False,
              callback=None,
              **fit_kwargs):
    """
    Fit the loop using the two nearest power data points of similar
    temperature and same field in the resonator as guesses. If there are no
    good guesses, nothing will happen.
    Args:
        data: Loop, Resonator, Sweep, or collection of those objects
            The loop or loops to fit. If Resonator or Sweep objects are given
            all of the contained loops are fit. The loops must be associated
            with resonator objects.
        fit_type: string
            The type of fit to use. Allowed options are "lmfit" and
            "loopfit". The default is "lmfit".
        label: string (optional)
            The label to store the fit results under. The default is
            "power_fit".
        sigma: boolean, complex number, or numpy.ndarray
            If True, the standard deviation of the S21 data is computed from
            the loop. It can also be specified with a complex number or an
            array of complex numbers. The value is then passed to the model
            residual with the sigma keyword argument. If the model does not
            have this key word, this will not work and sigma should be set to
            False.
        parallel: boolean or integer (optional)
            Compute the fit for each loop in parallel. The default is False,
            and the computation is done in serial. If True, a Pool object is
            created with multiprocessing.cpu_count() // 2 CPUs. If an integer,
            that many CPUs will be used. This method will only work on systems
            that can use os.fork().
        return_dict: boolean (optional)
            Return only the lmfit_results dictionary if True. The default is
            False.
        baseline: tuple of strings (optional)
            A list of parameter names corresponding to the baseline. They will
            use the model guess from the best fit done so far on this loop as a
            starting point. If no fit has been done a AttributeError will be
            raised.
        callback: function (optional)
            A function to be called after every loop fit. The default is None
            and no function call is done.
        fit_kwargs: optional keyword arguments
            Additional keyword arguments to pass to the fitting function. E.g.
            loop.lmfit() or loop.loopfit().
     Returns:
        loops: a list of mkidcalculator.Loop objects or a list of dictionaries
            The loop objects that were fit. If return_dict is True, the
            loop.lmfit_results or loop.loopfit_results dictionaries are
            returned instead.
    """
    loops = _get_loops(data)
    if parallel:
        loops = _parallel(power_fit,
                          loops,
                          parallel,
                          fit_type,
                          label=label,
                          sigma=sigma,
                          baseline=baseline,
                          **fit_kwargs)
        return _prepare_output(loops, fit_type, return_dict=return_dict)
    for loop in loops:
        # check that at least one other fit has been done first
        if "best" not in getattr(loop, fit_type + "_results").keys():
            raise AttributeError(
                "loop does not have a previous fit on which to base the power fit."
            )
        # find good fits from other loop
        good_guesses, powers, _, temperatures = _get_good_fits(loop,
                                                               "power",
                                                               fit_type,
                                                               fix=("field", ))
        # get the guesses nearest in power and temperature data sets
        distance = np.empty(len(good_guesses),
                            dtype=[('power', np.float),
                                   ('temperature', np.float)])
        distance['power'] = np.abs(loop.power - np.array(powers))
        distance['temperature'] = np.abs(loop.temperature -
                                         np.array(temperatures))
        indices = np.argsort(
            distance, order=("temperature",
                             "power"))  # closest in temperature then in power
        # fit the two nearest data sets
        used_powers = []
        for index in indices:
            if len(used_powers) >= 2:
                break
            if powers[
                    index] not in used_powers:  # don't try the same power more than once
                used_powers.append(powers[index])
                # pick guess
                guess = good_guesses[index]
                # do fit
                fit_label = label + "_" + str(len(used_powers) - 1)
                result = _do_fit(loop, guess, fit_type, fit_kwargs, sigma,
                                 fit_label)
                log.info(
                    FIT_MESSAGE.format(loop.name, fit_label,
                                       _red_chi(fit_type, result)))
        if callback is not None:
            callback()
    return _prepare_output(loops, fit_type, return_dict=return_dict)
def temperature_fit(data,
                    fit_type="lmfit",
                    label="temperature_fit",
                    sigma=True,
                    parallel=False,
                    return_dict=False,
                    callback=None,
                    **fit_kwargs):
    """
    Fit the loop using the two nearest temperature data points of the same
    power and field in the resonator as guesses. If there are no good guesses,
    nothing will happen.
    Args:
        data: Loop, Resonator, Sweep, or collection of those objects
            The loop or loops to fit. If Resonator or Sweep objects are given
            all of the contained loops are fit. The loops must be associated
            with resonator objects.
        fit_type: string
            The type of fit to use. Allowed options are "lmfit" and
            "loopfit". The default is "lmfit".
        label: string (optional)
            The label to store the fit results under. The default is
            "temperature_fit".
        sigma: boolean, complex number, or numpy.ndarray
            If True, the standard deviation of the S21 data is computed from
            the loop. It can also be specified with a complex number or an
            array of complex numbers. The value is then passed to the model
            residual with the sigma keyword argument. If the model does not
            have this key word, this will not work and sigma should be set to
            False.
        parallel: boolean or integer (optional)
            Compute the fit for each loop in parallel. The default is False,
            and the computation is done in serial. If True, a Pool object is
            created with multiprocessing.cpu_count() // 2 CPUs. If an integer,
            that many CPUs will be used. This method will only work on systems
            that can use os.fork().
        return_dict: boolean (optional)
            Return only the lmfit_results dictionary if True. The default is
            False.
        callback: function (optional)
            A function to be called after every loop fit. The default is None
            and no function call is done.
        fit_kwargs: optional keyword arguments
            Additional keyword arguments to pass to the fitting function. E.g.
            loop.lmfit() or loop.loopfit().
     Returns:
        loops: a list of mkidcalculator.Loop objects or a list of dictionaries
            The loop objects that were fit. If return_dict is True, the
            loop.lmfit_results or loop.loopfit_results dictionaries are
            returned instead.
    """
    loops = _get_loops(data)
    if parallel:
        loops = _parallel(temperature_fit,
                          loops,
                          parallel,
                          fit_type,
                          label=label,
                          sigma=sigma,
                          **fit_kwargs)
        return _prepare_output(loops, fit_type, return_dict=return_dict)
    for loop in loops:
        # find good fits from other loop
        good_guesses, _, _, temperatures = _get_good_fits(loop,
                                                          "temperature",
                                                          fit_type,
                                                          fix=("power",
                                                               "field"))
        # fit the two nearest temperature data sets
        indices = np.argsort(np.abs(loop.temperature - np.array(temperatures)))
        for iteration in range(2):
            if iteration < len(indices):
                # pick guess
                guess = good_guesses[indices[iteration]]
                # do fit
                fit_label = label + "_" + str(iteration)
                result = _do_fit(loop, guess, fit_type, fit_kwargs, sigma,
                                 fit_label)
                log.info(
                    FIT_MESSAGE.format(loop.name, fit_label,
                                       _red_chi(fit_type, result)))
        if callback is not None:
            callback()
    return _prepare_output(loops, fit_type, return_dict=return_dict)
def basic_fit(data,
              fit_type="lmfit",
              label="basic_fit",
              calibration=True,
              sigma=True,
              guess=None,
              guess_kwargs=None,
              parallel=False,
              return_dict=False,
              callback=None,
              **fit_kwargs):
    """
    Fit the loop using the standard model guess.
    Args:
        data: Loop, Resonator, Sweep, or collection of those objects
            The loop or loops to fit. If Resonator or Sweep objects are given
            all of the contained loops are fit.
        fit_type: string
            The type of fit to use. Allowed options are "lmfit" and
            "loopfit". The default is "lmfit".
        label: string (optional)
            The label to store the fit results under. The default is
            "basic_fit".
        calibration: boolean
            Automatically add 'offset' and 'imbalance' parameters to the guess
            keywords. The default is True, but if a model is used that doesn't
            have those keywords, it should be set to False.
        sigma: boolean, complex number, or numpy.ndarray
            If True, the standard deviation of the S21 data is computed from
            the loop. It can also be specified with a complex number or an
            array of complex numbers. The value is then passed to the model
            residual with the sigma keyword argument. If the model does not
            have this key word, this will not work and sigma should be set to
            False.
        guess: object
            The guess object for the 'fit_type'. Defaults to None and the guess
            is computed with 'guess_kwargs'. A different guess can be applied
            to each loop if a list of guesses is provided.
        guess_kwargs: dictionary
            A dictionary of keyword arguments that can overwrite the default
            options for model.guess().
        parallel: boolean or integer (optional)
            Compute the fit for each loop in parallel. The default is False,
            and the computation is done in serial. If True, a Pool object is
            created with multiprocessing.cpu_count() // 2 CPUs. If an integer,
            that many CPUs will be used. This method will only work on systems
            that can use os.fork().
        return_dict: boolean (optional)
            Return only the fit results dictionary if True. The default is
            False.
        callback: function (optional)
            A function to be called after every loop fit. The default is None
            and no function call is done.
        fit_kwargs: optional keyword arguments
            Additional keyword arguments to pass to the fitting function. E.g.
            loop.lmfit() or loop.loopfit().
    Returns:
        loops: a list of mkidcalculator.Loop objects or a list of dictionaries
            The loop objects that were fit. If return_dict is True, the
            loop.lmfit_results or loop.loopfit_results dictionaries are
            returned instead.
    """
    loops = _get_loops(data)
    if parallel:
        loops = _parallel(basic_fit,
                          loops,
                          parallel,
                          fit_type,
                          label=label,
                          calibration=calibration,
                          sigma=sigma,
                          guess=guess,
                          guess_kwargs=guess_kwargs,
                          **fit_kwargs)
        return _prepare_output(loops, fit_type, return_dict=return_dict)
    for i, loop in enumerate(loops):
        # make guess
        if guess is None:
            kwargs = {
                "imbalance": loop.imbalance_calibration,
                "offset": loop.offset_calibration
            } if calibration else {}
            if guess_kwargs is not None:
                kwargs.update(guess_kwargs)
            g = _make_guess(loop, fit_type, kwargs, fit_kwargs)
        else:
            g = guess[i] if isinstance(guess, (tuple, list)) else guess
        # do fit
        result = _do_fit(loop, g, fit_type, fit_kwargs, sigma, label)
        log.info(
            FIT_MESSAGE.format(loop.name, label, _red_chi(fit_type, result)))
        if callback is not None:
            callback()
    return _prepare_output(loops, fit_type, return_dict=return_dict)