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
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
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
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
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
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
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
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
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
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
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
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
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
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
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(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
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
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(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
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
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
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)