def make(*configs: ConConfig, weights: List[float] = None) -> MyRecipe: """ Make a FitRecipe based on single or multiple ConConfig. Parameters ---------- configs The configurations of single or multiple FitContribution. weights The weights for the evaluation of each FitContribution. It should have the same length as the number of ConConfigs. If None, every FitContribution has the same weight 1. Returns ------- recipe MyRecipe built from ConConfigs. """ recipe = MyRecipe(configs=configs) if weights is None: weights = [1.] * len(configs) else: msg = f"models and weights doe not have same length: {len(configs)}, {len(weights)}." assert len(configs) == len(weights), msg for config, weight in zip(configs, weights): contribution = make_contribution(config) recipe.addContribution(contribution, weight) recipe.fithooks[0].verbose = 0 return recipe
def refine(recipe: MyRecipe): recipe.fix("all") recipe.free("scale", "lat") fit(recipe) recipe.free("adp") fit(recipe) recipe.free("delta2") fit(recipe) return
def constrain(recipe: MyRecipe): con = recipe.crystal gen: PDFGenerator = con.Ni recipe.addVar(gen.scale) recipe.addVar(gen.delta2) sgpars = constrainAsSpaceGroup(gen.phase, 225) for par in sgpars.latpars: recipe.addVar(par, tag="lat") for par in sgpars.adppars: recipe.addVar(par, value=0.006, tag="adp") return
def free_and_fit(recipe: MyRecipe, *tags: Union[str, Tuple[str]], **kwargs) -> None: """ First fix all variables and then free the variables one by one and fit the recipe. Parameters ---------- recipe The recipe to fit. tags The tags of variables to free. It can be single tag or a tuple of tags. kwargs The kwargs of the 'fit'. Returns ------- None """ print(f"Start {recipe.name} with all parameters fixed.") recipe.fix('all') for n, tag in enumerate(tags): if isinstance(tag, tuple): print("Free " + ', '.join(tag) + ' ...') recipe.free(*tag) elif isinstance(tag, str): print(f"Free {tag} ...") recipe.free(tag) else: raise TypeError(f"Unknown tag type: {type(tag)}") if n == len(tags) - 1 and '_print' not in kwargs: fit(recipe, _print=True, **kwargs) else: fit(recipe, **kwargs) return
def fit(recipe: MyRecipe, **kwargs) -> None: """ Fit the data according to recipe. parameters associated with fitting can be set in kwargs. Parameters ---------- recipe MyRecipe to fit. kwargs Parameters in fitting. They are verbose: how much information to print. Default 2. values: initial value for fitting. Default get from recipe. bounds: two list of lower and upper bounds. Default get from recipe. xtol, gtol, ftol: tolerance in least squares. Default 1.E-4, 1.E-4, 1.E-4. max_nfev: maximum number of evaluation of residual function. Default None. _print: whether to print the data. Default False. Returns ------- None """ values = kwargs.get("values", recipe.values) bounds = kwargs.get("bounds", recipe.getBounds2()) verbose = kwargs.get("verbose", 2) xtol = kwargs.get("xtol", 1.E-4) gtol = kwargs.get("gtol", 1.E-4) ftol = kwargs.get("ftol", 1.E-4) max_nfev = kwargs.get("max_fev", None) least_squares(recipe.residual, values, bounds=bounds, verbose=verbose, xtol=xtol, gtol=gtol, ftol=ftol, max_nfev=max_nfev) _print = kwargs.get("_print", False) if _print: df, res = _make_df(recipe) print("-" * 90) print(f"Results of {recipe.name}") print("-" * 90) print(df.to_string()) print("-" * 90) else: pass return
def sgconstrain(recipe: MyRecipe, gen_name: str, con_name: str = None, sg: Union[int, str] = None, dv: Dict[str, float] = None, scatterers: List = None, constrainxyz=False) -> None: """ Constrain the generator by space group. The constrained parameters are scale, delta2, lattice parameters, ADPs and xyz coordinates. The lattice constants and xyz coordinates are constrained by space group while the ADPs are constrained by elements. All paramters will be added as '{par.name}_{gen.name}' The default values, ranges and tags for parameters: scale: 0, (0, inf), scale_{gen.name} delta2: 0, (0, 5), delta2_{gen.name} lat: par.value, (par.value +/- 10%), lat_{gen.name} adp: 0.006, (0.001, 0.02), adp_{gen.name} xyz: par.value, (par.value +/- 0.1), xyz_{gen.name} Parameters ---------- recipe The recipe to add variables. gen_name The name of the PDFGenerator to constrain. con_name The name of the FitContribution where the PDFGenerator is in. If None, get it according to the name of the first ConConfig in 'recipe.configs'. Default None. sg The space group. The expression can be the string or integer. If None, use the space group in GenConfig. Default None. dv The default value of the constrained parameters. If None, the default values will be used. Default None. scatterers The argument scatters of the constrainAsSpaceGroup. If None, None will be used. Default None. constrainxyz Whether to constrain xyz coordinates. Returns ------- None """ def get_config(): if con_name is None: return recipe.configs[0] for config in recipe.configs: if config.name == con_name: return config else: raise ValueError( f"No ConConfig in recipe '{recipe.name}' match the '{con_name}'." ) def get_sg(): config = get_config() for genconfig in config.phases: if genconfig.name == gen_name: return genconfig.sg else: raise ValueError( f"No GenConfig in the FitContribution '{config.name}' match the '{gen_name}'." ) # get sg if sg is None: sg = get_sg() # set default of variables dv = dv if dv else {} con = getattr(recipe, con_name) if con_name else getattr( recipe, recipe.configs[0].name) gen = getattr(con, gen_name) # add scale name = f'scale_{gen.name}' recipe.addVar(gen.scale, name=name, value=dv.get(name, 0.)).boundRange(0., np.inf) # add delta2 name = f'delta2_{gen.name}' recipe.addVar(gen.delta2, name=name, value=dv.get(name, 0.)).boundRange(0., 5.) # constrain by spacegroup sgpars = constrainAsSpaceGroup(gen.phase, sg, constrainadps=False, scatterers=scatterers) print( f"Constrain '{gen.name}' by space group '{sg}' without constraining ADPs." ) # add latpars for par in sgpars.latpars: name = f'{par.name}_{gen.name}' tag = f'lat_{gen.name}' recipe.addVar(par, name=name, value=dv.get(name, par.value), tag=tag).boundWindow(par.value * 0.1) # constrain adps atoms = gen.phase.getScatterers() elements = Counter([atom.element for atom in atoms]).keys() adp = { element: recipe.newVar(f'Uiso_{element}_{gen.name}', value=dv.get(f'Uiso_{element}_{gen.name}', 0.006), tag=f'adp_{gen.name}').boundRange(0.001, 0.02) for element in elements } for atom in atoms: recipe.constrain(atom.Uiso, adp[atom.element]) # add xyzpars if constrainxyz: for par in sgpars.xyzpars: name = f'{par.name}_{gen.name}' tag = f'xyz_{gen.name}' recipe.addVar(par, name=name, value=dv.get(name, par.value), tag=tag).boundWindow(0.1) return
def sgconstrain(recipe: MyRecipe, gen: Union[PDFGenerator, DebyePDFGenerator], sg: Union[int, str], dv: Dict[str, float] = None, scatterers: List = None) -> None: """ Constrain the generator by space group. The constrained parameters are scale, delta2, lattice parameters, ADPs and xyz coordinates. The lattice constants and xyz coordinates are constrained by space group while the ADPs are constrained by elements. All paramters will be added as '{par.name}_{gen.name}' The default values, ranges and tags for parameters: scale: 0, (0, inf), scale_{gen.name} delta2: 0, (0, inf), delta2_{gen.name} lat: par.value, (par.value +/- 20%), lat_{gen.name} adp: 0.006, (0, inf), adp_{gen.name} xyz: par.value, (par.value +/- 0.2), xyz_{gen.name} Parameters ---------- recipe The recipe to add variables. gen The generator to constrain. sg The space group. The expression can be the string or integer. dv The default value of the constrained parameters. scatterers The argument scatters of the constrainAsSpaceGroup. Returns ------- None """ dv = dv if dv else {} # add scale name = f'scale_{gen.name}' recipe.addVar(gen.scale, name=name, value=dv.get(name, 0.)).boundRange(0., np.inf) # add delta2 name = f'delta2_{gen.name}' recipe.addVar(gen.delta2, name=name, value=dv.get(name, 0.)).boundRange(0., np.inf) # constrain lat sgpars = constrainAsSpaceGroup(gen.phase, sg, constrainadps=False, scatterers=scatterers) for par in sgpars.latpars: name = f'{par.name}_{gen.name}' tag = f'lat_{gen.name}' recipe.addVar(par, name=name, value=dv.get(name, par.value), tag=tag).boundWindow(par.value * 0.2) # constrain adp atoms = gen.phase.getScatterers() elements = Counter([atom.element for atom in atoms]).keys() adp = { element: recipe.newVar(f'Uiso_{element}_{gen.name}', value=dv.get(f'Uiso_{element}_{gen.name}', 0.006), tag=f'adp_{gen.name}').boundRange(0., np.inf) for element in elements } for atom in atoms: recipe.constrain(atom.Uiso, adp[atom.element]) # constrain xyz for par in sgpars.xyzpars: name = f'{par.name}_{gen.name}' tag = f'xyz_{gen.name}' recipe.addVar(par, name=name, value=dv.get(name, par.value), tag=tag).boundWindow(0.2) return