crystalpdfobjcryst.py

#!/usr/bin/env python
########################################################################
#
# diffpy.srfit      by DANSE Diffraction group
#                   Simon J. L. Billinge
#                   (c) 2009 The Trustees of Columbia University
#                   in the City of New York.  All rights reserved.
#
# File coded by:    Chris Farrow
#
# See AUTHORS.txt for a list of people who contributed.
# See LICENSE_DANSE.txt for license information.
#
########################################################################
"""Example of a PDF refinement using pyobjcryst and PDFGenerator.

This example is similar to crystalpdf.py, except that here we refine a
pyobjcryst crystal object. In this example we use internal constraints
provided by the ObjCrystCrystalParSet structure adapter.
"""

from pathlib import Path

from gaussianrecipe import scipyOptimize
from pyobjcryst.crystal import create_crystal_from_cif

from diffpy.srfit.fitbase import (
    FitContribution,
    FitRecipe,
    FitResults,
    Profile,
)
from diffpy.srfit.pdf import PDFGenerator, PDFParser

######
#  Example Code


def makeRecipe(ciffile, datname):
    """Create a fitting recipe for crystalline PDF data."""
    # The Profile
    # This will be used to store the observed and calculated PDF profile.
    profile = Profile()

    # Load data and add it to the Profile. As before we use a PDFParser.
    # The metadata is still passed to the PDFGenerator later on.
    # The interaction between the PDFGenerator and the metadata does not
    # depend on type of structure being refined.
    parser = PDFParser()
    parser.parse_file(datname)
    profile.load_parsed_data(parser)
    profile.set_calculation_range(xmax=20)

    # The ProfileGenerator
    # This time we use the CreateCrystalFromCIF method of pyobjcryst.crystal to
    # create a Crystal object. That object is passed to the PDFGenerator as in
    # the previous example.
    generator = PDFGenerator("G")
    stru = create_crystal_from_cif(str(ciffile))
    generator.setStructure(stru)
    generator.setQmax(40.0)

    # The FitContribution
    contribution = FitContribution("nickel")
    contribution.add_profile_generator(generator)
    contribution.set_profile(profile, xname="r")

    # Make the FitRecipe and add the FitContribution.
    recipe = FitRecipe()
    recipe.add_contribution(contribution)

    # Configure the fit variables

    # As before, we get a handle to the structure parameter set. In this case,
    # it is a ObjCrystCrystalParSet instance that was created when we called
    # 'setStructure' above. The ObjCrystCrystalParSet has different Parameters
    # and options than the DiffpyStructureParSet used in the last example. See
    # its documentation in diffpy.srfit.structure.objcrystparset.
    phase = generator.phase

    # Here is where we created space group constraints in the previous example.
    # The difference in this example is that the ObjCrystCrystalParSet is aware
    # of space groups, and the DiffpyStructureParSet is not. Constraints are
    # created internally when "sgpars" attribute is called for. These
    # constraints get enforced within the ObjCrystCrystalParSet. Free
    # Parameters are stored within the 'sgpars' member of the
    # ObjCrystCrystalParSet, which is the same as the object returned from
    # 'constrain_as_space_group'.
    #
    # As before, we have one free lattice parameter ('a'). We can simplify
    # things by iterating through all the sgpars.
    for par in phase.sgpars:
        recipe.add_variable(par)

    # We now select non-structural parameters to refine.
    # This controls the scaling of the PDF.
    recipe.add_variable(generator.scale, 1)
    # This is a peak-damping resolution term.
    recipe.add_variable(generator.qdamp, 0.01)
    # This is a vibrational correlation term that sharpens peaks at low-r.
    recipe.add_variable(generator.delta2, 5)

    # Give the recipe away so it can be used!
    return recipe


plot_styles = {
    "xlabel": r"$r (\AA)$",
    "ylabel": r"$G (\AA^{-2})$",
}


if __name__ == "__main__":

    # Make the data and the recipe
    ciffile = Path(__file__).parent / "data/si.cif"
    data = Path(__file__).parent / "data/si-q27r60-xray.gr"

    # Make the recipe
    recipe = makeRecipe(ciffile, data)

    # Optimize
    scipyOptimize(recipe)

    # Generate and print the FitResults
    res = FitResults(recipe)
    res.print_results()

    # Plot!
    recipe.plot_recipe(**plot_styles)

# End of file