예제 #1
0
 def test_getParser(self):
     """Test passing of eps keyword argument by getParser function.
     """
     pcif = getParser('cif', eps=1e-6)
     grph = pcif.parseFile(self.graphiteciffile)
     self.assertEqual(8, len(grph))
     self.assertTrue(all(a.label.startswith('C1') for a in grph[:2]))
     self.assertTrue(all(a.label.startswith('C2') for a in grph[2:]))
     pcif2 = getParser('cif')
     grph2 = pcif2.parseFile(self.graphiteciffile)
     self.assertEqual(4, len(grph2))
     return
예제 #2
0
 def test_getParser(self):
     """Test passing of eps keyword argument by getParser function.
     """
     pcif = getParser('cif', eps=1e-6)
     grph = pcif.parseFile(self.graphiteciffile)
     self.assertEqual(8, len(grph))
     self.assertTrue(all(a.label.startswith('C1') for a in grph[:2]))
     self.assertTrue(all(a.label.startswith('C2') for a in grph[2:]))
     pcif2 = getParser('cif')
     grph2 = pcif2.parseFile(self.graphiteciffile)
     self.assertEqual(4, len(grph2))
     return
예제 #3
0
    def _wrapParseMethod(self, method, *args, **kwargs):
        """A helper evaluator method.  Try the specified parse method with
        each registered structure parser and return the first successful
        resul.  Structure parsers that match structure file extension are
        tried first.

        Set format attribute to the detected file format.
        Return Structure instance, or raise StructureFormatError.
        """
        from diffpy.structure.parsers import getParser
        ofmts = self._getOrderedFormats()
        stru = None
        # try all parsers in sequence
        parsers_emsgs = []
        for fmt in ofmts:
            p = getParser(fmt, **self.pkw)
            try:
                pmethod = getattr(p, method)
                stru = pmethod(*args, **kwargs)
                self.format = fmt
                break
            except StructureFormatError as err:
                parsers_emsgs.append("%s: %s" % (fmt, err))
            except NotImplementedError:
                pass
        if stru is None:
            emsg = "\n".join([
                "Unknown or invalid structure format.",
                "Errors per each tested structure format:"
            ] + parsers_emsgs)
            raise StructureFormatError(emsg)
        self.__dict__.update(p.__dict__)
        return stru
예제 #4
0
    def read(self, filename, format='auto'):
        """Load structure from a file, any original data become lost.

        filename -- file to be loaded
        format   -- all structure formats are defined in parsers submodule,
                    when format == 'auto' all parsers are tried one by one

        Return instance of data Parser used to process file.  This
        can be inspected for information related to particular format.
        """
        import diffpy.structure
        import diffpy.structure.parsers
        getParser = diffpy.structure.parsers.getParser
        p = getParser(format)
        new_structure = p.parseFile(filename)
        # reinitialize data after successful parsing
        # avoid calling __init__ from a derived class
        Structure.__init__(self)
        if new_structure is not None:
            self.__dict__.update(new_structure.__dict__)
            self[:] = new_structure
        if not self.title:
            import os.path
            tailname = os.path.basename(filename)
            tailbase = os.path.splitext(tailname)[0]
            self.title = tailbase
        return p
예제 #5
0
    def _wrapParseMethod(self, method, *args, **kwargs):
        """A helper evaluator method.  Try the specified parse method with
        each registered structure parser and return the first successful
        resul.  Structure parsers that match structure file extension are
        tried first.

        Set format attribute to the detected file format.
        Return Structure instance, or raise StructureFormatError.
        """
        from diffpy.structure.parsers import getParser
        ofmts = self._getOrderedFormats()
        stru = None
        # try all parsers in sequence
        parsers_emsgs = []
        for fmt in ofmts:
            p = getParser(fmt, **self.pkw)
            try:
                pmethod = getattr(p, method)
                stru = pmethod(*args, **kwargs)
                self.format = fmt
                break
            except StructureFormatError as err:
                parsers_emsgs.append("%s: %s" % (fmt, err))
            except NotImplementedError:
                pass
        if stru is None:
            emsg = "\n".join([
                "Unknown or invalid structure format.",
                "Errors per each tested structure format:"] + parsers_emsgs)
            raise StructureFormatError(emsg)
        self.__dict__.update(p.__dict__)
        return stru
예제 #6
0
    def read(self, filename, format='auto'):
        """Load structure from a file, any original data become lost.

        filename -- file to be loaded
        format   -- all structure formats are defined in parsers submodule,
                    when format == 'auto' all parsers are tried one by one

        Return instance of data Parser used to process file.  This
        can be inspected for information related to particular format.
        """
        import diffpy.structure
        import diffpy.structure.parsers
        getParser = diffpy.structure.parsers.getParser
        p = getParser(format)
        new_structure = p.parseFile(filename)
        # reinitialize data after successful parsing
        # avoid calling __init__ from a derived class
        Structure.__init__(self)
        if new_structure is not None:
            self.__dict__.update(new_structure.__dict__)
            self[:] = new_structure
        if not self.title:
            import os.path
            tailname = os.path.basename(filename)
            tailbase = os.path.splitext(tailname)[0]
            self.title = tailbase
        return p
예제 #7
0
def loadStructure(filename, fmt='auto', **kw):
    """
    Load new structure object from the specified file.

    Parameters
    ----------

    filename : str
        Path to the file to be loaded.
    fmt : str, optional
        Format of the structure file such as 'cif' or 'xyz'.  Must be
        one of the formats listed by the `parsers.inputFormats` function.
        When 'auto', all supported formats are tried in a sequence.
    kw : misc, optional
        Extra keyword arguments that are passed to `parsers.getParser`
        function.  These configure the dedicated Parser object that
        is used to read content in filename.

    Returns
    -------
    stru : `Structure`, `PDFFitStructure`
        The new Structure object loaded from the specified file.
        Return a more specific PDFFitStructure type for 'pdffit'
        and 'discus' formats.
    """
    from diffpy.structure.parsers import getParser
    p = getParser(fmt, **kw)
    rv = p.parseFile(filename)
    return rv
예제 #8
0
    def writeStr(self, format):
        """return string representation of the structure in specified format

        Note: available structure formats can be obtained by:
            from parsers import formats
        """
        from diffpy.structure.parsers import getParser
        p = getParser(format)
        s = p.tostring(self)
        return s
예제 #9
0
    def writeStr(self, format):
        """return string representation of the structure in specified format

        Note: available structure formats can be obtained by:
            from parsers import formats
        """
        from diffpy.structure.parsers import getParser
        p = getParser(format)
        s = p.tostring(self)
        return s
예제 #10
0
def loadCrystalStructureAdapter(filename):
    from diffpy.srreal.structureconverters import _fetchDiffPyStructureData
    fullpath = datafile(filename)
    pcif = getParser('cif')
    stru = pcif.parseFile(fullpath)
    asu = stru[:0] + pcif.asymmetric_unit
    adpt = CrystalStructureAdapter()
    _fetchDiffPyStructureData(adpt, asu)
    for op in pcif.spacegroup.iter_symops():
        adpt.addSymOp(op.R, op.t)
    return adpt
예제 #11
0
def loadCrystalStructureAdapter(filename):
    from diffpy.srreal.structureconverters import _fetchDiffPyStructureData
    fullpath = datafile(filename)
    pcif = getParser('cif')
    stru = pcif.parseFile(fullpath)
    asu = stru[:0] + pcif.asymmetric_unit
    adpt = CrystalStructureAdapter()
    _fetchDiffPyStructureData(adpt, asu)
    for op in pcif.spacegroup.iter_symops():
        adpt.addSymOp(op.R, op.t)
    return adpt
예제 #12
0
    def write(self, filename, format):
        """Save structure to file in the specified format

        No return value.

        Note: available structure formats can be obtained by:
            from parsers import formats
        """
        from diffpy.structure.parsers import getParser
        p = getParser(format)
        p.filename = filename
        s = p.tostring(self)
        with codecs.open(filename, 'w', encoding='UTF-8') as fp:
            fp.write(s)
        return
예제 #13
0
    def write(self, filename, format):
        """Save structure to file in the specified format

        No return value.

        Note: available structure formats can be obtained by:
            from parsers import formats
        """
        from diffpy.structure.parsers import getParser
        p = getParser(format)
        p.filename = filename
        s = p.tostring(self)
        with codecs.open(filename, 'w', encoding='UTF-8') as fp:
            fp.write(s)
        return
예제 #14
0
    def readStr(self, s, format='auto'):
        """Load structure from a string, any original data become lost.

        s        -- string with structure definition
        format   -- all structure formats are defined in parsers submodule,
                    when format == 'auto' all parsers are tried one by one

        Return instance of data Parser used to process input string.  This
        can be inspected for information related to particular format.
        """
        from diffpy.structure.parsers import getParser
        p = getParser(format)
        new_structure = p.parse(s)
        # reinitialize data after successful parsing
        # avoid calling __init__ from a derived class
        Structure.__init__(self)
        if new_structure is not None:
            self.__dict__.update(new_structure.__dict__)
            self[:] = new_structure
        return p
예제 #15
0
    def readStr(self, s, format='auto'):
        """Load structure from a string, any original data become lost.

        s        -- string with structure definition
        format   -- all structure formats are defined in parsers submodule,
                    when format == 'auto' all parsers are tried one by one

        Return instance of data Parser used to process input string.  This
        can be inspected for information related to particular format.
        """
        from diffpy.structure.parsers import getParser
        p = getParser(format)
        new_structure = p.parse(s)
        # reinitialize data after successful parsing
        # avoid calling __init__ from a derived class
        Structure.__init__(self)
        if new_structure is not None:
            self.__dict__.update(new_structure.__dict__)
            self[:] = new_structure
        return p
예제 #16
0
def makerecipe(structure_file, data_file):
    """
    Basic function for creating and properly constraining a fit recipe.

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

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

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

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

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

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

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

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

    # Return the Fit Recipe object to be optimized
    return recipe
예제 #17
0
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 = Path("res")
    fitdir = Path("fit")
    figdir = Path("fig")

    folders = [resdir, fitdir, figdir]

    # Loop over all folders
    for folder in folders:

        # If the folder does not exist...
        if not folder.exists():
            # ...then we create it.
            folder.mkdir()

    # Let the user know what fit we are running by printing to terminal.
    basename = FIT_ID
    print(f"\n{basename}\n")

    # Establish the full location of the two datasets.
    xray_data = DPATH / XRAY_GR_NAME
    nuetron_data = DPATH / NEUTRON_GR_NAME

    # Establish the location of the cif file with the structure of interest
    # and load it into a diffpy structure object.
    strudir = DPATH
    cif_file = strudir / CIF_NAME

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

    # Initialize the Fit Recipe by giving it this diffpy structure
    # as well as the path to the data file.
    # Here we use a new function, which takes both datasets.
    recipe = makerecipe_coref(cif_file, xray_data, nuetron_data)

    # We first want to add two scale parameters to our fit recipe,
    # one for each dataset.
    recipe.addVar(recipe.xray.s1, XRAY_SCALE_I, tag="scale")
    recipe.addVar(recipe.neutron.s2, NEUTRON_SCALE_I, tag="scale")

    # Let's set the calculation range!
    # Here we use a loop to make it easier to edit both ranges.
    for cont in recipe._contributions.values():
        cont.profile.setCalculationRange(xmin=PDF_RMIN, xmax=PDF_RMAX, dx=PDF_RSTEP)


    # assign Q_max and Q_min, all part of the PDF Generator object.
    # It's possible that the PDFParse function we used above
    # already parsed out ths information, but in case it didn't, we set it
    # explicitly again here.
    # We do it for both neutron and PDF configurations
    recipe.xray.xray_G.setQmax(XRAY_QMAX)
    recipe.xray.xray_G.setQmin(XRAY_QMIN)
    recipe.neutron.neutron_G.setQmax(NEUTRON_QMAX)
    recipe.neutron.neutron_G.setQmin(NEUTRON_QMAX)

    # Initialize and add the instrument parameters, Q_damp and Q_broad, and
    # delta and instrumental parameters to Fit Recipe.
    # We give them unique names, and tag them with our choice of relevant strings.
    # Again, two datasets means we need to do this for each.
    recipe.addVar(recipe.xray.xray_G.delta2,
                  name="Ni_Delta2",
                  value=DELTA2_I,
                  tag="d2")

    recipe.constrain(recipe.neutron.neutron_G.delta2,
                  "Ni_Delta2")

    recipe.addVar(recipe.xray.xray_G.qdamp,
                  name="xray_Calib_Qdamp",
                  value=XRAY_QDAMP_I,
                  tag="inst")

    recipe.addVar(recipe.xray.xray_G.qbroad,
                  name="xray_Calib_Qbroad",
                  value=XRAY_QBROAD_I,
                  tag="inst")

    recipe.addVar(recipe.neutron.neutron_G.qdamp,
                  name="neutron_Calib_Qdamp",
                  value=NEUTRON_QDAMP_I,
                  tag="inst")

    recipe.addVar(recipe.neutron.neutron_G.qbroad,
                  name="neutron_Calib_Qbroad",
                  value=NEUTRON_QBROAD_I,
                  tag="inst")

    # Configure some additional fit variables pertaining to symmetry.
    # We can use the srfit function constrainAsSpaceGroup to constrain
    # the lattice and ADP parameters according to the Fm-3m space group.
    # First we establish the relevant parameters, then we cycle through
    # the parameters and activate and tag them.
    # We must explicitly set the ADP parameters, because in this case, the
    # CIF had no ADP data.
    from diffpy.srfit.structure import constrainAsSpaceGroup

    # Create the symmetry distinct parameter sets, and constrain them
    # in the generator.
    neutron_spacegroupparams = constrainAsSpaceGroup(recipe.neutron.neutron_G.phase,
                                                     space_group)
    xray_spacegroupparams = constrainAsSpaceGroup(recipe.xray.xray_G.phase,
                                                  space_group)

    # Loop over all the symmetry distinct lattice parameters and add
    # them to the recipe.
    # We give them unique names, and tag them with our choice of a relevant string.
    for xray_par, neutron_par in zip(xray_spacegroupparams.latpars, neutron_spacegroupparams.latpars):
        recipe.addVar(xray_par,
                      value=CUBICLAT_I,
                      name="fcc_Lat",
                      tag="lat")
        recipe.constrain(neutron_par,
                         "fcc_Lat")

    # Loop over all the symmetry distinct ADPs and add
    # them to the recipe.
    # We give them unique names, and tag them with our choice of a relevant string.
    for xray_par, neutron_par in zip(xray_spacegroupparams.adppars, neutron_spacegroupparams.adppars):
        recipe.addVar(xray_par,
                      value=UISO_I,
                      name="fcc_ADP",
                      tag="adp")
        recipe.constrain(neutron_par,
                         "fcc_ADP")

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

    # During the optimization, we fix and free parameters sequentially
    # as you would in PDFgui. This leads to more stability in the refinement.
    # We first fix all variables. "all" is a tag which incorporates
    # every parameter.
    recipe.fix("all")

    # Here will will set the weight of each contribution. In this case, we give each equal weight
    conts = list(recipe._contributions.values())

    for cont in conts:
        recipe.setWeight(cont, 1.0/len(conts))

    # We then run a fit using the SciPy function "least_squares" which
    # takes as its arguments the function to be optimized, here recipe.residual,
    # as well as initial values for the fitted parameters, provided by
    # recipe.values. The x_scale="jac" argument is an optional argument
    # that provides for a bit more stability in the refinement.
    # "least_squares" is a bit more robust than "leastsq,"
    # which is another optimization function provided by SciPy.
    # "least_squares" supports bounds on refined parameters,
    #  while "leastsq" does not.

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

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

    # We use the savetxt method of the profile to write a text file
    # containing the measured and fitted PDF to disk.
    # The file is named based on the basename we created earlier, and
    # written to the fitdir directory.
    profile = recipe.crystal.profile
    profile.savetxt(fitdir / (basename + ".fit"))

    # We use the FitResults function to parse out the results from
    # the optimized Fit Recipe.
    res = FitResults(recipe)

    # We print these results to the terminal.
    res.printResults()

    # We grab the fit Rw
    rw = res.rw

    # We use the saveResults method of FitResults to write a text file
    # containing the fitted parameters and fit quality indices to disk.
    # The file is named based on the basename we created earlier, and
    # written to the resdir directory.
    header = "crystal_HF.\n"
    res.saveResults(resdir / (basename + ".res"), header=header)

    # We use the plotresults function we created earlier to make a plot of
    # the measured, calculated, and difference curves. We show this
    # as an interactive window and then write a pdf file to disk.
    # The file is named based on the basename we created earlier, and
    # written to the figdir directory.
    plotresults(recipe, figdir / basename)

    # Let make a dictionary to hold our results. This way make reloading the
    # fit parameters easier later
    refined_dict = dict()

    refined_dict['rw'] = rw.item()

    recipe.free("all")
    # 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 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.
        if name not in refined_dict:
            refined_dict[name] = dict()
        refined_dict[name]["value"] = val.item()
        refined_dict[name]["uncert"] = unc.item()

    # Finally, let's write our dictionary to a yaml file!
    with open(basename + ".yml", 'w') as outfile:
        yaml.safe_dump(refined_dict, outfile)
예제 #18
0
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 = Path("res")
    fitdir = Path("fit")
    figdir = Path("fig")

    folders = [resdir, fitdir, figdir]

    # Loop over all folders
    for folder in folders:

        # If the folder does not exist...
        if not folder.exists():
            # ...then we create it.
            folder.mkdir()

    # Let the user know what fit we are running by printing to terminal.
    basename = FIT_ID
    print(f"\n{basename}\n")

    # Establish the full location of the data.
    data = DPATH / GR_NAME

    # Establish the location of the cif file with the structure of interest
    # and load it into a diffpy structure object.
    strudir = DPATH
    cif_file = strudir / CIF_NAME

    # Initialize the Fit Recipe by giving it this diffpy structure
    # as well as the path to the data file.

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

    # Initialize the Fit Recipe by giving it this diffpy structure
    # as well as the path to the data file.
    recipe = makerecipe(cif_file, data)

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

    # Add, initialize, and tag variables in the Fit Recipe object.
    # In this case we also add psize, which is the NP size.
    recipe.addVar(recipe.crystal.s1, SCALE_I, tag="scale")

    # Set an equation, based on your PDF generators. Here we add an extra layer
    # of complexity, incorporating "f" int our equation. This new term
    # incorporates damping to our PDF to model the effect of finite crystallite size.
    # In this case we use a function which models a spherical NP.
    from diffpy.srfit.pdf.characteristicfunctions import sphericalCF
    recipe.crystal.registerFunction(sphericalCF, name="f")
    recipe.crystal.setEquation("s1*G1*f")

    recipe.addVar(recipe.crystal.psize, PSIZE_I, tag="psize")

    # Initialize the instrument parameters, Q_damp and Q_broad, and
    # assign Q_max and Q_min.
    # Note, here we do not add the qdamp and qbroad parameters to the fit!!!
    # They are fixed here, because we refined them in the Ni standard fit!
    recipe.crystal.G1.qdamp.value = QDAMP_I
    recipe.crystal.G1.qbroad.value = QBROAD_I
    recipe.crystal.G1.setQmax(QMAX)
    recipe.crystal.G1.setQmin(QMIN)

    # Use the srfit function constrainAsSpaceGroup to constrain
    # the lattice and ADP parameters according to the Fm-3m space group.
    from diffpy.srfit.structure import constrainAsSpaceGroup
    spacegroupparams = constrainAsSpaceGroup(recipe.crystal.G1.phase,
                                             space_group)

    # Add and initialize delta, the lattice parameter, and a thermal parameter,
    # but not instrumental parameters to Fit Recipe.
    # The instrumental parameters will remain fixed at values obtained from
    # the Ni calibrant in our previous example. As we have not added them through
    # recipe.addVar, they cannot be refined.
    for par in spacegroupparams.latpars:
        recipe.addVar(par, value=CUBICLAT_I, name="fcc_Lat", tag="lat")

    for par in spacegroupparams.adppars:
        recipe.addVar(par, value=UISO_I, name="fcc_ADP", tag="adp")

    recipe.addVar(recipe.crystal.G1.delta2,
                  name="Pt_Delta2",
                  value=DELTA2_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

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

    recipe.fix("all")

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

    # We use the savetxt method of the profile to write a text file
    # containing the measured and fitted PDF to disk.
    # The file is named based on the basename we created earlier, and
    # written to the fitdir directory.
    profile = recipe.crystal.profile
    profile.savetxt(fitdir / (basename + ".fit"))

    # We use the FitResults function to parse out the results from
    # the optimized Fit Recipe.
    res = FitResults(recipe)

    # We print these results to the terminal.
    res.printResults()

    # We grab the fit Rw
    rw = res.rw

    # We use the saveResults method of FitResults to write a text file
    # containing the fitted parameters and fit quality indices to disk.
    # The file is named based on the basename we created earlier, and
    # written to the resdir directory.
    header = "crystal_HF.\n"
    res.saveResults(resdir / (basename + ".res"), header=header)

    # We use the plotresults function we created earlier to make a plot of
    # the measured, calculated, and difference curves. We show this
    # as an interactive window and then write a pdf file to disk.
    # The file is named based on the basename we created earlier, and
    # written to the figdir directory.
    plotresults(recipe, figdir / basename)

    # Let make a dictionary to hold our results. This way make reloading the
    # fit parameters easier later
    refined_dict = dict()

    refined_dict['rw'] = rw.item()

    # 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 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.
        if name not in refined_dict:
            refined_dict[name] = dict()
        refined_dict[name]["value"] = val.item()
        refined_dict[name]["uncert"] = unc.item()

    with open(basename + ".yml", 'w') as outfile:
        yaml.safe_dump(refined_dict, outfile)
예제 #19
0
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)