def fit(self, fitter, data_arrays: list, *args, dask: str = 'forbidden', fit_kwargs: dict = None, fn_kwargs: dict = None, vectorized: bool = False, **kwargs) -> List[FitResults]: """ Perform a fit on one or more DataArrays. This fit utilises a given fitter from `easyCore.Fitting.Fitter`, though there are a few differences to a standard easyCore fit. In particular, key-word arguments to control the optimisation algorithm go in the `fit_kwargs` dictionary, fit function key-word arguments go in the `fn_kwargs` and given key-word arguments control the `xarray.apply_ufunc` function. :param fitter: Fitting object which controls the fitting :type fitter: easyCore.Fitting.Fitter :param args: Arguments to go to the fit function :type args: Any :param dask: Dask control string. See `xarray.apply_ufunc` documentation :type dask: str :param fit_kwargs: Dictionary of key-word arguments to be supplied to the Fitting control :type fit_kwargs: dict :param fn_kwargs: Dictionary of key-words to be supplied to the fit function :type fn_kwargs: dict :param vectorized: Should the fit function be given dependents in a single object or split :type vectorized: bool :param kwargs: Key-word arguments for `xarray.apply_ufunc`. See `xarray.apply_ufunc` documentation :type kwargs: Any :return: Results of the fit :rtype: List[FitResults] """ if fn_kwargs is None: fn_kwargs = {} if fit_kwargs is None: fit_kwargs = {} if not isinstance(data_arrays, (list, tuple)): data_arrays = [data_arrays] # In this case we are only fitting 1 dataset if len(data_arrays) == 1: variable_label = data_arrays[0] dataset = self._obj[variable_label] if self.__error_mapper.get(variable_label, False): # Pull out any sigmas and send them to the fitter. temp = self._obj[self.__error_mapper[variable_label]] temp[xr.ufuncs.isnan(temp)] = 1e5 fit_kwargs['weights'] = temp # Perform a standard DataArray fit. return dataset.easyCore.fit(fitter, *args, fit_kwargs=fit_kwargs, fn_kwargs=fn_kwargs, dask=dask, vectorize=vectorized, **kwargs) else: # In this case we are fitting multiple datasets to the same fn! bdim_f = [ self._obj[p].easyCore.fit_prep(fitter.fit_function) for p in data_arrays ] dim_names = [ list(self._obj[p].dims.keys()) if isinstance( self._obj[p].dims, dict) else self._obj[p].dims for p in data_arrays ] bdims = [bdim[0] for bdim in bdim_f] fs = [bdim[1] for bdim in bdim_f] old_fit_func = fitter.fit_function fn_array = [] y_list = [] for _idx, d in enumerate(bdims): dims = self._obj[data_arrays[_idx]].dims if isinstance(dims, dict): dims = list(dims.keys()) def local_fit_func(x, *args, idx=None, **kwargs): kwargs['vectorize'] = vectorized res = xr.apply_ufunc(fs[idx], *bdims[idx], *args, dask=dask, kwargs=fn_kwargs, **kwargs) if dask != 'forbidden': res.compute() return res.stack(all_x=dim_names[idx]) y_list.append(self._obj[data_arrays[_idx]].stack(all_x=dims)) fn_array.append(local_fit_func) def fit_func(x, *args, **kwargs): res = [] for idx in range(len(fn_array)): res.append(fn_array[idx](x, *args, idx=idx, **kwargs)) return xr.DataArray(np.concatenate(res, axis=0), coords={'all_x': x}, dims='all_x') fitter.initialize(fitter.fit_object, fit_func) try: if fit_kwargs.get('weights', None) is not None: del fit_kwargs['weights'] x = xr.DataArray(np.arange(np.sum([y.size for y in y_list])), dims='all_x') y = xr.DataArray(np.concatenate(y_list, axis=0), coords={'all_x': x}, dims='all_x') f_res = fitter.fit(x, y, **fit_kwargs) f_res = check_sanity_multiple( f_res, [self._obj[p] for p in data_arrays]) finally: fitter.fit_function = old_fit_func return f_res
def do_calc_setup(self, scale, this_x_array): if len(self.pattern.backgrounds) == 0: bg = np.zeros_like(this_x_array) else: bg = self.pattern.backgrounds[0].calculate(this_x_array) num_crys = len(self.current_crystal.keys()) if num_crys == 0: return bg crystals = [self.storage[key] for key in self.current_crystal.keys()] phase_scales = [ self.storage[str(key) + "_scale"] for key in self.current_crystal.keys() ] phase_lists = [] profiles = [] peak_dat = [] for crystal in crystals: phasesL = cryspy.PhaseL() idx = [ idx for idx, item in enumerate(self.phases.items) if item.label == crystal.data_name ][0] phasesL.items.append(self.phases.items[idx]) phase_lists.append(phasesL) profile, peak = _do_run(self.model, self.polarized, this_x_array, crystal, phasesL) profiles.append(profile) peak_dat.append(peak) # pool = mp.ProcessPool(num_crys) # print("\n\nPOOL = " + str(pool)) # result = pool.amap(functools.partial(_do_run, self.model, self.polarized, this_x_array), crystals, # phase_lists) # while not result.ready(): # time.sleep(0.01) # obtained = result.get() # profiles, peak_dat = zip(*obtained) # else: # raise ArithmeticError # Do this for now x_str = "ttheta" if self.type == "powder1DTOF": x_str = "time" if self.polarized: # TODO *REPLACE PLACEHOLDER FN* dependents, additional_data = self.polarized_update( lambda up, down: up + down, crystals, profiles, peak_dat, phase_scales, x_str, ) else: dependents, additional_data = self.nonPolarized_update( crystals, profiles, peak_dat, phase_scales, x_str) self.additional_data["phases"].update(additional_data) self.additional_data["global_scale"] = scale self.additional_data["background"] = bg self.additional_data["ivar_run"] = this_x_array self.additional_data["phase_names"] = list(additional_data.keys()) self.additional_data["type"] = self.type # just the sum of all phases dependent_output = scale * np.sum(dependents, axis=0) + bg scaled_dependents = [scale * dep for dep in dependents] self.additional_data["components"] = scaled_dependents self.additional_data["components"] = scaled_dependents if borg.debug: print(f"y_calc: {dependent_output}") return (np.sum( [s["profile"] for s in self.additional_data["phases"].values()], axis=0) + self.additional_data["background"])
def get_total_y_for_phases(self) -> Tuple[np.ndarray, np.ndarray]: x_values = self.additional_data["ivar_run"] y_values = (np.sum( [s["profile"] for s in self.additional_data["phases"].values()], axis=0) + self.additional_data["background"]) return x_values, y_values
def calculate(self, x_array: np.ndarray) -> np.ndarray: """ For a given x calculate the corresponding y :param x_array: array of data points to be calculated :type x_array: np.ndarray :return: points calculated at `x` :rtype: np.ndarray """ if self.filename is None: raise AttributeError if self.pattern is None: scale = 1.0 offset = 0 else: scale = self.pattern.scale.raw_value offset = self.pattern.zero_shift.raw_value this_x_array = x_array + offset # Experiment/Instrument/Simulation parameters x_min = this_x_array[0] x_max = this_x_array[-1] num_points = np.prod(x_array.shape) x_step = (x_max - x_min) / (num_points - 1) if len(self.pattern.backgrounds) == 0: bg = np.zeros_like(this_x_array) else: bg = self.pattern.backgrounds[0].calculate(this_x_array) dependents = [] # Sample parameters # We assume that the phases items has the same indexing as the knownphases item cifs = self.grab_cifs() if len(cifs) == 0: raise ValueError("No phases found for calculation") for idx, file in enumerate(cifs): cif_file = CFML_api.CIFFile(file) cell = cif_file.cell space_group = cif_file.space_group atom_list = cif_file.atom_list job_info = cif_file.job_info job_info.range_2theta = (x_min, x_max) job_info.theta_step = x_step job_info.u_resolution = self.conditions["u_resolution"] job_info.v_resolution = self.conditions["v_resolution"] job_info.w_resolution = self.conditions["w_resolution"] job_info.x_resolution = self.conditions["x_resolution"] job_info.y_resolution = self.conditions["y_resolution"] job_info.lambdas = (self.conditions["lamb"], self.conditions["lamb"]) job_info.bkg = 0.0 # Calculations try: reflection_list = CFML_api.ReflectionList( cell, space_group, True, job_info) reflection_list.compute_structure_factors( space_group, atom_list, job_info) diffraction_pattern = CFML_api.DiffractionPattern( job_info, reflection_list, cell.reciprocal_cell_vol) except Exception as e: for cif in cifs: os.remove(cif) raise ArithmeticError item = list(self.known_phases.items())[idx] key = list(self.known_phases.keys())[idx] phase_scale = self.getPhaseScale(key) dependent, additional_data = self.nonPolarized_update( item, diffraction_pattern, reflection_list, job_info, scales=phase_scale) dependents.append(dependent) self.additional_data["phases"].update(additional_data) for cif in cifs: os.remove(cif) self.additional_data["global_scale"] = scale self.additional_data["background"] = bg self.additional_data["ivar_run"] = this_x_array self.additional_data["ivar"] = x_array self.additional_data["components"] = [ scale * dep + bg for dep in dependents ] self.additional_data["phase_names"] = list(self.known_phases.items()) self.additional_data["type"] = "powder1DCW" dependent_output = scale * np.sum(dependents, axis=0) + bg if borg.debug: print(f"y_calc: {dependent_output}") return (np.sum( [s["profile"] for s in self.additional_data["phases"].values()], axis=0) + self.additional_data["background"])