示例#1
0
    def testParser1(self):
        data = datafile("ni-q27r100-neutron.gr")
        parser = PDFParser()
        parser.parseFile(data)

        meta = parser._meta

        self.assertEqual(data, meta['filename'])
        self.assertEqual(1, meta['nbanks'])
        self.assertEqual('N', meta['stype'])
        self.assertEqual(27, meta['qmax'])
        self.assertEquals(300, meta.get('temperature'))
        self.assertEquals(None, meta.get('qdamp'))
        self.assertEquals(None, meta.get('qbroad'))
        self.assertEquals(None, meta.get('spdiameter'))
        self.assertEquals(None, meta.get('scale'))
        self.assertEquals(None, meta.get('doping'))

        x, y, dx, dy = parser.getData()
        self.assertTrue(dx is None)
        self.assertTrue(dy is None)

        testx = numpy.linspace(0.01, 100, 10000)
        diff = testx - x
        res = numpy.dot(diff, diff)
        self.assertAlmostEqual(0, res)

        testy = numpy.array([1.144, 2.258, 3.312, 4.279, 5.135, 5.862, 6.445,
            6.875, 7.150, 7.272])
        diff = testy - y[:10]
        res = numpy.dot(diff, diff)
        self.assertAlmostEqual(0, res)

        return
示例#2
0
    def testParser1(self):
        data = datafile("ni-q27r100-neutron.gr")
        parser = PDFParser()
        parser.parseFile(data)

        meta = parser._meta

        self.assertEqual(data, meta['filename'])
        self.assertEqual(1, meta['nbanks'])
        self.assertEqual('N', meta['stype'])
        self.assertEqual(27, meta['qmax'])
        self.assertEquals(300, meta.get('temperature'))
        self.assertEquals(None, meta.get('qdamp'))
        self.assertEquals(None, meta.get('qbroad'))
        self.assertEquals(None, meta.get('spdiameter'))
        self.assertEquals(None, meta.get('scale'))
        self.assertEquals(None, meta.get('doping'))

        x, y, dx, dy = parser.getData()
        self.assertTrue(dx is None)
        self.assertTrue(dy is None)

        testx = numpy.linspace(0.01, 100, 10000)
        diff = testx - x
        res = numpy.dot(diff, diff)
        self.assertAlmostEqual(0, res)

        testy = numpy.array([1.144, 2.258, 3.312, 4.279, 5.135, 5.862, 6.445,
            6.875, 7.150, 7.272])
        diff = testy - y[:10]
        res = numpy.dot(diff, diff)
        self.assertAlmostEqual(0, res)

        return
示例#3
0
def makeProfile(datafile):
    """Make an place data within a Profile."""
    profile = Profile()
    parser = PDFParser()
    parser.parseFile(datafile)
    profile.loadParsedData(parser)
    profile.setCalculationRange(xmax = 20)
    return profile
示例#4
0
def makeProfile(datafile):
    """Make an place data within a Profile."""
    profile = Profile()
    parser = PDFParser()
    parser.parseFile(datafile)
    profile.loadParsedData(parser)
    profile.setCalculationRange(xmax=20)
    return profile
示例#5
0
def make_profile(data_file: str, fit_range: Tuple[float, float,
                                                  float]) -> Profile:
    """
    build profile for contribution.
    :param data_file: name of the data file.
    :param fit_range: fit range: (rmin, rmax, rstep).
    :return:
        profile: profile object.
    """
    profile = Profile()
    parser = PDFParser()
    parser.parseFile(data_file)
    profile.loadParsedData(parser)

    rmin, rmax, rstep = fit_range
    profile.setCalculationRange(rmin, rmax, rstep)

    return profile
示例#6
0
    def testParser2(self):
        data = datafile("si-q27r60-xray.gr")
        parser = PDFParser()
        parser.parseFile(data)

        meta = parser._meta

        self.assertEqual(data, meta['filename'])
        self.assertEqual(1, meta['nbanks'])
        self.assertEqual('X', meta['stype'])
        self.assertEqual(27, meta['qmax'])
        self.assertEquals(300, meta.get('temperature'))
        self.assertEquals(None, meta.get('qdamp'))
        self.assertEquals(None, meta.get('qbroad'))
        self.assertEquals(None, meta.get('spdiameter'))
        self.assertEquals(None, meta.get('scale'))
        self.assertEquals(None, meta.get('doping'))

        x, y, dx, dy = parser.getData()
        testx = numpy.linspace(0.01, 60, 5999, endpoint=False)
        diff = testx - x
        res = numpy.dot(diff, diff)
        self.assertAlmostEqual(0, res)

        testy = numpy.array([
            0.1105784, 0.2199684, 0.3270088, 0.4305913, 0.5296853, 0.6233606,
            0.7108060, 0.7913456, 0.8644501, 0.9297440
        ])
        diff = testy - y[:10]
        res = numpy.dot(diff, diff)
        self.assertAlmostEqual(0, res)

        testdy = numpy.array([
            0.001802192, 0.003521449, 0.005079115, 0.006404892, 0.007440527,
            0.008142955, 0.008486813, 0.008466340, 0.008096858, 0.007416456
        ])
        diff = testdy - dy[:10]
        res = numpy.dot(diff, diff)
        self.assertAlmostEqual(0, res)

        self.assertTrue(dx is None)
        return
示例#7
0
def makeRecipe(ciffile, grdata):
    """Make a recipe to model a crystal-like nanoparticle PDF."""

    # Set up a PDF fit as has been done in other examples.
    pdfprofile = Profile()

    pdfparser = PDFParser()
    pdfparser.parseFile(grdata)
    pdfprofile.loadParsedData(pdfparser)
    pdfprofile.setCalculationRange(xmin = 0.1, xmax = 20)

    pdfcontribution = FitContribution("pdf")
    pdfcontribution.setProfile(pdfprofile, xname = "r")

    pdfgenerator = PDFGenerator("G")
    pdfgenerator.setQmax(30.0)
    stru = CreateCrystalFromCIF(file(ciffile))
    pdfgenerator.setStructure(stru)
    pdfcontribution.addProfileGenerator(pdfgenerator)

    # Register the nanoparticle shape factor.
    from diffpy.srfit.pdf.characteristicfunctions import sphericalCF
    pdfcontribution.registerFunction(sphericalCF, name = "f")

    # Now we set up the fitting equation.
    pdfcontribution.setEquation("f * G")

    # Now make the recipe. Make sure we fit the characteristic function shape
    # parameters, in this case 'psize', which is the diameter of the particle.
    recipe = FitRecipe()
    recipe.addContribution(pdfcontribution)

    phase = pdfgenerator.phase
    for par in phase.sgpars:
        recipe.addVar(par)

    recipe.addVar(pdfcontribution.psize, 20)
    recipe.addVar(pdfgenerator.scale, 1)
    recipe.addVar(pdfgenerator.delta2, 0)
    recipe.B11_0 = 0.1

    return recipe
示例#8
0
def makeRecipe(ciffile, grdata):
    """Make a recipe to model a crystal-like nanoparticle PDF."""

    # Set up a PDF fit as has been done in other examples.
    pdfprofile = Profile()

    pdfparser = PDFParser()
    pdfparser.parseFile(grdata)
    pdfprofile.loadParsedData(pdfparser)
    pdfprofile.setCalculationRange(xmin=0.1, xmax=20)

    pdfcontribution = FitContribution("pdf")
    pdfcontribution.setProfile(pdfprofile, xname="r")

    pdfgenerator = PDFGenerator("G")
    pdfgenerator.setQmax(30.0)
    stru = CreateCrystalFromCIF(file(ciffile))
    pdfgenerator.setStructure(stru)
    pdfcontribution.addProfileGenerator(pdfgenerator)

    # Register the nanoparticle shape factor.
    from diffpy.srfit.pdf.characteristicfunctions import sphericalCF
    pdfcontribution.registerFunction(sphericalCF, name="f")

    # Now we set up the fitting equation.
    pdfcontribution.setEquation("f * G")

    # Now make the recipe. Make sure we fit the characteristic function shape
    # parameters, in this case 'psize', which is the diameter of the particle.
    recipe = FitRecipe()
    recipe.addContribution(pdfcontribution)

    phase = pdfgenerator.phase
    for par in phase.sgpars:
        recipe.addVar(par)

    recipe.addVar(pdfcontribution.psize, 20)
    recipe.addVar(pdfgenerator.scale, 1)
    recipe.addVar(pdfgenerator.delta2, 0)
    recipe.B11_0 = 0.1

    return recipe
示例#9
0
def make_profile(data_file: str, fit_range: Tuple[float, float,
                                                  float]) -> Profile:
    """
    Make a Profile, parse data file to it and set its calculation range.

    Parameters
    ----------
    data_file
        The path to the data file.
    fit_range
        The tuple of (rmax, rmin, dr) in Angstrom.
    """
    profile = Profile()
    parser = PDFParser()
    parser.parseFile(data_file)
    profile.loadParsedData(parser)

    rmin, rmax, rstep = fit_range
    profile.setCalculationRange(rmin, rmax, rstep)

    return profile
示例#10
0
    def testParser2(self):
        data = datafile("si-q27r60-xray.gr")
        parser = PDFParser()
        parser.parseFile(data)

        meta = parser._meta

        self.assertEqual(data, meta['filename'])
        self.assertEqual(1, meta['nbanks'])
        self.assertEqual('X', meta['stype'])
        self.assertEqual(27, meta['qmax'])
        self.assertEquals(300, meta.get('temperature'))
        self.assertEquals(None, meta.get('qdamp'))
        self.assertEquals(None, meta.get('qbroad'))
        self.assertEquals(None, meta.get('spdiameter'))
        self.assertEquals(None, meta.get('scale'))
        self.assertEquals(None, meta.get('doping'))

        x, y, dx, dy = parser.getData()
        testx = numpy.linspace(0.01, 60, 5999, endpoint=False)
        diff = testx - x
        res = numpy.dot(diff, diff)
        self.assertAlmostEqual(0, res)

        testy = numpy.array([0.1105784, 0.2199684, 0.3270088, 0.4305913,
            0.5296853, 0.6233606, 0.7108060, 0.7913456, 0.8644501, 0.9297440])
        diff = testy - y[:10]
        res = numpy.dot(diff, diff)
        self.assertAlmostEqual(0, res)

        testdy = numpy.array([0.001802192, 0.003521449, 0.005079115,
            0.006404892, 0.007440527, 0.008142955, 0.008486813, 0.008466340,
            0.008096858, 0.007416456])
        diff = testdy - dy[:10]
        res = numpy.dot(diff, diff)
        self.assertAlmostEqual(0, res)

        self.assertTrue(dx is None)
        return
示例#11
0
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.parseFile(datname)
    profile.loadParsedData(parser)
    profile.setCalculationRange(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 = CreateCrystalFromCIF(file(ciffile))
    generator.setStructure(stru)
    generator.setQmax(40.0)

    ## The FitContribution
    contribution = FitContribution("nickel")
    contribution.addProfileGenerator(generator)
    contribution.setProfile(profile, xname="r")

    # Make the FitRecipe and add the FitContribution.
    recipe = FitRecipe()
    recipe.addContribution(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
    # 'constrainAsSpaceGroup'.
    #
    # 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.addVar(par)
    # set the initial thermal factor to a non-zero value
    assert hasattr(recipe, 'B11_0')
    recipe.B11_0 = 0.1

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

    # Give the recipe away so it can be used!
    return recipe
示例#12
0
def makeRecipe(stru1, stru2, stru3, datname):
    """Create a fitting recipe for crystalline PDF data."""

    ## The Profile
    profile = Profile()

    # Load data and add it to the profile
    parser = PDFParser()
    parser.parseFile(datname)
    profile.loadParsedData(parser)
    profile.setCalculationRange(xmin=1, xmax = 40, dx = 0.01)

    ## The ProfileGenerator
    generator_MEF_Cryst_B = PDFGenerator("G_MEF_Cryst_B")
    generator_MEF_Cryst_B.setStructure(stru1, periodic = True)
    generator_MEF_Mole_B = DebyePDFGenerator("G_MEF_Mole_B")
    generator_MEF_Mole_B.setStructure(stru2, periodic = False)
    generator_MEF_Intra = DebyePDFGenerator("G_MEF_Intra")
    generator_MEF_Intra.setStructure(stru3, periodic = False)

    ## The FitContribution
    # Add both generators to the FitContribution. Add the Profile. This will
    # send the metadata to the generators.
    contribution = FitContribution("MEF")
    contribution.addProfileGenerator(generator_MEF_Cryst_B)
    contribution.addProfileGenerator(generator_MEF_Mole_B)
    contribution.addProfileGenerator(generator_MEF_Intra)
    contribution.setProfile(profile, xname = "r")
    #write down the fit equation:
    #(G_MEF_Cryst_B - G_MEF_Mole_B) gives the intermolecular PDF, using a larger atomic displacement parameter
    #G_MEF_Intra gives intramolecular PDF, using a smaller atomic displacement parameter.
    #The sum of both parts gives the total PDF.
    contribution.setEquation("scale * (G_MEF_Cryst_B - G_MEF_Mole_B + G_MEF_Intra)")

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

    qdamp = 0.02902
    generator_MEF_Cryst_B.qdamp.value = qdamp
    generator_MEF_Mole_B.qdamp.value = qdamp
    generator_MEF_Intra.qdamp.value = qdamp

    # Vary the gloabal scale as well.
    recipe.addVar(contribution.scale, 1) 

#############################################################################################
############### First the MEF_Cryst_B parameters ############################################
#############################################################################################
    phase_MEF_Cryst_B = generator_MEF_Cryst_B.phase

    lat = phase_MEF_Cryst_B.getLattice()
    atoms = phase_MEF_Cryst_B.getScatterers()
    
    recipe.newVar("Uiso_Inter", 0.05, tag = "T1")
    recipe.newVar("lat_a", 14.556, tag = "lat")
    recipe.newVar("lat_b", 6.811, tag = "lat")
    recipe.newVar("lat_c", 7.657, tag = "lat")
    recipe.newVar("alpha", 119.57, tag = "lat")
    recipe.newVar("beta", 103.93, tag = "lat")
    recipe.newVar("gamma", 91.30, tag = "lat")

    recipe.constrain(lat.a, "lat_a")
    recipe.constrain(lat.b, "lat_b")
    recipe.constrain(lat.c, "lat_c")
    recipe.constrain(lat.alpha, "alpha")
    recipe.constrain(lat.beta, "beta")
    recipe.constrain(lat.gamma, "gamma")
    
    for atom in atoms:
    	if atom.element.title() == "N":
             recipe.constrain(atom.Uiso, "Uiso_Inter")
             
    	elif atom.element.title() == "O":
             recipe.constrain(atom.Uiso, "Uiso_Inter")

    	elif atom.element.title() == "C":
             recipe.constrain(atom.Uiso, "Uiso_Inter")

    	elif atom.element.title() == "H":
             recipe.constrain(atom.Uiso, "Uiso_Inter")

    generator_MEF_Cryst_B.delta2.value = 0


#############################################################################################
############### Second the MEF_Mole_B parameters ############################################
#############################################################################################
    phase_MEF_Mole_B = generator_MEF_Mole_B.phase
    generator_MEF_Mole_B.setQmin(0.0)
    generator_MEF_Mole_B.setQmax(24.0)
    recipe.newVar("zoom_Mole_B", 1, tag = "lat2") 

    lat = phase_MEF_Mole_B.getLattice()
    recipe.constrain(lat.a, "zoom_Mole_B")
    recipe.constrain(lat.b, "zoom_Mole_B")
    recipe.constrain(lat.c, "zoom_Mole_B")
   # Constrain fractional xyz parameters
    atoms = phase_MEF_Mole_B.getScatterers()
   # Constrain ADPs

    for atom in atoms:
    	if atom.element.title() == "C":
             recipe.constrain(atom.Uiso, "Uiso_Inter")

    	elif atom.element.title() == "O":
             recipe.constrain(atom.Uiso, "Uiso_Inter")

    	elif atom.element.title() == "N":
             recipe.constrain(atom.Uiso, "Uiso_Inter")

    	elif atom.element.title() == "H":
             recipe.constrain(atom.Uiso, "Uiso_Inter")

    generator_MEF_Mole_B.delta2.value = 0

#############################################################################################
############### Third the intra molecule parameters##########################################
#############################################################################################
    phase_MEF_Intra = generator_MEF_Intra.phase
    generator_MEF_Intra.setQmin(0.0)
    generator_MEF_Intra.setQmax(24.0)
    recipe.newVar("zoom_Intra", 1, tag = "lat3") 

    lat = phase_MEF_Intra.getLattice()
    recipe.constrain(lat.a, "zoom_Intra")
    recipe.constrain(lat.b, "zoom_Intra")
    recipe.constrain(lat.c, "zoom_Intra")
   # Constrain fractional xyz parameters
    atoms = phase_MEF_Intra.getScatterers()
   # Constrain ADPs
    recipe.newVar("Uiso_Intra", 0.005, tag = "T2") 
    
    for atom in atoms:
    	if atom.element.title() == "C":
             recipe.constrain(atom.Uiso, "Uiso_Intra")

    	elif atom.element.title() == "O":
             recipe.constrain(atom.Uiso, "Uiso_Intra")

    	elif atom.element.title() == "N":
             recipe.constrain(atom.Uiso, "Uiso_Intra")

    	elif atom.element.title() == "H":
             recipe.constrain(atom.Uiso, "Uiso_Intra")

    generator_MEF_Intra.delta2.value = 0

    # Give the recipe away so it can be used!
    return recipe
示例#13
0
def makeRecipe(stru1, stru2, stru3, datname):
    """Create a fitting recipe for crystalline PDF data."""

    ## The Profile
    profile = Profile()

    # Load data and add it to the profile
    parser = PDFParser()
    parser.parseFile(datname)
    profile.loadParsedData(parser)
    profile.setCalculationRange(xmin=1, xmax=20, dx=0.01)

    ## The ProfileGenerator
    generator_ROY_Cryst_B = PDFGenerator("G_ROY_Cryst_B")
    generator_ROY_Cryst_B.setStructure(stru1, periodic=True)
    generator_ROY_Mole_B = DebyePDFGenerator("G_ROY_Mole_B")
    generator_ROY_Mole_B.setStructure(stru2, periodic=False)
    generator_ROY_Intra = DebyePDFGenerator("G_ROY_Intra")
    generator_ROY_Intra.setStructure(stru3, periodic=False)

    ## The FitContribution
    # Add both generators to the FitContribution. Add the Profile. This will
    # send the metadata to the generators.
    contribution = FitContribution("ROY")
    contribution.addProfileGenerator(generator_ROY_Cryst_B)
    contribution.addProfileGenerator(generator_ROY_Mole_B)
    contribution.addProfileGenerator(generator_ROY_Intra)
    contribution.setProfile(profile, xname="r")

    # Write the fitting equation. We want to sum the PDFs from each phase and
    # multiply it by a scaling factor. We also want a certain phase scaling
    # relationship between the PDFs which we will enforce with constraints in
    # the FitRecipe.
    #from diffpy.srfit.pdf.characteristicfunctions import sphericalCF
    #contribution.registerFunction(sphericalCF, name = "f_IMC")
    contribution.setEquation(
        "scale * (G_ROY_Cryst_B - G_ROY_Mole_B + G_ROY_Intra)")

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

    qdamp = 0.02902
    generator_ROY_Cryst_B.qdamp.value = qdamp
    generator_ROY_Mole_B.qdamp.value = qdamp
    generator_ROY_Intra.qdamp.value = qdamp

    qbroad = 0.017315
    generator_ROY_Cryst_B.qbroad.value = qbroad
    generator_ROY_Mole_B.qbroad.value = qbroad
    generator_ROY_Intra.qbroad.value = qbroad

    # Vary the gloabal scale as well.
    recipe.addVar(contribution.scale, 1)

    #############################################################################################
    ############### First the ROY_Cryst_B parameters ############################################
    #############################################################################################
    phase_ROY_Cryst_B = generator_ROY_Cryst_B.phase

    lat = phase_ROY_Cryst_B.getLattice()
    atoms = phase_ROY_Cryst_B.getScatterers()

    recipe.newVar("Uiso_Inter", 0.05, tag="T1")
    recipe.newVar("lat_a", 3.9453, tag="lat")
    recipe.newVar("lat_b", 18.685, tag="lat")
    recipe.newVar("lat_c", 16.3948, tag="lat")
    #recipe.newVar("alpha", 90, tag = "lat")
    recipe.newVar("beta", 93.83, tag="lat")
    #recipe.newVar("gamma", 90, tag = "lat")

    recipe.constrain(lat.a, "lat_a")
    recipe.constrain(lat.b, "lat_b")
    recipe.constrain(lat.c, "lat_c")
    #recipe.constrain(lat.alpha, "alpha")
    recipe.constrain(lat.beta, "beta")
    #recipe.constrain(lat.gamma, "gamma")

    for atom in atoms:
        if atom.element.title() == "N":
            recipe.constrain(atom.Uiso, "Uiso_Inter")

        elif atom.element.title() == "O":
            recipe.constrain(atom.Uiso, "Uiso_Inter")

        elif atom.element.title() == "C":
            recipe.constrain(atom.Uiso, "Uiso_Inter")

        elif atom.element.title() == "H":
            recipe.constrain(atom.Uiso, "Uiso_Inter")

        elif atom.element.title() == "S":
            recipe.constrain(atom.Uiso, "Uiso_Inter")

    generator_ROY_Cryst_B.delta2.value = 0
    #    recipe.addVar(generator_IMC_Cryst_B.delta2, name = "delta2_IMC_Cryst_B", value =
    #            0, tag = "delta")

    #############################################################################################
    ############### Second the ROY_Mole_B parameters ############################################
    #############################################################################################
    phase_ROY_Mole_B = generator_ROY_Mole_B.phase
    generator_ROY_Mole_B.setQmin(0.0)
    generator_ROY_Mole_B.setQmax(24.0)
    recipe.newVar("zoom_Mole_B", 1, tag="lat2")

    lat = phase_ROY_Mole_B.getLattice()
    recipe.constrain(lat.a, "zoom_Mole_B")
    recipe.constrain(lat.b, "zoom_Mole_B")
    recipe.constrain(lat.c, "zoom_Mole_B")
    # Constrain fractional xyz parameters
    atoms = phase_ROY_Mole_B.getScatterers()
    # Constrain ADPs
    #recipe.newVar("Uiso_Inter", 0.05, tag = "T2")

    for atom in atoms:
        if atom.element.title() == "C":
            recipe.constrain(atom.Uiso, "Uiso_Inter")

        elif atom.element.title() == "O":
            recipe.constrain(atom.Uiso, "Uiso_Inter")

        elif atom.element.title() == "N":
            recipe.constrain(atom.Uiso, "Uiso_Inter")

        elif atom.element.title() == "H":
            recipe.constrain(atom.Uiso, "Uiso_Inter")

        elif atom.element.title() == "S":
            recipe.constrain(atom.Uiso, "Uiso_Inter")

    generator_ROY_Mole_B.delta2.value = 0

    #    recipe.addVar(generator_IMC_Mole_B.delta2, name = "delta2_IMC_Mole_B", value
    #            = 5.66086478091, tag = "delta")

    #############################################################################################
    ############### Third the intra molecule parameters##########################################
    #############################################################################################
    phase_ROY_Intra = generator_ROY_Intra.phase
    generator_ROY_Intra.setQmin(0.0)
    generator_ROY_Intra.setQmax(24.0)
    recipe.newVar("zoom_Intra", 1, tag="lat3")

    lat = phase_ROY_Intra.getLattice()
    recipe.constrain(lat.a, "zoom_Intra")
    recipe.constrain(lat.b, "zoom_Intra")
    recipe.constrain(lat.c, "zoom_Intra")
    # Constrain fractional xyz parameters
    atoms = phase_ROY_Intra.getScatterers()
    # Constrain ADPs
    recipe.newVar("Uiso_Intra", 0.005, tag="T2")

    for atom in atoms:
        if atom.element.title() == "C":
            recipe.constrain(atom.Uiso, "Uiso_Intra")

        elif atom.element.title() == "O":
            recipe.constrain(atom.Uiso, "Uiso_Intra")

        elif atom.element.title() == "N":
            recipe.constrain(atom.Uiso, "Uiso_Intra")

        elif atom.element.title() == "H":
            recipe.constrain(atom.Uiso, "Uiso_Intra")

        elif atom.element.title() == "S":
            recipe.constrain(atom.Uiso, "Uiso_Intra")

    generator_ROY_Intra.delta2.value = 0

    # Give the recipe away so it can be used!
    return recipe
示例#14
0
def makeRecipe(niciffile, siciffile, datname):
    """Create a fitting recipe for crystalline PDF data."""

    ## The Profile
    profile = Profile()

    # Load data and add it to the profile
    parser = PDFParser()
    parser.parseFile(datname)
    profile.loadParsedData(parser)
    profile.setCalculationRange(xmax=20)

    ## The ProfileGenerator
    # In order to fit two phases simultaneously, we must use two PDFGenerators.
    # PDFGenerator is designed to take care of as little information as it
    # must. (Don't do too much, and do it well.) A PDFGenerator can generate
    # the signal from only a single phase at a time. So, we will create one
    # PDFGenerator for each phase and compose them within the same
    # FitContribution. Note that both generators will be associated with the
    # same Profile within the FitContribution, so they will both be
    # automatically configured according to the metadata.
    #
    # The generator for the nickel phase. We call it "G_ni" and will use this
    # name later when we set the fitting equation in the FitContribution.
    generator_ni = PDFGenerator("G_ni")
    stru = CreateCrystalFromCIF(file(niciffile))
    generator_ni.setStructure(stru)
    # The generator for the silicon phase. We call it "G_si".
    generator_si = PDFGenerator("G_si")
    stru = CreateCrystalFromCIF(file(siciffile))
    generator_si.setStructure(stru)

    ## The FitContribution
    # Add both generators to the FitContribution. Add the Profile. This will
    # send the metadata to the generators.
    contribution = FitContribution("nisi")
    contribution.addProfileGenerator(generator_ni)
    contribution.addProfileGenerator(generator_si)
    contribution.setProfile(profile, xname="r")

    # Write the fitting equation. We want to sum the PDFs from each phase and
    # multiply it by a scaling factor. We also want a certain phase scaling
    # relationship between the PDFs which we will enforce with constraints in
    # the FitRecipe.
    contribution.setEquation("scale * (G_ni +  G_si)")

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

    ## Configure the fit variables
    # Start by configuring the scale factor and resolution factors.
    # We want the sum of the phase scale factors to be 1.
    recipe.newVar("scale_ni", 0.1)
    recipe.constrain(generator_ni.scale, "scale_ni")
    recipe.constrain(generator_si.scale, "1 - scale_ni")
    # We also want the resolution factor to be the same on each.
    recipe.newVar("qdamp", 0.03)
    recipe.constrain(generator_ni.qdamp, "qdamp")
    recipe.constrain(generator_si.qdamp, "qdamp")

    # Vary the gloabal scale as well.
    recipe.addVar(contribution.scale, 1)

    # Now we can configure the structural parameters. Since we're using
    # ObjCrystCrystalParSets, the space group constraints are automatically
    # applied to each phase. We must selectively vary the free parameters.
    #
    # First the nickel parameters
    phase_ni = generator_ni.phase
    for par in phase_ni.sgpars:
        recipe.addVar(par, name=par.name + "_ni")
    recipe.addVar(generator_ni.delta2, name="delta2_ni")
    # Next the silicon parameters
    phase_si = generator_si.phase
    for par in phase_si.sgpars:
        recipe.addVar(par, name=par.name + "_si")
    recipe.addVar(generator_si.delta2, name="delta2_si")

    # We have prior information from the earlier examples so we'll use it here
    # in the form of restraints.
    #
    # The nickel lattice parameter was measured to be 3.527. The uncertainty
    # values are invalid for that measurement, since the data from which it is
    # derived has no uncertainty. Thus, we will tell the recipe to scale the
    # residual, which means that it will be weighted as much as the average
    # data point during the fit.
    recipe.restrain("a_ni", lb=3.527, ub=3.527, scaled=True)
    # Now we do the same with the delta2 and Biso parameters (remember that
    # Biso = 8*pi**2*Uiso)
    recipe.restrain("delta2_ni", lb=2.22, ub=2.22, scaled=True)
    recipe.restrain("Biso_0_ni", lb=0.454, ub=0.454, scaled=True)
    #
    # We can do the same with the silicon values. We haven't done a thorough
    # job of measuring the uncertainties in the results, so we'll scale these
    # as well.
    recipe.restrain("a_si", lb=5.430, ub=5.430, scaled=True)
    recipe.restrain("delta2_si", lb=3.54, ub=3.54, scaled=True)
    recipe.restrain("Biso_0_si", lb=0.645, ub=0.645, scaled=True)

    # Give the recipe away so it can be used!
    return recipe
示例#15
0
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.parseFile(datname)
    profile.loadParsedData(parser)
    profile.setCalculationRange(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 = CreateCrystalFromCIF(file(ciffile))
    generator.setStructure(stru)
    generator.setQmax(40.0)
    
    ## The FitContribution
    contribution = FitContribution("nickel")
    contribution.addProfileGenerator(generator)
    contribution.setProfile(profile, xname = "r")

    # Make the FitRecipe and add the FitContribution.
    recipe = FitRecipe()
    recipe.addContribution(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
    # constriants 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
    # 'constrainAsSpaceGroup'.
    #
    # 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.addVar(par)
    # set the initial thermal factor to a non-zero value
    assert hasattr(recipe, 'B11_0')
    recipe.B11_0 = 0.1

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

    # Give the recipe away so it can be used!
    return recipe
示例#16
0
from diffpy.Structure.Parsers import getParser
from diffpy.srfit.pdf import PDFGenerator, PDFParser
from diffpy.srfit.fitbase import FitRecipe, FitResults
from diffpy.srfit.fitbase import Profile, FitContribution

# Files containing our experimental data and structure file
dataFile = "npdf_07334.gr"
structureFile = "MnO_R-3m.cif"

# load structure and space group from the CIF file
pcif = getParser('cif')
mno = pcif.parseFile(structureFile)

# prepare profile object with experimental data
profile = Profile()
parser = PDFParser()
parser.parseFile(dataFile)
profile.loadParsedData(parser)

# define range for pdf calculation
rmin = 0.01
rmax = 20
rstep = 0.01

# setup calculation range for the PDF simulation
profile.setCalculationRange(xmin=rmin, xmax=rmax, dx=rstep)

# prepare nucpdf function that simulates the nuclear PDF
nucpdf = PDFGenerator("nucpdf")
nucpdf.setStructure(mno)
nucpdf.setProfile(profile)
示例#17
0
def makeRecipe(stru1, stru2, datname):
    """Create a fitting recipe for crystalline PDF data."""

    ## The Profile
    profile = Profile()

    # Load data and add it to the profile
    parser = PDFParser()
    parser.parseFile(datname)
    profile.loadParsedData(parser)
    profile.setCalculationRange(xmin=1.5, xmax = 45, dx = 0.1)

    ## The ProfileGenerator
    # In order to fit the core and shell phases simultaneously, we must use two
    # PDFGenerators.
    #
    # The generator for the CdS core. We call it "G_CdS" and will use this name
    # later when we set the fitting equation in the FitContribution.
    generator_cds = PDFGenerator("G_CdS")
    generator_cds.setStructure(stru1)
    generator_cds.setQmax(26)
    generator_cds.qdamp.value = 0.0396
    # The generator for the ZnS shell. We call it "G_ZnS".
    generator_zns = PDFGenerator("G_ZnS")
    generator_zns.setStructure(stru2)
    generator_zns.setQmax(26)
    generator_zns.qdamp.value = 0.0396

    ## The FitContribution
    # Add both generators and the profile to the FitContribution.
    contribution = FitContribution("cdszns")
    contribution.addProfileGenerator(generator_cds)
    contribution.addProfileGenerator(generator_zns)
    contribution.setProfile(profile, xname = "r")

    # Set up the characteristic functions. We use a spherical CF for the core
    # and a spherical shell CF for the shell. Since this is set up as two
    # phases, we implicitly assume that the core-shell correlations contribute
    # very little to the PDF.
    from diffpy.srfit.pdf.characteristicfunctions import sphericalCF, shellCF
    contribution.registerFunction(sphericalCF, name = "f_CdS")
    contribution.registerFunction(shellCF, name = "f_ZnS")

    # Write the fitting equation. We want to sum the PDFs from each phase and
    # multiply it by a scaling factor.
    contribution.setEquation("scale * (f_CdS * G_CdS +  f_ZnS * G_ZnS)")

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

    # Vary the inner radius and thickness of the shell. Constrain the core
    # diameter to twice the shell radius.
    recipe.addVar(contribution.radius, 15)
    recipe.addVar(contribution.thickness, 11)
    recipe.constrain(contribution.psize, "2 * radius")

    ## Configure the fit variables
    # Start by configuring the scale factor and resolution factors.
    # We want the sum of the phase scale factors to be 1.
    recipe.newVar("scale_CdS", 0.7)
    recipe.constrain(generator_cds.scale, "scale_CdS")
    recipe.constrain(generator_zns.scale, "1 - scale_CdS")
    # We also want the resolution factor to be the same on each.

    # Vary the gloabal scale as well.
    recipe.addVar(contribution.scale, 0.3)

    # Now we can configure the structural parameters. We tag the different
    # structural variables so we can easily turn them on and off in the
    # subsequent refinement.
    phase_cds = generator_cds.phase
    for par in phase_cds.sgpars.latpars:
        recipe.addVar(par, name = par.name + "_cds", tag = "lat")
    for par in phase_cds.sgpars.adppars:
        recipe.addVar(par, 1, name = par.name + "_cds", tag = "adp")
    recipe.addVar(phase_cds.sgpars.xyzpars.z_1, name = "z_1_cds", tag = "xyz")
    # Since we know these have stacking disorder, constrain the B33 adps for
    # each atom type.
    recipe.constrain("B33_1_cds", "B33_0_cds")
    recipe.addVar(generator_cds.delta2, name = "delta2_cds", value = 5)

    phase_zns = generator_zns.phase
    for par in phase_zns.sgpars.latpars:
        recipe.addVar(par, name = par.name + "_zns", tag = "lat")
    for par in phase_zns.sgpars.adppars:
        recipe.addVar(par, 1, name = par.name + "_zns", tag = "adp")
    recipe.addVar(phase_zns.sgpars.xyzpars.z_1, name = "z_1_zns", tag = "xyz")
    recipe.constrain("B33_1_zns", "B33_0_zns")
    recipe.addVar(generator_zns.delta2, name = "delta2_zns", value = 2.5)

    # Give the recipe away so it can be used!
    return recipe
def makeRecipe(ciffile, xdatname, ndatname):
    """Create a fitting recipe for crystalline PDF data."""

    ## The Profiles
    # We need a profile for each data set. This means that we will need two
    # FitContributions as well.
    xprofile = Profile()
    nprofile = Profile()

    # Load data and add it to the proper Profile.
    parser = PDFParser()
    parser.parseFile(xdatname)
    xprofile.loadParsedData(parser)
    xprofile.setCalculationRange(xmax = 20)

    parser = PDFParser()
    parser.parseFile(ndatname)
    nprofile.loadParsedData(parser)
    nprofile.setCalculationRange(xmax = 20)

    ## The ProfileGenerators
    # We need one of these for the x-ray data.
    xgenerator = PDFGenerator("G")
    stru = CreateCrystalFromCIF(file(ciffile))
    xgenerator.setStructure(stru)

    # And we need one for the neutron data. We want to refine the same
    # structure object in each PDFGenerator. This would suggest that we add the
    # same Crystal to each. However, if we do that then we will have two
    # Parameters for each Crystal data member (two Parameters for the "a"
    # lattice parameter, etc.), held in different ObjCrystCrystalParSets, each
    # managed by its own PDFGenerator. Thus, changes made to the Crystal
    # through one PDFGenerator will not be known to the other PDFGenerator
    # since their ObjCrystCrystalParSets don't know about each other. The
    # solution is to share ObjCrystCrystalParSets rather than Crystals. This
    # way there is only one Parameter for each Crystal data member. (An
    # alternative to this is to constrain each structure Parameter to be varied
    # to the same variable. The present approach is easier and less error
    # prone.)
    #
    # Tell the neutron PDFGenerator to use the phase from the x-ray
    # PDFGenerator.
    ngenerator = PDFGenerator("G")
    ngenerator.setPhase(xgenerator.phase)

    ## The FitContributions
    # We associate the x-ray PDFGenerator and Profile in one FitContribution...
    xcontribution = FitContribution("xnickel")
    xcontribution.addProfileGenerator(xgenerator)
    xcontribution.setProfile(xprofile, xname = "r")
    # and the neutron objects in another.
    ncontribution = FitContribution("nnickel")
    ncontribution.addProfileGenerator(ngenerator)
    ncontribution.setProfile(nprofile, xname = "r")

    # This example is different than the previous ones in that we are composing
    # a residual function from other residuals (one for the x-ray contribution
    # and one for the neutron contribution). The relative magnitude of these
    # residuals effectively determines the influence of each contribution over
    # the fit. This is a problem in this case because the x-ray data has
    # uncertainty values associated with it (on the order of 1e-4), and the
    # chi^2 residual is proportional to 1 / uncertainty**2. The neutron has no
    # uncertainty, so it's chi^2 is proportional to 1. Thus, my optimizing
    # chi^2 we would give the neutron data practically no weight in the fit. To
    # get around this, we will optimize a different metric.
    #
    # The contribution's residual can be either chi^2, Rw^2, or custom crafted.
    # In this case, we should minimize Rw^2 of each contribution so that each
    # one can contribute roughly equally to the fit.
    xcontribution.setResidualEquation("resv")
    ncontribution.setResidualEquation("resv")

    # Make the FitRecipe and add the FitContributions.
    recipe = FitRecipe()
    recipe.addContribution(xcontribution)
    recipe.addContribution(ncontribution)

    # Now we vary and constrain Parameters as before.
    recipe.addVar(xgenerator.scale, 1, "xscale")
    recipe.addVar(ngenerator.scale, 1, "nscale")
    recipe.addVar(xgenerator.qdamp, 0.01, "xqdamp")
    recipe.addVar(ngenerator.qdamp, 0.01, "nqdamp")
    # delta2 is a non-structual material propery. Thus, we constrain together
    # delta2 Parameter from each PDFGenerator.
    delta2 = recipe.newVar("delta2", 2)
    recipe.constrain(xgenerator.delta2, delta2)
    recipe.constrain(ngenerator.delta2, delta2)

    # We only need to constrain phase properties once since there is a single
    # ObjCrystCrystalParSet for the Crystal.
    phase = xgenerator.phase
    for par in phase.sgpars:
        recipe.addVar(par)
    recipe.B11_0 = 0.1

    # Give the recipe away so it can be used!
    return recipe
示例#19
0
def makerecipe(structure_file, data_file):
    """
    Basic function for creating and properly constraining a fit recipe.

    Parameters
    ----------
    structure_file : Path object or str
        Path to *.cif file, containing a structural model to use to fit the PDF data.
    data_file : Path object or str
        Path to data file containing PDF data to fit against.

    Returns
    -------
    recipe : FitRecipe object
        An initialized fit recipe object, ready for fitting.
    """
    ######## Profile Section ##################
    # Create a Profile object for the experimental dataset.
    # This handles all details about the dataset.
    # We also tell this profile the range and mesh of points in r-space.
    profile = Profile()
    parser = PDFParser()
    parser.parseFile(data_file)
    profile.loadParsedData(parser)

    p_cif = getParser('cif')
    structure = p_cif.parseFile(str(structure_file))
    space_group = p_cif.spacegroup.short_name

    ######## PDF Generator Section ##################
    # Create a PDF Generator object for a periodic structure model.
    # Here we name it "G1" and we give it the structure object.
    # This Generator will later compute the model PDF for the structure
    # object we provide it here.
    generator_crystal1 = PDFGenerator("G1")
    generator_crystal1.setStructure(structure, periodic=True)

    ######## Fit Contribution Section ##################
    # Create a Fit Contribution object, and name it "crystal."
    # We then give the PDF Generator object we created just above
    # to this Fit Contribution object. The Fit Contribution holds
    # the equation used to fit the PDF.
    contribution = FitContribution("crystal")
    contribution.addProfileGenerator(generator_crystal1)

    # Set an equation, within the Fit Contribution, based on your PDF
    # Generators. Here we simply have one Generator, G1, and a scale variable,
    # s1. Using this structure is a very flexible way of adding additional
    # Generators (ie. multiple structural phases), experimental Profiles,
    # PDF characteristic functions (ie. shape envelopes), and more.
    contribution.setEquation("s1*G1")

    # Set the experimental profile, within the Fit Contribution object,
    # to the Profile object we created earlier.
    contribution.setProfile(profile, xname="r")

    ######## Recipe Section ##################
    # Create the Fit Recipe object that holds all the details of the fit,
    # defined in the lines above. We give the Fit Recipe the Fit
    # Contribution we created earlier.
    recipe = FitRecipe()
    recipe.addContribution(contribution)

    # Return the Fit Recipe object to be optimized
    return recipe
示例#20
0
def makeRecipe(stru1, stru2, datname):
    """Create a fitting recipe for crystalline PDF data."""

    ## The Profile
    profile = Profile()

    # Load data and add it to the profile
    parser = PDFParser()
    parser.parseFile(datname)
    profile.loadParsedData(parser)
    profile.setCalculationRange(xmin=1.5, xmax=45, dx=0.1)

    ## The ProfileGenerator
    # In order to fit the core and shell phases simultaneously, we must use two
    # PDFGenerators.
    #
    # The generator for the CdS core. We call it "G_CdS" and will use this name
    # later when we set the fitting equation in the FitContribution.
    generator_cds = PDFGenerator("G_CdS")
    generator_cds.setStructure(stru1)
    generator_cds.setQmax(26)
    generator_cds.qdamp.value = 0.0396
    # The generator for the ZnS shell. We call it "G_ZnS".
    generator_zns = PDFGenerator("G_ZnS")
    generator_zns.setStructure(stru2)
    generator_zns.setQmax(26)
    generator_zns.qdamp.value = 0.0396

    ## The FitContribution
    # Add both generators and the profile to the FitContribution.
    contribution = FitContribution("cdszns")
    contribution.addProfileGenerator(generator_cds)
    contribution.addProfileGenerator(generator_zns)
    contribution.setProfile(profile, xname="r")

    # Set up the characteristic functions. We use a spherical CF for the core
    # and a spherical shell CF for the shell. Since this is set up as two
    # phases, we implicitly assume that the core-shell correlations contribute
    # very little to the PDF.
    from diffpy.srfit.pdf.characteristicfunctions import sphericalCF, shellCF
    contribution.registerFunction(sphericalCF, name="f_CdS")
    contribution.registerFunction(shellCF, name="f_ZnS")

    # Write the fitting equation. We want to sum the PDFs from each phase and
    # multiply it by a scaling factor.
    contribution.setEquation("scale * (f_CdS * G_CdS +  f_ZnS * G_ZnS)")

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

    # Vary the inner radius and thickness of the shell. Constrain the core
    # diameter to twice the shell radius.
    recipe.addVar(contribution.radius, 15)
    recipe.addVar(contribution.thickness, 11)
    recipe.constrain(contribution.psize, "2 * radius")

    ## Configure the fit variables
    # Start by configuring the scale factor and resolution factors.
    # We want the sum of the phase scale factors to be 1.
    recipe.newVar("scale_CdS", 0.7)
    recipe.constrain(generator_cds.scale, "scale_CdS")
    recipe.constrain(generator_zns.scale, "1 - scale_CdS")
    # We also want the resolution factor to be the same on each.

    # Vary the gloabal scale as well.
    recipe.addVar(contribution.scale, 0.3)

    # Now we can configure the structural parameters. We tag the different
    # structural variables so we can easily turn them on and off in the
    # subsequent refinement.
    phase_cds = generator_cds.phase
    for par in phase_cds.sgpars.latpars:
        recipe.addVar(par, name=par.name + "_cds", tag="lat")
    for par in phase_cds.sgpars.adppars:
        recipe.addVar(par, 1, name=par.name + "_cds", tag="adp")
    recipe.addVar(phase_cds.sgpars.xyzpars.z_1, name="z_1_cds", tag="xyz")
    # Since we know these have stacking disorder, constrain the B33 adps for
    # each atom type.
    recipe.constrain("B33_1_cds", "B33_0_cds")
    recipe.addVar(generator_cds.delta2, name="delta2_cds", value=5)

    phase_zns = generator_zns.phase
    for par in phase_zns.sgpars.latpars:
        recipe.addVar(par, name=par.name + "_zns", tag="lat")
    for par in phase_zns.sgpars.adppars:
        recipe.addVar(par, 1, name=par.name + "_zns", tag="adp")
    recipe.addVar(phase_zns.sgpars.xyzpars.z_1, name="z_1_zns", tag="xyz")
    recipe.constrain("B33_1_zns", "B33_0_zns")
    recipe.addVar(generator_zns.delta2, name="delta2_zns", value=2.5)

    # Give the recipe away so it can be used!
    return recipe
from diffpy.Structure.Parsers import getParser
from diffpy.srfit.pdf import PDFGenerator, PDFParser
from diffpy.srfit.fitbase import FitRecipe, FitResults
from diffpy.srfit.fitbase import Profile, FitContribution

# Files containing our experimental data and structure file
dataFile = "npdf_07334.gr"
structureFile = "MnO_R-3m.cif"

# load structure and space group from the CIF file
pcif = getParser('cif')
mno = pcif.parseFile(structureFile)

# prepare profile object with experimental data
profile = Profile()
parser = PDFParser()
parser.parseFile(dataFile)
profile.loadParsedData(parser)

# define range for pdf calculation
rmin = 0.01
rmax = 20
rstep = 0.01

# setup calculation range for the PDF simulation
profile.setCalculationRange(xmin=rmin, xmax=rmax, dx=rstep)

# prepare nucpdf function that simulates the nuclear PDF
nucpdf = PDFGenerator("nucpdf")
nucpdf.setStructure(mno)
nucpdf.setProfile(profile)
def makeRecipe(niciffile, siciffile, datname):
    """Create a fitting recipe for crystalline PDF data."""

    ## The Profile
    profile = Profile()

    # Load data and add it to the profile
    parser = PDFParser()
    parser.parseFile(datname)
    profile.loadParsedData(parser)
    profile.setCalculationRange(xmax = 20)

    ## The ProfileGenerator
    # In order to fit two phases simultaneously, we must use two PDFGenerators.
    # PDFGenerator is designed to take care of as little information as it
    # must. (Don't do too much, and do it well.) A PDFGenerator can generate
    # the signal from only a single phase at a time. So, we will create one
    # PDFGenerator for each phase and compose them within the same
    # FitContribution. Note that both generators will be associated with the
    # same Profile within the FitContribution, so they will both be
    # automatically configured according to the metadata.
    #
    # The generator for the nickel phase. We call it "G_ni" and will use this
    # name later when we set the fitting equation in the FitContribution.
    generator_ni = PDFGenerator("G_ni")
    stru = CreateCrystalFromCIF(file(niciffile))
    generator_ni.setStructure(stru)
    # The generator for the silicon phase. We call it "G_si".
    generator_si = PDFGenerator("G_si")
    stru = CreateCrystalFromCIF(file(siciffile))
    generator_si.setStructure(stru)

    ## The FitContribution
    # Add both generators to the FitContribution. Add the Profile. This will
    # send the metadata to the generators.
    contribution = FitContribution("nisi")
    contribution.addProfileGenerator(generator_ni)
    contribution.addProfileGenerator(generator_si)
    contribution.setProfile(profile, xname = "r")

    # Write the fitting equation. We want to sum the PDFs from each phase and
    # multiply it by a scaling factor. We also want a certain phase scaling
    # relationship between the PDFs which we will enforce with constraints in
    # the FitRecipe.
    contribution.setEquation("scale * (G_ni +  G_si)")

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

    ## Configure the fit variables
    # Start by configuring the scale factor and resolution factors.
    # We want the sum of the phase scale factors to be 1.
    recipe.newVar("scale_ni", 0.1)
    recipe.constrain(generator_ni.scale, "scale_ni")
    recipe.constrain(generator_si.scale, "1 - scale_ni")
    # We also want the resolution factor to be the same on each.
    recipe.newVar("qdamp", 0.03)
    recipe.constrain(generator_ni.qdamp, "qdamp")
    recipe.constrain(generator_si.qdamp, "qdamp")

    # Vary the gloabal scale as well.
    recipe.addVar(contribution.scale, 1)

    # Now we can configure the structural parameters. Since we're using
    # ObjCrystCrystalParSets, the space group constraints are automatically
    # applied to each phase. We must selectively vary the free parameters.
    #
    # First the nickel parameters
    phase_ni = generator_ni.phase
    for par in phase_ni.sgpars:
        recipe.addVar(par, name = par.name + "_ni")
    recipe.addVar(generator_ni.delta2, name = "delta2_ni")
    # Next the silicon parameters
    phase_si = generator_si.phase
    for par in phase_si.sgpars:
        recipe.addVar(par, name = par.name + "_si")
    recipe.addVar(generator_si.delta2, name = "delta2_si")

    # We have prior information from the earlier examples so we'll use it here
    # in the form of restraints.
    #
    # The nickel lattice parameter was measured to be 3.527. The uncertainty
    # values are invalid for that measurement, since the data from which it is
    # derived has no uncertainty. Thus, we will tell the recipe to scale the
    # residual, which means that it will be weighted as much as the average
    # data point during the fit.
    recipe.restrain("a_ni", lb = 3.527, ub = 3.527, scaled = True)
    # Now we do the same with the delta2 and Biso parameters (remember that
    # Biso = 8*pi**2*Uiso)
    recipe.restrain("delta2_ni", lb = 2.22, ub = 2.22, scaled = True)
    recipe.restrain("Biso_0_ni", lb = 0.454, ub = 0.454, scaled = True)
    #
    # We can do the same with the silicon values. We haven't done a thorough
    # job of measuring the uncertainties in the results, so we'll scale these
    # as well.
    recipe.restrain("a_si", lb = 5.430, ub = 5.430, scaled = True)
    recipe.restrain("delta2_si", lb = 3.54, ub = 3.54, scaled = True)
    recipe.restrain("Biso_0_si", lb = 0.645, ub = 0.645, scaled = True)

    # Give the recipe away so it can be used!
    return recipe
示例#23
0
def makeRecipe(ciffile, grdata, iqdata):
    """Make complex-modeling recipe where I(q) and G(r) are fit
    simultaneously.

    The fit I(q) is fed into the calculation of G(r), which provides feedback
    for the fit parameters of both.

    """

    # Create a PDF contribution as before
    pdfprofile = Profile()
    pdfparser = PDFParser()
    pdfparser.parseFile(grdata)
    pdfprofile.loadParsedData(pdfparser)
    pdfprofile.setCalculationRange(xmin = 0.1, xmax = 20)

    pdfcontribution = FitContribution("pdf")
    pdfcontribution.setProfile(pdfprofile, xname = "r")

    pdfgenerator = PDFGenerator("G")
    pdfgenerator.setQmax(30.0)
    stru = loadCrystal(ciffile)
    pdfgenerator.setStructure(stru)
    pdfcontribution.addProfileGenerator(pdfgenerator)
    pdfcontribution.setResidualEquation("resv")

    # Create a SAS contribution as well. We assume the nanoparticle is roughly
    # elliptical.
    sasprofile = Profile()
    sasparser = SASParser()
    sasparser.parseFile(iqdata)
    sasprofile.loadParsedData(sasparser)
    if all(sasprofile.dy == 0):
        sasprofile.dy[:] = 1

    sascontribution = FitContribution("sas")
    sascontribution.setProfile(sasprofile)

    from sas.models.EllipsoidModel import EllipsoidModel
    model = EllipsoidModel()
    sasgenerator = SASGenerator("generator", model)
    sascontribution.addProfileGenerator(sasgenerator)
    sascontribution.setResidualEquation("resv")

    # Now we set up a characteristic function calculator that depends on the
    # sas model.
    cfcalculator = SASCF("f", model)

    # Register the calculator with the pdf contribution and define the fitting
    # equation.
    pdfcontribution.registerCalculator(cfcalculator)
    # The PDF for a nanoscale crystalline is approximated by
    # Gnano = f * Gcryst
    pdfcontribution.setEquation("f * G")

    # Moving on
    recipe = FitRecipe()
    recipe.addContribution(pdfcontribution)
    recipe.addContribution(sascontribution)

    # PDF
    phase = pdfgenerator.phase
    for par in phase.sgpars:
        recipe.addVar(par)

    recipe.addVar(pdfgenerator.scale, 1)
    recipe.addVar(pdfgenerator.delta2, 0)

    # SAS
    recipe.addVar(sasgenerator.scale, 1, name = "iqscale")
    recipe.addVar(sasgenerator.radius_a, 10)
    recipe.addVar(sasgenerator.radius_b, 10)

    # Even though the cfcalculator and sasgenerator depend on the same sas
    # model, we must still constrain the cfcalculator Parameters so that it is
    # informed of changes in the refined parameters.
    recipe.constrain(cfcalculator.radius_a, "radius_a")
    recipe.constrain(cfcalculator.radius_b, "radius_b")

    return recipe
示例#24
0
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. Unlike in other examples, we use a
    # class (PDFParser) to help us load the data. This class will read the data
    # and relevant metadata from a two- to four-column data file generated
    # with PDFGetX2 or PDFGetN. The metadata will be passed to the PDFGenerator
    # when they are associated in the FitContribution, which saves some
    # configuration steps.
    parser = PDFParser()
    parser.parseFile(datname)
    profile.loadParsedData(parser)
    profile.setCalculationRange(xmax = 20)

    ## The ProfileGenerator
    # The PDFGenerator is for configuring and calculating a PDF profile. Here,
    # we want to refine a Structure object from diffpy.structure. We tell the
    # PDFGenerator that with the 'setStructure' method. All other configuration
    # options will be inferred from the metadata that is read by the PDFParser.
    # In particular, this will set the scattering type (x-ray or neutron), the
    # Qmax value, as well as initial values for the non-structural Parameters.
    generator = PDFGenerator("G")
    stru = Structure()
    stru.read(ciffile)
    generator.setStructure(stru)

    ## The FitContribution
    # Here we associate the Profile and ProfileGenerator, as has been done
    # before.
    contribution = FitContribution("nickel")
    contribution.addProfileGenerator(generator)
    contribution.setProfile(profile, xname = "r")

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

    ## Configure the fit variables

    # The PDFGenerator class holds the ParameterSet associated with the
    # Structure passed above in a data member named "phase". (We could have
    # given the ParameterSet a name other than "phase" when we added it to the
    # PDFGenerator.) The ParameterSet in this case is a StructureParameterSet,
    # the documentation for which is found in the
    # diffpy.srfit.structure.diffpystructure module.
    phase = generator.phase

    # We start by constraining the phase to the known space group. We could do
    # this by hand, but there is a method in diffpy.srfit.structure named
    # 'constrainAsSpaceGroup' for this purpose. The constraints will by default
    # be applied to the sites, the lattice and to the ADPs. See the method
    # documentation for more details. The 'constrainAsSpaceGroup' method may
    # create new Parameters, which it returns in a SpaceGroupParameters object.
    from diffpy.srfit.structure import constrainAsSpaceGroup
    sgpars = constrainAsSpaceGroup(phase, "Fm-3m")

    # The SpaceGroupParameters object returned by 'constrainAsSpaceGroup' holds
    # the free Parameters allowed by the space group constraints. Once a
    # structure is constrained, we need (should) only use the Parameters
    # provided in the SpaceGroupParameters, as the relevant structure
    # Parameters are constrained to these.
    #
    # We know that the space group does not allow for any free sites because
    # each atom is on a special position. There is one free (cubic) lattice
    # parameter and one free (isotropic) ADP. We can access these Parameters in
    # the xyzpars, latpars, and adppars members of the SpaceGroupParameters
    # object.
    for par in sgpars.latpars:
        recipe.addVar(par)
    for par in sgpars.adppars:
        recipe.addVar(par, 0.005)

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

    # Give the recipe away so it can be used!
    return recipe
示例#25
0
def makeRecipe(ciffile, grdata, iqdata):
    """Make complex-modeling recipe where I(q) and G(r) are fit
    simultaneously.

    The fit I(q) is fed into the calculation of G(r), which provides feedback
    for the fit parameters of both.
    
    """

    # Create a PDF contribution as before
    pdfprofile = Profile()
    pdfparser = PDFParser()
    pdfparser.parseFile(grdata)
    pdfprofile.loadParsedData(pdfparser)
    pdfprofile.setCalculationRange(xmin = 0.1, xmax = 20)

    pdfcontribution = FitContribution("pdf")
    pdfcontribution.setProfile(pdfprofile, xname = "r")

    pdfgenerator = PDFGenerator("G")
    pdfgenerator.setQmax(30.0)
    stru = CreateCrystalFromCIF(file(ciffile))
    pdfgenerator.setStructure(stru)
    pdfcontribution.addProfileGenerator(pdfgenerator)
    pdfcontribution.setResidualEquation("resv")

    # Create a SAS contribution as well. We assume the nanoparticle is roughly
    # elliptical.
    sasprofile = Profile()
    sasparser = SASParser()
    sasparser.parseFile(iqdata)
    sasprofile.loadParsedData(sasparser)

    sascontribution = FitContribution("sas")
    sascontribution.setProfile(sasprofile)

    from sans.models.EllipsoidModel import EllipsoidModel
    model = EllipsoidModel()
    sasgenerator = SASGenerator("generator", model)
    sascontribution.addProfileGenerator(sasgenerator)
    sascontribution.setResidualEquation("resv")

    # Now we set up a characteristic function calculator that depends on the
    # sas model.
    cfcalculator = SASCF("f", model)

    # Register the calculator with the pdf contribution and define the fitting
    # equation.
    pdfcontribution.registerCalculator(cfcalculator)
    # The PDF for a nanoscale crystalline is approximated by
    # Gnano = f * Gcryst
    pdfcontribution.setEquation("f * G")

    # Moving on
    recipe = FitRecipe()
    recipe.addContribution(pdfcontribution)
    recipe.addContribution(sascontribution)

    # PDF
    phase = pdfgenerator.phase
    for par in phase.sgpars:
        recipe.addVar(par)

    recipe.addVar(pdfgenerator.scale, 1)
    recipe.addVar(pdfgenerator.delta2, 0)

    # SAS
    recipe.addVar(sasgenerator.scale, 1, name = "iqscale")
    recipe.addVar(sasgenerator.radius_a, 10)
    recipe.addVar(sasgenerator.radius_b, 10)

    # Even though the cfcalculator and sasgenerator depend on the same sas
    # model, we must still constrain the cfcalculator Parameters so that it is
    # informed of changes in the refined parameters.
    recipe.constrain(cfcalculator.radius_a, "radius_a")
    recipe.constrain(cfcalculator.radius_b, "radius_b")

    return recipe
def makeRecipe(ciffile, xdatname, ndatname):
    """Create a fitting recipe for crystalline PDF data."""

    ## The Profiles
    # We need a profile for each data set. This means that we will need two
    # FitContributions as well.
    xprofile = Profile()
    nprofile = Profile()

    # Load data and add it to the proper Profile.
    parser = PDFParser()
    parser.parseFile(xdatname)
    xprofile.loadParsedData(parser)
    xprofile.setCalculationRange(xmax = 20)

    parser = PDFParser()
    parser.parseFile(ndatname)
    nprofile.loadParsedData(parser)
    nprofile.setCalculationRange(xmax = 20)

    ## The ProfileGenerators
    # We need one of these for the x-ray data.
    xgenerator = PDFGenerator("G")
    stru = loadCrystal(ciffile)
    xgenerator.setStructure(stru)

    # And we need one for the neutron data. We want to refine the same
    # structure object in each PDFGenerator. This would suggest that we add the
    # same Crystal to each. However, if we do that then we will have two
    # Parameters for each Crystal data member (two Parameters for the "a"
    # lattice parameter, etc.), held in different ObjCrystCrystalParSets, each
    # managed by its own PDFGenerator. Thus, changes made to the Crystal
    # through one PDFGenerator will not be known to the other PDFGenerator
    # since their ObjCrystCrystalParSets don't know about each other. The
    # solution is to share ObjCrystCrystalParSets rather than Crystals. This
    # way there is only one Parameter for each Crystal data member. (An
    # alternative to this is to constrain each structure Parameter to be varied
    # to the same variable. The present approach is easier and less error
    # prone.)
    #
    # Tell the neutron PDFGenerator to use the phase from the x-ray
    # PDFGenerator.
    ngenerator = PDFGenerator("G")
    ngenerator.setPhase(xgenerator.phase)

    ## The FitContributions
    # We associate the x-ray PDFGenerator and Profile in one FitContribution...
    xcontribution = FitContribution("xnickel")
    xcontribution.addProfileGenerator(xgenerator)
    xcontribution.setProfile(xprofile, xname = "r")
    # and the neutron objects in another.
    ncontribution = FitContribution("nnickel")
    ncontribution.addProfileGenerator(ngenerator)
    ncontribution.setProfile(nprofile, xname = "r")

    # This example is different than the previous ones in that we are composing
    # a residual function from other residuals (one for the x-ray contribution
    # and one for the neutron contribution). The relative magnitude of these
    # residuals effectively determines the influence of each contribution over
    # the fit. This is a problem in this case because the x-ray data has
    # uncertainty values associated with it (on the order of 1e-4), and the
    # chi^2 residual is proportional to 1 / uncertainty**2. The neutron has no
    # uncertainty, so it's chi^2 is proportional to 1. Thus, my optimizing
    # chi^2 we would give the neutron data practically no weight in the fit. To
    # get around this, we will optimize a different metric.
    #
    # The contribution's residual can be either chi^2, Rw^2, or custom crafted.
    # In this case, we should minimize Rw^2 of each contribution so that each
    # one can contribute roughly equally to the fit.
    xcontribution.setResidualEquation("resv")
    ncontribution.setResidualEquation("resv")

    # Make the FitRecipe and add the FitContributions.
    recipe = FitRecipe()
    recipe.addContribution(xcontribution)
    recipe.addContribution(ncontribution)

    # Now we vary and constrain Parameters as before.
    recipe.addVar(xgenerator.scale, 1, "xscale")
    recipe.addVar(ngenerator.scale, 1, "nscale")
    recipe.addVar(xgenerator.qdamp, 0.01, "xqdamp")
    recipe.addVar(ngenerator.qdamp, 0.01, "nqdamp")
    # delta2 is a non-structual material propery. Thus, we constrain together
    # delta2 Parameter from each PDFGenerator.
    delta2 = recipe.newVar("delta2", 2)
    recipe.constrain(xgenerator.delta2, delta2)
    recipe.constrain(ngenerator.delta2, delta2)

    # We only need to constrain phase properties once since there is a single
    # ObjCrystCrystalParSet for the Crystal.
    phase = xgenerator.phase
    for par in phase.sgpars:
        recipe.addVar(par)
    recipe.B11_0 = 0.1

    # Give the recipe away so it can be used!
    return recipe
def main():
    """
    This will run by default when the file is executed using
    "python file.py" in the command line

    Parameters
    ----------
    None

    Returns
    ----------
    None
    """

    # Make some folders to store our output files.
    resdir = "res"
    fitdir = "fit"
    figdir = "fig"

    folders = [resdir, fitdir, figdir]

    # Let's define our working directory.
    base_dir = Path()

    yaml_file = base_dir / (FIT_ID_BASE + "refined_params.yml")

    # This is a bit different than what we've done before.
    # We are going to look at a set of temperatures, so we want
    # to find all the relevant data files in the "DPATH" folder
    # we identified earlier which match a certain pattern.

    # To do this we will use list comprehension

    # For every file we find, we will link it up with the data path
    data_files = list(DPATH.glob(f"*{GR_NAME_BASE}*.gr"))

    # We now want to grab the temperature at which each file was measured.
    # We again use list comprehension, and we re-use the variable "temp"
    # This specific procedure depends on how the file is named.
    # In our case, we carefully named each file as:
    # "composition_TTTK.gr" where TTT is the temperature in Kelvin.

    # First we strip off the directory information for every file in "data_files"
    # This gives us a list of just full file names, without directories.
    temps = [f.stem for f in data_files]

    # Next we split every base filename into a list, delimited by "_"
    # We keep the second entry from this list, because we know it has the
    # temperature in the form TTTK.
    temps = [t.split('_')[1] for t in temps]

    # We want the temperature as an integer, so we need to drop the "K"
    # from each string and cast the values as integers using "int()"
    # Strings can be slides like arrays, so "[:-1]" means "take all the
    # values except the last one."
    temps = [int(t[:-1]) for t in temps]

    # This will sort the data files and temperatures in descending order
    # based on the temperature values.
    temps, data_files = zip(*sorted(zip(temps, data_files), reverse=True))

    # We want to test two structure models, so we should find both the cif files.
    # Similar to how we found the data files, we use list comprehension

    # For every file we find, we will link it up with the data path
    cif_files = list(DPATH.glob(f"*{CIF_NAME_BASE}*.cif"))

    # We initialize and empty dictionary, where we will save all
    # the details of the refined parameters.
    if yaml_file.exists():
        print(f"\n{yaml_file.name} exists, loading!\n")
        with open(yaml_file, 'r') as infile:
            refined_dict = yaml.safe_load(infile)
    else:
        print(f"\n{yaml_file.name} does not exist, creating!\n")
        refined_dict = dict()

    # We want to do a separate temperature series on each of the structures,
    # so we will use a loop on all the cif files we found.
    for cif in cif_files:

        # Let's get the space group, so we can refer to it later.
        p_cif = getParser('cif')
        p_cif.parseFile(str(cif))
        space_group = p_cif.spacegroup.short_name

        # Backslashes are bad form, so let's remove them...
        structure_string = space_group.replace("/", "_on_")

        # Lets check if we already ran this fit...so we dont duplicate work
        if structure_string not in refined_dict:
            print(f"\n{structure_string} IS NOT in dictionary!\n")
            # Nest a dictionary inside "refined_dict" with a key defined by "structure_string"
            refined_dict[structure_string] = dict()

            # This is just for ease of coding/readability
            sg_dict = refined_dict[structure_string]
            done = False
        elif structure_string in refined_dict:
            print(f"\n{structure_string} IS IN dictionary!\n")
            sg_dict = refined_dict[structure_string]
            done = True

        # Where will we work? here!
        work_dir = base_dir / structure_string

        # Make our folders!
        for folder in folders:
            new_folder = work_dir / folder
            if not new_folder.exists():
                new_folder.mkdir(parents=True)

        # Make a recipe based on this cif file, and the first data file in
        # "data_files"
        # We can pass in any data file at this point, this is only to initialize
        # the recipe, and we will replace this data before refining.
        recipe = makerecipe(cif, data_files[0])

        # Let's set the calculation range!
        recipe.crystal.profile.setCalculationRange(xmin=PDF_RMIN,
                                                   xmax=PDF_RMAX,
                                                   dx=PDF_RSTEP)

        # Initialize the instrument parameters, Q_damp and Q_broad, and
        # assign Q_max and Q_min.
        recipe.crystal.G1.qdamp.value = QDAMP_I
        recipe.crystal.G1.qbroad.value = QBROAD_I
        recipe.crystal.G1.setQmax(QMAX)
        recipe.crystal.G1.setQmin(QMIN)

        # Add, initialize, and tag the scale variable.
        recipe.addVar(recipe.crystal.s1, SCALE_I, tag="scale")

        # Use the srfit function constrainAsSpaceGroup to constrain
        # the lattice and ADP parameters according to the space group setting,
        # in this case, contained in the function argument "sg"
        #
        from diffpy.srfit.structure import constrainAsSpaceGroup

        spacegroupparams = constrainAsSpaceGroup(recipe.crystal.G1.phase,
                                                 space_group)

        for par in spacegroupparams.latpars:
            recipe.addVar(par, fixed=False, tag="lat")

        for par in spacegroupparams.adppars:
            recipe.addVar(par, fixed=False, tag="adp")

        # Note: here we also can refine atomic coordinates.
        # In our previous examples, all the atoms were on symmetry
        # operators, so their positions could not be refined.
        for par in spacegroupparams.xyzpars:
            recipe.addVar(par, fixed=False, tag="xyz")

        # Add delta, but not instrumental parameters to Fit Recipe.
        recipe.addVar(recipe.crystal.G1.delta2,
                      name="Delta2",
                      value=DELTA1_I,
                      tag="d2")

        # Tell the Fit Recipe we want to write the maximum amount of
        # information to the terminal during fitting.
        recipe.fithooks[0].verbose = 0

        # As we are doing a temperature series through a phase transition, we want to fit many
        # different data sets, each at a different temperature.
        # for this we will loop over both "temps" and "data_files"
        for file, temp in zip(data_files, temps):
            print(f"\nProcessing {file.name}!\n")

            if temp not in sg_dict:
                print(
                    f"\nT = {temp} K NOT IN {structure_string} dictionary, creating!\n"
                )
                # Nest a dictionary inside the nested dictionary "refined_dict[stru_type]"
                # with a key defined by "temp"
                sg_dict[temp] = dict()
                temp_dict = sg_dict[temp]
                done = False
            elif temp in sg_dict:
                print(f"\nT = {temp} K IS IN {structure_string} dictionary!\n")
                temp_dict = sg_dict[temp]
                done = True

            # We create a unique string to identify our fit,
            # using the structure type and the temperature.
            # This will be used when we write files later.
            basename = f"{FIT_ID_BASE}{structure_string}_{str(temp)}_K"

            # Print the basename to the terminal.
            print(f"\nWorking on {basename}!\n")

            # We now want to load in the proper dataset for the given temperature.
            # We do this by creating a new profile object and loading it into the fit recipe.
            profile = Profile()
            parser = PDFParser()
            parser.parseFile(file)
            profile.loadParsedData(parser)
            recipe.crystal.setProfile(profile)
            if not done:
                print(
                    f"{basename} is NOT DONE with structure {structure_string} at T = {temp} K\n"
                )

                # We are now ready to start the refinement.
                # During the optimization, fix and free parameters as you would
                # PDFgui. This leads to more stability in the refinement
                recipe.fix("all")

                refine_params = ["scale", "lat", "adp", "d2", "all"]

                for params in refine_params:
                    recipe.free(params)
                    print(f"\n****\nFitting {recipe.getNames()} against "
                          f"{file.name} with {cif.name}\n")
                    least_squares(recipe.residual,
                                  recipe.values,
                                  x_scale="jac")

            elif done:

                print(
                    f"{basename} IS done with structure {structure_string} at T = {temp} K\n"
                )

                recipe.free("all")

                print("\nLoading parameters...\n")
                for var in recipe.getNames():
                    if var not in temp_dict:
                        print(
                            f"{var} is not in the dictionary!! Let's try to fix it..."
                        )
                        recipe.fix("all")
                        recipe.free(var)
                        print(f"\nFitting {recipe.getNames()}\n")
                        least_squares(recipe.residual,
                                      recipe.values,
                                      x_scale="jac")
                        recipe.free("all")
                    elif var in temp_dict:
                        var_dict = temp_dict[var]
                        val = var_dict["value"]
                        recipe.get(var).setValue(val)
                if not SKIP_DONE:
                    print("\nPolishing...\n")
                    recipe.free("all")
                    print(f"\nFitting {recipe.getNames()}\n")
                    least_squares(recipe.residual,
                                  recipe.values,
                                  x_scale="jac")

                    print("\nPolishing done\n")
            if (done and not SKIP_DONE) or not done or REPLOT_DONE:
                print(f"\nStarting to write results for {basename}"
                      f" with structure {structure_string} at T = {temp} K\n")
                # Print the fit results to the terminal.
                res = FitResults(recipe)
                print("\n******************\n")
                res.printResults()
                print("\n******************\n")
                rw = res.rw

                # Write the fitted data to a file.
                profile = recipe.crystal.profile
                profile.savetxt(work_dir / fitdir / (basename + ".fit"))

                # Now, as we will use the optimized fit recipe in the next loop,
                # we want to keep all the refined parameters for this part of the loop
                # we do this by recording everything in the nested dictionaries we made earlier.

                # We loop over the variable names, the variable values, and the variable uncertainties (esd)
                for name, val, unc in zip(res.varnames, res.varvals,
                                          res.varunc):

                    # We create a new nested dictionary based on each variable name
                    if name not in temp_dict:
                        temp_dict[name] = dict()
                    var_dict = temp_dict[name]
                    # We store the refined value for this variable using the "value" key.
                    # We use the ".item()" method because "res.varvals" exist as
                    # numpy.float64 objects, and we want them as regular python floats.
                    var_dict["value"] = val.item()
                    var_dict["uncert"] = unc.item()

                # We also store the fit rw, for posterity.
                temp_dict['rw'] = rw.item()

                # Write the fit results to a file.
                header = "crystal_HF.\n"
                res.saveResults(work_dir / resdir / (basename + ".res"),
                                header=header)

                # Write a plot of the fit to a (pdf) file.
                plotresults(recipe, work_dir / figdir / basename)
                # plt.ion()

                # We now write this dictionary to a file for later use.
                with open(yaml_file, 'w') as outfile:
                    yaml.safe_dump(refined_dict, outfile)

    # We now write this dictionary to a file for later use.
    with open(yaml_file, 'w') as outfile:
        yaml.safe_dump(refined_dict, outfile)