Exemplo n.º 1
0
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
Exemplo n.º 2
0
def refine(recipe: MyRecipe):
    recipe.fix("all")
    recipe.free("scale", "lat")
    fit(recipe)
    recipe.free("adp")
    fit(recipe)
    recipe.free("delta2")
    fit(recipe)
    return
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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
Exemplo n.º 5
0
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
Exemplo n.º 6
0
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
Exemplo n.º 7
0
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