def free_one_by_one( recipe: MyRecipe, tags: tp.List[tp.Union[str, tp.Iterable[str]]]) -> tp.Generator: """Free the tags one by one.""" for n, tag in enumerate(tags): tag_lst = [tag] if isinstance(tag, str) else tag recipe.free(*tag_lst) yield tag_lst
def add_scale(recipe: MyRecipe, gen: G, scale: bool = True) -> None: """Add the scale of the generator.""" if not scale: return recipe.addVar(gen.scale, value=0., name="{}_scale".format(gen.name), tags=["{}_scale".format(gen.name), "scale", gen.name]).boundRange(lb=0.) return
def add_lat(recipe: MyRecipe, gen: G, lat: tp.Union[str, None]) -> None: """Add the lattice parameters of the phase.""" if not lat: return if lat == "s": pars = gen.phase.sgpars.latpars elif lat == "a": pars = gen.phase.getLattice() else: raise ValueError("Unknown lat: {}. Allowed: sg, all.".format(lat)) for par in pars: recipe.addVar(par, name="{}_{}".format(gen.name, par.name), tags=["lat", gen.name, "{}_lat".format(gen.name)]).boundRange(lb=0.) return
def add_delta(recipe: MyRecipe, gen: G, delta: tp.Union[str, None]) -> None: """Add the delta parameter of the generator.""" if not delta: return if delta == "1": par = gen.delta1 elif delta == "2": par = gen.delta2 else: raise ValueError( "Unknown delta: {}. Allowed: delta1, delta2.".format(delta)) recipe.addVar(par, value=0., name="{}_{}".format(gen.name, par.name), tags=["{}_delta".format(gen.name), "delta", gen.name]).boundRange(lb=0.) return
def add_params(recipe: MyRecipe, con: MyContribution, params: tp.Union[None, str, tp.List[str]]) -> None: """Add contribution-level parameters in the contribution.""" if not params: return args = { arg.name: arg for eq in con.eqfactory.equations if eq.name == "eq" for arg in eq.args if arg.name != con.xname } if params == "a": pars = args.values() else: pars = [args[p] for p in params] for par in pars: recipe.addVar(par, tag=par.name.split("_")[0]) return
def add_adp( recipe: MyRecipe, gen: G, adp: tp.Union[str, None], symbols: tp.Tuple[str] = ("Biso", "B11", "B22", "B33") ) -> None: """Add the atomic displacement parameter of the phase.""" if not adp: return atoms = gen.phase.getScatterers() if adp == "e": elements = set((atom.element for atom in atoms)) dct = dict() for element in elements: dct[element] = recipe.newVar( "{}_{}_Biso".format(gen.name, bleach(element)), value=0.05, tags=["adp", gen.name, "{}_adp".format(gen.name)]) for atom in atoms: recipe.constrain(atom.Biso, dct[atom.element]) return if adp == "a": pars = [atom.Biso for atom in atoms] names = ["{}_Biso".format(bleach(atom.name)) for atom in atoms] elif adp == "s": pars = gen.phase.sgpars.adppars names = [ rename_by_atom(par.name, atoms) for par in pars if par.name.split("_")[0] in symbols ] else: raise ValueError( "Unknown adp: {}. Allowed: element, sg, all.".format(adp)) for par, name in zip(pars, names): recipe.addVar(par, name="{}_{}".format(gen.name, name), value=par.value if par.value != 0. else 0.05, tags=["adp", gen.name, "{}_adp".format(gen.name)]).boundRange(lb=0.) return
def optimize(recipe: MyRecipe, tags: tp.List[tp.Union[str, tp.Iterable[str]]], validate: bool = True, verbose: int = 0, **kwargs): """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. validate Whether to validate the existences of the tags and variable names before the optimization. verbose How verbose the fit should be. kwargs The kwargs of the 'fit'. """ if verbose > 0: print(f"Start {recipe.name} with all parameters fixed.") if validate: recipe.fix("all") list(free_one_by_one(recipe, tags)) n = len(tags) count = 1 recipe.fix("all") for tag_lst in free_one_by_one(recipe, tags): if verbose > 0: print("Round {} / {}: Free {} ...".format(count, n, ", ".join(tag_lst))) fit(recipe, verbose=verbose, **kwargs) count += 1 return
def add_xyz(recipe: MyRecipe, gen: G, xyz: tp.Union[str, None]) -> None: """Add the coordinates of the atoms in the phase.""" if not xyz: return atoms = gen.phase.getScatterers() if xyz == "s": pars = gen.phase.sgpars.xyzpars names = [rename_by_atom(par.name, atoms) for par in pars] elif xyz == "a": pars, names = list(), list() for atom in atoms: pars.append(atom.x) names.append("{}_x".format(atom.name)) pars.append(atom.y) names.append("{}_y".format(atom.name)) pars.append(atom.z) names.append("{}_z".format(atom.name)) else: raise ValueError("Unknown xyz: {}. Allowed: s, a.".format(xyz)) for par, name in zip(pars, names): recipe.addVar(par, name="{}_{}".format(gen.name, name), tags=["xyz", gen.name, "{}_xyz".format(gen.name)]) return
def create(name: str, data: MyParser, arange: tp.Tuple[float, float, float], equation: str, functions: tp.Dict[str, tp.Callable], structures: tp.Dict[str, S], ncpu: int = None) -> MyRecipe: """Create a single-contribution recipe without any variables inside. Parameters ---------- name : The name of the contribution. data : The parser that contains the information of the arange : The rmin, rmax, rstep (inclusive). equation : The equation of the contribution. functions : The keys are function names in the equation and the values are function objects. structures : The keys are structure name in the equation and the values are structure object. ncpu : The number of cpu used in parallel computing. If None, no parallel. Default None. Returns ------- recipe : A single-contribution recipe without any variables inside. """ con = create_con(name, data, arange, equation, functions, structures, ncpu) recipe = MyRecipe() recipe.addContribution(con) recipe.clearFitHooks() return recipe
def get_value_dct(recipe: MyRecipe) -> tp.Dict[str, float]: """Get the values in the recipe in a dictionary.""" return dict(zip(recipe.getNames(), recipe.getValues()))
def get_bound_dct(recipe: MyRecipe) -> tp.Dict[str, tp.List[float]]: """Get the bounds in the recipe in a dictionary.""" return dict(zip(recipe.getNames(), recipe.getBounds()))