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_contribution(config: ConConfig) -> FitContribution: """ Make a FitContribution according to the ConConfig. Parameters ---------- config : ConConfig The configuration instance for the FitContribution. Returns ------- contribution : FitContribution The FitContribution built from ConConfig. """ contribution = FitContribution(config.name) fit_range = config.fit_range profile = make_profile(config.data_file, fit_range) contribution.setProfile(profile, xname="r") for phase in config.phases: generator = make_generator(phase) generator.qdamp.value = config.qparams[0] generator.qbroad.value = config.qparams[1] contribution.addProfileGenerator(generator) for base_line in config.base_lines: contribution.addProfileGenerator(base_line) for function in config.functions: name = function.name func_type = function.func_type argnames = function.argnames contribution.registerFunction(func_type, name, argnames) contribution.setEquation(config.eq) contribution.setResidualEquation(config.res_eq) return contribution
def makeRecipe(): """Make the recipe for the fit. The instructions for what we want to refine, and how to refine it will be defined within a FitRecipe instance. The job of a FitRecipe is to collect and associate all the data, the fitting equations, fitting variables, constraints and restrations. We will demonstrate each of these within the code. Data is held within a Profile object. The Profile is simply a container that holds the data, and the theoretical profile once it has been calculated. Data is associated with a fitting equation within a FitContribution. The FitContribution defines the equation and parameters that will be adjusted to fit the data. The fitting equation can be defined within a function or optionally within the ProfileGenerator class. We won't need the ProfileGenerator class in this example since the signature of the fitting equation (the 'debye' function defined below) is so simple. The FitContribution also defines the residual function to optimize for the data/equation pair. This can be modified, but we won't do that here. """ ## The Profile # Create a Profile to hold the experimental and calculated signal. profile = Profile() # Load data and add it to the profile. It is our responsibility to get our # data into the profile. xydy = numpy.array(map(float, data.split()), dtype=float).reshape(-1,3) x, y, dy = numpy.hsplit(xydy, 3) profile.setObservedProfile(x, y, dy) ## The FitContribution # The FitContribution associates the profile with the Debye function. contribution = FitContribution("pb") # Tell the contribution about the Profile. We will need to use the # independent variable (the temperature) from the data to calculate the # theoretical signal, so give it an informative name ('T') that we can use # later. contribution.setProfile(profile, xname="T") # We now need to create the fitting equation. We tell the FitContribution # to use the 'debye' function defined below. The 'registerFunction' method # will let us do this. Since we haven't told it otherwise, # 'registerFunction' will extract the name of the function ('debye') and # the names of the arguments ('T', 'm', 'thetaD'). These arguments will # become Parameters of the FitContribution. Since we named the x-variable # 'T' above, the 'T' in the 'debye' equation will refer to this x-variable # whenever it is used. contribution.registerFunction(debye) # Now we can create the fitting equation. We want to extend the 'debye' # equation by adding a vertical offset. We could wrap 'debye' in a new # function with an offset, and register that instead of 'debye', but what # we do here is easier. # # When we set the fitting equation, we do not need to specify the # Parameters to the 'debye' function since the FitContribution already # knows what they are. If we choose to specify the arguments, we can make # adjustments to their input values. We wish to have the thetaD value in # the debye equation to be positive, so we specify the input as abs(thetaD) # in the equation below. Furthermore, we know 'm', the mass of lead, so we # can specify that as well. contribution.setEquation("debye(T, 207.2, abs(thetaD)) + offset") ## The FitRecipe # The FitRecipe lets us define what we want to fit. It is where we can # create variables, constraints and restraints. If we had multiple profiles # to fit simultaneously, the contribution from each could be added to the # recipe. recipe = FitRecipe() recipe.addContribution(contribution) # Specify which Parameters we want to refine. # Vary the offset recipe.addVar(contribution.offset, 0) # We also vary the Debye temperature. recipe.addVar(contribution.thetaD, 100) # We would like to 'suggest' that the offset should remain positive. This # is somethine that we know about the system that might help the refinement # converge to a physically reasonable result. We will do this with a soft # contraint, or restraint. Here we restrain the offset variable to between # 0 and infinity. We tell the recipe that we want to scale the penalty for # breaking the restraint by the point-average chi^2 value so that the # restraint is roughly as significant as any other data point throughout # the fit. recipe.restrain(recipe.offset, lb = 0, scaled = True) # We're done setting up the recipe. We can now do other things with it. return recipe
def makeRecipe(strufile, datname): """Create a recipe that uses the IntensityGenerator. This will create a FitContribution that uses the IntensityGenerator, associate this with a Profile, and use this to define a FitRecipe. """ ## The Profile # Create a Profile. This will hold the experimental and calculated signal. profile = Profile() # Load data and add it to the profile x, y, u = profile.loadtxt(datname) ## The ProfileGenerator # Create an IntensityGenerator named "I". This will be the name we use to # refer to the generator from within the FitContribution equation. We also # need to load the model structure we're using. generator = IntensityGenerator("I") generator.setStructure(strufile) ## The FitContribution # Create a FitContribution, that will associate the Profile with the # ProfileGenerator. The ProfileGenerator will be accessible as an # attribute of the FitContribution by its name ("I"). We also want to tell # the FitContribution to name the x-variable of the profile "q", so we can # use it in equations with this name. contribution = FitContribution("bucky") contribution.addProfileGenerator(generator) contribution.setProfile(profile, xname = "q") # Now we're ready to define the fitting equation for the FitContribution. # We need to modify the intensity calculation, and we'll do that from # within the fitting equation for the sake of instruction. We want to # modify the calculation in three ways. We want to scale it, add a # polynomial background, and broaden the peaks. # # There is added benefit for defining these operations outside of the # IntensityGenerator. By combining the different parts of the calculation # within the fitting equation, the time-consuming iofq calculation is only # performed when a structural Parameter is changed. If only non-structural # parameters are changed, such as the background and broadening Parameters, # then then previously computed iofq value will be used to compute the # contribution equation. The benefit in this is very apparent when # refining the recipe with the LM optimizer, which only changes two # variables at a time most of the time. Note in the refinement output how # many times the residual is calculated, versus how many times iofq is # called when using the scipyOptimize function. # We will define the background as a string. bkgdstr = "b0 + b1*q + b2*q**2 + b3*q**3 + b4*q**4 + b5*q**5 + b6*q**6 +\ b7*q**7 + b8*q**8 + b9*q**9" # This creates a callable equation named "bkgd" within the FitContribution, # and turns the polynomial coefficients into Parameters. eq = contribution.registerStringFunction(bkgdstr, "bkgd") # We will create the broadening function that we need by creating a python # function and registering it with the FitContribution. pi = numpy.pi exp = numpy.exp def gaussian(q, q0, width): return 1/(2*pi*width**2)**0.5 * exp(-0.5 * ((q-q0)/width)**2) # This registers the python function and extracts the name and creates # Parameters from the arguments. contribution.registerFunction(gaussian) # Center the Gaussian so it is not truncated. contribution.q0.value = x[len(x)/2] # Now we can incorporate the scale and bkgd into our calculation. We also # convolve the signal with the Gaussian to broaden it. Recall that we don't # need to supply arguments to the registered functions unless we want to # make changes to their input values. contribution.setEquation("scale * convolve(I, gaussian) + bkgd") # Make the FitRecipe and add the FitContribution. recipe = FitRecipe() recipe.addContribution(contribution) # Specify which parameters we want to refine. recipe.addVar(contribution.b0, 0) recipe.addVar(contribution.b1, 0) recipe.addVar(contribution.b2, 0) recipe.addVar(contribution.b3, 0) recipe.addVar(contribution.b4, 0) recipe.addVar(contribution.b5, 0) recipe.addVar(contribution.b6, 0) recipe.addVar(contribution.b7, 0) recipe.addVar(contribution.b8, 0) recipe.addVar(contribution.b9, 0) # We also want to adjust the scale and the convolution width recipe.addVar(contribution.scale, 1) recipe.addVar(contribution.width, 0.1) # We can also refine structural parameters. Here we extract the # DiffpyStructureParSet from the intensity generator and use the parameters # like we would any others. phase = generator.phase # We want to allow for isotropic expansion, so we'll constrain the lattice # parameters to the same value (the lattice is cubic). Note that we # constrain to the "a" Parameter directly. In previous examples, we # constrained to a Variable by name. This has the same effect. lattice = phase.getLattice() a = lattice.a recipe.addVar(a) recipe.constrain(lattice.b, a) recipe.constrain(lattice.c, a) # We want to refine the thermal paramters as well. We will add a new # Variable that we call "Uiso" and constrain the atomic Uiso values to # this. Note that we don't give Uiso an initial value. The initial value # will be inferred from the following constraints. Uiso = recipe.newVar("Uiso") for atom in phase.getScatterers(): recipe.constrain(atom.Uiso, Uiso) # Give the recipe away so it can be used! return recipe
def makeRecipe(strufile, datname): """Create a recipe that uses the IntensityGenerator. This will create a FitContribution that uses the IntensityGenerator, associate this with a Profile, and use this to define a FitRecipe. """ ## The Profile # Create a Profile. This will hold the experimental and calculated signal. profile = Profile() # Load data and add it to the profile x, y, u = profile.loadtxt(datname) ## The ProfileGenerator # Create an IntensityGenerator named "I". This will be the name we use to # refer to the generator from within the FitContribution equation. We also # need to load the model structure we're using. generator = IntensityGenerator("I") generator.setStructure(strufile) ## The FitContribution # Create a FitContribution, that will associate the Profile with the # ProfileGenerator. The ProfileGenerator will be accessible as an # attribute of the FitContribution by its name ("I"). We also want to tell # the FitContribution to name the x-variable of the profile "q", so we can # use it in equations with this name. contribution = FitContribution("bucky") contribution.addProfileGenerator(generator) contribution.setProfile(profile, xname = "q") # Now we're ready to define the fitting equation for the FitContribution. # We need to modify the intensity calculation, and we'll do that from # within the fitting equation for the sake of instruction. We want to # modify the calculation in three ways. We want to scale it, add a # polynomial background, and broaden the peaks. # # There is added benefit for defining these operations outside of the # IntensityGenerator. By combining the different parts of the calculation # within the fitting equation, the time-consuming iofq calculation is only # performed when a structural Parameter is changed. If only non-structural # parameters are changed, such as the background and broadening Parameters, # then then previously computed iofq value will be used to compute the # contribution equation. The benefit in this is very apparent when # refining the recipe with the LM optimizer, which only changes two # variables at a time most of the time. Note in the refinement output how # many times the residual is calculated, versus how many times iofq is # called when using the scipyOptimize function. # We will define the background as a string. bkgdstr = "b0 + b1*q + b2*q**2 + b3*q**3 + b4*q**4 + b5*q**5 + b6*q**6 +\ b7*q**7 + b8*q**8 + b9*q**9" # This creates a callable equation named "bkgd" within the FitContribution, # and turns the polynomial coefficients into Parameters. eq = contribution.registerStringFunction(bkgdstr, "bkgd") # We will create the broadening function that we need by creating a python # function and registering it with the FitContribution. pi = numpy.pi exp = numpy.exp def gaussian(q, q0, width): return 1/(2*pi*width**2)**0.5 * exp(-0.5 * ((q-q0)/width)**2) # This registers the python function and extracts the name and creates # Parameters from the arguments. contribution.registerFunction(gaussian) # Center the Gaussian so it is not truncated. contribution.q0.value = x[len(x)/2] # Now we can incorporate the scale and bkgd into our calculation. We also # convolve the signal with the Gaussian to broaden it. Recall that we don't # need to supply arguments to the registered functions unless we want to # make changes to their input values. contribution.setEquation("scale * convolve(I, gaussian) + bkgd") # Make the FitRecipe and add the FitContribution. recipe = FitRecipe() recipe.addContribution(contribution) # Specify which parameters we want to refine. recipe.addVar(contribution.b0, 0) recipe.addVar(contribution.b1, 0) recipe.addVar(contribution.b2, 0) recipe.addVar(contribution.b3, 0) recipe.addVar(contribution.b4, 0) recipe.addVar(contribution.b5, 0) recipe.addVar(contribution.b6, 0) recipe.addVar(contribution.b7, 0) recipe.addVar(contribution.b8, 0) recipe.addVar(contribution.b9, 0) # We also want to adjust the scale and the convolution width recipe.addVar(contribution.scale, 1) recipe.addVar(contribution.width, 0.1) # We can also refine structural parameters. Here we extract the # DiffpyStructureParSet from the intensity generator and use the parameters # like we would any others. phase = generator.phase # We want to allow for isotropic expansion, so we'll constrain the lattice # parameters to the same value (the lattice is cubic). Note that we # constrain to the "a" Parameter directly. In previous examples, we # constrained to a Variable by name. This has the same effect. lattice = phase.getLattice() a = lattice.a recipe.addVar(a) recipe.constrain(lattice.b, a) recipe.constrain(lattice.c, a) # We want to refine the thermal parameters as well. We will add a new # Variable that we call "Uiso" and constrain the atomic Uiso values to # this. Note that we don't give Uiso an initial value. The initial value # will be inferred from the following constraints. Uiso = recipe.newVar("Uiso") for atom in phase.getScatterers(): recipe.constrain(atom.Uiso, Uiso) # Give the recipe away so it can be used! return recipe
plt.show() ### NOW DO THE CO-REFINEMENT OF STRUCTURAL AND MAGNETIC PDF def mpdf(parascale, ordscale): mc.paraScale = parascale mc.ordScale = ordscale mc.magstruc.makeAtoms() mc.magstruc.makeSpins() rv = mc.calc(both=True)[2] return rv # Add the mPDF to the totpdf FitContribution totpdf.registerFunction(mpdf) totpdf.setEquation("nucscale * nucpdf + mpdf(parascale, ordscale)") # Make magnetic PDF depend on any changes to the atomic structure. # Cover your eyes, but a structure change will now trigger the same # reevaluations as if ordscale were modified. nucpdf.phase.addObserver(totpdf.ordscale.notify) # add the mPDF variables mnofit.addVar(totpdf.parascale, value=mc.paraScale) mnofit.addVar(totpdf.ordscale, value=mc.ordScale) # do the co-refinement print("Refine PDF using scipy's least-squares optimizer:") print(" variables:", mnofit.names) print(" initial values:", mnofit.values)
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(): """Make a FitRecipe for fitting three double-gaussian curves to data. The separation and amplitude ratio of the double peaks follows a specific relationship. The peaks are broadend according to their position and they sit on top of a background. We are seeking the absolute locations of the peaks as well as their amplitudes. The independent variable is t. The relationship between the double peaks is sin(t2) / l2 = sin(t1) / l1 amplitude(peak2) = r * amplitude(peak1) The values of l1, l2 and r come from experiment. For this example, we use l1 = 1.012, l2 = 1.0 and r = 0.23. """ ## The Profile # Create a Profile to hold the experimental and calculated signal. profile = Profile() x, y, dy = profile.loadtxt("data/threedoublepeaks.dat") # Create the contribution contribution = FitContribution("peaks") contribution.setProfile(profile, xname = "t") pi = numpy.pi exp = numpy.exp # This is a building-block of our profile function def gaussian(t, mu, sig): return 1/(2*pi*sig**2)**0.5 * exp(-0.5 * ((t-mu)/sig)**2) contribution.registerFunction(gaussian, name = "peakshape") def delta(t, mu): """Calculate a delta-function. We don't have perfect precision, so we must make this a very thin Gaussian. """ sig = t[1] - t[0] return gaussian(t, mu, sig) contribution.registerFunction(delta) # Here is another one bkgdstr = "b0 + b1*t + b2*t**2 + b3*t**3 + b4*t**4 + b5*t**5 + b6*t**6" contribution.registerStringFunction(bkgdstr, "bkgd") # Now define our fitting equation. We will hardcode the peak ratios. contribution.setEquation( "A1 * ( convolve( delta(t, mu11), peakshape(t, c, sig11) ) \ + 0.23*convolve( delta(t, mu12), peakshape(t, c, sig12) ) ) + \ A2 * ( convolve( delta(t, mu21), peakshape(t, c, sig21) ) \ + 0.23*convolve( delta(t, mu22), peakshape(t, c, sig22) ) ) + \ A3 * ( convolve( delta(t, mu31), peakshape(t, c, sig31) ) \ + 0.23*convolve( delta(t, mu32), peakshape(t, c, sig32) ) ) + \ bkgd") # c is the center of the gaussian. contribution.c.value = x[len(x)/2] ## The FitRecipe # The FitRecipe lets us define what we want to fit. It is where we can # create variables, constraints and restraints. recipe = FitRecipe() # Here we tell the FitRecipe to use our FitContribution. When the FitRecipe # calculates its residual function, it will call on the FitContribution to # do part of the work. recipe.addContribution(contribution) # Vary the amplitudes for each double peak recipe.addVar(contribution.A1, 100) recipe.addVar(contribution.A2, 100) recipe.addVar(contribution.A3, 100) # Vary the position of the first of the double peaks recipe.addVar(contribution.mu11, 13.0) recipe.addVar(contribution.mu21, 24.0) recipe.addVar(contribution.mu31, 33.0) # Constrain the position of the second double peak from numpy import sin, arcsin def peakloc(mu): """Calculate the location of the second peak given the first.""" l1 = 1.012 l2 = 1.0 return 180 / pi * arcsin( pi / 180 * l2 * sin(mu) / l1 ) recipe.registerFunction(peakloc) recipe.constrain(contribution.mu12, "peakloc(mu11)") recipe.constrain(contribution.mu22, "peakloc(mu21)") recipe.constrain(contribution.mu32, "peakloc(mu31)") # Vary the width of the peaks. We know the functional form of the peak # broadening. sig0 = recipe.newVar("sig0", 0.001) dsig = recipe.newVar("dsig", 4) def sig(sig0, dsig, mu): """Calculate the peak broadening with respect to position.""" return sig0 * (1 - dsig * mu**2); recipe.registerFunction(sig) recipe.fix("mu") # Now constrain the peak widths to this recipe.sig0.value = 0.001 recipe.dsig.value = 4.0 recipe.constrain(contribution.sig11, "sig(sig0, dsig, mu11)") recipe.constrain(contribution.sig12, "sig(sig0, dsig, mu12)", ns = {"mu12" : contribution.mu12} ) recipe.constrain(contribution.sig21, "sig(sig0, dsig, mu21)") recipe.constrain(contribution.sig22, "sig(sig0, dsig, mu22)", ns = {"mu22" : contribution.mu22} ) recipe.constrain(contribution.sig31, "sig(sig0, dsig, mu31)") recipe.constrain(contribution.sig32, "sig(sig0, dsig, mu32)", ns = {"mu32" : contribution.mu32} ) # Also the background recipe.addVar(contribution.b0, 0, tag = "bkgd") recipe.addVar(contribution.b1, 0, tag = "bkgd") recipe.addVar(contribution.b2, 0, tag = "bkgd") recipe.addVar(contribution.b3, 0, tag = "bkgd") recipe.addVar(contribution.b4, 0, tag = "bkgd") recipe.addVar(contribution.b5, 0, tag = "bkgd") recipe.addVar(contribution.b6, 0, tag = "bkgd") return recipe
def makeRecipe(strufile, datname1, datname2): """Create a recipe that uses the IntensityGenerator. We will create two FitContributions that use the IntensityGenerator from npintensitygenerator.py and associate each of these with a Profile, and use this to define a FitRecipe. Both simulated data sets come from the same structure. We're going to make two FitContributions that are identical, except for the profile that is held in each. We're going to assure that the structures are identical by using the same DiffpyStructureParSet (which is generated by the IntensityGenerator when we load the structure) in both generators. """ ## The Profiles # Create two Profiles for the two FitContributions. profile1 = Profile() profile2 = Profile() # Load data into the Profiles profile1.loadtxt(datname1) x, y, u = profile2.loadtxt(datname2) ## The ProfileGenerators # Create two IntensityGenerators named "I". There will not be a name # conflict, since the name is only meaningful within the FitContribution # that holds the ProfileGenerator. Load the structure into one and make # sure that the second ProfileGenerator is using the same # DiffyStructureParSet. This will assure that both ProfileGenerators are # using the exact same Parameters, and underlying Structure object in the # calculation of the profile. generator1 = IntensityGenerator("I") generator1.setStructure(strufile) generator2 = IntensityGenerator("I") generator2.addParameterSet(generator1.phase) ## The FitContributions # Create the FitContributions. contribution1 = FitContribution("bucky1") contribution1.addProfileGenerator(generator1) contribution1.setProfile(profile1, xname = "q") contribution2 = FitContribution("bucky2") contribution2.addProfileGenerator(generator2) contribution2.setProfile(profile2, xname = "q") # Now we're ready to define the fitting equation for each FitContribution. # The functions registered below will be independent, even though they take # the same form and use the same Parameter names. By default, Parameters # in different contributions are different Parameters even if they have the # same names. FitContributions are isolated namespaces than only share # information if you tell them to by using addParameter or addParameterSet. bkgdstr = "b0 + b1*q + b2*q**2 + b3*q**3 + b4*q**4 + b5*q**5 + b6*q**6 +\ b7*q**7 +b8*q**8 + b9*q**9" contribution1.registerStringFunction(bkgdstr, "bkgd") contribution2.registerStringFunction(bkgdstr, "bkgd") # We will create the broadening function by registering a python function. pi = numpy.pi exp = numpy.exp def gaussian(q, q0, width): return 1/(2*pi*width**2)**0.5 * exp(-0.5 * ((q-q0)/width)**2) contribution1.registerFunction(gaussian) contribution2.registerFunction(gaussian) # Center the gaussian contribution1.q0.value = x[len(x) // 2] contribution2.q0.value = x[len(x) // 2] # Now we can incorporate the scale and bkgd into our calculation. We also # convolve the signal with the gaussian to broaden it. contribution1.setEquation("scale * convolve(I, gaussian) + bkgd") contribution2.setEquation("scale * convolve(I, gaussian) + bkgd") # Make a FitRecipe and associate the FitContributions. recipe = FitRecipe() recipe.addContribution(contribution1) recipe.addContribution(contribution2) # Specify which Parameters we want to refine. We want to refine the # background that we just defined in the FitContributions. We have to do # this separately for each FitContribution. We tag the variables so it is # easy to retrieve the background variables. recipe.addVar(contribution1.b0, 0, name = "b1_0", tag = "bcoeffs1") recipe.addVar(contribution1.b1, 0, name = "b1_1", tag = "bcoeffs1") recipe.addVar(contribution1.b2, 0, name = "b1_2", tag = "bcoeffs1") recipe.addVar(contribution1.b3, 0, name = "b1_3", tag = "bcoeffs1") recipe.addVar(contribution1.b4, 0, name = "b1_4", tag = "bcoeffs1") recipe.addVar(contribution1.b5, 0, name = "b1_5", tag = "bcoeffs1") recipe.addVar(contribution1.b6, 0, name = "b1_6", tag = "bcoeffs1") recipe.addVar(contribution1.b7, 0, name = "b1_7", tag = "bcoeffs1") recipe.addVar(contribution1.b8, 0, name = "b1_8", tag = "bcoeffs1") recipe.addVar(contribution1.b9, 0, name = "b1_9", tag = "bcoeffs1") recipe.addVar(contribution2.b0, 0, name = "b2_0", tag = "bcoeffs2") recipe.addVar(contribution2.b1, 0, name = "b2_1", tag = "bcoeffs2") recipe.addVar(contribution2.b2, 0, name = "b2_2", tag = "bcoeffs2") recipe.addVar(contribution2.b3, 0, name = "b2_3", tag = "bcoeffs2") recipe.addVar(contribution2.b4, 0, name = "b2_4", tag = "bcoeffs2") recipe.addVar(contribution2.b5, 0, name = "b2_5", tag = "bcoeffs2") recipe.addVar(contribution2.b6, 0, name = "b2_6", tag = "bcoeffs2") recipe.addVar(contribution2.b7, 0, name = "b2_7", tag = "bcoeffs2") recipe.addVar(contribution2.b8, 0, name = "b2_8", tag = "bcoeffs2") recipe.addVar(contribution2.b9, 0, name = "b2_9", tag = "bcoeffs2") # We also want to adjust the scale and the convolution width recipe.addVar(contribution1.scale, 1, name = "scale1") recipe.addVar(contribution1.width, 0.1, name = "width1") recipe.addVar(contribution2.scale, 1, name = "scale2") recipe.addVar(contribution2.width, 0.1, name = "width2") # We can also refine structural parameters. We only have to do this once, # since each generator holds the same DiffpyStructureParSet. phase = generator1.phase lattice = phase.getLattice() a = recipe.addVar(lattice.a) # We want to allow for isotropic expansion, so we'll make constraints for # that. recipe.constrain(lattice.b, a) recipe.constrain(lattice.c, a) # We want to refine the thermal parameters as well. We will add a new # variable that we call "Uiso" and constrain the atomic Uiso values to # this. Note that we don't give Uiso an initial value. The initial value # will be inferred from the subsequent constraints. Uiso = recipe.newVar("Uiso") for atom in phase.getScatterers(): recipe.constrain(atom.Uiso, Uiso) # Give the recipe away so it can be used! return recipe
def makeRecipe(strufile, datname1, datname2): """Create a recipe that uses the IntensityGenerator. We will create two FitContributions that use the IntensityGenerator from npintensitygenerator.py and associate each of these with a Profile, and use this to define a FitRecipe. Both simulated data sets come from the same structure. We're going to make two FitContributions that are identical, except for the profile that is held in each. We're going to assure that the structures are identical by using the same DiffpyStructureParSet (which is generated by the IntensityGenerator when we load the structure) in both generators. """ ## The Profiles # Create two Profiles for the two FitContributions. profile1 = Profile() profile2 = Profile() # Load data into the Profiles profile1.loadtxt(datname1) x, y, u = profile2.loadtxt(datname2) ## The ProfileGenerators # Create two IntensityGenerators named "I". There will not be a name # conflict, since the name is only meaningful within the FitContribution # that holds the ProfileGenerator. Load the structure into one and make # sure that the second ProfileGenerator is using the same # DiffyStructureParSet. This will assure that both ProfileGenerators are # using the exact same Parameters, and underlying Structure object in the # calculation of the profile. generator1 = IntensityGenerator("I") generator1.setStructure(strufile) generator2 = IntensityGenerator("I") generator2.addParameterSet(generator1.phase) ## The FitContributions # Create the FitContributions. contribution1 = FitContribution("bucky1") contribution1.addProfileGenerator(generator1) contribution1.setProfile(profile1, xname="q") contribution2 = FitContribution("bucky2") contribution2.addProfileGenerator(generator2) contribution2.setProfile(profile2, xname="q") # Now we're ready to define the fitting equation for each FitContribution. # The functions registered below will be independent, even though they take # the same form and use the same Parameter names. By default, Parameters # in different contributions are different Parameters even if they have the # same names. FitContributions are isolated namespaces than only share # information if you tell them to by using addParameter or addParameterSet. bkgdstr = "b0 + b1*q + b2*q**2 + b3*q**3 + b4*q**4 + b5*q**5 + b6*q**6 +\ b7*q**7 +b8*q**8 + b9*q**9" contribution1.registerStringFunction(bkgdstr, "bkgd") contribution2.registerStringFunction(bkgdstr, "bkgd") # We will create the broadening function by registering a python function. pi = numpy.pi exp = numpy.exp def gaussian(q, q0, width): return 1 / (2 * pi * width**2)**0.5 * exp(-0.5 * ((q - q0) / width)**2) contribution1.registerFunction(gaussian) contribution2.registerFunction(gaussian) # Center the gaussian contribution1.q0.value = x[len(x) / 2] contribution2.q0.value = x[len(x) / 2] # Now we can incorporate the scale and bkgd into our calculation. We also # convolve the signal with the gaussian to broaden it. contribution1.setEquation("scale * convolve(I, gaussian) + bkgd") contribution2.setEquation("scale * convolve(I, gaussian) + bkgd") # Make a FitRecipe and associate the FitContributions. recipe = FitRecipe() recipe.addContribution(contribution1) recipe.addContribution(contribution2) # Specify which Parameters we want to refine. We want to refine the # background that we just defined in the FitContributions. We have to do # this separately for each FitContribution. We tag the variables so it is # easy to retrieve the background variables. recipe.addVar(contribution1.b0, 0, name="b1_0", tag="bcoeffs1") recipe.addVar(contribution1.b1, 0, name="b1_1", tag="bcoeffs1") recipe.addVar(contribution1.b2, 0, name="b1_2", tag="bcoeffs1") recipe.addVar(contribution1.b3, 0, name="b1_3", tag="bcoeffs1") recipe.addVar(contribution1.b4, 0, name="b1_4", tag="bcoeffs1") recipe.addVar(contribution1.b5, 0, name="b1_5", tag="bcoeffs1") recipe.addVar(contribution1.b6, 0, name="b1_6", tag="bcoeffs1") recipe.addVar(contribution1.b7, 0, name="b1_7", tag="bcoeffs1") recipe.addVar(contribution1.b8, 0, name="b1_8", tag="bcoeffs1") recipe.addVar(contribution1.b9, 0, name="b1_9", tag="bcoeffs1") recipe.addVar(contribution2.b0, 0, name="b2_0", tag="bcoeffs2") recipe.addVar(contribution2.b1, 0, name="b2_1", tag="bcoeffs2") recipe.addVar(contribution2.b2, 0, name="b2_2", tag="bcoeffs2") recipe.addVar(contribution2.b3, 0, name="b2_3", tag="bcoeffs2") recipe.addVar(contribution2.b4, 0, name="b2_4", tag="bcoeffs2") recipe.addVar(contribution2.b5, 0, name="b2_5", tag="bcoeffs2") recipe.addVar(contribution2.b6, 0, name="b2_6", tag="bcoeffs2") recipe.addVar(contribution2.b7, 0, name="b2_7", tag="bcoeffs2") recipe.addVar(contribution2.b8, 0, name="b2_8", tag="bcoeffs2") recipe.addVar(contribution2.b9, 0, name="b2_9", tag="bcoeffs2") # We also want to adjust the scale and the convolution width recipe.addVar(contribution1.scale, 1, name="scale1") recipe.addVar(contribution1.width, 0.1, name="width1") recipe.addVar(contribution2.scale, 1, name="scale2") recipe.addVar(contribution2.width, 0.1, name="width2") # We can also refine structural parameters. We only have to do this once, # since each generator holds the same DiffpyStructureParSet. phase = generator1.phase lattice = phase.getLattice() a = recipe.addVar(lattice.a) # We want to allow for isotropic expansion, so we'll make constraints for # that. recipe.constrain(lattice.b, a) recipe.constrain(lattice.c, a) # We want to refine the thermal parameters as well. We will add a new # variable that we call "Uiso" and constrain the atomic Uiso values to # this. Note that we don't give Uiso an initial value. The initial value # will be inferred from the subsequent constraints. Uiso = recipe.newVar("Uiso") for atom in phase.getScatterers(): recipe.constrain(atom.Uiso, Uiso) # Give the recipe away so it can be used! return recipe
rstep=rstep, gaussPeakWidth=0.2) def magpdf(parascale, ordscale): mc.paraScale = parascale mc.ordScale = ordscale mc.magstruc.makeAtoms() mc.magstruc.makeSpins() rv = mc.calc(both=True)[2] return rv totpdf = FitContribution('totpdf') totpdf.addProfileGenerator(nucpdf) totpdf.setProfile(profile) # Add mPDF to the FitContribution totpdf.registerFunction(magpdf) totpdf.setEquation("nucscale * nucpdf + magpdf(parascale, ordscale)") # Make magnetic PDF depend on any changes to the atomic structure. # Cover your eyes, but a structure change will now trigger the same # reevaluations as if ordscale were modified. nucpdf.phase.addObserver(totpdf.ordscale.notify) # The FitRecipe does the work of calculating the PDF with the fit variable # that we give it. mnofit = FitRecipe() # give the PDFContribution to the FitRecipe mnofit.addContribution(totpdf) # Configure the fit variables and give them to the recipe. We can use the
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