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)